opencode-graphiti 0.0.0-development

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 (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +358 -0
  3. package/esm/_dnt.polyfills.d.ts +166 -0
  4. package/esm/_dnt.polyfills.d.ts.map +1 -0
  5. package/esm/_dnt.polyfills.js +177 -0
  6. package/esm/_dnt.shims.d.ts +6 -0
  7. package/esm/_dnt.shims.d.ts.map +1 -0
  8. package/esm/_dnt.shims.js +61 -0
  9. package/esm/deno.d.ts +45 -0
  10. package/esm/deno.d.ts.map +1 -0
  11. package/esm/deno.js +39 -0
  12. package/esm/mod.d.ts +3 -0
  13. package/esm/mod.d.ts.map +1 -0
  14. package/esm/mod.js +2 -0
  15. package/esm/package.json +3 -0
  16. package/esm/src/config.d.ts +20 -0
  17. package/esm/src/config.d.ts.map +1 -0
  18. package/esm/src/config.js +246 -0
  19. package/esm/src/handlers/chat.d.ts +14 -0
  20. package/esm/src/handlers/chat.d.ts.map +1 -0
  21. package/esm/src/handlers/chat.js +60 -0
  22. package/esm/src/handlers/compacting.d.ts +9 -0
  23. package/esm/src/handlers/compacting.d.ts.map +1 -0
  24. package/esm/src/handlers/compacting.js +30 -0
  25. package/esm/src/handlers/event.d.ts +22 -0
  26. package/esm/src/handlers/event.d.ts.map +1 -0
  27. package/esm/src/handlers/event.js +287 -0
  28. package/esm/src/handlers/messages.d.ts +9 -0
  29. package/esm/src/handlers/messages.d.ts.map +1 -0
  30. package/esm/src/handlers/messages.js +93 -0
  31. package/esm/src/index.d.ts +5 -0
  32. package/esm/src/index.d.ts.map +1 -0
  33. package/esm/src/index.js +153 -0
  34. package/esm/src/services/batch-drain.d.ts +23 -0
  35. package/esm/src/services/batch-drain.d.ts.map +1 -0
  36. package/esm/src/services/batch-drain.js +217 -0
  37. package/esm/src/services/connection-manager.d.ts +104 -0
  38. package/esm/src/services/connection-manager.d.ts.map +1 -0
  39. package/esm/src/services/connection-manager.js +621 -0
  40. package/esm/src/services/constants.d.ts +7 -0
  41. package/esm/src/services/constants.d.ts.map +1 -0
  42. package/esm/src/services/constants.js +6 -0
  43. package/esm/src/services/context-limit.d.ts +3 -0
  44. package/esm/src/services/context-limit.d.ts.map +1 -0
  45. package/esm/src/services/context-limit.js +44 -0
  46. package/esm/src/services/event-extractor.d.ts +29 -0
  47. package/esm/src/services/event-extractor.d.ts.map +1 -0
  48. package/esm/src/services/event-extractor.js +659 -0
  49. package/esm/src/services/graphiti-async.d.ts +22 -0
  50. package/esm/src/services/graphiti-async.d.ts.map +1 -0
  51. package/esm/src/services/graphiti-async.js +219 -0
  52. package/esm/src/services/graphiti-mcp.d.ts +57 -0
  53. package/esm/src/services/graphiti-mcp.d.ts.map +1 -0
  54. package/esm/src/services/graphiti-mcp.js +194 -0
  55. package/esm/src/services/logger.d.ts +9 -0
  56. package/esm/src/services/logger.d.ts.map +1 -0
  57. package/esm/src/services/logger.js +104 -0
  58. package/esm/src/services/opencode-warning.d.ts +8 -0
  59. package/esm/src/services/opencode-warning.d.ts.map +1 -0
  60. package/esm/src/services/opencode-warning.js +104 -0
  61. package/esm/src/services/redis-cache.d.ts +27 -0
  62. package/esm/src/services/redis-cache.d.ts.map +1 -0
  63. package/esm/src/services/redis-cache.js +215 -0
  64. package/esm/src/services/redis-client.d.ts +89 -0
  65. package/esm/src/services/redis-client.d.ts.map +1 -0
  66. package/esm/src/services/redis-client.js +906 -0
  67. package/esm/src/services/redis-events.d.ts +46 -0
  68. package/esm/src/services/redis-events.d.ts.map +1 -0
  69. package/esm/src/services/redis-events.js +517 -0
  70. package/esm/src/services/redis-snapshot.d.ts +16 -0
  71. package/esm/src/services/redis-snapshot.d.ts.map +1 -0
  72. package/esm/src/services/redis-snapshot.js +184 -0
  73. package/esm/src/services/render-utils.d.ts +23 -0
  74. package/esm/src/services/render-utils.d.ts.map +1 -0
  75. package/esm/src/services/render-utils.js +149 -0
  76. package/esm/src/services/runtime-teardown.d.ts +23 -0
  77. package/esm/src/services/runtime-teardown.d.ts.map +1 -0
  78. package/esm/src/services/runtime-teardown.js +119 -0
  79. package/esm/src/services/sdk-normalize.d.ts +55 -0
  80. package/esm/src/services/sdk-normalize.d.ts.map +1 -0
  81. package/esm/src/services/sdk-normalize.js +61 -0
  82. package/esm/src/session.d.ts +74 -0
  83. package/esm/src/session.d.ts.map +1 -0
  84. package/esm/src/session.js +694 -0
  85. package/esm/src/types/index.d.ts +120 -0
  86. package/esm/src/types/index.d.ts.map +1 -0
  87. package/esm/src/types/index.js +28 -0
  88. package/esm/src/utils.d.ts +27 -0
  89. package/esm/src/utils.d.ts.map +1 -0
  90. package/esm/src/utils.js +76 -0
  91. package/package.json +59 -0
  92. package/script/_dnt.polyfills.d.ts +166 -0
  93. package/script/_dnt.polyfills.d.ts.map +1 -0
  94. package/script/_dnt.polyfills.js +180 -0
  95. package/script/_dnt.shims.d.ts +6 -0
  96. package/script/_dnt.shims.d.ts.map +1 -0
  97. package/script/_dnt.shims.js +65 -0
  98. package/script/deno.d.ts +45 -0
  99. package/script/deno.d.ts.map +1 -0
  100. package/script/deno.js +41 -0
  101. package/script/mod.d.ts +3 -0
  102. package/script/mod.d.ts.map +1 -0
  103. package/script/mod.js +6 -0
  104. package/script/package.json +3 -0
  105. package/script/src/config.d.ts +20 -0
  106. package/script/src/config.d.ts.map +1 -0
  107. package/script/src/config.js +256 -0
  108. package/script/src/handlers/chat.d.ts +14 -0
  109. package/script/src/handlers/chat.d.ts.map +1 -0
  110. package/script/src/handlers/chat.js +63 -0
  111. package/script/src/handlers/compacting.d.ts +9 -0
  112. package/script/src/handlers/compacting.d.ts.map +1 -0
  113. package/script/src/handlers/compacting.js +33 -0
  114. package/script/src/handlers/event.d.ts +22 -0
  115. package/script/src/handlers/event.d.ts.map +1 -0
  116. package/script/src/handlers/event.js +290 -0
  117. package/script/src/handlers/messages.d.ts +9 -0
  118. package/script/src/handlers/messages.d.ts.map +1 -0
  119. package/script/src/handlers/messages.js +96 -0
  120. package/script/src/index.d.ts +5 -0
  121. package/script/src/index.d.ts.map +1 -0
  122. package/script/src/index.js +159 -0
  123. package/script/src/services/batch-drain.d.ts +23 -0
  124. package/script/src/services/batch-drain.d.ts.map +1 -0
  125. package/script/src/services/batch-drain.js +221 -0
  126. package/script/src/services/connection-manager.d.ts +104 -0
  127. package/script/src/services/connection-manager.d.ts.map +1 -0
  128. package/script/src/services/connection-manager.js +635 -0
  129. package/script/src/services/constants.d.ts +7 -0
  130. package/script/src/services/constants.d.ts.map +1 -0
  131. package/script/src/services/constants.js +9 -0
  132. package/script/src/services/context-limit.d.ts +3 -0
  133. package/script/src/services/context-limit.d.ts.map +1 -0
  134. package/script/src/services/context-limit.js +47 -0
  135. package/script/src/services/event-extractor.d.ts +29 -0
  136. package/script/src/services/event-extractor.d.ts.map +1 -0
  137. package/script/src/services/event-extractor.js +669 -0
  138. package/script/src/services/graphiti-async.d.ts +22 -0
  139. package/script/src/services/graphiti-async.d.ts.map +1 -0
  140. package/script/src/services/graphiti-async.js +223 -0
  141. package/script/src/services/graphiti-mcp.d.ts +57 -0
  142. package/script/src/services/graphiti-mcp.d.ts.map +1 -0
  143. package/script/src/services/graphiti-mcp.js +198 -0
  144. package/script/src/services/logger.d.ts +9 -0
  145. package/script/src/services/logger.d.ts.map +1 -0
  146. package/script/src/services/logger.js +142 -0
  147. package/script/src/services/opencode-warning.d.ts +8 -0
  148. package/script/src/services/opencode-warning.d.ts.map +1 -0
  149. package/script/src/services/opencode-warning.js +114 -0
  150. package/script/src/services/redis-cache.d.ts +27 -0
  151. package/script/src/services/redis-cache.d.ts.map +1 -0
  152. package/script/src/services/redis-cache.js +219 -0
  153. package/script/src/services/redis-client.d.ts +89 -0
  154. package/script/src/services/redis-client.d.ts.map +1 -0
  155. package/script/src/services/redis-client.js +943 -0
  156. package/script/src/services/redis-events.d.ts +46 -0
  157. package/script/src/services/redis-events.d.ts.map +1 -0
  158. package/script/src/services/redis-events.js +535 -0
  159. package/script/src/services/redis-snapshot.d.ts +16 -0
  160. package/script/src/services/redis-snapshot.d.ts.map +1 -0
  161. package/script/src/services/redis-snapshot.js +189 -0
  162. package/script/src/services/render-utils.d.ts +23 -0
  163. package/script/src/services/render-utils.d.ts.map +1 -0
  164. package/script/src/services/render-utils.js +165 -0
  165. package/script/src/services/runtime-teardown.d.ts +23 -0
  166. package/script/src/services/runtime-teardown.d.ts.map +1 -0
  167. package/script/src/services/runtime-teardown.js +155 -0
  168. package/script/src/services/sdk-normalize.d.ts +55 -0
  169. package/script/src/services/sdk-normalize.d.ts.map +1 -0
  170. package/script/src/services/sdk-normalize.js +67 -0
  171. package/script/src/session.d.ts +74 -0
  172. package/script/src/session.d.ts.map +1 -0
  173. package/script/src/session.js +698 -0
  174. package/script/src/types/index.d.ts +120 -0
  175. package/script/src/types/index.d.ts.map +1 -0
  176. package/script/src/types/index.js +33 -0
  177. package/script/src/utils.d.ts +27 -0
  178. package/script/src/utils.d.ts.map +1 -0
  179. package/script/src/utils.js +87 -0
@@ -0,0 +1,694 @@
1
+ import { DEFAULT_CONTEXT_LIMIT } from "./services/constants.js";
2
+ import { logger } from "./services/logger.js";
3
+ import { PERSISTENT_MEMORY_BODY_BUDGET, } from "./services/redis-cache.js";
4
+ import { escapeXml, normalizeMemoryText, renderXmlListSection, sanitizeMemoryInput, uniqueNormalizedValues, } from "./services/render-utils.js";
5
+ import { getSessionEventPrimaryText, } from "./types/index.js";
6
+ const findLatestUserRequest = (events) => {
7
+ for (let index = events.length - 1; index >= 0; index -= 1) {
8
+ const event = events[index];
9
+ if (event.role !== "user")
10
+ continue;
11
+ const candidate = sanitizeMemoryInput(getSessionEventPrimaryText(event));
12
+ if (candidate)
13
+ return candidate;
14
+ }
15
+ return "";
16
+ };
17
+ const RECENT_BASELINE_LIMIT = 20;
18
+ const RECALL_RESULT_LIMIT = 12;
19
+ const EXPLICIT_NOT_FOUND_CODES = new Set([
20
+ "not_found",
21
+ "session_not_found",
22
+ ]);
23
+ const asRecord = (value) => typeof value === "object" && value !== null
24
+ ? value
25
+ : null;
26
+ const normalizeErrorToken = (value) => {
27
+ if (typeof value !== "string")
28
+ return null;
29
+ const normalized = value.trim().toLowerCase();
30
+ return normalized.length > 0 ? normalized : null;
31
+ };
32
+ const isExplicitNotFoundCode = (value) => {
33
+ const normalized = normalizeErrorToken(value);
34
+ return normalized !== null && EXPLICIT_NOT_FOUND_CODES.has(normalized);
35
+ };
36
+ const isExplicitSessionNotFoundMessage = (value) => {
37
+ if (typeof value !== "string")
38
+ return false;
39
+ return /\bsession not found\b/i.test(value);
40
+ };
41
+ const isExplicitSessionNotFoundError = (error) => {
42
+ const queue = [error];
43
+ const visited = new Set();
44
+ while (queue.length > 0) {
45
+ const current = queue.shift();
46
+ const record = asRecord(current);
47
+ if (!record)
48
+ continue;
49
+ if (visited.has(record))
50
+ continue;
51
+ visited.add(record);
52
+ const status = record.status;
53
+ const statusCode = record.statusCode;
54
+ if (status === 404 || statusCode === 404)
55
+ return true;
56
+ if (isExplicitNotFoundCode(record.code) ||
57
+ isExplicitNotFoundCode(record.errorCode) ||
58
+ isExplicitNotFoundCode(record.type) ||
59
+ isExplicitSessionNotFoundMessage(record.message)) {
60
+ return true;
61
+ }
62
+ queue.push(record.cause, record.data, record.body, record.error, record.response);
63
+ }
64
+ return false;
65
+ };
66
+ const mergeSessionEvents = (recentEvents, recalledEvents) => {
67
+ const merged = new Map();
68
+ for (const event of recentEvents) {
69
+ if (!merged.has(event.id))
70
+ merged.set(event.id, event);
71
+ }
72
+ for (const event of recalledEvents) {
73
+ if (!merged.has(event.id))
74
+ merged.set(event.id, event);
75
+ }
76
+ return [...merged.values()].sort((left, right) => {
77
+ if (left.ts !== right.ts)
78
+ return left.ts - right.ts;
79
+ return left.id.localeCompare(right.id);
80
+ });
81
+ };
82
+ const collectRecentUniqueValues = (events, collect, limit, excludedNormalized = new Set()) => uniqueNormalizedValues(events.flatMap((event) => {
83
+ const value = collect(event);
84
+ if (value === null || value === undefined)
85
+ return [];
86
+ return Array.isArray(value) ? value : [value];
87
+ }).reverse(), limit, excludedNormalized);
88
+ const addNormalizedValues = (target, values) => {
89
+ for (const value of values) {
90
+ const normalized = normalizeMemoryText(value);
91
+ if (normalized)
92
+ target.add(normalized);
93
+ }
94
+ };
95
+ const filterDuplicateSnapshotLeaves = (snapshot, excludedNormalized) => {
96
+ if (!snapshot)
97
+ return "";
98
+ let filtered = snapshot.replace(/<([a-z_]+)>([^<>]*)<\/\1>/gi, (match, tag, text) => {
99
+ if (tag.toLowerCase() === "snapshot")
100
+ return match;
101
+ const normalized = normalizeMemoryText(text);
102
+ return normalized && excludedNormalized.has(normalized) ? "" : match;
103
+ });
104
+ filtered = filtered.replace(/<(?!snapshot\b)([a-z_]+)>\s*<\/\1>/gi, "");
105
+ return filtered;
106
+ };
107
+ const collectSectionValues = (events, predicate, limit, excludedNormalized = new Set()) => collectRecentUniqueValues(events, (event) => predicate(event)
108
+ ? sanitizeMemoryInput(getSessionEventPrimaryText(event))
109
+ : null, limit, excludedNormalized);
110
+ const collectPathValues = (events, limit, excludedNormalized = new Set()) => collectRecentUniqueValues(events, (event) => event.category.startsWith("file.") ? event.refs ?? [] : [], limit, excludedNormalized);
111
+ export class SessionManager {
112
+ constructor(defaultGroupId, defaultUserGroupId, sdkClient, redisEvents, redisSnapshot, redisCache, options = {}) {
113
+ Object.defineProperty(this, "defaultGroupId", {
114
+ enumerable: true,
115
+ configurable: true,
116
+ writable: true,
117
+ value: defaultGroupId
118
+ });
119
+ Object.defineProperty(this, "defaultUserGroupId", {
120
+ enumerable: true,
121
+ configurable: true,
122
+ writable: true,
123
+ value: defaultUserGroupId
124
+ });
125
+ Object.defineProperty(this, "sdkClient", {
126
+ enumerable: true,
127
+ configurable: true,
128
+ writable: true,
129
+ value: sdkClient
130
+ });
131
+ Object.defineProperty(this, "redisEvents", {
132
+ enumerable: true,
133
+ configurable: true,
134
+ writable: true,
135
+ value: redisEvents
136
+ });
137
+ Object.defineProperty(this, "redisSnapshot", {
138
+ enumerable: true,
139
+ configurable: true,
140
+ writable: true,
141
+ value: redisSnapshot
142
+ });
143
+ Object.defineProperty(this, "redisCache", {
144
+ enumerable: true,
145
+ configurable: true,
146
+ writable: true,
147
+ value: redisCache
148
+ });
149
+ Object.defineProperty(this, "sessions", {
150
+ enumerable: true,
151
+ configurable: true,
152
+ writable: true,
153
+ value: new Map()
154
+ });
155
+ Object.defineProperty(this, "parentIdCache", {
156
+ enumerable: true,
157
+ configurable: true,
158
+ writable: true,
159
+ value: new Map()
160
+ });
161
+ Object.defineProperty(this, "canonicalSessionIdCache", {
162
+ enumerable: true,
163
+ configurable: true,
164
+ writable: true,
165
+ value: new Map()
166
+ });
167
+ Object.defineProperty(this, "temporaryRootSessionIds", {
168
+ enumerable: true,
169
+ configurable: true,
170
+ writable: true,
171
+ value: new Set()
172
+ });
173
+ Object.defineProperty(this, "pendingAssistantMessages", {
174
+ enumerable: true,
175
+ configurable: true,
176
+ writable: true,
177
+ value: new Map()
178
+ });
179
+ Object.defineProperty(this, "pendingAssistantCompletions", {
180
+ enumerable: true,
181
+ configurable: true,
182
+ writable: true,
183
+ value: new Set()
184
+ });
185
+ Object.defineProperty(this, "bufferedAssistantMessageIds", {
186
+ enumerable: true,
187
+ configurable: true,
188
+ writable: true,
189
+ value: new Map()
190
+ });
191
+ Object.defineProperty(this, "sessionLifecycles", {
192
+ enumerable: true,
193
+ configurable: true,
194
+ writable: true,
195
+ value: new Map()
196
+ });
197
+ Object.defineProperty(this, "idleRetentionMs", {
198
+ enumerable: true,
199
+ configurable: true,
200
+ writable: true,
201
+ value: void 0
202
+ });
203
+ Object.defineProperty(this, "setTimerImpl", {
204
+ enumerable: true,
205
+ configurable: true,
206
+ writable: true,
207
+ value: void 0
208
+ });
209
+ Object.defineProperty(this, "clearTimerImpl", {
210
+ enumerable: true,
211
+ configurable: true,
212
+ writable: true,
213
+ value: void 0
214
+ });
215
+ this.idleRetentionMs = Math.max(0, options.idleRetentionMs ?? 0);
216
+ this.setTimerImpl = options.setTimer ??
217
+ ((callback, delayMs) => setTimeout(callback, delayMs));
218
+ this.clearTimerImpl = options.clearTimer ??
219
+ ((timer) => clearTimeout(timer));
220
+ }
221
+ createDefaultState(groupId, userGroupId) {
222
+ return {
223
+ groupId,
224
+ userGroupId,
225
+ injectedMemories: false,
226
+ messageCount: 0,
227
+ contextLimit: DEFAULT_CONTEXT_LIMIT,
228
+ isMain: true,
229
+ hotTierReady: false,
230
+ latestUserRequest: undefined,
231
+ latestRefreshQuery: undefined,
232
+ pendingInjection: undefined,
233
+ pendingInjectionGeneration: 0,
234
+ };
235
+ }
236
+ getState(sessionId) {
237
+ return this.sessions.get(sessionId);
238
+ }
239
+ setState(sessionId, state) {
240
+ this.sessions.set(sessionId, state);
241
+ }
242
+ markSessionActive(sessionId) {
243
+ this.markLifecycleActive(sessionId);
244
+ const canonicalSessionId = this.canonicalSessionIdCache.get(sessionId);
245
+ if (canonicalSessionId && canonicalSessionId !== sessionId) {
246
+ this.markLifecycleActive(canonicalSessionId);
247
+ }
248
+ }
249
+ markResolvedSessionActive(sessionId, canonicalSessionId) {
250
+ this.markLifecycleActive(sessionId);
251
+ if (canonicalSessionId && canonicalSessionId !== sessionId) {
252
+ this.markLifecycleActive(canonicalSessionId);
253
+ }
254
+ }
255
+ markLifecycleActive(sessionId) {
256
+ const lifecycle = this.getLifecycle(sessionId);
257
+ lifecycle.activityGeneration += 1;
258
+ if (lifecycle.idleCleanupTimer !== null) {
259
+ this.clearTimerImpl(lifecycle.idleCleanupTimer);
260
+ lifecycle.idleCleanupTimer = null;
261
+ }
262
+ }
263
+ captureIdleCleanupGeneration(sessionId) {
264
+ const state = this.sessions.get(sessionId);
265
+ if (!state?.isMain)
266
+ return null;
267
+ return this.getLifecycle(sessionId).activityGeneration;
268
+ }
269
+ scheduleIdleSessionCleanup(sessionId, expectedActivityGeneration) {
270
+ const state = this.sessions.get(sessionId);
271
+ if (!state?.isMain) {
272
+ this.deleteSession(sessionId);
273
+ return;
274
+ }
275
+ const lifecycle = this.getLifecycle(sessionId);
276
+ if (expectedActivityGeneration !== undefined &&
277
+ lifecycle.activityGeneration !== expectedActivityGeneration) {
278
+ return;
279
+ }
280
+ if (this.idleRetentionMs <= 0) {
281
+ this.deleteSession(sessionId);
282
+ return;
283
+ }
284
+ if (lifecycle.idleCleanupTimer !== null) {
285
+ this.clearTimerImpl(lifecycle.idleCleanupTimer);
286
+ lifecycle.idleCleanupTimer = null;
287
+ }
288
+ const activityGeneration = expectedActivityGeneration ??
289
+ lifecycle.activityGeneration;
290
+ const timerHandle = this.setTimerImpl(() => {
291
+ const currentLifecycle = this.sessionLifecycles.get(sessionId);
292
+ if (!currentLifecycle)
293
+ return;
294
+ if (currentLifecycle.idleCleanupTimer !== timerHandle)
295
+ return;
296
+ if (currentLifecycle.activityGeneration !== activityGeneration)
297
+ return;
298
+ this.deleteSession(sessionId);
299
+ }, this.idleRetentionMs);
300
+ lifecycle.idleCleanupTimer = timerHandle;
301
+ }
302
+ setParentId(sessionId, parentId) {
303
+ const wasTemporaryRoot = this.temporaryRootSessionIds.has(sessionId);
304
+ this.parentIdCache.set(sessionId, parentId);
305
+ if (!parentId) {
306
+ this.temporaryRootSessionIds.delete(sessionId);
307
+ this.canonicalSessionIdCache.set(sessionId, sessionId);
308
+ return;
309
+ }
310
+ const parentCanonical = this.canonicalSessionIdCache.get(parentId);
311
+ if (parentCanonical) {
312
+ this.canonicalSessionIdCache.set(sessionId, parentCanonical);
313
+ if (parentCanonical !== sessionId) {
314
+ this.migrateTemporaryRootRuntimeState(sessionId, parentCanonical);
315
+ }
316
+ if (wasTemporaryRoot) {
317
+ this.temporaryRootSessionIds.delete(sessionId);
318
+ }
319
+ return;
320
+ }
321
+ this.canonicalSessionIdCache.delete(sessionId);
322
+ }
323
+ mergeSessionState(target, source) {
324
+ target.injectedMemories ||= source.injectedMemories;
325
+ target.messageCount += source.messageCount;
326
+ target.contextLimit = Math.max(target.contextLimit, source.contextLimit);
327
+ target.isMain ||= source.isMain;
328
+ target.hotTierReady ||= source.hotTierReady;
329
+ if (source.latestUserRequest) {
330
+ target.latestUserRequest = source.latestUserRequest;
331
+ }
332
+ if (source.latestRefreshQuery) {
333
+ target.latestRefreshQuery = source.latestRefreshQuery;
334
+ }
335
+ if (source.pendingInjection !== undefined) {
336
+ target.pendingInjection = source.pendingInjection;
337
+ }
338
+ target.pendingInjectionGeneration = Math.max(target.pendingInjectionGeneration, source.pendingInjectionGeneration);
339
+ }
340
+ migrateTemporaryRootRuntimeState(sessionId, canonicalSessionId) {
341
+ if (sessionId === canonicalSessionId)
342
+ return;
343
+ const sourceState = this.sessions.get(sessionId);
344
+ const targetState = this.sessions.get(canonicalSessionId);
345
+ if (sourceState) {
346
+ if (targetState) {
347
+ this.mergeSessionState(targetState, sourceState);
348
+ }
349
+ else {
350
+ this.sessions.set(canonicalSessionId, sourceState);
351
+ }
352
+ this.sessions.delete(sessionId);
353
+ }
354
+ const sourceLifecycle = this.sessionLifecycles.get(sessionId);
355
+ const targetLifecycle = this.sessionLifecycles.get(canonicalSessionId);
356
+ if (sourceLifecycle) {
357
+ const targetIdleCleanupTimer = targetLifecycle?.idleCleanupTimer ?? null;
358
+ if (sourceLifecycle.idleCleanupTimer !== null) {
359
+ this.clearTimerImpl(sourceLifecycle.idleCleanupTimer);
360
+ }
361
+ if (targetIdleCleanupTimer !== null) {
362
+ this.clearTimerImpl(targetIdleCleanupTimer);
363
+ }
364
+ this.sessionLifecycles.set(canonicalSessionId, {
365
+ activityGeneration: Math.max(targetLifecycle?.activityGeneration ?? 0, sourceLifecycle.activityGeneration),
366
+ idleCleanupTimer: null,
367
+ });
368
+ this.sessionLifecycles.delete(sessionId);
369
+ }
370
+ const sessionPrefix = `${sessionId}:`;
371
+ for (const [key, buffered] of [...this.pendingAssistantMessages.entries()]) {
372
+ if (!key.startsWith(sessionPrefix))
373
+ continue;
374
+ const messageId = key.slice(sessionPrefix.length);
375
+ const canonicalKey = `${canonicalSessionId}:${messageId}`;
376
+ if (!this.pendingAssistantMessages.has(canonicalKey)) {
377
+ this.pendingAssistantMessages.set(canonicalKey, {
378
+ ...buffered,
379
+ sessionId: canonicalSessionId,
380
+ });
381
+ }
382
+ this.pendingAssistantMessages.delete(key);
383
+ }
384
+ for (const [key, sourceSessionId] of [...this.bufferedAssistantMessageIds
385
+ .entries()]) {
386
+ if (!key.startsWith(sessionPrefix))
387
+ continue;
388
+ const messageId = key.slice(sessionPrefix.length);
389
+ const canonicalKey = `${canonicalSessionId}:${messageId}`;
390
+ if (!this.bufferedAssistantMessageIds.has(canonicalKey)) {
391
+ this.bufferedAssistantMessageIds.set(canonicalKey, sourceSessionId);
392
+ }
393
+ this.bufferedAssistantMessageIds.delete(key);
394
+ }
395
+ for (const key of [...this.pendingAssistantCompletions]) {
396
+ if (!key.startsWith(sessionPrefix))
397
+ continue;
398
+ const messageId = key.slice(sessionPrefix.length);
399
+ this.pendingAssistantCompletions.add(`${canonicalSessionId}:${messageId}`);
400
+ this.pendingAssistantCompletions.delete(key);
401
+ }
402
+ for (const [cachedSessionId, cachedCanonicalSessionId] of [
403
+ ...this.canonicalSessionIdCache.entries(),
404
+ ]) {
405
+ if (cachedCanonicalSessionId === sessionId) {
406
+ this.canonicalSessionIdCache.set(cachedSessionId, canonicalSessionId);
407
+ }
408
+ }
409
+ }
410
+ async resolveParentId(sessionId) {
411
+ if (this.parentIdCache.has(sessionId)) {
412
+ return this.parentIdCache.get(sessionId) ?? null;
413
+ }
414
+ try {
415
+ const response = await this.sdkClient.session.get({
416
+ path: { id: sessionId },
417
+ });
418
+ const sessionInfo = typeof response === "object" && response !== null &&
419
+ "data" in response
420
+ ? response.data
421
+ : response;
422
+ if (!sessionInfo)
423
+ return undefined;
424
+ const parentId = sessionInfo.parentID ?? null;
425
+ this.parentIdCache.set(sessionId, parentId);
426
+ return parentId;
427
+ }
428
+ catch (err) {
429
+ if (isExplicitSessionNotFoundError(err)) {
430
+ this.parentIdCache.set(sessionId, null);
431
+ this.canonicalSessionIdCache.set(sessionId, sessionId);
432
+ this.temporaryRootSessionIds.add(sessionId);
433
+ logger.debug("Session not found during parent resolution; treating as temporary root", { sessionId });
434
+ return null;
435
+ }
436
+ logger.debug("Failed to resolve session parentID", { sessionId, err });
437
+ return undefined;
438
+ }
439
+ }
440
+ async resolveCanonicalSessionId(sessionId, visited = new Set()) {
441
+ const cached = this.canonicalSessionIdCache.get(sessionId);
442
+ if (cached)
443
+ return cached;
444
+ if (visited.has(sessionId)) {
445
+ logger.debug("Detected cycle while resolving canonical session", {
446
+ sessionId,
447
+ visited: [...visited],
448
+ });
449
+ return undefined;
450
+ }
451
+ visited.add(sessionId);
452
+ const parentId = await this.resolveParentId(sessionId);
453
+ if (parentId === undefined)
454
+ return undefined;
455
+ if (!parentId) {
456
+ this.canonicalSessionIdCache.set(sessionId, sessionId);
457
+ return sessionId;
458
+ }
459
+ const canonicalSessionId = await this.resolveCanonicalSessionId(parentId, visited);
460
+ if (!canonicalSessionId)
461
+ return undefined;
462
+ if (canonicalSessionId !== sessionId) {
463
+ this.migrateTemporaryRootRuntimeState(sessionId, canonicalSessionId);
464
+ this.temporaryRootSessionIds.delete(sessionId);
465
+ }
466
+ this.canonicalSessionIdCache.set(sessionId, canonicalSessionId);
467
+ return canonicalSessionId;
468
+ }
469
+ async resolveSessionState(sessionId) {
470
+ const canonicalSessionId = await this.resolveCanonicalSessionId(sessionId);
471
+ if (!canonicalSessionId) {
472
+ return { state: null, resolved: false, canonicalSessionId: undefined };
473
+ }
474
+ let state = this.sessions.get(canonicalSessionId);
475
+ if (!state) {
476
+ state = this.createDefaultState(this.defaultGroupId, this.defaultUserGroupId);
477
+ this.sessions.set(canonicalSessionId, state);
478
+ }
479
+ return { state, resolved: true, canonicalSessionId };
480
+ }
481
+ bufferAssistantPart(sessionId, messageId, text, sourceSessionId = sessionId) {
482
+ const key = `${sessionId}:${messageId}`;
483
+ this.pendingAssistantMessages.set(key, {
484
+ sessionId,
485
+ text,
486
+ sourceSessionId,
487
+ });
488
+ }
489
+ isAssistantBuffered(sessionId, messageId) {
490
+ return this.bufferedAssistantMessageIds.has(`${sessionId}:${messageId}`);
491
+ }
492
+ hasPendingAssistantCompletion(sessionId, messageId) {
493
+ return this.pendingAssistantCompletions.has(`${sessionId}:${messageId}`);
494
+ }
495
+ finalizeAssistantMessage(_state, sessionId, messageId, source) {
496
+ const key = `${sessionId}:${messageId}`;
497
+ if (this.bufferedAssistantMessageIds.has(key))
498
+ return null;
499
+ const buffered = this.pendingAssistantMessages.get(key);
500
+ const messageText = buffered?.text?.trim() ?? "";
501
+ if (!messageText) {
502
+ this.pendingAssistantCompletions.add(key);
503
+ return null;
504
+ }
505
+ this.pendingAssistantCompletions.delete(key);
506
+ this.pendingAssistantMessages.delete(key);
507
+ this.bufferedAssistantMessageIds.set(key, buffered?.sourceSessionId ?? sessionId);
508
+ logger.info("Assistant message completed", {
509
+ hook: source,
510
+ sessionId,
511
+ messageID: messageId,
512
+ messageLength: messageText.length,
513
+ });
514
+ return messageText;
515
+ }
516
+ deletePendingAssistant(sessionId, messageId) {
517
+ const key = `${sessionId}:${messageId}`;
518
+ this.pendingAssistantMessages.delete(key);
519
+ this.pendingAssistantCompletions.delete(key);
520
+ }
521
+ clearPendingInjection(state, prepared) {
522
+ if (!prepared)
523
+ return;
524
+ if (state.pendingInjection === prepared) {
525
+ state.pendingInjection = undefined;
526
+ }
527
+ }
528
+ purgeAssistantBufferSource(sourceSessionId) {
529
+ for (const [key, buffered] of [...this.pendingAssistantMessages.entries()]) {
530
+ if (buffered.sourceSessionId === sourceSessionId) {
531
+ this.pendingAssistantMessages.delete(key);
532
+ this.pendingAssistantCompletions.delete(key);
533
+ }
534
+ }
535
+ for (const [key, bufferedSourceSessionId] of [
536
+ ...this.bufferedAssistantMessageIds.entries(),
537
+ ]) {
538
+ if (bufferedSourceSessionId === sourceSessionId) {
539
+ this.bufferedAssistantMessageIds.delete(key);
540
+ }
541
+ }
542
+ }
543
+ async prepareInjection(sessionId, lastRequest) {
544
+ const state = this.sessions.get(sessionId);
545
+ if (!state?.isMain)
546
+ return null;
547
+ const generation = state.pendingInjectionGeneration + 1;
548
+ state.pendingInjectionGeneration = generation;
549
+ const [recentEvents, snapshot, cache, cacheMeta] = await Promise.all([
550
+ this.redisEvents.getRecentSessionEvents(sessionId, RECENT_BASELINE_LIMIT, true),
551
+ this.redisSnapshot.getSnapshot(sessionId),
552
+ this.redisCache.get(state.groupId),
553
+ this.redisCache.getMeta(state.groupId),
554
+ ]);
555
+ const canonicalLatestRequest = sanitizeMemoryInput(state.latestUserRequest ?? "");
556
+ const directFallbackRequest = sanitizeMemoryInput(lastRequest ?? "");
557
+ const cachedFallbackRequest = sanitizeMemoryInput(state.latestRefreshQuery ?? cacheMeta?.lastQuery ?? "");
558
+ const historyFallbackRequest = findLatestUserRequest(recentEvents);
559
+ const latestRequest = canonicalLatestRequest || directFallbackRequest ||
560
+ cachedFallbackRequest || historyFallbackRequest;
561
+ const recalledEvents = latestRequest
562
+ ? await this.redisEvents.recallSessionEvents(sessionId, latestRequest, {
563
+ resultLimit: RECALL_RESULT_LIMIT,
564
+ })
565
+ : [];
566
+ const events = mergeSessionEvents(recentEvents, recalledEvents);
567
+ const occupiedNormalized = new Set();
568
+ const normalizedLatestRequest = normalizeMemoryText(latestRequest);
569
+ if (normalizedLatestRequest) {
570
+ occupiedNormalized.add(normalizedLatestRequest);
571
+ }
572
+ const activeTasks = collectSectionValues(events, (event) => ["task.create", "task.update", "task.complete"].includes(event.category), 4, occupiedNormalized);
573
+ addNormalizedValues(occupiedNormalized, activeTasks);
574
+ const decisions = collectSectionValues(events, (event) => ["decision", "preference"].includes(event.category), 5, occupiedNormalized);
575
+ addNormalizedValues(occupiedNormalized, decisions);
576
+ const files = collectPathValues(events, 6, occupiedNormalized);
577
+ addNormalizedValues(occupiedNormalized, files);
578
+ const rules = collectSectionValues(events, (event) => event.category === "rule.load", 6, occupiedNormalized);
579
+ addNormalizedValues(occupiedNormalized, rules);
580
+ const unresolvedErrors = collectRecentUniqueValues(events, (event) => event.category === "error" && event.metadata?.resolved !== true &&
581
+ event.role !== "assistant"
582
+ ? sanitizeMemoryInput(getSessionEventPrimaryText(event))
583
+ : null, 4, occupiedNormalized);
584
+ addNormalizedValues(occupiedNormalized, unresolvedErrors);
585
+ const gitState = collectSectionValues(events, (event) => event.category === "git.activity", 4, occupiedNormalized);
586
+ addNormalizedValues(occupiedNormalized, gitState);
587
+ const subagentWork = collectSectionValues(events, (event) => event.category === "subagent.start" ||
588
+ event.category === "subagent.finish", 4, occupiedNormalized);
589
+ addNormalizedValues(occupiedNormalized, subagentWork);
590
+ const filteredSnapshot = filterDuplicateSnapshotLeaves(snapshot, occupiedNormalized);
591
+ const persistent = this.redisCache.renderPersistentMemory(cache, PERSISTENT_MEMORY_BODY_BUDGET);
592
+ const refreshDecision = this.redisCache.classifyRefresh(cache, latestRequest);
593
+ const sections = [
594
+ `<last_request>${escapeXml(latestRequest)}</last_request>`,
595
+ renderXmlListSection("active_tasks", "task", activeTasks, { itemCharLimit: 280, includeEmpty: true }),
596
+ renderXmlListSection("key_decisions", "decision", decisions, {
597
+ itemCharLimit: 280,
598
+ includeEmpty: true,
599
+ }),
600
+ renderXmlListSection("files_in_play", "file", files, {
601
+ itemCharLimit: 280,
602
+ includeEmpty: true,
603
+ }),
604
+ renderXmlListSection("project_rules", "rule", rules, {
605
+ itemCharLimit: 280,
606
+ includeEmpty: true,
607
+ }),
608
+ unresolvedErrors.length > 0
609
+ ? renderXmlListSection("unresolved_errors", "error", unresolvedErrors, {
610
+ itemCharLimit: 280,
611
+ })
612
+ : "",
613
+ gitState.length > 0
614
+ ? renderXmlListSection("git_state", "item", gitState, {
615
+ itemCharLimit: 280,
616
+ })
617
+ : "",
618
+ subagentWork.length > 0
619
+ ? renderXmlListSection("subagent_work", "item", subagentWork, {
620
+ itemCharLimit: 280,
621
+ })
622
+ : "",
623
+ filteredSnapshot
624
+ ? `<session_snapshot>${filteredSnapshot}</session_snapshot>`
625
+ : "",
626
+ persistent.body
627
+ ? `<persistent_memory node_refs="${escapeXml(persistent.nodeRefs.join(","))}">${persistent.body}</persistent_memory>`
628
+ : "",
629
+ ].filter(Boolean);
630
+ const envelope = `<session_memory source="falkordb+graphiti-cache" version="1">${sections.join("")}</session_memory>`;
631
+ const prepared = {
632
+ envelope,
633
+ nodeRefs: persistent.nodeRefs,
634
+ refreshDecision,
635
+ };
636
+ const currentState = this.sessions.get(sessionId);
637
+ if (currentState !== state || !currentState.isMain)
638
+ return null;
639
+ if (state.pendingInjectionGeneration !== generation)
640
+ return null;
641
+ state.pendingInjection = prepared;
642
+ state.hotTierReady = true;
643
+ state.latestRefreshQuery = latestRequest || cacheMeta?.lastQuery;
644
+ return prepared;
645
+ }
646
+ deleteSession(sessionId) {
647
+ const lifecycle = this.sessionLifecycles.get(sessionId);
648
+ if (lifecycle?.idleCleanupTimer != null) {
649
+ this.clearTimerImpl(lifecycle.idleCleanupTimer);
650
+ }
651
+ this.sessionLifecycles.delete(sessionId);
652
+ this.sessions.delete(sessionId);
653
+ this.parentIdCache.delete(sessionId);
654
+ this.canonicalSessionIdCache.delete(sessionId);
655
+ this.temporaryRootSessionIds.delete(sessionId);
656
+ for (const [childSessionId, parentId] of [...this.parentIdCache.entries()]) {
657
+ if (parentId === sessionId)
658
+ this.parentIdCache.delete(childSessionId);
659
+ }
660
+ for (const [childSessionId, canonicalSessionId] of [
661
+ ...this.canonicalSessionIdCache.entries(),
662
+ ]) {
663
+ if (canonicalSessionId === sessionId) {
664
+ this.canonicalSessionIdCache.delete(childSessionId);
665
+ }
666
+ }
667
+ const prefix = `${sessionId}:`;
668
+ for (const key of [...this.pendingAssistantMessages.keys()]) {
669
+ if (key.startsWith(prefix)) {
670
+ this.pendingAssistantMessages.delete(key);
671
+ this.pendingAssistantCompletions.delete(key);
672
+ }
673
+ }
674
+ for (const [key] of [...this.bufferedAssistantMessageIds.entries()]) {
675
+ if (key.startsWith(prefix))
676
+ this.bufferedAssistantMessageIds.delete(key);
677
+ }
678
+ for (const key of [...this.pendingAssistantCompletions]) {
679
+ if (key.startsWith(prefix))
680
+ this.pendingAssistantCompletions.delete(key);
681
+ }
682
+ }
683
+ getLifecycle(sessionId) {
684
+ let lifecycle = this.sessionLifecycles.get(sessionId);
685
+ if (!lifecycle) {
686
+ lifecycle = {
687
+ activityGeneration: 0,
688
+ idleCleanupTimer: null,
689
+ };
690
+ this.sessionLifecycles.set(sessionId, lifecycle);
691
+ }
692
+ return lifecycle;
693
+ }
694
+ }