@oh-my-pi/pi-coding-agent 8.0.20 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (421) hide show
  1. package/CHANGELOG.md +125 -0
  2. package/docs/session.md +111 -46
  3. package/examples/custom-tools/hello/index.ts +1 -1
  4. package/examples/custom-tools/todo/index.ts +3 -4
  5. package/examples/extensions/api-demo.ts +0 -1
  6. package/examples/extensions/chalk-logger.ts +2 -3
  7. package/examples/extensions/hello.ts +0 -1
  8. package/examples/extensions/pirate.ts +0 -1
  9. package/examples/extensions/plan-mode.ts +15 -16
  10. package/examples/extensions/todo.ts +3 -4
  11. package/examples/extensions/tools.ts +1 -2
  12. package/examples/extensions/with-deps/index.ts +0 -1
  13. package/examples/hooks/auto-commit-on-exit.ts +1 -2
  14. package/examples/hooks/confirm-destructive.ts +0 -1
  15. package/examples/hooks/custom-compaction.ts +1 -2
  16. package/examples/hooks/dirty-repo-guard.ts +0 -1
  17. package/examples/hooks/file-trigger.ts +3 -4
  18. package/examples/hooks/git-checkpoint.ts +0 -1
  19. package/examples/hooks/handoff.ts +3 -4
  20. package/examples/hooks/permission-gate.ts +1 -2
  21. package/examples/hooks/protected-paths.ts +1 -2
  22. package/examples/hooks/qna.ts +2 -3
  23. package/examples/hooks/snake.ts +4 -5
  24. package/examples/hooks/status-line.ts +0 -1
  25. package/examples/sdk/01-minimal.ts +2 -3
  26. package/examples/sdk/02-custom-model.ts +2 -3
  27. package/examples/sdk/03-custom-prompt.ts +3 -4
  28. package/examples/sdk/04-skills.ts +2 -3
  29. package/examples/sdk/06-extensions.ts +1 -2
  30. package/examples/sdk/06-hooks.ts +6 -7
  31. package/examples/sdk/07-context-files.ts +0 -1
  32. package/examples/sdk/08-prompt-templates.ts +0 -1
  33. package/examples/sdk/08-slash-commands.ts +0 -1
  34. package/examples/sdk/09-api-keys-and-oauth.ts +0 -1
  35. package/examples/sdk/10-settings.ts +0 -1
  36. package/examples/sdk/11-sessions.ts +0 -1
  37. package/package.json +54 -23
  38. package/scripts/format-prompts.ts +0 -1
  39. package/src/capability/context-file.ts +3 -4
  40. package/src/capability/extension-module.ts +3 -4
  41. package/src/capability/extension.ts +3 -4
  42. package/src/capability/fs.ts +20 -21
  43. package/src/capability/hook.ts +3 -4
  44. package/src/capability/index.ts +15 -16
  45. package/src/capability/instruction.ts +3 -4
  46. package/src/capability/mcp.ts +3 -4
  47. package/src/capability/prompt.ts +3 -4
  48. package/src/capability/rule.ts +3 -4
  49. package/src/capability/settings.ts +2 -3
  50. package/src/capability/skill.ts +3 -4
  51. package/src/capability/slash-command.ts +3 -4
  52. package/src/capability/ssh.ts +3 -4
  53. package/src/capability/system-prompt.ts +3 -4
  54. package/src/capability/tool.ts +3 -4
  55. package/src/cli/args.ts +5 -6
  56. package/src/cli/config-cli.ts +6 -7
  57. package/src/cli/file-processor.ts +19 -17
  58. package/src/cli/jupyter-cli.ts +105 -0
  59. package/src/cli/list-models.ts +10 -11
  60. package/src/cli/plugin-cli.ts +20 -25
  61. package/src/cli/session-picker.ts +2 -3
  62. package/src/cli/setup-cli.ts +2 -3
  63. package/src/cli/stats-cli.ts +2 -3
  64. package/src/cli/update-cli.ts +25 -22
  65. package/src/commit/agentic/agent.ts +307 -0
  66. package/src/commit/agentic/fallback.ts +96 -0
  67. package/src/commit/agentic/index.ts +351 -0
  68. package/src/commit/agentic/prompts/analyze-file.md +22 -0
  69. package/src/commit/agentic/prompts/session-user.md +26 -0
  70. package/src/commit/agentic/prompts/split-confirm.md +1 -0
  71. package/src/commit/agentic/prompts/system.md +40 -0
  72. package/src/commit/agentic/state.ts +69 -0
  73. package/src/commit/agentic/tools/analyze-file.ts +131 -0
  74. package/src/commit/agentic/tools/git-file-diff.ts +194 -0
  75. package/src/commit/agentic/tools/git-hunk.ts +50 -0
  76. package/src/commit/agentic/tools/git-overview.ts +84 -0
  77. package/src/commit/agentic/tools/index.ts +56 -0
  78. package/src/commit/agentic/tools/propose-changelog.ts +128 -0
  79. package/src/commit/agentic/tools/propose-commit.ts +154 -0
  80. package/src/commit/agentic/tools/recent-commits.ts +81 -0
  81. package/src/commit/agentic/tools/split-commit.ts +280 -0
  82. package/src/commit/agentic/topo-sort.ts +44 -0
  83. package/src/commit/agentic/trivial.ts +51 -0
  84. package/src/commit/agentic/validation.ts +200 -0
  85. package/src/commit/analysis/conventional.ts +165 -0
  86. package/src/commit/analysis/index.ts +4 -0
  87. package/src/commit/analysis/scope.ts +242 -0
  88. package/src/commit/analysis/summary.ts +112 -0
  89. package/src/commit/analysis/validation.ts +66 -0
  90. package/src/commit/changelog/detect.ts +36 -0
  91. package/src/commit/changelog/generate.ts +110 -0
  92. package/src/commit/changelog/index.ts +233 -0
  93. package/src/commit/changelog/parse.ts +44 -0
  94. package/src/commit/cli.ts +93 -0
  95. package/src/commit/git/diff.ts +148 -0
  96. package/src/commit/git/errors.ts +11 -0
  97. package/src/commit/git/index.ts +212 -0
  98. package/src/commit/git/operations.ts +53 -0
  99. package/src/commit/index.ts +5 -0
  100. package/src/commit/map-reduce/index.ts +63 -0
  101. package/src/commit/map-reduce/map-phase.ts +178 -0
  102. package/src/commit/map-reduce/reduce-phase.ts +145 -0
  103. package/src/commit/map-reduce/utils.ts +9 -0
  104. package/src/commit/message.ts +11 -0
  105. package/src/commit/model-selection.ts +80 -0
  106. package/src/commit/pipeline.ts +240 -0
  107. package/src/commit/prompts/analysis-system.md +155 -0
  108. package/src/commit/prompts/analysis-user.md +41 -0
  109. package/src/commit/prompts/changelog-system.md +56 -0
  110. package/src/commit/prompts/changelog-user.md +19 -0
  111. package/src/commit/prompts/file-observer-system.md +26 -0
  112. package/src/commit/prompts/file-observer-user.md +9 -0
  113. package/src/commit/prompts/reduce-system.md +60 -0
  114. package/src/commit/prompts/reduce-user.md +17 -0
  115. package/src/commit/prompts/summary-retry.md +4 -0
  116. package/src/commit/prompts/summary-system.md +52 -0
  117. package/src/commit/prompts/summary-user.md +13 -0
  118. package/src/commit/prompts/types-description.md +2 -0
  119. package/src/commit/types.ts +109 -0
  120. package/src/commit/utils/exclusions.ts +42 -0
  121. package/src/config/file-lock.ts +121 -0
  122. package/src/config/keybindings.ts +6 -8
  123. package/src/config/model-registry.ts +65 -38
  124. package/src/config/model-resolver.ts +18 -19
  125. package/src/config/prompt-templates.ts +11 -11
  126. package/src/config/settings-manager.ts +141 -50
  127. package/src/config.ts +64 -66
  128. package/src/cursor.ts +11 -9
  129. package/src/discovery/agents-md.ts +11 -12
  130. package/src/discovery/builtin.ts +68 -73
  131. package/src/discovery/claude.ts +41 -42
  132. package/src/discovery/cline.ts +11 -12
  133. package/src/discovery/codex.ts +52 -53
  134. package/src/discovery/cursor.ts +9 -10
  135. package/src/discovery/gemini.ts +17 -22
  136. package/src/discovery/github.ts +13 -14
  137. package/src/discovery/helpers.ts +35 -34
  138. package/src/discovery/index.ts +22 -24
  139. package/src/discovery/mcp-json.ts +8 -9
  140. package/src/discovery/ssh.ts +8 -9
  141. package/src/discovery/vscode.ts +4 -5
  142. package/src/discovery/windsurf.ts +6 -7
  143. package/src/exa/company.ts +1 -2
  144. package/src/exa/index.ts +2 -3
  145. package/src/exa/linkedin.ts +1 -2
  146. package/src/exa/mcp-client.ts +14 -16
  147. package/src/exa/render.ts +10 -11
  148. package/src/exa/researcher.ts +1 -2
  149. package/src/exa/search.ts +1 -2
  150. package/src/exa/types.ts +0 -1
  151. package/src/exa/websets.ts +1 -2
  152. package/src/exec/bash-executor.ts +3 -4
  153. package/src/exec/exec.ts +0 -1
  154. package/src/export/custom-share.ts +5 -6
  155. package/src/export/html/index.ts +24 -21
  156. package/src/export/ttsr.ts +2 -3
  157. package/src/extensibility/custom-commands/bundled/review/index.ts +7 -8
  158. package/src/extensibility/custom-commands/loader.ts +18 -15
  159. package/src/extensibility/custom-commands/types.ts +2 -3
  160. package/src/extensibility/custom-tools/loader.ts +11 -12
  161. package/src/extensibility/custom-tools/types.ts +7 -8
  162. package/src/extensibility/custom-tools/wrapper.ts +2 -3
  163. package/src/extensibility/extensions/loader.ts +76 -54
  164. package/src/extensibility/extensions/runner.ts +11 -12
  165. package/src/extensibility/extensions/types.ts +20 -27
  166. package/src/extensibility/extensions/wrapper.ts +3 -4
  167. package/src/extensibility/hooks/index.ts +1 -1
  168. package/src/extensibility/hooks/loader.ts +9 -10
  169. package/src/extensibility/hooks/runner.ts +7 -8
  170. package/src/extensibility/hooks/tool-wrapper.ts +0 -1
  171. package/src/extensibility/hooks/types.ts +11 -18
  172. package/src/extensibility/plugins/doctor.ts +3 -3
  173. package/src/extensibility/plugins/installer.ts +27 -27
  174. package/src/extensibility/plugins/loader.ts +59 -56
  175. package/src/extensibility/plugins/manager.ts +211 -171
  176. package/src/extensibility/plugins/parser.ts +1 -1
  177. package/src/extensibility/plugins/paths.ts +8 -8
  178. package/src/extensibility/skills.ts +63 -60
  179. package/src/extensibility/slash-commands.ts +10 -10
  180. package/src/index.ts +54 -54
  181. package/src/internal-urls/agent-protocol.ts +21 -11
  182. package/src/internal-urls/artifact-protocol.ts +17 -13
  183. package/src/internal-urls/router.ts +1 -2
  184. package/src/internal-urls/rule-protocol.ts +3 -4
  185. package/src/internal-urls/skill-protocol.ts +3 -4
  186. package/src/ipy/executor.ts +109 -9
  187. package/src/ipy/gateway-coordinator.ts +79 -90
  188. package/src/ipy/kernel.ts +32 -30
  189. package/src/ipy/modules.ts +13 -13
  190. package/src/lsp/client.ts +21 -10
  191. package/src/lsp/clients/biome-client.ts +1 -2
  192. package/src/lsp/clients/index.ts +3 -3
  193. package/src/lsp/clients/lsp-linter-client.ts +4 -5
  194. package/src/lsp/config.ts +15 -15
  195. package/src/lsp/edits.ts +4 -5
  196. package/src/lsp/index.ts +43 -44
  197. package/src/lsp/lspmux.ts +8 -8
  198. package/src/lsp/render.ts +99 -61
  199. package/src/lsp/utils.ts +3 -3
  200. package/src/main.ts +71 -37
  201. package/src/mcp/client.ts +2 -3
  202. package/src/mcp/config.ts +5 -6
  203. package/src/mcp/json-rpc.ts +0 -1
  204. package/src/mcp/loader.ts +6 -7
  205. package/src/mcp/manager.ts +17 -18
  206. package/src/mcp/tool-bridge.ts +4 -9
  207. package/src/mcp/tool-cache.ts +2 -3
  208. package/src/mcp/transports/http.ts +2 -4
  209. package/src/mcp/transports/stdio.ts +1 -2
  210. package/src/migrations.ts +63 -52
  211. package/src/modes/components/armin.ts +4 -5
  212. package/src/modes/components/assistant-message.ts +33 -5
  213. package/src/modes/components/bash-execution.ts +7 -8
  214. package/src/modes/components/bordered-loader.ts +3 -3
  215. package/src/modes/components/branch-summary-message.ts +3 -3
  216. package/src/modes/components/compaction-summary-message.ts +3 -3
  217. package/src/modes/components/countdown-timer.ts +0 -1
  218. package/src/modes/components/custom-message.ts +5 -5
  219. package/src/modes/components/diff.ts +1 -1
  220. package/src/modes/components/dynamic-border.ts +2 -2
  221. package/src/modes/components/extensions/extension-dashboard.ts +6 -7
  222. package/src/modes/components/extensions/extension-list.ts +2 -3
  223. package/src/modes/components/extensions/inspector-panel.ts +3 -4
  224. package/src/modes/components/extensions/state-manager.ts +25 -26
  225. package/src/modes/components/extensions/types.ts +1 -2
  226. package/src/modes/components/footer.ts +47 -43
  227. package/src/modes/components/history-search.ts +2 -2
  228. package/src/modes/components/hook-editor.ts +3 -4
  229. package/src/modes/components/hook-input.ts +2 -3
  230. package/src/modes/components/hook-message.ts +5 -5
  231. package/src/modes/components/hook-selector.ts +2 -3
  232. package/src/modes/components/keybinding-hints.ts +2 -3
  233. package/src/modes/components/login-dialog.ts +2 -2
  234. package/src/modes/components/model-selector.ts +12 -12
  235. package/src/modes/components/oauth-selector.ts +2 -2
  236. package/src/modes/components/plugin-settings.ts +20 -20
  237. package/src/modes/components/python-execution.ts +7 -8
  238. package/src/modes/components/queue-mode-selector.ts +3 -3
  239. package/src/modes/components/read-tool-group.ts +2 -2
  240. package/src/modes/components/session-selector.ts +4 -4
  241. package/src/modes/components/settings-defs.ts +77 -69
  242. package/src/modes/components/settings-selector.ts +16 -16
  243. package/src/modes/components/show-images-selector.ts +2 -2
  244. package/src/modes/components/status-line/segments.ts +4 -4
  245. package/src/modes/components/status-line/separators.ts +1 -1
  246. package/src/modes/components/status-line/types.ts +2 -2
  247. package/src/modes/components/status-line-segment-editor.ts +7 -8
  248. package/src/modes/components/status-line.ts +12 -12
  249. package/src/modes/components/theme-selector.ts +8 -7
  250. package/src/modes/components/thinking-selector.ts +4 -4
  251. package/src/modes/components/todo-display.ts +2 -2
  252. package/src/modes/components/todo-reminder.ts +4 -4
  253. package/src/modes/components/tool-execution.ts +16 -19
  254. package/src/modes/components/tree-selector.ts +12 -12
  255. package/src/modes/components/ttsr-notification.ts +5 -5
  256. package/src/modes/components/user-message-selector.ts +1 -1
  257. package/src/modes/components/user-message.ts +1 -1
  258. package/src/modes/components/visual-truncate.ts +0 -1
  259. package/src/modes/components/welcome.ts +4 -4
  260. package/src/modes/controllers/command-controller.ts +46 -47
  261. package/src/modes/controllers/event-controller.ts +16 -20
  262. package/src/modes/controllers/extension-ui-controller.ts +40 -46
  263. package/src/modes/controllers/input-controller.ts +17 -18
  264. package/src/modes/controllers/selector-controller.ts +103 -91
  265. package/src/modes/index.ts +3 -3
  266. package/src/modes/interactive-mode.ts +31 -31
  267. package/src/modes/print-mode.ts +12 -13
  268. package/src/modes/rpc/rpc-client.ts +7 -8
  269. package/src/modes/rpc/rpc-mode.ts +24 -28
  270. package/src/modes/rpc/rpc-types.ts +3 -4
  271. package/src/modes/theme/mermaid-cache.ts +89 -0
  272. package/src/modes/theme/theme.ts +130 -53
  273. package/src/modes/types.ts +10 -10
  274. package/src/modes/utils/ui-helpers.ts +17 -17
  275. package/src/patch/applicator.ts +18 -19
  276. package/src/patch/diff.ts +1 -2
  277. package/src/patch/fuzzy.ts +1 -2
  278. package/src/patch/index.ts +11 -18
  279. package/src/patch/normalize.ts +4 -4
  280. package/src/patch/normative.ts +1 -2
  281. package/src/patch/parser.ts +8 -9
  282. package/src/patch/shared.ts +43 -16
  283. package/src/prompts/tools/task.md +2 -0
  284. package/src/sdk.ts +100 -65
  285. package/src/session/agent-session.ts +84 -85
  286. package/src/session/agent-storage.ts +43 -39
  287. package/src/session/artifacts.ts +32 -10
  288. package/src/session/auth-storage.ts +50 -39
  289. package/src/session/compaction/branch-summarization.ts +7 -10
  290. package/src/session/compaction/compaction.ts +8 -19
  291. package/src/session/compaction/utils.ts +6 -9
  292. package/src/session/history-storage.ts +10 -10
  293. package/src/session/messages.ts +4 -5
  294. package/src/session/session-manager.ts +76 -65
  295. package/src/session/session-storage.ts +57 -69
  296. package/src/session/storage-migration.ts +14 -56
  297. package/src/session/streaming-output.ts +2 -2
  298. package/src/ssh/connection-manager.ts +43 -50
  299. package/src/ssh/ssh-executor.ts +2 -2
  300. package/src/ssh/sshfs-mount.ts +11 -18
  301. package/src/system-prompt.ts +28 -35
  302. package/src/task/agents.ts +45 -30
  303. package/src/task/commands.ts +6 -7
  304. package/src/task/discovery.ts +39 -76
  305. package/src/task/executor.ts +14 -15
  306. package/src/task/index.ts +40 -34
  307. package/src/task/output-manager.ts +93 -0
  308. package/src/task/parallel.ts +0 -1
  309. package/src/task/render.ts +24 -30
  310. package/src/task/subprocess-tool-registry.ts +1 -2
  311. package/src/task/worker-protocol.ts +3 -3
  312. package/src/task/worker.ts +33 -39
  313. package/src/task/worktree.ts +19 -19
  314. package/src/tools/ask.ts +41 -20
  315. package/src/tools/bash-interceptor.ts +1 -5
  316. package/src/tools/bash.ts +91 -97
  317. package/src/tools/calculator.ts +49 -47
  318. package/src/tools/complete.ts +4 -5
  319. package/src/tools/context.ts +2 -2
  320. package/src/tools/fetch.ts +84 -124
  321. package/src/tools/find.ts +94 -98
  322. package/src/tools/gemini-image.ts +14 -14
  323. package/src/tools/grep.ts +100 -116
  324. package/src/tools/index.ts +80 -55
  325. package/src/tools/list-limit.ts +1 -1
  326. package/src/tools/ls.ts +44 -70
  327. package/src/tools/notebook.ts +51 -67
  328. package/src/tools/output-meta.ts +3 -4
  329. package/src/tools/output-utils.ts +2 -2
  330. package/src/tools/path-utils.ts +5 -5
  331. package/src/tools/python.ts +104 -217
  332. package/src/tools/read.ts +92 -33
  333. package/src/tools/render-utils.ts +8 -23
  334. package/src/tools/renderers.ts +6 -7
  335. package/src/tools/review.ts +8 -11
  336. package/src/tools/ssh.ts +69 -49
  337. package/src/tools/todo-write.ts +37 -25
  338. package/src/tools/tool-errors.ts +3 -3
  339. package/src/tools/tool-result.ts +3 -8
  340. package/src/tools/write.ts +99 -75
  341. package/src/tui/code-cell.ts +109 -0
  342. package/src/tui/file-list.ts +47 -0
  343. package/src/tui/index.ts +11 -0
  344. package/src/tui/output-block.ts +72 -0
  345. package/src/tui/status-line.ts +39 -0
  346. package/src/tui/tree-list.ts +55 -0
  347. package/src/tui/types.ts +16 -0
  348. package/src/tui/utils.ts +48 -0
  349. package/src/utils/changelog.ts +9 -10
  350. package/src/utils/clipboard.ts +11 -11
  351. package/src/utils/file-mentions.ts +4 -10
  352. package/src/utils/frontmatter.ts +6 -3
  353. package/src/utils/fuzzy.ts +2 -2
  354. package/src/utils/image-convert.ts +1 -1
  355. package/src/utils/image-resize.ts +1 -1
  356. package/src/utils/mime.ts +2 -2
  357. package/src/utils/shell-snapshot.ts +11 -13
  358. package/src/utils/shell.ts +4 -5
  359. package/src/utils/title-generator.ts +8 -9
  360. package/src/utils/tools-manager.ts +23 -23
  361. package/src/vendor/photon/index.js +1099 -1059
  362. package/src/vendor/photon/photon_rs_bg.wasm +0 -0
  363. package/src/web/scrapers/artifacthub.ts +1 -1
  364. package/src/web/scrapers/arxiv.ts +2 -2
  365. package/src/web/scrapers/bluesky.ts +2 -2
  366. package/src/web/scrapers/cheatsh.ts +1 -1
  367. package/src/web/scrapers/chocolatey.ts +2 -2
  368. package/src/web/scrapers/choosealicense.ts +5 -5
  369. package/src/web/scrapers/cisa-kev.ts +1 -1
  370. package/src/web/scrapers/crossref.ts +2 -2
  371. package/src/web/scrapers/devto.ts +3 -3
  372. package/src/web/scrapers/discogs.ts +3 -4
  373. package/src/web/scrapers/discourse.ts +1 -1
  374. package/src/web/scrapers/dockerhub.ts +1 -1
  375. package/src/web/scrapers/fdroid.ts +2 -2
  376. package/src/web/scrapers/firefox-addons.ts +3 -3
  377. package/src/web/scrapers/flathub.ts +1 -1
  378. package/src/web/scrapers/github.ts +3 -3
  379. package/src/web/scrapers/gitlab.ts +4 -4
  380. package/src/web/scrapers/hackernews.ts +2 -2
  381. package/src/web/scrapers/huggingface.ts +1 -1
  382. package/src/web/scrapers/iacr.ts +2 -2
  383. package/src/web/scrapers/index.ts +0 -1
  384. package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
  385. package/src/web/scrapers/lemmy.ts +2 -2
  386. package/src/web/scrapers/maven.ts +2 -2
  387. package/src/web/scrapers/mdn.ts +2 -4
  388. package/src/web/scrapers/metacpan.ts +2 -2
  389. package/src/web/scrapers/musicbrainz.ts +1 -2
  390. package/src/web/scrapers/npm.ts +1 -1
  391. package/src/web/scrapers/nuget.ts +2 -2
  392. package/src/web/scrapers/nvd.ts +3 -3
  393. package/src/web/scrapers/ollama.ts +7 -9
  394. package/src/web/scrapers/opencorporates.ts +2 -2
  395. package/src/web/scrapers/openlibrary.ts +6 -6
  396. package/src/web/scrapers/orcid.ts +0 -1
  397. package/src/web/scrapers/osv.ts +2 -2
  398. package/src/web/scrapers/packagist.ts +1 -1
  399. package/src/web/scrapers/pubmed.ts +1 -2
  400. package/src/web/scrapers/rawg.ts +2 -2
  401. package/src/web/scrapers/readthedocs.ts +1 -2
  402. package/src/web/scrapers/repology.ts +2 -2
  403. package/src/web/scrapers/rfc.ts +1 -1
  404. package/src/web/scrapers/searchcode.ts +2 -2
  405. package/src/web/scrapers/semantic-scholar.ts +1 -1
  406. package/src/web/scrapers/snapcraft.ts +2 -2
  407. package/src/web/scrapers/sourcegraph.ts +1 -1
  408. package/src/web/scrapers/spdx.ts +3 -3
  409. package/src/web/scrapers/spotify.ts +0 -1
  410. package/src/web/scrapers/twitter.ts +1 -1
  411. package/src/web/scrapers/types.ts +1 -2
  412. package/src/web/scrapers/utils.ts +5 -5
  413. package/src/web/scrapers/wikidata.ts +3 -3
  414. package/src/web/scrapers/youtube.ts +9 -14
  415. package/src/web/search/auth.ts +5 -10
  416. package/src/web/search/index.ts +11 -21
  417. package/src/web/search/providers/anthropic.ts +3 -9
  418. package/src/web/search/providers/exa.ts +6 -10
  419. package/src/web/search/providers/perplexity.ts +5 -5
  420. package/src/web/search/render.ts +129 -175
  421. package/tsconfig.json +0 -42
@@ -1,23 +1,24 @@
1
- import { relative, resolve, sep } from "node:path";
1
+ import * as path from "node:path";
2
2
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
3
3
  import type { ImageContent } from "@oh-my-pi/pi-ai";
4
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
5
- import type { RenderResultOptions } from "@oh-my-pi/pi-coding-agent/extensibility/custom-tools/types";
6
- import { executePython, getPreludeDocs, type PythonExecutorOptions } from "@oh-my-pi/pi-coding-agent/ipy/executor";
7
- import type { PreludeHelper, PythonStatusEvent } from "@oh-my-pi/pi-coding-agent/ipy/kernel";
8
- import { truncateToVisualLines } from "@oh-my-pi/pi-coding-agent/modes/components/visual-truncate";
9
- import { highlightCode, type Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
10
- import pythonDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/python.md" with { type: "text" };
11
- import { OutputSink, type OutputSummary } from "@oh-my-pi/pi-coding-agent/session/streaming-output";
12
- import type { OutputMeta } from "@oh-my-pi/pi-coding-agent/tools/output-meta";
13
- import { ToolAbortError, ToolError } from "@oh-my-pi/pi-coding-agent/tools/tool-errors";
14
4
  import type { Component } from "@oh-my-pi/pi-tui";
15
- import { Text, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
5
+ import { Text, truncateToWidth } from "@oh-my-pi/pi-tui";
16
6
  import { type Static, Type } from "@sinclair/typebox";
17
- import type { ToolSession } from "./index";
7
+ import { renderPromptTemplate } from "../config/prompt-templates";
8
+ import type { RenderResultOptions } from "../extensibility/custom-tools/types";
9
+ import { executePython, getPreludeDocs, type PythonExecutorOptions } from "../ipy/executor";
10
+ import type { PreludeHelper, PythonStatusEvent } from "../ipy/kernel";
11
+ import { truncateToVisualLines } from "../modes/components/visual-truncate";
12
+ import type { Theme } from "../modes/theme/theme";
13
+ import pythonDescription from "../prompts/tools/python.md" with { type: "text" };
14
+ import { OutputSink, type OutputSummary } from "../session/streaming-output";
15
+ import { getTreeBranch, getTreeContinuePrefix, renderCodeCell } from "../tui";
16
+ import type { ToolSession } from ".";
17
+ import type { OutputMeta } from "./output-meta";
18
18
  import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
19
19
  import { resolveToCwd } from "./path-utils";
20
- import { getTreeBranch, getTreeContinuePrefix, shortenPath, ToolUIKit, truncate } from "./render-utils";
20
+ import { shortenPath, ToolUIKit, truncate } from "./render-utils";
21
+ import { ToolAbortError, ToolError } from "./tool-errors";
21
22
  import { toolResult } from "./tool-result";
22
23
  import { DEFAULT_MAX_BYTES } from "./truncate";
23
24
 
@@ -176,9 +177,8 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
176
177
  // Clamp to reasonable range: 1s - 600s (10 min)
177
178
  timeoutSec = Math.max(1, Math.min(600, timeoutSec));
178
179
  const timeoutMs = timeoutSec * 1000;
179
- const controller = new AbortController();
180
- const onAbort = () => controller.abort();
181
- signal?.addEventListener("abort", onAbort, { once: true });
180
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
181
+ const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
182
182
  let outputSink: OutputSink | undefined;
183
183
  let outputSummary: OutputSummary | undefined;
184
184
  let outputDumped = false;
@@ -225,7 +225,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
225
225
 
226
226
  const buildUpdateDetails = (): PythonToolDetails => {
227
227
  const details: PythonToolDetails = {
228
- cells: cellResults.map((cell) => ({
228
+ cells: cellResults.map(cell => ({
229
229
  ...cell,
230
230
  statusEvents: cell.statusEvents ? [...cell.statusEvents] : undefined,
231
231
  })),
@@ -257,7 +257,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
257
257
  outputSink = new OutputSink({
258
258
  artifactPath,
259
259
  artifactId,
260
- onChunk: (chunk) => {
260
+ onChunk: chunk => {
261
261
  appendTail(chunk);
262
262
  pushUpdate();
263
263
  },
@@ -266,7 +266,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
266
266
  const baseExecutorOptions: Omit<PythonExecutorOptions, "reset"> = {
267
267
  cwd: commandCwd,
268
268
  timeoutMs,
269
- signal: controller.signal,
269
+ signal: combinedSignal,
270
270
  sessionId,
271
271
  kernelMode: this.session.settings?.getPythonKernelMode?.() ?? "session",
272
272
  useSharedGateway: this.session.settings?.getPythonSharedGateway?.() ?? true,
@@ -288,7 +288,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
288
288
  const executorOptions: PythonExecutorOptions = {
289
289
  ...baseExecutorOptions,
290
290
  reset: isFirstCell ? reset : false,
291
- onChunk: async (chunk) => {
291
+ onChunk: async chunk => {
292
292
  await outputSink!.push(chunk);
293
293
  },
294
294
  };
@@ -397,7 +397,6 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
397
397
 
398
398
  return resultBuilder.done();
399
399
  } finally {
400
- signal?.removeEventListener("abort", onAbort);
401
400
  if (!outputDumped) {
402
401
  try {
403
402
  await finalizeOutput();
@@ -639,14 +638,14 @@ function formatStatusEventExpanded(event: PythonStatusEvent, theme: Theme): stri
639
638
  switch (op) {
640
639
  case "find":
641
640
  case "glob":
642
- if (data.matches) addItems(data.matches as unknown[], (m) => String(m));
641
+ if (data.matches) addItems(data.matches as unknown[], m => String(m));
643
642
  break;
644
643
  case "ls":
645
- if (data.items) addItems(data.items as unknown[], (m) => String(m));
644
+ if (data.items) addItems(data.items as unknown[], m => String(m));
646
645
  break;
647
646
  case "grep":
648
647
  if (data.hits) {
649
- addItems(data.hits as unknown[], (h) => {
648
+ addItems(data.hits as unknown[], h => {
650
649
  const hit = h as { line: number; text: string };
651
650
  return `${hit.line}: ${truncate(hit.text, 60, theme.format.ellipsis)}`;
652
651
  });
@@ -654,7 +653,7 @@ function formatStatusEventExpanded(event: PythonStatusEvent, theme: Theme): stri
654
653
  break;
655
654
  case "rgrep":
656
655
  if (data.hits) {
657
- addItems(data.hits as unknown[], (h) => {
656
+ addItems(data.hits as unknown[], h => {
658
657
  const hit = h as { file: string; line: number; text: string };
659
658
  return `${shortenPath(hit.file)}:${hit.line}: ${truncate(hit.text, 50, theme.format.ellipsis)}`;
660
659
  });
@@ -662,28 +661,28 @@ function formatStatusEventExpanded(event: PythonStatusEvent, theme: Theme): stri
662
661
  break;
663
662
  case "rsed":
664
663
  if (data.changed) {
665
- addItems(data.changed as unknown[], (c) => {
664
+ addItems(data.changed as unknown[], c => {
666
665
  const change = c as { file: string; count: number };
667
666
  return `${shortenPath(change.file)}: ${change.count} replacement${change.count !== 1 ? "s" : ""}`;
668
667
  });
669
668
  }
670
669
  break;
671
670
  case "env":
672
- if (data.keys) addItems(data.keys as unknown[], (k) => String(k), 10);
671
+ if (data.keys) addItems(data.keys as unknown[], k => String(k), 10);
673
672
  break;
674
673
  case "git_log":
675
674
  if (data.entries) {
676
- addItems(data.entries as unknown[], (e) => {
675
+ addItems(data.entries as unknown[], e => {
677
676
  const entry = e as { sha: string; subject: string };
678
677
  return `${entry.sha} ${truncate(entry.subject, 50, theme.format.ellipsis)}`;
679
678
  });
680
679
  }
681
680
  break;
682
681
  case "git_status":
683
- if (data.files) addItems(data.files as unknown[], (f) => String(f));
682
+ if (data.files) addItems(data.files as unknown[], f => String(f));
684
683
  break;
685
684
  case "git_branch":
686
- if (data.branches) addItems(data.branches as unknown[], (b) => String(b));
685
+ if (data.branches) addItems(data.branches as unknown[], b => String(b));
687
686
  break;
688
687
  case "read":
689
688
  case "cat":
@@ -740,56 +739,6 @@ function renderStatusEvents(events: PythonStatusEvent[], theme: Theme, expanded:
740
739
  return lines;
741
740
  }
742
741
 
743
- function applyCellBackground(line: string, width: number, bgFn?: (text: string) => string): string {
744
- if (!bgFn) return line;
745
- if (width <= 0) return bgFn(line);
746
- const paddingNeeded = Math.max(0, width - visibleWidth(line));
747
- const padded = line + " ".repeat(paddingNeeded);
748
- return bgFn(padded);
749
- }
750
-
751
- function highlightPythonCode(code?: string): string[] {
752
- return highlightCode(code ?? "", "python");
753
- }
754
-
755
- function formatCellStatus(cell: PythonCellResult, ui: ToolUIKit, spinnerFrame?: number): string | undefined {
756
- switch (cell.status) {
757
- case "pending":
758
- return `${ui.statusIcon("pending")} ${ui.theme.fg("muted", "pending")}`;
759
- case "running":
760
- return `${ui.statusIcon("running", spinnerFrame)} ${ui.theme.fg("muted", "running")}`;
761
- case "complete":
762
- return ui.statusIcon("success");
763
- case "error":
764
- return ui.statusIcon("error");
765
- }
766
- }
767
-
768
- function formatCellHeader(
769
- cell: PythonCellResult,
770
- index: number,
771
- total: number,
772
- ui: ToolUIKit,
773
- spinnerFrame?: number,
774
- workdirLabel?: string,
775
- ): string {
776
- const indexLabel = ui.theme.fg("accent", `[${index + 1}/${total}]`);
777
- const title = cell.title ? ` ${cell.title}` : "";
778
- const metaParts: string[] = [];
779
- if (workdirLabel) {
780
- metaParts.push(ui.theme.fg("dim", workdirLabel));
781
- }
782
- if (cell.durationMs !== undefined) {
783
- metaParts.push(ui.theme.fg("dim", `(${ui.formatDuration(cell.durationMs)})`));
784
- }
785
- const statusLabel = formatCellStatus(cell, ui, spinnerFrame);
786
- if (statusLabel) {
787
- metaParts.push(statusLabel);
788
- }
789
- const meta = metaParts.length > 0 ? ` ${metaParts.join(ui.theme.fg("dim", ui.theme.sep.dot))}` : "";
790
- return `${indexLabel}${title}${meta}`;
791
- }
792
-
793
742
  function formatCellOutputLines(
794
743
  cell: PythonCellResult,
795
744
  expanded: boolean,
@@ -799,7 +748,7 @@ function formatCellOutputLines(
799
748
  const rawLines = cell.output ? cell.output.split("\n") : [];
800
749
  const displayLines = expanded ? rawLines : rawLines.slice(-previewLines);
801
750
  const hiddenCount = rawLines.length - displayLines.length;
802
- const outputLines = displayLines.map((line) => theme.fg("toolOutput", line));
751
+ const outputLines = displayLines.map(line => theme.fg("toolOutput", line));
803
752
 
804
753
  if (outputLines.length === 0) {
805
754
  return { lines: [], hiddenCount: 0 };
@@ -808,101 +757,6 @@ function formatCellOutputLines(
808
757
  return { lines: outputLines, hiddenCount };
809
758
  }
810
759
 
811
- function renderCellBlock(
812
- cell: PythonCellResult,
813
- index: number,
814
- total: number,
815
- ui: ToolUIKit,
816
- options: {
817
- expanded: boolean;
818
- previewLines: number;
819
- spinnerFrame?: number;
820
- showOutput: boolean;
821
- workdirLabel?: string;
822
- width: number;
823
- bgFn?: (text: string) => string;
824
- },
825
- ): string[] {
826
- const { expanded, previewLines, spinnerFrame, showOutput, workdirLabel, width, bgFn } = options;
827
- const h = ui.theme.boxSharp.horizontal;
828
- const v = ui.theme.boxSharp.vertical;
829
- const cap = h.repeat(3);
830
- const border = (text: string) => ui.theme.fg("dim", text);
831
- const lineWidth = Math.max(0, width);
832
-
833
- const buildBarLine = (leftChar: string, label?: string): string => {
834
- const left = border(`${leftChar}${cap}`);
835
- if (lineWidth <= 0) return left;
836
- const rawLabel = label ? ` ${label} ` : " ";
837
- const maxLabelWidth = Math.max(0, lineWidth - visibleWidth(left));
838
- const trimmedLabel = truncateToWidth(rawLabel, maxLabelWidth, ui.theme.format.ellipsis);
839
- const fillCount = Math.max(0, lineWidth - visibleWidth(left + trimmedLabel));
840
- return `${left}${trimmedLabel}${border(h.repeat(fillCount))}`;
841
- };
842
-
843
- const lines: string[] = [];
844
- lines.push(
845
- applyCellBackground(
846
- buildBarLine(ui.theme.boxSharp.topLeft, formatCellHeader(cell, index, total, ui, spinnerFrame, workdirLabel)),
847
- lineWidth,
848
- bgFn,
849
- ),
850
- );
851
-
852
- const codePrefix = border(`${v} `);
853
- const codeWidth = Math.max(0, lineWidth - visibleWidth(codePrefix));
854
- const codeLines = highlightPythonCode(cell.code);
855
- for (const line of codeLines) {
856
- const text = truncateToWidth(line, codeWidth, ui.theme.format.ellipsis);
857
- lines.push(applyCellBackground(`${codePrefix}${text}`, lineWidth, bgFn));
858
- }
859
-
860
- const statusLines = renderStatusEvents(cell.statusEvents ?? [], ui.theme, expanded);
861
- const outputContent = formatCellOutputLines(cell, expanded, previewLines, ui.theme);
862
- const hasOutput = outputContent.lines.length > 0;
863
- const hasStatus = statusLines.length > 0;
864
- const showOutputSection = showOutput && (hasOutput || hasStatus);
865
-
866
- if (showOutputSection) {
867
- lines.push(
868
- applyCellBackground(
869
- buildBarLine(ui.theme.boxSharp.teeRight, ui.theme.fg("toolTitle", "Output")),
870
- lineWidth,
871
- bgFn,
872
- ),
873
- );
874
-
875
- for (const line of outputContent.lines) {
876
- const text = truncateToWidth(line, codeWidth, ui.theme.format.ellipsis);
877
- lines.push(applyCellBackground(`${codePrefix}${text}`, lineWidth, bgFn));
878
- }
879
- if (!expanded && outputContent.hiddenCount > 0) {
880
- const hint = ui.theme.fg(
881
- "dim",
882
- `${ui.theme.format.ellipsis} ${outputContent.hiddenCount} more lines (ctrl+o to expand)`,
883
- );
884
- lines.push(
885
- applyCellBackground(
886
- `${codePrefix}${truncateToWidth(hint, codeWidth, ui.theme.format.ellipsis)}`,
887
- lineWidth,
888
- bgFn,
889
- ),
890
- );
891
- }
892
-
893
- for (const line of statusLines) {
894
- const text = truncateToWidth(line, codeWidth, ui.theme.format.ellipsis);
895
- lines.push(applyCellBackground(`${codePrefix}${text}`, lineWidth, bgFn));
896
- }
897
- }
898
-
899
- const bottomLeft = border(`${ui.theme.boxSharp.bottomLeft}${cap}`);
900
- const bottomFillCount = Math.max(0, lineWidth - visibleWidth(bottomLeft));
901
- const bottomLine = `${bottomLeft}${border(h.repeat(bottomFillCount))}`;
902
- lines.push(applyCellBackground(bottomLine, lineWidth, bgFn));
903
- return lines;
904
- }
905
-
906
760
  export const pythonToolRenderer = {
907
761
  renderCall(args: PythonRenderArgs, uiTheme: Theme): Component {
908
762
  const ui = new ToolUIKit(uiTheme);
@@ -911,13 +765,14 @@ export const pythonToolRenderer = {
911
765
  let displayWorkdir = args.cwd;
912
766
 
913
767
  if (displayWorkdir) {
914
- const resolvedCwd = resolve(cwd);
915
- const resolvedWorkdir = resolve(displayWorkdir);
768
+ const resolvedCwd = path.resolve(cwd);
769
+ const resolvedWorkdir = path.resolve(displayWorkdir);
916
770
  if (resolvedWorkdir === resolvedCwd) {
917
771
  displayWorkdir = undefined;
918
772
  } else {
919
- const relativePath = relative(resolvedCwd, resolvedWorkdir);
920
- const isWithinCwd = relativePath && !relativePath.startsWith("..") && !relativePath.startsWith(`..${sep}`);
773
+ const relativePath = path.relative(resolvedCwd, resolvedWorkdir);
774
+ const isWithinCwd =
775
+ relativePath && !relativePath.startsWith("..") && !relativePath.startsWith(`..${path.sep}`);
921
776
  if (isWithinCwd) {
922
777
  displayWorkdir = relativePath;
923
778
  }
@@ -937,23 +792,24 @@ export const pythonToolRenderer = {
937
792
  const lines: string[] = [];
938
793
  for (let i = 0; i < cells.length; i++) {
939
794
  const cell = cells[i];
940
- const cellResult: PythonCellResult = {
941
- index: i,
942
- title: cell.title,
943
- code: cell.code,
944
- output: "",
945
- status: "pending",
946
- };
947
- lines.push(
948
- ...renderCellBlock(cellResult, i, cells.length, ui, {
949
- expanded: true,
950
- previewLines: PYTHON_DEFAULT_PREVIEW_LINES,
951
- showOutput: false,
952
- workdirLabel: i === 0 ? workdirLabel : undefined,
795
+ const cellTitle = cell.title;
796
+ const combinedTitle =
797
+ cellTitle && workdirLabel ? `${workdirLabel} · ${cellTitle}` : (cellTitle ?? workdirLabel);
798
+ const cellLines = renderCodeCell(
799
+ {
800
+ code: cell.code,
801
+ language: "python",
802
+ index: i,
803
+ total: cells.length,
804
+ title: combinedTitle,
805
+ status: "pending",
953
806
  width,
954
- bgFn: (text: string) => uiTheme.bg("toolPendingBg", text),
955
- }),
807
+ codeMaxLines: PYTHON_DEFAULT_PREVIEW_LINES,
808
+ expanded: true,
809
+ },
810
+ uiTheme,
956
811
  );
812
+ lines.push(...cellLines);
957
813
  if (i < cells.length - 1) {
958
814
  lines.push("");
959
815
  }
@@ -975,7 +831,7 @@ export const pythonToolRenderer = {
975
831
 
976
832
  const expanded = renderContext?.expanded ?? options.expanded;
977
833
  const previewLines = renderContext?.previewLines ?? PYTHON_DEFAULT_PREVIEW_LINES;
978
- const output = renderContext?.output ?? (result.content?.find((c) => c.type === "text")?.text ?? "").trim();
834
+ const output = renderContext?.output ?? (result.content?.find(c => c.type === "text")?.text ?? "").trimEnd();
979
835
 
980
836
  const jsonOutputs = details?.jsonOutputs ?? [];
981
837
  const jsonLines = jsonOutputs.flatMap((value, index) => {
@@ -1015,23 +871,42 @@ export const pythonToolRenderer = {
1015
871
  const lines: string[] = [];
1016
872
  for (let i = 0; i < cellResults.length; i++) {
1017
873
  const cell = cellResults[i];
1018
- const showOutput = cell.status !== "pending";
1019
- const bgColor =
1020
- cell.status === "error"
1021
- ? "toolErrorBg"
1022
- : cell.status === "complete"
1023
- ? "toolSuccessBg"
1024
- : "toolPendingBg";
1025
- lines.push(
1026
- ...renderCellBlock(cell, i, cellResults.length, ui, {
1027
- expanded,
1028
- previewLines,
874
+ const statusLines = renderStatusEvents(cell.statusEvents ?? [], uiTheme, expanded);
875
+ const outputContent = formatCellOutputLines(cell, expanded, previewLines, uiTheme);
876
+ const outputLines = [...outputContent.lines];
877
+ if (!expanded && outputContent.hiddenCount > 0) {
878
+ outputLines.push(
879
+ uiTheme.fg(
880
+ "dim",
881
+ `${uiTheme.format.ellipsis} ${outputContent.hiddenCount} more lines (ctrl+o to expand)`,
882
+ ),
883
+ );
884
+ }
885
+ if (statusLines.length > 0) {
886
+ if (outputLines.length > 0) {
887
+ outputLines.push(uiTheme.fg("dim", "Status"));
888
+ }
889
+ outputLines.push(...statusLines);
890
+ }
891
+ const cellLines = renderCodeCell(
892
+ {
893
+ code: cell.code,
894
+ language: "python",
895
+ index: i,
896
+ total: cellResults.length,
897
+ title: cell.title,
898
+ status: cell.status,
1029
899
  spinnerFrame: options.spinnerFrame,
1030
- showOutput,
900
+ duration: cell.durationMs,
901
+ output: outputLines.length > 0 ? outputLines.join("\n") : undefined,
902
+ outputMaxLines: outputLines.length,
903
+ codeMaxLines: expanded ? Number.POSITIVE_INFINITY : PYTHON_DEFAULT_PREVIEW_LINES,
904
+ expanded,
1031
905
  width,
1032
- bgFn: (text: string) => uiTheme.bg(bgColor, text),
1033
- }),
906
+ },
907
+ uiTheme,
1034
908
  );
909
+ lines.push(...cellLines);
1035
910
  if (i < cellResults.length - 1) {
1036
911
  lines.push("");
1037
912
  }
@@ -1066,22 +941,29 @@ export const pythonToolRenderer = {
1066
941
  }
1067
942
 
1068
943
  if (!combinedOutput && statusLines.length > 0) {
1069
- const lines = [...statusLines, timeoutLine, warningLine].filter(Boolean) as string[];
944
+ const lines = [uiTheme.fg("dim", "Status"), ...statusLines, timeoutLine, warningLine].filter(
945
+ Boolean,
946
+ ) as string[];
1070
947
  return new Text(lines.join("\n"), 0, 0);
1071
948
  }
1072
949
 
1073
950
  if (expanded) {
1074
951
  const styledOutput = combinedOutput
1075
952
  .split("\n")
1076
- .map((line) => uiTheme.fg("toolOutput", line))
953
+ .map(line => uiTheme.fg("toolOutput", line))
1077
954
  .join("\n");
1078
- const lines = [styledOutput, ...statusLines, timeoutLine, warningLine].filter(Boolean) as string[];
955
+ const lines = [
956
+ styledOutput,
957
+ ...(statusLines.length > 0 ? [uiTheme.fg("dim", "Status"), ...statusLines] : []),
958
+ timeoutLine,
959
+ warningLine,
960
+ ].filter(Boolean) as string[];
1079
961
  return new Text(lines.join("\n"), 0, 0);
1080
962
  }
1081
963
 
1082
964
  const styledOutput = combinedOutput
1083
965
  .split("\n")
1084
- .map((line) => uiTheme.fg("toolOutput", line))
966
+ .map(line => uiTheme.fg("toolOutput", line))
1085
967
  .join("\n");
1086
968
  const textContent = `\n${styledOutput}`;
1087
969
 
@@ -1107,8 +989,13 @@ export const pythonToolRenderer = {
1107
989
  outputLines.push(truncateToWidth(skippedLine, width, uiTheme.fg("dim", uiTheme.format.ellipsis)));
1108
990
  }
1109
991
  outputLines.push(...cachedLines);
1110
- for (const statusLine of statusLines) {
1111
- outputLines.push(truncateToWidth(statusLine, width, uiTheme.fg("dim", uiTheme.format.ellipsis)));
992
+ if (statusLines.length > 0) {
993
+ outputLines.push(
994
+ truncateToWidth(uiTheme.fg("dim", "Status"), width, uiTheme.fg("dim", uiTheme.format.ellipsis)),
995
+ );
996
+ for (const statusLine of statusLines) {
997
+ outputLines.push(truncateToWidth(statusLine, width, uiTheme.fg("dim", uiTheme.format.ellipsis)));
998
+ }
1112
999
  }
1113
1000
  if (timeoutLine) {
1114
1001
  outputLines.push(truncateToWidth(timeoutLine, width, uiTheme.fg("dim", uiTheme.format.ellipsis)));