@cortexkit/opencode-magic-context 0.22.0 → 0.22.1

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 (29) hide show
  1. package/README.md +10 -0
  2. package/dist/config/index.d.ts.map +1 -1
  3. package/dist/config/schema/agent-overrides.d.ts.map +1 -1
  4. package/dist/config/schema/magic-context.d.ts +15 -0
  5. package/dist/config/schema/magic-context.d.ts.map +1 -1
  6. package/dist/features/magic-context/memory/embedding-identity.d.ts.map +1 -1
  7. package/dist/features/magic-context/memory/embedding-openai.d.ts +6 -0
  8. package/dist/features/magic-context/memory/embedding-openai.d.ts.map +1 -1
  9. package/dist/features/magic-context/memory/embedding-probe.d.ts +5 -0
  10. package/dist/features/magic-context/memory/embedding-probe.d.ts.map +1 -1
  11. package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
  12. package/dist/hooks/auto-update-checker/cache.d.ts.map +1 -1
  13. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +195 -104
  16. package/dist/plugin/event.d.ts +10 -0
  17. package/dist/plugin/event.d.ts.map +1 -1
  18. package/dist/shared/announcement.d.ts +16 -0
  19. package/dist/shared/announcement.d.ts.map +1 -1
  20. package/dist/shared/models-dev-cache.d.ts.map +1 -1
  21. package/package.json +4 -4
  22. package/src/shared/announcement.test.ts +23 -7
  23. package/src/shared/announcement.ts +24 -1
  24. package/src/shared/conflict-detector.test.ts +15 -2
  25. package/src/shared/conflict-fixer.test.ts +5 -1
  26. package/src/shared/models-dev-cache.test.ts +72 -4
  27. package/src/shared/models-dev-cache.ts +47 -8
  28. package/src/shared/opencode-compaction-detector.test.ts +10 -2
  29. package/src/shared/rpc-client.test.ts +5 -1
package/README.md CHANGED
@@ -244,6 +244,16 @@ Settings live in `magic-context.jsonc`. Everything has sensible defaults; projec
244
244
 
245
245
  All durable state lives in a local SQLite database under the shared CortexKit store (`~/.local/share/cortexkit/magic-context/context.db`, XDG-equivalent on Windows; legacy OpenCode-folder databases are migrated forward on first boot). If the database can't be opened, Magic Context disables itself and notifies you. Memories are keyed to a **stable project identity** derived from the repo, so they follow a project across worktrees, clones, and forks rather than being tied to a directory path.
246
246
 
247
+ Magic Context also writes to a few other locations:
248
+
249
+ | Path | What | Persistence |
250
+ |---|---|---|
251
+ | `~/.local/share/cortexkit/magic-context/context.db` | SQLite database — tags, compartments, memories, all durable state (XDG-equivalent on Windows) | **Must persist.** Losing it loses your memory/history. |
252
+ | `~/.local/share/cortexkit/magic-context/models/` | Local embedding model cache (~90 MB `Xenova/all-MiniLM-L6-v2` ONNX), downloaded on first use when local embeddings are enabled | Should persist, else re-downloaded each run. Not used when `memory.enabled: false` or an `openai_compatible`/`ollama` embedding backend is configured. |
253
+ | `${TMPDIR}/opencode/magic-context/magic-context.log` (`pi/` for Pi) | Diagnostic log | Disposable. |
254
+
255
+ **Sandboxed / ephemeral environments (Docker, CI, disposable containers):** mount the `~/.local/share/cortexkit/magic-context/` directory on a persistent volume so the database and model cache survive between runs. If only the model cache is ephemeral, the model is simply re-downloaded; if the database is ephemeral, memory and history don't accumulate. To avoid the ~90 MB model download entirely, set `memory.enabled: false` or point `embedding` at a remote `openai_compatible`/`ollama` backend.
256
+
247
257
  ---
248
258
 
249
259
  ## Star History
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,kBAAkB,EAA4B,MAAM,wBAAwB,CAAC;AAG3F,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAChE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CACZ,MAAM,EACN;QACI,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;KACrB,CACJ,CAAC;CACL;AAmBD,MAAM,MAAM,WAAW,GACjB,IAAI,GACJ,0BAA0B,GAC1B,uBAAuB,GACvB,iBAAiB,GACjB,sBAAsB,CAAC;AAE7B,MAAM,WAAW,kBAAkB;IAC/B,MAAM,EAAE,wBAAwB,GAAG;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACjE,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE;QACL,UAAU,EAAE,WAAW,CAAC;QACxB,aAAa,EAAE,WAAW,CAAC;KAC9B,CAAC;IACF,oBAAoB,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9F,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACnC;AAyPD,wBAAgB,gBAAgB,CAC5B,SAAS,EAAE,MAAM,GAClB,wBAAwB,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA2E1D;AAgDD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAuE9E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,KAAK,kBAAkB,EAA4B,MAAM,wBAAwB,CAAC;AAG3F,MAAM,WAAW,wBAAyB,SAAQ,kBAAkB;IAChE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CACZ,MAAM,EACN;QACI,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,OAAO,CAAC;KACrB,CACJ,CAAC;CACL;AAmBD,MAAM,MAAM,WAAW,GACjB,IAAI,GACJ,0BAA0B,GAC1B,uBAAuB,GACvB,iBAAiB,GACjB,sBAAsB,CAAC;AAE7B,MAAM,WAAW,kBAAkB;IAC/B,MAAM,EAAE,wBAAwB,GAAG;QAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACjE,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE;QACL,UAAU,EAAE,WAAW,CAAC;QACxB,aAAa,EAAE,WAAW,CAAC;KAC9B,CAAC;IACF,oBAAoB,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9F,qBAAqB,EAAE,MAAM,EAAE,CAAC;CACnC;AAwQD,wBAAgB,gBAAgB,CAC5B,SAAS,EAAE,MAAM,GAClB,wBAAwB,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CA2E1D;AAgDD,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,kBAAkB,CAuE9E"}
@@ -1 +1 @@
1
- {"version":3,"file":"agent-overrides.d.ts","sourceRoot":"","sources":["../../../src/config/schema/agent-overrides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iCAAiC,+CAAgD,CAAC;AAgB/F,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAkBpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC"}
1
+ {"version":3,"file":"agent-overrides.d.ts","sourceRoot":"","sources":["../../../src/config/schema/agent-overrides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,eAAO,MAAM,iCAAiC,+CAAgD,CAAC;AAgB/F,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA4BpC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  export declare const DEFAULT_NUDGE_INTERVAL_TOKENS = 10000;
3
3
  export declare const DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65;
4
+ export declare const EXECUTE_THRESHOLD_CAP_MESSAGE = "execute_threshold is capped at 80% for cache safety: a single large agent step can overflow the context window before Magic Context can compact between turns, forcing OpenCode's native compaction (hard to recover from). 80% also leaves headroom below the 85%/95% emergency bands. Use a value between 20 and 80.";
4
5
  export declare const DEFAULT_HISTORIAN_TIMEOUT_MS = 300000;
5
6
  export declare const DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15;
6
7
  export declare const DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
@@ -241,10 +242,14 @@ export declare const EmbeddingConfigSchema: z.ZodPipe<z.ZodObject<{
241
242
  model: z.ZodOptional<z.ZodString>;
242
243
  endpoint: z.ZodOptional<z.ZodString>;
243
244
  api_key: z.ZodOptional<z.ZodString>;
245
+ input_type: z.ZodOptional<z.ZodString>;
246
+ truncate: z.ZodOptional<z.ZodString>;
244
247
  }, z.core.$strip>, z.ZodTransform<{
245
248
  provider: "local";
246
249
  model: string;
247
250
  } | {
251
+ truncate?: string | undefined;
252
+ input_type?: string | undefined;
248
253
  api_key?: string | undefined;
249
254
  provider: "openai-compatible";
250
255
  model: string;
@@ -257,6 +262,8 @@ export declare const EmbeddingConfigSchema: z.ZodPipe<z.ZodObject<{
257
262
  model?: string | undefined;
258
263
  endpoint?: string | undefined;
259
264
  api_key?: string | undefined;
265
+ input_type?: string | undefined;
266
+ truncate?: string | undefined;
260
267
  }>>;
261
268
  export type EmbeddingConfig = z.infer<typeof EmbeddingConfigSchema>;
262
269
  export interface MagicContextConfig {
@@ -560,10 +567,14 @@ export declare const MagicContextConfigSchema: z.ZodPipe<z.ZodObject<{
560
567
  model: z.ZodOptional<z.ZodString>;
561
568
  endpoint: z.ZodOptional<z.ZodString>;
562
569
  api_key: z.ZodOptional<z.ZodString>;
570
+ input_type: z.ZodOptional<z.ZodString>;
571
+ truncate: z.ZodOptional<z.ZodString>;
563
572
  }, z.core.$strip>, z.ZodTransform<{
564
573
  provider: "local";
565
574
  model: string;
566
575
  } | {
576
+ truncate?: string | undefined;
577
+ input_type?: string | undefined;
567
578
  api_key?: string | undefined;
568
579
  provider: "openai-compatible";
569
580
  model: string;
@@ -576,6 +587,8 @@ export declare const MagicContextConfigSchema: z.ZodPipe<z.ZodObject<{
576
587
  model?: string | undefined;
577
588
  endpoint?: string | undefined;
578
589
  api_key?: string | undefined;
590
+ input_type?: string | undefined;
591
+ truncate?: string | undefined;
579
592
  }>>>;
580
593
  temporal_awareness: z.ZodDefault<z.ZodBoolean>;
581
594
  caveman_text_compression: z.ZodDefault<z.ZodObject<{
@@ -692,6 +705,8 @@ export declare const MagicContextConfigSchema: z.ZodPipe<z.ZodObject<{
692
705
  provider: "local";
693
706
  model: string;
694
707
  } | {
708
+ truncate?: string | undefined;
709
+ input_type?: string | undefined;
695
710
  api_key?: string | undefined;
696
711
  provider: "openai-compatible";
697
712
  model: string;
@@ -1 +1 @@
1
- {"version":3,"file":"magic-context.d.ts","sourceRoot":"","sources":["../../../src/config/schema/magic-context.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"magic-context.d.ts","sourceRoot":"","sources":["../../../src/config/schema/magic-context.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"embedding-identity.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAQ5E;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CAqB5E"}
1
+ {"version":3,"file":"embedding-identity.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-identity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAQ5E;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,eAAe,GAAG,MAAM,CA0B5E"}
@@ -3,6 +3,10 @@ interface OpenAICompatibleEmbeddingProviderOptions {
3
3
  endpoint?: string;
4
4
  model?: string;
5
5
  apiKey?: string;
6
+ /** Optional `input_type` body field (e.g. NVIDIA NIM 'query'/'passage'). */
7
+ inputType?: string;
8
+ /** Optional `truncate` body field (e.g. NVIDIA NIM 'NONE'/'START'/'END'). */
9
+ truncate?: string;
6
10
  }
7
11
  type CircuitState = "closed" | "open" | "half_open";
8
12
  export declare class OpenAICompatibleEmbeddingProvider implements EmbeddingProvider {
@@ -10,6 +14,8 @@ export declare class OpenAICompatibleEmbeddingProvider implements EmbeddingProvi
10
14
  private readonly endpoint;
11
15
  private readonly model;
12
16
  private readonly apiKey;
17
+ private readonly inputType;
18
+ private readonly truncate;
13
19
  private initialized;
14
20
  private failureTimes;
15
21
  private circuitOpenUntil;
@@ -1 +1 @@
1
- {"version":3,"file":"embedding-openai.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-openai.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,UAAU,wCAAwC;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAyCD,KAAK,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAEpD,qBAAa,iCAAkC,YAAW,iBAAiB;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,WAAW,CAAS;IAI5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B;;sDAEkD;IAClD,OAAO,CAAC,qBAAqB,CAAS;gBAE1B,OAAO,EAAE,wCAAwC;IAYvD,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAc9B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAKvE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC;IA4InF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,QAAQ,IAAI,OAAO;IAInB;;;;;;;;OAQG;IACH,OAAO,CAAC,wBAAwB;IAsBhC,OAAO,CAAC,aAAa;IAiCrB,OAAO,CAAC,aAAa;IAUrB,gBAAgB,IAAI,YAAY;IAQhC,gBAAgB,IAAI,MAAM;IAG1B,aAAa,IAAI,IAAI;CAMxB"}
1
+ {"version":3,"file":"embedding-openai.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-openai.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,UAAU,wCAAwC;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAyCD,KAAK,YAAY,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAEpD,qBAAa,iCAAkC,YAAW,iBAAiB;IACvE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IAEzB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,WAAW,CAAS;IAI5B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,UAAU,CAAS;IAC3B;;sDAEkD;IAClD,OAAO,CAAC,qBAAqB,CAAS;gBAE1B,OAAO,EAAE,wCAAwC;IAcvD,UAAU,IAAI,OAAO,CAAC,OAAO,CAAC;IAc9B,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAKvE,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC;IAkJnF,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAI9B,QAAQ,IAAI,OAAO;IAInB;;;;;;;;OAQG;IACH,OAAO,CAAC,wBAAwB;IAsBhC,OAAO,CAAC,aAAa;IAiCrB,OAAO,CAAC,aAAa;IAUrB,gBAAgB,IAAI,YAAY;IAQhC,gBAAgB,IAAI,MAAM;IAG1B,aAAa,IAAI,IAAI;CAMxB"}
@@ -43,6 +43,11 @@ export interface EmbeddingProbeOptions {
43
43
  endpoint: string;
44
44
  model: string;
45
45
  apiKey?: string;
46
+ /** Optional `input_type` body field — required by some providers (NVIDIA NIM)
47
+ * for the probe to succeed. Omitted from the body when unset. */
48
+ inputType?: string;
49
+ /** Optional `truncate` body field (e.g. NVIDIA NIM). Omitted when unset. */
50
+ truncate?: string;
46
51
  /** Milliseconds before aborting the request. Defaults to 10000. */
47
52
  timeoutMs?: number;
48
53
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"embedding-probe.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,qBAAqB,GAC3B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CACxC,OAAO,EAAE,qBAAqB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CA+EhC"}
1
+ {"version":3,"file":"embedding-probe.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,qBAAqB,GAC3B;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,aAAa,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,sBAAsB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACjE;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,IAAI,EAAE,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD,MAAM,WAAW,qBAAqB;IAClC;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;sEACkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAKD;;;;;;;;;;;;GAYG;AACH,wBAAsB,sBAAsB,CACxC,OAAO,EAAE,qBAAqB,GAC/B,OAAO,CAAC,qBAAqB,CAAC,CAsFhC"}
@@ -1 +1 @@
1
- {"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAG5E,OAAO,KAAK,EAAE,QAAQ,EAAkC,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAOvD,YAAY,EACR,iBAAiB,EACjB,oCAAoC,GACvC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACH,sCAAsC,EACtC,iCAAiC,EACjC,oBAAoB,EACpB,mBAAmB,EACnB,iCAAiC,EACjC,2BAA2B,EAC3B,oCAAoC,EACpC,gCAAgC,EAChC,0BAA0B,EAC1B,0BAA0B,GAC7B,MAAM,+BAA+B,CAAC;AAwFvC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAoBjE;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAWhG;AAED,wBAAsB,UAAU,CAC5B,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,WAAW,GACrB,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC,CAelC;AAED,wBAAsB,uBAAuB,CACzC,EAAE,EAAE,QAAQ,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,EACvB,SAAS,SAAK,GACf,OAAO,CAAC,MAAM,CAAC,CAEjB;AAgBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,0BAA0B,CAC5C,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,eAAe,EACvB,SAAS,SAAK,GACf,OAAO,CAAC,MAAM,CAAC,CAsEjB;AAED,8CAA8C;AAC9C,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAmDD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3D"}
1
+ {"version":3,"file":"embedding.d.ts","sourceRoot":"","sources":["../../../../src/features/magic-context/memory/embedding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAG5E,OAAO,KAAK,EAAE,QAAQ,EAAkC,MAAM,wBAAwB,CAAC;AACvF,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAOvD,YAAY,EACR,iBAAiB,EACjB,oCAAoC,GACvC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACH,sCAAsC,EACtC,iCAAiC,EACjC,oBAAoB,EACpB,mBAAmB,EACnB,iCAAiC,EACjC,2BAA2B,EAC3B,oCAAoC,EACpC,gCAAgC,EAChC,0BAA0B,EAC1B,0BAA0B,GAC7B,MAAM,+BAA+B,CAAC;AA0FvC,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAoBjE;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,OAAO,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAWhG;AAED,wBAAsB,UAAU,CAC5B,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,WAAW,GACrB,OAAO,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC,CAelC;AAED,wBAAsB,uBAAuB,CACzC,EAAE,EAAE,QAAQ,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,eAAe,EACvB,SAAS,SAAK,GACf,OAAO,CAAC,MAAM,CAAC,CAEjB;AAgBD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,0BAA0B,CAC5C,EAAE,EAAE,QAAQ,EACZ,MAAM,EAAE,eAAe,EACvB,SAAS,SAAK,GACf,OAAO,CAAC,MAAM,CAAC,CAsEjB;AAED,8CAA8C;AAC9C,wBAAgB,yBAAyB,IAAI,IAAI,CAEhD;AAmDD,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAS3D"}
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/hooks/auto-update-checker/cache.ts"],"names":[],"mappings":"AAmBA,UAAU,wBAAwB;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CAC3B;AAgGD,wBAAgB,qBAAqB,CACjC,sBAAsB,GAAE,MAAM,GAAG,IAAyC,GAC3E,wBAAwB,GAAG,IAAI,CA2CjC;AAED,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,MAAqB,EAClC,sBAAsB,GAAE,MAAM,GAAG,IAAyC,GAC3E,MAAM,GAAG,IAAI,CAyBf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAC3D,OAAO,CAAC,OAAO,CAAC,CAgDlB"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../src/hooks/auto-update-checker/cache.ts"],"names":[],"mappings":"AAmBA,UAAU,wBAAwB;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CAC3B;AAoHD,wBAAgB,qBAAqB,CACjC,sBAAsB,GAAE,MAAM,GAAG,IAAyC,GAC3E,wBAAwB,GAAG,IAAI,CA2CjC;AAED,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,MAAM,EACf,WAAW,GAAE,MAAqB,EAClC,sBAAsB,GAAE,MAAM,GAAG,IAAyC,GAC3E,MAAM,GAAG,IAAI,CAyBf;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACnC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAC3D,OAAO,CAAC,OAAO,CAAC,CAgDlB"}
@@ -1 +1 @@
1
- {"version":3,"file":"event-resolvers.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/event-resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAQ5E,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,GAAG,CAAC,EAAE;IAAE,EAAE,CAAC,EAAE,eAAe,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,CAoBR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CACtC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,GAAG,CAAC,EAAE;IAAE,EAAE,CAAC,EAAE,eAAe,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,GAAG,SAAS,CAwBpB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAiB9F;AAED,KAAK,sBAAsB,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AACvF,KAAK,4BAA4B,GAC3B;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,GAC5D,SAAS,CAAC;AAEhB,MAAM,WAAW,uBAAuB;IACpC;oDACgD;IAChD,YAAY,CAAC,EAAE,4BAA4B,CAAC;IAC5C;yDACqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACnC,yFAAyF;IACzF,UAAU,EAAE,MAAM,CAAC;IACnB,kGAAkG;IAClG,IAAI,EAAE,oBAAoB,CAAC;IAC3B,6FAA6F;IAC7F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AA8CD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CACzC,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,uBAAuB,GAClC,sBAAsB,CA+ExB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,uBAAuB,GAClC,MAAM,CAER;AA2BD,wBAAgB,eAAe,CAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,gBAAgB,CAC5B,UAAU,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC/D,MAAM,GAAG,SAAS,CAmBpB"}
1
+ {"version":3,"file":"event-resolvers.d.ts","sourceRoot":"","sources":["../../../src/hooks/magic-context/event-resolvers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sCAAsC,CAAC;AAQ5E,KAAK,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAEtD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAC/B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,GAAG,CAAC,EAAE;IAAE,EAAE,CAAC,EAAE,eAAe,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,CAoBR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,0BAA0B,CACtC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,GAAG,CAAC,EAAE;IAAE,EAAE,CAAC,EAAE,eAAe,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACnD,MAAM,GAAG,SAAS,CAwBpB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAiB9F;AAED,KAAK,sBAAsB,GAAG,MAAM,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAC;AACvF,KAAK,4BAA4B,GAC3B;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;CAAE,GAC5D,SAAS,CAAC;AAEhB,MAAM,WAAW,uBAAuB;IACpC;oDACgD;IAChD,YAAY,CAAC,EAAE,4BAA4B,CAAC;IAC5C;yDACqD;IACrD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,oBAAoB,GAAG,YAAY,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACnC,yFAAyF;IACzF,UAAU,EAAE,MAAM,CAAC;IACnB,kGAAkG;IAClG,IAAI,EAAE,oBAAoB,CAAC;IAC3B,6FAA6F;IAC7F,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sGAAsG;IACtG,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AA8CD;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CACzC,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,uBAAuB,GAClC,sBAAsB,CA8FxB;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACnC,MAAM,EAAE,sBAAsB,EAC9B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,uBAAuB,GAClC,MAAM,CAER;AA2BD,wBAAgB,eAAe,CAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,GAC5B,MAAM,GAAG,SAAS,CAMpB;AAED,wBAAgB,gBAAgB,CAC5B,UAAU,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,GAC/D,MAAM,GAAG,SAAS,CAmBpB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA6ClD,QAAA,MAAM,MAAM,EAAE,MAkeb,CAAC;AAEF,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AA6ClD,QAAA,MAAM,MAAM,EAAE,MA4gBb,CAAC;AAEF,eAAe,MAAM,CAAC"}
package/dist/index.js CHANGED
@@ -14818,25 +14818,25 @@ var init_agent_overrides = __esm(() => {
14818
14818
  external_directory: PermissionValueSchema.optional()
14819
14819
  }).optional();
14820
14820
  AgentOverrideConfigSchema = exports_external.object({
14821
- model: exports_external.string().optional(),
14822
- temperature: exports_external.number().min(0).max(2).optional(),
14823
- top_p: exports_external.number().min(0).max(1).optional(),
14824
- prompt: exports_external.string().optional(),
14825
- tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional(),
14826
- disable: exports_external.boolean().optional(),
14827
- description: exports_external.string().optional(),
14828
- mode: exports_external.enum(["subagent", "primary", "all"]).optional(),
14829
- color: exports_external.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
14830
- maxSteps: exports_external.number().optional(),
14831
- permission: PermissionSchema,
14832
- maxTokens: exports_external.number().optional(),
14833
- variant: exports_external.string().optional(),
14834
- fallback_models: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional()
14821
+ model: exports_external.string().optional().describe("Primary model ID (e.g. 'claude-sonnet-4-6')"),
14822
+ temperature: exports_external.number().min(0).max(2).optional().describe("Sampling temperature (0-2)"),
14823
+ top_p: exports_external.number().min(0).max(1).optional().describe("Nucleus sampling top_p (0-1)"),
14824
+ prompt: exports_external.string().optional().describe("Additional system prompt text"),
14825
+ tools: exports_external.record(exports_external.string(), exports_external.boolean()).optional().describe("Tool enable/disable overrides"),
14826
+ disable: exports_external.boolean().optional().describe("Disable this agent"),
14827
+ description: exports_external.string().optional().describe("Agent description"),
14828
+ mode: exports_external.enum(["subagent", "primary", "all"]).optional().describe("Agent mode (subagent, primary, or all)"),
14829
+ color: exports_external.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Hex color for the agent (e.g. '#a1b2c3')"),
14830
+ maxSteps: exports_external.number().optional().describe("Maximum tool-call steps per invocation"),
14831
+ permission: PermissionSchema.describe("Per-tool permission overrides"),
14832
+ maxTokens: exports_external.number().optional().describe("Maximum output tokens"),
14833
+ variant: exports_external.string().optional().describe("OpenCode reasoning variant (e.g. for extended thinking)"),
14834
+ fallback_models: exports_external.union([exports_external.string(), exports_external.array(exports_external.string())]).optional().describe("Fallback model IDs if primary is unavailable")
14835
14835
  });
14836
14836
  });
14837
14837
 
14838
14838
  // src/config/schema/magic-context.ts
14839
- var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, PiThinkingLevelSchema, DreamerConfigSchema, SidekickConfigSchema, HistorianConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
14839
+ var DEFAULT_NUDGE_INTERVAL_TOKENS = 1e4, DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65, EXECUTE_THRESHOLD_CAP_MESSAGE = "execute_threshold is capped at 80% for cache safety: a single large agent step can overflow the context window before Magic Context can compact between turns, forcing OpenCode's native compaction (hard to recover from). 80% also leaves headroom below the 85%/95% emergency bands. Use a value between 20 and 80.", DEFAULT_HISTORIAN_TIMEOUT_MS = 300000, DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15, DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2", DREAMER_TASKS, DreamingTaskSchema, DEFAULT_DREAMER_TASKS, PiThinkingLevelSchema, DreamerConfigSchema, SidekickConfigSchema, HistorianConfigSchema, BaseEmbeddingConfigSchema, EmbeddingConfigSchema, MagicContextConfigSchema;
14840
14840
  var init_magic_context = __esm(() => {
14841
14841
  init_zod();
14842
14842
  init_agent_overrides();
@@ -14856,36 +14856,38 @@ var init_magic_context = __esm(() => {
14856
14856
  ];
14857
14857
  PiThinkingLevelSchema = exports_external.enum(["off", "minimal", "low", "medium", "high", "xhigh"]).optional();
14858
14858
  DreamerConfigSchema = AgentOverrideConfigSchema.merge(exports_external.object({
14859
- schedule: exports_external.string().default("02:00-06:00"),
14860
- max_runtime_minutes: exports_external.number().min(10).default(120),
14861
- tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS),
14862
- task_timeout_minutes: exports_external.number().min(5).default(20),
14863
- inject_docs: exports_external.boolean().default(true),
14859
+ schedule: exports_external.string().default("02:00-06:00").describe("Scheduled window for overnight dreaming (e.g. '02:00-06:00')"),
14860
+ max_runtime_minutes: exports_external.number().min(10).default(120).describe("Maximum runtime per dream session in minutes"),
14861
+ tasks: exports_external.array(DreamingTaskSchema).default(DEFAULT_DREAMER_TASKS).describe("Tasks to run during dreaming, in order"),
14862
+ task_timeout_minutes: exports_external.number().min(5).default(20).describe("Minutes allocated per task before moving to next"),
14863
+ inject_docs: exports_external.boolean().default(true).describe("Inject ARCHITECTURE.md and STRUCTURE.md into system prompt"),
14864
14864
  user_memories: exports_external.object({
14865
- enabled: exports_external.boolean().default(true),
14866
- promotion_threshold: exports_external.number().min(2).max(20).default(3)
14867
- }).default({ enabled: true, promotion_threshold: 3 }),
14865
+ enabled: exports_external.boolean().default(true).describe("Enable user memory extraction and promotion (default: true)"),
14866
+ promotion_threshold: exports_external.number().min(2).max(20).default(3).describe("Minimum candidate observations before dreamer considers promotion (default: 3)")
14867
+ }).default({ enabled: true, promotion_threshold: 3 }).describe("User memory pipeline: historian extracts behavior observations from each compartment run; dreamer reviews recurring patterns and promotes them to stable user memories injected into all sessions as <user-profile>. Requires dreamer to not be disabled for promotion to actually happen. Graduated from experimental in v0.14. Default: enabled."),
14868
14868
  pin_key_files: exports_external.object({
14869
- enabled: exports_external.boolean().default(false),
14870
- token_budget: exports_external.number().min(2000).max(30000).default(1e4),
14871
- min_reads: exports_external.number().min(2).max(20).default(4)
14872
- }).default({ enabled: false, token_budget: 1e4, min_reads: 4 }),
14873
- thinking_level: PiThinkingLevelSchema
14869
+ enabled: exports_external.boolean().default(false).describe("Enable key file pinning (default: false)"),
14870
+ token_budget: exports_external.number().min(2000).max(30000).default(1e4).describe("Total token budget for all pinned key files (min: 2000, max: 30000, default: 10000)"),
14871
+ min_reads: exports_external.number().min(2).max(20).default(4).describe("Minimum full-read count before a file is considered for pinning (min: 2, default: 4)")
14872
+ }).default({ enabled: false, token_budget: 1e4, min_reads: 4 }).describe("Pin frequently-read key files into the system prompt so the agent doesn't need to re-read them after context drops. Dreamer identifies key files per session based on read patterns. Requires dreamer to not be disabled for selection to happen. Graduated from experimental in v0.14. Default: disabled."),
14873
+ thinking_level: PiThinkingLevelSchema.describe("Pi only: explicit thinking level for dreamer subagent invocations. See historian.thinking_level.")
14874
14874
  }));
14875
14875
  SidekickConfigSchema = AgentOverrideConfigSchema.extend({
14876
- timeout_ms: exports_external.number().default(30000),
14877
- system_prompt: exports_external.string().optional(),
14878
- thinking_level: PiThinkingLevelSchema
14876
+ timeout_ms: exports_external.number().default(30000).describe("Timeout for sidekick calls in milliseconds"),
14877
+ system_prompt: exports_external.string().optional().describe("Custom system prompt for sidekick"),
14878
+ thinking_level: PiThinkingLevelSchema.describe("Pi only: explicit thinking level for sidekick subagent invocations. See historian.thinking_level.")
14879
14879
  }).optional();
14880
14880
  HistorianConfigSchema = AgentOverrideConfigSchema.extend({
14881
- two_pass: exports_external.boolean().default(false),
14882
- thinking_level: PiThinkingLevelSchema
14881
+ two_pass: exports_external.boolean().default(false).describe("Run a second editor pass over historian output to clean low-signal U: lines and cross-compartment duplicates. Adds ~1 extra API call and ~1.3x cost per historian run. Useful for models without extended thinking support. (default: false)"),
14882
+ thinking_level: PiThinkingLevelSchema.describe("Pi only: explicit thinking level passed as --thinking <level> to Pi historian subagent invocations. Required when using reasoning models (e.g. github-copilot/gpt-5.4) because Pi's default thinking-level resolution can pick a value the provider rejects. OpenCode users set variant instead. Valid: off | minimal | low | medium | high | xhigh")
14883
14883
  }).optional();
14884
14884
  BaseEmbeddingConfigSchema = exports_external.object({
14885
- provider: exports_external.enum(["local", "openai-compatible", "off"]).default("local"),
14886
- model: exports_external.string().optional(),
14887
- endpoint: exports_external.string().optional(),
14888
- api_key: exports_external.string().optional()
14885
+ provider: exports_external.enum(["local", "openai-compatible", "off"]).default("local").describe("Embedding provider. 'local' uses Xenova/all-MiniLM-L6-v2, 'openai-compatible' requires endpoint and model, 'off' disables embeddings."),
14886
+ model: exports_external.string().optional().describe("Embedding model name. Required for openai-compatible, ignored for local."),
14887
+ endpoint: exports_external.string().optional().describe("API endpoint URL. Required when provider is openai-compatible."),
14888
+ api_key: exports_external.string().optional().describe("API key for remote embedding provider (optional)"),
14889
+ input_type: exports_external.string().optional().describe("Optional input_type sent in the embedding request body. Required by some openai-compatible providers (e.g. NVIDIA NIM expects 'query' or 'passage'). Omitted from the request when unset."),
14890
+ truncate: exports_external.string().optional().describe("Optional truncate mode sent in the embedding request body (e.g. NVIDIA NIM accepts 'NONE' | 'START' | 'END'). Omitted from the request when unset.")
14889
14891
  }).superRefine((data, ctx) => {
14890
14892
  if (data.provider === "openai-compatible" && !data.endpoint?.trim()) {
14891
14893
  ctx.addIssue({
@@ -14911,76 +14913,80 @@ var init_magic_context = __esm(() => {
14911
14913
  }
14912
14914
  if (data.provider === "openai-compatible") {
14913
14915
  const apiKey = data.api_key?.trim();
14916
+ const inputType = data.input_type?.trim();
14917
+ const truncate = data.truncate?.trim();
14914
14918
  return {
14915
14919
  provider: "openai-compatible",
14916
14920
  model: data.model?.trim() ?? "",
14917
14921
  endpoint: data.endpoint?.trim() ?? "",
14918
- ...apiKey ? { api_key: apiKey } : {}
14922
+ ...apiKey ? { api_key: apiKey } : {},
14923
+ ...inputType ? { input_type: inputType } : {},
14924
+ ...truncate ? { truncate } : {}
14919
14925
  };
14920
14926
  }
14921
14927
  return { provider: "off" };
14922
14928
  });
14923
14929
  MagicContextConfigSchema = exports_external.object({
14924
- enabled: exports_external.boolean().default(true),
14925
- auto_update: exports_external.boolean().optional(),
14926
- ctx_reduce_enabled: exports_external.boolean().default(true),
14927
- historian: HistorianConfigSchema,
14928
- dreamer: DreamerConfigSchema.optional(),
14929
- cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m"),
14930
- nudge_interval_tokens: exports_external.number().min(1000).default(DEFAULT_NUDGE_INTERVAL_TOKENS),
14930
+ enabled: exports_external.boolean().default(true).describe("Enable magic context (default: true)"),
14931
+ auto_update: exports_external.boolean().optional().describe("Enable automatic npm self-update checks for the OpenCode plugin. Security: USER-only in config loader, so hostile project configs cannot suppress updates."),
14932
+ ctx_reduce_enabled: exports_external.boolean().default(true).describe("When false, ctx_reduce tool is hidden, all nudges disabled, and prompt guidance about ctx_reduce stripped. Heuristic cleanup, compartments, memory, and other features still work. (default: true)"),
14933
+ historian: HistorianConfigSchema.describe("Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)"),
14934
+ dreamer: DreamerConfigSchema.optional().describe("Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)"),
14935
+ cache_ttl: exports_external.union([exports_external.string(), exports_external.object({ default: exports_external.string() }).catchall(exports_external.string())]).default("5m").describe('Cache TTL: string (e.g. "5m") or per-model object ({ default: "5m", "model-id": "10m" })'),
14936
+ nudge_interval_tokens: exports_external.number().min(1000).default(DEFAULT_NUDGE_INTERVAL_TOKENS).describe("Minimum token growth between low-priority rolling nudges (default: DEFAULT_NUDGE_INTERVAL_TOKENS)"),
14931
14937
  execute_threshold_percentage: exports_external.union([
14932
- exports_external.number().min(20).max(80),
14933
- exports_external.object({ default: exports_external.number().min(20).max(80) }).catchall(exports_external.number().min(20).max(80))
14934
- ]).default(DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE),
14938
+ exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE),
14939
+ exports_external.object({ default: exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE) }).catchall(exports_external.number().min(20).max(80, EXECUTE_THRESHOLD_CAP_MESSAGE))
14940
+ ]).default(DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE).describe('Context percentage that forces queued operations to execute. Number or per-model object ({ default: 65, "provider/model": 45 }). Values above 80 are rejected because the runtime caps at 80% for cache safety (MAX_EXECUTE_THRESHOLD). Default: DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE'),
14935
14941
  execute_threshold_tokens: exports_external.object({
14936
14942
  default: exports_external.number().min(5000).max(2000000).optional()
14937
- }).catchall(exports_external.number().min(5000).max(2000000)).optional(),
14938
- protected_tags: exports_external.number().min(1).max(100).optional(),
14939
- auto_drop_tool_age: exports_external.number().min(10).default(100),
14940
- drop_tool_structure: exports_external.boolean().default(true),
14941
- clear_reasoning_age: exports_external.number().min(10).default(50),
14942
- iteration_nudge_threshold: exports_external.number().min(5).default(15),
14943
- history_budget_percentage: exports_external.number().min(0.05).max(0.5).default(DEFAULT_HISTORY_BUDGET_PERCENTAGE),
14944
- historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS),
14943
+ }).catchall(exports_external.number().min(5000).max(2000000)).optional().describe("Absolute token thresholds per model. When matched, overrides execute_threshold_percentage for that model. Accepts `default` for all models or per-model keys. Values above 80% × context_limit are clamped with a warning log. Min 5_000, max 2_000_000."),
14944
+ protected_tags: exports_external.number().min(1).max(100).optional().describe("Number of recent tags to protect from dropping (min: 1, max: 100, default: 20)"),
14945
+ auto_drop_tool_age: exports_external.number().min(10).default(100).describe("Auto-drop tool outputs older than N tags during queue execution (default: 100)"),
14946
+ drop_tool_structure: exports_external.boolean().default(true).describe("When true, dropped tool parts are fully removed instead of truncated in place (default: true)"),
14947
+ clear_reasoning_age: exports_external.number().min(10).default(50).describe("Clear reasoning/thinking blocks older than N tags (default: 50)"),
14948
+ iteration_nudge_threshold: exports_external.number().min(5).default(15).describe("Number of consecutive assistant messages without user input to trigger iteration nudge (default: 15)"),
14949
+ history_budget_percentage: exports_external.number().min(0.05).max(0.5).default(DEFAULT_HISTORY_BUDGET_PERCENTAGE).describe("Fraction of usable context (context_limit × execute_threshold) reserved for the session history block (default: 0.15)"),
14950
+ historian_timeout_ms: exports_external.number().min(60000).default(DEFAULT_HISTORIAN_TIMEOUT_MS).describe("Timeout for each historian prompt call in milliseconds (default: 300000)"),
14945
14951
  commit_cluster_trigger: exports_external.object({
14946
- enabled: exports_external.boolean().default(true),
14947
- min_clusters: exports_external.number().min(1).default(3)
14948
- }).default({ enabled: true, min_clusters: 3 }),
14952
+ enabled: exports_external.boolean().default(true).describe("Enable commit-cluster based historian triggering (default: true)"),
14953
+ min_clusters: exports_external.number().min(1).default(3).describe("Minimum commit clusters required to trigger historian (min: 1, default: 3)")
14954
+ }).default({ enabled: true, min_clusters: 3 }).describe("Commit-cluster trigger: fire historian when enough commit clusters accumulate in the unsummarized tail"),
14949
14955
  system_prompt_injection: exports_external.object({
14950
- enabled: exports_external.boolean().default(true),
14951
- skip_signatures: exports_external.array(exports_external.string()).default(["<!-- magic-context: skip -->"])
14956
+ enabled: exports_external.boolean().default(true).describe("When false, NO injection happens for ANY agent — global escape hatch. (default: true)"),
14957
+ skip_signatures: exports_external.array(exports_external.string()).default(["<!-- magic-context: skip -->"]).describe(`Substring opt-out list. If the agent's system prompt contains any of these strings, skip ALL Magic Context injection for that call. Default "<!-- magic-context: skip -->" is meant to be added inside a user's custom agent prompt to opt that agent out.`)
14952
14958
  }).default({
14953
14959
  enabled: true,
14954
14960
  skip_signatures: ["<!-- magic-context: skip -->"]
14955
- }),
14961
+ }).describe("Controls whether and where Magic Context augments the system prompt. Lets users opt specific agents out of the Magic Context guidance and the surrounding project-docs / user-profile / key-files blocks. OpenCode's internal hidden agents — title, summary, and compaction — are always skipped automatically."),
14956
14962
  sqlite: exports_external.object({
14957
- cache_size_mb: exports_external.number().min(2).max(2048).default(64),
14958
- mmap_size_mb: exports_external.number().min(0).max(8192).default(0)
14959
- }).default({ cache_size_mb: 64, mmap_size_mb: 0 }),
14963
+ cache_size_mb: exports_external.number().min(2).max(2048).default(64).describe("Page-cache size in MiB per connection (PRAGMA cache_size). Larger keeps more hot pages resident, cutting re-reads on repeated full-table scans. (min 2, max 2048, default 64)"),
14964
+ mmap_size_mb: exports_external.number().min(0).max(8192).default(0).describe("Memory-mapped I/O size in MiB (PRAGMA mmap_size). 0 disables mmap (SQLite default). Raising it can cut read overhead on large DBs at the cost of address space. (min 0, max 8192, default 0)")
14965
+ }).default({ cache_size_mb: 64, mmap_size_mb: 0 }).describe("SQLite connection tuning for Magic Context's own context.db. These are per-connection PRAGMAs applied at open; they do not change the schema or what is stored."),
14960
14966
  embedding: EmbeddingConfigSchema.default({
14961
14967
  provider: "local",
14962
14968
  model: DEFAULT_LOCAL_EMBEDDING_MODEL
14963
- }),
14964
- temporal_awareness: exports_external.boolean().default(true),
14969
+ }).describe("Embedding provider configuration"),
14970
+ temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
14965
14971
  caveman_text_compression: exports_external.object({
14966
- enabled: exports_external.boolean().default(false),
14967
- min_chars: exports_external.number().min(100).max(1e4).default(500)
14968
- }).default({ enabled: false, min_chars: 500 }),
14972
+ enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Only active when ctx_reduce_enabled=false. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
14973
+ min_chars: exports_external.number().min(100).max(1e4).default(500).describe("Text parts shorter than this (characters) stay untouched. Min 100, max 10000. Default: 500.")
14974
+ }).default({ enabled: false, min_chars: 500 }).describe("Age-tier caveman compression for long user/assistant text parts. Only active when ctx_reduce_enabled is false. Oldest 20% of eligible tags (outside protected tail) go to ultra, next 20% to full, next 20% to lite, newest 40% untouched. Graduated from experimental.caveman_text_compression; opt-in, default off (lossy)."),
14969
14975
  memory: exports_external.object({
14970
- enabled: exports_external.boolean().default(true),
14971
- injection_budget_tokens: exports_external.number().min(500).max(20000).default(4000),
14972
- auto_promote: exports_external.boolean().default(true),
14973
- retrieval_count_promotion_threshold: exports_external.number().min(1).default(3),
14976
+ enabled: exports_external.boolean().default(true).describe("Enable cross-session memory (default: true)"),
14977
+ injection_budget_tokens: exports_external.number().min(500).max(20000).default(4000).describe("Token budget for memory injection on session start (min: 500, max: 20000, default: 4000)"),
14978
+ auto_promote: exports_external.boolean().default(true).describe("Automatically promote eligible session facts into memory (default: true)"),
14979
+ retrieval_count_promotion_threshold: exports_external.number().min(1).default(3).describe("retrieval_count threshold for promoting memory to permanent status (min: 1, default: 3)"),
14974
14980
  auto_search: exports_external.object({
14975
- enabled: exports_external.boolean().default(true),
14976
- score_threshold: exports_external.number().min(0.3).max(0.95).default(0.6),
14977
- min_prompt_chars: exports_external.number().min(5).max(500).default(20)
14978
- }).default({ enabled: true, score_threshold: 0.6, min_prompt_chars: 20 }),
14981
+ enabled: exports_external.boolean().default(true).describe("Automatically append a compact <ctx-search-hint> to eligible user messages when relevant memories, conversation, or commits are found. Graduated from experimental.auto_search; on by default (set false to opt out). Independent of memory.enabled."),
14982
+ score_threshold: exports_external.number().min(0.3).max(0.95).default(0.6).describe("Top hit score must exceed this threshold for the hint to fire (min: 0.3, max: 0.95, default: 0.60)"),
14983
+ min_prompt_chars: exports_external.number().min(5).max(500).default(20).describe("Skip hint when user message is shorter than this (min: 5, max: 500, default: 20)")
14984
+ }).default({ enabled: true, score_threshold: 0.6, min_prompt_chars: 20 }).describe("Auto-search hint: transform-time ctx_search on each new user message; when the top hit clears the threshold, append a compact <ctx-search-hint> block of vague fragments to that user message. Does NOT inject full content. Graduated from experimental.auto_search; enabled by default (set enabled: false to opt out). Independent of memory.enabled."),
14979
14985
  git_commit_indexing: exports_external.object({
14980
- enabled: exports_external.boolean().default(false),
14981
- since_days: exports_external.number().min(7).max(3650).default(365),
14982
- max_commits: exports_external.number().min(100).max(20000).default(2000)
14983
- }).default({ enabled: false, since_days: 365, max_commits: 2000 })
14986
+ enabled: exports_external.boolean().default(false).describe("Index HEAD git commits for ctx_search (git_commit source). Graduated from experimental.git_commit_indexing; opt-in, default off. Independent of memory.enabled."),
14987
+ since_days: exports_external.number().min(7).max(3650).default(365).describe("Days of HEAD history to index (min: 7, max: 3650, default: 365)"),
14988
+ max_commits: exports_external.number().min(100).max(20000).default(2000).describe("Max commits kept per project; oldest evicted (min: 100, max: 20000, default: 2000)")
14989
+ }).default({ enabled: false, since_days: 365, max_commits: 2000 }).describe("Index git commit messages from HEAD into ctx_search. Commits become a 4th searchable source alongside memories and session history. Graduated from experimental.git_commit_indexing; opt-in, default off (per-project embedding cost). Independent of memory.enabled.")
14984
14990
  }).default({
14985
14991
  enabled: true,
14986
14992
  injection_budget_tokens: 4000,
@@ -14988,8 +14994,8 @@ var init_magic_context = __esm(() => {
14988
14994
  retrieval_count_promotion_threshold: 3,
14989
14995
  auto_search: { enabled: true, score_threshold: 0.6, min_prompt_chars: 20 },
14990
14996
  git_commit_indexing: { enabled: false, since_days: 365, max_commits: 2000 }
14991
- }),
14992
- sidekick: SidekickConfigSchema
14997
+ }).describe("Cross-session memory configuration"),
14998
+ sidekick: SidekickConfigSchema.describe("Optional sidekick agent configuration for session-start memory retrieval")
14993
14999
  }).transform((data) => {
14994
15000
  return {
14995
15001
  ...data,
@@ -163633,7 +163639,8 @@ function getEmbeddingProviderIdentity(config2) {
163633
163639
  provider: "openai-compatible",
163634
163640
  model: config2.model.trim(),
163635
163641
  endpoint: normalizeEndpoint(config2.endpoint),
163636
- apiKeyPresent: Boolean(config2.api_key?.trim())
163642
+ apiKeyPresent: Boolean(config2.api_key?.trim()),
163643
+ inputType: config2.input_type?.trim() || ""
163637
163644
  } : {
163638
163645
  provider: "local",
163639
163646
  model: config2.model?.trim() || DEFAULT_LOCAL_EMBEDDING_MODEL,
@@ -164002,6 +164009,8 @@ class OpenAICompatibleEmbeddingProvider {
164002
164009
  endpoint;
164003
164010
  model;
164004
164011
  apiKey;
164012
+ inputType;
164013
+ truncate;
164005
164014
  initialized = false;
164006
164015
  failureTimes = [];
164007
164016
  circuitOpenUntil = 0;
@@ -164011,6 +164020,8 @@ class OpenAICompatibleEmbeddingProvider {
164011
164020
  this.endpoint = normalizeEndpoint2(options.endpoint);
164012
164021
  this.model = options.model?.trim() ?? "";
164013
164022
  this.apiKey = options.apiKey?.trim() ?? "";
164023
+ this.inputType = options.inputType?.trim() ?? "";
164024
+ this.truncate = options.truncate?.trim() ?? "";
164014
164025
  this.modelId = getEmbeddingProviderIdentity({
164015
164026
  provider: "openai-compatible",
164016
164027
  endpoint: this.endpoint,
@@ -164067,7 +164078,9 @@ class OpenAICompatibleEmbeddingProvider {
164067
164078
  },
164068
164079
  body: JSON.stringify({
164069
164080
  model: this.model,
164070
- input: texts
164081
+ input: texts,
164082
+ ...this.inputType ? { input_type: this.inputType } : {},
164083
+ ...this.truncate ? { truncate: this.truncate } : {}
164071
164084
  }),
164072
164085
  signal: internalController.signal
164073
164086
  });
@@ -164590,7 +164603,9 @@ function createProvider2(config2) {
164590
164603
  return new OpenAICompatibleEmbeddingProvider({
164591
164604
  endpoint: config2.endpoint,
164592
164605
  model: config2.model,
164593
- apiKey: config2.api_key
164606
+ apiKey: config2.api_key,
164607
+ inputType: config2.input_type,
164608
+ truncate: config2.truncate
164594
164609
  });
164595
164610
  }
164596
164611
  return new LocalEmbeddingProvider(config2.model);
@@ -164788,18 +164803,32 @@ async function refreshModelLimitsFromApi(client) {
164788
164803
  }
164789
164804
  }
164790
164805
  function getModelsDevContextLimit(providerID, modelID) {
164791
- const key = `${providerID}/${modelID}`;
164792
- if (apiCache) {
164793
- const fromApi = apiCache.get(key)?.limit;
164794
- if (typeof fromApi === "number")
164795
- return fromApi;
164796
- }
164797
164806
  const now = Date.now();
164798
164807
  if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
164799
164808
  fileLastAttempt = now;
164800
164809
  fileCache = loadModelsDevMetadataFromFile();
164801
164810
  }
164802
- return fileCache.get(key)?.limit;
164811
+ const fromApi = lookupLimitWithTagFallback(apiCache, providerID, modelID);
164812
+ const fromFile = lookupLimitWithTagFallback(fileCache, providerID, modelID);
164813
+ if (typeof fromApi === "number" && typeof fromFile === "number") {
164814
+ return Math.max(fromApi, fromFile);
164815
+ }
164816
+ return fromApi ?? fromFile;
164817
+ }
164818
+ function lookupLimitWithTagFallback(cache, providerID, modelID) {
164819
+ if (!cache)
164820
+ return;
164821
+ const exact = cache.get(`${providerID}/${modelID}`)?.limit;
164822
+ if (typeof exact === "number")
164823
+ return exact;
164824
+ const colonIdx = modelID.lastIndexOf(":");
164825
+ if (colonIdx > 0) {
164826
+ const baseModel = modelID.slice(0, colonIdx);
164827
+ const fallback = cache.get(`${providerID}/${baseModel}`)?.limit;
164828
+ if (typeof fallback === "number")
164829
+ return fallback;
164830
+ }
164831
+ return;
164803
164832
  }
164804
164833
  var RELOAD_INTERVAL_MS, apiCache = null, apiLoadedAt = 0, fileCache = null, fileLastAttempt = 0;
164805
164834
  var init_models_dev_cache = __esm(() => {
@@ -171440,7 +171469,12 @@ function markAnnouncementSeen(version2) {
171440
171469
  function shouldShowAnnouncement() {
171441
171470
  if (!ANNOUNCEMENT_VERSION || ANNOUNCEMENT_FEATURES.length === 0)
171442
171471
  return false;
171443
- return readLastAnnouncedVersion() !== ANNOUNCEMENT_VERSION;
171472
+ const lastVersion = readLastAnnouncedVersion();
171473
+ if (!lastVersion) {
171474
+ markAnnouncementSeen(ANNOUNCEMENT_VERSION);
171475
+ return false;
171476
+ }
171477
+ return lastVersion !== ANNOUNCEMENT_VERSION;
171444
171478
  }
171445
171479
  var ANNOUNCEMENT_VERSION = "0.22.0", ANNOUNCEMENT_FEATURES, ANNOUNCEMENT_FOOTER = "Join us on Discord: https://discord.gg/F2uWxjGnU", STATE_FILENAME = "last_announced_version";
171446
171480
  var init_announcement = __esm(() => {
@@ -171905,10 +171939,19 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
171905
171939
  const defaults = MagicContextConfigSchema.parse({});
171906
171940
  const warnings = [];
171907
171941
  const errorPaths = new Set;
171942
+ const customMessagesByKey = new Map;
171943
+ const GENERIC_ZOD_PREFIXES = ["Too big", "Too small", "Invalid input", "Invalid", "Expected"];
171908
171944
  for (const issue2 of parsed.error.issues) {
171909
171945
  const topKey = issue2.path[0];
171910
171946
  if (topKey !== undefined) {
171911
- errorPaths.add(String(topKey));
171947
+ const key = String(topKey);
171948
+ errorPaths.add(key);
171949
+ const msg = issue2.message;
171950
+ if (msg && !GENERIC_ZOD_PREFIXES.some((p) => msg.startsWith(p))) {
171951
+ if (!customMessagesByKey.has(key)) {
171952
+ customMessagesByKey.set(key, msg);
171953
+ }
171954
+ }
171912
171955
  }
171913
171956
  }
171914
171957
  const patched = { ...rawConfig };
@@ -171921,7 +171964,8 @@ function parsePluginConfig(rawConfig, recoveredTopLevelKeys = []) {
171921
171964
  } else {
171922
171965
  delete patched[key];
171923
171966
  const defaultVal = defaults[key];
171924
- warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.`);
171967
+ const reason = customMessagesByKey.get(key);
171968
+ warnings.push(`"${key}": invalid value (${redactConfigValue(rawConfig[key])}), using default ${JSON.stringify(defaultVal)}.${reason ? ` ${reason}` : ""}`);
171925
171969
  }
171926
171970
  }
171927
171971
  const retryMigrated = migrateLegacyExperimental(patched, preMigrationWarnings);
@@ -173154,9 +173198,18 @@ function removeInstalledPackage(installDir, packageName) {
173154
173198
  const packageDir = join9(installDir, "node_modules", packageName);
173155
173199
  if (!existsSync9(packageDir))
173156
173200
  return false;
173157
- rmSync(packageDir, { recursive: true, force: true });
173158
- log(`[auto-update-checker] Package removed: ${packageDir}`);
173159
- return true;
173201
+ try {
173202
+ rmSync(packageDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
173203
+ log(`[auto-update-checker] Package removed: ${packageDir}`);
173204
+ return true;
173205
+ } catch (err) {
173206
+ const code = err.code;
173207
+ if (code === "EBUSY" || code === "EPERM" || code === "ENOTEMPTY") {
173208
+ warn2(`[auto-update-checker] Could not remove ${packageDir} (${code}); the file is locked by the running process. Continuing — npm install will overwrite it in place.`);
173209
+ return false;
173210
+ }
173211
+ throw err;
173212
+ }
173160
173213
  }
173161
173214
  function resolveInstallContext(runtimePackageJsonPath = getCurrentRuntimePackageJsonPath()) {
173162
173215
  if (runtimePackageJsonPath) {
@@ -175821,6 +175874,14 @@ function createEventHandler(args) {
175821
175874
  return async (input) => {
175822
175875
  await args.autoUpdateChecker?.(input);
175823
175876
  await args.magicContext?.event?.(input);
175877
+ if (args.onInstanceDisposed && input.event?.type === "server.instance.disposed") {
175878
+ const directory = input.event.properties?.directory;
175879
+ if (typeof directory === "string") {
175880
+ try {
175881
+ await args.onInstanceDisposed(directory);
175882
+ } catch {}
175883
+ }
175884
+ }
175824
175885
  };
175825
175886
  }
175826
175887
 
@@ -175966,8 +176027,21 @@ function resolveExecuteThresholdDetail(config2, modelKey, fallback, options) {
175966
176027
  if (!Number.isFinite(resolved) || resolved < 0) {
175967
176028
  resolved = fallback;
175968
176029
  }
176030
+ const cappedPercentage = Math.min(resolved, MAX_EXECUTE_THRESHOLD);
176031
+ if (cappedPercentage < resolved) {
176032
+ const dedupeKey = `pct|${options?.sessionId ?? "__global__"}|${modelKey ?? "__default__"}|${resolved}`;
176033
+ if (!clampWarnSeen.has(dedupeKey)) {
176034
+ clampWarnSeen.add(dedupeKey);
176035
+ const msg = `execute_threshold clamped ${resolved}% → ${MAX_EXECUTE_THRESHOLD}% for ${modelKey ?? "default"} (capped for cache safety; a large step can overflow before compaction, and 80% stays below the 85%/95% emergency bands)`;
176036
+ if (options?.sessionId) {
176037
+ sessionLog(options.sessionId, `WARN: ${msg}`);
176038
+ } else {
176039
+ log(`[magic-context] WARN: ${msg}`);
176040
+ }
176041
+ }
176042
+ }
175969
176043
  return {
175970
- percentage: Math.min(resolved, MAX_EXECUTE_THRESHOLD),
176044
+ percentage: cappedPercentage,
175971
176045
  mode: "percentage",
175972
176046
  matchedKey
175973
176047
  };
@@ -185041,6 +185115,8 @@ var plugin = async (ctx) => {
185041
185115
  }
185042
185116
  }
185043
185117
  const storageDir = getMagicContextStorageDir();
185118
+ let rpcServer = null;
185119
+ let stopDreamTimerRegistration;
185044
185120
  if (pluginConfig.enabled) {
185045
185121
  const dreamerRunnable = isDreamerRunnable(pluginConfig);
185046
185122
  const timerRegistration = {
@@ -185066,8 +185142,8 @@ var plugin = async (ctx) => {
185066
185142
  } : undefined,
185067
185143
  ensureRegistered: ensureProjectRegisteredFromOpenCodeDirectory
185068
185144
  };
185069
- await startDreamScheduleTimer(timerRegistration);
185070
- const rpcServer = new MagicContextRpcServer(storageDir, ctx.directory);
185145
+ stopDreamTimerRegistration = await startDreamScheduleTimer(timerRegistration);
185146
+ rpcServer = new MagicContextRpcServer(storageDir, ctx.directory);
185071
185147
  registerRpcHandlers(rpcServer, {
185072
185148
  directory: ctx.directory,
185073
185149
  config: pluginConfig,
@@ -185121,6 +185197,7 @@ var plugin = async (ctx) => {
185121
185197
  } catch {}
185122
185198
  }
185123
185199
  let lastChatContext = null;
185200
+ const ownProjectIdentity = resolveProjectIdentity(ctx.directory);
185124
185201
  return {
185125
185202
  tool: tools5,
185126
185203
  event: createEventHandler({
@@ -185129,7 +185206,21 @@ var plugin = async (ctx) => {
185129
185206
  autoUpdate: pluginConfig.auto_update !== false,
185130
185207
  signal: autoUpdateAbort.signal,
185131
185208
  storageDir
185132
- })
185209
+ }),
185210
+ onInstanceDisposed: (disposedDirectory) => {
185211
+ if (resolveProjectIdentity(disposedDirectory) !== ownProjectIdentity)
185212
+ return;
185213
+ try {
185214
+ autoUpdateAbort.abort();
185215
+ } catch {}
185216
+ try {
185217
+ stopDreamTimerRegistration?.();
185218
+ } catch {}
185219
+ try {
185220
+ rpcServer?.stop();
185221
+ } catch {}
185222
+ log("[magic-context] instance disposed — stopped RPC server, dream timer, auto-update");
185223
+ }
185133
185224
  }),
185134
185225
  "experimental.chat.messages.transform": createMessagesTransformHandler({
185135
185226
  magicContext: hooks.magicContext
@@ -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"}
@@ -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 +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;;;;;;;;;;;;;;;;;;;;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,CAqBhG;AAkCD,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"}
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.1",
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", () => {
@@ -39,7 +39,11 @@ describe("models-dev-cache", () => {
39
39
  if (v === undefined) delete process.env[k];
40
40
  else process.env[k] = v;
41
41
  }
42
- rmSync(tempDir, { recursive: true, force: true });
42
+ try {
43
+ rmSync(tempDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
44
+ } catch {
45
+ /* Ignore EBUSY on Windows */
46
+ }
43
47
  clearModelsDevCache();
44
48
  });
45
49
 
@@ -234,7 +238,7 @@ describe("models-dev-cache", () => {
234
238
  expect(getModelsDevContextLimit("anthropic", "claude-4")).toBeUndefined();
235
239
  });
236
240
 
237
- test("API cache takes priority over file cache", async () => {
241
+ test("takes the larger limit when both layers know the model (API larger)", async () => {
238
242
  // Seed file layer with one value.
239
243
  const opencodeDir = join(tempDir, "opencode");
240
244
  mkdirSync(opencodeDir, { recursive: true });
@@ -248,7 +252,7 @@ describe("models-dev-cache", () => {
248
252
  // Sanity: file layer returns 100000 before API refresh.
249
253
  expect(getModelsDevContextLimit("anthropic", "claude-4")).toBe(100000);
250
254
 
251
- // Mock client providing DIFFERENT value via API.
255
+ // Mock client providing a LARGER value via API.
252
256
  const mockClient = {
253
257
  config: {
254
258
  providers: async () => ({
@@ -267,7 +271,7 @@ describe("models-dev-cache", () => {
267
271
  };
268
272
  await refreshModelLimitsFromApi(mockClient);
269
273
 
270
- // API value wins.
274
+ // Larger (API) value wins.
271
275
  expect(getModelsDevContextLimit("anthropic", "claude-4")).toBe(1000000);
272
276
 
273
277
  const state = getModelsDevCacheState();
@@ -275,6 +279,70 @@ describe("models-dev-cache", () => {
275
279
  expect(state.apiCount).toBe(1);
276
280
  });
277
281
 
282
+ test("file value wins when the live API reports a smaller (wrong) limit (issue #117)", async () => {
283
+ // The ollama-cloud scenario: models.dev has the correct large window, but
284
+ // ollama reports its tiny default num_ctx via the live /config/providers
285
+ // API. The larger, correct file value must win so pressure isn't bogus.
286
+ const opencodeDir = join(tempDir, "opencode");
287
+ mkdirSync(opencodeDir, { recursive: true });
288
+ writeFileSync(
289
+ join(opencodeDir, "models.json"),
290
+ JSON.stringify({
291
+ "ollama-cloud": {
292
+ models: { "deepseek-v4-pro": { limit: { context: 1048576 } } },
293
+ },
294
+ }),
295
+ );
296
+
297
+ const mockClient = {
298
+ config: {
299
+ providers: async () => ({
300
+ data: {
301
+ providers: [
302
+ {
303
+ id: "ollama-cloud",
304
+ models: {
305
+ // Bogus tiny default num_ctx from ollama.
306
+ "deepseek-v4-pro": { limit: { context: 8192 } },
307
+ },
308
+ },
309
+ ],
310
+ },
311
+ }),
312
+ },
313
+ };
314
+ await refreshModelLimitsFromApi(mockClient);
315
+
316
+ // Larger (file/models.dev) value wins, not the tiny live-API value.
317
+ expect(getModelsDevContextLimit("ollama-cloud", "deepseek-v4-pro")).toBe(1048576);
318
+ });
319
+
320
+ test("matches a tagged ollama model against its tag-less models.dev entry (issue #117)", () => {
321
+ // ollama invokes cloud models with a tag (deepseek-v4-pro:cloud) while
322
+ // models.dev stores them tag-less (deepseek-v4-pro).
323
+ const opencodeDir = join(tempDir, "opencode");
324
+ mkdirSync(opencodeDir, { recursive: true });
325
+ writeFileSync(
326
+ join(opencodeDir, "models.json"),
327
+ JSON.stringify({
328
+ "ollama-cloud": {
329
+ models: {
330
+ "deepseek-v4-pro": { limit: { context: 1048576 } },
331
+ // A legitimately-tagged model must still match exactly.
332
+ "gemma3:27b": { limit: { context: 131072 } },
333
+ },
334
+ },
335
+ }),
336
+ );
337
+
338
+ // Tagged invocation falls back to the tag-less entry.
339
+ expect(getModelsDevContextLimit("ollama-cloud", "deepseek-v4-pro:cloud")).toBe(1048576);
340
+ // Exact tagged match still wins (no wrongful collapse).
341
+ expect(getModelsDevContextLimit("ollama-cloud", "gemma3:27b")).toBe(131072);
342
+ // Unknown tagged model with no tag-less base stays undefined.
343
+ expect(getModelsDevContextLimit("ollama-cloud", "nonexistent:cloud")).toBeUndefined();
344
+ });
345
+
278
346
  test("refreshModelLimitsFromApi tolerates empty/malformed responses", async () => {
279
347
  // Undefined data.
280
348
  await refreshModelLimitsFromApi({
@@ -298,19 +298,58 @@ export async function refreshModelLimitsFromApi(client: OpencodeClientLike): Pro
298
298
  * Returns `undefined` if neither layer knows the model.
299
299
  */
300
300
  export function getModelsDevContextLimit(providerID: string, modelID: string): number | undefined {
301
- const key = `${providerID}/${modelID}`;
302
-
303
- if (apiCache) {
304
- const fromApi = apiCache.get(key)?.limit;
305
- if (typeof fromApi === "number") return fromApi;
306
- }
307
-
308
301
  const now = Date.now();
309
302
  if (!fileCache || now - fileLastAttempt > RELOAD_INTERVAL_MS) {
310
303
  fileLastAttempt = now;
311
304
  fileCache = loadModelsDevMetadataFromFile();
312
305
  }
313
- return fileCache.get(key)?.limit;
306
+
307
+ const fromApi = lookupLimitWithTagFallback(apiCache, providerID, modelID);
308
+ const fromFile = lookupLimitWithTagFallback(fileCache, providerID, modelID);
309
+
310
+ // When BOTH layers know the model, take the LARGER limit. Providers never
311
+ // under-report their real window, so a suspiciously small value — e.g.
312
+ // ollama reporting its default `num_ctx` (4k/8k) for a cloud model via the
313
+ // live `/config/providers` API — must not override the correct, larger
314
+ // models.dev value. A genuinely smaller real limit (provider actually
315
+ // rejects at N) is captured separately via the overflow-detection path
316
+ // (detectedContextLimit), not here. (issue #117)
317
+ if (typeof fromApi === "number" && typeof fromFile === "number") {
318
+ return Math.max(fromApi, fromFile);
319
+ }
320
+ return fromApi ?? fromFile;
321
+ }
322
+
323
+ /**
324
+ * Look up a model's limit in one cache layer, with an ollama-style tag-suffix
325
+ * fallback.
326
+ *
327
+ * models.dev stores some models WITH a colon tag (e.g. `gemma3:27b`,
328
+ * `deepseek-v3.1:671b`) and ollama-cloud base models WITHOUT one
329
+ * (`deepseek-v4-pro`). But ollama invokes cloud models with a tag at runtime
330
+ * (`deepseek-v4-pro:cloud`), so OpenCode reports the tagged id. An exact-only
331
+ * match therefore misses → falls back to the 128k default → wrong pressure
332
+ * denominator (issue #117).
333
+ *
334
+ * Strategy: exact match first (never collapses a legitimately-tagged model),
335
+ * then retry once with the last `:tag` segment stripped.
336
+ */
337
+ function lookupLimitWithTagFallback(
338
+ cache: Map<string, CachedModelMetadata> | null,
339
+ providerID: string,
340
+ modelID: string,
341
+ ): number | undefined {
342
+ if (!cache) return undefined;
343
+ const exact = cache.get(`${providerID}/${modelID}`)?.limit;
344
+ if (typeof exact === "number") return exact;
345
+
346
+ const colonIdx = modelID.lastIndexOf(":");
347
+ if (colonIdx > 0) {
348
+ const baseModel = modelID.slice(0, colonIdx);
349
+ const fallback = cache.get(`${providerID}/${baseModel}`)?.limit;
350
+ if (typeof fallback === "number") return fallback;
351
+ }
352
+ return undefined;
314
353
  }
315
354
 
316
355
  /** Clear in-memory caches (for testing). */
@@ -18,7 +18,11 @@ describe("opencode-compaction-detector", () => {
18
18
  });
19
19
 
20
20
  afterEach(() => {
21
- rmSync(tmpDir, { recursive: true, force: true });
21
+ try {
22
+ rmSync(tmpDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
23
+ } catch {
24
+ /* Ignore EBUSY on Windows */
25
+ }
22
26
  delete process.env.OPENCODE_DISABLE_AUTOCOMPACT;
23
27
  });
24
28
 
@@ -30,7 +34,11 @@ describe("opencode-compaction-detector", () => {
30
34
  const result = isOpenCodeAutoCompactionEnabled(emptyDir);
31
35
 
32
36
  expect(result).toBe(true);
33
- rmSync(emptyDir, { recursive: true, force: true });
37
+ try {
38
+ rmSync(emptyDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
39
+ } catch {
40
+ /* Ignore EBUSY on Windows */
41
+ }
34
42
  });
35
43
  });
36
44
 
@@ -20,7 +20,11 @@ afterEach(async () => {
20
20
  await server.close();
21
21
  }
22
22
  for (const dir of tempDirs.splice(0)) {
23
- rmSync(dir, { recursive: true, force: true });
23
+ try {
24
+ rmSync(dir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
25
+ } catch {
26
+ /* Ignore EBUSY on Windows */
27
+ }
24
28
  }
25
29
  });
26
30