@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.js CHANGED
@@ -195,9 +195,9 @@ async function saveProjectFile(params) {
195
195
  }
196
196
  const targetPath = path3.join(resolvedTargetDir, filename);
197
197
  await fs3.writeFile(targetPath, buffer);
198
- const stat2 = await fs3.stat(targetPath);
198
+ const stat3 = await fs3.stat(targetPath);
199
199
  const uri = path3.posix.join("/", relativeDirectory, filename);
200
- return { uri, name: filename, size: stat2.size };
200
+ return { uri, name: filename, size: stat3.size };
201
201
  }
202
202
  var fs3, path3, import_core23, BINARY_EXTENSIONS, ProjectFileValidationError;
203
203
  var init_workspace_file_service = __esm({
@@ -1040,18 +1040,18 @@ function extractText(msg) {
1040
1040
  }
1041
1041
  return null;
1042
1042
  }
1043
- var import_zod6, logger4, wechatConfigSchema, MAX_RECONNECT_DELAY_MS, BASE_RECONNECT_DELAY_MS, HEARTBEAT_INTERVAL_MS, MSG_TYPE_USER, MSG_ITEM_TEXT, activeConnections2, seenClientIds, wechatChannelAdapter;
1043
+ var import_zod8, logger5, wechatConfigSchema, MAX_RECONNECT_DELAY_MS, BASE_RECONNECT_DELAY_MS, HEARTBEAT_INTERVAL_MS, MSG_TYPE_USER, MSG_ITEM_TEXT, activeConnections2, seenClientIds, wechatChannelAdapter;
1044
1044
  var init_WechatChannelAdapter = __esm({
1045
1045
  "src/channels/wechat/WechatChannelAdapter.ts"() {
1046
1046
  "use strict";
1047
- import_zod6 = require("zod");
1047
+ import_zod8 = require("zod");
1048
1048
  init_wechat_client();
1049
1049
  init_context_store();
1050
1050
  init_Logger();
1051
- logger4 = new Logger({ serviceName: "lattice/gateway/wechat" });
1052
- wechatConfigSchema = import_zod6.z.object({
1053
- botToken: import_zod6.z.string(),
1054
- uin: import_zod6.z.string().optional()
1051
+ logger5 = new Logger({ serviceName: "lattice/gateway/wechat" });
1052
+ wechatConfigSchema = import_zod8.z.object({
1053
+ botToken: import_zod8.z.string(),
1054
+ uin: import_zod8.z.string().optional()
1055
1055
  });
1056
1056
  MAX_RECONNECT_DELAY_MS = 3e4;
1057
1057
  BASE_RECONNECT_DELAY_MS = 1e3;
@@ -1099,7 +1099,7 @@ var init_WechatChannelAdapter = __esm({
1099
1099
  const senderId = replyTarget.rawTarget.senderId;
1100
1100
  const contextToken = getContextToken(installation.id, senderId);
1101
1101
  if (!contextToken) {
1102
- logger4.warn("WeChat context token expired, cannot send reply", {
1102
+ logger5.warn("WeChat context token expired, cannot send reply", {
1103
1103
  installationId: installation.id,
1104
1104
  senderId
1105
1105
  });
@@ -1117,14 +1117,14 @@ var init_WechatChannelAdapter = __esm({
1117
1117
  async connect(installation, deps) {
1118
1118
  const { id: installationId, tenantId, config } = installation;
1119
1119
  if (!config.botToken) {
1120
- logger4.warn("WeChat installation missing botToken, skipping", { installationId });
1120
+ logger5.warn("WeChat installation missing botToken, skipping", { installationId });
1121
1121
  return;
1122
1122
  }
1123
1123
  if (activeConnections2.has(installationId)) {
1124
- logger4.warn("WeChat polling already running for installation, skipping", { installationId });
1124
+ logger5.warn("WeChat polling already running for installation, skipping", { installationId });
1125
1125
  return;
1126
1126
  }
1127
- logger4.info("WeChat polling starting", { installationId, tenantId });
1127
+ logger5.info("WeChat polling starting", { installationId, tenantId });
1128
1128
  const abortController = new AbortController();
1129
1129
  const heartbeatTimer = setInterval(() => {
1130
1130
  const state2 = activeConnections2.get(installationId);
@@ -1134,7 +1134,7 @@ var init_WechatChannelAdapter = __esm({
1134
1134
  }
1135
1135
  const elapsed = Date.now() - state2.lastActivity;
1136
1136
  if (elapsed > HEARTBEAT_INTERVAL_MS * 2) {
1137
- logger4.error("WeChat polling heartbeat lost \u2014 no activity", {
1137
+ logger5.error("WeChat polling heartbeat lost \u2014 no activity", {
1138
1138
  installationId,
1139
1139
  elapsedMs: elapsed
1140
1140
  });
@@ -1153,7 +1153,7 @@ var init_WechatChannelAdapter = __esm({
1153
1153
  let reconnectDelay = BASE_RECONNECT_DELAY_MS;
1154
1154
  const scheduleNextPoll = () => {
1155
1155
  poll().catch((err) => {
1156
- logger4.error("WeChat poll iteration crashed", {
1156
+ logger5.error("WeChat poll iteration crashed", {
1157
1157
  installationId,
1158
1158
  error: err instanceof Error ? err.message : String(err)
1159
1159
  });
@@ -1166,7 +1166,7 @@ var init_WechatChannelAdapter = __esm({
1166
1166
  const poll = async () => {
1167
1167
  const currentState = activeConnections2.get(installationId);
1168
1168
  if (!currentState || currentState.abortController.signal.aborted) {
1169
- logger4.info("WeChat polling aborted", { installationId });
1169
+ logger5.info("WeChat polling aborted", { installationId });
1170
1170
  return;
1171
1171
  }
1172
1172
  try {
@@ -1179,7 +1179,7 @@ var init_WechatChannelAdapter = __esm({
1179
1179
  reconnectDelay = BASE_RECONNECT_DELAY_MS;
1180
1180
  currentState.lastActivity = Date.now();
1181
1181
  if (result.msgs.length > 0) {
1182
- logger4.info("WeChat poll received messages", {
1182
+ logger5.info("WeChat poll received messages", {
1183
1183
  installationId,
1184
1184
  count: result.msgs.length
1185
1185
  });
@@ -1196,7 +1196,7 @@ var init_WechatChannelAdapter = __esm({
1196
1196
  if (inbound && router) {
1197
1197
  const dispatchResult = await router.dispatch(inbound);
1198
1198
  if (!dispatchResult.success) {
1199
- logger4.warn("WeChat dispatch failed", {
1199
+ logger5.warn("WeChat dispatch failed", {
1200
1200
  installationId,
1201
1201
  error: dispatchResult.error?.message
1202
1202
  });
@@ -1205,32 +1205,32 @@ var init_WechatChannelAdapter = __esm({
1205
1205
  }
1206
1206
  } catch (err) {
1207
1207
  if (err instanceof Error && err.name === "AbortError") {
1208
- logger4.info("WeChat poll aborted by signal", { installationId });
1208
+ logger5.info("WeChat poll aborted by signal", { installationId });
1209
1209
  return;
1210
1210
  }
1211
- logger4.error("WeChat poll error", {
1211
+ logger5.error("WeChat poll error", {
1212
1212
  installationId,
1213
1213
  error: err instanceof Error ? err.message : String(err)
1214
1214
  });
1215
- await new Promise((resolve3) => setTimeout(resolve3, reconnectDelay));
1215
+ await new Promise((resolve4) => setTimeout(resolve4, reconnectDelay));
1216
1216
  reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
1217
1217
  }
1218
1218
  scheduleNextPoll();
1219
1219
  };
1220
1220
  scheduleNextPoll();
1221
- logger4.info("WeChat polling started", { installationId });
1221
+ logger5.info("WeChat polling started", { installationId });
1222
1222
  },
1223
1223
  async disconnect(installationId) {
1224
1224
  const state = activeConnections2.get(installationId);
1225
1225
  if (!state) {
1226
- logger4.warn("WeChat polling not running, nothing to disconnect", { installationId });
1226
+ logger5.warn("WeChat polling not running, nothing to disconnect", { installationId });
1227
1227
  return;
1228
1228
  }
1229
- logger4.info("WeChat polling disconnecting", { installationId });
1229
+ logger5.info("WeChat polling disconnecting", { installationId });
1230
1230
  state.abortController.abort();
1231
1231
  clearInterval(state.heartbeatTimer);
1232
1232
  activeConnections2.delete(installationId);
1233
- logger4.info("WeChat polling disconnected", { installationId });
1233
+ logger5.info("WeChat polling disconnected", { installationId });
1234
1234
  }
1235
1235
  };
1236
1236
  }
@@ -1895,12 +1895,12 @@ function agentMessage(text) {
1895
1895
  parts: [{ kind: "text", text }]
1896
1896
  };
1897
1897
  }
1898
- var import_uuid10, import_core55, import_protocols8, _AxiomAgentExecutor, AxiomAgentExecutor;
1898
+ var import_uuid10, import_core56, import_protocols8, _AxiomAgentExecutor, AxiomAgentExecutor;
1899
1899
  var init_AxiomAgentExecutor = __esm({
1900
1900
  "src/services/a2a/AxiomAgentExecutor.ts"() {
1901
1901
  "use strict";
1902
1902
  import_uuid10 = require("uuid");
1903
- import_core55 = require("@axiom-lattice/core");
1903
+ import_core56 = require("@axiom-lattice/core");
1904
1904
  import_protocols8 = require("@axiom-lattice/protocols");
1905
1905
  init_a2aFileRefs();
1906
1906
  init_task_file_refs();
@@ -1987,7 +1987,7 @@ var init_AxiomAgentExecutor = __esm({
1987
1987
  await this.taskStore.save(initialTask);
1988
1988
  bus.publish(initialTask);
1989
1989
  try {
1990
- const agent = import_core55.agentInstanceManager.getAgent({
1990
+ const agent = import_core56.agentInstanceManager.getAgent({
1991
1991
  assistant_id: this.scope.assistantId,
1992
1992
  thread_id: threadId,
1993
1993
  tenant_id: this.scope.tenantId,
@@ -2137,7 +2137,7 @@ ${formatFileRefsSection(inputFiles)}` : text;
2137
2137
  const threadId = task ? asString2(task.metadata?.threadId) : void 0;
2138
2138
  if (threadId) {
2139
2139
  try {
2140
- const agent = import_core55.agentInstanceManager.getAgent({
2140
+ const agent = import_core56.agentInstanceManager.getAgent({
2141
2141
  assistant_id: this.scope.assistantId,
2142
2142
  thread_id: threadId,
2143
2143
  tenant_id: this.scope.tenantId,
@@ -2499,13 +2499,13 @@ function isAsyncGenerator(value) {
2499
2499
  }
2500
2500
  function registerA2AStandardRoutes(app2, deps) {
2501
2501
  const authService = deps.authService;
2502
- const getTaskStore = deps.getTaskStoreLattice ?? (() => (0, import_core56.getStoreLattice)("default", "task").store);
2503
- const getTaskWorkItemStore2 = deps.getTaskWorkItemStore ?? (() => (0, import_core56.getStoreLattice)("default", "taskWorkItem").store);
2504
- const getAssistantStore3 = deps.getAssistantStore ?? (() => (0, import_core56.getStoreLattice)("default", "assistant").store);
2505
- const getProjectStore2 = deps.getProjectStore ?? (() => (0, import_core56.getStoreLattice)("default", "project").store);
2502
+ const getTaskStore = deps.getTaskStoreLattice ?? (() => (0, import_core57.getStoreLattice)("default", "task").store);
2503
+ const getTaskWorkItemStore2 = deps.getTaskWorkItemStore ?? (() => (0, import_core57.getStoreLattice)("default", "taskWorkItem").store);
2504
+ const getAssistantStore3 = deps.getAssistantStore ?? (() => (0, import_core57.getStoreLattice)("default", "assistant").store);
2505
+ const getProjectStore2 = deps.getProjectStore ?? (() => (0, import_core57.getStoreLattice)("default", "project").store);
2506
2506
  const getTenantStore = deps.getTenantStore ?? (() => {
2507
2507
  try {
2508
- return (0, import_core56.getStoreLattice)("default", "tenant").store;
2508
+ return (0, import_core57.getStoreLattice)("default", "tenant").store;
2509
2509
  } catch {
2510
2510
  return void 0;
2511
2511
  }
@@ -2520,10 +2520,10 @@ function registerA2AStandardRoutes(app2, deps) {
2520
2520
  const tenantStore = getTenantStore();
2521
2521
  if (!tenantStore) return null;
2522
2522
  const tenants = await tenantStore.getAllTenants();
2523
- for (const tenant of tenants) {
2524
- if (tenant.id === tenantId) continue;
2523
+ for (const tenant2 of tenants) {
2524
+ if (tenant2.id === tenantId) continue;
2525
2525
  const found = await getAssistantStore3().getAssistantById(
2526
- tenant.id,
2526
+ tenant2.id,
2527
2527
  assistantId
2528
2528
  );
2529
2529
  if (found) return found;
@@ -2597,7 +2597,7 @@ function registerA2AStandardRoutes(app2, deps) {
2597
2597
  const taskStore = new A2ATaskStoreAdapter({
2598
2598
  taskStore: internalTaskStore,
2599
2599
  workItemStore,
2600
- lifecycle: (0, import_core56.createTaskLifecycleService)({
2600
+ lifecycle: (0, import_core57.createTaskLifecycleService)({
2601
2601
  taskStore: internalTaskStore,
2602
2602
  workItemStore
2603
2603
  }),
@@ -2716,7 +2716,7 @@ data: ${JSON.stringify(errorResponse)}
2716
2716
  const taskStore = new A2ATaskStoreAdapter({
2717
2717
  taskStore: internalTaskStore,
2718
2718
  workItemStore,
2719
- lifecycle: (0, import_core56.createTaskLifecycleService)({
2719
+ lifecycle: (0, import_core57.createTaskLifecycleService)({
2720
2720
  taskStore: internalTaskStore,
2721
2721
  workItemStore
2722
2722
  }),
@@ -2761,13 +2761,13 @@ data: ${JSON.stringify(data)}
2761
2761
  }
2762
2762
  );
2763
2763
  }
2764
- var import_uuid11, import_server2, import_core56, SSE_HEADERS;
2764
+ var import_uuid11, import_server2, import_core57, SSE_HEADERS;
2765
2765
  var init_a2a_standard = __esm({
2766
2766
  "src/routes/a2a-standard.ts"() {
2767
2767
  "use strict";
2768
2768
  import_uuid11 = require("uuid");
2769
2769
  import_server2 = require("@a2a-js/sdk/server");
2770
- import_core56 = require("@axiom-lattice/core");
2770
+ import_core57 = require("@axiom-lattice/core");
2771
2771
  init_A2AAuthService();
2772
2772
  init_A2AAgentCardBuilder();
2773
2773
  init_A2ATaskStoreAdapter();
@@ -2941,12 +2941,12 @@ var resources_exports = {};
2941
2941
  __export(resources_exports, {
2942
2942
  ResourceController: () => ResourceController
2943
2943
  });
2944
- var import_bcryptjs, import_core57, ResourceController;
2944
+ var import_bcryptjs, import_core58, ResourceController;
2945
2945
  var init_resources = __esm({
2946
2946
  "src/controllers/resources.ts"() {
2947
2947
  "use strict";
2948
2948
  import_bcryptjs = __toESM(require("bcryptjs"));
2949
- import_core57 = require("@axiom-lattice/core");
2949
+ import_core58 = require("@axiom-lattice/core");
2950
2950
  init_mime();
2951
2951
  ResourceController = class {
2952
2952
  constructor(deps) {
@@ -2961,14 +2961,14 @@ var init_resources = __esm({
2961
2961
  if (!workspaceId || !projectId) {
2962
2962
  return reply.status(400).send({ error: "x-workspace-id and x-project-id headers required" });
2963
2963
  }
2964
- const payload = (0, import_core57.createSharePayload)(
2964
+ const payload = (0, import_core58.createSharePayload)(
2965
2965
  tenantId,
2966
2966
  workspaceId,
2967
2967
  projectId,
2968
2968
  userId,
2969
2969
  body
2970
2970
  );
2971
- const token = (0, import_core57.generateToken)();
2971
+ const token = (0, import_core58.generateToken)();
2972
2972
  try {
2973
2973
  await this.deps.store.create({ ...payload, token });
2974
2974
  this.deps.logger.info(
@@ -3120,7 +3120,7 @@ var init_resources = __esm({
3120
3120
  resourcePath: subPath
3121
3121
  }, subPath, ext, request, reply);
3122
3122
  }
3123
- const volume = (0, import_core57.buildNamedVolumeName)("p", "project", tenantId, workspaceId, projectId);
3123
+ const volume = (0, import_core58.buildNamedVolumeName)("p", "project", tenantId, workspaceId, projectId);
3124
3124
  return this._resolveAndServe(
3125
3125
  volume,
3126
3126
  subPath,
@@ -3350,8 +3350,8 @@ function resolveLocalA2AWorkingDirectory(options) {
3350
3350
  }
3351
3351
  function validateWorkingDirectory(cwd) {
3352
3352
  try {
3353
- const stat2 = (0, import_node_fs.statSync)(cwd);
3354
- if (!stat2.isDirectory()) {
3353
+ const stat3 = (0, import_node_fs.statSync)(cwd);
3354
+ if (!stat3.isDirectory()) {
3355
3355
  return `Working directory is not a directory: ${cwd}`;
3356
3356
  }
3357
3357
  return null;
@@ -4125,8 +4125,8 @@ async function pipeAsyncIterableToSse(raw, stream, options = {}) {
4125
4125
  let closed = raw.destroyed || raw.writableEnded;
4126
4126
  let returned = false;
4127
4127
  let resolveClosed;
4128
- const closedPromise = new Promise((resolve3) => {
4129
- resolveClosed = resolve3;
4128
+ const closedPromise = new Promise((resolve4) => {
4129
+ resolveClosed = resolve4;
4130
4130
  });
4131
4131
  const returnConsumer = () => {
4132
4132
  if (returned) return;
@@ -4176,14 +4176,14 @@ async function pipeAsyncIterableToSse(raw, stream, options = {}) {
4176
4176
  async function writeSse(raw, value, closed) {
4177
4177
  if (raw.destroyed || raw.writableEnded) return false;
4178
4178
  if (raw.write(value)) return true;
4179
- return new Promise((resolve3) => {
4179
+ return new Promise((resolve4) => {
4180
4180
  let settled = false;
4181
4181
  const onDrain = () => finish(!raw.destroyed && !raw.writableEnded);
4182
4182
  const finish = (writable) => {
4183
4183
  if (settled) return;
4184
4184
  settled = true;
4185
4185
  raw.removeListener("drain", onDrain);
4186
- resolve3(writable);
4186
+ resolve4(writable);
4187
4187
  };
4188
4188
  raw.once("drain", onDrain);
4189
4189
  void closed.then(() => finish(false));
@@ -4395,7 +4395,7 @@ var recoverRun = async (request, reply) => {
4395
4395
  const { assistantId, threadId } = request.params;
4396
4396
  const tenant_id = request.headers["x-tenant-id"];
4397
4397
  const workspace_id = request.headers["x-workspace-id"] || "default";
4398
- const project_id = request.headers["x-project-id"] || "default";
4398
+ const project_id = request.headers["x-project-id"];
4399
4399
  const agent = import_core3.agentInstanceManager.getAgent({
4400
4400
  assistant_id: assistantId,
4401
4401
  thread_id: threadId,
@@ -4403,8 +4403,7 @@ var recoverRun = async (request, reply) => {
4403
4403
  workspace_id,
4404
4404
  project_id
4405
4405
  });
4406
- await agent.resumeTask();
4407
- const status = await agent.getRunStatus();
4406
+ const { status } = await agent.resumeTask();
4408
4407
  reply.status(200).send({
4409
4408
  success: true,
4410
4409
  threadId,
@@ -5291,6 +5290,7 @@ async function getHealth(request, reply) {
5291
5290
 
5292
5291
  // src/services/skill_service.ts
5293
5292
  var import_core10 = require("@axiom-lattice/core");
5293
+ var PLUGIN_SKILL_SOURCE = "builtin-plugin";
5294
5294
  function getTenantId4(request) {
5295
5295
  return request.user?.tenantId || request.headers["x-tenant-id"] || "default";
5296
5296
  }
@@ -5306,28 +5306,63 @@ function buildContext(request) {
5306
5306
  projectId: getProjectId(request)
5307
5307
  };
5308
5308
  }
5309
+ function pluginSkillToSkill(item, tenantId, content) {
5310
+ return {
5311
+ id: item.id,
5312
+ tenantId,
5313
+ name: item.name,
5314
+ description: item.description,
5315
+ version: item.version,
5316
+ pluginType: item.pluginType,
5317
+ resourcePaths: item.resourcePaths,
5318
+ source: PLUGIN_SKILL_SOURCE,
5319
+ readOnly: true,
5320
+ content,
5321
+ createdAt: /* @__PURE__ */ new Date(0),
5322
+ updatedAt: /* @__PURE__ */ new Date(0)
5323
+ };
5324
+ }
5309
5325
  var SkillService = class {
5310
5326
  constructor() {
5311
5327
  this.store = new import_core10.SandboxSkillStore();
5312
5328
  }
5313
5329
  async getAllSkills(request) {
5314
5330
  const tenantId = getTenantId4(request);
5315
- return this.store.getAllSkills(tenantId, buildContext(request));
5331
+ const storeSkills = await this.store.getAllSkills(tenantId, buildContext(request));
5332
+ const merged = /* @__PURE__ */ new Map();
5333
+ for (const skill of storeSkills) {
5334
+ merged.set(skill.id, skill);
5335
+ }
5336
+ for (const item of import_core10.PluginRegistry.listSkills()) {
5337
+ merged.set(item.id, pluginSkillToSkill(item, tenantId));
5338
+ }
5339
+ return Array.from(merged.values());
5316
5340
  }
5317
5341
  async getSkillById(request, id) {
5318
5342
  const tenantId = getTenantId4(request);
5343
+ const plugin = import_core10.PluginRegistry.getSkill(id);
5344
+ if (plugin) {
5345
+ const { definition } = plugin;
5346
+ const item = import_core10.PluginRegistry.listSkills().find((entry) => entry.id === id);
5347
+ if (item) {
5348
+ return pluginSkillToSkill(item, tenantId, definition.content);
5349
+ }
5350
+ }
5319
5351
  return this.store.getSkillById(tenantId, id, buildContext(request));
5320
5352
  }
5321
5353
  async createSkill(request, id, data) {
5322
5354
  const tenantId = getTenantId4(request);
5355
+ this.assertNotPluginSkill(id, "created");
5323
5356
  return this.store.createSkill(tenantId, id, data, buildContext(request));
5324
5357
  }
5325
5358
  async updateSkill(request, id, updates) {
5326
5359
  const tenantId = getTenantId4(request);
5360
+ this.assertNotPluginSkill(id, "modified");
5327
5361
  return this.store.updateSkill(tenantId, id, updates, buildContext(request));
5328
5362
  }
5329
5363
  async deleteSkill(request, id) {
5330
5364
  const tenantId = getTenantId4(request);
5365
+ this.assertNotPluginSkill(id, "deleted");
5331
5366
  return this.store.deleteSkill(tenantId, id, buildContext(request));
5332
5367
  }
5333
5368
  async searchByMetadata(request, key, value) {
@@ -5342,6 +5377,16 @@ var SkillService = class {
5342
5377
  const tenantId = getTenantId4(request);
5343
5378
  return this.store.filterByLicense(tenantId, license, buildContext(request));
5344
5379
  }
5380
+ /**
5381
+ * Guard against mutating a registered plugin skill.
5382
+ * Message style mirrors SandboxSkillStore built-in errors so the controller's
5383
+ * existing "built-in skill" -> 403 mapping applies.
5384
+ */
5385
+ assertNotPluginSkill(id, action) {
5386
+ if (import_core10.PluginRegistry.getSkill(id)) {
5387
+ throw new Error(`Skill "${id}" is a built-in skill and cannot be ${action}`);
5388
+ }
5389
+ }
5345
5390
  };
5346
5391
 
5347
5392
  // src/controllers/skills.ts
@@ -5356,6 +5401,11 @@ function serializeSkill(skill) {
5356
5401
  metadata: skill.metadata || {},
5357
5402
  content: skill.content,
5358
5403
  subSkills: skill.subSkills,
5404
+ source: skill.source,
5405
+ pluginType: skill.pluginType,
5406
+ version: skill.version,
5407
+ resources: skill.resourcePaths,
5408
+ readOnly: skill.readOnly,
5359
5409
  createdAt: skill.createdAt instanceof Date ? skill.createdAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
5360
5410
  updatedAt: skill.updatedAt instanceof Date ? skill.updatedAt.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
5361
5411
  };
@@ -6977,7 +7027,7 @@ var ExportImportService = class {
6977
7027
  const files = /* @__PURE__ */ new Map();
6978
7028
  let bundle;
6979
7029
  const gunzip = zlib.createGunzip();
6980
- await new Promise((resolve3, reject) => {
7030
+ await new Promise((resolve4, reject) => {
6981
7031
  let settled = false;
6982
7032
  let entryCount = 0;
6983
7033
  let declaredTotalBytes = 0;
@@ -7097,7 +7147,7 @@ var ExportImportService = class {
7097
7147
  parser.on("end", () => {
7098
7148
  if (settled) return;
7099
7149
  settled = true;
7100
- resolve3();
7150
+ resolve4();
7101
7151
  });
7102
7152
  gunzip.pipe(parser);
7103
7153
  gunzip.end(buffer);
@@ -7782,6 +7832,20 @@ var dataQuerySchema = {
7782
7832
  };
7783
7833
 
7784
7834
  // src/schemas/index.ts
7835
+ var downloadFolderSchema = {
7836
+ description: "Download a workspace project folder as a ZIP archive",
7837
+ tags: ["Workspace"],
7838
+ querystring: {
7839
+ type: "object",
7840
+ properties: {
7841
+ path: { type: "string", minLength: 1 },
7842
+ assistantId: { type: "string", minLength: 1 },
7843
+ tenantId: { type: "string", minLength: 1 }
7844
+ },
7845
+ required: ["path"],
7846
+ additionalProperties: false
7847
+ }
7848
+ };
7785
7849
  var getAllMemoryItemsSchema = {
7786
7850
  description: "Get all memory items for an assistant thread",
7787
7851
  tags: ["Memory"],
@@ -8380,6 +8444,7 @@ function registerSandboxProxyRoutes(app2) {
8380
8444
  // src/controllers/workspace.ts
8381
8445
  var fs4 = __toESM(require("fs/promises"));
8382
8446
  var path4 = __toESM(require("path"));
8447
+ var import_fs = require("fs");
8383
8448
  var os2 = __toESM(require("os"));
8384
8449
  var import_core24 = require("@axiom-lattice/core");
8385
8450
  var import_core25 = require("@axiom-lattice/core");
@@ -8388,6 +8453,87 @@ var import_uuid3 = require("uuid");
8388
8453
  init_mime();
8389
8454
  init_workspace_file_service();
8390
8455
  init_path_containment();
8456
+ var MAX_ZIP_ENTRIES = 1e4;
8457
+ var MAX_ZIP_FILE_SIZE = 100 * 1024 * 1024;
8458
+ var MAX_ZIP_TOTAL_SIZE = 512 * 1024 * 1024;
8459
+ var FolderDownloadError = class extends Error {
8460
+ constructor(statusCode, code, message) {
8461
+ super(message);
8462
+ this.statusCode = statusCode;
8463
+ this.code = code;
8464
+ this.name = "FolderDownloadError";
8465
+ }
8466
+ };
8467
+ function zipCrc32(data) {
8468
+ let crc = 4294967295;
8469
+ for (const byte of data) {
8470
+ crc ^= byte;
8471
+ for (let bit = 0; bit < 8; bit += 1) {
8472
+ crc = crc >>> 1 ^ 3988292384 & -(crc & 1);
8473
+ }
8474
+ }
8475
+ return (crc ^ 4294967295) >>> 0;
8476
+ }
8477
+ function createZip(entries) {
8478
+ const localParts = [];
8479
+ const centralParts = [];
8480
+ let offset = 0;
8481
+ for (const entry of entries) {
8482
+ const name = Buffer.from(entry.name, "utf8");
8483
+ const data = entry.data;
8484
+ const crc = zipCrc32(data);
8485
+ const local = Buffer.alloc(30 + name.length + data.length);
8486
+ local.writeUInt32LE(67324752, 0);
8487
+ local.writeUInt16LE(20, 4);
8488
+ local.writeUInt16LE(0, 6);
8489
+ local.writeUInt16LE(2048, 8);
8490
+ local.writeUInt16LE(0, 10);
8491
+ local.writeUInt16LE(0, 12);
8492
+ local.writeUInt32LE(crc, 14);
8493
+ local.writeUInt32LE(data.length, 18);
8494
+ local.writeUInt32LE(data.length, 22);
8495
+ local.writeUInt16LE(name.length, 26);
8496
+ local.writeUInt16LE(0, 28);
8497
+ name.copy(local, 30);
8498
+ data.copy(local, 30 + name.length);
8499
+ localParts.push(local);
8500
+ const central = Buffer.alloc(46 + name.length);
8501
+ central.writeUInt32LE(33639248, 0);
8502
+ central.writeUInt16LE(20, 4);
8503
+ central.writeUInt16LE(20, 6);
8504
+ central.writeUInt16LE(0, 8);
8505
+ central.writeUInt16LE(2048, 10);
8506
+ central.writeUInt16LE(0, 12);
8507
+ central.writeUInt16LE(0, 14);
8508
+ central.writeUInt32LE(crc, 16);
8509
+ central.writeUInt32LE(data.length, 20);
8510
+ central.writeUInt32LE(data.length, 24);
8511
+ central.writeUInt16LE(name.length, 28);
8512
+ central.writeUInt16LE(0, 30);
8513
+ central.writeUInt16LE(0, 32);
8514
+ central.writeUInt16LE(0, 34);
8515
+ central.writeUInt16LE(entry.directory ? 16 : 0, 36);
8516
+ central.writeUInt32LE(entry.directory ? 16 : 0, 38);
8517
+ central.writeUInt32LE(offset, 42);
8518
+ name.copy(central, 46);
8519
+ centralParts.push(central);
8520
+ offset += local.length;
8521
+ }
8522
+ const centralDirectory = Buffer.concat(centralParts);
8523
+ const end = Buffer.alloc(22);
8524
+ end.writeUInt32LE(101010256, 0);
8525
+ end.writeUInt16LE(0, 4);
8526
+ end.writeUInt16LE(0, 6);
8527
+ end.writeUInt16LE(entries.length, 8);
8528
+ end.writeUInt16LE(entries.length, 10);
8529
+ end.writeUInt32LE(centralDirectory.length, 12);
8530
+ end.writeUInt32LE(offset, 16);
8531
+ return Buffer.concat([...localParts, centralDirectory, end]);
8532
+ }
8533
+ var CAPABILITY_BUNDLE_CONFIG_MESSAGE = "Use /api/projects/:projectId/capability-bundles to update capability bundle IDs";
8534
+ function writesCapabilityBundleIds(config) {
8535
+ return config !== void 0 && Object.prototype.hasOwnProperty.call(config, "capabilityBundleIds");
8536
+ }
8391
8537
  var WorkspaceController = class {
8392
8538
  constructor() {
8393
8539
  this.workspaceStore = (0, import_core24.getStoreLattice)("default", "workspace").store;
@@ -8495,6 +8641,13 @@ var WorkspaceController = class {
8495
8641
  const tenantId = this.getTenantId(request);
8496
8642
  const { workspaceId } = request.params;
8497
8643
  const data = request.body;
8644
+ if (writesCapabilityBundleIds(data.config)) {
8645
+ return reply.status(400).send({
8646
+ success: false,
8647
+ code: "INVALID_BUNDLE_CONFIG",
8648
+ message: CAPABILITY_BUNDLE_CONFIG_MESSAGE
8649
+ });
8650
+ }
8498
8651
  const id = (0, import_uuid3.v4)();
8499
8652
  const project = await this.projectStore.createProject(
8500
8653
  tenantId,
@@ -8517,6 +8670,13 @@ var WorkspaceController = class {
8517
8670
  const tenantId = this.getTenantId(request);
8518
8671
  const { projectId } = request.params;
8519
8672
  const updates = request.body;
8673
+ if (writesCapabilityBundleIds(updates.config)) {
8674
+ return reply.status(400).send({
8675
+ success: false,
8676
+ code: "INVALID_BUNDLE_CONFIG",
8677
+ message: CAPABILITY_BUNDLE_CONFIG_MESSAGE
8678
+ });
8679
+ }
8520
8680
  const project = await this.projectStore.updateProject(
8521
8681
  tenantId,
8522
8682
  projectId,
@@ -8653,6 +8813,293 @@ var WorkspaceController = class {
8653
8813
  return reply.status(502).send({ success: false, error: `Download proxy error: ${message}` });
8654
8814
  }
8655
8815
  }
8816
+ async downloadFolder(request, reply) {
8817
+ const tenantId = this.getTenantId(request);
8818
+ const { workspaceId, projectId } = request.params;
8819
+ const folderPath = request.query.path;
8820
+ const assistantId = request.query.assistantId;
8821
+ if (!folderPath || folderPath.includes("\\") || folderPath.split("/").includes("..")) {
8822
+ return reply.status(400).send({
8823
+ success: false,
8824
+ error: "Invalid folder path",
8825
+ code: "INVALID_PATH"
8826
+ });
8827
+ }
8828
+ try {
8829
+ const storedWorkspace = await this.workspaceStore.getWorkspaceById(tenantId, workspaceId);
8830
+ if (!storedWorkspace) {
8831
+ throw new FolderDownloadError(404, "WORKSPACE_NOT_FOUND", "Workspace not found");
8832
+ }
8833
+ const project = await this.projectStore.getProjectById(tenantId, projectId);
8834
+ if (!project || project.workspaceId !== workspaceId) {
8835
+ throw new FolderDownloadError(404, "PROJECT_NOT_FOUND", "Project not found");
8836
+ }
8837
+ if (storedWorkspace.storageType === "sandbox") {
8838
+ const sandboxManager = (0, import_core26.getSandBoxManager)();
8839
+ const volumeConfig = {
8840
+ assistant_id: assistantId || "",
8841
+ thread_id: "",
8842
+ tenantId,
8843
+ workspaceId,
8844
+ projectId
8845
+ };
8846
+ const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
8847
+ if (!sandbox.packDirectory || !sandbox.file.deleteFile) {
8848
+ throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not support folder archive cleanup");
8849
+ }
8850
+ let packed;
8851
+ try {
8852
+ packed = await sandbox.packDirectory(folderPath);
8853
+ if (packed.size === void 0) {
8854
+ throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not report packed archive size");
8855
+ }
8856
+ if (!Number.isFinite(packed.size) || packed.size < 0 || packed.size > MAX_ZIP_TOTAL_SIZE) {
8857
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Packed folder exceeds download size limits");
8858
+ }
8859
+ const buffer = await sandbox.file.downloadFile({ file: packed.path });
8860
+ return reply.status(200).type("application/zip").header("Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(packed.filename)}`).send(buffer);
8861
+ } finally {
8862
+ if (packed) {
8863
+ try {
8864
+ await sandbox.file.deleteFile(packed.path);
8865
+ } catch (cleanupError) {
8866
+ console.warn("Failed to clean up packed sandbox folder archive", cleanupError);
8867
+ }
8868
+ }
8869
+ }
8870
+ }
8871
+ const { backend, workspace } = await this.getBackend(
8872
+ tenantId,
8873
+ workspaceId,
8874
+ projectId,
8875
+ assistantId,
8876
+ folderPath
8877
+ );
8878
+ if (!backend.lsInfo) {
8879
+ throw new FolderDownloadError(502, "BACKEND_UNSUPPORTED", "Backend does not support folder downloads");
8880
+ }
8881
+ let listEntries2;
8882
+ if (workspace.storageType !== "sandbox") {
8883
+ const root = filesystemRootDirectory(tenantId, workspaceId, projectId);
8884
+ const target = path4.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
8885
+ const contained = await resolvePathWithinRoot(root, target).catch((error) => {
8886
+ if (error instanceof Error && error.code === "ENOENT") {
8887
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
8888
+ }
8889
+ throw error;
8890
+ });
8891
+ let stat3;
8892
+ try {
8893
+ stat3 = await fs4.stat(contained.targetPath);
8894
+ } catch (error) {
8895
+ if (error instanceof Error && error.code === "ENOENT") {
8896
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
8897
+ }
8898
+ throw error;
8899
+ }
8900
+ if (!stat3.isDirectory()) {
8901
+ throw new FolderDownloadError(400, "NOT_A_FOLDER", "Path is not a folder");
8902
+ }
8903
+ } else if (folderPath) {
8904
+ const sandboxManager = (0, import_core26.getSandBoxManager)();
8905
+ const volumeConfig = {
8906
+ assistant_id: assistantId || "",
8907
+ thread_id: "",
8908
+ tenantId,
8909
+ workspaceId,
8910
+ projectId
8911
+ };
8912
+ const volume = await sandboxManager.getVolumeBackendForPath(volumeConfig, folderPath);
8913
+ if (!volume) {
8914
+ const sandbox = await sandboxManager.getSandboxFromConfig(volumeConfig);
8915
+ try {
8916
+ await sandbox.file.listPath(folderPath, { recursive: false });
8917
+ } catch (error) {
8918
+ const code = error instanceof Error ? error.code : void 0;
8919
+ const message = error instanceof Error ? error.message : String(error);
8920
+ if (code === "ENOENT" || /not found|no such file|enoent/i.test(message)) {
8921
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
8922
+ }
8923
+ throw error;
8924
+ }
8925
+ listEntries2 = async (directoryPath) => {
8926
+ const result = await sandbox.file.listPath(directoryPath, { recursive: false });
8927
+ return result.files;
8928
+ };
8929
+ } else {
8930
+ listEntries2 = async (directoryPath) => backend.lsInfo(directoryPath);
8931
+ }
8932
+ }
8933
+ if (!listEntries2) {
8934
+ listEntries2 = async (directoryPath) => backend.lsInfo(directoryPath);
8935
+ }
8936
+ const folderName = path4.basename(folderPath.replace(/\/$/, "")) || "folder";
8937
+ const prefix = `${folderName}/`;
8938
+ const entries = [{ name: prefix, data: Buffer.alloc(0), directory: true }];
8939
+ let totalSize = 0;
8940
+ const filesystemRoot = workspace.storageType === "sandbox" ? void 0 : filesystemRootDirectory(tenantId, workspaceId, projectId);
8941
+ const addFile = (archivePath, data) => {
8942
+ if (data.length > MAX_ZIP_FILE_SIZE || totalSize + data.length > MAX_ZIP_TOTAL_SIZE) {
8943
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
8944
+ }
8945
+ totalSize += data.length;
8946
+ entries.push({ name: archivePath, data, directory: false });
8947
+ };
8948
+ const collectFilesystem = async (directoryPath, archivePath) => {
8949
+ let children;
8950
+ try {
8951
+ children = await fs4.readdir(directoryPath, { withFileTypes: true });
8952
+ } catch (error) {
8953
+ if (error instanceof Error && error.code === "ENOENT") {
8954
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
8955
+ }
8956
+ throw error;
8957
+ }
8958
+ for (const child of children) {
8959
+ if (entries.length >= MAX_ZIP_ENTRIES) {
8960
+ throw new FolderDownloadError(400, "ENTRY_LIMIT", "Folder contains too many entries");
8961
+ }
8962
+ const childPath = path4.join(directoryPath, child.name);
8963
+ const archiveChildPath = `${archivePath}${child.name}`;
8964
+ let resolvedChild;
8965
+ try {
8966
+ resolvedChild = await resolvePathWithinRoot(filesystemRoot, childPath);
8967
+ } catch (error) {
8968
+ if (error instanceof Error && error.code === "ENOENT") {
8969
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
8970
+ }
8971
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains a path outside the project");
8972
+ }
8973
+ let childStat;
8974
+ try {
8975
+ childStat = await fs4.lstat(resolvedChild.targetPath);
8976
+ } catch (error) {
8977
+ if (error instanceof Error && error.code === "ENOENT") {
8978
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
8979
+ }
8980
+ throw error;
8981
+ }
8982
+ if (childStat.isSymbolicLink() || child.isSymbolicLink()) {
8983
+ throw new FolderDownloadError(400, "INVALID_PATH", "Symbolic links are not allowed in folder downloads");
8984
+ }
8985
+ if (child.isDirectory()) {
8986
+ entries.push({ name: `${archiveChildPath}/`, data: Buffer.alloc(0), directory: true });
8987
+ await collectFilesystem(resolvedChild.targetPath, `${archiveChildPath}/`);
8988
+ } else if (child.isFile()) {
8989
+ if (childStat.size > MAX_ZIP_FILE_SIZE || totalSize + childStat.size > MAX_ZIP_TOTAL_SIZE) {
8990
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
8991
+ }
8992
+ const flags = import_fs.constants.O_NOFOLLOW === void 0 ? import_fs.constants.O_RDONLY : import_fs.constants.O_RDONLY | import_fs.constants.O_NOFOLLOW;
8993
+ let handle3;
8994
+ try {
8995
+ handle3 = await fs4.open(resolvedChild.targetPath, flags);
8996
+ } catch (error) {
8997
+ if (error instanceof Error && error.code === "ENOENT") {
8998
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
8999
+ }
9000
+ throw error;
9001
+ }
9002
+ try {
9003
+ const openedStat = await handle3.stat();
9004
+ if (!openedStat.isFile() || openedStat.size > MAX_ZIP_FILE_SIZE || totalSize + openedStat.size > MAX_ZIP_TOTAL_SIZE) {
9005
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
9006
+ }
9007
+ const readLimit = MAX_ZIP_FILE_SIZE + 1;
9008
+ const dataBuffer = Buffer.alloc(Math.min(openedStat.size + 1, readLimit));
9009
+ let bytesRead = 0;
9010
+ while (bytesRead < dataBuffer.length) {
9011
+ const result = await handle3.read(
9012
+ dataBuffer,
9013
+ bytesRead,
9014
+ dataBuffer.length - bytesRead,
9015
+ bytesRead
9016
+ );
9017
+ if (result.bytesRead === 0) break;
9018
+ bytesRead += result.bytesRead;
9019
+ }
9020
+ const data = dataBuffer.subarray(0, bytesRead);
9021
+ const finalStat = await handle3.stat();
9022
+ if (finalStat.size > MAX_ZIP_FILE_SIZE || totalSize + finalStat.size > MAX_ZIP_TOTAL_SIZE || finalStat.size > data.length) {
9023
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
9024
+ }
9025
+ addFile(archiveChildPath, data);
9026
+ } finally {
9027
+ await handle3.close();
9028
+ }
9029
+ }
9030
+ }
9031
+ };
9032
+ const collect = async (currentPath, archivePath) => {
9033
+ let children;
9034
+ try {
9035
+ children = await listEntries2(currentPath);
9036
+ } catch (error) {
9037
+ const message = error instanceof Error ? error.message : String(error);
9038
+ if (/not found|enoent|no such file/i.test(message)) {
9039
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
9040
+ }
9041
+ throw error;
9042
+ }
9043
+ for (const child of children) {
9044
+ if (entries.length >= MAX_ZIP_ENTRIES) {
9045
+ throw new FolderDownloadError(400, "ENTRY_LIMIT", "Folder contains too many entries");
9046
+ }
9047
+ const childPath = child.path;
9048
+ if (childPath.includes("\\") || childPath.split("/").includes("..") || !childPath.startsWith(folderPath.endsWith("/") ? folderPath : `${folderPath}/`)) {
9049
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains an unsafe child path");
9050
+ }
9051
+ if (workspace.storageType !== "sandbox") {
9052
+ const root = filesystemRootDirectory(tenantId, workspaceId, projectId);
9053
+ const virtualChildPath = childPath.startsWith("/") ? childPath : `/${childPath}`;
9054
+ const target = path4.resolve(root, `.${virtualChildPath}`);
9055
+ try {
9056
+ await resolvePathWithinRoot(root, target);
9057
+ const childStat = await fs4.lstat(target);
9058
+ if (childStat.isSymbolicLink()) {
9059
+ throw new FolderDownloadError(400, "INVALID_PATH", "Symbolic links are not allowed in folder downloads");
9060
+ }
9061
+ } catch (error) {
9062
+ if (error instanceof FolderDownloadError) throw error;
9063
+ if (error instanceof Error && error.code === "ENOENT") {
9064
+ throw new FolderDownloadError(404, "FOLDER_NOT_FOUND", "Folder not found");
9065
+ }
9066
+ throw new FolderDownloadError(400, "INVALID_PATH", "Folder contains a path outside the project");
9067
+ }
9068
+ }
9069
+ const childName = path4.basename(childPath.replace(/\/$/, ""));
9070
+ const childArchivePath = `${archivePath}${childName}`;
9071
+ if (child.is_dir) {
9072
+ entries.push({ name: `${childArchivePath}/`, data: Buffer.alloc(0), directory: true });
9073
+ await collect(childPath, `${childArchivePath}/`);
9074
+ } else {
9075
+ const size = child.size ?? 0;
9076
+ if (size > MAX_ZIP_FILE_SIZE || totalSize + size > MAX_ZIP_TOTAL_SIZE) {
9077
+ throw new FolderDownloadError(400, "SIZE_LIMIT", "Folder exceeds download size limits");
9078
+ }
9079
+ const data = await backend.readBinary(childPath);
9080
+ addFile(childArchivePath, data);
9081
+ }
9082
+ }
9083
+ };
9084
+ if (workspace.storageType === "sandbox") {
9085
+ await collect(folderPath, prefix);
9086
+ } else {
9087
+ const root = filesystemRoot;
9088
+ const target = path4.resolve(root, `.${folderPath.startsWith("/") ? folderPath : `/${folderPath}`}`);
9089
+ await collectFilesystem(target, prefix);
9090
+ }
9091
+ const zip = createZip(entries);
9092
+ const downloadName = `${folderName}.zip`;
9093
+ return reply.status(200).type("application/zip").header("Content-Disposition", `attachment; filename*=UTF-8''${encodeURIComponent(downloadName)}`).send(zip);
9094
+ } catch (error) {
9095
+ 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));
9096
+ return reply.status(folderError.statusCode).send({
9097
+ success: false,
9098
+ error: folderError.message,
9099
+ code: folderError.code
9100
+ });
9101
+ }
9102
+ }
8656
9103
  /**
8657
9104
  * View a file inline in the browser (not download).
8658
9105
  * Returns file content with inline Content-Disposition for preview.
@@ -8945,6 +9392,11 @@ function registerWorkspaceRoutes(app2) {
8945
9392
  "/api/workspaces/:workspaceId/projects/:projectId/downloadfile",
8946
9393
  controller.downloadFile.bind(controller)
8947
9394
  );
9395
+ app2.get(
9396
+ "/api/workspaces/:workspaceId/projects/:projectId/downloadfolder",
9397
+ { schema: downloadFolderSchema },
9398
+ controller.downloadFolder.bind(controller)
9399
+ );
8948
9400
  app2.get(
8949
9401
  "/api/workspaces/:workspaceId/projects/:projectId/viewfile",
8950
9402
  controller.viewFile.bind(controller)
@@ -9169,7 +9621,7 @@ async function testDatabaseConnection(request, reply) {
9169
9621
  await db.connect();
9170
9622
  await db.listTables();
9171
9623
  const latency = Date.now() - startTime;
9172
- await new Promise((resolve3) => setTimeout(resolve3, 100));
9624
+ await new Promise((resolve4) => setTimeout(resolve4, 100));
9173
9625
  await db.disconnect();
9174
9626
  await import_core27.sqlDatabaseManager.removeDatabase(tenantId, testKey);
9175
9627
  return {
@@ -9986,6 +10438,7 @@ var import_uuid5 = require("uuid");
9986
10438
  var import_events = require("events");
9987
10439
  var import_core31 = require("@axiom-lattice/core");
9988
10440
  var import_uuid4 = require("uuid");
10441
+ var TERMINAL_PERSIST_ATTEMPTS = 2;
9989
10442
  function mapLogs(logs) {
9990
10443
  return logs.map((l) => ({
9991
10444
  timestamp: l.ts,
@@ -9997,18 +10450,42 @@ function mapLogs(logs) {
9997
10450
  var EvalRunner = class {
9998
10451
  constructor() {
9999
10452
  this.runs = /* @__PURE__ */ new Map();
10453
+ this.projectLeases = /* @__PURE__ */ new Map();
10000
10454
  this.eventEmitter = new import_events.EventEmitter();
10001
10455
  this.processStartedAt = /* @__PURE__ */ new Date();
10002
10456
  }
10003
10457
  getEventEmitter() {
10004
10458
  return this.eventEmitter;
10005
10459
  }
10460
+ reserveProjectDeletion(tenantId, projectId) {
10461
+ const leaseKey = `${tenantId}:${projectId}`;
10462
+ if (this.projectLeases.has(leaseKey) || [...this.runs.values()].some(
10463
+ (ctx) => ctx.tenantId === tenantId && ctx.projectId === projectId && !ctx.terminal.workerSettled
10464
+ )) return null;
10465
+ const leaseToken = Symbol(`delete:${leaseKey}`);
10466
+ this.projectLeases.set(leaseKey, leaseToken);
10467
+ return () => this.releaseProjectLease(leaseKey, leaseToken);
10468
+ }
10006
10469
  async startRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId) {
10470
+ const leaseKey = `${tenantId}:${projectId}`;
10471
+ if (this.projectLeases.has(leaseKey)) {
10472
+ throw new Error("A run is already in progress for this project");
10473
+ }
10474
+ const leaseToken = Symbol(leaseKey);
10475
+ this.projectLeases.set(leaseKey, leaseToken);
10476
+ try {
10477
+ return await this.startLeasedRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId, leaseKey, leaseToken);
10478
+ } catch (error) {
10479
+ this.releaseProjectLease(leaseKey, leaseToken);
10480
+ throw error;
10481
+ }
10482
+ }
10483
+ async startLeasedRun(tenantId, projectId, suiteIds, caseIds, runConfig, taskId, leaseKey, leaseToken) {
10007
10484
  const store2 = this.getEvalStore();
10008
10485
  const project = await store2.getProjectById(tenantId, projectId);
10009
10486
  if (!project) throw new Error("Project not found");
10010
10487
  for (const ctx of this.runs.values()) {
10011
- if (ctx.projectId === projectId) {
10488
+ if (ctx.projectId === projectId && !ctx.terminal.persisted) {
10012
10489
  throw new Error("A run is already in progress for this project");
10013
10490
  }
10014
10491
  }
@@ -10112,14 +10589,35 @@ var EvalRunner = class {
10112
10589
  };
10113
10590
  const abortController = new AbortController();
10114
10591
  const stats = { passed: 0, failed: 0, interrupted: 0, totalScore: 0 };
10592
+ const terminal = { claiming: false, persisted: false, workerSettled: false };
10593
+ const terminalUnavailable = () => terminal.claiming || terminal.persisted;
10594
+ const persistTerminal = async (update, event) => {
10595
+ if (terminalUnavailable()) return false;
10596
+ terminal.claiming = true;
10597
+ try {
10598
+ let lastError;
10599
+ for (let attempt = 0; attempt < TERMINAL_PERSIST_ATTEMPTS; attempt++) {
10600
+ try {
10601
+ const persistedRun = await store2.updateRunStatus(tenantId, runId, update);
10602
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
10603
+ lastError = void 0;
10604
+ break;
10605
+ } catch (error) {
10606
+ lastError = error;
10607
+ }
10608
+ }
10609
+ if (lastError) throw lastError;
10610
+ terminal.persisted = true;
10611
+ this.eventEmitter.emit(`run:${runId}`, event);
10612
+ return true;
10613
+ } finally {
10614
+ terminal.claiming = false;
10615
+ }
10616
+ };
10115
10617
  const onCaseComplete = async (result, suiteName) => {
10618
+ if (terminalUnavailable()) return;
10116
10619
  const passed = result.result?.pass ?? false;
10117
10620
  const score = result.result?.final_score ?? 0;
10118
- if (passed) stats.passed++;
10119
- else stats.failed++;
10120
- stats.totalScore += score;
10121
- if (result.interrupted) stats.interrupted++;
10122
- const completedCount = stats.passed + stats.failed;
10123
10621
  await store2.createRunResult(tenantId, runId, (0, import_uuid4.v4)(), {
10124
10622
  suiteName,
10125
10623
  caseId: result.caseId,
@@ -10137,13 +10635,22 @@ var EvalRunner = class {
10137
10635
  error: result.error,
10138
10636
  interrupted: result.interrupted
10139
10637
  });
10638
+ if (terminalUnavailable()) return;
10639
+ if (passed) stats.passed++;
10640
+ else stats.failed++;
10641
+ stats.totalScore += score;
10642
+ if (result.interrupted) stats.interrupted++;
10643
+ const completedCount = stats.passed + stats.failed;
10644
+ if (terminalUnavailable()) return;
10140
10645
  const avgScore = completedCount > 0 ? stats.totalScore / completedCount : 0;
10141
- await store2.updateRunStatus(tenantId, runId, {
10646
+ const persistedRun = await store2.updateRunStatus(tenantId, runId, {
10142
10647
  passedCases: stats.passed,
10143
10648
  failedCases: stats.failed,
10144
10649
  interruptedCases: stats.interrupted,
10145
10650
  avgScore
10146
10651
  });
10652
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
10653
+ if (terminalUnavailable()) return;
10147
10654
  this.eventEmitter.emit(`run:${runId}`, {
10148
10655
  type: "progress",
10149
10656
  runId,
@@ -10155,65 +10662,86 @@ var EvalRunner = class {
10155
10662
  const evalProject = new import_core31.LatticeEvalProject(projectConfig, onCaseComplete);
10156
10663
  const calibration = await evalProject.calibrateJudge();
10157
10664
  if (!calibration.ok && !calibration.bypassed) {
10158
- await store2.updateRunStatus(tenantId, runId, {
10159
- status: "failed",
10160
- error: `Judge calibration failed: ${calibration.reason}`,
10161
- completedAt: /* @__PURE__ */ new Date()
10162
- });
10163
10665
  throw new Error(`Judge calibration failed: ${calibration.reason}`);
10164
10666
  }
10165
10667
  if (calibration.bypassed) {
10166
10668
  console.warn(`[eval-runner] Judge calibration bypassed \u2014 judge may be unreliable: ${calibration.reason}`);
10167
10669
  }
10168
10670
  const { report } = await evalProject.runAllSuitesBatch(concurrency, abortController.signal);
10169
- if (!abortController.signal.aborted) {
10170
- const completedCount = stats.passed + stats.failed;
10171
- await store2.updateRunStatus(tenantId, runId, {
10172
- status: "completed",
10173
- completedAt: /* @__PURE__ */ new Date(),
10174
- passedCases: stats.passed,
10175
- failedCases: stats.failed,
10176
- interruptedCases: stats.interrupted,
10177
- avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0
10178
- });
10179
- this.eventEmitter.emit(`run:${runId}`, {
10180
- type: "completed",
10181
- runId,
10182
- data: { passed: stats.passed, failed: stats.failed, avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0 }
10183
- });
10184
- }
10671
+ const completedCount = stats.passed + stats.failed;
10672
+ await persistTerminal({
10673
+ status: "completed",
10674
+ completedAt: /* @__PURE__ */ new Date(),
10675
+ passedCases: stats.passed,
10676
+ failedCases: stats.failed,
10677
+ interruptedCases: stats.interrupted,
10678
+ avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0
10679
+ }, {
10680
+ type: "completed",
10681
+ runId,
10682
+ data: { passed: stats.passed, failed: stats.failed, avgScore: completedCount > 0 ? stats.totalScore / completedCount : 0 }
10683
+ });
10185
10684
  return { report };
10186
10685
  } catch (err) {
10187
10686
  const errorMsg = err.message;
10188
- if (!abortController.signal.aborted) {
10189
- await store2.updateRunStatus(tenantId, runId, {
10687
+ try {
10688
+ await persistTerminal({
10190
10689
  status: "failed",
10191
10690
  error: errorMsg,
10192
10691
  completedAt: /* @__PURE__ */ new Date()
10193
- });
10194
- this.eventEmitter.emit(`run:${runId}`, {
10692
+ }, {
10195
10693
  type: "error",
10196
10694
  runId,
10197
10695
  data: { message: errorMsg }
10198
10696
  });
10697
+ } catch {
10199
10698
  }
10200
10699
  throw err;
10201
10700
  } finally {
10202
- this.runs.delete(runId);
10701
+ terminal.workerSettled = true;
10702
+ if (terminal.persisted) {
10703
+ this.runs.delete(runId);
10704
+ this.releaseProjectLease(leaseKey, leaseToken);
10705
+ }
10203
10706
  }
10204
10707
  })();
10205
- this.runs.set(runId, { runId, projectId, tenantId, abortController, promise: runPromise, taskId });
10708
+ this.runs.set(runId, { runId, projectId, tenantId, abortController, promise: runPromise, terminal, leaseKey, leaseToken, taskId });
10206
10709
  runPromise.catch(() => {
10207
10710
  });
10208
10711
  return runId;
10209
10712
  }
10210
- async abortRun(runId) {
10713
+ async abortRun(runId, tenantId) {
10211
10714
  const ctx = this.runs.get(runId);
10212
- if (!ctx) return false;
10715
+ if (!ctx || tenantId !== void 0 && ctx.tenantId !== tenantId) return false;
10716
+ if (ctx.terminal.claiming || ctx.terminal.persisted) return false;
10213
10717
  ctx.abortController.abort();
10214
10718
  const store2 = this.getEvalStore();
10215
- await store2.updateRunStatus(ctx.tenantId, runId, { status: "aborted", completedAt: /* @__PURE__ */ new Date() });
10216
- return true;
10719
+ ctx.terminal.claiming = true;
10720
+ try {
10721
+ let lastError;
10722
+ for (let attempt = 0; attempt < TERMINAL_PERSIST_ATTEMPTS; attempt++) {
10723
+ try {
10724
+ const persistedRun = await store2.updateRunStatus(ctx.tenantId, runId, { status: "aborted", completedAt: /* @__PURE__ */ new Date() });
10725
+ if (!persistedRun) throw new Error(`Evaluation run ${runId} no longer exists`);
10726
+ lastError = void 0;
10727
+ break;
10728
+ } catch (error) {
10729
+ lastError = error;
10730
+ }
10731
+ }
10732
+ if (lastError) throw lastError;
10733
+ ctx.terminal.persisted = true;
10734
+ this.eventEmitter.emit(`run:${runId}`, {
10735
+ type: "error",
10736
+ runId,
10737
+ data: { message: "Run aborted" }
10738
+ });
10739
+ this.releaseProjectLease(ctx.leaseKey, ctx.leaseToken);
10740
+ if (ctx.terminal.workerSettled) this.runs.delete(runId);
10741
+ return true;
10742
+ } finally {
10743
+ ctx.terminal.claiming = false;
10744
+ }
10217
10745
  }
10218
10746
  async waitForRun(runId) {
10219
10747
  const ctx = this.runs.get(runId);
@@ -10221,12 +10749,25 @@ var EvalRunner = class {
10221
10749
  const { report } = await ctx.promise;
10222
10750
  return report;
10223
10751
  }
10224
- isRunning(runId) {
10225
- return this.runs.has(runId);
10752
+ /**
10753
+ * Whether the run can still be controlled or observed as a non-terminal run.
10754
+ * Acknowledged aborts return false even while their worker is unwinding.
10755
+ */
10756
+ isRunning(runId, tenantId) {
10757
+ const ctx = this.runs.get(runId);
10758
+ return ctx !== void 0 && !ctx.terminal.persisted && (tenantId === void 0 || ctx.tenantId === tenantId);
10759
+ }
10760
+ /** Whether the run's worker promise has not yet reached its finally block. */
10761
+ hasActiveExecution(runId, tenantId) {
10762
+ const ctx = this.runs.get(runId);
10763
+ return ctx !== void 0 && !ctx.terminal.workerSettled && (tenantId === void 0 || ctx.tenantId === tenantId);
10226
10764
  }
10227
10765
  getEvalStore() {
10228
10766
  return (0, import_core31.getStoreLattice)("default", "eval").store;
10229
10767
  }
10768
+ releaseProjectLease(leaseKey, leaseToken) {
10769
+ if (this.projectLeases.get(leaseKey) === leaseToken) this.projectLeases.delete(leaseKey);
10770
+ }
10230
10771
  };
10231
10772
  var evalRunner = new EvalRunner();
10232
10773
 
@@ -10241,6 +10782,18 @@ function getTenantId12(request) {
10241
10782
  function getEvalStore() {
10242
10783
  return (0, import_core32.getStoreLattice)("default", "eval").store;
10243
10784
  }
10785
+ async function getOwnedSuite(store2, tenantId, projectId, suiteId) {
10786
+ const project = await store2.getProjectById(tenantId, projectId);
10787
+ if (!project) return null;
10788
+ const suite = await store2.getSuiteById(tenantId, suiteId);
10789
+ return suite?.projectId === projectId ? suite : null;
10790
+ }
10791
+ async function getOwnedCase(store2, tenantId, projectId, suiteId, caseId) {
10792
+ const suite = await getOwnedSuite(store2, tenantId, projectId, suiteId);
10793
+ if (!suite) return null;
10794
+ const evalCase = await store2.getCaseById(tenantId, caseId);
10795
+ return evalCase?.suiteId === suiteId ? evalCase : null;
10796
+ }
10244
10797
  async function createProject(request, reply) {
10245
10798
  try {
10246
10799
  const tenantId = getTenantId12(request);
@@ -10396,6 +10949,7 @@ async function updateProject(request, reply) {
10396
10949
  }
10397
10950
  }
10398
10951
  async function deleteProject(request, reply) {
10952
+ let releaseDeletion;
10399
10953
  try {
10400
10954
  const tenantId = getTenantId12(request);
10401
10955
  const store2 = getEvalStore();
@@ -10404,6 +10958,23 @@ async function deleteProject(request, reply) {
10404
10958
  if (!existing) {
10405
10959
  return reply.status(404).send({ success: false, message: "Project not found" });
10406
10960
  }
10961
+ releaseDeletion = evalRunner.reserveProjectDeletion(tenantId, pid) ?? void 0;
10962
+ if (!releaseDeletion) {
10963
+ return reply.status(409).send({
10964
+ success: false,
10965
+ message: "Cannot delete an evaluation project with an active run"
10966
+ });
10967
+ }
10968
+ const runs = await store2.getRunsByTenant(tenantId, { projectId: pid });
10969
+ const hasActiveRun = runs.some(
10970
+ (run) => run.status === "running" || evalRunner.hasActiveExecution(run.id, tenantId)
10971
+ );
10972
+ if (hasActiveRun) {
10973
+ return reply.status(409).send({
10974
+ success: false,
10975
+ message: "Cannot delete an evaluation project with an active run"
10976
+ });
10977
+ }
10407
10978
  const deleted = await store2.deleteProject(tenantId, pid);
10408
10979
  if (!deleted) {
10409
10980
  return reply.status(500).send({ success: false, message: "Failed to delete project" });
@@ -10412,6 +10983,8 @@ async function deleteProject(request, reply) {
10412
10983
  } catch (err) {
10413
10984
  const message = err instanceof Error ? err.message : "Failed to delete project";
10414
10985
  return reply.status(500).send({ success: false, message });
10986
+ } finally {
10987
+ releaseDeletion?.();
10415
10988
  }
10416
10989
  }
10417
10990
  async function createSuite(request, reply) {
@@ -10440,8 +11013,8 @@ async function updateSuite(request, reply) {
10440
11013
  try {
10441
11014
  const tenantId = getTenantId12(request);
10442
11015
  const store2 = getEvalStore();
10443
- const { sid } = request.params;
10444
- const existing = await store2.getSuiteById(tenantId, sid);
11016
+ const { pid, sid } = request.params;
11017
+ const existing = await getOwnedSuite(store2, tenantId, pid, sid);
10445
11018
  if (!existing) {
10446
11019
  return reply.status(404).send({ success: false, message: "Suite not found" });
10447
11020
  }
@@ -10460,8 +11033,8 @@ async function deleteSuite(request, reply) {
10460
11033
  try {
10461
11034
  const tenantId = getTenantId12(request);
10462
11035
  const store2 = getEvalStore();
10463
- const { sid } = request.params;
10464
- const existing = await store2.getSuiteById(tenantId, sid);
11036
+ const { pid, sid } = request.params;
11037
+ const existing = await getOwnedSuite(store2, tenantId, pid, sid);
10465
11038
  if (!existing) {
10466
11039
  return reply.status(404).send({ success: false, message: "Suite not found" });
10467
11040
  }
@@ -10479,8 +11052,8 @@ async function createCase(request, reply) {
10479
11052
  try {
10480
11053
  const tenantId = getTenantId12(request);
10481
11054
  const store2 = getEvalStore();
10482
- const { sid } = request.params;
10483
- const suite = await store2.getSuiteById(tenantId, sid);
11055
+ const { pid, sid } = request.params;
11056
+ const suite = await getOwnedSuite(store2, tenantId, pid, sid);
10484
11057
  if (!suite) {
10485
11058
  return reply.status(404).send({ success: false, message: "Suite not found" });
10486
11059
  }
@@ -10509,7 +11082,11 @@ async function listCasesForSuite(request, reply) {
10509
11082
  try {
10510
11083
  const tenantId = getTenantId12(request);
10511
11084
  const store2 = getEvalStore();
10512
- const { sid } = request.params;
11085
+ const { pid, sid } = request.params;
11086
+ const suite = await getOwnedSuite(store2, tenantId, pid, sid);
11087
+ if (!suite) {
11088
+ return reply.status(404).send({ success: false, message: "Suite not found" });
11089
+ }
10513
11090
  const cases = await store2.getCasesBySuite(tenantId, sid);
10514
11091
  return {
10515
11092
  success: true,
@@ -10525,8 +11102,8 @@ async function updateCase(request, reply) {
10525
11102
  try {
10526
11103
  const tenantId = getTenantId12(request);
10527
11104
  const store2 = getEvalStore();
10528
- const { cid } = request.params;
10529
- const existing = await store2.getCaseById(tenantId, cid);
11105
+ const { pid, sid, cid } = request.params;
11106
+ const existing = await getOwnedCase(store2, tenantId, pid, sid, cid);
10530
11107
  if (!existing) {
10531
11108
  return reply.status(404).send({ success: false, message: "Case not found" });
10532
11109
  }
@@ -10545,8 +11122,8 @@ async function deleteCase(request, reply) {
10545
11122
  try {
10546
11123
  const tenantId = getTenantId12(request);
10547
11124
  const store2 = getEvalStore();
10548
- const { cid } = request.params;
10549
- const existing = await store2.getCaseById(tenantId, cid);
11125
+ const { pid, sid, cid } = request.params;
11126
+ const existing = await getOwnedCase(store2, tenantId, pid, sid, cid);
10550
11127
  if (!existing) {
10551
11128
  return reply.status(404).send({ success: false, message: "Case not found" });
10552
11129
  }
@@ -10615,6 +11192,11 @@ function registerEvalRoutes(app2) {
10615
11192
  }
10616
11193
  });
10617
11194
  app2.get("/api/eval/runs/:rid/stream", async (request, reply) => {
11195
+ const tenantId = getTenantId12(request);
11196
+ const { rid } = request.params;
11197
+ const store2 = getEvalStore();
11198
+ const ownedRun = await store2.getRunById(tenantId, rid);
11199
+ if (!ownedRun) return reply.status(404).send({ success: false, message: "Run not found" });
10618
11200
  reply.hijack();
10619
11201
  reply.raw.writeHead(200, {
10620
11202
  "Content-Type": "text/event-stream",
@@ -10622,41 +11204,75 @@ function registerEvalRoutes(app2) {
10622
11204
  Connection: "keep-alive",
10623
11205
  "Access-Control-Allow-Origin": "*"
10624
11206
  });
10625
- const { rid } = request.params;
10626
11207
  const emitter = evalRunner.getEventEmitter();
10627
11208
  const eventKey = `run:${rid}`;
10628
- if (!evalRunner.isRunning(rid)) {
10629
- const tenantId = getTenantId12(request);
10630
- const run = await getEvalStore().getRunById(tenantId, rid);
10631
- if (run) {
10632
- const eventType = run.status === "completed" ? "completed" : "error";
10633
- reply.raw.write(`event: ${eventType}
10634
- data: ${JSON.stringify({ passed: run.passedCases, failed: run.failedCases, avgScore: run.avgScore })}
11209
+ let settled = false;
11210
+ const cleanup = () => {
11211
+ emitter.off(eventKey, handler);
11212
+ request.raw.off("close", disconnect);
11213
+ };
11214
+ const disconnect = () => {
11215
+ if (settled) return;
11216
+ settled = true;
11217
+ cleanup();
11218
+ };
11219
+ const finish = (type, data) => {
11220
+ if (settled) return;
11221
+ settled = true;
11222
+ cleanup();
11223
+ reply.raw.write(`event: ${type}
11224
+ data: ${JSON.stringify(data)}
10635
11225
 
10636
11226
  `);
10637
- }
10638
11227
  reply.raw.end();
10639
- return;
10640
- }
11228
+ };
10641
11229
  const handler = (event) => {
10642
- reply.raw.write(`event: ${event.type}
11230
+ if (event.type === "completed" || event.type === "error") {
11231
+ finish(event.type, event.data);
11232
+ return;
11233
+ }
11234
+ if (!settled) reply.raw.write(`event: ${event.type}
10643
11235
  data: ${JSON.stringify(event.data)}
10644
11236
 
10645
11237
  `);
10646
- if (event.type === "completed" || event.type === "error") {
10647
- emitter.off(eventKey, handler);
10648
- reply.raw.end();
10649
- }
10650
11238
  };
10651
11239
  emitter.on(eventKey, handler);
10652
- request.raw.on("close", () => {
10653
- emitter.off(eventKey, handler);
10654
- });
11240
+ request.raw.on("close", disconnect);
11241
+ let reconciledRun;
11242
+ try {
11243
+ reconciledRun = await store2.getRunById(tenantId, rid);
11244
+ } catch (error) {
11245
+ finish("error", { message: error.message });
11246
+ return;
11247
+ }
11248
+ if (settled) return;
11249
+ if (!reconciledRun) {
11250
+ cleanup();
11251
+ reply.raw.end();
11252
+ return;
11253
+ }
11254
+ if (reconciledRun.status === "completed") {
11255
+ finish("completed", {
11256
+ passed: reconciledRun.passedCases,
11257
+ failed: reconciledRun.failedCases,
11258
+ avgScore: reconciledRun.avgScore
11259
+ });
11260
+ } else if (reconciledRun.status === "failed") {
11261
+ finish("error", { message: reconciledRun.error ?? "Evaluation failed" });
11262
+ } else if (reconciledRun.status === "aborted") {
11263
+ finish("error", { message: "Run aborted" });
11264
+ } else if (!evalRunner.isRunning(rid, tenantId)) {
11265
+ cleanup();
11266
+ reply.raw.end();
11267
+ }
10655
11268
  });
10656
11269
  app2.post("/api/eval/runs/:rid/abort", async (request, reply) => {
10657
11270
  try {
11271
+ const tenantId = getTenantId12(request);
10658
11272
  const { rid } = request.params;
10659
- const aborted = await evalRunner.abortRun(rid);
11273
+ const run = await getEvalStore().getRunById(tenantId, rid);
11274
+ if (!run) return reply.status(404).send({ success: false, message: "Run not found" });
11275
+ const aborted = await evalRunner.abortRun(rid, tenantId);
10660
11276
  if (!aborted) return reply.status(404).send({ success: false, message: "Run not found or not running" });
10661
11277
  reply.send({ success: true, message: "Run aborted" });
10662
11278
  } catch (err) {
@@ -10667,7 +11283,13 @@ data: ${JSON.stringify(event.data)}
10667
11283
  try {
10668
11284
  const tenantId = getTenantId12(request);
10669
11285
  const { rid } = request.params;
10670
- const deleted = await getEvalStore().deleteRun(tenantId, rid);
11286
+ const store2 = getEvalStore();
11287
+ const run = await store2.getRunById(tenantId, rid);
11288
+ if (!run) return reply.status(404).send({ success: false, message: "Run not found" });
11289
+ if (run.status === "running" || evalRunner.hasActiveExecution(rid, tenantId)) {
11290
+ return reply.status(409).send({ success: false, message: "Cannot delete an active evaluation run" });
11291
+ }
11292
+ const deleted = await store2.deleteRun(tenantId, rid);
10671
11293
  if (!deleted) return reply.status(404).send({ success: false, message: "Run not found" });
10672
11294
  reply.send({ success: true, message: "Run deleted" });
10673
11295
  } catch (err) {
@@ -10784,31 +11406,31 @@ var TenantsController = class {
10784
11406
  async createTenant(request, reply) {
10785
11407
  const data = request.body;
10786
11408
  const id = (0, import_uuid7.v4)();
10787
- const tenant = await this.tenantStore.createTenant(id, data);
10788
- return reply.status(201).send({ success: true, data: tenant });
11409
+ const tenant2 = await this.tenantStore.createTenant(id, data);
11410
+ return reply.status(201).send({ success: true, data: tenant2 });
10789
11411
  }
10790
11412
  async getTenant(request, reply) {
10791
11413
  const { tenantId } = request.params;
10792
- const tenant = await this.tenantStore.getTenantById(tenantId);
10793
- if (!tenant) {
11414
+ const tenant2 = await this.tenantStore.getTenantById(tenantId);
11415
+ if (!tenant2) {
10794
11416
  return reply.status(404).send({
10795
11417
  success: false,
10796
11418
  error: "Tenant not found"
10797
11419
  });
10798
11420
  }
10799
- return { success: true, data: tenant };
11421
+ return { success: true, data: tenant2 };
10800
11422
  }
10801
11423
  async updateTenant(request, reply) {
10802
11424
  const { tenantId } = request.params;
10803
11425
  const updates = request.body;
10804
- const tenant = await this.tenantStore.updateTenant(tenantId, updates);
10805
- if (!tenant) {
11426
+ const tenant2 = await this.tenantStore.updateTenant(tenantId, updates);
11427
+ if (!tenant2) {
10806
11428
  return reply.status(404).send({
10807
11429
  success: false,
10808
11430
  error: "Tenant not found"
10809
11431
  });
10810
11432
  }
10811
- return { success: true, data: tenant };
11433
+ return { success: true, data: tenant2 };
10812
11434
  }
10813
11435
  async deleteTenant(request, reply) {
10814
11436
  const { tenantId } = request.params;
@@ -10961,10 +11583,10 @@ var AuthController = class {
10961
11583
  const links = await this.userTenantLinkStore.getTenantsByUser(userId);
10962
11584
  const tenantsWithDetails = await Promise.all(
10963
11585
  links.map(async (link) => {
10964
- const tenant = await this.tenantStore.getTenantById(link.tenantId);
11586
+ const tenant2 = await this.tenantStore.getTenantById(link.tenantId);
10965
11587
  return {
10966
11588
  ...link,
10967
- tenant: tenant || null
11589
+ tenant: tenant2 || null
10968
11590
  };
10969
11591
  })
10970
11592
  );
@@ -10997,8 +11619,8 @@ var AuthController = class {
10997
11619
  error: "You do not have access to this tenant"
10998
11620
  });
10999
11621
  }
11000
- const tenant = await this.tenantStore.getTenantById(tenantId);
11001
- if (!tenant) {
11622
+ const tenant2 = await this.tenantStore.getTenantById(tenantId);
11623
+ if (!tenant2) {
11002
11624
  return reply.status(404).send({
11003
11625
  success: false,
11004
11626
  error: "Tenant not found"
@@ -11022,7 +11644,7 @@ var AuthController = class {
11022
11644
  return {
11023
11645
  success: true,
11024
11646
  data: {
11025
- tenant,
11647
+ tenant: tenant2,
11026
11648
  token,
11027
11649
  personalAssistant
11028
11650
  }
@@ -11092,8 +11714,8 @@ var AuthController = class {
11092
11714
  error: "User not found"
11093
11715
  });
11094
11716
  }
11095
- const tenant = await this.tenantStore.getTenantById(tenantId);
11096
- if (!tenant) {
11717
+ const tenant2 = await this.tenantStore.getTenantById(tenantId);
11718
+ if (!tenant2) {
11097
11719
  return reply.status(404).send({
11098
11720
  success: false,
11099
11721
  error: "Tenant not found"
@@ -11263,7 +11885,7 @@ function registerConsoleAuthHook(app2, options) {
11263
11885
  if (PUBLIC_ROUTES.some((route) => request.url === route)) return;
11264
11886
  if (request.url.startsWith("/s/")) return;
11265
11887
  const path6 = request.url.split("?")[0];
11266
- if (path6.includes("/viewfile") || path6.includes("/downloadfile") || path6.includes("/uploadfile") || path6.startsWith("/f/") || path6 === "/f") {
11888
+ if (request.method === "GET" && /^\/api\/workspaces\/[^/]+\/projects\/[^/]+\/viewfile$/.test(path6) || request.method === "GET" && /^\/api\/workspaces\/[^/]+\/projects\/[^/]+\/downloadfile$/.test(path6) || request.method === "POST" && /^\/api\/workspaces\/[^/]+\/projects\/[^/]+\/uploadfile$/.test(path6) || path6.startsWith("/f/") || path6 === "/f") {
11267
11889
  return;
11268
11890
  }
11269
11891
  await reply.status(401).send({
@@ -12266,12 +12888,12 @@ async function handleBridgeProxy(request, reply) {
12266
12888
  };
12267
12889
  const timeout = 3e5;
12268
12890
  try {
12269
- const response = await new Promise((resolve3, reject) => {
12891
+ const response = await new Promise((resolve4, reject) => {
12270
12892
  const timer = setTimeout(() => {
12271
12893
  pending.delete(requestId);
12272
12894
  reject(new Error(`Bridge request timeout after ${timeout}ms`));
12273
12895
  }, timeout);
12274
- pending.set(requestId, { resolve: resolve3, reject, timer, agentId });
12896
+ pending.set(requestId, { resolve: resolve4, reject, timer, agentId });
12275
12897
  try {
12276
12898
  if (conn.ws.readyState !== conn.ws.OPEN) {
12277
12899
  pending.delete(requestId);
@@ -12938,7 +13560,7 @@ function registerTaskRoutes(app2) {
12938
13560
  }
12939
13561
 
12940
13562
  // src/routes/index.ts
12941
- var import_core41 = require("@axiom-lattice/core");
13563
+ var import_core42 = require("@axiom-lattice/core");
12942
13564
 
12943
13565
  // src/services/agent-web-app/AgentWebAppService.ts
12944
13566
  function createConfiguredAgentResolver(registry) {
@@ -13897,42 +14519,399 @@ function isAttachmentPayload(value) {
13897
14519
 
13898
14520
  // src/routes/index.ts
13899
14521
  init_workspace_file_service();
14522
+
14523
+ // src/controllers/capability-bundles.ts
14524
+ var import_zod7 = require("zod");
14525
+
14526
+ // src/services/capability/CapabilityBundleService.ts
14527
+ var import_zod6 = require("zod");
14528
+ var import_core41 = require("@axiom-lattice/core");
14529
+ var CapabilityBundleServiceError = class extends Error {
14530
+ constructor(code, message, statusCode) {
14531
+ super(message);
14532
+ this.code = code;
14533
+ this.statusCode = statusCode;
14534
+ this.name = "CapabilityBundleServiceError";
14535
+ }
14536
+ };
14537
+ var uuid = import_zod6.z.string().uuid();
14538
+ function ownRecord(value) {
14539
+ if (value === void 0 || value === null) return {};
14540
+ if (typeof value !== "object" || Array.isArray(value)) throw new CapabilityBundleServiceError("INVALID_BUNDLE_CONFIG", "Project config must be an object", 409);
14541
+ const result = {};
14542
+ for (const [key, entry] of Object.entries(value)) {
14543
+ if (key !== "__proto__" && key !== "constructor" && key !== "prototype") result[key] = entry;
14544
+ }
14545
+ return result;
14546
+ }
14547
+ function isDuplicate(error) {
14548
+ return error instanceof Error && /already exists|unique|duplicate/i.test(error.message);
14549
+ }
14550
+ function rejectStableKey(input) {
14551
+ if (input !== null && typeof input === "object" && !Array.isArray(input) && Object.prototype.hasOwnProperty.call(input, "key")) {
14552
+ throw new CapabilityBundleServiceError("INVALID_INPUT", "Capability bundle key is generated and cannot be supplied or changed", 400);
14553
+ }
14554
+ }
14555
+ function slugify(name) {
14556
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
14557
+ return slug || "runtime-profile";
14558
+ }
14559
+ function projectBundleIds(config) {
14560
+ const record = ownRecord(config);
14561
+ if (!Object.prototype.hasOwnProperty.call(record, "capabilityBundleIds")) return [];
14562
+ const value = record.capabilityBundleIds;
14563
+ if (!Array.isArray(value) || value.some((id) => typeof id !== "string" || !uuid.safeParse(id).success) || new Set(value).size !== value.length) {
14564
+ throw new CapabilityBundleServiceError("INVALID_BUNDLE_CONFIG", "Project capabilityBundleIds must be a unique array of UUIDs", 409);
14565
+ }
14566
+ return [...value];
14567
+ }
14568
+ function issue(error) {
14569
+ if (error instanceof import_core41.CapabilityBundleMergeError || error instanceof import_core41.CapabilityBundleValidationError) return { code: error instanceof import_core41.CapabilityBundleValidationError && error.code === "CAPABILITY_BUNDLE_INVALID_CONFIG" ? "INVALID_BUNDLE_CONFIG" : error.code, message: error.message, bundleId: error instanceof import_core41.CapabilityBundleMergeError ? error.bundleId : void 0, capabilityType: error.capabilityType, fieldPath: error.fieldPath };
14570
+ return { code: "CAPABILITY_BUNDLE_CONFIGURATION_ERROR", message: error instanceof Error ? error.message : "Invalid capability bundle configuration" };
14571
+ }
14572
+ var CapabilityBundleService = class {
14573
+ constructor(deps) {
14574
+ this.deps = deps;
14575
+ }
14576
+ async list(tenantId) {
14577
+ const values = await this.deps.bundleStore.listByTenant(tenantId);
14578
+ const bundles = [];
14579
+ const errors = [];
14580
+ for (const value of values) {
14581
+ try {
14582
+ (0, import_core41.validateCapabilityBundleInput)(value);
14583
+ bundles.push(value);
14584
+ } catch {
14585
+ errors.push({ bundleId: value.id, code: "INVALID_BUNDLE_CONFIG", message: "Capability bundle record is invalid" });
14586
+ }
14587
+ }
14588
+ return { bundles, errors };
14589
+ }
14590
+ async get(tenantId, id) {
14591
+ const value = await this.deps.bundleStore.getById(tenantId, id);
14592
+ if (!value) throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
14593
+ try {
14594
+ (0, import_core41.validateCapabilityBundleInput)(value);
14595
+ } catch {
14596
+ throw new CapabilityBundleServiceError("INVALID_BUNDLE_CONFIG", "Capability bundle record is invalid", 422);
14597
+ }
14598
+ return value;
14599
+ }
14600
+ async create(tenantId, input) {
14601
+ rejectStableKey(input);
14602
+ (0, import_core41.validateCapabilityBundleInput)(input);
14603
+ const value = input;
14604
+ const name = value.name.trim();
14605
+ const base = slugify(name);
14606
+ for (let attempt = 0; attempt < 5; attempt += 1) {
14607
+ const existing = await this.deps.bundleStore.listByTenant(tenantId) ?? [];
14608
+ const keys = new Set(existing.map((bundle) => bundle.key));
14609
+ let key = base;
14610
+ let suffix = 2;
14611
+ while (keys.has(key)) key = `${base}-${suffix++}`;
14612
+ const normalized = { ...value, key, name };
14613
+ (0, import_core41.validateCapabilityBundleInput)(normalized);
14614
+ try {
14615
+ const result = await this.deps.bundleStore.create(tenantId, normalized);
14616
+ (0, import_core41.validateCapabilityBundleInput)(result);
14617
+ return result;
14618
+ } catch (error) {
14619
+ if (!isDuplicate(error)) throw error;
14620
+ if (attempt === 4) throw new CapabilityBundleServiceError("DUPLICATE_BUNDLE_KEY", "Capability bundle key already exists", 409);
14621
+ }
14622
+ }
14623
+ throw new CapabilityBundleServiceError("DUPLICATE_BUNDLE_KEY", "Capability bundle key already exists", 409);
14624
+ }
14625
+ async update(tenantId, id, input) {
14626
+ rejectStableKey(input);
14627
+ (0, import_core41.validateCapabilityBundleInput)(input, { partial: true });
14628
+ 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);
14629
+ const value = input;
14630
+ const normalized = { ...value, ...value.name === void 0 ? {} : { name: value.name.trim() }, ...Object.prototype.hasOwnProperty.call(value, "description") ? { description: value.description?.trim() || void 0 } : {} };
14631
+ (0, import_core41.validateCapabilityBundleInput)(normalized, { partial: true });
14632
+ try {
14633
+ const result = await this.deps.bundleStore.update(tenantId, id, normalized);
14634
+ if (!result) throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
14635
+ if ("status" in result && result.status === "conflict") throw new CapabilityBundleServiceError("BUNDLE_CONFLICT", "Capability bundle changed after it was loaded; reload before saving", 409);
14636
+ (0, import_core41.validateCapabilityBundleInput)(result);
14637
+ return result;
14638
+ } catch (error) {
14639
+ if (error instanceof CapabilityBundleServiceError) throw error;
14640
+ if (isDuplicate(error)) throw new CapabilityBundleServiceError("DUPLICATE_BUNDLE_KEY", "Capability bundle key already exists", 409);
14641
+ throw error;
14642
+ }
14643
+ }
14644
+ async remove(tenantId, id) {
14645
+ const result = await this.deps.bundleStore.deleteIfUnreferenced(tenantId, id);
14646
+ if (result === "not_found") throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
14647
+ if (result === "in_use") throw new CapabilityBundleServiceError("BUNDLE_IN_USE", "Capability bundle is in use", 409);
14648
+ }
14649
+ async getProjectBundles(tenantId, projectId) {
14650
+ const project = await this.deps.projectStore.getProjectById(tenantId, projectId);
14651
+ if (!project) throw new CapabilityBundleServiceError("PROJECT_NOT_FOUND", "Project not found", 404);
14652
+ const ids = projectBundleIds(project.config);
14653
+ const stored = await this.deps.bundleStore.getManyByIds(tenantId, ids) ?? [];
14654
+ return this.compose(project, ids, stored);
14655
+ }
14656
+ async previewProject(tenantId, projectId) {
14657
+ const project = await this.deps.projectStore.getProjectById(tenantId, projectId);
14658
+ if (!project) throw new CapabilityBundleServiceError("PROJECT_NOT_FOUND", "Project not found", 404);
14659
+ let ids;
14660
+ try {
14661
+ ids = projectBundleIds(project.config);
14662
+ } catch (error) {
14663
+ return { bundleIds: [], capabilities: [], overrides: [], sources: [], warnings: [], errors: [{ code: "INVALID_BUNDLE_CONFIG", message: error instanceof Error ? error.message : "Invalid project capability configuration" }], revision: "" };
14664
+ }
14665
+ const stored = await this.deps.bundleStore.getManyByIds(tenantId, ids) ?? [];
14666
+ const byId = new Map(stored.map((value) => [value.id, value]));
14667
+ const missing = ids.filter((id) => !byId.has(id));
14668
+ 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: "" };
14669
+ try {
14670
+ const ordered = ids.map((id) => byId.get(id));
14671
+ ordered.forEach((bundle) => (0, import_core41.validateCapabilityBundleInput)(bundle));
14672
+ return (0, import_core41.mergeCapabilityBundles)(ordered);
14673
+ } catch (error) {
14674
+ return { bundleIds: ids, capabilities: [], overrides: [], sources: [], warnings: [], errors: [issue(error)], revision: "" };
14675
+ }
14676
+ }
14677
+ async project(tenantId, projectId, ids, persist, expectedRevisions) {
14678
+ const current = await this.deps.projectStore.getProjectById(tenantId, projectId);
14679
+ if (!current) throw new CapabilityBundleServiceError("PROJECT_NOT_FOUND", "Project not found", 404);
14680
+ if (new Set(ids).size !== ids.length) throw new CapabilityBundleServiceError("DUPLICATE_PROJECT_BUNDLE", "Duplicate capability bundle ID", 400);
14681
+ if (persist) this.assertExpectedRevisions(ids, expectedRevisions);
14682
+ const ordered = await this.loadOrderedBundles(tenantId, ids);
14683
+ let resultProject = current;
14684
+ if (persist) {
14685
+ projectBundleIds(current.config);
14686
+ const update = await this.deps.projectStore.updateCapabilityBundleIds(tenantId, projectId, ids, expectedRevisions);
14687
+ if (update.status === "project_not_found") throw new CapabilityBundleServiceError("PROJECT_NOT_FOUND", "Project not found", 404);
14688
+ if (update.status === "bundle_not_found") throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
14689
+ if (update.status === "bundle_conflict") throw new CapabilityBundleServiceError("BUNDLE_CONFLICT", "Capability bundle changed after preview; preview the current bundle contents before saving", 409);
14690
+ resultProject = update.project;
14691
+ }
14692
+ return this.compose(resultProject, ids, ordered);
14693
+ }
14694
+ assertExpectedRevisions(ids, revisions) {
14695
+ 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))) {
14696
+ throw new CapabilityBundleServiceError("BUNDLE_CONFLICT", "Expected revisions must exactly match the selected capability bundles", 409);
14697
+ }
14698
+ }
14699
+ async loadOrderedBundles(tenantId, ids) {
14700
+ const stored = await this.deps.bundleStore.getManyByIds(tenantId, ids) ?? [];
14701
+ const byId = new Map(stored.map((value) => [value.id, value]));
14702
+ return ids.map((id) => {
14703
+ const value = byId.get(id);
14704
+ if (!value) throw new CapabilityBundleServiceError("BUNDLE_NOT_FOUND", "Capability bundle not found", 404);
14705
+ (0, import_core41.validateCapabilityBundleInput)(value);
14706
+ return value;
14707
+ });
14708
+ }
14709
+ compose(project, ids, bundles) {
14710
+ const valid = [];
14711
+ const errors = [];
14712
+ const byId = new Map(bundles.map((bundle) => [bundle.id, bundle]));
14713
+ for (const id of ids) {
14714
+ const bundle = byId.get(id);
14715
+ if (!bundle) {
14716
+ errors.push({ code: "BUNDLE_NOT_FOUND", message: `Persisted capability bundle '${id}' is missing`, bundleId: id });
14717
+ continue;
14718
+ }
14719
+ try {
14720
+ (0, import_core41.validateCapabilityBundleInput)(bundle);
14721
+ valid.push(bundle);
14722
+ } catch (error) {
14723
+ errors.push({ ...issue(error), bundleId: id });
14724
+ }
14725
+ }
14726
+ let preview;
14727
+ try {
14728
+ preview = (0, import_core41.mergeCapabilityBundles)(valid);
14729
+ } catch (error) {
14730
+ preview = { bundleIds: ids, capabilities: [], overrides: [], sources: [], warnings: [], errors: [issue(error)], revision: "" };
14731
+ }
14732
+ preview = { ...preview, bundleIds: ids, errors: [...errors, ...preview.errors] };
14733
+ return { project, persistedBundleIds: ids, bundles: valid, preview };
14734
+ }
14735
+ };
14736
+
14737
+ // src/controllers/capability-bundles.ts
14738
+ init_Logger();
14739
+ var logger3 = new Logger({ serviceName: "lattice/gateway/capability-bundles" });
14740
+ var capability = import_zod7.z.unknown();
14741
+ var createSchema = import_zod7.z.object({ name: import_zod7.z.string(), description: import_zod7.z.string().optional(), capabilities: import_zod7.z.array(capability) }).strict();
14742
+ var updateSchema = createSchema.partial().extend({ expectedUpdatedAt: import_zod7.z.string().optional() });
14743
+ var bundleIdSchema = import_zod7.z.string().uuid();
14744
+ var bundleParamsSchema = import_zod7.z.object({ bundleId: bundleIdSchema });
14745
+ var projectSchema = import_zod7.z.object({ bundleIds: import_zod7.z.array(bundleIdSchema), expectedRevisions: import_zod7.z.record(import_zod7.z.string()).optional() }).strict();
14746
+ function tenant(request, authRequired2) {
14747
+ const authenticated = request.user?.tenantId;
14748
+ const header = request.headers["x-tenant-id"];
14749
+ const requested = Array.isArray(header) ? header[0] : header;
14750
+ if (authenticated) {
14751
+ if (requested && requested !== authenticated) throw new CapabilityBundleServiceError("INVALID_INPUT", "Forbidden", 403);
14752
+ return authenticated;
14753
+ }
14754
+ if (authRequired2) throw new CapabilityBundleServiceError("INVALID_INPUT", "Unauthorized", 401);
14755
+ return requested || "default";
14756
+ }
14757
+ function sendError(reply, error) {
14758
+ if (error instanceof CapabilityBundleServiceError) return reply.status(error.statusCode).send({ success: false, code: error.code, message: error.message });
14759
+ if (error instanceof Error && error.name === "CapabilityBundleValidationError") {
14760
+ const validationError = error;
14761
+ 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;
14762
+ return reply.status(400).send({ success: false, code, message: error.message, capabilityType: validationError.capabilityType, fieldPath: validationError.fieldPath });
14763
+ }
14764
+ logger3.error("Capability bundle controller error", { error: error instanceof Error ? error : new Error(String(error)) });
14765
+ return reply.status(500).send({ success: false, code: "INTERNAL_ERROR", message: "Internal server error" });
14766
+ }
14767
+ var CapabilityBundleController = class {
14768
+ constructor(deps, options = {}) {
14769
+ this.service = new CapabilityBundleService(deps);
14770
+ this.authRequired = options.authRequired ?? true;
14771
+ }
14772
+ async listBundles(request, reply) {
14773
+ try {
14774
+ const result = await this.service.list(tenant(request, this.authRequired));
14775
+ return reply.send({ success: true, data: result.bundles, errors: result.errors });
14776
+ } catch (error) {
14777
+ return sendError(reply, error);
14778
+ }
14779
+ }
14780
+ async getBundle(request, reply) {
14781
+ try {
14782
+ const tenantId = tenant(request, this.authRequired);
14783
+ const parsed = bundleParamsSchema.safeParse(request.params);
14784
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid capability bundle ID" });
14785
+ return reply.send({ success: true, data: await this.service.get(tenantId, parsed.data.bundleId) });
14786
+ } catch (error) {
14787
+ return sendError(reply, error);
14788
+ }
14789
+ }
14790
+ async createBundle(request, reply) {
14791
+ try {
14792
+ const tenantId = tenant(request, this.authRequired);
14793
+ const parsed = createSchema.safeParse(request.body);
14794
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid capability bundle body" });
14795
+ return reply.status(201).send({ success: true, data: await this.service.create(tenantId, parsed.data) });
14796
+ } catch (error) {
14797
+ return sendError(reply, error);
14798
+ }
14799
+ }
14800
+ async updateBundle(request, reply) {
14801
+ try {
14802
+ const tenantId = tenant(request, this.authRequired);
14803
+ const params = bundleParamsSchema.safeParse(request.params);
14804
+ const parsed = updateSchema.safeParse(request.body);
14805
+ if (!params.success || !parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid capability bundle request" });
14806
+ return reply.send({ success: true, data: await this.service.update(tenantId, params.data.bundleId, parsed.data) });
14807
+ } catch (error) {
14808
+ return sendError(reply, error);
14809
+ }
14810
+ }
14811
+ async deleteBundle(request, reply) {
14812
+ try {
14813
+ const tenantId = tenant(request, this.authRequired);
14814
+ const parsed = bundleParamsSchema.safeParse(request.params);
14815
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid capability bundle ID" });
14816
+ await this.service.remove(tenantId, parsed.data.bundleId);
14817
+ return reply.send({ success: true, data: null });
14818
+ } catch (error) {
14819
+ return sendError(reply, error);
14820
+ }
14821
+ }
14822
+ async updateProjectBundles(request, reply) {
14823
+ try {
14824
+ const tenantId = tenant(request, this.authRequired);
14825
+ const parsed = projectSchema.safeParse(request.body);
14826
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid project capability bundle body" });
14827
+ const result = await this.service.project(tenantId, request.params.projectId, parsed.data.bundleIds, true, parsed.data.expectedRevisions);
14828
+ return reply.send({ success: true, data: { project: result.project, persistedBundleIds: result.persistedBundleIds, bundles: result.bundles, preview: result.preview } });
14829
+ } catch (error) {
14830
+ return sendError(reply, error);
14831
+ }
14832
+ }
14833
+ async getProjectBundles(request, reply) {
14834
+ try {
14835
+ const result = await this.service.getProjectBundles(tenant(request, this.authRequired), request.params.projectId);
14836
+ 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" });
14837
+ return reply.send({ success: true, data: { project: result.project, persistedBundleIds: result.persistedBundleIds, bundles: result.bundles, preview: result.preview } });
14838
+ } catch (error) {
14839
+ return sendError(reply, error);
14840
+ }
14841
+ }
14842
+ async getProjectPreview(request, reply) {
14843
+ try {
14844
+ const preview = await this.service.previewProject(tenant(request, this.authRequired), request.params.projectId);
14845
+ return reply.send({ success: true, data: preview });
14846
+ } catch (error) {
14847
+ return sendError(reply, error);
14848
+ }
14849
+ }
14850
+ async postProjectPreview(request, reply) {
14851
+ try {
14852
+ const tenantId = tenant(request, this.authRequired);
14853
+ const parsed = projectSchema.safeParse(request.body);
14854
+ if (!parsed.success) return reply.status(400).send({ success: false, code: "INVALID_INPUT", message: "Invalid project capability bundle body" });
14855
+ const result = await this.service.project(tenantId, request.params.projectId, parsed.data.bundleIds, false);
14856
+ return reply.send({ success: true, data: result.preview });
14857
+ } catch (error) {
14858
+ return sendError(reply, error);
14859
+ }
14860
+ }
14861
+ };
14862
+ function registerCapabilityBundleRoutes(app2, controller) {
14863
+ app2.get("/api/capability-bundles", controller.listBundles.bind(controller));
14864
+ app2.post("/api/capability-bundles", controller.createBundle.bind(controller));
14865
+ app2.get("/api/capability-bundles/:bundleId", controller.getBundle.bind(controller));
14866
+ app2.put("/api/capability-bundles/:bundleId", controller.updateBundle.bind(controller));
14867
+ app2.delete("/api/capability-bundles/:bundleId", controller.deleteBundle.bind(controller));
14868
+ app2.get("/api/projects/:projectId/capability-bundles", controller.getProjectBundles.bind(controller));
14869
+ app2.put("/api/projects/:projectId/capability-bundles", controller.updateProjectBundles.bind(controller));
14870
+ app2.get("/api/projects/:projectId/capability-preview", controller.getProjectPreview.bind(controller));
14871
+ app2.post("/api/projects/:projectId/capability-preview", controller.postProjectPreview.bind(controller));
14872
+ }
14873
+
14874
+ // src/routes/index.ts
13900
14875
  var registerLatticeRoutes = (app2, channelDeps) => {
13901
- const webAppStore = (0, import_core41.getStoreLattice)("default", "agentWebApp").store;
13902
- const assistantStore = (0, import_core41.getStoreLattice)("default", "assistant").store;
13903
- const projectStore = (0, import_core41.getStoreLattice)("default", "project").store;
14876
+ const webAppStore = (0, import_core42.getStoreLattice)("default", "agentWebApp").store;
14877
+ const assistantStore = (0, import_core42.getStoreLattice)("default", "assistant").store;
14878
+ const projectStore = (0, import_core42.getStoreLattice)("default", "project").store;
14879
+ registerCapabilityBundleRoutes(app2, new CapabilityBundleController({
14880
+ bundleStore: (0, import_core42.getStoreLattice)("default", "capabilityBundle").store,
14881
+ projectStore
14882
+ }, { authRequired: process.env.AUTH_REQUIRED === "true" }));
13904
14883
  registerAgentWebAppRoutes(app2, {
13905
14884
  service: new AgentWebAppService({
13906
14885
  webAppStore,
13907
14886
  assistantStore,
13908
14887
  projectStore,
13909
- modelRegistry: { has: (key) => import_core41.modelLatticeManager.hasLattice(key) },
14888
+ modelRegistry: { has: (key) => import_core42.modelLatticeManager.hasLattice(key) },
13910
14889
  configuredAgentResolver: createConfiguredAgentResolver({
13911
- hydrate: (tenantId) => import_core41.agentLatticeManager.initializeStoredAssistantsForTenant(tenantId),
13912
- has: (tenantId, assistantId) => import_core41.agentLatticeManager.hasAgentLatticeWithTenant(tenantId, assistantId)
14890
+ hydrate: (tenantId) => import_core42.agentLatticeManager.initializeStoredAssistantsForTenant(tenantId),
14891
+ has: (tenantId, assistantId) => import_core42.agentLatticeManager.hasAgentLatticeWithTenant(tenantId, assistantId)
13913
14892
  })
13914
14893
  })
13915
14894
  });
13916
14895
  registerAgentWebAppRuntimeRoutes(app2, {
13917
14896
  service: new AgentWebAppRuntimeService({
13918
14897
  webAppStore,
13919
- threadStore: (0, import_core41.getStoreLattice)("default", "thread").store,
14898
+ threadStore: (0, import_core42.getStoreLattice)("default", "thread").store,
13920
14899
  projectStore,
13921
14900
  assistantResolver: {
13922
14901
  resolve: async (tenantId, assistantId) => {
13923
14902
  const stored = await assistantStore.getAssistantById(tenantId, assistantId);
13924
14903
  if (stored) return stored;
13925
- await import_core41.agentLatticeManager.initializeStoredAssistantsForTenant(tenantId);
13926
- const config = import_core41.agentLatticeManager.getAgentConfigWithTenant(tenantId, assistantId);
14904
+ await import_core42.agentLatticeManager.initializeStoredAssistantsForTenant(tenantId);
14905
+ const config = import_core42.agentLatticeManager.getAgentConfigWithTenant(tenantId, assistantId);
13927
14906
  return config ? { id: assistantId, name: config.name, description: config.description } : null;
13928
14907
  }
13929
14908
  },
13930
14909
  modelRegistry: {
13931
- has: (key) => import_core41.modelLatticeManager.hasLattice(key),
14910
+ has: (key) => import_core42.modelLatticeManager.hasLattice(key),
13932
14911
  label: (key) => key
13933
14912
  },
13934
14913
  messageReader: {
13935
- getCurrentMessages: ({ tenantId, assistantId, threadId, workspaceId, projectId }) => import_core41.agentInstanceManager.getAgent({
14914
+ getCurrentMessages: ({ tenantId, assistantId, threadId, workspaceId, projectId }) => import_core42.agentInstanceManager.getAgent({
13936
14915
  tenant_id: tenantId,
13937
14916
  assistant_id: assistantId,
13938
14917
  thread_id: threadId,
@@ -13941,7 +14920,7 @@ var registerLatticeRoutes = (app2, channelDeps) => {
13941
14920
  }).getCurrentMessages()
13942
14921
  },
13943
14922
  agentResolver: {
13944
- getAgent: ({ tenantId, assistantId, threadId, workspaceId, projectId }) => import_core41.agentInstanceManager.getAgent({
14923
+ getAgent: ({ tenantId, assistantId, threadId, workspaceId, projectId }) => import_core42.agentInstanceManager.getAgent({
13945
14924
  tenant_id: tenantId,
13946
14925
  assistant_id: assistantId,
13947
14926
  thread_id: threadId,
@@ -13959,8 +14938,8 @@ var registerLatticeRoutes = (app2, channelDeps) => {
13959
14938
  if (webApp.tenantId !== preview.tenantId) return false;
13960
14939
  const assistant = await assistantStore.getAssistantById(preview.tenantId, webApp.assistantId);
13961
14940
  if (assistant) return !assistant.ownerUserId || assistant.ownerUserId === preview.userId;
13962
- await import_core41.agentLatticeManager.initializeStoredAssistantsForTenant(preview.tenantId);
13963
- return import_core41.agentLatticeManager.hasAgentLatticeWithTenant(preview.tenantId, webApp.assistantId);
14941
+ await import_core42.agentLatticeManager.initializeStoredAssistantsForTenant(preview.tenantId);
14942
+ return import_core42.agentLatticeManager.hasAgentLatticeWithTenant(preview.tenantId, webApp.assistantId);
13964
14943
  }
13965
14944
  })
13966
14945
  });
@@ -14260,12 +15239,12 @@ function registerResourceRoutes(app2, controller) {
14260
15239
  }
14261
15240
 
14262
15241
  // src/export_registrations/index.ts
14263
- var import_core52 = require("@axiom-lattice/core");
15242
+ var import_core53 = require("@axiom-lattice/core");
14264
15243
 
14265
15244
  // src/export_registrations/skill.registration.ts
14266
- var import_core42 = require("@axiom-lattice/core");
15245
+ var import_core43 = require("@axiom-lattice/core");
14267
15246
  function getStore2(context) {
14268
- return new import_core42.SandboxSkillStore({
15247
+ return new import_core43.SandboxSkillStore({
14269
15248
  workspaceId: context?.workspaceId,
14270
15249
  projectId: context?.projectId
14271
15250
  });
@@ -14283,7 +15262,7 @@ var skillRegistration = {
14283
15262
  async listForExport(tenantId, context) {
14284
15263
  const store2 = getStore2(context);
14285
15264
  const skills = await store2.getAllSkills(tenantId, toStoreContext(context));
14286
- return skills.filter((s) => !(0, import_core42.isBuiltInSkill)(s.id)).map((s) => ({
15265
+ return skills.filter((s) => !(0, import_core43.isBuiltInSkill)(s.id)).map((s) => ({
14287
15266
  _exportId: "",
14288
15267
  data: {
14289
15268
  id: s.id,
@@ -14348,7 +15327,7 @@ var skillRegistration = {
14348
15327
  const insertions = [];
14349
15328
  for (const entity of entities) {
14350
15329
  const id = entity.data.id;
14351
- if ((0, import_core42.isBuiltInSkill)(id)) continue;
15330
+ if ((0, import_core43.isBuiltInSkill)(id)) continue;
14352
15331
  if (existingIds.has(id)) {
14353
15332
  conflicts.push({
14354
15333
  _exportId: entity._exportId,
@@ -14407,9 +15386,9 @@ var skillRegistration = {
14407
15386
  };
14408
15387
 
14409
15388
  // src/export_registrations/agent.registration.ts
14410
- var import_core43 = require("@axiom-lattice/core");
15389
+ var import_core44 = require("@axiom-lattice/core");
14411
15390
  function getStore3() {
14412
- return (0, import_core43.getStoreLattice)("default", "assistant").store;
15391
+ return (0, import_core44.getStoreLattice)("default", "assistant").store;
14413
15392
  }
14414
15393
  var agentRegistration = {
14415
15394
  entityType: "agent",
@@ -14489,9 +15468,9 @@ var agentRegistration = {
14489
15468
  };
14490
15469
 
14491
15470
  // src/export_registrations/database-config.registration.ts
14492
- var import_core44 = require("@axiom-lattice/core");
15471
+ var import_core45 = require("@axiom-lattice/core");
14493
15472
  function getStore4() {
14494
- return (0, import_core44.getStoreLattice)("default", "database").store;
15473
+ return (0, import_core45.getStoreLattice)("default", "database").store;
14495
15474
  }
14496
15475
  var databaseConfigRegistration = {
14497
15476
  entityType: "database_config",
@@ -14571,9 +15550,9 @@ var databaseConfigRegistration = {
14571
15550
  };
14572
15551
 
14573
15552
  // src/export_registrations/metrics-config.registration.ts
14574
- var import_core45 = require("@axiom-lattice/core");
15553
+ var import_core46 = require("@axiom-lattice/core");
14575
15554
  function getStore5() {
14576
- return (0, import_core45.getStoreLattice)("default", "metrics").store;
15555
+ return (0, import_core46.getStoreLattice)("default", "metrics").store;
14577
15556
  }
14578
15557
  var metricsConfigRegistration = {
14579
15558
  entityType: "metrics_config",
@@ -14653,9 +15632,9 @@ var metricsConfigRegistration = {
14653
15632
  };
14654
15633
 
14655
15634
  // src/export_registrations/mcp-config.registration.ts
14656
- var import_core46 = require("@axiom-lattice/core");
15635
+ var import_core47 = require("@axiom-lattice/core");
14657
15636
  function getStore6() {
14658
- return (0, import_core46.getStoreLattice)("default", "mcp").store;
15637
+ return (0, import_core47.getStoreLattice)("default", "mcp").store;
14659
15638
  }
14660
15639
  var mcpConfigRegistration = {
14661
15640
  entityType: "mcp_config",
@@ -14738,9 +15717,9 @@ var mcpConfigRegistration = {
14738
15717
  };
14739
15718
 
14740
15719
  // src/export_registrations/connection.registration.ts
14741
- var import_core47 = require("@axiom-lattice/core");
15720
+ var import_core48 = require("@axiom-lattice/core");
14742
15721
  function getStore7() {
14743
- return (0, import_core47.getStoreLattice)("default", "connection").store;
15722
+ return (0, import_core48.getStoreLattice)("default", "connection").store;
14744
15723
  }
14745
15724
  async function listAllConnections(tenantId) {
14746
15725
  const store2 = getStore7();
@@ -14822,9 +15801,9 @@ var connectionRegistration = {
14822
15801
  };
14823
15802
 
14824
15803
  // src/export_registrations/collection.registration.ts
14825
- var import_core48 = require("@axiom-lattice/core");
15804
+ var import_core49 = require("@axiom-lattice/core");
14826
15805
  function getStore8() {
14827
- return (0, import_core48.getStoreLattice)("default", "collection").store;
15806
+ return (0, import_core49.getStoreLattice)("default", "collection").store;
14828
15807
  }
14829
15808
  var collectionRegistration = {
14830
15809
  entityType: "collection",
@@ -14895,11 +15874,11 @@ var collectionRegistration = {
14895
15874
  };
14896
15875
 
14897
15876
  // src/export_registrations/channel-installation.registration.ts
14898
- var import_core49 = require("@axiom-lattice/core");
15877
+ var import_core50 = require("@axiom-lattice/core");
14899
15878
  var import_crypto10 = require("crypto");
14900
15879
  var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
14901
15880
  function getStore9() {
14902
- return (0, import_core49.getStoreLattice)("default", "channelInstallation").store;
15881
+ return (0, import_core50.getStoreLattice)("default", "channelInstallation").store;
14903
15882
  }
14904
15883
  var channelInstallationRegistration = {
14905
15884
  entityType: "channel_installation",
@@ -14998,9 +15977,9 @@ var channelInstallationRegistration = {
14998
15977
  };
14999
15978
 
15000
15979
  // src/export_registrations/menu.registration.ts
15001
- var import_core50 = require("@axiom-lattice/core");
15980
+ var import_core51 = require("@axiom-lattice/core");
15002
15981
  function getStore10() {
15003
- return (0, import_core50.getStoreLattice)("default", "menu").store;
15982
+ return (0, import_core51.getStoreLattice)("default", "menu").store;
15004
15983
  }
15005
15984
  var menuRegistration = {
15006
15985
  entityType: "menu",
@@ -15078,9 +16057,9 @@ var menuRegistration = {
15078
16057
  };
15079
16058
 
15080
16059
  // src/export_registrations/eval.registration.ts
15081
- var import_core51 = require("@axiom-lattice/core");
16060
+ var import_core52 = require("@axiom-lattice/core");
15082
16061
  function getStore11() {
15083
- return (0, import_core51.getStoreLattice)("default", "eval").store;
16062
+ return (0, import_core52.getStoreLattice)("default", "eval").store;
15084
16063
  }
15085
16064
  var evalRegistration = {
15086
16065
  entityType: "eval",
@@ -15406,7 +16385,7 @@ var evalRegistration = {
15406
16385
 
15407
16386
  // src/export_registrations/index.ts
15408
16387
  function registerAllBuiltinEntities() {
15409
- const registry = import_core52.ExportableEntityRegistry.getInstance();
16388
+ const registry = import_core53.ExportableEntityRegistry.getInstance();
15410
16389
  registry.register(skillRegistration);
15411
16390
  registry.register(agentRegistration);
15412
16391
  registry.register(databaseConfigRegistration);
@@ -15420,7 +16399,7 @@ function registerAllBuiltinEntities() {
15420
16399
  }
15421
16400
 
15422
16401
  // src/router/MessageRouter.ts
15423
- var import_core53 = require("@axiom-lattice/core");
16402
+ var import_core54 = require("@axiom-lattice/core");
15424
16403
  var import_crypto11 = require("crypto");
15425
16404
  var BindingNotFoundError = class extends Error {
15426
16405
  constructor(message) {
@@ -15590,7 +16569,7 @@ var MessageRouter = class {
15590
16569
  channel: message.channel,
15591
16570
  adapterChannel: adapter.channel
15592
16571
  }, "Thread resolved by adapter strategy");
15593
- const threadStore = (0, import_core53.getStoreLattice)("default", "thread").store;
16572
+ const threadStore = (0, import_core54.getStoreLattice)("default", "thread").store;
15594
16573
  try {
15595
16574
  await threadStore.createThread(
15596
16575
  tenantId,
@@ -15627,7 +16606,7 @@ var MessageRouter = class {
15627
16606
  }
15628
16607
  }
15629
16608
  if (!threadId) {
15630
- const threadStore = (0, import_core53.getStoreLattice)("default", "thread").store;
16609
+ const threadStore = (0, import_core54.getStoreLattice)("default", "thread").store;
15631
16610
  const newThreadId = (0, import_crypto11.randomUUID)();
15632
16611
  console.log({
15633
16612
  event: "dispatch:thread:create",
@@ -15667,7 +16646,7 @@ var MessageRouter = class {
15667
16646
  senderId: message.sender.id,
15668
16647
  contentLength: message.content.text.length
15669
16648
  }, "Dispatching to agent");
15670
- const agent = import_core53.agentInstanceManager.getAgent({
16649
+ const agent = import_core54.agentInstanceManager.getAgent({
15671
16650
  tenant_id: tenantId,
15672
16651
  assistant_id: agentId,
15673
16652
  thread_id: threadId,
@@ -15875,13 +16854,13 @@ function createRateLimitMiddleware(maxRequests = 10, windowMs = 60 * 1e3, maxEnt
15875
16854
 
15876
16855
  // src/router/middlewares/auditLogger.ts
15877
16856
  init_Logger();
15878
- var logger3 = new Logger({ serviceName: "lattice/gateway/audit" });
16857
+ var logger4 = new Logger({ serviceName: "lattice/gateway/audit" });
15879
16858
  function createAuditLoggerMiddleware() {
15880
16859
  return async (ctx, next) => {
15881
16860
  const start2 = Date.now();
15882
16861
  try {
15883
16862
  await next();
15884
- logger3.info("message routed", {
16863
+ logger4.info("message routed", {
15885
16864
  event: "message:routed",
15886
16865
  channel: ctx.inboundMessage.channel,
15887
16866
  senderId: ctx.inboundMessage.sender.id,
@@ -15891,7 +16870,7 @@ function createAuditLoggerMiddleware() {
15891
16870
  status: "success"
15892
16871
  });
15893
16872
  } catch (error) {
15894
- logger3.error(
16873
+ logger4.error(
15895
16874
  error instanceof Error ? error.message : String(error),
15896
16875
  {
15897
16876
  event: "message:error",
@@ -15907,7 +16886,7 @@ function createAuditLoggerMiddleware() {
15907
16886
  }
15908
16887
 
15909
16888
  // src/index.ts
15910
- var import_core58 = require("@axiom-lattice/core");
16889
+ var import_core59 = require("@axiom-lattice/core");
15911
16890
 
15912
16891
  // src/swagger.ts
15913
16892
  var import_swagger = __toESM(require("@fastify/swagger"));
@@ -15972,7 +16951,7 @@ var configureSwagger = async (app2, customSwaggerConfig, customSwaggerUiConfig)
15972
16951
  };
15973
16952
 
15974
16953
  // src/services/agent_task_consumer.ts
15975
- var import_core54 = require("@axiom-lattice/core");
16954
+ var import_core55 = require("@axiom-lattice/core");
15976
16955
  var handleAgentTask = async (taskRequest, retryCount = 0) => {
15977
16956
  const {
15978
16957
  assistant_id,
@@ -15990,17 +16969,17 @@ var handleAgentTask = async (taskRequest, retryCount = 0) => {
15990
16969
  console.log(
15991
16970
  `\u5F00\u59CB\u5904\u7406\u4EFB\u52A1 [assistant_id: ${assistant_id}, thread_id: ${thread_id}]`
15992
16971
  );
15993
- const agent = import_core54.agentInstanceManager.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig });
15994
- await agent.addMessage({ input, command, custom_run_config: runConfig }, import_core54.QueueMode.STEER);
16972
+ const agent = import_core55.agentInstanceManager.getAgent({ assistant_id, thread_id, tenant_id, workspace_id: runConfig?.workspaceId, project_id: runConfig?.projectId, custom_run_config: runConfig });
16973
+ await agent.addMessage({ input, command, custom_run_config: runConfig }, import_core55.QueueMode.STEER);
15995
16974
  if (callback_event) {
15996
16975
  agent.subscribeOnce("message:completed", (evt) => {
15997
- import_core54.eventBus.publish(callback_event, {
16976
+ import_core55.eventBus.publish(callback_event, {
15998
16977
  success: true,
15999
16978
  state: evt.state
16000
16979
  });
16001
16980
  if (main_thread_id && main_tenant_id) {
16002
16981
  try {
16003
- const mainAgent = import_core54.agentInstanceManager.getAgent({
16982
+ const mainAgent = import_core55.agentInstanceManager.getAgent({
16004
16983
  assistant_id: main_assistant_id ?? assistant_id,
16005
16984
  thread_id: main_thread_id,
16006
16985
  tenant_id: main_tenant_id,
@@ -16030,7 +17009,7 @@ ${summary}`
16030
17009
  }
16031
17010
  });
16032
17011
  agent.subscribeOnce("message:interrupted", (evt) => {
16033
- import_core54.eventBus.publish(callback_event, {
17012
+ import_core55.eventBus.publish(callback_event, {
16034
17013
  success: true,
16035
17014
  state: evt.state
16036
17015
  });
@@ -16049,11 +17028,11 @@ ${summary}`
16049
17028
  console.log(
16050
17029
  `\u5C06\u5728 ${delayMs}ms \u540E\u91CD\u8BD5\u4EFB\u52A1 (${nextRetryCount}/${maxRetries})`
16051
17030
  );
16052
- await new Promise((resolve3) => setTimeout(resolve3, delayMs));
17031
+ await new Promise((resolve4) => setTimeout(resolve4, delayMs));
16053
17032
  return handleAgentTask(taskRequest, nextRetryCount);
16054
17033
  }
16055
17034
  if (callback_event) {
16056
- import_core54.eventBus.publish(callback_event, {
17035
+ import_core55.eventBus.publish(callback_event, {
16057
17036
  success: false,
16058
17037
  error: error instanceof Error ? error.message : String(error)
16059
17038
  });
@@ -16090,7 +17069,7 @@ var _AgentTaskConsumer = class _AgentTaskConsumer {
16090
17069
  * 初始化事件监听和队列轮询
16091
17070
  */
16092
17071
  initialize() {
16093
- import_core54.eventBus.subscribe(import_core54.AGENT_TASK_EVENT, this.trigger_agent_task.bind(this));
17072
+ import_core55.eventBus.subscribe(import_core55.AGENT_TASK_EVENT, this.trigger_agent_task.bind(this));
16094
17073
  this.startPollingQueue();
16095
17074
  console.log("Agent\u4EFB\u52A1\u6D88\u8D39\u8005\u5DF2\u542F\u52A8\u5E76\u76D1\u542C\u4EFB\u52A1\u4E8B\u4EF6\u548C\u961F\u5217");
16096
17075
  }
@@ -16209,7 +17188,7 @@ var _AgentTaskConsumer = class _AgentTaskConsumer {
16209
17188
  handleAgentTask(taskRequest).catch((error) => {
16210
17189
  console.error("\u5904\u7406Agent\u4EFB\u52A1\u65F6\u53D1\u751F\u672A\u6355\u83B7\u7684\u9519\u8BEF:", error);
16211
17190
  if (taskRequest.callback_event) {
16212
- import_core54.eventBus.publish(taskRequest.callback_event, {
17191
+ import_core55.eventBus.publish(taskRequest.callback_event, {
16213
17192
  success: false,
16214
17193
  error: error instanceof Error ? error.message : String(error)
16215
17194
  });
@@ -16224,7 +17203,7 @@ _AgentTaskConsumer.agent_run_endpoint = "http://localhost:4001/api/runs";
16224
17203
  var AgentTaskConsumer = _AgentTaskConsumer;
16225
17204
 
16226
17205
  // src/index.ts
16227
- var import_core59 = require("@axiom-lattice/core");
17206
+ var import_core60 = require("@axiom-lattice/core");
16228
17207
  var import_protocols9 = require("@axiom-lattice/protocols");
16229
17208
  var import_meta = {};
16230
17209
  process.on("unhandledRejection", (reason, promise) => {
@@ -16238,13 +17217,13 @@ var DEFAULT_LOGGER_CONFIG = {
16238
17217
  loggerName: "lattice/gateway"
16239
17218
  };
16240
17219
  var loggerLattice = initializeLogger(DEFAULT_LOGGER_CONFIG);
16241
- var logger5 = loggerLattice.client;
17220
+ var logger6 = loggerLattice.client;
16242
17221
  function initializeLogger(config) {
16243
- if (import_core59.loggerLatticeManager.hasLattice("default")) {
16244
- import_core59.loggerLatticeManager.removeLattice("default");
17222
+ if (import_core60.loggerLatticeManager.hasLattice("default")) {
17223
+ import_core60.loggerLatticeManager.removeLattice("default");
16245
17224
  }
16246
- (0, import_core59.registerLoggerLattice)("default", config);
16247
- return (0, import_core59.getLoggerLattice)("default");
17225
+ (0, import_core60.registerLoggerLattice)("default", config);
17226
+ return (0, import_core60.getLoggerLattice)("default");
16248
17227
  }
16249
17228
  var app = (0, import_fastify.default)({
16250
17229
  logger: false,
@@ -16271,7 +17250,9 @@ var getHeaderValue = (header) => {
16271
17250
  var authRequired = process.env.AUTH_REQUIRED === "true";
16272
17251
  registerConsoleAuthHook(app, { authRequired });
16273
17252
  app.addHook("onRequest", (request, reply, done) => {
16274
- if (!request.headers["x-project-id"]) {
17253
+ const requestPath = request.url.split("?", 1)[0];
17254
+ const isAgentRunRequest = requestPath === "/api/runs" || requestPath === "/api/resume_stream" || requestPath.includes("/threads/") && requestPath.endsWith("/recover");
17255
+ if (!request.headers["x-project-id"] && !isAgentRunRequest) {
16275
17256
  request.headers["x-project-id"] = "default";
16276
17257
  }
16277
17258
  done();
@@ -16321,7 +17302,7 @@ app.setErrorHandler((error, request, reply) => {
16321
17302
  "x-request-id": getHeaderValue(request.headers["x-request-id"]),
16322
17303
  "x-user-id": getHeaderValue(request.headers["x-user-id"])
16323
17304
  };
16324
- logger5.error(
17305
+ logger6.error(
16325
17306
  `\u8BF7\u6C42\u9519\u8BEF: ${request.method} ${request.url} error:${error.message}`,
16326
17307
  {
16327
17308
  ...context,
@@ -16337,7 +17318,7 @@ app.setErrorHandler((error, request, reply) => {
16337
17318
  });
16338
17319
  function getConfiguredSandboxProvider() {
16339
17320
  const sandboxProviderType = process.env.SANDBOX_PROVIDER_TYPE || "microsandbox-remote";
16340
- return (0, import_core59.createSandboxProvider)({
17321
+ return (0, import_core60.createSandboxProvider)({
16341
17322
  type: sandboxProviderType,
16342
17323
  remoteBaseURL: process.env.SANDBOX_BASE_URL,
16343
17324
  microsandboxServiceBaseURL: process.env.MICROSANDBOX_SERVICE_BASE_URL,
@@ -16353,37 +17334,37 @@ function getConfiguredSandboxProvider() {
16353
17334
  }
16354
17335
  async function restoreMcpConnections() {
16355
17336
  try {
16356
- const storeLattice = (0, import_core59.getStoreLattice)("default", "mcp");
17337
+ const storeLattice = (0, import_core60.getStoreLattice)("default", "mcp");
16357
17338
  const store2 = storeLattice.store;
16358
17339
  if (!store2) {
16359
- logger5.info("MCP store not configured, skipping connection restoration");
17340
+ logger6.info("MCP store not configured, skipping connection restoration");
16360
17341
  return;
16361
17342
  }
16362
17343
  const configs = await store2.getAllConfigs("default");
16363
17344
  const connectedConfigs = configs.filter((c2) => c2.status === "connected");
16364
17345
  if (connectedConfigs.length === 0) return;
16365
- logger5.info(`Restoring ${connectedConfigs.length} MCP server connections...`);
17346
+ logger6.info(`Restoring ${connectedConfigs.length} MCP server connections...`);
16366
17347
  const { convertToConnection: convertToConnection2 } = await Promise.resolve().then(() => (init_mcp_configs(), mcp_configs_exports));
16367
17348
  for (const config of connectedConfigs) {
16368
17349
  try {
16369
17350
  const connection = convertToConnection2(config.config);
16370
- import_core59.mcpManager.addServer(config.key, connection);
17351
+ import_core60.mcpManager.addServer(config.key, connection);
16371
17352
  } catch (err) {
16372
- logger5.warn(`Failed to prepare MCP server "${config.key}" for restoration`, { error: err });
17353
+ logger6.warn(`Failed to prepare MCP server "${config.key}" for restoration`, { error: err });
16373
17354
  }
16374
17355
  }
16375
- await import_core59.mcpManager.connect();
17356
+ await import_core60.mcpManager.connect();
16376
17357
  for (const config of connectedConfigs) {
16377
17358
  try {
16378
- await import_core59.mcpManager.registerToolsToToolLattice(config.key, config.selectedTools);
16379
- logger5.info(`MCP server "${config.key}" restored with ${config.selectedTools.length} tools`);
17359
+ await import_core60.mcpManager.registerToolsToToolLattice(config.key, config.selectedTools);
17360
+ logger6.info(`MCP server "${config.key}" restored with ${config.selectedTools.length} tools`);
16380
17361
  } catch (err) {
16381
- logger5.warn(`Failed to register tools for MCP server "${config.key}"`, { error: err });
17362
+ logger6.warn(`Failed to register tools for MCP server "${config.key}"`, { error: err });
16382
17363
  }
16383
17364
  }
16384
- logger5.info(`MCP connection restoration complete: ${connectedConfigs.length} servers`);
17365
+ logger6.info(`MCP connection restoration complete: ${connectedConfigs.length} servers`);
16385
17366
  } catch (err) {
16386
- logger5.error("MCP connection restoration failed", { error: err });
17367
+ logger6.error("MCP connection restoration failed", { error: err });
16387
17368
  }
16388
17369
  }
16389
17370
  var start = async (config) => {
@@ -16396,7 +17377,7 @@ var start = async (config) => {
16396
17377
  file: config.loggerConfig.file || DEFAULT_LOGGER_CONFIG.file
16397
17378
  };
16398
17379
  loggerLattice = initializeLogger(loggerConfig);
16399
- logger5 = loggerLattice.client;
17380
+ logger6 = loggerLattice.client;
16400
17381
  }
16401
17382
  app.decorate("loggerLattice", loggerLattice);
16402
17383
  let channelDeps;
@@ -16408,7 +17389,7 @@ var start = async (config) => {
16408
17389
  const { getStoreLattice: getStore12 } = await import("@axiom-lattice/core");
16409
17390
  const bindingStore = getStore12("default", "channelBinding").store;
16410
17391
  const installationStore = getStore12("default", "channelInstallation").store;
16411
- (0, import_core58.setBindingRegistry)(bindingStore);
17392
+ (0, import_core59.setBindingRegistry)(bindingStore);
16412
17393
  const router = new MessageRouter({
16413
17394
  middlewares: [
16414
17395
  createDeduplicationMiddleware(),
@@ -16421,15 +17402,15 @@ var start = async (config) => {
16421
17402
  });
16422
17403
  channelDeps = { router, installationStore };
16423
17404
  } catch (err) {
16424
- logger5.warn("Channel infrastructure unavailable", {
17405
+ logger6.warn("Channel infrastructure unavailable", {
16425
17406
  error: err instanceof Error ? err.message : String(err)
16426
17407
  });
16427
17408
  }
16428
- (0, import_core58.setEvalRunService)(evalRunner);
17409
+ (0, import_core59.setEvalRunService)(evalRunner);
16429
17410
  try {
16430
- const menuStore = (0, import_core59.getStoreLattice)("default", "menu").store;
16431
- (0, import_core58.setMenuRegistry)(menuStore);
16432
- logger5.info("Menu registry initialized");
17411
+ const menuStore = (0, import_core60.getStoreLattice)("default", "menu").store;
17412
+ (0, import_core59.setMenuRegistry)(menuStore);
17413
+ logger6.info("Menu registry initialized");
16433
17414
  } catch {
16434
17415
  }
16435
17416
  registerAllBuiltinEntities();
@@ -16441,10 +17422,10 @@ var start = async (config) => {
16441
17422
  const { A2AAuthService: A2AAuthService2, parseEnvKeys: parseEnvKeys2 } = await Promise.resolve().then(() => (init_A2AAuthService(), A2AAuthService_exports));
16442
17423
  const { registerA2AStandardRoutes: registerA2AStandardRoutes2 } = await Promise.resolve().then(() => (init_a2a_standard(), a2a_standard_exports));
16443
17424
  const a2a = await Promise.resolve().then(() => (init_a2a(), a2a_exports));
16444
- const a2aKeyStore = (0, import_core59.getStoreLattice)("default", "a2aApiKey").store;
16445
- const assistantStore = (0, import_core59.getStoreLattice)("default", "assistant").store;
16446
- const projectStore = (0, import_core59.getStoreLattice)("default", "project").store;
16447
- const taskStore = (0, import_core59.getStoreLattice)("default", "task").store;
17425
+ const a2aKeyStore = (0, import_core60.getStoreLattice)("default", "a2aApiKey").store;
17426
+ const assistantStore = (0, import_core60.getStoreLattice)("default", "assistant").store;
17427
+ const projectStore = (0, import_core60.getStoreLattice)("default", "project").store;
17428
+ const taskStore = (0, import_core60.getStoreLattice)("default", "task").store;
16448
17429
  const authService = new A2AAuthService2({
16449
17430
  keyStore: a2aKeyStore,
16450
17431
  assistantStore,
@@ -16462,30 +17443,30 @@ var start = async (config) => {
16462
17443
  getProjectStore: () => projectStore
16463
17444
  });
16464
17445
  a2a.registerA2ARoutes(app);
16465
- logger5.info("A2A standard routes registered");
17446
+ logger6.info("A2A standard routes registered");
16466
17447
  } catch (err) {
16467
- logger5.warn("A2A standard routes unavailable", {
17448
+ logger6.warn("A2A standard routes unavailable", {
16468
17449
  error: err instanceof Error ? err.message : String(err)
16469
17450
  });
16470
17451
  }
16471
- if (!import_core59.sandboxLatticeManager.hasLattice("default")) {
16472
- import_core59.sandboxLatticeManager.registerLattice("default", getConfiguredSandboxProvider());
16473
- logger5.info("Registered sandbox manager from env configuration");
17452
+ if (!import_core60.sandboxLatticeManager.hasLattice("default")) {
17453
+ import_core60.sandboxLatticeManager.registerLattice("default", getConfiguredSandboxProvider());
17454
+ logger6.info("Registered sandbox manager from env configuration");
16474
17455
  }
16475
17456
  try {
16476
17457
  const { ResourceController: ResourceController2 } = await Promise.resolve().then(() => (init_resources(), resources_exports));
16477
- const sharedResourceStore = (0, import_core59.getStoreLattice)("default", "sharedResource").store;
16478
- const cache = new import_core59.TokenCache();
17458
+ const sharedResourceStore = (0, import_core60.getStoreLattice)("default", "sharedResource").store;
17459
+ const cache = new import_core60.TokenCache();
16479
17460
  const resourceController = new ResourceController2({
16480
17461
  store: sharedResourceStore,
16481
- sandboxManager: import_core59.sandboxLatticeManager,
17462
+ sandboxManager: import_core60.sandboxLatticeManager,
16482
17463
  cache,
16483
- logger: logger5
17464
+ logger: logger6
16484
17465
  });
16485
17466
  registerResourceRoutes(app, resourceController);
16486
- logger5.info("Resource share routes registered");
17467
+ logger6.info("Resource share routes registered");
16487
17468
  } catch (err) {
16488
- logger5.warn("Resource share infrastructure unavailable", {
17469
+ logger6.warn("Resource share infrastructure unavailable", {
16489
17470
  error: err instanceof Error ? err.message : String(err)
16490
17471
  });
16491
17472
  }
@@ -16497,14 +17478,14 @@ var start = async (config) => {
16497
17478
  { deps: { router: channelDeps.router } }
16498
17479
  );
16499
17480
  } catch (err) {
16500
- logger5.error("Failed to start channel connections", {
17481
+ logger6.error("Failed to start channel connections", {
16501
17482
  error: err instanceof Error ? err.message : String(err)
16502
17483
  });
16503
17484
  }
16504
17485
  }
16505
17486
  const target_port = config?.port || Number(process.env.PORT) || 4001;
16506
17487
  await app.listen({ port: target_port, host: "0.0.0.0" });
16507
- logger5.info(`Lattice Gateway is running on port: ${target_port}`);
17488
+ logger6.info(`Lattice Gateway is running on port: ${target_port}`);
16508
17489
  const queueServiceConfig = config?.queueServiceConfig;
16509
17490
  if (queueServiceConfig) {
16510
17491
  setQueueServiceType(queueServiceConfig.type);
@@ -16514,17 +17495,17 @@ var start = async (config) => {
16514
17495
  }
16515
17496
  }
16516
17497
  if (process.env.AXIOM_RESTORE_PENDING_ON_STARTUP === "true") {
16517
- import_core59.agentInstanceManager.restore().then((stats) => {
16518
- logger5.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
17498
+ import_core60.agentInstanceManager.restore().then((stats) => {
17499
+ logger6.info(`Agent recovery complete: ${stats.restored} threads restored, ${stats.errors} errors`);
16519
17500
  }).catch((error) => {
16520
- logger5.error("Agent recovery failed", { error });
17501
+ logger6.error("Agent recovery failed", { error });
16521
17502
  });
16522
17503
  }
16523
17504
  restoreMcpConnections().catch((error) => {
16524
- logger5.error("MCP connection restoration failed", { error });
17505
+ logger6.error("MCP connection restoration failed", { error });
16525
17506
  });
16526
17507
  } catch (err) {
16527
- logger5.error("Server start failed", { error: err });
17508
+ logger6.error("Server start failed", { error: err });
16528
17509
  process.exit(1);
16529
17510
  }
16530
17511
  };