opencode-graphiti 0.1.4 → 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 (84) hide show
  1. package/README.md +10 -17
  2. package/esm/deno.d.ts +47 -0
  3. package/esm/deno.d.ts.map +1 -0
  4. package/esm/deno.js +41 -0
  5. package/esm/src/config.d.ts.map +1 -1
  6. package/esm/src/config.js +4 -2
  7. package/esm/src/handlers/chat.d.ts +2 -1
  8. package/esm/src/handlers/chat.d.ts.map +1 -1
  9. package/esm/src/handlers/chat.js +89 -49
  10. package/esm/src/handlers/compacting.d.ts +1 -0
  11. package/esm/src/handlers/compacting.d.ts.map +1 -1
  12. package/esm/src/handlers/compacting.js +2 -1
  13. package/esm/src/handlers/event.d.ts +2 -2
  14. package/esm/src/handlers/event.d.ts.map +1 -1
  15. package/esm/src/handlers/event.js +52 -1
  16. package/esm/src/handlers/system.d.ts +11 -0
  17. package/esm/src/handlers/system.d.ts.map +1 -0
  18. package/esm/src/handlers/system.js +18 -0
  19. package/esm/src/index.d.ts.map +1 -1
  20. package/esm/src/index.js +8 -2
  21. package/esm/src/services/client.d.ts +8 -1
  22. package/esm/src/services/client.d.ts.map +1 -1
  23. package/esm/src/services/client.js +32 -2
  24. package/esm/src/services/compaction.d.ts +10 -0
  25. package/esm/src/services/compaction.d.ts.map +1 -1
  26. package/esm/src/services/compaction.js +106 -18
  27. package/esm/src/services/context-limit.d.ts +2 -8
  28. package/esm/src/services/context-limit.d.ts.map +1 -1
  29. package/esm/src/services/context-limit.js +3 -1
  30. package/esm/src/services/context.d.ts +27 -2
  31. package/esm/src/services/context.d.ts.map +1 -1
  32. package/esm/src/services/context.js +107 -13
  33. package/esm/src/services/logger.d.ts.map +1 -1
  34. package/esm/src/services/logger.js +1 -2
  35. package/esm/src/session.d.ts +6 -25
  36. package/esm/src/session.d.ts.map +1 -1
  37. package/esm/src/session.js +4 -7
  38. package/esm/src/types/index.d.ts +8 -2
  39. package/esm/src/types/index.d.ts.map +1 -1
  40. package/package.json +25 -3
  41. package/script/deno.d.ts +47 -0
  42. package/script/deno.d.ts.map +1 -0
  43. package/script/deno.js +43 -0
  44. package/script/src/config.d.ts.map +1 -1
  45. package/script/src/config.js +4 -2
  46. package/script/src/handlers/chat.d.ts +2 -1
  47. package/script/src/handlers/chat.d.ts.map +1 -1
  48. package/script/src/handlers/chat.js +88 -48
  49. package/script/src/handlers/compacting.d.ts +1 -0
  50. package/script/src/handlers/compacting.d.ts.map +1 -1
  51. package/script/src/handlers/compacting.js +2 -1
  52. package/script/src/handlers/event.d.ts +2 -2
  53. package/script/src/handlers/event.d.ts.map +1 -1
  54. package/script/src/handlers/event.js +52 -1
  55. package/script/src/handlers/system.d.ts +11 -0
  56. package/script/src/handlers/system.d.ts.map +1 -0
  57. package/script/src/handlers/system.js +21 -0
  58. package/script/src/index.d.ts.map +1 -1
  59. package/script/src/index.js +8 -2
  60. package/script/src/services/client.d.ts +8 -1
  61. package/script/src/services/client.d.ts.map +1 -1
  62. package/script/src/services/client.js +35 -2
  63. package/script/src/services/compaction.d.ts +10 -0
  64. package/script/src/services/compaction.d.ts.map +1 -1
  65. package/script/src/services/compaction.js +108 -17
  66. package/script/src/services/context-limit.d.ts +2 -8
  67. package/script/src/services/context-limit.d.ts.map +1 -1
  68. package/script/src/services/context-limit.js +3 -1
  69. package/script/src/services/context.d.ts +27 -2
  70. package/script/src/services/context.d.ts.map +1 -1
  71. package/script/src/services/context.js +118 -14
  72. package/script/src/services/logger.d.ts.map +1 -1
  73. package/script/src/services/logger.js +1 -35
  74. package/script/src/session.d.ts +6 -25
  75. package/script/src/session.d.ts.map +1 -1
  76. package/script/src/session.js +4 -7
  77. package/script/src/types/index.d.ts +8 -2
  78. package/script/src/types/index.d.ts.map +1 -1
  79. package/esm/_dnt.shims.d.ts +0 -6
  80. package/esm/_dnt.shims.d.ts.map +0 -1
  81. package/esm/_dnt.shims.js +0 -61
  82. package/script/_dnt.shims.d.ts +0 -6
  83. package/script/_dnt.shims.d.ts.map +0 -1
  84. package/script/_dnt.shims.js +0 -65
package/package.json CHANGED
@@ -1,10 +1,30 @@
1
1
  {
2
2
  "name": "opencode-graphiti",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "OpenCode plugin for persistent memory via Graphiti knowledge graph",
5
+ "keywords": [
6
+ "opencode",
7
+ "graphiti",
8
+ "knowledge-graph",
9
+ "persistent-memory",
10
+ "plugin",
11
+ "mcp",
12
+ "ai",
13
+ "context"
14
+ ],
15
+ "author": "Vicary A. <vicary.archangel@member.mensa.org>",
16
+ "homepage": "https://github.com/vicary/opencode-graphiti#readme",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/vicary/opencode-graphiti.git"
20
+ },
5
21
  "license": "MIT",
22
+ "bugs": {
23
+ "url": "https://github.com/vicary/opencode-graphiti/issues"
24
+ },
6
25
  "main": "./esm/mod.js",
7
26
  "module": "./esm/mod.js",
27
+ "types": "./esm/mod.d.ts",
8
28
  "exports": {
9
29
  ".": {
10
30
  "import": "./esm/mod.js",
@@ -12,6 +32,9 @@
12
32
  }
13
33
  },
14
34
  "scripts": {},
35
+ "engines": {
36
+ "node": ">=20"
37
+ },
15
38
  "opencode": {
16
39
  "type": "plugin",
17
40
  "hooks": [
@@ -25,8 +48,7 @@
25
48
  "@opencode-ai/plugin": "^1.1.53",
26
49
  "@opencode-ai/sdk": "^1.1.53",
27
50
  "cosmiconfig": "9.0.0",
28
- "zod": "4.3.6",
29
- "@deno/shim-deno": "~0.18.0"
51
+ "zod": "4.3.6"
30
52
  },
31
53
  "devDependencies": {
32
54
  "@types/node": "^20.9.0"
@@ -0,0 +1,47 @@
1
+ declare namespace _default {
2
+ export let name: string;
3
+ export let description: string;
4
+ export let version: string;
5
+ export let license: string;
6
+ export namespace tasks {
7
+ let build: string;
8
+ namespace deploy {
9
+ let command: string;
10
+ let dependencies: string[];
11
+ }
12
+ let dev: string;
13
+ let check: string;
14
+ let lint: string;
15
+ let fmt: string;
16
+ }
17
+ export namespace lint_1 {
18
+ let exclude: string[];
19
+ namespace rules {
20
+ let exclude_1: string[];
21
+ export { exclude_1 as exclude };
22
+ }
23
+ }
24
+ export { lint_1 as lint };
25
+ export namespace fmt_1 {
26
+ let exclude_2: string[];
27
+ export { exclude_2 as exclude };
28
+ }
29
+ export { fmt_1 as fmt };
30
+ export namespace compilerOptions {
31
+ let strict: boolean;
32
+ let lib: string[];
33
+ }
34
+ export let nodeModulesDir: string;
35
+ export let imports: {
36
+ "@modelcontextprotocol/sdk": string;
37
+ "@opencode-ai/plugin": string;
38
+ "@opencode-ai/sdk": string;
39
+ cosmiconfig: string;
40
+ zod: string;
41
+ };
42
+ export let exports: {
43
+ ".": string;
44
+ };
45
+ }
46
+ export default _default;
47
+ //# sourceMappingURL=deno.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deno.d.ts","sourceRoot":"","sources":["../src/deno.js"],"names":[],"mappings":""}
package/script/deno.js ADDED
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = {
4
+ "name": "opencode-graphiti",
5
+ "description": "OpenCode plugin for persistent memory via Graphiti knowledge graph",
6
+ "version": "0.0.0-development",
7
+ "license": "MIT",
8
+ "tasks": {
9
+ "build": "deno run -A dnt.ts",
10
+ "deploy": {
11
+ "command": "cd dist/ && npm publish",
12
+ "dependencies": ["build"]
13
+ },
14
+ "dev": "deno run --allow-all src/index.ts",
15
+ "check": "deno check src/index.ts",
16
+ "lint": "deno lint",
17
+ "fmt": "deno fmt"
18
+ },
19
+ "lint": {
20
+ "exclude": ["dist/"],
21
+ "rules": {
22
+ "exclude": ["no-import-prefix"]
23
+ }
24
+ },
25
+ "fmt": {
26
+ "exclude": ["dist/"]
27
+ },
28
+ "compilerOptions": {
29
+ "strict": true,
30
+ "lib": ["deno.ns", "dom", "esnext"]
31
+ },
32
+ "nodeModulesDir": "auto",
33
+ "imports": {
34
+ "@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.25.2",
35
+ "@opencode-ai/plugin": "npm:@opencode-ai/plugin@^1.1.53",
36
+ "@opencode-ai/sdk": "npm:@opencode-ai/sdk@^1.1.53",
37
+ "cosmiconfig": "npm:cosmiconfig@9.0.0",
38
+ "zod": "npm:zod@4.3.6"
39
+ },
40
+ "exports": {
41
+ ".": "./mod.ts"
42
+ }
43
+ };
@@ -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"}
@@ -39,12 +39,14 @@ const z = __importStar(require("zod/mini"));
39
39
  const DEFAULT_CONFIG = {
40
40
  endpoint: "http://localhost:8000/mcp",
41
41
  groupIdPrefix: "opencode",
42
- injectionInterval: 10,
42
+ driftThreshold: 0.5,
43
+ factStaleDays: 30,
43
44
  };
44
45
  const GraphitiConfigSchema = z.object({
45
46
  endpoint: z.string(),
46
47
  groupIdPrefix: z.string(),
47
- injectionInterval: z.number(),
48
+ driftThreshold: z.number(),
49
+ factStaleDays: z.number(),
48
50
  });
49
51
  /**
50
52
  * 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"}
@@ -7,17 +7,8 @@ const logger_js_1 = require("../services/logger.js");
7
7
  const utils_js_1 = require("../utils.js");
8
8
  /** Creates the `chat.message` hook handler. */
9
9
  function createChatHandler(deps) {
10
- const { sessionManager, injectionInterval, client } = deps;
11
- const removeSyntheticMemoryParts = (parts) => parts.filter((part) => {
12
- if (part.type !== "text")
13
- return true;
14
- if (part.id?.startsWith("graphiti-memory-"))
15
- return false;
16
- if (part.id?.startsWith("graphiti-refresh-"))
17
- return false;
18
- return true;
19
- });
20
- const injectMemoryContext = async (state, messageText, output, prefix, useUserScope, characterBudget, shouldReinject) => {
10
+ const { sessionManager, driftThreshold, factStaleDays, client } = deps;
11
+ const searchAndCacheMemoryContext = async (state, messageText, useUserScope, characterBudget, seedFactUuids) => {
21
12
  const userGroupId = state.userGroupId;
22
13
  const projectFactsPromise = client.searchFacts({
23
14
  query: messageText,
@@ -50,45 +41,83 @@ function createChatHandler(deps) {
50
41
  userFactsPromise,
51
42
  userNodesPromise,
52
43
  ]);
53
- const projectContext = (0, context_js_1.formatMemoryContext)(projectFacts, projectNodes);
54
- const userContext = (0, context_js_1.formatMemoryContext)(userFacts, userNodes);
55
- if (!projectContext && !userContext)
44
+ const projectContext = (0, context_js_1.deduplicateContext)({
45
+ facts: projectFacts,
46
+ nodes: projectNodes,
47
+ });
48
+ const userContext = (0, context_js_1.deduplicateContext)({
49
+ facts: userFacts,
50
+ nodes: userNodes,
51
+ });
52
+ const projectContextString = (0, context_js_1.formatMemoryContext)(projectContext.facts, projectContext.nodes, { factStaleDays });
53
+ const userContextString = (0, context_js_1.formatMemoryContext)(userContext.facts, userContext.nodes, { factStaleDays });
54
+ if (!projectContextString && !userContextString)
56
55
  return;
56
+ let snapshotPrimer = "";
57
+ if (useUserScope && characterBudget > 0) {
58
+ try {
59
+ const episodes = await client.getEpisodes({
60
+ groupId: state.groupId,
61
+ lastN: 10,
62
+ });
63
+ const snapshot = episodes
64
+ .filter((episode) => {
65
+ const description = episode.sourceDescription ??
66
+ episode.source_description ?? "";
67
+ return description === "session-snapshot";
68
+ })
69
+ .sort((a, b) => {
70
+ const aTime = a.created_at ? Date.parse(a.created_at) : 0;
71
+ const bTime = b.created_at ? Date.parse(b.created_at) : 0;
72
+ return bTime - aTime;
73
+ })[0];
74
+ if (snapshot?.content) {
75
+ const snapshotBudget = Math.min(characterBudget, 1200);
76
+ snapshotPrimer = [
77
+ "## Session Snapshot",
78
+ "> Most recent session snapshot; use to restore active strategy and open questions.",
79
+ "",
80
+ snapshot.content.slice(0, snapshotBudget),
81
+ ].join("\n");
82
+ }
83
+ }
84
+ catch (err) {
85
+ logger_js_1.logger.error("Failed to load session snapshot", { err });
86
+ }
87
+ }
57
88
  const projectBudget = useUserScope
58
89
  ? Math.floor(characterBudget * 0.7)
59
90
  : characterBudget;
60
91
  const userBudget = characterBudget - projectBudget;
61
- const truncatedProject = projectContext.slice(0, projectBudget);
62
- const truncatedUser = useUserScope ? userContext.slice(0, userBudget) : "";
63
- const memoryContext = [truncatedProject, truncatedUser]
92
+ const truncatedProject = projectContextString.slice(0, projectBudget);
93
+ const truncatedUser = useUserScope
94
+ ? userContextString.slice(0, userBudget)
95
+ : "";
96
+ const memoryContext = [snapshotPrimer, truncatedProject, truncatedUser]
64
97
  .filter((section) => section.trim().length > 0)
65
98
  .join("\n\n")
66
99
  .slice(0, characterBudget);
67
100
  if (!memoryContext)
68
101
  return;
69
- if ("system" in output.message) {
70
- try {
71
- output.message.system = memoryContext;
72
- return;
73
- }
74
- catch (_err) {
75
- // Fall through to synthetic injection.
76
- }
77
- }
78
- if (shouldReinject) {
79
- output.parts = removeSyntheticMemoryParts(output.parts);
80
- }
81
- {
82
- output.parts.unshift({
83
- type: "text",
84
- text: memoryContext,
85
- id: `${prefix}${Date.now()}`,
86
- sessionID: output.message.sessionID,
87
- messageID: output.message.id,
88
- synthetic: true,
89
- });
102
+ const factUuids = seedFactUuids ??
103
+ new Set([
104
+ ...projectContext.facts.map((fact) => fact.uuid),
105
+ ...userContext.facts.map((fact) => fact.uuid),
106
+ ]);
107
+ state.cachedMemoryContext = memoryContext;
108
+ logger_js_1.logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for system prompt injection`);
109
+ state.lastInjectionFactUuids = factUuids;
110
+ };
111
+ const computeJaccardSimilarity = (left, right) => {
112
+ if (left.size === 0 && right.size === 0)
113
+ return 1;
114
+ let intersection = 0;
115
+ for (const value of left) {
116
+ if (right.has(value))
117
+ intersection += 1;
90
118
  }
91
- logger_js_1.logger.info(`Injected ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes`);
119
+ const union = left.size + right.size - intersection;
120
+ return union === 0 ? 1 : intersection / union;
92
121
  };
93
122
  return async ({ sessionID }, output) => {
94
123
  if (await sessionManager.isSubagentSession(sessionID)) {
@@ -115,19 +144,30 @@ function createChatHandler(deps) {
115
144
  messageLength: messageText.length,
116
145
  });
117
146
  const shouldInjectOnFirst = !state.injectedMemories;
118
- const shouldReinject = !shouldInjectOnFirst &&
119
- injectionInterval > 0 &&
120
- (state.messageCount - state.lastInjectionMessageCount) >=
121
- injectionInterval;
122
- if (!shouldInjectOnFirst && !shouldReinject)
123
- return;
147
+ let shouldReinject = false;
148
+ let currentFactUuids = null;
149
+ if (!shouldInjectOnFirst) {
150
+ const driftFacts = await client.searchFacts({
151
+ query: messageText,
152
+ groupIds: [state.groupId],
153
+ maxFacts: 20,
154
+ });
155
+ currentFactUuids = new Set(driftFacts.map((fact) => fact.uuid));
156
+ const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
157
+ shouldReinject = similarity < driftThreshold;
158
+ if (!shouldReinject) {
159
+ logger_js_1.logger.debug("Skipping reinjection; drift above threshold", {
160
+ sessionID,
161
+ similarity,
162
+ });
163
+ return;
164
+ }
165
+ }
124
166
  try {
125
- const prefix = shouldReinject ? "graphiti-refresh-" : "graphiti-memory-";
126
167
  const useUserScope = shouldInjectOnFirst;
127
168
  const characterBudget = (0, context_limit_js_1.calculateInjectionBudget)(state.contextLimit);
128
- await injectMemoryContext(state, messageText, output, prefix, useUserScope, characterBudget, shouldReinject);
169
+ await searchAndCacheMemoryContext(state, messageText, useUserScope, characterBudget, currentFactUuids);
129
170
  state.injectedMemories = true;
130
- state.lastInjectionMessageCount = state.messageCount;
131
171
  }
132
172
  catch (err) {
133
173
  logger_js_1.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"}
@@ -6,7 +6,7 @@ const context_limit_js_1 = require("../services/context-limit.js");
6
6
  const logger_js_1 = require("../services/logger.js");
7
7
  /** Creates the `experimental.session.compacting` hook handler. */
8
8
  function createCompactingHandler(deps) {
9
- const { sessionManager, client, defaultGroupId } = deps;
9
+ const { sessionManager, client, defaultGroupId, factStaleDays } = deps;
10
10
  return async ({ sessionID }, output) => {
11
11
  const state = sessionManager.getState(sessionID);
12
12
  if (!state?.isMain) {
@@ -23,6 +23,7 @@ function createCompactingHandler(deps) {
23
23
  user: state.userGroupId,
24
24
  },
25
25
  contextStrings: output.context,
26
+ factStaleDays,
26
27
  });
27
28
  if (additionalContext.length > 0) {
28
29
  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"}
@@ -9,6 +9,41 @@ const utils_js_1 = require("../utils.js");
9
9
  function createEventHandler(deps) {
10
10
  const { sessionManager, client, defaultGroupId, sdkClient, directory, groupIdPrefix, } = deps;
11
11
  const defaultUserGroupId = (0, utils_js_1.makeUserGroupId)(groupIdPrefix);
12
+ const buildSessionSnapshot = (sessionId, messages) => {
13
+ const recentMessages = messages.slice(-12);
14
+ const recentAssistant = [...recentMessages]
15
+ .reverse()
16
+ .find((message) => message.startsWith("Assistant:"))
17
+ ?.replace(/^Assistant:\s*/, "")
18
+ .trim();
19
+ const recentUser = [...recentMessages]
20
+ .reverse()
21
+ .find((message) => message.startsWith("User:"))
22
+ ?.replace(/^User:\s*/, "")
23
+ .trim();
24
+ const questionRegex = /[^\n\r?]{3,200}\?/g;
25
+ const questions = recentMessages
26
+ .flatMap((message) => {
27
+ const text = message.replace(/^(User|Assistant):\s*/, "");
28
+ return text.match(questionRegex) ?? [];
29
+ })
30
+ .map((question) => question.trim());
31
+ const uniqueQuestions = Array.from(new Set(questions)).slice(0, 6);
32
+ const lines = [];
33
+ lines.push(`Session ${sessionId} working snapshot`);
34
+ if (recentUser)
35
+ lines.push(`Recent user focus: ${recentUser}`);
36
+ if (recentAssistant) {
37
+ lines.push(`Recent assistant focus: ${recentAssistant}`);
38
+ }
39
+ if (uniqueQuestions.length > 0) {
40
+ lines.push("Open questions:");
41
+ for (const question of uniqueQuestions) {
42
+ lines.push(`- ${question}`);
43
+ }
44
+ }
45
+ return lines.join("\n");
46
+ };
12
47
  return async ({ event }) => {
13
48
  try {
14
49
  if (event.type === "session.created") {
@@ -27,7 +62,7 @@ function createEventHandler(deps) {
27
62
  groupId: defaultGroupId,
28
63
  userGroupId: defaultUserGroupId,
29
64
  injectedMemories: false,
30
- lastInjectionMessageCount: 0,
65
+ lastInjectionFactUuids: new Set(),
31
66
  messageCount: 0,
32
67
  pendingMessages: [],
33
68
  contextLimit: 200_000,
@@ -74,6 +109,22 @@ function createEventHandler(deps) {
74
109
  logger_js_1.logger.debug("Ignoring non-main idle session:", sessionId);
75
110
  return;
76
111
  }
112
+ try {
113
+ const snapshotContent = buildSessionSnapshot(sessionId, state.pendingMessages);
114
+ if (snapshotContent.trim()) {
115
+ await client.addEpisode({
116
+ name: `Snapshot: ${sessionId}`,
117
+ episodeBody: snapshotContent,
118
+ groupId: state.groupId,
119
+ source: "text",
120
+ sourceDescription: "session-snapshot",
121
+ });
122
+ logger_js_1.logger.info("Saved session snapshot", { sessionId });
123
+ }
124
+ }
125
+ catch (err) {
126
+ logger_js_1.logger.error("Failed to save session snapshot", { sessionId, err });
127
+ }
77
128
  await sessionManager.flushPendingMessages(sessionId, "Buffered messages from OpenCode session", 50);
78
129
  return;
79
130
  }
@@ -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,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSystemHandler = createSystemHandler;
4
+ const logger_js_1 = require("../services/logger.js");
5
+ function createSystemHandler(deps) {
6
+ const { sessionManager } = deps;
7
+ // Assumes chat.message hook completes before system.transform fires for the same turn.
8
+ // deno-lint-ignore require-await
9
+ return async ({ sessionID }, output) => {
10
+ if (!sessionID)
11
+ return;
12
+ const state = sessionManager.getState(sessionID);
13
+ if (!state?.isMain)
14
+ return;
15
+ if (!state.cachedMemoryContext)
16
+ return;
17
+ output.system.push(state.cachedMemoryContext);
18
+ state.cachedMemoryContext = undefined;
19
+ logger_js_1.logger.info("Injected memory context into system prompt");
20
+ };
21
+ }
@@ -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"}
@@ -5,6 +5,7 @@ const config_js_1 = require("./config.js");
5
5
  const chat_js_1 = require("./handlers/chat.js");
6
6
  const compacting_js_1 = require("./handlers/compacting.js");
7
7
  const event_js_1 = require("./handlers/event.js");
8
+ const system_js_1 = require("./handlers/system.js");
8
9
  const client_js_1 = require("./services/client.js");
9
10
  const logger_js_1 = require("./services/logger.js");
10
11
  const session_js_1 = require("./session.js");
@@ -30,19 +31,24 @@ const graphiti = async (input) => {
30
31
  sessionManager,
31
32
  client,
32
33
  defaultGroupId,
33
- sdkClient: sdkClient,
34
+ sdkClient,
34
35
  directory: input.directory,
35
36
  groupIdPrefix: config.groupIdPrefix,
36
37
  }),
37
38
  "chat.message": (0, chat_js_1.createChatHandler)({
38
39
  sessionManager,
39
- injectionInterval: config.injectionInterval,
40
+ driftThreshold: config.driftThreshold,
41
+ factStaleDays: config.factStaleDays,
40
42
  client,
41
43
  }),
42
44
  "experimental.session.compacting": (0, compacting_js_1.createCompactingHandler)({
43
45
  sessionManager,
44
46
  client,
45
47
  defaultGroupId,
48
+ factStaleDays: config.factStaleDays,
49
+ }),
50
+ "experimental.chat.system.transform": (0, system_js_1.createSystemHandler)({
51
+ sessionManager,
46
52
  }),
47
53
  };
48
54
  };
@@ -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":"AAEA,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;IAM5B,oDAAoD;IACpD,OAAO,CAAC,wBAAwB;IAOhC;;;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"}