@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
@@ -16,7 +16,7 @@ export const DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE = 65;
16
16
  // escalation derives above the effective threshold and the 95% wall stays fixed.
17
17
  export const EXECUTE_THRESHOLD_CAP_MESSAGE =
18
18
  "execute_threshold is capped at 90% for cache safety: output capacity is reserved from the usable context window, and the remaining 10% absorbs mid-turn growth before the absolute 95% emergency wall. Use a value between 20 and 90.";
19
- export const DEFAULT_HISTORIAN_TIMEOUT_MS = 300_000;
19
+ export const DEFAULT_HISTORIAN_TIMEOUT_MS = 600_000;
20
20
  export const DEFAULT_HISTORY_BUDGET_PERCENTAGE = 0.15;
21
21
 
22
22
  export const DEFAULT_LOCAL_EMBEDDING_MODEL = "Xenova/all-MiniLM-L6-v2";
@@ -104,6 +104,212 @@ export const PromptSurfaceConfigSchema = z
104
104
  );
105
105
  export type PromptSurfaceConfig = z.infer<typeof PromptSurfaceConfigSchema>;
106
106
 
107
+ /**
108
+ * The flat model-resolution fields are the only fields that move into harness
109
+ * blocks. Keeping this inventory next to the schema gives the loader migration
110
+ * an exhaustive contract instead of a catch-all rule.
111
+ */
112
+ export const PER_HARNESS_MIGRATION_INVENTORY = {
113
+ historian: {
114
+ retained: [
115
+ "temperature",
116
+ "top_p",
117
+ "prompt",
118
+ "tools",
119
+ "disable",
120
+ "description",
121
+ "mode",
122
+ "color",
123
+ "maxSteps",
124
+ "permission",
125
+ "maxTokens",
126
+ "two_pass",
127
+ "disallowed_tools",
128
+ ],
129
+ migrated_execution: ["model", "fallback_models", "variant", "thinking_level"],
130
+ },
131
+ dreamer: {
132
+ retained: [
133
+ "temperature",
134
+ "top_p",
135
+ "prompt",
136
+ "tools",
137
+ "disable",
138
+ "description",
139
+ "mode",
140
+ "color",
141
+ "maxSteps",
142
+ "permission",
143
+ "maxTokens",
144
+ "inject_docs",
145
+ ],
146
+ migrated_execution: ["model", "fallback_models", "variant", "thinking_level"],
147
+ },
148
+ task: {
149
+ retained: ["schedule", "promotion_threshold"],
150
+ migrated_execution: [
151
+ "model",
152
+ "fallback_models",
153
+ "variant",
154
+ "thinking_level",
155
+ "timeout_minutes",
156
+ ],
157
+ },
158
+ } as const;
159
+
160
+ /** OpenCode entry objects permit `variant` and reject Pi-only `thinking_level`. */
161
+ const OcEntryObjectSchema = z
162
+ .object({
163
+ model: z.string().describe("OpenCode model ID (for example, provider/model)."),
164
+ variant: z.string().optional().describe("OpenCode reasoning variant for this entry."),
165
+ })
166
+ .strict();
167
+ export const OcEntrySchema = z.union([z.string(), OcEntryObjectSchema]);
168
+ export type OcEntry = z.infer<typeof OcEntrySchema>;
169
+
170
+ /** Pi entry objects permit `thinking_level` and reject OpenCode-only `variant`. */
171
+ const PiEntryObjectSchema = z
172
+ .object({
173
+ model: z.string().describe("Pi model ID (for example, provider/model)."),
174
+ thinking_level: PiThinkingLevelSchema.describe("Pi thinking level for this entry."),
175
+ })
176
+ .strict();
177
+ export const PiEntrySchema = z.union([z.string(), PiEntryObjectSchema]);
178
+ export type PiEntry = z.infer<typeof PiEntrySchema>;
179
+
180
+ /** Strict model-resolution block used by historian.opencode. */
181
+ export const OpenCodeHarnessBlockSchema = z
182
+ .object({
183
+ model: OcEntrySchema.optional().describe("Primary OpenCode model entry."),
184
+ fallback_models: z
185
+ .array(OcEntrySchema)
186
+ .optional()
187
+ .describe(
188
+ "Ordered fallback OpenCode entries. New-shape configuration requires an array; legacy singleton values migrate to a one-element array.",
189
+ ),
190
+ variant: z
191
+ .string()
192
+ .optional()
193
+ .describe(
194
+ "OpenCode reasoning variant for the primary entry when it declares none. Fallback entries declare variants per-entry.",
195
+ ),
196
+ })
197
+ .strict()
198
+ .describe("Strict OpenCode model-resolution block. It accepts no Pi vocabulary.");
199
+ export type OpenCodeHarnessBlock = z.infer<typeof OpenCodeHarnessBlockSchema>;
200
+
201
+ /** Strict model-resolution block used by historian.pi. */
202
+ export const PiHarnessBlockSchema = z
203
+ .object({
204
+ model: PiEntrySchema.optional().describe("Primary Pi model entry."),
205
+ fallback_models: z
206
+ .array(PiEntrySchema)
207
+ .optional()
208
+ .describe(
209
+ "Ordered fallback Pi entries. New-shape configuration requires an array; legacy singleton values migrate to a one-element array.",
210
+ ),
211
+ thinking_level: PiThinkingLevelSchema.describe(
212
+ "Pi thinking level for the primary entry when it declares none. Fallback entries declare thinking levels per-entry.",
213
+ ),
214
+ })
215
+ .strict()
216
+ .describe("Strict Pi model-resolution block. It accepts no OpenCode vocabulary.");
217
+ export type PiHarnessBlock = z.infer<typeof PiHarnessBlockSchema>;
218
+
219
+ /** Strict OpenCode-only execution override for one dreamer task. */
220
+ export const OpenCodeTaskExecutionSchema = z
221
+ .object({
222
+ model: OcEntrySchema.optional().describe("OpenCode model entry for this task."),
223
+ fallback_models: z
224
+ .array(OcEntrySchema)
225
+ .optional()
226
+ .describe("Ordered OpenCode fallback entries for this task."),
227
+ variant: z
228
+ .string()
229
+ .optional()
230
+ .describe(
231
+ "OpenCode reasoning variant for this task's primary entry when it declares none. Fallback entries declare variants per-entry.",
232
+ ),
233
+ timeout_minutes: z
234
+ .number()
235
+ .min(5)
236
+ .optional()
237
+ .describe("Minutes allowed for this task before it is aborted."),
238
+ })
239
+ .strict();
240
+ export type OpenCodeTaskExecution = z.infer<typeof OpenCodeTaskExecutionSchema>;
241
+
242
+ /** Strict Pi-only execution override for one dreamer task. */
243
+ export const PiTaskExecutionSchema = z
244
+ .object({
245
+ model: PiEntrySchema.optional().describe("Pi model entry for this task."),
246
+ fallback_models: z
247
+ .array(PiEntrySchema)
248
+ .optional()
249
+ .describe("Ordered Pi fallback entries for this task."),
250
+ thinking_level: PiThinkingLevelSchema.describe(
251
+ "Pi thinking level for this task's primary entry when it declares none. Fallback entries declare thinking levels per-entry.",
252
+ ),
253
+ timeout_minutes: z
254
+ .number()
255
+ .min(5)
256
+ .optional()
257
+ .describe("Minutes allowed for this task before it is aborted."),
258
+ })
259
+ .strict();
260
+ export type PiTaskExecution = z.infer<typeof PiTaskExecutionSchema>;
261
+
262
+ /** Strict OpenCode harness block for dreamer execution and task overrides. */
263
+ export const DreamerOpenCodeHarnessBlockSchema = z
264
+ .object({
265
+ model: OcEntrySchema.optional().describe("Primary OpenCode model entry."),
266
+ fallback_models: z
267
+ .array(OcEntrySchema)
268
+ .optional()
269
+ .describe(
270
+ "Ordered fallback OpenCode entries. New-shape configuration requires an array; legacy singleton values migrate to a one-element array.",
271
+ ),
272
+ variant: z
273
+ .string()
274
+ .optional()
275
+ .describe(
276
+ "OpenCode reasoning variant for the primary entry when it declares none. Fallback entries declare variants per-entry.",
277
+ ),
278
+ tasks: z
279
+ .record(z.string(), OpenCodeTaskExecutionSchema)
280
+ .optional()
281
+ .describe(
282
+ "OpenCode task execution overrides. Each named task accepts only model, fallback_models, variant, and timeout_minutes.",
283
+ ),
284
+ })
285
+ .strict()
286
+ .describe("Strict OpenCode dreamer model-resolution block. It accepts no Pi vocabulary.");
287
+ export type DreamerOpenCodeHarnessBlock = z.infer<typeof DreamerOpenCodeHarnessBlockSchema>;
288
+
289
+ /** Strict Pi harness block for dreamer execution and task overrides. */
290
+ export const DreamerPiHarnessBlockSchema = z
291
+ .object({
292
+ model: PiEntrySchema.optional().describe("Primary Pi model entry."),
293
+ fallback_models: z
294
+ .array(PiEntrySchema)
295
+ .optional()
296
+ .describe(
297
+ "Ordered fallback Pi entries. New-shape configuration requires an array; legacy singleton values migrate to a one-element array.",
298
+ ),
299
+ thinking_level: PiThinkingLevelSchema.describe(
300
+ "Pi thinking level for the primary entry when it declares none. Fallback entries declare thinking levels per-entry.",
301
+ ),
302
+ tasks: z
303
+ .record(z.string(), PiTaskExecutionSchema)
304
+ .optional()
305
+ .describe(
306
+ "Pi task execution overrides. Each named task accepts only model, fallback_models, thinking_level, and timeout_minutes.",
307
+ ),
308
+ })
309
+ .strict()
310
+ .describe("Strict Pi dreamer model-resolution block. It accepts no OpenCode vocabulary.");
311
+ export type DreamerPiHarnessBlock = z.infer<typeof DreamerPiHarnessBlockSchema>;
312
+
107
313
  /** A 5-field cron expression, or "" to disable the task. */
108
314
  const CronScheduleSchema = z
109
315
  .string()
@@ -113,23 +319,15 @@ const CronScheduleSchema = z
113
319
  })
114
320
  .describe('5-field cron schedule (e.g. "0 3 * * *"), or "" to disable this task.');
115
321
 
116
- /** Per-task scheduling + model config. Model/fallback/thinking inherit the
117
- * dreamer-level defaults when omitted. Task-specific params (promotion_threshold)
118
- * are optional and only meaningful for their owning task. */
119
- const DreamTaskBaseConfigSchema = z.object({
120
- schedule: CronScheduleSchema.default(""),
121
- model: z.string().optional().describe("Per-task model override (inherits dreamer.model)"),
122
- fallback_models: z
123
- .union([z.string(), z.array(z.string())])
124
- .optional()
125
- .describe("Per-task fallback chain (inherits dreamer.fallback_models)"),
126
- thinking_level: PiThinkingLevelSchema.describe("Pi only: per-task thinking level"),
127
- timeout_minutes: z
128
- .number()
129
- .min(5)
130
- .default(20)
131
- .describe("Minutes allowed for this task before it is aborted"),
132
- });
322
+ /**
323
+ * Harness-independent task metadata. These fields stay at dreamer.tasks.<task>
324
+ * during per-harness migration; model execution fields are deliberately absent.
325
+ */
326
+ const DreamTaskBaseConfigSchema = z
327
+ .object({
328
+ schedule: CronScheduleSchema.default(""),
329
+ })
330
+ .strict();
133
331
 
134
332
  const PromotionThresholdSchema = z
135
333
  .number()
@@ -188,11 +386,8 @@ function defaultTaskConfig(task: DreamTaskName): z.input<typeof DreamTaskConfigS
188
386
  return base;
189
387
  }
190
388
 
191
- // `.default()` in Zod 4 wants the OUTPUT shape; the function form lets us derive
192
- // it by parsing the (partial) input default so timeout_minutes etc. fill in.
193
- /** The `tasks` record: one entry per canonical task, each defaulting to its
194
- * v1-behavior-preserving schedule. Written explicitly (not via fromEntries) so
195
- * the inferred type stays a precise per-key object. */
389
+ /** The harness-independent task metadata record. Schedule remains the only
390
+ * disable control; runtime gates are not configuration fields. */
196
391
  export const DreamTasksSchema = z
197
392
  .object({
198
393
  "map-memories": DreamTaskBaseConfigSchema.default(() =>
@@ -233,50 +428,59 @@ export const DreamTasksSchema = z
233
428
  ),
234
429
  })
235
430
  .describe(
236
- "Per-task scheduling + model config. Each task has its own cron schedule and may override the dreamer-level model.",
431
+ "Harness-independent task metadata. schedule, promotion_threshold, and other task metadata remain here; execution settings live under dreamer.opencode.tasks or dreamer.pi.tasks.",
237
432
  );
238
433
 
239
- /** Combined dreamer agent + per-task scheduling configuration (Dreamer v2). */
240
- export const DreamerConfigSchema = AgentOverrideConfigSchema.merge(
241
- z.object({
242
- tasks: DreamTasksSchema.default(() => DreamTasksSchema.parse({})),
243
- inject_docs: z
244
- .boolean()
245
- .default(true)
246
- .describe(
247
- "Inject ARCHITECTURE.md and STRUCTURE.md into the m[0] `<project-docs>` block (default true)",
248
- ),
249
- thinking_level: PiThinkingLevelSchema.describe(
250
- "Pi only: default thinking level for dreamer subagent invocations. See historian.thinking_level.",
434
+ const AgentMetadataSchema = AgentOverrideConfigSchema.pick({
435
+ temperature: true,
436
+ top_p: true,
437
+ prompt: true,
438
+ tools: true,
439
+ disable: true,
440
+ description: true,
441
+ mode: true,
442
+ color: true,
443
+ maxSteps: true,
444
+ permission: true,
445
+ maxTokens: true,
446
+ });
447
+
448
+ /** Combined dreamer metadata plus two independent strict execution blocks. */
449
+ export const DreamerConfigSchema = AgentMetadataSchema.extend({
450
+ opencode: DreamerOpenCodeHarnessBlockSchema.optional(),
451
+ pi: DreamerPiHarnessBlockSchema.optional(),
452
+ tasks: DreamTasksSchema.default(() => DreamTasksSchema.parse({})),
453
+ inject_docs: z
454
+ .boolean()
455
+ .default(true)
456
+ .describe(
457
+ "Inject ARCHITECTURE.md and STRUCTURE.md into the m[0] `<project-docs>` block (default true)",
251
458
  ),
252
- }),
253
- );
459
+ });
254
460
  export type DreamerConfig = z.infer<typeof DreamerConfigSchema>;
255
461
 
256
462
  export const SidekickConfigSchema = AgentOverrideConfigSchema.extend({
257
463
  timeout_ms: z.number().default(30000).describe("Timeout for sidekick calls in milliseconds"),
258
464
  system_prompt: z.string().optional().describe("Custom system prompt for sidekick"),
259
465
  thinking_level: PiThinkingLevelSchema.describe(
260
- "Pi only: explicit thinking level for sidekick subagent invocations. See historian.thinking_level.",
466
+ "Pi only: explicit thinking level for sidekick subagent invocations. See historian.pi.thinking_level.",
261
467
  ),
262
468
  }).optional();
263
469
  export type SidekickConfig = NonNullable<z.infer<typeof SidekickConfigSchema>>;
264
470
 
265
- /** Historian agent configuration — includes all agent overrides plus two_pass mode.
266
- * Two-pass mode runs a second editor pass after the initial historian pass to clean
267
- * up low-signal U: lines and cross-compartment duplicates. Recommended for models
268
- * without extended thinking; not needed for Claude Sonnet/Opus when reasoning is
269
- * enabled via OpenCode variant config. */
270
- export const HistorianConfigSchema = AgentOverrideConfigSchema.extend({
471
+ /**
472
+ * Historian metadata remains harness-independent. Only model resolution moves to
473
+ * the strict opencode and pi blocks; two_pass and disallowed_tools stay here.
474
+ */
475
+ export const HistorianConfigSchema = AgentMetadataSchema.extend({
476
+ opencode: OpenCodeHarnessBlockSchema.optional(),
477
+ pi: PiHarnessBlockSchema.optional(),
271
478
  two_pass: z
272
479
  .boolean()
273
480
  .default(false)
274
481
  .describe(
275
482
  "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)",
276
483
  ),
277
- thinking_level: PiThinkingLevelSchema.describe(
278
- "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 | max",
279
- ),
280
484
  disallowed_tools: z
281
485
  .array(z.enum(["*", "read", "aft_outline", "aft_zoom", "aft_search"]))
282
486
  .default([])
@@ -689,10 +893,10 @@ export const MagicContextConfigSchema = z
689
893
  "original language until naturally rewritten.",
690
894
  ),
691
895
  historian: HistorianConfigSchema.describe(
692
- "Historian agent configuration (model, fallback_models, variant, temperature, maxTokens, permission, two_pass, etc.)",
896
+ "Historian metadata plus independent strict OpenCode and Pi execution blocks. Retained metadata stays at historian; model, fallback_models, variant, and thinking_level belong only in historian.opencode or historian.pi.",
693
897
  ),
694
898
  dreamer: DreamerConfigSchema.optional().describe(
695
- "Dreamer agent + scheduling configuration (model, fallback_models, disable, schedule, tasks, etc.)",
899
+ "Dreamer metadata and scheduling plus independent strict OpenCode and Pi execution blocks. schedule and promotion_threshold stay at dreamer.tasks; model, fallback_models, variant, thinking_level, and timeout_minutes belong only in the matching harness block.",
696
900
  ),
697
901
  smart_notes: z
698
902
  .object({
@@ -709,7 +913,7 @@ export const MagicContextConfigSchema = z
709
913
  .union([z.string(), z.object({ default: z.string() }).catchall(z.string())])
710
914
  .default("5m")
711
915
  .describe(
712
- 'Cache TTL: string (e.g. "5m", "1h", "30s") or per-model object ({ default: "5m", "model-id": "10m" }). Set to "never" for lanes kept warm by an external keepwarm proxy — disables the idle-TTL heuristic so MC never initiates a rebuild based on elapsed time.',
916
+ 'How long Magic Context assumes the provider\'s cached prefix stays valid. This is MC\'s own deferral gate — it does not change the provider\'s actual cache lifetime. String (e.g. "5m", "1h", "30s") or per-model object ({ default: "5m", "model-id": "10m" }). Set to "never" to mean MC never assumes expiry (for lanes kept warm externally by a cache-keep tool) — disables the idle-TTL heuristic so MC never initiates a rebuild based on elapsed time. Provider-side extended TTL is a separate request-level concern (cache_control: { ttl } in the request body).',
713
917
  ),
714
918
  prompt_surface: PromptSurfaceConfigSchema.default({ default: "full" }).describe(
715
919
  "Prompt-surface presets: default is full; models use bare model IDs, provider/model, or provider/* routing keys. Guidance and tool-description overrides are user-level only. On OpenCode and Pi, per-model routing applies to the guidance block only: tool descriptions are registered once per process, so they follow the default preset (a v1 plugin-surface limitation; per-model tool descriptions are planned for the OpenCode v2 plugin API once the SDK stabilizes).",
@@ -784,7 +988,7 @@ export const MagicContextConfigSchema = z
784
988
  .number()
785
989
  .min(60_000)
786
990
  .default(DEFAULT_HISTORIAN_TIMEOUT_MS)
787
- .describe("Timeout for each historian prompt call in milliseconds (default: 300000)"),
991
+ .describe("Timeout for each historian prompt call in milliseconds (default: 600000)"),
788
992
  commit_cluster_trigger: z
789
993
  .object({
790
994
  enabled: z
@@ -108,6 +108,42 @@ describe("OMP provider boundary", () => {
108
108
  expect(resolveModelRefForOmp(ompModelRefToCanonical(selector))).toBe(selector);
109
109
  });
110
110
 
111
+ it("translates the OpenCode Zen gateway (opencode) to OMP's opencode-zen spelling", () => {
112
+ expect(resolveModelRefForOmp("opencode/deepseek-v4-flash-free")).toBe(
113
+ "opencode-zen/deepseek-v4-flash-free",
114
+ );
115
+ expect(ompModelRefToCanonical("opencode-zen/deepseek-v4-flash-free")).toBe(
116
+ "opencode/deepseek-v4-flash-free",
117
+ );
118
+ });
119
+
120
+ it("resolves an opencode/ shared ref on OMP via modelRefLookupOrder", () => {
121
+ expect(modelRefLookupOrder("opencode/deepseek-v4-flash-free")).toEqual([
122
+ "opencode/deepseek-v4-flash-free",
123
+ "opencode-zen/deepseek-v4-flash-free",
124
+ ]);
125
+ expect(modelRefLookupOrder("opencode-zen/deepseek-v4-flash-free")).toEqual([
126
+ "opencode/deepseek-v4-flash-free",
127
+ "opencode-zen/deepseek-v4-flash-free",
128
+ ]);
129
+ });
130
+
131
+ it("leaves the OpenCode Zen gateway unchanged on plain Pi (Pi uses opencode)", () => {
132
+ expect(resolveModelRefForPi("opencode/deepseek-v4-flash-free")).toBe(
133
+ "opencode/deepseek-v4-flash-free",
134
+ );
135
+ expect(piModelRefToCanonical("opencode/deepseek-v4-flash-free")).toBe(
136
+ "opencode/deepseek-v4-flash-free",
137
+ );
138
+ });
139
+
140
+ it("keeps opencode-go unmapped on both harnesses (distinct gateway)", () => {
141
+ expect(resolveModelRefForOmp("opencode-go/kimi-k2.6")).toBe("opencode-go/kimi-k2.6");
142
+ expect(ompModelRefToCanonical("opencode-go/kimi-k2.6")).toBe("opencode-go/kimi-k2.6");
143
+ expect(resolveModelRefForPi("opencode-go/kimi-k2.6")).toBe("opencode-go/kimi-k2.6");
144
+ expect(piModelRefToCanonical("opencode-go/kimi-k2.6")).toBe("opencode-go/kimi-k2.6");
145
+ });
146
+
111
147
  it("passes through provider ids that collide with Object.prototype members", () => {
112
148
  for (const ref of [
113
149
  "constructor/model",
@@ -16,6 +16,12 @@
16
16
  * their exported functions remain distinct deliberately. A future OMP catalog
17
17
  * rename must not silently change plain-Pi behavior (or vice versa).
18
18
  *
19
+ * The OpenCode Zen gateway is the one place the two harnesses diverge: OpenCode
20
+ * and plain Pi both name it `opencode`, while OMP exposes the same gateway as
21
+ * `opencode-zen`. Only the OMP map carries that pair; plain Pi keeps `opencode`
22
+ * as an identity. `opencode-go` is a distinct gateway (the "go" variant at
23
+ * opencode.ai/zen/go) and stays unmapped on both harnesses.
24
+ *
19
25
  * The mapping is intentionally not a one-to-one provider identity. Both
20
26
  * harnesses also expose plain `openai` and `google` providers for direct API
21
27
  * keys, while the canonical prefix does not record whether a subscription or
@@ -43,11 +49,13 @@ const PI_TO_CANONICAL_PROVIDER: Readonly<Record<string, string>> = {
43
49
  const CANONICAL_TO_OMP_PROVIDER: Readonly<Record<string, string>> = {
44
50
  openai: "openai-codex",
45
51
  google: "google-antigravity",
52
+ opencode: "opencode-zen",
46
53
  };
47
54
 
48
55
  const OMP_TO_CANONICAL_PROVIDER: Readonly<Record<string, string>> = {
49
56
  "openai-codex": "openai",
50
57
  "google-antigravity": "google",
58
+ "opencode-zen": "opencode",
51
59
  };
52
60
 
53
61
  /** Remap only the provider prefix (text before the first "/"), preserving the
@@ -8,7 +8,14 @@ import {
8
8
  type Node,
9
9
  type ParseError,
10
10
  parseTree,
11
- } from "jsonc-parser";
11
+ } from "jsonc-parser/lib/esm/main.js";
12
+
13
+ // ^ Deep ESM import on purpose. jsonc-parser has no "exports" map, and its
14
+ // "main" points at a UMD build whose runtime-relative requires (./impl/format
15
+ // etc.) survive bundling verbatim and then fail to resolve from inside a
16
+ // bundled chunk. When this module became reachable from the plugin entry (the
17
+ // config raw-loader), that broken require made the ENTIRE plugin bundle fail
18
+ // to load at boot. The ESM build bundles statically and safely.
12
19
 
13
20
  interface TokenLocation {
14
21
  offset: number;
@@ -252,6 +259,72 @@ export function setJsoncValue(text: string, path: JSONPath, value: unknown): str
252
259
  return applyEdits(text, modify(text, path, value, {}));
253
260
  }
254
261
 
262
+ function removeObjectProperty(text: string, object: Node, key: string): string {
263
+ const properties = object.children ?? [];
264
+ const index = properties.findIndex((property) => {
265
+ const propertyKey = property.children?.[0];
266
+ return propertyKey !== undefined && getNodeValue(propertyKey) === key;
267
+ });
268
+ if (index === -1) return text;
269
+
270
+ const property = properties[index];
271
+ if (!property) return text;
272
+ const closingBrace = object.offset + object.length - 1;
273
+ const next = properties[index + 1];
274
+ if (next) {
275
+ const followingComma = findComma(text, property.offset + property.length, next.offset);
276
+ if (!followingComma) return text;
277
+ // Leading and trailing comments remain in their original positions. A
278
+ // comment previously adjacent to a migrated field therefore survives as
279
+ // standalone JSONC rather than being discarded with the old key.
280
+ return (
281
+ text.slice(0, property.offset) +
282
+ text.slice(followingComma.offset + followingComma.length)
283
+ );
284
+ }
285
+
286
+ const previous = properties[index - 1];
287
+ const trailingComma = findComma(text, property.offset + property.length, closingBrace);
288
+ if (!previous) {
289
+ const afterProperty = property.offset + property.length;
290
+ if (!trailingComma) return text.slice(0, property.offset) + text.slice(afterProperty);
291
+ return (
292
+ text.slice(0, property.offset) +
293
+ text.slice(afterProperty, trailingComma.offset) +
294
+ text.slice(trailingComma.offset + trailingComma.length)
295
+ );
296
+ }
297
+
298
+ const precedingComma = findComma(text, previous.offset + previous.length, property.offset);
299
+ if (!precedingComma) return text;
300
+ const afterProperty = property.offset + property.length;
301
+ const withoutProperty =
302
+ text.slice(0, precedingComma.offset) +
303
+ text.slice(precedingComma.offset + precedingComma.length, property.offset) +
304
+ text.slice(afterProperty);
305
+ if (!trailingComma) return withoutProperty;
306
+
307
+ // The trailing comma position is measured in the original string. Removing
308
+ // only one character before it (the preceding comma) shifts it left by one.
309
+ const shiftedTrailingComma = trailingComma.offset - 1;
310
+ return (
311
+ withoutProperty.slice(0, shiftedTrailingComma) +
312
+ withoutProperty.slice(shiftedTrailingComma + trailingComma.length)
313
+ );
314
+ }
315
+
316
+ /**
317
+ * Delete an object property without reserializing sibling fields. Comments are
318
+ * retained as JSONC trivia so key migrations do not erase a user's notes.
319
+ */
320
+ export function removeJsoncValue(text: string, path: JSONPath): string {
321
+ const key = path.at(-1);
322
+ if (typeof key !== "string") return text;
323
+ const parent = findNode(text, path.slice(0, -1));
324
+ if (parent?.type !== "object") return text;
325
+ return removeObjectProperty(text, parent, key);
326
+ }
327
+
255
328
  /**
256
329
  * Remove matching array entries while retaining the exact bytes for survivor
257
330
  * comments and surrounding JSONC regions.