@cortexkit/opencode-magic-context 0.22.0 → 0.22.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.
Files changed (69) hide show
  1. package/README.md +10 -0
  2. package/dist/config/agent-disable.d.ts +0 -9
  3. package/dist/config/agent-disable.d.ts.map +1 -1
  4. package/dist/config/index.d.ts.map +1 -1
  5. package/dist/config/schema/agent-overrides.d.ts +0 -3
  6. package/dist/config/schema/agent-overrides.d.ts.map +1 -1
  7. package/dist/config/schema/magic-context.d.ts +15 -0
  8. package/dist/config/schema/magic-context.d.ts.map +1 -1
  9. package/dist/features/builtin-commands/types.d.ts +0 -2
  10. package/dist/features/builtin-commands/types.d.ts.map +1 -1
  11. package/dist/features/magic-context/dreamer/scheduler.d.ts +0 -4
  12. package/dist/features/magic-context/dreamer/scheduler.d.ts.map +1 -1
  13. package/dist/features/magic-context/git-commits/index.d.ts +1 -0
  14. package/dist/features/magic-context/git-commits/index.d.ts.map +1 -1
  15. package/dist/features/magic-context/git-commits/storage-git-commits.d.ts.map +1 -1
  16. package/dist/features/magic-context/git-commits/sweep-coordinator.d.ts +48 -0
  17. package/dist/features/magic-context/git-commits/sweep-coordinator.d.ts.map +1 -0
  18. package/dist/features/magic-context/key-files/storage-key-files.d.ts +0 -5
  19. package/dist/features/magic-context/key-files/storage-key-files.d.ts.map +1 -1
  20. package/dist/features/magic-context/literal-probes.d.ts +24 -0
  21. package/dist/features/magic-context/literal-probes.d.ts.map +1 -0
  22. package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
  23. package/dist/features/magic-context/memory/embedding-openai.d.ts +6 -0
  24. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  25. package/dist/features/magic-context/memory/embedding-probe.d.ts +5 -0
  26. package/dist/features/magic-context/memory/embedding-probe.d.ts.map +1 -1
  27. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  28. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  29. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  30. package/dist/features/magic-context/search.d.ts +7 -0
  31. package/dist/features/magic-context/search.d.ts.map +1 -1
  32. package/dist/features/magic-context/storage-db.d.ts +1 -1
  33. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  34. package/dist/features/magic-context/storage-notes.d.ts +8 -0
  35. package/dist/features/magic-context/storage-notes.d.ts.map +1 -1
  36. package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
  37. package/dist/hooks/magic-context/compartment-runner-types.d.ts +14 -1
  38. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  39. package/dist/hooks/magic-context/derive-budgets.d.ts +3 -3
  40. package/dist/hooks/magic-context/event-resolvers.d.ts +1 -0
  41. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  42. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +7 -2
  43. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +667 -266
  46. package/dist/plugin/dream-timer.d.ts.map +1 -1
  47. package/dist/plugin/event.d.ts +10 -0
  48. package/dist/plugin/event.d.ts.map +1 -1
  49. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  50. package/dist/shared/announcement.d.ts +16 -0
  51. package/dist/shared/announcement.d.ts.map +1 -1
  52. package/dist/shared/models-dev-cache.d.ts +54 -27
  53. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  54. package/dist/shared/rpc-types.d.ts +3 -1
  55. package/dist/shared/rpc-types.d.ts.map +1 -1
  56. package/dist/tools/ctx-note/tools.d.ts.map +1 -1
  57. package/dist/tools/ctx-search/tools.d.ts.map +1 -1
  58. package/package.json +4 -4
  59. package/src/shared/announcement.test.ts +23 -7
  60. package/src/shared/announcement.ts +24 -1
  61. package/src/shared/conflict-detector.test.ts +15 -2
  62. package/src/shared/conflict-fixer.test.ts +5 -1
  63. package/src/shared/models-dev-cache.test.ts +200 -300
  64. package/src/shared/models-dev-cache.ts +184 -176
  65. package/src/shared/opencode-compaction-detector.test.ts +10 -2
  66. package/src/shared/rpc-client.test.ts +5 -1
  67. package/src/shared/rpc-types.ts +3 -1
  68. package/src/tui/index.tsx +17 -8
  69. package/src/tui/slots/sidebar-content.tsx +20 -10
@@ -1 +1 @@
1
- {"version":3,"file":"dream-timer.d.ts","sourceRoot":"","sources":["../../src/plugin/dream-timer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAapE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C;;;;;GAKG;AACH,UAAU,mBAAmB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,wBAAwB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,uBAAuB,CAAC,EAAE;QACtB,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,iBAAiB,CAAC,EAAE;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AA2BD;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CACzC,IAAI,EAAE,mBAAmB,GAC1B,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CA6DnC"}
1
+ {"version":3,"file":"dream-timer.d.ts","sourceRoot":"","sources":["../../src/plugin/dream-timer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAkBpE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C;;;;;GAKG;AACH,UAAU,mBAAmB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IAChC,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,wBAAwB,CAAC,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,kBAAkB,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5E,uBAAuB,CAAC,EAAE;QACtB,OAAO,EAAE,OAAO,CAAC;QACjB,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,iBAAiB,CAAC,EAAE;QAChB,OAAO,EAAE,OAAO,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxE;AA2BD;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CACzC,IAAI,EAAE,mBAAmB,GAC1B,OAAO,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,CA6DnC"}
@@ -7,6 +7,16 @@ export declare function createEventHandler(args: {
7
7
  autoUpdateChecker?: ((input: {
8
8
  event: import("@opencode-ai/sdk").Event;
9
9
  }) => Promise<void>) | null;
10
+ /**
11
+ * Orderly cleanup for THIS plugin instance when OpenCode disposes it. Fires
12
+ * on the SDK `server.instance.disposed` event; the callback receives the
13
+ * disposed instance's `directory` so the caller can match it against its own
14
+ * `ctx.directory` (OpenCode Desktop runs multiple instances in one process,
15
+ * each disposed independently, so a dispose for a different directory must
16
+ * not tear down this instance's resources). Best-effort: failures are
17
+ * swallowed so a cleanup error never propagates into OpenCode's event loop.
18
+ */
19
+ onInstanceDisposed?: (directory: string) => void | Promise<void>;
10
20
  }): (input: {
11
21
  event: import("@opencode-ai/sdk").Event;
12
22
  }) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/plugin/event.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,YAAY,EAAE;QACV,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,OAAO,kBAAkB,EAAE,KAAK,CAAA;SAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACjF,GAAG,IAAI,CAAC;IACT,iBAAiB,CAAC,EACZ,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,OAAO,kBAAkB,EAAE,KAAK,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GACvE,IAAI,CAAC;CACd,GAAG,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,OAAO,kBAAkB,EAAE,KAAK,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAKxE"}
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/plugin/event.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACrC,YAAY,EAAE;QACV,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE;YAAE,KAAK,EAAE,OAAO,kBAAkB,EAAE,KAAK,CAAA;SAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACjF,GAAG,IAAI,CAAC;IACT,iBAAiB,CAAC,EACZ,CAAC,CAAC,KAAK,EAAE;QAAE,KAAK,EAAE,OAAO,kBAAkB,EAAE,KAAK,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC,GACvE,IAAI,CAAC;IACX;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE,GAAG,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,OAAO,kBAAkB,EAAE,KAAK,CAAA;CAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAgBxE"}
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,OAAO,EACH,KAAK,eAAe,IAAI,QAAQ,EAGnC,MAAM,mCAAmC,CAAC;AAY3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAqBlF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA0FzE,wBAAgB,oBAAoB,CAChC,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,EAK9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,eAAe,CAyVjB;AAED,wBAAgB,iBAAiB,CAC7B,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,GAC/B,YAAY,CAuKd;AAED;;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,CAgON"}
1
+ {"version":3,"file":"rpc-handlers.d.ts","sourceRoot":"","sources":["../../src/plugin/rpc-handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEzE,OAAO,EACH,KAAK,eAAe,IAAI,QAAQ,EAGnC,MAAM,mCAAmC,CAAC;AAY3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAqBlF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAClE,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AA0FzE,wBAAgB,oBAAoB,CAChC,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,EAK9B,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,eAAe,CA2VjB;AAED,wBAAgB,iBAAiB,CAC7B,EAAE,EAAE,QAAQ,EACZ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,gBAAgB,CAAC,EAAE,gBAAgB,EACnC,qBAAqB,CAAC,EAAE,MAAM,GAC/B,YAAY,CAuKd;AAED;;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,CAgON"}
@@ -50,6 +50,22 @@ export declare function markAnnouncementSeen(version: string): void;
50
50
  * True when the configured `ANNOUNCEMENT_VERSION` has not yet been dismissed
51
51
  * AND there is at least one feature to show. Used by both the TUI dialog path
52
52
  * and the Desktop ignored-message fallback.
53
+ *
54
+ * First-run / sandbox handling: when NO state file exists yet, we seed it to the
55
+ * current `ANNOUNCEMENT_VERSION` and return false instead of announcing. This
56
+ * covers two cases that previously spammed the dialog (issue #99):
57
+ * - Fresh installs: a brand-new user shouldn't be shown a changelog of release
58
+ * bullets they have no context for — they need onboarding, not patch notes.
59
+ * - Ephemeral/sandbox environments (Docker, CI, disposable dev containers)
60
+ * where the storage dir is wiped between launches: without the seed, the
61
+ * missing file made the announcement re-show on every single startup.
62
+ * Real upgrades still announce exactly once: an existing user already has a
63
+ * state file at the prior version, so the version mismatch shows the dialog and
64
+ * dismissing it advances the file to the current version.
65
+ *
66
+ * The seed is a deliberate write side-effect on the "no file" branch — folding
67
+ * it here (rather than a separate startup call) makes every caller path (plugin
68
+ * startup, Pi startup, TUI rpc pull) consistent with no ordering dependency.
53
69
  */
54
70
  export declare function shouldShowAnnouncement(): boolean;
55
71
  //# sourceMappingURL=announcement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../../src/shared/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAMvD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,qDAAqD,CAAC;AAQtF;;;;;;GAMG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAQjD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS1D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAGhD"}
1
+ {"version":3,"file":"announcement.d.ts","sourceRoot":"","sources":["../../src/shared/announcement.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH;;;GAGG;AACH,eAAO,MAAM,oBAAoB,WAAW,CAAC;AAE7C;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,aAAa,CAAC,MAAM,CAMvD,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,qDAAqD,CAAC;AAQtF;;;;;;GAMG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAQjD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAS1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAUhD"}
@@ -1,23 +1,28 @@
1
1
  /**
2
- * Resolve per-model context limits to match whatever OpenCode itself sees.
2
+ * Resolve per-model context limits from OpenCode's SDK the single source of
3
+ * truth — for OpenCode sessions.
3
4
  *
4
- * Two layers:
5
+ * `client.config.providers()` returns OpenCode's fully-resolved config: the
6
+ * live models.dev cache + compiled-in snapshot + opencode.json custom-provider
7
+ * overrides + auth-plugin caps (e.g. the Codex-OAuth gpt-5.5 400k cap). We
8
+ * consume ONLY that. We no longer read OpenCode's `models.json` file ourselves:
9
+ * a torn read mid-write produced impossible limits (a 6748 "limit" for a session
10
+ * that had run for hours), and a stale on-disk copy out-voted the live
11
+ * auth-resolved cap (922k vs the real 400k). OpenCode reads that file safely in
12
+ * its own process and hands us the merged answer.
5
13
  *
6
- * 1. API cache (primary): populated asynchronously via
7
- * `client.config.providers()`. OpenCode's own provider service merges
8
- * the live models.dev cache file, its compiled-in snapshot fallback,
9
- * opencode.json custom provider overrides, and derived experimental
10
- * modes. Whatever OpenCode reports is the source of truth.
14
+ * Layers:
15
+ * 1. `apiCache` (authoritative): warmed once at startup from the SDK; seeded
16
+ * from a persisted last-known-good file on cold start so a restart uses the
17
+ * real limit immediately (no 128k-default budget-collapse window).
11
18
  *
12
- * 2. File cache (fallback): read-from-disk parse of OpenCode's
13
- * `models.json` plus `opencode.json(c)` custom provider entries.
14
- * Used during cold starts before the API cache warms up and in any
15
- * code path that cannot reach the SDK client.
19
+ * All cached values are bounded to a sane [20k, 3M] range on insert, so torn /
20
+ * unconfigured-default garbage can never be returned or persisted. The startup
21
+ * warm retries a couple times when OpenCode's provider service isn't ready yet.
16
22
  *
17
- * The public getter (`getModelsDevContextLimit()`) is synchronous: it checks
18
- * the API cache first, then the file cache. The plugin warms the API cache
19
- * once from `src/index.ts` at startup. Runtime retries are reserved for the
20
- * issue #77 cache-regression recovery path.
23
+ * Pi does NOT use this it resolves from its own `ctx.getModel().contextWindow`
24
+ * (instant at extension load), so `getSdkContextLimit()` returns `undefined`
25
+ * for Pi and Pi's own path is used.
21
26
  */
22
27
  interface OpencodeClientLike {
23
28
  config: {
@@ -28,6 +33,12 @@ interface OpencodeClientLike {
28
33
  }>;
29
34
  };
30
35
  }
36
+ export declare const MIN_SANE_LIMIT = 20000;
37
+ export declare const MAX_SANE_LIMIT = 3000000;
38
+ /** True when `limit` is a plausible real prompt window — used to reject torn /
39
+ * unconfigured-default garbage in BOTH harnesses (OpenCode's SDK values and
40
+ * Pi's reported `contextWindow`). Exported so Pi applies the identical bound. */
41
+ export declare function isSaneLimit(limit: number | undefined): limit is number;
31
42
  /**
32
43
  * Asynchronously refresh the API-layer cache from OpenCode's SDK.
33
44
  *
@@ -35,31 +46,47 @@ interface OpencodeClientLike {
35
46
  * OpenCode's `/config/providers` endpoint returns every provider with full
36
47
  * model metadata — including `limit.context` — resolved through the same path
37
48
  * OpenCode itself uses (live cache + compiled-in snapshot + opencode.json
38
- * overrides + derived experimental modes).
49
+ * overrides + derived experimental modes + auth-plugin caps).
50
+ *
51
+ * `retries`/`retryDelayMs`: when OpenCode's provider service isn't ready at our
52
+ * startup, `config.providers()` can return an empty/no-providers payload. Retry
53
+ * a few times so the cache warms instead of leaving the session on the 128k
54
+ * default until the next restart. A successful load (any providers) stops early.
39
55
  *
40
56
  * Safe to call concurrently; only overwrites the cache on success.
41
57
  */
42
- export declare function refreshModelLimitsFromApi(client: OpencodeClientLike): Promise<void>;
58
+ export declare function refreshModelLimitsFromApi(client: OpencodeClientLike, options?: {
59
+ retries?: number;
60
+ retryDelayMs?: number;
61
+ }): Promise<void>;
43
62
  /**
44
- * Returns the context limit for a provider/model.
63
+ * Resolve a model's prompt limit from OpenCode's SDK (`config.providers()`),
64
+ * the single source of truth: it already merges models.dev + compiled-in
65
+ * snapshot + opencode.json overrides + auth-plugin caps (e.g. the Codex-OAuth
66
+ * gpt-5.5 400k cap). We deliberately do NOT read OpenCode's `models.json` file
67
+ * ourselves — a torn read of that file mid-write produced garbage limits, and a
68
+ * stale on-disk copy out-voted the live auth-resolved cap (922k vs the real
69
+ * 400k). OpenCode reads that file safely within its own process and exposes the
70
+ * merged result here.
45
71
  *
46
- * Lookup order:
47
- * 1. API cache (populated by {@link refreshModelLimitsFromApi}). Matches
48
- * what OpenCode sees exactly, including snapshot-only models.
49
- * 2. File cache (parsed from models.json + opencode.json overrides).
50
- * Used before the API cache warms and as a last resort.
72
+ * Resolution:
73
+ * 1. Seed `apiCache` from the persisted last-known-good file once (cold start).
74
+ * 2. Return the SDK value (sane by construction — only [20k,3M] is cached).
75
+ * 3. `undefined` when the SDK hasn't reported this model yet → the caller
76
+ * defaults / retries (the startup warm retries when OpenCode isn't ready).
51
77
  *
52
- * Returns `undefined` if neither layer knows the model.
78
+ * OpenCode-only: Pi never warms `apiCache` (it resolves from its own
79
+ * `ctx.getModel().contextWindow`), so for Pi this returns `undefined` and Pi's
80
+ * own resolution path is used.
53
81
  */
54
- export declare function getModelsDevContextLimit(providerID: string, modelID: string): number | undefined;
55
- /** Clear in-memory caches (for testing). */
82
+ export declare function getSdkContextLimit(providerID: string, modelID: string): number | undefined;
83
+ /** Clear in-memory caches (for testing and the regression-recovery refetch). */
56
84
  export declare function clearModelsDevCache(): void;
57
85
  /** Inspection helpers (for logging / debugging). */
58
86
  export declare function getModelsDevCacheState(): {
59
87
  apiLoaded: boolean;
60
88
  apiCount: number;
61
89
  apiAgeMs: number;
62
- fileCount: number;
63
90
  };
64
91
  export {};
65
92
  //# sourceMappingURL=models-dev-cache.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAUH,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;AA+LD;;;;;;;;;;GAUG;AACH,wBAAsB,yBAAyB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDzF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAchG;AAED,4CAA4C;AAC5C,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED,oDAAoD;AACpD,wBAAgB,sBAAsB,IAAI;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACrB,CAOA"}
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"}
@@ -76,7 +76,9 @@ export interface SidebarSnapshot {
76
76
  * the runtime `RecompProgress` shape from compartment-runner-types.ts.
77
77
  */
78
78
  recompProgress?: {
79
- phase: "recomp" | "migration" | "done" | "failed";
79
+ /** "recomp" "Recomp" labels; "upgrade" "Upgrade" labels. */
80
+ kind?: "recomp" | "upgrade";
81
+ phase: "recomp" | "migration" | "done" | "failed" | "skipped";
80
82
  processedMessages: number;
81
83
  totalMessages: number;
82
84
  passCount: number;
@@ -1 +1 @@
1
- {"version":3,"file":"rpc-types.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rpc-types.d.ts","sourceRoot":"","sources":["../../src/shared/rpc-types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAWhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CACtD;AA2OD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAIxF"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-note/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAYhE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,MAAM,WAAW,eAAe;IAC5B,EAAE,EAAE,QAAQ,CAAC;IACb,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAC;CACtD;AA2QD,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAIxF"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAahE,OAAO,KAAK,EAAkC,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAiKjF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAI5F"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../../../src/tools/ctx-search/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAQ,MAAM,qBAAqB,CAAC;AAahE,OAAO,KAAK,EAAkC,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAqKjF,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAI5F"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.22.0",
3
+ "version": "0.22.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",
@@ -41,8 +41,8 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@huggingface/transformers": "^4.1.0",
44
- "@opencode-ai/plugin": "^1.14.39",
45
- "@opencode-ai/sdk": "^1.14.39",
44
+ "@opencode-ai/plugin": "^1.15.13",
45
+ "@opencode-ai/sdk": "^1.15.13",
46
46
  "ai-tokenizer": "^1.0.6",
47
47
  "comment-json": "^4.2.5",
48
48
  "zod": "^4.1.8"
@@ -69,6 +69,6 @@
69
69
  "tui"
70
70
  ],
71
71
  "peerDependencies": {
72
- "@opencode-ai/plugin": ">=1.14.0"
72
+ "@opencode-ai/plugin": ">=1.15.0"
73
73
  }
74
74
  }
@@ -8,8 +8,11 @@ import * as path from "node:path";
8
8
  * `getMagicContextStorageDir()`. The behavior we test:
9
9
  * 1. `markAnnouncementSeen` then `readLastAnnouncedVersion` round-trips
10
10
  * 2. `shouldShowAnnouncement` returns false after a matching mark
11
- * 3. `shouldShowAnnouncement` returns true after a non-matching mark
12
- * 4. Empty-version inputs are no-ops (don't crash, don't write garbage)
11
+ * 3. `shouldShowAnnouncement` returns true after a non-matching (older) mark
12
+ * 4. `shouldShowAnnouncement` seeds state + returns false on first run / wiped
13
+ * sandbox (no prior file), so fresh installs and ephemeral envs aren't
14
+ * spammed with a changelog (issue #99)
15
+ * 5. Empty-version inputs are no-ops (don't crash, don't write garbage)
13
16
  *
14
17
  * We isolate writes by pointing `XDG_DATA_HOME` at a temp dir before requiring
15
18
  * the module fresh per test, since the module captures the storage path at
@@ -32,7 +35,8 @@ afterEach(() => {
32
35
  process.env.XDG_DATA_HOME = originalXdg;
33
36
  }
34
37
  try {
35
- fs.rmSync(tmpRoot, { recursive: true, force: true });
38
+ // maxRetries/retryDelay ride out transient EBUSY/EPERM on Windows.
39
+ fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
36
40
  } catch {
37
41
  // best-effort
38
42
  }
@@ -110,9 +114,14 @@ describe("shouldShowAnnouncement gating", () => {
110
114
  expect(shouldShowAnnouncement()).toBe(false);
111
115
  });
112
116
 
113
- test("returns true when the live version was never marked", async () => {
117
+ test("seeds state and returns false on first run / wiped sandbox (issue #99)", async () => {
114
118
  const mod = await import(`./announcement?t=${Date.now()}-none`);
115
- const { ANNOUNCEMENT_VERSION, ANNOUNCEMENT_FEATURES, shouldShowAnnouncement } = mod;
119
+ const {
120
+ ANNOUNCEMENT_VERSION,
121
+ ANNOUNCEMENT_FEATURES,
122
+ shouldShowAnnouncement,
123
+ readLastAnnouncedVersion,
124
+ } = mod;
116
125
 
117
126
  if (!ANNOUNCEMENT_VERSION || ANNOUNCEMENT_FEATURES.length === 0) {
118
127
  // When empty, the gate is always false regardless of state
@@ -120,8 +129,15 @@ describe("shouldShowAnnouncement gating", () => {
120
129
  return;
121
130
  }
122
131
 
123
- // No mark has been written in this fresh tmpRoot, so the gate is open
124
- expect(shouldShowAnnouncement()).toBe(true);
132
+ // No mark exists yet (fresh install or ephemeral/wiped sandbox). The
133
+ // gate must NOT announce — it seeds the state to the current version and
134
+ // returns false, so first-run users and disposable containers are never
135
+ // spammed with a changelog they have no context for.
136
+ expect(readLastAnnouncedVersion()).toBe("");
137
+ expect(shouldShowAnnouncement()).toBe(false);
138
+ // The seed was written, so a subsequent check stays quiet too.
139
+ expect(readLastAnnouncedVersion()).toBe(ANNOUNCEMENT_VERSION);
140
+ expect(shouldShowAnnouncement()).toBe(false);
125
141
  });
126
142
 
127
143
  test("returns true when a different (older) version is marked", async () => {
@@ -89,8 +89,31 @@ export function markAnnouncementSeen(version: string): void {
89
89
  * True when the configured `ANNOUNCEMENT_VERSION` has not yet been dismissed
90
90
  * AND there is at least one feature to show. Used by both the TUI dialog path
91
91
  * and the Desktop ignored-message fallback.
92
+ *
93
+ * First-run / sandbox handling: when NO state file exists yet, we seed it to the
94
+ * current `ANNOUNCEMENT_VERSION` and return false instead of announcing. This
95
+ * covers two cases that previously spammed the dialog (issue #99):
96
+ * - Fresh installs: a brand-new user shouldn't be shown a changelog of release
97
+ * bullets they have no context for — they need onboarding, not patch notes.
98
+ * - Ephemeral/sandbox environments (Docker, CI, disposable dev containers)
99
+ * where the storage dir is wiped between launches: without the seed, the
100
+ * missing file made the announcement re-show on every single startup.
101
+ * Real upgrades still announce exactly once: an existing user already has a
102
+ * state file at the prior version, so the version mismatch shows the dialog and
103
+ * dismissing it advances the file to the current version.
104
+ *
105
+ * The seed is a deliberate write side-effect on the "no file" branch — folding
106
+ * it here (rather than a separate startup call) makes every caller path (plugin
107
+ * startup, Pi startup, TUI rpc pull) consistent with no ordering dependency.
92
108
  */
93
109
  export function shouldShowAnnouncement(): boolean {
94
110
  if (!ANNOUNCEMENT_VERSION || ANNOUNCEMENT_FEATURES.length === 0) return false;
95
- return readLastAnnouncedVersion() !== ANNOUNCEMENT_VERSION;
111
+ const lastVersion = readLastAnnouncedVersion();
112
+ if (!lastVersion) {
113
+ // No prior state: fresh install or wiped sandbox. Seed to current and
114
+ // skip the announcement so we never pester first-run / ephemeral envs.
115
+ markAnnouncementSeen(ANNOUNCEMENT_VERSION);
116
+ return false;
117
+ }
118
+ return lastVersion !== ANNOUNCEMENT_VERSION;
96
119
  }
@@ -46,8 +46,21 @@ describe("detectConflicts", () => {
46
46
  else process.env[k] = v;
47
47
  }
48
48
  // Test directories live under tmpdir(); cleanup is best-effort.
49
- rmSync(projectDir, { recursive: true, force: true });
50
- rmSync(userConfigDir, { recursive: true, force: true });
49
+ try {
50
+ rmSync(projectDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
51
+ } catch {
52
+ /* Ignore EBUSY on Windows */
53
+ }
54
+ try {
55
+ rmSync(userConfigDir, {
56
+ recursive: true,
57
+ force: true,
58
+ maxRetries: 10,
59
+ retryDelay: 100,
60
+ });
61
+ } catch {
62
+ /* Ignore EBUSY on Windows */
63
+ }
51
64
  });
52
65
 
53
66
  function writeProjectConfig(plugins: Array<string | [string, unknown]>): void {
@@ -38,7 +38,11 @@ describe("fixConflicts", () => {
38
38
  if (value === undefined) delete process.env[key];
39
39
  else process.env[key] = value;
40
40
  }
41
- rmSync(root, { recursive: true, force: true });
41
+ try {
42
+ rmSync(root, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
43
+ } catch {
44
+ /* Ignore EBUSY on Windows */
45
+ }
42
46
  });
43
47
 
44
48
  it("preserves JSONC comments and tuple plugin entries while removing canonical DCP", () => {