@cortexkit/opencode-magic-context 0.27.1 → 0.27.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
@@ -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
 
@@ -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;AASD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,GAAG;IAC3D,IAAI,EAAE,kBAAkB,EAAE,CAAC;IAC3B,OAAO,EAAE,kBAAkB,EAAE,CAAC;CACjC,CAqBA;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"}
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"}
@@ -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;AAqBpF,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,CAyfzF"}
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"}
package/dist/index.js CHANGED
@@ -176602,6 +176602,14 @@ async function refreshModelLimitsFromApi(client, options) {
176602
176602
  }
176603
176603
  }
176604
176604
  }
176605
+ async function refreshModelLimitsAfterAuthOnce(client) {
176606
+ if (authRewarmDone)
176607
+ return;
176608
+ authRewarmDone = true;
176609
+ const ok = await refreshModelLimitsOnce(client);
176610
+ if (!ok)
176611
+ authRewarmDone = false;
176612
+ }
176605
176613
  async function refreshModelLimitsOnce(client) {
176606
176614
  try {
176607
176615
  const result = await client.config.providers();
@@ -176655,7 +176663,7 @@ function lookupLimitWithTagFallback(cache, providerID, modelID) {
176655
176663
  }
176656
176664
  return;
176657
176665
  }
176658
- var MIN_SANE_LIMIT = 20000, MAX_SANE_LIMIT = 3000000, apiCache = null, apiLoadedAt = 0, persistSeedLoaded = false;
176666
+ var MIN_SANE_LIMIT = 20000, MAX_SANE_LIMIT = 3000000, apiCache = null, apiLoadedAt = 0, persistSeedLoaded = false, authRewarmDone = false;
176659
176667
  var init_models_dev_cache = __esm(() => {
176660
176668
  init_data_path();
176661
176669
  init_logger();
@@ -186475,7 +186483,7 @@ function resolveTuiConfigPath() {
186475
186483
  return jsoncPath;
186476
186484
  if (existsSync19(jsonPath))
186477
186485
  return jsonPath;
186478
- return jsonPath;
186486
+ return jsoncPath;
186479
186487
  }
186480
186488
  function ensureTuiPluginEntry() {
186481
186489
  try {
@@ -186811,7 +186819,18 @@ function legacySourcesForBase(basePath, label) {
186811
186819
  { path: `${basePath}.json`, label: `${label} magic-context.json` }
186812
186820
  ];
186813
186821
  }
186822
+ function userScopeConfigPaths() {
186823
+ return new Set([
186824
+ `${cortexKitUserConfigBasePath()}.jsonc`,
186825
+ `${cortexKitUserConfigBasePath()}.json`,
186826
+ join2(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.jsonc`),
186827
+ join2(configHome(), "opencode", `${CONFIG_FILE_BASENAME}.json`),
186828
+ join2(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.jsonc`),
186829
+ join2(homeDir(), ".pi", "agent", `${CONFIG_FILE_BASENAME}.json`)
186830
+ ]);
186831
+ }
186814
186832
  function resolveLegacyConfigSources(directory) {
186833
+ const userPaths = userScopeConfigPaths();
186815
186834
  return {
186816
186835
  user: [
186817
186836
  ...legacySourcesForBase(join2(configHome(), "opencode", CONFIG_FILE_BASENAME), "OpenCode user"),
@@ -186821,7 +186840,7 @@ function resolveLegacyConfigSources(directory) {
186821
186840
  ...legacySourcesForBase(join2(directory, CONFIG_FILE_BASENAME), "project root"),
186822
186841
  ...legacySourcesForBase(join2(directory, ".opencode", CONFIG_FILE_BASENAME), "OpenCode project"),
186823
186842
  ...legacySourcesForBase(join2(directory, ".pi", CONFIG_FILE_BASENAME), "Pi project")
186824
- ]
186843
+ ].filter((source) => !userPaths.has(source.path))
186825
186844
  };
186826
186845
  }
186827
186846
  function resolveLegacyConfigSourcesForHarness(directory, harness) {
@@ -202317,6 +202336,9 @@ function createEventHandler2(deps) {
202317
202336
  }
202318
202337
  if (hasUsageTokens) {
202319
202338
  const totalInputTokens = (info.tokens?.input ?? 0) + (info.tokens?.cache?.read ?? 0) + (info.tokens?.cache?.write ?? 0);
202339
+ if (deps.client) {
202340
+ await refreshModelLimitsAfterAuthOnce(deps.client);
202341
+ }
202320
202342
  let contextLimit = resolveContextLimit(info.providerID, info.modelID, {
202321
202343
  db: deps.db,
202322
202344
  sessionID: info.sessionID
@@ -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;AAkEH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAqD9C"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.27.1",
3
+ "version": "0.27.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",
@@ -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
  });
@@ -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 jsonPath; // default: create tui.json
74
+ return jsoncPath;
68
75
  }
69
76
 
70
77
  /**