devchain-cli 0.3.1 → 0.4.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.
- 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/chat/dtos/chat.dto.js +1 -1
- package/dist/server/modules/chat/dtos/chat.dto.js.map +1 -1
- 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/constants.d.ts +2 -0
- package/dist/server/modules/mcp/constants.js +6 -1
- package/dist/server/modules/mcp/constants.js.map +1 -1
- package/dist/server/modules/mcp/controllers/mcp-http.controller.js +2 -6
- package/dist/server/modules/mcp/controllers/mcp-http.controller.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 +142 -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 +83 -0
- package/dist/server/modules/projects/dtos/export.dto.js +34 -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 +39 -3
- package/dist/server/modules/projects/services/projects.service.js +358 -434
- 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 +78 -17
- package/dist/server/templates/claude-opus.json +251 -37
- package/dist/server/templates/simple-codex.json +141 -38
- 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-4Q4XuabC.js +733 -0
- package/dist/server/ui/assets/index-BkGGbapJ.css +32 -0
- package/dist/server/ui/index.html +2 -2
- package/dist/templates/claude-codex-advanced.json +78 -17
- package/dist/templates/claude-opus.json +251 -37
- package/dist/templates/simple-codex.json +141 -38
- package/package.json +15 -2
- 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/ui/assets/index-C9GXCjnF.js +0 -700
- package/dist/server/ui/assets/index-o0FbZg-1.css +0 -32
|
@@ -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,115 +180,81 @@ let ProjectsService = class ProjectsService {
|
|
|
342
180
|
rootPath: input.rootPath,
|
|
343
181
|
isTemplate: false,
|
|
344
182
|
}, templatePayload);
|
|
345
|
-
|
|
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
|
-
const archiveTemplateStatus = templatePayload.statuses.find((s) => s.label.toLowerCase() === 'archive');
|
|
403
|
-
if (archiveTemplateStatus?.id) {
|
|
404
|
-
const archiveNewId = result.mappings.statusIdMap[archiveTemplateStatus.id];
|
|
405
|
-
if (archiveNewId) {
|
|
406
|
-
const currentSettings = this.settings.getSettings();
|
|
407
|
-
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
408
|
-
await this.settings.updateSettings({
|
|
409
|
-
autoClean: {
|
|
410
|
-
statusIds: { ...existingAutoClean, [result.project.id]: [archiveNewId] },
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
logger.info({ projectId: result.project.id, archiveStatusId: archiveNewId }, 'Auto-configured Archive status for auto-clean (fallback)');
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
if (ps.epicAssignedTemplate) {
|
|
418
|
-
await this.settings.updateSettings({
|
|
419
|
-
events: {
|
|
420
|
-
epicAssigned: { template: ps.epicAssignedTemplate },
|
|
421
|
-
},
|
|
422
|
-
});
|
|
423
|
-
logger.info({ projectId: result.project.id }, 'Applied epicAssigned template from projectSettings');
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
else {
|
|
427
|
-
const archiveTemplateStatus = templatePayload.statuses.find((s) => s.label.toLowerCase() === 'archive');
|
|
428
|
-
if (archiveTemplateStatus?.id) {
|
|
429
|
-
const archiveNewId = result.mappings.statusIdMap[archiveTemplateStatus.id];
|
|
430
|
-
if (archiveNewId) {
|
|
431
|
-
const currentSettings = this.settings.getSettings();
|
|
432
|
-
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
433
|
-
await this.settings.updateSettings({
|
|
434
|
-
autoClean: {
|
|
435
|
-
statusIds: { ...existingAutoClean, [result.project.id]: [archiveNewId] },
|
|
436
|
-
},
|
|
437
|
-
});
|
|
438
|
-
logger.info({ projectId: result.project.id, archiveStatusId: archiveNewId }, 'Auto-configured Archive status for auto-clean');
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
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');
|
|
442
240
|
return {
|
|
443
241
|
success: true,
|
|
444
242
|
project: result.project,
|
|
445
|
-
imported:
|
|
243
|
+
imported: {
|
|
244
|
+
...result.imported,
|
|
245
|
+
watchers: watchersCreated,
|
|
246
|
+
subscribers: subscribersCreated,
|
|
247
|
+
},
|
|
446
248
|
mappings: result.mappings,
|
|
447
249
|
initialPromptSet,
|
|
448
250
|
message: 'Project created from template successfully.',
|
|
449
251
|
};
|
|
450
252
|
}
|
|
451
|
-
async exportProject(projectId) {
|
|
253
|
+
async exportProject(projectId, opts) {
|
|
452
254
|
logger.info({ projectId }, 'exportProject');
|
|
453
|
-
const
|
|
255
|
+
const { manifestOverrides } = opts ?? {};
|
|
256
|
+
const [project, promptsRes, profilesRes, agentsRes, statusesRes, initialPrompt, settings, watchersRes, subscribersRes,] = await Promise.all([
|
|
257
|
+
this.storage.getProject(projectId),
|
|
454
258
|
this.storage.listPrompts({ projectId, limit: 1000, offset: 0 }),
|
|
455
259
|
this.storage.listAgentProfiles({ projectId, limit: 1000, offset: 0 }),
|
|
456
260
|
this.storage.listAgents(projectId, { limit: 1000, offset: 0 }),
|
|
@@ -557,6 +361,10 @@ let ProjectsService = class ProjectsService {
|
|
|
557
361
|
if (epicAssignedTemplate) {
|
|
558
362
|
projectSettings.epicAssignedTemplate = epicAssignedTemplate;
|
|
559
363
|
}
|
|
364
|
+
const poolSettings = settings.messagePool?.projects?.[projectId];
|
|
365
|
+
if (poolSettings && Object.keys(poolSettings).length > 0) {
|
|
366
|
+
projectSettings.messagePoolSettings = poolSettings;
|
|
367
|
+
}
|
|
560
368
|
const watchers = await Promise.all(watchersRes.map(async (w) => {
|
|
561
369
|
let scopeFilterName = null;
|
|
562
370
|
if (w.scopeFilterId) {
|
|
@@ -614,7 +422,17 @@ let ProjectsService = class ProjectsService {
|
|
|
614
422
|
position: s.position,
|
|
615
423
|
priority: s.priority,
|
|
616
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
|
+
};
|
|
617
434
|
const exportPayload = {
|
|
435
|
+
_manifest: manifest,
|
|
618
436
|
version: 1,
|
|
619
437
|
exportedAt: new Date().toISOString(),
|
|
620
438
|
prompts,
|
|
@@ -633,14 +451,9 @@ let ProjectsService = class ProjectsService {
|
|
|
633
451
|
async importProject(input) {
|
|
634
452
|
logger.info({ projectId: input.projectId, dryRun: input.dryRun }, 'importProject');
|
|
635
453
|
const isDryRun = input.dryRun ?? false;
|
|
636
|
-
const payload = ExportSchema.parse(input.payload ?? {});
|
|
454
|
+
const payload = shared_1.ExportSchema.parse(input.payload ?? {});
|
|
637
455
|
const providerNames = new Set((payload.profiles ?? []).map((p) => p.provider.name.trim().toLowerCase()));
|
|
638
|
-
const
|
|
639
|
-
const available = new Map();
|
|
640
|
-
for (const prov of providers.items) {
|
|
641
|
-
available.set(prov.name.trim().toLowerCase(), prov.id);
|
|
642
|
-
}
|
|
643
|
-
const missingProviders = Array.from(providerNames).filter((n) => !available.has(n));
|
|
456
|
+
const { available, missing: missingProviders } = await this.resolveProviders(providerNames);
|
|
644
457
|
const [existingPrompts, existingProfiles, existingAgents, existingStatuses, existingWatchers, existingSubscribers,] = await Promise.all([
|
|
645
458
|
this.storage.listPrompts({ projectId: input.projectId, limit: 10000, offset: 0 }),
|
|
646
459
|
this.storage.listAgentProfiles({ projectId: input.projectId, limit: 10000, offset: 0 }),
|
|
@@ -695,16 +508,14 @@ let ProjectsService = class ProjectsService {
|
|
|
695
508
|
};
|
|
696
509
|
}
|
|
697
510
|
if (missingProviders.length > 0) {
|
|
698
|
-
throw new
|
|
699
|
-
message: 'Import aborted: missing providers',
|
|
511
|
+
throw new error_types_1.ValidationError('Import aborted: missing providers', {
|
|
700
512
|
missingProviders,
|
|
701
513
|
hint: 'Install/configure providers by name before importing profiles.',
|
|
702
514
|
});
|
|
703
515
|
}
|
|
704
516
|
const activeSessions = this.sessions.getActiveSessionsForProject(input.projectId);
|
|
705
517
|
if (activeSessions.length > 0) {
|
|
706
|
-
throw new
|
|
707
|
-
message: 'Import aborted: active agent sessions detected',
|
|
518
|
+
throw new error_types_1.ConflictError('Import aborted: active agent sessions detected', {
|
|
708
519
|
activeSessions: activeSessions.map((s) => ({ id: s.id, agentId: s.agentId })),
|
|
709
520
|
hint: 'Terminate all running sessions for this project before importing.',
|
|
710
521
|
});
|
|
@@ -804,45 +615,29 @@ let ProjectsService = class ProjectsService {
|
|
|
804
615
|
promptIdMap[p.id] = created.id;
|
|
805
616
|
createdPrompts.push({ id: created.id, title: created.title });
|
|
806
617
|
}
|
|
807
|
-
const profileNameToNewId = new Map();
|
|
808
618
|
for (const prof of payload.profiles) {
|
|
809
619
|
const providerId = available.get(prof.provider.name.trim().toLowerCase());
|
|
810
620
|
if (!providerId) {
|
|
811
|
-
throw new
|
|
812
|
-
}
|
|
813
|
-
let optionsStr = null;
|
|
814
|
-
if (typeof prof.options === 'string') {
|
|
815
|
-
optionsStr = prof.options;
|
|
816
|
-
}
|
|
817
|
-
else if (prof.options && typeof prof.options === 'object') {
|
|
818
|
-
try {
|
|
819
|
-
optionsStr = JSON.stringify(prof.options);
|
|
820
|
-
}
|
|
821
|
-
catch {
|
|
822
|
-
optionsStr = null;
|
|
823
|
-
}
|
|
621
|
+
throw new error_types_1.NotFoundError('Provider', prof.provider.name);
|
|
824
622
|
}
|
|
825
623
|
const created = await this.storage.createAgentProfile({
|
|
826
624
|
projectId: input.projectId,
|
|
827
625
|
name: prof.name,
|
|
828
626
|
providerId,
|
|
829
|
-
options:
|
|
627
|
+
options: this.normalizeProfileOptions(prof.options),
|
|
830
628
|
systemPrompt: null,
|
|
831
629
|
instructions: prof.instructions ?? null,
|
|
832
630
|
temperature: prof.temperature ?? null,
|
|
833
631
|
maxTokens: prof.maxTokens ?? null,
|
|
834
632
|
});
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
profileNameToNewId.set(prof.name.trim().toLowerCase(), created.id);
|
|
633
|
+
const profKey = prof.id || `name:${prof.name.trim().toLowerCase()}`;
|
|
634
|
+
profileIdMap[profKey] = created.id;
|
|
838
635
|
}
|
|
839
|
-
const agentNameToNewId = new Map();
|
|
840
636
|
for (const a of payload.agents) {
|
|
841
637
|
const oldProfileId = a.profileId ?? '';
|
|
842
638
|
const newProfileId = oldProfileId && profileIdMap[oldProfileId] ? profileIdMap[oldProfileId] : undefined;
|
|
843
639
|
if (!newProfileId) {
|
|
844
|
-
throw new
|
|
845
|
-
message: `Profile mapping missing for agent ${a.name}`,
|
|
640
|
+
throw new error_types_1.ValidationError(`Profile mapping missing for agent ${a.name}`, {
|
|
846
641
|
profileId: oldProfileId || null,
|
|
847
642
|
});
|
|
848
643
|
}
|
|
@@ -852,75 +647,26 @@ let ProjectsService = class ProjectsService {
|
|
|
852
647
|
profileId: newProfileId,
|
|
853
648
|
description: a.description ?? null,
|
|
854
649
|
});
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
agentNameToNewId.set(a.name.trim().toLowerCase(), created.id);
|
|
858
|
-
}
|
|
859
|
-
const watcherIdMap = {};
|
|
860
|
-
for (const w of payload.watchers) {
|
|
861
|
-
let scopeFilterId = null;
|
|
862
|
-
if (w.scopeFilterName && w.scope !== 'all') {
|
|
863
|
-
const scopeFilterNameLower = w.scopeFilterName.trim().toLowerCase();
|
|
864
|
-
switch (w.scope) {
|
|
865
|
-
case 'agent': {
|
|
866
|
-
scopeFilterId = agentNameToNewId.get(scopeFilterNameLower) ?? null;
|
|
867
|
-
break;
|
|
868
|
-
}
|
|
869
|
-
case 'profile': {
|
|
870
|
-
scopeFilterId = profileNameToNewId.get(scopeFilterNameLower) ?? null;
|
|
871
|
-
break;
|
|
872
|
-
}
|
|
873
|
-
case 'provider': {
|
|
874
|
-
const provider = providers.items.find((p) => p.name.trim().toLowerCase() === scopeFilterNameLower);
|
|
875
|
-
scopeFilterId = provider?.id ?? null;
|
|
876
|
-
break;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
if (!scopeFilterId) {
|
|
880
|
-
logger.warn({ watcherName: w.name, scope: w.scope, scopeFilterName: w.scopeFilterName }, 'Could not resolve scope filter, setting scope to "all"');
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
const created = await this.storage.createWatcher({
|
|
884
|
-
projectId: input.projectId,
|
|
885
|
-
name: w.name,
|
|
886
|
-
description: w.description ?? null,
|
|
887
|
-
enabled: w.enabled,
|
|
888
|
-
scope: scopeFilterId ? w.scope : 'all',
|
|
889
|
-
scopeFilterId,
|
|
890
|
-
pollIntervalMs: w.pollIntervalMs,
|
|
891
|
-
viewportLines: w.viewportLines,
|
|
892
|
-
condition: w.condition,
|
|
893
|
-
cooldownMs: w.cooldownMs,
|
|
894
|
-
cooldownMode: w.cooldownMode,
|
|
895
|
-
eventName: w.eventName,
|
|
896
|
-
});
|
|
897
|
-
if (w.id)
|
|
898
|
-
watcherIdMap[w.id] = created.id;
|
|
899
|
-
if (created.enabled) {
|
|
900
|
-
await this.watcherRunner.startWatcher(created);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
const subscriberIdMap = {};
|
|
904
|
-
for (const s of payload.subscribers) {
|
|
905
|
-
const created = await this.storage.createSubscriber({
|
|
906
|
-
projectId: input.projectId,
|
|
907
|
-
name: s.name,
|
|
908
|
-
description: s.description ?? null,
|
|
909
|
-
enabled: s.enabled,
|
|
910
|
-
eventName: s.eventName,
|
|
911
|
-
eventFilter: s.eventFilter ?? null,
|
|
912
|
-
actionType: s.actionType,
|
|
913
|
-
actionInputs: s.actionInputs,
|
|
914
|
-
delayMs: s.delayMs,
|
|
915
|
-
cooldownMs: s.cooldownMs,
|
|
916
|
-
retryOnError: s.retryOnError,
|
|
917
|
-
groupName: s.groupName ?? null,
|
|
918
|
-
position: s.position ?? 0,
|
|
919
|
-
priority: s.priority ?? 0,
|
|
920
|
-
});
|
|
921
|
-
if (s.id)
|
|
922
|
-
subscriberIdMap[s.id] = created.id;
|
|
650
|
+
const agentKey = a.id || `name:${a.name.trim().toLowerCase()}`;
|
|
651
|
+
agentIdMap[agentKey] = created.id;
|
|
923
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);
|
|
924
670
|
logger.info({
|
|
925
671
|
watchersCreated: payload.watchers.length,
|
|
926
672
|
subscribersCreated: payload.subscribers.length,
|
|
@@ -952,62 +698,31 @@ let ProjectsService = class ProjectsService {
|
|
|
952
698
|
}
|
|
953
699
|
}
|
|
954
700
|
logger.info({ epicsRemapped, epicsCleared }, 'Epic agent references updated after import');
|
|
955
|
-
let
|
|
956
|
-
if (payload.initialPrompt) {
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
if (!targetPromptId && payload.initialPrompt.promptId) {
|
|
964
|
-
const mapped = promptIdMap[payload.initialPrompt.promptId];
|
|
965
|
-
if (mapped)
|
|
966
|
-
targetPromptId = mapped;
|
|
967
|
-
}
|
|
968
|
-
await this.settings.updateSettings({
|
|
969
|
-
projectId: input.projectId,
|
|
970
|
-
initialSessionPromptId: targetPromptId ?? null,
|
|
971
|
-
});
|
|
972
|
-
initialPromptSet = Boolean(targetPromptId);
|
|
973
|
-
}
|
|
974
|
-
if (payload.projectSettings) {
|
|
975
|
-
const ps = payload.projectSettings;
|
|
976
|
-
if (ps.initialPromptTitle) {
|
|
977
|
-
const matchByTitle = createdPrompts.find((cp) => cp.title.toLowerCase() === ps.initialPromptTitle.toLowerCase());
|
|
978
|
-
if (matchByTitle) {
|
|
979
|
-
await this.settings.updateSettings({
|
|
980
|
-
projectId: input.projectId,
|
|
981
|
-
initialSessionPromptId: matchByTitle.id,
|
|
982
|
-
});
|
|
983
|
-
initialPromptSet = true;
|
|
984
|
-
logger.info({ projectId: input.projectId, promptTitle: ps.initialPromptTitle }, 'Applied initial prompt from projectSettings');
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
if (ps.autoCleanStatusLabels && ps.autoCleanStatusLabels.length > 0) {
|
|
988
|
-
const autoCleanStatusIds = ps.autoCleanStatusLabels
|
|
989
|
-
.map((label) => templateLabelToStatusId.get(label.toLowerCase()))
|
|
990
|
-
.filter((id) => !!id);
|
|
991
|
-
if (autoCleanStatusIds.length > 0) {
|
|
992
|
-
const currentSettings = this.settings.getSettings();
|
|
993
|
-
const existingAutoClean = currentSettings.autoClean?.statusIds ?? {};
|
|
994
|
-
await this.settings.updateSettings({
|
|
995
|
-
autoClean: {
|
|
996
|
-
statusIds: { ...existingAutoClean, [input.projectId]: autoCleanStatusIds },
|
|
997
|
-
},
|
|
998
|
-
});
|
|
999
|
-
logger.info({ projectId: input.projectId, autoCleanStatusIds }, 'Applied autoClean statuses from projectSettings');
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
if (ps.epicAssignedTemplate) {
|
|
1003
|
-
await this.settings.updateSettings({
|
|
1004
|
-
events: {
|
|
1005
|
-
epicAssigned: { template: ps.epicAssignedTemplate },
|
|
1006
|
-
},
|
|
1007
|
-
});
|
|
1008
|
-
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;
|
|
1009
709
|
}
|
|
1010
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;
|
|
1011
726
|
return {
|
|
1012
727
|
success: true,
|
|
1013
728
|
mode: 'replace',
|
|
@@ -1051,8 +766,210 @@ let ProjectsService = class ProjectsService {
|
|
|
1051
766
|
catch (error) {
|
|
1052
767
|
logger.error({ error, projectId: input.projectId }, 'Import failed');
|
|
1053
768
|
const message = this.getImportErrorMessage(error);
|
|
1054
|
-
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);
|
|
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
|
+
}
|
|
1055
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;
|
|
1056
973
|
}
|
|
1057
974
|
getImportErrorMessage(error) {
|
|
1058
975
|
const errorCode = error?.code;
|
|
@@ -1076,6 +993,12 @@ let ProjectsService = class ProjectsService {
|
|
|
1076
993
|
}
|
|
1077
994
|
return 'Import failed: An unexpected error occurred.';
|
|
1078
995
|
}
|
|
996
|
+
slugify(name) {
|
|
997
|
+
return name
|
|
998
|
+
.toLowerCase()
|
|
999
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
1000
|
+
.replace(/(^-|-$)/g, '');
|
|
1001
|
+
}
|
|
1079
1002
|
};
|
|
1080
1003
|
exports.ProjectsService = ProjectsService;
|
|
1081
1004
|
exports.ProjectsService = ProjectsService = __decorate([
|
|
@@ -1084,6 +1007,7 @@ exports.ProjectsService = ProjectsService = __decorate([
|
|
|
1084
1007
|
__metadata("design:paramtypes", [Object, sessions_service_1.SessionsService,
|
|
1085
1008
|
settings_service_1.SettingsService,
|
|
1086
1009
|
watchers_service_1.WatchersService,
|
|
1087
|
-
watcher_runner_service_1.WatcherRunnerService
|
|
1010
|
+
watcher_runner_service_1.WatcherRunnerService,
|
|
1011
|
+
unified_template_service_1.UnifiedTemplateService])
|
|
1088
1012
|
], ProjectsService);
|
|
1089
1013
|
//# sourceMappingURL=projects.service.js.map
|