@oh-my-pi/pi-coding-agent 3.30.0 → 3.31.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 (155) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/package.json +5 -5
  3. package/src/cli/args.ts +4 -0
  4. package/src/core/agent-session.ts +29 -2
  5. package/src/core/bash-executor.ts +2 -1
  6. package/src/core/custom-commands/bundled/review/index.ts +369 -14
  7. package/src/core/custom-commands/bundled/wt/index.ts +1 -1
  8. package/src/core/session-manager.ts +158 -246
  9. package/src/core/session-storage.ts +379 -0
  10. package/src/core/settings-manager.ts +155 -4
  11. package/src/core/system-prompt.ts +62 -64
  12. package/src/core/tools/ask.ts +5 -4
  13. package/src/core/tools/bash-interceptor.ts +26 -61
  14. package/src/core/tools/bash.ts +13 -8
  15. package/src/core/tools/edit-diff.ts +11 -4
  16. package/src/core/tools/edit.ts +7 -13
  17. package/src/core/tools/find.ts +111 -50
  18. package/src/core/tools/gemini-image.ts +128 -147
  19. package/src/core/tools/grep.ts +397 -415
  20. package/src/core/tools/index.test.ts +5 -1
  21. package/src/core/tools/index.ts +6 -8
  22. package/src/core/tools/ls.ts +12 -10
  23. package/src/core/tools/lsp/client.ts +58 -9
  24. package/src/core/tools/lsp/config.ts +205 -656
  25. package/src/core/tools/lsp/defaults.json +465 -0
  26. package/src/core/tools/lsp/index.ts +55 -32
  27. package/src/core/tools/lsp/rust-analyzer.ts +49 -10
  28. package/src/core/tools/lsp/types.ts +1 -0
  29. package/src/core/tools/lsp/utils.ts +1 -1
  30. package/src/core/tools/read.ts +150 -74
  31. package/src/core/tools/render-utils.ts +70 -10
  32. package/src/core/tools/review.ts +38 -126
  33. package/src/core/tools/task/artifacts.ts +5 -4
  34. package/src/core/tools/task/executor.ts +94 -83
  35. package/src/core/tools/task/index.ts +129 -92
  36. package/src/core/tools/task/parallel.ts +30 -3
  37. package/src/core/tools/task/render.ts +85 -39
  38. package/src/core/tools/task/types.ts +15 -6
  39. package/src/core/tools/task/worker.ts +124 -89
  40. package/src/core/tools/web-fetch.ts +112 -377
  41. package/src/core/tools/{web-fetch-handlers → web-scrapers}/artifacthub.ts +6 -1
  42. package/src/core/tools/{web-fetch-handlers → web-scrapers}/arxiv.ts +8 -4
  43. package/src/core/tools/{web-fetch-handlers → web-scrapers}/aur.ts +6 -2
  44. package/src/core/tools/{web-fetch-handlers → web-scrapers}/biorxiv.ts +6 -1
  45. package/src/core/tools/{web-fetch-handlers → web-scrapers}/bluesky.ts +10 -3
  46. package/src/core/tools/{web-fetch-handlers → web-scrapers}/brew.ts +6 -2
  47. package/src/core/tools/{web-fetch-handlers → web-scrapers}/cheatsh.ts +6 -1
  48. package/src/core/tools/{web-fetch-handlers → web-scrapers}/chocolatey.ts +6 -1
  49. package/src/core/tools/web-scrapers/choosealicense.ts +110 -0
  50. package/src/core/tools/web-scrapers/cisa-kev.ts +100 -0
  51. package/src/core/tools/web-scrapers/clojars.ts +180 -0
  52. package/src/core/tools/{web-fetch-handlers → web-scrapers}/coingecko.ts +6 -1
  53. package/src/core/tools/{web-fetch-handlers → web-scrapers}/crates-io.ts +7 -2
  54. package/src/core/tools/web-scrapers/crossref.ts +149 -0
  55. package/src/core/tools/{web-fetch-handlers → web-scrapers}/devto.ts +8 -4
  56. package/src/core/tools/{web-fetch-handlers → web-scrapers}/discogs.ts +6 -1
  57. package/src/core/tools/web-scrapers/discourse.ts +221 -0
  58. package/src/core/tools/{web-fetch-handlers → web-scrapers}/dockerhub.ts +7 -3
  59. package/src/core/tools/web-scrapers/fdroid.ts +158 -0
  60. package/src/core/tools/web-scrapers/firefox-addons.ts +214 -0
  61. package/src/core/tools/web-scrapers/flathub.ts +239 -0
  62. package/src/core/tools/{web-fetch-handlers → web-scrapers}/github-gist.ts +6 -2
  63. package/src/core/tools/{web-fetch-handlers → web-scrapers}/github.ts +63 -32
  64. package/src/core/tools/{web-fetch-handlers → web-scrapers}/gitlab.ts +31 -19
  65. package/src/core/tools/{web-fetch-handlers → web-scrapers}/go-pkg.ts +8 -4
  66. package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackage.ts +6 -1
  67. package/src/core/tools/{web-fetch-handlers → web-scrapers}/hackernews.ts +18 -18
  68. package/src/core/tools/{web-fetch-handlers → web-scrapers}/hex.ts +3 -3
  69. package/src/core/tools/{web-fetch-handlers → web-scrapers}/huggingface.ts +10 -10
  70. package/src/core/tools/{web-fetch-handlers → web-scrapers}/iacr.ts +8 -4
  71. package/src/core/tools/web-scrapers/index.ts +250 -0
  72. package/src/core/tools/web-scrapers/jetbrains-marketplace.ts +169 -0
  73. package/src/core/tools/web-scrapers/lemmy.ts +220 -0
  74. package/src/core/tools/{web-fetch-handlers → web-scrapers}/lobsters.ts +3 -3
  75. package/src/core/tools/{web-fetch-handlers → web-scrapers}/mastodon.ts +11 -3
  76. package/src/core/tools/{web-fetch-handlers → web-scrapers}/maven.ts +6 -1
  77. package/src/core/tools/{web-fetch-handlers → web-scrapers}/mdn.ts +2 -2
  78. package/src/core/tools/{web-fetch-handlers → web-scrapers}/metacpan.ts +13 -7
  79. package/src/core/tools/web-scrapers/musicbrainz.ts +273 -0
  80. package/src/core/tools/{web-fetch-handlers → web-scrapers}/npm.ts +12 -5
  81. package/src/core/tools/{web-fetch-handlers → web-scrapers}/nuget.ts +9 -5
  82. package/src/core/tools/{web-fetch-handlers → web-scrapers}/nvd.ts +6 -1
  83. package/src/core/tools/web-scrapers/ollama.ts +267 -0
  84. package/src/core/tools/web-scrapers/open-vsx.ts +119 -0
  85. package/src/core/tools/{web-fetch-handlers → web-scrapers}/opencorporates.ts +2 -0
  86. package/src/core/tools/{web-fetch-handlers → web-scrapers}/openlibrary.ts +18 -12
  87. package/src/core/tools/web-scrapers/orcid.ts +299 -0
  88. package/src/core/tools/{web-fetch-handlers → web-scrapers}/osv.ts +6 -1
  89. package/src/core/tools/{web-fetch-handlers → web-scrapers}/packagist.ts +6 -2
  90. package/src/core/tools/{web-fetch-handlers → web-scrapers}/pub-dev.ts +3 -3
  91. package/src/core/tools/{web-fetch-handlers → web-scrapers}/pubmed.ts +8 -4
  92. package/src/core/tools/{web-fetch-handlers → web-scrapers}/pypi.ts +7 -3
  93. package/src/core/tools/web-scrapers/rawg.ts +124 -0
  94. package/src/core/tools/{web-fetch-handlers → web-scrapers}/readthedocs.ts +7 -3
  95. package/src/core/tools/{web-fetch-handlers → web-scrapers}/reddit.ts +6 -2
  96. package/src/core/tools/{web-fetch-handlers → web-scrapers}/repology.ts +6 -1
  97. package/src/core/tools/{web-fetch-handlers → web-scrapers}/rfc.ts +7 -3
  98. package/src/core/tools/{web-fetch-handlers → web-scrapers}/rubygems.ts +6 -1
  99. package/src/core/tools/web-scrapers/searchcode.ts +217 -0
  100. package/src/core/tools/{web-fetch-handlers → web-scrapers}/sec-edgar.ts +6 -1
  101. package/src/core/tools/{web-fetch-handlers → web-scrapers}/semantic-scholar.ts +2 -2
  102. package/src/core/tools/web-scrapers/snapcraft.ts +200 -0
  103. package/src/core/tools/web-scrapers/sourcegraph.ts +373 -0
  104. package/src/core/tools/web-scrapers/spdx.ts +121 -0
  105. package/src/core/tools/{web-fetch-handlers → web-scrapers}/spotify.ts +3 -3
  106. package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackoverflow.ts +3 -2
  107. package/src/core/tools/{web-fetch-handlers → web-scrapers}/terraform.ts +11 -3
  108. package/src/core/tools/{web-fetch-handlers → web-scrapers}/tldr.ts +6 -2
  109. package/src/core/tools/{web-fetch-handlers → web-scrapers}/twitter.ts +15 -3
  110. package/src/core/tools/{web-fetch-handlers → web-scrapers}/types.ts +98 -27
  111. package/src/core/tools/web-scrapers/utils.ts +162 -0
  112. package/src/core/tools/{web-fetch-handlers → web-scrapers}/vimeo.ts +3 -3
  113. package/src/core/tools/web-scrapers/vscode-marketplace.ts +195 -0
  114. package/src/core/tools/web-scrapers/w3c.ts +163 -0
  115. package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikidata.ts +13 -5
  116. package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.ts +7 -3
  117. package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.ts +72 -20
  118. package/src/core/tools/write.ts +21 -18
  119. package/src/core/voice.ts +3 -2
  120. package/src/lib/worktree/collapse.ts +2 -1
  121. package/src/lib/worktree/git.ts +2 -18
  122. package/src/main.ts +59 -3
  123. package/src/modes/interactive/components/extensions/extension-dashboard.ts +33 -19
  124. package/src/modes/interactive/components/extensions/extension-list.ts +15 -8
  125. package/src/modes/interactive/components/hook-editor.ts +2 -1
  126. package/src/modes/interactive/components/model-selector.ts +19 -4
  127. package/src/modes/interactive/interactive-mode.ts +41 -38
  128. package/src/modes/interactive/theme/theme.ts +58 -58
  129. package/src/modes/rpc/rpc-mode.ts +10 -9
  130. package/src/prompts/review-request.md +27 -0
  131. package/src/prompts/reviewer.md +64 -68
  132. package/src/prompts/tools/output.md +22 -3
  133. package/src/prompts/tools/task.md +32 -33
  134. package/src/utils/clipboard.ts +2 -1
  135. package/examples/extensions/subagent/agents/reviewer.md +0 -35
  136. package/src/core/tools/web-fetch-handlers/index.ts +0 -69
  137. package/src/core/tools/web-fetch-handlers/utils.ts +0 -91
  138. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/academic.test.ts +0 -0
  139. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/business.test.ts +0 -0
  140. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/dev-platforms.test.ts +0 -0
  141. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/documentation.test.ts +0 -0
  142. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/finance-media.test.ts +0 -0
  143. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/git-hosting.test.ts +0 -0
  144. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/media.test.ts +0 -0
  145. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers-2.test.ts +0 -0
  146. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-managers.test.ts +0 -0
  147. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/package-registries.test.ts +0 -0
  148. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/research.test.ts +0 -0
  149. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/security.test.ts +0 -0
  150. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social-extended.test.ts +0 -0
  151. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/social.test.ts +0 -0
  152. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/stackexchange.test.ts +0 -0
  153. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/standards.test.ts +0 -0
  154. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/wikipedia.test.ts +0 -0
  155. /package/src/core/tools/{web-fetch-handlers → web-scrapers}/youtube.test.ts +0 -0
@@ -0,0 +1,465 @@
1
+ {
2
+ "rust-analyzer": {
3
+ "command": "rust-analyzer",
4
+ "args": [],
5
+ "fileTypes": [".rs"],
6
+ "rootMarkers": ["Cargo.toml", "rust-analyzer.toml"],
7
+ "initOptions": {
8
+ "checkOnSave": { "command": "clippy" },
9
+ "cargo": { "allFeatures": true },
10
+ "procMacro": { "enable": true }
11
+ },
12
+ "settings": {
13
+ "rust-analyzer": {
14
+ "diagnostics": { "enable": true },
15
+ "inlayHints": { "enable": true }
16
+ }
17
+ },
18
+ "capabilities": {
19
+ "flycheck": true,
20
+ "ssr": true,
21
+ "expandMacro": true,
22
+ "runnables": true,
23
+ "relatedTests": true
24
+ }
25
+ },
26
+ "clangd": {
27
+ "command": "clangd",
28
+ "args": ["--background-index", "--clang-tidy", "--header-insertion=iwyu"],
29
+ "fileTypes": [".c", ".cpp", ".cc", ".cxx", ".h", ".hpp", ".hxx", ".m", ".mm"],
30
+ "rootMarkers": ["compile_commands.json", "CMakeLists.txt", ".clangd", ".clang-format", "Makefile"]
31
+ },
32
+ "zls": {
33
+ "command": "zls",
34
+ "args": [],
35
+ "fileTypes": [".zig"],
36
+ "rootMarkers": ["build.zig", "build.zig.zon", "zls.json"]
37
+ },
38
+ "gopls": {
39
+ "command": "gopls",
40
+ "args": ["serve"],
41
+ "fileTypes": [".go", ".mod", ".sum"],
42
+ "rootMarkers": ["go.mod", "go.work", "go.sum"],
43
+ "settings": {
44
+ "gopls": {
45
+ "analyses": { "unusedparams": true, "shadow": true },
46
+ "staticcheck": true,
47
+ "gofumpt": true
48
+ }
49
+ }
50
+ },
51
+ "typescript-language-server": {
52
+ "command": "typescript-language-server",
53
+ "args": ["--stdio"],
54
+ "fileTypes": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
55
+ "rootMarkers": ["package.json", "tsconfig.json", "jsconfig.json"],
56
+ "initOptions": {
57
+ "hostInfo": "omp-coding-agent",
58
+ "preferences": {
59
+ "includeInlayParameterNameHints": "all",
60
+ "includeInlayVariableTypeHints": true,
61
+ "includeInlayFunctionParameterTypeHints": true
62
+ }
63
+ }
64
+ },
65
+ "biome": {
66
+ "command": "biome",
67
+ "args": ["lsp-proxy"],
68
+ "fileTypes": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".json", ".jsonc"],
69
+ "rootMarkers": ["biome.json", "biome.jsonc"],
70
+ "isLinter": true
71
+ },
72
+ "eslint": {
73
+ "command": "vscode-eslint-language-server",
74
+ "args": ["--stdio"],
75
+ "fileTypes": [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".vue", ".svelte"],
76
+ "rootMarkers": [
77
+ ".eslintrc",
78
+ ".eslintrc.js",
79
+ ".eslintrc.json",
80
+ ".eslintrc.yml",
81
+ "eslint.config.js",
82
+ "eslint.config.mjs"
83
+ ],
84
+ "isLinter": true,
85
+ "settings": {
86
+ "validate": "on",
87
+ "run": "onType"
88
+ }
89
+ },
90
+ "denols": {
91
+ "command": "deno",
92
+ "args": ["lsp"],
93
+ "fileTypes": [".ts", ".tsx", ".js", ".jsx"],
94
+ "rootMarkers": ["deno.json", "deno.jsonc", "deno.lock"],
95
+ "initOptions": {
96
+ "enable": true,
97
+ "lint": true,
98
+ "unstable": true
99
+ }
100
+ },
101
+ "vscode-html-language-server": {
102
+ "command": "vscode-html-language-server",
103
+ "args": ["--stdio"],
104
+ "fileTypes": [".html", ".htm"],
105
+ "rootMarkers": ["package.json", ".git"],
106
+ "initOptions": {
107
+ "provideFormatter": true
108
+ }
109
+ },
110
+ "vscode-css-language-server": {
111
+ "command": "vscode-css-language-server",
112
+ "args": ["--stdio"],
113
+ "fileTypes": [".css", ".scss", ".sass", ".less"],
114
+ "rootMarkers": ["package.json", ".git"],
115
+ "initOptions": {
116
+ "provideFormatter": true
117
+ }
118
+ },
119
+ "vscode-json-language-server": {
120
+ "command": "vscode-json-language-server",
121
+ "args": ["--stdio"],
122
+ "fileTypes": [".json", ".jsonc"],
123
+ "rootMarkers": ["package.json", ".git"],
124
+ "initOptions": {
125
+ "provideFormatter": true
126
+ }
127
+ },
128
+ "tailwindcss": {
129
+ "command": "tailwindcss-language-server",
130
+ "args": ["--stdio"],
131
+ "fileTypes": [".html", ".css", ".scss", ".js", ".jsx", ".ts", ".tsx", ".vue", ".svelte"],
132
+ "rootMarkers": ["tailwind.config.js", "tailwind.config.ts", "tailwind.config.mjs", "tailwind.config.cjs"]
133
+ },
134
+ "svelte": {
135
+ "command": "svelteserver",
136
+ "args": ["--stdio"],
137
+ "fileTypes": [".svelte"],
138
+ "rootMarkers": ["svelte.config.js", "svelte.config.mjs", "package.json"]
139
+ },
140
+ "vue-language-server": {
141
+ "command": "vue-language-server",
142
+ "args": ["--stdio"],
143
+ "fileTypes": [".vue"],
144
+ "rootMarkers": ["vue.config.js", "nuxt.config.js", "nuxt.config.ts", "package.json"]
145
+ },
146
+ "astro": {
147
+ "command": "astro-ls",
148
+ "args": ["--stdio"],
149
+ "fileTypes": [".astro"],
150
+ "rootMarkers": ["astro.config.mjs", "astro.config.js", "astro.config.ts"]
151
+ },
152
+ "pyright": {
153
+ "command": "pyright-langserver",
154
+ "args": ["--stdio"],
155
+ "fileTypes": [".py", ".pyi"],
156
+ "rootMarkers": ["pyproject.toml", "pyrightconfig.json", "setup.py", "setup.cfg", "requirements.txt", "Pipfile"],
157
+ "settings": {
158
+ "python": {
159
+ "analysis": {
160
+ "autoSearchPaths": true,
161
+ "diagnosticMode": "openFilesOnly",
162
+ "useLibraryCodeForTypes": true
163
+ }
164
+ }
165
+ }
166
+ },
167
+ "basedpyright": {
168
+ "command": "basedpyright-langserver",
169
+ "args": ["--stdio"],
170
+ "fileTypes": [".py", ".pyi"],
171
+ "rootMarkers": ["pyproject.toml", "pyrightconfig.json", "setup.py", "requirements.txt"],
172
+ "settings": {
173
+ "basedpyright": {
174
+ "analysis": {
175
+ "autoSearchPaths": true,
176
+ "diagnosticMode": "openFilesOnly",
177
+ "useLibraryCodeForTypes": true
178
+ }
179
+ }
180
+ }
181
+ },
182
+ "pylsp": {
183
+ "command": "pylsp",
184
+ "args": [],
185
+ "fileTypes": [".py"],
186
+ "rootMarkers": ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile"]
187
+ },
188
+ "ruff": {
189
+ "command": "ruff",
190
+ "args": ["server"],
191
+ "fileTypes": [".py", ".pyi"],
192
+ "rootMarkers": ["pyproject.toml", "ruff.toml", ".ruff.toml"],
193
+ "isLinter": true
194
+ },
195
+ "jdtls": {
196
+ "command": "jdtls",
197
+ "args": [],
198
+ "fileTypes": [".java"],
199
+ "rootMarkers": ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", ".project"]
200
+ },
201
+ "kotlin-language-server": {
202
+ "command": "kotlin-language-server",
203
+ "args": [],
204
+ "fileTypes": [".kt", ".kts"],
205
+ "rootMarkers": ["build.gradle", "build.gradle.kts", "pom.xml", "settings.gradle", "settings.gradle.kts"]
206
+ },
207
+ "metals": {
208
+ "command": "metals",
209
+ "args": [],
210
+ "fileTypes": [".scala", ".sbt", ".sc"],
211
+ "rootMarkers": ["build.sbt", "build.sc", "build.gradle", "pom.xml"],
212
+ "initOptions": {
213
+ "statusBarProvider": "show-message",
214
+ "isHttpEnabled": true
215
+ }
216
+ },
217
+ "hls": {
218
+ "command": "haskell-language-server-wrapper",
219
+ "args": ["--lsp"],
220
+ "fileTypes": [".hs", ".lhs"],
221
+ "rootMarkers": ["stack.yaml", "cabal.project", "hie.yaml", "package.yaml", "*.cabal"],
222
+ "settings": {
223
+ "haskell": {
224
+ "formattingProvider": "ormolu",
225
+ "checkProject": true
226
+ }
227
+ }
228
+ },
229
+ "ocamllsp": {
230
+ "command": "ocamllsp",
231
+ "args": [],
232
+ "fileTypes": [".ml", ".mli", ".mll", ".mly"],
233
+ "rootMarkers": ["dune-project", "dune-workspace", "*.opam", ".ocamlformat"]
234
+ },
235
+ "elixirls": {
236
+ "command": "elixir-ls",
237
+ "args": [],
238
+ "fileTypes": [".ex", ".exs", ".heex", ".eex"],
239
+ "rootMarkers": ["mix.exs", "mix.lock"],
240
+ "settings": {
241
+ "elixirLS": {
242
+ "dialyzerEnabled": true,
243
+ "fetchDeps": false
244
+ }
245
+ }
246
+ },
247
+ "erlangls": {
248
+ "command": "erlang_ls",
249
+ "args": [],
250
+ "fileTypes": [".erl", ".hrl"],
251
+ "rootMarkers": ["rebar.config", "erlang.mk", "rebar.lock"]
252
+ },
253
+ "gleam": {
254
+ "command": "gleam",
255
+ "args": ["lsp"],
256
+ "fileTypes": [".gleam"],
257
+ "rootMarkers": ["gleam.toml"]
258
+ },
259
+ "solargraph": {
260
+ "command": "solargraph",
261
+ "args": ["stdio"],
262
+ "fileTypes": [".rb", ".rake", ".gemspec"],
263
+ "rootMarkers": ["Gemfile", ".solargraph.yml", "Rakefile"],
264
+ "initOptions": {
265
+ "formatting": true
266
+ },
267
+ "settings": {
268
+ "solargraph": {
269
+ "diagnostics": true,
270
+ "completion": true,
271
+ "hover": true,
272
+ "formatting": true,
273
+ "references": true,
274
+ "rename": true,
275
+ "symbols": true
276
+ }
277
+ }
278
+ },
279
+ "ruby-lsp": {
280
+ "command": "ruby-lsp",
281
+ "args": [],
282
+ "fileTypes": [".rb", ".rake", ".gemspec", ".erb"],
283
+ "rootMarkers": ["Gemfile", ".ruby-version", ".ruby-gemset"],
284
+ "initOptions": {
285
+ "formatter": "auto"
286
+ }
287
+ },
288
+ "rubocop": {
289
+ "command": "rubocop",
290
+ "args": ["--lsp"],
291
+ "fileTypes": [".rb", ".rake"],
292
+ "rootMarkers": [".rubocop.yml", "Gemfile"],
293
+ "isLinter": true
294
+ },
295
+ "bashls": {
296
+ "command": "bash-language-server",
297
+ "args": ["start"],
298
+ "fileTypes": [".sh", ".bash", ".zsh"],
299
+ "rootMarkers": [".git"],
300
+ "settings": {
301
+ "bashIde": {
302
+ "globPattern": "*@(.sh|.inc|.bash|.command)"
303
+ }
304
+ }
305
+ },
306
+ "nushell": {
307
+ "command": "nu",
308
+ "args": ["--lsp"],
309
+ "fileTypes": [".nu"],
310
+ "rootMarkers": [".git"]
311
+ },
312
+ "lua-language-server": {
313
+ "command": "lua-language-server",
314
+ "args": [],
315
+ "fileTypes": [".lua"],
316
+ "rootMarkers": [".luarc.json", ".luarc.jsonc", ".luacheckrc", ".stylua.toml", "stylua.toml"],
317
+ "settings": {
318
+ "Lua": {
319
+ "runtime": { "version": "LuaJIT" },
320
+ "diagnostics": { "globals": ["vim"] },
321
+ "workspace": { "checkThirdParty": false },
322
+ "telemetry": { "enable": false }
323
+ }
324
+ }
325
+ },
326
+ "intelephense": {
327
+ "command": "intelephense",
328
+ "args": ["--stdio"],
329
+ "fileTypes": [".php", ".phtml"],
330
+ "rootMarkers": ["composer.json", "composer.lock", ".git"]
331
+ },
332
+ "phpactor": {
333
+ "command": "phpactor",
334
+ "args": ["language-server"],
335
+ "fileTypes": [".php"],
336
+ "rootMarkers": ["composer.json", ".phpactor.json", ".phpactor.yml"]
337
+ },
338
+ "omnisharp": {
339
+ "command": "omnisharp",
340
+ "args": ["-z", "--hostPID", "$PID", "--encoding", "utf-8", "--languageserver"],
341
+ "fileTypes": [".cs", ".csx"],
342
+ "rootMarkers": ["*.sln", "*.csproj", "omnisharp.json", ".git"],
343
+ "settings": {
344
+ "FormattingOptions": { "EnableEditorConfigSupport": true },
345
+ "RoslynExtensionsOptions": { "EnableAnalyzersSupport": true }
346
+ }
347
+ },
348
+ "yamlls": {
349
+ "command": "yaml-language-server",
350
+ "args": ["--stdio"],
351
+ "fileTypes": [".yaml", ".yml"],
352
+ "rootMarkers": [".git"],
353
+ "settings": {
354
+ "yaml": {
355
+ "validate": true,
356
+ "format": { "enable": true },
357
+ "hover": true,
358
+ "completion": true
359
+ },
360
+ "redhat": { "telemetry": { "enabled": false } }
361
+ }
362
+ },
363
+ "taplo": {
364
+ "command": "taplo",
365
+ "args": ["lsp", "stdio"],
366
+ "fileTypes": [".toml"],
367
+ "rootMarkers": [".taplo.toml", "taplo.toml", ".git"]
368
+ },
369
+ "terraformls": {
370
+ "command": "terraform-ls",
371
+ "args": ["serve"],
372
+ "fileTypes": [".tf", ".tfvars"],
373
+ "rootMarkers": [".terraform", "terraform.tfstate", "*.tf"]
374
+ },
375
+ "dockerls": {
376
+ "command": "docker-langserver",
377
+ "args": ["--stdio"],
378
+ "fileTypes": [".dockerfile", "Dockerfile"],
379
+ "rootMarkers": ["Dockerfile", "docker-compose.yml", "docker-compose.yaml", ".dockerignore"]
380
+ },
381
+ "helm-ls": {
382
+ "command": "helm_ls",
383
+ "args": ["serve"],
384
+ "fileTypes": [".yaml", ".yml", ".tpl"],
385
+ "rootMarkers": ["Chart.yaml", "Chart.yml"]
386
+ },
387
+ "nixd": {
388
+ "command": "nixd",
389
+ "args": [],
390
+ "fileTypes": [".nix"],
391
+ "rootMarkers": ["flake.nix", "default.nix", "shell.nix"]
392
+ },
393
+ "nil": {
394
+ "command": "nil",
395
+ "args": [],
396
+ "fileTypes": [".nix"],
397
+ "rootMarkers": ["flake.nix", "default.nix", "shell.nix"]
398
+ },
399
+ "ols": {
400
+ "command": "ols",
401
+ "args": [],
402
+ "fileTypes": [".odin"],
403
+ "rootMarkers": ["ols.json", ".git"]
404
+ },
405
+ "dartls": {
406
+ "command": "dart",
407
+ "args": ["language-server", "--protocol=lsp"],
408
+ "fileTypes": [".dart"],
409
+ "rootMarkers": ["pubspec.yaml", "pubspec.lock"],
410
+ "initOptions": {
411
+ "closingLabels": true,
412
+ "flutterOutline": true,
413
+ "outline": true
414
+ }
415
+ },
416
+ "marksman": {
417
+ "command": "marksman",
418
+ "args": ["server"],
419
+ "fileTypes": [".md", ".markdown"],
420
+ "rootMarkers": [".marksman.toml", ".git"]
421
+ },
422
+ "texlab": {
423
+ "command": "texlab",
424
+ "args": [],
425
+ "fileTypes": [".tex", ".bib", ".sty", ".cls"],
426
+ "rootMarkers": [".latexmkrc", "latexmkrc", ".texlabroot", "texlabroot", "Tectonic.toml"],
427
+ "settings": {
428
+ "texlab": {
429
+ "build": {
430
+ "executable": "latexmk",
431
+ "args": ["-pdf", "-interaction=nonstopmode", "-synctex=1", "%f"]
432
+ },
433
+ "chktex": { "onOpenAndSave": true }
434
+ }
435
+ }
436
+ },
437
+ "graphql": {
438
+ "command": "graphql-lsp",
439
+ "args": ["server", "-m", "stream"],
440
+ "fileTypes": [".graphql", ".gql"],
441
+ "rootMarkers": [".graphqlrc", ".graphqlrc.json", ".graphqlrc.yml", ".graphqlrc.yaml", "graphql.config.js"]
442
+ },
443
+ "prismals": {
444
+ "command": "prisma-language-server",
445
+ "args": ["--stdio"],
446
+ "fileTypes": [".prisma"],
447
+ "rootMarkers": ["schema.prisma", "prisma/schema.prisma"]
448
+ },
449
+ "vimls": {
450
+ "command": "vim-language-server",
451
+ "args": ["--stdio"],
452
+ "fileTypes": [".vim", ".vimrc"],
453
+ "rootMarkers": [".git"],
454
+ "initOptions": {
455
+ "isNeovim": true,
456
+ "diagnostic": { "enable": true }
457
+ }
458
+ },
459
+ "emmet-language-server": {
460
+ "command": "emmet-language-server",
461
+ "args": ["--stdio"],
462
+ "fileTypes": [".html", ".css", ".scss", ".less", ".jsx", ".tsx", ".vue", ".svelte"],
463
+ "rootMarkers": [".git"]
464
+ }
465
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Dirent } from "node:fs";
2
- import { existsSync } from "node:fs";
2
+ import { existsSync, statSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
5
5
  import type { BunFile } from "bun";
@@ -136,14 +136,18 @@ async function syncFileContent(
136
136
  content: string,
137
137
  cwd: string,
138
138
  servers: Array<[string, ServerConfig]>,
139
+ signal?: AbortSignal,
139
140
  ): Promise<void> {
141
+ signal?.throwIfAborted();
140
142
  await Promise.allSettled(
141
143
  servers.map(async ([_serverName, serverConfig]) => {
144
+ signal?.throwIfAborted();
142
145
  if (serverConfig.createClient) {
143
146
  return;
144
147
  }
145
148
  const client = await getOrCreateClient(serverConfig, cwd);
146
- await syncContent(client, absolutePath, content);
149
+ signal?.throwIfAborted();
150
+ await syncContent(client, absolutePath, content, signal);
147
151
  }),
148
152
  );
149
153
  }
@@ -160,14 +164,17 @@ async function notifyFileSaved(
160
164
  absolutePath: string,
161
165
  cwd: string,
162
166
  servers: Array<[string, ServerConfig]>,
167
+ signal?: AbortSignal,
163
168
  ): Promise<void> {
169
+ signal?.throwIfAborted();
164
170
  await Promise.allSettled(
165
171
  servers.map(async ([_serverName, serverConfig]) => {
172
+ signal?.throwIfAborted();
166
173
  if (serverConfig.createClient) {
167
174
  return;
168
175
  }
169
176
  const client = await getOrCreateClient(serverConfig, cwd);
170
- await notifySaved(client, absolutePath);
177
+ await notifySaved(client, absolutePath, signal);
171
178
  }),
172
179
  );
173
180
  }
@@ -227,13 +234,19 @@ function findFileByExtensions(baseDir: string, extensions: string[], maxDepth: n
227
234
  const normalized = extensions.map((ext) => ext.toLowerCase());
228
235
  const search = (dir: string, depth: number): string | null => {
229
236
  if (depth > maxDepth) return null;
230
- let entries: Dirent[];
237
+ const entries: Dirent[] = [];
231
238
  try {
232
- entries = Array.from(new Bun.Glob("*").scanSync({ cwd: dir, onlyFiles: false })).map((name) => ({
233
- name,
234
- isFile: () => !existsSync(path.join(dir, name)) || Bun.file(path.join(dir, name)).type !== "directory",
235
- isDirectory: () => existsSync(path.join(dir, name)) && Bun.file(path.join(dir, name)).type === "directory",
236
- })) as Dirent[];
239
+ const names = Array.from(new Bun.Glob("*").scanSync({ cwd: dir, onlyFiles: false }));
240
+ for (const name of names) {
241
+ const fullPath = path.join(dir, name);
242
+ let isDir = false;
243
+ try {
244
+ isDir = statSync(fullPath).isDirectory();
245
+ } catch {
246
+ continue;
247
+ }
248
+ entries.push({ name, isFile: () => !isDir, isDirectory: () => isDir } as Dirent);
249
+ }
237
250
  } catch {
238
251
  return null;
239
252
  }
@@ -298,9 +311,15 @@ function getServerForWorkspaceAction(config: LspConfig, action: string): [string
298
311
  return null;
299
312
  }
300
313
 
301
- async function waitForDiagnostics(client: LspClient, uri: string, timeoutMs = 3000): Promise<Diagnostic[]> {
314
+ async function waitForDiagnostics(
315
+ client: LspClient,
316
+ uri: string,
317
+ timeoutMs = 3000,
318
+ signal?: AbortSignal,
319
+ ): Promise<Diagnostic[]> {
302
320
  const start = Date.now();
303
321
  while (Date.now() - start < timeoutMs) {
322
+ signal?.throwIfAborted();
304
323
  const diagnostics = client.diagnostics.get(uri);
305
324
  if (diagnostics !== undefined) return diagnostics;
306
325
  await sleep(100);
@@ -440,6 +459,7 @@ async function getDiagnosticsForFile(
440
459
  absolutePath: string,
441
460
  cwd: string,
442
461
  servers: Array<[string, ServerConfig]>,
462
+ signal?: AbortSignal,
443
463
  ): Promise<FileDiagnosticsResult | undefined> {
444
464
  if (servers.length === 0) {
445
465
  return undefined;
@@ -453,6 +473,7 @@ async function getDiagnosticsForFile(
453
473
  // Wait for diagnostics from all servers in parallel
454
474
  const results = await Promise.allSettled(
455
475
  servers.map(async ([serverName, serverConfig]) => {
476
+ signal?.throwIfAborted();
456
477
  // Use custom linter client if configured
457
478
  if (serverConfig.createClient) {
458
479
  const linterClient = getLinterClient(serverName, serverConfig, cwd);
@@ -462,8 +483,9 @@ async function getDiagnosticsForFile(
462
483
 
463
484
  // Default: use LSP
464
485
  const client = await getOrCreateClient(serverConfig, cwd);
486
+ signal?.throwIfAborted();
465
487
  // Content already synced + didSave sent, just wait for diagnostics
466
- const diagnostics = await waitForDiagnostics(client, uri);
488
+ const diagnostics = await waitForDiagnostics(client, uri, 3000, signal);
467
489
  return { serverName, diagnostics };
468
490
  }),
469
491
  );
@@ -539,6 +561,7 @@ async function formatContent(
539
561
  content: string,
540
562
  cwd: string,
541
563
  servers: Array<[string, ServerConfig]>,
564
+ signal?: AbortSignal,
542
565
  ): Promise<string> {
543
566
  if (servers.length === 0) {
544
567
  return content;
@@ -548,6 +571,7 @@ async function formatContent(
548
571
 
549
572
  for (const [serverName, serverConfig] of servers) {
550
573
  try {
574
+ signal?.throwIfAborted();
551
575
  // Use custom linter client if configured
552
576
  if (serverConfig.createClient) {
553
577
  const linterClient = getLinterClient(serverName, serverConfig, cwd);
@@ -556,6 +580,7 @@ async function formatContent(
556
580
 
557
581
  // Default: use LSP
558
582
  const client = await getOrCreateClient(serverConfig, cwd);
583
+ signal?.throwIfAborted();
559
584
 
560
585
  const caps = client.serverCapabilities;
561
586
  if (!caps?.documentFormattingProvider) {
@@ -563,10 +588,15 @@ async function formatContent(
563
588
  }
564
589
 
565
590
  // Request formatting (content already synced)
566
- const edits = (await sendRequest(client, "textDocument/formatting", {
567
- textDocument: { uri },
568
- options: DEFAULT_FORMAT_OPTIONS,
569
- })) as TextEdit[] | null;
591
+ const edits = (await sendRequest(
592
+ client,
593
+ "textDocument/formatting",
594
+ {
595
+ textDocument: { uri },
596
+ options: DEFAULT_FORMAT_OPTIONS,
597
+ },
598
+ signal,
599
+ )) as TextEdit[] | null;
570
600
 
571
601
  if (!edits || edits.length === 0) {
572
602
  return content;
@@ -633,28 +663,29 @@ export function createLspWritethrough(cwd: string, options?: WritethroughOptions
633
663
  let formatter: FileFormatResult | undefined;
634
664
  let diagnostics: FileDiagnosticsResult | undefined;
635
665
  try {
636
- signal ??= AbortSignal.timeout(10_000);
637
- await untilAborted(signal, async () => {
666
+ const timeoutSignal = AbortSignal.timeout(10_000);
667
+ const operationSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
668
+ await untilAborted(operationSignal, async () => {
638
669
  if (useCustomFormatter) {
639
670
  // Custom linters (e.g. Biome CLI) require on-disk input.
640
671
  await writeContent(content);
641
- finalContent = await formatContent(dst, content, cwd, customLinterServers);
672
+ finalContent = await formatContent(dst, content, cwd, customLinterServers, operationSignal);
642
673
  formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
643
674
  await writeContent(finalContent);
644
- await syncFileContent(dst, finalContent, cwd, lspServers);
675
+ await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal);
645
676
  } else {
646
677
  // 1. Sync original content to LSP servers
647
- await syncFileContent(dst, content, cwd, lspServers);
678
+ await syncFileContent(dst, content, cwd, lspServers, operationSignal);
648
679
 
649
680
  // 2. Format in-memory via LSP
650
681
  if (enableFormat) {
651
- finalContent = await formatContent(dst, content, cwd, lspServers);
682
+ finalContent = await formatContent(dst, content, cwd, lspServers, operationSignal);
652
683
  formatter = finalContent !== content ? FileFormatResult.FORMATTED : FileFormatResult.UNCHANGED;
653
684
  }
654
685
 
655
686
  // 3. If formatted, sync formatted content to LSP servers
656
687
  if (finalContent !== content) {
657
- await syncFileContent(dst, finalContent, cwd, lspServers);
688
+ await syncFileContent(dst, finalContent, cwd, lspServers, operationSignal);
658
689
  }
659
690
 
660
691
  // 4. Write to disk
@@ -662,11 +693,11 @@ export function createLspWritethrough(cwd: string, options?: WritethroughOptions
662
693
  }
663
694
 
664
695
  // 5. Notify saved to LSP servers
665
- await notifyFileSaved(dst, cwd, lspServers);
696
+ await notifyFileSaved(dst, cwd, lspServers, operationSignal);
666
697
 
667
698
  // 6. Get diagnostics from all servers
668
699
  if (enableDiagnostics) {
669
- diagnostics = await getDiagnosticsForFile(dst, cwd, servers);
700
+ diagnostics = await getDiagnosticsForFile(dst, cwd, servers, operationSignal);
670
701
  }
671
702
  });
672
703
  } catch {
@@ -1362,11 +1393,3 @@ export function createLspTool(session: ToolSession): AgentTool<typeof lspSchema,
1362
1393
  },
1363
1394
  };
1364
1395
  }
1365
-
1366
- export const lspTool = createLspTool({
1367
- cwd: process.cwd(),
1368
- hasUI: false,
1369
- rulebookRules: [],
1370
- getSessionFile: () => null,
1371
- getSessionSpawns: () => null,
1372
- });