@axiom-lattice/gateway 3.0.2 → 3.0.3
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 +31 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -6
- package/.eslintrc.json +0 -22
- package/.turbo/turbo-build.log +0 -46
- package/AGENTS.md +0 -112
- package/CHANGELOG.md +0 -1190
- package/LOGGER_CONFIG.md +0 -135
- package/RESUME_STREAM_CONTENT_BASED.md +0 -326
- package/RESUME_STREAM_README.md +0 -388
- package/jest.config.js +0 -72
- package/public/sdk/README.md +0 -695
- package/public/sdk/dashboard-engine-skill.md +0 -1122
- package/public/sdk/dashboard-single-file-spec.md +0 -357
- package/public/sdk/data-query-sdk-skill.md +0 -307
- package/public/sdk/data-query-sdk.d.ts +0 -252
- package/public/sdk/data-query-sdk.js +0 -970
- package/public/sdk/occupancy-dashboard.html +0 -363
- package/public/sdk/test-dashboard.html +0 -690
- package/scripts/seed-lark-installation.ts +0 -54
- package/src/__tests__/__mocks__/e2b.ts +0 -1
- package/src/__tests__/a2a.test.ts +0 -428
- package/src/__tests__/channel-installations.test.ts +0 -199
- package/src/__tests__/data-query.test.ts +0 -77
- package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
- package/src/__tests__/setup.ts +0 -5
- package/src/__tests__/workspace.test.ts +0 -400
- package/src/channels/__tests__/routes.test.ts +0 -50
- package/src/channels/lark/LarkChannelAdapter.ts +0 -217
- package/src/channels/lark/README.md +0 -187
- package/src/channels/lark/__tests__/controller.test.ts +0 -136
- package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
- package/src/channels/lark/__tests__/parser.test.ts +0 -72
- package/src/channels/lark/__tests__/sender.test.ts +0 -37
- package/src/channels/lark/__tests__/verification.test.ts +0 -157
- package/src/channels/lark/controller.ts +0 -48
- package/src/channels/lark/mapping-service.ts +0 -138
- package/src/channels/lark/parser.ts +0 -68
- package/src/channels/lark/routes.ts +0 -22
- package/src/channels/lark/sender.ts +0 -58
- package/src/channels/lark/types.ts +0 -20
- package/src/channels/lark/verification.ts +0 -32
- package/src/channels/registry.ts +0 -20
- package/src/channels/routes.ts +0 -30
- package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
- package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
- package/src/channels/wechat/context-store.ts +0 -54
- package/src/channels/wechat/controller.ts +0 -59
- package/src/channels/wechat/routes.ts +0 -7
- package/src/channels/wechat/types.ts +0 -62
- package/src/channels/wechat/wechat-client.ts +0 -162
- package/src/config/local-a2a-templates.ts +0 -59
- package/src/config.ts +0 -124
- package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
- package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
- package/src/controllers/__tests__/mime.test.ts +0 -74
- package/src/controllers/__tests__/resources.test.ts +0 -122
- package/src/controllers/__tests__/run.test.ts +0 -115
- package/src/controllers/__tests__/tasks.test.ts +0 -215
- package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
- package/src/controllers/agent_task.ts +0 -72
- package/src/controllers/assistant.ts +0 -490
- package/src/controllers/auth.ts +0 -524
- package/src/controllers/channel-bindings.ts +0 -135
- package/src/controllers/channel-installations.ts +0 -409
- package/src/controllers/collections.ts +0 -176
- package/src/controllers/config.ts +0 -126
- package/src/controllers/connections.ts +0 -181
- package/src/controllers/data-query.ts +0 -236
- package/src/controllers/database-configs.ts +0 -440
- package/src/controllers/embeddings.ts +0 -22
- package/src/controllers/entries.ts +0 -167
- package/src/controllers/eval.ts +0 -513
- package/src/controllers/export-import.ts +0 -166
- package/src/controllers/health.ts +0 -36
- package/src/controllers/local-a2a-templates.ts +0 -18
- package/src/controllers/local-a2a.ts +0 -149
- package/src/controllers/mcp-configs.ts +0 -673
- package/src/controllers/memory.ts +0 -257
- package/src/controllers/menu-items.ts +0 -114
- package/src/controllers/metrics-configs.ts +0 -1061
- package/src/controllers/middleware-types.ts +0 -15
- package/src/controllers/models.ts +0 -199
- package/src/controllers/personal-assistant.ts +0 -191
- package/src/controllers/resources.ts +0 -477
- package/src/controllers/run.ts +0 -338
- package/src/controllers/sandbox.ts +0 -255
- package/src/controllers/schedules.ts +0 -336
- package/src/controllers/skills.ts +0 -256
- package/src/controllers/tasks.ts +0 -213
- package/src/controllers/tenants.ts +0 -121
- package/src/controllers/thread_status.ts +0 -66
- package/src/controllers/threads.ts +0 -260
- package/src/controllers/tools.ts +0 -139
- package/src/controllers/users.ts +0 -135
- package/src/controllers/workflow-tracking.ts +0 -693
- package/src/controllers/workspace.ts +0 -879
- package/src/export_registrations/a2a-api-key.registration.ts +0 -89
- package/src/export_registrations/agent.registration.ts +0 -80
- package/src/export_registrations/binding.registration.ts +0 -106
- package/src/export_registrations/channel-installation.registration.ts +0 -88
- package/src/export_registrations/collection.registration.ts +0 -85
- package/src/export_registrations/connection.registration.ts +0 -97
- package/src/export_registrations/database-config.registration.ts +0 -94
- package/src/export_registrations/eval.registration.ts +0 -334
- package/src/export_registrations/index.ts +0 -31
- package/src/export_registrations/mcp-config.registration.ts +0 -97
- package/src/export_registrations/menu.registration.ts +0 -91
- package/src/export_registrations/metrics-config.registration.ts +0 -94
- package/src/export_registrations/skill.registration.ts +0 -88
- package/src/export_registrations/task.registration.ts +0 -99
- package/src/index.ts +0 -497
- package/src/logger/Logger.ts +0 -186
- package/src/router/MessageContext.ts +0 -14
- package/src/router/MessageRouter.ts +0 -490
- package/src/router/middlewares/auditLogger.ts +0 -34
- package/src/router/middlewares/deduplication.ts +0 -38
- package/src/router/middlewares/index.ts +0 -3
- package/src/router/middlewares/rateLimit.ts +0 -46
- package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
- package/src/routes/a2a-bridge.ts +0 -266
- package/src/routes/a2a-test-connection.ts +0 -126
- package/src/routes/a2a.ts +0 -851
- package/src/routes/channel-bindings.ts +0 -11
- package/src/routes/channel-installations.ts +0 -33
- package/src/routes/index.ts +0 -640
- package/src/routes/menu-items.ts +0 -10
- package/src/routes/resource-routes.ts +0 -35
- package/src/routes/stt.ts +0 -102
- package/src/schemas/data-query.ts +0 -69
- package/src/schemas/index.ts +0 -852
- package/src/services/ExportImportService.ts +0 -296
- package/src/services/__tests__/ExportImportService.test.ts +0 -130
- package/src/services/__tests__/eval-runner.test.ts +0 -131
- package/src/services/__tests__/local-a2a.test.ts +0 -124
- package/src/services/agent_task_consumer.ts +0 -348
- package/src/services/collection_service.ts +0 -258
- package/src/services/eval-runner.ts +0 -283
- package/src/services/local-a2a.ts +0 -354
- package/src/services/queue_service.ts +0 -104
- package/src/services/sandbox_service.ts +0 -223
- package/src/services/skill_service.ts +0 -97
- package/src/services/supabase.ts +0 -60
- package/src/swagger.ts +0 -77
- package/src/types/index.ts +0 -118
- package/src/types/koa.d.ts +0 -7
- package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
- package/src/utils/__tests__/path-containment.test.ts +0 -35
- package/src/utils/a2a-url.ts +0 -20
- package/src/utils/local-a2a-runtime.ts +0 -107
- package/src/utils/mime.ts +0 -44
- package/src/utils/path-containment.ts +0 -38
- package/tsconfig.json +0 -26
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Core orchestration service for tenant configuration export and import.
|
|
3
|
-
*
|
|
4
|
-
* @module @axiom-lattice/gateway/services/ExportImportService
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { ExportableEntityRegistry, DependencyResolver, IdRemapper } from '@axiom-lattice/core';
|
|
8
|
-
import type {
|
|
9
|
-
ExportBundle,
|
|
10
|
-
ExportableEntity,
|
|
11
|
-
ImportPreviewResult,
|
|
12
|
-
Resolution,
|
|
13
|
-
ImportApplyResult,
|
|
14
|
-
ImportEntityResult,
|
|
15
|
-
} from '@axiom-lattice/core';
|
|
16
|
-
import { randomUUID } from 'crypto';
|
|
17
|
-
|
|
18
|
-
interface JobEntry {
|
|
19
|
-
bundle: ExportBundle;
|
|
20
|
-
tenantId: string;
|
|
21
|
-
createdAt: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Ephemeral in-memory job store (jobs expire after TTL). */
|
|
25
|
-
const exportJobs = new Map<string, JobEntry>();
|
|
26
|
-
|
|
27
|
-
/** TTL: 1 hour */
|
|
28
|
-
const JOB_TTL_MS = 60 * 60 * 1000;
|
|
29
|
-
|
|
30
|
-
function cleanExpiredJobs(): void {
|
|
31
|
-
const now = Date.now();
|
|
32
|
-
for (const [id, entry] of exportJobs) {
|
|
33
|
-
if (now - entry.createdAt > JOB_TTL_MS) {
|
|
34
|
-
exportJobs.delete(id);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export class ExportImportService {
|
|
40
|
-
private registry = ExportableEntityRegistry.getInstance();
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Preview an export: check which types would be auto-included, which dependencies are missing.
|
|
44
|
-
*/
|
|
45
|
-
async exportPreview(
|
|
46
|
-
tenantId: string,
|
|
47
|
-
selectedTypes: string[],
|
|
48
|
-
): Promise<{ missingDependencies: string[]; cascadeAdditions: string[] }> {
|
|
49
|
-
const allDefs = this.registry.getAll();
|
|
50
|
-
|
|
51
|
-
const expanded = DependencyResolver.expandCascade(allDefs, selectedTypes);
|
|
52
|
-
const cascadeAdditions = expanded.filter((t) => !selectedTypes.includes(t));
|
|
53
|
-
|
|
54
|
-
const deps = DependencyResolver.computeDependencies(allDefs, expanded);
|
|
55
|
-
const registeredTypes = new Set(allDefs.map((d) => d.entityType));
|
|
56
|
-
const missingDependencies = deps.missing.filter((t) => registeredTypes.has(t));
|
|
57
|
-
|
|
58
|
-
return { missingDependencies, cascadeAdditions };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Generate a full ExportBundle for the given tenant and entity types.
|
|
63
|
-
* Stores the bundle in-memory and returns a jobId for download.
|
|
64
|
-
*/
|
|
65
|
-
async export(
|
|
66
|
-
tenantId: string,
|
|
67
|
-
entityTypes: string[],
|
|
68
|
-
entityIds?: Record<string, string[]>,
|
|
69
|
-
): Promise<{ jobId: string; bundle: ExportBundle }> {
|
|
70
|
-
const allDefs = this.registry.getAll();
|
|
71
|
-
const expanded = DependencyResolver.expandCascade(allDefs, entityTypes);
|
|
72
|
-
const finalTypes = [...new Set([...expanded, ...entityTypes])];
|
|
73
|
-
|
|
74
|
-
const dependencyOrder = DependencyResolver.resolveOrder(
|
|
75
|
-
finalTypes.map((t) => this.registry.get(t)),
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
const entities: Record<string, ExportableEntity[]> = {};
|
|
79
|
-
let exportCounter = 0;
|
|
80
|
-
|
|
81
|
-
for (const type of dependencyOrder) {
|
|
82
|
-
const def = this.registry.get(type);
|
|
83
|
-
let raw = await def.listForExport(tenantId);
|
|
84
|
-
|
|
85
|
-
if (entityIds && entityIds[type] && entityIds[type].length > 0) {
|
|
86
|
-
const idSet = new Set(entityIds[type]);
|
|
87
|
-
raw = raw.filter((e) => idSet.has(e.data.id as string));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
entities[type] = raw.map((e) => ({
|
|
91
|
-
...e,
|
|
92
|
-
_exportId: `exp-${type}-${String(++exportCounter).padStart(3, '0')}`,
|
|
93
|
-
data: this.sanitizeEntityData(e.data),
|
|
94
|
-
}));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Build reverse map: raw entity ID → _exportId
|
|
98
|
-
const rawIdToExportId = new Map<string, string>();
|
|
99
|
-
for (const [, entityList] of Object.entries(entities)) {
|
|
100
|
-
for (const entity of entityList) {
|
|
101
|
-
const rawId = entity.data.id as string;
|
|
102
|
-
if (rawId) {
|
|
103
|
-
rawIdToExportId.set(rawId, entity._exportId);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Rewrite reference fields to @type/exportId format
|
|
109
|
-
for (const [type, entityList] of Object.entries(entities)) {
|
|
110
|
-
const def = this.registry.get(type);
|
|
111
|
-
const refFields = def.referenceFields || {};
|
|
112
|
-
for (const entity of entityList) {
|
|
113
|
-
for (const [fieldName, refEntityType] of Object.entries(refFields)) {
|
|
114
|
-
const rawValue = entity.data[fieldName];
|
|
115
|
-
if (typeof rawValue === 'string' && rawValue) {
|
|
116
|
-
const exportId = rawIdToExportId.get(rawValue);
|
|
117
|
-
if (exportId) {
|
|
118
|
-
entity.data[fieldName] = `@${refEntityType}/${exportId}`;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const bundle: ExportBundle = {
|
|
126
|
-
version: 1,
|
|
127
|
-
exportedAt: new Date().toISOString(),
|
|
128
|
-
sourceTenantId: tenantId,
|
|
129
|
-
entities,
|
|
130
|
-
dependencyOrder,
|
|
131
|
-
_warning: 'This file contains plaintext secrets (passwords, API keys, tokens). Store it securely and delete after import.',
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const jobId = randomUUID();
|
|
135
|
-
exportJobs.set(jobId, { bundle, tenantId, createdAt: Date.now() });
|
|
136
|
-
|
|
137
|
-
return { jobId, bundle };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Retrieve a previously generated export bundle by jobId with tenant validation.
|
|
142
|
-
*/
|
|
143
|
-
getExportJob(jobId: string, tenantId: string): ExportBundle | undefined {
|
|
144
|
-
cleanExpiredJobs();
|
|
145
|
-
const entry = exportJobs.get(jobId);
|
|
146
|
-
if (!entry || entry.tenantId !== tenantId) return undefined;
|
|
147
|
-
return entry.bundle;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Preview import: validate bundle and detect conflicts.
|
|
152
|
-
*/
|
|
153
|
-
async previewImport(
|
|
154
|
-
tenantId: string,
|
|
155
|
-
bundle: ExportBundle,
|
|
156
|
-
): Promise<ImportPreviewResult> {
|
|
157
|
-
this.validateBundle(bundle);
|
|
158
|
-
|
|
159
|
-
const allConflicts: ImportPreviewResult['conflicts'] = [];
|
|
160
|
-
const allInsertions: ImportPreviewResult['insertions'] = [];
|
|
161
|
-
const allErrors: ImportPreviewResult['errors'] = [];
|
|
162
|
-
|
|
163
|
-
for (const [entityType, entities] of Object.entries(bundle.entities)) {
|
|
164
|
-
if (entities.length === 0) continue;
|
|
165
|
-
|
|
166
|
-
try {
|
|
167
|
-
const def = this.registry.get(entityType);
|
|
168
|
-
const result = await def.previewImport(tenantId, entities);
|
|
169
|
-
|
|
170
|
-
allConflicts.push(...result.conflicts);
|
|
171
|
-
allInsertions.push(...result.insertions);
|
|
172
|
-
allErrors.push(...result.errors);
|
|
173
|
-
} catch (err) {
|
|
174
|
-
for (const entity of entities) {
|
|
175
|
-
allErrors.push({
|
|
176
|
-
_exportId: entity._exportId,
|
|
177
|
-
entityType,
|
|
178
|
-
error: `Preview failed: ${(err as Error).message}`,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return { conflicts: allConflicts, insertions: allInsertions, errors: allErrors };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Apply import: create entities in dependency order with ID remapping.
|
|
189
|
-
*/
|
|
190
|
-
async applyImport(
|
|
191
|
-
tenantId: string,
|
|
192
|
-
bundle: ExportBundle,
|
|
193
|
-
resolutions: Record<string, Resolution>,
|
|
194
|
-
): Promise<ImportApplyResult> {
|
|
195
|
-
this.validateBundle(bundle);
|
|
196
|
-
|
|
197
|
-
const results: ImportEntityResult[] = [];
|
|
198
|
-
const idMap: Record<string, string> = {};
|
|
199
|
-
// Track skill old id → new id for agent graphDefinition implicit refs
|
|
200
|
-
const skillIdRemap: Record<string, string> = {};
|
|
201
|
-
|
|
202
|
-
for (const entityType of bundle.dependencyOrder) {
|
|
203
|
-
const entities = bundle.entities[entityType];
|
|
204
|
-
if (!entities || entities.length === 0) continue;
|
|
205
|
-
|
|
206
|
-
const def = this.registry.get(entityType);
|
|
207
|
-
const remapper = new IdRemapper(idMap);
|
|
208
|
-
|
|
209
|
-
for (const entity of entities) {
|
|
210
|
-
const resolution = resolutions[entity._exportId];
|
|
211
|
-
|
|
212
|
-
if (resolution?.action === 'skip') {
|
|
213
|
-
results.push({ _exportId: entity._exportId, entityType, status: 'skipped' });
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
try {
|
|
218
|
-
const remappedData = remapper.remapReferences(entity.data) as Record<string, unknown>;
|
|
219
|
-
|
|
220
|
-
// Remap implicit skill references in agent graphDefinition.skillIds
|
|
221
|
-
if (entityType === 'agent' && Object.keys(skillIdRemap).length > 0) {
|
|
222
|
-
const skillRemapper = new IdRemapper({});
|
|
223
|
-
remappedData.graphDefinition = skillRemapper.remapSkillIds(
|
|
224
|
-
(remappedData.graphDefinition as Record<string, unknown>) || {},
|
|
225
|
-
skillIdRemap,
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (resolution?.action === 'rename' && resolution.newId) {
|
|
230
|
-
remappedData.id = resolution.newId;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const entityWithRemappedData = { ...entity, data: remappedData };
|
|
234
|
-
const { newId } = await def.applyImport(
|
|
235
|
-
tenantId,
|
|
236
|
-
entityWithRemappedData,
|
|
237
|
-
resolution ?? { _exportId: entity._exportId, action: 'overwrite' },
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
if (newId) {
|
|
241
|
-
idMap[entity._exportId] = newId;
|
|
242
|
-
// Track skill ID renames for agent graphDefinition remapping
|
|
243
|
-
if (entityType === 'skill' && newId) {
|
|
244
|
-
const oldSkillId = remappedData.id as string;
|
|
245
|
-
if (oldSkillId !== newId) {
|
|
246
|
-
skillIdRemap[oldSkillId] = newId;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
results.push({
|
|
250
|
-
_exportId: entity._exportId,
|
|
251
|
-
entityType,
|
|
252
|
-
status: resolution?.action === 'overwrite' ? 'updated' : 'created',
|
|
253
|
-
newId,
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
} catch (err) {
|
|
257
|
-
results.push({
|
|
258
|
-
_exportId: entity._exportId,
|
|
259
|
-
entityType,
|
|
260
|
-
status: 'failed',
|
|
261
|
-
error: (err as Error).message,
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return { results, idMap };
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* List all registered exportable types (for frontend).
|
|
272
|
-
*/
|
|
273
|
-
listExportableTypes() {
|
|
274
|
-
return this.registry.listTypes();
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
private validateBundle(bundle: ExportBundle): void {
|
|
278
|
-
if (bundle.version !== 1) {
|
|
279
|
-
throw new Error(`Unsupported bundle version: ${bundle.version}. Expected: 1`);
|
|
280
|
-
}
|
|
281
|
-
if (!bundle.entities || typeof bundle.entities !== 'object') {
|
|
282
|
-
throw new Error('Invalid bundle: missing entities');
|
|
283
|
-
}
|
|
284
|
-
if (!Array.isArray(bundle.dependencyOrder)) {
|
|
285
|
-
throw new Error('Invalid bundle: missing dependencyOrder');
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
private sanitizeEntityData(data: Record<string, unknown>): Record<string, unknown> {
|
|
290
|
-
const sanitized = { ...data };
|
|
291
|
-
delete sanitized.tenantId;
|
|
292
|
-
delete sanitized.createdAt;
|
|
293
|
-
delete sanitized.updatedAt;
|
|
294
|
-
return sanitized;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { ExportImportService } from '../ExportImportService';
|
|
2
|
-
import { ExportableEntityRegistry } from '@axiom-lattice/core';
|
|
3
|
-
import type { ExportableEntityDefinition, ExportableEntity } from '@axiom-lattice/core';
|
|
4
|
-
|
|
5
|
-
function makeDef(
|
|
6
|
-
entityType: string,
|
|
7
|
-
entities: ExportableEntity[] = [],
|
|
8
|
-
dependsOn: string[] = [],
|
|
9
|
-
): ExportableEntityDefinition {
|
|
10
|
-
return {
|
|
11
|
-
entityType,
|
|
12
|
-
label: entityType,
|
|
13
|
-
category: 'core',
|
|
14
|
-
dependsOn,
|
|
15
|
-
cascadeParents: [],
|
|
16
|
-
listForExport: jest.fn().mockResolvedValue(entities),
|
|
17
|
-
previewImport: jest.fn().mockResolvedValue({ conflicts: [], insertions: [], errors: [] }),
|
|
18
|
-
applyImport: jest.fn().mockResolvedValue({ newId: 'new-id' }),
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
describe('ExportImportService', () => {
|
|
23
|
-
beforeEach(() => {
|
|
24
|
-
(ExportableEntityRegistry as any).instance = undefined;
|
|
25
|
-
const registry = ExportableEntityRegistry.getInstance();
|
|
26
|
-
const s1 = { _exportId: '', data: { id: 's1', name: 'Skill 1' } };
|
|
27
|
-
const s2 = { _exportId: '', data: { id: 's2', name: 'Skill 2' } };
|
|
28
|
-
registry.register(makeDef('skill', [s1, s2]));
|
|
29
|
-
registry.register(makeDef('agent', [], ['skill']));
|
|
30
|
-
registry.register(makeDef('binding', [], ['agent']));
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('generates a valid ExportBundle', async () => {
|
|
34
|
-
const svc = new ExportImportService();
|
|
35
|
-
const { bundle } = await svc.export('tenant-1', ['skill']);
|
|
36
|
-
|
|
37
|
-
expect(bundle.version).toBe(1);
|
|
38
|
-
expect(bundle.sourceTenantId).toBe('tenant-1');
|
|
39
|
-
expect(bundle.entities.skill).toHaveLength(2);
|
|
40
|
-
expect(bundle.entities.skill[0]._exportId).toMatch(/^exp-skill-0/);
|
|
41
|
-
expect(bundle.entities.skill[0].data.id).toBe('s1');
|
|
42
|
-
expect(bundle.entities.skill[0].data).not.toHaveProperty('tenantId');
|
|
43
|
-
expect(bundle.entities.skill[0].data).not.toHaveProperty('createdAt');
|
|
44
|
-
expect(bundle.entities.skill[0].data).not.toHaveProperty('updatedAt');
|
|
45
|
-
expect(bundle.dependencyOrder).toEqual(['skill']);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('filters entities by entityIds', async () => {
|
|
49
|
-
const svc = new ExportImportService();
|
|
50
|
-
const { bundle } = await svc.export('tenant-1', ['skill'], { skill: ['s1'] });
|
|
51
|
-
expect(bundle.entities.skill).toHaveLength(1);
|
|
52
|
-
expect(bundle.entities.skill[0].data.id).toBe('s1');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('exportPreview detects missing dependencies', async () => {
|
|
56
|
-
const svc = new ExportImportService();
|
|
57
|
-
const result = await svc.exportPreview('tenant-1', ['agent']);
|
|
58
|
-
expect(result.missingDependencies).toContain('skill');
|
|
59
|
-
expect(result.cascadeAdditions).toEqual([]);
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
it('export includes transitive deps when explicitly requested', async () => {
|
|
63
|
-
const svc = new ExportImportService();
|
|
64
|
-
const { bundle } = await svc.export('tenant-1', ['skill', 'agent']);
|
|
65
|
-
expect(bundle.dependencyOrder).toContain('skill');
|
|
66
|
-
expect(bundle.dependencyOrder).toContain('agent');
|
|
67
|
-
expect(bundle.dependencyOrder.indexOf('skill')).toBeLessThan(bundle.dependencyOrder.indexOf('agent'));
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('previewImport returns insertions for new entities', async () => {
|
|
71
|
-
const svc = new ExportImportService();
|
|
72
|
-
const bundle = {
|
|
73
|
-
version: 1 as const,
|
|
74
|
-
exportedAt: new Date().toISOString(),
|
|
75
|
-
sourceTenantId: 't1',
|
|
76
|
-
entities: {
|
|
77
|
-
skill: [{ _exportId: 'exp-s-1', data: { id: 's1', name: 'Skill' } }],
|
|
78
|
-
},
|
|
79
|
-
dependencyOrder: ['skill'],
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
await svc.previewImport('tenant-2', bundle);
|
|
83
|
-
const registry = ExportableEntityRegistry.getInstance();
|
|
84
|
-
expect(registry.get('skill').previewImport).toHaveBeenCalled();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('throws on invalid bundle version', async () => {
|
|
88
|
-
const svc = new ExportImportService();
|
|
89
|
-
await expect(
|
|
90
|
-
svc.previewImport('tenant-2', {
|
|
91
|
-
version: 99 as any,
|
|
92
|
-
entities: {},
|
|
93
|
-
dependencyOrder: [],
|
|
94
|
-
exportedAt: '',
|
|
95
|
-
sourceTenantId: '',
|
|
96
|
-
}),
|
|
97
|
-
).rejects.toThrow(/version/);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('throws on missing entities field', async () => {
|
|
101
|
-
const svc = new ExportImportService();
|
|
102
|
-
await expect(
|
|
103
|
-
svc.previewImport('tenant-2', {
|
|
104
|
-
version: 1,
|
|
105
|
-
exportedAt: '',
|
|
106
|
-
sourceTenantId: '',
|
|
107
|
-
dependencyOrder: [],
|
|
108
|
-
} as any),
|
|
109
|
-
).rejects.toThrow(/entities/);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('getExportJob returns undefined for unknown jobId', () => {
|
|
113
|
-
const svc = new ExportImportService();
|
|
114
|
-
expect(svc.getExportJob('nonexistent', 'tenant-1')).toBeUndefined();
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it('getExportJob returns bundle after export', async () => {
|
|
118
|
-
const svc = new ExportImportService();
|
|
119
|
-
const { jobId, bundle } = await svc.export('tenant-1', ['skill']);
|
|
120
|
-
const retrieved = svc.getExportJob(jobId, 'tenant-1');
|
|
121
|
-
expect(retrieved).toEqual(bundle);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('listExportableTypes returns registered types', () => {
|
|
125
|
-
const svc = new ExportImportService();
|
|
126
|
-
const types = svc.listExportableTypes();
|
|
127
|
-
expect(types).toHaveLength(3);
|
|
128
|
-
expect(types.map((t) => t.entityType)).toEqual(['skill', 'agent', 'binding']);
|
|
129
|
-
});
|
|
130
|
-
});
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EvalRunner 孤儿 run 处理测试
|
|
3
|
-
*
|
|
4
|
-
* 网关重启后 DB 中 status=running 的 run 在内存中不存在(孤儿)。
|
|
5
|
-
* startRun 应先将孤儿标记为 failed,再允许新 run 启动。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { describe, expect, it, jest, beforeEach, afterEach } from "@jest/globals";
|
|
9
|
-
|
|
10
|
-
const mockStore = {
|
|
11
|
-
getProjectById: jest.fn(),
|
|
12
|
-
getRunsByTenant: jest.fn(),
|
|
13
|
-
getSuitesByProject: jest.fn(),
|
|
14
|
-
getCasesBySuite: jest.fn(),
|
|
15
|
-
updateRunStatus: jest.fn(),
|
|
16
|
-
createRun: jest.fn(),
|
|
17
|
-
createRunResult: jest.fn(),
|
|
18
|
-
getResultsByRun: jest.fn(),
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
jest.mock("@axiom-lattice/core", () => {
|
|
22
|
-
const actual = jest.requireActual("@axiom-lattice/core");
|
|
23
|
-
return {
|
|
24
|
-
...actual,
|
|
25
|
-
getStoreLattice: jest.fn(() => ({ store: mockStore })),
|
|
26
|
-
modelLatticeManager: {
|
|
27
|
-
hasLattice: jest.fn(() => true),
|
|
28
|
-
getAllLattices: jest.fn(() => [{ key: "mock-model" }]),
|
|
29
|
-
},
|
|
30
|
-
LatticeEvalProject: jest.fn().mockImplementation(() => ({
|
|
31
|
-
calibrateJudge: jest.fn().mockResolvedValue({ ok: true }),
|
|
32
|
-
runAllSuitesBatch: jest.fn().mockResolvedValue({
|
|
33
|
-
report: { summary: { total_cases: 0, passed_cases: 0, failed_cases: 0, pass_rate: 0 } },
|
|
34
|
-
}),
|
|
35
|
-
})),
|
|
36
|
-
};
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
import { evalRunner } from "../eval-runner";
|
|
40
|
-
|
|
41
|
-
const runningRun = (id: string, createdAt = new Date()) => ({
|
|
42
|
-
id,
|
|
43
|
-
projectId: "p1",
|
|
44
|
-
tenantId: "t1",
|
|
45
|
-
status: "running" as const,
|
|
46
|
-
concurrency: 3,
|
|
47
|
-
totalCases: 0,
|
|
48
|
-
passedCases: 0,
|
|
49
|
-
failedCases: 0,
|
|
50
|
-
avgScore: 0,
|
|
51
|
-
createdAt,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
describe("EvalRunner orphaned run handling", () => {
|
|
55
|
-
beforeEach(() => {
|
|
56
|
-
jest.clearAllMocks();
|
|
57
|
-
mockStore.getProjectById.mockResolvedValue({ id: "p1", name: "eval-test", tenantId: "t1" });
|
|
58
|
-
mockStore.getSuitesByProject.mockResolvedValue([]);
|
|
59
|
-
mockStore.getCasesBySuite.mockResolvedValue([]);
|
|
60
|
-
mockStore.updateRunStatus.mockResolvedValue(null);
|
|
61
|
-
mockStore.createRun.mockResolvedValue(runningRun("new-run-1"));
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
afterEach(() => {
|
|
65
|
-
jest.restoreAllMocks();
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
it("marks orphaned running runs as failed before starting a new run", async () => {
|
|
69
|
-
const orphan = runningRun("orphan-1", new Date(Date.now() - 60_000));
|
|
70
|
-
mockStore.getRunsByTenant
|
|
71
|
-
.mockResolvedValueOnce([orphan]) // first call: orphan found
|
|
72
|
-
.mockResolvedValueOnce([]); // second call: all cleaned
|
|
73
|
-
|
|
74
|
-
const runId = await evalRunner.startRun("t1", "p1");
|
|
75
|
-
|
|
76
|
-
expect(mockStore.updateRunStatus).toHaveBeenCalledWith(
|
|
77
|
-
"t1",
|
|
78
|
-
"orphan-1",
|
|
79
|
-
expect.objectContaining({ status: "failed" })
|
|
80
|
-
);
|
|
81
|
-
expect(mockStore.createRun).toHaveBeenCalled();
|
|
82
|
-
expect(runId).toBeTruthy();
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("does not clean a run that is alive in memory", async () => {
|
|
86
|
-
mockStore.getRunsByTenant.mockResolvedValue([]);
|
|
87
|
-
|
|
88
|
-
const runId = await evalRunner.startRun("t1", "p1");
|
|
89
|
-
expect(runId).toBeTruthy();
|
|
90
|
-
expect(mockStore.updateRunStatus).not.toHaveBeenCalled();
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it("does not clean a run created after process start (other instance)", async () => {
|
|
94
|
-
const live = runningRun("other-instance-1", new Date()); // created now → after process start
|
|
95
|
-
mockStore.getRunsByTenant
|
|
96
|
-
.mockResolvedValueOnce([live])
|
|
97
|
-
.mockResolvedValueOnce([live]); // still running after cleanup attempt
|
|
98
|
-
|
|
99
|
-
await expect(evalRunner.startRun("t1", "p1")).rejects.toThrow(/already in progress/);
|
|
100
|
-
expect(mockStore.updateRunStatus).not.toHaveBeenCalled();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("runs only the requested suites when suiteIds is provided", async () => {
|
|
104
|
-
mockStore.getSuitesByProject.mockResolvedValue([
|
|
105
|
-
{ id: "s-dev", name: "po-extraction-user-sample", tenantId: "t1", projectId: "p1", createdAt: new Date(), updatedAt: new Date() },
|
|
106
|
-
{ id: "s-val", name: "po-extraction-validation", tenantId: "t1", projectId: "p1", createdAt: new Date(), updatedAt: new Date() },
|
|
107
|
-
]);
|
|
108
|
-
const { LatticeEvalProject } = jest.requireMock("@axiom-lattice/core");
|
|
109
|
-
const runProjectConfigs: any[] = [];
|
|
110
|
-
LatticeEvalProject.mockImplementation((config: any) => {
|
|
111
|
-
runProjectConfigs.push(config);
|
|
112
|
-
return {
|
|
113
|
-
calibrateJudge: jest.fn().mockResolvedValue({ ok: true }),
|
|
114
|
-
runAllSuitesBatch: jest.fn().mockResolvedValue({
|
|
115
|
-
report: { summary: { total_cases: 0, passed_cases: 0, failed_cases: 0, pass_rate: 0 } },
|
|
116
|
-
}),
|
|
117
|
-
};
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
const runId = await evalRunner.startRun("t1", "p1", ["s-val"]);
|
|
121
|
-
expect(runId).toBeTruthy();
|
|
122
|
-
expect(runProjectConfigs[0].suites.map((s: any) => s.suiteName)).toEqual(["po-extraction-validation"]);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
it("throws when suiteIds filter matches nothing", async () => {
|
|
126
|
-
mockStore.getSuitesByProject.mockResolvedValue([
|
|
127
|
-
{ id: "s-dev", name: "dev", tenantId: "t1", projectId: "p1", createdAt: new Date(), updatedAt: new Date() },
|
|
128
|
-
]);
|
|
129
|
-
await expect(evalRunner.startRun("t1", "p1", ["s-val"])).rejects.toThrow(/No suites match/);
|
|
130
|
-
});
|
|
131
|
-
});
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import {
|
|
5
|
-
appendStderrTail,
|
|
6
|
-
LocalA2AService,
|
|
7
|
-
resolveLocalA2AWorkingDirectory,
|
|
8
|
-
} from "../local-a2a";
|
|
9
|
-
import type { LocalRuntimeConfig } from "@axiom-lattice/protocols";
|
|
10
|
-
|
|
11
|
-
describe("LocalA2AService working directory resolution", () => {
|
|
12
|
-
let tempDir: string;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
tempDir = mkdtempSync(join(tmpdir(), "local-a2a-"));
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
afterEach(() => {
|
|
19
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("prefers env override, then explicit command cwd", () => {
|
|
23
|
-
expect(resolveLocalA2AWorkingDirectory({
|
|
24
|
-
envWorkdir: "/env/root",
|
|
25
|
-
commandCwd: "/command/root",
|
|
26
|
-
currentCwd: tempDir,
|
|
27
|
-
})).toBe("/env/root");
|
|
28
|
-
|
|
29
|
-
expect(resolveLocalA2AWorkingDirectory({
|
|
30
|
-
envWorkdir: "",
|
|
31
|
-
commandCwd: "/command/root",
|
|
32
|
-
currentCwd: tempDir,
|
|
33
|
-
})).toBe("/command/root");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it("falls back to the nearest pnpm workspace root", () => {
|
|
37
|
-
writeFileSync(join(tempDir, "pnpm-workspace.yaml"), "packages: []\n");
|
|
38
|
-
const nested = join(tempDir, "examples", "deep_research");
|
|
39
|
-
mkdirSync(nested, { recursive: true });
|
|
40
|
-
|
|
41
|
-
expect(resolveLocalA2AWorkingDirectory({
|
|
42
|
-
commandCwd: undefined,
|
|
43
|
-
currentCwd: nested,
|
|
44
|
-
})).toBe(tempDir);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("falls back to current cwd when no workspace root exists", () => {
|
|
48
|
-
expect(resolveLocalA2AWorkingDirectory({
|
|
49
|
-
commandCwd: undefined,
|
|
50
|
-
currentCwd: tempDir,
|
|
51
|
-
})).toBe(tempDir);
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe("LocalA2AService startup diagnostics", () => {
|
|
56
|
-
const originalAutoStart = process.env.LOCAL_A2A_AUTO_START;
|
|
57
|
-
const originalWorkdir = process.env.LOCAL_A2A_WORKDIR;
|
|
58
|
-
let tempDir: string;
|
|
59
|
-
|
|
60
|
-
beforeEach(() => {
|
|
61
|
-
tempDir = mkdtempSync(join(tmpdir(), "local-a2a-"));
|
|
62
|
-
process.env.LOCAL_A2A_AUTO_START = "true";
|
|
63
|
-
delete process.env.LOCAL_A2A_WORKDIR;
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
afterEach(() => {
|
|
67
|
-
if (originalAutoStart === undefined) {
|
|
68
|
-
delete process.env.LOCAL_A2A_AUTO_START;
|
|
69
|
-
} else {
|
|
70
|
-
process.env.LOCAL_A2A_AUTO_START = originalAutoStart;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (originalWorkdir === undefined) {
|
|
74
|
-
delete process.env.LOCAL_A2A_WORKDIR;
|
|
75
|
-
} else {
|
|
76
|
-
process.env.LOCAL_A2A_WORKDIR = originalWorkdir;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
function runtime(command: LocalRuntimeConfig["command"]): LocalRuntimeConfig {
|
|
83
|
-
return {
|
|
84
|
-
label: "Test Runtime",
|
|
85
|
-
agentCardUrl: "http://127.0.0.1:1/.well-known/agent-card.json",
|
|
86
|
-
command,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
it("fails before spawn when cwd is not accessible", async () => {
|
|
91
|
-
const service = new LocalA2AService();
|
|
92
|
-
|
|
93
|
-
const state = await service.startProvider("runtime-1", runtime({
|
|
94
|
-
executable: process.execPath,
|
|
95
|
-
args: ["-e", "process.exit(0)"],
|
|
96
|
-
cwd: join(tempDir, "missing"),
|
|
97
|
-
}));
|
|
98
|
-
|
|
99
|
-
expect(state.status).toBe("failed");
|
|
100
|
-
expect(state.message).toContain("Working directory is not accessible");
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it("includes stderr tail when the child exits before ready", async () => {
|
|
104
|
-
const service = new LocalA2AService();
|
|
105
|
-
|
|
106
|
-
const state = await service.startProvider("runtime-2", runtime({
|
|
107
|
-
executable: process.execPath,
|
|
108
|
-
args: ["-e", "console.error('diagnostic stderr'); process.exit(1)"],
|
|
109
|
-
cwd: tempDir,
|
|
110
|
-
}));
|
|
111
|
-
|
|
112
|
-
expect(state.status).toBe("failed");
|
|
113
|
-
expect(state.message).toContain("Exited before becoming ready (1)");
|
|
114
|
-
expect(state.message).toContain("diagnostic stderr");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("keeps only the last 4KB of stderr", () => {
|
|
118
|
-
const tail = appendStderrTail("prefix", `${"x".repeat(5000)}end`);
|
|
119
|
-
|
|
120
|
-
expect(tail.length).toBe(4096);
|
|
121
|
-
expect(tail.endsWith("end")).toBe(true);
|
|
122
|
-
expect(tail.startsWith("prefix")).toBe(false);
|
|
123
|
-
});
|
|
124
|
-
});
|