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.
- package/README.md +10 -17
- package/esm/deno.d.ts +47 -0
- package/esm/deno.d.ts.map +1 -0
- package/esm/deno.js +41 -0
- package/esm/src/config.d.ts.map +1 -1
- package/esm/src/config.js +4 -2
- package/esm/src/handlers/chat.d.ts +2 -1
- package/esm/src/handlers/chat.d.ts.map +1 -1
- package/esm/src/handlers/chat.js +89 -49
- package/esm/src/handlers/compacting.d.ts +1 -0
- package/esm/src/handlers/compacting.d.ts.map +1 -1
- package/esm/src/handlers/compacting.js +2 -1
- package/esm/src/handlers/event.d.ts +2 -2
- package/esm/src/handlers/event.d.ts.map +1 -1
- package/esm/src/handlers/event.js +52 -1
- package/esm/src/handlers/system.d.ts +11 -0
- package/esm/src/handlers/system.d.ts.map +1 -0
- package/esm/src/handlers/system.js +18 -0
- package/esm/src/index.d.ts.map +1 -1
- package/esm/src/index.js +8 -2
- package/esm/src/services/client.d.ts +8 -1
- package/esm/src/services/client.d.ts.map +1 -1
- package/esm/src/services/client.js +32 -2
- package/esm/src/services/compaction.d.ts +10 -0
- package/esm/src/services/compaction.d.ts.map +1 -1
- package/esm/src/services/compaction.js +106 -18
- package/esm/src/services/context-limit.d.ts +2 -8
- package/esm/src/services/context-limit.d.ts.map +1 -1
- package/esm/src/services/context-limit.js +3 -1
- package/esm/src/services/context.d.ts +27 -2
- package/esm/src/services/context.d.ts.map +1 -1
- package/esm/src/services/context.js +107 -13
- package/esm/src/services/logger.d.ts.map +1 -1
- package/esm/src/services/logger.js +1 -2
- package/esm/src/session.d.ts +6 -25
- package/esm/src/session.d.ts.map +1 -1
- package/esm/src/session.js +4 -7
- package/esm/src/types/index.d.ts +8 -2
- package/esm/src/types/index.d.ts.map +1 -1
- package/package.json +25 -3
- package/script/deno.d.ts +47 -0
- package/script/deno.d.ts.map +1 -0
- package/script/deno.js +43 -0
- package/script/src/config.d.ts.map +1 -1
- package/script/src/config.js +4 -2
- package/script/src/handlers/chat.d.ts +2 -1
- package/script/src/handlers/chat.d.ts.map +1 -1
- package/script/src/handlers/chat.js +88 -48
- package/script/src/handlers/compacting.d.ts +1 -0
- package/script/src/handlers/compacting.d.ts.map +1 -1
- package/script/src/handlers/compacting.js +2 -1
- package/script/src/handlers/event.d.ts +2 -2
- package/script/src/handlers/event.d.ts.map +1 -1
- package/script/src/handlers/event.js +52 -1
- package/script/src/handlers/system.d.ts +11 -0
- package/script/src/handlers/system.d.ts.map +1 -0
- package/script/src/handlers/system.js +21 -0
- package/script/src/index.d.ts.map +1 -1
- package/script/src/index.js +8 -2
- package/script/src/services/client.d.ts +8 -1
- package/script/src/services/client.d.ts.map +1 -1
- package/script/src/services/client.js +35 -2
- package/script/src/services/compaction.d.ts +10 -0
- package/script/src/services/compaction.d.ts.map +1 -1
- package/script/src/services/compaction.js +108 -17
- package/script/src/services/context-limit.d.ts +2 -8
- package/script/src/services/context-limit.d.ts.map +1 -1
- package/script/src/services/context-limit.js +3 -1
- package/script/src/services/context.d.ts +27 -2
- package/script/src/services/context.d.ts.map +1 -1
- package/script/src/services/context.js +118 -14
- package/script/src/services/logger.d.ts.map +1 -1
- package/script/src/services/logger.js +1 -35
- package/script/src/session.d.ts +6 -25
- package/script/src/session.d.ts.map +1 -1
- package/script/src/session.js +4 -7
- package/script/src/types/index.d.ts +8 -2
- package/script/src/types/index.d.ts.map +1 -1
- package/esm/_dnt.shims.d.ts +0 -6
- package/esm/_dnt.shims.d.ts.map +0 -1
- package/esm/_dnt.shims.js +0 -61
- package/script/_dnt.shims.d.ts +0 -6
- package/script/_dnt.shims.d.ts.map +0 -1
- package/script/_dnt.shims.js +0 -65
package/README.md
CHANGED
|
@@ -101,7 +101,7 @@ Create a config file at `~/.config/opencode/graphiti.jsonc`:
|
|
|
101
101
|
// Graphiti MCP server endpoint
|
|
102
102
|
"endpoint": "http://localhost:8000/mcp",
|
|
103
103
|
|
|
104
|
-
// Prefix for project group IDs (e.g. "
|
|
104
|
+
// Prefix for project group IDs (e.g. "opencode-my-project")
|
|
105
105
|
"groupIdPrefix": "opencode",
|
|
106
106
|
|
|
107
107
|
// Number of user messages between memory re-injections (0 = disabled)
|
|
@@ -118,8 +118,12 @@ values.
|
|
|
118
118
|
|
|
119
119
|
On the first user message in a session, the plugin searches Graphiti for facts
|
|
120
120
|
and entities relevant to the message content. Results are split into project and
|
|
121
|
-
user scopes (70% / 30% budget split), formatted
|
|
122
|
-
|
|
121
|
+
user scopes (70% / 30% budget split), formatted in XML-style `<memory>` blocks
|
|
122
|
+
with explicit de-emphasis instructions, and injected into the conversation.
|
|
123
|
+
|
|
124
|
+
Memory is injected via `output.message.system` (system-level instruction) when
|
|
125
|
+
available, which prevents memory from influencing session titles. If the system
|
|
126
|
+
field is unavailable, the plugin falls back to prepending synthetic parts.
|
|
123
127
|
|
|
124
128
|
The injection budget is calculated dynamically: 5% of the model's context limit
|
|
125
129
|
(resolved from the provider list) multiplied by 4 characters per token.
|
|
@@ -167,21 +171,10 @@ Each project gets a unique `group_id` derived from its directory name (e.g.
|
|
|
167
171
|
underscores (colons are not allowed). This ensures memories from different
|
|
168
172
|
projects stay isolated.
|
|
169
173
|
|
|
170
|
-
##
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
# Format
|
|
174
|
-
deno fmt
|
|
175
|
-
|
|
176
|
-
# Lint
|
|
177
|
-
deno lint
|
|
174
|
+
## Contributing
|
|
178
175
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
# Build
|
|
183
|
-
deno task build
|
|
184
|
-
```
|
|
176
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and release
|
|
177
|
+
process.
|
|
185
178
|
|
|
186
179
|
## License
|
|
187
180
|
|
package/esm/deno.d.ts
ADDED
|
@@ -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/esm/deno.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"name": "opencode-graphiti",
|
|
3
|
+
"description": "OpenCode plugin for persistent memory via Graphiti knowledge graph",
|
|
4
|
+
"version": "0.0.0-development",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"tasks": {
|
|
7
|
+
"build": "deno run -A dnt.ts",
|
|
8
|
+
"deploy": {
|
|
9
|
+
"command": "cd dist/ && npm publish",
|
|
10
|
+
"dependencies": ["build"]
|
|
11
|
+
},
|
|
12
|
+
"dev": "deno run --allow-all src/index.ts",
|
|
13
|
+
"check": "deno check src/index.ts",
|
|
14
|
+
"lint": "deno lint",
|
|
15
|
+
"fmt": "deno fmt"
|
|
16
|
+
},
|
|
17
|
+
"lint": {
|
|
18
|
+
"exclude": ["dist/"],
|
|
19
|
+
"rules": {
|
|
20
|
+
"exclude": ["no-import-prefix"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"fmt": {
|
|
24
|
+
"exclude": ["dist/"]
|
|
25
|
+
},
|
|
26
|
+
"compilerOptions": {
|
|
27
|
+
"strict": true,
|
|
28
|
+
"lib": ["deno.ns", "dom", "esnext"]
|
|
29
|
+
},
|
|
30
|
+
"nodeModulesDir": "auto",
|
|
31
|
+
"imports": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.25.2",
|
|
33
|
+
"@opencode-ai/plugin": "npm:@opencode-ai/plugin@^1.1.53",
|
|
34
|
+
"@opencode-ai/sdk": "npm:@opencode-ai/sdk@^1.1.53",
|
|
35
|
+
"cosmiconfig": "npm:cosmiconfig@9.0.0",
|
|
36
|
+
"zod": "npm:zod@4.3.6"
|
|
37
|
+
},
|
|
38
|
+
"exports": {
|
|
39
|
+
".": "./mod.ts"
|
|
40
|
+
}
|
|
41
|
+
};
|
package/esm/src/config.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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"}
|
package/esm/src/handlers/chat.js
CHANGED
|
@@ -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,
|
|
8
|
-
const
|
|
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 =
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
59
|
-
const truncatedUser = useUserScope
|
|
60
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
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;
|
|
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:
|
|
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,
|
|
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
|
-
|
|
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
|
+
}
|
package/esm/src/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
31
|
+
sdkClient,
|
|
31
32
|
directory: input.directory,
|
|
32
33
|
groupIdPrefix: config.groupIdPrefix,
|
|
33
34
|
}),
|
|
34
35
|
"chat.message": createChatHandler({
|
|
35
36
|
sessionManager,
|
|
36
|
-
|
|
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":"
|
|
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"}
|