@cortexkit/opencode-magic-context 0.15.1 → 0.15.2

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 CHANGED
@@ -48,16 +48,22 @@ Keep using the **same session** for **weeks**, **months**, or even **years**. **
48
48
 
49
49
  ---
50
50
 
51
- ### ✨ New Features Graduated in v0.14
51
+ ### ✨ Recent Highlights
52
52
 
53
- **User Memories** — now enabled by default under `dreamer.user_memories`. Historian extracts behavioral observations about you alongside its normal compartment output (communication style, expertise level, review focus, working patterns). Recurring observations are promoted by the dreamer to stable user memories that appear in all sessions via `<user-profile>`. Set `dreamer.user_memories.enabled: false` to opt out. Requires dreamer.
53
+ **Subagents now self-manage context (v0.15)** — subagent sessions (historian, dreamer, Athena council members, any `mode: "subagent"` agent) now run age-based tool drops, reasoning clearing, and structural stripping at the execute threshold the same way primary sessions with `ctx_reduce_enabled: false` behave. Previously they had no automatic reduction and grew silently until overflow. Nudges, historian/compartment runs, and the `<session-history>` block remain primary-only subagents stay lean and parent-driven.
54
54
 
55
- **Key File Pinning** — now under `dreamer.pin_key_files`, still opt-in. Dreamer analyzes which files your agent reads most frequently across the session. Core orientation files (architecture, config, types) that get re-read after every context drop are pinned into the system prompt as `<key-files>`, so the agent always has them without needing to re-read from disk. Files are read fresh on each cache-busting pass. Enable with `dreamer.pin_key_files.enabled: true`.
55
+ **Lean sessions when `ctx_reduce_enabled: false` (v0.15)** — when you opt out of agent-driven reduction, the `§N§` tag prefix on user/assistant text and tool output is no longer injected, saving several thousand tokens per long session. The injected prompt guidance also switches to the no-reduce variant so the agent isn't told about a tool it can't use. DB tag records still exist (heuristic cleanup, persistence, and replay all depend on them); only the agent-visible prefix is skipped.
56
+
57
+ **User Memories (v0.14)** — enabled by default under `dreamer.user_memories`. Historian extracts behavioral observations about you alongside its normal compartment output (communication style, expertise level, review focus, working patterns). Recurring observations are promoted by the dreamer to stable user memories that appear in all sessions via `<user-profile>`. Set `dreamer.user_memories.enabled: false` to opt out. Requires dreamer.
58
+
59
+ **Key File Pinning (v0.14)** — under `dreamer.pin_key_files`, still opt-in. Dreamer analyzes which files your agent reads most frequently across the session. Core orientation files (architecture, config, types) that get re-read after every context drop are pinned into the system prompt as `<key-files>`, so the agent always has them without needing to re-read from disk. Files are read fresh on each cache-busting pass. Enable with `dreamer.pin_key_files.enabled: true`.
56
60
 
57
61
  > Migrating from an earlier version? Running `bunx --bun @cortexkit/opencode-magic-context@latest doctor` rewrites old `experimental.user_memories.*` and `experimental.pin_key_files.*` keys into their new `dreamer.*` homes, preserving any `enabled` state you had.
58
62
 
59
63
  ### 🧪 New Experimental Features
60
64
 
65
+ **Age-tier caveman text compression (v0.15)** — opt-in companion to `ctx_reduce_enabled: false`. Older user/assistant text parts are progressively compressed using deterministic [caveman rules](https://github.com/cortexkit/opencode-magic-context/blob/master/packages/plugin/src/hooks/magic-context/caveman.ts) — the oldest 20% go to ultra-compressed, next 20% to full, next 20% to lite, newest 40% untouched. Tier shifts always recompress from the pristine original, never from an already-cavemaned intermediate, so the result is stable across passes. Cache-safe by design. Enable with `experimental.caveman_text_compression: { enabled: true }`. Only active when `ctx_reduce_enabled: false`.
66
+
61
67
  **Temporal Awareness** — gives the agent real-time perception. Each user message gets a small `<!-- +5m -->`/`<!-- +2h 15m -->`/`<!-- +3d 4h -->` gap marker showing time since the previous message, and every compartment in `<session-history>` carries `start-date`/`end-date` attributes. Lets the agent reason correctly about how long a build ran, when a decision was made, or how stale a prior session is. Cache-safe — markers derive from immutable timestamps. Enable with `experimental.temporal_awareness: true`.
62
68
 
63
69
  **Git Commit Indexing** — indexes HEAD git commits (skipping merges) from the project and makes them searchable through `ctx_search`. Commits are embedded so semantic queries like "when did we change the auth pattern" or "why did we pick X over Y" surface the right work. HEAD-only, windowed to the last year by default, capped at 2000 commits per project with oldest evicted. Enable with `experimental.git_commit_indexing.enabled: true`.
@@ -161,7 +167,7 @@ Use `--force` to force-clear the plugin cache even when versions match (fixes br
161
167
  bunx --bun @cortexkit/opencode-magic-context@latest doctor --force
162
168
  ```
163
169
 
164
- Hit a real bug? Use `--issue` to collect environment, sanitized config, and the last 200 log lines into a ready-to-submit report. It can also open the issue directly via `gh` if you have it installed:
170
+ Hit a real bug? Use `--issue` to collect environment, sanitized config, and the last 400 log lines into a ready-to-submit report. It can also open the issue directly via `gh` if you have it installed:
165
171
 
166
172
  ```bash
167
173
  bunx --bun @cortexkit/opencode-magic-context@latest doctor --issue
@@ -272,7 +278,7 @@ A **separate compressor** pass fires when the rendered history block exceeds the
272
278
 
273
279
  ### Nudging
274
280
 
275
- As context usage grows, Magic Context sends rolling reminders suggesting the agent reduce. Cadence tightens as usage approaches the threshold — from gentle reminders to urgent warnings. If the agent recently called `ctx_reduce`, reminders are suppressed. An emergency nudge at 80% always fires.
281
+ As context usage grows, Magic Context sends rolling reminders suggesting the agent reduce. Cadence tightens as usage approaches the threshold — from gentle reminders to urgent warnings. If the agent recently called `ctx_reduce`, reminders are suppressed. At 85% Magic Context force-materializes queued drops and emergency cleanup; at 95% it blocks the turn until background historian completes.
276
282
 
277
283
  ### Cross-session memory
278
284
 
@@ -308,7 +314,7 @@ Stable user memories are visible and manageable in the dashboard's User Memories
308
314
 
309
315
  When running in OpenCode's terminal UI, Magic Context shows a live sidebar panel with:
310
316
 
311
- - **Context breakdown bar** — visual token split across System Prompt, Compartments, Facts, Memories, and Conversation
317
+ - **Context breakdown bar** — visual token split across System Prompt, Compartments, Facts, Memories, Conversation, Tool Calls, Tool Definitions (measured from the `tool.definition` hook), and Overhead. Cool palette for structured injections, warm palette for user/tool traffic.
312
318
  - **Historian status** — idle, running, or compartment/fact counts
313
319
  - **Memory counts** — total project memories and how many are injected
314
320
  - **Dreamer status** — last run time
@@ -1 +1 @@
1
- {"version":3,"file":"magic-context-prompt.d.ts","sourceRoot":"","sources":["../../src/agents/magic-context-prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,KAAK,SAAS,GACR,UAAU,GACV,OAAO,GACP,YAAY,GACZ,iBAAiB,GACjB,QAAQ,GACR,QAAQ,GACR,eAAe,CAAC;AAyMtB;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAOlF;AAID,wBAAgB,wBAAwB,CACpC,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,aAAa,EAAE,MAAM,EACrB,gBAAgB,UAAO,EACvB,cAAc,UAAQ,EACtB,iBAAiB,UAAO,EACxB,wBAAwB,UAAQ,GACjC,MAAM,CAWR"}
1
+ {"version":3,"file":"magic-context-prompt.d.ts","sourceRoot":"","sources":["../../src/agents/magic-context-prompt.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,KAAK,SAAS,GACR,UAAU,GACV,OAAO,GACP,YAAY,GACZ,iBAAiB,GACjB,QAAQ,GACR,QAAQ,GACR,eAAe,CAAC;AA4MtB;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAOlF;AAID,wBAAgB,wBAAwB,CACpC,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,aAAa,EAAE,MAAM,EACrB,gBAAgB,UAAO,EACvB,cAAc,UAAQ,EACtB,iBAAiB,UAAO,EACxB,wBAAwB,UAAQ,GACjC,MAAM,CAWR"}
@@ -1 +1 @@
1
- {"version":3,"file":"compaction-marker.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/compaction-marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA+BH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,CAE3E;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,CAExE;AA8BD,wBAAgB,uBAAuB,IAAI,IAAI,CAS9C;AAID,UAAU,mBAAmB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACnC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACnB,mBAAmB,GAAG,IAAI,CAkC5B;AAID,UAAU,qBAAqB;IAC3B,uDAAuD;IACvD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;CACzB;AAID,MAAM,WAAW,0BAA0B;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAClC,IAAI,EAAE,0BAA0B,GACjC,qBAAqB,GAAG,IAAI,CAiF9B;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAgB5E"}
1
+ {"version":3,"file":"compaction-marker.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/compaction-marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AA+BH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,CAE3E;AAED,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,CAExE;AAsGD,wBAAgB,uBAAuB,IAAI,IAAI,CAY9C;AAID,UAAU,mBAAmB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACnC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACnB,mBAAmB,GAAG,IAAI,CAgC5B;AAID,UAAU,qBAAqB;IAC3B,uDAAuD;IACvD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,gBAAgB,EAAE,MAAM,CAAC;IACzB,iDAAiD;IACjD,gBAAgB,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAC;CACzB;AAID,MAAM,WAAW,0BAA0B;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAClC,IAAI,EAAE,0BAA0B,GACjC,qBAAqB,GAAG,IAAI,CAwF9B;AAID;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAgB5E"}
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/dreamer/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA2C3D,UAAU,6BAA6B;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAE9F;AAMD,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAClB,EAAE,CAAC;CACP;AAqUD,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACjC,EAAE,EAAE,QAAQ,CAAC;IACb,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,6FAA6F;IAC7F,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,uBAAuB,CAAC,EAAE,6BAA6B,CAAC;CAC3D,GAAG,OAAO,CAAC,cAAc,CAAC,CA+U1B;AA0LD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC1C,EAAE,EAAE,QAAQ,CAAC;IACb,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wBAAwB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,uBAAuB,CAAC,EAAE,6BAA6B,CAAC;CAC3D,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAyDjC"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/dreamer/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAItC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sCAAsC,CAAC;AACzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AA2C3D,UAAU,6BAA6B;IACnC,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,6BAA6B,CAAC,eAAe,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAE9F;AAMD,MAAM,WAAW,cAAc;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,KAAK,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,EAAE,MAAM,CAAC;QACnB,MAAM,EAAE,OAAO,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAClB,EAAE,CAAC;CACP;AAqUD,wBAAsB,QAAQ,CAAC,IAAI,EAAE;IACjC,EAAE,EAAE,QAAQ,CAAC;IACb,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,6FAA6F;IAC7F,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wBAAwB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,uBAAuB,CAAC,EAAE,6BAA6B,CAAC;CAC3D,GAAG,OAAO,CAAC,cAAc,CAAC,CAqV1B;AA0LD,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC1C,EAAE,EAAE,QAAQ,CAAC;IACb,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,wBAAwB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,uBAAuB,CAAC,EAAE,6BAA6B,CAAC;CAC3D,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAyDjC"}
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA8O3C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAkChD"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAsQ3C;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAkChD"}
@@ -1,5 +1,25 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  export declare function initializeDatabase(db: Database): void;
3
+ /**
4
+ * Heal NULL columns added via ensureColumn against pre-existing rows.
5
+ *
6
+ * SQLite does NOT backfill column defaults when ALTER TABLE ADD COLUMN runs
7
+ * on an already-populated table — old rows get NULL regardless of the
8
+ * DEFAULT clause. isSessionMetaRow used to require strict typeof === "string"
9
+ * / "number", which NULL fails, so rows with NULL columns were rejected,
10
+ * getOrCreateSessionMeta returned zeroed defaults (lastResponseTime=0,
11
+ * cacheTtl="5m"), the scheduler returned "execute" forever, and every
12
+ * execute pass mutated message content — a sustained cache-bust cascade.
13
+ *
14
+ * The validator now tolerates NULL, but we normalize the data too so every
15
+ * code path sees well-formed values. Each UPDATE is best-effort: if a column
16
+ * doesn't exist yet (migration ran on a DB older than the ensureColumn call),
17
+ * the UPDATE throws and we move on — the next schema upgrade runs ensureColumn
18
+ * first, then this heal again.
19
+ *
20
+ * Exported so migration v5 can call it. Not exported from any barrel.
21
+ */
22
+ export declare function healAllNullColumns(db: Database): void;
3
23
  export declare function openDatabase(): Database;
4
24
  export declare function isDatabasePersisted(db: Database): boolean;
5
25
  export declare function getDatabasePersistenceError(db: Database): string | null;
@@ -1 +1 @@
1
- {"version":3,"file":"storage-db.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/storage-db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAkBtC,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAoVrD;AAsHD,wBAAgB,YAAY,IAAI,QAAQ,CAsCvC;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAEzD;AAED,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAEvE;AAED,wBAAgB,aAAa,IAAI,IAAI,CAUpC;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC"}
1
+ {"version":3,"file":"storage-db.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/storage-db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAkBtC,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CA8TrD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAIrD;AAsHD,wBAAgB,YAAY,IAAI,QAAQ,CAsCvC;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,GAAG,OAAO,CAEzD;AAED,wBAAgB,2BAA2B,CAAC,EAAE,EAAE,QAAQ,GAAG,MAAM,GAAG,IAAI,CAEvE;AAED,wBAAgB,aAAa,IAAI,IAAI,CAUpC;AAED,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"inject-compartments.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/inject-compartments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAU3C,OAAO,KAAK,EAAE,MAAM,EAAkB,MAAM,2CAA2C,CAAC;AAIxF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,MAAM,WAAW,4BAA4B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB,EAAE,MAAM,CAAC;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACvB;AAWD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAkBvF;AAED,MAAM,WAAW,0BAA0B;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;CAClC;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CA6BnE;AAoFD,wBAAgB,2BAA2B,CACvC,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,WAAW,EAAE,EACvB,cAAc,EAAE,OAAO,EACvB,WAAW,CAAC,EAAE,MAAM,EACpB,qBAAqB,CAAC,EAAE,MAAM,EAC9B,iBAAiB,CAAC,EAAE,OAAO,GAC5B,4BAA4B,GAAG,IAAI,CAgLrC;AAED,wBAAgB,0BAA0B,CACtC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,WAAW,EAAE,EACvB,QAAQ,EAAE,4BAA4B,GACvC,0BAA0B,CAgC5B"}
1
+ {"version":3,"file":"inject-compartments.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/inject-compartments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAU3C,OAAO,KAAK,EAAE,MAAM,EAAkB,MAAM,2CAA2C,CAAC;AAKxF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGlD,MAAM,WAAW,4BAA4B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB,EAAE,MAAM,CAAC;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;CACvB;AAmBD,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,CAkBvF;AAED,MAAM,WAAW,0BAA0B;IACvC,QAAQ,EAAE,OAAO,CAAC;IAClB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,gBAAgB,EAAE,MAAM,CAAC;IACzB,sBAAsB,EAAE,MAAM,CAAC;CAClC;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CA6BnE;AAoFD,wBAAgB,2BAA2B,CACvC,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,WAAW,EAAE,EACvB,cAAc,EAAE,OAAO,EACvB,WAAW,CAAC,EAAE,MAAM,EACpB,qBAAqB,CAAC,EAAE,MAAM,EAC9B,iBAAiB,CAAC,EAAE,OAAO,GAC5B,4BAA4B,GAAG,IAAI,CAgLrC;AAED,wBAAgB,0BAA0B,CACtC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,WAAW,EAAE,EACvB,QAAQ,EAAE,4BAA4B,GACvC,0BAA0B,CAgC5B"}
@@ -1 +1 @@
1
- {"version":3,"file":"system-prompt-hash.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/system-prompt-hash.ts"],"names":[],"mappings":"AAQA,OAAO,EACH,KAAK,eAAe,EAGvB,MAAM,sCAAsC,CAAC;AAiB9C;;;;;GAKG;AACH,wBAAgB,4BAA4B,CACxC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE;IACR,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACnD,GACF,IAAI,CAKN;AA8BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAChD,EAAE,EAAE,eAAe,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,6FAA6F;IAC7F,UAAU,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,kFAAkF;IAClF,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,2EAA2E;IAC3E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,2DAA2D;IAC3D,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAC5C,yFAAyF;IACzF,6BAA6B,CAAC,EAAE,OAAO,CAAC;CAC3C,GAAG;IACA,OAAO,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxF,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C,CA0SA"}
1
+ {"version":3,"file":"system-prompt-hash.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/system-prompt-hash.ts"],"names":[],"mappings":"AAQA,OAAO,EACH,KAAK,eAAe,EAGvB,MAAM,sCAAsC,CAAC;AAiB9C;;;;;GAKG;AACH,wBAAgB,4BAA4B,CACxC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE;IACR,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,mBAAmB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACnD,GACF,IAAI,CAKN;AA8BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,6BAA6B,CAAC,IAAI,EAAE;IAChD,EAAE,EAAE,eAAe,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,6FAA6F;IAC7F,UAAU,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,kFAAkF;IAClF,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,2EAA2E;IAC3E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,2DAA2D;IAC3D,kCAAkC,CAAC,EAAE,MAAM,CAAC;IAC5C,yFAAyF;IACzF,6BAA6B,CAAC,EAAE,OAAO,CAAC;CAC3C,GAAG;IACA,OAAO,EAAE,CAAC,KAAK,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxF,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7C,CA4SA"}
@@ -1 +1 @@
1
- {"version":3,"file":"tag-content-primitives.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/tag-content-primitives.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAKvD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,gBAAgB,CAItE"}
1
+ {"version":3,"file":"tag-content-primitives.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/tag-content-primitives.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAuCvD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOpD;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,gBAAgB,CAItE"}
@@ -1 +1 @@
1
- {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -149981,7 +149981,9 @@ function byteSize(value) {
149981
149981
  return encoder.encode(value).length;
149982
149982
  }
149983
149983
  function stripTagPrefix(value) {
149984
- return value.replace(TAG_PREFIX_REGEX, "");
149984
+ let stripped = value.replace(MALFORMED_TAG_PREFIX_REGEX, "");
149985
+ stripped = stripped.replace(TAG_PREFIX_REGEX, "");
149986
+ return stripped;
149985
149987
  }
149986
149988
  function prependTag(tagId, value) {
149987
149989
  const stripped = stripTagPrefix(value);
@@ -149993,10 +149995,11 @@ function isThinkingPart(part) {
149993
149995
  const candidate = part;
149994
149996
  return candidate.type === "thinking" || candidate.type === "reasoning";
149995
149997
  }
149996
- var encoder, TAG_PREFIX_REGEX;
149998
+ var encoder, TAG_PREFIX_REGEX, MALFORMED_TAG_PREFIX_REGEX;
149997
149999
  var init_tag_content_primitives = __esm(() => {
149998
150000
  encoder = new TextEncoder;
149999
150001
  TAG_PREFIX_REGEX = /^(?:\u00A7\d+\u00A7\s*)+/;
150002
+ MALFORMED_TAG_PREFIX_REGEX = /^(?:\u00A7\d+">\u00A7(?:\d+\u00A7)?\s*)+/;
150000
150003
  });
150001
150004
 
150002
150005
  // src/hooks/magic-context/tag-part-guards.ts
@@ -150397,6 +150400,7 @@ function runMigrations(db) {
150397
150400
  var MIGRATIONS;
150398
150401
  var init_migrations = __esm(() => {
150399
150402
  init_logger();
150403
+ init_storage_db();
150400
150404
  MIGRATIONS = [
150401
150405
  {
150402
150406
  version: 1,
@@ -150563,6 +150567,13 @@ var init_migrations = __esm(() => {
150563
150567
  END;
150564
150568
  `);
150565
150569
  }
150570
+ },
150571
+ {
150572
+ version: 5,
150573
+ description: "One-shot heal of NULL session_meta columns",
150574
+ up: (db) => {
150575
+ healAllNullColumns(db);
150576
+ }
150566
150577
  }
150567
150578
  ];
150568
150579
  });
@@ -150639,12 +150650,10 @@ function initializeDatabase(db) {
150639
150650
  updated_at INTEGER NOT NULL
150640
150651
  );
150641
150652
 
150642
- CREATE TABLE IF NOT EXISTS session_notes (
150643
- id INTEGER PRIMARY KEY AUTOINCREMENT,
150644
- session_id TEXT NOT NULL,
150645
- content TEXT NOT NULL,
150646
- created_at INTEGER NOT NULL
150647
- );
150653
+ -- session_notes and smart_notes were merged into the unified notes table
150654
+ -- by migration v1 (see features/magic-context/migrations.ts). The old tables
150655
+ -- are never recreated; fresh DBs create only notes, upgraded DBs have
150656
+ -- their old tables migrated and dropped by the migration runner.
150648
150657
 
150649
150658
  CREATE TABLE IF NOT EXISTS memories (
150650
150659
  id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -150708,20 +150717,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150708
150717
  );
150709
150718
  CREATE INDEX IF NOT EXISTS idx_dream_runs_project ON dream_runs(project_path, finished_at DESC);
150710
150719
 
150711
- CREATE TABLE IF NOT EXISTS smart_notes (
150712
- id INTEGER PRIMARY KEY AUTOINCREMENT,
150713
- project_path TEXT NOT NULL,
150714
- content TEXT NOT NULL,
150715
- surface_condition TEXT NOT NULL,
150716
- status TEXT NOT NULL DEFAULT 'pending',
150717
- created_session_id TEXT,
150718
- created_at INTEGER NOT NULL,
150719
- updated_at INTEGER NOT NULL,
150720
- last_checked_at INTEGER,
150721
- ready_at INTEGER,
150722
- ready_reason TEXT
150723
- );
150724
- CREATE INDEX IF NOT EXISTS idx_smart_notes_project_status ON smart_notes(project_path, status);
150720
+ -- (smart_notes: see note above; merged into unified notes table by migration v1)
150725
150721
 
150726
150722
  CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
150727
150723
  content,
@@ -150821,7 +150817,6 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150821
150817
  CREATE INDEX IF NOT EXISTS idx_session_facts_session ON session_facts(session_id);
150822
150818
  CREATE INDEX IF NOT EXISTS idx_recomp_compartments_session ON recomp_compartments(session_id);
150823
150819
  CREATE INDEX IF NOT EXISTS idx_recomp_facts_session ON recomp_facts(session_id);
150824
- CREATE INDEX IF NOT EXISTS idx_session_notes_session ON session_notes(session_id);
150825
150820
  CREATE INDEX IF NOT EXISTS idx_memories_project_status_category ON memories(project_path, status, category);
150826
150821
  CREATE INDEX IF NOT EXISTS idx_memories_project_status_expires ON memories(project_path, status, expires_at);
150827
150822
  CREATE INDEX IF NOT EXISTS idx_memories_project_category_hash ON memories(project_path, category, normalized_hash);
@@ -150867,6 +150862,8 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
150867
150862
  ensureColumn(db, "session_meta", "recomp_partial_range_end", "INTEGER DEFAULT 0");
150868
150863
  ensureColumn(db, "session_meta", "detected_context_limit", "INTEGER DEFAULT 0");
150869
150864
  ensureColumn(db, "session_meta", "needs_emergency_recovery", "INTEGER DEFAULT 0");
150865
+ }
150866
+ function healAllNullColumns(db) {
150870
150867
  healNullTextColumns(db);
150871
150868
  healNullIntegerColumns(db);
150872
150869
  healMissingMemoryBlockIds(db);
@@ -152428,6 +152425,51 @@ var init_constants = __esm(() => {
152428
152425
  };
152429
152426
  });
152430
152427
 
152428
+ // src/shared/bounded-session-map.ts
152429
+ class BoundedSessionMap {
152430
+ maxEntries;
152431
+ store = new Map;
152432
+ constructor(maxEntries) {
152433
+ if (!Number.isFinite(maxEntries) || maxEntries < 1) {
152434
+ throw new Error(`BoundedSessionMap: maxEntries must be >= 1, got ${maxEntries}`);
152435
+ }
152436
+ this.maxEntries = maxEntries;
152437
+ }
152438
+ get(sessionId) {
152439
+ const value = this.store.get(sessionId);
152440
+ if (value === undefined)
152441
+ return;
152442
+ this.store.delete(sessionId);
152443
+ this.store.set(sessionId, value);
152444
+ return value;
152445
+ }
152446
+ peek(sessionId) {
152447
+ return this.store.get(sessionId);
152448
+ }
152449
+ has(sessionId) {
152450
+ return this.store.has(sessionId);
152451
+ }
152452
+ set(sessionId, value) {
152453
+ if (this.store.has(sessionId)) {
152454
+ this.store.delete(sessionId);
152455
+ } else if (this.store.size >= this.maxEntries) {
152456
+ const oldest = this.store.keys().next().value;
152457
+ if (oldest !== undefined)
152458
+ this.store.delete(oldest);
152459
+ }
152460
+ this.store.set(sessionId, value);
152461
+ }
152462
+ delete(sessionId) {
152463
+ return this.store.delete(sessionId);
152464
+ }
152465
+ clear() {
152466
+ this.store.clear();
152467
+ }
152468
+ get size() {
152469
+ return this.store.size;
152470
+ }
152471
+ }
152472
+
152431
152473
  // src/hooks/magic-context/temporal-awareness.ts
152432
152474
  function formatGap(seconds) {
152433
152475
  if (!Number.isFinite(seconds) || seconds < TEMPORAL_AWARENESS_THRESHOLD_SECONDS) {
@@ -152774,7 +152816,7 @@ function findFirstTextPart(parts) {
152774
152816
  function isDroppedPlaceholder(text) {
152775
152817
  return /^\[dropped \u00A7\d+\u00A7\]$/.test(text.trim());
152776
152818
  }
152777
- var injectionCache, CONSTRAINT_KEYWORDS;
152819
+ var INJECTION_CACHE_MAX = 100, injectionCache, CONSTRAINT_KEYWORDS;
152778
152820
  var init_inject_compartments = __esm(() => {
152779
152821
  init_compartment_storage();
152780
152822
  init_constants();
@@ -152783,7 +152825,7 @@ var init_inject_compartments = __esm(() => {
152783
152825
  init_read_session_db();
152784
152826
  init_read_session_formatting();
152785
152827
  init_temporal_awareness();
152786
- injectionCache = new Map;
152828
+ injectionCache = new BoundedSessionMap(INJECTION_CACHE_MAX);
152787
152829
  CONSTRAINT_KEYWORDS = /\b(must|never|always|cannot|should not|must not)\b/i;
152788
152830
  });
152789
152831
 
@@ -153309,6 +153351,28 @@ function generatePartId(timestampMs, counter = 0n) {
153309
153351
  function getOpenCodeDbPath3() {
153310
153352
  return join13(getDataDir(), "opencode", "opencode.db");
153311
153353
  }
153354
+ function isOpenCodeSchemaCompatible(db, dbPath) {
153355
+ if (cachedSchemaCompatible?.path === dbPath) {
153356
+ return cachedSchemaCompatible.compatible;
153357
+ }
153358
+ try {
153359
+ const messageCols = new Set(db.prepare("PRAGMA table_info(message)").all().map((r) => r.name ?? "").filter((n) => n.length > 0));
153360
+ const partCols = new Set(db.prepare("PRAGMA table_info(part)").all().map((r) => r.name ?? "").filter((n) => n.length > 0));
153361
+ const missingMessage = REQUIRED_MESSAGE_COLUMNS.filter((c) => !messageCols.has(c));
153362
+ const missingPart = REQUIRED_PART_COLUMNS.filter((c) => !partCols.has(c));
153363
+ if (missingMessage.length > 0 || missingPart.length > 0) {
153364
+ log(`[magic-context] compaction-marker: OpenCode DB schema missing required columns ` + `(message: [${missingMessage.join(", ")}], part: [${missingPart.join(", ")}]). ` + `Marker injection disabled for this process. ` + `This usually means OpenCode was updated and magic-context is out of date.`);
153365
+ cachedSchemaCompatible = { path: dbPath, compatible: false };
153366
+ return false;
153367
+ }
153368
+ cachedSchemaCompatible = { path: dbPath, compatible: true };
153369
+ return true;
153370
+ } catch (error48) {
153371
+ log(`[magic-context] compaction-marker: schema probe failed: ${error48 instanceof Error ? error48.message : String(error48)}. ` + `Marker injection disabled until next process restart.`);
153372
+ cachedSchemaCompatible = { path: dbPath, compatible: false };
153373
+ return false;
153374
+ }
153375
+ }
153312
153376
  function getWritableOpenCodeDb() {
153313
153377
  const dbPath = getOpenCodeDbPath3();
153314
153378
  if (cachedWriteDb?.path === dbPath) {
@@ -153327,18 +153391,15 @@ function getWritableOpenCodeDb() {
153327
153391
  }
153328
153392
  function findBoundaryUserMessage(sessionId, endOrdinal) {
153329
153393
  const db = getWritableOpenCodeDb();
153330
- const rows = db.prepare("SELECT id, time_created, data FROM message WHERE session_id = ? ORDER BY time_created ASC, id ASC").all(sessionId);
153331
- const filtered = rows.filter((row) => {
153332
- try {
153333
- const info = JSON.parse(row.data);
153334
- return !(info.summary === true && info.finish === "stop");
153335
- } catch {
153336
- return true;
153337
- }
153338
- });
153394
+ const rows = db.prepare(`SELECT id, time_created, data
153395
+ FROM message
153396
+ WHERE session_id = ?
153397
+ AND NOT (COALESCE(json_extract(data, '$.summary'), 0) = 1
153398
+ AND COALESCE(json_extract(data, '$.finish'), '') = 'stop')
153399
+ ORDER BY time_created ASC, id ASC
153400
+ LIMIT ?`).all(sessionId, endOrdinal);
153339
153401
  let bestMatch = null;
153340
- for (let i = 0;i < filtered.length && i < endOrdinal; i++) {
153341
- const row = filtered[i];
153402
+ for (const row of rows) {
153342
153403
  try {
153343
153404
  const info = JSON.parse(row.data);
153344
153405
  if (info.role === "user") {
@@ -153349,12 +153410,15 @@ function findBoundaryUserMessage(sessionId, endOrdinal) {
153349
153410
  return bestMatch;
153350
153411
  }
153351
153412
  function injectCompactionMarker(args) {
153413
+ const db = getWritableOpenCodeDb();
153414
+ if (!isOpenCodeSchemaCompatible(db, getOpenCodeDbPath3())) {
153415
+ return null;
153416
+ }
153352
153417
  const boundary = findBoundaryUserMessage(args.sessionId, args.endOrdinal);
153353
153418
  if (!boundary) {
153354
153419
  log(`[magic-context] compaction-marker: no user message found at or before ordinal ${args.endOrdinal}`);
153355
153420
  return null;
153356
153421
  }
153357
- const db = getWritableOpenCodeDb();
153358
153422
  const boundaryTime = boundary.timeCreated;
153359
153423
  const summaryMsgId = generateMessageId(boundaryTime + 1, 1n);
153360
153424
  const compactionPartId = generatePartId(boundaryTime, 1n);
@@ -153405,10 +153469,19 @@ function removeCompactionMarker(state) {
153405
153469
  return false;
153406
153470
  }
153407
153471
  }
153408
- var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", cachedWriteDb = null;
153472
+ var BASE62_CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", cachedWriteDb = null, REQUIRED_MESSAGE_COLUMNS, REQUIRED_PART_COLUMNS, cachedSchemaCompatible = null;
153409
153473
  var init_compaction_marker = __esm(() => {
153410
153474
  init_data_path();
153411
153475
  init_logger();
153476
+ REQUIRED_MESSAGE_COLUMNS = ["id", "session_id", "time_created", "time_updated", "data"];
153477
+ REQUIRED_PART_COLUMNS = [
153478
+ "id",
153479
+ "message_id",
153480
+ "session_id",
153481
+ "time_created",
153482
+ "time_updated",
153483
+ "data"
153484
+ ];
153412
153485
  });
153413
153486
 
153414
153487
  // src/hooks/magic-context/compaction-marker-manager.ts
@@ -164485,7 +164558,8 @@ async function runDream(args) {
164485
164558
  smartNotesPending: result.smartNotesPending,
164486
164559
  memoryChanges: persistedMemoryChanges
164487
164560
  });
164488
- const hasSuccessfulTask = result.tasks.some((t) => !t.error && t.name !== "smart-notes");
164561
+ const POST_TASK_NAMES = new Set(["smart-notes", "user memories", "key files"]);
164562
+ const hasSuccessfulTask = result.tasks.some((t) => !t.error && !POST_TASK_NAMES.has(t.name));
164489
164563
  if (hasSuccessfulTask) {
164490
164564
  setDreamState(args.db, `last_dream_at:${args.projectIdentity}`, String(result.finishedAt));
164491
164565
  setDreamState(args.db, "last_dream_at", String(result.finishedAt));
@@ -169438,7 +169512,8 @@ function createNudgePlacementStore(db) {
169438
169512
 
169439
169513
  // src/hooks/magic-context/transform.ts
169440
169514
  init_storage_meta_persisted();
169441
- var messageTokensBySession = new Map;
169515
+ var MESSAGE_TOKENS_CACHE_MAX = 100;
169516
+ var messageTokensBySession = new BoundedSessionMap(MESSAGE_TOKENS_CACHE_MAX);
169442
169517
  function getMessageTokensCache(sessionId) {
169443
169518
  let cache = messageTokensBySession.get(sessionId);
169444
169519
  if (!cache) {
@@ -169628,11 +169703,11 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
169628
169703
  }
169629
169704
  }
169630
169705
  logTransformTiming(sessionId, "emergencyRecoveryBlock", tFirstPass);
169706
+ const projectIdentity = deps.memoryConfig?.enabled ? resolveProjectIdentity(deps.directory ?? process.cwd()) : undefined;
169631
169707
  let pendingCompartmentInjection = null;
169632
169708
  if (fullFeatureMode) {
169633
169709
  const tInj = performance.now();
169634
- const projectPath = deps.memoryConfig?.enabled ? resolveProjectIdentity(deps.directory ?? process.cwd()) : undefined;
169635
- pendingCompartmentInjection = prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectPath, deps.memoryConfig?.injectionBudgetTokens, deps.experimentalTemporalAwareness);
169710
+ pendingCompartmentInjection = prepareCompartmentInjection(db, sessionId, messages, isCacheBusting, projectIdentity, deps.memoryConfig?.injectionBudgetTokens, deps.experimentalTemporalAwareness);
169636
169711
  logTransformTiming(sessionId, "prepareCompartmentInjection", tInj);
169637
169712
  }
169638
169713
  let targets = new Map;
@@ -169747,7 +169822,7 @@ Historian previously failed ${historianFailureState.failureCount} time(s), so ma
169747
169822
  messages,
169748
169823
  pendingCompartmentInjection,
169749
169824
  fallbackModelId,
169750
- projectPath: deps.memoryConfig?.enabled ? resolveProjectIdentity(deps.directory ?? process.cwd()) : undefined,
169825
+ projectPath: projectIdentity,
169751
169826
  injectionBudgetTokens: deps.memoryConfig?.injectionBudgetTokens,
169752
169827
  getNotificationParams: rawGetNotifParams ? () => rawGetNotifParams(sessionId) : undefined,
169753
169828
  cacheAlreadyBusting: isCacheBusting || schedulerDecisionEarly === "execute",
@@ -170539,7 +170614,7 @@ Use \`ctx_memory\` to manage cross-session project memories. Write new memories
170539
170614
  - Discovered a non-obvious build/test command \u2192 \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
170540
170615
  - Learned a constraint the hard way \u2192 \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
170541
170616
  Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
170542
- Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start=N end=M>\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
170617
+ Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
170543
170618
  **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
170544
170619
  - Can't remember where a related codebase or dependency lives \u2192 \`ctx_search(query="opencode source code path")\`
170545
170620
  - Forgot a prior architectural decision or constraint \u2192 \`ctx_search(query="why did we choose SQLite over postgres")\`
@@ -170552,15 +170627,14 @@ NEVER drop large ranges blindly (e.g., "1-50"). Review each tag before deciding.
170552
170627
  NEVER drop user messages \u2014 they are short and will be summarized by compartmentalization automatically. Dropping them loses context the historian needs.
170553
170628
  NEVER drop assistant text messages unless they are exceptionally large. Your conversation messages are lightweight; only large tool outputs are worth dropping.
170554
170629
  Before your turn finishes, consider using \`ctx_reduce\` to drop large tool outputs you no longer need.`;
170555
- var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `Messages and tool outputs are tagged with \xA7N\xA7 identifiers (e.g., \xA71\xA7, \xA742\xA7).
170556
- Use \`ctx_note\` for deferred intentions \u2014 things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
170630
+ var BASE_INTRO_NO_REDUCE = (dropToolStructure) => `Use \`ctx_note\` for deferred intentions \u2014 things to tackle later, not right now. NOT for task tracking (use todos). Notes survive context compression and you'll be reminded at natural work boundaries (after commits, historian runs, todo completion).
170557
170631
  Use \`ctx_memory\` to manage cross-session project memories. Write new memories or delete stale ones. Memories persist across sessions and are automatically injected into new sessions.
170558
170632
  **Save to memory proactively**: If you spent multiple turns finding something (a file path, a DB location, a config pattern, a workaround), save it with \`ctx_memory\` so future sessions don't repeat the search. Examples:
170559
170633
  - Found a project's source code path after searching \u2192 \`ctx_memory(action="write", category="ENVIRONMENT", content="OpenCode source is at ~/Work/OSS/opencode")\`
170560
170634
  - Discovered a non-obvious build/test command \u2192 \`ctx_memory(action="write", category="WORKFLOW_RULES", content="Always use scripts/release.sh for releases")\`
170561
170635
  - Learned a constraint the hard way \u2192 \`ctx_memory(action="write", category="CONSTRAINTS", content="Dashboard Tauri build needs RGBA PNGs, not grayscale")\`
170562
170636
  Use \`ctx_search\` to search across project memories, session facts, and conversation history from one query.
170563
- Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start=N end=M>\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
170637
+ Use \`ctx_expand\` to decompress a compartment range to see the original conversation transcript. Use \`start\`/\`end\` from \`<compartment start="N" end="M">\` attributes. Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.
170564
170638
  **Search before asking the user**: If you can't remember or don't know something that might have been discussed before or stored in project memory, use \`ctx_search\` before asking the user. Examples:
170565
170639
  - Can't remember where a related codebase or dependency lives \u2192 \`ctx_search(query="opencode source code path")\`
170566
170640
  - Forgot a prior architectural decision or constraint \u2192 \`ctx_search(query="why did we choose SQLite over postgres")\`
@@ -170924,13 +170998,10 @@ ${sections.join(`
170924
170998
  if (systemContent.length === 0)
170925
170999
  return;
170926
171000
  const currentHash = new Bun.CryptoHasher("md5").update(systemContent).digest("hex");
170927
- let sessionMeta;
170928
- try {
170929
- sessionMeta = getOrCreateSessionMeta(deps.db, sessionId);
170930
- } catch (error48) {
170931
- sessionLog(sessionId, "system-prompt-hash DB update failed:", error48);
171001
+ if (!sessionMetaEarly) {
170932
171002
  return;
170933
171003
  }
171004
+ const sessionMeta = sessionMetaEarly;
170934
171005
  const previousHash = sessionMeta.systemPromptHash;
170935
171006
  if (previousHash !== "" && previousHash !== "0" && previousHash !== currentHash) {
170936
171007
  sessionLog(sessionId, `system prompt hash changed: ${previousHash} \u2192 ${currentHash} (len=${systemContent.length}), triggering flush`);
@@ -171743,7 +171814,7 @@ init_read_session_chunk();
171743
171814
  import { tool } from "@opencode-ai/plugin";
171744
171815
 
171745
171816
  // src/tools/ctx-expand/constants.ts
171746
- var CTX_EXPAND_DESCRIPTION = "Decompress a compartment range to see the original conversation transcript. " + "Use start/end from <compartment start=N end=M> attributes. " + "Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.";
171817
+ var CTX_EXPAND_DESCRIPTION = "Decompress a compartment range to see the original conversation transcript. " + 'Use start/end from <compartment start="N" end="M"> attributes. ' + "Returns the compacted U:/A: transcript for that message range, capped at ~15K tokens.";
171747
171818
  var CTX_EXPAND_TOKEN_BUDGET = 15000;
171748
171819
 
171749
171820
  // src/tools/ctx-expand/tools.ts
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Bounded LRU map keyed by session id.
3
+ *
4
+ * Rationale: magic-context maintains several module-scope Maps that track
5
+ * per-session state (prepared injection cache, per-message token cache, etc.).
6
+ * These are cleared on the `session.deleted` event, but sessions that are
7
+ * never explicitly deleted — because OpenCode crashed, the user force-quit,
8
+ * the session was archived rather than deleted, or the session simply outlived
9
+ * the plugin process's interest in it — leak entries for the lifetime of the
10
+ * plugin process.
11
+ *
12
+ * In long-running OpenCode instances with thousands of sessions over time,
13
+ * an unbounded `Map<sessionId, LargeObject>` can retain tens of megabytes
14
+ * indefinitely. A session-scoped LRU with a generous cap (e.g. 100) covers
15
+ * any realistic working-set of active sessions a user actually cares about,
16
+ * while evicting cold session ids that will either never return or be
17
+ * rebuilt from durable SQLite state on their next transform pass.
18
+ *
19
+ * Implementation notes:
20
+ * - Built on `Map` which preserves insertion order. On every `set`/`get`
21
+ * touch we delete+reinsert to move the key to the tail (most-recent).
22
+ * - Eviction drops the oldest entry (first in iteration order).
23
+ * - The cached value type is generic — callers decide what per-session state
24
+ * to store. For injection/token state, all three properties of the cached
25
+ * object are safe to throw away: they are either recomputable from the
26
+ * messages array on the next pass, or reloadable from SQLite.
27
+ */
28
+ export declare class BoundedSessionMap<V> {
29
+ private readonly maxEntries;
30
+ private readonly store;
31
+ constructor(maxEntries: number);
32
+ get(sessionId: string): V | undefined;
33
+ /**
34
+ * Peek without touching recency — useful for `has`-style checks that
35
+ * should not rearrange LRU order. Use sparingly; `get` is the normal
36
+ * access path.
37
+ */
38
+ peek(sessionId: string): V | undefined;
39
+ has(sessionId: string): boolean;
40
+ set(sessionId: string, value: V): void;
41
+ delete(sessionId: string): boolean;
42
+ clear(): void;
43
+ get size(): number;
44
+ }
45
+ //# sourceMappingURL=bounded-session-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bounded-session-map.d.ts","sourceRoot":"","sources":["../../src/shared/bounded-session-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,iBAAiB,CAAC,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAwB;gBAElC,UAAU,EAAE,MAAM;IAO9B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IASrC;;;;OAIG;IACH,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAItC,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAI/B,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAYtC,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;IAIlC,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,97 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { BoundedSessionMap } from "./bounded-session-map";
3
+
4
+ describe("BoundedSessionMap", () => {
5
+ it("rejects non-positive caps", () => {
6
+ expect(() => new BoundedSessionMap(0)).toThrow();
7
+ expect(() => new BoundedSessionMap(-5)).toThrow();
8
+ expect(() => new BoundedSessionMap(Number.NaN)).toThrow();
9
+ });
10
+
11
+ it("stores and retrieves values", () => {
12
+ const map = new BoundedSessionMap<number>(3);
13
+ map.set("a", 1);
14
+ map.set("b", 2);
15
+ expect(map.get("a")).toBe(1);
16
+ expect(map.get("b")).toBe(2);
17
+ expect(map.get("missing")).toBeUndefined();
18
+ expect(map.size).toBe(2);
19
+ });
20
+
21
+ it("evicts the oldest entry when cap is exceeded", () => {
22
+ const map = new BoundedSessionMap<string>(3);
23
+ map.set("a", "alpha");
24
+ map.set("b", "bravo");
25
+ map.set("c", "charlie");
26
+ map.set("d", "delta"); // evicts "a"
27
+ expect(map.has("a")).toBe(false);
28
+ expect(map.has("b")).toBe(true);
29
+ expect(map.has("c")).toBe(true);
30
+ expect(map.has("d")).toBe(true);
31
+ expect(map.size).toBe(3);
32
+ });
33
+
34
+ it("treats get() as a touch for LRU ordering", () => {
35
+ const map = new BoundedSessionMap<string>(3);
36
+ map.set("a", "alpha");
37
+ map.set("b", "bravo");
38
+ map.set("c", "charlie");
39
+ // Touch "a" — now "b" is the oldest.
40
+ expect(map.get("a")).toBe("alpha");
41
+ map.set("d", "delta");
42
+ expect(map.has("b")).toBe(false);
43
+ expect(map.has("a")).toBe(true);
44
+ expect(map.has("c")).toBe(true);
45
+ expect(map.has("d")).toBe(true);
46
+ });
47
+
48
+ it("peek() does NOT touch recency", () => {
49
+ const map = new BoundedSessionMap<number>(3);
50
+ map.set("a", 1);
51
+ map.set("b", 2);
52
+ map.set("c", 3);
53
+ expect(map.peek("a")).toBe(1);
54
+ // Adding a fourth entry should still evict "a" since peek didn't touch it.
55
+ map.set("d", 4);
56
+ expect(map.has("a")).toBe(false);
57
+ });
58
+
59
+ it("set() on existing key refreshes recency without growing size", () => {
60
+ const map = new BoundedSessionMap<number>(3);
61
+ map.set("a", 1);
62
+ map.set("b", 2);
63
+ map.set("c", 3);
64
+ map.set("a", 100); // refresh "a" to most-recent with new value
65
+ expect(map.size).toBe(3);
66
+ expect(map.get("a")).toBe(100);
67
+ map.set("d", 4); // evicts "b" (now oldest)
68
+ expect(map.has("b")).toBe(false);
69
+ expect(map.has("a")).toBe(true);
70
+ });
71
+
72
+ it("delete() removes entries and returns true when present", () => {
73
+ const map = new BoundedSessionMap<number>(3);
74
+ map.set("a", 1);
75
+ expect(map.delete("a")).toBe(true);
76
+ expect(map.delete("a")).toBe(false);
77
+ expect(map.size).toBe(0);
78
+ });
79
+
80
+ it("clear() drops all entries", () => {
81
+ const map = new BoundedSessionMap<number>(3);
82
+ map.set("a", 1);
83
+ map.set("b", 2);
84
+ map.clear();
85
+ expect(map.size).toBe(0);
86
+ expect(map.get("a")).toBeUndefined();
87
+ });
88
+
89
+ it("tolerates cap=1 edge case (every set evicts previous)", () => {
90
+ const map = new BoundedSessionMap<number>(1);
91
+ map.set("a", 1);
92
+ map.set("b", 2);
93
+ expect(map.has("a")).toBe(false);
94
+ expect(map.get("b")).toBe(2);
95
+ expect(map.size).toBe(1);
96
+ });
97
+ });
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Bounded LRU map keyed by session id.
3
+ *
4
+ * Rationale: magic-context maintains several module-scope Maps that track
5
+ * per-session state (prepared injection cache, per-message token cache, etc.).
6
+ * These are cleared on the `session.deleted` event, but sessions that are
7
+ * never explicitly deleted — because OpenCode crashed, the user force-quit,
8
+ * the session was archived rather than deleted, or the session simply outlived
9
+ * the plugin process's interest in it — leak entries for the lifetime of the
10
+ * plugin process.
11
+ *
12
+ * In long-running OpenCode instances with thousands of sessions over time,
13
+ * an unbounded `Map<sessionId, LargeObject>` can retain tens of megabytes
14
+ * indefinitely. A session-scoped LRU with a generous cap (e.g. 100) covers
15
+ * any realistic working-set of active sessions a user actually cares about,
16
+ * while evicting cold session ids that will either never return or be
17
+ * rebuilt from durable SQLite state on their next transform pass.
18
+ *
19
+ * Implementation notes:
20
+ * - Built on `Map` which preserves insertion order. On every `set`/`get`
21
+ * touch we delete+reinsert to move the key to the tail (most-recent).
22
+ * - Eviction drops the oldest entry (first in iteration order).
23
+ * - The cached value type is generic — callers decide what per-session state
24
+ * to store. For injection/token state, all three properties of the cached
25
+ * object are safe to throw away: they are either recomputable from the
26
+ * messages array on the next pass, or reloadable from SQLite.
27
+ */
28
+ export class BoundedSessionMap<V> {
29
+ private readonly maxEntries: number;
30
+ private readonly store = new Map<string, V>();
31
+
32
+ constructor(maxEntries: number) {
33
+ if (!Number.isFinite(maxEntries) || maxEntries < 1) {
34
+ throw new Error(`BoundedSessionMap: maxEntries must be >= 1, got ${maxEntries}`);
35
+ }
36
+ this.maxEntries = maxEntries;
37
+ }
38
+
39
+ get(sessionId: string): V | undefined {
40
+ const value = this.store.get(sessionId);
41
+ if (value === undefined) return undefined;
42
+ // Touch: move to most-recent position.
43
+ this.store.delete(sessionId);
44
+ this.store.set(sessionId, value);
45
+ return value;
46
+ }
47
+
48
+ /**
49
+ * Peek without touching recency — useful for `has`-style checks that
50
+ * should not rearrange LRU order. Use sparingly; `get` is the normal
51
+ * access path.
52
+ */
53
+ peek(sessionId: string): V | undefined {
54
+ return this.store.get(sessionId);
55
+ }
56
+
57
+ has(sessionId: string): boolean {
58
+ return this.store.has(sessionId);
59
+ }
60
+
61
+ set(sessionId: string, value: V): void {
62
+ if (this.store.has(sessionId)) {
63
+ // Refresh recency.
64
+ this.store.delete(sessionId);
65
+ } else if (this.store.size >= this.maxEntries) {
66
+ // Evict oldest entry. Map iteration is insertion-ordered.
67
+ const oldest = this.store.keys().next().value;
68
+ if (oldest !== undefined) this.store.delete(oldest);
69
+ }
70
+ this.store.set(sessionId, value);
71
+ }
72
+
73
+ delete(sessionId: string): boolean {
74
+ return this.store.delete(sessionId);
75
+ }
76
+
77
+ clear(): void {
78
+ this.store.clear();
79
+ }
80
+
81
+ get size(): number {
82
+ return this.store.size;
83
+ }
84
+ }