@cortexkit/opencode-magic-context 0.13.0 → 0.13.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
@@ -335,7 +335,7 @@ On startup, Magic Context checks for common configuration problems — OpenCode'
335
335
  A companion desktop app for browsing and managing Magic Context state outside of OpenCode.
336
336
 
337
337
  <p align="center">
338
- <a href="https://github.com/cortexkit/opencode-magic-context/releases/tag/dashboard-v0.2.8"><strong>⬇️ Download for macOS · Windows · Linux</strong></a></p>
338
+ <a href="https://github.com/cortexkit/opencode-magic-context/releases/tag/dashboard-v0.3.0"><strong>⬇️ Download for macOS · Windows · Linux</strong></a></p>
339
339
 
340
340
  **Features:**
341
341
  - **Memory Browser** — search, filter, and edit project memories with category and project filtering
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,EAEL,GAAG,EACH,IAAI,EACJ,KAAK,EAEL,OAAO,EACV,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAS5C,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlF;AAED,wBAAsB,IAAI,CACtB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACL,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC/C,GACP,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,wBAAsB,SAAS,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,GACnE,OAAO,CAAC,MAAM,CAAC,CAWjB"}
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,EAEL,GAAG,EACH,IAAI,EACJ,KAAK,EAEL,OAAO,EACV,MAAM,gBAAgB,CAAC;AAExB,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAiC5C,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlF;AAED,wBAAsB,IAAI,CACtB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IACL,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC/C,GACP,OAAO,CAAC,MAAM,CAAC,CAejB;AAED,wBAAsB,SAAS,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,GACnE,OAAO,CAAC,MAAM,CAAC,CAWjB"}
@@ -8,10 +8,29 @@ export declare function embedText(text: string): Promise<Float32Array | null>;
8
8
  export declare function embedBatch(texts: string[]): Promise<(Float32Array | null)[]>;
9
9
  export declare function embedUnembeddedMemories(db: Database, projectPath: string, config: EmbeddingConfig, batchSize?: number): Promise<number>;
10
10
  /**
11
- * Sweep ALL projects for unembedded memories, not just one project.
12
- * Used by the dream timer to ensure cross-project embedding coverage.
11
+ * Sweep ALL projects for unembedded memories, draining each fully before
12
+ * moving to the next. Projects are ordered by most-recent memory activity
13
+ * (MAX(updated_at)), so the project you're actively working in gets
14
+ * embedded first after a provider switch.
15
+ *
16
+ * Within one invocation:
17
+ * - Each project is drained in batches of `batchSize` until `embedUnembeddedMemoriesForProject`
18
+ * returns 0 (nothing left, or a batch failure).
19
+ * - Wall-clock deadline + consecutive-empty fail-safe prevent runaway
20
+ * or infinite looping when the provider is unhealthy.
21
+ *
22
+ * Between invocations:
23
+ * - The module-level `sweepInProgress` flag guards against parallel runs
24
+ * from the same process. If a sweep is still running when the next
25
+ * 15-min tick fires, that tick is a no-op.
26
+ *
27
+ * Used by the dream timer for proactive embedding coverage. After a
28
+ * provider change wipes embeddings, this path drains the full backlog on
29
+ * a single tick (bounded by wall clock) instead of trickling 10/15min.
13
30
  */
14
31
  export declare function embedAllUnembeddedMemories(db: Database, config: EmbeddingConfig, batchSize?: number): Promise<number>;
32
+ /** Test-only: reset the in-progress guard. */
33
+ export declare function _resetEmbeddingSweepGuard(): void;
15
34
  export declare function getEmbeddingModelId(): string;
16
35
  export { cosineSimilarity };
17
36
  export declare function disposeEmbeddingModel(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAG5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AA0GvD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAmBjE;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAW1E;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC,CAelF;AAED,wBAAsB,uBAAuB,CACzC,EAAE,EAAE,QAAQ,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,EACvB,SAAS,SAAK,GACf,OAAO,CAAC,MAAM,CAAC,CAEjB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAC5C,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,eAAe,EACvB,SAAS,SAAK,GACf,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAmDD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3D"}
1
+ {"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAG5E,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AA0GvD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAmBjE;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAW1E;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC,CAelF;AAED,wBAAsB,uBAAuB,CACzC,EAAE,EAAE,QAAQ,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,EACvB,SAAS,SAAK,GACf,OAAO,CAAC,MAAM,CAAC,CAEjB;AAgBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,0BAA0B,CAC5C,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,eAAe,EACvB,SAAS,SAAK,GACf,OAAO,CAAC,MAAM,CAAC,CAsEjB;AAED,8CAA8C;AAC9C,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAmDD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3D"}
@@ -22,7 +22,6 @@ interface ScoredCompartment {
22
22
  index: number;
23
23
  tokenEstimate: number;
24
24
  averageDepth: number;
25
- score: number;
26
25
  }
27
26
  /**
28
27
  * Check if the compartment block exceeds the history budget and run a compression pass if needed.
@@ -40,23 +39,44 @@ interface SelectionConstraints {
40
39
  floorHeadroom: number;
41
40
  }
42
41
  /**
43
- * Find the oldest contiguous band of compartments that share the same rounded depth.
42
+ * Pick a contiguous same-depth band of compartments to compress next.
44
43
  *
45
- * Strategy: scan oldest→newest (low index first). Skip compartments at max depth,
46
- * and skip the newest `graceCompartments` (grace period). Within the remaining
47
- * scope, find the oldest run of 2+ consecutive compartments with the SAME rounded
48
- * averageDepth. This keeps per-pass work uniform (same LLM prompt tier for all
49
- * inputs) and naturally progresses: depth 0 bands get compressed first, producing
50
- * depth 1 bands, which compress next, etc.
44
+ * Strategy (depth-first, oldest-within-tier):
45
+ * 1. Eligible scope = [0, scored.length - graceCompartments).
46
+ * Newest `graceCompartments` are never compressed (protects just-published
47
+ * historian output).
48
+ * 2. Within eligible scope, ignore compartments whose rounded depth is
49
+ * already at `maxMergeDepth` they're done.
50
+ * 3. Find the **minimum** depth tier that still exists in scope.
51
+ * 4. Anchor on the **oldest** compartment at that minimum depth (lowest
52
+ * index). Extend forward while the next compartment has the same rounded
53
+ * depth, stopping at maxPickable / floorHeadroom / scope end.
54
+ * 5. Require runLen ≥ 2. If the oldest minimum-depth compartment can't form
55
+ * a run (neighbor has a different depth), the algorithm would stall —
56
+ * so fall back to finding the *next* oldest compartment at minDepth and
57
+ * retry. This preserves the old "skip singleton and move on" safety
58
+ * without abandoning the min-depth invariant.
51
59
  *
52
- * Constraints:
53
- * - Skip compartments with averageDepth >= maxMergeDepth (already maxed out).
54
- * - Skip the newest graceCompartments (never compress fresh work).
55
- * - Cap picks at maxPickable to avoid huge LLM inputs.
56
- * - Cap picks at floorHeadroom to avoid violating min-compartment floor. Each
57
- * merge reduces count by (input - output), so limiting picks to floorHeadroom
58
- * guarantees we can't fall below floor even in the worst case (output = 1).
60
+ * Why this shape:
61
+ * The previous algorithm was oldest-first regardless of depth. After the
62
+ * first pass compressed seq 0-14 to depth 1, the next pass picked seq 0-X
63
+ * AGAIN because they were still the oldest. The cascade ran depth 0→1→2→
64
+ * 3→4→5 on the same range within hours, crushing early compartments to
65
+ * empty title-only shells while the rest of history stayed at depth 0.
66
+ *
67
+ * Depth-first selection means: once seq 0-14 reach depth 1, the next pass
68
+ * prefers any depth-0 band elsewhere (seq 15+) before touching seq 0-14
69
+ * again. Old→new gets pushed down one tier at a time, producing a smooth
70
+ * depth gradient (old = deeper, recent = shallower) like memory decay.
71
+ *
72
+ * Grace window still protects the newest N from compression entirely so
73
+ * freshly-published historian output has time to settle.
74
+ */
75
+ export declare function selectCompressionBand(scored: ScoredCompartment[], constraints: SelectionConstraints): ScoredCompartment[];
76
+ /**
77
+ * @deprecated Use {@link selectCompressionBand}. Kept as an export for the
78
+ * existing test suite that targets the older naming; semantics are identical.
59
79
  */
60
- export declare function findOldestContiguousSameDepthBand(scored: ScoredCompartment[], constraints: SelectionConstraints): ScoredCompartment[];
80
+ export declare const findOldestContiguousSameDepthBand: typeof selectCompressionBand;
61
81
  export {};
62
82
  //# sourceMappingURL=compartment-runner-compressor.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"compartment-runner-compressor.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/compartment-runner-compressor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAS3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAC;AAQpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAaxD,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,EAAE,EAAE,QAAQ,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAaD,UAAU,iBAAiB;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAqKvF;AAoCD,UAAU,oBAAoB;IAC1B,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,aAAa,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iCAAiC,CAC7C,MAAM,EAAE,iBAAiB,EAAE,EAC3B,WAAW,EAAE,oBAAoB,GAClC,iBAAiB,EAAE,CA6CrB"}
1
+ {"version":3,"file":"compartment-runner-compressor.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/compartment-runner-compressor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAS3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAC;AAQpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAkBxD,MAAM,WAAW,cAAc;IAC3B,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,EAAE,EAAE,QAAQ,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,2EAA2E;IAC3E,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,mEAAmE;IACnE,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wEAAwE;IACxE,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAaD,UAAU,iBAAiB;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAyLvF;AAiCD,UAAU,oBAAoB;IAC1B,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,2DAA2D;IAC3D,aAAa,EAAE,MAAM,CAAC;IACtB,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uFAAuF;IACvF,aAAa,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,qBAAqB,CACjC,MAAM,EAAE,iBAAiB,EAAE,EAC3B,WAAW,EAAE,oBAAoB,GAClC,iBAAiB,EAAE,CA4DrB;AAED;;;GAGG;AACH,eAAO,MAAM,iCAAiC,8BAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -16871,19 +16871,52 @@ async function embedBatch(texts) {
16871
16871
  return currentProvider.embedBatch(texts);
16872
16872
  }
16873
16873
  async function embedAllUnembeddedMemories(db, config2, batchSize = 10) {
16874
- const resolvedConfig = resolveEmbeddingConfig(config2);
16875
- if (resolvedConfig.provider === "off")
16874
+ if (sweepInProgress) {
16875
+ log("[magic-context] embedding sweep already in progress, skipping this tick");
16876
16876
  return 0;
16877
- const projects = db.prepare(`SELECT DISTINCT m.project_path FROM memories m
16878
- WHERE m.status IN ('active', 'permanent')
16879
- AND m.id NOT IN (SELECT memory_id FROM memory_embeddings)
16880
- LIMIT 20`).all();
16881
- let total = 0;
16882
- for (const row of projects) {
16883
- const count = await embedUnembeddedMemoriesForProject(db, row.project_path, config2, batchSize);
16884
- total += count;
16885
16877
  }
16886
- return total;
16878
+ sweepInProgress = true;
16879
+ const startedAt = Date.now();
16880
+ const deadline = startedAt + SWEEP_MAX_WALL_CLOCK_MS;
16881
+ try {
16882
+ const resolvedConfig = resolveEmbeddingConfig(config2);
16883
+ if (resolvedConfig.provider === "off")
16884
+ return 0;
16885
+ const projects = db.prepare(`SELECT m.project_path, MAX(m.updated_at) AS latest
16886
+ FROM memories m
16887
+ WHERE m.status IN ('active', 'permanent')
16888
+ AND m.id NOT IN (SELECT memory_id FROM memory_embeddings)
16889
+ GROUP BY m.project_path
16890
+ ORDER BY latest DESC
16891
+ LIMIT 20`).all();
16892
+ let total = 0;
16893
+ let consecutiveEmpty = 0;
16894
+ outer:
16895
+ for (const project of projects) {
16896
+ while (Date.now() < deadline) {
16897
+ const count = await embedUnembeddedMemoriesForProject(db, project.project_path, config2, batchSize);
16898
+ if (count === 0) {
16899
+ consecutiveEmpty += 1;
16900
+ if (consecutiveEmpty >= SWEEP_MAX_CONSECUTIVE_EMPTY) {
16901
+ log(`[magic-context] embedding sweep: ${SWEEP_MAX_CONSECUTIVE_EMPTY} consecutive empty batches, stopping (total=${total})`);
16902
+ break outer;
16903
+ }
16904
+ break;
16905
+ }
16906
+ consecutiveEmpty = 0;
16907
+ total += count;
16908
+ if (count < batchSize)
16909
+ break;
16910
+ }
16911
+ if (Date.now() >= deadline) {
16912
+ log(`[magic-context] embedding sweep: wall-clock deadline reached after ${((Date.now() - startedAt) / 1000).toFixed(1)}s (total=${total})`);
16913
+ break;
16914
+ }
16915
+ }
16916
+ return total;
16917
+ } finally {
16918
+ sweepInProgress = false;
16919
+ }
16887
16920
  }
16888
16921
  async function embedUnembeddedMemoriesForProject(db, projectPath, config2, batchSize = 10) {
16889
16922
  const normalizedBatchSize = Math.max(1, Math.floor(batchSize));
@@ -16922,7 +16955,7 @@ async function embedUnembeddedMemoriesForProject(db, projectPath, config2, batch
16922
16955
  function getEmbeddingModelId() {
16923
16956
  return getOrCreateProvider()?.modelId ?? "off";
16924
16957
  }
16925
- var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMemoriesStatements;
16958
+ var DEFAULT_EMBEDDING_CONFIG, embeddingConfig, provider = null, loadUnembeddedMemoriesStatements, SWEEP_MAX_WALL_CLOCK_MS, SWEEP_MAX_CONSECUTIVE_EMPTY = 3, sweepInProgress = false;
16926
16959
  var init_embedding = __esm(() => {
16927
16960
  init_magic_context();
16928
16961
  init_logger();
@@ -16935,6 +16968,7 @@ var init_embedding = __esm(() => {
16935
16968
  };
16936
16969
  embeddingConfig = DEFAULT_EMBEDDING_CONFIG;
16937
16970
  loadUnembeddedMemoriesStatements = new WeakMap;
16971
+ SWEEP_MAX_WALL_CLOCK_MS = 10 * 60 * 1000;
16938
16972
  });
16939
16973
 
16940
16974
  // src/features/magic-context/compression-depth-storage.ts
@@ -153507,12 +153541,23 @@ ${c.content}
153507
153541
  const maxCompartmentsPerPass = deps.maxCompartmentsPerPass ?? DEFAULT_COMPRESSOR_MAX_COMPARTMENTS_PER_PASS;
153508
153542
  const graceCompartments = deps.graceCompartments ?? DEFAULT_COMPRESSOR_GRACE_COMPARTMENTS;
153509
153543
  const scored = scoreCompartments(db, sessionId, compartments);
153544
+ const depthHistogram = new Map;
153545
+ for (const s of scored) {
153546
+ const bucket = Math.round(s.averageDepth);
153547
+ depthHistogram.set(bucket, (depthHistogram.get(bucket) ?? 0) + 1);
153548
+ }
153549
+ const histText = [...depthHistogram.entries()].sort((a, b) => a[0] - b[0]).map(([d, n]) => `d${d}=${n}`).join(" ");
153550
+ const histKey = `${scored.length}|${histText}`;
153551
+ if (lastDepthHistogramBySession.get(sessionId) !== histKey) {
153552
+ lastDepthHistogramBySession.set(sessionId, histKey);
153553
+ sessionLog(sessionId, `compressor: depth histogram (${scored.length} total) ${histText}`);
153554
+ }
153510
153555
  const floorHeadroom = compartments.length - floor;
153511
153556
  if (floorHeadroom < 1) {
153512
153557
  sessionLog(sessionId, `compressor: no floor headroom (${compartments.length} compartments, floor=${floor}), skipping`);
153513
153558
  return false;
153514
153559
  }
153515
- const contiguous = findOldestContiguousSameDepthBand(scored, {
153560
+ const contiguous = selectCompressionBand(scored, {
153516
153561
  maxPickable: maxCompartmentsPerPass,
153517
153562
  maxMergeDepth,
153518
153563
  graceCompartments,
@@ -153588,25 +153633,16 @@ ${c.content}
153588
153633
  }
153589
153634
  }
153590
153635
  function scoreCompartments(db, sessionId, compartments) {
153591
- let maxDepthAcrossSession = 0;
153592
- for (const c of compartments) {
153593
- const d = getAverageCompressionDepth(db, sessionId, c.startMessage, c.endMessage);
153594
- if (d > maxDepthAcrossSession)
153595
- maxDepthAcrossSession = d;
153596
- }
153597
153636
  return compartments.map((compartment, index) => {
153598
153637
  const tokenEstimate = estimateTokens(`<compartment start="${compartment.startMessage}" end="${compartment.endMessage}" title="${compartment.title}">
153599
153638
  ${compartment.content}
153600
153639
  </compartment>
153601
153640
  `);
153602
153641
  const averageDepth = getAverageCompressionDepth(db, sessionId, compartment.startMessage, compartment.endMessage);
153603
- const normalizedAge = compartments.length > 1 ? 1 - index / (compartments.length - 1) : 1;
153604
- const normalizedDepth = maxDepthAcrossSession > 0 ? 1 - averageDepth / maxDepthAcrossSession : 1;
153605
- const score = 0.7 * normalizedAge + 0.3 * normalizedDepth;
153606
- return { compartment, index, tokenEstimate, averageDepth, score };
153642
+ return { compartment, index, tokenEstimate, averageDepth };
153607
153643
  });
153608
153644
  }
153609
- function findOldestContiguousSameDepthBand(scored, constraints) {
153645
+ function selectCompressionBand(scored, constraints) {
153610
153646
  const { maxPickable, maxMergeDepth, graceCompartments, floorHeadroom } = constraints;
153611
153647
  const hardMaxPick = Math.max(0, Math.min(maxPickable, floorHeadroom));
153612
153648
  if (hardMaxPick < 2)
@@ -153614,32 +153650,49 @@ function findOldestContiguousSameDepthBand(scored, constraints) {
153614
153650
  const scanEnd = Math.max(0, scored.length - graceCompartments);
153615
153651
  if (scanEnd < 2)
153616
153652
  return [];
153617
- let i = 0;
153618
- while (i < scanEnd) {
153619
- const c = scored[i];
153620
- if (!c || c.averageDepth >= maxMergeDepth) {
153621
- i++;
153653
+ const tiers = new Set;
153654
+ for (let i = 0;i < scanEnd; i++) {
153655
+ const entry = scored[i];
153656
+ if (!entry)
153622
153657
  continue;
153658
+ if (entry.averageDepth >= maxMergeDepth)
153659
+ continue;
153660
+ tiers.add(Math.round(entry.averageDepth));
153661
+ }
153662
+ if (tiers.size === 0)
153663
+ return [];
153664
+ const orderedTiers = [...tiers].sort((a, b) => a - b);
153665
+ for (const targetDepth of orderedTiers) {
153666
+ let i = 0;
153667
+ while (i < scanEnd) {
153668
+ const anchor = scored[i];
153669
+ if (!anchor) {
153670
+ i++;
153671
+ continue;
153672
+ }
153673
+ if (anchor.averageDepth >= maxMergeDepth || Math.round(anchor.averageDepth) !== targetDepth) {
153674
+ i++;
153675
+ continue;
153676
+ }
153677
+ let j = i;
153678
+ while (j < scanEnd) {
153679
+ const entry = scored[j];
153680
+ if (!entry)
153681
+ break;
153682
+ if (entry.averageDepth >= maxMergeDepth)
153683
+ break;
153684
+ if (Math.round(entry.averageDepth) !== targetDepth)
153685
+ break;
153686
+ if (j - i >= hardMaxPick)
153687
+ break;
153688
+ j++;
153689
+ }
153690
+ const runLen = j - i;
153691
+ if (runLen >= 2) {
153692
+ return scored.slice(i, j);
153693
+ }
153694
+ i = Math.max(j, i + 1);
153623
153695
  }
153624
- const anchorDepth = Math.round(c.averageDepth);
153625
- let j = i;
153626
- while (j < scanEnd) {
153627
- const entry = scored[j];
153628
- if (!entry)
153629
- break;
153630
- if (entry.averageDepth >= maxMergeDepth)
153631
- break;
153632
- if (Math.round(entry.averageDepth) !== anchorDepth)
153633
- break;
153634
- if (j - i >= hardMaxPick)
153635
- break;
153636
- j++;
153637
- }
153638
- const runLen = j - i;
153639
- if (runLen >= 2) {
153640
- return scored.slice(i, j);
153641
- }
153642
- i = Math.max(j, i + 1);
153643
153696
  }
153644
153697
  return [];
153645
153698
  }
@@ -153823,7 +153876,7 @@ async function runCompressorPass(args) {
153823
153876
  }
153824
153877
  }
153825
153878
  }
153826
- var HISTORIAN_AGENT2 = "historian";
153879
+ var HISTORIAN_AGENT2 = "historian", lastDepthHistogramBySession;
153827
153880
  var init_compartment_runner_compressor = __esm(() => {
153828
153881
  init_magic_context();
153829
153882
  init_storage();
@@ -153834,6 +153887,7 @@ var init_compartment_runner_compressor = __esm(() => {
153834
153887
  init_compartment_parser();
153835
153888
  init_compartment_prompt();
153836
153889
  init_read_session_formatting();
153890
+ lastDepthHistogramBySession = new Map;
153837
153891
  });
153838
153892
 
153839
153893
  // src/hooks/magic-context/compartment-runner-drop-queue.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.13.0",
3
+ "version": "0.13.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",