devchain-cli 0.3.2 → 0.4.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/README.md +14 -0
- package/dist/server/app.module.js +2 -0
- package/dist/server/app.module.js.map +1 -1
- package/dist/server/common/constants/terminal.d.ts +3 -0
- package/dist/server/common/constants/terminal.js +7 -0
- package/dist/server/common/constants/terminal.js.map +1 -0
- package/dist/server/common/validation/template-validation.d.ts +8 -0
- package/dist/server/common/validation/template-validation.js +18 -0
- package/dist/server/common/validation/template-validation.js.map +1 -0
- package/dist/server/modules/epics/services/epics.service.d.ts +2 -0
- package/dist/server/modules/epics/services/epics.service.js +194 -32
- package/dist/server/modules/epics/services/epics.service.js.map +1 -1
- package/dist/server/modules/events/catalog/epic.assigned.d.ts +9 -13
- package/dist/server/modules/events/catalog/epic.assigned.js +5 -5
- package/dist/server/modules/events/catalog/epic.assigned.js.map +1 -1
- package/dist/server/modules/events/catalog/epic.created.d.ts +39 -0
- package/dist/server/modules/events/catalog/epic.created.js +20 -0
- package/dist/server/modules/events/catalog/epic.created.js.map +1 -0
- package/dist/server/modules/events/catalog/epic.updated.d.ts +178 -0
- package/dist/server/modules/events/catalog/epic.updated.js +43 -0
- package/dist/server/modules/events/catalog/epic.updated.js.map +1 -0
- package/dist/server/modules/events/catalog/index.d.ts +221 -12
- package/dist/server/modules/events/catalog/index.js +9 -1
- package/dist/server/modules/events/catalog/index.js.map +1 -1
- package/dist/server/modules/events/catalog/settings.terminal.changed.d.ts +12 -0
- package/dist/server/modules/events/catalog/settings.terminal.changed.js +11 -0
- package/dist/server/modules/events/catalog/settings.terminal.changed.js.map +1 -0
- package/dist/server/modules/events/services/events.service.js +0 -7
- package/dist/server/modules/events/services/events.service.js.map +1 -1
- package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.d.ts +5 -2
- package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.js +44 -24
- package/dist/server/modules/events/subscribers/chat-message-delivery.subscriber.js.map +1 -1
- package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.d.ts +5 -8
- package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.js +37 -56
- package/dist/server/modules/events/subscribers/epic-assignment-notifier.subscriber.js.map +1 -1
- package/dist/server/modules/mcp/dtos/mcp.dto.d.ts +8 -6
- package/dist/server/modules/mcp/dtos/mcp.dto.js.map +1 -1
- package/dist/server/modules/mcp/mcp.module.js +2 -0
- package/dist/server/modules/mcp/mcp.module.js.map +1 -1
- package/dist/server/modules/mcp/services/mcp.service.d.ts +5 -1
- package/dist/server/modules/mcp/services/mcp.service.js +65 -33
- package/dist/server/modules/mcp/services/mcp.service.js.map +1 -1
- package/dist/server/modules/projects/controllers/projects.controller.d.ts +140 -6
- package/dist/server/modules/projects/controllers/projects.controller.js +91 -31
- package/dist/server/modules/projects/controllers/projects.controller.js.map +1 -1
- package/dist/server/modules/projects/dtos/export.dto.d.ts +91 -0
- package/dist/server/modules/projects/dtos/export.dto.js +38 -0
- package/dist/server/modules/projects/dtos/export.dto.js.map +1 -0
- package/dist/server/modules/projects/projects.module.js +1 -0
- package/dist/server/modules/projects/projects.module.js.map +1 -1
- package/dist/server/modules/projects/services/projects.service.d.ts +37 -3
- package/dist/server/modules/projects/services/projects.service.js +353 -526
- package/dist/server/modules/projects/services/projects.service.js.map +1 -1
- package/dist/server/modules/registry/controllers/registry.controller.d.ts +98 -0
- package/dist/server/modules/registry/controllers/registry.controller.js +352 -0
- package/dist/server/modules/registry/controllers/registry.controller.js.map +1 -0
- package/dist/server/modules/registry/controllers/templates.controller.d.ts +35 -0
- package/dist/server/modules/registry/controllers/templates.controller.js +157 -0
- package/dist/server/modules/registry/controllers/templates.controller.js.map +1 -0
- package/dist/server/modules/registry/dtos/registry-error.d.ts +11 -0
- package/dist/server/modules/registry/dtos/registry-error.js +30 -0
- package/dist/server/modules/registry/dtos/registry-error.js.map +1 -0
- package/dist/server/modules/registry/dtos/registry.dto.d.ts +7 -0
- package/dist/server/modules/registry/dtos/registry.dto.js +50 -0
- package/dist/server/modules/registry/dtos/registry.dto.js.map +1 -0
- package/dist/server/modules/registry/index.d.ts +7 -0
- package/dist/server/modules/registry/index.js +24 -0
- package/dist/server/modules/registry/index.js.map +1 -0
- package/dist/server/modules/registry/interfaces/registry.interface.d.ts +68 -0
- package/dist/server/modules/registry/interfaces/registry.interface.js +3 -0
- package/dist/server/modules/registry/interfaces/registry.interface.js.map +1 -0
- package/dist/server/modules/registry/registry.module.d.ts +2 -0
- package/dist/server/modules/registry/registry.module.js +45 -0
- package/dist/server/modules/registry/registry.module.js.map +1 -0
- package/dist/server/modules/registry/services/registry-client.service.d.ts +14 -0
- package/dist/server/modules/registry/services/registry-client.service.js +226 -0
- package/dist/server/modules/registry/services/registry-client.service.js.map +1 -0
- package/dist/server/modules/registry/services/registry-orchestration.service.d.ts +45 -0
- package/dist/server/modules/registry/services/registry-orchestration.service.js +165 -0
- package/dist/server/modules/registry/services/registry-orchestration.service.js.map +1 -0
- package/dist/server/modules/registry/services/template-cache.service.d.ts +66 -0
- package/dist/server/modules/registry/services/template-cache.service.js +310 -0
- package/dist/server/modules/registry/services/template-cache.service.js.map +1 -0
- package/dist/server/modules/registry/services/template-upgrade.service.d.ts +41 -0
- package/dist/server/modules/registry/services/template-upgrade.service.js +212 -0
- package/dist/server/modules/registry/services/template-upgrade.service.js.map +1 -0
- package/dist/server/modules/registry/services/unified-template.service.d.ts +35 -0
- package/dist/server/modules/registry/services/unified-template.service.js +228 -0
- package/dist/server/modules/registry/services/unified-template.service.js.map +1 -0
- package/dist/server/modules/sessions/controllers/sessions.controller.d.ts +32 -1
- package/dist/server/modules/sessions/controllers/sessions.controller.js +150 -8
- package/dist/server/modules/sessions/controllers/sessions.controller.js.map +1 -1
- package/dist/server/modules/sessions/services/message-activity-stream.service.d.ts +12 -0
- package/dist/server/modules/sessions/services/message-activity-stream.service.js +51 -0
- package/dist/server/modules/sessions/services/message-activity-stream.service.js.map +1 -0
- package/dist/server/modules/sessions/services/sessions-message-pool.service.d.ts +125 -0
- package/dist/server/modules/sessions/services/sessions-message-pool.service.js +599 -0
- package/dist/server/modules/sessions/services/sessions-message-pool.service.js.map +1 -0
- package/dist/server/modules/sessions/services/sessions.service.d.ts +2 -9
- package/dist/server/modules/sessions/services/sessions.service.js +32 -27
- package/dist/server/modules/sessions/services/sessions.service.js.map +1 -1
- package/dist/server/modules/sessions/sessions.module.js +16 -2
- package/dist/server/modules/sessions/sessions.module.js.map +1 -1
- package/dist/server/modules/settings/dtos/settings.dto.d.ts +231 -0
- package/dist/server/modules/settings/dtos/settings.dto.js +40 -1
- package/dist/server/modules/settings/dtos/settings.dto.js.map +1 -1
- package/dist/server/modules/settings/services/settings.service.d.ts +56 -15
- package/dist/server/modules/settings/services/settings.service.js +295 -10
- package/dist/server/modules/settings/services/settings.service.js.map +1 -1
- package/dist/server/modules/subscribers/actions/action.interface.d.ts +2 -0
- package/dist/server/modules/subscribers/actions/send-message.action.js +34 -11
- package/dist/server/modules/subscribers/actions/send-message.action.js.map +1 -1
- package/dist/server/modules/subscribers/events/event-fields-catalog.js +109 -6
- package/dist/server/modules/subscribers/events/event-fields-catalog.js.map +1 -1
- package/dist/server/modules/subscribers/services/subscriber-executor.service.d.ts +3 -1
- package/dist/server/modules/subscribers/services/subscriber-executor.service.js +13 -1
- package/dist/server/modules/subscribers/services/subscriber-executor.service.js.map +1 -1
- package/dist/server/modules/terminal/dtos/ws-envelope.dto.d.ts +20 -0
- package/dist/server/modules/terminal/dtos/ws-envelope.dto.js +8 -1
- package/dist/server/modules/terminal/dtos/ws-envelope.dto.js.map +1 -1
- package/dist/server/modules/terminal/gateways/terminal.gateway.d.ts +1 -3
- package/dist/server/modules/terminal/gateways/terminal.gateway.js +50 -11
- package/dist/server/modules/terminal/gateways/terminal.gateway.js.map +1 -1
- package/dist/server/modules/terminal/services/pty.service.d.ts +1 -3
- package/dist/server/modules/terminal/services/pty.service.js +2 -14
- package/dist/server/modules/terminal/services/pty.service.js.map +1 -1
- package/dist/server/modules/terminal/services/terminal-seed.service.d.ts +6 -4
- package/dist/server/modules/terminal/services/terminal-seed.service.js +74 -67
- package/dist/server/modules/terminal/services/terminal-seed.service.js.map +1 -1
- package/dist/server/modules/terminal/services/tmux.service.js +3 -2
- package/dist/server/modules/terminal/services/tmux.service.js.map +1 -1
- package/dist/server/modules/terminal/terminal.module.js +0 -3
- package/dist/server/modules/terminal/terminal.module.js.map +1 -1
- package/dist/server/templates/claude-codex-advanced.json +132 -70
- package/dist/server/templates/claude-opus.json +32 -20
- package/dist/server/templates/simple-codex.json +32 -20
- package/dist/server/test-setup.js +19 -0
- package/dist/server/test-setup.js.map +1 -1
- package/dist/server/tsconfig.tsbuildinfo +1 -1
- package/dist/server/ui/assets/index-BkGGbapJ.css +32 -0
- package/dist/server/ui/assets/index-DLpDwHdv.js +733 -0
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/claude-codex-advanced.json +132 -70
- package/dist/templates/claude-opus.json +32 -20
- package/dist/templates/simple-codex.json +32 -20
- package/package.json +15 -4
- package/dist/server/modules/terminal/services/terminal-emulator.service.d.ts +0 -40
- package/dist/server/modules/terminal/services/terminal-emulator.service.js +0 -356
- package/dist/server/modules/terminal/services/terminal-emulator.service.js.map +0 -1
- package/dist/server/templates/claude-codex-advanced-swe.json +0 -130
- package/dist/server/ui/assets/index-C9GXCjnF.js +0 -700
- package/dist/server/ui/assets/index-o0FbZg-1.css +0 -32
- package/dist/templates/claude-codex-advanced-swe.json +0 -130
|
@@ -24,124 +24,17 @@ const error_types_1 = require("../../../common/errors/error-types");
|
|
|
24
24
|
const path_1 = require("path");
|
|
25
25
|
const fs_1 = require("fs");
|
|
26
26
|
const env_config_1 = require("../../../common/config/env.config");
|
|
27
|
-
const
|
|
27
|
+
const shared_1 = require("@devchain/shared");
|
|
28
|
+
const unified_template_service_1 = require("../../registry/services/unified-template.service");
|
|
28
29
|
const logger = (0, logger_1.createLogger)('ProjectsService');
|
|
29
|
-
const ExportSchema = zod_1.z
|
|
30
|
-
.object({
|
|
31
|
-
version: zod_1.z.number().optional().default(1),
|
|
32
|
-
exportedAt: zod_1.z.string().optional(),
|
|
33
|
-
prompts: zod_1.z
|
|
34
|
-
.array(zod_1.z.object({
|
|
35
|
-
id: zod_1.z.string().uuid().optional(),
|
|
36
|
-
title: zod_1.z.string().min(1),
|
|
37
|
-
content: zod_1.z.string().default(''),
|
|
38
|
-
version: zod_1.z.number().optional(),
|
|
39
|
-
tags: zod_1.z.array(zod_1.z.string()).optional().default([]),
|
|
40
|
-
}))
|
|
41
|
-
.optional()
|
|
42
|
-
.default([]),
|
|
43
|
-
profiles: zod_1.z
|
|
44
|
-
.array(zod_1.z.object({
|
|
45
|
-
id: zod_1.z.string().uuid().optional(),
|
|
46
|
-
name: zod_1.z.string().min(1),
|
|
47
|
-
provider: zod_1.z.object({ id: zod_1.z.string().optional(), name: zod_1.z.string().min(1) }),
|
|
48
|
-
options: zod_1.z.unknown().nullable().optional(),
|
|
49
|
-
instructions: zod_1.z.string().nullable().optional(),
|
|
50
|
-
temperature: zod_1.z.number().nullable().optional(),
|
|
51
|
-
maxTokens: zod_1.z.number().int().nullable().optional(),
|
|
52
|
-
}))
|
|
53
|
-
.optional()
|
|
54
|
-
.default([]),
|
|
55
|
-
agents: zod_1.z
|
|
56
|
-
.array(zod_1.z.object({
|
|
57
|
-
id: zod_1.z.string().uuid().optional(),
|
|
58
|
-
name: zod_1.z.string().min(1),
|
|
59
|
-
profileId: zod_1.z.string().uuid().optional(),
|
|
60
|
-
description: zod_1.z.string().nullable().optional(),
|
|
61
|
-
}))
|
|
62
|
-
.optional()
|
|
63
|
-
.default([]),
|
|
64
|
-
statuses: zod_1.z
|
|
65
|
-
.array(zod_1.z.object({
|
|
66
|
-
id: zod_1.z.string().uuid().optional(),
|
|
67
|
-
label: zod_1.z.string().min(1),
|
|
68
|
-
color: zod_1.z.string().min(1),
|
|
69
|
-
position: zod_1.z.number().int(),
|
|
70
|
-
mcpHidden: zod_1.z.boolean().optional().default(false),
|
|
71
|
-
}))
|
|
72
|
-
.optional()
|
|
73
|
-
.default([]),
|
|
74
|
-
initialPrompt: zod_1.z
|
|
75
|
-
.object({ promptId: zod_1.z.string().uuid().optional(), title: zod_1.z.string().optional() })
|
|
76
|
-
.nullable()
|
|
77
|
-
.optional(),
|
|
78
|
-
projectSettings: zod_1.z
|
|
79
|
-
.object({
|
|
80
|
-
initialPromptTitle: zod_1.z.string().optional(),
|
|
81
|
-
autoCleanStatusLabels: zod_1.z.array(zod_1.z.string()).optional(),
|
|
82
|
-
epicAssignedTemplate: zod_1.z.string().optional(),
|
|
83
|
-
})
|
|
84
|
-
.optional(),
|
|
85
|
-
watchers: zod_1.z
|
|
86
|
-
.array(zod_1.z.object({
|
|
87
|
-
id: zod_1.z.string().uuid().optional(),
|
|
88
|
-
name: zod_1.z.string().min(1),
|
|
89
|
-
description: zod_1.z.string().nullable().optional(),
|
|
90
|
-
enabled: zod_1.z.boolean(),
|
|
91
|
-
scope: zod_1.z.enum(['all', 'agent', 'profile', 'provider']),
|
|
92
|
-
scopeFilterName: zod_1.z.string().nullable().optional(),
|
|
93
|
-
pollIntervalMs: zod_1.z.number().int(),
|
|
94
|
-
viewportLines: zod_1.z.number().int(),
|
|
95
|
-
condition: zod_1.z.object({
|
|
96
|
-
type: zod_1.z.enum(['contains', 'regex', 'not_contains']),
|
|
97
|
-
pattern: zod_1.z.string(),
|
|
98
|
-
flags: zod_1.z.string().optional(),
|
|
99
|
-
}),
|
|
100
|
-
cooldownMs: zod_1.z.number().int(),
|
|
101
|
-
cooldownMode: zod_1.z.enum(['time', 'until_clear']),
|
|
102
|
-
eventName: zod_1.z.string(),
|
|
103
|
-
}))
|
|
104
|
-
.optional()
|
|
105
|
-
.default([]),
|
|
106
|
-
subscribers: zod_1.z
|
|
107
|
-
.array(zod_1.z.object({
|
|
108
|
-
id: zod_1.z.string().uuid().optional(),
|
|
109
|
-
name: zod_1.z.string().min(1),
|
|
110
|
-
description: zod_1.z.string().nullable().optional(),
|
|
111
|
-
enabled: zod_1.z.boolean(),
|
|
112
|
-
eventName: zod_1.z.string(),
|
|
113
|
-
eventFilter: zod_1.z
|
|
114
|
-
.object({
|
|
115
|
-
field: zod_1.z.string(),
|
|
116
|
-
operator: zod_1.z.enum(['equals', 'contains', 'regex']),
|
|
117
|
-
value: zod_1.z.string(),
|
|
118
|
-
})
|
|
119
|
-
.nullable()
|
|
120
|
-
.optional(),
|
|
121
|
-
actionType: zod_1.z.string(),
|
|
122
|
-
actionInputs: zod_1.z.record(zod_1.z.string(), zod_1.z.object({
|
|
123
|
-
source: zod_1.z.enum(['event_field', 'custom']),
|
|
124
|
-
eventField: zod_1.z.string().optional(),
|
|
125
|
-
customValue: zod_1.z.string().optional(),
|
|
126
|
-
})),
|
|
127
|
-
delayMs: zod_1.z.number().int(),
|
|
128
|
-
cooldownMs: zod_1.z.number().int(),
|
|
129
|
-
retryOnError: zod_1.z.boolean(),
|
|
130
|
-
groupName: zod_1.z.string().nullable().optional(),
|
|
131
|
-
position: zod_1.z.number().int().optional().default(0),
|
|
132
|
-
priority: zod_1.z.number().int().optional().default(0),
|
|
133
|
-
}))
|
|
134
|
-
.optional()
|
|
135
|
-
.default([]),
|
|
136
|
-
})
|
|
137
|
-
.strict();
|
|
138
30
|
let ProjectsService = class ProjectsService {
|
|
139
|
-
constructor(storage, sessions, settings, watchersService, watcherRunner) {
|
|
31
|
+
constructor(storage, sessions, settings, watchersService, watcherRunner, unifiedTemplateService) {
|
|
140
32
|
this.storage = storage;
|
|
141
33
|
this.sessions = sessions;
|
|
142
34
|
this.settings = settings;
|
|
143
35
|
this.watchersService = watchersService;
|
|
144
36
|
this.watcherRunner = watcherRunner;
|
|
37
|
+
this.unifiedTemplateService = unifiedTemplateService;
|
|
145
38
|
}
|
|
146
39
|
findTemplatesDirectory() {
|
|
147
40
|
const env = (0, env_config_1.getEnvConfig)();
|
|
@@ -169,8 +62,7 @@ let ProjectsService = class ProjectsService {
|
|
|
169
62
|
const templatesDir = this.findTemplatesDirectory();
|
|
170
63
|
if (!templatesDir) {
|
|
171
64
|
logger.error('Templates directory not found');
|
|
172
|
-
throw new
|
|
173
|
-
message: 'Templates directory not found',
|
|
65
|
+
throw new error_types_1.StorageError('Templates directory not found', {
|
|
174
66
|
hint: 'Templates directory is not available in this deployment',
|
|
175
67
|
});
|
|
176
68
|
}
|
|
@@ -186,8 +78,7 @@ let ProjectsService = class ProjectsService {
|
|
|
186
78
|
}
|
|
187
79
|
catch (error) {
|
|
188
80
|
logger.error({ error, templatesDir }, 'Failed to read templates directory');
|
|
189
|
-
throw new
|
|
190
|
-
message: 'Failed to read templates directory',
|
|
81
|
+
throw new error_types_1.StorageError('Failed to read templates directory', {
|
|
191
82
|
hint: 'Error accessing templates',
|
|
192
83
|
});
|
|
193
84
|
}
|
|
@@ -196,8 +87,7 @@ let ProjectsService = class ProjectsService {
|
|
|
196
87
|
logger.info({ templateId }, 'getTemplateContent');
|
|
197
88
|
const templatesDir = this.findTemplatesDirectory();
|
|
198
89
|
if (!templatesDir) {
|
|
199
|
-
throw new
|
|
200
|
-
message: 'Templates directory not found',
|
|
90
|
+
throw new error_types_1.StorageError('Templates directory not found', {
|
|
201
91
|
hint: 'Templates directory is not available in this deployment',
|
|
202
92
|
});
|
|
203
93
|
}
|
|
@@ -220,68 +110,28 @@ let ProjectsService = class ProjectsService {
|
|
|
220
110
|
}
|
|
221
111
|
catch (error) {
|
|
222
112
|
logger.error({ error, templatePath }, 'Failed to read template file');
|
|
223
|
-
throw new
|
|
224
|
-
message: 'Failed to read template file',
|
|
113
|
+
throw new error_types_1.StorageError('Failed to read template file', {
|
|
225
114
|
hint: 'Template file exists but cannot be read or parsed',
|
|
226
115
|
});
|
|
227
116
|
}
|
|
228
117
|
}
|
|
229
118
|
async createFromTemplate(input) {
|
|
230
119
|
logger.info({ input }, 'createFromTemplate');
|
|
231
|
-
const
|
|
232
|
-
if (!templatesDir) {
|
|
233
|
-
throw new common_1.InternalServerErrorException({
|
|
234
|
-
message: 'Templates directory not found',
|
|
235
|
-
hint: 'Templates directory is not available in this deployment',
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
const TEMPLATE_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
239
|
-
if (!TEMPLATE_ID_REGEX.test(input.templateId)) {
|
|
240
|
-
throw new error_types_1.ValidationError('Invalid template ID: must contain only alphanumeric characters, hyphens, and underscores', { templateId: input.templateId });
|
|
241
|
-
}
|
|
242
|
-
const resolvedTemplatesDir = (0, path_1.resolve)(templatesDir);
|
|
243
|
-
const templatePath = (0, path_1.resolve)(templatesDir, `${input.templateId}.json`);
|
|
244
|
-
if (!templatePath.startsWith(resolvedTemplatesDir + path_1.sep)) {
|
|
245
|
-
logger.warn({ templateId: input.templateId, templatePath, templatesDir: resolvedTemplatesDir }, 'Path traversal attempt detected');
|
|
246
|
-
throw new error_types_1.ValidationError('Invalid template ID: path traversal not allowed', {
|
|
247
|
-
templateId: input.templateId,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
if (!(0, fs_1.existsSync)(templatePath)) {
|
|
251
|
-
throw new error_types_1.NotFoundError('Template', input.templateId);
|
|
252
|
-
}
|
|
253
|
-
let templateContent;
|
|
254
|
-
try {
|
|
255
|
-
templateContent = (0, fs_1.readFileSync)(templatePath, 'utf-8');
|
|
256
|
-
}
|
|
257
|
-
catch (error) {
|
|
258
|
-
logger.error({ error, templatePath }, 'Failed to read template file');
|
|
259
|
-
throw new common_1.InternalServerErrorException({
|
|
260
|
-
message: 'Failed to read template file',
|
|
261
|
-
hint: 'Template file exists but cannot be read',
|
|
262
|
-
});
|
|
263
|
-
}
|
|
120
|
+
const templateResult = await this.unifiedTemplateService.getTemplate(input.slug, input.version ?? undefined);
|
|
264
121
|
let payload;
|
|
265
122
|
try {
|
|
266
|
-
payload = ExportSchema.parse(
|
|
123
|
+
payload = shared_1.ExportSchema.parse(templateResult.content);
|
|
267
124
|
}
|
|
268
125
|
catch (error) {
|
|
269
|
-
logger.error({ error,
|
|
270
|
-
throw new
|
|
271
|
-
message: 'Invalid template format',
|
|
126
|
+
logger.error({ error, slug: input.slug, version: input.version }, 'Invalid template format');
|
|
127
|
+
throw new error_types_1.ValidationError('Invalid template format', {
|
|
272
128
|
hint: 'Template file does not match expected export schema',
|
|
273
129
|
});
|
|
274
130
|
}
|
|
275
131
|
const providerNames = new Set((payload.profiles ?? []).map((p) => p.provider.name.trim().toLowerCase()));
|
|
276
|
-
const
|
|
277
|
-
const available = new Map();
|
|
278
|
-
for (const prov of providers.items) {
|
|
279
|
-
available.set(prov.name.trim().toLowerCase(), prov.id);
|
|
280
|
-
}
|
|
281
|
-
const missingProviders = Array.from(providerNames).filter((n) => !available.has(n));
|
|
132
|
+
const { available, missing: missingProviders } = await this.resolveProviders(providerNames);
|
|
282
133
|
if (missingProviders.length > 0) {
|
|
283
|
-
throw new
|
|
284
|
-
message: 'Import aborted: missing providers',
|
|
134
|
+
throw new error_types_1.ValidationError('Import aborted: missing providers', {
|
|
285
135
|
missingProviders,
|
|
286
136
|
hint: 'Install/configure providers by name before creating project from template.',
|
|
287
137
|
});
|
|
@@ -297,25 +147,13 @@ let ProjectsService = class ProjectsService {
|
|
|
297
147
|
profiles: payload.profiles.map((prof) => {
|
|
298
148
|
const providerId = available.get(prof.provider.name.trim().toLowerCase());
|
|
299
149
|
if (!providerId) {
|
|
300
|
-
throw new
|
|
301
|
-
}
|
|
302
|
-
let optionsStr = null;
|
|
303
|
-
if (typeof prof.options === 'string') {
|
|
304
|
-
optionsStr = prof.options;
|
|
305
|
-
}
|
|
306
|
-
else if (prof.options && typeof prof.options === 'object') {
|
|
307
|
-
try {
|
|
308
|
-
optionsStr = JSON.stringify(prof.options);
|
|
309
|
-
}
|
|
310
|
-
catch {
|
|
311
|
-
optionsStr = null;
|
|
312
|
-
}
|
|
150
|
+
throw new error_types_1.NotFoundError('Provider', prof.provider.name);
|
|
313
151
|
}
|
|
314
152
|
return {
|
|
315
153
|
id: prof.id,
|
|
316
154
|
name: prof.name,
|
|
317
155
|
providerId,
|
|
318
|
-
options:
|
|
156
|
+
options: this.normalizeProfileOptions(prof.options),
|
|
319
157
|
instructions: prof.instructions ?? null,
|
|
320
158
|
temperature: prof.temperature ?? null,
|
|
321
159
|
maxTokens: prof.maxTokens ?? null,
|
|
@@ -342,196 +180,63 @@ let ProjectsService = class ProjectsService {
|
|
|
342
180
|
rootPath: input.rootPath,
|
|
343
181
|
isTemplate: false,
|
|
344
182
|
}, templatePayload);
|
|
345
|
-
const agentNameToNewId =
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
if (autoCleanStatusIds.length > 0) {
|
|
403
|
-
const currentSettings = this.settings.getSettings();
|
|
404
|
-
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
405
|
-
await this.settings.updateSettings({
|
|
406
|
-
autoClean: {
|
|
407
|
-
statusIds: { ...existingAutoClean, [result.project.id]: autoCleanStatusIds },
|
|
408
|
-
},
|
|
409
|
-
});
|
|
410
|
-
logger.info({ projectId: result.project.id, autoCleanStatusIds }, 'Applied autoClean statuses from projectSettings');
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
else {
|
|
414
|
-
const archiveTemplateStatus = templatePayload.statuses.find((s) => s.label.toLowerCase() === 'archive');
|
|
415
|
-
if (archiveTemplateStatus?.id) {
|
|
416
|
-
const archiveNewId = result.mappings.statusIdMap[archiveTemplateStatus.id];
|
|
417
|
-
if (archiveNewId) {
|
|
418
|
-
const currentSettings = this.settings.getSettings();
|
|
419
|
-
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
420
|
-
await this.settings.updateSettings({
|
|
421
|
-
autoClean: {
|
|
422
|
-
statusIds: { ...existingAutoClean, [result.project.id]: [archiveNewId] },
|
|
423
|
-
},
|
|
424
|
-
});
|
|
425
|
-
logger.info({ projectId: result.project.id, archiveStatusId: archiveNewId }, 'Auto-configured Archive status for auto-clean (fallback)');
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (ps.epicAssignedTemplate) {
|
|
430
|
-
await this.settings.updateSettings({
|
|
431
|
-
events: {
|
|
432
|
-
epicAssigned: { template: ps.epicAssignedTemplate },
|
|
433
|
-
},
|
|
434
|
-
});
|
|
435
|
-
logger.info({ projectId: result.project.id }, 'Applied epicAssigned template from projectSettings');
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
else {
|
|
439
|
-
const archiveTemplateStatus = templatePayload.statuses.find((s) => s.label.toLowerCase() === 'archive');
|
|
440
|
-
if (archiveTemplateStatus?.id) {
|
|
441
|
-
const archiveNewId = result.mappings.statusIdMap[archiveTemplateStatus.id];
|
|
442
|
-
if (archiveNewId) {
|
|
443
|
-
const currentSettings = this.settings.getSettings();
|
|
444
|
-
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
445
|
-
await this.settings.updateSettings({
|
|
446
|
-
autoClean: {
|
|
447
|
-
statusIds: { ...existingAutoClean, [result.project.id]: [archiveNewId] },
|
|
448
|
-
},
|
|
449
|
-
});
|
|
450
|
-
logger.info({ projectId: result.project.id, archiveStatusId: archiveNewId }, 'Auto-configured Archive status for auto-clean');
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
let watchersCreated = 0;
|
|
455
|
-
for (const w of payload.watchers) {
|
|
456
|
-
let scopeFilterId = null;
|
|
457
|
-
if (w.scopeFilterName && w.scope !== 'all') {
|
|
458
|
-
const scopeFilterNameLower = w.scopeFilterName.trim().toLowerCase();
|
|
459
|
-
switch (w.scope) {
|
|
460
|
-
case 'agent': {
|
|
461
|
-
scopeFilterId = agentNameToNewId.get(scopeFilterNameLower) ?? null;
|
|
462
|
-
break;
|
|
463
|
-
}
|
|
464
|
-
case 'profile': {
|
|
465
|
-
scopeFilterId = profileNameToNewId.get(scopeFilterNameLower) ?? null;
|
|
466
|
-
break;
|
|
467
|
-
}
|
|
468
|
-
case 'provider': {
|
|
469
|
-
const provider = providers.items.find((p) => p.name.trim().toLowerCase() === scopeFilterNameLower);
|
|
470
|
-
scopeFilterId = provider?.id ?? null;
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
if (!scopeFilterId) {
|
|
475
|
-
logger.warn({
|
|
476
|
-
projectId: result.project.id,
|
|
477
|
-
watcherName: w.name,
|
|
478
|
-
scope: w.scope,
|
|
479
|
-
scopeFilterName: w.scopeFilterName,
|
|
480
|
-
}, 'Could not resolve scope filter for template watcher, setting scope to "all"');
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
try {
|
|
484
|
-
const created = await this.storage.createWatcher({
|
|
485
|
-
projectId: result.project.id,
|
|
486
|
-
name: w.name,
|
|
487
|
-
description: w.description ?? null,
|
|
488
|
-
enabled: w.enabled,
|
|
489
|
-
scope: scopeFilterId ? w.scope : 'all',
|
|
490
|
-
scopeFilterId,
|
|
491
|
-
pollIntervalMs: w.pollIntervalMs,
|
|
492
|
-
viewportLines: w.viewportLines,
|
|
493
|
-
condition: w.condition,
|
|
494
|
-
cooldownMs: w.cooldownMs,
|
|
495
|
-
cooldownMode: w.cooldownMode,
|
|
496
|
-
eventName: w.eventName,
|
|
497
|
-
});
|
|
498
|
-
watchersCreated++;
|
|
499
|
-
if (created.enabled) {
|
|
500
|
-
await this.watcherRunner.startWatcher(created);
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
catch (error) {
|
|
504
|
-
if (error instanceof Error &&
|
|
505
|
-
error.message.includes('UNIQUE constraint failed') &&
|
|
506
|
-
error.message.includes('event_name')) {
|
|
507
|
-
throw new common_1.BadRequestException({
|
|
508
|
-
message: `Duplicate watcher eventName: "${w.eventName}"`,
|
|
509
|
-
hint: 'Each watcher must have a unique eventName within the project.',
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
throw error;
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
let subscribersCreated = 0;
|
|
516
|
-
for (const s of payload.subscribers) {
|
|
517
|
-
await this.storage.createSubscriber({
|
|
518
|
-
projectId: result.project.id,
|
|
519
|
-
name: s.name,
|
|
520
|
-
description: s.description ?? null,
|
|
521
|
-
enabled: s.enabled,
|
|
522
|
-
eventName: s.eventName,
|
|
523
|
-
eventFilter: s.eventFilter ?? null,
|
|
524
|
-
actionType: s.actionType,
|
|
525
|
-
actionInputs: s.actionInputs,
|
|
526
|
-
delayMs: s.delayMs,
|
|
527
|
-
cooldownMs: s.cooldownMs,
|
|
528
|
-
retryOnError: s.retryOnError,
|
|
529
|
-
groupName: s.groupName ?? null,
|
|
530
|
-
position: s.position ?? 0,
|
|
531
|
-
priority: s.priority ?? 0,
|
|
532
|
-
});
|
|
533
|
-
subscribersCreated++;
|
|
534
|
-
}
|
|
183
|
+
const { agentNameToId: agentNameToNewId, profileNameToId: profileNameToNewId } = this.buildNameToIdMaps(templatePayload, result.mappings);
|
|
184
|
+
let mergedInitialPromptTitle;
|
|
185
|
+
if (payload.initialPrompt?.title) {
|
|
186
|
+
mergedInitialPromptTitle = payload.initialPrompt.title;
|
|
187
|
+
}
|
|
188
|
+
else if (payload.initialPrompt?.promptId) {
|
|
189
|
+
const matchingPrompt = payload.prompts.find((p) => p.id === payload.initialPrompt.promptId);
|
|
190
|
+
if (matchingPrompt) {
|
|
191
|
+
mergedInitialPromptTitle = matchingPrompt.title;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
else if (payload.projectSettings?.initialPromptTitle) {
|
|
195
|
+
mergedInitialPromptTitle = payload.projectSettings.initialPromptTitle;
|
|
196
|
+
}
|
|
197
|
+
const mergedSettings = payload.projectSettings
|
|
198
|
+
? { ...payload.projectSettings, initialPromptTitle: mergedInitialPromptTitle }
|
|
199
|
+
: mergedInitialPromptTitle
|
|
200
|
+
? { initialPromptTitle: mergedInitialPromptTitle }
|
|
201
|
+
: undefined;
|
|
202
|
+
const promptTitleToId = new Map();
|
|
203
|
+
for (const p of payload.prompts) {
|
|
204
|
+
if (p.id && result.mappings.promptIdMap[p.id]) {
|
|
205
|
+
promptTitleToId.set(p.title.toLowerCase(), result.mappings.promptIdMap[p.id]);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const statusLabelToId = new Map();
|
|
209
|
+
for (const s of templatePayload.statuses) {
|
|
210
|
+
if (s.id && result.mappings.statusIdMap[s.id]) {
|
|
211
|
+
statusLabelToId.set(s.label.toLowerCase(), result.mappings.statusIdMap[s.id]);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const archiveTemplateStatus = templatePayload.statuses.find((s) => s.label.toLowerCase() === 'archive');
|
|
215
|
+
const archiveStatusId = archiveTemplateStatus?.id
|
|
216
|
+
? (result.mappings.statusIdMap[archiveTemplateStatus.id] ?? null)
|
|
217
|
+
: null;
|
|
218
|
+
const settingsResult = await this.applyProjectSettings(result.project.id, mergedSettings, { promptTitleToId, statusLabelToId }, archiveStatusId);
|
|
219
|
+
const initialPromptSet = settingsResult.initialPromptSet;
|
|
220
|
+
const { created: watchersCreated } = await this.createWatchersFromPayload(result.project.id, payload.watchers, {
|
|
221
|
+
agentNameToId: agentNameToNewId,
|
|
222
|
+
profileNameToId: profileNameToNewId,
|
|
223
|
+
providerNameToId: available,
|
|
224
|
+
});
|
|
225
|
+
const { created: subscribersCreated } = await this.createSubscribersFromPayload(result.project.id, payload.subscribers);
|
|
226
|
+
const registryConfig = this.settings.getRegistryConfig();
|
|
227
|
+
await this.settings.setProjectTemplateMetadata(result.project.id, {
|
|
228
|
+
templateSlug: input.slug,
|
|
229
|
+
source: templateResult.source,
|
|
230
|
+
installedVersion: templateResult.version,
|
|
231
|
+
registryUrl: templateResult.source === 'registry' ? registryConfig.url : null,
|
|
232
|
+
installedAt: new Date().toISOString(),
|
|
233
|
+
});
|
|
234
|
+
logger.info({
|
|
235
|
+
projectId: result.project.id,
|
|
236
|
+
slug: input.slug,
|
|
237
|
+
source: templateResult.source,
|
|
238
|
+
version: templateResult.version,
|
|
239
|
+
}, 'Template metadata set for project');
|
|
535
240
|
return {
|
|
536
241
|
success: true,
|
|
537
242
|
project: result.project,
|
|
@@ -545,9 +250,11 @@ let ProjectsService = class ProjectsService {
|
|
|
545
250
|
message: 'Project created from template successfully.',
|
|
546
251
|
};
|
|
547
252
|
}
|
|
548
|
-
async exportProject(projectId) {
|
|
253
|
+
async exportProject(projectId, opts) {
|
|
549
254
|
logger.info({ projectId }, 'exportProject');
|
|
550
|
-
const
|
|
255
|
+
const { manifestOverrides } = opts ?? {};
|
|
256
|
+
const [project, promptsRes, profilesRes, agentsRes, statusesRes, initialPrompt, settings, watchersRes, subscribersRes,] = await Promise.all([
|
|
257
|
+
this.storage.getProject(projectId),
|
|
551
258
|
this.storage.listPrompts({ projectId, limit: 1000, offset: 0 }),
|
|
552
259
|
this.storage.listAgentProfiles({ projectId, limit: 1000, offset: 0 }),
|
|
553
260
|
this.storage.listAgents(projectId, { limit: 1000, offset: 0 }),
|
|
@@ -654,6 +361,10 @@ let ProjectsService = class ProjectsService {
|
|
|
654
361
|
if (epicAssignedTemplate) {
|
|
655
362
|
projectSettings.epicAssignedTemplate = epicAssignedTemplate;
|
|
656
363
|
}
|
|
364
|
+
const poolSettings = settings.messagePool?.projects?.[projectId];
|
|
365
|
+
if (poolSettings && Object.keys(poolSettings).length > 0) {
|
|
366
|
+
projectSettings.messagePoolSettings = poolSettings;
|
|
367
|
+
}
|
|
657
368
|
const watchers = await Promise.all(watchersRes.map(async (w) => {
|
|
658
369
|
let scopeFilterName = null;
|
|
659
370
|
if (w.scopeFilterId) {
|
|
@@ -711,7 +422,17 @@ let ProjectsService = class ProjectsService {
|
|
|
711
422
|
position: s.position,
|
|
712
423
|
priority: s.priority,
|
|
713
424
|
}));
|
|
425
|
+
const existingMetadata = this.settings.getProjectTemplateMetadata(projectId);
|
|
426
|
+
const manifest = {
|
|
427
|
+
slug: existingMetadata?.templateSlug || this.slugify(project.name),
|
|
428
|
+
name: project.name,
|
|
429
|
+
description: project.description || null,
|
|
430
|
+
version: existingMetadata?.installedVersion || '1.0.0',
|
|
431
|
+
...manifestOverrides,
|
|
432
|
+
publishedAt: new Date().toISOString(),
|
|
433
|
+
};
|
|
714
434
|
const exportPayload = {
|
|
435
|
+
_manifest: manifest,
|
|
715
436
|
version: 1,
|
|
716
437
|
exportedAt: new Date().toISOString(),
|
|
717
438
|
prompts,
|
|
@@ -730,14 +451,9 @@ let ProjectsService = class ProjectsService {
|
|
|
730
451
|
async importProject(input) {
|
|
731
452
|
logger.info({ projectId: input.projectId, dryRun: input.dryRun }, 'importProject');
|
|
732
453
|
const isDryRun = input.dryRun ?? false;
|
|
733
|
-
const payload = ExportSchema.parse(input.payload ?? {});
|
|
454
|
+
const payload = shared_1.ExportSchema.parse(input.payload ?? {});
|
|
734
455
|
const providerNames = new Set((payload.profiles ?? []).map((p) => p.provider.name.trim().toLowerCase()));
|
|
735
|
-
const
|
|
736
|
-
const available = new Map();
|
|
737
|
-
for (const prov of providers.items) {
|
|
738
|
-
available.set(prov.name.trim().toLowerCase(), prov.id);
|
|
739
|
-
}
|
|
740
|
-
const missingProviders = Array.from(providerNames).filter((n) => !available.has(n));
|
|
456
|
+
const { available, missing: missingProviders } = await this.resolveProviders(providerNames);
|
|
741
457
|
const [existingPrompts, existingProfiles, existingAgents, existingStatuses, existingWatchers, existingSubscribers,] = await Promise.all([
|
|
742
458
|
this.storage.listPrompts({ projectId: input.projectId, limit: 10000, offset: 0 }),
|
|
743
459
|
this.storage.listAgentProfiles({ projectId: input.projectId, limit: 10000, offset: 0 }),
|
|
@@ -792,16 +508,14 @@ let ProjectsService = class ProjectsService {
|
|
|
792
508
|
};
|
|
793
509
|
}
|
|
794
510
|
if (missingProviders.length > 0) {
|
|
795
|
-
throw new
|
|
796
|
-
message: 'Import aborted: missing providers',
|
|
511
|
+
throw new error_types_1.ValidationError('Import aborted: missing providers', {
|
|
797
512
|
missingProviders,
|
|
798
513
|
hint: 'Install/configure providers by name before importing profiles.',
|
|
799
514
|
});
|
|
800
515
|
}
|
|
801
516
|
const activeSessions = this.sessions.getActiveSessionsForProject(input.projectId);
|
|
802
517
|
if (activeSessions.length > 0) {
|
|
803
|
-
throw new
|
|
804
|
-
message: 'Import aborted: active agent sessions detected',
|
|
518
|
+
throw new error_types_1.ConflictError('Import aborted: active agent sessions detected', {
|
|
805
519
|
activeSessions: activeSessions.map((s) => ({ id: s.id, agentId: s.agentId })),
|
|
806
520
|
hint: 'Terminate all running sessions for this project before importing.',
|
|
807
521
|
});
|
|
@@ -901,45 +615,29 @@ let ProjectsService = class ProjectsService {
|
|
|
901
615
|
promptIdMap[p.id] = created.id;
|
|
902
616
|
createdPrompts.push({ id: created.id, title: created.title });
|
|
903
617
|
}
|
|
904
|
-
const profileNameToNewId = new Map();
|
|
905
618
|
for (const prof of payload.profiles) {
|
|
906
619
|
const providerId = available.get(prof.provider.name.trim().toLowerCase());
|
|
907
620
|
if (!providerId) {
|
|
908
|
-
throw new
|
|
909
|
-
}
|
|
910
|
-
let optionsStr = null;
|
|
911
|
-
if (typeof prof.options === 'string') {
|
|
912
|
-
optionsStr = prof.options;
|
|
913
|
-
}
|
|
914
|
-
else if (prof.options && typeof prof.options === 'object') {
|
|
915
|
-
try {
|
|
916
|
-
optionsStr = JSON.stringify(prof.options);
|
|
917
|
-
}
|
|
918
|
-
catch {
|
|
919
|
-
optionsStr = null;
|
|
920
|
-
}
|
|
621
|
+
throw new error_types_1.NotFoundError('Provider', prof.provider.name);
|
|
921
622
|
}
|
|
922
623
|
const created = await this.storage.createAgentProfile({
|
|
923
624
|
projectId: input.projectId,
|
|
924
625
|
name: prof.name,
|
|
925
626
|
providerId,
|
|
926
|
-
options:
|
|
627
|
+
options: this.normalizeProfileOptions(prof.options),
|
|
927
628
|
systemPrompt: null,
|
|
928
629
|
instructions: prof.instructions ?? null,
|
|
929
630
|
temperature: prof.temperature ?? null,
|
|
930
631
|
maxTokens: prof.maxTokens ?? null,
|
|
931
632
|
});
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
profileNameToNewId.set(prof.name.trim().toLowerCase(), created.id);
|
|
633
|
+
const profKey = prof.id || `name:${prof.name.trim().toLowerCase()}`;
|
|
634
|
+
profileIdMap[profKey] = created.id;
|
|
935
635
|
}
|
|
936
|
-
const agentNameToNewId = new Map();
|
|
937
636
|
for (const a of payload.agents) {
|
|
938
637
|
const oldProfileId = a.profileId ?? '';
|
|
939
638
|
const newProfileId = oldProfileId && profileIdMap[oldProfileId] ? profileIdMap[oldProfileId] : undefined;
|
|
940
639
|
if (!newProfileId) {
|
|
941
|
-
throw new
|
|
942
|
-
message: `Profile mapping missing for agent ${a.name}`,
|
|
640
|
+
throw new error_types_1.ValidationError(`Profile mapping missing for agent ${a.name}`, {
|
|
943
641
|
profileId: oldProfileId || null,
|
|
944
642
|
});
|
|
945
643
|
}
|
|
@@ -949,75 +647,26 @@ let ProjectsService = class ProjectsService {
|
|
|
949
647
|
profileId: newProfileId,
|
|
950
648
|
description: a.description ?? null,
|
|
951
649
|
});
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
agentNameToNewId.set(a.name.trim().toLowerCase(), created.id);
|
|
955
|
-
}
|
|
956
|
-
const watcherIdMap = {};
|
|
957
|
-
for (const w of payload.watchers) {
|
|
958
|
-
let scopeFilterId = null;
|
|
959
|
-
if (w.scopeFilterName && w.scope !== 'all') {
|
|
960
|
-
const scopeFilterNameLower = w.scopeFilterName.trim().toLowerCase();
|
|
961
|
-
switch (w.scope) {
|
|
962
|
-
case 'agent': {
|
|
963
|
-
scopeFilterId = agentNameToNewId.get(scopeFilterNameLower) ?? null;
|
|
964
|
-
break;
|
|
965
|
-
}
|
|
966
|
-
case 'profile': {
|
|
967
|
-
scopeFilterId = profileNameToNewId.get(scopeFilterNameLower) ?? null;
|
|
968
|
-
break;
|
|
969
|
-
}
|
|
970
|
-
case 'provider': {
|
|
971
|
-
const provider = providers.items.find((p) => p.name.trim().toLowerCase() === scopeFilterNameLower);
|
|
972
|
-
scopeFilterId = provider?.id ?? null;
|
|
973
|
-
break;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
if (!scopeFilterId) {
|
|
977
|
-
logger.warn({ watcherName: w.name, scope: w.scope, scopeFilterName: w.scopeFilterName }, 'Could not resolve scope filter, setting scope to "all"');
|
|
978
|
-
}
|
|
979
|
-
}
|
|
980
|
-
const created = await this.storage.createWatcher({
|
|
981
|
-
projectId: input.projectId,
|
|
982
|
-
name: w.name,
|
|
983
|
-
description: w.description ?? null,
|
|
984
|
-
enabled: w.enabled,
|
|
985
|
-
scope: scopeFilterId ? w.scope : 'all',
|
|
986
|
-
scopeFilterId,
|
|
987
|
-
pollIntervalMs: w.pollIntervalMs,
|
|
988
|
-
viewportLines: w.viewportLines,
|
|
989
|
-
condition: w.condition,
|
|
990
|
-
cooldownMs: w.cooldownMs,
|
|
991
|
-
cooldownMode: w.cooldownMode,
|
|
992
|
-
eventName: w.eventName,
|
|
993
|
-
});
|
|
994
|
-
if (w.id)
|
|
995
|
-
watcherIdMap[w.id] = created.id;
|
|
996
|
-
if (created.enabled) {
|
|
997
|
-
await this.watcherRunner.startWatcher(created);
|
|
998
|
-
}
|
|
999
|
-
}
|
|
1000
|
-
const subscriberIdMap = {};
|
|
1001
|
-
for (const s of payload.subscribers) {
|
|
1002
|
-
const created = await this.storage.createSubscriber({
|
|
1003
|
-
projectId: input.projectId,
|
|
1004
|
-
name: s.name,
|
|
1005
|
-
description: s.description ?? null,
|
|
1006
|
-
enabled: s.enabled,
|
|
1007
|
-
eventName: s.eventName,
|
|
1008
|
-
eventFilter: s.eventFilter ?? null,
|
|
1009
|
-
actionType: s.actionType,
|
|
1010
|
-
actionInputs: s.actionInputs,
|
|
1011
|
-
delayMs: s.delayMs,
|
|
1012
|
-
cooldownMs: s.cooldownMs,
|
|
1013
|
-
retryOnError: s.retryOnError,
|
|
1014
|
-
groupName: s.groupName ?? null,
|
|
1015
|
-
position: s.position ?? 0,
|
|
1016
|
-
priority: s.priority ?? 0,
|
|
1017
|
-
});
|
|
1018
|
-
if (s.id)
|
|
1019
|
-
subscriberIdMap[s.id] = created.id;
|
|
650
|
+
const agentKey = a.id || `name:${a.name.trim().toLowerCase()}`;
|
|
651
|
+
agentIdMap[agentKey] = created.id;
|
|
1020
652
|
}
|
|
653
|
+
const augmentedPayload = {
|
|
654
|
+
agents: payload.agents.map((a) => ({
|
|
655
|
+
...a,
|
|
656
|
+
id: a.id || `name:${a.name.trim().toLowerCase()}`,
|
|
657
|
+
})),
|
|
658
|
+
profiles: payload.profiles.map((p) => ({
|
|
659
|
+
...p,
|
|
660
|
+
id: p.id || `name:${p.name.trim().toLowerCase()}`,
|
|
661
|
+
})),
|
|
662
|
+
};
|
|
663
|
+
const { agentNameToId: agentNameToNewId, profileNameToId: profileNameToNewId } = this.buildNameToIdMaps(augmentedPayload, { agentIdMap, profileIdMap });
|
|
664
|
+
const { watcherIdMap } = await this.createWatchersFromPayload(input.projectId, payload.watchers, {
|
|
665
|
+
agentNameToId: agentNameToNewId,
|
|
666
|
+
profileNameToId: profileNameToNewId,
|
|
667
|
+
providerNameToId: available,
|
|
668
|
+
});
|
|
669
|
+
const { subscriberIdMap } = await this.createSubscribersFromPayload(input.projectId, payload.subscribers);
|
|
1021
670
|
logger.info({
|
|
1022
671
|
watchersCreated: payload.watchers.length,
|
|
1023
672
|
subscribersCreated: payload.subscribers.length,
|
|
@@ -1049,62 +698,31 @@ let ProjectsService = class ProjectsService {
|
|
|
1049
698
|
}
|
|
1050
699
|
}
|
|
1051
700
|
logger.info({ epicsRemapped, epicsCleared }, 'Epic agent references updated after import');
|
|
1052
|
-
let
|
|
1053
|
-
if (payload.initialPrompt) {
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
if (!targetPromptId && payload.initialPrompt.promptId) {
|
|
1061
|
-
const mapped = promptIdMap[payload.initialPrompt.promptId];
|
|
1062
|
-
if (mapped)
|
|
1063
|
-
targetPromptId = mapped;
|
|
1064
|
-
}
|
|
1065
|
-
await this.settings.updateSettings({
|
|
1066
|
-
projectId: input.projectId,
|
|
1067
|
-
initialSessionPromptId: targetPromptId ?? null,
|
|
1068
|
-
});
|
|
1069
|
-
initialPromptSet = Boolean(targetPromptId);
|
|
1070
|
-
}
|
|
1071
|
-
if (payload.projectSettings) {
|
|
1072
|
-
const ps = payload.projectSettings;
|
|
1073
|
-
if (ps.initialPromptTitle) {
|
|
1074
|
-
const matchByTitle = createdPrompts.find((cp) => cp.title.toLowerCase() === ps.initialPromptTitle.toLowerCase());
|
|
1075
|
-
if (matchByTitle) {
|
|
1076
|
-
await this.settings.updateSettings({
|
|
1077
|
-
projectId: input.projectId,
|
|
1078
|
-
initialSessionPromptId: matchByTitle.id,
|
|
1079
|
-
});
|
|
1080
|
-
initialPromptSet = true;
|
|
1081
|
-
logger.info({ projectId: input.projectId, promptTitle: ps.initialPromptTitle }, 'Applied initial prompt from projectSettings');
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
if (ps.autoCleanStatusLabels && ps.autoCleanStatusLabels.length > 0) {
|
|
1085
|
-
const autoCleanStatusIds = ps.autoCleanStatusLabels
|
|
1086
|
-
.map((label) => templateLabelToStatusId.get(label.toLowerCase()))
|
|
1087
|
-
.filter((id) => !!id);
|
|
1088
|
-
if (autoCleanStatusIds.length > 0) {
|
|
1089
|
-
const currentSettings = this.settings.getSettings();
|
|
1090
|
-
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
1091
|
-
await this.settings.updateSettings({
|
|
1092
|
-
autoClean: {
|
|
1093
|
-
statusIds: { ...existingAutoClean, [input.projectId]: autoCleanStatusIds },
|
|
1094
|
-
},
|
|
1095
|
-
});
|
|
1096
|
-
logger.info({ projectId: input.projectId, autoCleanStatusIds }, 'Applied autoClean statuses from projectSettings');
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
if (ps.epicAssignedTemplate) {
|
|
1100
|
-
await this.settings.updateSettings({
|
|
1101
|
-
events: {
|
|
1102
|
-
epicAssigned: { template: ps.epicAssignedTemplate },
|
|
1103
|
-
},
|
|
1104
|
-
});
|
|
1105
|
-
logger.info({ projectId: input.projectId }, 'Applied epicAssigned template from projectSettings');
|
|
701
|
+
let mergedInitialPromptTitle;
|
|
702
|
+
if (payload.initialPrompt?.title) {
|
|
703
|
+
mergedInitialPromptTitle = payload.initialPrompt.title;
|
|
704
|
+
}
|
|
705
|
+
else if (payload.initialPrompt?.promptId) {
|
|
706
|
+
const matchingPrompt = payload.prompts.find((p) => p.id === payload.initialPrompt.promptId);
|
|
707
|
+
if (matchingPrompt) {
|
|
708
|
+
mergedInitialPromptTitle = matchingPrompt.title;
|
|
1106
709
|
}
|
|
1107
710
|
}
|
|
711
|
+
else if (payload.projectSettings?.initialPromptTitle) {
|
|
712
|
+
mergedInitialPromptTitle = payload.projectSettings.initialPromptTitle;
|
|
713
|
+
}
|
|
714
|
+
const mergedSettings = payload.projectSettings
|
|
715
|
+
? { ...payload.projectSettings, initialPromptTitle: mergedInitialPromptTitle }
|
|
716
|
+
: mergedInitialPromptTitle
|
|
717
|
+
? { initialPromptTitle: mergedInitialPromptTitle }
|
|
718
|
+
: undefined;
|
|
719
|
+
const promptTitleToId = new Map();
|
|
720
|
+
for (const cp of createdPrompts) {
|
|
721
|
+
promptTitleToId.set(cp.title.toLowerCase(), cp.id);
|
|
722
|
+
}
|
|
723
|
+
const archiveStatusId = templateLabelToStatusId.get('archive') ?? null;
|
|
724
|
+
const settingsResult = await this.applyProjectSettings(input.projectId, mergedSettings, { promptTitleToId, statusLabelToId: templateLabelToStatusId }, archiveStatusId);
|
|
725
|
+
const initialPromptSet = settingsResult.initialPromptSet;
|
|
1108
726
|
return {
|
|
1109
727
|
success: true,
|
|
1110
728
|
mode: 'replace',
|
|
@@ -1148,8 +766,210 @@ let ProjectsService = class ProjectsService {
|
|
|
1148
766
|
catch (error) {
|
|
1149
767
|
logger.error({ error, projectId: input.projectId }, 'Import failed');
|
|
1150
768
|
const message = this.getImportErrorMessage(error);
|
|
1151
|
-
throw new
|
|
769
|
+
throw new error_types_1.StorageError(message);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
async resolveProviders(providerNames) {
|
|
773
|
+
const providers = await this.storage.listProviders();
|
|
774
|
+
const available = new Map();
|
|
775
|
+
for (const prov of providers.items) {
|
|
776
|
+
available.set(prov.name.trim().toLowerCase(), prov.id);
|
|
1152
777
|
}
|
|
778
|
+
const missing = Array.from(providerNames).filter((n) => !available.has(n));
|
|
779
|
+
return { available, missing };
|
|
780
|
+
}
|
|
781
|
+
buildNameToIdMaps(payload, mappings) {
|
|
782
|
+
const agentNameToId = new Map();
|
|
783
|
+
for (const a of payload.agents) {
|
|
784
|
+
if (a.id && mappings.agentIdMap[a.id]) {
|
|
785
|
+
const nameLower = a.name.trim().toLowerCase();
|
|
786
|
+
if (agentNameToId.has(nameLower)) {
|
|
787
|
+
logger.warn({
|
|
788
|
+
name: a.name,
|
|
789
|
+
existingId: agentNameToId.get(nameLower),
|
|
790
|
+
newId: mappings.agentIdMap[a.id],
|
|
791
|
+
}, 'Duplicate agent name detected, using last occurrence');
|
|
792
|
+
}
|
|
793
|
+
agentNameToId.set(nameLower, mappings.agentIdMap[a.id]);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
const profileNameToId = new Map();
|
|
797
|
+
for (const prof of payload.profiles) {
|
|
798
|
+
if (prof.id && mappings.profileIdMap[prof.id]) {
|
|
799
|
+
const nameLower = prof.name.trim().toLowerCase();
|
|
800
|
+
if (profileNameToId.has(nameLower)) {
|
|
801
|
+
logger.warn({
|
|
802
|
+
name: prof.name,
|
|
803
|
+
existingId: profileNameToId.get(nameLower),
|
|
804
|
+
newId: mappings.profileIdMap[prof.id],
|
|
805
|
+
}, 'Duplicate profile name detected, using last occurrence');
|
|
806
|
+
}
|
|
807
|
+
profileNameToId.set(nameLower, mappings.profileIdMap[prof.id]);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return { agentNameToId, profileNameToId };
|
|
811
|
+
}
|
|
812
|
+
async createWatchersFromPayload(projectId, watchers, maps) {
|
|
813
|
+
const watcherIdMap = {};
|
|
814
|
+
let created = 0;
|
|
815
|
+
for (const w of watchers) {
|
|
816
|
+
let scopeFilterId = null;
|
|
817
|
+
if (w.scopeFilterName && w.scope !== 'all') {
|
|
818
|
+
const scopeFilterNameLower = w.scopeFilterName.trim().toLowerCase();
|
|
819
|
+
switch (w.scope) {
|
|
820
|
+
case 'agent': {
|
|
821
|
+
scopeFilterId = maps.agentNameToId.get(scopeFilterNameLower) ?? null;
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
case 'profile': {
|
|
825
|
+
scopeFilterId = maps.profileNameToId.get(scopeFilterNameLower) ?? null;
|
|
826
|
+
break;
|
|
827
|
+
}
|
|
828
|
+
case 'provider': {
|
|
829
|
+
scopeFilterId = maps.providerNameToId.get(scopeFilterNameLower) ?? null;
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (!scopeFilterId) {
|
|
834
|
+
logger.warn({ projectId, watcherName: w.name, scope: w.scope, scopeFilterName: w.scopeFilterName }, 'Could not resolve scope filter, setting scope to "all"');
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
try {
|
|
838
|
+
const createdWatcher = await this.watchersService.createWatcher({
|
|
839
|
+
projectId,
|
|
840
|
+
name: w.name,
|
|
841
|
+
description: w.description ?? null,
|
|
842
|
+
enabled: w.enabled,
|
|
843
|
+
scope: scopeFilterId ? w.scope : 'all',
|
|
844
|
+
scopeFilterId,
|
|
845
|
+
pollIntervalMs: w.pollIntervalMs,
|
|
846
|
+
viewportLines: w.viewportLines,
|
|
847
|
+
condition: w.condition,
|
|
848
|
+
cooldownMs: w.cooldownMs,
|
|
849
|
+
cooldownMode: w.cooldownMode,
|
|
850
|
+
eventName: w.eventName,
|
|
851
|
+
});
|
|
852
|
+
if (w.id)
|
|
853
|
+
watcherIdMap[w.id] = createdWatcher.id;
|
|
854
|
+
created++;
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
if (error instanceof Error &&
|
|
858
|
+
error.message.includes('UNIQUE constraint failed') &&
|
|
859
|
+
error.message.includes('event_name')) {
|
|
860
|
+
throw new error_types_1.ConflictError(`Duplicate watcher eventName: "${w.eventName}"`, {
|
|
861
|
+
hint: 'Each watcher must have a unique eventName within the project.',
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
throw error;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
return { created, watcherIdMap };
|
|
868
|
+
}
|
|
869
|
+
async createSubscribersFromPayload(projectId, subscribers) {
|
|
870
|
+
const subscriberIdMap = {};
|
|
871
|
+
let created = 0;
|
|
872
|
+
for (const s of subscribers) {
|
|
873
|
+
const createdSubscriber = await this.storage.createSubscriber({
|
|
874
|
+
projectId,
|
|
875
|
+
name: s.name,
|
|
876
|
+
description: s.description ?? null,
|
|
877
|
+
enabled: s.enabled,
|
|
878
|
+
eventName: s.eventName,
|
|
879
|
+
eventFilter: s.eventFilter ?? null,
|
|
880
|
+
actionType: s.actionType,
|
|
881
|
+
actionInputs: s.actionInputs,
|
|
882
|
+
delayMs: s.delayMs,
|
|
883
|
+
cooldownMs: s.cooldownMs,
|
|
884
|
+
retryOnError: s.retryOnError,
|
|
885
|
+
groupName: s.groupName ?? null,
|
|
886
|
+
position: s.position ?? 0,
|
|
887
|
+
priority: s.priority ?? 0,
|
|
888
|
+
});
|
|
889
|
+
if (s.id)
|
|
890
|
+
subscriberIdMap[s.id] = createdSubscriber.id;
|
|
891
|
+
created++;
|
|
892
|
+
}
|
|
893
|
+
return { created, subscriberIdMap };
|
|
894
|
+
}
|
|
895
|
+
async applyProjectSettings(projectId, projectSettings, maps, archiveStatusId) {
|
|
896
|
+
let initialPromptSet = false;
|
|
897
|
+
if (projectSettings) {
|
|
898
|
+
const ps = projectSettings;
|
|
899
|
+
if (ps.initialPromptTitle) {
|
|
900
|
+
const promptId = maps.promptTitleToId.get(ps.initialPromptTitle.toLowerCase());
|
|
901
|
+
if (promptId) {
|
|
902
|
+
await this.settings.updateSettings({
|
|
903
|
+
projectId,
|
|
904
|
+
initialSessionPromptId: promptId,
|
|
905
|
+
});
|
|
906
|
+
initialPromptSet = true;
|
|
907
|
+
logger.info({ projectId, promptTitle: ps.initialPromptTitle }, 'Applied initial prompt from projectSettings');
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
if (ps.autoCleanStatusLabels && ps.autoCleanStatusLabels.length > 0) {
|
|
911
|
+
const autoCleanStatusIds = ps.autoCleanStatusLabels
|
|
912
|
+
.map((label) => maps.statusLabelToId.get(label.toLowerCase()))
|
|
913
|
+
.filter((id) => !!id);
|
|
914
|
+
if (autoCleanStatusIds.length > 0) {
|
|
915
|
+
const currentSettings = this.settings.getSettings();
|
|
916
|
+
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
917
|
+
await this.settings.updateSettings({
|
|
918
|
+
autoClean: {
|
|
919
|
+
statusIds: { ...existingAutoClean, [projectId]: autoCleanStatusIds },
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
logger.info({ projectId, autoCleanStatusIds }, 'Applied autoClean statuses from projectSettings');
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
else if (archiveStatusId) {
|
|
926
|
+
const currentSettings = this.settings.getSettings();
|
|
927
|
+
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
928
|
+
await this.settings.updateSettings({
|
|
929
|
+
autoClean: {
|
|
930
|
+
statusIds: { ...existingAutoClean, [projectId]: [archiveStatusId] },
|
|
931
|
+
},
|
|
932
|
+
});
|
|
933
|
+
logger.info({ projectId, archiveStatusId }, 'Auto-configured Archive status for auto-clean (fallback)');
|
|
934
|
+
}
|
|
935
|
+
if (ps.epicAssignedTemplate) {
|
|
936
|
+
await this.settings.updateSettings({
|
|
937
|
+
events: {
|
|
938
|
+
epicAssigned: { template: ps.epicAssignedTemplate },
|
|
939
|
+
},
|
|
940
|
+
});
|
|
941
|
+
logger.info({ projectId }, 'Applied epicAssigned template from projectSettings');
|
|
942
|
+
}
|
|
943
|
+
if (ps.messagePoolSettings) {
|
|
944
|
+
await this.settings.setProjectPoolSettings(projectId, ps.messagePoolSettings);
|
|
945
|
+
logger.info({ projectId, poolSettings: ps.messagePoolSettings }, 'Applied message pool settings from projectSettings');
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
else if (archiveStatusId) {
|
|
949
|
+
const currentSettings = this.settings.getSettings();
|
|
950
|
+
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
951
|
+
await this.settings.updateSettings({
|
|
952
|
+
autoClean: {
|
|
953
|
+
statusIds: { ...existingAutoClean, [projectId]: [archiveStatusId] },
|
|
954
|
+
},
|
|
955
|
+
});
|
|
956
|
+
logger.info({ projectId, archiveStatusId }, 'Auto-configured Archive status for auto-clean');
|
|
957
|
+
}
|
|
958
|
+
return { initialPromptSet };
|
|
959
|
+
}
|
|
960
|
+
normalizeProfileOptions(options) {
|
|
961
|
+
if (typeof options === 'string') {
|
|
962
|
+
return options;
|
|
963
|
+
}
|
|
964
|
+
if (options && typeof options === 'object') {
|
|
965
|
+
try {
|
|
966
|
+
return JSON.stringify(options);
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
return null;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return null;
|
|
1153
973
|
}
|
|
1154
974
|
getImportErrorMessage(error) {
|
|
1155
975
|
const errorCode = error?.code;
|
|
@@ -1173,6 +993,12 @@ let ProjectsService = class ProjectsService {
|
|
|
1173
993
|
}
|
|
1174
994
|
return 'Import failed: An unexpected error occurred.';
|
|
1175
995
|
}
|
|
996
|
+
slugify(name) {
|
|
997
|
+
return name
|
|
998
|
+
.toLowerCase()
|
|
999
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1000
|
+
.replace(/(^-|-$)/g, '');
|
|
1001
|
+
}
|
|
1176
1002
|
};
|
|
1177
1003
|
exports.ProjectsService = ProjectsService;
|
|
1178
1004
|
exports.ProjectsService = ProjectsService = __decorate([
|
|
@@ -1181,6 +1007,7 @@ exports.ProjectsService = ProjectsService = __decorate([
|
|
|
1181
1007
|
__metadata("design:paramtypes", [Object, sessions_service_1.SessionsService,
|
|
1182
1008
|
settings_service_1.SettingsService,
|
|
1183
1009
|
watchers_service_1.WatchersService,
|
|
1184
|
-
watcher_runner_service_1.WatcherRunnerService
|
|
1010
|
+
watcher_runner_service_1.WatcherRunnerService,
|
|
1011
|
+
unified_template_service_1.UnifiedTemplateService])
|
|
1185
1012
|
], ProjectsService);
|
|
1186
1013
|
//# sourceMappingURL=projects.service.js.map
|