@oh-my-pi/pi-coding-agent 15.10.10 → 15.10.12

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 (415) hide show
  1. package/CHANGELOG.md +142 -7
  2. package/dist/cli.js +23108 -0
  3. package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
  4. package/dist/types/async/job-manager.d.ts +18 -0
  5. package/dist/types/cli/args.d.ts +2 -1
  6. package/dist/types/cli/dry-balance-cli.d.ts +1 -1
  7. package/dist/types/cli/gallery-cli.d.ts +1 -1
  8. package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
  9. package/dist/types/cli/usage-cli.d.ts +72 -0
  10. package/dist/types/cli-commands.d.ts +12 -0
  11. package/dist/types/commands/launch.d.ts +5 -1
  12. package/dist/types/commands/read.d.ts +1 -1
  13. package/dist/types/commands/usage.d.ts +25 -0
  14. package/dist/types/config/api-key-resolver.d.ts +3 -0
  15. package/dist/types/config/append-only-context-mode.d.ts +2 -1
  16. package/dist/types/config/model-discovery.d.ts +55 -0
  17. package/dist/types/config/model-registry.d.ts +8 -219
  18. package/dist/types/config/model-resolver.d.ts +34 -10
  19. package/dist/types/config/model-roles.d.ts +28 -0
  20. package/dist/types/config/models-config-schema.d.ts +523 -42
  21. package/dist/types/config/models-config.d.ts +385 -0
  22. package/dist/types/config/settings-schema.d.ts +41 -8
  23. package/dist/types/config/settings.d.ts +8 -1
  24. package/dist/types/debug/log-viewer.d.ts +1 -1
  25. package/dist/types/debug/raw-sse.d.ts +1 -1
  26. package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
  27. package/dist/types/eval/backend.d.ts +0 -2
  28. package/dist/types/eval/idle-timeout.d.ts +0 -4
  29. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  30. package/dist/types/eval/py/executor.d.ts +5 -0
  31. package/dist/types/eval/py/kernel.d.ts +6 -1
  32. package/dist/types/eval/py/runtime.d.ts +9 -0
  33. package/dist/types/exec/bash-executor.d.ts +2 -0
  34. package/dist/types/export/html/template.generated.d.ts +1 -1
  35. package/dist/types/extensibility/extensions/runner.d.ts +3 -2
  36. package/dist/types/extensibility/extensions/types.d.ts +6 -3
  37. package/dist/types/hindsight/mental-models.d.ts +17 -8
  38. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  39. package/dist/types/internal-urls/types.d.ts +1 -1
  40. package/dist/types/lsp/edits.d.ts +9 -0
  41. package/dist/types/lsp/index.d.ts +2 -2
  42. package/dist/types/lsp/types.d.ts +2 -0
  43. package/dist/types/lsp/utils.d.ts +3 -0
  44. package/dist/types/mcp/json-rpc.d.ts +5 -0
  45. package/dist/types/memory-backend/index.d.ts +1 -0
  46. package/dist/types/memory-backend/runtime.d.ts +4 -0
  47. package/dist/types/memory-backend/types.d.ts +66 -1
  48. package/dist/types/mnemopi/state.d.ts +11 -1
  49. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  50. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  51. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  52. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  53. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  54. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  55. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  56. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  57. package/dist/types/modes/components/footer.d.ts +1 -1
  58. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  59. package/dist/types/modes/components/hook-input.d.ts +4 -0
  60. package/dist/types/modes/components/hook-selector.d.ts +1 -1
  61. package/dist/types/modes/components/model-selector.d.ts +1 -1
  62. package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
  63. package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
  64. package/dist/types/modes/components/session-selector.d.ts +1 -1
  65. package/dist/types/modes/components/status-line/component.d.ts +1 -1
  66. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  67. package/dist/types/modes/components/transcript-container.d.ts +25 -6
  68. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  69. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  70. package/dist/types/modes/components/user-message.d.ts +2 -1
  71. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  72. package/dist/types/modes/components/welcome.d.ts +19 -3
  73. package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
  74. package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
  75. package/dist/types/modes/index.d.ts +3 -3
  76. package/dist/types/modes/interactive-mode.d.ts +8 -3
  77. package/dist/types/modes/oauth-manual-input.d.ts +7 -0
  78. package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
  79. package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
  80. package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
  81. package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
  82. package/dist/types/modes/setup-wizard/index.d.ts +5 -1
  83. package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
  84. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  85. package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
  86. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  87. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  88. package/dist/types/modes/types.d.ts +4 -1
  89. package/dist/types/secrets/index.d.ts +1 -1
  90. package/dist/types/secrets/obfuscator.d.ts +8 -2
  91. package/dist/types/session/agent-session.d.ts +15 -3
  92. package/dist/types/session/auth-broker-config.d.ts +4 -0
  93. package/dist/types/session/session-manager.d.ts +1 -1
  94. package/dist/types/session/streaming-output.d.ts +23 -0
  95. package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
  96. package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
  97. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  98. package/dist/types/slash-commands/types.d.ts +1 -1
  99. package/dist/types/ssh/connection-manager.d.ts +8 -0
  100. package/dist/types/system-prompt.d.ts +2 -0
  101. package/dist/types/task/executor.d.ts +1 -0
  102. package/dist/types/task/index.d.ts +2 -2
  103. package/dist/types/task/parallel.d.ts +2 -2
  104. package/dist/types/task/types.d.ts +8 -0
  105. package/dist/types/task/worktree.d.ts +2 -0
  106. package/dist/types/thinking.d.ts +4 -0
  107. package/dist/types/tiny/title-client.d.ts +11 -0
  108. package/dist/types/tiny/title-protocol.d.ts +1 -0
  109. package/dist/types/tools/ask.d.ts +4 -0
  110. package/dist/types/tools/conflict-detect.d.ts +16 -0
  111. package/dist/types/tools/github-cache.d.ts +7 -0
  112. package/dist/types/tools/index.d.ts +6 -0
  113. package/dist/types/tools/sqlite-reader.d.ts +3 -0
  114. package/dist/types/tui/output-block.d.ts +3 -3
  115. package/dist/types/utils/changelog.d.ts +8 -0
  116. package/dist/types/utils/git.d.ts +15 -2
  117. package/dist/types/utils/title-generator.d.ts +3 -2
  118. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  119. package/dist/types/web/scrapers/types.d.ts +12 -0
  120. package/dist/types/web/search/providers/codex.d.ts +1 -1
  121. package/dist/types/web/search/providers/gemini.d.ts +1 -1
  122. package/examples/extensions/tools.ts +5 -4
  123. package/package.json +14 -11
  124. package/scripts/build-binary.ts +18 -23
  125. package/scripts/bundle-dist.ts +81 -0
  126. package/scripts/{dev-launch → omp} +1 -1
  127. package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
  128. package/src/async/job-manager.ts +57 -3
  129. package/src/auto-thinking/classifier.ts +1 -0
  130. package/src/autoresearch/dashboard.ts +1 -1
  131. package/src/autoresearch/prompt-setup.md +6 -6
  132. package/src/autoresearch/prompt.md +6 -6
  133. package/src/capability/fs.ts +10 -0
  134. package/src/cli/args.ts +4 -1
  135. package/src/cli/auth-gateway-cli.ts +1 -3
  136. package/src/cli/dry-balance-cli.ts +1 -1
  137. package/src/cli/gallery-cli.ts +1 -1
  138. package/src/cli/gallery-fixtures/fs.ts +1 -1
  139. package/src/cli/gallery-fixtures/types.ts +5 -1
  140. package/src/cli/list-models.ts +2 -1
  141. package/src/cli/usage-cli.ts +603 -0
  142. package/src/cli-commands.ts +30 -0
  143. package/src/cli.ts +76 -13
  144. package/src/commands/complete.ts +1 -1
  145. package/src/commands/launch.ts +5 -1
  146. package/src/commands/read.ts +6 -3
  147. package/src/commands/usage.ts +35 -0
  148. package/src/commit/agentic/agent.ts +1 -1
  149. package/src/commit/model-selection.ts +4 -3
  150. package/src/config/api-key-resolver.ts +8 -6
  151. package/src/config/append-only-context-mode.ts +6 -12
  152. package/src/config/model-discovery.ts +554 -0
  153. package/src/config/model-registry.ts +320 -1041
  154. package/src/config/model-resolver.ts +173 -156
  155. package/src/config/model-roles.ts +74 -0
  156. package/src/config/models-config-schema.ts +57 -8
  157. package/src/config/models-config.ts +129 -0
  158. package/src/config/settings-schema.ts +61 -19
  159. package/src/config/settings.ts +98 -4
  160. package/src/dap/client.ts +124 -37
  161. package/src/dap/session.ts +259 -158
  162. package/src/debug/log-viewer.ts +1 -1
  163. package/src/debug/raw-sse.ts +1 -1
  164. package/src/edit/diff.ts +47 -3
  165. package/src/edit/hashline/block-resolver.ts +20 -1
  166. package/src/edit/hashline/diff.ts +36 -1
  167. package/src/edit/hashline/execute.ts +47 -4
  168. package/src/edit/hashline/noop-loop-guard.ts +99 -0
  169. package/src/edit/index.ts +16 -1
  170. package/src/edit/modes/patch.ts +52 -0
  171. package/src/edit/modes/replace.ts +56 -22
  172. package/src/edit/notebook.ts +22 -2
  173. package/src/edit/renderer.ts +36 -10
  174. package/src/eval/__tests__/completion-bridge.test.ts +1 -1
  175. package/src/eval/backend.ts +0 -2
  176. package/src/eval/completion-bridge.ts +3 -1
  177. package/src/eval/idle-timeout.ts +2 -9
  178. package/src/eval/js/context-manager.ts +6 -8
  179. package/src/eval/js/executor.ts +6 -2
  180. package/src/eval/js/index.ts +0 -2
  181. package/src/eval/js/shared/helpers.ts +5 -6
  182. package/src/eval/js/shared/local-module-loader.ts +1 -1
  183. package/src/eval/js/shared/prelude.txt +62 -1
  184. package/src/eval/js/shared/rewrite-imports.ts +40 -22
  185. package/src/eval/js/shared/runtime.ts +1 -1
  186. package/src/eval/py/executor.ts +29 -7
  187. package/src/eval/py/index.ts +6 -3
  188. package/src/eval/py/kernel.ts +43 -4
  189. package/src/eval/py/runner.py +107 -3
  190. package/src/eval/py/runtime.ts +37 -0
  191. package/src/exec/bash-executor.ts +85 -4
  192. package/src/export/html/template.generated.ts +1 -1
  193. package/src/export/html/template.js +3 -1
  194. package/src/extensibility/extensions/get-commands-handler.ts +2 -1
  195. package/src/extensibility/extensions/runner.ts +6 -1
  196. package/src/extensibility/extensions/types.ts +6 -2
  197. package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
  198. package/src/hindsight/bank.ts +17 -2
  199. package/src/hindsight/mental-models.ts +59 -12
  200. package/src/hindsight/state.ts +6 -1
  201. package/src/internal-urls/artifact-protocol.ts +11 -2
  202. package/src/internal-urls/docs-index.generated.ts +11 -11
  203. package/src/internal-urls/issue-pr-protocol.ts +12 -5
  204. package/src/internal-urls/router.ts +1 -1
  205. package/src/internal-urls/types.ts +1 -1
  206. package/src/lib/xai-http.ts +1 -1
  207. package/src/lsp/client.ts +118 -38
  208. package/src/lsp/clients/biome-client.ts +101 -39
  209. package/src/lsp/edits.ts +143 -95
  210. package/src/lsp/index.ts +31 -22
  211. package/src/lsp/render.ts +1 -1
  212. package/src/lsp/types.ts +2 -0
  213. package/src/lsp/utils.ts +28 -10
  214. package/src/main.ts +183 -23
  215. package/src/mcp/json-rpc.ts +35 -5
  216. package/src/mcp/transports/stdio.ts +7 -1
  217. package/src/memories/index.ts +4 -1
  218. package/src/memory-backend/index.ts +1 -0
  219. package/src/memory-backend/local-backend.ts +9 -0
  220. package/src/memory-backend/off-backend.ts +9 -0
  221. package/src/memory-backend/runtime.ts +66 -0
  222. package/src/memory-backend/types.ts +81 -1
  223. package/src/mnemopi/backend.ts +176 -7
  224. package/src/mnemopi/state.ts +38 -2
  225. package/src/modes/acp/acp-agent.ts +119 -11
  226. package/src/modes/components/agent-dashboard.ts +10 -7
  227. package/src/modes/components/assistant-message.ts +32 -28
  228. package/src/modes/components/bash-execution.ts +1 -1
  229. package/src/modes/components/copy-selector.ts +1 -1
  230. package/src/modes/components/diff.ts +13 -2
  231. package/src/modes/components/dynamic-border.ts +12 -3
  232. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  233. package/src/modes/components/extensions/extension-list.ts +1 -1
  234. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  235. package/src/modes/components/footer.ts +4 -2
  236. package/src/modes/components/history-search.ts +1 -1
  237. package/src/modes/components/hook-editor.ts +8 -0
  238. package/src/modes/components/hook-input.ts +8 -0
  239. package/src/modes/components/hook-selector.ts +2 -2
  240. package/src/modes/components/model-selector.ts +4 -2
  241. package/src/modes/components/plan-review-overlay.ts +1 -1
  242. package/src/modes/components/session-observer-overlay.ts +2 -2
  243. package/src/modes/components/session-selector.ts +1 -1
  244. package/src/modes/components/settings-selector.ts +5 -1
  245. package/src/modes/components/status-line/component.ts +119 -35
  246. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  247. package/src/modes/components/transcript-container.ts +258 -53
  248. package/src/modes/components/tree-selector.ts +3 -3
  249. package/src/modes/components/user-message-selector.ts +1 -1
  250. package/src/modes/components/user-message.ts +17 -5
  251. package/src/modes/components/visual-truncate.ts +1 -1
  252. package/src/modes/components/welcome.ts +108 -26
  253. package/src/modes/controllers/command-controller.ts +11 -4
  254. package/src/modes/controllers/event-controller.ts +73 -4
  255. package/src/modes/controllers/input-controller.ts +2 -1
  256. package/src/modes/controllers/mcp-command-controller.ts +39 -4
  257. package/src/modes/controllers/selector-controller.ts +1 -1
  258. package/src/modes/controllers/streaming-reveal.ts +85 -18
  259. package/src/modes/index.ts +3 -21
  260. package/src/modes/interactive-mode.ts +42 -18
  261. package/src/modes/oauth-manual-input.ts +30 -3
  262. package/src/modes/rpc/rpc-client.ts +154 -3
  263. package/src/modes/rpc/rpc-mode.ts +97 -12
  264. package/src/modes/rpc/rpc-subagents.ts +265 -0
  265. package/src/modes/rpc/rpc-types.ts +81 -1
  266. package/src/modes/setup-wizard/index.ts +12 -2
  267. package/src/modes/setup-wizard/lazy.ts +16 -0
  268. package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
  269. package/src/modes/setup-wizard/scenes/providers.ts +1 -1
  270. package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
  271. package/src/modes/setup-wizard/scenes/theme.ts +1 -1
  272. package/src/modes/setup-wizard/scenes/types.ts +1 -1
  273. package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
  274. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  275. package/src/modes/types.ts +4 -1
  276. package/src/prompts/agents/explore.md +2 -2
  277. package/src/prompts/agents/librarian.md +1 -2
  278. package/src/prompts/agents/oracle.md +1 -1
  279. package/src/prompts/agents/plan.md +5 -5
  280. package/src/prompts/agents/task.md +5 -5
  281. package/src/prompts/ci-green-request.md +5 -7
  282. package/src/prompts/goals/goal-budget-limit.md +2 -2
  283. package/src/prompts/goals/goal-continuation.md +4 -4
  284. package/src/prompts/goals/goal-mode-active.md +1 -1
  285. package/src/prompts/memories/read-path.md +1 -1
  286. package/src/prompts/memories/stage_one_system.md +2 -2
  287. package/src/prompts/review-custom-request.md +1 -1
  288. package/src/prompts/system/agent-creation-architect.md +2 -2
  289. package/src/prompts/system/auto-continue.md +1 -1
  290. package/src/prompts/system/background-tan-dispatch.md +1 -1
  291. package/src/prompts/system/btw-user.md +2 -2
  292. package/src/prompts/system/commit-message-system.md +13 -1
  293. package/src/prompts/system/custom-system-prompt.md +1 -1
  294. package/src/prompts/system/eager-todo.md +2 -2
  295. package/src/prompts/system/irc-incoming.md +1 -1
  296. package/src/prompts/system/manual-continue.md +1 -1
  297. package/src/prompts/system/omfg-user.md +3 -4
  298. package/src/prompts/system/orchestrate-notice.md +9 -9
  299. package/src/prompts/system/plan-mode-active.md +4 -4
  300. package/src/prompts/system/plan-mode-subagent.md +4 -5
  301. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  302. package/src/prompts/system/project-prompt.md +2 -2
  303. package/src/prompts/system/subagent-system-prompt.md +4 -4
  304. package/src/prompts/system/system-prompt.md +13 -24
  305. package/src/prompts/system/title-system.md +2 -2
  306. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  307. package/src/prompts/system/workflow-notice.md +1 -1
  308. package/src/prompts/tools/ast-edit.md +1 -1
  309. package/src/prompts/tools/ast-grep.md +2 -2
  310. package/src/prompts/tools/bash.md +5 -7
  311. package/src/prompts/tools/browser.md +7 -7
  312. package/src/prompts/tools/debug.md +1 -1
  313. package/src/prompts/tools/eval.md +3 -3
  314. package/src/prompts/tools/find.md +0 -1
  315. package/src/prompts/tools/github.md +8 -7
  316. package/src/prompts/tools/goal.md +1 -1
  317. package/src/prompts/tools/image-gen.md +1 -1
  318. package/src/prompts/tools/inspect-image-system.md +1 -1
  319. package/src/prompts/tools/irc.md +15 -15
  320. package/src/prompts/tools/lsp.md +2 -2
  321. package/src/prompts/tools/patch.md +2 -2
  322. package/src/prompts/tools/read.md +3 -4
  323. package/src/prompts/tools/recall.md +1 -1
  324. package/src/prompts/tools/reflect.md +1 -1
  325. package/src/prompts/tools/render-mermaid.md +2 -2
  326. package/src/prompts/tools/replace.md +4 -10
  327. package/src/prompts/tools/rewind.md +2 -2
  328. package/src/prompts/tools/search-tool-bm25.md +1 -9
  329. package/src/prompts/tools/search.md +0 -1
  330. package/src/prompts/tools/ssh.md +0 -4
  331. package/src/prompts/tools/task.md +2 -3
  332. package/src/prompts/tools/todo.md +1 -1
  333. package/src/sdk.ts +31 -11
  334. package/src/secrets/index.ts +8 -1
  335. package/src/secrets/obfuscator.ts +39 -18
  336. package/src/session/agent-session.ts +223 -64
  337. package/src/session/auth-broker-config.ts +30 -1
  338. package/src/session/session-manager.ts +2 -2
  339. package/src/session/streaming-output.ts +188 -11
  340. package/src/slash-commands/acp-builtins.ts +24 -0
  341. package/src/slash-commands/builtin-registry.ts +40 -0
  342. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  343. package/src/slash-commands/types.ts +1 -1
  344. package/src/ssh/connection-manager.ts +27 -0
  345. package/src/system-prompt.ts +14 -0
  346. package/src/task/commands.ts +2 -1
  347. package/src/task/executor.ts +74 -65
  348. package/src/task/index.ts +146 -68
  349. package/src/task/parallel.ts +3 -3
  350. package/src/task/render.ts +20 -5
  351. package/src/task/types.ts +9 -0
  352. package/src/task/worktree.ts +64 -56
  353. package/src/thinking.ts +9 -1
  354. package/src/tiny/title-client.ts +60 -16
  355. package/src/tiny/title-protocol.ts +1 -1
  356. package/src/tiny/worker.ts +6 -4
  357. package/src/tools/archive-reader.ts +30 -2
  358. package/src/tools/ask.ts +104 -21
  359. package/src/tools/ast-edit.ts +25 -5
  360. package/src/tools/auto-generated-guard.ts +20 -3
  361. package/src/tools/bash-interactive.ts +27 -7
  362. package/src/tools/bash.ts +100 -18
  363. package/src/tools/browser/launch.ts +11 -2
  364. package/src/tools/browser/readable.ts +19 -2
  365. package/src/tools/browser/registry.ts +4 -1
  366. package/src/tools/browser/render.ts +2 -2
  367. package/src/tools/browser/tab-supervisor.ts +55 -16
  368. package/src/tools/conflict-detect.ts +50 -4
  369. package/src/tools/debug.ts +1 -1
  370. package/src/tools/eval-render.ts +5 -5
  371. package/src/tools/eval.ts +0 -2
  372. package/src/tools/fetch.ts +33 -10
  373. package/src/tools/gh-cache-invalidation.ts +63 -8
  374. package/src/tools/gh-renderer.ts +1 -1
  375. package/src/tools/gh.ts +172 -29
  376. package/src/tools/github-cache.ts +70 -6
  377. package/src/tools/image-gen.ts +14 -13
  378. package/src/tools/index.ts +13 -1
  379. package/src/tools/inspect-image.ts +1 -0
  380. package/src/tools/irc.ts +5 -1
  381. package/src/tools/job.ts +1 -1
  382. package/src/tools/read.ts +202 -61
  383. package/src/tools/render-utils.ts +3 -3
  384. package/src/tools/resolve.ts +1 -1
  385. package/src/tools/search.ts +92 -29
  386. package/src/tools/sqlite-reader.ts +17 -5
  387. package/src/tools/ssh.ts +8 -8
  388. package/src/tools/todo.ts +38 -8
  389. package/src/tools/write.ts +118 -18
  390. package/src/tui/output-block.ts +4 -4
  391. package/src/utils/changelog.ts +27 -1
  392. package/src/utils/commit-message-generator.ts +1 -0
  393. package/src/utils/file-mentions.ts +2 -1
  394. package/src/utils/git.ts +267 -13
  395. package/src/utils/title-generator.ts +24 -5
  396. package/src/web/scrapers/arxiv.ts +1 -1
  397. package/src/web/scrapers/go-pkg.ts +1 -1
  398. package/src/web/scrapers/iacr.ts +1 -1
  399. package/src/web/scrapers/readthedocs.ts +1 -1
  400. package/src/web/scrapers/twitter.ts +2 -1
  401. package/src/web/scrapers/types.ts +87 -8
  402. package/src/web/scrapers/wikipedia.ts +1 -1
  403. package/src/web/scrapers/youtube.ts +6 -1
  404. package/src/web/search/index.ts +1 -1
  405. package/src/web/search/providers/codex.ts +2 -1
  406. package/src/web/search/providers/gemini.ts +2 -3
  407. package/src/web/search/render.ts +8 -6
  408. package/dist/types/config/model-equivalence.d.ts +0 -24
  409. package/dist/types/config/model-id-affixes.d.ts +0 -12
  410. package/dist/types/config/model-provider-priority.d.ts +0 -1
  411. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  412. package/src/config/model-equivalence.ts +0 -875
  413. package/src/config/model-id-affixes.ts +0 -81
  414. package/src/config/model-provider-priority.ts +0 -56
  415. package/src/exec/idle-timeout-watchdog.ts +0 -126
@@ -20,8 +20,6 @@ export interface ExecutorBackendExecOptions {
20
20
  */
21
21
  idleTimeoutMs: number;
22
22
  reset: boolean;
23
- artifactPath: string | undefined;
24
- artifactId: string | undefined;
25
23
  onChunk: (chunk: string) => void;
26
24
  /**
27
25
  * Live status events (read/write/agent/…) delivered as they are emitted,
@@ -12,7 +12,8 @@
12
12
  * in, text (or, with `schema`, a structured object) out.
13
13
  */
14
14
  import { instrumentedCompleteSimple, resolveTelemetry } from "@oh-my-pi/pi-agent-core";
15
- import { type Api, Effort, getSupportedEfforts, type Model, type Tool } from "@oh-my-pi/pi-ai";
15
+ import { type Api, Effort, type Model, type Tool } from "@oh-my-pi/pi-ai";
16
+ import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
16
17
  import * as z from "zod/v4";
17
18
  import { extractTextContent, extractToolCall, parseJsonPayload } from "../commit/utils";
18
19
 
@@ -162,6 +163,7 @@ export async function runEvalCompletion(
162
163
  apiKey: registry.resolver(model.provider, {
163
164
  sessionId: options.session.getSessionId?.() ?? undefined,
164
165
  baseUrl: model.baseUrl,
166
+ modelId: model.id,
165
167
  }),
166
168
  signal: options.signal,
167
169
  reasoning: reasoningForTier(tier, model),
@@ -6,8 +6,6 @@
6
6
  * `agent()`/`parallel()`/`completion()` work is ignored completely, then {@link resume}
7
7
  * starts a fresh timeout window once the runtime gets control back.
8
8
  *
9
- * The active timer self-reschedules instead of being torn down on every
10
- * activity event, so frequent activity costs one timestamp write per event.
11
9
  * Pause is reference-counted because `parallel()` can have multiple bridge calls
12
10
  * in flight at once.
13
11
  */
@@ -36,11 +34,6 @@ export class IdleTimeout {
36
34
  return this.#idleMs;
37
35
  }
38
36
 
39
- /** Record runtime activity, pushing the active deadline forward by `idleMs`. */
40
- bump(): void {
41
- if (this.#settled || this.#pauseDepth > 0) return;
42
- this.#deadlineMs = Date.now() + this.#idleMs;
43
- }
44
37
  /** Suspend timeout accounting while control is delegated to host-side work. */
45
38
  pause(): void {
46
39
  if (this.#settled) return;
@@ -86,8 +79,8 @@ export class IdleTimeout {
86
79
  if (this.#settled || this.#pauseDepth > 0) return;
87
80
  const remainingMs = this.#deadlineMs - Date.now();
88
81
  if (remainingMs > 0) {
89
- // A bump moved the deadline forward after this timer was armed; wait
90
- // out the remaining window instead of firing early.
82
+ // The deadline moved forward (resume re-arming) after this timer was
83
+ // armed; wait out the remaining window instead of firing early.
91
84
  this.#arm(remainingMs);
92
85
  return;
93
86
  }
@@ -1,13 +1,10 @@
1
- import { isCompiledBinary, logger, Snowflake } from "@oh-my-pi/pi-utils";
1
+ import { logger, Snowflake, workerHostEntry } from "@oh-my-pi/pi-utils";
2
2
  import type { ToolSession } from "../../tools";
3
3
  import { ToolAbortError, ToolError } from "../../tools/tool-errors";
4
4
  import { callSessionTool, type JsStatusEvent } from "./tool-bridge";
5
5
  import { WorkerCore } from "./worker-core";
6
- // Worker entry. See `tab-supervisor.ts` for the rationale behind the
7
- // literal-string + `new URL(import.meta.url)` hybrid: the literal is what
8
- // Bun's `--compile` bundler discovers, the `new URL` form is what makes dev
9
- // runs portable across cwds. The worker is registered as an additional
10
- // `--compile` entrypoint in `scripts/build-binary.ts`.
6
+ // Coding-agent binary/bundle workers route through the CLI entrypoint with a
7
+ // hidden argv mode, so compiled/npm builds only need one JavaScript entry.
11
8
  import type {
12
9
  JsDisplayOutput,
13
10
  RunErrorPayload,
@@ -384,8 +381,9 @@ async function raceWithTimeout<T>(promise: Promise<T>, timeoutMs: number, reason
384
381
 
385
382
  async function spawnJsWorker(): Promise<WorkerHandle> {
386
383
  try {
387
- const worker = isCompiledBinary()
388
- ? new Worker("./packages/coding-agent/src/eval/js/worker-entry.ts", { type: "module" })
384
+ const hostEntry = workerHostEntry();
385
+ const worker = hostEntry
386
+ ? new Worker(hostEntry, { type: "module", argv: ["__omp_js_eval_worker"] })
389
387
  : new Worker(new URL("./worker-entry.ts", import.meta.url).href, { type: "module" });
390
388
  return wrapBunWorker(worker);
391
389
  } catch (err) {
@@ -63,9 +63,13 @@ function isTimeoutReason(reason: unknown): boolean {
63
63
  }
64
64
 
65
65
  function formatJsTimeoutAnnotation(timeoutMs: number | undefined): string {
66
- if (timeoutMs === undefined) return "Command timed out";
66
+ // Timeout cancellation force-kills the worker (the only way to interrupt
67
+ // synchronous user code), which discards the persistent VM state. Say so,
68
+ // or the model will keep referencing variables that no longer exist.
69
+ const reset = "The JS worker was force-killed and its VM state was reset; variables from earlier cells are gone.";
70
+ if (timeoutMs === undefined) return `Command timed out. ${reset}`;
67
71
  const secs = Math.max(1, Math.round(timeoutMs / 1000));
68
- return `Command timed out after ${secs} seconds`;
72
+ return `Command timed out after ${secs} seconds. ${reset}`;
69
73
  }
70
74
 
71
75
  export async function executeJs(code: string, options: JsExecutorOptions): Promise<JsResult> {
@@ -30,8 +30,6 @@ export default {
30
30
  sessionId: namespaceSessionId(opts.sessionId),
31
31
  sessionFile: opts.sessionFile,
32
32
  reset: opts.reset,
33
- artifactPath: opts.artifactPath,
34
- artifactId: opts.artifactId,
35
33
  onChunk: opts.onChunk,
36
34
  onStatus: opts.onStatus,
37
35
  session: opts.session,
@@ -83,12 +83,11 @@ export function createHelpers(ctx: HelperContext): HelperBundle {
83
83
  },
84
84
  append: async (rawPath, content) => {
85
85
  const target = resolveHelperPath(ctx, rawPath, "write");
86
- await Bun.write(
87
- target,
88
- `${await Bun.file(target)
89
- .text()
90
- .catch(() => "")}${content}`,
91
- );
86
+ // O(1) append; read-all+rewrite both raced concurrent writers and went
87
+ // quadratic when called in a loop. Bun.write creates parent dirs, so
88
+ // keep that behavior for the append path too.
89
+ await fs.promises.mkdir(path.dirname(target), { recursive: true });
90
+ await fs.promises.appendFile(target, content, "utf-8");
92
91
  ctx.emitStatus({
93
92
  op: "append",
94
93
  path: target,
@@ -102,7 +102,7 @@ export class LocalModuleLoader {
102
102
  });
103
103
  const moduleDir = path.dirname(modulePath);
104
104
  const localDeps = new Set<string>();
105
- for (const specifier of collectModuleSourceSpecifiers(stripped)) {
105
+ for (const specifier of await collectModuleSourceSpecifiers(stripped)) {
106
106
  const resolved = resolveImportSpecifier(moduleDir, specifier);
107
107
  if (isLocalPathSpecifier(specifier) && isManagedLocalModulePath(resolved)) {
108
108
  localDeps.add(resolved);
@@ -90,15 +90,25 @@ if (!globalThis.__omp_js_prelude_loaded__) {
90
90
  const limit = await __concurrencyLimit();
91
91
  const concurrency = limit > 0 ? Math.min(limit, list.length) : list.length;
92
92
  const results = new Array(list.length);
93
+ // Barrier semantics (mirrors the Python _pool_map): every item settles
94
+ // before we return or throw, then the lowest-index error propagates.
95
+ // Early-rejecting would orphan in-flight thunks (e.g. live agent()
96
+ // subagents) whose worker-side promises would never be observed.
97
+ const errors = new Map();
93
98
  let next = 0;
94
99
  const worker = async () => {
95
100
  while (true) {
96
101
  const index = next++;
97
102
  if (index >= list.length) return;
98
- results[index] = await fn(list[index], index);
103
+ try {
104
+ results[index] = await fn(list[index], index);
105
+ } catch (error) {
106
+ errors.set(index, error);
107
+ }
99
108
  }
100
109
  };
101
110
  await Promise.all(Array.from({ length: concurrency }, () => worker()));
111
+ if (errors.size > 0) throw errors.get(Math.min(...errors.keys()));
102
112
  return results;
103
113
  };
104
114
 
@@ -148,6 +158,8 @@ if (!globalThis.__omp_js_prelude_loaded__) {
148
158
 
149
159
  const formatArgs = args => args.map(arg => (typeof arg === "string" ? arg : arg));
150
160
 
161
+ const consoleTimers = new Map();
162
+ const consoleCounts = new Map();
151
163
  const consoleBridge = {
152
164
  log: (...args) => globalThis.__omp_log__("log", ...formatArgs(args)),
153
165
  info: (...args) => globalThis.__omp_log__("info", ...formatArgs(args)),
@@ -158,6 +170,55 @@ if (!globalThis.__omp_js_prelude_loaded__) {
158
170
  columns === undefined
159
171
  ? globalThis.__omp_table__(data)
160
172
  : globalThis.__omp_table__(data, columns),
173
+ dir: (value, _options) => globalThis.__omp_log__("log", value),
174
+ dirxml: (...args) => globalThis.__omp_log__("log", ...formatArgs(args)),
175
+ trace: (...args) => {
176
+ const stack = (new Error().stack ?? "").split("\n").slice(2).join("\n");
177
+ globalThis.__omp_log__("log", args.length > 0 ? `Trace: ${formatArgs(args).join(" ")}` : "Trace", `\n${stack}`);
178
+ },
179
+ assert: (condition, ...args) => {
180
+ if (condition) return;
181
+ if (args.length > 0) globalThis.__omp_log__("error", "Assertion failed:", ...formatArgs(args));
182
+ else globalThis.__omp_log__("error", "Assertion failed");
183
+ },
184
+ group: (...args) => {
185
+ if (args.length > 0) globalThis.__omp_log__("log", ...formatArgs(args));
186
+ },
187
+ groupCollapsed: (...args) => {
188
+ if (args.length > 0) globalThis.__omp_log__("log", ...formatArgs(args));
189
+ },
190
+ groupEnd: () => {},
191
+ time: label => {
192
+ consoleTimers.set(String(label ?? "default"), Date.now());
193
+ },
194
+ timeLog: (label, ...args) => {
195
+ const key = String(label ?? "default");
196
+ const start = consoleTimers.get(key);
197
+ if (start === undefined) {
198
+ globalThis.__omp_log__("warn", `Timer '${key}' does not exist`);
199
+ return;
200
+ }
201
+ globalThis.__omp_log__("log", `${key}: ${Date.now() - start}ms`, ...formatArgs(args));
202
+ },
203
+ timeEnd: label => {
204
+ const key = String(label ?? "default");
205
+ const start = consoleTimers.get(key);
206
+ if (start === undefined) {
207
+ globalThis.__omp_log__("warn", `Timer '${key}' does not exist`);
208
+ return;
209
+ }
210
+ consoleTimers.delete(key);
211
+ globalThis.__omp_log__("log", `${key}: ${Date.now() - start}ms`);
212
+ },
213
+ count: label => {
214
+ const key = String(label ?? "default");
215
+ const next = (consoleCounts.get(key) ?? 0) + 1;
216
+ consoleCounts.set(key, next);
217
+ globalThis.__omp_log__("log", `${key}: ${next}`);
218
+ },
219
+ countReset: label => {
220
+ consoleCounts.delete(String(label ?? "default"));
221
+ },
161
222
  };
162
223
 
163
224
  globalThis.console = consoleBridge;
@@ -1,4 +1,4 @@
1
- import { parse as babelParse } from "@babel/parser";
1
+ import type * as BabelParser from "@babel/parser";
2
2
 
3
3
  // Static ESM `import` declarations are not valid inside vm.runInContext (script-mode parsing),
4
4
  // and dynamic `import(...)` would otherwise resolve specifiers against the worker module's URL
@@ -64,9 +64,22 @@ type BabelModuleSourceDeclaration = {
64
64
 
65
65
  type BabelNode = { type: string; start: number; end: number; [key: string]: unknown };
66
66
 
67
- function parseProgram(code: string): { program: { body: ReadonlyArray<BabelProgramNode> } } | null {
67
+ // @babel/parser sits on the CLI launch graph (tools eval backend → worker-core →
68
+ // runtime → this module) but only runs when an eval cell executes, so it is loaded
69
+ // lazily and memoized.
70
+ let babelParser: typeof BabelParser | undefined;
71
+
72
+ async function loadBabelParser(): Promise<typeof BabelParser> {
73
+ if (!babelParser) {
74
+ babelParser = await import("@babel/parser");
75
+ }
76
+ return babelParser;
77
+ }
78
+
79
+ async function parseProgram(code: string): Promise<{ program: { body: ReadonlyArray<BabelProgramNode> } } | null> {
80
+ const { parse } = await loadBabelParser();
68
81
  try {
69
- return babelParse(code, {
82
+ return parse(code, {
70
83
  sourceType: "module",
71
84
  allowAwaitOutsideFunction: true,
72
85
  allowReturnOutsideFunction: true,
@@ -162,10 +175,10 @@ function rewriteImportNode(node: BabelImportDeclaration): string {
162
175
  return `await ${importCall};`;
163
176
  }
164
177
 
165
- export function rewriteImports(code: string): string {
178
+ export async function rewriteImports(code: string): Promise<string> {
166
179
  if (!code.includes("import")) return code;
167
180
 
168
- const ast = parseProgram(code);
181
+ const ast = await parseProgram(code);
169
182
  if (!ast) {
170
183
  // Parser bailed entirely — let the VM surface the real syntax error.
171
184
  return code;
@@ -201,8 +214,8 @@ export function rewriteImports(code: string): string {
201
214
  }
202
215
  return result;
203
216
  }
204
- export function collectModuleSourceSpecifiers(code: string): string[] {
205
- const ast = parseProgram(code);
217
+ export async function collectModuleSourceSpecifiers(code: string): Promise<string[]> {
218
+ const ast = await parseProgram(code);
206
219
  if (!ast) return [];
207
220
  const sources: string[] = [];
208
221
  for (const node of ast.program.body) {
@@ -218,8 +231,11 @@ export function collectModuleSourceSpecifiers(code: string): string[] {
218
231
  return sources;
219
232
  }
220
233
 
221
- export function rewriteModuleSourceSpecifiers(code: string, replacer: (source: string) => string): string {
222
- const ast = parseProgram(code);
234
+ export async function rewriteModuleSourceSpecifiers(
235
+ code: string,
236
+ replacer: (source: string) => string,
237
+ ): Promise<string> {
238
+ const ast = await parseProgram(code);
223
239
  if (!ast) return code;
224
240
 
225
241
  type Edit = { start: number; end: number; text: string };
@@ -249,9 +265,9 @@ export function rewriteModuleSourceSpecifiers(code: string, replacer: (source: s
249
265
  return result;
250
266
  }
251
267
 
252
- export function rewriteDynamicImports(code: string, callee = "__omp_import__"): string {
268
+ export async function rewriteDynamicImports(code: string, callee = "__omp_import__"): Promise<string> {
253
269
  if (!code.includes("import")) return code;
254
- const ast = parseProgram(code);
270
+ const ast = await parseProgram(code);
255
271
  if (!ast) return code;
256
272
 
257
273
  type Edit = { start: number; end: number; text: string };
@@ -339,10 +355,10 @@ function appendGlobalBindingPublish(source: string, names: readonly string[]): s
339
355
  * Nested declarations (inside functions, blocks, classes) are left alone \u2014 they're
340
356
  * scoped to their enclosing function/block regardless of `var` vs `let`/`const`.
341
357
  */
342
- function demoteTopLevelLexicals(code: string, options: { publishGlobals?: boolean } = {}): string {
358
+ async function demoteTopLevelLexicals(code: string, options: { publishGlobals?: boolean } = {}): Promise<string> {
343
359
  if (!/\b(?:const|let|class)\b/.test(code)) return code;
344
360
 
345
- const ast = parseProgram(code);
361
+ const ast = await parseProgram(code);
346
362
  if (!ast) {
347
363
  return code;
348
364
  }
@@ -381,8 +397,8 @@ function demoteTopLevelLexicals(code: string, options: { publishGlobals?: boolea
381
397
  return result;
382
398
  }
383
399
 
384
- function returnFinalExpression(code: string): { source: string; returned: boolean } {
385
- const ast = parseProgram(code);
400
+ async function returnFinalExpression(code: string): Promise<{ source: string; returned: boolean }> {
401
+ const ast = await parseProgram(code);
386
402
  const body = ast?.program.body;
387
403
  if (!body) return { source: code, returned: false };
388
404
  let lastIndex = body.length - 1;
@@ -446,8 +462,8 @@ function containsAsyncWrapperSyntax(value: unknown): boolean {
446
462
  return false;
447
463
  }
448
464
 
449
- function requiresAsyncWrapper(code: string): boolean {
450
- const ast = parseProgram(code);
465
+ async function requiresAsyncWrapper(code: string): Promise<boolean> {
466
+ const ast = await parseProgram(code);
451
467
  if (!ast) return false;
452
468
  for (const node of ast.program.body) {
453
469
  if (containsAsyncWrapperSyntax(node)) return true;
@@ -494,13 +510,15 @@ export function stripTypeScriptSyntax(
494
510
  const LOOKS_LIKE_TS =
495
511
  /(?:\bimport\s+type\b|\bexport\s+type\b|\b(?:import|export)\s*\{[^}\n]*\btype\s+\w|\binterface\s+\w|\btype\s+\w+\s*=|\b(?:as|satisfies)\s+(?:[A-Z]|\bconst\b)|:\s*(?:string|number|boolean|any|unknown|void|never|object|[A-Z]\w*)\b|<\s*[A-Z]\w*\s*[,>])/;
496
512
 
497
- export function wrapCode(code: string): { source: string; asyncWrapped: boolean; finalExpressionReturned: boolean } {
498
- const finalExpression = returnFinalExpression(code);
513
+ export async function wrapCode(
514
+ code: string,
515
+ ): Promise<{ source: string; asyncWrapped: boolean; finalExpressionReturned: boolean }> {
516
+ const finalExpression = await returnFinalExpression(code);
499
517
  const stripped = stripTypeScript(finalExpression.source);
500
- const importsRewritten = rewriteImports(stripped);
501
- const needsAsyncWrapper = requiresAsyncWrapper(importsRewritten);
518
+ const importsRewritten = await rewriteImports(stripped);
519
+ const needsAsyncWrapper = await requiresAsyncWrapper(importsRewritten);
502
520
  const rewritten = {
503
- source: demoteTopLevelLexicals(importsRewritten, { publishGlobals: needsAsyncWrapper }),
521
+ source: await demoteTopLevelLexicals(importsRewritten, { publishGlobals: needsAsyncWrapper }),
504
522
  returned: finalExpression.returned,
505
523
  };
506
524
  if (!needsAsyncWrapper) {
@@ -181,7 +181,7 @@ export class JsRuntime {
181
181
  finalExpressionValue: undefined,
182
182
  };
183
183
  return await this.#als.run(context, async () => {
184
- const wrapped = wrapCode(code);
184
+ const wrapped = await wrapCode(code);
185
185
  const value = indirectEval(wrapped.source, filename);
186
186
  if (wrapped.finalExpressionReturned) {
187
187
  const awaited = await awaitMaybePromise(value);
@@ -1,3 +1,4 @@
1
+ import * as fs from "node:fs";
1
2
  import * as path from "node:path";
2
3
 
3
4
  import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
@@ -15,6 +16,7 @@ import {
15
16
  type KernelRuntimeEnv,
16
17
  PythonKernel,
17
18
  } from "./kernel";
19
+ import { resolveExplicitPythonRuntime } from "./runtime";
18
20
  import { ensurePyToolBridge, registerPyToolBridge } from "./tool-bridge";
19
21
 
20
22
  export type PythonKernelMode = "session" | "per-call";
@@ -42,6 +44,11 @@ export interface PythonExecutorOptions {
42
44
  kernelOwnerId?: string;
43
45
  /** Kernel mode (session reuse vs per-call) */
44
46
  kernelMode?: PythonKernelMode;
47
+ /**
48
+ * Explicit interpreter path (`python.interpreter` resolved from the
49
+ * session's settings). Skips automatic runtime discovery when set.
50
+ */
51
+ interpreter?: string;
45
52
  /** Restart the kernel before executing */
46
53
  reset?: boolean;
47
54
  /** Session file path for accessing task outputs */
@@ -116,9 +123,9 @@ export interface PythonResult {
116
123
  // ---------------------------------------------------------------------------
117
124
  // Session bookkeeping
118
125
  //
119
- // One PythonKernel subprocess per (session id, cwd) tuple. The runner mutates
120
- // process-global cwd/sys.path during execution, so cross-directory work MUST
121
- // never share a live kernel. Multiple agent owners can still register against
126
+ // One PythonKernel subprocess per (session id, cwd, interpreter) tuple. The
127
+ // runner mutates process-global cwd/sys.path during execution, so cross-directory
128
+ // work must never share a live kernel. Multiple agent owners can still register against
122
129
  // the same tuple; the kernel stays alive until the last owner detaches.
123
130
  // ---------------------------------------------------------------------------
124
131
 
@@ -139,8 +146,19 @@ function normalizeSessionCwd(cwd: string): string {
139
146
  return path.resolve(cwd);
140
147
  }
141
148
 
142
- function buildSessionKey(sessionId: string, cwd: string): string {
143
- return `${sessionId}\0${normalizeSessionCwd(cwd)}`;
149
+ function normalizeExplicitInterpreter(cwd: string, interpreter: string | undefined): string {
150
+ if (interpreter === undefined) return "";
151
+ const resolved = resolveExplicitPythonRuntime(interpreter, cwd, {}).pythonPath;
152
+ try {
153
+ return fs.realpathSync.native(resolved);
154
+ } catch {
155
+ return resolved;
156
+ }
157
+ }
158
+
159
+ function buildSessionKey(sessionId: string, cwd: string, interpreter: string | undefined): string {
160
+ const normalizedCwd = normalizeSessionCwd(cwd);
161
+ return `${sessionId}\0${normalizedCwd}\0${normalizeExplicitInterpreter(normalizedCwd, interpreter)}`;
144
162
  }
145
163
 
146
164
  // ---------------------------------------------------------------------------
@@ -326,6 +344,7 @@ async function startKernel(cwd: string, options: PythonExecutorOptions): Promise
326
344
  env: buildKernelEnv(options),
327
345
  signal: options.signal,
328
346
  deadlineMs: options.deadlineMs,
347
+ interpreter: options.interpreter,
329
348
  });
330
349
  }
331
350
 
@@ -587,7 +606,10 @@ async function executeWithKernel(
587
606
  }
588
607
 
589
608
  async function ensureKernelAvailable(cwd: string, options: PythonExecutorOptions): Promise<void> {
590
- const availability = await waitForPromiseWithCancellation(checkPythonKernelAvailability(cwd), options);
609
+ const availability = await waitForPromiseWithCancellation(
610
+ checkPythonKernelAvailability(cwd, options.interpreter),
611
+ options,
612
+ );
591
613
  if (!availability.ok) {
592
614
  throw new Error(availability.reason ?? "Python kernel unavailable");
593
615
  }
@@ -618,7 +640,7 @@ async function executePerCall(code: string, cwd: string, options: PythonExecutor
618
640
 
619
641
  async function executeOnSession(code: string, cwd: string, options: PythonExecutorOptions): Promise<PythonResult> {
620
642
  const sessionId = options.sessionId ?? `session:${cwd}`;
621
- const sessionKey = buildSessionKey(sessionId, cwd);
643
+ const sessionKey = buildSessionKey(sessionId, cwd, options.interpreter);
622
644
  if (options.bridge && !options.bridgeSessionId) {
623
645
  options.bridgeSessionId = sessionId;
624
646
  }
@@ -19,13 +19,17 @@ function readSetting<T>(session: ToolSession, key: string): T | undefined {
19
19
  return settings?.get?.(key);
20
20
  }
21
21
 
22
+ function readInterpreterSetting(session: ToolSession): string | undefined {
23
+ return readSetting<string>(session, "python.interpreter")?.trim() || undefined;
24
+ }
25
+
22
26
  export default {
23
27
  id: "python",
24
28
  label: "Python",
25
29
  highlightLang: "python",
26
30
 
27
31
  async isAvailable(session: ToolSession): Promise<boolean> {
28
- const availability = await checkPythonKernelAvailability(session.cwd);
32
+ const availability = await checkPythonKernelAvailability(session.cwd, readInterpreterSetting(session));
29
33
  return availability.ok;
30
34
  },
31
35
 
@@ -37,13 +41,12 @@ export default {
37
41
  signal: opts.signal,
38
42
  sessionId: namespaceSessionId(opts.sessionId),
39
43
  kernelMode,
44
+ interpreter: readInterpreterSetting(opts.session),
40
45
  sessionFile: opts.sessionFile,
41
46
  artifactsDir: opts.session.getArtifactsDir?.() ?? undefined,
42
47
  localRoots: resolveEvalUrlRoots(opts.session),
43
48
  kernelOwnerId: opts.kernelOwnerId,
44
49
  reset: opts.reset,
45
- artifactPath: opts.artifactPath,
46
- artifactId: opts.artifactId,
47
50
  onChunk: opts.onChunk,
48
51
  onStatus: opts.onStatus,
49
52
  toolSession: opts.session,
@@ -17,7 +17,13 @@ import { Settings } from "../../config/settings";
17
17
  import { type KernelDisplayOutput, renderKernelDisplay } from "./display";
18
18
  import { PYTHON_PRELUDE } from "./prelude";
19
19
  import RUNNER_SCRIPT from "./runner.py" with { type: "text" };
20
- import { enumeratePythonRuntimes, filterEnv, type PythonRuntime, resolvePythonRuntime } from "./runtime";
20
+ import {
21
+ enumeratePythonRuntimes,
22
+ filterEnv,
23
+ type PythonRuntime,
24
+ resolveExplicitPythonRuntime,
25
+ resolvePythonRuntime,
26
+ } from "./runtime";
21
27
  import { hostHasInheritableConsole, shouldHideKernelWindow } from "./spawn-options";
22
28
 
23
29
  export type { KernelDisplayOutput, PythonStatusEvent } from "./display";
@@ -96,6 +102,11 @@ interface KernelLifecycleOptions {
96
102
  interface KernelStartOptions extends KernelLifecycleOptions {
97
103
  cwd: string;
98
104
  env?: Record<string, string | undefined>;
105
+ /**
106
+ * Explicit interpreter path (`python.interpreter` from the session's
107
+ * settings). When set, runtime discovery is skipped entirely.
108
+ */
109
+ interpreter?: string;
99
110
  }
100
111
 
101
112
  interface KernelShutdownOptions {
@@ -129,15 +140,40 @@ function throwIfAborted(signal: AbortSignal | undefined, fallbackReason: string)
129
140
  throw createAbortError("AbortError", typeof reason === "string" ? reason : fallbackReason);
130
141
  }
131
142
 
132
- export async function checkPythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
143
+ // Cache successful probes per resolved cwd + explicit interpreter: every cell
144
+ // otherwise pays one (or two — backend.isAvailable + ensureKernelAvailable)
145
+ // interpreter spawns even when the kernel is already hot. Failures are not
146
+ // cached so installing a Python mid-session is picked up on the next attempt.
147
+ const availabilityCache = new Map<string, Promise<PythonKernelAvailability>>();
148
+
149
+ export async function checkPythonKernelAvailability(
150
+ cwd: string,
151
+ interpreter?: string,
152
+ ): Promise<PythonKernelAvailability> {
133
153
  if (isBunTestRuntime() || $flag("PI_PYTHON_SKIP_CHECK")) {
134
154
  return { ok: true };
135
155
  }
156
+ const resolvedCwd = path.resolve(cwd);
157
+ const key = `${resolvedCwd}\0${interpreter ?? ""}`;
158
+ const cached = availabilityCache.get(key);
159
+ if (cached) return await cached;
160
+ const probe = probePythonKernelAvailability(resolvedCwd, interpreter);
161
+ availabilityCache.set(key, probe);
162
+ const result = await probe;
163
+ if (!result.ok && availabilityCache.get(key) === probe) {
164
+ availabilityCache.delete(key);
165
+ }
166
+ return result;
167
+ }
168
+
169
+ async function probePythonKernelAvailability(cwd: string, interpreter?: string): Promise<PythonKernelAvailability> {
136
170
  try {
137
171
  const settings = await Settings.init();
138
172
  const { env } = settings.getShellConfig();
139
173
  const baseEnv = filterEnv(env);
140
- const runtimes = enumeratePythonRuntimes(cwd, baseEnv);
174
+ const runtimes = interpreter
175
+ ? [resolveExplicitPythonRuntime(interpreter, cwd, baseEnv)]
176
+ : enumeratePythonRuntimes(cwd, baseEnv);
141
177
  if (runtimes.length === 0) {
142
178
  return { ok: false, reason: "Python executable not found on PATH" };
143
179
  }
@@ -220,6 +256,7 @@ export class PythonKernel {
220
256
  "PythonKernel.start:availabilityCheck",
221
257
  checkPythonKernelAvailability,
222
258
  options.cwd,
259
+ options.interpreter,
223
260
  );
224
261
  if (!availability.ok) {
225
262
  throw new Error(availability.reason ?? "Python kernel unavailable");
@@ -232,7 +269,9 @@ export class PythonKernel {
232
269
  let runtime = availability.runtime;
233
270
  if (!runtime) {
234
271
  const { env: shellEnv } = (await Settings.init()).getShellConfig();
235
- runtime = resolvePythonRuntime(options.cwd, filterEnv(shellEnv));
272
+ runtime = options.interpreter
273
+ ? resolveExplicitPythonRuntime(options.interpreter, options.cwd, filterEnv(shellEnv))
274
+ : resolvePythonRuntime(options.cwd, filterEnv(shellEnv));
236
275
  }
237
276
  const spawnEnv: Record<string, string> = {};
238
277
  for (const [key, value] of Object.entries(runtime.env)) {