@oh-my-pi/pi-coding-agent 15.10.10 → 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 (345) hide show
  1. package/CHANGELOG.md +95 -4
  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 +7 -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 -7
  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/eval/backend.d.ts +0 -2
  25. package/dist/types/eval/idle-timeout.d.ts +0 -4
  26. package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
  27. package/dist/types/export/html/template.generated.d.ts +1 -1
  28. package/dist/types/extensibility/extensions/types.d.ts +3 -3
  29. package/dist/types/hindsight/mental-models.d.ts +17 -8
  30. package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
  31. package/dist/types/internal-urls/types.d.ts +1 -1
  32. package/dist/types/lsp/edits.d.ts +9 -0
  33. package/dist/types/lsp/index.d.ts +2 -2
  34. package/dist/types/lsp/types.d.ts +2 -0
  35. package/dist/types/lsp/utils.d.ts +3 -0
  36. package/dist/types/mcp/json-rpc.d.ts +5 -0
  37. package/dist/types/mnemopi/state.d.ts +11 -1
  38. package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
  39. package/dist/types/modes/components/assistant-message.d.ts +3 -1
  40. package/dist/types/modes/components/bash-execution.d.ts +1 -1
  41. package/dist/types/modes/components/copy-selector.d.ts +1 -1
  42. package/dist/types/modes/components/dynamic-border.d.ts +1 -1
  43. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
  44. package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
  45. package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
  46. package/dist/types/modes/components/footer.d.ts +1 -1
  47. package/dist/types/modes/components/hook-editor.d.ts +5 -0
  48. package/dist/types/modes/components/hook-input.d.ts +4 -0
  49. package/dist/types/modes/components/hook-selector.d.ts +1 -1
  50. package/dist/types/modes/components/model-selector.d.ts +1 -1
  51. package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
  52. package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
  53. package/dist/types/modes/components/session-selector.d.ts +1 -1
  54. package/dist/types/modes/components/status-line/component.d.ts +1 -1
  55. package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
  56. package/dist/types/modes/components/transcript-container.d.ts +25 -6
  57. package/dist/types/modes/components/tree-selector.d.ts +1 -1
  58. package/dist/types/modes/components/user-message-selector.d.ts +1 -1
  59. package/dist/types/modes/components/user-message.d.ts +2 -1
  60. package/dist/types/modes/components/visual-truncate.d.ts +1 -1
  61. package/dist/types/modes/components/welcome.d.ts +19 -3
  62. package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
  63. package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
  64. package/dist/types/modes/interactive-mode.d.ts +1 -1
  65. package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
  66. package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
  67. package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
  68. package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
  69. package/dist/types/modes/types.d.ts +2 -1
  70. package/dist/types/session/agent-session.d.ts +1 -1
  71. package/dist/types/session/auth-broker-config.d.ts +4 -0
  72. package/dist/types/session/session-manager.d.ts +1 -1
  73. package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
  74. package/dist/types/ssh/connection-manager.d.ts +8 -0
  75. package/dist/types/task/parallel.d.ts +2 -2
  76. package/dist/types/task/worktree.d.ts +2 -0
  77. package/dist/types/tools/ask.d.ts +4 -0
  78. package/dist/types/tools/conflict-detect.d.ts +16 -0
  79. package/dist/types/tools/github-cache.d.ts +7 -0
  80. package/dist/types/tools/sqlite-reader.d.ts +3 -0
  81. package/dist/types/tui/output-block.d.ts +3 -3
  82. package/dist/types/utils/changelog.d.ts +8 -0
  83. package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
  84. package/dist/types/web/scrapers/types.d.ts +12 -0
  85. package/dist/types/web/search/providers/codex.d.ts +1 -1
  86. package/dist/types/web/search/providers/gemini.d.ts +1 -1
  87. package/examples/extensions/tools.ts +5 -4
  88. package/package.json +14 -11
  89. package/scripts/build-binary.ts +18 -23
  90. package/scripts/bundle-dist.ts +81 -0
  91. package/scripts/{dev-launch → omp} +1 -1
  92. package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
  93. package/src/async/job-manager.ts +57 -3
  94. package/src/autoresearch/dashboard.ts +1 -1
  95. package/src/autoresearch/prompt-setup.md +6 -6
  96. package/src/autoresearch/prompt.md +6 -6
  97. package/src/capability/fs.ts +10 -0
  98. package/src/cli/args.ts +1 -1
  99. package/src/cli/auth-gateway-cli.ts +1 -3
  100. package/src/cli/dry-balance-cli.ts +1 -1
  101. package/src/cli/gallery-cli.ts +1 -1
  102. package/src/cli/gallery-fixtures/fs.ts +1 -1
  103. package/src/cli/gallery-fixtures/types.ts +5 -1
  104. package/src/cli/list-models.ts +2 -1
  105. package/src/cli/usage-cli.ts +603 -0
  106. package/src/cli-commands.ts +1 -0
  107. package/src/cli.ts +69 -5
  108. package/src/commands/complete.ts +1 -1
  109. package/src/commands/launch.ts +1 -1
  110. package/src/commands/read.ts +6 -3
  111. package/src/commands/usage.ts +35 -0
  112. package/src/commit/agentic/agent.ts +1 -1
  113. package/src/commit/model-selection.ts +1 -1
  114. package/src/config/append-only-context-mode.ts +6 -12
  115. package/src/config/model-discovery.ts +554 -0
  116. package/src/config/model-registry.ts +231 -1019
  117. package/src/config/model-resolver.ts +113 -156
  118. package/src/config/model-roles.ts +74 -0
  119. package/src/config/models-config-schema.ts +57 -8
  120. package/src/config/models-config.ts +129 -0
  121. package/src/config/settings-schema.ts +18 -4
  122. package/src/config/settings.ts +37 -1
  123. package/src/dap/client.ts +124 -37
  124. package/src/dap/session.ts +259 -158
  125. package/src/debug/log-viewer.ts +1 -1
  126. package/src/debug/raw-sse.ts +1 -1
  127. package/src/edit/diff.ts +47 -3
  128. package/src/edit/hashline/block-resolver.ts +20 -1
  129. package/src/edit/hashline/diff.ts +36 -1
  130. package/src/edit/hashline/execute.ts +8 -2
  131. package/src/edit/index.ts +16 -1
  132. package/src/edit/modes/patch.ts +52 -0
  133. package/src/edit/modes/replace.ts +56 -22
  134. package/src/edit/notebook.ts +22 -2
  135. package/src/edit/renderer.ts +36 -10
  136. package/src/eval/__tests__/completion-bridge.test.ts +1 -1
  137. package/src/eval/backend.ts +0 -2
  138. package/src/eval/completion-bridge.ts +2 -1
  139. package/src/eval/idle-timeout.ts +2 -9
  140. package/src/eval/js/context-manager.ts +6 -8
  141. package/src/eval/js/executor.ts +6 -2
  142. package/src/eval/js/index.ts +0 -2
  143. package/src/eval/js/shared/helpers.ts +5 -6
  144. package/src/eval/js/shared/local-module-loader.ts +1 -1
  145. package/src/eval/js/shared/prelude.txt +62 -1
  146. package/src/eval/js/shared/rewrite-imports.ts +40 -22
  147. package/src/eval/js/shared/runtime.ts +1 -1
  148. package/src/eval/py/index.ts +0 -2
  149. package/src/eval/py/kernel.ts +19 -0
  150. package/src/eval/py/runner.py +107 -3
  151. package/src/exec/bash-executor.ts +3 -1
  152. package/src/export/html/template.generated.ts +1 -1
  153. package/src/export/html/template.js +3 -1
  154. package/src/extensibility/extensions/types.ts +3 -2
  155. package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
  156. package/src/hindsight/mental-models.ts +59 -12
  157. package/src/hindsight/state.ts +6 -1
  158. package/src/internal-urls/artifact-protocol.ts +11 -2
  159. package/src/internal-urls/docs-index.generated.ts +8 -8
  160. package/src/internal-urls/issue-pr-protocol.ts +12 -5
  161. package/src/internal-urls/router.ts +1 -1
  162. package/src/internal-urls/types.ts +1 -1
  163. package/src/lib/xai-http.ts +1 -1
  164. package/src/lsp/client.ts +118 -38
  165. package/src/lsp/clients/biome-client.ts +101 -39
  166. package/src/lsp/edits.ts +143 -95
  167. package/src/lsp/index.ts +31 -22
  168. package/src/lsp/render.ts +1 -1
  169. package/src/lsp/types.ts +2 -0
  170. package/src/lsp/utils.ts +28 -10
  171. package/src/main.ts +165 -17
  172. package/src/mcp/json-rpc.ts +35 -5
  173. package/src/mcp/transports/stdio.ts +7 -1
  174. package/src/memories/index.ts +2 -1
  175. package/src/mnemopi/backend.ts +25 -3
  176. package/src/mnemopi/state.ts +38 -2
  177. package/src/modes/components/agent-dashboard.ts +10 -7
  178. package/src/modes/components/assistant-message.ts +19 -13
  179. package/src/modes/components/bash-execution.ts +1 -1
  180. package/src/modes/components/copy-selector.ts +1 -1
  181. package/src/modes/components/diff.ts +13 -2
  182. package/src/modes/components/dynamic-border.ts +12 -3
  183. package/src/modes/components/extensions/extension-dashboard.ts +8 -5
  184. package/src/modes/components/extensions/extension-list.ts +1 -1
  185. package/src/modes/components/extensions/inspector-panel.ts +1 -1
  186. package/src/modes/components/footer.ts +1 -1
  187. package/src/modes/components/history-search.ts +1 -1
  188. package/src/modes/components/hook-editor.ts +8 -0
  189. package/src/modes/components/hook-input.ts +8 -0
  190. package/src/modes/components/hook-selector.ts +2 -2
  191. package/src/modes/components/model-selector.ts +4 -2
  192. package/src/modes/components/plan-review-overlay.ts +1 -1
  193. package/src/modes/components/session-observer-overlay.ts +2 -2
  194. package/src/modes/components/session-selector.ts +1 -1
  195. package/src/modes/components/settings-selector.ts +5 -1
  196. package/src/modes/components/status-line/component.ts +1 -1
  197. package/src/modes/components/tiny-title-download-progress.ts +1 -1
  198. package/src/modes/components/transcript-container.ts +258 -53
  199. package/src/modes/components/tree-selector.ts +3 -3
  200. package/src/modes/components/user-message-selector.ts +1 -1
  201. package/src/modes/components/user-message.ts +17 -5
  202. package/src/modes/components/visual-truncate.ts +1 -1
  203. package/src/modes/components/welcome.ts +108 -26
  204. package/src/modes/controllers/command-controller.ts +10 -3
  205. package/src/modes/controllers/event-controller.ts +73 -4
  206. package/src/modes/controllers/input-controller.ts +1 -1
  207. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  208. package/src/modes/controllers/selector-controller.ts +1 -1
  209. package/src/modes/controllers/streaming-reveal.ts +85 -18
  210. package/src/modes/interactive-mode.ts +3 -9
  211. package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
  212. package/src/modes/setup-wizard/scenes/providers.ts +1 -1
  213. package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
  214. package/src/modes/setup-wizard/scenes/theme.ts +1 -1
  215. package/src/modes/setup-wizard/scenes/types.ts +1 -1
  216. package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
  217. package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
  218. package/src/modes/types.ts +2 -1
  219. package/src/prompts/agents/explore.md +2 -2
  220. package/src/prompts/agents/librarian.md +1 -2
  221. package/src/prompts/agents/oracle.md +1 -1
  222. package/src/prompts/agents/plan.md +5 -5
  223. package/src/prompts/agents/task.md +5 -5
  224. package/src/prompts/ci-green-request.md +5 -7
  225. package/src/prompts/goals/goal-budget-limit.md +2 -2
  226. package/src/prompts/goals/goal-continuation.md +4 -4
  227. package/src/prompts/goals/goal-mode-active.md +1 -1
  228. package/src/prompts/memories/read-path.md +1 -1
  229. package/src/prompts/memories/stage_one_system.md +2 -2
  230. package/src/prompts/review-custom-request.md +1 -1
  231. package/src/prompts/system/agent-creation-architect.md +2 -2
  232. package/src/prompts/system/auto-continue.md +1 -1
  233. package/src/prompts/system/background-tan-dispatch.md +1 -1
  234. package/src/prompts/system/btw-user.md +2 -2
  235. package/src/prompts/system/commit-message-system.md +13 -1
  236. package/src/prompts/system/custom-system-prompt.md +1 -1
  237. package/src/prompts/system/eager-todo.md +2 -2
  238. package/src/prompts/system/irc-incoming.md +1 -1
  239. package/src/prompts/system/manual-continue.md +1 -1
  240. package/src/prompts/system/omfg-user.md +3 -4
  241. package/src/prompts/system/orchestrate-notice.md +9 -9
  242. package/src/prompts/system/plan-mode-active.md +4 -4
  243. package/src/prompts/system/plan-mode-subagent.md +4 -5
  244. package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
  245. package/src/prompts/system/project-prompt.md +2 -2
  246. package/src/prompts/system/subagent-system-prompt.md +4 -4
  247. package/src/prompts/system/system-prompt.md +13 -24
  248. package/src/prompts/system/title-system.md +2 -2
  249. package/src/prompts/system/ttsr-tool-reminder.md +1 -1
  250. package/src/prompts/system/workflow-notice.md +1 -1
  251. package/src/prompts/tools/ast-edit.md +1 -1
  252. package/src/prompts/tools/ast-grep.md +2 -2
  253. package/src/prompts/tools/bash.md +5 -7
  254. package/src/prompts/tools/browser.md +7 -7
  255. package/src/prompts/tools/debug.md +1 -1
  256. package/src/prompts/tools/eval.md +3 -3
  257. package/src/prompts/tools/find.md +0 -1
  258. package/src/prompts/tools/github.md +8 -7
  259. package/src/prompts/tools/goal.md +1 -1
  260. package/src/prompts/tools/image-gen.md +1 -1
  261. package/src/prompts/tools/inspect-image-system.md +1 -1
  262. package/src/prompts/tools/irc.md +15 -15
  263. package/src/prompts/tools/lsp.md +2 -2
  264. package/src/prompts/tools/patch.md +2 -2
  265. package/src/prompts/tools/read.md +3 -4
  266. package/src/prompts/tools/recall.md +1 -1
  267. package/src/prompts/tools/reflect.md +1 -1
  268. package/src/prompts/tools/render-mermaid.md +2 -2
  269. package/src/prompts/tools/replace.md +4 -10
  270. package/src/prompts/tools/rewind.md +2 -2
  271. package/src/prompts/tools/search-tool-bm25.md +1 -9
  272. package/src/prompts/tools/search.md +0 -1
  273. package/src/prompts/tools/ssh.md +0 -4
  274. package/src/prompts/tools/task.md +2 -3
  275. package/src/prompts/tools/todo.md +1 -1
  276. package/src/sdk.ts +23 -10
  277. package/src/session/agent-session.ts +44 -10
  278. package/src/session/auth-broker-config.ts +30 -1
  279. package/src/session/session-manager.ts +2 -2
  280. package/src/session/streaming-output.ts +23 -2
  281. package/src/slash-commands/builtin-registry.ts +20 -0
  282. package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
  283. package/src/ssh/connection-manager.ts +27 -0
  284. package/src/task/commands.ts +2 -1
  285. package/src/task/executor.ts +61 -53
  286. package/src/task/index.ts +137 -60
  287. package/src/task/parallel.ts +3 -3
  288. package/src/task/render.ts +2 -2
  289. package/src/task/worktree.ts +64 -56
  290. package/src/thinking.ts +2 -1
  291. package/src/tiny/title-client.ts +26 -11
  292. package/src/tools/archive-reader.ts +30 -2
  293. package/src/tools/ask.ts +104 -21
  294. package/src/tools/ast-edit.ts +25 -5
  295. package/src/tools/auto-generated-guard.ts +20 -3
  296. package/src/tools/bash-interactive.ts +27 -7
  297. package/src/tools/bash.ts +54 -13
  298. package/src/tools/browser/launch.ts +11 -2
  299. package/src/tools/browser/readable.ts +19 -2
  300. package/src/tools/browser/registry.ts +4 -1
  301. package/src/tools/browser/render.ts +2 -2
  302. package/src/tools/browser/tab-supervisor.ts +55 -16
  303. package/src/tools/conflict-detect.ts +50 -4
  304. package/src/tools/debug.ts +1 -1
  305. package/src/tools/eval-render.ts +5 -5
  306. package/src/tools/eval.ts +0 -2
  307. package/src/tools/fetch.ts +33 -10
  308. package/src/tools/gh-cache-invalidation.ts +63 -8
  309. package/src/tools/gh-renderer.ts +1 -1
  310. package/src/tools/gh.ts +172 -29
  311. package/src/tools/github-cache.ts +70 -6
  312. package/src/tools/image-gen.ts +3 -9
  313. package/src/tools/irc.ts +5 -1
  314. package/src/tools/job.ts +1 -1
  315. package/src/tools/read.ts +202 -61
  316. package/src/tools/render-utils.ts +3 -3
  317. package/src/tools/resolve.ts +1 -1
  318. package/src/tools/search.ts +92 -29
  319. package/src/tools/sqlite-reader.ts +17 -5
  320. package/src/tools/ssh.ts +8 -8
  321. package/src/tools/todo.ts +38 -8
  322. package/src/tools/write.ts +118 -18
  323. package/src/tui/output-block.ts +4 -4
  324. package/src/utils/changelog.ts +27 -1
  325. package/src/utils/file-mentions.ts +2 -1
  326. package/src/web/scrapers/arxiv.ts +1 -1
  327. package/src/web/scrapers/go-pkg.ts +1 -1
  328. package/src/web/scrapers/iacr.ts +1 -1
  329. package/src/web/scrapers/readthedocs.ts +1 -1
  330. package/src/web/scrapers/twitter.ts +2 -1
  331. package/src/web/scrapers/types.ts +87 -8
  332. package/src/web/scrapers/wikipedia.ts +1 -1
  333. package/src/web/scrapers/youtube.ts +6 -1
  334. package/src/web/search/index.ts +1 -1
  335. package/src/web/search/providers/codex.ts +2 -1
  336. package/src/web/search/providers/gemini.ts +2 -3
  337. package/src/web/search/render.ts +8 -6
  338. package/dist/types/config/model-equivalence.d.ts +0 -24
  339. package/dist/types/config/model-id-affixes.d.ts +0 -12
  340. package/dist/types/config/model-provider-priority.d.ts +0 -1
  341. package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
  342. package/src/config/model-equivalence.ts +0 -875
  343. package/src/config/model-id-affixes.ts +0 -81
  344. package/src/config/model-provider-priority.ts +0 -56
  345. package/src/exec/idle-timeout-watchdog.ts +0 -126
@@ -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);
@@ -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();
@@ -51,8 +51,23 @@ from typing import Any
51
51
  # Frame writer
52
52
  # ---------------------------------------------------------------------------
53
53
 
54
- _RAW_STDOUT = sys.__stdout__
54
+ # Frames travel on a private dup of the original stdout. fd 1 itself is then
55
+ # repointed at a capture pipe: child processes spawned by user code without
56
+ # stdout=PIPE inherit fd 1, and their output is forwarded to the host as
57
+ # regular stdout frames by a drain thread instead of being written raw into
58
+ # the NDJSON channel (where it would be dropped as invalid JSON — or worse,
59
+ # spoof a frame). The wire protocol is unchanged: the host still reads NDJSON
60
+ # frames from the subprocess stdout.
55
61
  _RAW_STDERR = sys.__stderr__
62
+ try:
63
+ _FRAME_FD = os.dup(sys.__stdout__.fileno())
64
+ _RAW_STDOUT = os.fdopen(_FRAME_FD, "w", encoding="utf-8", errors="backslashreplace")
65
+ _CAPTURE_READ_FD, _capture_write_fd = os.pipe()
66
+ os.dup2(_capture_write_fd, sys.__stdout__.fileno())
67
+ os.close(_capture_write_fd)
68
+ except (AttributeError, OSError, ValueError, io.UnsupportedOperation):
69
+ _RAW_STDOUT = sys.__stdout__
70
+ _CAPTURE_READ_FD = None
56
71
  _OUT_LOCK = threading.Lock()
57
72
 
58
73
 
@@ -78,11 +93,22 @@ def _emit(frame: dict) -> None:
78
93
 
79
94
 
80
95
  class _StreamProxy(io.TextIOBase):
81
- """Emit each ``write()`` as a typed frame tied to the current request."""
96
+ """Emit ``write()`` data as typed frames tied to the current request.
97
+
98
+ Writes are coalesced per request: a frame is emitted once the buffer holds
99
+ a complete line (everything up to the last newline goes out together) or
100
+ grows past ``_MAX_BUFFER`` bytes, so the common ``print()`` pair of
101
+ ``write(text)`` + ``write("\\n")`` costs one frame instead of two. Partial
102
+ lines are bounded by ``flush()`` and the end-of-request flush.
103
+ """
104
+
105
+ _MAX_BUFFER = 8192
82
106
 
83
107
  def __init__(self, kind: str) -> None:
84
108
  super().__init__()
85
109
  self._kind = kind
110
+ self._lock = threading.Lock()
111
+ self._buffers: dict[str, str] = {}
86
112
 
87
113
  def writable(self) -> bool: # noqa: D401 - protocol method
88
114
  return True
@@ -100,12 +126,44 @@ class _StreamProxy(io.TextIOBase):
100
126
  _RAW_STDERR.write(data)
101
127
  _RAW_STDERR.flush()
102
128
  return len(data)
103
- _emit({"type": self._kind, "id": rid, "data": data})
129
+ emit_text = None
130
+ with self._lock:
131
+ buf = self._buffers.pop(rid, "") + data
132
+ if len(buf) >= self._MAX_BUFFER:
133
+ emit_text = buf
134
+ else:
135
+ nl = buf.rfind("\n")
136
+ if nl >= 0:
137
+ emit_text = buf[: nl + 1]
138
+ rest = buf[nl + 1 :]
139
+ if rest:
140
+ self._buffers[rid] = rest
141
+ else:
142
+ self._buffers[rid] = buf
143
+ if emit_text:
144
+ _emit({"type": self._kind, "id": rid, "data": emit_text})
104
145
  return len(data)
105
146
 
106
147
  def flush(self) -> None: # noqa: D401 - protocol method
148
+ rid = _CURRENT_RID.get()
149
+ if rid is not None:
150
+ self.flush_rid(rid)
107
151
  return None
108
152
 
153
+ def flush_rid(self, rid: str) -> None:
154
+ """Flush any buffered partial line for ``rid`` as its own frame."""
155
+ with self._lock:
156
+ buf = self._buffers.pop(rid, None)
157
+ if buf:
158
+ _emit({"type": self._kind, "id": rid, "data": buf})
159
+
160
+
161
+ def _flush_stream_proxies(rid: str) -> None:
162
+ """Drain buffered proxy output for ``rid`` (called before its done frame)."""
163
+ for stream in (sys.stdout, sys.stderr):
164
+ if isinstance(stream, _StreamProxy):
165
+ stream.flush_rid(rid)
166
+
109
167
 
110
168
  # ---------------------------------------------------------------------------
111
169
  # Runner state
@@ -125,6 +183,10 @@ class _RunnerState:
125
183
  self.last_install_marker: int = 0
126
184
  self.loop: asyncio.AbstractEventLoop | None = None
127
185
  self.active_executions: int = 0
186
+ # Best-effort attribution target for captured fd-1 bytes (child
187
+ # processes inheriting stdout). With overlapping requests the most
188
+ # recently started one wins — strictly better than dropping the bytes.
189
+ self.capture_rid: str | None = None
128
190
 
129
191
 
130
192
  _CURRENT_RID: contextvars.ContextVar[str | None] = contextvars.ContextVar("omp_current_rid", default=None)
@@ -132,6 +194,42 @@ _CURRENT_RID: contextvars.ContextVar[str | None] = contextvars.ContextVar("omp_c
132
194
  _STATE = _RunnerState()
133
195
 
134
196
 
197
+ def _drain_captured_stdout() -> None:
198
+ """Forward bytes written to the captured fd 1 as stdout frames.
199
+
200
+ Runs on a daemon thread for the life of the process. Child processes that
201
+ inherit fd 1 (any ``subprocess`` call without ``stdout=PIPE``) land here.
202
+ """
203
+ if _CAPTURE_READ_FD is None:
204
+ return
205
+ import codecs
206
+
207
+ decoder = codecs.getincrementaldecoder("utf-8")("replace")
208
+ while True:
209
+ try:
210
+ chunk = os.read(_CAPTURE_READ_FD, 65536)
211
+ except OSError:
212
+ return
213
+ if not chunk:
214
+ return
215
+ text = decoder.decode(chunk)
216
+ if not text:
217
+ continue
218
+ rid = _STATE.capture_rid
219
+ if rid is None:
220
+ _RAW_STDERR.write(text)
221
+ _RAW_STDERR.flush()
222
+ else:
223
+ _emit({"type": "stdout", "id": rid, "data": text})
224
+
225
+
226
+ def _start_capture_drain() -> None:
227
+ if _CAPTURE_READ_FD is None:
228
+ return
229
+ thread = threading.Thread(target=_drain_captured_stdout, name="omp-fd1-capture", daemon=True)
230
+ thread.start()
231
+
232
+
135
233
  # ---------------------------------------------------------------------------
136
234
  # Magic source transformer
137
235
  # ---------------------------------------------------------------------------
@@ -880,6 +978,7 @@ def _start_parent_watchdog() -> None:
880
978
  async def _handle_request_async(req: dict) -> None:
881
979
  rid = str(req.get("id"))
882
980
  token = _CURRENT_RID.set(rid)
981
+ _STATE.capture_rid = rid
883
982
  _STATE.user_ns["__omp_run_id__"] = rid
884
983
  _STATE.cancel_requested = False
885
984
  _STATE.execution_count += 1
@@ -934,6 +1033,7 @@ async def _handle_request_async(req: dict) -> None:
934
1033
  except Exception:
935
1034
  pass
936
1035
 
1036
+ _flush_stream_proxies(rid)
937
1037
  _emit({
938
1038
  "type": "done",
939
1039
  "id": rid,
@@ -942,6 +1042,9 @@ async def _handle_request_async(req: dict) -> None:
942
1042
  "cancelled": cancelled,
943
1043
  })
944
1044
  finally:
1045
+ if _STATE.capture_rid == rid:
1046
+ _STATE.capture_rid = None
1047
+ _flush_stream_proxies(rid)
945
1048
  _CURRENT_RID.reset(token)
946
1049
 
947
1050
 
@@ -986,6 +1089,7 @@ async def _main_async() -> None:
986
1089
  sys.stderr = _StreamProxy("stderr")
987
1090
  _install_idle_sigint()
988
1091
  _start_parent_watchdog()
1092
+ _start_capture_drain()
989
1093
 
990
1094
  stdin = sys.__stdin__
991
1095
  if stdin is None:
@@ -314,7 +314,9 @@ export async function executeBash(command: string, options?: BashExecutorOptions
314
314
  if (userSignal) {
315
315
  userSignal.removeEventListener("abort", abortHandler);
316
316
  }
317
- if (resetSession) {
317
+ if (resetSession || options?.sessionKey?.includes(":async:")) {
318
+ // `:async:` keys are per-job (jobId is unique), so the Shell would
319
+ // otherwise stay in the process-global map forever after completion.
318
320
  shellSessions.delete(sessionKey);
319
321
  }
320
322
  }