devchain-cli 0.7.2 → 0.8.0

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 (135) hide show
  1. package/dist/drizzle/0027_legal_malice.sql +17 -0
  2. package/dist/drizzle/0028_populate_provider_configs.sql +19 -0
  3. package/dist/drizzle/0029_merge_profiles_by_family_slug.sql +116 -0
  4. package/dist/drizzle/0030_set_agents_provider_config_id.sql +14 -0
  5. package/dist/drizzle/0031_remove_legacy_profile_columns.sql +68 -0
  6. package/dist/drizzle/0032_agents_provider_config_not_null.sql +52 -0
  7. package/dist/drizzle/0033_profile_provider_configs_name.sql +38 -0
  8. package/dist/drizzle/0034_sessions_agent_running_unique.sql +30 -0
  9. package/dist/drizzle/0035_provider_config_position.sql +26 -0
  10. package/dist/drizzle/PHASE2_MIGRATION_GUIDE.md +109 -0
  11. package/dist/drizzle/meta/0027_snapshot.json +3648 -0
  12. package/dist/drizzle/meta/0035_snapshot.json +3670 -0
  13. package/dist/drizzle/meta/_journal.json +64 -1
  14. package/dist/drizzle/migration-guard.sql +17 -0
  15. package/dist/drizzle/phase2-rollback.sql +44 -0
  16. package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts +101 -12
  17. package/dist/node_modules/@devchain/shared/schemas/export-schema.d.ts.map +1 -1
  18. package/dist/node_modules/@devchain/shared/schemas/export-schema.js +20 -0
  19. package/dist/node_modules/@devchain/shared/schemas/export-schema.js.map +1 -1
  20. package/dist/node_modules/@devchain/shared/tsconfig.tsbuildinfo +1 -1
  21. package/dist/server/modules/agents/controllers/agents.controller.d.ts +15 -0
  22. package/dist/server/modules/agents/controllers/agents.controller.js +120 -57
  23. package/dist/server/modules/agents/controllers/agents.controller.js.map +1 -1
  24. package/dist/server/modules/core/services/preflight.service.d.ts +6 -0
  25. package/dist/server/modules/core/services/preflight.service.js +143 -57
  26. package/dist/server/modules/core/services/preflight.service.js.map +1 -1
  27. package/dist/server/modules/epics/services/epics.service.d.ts +10 -4
  28. package/dist/server/modules/epics/services/epics.service.js +8 -5
  29. package/dist/server/modules/epics/services/epics.service.js.map +1 -1
  30. package/dist/server/modules/events/catalog/epic.created.d.ts +18 -0
  31. package/dist/server/modules/events/catalog/epic.created.js +8 -0
  32. package/dist/server/modules/events/catalog/epic.created.js.map +1 -1
  33. package/dist/server/modules/events/catalog/epic.updated.d.ts +18 -0
  34. package/dist/server/modules/events/catalog/epic.updated.js +8 -0
  35. package/dist/server/modules/events/catalog/epic.updated.js.map +1 -1
  36. package/dist/server/modules/events/catalog/index.d.ts +36 -0
  37. package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.d.ts +1 -0
  38. package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.js +33 -2
  39. package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.js.map +1 -1
  40. package/dist/server/modules/events/subscribers/review-comment-notifier.subscriber.js +12 -5
  41. package/dist/server/modules/events/subscribers/review-comment-notifier.subscriber.js.map +1 -1
  42. package/dist/server/modules/mcp/services/mcp.service.js +16 -2
  43. package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
  44. package/dist/server/modules/mcp/services/terminal-activity.service.d.ts +1 -1
  45. package/dist/server/modules/mcp/services/terminal-activity.service.js +22 -2
  46. package/dist/server/modules/mcp/services/terminal-activity.service.js.map +1 -1
  47. package/dist/server/modules/profiles/controllers/profiles.controller.d.ts +11 -8
  48. package/dist/server/modules/profiles/controllers/profiles.controller.js +88 -14
  49. package/dist/server/modules/profiles/controllers/profiles.controller.js.map +1 -1
  50. package/dist/server/modules/profiles/controllers/provider-configs.controller.d.ts +9 -0
  51. package/dist/server/modules/profiles/controllers/provider-configs.controller.js +94 -0
  52. package/dist/server/modules/profiles/controllers/provider-configs.controller.js.map +1 -0
  53. package/dist/server/modules/profiles/dto.d.ts +81 -6
  54. package/dist/server/modules/profiles/dto.js +45 -3
  55. package/dist/server/modules/profiles/dto.js.map +1 -1
  56. package/dist/server/modules/profiles/profiles.module.js +2 -1
  57. package/dist/server/modules/profiles/profiles.module.js.map +1 -1
  58. package/dist/server/modules/projects/controllers/projects.controller.d.ts +78 -19
  59. package/dist/server/modules/projects/controllers/projects.controller.js +243 -17
  60. package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
  61. package/dist/server/modules/projects/dtos/export.dto.d.ts +44 -0
  62. package/dist/server/modules/projects/dtos/export.dto.js +10 -0
  63. package/dist/server/modules/projects/dtos/export.dto.js.map +1 -1
  64. package/dist/server/modules/projects/services/projects.service.d.ts +48 -11
  65. package/dist/server/modules/projects/services/projects.service.js +359 -27
  66. package/dist/server/modules/projects/services/projects.service.js.map +1 -1
  67. package/dist/server/modules/providers/controllers/providers.controller.js +12 -7
  68. package/dist/server/modules/providers/controllers/providers.controller.js.map +1 -1
  69. package/dist/server/modules/registry/controllers/templates.controller.d.ts +2 -2
  70. package/dist/server/modules/registry/services/registry-orchestration.service.js +11 -0
  71. package/dist/server/modules/registry/services/registry-orchestration.service.js.map +1 -1
  72. package/dist/server/modules/registry/services/template-upgrade.service.js +6 -0
  73. package/dist/server/modules/registry/services/template-upgrade.service.js.map +1 -1
  74. package/dist/server/modules/registry/services/unified-template.service.d.ts +2 -1
  75. package/dist/server/modules/registry/services/unified-template.service.js +61 -0
  76. package/dist/server/modules/registry/services/unified-template.service.js.map +1 -1
  77. package/dist/server/modules/reviews/services/reviews.service.js +8 -0
  78. package/dist/server/modules/reviews/services/reviews.service.js.map +1 -1
  79. package/dist/server/modules/sessions/services/activity-tracker.service.d.ts +2 -0
  80. package/dist/server/modules/sessions/services/activity-tracker.service.js +20 -0
  81. package/dist/server/modules/sessions/services/activity-tracker.service.js.map +1 -1
  82. package/dist/server/modules/sessions/services/sessions-message-pool.service.js +2 -2
  83. package/dist/server/modules/sessions/services/sessions-message-pool.service.js.map +1 -1
  84. package/dist/server/modules/sessions/services/sessions.service.d.ts +3 -1
  85. package/dist/server/modules/sessions/services/sessions.service.js +275 -144
  86. package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
  87. package/dist/server/modules/sessions/utils/env-builder.d.ts +8 -0
  88. package/dist/server/modules/sessions/utils/env-builder.js +62 -0
  89. package/dist/server/modules/sessions/utils/env-builder.js.map +1 -0
  90. package/dist/server/modules/settings/dtos/settings.dto.d.ts +84 -8
  91. package/dist/server/modules/settings/dtos/settings.dto.js +15 -2
  92. package/dist/server/modules/settings/dtos/settings.dto.js.map +1 -1
  93. package/dist/server/modules/settings/services/settings.service.d.ts +10 -1
  94. package/dist/server/modules/settings/services/settings.service.js +275 -0
  95. package/dist/server/modules/settings/services/settings.service.js.map +1 -1
  96. package/dist/server/modules/storage/db/db.provider.js +2 -0
  97. package/dist/server/modules/storage/db/db.provider.js.map +1 -1
  98. package/dist/server/modules/storage/db/schema.d.ts +185 -28
  99. package/dist/server/modules/storage/db/schema.js +32 -6
  100. package/dist/server/modules/storage/db/schema.js.map +1 -1
  101. package/dist/server/modules/storage/interfaces/storage.interface.d.ts +17 -1
  102. package/dist/server/modules/storage/interfaces/storage.interface.js.map +1 -1
  103. package/dist/server/modules/storage/local/local-storage.service.d.ts +15 -1
  104. package/dist/server/modules/storage/local/local-storage.service.js +352 -21
  105. package/dist/server/modules/storage/local/local-storage.service.js.map +1 -1
  106. package/dist/server/modules/storage/models/domain.models.d.ts +26 -7
  107. package/dist/server/modules/subscribers/actions/restart-agent.action.js +17 -19
  108. package/dist/server/modules/subscribers/actions/restart-agent.action.js.map +1 -1
  109. package/dist/server/modules/subscribers/events/event-fields-catalog.js +4 -0
  110. package/dist/server/modules/subscribers/events/event-fields-catalog.js.map +1 -1
  111. package/dist/server/modules/terminal/services/pty.service.js +6 -1
  112. package/dist/server/modules/terminal/services/pty.service.js.map +1 -1
  113. package/dist/server/modules/watchers/services/watcher-runner.service.js +27 -8
  114. package/dist/server/modules/watchers/services/watcher-runner.service.js.map +1 -1
  115. package/dist/{templates/claude-opus.json → server/templates/dev-loop.json} +365 -209
  116. package/dist/server/tsconfig.tsbuildinfo +1 -1
  117. package/dist/server/ui/assets/{ReviewDetailPage-I54h-2L-.js → ReviewDetailPage-YlFGGJv7.js} +2 -2
  118. package/dist/server/ui/assets/ReviewsPage-Diegg4jt.js +19 -0
  119. package/dist/server/ui/assets/index-DuMIsIyY.css +32 -0
  120. package/dist/server/ui/assets/index-sEtQpiB4.js +868 -0
  121. package/dist/server/ui/assets/{useReviewSubscription-C0GEsiRw.js → useReviewSubscription-CMhQ2m0h.js} +22 -22
  122. package/dist/server/ui/index.html +2 -2
  123. package/dist/{server/templates/claude-opus.json → templates/dev-loop.json} +365 -209
  124. package/package.json +9 -4
  125. package/dist/node_modules/@devchain/shared/schemas/export-schema.spec.d.ts +0 -2
  126. package/dist/node_modules/@devchain/shared/schemas/export-schema.spec.d.ts.map +0 -1
  127. package/dist/node_modules/@devchain/shared/schemas/export-schema.spec.js +0 -88
  128. package/dist/node_modules/@devchain/shared/schemas/export-schema.spec.js.map +0 -1
  129. package/dist/server/templates/claude-codex-advanced.json +0 -470
  130. package/dist/server/templates/simple-codex.json +0 -469
  131. package/dist/server/ui/assets/ReviewsPage-B4ua5hiX.js +0 -19
  132. package/dist/server/ui/assets/index-CqcmnFBh.css +0 -32
  133. package/dist/server/ui/assets/index-JbUMpbg7.js +0 -858
  134. package/dist/templates/claude-codex-advanced.json +0 -470
  135. package/dist/templates/simple-codex.json +0 -469
@@ -117,13 +117,26 @@ let ProjectsService = class ProjectsService {
117
117
  }
118
118
  async createFromTemplate(input) {
119
119
  logger.info({ input }, 'createFromTemplate');
120
- const templateResult = await this.unifiedTemplateService.getTemplate(input.slug, input.version ?? undefined);
120
+ let templateResult;
121
+ let templateSlug;
122
+ if (input.templatePath) {
123
+ templateResult = this.unifiedTemplateService.getTemplateFromFilePath(input.templatePath);
124
+ const manifest = templateResult.content._manifest;
125
+ templateSlug = manifest?.slug ?? this.deriveSlugFromPath(input.templatePath);
126
+ }
127
+ else if (input.slug) {
128
+ templateResult = await this.unifiedTemplateService.getTemplate(input.slug, input.version ?? undefined);
129
+ templateSlug = input.slug;
130
+ }
131
+ else {
132
+ throw new error_types_1.ValidationError('Either slug or templatePath is required', {});
133
+ }
121
134
  let payload;
122
135
  try {
123
136
  payload = shared_1.ExportSchema.parse(templateResult.content);
124
137
  }
125
138
  catch (error) {
126
- logger.error({ error, slug: input.slug, version: input.version }, 'Invalid template format');
139
+ logger.error({ error, slug: templateSlug, version: input.version }, 'Invalid template format');
127
140
  throw new error_types_1.ValidationError('Invalid template format', {
128
141
  hint: 'Template file does not match expected export schema',
129
142
  });
@@ -199,6 +212,81 @@ let ProjectsService = class ProjectsService {
199
212
  isTemplate: false,
200
213
  }, templatePayload);
201
214
  const { agentNameToId: agentNameToNewId, profileNameToId: profileNameToNewId } = this.buildNameToIdMaps(templatePayload, result.mappings);
215
+ const configLookupMap = new Map();
216
+ for (const prof of selectedProfilesByFamily.profilesToCreate) {
217
+ if (!prof.id)
218
+ continue;
219
+ const newProfileId = result.mappings.profileIdMap[prof.id];
220
+ if (!newProfileId)
221
+ continue;
222
+ const providerConfigs = prof.providerConfigs;
223
+ if (providerConfigs && providerConfigs.length > 0) {
224
+ for (const config of providerConfigs) {
225
+ const configProviderId = available.get(config.providerName.trim().toLowerCase());
226
+ if (!configProviderId) {
227
+ logger.warn({ profileName: prof.name, providerName: config.providerName }, 'Provider not found for config in createFromTemplate, skipping');
228
+ continue;
229
+ }
230
+ const createdConfig = await this.storage.createProfileProviderConfig({
231
+ profileId: newProfileId,
232
+ providerId: configProviderId,
233
+ name: config.name,
234
+ options: config.options ?? null,
235
+ env: config.env ?? null,
236
+ });
237
+ const lookupKey = `${newProfileId}:${config.name.trim().toLowerCase()}`;
238
+ configLookupMap.set(lookupKey, createdConfig.id);
239
+ }
240
+ }
241
+ }
242
+ const profilesWithProviderConfigs = new Map();
243
+ for (const prof of selectedProfilesByFamily.profilesToCreate) {
244
+ if (!prof.id)
245
+ continue;
246
+ const newProfileId = result.mappings.profileIdMap[prof.id];
247
+ if (!newProfileId)
248
+ continue;
249
+ const providerConfigs = prof.providerConfigs;
250
+ if (providerConfigs && providerConfigs.length > 0) {
251
+ profilesWithProviderConfigs.set(newProfileId, {
252
+ profileName: prof.name,
253
+ configNames: new Set(providerConfigs.map((pc) => pc.name.trim().toLowerCase())),
254
+ });
255
+ }
256
+ }
257
+ for (const a of payload.agents) {
258
+ const agentWithConfig = a;
259
+ if (!agentWithConfig.providerConfigName || !a.id)
260
+ continue;
261
+ const newAgentId = result.mappings.agentIdMap[a.id];
262
+ if (!newAgentId)
263
+ continue;
264
+ const remappedProfileId = selectedProfilesByFamily.agentProfileMap.get(a.id) ?? a.profileId;
265
+ const newProfileId = remappedProfileId
266
+ ? result.mappings.profileIdMap[remappedProfileId]
267
+ : null;
268
+ if (!newProfileId)
269
+ continue;
270
+ const lookupKey = `${newProfileId}:${agentWithConfig.providerConfigName.trim().toLowerCase()}`;
271
+ const providerConfigId = configLookupMap.get(lookupKey);
272
+ if (providerConfigId) {
273
+ await this.storage.updateAgent(newAgentId, { providerConfigId });
274
+ logger.debug({ agentName: a.name, providerConfigId }, 'Updated agent with providerConfigId');
275
+ }
276
+ else {
277
+ logger.warn({ agentName: a.name, providerConfigName: agentWithConfig.providerConfigName }, 'Provider config not found for agent in createFromTemplate');
278
+ }
279
+ }
280
+ for (const [newProfileId, { profileName, configNames }] of profilesWithProviderConfigs) {
281
+ const existingConfigs = await this.storage.listProfileProviderConfigsByProfile(newProfileId);
282
+ for (const existingConfig of existingConfigs) {
283
+ const isFromProviderConfigs = configNames.has(existingConfig.name.trim().toLowerCase());
284
+ if (!isFromProviderConfigs && existingConfig.name === profileName) {
285
+ await this.storage.deleteProfileProviderConfig(existingConfig.id);
286
+ logger.debug({ profileName, configId: existingConfig.id }, 'Deleted duplicate config created by storage layer');
287
+ }
288
+ }
289
+ }
202
290
  let mergedInitialPromptTitle;
203
291
  if (payload.initialPrompt?.title) {
204
292
  mergedInitialPromptTitle = payload.initialPrompt.title;
@@ -246,7 +334,7 @@ let ProjectsService = class ProjectsService {
246
334
  const installedVersion = templateResult.version ?? manifestVersion;
247
335
  const registryConfig = this.settings.getRegistryConfig();
248
336
  await this.settings.setProjectTemplateMetadata(result.project.id, {
249
- templateSlug: input.slug,
337
+ templateSlug,
250
338
  source: templateResult.source,
251
339
  installedVersion,
252
340
  registryUrl: templateResult.source === 'registry' ? registryConfig.url : null,
@@ -254,10 +342,29 @@ let ProjectsService = class ProjectsService {
254
342
  });
255
343
  logger.info({
256
344
  projectId: result.project.id,
257
- slug: input.slug,
345
+ slug: templateSlug,
258
346
  source: templateResult.source,
259
347
  version: installedVersion,
260
348
  }, 'Template metadata set for project');
349
+ const rawPresets = payload.presets;
350
+ const templatePresets = Array.isArray(rawPresets) ? rawPresets : [];
351
+ if (templatePresets.length > 0) {
352
+ await this.settings.setProjectPresets(result.project.id, templatePresets);
353
+ logger.info({ projectId: result.project.id, presetCount: templatePresets.length }, 'Presets stored for project');
354
+ }
355
+ if (input.presetName) {
356
+ const selectedPreset = templatePresets.find((p) => typeof p === 'object' && p !== null && 'name' in p && p.name === input.presetName);
357
+ if (!selectedPreset) {
358
+ logger.warn({ projectId: result.project.id, presetName: input.presetName }, 'Selected preset not found in template');
359
+ }
360
+ else {
361
+ await this.applyPreset(result.project.id, input.presetName, {
362
+ agentNameToId: agentNameToNewId,
363
+ configLookupMap,
364
+ });
365
+ logger.info({ projectId: result.project.id, presetName: input.presetName }, 'Applied preset to project');
366
+ }
367
+ }
261
368
  return {
262
369
  success: true,
263
370
  project: result.project,
@@ -273,7 +380,7 @@ let ProjectsService = class ProjectsService {
273
380
  }
274
381
  async exportProject(projectId, opts) {
275
382
  logger.info({ projectId }, 'exportProject');
276
- const { manifestOverrides } = opts ?? {};
383
+ const { manifestOverrides, presets: presetsOverride } = opts ?? {};
277
384
  const [project, promptsRes, profilesRes, agentsRes, statusesRes, initialPrompt, settings, watchersRes, subscribersRes,] = await Promise.all([
278
385
  this.storage.getProject(projectId),
279
386
  this.storage.listPrompts({ projectId, limit: 1000, offset: 0 }),
@@ -326,11 +433,6 @@ let ProjectsService = class ProjectsService {
326
433
  }
327
434
  return value;
328
435
  };
329
- const sanitizeOptionsString = (options) => {
330
- if (!options)
331
- return null;
332
- return options;
333
- };
334
436
  const fullPrompts = await Promise.all(promptsRes.items.map((p) => this.storage.getPrompt(p.id)));
335
437
  const prompts = fullPrompts.map((p) => ({
336
438
  id: p.id,
@@ -339,25 +441,66 @@ let ProjectsService = class ProjectsService {
339
441
  version: p.version,
340
442
  tags: p.tags,
341
443
  }));
342
- const profiles = await Promise.all(profilesRes.items.map(async (prof) => {
343
- const provider = await this.storage.getProvider(prof.providerId);
444
+ const configIdToInfo = new Map();
445
+ const profileIds = profilesRes.items.map((p) => p.id);
446
+ const allConfigsByProfile = new Map();
447
+ await Promise.all(profileIds.map(async (profileId) => {
448
+ const configs = await this.storage.listProfileProviderConfigsByProfile(profileId);
449
+ allConfigsByProfile.set(profileId, configs);
450
+ }));
451
+ const allProviderIds = new Set();
452
+ for (const configs of allConfigsByProfile.values()) {
453
+ for (const config of configs) {
454
+ allProviderIds.add(config.providerId);
455
+ }
456
+ }
457
+ const providersArray = await this.storage.listProvidersByIds([...allProviderIds]);
458
+ const providersMap = new Map(providersArray.map((p) => [p.id, p]));
459
+ const profiles = profilesRes.items.map((prof) => {
460
+ const configs = allConfigsByProfile.get(prof.id) || [];
461
+ let primaryProvider = null;
462
+ const providerConfigs = configs.map((config) => {
463
+ const configProvider = providersMap.get(config.providerId);
464
+ if (!primaryProvider && configProvider) {
465
+ primaryProvider = { id: configProvider.id, name: configProvider.name };
466
+ }
467
+ const configName = config.name;
468
+ configIdToInfo.set(config.id, { name: configName, profileId: prof.id });
469
+ return {
470
+ name: configName,
471
+ providerName: configProvider?.name || 'unknown',
472
+ options: config.options,
473
+ env: config.env,
474
+ position: config.position,
475
+ };
476
+ });
344
477
  return {
345
478
  id: prof.id,
346
479
  name: prof.name,
347
- provider: { id: provider.id, name: provider.name },
480
+ provider: primaryProvider,
348
481
  familySlug: prof.familySlug,
349
- options: sanitizeOptionsString(prof.options),
350
482
  instructions: prof.instructions,
351
483
  temperature: prof.temperature,
352
484
  maxTokens: prof.maxTokens,
485
+ ...(providerConfigs.length > 0 && { providerConfigs }),
353
486
  };
354
- }));
355
- const agents = agentsRes.items.map((a) => ({
356
- id: a.id,
357
- name: a.name,
358
- profileId: a.profileId,
359
- description: a.description,
360
- }));
487
+ });
488
+ const agents = agentsRes.items.map((a) => {
489
+ let providerConfigName = null;
490
+ if (a.providerConfigId) {
491
+ const configInfo = configIdToInfo.get(a.providerConfigId);
492
+ if (configInfo) {
493
+ providerConfigName = configInfo.name;
494
+ }
495
+ }
496
+ return {
497
+ id: a.id,
498
+ name: a.name,
499
+ profileId: a.profileId,
500
+ description: a.description,
501
+ ...(providerConfigName && { providerConfigName }),
502
+ };
503
+ });
361
504
  const statuses = statusesRes.items.map((s) => ({
362
505
  id: s.id,
363
506
  label: s.label,
@@ -467,9 +610,114 @@ let ProjectsService = class ProjectsService {
467
610
  ...(Object.keys(projectSettings).length > 0 && { projectSettings }),
468
611
  watchers,
469
612
  subscribers,
613
+ ...(presetsOverride !== undefined
614
+ ? { presets: presetsOverride }
615
+ : this.settings.getProjectPresets(projectId).length > 0
616
+ ? { presets: this.settings.getProjectPresets(projectId) }
617
+ : {}),
470
618
  };
471
619
  return exportPayload;
472
620
  }
621
+ async doesProjectMatchPreset(projectId, preset) {
622
+ const agentsRes = await this.storage.listAgents(projectId, { limit: 1000, offset: 0 });
623
+ const agentsByName = new Map(agentsRes.items.map((a) => [a.name.toLowerCase(), a]));
624
+ const uniqueProviderConfigIds = new Set();
625
+ for (const agent of agentsRes.items) {
626
+ if (agent.providerConfigId) {
627
+ uniqueProviderConfigIds.add(agent.providerConfigId);
628
+ }
629
+ }
630
+ const allProviderConfigs = uniqueProviderConfigIds.size > 0
631
+ ? await this.storage.listProfileProviderConfigsByIds(Array.from(uniqueProviderConfigIds))
632
+ : [];
633
+ const providerConfigNames = new Map(allProviderConfigs.map((c) => [c.id, c.name]));
634
+ for (const agentConfig of preset.agentConfigs) {
635
+ const agentNameLower = agentConfig.agentName.trim().toLowerCase();
636
+ const providerConfigNameLower = agentConfig.providerConfigName.trim().toLowerCase();
637
+ const agent = agentsByName.get(agentNameLower);
638
+ if (!agent) {
639
+ return false;
640
+ }
641
+ const currentProviderConfigName = providerConfigNames.get(agent.providerConfigId ?? '');
642
+ if (currentProviderConfigName?.toLowerCase() !== providerConfigNameLower) {
643
+ return false;
644
+ }
645
+ }
646
+ return true;
647
+ }
648
+ async applyPreset(projectId, presetName, nameMaps) {
649
+ logger.info({ projectId, presetName }, 'applyPreset');
650
+ const warnings = [];
651
+ const presets = this.settings.getProjectPresets(projectId);
652
+ const selectedPreset = presets.find((p) => p.name === presetName);
653
+ if (!selectedPreset) {
654
+ throw new error_types_1.NotFoundError('Preset', presetName);
655
+ }
656
+ if (!selectedPreset.agentConfigs || !Array.isArray(selectedPreset.agentConfigs)) {
657
+ throw new error_types_1.ValidationError(`Preset "${presetName}" has invalid or missing agentConfigs`, {
658
+ presetName,
659
+ });
660
+ }
661
+ const agentsRes = await this.storage.listAgents(projectId, { limit: 1000, offset: 0 });
662
+ let agentNameToId;
663
+ if (nameMaps?.agentNameToId) {
664
+ agentNameToId = nameMaps.agentNameToId;
665
+ }
666
+ else {
667
+ agentNameToId = new Map();
668
+ for (const agent of agentsRes.items) {
669
+ agentNameToId.set(agent.name.toLowerCase(), agent.id);
670
+ }
671
+ }
672
+ let configLookupMap;
673
+ if (nameMaps?.configLookupMap) {
674
+ configLookupMap = nameMaps.configLookupMap;
675
+ }
676
+ else {
677
+ configLookupMap = new Map();
678
+ const profilesRes = await this.storage.listAgentProfiles({
679
+ projectId,
680
+ limit: 1000,
681
+ offset: 0,
682
+ });
683
+ for (const profile of profilesRes.items) {
684
+ const configs = await this.storage.listProfileProviderConfigsByProfile(profile.id);
685
+ for (const config of configs) {
686
+ const lookupKey = `${profile.id}:${config.name.trim().toLowerCase()}`;
687
+ configLookupMap.set(lookupKey, config.id);
688
+ }
689
+ }
690
+ }
691
+ let applied = 0;
692
+ const agentsById = new Map(agentsRes.items.map((a) => [a.id, a]));
693
+ for (const agentConfig of selectedPreset.agentConfigs) {
694
+ const agentId = agentNameToId.get(agentConfig.agentName.trim().toLowerCase());
695
+ if (!agentId) {
696
+ warnings.push(`Agent "${agentConfig.agentName}" not found in project`);
697
+ continue;
698
+ }
699
+ const agent = agentsById.get(agentId);
700
+ if (!agent)
701
+ continue;
702
+ const profileId = agent.profileId;
703
+ const lookupKey = `${profileId}:${agentConfig.providerConfigName.trim().toLowerCase()}`;
704
+ const providerConfigId = configLookupMap.get(lookupKey);
705
+ if (!providerConfigId) {
706
+ warnings.push(`Provider config "${agentConfig.providerConfigName}" not found for agent "${agentConfig.agentName}"`);
707
+ continue;
708
+ }
709
+ await this.storage.updateAgent(agentId, { providerConfigId });
710
+ applied++;
711
+ logger.debug({ projectId, agentId, agentName: agentConfig.agentName, providerConfigId }, 'Applied preset: updated agent provider config');
712
+ }
713
+ const fullMatch = warnings.length === 0 && applied === selectedPreset.agentConfigs.length;
714
+ if (fullMatch) {
715
+ await this.settings.setProjectActivePreset(projectId, presetName);
716
+ logger.info({ projectId, presetName }, 'Active preset set (full match)');
717
+ }
718
+ logger.info({ projectId, presetName, applied, warnings: warnings.length }, 'Preset applied');
719
+ return { applied, warnings };
720
+ }
473
721
  async importProject(input) {
474
722
  logger.info({ projectId: input.projectId, dryRun: input.dryRun }, 'importProject');
475
723
  const isDryRun = input.dryRun ?? false;
@@ -668,16 +916,10 @@ let ProjectsService = class ProjectsService {
668
916
  createdPrompts.push({ id: created.id, title: created.title });
669
917
  }
670
918
  for (const prof of selectedProfilesByFamily.profilesToCreate) {
671
- const providerId = available.get(prof.provider.name.trim().toLowerCase());
672
- if (!providerId) {
673
- throw new error_types_1.NotFoundError('Provider', prof.provider.name);
674
- }
675
919
  const created = await this.storage.createAgentProfile({
676
920
  projectId: input.projectId,
677
921
  name: prof.name,
678
- providerId,
679
922
  familySlug: prof.familySlug ?? null,
680
- options: this.normalizeProfileOptions(prof.options),
681
923
  systemPrompt: null,
682
924
  instructions: prof.instructions ?? null,
683
925
  temperature: prof.temperature ?? null,
@@ -686,6 +928,55 @@ let ProjectsService = class ProjectsService {
686
928
  const profKey = prof.id || `name:${prof.name.trim().toLowerCase()}`;
687
929
  profileIdMap[profKey] = created.id;
688
930
  }
931
+ const configLookupMap = new Map();
932
+ const configIdMap = {};
933
+ for (const prof of selectedProfilesByFamily.profilesToCreate) {
934
+ const profKey = prof.id || `name:${prof.name.trim().toLowerCase()}`;
935
+ const newProfileId = profileIdMap[profKey];
936
+ if (!newProfileId)
937
+ continue;
938
+ const providerConfigs = prof.providerConfigs;
939
+ if (providerConfigs && providerConfigs.length > 0) {
940
+ for (const config of providerConfigs) {
941
+ const configProviderId = available.get(config.providerName.trim().toLowerCase());
942
+ if (!configProviderId) {
943
+ logger.warn({ profileName: prof.name, providerName: config.providerName }, 'Provider not found for config, skipping');
944
+ continue;
945
+ }
946
+ const createdConfig = await this.storage.createProfileProviderConfig({
947
+ profileId: newProfileId,
948
+ providerId: configProviderId,
949
+ name: config.name,
950
+ options: config.options ?? null,
951
+ env: config.env ?? null,
952
+ });
953
+ const lookupKey = `${newProfileId}:${config.name.trim().toLowerCase()}`;
954
+ configLookupMap.set(lookupKey, createdConfig.id);
955
+ configIdMap[`${profKey}:${config.name}`] = createdConfig.id;
956
+ }
957
+ }
958
+ else {
959
+ const providerName = prof.provider.name.trim().toLowerCase();
960
+ const configProviderId = available.get(providerName);
961
+ if (configProviderId) {
962
+ const options = prof.options != null
963
+ ? typeof prof.options === 'string'
964
+ ? prof.options
965
+ : JSON.stringify(prof.options)
966
+ : null;
967
+ const createdConfig = await this.storage.createProfileProviderConfig({
968
+ profileId: newProfileId,
969
+ providerId: configProviderId,
970
+ name: 'default',
971
+ options,
972
+ env: null,
973
+ });
974
+ const lookupKey = `${newProfileId}:default`;
975
+ configLookupMap.set(lookupKey, createdConfig.id);
976
+ configIdMap[`${profKey}:default`] = createdConfig.id;
977
+ }
978
+ }
979
+ }
689
980
  for (const a of payload.agents) {
690
981
  const remappedProfileId = selectedProfilesByFamily.agentProfileMap.get(a.id ?? '');
691
982
  const oldProfileId = remappedProfileId ?? a.profileId ?? '';
@@ -695,11 +986,33 @@ let ProjectsService = class ProjectsService {
695
986
  profileId: oldProfileId || null,
696
987
  });
697
988
  }
989
+ let providerConfigId;
990
+ const agentWithConfig = a;
991
+ if (agentWithConfig.providerConfigName && newProfileId) {
992
+ const lookupKey = `${newProfileId}:${agentWithConfig.providerConfigName.trim().toLowerCase()}`;
993
+ providerConfigId = configLookupMap.get(lookupKey);
994
+ }
995
+ if (!providerConfigId && newProfileId) {
996
+ const profilePrefix = `${newProfileId}:`;
997
+ for (const [key, configId] of configLookupMap.entries()) {
998
+ if (key.startsWith(profilePrefix)) {
999
+ providerConfigId = configId;
1000
+ break;
1001
+ }
1002
+ }
1003
+ }
1004
+ if (!providerConfigId) {
1005
+ throw new error_types_1.ValidationError(`No provider config available for agent ${a.name}`, {
1006
+ profileId: newProfileId,
1007
+ providerConfigName: agentWithConfig.providerConfigName || null,
1008
+ });
1009
+ }
698
1010
  const created = await this.storage.createAgent({
699
1011
  projectId: input.projectId,
700
1012
  name: a.name,
701
1013
  profileId: newProfileId,
702
1014
  description: a.description ?? null,
1015
+ providerConfigId,
703
1016
  });
704
1017
  const agentKey = a.id || `name:${a.name.trim().toLowerCase()}`;
705
1018
  agentIdMap[agentKey] = created.id;
@@ -796,6 +1109,16 @@ let ProjectsService = class ProjectsService {
796
1109
  });
797
1110
  logger.info({ projectId: input.projectId, slug: payload._manifest.slug, source: templateSource }, 'Updated template metadata after import');
798
1111
  }
1112
+ const rawPresets = payload.presets;
1113
+ const templatePresets = Array.isArray(rawPresets) ? rawPresets : [];
1114
+ if (templatePresets.length > 0) {
1115
+ await this.settings.setProjectPresets(input.projectId, templatePresets);
1116
+ logger.info({ projectId: input.projectId, presetCount: templatePresets.length }, 'Presets replaced from template during import');
1117
+ }
1118
+ else {
1119
+ await this.settings.clearProjectPresets(input.projectId);
1120
+ logger.info({ projectId: input.projectId }, 'Presets cleared during import (template has none)');
1121
+ }
799
1122
  return {
800
1123
  success: true,
801
1124
  mode: 'replace',
@@ -1261,6 +1584,11 @@ let ProjectsService = class ProjectsService {
1261
1584
  .replace(/[^a-z0-9]+/g, '-')
1262
1585
  .replace(/(^-|-$)/g, '');
1263
1586
  }
1587
+ deriveSlugFromPath(filePath) {
1588
+ const filename = (0, path_1.basename)(filePath);
1589
+ const nameWithoutExt = filename.replace(/\.json$/i, '');
1590
+ return this.slugify(nameWithoutExt);
1591
+ }
1264
1592
  async getTemplateManifestForProject(projectId) {
1265
1593
  const metadata = this.settings.getProjectTemplateMetadata(projectId);
1266
1594
  if (!metadata?.templateSlug) {
@@ -1268,6 +1596,10 @@ let ProjectsService = class ProjectsService {
1268
1596
  return null;
1269
1597
  }
1270
1598
  try {
1599
+ if (metadata.source === 'file') {
1600
+ logger.debug({ projectId, templateSlug: metadata.templateSlug }, 'File-based template - no manifest available');
1601
+ return null;
1602
+ }
1271
1603
  if (metadata.source === 'bundled') {
1272
1604
  const template = this.unifiedTemplateService.getBundledTemplate(metadata.templateSlug);
1273
1605
  return template.content._manifest ?? null;