chainlesschain 0.37.12 → 0.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/package.json +3 -2
  2. package/src/commands/agent.js +7 -1
  3. package/src/commands/ask.js +24 -9
  4. package/src/commands/chat.js +7 -1
  5. package/src/commands/cli-anything.js +266 -0
  6. package/src/commands/compliance.js +216 -0
  7. package/src/commands/dao.js +312 -0
  8. package/src/commands/dlp.js +278 -0
  9. package/src/commands/evomap.js +558 -0
  10. package/src/commands/hardening.js +230 -0
  11. package/src/commands/matrix.js +168 -0
  12. package/src/commands/nostr.js +185 -0
  13. package/src/commands/pqc.js +162 -0
  14. package/src/commands/scim.js +218 -0
  15. package/src/commands/serve.js +109 -0
  16. package/src/commands/siem.js +156 -0
  17. package/src/commands/social.js +480 -0
  18. package/src/commands/terraform.js +148 -0
  19. package/src/constants.js +1 -0
  20. package/src/index.js +60 -0
  21. package/src/lib/autonomous-agent.js +487 -0
  22. package/src/lib/cli-anything-bridge.js +379 -0
  23. package/src/lib/cli-context-engineering.js +472 -0
  24. package/src/lib/compliance-manager.js +290 -0
  25. package/src/lib/content-recommender.js +205 -0
  26. package/src/lib/dao-governance.js +296 -0
  27. package/src/lib/dlp-engine.js +304 -0
  28. package/src/lib/evomap-client.js +135 -0
  29. package/src/lib/evomap-federation.js +240 -0
  30. package/src/lib/evomap-governance.js +250 -0
  31. package/src/lib/evomap-manager.js +227 -0
  32. package/src/lib/git-integration.js +1 -1
  33. package/src/lib/hardening-manager.js +275 -0
  34. package/src/lib/llm-providers.js +14 -1
  35. package/src/lib/matrix-bridge.js +196 -0
  36. package/src/lib/nostr-bridge.js +195 -0
  37. package/src/lib/permanent-memory.js +370 -0
  38. package/src/lib/plan-mode.js +211 -0
  39. package/src/lib/pqc-manager.js +196 -0
  40. package/src/lib/scim-manager.js +212 -0
  41. package/src/lib/session-manager.js +38 -0
  42. package/src/lib/siem-exporter.js +137 -0
  43. package/src/lib/social-manager.js +283 -0
  44. package/src/lib/task-model-selector.js +232 -0
  45. package/src/lib/terraform-manager.js +201 -0
  46. package/src/lib/ws-server.js +474 -0
  47. package/src/repl/agent-repl.js +796 -41
  48. package/src/repl/chat-repl.js +14 -6
@@ -0,0 +1,370 @@
1
+ /**
2
+ * CLI Permanent Memory — cross-session persistent memory with Daily Notes,
3
+ * MEMORY.md knowledge base, and BM25 hybrid search.
4
+ *
5
+ * Graceful degradation: works without DB (file-only mode).
6
+ * Keeps CLI < 2MB — uses BM25 for search, no heavy vector dependencies.
7
+ */
8
+
9
+ import fs from "fs";
10
+ import path from "path";
11
+ import { BM25Search } from "./bm25-search.js";
12
+
13
+ // Exported for test injection
14
+ export const _deps = {
15
+ fs,
16
+ path,
17
+ BM25Search,
18
+ };
19
+
20
+ export class CLIPermanentMemory {
21
+ /**
22
+ * @param {object} options
23
+ * @param {object|null} options.db - Database instance (null for file-only mode)
24
+ * @param {string} options.memoryDir - Directory for memory files
25
+ */
26
+ constructor({ db, memoryDir } = {}) {
27
+ this.db = db || null;
28
+ this.memoryDir = memoryDir || "";
29
+ this._bm25 = null;
30
+ this._initialized = false;
31
+ this._memoryFileContent = "";
32
+ this._dailyNotes = [];
33
+ this._dbEntries = [];
34
+ }
35
+
36
+ /**
37
+ * Initialize: create tables, load MEMORY.md, build BM25 index.
38
+ */
39
+ initialize() {
40
+ if (this._initialized) return;
41
+ this._initialized = true;
42
+
43
+ // Ensure directories
44
+ if (this.memoryDir) {
45
+ try {
46
+ const dailyDir = _deps.path.join(this.memoryDir, "daily");
47
+ if (!_deps.fs.existsSync(this.memoryDir)) {
48
+ _deps.fs.mkdirSync(this.memoryDir, { recursive: true });
49
+ }
50
+ if (!_deps.fs.existsSync(dailyDir)) {
51
+ _deps.fs.mkdirSync(dailyDir, { recursive: true });
52
+ }
53
+ } catch (_err) {
54
+ // Directory creation failed — continue in degraded mode
55
+ }
56
+ }
57
+
58
+ // Create DB table
59
+ if (this.db) {
60
+ try {
61
+ this.db.exec(`
62
+ CREATE TABLE IF NOT EXISTS permanent_memory (
63
+ id TEXT PRIMARY KEY,
64
+ content TEXT NOT NULL,
65
+ source TEXT DEFAULT 'auto',
66
+ category TEXT DEFAULT 'general',
67
+ importance REAL DEFAULT 0.5,
68
+ created_at TEXT DEFAULT (datetime('now')),
69
+ updated_at TEXT DEFAULT (datetime('now'))
70
+ )
71
+ `);
72
+ } catch (_err) {
73
+ // Table creation failed — continue without DB
74
+ }
75
+ }
76
+
77
+ // Load MEMORY.md
78
+ this._loadMemoryFile();
79
+
80
+ // Load daily notes (recent 7 days)
81
+ this._loadRecentDailyNotes();
82
+
83
+ // Load DB entries
84
+ this._loadDbEntries();
85
+
86
+ // Build BM25 index
87
+ this._buildIndex();
88
+ }
89
+
90
+ /**
91
+ * Append content to today's daily note.
92
+ */
93
+ appendDailyNote(content) {
94
+ if (!this.memoryDir || !content) return null;
95
+
96
+ try {
97
+ const today = new Date().toISOString().slice(0, 10);
98
+ const dailyDir = _deps.path.join(this.memoryDir, "daily");
99
+ const filePath = _deps.path.join(dailyDir, `${today}.md`);
100
+ const timestamp = new Date().toISOString().slice(11, 19);
101
+ const entry = `\n## ${timestamp}\n\n${content}\n`;
102
+
103
+ if (_deps.fs.existsSync(filePath)) {
104
+ _deps.fs.appendFileSync(filePath, entry, "utf-8");
105
+ } else {
106
+ _deps.fs.writeFileSync(
107
+ filePath,
108
+ `# Daily Note: ${today}\n${entry}`,
109
+ "utf-8",
110
+ );
111
+ }
112
+
113
+ // Rebuild index
114
+ this._loadRecentDailyNotes();
115
+ this._buildIndex();
116
+
117
+ return { date: today, path: filePath };
118
+ } catch (_err) {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Update a section of MEMORY.md.
125
+ * If section exists, replaces it. Otherwise appends.
126
+ */
127
+ updateMemoryFile(section, content) {
128
+ if (!this.memoryDir) return null;
129
+
130
+ try {
131
+ const filePath = _deps.path.join(this.memoryDir, "MEMORY.md");
132
+ let existing = "";
133
+ if (_deps.fs.existsSync(filePath)) {
134
+ existing = _deps.fs.readFileSync(filePath, "utf-8");
135
+ }
136
+
137
+ const sectionHeader = `## ${section}`;
138
+ const sectionIdx = existing.indexOf(sectionHeader);
139
+
140
+ if (sectionIdx >= 0) {
141
+ // Find next ## or end of file
142
+ const afterHeader = existing.indexOf(
143
+ "\n## ",
144
+ sectionIdx + sectionHeader.length,
145
+ );
146
+ const endIdx = afterHeader >= 0 ? afterHeader : existing.length;
147
+ const newContent =
148
+ existing.slice(0, sectionIdx) +
149
+ `${sectionHeader}\n\n${content}\n` +
150
+ existing.slice(endIdx);
151
+ _deps.fs.writeFileSync(filePath, newContent, "utf-8");
152
+ } else {
153
+ // Append new section
154
+ const append = existing
155
+ ? `\n${sectionHeader}\n\n${content}\n`
156
+ : `# Memory\n\n${sectionHeader}\n\n${content}\n`;
157
+ _deps.fs.writeFileSync(filePath, existing + append, "utf-8");
158
+ }
159
+
160
+ this._loadMemoryFile();
161
+ this._buildIndex();
162
+ return { path: filePath };
163
+ } catch (_err) {
164
+ return null;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * BM25 hybrid search across all memory sources.
170
+ */
171
+ hybridSearch(query, { topK = 5 } = {}) {
172
+ if (!this._bm25 || !query) return [];
173
+
174
+ try {
175
+ return this._bm25.search(query, { topK, threshold: 0.1 });
176
+ } catch (_err) {
177
+ return [];
178
+ }
179
+ }
180
+
181
+ /**
182
+ * Get relevant context for a query (used by CLIContextEngineering).
183
+ * Returns array of { content, source, score }.
184
+ */
185
+ getRelevantContext(query, limit = 3) {
186
+ if (!query) return [];
187
+
188
+ this.initialize();
189
+ const results = this.hybridSearch(query, { topK: limit });
190
+ return results.map((r) => ({
191
+ content: (r.doc.content || "").substring(0, 300),
192
+ source: r.doc.source || "memory",
193
+ score: r.score,
194
+ }));
195
+ }
196
+
197
+ /**
198
+ * Auto-summarize session messages and store key facts.
199
+ * Called at session end.
200
+ */
201
+ autoSummarize(sessionMessages) {
202
+ if (!sessionMessages || sessionMessages.length < 4) return [];
203
+
204
+ const facts = [];
205
+
206
+ // Extract tool usage patterns
207
+ const toolUses = sessionMessages.filter(
208
+ (m) => m.role === "tool" || m.tool_calls,
209
+ );
210
+ if (toolUses.length > 0) {
211
+ const toolNames = new Set();
212
+ for (const m of sessionMessages) {
213
+ if (m.tool_calls) {
214
+ for (const tc of m.tool_calls) {
215
+ toolNames.add(tc.function?.name || tc.name || "unknown");
216
+ }
217
+ }
218
+ }
219
+ if (toolNames.size > 0) {
220
+ facts.push(`Tools used: ${[...toolNames].join(", ")}`);
221
+ }
222
+ }
223
+
224
+ // Extract user questions/topics
225
+ const userMsgs = sessionMessages.filter((m) => m.role === "user");
226
+ if (userMsgs.length > 0) {
227
+ const topics = userMsgs
228
+ .slice(0, 3)
229
+ .map((m) => (m.content || "").substring(0, 60).replace(/\n/g, " "))
230
+ .filter(Boolean);
231
+ if (topics.length > 0) {
232
+ facts.push(`Topics discussed: ${topics.join("; ")}`);
233
+ }
234
+ }
235
+
236
+ // Store facts
237
+ for (const fact of facts) {
238
+ this._storeEntry(fact, "auto-summary");
239
+ }
240
+
241
+ // Append to daily note
242
+ if (facts.length > 0) {
243
+ this.appendDailyNote(
244
+ `Session summary:\n${facts.map((f) => `- ${f}`).join("\n")}`,
245
+ );
246
+ }
247
+
248
+ return facts;
249
+ }
250
+
251
+ /**
252
+ * Store a permanent memory entry in DB.
253
+ */
254
+ _storeEntry(content, source = "auto", importance = 0.5) {
255
+ if (!this.db) return null;
256
+ try {
257
+ const id = `pm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
258
+ this.db
259
+ .prepare(
260
+ "INSERT INTO permanent_memory (id, content, source, importance) VALUES (?, ?, ?, ?)",
261
+ )
262
+ .run(id, content, source, importance);
263
+ return id;
264
+ } catch (_err) {
265
+ return null;
266
+ }
267
+ }
268
+
269
+ // ─── Internal ───
270
+
271
+ _loadMemoryFile() {
272
+ if (!this.memoryDir) return;
273
+ try {
274
+ const filePath = _deps.path.join(this.memoryDir, "MEMORY.md");
275
+ if (_deps.fs.existsSync(filePath)) {
276
+ this._memoryFileContent = _deps.fs.readFileSync(filePath, "utf-8");
277
+ }
278
+ } catch (_err) {
279
+ this._memoryFileContent = "";
280
+ }
281
+ }
282
+
283
+ _loadRecentDailyNotes() {
284
+ if (!this.memoryDir) return;
285
+ this._dailyNotes = [];
286
+ try {
287
+ const dailyDir = _deps.path.join(this.memoryDir, "daily");
288
+ if (!_deps.fs.existsSync(dailyDir)) return;
289
+
290
+ const files = _deps.fs
291
+ .readdirSync(dailyDir)
292
+ .filter((f) => f.endsWith(".md"))
293
+ .sort()
294
+ .reverse()
295
+ .slice(0, 7);
296
+
297
+ for (const f of files) {
298
+ const content = _deps.fs.readFileSync(
299
+ _deps.path.join(dailyDir, f),
300
+ "utf-8",
301
+ );
302
+ this._dailyNotes.push({
303
+ date: f.replace(".md", ""),
304
+ content,
305
+ });
306
+ }
307
+ } catch (_err) {
308
+ // Non-critical
309
+ }
310
+ }
311
+
312
+ _loadDbEntries() {
313
+ if (!this.db) return;
314
+ this._dbEntries = [];
315
+ try {
316
+ this._dbEntries = this.db
317
+ .prepare(
318
+ "SELECT id, content, source, importance FROM permanent_memory ORDER BY importance DESC LIMIT 100",
319
+ )
320
+ .all();
321
+ } catch (_err) {
322
+ // Table may not exist
323
+ }
324
+ }
325
+
326
+ _buildIndex() {
327
+ const docs = [];
328
+
329
+ // MEMORY.md sections
330
+ if (this._memoryFileContent) {
331
+ const sections = this._memoryFileContent.split(/^## /m).filter(Boolean);
332
+ for (const section of sections) {
333
+ const firstLine = section.split("\n")[0].trim();
334
+ docs.push({
335
+ id: `memfile-${firstLine.substring(0, 30)}`,
336
+ title: firstLine,
337
+ content: section.substring(0, 500),
338
+ source: "MEMORY.md",
339
+ });
340
+ }
341
+ }
342
+
343
+ // Daily notes
344
+ for (const note of this._dailyNotes) {
345
+ docs.push({
346
+ id: `daily-${note.date}`,
347
+ title: `Daily Note ${note.date}`,
348
+ content: note.content.substring(0, 500),
349
+ source: "daily-note",
350
+ });
351
+ }
352
+
353
+ // DB entries
354
+ for (const entry of this._dbEntries) {
355
+ docs.push({
356
+ id: entry.id,
357
+ title: (entry.content || "").substring(0, 60),
358
+ content: entry.content || "",
359
+ source: entry.source || "db",
360
+ });
361
+ }
362
+
363
+ if (docs.length > 0) {
364
+ this._bm25 = new _deps.BM25Search();
365
+ this._bm25.indexDocuments(docs);
366
+ } else {
367
+ this._bm25 = null;
368
+ }
369
+ }
370
+ }
@@ -54,6 +54,26 @@ const WRITE_TOOLS = new Set([
54
54
  /**
55
55
  * A single item in an execution plan
56
56
  */
57
+ /**
58
+ * Risk weights for tool categories
59
+ */
60
+ const TOOL_RISK_WEIGHTS = {
61
+ read_file: 1,
62
+ search_files: 1,
63
+ list_dir: 1,
64
+ list_skills: 1,
65
+ write_file: 2,
66
+ edit_file: 2,
67
+ run_skill: 2,
68
+ run_shell: 3,
69
+ };
70
+
71
+ const IMPACT_MULTIPLIERS = {
72
+ low: 1,
73
+ medium: 2,
74
+ high: 3,
75
+ };
76
+
57
77
  export class PlanItem {
58
78
  constructor(data = {}) {
59
79
  this.id =
@@ -69,6 +89,16 @@ export class PlanItem {
69
89
  this.result = null;
70
90
  this.error = null;
71
91
  }
92
+
93
+ /**
94
+ * Calculate risk score for this item.
95
+ * Score = tool_weight × impact_multiplier
96
+ */
97
+ get riskScore() {
98
+ const toolWeight = TOOL_RISK_WEIGHTS[this.tool] || 1;
99
+ const impactMul = IMPACT_MULTIPLIERS[this.estimatedImpact] || 1;
100
+ return toolWeight * impactMul;
101
+ }
72
102
  }
73
103
 
74
104
  /**
@@ -102,6 +132,115 @@ export class ExecutionPlan {
102
132
  getItem(itemId) {
103
133
  return this.items.find((i) => i.id === itemId);
104
134
  }
135
+
136
+ /**
137
+ * Topological sort of items by dependencies.
138
+ * Returns items in execution order. Throws if cycle detected.
139
+ */
140
+ topologicalSort() {
141
+ const itemMap = new Map(this.items.map((i) => [i.id, i]));
142
+ const visited = new Set();
143
+ const visiting = new Set();
144
+ const sorted = [];
145
+
146
+ const visit = (id) => {
147
+ if (visited.has(id)) return;
148
+ if (visiting.has(id))
149
+ throw new Error(`Dependency cycle detected involving ${id}`);
150
+
151
+ visiting.add(id);
152
+ const item = itemMap.get(id);
153
+ if (item && item.dependencies) {
154
+ for (const depId of item.dependencies) {
155
+ if (itemMap.has(depId)) {
156
+ visit(depId);
157
+ }
158
+ }
159
+ }
160
+ visiting.delete(id);
161
+ visited.add(id);
162
+ if (item) sorted.push(item);
163
+ };
164
+
165
+ for (const item of this.items) {
166
+ visit(item.id);
167
+ }
168
+
169
+ return sorted;
170
+ }
171
+
172
+ /**
173
+ * Execute items in DAG topological order using provided executor.
174
+ * If a dependency fails, downstream items are marked as blocked.
175
+ *
176
+ * @param {function} executor - async (item) => result
177
+ * @returns {Array<{ item: PlanItem, success: boolean, result: any, error: string }>}
178
+ */
179
+ async executeInOrder(executor) {
180
+ const sorted = this.topologicalSort();
181
+ const results = [];
182
+ const failedIds = new Set();
183
+
184
+ for (const item of sorted) {
185
+ // Check if any dependency failed
186
+ const blocked = (item.dependencies || []).some((depId) =>
187
+ failedIds.has(depId),
188
+ );
189
+ if (blocked) {
190
+ item.status = PlanStatus.FAILED;
191
+ item.error = "Blocked by failed dependency";
192
+ failedIds.add(item.id);
193
+ results.push({ item, success: false, result: null, error: item.error });
194
+ continue;
195
+ }
196
+
197
+ item.status = PlanStatus.EXECUTING;
198
+ try {
199
+ const result = await executor(item);
200
+ item.status = PlanStatus.COMPLETED;
201
+ item.result = result;
202
+ results.push({ item, success: true, result, error: null });
203
+ } catch (err) {
204
+ item.status = PlanStatus.FAILED;
205
+ item.error = err.message;
206
+ failedIds.add(item.id);
207
+ results.push({
208
+ item,
209
+ success: false,
210
+ result: null,
211
+ error: err.message,
212
+ });
213
+ }
214
+ }
215
+
216
+ return results;
217
+ }
218
+
219
+ /**
220
+ * Calculate aggregate risk score for the plan.
221
+ */
222
+ getRiskAssessment() {
223
+ const scores = this.items.map((i) => i.riskScore);
224
+ const total = scores.reduce((sum, s) => sum + s, 0);
225
+ const max = Math.max(...scores, 0);
226
+ const avg = scores.length > 0 ? total / scores.length : 0;
227
+
228
+ let level = "low";
229
+ if (max >= 6 || avg >= 4) level = "high";
230
+ else if (max >= 4 || avg >= 2) level = "medium";
231
+
232
+ return {
233
+ level,
234
+ totalScore: total,
235
+ maxScore: max,
236
+ averageScore: Math.round(avg * 100) / 100,
237
+ itemScores: this.items.map((i) => ({
238
+ id: i.id,
239
+ title: i.title,
240
+ score: i.riskScore,
241
+ })),
242
+ };
243
+ }
105
244
  }
106
245
 
107
246
  /**
@@ -116,6 +255,14 @@ export class PlanModeManager extends EventEmitter {
116
255
  this.currentPlan = null;
117
256
  this.history = [];
118
257
  this.blockedToolLog = [];
258
+ this._hookDb = null;
259
+ }
260
+
261
+ /**
262
+ * Set DB reference for hook execution.
263
+ */
264
+ setHookDb(db) {
265
+ this._hookDb = db;
119
266
  }
120
267
 
121
268
  /**
@@ -141,6 +288,7 @@ export class PlanModeManager extends EventEmitter {
141
288
  this.blockedToolLog = [];
142
289
 
143
290
  this.emit("enter", { plan: this.currentPlan, state: this.state });
291
+ this._fireHook("PlanModeEnter", { planId: this.currentPlan.id });
144
292
  return { plan: this.currentPlan };
145
293
  }
146
294
 
@@ -217,6 +365,10 @@ export class PlanModeManager extends EventEmitter {
217
365
  plan: this.currentPlan,
218
366
  approvedCount: approvedItems.length,
219
367
  });
368
+ this._fireHook("PlanApproved", {
369
+ planId: this.currentPlan.id,
370
+ itemCount: approvedItems.length,
371
+ });
220
372
  return { plan: this.currentPlan, approvedCount: approvedItems.length };
221
373
  }
222
374
 
@@ -233,6 +385,7 @@ export class PlanModeManager extends EventEmitter {
233
385
  }
234
386
 
235
387
  this.state = PlanState.REJECTED;
388
+ this._fireHook("PlanRejected", { planId: this.currentPlan.id, reason });
236
389
  return this.exitPlanMode({ savePlan: true, reason: reason || "rejected" });
237
390
  }
238
391
 
@@ -297,6 +450,13 @@ export class PlanModeManager extends EventEmitter {
297
450
  }
298
451
  }
299
452
 
453
+ // Risk assessment
454
+ const risk = plan.getRiskAssessment();
455
+ lines.push("");
456
+ lines.push(
457
+ `**Risk**: ${risk.level} (total: ${risk.totalScore}, max: ${risk.maxScore}, avg: ${risk.averageScore})`,
458
+ );
459
+
300
460
  if (this.blockedToolLog.length > 0) {
301
461
  lines.push("");
302
462
  lines.push(
@@ -307,12 +467,63 @@ export class PlanModeManager extends EventEmitter {
307
467
  return lines.filter(Boolean).join("\n");
308
468
  }
309
469
 
470
+ /**
471
+ * Get risk assessment for current plan.
472
+ */
473
+ getRiskAssessment() {
474
+ if (!this.currentPlan) return null;
475
+ return this.currentPlan.getRiskAssessment();
476
+ }
477
+
478
+ /**
479
+ * Execute approved plan items in DAG order.
480
+ * @param {function} executor - async (item) => result
481
+ */
482
+ async executePlan(executor) {
483
+ if (!this.currentPlan) return { error: "No active plan" };
484
+ if (this.state !== PlanState.APPROVED)
485
+ return { error: "Plan not approved" };
486
+
487
+ this.state = PlanState.EXECUTING;
488
+ this.currentPlan.status = PlanState.EXECUTING;
489
+
490
+ const results = await this.currentPlan.executeInOrder(async (item) => {
491
+ this._fireHook("PlanItemExecute", {
492
+ planId: this.currentPlan.id,
493
+ itemId: item.id,
494
+ tool: item.tool,
495
+ });
496
+ return executor(item);
497
+ });
498
+
499
+ const allDone = results.every((r) => r.success);
500
+ this.state = allDone ? PlanState.COMPLETED : PlanState.COMPLETED;
501
+ this.currentPlan.status = allDone
502
+ ? PlanState.COMPLETED
503
+ : PlanState.COMPLETED;
504
+
505
+ return { results, success: allDone };
506
+ }
507
+
310
508
  /**
311
509
  * Get plans history
312
510
  */
313
511
  getHistory() {
314
512
  return this.history;
315
513
  }
514
+
515
+ /**
516
+ * Fire a hook event (best-effort, non-blocking).
517
+ */
518
+ _fireHook(eventName, context) {
519
+ if (!this._hookDb) return;
520
+ // Dynamic import to avoid circular deps
521
+ import("./hook-manager.js")
522
+ .then(({ executeHooks }) => {
523
+ executeHooks(this._hookDb, eventName, context).catch(() => {});
524
+ })
525
+ .catch(() => {});
526
+ }
316
527
  }
317
528
 
318
529
  // Singleton