@cortexkit/opencode-magic-context 0.38.0 → 0.39.0

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 (188) hide show
  1. package/README.md +37 -4
  2. package/dist/{announcement-6c1g4es4.js → announcement-5eg4t64s.js} +6 -6
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/project-security.d.ts +11 -6
  5. package/dist/config/project-security.d.ts.map +1 -1
  6. package/dist/config/raw-loader.d.ts +42 -0
  7. package/dist/config/raw-loader.d.ts.map +1 -0
  8. package/dist/config/schema/magic-context.d.ts +586 -551
  9. package/dist/config/schema/magic-context.d.ts.map +1 -1
  10. package/dist/{conflict-warning-hook-an4fxhwf.js → conflict-warning-hook-f406ggbw.js} +5 -5
  11. package/dist/{derive-budgets-da4yg3tz.js → derive-budgets-xk8cy5e6.js} +5 -5
  12. package/dist/{emscripten-module-ap8t3st0.js → emscripten-module-49k43zfy.js} +1 -1
  13. package/dist/{emscripten-module-682pfzaa.js → emscripten-module-ex4v0x4z.js} +1 -1
  14. package/dist/{emscripten-module-r5h7aghp.js → emscripten-module-jfs3d1dq.js} +1 -1
  15. package/dist/{emscripten-module-bkktz927.js → emscripten-module-ss4g4spn.js} +1 -1
  16. package/dist/features/magic-context/dreamer/classify.d.ts +3 -2
  17. package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
  18. package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts +9 -2
  19. package/dist/features/magic-context/dreamer/evaluate-smart-notes.d.ts.map +1 -1
  20. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +4 -2
  21. package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
  22. package/dist/features/magic-context/dreamer/map-memories.d.ts +21 -5
  23. package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
  24. package/dist/features/magic-context/dreamer/refresh-primers.d.ts +3 -2
  25. package/dist/features/magic-context/dreamer/refresh-primers.d.ts.map +1 -1
  26. package/dist/features/magic-context/dreamer/retrospective-orphan-sweep.d.ts +17 -6
  27. package/dist/features/magic-context/dreamer/retrospective-orphan-sweep.d.ts.map +1 -1
  28. package/dist/features/magic-context/dreamer/task-config.d.ts +2 -1
  29. package/dist/features/magic-context/dreamer/task-config.d.ts.map +1 -1
  30. package/dist/features/magic-context/dreamer/task-executor.d.ts +0 -1
  31. package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
  32. package/dist/features/magic-context/dreamer/task-scheduler.d.ts +3 -2
  33. package/dist/features/magic-context/dreamer/task-scheduler.d.ts.map +1 -1
  34. package/dist/features/magic-context/dreamer/verify-prompt.d.ts +5 -2
  35. package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
  36. package/dist/features/magic-context/dreamer/verify.d.ts +6 -3
  37. package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
  38. package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
  39. package/dist/features/magic-context/memory/types.d.ts +16 -0
  40. package/dist/features/magic-context/memory/types.d.ts.map +1 -1
  41. package/dist/features/magic-context/message-index.d.ts +3 -2
  42. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  43. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  44. package/dist/features/magic-context/mural/compress-cues.d.ts +3 -2
  45. package/dist/features/magic-context/mural/compress-cues.d.ts.map +1 -1
  46. package/dist/features/magic-context/smart-notes/compiler.d.ts +3 -2
  47. package/dist/features/magic-context/smart-notes/compiler.d.ts.map +1 -1
  48. package/dist/features/magic-context/smart-notes/condition-compiler.d.ts +2 -0
  49. package/dist/features/magic-context/smart-notes/condition-compiler.d.ts.map +1 -1
  50. package/dist/features/magic-context/smart-notes/sandbox-runner.d.ts.map +1 -1
  51. package/dist/features/magic-context/storage-db.d.ts +1 -1
  52. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  53. package/dist/features/magic-context/storage-embedding-measurements.d.ts +1 -1
  54. package/dist/features/magic-context/storage-embedding-measurements.d.ts.map +1 -1
  55. package/dist/features/magic-context/storage-meta-persisted.d.ts +7 -2
  56. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  57. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  58. package/dist/features/magic-context/storage-meta-shared.d.ts +1 -0
  59. package/dist/features/magic-context/storage-meta-shared.d.ts.map +1 -1
  60. package/dist/features/magic-context/storage-meta.d.ts +1 -1
  61. package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
  62. package/dist/features/magic-context/storage-session-tables.d.ts +18 -0
  63. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -0
  64. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  65. package/dist/features/magic-context/storage.d.ts +1 -1
  66. package/dist/features/magic-context/storage.d.ts.map +1 -1
  67. package/dist/features/magic-context/tool-definition-tokens.d.ts +10 -0
  68. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  69. package/dist/features/magic-context/transform-decision-log.d.ts +33 -0
  70. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  71. package/dist/features/magic-context/user-memory/review-user-memories.d.ts +3 -2
  72. package/dist/features/magic-context/user-memory/review-user-memories.d.ts.map +1 -1
  73. package/dist/{ffi-d8pnn3mn.js → ffi-bbm9vkfa.js} +1 -1
  74. package/dist/{ffi-04jzypzk.js → ffi-kb5r0x1j.js} +1 -1
  75. package/dist/{ffi-t8b862vx.js → ffi-qnrt0y78.js} +1 -1
  76. package/dist/{ffi-9wvt2wwz.js → ffi-yx2hpf4p.js} +1 -1
  77. package/dist/hooks/magic-context/channel2-delivery.d.ts +3 -1
  78. package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
  79. package/dist/hooks/magic-context/command-handler.d.ts +4 -0
  80. package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
  81. package/dist/hooks/magic-context/compartment-runner-historian.d.ts +4 -1
  82. package/dist/hooks/magic-context/compartment-runner-historian.d.ts.map +1 -1
  83. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  84. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  85. package/dist/hooks/magic-context/compartment-runner-types.d.ts +4 -1
  86. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  87. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +14 -2
  88. package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
  89. package/dist/hooks/magic-context/emergency-fail-closed.d.ts +7 -0
  90. package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
  91. package/dist/hooks/magic-context/empty-task-output.d.ts +16 -0
  92. package/dist/hooks/magic-context/empty-task-output.d.ts.map +1 -0
  93. package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
  94. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  95. package/dist/hooks/magic-context/inject-compartments.d.ts +18 -1
  96. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  97. package/dist/hooks/magic-context/lkg-persist.d.ts +14 -0
  98. package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -0
  99. package/dist/hooks/magic-context/lkg-slot.d.ts +13 -0
  100. package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
  101. package/dist/hooks/magic-context/module-state-sync.d.ts +1 -0
  102. package/dist/hooks/magic-context/module-state-sync.d.ts.map +1 -1
  103. package/dist/hooks/magic-context/module-transport.d.ts +12 -0
  104. package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
  105. package/dist/hooks/magic-context/raw-fallback-context-limit.d.ts.map +1 -1
  106. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +4 -1
  107. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  108. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  109. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +12 -0
  110. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
  111. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -1
  112. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  113. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +8 -0
  114. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  115. package/dist/hooks/magic-context/transform.d.ts +9 -1
  116. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  117. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  118. package/dist/{index-cae89eas.js → index-1h9t9xb5.js} +2 -2
  119. package/dist/{index-wckvcay0.js → index-1yh8g550.js} +8 -6
  120. package/dist/{index-cgyfn1s2.js → index-2wchnyk6.js} +4 -2
  121. package/dist/{index-2y0ybv5x.js → index-3dkzzh0t.js} +15 -17
  122. package/dist/{index-4cz5nfxf.js → index-btj2qhmc.js} +5 -5
  123. package/dist/index-ehpzdy0v.js +126 -0
  124. package/dist/{index-mmvhrqqb.js → index-k2ts0wk1.js} +6435 -4339
  125. package/dist/{index-9zhhvr4w.js → index-mg1yx1ph.js} +1 -1
  126. package/dist/{index-ayd60xnw.js → index-rgdqj8nj.js} +3 -3
  127. package/dist/{index-ygcng9t8.js → index-rpgg35ms.js} +50 -50
  128. package/dist/index-rw0qrkvq.js +560 -0
  129. package/dist/index.d.ts.map +1 -1
  130. package/dist/index.js +27158 -24736
  131. package/dist/model-resolution-r9jbcz86.js +15 -0
  132. package/dist/{module-ES6BEMUI-atfbtnbp.js → module-ES6BEMUI-1yjdz59c.js} +4 -4
  133. package/dist/{module-asyncify-2EFITU5U-7xckhw8w.js → module-asyncify-2EFITU5U-fdkx4fhy.js} +1 -1
  134. package/dist/plugin/dream-timer.d.ts +8 -0
  135. package/dist/plugin/dream-timer.d.ts.map +1 -1
  136. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  137. package/dist/plugin/messages-transform.d.ts +14 -6
  138. package/dist/plugin/messages-transform.d.ts.map +1 -1
  139. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  140. package/dist/{prompt-context-jpe2dstn.js → prompt-context-29qkb28f.js} +1 -1
  141. package/dist/{recomp-orchestrator-jsde4ygw.js → recomp-orchestrator-2pncxee7.js} +15 -16
  142. package/dist/{rpc-notifications-4d22ybn5.js → rpc-notifications-1ffm755w.js} +6 -6
  143. package/dist/{safe-notification-target-b693f9x3.js → safe-notification-target-tepzc2xa.js} +3 -3
  144. package/dist/{send-session-notification-dxske5zm.js → send-session-notification-6v5b72b4.js} +7 -7
  145. package/dist/shared/harness-provider-map.d.ts +6 -0
  146. package/dist/shared/harness-provider-map.d.ts.map +1 -1
  147. package/dist/shared/jsonc-edit.d.ts +6 -1
  148. package/dist/shared/jsonc-edit.d.ts.map +1 -1
  149. package/dist/shared/model-resolution.d.ts +53 -0
  150. package/dist/shared/model-resolution.d.ts.map +1 -0
  151. package/dist/shared/model-suggestion-retry.d.ts +4 -1
  152. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  153. package/dist/shared/resolve-fallbacks.d.ts +5 -1
  154. package/dist/shared/resolve-fallbacks.d.ts.map +1 -1
  155. package/dist/shared/status-detail-text.d.ts +4 -0
  156. package/dist/shared/status-detail-text.d.ts.map +1 -0
  157. package/dist/shared/subagent-runner.d.ts +2 -1
  158. package/dist/shared/subagent-runner.d.ts.map +1 -1
  159. package/dist/{storage-meta-session-mk4gsnke.js → storage-meta-session-yvtbaw7c.js} +8 -8
  160. package/dist/{task-config-gbp7r23g.js → task-config-a1qmj9bx.js} +8 -8
  161. package/package.json +4 -2
  162. package/src/config/index.test.ts +73 -17
  163. package/src/config/index.ts +38 -23
  164. package/src/config/project-security.hostile-matrix.test.ts +471 -0
  165. package/src/config/project-security.test.ts +26 -5
  166. package/src/config/project-security.ts +141 -22
  167. package/src/config/raw-loader.test.ts +424 -0
  168. package/src/config/raw-loader.ts +490 -0
  169. package/src/config/schema/magic-context.test.ts +145 -2
  170. package/src/config/schema/magic-context.ts +256 -52
  171. package/src/shared/harness-provider-map.test.ts +36 -0
  172. package/src/shared/harness-provider-map.ts +8 -0
  173. package/src/shared/jsonc-edit.ts +74 -1
  174. package/src/shared/model-resolution.test.ts +170 -0
  175. package/src/shared/model-resolution.ts +227 -0
  176. package/src/shared/model-suggestion-retry.test.ts +27 -0
  177. package/src/shared/model-suggestion-retry.ts +19 -10
  178. package/src/shared/resolve-fallbacks.test.ts +13 -1
  179. package/src/shared/resolve-fallbacks.ts +19 -4
  180. package/src/shared/status-detail-text.test.ts +80 -0
  181. package/src/shared/status-detail-text.ts +56 -0
  182. package/src/shared/subagent-runner.ts +3 -1
  183. package/src/tui/index.tsx +1 -1
  184. package/src/tui/tui-compiled-runtime-imports.test.ts +3 -0
  185. package/src/tui-compiled/index.tsx +1 -1
  186. package/dist/index-crke4z5j.js +0 -3022
  187. package/dist/index-td9depx5.js +0 -46
  188. package/dist/resolve-fallbacks-5n5tvge1.js +0 -11
@@ -342,6 +342,40 @@ describe("loadPluginConfig — secret redaction", () => {
342
342
  expect(w).toBeDefined();
343
343
  });
344
344
 
345
+ it("prunes only cross-harness qualifier leaves and names their full paths", () => {
346
+ const result = loadWithUserConfig(
347
+ JSON.stringify({
348
+ historian: {
349
+ two_pass: true,
350
+ opencode: {
351
+ model: "anthropic/claude-sonnet",
352
+ variant: "high",
353
+ thinking_level: "minimal",
354
+ },
355
+ pi: {
356
+ model: "github-copilot/gpt-5",
357
+ thinking_level: "medium",
358
+ variant: "fast",
359
+ },
360
+ },
361
+ }),
362
+ );
363
+
364
+ expect(result.historian?.two_pass).toBe(true);
365
+ expect(result.historian?.opencode).toEqual({
366
+ model: "anthropic/claude-sonnet",
367
+ variant: "high",
368
+ });
369
+ expect(result.historian?.pi).toEqual({
370
+ model: "github-copilot/gpt-5",
371
+ thinking_level: "medium",
372
+ });
373
+ const warnings = result.configWarnings?.join("\n") ?? "";
374
+ expect(warnings).toContain("historian.opencode.thinking_level");
375
+ expect(warnings).toContain("historian.pi.variant");
376
+ expect(warnings).not.toContain("invalid agent configuration, ignoring");
377
+ });
378
+
345
379
  it("still shows numeric and boolean invalid values (not secrets by nature)", () => {
346
380
  // Numbers/booleans in config fields are never secrets — they're
347
381
  // plain validation mistakes — so we surface them fully to help
@@ -740,25 +774,43 @@ describe("loadPluginConfig — user-only settings", () => {
740
774
  const result = loadWithUserAndProjectConfig(
741
775
  JSON.stringify({
742
776
  historian: {
743
- model: "anthropic/user-historian",
744
- fallback_models: ["anthropic/user-fallback"],
777
+ opencode: {
778
+ model: "anthropic/user-historian",
779
+ fallback_models: ["anthropic/user-fallback"],
780
+ },
781
+ pi: {
782
+ model: "github-copilot/user-historian",
783
+ fallback_models: ["github-copilot/user-fallback"],
784
+ },
745
785
  },
746
786
  }),
747
787
  JSON.stringify({
748
788
  historian: {
749
- model: "anthropic/project-historian",
750
- fallback_models: ["anthropic/project-fallback"],
789
+ opencode: {
790
+ model: "anthropic/project-historian",
791
+ fallback_models: ["anthropic/project-fallback"],
792
+ },
793
+ pi: {
794
+ model: "github-copilot/project-historian",
795
+ fallback_models: ["github-copilot/project-fallback"],
796
+ },
751
797
  temperature: 0.2,
752
798
  },
753
799
  }),
754
800
  );
755
801
 
756
- expect(result.historian?.model).toBe("anthropic/user-historian");
757
- expect(result.historian?.fallback_models).toEqual(["anthropic/user-fallback"]);
802
+ expect(result.historian?.opencode).toEqual({
803
+ model: "anthropic/user-historian",
804
+ fallback_models: ["anthropic/user-fallback"],
805
+ });
806
+ expect(result.historian?.pi).toEqual({
807
+ model: "github-copilot/user-historian",
808
+ fallback_models: ["github-copilot/user-fallback"],
809
+ });
758
810
  expect(result.historian?.temperature).toBe(0.2);
759
- expect(result.configWarnings?.join("\n")).toContain(
760
- "Ignoring historian.model/fallback_models",
761
- );
811
+ const warnings = result.configWarnings?.join("\n") ?? "";
812
+ expect(warnings).toContain("historian.opencode.model");
813
+ expect(warnings).toContain("historian.pi.model");
762
814
  });
763
815
  });
764
816
 
@@ -877,21 +929,25 @@ describe("loadPluginConfig — raw merge preserves user fields not set in projec
877
929
  JSON.stringify({ language: "tr" }),
878
930
  JSON.stringify({
879
931
  dreamer: {
880
- model: "anthropic/project-dreamer",
881
- tasks: {
882
- verify: {
883
- schedule: "0 3 * * *",
884
- model: "anthropic/project-verify",
885
- },
932
+ tasks: { verify: { schedule: "0 3 * * *" } },
933
+ opencode: {
934
+ model: "anthropic/project-dreamer",
935
+ tasks: { verify: { model: "anthropic/project-verify" } },
936
+ },
937
+ pi: {
938
+ model: "github-copilot/project-dreamer",
939
+ tasks: { verify: { model: "github-copilot/project-verify" } },
886
940
  },
887
941
  },
888
942
  }),
889
943
  );
890
944
 
891
945
  expect(result.language).toBe("tr");
892
- expect(result.dreamer?.model).toBe("anthropic/project-dreamer");
946
+ expect(result.dreamer?.opencode?.model).toBe("anthropic/project-dreamer");
947
+ expect(result.dreamer?.pi?.model).toBe("github-copilot/project-dreamer");
893
948
  expect(result.dreamer?.tasks.verify.schedule).toBe("0 3 * * *");
894
- expect(result.dreamer?.tasks.verify.model).toBe("anthropic/project-verify");
949
+ expect(result.dreamer?.opencode?.tasks?.verify?.model).toBe("anthropic/project-verify");
950
+ expect(result.dreamer?.pi?.tasks?.verify?.model).toBe("github-copilot/project-verify");
895
951
  });
896
952
 
897
953
  it("project boolean override beats user default", () => {
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
1
+ import { existsSync } from "node:fs";
2
2
 
3
3
  import { detectConfigFile, isPrototypePollutionKey, parseJsonc } from "../shared/jsonc-parser";
4
4
  import { setOutputReserveConfig } from "../shared/models-dev-cache";
@@ -20,6 +20,7 @@ import {
20
20
  stripUnsafeProjectConfigFields,
21
21
  } from "./project-security";
22
22
  import { pruneNestedConfigLeaf } from "./prune-config-leaf";
23
+ import { loadRawConfigFile } from "./raw-loader";
23
24
  import { type MagicContextConfig, MagicContextConfigSchema } from "./schema/magic-context";
24
25
  import { resolveTransformMode } from "./transform-mode";
25
26
  import { substituteConfigVariables } from "./variable";
@@ -109,8 +110,12 @@ function loadConfigFileDetailed(
109
110
  }
110
111
 
111
112
  let rawText: string;
113
+ let rawWarnings: string[];
112
114
  try {
113
- rawText = readFileSync(configPath, "utf-8");
115
+ const raw = loadRawConfigFile({ configPath, tier: source });
116
+ if (!raw) return null;
117
+ rawText = raw.text;
118
+ rawWarnings = raw.warnings;
114
119
  } catch (error) {
115
120
  return {
116
121
  config: {},
@@ -138,7 +143,7 @@ function loadConfigFileDetailed(
138
143
  );
139
144
  return {
140
145
  config,
141
- warnings: [...substituted.warnings, ...unsafeKeyWarnings].map(
146
+ warnings: [...rawWarnings, ...substituted.warnings, ...unsafeKeyWarnings].map(
142
147
  (warning) => `${configPath}: ${warning}`,
143
148
  ),
144
149
  outcome:
@@ -285,8 +290,9 @@ function parsePluginConfig(
285
290
  }
286
291
 
287
292
  // Full parse failed — recover field-by-field using defaults for invalid fields.
288
- // Agent configs (historian, dreamer, sidekick) are dropped on error rather than defaulted
289
- // because wrong model config could run expensive models or fail silently.
293
+ // Invalid nested leaves are pruned from agent blocks; only root-level or
294
+ // unreachable agent errors drop the block, because guessing a model config
295
+ // could run an expensive unintended model or fail silently.
290
296
  const defaults = MagicContextConfigSchema.parse({});
291
297
  const warnings: string[] = [];
292
298
 
@@ -309,7 +315,13 @@ function parsePluginConfig(
309
315
  const key = String(topKey);
310
316
  errorPaths.add(key);
311
317
  const paths = issuePathsByKey.get(key) ?? [];
312
- paths.push([...issue.path]);
318
+ if (issue.code === "unrecognized_keys") {
319
+ for (const unrecognizedKey of issue.keys) {
320
+ paths.push([...issue.path, unrecognizedKey]);
321
+ }
322
+ } else {
323
+ paths.push([...issue.path]);
324
+ }
313
325
  issuePathsByKey.set(key, paths);
314
326
  const msg = issue.message;
315
327
  if (msg && !GENERIC_ZOD_PREFIXES.some((p) => msg.startsWith(p))) {
@@ -324,21 +336,12 @@ function parsePluginConfig(
324
336
  for (const key of errorPaths) {
325
337
  recoveredTopLevelKeys.push(key);
326
338
  const isAgentConfig = key === "historian" || key === "dreamer" || key === "sidekick";
327
- if (isAgentConfig) {
328
- // Drop agent configs entirely on error — don't default them
329
- delete patched[key];
330
- warnings.push(
331
- `"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`,
332
- );
333
- continue;
334
- }
335
339
 
336
- // For object-valued keys (e.g. `memory`), prune ONLY the invalid nested
337
- // leaves and keep valid siblings, so one bad nested field doesn't wipe the
338
- // whole block — which would silently drop already-migrated graduated keys
339
- // like memory.auto_search / memory.git_commit_indexing. Falls back to
340
- // whole-key deletion when the issue is at the key itself or the value
341
- // isn't a prunable object.
340
+ // For object-valued keys (including agent harness blocks), prune only invalid
341
+ // nested leaves and keep valid siblings, so one bad field does not remove
342
+ // already-migrated keys such as memory.auto_search and
343
+ // memory.git_commit_indexing. Fall back to whole-key deletion when the issue
344
+ // is at the key itself or the value is not a prunable object.
342
345
  const issuePaths = issuePathsByKey.get(key) ?? [];
343
346
  const rawValue = rawConfig[key];
344
347
  const allNested =
@@ -364,10 +367,22 @@ function parsePluginConfig(
364
367
  prunedLeaves.push(result.removed);
365
368
  }
366
369
  }
367
- patched[key] = prunedBlock;
368
- const reason = customMessagesByKey.get(key);
370
+ if (prunedLeaves.length === issuePaths.length) {
371
+ patched[key] = prunedBlock;
372
+ const reason = customMessagesByKey.get(key);
373
+ warnings.push(
374
+ `"${key}": invalid nested field(s) ${prunedLeaves.map((leaf) => `"${key}.${leaf}"`).join(", ")}, using defaults for those.${reason ? ` ${reason}` : ""}`,
375
+ );
376
+ continue;
377
+ }
378
+ }
379
+
380
+ // Root-level or unreachable agent errors cannot be repaired safely because
381
+ // guessing a model configuration could select an expensive unintended model.
382
+ if (isAgentConfig) {
383
+ delete patched[key];
369
384
  warnings.push(
370
- `"${key}": invalid nested field(s) ${prunedLeaves.map((l) => `"${l}"`).join(", ")}, using defaults for those.${reason ? ` ${reason}` : ""}`,
385
+ `"${key}": invalid agent configuration, ignoring. Check your magic-context.jsonc.`,
371
386
  );
372
387
  continue;
373
388
  }