team-chat 0.0.0-stage → 0.3.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/LICENSE +21 -0
- package/README.md +458 -2
- package/agents/rules.md +52 -0
- package/agents/runtimes/codex.md +14 -0
- package/bin/bridge +3 -0
- package/bin/runner +3 -0
- package/cli/lib/agents.js +97 -0
- package/cli/lib/docs.js +151 -0
- package/cli/lib/local.js +41 -0
- package/cli/lib/server.js +52 -0
- package/cli/lib/util.js +107 -0
- package/cli/team-chat.js +383 -0
- package/host/bridge/attempts.js +43 -0
- package/host/bridge/bridge.js +326 -0
- package/host/bridge/config.js +105 -0
- package/host/bridge/knowledge-outbox.js +96 -0
- package/host/bridge/launcher.js +126 -0
- package/host/bridge/main.js +87 -0
- package/host/bridge/progress.js +74 -0
- package/host/bridge/prompt.js +97 -0
- package/host/bridge/run-archive.js +200 -0
- package/host/bridge/thread.js +44 -0
- package/host/bridge/triage.js +97 -0
- package/host/bridge/workdir.js +41 -0
- package/host/knowledge/card.js +43 -0
- package/host/knowledge/errors.js +50 -0
- package/host/knowledge/lesson.js +153 -0
- package/host/knowledge/process.js +160 -0
- package/host/knowledge/scrub.js +29 -0
- package/host/knowledge/shell.js +74 -0
- package/host/knowledge/stack.js +229 -0
- package/host/knowledge/transcript.js +82 -0
- package/host/lib/chat-api.js +120 -0
- package/host/lib/log.js +6 -0
- package/host/lib/pid-lock.js +65 -0
- package/host/lib/shell-words.js +45 -0
- package/host/runner/bridge-process.js +82 -0
- package/host/runner/config.js +81 -0
- package/host/runner/main.js +69 -0
- package/host/runner/retention.js +40 -0
- package/host/runner/runner.js +161 -0
- package/package.json +67 -5
- package/server/dist/src/app.js +25 -0
- package/server/dist/src/app.module.js +50 -0
- package/server/dist/src/attachments/attachment-store.js +111 -0
- package/server/dist/src/attachments/attachments.controller.js +67 -0
- package/server/dist/src/attachments/mime.js +84 -0
- package/server/dist/src/attachments/uploads.interceptor.js +72 -0
- package/server/dist/src/auth/participant.guard.js +53 -0
- package/server/dist/src/bridges/bridge-restart.service.js +80 -0
- package/server/dist/src/bridges/bridges.controller.js +47 -0
- package/server/dist/src/chats/chats.controller.js +83 -0
- package/server/dist/src/chats/chats.service.js +128 -0
- package/server/dist/src/collector/collector.controller.js +71 -0
- package/server/dist/src/collector/collector.module.js +23 -0
- package/server/dist/src/collector/collector.service.js +111 -0
- package/server/dist/src/common/clock.js +20 -0
- package/server/dist/src/common/errors.js +37 -0
- package/server/dist/src/common/format.js +14 -0
- package/server/dist/src/common/http.js +17 -0
- package/server/dist/src/common/slug.js +28 -0
- package/server/dist/src/common/tokens.js +19 -0
- package/server/dist/src/common/validation.js +41 -0
- package/server/dist/src/config/config.js +39 -0
- package/server/dist/src/conversation.module.js +41 -0
- package/server/dist/src/db/database.js +73 -0
- package/server/dist/src/db/database.module.js +51 -0
- package/server/dist/src/db/schema.js +212 -0
- package/server/dist/src/decisions/decisions.controller.js +55 -0
- package/server/dist/src/decisions/decisions.service.js +109 -0
- package/server/dist/src/documents/documents.controller.js +87 -0
- package/server/dist/src/documents/documents.service.js +189 -0
- package/server/dist/src/health/health.controller.js +29 -0
- package/server/dist/src/knowledge/knowledge.controller.js +83 -0
- package/server/dist/src/knowledge/knowledge.module.js +25 -0
- package/server/dist/src/knowledge/knowledge.service.js +189 -0
- package/server/dist/src/local/docs-sync.service.js +97 -0
- package/server/dist/src/local/local.controller.js +197 -0
- package/server/dist/src/local/local.guard.js +55 -0
- package/server/dist/src/local/local.module.js +30 -0
- package/server/dist/src/local/setup.service.js +154 -0
- package/server/dist/src/local/transfer.service.js +151 -0
- package/server/dist/src/main.js +33 -0
- package/server/dist/src/mcp/chat-tools.js +282 -0
- package/server/dist/src/mcp/mcp.controller.js +58 -0
- package/server/dist/src/mcp/mcp.module.js +24 -0
- package/server/dist/src/mentions/mention-state.js +80 -0
- package/server/dist/src/mentions/mentions.controller.js +100 -0
- package/server/dist/src/mentions/mentions.service.js +203 -0
- package/server/dist/src/messages/message.presenter.js +102 -0
- package/server/dist/src/messages/messages.service.js +149 -0
- package/server/dist/src/messages/thread.controller.js +179 -0
- package/server/dist/src/participants/directory.service.js +68 -0
- package/server/dist/src/participants/participants.module.js +24 -0
- package/server/dist/src/participants/participants.service.js +84 -0
- package/server/dist/src/settings/settings.service.js +42 -0
- package/server/dist/src/sharing/consent.js +16 -0
- package/server/dist/src/sharing/sharing.module.js +22 -0
- package/server/dist/src/sharing/sharing.service.js +175 -0
- package/server/dist/src/team/runner.service.js +75 -0
- package/server/dist/src/team/team.controller.js +178 -0
- package/server/dist/src/team/team.module.js +25 -0
- package/server/dist/src/team/team.service.js +280 -0
- package/server/dist/src/web/template.js +57 -0
- package/server/dist/src/web/web-login.service.js +74 -0
- package/server/dist/src/web/web.controller.js +203 -0
- package/server/dist/src/web/web.module.js +25 -0
- package/server/drizzle/0000_init.sql +169 -0
- package/server/drizzle/0001_seed_roles.sql +25 -0
- package/server/drizzle/0002_knowledge.sql +17 -0
- package/server/drizzle/0003_sharing.sql +30 -0
- package/server/drizzle/meta/0000_snapshot.json +1224 -0
- package/server/drizzle/meta/0001_snapshot.json +1224 -0
- package/server/drizzle/meta/0002_snapshot.json +1359 -0
- package/server/drizzle/meta/0003_snapshot.json +1545 -0
- package/server/drizzle/meta/_journal.json +34 -0
- package/server/package.json +1 -0
- package/server/views/chat.html +865 -0
- package/server/views/documents.html +204 -0
- package/server/views/login.html +31 -0
- package/server/views/setup.html +60 -0
- package/server/views/team.html +369 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DocumentsService = exports.STATUSES = exports.KINDS = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
18
|
+
const zod_1 = require("zod");
|
|
19
|
+
const errors_1 = require("../common/errors");
|
|
20
|
+
const format_1 = require("../common/format");
|
|
21
|
+
const validation_1 = require("../common/validation");
|
|
22
|
+
const database_module_1 = require("../db/database.module");
|
|
23
|
+
const schema_1 = require("../db/schema");
|
|
24
|
+
const participants_service_1 = require("../participants/participants.service");
|
|
25
|
+
exports.KINDS = ['plan', 'audit', 'handoff', 'report', 'reference'];
|
|
26
|
+
exports.STATUSES = ['draft', 'current', 'archived'];
|
|
27
|
+
const writeSchema = zod_1.z.object({
|
|
28
|
+
slug: zod_1.z.string().max(200).regex(/^[a-z0-9][a-z0-9._-]*$/, {
|
|
29
|
+
error: 'Адрес документа — латиница, цифры, точка, дефис и подчёркивание.',
|
|
30
|
+
}),
|
|
31
|
+
title: zod_1.z.string().max(300),
|
|
32
|
+
body: zod_1.z.string().max(500_000),
|
|
33
|
+
kind: zod_1.z.enum(exports.KINDS),
|
|
34
|
+
status: zod_1.z.enum(exports.STATUSES).nullish(),
|
|
35
|
+
origin: (0, validation_1.optionalText)(300),
|
|
36
|
+
note: (0, validation_1.optionalText)(300),
|
|
37
|
+
});
|
|
38
|
+
const agentWriteSchema = zod_1.z.object({
|
|
39
|
+
slug: zod_1.z.string().max(200),
|
|
40
|
+
title: zod_1.z.string().max(300),
|
|
41
|
+
body: zod_1.z.string(),
|
|
42
|
+
kind: zod_1.z.enum(exports.KINDS),
|
|
43
|
+
status: zod_1.z.enum(exports.STATUSES).nullish(),
|
|
44
|
+
note: (0, validation_1.optionalText)(300),
|
|
45
|
+
// Куда документ ляжет при выгрузке в репозиторий, если его подтвердят.
|
|
46
|
+
path: (0, validation_1.optionalText)(300),
|
|
47
|
+
});
|
|
48
|
+
const updateSchema = zod_1.z.object({
|
|
49
|
+
status: zod_1.z.enum(exports.STATUSES).nullish(),
|
|
50
|
+
origin: (0, validation_1.optionalText)(300),
|
|
51
|
+
kind: zod_1.z.enum(exports.KINDS).nullish(),
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* Документы проекта: планы, аудиты, хендоффы. Тело не перезаписывается — каждая правка
|
|
55
|
+
* новая редакция. Подтвердить актуальность (current) может только владелец: тот же
|
|
56
|
+
* принцип, что с фиксацией решений.
|
|
57
|
+
*/
|
|
58
|
+
let DocumentsService = class DocumentsService {
|
|
59
|
+
db;
|
|
60
|
+
constructor(db) {
|
|
61
|
+
this.db = db;
|
|
62
|
+
}
|
|
63
|
+
async list(chat, filter = {}) {
|
|
64
|
+
const rows = await this.db.select().from(schema_1.documents).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.documents.chatId, chat.id), filter.status ? (0, drizzle_orm_1.eq)(schema_1.documents.status, filter.status) : undefined, filter.kind ? (0, drizzle_orm_1.eq)(schema_1.documents.kind, filter.kind) : undefined)).orderBy((0, drizzle_orm_1.asc)(schema_1.documents.slug));
|
|
65
|
+
const latest = await this.latestVersions(rows.map(d => d.id));
|
|
66
|
+
return rows.map(d => documentApi(d, latest.get(d.id)));
|
|
67
|
+
}
|
|
68
|
+
/** Документ целиком — с телом последней редакции и историей правок. */
|
|
69
|
+
async show(chat, slug) {
|
|
70
|
+
const document = await this.find(chat, slug);
|
|
71
|
+
if (!document)
|
|
72
|
+
throw (0, errors_1.notFound)();
|
|
73
|
+
const history = await this.db.select({ version: schema_1.documentVersions, author: schema_1.participants.handle }).from(schema_1.documentVersions)
|
|
74
|
+
.leftJoin(schema_1.participants, (0, drizzle_orm_1.eq)(schema_1.participants.id, schema_1.documentVersions.participantId))
|
|
75
|
+
.where((0, drizzle_orm_1.eq)(schema_1.documentVersions.documentId, document.id))
|
|
76
|
+
.orderBy((0, drizzle_orm_1.desc)(schema_1.documentVersions.revision));
|
|
77
|
+
return {
|
|
78
|
+
...documentApi(document, history[0]?.version, true),
|
|
79
|
+
history: history.map(h => ({ revision: h.version.revision, note: h.version.note, author: h.author, at: (0, format_1.iso)(h.version.createdAt) })),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Для MCP: документ с телом, без истории — или null. */
|
|
83
|
+
async read(chat, slug) {
|
|
84
|
+
const document = await this.find(chat, slug);
|
|
85
|
+
if (!document)
|
|
86
|
+
return null;
|
|
87
|
+
return documentApi(document, (await this.latestVersions([document.id])).get(document.id), true);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Создать документ или добавить ему редакцию — из интерфейса. Новая редакция без явного
|
|
91
|
+
* статуса не меняет его, но только у владельца: правка не-владельца возвращает документ
|
|
92
|
+
* в черновик, иначе непроверенный текст остался бы «подтверждённым».
|
|
93
|
+
*/
|
|
94
|
+
async write(chat, me, input) {
|
|
95
|
+
const data = (0, validation_1.validate)(writeSchema, input);
|
|
96
|
+
if (data.status === 'current' && !(0, participants_service_1.isOwner)(me))
|
|
97
|
+
throw (0, errors_1.forbidden)('Подтвердить документ может только владелец.');
|
|
98
|
+
const existing = await this.find(chat, data.slug);
|
|
99
|
+
const document = await this.put(chat, me, {
|
|
100
|
+
...data,
|
|
101
|
+
status: data.status ?? ((0, participants_service_1.isOwner)(me) ? existing?.status ?? 'draft' : 'draft'),
|
|
102
|
+
origin: data.origin ?? existing?.origin ?? null,
|
|
103
|
+
});
|
|
104
|
+
return { document, created: !existing };
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Запись агента через MCP: статус по умолчанию — черновик, подтверждение (current) —
|
|
108
|
+
* право владельца. Путь выгрузки — поле path; не передан — остаётся прежний.
|
|
109
|
+
*/
|
|
110
|
+
async writeFromAgent(chat, me, input) {
|
|
111
|
+
const data = (0, validation_1.validate)(agentWriteSchema, input);
|
|
112
|
+
const status = data.status ?? 'draft';
|
|
113
|
+
if (status === 'current' && !(0, participants_service_1.isOwner)(me)) {
|
|
114
|
+
throw (0, errors_1.forbidden)('Статус current ставит только владелец. Сохрани как draft и попроси подтвердить в треде.');
|
|
115
|
+
}
|
|
116
|
+
const existing = await this.find(chat, data.slug);
|
|
117
|
+
const { body: _, ...document } = await this.put(chat, me, {
|
|
118
|
+
...data, status, origin: data.path ?? existing?.origin ?? null,
|
|
119
|
+
});
|
|
120
|
+
return document;
|
|
121
|
+
}
|
|
122
|
+
/** Смена статуса, типа и пути выгрузки — без новой редакции. */
|
|
123
|
+
async update(chat, slug, me, input) {
|
|
124
|
+
const document = await this.find(chat, slug);
|
|
125
|
+
if (!document)
|
|
126
|
+
throw (0, errors_1.notFound)();
|
|
127
|
+
const data = (0, validation_1.validate)(updateSchema, input);
|
|
128
|
+
if (data.status === 'current' && !(0, participants_service_1.isOwner)(me))
|
|
129
|
+
throw (0, errors_1.forbidden)('Подтвердить документ может только владелец.');
|
|
130
|
+
const changes = Object.fromEntries(Object.entries(data).filter(([, v]) => v != null));
|
|
131
|
+
const [updated] = Object.keys(changes).length
|
|
132
|
+
? await this.db.update(schema_1.documents).set(changes).where((0, drizzle_orm_1.eq)(schema_1.documents.id, document.id)).returning()
|
|
133
|
+
: [document];
|
|
134
|
+
return documentApi(updated, (await this.latestVersions([document.id])).get(document.id));
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Создать документ или добавить редакцию. Прошлые редакции остаются читаемыми.
|
|
138
|
+
* Общий путь для интерфейса, MCP и импорта из репозиториев.
|
|
139
|
+
*/
|
|
140
|
+
async put(chat, author, doc) {
|
|
141
|
+
return this.db.transaction(async (tx) => {
|
|
142
|
+
const [document] = await tx.insert(schema_1.documents).values({
|
|
143
|
+
chatId: chat.id, slug: doc.slug, title: doc.title, kind: doc.kind, status: doc.status,
|
|
144
|
+
origin: doc.origin, participantId: author?.id ?? null,
|
|
145
|
+
}).onConflictDoUpdate({
|
|
146
|
+
target: [schema_1.documents.chatId, schema_1.documents.slug],
|
|
147
|
+
set: { title: doc.title, kind: doc.kind, status: doc.status, origin: doc.origin },
|
|
148
|
+
}).returning();
|
|
149
|
+
const [{ last }] = await tx.select({ last: (0, drizzle_orm_1.max)(schema_1.documentVersions.revision) }).from(schema_1.documentVersions)
|
|
150
|
+
.where((0, drizzle_orm_1.eq)(schema_1.documentVersions.documentId, document.id));
|
|
151
|
+
const [version] = await tx.insert(schema_1.documentVersions).values({
|
|
152
|
+
documentId: document.id, revision: (last ?? 0) + 1, body: doc.body,
|
|
153
|
+
participantId: author?.id ?? null, note: doc.note ?? null,
|
|
154
|
+
}).returning();
|
|
155
|
+
return documentApi(document, version, true);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
async find(chat, slug) {
|
|
159
|
+
const [row] = await this.db.select().from(schema_1.documents).where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.documents.chatId, chat.id), (0, drizzle_orm_1.eq)(schema_1.documents.slug, slug)));
|
|
160
|
+
return row ?? null;
|
|
161
|
+
}
|
|
162
|
+
async latestVersions(ids) {
|
|
163
|
+
if (ids.length === 0)
|
|
164
|
+
return new Map();
|
|
165
|
+
const rows = await this.db.select().from(schema_1.documentVersions)
|
|
166
|
+
.where((0, drizzle_orm_1.inArray)(schema_1.documentVersions.documentId, ids)).orderBy((0, drizzle_orm_1.asc)(schema_1.documentVersions.revision));
|
|
167
|
+
return new Map(rows.map(v => [v.documentId, v])); // последняя редакция перекрывает прежние
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
exports.DocumentsService = DocumentsService;
|
|
171
|
+
exports.DocumentsService = DocumentsService = __decorate([
|
|
172
|
+
(0, common_1.Injectable)(),
|
|
173
|
+
__param(0, (0, common_1.Inject)(database_module_1.DATABASE)),
|
|
174
|
+
__metadata("design:paramtypes", [Object])
|
|
175
|
+
], DocumentsService);
|
|
176
|
+
/** Документ так, как его видят клиенты: пустые поля не отдаются вовсе. */
|
|
177
|
+
function documentApi(d, version, withBody = false) {
|
|
178
|
+
const api = {
|
|
179
|
+
slug: d.slug,
|
|
180
|
+
title: d.title,
|
|
181
|
+
kind: d.kind,
|
|
182
|
+
status: d.status,
|
|
183
|
+
origin: d.origin,
|
|
184
|
+
revision: version?.revision,
|
|
185
|
+
updated_at: (0, format_1.iso)(d.updatedAt),
|
|
186
|
+
body: withBody ? version?.body : undefined,
|
|
187
|
+
};
|
|
188
|
+
return Object.fromEntries(Object.entries(api).filter(([, v]) => v !== null && v !== undefined));
|
|
189
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.HealthController = void 0;
|
|
13
|
+
const common_1 = require("@nestjs/common");
|
|
14
|
+
/** Жив ли сервер: по нему CLI ждёт запуска (team-chat start). Путь — как был у Laravel. */
|
|
15
|
+
let HealthController = class HealthController {
|
|
16
|
+
up() {
|
|
17
|
+
return { status: 'ok' };
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
exports.HealthController = HealthController;
|
|
21
|
+
__decorate([
|
|
22
|
+
(0, common_1.Get)('up'),
|
|
23
|
+
__metadata("design:type", Function),
|
|
24
|
+
__metadata("design:paramtypes", []),
|
|
25
|
+
__metadata("design:returntype", void 0)
|
|
26
|
+
], HealthController.prototype, "up", null);
|
|
27
|
+
exports.HealthController = HealthController = __decorate([
|
|
28
|
+
(0, common_1.Controller)()
|
|
29
|
+
], HealthController);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.KnowledgeController = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const participant_guard_1 = require("../auth/participant.guard");
|
|
18
|
+
const chats_service_1 = require("../chats/chats.service");
|
|
19
|
+
const http_1 = require("../common/http");
|
|
20
|
+
const knowledge_service_1 = require("./knowledge.service");
|
|
21
|
+
/** Опыт агентов: мост присылает карточки, агенты ищут уроки, удаляет только владелец. */
|
|
22
|
+
let KnowledgeController = class KnowledgeController {
|
|
23
|
+
chats;
|
|
24
|
+
knowledge;
|
|
25
|
+
constructor(chats, knowledge) {
|
|
26
|
+
this.chats = chats;
|
|
27
|
+
this.knowledge = knowledge;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* С q — уроки, похожие на запрос: по всем чатам, свой выше. Без q — последние уроки чата.
|
|
31
|
+
* category — только этого типа задач (bug, task, search, review…).
|
|
32
|
+
*/
|
|
33
|
+
async index(slug, q, limit, category) {
|
|
34
|
+
const chat = await this.chats.resolve(slug);
|
|
35
|
+
const lessons = q?.trim()
|
|
36
|
+
? await this.knowledge.search(q, { chat, limit: (0, http_1.limitFrom)(limit, 5), category })
|
|
37
|
+
: await this.knowledge.lessons(chat, { limit: (0, http_1.limitFrom)(limit), category });
|
|
38
|
+
return { lessons, count: lessons.length };
|
|
39
|
+
}
|
|
40
|
+
/** Карточка запуска от моста: новая — 201, обновление той же записи запуска — 200. */
|
|
41
|
+
async store(me, slug, body, res) {
|
|
42
|
+
const { card, created } = await this.knowledge.save(await this.chats.bySlug(slug), me, body);
|
|
43
|
+
res.status(created ? 201 : 200);
|
|
44
|
+
return card;
|
|
45
|
+
}
|
|
46
|
+
remove(me, id) {
|
|
47
|
+
return this.knowledge.remove(me, id);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.KnowledgeController = KnowledgeController;
|
|
51
|
+
__decorate([
|
|
52
|
+
(0, common_1.Get)(['chats/:chat/knowledge', 'knowledge']),
|
|
53
|
+
__param(0, (0, common_1.Param)('chat')),
|
|
54
|
+
__param(1, (0, common_1.Query)('q')),
|
|
55
|
+
__param(2, (0, common_1.Query)('limit')),
|
|
56
|
+
__param(3, (0, common_1.Query)('category')),
|
|
57
|
+
__metadata("design:type", Function),
|
|
58
|
+
__metadata("design:paramtypes", [Object, String, String, String]),
|
|
59
|
+
__metadata("design:returntype", Promise)
|
|
60
|
+
], KnowledgeController.prototype, "index", null);
|
|
61
|
+
__decorate([
|
|
62
|
+
(0, common_1.Post)('chats/:chat/knowledge'),
|
|
63
|
+
__param(0, (0, participant_guard_1.Me)()),
|
|
64
|
+
__param(1, (0, common_1.Param)('chat')),
|
|
65
|
+
__param(2, (0, common_1.Body)()),
|
|
66
|
+
__param(3, (0, common_1.Res)({ passthrough: true })),
|
|
67
|
+
__metadata("design:type", Function),
|
|
68
|
+
__metadata("design:paramtypes", [Object, String, Object, Object]),
|
|
69
|
+
__metadata("design:returntype", Promise)
|
|
70
|
+
], KnowledgeController.prototype, "store", null);
|
|
71
|
+
__decorate([
|
|
72
|
+
(0, common_1.Delete)('knowledge/:id'),
|
|
73
|
+
__param(0, (0, participant_guard_1.Me)()),
|
|
74
|
+
__param(1, (0, common_1.Param)('id', common_1.ParseIntPipe)),
|
|
75
|
+
__metadata("design:type", Function),
|
|
76
|
+
__metadata("design:paramtypes", [Object, Number]),
|
|
77
|
+
__metadata("design:returntype", void 0)
|
|
78
|
+
], KnowledgeController.prototype, "remove", null);
|
|
79
|
+
exports.KnowledgeController = KnowledgeController = __decorate([
|
|
80
|
+
(0, common_1.Controller)('api'),
|
|
81
|
+
(0, common_1.UseGuards)(participant_guard_1.ParticipantGuard),
|
|
82
|
+
__metadata("design:paramtypes", [chats_service_1.ChatsService, knowledge_service_1.KnowledgeService])
|
|
83
|
+
], KnowledgeController);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.KnowledgeModule = void 0;
|
|
10
|
+
const common_1 = require("@nestjs/common");
|
|
11
|
+
const conversation_module_1 = require("../conversation.module");
|
|
12
|
+
const knowledge_controller_1 = require("./knowledge.controller");
|
|
13
|
+
const knowledge_service_1 = require("./knowledge.service");
|
|
14
|
+
/** Опыт агентов — карточки запусков и уроки из них (docs/plan-2026-10-24-knowledge.md). */
|
|
15
|
+
let KnowledgeModule = class KnowledgeModule {
|
|
16
|
+
};
|
|
17
|
+
exports.KnowledgeModule = KnowledgeModule;
|
|
18
|
+
exports.KnowledgeModule = KnowledgeModule = __decorate([
|
|
19
|
+
(0, common_1.Module)({
|
|
20
|
+
imports: [conversation_module_1.ConversationModule],
|
|
21
|
+
providers: [knowledge_service_1.KnowledgeService],
|
|
22
|
+
controllers: [knowledge_controller_1.KnowledgeController],
|
|
23
|
+
exports: [knowledge_service_1.KnowledgeService],
|
|
24
|
+
})
|
|
25
|
+
], KnowledgeModule);
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.KnowledgeService = exports.MAX_CARD = exports.cardSchema = exports.CATEGORIES = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
18
|
+
const zod_1 = require("zod");
|
|
19
|
+
const errors_1 = require("../common/errors");
|
|
20
|
+
const format_1 = require("../common/format");
|
|
21
|
+
const validation_1 = require("../common/validation");
|
|
22
|
+
const database_module_1 = require("../db/database.module");
|
|
23
|
+
const schema_1 = require("../db/schema");
|
|
24
|
+
const participants_service_1 = require("../participants/participants.service");
|
|
25
|
+
/** Типы задач, как их ставит мост (host/knowledge/lesson.js). Сервер принимает и другие — данные ценнее порядка. */
|
|
26
|
+
exports.CATEGORIES = 'bug, task, search, review, refactor, test, config, infra, performance, security, question, other';
|
|
27
|
+
/** Поля урока — короткие фразы: мост режет их до 300 знаков, здесь — запас. */
|
|
28
|
+
const phrase = () => zod_1.z.string().max(400).nullish();
|
|
29
|
+
const phrases = () => zod_1.z.array(zod_1.z.string().max(400)).max(8).default([]);
|
|
30
|
+
/** Карточка формата 1 — как её собирает мост (host/knowledge/card.js). Её же принимает сборщик. */
|
|
31
|
+
exports.cardSchema = zod_1.z.object({
|
|
32
|
+
format: zod_1.z.literal(1),
|
|
33
|
+
stack: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
34
|
+
process: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
35
|
+
problem: zod_1.z.object({
|
|
36
|
+
category: zod_1.z.string().max(40),
|
|
37
|
+
area: phrase(),
|
|
38
|
+
symptom: phrase(),
|
|
39
|
+
root_cause: phrase(),
|
|
40
|
+
}).nullish(),
|
|
41
|
+
solution: zod_1.z.object({
|
|
42
|
+
approach: phrases(),
|
|
43
|
+
dead_ends: phrases(),
|
|
44
|
+
verified_by: phrase(),
|
|
45
|
+
rule: phrase(),
|
|
46
|
+
applies_when: phrase(),
|
|
47
|
+
}).nullish(),
|
|
48
|
+
outcome: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()),
|
|
49
|
+
});
|
|
50
|
+
const saveSchema = zod_1.z.object({
|
|
51
|
+
key: zod_1.z.string().min(1).max(200),
|
|
52
|
+
card: exports.cardSchema,
|
|
53
|
+
});
|
|
54
|
+
/** Карточка целиком — в пределах разумного: в ней нет ни кода, ни вывода команд. */
|
|
55
|
+
exports.MAX_CARD = 64_000;
|
|
56
|
+
/** Сколько слов запроса идёт в поиск: вопрос бывает длинным, а смысл — в первых словах. */
|
|
57
|
+
const MAX_QUERY_WORDS = 64;
|
|
58
|
+
/**
|
|
59
|
+
* База знаний: карточки опыта агентов. Пишет мост после каждого запуска, читают агенты —
|
|
60
|
+
* мост кладёт похожие уроки в задание, агент ищет сам через find-knowledge. В поиск идут
|
|
61
|
+
* только карточки с уроком: факты без вывода агенту ничего не подскажут.
|
|
62
|
+
*/
|
|
63
|
+
let KnowledgeService = class KnowledgeService {
|
|
64
|
+
db;
|
|
65
|
+
constructor(db) {
|
|
66
|
+
this.db = db;
|
|
67
|
+
}
|
|
68
|
+
/** Сохранить карточку запуска; та же запись запуска — та же карточка. */
|
|
69
|
+
async save(chat, me, input) {
|
|
70
|
+
const { key, card } = (0, validation_1.validate)(saveSchema, input);
|
|
71
|
+
if (JSON.stringify(card).length > exports.MAX_CARD)
|
|
72
|
+
throw errors_1.ValidationFailed.field('card', 'Карточка слишком большая.');
|
|
73
|
+
const values = { card, format: card.format, search: searchText(card) };
|
|
74
|
+
const [existing] = await this.db.select().from(schema_1.knowledgeCards).where((0, drizzle_orm_1.eq)(schema_1.knowledgeCards.key, key));
|
|
75
|
+
if (existing && (existing.participantId !== me.id || existing.chatId !== chat.id)) {
|
|
76
|
+
throw (0, errors_1.conflict)('key_taken', 'Эта запись запуска уже принадлежит другой карточке.');
|
|
77
|
+
}
|
|
78
|
+
const [row] = existing
|
|
79
|
+
? await this.db.update(schema_1.knowledgeCards).set({ ...values, revision: (0, drizzle_orm_1.sql) `${schema_1.knowledgeCards.revision} + 1` })
|
|
80
|
+
.where((0, drizzle_orm_1.eq)(schema_1.knowledgeCards.id, existing.id)).returning()
|
|
81
|
+
: await this.db.insert(schema_1.knowledgeCards).values({ ...values, key, chatId: chat.id, participantId: me.id }).returning();
|
|
82
|
+
return { card: lessonApi(row, chat.slug), created: !existing };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Уроки, похожие на текст запроса. Ищется по всем чатам — урок обобщён и годится в
|
|
86
|
+
* другом проекте, — но свой проект выше. Совпадение по одному слову из многих — шум:
|
|
87
|
+
* нужно хотя бы два общих слова (или все, если в запросе одно).
|
|
88
|
+
*/
|
|
89
|
+
async search(query, { chat, limit = 5, category } = {}) {
|
|
90
|
+
const words = await this.words(query);
|
|
91
|
+
if (words.length === 0)
|
|
92
|
+
return [];
|
|
93
|
+
const tsquery = words.map(w => `'${w.replace(/\\/g, '\\\\').replace(/'/g, "''")}'`).join(' | ');
|
|
94
|
+
const vector = (0, drizzle_orm_1.sql) `to_tsvector('russian', ${schema_1.knowledgeCards.search})`;
|
|
95
|
+
const wanted = (0, drizzle_orm_1.sql) `to_tsquery('simple', ${tsquery})`;
|
|
96
|
+
const common = (0, drizzle_orm_1.sql) `cardinality(array(select unnest(tsvector_to_array(${vector})) intersect select unnest(string_to_array(${words.join(' ')}, ' '))))`; // в лексемах пробелов нет
|
|
97
|
+
const ownChat = chat ? (0, drizzle_orm_1.sql) `case when ${schema_1.knowledgeCards.chatId} = ${chat.id} then 1.5 else 1 end` : (0, drizzle_orm_1.sql) `1`;
|
|
98
|
+
const rows = await this.db.select({ row: schema_1.knowledgeCards, chat: schema_1.chats.slug }).from(schema_1.knowledgeCards)
|
|
99
|
+
.innerJoin(schema_1.chats, (0, drizzle_orm_1.eq)(schema_1.chats.id, schema_1.knowledgeCards.chatId))
|
|
100
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.isNotNull)(schema_1.knowledgeCards.search), ofCategory(category), (0, drizzle_orm_1.sql) `${vector} @@ ${wanted}`, (0, drizzle_orm_1.sql) `${common} >= ${Math.min(2, words.length)}`))
|
|
101
|
+
.orderBy((0, drizzle_orm_1.sql) `ts_rank(${vector}, ${wanted}) * ${ownChat} desc`, (0, drizzle_orm_1.desc)(schema_1.knowledgeCards.id))
|
|
102
|
+
.limit(clamp(limit, 1, 20));
|
|
103
|
+
return rows.map(r => lessonApi(r.row, r.chat));
|
|
104
|
+
}
|
|
105
|
+
/** Последние уроки проекта — для выгрузки KNOWLEDGE.md и просмотра. */
|
|
106
|
+
async lessons(chat, { limit = 500, category } = {}) {
|
|
107
|
+
const rows = await this.db.select().from(schema_1.knowledgeCards)
|
|
108
|
+
.where((0, drizzle_orm_1.and)((0, drizzle_orm_1.eq)(schema_1.knowledgeCards.chatId, chat.id), (0, drizzle_orm_1.isNotNull)(schema_1.knowledgeCards.search), ofCategory(category)))
|
|
109
|
+
.orderBy((0, drizzle_orm_1.desc)(schema_1.knowledgeCards.id)).limit(limit);
|
|
110
|
+
return rows.map(r => lessonApi(r, chat.slug));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Заменить карточку целиком — правка владельцем машины (служебные пути): убрать из урока
|
|
114
|
+
* то, чему там не место. Ключ и автор остаются, ревизия растёт — правка уйдёт и сборщику.
|
|
115
|
+
*/
|
|
116
|
+
async replace(id, input) {
|
|
117
|
+
const card = (0, validation_1.validate)(exports.cardSchema, input);
|
|
118
|
+
if (JSON.stringify(card).length > exports.MAX_CARD)
|
|
119
|
+
throw errors_1.ValidationFailed.field('card', 'Карточка слишком большая.');
|
|
120
|
+
const [row] = await this.db.update(schema_1.knowledgeCards)
|
|
121
|
+
.set({ card, format: card.format, search: searchText(card), revision: (0, drizzle_orm_1.sql) `${schema_1.knowledgeCards.revision} + 1` })
|
|
122
|
+
.where((0, drizzle_orm_1.eq)(schema_1.knowledgeCards.id, id)).returning();
|
|
123
|
+
if (!row)
|
|
124
|
+
throw (0, errors_1.notFound)();
|
|
125
|
+
const [chat] = await this.db.select({ slug: schema_1.chats.slug }).from(schema_1.chats).where((0, drizzle_orm_1.eq)(schema_1.chats.id, row.chatId));
|
|
126
|
+
return lessonApi(row, chat.slug);
|
|
127
|
+
}
|
|
128
|
+
/** Удалить карточку может только владелец: опыт команды — его данные. */
|
|
129
|
+
async remove(me, id) {
|
|
130
|
+
if (!(0, participants_service_1.isOwner)(me))
|
|
131
|
+
throw (0, errors_1.forbidden)('Удалять опыт может только владелец.');
|
|
132
|
+
const [row] = await this.db.delete(schema_1.knowledgeCards).where((0, drizzle_orm_1.eq)(schema_1.knowledgeCards.id, id)).returning({ id: schema_1.knowledgeCards.id });
|
|
133
|
+
if (!row)
|
|
134
|
+
throw (0, errors_1.notFound)();
|
|
135
|
+
return { deleted: row.id };
|
|
136
|
+
}
|
|
137
|
+
/** Слова запроса так, как их видит поиск: основы без окончаний, без служебных слов. */
|
|
138
|
+
async words(query) {
|
|
139
|
+
const text = query.replace(/@[\w-]+/g, ' ').replace(/https?:\/\/\S+/g, ' ').slice(0, 4000);
|
|
140
|
+
if (!text.trim())
|
|
141
|
+
return [];
|
|
142
|
+
const [{ words }] = await this.db.execute((0, drizzle_orm_1.sql) `select tsvector_to_array(to_tsvector('russian', ${text})) as words`)
|
|
143
|
+
.then(r => r.rows);
|
|
144
|
+
return (words ?? []).slice(0, MAX_QUERY_WORDS);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
exports.KnowledgeService = KnowledgeService;
|
|
148
|
+
exports.KnowledgeService = KnowledgeService = __decorate([
|
|
149
|
+
(0, common_1.Injectable)(),
|
|
150
|
+
__param(0, (0, common_1.Inject)(database_module_1.DATABASE)),
|
|
151
|
+
__metadata("design:paramtypes", [Object])
|
|
152
|
+
], KnowledgeService);
|
|
153
|
+
function ofCategory(category) {
|
|
154
|
+
return category ? (0, drizzle_orm_1.sql) `${schema_1.knowledgeCards.card} -> 'problem' ->> 'category' = ${category}` : undefined;
|
|
155
|
+
}
|
|
156
|
+
/** Текст, по которому ищется урок: сам урок и стек. Без правила урока нет — и искать нечего. */
|
|
157
|
+
function searchText(card) {
|
|
158
|
+
const { problem, solution } = card;
|
|
159
|
+
if (!problem?.symptom || !solution?.rule)
|
|
160
|
+
return null;
|
|
161
|
+
return [
|
|
162
|
+
problem.category, problem.area, problem.symptom, problem.root_cause,
|
|
163
|
+
solution.rule, solution.applies_when, ...solution.approach, ...stackNames(card.stack),
|
|
164
|
+
].filter(Boolean).join('\n');
|
|
165
|
+
}
|
|
166
|
+
/** Имена технологий стека: «Node.js 22», «NestJS 11». */
|
|
167
|
+
function stackNames(stack) {
|
|
168
|
+
const named = (list) => (Array.isArray(list) ? list : [])
|
|
169
|
+
.map(x => (typeof x === 'string' ? x : [x?.name, x?.version].filter(Boolean).join(' ')))
|
|
170
|
+
.filter(Boolean);
|
|
171
|
+
return [...named(stack.languages), ...named(stack.frameworks), ...named(stack.databases), ...named(stack.infra), ...named(stack.tests)];
|
|
172
|
+
}
|
|
173
|
+
/** Урок так, как его видят агенты и выгрузка: без счётчиков хода работы. */
|
|
174
|
+
function lessonApi(row, chat) {
|
|
175
|
+
const card = row.card;
|
|
176
|
+
const process = card.process;
|
|
177
|
+
return {
|
|
178
|
+
id: row.id,
|
|
179
|
+
chat,
|
|
180
|
+
problem: card.problem ?? null,
|
|
181
|
+
solution: card.solution ?? null,
|
|
182
|
+
stack: stackNames(card.stack),
|
|
183
|
+
engine: process.engine ?? null,
|
|
184
|
+
at: (0, format_1.iso)(row.createdAt),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function clamp(value, min, max) {
|
|
188
|
+
return Math.max(min, Math.min(max, Number.isFinite(value) ? Math.trunc(value) : min));
|
|
189
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.DocsSyncService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
18
|
+
const zod_1 = require("zod");
|
|
19
|
+
const errors_1 = require("../common/errors");
|
|
20
|
+
const validation_1 = require("../common/validation");
|
|
21
|
+
const chats_service_1 = require("../chats/chats.service");
|
|
22
|
+
const database_module_1 = require("../db/database.module");
|
|
23
|
+
const schema_1 = require("../db/schema");
|
|
24
|
+
const decisions_service_1 = require("../decisions/decisions.service");
|
|
25
|
+
const documents_service_1 = require("../documents/documents.service");
|
|
26
|
+
const knowledge_service_1 = require("../knowledge/knowledge.service");
|
|
27
|
+
const participants_service_1 = require("../participants/participants.service");
|
|
28
|
+
const importSchema = zod_1.z.object({
|
|
29
|
+
chat: zod_1.z.string().nullish(),
|
|
30
|
+
documents: zod_1.z.array(zod_1.z.object({
|
|
31
|
+
slug: zod_1.z.string().max(200),
|
|
32
|
+
title: zod_1.z.string().max(300),
|
|
33
|
+
kind: zod_1.z.enum(documents_service_1.KINDS),
|
|
34
|
+
origin: zod_1.z.string().max(300),
|
|
35
|
+
body: zod_1.z.string(),
|
|
36
|
+
})),
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Документы и решения ↔ репозитории. Файлы читает и пишет CLI на машине с репозиториями;
|
|
40
|
+
* сервер только отдаёт подтверждённое и принимает импортированное — к файловой системе
|
|
41
|
+
* чужих репозиториев он не прикасается.
|
|
42
|
+
*/
|
|
43
|
+
let DocsSyncService = class DocsSyncService {
|
|
44
|
+
db;
|
|
45
|
+
chats;
|
|
46
|
+
documents;
|
|
47
|
+
decisions;
|
|
48
|
+
knowledge;
|
|
49
|
+
constructor(db, chats, documents, decisions, knowledge) {
|
|
50
|
+
this.db = db;
|
|
51
|
+
this.chats = chats;
|
|
52
|
+
this.documents = documents;
|
|
53
|
+
this.decisions = decisions;
|
|
54
|
+
this.knowledge = knowledge;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Для выгрузки наружу уходит ТОЛЬКО current — то, что владелец подтвердил. Уроки агентов —
|
|
58
|
+
* для KNOWLEDGE.md: в них нет кода, только обобщённый опыт.
|
|
59
|
+
*/
|
|
60
|
+
async exportData(chatSlug) {
|
|
61
|
+
const chat = await this.chats.resolve(chatSlug);
|
|
62
|
+
const current = await this.documents.list(chat, { status: 'current' });
|
|
63
|
+
const documents = [];
|
|
64
|
+
for (const d of current)
|
|
65
|
+
documents.push(await this.documents.read(chat, String(d.slug)));
|
|
66
|
+
return {
|
|
67
|
+
chat: { slug: chat.slug, name: chat.name, workdir: chat.workdir },
|
|
68
|
+
documents,
|
|
69
|
+
decisions: await this.decisions.list(chat),
|
|
70
|
+
knowledge: await this.knowledge.lessons(chat),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Импорт .md из репозиториев: приезжает черновиком — актуальность подтверждает человек,
|
|
75
|
+
* иначе протухшие аудиты снова станут неотличимы от живых.
|
|
76
|
+
*/
|
|
77
|
+
async importDocuments(input) {
|
|
78
|
+
const data = (0, validation_1.validate)(importSchema, input);
|
|
79
|
+
const chat = await this.chats.resolve(data.chat ?? undefined);
|
|
80
|
+
const [owner] = await this.db.select().from(schema_1.participants).where((0, drizzle_orm_1.eq)(schema_1.participants.role, participants_service_1.ROLE_OWNER)).orderBy((0, drizzle_orm_1.asc)(schema_1.participants.id)).limit(1);
|
|
81
|
+
if (!owner)
|
|
82
|
+
throw errors_1.ValidationFailed.field('chat', 'Владельца ещё нет — сначала team-chat start.');
|
|
83
|
+
for (const d of data.documents) {
|
|
84
|
+
await this.documents.put(chat, owner, { ...d, status: 'draft', note: 'импорт из репозитория' });
|
|
85
|
+
}
|
|
86
|
+
return { chat: chat.slug, imported: data.documents.length };
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
exports.DocsSyncService = DocsSyncService;
|
|
90
|
+
exports.DocsSyncService = DocsSyncService = __decorate([
|
|
91
|
+
(0, common_1.Injectable)(),
|
|
92
|
+
__param(0, (0, common_1.Inject)(database_module_1.DATABASE)),
|
|
93
|
+
__metadata("design:paramtypes", [Object, chats_service_1.ChatsService,
|
|
94
|
+
documents_service_1.DocumentsService,
|
|
95
|
+
decisions_service_1.DecisionsService,
|
|
96
|
+
knowledge_service_1.KnowledgeService])
|
|
97
|
+
], DocsSyncService);
|