opencode-graphiti 0.1.5 → 0.1.6

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 (77) hide show
  1. package/esm/src/config.d.ts.map +1 -1
  2. package/esm/src/config.js +4 -2
  3. package/esm/src/handlers/chat.d.ts +2 -1
  4. package/esm/src/handlers/chat.d.ts.map +1 -1
  5. package/esm/src/handlers/chat.js +89 -49
  6. package/esm/src/handlers/compacting.d.ts +1 -0
  7. package/esm/src/handlers/compacting.d.ts.map +1 -1
  8. package/esm/src/handlers/compacting.js +2 -1
  9. package/esm/src/handlers/event.d.ts +2 -2
  10. package/esm/src/handlers/event.d.ts.map +1 -1
  11. package/esm/src/handlers/event.js +52 -1
  12. package/esm/src/handlers/system.d.ts +11 -0
  13. package/esm/src/handlers/system.d.ts.map +1 -0
  14. package/esm/src/handlers/system.js +18 -0
  15. package/esm/src/index.d.ts.map +1 -1
  16. package/esm/src/index.js +8 -2
  17. package/esm/src/services/client.d.ts +8 -1
  18. package/esm/src/services/client.d.ts.map +1 -1
  19. package/esm/src/services/client.js +23 -0
  20. package/esm/src/services/compaction.d.ts +10 -0
  21. package/esm/src/services/compaction.d.ts.map +1 -1
  22. package/esm/src/services/compaction.js +106 -18
  23. package/esm/src/services/context-limit.d.ts +2 -8
  24. package/esm/src/services/context-limit.d.ts.map +1 -1
  25. package/esm/src/services/context-limit.js +3 -1
  26. package/esm/src/services/context.d.ts +27 -2
  27. package/esm/src/services/context.d.ts.map +1 -1
  28. package/esm/src/services/context.js +107 -13
  29. package/esm/src/services/logger.d.ts.map +1 -1
  30. package/esm/src/services/logger.js +1 -2
  31. package/esm/src/session.d.ts +6 -25
  32. package/esm/src/session.d.ts.map +1 -1
  33. package/esm/src/session.js +4 -7
  34. package/esm/src/types/index.d.ts +8 -2
  35. package/esm/src/types/index.d.ts.map +1 -1
  36. package/package.json +2 -3
  37. package/script/src/config.d.ts.map +1 -1
  38. package/script/src/config.js +4 -2
  39. package/script/src/handlers/chat.d.ts +2 -1
  40. package/script/src/handlers/chat.d.ts.map +1 -1
  41. package/script/src/handlers/chat.js +88 -48
  42. package/script/src/handlers/compacting.d.ts +1 -0
  43. package/script/src/handlers/compacting.d.ts.map +1 -1
  44. package/script/src/handlers/compacting.js +2 -1
  45. package/script/src/handlers/event.d.ts +2 -2
  46. package/script/src/handlers/event.d.ts.map +1 -1
  47. package/script/src/handlers/event.js +52 -1
  48. package/script/src/handlers/system.d.ts +11 -0
  49. package/script/src/handlers/system.d.ts.map +1 -0
  50. package/script/src/handlers/system.js +21 -0
  51. package/script/src/index.d.ts.map +1 -1
  52. package/script/src/index.js +8 -2
  53. package/script/src/services/client.d.ts +8 -1
  54. package/script/src/services/client.d.ts.map +1 -1
  55. package/script/src/services/client.js +23 -0
  56. package/script/src/services/compaction.d.ts +10 -0
  57. package/script/src/services/compaction.d.ts.map +1 -1
  58. package/script/src/services/compaction.js +108 -17
  59. package/script/src/services/context-limit.d.ts +2 -8
  60. package/script/src/services/context-limit.d.ts.map +1 -1
  61. package/script/src/services/context-limit.js +3 -1
  62. package/script/src/services/context.d.ts +27 -2
  63. package/script/src/services/context.d.ts.map +1 -1
  64. package/script/src/services/context.js +118 -14
  65. package/script/src/services/logger.d.ts.map +1 -1
  66. package/script/src/services/logger.js +1 -35
  67. package/script/src/session.d.ts +6 -25
  68. package/script/src/session.d.ts.map +1 -1
  69. package/script/src/session.js +4 -7
  70. package/script/src/types/index.d.ts +8 -2
  71. package/script/src/types/index.d.ts.map +1 -1
  72. package/esm/_dnt.shims.d.ts +0 -6
  73. package/esm/_dnt.shims.d.ts.map +0 -1
  74. package/esm/_dnt.shims.js +0 -61
  75. package/script/_dnt.shims.d.ts +0 -6
  76. package/script/_dnt.shims.d.ts.map +0 -1
  77. package/script/_dnt.shims.js +0 -65
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAcvD;;GAEG;AACH,wBAAgB,UAAU,IAAI,cAAc,CAc3C"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/src/config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgBvD;;GAEG;AACH,wBAAgB,UAAU,IAAI,cAAc,CAc3C"}
package/esm/src/config.js CHANGED
@@ -3,12 +3,14 @@ import * as z from "zod/mini";
3
3
  const DEFAULT_CONFIG = {
4
4
  endpoint: "http://localhost:8000/mcp",
5
5
  groupIdPrefix: "opencode",
6
- injectionInterval: 10,
6
+ driftThreshold: 0.5,
7
+ factStaleDays: 30,
7
8
  };
8
9
  const GraphitiConfigSchema = z.object({
9
10
  endpoint: z.string(),
10
11
  groupIdPrefix: z.string(),
11
- injectionInterval: z.number(),
12
+ driftThreshold: z.number(),
13
+ factStaleDays: z.number(),
12
14
  });
13
15
  /**
14
16
  * Load Graphiti configuration from JSONC files with defaults applied.
@@ -7,7 +7,8 @@ type ChatMessageOutput = Parameters<ChatMessageHook>[1];
7
7
  /** Dependencies for the chat message handler. */
8
8
  export interface ChatHandlerDeps {
9
9
  sessionManager: SessionManager;
10
- injectionInterval: number;
10
+ driftThreshold: number;
11
+ factStaleDays: number;
11
12
  client: GraphitiClient;
12
13
  }
13
14
  /** Creates the `chat.message` hook handler. */
@@ -1 +1 @@
1
- {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,KAAK,gBAAgB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,iBAAiB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IAyGvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBAuDzE"}
1
+ {"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/chat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,eAAe,GAAG,WAAW,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC;AAC1D,KAAK,gBAAgB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AACvD,KAAK,iBAAiB,GAAG,UAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAExD,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,cAAc,CAAC;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,cAAc,CAAC;CACxB;AAED,+CAA+C;AAC/C,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,eAAe,IA+IvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBAqEzE"}
@@ -1,20 +1,11 @@
1
1
  import { calculateInjectionBudget } from "../services/context-limit.js";
2
- import { formatMemoryContext } from "../services/context.js";
2
+ import { deduplicateContext, formatMemoryContext, } from "../services/context.js";
3
3
  import { logger } from "../services/logger.js";
4
4
  import { extractTextFromParts } from "../utils.js";
5
5
  /** Creates the `chat.message` hook handler. */
6
6
  export function createChatHandler(deps) {
7
- const { sessionManager, injectionInterval, client } = deps;
8
- const removeSyntheticMemoryParts = (parts) => parts.filter((part) => {
9
- if (part.type !== "text")
10
- return true;
11
- if (part.id?.startsWith("graphiti-memory-"))
12
- return false;
13
- if (part.id?.startsWith("graphiti-refresh-"))
14
- return false;
15
- return true;
16
- });
17
- const injectMemoryContext = async (state, messageText, output, prefix, useUserScope, characterBudget, shouldReinject) => {
7
+ const { sessionManager, driftThreshold, factStaleDays, client } = deps;
8
+ const searchAndCacheMemoryContext = async (state, messageText, useUserScope, characterBudget, seedFactUuids) => {
18
9
  const userGroupId = state.userGroupId;
19
10
  const projectFactsPromise = client.searchFacts({
20
11
  query: messageText,
@@ -47,45 +38,83 @@ export function createChatHandler(deps) {
47
38
  userFactsPromise,
48
39
  userNodesPromise,
49
40
  ]);
50
- const projectContext = formatMemoryContext(projectFacts, projectNodes);
51
- const userContext = formatMemoryContext(userFacts, userNodes);
52
- if (!projectContext && !userContext)
41
+ const projectContext = deduplicateContext({
42
+ facts: projectFacts,
43
+ nodes: projectNodes,
44
+ });
45
+ const userContext = deduplicateContext({
46
+ facts: userFacts,
47
+ nodes: userNodes,
48
+ });
49
+ const projectContextString = formatMemoryContext(projectContext.facts, projectContext.nodes, { factStaleDays });
50
+ const userContextString = formatMemoryContext(userContext.facts, userContext.nodes, { factStaleDays });
51
+ if (!projectContextString && !userContextString)
53
52
  return;
53
+ let snapshotPrimer = "";
54
+ if (useUserScope && characterBudget > 0) {
55
+ try {
56
+ const episodes = await client.getEpisodes({
57
+ groupId: state.groupId,
58
+ lastN: 10,
59
+ });
60
+ const snapshot = episodes
61
+ .filter((episode) => {
62
+ const description = episode.sourceDescription ??
63
+ episode.source_description ?? "";
64
+ return description === "session-snapshot";
65
+ })
66
+ .sort((a, b) => {
67
+ const aTime = a.created_at ? Date.parse(a.created_at) : 0;
68
+ const bTime = b.created_at ? Date.parse(b.created_at) : 0;
69
+ return bTime - aTime;
70
+ })[0];
71
+ if (snapshot?.content) {
72
+ const snapshotBudget = Math.min(characterBudget, 1200);
73
+ snapshotPrimer = [
74
+ "## Session Snapshot",
75
+ "> Most recent session snapshot; use to restore active strategy and open questions.",
76
+ "",
77
+ snapshot.content.slice(0, snapshotBudget),
78
+ ].join("\n");
79
+ }
80
+ }
81
+ catch (err) {
82
+ logger.error("Failed to load session snapshot", { err });
83
+ }
84
+ }
54
85
  const projectBudget = useUserScope
55
86
  ? Math.floor(characterBudget * 0.7)
56
87
  : characterBudget;
57
88
  const userBudget = characterBudget - projectBudget;
58
- const truncatedProject = projectContext.slice(0, projectBudget);
59
- const truncatedUser = useUserScope ? userContext.slice(0, userBudget) : "";
60
- const memoryContext = [truncatedProject, truncatedUser]
89
+ const truncatedProject = projectContextString.slice(0, projectBudget);
90
+ const truncatedUser = useUserScope
91
+ ? userContextString.slice(0, userBudget)
92
+ : "";
93
+ const memoryContext = [snapshotPrimer, truncatedProject, truncatedUser]
61
94
  .filter((section) => section.trim().length > 0)
62
95
  .join("\n\n")
63
96
  .slice(0, characterBudget);
64
97
  if (!memoryContext)
65
98
  return;
66
- if ("system" in output.message) {
67
- try {
68
- output.message.system = memoryContext;
69
- return;
70
- }
71
- catch (_err) {
72
- // Fall through to synthetic injection.
73
- }
74
- }
75
- if (shouldReinject) {
76
- output.parts = removeSyntheticMemoryParts(output.parts);
77
- }
78
- {
79
- output.parts.unshift({
80
- type: "text",
81
- text: memoryContext,
82
- id: `${prefix}${Date.now()}`,
83
- sessionID: output.message.sessionID,
84
- messageID: output.message.id,
85
- synthetic: true,
86
- });
99
+ const factUuids = seedFactUuids ??
100
+ new Set([
101
+ ...projectContext.facts.map((fact) => fact.uuid),
102
+ ...userContext.facts.map((fact) => fact.uuid),
103
+ ]);
104
+ state.cachedMemoryContext = memoryContext;
105
+ logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for system prompt injection`);
106
+ state.lastInjectionFactUuids = factUuids;
107
+ };
108
+ const computeJaccardSimilarity = (left, right) => {
109
+ if (left.size === 0 && right.size === 0)
110
+ return 1;
111
+ let intersection = 0;
112
+ for (const value of left) {
113
+ if (right.has(value))
114
+ intersection += 1;
87
115
  }
88
- logger.info(`Injected ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes`);
116
+ const union = left.size + right.size - intersection;
117
+ return union === 0 ? 1 : intersection / union;
89
118
  };
90
119
  return async ({ sessionID }, output) => {
91
120
  if (await sessionManager.isSubagentSession(sessionID)) {
@@ -112,19 +141,30 @@ export function createChatHandler(deps) {
112
141
  messageLength: messageText.length,
113
142
  });
114
143
  const shouldInjectOnFirst = !state.injectedMemories;
115
- const shouldReinject = !shouldInjectOnFirst &&
116
- injectionInterval > 0 &&
117
- (state.messageCount - state.lastInjectionMessageCount) >=
118
- injectionInterval;
119
- if (!shouldInjectOnFirst && !shouldReinject)
120
- return;
144
+ let shouldReinject = false;
145
+ let currentFactUuids = null;
146
+ if (!shouldInjectOnFirst) {
147
+ const driftFacts = await client.searchFacts({
148
+ query: messageText,
149
+ groupIds: [state.groupId],
150
+ maxFacts: 20,
151
+ });
152
+ currentFactUuids = new Set(driftFacts.map((fact) => fact.uuid));
153
+ const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
154
+ shouldReinject = similarity < driftThreshold;
155
+ if (!shouldReinject) {
156
+ logger.debug("Skipping reinjection; drift above threshold", {
157
+ sessionID,
158
+ similarity,
159
+ });
160
+ return;
161
+ }
162
+ }
121
163
  try {
122
- const prefix = shouldReinject ? "graphiti-refresh-" : "graphiti-memory-";
123
164
  const useUserScope = shouldInjectOnFirst;
124
165
  const characterBudget = calculateInjectionBudget(state.contextLimit);
125
- await injectMemoryContext(state, messageText, output, prefix, useUserScope, characterBudget, shouldReinject);
166
+ await searchAndCacheMemoryContext(state, messageText, useUserScope, characterBudget, currentFactUuids);
126
167
  state.injectedMemories = true;
127
- state.lastInjectionMessageCount = state.messageCount;
128
168
  }
129
169
  catch (err) {
130
170
  logger.error("Failed to inject memories:", err);
@@ -9,6 +9,7 @@ export interface CompactingHandlerDeps {
9
9
  sessionManager: SessionManager;
10
10
  client: GraphitiClient;
11
11
  defaultGroupId: string;
12
+ factStaleDays: number;
12
13
  }
13
14
  /** Creates the `experimental.session.compacting` hook handler. */
14
15
  export declare function createCompactingHandler(deps: CompactingHandlerDeps): ({ sessionID }: CompactingInput, output: CompactingOutput) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"compacting.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/compacting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;AAC5E,KAAK,eAAe,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtD,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,kEAAkE;AAClE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,qBAAqB,IAI/D,eAAe,eAAe,EAC9B,QAAQ,gBAAgB,mBAyB3B"}
1
+ {"version":3,"file":"compacting.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/compacting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,cAAc,GAAG,WAAW,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC,CAAC;AAC5E,KAAK,eAAe,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC;AAEtD,+CAA+C;AAC/C,MAAM,WAAW,qBAAqB;IACpC,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,kEAAkE;AAClE,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,qBAAqB,IAI/D,eAAe,eAAe,EAC9B,QAAQ,gBAAgB,mBA0B3B"}
@@ -3,7 +3,7 @@ import { calculateInjectionBudget } from "../services/context-limit.js";
3
3
  import { logger } from "../services/logger.js";
4
4
  /** Creates the `experimental.session.compacting` hook handler. */
5
5
  export function createCompactingHandler(deps) {
6
- const { sessionManager, client, defaultGroupId } = deps;
6
+ const { sessionManager, client, defaultGroupId, factStaleDays } = deps;
7
7
  return async ({ sessionID }, output) => {
8
8
  const state = sessionManager.getState(sessionID);
9
9
  if (!state?.isMain) {
@@ -20,6 +20,7 @@ export function createCompactingHandler(deps) {
20
20
  user: state.userGroupId,
21
21
  },
22
22
  contextStrings: output.context,
23
+ factStaleDays,
23
24
  });
24
25
  if (additionalContext.length > 0) {
25
26
  output.context.push(...additionalContext);
@@ -1,6 +1,6 @@
1
1
  import type { Hooks } from "@opencode-ai/plugin";
2
+ import type { OpencodeClient } from "@opencode-ai/sdk";
2
3
  import type { GraphitiClient } from "../services/client.js";
3
- import type { ProviderListClient } from "../services/context-limit.js";
4
4
  import type { SessionManager } from "../session.js";
5
5
  type EventHook = NonNullable<Hooks["event"]>;
6
6
  type EventInput = Parameters<EventHook>[0];
@@ -9,7 +9,7 @@ export interface EventHandlerDeps {
9
9
  sessionManager: SessionManager;
10
10
  client: GraphitiClient;
11
11
  defaultGroupId: string;
12
- sdkClient: ProviderListClient;
12
+ sdkClient: OpencodeClient;
13
13
  directory: string;
14
14
  groupIdPrefix: string;
15
15
  }
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,8BAA8B,CAAC;AAGvE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7C,KAAK,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3C,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,kBAAkB,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wCAAwC;AACxC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IAWzC,WAAW,UAAU,mBA+JpC"}
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/event.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,KAAK,SAAS,GAAG,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;AAC7C,KAAK,UAAU,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3C,0CAA0C;AAC1C,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,cAAc,CAAC;IAC/B,MAAM,EAAE,cAAc,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,cAAc,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,wCAAwC;AACxC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IAkDzC,WAAW,UAAU,mBAkLpC"}
@@ -6,6 +6,41 @@ import { isTextPart, makeUserGroupId } from "../utils.js";
6
6
  export function createEventHandler(deps) {
7
7
  const { sessionManager, client, defaultGroupId, sdkClient, directory, groupIdPrefix, } = deps;
8
8
  const defaultUserGroupId = makeUserGroupId(groupIdPrefix);
9
+ const buildSessionSnapshot = (sessionId, messages) => {
10
+ const recentMessages = messages.slice(-12);
11
+ const recentAssistant = [...recentMessages]
12
+ .reverse()
13
+ .find((message) => message.startsWith("Assistant:"))
14
+ ?.replace(/^Assistant:\s*/, "")
15
+ .trim();
16
+ const recentUser = [...recentMessages]
17
+ .reverse()
18
+ .find((message) => message.startsWith("User:"))
19
+ ?.replace(/^User:\s*/, "")
20
+ .trim();
21
+ const questionRegex = /[^\n\r?]{3,200}\?/g;
22
+ const questions = recentMessages
23
+ .flatMap((message) => {
24
+ const text = message.replace(/^(User|Assistant):\s*/, "");
25
+ return text.match(questionRegex) ?? [];
26
+ })
27
+ .map((question) => question.trim());
28
+ const uniqueQuestions = Array.from(new Set(questions)).slice(0, 6);
29
+ const lines = [];
30
+ lines.push(`Session ${sessionId} working snapshot`);
31
+ if (recentUser)
32
+ lines.push(`Recent user focus: ${recentUser}`);
33
+ if (recentAssistant) {
34
+ lines.push(`Recent assistant focus: ${recentAssistant}`);
35
+ }
36
+ if (uniqueQuestions.length > 0) {
37
+ lines.push("Open questions:");
38
+ for (const question of uniqueQuestions) {
39
+ lines.push(`- ${question}`);
40
+ }
41
+ }
42
+ return lines.join("\n");
43
+ };
9
44
  return async ({ event }) => {
10
45
  try {
11
46
  if (event.type === "session.created") {
@@ -24,7 +59,7 @@ export function createEventHandler(deps) {
24
59
  groupId: defaultGroupId,
25
60
  userGroupId: defaultUserGroupId,
26
61
  injectedMemories: false,
27
- lastInjectionMessageCount: 0,
62
+ lastInjectionFactUuids: new Set(),
28
63
  messageCount: 0,
29
64
  pendingMessages: [],
30
65
  contextLimit: 200_000,
@@ -71,6 +106,22 @@ export function createEventHandler(deps) {
71
106
  logger.debug("Ignoring non-main idle session:", sessionId);
72
107
  return;
73
108
  }
109
+ try {
110
+ const snapshotContent = buildSessionSnapshot(sessionId, state.pendingMessages);
111
+ if (snapshotContent.trim()) {
112
+ await client.addEpisode({
113
+ name: `Snapshot: ${sessionId}`,
114
+ episodeBody: snapshotContent,
115
+ groupId: state.groupId,
116
+ source: "text",
117
+ sourceDescription: "session-snapshot",
118
+ });
119
+ logger.info("Saved session snapshot", { sessionId });
120
+ }
121
+ }
122
+ catch (err) {
123
+ logger.error("Failed to save session snapshot", { sessionId, err });
124
+ }
74
125
  await sessionManager.flushPendingMessages(sessionId, "Buffered messages from OpenCode session", 50);
75
126
  return;
76
127
  }
@@ -0,0 +1,11 @@
1
+ import type { Hooks } from "@opencode-ai/plugin";
2
+ import type { SessionManager } from "../session.js";
3
+ type SystemTransformHook = NonNullable<Hooks["experimental.chat.system.transform"]>;
4
+ type SystemTransformInput = Parameters<SystemTransformHook>[0];
5
+ type SystemTransformOutput = Parameters<SystemTransformHook>[1];
6
+ export interface SystemHandlerDeps {
7
+ sessionManager: SessionManager;
8
+ }
9
+ export declare function createSystemHandler(deps: SystemHandlerDeps): ({ sessionID }: SystemTransformInput, output: SystemTransformOutput) => Promise<void>;
10
+ export {};
11
+ //# sourceMappingURL=system.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"system.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/system.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,mBAAmB,GAAG,WAAW,CACpC,KAAK,CAAC,oCAAoC,CAAC,CAC5C,CAAC;AACF,KAAK,oBAAoB,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAC/D,KAAK,qBAAqB,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEhE,MAAM,WAAW,iBAAiB;IAChC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,IAMvD,eAAe,oBAAoB,EACnC,QAAQ,qBAAqB,mBAYhC"}
@@ -0,0 +1,18 @@
1
+ import { logger } from "../services/logger.js";
2
+ export function createSystemHandler(deps) {
3
+ const { sessionManager } = deps;
4
+ // Assumes chat.message hook completes before system.transform fires for the same turn.
5
+ // deno-lint-ignore require-await
6
+ return async ({ sessionID }, output) => {
7
+ if (!sessionID)
8
+ return;
9
+ const state = sessionManager.getState(sessionID);
10
+ if (!state?.isMain)
11
+ return;
12
+ if (!state.cachedMemoryContext)
13
+ return;
14
+ output.system.push(state.cachedMemoryContext);
15
+ state.cachedMemoryContext = undefined;
16
+ logger.info("Injected memory context into system prompt");
17
+ };
18
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAW/D;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,MAkDtB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAC;AAW/D;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,MAuDtB,CAAC"}
package/esm/src/index.js CHANGED
@@ -2,6 +2,7 @@ import { loadConfig } from "./config.js";
2
2
  import { createChatHandler } from "./handlers/chat.js";
3
3
  import { createCompactingHandler } from "./handlers/compacting.js";
4
4
  import { createEventHandler } from "./handlers/event.js";
5
+ import { createSystemHandler } from "./handlers/system.js";
5
6
  import { GraphitiClient } from "./services/client.js";
6
7
  import { logger } from "./services/logger.js";
7
8
  import { SessionManager } from "./session.js";
@@ -27,19 +28,24 @@ export const graphiti = async (input) => {
27
28
  sessionManager,
28
29
  client,
29
30
  defaultGroupId,
30
- sdkClient: sdkClient,
31
+ sdkClient,
31
32
  directory: input.directory,
32
33
  groupIdPrefix: config.groupIdPrefix,
33
34
  }),
34
35
  "chat.message": createChatHandler({
35
36
  sessionManager,
36
- injectionInterval: config.injectionInterval,
37
+ driftThreshold: config.driftThreshold,
38
+ factStaleDays: config.factStaleDays,
37
39
  client,
38
40
  }),
39
41
  "experimental.session.compacting": createCompactingHandler({
40
42
  sessionManager,
41
43
  client,
42
44
  defaultGroupId,
45
+ factStaleDays: config.factStaleDays,
46
+ }),
47
+ "experimental.chat.system.transform": createSystemHandler({
48
+ sessionManager,
43
49
  }),
44
50
  };
45
51
  };
@@ -1,4 +1,4 @@
1
- import type { GraphitiFact, GraphitiNode } from "../types/index.js";
1
+ import type { GraphitiEpisode, GraphitiFact, GraphitiNode } from "../types/index.js";
2
2
  /**
3
3
  * Graphiti MCP client wrapper for connecting, querying,
4
4
  * and persisting episodes with basic reconnection handling.
@@ -57,6 +57,13 @@ export declare class GraphitiClient {
57
57
  groupIds?: string[];
58
58
  maxNodes?: number;
59
59
  }): Promise<GraphitiNode[]>;
60
+ /**
61
+ * Retrieve recent episodes for a group.
62
+ */
63
+ getEpisodes(params: {
64
+ groupId?: string;
65
+ lastN?: number;
66
+ }): Promise<GraphitiEpisode[]>;
60
67
  /**
61
68
  * Check whether the Graphiti MCP server is reachable.
62
69
  */
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/src/services/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,YAAY,EAEZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAG3B;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IAEzB;;OAEG;gBACS,QAAQ,EAAE,MAAM;IAS5B,oDAAoD;IACpD,OAAO,CAAC,wBAAwB;IAUhC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBjC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAOnB,QAAQ;IAkCtB,OAAO,CAAC,gBAAgB;YASV,SAAS;IAavB;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO;IAyBzC;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjB;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB3B;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB3B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAQpC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/src/services/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,eAAe,EACf,YAAY,EAEZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAG3B;;;GAGG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,SAAS,CAAgC;IACjD,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,QAAQ,CAAS;IAEzB;;OAEG;gBACS,QAAQ,EAAE,MAAM;IAS5B,oDAAoD;IACpD,OAAO,CAAC,wBAAwB;IAUhC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAgBjC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAOnB,QAAQ;IAkCtB,OAAO,CAAC,gBAAgB;YASV,SAAS;IAavB;;;OAGG;IACH,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO;IAyBzC;;OAEG;IACG,UAAU,CAAC,MAAM,EAAE;QACvB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;QACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,GAAG,OAAO,CAAC,IAAI,CAAC;IAWjB;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB3B;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;QACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAsB3B;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAqB9B;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAQpC"}
@@ -215,6 +215,29 @@ export class GraphitiClient {
215
215
  return [];
216
216
  }
217
217
  }
218
+ /**
219
+ * Retrieve recent episodes for a group.
220
+ */
221
+ async getEpisodes(params) {
222
+ try {
223
+ const result = await this.callTool("get_episodes", {
224
+ group_id: params.groupId,
225
+ last_n: params.lastN,
226
+ });
227
+ if (Array.isArray(result))
228
+ return result;
229
+ if (result &&
230
+ typeof result === "object" &&
231
+ Array.isArray(result.episodes)) {
232
+ return result.episodes;
233
+ }
234
+ return [];
235
+ }
236
+ catch (err) {
237
+ logger.error("getEpisodes error:", err);
238
+ return [];
239
+ }
240
+ }
218
241
  /**
219
242
  * Check whether the Graphiti MCP server is reachable.
220
243
  */
@@ -1,4 +1,13 @@
1
1
  import type { GraphitiFact, GraphitiNode } from "../types/index.js";
2
+ export declare const classifyFacts: (facts: GraphitiFact[], now: Date) => {
3
+ decisions: GraphitiFact[];
4
+ active: GraphitiFact[];
5
+ background: GraphitiFact[];
6
+ };
7
+ export declare const takeFactsWithinBudget: (facts: GraphitiFact[], budget: number, formatOptions: {
8
+ factStaleDays: number;
9
+ now: Date;
10
+ }) => GraphitiFact[];
2
11
  /**
3
12
  * Persist a compaction summary episode when enabled.
4
13
  */
@@ -38,5 +47,6 @@ export declare function getCompactionContext(params: {
38
47
  user?: string;
39
48
  };
40
49
  contextStrings: string[];
50
+ factStaleDays?: number;
41
51
  }): Promise<string[]>;
42
52
  //# sourceMappingURL=compaction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/src/services/compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpE;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,IAAI,EAAE;YACjB,IAAI,EAAE,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;YACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;SAC5B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE;QACN,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAC9B,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;KAC/B,CAAC;IACF,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgIpB"}
1
+ {"version":3,"file":"compaction.d.ts","sourceRoot":"","sources":["../../../src/src/services/compaction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AA0BpE,eAAO,MAAM,aAAa,GACxB,OAAO,YAAY,EAAE,EACrB,KAAK,IAAI,KACR;IACD,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,UAAU,EAAE,YAAY,EAAE,CAAC;CA4B5B,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,OAAO,YAAY,EAAE,EACrB,QAAQ,MAAM,EACd,eAAe;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,KAClD,YAAY,EAoBd,CAAC;AAEF;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE;QACN,UAAU,EAAE,CAAC,IAAI,EAAE;YACjB,IAAI,EAAE,MAAM,CAAC;YACb,WAAW,EAAE,MAAM,CAAC;YACpB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;YACrC,iBAAiB,CAAC,EAAE,MAAM,CAAC;SAC5B,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACrB,CAAC;IACF,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBhB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE;IACjD,MAAM,EAAE;QACN,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QAC9B,WAAW,EAAE,CAAC,IAAI,EAAE;YAClB,KAAK,EAAE,MAAM,CAAC;YACd,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;YACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;SACnB,KAAK,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;KAC/B,CAAC;IACF,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE;QACR,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;IACF,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CA0KpB"}