@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 +1235 -254
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1116 -138
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
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
|
|
198
|
+
const stat3 = await fs3.stat(targetPath);
|
|
199
199
|
const uri = path3.posix.join("/", relativeDirectory, filename);
|
|
200
|
-
return { uri, name: filename, 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
|
|
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
|
-
|
|
1047
|
+
import_zod8 = require("zod");
|
|
1048
1048
|
init_wechat_client();
|
|
1049
1049
|
init_context_store();
|
|
1050
1050
|
init_Logger();
|
|
1051
|
-
|
|
1052
|
-
wechatConfigSchema =
|
|
1053
|
-
botToken:
|
|
1054
|
-
uin:
|
|
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
|
-
|
|
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
|
-
|
|
1120
|
+
logger5.warn("WeChat installation missing botToken, skipping", { installationId });
|
|
1121
1121
|
return;
|
|
1122
1122
|
}
|
|
1123
1123
|
if (activeConnections2.has(installationId)) {
|
|
1124
|
-
|
|
1124
|
+
logger5.warn("WeChat polling already running for installation, skipping", { installationId });
|
|
1125
1125
|
return;
|
|
1126
1126
|
}
|
|
1127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1208
|
+
logger5.info("WeChat poll aborted by signal", { installationId });
|
|
1209
1209
|
return;
|
|
1210
1210
|
}
|
|
1211
|
-
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
1226
|
+
logger5.warn("WeChat polling not running, nothing to disconnect", { installationId });
|
|
1227
1227
|
return;
|
|
1228
1228
|
}
|
|
1229
|
-
|
|
1229
|
+
logger5.info("WeChat polling disconnecting", { installationId });
|
|
1230
1230
|
state.abortController.abort();
|
|
1231
1231
|
clearInterval(state.heartbeatTimer);
|
|
1232
1232
|
activeConnections2.delete(installationId);
|
|
1233
|
-
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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,
|
|
2503
|
-
const getTaskWorkItemStore2 = deps.getTaskWorkItemStore ?? (() => (0,
|
|
2504
|
-
const getAssistantStore3 = deps.getAssistantStore ?? (() => (0,
|
|
2505
|
-
const getProjectStore2 = deps.getProjectStore ?? (() => (0,
|
|
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,
|
|
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
|
|
2524
|
-
if (
|
|
2523
|
+
for (const tenant2 of tenants) {
|
|
2524
|
+
if (tenant2.id === tenantId) continue;
|
|
2525
2525
|
const found = await getAssistantStore3().getAssistantById(
|
|
2526
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
3354
|
-
if (!
|
|
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((
|
|
4129
|
-
resolveClosed =
|
|
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((
|
|
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
|
-
|
|
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"]
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
|
|
10174
|
-
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
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
|
-
|
|
10189
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
10216
|
-
|
|
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
|
-
|
|
10225
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
10629
|
-
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
|
|
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
|
-
|
|
10640
|
-
}
|
|
11228
|
+
};
|
|
10641
11229
|
const handler = (event) => {
|
|
10642
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
10788
|
-
return reply.status(201).send({ success: true, data:
|
|
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
|
|
10793
|
-
if (!
|
|
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:
|
|
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
|
|
10805
|
-
if (!
|
|
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:
|
|
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
|
|
11586
|
+
const tenant2 = await this.tenantStore.getTenantById(link.tenantId);
|
|
10965
11587
|
return {
|
|
10966
11588
|
...link,
|
|
10967
|
-
tenant:
|
|
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
|
|
11001
|
-
if (!
|
|
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
|
|
11096
|
-
if (!
|
|
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 (
|
|
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((
|
|
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:
|
|
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
|
|
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,
|
|
13902
|
-
const assistantStore = (0,
|
|
13903
|
-
const projectStore = (0,
|
|
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) =>
|
|
14888
|
+
modelRegistry: { has: (key) => import_core42.modelLatticeManager.hasLattice(key) },
|
|
13910
14889
|
configuredAgentResolver: createConfiguredAgentResolver({
|
|
13911
|
-
hydrate: (tenantId) =>
|
|
13912
|
-
has: (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,
|
|
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
|
|
13926
|
-
const config =
|
|
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) =>
|
|
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 }) =>
|
|
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 }) =>
|
|
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
|
|
13963
|
-
return
|
|
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
|
|
15242
|
+
var import_core53 = require("@axiom-lattice/core");
|
|
14264
15243
|
|
|
14265
15244
|
// src/export_registrations/skill.registration.ts
|
|
14266
|
-
var
|
|
15245
|
+
var import_core43 = require("@axiom-lattice/core");
|
|
14267
15246
|
function getStore2(context) {
|
|
14268
|
-
return new
|
|
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,
|
|
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,
|
|
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
|
|
15389
|
+
var import_core44 = require("@axiom-lattice/core");
|
|
14411
15390
|
function getStore3() {
|
|
14412
|
-
return (0,
|
|
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
|
|
15471
|
+
var import_core45 = require("@axiom-lattice/core");
|
|
14493
15472
|
function getStore4() {
|
|
14494
|
-
return (0,
|
|
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
|
|
15553
|
+
var import_core46 = require("@axiom-lattice/core");
|
|
14575
15554
|
function getStore5() {
|
|
14576
|
-
return (0,
|
|
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
|
|
15635
|
+
var import_core47 = require("@axiom-lattice/core");
|
|
14657
15636
|
function getStore6() {
|
|
14658
|
-
return (0,
|
|
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
|
|
15720
|
+
var import_core48 = require("@axiom-lattice/core");
|
|
14742
15721
|
function getStore7() {
|
|
14743
|
-
return (0,
|
|
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
|
|
15804
|
+
var import_core49 = require("@axiom-lattice/core");
|
|
14826
15805
|
function getStore8() {
|
|
14827
|
-
return (0,
|
|
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
|
|
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,
|
|
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
|
|
15980
|
+
var import_core51 = require("@axiom-lattice/core");
|
|
15002
15981
|
function getStore10() {
|
|
15003
|
-
return (0,
|
|
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
|
|
16060
|
+
var import_core52 = require("@axiom-lattice/core");
|
|
15082
16061
|
function getStore11() {
|
|
15083
|
-
return (0,
|
|
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 =
|
|
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
|
|
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,
|
|
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,
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
15994
|
-
await agent.addMessage({ input, command, custom_run_config: runConfig },
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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((
|
|
17031
|
+
await new Promise((resolve4) => setTimeout(resolve4, delayMs));
|
|
16053
17032
|
return handleAgentTask(taskRequest, nextRetryCount);
|
|
16054
17033
|
}
|
|
16055
17034
|
if (callback_event) {
|
|
16056
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
17220
|
+
var logger6 = loggerLattice.client;
|
|
16242
17221
|
function initializeLogger(config) {
|
|
16243
|
-
if (
|
|
16244
|
-
|
|
17222
|
+
if (import_core60.loggerLatticeManager.hasLattice("default")) {
|
|
17223
|
+
import_core60.loggerLatticeManager.removeLattice("default");
|
|
16245
17224
|
}
|
|
16246
|
-
(0,
|
|
16247
|
-
return (0,
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
17337
|
+
const storeLattice = (0, import_core60.getStoreLattice)("default", "mcp");
|
|
16357
17338
|
const store2 = storeLattice.store;
|
|
16358
17339
|
if (!store2) {
|
|
16359
|
-
|
|
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
|
-
|
|
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
|
-
|
|
17351
|
+
import_core60.mcpManager.addServer(config.key, connection);
|
|
16371
17352
|
} catch (err) {
|
|
16372
|
-
|
|
17353
|
+
logger6.warn(`Failed to prepare MCP server "${config.key}" for restoration`, { error: err });
|
|
16373
17354
|
}
|
|
16374
17355
|
}
|
|
16375
|
-
await
|
|
17356
|
+
await import_core60.mcpManager.connect();
|
|
16376
17357
|
for (const config of connectedConfigs) {
|
|
16377
17358
|
try {
|
|
16378
|
-
await
|
|
16379
|
-
|
|
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
|
-
|
|
17362
|
+
logger6.warn(`Failed to register tools for MCP server "${config.key}"`, { error: err });
|
|
16382
17363
|
}
|
|
16383
17364
|
}
|
|
16384
|
-
|
|
17365
|
+
logger6.info(`MCP connection restoration complete: ${connectedConfigs.length} servers`);
|
|
16385
17366
|
} catch (err) {
|
|
16386
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
17405
|
+
logger6.warn("Channel infrastructure unavailable", {
|
|
16425
17406
|
error: err instanceof Error ? err.message : String(err)
|
|
16426
17407
|
});
|
|
16427
17408
|
}
|
|
16428
|
-
(0,
|
|
17409
|
+
(0, import_core59.setEvalRunService)(evalRunner);
|
|
16429
17410
|
try {
|
|
16430
|
-
const menuStore = (0,
|
|
16431
|
-
(0,
|
|
16432
|
-
|
|
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,
|
|
16445
|
-
const assistantStore = (0,
|
|
16446
|
-
const projectStore = (0,
|
|
16447
|
-
const taskStore = (0,
|
|
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
|
-
|
|
17446
|
+
logger6.info("A2A standard routes registered");
|
|
16466
17447
|
} catch (err) {
|
|
16467
|
-
|
|
17448
|
+
logger6.warn("A2A standard routes unavailable", {
|
|
16468
17449
|
error: err instanceof Error ? err.message : String(err)
|
|
16469
17450
|
});
|
|
16470
17451
|
}
|
|
16471
|
-
if (!
|
|
16472
|
-
|
|
16473
|
-
|
|
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,
|
|
16478
|
-
const cache = new
|
|
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:
|
|
17462
|
+
sandboxManager: import_core60.sandboxLatticeManager,
|
|
16482
17463
|
cache,
|
|
16483
|
-
logger:
|
|
17464
|
+
logger: logger6
|
|
16484
17465
|
});
|
|
16485
17466
|
registerResourceRoutes(app, resourceController);
|
|
16486
|
-
|
|
17467
|
+
logger6.info("Resource share routes registered");
|
|
16487
17468
|
} catch (err) {
|
|
16488
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
16518
|
-
|
|
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
|
-
|
|
17501
|
+
logger6.error("Agent recovery failed", { error });
|
|
16521
17502
|
});
|
|
16522
17503
|
}
|
|
16523
17504
|
restoreMcpConnections().catch((error) => {
|
|
16524
|
-
|
|
17505
|
+
logger6.error("MCP connection restoration failed", { error });
|
|
16525
17506
|
});
|
|
16526
17507
|
} catch (err) {
|
|
16527
|
-
|
|
17508
|
+
logger6.error("Server start failed", { error: err });
|
|
16528
17509
|
process.exit(1);
|
|
16529
17510
|
}
|
|
16530
17511
|
};
|