@oh-my-pi/pi-coding-agent 15.10.9 → 15.10.11

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 (352) hide show
  1. package/CHANGELOG.md +117 -0
  2. package/dist/cli.js +23087 -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 +1 -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/commands/launch.d.ts +1 -1
  11. package/dist/types/commands/read.d.ts +1 -1
  12. package/dist/types/commands/usage.d.ts +25 -0
  13. package/dist/types/config/append-only-context-mode.d.ts +2 -1
  14. package/dist/types/config/model-discovery.d.ts +55 -0
  15. package/dist/types/config/model-registry.d.ts +20 -219
  16. package/dist/types/config/model-resolver.d.ts +16 -10
  17. package/dist/types/config/model-roles.d.ts +28 -0
  18. package/dist/types/config/models-config-schema.d.ts +523 -42
  19. package/dist/types/config/models-config.d.ts +385 -0
  20. package/dist/types/config/settings-schema.d.ts +12 -16
  21. package/dist/types/config/settings.d.ts +1 -1
  22. package/dist/types/debug/log-viewer.d.ts +1 -1
  23. package/dist/types/debug/raw-sse.d.ts +1 -1
  24. package/dist/types/debug/terminal-info.d.ts +0 -1
  25. package/dist/types/eval/backend.d.ts +0 -2
  26. package/dist/types/eval/idle-timeout.d.ts +0 -4
  27. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  28. package/dist/types/export/html/template.generated.d.ts +1 -1
  29. package/dist/types/extensibility/extensions/types.d.ts +3 -3
  30. package/dist/types/hindsight/mental-models.d.ts +17 -8
  31. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  32. package/dist/types/internal-urls/types.d.ts +1 -1
  33. package/dist/types/lsp/edits.d.ts +9 -0
  34. package/dist/types/lsp/index.d.ts +2 -2
  35. package/dist/types/lsp/types.d.ts +2 -0
  36. package/dist/types/lsp/utils.d.ts +3 -0
  37. package/dist/types/mcp/json-rpc.d.ts +5 -0
  38. package/dist/types/mnemopi/state.d.ts +11 -1
  39. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  40. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  41. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  42. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  43. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  44. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  45. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  46. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  47. package/dist/types/modes/components/footer.d.ts +1 -1
  48. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  49. package/dist/types/modes/components/hook-input.d.ts +4 -0
  50. package/dist/types/modes/components/hook-selector.d.ts +1 -1
  51. package/dist/types/modes/components/model-selector.d.ts +1 -1
  52. package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
  53. package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
  54. package/dist/types/modes/components/session-selector.d.ts +1 -1
  55. package/dist/types/modes/components/status-line/component.d.ts +1 -1
  56. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  57. package/dist/types/modes/components/transcript-container.d.ts +31 -26
  58. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  59. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  60. package/dist/types/modes/components/user-message.d.ts +2 -1
  61. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  62. package/dist/types/modes/components/welcome.d.ts +19 -3
  63. package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
  64. package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
  65. package/dist/types/modes/interactive-mode.d.ts +1 -1
  66. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  67. package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
  68. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  69. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  70. package/dist/types/modes/types.d.ts +2 -1
  71. package/dist/types/session/agent-session.d.ts +1 -1
  72. package/dist/types/session/auth-broker-config.d.ts +4 -0
  73. package/dist/types/session/session-manager.d.ts +1 -1
  74. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  75. package/dist/types/ssh/connection-manager.d.ts +8 -0
  76. package/dist/types/task/discovery.d.ts +1 -2
  77. package/dist/types/task/parallel.d.ts +2 -2
  78. package/dist/types/task/worktree.d.ts +2 -0
  79. package/dist/types/tiny/title-client.d.ts +1 -1
  80. package/dist/types/tools/ask.d.ts +4 -0
  81. package/dist/types/tools/conflict-detect.d.ts +16 -0
  82. package/dist/types/tools/github-cache.d.ts +7 -0
  83. package/dist/types/tools/sqlite-reader.d.ts +3 -0
  84. package/dist/types/tools/todo.d.ts +2 -0
  85. package/dist/types/tui/output-block.d.ts +3 -3
  86. package/dist/types/utils/changelog.d.ts +8 -0
  87. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  88. package/dist/types/web/scrapers/types.d.ts +12 -0
  89. package/dist/types/web/search/providers/codex.d.ts +1 -1
  90. package/dist/types/web/search/providers/gemini.d.ts +1 -1
  91. package/examples/extensions/tools.ts +5 -4
  92. package/package.json +14 -11
  93. package/scripts/build-binary.ts +18 -23
  94. package/scripts/bundle-dist.ts +81 -0
  95. package/scripts/{dev-launch → omp} +1 -1
  96. package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
  97. package/src/async/job-manager.ts +57 -3
  98. package/src/autoresearch/dashboard.ts +1 -1
  99. package/src/autoresearch/prompt-setup.md +6 -6
  100. package/src/autoresearch/prompt.md +6 -6
  101. package/src/capability/fs.ts +10 -0
  102. package/src/cli/args.ts +1 -1
  103. package/src/cli/auth-gateway-cli.ts +1 -3
  104. package/src/cli/dry-balance-cli.ts +1 -1
  105. package/src/cli/gallery-cli.ts +1 -1
  106. package/src/cli/gallery-fixtures/fs.ts +1 -1
  107. package/src/cli/gallery-fixtures/types.ts +5 -1
  108. package/src/cli/list-models.ts +7 -12
  109. package/src/cli/usage-cli.ts +603 -0
  110. package/src/cli-commands.ts +1 -0
  111. package/src/cli.ts +69 -5
  112. package/src/commands/complete.ts +1 -1
  113. package/src/commands/launch.ts +1 -1
  114. package/src/commands/read.ts +6 -3
  115. package/src/commands/usage.ts +35 -0
  116. package/src/commit/agentic/agent.ts +1 -1
  117. package/src/commit/model-selection.ts +1 -1
  118. package/src/config/append-only-context-mode.ts +6 -12
  119. package/src/config/model-discovery.ts +554 -0
  120. package/src/config/model-registry.ts +308 -1025
  121. package/src/config/model-resolver.ts +113 -156
  122. package/src/config/model-roles.ts +74 -0
  123. package/src/config/models-config-schema.ts +57 -8
  124. package/src/config/models-config.ts +129 -0
  125. package/src/config/settings-schema.ts +18 -14
  126. package/src/config/settings.ts +37 -1
  127. package/src/dap/client.ts +124 -37
  128. package/src/dap/session.ts +259 -158
  129. package/src/debug/log-viewer.ts +1 -1
  130. package/src/debug/raw-sse.ts +1 -1
  131. package/src/debug/terminal-info.ts +0 -3
  132. package/src/edit/diff.ts +95 -18
  133. package/src/edit/hashline/block-resolver.ts +20 -1
  134. package/src/edit/hashline/diff.ts +36 -1
  135. package/src/edit/hashline/execute.ts +8 -2
  136. package/src/edit/index.ts +16 -1
  137. package/src/edit/modes/patch.ts +52 -0
  138. package/src/edit/modes/replace.ts +56 -22
  139. package/src/edit/notebook.ts +22 -2
  140. package/src/edit/renderer.ts +36 -10
  141. package/src/eval/__tests__/completion-bridge.test.ts +1 -1
  142. package/src/eval/backend.ts +0 -2
  143. package/src/eval/completion-bridge.ts +2 -1
  144. package/src/eval/idle-timeout.ts +2 -9
  145. package/src/eval/js/context-manager.ts +6 -8
  146. package/src/eval/js/executor.ts +6 -2
  147. package/src/eval/js/index.ts +0 -2
  148. package/src/eval/js/shared/helpers.ts +5 -6
  149. package/src/eval/js/shared/local-module-loader.ts +1 -1
  150. package/src/eval/js/shared/prelude.txt +62 -1
  151. package/src/eval/js/shared/rewrite-imports.ts +49 -23
  152. package/src/eval/js/shared/runtime.ts +1 -1
  153. package/src/eval/py/index.ts +0 -2
  154. package/src/eval/py/kernel.ts +19 -0
  155. package/src/eval/py/runner.py +107 -3
  156. package/src/exec/bash-executor.ts +3 -1
  157. package/src/export/html/template.generated.ts +1 -1
  158. package/src/export/html/template.js +3 -1
  159. package/src/extensibility/extensions/types.ts +3 -2
  160. package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
  161. package/src/hindsight/mental-models.ts +59 -12
  162. package/src/hindsight/state.ts +6 -1
  163. package/src/internal-urls/artifact-protocol.ts +11 -2
  164. package/src/internal-urls/docs-index.generated.ts +10 -10
  165. package/src/internal-urls/issue-pr-protocol.ts +12 -5
  166. package/src/internal-urls/router.ts +1 -1
  167. package/src/internal-urls/types.ts +1 -1
  168. package/src/lib/xai-http.ts +1 -1
  169. package/src/lsp/client.ts +118 -38
  170. package/src/lsp/clients/biome-client.ts +101 -39
  171. package/src/lsp/edits.ts +143 -95
  172. package/src/lsp/index.ts +31 -22
  173. package/src/lsp/render.ts +1 -1
  174. package/src/lsp/types.ts +2 -0
  175. package/src/lsp/utils.ts +28 -10
  176. package/src/main.ts +165 -17
  177. package/src/mcp/json-rpc.ts +35 -5
  178. package/src/mcp/transports/stdio.ts +7 -1
  179. package/src/memories/index.ts +2 -1
  180. package/src/mnemopi/backend.ts +25 -3
  181. package/src/mnemopi/state.ts +38 -2
  182. package/src/modes/components/agent-dashboard.ts +10 -7
  183. package/src/modes/components/assistant-message.ts +19 -13
  184. package/src/modes/components/bash-execution.ts +1 -1
  185. package/src/modes/components/copy-selector.ts +1 -1
  186. package/src/modes/components/diff.ts +13 -2
  187. package/src/modes/components/dynamic-border.ts +12 -3
  188. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  189. package/src/modes/components/extensions/extension-list.ts +1 -1
  190. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  191. package/src/modes/components/footer.ts +1 -1
  192. package/src/modes/components/history-search.ts +1 -1
  193. package/src/modes/components/hook-editor.ts +8 -0
  194. package/src/modes/components/hook-input.ts +8 -0
  195. package/src/modes/components/hook-selector.ts +2 -2
  196. package/src/modes/components/model-selector.ts +66 -54
  197. package/src/modes/components/plan-review-overlay.ts +1 -1
  198. package/src/modes/components/session-observer-overlay.ts +2 -2
  199. package/src/modes/components/session-selector.ts +1 -1
  200. package/src/modes/components/settings-selector.ts +5 -1
  201. package/src/modes/components/status-line/component.ts +1 -1
  202. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  203. package/src/modes/components/transcript-container.ts +373 -141
  204. package/src/modes/components/tree-selector.ts +3 -3
  205. package/src/modes/components/user-message-selector.ts +1 -1
  206. package/src/modes/components/user-message.ts +17 -5
  207. package/src/modes/components/visual-truncate.ts +1 -1
  208. package/src/modes/components/welcome.ts +108 -26
  209. package/src/modes/controllers/command-controller.ts +10 -3
  210. package/src/modes/controllers/event-controller.ts +73 -49
  211. package/src/modes/controllers/input-controller.ts +5 -5
  212. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  213. package/src/modes/controllers/selector-controller.ts +1 -5
  214. package/src/modes/controllers/streaming-reveal.ts +85 -18
  215. package/src/modes/interactive-mode.ts +5 -19
  216. package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
  217. package/src/modes/setup-wizard/scenes/providers.ts +1 -1
  218. package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
  219. package/src/modes/setup-wizard/scenes/theme.ts +1 -1
  220. package/src/modes/setup-wizard/scenes/types.ts +1 -1
  221. package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
  222. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  223. package/src/modes/types.ts +2 -1
  224. package/src/prompts/agents/explore.md +2 -2
  225. package/src/prompts/agents/librarian.md +1 -2
  226. package/src/prompts/agents/oracle.md +1 -1
  227. package/src/prompts/agents/plan.md +5 -5
  228. package/src/prompts/agents/task.md +5 -5
  229. package/src/prompts/ci-green-request.md +5 -7
  230. package/src/prompts/goals/goal-budget-limit.md +2 -2
  231. package/src/prompts/goals/goal-continuation.md +4 -4
  232. package/src/prompts/goals/goal-mode-active.md +1 -1
  233. package/src/prompts/memories/read-path.md +1 -1
  234. package/src/prompts/memories/stage_one_system.md +2 -2
  235. package/src/prompts/review-custom-request.md +1 -1
  236. package/src/prompts/system/agent-creation-architect.md +2 -2
  237. package/src/prompts/system/auto-continue.md +1 -1
  238. package/src/prompts/system/background-tan-dispatch.md +1 -1
  239. package/src/prompts/system/btw-user.md +2 -2
  240. package/src/prompts/system/commit-message-system.md +13 -1
  241. package/src/prompts/system/custom-system-prompt.md +1 -1
  242. package/src/prompts/system/eager-todo.md +2 -2
  243. package/src/prompts/system/irc-incoming.md +1 -1
  244. package/src/prompts/system/manual-continue.md +1 -1
  245. package/src/prompts/system/omfg-user.md +3 -4
  246. package/src/prompts/system/orchestrate-notice.md +9 -9
  247. package/src/prompts/system/plan-mode-active.md +4 -4
  248. package/src/prompts/system/plan-mode-subagent.md +4 -5
  249. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  250. package/src/prompts/system/project-prompt.md +2 -2
  251. package/src/prompts/system/subagent-system-prompt.md +4 -4
  252. package/src/prompts/system/system-prompt.md +15 -26
  253. package/src/prompts/system/title-system.md +2 -2
  254. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  255. package/src/prompts/system/workflow-notice.md +1 -1
  256. package/src/prompts/tools/ast-edit.md +1 -1
  257. package/src/prompts/tools/ast-grep.md +2 -2
  258. package/src/prompts/tools/bash.md +8 -10
  259. package/src/prompts/tools/browser.md +7 -7
  260. package/src/prompts/tools/debug.md +1 -1
  261. package/src/prompts/tools/eval.md +3 -3
  262. package/src/prompts/tools/find.md +0 -1
  263. package/src/prompts/tools/github.md +8 -7
  264. package/src/prompts/tools/goal.md +1 -1
  265. package/src/prompts/tools/image-gen.md +1 -1
  266. package/src/prompts/tools/inspect-image-system.md +1 -1
  267. package/src/prompts/tools/irc.md +15 -15
  268. package/src/prompts/tools/lsp.md +2 -2
  269. package/src/prompts/tools/patch.md +2 -2
  270. package/src/prompts/tools/read.md +3 -4
  271. package/src/prompts/tools/recall.md +1 -1
  272. package/src/prompts/tools/reflect.md +1 -1
  273. package/src/prompts/tools/render-mermaid.md +2 -2
  274. package/src/prompts/tools/replace.md +4 -10
  275. package/src/prompts/tools/rewind.md +2 -2
  276. package/src/prompts/tools/search-tool-bm25.md +1 -9
  277. package/src/prompts/tools/search.md +0 -1
  278. package/src/prompts/tools/ssh.md +0 -4
  279. package/src/prompts/tools/task.md +2 -3
  280. package/src/prompts/tools/todo.md +6 -2
  281. package/src/sdk.ts +23 -10
  282. package/src/session/agent-session.ts +44 -10
  283. package/src/session/auth-broker-config.ts +30 -1
  284. package/src/session/session-manager.ts +2 -2
  285. package/src/session/streaming-output.ts +23 -2
  286. package/src/slash-commands/builtin-registry.ts +20 -0
  287. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  288. package/src/ssh/connection-manager.ts +27 -0
  289. package/src/task/commands.ts +2 -1
  290. package/src/task/discovery.ts +17 -24
  291. package/src/task/executor.ts +61 -53
  292. package/src/task/index.ts +137 -60
  293. package/src/task/parallel.ts +3 -3
  294. package/src/task/render.ts +2 -2
  295. package/src/task/worktree.ts +64 -56
  296. package/src/thinking.ts +2 -1
  297. package/src/tiny/title-client.ts +32 -14
  298. package/src/tools/archive-reader.ts +30 -2
  299. package/src/tools/ask.ts +104 -21
  300. package/src/tools/ast-edit.ts +25 -5
  301. package/src/tools/auto-generated-guard.ts +20 -3
  302. package/src/tools/bash-interactive.ts +27 -7
  303. package/src/tools/bash.ts +54 -13
  304. package/src/tools/browser/launch.ts +11 -2
  305. package/src/tools/browser/readable.ts +19 -2
  306. package/src/tools/browser/registry.ts +4 -1
  307. package/src/tools/browser/render.ts +2 -2
  308. package/src/tools/browser/tab-supervisor.ts +55 -16
  309. package/src/tools/conflict-detect.ts +50 -4
  310. package/src/tools/debug.ts +1 -1
  311. package/src/tools/eval-render.ts +5 -5
  312. package/src/tools/eval.ts +0 -2
  313. package/src/tools/fetch.ts +33 -10
  314. package/src/tools/gh-cache-invalidation.ts +63 -8
  315. package/src/tools/gh-renderer.ts +1 -1
  316. package/src/tools/gh.ts +172 -29
  317. package/src/tools/github-cache.ts +70 -6
  318. package/src/tools/image-gen.ts +3 -9
  319. package/src/tools/irc.ts +5 -1
  320. package/src/tools/job.ts +1 -1
  321. package/src/tools/read.ts +202 -61
  322. package/src/tools/render-utils.ts +3 -3
  323. package/src/tools/resolve.ts +1 -1
  324. package/src/tools/search.ts +92 -29
  325. package/src/tools/sqlite-reader.ts +17 -5
  326. package/src/tools/ssh.ts +8 -8
  327. package/src/tools/todo.ts +51 -12
  328. package/src/tools/write.ts +118 -18
  329. package/src/tui/output-block.ts +4 -4
  330. package/src/utils/changelog.ts +27 -1
  331. package/src/utils/file-mentions.ts +2 -1
  332. package/src/web/scrapers/arxiv.ts +1 -1
  333. package/src/web/scrapers/go-pkg.ts +1 -1
  334. package/src/web/scrapers/iacr.ts +1 -1
  335. package/src/web/scrapers/readthedocs.ts +1 -1
  336. package/src/web/scrapers/twitter.ts +2 -1
  337. package/src/web/scrapers/types.ts +87 -8
  338. package/src/web/scrapers/wikipedia.ts +1 -1
  339. package/src/web/scrapers/youtube.ts +6 -1
  340. package/src/web/search/index.ts +1 -1
  341. package/src/web/search/providers/anthropic.ts +8 -2
  342. package/src/web/search/providers/codex.ts +2 -1
  343. package/src/web/search/providers/gemini.ts +2 -3
  344. package/src/web/search/render.ts +8 -6
  345. package/dist/types/config/model-equivalence.d.ts +0 -24
  346. package/dist/types/config/model-id-affixes.d.ts +0 -12
  347. package/dist/types/config/model-provider-priority.d.ts +0 -1
  348. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  349. package/src/config/model-equivalence.ts +0 -875
  350. package/src/config/model-id-affixes.ts +0 -81
  351. package/src/config/model-provider-priority.ts +0 -56
  352. package/src/exec/idle-timeout-watchdog.ts +0 -126
@@ -261,7 +261,6 @@ function renderEditHeader(
261
261
  options: {
262
262
  icon: "pending" | "success" | "error";
263
263
  iconOverride?: string;
264
- spinnerFrame?: number;
265
264
  op?: Operation;
266
265
  rawPath: string;
267
266
  rename?: string;
@@ -284,7 +283,6 @@ function renderEditHeader(
284
283
  {
285
284
  icon: options.icon,
286
285
  iconOverride: options.iconOverride,
287
- spinnerFrame: options.spinnerFrame,
288
286
  title,
289
287
  description,
290
288
  },
@@ -322,6 +320,7 @@ function formatStreamingDiff(
322
320
  uiTheme: Theme,
323
321
  expanded: boolean,
324
322
  label = "streaming",
323
+ spinnerFrame?: number,
325
324
  ): string {
326
325
  if (!diff) return "";
327
326
  // Collapsed uses a "Cursor" tail window: pin the last
@@ -342,11 +341,26 @@ function formatStreamingDiff(
342
341
  text += `${uiTheme.fg("dim", `… (${remainder.join(", ")} above)`)}\n`;
343
342
  }
344
343
  text += renderDiffColored(visible.join("\n"), { filePath: rawPath });
345
- if (!expanded || label !== "preview") text += uiTheme.fg("dim", `\n(${label})`);
344
+ // The animated glyph rides this trailing line inside the transcript's
345
+ // volatile-tail holdback — never the block header: an animating head row
346
+ // pins the native-scrollback commit boundary at the top of the block, so a
347
+ // tall expanded preview could never scroll-append mid-stream.
348
+ const spinner = spinnerFrame !== undefined ? `${formatStatusIcon("running", uiTheme, spinnerFrame)} ` : "";
349
+ // Expanded approval previews hide the "(preview)" label (#1992) but keep
350
+ // the animated glyph when one is active so the volatile tail stays live.
351
+ const hideLabel = expanded && label === "preview";
352
+ if (spinner || !hideLabel) {
353
+ text += `\n${hideLabel ? spinner.trimEnd() : `${spinner}${uiTheme.fg("dim", `(${label})`)}`}`;
354
+ }
346
355
  return text;
347
356
  }
348
357
 
349
- function formatMultiFileStreamingDiff(previews: PerFileDiffPreview[], uiTheme: Theme, expanded: boolean): string {
358
+ function formatMultiFileStreamingDiff(
359
+ previews: PerFileDiffPreview[],
360
+ uiTheme: Theme,
361
+ expanded: boolean,
362
+ spinnerFrame?: number,
363
+ ): string {
350
364
  const parts: string[] = [];
351
365
  for (const preview of previews) {
352
366
  if (!preview.diff && !preview.error) continue;
@@ -356,7 +370,13 @@ function formatMultiFileStreamingDiff(previews: PerFileDiffPreview[], uiTheme: T
356
370
  continue;
357
371
  }
358
372
  if (preview.diff) {
359
- parts.push(`${header}${formatStreamingDiff(preview.diff, preview.path, uiTheme, expanded, "preview")}`);
373
+ // Only the last file's preview carries the animated streaming glyph;
374
+ // earlier files have settled and must stay byte-stable so their rows
375
+ // can commit to native scrollback mid-stream.
376
+ const isLast = preview === previews[previews.length - 1];
377
+ parts.push(
378
+ `${header}${formatStreamingDiff(preview.diff, preview.path, uiTheme, expanded, "preview", isLast ? spinnerFrame : undefined)}`,
379
+ );
360
380
  }
361
381
  }
362
382
  return parts.join("");
@@ -368,16 +388,17 @@ function getCallPreview(
368
388
  uiTheme: Theme,
369
389
  renderContext: EditRenderContext | undefined,
370
390
  expanded: boolean,
391
+ spinnerFrame?: number,
371
392
  ): string {
372
393
  const multi = renderContext?.perFileDiffPreview;
373
394
  if (multi && multi.length > 1 && multi.some(p => p.diff || p.error)) {
374
- return formatMultiFileStreamingDiff(multi, uiTheme, expanded);
395
+ return formatMultiFileStreamingDiff(multi, uiTheme, expanded, spinnerFrame);
375
396
  }
376
397
  if (args.previewDiff) {
377
- return formatStreamingDiff(args.previewDiff, rawPath, uiTheme, expanded, "preview");
398
+ return formatStreamingDiff(args.previewDiff, rawPath, uiTheme, expanded, "preview", spinnerFrame);
378
399
  }
379
400
  if (args.diff && args.op) {
380
- return formatStreamingDiff(args.diff, rawPath, uiTheme, expanded);
401
+ return formatStreamingDiff(args.diff, rawPath, uiTheme, expanded, "streaming", spinnerFrame);
381
402
  }
382
403
  if (args.diff) {
383
404
  return renderPlainTextPreview(args.diff, uiTheme, rawPath);
@@ -554,15 +575,20 @@ export const editToolRenderer = {
554
575
  fileCount = countEditFiles(editArgs.edits);
555
576
  }
556
577
  return framedBlock(uiTheme, width => {
578
+ // Static pending icon, never the animated glyph: the header is the
579
+ // head row of the framed block, and native-scrollback commits are
580
+ // prefix-only — an animating head row would pin the commit boundary
581
+ // at the top and keep a tall expanded preview from scroll-appending
582
+ // mid-stream. The liveness cue rides the trailing "(preview)" /
583
+ // "(streaming)" line instead.
557
584
  const header = renderEditHeader(width, uiTheme, {
558
585
  icon: "pending",
559
- spinnerFrame: options?.spinnerFrame,
560
586
  op,
561
587
  rawPath,
562
588
  rename,
563
589
  extraSuffix: fileCount > 1 ? uiTheme.fg("dim", ` (+${fileCount - 1} more)`) : undefined,
564
590
  });
565
- let body = getCallPreview(editArgs, rawPath, uiTheme, renderContext, options.expanded);
591
+ let body = getCallPreview(editArgs, rawPath, uiTheme, renderContext, options.expanded, options?.spinnerFrame);
566
592
  if (applyPatchSummary?.error) {
567
593
  body += `\n${uiTheme.fg("error", truncateToWidth(replaceTabs(applyPatchSummary.error, rawPath), Math.max(1, width - 2)))}`;
568
594
  }
@@ -38,7 +38,7 @@ const SLOW = makeModel("p", "slow");
38
38
  const REASONING_SLOW = makeModel("p", "slow", {
39
39
  api: "anthropic-messages",
40
40
  reasoning: true,
41
- thinking: { minLevel: Effort.Low, maxLevel: Effort.High, mode: "anthropic-adaptive" },
41
+ thinking: { efforts: [Effort.Low, Effort.Medium, Effort.High], mode: "anthropic-adaptive" },
42
42
  });
43
43
 
44
44
  interface SessionOptions {
@@ -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
 
@@ -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,
@@ -82,6 +95,14 @@ function parseProgram(code: string): { program: { body: ReadonlyArray<BabelProgr
82
95
  }
83
96
  }
84
97
 
98
+ // Callee substituted for dynamic `import(...)` calls. Functions handed to puppeteer
99
+ // (`tab.evaluate`, `page.evaluate`, `waitForFunction`, `$$eval`, ...) are serialized with
100
+ // `Function.prototype.toString()` and re-evaluated inside the browser page, where the
101
+ // worker-injected `__omp_import__` global does not exist. The swap therefore guards on the
102
+ // helper's presence and falls back to native dynamic import, so serialized code keeps
103
+ // working in foreign realms while in-worker code still resolves against the session cwd.
104
+ const DYNAMIC_IMPORT_CALLEE = '(typeof __omp_import__ === "function" ? __omp_import__ : (s, o) => import(s, o))';
105
+
85
106
  function buildOmpImportCall(sourceLiteral: string, optionsLiteral: string | undefined): string {
86
107
  // Route every static import through the worker-injected `__omp_import__` helper so the
87
108
  // specifier resolves against the session cwd (and `with`-attribute imports keep working).
@@ -154,10 +175,10 @@ function rewriteImportNode(node: BabelImportDeclaration): string {
154
175
  return `await ${importCall};`;
155
176
  }
156
177
 
157
- export function rewriteImports(code: string): string {
178
+ export async function rewriteImports(code: string): Promise<string> {
158
179
  if (!code.includes("import")) return code;
159
180
 
160
- const ast = parseProgram(code);
181
+ const ast = await parseProgram(code);
161
182
  if (!ast) {
162
183
  // Parser bailed entirely — let the VM surface the real syntax error.
163
184
  return code;
@@ -180,7 +201,7 @@ export function rewriteImports(code: string): string {
180
201
  const call = node as unknown as { callee?: { type?: string; start?: number; end?: number } };
181
202
  const callee = call.callee;
182
203
  if (callee?.type !== "Import" || typeof callee.start !== "number" || typeof callee.end !== "number") return;
183
- edits.push({ start: callee.start, end: callee.end, text: "__omp_import__" });
204
+ edits.push({ start: callee.start, end: callee.end, text: DYNAMIC_IMPORT_CALLEE });
184
205
  });
185
206
 
186
207
  if (edits.length === 0) return code;
@@ -193,8 +214,8 @@ export function rewriteImports(code: string): string {
193
214
  }
194
215
  return result;
195
216
  }
196
- export function collectModuleSourceSpecifiers(code: string): string[] {
197
- const ast = parseProgram(code);
217
+ export async function collectModuleSourceSpecifiers(code: string): Promise<string[]> {
218
+ const ast = await parseProgram(code);
198
219
  if (!ast) return [];
199
220
  const sources: string[] = [];
200
221
  for (const node of ast.program.body) {
@@ -210,8 +231,11 @@ export function collectModuleSourceSpecifiers(code: string): string[] {
210
231
  return sources;
211
232
  }
212
233
 
213
- export function rewriteModuleSourceSpecifiers(code: string, replacer: (source: string) => string): string {
214
- 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);
215
239
  if (!ast) return code;
216
240
 
217
241
  type Edit = { start: number; end: number; text: string };
@@ -241,9 +265,9 @@ export function rewriteModuleSourceSpecifiers(code: string, replacer: (source: s
241
265
  return result;
242
266
  }
243
267
 
244
- export function rewriteDynamicImports(code: string, callee = "__omp_import__"): string {
268
+ export async function rewriteDynamicImports(code: string, callee = "__omp_import__"): Promise<string> {
245
269
  if (!code.includes("import")) return code;
246
- const ast = parseProgram(code);
270
+ const ast = await parseProgram(code);
247
271
  if (!ast) return code;
248
272
 
249
273
  type Edit = { start: number; end: number; text: string };
@@ -331,10 +355,10 @@ function appendGlobalBindingPublish(source: string, names: readonly string[]): s
331
355
  * Nested declarations (inside functions, blocks, classes) are left alone \u2014 they're
332
356
  * scoped to their enclosing function/block regardless of `var` vs `let`/`const`.
333
357
  */
334
- function demoteTopLevelLexicals(code: string, options: { publishGlobals?: boolean } = {}): string {
358
+ async function demoteTopLevelLexicals(code: string, options: { publishGlobals?: boolean } = {}): Promise<string> {
335
359
  if (!/\b(?:const|let|class)\b/.test(code)) return code;
336
360
 
337
- const ast = parseProgram(code);
361
+ const ast = await parseProgram(code);
338
362
  if (!ast) {
339
363
  return code;
340
364
  }
@@ -373,8 +397,8 @@ function demoteTopLevelLexicals(code: string, options: { publishGlobals?: boolea
373
397
  return result;
374
398
  }
375
399
 
376
- function returnFinalExpression(code: string): { source: string; returned: boolean } {
377
- const ast = parseProgram(code);
400
+ async function returnFinalExpression(code: string): Promise<{ source: string; returned: boolean }> {
401
+ const ast = await parseProgram(code);
378
402
  const body = ast?.program.body;
379
403
  if (!body) return { source: code, returned: false };
380
404
  let lastIndex = body.length - 1;
@@ -438,8 +462,8 @@ function containsAsyncWrapperSyntax(value: unknown): boolean {
438
462
  return false;
439
463
  }
440
464
 
441
- function requiresAsyncWrapper(code: string): boolean {
442
- const ast = parseProgram(code);
465
+ async function requiresAsyncWrapper(code: string): Promise<boolean> {
466
+ const ast = await parseProgram(code);
443
467
  if (!ast) return false;
444
468
  for (const node of ast.program.body) {
445
469
  if (containsAsyncWrapperSyntax(node)) return true;
@@ -486,13 +510,15 @@ export function stripTypeScriptSyntax(
486
510
  const LOOKS_LIKE_TS =
487
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*[,>])/;
488
512
 
489
- export function wrapCode(code: string): { source: string; asyncWrapped: boolean; finalExpressionReturned: boolean } {
490
- 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);
491
517
  const stripped = stripTypeScript(finalExpression.source);
492
- const importsRewritten = rewriteImports(stripped);
493
- const needsAsyncWrapper = requiresAsyncWrapper(importsRewritten);
518
+ const importsRewritten = await rewriteImports(stripped);
519
+ const needsAsyncWrapper = await requiresAsyncWrapper(importsRewritten);
494
520
  const rewritten = {
495
- source: demoteTopLevelLexicals(importsRewritten, { publishGlobals: needsAsyncWrapper }),
521
+ source: await demoteTopLevelLexicals(importsRewritten, { publishGlobals: needsAsyncWrapper }),
496
522
  returned: finalExpression.returned,
497
523
  };
498
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);
@@ -42,8 +42,6 @@ export default {
42
42
  localRoots: resolveEvalUrlRoots(opts.session),
43
43
  kernelOwnerId: opts.kernelOwnerId,
44
44
  reset: opts.reset,
45
- artifactPath: opts.artifactPath,
46
- artifactId: opts.artifactId,
47
45
  onChunk: opts.onChunk,
48
46
  onStatus: opts.onStatus,
49
47
  toolSession: opts.session,
@@ -129,10 +129,29 @@ function throwIfAborted(signal: AbortSignal | undefined, fallbackReason: string)
129
129
  throw createAbortError("AbortError", typeof reason === "string" ? reason : fallbackReason);
130
130
  }
131
131
 
132
+ // Cache successful probes per resolved cwd: every cell otherwise pays one (or
133
+ // two — backend.isAvailable + ensureKernelAvailable) interpreter spawns even
134
+ // when the kernel is already hot. Failures are not cached so installing a
135
+ // Python mid-session is picked up on the next attempt.
136
+ const availabilityCache = new Map<string, Promise<PythonKernelAvailability>>();
137
+
132
138
  export async function checkPythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
133
139
  if (isBunTestRuntime() || $flag("PI_PYTHON_SKIP_CHECK")) {
134
140
  return { ok: true };
135
141
  }
142
+ const key = path.resolve(cwd);
143
+ const cached = availabilityCache.get(key);
144
+ if (cached) return await cached;
145
+ const probe = probePythonKernelAvailability(key);
146
+ availabilityCache.set(key, probe);
147
+ const result = await probe;
148
+ if (!result.ok && availabilityCache.get(key) === probe) {
149
+ availabilityCache.delete(key);
150
+ }
151
+ return result;
152
+ }
153
+
154
+ async function probePythonKernelAvailability(cwd: string): Promise<PythonKernelAvailability> {
136
155
  try {
137
156
  const settings = await Settings.init();
138
157
  const { env } = settings.getShellConfig();