@retaw106/mmc-oc 1.2.3 → 2.1.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 (290) hide show
  1. package/dist/cli.js +7095 -0
  2. package/dist/config/index.d.ts +0 -12
  3. package/dist/config/index.d.ts.map +1 -1
  4. package/dist/config/legacy-harness-blocks.d.ts +27 -0
  5. package/dist/config/legacy-harness-blocks.d.ts.map +1 -0
  6. package/dist/config/live-run-config.d.ts.map +1 -1
  7. package/dist/config/live-snapshot.d.ts +2 -1
  8. package/dist/config/live-snapshot.d.ts.map +1 -1
  9. package/dist/config/project-security.d.ts +4 -5
  10. package/dist/config/project-security.d.ts.map +1 -1
  11. package/dist/config/raw-loader.d.ts +7 -26
  12. package/dist/config/raw-loader.d.ts.map +1 -1
  13. package/dist/config/schema/magic-context.d.ts +164 -259
  14. package/dist/config/schema/magic-context.d.ts.map +1 -1
  15. package/dist/config/variable.d.ts +8 -0
  16. package/dist/config/variable.d.ts.map +1 -1
  17. package/dist/config-init.js +5595 -0
  18. package/dist/{context-a6px04s0.js → context-vhcstgs3.js} +5758 -4727
  19. package/dist/{derive-budgets-fhk00zv8.js → derive-budgets-rjjz5f7y.js} +2 -2
  20. package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
  21. package/dist/features/magic-context/memory/types.d.ts +1 -0
  22. package/dist/features/magic-context/memory/types.d.ts.map +1 -1
  23. package/dist/features/magic-context/message-index.d.ts +2 -0
  24. package/dist/features/magic-context/message-index.d.ts.map +1 -1
  25. package/dist/features/magic-context/migration-v3-perf-schema.d.ts +8 -0
  26. package/dist/features/magic-context/migration-v3-perf-schema.d.ts.map +1 -0
  27. package/dist/features/magic-context/migration-v4-temporal-decisions.d.ts +12 -0
  28. package/dist/features/magic-context/migration-v4-temporal-decisions.d.ts.map +1 -0
  29. package/dist/features/magic-context/migrations.d.ts.map +1 -1
  30. package/dist/features/magic-context/overflow-detection.d.ts +6 -0
  31. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  32. package/dist/features/magic-context/reclaim-protection.d.ts +15 -10
  33. package/dist/features/magic-context/reclaim-protection.d.ts.map +1 -1
  34. package/dist/features/magic-context/session-cache-ttl.d.ts +4 -2
  35. package/dist/features/magic-context/session-cache-ttl.d.ts.map +1 -1
  36. package/dist/features/magic-context/session-project-storage.d.ts +8 -2
  37. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
  38. package/dist/features/magic-context/storage-db.d.ts +1 -1
  39. package/dist/features/magic-context/storage-db.d.ts.map +1 -1
  40. package/dist/features/magic-context/storage-historian-runs.d.ts +2 -0
  41. package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
  42. package/dist/features/magic-context/storage-meta-persisted.d.ts +12 -0
  43. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  44. package/dist/features/magic-context/storage-meta-session.d.ts +6 -0
  45. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  46. package/dist/features/magic-context/storage-ops.d.ts +2 -0
  47. package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
  48. package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
  49. package/dist/features/magic-context/storage-tags.d.ts +17 -4
  50. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  51. package/dist/features/magic-context/storage.d.ts +2 -2
  52. package/dist/features/magic-context/storage.d.ts.map +1 -1
  53. package/dist/features/magic-context/temporal-decisions.d.ts +10 -0
  54. package/dist/features/magic-context/temporal-decisions.d.ts.map +1 -0
  55. package/dist/features/magic-context/tool-definition-tokens.d.ts +3 -0
  56. package/dist/features/magic-context/tool-definition-tokens.d.ts.map +1 -1
  57. package/dist/features/magic-context/transform-decision-log.d.ts +3 -1
  58. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/active-anthropic-turn.d.ts +8 -0
  60. package/dist/hooks/magic-context/active-anthropic-turn.d.ts.map +1 -0
  61. package/dist/hooks/magic-context/apply-operations.d.ts +15 -0
  62. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/auto-search-deadline.d.ts +10 -3
  64. package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
  66. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
  68. package/dist/hooks/magic-context/channel2-cycle.d.ts +2 -0
  69. package/dist/hooks/magic-context/channel2-cycle.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
  71. package/dist/hooks/magic-context/compartment-render-epoch.d.ts +8 -0
  72. package/dist/hooks/magic-context/compartment-render-epoch.d.ts.map +1 -1
  73. package/dist/hooks/magic-context/compartment-runner-partial-recomp.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
  75. package/dist/hooks/magic-context/compartment-runner-types.d.ts +3 -5
  76. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  77. package/dist/hooks/magic-context/compartment-runner.d.ts +2 -1
  78. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  79. package/dist/hooks/magic-context/compartment-trigger.d.ts +3 -1
  80. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  81. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts +12 -0
  82. package/dist/hooks/magic-context/ctx-reduce-availability.d.ts.map +1 -1
  83. package/dist/hooks/magic-context/decision-calibration.d.ts +3 -0
  84. package/dist/hooks/magic-context/decision-calibration.d.ts.map +1 -1
  85. package/dist/hooks/magic-context/emergency-drop.d.ts +2 -0
  86. package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
  87. package/dist/hooks/magic-context/emergency-fail-closed.d.ts +21 -1
  88. package/dist/hooks/magic-context/emergency-fail-closed.d.ts.map +1 -1
  89. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +32 -1
  90. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  91. package/dist/hooks/magic-context/heuristic-cleanup.d.ts +2 -0
  92. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  93. package/dist/hooks/magic-context/historian-drain-gate.d.ts +11 -0
  94. package/dist/hooks/magic-context/historian-drain-gate.d.ts.map +1 -0
  95. package/dist/hooks/magic-context/inject-compartments.d.ts +10 -8
  96. package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
  97. package/dist/hooks/magic-context/latest-assistant-turn.d.ts +25 -0
  98. package/dist/hooks/magic-context/latest-assistant-turn.d.ts.map +1 -0
  99. package/dist/hooks/magic-context/lkg-measured-request.d.ts +69 -0
  100. package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -0
  101. package/dist/hooks/magic-context/lkg-replay-fit.d.ts +118 -0
  102. package/dist/hooks/magic-context/lkg-replay-fit.d.ts.map +1 -0
  103. package/dist/hooks/magic-context/lkg-replay.d.ts +2 -0
  104. package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
  105. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  106. package/dist/hooks/magic-context/read-session-formatting.d.ts +3 -0
  107. package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
  108. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  109. package/dist/hooks/magic-context/reasoning-budget-status.d.ts +15 -0
  110. package/dist/hooks/magic-context/reasoning-budget-status.d.ts.map +1 -0
  111. package/dist/hooks/magic-context/reasoning-budget.d.ts +40 -0
  112. package/dist/hooks/magic-context/reasoning-budget.d.ts.map +1 -0
  113. package/dist/hooks/magic-context/reasoning-removal.d.ts +6 -6
  114. package/dist/hooks/magic-context/reasoning-removal.d.ts.map +1 -1
  115. package/dist/hooks/magic-context/recomp-orchestrator.d.ts +2 -0
  116. package/dist/hooks/magic-context/recomp-orchestrator.d.ts.map +1 -1
  117. package/dist/hooks/magic-context/sentinel.d.ts +2 -0
  118. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  119. package/dist/hooks/magic-context/strip-content.d.ts +7 -3
  120. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  121. package/dist/hooks/magic-context/supersession-reclaim.d.ts +4 -0
  122. package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
  123. package/dist/hooks/magic-context/tag-messages.d.ts +13 -0
  124. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  125. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +5 -0
  126. package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
  127. package/dist/hooks/magic-context/temporal-awareness.d.ts +6 -3
  128. package/dist/hooks/magic-context/temporal-awareness.d.ts.map +1 -1
  129. package/dist/hooks/magic-context/temporal-served-projection.d.ts +4 -0
  130. package/dist/hooks/magic-context/temporal-served-projection.d.ts.map +1 -0
  131. package/dist/hooks/magic-context/tool-reclaim.d.ts +2 -0
  132. package/dist/hooks/magic-context/tool-reclaim.d.ts.map +1 -1
  133. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -28
  134. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  135. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +34 -12
  136. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  137. package/dist/hooks/magic-context/transform.d.ts +11 -5
  138. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  139. package/dist/hooks/magic-context/wrapup-orchestrator.d.ts.map +1 -1
  140. package/dist/{index-fg34g6a5.js → index-1j34a56j.js} +49 -8
  141. package/dist/index-33gprt9g.js +30 -0
  142. package/dist/{index-zx63j2e2.js → index-5cep2r9n.js} +3 -2
  143. package/dist/{index-5pg0acf7.js → index-amycb7b1.js} +1 -1
  144. package/dist/{index-wgt4gamw.js → index-dr0gepzw.js} +507 -53
  145. package/dist/{index-1vs6anwz.js → index-n0vnzz6h.js} +2 -1
  146. package/dist/{index-977gxfz0.js → index-ramz5kkm.js} +1 -1
  147. package/dist/{index-by6h2pav.js → index-wtb9hzk6.js} +499 -284
  148. package/dist/{index-3wagyrnj.js → index-x37vg474.js} +5 -1
  149. package/dist/{index-me3zcj5t.js → index-z2g20msd.js} +4 -8
  150. package/dist/index.js +1 -1
  151. package/dist/{message-time-backfill-5ewk0axe.js → message-time-backfill-phrwp2fz.js} +2 -2
  152. package/dist/{model-resolution-5haaxnpt.js → model-resolution-wazv26n0.js} +1 -1
  153. package/dist/plugin/rpc-handlers.d.ts +4 -0
  154. package/dist/plugin/rpc-handlers.d.ts.map +1 -1
  155. package/dist/{read-session-chunk-apjwrttc.js → read-session-chunk-82rkqa8j.js} +4 -4
  156. package/dist/{recomp-orchestrator-gyeevxaw.js → recomp-orchestrator-vc5dkzy2.js} +9 -9
  157. package/dist/{send-session-notification-y29y9fsr.js → send-session-notification-rnc4nrsr.js} +3 -3
  158. package/dist/shared/auto-search-lifecycle.d.ts +7 -0
  159. package/dist/shared/auto-search-lifecycle.d.ts.map +1 -0
  160. package/dist/shared/cache-ttl-display.d.ts +6 -4
  161. package/dist/shared/cache-ttl-display.d.ts.map +1 -1
  162. package/dist/shared/harness.d.ts +1 -1
  163. package/dist/shared/harness.d.ts.map +1 -1
  164. package/dist/shared/historian-tool-expansions.d.ts +1 -1
  165. package/dist/shared/historian-tool-expansions.d.ts.map +1 -1
  166. package/dist/shared/historian-tool-template.d.ts +2 -2
  167. package/dist/shared/historian-tool-template.d.ts.map +1 -1
  168. package/dist/shared/model-cache-ttl.d.ts +1 -1
  169. package/dist/shared/model-cache-ttl.d.ts.map +1 -1
  170. package/dist/shared/model-resolution.d.ts +6 -3
  171. package/dist/shared/model-resolution.d.ts.map +1 -1
  172. package/dist/shared/model-suggestion-retry.d.ts +2 -0
  173. package/dist/shared/model-suggestion-retry.d.ts.map +1 -1
  174. package/dist/shared/protected-tools-policy.d.ts +6 -0
  175. package/dist/shared/protected-tools-policy.d.ts.map +1 -0
  176. package/dist/shared/provider-output-failure.d.ts +19 -0
  177. package/dist/shared/provider-output-failure.d.ts.map +1 -0
  178. package/dist/shared/sqlite.d.ts +14 -1
  179. package/dist/shared/sqlite.d.ts.map +1 -1
  180. package/dist/shared/temporal-upgrade-fixture.d.ts +19 -0
  181. package/dist/shared/temporal-upgrade-fixture.d.ts.map +1 -0
  182. package/dist/shared/window-geometry.d.ts +4 -0
  183. package/dist/shared/window-geometry.d.ts.map +1 -1
  184. package/dist/shared/write-transaction-timing.d.ts +8 -1
  185. package/dist/shared/write-transaction-timing.d.ts.map +1 -1
  186. package/dist/tools/ctx-expand/render.d.ts.map +1 -1
  187. package/dist/tools/ctx-reduce/tools.d.ts +2 -0
  188. package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
  189. package/dist/v2/fold/host-media.d.ts +4 -0
  190. package/dist/v2/fold/host-media.d.ts.map +1 -1
  191. package/dist/v2/fold/memory-cache.d.ts +70 -0
  192. package/dist/v2/fold/memory-cache.d.ts.map +1 -0
  193. package/dist/v2/fold/native-codec.d.ts +10 -0
  194. package/dist/v2/fold/native-codec.d.ts.map +1 -0
  195. package/dist/v2/fold/native-replay.d.ts +58 -0
  196. package/dist/v2/fold/native-replay.d.ts.map +1 -0
  197. package/dist/v2/fold/policy.d.ts +24 -0
  198. package/dist/v2/fold/policy.d.ts.map +1 -0
  199. package/dist/v2/hidden-child-native.d.ts +7 -0
  200. package/dist/v2/hidden-child-native.d.ts.map +1 -1
  201. package/dist/v2/hidden-child-record.d.ts +19 -0
  202. package/dist/v2/hidden-child-record.d.ts.map +1 -1
  203. package/dist/v2/hidden-completion.d.ts.map +1 -1
  204. package/dist/v2/hooks/context.d.ts.map +1 -1
  205. package/dist/v2/hooks/hidden-child.d.ts +46 -2
  206. package/dist/v2/hooks/hidden-child.d.ts.map +1 -1
  207. package/dist/v2/hooks/payload.d.ts.map +1 -1
  208. package/dist/v2/hooks/store.d.ts.map +1 -1
  209. package/dist/v2/hooks/tools.d.ts.map +1 -1
  210. package/dist/v2/hooks/types.d.ts +14 -0
  211. package/dist/v2/hooks/types.d.ts.map +1 -1
  212. package/dist/v2/server.js +13226 -11340
  213. package/dist/v2/store-reader.d.ts +16 -0
  214. package/dist/v2/store-reader.d.ts.map +1 -1
  215. package/dist/v2/tool-result.d.ts +8 -0
  216. package/dist/v2/tool-result.d.ts.map +1 -0
  217. package/dist/web/api.d.ts +6 -0
  218. package/dist/web/api.d.ts.map +1 -1
  219. package/dist/web/context-store.d.ts +13 -0
  220. package/dist/web/context-store.d.ts.map +1 -1
  221. package/dist/web/display-path.d.ts +28 -0
  222. package/dist/web/display-path.d.ts.map +1 -0
  223. package/dist/web/entry-args.d.ts +33 -0
  224. package/dist/web/entry-args.d.ts.map +1 -0
  225. package/dist/web/fixtures.d.ts +41 -0
  226. package/dist/web/fixtures.d.ts.map +1 -1
  227. package/dist/web/host-opencode.d.ts +9 -0
  228. package/dist/web/host-opencode.d.ts.map +1 -1
  229. package/dist/web/host-pi.d.ts +2 -0
  230. package/dist/web/host-pi.d.ts.map +1 -1
  231. package/dist/web/main.d.ts +4 -6
  232. package/dist/web/main.d.ts.map +1 -1
  233. package/dist/web/main.js +1477 -130
  234. package/dist/web/memories.d.ts +18 -2
  235. package/dist/web/memories.d.ts.map +1 -1
  236. package/dist/web/message-stats.d.ts +45 -0
  237. package/dist/web/message-stats.d.ts.map +1 -0
  238. package/dist/web/ui/index.html +1 -0
  239. package/dist/web/ui/panel.css +7 -0
  240. package/dist/web/ui/panel.js +332 -124
  241. package/dist/web/views.d.ts +73 -9
  242. package/dist/web/views.d.ts.map +1 -1
  243. package/dist/{wrapup-orchestrator-z9sqpqjf.js → wrapup-orchestrator-x9xamts2.js} +10 -9
  244. package/package.json +7 -2
  245. package/src/config/index.test.ts +257 -126
  246. package/src/config/index.ts +45 -39
  247. package/src/config/legacy-harness-blocks.test.ts +78 -0
  248. package/src/config/legacy-harness-blocks.ts +71 -0
  249. package/src/config/live-run-config.test.ts +49 -5
  250. package/src/config/live-run-config.ts +29 -0
  251. package/src/config/live-snapshot.ts +5 -1
  252. package/src/config/profiles.ts +1 -1
  253. package/src/config/project-config-trust-boundary.test.ts +2 -2
  254. package/src/config/project-security.hostile-matrix.test.ts +51 -65
  255. package/src/config/project-security.test.ts +6 -1
  256. package/src/config/project-security.ts +13 -12
  257. package/src/config/protected-tokens.test.ts +1 -1
  258. package/src/config/raw-loader.test.ts +29 -352
  259. package/src/config/raw-loader.ts +17 -406
  260. package/src/config/removed-config-keys.test.ts +1 -1
  261. package/src/config/schema/magic-context.test.ts +60 -64
  262. package/src/config/schema/magic-context.ts +105 -137
  263. package/src/config/variable.ts +21 -12
  264. package/src/shared/auto-search-lifecycle.ts +27 -0
  265. package/src/shared/cache-ttl-display.test.ts +1 -1
  266. package/src/shared/cache-ttl-display.ts +17 -16
  267. package/src/shared/harness.ts +1 -1
  268. package/src/shared/historian-tool-defaults.json +6 -6
  269. package/src/shared/historian-tool-expansions.ts +65 -8
  270. package/src/shared/historian-tool-preview-defaults.json +13 -0
  271. package/src/shared/historian-tool-template.ts +48 -14
  272. package/src/shared/model-cache-ttl.test.ts +2 -2
  273. package/src/shared/model-cache-ttl.ts +8 -4
  274. package/src/shared/model-resolution.test.ts +22 -32
  275. package/src/shared/model-resolution.ts +14 -19
  276. package/src/shared/model-suggestion-retry.ts +63 -7
  277. package/src/shared/protected-tools-policy.ts +25 -0
  278. package/src/shared/provider-output-failure.ts +176 -0
  279. package/src/shared/sqlite-acquisition-routing.test.ts +18 -0
  280. package/src/shared/sqlite.ts +100 -15
  281. package/src/shared/status-detail-text.test.ts +1 -1
  282. package/src/shared/status-view.test.ts +1 -1
  283. package/src/shared/temporal-upgrade-fixture.ts +50 -0
  284. package/src/shared/window-geometry.ts +10 -0
  285. package/src/shared/write-transaction-attribution.test.ts +2 -2
  286. package/src/shared/write-transaction-timing.ts +25 -1
  287. package/dist/features/magic-context/window-report-ledger.d.ts +0 -70
  288. package/dist/features/magic-context/window-report-ledger.d.ts.map +0 -1
  289. package/dist/index-e1q8002h.js +0 -16
  290. package/src/config/misplaced-agent-model.test.ts +0 -55
@@ -116,6 +116,46 @@ function loadWithUserAndProjectConfig(
116
116
  }
117
117
  }
118
118
 
119
+ /**
120
+ * Like loadWithUserAndProjectConfig, but returns the full detailed load result
121
+ * so tests can assert loadOutcome / configParseFailures (config rejection).
122
+ * Omit projectConfigText to load only the user tier.
123
+ */
124
+ function loadDetailedWithConfigs(userConfigText: string, projectConfigText?: string) {
125
+ const xdg = createTestTempDirFromPath(join(tmpdir(), "mc-config-test-"));
126
+ const projectDir = createTestTempDirFromPath(join(tmpdir(), "mc-config-proj-"));
127
+ const fs = require("node:fs") as typeof import("node:fs");
128
+ const configDir = join(xdg, "opencode");
129
+ fs.mkdirSync(configDir, { recursive: true });
130
+ writeFileSync(join(configDir, "mmc.jsonc"), userConfigText, "utf-8");
131
+ if (projectConfigText !== undefined) {
132
+ fs.mkdirSync(join(projectDir, ".mmc"), { recursive: true });
133
+ writeFileSync(join(projectDir, ".mmc", "mmc.jsonc"), projectConfigText, "utf-8");
134
+ }
135
+
136
+ const origXdg = process.env.XDG_CONFIG_HOME;
137
+ process.env.XDG_CONFIG_HOME = xdg;
138
+ try {
139
+ return loadPluginConfigDetailed(projectDir);
140
+ } finally {
141
+ if (origXdg === undefined) {
142
+ delete process.env.XDG_CONFIG_HOME;
143
+ } else {
144
+ process.env.XDG_CONFIG_HOME = origXdg;
145
+ }
146
+ try {
147
+ rmSync(xdg, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
148
+ } catch {
149
+ /* Ignore EBUSY on Windows */
150
+ }
151
+ try {
152
+ rmSync(projectDir, { recursive: true, force: true, maxRetries: 10, retryDelay: 100 });
153
+ } catch {
154
+ /* Ignore EBUSY on Windows */
155
+ }
156
+ }
157
+ }
158
+
119
159
  describe("loadPluginConfig — Fusiform overlay reload", () => {
120
160
  it("does not invalidate a same-path overlay during routine config reads", () => {
121
161
  const overlayDir = createTestTempDirFromPath(join(tmpdir(), "mc-config-overlay-"));
@@ -319,13 +359,13 @@ describe("loadPluginConfig — secret redaction", () => {
319
359
  const result = loadWithUserAndProjectConfig(
320
360
  JSON.stringify({
321
361
  historian: {
322
- opencode: { model: "trusted/historian" },
362
+ model: "trusted/historian",
323
363
  expand_tools: { ask: "Question ${input.question}", peer_send: false },
324
364
  },
325
365
  }),
326
366
  JSON.stringify({
327
367
  historian: {
328
- opencode: { model: "untrusted/historian" },
368
+ model: "untrusted/historian",
329
369
  expand_tools: { ask: false, board: '${input.ops.each("${op}")}' },
330
370
  },
331
371
  }),
@@ -335,41 +375,137 @@ describe("loadPluginConfig — secret redaction", () => {
335
375
  peer_send: false,
336
376
  board: '${input.ops.each("${op}")}',
337
377
  });
338
- expect(result.historian?.opencode?.model).toBe("trusted/historian");
378
+ expect(result.historian?.model).toBe("trusted/historian");
339
379
  });
340
380
 
341
- it("prunes only cross-harness qualifier leaves and names their full paths", () => {
381
+ it("accepts the flat qualifiers without treating them as an agent-config error", () => {
342
382
  const result = loadWithUserConfig(
343
383
  JSON.stringify({
344
384
  historian: {
345
385
  two_pass: true,
346
- opencode: {
386
+ model: {
347
387
  model: "anthropic/claude-sonnet",
348
388
  variant: "high",
349
- thinking_level: "minimal",
350
- },
351
- pi: {
352
- model: "github-copilot/gpt-5",
353
389
  thinking_level: "medium",
354
- variant: "fast",
355
390
  },
391
+ variant: "fast",
356
392
  },
357
393
  }),
358
394
  );
359
395
 
360
396
  expect(result.historian?.two_pass).toBe(true);
361
- expect(result.historian?.opencode).toEqual({
397
+ expect(result.historian?.model).toEqual({
362
398
  model: "anthropic/claude-sonnet",
363
399
  variant: "high",
364
- });
365
- expect(result.historian?.pi).toEqual({
366
- model: "github-copilot/gpt-5",
367
400
  thinking_level: "medium",
368
401
  });
369
- const warnings = result.configWarnings?.join("\n") ?? "";
370
- expect(warnings).toContain("historian.opencode.thinking_level");
371
- expect(warnings).toContain("historian.pi.variant");
372
- expect(warnings).not.toContain("invalid agent configuration, ignoring");
402
+ expect(result.historian?.variant).toBe("fast");
403
+ expect(result.configWarnings?.join("\n") ?? "").not.toContain(
404
+ "invalid agent configuration, ignoring",
405
+ );
406
+ });
407
+
408
+ it("rejects a user config that still carries the removed per-harness historian blocks", () => {
409
+ const detailed = loadDetailedWithConfigs(
410
+ JSON.stringify({
411
+ historian: {
412
+ two_pass: true,
413
+ opencode: { model: "anthropic/claude-sonnet", variant: "high" },
414
+ pi: { model: "github-copilot/gpt-5", thinking_level: "medium" },
415
+ },
416
+ }),
417
+ );
418
+
419
+ // The whole file is rejected: a parse-failure outcome (defaults at boot,
420
+ // last-known-good on live reload), not a silent strip of the block.
421
+ expect(detailed.loadOutcome).toBe("project-file-parse-error");
422
+ expect(detailed.sources.userConfig).toBe("project-file-parse-error");
423
+ expect(detailed.config.historian?.two_pass).toBeUndefined();
424
+ const failures = detailed.configParseFailures.map((f) => f.message).join("\n");
425
+ expect(failures).toContain("historian.opencode");
426
+ expect(failures).toContain("historian.pi");
427
+ });
428
+
429
+ it("rejects a project config that still carries the removed per-harness historian blocks", () => {
430
+ const detailed = loadDetailedWithConfigs(
431
+ JSON.stringify({ historian: { model: "anthropic/user-base" } }),
432
+ JSON.stringify({
433
+ historian: {
434
+ opencode: { model: "attacker/project-historian" },
435
+ pi: { model: "attacker/project-historian" },
436
+ },
437
+ }),
438
+ );
439
+
440
+ expect(detailed.loadOutcome).toBe("project-file-parse-error");
441
+ expect(detailed.sources.projectConfig).toBe("project-file-parse-error");
442
+ // The project file is rejected before tier hardening, so the block is
443
+ // reported (not pre-stripped and invisible); the user base still applies.
444
+ expect(detailed.config.historian?.model).toBe("anthropic/user-base");
445
+ const failures = detailed.configParseFailures.map((f) => f.message).join("\n");
446
+ expect(failures).toContain("historian.opencode");
447
+ expect(failures).toContain("historian.pi");
448
+ });
449
+
450
+ it("still strips the legal flat model fields from a project config with a warning", () => {
451
+ const result = loadWithUserAndProjectConfig(
452
+ JSON.stringify({ historian: { model: "anthropic/user-base" } }),
453
+ JSON.stringify({
454
+ historian: {
455
+ model: "attacker/project-model",
456
+ fallback_models: ["attacker/project-fallback"],
457
+ variant: "high",
458
+ thinking_level: "high",
459
+ },
460
+ }),
461
+ );
462
+
463
+ expect(result.historian?.model).toBe("anthropic/user-base");
464
+ expect(result.configWarnings?.join("\n")).toContain("Ignoring historian.model");
465
+ });
466
+
467
+ it("rejects a user config whose legacy block survives JSONC partial recovery (no crash)", () => {
468
+ // A JSONC syntax error (`tru`) must not hide the removed block: recovery
469
+ // still yields historian.opencode, which must be rejected at load rather
470
+ // than handed to the parser (which would throw uncaught).
471
+ const detailed = loadDetailedWithConfigs(
472
+ '{"historian":{"opencode":{"model":"vendor/old"}},"enabled":tru}',
473
+ );
474
+
475
+ expect(detailed.loadOutcome).toBe("project-file-parse-error");
476
+ expect(detailed.sources.userConfig).toBe("project-file-parse-error");
477
+ expect(detailed.config.historian?.model).toBeUndefined();
478
+ expect(detailed.configParseFailures.every((f) => f.recovered === false)).toBe(true);
479
+ expect(detailed.configParseFailures.map((f) => f.message).join("\n")).toContain(
480
+ "historian.opencode",
481
+ );
482
+ });
483
+
484
+ it("rejects a user config whose UNSELECTED profile legacy block survives partial recovery", () => {
485
+ const detailed = loadDetailedWithConfigs(
486
+ '{"profiles":{"work":{"historian":{"pi":{"model":"x/y"}}}},"enabled":tru}',
487
+ );
488
+
489
+ expect(detailed.loadOutcome).toBe("project-file-parse-error");
490
+ expect(detailed.configParseFailures.every((f) => f.recovered === false)).toBe(true);
491
+ expect(detailed.configParseFailures.map((f) => f.message).join("\n")).toContain(
492
+ "profiles.work.historian.pi",
493
+ );
494
+ });
495
+
496
+ it("rejects a project config whose legacy block survives partial recovery while the user tier stays", () => {
497
+ const detailed = loadDetailedWithConfigs(
498
+ JSON.stringify({ historian: { model: "anthropic/user-base" } }),
499
+ '{"historian":{"opencode":{"model":"attacker/project"}},"enabled":tru}',
500
+ );
501
+
502
+ expect(detailed.loadOutcome).toBe("project-file-parse-error");
503
+ expect(detailed.sources.projectConfig).toBe("project-file-parse-error");
504
+ expect(detailed.config.historian?.model).toBe("anthropic/user-base");
505
+ expect(detailed.configParseFailures.every((f) => f.recovered === false)).toBe(true);
506
+ expect(detailed.configParseFailures.map((f) => f.message).join("\n")).toContain(
507
+ "historian.opencode",
508
+ );
373
509
  });
374
510
 
375
511
  it("still shows numeric and boolean invalid values (not secrets by nature)", () => {
@@ -476,14 +612,14 @@ describe("loadPluginConfig — legacy agent enabled migration", () => {
476
612
  profiles: {
477
613
  work: {
478
614
  [removedKey]: { model: "example/profile-model" },
479
- historian: { opencode: { model: "example/historian" } },
615
+ historian: { model: "example/historian" },
480
616
  },
481
617
  },
482
618
  }),
483
619
  );
484
620
 
485
621
  expect(removedKey in result).toBe(false);
486
- expect(result.historian?.opencode?.model).toBe("example/historian");
622
+ expect(result.historian?.model).toBe("example/historian");
487
623
  expect(result.configWarnings?.filter((warning) => warning.includes(removedKey))).toEqual([
488
624
  `[config] ${REMOVED_AGENT_CONFIG_WARNING}`,
489
625
  ]);
@@ -590,43 +726,25 @@ describe("loadPluginConfig — user-only settings", () => {
590
726
  const result = loadWithUserAndProjectConfig(
591
727
  JSON.stringify({
592
728
  historian: {
593
- opencode: {
594
- model: "anthropic/user-historian",
595
- fallback_models: ["anthropic/user-fallback"],
596
- },
597
- pi: {
598
- model: "github-copilot/user-historian",
599
- fallback_models: ["github-copilot/user-fallback"],
600
- },
729
+ model: "anthropic/user-historian",
730
+ fallback_models: ["anthropic/user-fallback"],
601
731
  },
602
732
  }),
603
733
  JSON.stringify({
604
734
  historian: {
605
- opencode: {
606
- model: "anthropic/project-historian",
607
- fallback_models: ["anthropic/project-fallback"],
608
- },
609
- pi: {
610
- model: "github-copilot/project-historian",
611
- fallback_models: ["github-copilot/project-fallback"],
612
- },
735
+ model: "anthropic/project-historian",
736
+ fallback_models: ["anthropic/project-fallback"],
613
737
  temperature: 0.2,
614
738
  },
615
739
  }),
616
740
  );
617
741
 
618
- expect(result.historian?.opencode).toEqual({
619
- model: "anthropic/user-historian",
620
- fallback_models: ["anthropic/user-fallback"],
621
- });
622
- expect(result.historian?.pi).toEqual({
623
- model: "github-copilot/user-historian",
624
- fallback_models: ["github-copilot/user-fallback"],
625
- });
742
+ expect(result.historian?.model).toBe("anthropic/user-historian");
743
+ expect(result.historian?.fallback_models).toEqual(["anthropic/user-fallback"]);
626
744
  expect(result.historian?.temperature).toBe(0.2);
627
745
  const warnings = result.configWarnings?.join("\n") ?? "";
628
- expect(warnings).toContain("historian.opencode.model");
629
- expect(warnings).toContain("historian.pi.model");
746
+ expect(warnings).toContain("historian.model");
747
+ expect(warnings).toContain("historian.fallback_models");
630
748
  });
631
749
  });
632
750
 
@@ -796,6 +914,7 @@ describe("loadPluginConfig — raw merge preserves user fields not set in projec
796
914
  const result = loadWithUserAndProjectConfig(userConfig, projectConfig);
797
915
 
798
916
  expect(result.memory.enabled).toBe(false);
917
+ // Deprecated key still passes through the raw merge; runtime ignores it.
799
918
  expect(result.smart_drops).toBe(true);
800
919
  });
801
920
 
@@ -812,7 +931,7 @@ describe("loadPluginConfig — raw merge preserves user fields not set in projec
812
931
  });
813
932
 
814
933
  it("project boolean override beats user default", () => {
815
- // User omits smart_drops (default false). Project turns it on.
934
+ // Deprecated smart_drops no longer gates supersession reclaim.
816
935
  const result = loadWithUserAndProjectConfig(
817
936
  JSON.stringify({ enabled: true }),
818
937
  JSON.stringify({ smart_drops: true }),
@@ -848,24 +967,18 @@ describe("loadPluginConfig — user-owned model profiles", () => {
848
967
  profile: "personal",
849
968
  historian: {
850
969
  two_pass: true,
851
- opencode: {
852
- model: "anthropic/base-historian",
853
- fallback_models: ["anthropic/base-fallback"],
854
- },
970
+ model: "anthropic/base-historian",
971
+ fallback_models: ["anthropic/base-fallback"],
855
972
  },
856
973
  profiles: {
857
974
  work: {
858
975
  historian: {
859
- opencode: {
860
- model: { model: "anthropic/work-historian", variant: "high" },
861
- fallback_models: [
862
- { model: "openai/work-fallback", variant: "low" },
863
- ],
864
- },
976
+ model: { model: "anthropic/work-historian", variant: "high" },
977
+ fallback_models: [{ model: "openai/work-fallback", variant: "low" }],
865
978
  },
866
979
  },
867
980
  personal: {
868
- historian: { opencode: { model: "anthropic/personal-historian" } },
981
+ historian: { model: "anthropic/personal-historian" },
869
982
  },
870
983
  },
871
984
  }),
@@ -878,19 +991,22 @@ describe("loadPluginConfig — user-owned model profiles", () => {
878
991
  expect(result.profile).toBe("work");
879
992
  expect(result.memory.enabled).toBe(false);
880
993
  expect(result.historian?.two_pass).toBe(true);
881
- expect(result.historian?.opencode).toEqual({
882
- model: { model: "anthropic/work-historian", variant: "high" },
883
- fallback_models: [{ model: "openai/work-fallback", variant: "low" }],
994
+ expect(result.historian?.model).toEqual({
995
+ model: "anthropic/work-historian",
996
+ variant: "high",
884
997
  });
998
+ expect(result.historian?.fallback_models).toEqual([
999
+ { model: "openai/work-fallback", variant: "low" },
1000
+ ]);
885
1001
  });
886
1002
 
887
1003
  it("uses project selection over user selection and falls back to the base on an unknown name", () => {
888
1004
  const userConfig = JSON.stringify({
889
1005
  profile: "personal",
890
- historian: { opencode: { model: "anthropic/base" } },
1006
+ historian: { model: "anthropic/base" },
891
1007
  profiles: {
892
- personal: { historian: { opencode: { model: "anthropic/personal" } } },
893
- work: { historian: { opencode: { model: "anthropic/work" } } },
1008
+ personal: { historian: { model: "anthropic/personal" } },
1009
+ work: { historian: { model: "anthropic/work" } },
894
1010
  },
895
1011
  });
896
1012
 
@@ -899,12 +1015,12 @@ describe("loadPluginConfig — user-owned model profiles", () => {
899
1015
  const unknownProject = loadWithUserAndProjectConfig(userConfig, '{"profile":"missing"}');
900
1016
 
901
1017
  expect(userDefault.profile).toBe("personal");
902
- expect(userDefault.historian?.opencode?.model).toBe("anthropic/personal");
1018
+ expect(userDefault.historian?.model).toBe("anthropic/personal");
903
1019
  expect(projectOverride.profile).toBe("work");
904
- expect(projectOverride.historian?.opencode?.model).toBe("anthropic/work");
1020
+ expect(projectOverride.historian?.model).toBe("anthropic/work");
905
1021
  expect(unknownProject.profile).toBeUndefined();
906
1022
  expect(unknownProject.enabled).toBe(true);
907
- expect(unknownProject.historian?.opencode?.model).toBe("anthropic/base");
1023
+ expect(unknownProject.historian?.model).toBe("anthropic/base");
908
1024
  expect(unknownProject.configWarnings?.join("\n")).toContain(
909
1025
  'Unknown profile "missing" selected by project config',
910
1026
  );
@@ -914,7 +1030,7 @@ describe("loadPluginConfig — user-owned model profiles", () => {
914
1030
  const result = loadWithUserConfig(
915
1031
  JSON.stringify({
916
1032
  profile: "unsafe",
917
- historian: { opencode: { model: "anthropic/base" } },
1033
+ historian: { model: "anthropic/base" },
918
1034
  profiles: {
919
1035
  unsafe: {
920
1036
  historian: { two_pass: true },
@@ -924,29 +1040,46 @@ describe("loadPluginConfig — user-owned model profiles", () => {
924
1040
  );
925
1041
 
926
1042
  expect(result.profile).toBeUndefined();
927
- expect(result.historian?.opencode?.model).toBe("anthropic/base");
1043
+ expect(result.historian?.model).toBe("anthropic/base");
928
1044
  // two_pass defaults to false; the profile's `true` must not leak through.
929
1045
  expect(result.historian?.two_pass).toBe(false);
930
1046
  expect(result.configWarnings?.join("\n")).toContain("Ignoring profiles from user config");
931
1047
  });
932
1048
 
1049
+ it("rejects a user config whose profile still carries a removed per-harness block", () => {
1050
+ const detailed = loadDetailedWithConfigs(
1051
+ JSON.stringify({
1052
+ profile: "work",
1053
+ profiles: {
1054
+ work: { historian: { opencode: { model: "anthropic/x" } } },
1055
+ },
1056
+ }),
1057
+ );
1058
+
1059
+ // No silent fallback to the base config: the whole file is rejected.
1060
+ expect(detailed.loadOutcome).toBe("project-file-parse-error");
1061
+ expect(detailed.config.profile).toBeUndefined();
1062
+ const failures = detailed.configParseFailures.map((f) => f.message).join("\n");
1063
+ expect(failures).toContain("profiles.work.historian.opencode");
1064
+ });
1065
+
933
1066
  it("strips hostile project profile definitions while allowing only user-owned definitions", () => {
934
1067
  const result = loadWithUserAndProjectConfig(
935
1068
  JSON.stringify({
936
- historian: { opencode: { model: "anthropic/user-base" } },
1069
+ historian: { model: "anthropic/user-base" },
937
1070
  }),
938
1071
  JSON.stringify({
939
1072
  profile: "work",
940
1073
  profiles: {
941
1074
  work: {
942
- historian: { opencode: { model: "attacker/project-profile" } },
1075
+ historian: { model: "attacker/project-profile" },
943
1076
  },
944
1077
  },
945
1078
  }),
946
1079
  );
947
1080
 
948
1081
  expect(result.profile).toBeUndefined();
949
- expect(result.historian?.opencode?.model).toBe("anthropic/user-base");
1082
+ expect(result.historian?.model).toBe("anthropic/user-base");
950
1083
  expect(result.configWarnings?.join("\n")).toContain(
951
1084
  "Ignoring profiles from project config",
952
1085
  );
@@ -954,8 +1087,8 @@ describe("loadPluginConfig — user-owned model profiles", () => {
954
1087
 
955
1088
  it("falls back loudly for inherited profile names without changing the resolved base config", () => {
956
1089
  const userConfig = JSON.stringify({
957
- historian: { opencode: { model: "anthropic/base", fallback_models: ["openai/base"] } },
958
- profiles: { known: { historian: { opencode: { model: "anthropic/known" } } } },
1090
+ historian: { model: "anthropic/base", fallback_models: ["openai/base"] },
1091
+ profiles: { known: { historian: { model: "anthropic/known" } } },
959
1092
  });
960
1093
  const base = loadWithUserConfig(userConfig);
961
1094
  const { configWarnings: _baseWarnings, ...baseWithoutWarnings } = base;
@@ -986,13 +1119,10 @@ describe("loadPluginConfig — user-owned model profiles", () => {
986
1119
  it("ignores a profile inherited from a polluted Object prototype", () => {
987
1120
  const profileName = "profile_overlay_pollution_canary";
988
1121
  const previousDescriptor = Object.getOwnPropertyDescriptor(Object.prototype, profileName);
989
- const userBase = { historian: { opencode: { model: "anthropic/base" } } };
1122
+ const userBase = { historian: { model: "anthropic/base" } };
990
1123
  // Null-prototype input isolates lookup behavior from strict schema validation of inherited data.
991
- const profileOpenCode = Object.assign(Object.create(null), {
992
- model: "anthropic/known",
993
- });
994
1124
  const profileHistorian = Object.assign(Object.create(null), {
995
- opencode: profileOpenCode,
1125
+ model: "anthropic/known",
996
1126
  });
997
1127
  const knownProfile = Object.assign(Object.create(null), {
998
1128
  historian: profileHistorian,
@@ -1001,7 +1131,7 @@ describe("loadPluginConfig — user-owned model profiles", () => {
1001
1131
  known: knownProfile,
1002
1132
  }) as Record<string, unknown>;
1003
1133
  Object.defineProperty(Object.prototype, profileName, {
1004
- value: { historian: { opencode: { model: "attacker/inherited-overlay" } } },
1134
+ value: { historian: { model: "attacker/inherited-overlay" } },
1005
1135
  enumerable: true,
1006
1136
  configurable: true,
1007
1137
  });
@@ -1034,9 +1164,9 @@ describe("loadPluginConfig — user-owned model profiles", () => {
1034
1164
  it("treats empty, null, and non-string project selectors as no selection", () => {
1035
1165
  const userConfig = JSON.stringify({
1036
1166
  profile: "personal",
1037
- historian: { opencode: { model: "anthropic/base" } },
1167
+ historian: { model: "anthropic/base" },
1038
1168
  profiles: {
1039
- personal: { historian: { opencode: { model: "anthropic/personal" } } },
1169
+ personal: { historian: { model: "anthropic/personal" } },
1040
1170
  },
1041
1171
  });
1042
1172
  const invalidProjectSelectors = ["", null, { name: "work" }];
@@ -1045,39 +1175,40 @@ describe("loadPluginConfig — user-owned model profiles", () => {
1045
1175
  const result = loadWithUserAndProjectConfig(userConfig, JSON.stringify({ profile }));
1046
1176
 
1047
1177
  expect(result.profile).toBe("personal");
1048
- expect(result.historian?.opencode?.model).toBe("anthropic/personal");
1178
+ expect(result.historian?.model).toBe("anthropic/personal");
1049
1179
  expect(result.configWarnings?.join("\n")).toContain(
1050
1180
  "Ignoring invalid profile selection from project config",
1051
1181
  );
1052
1182
  }
1053
1183
  });
1054
1184
 
1055
- it("preserves an untouched historian Pi block when a profile overlays OpenCode", () => {
1056
- const piBase = {
1057
- model: { model: "github-copilot/base", thinking_level: "high" },
1058
- fallback_models: [
1059
- { model: "openai/base-fallback", thinking_level: "minimal" },
1060
- { model: "github-copilot/second-base-fallback", thinking_level: "medium" },
1061
- ],
1062
- thinking_level: "high",
1063
- };
1185
+ it("keeps base historian metadata when a profile overlays the model", () => {
1064
1186
  const result = loadWithUserConfig(
1065
1187
  JSON.stringify({
1066
1188
  profile: "work",
1067
1189
  historian: {
1068
- opencode: { model: "anthropic/base-opencode" },
1069
- pi: piBase,
1190
+ two_pass: true,
1191
+ model: "anthropic/base",
1192
+ expand_tools: { read: "Read ${input.path}" },
1070
1193
  },
1071
1194
  profiles: {
1072
1195
  work: {
1073
- historian: { opencode: { model: "anthropic/work-opencode" } },
1196
+ historian: {
1197
+ model: { model: "github-copilot/work", thinking_level: "high" },
1198
+ thinking_level: "medium",
1199
+ },
1074
1200
  },
1075
1201
  },
1076
1202
  }),
1077
1203
  );
1078
1204
 
1079
- expect(result.historian?.opencode).toEqual({ model: "anthropic/work-opencode" });
1080
- expect(result.historian?.pi).toEqual(piBase);
1205
+ expect(result.historian?.two_pass).toBe(true);
1206
+ expect(result.historian?.expand_tools).toEqual({ read: "Read ${input.path}" });
1207
+ expect(result.historian?.model).toEqual({
1208
+ model: "github-copilot/work",
1209
+ thinking_level: "high",
1210
+ });
1211
+ expect(result.historian?.thinking_level).toBe("medium");
1081
1212
  });
1082
1213
 
1083
1214
  it("replaces a base fallback_models array wholesale when a profile provides one", () => {
@@ -1085,27 +1216,23 @@ describe("loadPluginConfig — user-owned model profiles", () => {
1085
1216
  JSON.stringify({
1086
1217
  profile: "work",
1087
1218
  historian: {
1088
- opencode: {
1089
- model: "anthropic/base",
1090
- fallback_models: [
1091
- { model: "openai/base-first", variant: "low" },
1092
- { model: "anthropic/base-second", variant: "high" },
1093
- ],
1094
- },
1219
+ model: "anthropic/base",
1220
+ fallback_models: [
1221
+ { model: "openai/base-first", variant: "low" },
1222
+ { model: "anthropic/base-second", variant: "high" },
1223
+ ],
1095
1224
  },
1096
1225
  profiles: {
1097
1226
  work: {
1098
1227
  historian: {
1099
- opencode: {
1100
- fallback_models: [{ model: "google/work-only", variant: "medium" }],
1101
- },
1228
+ fallback_models: [{ model: "google/work-only", variant: "medium" }],
1102
1229
  },
1103
1230
  },
1104
1231
  },
1105
1232
  }),
1106
1233
  );
1107
1234
 
1108
- expect(result.historian?.opencode?.fallback_models).toEqual([
1235
+ expect(result.historian?.fallback_models).toEqual([
1109
1236
  { model: "google/work-only", variant: "medium" },
1110
1237
  ]);
1111
1238
  });
@@ -1122,9 +1249,9 @@ describe("loadPluginConfig — user-owned model profiles", () => {
1122
1249
  join(xdg, "opencode", "mmc.jsonc"),
1123
1250
  JSON.stringify({
1124
1251
  profiles: {
1125
- work: { historian: { opencode: { model: "anthropic/work" } } },
1252
+ work: { historian: { model: "anthropic/work" } },
1126
1253
  personal: {
1127
- historian: { opencode: { model: "anthropic/personal" } },
1254
+ historian: { model: "anthropic/personal" },
1128
1255
  },
1129
1256
  },
1130
1257
  }),
@@ -1139,9 +1266,9 @@ describe("loadPluginConfig — user-owned model profiles", () => {
1139
1266
  expect(work.profile).toBe("work");
1140
1267
  expect(personal.profile).toBe("personal");
1141
1268
  expect(workAgain.profile).toBe("work");
1142
- expect(work.historian?.opencode?.model).toBe("anthropic/work");
1143
- expect(personal.historian?.opencode?.model).toBe("anthropic/personal");
1144
- expect(workAgain.historian?.opencode?.model).toBe("anthropic/work");
1269
+ expect(work.historian?.model).toBe("anthropic/work");
1270
+ expect(personal.historian?.model).toBe("anthropic/personal");
1271
+ expect(workAgain.historian?.model).toBe("anthropic/work");
1145
1272
  } finally {
1146
1273
  if (previousXdg === undefined) delete process.env.XDG_CONFIG_HOME;
1147
1274
  else process.env.XDG_CONFIG_HOME = previousXdg;
@@ -1157,12 +1284,8 @@ describe("loadPluginConfig — user-owned model profiles", () => {
1157
1284
  profiles: {
1158
1285
  work: {
1159
1286
  historian: {
1160
- opencode: {
1161
- model: { model: "anthropic/work", variant: "high" },
1162
- fallback_models: [
1163
- { model: "openai/work-fallback", variant: "low" },
1164
- ],
1165
- },
1287
+ model: { model: "anthropic/work", variant: "high" },
1288
+ fallback_models: [{ model: "openai/work-fallback", variant: "low" }],
1166
1289
  },
1167
1290
  },
1168
1291
  },
@@ -1233,20 +1356,28 @@ describe("loadPluginConfigDetailed — prompt-surface registration owner", () =>
1233
1356
  });
1234
1357
  });
1235
1358
 
1236
- describe("shared per-harness config loading", () => {
1237
- it("lets OpenCode load Pi agent blocks without schema recovery warnings", () => {
1359
+ describe("shared flat historian config loading", () => {
1360
+ it("resolves one flat entry for both harnesses without schema warnings", () => {
1238
1361
  const config = loadWithUserConfig(
1239
1362
  JSON.stringify({
1240
1363
  historian: {
1241
- opencode: { model: "anthropic/opencode-historian" },
1242
- pi: { model: "anthropic/pi-historian", thinking_level: "high" },
1364
+ model: {
1365
+ model: "anthropic/shared-historian",
1366
+ variant: "high",
1367
+ thinking_level: "high",
1368
+ },
1243
1369
  },
1244
1370
  }),
1245
1371
  );
1246
1372
 
1247
1373
  expect(config.configWarnings).toBeUndefined();
1248
- expect(resolveHistorianModel(config, "opencode").primary?.model).toBe(
1249
- "anthropic/opencode-historian",
1250
- );
1374
+ expect(resolveHistorianModel(config, "opencode").primary).toEqual({
1375
+ model: "anthropic/shared-historian",
1376
+ qualifier: "high",
1377
+ });
1378
+ expect(resolveHistorianModel(config, "pi").primary).toEqual({
1379
+ model: "anthropic/shared-historian",
1380
+ qualifier: "high",
1381
+ });
1251
1382
  });
1252
1383
  });