one-code-extension 0.4.0 → 0.5.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 (419) hide show
  1. package/README.md +219 -219
  2. package/agents/general-purpose.md +2 -2
  3. package/docs/guide/README.md +2 -1
  4. package/docs/guide/bring-your-claude-code-setup.md +60 -9
  5. package/docs/guide/configuration.md +48 -14
  6. package/docs/guide/differences-from-claude-code.md +32 -23
  7. package/docs/guide/doctor.md +20 -13
  8. package/docs/guide/hooks.md +18 -3
  9. package/docs/guide/installation.md +27 -4
  10. package/docs/guide/permissions-modes-and-auto-mode.md +225 -49
  11. package/docs/guide/providers-and-models.md +137 -54
  12. package/docs/guide/reference.md +27 -15
  13. package/docs/guide/sessions-and-context.md +69 -16
  14. package/docs/guide/skills-plugins-and-mcp.md +57 -20
  15. package/docs/guide/subagents-and-workflows.md +95 -29
  16. package/docs/guide/tasks-and-background-work.md +101 -26
  17. package/docs/guide/terminal-interface.md +24 -12
  18. package/docs/guide/tools.md +46 -5
  19. package/docs/guide/troubleshooting.md +13 -9
  20. package/docs/guide/windows.md +66 -34
  21. package/extensions/artifacts/gallery.ts +227 -0
  22. package/extensions/artifacts/index.ts +288 -0
  23. package/extensions/artifacts/store.ts +570 -0
  24. package/extensions/artifacts/viewer.ts +199 -0
  25. package/extensions/ask-user/description.ts +67 -0
  26. package/extensions/ask-user/dialogs.ts +80 -0
  27. package/extensions/ask-user/index.ts +37 -33
  28. package/extensions/ask-user/questions.ts +32 -7
  29. package/extensions/ask-user/widget.ts +45 -25
  30. package/extensions/auto-mode/actions.ts +6 -0
  31. package/extensions/auto-mode/cache.ts +88 -0
  32. package/extensions/auto-mode/classifier-prompt.ts +5 -4
  33. package/extensions/auto-mode/classifier.ts +290 -65
  34. package/extensions/auto-mode/config.ts +72 -20
  35. package/extensions/auto-mode/decision-log.ts +2 -0
  36. package/extensions/auto-mode/defaults.ts +1 -1
  37. package/extensions/auto-mode/denials.ts +123 -0
  38. package/extensions/auto-mode/git-checkout-programs.ts +187 -0
  39. package/extensions/auto-mode/git-status-meta.ts +159 -0
  40. package/extensions/auto-mode/history.ts +189 -0
  41. package/extensions/auto-mode/instructions.ts +58 -52
  42. package/extensions/auto-mode/model-select.ts +159 -134
  43. package/extensions/auto-mode/paths.ts +57 -5
  44. package/extensions/auto-mode/pause.ts +57 -2
  45. package/extensions/auto-mode/prompt.ts +37 -21
  46. package/extensions/auto-mode/read-only-options.ts +724 -0
  47. package/extensions/auto-mode/resolved-paths-meta.ts +133 -0
  48. package/extensions/auto-mode/rules.ts +18 -1
  49. package/extensions/auto-mode/safety-floor.ts +361 -18
  50. package/extensions/auto-mode/setup-run.ts +20 -14
  51. package/extensions/auto-mode/shell-analysis.ts +764 -490
  52. package/extensions/auto-mode/shell-parse.ts +885 -0
  53. package/extensions/auto-mode/system-note.ts +33 -0
  54. package/extensions/auto-mode/transcript.ts +80 -51
  55. package/extensions/background/cron.ts +515 -0
  56. package/extensions/background/index.ts +514 -236
  57. package/extensions/background/loop-fire.ts +136 -0
  58. package/extensions/background/loop-preamble.ts +59 -0
  59. package/extensions/background/loop-skill.ts +163 -0
  60. package/extensions/background/monitor-batch.ts +53 -0
  61. package/extensions/background/noop-fold.ts +128 -0
  62. package/extensions/background/switch-guard.ts +52 -0
  63. package/extensions/background/wakeup.ts +159 -55
  64. package/extensions/bash/background.ts +53 -17
  65. package/extensions/bash/child.ts +7 -0
  66. package/extensions/bash/description.ts +188 -0
  67. package/extensions/bash/guards.ts +15 -67
  68. package/extensions/bash/index.ts +88 -35
  69. package/extensions/branding/compaction-keep.ts +63 -0
  70. package/extensions/branding/declined.ts +22 -0
  71. package/extensions/branding/index.ts +133 -9
  72. package/extensions/branding/prompt-editor.ts +53 -6
  73. package/extensions/branding/queued-edit.ts +31 -0
  74. package/extensions/branding/replaced-builtins.ts +68 -0
  75. package/extensions/branding/startup.ts +33 -71
  76. package/extensions/btw/index.ts +229 -71
  77. package/extensions/btw/panel.ts +228 -100
  78. package/extensions/btw/prompt.ts +41 -4
  79. package/extensions/claude-compat/index.ts +7 -11
  80. package/extensions/claude-context/index.ts +446 -58
  81. package/extensions/compaction/cut.ts +157 -0
  82. package/extensions/compaction/index.ts +216 -75
  83. package/extensions/compaction/kept-thinking.ts +62 -0
  84. package/extensions/compaction/prompt.ts +15 -12
  85. package/extensions/context-budget/index.ts +24 -5
  86. package/extensions/context-management/index.ts +25 -0
  87. package/extensions/doctor/build.ts +59 -8
  88. package/extensions/doctor/builtins.ts +84 -0
  89. package/extensions/doctor/cli.ts +7 -1
  90. package/extensions/doctor/compat.ts +88 -23
  91. package/extensions/doctor/dependencies.ts +41 -5
  92. package/extensions/doctor/fix-prompt.ts +9 -2
  93. package/extensions/doctor/index.ts +102 -58
  94. package/extensions/doctor/models.ts +102 -63
  95. package/extensions/doctor/presets.ts +42 -27
  96. package/extensions/doctor/report.ts +19 -2
  97. package/extensions/doctor/viewer.ts +49 -27
  98. package/extensions/effort/index.ts +72 -19
  99. package/extensions/effort/settings.ts +21 -0
  100. package/extensions/effort/slider.ts +24 -20
  101. package/extensions/exit/index.ts +32 -1
  102. package/extensions/file-tracker/index.ts +337 -26
  103. package/extensions/file-tracker/replay.ts +96 -10
  104. package/extensions/file-tracker/restore.ts +63 -0
  105. package/extensions/file-tracker/results.ts +66 -0
  106. package/extensions/file-tracker/shell-reads.ts +211 -0
  107. package/extensions/file-tracker/tracker.ts +32 -15
  108. package/extensions/footer/footer-line.ts +45 -24
  109. package/extensions/footer/index.ts +47 -2
  110. package/extensions/footer/pr.ts +34 -5
  111. package/extensions/hooks/compaction.ts +7 -0
  112. package/extensions/hooks/executor.ts +32 -30
  113. package/extensions/hooks/index.ts +198 -31
  114. package/extensions/hooks/matcher.ts +3 -0
  115. package/extensions/hooks/protocol.ts +41 -3
  116. package/extensions/hooks/settings.ts +50 -19
  117. package/extensions/hooks/subagent-bridge.ts +2 -0
  118. package/extensions/hooks/trust.ts +13 -5
  119. package/extensions/idle-compact/index.ts +119 -0
  120. package/extensions/idle-compact/policy.ts +143 -0
  121. package/extensions/init/index.ts +11 -10
  122. package/extensions/init/prompt.ts +25 -32
  123. package/extensions/interrupted/index.ts +13 -1
  124. package/extensions/kept-thinking/index.ts +27 -0
  125. package/extensions/lib/agent-cron.ts +130 -0
  126. package/extensions/lib/agent-loader.ts +15 -1
  127. package/extensions/lib/agent-view.ts +85 -0
  128. package/extensions/lib/anthropic-payload.ts +82 -1
  129. package/extensions/lib/anthropic-server-call.ts +230 -0
  130. package/extensions/lib/anthropic-server-tools.ts +485 -0
  131. package/extensions/lib/app-launch.mjs +94 -0
  132. package/extensions/lib/argument-hints.ts +190 -0
  133. package/extensions/lib/atomic-write.ts +6 -2
  134. package/extensions/lib/auto-memory.ts +75 -0
  135. package/extensions/lib/bash-parser.ts +84 -0
  136. package/extensions/lib/btw-fork.ts +93 -0
  137. package/extensions/lib/child-extensions.ts +42 -0
  138. package/extensions/lib/child-writes.ts +45 -0
  139. package/extensions/lib/claude-context.ts +515 -100
  140. package/extensions/lib/claude-external-includes.ts +68 -0
  141. package/extensions/lib/claude-id.ts +22 -0
  142. package/extensions/lib/claude-rules.ts +323 -0
  143. package/extensions/lib/claude-settings.ts +76 -4
  144. package/extensions/lib/compaction-boundary.ts +36 -0
  145. package/extensions/lib/compaction-keep.mjs +83 -0
  146. package/extensions/lib/config-mode.ts +112 -0
  147. package/extensions/lib/consent-dialogs.ts +63 -0
  148. package/extensions/lib/consent-preview.ts +5 -1
  149. package/extensions/lib/consent-stores.ts +48 -0
  150. package/extensions/lib/context-facts.ts +78 -0
  151. package/extensions/lib/context-stack.ts +81 -0
  152. package/extensions/lib/deferred.ts +219 -25
  153. package/extensions/lib/dock-rows.ts +60 -0
  154. package/extensions/lib/empty-tool-result.ts +27 -0
  155. package/extensions/lib/environment-block.ts +76 -0
  156. package/extensions/lib/fetch-timeout.ts +24 -6
  157. package/extensions/{system-prompt → lib}/git-status.ts +51 -13
  158. package/extensions/lib/git.ts +75 -13
  159. package/extensions/lib/grammars/LICENSE-tree-sitter-bash +21 -0
  160. package/extensions/lib/grammars/README.md +2 -0
  161. package/extensions/lib/grammars/tree-sitter-bash.wasm +0 -0
  162. package/extensions/lib/guide-docs.ts +45 -0
  163. package/extensions/lib/headless-output.ts +46 -0
  164. package/extensions/lib/hook-gate.ts +7 -7
  165. package/extensions/lib/interrupt.ts +20 -4
  166. package/extensions/lib/key-input.ts +455 -0
  167. package/extensions/lib/keys.ts +14 -1
  168. package/extensions/lib/local-command.ts +47 -13
  169. package/extensions/lib/mcp-overrides.ts +1 -1
  170. package/extensions/lib/mcp-share.ts +46 -0
  171. package/extensions/lib/memory-content.ts +146 -0
  172. package/extensions/lib/memory-index.ts +18 -0
  173. package/extensions/lib/memory.ts +134 -123
  174. package/extensions/lib/model-catalog/huggingface.json +174 -0
  175. package/extensions/lib/model-catalog/models-dev.json +18297 -0
  176. package/extensions/lib/model-catalog/openrouter.json +5252 -0
  177. package/extensions/lib/model-catalog-data.ts +513 -0
  178. package/extensions/lib/model-catalog.ts +462 -0
  179. package/extensions/lib/model-choice-warnings.ts +137 -0
  180. package/extensions/lib/model-pairings.ts +152 -0
  181. package/extensions/{auto-mode → lib}/model-picker.ts +26 -43
  182. package/extensions/lib/model-policy.ts +112 -15
  183. package/extensions/lib/model-tier.ts +283 -355
  184. package/extensions/lib/model-unusable.ts +1 -1
  185. package/extensions/lib/newer-model.ts +100 -0
  186. package/extensions/lib/notices.ts +87 -0
  187. package/extensions/lib/notifications.ts +308 -64
  188. package/extensions/lib/one-code-settings.ts +74 -8
  189. package/extensions/lib/open-browser.ts +63 -0
  190. package/extensions/lib/openrouter-generation.ts +94 -0
  191. package/extensions/lib/operation-deadline.ts +30 -0
  192. package/extensions/lib/original-command.ts +4 -4
  193. package/extensions/lib/package-version.ts +1 -1
  194. package/extensions/lib/panel-focus.ts +134 -0
  195. package/extensions/lib/paths.ts +17 -2
  196. package/extensions/lib/permission-gate.ts +72 -6
  197. package/extensions/lib/persisted-output.ts +60 -8
  198. package/extensions/lib/pi-ai-estimate.ts +19 -3
  199. package/extensions/lib/pi-version.ts +21 -2
  200. package/extensions/lib/plan-mode-channels.ts +20 -5
  201. package/extensions/lib/plugins.ts +26 -17
  202. package/extensions/lib/powershell-parse-script.ts +208 -0
  203. package/extensions/lib/powershell-parser.ts +344 -0
  204. package/extensions/lib/process-tree.ts +131 -8
  205. package/extensions/lib/prompt-options.ts +82 -0
  206. package/extensions/lib/queued-delivery.ts +44 -0
  207. package/extensions/lib/reminders.ts +364 -58
  208. package/extensions/lib/replaced-builtins.mjs +199 -0
  209. package/extensions/lib/replay-call.ts +70 -0
  210. package/extensions/lib/request-replay.ts +439 -0
  211. package/extensions/lib/scratchpad.ts +81 -14
  212. package/extensions/lib/send-now-channel.ts +7 -0
  213. package/extensions/lib/session-model-tier.ts +51 -0
  214. package/extensions/lib/session-work.ts +64 -0
  215. package/extensions/lib/settings-channels.ts +4 -4
  216. package/extensions/lib/shell-exit-semantics.ts +170 -0
  217. package/extensions/lib/shell-quote.ts +11 -0
  218. package/extensions/lib/shell-result.ts +70 -0
  219. package/extensions/lib/shell-spawn.ts +70 -6
  220. package/extensions/lib/shell-tool.ts +75 -25
  221. package/extensions/lib/side-call-cache.ts +124 -0
  222. package/extensions/lib/side-call-run.ts +96 -0
  223. package/extensions/lib/side-call-usage.ts +38 -0
  224. package/extensions/lib/side-call.ts +13 -1
  225. package/extensions/lib/single-flight.ts +18 -0
  226. package/extensions/lib/skill-body.ts +42 -0
  227. package/extensions/lib/skill-scan.ts +63 -24
  228. package/extensions/lib/spill-file.ts +140 -0
  229. package/extensions/lib/subagent-handback.ts +113 -0
  230. package/extensions/lib/system-role.ts +156 -0
  231. package/extensions/lib/terminal-text.ts +114 -0
  232. package/extensions/lib/text-width.ts +47 -16
  233. package/extensions/lib/timer-ops.ts +20 -0
  234. package/extensions/lib/tool-additions.ts +149 -0
  235. package/extensions/lib/tool-call-corruption.ts +134 -0
  236. package/extensions/lib/tool-path.ts +66 -0
  237. package/extensions/lib/tool-variants.ts +69 -0
  238. package/extensions/lib/tui-render.ts +255 -27
  239. package/extensions/lib/turn-budget-layout.ts +143 -0
  240. package/extensions/lib/update-check.mjs +15 -2
  241. package/extensions/lib/usage-bus.ts +1 -1
  242. package/extensions/lib/user-denial.ts +38 -0
  243. package/extensions/lib/workspace-channel.ts +16 -0
  244. package/extensions/lib/worktree-channel.ts +31 -3
  245. package/extensions/lib/worktree-isolation.ts +40 -10
  246. package/extensions/lsp/client.ts +8 -9
  247. package/extensions/lsp/index.ts +70 -4
  248. package/extensions/lsp/keep-alive.ts +1 -1
  249. package/extensions/lsp/trust.ts +119 -0
  250. package/extensions/lsp/watcher.ts +1 -1
  251. package/extensions/mcp/announce.ts +87 -5
  252. package/extensions/mcp/client.ts +161 -48
  253. package/extensions/mcp/config.ts +132 -8
  254. package/extensions/mcp/discovery.ts +19 -0
  255. package/extensions/mcp/index.ts +209 -71
  256. package/extensions/mcp/oauth/browser.ts +38 -17
  257. package/extensions/mcp/oauth/flow.ts +25 -17
  258. package/extensions/mcp/oauth/provider.ts +14 -8
  259. package/extensions/mcp/oauth/store.ts +34 -16
  260. package/extensions/mcp/panel/keys.ts +12 -11
  261. package/extensions/mcp/panel/render.ts +9 -6
  262. package/extensions/mcp/schema.ts +104 -5
  263. package/extensions/mcp/trust.ts +151 -67
  264. package/extensions/memory/entries.ts +23 -14
  265. package/extensions/memory/index.ts +134 -28
  266. package/extensions/memory/open-external.ts +33 -18
  267. package/extensions/memory/panel.ts +135 -26
  268. package/extensions/newer-model/index.ts +31 -0
  269. package/extensions/notebook/description.ts +18 -0
  270. package/extensions/notebook/index.ts +6 -5
  271. package/extensions/notebook/notebook.ts +1 -1
  272. package/extensions/permissions/index.ts +1300 -381
  273. package/extensions/permissions/matcher.ts +425 -74
  274. package/extensions/permissions/modes.ts +6 -4
  275. package/extensions/permissions/panel/host.ts +130 -0
  276. package/extensions/permissions/panel/keys.ts +48 -0
  277. package/extensions/permissions/panel/render.ts +450 -0
  278. package/extensions/permissions/panel/state.ts +550 -0
  279. package/extensions/permissions/powershell-cmdlets.ts +198 -0
  280. package/extensions/permissions/powershell-paths.ts +197 -0
  281. package/extensions/permissions/powershell-rules.ts +207 -280
  282. package/extensions/permissions/powershell-tree.ts +557 -0
  283. package/extensions/permissions/project-trust.ts +27 -11
  284. package/extensions/permissions/protected-paths.ts +14 -2
  285. package/extensions/permissions/session-grant.ts +6 -0
  286. package/extensions/permissions/settings.ts +166 -31
  287. package/extensions/permissions/subagent-gate.ts +25 -0
  288. package/extensions/permissions/workspace.ts +78 -0
  289. package/extensions/plan-mode/index.ts +156 -69
  290. package/extensions/plan-mode/reminder.ts +89 -21
  291. package/extensions/plan-mode/texts.ts +177 -0
  292. package/extensions/plan-mode/viewer.ts +92 -27
  293. package/extensions/plugins/counts.ts +3 -3
  294. package/extensions/plugins/index.ts +61 -46
  295. package/extensions/plugins/install/install.ts +16 -0
  296. package/extensions/plugins/marketplace/sync.ts +9 -3
  297. package/extensions/plugins/marketplace/types.ts +3 -1
  298. package/extensions/plugins/panel/keys.ts +21 -24
  299. package/extensions/plugins/panel/render.ts +12 -6
  300. package/extensions/plugins/shell-expand.ts +107 -0
  301. package/extensions/plugins/template.ts +32 -15
  302. package/extensions/powershell/child.ts +7 -0
  303. package/extensions/powershell/description.ts +38 -6
  304. package/extensions/powershell/guards.ts +1 -1
  305. package/extensions/powershell/index.ts +12 -7
  306. package/extensions/recap/index.ts +65 -60
  307. package/extensions/recap/prompt.ts +2 -2
  308. package/extensions/recap/scheduler.ts +3 -5
  309. package/extensions/search-tools/grep-merge.ts +39 -0
  310. package/extensions/search-tools/index.ts +0 -0
  311. package/extensions/send-now/chord.ts +62 -0
  312. package/extensions/send-now/index.ts +133 -0
  313. package/extensions/session-title/index.ts +3 -0
  314. package/extensions/session-title/title.ts +4 -1
  315. package/extensions/skill/code-review.ts +97 -0
  316. package/extensions/skill/index.ts +421 -101
  317. package/extensions/skill/invoke.ts +128 -18
  318. package/extensions/skill/listing.ts +133 -9
  319. package/extensions/skill/panel/keys.ts +15 -16
  320. package/extensions/skill/panel/render.ts +5 -2
  321. package/extensions/skill/panel/state.ts +12 -1
  322. package/extensions/subagents/agent-args.ts +39 -0
  323. package/extensions/subagents/agent-description.ts +132 -0
  324. package/extensions/subagents/agents.ts +116 -23
  325. package/extensions/subagents/announce.ts +69 -0
  326. package/extensions/subagents/default-model.ts +2 -3
  327. package/extensions/subagents/delegation-steer.ts +4 -4
  328. package/extensions/subagents/fork-cache.ts +76 -0
  329. package/extensions/subagents/guide-agent.ts +189 -0
  330. package/extensions/subagents/hand-back-review.ts +3 -0
  331. package/extensions/subagents/index.ts +1493 -824
  332. package/extensions/subagents/live-runs.ts +26 -6
  333. package/extensions/subagents/model-select.ts +38 -25
  334. package/extensions/subagents/outcome.ts +2 -2
  335. package/extensions/subagents/panel-keys.ts +73 -31
  336. package/extensions/subagents/panel-render.ts +120 -96
  337. package/extensions/subagents/panel-widget.ts +60 -89
  338. package/extensions/subagents/runner.ts +108 -80
  339. package/extensions/subagents/runs.ts +39 -5
  340. package/extensions/subagents/send-to-main-tool.ts +1 -1
  341. package/extensions/subagents/session-turns.ts +32 -8
  342. package/extensions/subagents/shell-panel.ts +36 -178
  343. package/extensions/subagents/tasks-dialog.ts +170 -0
  344. package/extensions/subagents/viewport-mirror.ts +95 -0
  345. package/extensions/subagents/worktree.ts +57 -13
  346. package/extensions/system-prompt/environment.ts +19 -19
  347. package/extensions/system-prompt/idle-turn.ts +65 -0
  348. package/extensions/system-prompt/index.ts +131 -47
  349. package/extensions/system-prompt/template.ts +24 -50
  350. package/extensions/system-prompt/tiers/common.ts +23 -64
  351. package/extensions/system-prompt/tiers/frontier.ts +32 -11
  352. package/extensions/system-prompt/tiers/low.ts +67 -45
  353. package/extensions/system-prompt/tiers/mid.ts +96 -38
  354. package/extensions/system-reminder/index.ts +175 -13
  355. package/extensions/tasks/descriptions.ts +190 -0
  356. package/extensions/tasks/index.ts +120 -41
  357. package/extensions/tasks/store.ts +83 -16
  358. package/extensions/tool-call-corruption/child.ts +7 -0
  359. package/extensions/tool-call-corruption/index.ts +237 -0
  360. package/extensions/tool-search/announce.ts +16 -0
  361. package/extensions/tool-search/index.ts +189 -41
  362. package/extensions/tool-style/index.ts +68 -10
  363. package/extensions/tool-style/write-description.ts +40 -0
  364. package/extensions/tool-style/write-render.ts +237 -0
  365. package/extensions/turn-duration/index.ts +7 -3
  366. package/extensions/turn-duration/line.ts +47 -4
  367. package/extensions/web/backends.ts +6 -9
  368. package/extensions/web/description.ts +50 -0
  369. package/extensions/web/index.ts +118 -53
  370. package/extensions/web/native-call.ts +15 -0
  371. package/extensions/web/native-results.ts +41 -0
  372. package/extensions/web-fetch/description.ts +42 -0
  373. package/extensions/web-fetch/extract.ts +53 -6
  374. package/extensions/web-fetch/index.ts +125 -76
  375. package/extensions/web-fetch/page-content.ts +19 -0
  376. package/extensions/web-fetch/response.ts +49 -0
  377. package/extensions/web-fetch/summarize.ts +9 -10
  378. package/extensions/workflow/agent-session.ts +105 -18
  379. package/extensions/workflow/args.ts +24 -0
  380. package/extensions/workflow/description.ts +67 -0
  381. package/extensions/workflow/globals.ts +17 -3
  382. package/extensions/workflow/index.ts +145 -162
  383. package/extensions/workflow/records.ts +1 -0
  384. package/extensions/workflow/run-manager.ts +29 -6
  385. package/extensions/workflow/saved-workflows.ts +3 -2
  386. package/extensions/workflow/types.ts +22 -1
  387. package/extensions/workflow/viewer.ts +54 -24
  388. package/extensions/workflow/widget.ts +20 -0
  389. package/extensions/worktree/descriptions.ts +91 -0
  390. package/extensions/worktree/guards.ts +316 -54
  391. package/extensions/worktree/index.ts +121 -39
  392. package/extensions/worktree/powershell-guards.ts +167 -0
  393. package/extensions/worktree/rewrite.ts +14 -16
  394. package/package.json +19 -7
  395. package/skills/artifact-design/SKILL.md +85 -0
  396. package/skills/code-review/SKILL.md +20 -20
  397. package/skills/code-review/cells/arguments-and-flags.md +19 -0
  398. package/skills/code-review/cells/high-3x5-recall-verify.md +137 -0
  399. package/skills/code-review/cells/high-8-inline.md +122 -0
  400. package/skills/code-review/cells/low-1-pass-max4.md +27 -0
  401. package/skills/code-review/cells/low-1-pass-min-files-4.md +28 -0
  402. package/skills/code-review/cells/max-5x5-verify-sweep.md +162 -0
  403. package/skills/code-review/cells/medium-3x5-verify.md +132 -0
  404. package/skills/code-review/cells/minimal-prompt.md +32 -0
  405. package/skills/code-review/cells/xhigh-10-inline-sweep.md +149 -0
  406. package/skills/code-review/cells/xhigh-5x5-verify-sweep.md +162 -0
  407. package/skills/fewer-permission-prompts/SKILL.md +1 -1
  408. package/skills/loop/SKILL.md +11 -0
  409. package/skills/security-review/SKILL.md +2 -5
  410. package/skills/simplify/SKILL.md +7 -5
  411. package/skills/workflow-authoring/SKILL.md +143 -0
  412. package/themes/onecode-light.json +1 -1
  413. package/themes/onecode.json +1 -1
  414. package/types/app.d.ts +58 -0
  415. package/types/community-packages.d.ts +26 -1
  416. package/extensions/auto-mode/recoverability.ts +0 -229
  417. package/extensions/lib/capability-index.ts +0 -466
  418. package/extensions/lib/model-facts.generated.json +0 -2675
  419. package/extensions/lib/model-facts.ts +0 -162
@@ -0,0 +1,152 @@
1
+ /**
2
+ * The model pairing table (pure): for the models people run most, which model
3
+ * a session hands its subagents and its auto-mode classifier. Each row comes
4
+ * from measured data, not from tiers or price alone: Artificial Analysis's
5
+ * Intelligence Index (maximum effort for subagents; low effort for the
6
+ * classifier, which runs with thinking off) and list prices, with OpenRouter's
7
+ * usage rankings choosing which models get a row. The snapshot and the numbers
8
+ * behind every row are in `working-docs/decisions/model-policy.md` ("Model
9
+ * pairing table").
10
+ *
11
+ * A pick is the cheapest same-vendor model within 15 points at maximum effort
12
+ * (subagent) or 7 points at low effort (classifier); `"session"` keeps the
13
+ * session's own model, which also overrides the cost algorithm. A model the
14
+ * table does not name goes to the cost algorithm (`model-tier.ts`
15
+ * `sameTierContainedCandidates`).
16
+ *
17
+ * The table knows only what existed on PAIRINGS_AS_OF. When the session's
18
+ * provider serves a model of the session's vendor released after that date,
19
+ * the table is skipped for that vendor: a new model may change every pairing,
20
+ * and the algorithm judges it where the table cannot.
21
+ */
22
+
23
+ import type { Api, Model } from "@earendil-works/pi-ai";
24
+ import { catalogModelFor } from "./model-catalog.ts";
25
+ import { isAliasOrVariantId, isDatedDuplicate, modelsContainedToSession, pricedInput, supportsImageInput, vendorProfile } from "./model-policy.ts";
26
+ import { isAutoSelectable, type ModelPrice, servedCatalogIds } from "./model-tier.ts";
27
+
28
+ export type PairingRole = "subagent" | "classifier";
29
+
30
+ interface PairingRow {
31
+ /** Session models, as catalog ids, all of one vendor. */
32
+ sessions: readonly string[];
33
+ /** A catalog id (`vendor/id`), or SESSION for the session's own model. */
34
+ subagent: string;
35
+ classifier: string;
36
+ }
37
+
38
+ /** The data date: Artificial Analysis and OpenRouter rankings, 2026-10-13. */
39
+ export const PAIRINGS_AS_OF = "2026-10-13";
40
+
41
+ const SESSION = "session";
42
+
43
+ export const PAIRING_TABLE: readonly PairingRow[] = [
44
+ // Anthropic. Sonnet 5.5 is within 2 points of Opus 5.5 at half the price and above Fable 5.1.
45
+ {
46
+ sessions: ["anthropic/claude-opus-5-5", "anthropic/claude-opus-5", "anthropic/claude-opus-4-8", "anthropic/claude-fable-5", "anthropic/claude-fable-5-1"],
47
+ subagent: "anthropic/claude-sonnet-5-5",
48
+ classifier: "anthropic/claude-sonnet-5-5",
49
+ },
50
+ { sessions: ["anthropic/claude-sonnet-5-5", "anthropic/claude-sonnet-5"], subagent: "anthropic/claude-haiku-5-5", classifier: "anthropic/claude-haiku-5-5" },
51
+ { sessions: ["anthropic/claude-haiku-5-5"], subagent: SESSION, classifier: SESSION },
52
+ // OpenAI. GPT-6.1 Sol is within a point of Astra at a fifth of the price; Luna drops 20 points at low effort.
53
+ { sessions: ["openai/gpt-6-astra"], subagent: "openai/gpt-6.1-sol", classifier: "openai/gpt-6.1-sol" },
54
+ { sessions: ["openai/gpt-6.1-sol", "openai/gpt-6-sol"], subagent: "openai/gpt-6-luna", classifier: SESSION },
55
+ { sessions: ["openai/gpt-5.6-sol"], subagent: "openai/gpt-6.1-sol", classifier: "openai/gpt-6.1-sol" },
56
+ { sessions: ["openai/gpt-5.6-terra", "openai/gpt-5.6-luna"], subagent: "openai/gpt-6-luna", classifier: "openai/gpt-6-luna" },
57
+ { sessions: ["openai/gpt-6-luna"], subagent: SESSION, classifier: SESSION },
58
+ // Google. Gemini 3.8 Flash outscores 3.1 Pro; Argon's classifier pick is the user's call (2026-10-13).
59
+ {
60
+ sessions: ["google/gemini-4-argon", "google/gemini-3.1-pro-preview", "google/gemini-3.7-flash", "google/gemini-3.6-flash", "google/gemini-3.5-flash"],
61
+ subagent: "google/gemini-3.8-flash",
62
+ classifier: "google/gemini-3.8-flash",
63
+ },
64
+ { sessions: ["google/gemini-3.8-flash"], subagent: SESSION, classifier: SESSION },
65
+ // DeepSeek. V4.1 Flash outscores V4 Pro at under a quarter of its price.
66
+ {
67
+ sessions: ["deepseek/deepseek-v4-pro", "deepseek/deepseek-v4-pro-0813", "deepseek/deepseek-v4-flash", "deepseek/deepseek-v4-flash-0731"],
68
+ subagent: "deepseek/deepseek-v4.1-flash",
69
+ classifier: "deepseek/deepseek-v4.1-flash",
70
+ },
71
+ { sessions: ["deepseek/deepseek-v4.1-flash"], subagent: SESSION, classifier: SESSION },
72
+ // Z.ai, Xiaomi, Qwen: each vendor's flash model.
73
+ { sessions: ["zhipuai/glm-5.3"], subagent: "zhipuai/glm-5.3-flash", classifier: "zhipuai/glm-5.3-flash" },
74
+ { sessions: ["zhipuai/glm-5.3-flash"], subagent: SESSION, classifier: SESSION },
75
+ { sessions: ["xiaomi/mimo-v2.6-pro"], subagent: "xiaomi/mimo-v2.6-flash", classifier: SESSION },
76
+ { sessions: ["xiaomi/mimo-v2.6-flash"], subagent: SESSION, classifier: SESSION },
77
+ { sessions: ["alibaba/qwen3.8-max", "alibaba/qwen3.8-max-0902"], subagent: "alibaba/qwen3.8-flash", classifier: "alibaba/qwen3.8-flash" },
78
+ { sessions: ["alibaba/qwen3.8-flash"], subagent: SESSION, classifier: SESSION },
79
+ // No same-vendor model within 15 points: the session model serves both roles.
80
+ { sessions: ["moonshotai/kimi-k3"], subagent: SESSION, classifier: SESSION },
81
+ { sessions: ["xai/grok-4.7"], subagent: SESSION, classifier: SESSION },
82
+ { sessions: ["meta/muse-spark-1.3"], subagent: SESSION, classifier: SESSION },
83
+ { sessions: ["minimax/MiniMax-M3"], subagent: SESSION, classifier: SESSION },
84
+ { sessions: ["stepfun/step-5-preview"], subagent: SESSION, classifier: SESSION },
85
+ { sessions: ["mistral/mistral-large-4"], subagent: SESSION, classifier: SESSION },
86
+ ];
87
+
88
+ const ROW_BY_SESSION: ReadonlyMap<string, PairingRow> = new Map(PAIRING_TABLE.flatMap((row) => row.sessions.map((id) => [id.toLowerCase(), row] as const)));
89
+ const AS_OF_TIME = Date.parse(`${PAIRINGS_AS_OF}T23:59:59Z`);
90
+
91
+ export type Pairing = { kind: "session" } | { kind: "model"; model: Model<Api> };
92
+
93
+ /**
94
+ * The table's pick for `role` on `sessionModel`, or undefined when the table
95
+ * does not decide: no row names the session model, the session's provider
96
+ * serves a model of its vendor released after PAIRINGS_AS_OF, or no model in
97
+ * the session's contained pool serves the pick, costs no more than the
98
+ * session model on that route, and passes the caller's gates
99
+ * (`requireImageInput`, and `accept`: the classifier's context window). The
100
+ * caller then runs the cost algorithm.
101
+ *
102
+ * Both checks read the pool the cost algorithm starts from (no dated
103
+ * duplicates, only auto-selectable models) but need no price: the table's
104
+ * picks are measured, so they hold on a route with no list prices. Where both
105
+ * are priced, a pick dearer than the session is skipped: a route can price an
106
+ * older model below its successor (OpenRouter's DeepSeek V4 Flash), and an
107
+ * automatic pick never costs more than the session (`price`, list input by
108
+ * default; the classifier passes its per-call price).
109
+ */
110
+ export function pairedModel(
111
+ role: PairingRole,
112
+ available: Model<Api>[],
113
+ sessionModel: Model<Api>,
114
+ opts: { contained?: Model<Api>[]; requireImageInput?: boolean; price?: ModelPrice; accept?: (model: Model<Api>) => boolean } = {},
115
+ ): Pairing | undefined {
116
+ const sessionEntry = catalogModelFor(sessionModel);
117
+ const row = sessionEntry && ROW_BY_SESSION.get(sessionEntry.id.toLowerCase());
118
+ if (!sessionEntry || !row) return undefined;
119
+ const pool = opts.contained ?? modelsContainedToSession(available, sessionModel);
120
+ const served = servedCatalogIds(pool);
121
+ const selectable = pool.filter((model) => isAutoSelectable(model, served));
122
+ // The table predates this vendor's newest model: it no longer speaks for the
123
+ // vendor. A new dated snapshot counts, even beside the undated alias.
124
+ const vendor = vendorProfile(sessionEntry.vendor);
125
+ const newer = selectable.some((model) => {
126
+ const entry = catalogModelFor(model);
127
+ return entry !== undefined && vendorProfile(entry.vendor) === vendor && entry.released !== undefined && entry.released > AS_OF_TIME;
128
+ });
129
+ if (newer) return undefined;
130
+ const usable = selectable.filter((model) => !isDatedDuplicate(model, pool));
131
+ const pick = row[role];
132
+ if (pick === SESSION) return { kind: "session" };
133
+ const wanted = pick.toLowerCase();
134
+ const accept = opts.accept ?? (() => true);
135
+ const priceOf = opts.price ?? pricedInput;
136
+ const sessionPrice = priceOf(sessionModel);
137
+ const notDearer = (model: Model<Api>) => {
138
+ const price = priceOf(model);
139
+ return sessionPrice === undefined || price === undefined || price <= sessionPrice;
140
+ };
141
+ const serving = usable
142
+ .filter(
143
+ (model) =>
144
+ catalogModelFor(model)?.id.toLowerCase() === wanted &&
145
+ (!opts.requireImageInput || supportsImageInput(model)) &&
146
+ notDearer(model) &&
147
+ accept(model),
148
+ )
149
+ // A plain row before a gateway variant (`…:batch`) of the same model.
150
+ .sort((a, b) => Number(isAliasOrVariantId(a.id)) - Number(isAliasOrVariantId(b.id)) || a.id.length - b.id.length);
151
+ return serving[0] ? { kind: "model", model: serving[0] } : undefined;
152
+ }
@@ -1,21 +1,12 @@
1
1
  /**
2
- * The `/auto-mode model` picker (pure).
2
+ * Shared interactive model picker (pure).
3
3
  *
4
- * Filtering, key decoding, selection movement, and rendering for the
5
- * interactive classifier-model picker, kept free of pi imports so they are
6
- * unit-testable. The thin `ctx.ui.custom` component in the permissions
7
- * extension owns nothing but mutable state and repaint calls.
8
- *
9
- * A picker exists because the alternative was worse in practice: choosing the
10
- * classifier automatically took three rounds of fault-fixing against real
11
- * catalogs, and when auto-selection picks badly the only recourse was
12
- * hand-editing settings.json with a model id the user has to guess. Naming a
13
- * model here is the explicit consent `autoMode.classifierModel` stands for —
14
- * including consent to another provider, which auto-selection must never
15
- * assume (see model-select.ts).
4
+ * Filtering, key decoding, selection movement, and rendering stay free of pi
5
+ * imports so command wiring can own only mutable state and repaint calls.
16
6
  */
17
7
 
18
- import { truncateLine } from "../lib/tui-render.ts";
8
+ import { truncateLine } from "./tui-render.ts";
9
+ import { keyId, keyText } from "./key-input.ts";
19
10
 
20
11
  export interface PickerEntry {
21
12
  provider: string;
@@ -68,26 +59,23 @@ export type PickerKey =
68
59
  * shortcuts: every printable character belongs to the filter query.
69
60
  */
70
61
  export function decodePickerKey(data: string): PickerKey | undefined {
71
- switch (data) {
72
- case "\x1b[A":
73
- case "\x1bOA":
62
+ switch (keyId(data)) {
63
+ case "up":
74
64
  return { kind: "up" };
75
- case "\x1b[B":
76
- case "\x1bOB":
65
+ case "down":
77
66
  return { kind: "down" };
78
- case "\r":
79
- case "\n":
67
+ case "enter":
80
68
  return { kind: "confirm" };
81
- case "\x1b":
82
- case "\x03": // ctrl+c — same intent as escape while the picker is focused
69
+ case "escape":
70
+ case "ctrl+c": // same intent as escape while the picker is focused
83
71
  return { kind: "cancel" };
84
- case "\x7f":
85
- case "\b":
72
+ case "backspace":
73
+ case "ctrl+backspace":
86
74
  return { kind: "backspace" };
87
75
  default: {
88
- // Printable text only (covers paste); control sequences are ignored.
89
- const text = [...data].filter((char) => char >= " " && char !== "\x7f").join("");
90
- return text.length > 0 && !data.startsWith("\x1b") ? { kind: "type", text } : undefined;
76
+ // Printable text only (covers paste); control sequences type nothing.
77
+ const text = keyText(data);
78
+ return text ? { kind: "type", text } : undefined;
91
79
  }
92
80
  }
93
81
  }
@@ -109,9 +97,9 @@ export interface PickerView {
109
97
  /** The currently configured spec, marked in the listing. */
110
98
  current?: string;
111
99
  maxVisible?: number;
112
- /** Picker header; defaults to the auto-mode classifier copy. */
113
- title?: string;
114
- subtitle?: string;
100
+ /** Picker header and explanatory subtitle. */
101
+ title: string;
102
+ subtitle: string;
115
103
  }
116
104
 
117
105
  const formatPrice = (price?: number): string => (price === undefined ? "" : `$${price}/M in`);
@@ -130,15 +118,13 @@ export function toPickerEntries(models: { provider: string; id: string; cost?: {
130
118
  export interface PickerComponentOptions {
131
119
  entries: PickerEntry[];
132
120
  current?: string;
133
- title?: string;
134
- subtitle?: string;
121
+ title: string;
122
+ subtitle: string;
135
123
  }
136
124
 
137
125
  /**
138
- * The `ctx.ui.custom` component behind `/auto-mode model` and `/subagent`:
139
- * filter-as-you-type over the entries, enter confirms, esc cancels. Kept here
140
- * (structurally typed, no pi imports) so both commands share one keyboard
141
- * loop instead of drifting copies.
126
+ * A `ctx.ui.custom` model picker: filter-as-you-type over the entries, enter
127
+ * confirms, esc cancels. Kept here with no pi imports.
142
128
  */
143
129
  export function modelPickerComponent(
144
130
  options: PickerComponentOptions,
@@ -191,7 +177,7 @@ export function modelPickerComponent(
191
177
  if (key.kind === "cancel") return done(null);
192
178
  if (key.kind === "confirm") return done(filtered[index] ?? null);
193
179
  if (key.kind === "up") index = Math.max(0, index - 1);
194
- else if (key.kind === "down") index = Math.min(filtered.length - 1, index + 1);
180
+ else if (key.kind === "down") index = Math.max(0, Math.min(filtered.length - 1, index + 1));
195
181
  else {
196
182
  query = key.kind === "backspace" ? query.slice(0, -1) : query + key.text;
197
183
  filtered = filterEntries(options.entries, query);
@@ -208,9 +194,6 @@ export function modelPickerComponent(
208
194
 
209
195
  /** Key hint, kept on its own line so it survives at narrow widths. */
210
196
  const PICKER_HINT = "type to filter · ↑/↓ · enter · esc";
211
- /** Default privacy note for the classifier picker (the key hint rides its own line below). */
212
- const CLASSIFIER_SUBTITLE = "It reads your prompts and CLAUDE.md — picking another provider sends them there.";
213
-
214
197
  export function renderModelPicker(view: PickerView, paint: Paint, width = Infinity): string[] {
215
198
  const maxVisible = view.maxVisible ?? 10;
216
199
  // Every line is cut to `width` — pi-tui crashes the whole app on a rendered
@@ -218,8 +201,8 @@ export function renderModelPicker(view: PickerView, paint: Paint, width = Infini
218
201
  // lines uncut (truncateLine returns them unchanged) for unit tests.
219
202
  const cut = (line: string): string => truncateLine(line, width);
220
203
  const lines: string[] = [];
221
- lines.push(cut(paint("accent", view.title ?? "Select the auto-mode classifier model")));
222
- lines.push(cut(paint("dim", view.subtitle ?? CLASSIFIER_SUBTITLE)));
204
+ lines.push(cut(paint("accent", view.title)));
205
+ lines.push(cut(paint("dim", view.subtitle)));
223
206
  // The key hint is always its own line, so a narrow terminal never clips the
224
207
  // only place the keys are documented.
225
208
  lines.push(cut(paint("dim", ` ${PICKER_HINT}`)));
@@ -18,7 +18,7 @@ export function isClaudeFamilyModel(model: { provider: string; id: string }): bo
18
18
  * subagent opens either. A model whose modality is unknown (undefined `input`, as
19
19
  * on some opaque/self-hosted providers, or a minimal test stub, or no model at all) reads as
20
20
  * text-only here — the safe direction for a gate that only ever ADDS a
21
- * requirement, never relaxes one. See `docs/decisions/model-policy.md`.
21
+ * requirement, never relaxes one. See `working-docs/decisions/model-policy.md`.
22
22
  */
23
23
  export function supportsImageInput(model: { input?: readonly string[] } | undefined): boolean {
24
24
  return Array.isArray(model?.input) && model.input.includes("image");
@@ -30,6 +30,8 @@ export interface ProviderPolicy {
30
30
  kind: ProviderPolicyKind;
31
31
  /** Fixed profile for direct vendors and stable hosted catalogs. */
32
32
  profile?: string;
33
+ /** The provider also serves other vendors' models: a row whose id names its vendor takes that vendor's profile. */
34
+ profileFromId?: boolean;
33
35
  }
34
36
 
35
37
  const direct = (profile: string): ProviderPolicy => ({ kind: "direct", profile });
@@ -38,7 +40,9 @@ const gateway = (): ProviderPolicy => ({ kind: "gateway" });
38
40
  const opaque = (): ProviderPolicy => ({ kind: "opaque" });
39
41
 
40
42
  /**
41
- * Every built-in language-model provider in pi 0.84.0. Unknown/custom providers
43
+ * Every built-in language-model provider in pi 1.1.0 (`model-policy.test.ts`
44
+ * checks the list against pi's own), plus the names older pi still in the
45
+ * tested range uses. Unknown/custom providers
42
46
  * get the opaque policy below, so a pi upgrade fails safe rather than widening
43
47
  * automatic routing.
44
48
  */
@@ -49,6 +53,9 @@ export const BUILTIN_PROVIDER_POLICIES: Readonly<Record<string, ProviderPolicy>>
49
53
  google: direct("google"),
50
54
  "google-vertex": direct("google"),
51
55
  openai: direct("openai"),
56
+ // Also serves Foundry models from other vendors (`deepseek-v4-pro`).
57
+ azure: { ...direct("openai"), profileFromId: true },
58
+ // pi before 1.0.3 named `azure` this way.
52
59
  "azure-openai-responses": direct("openai"),
53
60
  "openai-codex": direct("openai"),
54
61
  radius: opaque(),
@@ -78,6 +85,8 @@ export const BUILTIN_PROVIDER_POLICIES: Readonly<Record<string, ProviderPolicy>>
78
85
  "cloudflare-ai-gateway": gateway(),
79
86
  "qwen-token-plan": hosted("host:qwen-plan"),
80
87
  "qwen-token-plan-cn": hosted("host:qwen-plan"),
88
+ "qwen-token-plan-individual": hosted("host:qwen-plan"),
89
+ meta: direct("meta"),
81
90
  xiaomi: direct("xiaomi"),
82
91
  "xiaomi-token-plan-cn": direct("xiaomi"),
83
92
  "xiaomi-token-plan-ams": direct("xiaomi"),
@@ -95,6 +104,7 @@ const VENDOR_ALIASES: Readonly<Record<string, string>> = {
95
104
  deepseek: "deepseek",
96
105
  zai: "zai",
97
106
  "z-ai": "zai",
107
+ zhipuai: "zai",
98
108
  qwen: "host:qwen-plan",
99
109
  alibaba: "host:qwen-plan",
100
110
  moonshotai: "moonshot",
@@ -115,12 +125,21 @@ export interface ModelIdentity {
115
125
  confidence: "exact" | "family" | "opaque";
116
126
  }
117
127
 
118
- const providerPolicy = (provider: string): ProviderPolicy => BUILTIN_PROVIDER_POLICIES[provider] ?? opaque();
128
+ export const providerPolicy = (provider: string): ProviderPolicy => BUILTIN_PROVIDER_POLICIES[provider] ?? opaque();
119
129
 
120
130
  function canonicalVendor(value: string): string | undefined {
121
131
  return VENDOR_ALIASES[value.toLowerCase()];
122
132
  }
123
133
 
134
+ /**
135
+ * The identity profile a catalog vendor name stands for (`z-ai`, `zhipuai` and
136
+ * `zai` are one vendor), else the name itself: the key `model-catalog.ts`
137
+ * matches a row to its vendor's model under.
138
+ */
139
+ export function vendorProfile(vendor: string): string {
140
+ return canonicalVendor(vendor) ?? vendor.toLowerCase();
141
+ }
142
+
124
143
  function prefixedIdentity(model: Model<Api>, prefix: string): ModelIdentity {
125
144
  const raw = model.id.startsWith("~") ? model.id.slice(1) : model.id;
126
145
  const slash = raw.indexOf("/");
@@ -153,8 +172,13 @@ function bareFamily(id: string): string | undefined {
153
172
  }
154
173
 
155
174
  function bedrockIdentity(model: Model<Api>): ModelIdentity {
156
- const geography = model.id.match(/^(us|eu|apac|au|global)\./i)?.[1]?.toLowerCase() ?? "in-region";
157
- const raw = model.id.replace(/^(?:us|eu|apac|au|global)\./i, "");
175
+ // A cross-region inference profile prefixes the vendor with its geography
176
+ // (`us.`, `jp.`, `us-gov.`, …): a first segment that is not a vendor, followed
177
+ // by one that is. Reading it that way needs no list of AWS region codes.
178
+ const [first, second] = model.id.split(".");
179
+ const regional = second !== undefined && !canonicalVendor(first) && canonicalVendor(second) !== undefined;
180
+ const geography = regional ? first.toLowerCase() : "in-region";
181
+ const raw = regional ? model.id.slice(first.length + 1) : model.id;
158
182
  const dot = raw.indexOf(".");
159
183
  if (dot <= 0 || raw.startsWith("arn:")) {
160
184
  return { containment: `${model.provider}:opaque:${model.id}`, normalizedId: model.id, confidence: "opaque" };
@@ -209,7 +233,7 @@ export function modelIdentity(model: Model<Api>): ModelIdentity {
209
233
  }
210
234
  return {
211
235
  containment: model.provider,
212
- profile: policy.profile,
236
+ profile: (policy.profileFromId && bareFamily(model.id)) || policy.profile,
213
237
  normalizedId: model.id,
214
238
  confidence: "exact",
215
239
  };
@@ -219,8 +243,8 @@ const UNSUITABLE_VARIANT = /:(batch|free|online|thinking)$/i;
219
243
 
220
244
  /**
221
245
  * The id without OpenRouter's `~` redirect-alias marker and without a
222
- * `:batch`-style endpoint variant — the spelling the name rules, the facts
223
- * table and the capability index all key on (`~deepseek/deepseek-v4-flash-latest`
246
+ * `:batch`-style endpoint variant — the spelling the model-catalog lookups key
247
+ * on (`~deepseek/deepseek-v4-flash-latest`
224
248
  * → `deepseek/deepseek-v4-flash-latest`, `gpt-5-mini:batch` → `gpt-5-mini`).
225
249
  */
226
250
  export function baseModelId(id: string): string {
@@ -237,20 +261,88 @@ export function isAliasOrVariantId(id: string): boolean {
237
261
  * and OpenRouter's `~vendor/family-latest` redirect aliases ("always redirects to
238
262
  * the latest model in the family"): the model behind one moves without notice,
239
263
  * so it has no stable identity to tier, date or price — and the classifier is a
240
- * permission boundary. The alias stays available by hand (/model, an explicit
241
- * `subagentModel` / `classifierModel` setting).
264
+ * permission boundary. The alias stays available by hand through `/model` or
265
+ * an explicit `subagentModel` setting.
242
266
  */
243
267
  export function isSelectableVariant(model: Model<Api>): boolean {
244
268
  return !UNSUITABLE_VARIANT.test(model.id) && !model.id.startsWith("~");
245
269
  }
246
270
 
247
- /** One day in milliseconds, for the release-date arithmetic in model-facts.ts and capability-index.ts. */
271
+ /**
272
+ * A moving alias: an id that names whichever model its vendor points it at
273
+ * today (`mistral-large-latest`, `gpt-daybreak-blue-latest`, OpenRouter's
274
+ * `~vendor/…-latest` redirects). What answers can change without notice, so
275
+ * it never supersedes a dated model, is never suggested as an upgrade, and is
276
+ * never picked as the auto-mode classifier (a permission boundary). Other
277
+ * automatic picks may still use one: Mistral serves most models only so.
278
+ */
279
+ export function isMovingAlias(id: string): boolean {
280
+ return id.startsWith("~") || /-latest(?=$|[-:@])/i.test(id);
281
+ }
282
+
283
+ /**
284
+ * An experimental build (`deepseek-v4-flash-vision-exp`, `tev1-4b-experimental`):
285
+ * a research variant, never picked automatically (as a screener or the
286
+ * subagent default) or suggested as an upgrade. `preview` is deliberately not matched: Google ships mainline
287
+ * Gemini models under it (`gemini-3-flash-preview`).
288
+ */
289
+ export function isExperimentalBuild(id: string): boolean {
290
+ return /-exp(?:$|[-:])|experimental/i.test(id);
291
+ }
292
+
293
+ /** A positive finite catalog context window, the only value safe to compare windows by. */
294
+ export function hasCatalogContextWindow(model: { contextWindow?: number }): model is { contextWindow: number } {
295
+ return Number.isFinite(model.contextWindow) && (model.contextWindow as number) > 0;
296
+ }
297
+
298
+ /** One day in milliseconds, for the release-date arithmetic in model-catalog.ts. */
248
299
  export const DAY_MS = 86_400_000;
249
300
 
250
- /** Non-positive values are catalog sentinels/unpriced, not evidence of being free. */
301
+ /** A catalog price, or undefined: non-positive values are sentinels/unpriced, not evidence of being free. */
302
+ function catalogPrice(value: number | undefined): number | undefined {
303
+ return typeof value === "number" && value > 0 ? value : undefined;
304
+ }
305
+
306
+ /** The model's list input price, or undefined when it is unpriced. */
251
307
  export function pricedInput(model: Model<Api>): number | undefined {
252
- const input = model.cost?.input;
253
- return typeof input === "number" && input > 0 ? input : undefined;
308
+ return catalogPrice(model.cost?.input);
309
+ }
310
+
311
+ /** A classifier call's measured shape: the cached ruleset and transcript, the new turn, the verdict. */
312
+ export const CLASSIFIER_CALL_TOKENS = { cachedInput: 28_000, freshInput: 2_000, output: 700 } as const;
313
+
314
+ /**
315
+ * One classifier call's estimated cost in dollars at `CLASSIFIER_CALL_TOKENS`,
316
+ * or undefined for a model with no input price. A model without a cache-read
317
+ * price pays its input price for the cached part, and one without an output
318
+ * price its input price for the verdict: both overstate, never understate.
319
+ */
320
+ export function classifierCallCost(model: Model<Api>): number | undefined {
321
+ const input = pricedInput(model);
322
+ if (input === undefined) return undefined;
323
+ const priced = (value: number | undefined) => catalogPrice(value) ?? input;
324
+ const { cachedInput, freshInput, output } = CLASSIFIER_CALL_TOKENS;
325
+ return (priced(model.cost?.cacheRead) * cachedInput + input * freshInput + priced(model.cost?.output) * output) / 1_000_000;
326
+ }
327
+
328
+ /**
329
+ * How classifier candidates are priced against `base` (the session model):
330
+ * per classifier call, unless `base` lists no cache-read price. Then its
331
+ * per-call cost would be overstated (a quarter of OpenRouter's priced models
332
+ * list none) and a dearer model could win, so both sides compare by list
333
+ * input price. The selector and the cost warnings share this, so "cheaper per
334
+ * call" means one thing everywhere.
335
+ */
336
+ export function classifierPrice(base: Model<Api>): (model: Model<Api>) => number | undefined {
337
+ return catalogPrice(base.cost?.cacheRead) === undefined ? pricedInput : classifierCallCost;
338
+ }
339
+
340
+ /** How many times `model`'s classifier call costs `base`'s (priced by `classifierPrice(base)`), or undefined when either price is unknown. */
341
+ export function classifierCostRatio(model: Model<Api>, base: Model<Api>): number | undefined {
342
+ const price = classifierPrice(base);
343
+ const cost = price(model);
344
+ const baseCost = price(base);
345
+ return cost !== undefined && baseCost !== undefined && baseCost > 0 ? cost / baseCost : undefined;
254
346
  }
255
347
 
256
348
  /** A dated snapshot id (`claude-haiku-4-5-20251001`): the trailing `-YYYYMMDD` marks a pinned alias of an undated model. */
@@ -339,7 +431,12 @@ export function forcedReasoningLevel(model: Model<Api>): ThinkingLevel | undefin
339
431
  * A false positive only costs one retry with minimal thinking.
340
432
  */
341
433
  export function isReasoningMandatoryError(message: string): boolean {
342
- return /(reasoning|thinking)[^.]*\b(mandatory|cannot be disabled|can't be disabled|must be enabled|is required)\b/i.test(message);
434
+ return (
435
+ /(reasoning|thinking)[^.]*\b(mandatory|cannot be disabled|can't be disabled|must be enabled|is required)\b/i.test(message) ||
436
+ // Anthropic's: Opus 5.5 ("\"thinking.type.disabled\" is not supported for this model")
437
+ // and Sonnet 5.5 ("To turn thinking off on this model, send … between_tools"), findings §43.
438
+ /thinking\.type\.disabled\W+is not supported|to turn thinking off on this model/i.test(message)
439
+ );
343
440
  }
344
441
 
345
442
  /** The level to retry with after isReasoningMandatoryError: the lowest real level the model supports. */