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.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +458 -2
  3. package/agents/rules.md +52 -0
  4. package/agents/runtimes/codex.md +14 -0
  5. package/bin/bridge +3 -0
  6. package/bin/runner +3 -0
  7. package/cli/lib/agents.js +97 -0
  8. package/cli/lib/docs.js +151 -0
  9. package/cli/lib/local.js +41 -0
  10. package/cli/lib/server.js +52 -0
  11. package/cli/lib/util.js +107 -0
  12. package/cli/team-chat.js +383 -0
  13. package/host/bridge/attempts.js +43 -0
  14. package/host/bridge/bridge.js +326 -0
  15. package/host/bridge/config.js +105 -0
  16. package/host/bridge/knowledge-outbox.js +96 -0
  17. package/host/bridge/launcher.js +126 -0
  18. package/host/bridge/main.js +87 -0
  19. package/host/bridge/progress.js +74 -0
  20. package/host/bridge/prompt.js +97 -0
  21. package/host/bridge/run-archive.js +200 -0
  22. package/host/bridge/thread.js +44 -0
  23. package/host/bridge/triage.js +97 -0
  24. package/host/bridge/workdir.js +41 -0
  25. package/host/knowledge/card.js +43 -0
  26. package/host/knowledge/errors.js +50 -0
  27. package/host/knowledge/lesson.js +153 -0
  28. package/host/knowledge/process.js +160 -0
  29. package/host/knowledge/scrub.js +29 -0
  30. package/host/knowledge/shell.js +74 -0
  31. package/host/knowledge/stack.js +229 -0
  32. package/host/knowledge/transcript.js +82 -0
  33. package/host/lib/chat-api.js +120 -0
  34. package/host/lib/log.js +6 -0
  35. package/host/lib/pid-lock.js +65 -0
  36. package/host/lib/shell-words.js +45 -0
  37. package/host/runner/bridge-process.js +82 -0
  38. package/host/runner/config.js +81 -0
  39. package/host/runner/main.js +69 -0
  40. package/host/runner/retention.js +40 -0
  41. package/host/runner/runner.js +161 -0
  42. package/package.json +67 -5
  43. package/server/dist/src/app.js +25 -0
  44. package/server/dist/src/app.module.js +50 -0
  45. package/server/dist/src/attachments/attachment-store.js +111 -0
  46. package/server/dist/src/attachments/attachments.controller.js +67 -0
  47. package/server/dist/src/attachments/mime.js +84 -0
  48. package/server/dist/src/attachments/uploads.interceptor.js +72 -0
  49. package/server/dist/src/auth/participant.guard.js +53 -0
  50. package/server/dist/src/bridges/bridge-restart.service.js +80 -0
  51. package/server/dist/src/bridges/bridges.controller.js +47 -0
  52. package/server/dist/src/chats/chats.controller.js +83 -0
  53. package/server/dist/src/chats/chats.service.js +128 -0
  54. package/server/dist/src/collector/collector.controller.js +71 -0
  55. package/server/dist/src/collector/collector.module.js +23 -0
  56. package/server/dist/src/collector/collector.service.js +111 -0
  57. package/server/dist/src/common/clock.js +20 -0
  58. package/server/dist/src/common/errors.js +37 -0
  59. package/server/dist/src/common/format.js +14 -0
  60. package/server/dist/src/common/http.js +17 -0
  61. package/server/dist/src/common/slug.js +28 -0
  62. package/server/dist/src/common/tokens.js +19 -0
  63. package/server/dist/src/common/validation.js +41 -0
  64. package/server/dist/src/config/config.js +39 -0
  65. package/server/dist/src/conversation.module.js +41 -0
  66. package/server/dist/src/db/database.js +73 -0
  67. package/server/dist/src/db/database.module.js +51 -0
  68. package/server/dist/src/db/schema.js +212 -0
  69. package/server/dist/src/decisions/decisions.controller.js +55 -0
  70. package/server/dist/src/decisions/decisions.service.js +109 -0
  71. package/server/dist/src/documents/documents.controller.js +87 -0
  72. package/server/dist/src/documents/documents.service.js +189 -0
  73. package/server/dist/src/health/health.controller.js +29 -0
  74. package/server/dist/src/knowledge/knowledge.controller.js +83 -0
  75. package/server/dist/src/knowledge/knowledge.module.js +25 -0
  76. package/server/dist/src/knowledge/knowledge.service.js +189 -0
  77. package/server/dist/src/local/docs-sync.service.js +97 -0
  78. package/server/dist/src/local/local.controller.js +197 -0
  79. package/server/dist/src/local/local.guard.js +55 -0
  80. package/server/dist/src/local/local.module.js +30 -0
  81. package/server/dist/src/local/setup.service.js +154 -0
  82. package/server/dist/src/local/transfer.service.js +151 -0
  83. package/server/dist/src/main.js +33 -0
  84. package/server/dist/src/mcp/chat-tools.js +282 -0
  85. package/server/dist/src/mcp/mcp.controller.js +58 -0
  86. package/server/dist/src/mcp/mcp.module.js +24 -0
  87. package/server/dist/src/mentions/mention-state.js +80 -0
  88. package/server/dist/src/mentions/mentions.controller.js +100 -0
  89. package/server/dist/src/mentions/mentions.service.js +203 -0
  90. package/server/dist/src/messages/message.presenter.js +102 -0
  91. package/server/dist/src/messages/messages.service.js +149 -0
  92. package/server/dist/src/messages/thread.controller.js +179 -0
  93. package/server/dist/src/participants/directory.service.js +68 -0
  94. package/server/dist/src/participants/participants.module.js +24 -0
  95. package/server/dist/src/participants/participants.service.js +84 -0
  96. package/server/dist/src/settings/settings.service.js +42 -0
  97. package/server/dist/src/sharing/consent.js +16 -0
  98. package/server/dist/src/sharing/sharing.module.js +22 -0
  99. package/server/dist/src/sharing/sharing.service.js +175 -0
  100. package/server/dist/src/team/runner.service.js +75 -0
  101. package/server/dist/src/team/team.controller.js +178 -0
  102. package/server/dist/src/team/team.module.js +25 -0
  103. package/server/dist/src/team/team.service.js +280 -0
  104. package/server/dist/src/web/template.js +57 -0
  105. package/server/dist/src/web/web-login.service.js +74 -0
  106. package/server/dist/src/web/web.controller.js +203 -0
  107. package/server/dist/src/web/web.module.js +25 -0
  108. package/server/drizzle/0000_init.sql +169 -0
  109. package/server/drizzle/0001_seed_roles.sql +25 -0
  110. package/server/drizzle/0002_knowledge.sql +17 -0
  111. package/server/drizzle/0003_sharing.sql +30 -0
  112. package/server/drizzle/meta/0000_snapshot.json +1224 -0
  113. package/server/drizzle/meta/0001_snapshot.json +1224 -0
  114. package/server/drizzle/meta/0002_snapshot.json +1359 -0
  115. package/server/drizzle/meta/0003_snapshot.json +1545 -0
  116. package/server/drizzle/meta/_journal.json +34 -0
  117. package/server/package.json +1 -0
  118. package/server/views/chat.html +865 -0
  119. package/server/views/documents.html +204 -0
  120. package/server/views/login.html +31 -0
  121. package/server/views/setup.html +60 -0
  122. package/server/views/team.html +369 -0
@@ -0,0 +1,197 @@
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.LocalController = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const collector_service_1 = require("../collector/collector.service");
18
+ const knowledge_service_1 = require("../knowledge/knowledge.service");
19
+ const sharing_service_1 = require("../sharing/sharing.service");
20
+ const docs_sync_service_1 = require("./docs-sync.service");
21
+ const local_guard_1 = require("./local.guard");
22
+ const setup_service_1 = require("./setup.service");
23
+ const transfer_service_1 = require("./transfer.service");
24
+ /** Служебные пути для CLI этой машины: то, что раньше делал `php artisan`. */
25
+ let LocalController = class LocalController {
26
+ setupService;
27
+ transfer;
28
+ docs;
29
+ sharing;
30
+ collector;
31
+ knowledge;
32
+ constructor(setupService, transfer, docs, sharing, collector, knowledge) {
33
+ this.setupService = setupService;
34
+ this.transfer = transfer;
35
+ this.docs = docs;
36
+ this.sharing = sharing;
37
+ this.collector = collector;
38
+ this.knowledge = knowledge;
39
+ }
40
+ setup(body) {
41
+ return this.setupService.setup(body);
42
+ }
43
+ login(body) {
44
+ return this.setupService.loginLink(body);
45
+ }
46
+ participants() {
47
+ return this.setupService.list();
48
+ }
49
+ participant(body) {
50
+ return this.setupService.upsert(body);
51
+ }
52
+ dump() {
53
+ return this.transfer.dump();
54
+ }
55
+ restore(body) {
56
+ return this.transfer.restore(body);
57
+ }
58
+ exportDocs(chat) {
59
+ return this.docs.exportData(chat);
60
+ }
61
+ importDocs(body) {
62
+ return this.docs.importDocuments(body);
63
+ }
64
+ // --- опыт агентов: правка карточки владельцем машины, согласие на отправку автору пакета ---
65
+ replaceCard(id, body) {
66
+ return this.knowledge.replace(id, body);
67
+ }
68
+ consent() {
69
+ return this.sharing.status();
70
+ }
71
+ setConsent(body) {
72
+ return this.sharing.setConsent(body);
73
+ }
74
+ /** Отправить накопленное сейчас, не дожидаясь таймера. */
75
+ share() {
76
+ return this.sharing.share();
77
+ }
78
+ // --- сборщик: что собрано, удаление — только владельцем этой машины ---
79
+ collected() {
80
+ return this.collector.overview();
81
+ }
82
+ removeInstallation(id) {
83
+ return this.collector.removeInstallation(id);
84
+ }
85
+ };
86
+ exports.LocalController = LocalController;
87
+ __decorate([
88
+ (0, common_1.Post)('setup'),
89
+ (0, common_1.HttpCode)(200),
90
+ __param(0, (0, common_1.Body)()),
91
+ __metadata("design:type", Function),
92
+ __metadata("design:paramtypes", [Object]),
93
+ __metadata("design:returntype", void 0)
94
+ ], LocalController.prototype, "setup", null);
95
+ __decorate([
96
+ (0, common_1.Post)('login'),
97
+ (0, common_1.HttpCode)(200),
98
+ __param(0, (0, common_1.Body)()),
99
+ __metadata("design:type", Function),
100
+ __metadata("design:paramtypes", [Object]),
101
+ __metadata("design:returntype", void 0)
102
+ ], LocalController.prototype, "login", null);
103
+ __decorate([
104
+ (0, common_1.Get)('participants'),
105
+ __metadata("design:type", Function),
106
+ __metadata("design:paramtypes", []),
107
+ __metadata("design:returntype", void 0)
108
+ ], LocalController.prototype, "participants", null);
109
+ __decorate([
110
+ (0, common_1.Post)('participants'),
111
+ (0, common_1.HttpCode)(200),
112
+ __param(0, (0, common_1.Body)()),
113
+ __metadata("design:type", Function),
114
+ __metadata("design:paramtypes", [Object]),
115
+ __metadata("design:returntype", void 0)
116
+ ], LocalController.prototype, "participant", null);
117
+ __decorate([
118
+ (0, common_1.Get)('dump'),
119
+ __metadata("design:type", Function),
120
+ __metadata("design:paramtypes", []),
121
+ __metadata("design:returntype", void 0)
122
+ ], LocalController.prototype, "dump", null);
123
+ __decorate([
124
+ (0, common_1.Post)('restore'),
125
+ (0, common_1.HttpCode)(200),
126
+ __param(0, (0, common_1.Body)()),
127
+ __metadata("design:type", Function),
128
+ __metadata("design:paramtypes", [Object]),
129
+ __metadata("design:returntype", void 0)
130
+ ], LocalController.prototype, "restore", null);
131
+ __decorate([
132
+ (0, common_1.Get)('export'),
133
+ __param(0, (0, common_1.Query)('chat')),
134
+ __metadata("design:type", Function),
135
+ __metadata("design:paramtypes", [String]),
136
+ __metadata("design:returntype", void 0)
137
+ ], LocalController.prototype, "exportDocs", null);
138
+ __decorate([
139
+ (0, common_1.Post)('documents'),
140
+ (0, common_1.HttpCode)(200),
141
+ __param(0, (0, common_1.Body)()),
142
+ __metadata("design:type", Function),
143
+ __metadata("design:paramtypes", [Object]),
144
+ __metadata("design:returntype", void 0)
145
+ ], LocalController.prototype, "importDocs", null);
146
+ __decorate([
147
+ (0, common_1.Put)('knowledge/:id'),
148
+ __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
149
+ __param(1, (0, common_1.Body)()),
150
+ __metadata("design:type", Function),
151
+ __metadata("design:paramtypes", [Number, Object]),
152
+ __metadata("design:returntype", void 0)
153
+ ], LocalController.prototype, "replaceCard", null);
154
+ __decorate([
155
+ (0, common_1.Get)('consent'),
156
+ __metadata("design:type", Function),
157
+ __metadata("design:paramtypes", []),
158
+ __metadata("design:returntype", void 0)
159
+ ], LocalController.prototype, "consent", null);
160
+ __decorate([
161
+ (0, common_1.Post)('consent'),
162
+ (0, common_1.HttpCode)(200),
163
+ __param(0, (0, common_1.Body)()),
164
+ __metadata("design:type", Function),
165
+ __metadata("design:paramtypes", [Object]),
166
+ __metadata("design:returntype", void 0)
167
+ ], LocalController.prototype, "setConsent", null);
168
+ __decorate([
169
+ (0, common_1.Post)('share'),
170
+ (0, common_1.HttpCode)(200),
171
+ __metadata("design:type", Function),
172
+ __metadata("design:paramtypes", []),
173
+ __metadata("design:returntype", void 0)
174
+ ], LocalController.prototype, "share", null);
175
+ __decorate([
176
+ (0, common_1.Get)('collected'),
177
+ __metadata("design:type", Function),
178
+ __metadata("design:paramtypes", []),
179
+ __metadata("design:returntype", void 0)
180
+ ], LocalController.prototype, "collected", null);
181
+ __decorate([
182
+ (0, common_1.Delete)('collected/installations/:id'),
183
+ __param(0, (0, common_1.Param)('id', common_1.ParseIntPipe)),
184
+ __metadata("design:type", Function),
185
+ __metadata("design:paramtypes", [Number]),
186
+ __metadata("design:returntype", void 0)
187
+ ], LocalController.prototype, "removeInstallation", null);
188
+ exports.LocalController = LocalController = __decorate([
189
+ (0, common_1.Controller)('api/local'),
190
+ (0, common_1.UseGuards)(local_guard_1.LocalGuard),
191
+ __metadata("design:paramtypes", [setup_service_1.SetupService,
192
+ transfer_service_1.TransferService,
193
+ docs_sync_service_1.DocsSyncService,
194
+ sharing_service_1.SharingService,
195
+ collector_service_1.CollectorService,
196
+ knowledge_service_1.KnowledgeService])
197
+ ], LocalController);
@@ -0,0 +1,55 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
15
+ return (mod && mod.__esModule) ? mod : { "default": mod };
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.LocalGuard = exports.LOCAL_HEADER = void 0;
19
+ const common_1 = require("@nestjs/common");
20
+ const node_crypto_1 = __importDefault(require("node:crypto"));
21
+ const errors_1 = require("../common/errors");
22
+ const config_1 = require("../config/config");
23
+ exports.LOCAL_HEADER = 'x-local-secret';
24
+ /**
25
+ * Служебные пути для CLI на этой же машине. Пускает по секрету из конфига CLI
26
+ * (~/.team-chat/config.json, права 600): кто может его прочитать, тот и владелец машины —
27
+ * тот же принцип, что у прежнего `php artisan chat:login`. Сервер без секрета этих путей
28
+ * не показывает вовсе.
29
+ */
30
+ let LocalGuard = class LocalGuard {
31
+ config;
32
+ constructor(config) {
33
+ this.config = config;
34
+ }
35
+ canActivate(context) {
36
+ const secret = this.config.localSecret;
37
+ if (!secret)
38
+ throw (0, errors_1.notFound)();
39
+ const given = context.switchToHttp().getRequest().headers[exports.LOCAL_HEADER];
40
+ if (typeof given !== 'string' || !sameSecret(given, secret))
41
+ throw (0, errors_1.forbidden)('Нужен секрет CLI этой машины.');
42
+ return true;
43
+ }
44
+ };
45
+ exports.LocalGuard = LocalGuard;
46
+ exports.LocalGuard = LocalGuard = __decorate([
47
+ (0, common_1.Injectable)(),
48
+ __param(0, (0, common_1.Inject)(config_1.SERVER_CONFIG)),
49
+ __metadata("design:paramtypes", [Object])
50
+ ], LocalGuard);
51
+ function sameSecret(a, b) {
52
+ const x = Buffer.from(a);
53
+ const y = Buffer.from(b);
54
+ return x.length === y.length && node_crypto_1.default.timingSafeEqual(x, y);
55
+ }
@@ -0,0 +1,30 @@
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.LocalModule = void 0;
10
+ const common_1 = require("@nestjs/common");
11
+ const conversation_module_1 = require("../conversation.module");
12
+ const collector_module_1 = require("../collector/collector.module");
13
+ const knowledge_module_1 = require("../knowledge/knowledge.module");
14
+ const sharing_module_1 = require("../sharing/sharing.module");
15
+ const web_module_1 = require("../web/web.module");
16
+ const docs_sync_service_1 = require("./docs-sync.service");
17
+ const local_controller_1 = require("./local.controller");
18
+ const local_guard_1 = require("./local.guard");
19
+ const setup_service_1 = require("./setup.service");
20
+ const transfer_service_1 = require("./transfer.service");
21
+ let LocalModule = class LocalModule {
22
+ };
23
+ exports.LocalModule = LocalModule;
24
+ exports.LocalModule = LocalModule = __decorate([
25
+ (0, common_1.Module)({
26
+ imports: [conversation_module_1.ConversationModule, knowledge_module_1.KnowledgeModule, sharing_module_1.SharingModule, collector_module_1.CollectorModule, web_module_1.WebModule],
27
+ providers: [local_guard_1.LocalGuard, setup_service_1.SetupService, transfer_service_1.TransferService, docs_sync_service_1.DocsSyncService],
28
+ controllers: [local_controller_1.LocalController],
29
+ })
30
+ ], LocalModule);
@@ -0,0 +1,154 @@
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.SetupService = 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 slug_1 = require("../common/slug");
22
+ const validation_1 = require("../common/validation");
23
+ const database_module_1 = require("../db/database.module");
24
+ const schema_1 = require("../db/schema");
25
+ const participants_service_1 = require("../participants/participants.service");
26
+ const web_login_service_1 = require("../web/web-login.service");
27
+ const setupSchema = zod_1.z.object({
28
+ name: zod_1.z.string().max(60).nullish(),
29
+ handle: zod_1.z.string().nullish(),
30
+ rotate_host: (0, validation_1.flag)().optional(),
31
+ // имя:движок:роль — агенты под движки, найденные на машине
32
+ agents: zod_1.z.array(zod_1.z.string()).optional(),
33
+ });
34
+ const participantSchema = zod_1.z.object({
35
+ handle: zod_1.z.string().regex(participants_service_1.HANDLE_PATTERN, { error: 'Имя — латиница, цифры, дефис и подчёркивание, от 2 до 40 знаков.' }),
36
+ name: zod_1.z.string().max(60).nullish(),
37
+ role: zod_1.z.string().nullish(),
38
+ kind: zod_1.z.enum(['human', 'ai', 'host']).nullish(),
39
+ accent: zod_1.z.string().regex(/^#[0-9a-fA-F]{6}$/).nullish(),
40
+ reissue: (0, validation_1.flag)().optional(),
41
+ });
42
+ /** Первый запуск, вход и участники — то, что делали `chat:setup`, `chat:login`, `chat:participant`. */
43
+ let SetupService = class SetupService {
44
+ db;
45
+ participants;
46
+ login;
47
+ constructor(db, participants, login) {
48
+ this.db = db;
49
+ this.participants = participants;
50
+ this.login = login;
51
+ }
52
+ /**
53
+ * Владелец, раннер и агенты под найденные движки. Повторный запуск ничего не ломает —
54
+ * заводит недостающее. Токен раннера отдаётся, только когда выпущен: у нас — один хеш.
55
+ */
56
+ async setup(input) {
57
+ const data = (0, validation_1.validate)(setupSchema, input);
58
+ const agents = (data.agents ?? []).map(spec => this.parseAgent(spec));
59
+ for (const agent of agents)
60
+ await this.requireRole(agent.role, agent.spec);
61
+ const owner = await this.owner() ?? await this.createOwner(data.name || 'Владелец', data.handle);
62
+ let hostToken = null;
63
+ const [host] = await this.db.select().from(schema_1.participants).where((0, drizzle_orm_1.eq)(schema_1.participants.kind, participants_service_1.KIND_HOST)).orderBy((0, drizzle_orm_1.asc)(schema_1.participants.id)).limit(1);
64
+ if (!host) {
65
+ const [created] = await this.db.insert(schema_1.participants).values({ name: 'Раннер', handle: 'host', kind: participants_service_1.KIND_HOST, role: participants_service_1.ROLE_HOST }).returning();
66
+ hostToken = await this.participants.issueToken(created.id);
67
+ }
68
+ else if (data.rotate_host) {
69
+ hostToken = await this.participants.issueToken(host.id);
70
+ }
71
+ const created = [];
72
+ for (const { handle, runtime, role } of agents) {
73
+ // Есть — не трогаем: роль и движок мог поменять владелец в интерфейсе.
74
+ if (await this.byHandle(handle))
75
+ continue;
76
+ await this.db.insert(schema_1.participants).values({ handle, name: handle[0].toUpperCase() + handle.slice(1), kind: 'ai', role, runtime });
77
+ created.push(handle);
78
+ }
79
+ return { owner: owner.handle, agents: created, login_url: await this.login.link(owner), host_token: hostToken };
80
+ }
81
+ /** Одноразовая ссылка входа — владельцу или названному человеку. */
82
+ async loginLink(input) {
83
+ const { handle } = (0, validation_1.validate)(zod_1.z.object({ handle: zod_1.z.string().nullish() }), input);
84
+ const who = handle ? await this.byHandle(handle.replace(/^@/, '')) : await this.owner();
85
+ if (!who || who.kind !== 'human') {
86
+ throw errors_1.ValidationFailed.field('handle', handle ? 'Такого человека среди участников нет.' : 'Владельца ещё нет — сначала team-chat start.');
87
+ }
88
+ return { url: await this.login.link(who) };
89
+ }
90
+ async list() {
91
+ const all = await this.db.select().from(schema_1.participants).orderBy((0, drizzle_orm_1.asc)(schema_1.participants.id));
92
+ return {
93
+ participants: all.map(p => ({
94
+ handle: p.handle, name: p.name, role: p.role, kind: p.kind, token: p.tokenHash !== null, last_seen_at: (0, format_1.iso)(p.lastSeenAt),
95
+ })),
96
+ };
97
+ }
98
+ /**
99
+ * Завести участника или обновить. Токен — новому, а существующему только по reissue:
100
+ * случайный повторный вызов не должен отключить работающего агента. Умолчания — только
101
+ * новому, иначе перевыпуск сбросил бы владельцу роль.
102
+ */
103
+ async upsert(input) {
104
+ const data = (0, validation_1.validate)(participantSchema, input);
105
+ const existing = await this.byHandle(data.handle);
106
+ const values = {
107
+ name: data.name || existing?.name || data.handle,
108
+ role: data.role || existing?.role || 'developer',
109
+ kind: data.kind || existing?.kind || 'ai',
110
+ accent: data.accent || existing?.accent || '#0057c2',
111
+ };
112
+ const [participant] = existing
113
+ ? await this.db.update(schema_1.participants).set(values).where((0, drizzle_orm_1.eq)(schema_1.participants.id, existing.id)).returning()
114
+ : await this.db.insert(schema_1.participants).values({ handle: data.handle, ...values }).returning();
115
+ const token = !existing || data.reissue ? await this.participants.issueToken(participant.id) : null;
116
+ return { handle: participant.handle, created: !existing, token };
117
+ }
118
+ async createOwner(name, wanted) {
119
+ let handle = wanted || (0, slug_1.slugify)(name) || 'owner';
120
+ if (!participants_service_1.HANDLE_PATTERN.test(handle) || await this.byHandle(handle))
121
+ handle = 'owner';
122
+ const [owner] = await this.db.insert(schema_1.participants).values({ name, handle, kind: 'human', role: participants_service_1.ROLE_OWNER, accent: '#0f766e' }).returning();
123
+ return owner;
124
+ }
125
+ async owner() {
126
+ 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);
127
+ return owner ?? null;
128
+ }
129
+ async byHandle(handle) {
130
+ const [p] = await this.db.select().from(schema_1.participants).where((0, drizzle_orm_1.eq)(schema_1.participants.handle, handle));
131
+ return p ?? null;
132
+ }
133
+ parseAgent(spec) {
134
+ const [handle, runtime, role] = spec.split(':', 3);
135
+ if (!handle || !runtime || !role || !participants_service_1.HANDLE_PATTERN.test(handle))
136
+ throw this.badAgent(spec);
137
+ return { spec, handle, runtime, role };
138
+ }
139
+ async requireRole(slug, spec) {
140
+ const [role] = await this.db.select({ id: schema_1.roles.id }).from(schema_1.roles).where((0, drizzle_orm_1.eq)(schema_1.roles.slug, slug));
141
+ if (!role)
142
+ throw this.badAgent(spec);
143
+ }
144
+ badAgent(spec) {
145
+ return errors_1.ValidationFailed.field('agents', `Не понял агента «${spec}»: нужно имя:движок:роль, и роль должна существовать.`);
146
+ }
147
+ };
148
+ exports.SetupService = SetupService;
149
+ exports.SetupService = SetupService = __decorate([
150
+ (0, common_1.Injectable)(),
151
+ __param(0, (0, common_1.Inject)(database_module_1.DATABASE)),
152
+ __metadata("design:paramtypes", [Object, participants_service_1.ParticipantsService,
153
+ web_login_service_1.WebLoginService])
154
+ ], SetupService);
@@ -0,0 +1,151 @@
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.TransferService = exports.TABLES = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const drizzle_orm_1 = require("drizzle-orm");
18
+ const clock_1 = require("../common/clock");
19
+ const errors_1 = require("../common/errors");
20
+ const format_1 = require("../common/format");
21
+ const database_module_1 = require("../db/database.module");
22
+ /**
23
+ * Что переносится, и в каком порядке: родители раньше ссылок на них. Тот же состав, что
24
+ * у `chat:dump` PHP-сервера, — дамп оттуда восстанавливается здесь как есть. Токены
25
+ * раннеров и браузеров не переносятся: раннер возьмёт свои, браузер войдёт заново.
26
+ */
27
+ exports.TABLES = [
28
+ 'roles', 'participants', 'chats', 'chat_agent_roles', 'messages', 'attachments', 'mentions',
29
+ 'decisions', 'documents', 'document_versions', 'knowledge_cards',
30
+ ];
31
+ /** Не переносятся, но после восстановления чистятся: они ссылаются на прежних участников. */
32
+ const DERIVED = ['participant_tokens', 'login_codes'];
33
+ /**
34
+ * Ссылки на свою же таблицу: строка может ссылаться на ту, что в дампе идёт позже (агент —
35
+ * на раннер, заведённый после него). Такие столбцы ставятся вторым проходом, когда все
36
+ * строки таблицы уже на месте. SQLite у PHP-сервера внешних ключей не проверял.
37
+ */
38
+ const SELF_REFERENCES = {
39
+ participants: ['host_id'],
40
+ messages: ['reply_to_id'],
41
+ decisions: ['supersedes_id'],
42
+ };
43
+ /** Таблицы, где у строк есть чат: в дампе времён одного чата его нет. */
44
+ const CHAT_SCOPED = new Set(['messages', 'decisions', 'documents']);
45
+ /**
46
+ * Перенос состояния чата между машинами и между PHP- и Node-сервером. Это перенос,
47
+ * а не синхронизация: восстановление заменяет содержимое целиком.
48
+ */
49
+ let TransferService = class TransferService {
50
+ db;
51
+ constructor(db) {
52
+ this.db = db;
53
+ }
54
+ async dump() {
55
+ const tables = {};
56
+ for (const table of exports.TABLES) {
57
+ const rows = await this.rows(`select * from "${table}" order by id`);
58
+ tables[table] = rows.map(row => Object.fromEntries(Object.entries(row).map(([k, v]) => [k, v instanceof Date ? (0, format_1.iso)(v) : v])));
59
+ }
60
+ return { exported_at: (0, format_1.iso)((0, clock_1.now)()), tables };
61
+ }
62
+ async restore(input) {
63
+ const tables = input?.tables;
64
+ if (!tables || typeof tables !== 'object')
65
+ throw errors_1.ValidationFailed.field('tables', 'Файл не похож на дамп чата.');
66
+ const unknown = Object.keys(tables).filter(t => !exports.TABLES.includes(t));
67
+ if (unknown.length)
68
+ throw errors_1.ValidationFailed.field('tables', `В дампе незнакомые таблицы: ${unknown.join(', ')}.`);
69
+ const counts = {};
70
+ await this.db.transaction(async (tx) => {
71
+ const run = (query) => tx.execute(drizzle_orm_1.sql.raw(query));
72
+ await run(`truncate ${[...exports.TABLES, ...DERIVED].map(t => `"${t}"`).join(', ')} restart identity cascade`);
73
+ const legacyChat = this.legacyChatId(tables);
74
+ for (const table of exports.TABLES) {
75
+ const rows = [...(table === 'chats' && legacyChat.create ? [legacyChat.create] : []), ...(tables[table] ?? [])];
76
+ const columns = await this.columns(tx, table);
77
+ const later = SELF_REFERENCES[table] ?? [];
78
+ for (const row of rows) {
79
+ const values = CHAT_SCOPED.has(table) && row.chat_id == null ? { ...row, chat_id: legacyChat.id } : { ...row };
80
+ for (const column of later)
81
+ values[column] = null;
82
+ await tx.execute(insert(table, columns, values));
83
+ }
84
+ for (const row of rows) {
85
+ for (const column of later.filter(c => row[c] != null)) {
86
+ await tx.execute((0, drizzle_orm_1.sql) `update ${drizzle_orm_1.sql.raw(`"${table}"`)} set ${drizzle_orm_1.sql.raw(`"${column}"`)} = ${row[column]} where id = ${row.id}`);
87
+ }
88
+ }
89
+ counts[table] = rows.length;
90
+ if (rows.length)
91
+ await run(`select setval(pg_get_serial_sequence('"${table}"', 'id'), (select max(id) from "${table}"))`);
92
+ }
93
+ });
94
+ return { restored: counts };
95
+ }
96
+ /**
97
+ * Дамп времён одного чата: у сообщений, решений и документов нет chat_id. Всё это —
98
+ * обсуждение QBazar: так же его разложила PHP-миграция, заводившая чат qbazar.
99
+ */
100
+ legacyChatId(tables) {
101
+ const chats = tables.chats ?? [];
102
+ const legacy = [...CHAT_SCOPED].some(t => (tables[t] ?? []).some(r => r.chat_id == null));
103
+ if (!legacy)
104
+ return { id: null };
105
+ if (chats.length)
106
+ return { id: Math.min(...chats.map(c => Number(c.id))) };
107
+ const id = 1;
108
+ return { id, create: { id, slug: 'qbazar', name: 'QBazar', description: null, workdir: null } };
109
+ }
110
+ async columns(tx, table) {
111
+ const rows = await rowsOf(tx, `select column_name as name, data_type as type from information_schema.columns
112
+ where table_schema = 'public' and table_name = '${table}'`);
113
+ return new Map(rows.map(r => [String(r.name), { name: String(r.name), type: String(r.type) }]));
114
+ }
115
+ rows(query) {
116
+ return rowsOf(this.db, query);
117
+ }
118
+ };
119
+ exports.TransferService = TransferService;
120
+ exports.TransferService = TransferService = __decorate([
121
+ (0, common_1.Injectable)(),
122
+ __param(0, (0, common_1.Inject)(database_module_1.DATABASE)),
123
+ __metadata("design:paramtypes", [Object])
124
+ ], TransferService);
125
+ /** INSERT строки дампа: только известные столбцы, значения — в типах этой базы. */
126
+ function insert(table, columns, row) {
127
+ const entries = Object.entries(row).filter(([name]) => columns.has(name));
128
+ const names = drizzle_orm_1.sql.raw(entries.map(([name]) => `"${name}"`).join(', '));
129
+ const values = drizzle_orm_1.sql.join(entries.map(([name, value]) => (0, drizzle_orm_1.sql) `${convert(value, columns.get(name).type)}`), (0, drizzle_orm_1.sql) `, `);
130
+ return (0, drizzle_orm_1.sql) `insert into ${drizzle_orm_1.sql.raw(`"${table}"`)} (${names}) values (${values})`;
131
+ }
132
+ function convert(value, type) {
133
+ if (value === null || value === undefined)
134
+ return null;
135
+ if (type === 'boolean')
136
+ return value === true || value === 1 || value === '1' || value === 'true';
137
+ if (type.startsWith('timestamp'))
138
+ return laravelTime(String(value));
139
+ if (type === 'jsonb' || type === 'json')
140
+ return typeof value === 'string' ? value : JSON.stringify(value);
141
+ return value;
142
+ }
143
+ /** Laravel хранил время без пояса — это UTC: «2026-09-13 11:21:36» → 2026-09-13T11:21:36Z. */
144
+ function laravelTime(value) {
145
+ return /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(\.\d+)?$/.test(value) ? value.replace(' ', 'T') + 'Z' : value;
146
+ }
147
+ async function rowsOf(db, query) {
148
+ // Оба драйвера (PGlite и pg) отдают строки в rows; общий тип drizzle этого не знает.
149
+ const result = await db.execute(drizzle_orm_1.sql.raw(query));
150
+ return result.rows;
151
+ }
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // Точка входа сервера чата. Настройки — переменные CHAT_* (config/config.ts).
4
+ var __importDefault = (this && this.__importDefault) || function (mod) {
5
+ return (mod && mod.__esModule) ? mod : { "default": mod };
6
+ };
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const app_1 = require("./app");
11
+ const config_1 = require("./config/config");
12
+ const database_1 = require("./db/database");
13
+ async function main() {
14
+ let config;
15
+ try {
16
+ config = (0, config_1.serverConfig)();
17
+ }
18
+ catch (e) {
19
+ if (!(e instanceof config_1.ConfigError))
20
+ throw e;
21
+ console.error(e.message);
22
+ process.exit(1);
23
+ }
24
+ node_fs_1.default.mkdirSync(config.dataDir, { recursive: true, mode: 0o700 });
25
+ const database = await (0, database_1.openDatabase)(config.databaseUrl ? { url: config.databaseUrl } : { dataDir: node_path_1.default.join(config.dataDir, 'db') });
26
+ const app = await (0, app_1.createApp)({ config, database, logger: ['error', 'warn', 'log'] });
27
+ await app.listen(config.port, config.host);
28
+ console.log(`чат: ${config.url} (слушаю ${config.host}:${config.port}, данные — ${config.dataDir})`);
29
+ }
30
+ main().catch(e => {
31
+ console.error(e);
32
+ process.exit(1);
33
+ });