@vibecheckai/cli 3.9.0 → 4.0.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 (88) hide show
  1. package/README.md +1 -1
  2. package/bin/runners/context/generators/cursor-enhanced.js +99 -13
  3. package/bin/runners/lib/unified-cli-output.js +16 -0
  4. package/bin/runners/runCI.js +353 -0
  5. package/bin/runners/runCheckpoint.js +2 -2
  6. package/mcp-server/.eslintrc.json +24 -0
  7. package/mcp-server/README.md +425 -135
  8. package/mcp-server/SPEC.md +583 -0
  9. package/mcp-server/configs/README.md +172 -0
  10. package/mcp-server/configs/claude-desktop-pro.json +31 -0
  11. package/mcp-server/configs/claude-desktop-with-workspace.json +25 -0
  12. package/mcp-server/configs/claude-desktop.json +19 -0
  13. package/mcp-server/configs/cursor-mcp.json +21 -0
  14. package/mcp-server/configs/windsurf-mcp.json +17 -0
  15. package/mcp-server/mcp-config.example.json +9 -0
  16. package/mcp-server/package.json +49 -34
  17. package/mcp-server/src/cli.ts +185 -0
  18. package/mcp-server/src/index.ts +85 -0
  19. package/mcp-server/src/server.ts +1933 -0
  20. package/mcp-server/src/services/cache-service.ts +466 -0
  21. package/mcp-server/src/services/cli-service.ts +345 -0
  22. package/mcp-server/src/services/context-manager.ts +717 -0
  23. package/mcp-server/src/services/firewall-service.ts +662 -0
  24. package/mcp-server/src/services/git-service.ts +671 -0
  25. package/mcp-server/src/services/index.ts +52 -0
  26. package/mcp-server/src/services/prompt-builder-service.ts +1031 -0
  27. package/mcp-server/src/services/session-service.ts +550 -0
  28. package/mcp-server/src/services/tier-service.ts +470 -0
  29. package/mcp-server/src/types.ts +351 -0
  30. package/mcp-server/tsconfig.json +16 -27
  31. package/package.json +6 -6
  32. package/mcp-server/.guardrail/audit/audit.log.jsonl +0 -2
  33. package/mcp-server/.specs/architecture.mdc +0 -90
  34. package/mcp-server/.specs/security.mdc +0 -30
  35. package/mcp-server/HARDENING_SUMMARY.md +0 -299
  36. package/mcp-server/agent-checkpoint.js +0 -364
  37. package/mcp-server/agent-firewall-interceptor.js +0 -500
  38. package/mcp-server/architect-tools.js +0 -707
  39. package/mcp-server/audit-mcp.js +0 -206
  40. package/mcp-server/authority-tools.js +0 -569
  41. package/mcp-server/codebase-architect-tools.js +0 -838
  42. package/mcp-server/conductor/conflict-resolver.js +0 -588
  43. package/mcp-server/conductor/execution-planner.js +0 -544
  44. package/mcp-server/conductor/index.js +0 -377
  45. package/mcp-server/conductor/lock-manager.js +0 -615
  46. package/mcp-server/conductor/request-queue.js +0 -550
  47. package/mcp-server/conductor/session-manager.js +0 -500
  48. package/mcp-server/conductor/tools.js +0 -510
  49. package/mcp-server/consolidated-tools.js +0 -1170
  50. package/mcp-server/deprecation-middleware.js +0 -282
  51. package/mcp-server/handlers/index.ts +0 -15
  52. package/mcp-server/handlers/tool-handler.ts +0 -593
  53. package/mcp-server/hygiene-tools.js +0 -428
  54. package/mcp-server/index-v1.js +0 -698
  55. package/mcp-server/index.js +0 -2940
  56. package/mcp-server/intelligence-tools.js +0 -664
  57. package/mcp-server/intent-drift-tools.js +0 -873
  58. package/mcp-server/intent-firewall-interceptor.js +0 -529
  59. package/mcp-server/lib/api-client.cjs +0 -13
  60. package/mcp-server/lib/cache-wrapper.cjs +0 -383
  61. package/mcp-server/lib/error-envelope.js +0 -138
  62. package/mcp-server/lib/executor.ts +0 -499
  63. package/mcp-server/lib/index.ts +0 -29
  64. package/mcp-server/lib/logger.cjs +0 -30
  65. package/mcp-server/lib/rate-limiter.js +0 -166
  66. package/mcp-server/lib/sandbox.test.ts +0 -519
  67. package/mcp-server/lib/sandbox.ts +0 -395
  68. package/mcp-server/lib/types.ts +0 -267
  69. package/mcp-server/logger.js +0 -173
  70. package/mcp-server/manifest.json +0 -473
  71. package/mcp-server/mdc-generator.js +0 -298
  72. package/mcp-server/premium-tools.js +0 -1275
  73. package/mcp-server/proof-tools.js +0 -571
  74. package/mcp-server/registry/tool-registry.js +0 -586
  75. package/mcp-server/registry/tools.json +0 -619
  76. package/mcp-server/registry.test.ts +0 -340
  77. package/mcp-server/test-mcp.js +0 -108
  78. package/mcp-server/test-tools.js +0 -36
  79. package/mcp-server/tests/tier-gating.test.js +0 -297
  80. package/mcp-server/tier-auth.js +0 -767
  81. package/mcp-server/tools/index.js +0 -72
  82. package/mcp-server/tools-reorganized.ts +0 -244
  83. package/mcp-server/tools-v3.js +0 -1004
  84. package/mcp-server/truth-context.js +0 -622
  85. package/mcp-server/truth-firewall-tools.js +0 -2183
  86. package/mcp-server/vibecheck-2.0-tools.js +0 -761
  87. package/mcp-server/vibecheck-mcp-server-3.2.0.tgz +0 -0
  88. package/mcp-server/vibecheck-tools.js +0 -1075
@@ -0,0 +1,550 @@
1
+ /**
2
+ * Session Service - Comprehensive agent session management
3
+ * Tracks agent interactions, provides context continuity, and enables audit trails
4
+ */
5
+
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import * as os from 'os';
9
+ import * as crypto from 'crypto';
10
+
11
+ // ═══════════════════════════════════════════════════════════════════════════════
12
+ // Types
13
+ // ═══════════════════════════════════════════════════════════════════════════════
14
+
15
+ export interface AgentSession {
16
+ id: string;
17
+ agentId?: string;
18
+ agentName?: string;
19
+ startedAt: string;
20
+ lastActivityAt: string;
21
+ expiresAt: string;
22
+ status: 'active' | 'idle' | 'expired' | 'terminated';
23
+
24
+ // Context
25
+ workspacePath: string;
26
+ currentIntent?: SessionIntent;
27
+
28
+ // Metrics
29
+ metrics: SessionMetrics;
30
+
31
+ // History
32
+ toolCalls: ToolCallRecord[];
33
+ stateChanges: StateChange[];
34
+
35
+ // Security
36
+ securityContext: SecurityContext;
37
+ }
38
+
39
+ export interface SessionIntent {
40
+ summary: string;
41
+ constraints: string[];
42
+ setAt: string;
43
+ hash: string;
44
+ }
45
+
46
+ export interface SessionMetrics {
47
+ totalToolCalls: number;
48
+ successfulCalls: number;
49
+ failedCalls: number;
50
+ blockedCalls: number;
51
+ totalDurationMs: number;
52
+ averageLatencyMs: number;
53
+
54
+ // By category
55
+ callsByCategory: Record<string, number>;
56
+
57
+ // Firewall metrics
58
+ firewallViolations: number;
59
+ claimsVerified: number;
60
+ claimsPassed: number;
61
+ claimsFailed: number;
62
+ }
63
+
64
+ export interface ToolCallRecord {
65
+ id: string;
66
+ timestamp: string;
67
+ toolName: string;
68
+ category: 'cli' | 'firewall' | 'prompt' | 'utility';
69
+ tier: 'free' | 'pro';
70
+
71
+ // Input/Output
72
+ inputSummary: string;
73
+ outputSummary?: string;
74
+
75
+ // Result
76
+ status: 'success' | 'error' | 'blocked';
77
+ durationMs: number;
78
+ errorMessage?: string;
79
+ blockReason?: string;
80
+
81
+ // Context
82
+ intentHash?: string;
83
+ firewallMode?: string;
84
+ }
85
+
86
+ export interface StateChange {
87
+ timestamp: string;
88
+ type: 'intent_set' | 'intent_cleared' | 'mode_changed' | 'checkpoint_created' | 'fix_applied';
89
+ before: string;
90
+ after: string;
91
+ metadata?: Record<string, unknown>;
92
+ }
93
+
94
+ export interface SecurityContext {
95
+ tier: 'free' | 'pro';
96
+ firewallMode: 'off' | 'observe' | 'enforce';
97
+ hasActiveIntent: boolean;
98
+ intentHash?: string;
99
+ lastVerificationAt?: string;
100
+ trustScore: number; // 0-100
101
+ }
102
+
103
+ export interface SessionSummary {
104
+ id: string;
105
+ duration: string;
106
+ status: 'active' | 'idle' | 'expired' | 'terminated';
107
+ metrics: {
108
+ totalCalls: number;
109
+ successRate: string;
110
+ avgLatency: string;
111
+ };
112
+ intent?: string;
113
+ recentActivity: string[];
114
+ }
115
+
116
+ // ═══════════════════════════════════════════════════════════════════════════════
117
+ // Session Service Implementation
118
+ // ═══════════════════════════════════════════════════════════════════════════════
119
+
120
+ export class SessionService {
121
+ private currentSession: AgentSession | null = null;
122
+ private sessionStorePath: string;
123
+ private readonly SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
124
+ private readonly IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
125
+ private readonly MAX_TOOL_CALLS_STORED = 500;
126
+
127
+ constructor(workspacePath: string) {
128
+ this.sessionStorePath = path.join(os.homedir(), '.vibecheck', 'sessions');
129
+ this.ensureSessionStore();
130
+ this.currentSession = this.createSession(workspacePath);
131
+ }
132
+
133
+ private ensureSessionStore(): void {
134
+ if (!fs.existsSync(this.sessionStorePath)) {
135
+ fs.mkdirSync(this.sessionStorePath, { recursive: true });
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Create a new session
141
+ */
142
+ private createSession(workspacePath: string): AgentSession {
143
+ const now = new Date();
144
+ const session: AgentSession = {
145
+ id: this.generateSessionId(),
146
+ startedAt: now.toISOString(),
147
+ lastActivityAt: now.toISOString(),
148
+ expiresAt: new Date(now.getTime() + this.SESSION_TIMEOUT_MS).toISOString(),
149
+ status: 'active',
150
+ workspacePath,
151
+
152
+ metrics: {
153
+ totalToolCalls: 0,
154
+ successfulCalls: 0,
155
+ failedCalls: 0,
156
+ blockedCalls: 0,
157
+ totalDurationMs: 0,
158
+ averageLatencyMs: 0,
159
+ callsByCategory: {},
160
+ firewallViolations: 0,
161
+ claimsVerified: 0,
162
+ claimsPassed: 0,
163
+ claimsFailed: 0,
164
+ },
165
+
166
+ toolCalls: [],
167
+ stateChanges: [],
168
+
169
+ securityContext: {
170
+ tier: 'free',
171
+ firewallMode: 'off',
172
+ hasActiveIntent: false,
173
+ trustScore: 100,
174
+ },
175
+ };
176
+
177
+ this.saveSession(session);
178
+ return session;
179
+ }
180
+
181
+ private generateSessionId(): string {
182
+ const timestamp = Date.now().toString(36);
183
+ const random = crypto.randomBytes(4).toString('hex');
184
+ return `vcs_${timestamp}_${random}`;
185
+ }
186
+
187
+ /**
188
+ * Record a tool call
189
+ */
190
+ recordToolCall(record: Omit<ToolCallRecord, 'id' | 'timestamp'>): void {
191
+ if (!this.currentSession) return;
192
+
193
+ const fullRecord: ToolCallRecord = {
194
+ ...record,
195
+ id: `tc_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`,
196
+ timestamp: new Date().toISOString(),
197
+ };
198
+
199
+ // Add to history (keep last N calls)
200
+ this.currentSession.toolCalls.push(fullRecord);
201
+ if (this.currentSession.toolCalls.length > this.MAX_TOOL_CALLS_STORED) {
202
+ this.currentSession.toolCalls = this.currentSession.toolCalls.slice(-this.MAX_TOOL_CALLS_STORED);
203
+ }
204
+
205
+ // Update metrics
206
+ this.currentSession.metrics.totalToolCalls++;
207
+ this.currentSession.metrics.totalDurationMs += record.durationMs;
208
+ this.currentSession.metrics.averageLatencyMs =
209
+ this.currentSession.metrics.totalDurationMs / this.currentSession.metrics.totalToolCalls;
210
+
211
+ if (record.status === 'success') {
212
+ this.currentSession.metrics.successfulCalls++;
213
+ } else if (record.status === 'error') {
214
+ this.currentSession.metrics.failedCalls++;
215
+ } else if (record.status === 'blocked') {
216
+ this.currentSession.metrics.blockedCalls++;
217
+ }
218
+
219
+ // Update category counts
220
+ this.currentSession.metrics.callsByCategory[record.category] =
221
+ (this.currentSession.metrics.callsByCategory[record.category] || 0) + 1;
222
+
223
+ // Update activity
224
+ this.currentSession.lastActivityAt = new Date().toISOString();
225
+ this.currentSession.status = 'active';
226
+
227
+ this.saveSession(this.currentSession);
228
+ }
229
+
230
+ /**
231
+ * Record a state change
232
+ */
233
+ recordStateChange(change: Omit<StateChange, 'timestamp'>): void {
234
+ if (!this.currentSession) return;
235
+
236
+ this.currentSession.stateChanges.push({
237
+ ...change,
238
+ timestamp: new Date().toISOString(),
239
+ });
240
+
241
+ // Keep last 100 state changes
242
+ if (this.currentSession.stateChanges.length > 100) {
243
+ this.currentSession.stateChanges = this.currentSession.stateChanges.slice(-100);
244
+ }
245
+
246
+ this.saveSession(this.currentSession);
247
+ }
248
+
249
+ /**
250
+ * Update security context
251
+ */
252
+ updateSecurityContext(updates: Partial<SecurityContext>): void {
253
+ if (!this.currentSession) return;
254
+
255
+ this.currentSession.securityContext = {
256
+ ...this.currentSession.securityContext,
257
+ ...updates,
258
+ };
259
+
260
+ this.saveSession(this.currentSession);
261
+ }
262
+
263
+ /**
264
+ * Set current intent
265
+ */
266
+ setIntent(summary: string, constraints: string[]): void {
267
+ if (!this.currentSession) return;
268
+
269
+ const intent: SessionIntent = {
270
+ summary,
271
+ constraints,
272
+ setAt: new Date().toISOString(),
273
+ hash: this.hashIntent(summary, constraints),
274
+ };
275
+
276
+ const previousIntent = this.currentSession.currentIntent?.summary || 'none';
277
+
278
+ this.currentSession.currentIntent = intent;
279
+ this.currentSession.securityContext.hasActiveIntent = true;
280
+ this.currentSession.securityContext.intentHash = intent.hash;
281
+
282
+ this.recordStateChange({
283
+ type: 'intent_set',
284
+ before: previousIntent,
285
+ after: summary,
286
+ metadata: { constraints },
287
+ });
288
+ }
289
+
290
+ /**
291
+ * Clear current intent
292
+ */
293
+ clearIntent(): void {
294
+ if (!this.currentSession) return;
295
+
296
+ const previousIntent = this.currentSession.currentIntent?.summary || 'none';
297
+
298
+ this.currentSession.currentIntent = undefined;
299
+ this.currentSession.securityContext.hasActiveIntent = false;
300
+ this.currentSession.securityContext.intentHash = undefined;
301
+
302
+ this.recordStateChange({
303
+ type: 'intent_cleared',
304
+ before: previousIntent,
305
+ after: 'none',
306
+ });
307
+ }
308
+
309
+ private hashIntent(summary: string, constraints: string[]): string {
310
+ const content = `${summary}|${constraints.sort().join('|')}`;
311
+ return crypto.createHash('sha256').update(content).digest('hex').substring(0, 16);
312
+ }
313
+
314
+ /**
315
+ * Record firewall violation
316
+ */
317
+ recordFirewallViolation(): void {
318
+ if (!this.currentSession) return;
319
+
320
+ this.currentSession.metrics.firewallViolations++;
321
+ this.currentSession.securityContext.trustScore = Math.max(
322
+ 0,
323
+ this.currentSession.securityContext.trustScore - 10
324
+ );
325
+
326
+ this.saveSession(this.currentSession);
327
+ }
328
+
329
+ /**
330
+ * Record claim verification
331
+ */
332
+ recordClaimVerification(passed: boolean): void {
333
+ if (!this.currentSession) return;
334
+
335
+ this.currentSession.metrics.claimsVerified++;
336
+ if (passed) {
337
+ this.currentSession.metrics.claimsPassed++;
338
+ // Slightly increase trust score on passed verification
339
+ this.currentSession.securityContext.trustScore = Math.min(
340
+ 100,
341
+ this.currentSession.securityContext.trustScore + 2
342
+ );
343
+ } else {
344
+ this.currentSession.metrics.claimsFailed++;
345
+ // Decrease trust score on failed verification
346
+ this.currentSession.securityContext.trustScore = Math.max(
347
+ 0,
348
+ this.currentSession.securityContext.trustScore - 15
349
+ );
350
+ }
351
+
352
+ this.currentSession.securityContext.lastVerificationAt = new Date().toISOString();
353
+ this.saveSession(this.currentSession);
354
+ }
355
+
356
+ /**
357
+ * Get current session
358
+ */
359
+ getSession(): AgentSession | null {
360
+ return this.currentSession;
361
+ }
362
+
363
+ /**
364
+ * Get session summary
365
+ */
366
+ getSessionSummary(): SessionSummary | null {
367
+ if (!this.currentSession) return null;
368
+
369
+ const session = this.currentSession;
370
+ const startTime = new Date(session.startedAt);
371
+ const now = new Date();
372
+ const durationMs = now.getTime() - startTime.getTime();
373
+
374
+ const hours = Math.floor(durationMs / (1000 * 60 * 60));
375
+ const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
376
+ const seconds = Math.floor((durationMs % (1000 * 60)) / 1000);
377
+
378
+ const successRate = session.metrics.totalToolCalls > 0
379
+ ? ((session.metrics.successfulCalls / session.metrics.totalToolCalls) * 100).toFixed(1)
380
+ : '100';
381
+
382
+ const recentCalls = session.toolCalls.slice(-5).map(tc =>
383
+ `${tc.toolName} (${tc.status})`
384
+ );
385
+
386
+ return {
387
+ id: session.id,
388
+ duration: `${hours}h ${minutes}m ${seconds}s`,
389
+ status: session.status,
390
+ metrics: {
391
+ totalCalls: session.metrics.totalToolCalls,
392
+ successRate: `${successRate}%`,
393
+ avgLatency: `${Math.round(session.metrics.averageLatencyMs)}ms`,
394
+ },
395
+ intent: session.currentIntent?.summary,
396
+ recentActivity: recentCalls,
397
+ };
398
+ }
399
+
400
+ /**
401
+ * Get detailed session metrics
402
+ */
403
+ getDetailedMetrics(): SessionMetrics & { trustScore: number; sessionDuration: string } | null {
404
+ if (!this.currentSession) return null;
405
+
406
+ const startTime = new Date(this.currentSession.startedAt);
407
+ const now = new Date();
408
+ const durationMs = now.getTime() - startTime.getTime();
409
+ const hours = Math.floor(durationMs / (1000 * 60 * 60));
410
+ const minutes = Math.floor((durationMs % (1000 * 60 * 60)) / (1000 * 60));
411
+
412
+ return {
413
+ ...this.currentSession.metrics,
414
+ trustScore: this.currentSession.securityContext.trustScore,
415
+ sessionDuration: `${hours}h ${minutes}m`,
416
+ };
417
+ }
418
+
419
+ /**
420
+ * Get tool call history
421
+ */
422
+ getToolCallHistory(limit?: number): ToolCallRecord[] {
423
+ if (!this.currentSession) return [];
424
+
425
+ const calls = this.currentSession.toolCalls;
426
+ return limit ? calls.slice(-limit) : calls;
427
+ }
428
+
429
+ /**
430
+ * Get state change history
431
+ */
432
+ getStateChangeHistory(limit?: number): StateChange[] {
433
+ if (!this.currentSession) return [];
434
+
435
+ const changes = this.currentSession.stateChanges;
436
+ return limit ? changes.slice(-limit) : changes;
437
+ }
438
+
439
+ /**
440
+ * Check if session is healthy
441
+ */
442
+ isSessionHealthy(): { healthy: boolean; issues: string[] } {
443
+ if (!this.currentSession) {
444
+ return { healthy: false, issues: ['No active session'] };
445
+ }
446
+
447
+ const issues: string[] = [];
448
+ const session = this.currentSession;
449
+
450
+ // Check expiry
451
+ if (new Date(session.expiresAt) <= new Date()) {
452
+ issues.push('Session has expired');
453
+ }
454
+
455
+ // Check trust score
456
+ if (session.securityContext.trustScore < 50) {
457
+ issues.push(`Low trust score (${session.securityContext.trustScore}/100)`);
458
+ }
459
+
460
+ // Check error rate
461
+ if (session.metrics.totalToolCalls > 10) {
462
+ const errorRate = session.metrics.failedCalls / session.metrics.totalToolCalls;
463
+ if (errorRate > 0.3) {
464
+ issues.push(`High error rate (${(errorRate * 100).toFixed(1)}%)`);
465
+ }
466
+ }
467
+
468
+ // Check firewall violations
469
+ if (session.metrics.firewallViolations > 5) {
470
+ issues.push(`Multiple firewall violations (${session.metrics.firewallViolations})`);
471
+ }
472
+
473
+ return {
474
+ healthy: issues.length === 0,
475
+ issues,
476
+ };
477
+ }
478
+
479
+ /**
480
+ * Terminate session
481
+ */
482
+ terminateSession(reason?: string): void {
483
+ if (!this.currentSession) return;
484
+
485
+ this.currentSession.status = 'terminated';
486
+ this.recordStateChange({
487
+ type: 'mode_changed',
488
+ before: 'active',
489
+ after: 'terminated',
490
+ metadata: { reason },
491
+ });
492
+
493
+ this.saveSession(this.currentSession);
494
+ }
495
+
496
+ /**
497
+ * Save session to disk
498
+ */
499
+ private saveSession(session: AgentSession): void {
500
+ try {
501
+ const filePath = path.join(this.sessionStorePath, `${session.id}.json`);
502
+ fs.writeFileSync(filePath, JSON.stringify(session, null, 2));
503
+ } catch {
504
+ // Failed to save - continue anyway
505
+ }
506
+ }
507
+
508
+ /**
509
+ * Load session from disk
510
+ */
511
+ loadSession(sessionId: string): AgentSession | null {
512
+ try {
513
+ const filePath = path.join(this.sessionStorePath, `${sessionId}.json`);
514
+ if (fs.existsSync(filePath)) {
515
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
516
+ }
517
+ } catch {
518
+ // Failed to load
519
+ }
520
+ return null;
521
+ }
522
+
523
+ /**
524
+ * List recent sessions
525
+ */
526
+ listRecentSessions(limit: number = 10): { id: string; startedAt: string; status: string }[] {
527
+ try {
528
+ const files = fs.readdirSync(this.sessionStorePath)
529
+ .filter(f => f.endsWith('.json'))
530
+ .map(f => {
531
+ const filePath = path.join(this.sessionStorePath, f);
532
+ const stats = fs.statSync(filePath);
533
+ return { file: f, mtime: stats.mtime };
534
+ })
535
+ .sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
536
+ .slice(0, limit);
537
+
538
+ return files.map(f => {
539
+ const session = this.loadSession(f.file.replace('.json', ''));
540
+ return session ? {
541
+ id: session.id,
542
+ startedAt: session.startedAt,
543
+ status: session.status,
544
+ } : null;
545
+ }).filter((s): s is { id: string; startedAt: string; status: string } => s !== null);
546
+ } catch {
547
+ return [];
548
+ }
549
+ }
550
+ }