opencode-graphiti 0.1.6 → 0.1.8

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 (36) hide show
  1. package/README.md +39 -18
  2. package/esm/src/handlers/chat.d.ts.map +1 -1
  3. package/esm/src/handlers/chat.js +55 -21
  4. package/esm/src/handlers/event.d.ts.map +1 -1
  5. package/esm/src/handlers/event.js +4 -1
  6. package/esm/src/handlers/messages.d.ts +11 -0
  7. package/esm/src/handlers/messages.d.ts.map +1 -0
  8. package/esm/src/handlers/messages.js +75 -0
  9. package/esm/src/index.js +2 -2
  10. package/esm/src/services/context.d.ts +4 -0
  11. package/esm/src/services/context.d.ts.map +1 -1
  12. package/esm/src/services/context.js +15 -0
  13. package/esm/src/session.d.ts +6 -2
  14. package/esm/src/session.d.ts.map +1 -1
  15. package/esm/src/session.js +3 -1
  16. package/package.json +3 -2
  17. package/script/src/handlers/chat.d.ts.map +1 -1
  18. package/script/src/handlers/chat.js +55 -21
  19. package/script/src/handlers/event.d.ts.map +1 -1
  20. package/script/src/handlers/event.js +4 -1
  21. package/script/src/handlers/messages.d.ts +11 -0
  22. package/script/src/handlers/messages.d.ts.map +1 -0
  23. package/script/src/handlers/messages.js +78 -0
  24. package/script/src/index.js +2 -2
  25. package/script/src/services/context.d.ts +4 -0
  26. package/script/src/services/context.d.ts.map +1 -1
  27. package/script/src/services/context.js +16 -0
  28. package/script/src/session.d.ts +6 -2
  29. package/script/src/session.d.ts.map +1 -1
  30. package/script/src/session.js +3 -1
  31. package/esm/src/handlers/system.d.ts +0 -11
  32. package/esm/src/handlers/system.d.ts.map +0 -1
  33. package/esm/src/handlers/system.js +0 -18
  34. package/script/src/handlers/system.d.ts +0 -11
  35. package/script/src/handlers/system.d.ts.map +0 -1
  36. package/script/src/handlers/system.js +0 -21
package/README.md CHANGED
@@ -22,12 +22,17 @@ reminded of recent project context — regardless of what survived the summary.
22
22
 
23
23
  This plugin connects to a Graphiti MCP server and:
24
24
 
25
- - Injects relevant memories into the first user message of each session
26
- - Periodically re-injects project memories as the conversation progresses
25
+ - Searches Graphiti for relevant facts and entities on each user message
26
+ - Injects memories into the last user message as a `<memory>` block via
27
+ `experimental.chat.messages.transform`, keeping the system prompt static for
28
+ prefix caching
29
+ - Detects context drift using Jaccard similarity and re-injects when the
30
+ conversation topic shifts
27
31
  - Buffers user and assistant messages, flushing them to Graphiti on idle or
28
32
  before compaction
29
33
  - Preserves key facts during context compaction
30
34
  - Saves compaction summaries as episodes so knowledge survives across boundaries
35
+ - Annotates stale facts and filters expired ones automatically
31
36
  - Scopes memories per project (and per user) using directory-based group IDs
32
37
 
33
38
  ## Prerequisites
@@ -104,8 +109,12 @@ Create a config file at `~/.config/opencode/graphiti.jsonc`:
104
109
  // Prefix for project group IDs (e.g. "opencode-my-project")
105
110
  "groupIdPrefix": "opencode",
106
111
 
107
- // Number of user messages between memory re-injections (0 = disabled)
108
- "injectionInterval": 10
112
+ // Jaccard similarity threshold (0–1) below which memory is re-injected
113
+ // Lower values mean the topic must drift further before re-injection
114
+ "driftThreshold": 0.5,
115
+
116
+ // Number of days after which facts are annotated as stale
117
+ "factStaleDays": 30
109
118
  }
110
119
  ```
111
120
 
@@ -114,27 +123,39 @@ values.
114
123
 
115
124
  ## How It Works
116
125
 
117
- ### Memory Injection (`chat.message`)
126
+ ### Memory Search and Caching (`chat.message`)
118
127
 
119
- On the first user message in a session, the plugin searches Graphiti for facts
120
- and entities relevant to the message content. Results are split into project and
121
- user scopes (70% / 30% budget split), formatted in XML-style `<memory>` blocks
122
- with explicit de-emphasis instructions, and injected into the conversation.
128
+ On each user message the plugin searches Graphiti for facts and entities
129
+ relevant to the message content. Results are split into project and user scopes
130
+ (70% / 30% budget), deduplicated, filtered for validity, annotated with
131
+ staleness if older than `factStaleDays`, and formatted as Markdown. The
132
+ formatted context is cached on the session state for the messages transform hook
133
+ to pick up.
123
134
 
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.
135
+ On the very first message of a session, the plugin also loads the most recent
136
+ session snapshot episode to prime the conversation with prior context.
127
137
 
128
138
  The injection budget is calculated dynamically: 5% of the model's context limit
129
139
  (resolved from the provider list) multiplied by 4 characters per token.
130
140
 
131
- ### Re-injection (`chat.message`)
141
+ ### User Message Injection (`experimental.chat.messages.transform`)
142
+
143
+ A separate hook reads the cached memory context and prepends it to the last user
144
+ message as a `<memory data-uuids="...">` block. The `data-uuids` attribute lists
145
+ the fact UUIDs included in the injection, which are tracked in
146
+ `visibleFactUuids` so subsequent searches can filter out already-visible facts.
147
+ This approach keeps the system prompt static, enabling provider-side prefix
148
+ caching, and avoids influencing session titles. The cache is cleared after
149
+ injection so stale context is not re-injected on subsequent LLM calls within the
150
+ same turn.
151
+
152
+ ### Drift-Based Re-injection (`chat.message`)
132
153
 
133
- When `injectionInterval` is greater than 0, the plugin periodically re-injects
134
- project-scoped memories as the conversation progresses. After every N user
135
- messages (where N is `injectionInterval`), stale synthetic memory parts are
136
- replaced with fresh results. Re-injection uses the full character budget for
137
- project memories only (no user scope).
154
+ After the first injection, the plugin monitors for context drift on every user
155
+ message. It searches Graphiti for the current message and compares the returned
156
+ fact UUIDs against the previously injected set using Jaccard similarity. When
157
+ similarity drops below `driftThreshold` (default 0.5), the memory cache is
158
+ refreshed with project-scoped results only (no user scope).
138
159
 
139
160
  ### Message Buffering (`event`)
140
161
 
@@ -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;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
+ {"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,IAkLvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBA6EzE"}
@@ -46,6 +46,28 @@ export function createChatHandler(deps) {
46
46
  facts: userFacts,
47
47
  nodes: userNodes,
48
48
  });
49
+ const visibleSet = new Set(state.visibleFactUuids ?? []);
50
+ const beforeProjectFacts = projectContext.facts.length;
51
+ const beforeUserFacts = userContext.facts.length;
52
+ projectContext.facts = projectContext.facts.filter((fact) => !visibleSet.has(fact.uuid));
53
+ userContext.facts = userContext.facts.filter((fact) => !visibleSet.has(fact.uuid));
54
+ logger.debug("Filtered visible facts from injection", {
55
+ visibleCount: visibleSet.size,
56
+ filteredProjectFacts: beforeProjectFacts - projectContext.facts.length,
57
+ filteredUserFacts: beforeUserFacts - userContext.facts.length,
58
+ remainingProjectFacts: projectContext.facts.length,
59
+ remainingUserFacts: userContext.facts.length,
60
+ });
61
+ if (projectContext.facts.length === 0 &&
62
+ userContext.facts.length === 0 &&
63
+ projectContext.nodes.length === 0 &&
64
+ userContext.nodes.length === 0) {
65
+ logger.debug("All facts filtered; skipping context cache", {
66
+ groupId: state.groupId,
67
+ userGroupId: state.userGroupId,
68
+ });
69
+ return;
70
+ }
49
71
  const projectContextString = formatMemoryContext(projectContext.facts, projectContext.nodes, { factStaleDays });
50
72
  const userContextString = formatMemoryContext(userContext.facts, userContext.nodes, { factStaleDays });
51
73
  if (!projectContextString && !userContextString)
@@ -96,24 +118,27 @@ export function createChatHandler(deps) {
96
118
  .slice(0, characterBudget);
97
119
  if (!memoryContext)
98
120
  return;
99
- const factUuids = seedFactUuids ??
100
- new Set([
101
- ...projectContext.facts.map((fact) => fact.uuid),
102
- ...userContext.facts.map((fact) => fact.uuid),
103
- ]);
121
+ const allFactUuids = [
122
+ ...projectContext.facts.map((fact) => fact.uuid),
123
+ ...userContext.facts.map((fact) => fact.uuid),
124
+ ];
125
+ const factUuids = seedFactUuids ?? Array.from(new Set(allFactUuids));
104
126
  state.cachedMemoryContext = memoryContext;
105
- logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for system prompt injection`);
127
+ state.cachedFactUuids = factUuids;
128
+ logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for user message injection`);
106
129
  state.lastInjectionFactUuids = factUuids;
107
130
  };
108
131
  const computeJaccardSimilarity = (left, right) => {
109
- if (left.size === 0 && right.size === 0)
132
+ if (left.length === 0 && right.length === 0)
110
133
  return 1;
134
+ const leftSet = new Set(left);
135
+ const rightSet = new Set(right);
111
136
  let intersection = 0;
112
- for (const value of left) {
113
- if (right.has(value))
137
+ for (const value of leftSet) {
138
+ if (rightSet.has(value))
114
139
  intersection += 1;
115
140
  }
116
- const union = left.size + right.size - intersection;
141
+ const union = leftSet.size + rightSet.size - intersection;
117
142
  return union === 0 ? 1 : intersection / union;
118
143
  };
119
144
  return async ({ sessionID }, output) => {
@@ -144,18 +169,27 @@ export function createChatHandler(deps) {
144
169
  let shouldReinject = false;
145
170
  let currentFactUuids = null;
146
171
  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", {
172
+ try {
173
+ const driftFacts = await client.searchFacts({
174
+ query: messageText,
175
+ groupIds: [state.groupId],
176
+ maxFacts: 20,
177
+ });
178
+ currentFactUuids = driftFacts.map((fact) => fact.uuid);
179
+ const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
180
+ shouldReinject = similarity < driftThreshold;
181
+ if (!shouldReinject) {
182
+ logger.debug("Skipping reinjection; similarity above threshold", {
183
+ sessionID,
184
+ similarity,
185
+ });
186
+ return;
187
+ }
188
+ }
189
+ catch (err) {
190
+ logger.error("Failed to check topic drift, skipping reinjection", {
157
191
  sessionID,
158
- similarity,
192
+ err,
159
193
  });
160
194
  return;
161
195
  }
@@ -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,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"}
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,mBAqLpC"}
@@ -59,7 +59,10 @@ export function createEventHandler(deps) {
59
59
  groupId: defaultGroupId,
60
60
  userGroupId: defaultUserGroupId,
61
61
  injectedMemories: false,
62
- lastInjectionFactUuids: new Set(),
62
+ lastInjectionFactUuids: [],
63
+ cachedMemoryContext: undefined,
64
+ cachedFactUuids: undefined,
65
+ visibleFactUuids: [],
63
66
  messageCount: 0,
64
67
  pendingMessages: [],
65
68
  contextLimit: 200_000,
@@ -0,0 +1,11 @@
1
+ import type { Hooks } from "@opencode-ai/plugin";
2
+ import type { SessionManager } from "../session.js";
3
+ type MessagesTransformHook = NonNullable<Hooks["experimental.chat.messages.transform"]>;
4
+ type MessagesTransformInput = Parameters<MessagesTransformHook>[0];
5
+ type MessagesTransformOutput = Parameters<MessagesTransformHook>[1];
6
+ export interface MessagesHandlerDeps {
7
+ sessionManager: SessionManager;
8
+ }
9
+ export declare function createMessagesHandler(deps: MessagesHandlerDeps): (_input: MessagesTransformInput, output: MessagesTransformOutput) => Promise<void>;
10
+ export {};
11
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAGjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,qBAAqB,GAAG,WAAW,CACtC,KAAK,CAAC,sCAAsC,CAAC,CAC9C,CAAC;AACF,KAAK,sBAAsB,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,KAAK,uBAAuB,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,IAK3D,QAAQ,sBAAsB,EAC9B,QAAQ,uBAAuB,mBAkFlC"}
@@ -0,0 +1,75 @@
1
+ import { extractVisibleUuids } from "../services/context.js";
2
+ import { logger } from "../services/logger.js";
3
+ export function createMessagesHandler(deps) {
4
+ const { sessionManager } = deps;
5
+ // deno-lint-ignore require-await
6
+ return async (_input, output) => {
7
+ const lastUserEntry = [...output.messages]
8
+ .reverse()
9
+ .find((message) => message.info.role === "user");
10
+ if (!lastUserEntry)
11
+ return;
12
+ const sessionID = lastUserEntry.info.sessionID;
13
+ const state = sessionManager.getState(sessionID);
14
+ if (!state?.isMain) {
15
+ logger.debug("Skipping memory injection; not main session", {
16
+ sessionID,
17
+ });
18
+ return;
19
+ }
20
+ const allVisibleUuids = [];
21
+ for (const entry of output.messages) {
22
+ for (const part of entry.parts) {
23
+ if (part.type === "text" && "text" in part) {
24
+ const uuids = extractVisibleUuids(part.text);
25
+ if (uuids.length > 0) {
26
+ logger.debug("Found <memory> block UUIDs", {
27
+ sessionID,
28
+ uuids,
29
+ messageID: entry.info.id,
30
+ });
31
+ }
32
+ allVisibleUuids.push(...uuids);
33
+ }
34
+ }
35
+ }
36
+ state.visibleFactUuids = [...new Set(allVisibleUuids)];
37
+ logger.debug("Updated visibleFactUuids from message scan", {
38
+ sessionID,
39
+ visibleCount: state.visibleFactUuids.length,
40
+ });
41
+ if (!state.cachedMemoryContext) {
42
+ logger.debug("Skipping memory injection; no cached context", {
43
+ sessionID,
44
+ });
45
+ return;
46
+ }
47
+ const textPart = lastUserEntry.parts.find((part) => part.type === "text" && "text" in part);
48
+ if (!textPart) {
49
+ logger.debug("Skipping memory injection; no text part", {
50
+ sessionID,
51
+ });
52
+ return;
53
+ }
54
+ if (textPart.text.includes("<memory")) {
55
+ logger.debug("Skipping memory injection; already injected", {
56
+ sessionID,
57
+ });
58
+ state.cachedMemoryContext = undefined;
59
+ state.cachedFactUuids = undefined;
60
+ return;
61
+ }
62
+ const uuids = state.cachedFactUuids ?? [];
63
+ const uuidAttr = uuids.length > 0 ? ` data-uuids="${uuids.join(",")}"` : "";
64
+ const memoryBlock = `<memory${uuidAttr}>\n${state.cachedMemoryContext}\n</memory>`;
65
+ textPart.text = `${memoryBlock}\n\n${textPart.text}`;
66
+ logger.info("Injected memory context into last user message", {
67
+ sessionID,
68
+ factCount: uuids.length,
69
+ blockLength: memoryBlock.length,
70
+ preview: state.cachedMemoryContext.slice(0, 100),
71
+ });
72
+ state.cachedMemoryContext = undefined;
73
+ state.cachedFactUuids = undefined;
74
+ };
75
+ }
package/esm/src/index.js CHANGED
@@ -2,7 +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
+ import { createMessagesHandler } from "./handlers/messages.js";
6
6
  import { GraphitiClient } from "./services/client.js";
7
7
  import { logger } from "./services/logger.js";
8
8
  import { SessionManager } from "./session.js";
@@ -44,7 +44,7 @@ export const graphiti = async (input) => {
44
44
  defaultGroupId,
45
45
  factStaleDays: config.factStaleDays,
46
46
  }),
47
- "experimental.chat.system.transform": createSystemHandler({
47
+ "experimental.chat.messages.transform": createMessagesHandler({
48
48
  sessionManager,
49
49
  }),
50
50
  };
@@ -30,4 +30,8 @@ export declare function formatMemoryContext(facts: GraphitiFact[], nodes: Graphi
30
30
  factStaleDays?: number;
31
31
  now?: Date;
32
32
  }): string;
33
+ /**
34
+ * Extract fact UUIDs from all <memory data-uuids="..."> blocks in a text string.
35
+ */
36
+ export declare function extractVisibleUuids(text: string): string[];
33
37
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/src/services/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,IAAI,GAAG,IAKjD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,YAAY,EAAE,KAAK,IAAI,KAAG,OAQ7D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,MAAM,YAAY,EAClB,KAAK,IAAI,EACT,eAAe,MAAM,KACpB,YAUF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,YAAY,EAAE,KAAG,YAAY,EAWtE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,YAAY,EAMd,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,YAAY,KAAG,MAMnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,EAAE,KAAG,MAAM,EAK1D,CAAC;AAEL,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,OAAO,YAAY,EAAE,EACrB,OAAO,YAAY,EAAE,KACpB,YAAY,EAOd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,QAAQ;IACzC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,KAAG;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAQjD,CAAC;AAEF;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,CAAC,EAAE;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,MAAM,CA6BR"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/src/services/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,IAAI,GAAG,IAKjD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,YAAY,EAAE,KAAK,IAAI,KAAG,OAQ7D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,MAAM,YAAY,EAClB,KAAK,IAAI,EACT,eAAe,MAAM,KACpB,YAUF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,YAAY,EAAE,KAAG,YAAY,EAWtE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,YAAY,EAMd,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,YAAY,KAAG,MAMnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,EAAE,KAAG,MAAM,EAK1D,CAAC;AAEL,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,OAAO,YAAY,EAAE,EACrB,OAAO,YAAY,EAAE,KACpB,YAAY,EAOd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,QAAQ;IACzC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,KAAG;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAQjD,CAAC;AAEF;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,CAAC,EAAE;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,MAAM,CA6BR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAW1D"}
@@ -129,3 +129,18 @@ export function formatMemoryContext(facts, nodes, options) {
129
129
  }
130
130
  return sections.join("\n");
131
131
  }
132
+ /**
133
+ * Extract fact UUIDs from all <memory data-uuids="..."> blocks in a text string.
134
+ */
135
+ export function extractVisibleUuids(text) {
136
+ const uuids = [];
137
+ const regex = /<memory[^>]*\bdata-uuids="([^"]*)"[^>]*>/g;
138
+ let match;
139
+ while ((match = regex.exec(text)) !== null) {
140
+ const raw = match[1];
141
+ if (raw) {
142
+ uuids.push(...raw.split(",").filter(Boolean));
143
+ }
144
+ }
145
+ return uuids;
146
+ }
@@ -11,9 +11,13 @@ export type SessionState = {
11
11
  /** Whether memories have been injected into this session yet. */
12
12
  injectedMemories: boolean;
13
13
  /** Fact UUIDs included in the last memory injection. */
14
- lastInjectionFactUuids: Set<string>;
15
- /** Cached formatted memory context for system prompt injection. */
14
+ lastInjectionFactUuids: string[];
15
+ /** Cached formatted memory context for user message injection. */
16
16
  cachedMemoryContext?: string;
17
+ /** Fact UUIDs from cached context, for embedding in <memory> tag. */
18
+ cachedFactUuids?: string[];
19
+ /** Fact UUIDs currently visible in <memory> blocks across all messages. */
20
+ visibleFactUuids: string[];
17
21
  /** Count of messages observed in this session. */
18
22
  messageCount: number;
19
23
  /** Buffered message strings awaiting flush. */
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,wDAAwD;IACxD,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,qBAAa,cAAc;IAUvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAZjC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,wBAAwB,CAG5B;IACJ,OAAO,CAAC,2BAA2B,CAAqB;gBAGrC,cAAc,EAAE,MAAM,EACtB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,cAAc,EACzB,cAAc,EAAE,cAAc;IAGjD,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIrD,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAItD,gDAAgD;IAChD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7D,qDAAqD;IAC/C,eAAe,CACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAsBrC,yDAAyD;IACnD,mBAAmB,CACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA0B7D,yDAAyD;IACnD,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5D,6DAA6D;IAC7D,mBAAmB,CACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,IAAI;IAKP,gEAAgE;IAChE,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAKlE;;OAEG;IACH,wBAAwB,CACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI;IAwCP,+EAA+E;IACzE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgFhB,iDAAiD;IACjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKlE,uCAAuC;IACvC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YAexB,2BAA2B;CAiC1C"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/src/session.ts"],"names":[],"mappings":"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"}
@@ -106,8 +106,10 @@ export class SessionManager {
106
106
  groupId: this.defaultGroupId,
107
107
  userGroupId: this.defaultUserGroupId,
108
108
  injectedMemories: false,
109
- lastInjectionFactUuids: new Set(),
109
+ lastInjectionFactUuids: [],
110
110
  cachedMemoryContext: undefined,
111
+ cachedFactUuids: undefined,
112
+ visibleFactUuids: [],
111
113
  messageCount: 0,
112
114
  pendingMessages: [],
113
115
  contextLimit: 200_000,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-graphiti",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "OpenCode plugin for persistent memory via Graphiti knowledge graph",
5
5
  "keywords": [
6
6
  "opencode",
@@ -40,7 +40,8 @@
40
40
  "hooks": [
41
41
  "chat.message",
42
42
  "event",
43
- "experimental.session.compacting"
43
+ "experimental.session.compacting",
44
+ "experimental.chat.messages.transform"
44
45
  ]
45
46
  },
46
47
  "dependencies": {
@@ -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;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
+ {"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,IAkLvC,eAAe,gBAAgB,EAAE,QAAQ,iBAAiB,mBA6EzE"}
@@ -49,6 +49,28 @@ function createChatHandler(deps) {
49
49
  facts: userFacts,
50
50
  nodes: userNodes,
51
51
  });
52
+ const visibleSet = new Set(state.visibleFactUuids ?? []);
53
+ const beforeProjectFacts = projectContext.facts.length;
54
+ const beforeUserFacts = userContext.facts.length;
55
+ projectContext.facts = projectContext.facts.filter((fact) => !visibleSet.has(fact.uuid));
56
+ userContext.facts = userContext.facts.filter((fact) => !visibleSet.has(fact.uuid));
57
+ logger_js_1.logger.debug("Filtered visible facts from injection", {
58
+ visibleCount: visibleSet.size,
59
+ filteredProjectFacts: beforeProjectFacts - projectContext.facts.length,
60
+ filteredUserFacts: beforeUserFacts - userContext.facts.length,
61
+ remainingProjectFacts: projectContext.facts.length,
62
+ remainingUserFacts: userContext.facts.length,
63
+ });
64
+ if (projectContext.facts.length === 0 &&
65
+ userContext.facts.length === 0 &&
66
+ projectContext.nodes.length === 0 &&
67
+ userContext.nodes.length === 0) {
68
+ logger_js_1.logger.debug("All facts filtered; skipping context cache", {
69
+ groupId: state.groupId,
70
+ userGroupId: state.userGroupId,
71
+ });
72
+ return;
73
+ }
52
74
  const projectContextString = (0, context_js_1.formatMemoryContext)(projectContext.facts, projectContext.nodes, { factStaleDays });
53
75
  const userContextString = (0, context_js_1.formatMemoryContext)(userContext.facts, userContext.nodes, { factStaleDays });
54
76
  if (!projectContextString && !userContextString)
@@ -99,24 +121,27 @@ function createChatHandler(deps) {
99
121
  .slice(0, characterBudget);
100
122
  if (!memoryContext)
101
123
  return;
102
- const factUuids = seedFactUuids ??
103
- new Set([
104
- ...projectContext.facts.map((fact) => fact.uuid),
105
- ...userContext.facts.map((fact) => fact.uuid),
106
- ]);
124
+ const allFactUuids = [
125
+ ...projectContext.facts.map((fact) => fact.uuid),
126
+ ...userContext.facts.map((fact) => fact.uuid),
127
+ ];
128
+ const factUuids = seedFactUuids ?? Array.from(new Set(allFactUuids));
107
129
  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`);
130
+ state.cachedFactUuids = factUuids;
131
+ logger_js_1.logger.info(`Cached ${projectFacts.length + userFacts.length} facts and ${projectNodes.length + userNodes.length} nodes for user message injection`);
109
132
  state.lastInjectionFactUuids = factUuids;
110
133
  };
111
134
  const computeJaccardSimilarity = (left, right) => {
112
- if (left.size === 0 && right.size === 0)
135
+ if (left.length === 0 && right.length === 0)
113
136
  return 1;
137
+ const leftSet = new Set(left);
138
+ const rightSet = new Set(right);
114
139
  let intersection = 0;
115
- for (const value of left) {
116
- if (right.has(value))
140
+ for (const value of leftSet) {
141
+ if (rightSet.has(value))
117
142
  intersection += 1;
118
143
  }
119
- const union = left.size + right.size - intersection;
144
+ const union = leftSet.size + rightSet.size - intersection;
120
145
  return union === 0 ? 1 : intersection / union;
121
146
  };
122
147
  return async ({ sessionID }, output) => {
@@ -147,18 +172,27 @@ function createChatHandler(deps) {
147
172
  let shouldReinject = false;
148
173
  let currentFactUuids = null;
149
174
  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", {
175
+ try {
176
+ const driftFacts = await client.searchFacts({
177
+ query: messageText,
178
+ groupIds: [state.groupId],
179
+ maxFacts: 20,
180
+ });
181
+ currentFactUuids = driftFacts.map((fact) => fact.uuid);
182
+ const similarity = computeJaccardSimilarity(currentFactUuids, state.lastInjectionFactUuids);
183
+ shouldReinject = similarity < driftThreshold;
184
+ if (!shouldReinject) {
185
+ logger_js_1.logger.debug("Skipping reinjection; similarity above threshold", {
186
+ sessionID,
187
+ similarity,
188
+ });
189
+ return;
190
+ }
191
+ }
192
+ catch (err) {
193
+ logger_js_1.logger.error("Failed to check topic drift, skipping reinjection", {
160
194
  sessionID,
161
- similarity,
195
+ err,
162
196
  });
163
197
  return;
164
198
  }
@@ -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,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"}
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,mBAqLpC"}
@@ -62,7 +62,10 @@ function createEventHandler(deps) {
62
62
  groupId: defaultGroupId,
63
63
  userGroupId: defaultUserGroupId,
64
64
  injectedMemories: false,
65
- lastInjectionFactUuids: new Set(),
65
+ lastInjectionFactUuids: [],
66
+ cachedMemoryContext: undefined,
67
+ cachedFactUuids: undefined,
68
+ visibleFactUuids: [],
66
69
  messageCount: 0,
67
70
  pendingMessages: [],
68
71
  contextLimit: 200_000,
@@ -0,0 +1,11 @@
1
+ import type { Hooks } from "@opencode-ai/plugin";
2
+ import type { SessionManager } from "../session.js";
3
+ type MessagesTransformHook = NonNullable<Hooks["experimental.chat.messages.transform"]>;
4
+ type MessagesTransformInput = Parameters<MessagesTransformHook>[0];
5
+ type MessagesTransformOutput = Parameters<MessagesTransformHook>[1];
6
+ export interface MessagesHandlerDeps {
7
+ sessionManager: SessionManager;
8
+ }
9
+ export declare function createMessagesHandler(deps: MessagesHandlerDeps): (_input: MessagesTransformInput, output: MessagesTransformOutput) => Promise<void>;
10
+ export {};
11
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../../../src/src/handlers/messages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAGjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,KAAK,qBAAqB,GAAG,WAAW,CACtC,KAAK,CAAC,sCAAsC,CAAC,CAC9C,CAAC;AACF,KAAK,sBAAsB,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AACnE,KAAK,uBAAuB,GAAG,UAAU,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,mBAAmB;IAClC,cAAc,EAAE,cAAc,CAAC;CAChC;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,mBAAmB,IAK3D,QAAQ,sBAAsB,EAC9B,QAAQ,uBAAuB,mBAkFlC"}
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createMessagesHandler = createMessagesHandler;
4
+ const context_js_1 = require("../services/context.js");
5
+ const logger_js_1 = require("../services/logger.js");
6
+ function createMessagesHandler(deps) {
7
+ const { sessionManager } = deps;
8
+ // deno-lint-ignore require-await
9
+ return async (_input, output) => {
10
+ const lastUserEntry = [...output.messages]
11
+ .reverse()
12
+ .find((message) => message.info.role === "user");
13
+ if (!lastUserEntry)
14
+ return;
15
+ const sessionID = lastUserEntry.info.sessionID;
16
+ const state = sessionManager.getState(sessionID);
17
+ if (!state?.isMain) {
18
+ logger_js_1.logger.debug("Skipping memory injection; not main session", {
19
+ sessionID,
20
+ });
21
+ return;
22
+ }
23
+ const allVisibleUuids = [];
24
+ for (const entry of output.messages) {
25
+ for (const part of entry.parts) {
26
+ if (part.type === "text" && "text" in part) {
27
+ const uuids = (0, context_js_1.extractVisibleUuids)(part.text);
28
+ if (uuids.length > 0) {
29
+ logger_js_1.logger.debug("Found <memory> block UUIDs", {
30
+ sessionID,
31
+ uuids,
32
+ messageID: entry.info.id,
33
+ });
34
+ }
35
+ allVisibleUuids.push(...uuids);
36
+ }
37
+ }
38
+ }
39
+ state.visibleFactUuids = [...new Set(allVisibleUuids)];
40
+ logger_js_1.logger.debug("Updated visibleFactUuids from message scan", {
41
+ sessionID,
42
+ visibleCount: state.visibleFactUuids.length,
43
+ });
44
+ if (!state.cachedMemoryContext) {
45
+ logger_js_1.logger.debug("Skipping memory injection; no cached context", {
46
+ sessionID,
47
+ });
48
+ return;
49
+ }
50
+ const textPart = lastUserEntry.parts.find((part) => part.type === "text" && "text" in part);
51
+ if (!textPart) {
52
+ logger_js_1.logger.debug("Skipping memory injection; no text part", {
53
+ sessionID,
54
+ });
55
+ return;
56
+ }
57
+ if (textPart.text.includes("<memory")) {
58
+ logger_js_1.logger.debug("Skipping memory injection; already injected", {
59
+ sessionID,
60
+ });
61
+ state.cachedMemoryContext = undefined;
62
+ state.cachedFactUuids = undefined;
63
+ return;
64
+ }
65
+ const uuids = state.cachedFactUuids ?? [];
66
+ const uuidAttr = uuids.length > 0 ? ` data-uuids="${uuids.join(",")}"` : "";
67
+ const memoryBlock = `<memory${uuidAttr}>\n${state.cachedMemoryContext}\n</memory>`;
68
+ textPart.text = `${memoryBlock}\n\n${textPart.text}`;
69
+ logger_js_1.logger.info("Injected memory context into last user message", {
70
+ sessionID,
71
+ factCount: uuids.length,
72
+ blockLength: memoryBlock.length,
73
+ preview: state.cachedMemoryContext.slice(0, 100),
74
+ });
75
+ state.cachedMemoryContext = undefined;
76
+ state.cachedFactUuids = undefined;
77
+ };
78
+ }
@@ -5,7 +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
+ const messages_js_1 = require("./handlers/messages.js");
9
9
  const client_js_1 = require("./services/client.js");
10
10
  const logger_js_1 = require("./services/logger.js");
11
11
  const session_js_1 = require("./session.js");
@@ -47,7 +47,7 @@ const graphiti = async (input) => {
47
47
  defaultGroupId,
48
48
  factStaleDays: config.factStaleDays,
49
49
  }),
50
- "experimental.chat.system.transform": (0, system_js_1.createSystemHandler)({
50
+ "experimental.chat.messages.transform": (0, messages_js_1.createMessagesHandler)({
51
51
  sessionManager,
52
52
  }),
53
53
  };
@@ -30,4 +30,8 @@ export declare function formatMemoryContext(facts: GraphitiFact[], nodes: Graphi
30
30
  factStaleDays?: number;
31
31
  now?: Date;
32
32
  }): string;
33
+ /**
34
+ * Extract fact UUIDs from all <memory data-uuids="..."> blocks in a text string.
35
+ */
36
+ export declare function extractVisibleUuids(text: string): string[];
33
37
  //# sourceMappingURL=context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/src/services/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,IAAI,GAAG,IAKjD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,YAAY,EAAE,KAAK,IAAI,KAAG,OAQ7D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,MAAM,YAAY,EAClB,KAAK,IAAI,EACT,eAAe,MAAM,KACpB,YAUF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,YAAY,EAAE,KAAG,YAAY,EAWtE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,YAAY,EAMd,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,YAAY,KAAG,MAMnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,EAAE,KAAG,MAAM,EAK1D,CAAC;AAEL,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,OAAO,YAAY,EAAE,EACrB,OAAO,YAAY,EAAE,KACpB,YAAY,EAOd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,QAAQ;IACzC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,KAAG;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAQjD,CAAC;AAEF;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,CAAC,EAAE;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,MAAM,CA6BR"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/src/services/context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAIpE,eAAO,MAAM,SAAS,GAAI,QAAQ,MAAM,KAAG,IAAI,GAAG,IAKjD,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,MAAM,YAAY,EAAE,KAAK,IAAI,KAAG,OAQ7D,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,MAAM,YAAY,EAClB,KAAK,IAAI,EACT,eAAe,MAAM,KACpB,YAUF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,OAAO,YAAY,EAAE,KAAG,YAAY,EAWtE,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,YAAY,EAMd,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,MAAM,YAAY,KAAG,MAMnD,CAAC;AAEF,eAAO,MAAM,eAAe,GAC1B,OAAO,YAAY,EAAE,EACrB,UAAU;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,KACA,MAAM,EAGR,CAAC;AAEF,eAAO,MAAM,eAAe,GAAI,OAAO,YAAY,EAAE,KAAG,MAAM,EAK1D,CAAC;AAEL,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,sBAAsB,GACjC,OAAO,YAAY,EAAE,KACpB,YAAY,EASd,CAAC;AAEF,eAAO,MAAM,4BAA4B,GACvC,OAAO,YAAY,EAAE,EACrB,OAAO,YAAY,EAAE,KACpB,YAAY,EAOd,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,QAAQ;IACzC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB,KAAG;IAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAAC,KAAK,EAAE,YAAY,EAAE,CAAA;CAQjD,CAAC;AAEF;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,YAAY,EAAE,EACrB,KAAK,EAAE,YAAY,EAAE,EACrB,OAAO,CAAC,EAAE;IACR,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GACA,MAAM,CA6BR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAW1D"}
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.deduplicateContext = exports.removeNodesReferencedByFacts = exports.deduplicateNodesByUuid = exports.deduplicateFactsByUuid = exports.formatNodeLines = exports.formatFactLines = exports.formatFactLine = exports.filterAndAnnotateFacts = exports.sortFactsByRecency = exports.annotateStaleFact = exports.isFactInvalid = exports.parseDate = void 0;
4
4
  exports.formatMemoryContext = formatMemoryContext;
5
+ exports.extractVisibleUuids = extractVisibleUuids;
5
6
  const DAY_MS = 24 * 60 * 60 * 1000;
6
7
  const parseDate = (value) => {
7
8
  if (!value)
@@ -145,3 +146,18 @@ function formatMemoryContext(facts, nodes, options) {
145
146
  }
146
147
  return sections.join("\n");
147
148
  }
149
+ /**
150
+ * Extract fact UUIDs from all <memory data-uuids="..."> blocks in a text string.
151
+ */
152
+ function extractVisibleUuids(text) {
153
+ const uuids = [];
154
+ const regex = /<memory[^>]*\bdata-uuids="([^"]*)"[^>]*>/g;
155
+ let match;
156
+ while ((match = regex.exec(text)) !== null) {
157
+ const raw = match[1];
158
+ if (raw) {
159
+ uuids.push(...raw.split(",").filter(Boolean));
160
+ }
161
+ }
162
+ return uuids;
163
+ }
@@ -11,9 +11,13 @@ export type SessionState = {
11
11
  /** Whether memories have been injected into this session yet. */
12
12
  injectedMemories: boolean;
13
13
  /** Fact UUIDs included in the last memory injection. */
14
- lastInjectionFactUuids: Set<string>;
15
- /** Cached formatted memory context for system prompt injection. */
14
+ lastInjectionFactUuids: string[];
15
+ /** Cached formatted memory context for user message injection. */
16
16
  cachedMemoryContext?: string;
17
+ /** Fact UUIDs from cached context, for embedding in <memory> tag. */
18
+ cachedFactUuids?: string[];
19
+ /** Fact UUIDs currently visible in <memory> blocks across all messages. */
20
+ visibleFactUuids: string[];
17
21
  /** Count of messages observed in this session. */
18
22
  messageCount: number;
19
23
  /** Buffered message strings awaiting flush. */
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/src/session.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAGf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,gBAAgB,EAAE,OAAO,CAAC;IAC1B,wDAAwD;IACxD,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,mEAAmE;IACnE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kDAAkD;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,+CAA+C;IAC/C,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,sCAAsC;IACtC,YAAY,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,MAAM,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,qBAAa,cAAc;IAUvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,kBAAkB;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,cAAc;IAZjC,OAAO,CAAC,QAAQ,CAAmC;IACnD,OAAO,CAAC,aAAa,CAAoC;IACzD,OAAO,CAAC,wBAAwB,CAG5B;IACJ,OAAO,CAAC,2BAA2B,CAAqB;gBAGrC,cAAc,EAAE,MAAM,EACtB,kBAAkB,EAAE,MAAM,EAC1B,SAAS,EAAE,cAAc,EACzB,cAAc,EAAE,cAAc;IAGjD,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;IAIrD,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI;IAItD,gDAAgD;IAChD,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAI7D,qDAAqD;IAC/C,eAAe,CACnB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAsBrC,yDAAyD;IACnD,mBAAmB,CACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;QAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA0B7D,yDAAyD;IACnD,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAK5D,6DAA6D;IAC7D,mBAAmB,CACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,IAAI;IAKP,gEAAgE;IAChE,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO;IAKlE;;OAEG;IACH,wBAAwB,CACtB,KAAK,EAAE,YAAY,EACnB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,IAAI;IAwCP,+EAA+E;IACzE,oBAAoB,CACxB,SAAS,EAAE,MAAM,EACjB,iBAAiB,EAAE,MAAM,EACzB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAgFhB,iDAAiD;IACjD,sBAAsB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI;IAKlE,uCAAuC;IACvC,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;YAexB,2BAA2B;CAiC1C"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/src/session.ts"],"names":[],"mappings":"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"}
@@ -109,8 +109,10 @@ class SessionManager {
109
109
  groupId: this.defaultGroupId,
110
110
  userGroupId: this.defaultUserGroupId,
111
111
  injectedMemories: false,
112
- lastInjectionFactUuids: new Set(),
112
+ lastInjectionFactUuids: [],
113
113
  cachedMemoryContext: undefined,
114
+ cachedFactUuids: undefined,
115
+ visibleFactUuids: [],
114
116
  messageCount: 0,
115
117
  pendingMessages: [],
116
118
  contextLimit: 200_000,
@@ -1,11 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,18 +0,0 @@
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,11 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,21 +0,0 @@
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
- }