@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
@@ -1,18 +1,75 @@
1
1
  import defaults from "./historian-tool-defaults.json";
2
+ import previewDefaults from "./historian-tool-preview-defaults.json";
2
3
  import { renderToolTemplate, type ToolExpansionMap } from "./historian-tool-template";
3
4
 
4
5
  export { defaults as DEFAULT_TOOL_EXPANSIONS };
5
- export function expandToolPart(part: unknown, overrides?: ToolExpansionMap): string | null {
6
+
7
+ // Room actions, private-message addresses and board lanes have multiple input
8
+ // shapes. Derive readable labels only for built-ins; configured template overrides
9
+ // keep the unmodified tool input.
10
+ function builtinInput(tool: string, input: unknown): unknown {
11
+ if (!input || typeof input !== "object") return input;
12
+ const i = input as Record<string, unknown>;
13
+ if (tool === "peer_send") {
14
+ const external = i.external as Record<string, unknown> | undefined;
15
+ const recipient = i.reply_to_pmid
16
+ ? `reply to ${i.reply_to_pmid}`
17
+ : i.agent || i.agent_id
18
+ ? `to ${i.agent || i.agent_id}`
19
+ : external?.session_id || i.session_id
20
+ ? `to session ${external?.session_id || i.session_id}`
21
+ : "to unknown recipient";
22
+ return { ...i, recipient };
23
+ }
24
+ if (tool === "room") {
25
+ return {
26
+ ...i,
27
+ room_label: i.room_id || i.title,
28
+ room_detail:
29
+ i.action === "create" || i.action === "invite"
30
+ ? `invitees: ${renderToolTemplate("${input.invitees}", i)}`
31
+ : i.text,
32
+ };
33
+ }
34
+ if (tool === "board") {
35
+ return {
36
+ ...i,
37
+ board_lane: i.lane ? ` ${i.lane}` : "",
38
+ ops: Array.isArray(i.ops)
39
+ ? i.ops.map((op) =>
40
+ op && typeof op === "object"
41
+ ? {
42
+ ...op,
43
+ lane_label:
44
+ typeof op.lane === "string"
45
+ ? op.lane
46
+ : op.lane?.title || op.lane?.id,
47
+ }
48
+ : op,
49
+ )
50
+ : i.ops,
51
+ };
52
+ }
53
+ return input;
54
+ }
55
+
56
+ export function expandToolPart(
57
+ part: unknown,
58
+ overrides?: ToolExpansionMap,
59
+ legacyPreview = false,
60
+ ): string | null {
6
61
  if (!part || typeof part !== "object") return null;
7
62
  const p = part as Record<string, unknown>;
8
63
  if (p.type !== "tool" || typeof p.tool !== "string") return null;
9
- const template =
10
- overrides && Object.hasOwn(overrides, p.tool)
11
- ? overrides[p.tool]
12
- : Object.hasOwn(defaults, p.tool)
13
- ? defaults[p.tool as keyof typeof defaults]
14
- : undefined;
64
+ const builtinDefaults = legacyPreview ? previewDefaults : defaults;
65
+ const overridden = overrides && Object.hasOwn(overrides, p.tool);
66
+ const template = overridden
67
+ ? overrides[p.tool]
68
+ : Object.hasOwn(builtinDefaults, p.tool)
69
+ ? builtinDefaults[p.tool as keyof typeof defaults]
70
+ : undefined;
15
71
  if (typeof template !== "string") return null;
16
72
  const state = p.state as Record<string, unknown> | undefined;
17
- return renderToolTemplate(template, state?.input, state?.output ?? state?.error);
73
+ const input = overridden || legacyPreview ? state?.input : builtinInput(p.tool, state?.input);
74
+ return renderToolTemplate(template, input, state?.output ?? state?.error, legacyPreview);
18
75
  }
@@ -0,0 +1,13 @@
1
+ {
2
+ "todowrite": "Todos (${input.todos.count}): ${input.todos.each(\"${status}: ${content}\")}",
3
+ "question": "Asked user: ${input.questions[*].question.join(\" / \")} → ${output}",
4
+ "task": "Task ${input.subagent_type}: ${input.prompt} → ${output.truncate(200)}",
5
+ "ask": "Asked: ${input.question.truncate(250)} [options: ${input.options.join(\" / \")}] ${input.resolution} → ${output.truncate(300)}",
6
+ "peer_send": "PM to ${input.agent}${input.agent_id}: ${input.message.truncate(400)}",
7
+ "board": "Board ${input.verb}: ${input.ops.each(\"${op} ${item}${lane.title} ${state}${lane.status}${props.text} ${lane.items[*].text}\")}",
8
+ "room": "Room ${input.action} ${input.room_id}: ${input.text.truncate(400)}",
9
+ "work": "Work ${input.action} ${input.id}: ${input.prompt_file}${input.prompt.truncate(200)}${input.notes.truncate(200)} → ${output.truncate(160)}",
10
+ "knowhow": "Looked up how-to: ${input.query}${input.id}",
11
+ "mmc_note": "Note ${input.action} ${input.note_ids}: ${input.content} → ${output.truncate(160)}",
12
+ "mmc_memory": "Memory ${input.action} ${input.category} ${input.ids}: ${input.content} → ${output.truncate(160)}"
13
+ }
@@ -1,4 +1,4 @@
1
- /** A deliberately non-executable language for readable, bounded historian tool lines. */
1
+ /** A deliberately non-executable language for readable historian tool lines. */
2
2
  export type ToolExpansionMap = Record<string, string | false>;
3
3
  type Step = { field: string } | { index: number } | { project: string };
4
4
  type Expression = {
@@ -6,7 +6,7 @@ type Expression = {
6
6
  each?: Template;
7
7
  join?: string;
8
8
  count?: boolean;
9
- cap: number;
9
+ cap?: number;
10
10
  };
11
11
  type Template = Array<string | Expression>;
12
12
 
@@ -78,7 +78,7 @@ class Parser {
78
78
  if (this.source[this.pos] === "(") return this.error();
79
79
  } else break;
80
80
  }
81
- const expression: Expression = { steps, cap: 300 };
81
+ const expression: Expression = { steps };
82
82
  if (this.take(".each(")) {
83
83
  if (relative) return this.error();
84
84
  expression.each = parseTemplate(this.quoted(), true);
@@ -145,9 +145,24 @@ function compiled(source: string): Template | null {
145
145
  function oneLine(value: string): string {
146
146
  return value.replace(/[\r\n\u2028\u2029]+/g, " ");
147
147
  }
148
- function truncate(value: string, cap: number): string {
148
+ function truncate(value: string, cap?: number, legacyPreview = false): string {
149
149
  const chars = Array.from(oneLine(value));
150
- return chars.length > cap ? `${chars.slice(0, cap).join("")}…` : chars.join("");
150
+ if (cap === undefined || chars.length <= cap) return chars.join("");
151
+ if (legacyPreview) return `${chars.slice(0, cap).join("")}…`;
152
+ // Explicit caps stop after sentence-ending punctuation so a cut is not
153
+ // mistaken for an intact tool message. If no sentence fits, show only the marker.
154
+ let end = 0;
155
+ for (let i = 0; i < Math.min(cap, chars.length); i++) {
156
+ if (!".!?。!?".includes(chars[i])) continue;
157
+ let next = i + 1;
158
+ while (next < chars.length && "\"'”’)]".includes(chars[next])) next++;
159
+ if (
160
+ next <= cap &&
161
+ ("。!?".includes(chars[i]) || next === chars.length || /[ \t]/.test(chars[next]))
162
+ )
163
+ end = next;
164
+ }
165
+ return `${end ? `${chars.slice(0, end).join("")} ` : ""}[… ${chars.length - end} more characters]`;
151
166
  }
152
167
  function scalar(value: unknown): string {
153
168
  if (value === undefined) return "";
@@ -174,10 +189,11 @@ function field(value: unknown, key: string): unknown {
174
189
  ? (value as Record<string, unknown>)[key]
175
190
  : undefined;
176
191
  }
177
- function render(nodes: Template, root: unknown): string {
192
+ function render(nodes: Template, root: unknown, legacyPreview: boolean): string {
178
193
  return nodes
179
194
  .map((node) => {
180
195
  if (typeof node === "string") return node;
196
+ const cap = node.cap ?? (legacyPreview ? 300 : undefined);
181
197
  let value = root;
182
198
  let list = false;
183
199
  for (const step of node.steps) {
@@ -196,18 +212,31 @@ function render(nodes: Template, root: unknown): string {
196
212
  }
197
213
  }
198
214
  if (node.count)
199
- return truncate(Array.isArray(value) ? String(value.length) : "", node.cap);
215
+ return truncate(
216
+ Array.isArray(value) ? String(value.length) : "",
217
+ cap,
218
+ legacyPreview,
219
+ );
200
220
  if (node.each || list || node.join !== undefined) {
201
221
  if (!Array.isArray(value)) return "";
202
222
  const elements = value
203
- .slice(0, 10)
223
+ .slice(0, legacyPreview ? 10 : undefined)
204
224
  .map((item) =>
205
- truncate(node.each ? render(node.each, item) : scalar(item), 300),
225
+ truncate(
226
+ node.each ? render(node.each, item, legacyPreview) : scalar(item),
227
+ legacyPreview ? 300 : undefined,
228
+ legacyPreview,
229
+ ),
206
230
  );
207
- if (value.length > 10) elements.push(`… +${value.length - 10} more`);
208
- return truncate(elements.join(node.join ?? (node.each ? "; " : ", ")), node.cap);
231
+ if (legacyPreview && value.length > 10)
232
+ elements.push(`… +${value.length - 10} more`);
233
+ return truncate(
234
+ elements.join(node.join ?? (node.each ? "; " : ", ")),
235
+ cap,
236
+ legacyPreview,
237
+ );
209
238
  }
210
- return truncate(scalar(value), node.cap);
239
+ return truncate(scalar(value), cap, legacyPreview);
211
240
  })
212
241
  .join("");
213
242
  }
@@ -216,6 +245,7 @@ export function renderToolTemplate(
216
245
  source: string,
217
246
  input: unknown,
218
247
  output?: unknown,
248
+ legacyPreview = false,
219
249
  ): string | null {
220
250
  const nodes = compiled(source);
221
251
  if (!nodes) return null;
@@ -241,8 +271,12 @@ export function renderToolTemplate(
241
271
  !node.each &&
242
272
  !node.count &&
243
273
  node.join === undefined
244
- ? truncate(text ?? "", node.cap)
274
+ ? truncate(text ?? "", node.cap ?? (legacyPreview ? 300 : undefined), legacyPreview)
245
275
  : node,
246
276
  );
247
- return truncate(render(bareOutputNodes, root), 1000);
277
+ return truncate(
278
+ render(bareOutputNodes, root, legacyPreview),
279
+ legacyPreview ? 1000 : undefined,
280
+ legacyPreview,
281
+ );
248
282
  }
@@ -29,8 +29,8 @@ describe("known model cache TTL", () => {
29
29
  expect(resolveCacheTtl("5m", "openai/gpt-6")).toBe("30m");
30
30
  expect(resolveCacheTtl("10m", "openai/gpt-6")).toBe("10m");
31
31
  });
32
- it("honors per-model entries before built-ins and built-ins before object defaults", () => {
33
- expect(resolveCacheTtl({ default: "10m" }, "openai/gpt-6")).toBe("30m");
32
+ it("honors per-model entries and configured defaults before built-ins", () => {
33
+ expect(resolveCacheTtl({ default: "10m" }, "openai/gpt-6")).toBe("10m");
34
34
  expect(resolveCacheTtl({ default: "10m", "gpt-6": "5m" }, "openai/gpt-6")).toBe("5m");
35
35
  expect(resolveCacheTtl({ default: "10m", "openai/*": "never" }, "openai/gpt-6")).toBe(
36
36
  "never",
@@ -28,6 +28,8 @@ const MODEL_CACHE_LIFETIMES = [
28
28
  export function resolveModelCacheTtl(
29
29
  config: CacheTtlConfig | undefined,
30
30
  modelKey: string | undefined,
31
+ configuredExplicitly = typeof config === "object" ||
32
+ (typeof config === "string" && config !== "5m"),
31
33
  ): ResolvedCacheTtl {
32
34
  if (config && typeof config !== "string") {
33
35
  const match =
@@ -37,11 +39,13 @@ export function resolveModelCacheTtl(
37
39
  modelKey !== "default"
38
40
  ? config[modelKey]
39
41
  : resolveModelConfigValue(config, modelKey)?.value;
40
- if (match !== undefined) return { value: match, source: "config", modelKey };
42
+ const value = match ?? config.default;
43
+ if (value !== undefined && configuredExplicitly)
44
+ return { value, source: "config", modelKey };
41
45
  }
42
- // A non-5m global string is an explicit policy. The generic 5m default is
43
- // not evidence of provider eviction: documented lifetimes avoid paid rewrites.
44
- if (typeof config === "string" && config !== "5m")
46
+ // The loader supplies provenance: an explicit 5m is policy too. Older callers
47
+ // without that bit still distinguish non-default strings from the schema's 5m.
48
+ if (typeof config === "string" && configuredExplicitly)
45
49
  return { value: config, source: "config", modelKey };
46
50
  const model =
47
51
  canonicalModelIdentity(modelKey ?? "")
@@ -18,8 +18,9 @@ describe("model-resolution", () => {
18
18
  const resolved = resolveHistorianAgentOverrides({
19
19
  ...(fixture.temperature !== undefined ? { temperature: fixture.temperature } : {}),
20
20
  maxTokens: 16_000,
21
- opencode: { model: "google/flash", variant: "low" },
22
- pi: { model: "pi/must-not-leak", thinking_level: "high" },
21
+ model: "google/flash",
22
+ variant: "low",
23
+ thinking_level: "high",
23
24
  });
24
25
  expect(resolved).toEqual({
25
26
  maxTokens: 16_000,
@@ -61,8 +62,7 @@ describe("model-resolution", () => {
61
62
  test("keeps the flash calibration available only as an explicit override", () => {
62
63
  expect(
63
64
  resolveHistorianAgentOverrides({
64
- opencode: { model: { model: "google/flash", variant: "fast" } },
65
- pi: { model: "pi/ignored" },
65
+ model: { model: "google/flash", variant: "fast" },
66
66
  two_pass: true,
67
67
  }),
68
68
  ).toEqual({
@@ -76,56 +76,46 @@ describe("model-resolution", () => {
76
76
  ).toMatchObject({ temperature: 0.1, maxTokens: 12_000 });
77
77
  });
78
78
 
79
- test("does not inherit a block qualifier into unqualified fallbacks", () => {
79
+ test("does not inherit the primary qualifier into unqualified fallbacks", () => {
80
80
  const config = {
81
81
  historian: {
82
- opencode: {
83
- model: "open/primary",
84
- fallback_models: ["open/fallback"],
85
- variant: "primary-only",
86
- },
87
- pi: {
88
- model: "pi/primary",
89
- fallback_models: ["pi/fallback"],
90
- thinking_level: "high",
91
- },
82
+ model: "open/primary",
83
+ fallback_models: ["open/fallback"],
84
+ variant: "primary-only",
85
+ thinking_level: "high",
92
86
  },
93
87
  };
94
88
 
95
89
  // Inheritance is deliberately absent because a fallback model may not support the
96
- // qualifier selected for the primary model.
90
+ // qualifier selected for the primary model. Each harness reads only its own
91
+ // qualifier (OpenCode variant, Pi thinking_level) from the flat entry.
97
92
  expect(resolveHistorianModel(config, "opencode")).toEqual({
98
93
  primary: { model: "open/primary", qualifier: "primary-only" },
99
94
  fallbacks: [{ model: "open/fallback" }],
100
95
  });
101
96
  expect(resolveHistorianModel(config, "pi")).toEqual({
102
- primary: { model: "pi/primary", qualifier: "high" },
103
- fallbacks: [{ model: "pi/fallback" }],
97
+ primary: { model: "open/primary", qualifier: "high" },
98
+ fallbacks: [{ model: "open/fallback" }],
104
99
  });
105
100
  });
106
101
 
107
- test("reads historian attempts only from the requested harness", () => {
102
+ test("selects the requested harness's qualifier from a shared flat entry", () => {
108
103
  const config = {
109
104
  historian: {
110
- model: "flat/ignored",
111
- opencode: {
112
- model: { model: "open/code", variant: "oc-primary" },
113
- fallback_models: [{ model: "open/fallback", variant: "oc-fallback" }],
114
- },
115
- pi: {
116
- model: { model: "pi/model", thinking_level: "high" },
117
- fallback_models: [{ model: "pi/fallback", thinking_level: "max" }],
118
- },
105
+ model: { model: "shared/model", variant: "oc-primary", thinking_level: "high" },
106
+ fallback_models: [
107
+ { model: "shared/fallback", variant: "oc-fallback", thinking_level: "max" },
108
+ ],
119
109
  },
120
110
  };
121
111
 
122
112
  expect(resolveHistorianModel(config, "opencode")).toEqual({
123
- primary: { model: "open/code", qualifier: "oc-primary" },
124
- fallbacks: [{ model: "open/fallback", qualifier: "oc-fallback" }],
113
+ primary: { model: "shared/model", qualifier: "oc-primary" },
114
+ fallbacks: [{ model: "shared/fallback", qualifier: "oc-fallback" }],
125
115
  });
126
116
  expect(resolveHistorianModel(config, "pi")).toEqual({
127
- primary: { model: "pi/model", qualifier: "high" },
128
- fallbacks: [{ model: "pi/fallback", qualifier: "max" }],
117
+ primary: { model: "shared/model", qualifier: "high" },
118
+ fallbacks: [{ model: "shared/fallback", qualifier: "max" }],
129
119
  });
130
120
  expect(resolveHistorianModel({}, "pi")).toEqual({ fallbacks: [] });
131
121
  });
@@ -1,4 +1,4 @@
1
- export type ModelHarness = "opencode" | "pi";
1
+ export type ModelHarness = "opencode" | "pi" | "omp";
2
2
 
3
3
  export interface ResolvedModelEntry {
4
4
  /** Canonical provider/model reference used for identity and model selection. */
@@ -23,13 +23,6 @@ function readString(value: unknown): string | undefined {
23
23
  return trimmed.length > 0 ? trimmed : undefined;
24
24
  }
25
25
 
26
- function resolveHarnessBlock(
27
- container: ConfigRecord | undefined,
28
- harness: ModelHarness,
29
- ): ConfigRecord | undefined {
30
- return asRecord(container?.[harness]);
31
- }
32
-
33
26
  /**
34
27
  * Collapses the string and object spellings of a harness model entry into the
35
28
  * same model identity. Qualifiers stay separate because they alter an attempt,
@@ -101,29 +94,31 @@ export interface ResolvedHistorianModel {
101
94
  }
102
95
 
103
96
  /**
104
- * Resolve historian model attempts from the selected harness block.
97
+ * Resolve historian model attempts from the flat historian config. The harness
98
+ * parameter selects which qualifier is read: OpenCode uses `variant`, Pi uses
99
+ * `thinking_level`. The other harness's qualifier is ignored (no cross-harness
100
+ * mapping).
105
101
  */
106
102
  export function resolveHistorianModel(
107
103
  config: unknown,
108
104
  harness: ModelHarness,
109
105
  ): ResolvedHistorianModel {
110
106
  const historian = asRecord(asRecord(config)?.historian);
111
- const block = resolveHarnessBlock(historian, harness);
112
- if (!block) return { fallbacks: [] };
107
+ if (!historian) return { fallbacks: [] };
113
108
 
114
109
  return {
115
110
  primary: resolvePrimaryEntry({
116
- entry: block.model,
117
- defaultQualifier: block[harness === "opencode" ? "variant" : "thinking_level"],
111
+ entry: historian.model,
112
+ defaultQualifier: historian[harness === "opencode" ? "variant" : "thinking_level"],
118
113
  harness,
119
114
  }),
120
- fallbacks: resolveFallbackEntries(block.fallback_models, harness),
115
+ fallbacks: resolveFallbackEntries(historian.fallback_models, harness),
121
116
  };
122
117
  }
123
118
 
124
119
  /**
125
120
  * Keep non-model agent settings at their original level while replacing only
126
- * OpenCode's model-resolution fields with the executing harness's primary.
121
+ * OpenCode's model-resolution fields with the flat primary entry.
127
122
  */
128
123
  export function resolveOpenCodeAgentOverrides(agent: unknown): Record<string, unknown> {
129
124
  const source = asRecord(agent);
@@ -133,14 +128,14 @@ export function resolveOpenCodeAgentOverrides(agent: unknown): Record<string, un
133
128
  fallback_models: _fallbackModels,
134
129
  variant: _variant,
135
130
  thinking_level: _thinking,
136
- opencode,
137
- pi,
131
+ opencode: _opencode,
132
+ pi: _pi,
138
133
  tasks,
139
134
  ...rest
140
135
  } = source;
141
136
  const primary = resolvePrimaryEntry({
142
- entry: asRecord(opencode)?.model,
143
- defaultQualifier: asRecord(opencode)?.variant,
137
+ entry: source.model,
138
+ defaultQualifier: source.variant,
144
139
  harness: "opencode",
145
140
  });
146
141
  return {
@@ -1,5 +1,4 @@
1
1
  import type { createOpencodeClient } from "@opencode-ai/sdk";
2
-
3
2
  import { detectOverflow } from "../features/magic-context/overflow-detection";
4
3
  import { HiddenCompletionRefusal } from "../hooks/magic-context/compartment-runner-types";
5
4
  import { HiddenAgentStepLimit } from "../v2/hooks/hidden-child";
@@ -11,6 +10,12 @@ import {
11
10
  } from "./assistant-message-extractor";
12
11
  import { log } from "./logger";
13
12
  import type { ModelInput } from "./model-resolution";
13
+ import {
14
+ ProviderOutputFailureError,
15
+ primaryQuotaFailure,
16
+ providerOutputFailureFromInvalidManifest,
17
+ rememberPrimaryQuota,
18
+ } from "./provider-output-failure";
14
19
  import { sanitizeDiagnosticText } from "./redaction";
15
20
  import { parseProviderModel, toModelEntry } from "./resolve-fallbacks";
16
21
 
@@ -59,6 +64,8 @@ export interface PromptAttemptInfo {
59
64
  totalAttempts: number;
60
65
  /** Explicit model override for this attempt, when one was supplied. */
61
66
  model?: { providerID: string; modelID: string };
67
+ /** A run-local quota deadline prevented dispatch; not an actual model call. */
68
+ skipped?: boolean;
62
69
  }
63
70
 
64
71
  export interface PromptRetryOptions {
@@ -433,14 +440,25 @@ function throwWithPromptFailure(
433
440
  legacyError instanceof Error ? legacyError : new Error(extractMessage(legacyError));
434
441
  const last = failedAttempts.at(-1);
435
442
  const failureClass = last?.failureClass ?? "unknown";
443
+ const quota = failedAttempts.find(
444
+ (failure) =>
445
+ failure.error instanceof ProviderOutputFailureError &&
446
+ failure.error.quotaResetAt !== undefined,
447
+ )?.error as ProviderOutputFailureError | undefined;
448
+ if (quota && error !== quota && !error.message.includes(quota.message))
449
+ error.message = `${quota.message}; ${error.message}`;
436
450
  const providerError =
437
- failureClass === "provider_error"
438
- ? sanitizeDiagnosticText(shortErr(last?.error ?? legacyError)).slice(0, 500)
451
+ failureClass === "provider_error" || quota
452
+ ? sanitizeDiagnosticText(
453
+ `${quota ? `${quota.message}; ` : ""}${shortErr(last?.error ?? legacyError)}`,
454
+ ).slice(0, 500)
439
455
  : null;
440
456
  promptFailureDetails.set(error, {
441
457
  failureClass,
442
458
  modelAttempted: last?.attempt.label ?? null,
443
- modelsTried: failedAttempts.map((failure) => failure.attempt.label),
459
+ modelsTried: failedAttempts
460
+ .filter((failure) => !failure.attempt.skipped)
461
+ .map((failure) => failure.attempt.label),
444
462
  providerError,
445
463
  timeoutMs: failureClass === "provider_timeout" ? timeoutMs : null,
446
464
  childSessionId: transport ? (transport.childSessionId ?? null) : args.path.id || null,
@@ -664,6 +682,16 @@ async function attemptAndValidate<TOutput, TValidated>(
664
682
  }
665
683
 
666
684
  try {
685
+ // Only the structured account-pool notice bypasses caller validation.
686
+ // The near-zero-token heuristic remains owned by manifest callers.
687
+ const messages = Array.isArray(output)
688
+ ? output
689
+ : (output as { messages?: unknown } | null)?.messages;
690
+ const text = extractLatestAssistantText(messages);
691
+ if (text) {
692
+ const failure = providerOutputFailureFromInvalidManifest(messages, text);
693
+ if (failure?.quotaResetAt !== undefined) throw failure;
694
+ }
667
695
  if (!extractLatestAssistantText(output)) {
668
696
  const assistantFailure = extractLatestAssistantFailure(output);
669
697
  if (assistantFailure) {
@@ -718,8 +746,22 @@ export async function promptSyncWithValidatedOutputRetry<TOutput, TValidated = T
718
746
  const failedAttempts: FailedAttempt[] = [];
719
747
  let firstError: unknown = null;
720
748
  let lastError: unknown = null;
749
+ const cooledPrimary = primaryQuotaFailure(explicitPrimaryLabel);
721
750
 
722
751
  try {
752
+ if (cooledPrimary)
753
+ throw {
754
+ error: cooledPrimary,
755
+ failureClass: "provider_error",
756
+ attempt: {
757
+ label: explicitPrimaryLabel,
758
+ attemptIndex: 0,
759
+ isFallback: false,
760
+ totalAttempts,
761
+ model: baseBody.model,
762
+ skipped: true,
763
+ },
764
+ } satisfies FailedAttempt;
723
765
  return await attemptAndValidate(
724
766
  client,
725
767
  baseArgs,
@@ -739,6 +781,19 @@ export async function promptSyncWithValidatedOutputRetry<TOutput, TValidated = T
739
781
  const failure = caught as FailedAttempt;
740
782
  failedAttempts.push(failure);
741
783
  firstError = failure.error;
784
+ if (
785
+ failure.error instanceof ProviderOutputFailureError &&
786
+ failure.error.quotaResetAt !== undefined
787
+ )
788
+ failure.error.message = failure.error.message.replace(
789
+ /^provider quota exhausted/,
790
+ "primary quota exhausted",
791
+ );
792
+ const quota = !cooledPrimary && rememberPrimaryQuota(explicitPrimaryLabel, failure.error);
793
+ if (quota)
794
+ log(
795
+ `[${callContext}] ${quota.message}; skipping ${explicitPrimaryLabel} for this run until reset`,
796
+ );
742
797
  lastError = failure.error;
743
798
  if (isNonRetryable(failure.error, options.signal)) {
744
799
  throwWithPromptFailure(
@@ -760,9 +815,10 @@ export async function promptSyncWithValidatedOutputRetry<TOutput, TValidated = T
760
815
  );
761
816
  }
762
817
 
763
- log(
764
- `[${callContext}] primary (${explicitPrimaryLabel}) failed validation/prompt: ${shortErr(failure.error)}; trying ${fallbacks.length} fallback(s)`,
765
- );
818
+ if (!cooledPrimary && !quota)
819
+ log(
820
+ `[${callContext}] primary (${explicitPrimaryLabel}) failed validation/prompt: ${shortErr(failure.error)}; trying ${fallbacks.length} fallback(s)`,
821
+ );
766
822
  }
767
823
 
768
824
  for (let i = 0; i < fallbacks.length; i += 1) {
@@ -0,0 +1,25 @@
1
+ /** Default result keep counts shared by runtime and configuration. */
2
+ export const DEFAULT_PROTECTED_TOOLS: Readonly<Record<string, number>> = {
3
+ todowrite: 1,
4
+ // The fork's tool name; upstream's ctx_reduce alias merges onto the same key.
5
+ mmc_reduce: 3,
6
+ };
7
+
8
+ export function normalizeProtectedToolName(name: string | null): string {
9
+ const lowered = (name ?? "").toLowerCase().replace(/^mcp_/, "");
10
+ return lowered === "ctx_reduce" ? "mmc_reduce" : lowered;
11
+ }
12
+
13
+ /** Normalize before merging tiers so aliases cannot defeat a project override. */
14
+ export function mergeProtectedTools(
15
+ ...maps: (Readonly<Record<string, number>> | undefined)[]
16
+ ): Record<string, number> {
17
+ return Object.fromEntries(
18
+ [DEFAULT_PROTECTED_TOOLS, ...maps].flatMap((map) =>
19
+ Object.entries(map ?? {}).map(([name, count]) => [
20
+ normalizeProtectedToolName(name),
21
+ count,
22
+ ]),
23
+ ),
24
+ );
25
+ }