@oh-my-pi/pi-coding-agent 17.0.8 → 17.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 (340) hide show
  1. package/CHANGELOG.md +87 -1
  2. package/README.md +1 -1
  3. package/dist/cli.js +5495 -5021
  4. package/dist/types/async/job-manager.d.ts +37 -1
  5. package/dist/types/cli/args.d.ts +2 -0
  6. package/dist/types/cli/bench-cli.d.ts +41 -0
  7. package/dist/types/commands/bench.d.ts +15 -3
  8. package/dist/types/commands/launch.d.ts +4 -0
  9. package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
  10. package/dist/types/config/model-registry.d.ts +30 -1
  11. package/dist/types/config/models-config-schema.d.ts +18 -6
  12. package/dist/types/config/models-config.d.ts +14 -5
  13. package/dist/types/config/provider-globals.d.ts +2 -2
  14. package/dist/types/config/service-tier.d.ts +5 -1
  15. package/dist/types/config/settings-schema.d.ts +350 -50
  16. package/dist/types/config/settings.d.ts +7 -0
  17. package/dist/types/dap/client.d.ts +20 -0
  18. package/dist/types/exec/bash-executor.d.ts +38 -0
  19. package/dist/types/exec/direnv.d.ts +33 -0
  20. package/dist/types/export/html/args.d.ts +15 -0
  21. package/dist/types/export/html/index.d.ts +11 -26
  22. package/dist/types/export/html/web-palette.d.ts +55 -116
  23. package/dist/types/extensibility/extensions/loader.d.ts +3 -0
  24. package/dist/types/extensibility/extensions/types.d.ts +17 -2
  25. package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
  26. package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
  27. package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
  28. package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
  29. package/dist/types/live/controller.d.ts +40 -0
  30. package/dist/types/live/protocol.d.ts +85 -0
  31. package/dist/types/live/protocol.test.d.ts +1 -0
  32. package/dist/types/live/transport.d.ts +48 -0
  33. package/dist/types/live/visualizer.d.ts +33 -0
  34. package/dist/types/lsp/client.d.ts +18 -1
  35. package/dist/types/mcp/config-writer.test.d.ts +1 -0
  36. package/dist/types/mcp/manager.d.ts +6 -2
  37. package/dist/types/mcp/oauth-discovery.d.ts +2 -0
  38. package/dist/types/mcp/render.d.ts +2 -2
  39. package/dist/types/mcp/smithery-auth.d.ts +1 -1
  40. package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
  41. package/dist/types/mcp/tool-bridge.d.ts +7 -3
  42. package/dist/types/mcp/types.d.ts +6 -0
  43. package/dist/types/memory-backend/tool-names.d.ts +2 -0
  44. package/dist/types/modes/components/login-dialog.d.ts +2 -2
  45. package/dist/types/modes/components/oauth-selector.d.ts +8 -0
  46. package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
  47. package/dist/types/modes/components/settings-defs.d.ts +8 -1
  48. package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
  49. package/dist/types/modes/components/tool-execution.d.ts +9 -1
  50. package/dist/types/modes/controllers/command-controller.d.ts +1 -1
  51. package/dist/types/modes/controllers/event-controller.d.ts +6 -1
  52. package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
  53. package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
  54. package/dist/types/modes/interactive-mode.d.ts +5 -0
  55. package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
  56. package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
  57. package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
  58. package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
  59. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  60. package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
  61. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  62. package/dist/types/modes/theme/theme.d.ts +1 -1
  63. package/dist/types/modes/types.d.ts +3 -1
  64. package/dist/types/modes/utils/context-usage.d.ts +12 -2
  65. package/dist/types/plan-mode/plan-files.d.ts +10 -0
  66. package/dist/types/registry/agent-lifecycle.d.ts +21 -9
  67. package/dist/types/registry/agent-registry.d.ts +11 -4
  68. package/dist/types/sdk.d.ts +15 -1
  69. package/dist/types/secrets/index.d.ts +15 -2
  70. package/dist/types/secrets/obfuscator.d.ts +113 -15
  71. package/dist/types/session/acp-permission-gate.d.ts +19 -0
  72. package/dist/types/session/agent-session-events.d.ts +84 -0
  73. package/dist/types/session/agent-session-types.d.ts +314 -0
  74. package/dist/types/session/agent-session.d.ts +120 -750
  75. package/dist/types/session/async-job-delivery.d.ts +28 -0
  76. package/dist/types/session/bash-runner.d.ts +75 -0
  77. package/dist/types/session/blob-store.d.ts +11 -1
  78. package/dist/types/session/blob-store.test.d.ts +1 -0
  79. package/dist/types/session/checkpoint-entries.d.ts +28 -0
  80. package/dist/types/session/eval-runner.d.ts +49 -0
  81. package/dist/types/session/irc-bridge.d.ts +46 -0
  82. package/dist/types/session/messages.d.ts +22 -0
  83. package/dist/types/session/model-controls.d.ts +180 -0
  84. package/dist/types/session/prewalk.d.ts +46 -0
  85. package/dist/types/session/queued-messages.d.ts +22 -0
  86. package/dist/types/session/redis-session-storage.d.ts +1 -0
  87. package/dist/types/session/retry-fallback-chains.d.ts +74 -0
  88. package/dist/types/session/role-models.d.ts +15 -0
  89. package/dist/types/session/session-advisors.d.ts +228 -0
  90. package/dist/types/session/session-entries.d.ts +8 -0
  91. package/dist/types/session/session-handoff.d.ts +70 -0
  92. package/dist/types/session/session-history-format.d.ts +6 -1
  93. package/dist/types/session/session-loader.d.ts +6 -4
  94. package/dist/types/session/session-maintenance.d.ts +246 -0
  95. package/dist/types/session/session-manager.d.ts +41 -0
  96. package/dist/types/session/session-memory.d.ts +56 -0
  97. package/dist/types/session/session-provider-boundary.d.ts +62 -0
  98. package/dist/types/session/session-stats.d.ts +48 -0
  99. package/dist/types/session/session-tools.d.ts +143 -0
  100. package/dist/types/session/session-workspace.d.ts +32 -0
  101. package/dist/types/session/stream-guards.d.ts +44 -0
  102. package/dist/types/session/todo-tracker.d.ts +56 -0
  103. package/dist/types/session/ttsr-coordinator.d.ts +51 -0
  104. package/dist/types/session/turn-recovery.d.ts +225 -0
  105. package/dist/types/system-prompt.d.ts +2 -0
  106. package/dist/types/task/executor.d.ts +4 -1
  107. package/dist/types/task/output-manager.d.ts +3 -1
  108. package/dist/types/task/types.d.ts +14 -0
  109. package/dist/types/tools/approval.d.ts +1 -0
  110. package/dist/types/tools/browser/launch.d.ts +5 -0
  111. package/dist/types/tools/hub/jobs.d.ts +1 -1
  112. package/dist/types/tools/image-gen.d.ts +4 -4
  113. package/dist/types/tools/image-providers.d.ts +38 -0
  114. package/dist/types/tools/index.d.ts +8 -0
  115. package/dist/types/tools/path-utils.d.ts +8 -0
  116. package/dist/types/tools/report-tool-issue.d.ts +24 -9
  117. package/dist/types/tools/todo.d.ts +10 -4
  118. package/dist/types/tools/xdev.d.ts +5 -1
  119. package/dist/types/tts/streaming-player.d.ts +14 -0
  120. package/dist/types/tts/vocalizer.d.ts +5 -0
  121. package/dist/types/utils/jj.d.ts +29 -0
  122. package/dist/types/utils/lang-from-path.d.ts +1 -0
  123. package/dist/types/utils/title-generator.d.ts +30 -0
  124. package/dist/types/vibe/runtime.d.ts +36 -8
  125. package/dist/types/web/search/index.d.ts +1 -1
  126. package/dist/types/web/search/provider.d.ts +15 -5
  127. package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
  128. package/dist/types/web/search/types.d.ts +95 -1
  129. package/package.json +13 -12
  130. package/scripts/generate-share-viewer.ts +4 -6
  131. package/scripts/legacy-pi-virtual-module.ts +1 -1
  132. package/src/advisor/__tests__/advisor.test.ts +643 -0
  133. package/src/advisor/runtime.ts +144 -43
  134. package/src/async/job-manager.ts +90 -2
  135. package/src/cli/args.ts +2 -0
  136. package/src/cli/auth-gateway-cli.ts +18 -3
  137. package/src/cli/bench-cli.ts +395 -29
  138. package/src/cli/flag-tables.ts +3 -0
  139. package/src/cli/gc-cli.ts +2 -2
  140. package/src/cli/grievances-cli.ts +2 -2
  141. package/src/cli/usage-cli.ts +11 -2
  142. package/src/collab/host.ts +3 -2
  143. package/src/commands/bench.ts +18 -4
  144. package/src/commands/launch.ts +4 -0
  145. package/src/config/__tests__/model-registry.test.ts +147 -0
  146. package/src/config/model-registry.ts +50 -2
  147. package/src/config/model-resolver.ts +0 -1
  148. package/src/config/models-config-schema.ts +15 -4
  149. package/src/config/provider-globals.ts +9 -9
  150. package/src/config/service-tier.ts +26 -1
  151. package/src/config/settings-schema.ts +250 -54
  152. package/src/config/settings.ts +81 -2
  153. package/src/dap/client.ts +37 -4
  154. package/src/edit/index.ts +49 -12
  155. package/src/eval/py/executor.ts +103 -33
  156. package/src/exec/bash-executor.ts +89 -6
  157. package/src/exec/direnv.ts +145 -0
  158. package/src/export/html/args.ts +20 -0
  159. package/src/export/html/index.ts +45 -47
  160. package/src/export/html/template.css +19 -1
  161. package/src/export/html/template.html +6 -0
  162. package/src/export/html/template.js +21 -0
  163. package/src/export/html/tool-views.generated.js +31 -31
  164. package/src/export/html/web-palette.ts +116 -132
  165. package/src/export/share.ts +249 -49
  166. package/src/extensibility/extensions/loader.ts +29 -1
  167. package/src/extensibility/extensions/runner.ts +6 -0
  168. package/src/extensibility/extensions/types.ts +33 -2
  169. package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
  170. package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
  171. package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
  172. package/src/hindsight/state.ts +64 -6
  173. package/src/internal-urls/local-protocol.ts +2 -1
  174. package/src/internal-urls/memory-protocol.ts +2 -2
  175. package/src/internal-urls/registry-helpers.ts +40 -0
  176. package/src/internal-urls/skill-protocol.ts +2 -2
  177. package/src/internal-urls/ssh-protocol.ts +2 -1
  178. package/src/internal-urls/vault-protocol.ts +2 -1
  179. package/src/live/audio-worklet.txt +59 -0
  180. package/src/live/browser-runtime.txt +221 -0
  181. package/src/live/controller.ts +464 -0
  182. package/src/live/prompts/agent-final-message.md +3 -0
  183. package/src/live/prompts/live-instructions.md +23 -0
  184. package/src/live/protocol.test.ts +140 -0
  185. package/src/live/protocol.ts +233 -0
  186. package/src/live/transport.ts +490 -0
  187. package/src/live/visualizer.ts +234 -0
  188. package/src/lsp/client.ts +52 -9
  189. package/src/lsp/clients/biome-client.ts +3 -4
  190. package/src/lsp/index.ts +44 -11
  191. package/src/main.ts +9 -1
  192. package/src/mcp/config-writer.test.ts +43 -0
  193. package/src/mcp/config-writer.ts +109 -82
  194. package/src/mcp/manager.ts +39 -8
  195. package/src/mcp/oauth-discovery.ts +10 -2
  196. package/src/mcp/render.ts +94 -35
  197. package/src/mcp/smithery-auth.test.ts +29 -0
  198. package/src/mcp/smithery-auth.ts +3 -2
  199. package/src/mcp/tool-bridge.ts +107 -11
  200. package/src/mcp/types.ts +7 -0
  201. package/src/memories/index.ts +40 -20
  202. package/src/memory-backend/tool-names.ts +2 -0
  203. package/src/mnemopi/backend.ts +24 -7
  204. package/src/modes/acp/acp-agent.ts +56 -10
  205. package/src/modes/acp/acp-event-mapper.ts +8 -1
  206. package/src/modes/components/agent-hub.ts +27 -10
  207. package/src/modes/components/login-dialog.ts +14 -4
  208. package/src/modes/components/oauth-selector.ts +24 -7
  209. package/src/modes/components/plan-review-overlay.ts +350 -35
  210. package/src/modes/components/settings-defs.ts +28 -3
  211. package/src/modes/components/settings-selector.ts +178 -6
  212. package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
  213. package/src/modes/components/status-line/component.ts +123 -2
  214. package/src/modes/components/tool-execution.test.ts +63 -2
  215. package/src/modes/components/tool-execution.ts +11 -2
  216. package/src/modes/controllers/command-controller.ts +70 -22
  217. package/src/modes/controllers/event-controller.ts +111 -16
  218. package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
  219. package/src/modes/controllers/extension-ui-controller.ts +20 -4
  220. package/src/modes/controllers/live-command-controller.ts +178 -0
  221. package/src/modes/controllers/mcp-command-controller.ts +80 -41
  222. package/src/modes/controllers/omfg-controller.ts +44 -40
  223. package/src/modes/controllers/selector-controller.ts +18 -10
  224. package/src/modes/controllers/session-focus-controller.ts +6 -1
  225. package/src/modes/interactive-mode.ts +160 -38
  226. package/src/modes/rpc/rpc-client.ts +94 -3
  227. package/src/modes/rpc/rpc-frame.ts +164 -4
  228. package/src/modes/rpc/rpc-messages.ts +127 -0
  229. package/src/modes/rpc/rpc-mode.ts +79 -7
  230. package/src/modes/rpc/rpc-types.ts +34 -2
  231. package/src/modes/runtime-init.ts +2 -0
  232. package/src/modes/setup-wizard/scenes/model.ts +5 -7
  233. package/src/modes/setup-wizard/scenes/providers.ts +3 -2
  234. package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
  235. package/src/modes/setup-wizard/scenes/theme.ts +13 -3
  236. package/src/modes/setup-wizard/scenes/types.ts +9 -1
  237. package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
  238. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  239. package/src/modes/theme/theme.ts +1 -1
  240. package/src/modes/types.ts +3 -1
  241. package/src/modes/utils/context-usage.ts +23 -7
  242. package/src/modes/utils/ui-helpers.ts +10 -5
  243. package/src/plan-mode/plan-files.ts +40 -0
  244. package/src/prompts/bench/cache-prefix-chunk.md +1 -0
  245. package/src/prompts/bench/cache-prefix.md +3 -0
  246. package/src/prompts/bench/cache-suffix.md +1 -0
  247. package/src/prompts/goals/guided-goal-system.md +24 -3
  248. package/src/prompts/system/custom-system-prompt.md +1 -1
  249. package/src/prompts/system/project-prompt.md +8 -1
  250. package/src/prompts/system/subagent-async-pending.md +6 -0
  251. package/src/prompts/system/system-prompt.md +1 -1
  252. package/src/prompts/system/workflow-notice.md +48 -40
  253. package/src/prompts/system/xdev-mount-notice.md +4 -0
  254. package/src/prompts/tools/task.md +12 -2
  255. package/src/prompts/tools/todo.md +3 -1
  256. package/src/registry/agent-lifecycle.ts +116 -46
  257. package/src/registry/agent-registry.ts +35 -8
  258. package/src/registry/persisted-agents.ts +26 -2
  259. package/src/sdk.ts +255 -139
  260. package/src/secrets/index.ts +127 -4
  261. package/src/secrets/obfuscator.ts +2328 -105
  262. package/src/session/acp-permission-gate.ts +165 -0
  263. package/src/session/agent-session-error-log.test.ts +1 -1
  264. package/src/session/agent-session-events.ts +66 -0
  265. package/src/session/agent-session-types.ts +334 -0
  266. package/src/session/agent-session.ts +1910 -12213
  267. package/src/session/artifacts.ts +6 -5
  268. package/src/session/async-job-delivery.ts +74 -0
  269. package/src/session/bash-runner.ts +326 -0
  270. package/src/session/blob-store.test.ts +56 -0
  271. package/src/session/blob-store.ts +18 -2
  272. package/src/session/checkpoint-entries.ts +81 -0
  273. package/src/session/eval-runner.ts +212 -0
  274. package/src/session/indexed-session-storage.ts +9 -2
  275. package/src/session/irc-bridge.ts +203 -0
  276. package/src/session/messages.ts +263 -1
  277. package/src/session/model-controls.ts +714 -0
  278. package/src/session/prewalk.ts +252 -0
  279. package/src/session/queued-messages.ts +93 -0
  280. package/src/session/redis-session-storage.ts +51 -11
  281. package/src/session/retry-fallback-chains.ts +455 -0
  282. package/src/session/role-models.ts +85 -0
  283. package/src/session/session-advisors.ts +1679 -0
  284. package/src/session/session-context.test.ts +224 -1
  285. package/src/session/session-context.ts +47 -5
  286. package/src/session/session-entries.ts +8 -0
  287. package/src/session/session-handoff.ts +308 -0
  288. package/src/session/session-history-format.ts +20 -9
  289. package/src/session/session-loader.ts +10 -51
  290. package/src/session/session-maintenance.ts +2983 -0
  291. package/src/session/session-manager.ts +390 -30
  292. package/src/session/session-memory.ts +222 -0
  293. package/src/session/session-provider-boundary.ts +307 -0
  294. package/src/session/session-stats.ts +293 -0
  295. package/src/session/session-tools.ts +943 -0
  296. package/src/session/session-workspace.ts +53 -0
  297. package/src/session/stream-guards.ts +417 -0
  298. package/src/session/todo-tracker.ts +380 -0
  299. package/src/session/ttsr-coordinator.ts +496 -0
  300. package/src/session/turn-recovery.ts +1629 -0
  301. package/src/slash-commands/builtin-registry.ts +92 -11
  302. package/src/slash-commands/helpers/usage-report.ts +25 -4
  303. package/src/system-prompt.ts +41 -17
  304. package/src/task/executor.ts +288 -70
  305. package/src/task/index.ts +148 -111
  306. package/src/task/output-manager.ts +25 -3
  307. package/src/task/persisted-revive.ts +4 -3
  308. package/src/task/structured-subagent.ts +4 -1
  309. package/src/task/types.ts +16 -0
  310. package/src/tools/approval.ts +55 -10
  311. package/src/tools/bash-interactive.ts +26 -0
  312. package/src/tools/bash-skill-urls.ts +28 -2
  313. package/src/tools/bash.ts +339 -123
  314. package/src/tools/browser/launch.ts +9 -1
  315. package/src/tools/browser/tab-supervisor.ts +8 -5
  316. package/src/tools/browser.ts +94 -47
  317. package/src/tools/hub/index.ts +1 -1
  318. package/src/tools/hub/jobs.ts +67 -8
  319. package/src/tools/image-gen.ts +20 -28
  320. package/src/tools/image-providers.ts +50 -0
  321. package/src/tools/index.ts +12 -0
  322. package/src/tools/path-utils.ts +49 -2
  323. package/src/tools/read.ts +156 -88
  324. package/src/tools/renderers.ts +7 -1
  325. package/src/tools/report-tool-issue.ts +79 -28
  326. package/src/tools/todo.ts +101 -11
  327. package/src/tools/vibe.ts +1 -2
  328. package/src/tools/write.ts +51 -1
  329. package/src/tools/xdev.ts +67 -3
  330. package/src/tts/streaming-player.ts +62 -4
  331. package/src/tts/vocalizer.ts +18 -1
  332. package/src/utils/jj.ts +125 -4
  333. package/src/utils/lang-from-path.ts +7 -0
  334. package/src/utils/title-generator.ts +137 -1
  335. package/src/vibe/runtime.ts +852 -72
  336. package/src/web/search/index.ts +3 -6
  337. package/src/web/search/provider.ts +31 -16
  338. package/src/web/search/providers/firecrawl.ts +46 -13
  339. package/src/web/search/providers/xai.ts +7 -1
  340. package/src/web/search/types.ts +8 -1
@@ -15,15 +15,24 @@ export default class Bench extends Command {
15
15
  };
16
16
 
17
17
  static flags = {
18
- runs: Flags.integer({ description: "Requests per model (results are averaged)", default: 10 }),
19
- "max-tokens": Flags.integer({ description: "Max output tokens per request", default: 512 }),
18
+ runs: Flags.integer({ description: "Requests per model (results are averaged; default: 10)" }),
19
+ "max-tokens": Flags.integer({ description: "Max output tokens per request (default: 512; cache mode: 64)" }),
20
20
  prompt: Flags.string({ description: "Custom prompt text (default: bundled bench prompt)" }),
21
21
  "service-tier": Flags.string({
22
22
  description: "Service tier applied per model family (default: configured `tier.*` settings; `none` omits it)",
23
23
  options: SERVICE_TIER_OPENAI_VALUES,
24
24
  }),
25
25
  json: Flags.boolean({ description: "Output JSON" }),
26
- par: Flags.integer({ description: "Execute runs with N parallel queries/requests", default: 4 }),
26
+ par: Flags.integer({ description: "Execute runs with N parallel queries/requests (default: 4)" }),
27
+ cache: Flags.boolean({
28
+ description: "Run independent cold/warm prompt-cache pairs (not supported for openai-codex-responses)",
29
+ }),
30
+ "cache-prefix-file": Flags.string({ description: "Stable prompt prefix file for --cache" }),
31
+ "cache-prefix-bytes": Flags.integer({ description: "Stable prefix byte budget for --cache (default: 8192)" }),
32
+ "cache-pairs": Flags.integer({ description: "Cold/warm pairs per model for --cache (default: 1)" }),
33
+ "cache-concurrency": Flags.integer({
34
+ description: "Concurrent cache pairs for --cache; each pair remains sequential (default: 1)",
35
+ }),
27
36
  };
28
37
 
29
38
  static examples = [
@@ -31,7 +40,7 @@ export default class Bench extends Command {
31
40
  "# Fuzzy selectors work\n omp bench opus sonnet",
32
41
  "# Average over 3 runs each\n omp bench opus gpt-5.2 --runs 3",
33
42
  "# Force priority serving tier\n omp bench openai-codex/gpt-5.5:low --runs 10 --service-tier priority",
34
- "# Machine-readable output\n omp bench opus --json",
43
+ "# Measure one cold/warm prompt-cache pair\n omp bench openai/gpt-5.6 --cache --json",
35
44
  ];
36
45
 
37
46
  async run(): Promise<void> {
@@ -45,6 +54,11 @@ export default class Bench extends Command {
45
54
  serviceTier: flags["service-tier"],
46
55
  json: flags.json,
47
56
  par: flags.par,
57
+ cache: flags.cache,
58
+ cachePrefixFile: flags["cache-prefix-file"],
59
+ cachePrefixBytes: flags["cache-prefix-bytes"],
60
+ cachePairs: flags["cache-pairs"],
61
+ cacheConcurrency: flags["cache-concurrency"],
48
62
  },
49
63
  });
50
64
  }
@@ -83,6 +83,10 @@ export default class Index extends Command {
83
83
  description: "Load an extra config.yml-style overlay for this run (repeatable)",
84
84
  multiple: true,
85
85
  }),
86
+ "add-dir": Flags.string({
87
+ description: "Add a workspace directory beyond the working directory (repeatable)",
88
+ multiple: true,
89
+ }),
86
90
  print: Flags.boolean({
87
91
  char: "p",
88
92
  description: "Non-interactive mode: process prompt and exit",
@@ -0,0 +1,147 @@
1
+ import { afterEach, beforeEach, describe, expect, test, vi } from "bun:test";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+ import type { AuthStorage } from "@oh-my-pi/pi-ai";
6
+ import { ModelRegistry } from "../model-registry";
7
+
8
+ /**
9
+ * Stub AuthStorage that satisfies the surface used by ModelRegistry's
10
+ * constructor (#loadModels → clearConfigApiKeys, constructor →
11
+ * setFallbackResolver). The awaiter under test never reaches auth-gated code
12
+ * paths, so no real credential store is required.
13
+ */
14
+ function createStubAuthStorage(): AuthStorage {
15
+ const stub = {
16
+ setFallbackResolver: () => {},
17
+ clearConfigApiKeys: () => {},
18
+ hasAuth: () => false,
19
+ };
20
+ return stub as unknown as AuthStorage;
21
+ }
22
+
23
+ describe("ModelRegistry.awaitBackgroundRefresh", () => {
24
+ let tmpDir: string;
25
+ let registry: ModelRegistry;
26
+
27
+ beforeEach(() => {
28
+ tmpDir = mkdtempSync(path.join(os.tmpdir(), "omp-reg-"));
29
+ // Construct with an explicit modelsPath inside the temp dir so the
30
+ // constructor's #loadModels read returns "not-found" rather than
31
+ // touching the host's ~/.omp/agent/models.yaml. isBunTestRuntime()
32
+ // auto-stubs #fetch in the constructor.
33
+ registry = new ModelRegistry(createStubAuthStorage(), path.join(tmpDir, "models.yaml"));
34
+ });
35
+
36
+ afterEach(() => {
37
+ vi.restoreAllMocks();
38
+ rmSync(tmpDir, { recursive: true, force: true });
39
+ });
40
+
41
+ test("resolves immediately when no background refresh is in flight", async () => {
42
+ // No refreshInBackground() called → #backgroundRefresh is undefined.
43
+ // The awaiter must settle within a single microtask, never hanging.
44
+ let settled = false;
45
+ const p = registry.awaitBackgroundRefresh().then(() => {
46
+ settled = true;
47
+ });
48
+ await Promise.resolve();
49
+ await p;
50
+ expect(settled).toBe(true);
51
+ });
52
+
53
+ test("blocks until the in-flight background refresh resolves, then resolves", async () => {
54
+ // Drive refreshInBackground with a controlled refresh() return value so
55
+ // #backgroundRefresh is captured but not yet settled.
56
+ const { promise, resolve } = Promise.withResolvers<void>();
57
+ const refreshSpy = vi.spyOn(registry, "refresh").mockReturnValue(promise);
58
+
59
+ registry.refreshInBackground();
60
+ expect(refreshSpy).toHaveBeenCalledTimes(1);
61
+
62
+ let settled = false;
63
+ const awaitPromise = registry.awaitBackgroundRefresh().then(() => {
64
+ settled = true;
65
+ });
66
+
67
+ // Yield to the microtask queue: the awaiter must still be pending.
68
+ for (let i = 0; i < 5; i++) await Promise.resolve();
69
+ expect(settled).toBe(false);
70
+
71
+ resolve();
72
+
73
+ await awaitPromise;
74
+ expect(settled).toBe(true);
75
+ });
76
+
77
+ test("resolves even when the underlying refresh rejects (refreshInBackground swallows)", async () => {
78
+ // refreshInBackground wraps refresh() in .catch(...) so discovery errors
79
+ // never reach awaitBackgroundRefresh callers. The awaiter must resolve,
80
+ // not propagate the rejection.
81
+ const { promise, reject } = Promise.withResolvers<void>();
82
+ vi.spyOn(registry, "refresh").mockReturnValue(promise);
83
+
84
+ registry.refreshInBackground();
85
+
86
+ let settled = false;
87
+ const awaitPromise = registry.awaitBackgroundRefresh().then(() => {
88
+ settled = true;
89
+ });
90
+
91
+ reject(new Error("synthetic discovery failure"));
92
+
93
+ await awaitPromise;
94
+ expect(settled).toBe(true);
95
+ });
96
+
97
+ test("awaiter is a no-op after the in-flight refresh settles and clears #backgroundRefresh", async () => {
98
+ // Once refreshInBackground's promise resolves, #backgroundRefresh is
99
+ // cleared in the .finally. A subsequent awaitBackgroundRefresh must be
100
+ // an immediate no-op (microtask), not hang waiting for a stale promise
101
+ // or a second refresh that was never started.
102
+ const { promise, resolve } = Promise.withResolvers<void>();
103
+ vi.spyOn(registry, "refresh").mockReturnValue(promise);
104
+
105
+ registry.refreshInBackground();
106
+ resolve();
107
+ await registry.awaitBackgroundRefresh();
108
+
109
+ // Now #backgroundRefresh is cleared. A fresh await must resolve in a
110
+ // single microtask — measure by asserting it settles before a second
111
+ // microtask tick.
112
+ let settled = false;
113
+ const p = registry.awaitBackgroundRefresh().then(() => {
114
+ settled = true;
115
+ });
116
+ await Promise.resolve();
117
+ expect(settled).toBe(true);
118
+ await p;
119
+ });
120
+
121
+ test("refreshInBackground deduplicates: a second call while in-flight starts no new refresh", async () => {
122
+ // The guard `if (this.#backgroundRefresh) return` at the top of
123
+ // refreshInBackground prevents concurrent refreshes. A second call
124
+ // while the first is still pending must not invoke refresh() again.
125
+ const { promise, resolve } = Promise.withResolvers<void>();
126
+ const refreshSpy = vi.spyOn(registry, "refresh").mockReturnValue(promise);
127
+
128
+ registry.refreshInBackground();
129
+ registry.refreshInBackground();
130
+ registry.refreshInBackground();
131
+
132
+ expect(refreshSpy).toHaveBeenCalledTimes(1);
133
+
134
+ resolve();
135
+ await registry.awaitBackgroundRefresh();
136
+
137
+ // After settle, #backgroundRefresh is cleared — a new call DOES start
138
+ // a fresh refresh.
139
+ const { promise: secondPromise, resolve: secondResolve } = Promise.withResolvers<void>();
140
+ refreshSpy.mockReturnValue(secondPromise);
141
+ registry.refreshInBackground();
142
+ expect(refreshSpy).toHaveBeenCalledTimes(2);
143
+
144
+ secondResolve();
145
+ await registry.awaitBackgroundRefresh();
146
+ });
147
+ });
@@ -869,6 +869,28 @@ export class ModelRegistry {
869
869
  this.#backgroundRefresh = refreshPromise;
870
870
  }
871
871
 
872
+ /**
873
+ * Wait for any in-flight background model discovery to settle.
874
+ *
875
+ * Background discovery started by {@link refreshInBackground} is
876
+ * fire-and-forget; RPC consumers (e.g. `get_available_models`,
877
+ * `set_model`) and deferred `--model` resolution that read the registry
878
+ * immediately after session creation can otherwise observe a partial
879
+ * catalog before discovery-backed providers have populated `#models`.
880
+ * Awaiting the tracked promise ensures the response reflects every
881
+ * configured provider once the initial background refresh resolves.
882
+ *
883
+ * No-op when no refresh is in flight (`#backgroundRefresh` cleared in the
884
+ * `finally` of `refreshInBackground` on completion). Resolves immediately
885
+ * in that case so already-warm sessions are unaffected. Discovery errors
886
+ * remain swallowed by `refreshInBackground`'s existing `.catch`.
887
+ */
888
+ async awaitBackgroundRefresh(): Promise<void> {
889
+ if (this.#backgroundRefresh) {
890
+ await this.#backgroundRefresh;
891
+ }
892
+ }
893
+
872
894
  async refreshProvider(providerId: string, strategy: ModelRefreshStrategy = "online"): Promise<void> {
873
895
  this.#reloadStaticModels();
874
896
  for (const selector of this.#suppressedSelectors.keys()) {
@@ -2101,6 +2123,24 @@ export class ModelRegistry {
2101
2123
  .map(provider => provider.provider);
2102
2124
  }
2103
2125
 
2126
+ /**
2127
+ * Whether `providerId` is known to the registry: it has at least one live
2128
+ * model, or it is configured for dynamic discovery (models.yml `discovery:`
2129
+ * or a runtime extension provider) and is not disabled. Discovery-only
2130
+ * providers can hold zero models at startup — cached rows never persist
2131
+ * live auth headers (#5780), so a provider whose discovered models all
2132
+ * carry config headers (`authHeader: true`) only materializes models after
2133
+ * the online refresh completes.
2134
+ */
2135
+ hasProvider(providerId: string): boolean {
2136
+ if (this.#models.some(model => model.provider === providerId)) return true;
2137
+ if (getDisabledProviderIdsFromSettings().has(providerId)) return false;
2138
+ return (
2139
+ this.#discoverableProviders.some(provider => provider.provider === providerId) ||
2140
+ this.#runtimeModelManagers.has(providerId)
2141
+ );
2142
+ }
2143
+
2104
2144
  getProviderDiscoveryState(provider: string): ProviderDiscoveryState | undefined {
2105
2145
  return this.#providerDiscoveryStates.get(provider);
2106
2146
  }
@@ -2131,13 +2171,21 @@ export class ModelRegistry {
2131
2171
  /**
2132
2172
  * Get API key for a model.
2133
2173
  */
2134
- async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
2174
+ async getApiKey(
2175
+ model: Model<Api>,
2176
+ sessionId?: string,
2177
+ options?: { signal?: AbortSignal },
2178
+ ): Promise<string | undefined> {
2135
2179
  const commandKey = this.#resolveCommandBackedApiKey(model.provider);
2136
2180
  if (commandKey.configured) return commandKey.value;
2137
2181
  if (this.#keylessProviders.has(model.provider) && !this.authStorage.hasAuth(model.provider)) {
2138
2182
  return kNoAuth;
2139
2183
  }
2140
- return this.authStorage.getApiKey(model.provider, sessionId, { baseUrl: model.baseUrl, modelId: model.id });
2184
+ return this.authStorage.getApiKey(model.provider, sessionId, {
2185
+ baseUrl: model.baseUrl,
2186
+ modelId: model.id,
2187
+ signal: options?.signal,
2188
+ });
2141
2189
  }
2142
2190
 
2143
2191
  /**
@@ -782,7 +782,6 @@ function parseModelPatternWithContext(
782
782
  options?: { allowInvalidThinkingSelectorFallback?: boolean },
783
783
  ): ParsedModelResult {
784
784
  // Exact match on the full pattern first (no fuzzy): a literal id that
785
- // contains a colon (`coding-router:max`) wins over any suffix split.
786
785
  const exactMatch = matchModel(pattern, availableModels, context, { exactOnly: true });
787
786
  if (exactMatch) {
788
787
  return { model: exactMatch, thinkingLevel: undefined, warning: undefined, explicitThinkingLevel: false };
@@ -73,8 +73,19 @@ export const OpenAICompatSchema = type({
73
73
  "whenThinking?": OpenAICompatFieldsSchema,
74
74
  });
75
75
 
76
+ const BedrockCompatSchema = type({
77
+ "promptCacheMode?": '"none" | "automatic" | "explicit"',
78
+ "supportsLongPromptCacheRetention?": "boolean",
79
+ "promptCacheMinimumTokens?": "number >= 0",
80
+ "promptCacheMaximumCheckpoints?": "number >= 0",
81
+ });
82
+
83
+ // Provider-level overrides can target bundled models whose API is not repeated
84
+ // in models.yml, so preserve the sparse compat shape for each supported API.
85
+ const ApiCompatSchema = OpenAICompatSchema.and(BedrockCompatSchema);
86
+
76
87
  const ApiSchema = type(
77
- '"openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "google-generative-ai" | "google-gemini-cli" | "google-vertex"',
88
+ '"openai-completions" | "openai-responses" | "openai-codex-responses" | "azure-openai-responses" | "anthropic-messages" | "bedrock-converse-stream" | "google-generative-ai" | "google-gemini-cli" | "google-vertex"',
78
89
  );
79
90
 
80
91
  const EffortSchema = type('"minimal" | "low" | "medium" | "high" | "xhigh" | "max"');
@@ -173,7 +184,7 @@ const ModelDefinitionSchema = type({
173
184
  "maxTokens?": "number",
174
185
  "omitMaxOutputTokens?": "boolean",
175
186
  "headers?": { "[string]": "string" },
176
- "compat?": OpenAICompatSchema,
187
+ "compat?": ApiCompatSchema,
177
188
  "contextPromotionTarget?": "string",
178
189
  "compactionModel?": "string",
179
190
  "remoteCompaction?": RemoteCompactionSchema,
@@ -222,7 +233,7 @@ export const ModelOverrideSchema = type({
222
233
  "maxTokens?": "number",
223
234
  "omitMaxOutputTokens?": "boolean",
224
235
  "headers?": { "[string]": "string" },
225
- "compat?": OpenAICompatSchema,
236
+ "compat?": ApiCompatSchema,
226
237
  "contextPromotionTarget?": "string",
227
238
  "compactionModel?": "string",
228
239
  "remoteCompaction?": RemoteCompactionSchema,
@@ -263,7 +274,7 @@ const ProviderConfigSchema = type({
263
274
  "apiKey?": "string",
264
275
  "api?": ApiSchema,
265
276
  "headers?": { "[string]": "string" },
266
- "compat?": OpenAICompatSchema,
277
+ "compat?": ApiCompatSchema,
267
278
  "remoteCompaction?": RemoteCompactionSchema,
268
279
  "authHeader?": "boolean",
269
280
  "auth?": ProviderAuthSchema,
@@ -1,10 +1,10 @@
1
- import * as imageGen from "../tools/image-gen";
1
+ import { setImageProviderOrder } from "../tools/image-gen";
2
2
  import * as webSearch from "../web/search";
3
3
 
4
4
  interface ProviderGlobalSettings {
5
+ get(path: "providers.webSearchOrder"): unknown;
5
6
  get(path: "providers.webSearchExclude"): unknown;
6
- get(path: "providers.webSearch"): unknown;
7
- get(path: "providers.image"): unknown;
7
+ get(path: "providers.imageOrder"): unknown;
8
8
  }
9
9
 
10
10
  export function applyProviderGlobalsFromSettings(settings: ProviderGlobalSettings): void {
@@ -13,13 +13,13 @@ export function applyProviderGlobalsFromSettings(settings: ProviderGlobalSetting
13
13
  webSearch.setExcludedSearchProviders(excludedWebSearchProviders.filter(webSearch.isSearchProviderId));
14
14
  }
15
15
 
16
- const webSearchProvider = settings.get("providers.webSearch");
17
- if (typeof webSearchProvider === "string" && webSearch.isSearchProviderPreference(webSearchProvider)) {
18
- webSearch.setPreferredSearchProvider(webSearchProvider);
16
+ const orderedWebSearchProviders = settings.get("providers.webSearchOrder");
17
+ if (Array.isArray(orderedWebSearchProviders)) {
18
+ webSearch.setSearchProviderOrder(orderedWebSearchProviders.filter(webSearch.isSearchProviderId));
19
19
  }
20
20
 
21
- const imageProvider = settings.get("providers.image");
22
- if (imageGen.isImageProviderPreference(imageProvider)) {
23
- imageGen.setPreferredImageProvider(imageProvider);
21
+ const orderedImageProviders = settings.get("providers.imageOrder");
22
+ if (Array.isArray(orderedImageProviders)) {
23
+ setImageProviderOrder(orderedImageProviders.filter((entry): entry is string => typeof entry === "string"));
24
24
  }
25
25
  }
@@ -1,4 +1,4 @@
1
- import type { ServiceTier, ServiceTierByFamily } from "@oh-my-pi/pi-ai";
1
+ import type { ServiceTier, ServiceTierByFamily, ServiceTierFamily } from "@oh-my-pi/pi-ai";
2
2
  import type { SubmenuOption } from "./settings-schema";
3
3
 
4
4
  /**
@@ -16,6 +16,31 @@ export type ServiceTierOpenAISettingValue = (typeof SERVICE_TIER_OPENAI_VALUES)[
16
16
  export type ServiceTierAnthropicSettingValue = (typeof SERVICE_TIER_ANTHROPIC_VALUES)[number];
17
17
  export type ServiceTierGoogleSettingValue = (typeof SERVICE_TIER_GOOGLE_VALUES)[number];
18
18
 
19
+ /** Whether a runtime value names a provider family with an independent service-tier knob. */
20
+ export function isServiceTierFamily(value: unknown): value is ServiceTierFamily {
21
+ return value === "openai" || value === "anthropic" || value === "google";
22
+ }
23
+
24
+ /** Whether a runtime value is a supported service tier for one provider family. */
25
+ export function isServiceTierForFamily(family: string, tier: unknown): tier is ServiceTier {
26
+ if (typeof tier !== "string" || tier === "none") return false;
27
+ let values: readonly string[];
28
+ switch (family) {
29
+ case "openai":
30
+ values = SERVICE_TIER_OPENAI_VALUES;
31
+ break;
32
+ case "anthropic":
33
+ values = SERVICE_TIER_ANTHROPIC_VALUES;
34
+ break;
35
+ case "google":
36
+ values = SERVICE_TIER_GOOGLE_VALUES;
37
+ break;
38
+ default:
39
+ return false;
40
+ }
41
+ return values.includes(tier);
42
+ }
43
+
19
44
  /**
20
45
  * Inherit-capable single value for the subagent/advisor tiers. The chosen tier
21
46
  * is broadcast across families and applied to whichever family the spawned