@cortexkit/opencode-magic-context 0.27.1 → 0.27.3
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 +2 -7
- package/dist/config/migrate-config-location.d.ts +8 -0
- package/dist/config/migrate-config-location.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +1 -0
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index.js +68 -17
- package/dist/plugin/conflict-warning-hook.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts +23 -0
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/tui-config.d.ts.map +1 -1
- package/dist/shared/tui-preferences.d.ts +1 -1
- package/dist/tui/badge-contrast.d.ts +31 -0
- package/dist/tui/badge-contrast.d.ts.map +1 -0
- package/package.json +4 -1
- package/src/shared/models-dev-cache.test.ts +82 -0
- package/src/shared/models-dev-cache.ts +35 -0
- package/src/shared/tui-config.test.ts +43 -0
- package/src/shared/tui-config.ts +8 -1
- package/src/shared/tui-preferences.ts +2 -2
- package/src/tui/badge-contrast.test.ts +45 -0
- package/src/tui/badge-contrast.ts +46 -0
- package/src/tui/slots/sidebar-content.tsx +2 -1
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
<a href="#-recall">Recall</a> ·
|
|
29
29
|
<a href="https://docs.cortexkit.io/magic-context">Docs</a> ·
|
|
30
30
|
<a href="./CONFIGURATION.md">Configuration</a> ·
|
|
31
|
+
<a href="https://github.com/cortexkit/magic-context/releases?q=dashboard&expanded=true">Dashboard</a> ·
|
|
31
32
|
<a href="https://discord.gg/DSa65w8wuf">💬 Discord</a>
|
|
32
33
|
</p>
|
|
33
34
|
|
|
@@ -87,13 +88,7 @@ The wizard auto-detects which harnesses you have (OpenCode, Pi, or both), adds t
|
|
|
87
88
|
|
|
88
89
|
**Troubleshooting:** `npx @cortexkit/magic-context@latest doctor` auto-detects your harnesses, checks for conflicts (compaction, OMO hooks, DCP), verifies the plugin and TUI sidebar, runs an integrity check on the database, and fixes what it can. Add `--issue` to file a ready-to-submit bug report.
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Magic Context is not limited to greenfield work. Run the setup wizard from the root of the project you already use with OpenCode or Pi, then restart the harness in that same directory so the plugin can attach to the existing project identity. From that point forward, new turns are captured automatically and the historian compartmentalizes the active session as it grows.
|
|
93
|
-
|
|
94
|
-
Magic Context does **not** retroactively import or reprocess OpenCode or Pi sessions that happened before it was installed. Those past conversations are not backfilled into compartments. What builds up across an existing project from day one includes project memories, dreamer-maintained docs, key files, and other context captured after installation.
|
|
95
|
-
|
|
96
|
-
If you want to seed knowledge from older work, add the important facts explicitly with `ctx_memory`, write a short project note in the repo, or keep working in the project and let the historian and dreamer promote recurring facts over time. Use `/ctx-recomp` when you need to rebuild Magic Context compartments from raw message history that Magic Context has captured in that session, such as after upgrading historian rules; it is not an importer for unrelated pre-install OpenCode or Pi sessions. The dashboard can also inspect the shared CortexKit database, memories, compartments, and dreamer runs without starting a coding session.
|
|
91
|
+
Works the same on a brand-new or a long-running project: install, restart the harness, and Magic Context captures context from that point forward. It does not backfill OpenCode or Pi sessions from before it was installed.
|
|
97
92
|
|
|
98
93
|
<details>
|
|
99
94
|
<summary><strong>Compatibility with other context-management plugins</strong></summary>
|
|
@@ -58,6 +58,14 @@ export declare function resolveCortexKitProjectConfigPath(directory: string): st
|
|
|
58
58
|
* `.jsonc` and a `.json` candidate; whichever exists migrates, target is always
|
|
59
59
|
* `.jsonc`. The bare-root project source (`<root>/magic-context.*`) is unique to
|
|
60
60
|
* Magic Context (AFT never had it) — omitting it would orphan repo-root configs.
|
|
61
|
+
*
|
|
62
|
+
* Project sources are filtered against the user-scope path set: when a session's
|
|
63
|
+
* project directory IS the user config home (e.g. opencode opened in
|
|
64
|
+
* `~/.config/cortexkit`), the bare-root project source `<root>/magic-context.jsonc`
|
|
65
|
+
* resolves to the USER config path. Without this guard the project migration
|
|
66
|
+
* would "migrate" the user's own config into `<root>/.cortexkit/` and rename the
|
|
67
|
+
* original aside, leaving the user on schema defaults (the config-eats-itself
|
|
68
|
+
* bug). A project migration must never touch a user-scope file.
|
|
61
69
|
*/
|
|
62
70
|
export declare function resolveLegacyConfigSources(directory: string): {
|
|
63
71
|
user: LegacyConfigSource[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-config-location.d.ts","sourceRoot":"","sources":["../../src/config/migrate-config-location.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IAClC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACvC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC7C,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAoBD,iFAAiF;AACjF,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,2DAA2D;AAC3D,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,2DAA2D;AAC3D,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3E;
|
|
1
|
+
{"version":3,"file":"migrate-config-location.d.ts","sourceRoot":"","sources":["../../src/config/migrate-config-location.ts"],"names":[],"mappings":"AAeA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,kBAAkB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IAClC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,0BAA0B;IACvC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC7C,MAAM,CAAC,EAAE,qBAAqB,CAAC;CAClC;AAED,MAAM,WAAW,yBAAyB;IACtC,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAoBD,iFAAiF;AACjF,wBAAgB,2BAA2B,IAAI,MAAM,CAEpD;AAED,+EAA+E;AAC/E,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,2DAA2D;AAC3D,wBAAgB,8BAA8B,IAAI,MAAM,CAEvD;AAED,2DAA2D;AAC3D,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE3E;AA4BD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG;IAC3D,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,OAAO,EAAE,kBAAkB,EAAE,CAAC;CACjC,CAsBA;AAED,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,IAAI,CAAC;AAE9C;;;;;;;;;GASG;AACH,wBAAgB,oCAAoC,CAChD,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,aAAa,GACvB;IAAE,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAAC,OAAO,EAAE,kBAAkB,EAAE,CAAA;CAAE,CA0B/D;AAmSD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,0BAA0B,GAAG,yBAAyB,CA0F7F;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAC9C,SAAS,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,qBAAqB,GAC/B,MAAM,EAAE,CA6BV"}
|
|
@@ -52,6 +52,7 @@ export declare const __test: {
|
|
|
52
52
|
getPendingPi(sessionId: string): PendingPiTransformDecision | undefined;
|
|
53
53
|
reset(): void;
|
|
54
54
|
setWriterForTests(writer: TransformDecisionWriter | null): void;
|
|
55
|
+
setRetentionForTests(cap: number | null): void;
|
|
55
56
|
writeRow(dbPath: string, row: TransformDecisionRow): void;
|
|
56
57
|
findNewestPiAssistantEntryIdAfter: typeof findNewestPiAssistantEntryIdAfter;
|
|
57
58
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform-decision-log.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/transform-decision-log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAI/C,MAAM,MAAM,wBAAwB,GAAG,UAAU,GAAG,IAAI,CAAC;AACzD,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,OAAO,CAAC;AAE7D;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAElD,MAAM,MAAM,0BAA0B,GAChC,aAAa,GACb,cAAc,GACd,sBAAsB,GACtB,UAAU,GACV,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,GACd,iBAAiB,GACjB,eAAe,GACf,mBAAmB,CAAC;AAE1B,MAAM,WAAW,wBAAwB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,0BAA0B,CAAC;IACrC,YAAY,EAAE,OAAO,CAAC;IACtB,iBAAiB,EAAE,0BAA0B,GAAG,IAAI,CAAC;IACrD,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,OAAO,CAAC;CAC3B;AAED,UAAU,oBAAqB,SAAQ,wBAAwB;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,wBAAwB,CAAC;IAClC,SAAS,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,0BAA2B,SAAQ,wBAAwB;IACjE,8BAA8B,EAAE,MAAM,GAAG,IAAI,CAAC;CACjD;AAED,KAAK,uBAAuB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,oBAAoB,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"transform-decision-log.d.ts","sourceRoot":"","sources":["../../../src/features/magic-context/transform-decision-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-handler.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/event-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAyBvF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAKlE,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"event-handler.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/event-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,yCAAyC,CAAC;AAyBvF,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAKlE,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,oCAAoC,CAAC;AAwBpF,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,6BAA6B,CAAC;AAM1F,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD,UAAU,iBAAiB;IACvB,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAMD,MAAM,WAAW,gBAAgB;IAC7B,eAAe,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAChD,iBAAiB,EAAE,UAAU,CAAC,OAAO,uBAAuB,CAAC,CAAC;IAC9D,yBAAyB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IACxD,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,MAAM,EAAE;QACJ,cAAc,EAAE,MAAM,CAAC;QACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,4BAA4B,CAAC,EAAE,MAAM,GAAG;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE,CAAC;QACxF,wBAAwB,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,MAAM,CAAC;YAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;SAAE,CAAC;QACxF,SAAS,EAAE,cAAc,CAAC;QAC1B,sBAAsB,CAAC,EAAE;YAAE,OAAO,EAAE,OAAO,CAAC;YAAC,YAAY,EAAE,MAAM,CAAA;SAAE,CAAC;KACvE,CAAC;IACF,MAAM,EAAE,MAAM,CAAC;IAIf,EAAE,EAAE,OAAO,qBAAqB,EAAE,QAAQ,CAAC;IAC3C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2FAA2F;IAC3F,sBAAsB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,oBAAoB,EAAE,aAAa,CAAC,CAAC;IACjF,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,kBAAkB,CAAC;IAClE;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACvC;AAqID,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,IACvC,OAAO;IAAE,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,KAAG,OAAO,CAAC,IAAI,CAAC,CAmgBzF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAIxE,OAAO,EACH,KAAK,eAAe,EAYvB,MAAM,sCAAsC,CAAC;
|
|
1
|
+
{"version":3,"file":"transform.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/transform.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wCAAwC,CAAC;AAIxE,OAAO,EACH,KAAK,eAAe,EAYvB,MAAM,sCAAsC,CAAC;AAe9C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qCAAqC,CAAC;AAMlE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AACvE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAsBxD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAsE1D,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAOnF;AAyCD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC1C,SAAS,EAAE,MAAM,GAClB,GAAG,CAAC,MAAM,EAAE;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAEzD;AA6DD,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,eAAe,EAAE,GAAG,CAChB,MAAM,EACN;QAAE,KAAK,EAAE,YAAY,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;KAAE,CACxE,CAAC;IACF,EAAE,EAAE,eAAe,CAAC;IACpB;;;;;OAKG;IACH,sBAAsB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,oBAAoB,EAAE,aAAa,CAAC,CAAC;IACjF,aAAa,EAAE,MAAM,CAAC;IACtB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,0EAA0E;IAC1E,oBAAoB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE;;;;;OAKG;IACH,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC,8BAA8B,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7C;;;;OAIG;IACH,8BAA8B,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC5C,+BAA+B,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC9C,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,kBAAkB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE;QACX,OAAO,EAAE,OAAO,CAAC;QACjB,qBAAqB,EAAE,MAAM,CAAC;QAC9B;;iFAEyE;QACzE,WAAW,EAAE,OAAO,CAAC;KACxB,CAAC;IACF,uBAAuB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpF;;;;;OAKG;IACH,uBAAuB,CAAC,EAAE,MAAM,MAAM,CAAC;IACvC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,0BAA0B,CAAC,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;KAAE,CAAC;IACtF,sBAAsB,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,0DAA0D;IAC1D,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,iFAAiF;IACjF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qBAAqB,CAAC,EAAE,CACpB,SAAS,EAAE,MAAM,KAChB,OAAO,6BAA6B,EAAE,kBAAkB,CAAC;IAC9D,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IAC/D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IAEnC;;kEAE8D;IAC9D,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC;yFACqF;IACrF,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;;;OAKG;IACH,yBAAyB,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD;;;;;;;OAOG;IACH,qBAAqB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IACpC;;;+CAG2C;IAC3C,UAAU,CAAC,EAAE;QACT,OAAO,EAAE,OAAO,CAAC;QACjB,cAAc,EAAE,MAAM,CAAC;QACvB,cAAc,EAAE,MAAM,CAAC;QACvB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,uBAAuB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACvF,CAAC;IACF;;;;;;OAMG;IACH,sBAAsB,CAAC,EAAE;QACrB,OAAO,EAAE,OAAO,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,6EAA6E;IAC7E,qBAAqB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,aAAa,IAQ3C,QAAQ,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,EAC7B,QAAQ;IAAE,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,KAChC,OAAO,CAAC,IAAI,CAAC,CAysDnB;AAED,wBAAgB,0BAA0B,CACtC,uBAAuB,EAAE,MAAM,GAAG,SAAS,EAC3C,YAAY,EAAE,YAAY,EAC1B,0BAA0B,EACpB,MAAM,GACN;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAC/C,SAAS,EACf,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,sBAAsB,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,EACrF,oBAAoB,CAAC,EAAE,MAAM,GAC9B,MAAM,GAAG,SAAS,CAqCpB"}
|
package/dist/index.js
CHANGED
|
@@ -151003,7 +151003,7 @@ function enforceSchemaFence(db, dbPath, latestSupportedVersion) {
|
|
|
151003
151003
|
return true;
|
|
151004
151004
|
}
|
|
151005
151005
|
lastSchemaFenceRejection = { persistedVersion, supportedVersion: latestSupportedVersion };
|
|
151006
|
-
log(`[magic-context] storage fatal: refusing to open ${dbPath}; database schema v${persistedVersion} is newer than this binary supports (max v${latestSupportedVersion}).
|
|
151006
|
+
log(`[magic-context] storage fatal: refusing to open ${dbPath}; database schema v${persistedVersion} is newer than this binary supports (max v${latestSupportedVersion}). A pinned or stale plugin is likely sharing this database with a newer instance; update or unpin Magic Context with 'npx @cortexkit/magic-context@latest doctor --force', then restart.`);
|
|
151007
151007
|
return false;
|
|
151008
151008
|
}
|
|
151009
151009
|
function setSqlitePragmaConfig(config2) {
|
|
@@ -165579,8 +165579,13 @@ async function sendSchemaFenceWarning(client, directory, detail) {
|
|
|
165579
165579
|
`newer build (OpenCode and Pi share one database). This build only supports`,
|
|
165580
165580
|
`up to v${detail.supportedVersion}, so it has fail-closed to avoid corrupting the cache.`,
|
|
165581
165581
|
"",
|
|
165582
|
-
"
|
|
165583
|
-
"
|
|
165582
|
+
"This usually means a pinned or stale plugin is sharing the database with a",
|
|
165583
|
+
"newer instance. Update or unpin Magic Context on this harness (or update",
|
|
165584
|
+
"OpenCode/Pi) to the latest version, then restart. The fastest fix is:",
|
|
165585
|
+
"",
|
|
165586
|
+
" npx @cortexkit/magic-context@latest doctor --force",
|
|
165587
|
+
"",
|
|
165588
|
+
"Your data is safe; nothing is disabled permanently."
|
|
165584
165589
|
].join(`
|
|
165585
165590
|
`);
|
|
165586
165591
|
try {
|
|
@@ -176602,6 +176607,14 @@ async function refreshModelLimitsFromApi(client, options) {
|
|
|
176602
176607
|
}
|
|
176603
176608
|
}
|
|
176604
176609
|
}
|
|
176610
|
+
async function refreshModelLimitsAfterAuthOnce(client) {
|
|
176611
|
+
if (authRewarmDone)
|
|
176612
|
+
return;
|
|
176613
|
+
authRewarmDone = true;
|
|
176614
|
+
const ok = await refreshModelLimitsOnce(client);
|
|
176615
|
+
if (!ok)
|
|
176616
|
+
authRewarmDone = false;
|
|
176617
|
+
}
|
|
176605
176618
|
async function refreshModelLimitsOnce(client) {
|
|
176606
176619
|
try {
|
|
176607
176620
|
const result = await client.config.providers();
|
|
@@ -176655,7 +176668,7 @@ function lookupLimitWithTagFallback(cache, providerID, modelID) {
|
|
|
176655
176668
|
}
|
|
176656
176669
|
return;
|
|
176657
176670
|
}
|
|
176658
|
-
var MIN_SANE_LIMIT = 20000, MAX_SANE_LIMIT = 3000000, apiCache = null, apiLoadedAt = 0, persistSeedLoaded = false;
|
|
176671
|
+
var MIN_SANE_LIMIT = 20000, MAX_SANE_LIMIT = 3000000, apiCache = null, apiLoadedAt = 0, persistSeedLoaded = false, authRewarmDone = false;
|
|
176659
176672
|
var init_models_dev_cache = __esm(() => {
|
|
176660
176673
|
init_data_path();
|
|
176661
176674
|
init_logger();
|
|
@@ -186475,7 +186488,7 @@ function resolveTuiConfigPath() {
|
|
|
186475
186488
|
return jsoncPath;
|
|
186476
186489
|
if (existsSync19(jsonPath))
|
|
186477
186490
|
return jsonPath;
|
|
186478
|
-
return
|
|
186491
|
+
return jsoncPath;
|
|
186479
186492
|
}
|
|
186480
186493
|
function ensureTuiPluginEntry() {
|
|
186481
186494
|
try {
|
|
@@ -186811,7 +186824,18 @@ function legacySourcesForBase(basePath, label) {
|
|
|
186811
186824
|
{ path: `${basePath}.json`, label: `${label} magic-context.json` }
|
|
186812
186825
|
];
|
|
186813
186826
|
}
|
|
186827
|
+
function userScopeConfigPaths() {
|
|
186828
|
+
return new Set([
|
|
186829
|
+
`${cortexKitUserConfigBasePath()}.jsonc`,
|
|
186830
|
+
`${cortexKitUserConfigBasePath()}.json`,
|
|
186831
|
+
join2(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.jsonc`),
|
|
186832
|
+
join2(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.json`),
|
|
186833
|
+
join2(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.jsonc`),
|
|
186834
|
+
join2(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.json`)
|
|
186835
|
+
]);
|
|
186836
|
+
}
|
|
186814
186837
|
function resolveLegacyConfigSources(directory) {
|
|
186838
|
+
const userPaths = userScopeConfigPaths();
|
|
186815
186839
|
return {
|
|
186816
186840
|
user: [
|
|
186817
186841
|
...legacySourcesForBase(join2(configHome(), "opencode", CONFIG_FILE_BASENAME), "OpenCode user"),
|
|
@@ -186821,7 +186845,7 @@ function resolveLegacyConfigSources(directory) {
|
|
|
186821
186845
|
...legacySourcesForBase(join2(directory, CONFIG_FILE_BASENAME), "project root"),
|
|
186822
186846
|
...legacySourcesForBase(join2(directory, ".opencode", CONFIG_FILE_BASENAME), "OpenCode project"),
|
|
186823
186847
|
...legacySourcesForBase(join2(directory, ".pi", CONFIG_FILE_BASENAME), "Pi project")
|
|
186824
|
-
]
|
|
186848
|
+
].filter((source) => !userPaths.has(source.path))
|
|
186825
186849
|
};
|
|
186826
186850
|
}
|
|
186827
186851
|
function resolveLegacyConfigSourcesForHarness(directory, harness) {
|
|
@@ -196678,6 +196702,7 @@ var pendingPiDecisionBySession = new Map;
|
|
|
196678
196702
|
var lastBoundMessageIdBySession = new Map;
|
|
196679
196703
|
var scheduledWriteTokensBySession = new Map;
|
|
196680
196704
|
var writerOverrideForTests = null;
|
|
196705
|
+
var retentionOverrideForTests = null;
|
|
196681
196706
|
function normalizeMaterializeReason(harness, reason, rematerialized) {
|
|
196682
196707
|
const raw = typeof reason === "string" ? reason.trim() : "";
|
|
196683
196708
|
if (raw.length > 0) {
|
|
@@ -196778,7 +196803,7 @@ function writeTransformDecisionRow(dbPath, row) {
|
|
|
196778
196803
|
WHERE session_id = ? AND harness = ?
|
|
196779
196804
|
ORDER BY ts_ms DESC, rowid DESC
|
|
196780
196805
|
LIMIT ?
|
|
196781
|
-
)`).run(row.sessionId, row.harness, row.sessionId, row.harness, TRANSFORM_DECISIONS_RETENTION);
|
|
196806
|
+
)`).run(row.sessionId, row.harness, row.sessionId, row.harness, retentionOverrideForTests ?? TRANSFORM_DECISIONS_RETENTION);
|
|
196782
196807
|
} finally {
|
|
196783
196808
|
closeQuietly(db);
|
|
196784
196809
|
}
|
|
@@ -197187,6 +197212,7 @@ init_session_project_storage();
|
|
|
197187
197212
|
init_storage_meta_persisted();
|
|
197188
197213
|
await init_storage();
|
|
197189
197214
|
init_logger();
|
|
197215
|
+
init_models_dev_cache();
|
|
197190
197216
|
|
|
197191
197217
|
// src/hooks/magic-context/boundary-execution.ts
|
|
197192
197218
|
var FORCE_MATERIALIZE_PERCENTAGE2 = 85;
|
|
@@ -201326,6 +201352,18 @@ function findLastAssistantModel2(messages) {
|
|
|
201326
201352
|
}
|
|
201327
201353
|
return null;
|
|
201328
201354
|
}
|
|
201355
|
+
function findNewestUserModel(messages) {
|
|
201356
|
+
for (let i = messages.length - 1;i >= 0; i--) {
|
|
201357
|
+
const info = messages[i].info;
|
|
201358
|
+
if (info.role !== "user")
|
|
201359
|
+
continue;
|
|
201360
|
+
if (info.model?.providerID && info.model.modelID) {
|
|
201361
|
+
return { providerID: info.model.providerID, modelID: info.model.modelID };
|
|
201362
|
+
}
|
|
201363
|
+
return null;
|
|
201364
|
+
}
|
|
201365
|
+
return null;
|
|
201366
|
+
}
|
|
201329
201367
|
function createTransform(deps) {
|
|
201330
201368
|
const loadedSessions = new Set;
|
|
201331
201369
|
const lastEmergencyNotificationCount = new Map;
|
|
@@ -201386,15 +201424,15 @@ function createTransform(deps) {
|
|
|
201386
201424
|
const canRunCompartments = fullFeatureMode && historianRunnable && deps.client !== undefined && compartmentDirectory.length > 0;
|
|
201387
201425
|
const fallbackModelId = deps.getFallbackModelId?.(sessionId);
|
|
201388
201426
|
const tModelDetect = performance.now();
|
|
201427
|
+
const persistedUsageBeforeResets = loadPersistedUsage(db, sessionId);
|
|
201389
201428
|
if (deps.liveModelBySession) {
|
|
201390
|
-
const
|
|
201391
|
-
if (
|
|
201392
|
-
|
|
201393
|
-
|
|
201394
|
-
|
|
201395
|
-
|
|
201396
|
-
sessionLog(sessionId, `transform: model change
|
|
201397
|
-
deps.liveModelBySession.set(sessionId, lastAssistantModel);
|
|
201429
|
+
const currentOutgoingModel = findNewestUserModel(messages) ?? deps.liveModelBySession.get(sessionId) ?? findLastAssistantModel2(messages);
|
|
201430
|
+
if (currentOutgoingModel) {
|
|
201431
|
+
deps.liveModelBySession.set(sessionId, currentOutgoingModel);
|
|
201432
|
+
const outgoingModelKey = resolveModelKey(currentOutgoingModel.providerID, currentOutgoingModel.modelID);
|
|
201433
|
+
const lastUsageModelKey = persistedUsageBeforeResets?.lastObservedModelKey ?? null;
|
|
201434
|
+
if (lastUsageModelKey != null && outgoingModelKey != null && lastUsageModelKey !== outgoingModelKey) {
|
|
201435
|
+
sessionLog(sessionId, `transform: model change since last usage (${lastUsageModelKey} -> ${outgoingModelKey}), clearing stale per-model state`);
|
|
201398
201436
|
updateSessionMeta(db, sessionId, {
|
|
201399
201437
|
lastContextPercentage: 0,
|
|
201400
201438
|
lastInputTokens: 0,
|
|
@@ -201424,7 +201462,6 @@ function createTransform(deps) {
|
|
|
201424
201462
|
const tFirstPass = performance.now();
|
|
201425
201463
|
const isFirstTransformPassForSession = !loadedSessions.has(sessionId);
|
|
201426
201464
|
loadedSessions.add(sessionId);
|
|
201427
|
-
const persistedUsageBeforeFirstPassReset = loadPersistedUsage(db, sessionId);
|
|
201428
201465
|
const historianFailureState = getHistorianFailureState(db, sessionId);
|
|
201429
201466
|
if (isFirstTransformPassForSession && sessionMeta) {
|
|
201430
201467
|
const persistedPct = sessionMeta.lastContextPercentage ?? 0;
|
|
@@ -201443,6 +201480,17 @@ function createTransform(deps) {
|
|
|
201443
201480
|
let emergencyRecoveryArmed = false;
|
|
201444
201481
|
if (fullFeatureMode) {
|
|
201445
201482
|
try {
|
|
201483
|
+
const armModel = deps.liveModelBySession?.get(sessionId);
|
|
201484
|
+
const armModelKey = deps.getModelKey?.(sessionId);
|
|
201485
|
+
const armSnapshot = persistedUsageBeforeResets;
|
|
201486
|
+
const lastMeasuredInput = armSnapshot?.usage.inputTokens ?? sessionMeta?.lastInputTokens ?? 0;
|
|
201487
|
+
const lastMeasuredModelKey = armSnapshot?.lastObservedModelKey ?? null;
|
|
201488
|
+
const armCatalogLimit = armModel ? getSdkContextLimit(armModel.providerID, armModel.modelID) : undefined;
|
|
201489
|
+
if (!sessionMeta?.isSubagent && armModel && typeof armCatalogLimit === "number" && armCatalogLimit > 0 && lastMeasuredInput > armCatalogLimit && lastMeasuredModelKey != null && armModelKey != null && lastMeasuredModelKey !== armModelKey && !getOverflowState(db, sessionId).needsEmergencyRecovery) {
|
|
201490
|
+
sessionLog(sessionId, `transform: last input ${lastMeasuredInput} (model ${lastMeasuredModelKey}) exceeds new model ${armModelKey} catalog limit ${armCatalogLimit}; arming overflow recovery proactively for the shrinking switch`);
|
|
201491
|
+
recordOverflowDetected(db, sessionId, undefined, armModelKey);
|
|
201492
|
+
resetProtectedTailNoEligibleHead(db, sessionId);
|
|
201493
|
+
}
|
|
201446
201494
|
const overflowState = getOverflowState(db, sessionId);
|
|
201447
201495
|
emergencyRecoveryArmed = overflowState.needsEmergencyRecovery;
|
|
201448
201496
|
if (contextUsageEarly.percentage < 80 && !overflowState.needsEmergencyRecovery) {
|
|
@@ -201479,7 +201527,7 @@ function createTransform(deps) {
|
|
|
201479
201527
|
sessionID: sessionId
|
|
201480
201528
|
}) : undefined;
|
|
201481
201529
|
const currentModelKeyForBoundary = deps.getModelKey?.(sessionId);
|
|
201482
|
-
const persistedUsageFreshForBoundary =
|
|
201530
|
+
const persistedUsageFreshForBoundary = persistedUsageBeforeResets && Date.now() - persistedUsageBeforeResets.updatedAt <= 10 * 60 * 1000 && (persistedUsageBeforeResets.lastObservedModelKey === null || currentModelKeyForBoundary === undefined || persistedUsageBeforeResets.lastObservedModelKey === currentModelKeyForBoundary) && (resolvedContextLimit === undefined || persistedUsageBeforeResets.lastUsageContextLimit === 0 || persistedUsageBeforeResets.lastUsageContextLimit === resolvedContextLimit) ? persistedUsageBeforeResets.usage : null;
|
|
201483
201531
|
const boundaryUsageForProtectedTail = persistedUsageFreshForBoundary ?? contextUsageEarly;
|
|
201484
201532
|
const boundaryUsageSource = persistedUsageFreshForBoundary ? "persisted" : "live";
|
|
201485
201533
|
const historyBudgetTokens = resolveHistoryBudgetTokens(deps.historyBudgetPercentage, contextUsageEarly, deps.executeThresholdPercentage, deps.getModelKey?.(sessionId), deps.executeThresholdTokens, resolvedContextLimit);
|
|
@@ -202317,6 +202365,9 @@ function createEventHandler2(deps) {
|
|
|
202317
202365
|
}
|
|
202318
202366
|
if (hasUsageTokens) {
|
|
202319
202367
|
const totalInputTokens = (info.tokens?.input ?? 0) + (info.tokens?.cache?.read ?? 0) + (info.tokens?.cache?.write ?? 0);
|
|
202368
|
+
if (deps.client) {
|
|
202369
|
+
await refreshModelLimitsAfterAuthOnce(deps.client);
|
|
202370
|
+
}
|
|
202320
202371
|
let contextLimit = resolveContextLimit(info.providerID, info.modelID, {
|
|
202321
202372
|
db: deps.db,
|
|
202322
202373
|
sessionID: info.sessionID
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"conflict-warning-hook.d.ts","sourceRoot":"","sources":["../../src/plugin/conflict-warning-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAsLlE;;GAEG;AACH,wBAAsB,mBAAmB,CACrC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,GAC/B,OAAO,CAAC,IAAI,CAAC,CAqDf;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAwHf;AAkCD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC1C,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAsEf;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IAAE,gBAAgB,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC/D,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"conflict-warning-hook.d.ts","sourceRoot":"","sources":["../../src/plugin/conflict-warning-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAsLlE;;GAEG;AACH,wBAAsB,mBAAmB,CACrC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,cAAc,GAC/B,OAAO,CAAC,IAAI,CAAC,CAqDf;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAwHf;AAkCD;;;GAGG;AACH,wBAAsB,wBAAwB,CAC1C,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC,CAsEf;AAED;;;;;;;;GAQG;AACH,wBAAsB,sBAAsB,CACxC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE;IAAE,gBAAgB,EAAE,MAAM,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAA;CAAE,GAC/D,OAAO,CAAC,IAAI,CAAC,CA2Cf;AAED;;;;;;;;GAQG;AACH,wBAAsB,uBAAuB,CACzC,MAAM,EAAE,OAAO,EACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,EAC/B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GACpC,OAAO,CAAC,IAAI,CAAC,CAkFf"}
|
|
@@ -59,6 +59,29 @@ export declare function refreshModelLimitsFromApi(client: OpencodeClientLike, op
|
|
|
59
59
|
retries?: number;
|
|
60
60
|
retryDelayMs?: number;
|
|
61
61
|
}): Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Re-warm the limit cache ONCE per process, after auth is provably live.
|
|
64
|
+
*
|
|
65
|
+
* The startup warm (index.ts) can run before the user's provider auth is
|
|
66
|
+
* loaded. When it does, an auth-conditional limit patch hasn't applied yet, so
|
|
67
|
+
* `config.providers()` returns the RAW catalog limit (e.g. OpenAI gpt-5.5 OAuth
|
|
68
|
+
* is downshifted to a 272k input cap by OpenCode's Codex auth plugin only when
|
|
69
|
+
* `ctx.auth.type === "oauth"`; before auth loads it reports the raw 922k). That
|
|
70
|
+
* too-high value gets cached AND persisted as last-known-good, survives
|
|
71
|
+
* restarts, and the existing recovery only re-resolves a too-LOW limit
|
|
72
|
+
* (overflow / `percentage > 100`), so a too-HIGH one never self-corrects: the
|
|
73
|
+
* sidebar shows huge headroom while the backend rejects at the real cap (#179).
|
|
74
|
+
*
|
|
75
|
+
* The first `message.updated` carrying usage tokens proves a request succeeded,
|
|
76
|
+
* so auth + providers are fully resolved. Re-warming there overwrites any stale
|
|
77
|
+
* pre-auth limit with the live auth-adjusted one. Idempotent and cheap: a single
|
|
78
|
+
* `config.providers()` round-trip, then a no-op for the rest of the process. The
|
|
79
|
+
* latch is set before the await so concurrent `message.updated` events don't
|
|
80
|
+
* stack duplicate warms; a failed warm resets it so a later message retries.
|
|
81
|
+
*/
|
|
82
|
+
export declare function refreshModelLimitsAfterAuthOnce(client: OpencodeClientLike): Promise<void>;
|
|
83
|
+
/** Test-only: reset the after-auth re-warm latch between cases. */
|
|
84
|
+
export declare function resetAuthRewarmLatchForTest(): void;
|
|
62
85
|
/**
|
|
63
86
|
* Resolve a model's prompt limit from OpenCode's SDK (`config.providers()`),
|
|
64
87
|
* the single source of truth: it already merges models.dev + compiled-in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,UAAU,kBAAkB;IACxB,MAAM,EAAE;QACJ,SAAS,EAAE,MAAM,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,SAAS,CAAC,EAAE,OAAO,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAChE,CAAC;CACL;AASD,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,cAAc,UAAY,CAAC;AAExC;;kFAEkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,MAAM,CAEtE;AA+HD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,yBAAyB,CAC3C,MAAM,EAAE,kBAAkB,EAC1B,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,IAAI,CAAC,CAUf;AA+DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1F;AA6BD,gFAAgF;AAChF,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAMA"}
|
|
1
|
+
{"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,UAAU,kBAAkB;IACxB,MAAM,EAAE;QACJ,SAAS,EAAE,MAAM,OAAO,CAAC;YAAE,IAAI,CAAC,EAAE;gBAAE,SAAS,CAAC,EAAE,OAAO,CAAA;aAAE,CAAA;SAAE,CAAC,CAAC;KAChE,CAAC;CACL;AASD,eAAO,MAAM,cAAc,QAAS,CAAC;AACrC,eAAO,MAAM,cAAc,UAAY,CAAC;AAExC;;kFAEkF;AAClF,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,KAAK,IAAI,MAAM,CAEtE;AA+HD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,yBAAyB,CAC3C,MAAM,EAAE,kBAAkB,EAC1B,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GACtD,OAAO,CAAC,IAAI,CAAC,CAUf;AAKD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,+BAA+B,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAK/F;AAED,mEAAmE;AACnE,wBAAgB,2BAA2B,IAAI,IAAI,CAElD;AA+DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI1F;AA6BD,gFAAgF;AAChF,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CACpB,CAMA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAyEH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAqD9C"}
|
|
@@ -3,7 +3,7 @@ export declare function getTuiPreferencesFile(): string;
|
|
|
3
3
|
export declare function readTuiPreferencesFile(): Promise<Record<string, unknown>>;
|
|
4
4
|
export declare function readTuiPreferencesFileSync(): Record<string, unknown>;
|
|
5
5
|
export declare const PLUGIN_KEY = "magic-context";
|
|
6
|
-
export declare const DEFAULT_SLOT_ORDER =
|
|
6
|
+
export declare const DEFAULT_SLOT_ORDER = 170;
|
|
7
7
|
export interface MagicContextTuiPrefs {
|
|
8
8
|
forceToTop: boolean;
|
|
9
9
|
order: number;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick a readable text color (black or white) for text drawn ON TOP of a given
|
|
3
|
+
* background color.
|
|
4
|
+
*
|
|
5
|
+
* The sidebar header badge previously drew its label with `fg={theme.background}`
|
|
6
|
+
* on a `theme.accent` background. That breaks for themes that set
|
|
7
|
+
* `background: "none"` (transparent) to respect terminal transparency: the
|
|
8
|
+
* resolved background is `RGBA(0,0,0,0)`, so the badge text renders fully
|
|
9
|
+
* transparent and disappears (issue #186). The badge background (`accent`) is
|
|
10
|
+
* always opaque, so deriving the text color from it is transparency-proof.
|
|
11
|
+
*
|
|
12
|
+
* The pick is WHITE-BIASED off the accent's relative luminance: white for any
|
|
13
|
+
* accent in the dark half (luminance < 0.5), black only for genuinely light
|
|
14
|
+
* accents. A strict "higher-contrast-wins" pick (crossover at luminance 0.179)
|
|
15
|
+
* flips ordinary mid-tone accents to black: a typical orange/amber sidebar
|
|
16
|
+
* accent (luminance ~0.3) reads black ~5:1 vs white ~3.7:1, so contrast-wins
|
|
17
|
+
* picks black even though white at ~3.7:1 is perfectly legible for a short bold
|
|
18
|
+
* label. That looks heavy and clashes with the sibling status badges, so we
|
|
19
|
+
* prefer white across the whole dark half and only fall to black once the accent
|
|
20
|
+
* is actually light (pale/pastel/near-white), where white would be unreadable.
|
|
21
|
+
*
|
|
22
|
+
* `RGBA` channels from @opentui/core are normalized 0..1 floats. We accept the
|
|
23
|
+
* minimal `{ r, g, b }` shape so this stays a pure, trivially testable function
|
|
24
|
+
* independent of the native color class.
|
|
25
|
+
*/
|
|
26
|
+
export declare function readableTextColorOn(bg: {
|
|
27
|
+
r: number;
|
|
28
|
+
g: number;
|
|
29
|
+
b: number;
|
|
30
|
+
}): string;
|
|
31
|
+
//# sourceMappingURL=badge-contrast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"badge-contrast.d.ts","sourceRoot":"","sources":["../../src/tui/badge-contrast.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAmBH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAEnF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cortexkit/opencode-magic-context",
|
|
3
|
-
"version": "0.27.
|
|
3
|
+
"version": "0.27.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenCode plugin for Magic Context — cross-session memory and context management",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -44,9 +44,12 @@
|
|
|
44
44
|
"@jitl/quickjs-singlefile-cjs-release-asyncify": "0.32.0",
|
|
45
45
|
"@opencode-ai/plugin": "^1.15.13",
|
|
46
46
|
"@opencode-ai/sdk": "^1.15.13",
|
|
47
|
+
"@opentui/core": "^0.4.2",
|
|
48
|
+
"@opentui/solid": "^0.4.2",
|
|
47
49
|
"ai-tokenizer": "^1.0.6",
|
|
48
50
|
"comment-json": "^4.2.5",
|
|
49
51
|
"quickjs-emscripten": "^0.32.0",
|
|
52
|
+
"solid-js": "1.9.12",
|
|
50
53
|
"zod": "^4.1.8"
|
|
51
54
|
},
|
|
52
55
|
"devDependencies": {
|
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
clearModelsDevCache,
|
|
7
7
|
getModelsDevCacheState,
|
|
8
8
|
getSdkContextLimit,
|
|
9
|
+
refreshModelLimitsAfterAuthOnce,
|
|
9
10
|
refreshModelLimitsFromApi,
|
|
11
|
+
resetAuthRewarmLatchForTest,
|
|
10
12
|
} from "./models-dev-cache";
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -198,6 +200,86 @@ describe("models-dev-cache (SDK-only)", () => {
|
|
|
198
200
|
});
|
|
199
201
|
});
|
|
200
202
|
|
|
203
|
+
describe("after-auth re-warm (once per process)", () => {
|
|
204
|
+
// The startup warm can run before provider auth is loaded, caching a raw
|
|
205
|
+
// pre-downshift limit (gpt-5.5 922k) that then survives restarts and is
|
|
206
|
+
// never corrected by the too-low-only recovery. The first usage event
|
|
207
|
+
// proves auth is live; one re-warm there overwrites the stale value.
|
|
208
|
+
function makeCountingClient(input: number) {
|
|
209
|
+
let calls = 0;
|
|
210
|
+
return {
|
|
211
|
+
client: {
|
|
212
|
+
config: {
|
|
213
|
+
providers: async () => {
|
|
214
|
+
calls++;
|
|
215
|
+
return {
|
|
216
|
+
data: {
|
|
217
|
+
providers: [
|
|
218
|
+
{
|
|
219
|
+
id: "openai",
|
|
220
|
+
models: { "gpt-5.5": { limit: { input } } },
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
calls: () => calls,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
test("re-warm overwrites a stale pre-auth limit and runs only once per process", async () => {
|
|
233
|
+
resetAuthRewarmLatchForTest();
|
|
234
|
+
// Pre-auth startup warm cached the raw 922k.
|
|
235
|
+
await refreshModelLimitsFromApi(
|
|
236
|
+
makeClient([{ id: "openai", models: { "gpt-5.5": { limit: { input: 922000 } } } }]),
|
|
237
|
+
);
|
|
238
|
+
expect(getSdkContextLimit("openai", "gpt-5.5")).toBe(922000);
|
|
239
|
+
|
|
240
|
+
// First usage: auth is live, providers() now reports the 272k cap.
|
|
241
|
+
const { client, calls } = makeCountingClient(272000);
|
|
242
|
+
await refreshModelLimitsAfterAuthOnce(client);
|
|
243
|
+
expect(getSdkContextLimit("openai", "gpt-5.5")).toBe(272000);
|
|
244
|
+
expect(calls()).toBe(1);
|
|
245
|
+
|
|
246
|
+
// Subsequent usage events are a no-op (latch held).
|
|
247
|
+
await refreshModelLimitsAfterAuthOnce(client);
|
|
248
|
+
await refreshModelLimitsAfterAuthOnce(client);
|
|
249
|
+
expect(calls()).toBe(1);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("a failed re-warm resets the latch so a later usage event retries", async () => {
|
|
253
|
+
resetAuthRewarmLatchForTest();
|
|
254
|
+
let calls = 0;
|
|
255
|
+
const flaky = {
|
|
256
|
+
config: {
|
|
257
|
+
providers: async () => {
|
|
258
|
+
calls++;
|
|
259
|
+
// First attempt: empty payload (auth still settling) → no warm.
|
|
260
|
+
if (calls === 1) return { data: { providers: [] } };
|
|
261
|
+
return {
|
|
262
|
+
data: {
|
|
263
|
+
providers: [
|
|
264
|
+
{
|
|
265
|
+
id: "openai",
|
|
266
|
+
models: { "gpt-5.5": { limit: { input: 272000 } } },
|
|
267
|
+
},
|
|
268
|
+
],
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
await refreshModelLimitsAfterAuthOnce(flaky);
|
|
275
|
+
expect(getSdkContextLimit("openai", "gpt-5.5")).toBeUndefined();
|
|
276
|
+
// Latch was reset on failure, so the next event retries and succeeds.
|
|
277
|
+
await refreshModelLimitsAfterAuthOnce(flaky);
|
|
278
|
+
expect(getSdkContextLimit("openai", "gpt-5.5")).toBe(272000);
|
|
279
|
+
expect(calls).toBe(2);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
201
283
|
describe("startup retry", () => {
|
|
202
284
|
test("retries when the provider payload is empty, then succeeds", async () => {
|
|
203
285
|
let calls = 0;
|
|
@@ -210,6 +210,41 @@ export async function refreshModelLimitsFromApi(
|
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
212
|
|
|
213
|
+
// Once-per-process latch for the after-auth re-warm below.
|
|
214
|
+
let authRewarmDone = false;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Re-warm the limit cache ONCE per process, after auth is provably live.
|
|
218
|
+
*
|
|
219
|
+
* The startup warm (index.ts) can run before the user's provider auth is
|
|
220
|
+
* loaded. When it does, an auth-conditional limit patch hasn't applied yet, so
|
|
221
|
+
* `config.providers()` returns the RAW catalog limit (e.g. OpenAI gpt-5.5 OAuth
|
|
222
|
+
* is downshifted to a 272k input cap by OpenCode's Codex auth plugin only when
|
|
223
|
+
* `ctx.auth.type === "oauth"`; before auth loads it reports the raw 922k). That
|
|
224
|
+
* too-high value gets cached AND persisted as last-known-good, survives
|
|
225
|
+
* restarts, and the existing recovery only re-resolves a too-LOW limit
|
|
226
|
+
* (overflow / `percentage > 100`), so a too-HIGH one never self-corrects: the
|
|
227
|
+
* sidebar shows huge headroom while the backend rejects at the real cap (#179).
|
|
228
|
+
*
|
|
229
|
+
* The first `message.updated` carrying usage tokens proves a request succeeded,
|
|
230
|
+
* so auth + providers are fully resolved. Re-warming there overwrites any stale
|
|
231
|
+
* pre-auth limit with the live auth-adjusted one. Idempotent and cheap: a single
|
|
232
|
+
* `config.providers()` round-trip, then a no-op for the rest of the process. The
|
|
233
|
+
* latch is set before the await so concurrent `message.updated` events don't
|
|
234
|
+
* stack duplicate warms; a failed warm resets it so a later message retries.
|
|
235
|
+
*/
|
|
236
|
+
export async function refreshModelLimitsAfterAuthOnce(client: OpencodeClientLike): Promise<void> {
|
|
237
|
+
if (authRewarmDone) return;
|
|
238
|
+
authRewarmDone = true;
|
|
239
|
+
const ok = await refreshModelLimitsOnce(client);
|
|
240
|
+
if (!ok) authRewarmDone = false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Test-only: reset the after-auth re-warm latch between cases. */
|
|
244
|
+
export function resetAuthRewarmLatchForTest(): void {
|
|
245
|
+
authRewarmDone = false;
|
|
246
|
+
}
|
|
247
|
+
|
|
213
248
|
/** Single SDK fetch + cache rebuild. Returns true when providers were loaded. */
|
|
214
249
|
async function refreshModelLimitsOnce(client: OpencodeClientLike): Promise<boolean> {
|
|
215
250
|
try {
|
|
@@ -60,4 +60,47 @@ describe("ensureTuiPluginEntry", () => {
|
|
|
60
60
|
expect(entry[0]).toBe("@cortexkit/opencode-magic-context@latest");
|
|
61
61
|
expect(entry[1]).toEqual({ enabled: true });
|
|
62
62
|
});
|
|
63
|
+
|
|
64
|
+
it("creates tui.jsonc (not tui.json) on a fresh install", async () => {
|
|
65
|
+
const root = mkdtempSync(join(tmpdir(), "mc-tui-fresh-"));
|
|
66
|
+
roots.push(root);
|
|
67
|
+
process.env.OPENCODE_CONFIG_DIR = root;
|
|
68
|
+
|
|
69
|
+
const { ensureTuiPluginEntry } = await import("./tui-config");
|
|
70
|
+
expect(ensureTuiPluginEntry()).toBe(true);
|
|
71
|
+
|
|
72
|
+
// The new file must be tui.jsonc so a tui.json stub never ends up
|
|
73
|
+
// sitting next to a tui.jsonc the user writes later (#176).
|
|
74
|
+
expect(existsSync(join(root, "tui.jsonc"))).toBe(true);
|
|
75
|
+
expect(existsSync(join(root, "tui.json"))).toBe(false);
|
|
76
|
+
const parsed = JSON.parse(readFileSync(join(root, "tui.jsonc"), "utf-8")) as {
|
|
77
|
+
plugin: unknown[];
|
|
78
|
+
};
|
|
79
|
+
expect(parsed.plugin).toContain("@cortexkit/opencode-magic-context@latest");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("writes into the existing tui.jsonc when both files exist", async () => {
|
|
83
|
+
const root = mkdtempSync(join(tmpdir(), "mc-tui-both-"));
|
|
84
|
+
roots.push(root);
|
|
85
|
+
process.env.OPENCODE_CONFIG_DIR = root;
|
|
86
|
+
// A real user config in tui.jsonc plus a leftover empty tui.json.
|
|
87
|
+
writeFileSync(
|
|
88
|
+
join(root, "tui.jsonc"),
|
|
89
|
+
`${JSON.stringify({ keybinds: { x: "y" } }, null, 2)}\n`,
|
|
90
|
+
);
|
|
91
|
+
writeFileSync(join(root, "tui.json"), "{}\n");
|
|
92
|
+
|
|
93
|
+
const { ensureTuiPluginEntry } = await import("./tui-config");
|
|
94
|
+
expect(ensureTuiPluginEntry()).toBe(true);
|
|
95
|
+
|
|
96
|
+
// The plugin entry must land in tui.jsonc (higher precedence), and the
|
|
97
|
+
// user's keybinds must survive; tui.json must be left untouched.
|
|
98
|
+
const jsonc = JSON.parse(readFileSync(join(root, "tui.jsonc"), "utf-8")) as {
|
|
99
|
+
plugin: unknown[];
|
|
100
|
+
keybinds: Record<string, string>;
|
|
101
|
+
};
|
|
102
|
+
expect(jsonc.plugin).toContain("@cortexkit/opencode-magic-context@latest");
|
|
103
|
+
expect(jsonc.keybinds).toEqual({ x: "y" });
|
|
104
|
+
expect(readFileSync(join(root, "tui.json"), "utf-8")).toBe("{}\n");
|
|
105
|
+
});
|
|
63
106
|
});
|
package/src/shared/tui-config.ts
CHANGED
|
@@ -62,9 +62,16 @@ function resolveTuiConfigPath(): string {
|
|
|
62
62
|
const jsoncPath = join(configDir, "tui.jsonc");
|
|
63
63
|
const jsonPath = join(configDir, "tui.json");
|
|
64
64
|
|
|
65
|
+
// OpenCode loads BOTH tui.json and tui.jsonc and merges them (tui.json first,
|
|
66
|
+
// tui.jsonc second, so .jsonc wins overlapping keys; plugin origins are
|
|
67
|
+
// deduped). So an existing tui.jsonc is the higher-precedence, user-facing
|
|
68
|
+
// file — write into it when present. Otherwise update an existing tui.json.
|
|
69
|
+
// For a fresh install create tui.jsonc, not tui.json: it lets the user add
|
|
70
|
+
// comments later and avoids leaving a second, lower-precedence config file
|
|
71
|
+
// alongside a tui.jsonc they create afterward (#176).
|
|
65
72
|
if (existsSync(jsoncPath)) return jsoncPath;
|
|
66
73
|
if (existsSync(jsonPath)) return jsonPath;
|
|
67
|
-
return
|
|
74
|
+
return jsoncPath;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
/**
|
|
@@ -11,7 +11,7 @@ import { parse, stringify } from "comment-json";
|
|
|
11
11
|
// Cross-plugin convention (anthropic-auth / aft / magic-context all mirror it):
|
|
12
12
|
// - same file name + env override + lookup order,
|
|
13
13
|
// - byte-identical `computeEffectiveOrder` so the three sort consistently,
|
|
14
|
-
// - a coordinated default-order ladder (anthropic-auth 160,
|
|
14
|
+
// - a coordinated default-order ladder (anthropic-auth 160, MC 170, AFT 180).
|
|
15
15
|
//
|
|
16
16
|
// MC uses `comment-json` (already a dep, Bun-safe) for the WRITE path — a full
|
|
17
17
|
// parse → mutate-one-key → stringify round-trip that preserves comments and
|
|
@@ -64,7 +64,7 @@ export function readTuiPreferencesFileSync(): Record<string, unknown> {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export const PLUGIN_KEY = "magic-context";
|
|
67
|
-
export const DEFAULT_SLOT_ORDER =
|
|
67
|
+
export const DEFAULT_SLOT_ORDER = 170;
|
|
68
68
|
|
|
69
69
|
export interface MagicContextTuiPrefs {
|
|
70
70
|
forceToTop: boolean;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { readableTextColorOn } from "./badge-contrast";
|
|
3
|
+
|
|
4
|
+
describe("readableTextColorOn", () => {
|
|
5
|
+
test("dark accent gets white text", () => {
|
|
6
|
+
// A typical dark accent (deep blue/purple) should read as white.
|
|
7
|
+
expect(readableTextColorOn({ r: 0.1, g: 0.1, b: 0.3 })).toBe("#ffffff");
|
|
8
|
+
expect(readableTextColorOn({ r: 0, g: 0, b: 0 })).toBe("#ffffff");
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("light accent gets black text", () => {
|
|
12
|
+
// Only accents pale enough that white fails the contrast bar go black.
|
|
13
|
+
expect(readableTextColorOn({ r: 0.9, g: 0.9, b: 0.7 })).toBe("#000000");
|
|
14
|
+
expect(readableTextColorOn({ r: 1, g: 1, b: 1 })).toBe("#000000");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("mid-tone orange accent prefers white (white-bias, matches sibling badges)", () => {
|
|
18
|
+
// A typical orange/amber accent reads white ~4:1 and black ~5:1. A
|
|
19
|
+
// higher-contrast-wins pick would flip it to black (the #186 over-
|
|
20
|
+
// correction); the white bias keeps it white, clearing the bold-text bar.
|
|
21
|
+
expect(readableTextColorOn({ r: 0.69, g: 0.455, b: 0.188 })).toBe("#ffffff");
|
|
22
|
+
expect(readableTextColorOn({ r: 0.741, g: 0.482, b: 0.2 })).toBe("#ffffff");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("pure green is treated as light (white fails the contrast bar)", () => {
|
|
26
|
+
// A saturated green is bright enough that white drops below the bar.
|
|
27
|
+
expect(readableTextColorOn({ r: 0, g: 1, b: 0 })).toBe("#000000");
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("pure blue is treated as dark (low luma weight)", () => {
|
|
31
|
+
// Blue contributes little to perceived brightness, so a saturated blue
|
|
32
|
+
// badge needs light text.
|
|
33
|
+
expect(readableTextColorOn({ r: 0, g: 0, b: 1 })).toBe("#ffffff");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("does not depend on the (possibly transparent) background alpha", () => {
|
|
37
|
+
// The helper only reads r/g/b — the regression in #186 was using a
|
|
38
|
+
// background color whose alpha could be 0. Two accents with identical
|
|
39
|
+
// rgb resolve identically regardless of any alpha the caller might pass.
|
|
40
|
+
const a = readableTextColorOn({ r: 0.2, g: 0.2, b: 0.2 });
|
|
41
|
+
const b = readableTextColorOn({ r: 0.2, g: 0.2, b: 0.2 });
|
|
42
|
+
expect(a).toBe(b);
|
|
43
|
+
expect(a).toBe("#ffffff");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pick a readable text color (black or white) for text drawn ON TOP of a given
|
|
3
|
+
* background color.
|
|
4
|
+
*
|
|
5
|
+
* The sidebar header badge previously drew its label with `fg={theme.background}`
|
|
6
|
+
* on a `theme.accent` background. That breaks for themes that set
|
|
7
|
+
* `background: "none"` (transparent) to respect terminal transparency: the
|
|
8
|
+
* resolved background is `RGBA(0,0,0,0)`, so the badge text renders fully
|
|
9
|
+
* transparent and disappears (issue #186). The badge background (`accent`) is
|
|
10
|
+
* always opaque, so deriving the text color from it is transparency-proof.
|
|
11
|
+
*
|
|
12
|
+
* The pick is WHITE-BIASED off the accent's relative luminance: white for any
|
|
13
|
+
* accent in the dark half (luminance < 0.5), black only for genuinely light
|
|
14
|
+
* accents. A strict "higher-contrast-wins" pick (crossover at luminance 0.179)
|
|
15
|
+
* flips ordinary mid-tone accents to black: a typical orange/amber sidebar
|
|
16
|
+
* accent (luminance ~0.3) reads black ~5:1 vs white ~3.7:1, so contrast-wins
|
|
17
|
+
* picks black even though white at ~3.7:1 is perfectly legible for a short bold
|
|
18
|
+
* label. That looks heavy and clashes with the sibling status badges, so we
|
|
19
|
+
* prefer white across the whole dark half and only fall to black once the accent
|
|
20
|
+
* is actually light (pale/pastel/near-white), where white would be unreadable.
|
|
21
|
+
*
|
|
22
|
+
* `RGBA` channels from @opentui/core are normalized 0..1 floats. We accept the
|
|
23
|
+
* minimal `{ r, g, b }` shape so this stays a pure, trivially testable function
|
|
24
|
+
* independent of the native color class.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// Luminance midpoint: accents below this keep white text, accents at/above it
|
|
28
|
+
// (light/pastel/near-white) get black. White-biased relative to the strict
|
|
29
|
+
// equal-contrast crossover (~0.179) so saturated mid-tone accents stay white.
|
|
30
|
+
const LIGHT_ACCENT_LUMINANCE = 0.5;
|
|
31
|
+
|
|
32
|
+
function srgbChannelToLinear(c: number): number {
|
|
33
|
+
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function relativeLuminance(bg: { r: number; g: number; b: number }): number {
|
|
37
|
+
return (
|
|
38
|
+
0.2126 * srgbChannelToLinear(bg.r) +
|
|
39
|
+
0.7152 * srgbChannelToLinear(bg.g) +
|
|
40
|
+
0.0722 * srgbChannelToLinear(bg.b)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function readableTextColorOn(bg: { r: number; g: number; b: number }): string {
|
|
45
|
+
return relativeLuminance(bg) < LIGHT_ACCENT_LUMINANCE ? "#ffffff" : "#000000";
|
|
46
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { Show, createEffect, createMemo, createSignal, on, onCleanup } from "solid-js"
|
|
3
3
|
import type { TuiSlotPlugin, TuiPluginApi, TuiThemeCurrent } from "@opencode-ai/plugin/tui"
|
|
4
4
|
import packageJson from "../../../package.json"
|
|
5
|
+
import { readableTextColorOn } from '../badge-contrast';
|
|
5
6
|
import { loadSidebarSnapshot, type SidebarSnapshot } from "../data/context-db"
|
|
6
7
|
import { formatThresholdPercent } from "../../shared/format-threshold"
|
|
7
8
|
import {
|
|
@@ -694,7 +695,7 @@ const SidebarContent = (props: {
|
|
|
694
695
|
onMouseDown={() => props.controller.toggleCollapsed()}
|
|
695
696
|
>
|
|
696
697
|
<box paddingLeft={1} paddingRight={1} backgroundColor={props.theme.accent}>
|
|
697
|
-
<text fg={props.theme.
|
|
698
|
+
<text fg={readableTextColorOn(props.theme.accent)}>
|
|
698
699
|
<b>{collapsed() ? "▶ " : "▼ "}{headerLabel()}</b>
|
|
699
700
|
</text>
|
|
700
701
|
</box>
|