@ilya-lesikov/pi-pi 0.1.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 (271) hide show
  1. package/3p/pi-ask-user/index.ts +1835 -0
  2. package/3p/pi-ask-user/package.json +50 -0
  3. package/3p/pi-ask-user/single-select-layout.ts +203 -0
  4. package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
  5. package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
  6. package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
  7. package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
  8. package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
  9. package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
  10. package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
  11. package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
  12. package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
  13. package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
  14. package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
  15. package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
  16. package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
  17. package/3p/pi-lsp/package.json +54 -0
  18. package/3p/pi-plannotator/AGENTS.md +583 -0
  19. package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
  20. package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
  21. package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
  22. package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
  23. package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
  24. package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
  25. package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
  26. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
  27. package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
  28. package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
  29. package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
  30. package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
  31. package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
  32. package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
  33. package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
  34. package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
  35. package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
  36. package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
  37. package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
  38. package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
  39. package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
  40. package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
  41. package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
  42. package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
  43. package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
  44. package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
  45. package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
  46. package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
  47. package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
  48. package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
  49. package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
  50. package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
  51. package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
  52. package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
  53. package/3p/pi-plannotator/package.json +53 -0
  54. package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
  55. package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
  56. package/3p/pi-plannotator/packages/ai/context.ts +250 -0
  57. package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
  58. package/3p/pi-plannotator/packages/ai/index.ts +105 -0
  59. package/3p/pi-plannotator/packages/ai/package.json +21 -0
  60. package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
  61. package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
  62. package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
  63. package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
  64. package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
  65. package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
  66. package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
  67. package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
  68. package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
  69. package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
  70. package/3p/pi-plannotator/packages/ai/types.ts +379 -0
  71. package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
  72. package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
  73. package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
  74. package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
  75. package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
  76. package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
  77. package/3p/pi-plannotator/packages/server/browser.ts +245 -0
  78. package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
  79. package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
  80. package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
  81. package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
  82. package/3p/pi-plannotator/packages/server/config.ts +8 -0
  83. package/3p/pi-plannotator/packages/server/draft.ts +1 -0
  84. package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
  85. package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
  86. package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
  87. package/3p/pi-plannotator/packages/server/git.ts +141 -0
  88. package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
  89. package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
  90. package/3p/pi-plannotator/packages/server/ide.ts +43 -0
  91. package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
  92. package/3p/pi-plannotator/packages/server/image.ts +66 -0
  93. package/3p/pi-plannotator/packages/server/index.ts +648 -0
  94. package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
  95. package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
  96. package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
  97. package/3p/pi-plannotator/packages/server/jj.ts +86 -0
  98. package/3p/pi-plannotator/packages/server/p4.ts +417 -0
  99. package/3p/pi-plannotator/packages/server/package.json +40 -0
  100. package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
  101. package/3p/pi-plannotator/packages/server/pr.ts +146 -0
  102. package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
  103. package/3p/pi-plannotator/packages/server/project.ts +45 -0
  104. package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
  105. package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
  106. package/3p/pi-plannotator/packages/server/remote.ts +74 -0
  107. package/3p/pi-plannotator/packages/server/repo.ts +80 -0
  108. package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
  109. package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
  110. package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
  111. package/3p/pi-plannotator/packages/server/review.ts +1318 -0
  112. package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
  113. package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
  114. package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
  115. package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
  116. package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
  117. package/3p/pi-plannotator/packages/server/storage.ts +17 -0
  118. package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
  119. package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
  120. package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
  121. package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
  122. package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
  123. package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
  124. package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
  125. package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
  126. package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
  127. package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
  128. package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
  129. package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
  130. package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
  131. package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
  132. package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
  133. package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
  134. package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
  135. package/3p/pi-plannotator/packages/shared/config.ts +262 -0
  136. package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
  137. package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
  138. package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
  139. package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
  140. package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
  141. package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
  142. package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
  143. package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
  144. package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
  145. package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
  146. package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
  147. package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
  148. package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
  149. package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
  150. package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
  151. package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
  152. package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
  153. package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
  154. package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
  155. package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
  156. package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
  157. package/3p/pi-plannotator/packages/shared/package.json +55 -0
  158. package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
  159. package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
  160. package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
  161. package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
  162. package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
  163. package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
  164. package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
  165. package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
  166. package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
  167. package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
  168. package/3p/pi-plannotator/packages/shared/project.ts +71 -0
  169. package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
  170. package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
  171. package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
  172. package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
  173. package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
  174. package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
  175. package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
  176. package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
  177. package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
  178. package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
  179. package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
  180. package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
  181. package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
  182. package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
  183. package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
  184. package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
  185. package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
  186. package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
  187. package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
  188. package/3p/pi-plannotator/packages/shared/types.ts +29 -0
  189. package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
  190. package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
  191. package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
  192. package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
  193. package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
  194. package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
  195. package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
  196. package/3p/pi-subagents/package.json +50 -0
  197. package/3p/pi-subagents/src/agent-manager.ts +413 -0
  198. package/3p/pi-subagents/src/agent-runner.ts +502 -0
  199. package/3p/pi-subagents/src/agent-types.ts +248 -0
  200. package/3p/pi-subagents/src/context.ts +58 -0
  201. package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
  202. package/3p/pi-subagents/src/custom-agents.ts +137 -0
  203. package/3p/pi-subagents/src/default-agents.ts +144 -0
  204. package/3p/pi-subagents/src/env.ts +33 -0
  205. package/3p/pi-subagents/src/group-join.ts +141 -0
  206. package/3p/pi-subagents/src/index.ts +1811 -0
  207. package/3p/pi-subagents/src/invocation-config.ts +40 -0
  208. package/3p/pi-subagents/src/memory.ts +165 -0
  209. package/3p/pi-subagents/src/model-resolver.ts +81 -0
  210. package/3p/pi-subagents/src/output-file.ts +77 -0
  211. package/3p/pi-subagents/src/prompts.ts +85 -0
  212. package/3p/pi-subagents/src/skill-loader.ts +79 -0
  213. package/3p/pi-subagents/src/types.ts +111 -0
  214. package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
  215. package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
  216. package/3p/pi-subagents/src/worktree.ts +162 -0
  217. package/3p/pi-tasks/package.json +52 -0
  218. package/3p/pi-tasks/src/auto-clear.ts +91 -0
  219. package/3p/pi-tasks/src/index.ts +1150 -0
  220. package/3p/pi-tasks/src/process-tracker.ts +140 -0
  221. package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
  222. package/3p/pi-tasks/src/task-store.ts +324 -0
  223. package/3p/pi-tasks/src/tasks-config.ts +27 -0
  224. package/3p/pi-tasks/src/types.ts +40 -0
  225. package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
  226. package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
  227. package/AGENTS.md +28 -0
  228. package/LICENSE +201 -0
  229. package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
  230. package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
  231. package/extensions/orchestrator/agents/explore.ts +33 -0
  232. package/extensions/orchestrator/agents/librarian.ts +43 -0
  233. package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
  234. package/extensions/orchestrator/agents/planner.ts +61 -0
  235. package/extensions/orchestrator/agents/registry.ts +156 -0
  236. package/extensions/orchestrator/agents/task.ts +50 -0
  237. package/extensions/orchestrator/agents/tool-routing.ts +84 -0
  238. package/extensions/orchestrator/ast-search.ts +85 -0
  239. package/extensions/orchestrator/cbm.ts +330 -0
  240. package/extensions/orchestrator/command-handlers.test.ts +163 -0
  241. package/extensions/orchestrator/command-handlers.ts +116 -0
  242. package/extensions/orchestrator/commands.test.ts +81 -0
  243. package/extensions/orchestrator/commands.ts +75 -0
  244. package/extensions/orchestrator/config.test.ts +221 -0
  245. package/extensions/orchestrator/config.ts +230 -0
  246. package/extensions/orchestrator/context.test.ts +293 -0
  247. package/extensions/orchestrator/context.ts +206 -0
  248. package/extensions/orchestrator/event-handlers.test.ts +190 -0
  249. package/extensions/orchestrator/event-handlers.ts +1416 -0
  250. package/extensions/orchestrator/exa.ts +104 -0
  251. package/extensions/orchestrator/flant-infra.ts +486 -0
  252. package/extensions/orchestrator/index.ts +80 -0
  253. package/extensions/orchestrator/integration.test.ts +1214 -0
  254. package/extensions/orchestrator/orchestrator.test.ts +252 -0
  255. package/extensions/orchestrator/orchestrator.ts +538 -0
  256. package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
  257. package/extensions/orchestrator/phases/brainstorm.ts +273 -0
  258. package/extensions/orchestrator/phases/implementation.ts +40 -0
  259. package/extensions/orchestrator/phases/machine.test.ts +293 -0
  260. package/extensions/orchestrator/phases/machine.ts +209 -0
  261. package/extensions/orchestrator/phases/planning.ts +255 -0
  262. package/extensions/orchestrator/phases/review.ts +193 -0
  263. package/extensions/orchestrator/plannotator.ts +56 -0
  264. package/extensions/orchestrator/pp-menu.ts +866 -0
  265. package/extensions/orchestrator/state.test.ts +343 -0
  266. package/extensions/orchestrator/state.ts +237 -0
  267. package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
  268. package/extensions/orchestrator/validate-artifacts.ts +272 -0
  269. package/extensions/orchestrator/vendor.d.ts +26 -0
  270. package/package.json +73 -0
  271. package/scripts/postinstall.sh +18 -0
@@ -0,0 +1,231 @@
1
+ /**
2
+ * Pi LSP Extension
3
+ *
4
+ * Language-agnostic code intelligence via LSP.
5
+ * Auto-detects servers by file extension, configurable via:
6
+ * - ~/.pi/agent/extensions/lsp/config.json (global defaults)
7
+ * - .pi/lsp.json (project overrides)
8
+ *
9
+ * Any LSP server can be added via config.
10
+ */
11
+
12
+ import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';
13
+
14
+ import { LspClient } from './client';
15
+ import { loadConfig, scaffoldGlobalConfig, serversForExtension, type LoadedConfig } from './config';
16
+ import { registerLspTool, type ServerManagerService } from './tools';
17
+ import type { ResolvedServerConfig } from './types';
18
+
19
+ export default function lspExtension(pi: ExtensionAPI) {
20
+ const subagentSessionKey = Symbol.for('pi-pi:subagent-session');
21
+ const lspApiKey = Symbol.for('pi-lsp:api');
22
+ let rootPath = '';
23
+ let config: LoadedConfig | null = null;
24
+ const clients = new Map<string, LspClient>();
25
+
26
+ // ── Client management ───────────────────────────────────────────────
27
+
28
+ function getOrCreateClient(serverConfig: ResolvedServerConfig): LspClient {
29
+ const existing = clients.get(serverConfig.name);
30
+ if (existing) return existing;
31
+
32
+ const client = new LspClient(serverConfig, rootPath);
33
+ clients.set(serverConfig.name, client);
34
+ return client;
35
+ }
36
+
37
+ async function shutdownAll(): Promise<void> {
38
+ const shutdowns = [...clients.values()].map((c) => c.shutdown().catch(() => {}));
39
+ await Promise.all(shutdowns);
40
+ clients.clear();
41
+ }
42
+
43
+ function refreshStatus(
44
+ ui: { setStatus: (key: string, value: string) => void },
45
+ cfg: LoadedConfig | null,
46
+ ) {
47
+ if (!cfg) {
48
+ ui.setStatus('lsp', 'LSP: no servers detected');
49
+ return;
50
+ }
51
+
52
+ if (cfg.globalDisabled) {
53
+ ui.setStatus('lsp', 'LSP: disabled');
54
+ return;
55
+ }
56
+
57
+ if (cfg.servers.length === 0) {
58
+ ui.setStatus('lsp', 'LSP: no servers detected');
59
+ return;
60
+ }
61
+
62
+ const running = cfg.servers.filter((server) => clients.get(server.name)?.isInitialized);
63
+ if (running.length > 0) {
64
+ ui.setStatus('lsp', `LSP: ${running.map((s) => s.name).join(', ')} (running)`);
65
+ return;
66
+ }
67
+
68
+ ui.setStatus('lsp', `LSP: ${cfg.servers.map((s) => s.name).join(', ')}`);
69
+ }
70
+
71
+ // ── Server manager (passed to tool) ───────────────────────────────────
72
+
73
+ const serverManager: ServerManagerService = {
74
+ clientsForFile(filePath: string): LspClient[] {
75
+ if (!config) return [];
76
+ const matching = serversForExtension(config.servers, filePath);
77
+ return matching.map((s) => getOrCreateClient(s));
78
+ },
79
+
80
+ clientForFileWithCapability(filePath: string, capability: string): LspClient | null {
81
+ if (!config) return null;
82
+ const matching = serversForExtension(config.servers, filePath);
83
+ for (const serverConfig of matching) {
84
+ const client = getOrCreateClient(serverConfig);
85
+ // If not yet initialized, return it (capability check happens after init)
86
+ if (!client.isInitialized) return client;
87
+ if (client.hasCapability(capability)) return client;
88
+ }
89
+ return null;
90
+ },
91
+
92
+ anyClient(): LspClient | null {
93
+ // Return first initialized client, or first available
94
+ for (const client of clients.values()) {
95
+ if (client.isInitialized) return client;
96
+ }
97
+ // Try to create one from config
98
+ if (config && config.servers.length > 0) {
99
+ return getOrCreateClient(config.servers[0]);
100
+ }
101
+ return null;
102
+ },
103
+
104
+ getRootPath: () => rootPath,
105
+ };
106
+
107
+ // ── Register tool ─────────────────────────────────────────────────────
108
+
109
+ registerLspTool(pi, serverManager);
110
+
111
+ // ── Session lifecycle ─────────────────────────────────────────────────
112
+
113
+ pi.on('session_start', async (_event, ctx) => {
114
+ if ((globalThis as any)[subagentSessionKey]) {
115
+ return;
116
+ }
117
+ rootPath = ctx.cwd;
118
+
119
+ const scaffolded = await scaffoldGlobalConfig(rootPath);
120
+ if (scaffolded) {
121
+ ctx.ui.notify(
122
+ 'LSP: created starter config at ~/.pi/agent/extensions/lsp/config.json — edit it to add your servers.',
123
+ 'info',
124
+ );
125
+ }
126
+
127
+ config = await loadConfig(rootPath);
128
+ refreshStatus(ctx.ui, config);
129
+ });
130
+
131
+ pi.on('session_shutdown', async () => {
132
+ if ((globalThis as any)[subagentSessionKey]) {
133
+ return;
134
+ }
135
+ await shutdownAll();
136
+ config = null;
137
+ });
138
+
139
+ pi.on('tool_execution_end', async (event, ctx) => {
140
+ if ((globalThis as any)[subagentSessionKey]) {
141
+ return;
142
+ }
143
+ if (event.toolName !== 'lsp') return;
144
+ refreshStatus(ctx.ui, config);
145
+ });
146
+
147
+ // ── Commands ──────────────────────────────────────────────────────────
148
+
149
+ pi.registerCommand('lsp', {
150
+ description: 'Show LSP server status',
151
+ handler: async (_args, ctx) => {
152
+ rootPath = ctx.cwd;
153
+ const cfg = await loadConfig(ctx.cwd);
154
+ config = cfg;
155
+ refreshStatus(ctx.ui, cfg);
156
+ const lines: string[] = ['LSP Status:'];
157
+
158
+ if (cfg.globalDisabled) {
159
+ lines.push(' All servers disabled via config.');
160
+ } else if (cfg.servers.length === 0) {
161
+ lines.push(' No servers configured.');
162
+ lines.push(' Add servers to ~/.pi/agent/extensions/lsp/config.json or .pi/lsp.json');
163
+ } else {
164
+ for (const server of cfg.servers) {
165
+ const client = clients.get(server.name);
166
+ const status = client?.isInitialized ? 'running' : 'available (lazy start)';
167
+ const exts = server.extensions.join(', ');
168
+ lines.push(` ${server.name}: ${status} — handles ${exts}`);
169
+ }
170
+ }
171
+
172
+ if (cfg.errors.length > 0) {
173
+ lines.push('', 'Config errors:');
174
+ for (const err of cfg.errors) lines.push(` - ${err}`);
175
+ }
176
+
177
+ ctx.ui.notify(lines.join('\n'), 'info');
178
+ },
179
+ });
180
+
181
+ pi.registerCommand('lsp-restart', {
182
+ description: 'Restart all LSP servers',
183
+ handler: async (_args, ctx) => {
184
+ await shutdownAll();
185
+ config = null;
186
+ rootPath = ctx.cwd;
187
+ config = await loadConfig(ctx.cwd);
188
+ refreshStatus(ctx.ui, config);
189
+ ctx.ui.notify('LSP servers stopped. Will reinitialize on next tool use.', 'info');
190
+ },
191
+ });
192
+
193
+ (globalThis as any)[lspApiKey] = {
194
+ status: async (ctx: any) => {
195
+ rootPath = ctx.cwd;
196
+ const cfg = await loadConfig(ctx.cwd);
197
+ config = cfg;
198
+ refreshStatus(ctx.ui, cfg);
199
+ const lines: string[] = ['LSP Status:'];
200
+
201
+ if (cfg.globalDisabled) {
202
+ lines.push(' All servers disabled via config.');
203
+ } else if (cfg.servers.length === 0) {
204
+ lines.push(' No servers configured.');
205
+ lines.push(' Add servers to ~/.pi/agent/extensions/lsp/config.json or .pi/lsp.json');
206
+ } else {
207
+ for (const server of cfg.servers) {
208
+ const client = clients.get(server.name);
209
+ const status = client?.isInitialized ? 'running' : 'available (lazy start)';
210
+ const exts = server.extensions.join(', ');
211
+ lines.push(` ${server.name}: ${status} — handles ${exts}`);
212
+ }
213
+ }
214
+
215
+ if (cfg.errors.length > 0) {
216
+ lines.push('', 'Config errors:');
217
+ for (const err of cfg.errors) lines.push(` - ${err}`);
218
+ }
219
+
220
+ ctx.ui.notify(lines.join('\n'), 'info');
221
+ },
222
+ restart: async (ctx: any) => {
223
+ await shutdownAll();
224
+ config = null;
225
+ rootPath = ctx.cwd;
226
+ config = await loadConfig(ctx.cwd);
227
+ refreshStatus(ctx.ui, config);
228
+ ctx.ui.notify('LSP servers stopped. Will reinitialize on next tool use.', 'info');
229
+ },
230
+ };
231
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * JSON-RPC over stdio transport for LSP.
3
+ *
4
+ * Handles Content-Length framing, request/response matching, and notification dispatch.
5
+ */
6
+
7
+ import { spawn, type ChildProcess } from 'node:child_process';
8
+
9
+ import type { JsonRpcMessage } from './types';
10
+
11
+ type PendingRequest = {
12
+ resolve: (value: unknown) => void;
13
+ reject: (error: Error) => void;
14
+ timer: ReturnType<typeof setTimeout>;
15
+ };
16
+
17
+ type NotificationHandler = (method: string, params: unknown) => void;
18
+ type ServerRequestHandler = (id: number | string, method: string, params: unknown) => void;
19
+
20
+ export class LspConnection {
21
+ private process: ChildProcess | null = null;
22
+ private buffer = Buffer.alloc(0);
23
+ private nextId = 1;
24
+ private pending = new Map<number, PendingRequest>();
25
+ private onNotification: NotificationHandler = () => {};
26
+ private onServerRequest: ServerRequestHandler = () => {};
27
+ private onExit: ((code: number | null) => void) | null = null;
28
+ private onStderr: ((text: string) => void) | null = null;
29
+ private disposed = false;
30
+
31
+ constructor(
32
+ private command: string,
33
+ private args: string[],
34
+ private options?: { cwd?: string; env?: Record<string, string> },
35
+ ) {}
36
+
37
+ // ── Lifecycle ─────────────────────────────────────────────────────────
38
+
39
+ spawn(): void {
40
+ if (this.process) return;
41
+
42
+ this.process = spawn(this.command, this.args, {
43
+ stdio: ['pipe', 'pipe', 'pipe'],
44
+ cwd: this.options?.cwd,
45
+ env: { ...process.env, ...this.options?.env },
46
+ shell: process.platform === 'win32',
47
+ });
48
+
49
+ this.process.stdout!.on('data', (chunk: Buffer) => {
50
+ this.buffer = Buffer.concat([this.buffer, chunk]);
51
+ this.drainBuffer();
52
+ });
53
+
54
+ this.process.stderr!.on('data', (chunk: Buffer) => {
55
+ this.onStderr?.(chunk.toString('utf8'));
56
+ });
57
+
58
+ this.process.on('exit', (code) => {
59
+ this.rejectAllPending('Server process exited');
60
+ this.onExit?.(code);
61
+ });
62
+
63
+ this.process.on('error', (err) => {
64
+ this.rejectAllPending(`Server process error: ${err.message}`);
65
+ });
66
+ }
67
+
68
+ dispose(): void {
69
+ if (this.disposed) return;
70
+ this.disposed = true;
71
+ this.rejectAllPending('Connection disposed');
72
+
73
+ if (this.process) {
74
+ this.process.stdin?.end();
75
+ this.process.kill();
76
+ this.process = null;
77
+ }
78
+ }
79
+
80
+ get alive(): boolean {
81
+ return this.process !== null && !this.disposed && this.process.exitCode === null;
82
+ }
83
+
84
+ // ── Handlers ──────────────────────────────────────────────────────────
85
+
86
+ setNotificationHandler(handler: NotificationHandler): void {
87
+ this.onNotification = handler;
88
+ }
89
+
90
+ setServerRequestHandler(handler: ServerRequestHandler): void {
91
+ this.onServerRequest = handler;
92
+ }
93
+
94
+ setExitHandler(handler: (code: number | null) => void): void {
95
+ this.onExit = handler;
96
+ }
97
+
98
+ setStderrHandler(handler: (text: string) => void): void {
99
+ this.onStderr = handler;
100
+ }
101
+
102
+ // ── Sending ───────────────────────────────────────────────────────────
103
+
104
+ sendRequest(method: string, params: unknown, timeoutMs = 30_000): Promise<unknown> {
105
+ if (!this.alive) return Promise.reject(new Error('Connection not alive'));
106
+
107
+ const id = this.nextId++;
108
+ return new Promise<unknown>((resolve, reject) => {
109
+ const timer = setTimeout(() => {
110
+ this.pending.delete(id);
111
+ reject(new Error(`LSP request '${method}' timed out after ${timeoutMs}ms`));
112
+ }, timeoutMs);
113
+
114
+ this.pending.set(id, { resolve, reject, timer });
115
+ this.writeMessage({ jsonrpc: '2.0', id, method, params });
116
+ });
117
+ }
118
+
119
+ sendNotification(method: string, params: unknown): void {
120
+ if (!this.alive) return;
121
+ this.writeMessage({ jsonrpc: '2.0', method, params });
122
+ }
123
+
124
+ sendResponse(id: number | string, result: unknown): void {
125
+ if (!this.alive) return;
126
+ this.writeMessage({ jsonrpc: '2.0', id, result } as unknown as JsonRpcMessage);
127
+ }
128
+
129
+ // ── Framing ───────────────────────────────────────────────────────────
130
+
131
+ private writeMessage(message: JsonRpcMessage): void {
132
+ const body = JSON.stringify(message);
133
+ const length = Buffer.byteLength(body, 'utf8');
134
+ const header = `Content-Length: ${length}\r\n\r\n`;
135
+ this.process?.stdin?.write(header + body);
136
+ }
137
+
138
+ private drainBuffer(): void {
139
+ const HEADER_DELIMITER = '\r\n\r\n';
140
+
141
+ while (true) {
142
+ const headerEnd = this.buffer.indexOf(HEADER_DELIMITER);
143
+ if (headerEnd === -1) break;
144
+
145
+ const headerText = this.buffer.subarray(0, headerEnd).toString('ascii');
146
+ const match = headerText.match(/Content-Length:\s*(\d+)/i);
147
+ if (!match) {
148
+ // Malformed header — skip past it
149
+ this.buffer = this.buffer.subarray(headerEnd + HEADER_DELIMITER.length);
150
+ continue;
151
+ }
152
+
153
+ const contentLength = parseInt(match[1], 10);
154
+ const bodyStart = headerEnd + HEADER_DELIMITER.length;
155
+ const bodyEnd = bodyStart + contentLength;
156
+
157
+ if (this.buffer.length < bodyEnd) break; // Incomplete body
158
+
159
+ const body = this.buffer.subarray(bodyStart, bodyEnd).toString('utf8');
160
+ this.buffer = this.buffer.subarray(bodyEnd);
161
+
162
+ try {
163
+ this.handleMessage(JSON.parse(body) as JsonRpcMessage);
164
+ } catch {
165
+ // Malformed JSON — skip
166
+ }
167
+ }
168
+ }
169
+
170
+ private handleMessage(message: JsonRpcMessage): void {
171
+ const msg = message as unknown as Record<string, unknown>;
172
+
173
+ // Response to one of our requests
174
+ if (msg.id !== undefined && (msg.result !== undefined || msg.error !== undefined)) {
175
+ const pending = this.pending.get(msg.id as number);
176
+ if (pending) {
177
+ clearTimeout(pending.timer);
178
+ this.pending.delete(msg.id as number);
179
+
180
+ if (msg.error) {
181
+ const err = msg.error as { code: number; message: string };
182
+ pending.reject(new Error(`LSP error ${err.code}: ${err.message}`));
183
+ } else {
184
+ pending.resolve(msg.result);
185
+ }
186
+ }
187
+ return;
188
+ }
189
+
190
+ // Notification or server-initiated request
191
+ if (typeof msg.method === 'string') {
192
+ if (msg.id !== undefined) {
193
+ // Server request — needs a response
194
+ this.onServerRequest(msg.id as number, msg.method, msg.params);
195
+ } else {
196
+ // Notification
197
+ this.onNotification(msg.method, msg.params);
198
+ }
199
+ }
200
+ }
201
+
202
+ private rejectAllPending(reason: string): void {
203
+ for (const [id, pending] of this.pending) {
204
+ clearTimeout(pending.timer);
205
+ pending.reject(new Error(reason));
206
+ this.pending.delete(id);
207
+ }
208
+ }
209
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Retry utility for LSP operations that may return empty results during server indexing.
3
+ *
4
+ * Only retries when the server was recently initialized (within a configurable window),
5
+ * avoiding unnecessary delays for servers that are already warmed up.
6
+ */
7
+
8
+ export interface RetryOptions {
9
+ /** Maximum number of retry attempts. Default: 2 */
10
+ maxRetries?: number;
11
+ /** Delay between retries in milliseconds. Default: 2000 */
12
+ delayMs?: number;
13
+ }
14
+
15
+ /**
16
+ * Retry an async operation if the result is considered "empty".
17
+ *
18
+ * Useful for LSP operations that return empty results while the server is still indexing
19
+ * (e.g. workspaceSymbol, hover, definition, references).
20
+ */
21
+ export async function withRetry<T>(
22
+ operation: () => Promise<T>,
23
+ isEmpty: (result: T) => boolean,
24
+ options?: RetryOptions,
25
+ ): Promise<T> {
26
+ const maxRetries = options?.maxRetries ?? 2;
27
+ const delayMs = options?.delayMs ?? 2000;
28
+
29
+ let result = await operation();
30
+ let attempt = 0;
31
+
32
+ while (isEmpty(result) && attempt < maxRetries) {
33
+ attempt++;
34
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
35
+ result = await operation();
36
+ }
37
+
38
+ return result;
39
+ }