@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
package/src/lsp/index.ts CHANGED
@@ -1,15 +1,14 @@
1
- import type { Dirent } from "node:fs";
2
- import { existsSync, statSync } from "node:fs";
1
+ import * as fs from "node:fs";
3
2
  import path from "node:path";
4
3
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
5
- import { renderPromptTemplate } from "@oh-my-pi/pi-coding-agent/config/prompt-templates";
6
- import { type Theme, theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
7
- import lspDescription from "@oh-my-pi/pi-coding-agent/prompts/tools/lsp.md" with { type: "text" };
8
- import type { ToolSession } from "@oh-my-pi/pi-coding-agent/tools/index";
9
- import { resolveToCwd } from "@oh-my-pi/pi-coding-agent/tools/path-utils";
10
- import { throwIfAborted } from "@oh-my-pi/pi-coding-agent/tools/tool-errors";
11
4
  import { logger, once, untilAborted } from "@oh-my-pi/pi-utils";
12
5
  import type { BunFile } from "bun";
6
+ import { renderPromptTemplate } from "../config/prompt-templates";
7
+ import { type Theme, theme } from "../modes/theme/theme";
8
+ import lspDescription from "../prompts/tools/lsp.md" with { type: "text" };
9
+ import type { ToolSession } from "../tools";
10
+ import { resolveToCwd } from "../tools/path-utils";
11
+ import { throwIfAborted } from "../tools/tool-errors";
13
12
  import {
14
13
  ensureFileOpen,
15
14
  getActiveClients,
@@ -254,21 +253,21 @@ function limitDiagnosticMessages(messages: string[]): string[] {
254
253
  }
255
254
 
256
255
  function findFileByExtensions(baseDir: string, extensions: string[], maxDepth: number): string | null {
257
- const normalized = extensions.map((ext) => ext.toLowerCase());
256
+ const normalized = extensions.map(ext => ext.toLowerCase());
258
257
  const search = (dir: string, depth: number): string | null => {
259
258
  if (depth > maxDepth) return null;
260
- const entries: Dirent[] = [];
259
+ const entries: fs.Dirent[] = [];
261
260
  try {
262
261
  const names = Array.from(new Bun.Glob("*").scanSync({ cwd: dir, onlyFiles: false }));
263
262
  for (const name of names) {
264
263
  const fullPath = path.join(dir, name);
265
264
  let isDir = false;
266
265
  try {
267
- isDir = statSync(fullPath).isDirectory();
266
+ isDir = fs.statSync(fullPath).isDirectory();
268
267
  } catch {
269
268
  continue;
270
269
  }
271
- entries.push({ name, isFile: () => !isDir, isDirectory: () => isDir } as Dirent);
270
+ entries.push({ name, isFile: () => !isDir, isDirectory: () => isDir } as fs.Dirent);
272
271
  }
273
272
  } catch {
274
273
  return null;
@@ -281,7 +280,7 @@ function findFileByExtensions(baseDir: string, extensions: string[], maxDepth: n
281
280
 
282
281
  if (entry.isFile()) {
283
282
  const lowerName = entry.name.toLowerCase();
284
- if (normalized.some((ext) => lowerName.endsWith(ext))) {
283
+ if (normalized.some(ext => lowerName.endsWith(ext))) {
285
284
  return fullPath;
286
285
  }
287
286
  } else if (entry.isDirectory()) {
@@ -362,22 +361,22 @@ interface ProjectType {
362
361
  /** Detect project type from root markers */
363
362
  function detectProjectType(cwd: string): ProjectType {
364
363
  // Check for Rust (Cargo.toml)
365
- if (existsSync(path.join(cwd, "Cargo.toml"))) {
364
+ if (fs.existsSync(path.join(cwd, "Cargo.toml"))) {
366
365
  return { type: "rust", command: ["cargo", "check", "--message-format=short"], description: "Rust (cargo check)" };
367
366
  }
368
367
 
369
368
  // Check for TypeScript (tsconfig.json)
370
- if (existsSync(path.join(cwd, "tsconfig.json"))) {
369
+ if (fs.existsSync(path.join(cwd, "tsconfig.json"))) {
371
370
  return { type: "typescript", command: ["npx", "tsc", "--noEmit"], description: "TypeScript (tsc --noEmit)" };
372
371
  }
373
372
 
374
373
  // Check for Go (go.mod)
375
- if (existsSync(path.join(cwd, "go.mod"))) {
374
+ if (fs.existsSync(path.join(cwd, "go.mod"))) {
376
375
  return { type: "go", command: ["go", "build", "./..."], description: "Go (go build)" };
377
376
  }
378
377
 
379
378
  // Check for Python (pyproject.toml or pyrightconfig.json)
380
- if (existsSync(path.join(cwd, "pyproject.toml")) || existsSync(path.join(cwd, "pyrightconfig.json"))) {
379
+ if (fs.existsSync(path.join(cwd, "pyproject.toml")) || fs.existsSync(path.join(cwd, "pyrightconfig.json"))) {
381
380
  return { type: "python", command: ["pyright"], description: "Python (pyright)" };
382
381
  }
383
382
 
@@ -412,10 +411,10 @@ async function runWorkspaceDiagnostics(
412
411
  return { output: "No issues found", projectType };
413
412
  }
414
413
 
415
- const summary = formatDiagnosticsSummary(collected.map((d) => d.diagnostic));
416
- const formatted = collected.slice(0, 50).map((d) => formatDiagnostic(d.diagnostic, d.filePath));
414
+ const summary = formatDiagnosticsSummary(collected.map(d => d.diagnostic));
415
+ const formatted = collected.slice(0, 50).map(d => formatDiagnostic(d.diagnostic, d.filePath));
417
416
  const more = collected.length > 50 ? `\n ... and ${collected.length - 50} more` : "";
418
- return { output: `${summary}:\n${formatted.map((f) => ` ${f}`).join("\n")}${more}`, projectType };
417
+ return { output: `${summary}:\n${formatted.map(f => ` ${f}`).join("\n")}${more}`, projectType };
419
418
  } catch (err) {
420
419
  logger.debug("LSP diagnostics failed, falling back to shell", { error: String(err) });
421
420
  // Fall through to shell command
@@ -571,10 +570,10 @@ async function getDiagnosticsForFile(
571
570
  }
572
571
  }
573
572
 
574
- const formatted = uniqueDiagnostics.map((d) => formatDiagnostic(d, relPath));
573
+ const formatted = uniqueDiagnostics.map(d => formatDiagnostic(d, relPath));
575
574
  const limited = limitDiagnosticMessages(formatted);
576
575
  const summary = formatDiagnosticsSummary(uniqueDiagnostics);
577
- const hasErrors = uniqueDiagnostics.some((d) => d.severity === 1);
576
+ const hasErrors = uniqueDiagnostics.some(d => d.severity === 1);
578
577
 
579
578
  return {
580
579
  server: serverNames.join(", "),
@@ -1096,8 +1095,8 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1096
1095
  }
1097
1096
 
1098
1097
  const summary = formatDiagnosticsSummary(uniqueDiagnostics);
1099
- const formatted = uniqueDiagnostics.map((d) => formatDiagnostic(d, relPath));
1100
- const output = `${summary}:\n${formatted.map((f) => ` ${f}`).join("\n")}`;
1098
+ const formatted = uniqueDiagnostics.map(d => formatDiagnostic(d, relPath));
1099
+ const output = `${summary}:\n${formatted.map(f => ` ${f}`).join("\n")}`;
1101
1100
  return {
1102
1101
  content: [{ type: "text", text: output }],
1103
1102
  details: { action, serverName: Array.from(allServerNames).join(", "), success: true },
@@ -1187,7 +1186,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1187
1186
  output = "No definition found";
1188
1187
  } else {
1189
1188
  const raw = Array.isArray(result) ? result : [result];
1190
- const locations = raw.flatMap((loc) => {
1189
+ const locations = raw.flatMap(loc => {
1191
1190
  if ("uri" in loc) {
1192
1191
  return [loc as Location];
1193
1192
  }
@@ -1203,7 +1202,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1203
1202
  output = "No definition found";
1204
1203
  } else {
1205
1204
  output = `Found ${locations.length} definition(s):\n${locations
1206
- .map((loc) => ` ${formatLocation(loc, this.session.cwd)}`)
1205
+ .map(loc => ` ${formatLocation(loc, this.session.cwd)}`)
1207
1206
  .join("\n")}`;
1208
1207
  }
1209
1208
  }
@@ -1220,7 +1219,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1220
1219
  if (!result || result.length === 0) {
1221
1220
  output = "No references found";
1222
1221
  } else {
1223
- const lines = result.map((loc) => ` ${formatLocation(loc, this.session.cwd)}`);
1222
+ const lines = result.map(loc => ` ${formatLocation(loc, this.session.cwd)}`);
1224
1223
  output = `Found ${result.length} reference(s):\n${lines.join("\n")}`;
1225
1224
  }
1226
1225
  break;
@@ -1257,11 +1256,11 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1257
1256
  // Check if hierarchical (DocumentSymbol) or flat (SymbolInformation)
1258
1257
  if ("selectionRange" in result[0]) {
1259
1258
  // Hierarchical
1260
- const lines = (result as DocumentSymbol[]).flatMap((s) => formatDocumentSymbol(s));
1259
+ const lines = (result as DocumentSymbol[]).flatMap(s => formatDocumentSymbol(s));
1261
1260
  output = `Symbols in ${relPath}:\n${lines.join("\n")}`;
1262
1261
  } else {
1263
1262
  // Flat
1264
- const lines = (result as SymbolInformation[]).map((s) => {
1263
+ const lines = (result as SymbolInformation[]).map(s => {
1265
1264
  const line = s.location.range.start.line + 1;
1266
1265
  const icon = symbolKindToIcon(s.kind);
1267
1266
  return `${icon} ${s.name} @ line ${line}`;
@@ -1285,8 +1284,8 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1285
1284
  if (!result || result.length === 0) {
1286
1285
  output = `No symbols matching "${query}"`;
1287
1286
  } else {
1288
- const lines = result.map((s) => formatSymbolInformation(s, this.session.cwd));
1289
- output = `Found ${result.length} symbol(s) matching "${query}":\n${lines.map((l) => ` ${l}`).join("\n")}`;
1287
+ const lines = result.map(s => formatSymbolInformation(s, this.session.cwd));
1288
+ output = `Found ${result.length} symbol(s) matching "${query}":\n${lines.map(l => ` ${l}`).join("\n")}`;
1290
1289
  }
1291
1290
  break;
1292
1291
  }
@@ -1311,10 +1310,10 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1311
1310
  const shouldApply = apply !== false;
1312
1311
  if (shouldApply) {
1313
1312
  const applied = await applyWorkspaceEdit(result, this.session.cwd);
1314
- output = `Applied rename:\n${applied.map((a) => ` ${a}`).join("\n")}`;
1313
+ output = `Applied rename:\n${applied.map(a => ` ${a}`).join("\n")}`;
1315
1314
  } else {
1316
1315
  const preview = formatWorkspaceEdit(result, this.session.cwd);
1317
- output = `Rename preview:\n${preview.map((p) => ` ${p}`).join("\n")}`;
1316
+ output = `Rename preview:\n${preview.map(p => ` ${p}`).join("\n")}`;
1318
1317
  }
1319
1318
  }
1320
1319
  break;
@@ -1335,7 +1334,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1335
1334
  const endCharacter = (end_character ?? column ?? 1) - 1;
1336
1335
  const range = { start: position, end: { line: endLine, character: endCharacter } };
1337
1336
  const relevantDiagnostics = diagnostics.filter(
1338
- (d) => d.range.start.line <= range.end.line && d.range.end.line >= range.start.line,
1337
+ d => d.range.start.line <= range.end.line && d.range.end.line >= range.start.line,
1339
1338
  );
1340
1339
 
1341
1340
  const codeActionContext: { diagnostics: Diagnostic[]; only?: string[] } = {
@@ -1401,7 +1400,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1401
1400
 
1402
1401
  if (isCodeAction(resolvedAction) && resolvedAction.edit) {
1403
1402
  const applied = await applyWorkspaceEdit(resolvedAction.edit, this.session.cwd);
1404
- output = `Applied "${codeAction.title}":\n${applied.map((a) => ` ${a}`).join("\n")}`;
1403
+ output = `Applied "${codeAction.title}":\n${applied.map(a => ` ${a}`).join("\n")}`;
1405
1404
  } else {
1406
1405
  const commandPayload = getCommandPayload(resolvedAction);
1407
1406
  if (commandPayload) {
@@ -1450,7 +1449,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1450
1449
  if (!calls || calls.length === 0) {
1451
1450
  output = `No callers found for "${item.name}"`;
1452
1451
  } else {
1453
- const lines = calls.map((call) => {
1452
+ const lines = calls.map(call => {
1454
1453
  const loc = { uri: call.from.uri, range: call.from.selectionRange };
1455
1454
  const detail = call.from.detail ? ` (${call.from.detail})` : "";
1456
1455
  return ` ${call.from.name}${detail} @ ${formatLocation(loc, this.session.cwd)}`;
@@ -1465,7 +1464,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1465
1464
  if (!calls || calls.length === 0) {
1466
1465
  output = `"${item.name}" doesn't call any functions`;
1467
1466
  } else {
1468
- const lines = calls.map((call) => {
1467
+ const lines = calls.map(call => {
1469
1468
  const loc = { uri: call.to.uri, range: call.to.selectionRange };
1470
1469
  const detail = call.to.detail ? ` (${call.to.detail})` : "";
1471
1470
  return ` ${call.to.name}${detail} @ ${formatLocation(loc, this.session.cwd)}`;
@@ -1500,10 +1499,10 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1500
1499
  if (collected.length === 0) {
1501
1500
  output = "Flycheck: no issues found";
1502
1501
  } else {
1503
- const summary = formatDiagnosticsSummary(collected.map((d) => d.diagnostic));
1504
- const formatted = collected.slice(0, 20).map((d) => formatDiagnostic(d.diagnostic, d.filePath));
1502
+ const summary = formatDiagnosticsSummary(collected.map(d => d.diagnostic));
1503
+ const formatted = collected.slice(0, 20).map(d => formatDiagnostic(d.diagnostic, d.filePath));
1505
1504
  const more = collected.length > 20 ? `\n ... and ${collected.length - 20} more` : "";
1506
- output = `Flycheck ${summary}:\n${formatted.map((f) => ` ${f}`).join("\n")}${more}`;
1505
+ output = `Flycheck ${summary}:\n${formatted.map(f => ` ${f}`).join("\n")}${more}`;
1507
1506
  }
1508
1507
  break;
1509
1508
  }
@@ -1561,13 +1560,13 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1561
1560
  const applied = await applyWorkspaceEdit(result, this.session.cwd);
1562
1561
  output =
1563
1562
  applied.length > 0
1564
- ? `Applied SSR:\n${applied.map((a) => ` ${a}`).join("\n")}`
1563
+ ? `Applied SSR:\n${applied.map(a => ` ${a}`).join("\n")}`
1565
1564
  : "SSR: no matches found";
1566
1565
  } else {
1567
1566
  const preview = formatWorkspaceEdit(result, this.session.cwd);
1568
1567
  output =
1569
1568
  preview.length > 0
1570
- ? `SSR preview:\n${preview.map((p) => ` ${p}`).join("\n")}`
1569
+ ? `SSR preview:\n${preview.map(p => ` ${p}`).join("\n")}`
1571
1570
  : "SSR: no matches found";
1572
1571
  }
1573
1572
  break;
@@ -1592,7 +1591,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1592
1591
  if (result.length === 0) {
1593
1592
  output = "No runnables found";
1594
1593
  } else {
1595
- const lines = result.map((r) => {
1594
+ const lines = result.map(r => {
1596
1595
  const args = r.args?.cargoArgs?.join(" ") || "";
1597
1596
  return ` [${r.kind}] ${r.label}${args ? ` (cargo ${args})` : ""}`;
1598
1597
  });
@@ -1620,7 +1619,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
1620
1619
  if (result.length === 0) {
1621
1620
  output = "No related tests found";
1622
1621
  } else {
1623
- output = `Found ${result.length} related test(s):\n${result.map((t) => ` ${t}`).join("\n")}`;
1622
+ output = `Found ${result.length} related test(s):\n${result.map(t => ` ${t}`).join("\n")}`;
1624
1623
  }
1625
1624
  break;
1626
1625
  }
package/src/lsp/lspmux.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { homedir, platform } from "node:os";
2
- import { join } from "node:path";
1
+ import * as os from "node:os";
2
+ import * as path from "node:path";
3
3
  import { logger } from "@oh-my-pi/pi-utils";
4
4
  import { TOML } from "bun";
5
5
 
@@ -63,14 +63,14 @@ const STATE_CACHE_TTL_MS = 5 * 60 * 1000;
63
63
  * Matches Rust's `dirs::config_dir()` behavior.
64
64
  */
65
65
  function getConfigPath(): string {
66
- const home = homedir();
67
- switch (platform()) {
66
+ const home = os.homedir();
67
+ switch (os.platform()) {
68
68
  case "win32":
69
- return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "lspmux", "config.toml");
69
+ return path.join(process.env.APPDATA ?? path.join(home, "AppData", "Roaming"), "lspmux", "config.toml");
70
70
  case "darwin":
71
- return join(home, "Library", "Application Support", "lspmux", "config.toml");
71
+ return path.join(home, "Library", "Application Support", "lspmux", "config.toml");
72
72
  default:
73
- return join(process.env.XDG_CONFIG_HOME ?? join(home, ".config"), "lspmux", "config.toml");
73
+ return path.join(process.env.XDG_CONFIG_HOME ?? path.join(home, ".config"), "lspmux", "config.toml");
74
74
  }
75
75
  }
76
76
 
@@ -108,7 +108,7 @@ async function checkServerRunning(binaryPath: string): Promise<boolean> {
108
108
 
109
109
  const exited = await Promise.race([
110
110
  proc.exited,
111
- new Promise<null>((resolve) => setTimeout(() => resolve(null), LIVENESS_TIMEOUT_MS)),
111
+ new Promise<null>(resolve => setTimeout(() => resolve(null), LIVENESS_TIMEOUT_MS)),
112
112
  ]);
113
113
 
114
114
  if (exited === null) {
package/src/lsp/render.ts CHANGED
@@ -7,17 +7,12 @@
7
7
  * - Grouped references and symbols
8
8
  * - Collapsible/expandable views
9
9
  */
10
-
11
10
  import type { AgentToolResult, RenderResultOptions } from "@oh-my-pi/pi-agent-core";
12
- import { getLanguageFromPath, type Theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
13
- import {
14
- formatExpandHint,
15
- formatMoreItems,
16
- TRUNCATE_LENGTHS,
17
- truncate,
18
- } from "@oh-my-pi/pi-coding-agent/tools/render-utils";
19
- import { Text } from "@oh-my-pi/pi-tui";
11
+ import { type Component, Text } from "@oh-my-pi/pi-tui";
20
12
  import { highlight, supportsLanguage } from "cli-highlight";
13
+ import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
14
+ import { formatExpandHint, formatMoreItems, TRUNCATE_LENGTHS, truncate } from "../tools/render-utils";
15
+ import { renderOutputBlock, renderStatusLine } from "../tui";
21
16
  import type { LspParams, LspToolDetails } from "./types";
22
17
 
23
18
  // =============================================================================
@@ -30,16 +25,13 @@ import type { LspParams, LspToolDetails } from "./types";
30
25
  */
31
26
  export function renderCall(args: unknown, theme: Theme): Text {
32
27
  const p = args as LspParams & { file?: string; files?: string[] };
33
-
34
- let text = theme.fg("toolTitle", theme.bold("LSP"));
35
- text += ` ${theme.fg("accent", p.action || "?")}`;
36
-
28
+ const meta: string[] = [];
37
29
  if (p.file) {
38
- text += ` ${theme.fg("muted", p.file)}`;
30
+ meta.push(p.file);
39
31
  } else if (p.files?.length) {
40
- text += ` ${theme.fg("muted", `${p.files.length} file(s)`)}`;
32
+ meta.push(`${p.files.length} file(s)`);
41
33
  }
42
-
34
+ const text = renderStatusLine({ icon: "pending", title: "LSP", description: p.action || "?", meta }, theme);
43
35
  return new Text(text, 0, 0);
44
36
  }
45
37
 
@@ -55,40 +47,74 @@ export function renderResult(
55
47
  result: AgentToolResult<LspToolDetails>,
56
48
  options: RenderResultOptions,
57
49
  theme: Theme,
58
- ): Text {
50
+ args?: LspParams & { file?: string; files?: string[] },
51
+ ): Component {
59
52
  const content = result.content?.[0];
60
53
  if (!content || content.type !== "text" || !("text" in content) || !content.text) {
61
- return new Text(theme.fg("error", "No result"), 0, 0);
54
+ const header = renderStatusLine({ icon: "warning", title: "LSP", description: "No result" }, theme);
55
+ return new Text([header, theme.fg("dim", "No result")].join("\n"), 0, 0);
62
56
  }
63
57
 
64
58
  const text = content.text;
65
- const lines = text.split("\n").filter((l) => l.trim());
59
+ const lines = text.split("\n");
66
60
  const expanded = options.expanded;
67
61
 
68
- // Detect result type and render accordingly
62
+ let label = "Result";
63
+ let state: "success" | "warning" | "error" = "success";
64
+ let bodyLines: string[] = [];
65
+
69
66
  const codeBlockMatch = text.match(/```(\w*)\n([\s\S]*?)```/);
70
67
  if (codeBlockMatch) {
71
- return renderHover(codeBlockMatch, text, lines, expanded, theme);
72
- }
73
-
74
- const errorMatch = text.match(/(\d+)\s+error\(s\)/);
75
- const warningMatch = text.match(/(\d+)\s+warning\(s\)/);
76
- if (errorMatch || warningMatch || text.includes(theme.status.error)) {
77
- return renderDiagnostics(errorMatch, warningMatch, lines, expanded, theme);
78
- }
79
-
80
- const refMatch = text.match(/(\d+)\s+reference\(s\)/);
81
- if (refMatch) {
82
- return renderReferences(refMatch, lines, expanded, theme);
68
+ label = "Hover";
69
+ bodyLines = renderHover(codeBlockMatch, text, lines, expanded, theme);
70
+ } else {
71
+ const errorMatch = text.match(/(\d+)\s+error\(s\)/);
72
+ const warningMatch = text.match(/(\d+)\s+warning\(s\)/);
73
+ if (errorMatch || warningMatch || text.includes(theme.status.error)) {
74
+ label = "Diagnostics";
75
+ const errorCount = errorMatch ? Number.parseInt(errorMatch[1], 10) : 0;
76
+ const warnCount = warningMatch ? Number.parseInt(warningMatch[1], 10) : 0;
77
+ state = errorCount > 0 ? "error" : warnCount > 0 ? "warning" : "success";
78
+ bodyLines = renderDiagnostics(errorMatch, warningMatch, lines, expanded, theme);
79
+ } else {
80
+ const refMatch = text.match(/(\d+)\s+reference\(s\)/);
81
+ if (refMatch) {
82
+ label = "References";
83
+ bodyLines = renderReferences(refMatch, lines, expanded, theme);
84
+ } else {
85
+ const symbolsMatch = text.match(/Symbols in (.+):/);
86
+ if (symbolsMatch) {
87
+ label = "Symbols";
88
+ bodyLines = renderSymbols(symbolsMatch, lines, expanded, theme);
89
+ } else {
90
+ label = "Response";
91
+ bodyLines = renderGeneric(text, lines, expanded, theme);
92
+ }
93
+ }
94
+ }
83
95
  }
84
96
 
85
- const symbolsMatch = text.match(/Symbols in (.+):/);
86
- if (symbolsMatch) {
87
- return renderSymbols(symbolsMatch, lines, expanded, theme);
97
+ const meta: string[] = [];
98
+ if (args?.action) meta.push(args.action);
99
+ if (args?.file) {
100
+ meta.push(args.file);
101
+ } else if (args?.files?.length) {
102
+ meta.push(`${args.files.length} file(s)`);
88
103
  }
89
-
90
- // Default fallback rendering
91
- return renderGeneric(text, lines, expanded, theme);
104
+ const header = renderStatusLine({ icon: state, title: "LSP", description: label, meta }, theme);
105
+ return {
106
+ render: (width: number) =>
107
+ renderOutputBlock(
108
+ {
109
+ header,
110
+ state,
111
+ sections: [{ label: theme.fg("toolTitle", label), lines: bodyLines }],
112
+ width,
113
+ },
114
+ theme,
115
+ ),
116
+ invalidate: () => {},
117
+ };
92
118
  }
93
119
 
94
120
  // =============================================================================
@@ -104,9 +130,11 @@ function renderHover(
104
130
  _lines: string[],
105
131
  expanded: boolean,
106
132
  theme: Theme,
107
- ): Text {
133
+ ): string[] {
108
134
  const lang = codeBlockMatch[1] || "";
109
135
  const code = codeBlockMatch[2].trim();
136
+ const codeStart = codeBlockMatch.index ?? 0;
137
+ const beforeCode = fullText.slice(0, codeStart).trimEnd();
110
138
  const afterCode = fullText.slice(fullText.indexOf("```", 3) + 3).trim();
111
139
 
112
140
  const codeLines = highlightCode(code, lang, theme);
@@ -119,6 +147,11 @@ function renderHover(
119
147
  const top = `${theme.boxSharp.topLeft}${h.repeat(3)}`;
120
148
  const bottom = `${theme.boxSharp.bottomLeft}${h.repeat(3)}`;
121
149
  let output = `${icon}${langLabel}`;
150
+ if (beforeCode) {
151
+ for (const line of beforeCode.split("\n")) {
152
+ output += `\n ${theme.fg("muted", line)}`;
153
+ }
154
+ }
122
155
  output += `\n ${theme.fg("mdCodeBlockBorder", top)}`;
123
156
  for (const line of codeLines) {
124
157
  output += `\n ${theme.fg("mdCodeBlockBorder", v)} ${line}`;
@@ -127,15 +160,19 @@ function renderHover(
127
160
  if (afterCode) {
128
161
  output += `\n ${theme.fg("muted", afterCode)}`;
129
162
  }
130
- return new Text(output, 0, 0);
163
+ return output.split("\n");
131
164
  }
132
165
 
133
166
  // Collapsed view
134
167
  const firstCodeLine = codeLines[0] || "";
135
- const hasMore = codeLines.length > 1 || Boolean(afterCode);
168
+ const hasMore = codeLines.length > 1 || Boolean(afterCode) || Boolean(beforeCode);
136
169
  const expandHint = formatExpandHint(theme, expanded, hasMore);
137
170
 
138
171
  let output = `${icon}${langLabel}${expandHint}`;
172
+ if (beforeCode) {
173
+ const preview = truncate(beforeCode, TRUNCATE_LENGTHS.TITLE, theme.format.ellipsis);
174
+ output += `\n ${theme.fg("dim", theme.tree.branch)} ${theme.fg("muted", preview)}`;
175
+ }
139
176
  const h = theme.boxSharp.horizontal;
140
177
  const v = theme.boxSharp.vertical;
141
178
  const bottom = `${theme.boxSharp.bottomLeft}${h.repeat(3)}`;
@@ -155,7 +192,7 @@ function renderHover(
155
192
  output += `\n ${theme.fg("mdCodeBlockBorder", bottom)}`;
156
193
  }
157
194
 
158
- return new Text(output, 0, 0);
195
+ return output.split("\n");
159
196
  }
160
197
 
161
198
  /**
@@ -206,7 +243,7 @@ function renderDiagnostics(
206
243
  lines: string[],
207
244
  expanded: boolean,
208
245
  theme: Theme,
209
- ): Text {
246
+ ): string[] {
210
247
  const errorCount = errorMatch ? Number.parseInt(errorMatch[1], 10) : 0;
211
248
  const warnCount = warningMatch ? Number.parseInt(warningMatch[1], 10) : 0;
212
249
 
@@ -222,11 +259,11 @@ function renderDiagnostics(
222
259
  if (warnCount > 0) meta.push(`${warnCount} warning${warnCount !== 1 ? "s" : ""}`);
223
260
  if (meta.length === 0) meta.push("No issues");
224
261
 
225
- const diagLines = lines.filter((l) => l.includes(theme.status.error) || /:\d+:\d+/.test(l));
262
+ const diagLines = lines.filter(l => l.includes(theme.status.error) || /:\d+:\d+/.test(l));
226
263
  const parsedDiagnostics = diagLines
227
- .map((line) => parseDiagnosticLine(line))
264
+ .map(line => parseDiagnosticLine(line))
228
265
  .filter((diag): diag is ParsedDiagnostic => diag !== null);
229
- const fallbackDiagnostics: RawDiagnostic[] = diagLines.map((line) => ({ raw: line.trim() }));
266
+ const fallbackDiagnostics: RawDiagnostic[] = diagLines.map(line => ({ raw: line.trim() }));
230
267
 
231
268
  if (expanded) {
232
269
  let output = `${icon} ${theme.fg("dim", meta.join(theme.sep.dot))}`;
@@ -253,7 +290,7 @@ function renderDiagnostics(
253
290
  )}`;
254
291
  }
255
292
  }
256
- return new Text(output, 0, 0);
293
+ return output.split("\n");
257
294
  }
258
295
 
259
296
  // Collapsed view
@@ -285,7 +322,7 @@ function renderDiagnostics(
285
322
  )}`;
286
323
  }
287
324
 
288
- return new Text(output, 0, 0);
325
+ return output.split("\n");
289
326
  }
290
327
 
291
328
  // =============================================================================
@@ -295,12 +332,12 @@ function renderDiagnostics(
295
332
  /**
296
333
  * Render references grouped by file.
297
334
  */
298
- function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): Text {
335
+ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): string[] {
299
336
  const refCount = Number.parseInt(refMatch[1], 10);
300
337
  const icon =
301
338
  refCount > 0 ? theme.styledSymbol("status.success", "success") : theme.styledSymbol("status.warning", "warning");
302
339
 
303
- const locLines = lines.filter((l) => /^\s*\S+:\d+:\d+/.test(l));
340
+ const locLines = lines.filter(l => /^\s*\S+:\d+:\d+/.test(l));
304
341
 
305
342
  // Group by file
306
343
  const byFile = new Map<string, Array<[string, string]>>();
@@ -369,10 +406,10 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
369
406
  };
370
407
 
371
408
  if (expanded) {
372
- return new Text(renderGrouped(files.length, 3, false), 0, 0);
409
+ return renderGrouped(files.length, 3, false).split("\n");
373
410
  }
374
411
 
375
- return new Text(renderGrouped(3, 1, true), 0, 0);
412
+ return renderGrouped(3, 1, true).split("\n");
376
413
  }
377
414
 
378
415
  // =============================================================================
@@ -382,7 +419,7 @@ function renderReferences(refMatch: RegExpMatchArray, lines: string[], expanded:
382
419
  /**
383
420
  * Render document symbols in a hierarchical tree.
384
421
  */
385
- function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): Text {
422
+ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded: boolean, theme: Theme): string[] {
386
423
  const fileName = symbolsMatch[1];
387
424
  const icon = theme.styledSymbol("status.info", "accent");
388
425
 
@@ -393,7 +430,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
393
430
  icon: string;
394
431
  }
395
432
 
396
- const symbolLines = lines.filter((l) => l.includes("@") && l.includes("line"));
433
+ const symbolLines = lines.filter(l => l.includes("@") && l.includes("line"));
397
434
  const symbols: SymbolInfo[] = [];
398
435
 
399
436
  for (const line of symbolLines) {
@@ -436,7 +473,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
436
473
  return prefix;
437
474
  };
438
475
 
439
- const topLevelCount = symbols.filter((s) => s.indent === 0).length;
476
+ const topLevelCount = symbols.filter(s => s.indent === 0).length;
440
477
 
441
478
  if (expanded) {
442
479
  let output = `${icon} ${theme.fg("dim", `in ${fileName}`)}`;
@@ -450,11 +487,11 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
450
487
  output += `\n${prefix}${theme.fg("dim", branch)} ${theme.fg("accent", sym.icon)} ${theme.fg("accent", sym.name)}`;
451
488
  output += `\n${prefix}${theme.fg("dim", detailPrefix)}${theme.fg("muted", `line ${sym.line}`)}`;
452
489
  }
453
- return new Text(output, 0, 0);
490
+ return output.split("\n");
454
491
  }
455
492
 
456
493
  // Collapsed: show first 3 top-level symbols
457
- const topLevel = symbols.filter((s) => s.indent === 0).slice(0, 3);
494
+ const topLevel = symbols.filter(s => s.indent === 0).slice(0, 3);
458
495
  const hasMoreSymbols = symbols.length > topLevel.length;
459
496
  const expandHint = formatExpandHint(theme, expanded, hasMoreSymbols);
460
497
  let output = `${icon} ${theme.fg("dim", `in ${fileName}`)}${expandHint}`;
@@ -474,7 +511,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
474
511
  )}`;
475
512
  }
476
513
 
477
- return new Text(output, 0, 0);
514
+ return output.split("\n");
478
515
  }
479
516
 
480
517
  // =============================================================================
@@ -484,7 +521,7 @@ function renderSymbols(symbolsMatch: RegExpMatchArray, lines: string[], expanded
484
521
  /**
485
522
  * Generic fallback rendering for unknown result types.
486
523
  */
487
- function renderGeneric(text: string, lines: string[], expanded: boolean, theme: Theme): Text {
524
+ function renderGeneric(text: string, lines: string[], expanded: boolean, theme: Theme): string[] {
488
525
  const hasError = text.includes("Error:") || text.includes(theme.status.error);
489
526
  const hasSuccess = text.includes(theme.status.success) || text.includes("Applied");
490
527
 
@@ -502,7 +539,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
502
539
  const branch = isLast ? theme.tree.last : theme.tree.branch;
503
540
  output += `\n ${theme.fg("dim", branch)} ${lines[i]}`;
504
541
  }
505
- return new Text(output, 0, 0);
542
+ return output.split("\n");
506
543
  }
507
544
 
508
545
  const firstLine = lines[0] || "No output";
@@ -530,7 +567,7 @@ function renderGeneric(text: string, lines: string[], expanded: boolean, theme:
530
567
  }
531
568
  }
532
569
 
533
- return new Text(output, 0, 0);
570
+ return output.split("\n");
534
571
  }
535
572
 
536
573
  // =============================================================================
@@ -574,4 +611,5 @@ function severityToColor(severity: string): "error" | "warning" | "accent" | "di
574
611
  export const lspToolRenderer = {
575
612
  renderCall,
576
613
  renderResult,
614
+ mergeCallAndResult: true,
577
615
  };