@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.
Files changed (155) hide show
  1. package/dist/index.js +31 -9
  2. package/dist/index.js.map +1 -1
  3. package/dist/index.mjs +31 -9
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +9 -6
  6. package/.eslintrc.json +0 -22
  7. package/.turbo/turbo-build.log +0 -46
  8. package/AGENTS.md +0 -112
  9. package/CHANGELOG.md +0 -1190
  10. package/LOGGER_CONFIG.md +0 -135
  11. package/RESUME_STREAM_CONTENT_BASED.md +0 -326
  12. package/RESUME_STREAM_README.md +0 -388
  13. package/jest.config.js +0 -72
  14. package/public/sdk/README.md +0 -695
  15. package/public/sdk/dashboard-engine-skill.md +0 -1122
  16. package/public/sdk/dashboard-single-file-spec.md +0 -357
  17. package/public/sdk/data-query-sdk-skill.md +0 -307
  18. package/public/sdk/data-query-sdk.d.ts +0 -252
  19. package/public/sdk/data-query-sdk.js +0 -970
  20. package/public/sdk/occupancy-dashboard.html +0 -363
  21. package/public/sdk/test-dashboard.html +0 -690
  22. package/scripts/seed-lark-installation.ts +0 -54
  23. package/src/__tests__/__mocks__/e2b.ts +0 -1
  24. package/src/__tests__/a2a.test.ts +0 -428
  25. package/src/__tests__/channel-installations.test.ts +0 -199
  26. package/src/__tests__/data-query.test.ts +0 -77
  27. package/src/__tests__/sandbox-provider-registration.test.ts +0 -74
  28. package/src/__tests__/setup.ts +0 -5
  29. package/src/__tests__/workspace.test.ts +0 -400
  30. package/src/channels/__tests__/routes.test.ts +0 -50
  31. package/src/channels/lark/LarkChannelAdapter.ts +0 -217
  32. package/src/channels/lark/README.md +0 -187
  33. package/src/channels/lark/__tests__/controller.test.ts +0 -136
  34. package/src/channels/lark/__tests__/mapping-service.test.ts +0 -118
  35. package/src/channels/lark/__tests__/parser.test.ts +0 -72
  36. package/src/channels/lark/__tests__/sender.test.ts +0 -37
  37. package/src/channels/lark/__tests__/verification.test.ts +0 -157
  38. package/src/channels/lark/controller.ts +0 -48
  39. package/src/channels/lark/mapping-service.ts +0 -138
  40. package/src/channels/lark/parser.ts +0 -68
  41. package/src/channels/lark/routes.ts +0 -22
  42. package/src/channels/lark/sender.ts +0 -58
  43. package/src/channels/lark/types.ts +0 -20
  44. package/src/channels/lark/verification.ts +0 -32
  45. package/src/channels/registry.ts +0 -20
  46. package/src/channels/routes.ts +0 -30
  47. package/src/channels/wechat/WechatChannelAdapter.ts +0 -294
  48. package/src/channels/wechat/__tests__/context-store.test.ts +0 -148
  49. package/src/channels/wechat/context-store.ts +0 -54
  50. package/src/channels/wechat/controller.ts +0 -59
  51. package/src/channels/wechat/routes.ts +0 -7
  52. package/src/channels/wechat/types.ts +0 -62
  53. package/src/channels/wechat/wechat-client.ts +0 -162
  54. package/src/config/local-a2a-templates.ts +0 -59
  55. package/src/config.ts +0 -124
  56. package/src/controllers/__tests__/assistant-local-a2a.test.ts +0 -147
  57. package/src/controllers/__tests__/local-a2a-templates.test.ts +0 -112
  58. package/src/controllers/__tests__/mime.test.ts +0 -74
  59. package/src/controllers/__tests__/resources.test.ts +0 -122
  60. package/src/controllers/__tests__/run.test.ts +0 -115
  61. package/src/controllers/__tests__/tasks.test.ts +0 -215
  62. package/src/controllers/__tests__/workflow-tracking.test.ts +0 -113
  63. package/src/controllers/agent_task.ts +0 -72
  64. package/src/controllers/assistant.ts +0 -490
  65. package/src/controllers/auth.ts +0 -524
  66. package/src/controllers/channel-bindings.ts +0 -135
  67. package/src/controllers/channel-installations.ts +0 -409
  68. package/src/controllers/collections.ts +0 -176
  69. package/src/controllers/config.ts +0 -126
  70. package/src/controllers/connections.ts +0 -181
  71. package/src/controllers/data-query.ts +0 -236
  72. package/src/controllers/database-configs.ts +0 -440
  73. package/src/controllers/embeddings.ts +0 -22
  74. package/src/controllers/entries.ts +0 -167
  75. package/src/controllers/eval.ts +0 -513
  76. package/src/controllers/export-import.ts +0 -166
  77. package/src/controllers/health.ts +0 -36
  78. package/src/controllers/local-a2a-templates.ts +0 -18
  79. package/src/controllers/local-a2a.ts +0 -149
  80. package/src/controllers/mcp-configs.ts +0 -673
  81. package/src/controllers/memory.ts +0 -257
  82. package/src/controllers/menu-items.ts +0 -114
  83. package/src/controllers/metrics-configs.ts +0 -1061
  84. package/src/controllers/middleware-types.ts +0 -15
  85. package/src/controllers/models.ts +0 -199
  86. package/src/controllers/personal-assistant.ts +0 -191
  87. package/src/controllers/resources.ts +0 -477
  88. package/src/controllers/run.ts +0 -338
  89. package/src/controllers/sandbox.ts +0 -255
  90. package/src/controllers/schedules.ts +0 -336
  91. package/src/controllers/skills.ts +0 -256
  92. package/src/controllers/tasks.ts +0 -213
  93. package/src/controllers/tenants.ts +0 -121
  94. package/src/controllers/thread_status.ts +0 -66
  95. package/src/controllers/threads.ts +0 -260
  96. package/src/controllers/tools.ts +0 -139
  97. package/src/controllers/users.ts +0 -135
  98. package/src/controllers/workflow-tracking.ts +0 -693
  99. package/src/controllers/workspace.ts +0 -879
  100. package/src/export_registrations/a2a-api-key.registration.ts +0 -89
  101. package/src/export_registrations/agent.registration.ts +0 -80
  102. package/src/export_registrations/binding.registration.ts +0 -106
  103. package/src/export_registrations/channel-installation.registration.ts +0 -88
  104. package/src/export_registrations/collection.registration.ts +0 -85
  105. package/src/export_registrations/connection.registration.ts +0 -97
  106. package/src/export_registrations/database-config.registration.ts +0 -94
  107. package/src/export_registrations/eval.registration.ts +0 -334
  108. package/src/export_registrations/index.ts +0 -31
  109. package/src/export_registrations/mcp-config.registration.ts +0 -97
  110. package/src/export_registrations/menu.registration.ts +0 -91
  111. package/src/export_registrations/metrics-config.registration.ts +0 -94
  112. package/src/export_registrations/skill.registration.ts +0 -88
  113. package/src/export_registrations/task.registration.ts +0 -99
  114. package/src/index.ts +0 -497
  115. package/src/logger/Logger.ts +0 -186
  116. package/src/router/MessageContext.ts +0 -14
  117. package/src/router/MessageRouter.ts +0 -490
  118. package/src/router/middlewares/auditLogger.ts +0 -34
  119. package/src/router/middlewares/deduplication.ts +0 -38
  120. package/src/router/middlewares/index.ts +0 -3
  121. package/src/router/middlewares/rateLimit.ts +0 -46
  122. package/src/routes/__tests__/a2a-test-connection.test.ts +0 -177
  123. package/src/routes/a2a-bridge.ts +0 -266
  124. package/src/routes/a2a-test-connection.ts +0 -126
  125. package/src/routes/a2a.ts +0 -851
  126. package/src/routes/channel-bindings.ts +0 -11
  127. package/src/routes/channel-installations.ts +0 -33
  128. package/src/routes/index.ts +0 -640
  129. package/src/routes/menu-items.ts +0 -10
  130. package/src/routes/resource-routes.ts +0 -35
  131. package/src/routes/stt.ts +0 -102
  132. package/src/schemas/data-query.ts +0 -69
  133. package/src/schemas/index.ts +0 -852
  134. package/src/services/ExportImportService.ts +0 -296
  135. package/src/services/__tests__/ExportImportService.test.ts +0 -130
  136. package/src/services/__tests__/eval-runner.test.ts +0 -131
  137. package/src/services/__tests__/local-a2a.test.ts +0 -124
  138. package/src/services/agent_task_consumer.ts +0 -348
  139. package/src/services/collection_service.ts +0 -258
  140. package/src/services/eval-runner.ts +0 -283
  141. package/src/services/local-a2a.ts +0 -354
  142. package/src/services/queue_service.ts +0 -104
  143. package/src/services/sandbox_service.ts +0 -223
  144. package/src/services/skill_service.ts +0 -97
  145. package/src/services/supabase.ts +0 -60
  146. package/src/swagger.ts +0 -77
  147. package/src/types/index.ts +0 -118
  148. package/src/types/koa.d.ts +0 -7
  149. package/src/utils/__tests__/local-a2a-runtime.test.ts +0 -77
  150. package/src/utils/__tests__/path-containment.test.ts +0 -35
  151. package/src/utils/a2a-url.ts +0 -20
  152. package/src/utils/local-a2a-runtime.ts +0 -107
  153. package/src/utils/mime.ts +0 -44
  154. package/src/utils/path-containment.ts +0 -38
  155. 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
- });