Skip to content

Commit 1b7a90c

Browse files
author
StackMemory Bot (CLI)
committed
fix(lint): fix prettier/eslint errors in graphiti hooks
1 parent 689e976 commit 1b7a90c

2 files changed

Lines changed: 138 additions & 0 deletions

File tree

src/hooks/daemon.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
import { join, extname, relative } from 'path';
1515
import { spawn } from 'child_process';
1616
import { loadConfig, HooksConfig } from './config.js';
17+
import { GraphitiHooks } from './graphiti-hooks.js';
1718
import {
1819
hookEmitter,
1920
HookEventData,
@@ -196,6 +197,19 @@ function registerBuiltinHandlers(): void {
196197
hookEmitter.registerHandler('suggestion_ready', handleSuggestionReady);
197198
hookEmitter.registerHandler('error', handleError);
198199

200+
// Optional: register Graphiti hooks if configured
201+
try {
202+
if (
203+
process.env.GRAPHITI_ENDPOINT ||
204+
process.env.GRAPHITI_ENABLED === 'true'
205+
) {
206+
const graphiti = new GraphitiHooks();
207+
graphiti.register(hookEmitter);
208+
}
209+
} catch {
210+
// avoid crashing the daemon on optional integration failures
211+
}
212+
199213
hookEmitter.on('*', () => {
200214
state.eventsProcessed++;
201215
});

src/hooks/graphiti-hooks.ts

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* Graphiti Session Hooks
3+
* Emits Hook events as Graphiti episodes and enables temporal context queries
4+
*/
5+
6+
import { logger } from '../core/monitoring/logger.js';
7+
import type {
8+
HookEventEmitter,
9+
HookEventData,
10+
FileChangeEvent,
11+
} from './events.js';
12+
import { GraphitiClient } from '../integrations/graphiti/client.js';
13+
import type { Episode, TemporalQuery } from '../integrations/graphiti/types.js';
14+
import type { GraphitiIntegrationConfig } from '../integrations/graphiti/config.js';
15+
import { DEFAULT_GRAPHITI_CONFIG } from '../integrations/graphiti/config.js';
16+
17+
export type GraphitiHookConfig = Partial<GraphitiIntegrationConfig>;
18+
19+
export class GraphitiHooks {
20+
private client: GraphitiClient;
21+
private config: GraphitiIntegrationConfig;
22+
23+
constructor(config: Partial<GraphitiHookConfig> = {}) {
24+
this.config = { ...DEFAULT_GRAPHITI_CONFIG, ...config };
25+
this.client = new GraphitiClient(this.config);
26+
}
27+
28+
register(emitter: HookEventEmitter): void {
29+
if (!this.config.enabled) {
30+
logger.debug('Graphiti hooks disabled');
31+
return;
32+
}
33+
34+
emitter.registerHandler('session_start', this.onSessionStart.bind(this));
35+
emitter.registerHandler('file_change', this.onFileChange.bind(this));
36+
emitter.registerHandler('session_end', this.onSessionEnd.bind(this));
37+
38+
logger.info('Graphiti hooks registered', {
39+
endpoint: this.config.endpoint,
40+
backend: this.config.backend,
41+
maxHops: this.config.maxHops,
42+
});
43+
}
44+
45+
private async onSessionStart(event: HookEventData): Promise<void> {
46+
try {
47+
const status = await this.client.getStatus();
48+
if (!status.connected) {
49+
logger.warn('Graphiti not available - operating in degraded mode');
50+
return;
51+
}
52+
53+
// Record a session_start episode
54+
const episode: Episode = {
55+
type: 'session_start',
56+
content: event.data || {},
57+
timestamp: Date.now(),
58+
source: 'stackmemory',
59+
metadata: { severity: 'info' },
60+
};
61+
await this.client.upsertEpisode(episode);
62+
} catch (error) {
63+
logger.debug('Graphiti session_start failed', {
64+
error: error instanceof Error ? error.message : String(error),
65+
});
66+
}
67+
}
68+
69+
private async onFileChange(event: HookEventData): Promise<void> {
70+
const fileEvent = event as FileChangeEvent;
71+
try {
72+
const episode: Episode = {
73+
type: 'file_change',
74+
content: {
75+
path: fileEvent.data.path,
76+
changeType: fileEvent.data.changeType,
77+
size:
78+
typeof fileEvent.data.content === 'string'
79+
? fileEvent.data.content.length
80+
: undefined,
81+
},
82+
timestamp: Date.now(),
83+
source: 'stackmemory',
84+
};
85+
await this.client.upsertEpisode(episode);
86+
} catch (error) {
87+
logger.debug('Graphiti file_change episode failed', {
88+
error: error instanceof Error ? error.message : String(error),
89+
});
90+
}
91+
}
92+
93+
private async onSessionEnd(event: HookEventData): Promise<void> {
94+
try {
95+
const episode: Episode = {
96+
type: 'session_end',
97+
content: event.data || {},
98+
timestamp: Date.now(),
99+
source: 'stackmemory',
100+
};
101+
await this.client.upsertEpisode(episode);
102+
} catch (error) {
103+
logger.debug('Graphiti session_end failed', {
104+
error: error instanceof Error ? error.message : String(error),
105+
});
106+
}
107+
}
108+
109+
// Expose a simple temporal query helper for future MCP tooling
110+
async buildTemporalContext(query: Partial<TemporalQuery> = {}) {
111+
const now = Date.now();
112+
const q: TemporalQuery = {
113+
query: query.query || undefined,
114+
entityTypes: query.entityTypes || undefined,
115+
relationTypes: query.relationTypes || undefined,
116+
validFrom: query.validFrom ?? now - 1000 * 60 * 60 * 24 * 30, // 30d default
117+
validTo: query.validTo ?? now,
118+
maxHops: query.maxHops ?? this.config.maxHops,
119+
k: query.k ?? 20,
120+
rerank: query.rerank ?? true,
121+
};
122+
return this.client.queryTemporal(q);
123+
}
124+
}

0 commit comments

Comments
 (0)