devchain-cli 0.1.1 → 0.2.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/cli.js +184 -16
- package/dist/drizzle/0017_agents_description.sql +1 -0
- package/dist/drizzle/meta/_journal.json +7 -0
- package/dist/server/common/filters/http-exception.filter.js +3 -0
- package/dist/server/common/filters/http-exception.filter.js.map +1 -1
- package/dist/server/common/filters/ws-exception.filter.js +1 -1
- package/dist/server/common/filters/ws-exception.filter.js.map +1 -1
- package/dist/server/main.js +18 -1
- package/dist/server/main.js.map +1 -1
- package/dist/server/modules/agents/controllers/agents.controller.js +2 -0
- package/dist/server/modules/agents/controllers/agents.controller.js.map +1 -1
- package/dist/server/modules/epics/epics.module.js +2 -1
- package/dist/server/modules/epics/epics.module.js.map +1 -1
- package/dist/server/modules/epics/services/epics.service.d.ts +6 -1
- package/dist/server/modules/epics/services/epics.service.js +48 -3
- package/dist/server/modules/epics/services/epics.service.js.map +1 -1
- package/dist/server/modules/events/services/events.service.js +7 -0
- package/dist/server/modules/events/services/events.service.js.map +1 -1
- package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.d.ts +3 -1
- package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.js +8 -4
- package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +1 -0
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.d.ts +6 -2
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.js +32 -2
- package/dist/server/modules/mcp/services/mcp-provider-registration.service.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp.service.js +2 -0
- package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
- package/dist/server/modules/projects/controllers/projects.controller.d.ts +28 -1
- package/dist/server/modules/projects/controllers/projects.controller.js +7 -1
- package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
- package/dist/server/modules/projects/services/projects.service.d.ts +26 -0
- package/dist/server/modules/projects/services/projects.service.js +388 -119
- package/dist/server/modules/projects/services/projects.service.js.map +1 -1
- package/dist/server/modules/providers/controllers/providers.controller.d.ts +1 -0
- package/dist/server/modules/providers/controllers/providers.controller.js +33 -0
- package/dist/server/modules/providers/controllers/providers.controller.js.map +1 -1
- package/dist/server/modules/sessions/services/session-coordinator.service.d.ts +4 -0
- package/dist/server/modules/sessions/services/session-coordinator.service.js +31 -0
- package/dist/server/modules/sessions/services/session-coordinator.service.js.map +1 -0
- package/dist/server/modules/sessions/services/sessions.service.d.ts +1 -0
- package/dist/server/modules/sessions/services/sessions.service.js +27 -0
- package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
- package/dist/server/modules/sessions/sessions.module.js +3 -2
- package/dist/server/modules/sessions/sessions.module.js.map +1 -1
- package/dist/server/modules/settings/controllers/settings.controller.d.ts +3 -0
- package/dist/server/modules/settings/controllers/settings.controller.js +27 -0
- package/dist/server/modules/settings/controllers/settings.controller.js.map +1 -1
- package/dist/server/modules/settings/dtos/settings.dto.d.ts +13 -0
- package/dist/server/modules/settings/dtos/settings.dto.js +4 -0
- package/dist/server/modules/settings/dtos/settings.dto.js.map +1 -1
- package/dist/server/modules/settings/services/settings.service.d.ts +16 -0
- package/dist/server/modules/settings/services/settings.service.js +74 -1
- package/dist/server/modules/settings/services/settings.service.js.map +1 -1
- package/dist/server/modules/storage/db/schema.d.ts +19 -0
- package/dist/server/modules/storage/db/schema.js +1 -0
- package/dist/server/modules/storage/db/schema.js.map +1 -1
- package/dist/server/modules/storage/interfaces/storage.interface.d.ts +2 -0
- package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
- package/dist/server/modules/storage/local/local-storage.service.d.ts +2 -0
- package/dist/server/modules/storage/local/local-storage.service.js +50 -1
- package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
- package/dist/server/modules/storage/models/domain.models.d.ts +4 -1
- package/dist/server/templates/claude-opus.json +146 -0
- package/dist/server/templates/codex-claude.json +178 -0
- package/dist/server/templates/simple-codex.json +146 -0
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/index-5Xb7jFMJ.js +641 -0
- package/dist/server/ui/assets/index-CbYIbCQV.css +32 -0
- package/dist/server/ui/favicon.svg +17 -0
- package/dist/server/ui/index.html +3 -2
- package/dist/templates/claude-opus.json +146 -0
- package/dist/templates/codex-claude.json +178 -0
- package/dist/templates/simple-codex.json +20 -8
- package/package.json +4 -2
- package/dist/server/ui/assets/index-DFChYYFN.css +0 -32
- package/dist/server/ui/assets/index-DpXRypHy.js +0 -636
|
@@ -55,6 +55,7 @@ const ExportSchema = zod_1.z
|
|
|
55
55
|
id: zod_1.z.string().uuid().optional(),
|
|
56
56
|
name: zod_1.z.string().min(1),
|
|
57
57
|
profileId: zod_1.z.string().uuid().optional(),
|
|
58
|
+
description: zod_1.z.string().nullable().optional(),
|
|
58
59
|
}))
|
|
59
60
|
.optional()
|
|
60
61
|
.default([]),
|
|
@@ -71,6 +72,13 @@ const ExportSchema = zod_1.z
|
|
|
71
72
|
.object({ promptId: zod_1.z.string().uuid().optional(), title: zod_1.z.string().optional() })
|
|
72
73
|
.nullable()
|
|
73
74
|
.optional(),
|
|
75
|
+
projectSettings: zod_1.z
|
|
76
|
+
.object({
|
|
77
|
+
initialPromptTitle: zod_1.z.string().optional(),
|
|
78
|
+
autoCleanStatusLabels: zod_1.z.array(zod_1.z.string()).optional(),
|
|
79
|
+
epicAssignedTemplate: zod_1.z.string().optional(),
|
|
80
|
+
})
|
|
81
|
+
.optional(),
|
|
74
82
|
})
|
|
75
83
|
.strict();
|
|
76
84
|
let ProjectsService = class ProjectsService {
|
|
@@ -230,6 +238,7 @@ let ProjectsService = class ProjectsService {
|
|
|
230
238
|
id: a.id,
|
|
231
239
|
name: a.name,
|
|
232
240
|
profileId: a.profileId,
|
|
241
|
+
description: a.description,
|
|
233
242
|
})),
|
|
234
243
|
statuses: payload.statuses.map((s) => ({
|
|
235
244
|
id: s.id,
|
|
@@ -266,6 +275,76 @@ let ProjectsService = class ProjectsService {
|
|
|
266
275
|
});
|
|
267
276
|
initialPromptSet = Boolean(targetPromptId);
|
|
268
277
|
}
|
|
278
|
+
if (payload.projectSettings) {
|
|
279
|
+
const ps = payload.projectSettings;
|
|
280
|
+
if (ps.initialPromptTitle) {
|
|
281
|
+
const promptByTitle = payload.prompts.find((p) => p.title.toLowerCase() === ps.initialPromptTitle.toLowerCase());
|
|
282
|
+
if (promptByTitle?.id) {
|
|
283
|
+
const mappedPromptId = result.mappings.promptIdMap[promptByTitle.id];
|
|
284
|
+
if (mappedPromptId) {
|
|
285
|
+
await this.settings.updateSettings({
|
|
286
|
+
projectId: result.project.id,
|
|
287
|
+
initialSessionPromptId: mappedPromptId,
|
|
288
|
+
});
|
|
289
|
+
initialPromptSet = true;
|
|
290
|
+
logger.info({ projectId: result.project.id, promptTitle: ps.initialPromptTitle }, 'Applied initial prompt from projectSettings');
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (ps.autoCleanStatusLabels && ps.autoCleanStatusLabels.length > 0) {
|
|
295
|
+
const statusLabelMap = new Map(templatePayload.statuses.map((s) => [s.label.toLowerCase(), s.id]));
|
|
296
|
+
const autoCleanStatusIds = ps.autoCleanStatusLabels
|
|
297
|
+
.map((label) => {
|
|
298
|
+
const templateId = statusLabelMap.get(label.toLowerCase());
|
|
299
|
+
return templateId ? result.mappings.statusIdMap[templateId] : undefined;
|
|
300
|
+
})
|
|
301
|
+
.filter((id) => !!id);
|
|
302
|
+
if (autoCleanStatusIds.length > 0) {
|
|
303
|
+
await this.settings.updateSettings({
|
|
304
|
+
autoClean: {
|
|
305
|
+
statusIds: { [result.project.id]: autoCleanStatusIds },
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
logger.info({ projectId: result.project.id, autoCleanStatusIds }, 'Applied autoClean statuses from projectSettings');
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const archiveTemplateStatus = templatePayload.statuses.find((s) => s.label.toLowerCase() === 'archive');
|
|
313
|
+
if (archiveTemplateStatus?.id) {
|
|
314
|
+
const archiveNewId = result.mappings.statusIdMap[archiveTemplateStatus.id];
|
|
315
|
+
if (archiveNewId) {
|
|
316
|
+
await this.settings.updateSettings({
|
|
317
|
+
autoClean: {
|
|
318
|
+
statusIds: { [result.project.id]: [archiveNewId] },
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
logger.info({ projectId: result.project.id, archiveStatusId: archiveNewId }, 'Auto-configured Archive status for auto-clean (fallback)');
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
if (ps.epicAssignedTemplate) {
|
|
326
|
+
await this.settings.updateSettings({
|
|
327
|
+
events: {
|
|
328
|
+
epicAssigned: { template: ps.epicAssignedTemplate },
|
|
329
|
+
},
|
|
330
|
+
});
|
|
331
|
+
logger.info({ projectId: result.project.id }, 'Applied epicAssigned template from projectSettings');
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
const archiveTemplateStatus = templatePayload.statuses.find((s) => s.label.toLowerCase() === 'archive');
|
|
336
|
+
if (archiveTemplateStatus?.id) {
|
|
337
|
+
const archiveNewId = result.mappings.statusIdMap[archiveTemplateStatus.id];
|
|
338
|
+
if (archiveNewId) {
|
|
339
|
+
await this.settings.updateSettings({
|
|
340
|
+
autoClean: {
|
|
341
|
+
statusIds: { [result.project.id]: [archiveNewId] },
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
logger.info({ projectId: result.project.id, archiveStatusId: archiveNewId }, 'Auto-configured Archive status for auto-clean');
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
269
348
|
return {
|
|
270
349
|
success: true,
|
|
271
350
|
project: result.project,
|
|
@@ -277,12 +356,13 @@ let ProjectsService = class ProjectsService {
|
|
|
277
356
|
}
|
|
278
357
|
async exportProject(projectId) {
|
|
279
358
|
logger.info({ projectId }, 'exportProject');
|
|
280
|
-
const [promptsRes, profilesRes, agentsRes, statusesRes, initialPrompt] = await Promise.all([
|
|
359
|
+
const [promptsRes, profilesRes, agentsRes, statusesRes, initialPrompt, settings] = await Promise.all([
|
|
281
360
|
this.storage.listPrompts(projectId, { limit: 1000, offset: 0 }),
|
|
282
361
|
this.storage.listAgentProfiles({ projectId, limit: 1000, offset: 0 }),
|
|
283
362
|
this.storage.listAgents(projectId, { limit: 1000, offset: 0 }),
|
|
284
363
|
this.storage.listStatuses(projectId, { limit: 1000, offset: 0 }),
|
|
285
364
|
this.storage.getInitialSessionPrompt(projectId),
|
|
365
|
+
Promise.resolve(this.settings.getSettings()),
|
|
286
366
|
]);
|
|
287
367
|
const secretKeys = new Set([
|
|
288
368
|
'apikey',
|
|
@@ -349,13 +429,36 @@ let ProjectsService = class ProjectsService {
|
|
|
349
429
|
maxTokens: prof.maxTokens,
|
|
350
430
|
};
|
|
351
431
|
}));
|
|
352
|
-
const agents = agentsRes.items.map((a) => ({
|
|
432
|
+
const agents = agentsRes.items.map((a) => ({
|
|
433
|
+
id: a.id,
|
|
434
|
+
name: a.name,
|
|
435
|
+
profileId: a.profileId,
|
|
436
|
+
description: a.description,
|
|
437
|
+
}));
|
|
353
438
|
const statuses = statusesRes.items.map((s) => ({
|
|
354
439
|
id: s.id,
|
|
355
440
|
label: s.label,
|
|
356
441
|
color: s.color,
|
|
357
442
|
position: s.position,
|
|
358
443
|
}));
|
|
444
|
+
const projectSettings = {};
|
|
445
|
+
if (initialPrompt?.title) {
|
|
446
|
+
projectSettings.initialPromptTitle = initialPrompt.title;
|
|
447
|
+
}
|
|
448
|
+
const autoCleanStatusIds = settings.autoClean?.statusIds?.[projectId] ?? [];
|
|
449
|
+
if (autoCleanStatusIds.length > 0) {
|
|
450
|
+
const statusMap = new Map(statusesRes.items.map((s) => [s.id, s.label]));
|
|
451
|
+
const autoCleanLabels = autoCleanStatusIds
|
|
452
|
+
.map((id) => statusMap.get(id))
|
|
453
|
+
.filter((label) => !!label);
|
|
454
|
+
if (autoCleanLabels.length > 0) {
|
|
455
|
+
projectSettings.autoCleanStatusLabels = autoCleanLabels;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const epicAssignedTemplate = settings.events?.epicAssigned?.template;
|
|
459
|
+
if (epicAssignedTemplate) {
|
|
460
|
+
projectSettings.epicAssignedTemplate = epicAssignedTemplate;
|
|
461
|
+
}
|
|
359
462
|
const exportPayload = {
|
|
360
463
|
version: 1,
|
|
361
464
|
exportedAt: new Date().toISOString(),
|
|
@@ -366,6 +469,7 @@ let ProjectsService = class ProjectsService {
|
|
|
366
469
|
initialPrompt: initialPrompt
|
|
367
470
|
? { promptId: initialPrompt.id, title: initialPrompt.title }
|
|
368
471
|
: null,
|
|
472
|
+
...(Object.keys(projectSettings).length > 0 && { projectSettings }),
|
|
369
473
|
};
|
|
370
474
|
return exportPayload;
|
|
371
475
|
}
|
|
@@ -386,10 +490,31 @@ let ProjectsService = class ProjectsService {
|
|
|
386
490
|
this.storage.listAgents(input.projectId, { limit: 10000, offset: 0 }),
|
|
387
491
|
this.storage.listStatuses(input.projectId, { limit: 10000, offset: 0 }),
|
|
388
492
|
]);
|
|
493
|
+
const templateStatusLabels = new Set(payload.statuses.map((s) => s.label.trim().toLowerCase()));
|
|
494
|
+
const unmatchedStatuses = [];
|
|
495
|
+
for (const s of existingStatuses.items) {
|
|
496
|
+
const labelKey = s.label.trim().toLowerCase();
|
|
497
|
+
if (!templateStatusLabels.has(labelKey)) {
|
|
498
|
+
const epicCount = await this.storage.countEpicsByStatus(s.id);
|
|
499
|
+
if (epicCount > 0) {
|
|
500
|
+
unmatchedStatuses.push({
|
|
501
|
+
id: s.id,
|
|
502
|
+
label: s.label,
|
|
503
|
+
color: s.color,
|
|
504
|
+
epicCount,
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
389
509
|
if (isDryRun) {
|
|
390
510
|
return {
|
|
391
511
|
dryRun: true,
|
|
392
512
|
missingProviders,
|
|
513
|
+
unmatchedStatuses,
|
|
514
|
+
templateStatuses: payload.statuses.map((s) => ({
|
|
515
|
+
label: s.label,
|
|
516
|
+
color: s.color,
|
|
517
|
+
})),
|
|
393
518
|
counts: {
|
|
394
519
|
toImport: {
|
|
395
520
|
prompts: payload.prompts.length,
|
|
@@ -413,7 +538,7 @@ let ProjectsService = class ProjectsService {
|
|
|
413
538
|
hint: 'Install/configure providers by name before importing profiles.',
|
|
414
539
|
});
|
|
415
540
|
}
|
|
416
|
-
const activeSessions =
|
|
541
|
+
const activeSessions = this.sessions.getActiveSessionsForProject(input.projectId);
|
|
417
542
|
if (activeSessions.length > 0) {
|
|
418
543
|
throw new common_1.BadRequestException({
|
|
419
544
|
message: 'Import aborted: active agent sessions detected',
|
|
@@ -421,137 +546,281 @@ let ProjectsService = class ProjectsService {
|
|
|
421
546
|
hint: 'Terminate all running sessions for this project before importing.',
|
|
422
547
|
});
|
|
423
548
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
});
|
|
440
|
-
const statusIdMap = {};
|
|
441
|
-
const promptIdMap = {};
|
|
442
|
-
const profileIdMap = {};
|
|
443
|
-
const agentIdMap = {};
|
|
444
|
-
for (const s of payload.statuses.sort((a, b) => a.position - b.position)) {
|
|
445
|
-
const created = await this.storage.createStatus({
|
|
549
|
+
try {
|
|
550
|
+
const oldAgentIdToName = new Map();
|
|
551
|
+
for (const a of existingAgents.items) {
|
|
552
|
+
oldAgentIdToName.set(a.id, a.name.trim().toLowerCase());
|
|
553
|
+
}
|
|
554
|
+
for (const a of existingAgents.items) {
|
|
555
|
+
await this.storage.deleteAgent(a.id);
|
|
556
|
+
}
|
|
557
|
+
for (const p of existingProfiles.items) {
|
|
558
|
+
await this.storage.deleteAgentProfile(p.id);
|
|
559
|
+
}
|
|
560
|
+
for (const pr of existingPrompts.items) {
|
|
561
|
+
await this.storage.deletePrompt(pr.id);
|
|
562
|
+
}
|
|
563
|
+
await this.settings.updateSettings({
|
|
446
564
|
projectId: input.projectId,
|
|
447
|
-
|
|
448
|
-
color: s.color,
|
|
449
|
-
position: s.position,
|
|
565
|
+
initialSessionPromptId: null,
|
|
450
566
|
});
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
567
|
+
const statusIdMap = {};
|
|
568
|
+
const promptIdMap = {};
|
|
569
|
+
const profileIdMap = {};
|
|
570
|
+
const agentIdMap = {};
|
|
571
|
+
const existingStatusByLabel = new Map();
|
|
572
|
+
for (const s of existingStatuses.items) {
|
|
573
|
+
existingStatusByLabel.set(s.label.trim().toLowerCase(), s);
|
|
574
|
+
}
|
|
575
|
+
for (const s of payload.statuses.sort((a, b) => a.position - b.position)) {
|
|
576
|
+
const labelKey = s.label.trim().toLowerCase();
|
|
577
|
+
const existing = existingStatusByLabel.get(labelKey);
|
|
578
|
+
if (existing) {
|
|
579
|
+
const updated = await this.storage.updateStatus(existing.id, {
|
|
580
|
+
color: s.color,
|
|
581
|
+
position: s.position,
|
|
582
|
+
});
|
|
583
|
+
if (s.id)
|
|
584
|
+
statusIdMap[s.id] = updated.id;
|
|
585
|
+
existingStatusByLabel.delete(labelKey);
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
const created = await this.storage.createStatus({
|
|
589
|
+
projectId: input.projectId,
|
|
590
|
+
label: s.label,
|
|
591
|
+
color: s.color,
|
|
592
|
+
position: s.position,
|
|
593
|
+
});
|
|
594
|
+
if (s.id)
|
|
595
|
+
statusIdMap[s.id] = created.id;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
const templateLabelToStatusId = new Map();
|
|
599
|
+
const allStatuses = await this.storage.listStatuses(input.projectId, {
|
|
600
|
+
limit: 10000,
|
|
601
|
+
offset: 0,
|
|
461
602
|
});
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
603
|
+
for (const s of allStatuses.items) {
|
|
604
|
+
templateLabelToStatusId.set(s.label.trim().toLowerCase(), s.id);
|
|
605
|
+
}
|
|
606
|
+
if (input.statusMappings && Object.keys(input.statusMappings).length > 0) {
|
|
607
|
+
let epicsMapped = 0;
|
|
608
|
+
let statusesDeleted = 0;
|
|
609
|
+
for (const [oldStatusId, targetLabel] of Object.entries(input.statusMappings)) {
|
|
610
|
+
const targetStatusId = templateLabelToStatusId.get(targetLabel.trim().toLowerCase());
|
|
611
|
+
if (targetStatusId) {
|
|
612
|
+
const remapped = await this.storage.updateEpicsStatus(oldStatusId, targetStatusId);
|
|
613
|
+
epicsMapped += remapped;
|
|
614
|
+
await this.storage.deleteStatus(oldStatusId);
|
|
615
|
+
statusesDeleted++;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
logger.info({ epicsMapped, statusesDeleted }, 'Applied status mappings: epics remapped and old statuses deleted');
|
|
470
619
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
620
|
+
const createdPrompts = [];
|
|
621
|
+
for (const p of payload.prompts) {
|
|
622
|
+
const created = await this.storage.createPrompt({
|
|
623
|
+
projectId: input.projectId,
|
|
624
|
+
title: p.title,
|
|
625
|
+
content: p.content,
|
|
626
|
+
tags: p.tags ?? [],
|
|
627
|
+
});
|
|
628
|
+
if (p.id)
|
|
629
|
+
promptIdMap[p.id] = created.id;
|
|
630
|
+
createdPrompts.push({ id: created.id, title: created.title });
|
|
631
|
+
}
|
|
632
|
+
for (const prof of payload.profiles) {
|
|
633
|
+
const providerId = available.get(prof.provider.name.trim().toLowerCase());
|
|
634
|
+
if (!providerId) {
|
|
635
|
+
throw new common_1.BadRequestException({ message: `Provider not found: ${prof.provider.name}` });
|
|
636
|
+
}
|
|
637
|
+
let optionsStr = null;
|
|
638
|
+
if (typeof prof.options === 'string') {
|
|
639
|
+
optionsStr = prof.options;
|
|
640
|
+
}
|
|
641
|
+
else if (prof.options && typeof prof.options === 'object') {
|
|
642
|
+
try {
|
|
643
|
+
optionsStr = JSON.stringify(prof.options);
|
|
644
|
+
}
|
|
645
|
+
catch {
|
|
646
|
+
optionsStr = null;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
const created = await this.storage.createAgentProfile({
|
|
650
|
+
projectId: input.projectId,
|
|
651
|
+
name: prof.name,
|
|
652
|
+
providerId,
|
|
653
|
+
options: optionsStr ?? null,
|
|
654
|
+
systemPrompt: null,
|
|
655
|
+
instructions: prof.instructions ?? null,
|
|
656
|
+
temperature: prof.temperature ?? null,
|
|
657
|
+
maxTokens: prof.maxTokens ?? null,
|
|
658
|
+
});
|
|
659
|
+
if (prof.id)
|
|
660
|
+
profileIdMap[prof.id] = created.id;
|
|
474
661
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
662
|
+
for (const a of payload.agents) {
|
|
663
|
+
const oldProfileId = a.profileId ?? '';
|
|
664
|
+
const newProfileId = oldProfileId && profileIdMap[oldProfileId] ? profileIdMap[oldProfileId] : undefined;
|
|
665
|
+
if (!newProfileId) {
|
|
666
|
+
throw new common_1.BadRequestException({
|
|
667
|
+
message: `Profile mapping missing for agent ${a.name}`,
|
|
668
|
+
profileId: oldProfileId || null,
|
|
669
|
+
});
|
|
478
670
|
}
|
|
479
|
-
|
|
480
|
-
|
|
671
|
+
const created = await this.storage.createAgent({
|
|
672
|
+
projectId: input.projectId,
|
|
673
|
+
name: a.name,
|
|
674
|
+
profileId: newProfileId,
|
|
675
|
+
description: a.description ?? null,
|
|
676
|
+
});
|
|
677
|
+
if (a.id)
|
|
678
|
+
agentIdMap[a.id] = created.id;
|
|
679
|
+
}
|
|
680
|
+
const newAgentByName = new Map();
|
|
681
|
+
for (const a of payload.agents) {
|
|
682
|
+
const created = agentIdMap[a.id ?? ''];
|
|
683
|
+
if (created) {
|
|
684
|
+
newAgentByName.set(a.name.trim().toLowerCase(), created);
|
|
481
685
|
}
|
|
482
686
|
}
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
providerId,
|
|
487
|
-
options: optionsStr ?? null,
|
|
488
|
-
systemPrompt: null,
|
|
489
|
-
instructions: prof.instructions ?? null,
|
|
490
|
-
temperature: prof.temperature ?? null,
|
|
491
|
-
maxTokens: prof.maxTokens ?? null,
|
|
687
|
+
const existingEpics = await this.storage.listEpics(input.projectId, {
|
|
688
|
+
limit: 100000,
|
|
689
|
+
offset: 0,
|
|
492
690
|
});
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
691
|
+
let epicsRemapped = 0;
|
|
692
|
+
let epicsCleared = 0;
|
|
693
|
+
for (const epic of existingEpics.items) {
|
|
694
|
+
if (epic.agentId) {
|
|
695
|
+
const oldAgentName = oldAgentIdToName.get(epic.agentId);
|
|
696
|
+
if (oldAgentName) {
|
|
697
|
+
const newAgentId = newAgentByName.get(oldAgentName);
|
|
698
|
+
if (newAgentId) {
|
|
699
|
+
await this.storage.updateEpic(epic.id, { agentId: newAgentId }, epic.version);
|
|
700
|
+
epicsRemapped++;
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
await this.storage.updateEpic(epic.id, { agentId: null }, epic.version);
|
|
704
|
+
epicsCleared++;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
else {
|
|
708
|
+
await this.storage.updateEpic(epic.id, { agentId: null }, epic.version);
|
|
709
|
+
epicsCleared++;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
logger.info({ epicsRemapped, epicsCleared }, 'Epic agent references updated after import');
|
|
714
|
+
let initialPromptSet = false;
|
|
715
|
+
if (payload.initialPrompt) {
|
|
716
|
+
let targetPromptId = null;
|
|
717
|
+
if (payload.initialPrompt.title) {
|
|
718
|
+
const match = createdPrompts.find((cp) => cp.title.trim().toLowerCase() === payload.initialPrompt.title.trim().toLowerCase());
|
|
719
|
+
if (match)
|
|
720
|
+
targetPromptId = match.id;
|
|
721
|
+
}
|
|
722
|
+
if (!targetPromptId && payload.initialPrompt.promptId) {
|
|
723
|
+
const mapped = promptIdMap[payload.initialPrompt.promptId];
|
|
724
|
+
if (mapped)
|
|
725
|
+
targetPromptId = mapped;
|
|
726
|
+
}
|
|
727
|
+
await this.settings.updateSettings({
|
|
728
|
+
projectId: input.projectId,
|
|
729
|
+
initialSessionPromptId: targetPromptId ?? null,
|
|
503
730
|
});
|
|
731
|
+
initialPromptSet = Boolean(targetPromptId);
|
|
504
732
|
}
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
733
|
+
if (payload.projectSettings) {
|
|
734
|
+
const ps = payload.projectSettings;
|
|
735
|
+
if (ps.initialPromptTitle) {
|
|
736
|
+
const matchByTitle = createdPrompts.find((cp) => cp.title.toLowerCase() === ps.initialPromptTitle.toLowerCase());
|
|
737
|
+
if (matchByTitle) {
|
|
738
|
+
await this.settings.updateSettings({
|
|
739
|
+
projectId: input.projectId,
|
|
740
|
+
initialSessionPromptId: matchByTitle.id,
|
|
741
|
+
});
|
|
742
|
+
initialPromptSet = true;
|
|
743
|
+
logger.info({ projectId: input.projectId, promptTitle: ps.initialPromptTitle }, 'Applied initial prompt from projectSettings');
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (ps.autoCleanStatusLabels && ps.autoCleanStatusLabels.length > 0) {
|
|
747
|
+
const autoCleanStatusIds = ps.autoCleanStatusLabels
|
|
748
|
+
.map((label) => templateLabelToStatusId.get(label.toLowerCase()))
|
|
749
|
+
.filter((id) => !!id);
|
|
750
|
+
if (autoCleanStatusIds.length > 0) {
|
|
751
|
+
await this.settings.updateSettings({
|
|
752
|
+
autoClean: {
|
|
753
|
+
statusIds: { [input.projectId]: autoCleanStatusIds },
|
|
754
|
+
},
|
|
755
|
+
});
|
|
756
|
+
logger.info({ projectId: input.projectId, autoCleanStatusIds }, 'Applied autoClean statuses from projectSettings');
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (ps.epicAssignedTemplate) {
|
|
760
|
+
await this.settings.updateSettings({
|
|
761
|
+
events: {
|
|
762
|
+
epicAssigned: { template: ps.epicAssignedTemplate },
|
|
763
|
+
},
|
|
764
|
+
});
|
|
765
|
+
logger.info({ projectId: input.projectId }, 'Applied epicAssigned template from projectSettings');
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
success: true,
|
|
770
|
+
mode: 'replace',
|
|
771
|
+
replaced: true,
|
|
772
|
+
missingProviders: [],
|
|
773
|
+
counts: {
|
|
774
|
+
imported: {
|
|
775
|
+
prompts: payload.prompts.length,
|
|
776
|
+
profiles: payload.profiles.length,
|
|
777
|
+
agents: payload.agents.length,
|
|
778
|
+
statuses: payload.statuses.length,
|
|
779
|
+
},
|
|
780
|
+
deleted: {
|
|
781
|
+
prompts: existingPrompts.total,
|
|
782
|
+
profiles: existingProfiles.total,
|
|
783
|
+
agents: existingAgents.total,
|
|
784
|
+
statuses: 0,
|
|
785
|
+
},
|
|
786
|
+
epics: {
|
|
787
|
+
preserved: existingEpics.total,
|
|
788
|
+
agentRemapped: epicsRemapped,
|
|
789
|
+
agentCleared: epicsCleared,
|
|
790
|
+
},
|
|
791
|
+
},
|
|
792
|
+
mappings: { promptIdMap, profileIdMap, agentIdMap, statusIdMap },
|
|
793
|
+
initialPromptSet,
|
|
794
|
+
message: 'Project configuration replaced. Epics preserved.',
|
|
795
|
+
};
|
|
512
796
|
}
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
797
|
+
catch (error) {
|
|
798
|
+
logger.error({ error, projectId: input.projectId }, 'Import failed');
|
|
799
|
+
const message = this.getImportErrorMessage(error);
|
|
800
|
+
throw new common_1.BadRequestException({ message });
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
getImportErrorMessage(error) {
|
|
804
|
+
const errorCode = error?.code;
|
|
805
|
+
if (errorCode === 'SQLITE_CONSTRAINT_FOREIGNKEY') {
|
|
806
|
+
return 'Import failed: Cannot delete items that are still referenced. Check for cross-project agents using these profiles.';
|
|
807
|
+
}
|
|
808
|
+
if (errorCode === 'SQLITE_CONSTRAINT_UNIQUE') {
|
|
809
|
+
return 'Import failed: Duplicate entry detected.';
|
|
810
|
+
}
|
|
811
|
+
if (error instanceof Error) {
|
|
812
|
+
if (error.message.includes('FOREIGN KEY constraint failed')) {
|
|
813
|
+
return 'Import failed: Cannot delete items that are still referenced. Check for cross-project agents using these profiles.';
|
|
520
814
|
}
|
|
521
|
-
if (
|
|
522
|
-
|
|
523
|
-
if (mapped)
|
|
524
|
-
targetPromptId = mapped;
|
|
815
|
+
if (error.message.includes('UNIQUE constraint failed')) {
|
|
816
|
+
return 'Import failed: Duplicate entry detected.';
|
|
525
817
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
}
|
|
530
|
-
initialPromptSet = Boolean(targetPromptId);
|
|
818
|
+
if (error.message.startsWith('Import failed')) {
|
|
819
|
+
return error.message;
|
|
820
|
+
}
|
|
821
|
+
return `Import failed: ${error.message}`;
|
|
531
822
|
}
|
|
532
|
-
return
|
|
533
|
-
success: true,
|
|
534
|
-
mode: 'replace',
|
|
535
|
-
replaced: true,
|
|
536
|
-
missingProviders: [],
|
|
537
|
-
counts: {
|
|
538
|
-
imported: {
|
|
539
|
-
prompts: payload.prompts.length,
|
|
540
|
-
profiles: payload.profiles.length,
|
|
541
|
-
agents: payload.agents.length,
|
|
542
|
-
statuses: payload.statuses.length,
|
|
543
|
-
},
|
|
544
|
-
deleted: {
|
|
545
|
-
prompts: existingPrompts.total,
|
|
546
|
-
profiles: existingProfiles.total,
|
|
547
|
-
agents: existingAgents.total,
|
|
548
|
-
statuses: existingStatuses.total,
|
|
549
|
-
},
|
|
550
|
-
},
|
|
551
|
-
mappings: { promptIdMap, profileIdMap, agentIdMap, statusIdMap },
|
|
552
|
-
initialPromptSet,
|
|
553
|
-
message: 'Project configuration replaced.',
|
|
554
|
-
};
|
|
823
|
+
return 'Import failed: An unexpected error occurred.';
|
|
555
824
|
}
|
|
556
825
|
};
|
|
557
826
|
exports.ProjectsService = ProjectsService;
|