claude-brain 0.9.3 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/VERSION +1 -1
- package/assets/CLAUDE.md +9 -1
- package/package.json +1 -1
- package/src/automation/phase12-manager.ts +456 -0
- package/src/automation/project-detector.ts +13 -0
- package/src/automation/repo-scanner.ts +205 -0
- package/src/cli/bin.ts +30 -0
- package/src/cli/commands/git-hook.ts +189 -0
- package/src/cli/commands/hooks.ts +8 -9
- package/src/cli/commands/init.ts +98 -0
- package/src/cli/commands/serve.ts +7 -20
- package/src/cli/commands/update.ts +3 -3
- package/src/config/defaults.ts +4 -1
- package/src/config/schema.ts +27 -7
- package/src/hooks/brain-hook.ts +8 -6
- package/src/hooks/capture.ts +9 -2
- package/src/hooks/git-capture.ts +94 -0
- package/src/hooks/git-hook-installer.ts +197 -0
- package/src/hooks/index.ts +1 -0
- package/src/hooks/session-tracker.ts +79 -3
- package/src/hooks/types.ts +1 -1
- package/src/intelligence/index.ts +24 -0
- package/src/knowledge/graph/builder.ts +26 -0
- package/src/memory/chroma/store.ts +18 -2
- package/src/memory/episodic/manager.ts +17 -0
- package/src/memory/index.ts +48 -18
- package/src/phase12/index.ts +3 -454
- package/src/routing/intent-classifier.ts +107 -9
- package/src/routing/response-filter.ts +50 -17
- package/src/routing/router.ts +472 -224
- package/src/routing/search-engine.ts +464 -0
- package/src/routing/types.ts +84 -0
- package/src/server/handlers/call-tool.ts +4 -49
- package/src/server/handlers/tools/analyze-decision-evolution.ts +1 -1
- package/src/server/handlers/tools/detect-trends.ts +1 -1
- package/src/server/handlers/tools/find-cross-project-patterns.ts +1 -1
- package/src/server/handlers/tools/get-decision-timeline.ts +2 -2
- package/src/server/handlers/tools/get-recommendations.ts +1 -1
- package/src/server/handlers/tools/index.ts +5 -7
- package/src/server/handlers/tools/what-if-analysis.ts +1 -1
- package/src/server/providers/resources.ts +195 -0
- package/src/server/services.ts +81 -6
- package/src/tools/schemas.ts +7 -329
- package/src/utils/phase12-helper.ts +2 -2
- package/src/utils/timing.ts +47 -0
- package/src/vault/writer.ts +22 -2
- /package/src/{cross-project → intelligence/cross-project}/affinity.ts +0 -0
- /package/src/{cross-project → intelligence/cross-project}/generalizer.ts +0 -0
- /package/src/{cross-project → intelligence/cross-project}/index.ts +0 -0
- /package/src/{cross-project → intelligence/cross-project}/transfer.ts +0 -0
- /package/src/{optimization → intelligence/optimization}/index.ts +0 -0
- /package/src/{optimization → intelligence/optimization}/precompute.ts +0 -0
- /package/src/{optimization → intelligence/optimization}/semantic-cache.ts +0 -0
- /package/src/{prediction → intelligence/prediction}/context-anticipator.ts +0 -0
- /package/src/{prediction → intelligence/prediction}/decision-predictor.ts +0 -0
- /package/src/{prediction → intelligence/prediction}/index.ts +0 -0
- /package/src/{prediction → intelligence/prediction}/recommender.ts +0 -0
- /package/src/{reasoning → intelligence/reasoning}/chain-retrieval.ts +0 -0
- /package/src/{reasoning → intelligence/reasoning}/counterfactual.ts +0 -0
- /package/src/{reasoning → intelligence/reasoning}/index.ts +0 -0
- /package/src/{reasoning → intelligence/reasoning}/synthesizer.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/evolution.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/index.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/query-processor.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/timeline.ts +0 -0
- /package/src/{temporal → intelligence/temporal}/trends.ts +0 -0
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
getVaultService,
|
|
10
10
|
getContextService,
|
|
11
11
|
getMemoryService,
|
|
12
|
+
getEpisodeService,
|
|
13
|
+
getSessionTracker,
|
|
12
14
|
isServicesInitialized
|
|
13
15
|
} from '@/server/services'
|
|
14
16
|
import { isPhase12Available, getPhase12Instance } from '@/utils/phase12-helper'
|
|
@@ -42,6 +44,12 @@ export class ResourceProvider {
|
|
|
42
44
|
const detectedProject = await this.detectCurrentProject()
|
|
43
45
|
|
|
44
46
|
const resources: ResourceItem[] = [
|
|
47
|
+
{
|
|
48
|
+
uri: 'brain://context/auto',
|
|
49
|
+
name: 'Auto Context',
|
|
50
|
+
description: 'Session context auto-loaded by brain',
|
|
51
|
+
mimeType: 'text/markdown'
|
|
52
|
+
},
|
|
45
53
|
{
|
|
46
54
|
uri: 'project://list',
|
|
47
55
|
name: 'All Projects',
|
|
@@ -88,6 +96,10 @@ export class ResourceProvider {
|
|
|
88
96
|
async readResource(uri: string) {
|
|
89
97
|
this.logger.info({ uri }, 'Reading resource')
|
|
90
98
|
|
|
99
|
+
if (uri.startsWith('brain://')) {
|
|
100
|
+
return await this.getBrainResource(uri)
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
if (uri === 'project://list') {
|
|
92
104
|
return await this.getProjectListResource()
|
|
93
105
|
}
|
|
@@ -372,6 +384,189 @@ export class ResourceProvider {
|
|
|
372
384
|
}
|
|
373
385
|
}
|
|
374
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Handle brain:// resource URIs
|
|
389
|
+
*/
|
|
390
|
+
private async getBrainResource(uri: string) {
|
|
391
|
+
if (uri === 'brain://context/auto') {
|
|
392
|
+
const content = await this.getAutoContextContent()
|
|
393
|
+
return {
|
|
394
|
+
contents: [
|
|
395
|
+
{
|
|
396
|
+
uri,
|
|
397
|
+
mimeType: 'text/markdown',
|
|
398
|
+
text: content
|
|
399
|
+
}
|
|
400
|
+
]
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
throw new Error(`Unknown brain resource URI: ${uri}`)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Phase 21: Build aggregated auto-context payload
|
|
409
|
+
* Combines last session summary, recent decisions, corrections,
|
|
410
|
+
* cross-project knowledge, and current session info.
|
|
411
|
+
*/
|
|
412
|
+
private async getAutoContextContent(): Promise<string> {
|
|
413
|
+
if (!isServicesInitialized()) {
|
|
414
|
+
return '# Auto Context\n\nServices not initialized.'
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
const sections: string[] = ['# Auto Context\n']
|
|
418
|
+
const detectedProject = await this.detectCurrentProject()
|
|
419
|
+
const projectName = detectedProject?.name
|
|
420
|
+
|
|
421
|
+
// 1. Last session summary
|
|
422
|
+
try {
|
|
423
|
+
const episodeService = getEpisodeService()
|
|
424
|
+
if (episodeService) {
|
|
425
|
+
const recentEpisodes = await episodeService.getRecentEpisodes(projectName, 1)
|
|
426
|
+
if (recentEpisodes.length > 0 && recentEpisodes[0].summary?.brief) {
|
|
427
|
+
const ep = recentEpisodes[0]
|
|
428
|
+
sections.push('## Last Session\n')
|
|
429
|
+
sections.push(ep.summary!.brief)
|
|
430
|
+
if (ep.summary!.key_topics.length > 0) {
|
|
431
|
+
sections.push(`\n**Topics:** ${ep.summary!.key_topics.join(', ')}`)
|
|
432
|
+
}
|
|
433
|
+
sections.push('')
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
} catch (err) {
|
|
437
|
+
this.logger.debug({ err }, 'Failed to get last session for auto-context')
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// 2. Current session info
|
|
441
|
+
try {
|
|
442
|
+
const tracker = getSessionTracker()
|
|
443
|
+
if (tracker) {
|
|
444
|
+
const stats = tracker.getStats()
|
|
445
|
+
if (stats.activeSessions > 0) {
|
|
446
|
+
sections.push('## Current Session\n')
|
|
447
|
+
sections.push(`Active sessions: ${stats.activeSessions}, items tracked: ${stats.totalItems}`)
|
|
448
|
+
sections.push('')
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} catch (err) {
|
|
452
|
+
this.logger.debug({ err }, 'Failed to get current session for auto-context')
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// 3. Recent decisions
|
|
456
|
+
try {
|
|
457
|
+
const memory = getMemoryService()
|
|
458
|
+
const searchProject = projectName || 'general'
|
|
459
|
+
const decisions = await memory.searchRaw(searchProject, {
|
|
460
|
+
project: searchProject,
|
|
461
|
+
limit: 5,
|
|
462
|
+
minSimilarity: 0.1
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
if (decisions.length > 0) {
|
|
466
|
+
sections.push('## Recent Decisions\n')
|
|
467
|
+
for (const d of decisions) {
|
|
468
|
+
const title = d.decision || d.content || 'Untitled'
|
|
469
|
+
const truncated = title.length > 120 ? title.slice(0, 120) + '...' : title
|
|
470
|
+
sections.push(`- ${truncated}`)
|
|
471
|
+
}
|
|
472
|
+
sections.push('')
|
|
473
|
+
}
|
|
474
|
+
} catch (err) {
|
|
475
|
+
this.logger.debug({ err }, 'Failed to get decisions for auto-context')
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// 4. Corrections (watch out for)
|
|
479
|
+
try {
|
|
480
|
+
const memory = getMemoryService()
|
|
481
|
+
if (typeof memory.getCorrections === 'function') {
|
|
482
|
+
const corrections = await memory.getCorrections(projectName || 'general', { limit: 3 })
|
|
483
|
+
if (corrections && corrections.length > 0) {
|
|
484
|
+
sections.push('## Watch Out For\n')
|
|
485
|
+
for (const c of corrections) {
|
|
486
|
+
const text = typeof c === 'string' ? c : (c.content || c.correction || JSON.stringify(c))
|
|
487
|
+
const truncated = text.length > 120 ? text.slice(0, 120) + '...' : text
|
|
488
|
+
sections.push(`- ${truncated}`)
|
|
489
|
+
}
|
|
490
|
+
sections.push('')
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} catch (err) {
|
|
494
|
+
this.logger.debug({ err }, 'Failed to get corrections for auto-context')
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 5. Cross-project knowledge
|
|
498
|
+
if (projectName) {
|
|
499
|
+
try {
|
|
500
|
+
const vault = getVaultService()
|
|
501
|
+
const allProjects = await vault.reader.getProjectDirectories(vault.paths.projects)
|
|
502
|
+
const otherProjects = allProjects.filter(p => p !== projectName)
|
|
503
|
+
|
|
504
|
+
if (otherProjects.length > 0) {
|
|
505
|
+
const memory = getMemoryService()
|
|
506
|
+
if (memory.isChromaDBEnabled()) {
|
|
507
|
+
const { KnowledgeTransfer } = await import('@/intelligence/cross-project/transfer')
|
|
508
|
+
const transfer = new KnowledgeTransfer(
|
|
509
|
+
this.logger,
|
|
510
|
+
memory.chroma.collections,
|
|
511
|
+
memory.chroma.embeddings
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
const transferItems: string[] = []
|
|
515
|
+
// Check up to 3 other projects for transferable knowledge
|
|
516
|
+
for (const other of otherProjects.slice(0, 3)) {
|
|
517
|
+
try {
|
|
518
|
+
const result = await transfer.findTransferable(other, projectName, { limit: 2, minRelevance: 0.5 })
|
|
519
|
+
for (const item of result.items) {
|
|
520
|
+
const truncated = item.content.length > 100 ? item.content.slice(0, 100) + '...' : item.content
|
|
521
|
+
transferItems.push(`- [${item.type} from ${other}] ${truncated}`)
|
|
522
|
+
}
|
|
523
|
+
} catch {
|
|
524
|
+
// Skip failed transfers
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
if (transferItems.length > 0) {
|
|
529
|
+
sections.push('## From Other Projects\n')
|
|
530
|
+
sections.push(...transferItems)
|
|
531
|
+
sections.push('')
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
} catch (err) {
|
|
536
|
+
this.logger.debug({ err }, 'Failed to get cross-project knowledge for auto-context')
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// If no sections beyond the header, try auto-scan for first-session value
|
|
541
|
+
if (sections.length <= 1) {
|
|
542
|
+
try {
|
|
543
|
+
const cwd = process.cwd()
|
|
544
|
+
const { scanRepo } = await import('@/automation/repo-scanner')
|
|
545
|
+
const repoContext = await scanRepo(cwd)
|
|
546
|
+
if (repoContext.name) {
|
|
547
|
+
sections.push('## Project Detected\n')
|
|
548
|
+
sections.push(`**Name:** ${repoContext.name}`)
|
|
549
|
+
if (repoContext.techStack.length > 0) {
|
|
550
|
+
sections.push(`**Tech stack:** ${repoContext.techStack.join(', ')}`)
|
|
551
|
+
}
|
|
552
|
+
if (repoContext.description) {
|
|
553
|
+
const desc = repoContext.description.split('\n')[0]?.slice(0, 200) || ''
|
|
554
|
+
sections.push(`**Description:** ${desc}`)
|
|
555
|
+
}
|
|
556
|
+
sections.push('')
|
|
557
|
+
}
|
|
558
|
+
} catch {
|
|
559
|
+
// Scan failed, continue with empty context
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
if (sections.length <= 1) {
|
|
564
|
+
return '# Auto Context\n\nNo context available yet. Use brain to store decisions, patterns, and corrections.'
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return sections.join('\n')
|
|
568
|
+
}
|
|
569
|
+
|
|
375
570
|
/**
|
|
376
571
|
* Detect current project from working directory
|
|
377
572
|
*/
|
package/src/server/services.ts
CHANGED
|
@@ -9,16 +9,18 @@ import * as path from 'path'
|
|
|
9
9
|
import { MemoryManager } from '@/memory'
|
|
10
10
|
import { VaultManager } from '@/vault'
|
|
11
11
|
import { ContextManager } from '@/context'
|
|
12
|
-
import { Phase12Manager } from '@/phase12'
|
|
12
|
+
import { Phase12Manager } from '@/automation/phase12-manager'
|
|
13
13
|
import { RetrievalService } from '@/retrieval/service'
|
|
14
|
+
import { RetrievalPipeline } from '@/retrieval/pipeline'
|
|
14
15
|
import { InMemoryKnowledgeGraph } from '@/knowledge/graph/memory-graph'
|
|
15
16
|
import { GraphSearchEngine } from '@/knowledge/graph/search'
|
|
16
17
|
import { KnowledgeGraphBuilder } from '@/knowledge/graph/builder'
|
|
17
18
|
import { CrossReferenceLinker } from '@/knowledge/graph/linker'
|
|
18
19
|
import { EpisodeManager } from '@/memory/episodic/manager'
|
|
20
|
+
import { HookSessionTracker } from '@/hooks/session-tracker'
|
|
19
21
|
import type { Config } from '@/config'
|
|
20
|
-
import { SemanticCache } from '@/optimization/semantic-cache'
|
|
21
|
-
import { PrecomputeEngine } from '@/optimization/precompute'
|
|
22
|
+
import { SemanticCache } from '@/intelligence/optimization/semantic-cache'
|
|
23
|
+
import { PrecomputeEngine } from '@/intelligence/optimization/precompute'
|
|
22
24
|
|
|
23
25
|
export interface KnowledgeGraphServiceContainer {
|
|
24
26
|
graph: InMemoryKnowledgeGraph
|
|
@@ -36,8 +38,10 @@ export interface Services {
|
|
|
36
38
|
context: ContextManager
|
|
37
39
|
phase12: Phase12Manager
|
|
38
40
|
retrieval: RetrievalService | null
|
|
41
|
+
retrievalPipeline: RetrievalPipeline | null
|
|
39
42
|
knowledgeGraph: KnowledgeGraphServiceContainer | null
|
|
40
43
|
episodeManager: EpisodeManager | null
|
|
44
|
+
sessionTracker: HookSessionTracker | null
|
|
41
45
|
semanticCache: SemanticCache | null
|
|
42
46
|
precompute: PrecomputeEngine | null
|
|
43
47
|
logger: Logger
|
|
@@ -127,6 +131,23 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
127
131
|
serviceLogger.warn('Retrieval service requires ChromaDB, skipping initialization')
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
// Initialize Retrieval Pipeline (Phase 19) — hybrid search with BM25 + semantic + fusion
|
|
135
|
+
let retrievalPipeline: RetrievalPipeline | null = null
|
|
136
|
+
if (config.retrieval?.enabled && memory.isChromaDBEnabled()) {
|
|
137
|
+
try {
|
|
138
|
+
retrievalPipeline = new RetrievalPipeline(
|
|
139
|
+
logger,
|
|
140
|
+
memory.chroma.collections,
|
|
141
|
+
memory.chroma.embeddings,
|
|
142
|
+
config.retrieval
|
|
143
|
+
)
|
|
144
|
+
await retrievalPipeline.initialize()
|
|
145
|
+
serviceLogger.info('Retrieval pipeline initialized (hybrid search)')
|
|
146
|
+
} catch (error) {
|
|
147
|
+
serviceLogger.warn({ error }, 'Failed to initialize retrieval pipeline, continuing without hybrid search')
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
130
151
|
// Initialize Knowledge Graph Service (Phase 14)
|
|
131
152
|
let knowledgeGraph: KnowledgeGraphServiceContainer | null = null
|
|
132
153
|
if (config.knowledge?.graph?.enabled !== false) {
|
|
@@ -166,6 +187,21 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
166
187
|
builder.processDecision(input)
|
|
167
188
|
})
|
|
168
189
|
|
|
190
|
+
// Hook builder into decision deletion for graph synchronization
|
|
191
|
+
memory.addDecisionDeletedListener((id) => {
|
|
192
|
+
builder.removeDecision(id)
|
|
193
|
+
|
|
194
|
+
// Phase 20: Also unlink from active episodes
|
|
195
|
+
if (episodeManager) {
|
|
196
|
+
episodeManager.unlinkDecision(id)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Phase 20: Invalidate semantic cache for deleted decisions
|
|
200
|
+
if (semanticCache) {
|
|
201
|
+
semanticCache.clear()
|
|
202
|
+
}
|
|
203
|
+
})
|
|
204
|
+
|
|
169
205
|
serviceLogger.info(
|
|
170
206
|
{ nodes: graph.getNodeCount(), edges: graph.getEdgeCount() },
|
|
171
207
|
'Knowledge graph service initialized'
|
|
@@ -191,6 +227,17 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
191
227
|
}
|
|
192
228
|
}
|
|
193
229
|
|
|
230
|
+
// Initialize Session Tracker (Phase 21) — promoted from serve.ts
|
|
231
|
+
let sessionTracker: HookSessionTracker | null = null
|
|
232
|
+
if (config.hooks?.enabled !== false) {
|
|
233
|
+
try {
|
|
234
|
+
sessionTracker = new HookSessionTracker(logger, episodeManager, config.hooks?.sessions, memory)
|
|
235
|
+
serviceLogger.info('Session tracker initialized')
|
|
236
|
+
} catch (error) {
|
|
237
|
+
serviceLogger.warn({ error }, 'Failed to initialize session tracker, continuing without it')
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
194
241
|
// Initialize Semantic Cache & Precompute (Phase 15) — requires ChromaDB
|
|
195
242
|
let semanticCache: SemanticCache | null = null
|
|
196
243
|
let precompute: PrecomputeEngine | null = null
|
|
@@ -198,9 +245,9 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
198
245
|
try {
|
|
199
246
|
const cacheConfig = config.advancedIntelligence?.cache || {}
|
|
200
247
|
semanticCache = new SemanticCache(logger, {
|
|
201
|
-
maxSize: cacheConfig.maxSize ||
|
|
202
|
-
ttlMs: cacheConfig.ttlMs ||
|
|
203
|
-
similarityThreshold: cacheConfig.similarityThreshold || 0.
|
|
248
|
+
maxSize: cacheConfig.maxSize || 1000,
|
|
249
|
+
ttlMs: cacheConfig.ttlMs || 60 * 60 * 1000,
|
|
250
|
+
similarityThreshold: cacheConfig.similarityThreshold || 0.80,
|
|
204
251
|
embeddings: memory.chroma.embeddings
|
|
205
252
|
})
|
|
206
253
|
|
|
@@ -221,8 +268,10 @@ export async function initializeServices(config: Config, logger: Logger): Promis
|
|
|
221
268
|
context,
|
|
222
269
|
phase12,
|
|
223
270
|
retrieval,
|
|
271
|
+
retrievalPipeline,
|
|
224
272
|
knowledgeGraph,
|
|
225
273
|
episodeManager,
|
|
274
|
+
sessionTracker,
|
|
226
275
|
semanticCache,
|
|
227
276
|
precompute,
|
|
228
277
|
logger,
|
|
@@ -283,6 +332,14 @@ export function getRetrievalService(): RetrievalService | null {
|
|
|
283
332
|
return getServices().retrieval
|
|
284
333
|
}
|
|
285
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Get Retrieval Pipeline (Phase 19)
|
|
337
|
+
* Returns null if hybrid search is not enabled
|
|
338
|
+
*/
|
|
339
|
+
export function getRetrievalPipeline(): RetrievalPipeline | null {
|
|
340
|
+
return getServices().retrievalPipeline
|
|
341
|
+
}
|
|
342
|
+
|
|
286
343
|
/**
|
|
287
344
|
* Get Knowledge Graph service
|
|
288
345
|
* Returns null if knowledge graph is not enabled
|
|
@@ -299,6 +356,14 @@ export function getEpisodeService(): EpisodeManager | null {
|
|
|
299
356
|
return getServices().episodeManager
|
|
300
357
|
}
|
|
301
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Get Session Tracker service
|
|
361
|
+
* Returns null if hooks are not enabled
|
|
362
|
+
*/
|
|
363
|
+
export function getSessionTracker(): HookSessionTracker | null {
|
|
364
|
+
return getServices().sessionTracker
|
|
365
|
+
}
|
|
366
|
+
|
|
302
367
|
/**
|
|
303
368
|
* Get Semantic Cache service
|
|
304
369
|
* Returns null if caching is not enabled
|
|
@@ -352,6 +417,16 @@ export async function shutdownServices(): Promise<void> {
|
|
|
352
417
|
const serviceLogger = services.logger.child({ component: 'services' })
|
|
353
418
|
serviceLogger.info('Shutting down services...')
|
|
354
419
|
|
|
420
|
+
// End all active sessions
|
|
421
|
+
if (services.sessionTracker) {
|
|
422
|
+
try {
|
|
423
|
+
await services.sessionTracker.endAllSessions()
|
|
424
|
+
serviceLogger.info('Session tracker shut down')
|
|
425
|
+
} catch (error) {
|
|
426
|
+
serviceLogger.error({ error }, 'Failed to end sessions on shutdown')
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
355
430
|
// Save and stop knowledge graph
|
|
356
431
|
if (services.knowledgeGraph) {
|
|
357
432
|
services.knowledgeGraph.graph.stopAutoSave()
|