opencode-graphiti 0.0.0-development

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +358 -0
  3. package/esm/_dnt.polyfills.d.ts +166 -0
  4. package/esm/_dnt.polyfills.d.ts.map +1 -0
  5. package/esm/_dnt.polyfills.js +177 -0
  6. package/esm/_dnt.shims.d.ts +6 -0
  7. package/esm/_dnt.shims.d.ts.map +1 -0
  8. package/esm/_dnt.shims.js +61 -0
  9. package/esm/deno.d.ts +45 -0
  10. package/esm/deno.d.ts.map +1 -0
  11. package/esm/deno.js +39 -0
  12. package/esm/mod.d.ts +3 -0
  13. package/esm/mod.d.ts.map +1 -0
  14. package/esm/mod.js +2 -0
  15. package/esm/package.json +3 -0
  16. package/esm/src/config.d.ts +20 -0
  17. package/esm/src/config.d.ts.map +1 -0
  18. package/esm/src/config.js +246 -0
  19. package/esm/src/handlers/chat.d.ts +14 -0
  20. package/esm/src/handlers/chat.d.ts.map +1 -0
  21. package/esm/src/handlers/chat.js +60 -0
  22. package/esm/src/handlers/compacting.d.ts +9 -0
  23. package/esm/src/handlers/compacting.d.ts.map +1 -0
  24. package/esm/src/handlers/compacting.js +30 -0
  25. package/esm/src/handlers/event.d.ts +22 -0
  26. package/esm/src/handlers/event.d.ts.map +1 -0
  27. package/esm/src/handlers/event.js +287 -0
  28. package/esm/src/handlers/messages.d.ts +9 -0
  29. package/esm/src/handlers/messages.d.ts.map +1 -0
  30. package/esm/src/handlers/messages.js +93 -0
  31. package/esm/src/index.d.ts +5 -0
  32. package/esm/src/index.d.ts.map +1 -0
  33. package/esm/src/index.js +153 -0
  34. package/esm/src/services/batch-drain.d.ts +23 -0
  35. package/esm/src/services/batch-drain.d.ts.map +1 -0
  36. package/esm/src/services/batch-drain.js +217 -0
  37. package/esm/src/services/connection-manager.d.ts +104 -0
  38. package/esm/src/services/connection-manager.d.ts.map +1 -0
  39. package/esm/src/services/connection-manager.js +621 -0
  40. package/esm/src/services/constants.d.ts +7 -0
  41. package/esm/src/services/constants.d.ts.map +1 -0
  42. package/esm/src/services/constants.js +6 -0
  43. package/esm/src/services/context-limit.d.ts +3 -0
  44. package/esm/src/services/context-limit.d.ts.map +1 -0
  45. package/esm/src/services/context-limit.js +44 -0
  46. package/esm/src/services/event-extractor.d.ts +29 -0
  47. package/esm/src/services/event-extractor.d.ts.map +1 -0
  48. package/esm/src/services/event-extractor.js +659 -0
  49. package/esm/src/services/graphiti-async.d.ts +22 -0
  50. package/esm/src/services/graphiti-async.d.ts.map +1 -0
  51. package/esm/src/services/graphiti-async.js +219 -0
  52. package/esm/src/services/graphiti-mcp.d.ts +57 -0
  53. package/esm/src/services/graphiti-mcp.d.ts.map +1 -0
  54. package/esm/src/services/graphiti-mcp.js +194 -0
  55. package/esm/src/services/logger.d.ts +9 -0
  56. package/esm/src/services/logger.d.ts.map +1 -0
  57. package/esm/src/services/logger.js +104 -0
  58. package/esm/src/services/opencode-warning.d.ts +8 -0
  59. package/esm/src/services/opencode-warning.d.ts.map +1 -0
  60. package/esm/src/services/opencode-warning.js +104 -0
  61. package/esm/src/services/redis-cache.d.ts +27 -0
  62. package/esm/src/services/redis-cache.d.ts.map +1 -0
  63. package/esm/src/services/redis-cache.js +215 -0
  64. package/esm/src/services/redis-client.d.ts +89 -0
  65. package/esm/src/services/redis-client.d.ts.map +1 -0
  66. package/esm/src/services/redis-client.js +906 -0
  67. package/esm/src/services/redis-events.d.ts +46 -0
  68. package/esm/src/services/redis-events.d.ts.map +1 -0
  69. package/esm/src/services/redis-events.js +517 -0
  70. package/esm/src/services/redis-snapshot.d.ts +16 -0
  71. package/esm/src/services/redis-snapshot.d.ts.map +1 -0
  72. package/esm/src/services/redis-snapshot.js +184 -0
  73. package/esm/src/services/render-utils.d.ts +23 -0
  74. package/esm/src/services/render-utils.d.ts.map +1 -0
  75. package/esm/src/services/render-utils.js +149 -0
  76. package/esm/src/services/runtime-teardown.d.ts +23 -0
  77. package/esm/src/services/runtime-teardown.d.ts.map +1 -0
  78. package/esm/src/services/runtime-teardown.js +119 -0
  79. package/esm/src/services/sdk-normalize.d.ts +55 -0
  80. package/esm/src/services/sdk-normalize.d.ts.map +1 -0
  81. package/esm/src/services/sdk-normalize.js +61 -0
  82. package/esm/src/session.d.ts +74 -0
  83. package/esm/src/session.d.ts.map +1 -0
  84. package/esm/src/session.js +694 -0
  85. package/esm/src/types/index.d.ts +120 -0
  86. package/esm/src/types/index.d.ts.map +1 -0
  87. package/esm/src/types/index.js +28 -0
  88. package/esm/src/utils.d.ts +27 -0
  89. package/esm/src/utils.d.ts.map +1 -0
  90. package/esm/src/utils.js +76 -0
  91. package/package.json +59 -0
  92. package/script/_dnt.polyfills.d.ts +166 -0
  93. package/script/_dnt.polyfills.d.ts.map +1 -0
  94. package/script/_dnt.polyfills.js +180 -0
  95. package/script/_dnt.shims.d.ts +6 -0
  96. package/script/_dnt.shims.d.ts.map +1 -0
  97. package/script/_dnt.shims.js +65 -0
  98. package/script/deno.d.ts +45 -0
  99. package/script/deno.d.ts.map +1 -0
  100. package/script/deno.js +41 -0
  101. package/script/mod.d.ts +3 -0
  102. package/script/mod.d.ts.map +1 -0
  103. package/script/mod.js +6 -0
  104. package/script/package.json +3 -0
  105. package/script/src/config.d.ts +20 -0
  106. package/script/src/config.d.ts.map +1 -0
  107. package/script/src/config.js +256 -0
  108. package/script/src/handlers/chat.d.ts +14 -0
  109. package/script/src/handlers/chat.d.ts.map +1 -0
  110. package/script/src/handlers/chat.js +63 -0
  111. package/script/src/handlers/compacting.d.ts +9 -0
  112. package/script/src/handlers/compacting.d.ts.map +1 -0
  113. package/script/src/handlers/compacting.js +33 -0
  114. package/script/src/handlers/event.d.ts +22 -0
  115. package/script/src/handlers/event.d.ts.map +1 -0
  116. package/script/src/handlers/event.js +290 -0
  117. package/script/src/handlers/messages.d.ts +9 -0
  118. package/script/src/handlers/messages.d.ts.map +1 -0
  119. package/script/src/handlers/messages.js +96 -0
  120. package/script/src/index.d.ts +5 -0
  121. package/script/src/index.d.ts.map +1 -0
  122. package/script/src/index.js +159 -0
  123. package/script/src/services/batch-drain.d.ts +23 -0
  124. package/script/src/services/batch-drain.d.ts.map +1 -0
  125. package/script/src/services/batch-drain.js +221 -0
  126. package/script/src/services/connection-manager.d.ts +104 -0
  127. package/script/src/services/connection-manager.d.ts.map +1 -0
  128. package/script/src/services/connection-manager.js +635 -0
  129. package/script/src/services/constants.d.ts +7 -0
  130. package/script/src/services/constants.d.ts.map +1 -0
  131. package/script/src/services/constants.js +9 -0
  132. package/script/src/services/context-limit.d.ts +3 -0
  133. package/script/src/services/context-limit.d.ts.map +1 -0
  134. package/script/src/services/context-limit.js +47 -0
  135. package/script/src/services/event-extractor.d.ts +29 -0
  136. package/script/src/services/event-extractor.d.ts.map +1 -0
  137. package/script/src/services/event-extractor.js +669 -0
  138. package/script/src/services/graphiti-async.d.ts +22 -0
  139. package/script/src/services/graphiti-async.d.ts.map +1 -0
  140. package/script/src/services/graphiti-async.js +223 -0
  141. package/script/src/services/graphiti-mcp.d.ts +57 -0
  142. package/script/src/services/graphiti-mcp.d.ts.map +1 -0
  143. package/script/src/services/graphiti-mcp.js +198 -0
  144. package/script/src/services/logger.d.ts +9 -0
  145. package/script/src/services/logger.d.ts.map +1 -0
  146. package/script/src/services/logger.js +142 -0
  147. package/script/src/services/opencode-warning.d.ts +8 -0
  148. package/script/src/services/opencode-warning.d.ts.map +1 -0
  149. package/script/src/services/opencode-warning.js +114 -0
  150. package/script/src/services/redis-cache.d.ts +27 -0
  151. package/script/src/services/redis-cache.d.ts.map +1 -0
  152. package/script/src/services/redis-cache.js +219 -0
  153. package/script/src/services/redis-client.d.ts +89 -0
  154. package/script/src/services/redis-client.d.ts.map +1 -0
  155. package/script/src/services/redis-client.js +943 -0
  156. package/script/src/services/redis-events.d.ts +46 -0
  157. package/script/src/services/redis-events.d.ts.map +1 -0
  158. package/script/src/services/redis-events.js +535 -0
  159. package/script/src/services/redis-snapshot.d.ts +16 -0
  160. package/script/src/services/redis-snapshot.d.ts.map +1 -0
  161. package/script/src/services/redis-snapshot.js +189 -0
  162. package/script/src/services/render-utils.d.ts +23 -0
  163. package/script/src/services/render-utils.d.ts.map +1 -0
  164. package/script/src/services/render-utils.js +165 -0
  165. package/script/src/services/runtime-teardown.d.ts +23 -0
  166. package/script/src/services/runtime-teardown.d.ts.map +1 -0
  167. package/script/src/services/runtime-teardown.js +155 -0
  168. package/script/src/services/sdk-normalize.d.ts +55 -0
  169. package/script/src/services/sdk-normalize.d.ts.map +1 -0
  170. package/script/src/services/sdk-normalize.js +67 -0
  171. package/script/src/session.d.ts +74 -0
  172. package/script/src/session.d.ts.map +1 -0
  173. package/script/src/session.js +698 -0
  174. package/script/src/types/index.d.ts +120 -0
  175. package/script/src/types/index.d.ts.map +1 -0
  176. package/script/src/types/index.js +33 -0
  177. package/script/src/utils.d.ts +27 -0
  178. package/script/src/utils.d.ts.map +1 -0
  179. package/script/src/utils.js +87 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 vicary
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,358 @@
1
+ # opencode-graphiti
2
+
3
+ OpenCode plugin that gives your AI agent **short-term memory** and **long-term
4
+ memory**.
5
+
6
+ **Short-term memory** continuously summarizes and compacts every meaningful
7
+ session event — decisions, active tasks, file edits, errors, and more — into a
8
+ priority-tiered snapshot that is re-injected before every LLM call and every
9
+ compaction. The result is a rolling window of session continuity that
10
+ effectively extends the usable context far beyond the model's native limit: the
11
+ agent always knows what it was doing, even after the conversation is compacted.
12
+
13
+ **Long-term memory** persists knowledge across sessions via a
14
+ [Graphiti](https://github.com/getzep/graphiti) knowledge graph, so the agent can
15
+ recall project facts, past decisions, and learned preferences from earlier work
16
+ — not just the current session.
17
+
18
+ ## Motivation
19
+
20
+ Long-running AI coding sessions depend on persistent memory to stay on track.
21
+ When the context window fills up and OpenCode triggers compaction, the
22
+ summarizer discards details that were never captured outside the conversation.
23
+ The result is **context rot**: the agent loses track of recent decisions,
24
+ re-explores solved problems, and drifts away from the original goal.
25
+
26
+ Graphiti's MCP server is a powerful knowledge-graph backend, but calling it on
27
+ every message adds latency and introduces a single point of failure —
28
+ connections drop, queries time out, and ingestion silently fails.
29
+
30
+ This plugin exists to close both gaps.
31
+
32
+ **Short-term memory** captures every meaningful event during the session —
33
+ decisions, task progress, file edits, errors, environment changes — and
34
+ continuously summarizes them into a compact, priority-tiered snapshot. That
35
+ snapshot is re-injected before every LLM call and before every compaction, so
36
+ the agent always retains a coherent picture of the active workstream. Because
37
+ the snapshot is continuously rebuilt from structured events rather than raw
38
+ conversation text, it survives compaction intact: the model picks up exactly
39
+ where it left off, no matter how many times the conversation has been
40
+ summarized. In practice, this creates a rolling session memory that extends the
41
+ effective context window well beyond the model's native limit.
42
+
43
+ **Long-term memory** lives in Graphiti's knowledge graph, which is updated in
44
+ the background so it never slows down your conversation. It provides
45
+ cross-session recall — project facts, past decisions, and learned preferences
46
+ from earlier sessions — cached locally for instant injection alongside the
47
+ short-term snapshot.
48
+
49
+ ## Overview
50
+
51
+ This plugin uses a two-layer memory architecture:
52
+
53
+ **Short-term memory — continuously summarized session continuity:**
54
+
55
+ - Captures every meaningful event (decisions, tasks, file edits, errors,
56
+ environment changes) as structured session events
57
+ - Continuously rebuilds a priority-tiered snapshot from those events, keeping
58
+ the most important context within a tight budget
59
+ - Re-injects the snapshot before every LLM call and every compaction as a
60
+ `<session_memory>` block, so the agent never loses track of the active
61
+ workstream — even after repeated compactions
62
+ - Detects topic drift and schedules a background refresh of cached long-term
63
+ facts when the conversation shifts
64
+
65
+ **Long-term memory — persistent cross-session recall via Graphiti:**
66
+
67
+ - Sends buffered session events to Graphiti as episodes on idle or before
68
+ compaction
69
+ - Refreshes the local memory cache from Graphiti search results in the
70
+ background
71
+ - Provides cross-session recall via vector/graph search, cached locally for
72
+ instant injection alongside the short-term snapshot
73
+ - Saves compaction summaries as episodes so knowledge survives across session
74
+ boundaries
75
+
76
+ Graphiti stays off the steady-state hook path entirely: hook-time injection uses
77
+ only Redis/local cached recall, while fresh Graphiti data arrives through the
78
+ existing background refresh path on later turns.
79
+
80
+ ## Prerequisites
81
+
82
+ Start the
83
+ [Graphiti MCP server](https://github.com/getzep/graphiti/tree/main/mcp_server)
84
+ with its default [FalkorDB](https://www.falkordb.com/) backend:
85
+
86
+ ```bash
87
+ git clone https://github.com/getzep/graphiti.git
88
+ cd graphiti/mcp_server
89
+ docker compose up -d
90
+ ```
91
+
92
+ This starts Graphiti at `http://localhost:8000/mcp` and FalkorDB/Redis on
93
+ `localhost:6379`.
94
+
95
+ This plugin reuses that same FalkorDB/Redis storage layer alongside Graphiti: it
96
+ keeps short-term memory locally for every turn, while Graphiti builds the
97
+ long-term knowledge graph on top of the same backend.
98
+
99
+ > **Note:** Graphiti is optional for basic operation. If Graphiti is
100
+ > unavailable, the plugin continues to function with FalkorDB/Redis-sourced
101
+ > session memory; only the `<persistent_memory>` section (long-term
102
+ > cross-session facts) will be empty until Graphiti comes online.
103
+
104
+ ## Installation
105
+
106
+ ### Option A: npm package (recommended)
107
+
108
+ Add the plugin to your `opencode.json` (or `opencode.jsonc`):
109
+
110
+ ```jsonc
111
+ {
112
+ "plugin": ["opencode-graphiti"]
113
+ }
114
+ ```
115
+
116
+ ### Option B: Local build
117
+
118
+ Local distributable builds are not a routine local setup step: `deno task
119
+ build`
120
+ requires an explicit `VERSION` via `dnt.ts`. If you already have a built
121
+ artifact, add it to your `opencode.json`:
122
+
123
+ ```jsonc
124
+ {
125
+ "plugin": ["file:///absolute/path/to/opencode-graphiti/dist/esm/mod.js"]
126
+ }
127
+ ```
128
+
129
+ ### Option C: Plugin directory
130
+
131
+ Copy the built plugin into OpenCode's auto-loaded plugin directory:
132
+
133
+ ```bash
134
+ # Global (all projects)
135
+ cp dist/esm/mod.js ~/.config/opencode/plugins/opencode-graphiti.js
136
+
137
+ # Or project-level
138
+ mkdir -p .opencode/plugins
139
+ cp dist/esm/mod.js .opencode/plugins/opencode-graphiti.js
140
+ ```
141
+
142
+ No config entry needed — OpenCode loads plugins from these directories
143
+ automatically.
144
+
145
+ ## Configuration
146
+
147
+ Supported config locations, in lookup order:
148
+
149
+ 1. The provided project directory: `package.json#graphiti`, `.graphitirc`, and
150
+ other standard `cosmiconfig` `graphiti` filenames
151
+ 2. Standard global/home `graphiti` config locations discovered by `cosmiconfig`
152
+ (for example `~/.graphitirc`)
153
+ 3. Legacy fallback: `~/.config/opencode/.graphitirc`
154
+
155
+ ### Nested Config Shape (recommended)
156
+
157
+ ```jsonc
158
+ {
159
+ "redis": {
160
+ // Redis endpoint used for the plugin hot tier
161
+ "endpoint": "redis://localhost:6379",
162
+ // Max events per drain batch
163
+ "batchSize": 20,
164
+ // Max combined body bytes per drain batch
165
+ "batchMaxBytes": 51200,
166
+ // Session event TTL in seconds (default: 24 h)
167
+ "sessionTtlSeconds": 86400,
168
+ // Memory cache TTL in seconds (default: 10 min)
169
+ "cacheTtlSeconds": 600,
170
+ // Max drain retry attempts before dead-lettering
171
+ "drainRetryMax": 3
172
+ },
173
+ "graphiti": {
174
+ // Graphiti MCP server endpoint
175
+ "endpoint": "http://localhost:8000/mcp",
176
+ // Prefix for project group IDs (e.g. "opencode-my-project")
177
+ "groupIdPrefix": "opencode",
178
+ // Jaccard similarity threshold (0–1) below which cache is refreshed
179
+ "driftThreshold": 0.5
180
+ }
181
+ }
182
+ ```
183
+
184
+ All fields are optional — defaults (shown above) are used for any missing
185
+ values. Canonical nested values take precedence when both forms are supplied.
186
+
187
+ ### Retained Compatibility
188
+
189
+ The canonical hot-tier config shape is now `redis.*`. For backward
190
+ compatibility, the loader still accepts legacy nested `falkordb.*` values. Only
191
+ the original Graphiti top-level aliases remain supported. Precedence is:
192
+
193
+ 1. `redis.*` (canonical)
194
+ 2. `falkordb.*` (legacy nested compatibility)
195
+ 3. top-level Graphiti aliases such as `endpoint` and `groupIdPrefix`
196
+
197
+ ### Legacy Top-Level Keys
198
+
199
+ For backward compatibility, the following original Graphiti top-level keys are
200
+ still accepted and map to their nested equivalents:
201
+
202
+ | Legacy key | Nested equivalent |
203
+ | ---------------- | ------------------------- |
204
+ | `endpoint` | `graphiti.endpoint` |
205
+ | `groupIdPrefix` | `graphiti.groupIdPrefix` |
206
+ | `driftThreshold` | `graphiti.driftThreshold` |
207
+
208
+ Legacy nested `falkordb.*` keys remain accepted as compatibility aliases for the
209
+ same `redis.*` settings. Removed top-level Redis aliases are no longer
210
+ supported.
211
+
212
+ ## How It Works
213
+
214
+ ### Injection Format
215
+
216
+ The plugin currently injects a `<session_memory>` XML envelope into the last
217
+ user message. This envelope is assembled from short-term memory in Redis and can
218
+ contain structured sections such as `<last_request>`, `<active_tasks>`,
219
+ `<key_decisions>`, `<files_in_play>`, `<project_rules>`, and an optional
220
+ `<session_snapshot>`.
221
+
222
+ When long-term memory is available, a nested `<persistent_memory>` section is
223
+ included with a `node_refs` attribute naming the emitted cached entities. On a
224
+ cold first turn or when Graphiti is unreachable, `<persistent_memory>` is simply
225
+ absent — the rest of the session memory is always available from short-term
226
+ storage in FalkorDB/Redis.
227
+
228
+ ```xml
229
+ <session_memory source="falkordb+graphiti-cache" version="1">
230
+ <last_request>Continue the current task.</last_request>
231
+ <active_tasks><task>Implement the new feature.</task></active_tasks>
232
+ <key_decisions><decision>Use Redis for short-term memory.</decision></key_decisions>
233
+ <files_in_play><file>src/index.ts</file></files_in_play>
234
+ <project_rules><rule>Graphiti runs in the background only.</rule></project_rules>
235
+ <session_snapshot><!-- priority-tiered snapshot --></session_snapshot>
236
+ <persistent_memory node_refs="nodeA">
237
+ <!-- long-term node/episode summaries from Graphiti, optional -->
238
+ </persistent_memory>
239
+ </session_memory>
240
+ ```
241
+
242
+ ### Session Memory Preparation (`chat.message`)
243
+
244
+ On each user message the plugin assembles the current session memory from three
245
+ sources:
246
+
247
+ - Recent structured session events
248
+ - The continuously rebuilt priority-tiered snapshot
249
+ - Cached long-term facts from Graphiti
250
+
251
+ These are composed into a `<session_memory>` envelope and staged for the
252
+ transform hook. The hook-time reads are local/cache-backed only; any fresh
253
+ Graphiti lookup remains on the existing background refresh path and benefits the
254
+ next turn instead of blocking the current one.
255
+
256
+ ### User Message Injection (`experimental.chat.messages.transform`)
257
+
258
+ The transform hook reads the prepared `<session_memory>` envelope and prepends
259
+ it to the last user message. Legacy `<memory data-uuids>` and older
260
+ `<persistent_memory fact_uuids>` blocks are still scrubbed and parsed for
261
+ compatibility, while current `<persistent_memory>` output uses `node_refs`. This
262
+ approach keeps the system prompt static, enabling provider-side prefix caching,
263
+ and avoids influencing session titles. The prepared injection is cleared after
264
+ use so stale context is not re-injected on subsequent LLM calls within the same
265
+ turn.
266
+
267
+ ### Drift Detection and Background Cache Refresh
268
+
269
+ On each user message, the plugin compares the current query against the query
270
+ that produced the cached memory. When Jaccard similarity between the current
271
+ query text and cached query text drops below `driftThreshold` (default 0.5), a
272
+ background cache refresh is scheduled via Graphiti. The current cached context
273
+ is still injected immediately; the refreshed cache becomes available on the next
274
+ message. This trades one message of staleness for keeping most long-term memory
275
+ refresh work off the response-time path.
276
+
277
+ ### Event Extraction and Buffering (`event`)
278
+
279
+ User and assistant messages are captured as structured `SessionEvent` objects
280
+ and stored in Redis (`session:{id}:events`). The plugin listens on
281
+ `message.part.updated` to buffer assistant text as it streams, and on
282
+ `message.updated` to finalize completed assistant replies.
283
+
284
+ Events are also queued for background ingestion into long-term memory:
285
+
286
+ - **On idle** (`session.idle`): buffered events are sent to Graphiti and the
287
+ priority-tiered snapshot is rebuilt.
288
+ - **Before compaction** (`session.compacted`): all pending events are sent
289
+ immediately so nothing is lost.
290
+
291
+ ### Compaction Preservation
292
+
293
+ Compaction is handled entirely by OpenCode's native compaction mechanism. The
294
+ plugin ensures session continuity survives each compaction cycle:
295
+
296
+ 1. **Before compaction** (`experimental.session.compacting`): The plugin injects
297
+ the same `<session_memory>` envelope used for chat — including the
298
+ priority-tiered snapshot and cached long-term facts — so the summarizer
299
+ preserves important knowledge. No Graphiti call is made.
300
+ 2. **After compaction** (`session.compacted`): The snapshot is rebuilt from
301
+ structured events and the compaction summary is sent to Graphiti in the
302
+ background, ensuring knowledge survives across compaction boundaries.
303
+
304
+ Because the snapshot is rebuilt from structured events rather than raw
305
+ conversation text, the agent retains a coherent picture of the workstream
306
+ regardless of how aggressively the conversation was summarized.
307
+
308
+ ### Child / Subagent Session Handling
309
+
310
+ > **Note:** This behavior intentionally diverges from
311
+ > [context-mode](https://github.com/mksglu/context-mode), which records subagent
312
+ > work as summarized tool events. This plugin promotes child sessions to
313
+ > first-class participants in the root session's state so that decisions, file
314
+ > edits, and errors from delegated work are fully visible to the parent session.
315
+ > See `plans/ContextOverhaul.md` §10.1 for the design rationale.
316
+
317
+ When OpenCode spawns a child session (e.g. a subagent or delegated task), the
318
+ plugin resolves the child's `sessionID` to the root/parent session by walking
319
+ the `parentID` chain. All event storage, snapshot builds, and `<session_memory>`
320
+ injection then operate on the canonical root session, so child activity is
321
+ treated identically to parent activity:
322
+
323
+ - Child prompts and responses are recorded in the same event log as the parent.
324
+ - The priority-tiered snapshot includes child-derived events when it is rebuilt.
325
+ - Future `<session_memory>` injections — for both parent and child turns —
326
+ reflect the combined activity of the entire session lineage.
327
+ - Deleting a child session removes only that child's local bookkeeping; the root
328
+ session's state, events, and snapshot are preserved.
329
+
330
+ This means the agent retains full continuity across delegation boundaries
331
+ without any special configuration.
332
+
333
+ ### Project Scoping
334
+
335
+ Each project gets a unique `group_id` derived from its directory name (e.g.
336
+ `opencode_my-project`). Group IDs only allow letters, numbers, dashes, and
337
+ underscores (colons are not allowed). This ensures memories from different
338
+ projects stay isolated.
339
+
340
+ ## Contributing
341
+
342
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and release
343
+ process. In CI, pushes to `main` publish `latest` releases, while pull requests
344
+ targeting `main` publish canary builds under the `canary` dist-tag.
345
+
346
+ ## License
347
+
348
+ MIT
349
+
350
+ ## Acknowledgements
351
+
352
+ The structured event extraction, priority-tiered snapshots, and session
353
+ continuity design in this plugin are inspired by
354
+ [context-mode](https://github.com/mksglu/context-mode) by
355
+ [Mert Köseoğlu](https://github.com/mksglu).
356
+
357
+ The original plugin concept is inspired by
358
+ [opencode-openmemory](https://github.com/happycastle114/opencode-openmemory).
@@ -0,0 +1,166 @@
1
+ declare global {
2
+ interface Array<T> {
3
+ /**
4
+ * Returns the value of the last element in the array where predicate is true, and undefined
5
+ * otherwise.
6
+ * @param predicate find calls predicate once for each element of the array, in ascending
7
+ * order, until it finds one where predicate returns true. If such an element is found, find
8
+ * immediately returns that element value. Otherwise, find returns undefined.
9
+ * @param thisArg If provided, it will be used as the this value for each invocation of
10
+ * predicate. If it is not provided, undefined is used instead.
11
+ */
12
+ findLast<S extends T>(predicate: (this: void, value: T, index: number, obj: T[]) => value is S, thisArg?: any): S | undefined;
13
+ findLast(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined;
14
+ /**
15
+ * Returns the index of the last element in the array where predicate is true, and -1
16
+ * otherwise.
17
+ * @param predicate find calls predicate once for each element of the array, in ascending
18
+ * order, until it finds one where predicate returns true. If such an element is found,
19
+ * findIndex immediately returns that element index. Otherwise, findIndex returns -1.
20
+ * @param thisArg If provided, it will be used as the this value for each invocation of
21
+ * predicate. If it is not provided, undefined is used instead.
22
+ */
23
+ findLastIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number;
24
+ }
25
+ interface Uint8Array {
26
+ /**
27
+ * Returns the value of the last element in the array where predicate is true, and undefined
28
+ * otherwise.
29
+ * @param predicate findLast calls predicate once for each element of the array, in descending
30
+ * order, until it finds one where predicate returns true. If such an element is found, findLast
31
+ * immediately returns that element value. Otherwise, findLast returns undefined.
32
+ * @param thisArg If provided, it will be used as the this value for each invocation of
33
+ * predicate. If it is not provided, undefined is used instead.
34
+ */
35
+ findLast<S extends number>(predicate: (value: number, index: number, array: Uint8Array) => value is S, thisArg?: any): S | undefined;
36
+ findLast(predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number | undefined;
37
+ /**
38
+ * Returns the index of the last element in the array where predicate is true, and -1
39
+ * otherwise.
40
+ * @param predicate findLastIndex calls predicate once for each element of the array, in descending
41
+ * order, until it finds one where predicate returns true. If such an element is found,
42
+ * findLastIndex immediately returns that element index. Otherwise, findLastIndex returns -1.
43
+ * @param thisArg If provided, it will be used as the this value for each invocation of
44
+ * predicate. If it is not provided, undefined is used instead.
45
+ */
46
+ findLastIndex(predicate: (value: number, index: number, array: Uint8Array) => unknown, thisArg?: any): number;
47
+ }
48
+ }
49
+ export {};
50
+ declare global {
51
+ interface Object {
52
+ /**
53
+ * Determines whether an object has a property with the specified name.
54
+ * @param o An object.
55
+ * @param v A property name.
56
+ */
57
+ hasOwn(o: object, v: PropertyKey): boolean;
58
+ }
59
+ }
60
+ export {};
61
+ declare global {
62
+ interface Error {
63
+ cause?: unknown;
64
+ }
65
+ }
66
+ export {};
67
+ /**
68
+ * Based on [import-meta-ponyfill](https://github.com/gaubee/import-meta-ponyfill),
69
+ * but instead of using npm to install additional dependencies,
70
+ * this approach manually consolidates cjs/mjs/d.ts into a single file.
71
+ *
72
+ * Note that this code might be imported multiple times
73
+ * (for example, both dnt.test.polyfills.ts and dnt.polyfills.ts contain this code;
74
+ * or Node.js might dynamically clear the cache and then force a require).
75
+ * Therefore, it's important to avoid redundant writes to global objects.
76
+ * Additionally, consider that commonjs is used alongside esm,
77
+ * so the two ponyfill functions are stored independently in two separate global objects.
78
+ */
79
+ import { createRequire } from "node:module";
80
+ import { type URL } from "node:url";
81
+ declare global {
82
+ interface ImportMeta {
83
+ /** A string representation of the fully qualified module URL. When the
84
+ * module is loaded locally, the value will be a file URL (e.g.
85
+ * `file:///path/module.ts`).
86
+ *
87
+ * You can also parse the string as a URL to determine more information about
88
+ * how the current module was loaded. For example to determine if a module was
89
+ * local or not:
90
+ *
91
+ * ```ts
92
+ * const url = new URL(import.meta.url);
93
+ * if (url.protocol === "file:") {
94
+ * console.log("this module was loaded locally");
95
+ * }
96
+ * ```
97
+ */
98
+ url: string;
99
+ /**
100
+ * A function that returns resolved specifier as if it would be imported
101
+ * using `import(specifier)`.
102
+ *
103
+ * ```ts
104
+ * console.log(import.meta.resolve("./foo.js"));
105
+ * // file:///dev/foo.js
106
+ * ```
107
+ *
108
+ * @param specifier The module specifier to resolve relative to `parent`.
109
+ * @param parent The absolute parent module URL to resolve from.
110
+ * @returns The absolute (`file:`) URL string for the resolved module.
111
+ */
112
+ resolve(specifier: string, parent?: string | URL | undefined): string;
113
+ /** A flag that indicates if the current module is the main module that was
114
+ * called when starting the program under Deno.
115
+ *
116
+ * ```ts
117
+ * if (import.meta.main) {
118
+ * // this was loaded as the main module, maybe do some bootstrapping
119
+ * }
120
+ * ```
121
+ */
122
+ main: boolean;
123
+ /** The absolute path of the current module.
124
+ *
125
+ * This property is only provided for local modules (ie. using `file://` URLs).
126
+ *
127
+ * Example:
128
+ * ```
129
+ * // Unix
130
+ * console.log(import.meta.filename); // /home/alice/my_module.ts
131
+ *
132
+ * // Windows
133
+ * console.log(import.meta.filename); // C:\alice\my_module.ts
134
+ * ```
135
+ */
136
+ filename: string;
137
+ /** The absolute path of the directory containing the current module.
138
+ *
139
+ * This property is only provided for local modules (ie. using `file://` URLs).
140
+ *
141
+ * * Example:
142
+ * ```
143
+ * // Unix
144
+ * console.log(import.meta.dirname); // /home/alice
145
+ *
146
+ * // Windows
147
+ * console.log(import.meta.dirname); // C:\alice
148
+ * ```
149
+ */
150
+ dirname: string;
151
+ }
152
+ }
153
+ type NodeRequest = ReturnType<typeof createRequire>;
154
+ type NodeModule = NonNullable<NodeRequest["main"]>;
155
+ interface ImportMetaPonyfillCommonjs {
156
+ (require: NodeRequest, module: NodeModule): ImportMeta;
157
+ }
158
+ interface ImportMetaPonyfillEsmodule {
159
+ (importMeta: ImportMeta): ImportMeta;
160
+ }
161
+ interface ImportMetaPonyfill extends ImportMetaPonyfillCommonjs, ImportMetaPonyfillEsmodule {
162
+ }
163
+ export declare let import_meta_ponyfill_commonjs: ImportMetaPonyfillCommonjs;
164
+ export declare let import_meta_ponyfill_esmodule: ImportMetaPonyfillEsmodule;
165
+ export declare let import_meta_ponyfill: ImportMetaPonyfill;
166
+ //# sourceMappingURL=_dnt.polyfills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dnt.polyfills.d.ts","sourceRoot":"","sources":["../src/_dnt.polyfills.ts"],"names":[],"mappings":"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"}