@cortexkit/opencode-magic-context 0.15.2 → 0.15.4

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 (51) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.js +12 -2
  3. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  4. package/dist/features/magic-context/resolve-subagent-fallback.d.ts +40 -0
  5. package/dist/features/magic-context/resolve-subagent-fallback.d.ts.map +1 -0
  6. package/dist/features/magic-context/storage-meta-persisted.d.ts +7 -0
  7. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  8. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  9. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  10. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  11. package/dist/features/magic-context/storage.d.ts +1 -1
  12. package/dist/features/magic-context/storage.d.ts.map +1 -1
  13. package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
  14. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  15. package/dist/hooks/magic-context/compartment-runner-types.d.ts +13 -0
  16. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  17. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  18. package/dist/hooks/magic-context/hook-handlers.d.ts +71 -2
  19. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  20. package/dist/hooks/magic-context/hook.d.ts +3 -0
  21. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  22. package/dist/hooks/magic-context/live-session-state.d.ts +19 -0
  23. package/dist/hooks/magic-context/live-session-state.d.ts.map +1 -1
  24. package/dist/hooks/magic-context/system-prompt-hash.d.ts +17 -1
  25. package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
  26. package/dist/hooks/magic-context/tokenizer-calibration.d.ts +85 -0
  27. package/dist/hooks/magic-context/tokenizer-calibration.d.ts.map +1 -0
  28. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +4 -0
  29. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  30. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +8 -1
  31. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  32. package/dist/hooks/magic-context/transform.d.ts +17 -1
  33. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  34. package/dist/index.js +396 -51
  35. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  36. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  37. package/dist/plugin/sidebar-snapshot-cache.d.ts +19 -0
  38. package/dist/plugin/sidebar-snapshot-cache.d.ts.map +1 -0
  39. package/dist/shared/conflict-detector.d.ts.map +1 -1
  40. package/dist/shared/rpc-types.d.ts +0 -9
  41. package/dist/shared/rpc-types.d.ts.map +1 -1
  42. package/dist/tui/data/context-db.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/shared/conflict-detector.test.ts +189 -0
  45. package/src/shared/conflict-detector.ts +68 -7
  46. package/src/shared/rpc-types.ts +0 -9
  47. package/src/tui/data/context-db.ts +66 -4
  48. package/src/tui/index.tsx +0 -3
  49. package/src/tui/slots/sidebar-content.tsx +0 -15
  50. package/dist/plugin/tui-action-consumer.d.ts +0 -15
  51. package/dist/plugin/tui-action-consumer.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAU7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;;;;;;qBAmD4gD,CAAC;;;;;;;;;;;;qBAbz+C,CAAC;mBAAyB,CAAC;iBAChD,CAAX;iBAAuB,CAAC;0BAAc,CAAC;uBAAiB,CAAC;;;;;;0BAYi0nB,CAAC;;;;;;EAD/3nB"}
1
+ {"version":3,"file":"create-session-hooks.d.ts","sourceRoot":"","sources":["../../../src/plugin/hooks/create-session-hooks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAU7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8CAA8C,CAAC;AACrF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE9C,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,GAAG,EAAE,aAAa,CAAC;IACnB,YAAY,EAAE,wBAAwB,CAAC;IACvC,gBAAgB,EAAE,gBAAgB,CAAC;CACtC;;;;;;qBAmDu0E,CAAC;;;;;;;;;;;;qBAAqd,CAAC;mBAAyB,CAAC;iBAAuB,CAAC;iBAAuB,CAAC;0BAAc,CAAC;uBAAiB,CAAC;;;;;;0BAAi+mB,CAAC;;;;;;EAD32sB"}
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAMzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAIlF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AA0elE;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,qBAAqB,EAChC,IAAI,EAAE;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;CACtC,GACF,IAAI,CA4GN"}
1
+ {"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAMzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAQlF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAkflE;;GAEG;AACH,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,qBAAqB,EAChC,IAAI,EAAE;IACF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,gBAAgB,EAAE,gBAAgB,CAAC;CACtC,GACF,IAAI,CA8HN"}
@@ -0,0 +1,19 @@
1
+ import type { SidebarSnapshot } from "../shared/rpc-types";
2
+ /**
3
+ * Apply the sticky-cache policy to a freshly built snapshot.
4
+ *
5
+ * Returns either the live snapshot (preferred) or a hybrid snapshot that
6
+ * preserves token-breakdown values from the previous good reading while keeping
7
+ * fresh DB-backed counts (compartmentCount, memoryCount, historian state, etc.)
8
+ * from the current build.
9
+ */
10
+ export declare function applyStickySnapshotCache(sessionId: string, fresh: SidebarSnapshot): SidebarSnapshot;
11
+ /**
12
+ * Drop the cached snapshot for a session. Wired to `session.deleted`.
13
+ */
14
+ export declare function clearSidebarSnapshotCache(sessionId: string): void;
15
+ /**
16
+ * Test helper — drop the entire cache.
17
+ */
18
+ export declare function resetSidebarSnapshotCache(): void;
19
+ //# sourceMappingURL=sidebar-snapshot-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sidebar-snapshot-cache.d.ts","sourceRoot":"","sources":["../../src/plugin/sidebar-snapshot-cache.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAY3D;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CACpC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,eAAe,GACvB,eAAe,CAsCjB;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAEjE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD"}
@@ -1 +1 @@
1
- {"version":3,"file":"conflict-detector.d.ts","sourceRoot":"","sources":["../../src/shared/conflict-detector.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,cAAc;IAC3B,8CAA8C;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE;QACP,cAAc,EAAE,OAAO,CAAC;QACxB,eAAe,EAAE,OAAO,CAAC;QACzB,SAAS,EAAE,OAAO,CAAC;QACnB,uBAAuB,EAAE,OAAO,CAAC;QACjC,uBAAuB,EAAE,OAAO,CAAC;QACjC,oBAAoB,EAAE,OAAO,CAAC;KACjC,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAyDjE;AA0LD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAWlE"}
1
+ {"version":3,"file":"conflict-detector.d.ts","sourceRoot":"","sources":["../../src/shared/conflict-detector.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,cAAc;IAC3B,8CAA8C;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB,+CAA+C;IAC/C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,2DAA2D;IAC3D,SAAS,EAAE;QACP,cAAc,EAAE,OAAO,CAAC;QACxB,eAAe,EAAE,OAAO,CAAC;QACzB,SAAS,EAAE,OAAO,CAAC;QACnB,uBAAuB,EAAE,OAAO,CAAC;QACjC,uBAAuB,EAAE,OAAO,CAAC;QACjC,oBAAoB,EAAE,OAAO,CAAC;KACjC,CAAC;CACL;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,CAyDjE;AAuPD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,CAWlE"}
@@ -43,15 +43,6 @@ export interface SidebarSnapshot {
43
43
  * shows this as "Tool Definitions".
44
44
  */
45
45
  toolDefinitionTokens: number;
46
- /**
47
- * Residual catch-all: provider-side wrapping not captured elsewhere —
48
- * the JSON envelope around the `tools` array, tool-choice fields,
49
- * provider-specific cache-control markers, tokenizer imprecision, etc.
50
- * Computed as `inputTokens − systemPromptTokens − messagesBlock −
51
- * toolCallTokens − toolDefinitionTokens` and clamped to ≥ 0. Display
52
- * layer shows this as "Overhead".
53
- */
54
- overheadTokens: number;
55
46
  }
56
47
  export interface StatusDetail extends SidebarSnapshot {
57
48
  tagCounter: number;
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-types.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,oBAAoB,EAAE,MAAM,CAAC;IAC7B;;;;;;;OAOG;IACH,cAAc,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,YAAa,SAAQ,eAAe;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,oBAAoB,EAAE,YAAY,GAAG,QAAQ,CAAC;IAC9C;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,sBAAsB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB"}
1
+ {"version":3,"file":"rpc-types.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;;;;;;OAOG;IACH,oBAAoB,EAAE,MAAM,CAAC;CAChC;AAED,MAAM,WAAW,YAAa,SAAQ,eAAe;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACxD,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,OAAO,CAAC;IACtB,gBAAgB,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,oBAAoB,EAAE,YAAY,GAAG,QAAQ,CAAC;IAC9C;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,uBAAuB,EAAE,MAAM,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,sBAAsB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -1 +1 @@
1
- {"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui/data/context-db.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA0B,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEpG,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAS9C,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGrD;AAED,+BAA+B;AAC/B,wBAAgB,QAAQ,IAAI,IAAI,CAG/B;AA4BD,sDAAsD;AACtD,wBAAsB,mBAAmB,CACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC,CAc1B;AAED,wDAAwD;AACxD,wBAAsB,gBAAgB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CA4CvB;AAED,qCAAqC;AACrC,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED,6CAA6C;AAC7C,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQvE;AAED,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,yDAAyD;AACzD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAchE"}
1
+ {"version":3,"file":"context-db.d.ts","sourceRoot":"","sources":["../../../src/tui/data/context-db.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAA0B,eAAe,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEpG,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,CAAC;AAS9C,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAGrD;AAED,+BAA+B;AAC/B,wBAAgB,QAAQ,IAAI,IAAI,CAG/B;AA4ED,sDAAsD;AACtD,wBAAsB,mBAAmB,CACrC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAClB,OAAO,CAAC,eAAe,CAAC,CA4B1B;AAED,wDAAwD;AACxD,wBAAsB,gBAAgB,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,YAAY,CAAC,CA4CvB;AAED,qCAAqC;AACrC,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQ5E;AAED,6CAA6C;AAC7C,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQvE;AAED,MAAM,WAAW,UAAU;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,yDAAyD;AACzD,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC,CAchE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.15.2",
3
+ "version": "0.15.4",
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,189 @@
1
+ /// <reference types="bun-types" />
2
+
3
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
4
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { detectConflicts } from "./conflict-detector";
8
+
9
+ /**
10
+ * Regression tests for plugin-conflict detection. The previous substring-
11
+ * based matcher misclassified `oh-my-opencode-slim` and `opencode-dcp-fork`
12
+ * as the canonical plugins, causing magic-context to disable itself with
13
+ * a false-positive conflict warning. See issue #43.
14
+ */
15
+ describe("detectConflicts", () => {
16
+ let projectDir: string;
17
+ let userConfigDir: string;
18
+ let originalEnv: Record<string, string | undefined>;
19
+
20
+ beforeEach(() => {
21
+ const root = mkdtempSync(join(tmpdir(), "mc-conflict-"));
22
+ projectDir = join(root, "project");
23
+ mkdirSync(projectDir, { recursive: true });
24
+ userConfigDir = join(root, "user-config", "opencode");
25
+ mkdirSync(userConfigDir, { recursive: true });
26
+
27
+ // Save and override every env var that affects config-path resolution.
28
+ // OPENCODE_CONFIG_DIR takes precedence over XDG_CONFIG_HOME, so we set
29
+ // it directly and clear XDG to fully isolate from any inherited or
30
+ // test-leaked state.
31
+ originalEnv = {
32
+ OPENCODE_CONFIG_DIR: process.env.OPENCODE_CONFIG_DIR,
33
+ XDG_CONFIG_HOME: process.env.XDG_CONFIG_HOME,
34
+ OPENCODE_DISABLE_AUTOCOMPACT: process.env.OPENCODE_DISABLE_AUTOCOMPACT,
35
+ };
36
+ process.env.OPENCODE_CONFIG_DIR = userConfigDir;
37
+ delete process.env.XDG_CONFIG_HOME;
38
+ // Disable auto-compaction default during tests so we isolate plugin
39
+ // detection from compaction detection.
40
+ process.env.OPENCODE_DISABLE_AUTOCOMPACT = "1";
41
+ });
42
+
43
+ afterEach(() => {
44
+ for (const [k, v] of Object.entries(originalEnv)) {
45
+ if (v === undefined) delete process.env[k];
46
+ else process.env[k] = v;
47
+ }
48
+ // Test directories live under tmpdir(); cleanup is best-effort.
49
+ rmSync(projectDir, { recursive: true, force: true });
50
+ rmSync(userConfigDir, { recursive: true, force: true });
51
+ });
52
+
53
+ function writeProjectConfig(plugins: string[]): void {
54
+ writeFileSync(join(projectDir, "opencode.json"), JSON.stringify({ plugin: plugins }));
55
+ }
56
+
57
+ // --- DCP detection ---
58
+
59
+ describe("DCP detection", () => {
60
+ it("matches the canonical @tarquinen/opencode-dcp package", () => {
61
+ writeProjectConfig(["@tarquinen/opencode-dcp"]);
62
+ const result = detectConflicts(projectDir);
63
+ expect(result.conflicts.dcpPlugin).toBe(true);
64
+ });
65
+
66
+ it("matches the canonical package with a version suffix", () => {
67
+ writeProjectConfig(["@tarquinen/opencode-dcp@latest"]);
68
+ const result = detectConflicts(projectDir);
69
+ expect(result.conflicts.dcpPlugin).toBe(true);
70
+ });
71
+
72
+ it("matches with a semver range suffix", () => {
73
+ writeProjectConfig(["@tarquinen/opencode-dcp@^3.1.0"]);
74
+ const result = detectConflicts(projectDir);
75
+ expect(result.conflicts.dcpPlugin).toBe(true);
76
+ });
77
+
78
+ it("does NOT match a fork with a different package name", () => {
79
+ writeProjectConfig(["@some-fork/opencode-dcp-fork"]);
80
+ const result = detectConflicts(projectDir);
81
+ expect(result.conflicts.dcpPlugin).toBe(false);
82
+ });
83
+
84
+ it("does NOT match a file:// path that contains 'opencode-dcp'", () => {
85
+ writeProjectConfig(["file:///home/user/work/opencode-dcp-fork"]);
86
+ const result = detectConflicts(projectDir);
87
+ expect(result.conflicts.dcpPlugin).toBe(false);
88
+ });
89
+ });
90
+
91
+ // --- OMO detection (the issue #43 case) ---
92
+
93
+ describe("OMO detection", () => {
94
+ it("matches the canonical oh-my-opencode package", () => {
95
+ writeProjectConfig(["oh-my-opencode"]);
96
+ const result = detectConflicts(projectDir);
97
+ // No OMO config = hooks default ACTIVE = all three flagged
98
+ expect(result.conflicts.omoPreemptiveCompaction).toBe(true);
99
+ expect(result.conflicts.omoContextWindowMonitor).toBe(true);
100
+ expect(result.conflicts.omoAnthropicRecovery).toBe(true);
101
+ });
102
+
103
+ it("matches the canonical oh-my-openagent package alias", () => {
104
+ writeProjectConfig(["oh-my-openagent"]);
105
+ const result = detectConflicts(projectDir);
106
+ expect(result.conflicts.omoPreemptiveCompaction).toBe(true);
107
+ });
108
+
109
+ it("matches a canonical OMO with a version suffix", () => {
110
+ writeProjectConfig(["oh-my-opencode@3.17.5", "oh-my-openagent@latest"]);
111
+ const result = detectConflicts(projectDir);
112
+ expect(result.conflicts.omoPreemptiveCompaction).toBe(true);
113
+ expect(result.conflicts.omoContextWindowMonitor).toBe(true);
114
+ expect(result.conflicts.omoAnthropicRecovery).toBe(true);
115
+ });
116
+
117
+ it("does NOT match oh-my-opencode-slim (issue #43)", () => {
118
+ writeProjectConfig(["oh-my-opencode-slim"]);
119
+ const result = detectConflicts(projectDir);
120
+ expect(result.hasConflict).toBe(false);
121
+ expect(result.conflicts.omoPreemptiveCompaction).toBe(false);
122
+ expect(result.conflicts.omoContextWindowMonitor).toBe(false);
123
+ expect(result.conflicts.omoAnthropicRecovery).toBe(false);
124
+ });
125
+
126
+ it("does NOT match oh-my-opencode-slim with a version suffix (issue #43)", () => {
127
+ writeProjectConfig(["oh-my-opencode-slim@latest", "oh-my-opencode-slim@1.0.3"]);
128
+ const result = detectConflicts(projectDir);
129
+ expect(result.hasConflict).toBe(false);
130
+ });
131
+
132
+ it("does NOT match a file:// path containing 'oh-my-opencode' (issue #43)", () => {
133
+ writeProjectConfig(["file:///home/user/workspace/oh-my-opencode-slim-dev"]);
134
+ const result = detectConflicts(projectDir);
135
+ expect(result.hasConflict).toBe(false);
136
+ });
137
+
138
+ it("does NOT match other forks under different package names", () => {
139
+ writeProjectConfig([
140
+ "oh-my-opencode-cli",
141
+ "@some-org/oh-my-opencode-fork",
142
+ "my-oh-my-opencode-customizations",
143
+ ]);
144
+ const result = detectConflicts(projectDir);
145
+ expect(result.hasConflict).toBe(false);
146
+ });
147
+
148
+ it("still detects canonical OMO when slim is also installed", () => {
149
+ // A user running both slim and the real OMO should still get
150
+ // the conflict warning for the real one.
151
+ writeProjectConfig(["oh-my-opencode-slim", "oh-my-opencode@latest"]);
152
+ const result = detectConflicts(projectDir);
153
+ expect(result.conflicts.omoPreemptiveCompaction).toBe(true);
154
+ });
155
+
156
+ it("respects disabled_hooks in project-level OMO config", () => {
157
+ writeProjectConfig(["oh-my-opencode"]);
158
+ // Use project-scoped OMO config to avoid relying on user
159
+ // config-path resolution, which can be leaked across files
160
+ // by `spyOn(getOpenCodeConfigPaths)` mocks in sibling tests.
161
+ writeFileSync(
162
+ join(projectDir, "oh-my-opencode.json"),
163
+ JSON.stringify({
164
+ disabled_hooks: [
165
+ "preemptive-compaction",
166
+ "context-window-monitor",
167
+ "anthropic-context-window-limit-recovery",
168
+ ],
169
+ }),
170
+ );
171
+ const result = detectConflicts(projectDir);
172
+ expect(result.hasConflict).toBe(false);
173
+ });
174
+ });
175
+
176
+ // --- Combined / control cases ---
177
+
178
+ it("returns no conflicts for an empty plugin list", () => {
179
+ writeProjectConfig([]);
180
+ const result = detectConflicts(projectDir);
181
+ expect(result.hasConflict).toBe(false);
182
+ });
183
+
184
+ it("returns no conflicts for unrelated plugins", () => {
185
+ writeProjectConfig(["@cortexkit/opencode-magic-context@latest", "some-other-plugin"]);
186
+ const result = detectConflicts(projectDir);
187
+ expect(result.hasConflict).toBe(false);
188
+ });
189
+ });
@@ -167,9 +167,60 @@ function readUserCompaction(): { auto: boolean; prune: boolean; resolved: boolea
167
167
 
168
168
  // --- DCP detection ---
169
169
 
170
+ /**
171
+ * Canonical npm package names that represent the conflicting plugin.
172
+ * Matched against the npm-style segment of each plugin entry, so:
173
+ * - "@tarquinen/opencode-dcp" ✓ direct match
174
+ * - "@tarquinen/opencode-dcp@latest" ✓ version suffix stripped
175
+ * - "@tarquinen/opencode-dcp@^3.1.0" ✓ semver suffix stripped
176
+ * - "file:///path/to/opencode-dcp-fork" ✗ unrelated path
177
+ *
178
+ * forks/renames that don't ship the conflicting transform/system hooks are
179
+ * intentionally NOT matched.
180
+ */
181
+ const DCP_PACKAGE_NAMES = new Set(["@tarquinen/opencode-dcp"]);
182
+
170
183
  function checkDcpPlugin(directory: string): boolean {
171
184
  const plugins = collectPluginEntries(directory);
172
- return plugins.some((p) => p.includes("opencode-dcp"));
185
+ return plugins.some((p) => matchesPackageName(p, DCP_PACKAGE_NAMES));
186
+ }
187
+
188
+ /**
189
+ * Match a plugin entry against a set of canonical npm package names.
190
+ *
191
+ * A plugin entry can be:
192
+ * - "pkg-name"
193
+ * - "pkg-name@version"
194
+ * - "@scope/pkg-name"
195
+ * - "@scope/pkg-name@version"
196
+ * - "file://..." or other URL/path forms (never matched here)
197
+ *
198
+ * For the canonical-name path we only match the exact package name (with
199
+ * optional version suffix). file:// paths and forks with different
200
+ * package names are intentionally NOT matched — even if a path string
201
+ * happens to contain a substring like "oh-my-opencode" (e.g. forks like
202
+ * "oh-my-opencode-slim" published under a different package name).
203
+ */
204
+ function matchesPackageName(entry: string, canonicalNames: Set<string>): boolean {
205
+ // Skip URL/path forms — only npm-style entries can be canonically matched.
206
+ // (Local file:// checkouts of canonical plugins are rare; users running
207
+ // those need to ensure the path itself doesn't match a fork's name.)
208
+ if (
209
+ entry.startsWith("file:") ||
210
+ entry.startsWith("http:") ||
211
+ entry.startsWith("https:") ||
212
+ entry.startsWith("/") ||
213
+ entry.startsWith("./") ||
214
+ entry.startsWith("../")
215
+ ) {
216
+ return false;
217
+ }
218
+
219
+ // Strip version suffix: "@scope/pkg@1.2.3" → "@scope/pkg"
220
+ // Careful with scoped packages: the leading "@" is part of the name.
221
+ const lastAt = entry.lastIndexOf("@");
222
+ const nameOnly = lastAt > 0 ? entry.slice(0, lastAt) : entry;
223
+ return canonicalNames.has(nameOnly);
173
224
  }
174
225
 
175
226
  function collectPluginEntries(directory: string): string[] {
@@ -206,6 +257,21 @@ function collectPluginEntries(directory: string): string[] {
206
257
 
207
258
  // --- OMO hook detection ---
208
259
 
260
+ /**
261
+ * Canonical OMO npm package names. The plugin publishes under both names as
262
+ * a versioned alias (latest 3.17.5 on npm at time of writing).
263
+ *
264
+ * Forks under a different package name (e.g. `oh-my-opencode-slim`,
265
+ * `oh-my-opencode-cli`, etc.) are intentionally NOT matched here — they
266
+ * don't ship the `preemptive-compaction`, `context-window-monitor`, or
267
+ * `anthropic-context-window-limit-recovery` hooks that conflict with
268
+ * Magic Context. See https://github.com/cortexkit/opencode-magic-context/issues/43.
269
+ *
270
+ * The legacy `@code-yeongyu/` scope is no longer used — both names are
271
+ * unscoped on npm.
272
+ */
273
+ const OMO_PACKAGE_NAMES = new Set(["oh-my-opencode", "oh-my-openagent"]);
274
+
209
275
  function checkOmoHooks(directory: string): {
210
276
  preemptiveCompaction: boolean;
211
277
  contextWindowMonitor: boolean;
@@ -219,12 +285,7 @@ function checkOmoHooks(directory: string): {
219
285
 
220
286
  // First check if OMO is even installed
221
287
  const plugins = collectPluginEntries(directory);
222
- const hasOmo = plugins.some(
223
- (p) =>
224
- p.includes("oh-my-opencode") ||
225
- p.includes("oh-my-openagent") ||
226
- p.includes("@code-yeongyu/"),
227
- );
288
+ const hasOmo = plugins.some((p) => matchesPackageName(p, OMO_PACKAGE_NAMES));
228
289
  if (!hasOmo) return result;
229
290
 
230
291
  // Read OMO config to check disabled_hooks
@@ -44,15 +44,6 @@ export interface SidebarSnapshot {
44
44
  * shows this as "Tool Definitions".
45
45
  */
46
46
  toolDefinitionTokens: number;
47
- /**
48
- * Residual catch-all: provider-side wrapping not captured elsewhere —
49
- * the JSON envelope around the `tools` array, tool-choice fields,
50
- * provider-specific cache-control markers, tokenizer imprecision, etc.
51
- * Computed as `inputTokens − systemPromptTokens − messagesBlock −
52
- * toolCallTokens − toolDefinitionTokens` and clamped to ≥ 0. Display
53
- * layer shows this as "Overhead".
54
- */
55
- overheadTokens: number;
56
47
  }
57
48
 
58
49
  export interface StatusDetail extends SidebarSnapshot {
@@ -51,26 +51,88 @@ const EMPTY_SNAPSHOT: SidebarSnapshot = {
51
51
  conversationTokens: 0,
52
52
  toolCallTokens: 0,
53
53
  toolDefinitionTokens: 0,
54
- overheadTokens: 0,
55
54
  };
56
55
 
56
+ /**
57
+ * Per-session client-side sticky cache. Mirrors the server-side cache in
58
+ * `sidebar-snapshot-cache.ts` but covers the cases the server can't:
59
+ * - RPC call fails entirely (timeout, abort, parse error) → server is never reached
60
+ * - RPC server is not yet up (port file missing, retries exhausted)
61
+ * - Server returns an error envelope
62
+ *
63
+ * In all three cases the breakdown bar would otherwise disappear until the
64
+ * next successful refresh. With this cache, the client returns the most
65
+ * recent good snapshot for the same session so the UI stays stable through
66
+ * transient RPC blips. 5-minute staleness ceiling keeps it from showing
67
+ * obviously old data after long disconnects.
68
+ */
69
+ interface CachedSnapshot {
70
+ snapshot: SidebarSnapshot;
71
+ cachedAt: number;
72
+ }
73
+ const STICKY_TTL_MS = 5 * 60 * 1000;
74
+ const STICKY_MAX_ENTRIES = 100;
75
+ const stickySidebarCache = new Map<string, CachedSnapshot>();
76
+
77
+ function rememberSidebarSnapshot(snapshot: SidebarSnapshot): void {
78
+ if (!snapshot.sessionId || snapshot.inputTokens <= 0) return;
79
+ // LRU-style bound: drop the oldest entry once we hit the cap. With a
80
+ // 5-min TTL most stale entries time out naturally; this just prevents
81
+ // unbounded growth across many session switches in a long TUI session.
82
+ if (
83
+ stickySidebarCache.size >= STICKY_MAX_ENTRIES &&
84
+ !stickySidebarCache.has(snapshot.sessionId)
85
+ ) {
86
+ const firstKey = stickySidebarCache.keys().next().value;
87
+ if (firstKey) stickySidebarCache.delete(firstKey);
88
+ }
89
+ stickySidebarCache.set(snapshot.sessionId, {
90
+ snapshot,
91
+ cachedAt: Date.now(),
92
+ });
93
+ }
94
+
95
+ function recallSidebarSnapshot(sessionId: string, fallback: SidebarSnapshot): SidebarSnapshot {
96
+ const cached = stickySidebarCache.get(sessionId);
97
+ if (!cached) return fallback;
98
+ if (Date.now() - cached.cachedAt > STICKY_TTL_MS) {
99
+ stickySidebarCache.delete(sessionId);
100
+ return fallback;
101
+ }
102
+ return cached.snapshot;
103
+ }
104
+
57
105
  /** Fetch sidebar snapshot from the server via RPC. */
58
106
  export async function loadSidebarSnapshot(
59
107
  sessionId: string,
60
108
  directory: string,
61
109
  ): Promise<SidebarSnapshot> {
62
- if (!rpcClient) return { ...EMPTY_SNAPSHOT, sessionId };
110
+ const empty: SidebarSnapshot = { ...EMPTY_SNAPSHOT, sessionId };
111
+ if (!rpcClient) return recallSidebarSnapshot(sessionId, empty);
63
112
  try {
64
113
  const result = await rpcClient.call<SidebarSnapshot>("sidebar-snapshot", {
65
114
  sessionId,
66
115
  directory,
67
116
  });
68
117
  if ((result as unknown as Record<string, unknown>).error) {
69
- return { ...EMPTY_SNAPSHOT, sessionId };
118
+ return recallSidebarSnapshot(sessionId, empty);
70
119
  }
120
+ // Trust successful server responses. The server has its own sticky
121
+ // sidebar cache (`sidebar-snapshot-cache.ts`) that handles transient
122
+ // zero-token windows by hybriding cached breakdown values into a
123
+ // fresh snapshot, AND clears that cache on `session.deleted`. If the
124
+ // server reaches us with `inputTokens === 0`, that's its considered
125
+ // answer — typically because the session was deleted, reverted, or
126
+ // is brand-new with no responses yet.
127
+ //
128
+ // Falling back to the client cache here would resurrect old token
129
+ // data for a deleted session (the client never sees `session.deleted`
130
+ // events, so its cache TTL is the only expiry). Sticky behavior is
131
+ // owned exclusively by the server side.
132
+ rememberSidebarSnapshot(result);
71
133
  return result;
72
134
  } catch {
73
- return { ...EMPTY_SNAPSHOT, sessionId };
135
+ return recallSidebarSnapshot(sessionId, empty);
74
136
  }
75
137
  }
76
138
 
package/src/tui/index.tsx CHANGED
@@ -219,7 +219,6 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
219
219
  conversation: "#f87171",
220
220
  toolCalls: "#fb923c",
221
221
  toolDefs: "#f472b6",
222
- overhead: "#9ca3af",
223
222
  }
224
223
 
225
224
  const breakdownSegments = () => {
@@ -257,8 +256,6 @@ const StatusDialog = (props: { api: TuiPluginApi; s: StatusDetail }) => {
257
256
  segs.push({ label: "Tool Calls", tokens: d.toolCallTokens, color: COLORS.toolCalls })
258
257
  if (d.toolDefinitionTokens > 0)
259
258
  segs.push({ label: "Tool Defs", tokens: d.toolDefinitionTokens, color: COLORS.toolDefs })
260
- if (d.overheadTokens > 0)
261
- segs.push({ label: "Overhead", tokens: d.overheadTokens, color: COLORS.overhead })
262
259
 
263
260
  return { segs, total }
264
261
  }
@@ -33,7 +33,6 @@ const COLORS = {
33
33
  conversation: "#f87171", // Red
34
34
  toolCalls: "#fb923c", // Orange
35
35
  toolDefs: "#f472b6", // Pink
36
- overhead: "#9ca3af", // Gray — catch-all residual
37
36
  }
38
37
 
39
38
  interface TokenSegment {
@@ -130,20 +129,6 @@ const TokenBreakdown = (props: {
130
129
  })
131
130
  }
132
131
 
133
- // Overhead = residual between input tokens and everything measured above.
134
- // Captures provider-side JSON wrapping around the tools array,
135
- // tool_choice/cache-control markers, and tokenizer imprecision. Before
136
- // the first turn's tool.definition measurement lands, the real tool
137
- // schema cost also shows up here.
138
- if (s.overheadTokens > 0) {
139
- result.push({
140
- key: "overhead",
141
- tokens: s.overheadTokens,
142
- color: COLORS.overhead,
143
- label: "Overhead",
144
- })
145
- }
146
-
147
132
  return result
148
133
  })
149
134
 
@@ -1,15 +0,0 @@
1
- import type { MagicContextConfig } from "../config/schema/magic-context";
2
- import type { LiveSessionState } from "../hooks/magic-context/live-session-state";
3
- import type { PluginContext } from "./types";
4
- /**
5
- * Start a server-side consumer that polls plugin_messages for TUI→server
6
- * action messages and dispatches them. Currently handles:
7
- * - { command: "recomp" } — executes /ctx-recomp for the given session
8
- */
9
- export declare function startTuiActionConsumer(args: {
10
- client: PluginContext["client"];
11
- directory: string;
12
- config: MagicContextConfig;
13
- liveSessionState: LiveSessionState;
14
- }): (() => void) | undefined;
15
- //# sourceMappingURL=tui-action-consumer.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"tui-action-consumer.d.ts","sourceRoot":"","sources":["../../src/plugin/tui-action-consumer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AASzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAGlF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAO7C;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IACzC,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,gBAAgB,EAAE,gBAAgB,CAAC;CACtC,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAiF3B"}