crewly 1.8.4 → 1.8.5
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/config/roles/_common/wiki-instructions.md +33 -0
- package/config/roles/orchestrator/prompt.md +35 -0
- package/config/roles/team-leader/prompt.md +38 -0
- package/config/skills/agent/core/wiki-query/SKILL.md +66 -0
- package/config/skills/agent/core/wiki-query/execute.sh +107 -0
- package/config/skills/orchestrator/wiki-bookkeep/SKILL.md +71 -0
- package/config/skills/orchestrator/wiki-bookkeep/execute.sh +72 -0
- package/config/skills/orchestrator/wiki-ingest/SKILL.md +63 -0
- package/config/skills/orchestrator/wiki-ingest/execute.sh +113 -0
- package/config/skills/orchestrator/wiki-process-queue/SKILL.md +71 -0
- package/config/skills/orchestrator/wiki-process-queue/execute.sh +93 -0
- package/config/skills/orchestrator/wiki-queue-add/SKILL.md +89 -0
- package/config/skills/orchestrator/wiki-queue-add/execute.sh +115 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +134 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +718 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts +23 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +43 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +39 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +4 -0
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts +28 -0
- package/dist/backend/backend/src/services/session/pty/pty-session.d.ts.map +1 -1
- package/dist/backend/backend/src/services/session/pty/pty-session.js +162 -4
- package/dist/backend/backend/src/services/session/pty/pty-session.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts +69 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js +174 -0
- package/dist/backend/backend/src/services/wiki/referenced-by.resolver.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts +57 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.js +183 -0
- package/dist/backend/backend/src/services/wiki/schema-loader.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts +86 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +187 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts +116 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js +299 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts +74 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js +154 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts +100 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js +212 -0
- package/dist/backend/backend/src/services/wiki/wiki-ingest.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts +84 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.js +138 -0
- package/dist/backend/backend/src/services/wiki/wiki-process.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts +115 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.js +291 -0
- package/dist/backend/backend/src/services/wiki/wiki-query.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts +115 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.js +261 -0
- package/dist/backend/backend/src/services/wiki/wiki-queue.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.d.ts +84 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.js +10 -0
- package/dist/backend/backend/src/services/wiki/wiki.types.js.map +1 -0
- package/frontend/dist/assets/{index-b279da34.js → index-cc115bb4.js} +246 -246
- package/frontend/dist/assets/{index-c07e04c0.css → index-db3f5041.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiBookkeepService — vault health metrics + consolidation signals.
|
|
3
|
+
*
|
|
4
|
+
* Per Steve's 2026-05-22 design point #5: "还要时不时让 agent 针对自己的
|
|
5
|
+
* vault 进行 bookkeeping (可以根据存入的 md 数量来 trigger), 譬如过去 N
|
|
6
|
+
* 天超过 X 个 md, 然后总结一下."
|
|
7
|
+
*
|
|
8
|
+
* This service produces the report the bookkeeping agent reads:
|
|
9
|
+
* - md count per top-level llm-curated subfolder
|
|
10
|
+
* - new mds in the last N days
|
|
11
|
+
* - filename clusters (likely-duplicates by Jaccard of token sets)
|
|
12
|
+
* - queue stats (so the agent sees pending items too)
|
|
13
|
+
*
|
|
14
|
+
* The agent uses this report (+ its LLM) to decide:
|
|
15
|
+
* - which pages to consolidate / dedupe (calls wiki-ingest with a new
|
|
16
|
+
* consolidated body + deletes the originals via a separate skill —
|
|
17
|
+
* deletion is OUT of scope for Phase 1; surface as proposals).
|
|
18
|
+
* - which topics deserve a summary "rollup" page.
|
|
19
|
+
*
|
|
20
|
+
* The service does NOT delete or rewrite anything. It reports.
|
|
21
|
+
*
|
|
22
|
+
* @module services/wiki/wiki-bookkeep.service
|
|
23
|
+
*/
|
|
24
|
+
import * as path from 'path';
|
|
25
|
+
import * as fs from 'fs/promises';
|
|
26
|
+
import { existsSync } from 'fs';
|
|
27
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
28
|
+
import { SchemaLoaderService } from './schema-loader.service.js';
|
|
29
|
+
import { WikiQueueService } from './wiki-queue.service.js';
|
|
30
|
+
const DEFAULT_WINDOW_DAYS = 7;
|
|
31
|
+
const DEFAULT_MD_THRESHOLD = 10;
|
|
32
|
+
const MAX_PAGES_SCANNED = 1000;
|
|
33
|
+
/**
|
|
34
|
+
* Vault health + bookkeeping report generator. Stateless; uses the
|
|
35
|
+
* schema loader + queue services it composes.
|
|
36
|
+
*/
|
|
37
|
+
export class WikiBookkeepService {
|
|
38
|
+
static instance = null;
|
|
39
|
+
logger;
|
|
40
|
+
schemaLoader;
|
|
41
|
+
queue;
|
|
42
|
+
constructor(schemaLoader, queue) {
|
|
43
|
+
this.logger = LoggerService.getInstance().createComponentLogger('WikiBookkeep');
|
|
44
|
+
this.schemaLoader = schemaLoader ?? new SchemaLoaderService();
|
|
45
|
+
this.queue = queue ?? WikiQueueService.getInstance();
|
|
46
|
+
}
|
|
47
|
+
static getInstance() {
|
|
48
|
+
if (!this.instance)
|
|
49
|
+
this.instance = new WikiBookkeepService();
|
|
50
|
+
return this.instance;
|
|
51
|
+
}
|
|
52
|
+
/** Test-only reset. */
|
|
53
|
+
static _resetForTesting() {
|
|
54
|
+
this.instance = null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Build the bookkeeping report. Caller (skill / cron / agent) decides
|
|
58
|
+
* whether to act on `shouldFire`. The service never writes; it reports.
|
|
59
|
+
*/
|
|
60
|
+
async generate(input) {
|
|
61
|
+
if (!input.vaultPath || !path.isAbsolute(input.vaultPath)) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
reason: 'invalid_input',
|
|
65
|
+
message: 'vaultPath must be absolute',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
if (!existsSync(input.vaultPath)) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
reason: 'vault_missing',
|
|
72
|
+
message: `vault directory does not exist: ${input.vaultPath}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
let schema;
|
|
76
|
+
try {
|
|
77
|
+
schema = await this.schemaLoader.load(input.vaultPath);
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
return {
|
|
81
|
+
ok: false,
|
|
82
|
+
reason: 'schema_missing',
|
|
83
|
+
message: err.message,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const windowDays = input.windowDays ?? DEFAULT_WINDOW_DAYS;
|
|
87
|
+
const threshold = input.threshold ?? DEFAULT_MD_THRESHOLD;
|
|
88
|
+
const windowMs = windowDays * 24 * 3600 * 1000;
|
|
89
|
+
const now = Date.now();
|
|
90
|
+
const staleMs = 90 * 24 * 3600 * 1000;
|
|
91
|
+
const pages = await this.collectPages(input.vaultPath, schema.llm_curated.map((l) => l.path));
|
|
92
|
+
const totalMdCount = pages.length;
|
|
93
|
+
let recentMdCount = 0;
|
|
94
|
+
let staleCount = 0;
|
|
95
|
+
const countsByFolder = {};
|
|
96
|
+
for (const p of pages) {
|
|
97
|
+
const mtime = Date.parse(p.modifiedAt);
|
|
98
|
+
if (Number.isFinite(mtime)) {
|
|
99
|
+
if (now - mtime <= windowMs)
|
|
100
|
+
recentMdCount++;
|
|
101
|
+
if (now - mtime > staleMs)
|
|
102
|
+
staleCount++;
|
|
103
|
+
}
|
|
104
|
+
// Folder bucket: first directory segment under llm-curated/.
|
|
105
|
+
const rel = p.path;
|
|
106
|
+
const parts = rel.split('/').filter(Boolean);
|
|
107
|
+
// rel looks like "llm-curated/<folder>/<page>.md" → bucket = "llm-curated/<folder>".
|
|
108
|
+
if (parts.length >= 2) {
|
|
109
|
+
const bucket = `${parts[0]}/${parts[1]}`;
|
|
110
|
+
countsByFolder[bucket] = (countsByFolder[bucket] ?? 0) + 1;
|
|
111
|
+
}
|
|
112
|
+
else if (parts.length === 1) {
|
|
113
|
+
const bucket = parts[0];
|
|
114
|
+
countsByFolder[bucket] = (countsByFolder[bucket] ?? 0) + 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const duplicateCandidates = this.findDuplicateClusters(pages);
|
|
118
|
+
const queueStats = await this.queue.getStats(input.vaultPath);
|
|
119
|
+
const shouldFire = recentMdCount >= threshold || duplicateCandidates.length > 0;
|
|
120
|
+
const recommendations = this.buildRecommendations({
|
|
121
|
+
shouldFire,
|
|
122
|
+
recentMdCount,
|
|
123
|
+
threshold,
|
|
124
|
+
duplicates: duplicateCandidates,
|
|
125
|
+
countsByFolder,
|
|
126
|
+
staleCount,
|
|
127
|
+
queueStats,
|
|
128
|
+
});
|
|
129
|
+
const report = {
|
|
130
|
+
vault: {
|
|
131
|
+
scope: schema.vault_scope,
|
|
132
|
+
id: schema.vault_id,
|
|
133
|
+
path: input.vaultPath,
|
|
134
|
+
},
|
|
135
|
+
generatedAt: new Date().toISOString(),
|
|
136
|
+
windowDays,
|
|
137
|
+
threshold,
|
|
138
|
+
shouldFire,
|
|
139
|
+
totalMdCount,
|
|
140
|
+
recentMdCount,
|
|
141
|
+
countsByFolder,
|
|
142
|
+
duplicateCandidates,
|
|
143
|
+
staleCount,
|
|
144
|
+
queue: queueStats,
|
|
145
|
+
recommendations,
|
|
146
|
+
};
|
|
147
|
+
this.logger.info('WikiBookkeep generated', {
|
|
148
|
+
vault: input.vaultPath,
|
|
149
|
+
totalMd: totalMdCount,
|
|
150
|
+
recentMd: recentMdCount,
|
|
151
|
+
duplicates: duplicateCandidates.length,
|
|
152
|
+
shouldFire,
|
|
153
|
+
});
|
|
154
|
+
return { ok: true, report };
|
|
155
|
+
}
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
async collectPages(vaultPath, llmCuratedFolders) {
|
|
158
|
+
const pages = [];
|
|
159
|
+
let scanned = 0;
|
|
160
|
+
for (const folder of llmCuratedFolders) {
|
|
161
|
+
const root = path.join(vaultPath, folder.replace(/[/\\]+$/, ''));
|
|
162
|
+
if (!existsSync(root))
|
|
163
|
+
continue;
|
|
164
|
+
const stack = [root];
|
|
165
|
+
while (stack.length > 0 && scanned < MAX_PAGES_SCANNED) {
|
|
166
|
+
const current = stack.pop();
|
|
167
|
+
let entries;
|
|
168
|
+
try {
|
|
169
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
if (entry.name.startsWith('.'))
|
|
176
|
+
continue;
|
|
177
|
+
const full = path.join(current, entry.name);
|
|
178
|
+
if (entry.isDirectory()) {
|
|
179
|
+
stack.push(full);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
183
|
+
continue;
|
|
184
|
+
scanned++;
|
|
185
|
+
try {
|
|
186
|
+
const stat = await fs.stat(full);
|
|
187
|
+
pages.push({
|
|
188
|
+
path: path.relative(vaultPath, full).replace(/\\/g, '/'),
|
|
189
|
+
bytes: stat.size,
|
|
190
|
+
modifiedAt: new Date(stat.mtimeMs).toISOString(),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// ignore
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return pages;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Find filename clusters by Jaccard over name tokens.
|
|
203
|
+
* Two pages with ≥ 0.6 overlap on their non-trivial filename tokens
|
|
204
|
+
* are flagged as potential duplicates. Token = anything ≥ 3 chars
|
|
205
|
+
* after splitting on `[-_./]`, lowercased, minus date prefix.
|
|
206
|
+
*/
|
|
207
|
+
findDuplicateClusters(pages) {
|
|
208
|
+
if (pages.length < 2)
|
|
209
|
+
return [];
|
|
210
|
+
const tokens = pages.map((p) => ({
|
|
211
|
+
path: p.path,
|
|
212
|
+
tokens: this.tokenizeFilename(p.path),
|
|
213
|
+
}));
|
|
214
|
+
const used = new Set();
|
|
215
|
+
const clusters = [];
|
|
216
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
217
|
+
if (used.has(i))
|
|
218
|
+
continue;
|
|
219
|
+
const peers = [i];
|
|
220
|
+
for (let j = i + 1; j < tokens.length; j++) {
|
|
221
|
+
if (used.has(j))
|
|
222
|
+
continue;
|
|
223
|
+
if (this.jaccard(tokens[i].tokens, tokens[j].tokens) >= 0.6) {
|
|
224
|
+
peers.push(j);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (peers.length >= 2) {
|
|
228
|
+
peers.forEach((p) => used.add(p));
|
|
229
|
+
const memberPaths = peers.map((p) => tokens[p].path);
|
|
230
|
+
clusters.push({
|
|
231
|
+
basis: this.longestCommonPrefix(memberPaths),
|
|
232
|
+
pages: memberPaths,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return clusters;
|
|
237
|
+
}
|
|
238
|
+
tokenizeFilename(relPath) {
|
|
239
|
+
const basename = relPath.split('/').pop()?.replace(/\.md$/, '') ?? '';
|
|
240
|
+
// Strip ISO-date prefix (YYYY-MM-DD-)
|
|
241
|
+
const withoutDate = basename.replace(/^\d{4}-\d{2}-\d{2}-/, '');
|
|
242
|
+
const tokens = withoutDate
|
|
243
|
+
.toLowerCase()
|
|
244
|
+
.split(/[-_./\s]+/)
|
|
245
|
+
.filter((t) => t.length >= 3);
|
|
246
|
+
return new Set(tokens);
|
|
247
|
+
}
|
|
248
|
+
jaccard(a, b) {
|
|
249
|
+
if (a.size === 0 && b.size === 0)
|
|
250
|
+
return 0;
|
|
251
|
+
let inter = 0;
|
|
252
|
+
for (const x of a)
|
|
253
|
+
if (b.has(x))
|
|
254
|
+
inter++;
|
|
255
|
+
const union = a.size + b.size - inter;
|
|
256
|
+
return union === 0 ? 0 : inter / union;
|
|
257
|
+
}
|
|
258
|
+
longestCommonPrefix(paths) {
|
|
259
|
+
if (paths.length === 0)
|
|
260
|
+
return '';
|
|
261
|
+
let prefix = paths[0];
|
|
262
|
+
for (const p of paths.slice(1)) {
|
|
263
|
+
while (p.indexOf(prefix) !== 0) {
|
|
264
|
+
prefix = prefix.slice(0, -1);
|
|
265
|
+
if (!prefix)
|
|
266
|
+
return '';
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return prefix;
|
|
270
|
+
}
|
|
271
|
+
buildRecommendations(args) {
|
|
272
|
+
const recs = [];
|
|
273
|
+
if (!args.shouldFire) {
|
|
274
|
+
recs.push(`Quiet vault — only ${args.recentMdCount} new md(s) in the window (threshold ${args.threshold}). No action required.`);
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
recs.push(`Window has ${args.recentMdCount} new md(s) — at or above threshold (${args.threshold}). Consider a consolidation pass.`);
|
|
278
|
+
}
|
|
279
|
+
if (args.duplicates.length > 0) {
|
|
280
|
+
recs.push(`${args.duplicates.length} likely-duplicate cluster(s) detected. Review each with your LLM and call wiki-ingest with a consolidated body when you can merge.`);
|
|
281
|
+
args.duplicates.slice(0, 3).forEach((c) => {
|
|
282
|
+
recs.push(` • cluster "${c.basis}": ${c.pages.join(', ')}`);
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
if (args.queueStats.pending > 5) {
|
|
286
|
+
recs.push(`${args.queueStats.pending} pending queue items. Drain via wiki-process-queue before doing other bookkeeping.`);
|
|
287
|
+
}
|
|
288
|
+
const folderEntries = Object.entries(args.countsByFolder).sort((a, b) => b[1] - a[1]);
|
|
289
|
+
if (folderEntries.length > 0) {
|
|
290
|
+
const top = folderEntries[0];
|
|
291
|
+
recs.push(`Hottest folder: \`${top[0]}\` (${top[1]} pages). If it's grown >20 pages without a rollup, write a summary index page.`);
|
|
292
|
+
}
|
|
293
|
+
if (args.staleCount > 0) {
|
|
294
|
+
recs.push(`${args.staleCount} page(s) untouched in 90+ days. Consider whether they're still authoritative — flag stale or archive.`);
|
|
295
|
+
}
|
|
296
|
+
return recs;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
//# sourceMappingURL=wiki-bookkeep.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-bookkeep.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-bookkeep.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiChatSubscriberService — listens on chat-v2's `chat_message` event
|
|
3
|
+
* and fires `WikiIngestService.ingest` for every user-authored message.
|
|
4
|
+
*
|
|
5
|
+
* Phase A (rules-only routing):
|
|
6
|
+
* senderType === 'user' → ingest into the Crewly project vault.
|
|
7
|
+
* senderType === 'agent' | 'system' → skip.
|
|
8
|
+
*
|
|
9
|
+
* Phase B (per spec §4.5) will replace the project-only routing with an
|
|
10
|
+
* LLM-driven gate that picks team / project / global per source.
|
|
11
|
+
*
|
|
12
|
+
* Failure mode is FIRE-AND-FORGET: a malformed message, missing schema,
|
|
13
|
+
* or filesystem error MUST NOT block the live chat flow. Errors are
|
|
14
|
+
* logged at warn-level and swallowed.
|
|
15
|
+
*
|
|
16
|
+
* @module services/wiki/wiki-chat-subscriber.service
|
|
17
|
+
*/
|
|
18
|
+
import type { ChatV2Service } from '../chat-v2/chat-v2.service.js';
|
|
19
|
+
import { WikiIngestService } from './wiki-ingest.service.js';
|
|
20
|
+
/**
|
|
21
|
+
* Shape we consume from chat-v2's `chat_message` payload. Kept narrow so
|
|
22
|
+
* we don't take a heavy structural dep on the wire DTO; matches the
|
|
23
|
+
* fields produced by `toMessageDTO` (chat-v2.service.ts:1537).
|
|
24
|
+
*/
|
|
25
|
+
interface MinimalChatMessageDTO {
|
|
26
|
+
id: string;
|
|
27
|
+
channelId: string;
|
|
28
|
+
senderType: 'user' | 'agent' | 'system';
|
|
29
|
+
senderId: string;
|
|
30
|
+
content: string;
|
|
31
|
+
contentType: string;
|
|
32
|
+
createdAt: string | number;
|
|
33
|
+
}
|
|
34
|
+
export interface WikiChatSubscriberOptions {
|
|
35
|
+
/** chat-v2 singleton — `getChatV2Service()` at composition root. */
|
|
36
|
+
chatService: ChatV2Service;
|
|
37
|
+
/** Absolute path to the project vault. Default falls back to env / well-known location. */
|
|
38
|
+
projectVaultPath?: string;
|
|
39
|
+
/** Optional override for tests. */
|
|
40
|
+
ingestService?: WikiIngestService;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Process-singleton subscriber. Call `start()` once at server boot,
|
|
44
|
+
* `stop()` at shutdown.
|
|
45
|
+
*/
|
|
46
|
+
export declare class WikiChatSubscriberService {
|
|
47
|
+
private readonly logger;
|
|
48
|
+
private readonly chatService;
|
|
49
|
+
private readonly projectVaultPath;
|
|
50
|
+
private readonly ingestService;
|
|
51
|
+
private boundHandler;
|
|
52
|
+
private started;
|
|
53
|
+
constructor(opts: WikiChatSubscriberOptions);
|
|
54
|
+
/**
|
|
55
|
+
* Subscribe to `chat_message`. Idempotent — calling start() twice
|
|
56
|
+
* leaves a single listener.
|
|
57
|
+
*/
|
|
58
|
+
start(): void;
|
|
59
|
+
/** Detach. Safe to call when not started. */
|
|
60
|
+
stop(): void;
|
|
61
|
+
/**
|
|
62
|
+
* Handle a single chat message. Public for tests.
|
|
63
|
+
*/
|
|
64
|
+
handle(dto: MinimalChatMessageDTO): Promise<void>;
|
|
65
|
+
/**
|
|
66
|
+
* Decide whether a chat-v2 message should drive a wiki write.
|
|
67
|
+
* Public for tests; logic is conservative — only ingest user-authored
|
|
68
|
+
* text messages, skip agents/system, mentions-only pings, and slash
|
|
69
|
+
* commands.
|
|
70
|
+
*/
|
|
71
|
+
isIngestable(dto: MinimalChatMessageDTO): boolean;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
74
|
+
//# sourceMappingURL=wiki-chat-subscriber.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-chat-subscriber.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-chat-subscriber.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAsB,MAAM,0BAA0B,CAAC;AAEjF;;;;GAIG;AACH,UAAU,qBAAqB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;IACxC,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,yBAAyB;IACxC,oEAAoE;IACpE,WAAW,EAAE,aAAa,CAAC;IAC3B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mCAAmC;IACnC,aAAa,CAAC,EAAE,iBAAiB,CAAC;CACnC;AAED;;;GAGG;AACH,qBAAa,yBAAyB;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAgB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAS;IAC1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAoB;IAClD,OAAO,CAAC,YAAY,CAAuD;IAC3E,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,EAAE,yBAAyB;IAO3C;;;OAGG;IACH,KAAK,IAAI,IAAI;IAgBb,6CAA6C;IAC7C,IAAI,IAAI,IAAI;IAQZ;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmDvD;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,qBAAqB,GAAG,OAAO;CAYlD"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiChatSubscriberService — listens on chat-v2's `chat_message` event
|
|
3
|
+
* and fires `WikiIngestService.ingest` for every user-authored message.
|
|
4
|
+
*
|
|
5
|
+
* Phase A (rules-only routing):
|
|
6
|
+
* senderType === 'user' → ingest into the Crewly project vault.
|
|
7
|
+
* senderType === 'agent' | 'system' → skip.
|
|
8
|
+
*
|
|
9
|
+
* Phase B (per spec §4.5) will replace the project-only routing with an
|
|
10
|
+
* LLM-driven gate that picks team / project / global per source.
|
|
11
|
+
*
|
|
12
|
+
* Failure mode is FIRE-AND-FORGET: a malformed message, missing schema,
|
|
13
|
+
* or filesystem error MUST NOT block the live chat flow. Errors are
|
|
14
|
+
* logged at warn-level and swallowed.
|
|
15
|
+
*
|
|
16
|
+
* @module services/wiki/wiki-chat-subscriber.service
|
|
17
|
+
*/
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
20
|
+
import { WikiIngestService, detectMessageShape } from './wiki-ingest.service.js';
|
|
21
|
+
/**
|
|
22
|
+
* Process-singleton subscriber. Call `start()` once at server boot,
|
|
23
|
+
* `stop()` at shutdown.
|
|
24
|
+
*/
|
|
25
|
+
export class WikiChatSubscriberService {
|
|
26
|
+
logger;
|
|
27
|
+
chatService;
|
|
28
|
+
projectVaultPath;
|
|
29
|
+
ingestService;
|
|
30
|
+
boundHandler = null;
|
|
31
|
+
started = false;
|
|
32
|
+
constructor(opts) {
|
|
33
|
+
this.logger = LoggerService.getInstance().createComponentLogger('WikiChatSubscriber');
|
|
34
|
+
this.chatService = opts.chatService;
|
|
35
|
+
this.projectVaultPath = opts.projectVaultPath ?? resolveDefaultProjectVaultPath();
|
|
36
|
+
this.ingestService = opts.ingestService ?? WikiIngestService.getInstance();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Subscribe to `chat_message`. Idempotent — calling start() twice
|
|
40
|
+
* leaves a single listener.
|
|
41
|
+
*/
|
|
42
|
+
start() {
|
|
43
|
+
if (this.started) {
|
|
44
|
+
this.logger.debug('start() called twice — ignoring');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.boundHandler = (dto) => {
|
|
48
|
+
// Fire-and-forget; any thrown error is logged + swallowed in handle().
|
|
49
|
+
void this.handle(dto);
|
|
50
|
+
};
|
|
51
|
+
this.chatService.on('chat_message', this.boundHandler);
|
|
52
|
+
this.started = true;
|
|
53
|
+
this.logger.info('WikiChatSubscriber started', {
|
|
54
|
+
vaultPath: this.projectVaultPath,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Detach. Safe to call when not started. */
|
|
58
|
+
stop() {
|
|
59
|
+
if (!this.started || !this.boundHandler)
|
|
60
|
+
return;
|
|
61
|
+
this.chatService.off('chat_message', this.boundHandler);
|
|
62
|
+
this.boundHandler = null;
|
|
63
|
+
this.started = false;
|
|
64
|
+
this.logger.info('WikiChatSubscriber stopped');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Handle a single chat message. Public for tests.
|
|
68
|
+
*/
|
|
69
|
+
async handle(dto) {
|
|
70
|
+
try {
|
|
71
|
+
if (!this.isIngestable(dto)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const ingestInput = {
|
|
75
|
+
vaultPath: this.projectVaultPath,
|
|
76
|
+
sourceType: 'user_chat',
|
|
77
|
+
sourceRef: `chat:${dto.channelId}:${dto.id}`,
|
|
78
|
+
sourceBody: dto.content,
|
|
79
|
+
callerSession: `user/${dto.senderId}`,
|
|
80
|
+
};
|
|
81
|
+
// Phase A rules-based promotion: decision-shaped chats land in
|
|
82
|
+
// BOTH log.md (audit) and llm-curated/decisions/<date>-<slug>.md
|
|
83
|
+
// (canonical). Phase B will replace `detectMessageShape` with an
|
|
84
|
+
// LLM gate per v2.1 §4.
|
|
85
|
+
const shape = detectMessageShape(dto.content);
|
|
86
|
+
if (shape === 'decision') {
|
|
87
|
+
const result = await this.ingestService.ingestDecision(ingestInput);
|
|
88
|
+
if (!result.log.ok) {
|
|
89
|
+
this.logger.warn('WikiChatSubscriber decision-log ingest refused', {
|
|
90
|
+
reason: result.log.reason,
|
|
91
|
+
messageId: dto.id,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
if (!result.decision.ok) {
|
|
95
|
+
this.logger.warn('WikiChatSubscriber decisions/ promotion refused', {
|
|
96
|
+
reason: result.decision.reason,
|
|
97
|
+
messageId: dto.id,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const outcome = await this.ingestService.ingest(ingestInput);
|
|
103
|
+
if (!outcome.ok) {
|
|
104
|
+
this.logger.warn('WikiChatSubscriber ingest refused', {
|
|
105
|
+
reason: outcome.reason,
|
|
106
|
+
messageId: dto.id,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
// Swallow — chat flow must not block on wiki write failures.
|
|
112
|
+
this.logger.warn('WikiChatSubscriber.handle threw (swallowed)', {
|
|
113
|
+
messageId: dto.id,
|
|
114
|
+
error: err.message,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Decide whether a chat-v2 message should drive a wiki write.
|
|
120
|
+
* Public for tests; logic is conservative — only ingest user-authored
|
|
121
|
+
* text messages, skip agents/system, mentions-only pings, and slash
|
|
122
|
+
* commands.
|
|
123
|
+
*/
|
|
124
|
+
isIngestable(dto) {
|
|
125
|
+
if (dto.senderType !== 'user')
|
|
126
|
+
return false;
|
|
127
|
+
if (!dto.content || dto.content.trim().length === 0)
|
|
128
|
+
return false;
|
|
129
|
+
// Skip slash commands — they're orc-controls, not knowledge.
|
|
130
|
+
if (dto.content.trimStart().startsWith('/'))
|
|
131
|
+
return false;
|
|
132
|
+
// chat-v2 defaults `contentType` to 'markdown' when sendMessage doesn't
|
|
133
|
+
// supply one (chat-v2.service.ts ~line 1240). Accept the two readable
|
|
134
|
+
// text-bearing types; reject binary/system flavors like 'system_note'.
|
|
135
|
+
const acceptable = new Set(['text', 'markdown']);
|
|
136
|
+
if (dto.contentType && !acceptable.has(dto.contentType))
|
|
137
|
+
return false;
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolve a sensible default vault path:
|
|
143
|
+
* 1. `CREWLY_PROJECT_VAULT_PATH` env var (explicit override)
|
|
144
|
+
* 2. `<cwd>/.crewly/wiki` if it exists
|
|
145
|
+
* 3. The Crewly repo's own project vault (the only one with SCHEMA.md today)
|
|
146
|
+
*/
|
|
147
|
+
function resolveDefaultProjectVaultPath() {
|
|
148
|
+
const fromEnv = process.env['CREWLY_PROJECT_VAULT_PATH'];
|
|
149
|
+
if (fromEnv && path.isAbsolute(fromEnv)) {
|
|
150
|
+
return fromEnv;
|
|
151
|
+
}
|
|
152
|
+
return path.join('/Users/yellowsunhy/Desktop/projects/crewly-projects/crewly', '.crewly/wiki');
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=wiki-chat-subscriber.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-chat-subscriber.service.js","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-chat-subscriber.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiIngestService — write canonical pages into a vault's `llm-curated/`.
|
|
3
|
+
*
|
|
4
|
+
* Phase A scope per v2.1 spec: append entries to `llm-curated/log.md` for
|
|
5
|
+
* every chat/spec/learning source. Decisions/pattern pages (separate files
|
|
6
|
+
* under `llm-curated/decisions/`) are gated behind the LLM routing layer
|
|
7
|
+
* which lands in Phase B.
|
|
8
|
+
*
|
|
9
|
+
* Frozen-path contract (§2): refuses ANY write whose target lives under a
|
|
10
|
+
* `hardcoded:` folder. SchemaLoaderService.isFrozenPath() is the gate.
|
|
11
|
+
*
|
|
12
|
+
* @module services/wiki/wiki-ingest.service
|
|
13
|
+
*/
|
|
14
|
+
import { SchemaLoaderService } from './schema-loader.service.js';
|
|
15
|
+
/**
|
|
16
|
+
* Categories of sources that trigger ingest. Mirrors §4 (ingest trigger
|
|
17
|
+
* taxonomy) plus `user_chat` for the demo path.
|
|
18
|
+
*/
|
|
19
|
+
export type WikiSourceType = 'user_chat' | 'slack_message' | 'spec_file' | 'pr_merge' | 'record_learning' | 'task_verified';
|
|
20
|
+
export interface WikiIngestInput {
|
|
21
|
+
/** Absolute path to the vault root (containing SCHEMA.md). */
|
|
22
|
+
vaultPath: string;
|
|
23
|
+
/** Where the content came from. */
|
|
24
|
+
sourceType: WikiSourceType;
|
|
25
|
+
/** Stable reference for audit (URL, file path, WI id, chat msg id). */
|
|
26
|
+
sourceRef: string;
|
|
27
|
+
/** Body content to ingest. Empty bodies are rejected. */
|
|
28
|
+
sourceBody: string;
|
|
29
|
+
/** Session/user that authored the source — appears in the log header. */
|
|
30
|
+
callerSession?: string;
|
|
31
|
+
/** Optional override of the relative target path; defaults to `llm-curated/log.md`. */
|
|
32
|
+
targetRelativePath?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface WikiIngestResult {
|
|
35
|
+
ok: true;
|
|
36
|
+
pagesWritten: string[];
|
|
37
|
+
logEntry: string;
|
|
38
|
+
frozenPathsTouched: string[];
|
|
39
|
+
}
|
|
40
|
+
export interface WikiIngestRefusedFrozen {
|
|
41
|
+
ok: false;
|
|
42
|
+
reason: 'frozen_path';
|
|
43
|
+
attemptedPath: string;
|
|
44
|
+
frozenFolders: string[];
|
|
45
|
+
}
|
|
46
|
+
export interface WikiIngestRefusedInvalid {
|
|
47
|
+
ok: false;
|
|
48
|
+
reason: 'invalid_input' | 'schema_missing' | 'empty_body';
|
|
49
|
+
message: string;
|
|
50
|
+
}
|
|
51
|
+
export type WikiIngestOutcome = WikiIngestResult | WikiIngestRefusedFrozen | WikiIngestRefusedInvalid;
|
|
52
|
+
/**
|
|
53
|
+
* Writes ingest pages to a vault. Construct one per process; stateless.
|
|
54
|
+
*/
|
|
55
|
+
export declare class WikiIngestService {
|
|
56
|
+
private static instance;
|
|
57
|
+
private readonly logger;
|
|
58
|
+
private readonly schemaLoader;
|
|
59
|
+
constructor(schemaLoader?: SchemaLoaderService);
|
|
60
|
+
static getInstance(): WikiIngestService;
|
|
61
|
+
/** Test-only reset. */
|
|
62
|
+
static _resetForTesting(): void;
|
|
63
|
+
/**
|
|
64
|
+
* Ingest a single source into the vault.
|
|
65
|
+
*
|
|
66
|
+
* Errors are returned as structured outcomes — this method never throws
|
|
67
|
+
* on legitimate refusals (frozen path, missing schema, empty body) so
|
|
68
|
+
* callers can fold the result into their normal control flow.
|
|
69
|
+
*/
|
|
70
|
+
ingest(input: WikiIngestInput): Promise<WikiIngestOutcome>;
|
|
71
|
+
private validateInput;
|
|
72
|
+
/**
|
|
73
|
+
* Build the markdown entry. Format is chosen to be safely append-only
|
|
74
|
+
* (no preceding section to rewrite) and grep-friendly:
|
|
75
|
+
*
|
|
76
|
+
* ## [<ISO>] <sourceType> | <callerSession or sourceRef>
|
|
77
|
+
*
|
|
78
|
+
* ref: <sourceRef>
|
|
79
|
+
*
|
|
80
|
+
* <body>
|
|
81
|
+
*/
|
|
82
|
+
private formatLogEntry;
|
|
83
|
+
private appendOrCreate;
|
|
84
|
+
/**
|
|
85
|
+
* Pick the right header for a freshly-created page:
|
|
86
|
+
* - `log.md` → audit-log preamble (append-only)
|
|
87
|
+
* - `decisions/*.md` → decision-page title + provenance block
|
|
88
|
+
* - anything else → minimal title block from the source ref
|
|
89
|
+
*/
|
|
90
|
+
private buildPageHeader;
|
|
91
|
+
/**
|
|
92
|
+
* Defuse markers a future skill might mis-route on: `[CHAT]`, `[NOTIFY]`,
|
|
93
|
+
* `[EVENT]`, `[ESCALATION]` get a zero-width space inserted so they're
|
|
94
|
+
* still human-readable but don't trigger regex matchers downstream.
|
|
95
|
+
* (Mirrors the escalation-router sanitizer from PR #606.)
|
|
96
|
+
*/
|
|
97
|
+
private sanitizeBody;
|
|
98
|
+
private sanitizeOneLine;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=wiki-ingest.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-ingest.service.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/wiki/wiki-ingest.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAGjE;;;GAGG;AACH,MAAM,MAAM,cAAc,GACtB,WAAW,GACX,eAAe,GACf,WAAW,GACX,UAAU,GACV,iBAAiB,GACjB,eAAe,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,SAAS,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,UAAU,EAAE,cAAc,CAAC;IAC3B,uEAAuE;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,yEAAyE;IACzE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,IAAI,CAAC;IACT,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,aAAa,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,KAAK,CAAC;IACV,MAAM,EAAE,eAAe,GAAG,gBAAgB,GAAG,YAAY,CAAC;IAC1D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,iBAAiB,GACzB,gBAAgB,GAChB,uBAAuB,GACvB,wBAAwB,CAAC;AAc7B;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAkC;IACzD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;gBAEvC,YAAY,CAAC,EAAE,mBAAmB;IAK9C,MAAM,CAAC,WAAW,IAAI,iBAAiB;IAOvC,uBAAuB;IACvB,MAAM,CAAC,gBAAgB,IAAI,IAAI;IAI/B;;;;;;OAMG;IACG,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAqDhE,OAAO,CAAC,aAAa;IAgCrB;;;;;;;;;OASG;IACH,OAAO,CAAC,cAAc;YAgBR,cAAc;IAa5B;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAuBvB;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAQpB,OAAO,CAAC,eAAe;CAKxB"}
|