patchwork-os 0.2.0-alpha.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 (721) hide show
  1. package/LICENSE +21 -0
  2. package/README.bridge.md +352 -0
  3. package/README.md +72 -0
  4. package/deploy/README.md +172 -0
  5. package/deploy/bootstrap-new-vps.sh +364 -0
  6. package/deploy/claude-ide-bridge.service.template +67 -0
  7. package/deploy/claude-ide-bridge@.service +31 -0
  8. package/deploy/ecosystem.config.js.example +36 -0
  9. package/deploy/install-vps-service.sh +240 -0
  10. package/deploy/nginx-claude-bridge.conf.template +129 -0
  11. package/dist/activityLog.d.ts +112 -0
  12. package/dist/activityLog.js +399 -0
  13. package/dist/activityLog.js.map +1 -0
  14. package/dist/activityTypes.d.ts +28 -0
  15. package/dist/activityTypes.js +9 -0
  16. package/dist/activityTypes.js.map +1 -0
  17. package/dist/adapters/base.d.ts +78 -0
  18. package/dist/adapters/base.js +14 -0
  19. package/dist/adapters/base.js.map +1 -0
  20. package/dist/adapters/claude.d.ts +18 -0
  21. package/dist/adapters/claude.js +276 -0
  22. package/dist/adapters/claude.js.map +1 -0
  23. package/dist/adapters/gemini.d.ts +17 -0
  24. package/dist/adapters/gemini.js +218 -0
  25. package/dist/adapters/gemini.js.map +1 -0
  26. package/dist/adapters/grok.d.ts +7 -0
  27. package/dist/adapters/grok.js +21 -0
  28. package/dist/adapters/grok.js.map +1 -0
  29. package/dist/adapters/index.d.ts +5 -0
  30. package/dist/adapters/index.js +37 -0
  31. package/dist/adapters/index.js.map +1 -0
  32. package/dist/adapters/local.d.ts +7 -0
  33. package/dist/adapters/local.js +22 -0
  34. package/dist/adapters/local.js.map +1 -0
  35. package/dist/adapters/openai.d.ts +22 -0
  36. package/dist/adapters/openai.js +284 -0
  37. package/dist/adapters/openai.js.map +1 -0
  38. package/dist/adapters/sse.d.ts +13 -0
  39. package/dist/adapters/sse.js +58 -0
  40. package/dist/adapters/sse.js.map +1 -0
  41. package/dist/analyticsAggregator.d.ts +28 -0
  42. package/dist/analyticsAggregator.js +133 -0
  43. package/dist/analyticsAggregator.js.map +1 -0
  44. package/dist/analyticsPrefs.d.ts +9 -0
  45. package/dist/analyticsPrefs.js +50 -0
  46. package/dist/analyticsPrefs.js.map +1 -0
  47. package/dist/analyticsSend.d.ts +12 -0
  48. package/dist/analyticsSend.js +34 -0
  49. package/dist/analyticsSend.js.map +1 -0
  50. package/dist/approvalHttp.d.ts +46 -0
  51. package/dist/approvalHttp.js +370 -0
  52. package/dist/approvalHttp.js.map +1 -0
  53. package/dist/approvalQueue.d.ts +49 -0
  54. package/dist/approvalQueue.js +84 -0
  55. package/dist/approvalQueue.js.map +1 -0
  56. package/dist/automation.d.ts +675 -0
  57. package/dist/automation.js +1038 -0
  58. package/dist/automation.js.map +1 -0
  59. package/dist/bridge.d.ts +85 -0
  60. package/dist/bridge.js +1535 -0
  61. package/dist/bridge.js.map +1 -0
  62. package/dist/bridgeLockDiscovery.d.ts +11 -0
  63. package/dist/bridgeLockDiscovery.js +49 -0
  64. package/dist/bridgeLockDiscovery.js.map +1 -0
  65. package/dist/bridgeToken.d.ts +22 -0
  66. package/dist/bridgeToken.js +114 -0
  67. package/dist/bridgeToken.js.map +1 -0
  68. package/dist/bridgeToolsRules.d.ts +20 -0
  69. package/dist/bridgeToolsRules.js +79 -0
  70. package/dist/bridgeToolsRules.js.map +1 -0
  71. package/dist/ccPermissions.d.ts +59 -0
  72. package/dist/ccPermissions.js +163 -0
  73. package/dist/ccPermissions.js.map +1 -0
  74. package/dist/claudeDriver.d.ts +129 -0
  75. package/dist/claudeDriver.js +459 -0
  76. package/dist/claudeDriver.js.map +1 -0
  77. package/dist/claudeMdPatch.d.ts +29 -0
  78. package/dist/claudeMdPatch.js +164 -0
  79. package/dist/claudeMdPatch.js.map +1 -0
  80. package/dist/claudeOrchestrator.d.ts +171 -0
  81. package/dist/claudeOrchestrator.js +591 -0
  82. package/dist/claudeOrchestrator.js.map +1 -0
  83. package/dist/commands/install.d.ts +1 -0
  84. package/dist/commands/install.js +158 -0
  85. package/dist/commands/install.js.map +1 -0
  86. package/dist/commands/marketplace.d.ts +11 -0
  87. package/dist/commands/marketplace.js +120 -0
  88. package/dist/commands/marketplace.js.map +1 -0
  89. package/dist/commands/patchworkInit.d.ts +14 -0
  90. package/dist/commands/patchworkInit.js +155 -0
  91. package/dist/commands/patchworkInit.js.map +1 -0
  92. package/dist/commands/task.d.ts +14 -0
  93. package/dist/commands/task.js +289 -0
  94. package/dist/commands/task.js.map +1 -0
  95. package/dist/commands/tokenEfficiency.d.ts +9 -0
  96. package/dist/commands/tokenEfficiency.js +211 -0
  97. package/dist/commands/tokenEfficiency.js.map +1 -0
  98. package/dist/commands/tools.d.ts +28 -0
  99. package/dist/commands/tools.js +326 -0
  100. package/dist/commands/tools.js.map +1 -0
  101. package/dist/commitIssueLinkLog.d.ts +77 -0
  102. package/dist/commitIssueLinkLog.js +142 -0
  103. package/dist/commitIssueLinkLog.js.map +1 -0
  104. package/dist/companions/registry.d.ts +12 -0
  105. package/dist/companions/registry.js +71 -0
  106. package/dist/companions/registry.js.map +1 -0
  107. package/dist/config.d.ts +105 -0
  108. package/dist/config.js +720 -0
  109. package/dist/config.js.map +1 -0
  110. package/dist/crypto.d.ts +16 -0
  111. package/dist/crypto.js +34 -0
  112. package/dist/crypto.js.map +1 -0
  113. package/dist/dashboard.d.ts +12 -0
  114. package/dist/dashboard.js +149 -0
  115. package/dist/dashboard.js.map +1 -0
  116. package/dist/decisionTraceLog.d.ts +77 -0
  117. package/dist/decisionTraceLog.js +147 -0
  118. package/dist/decisionTraceLog.js.map +1 -0
  119. package/dist/errors.d.ts +32 -0
  120. package/dist/errors.js +34 -0
  121. package/dist/errors.js.map +1 -0
  122. package/dist/extensionClient.d.ts +279 -0
  123. package/dist/extensionClient.js +1253 -0
  124. package/dist/extensionClient.js.map +1 -0
  125. package/dist/fileLock.d.ts +36 -0
  126. package/dist/fileLock.js +121 -0
  127. package/dist/fileLock.js.map +1 -0
  128. package/dist/fp/activityAnalytics.d.ts +39 -0
  129. package/dist/fp/activityAnalytics.js +111 -0
  130. package/dist/fp/activityAnalytics.js.map +1 -0
  131. package/dist/fp/async.d.ts +48 -0
  132. package/dist/fp/async.js +60 -0
  133. package/dist/fp/async.js.map +1 -0
  134. package/dist/fp/automationInterpreter.d.ts +37 -0
  135. package/dist/fp/automationInterpreter.js +523 -0
  136. package/dist/fp/automationInterpreter.js.map +1 -0
  137. package/dist/fp/automationProgram.d.ts +89 -0
  138. package/dist/fp/automationProgram.js +29 -0
  139. package/dist/fp/automationProgram.js.map +1 -0
  140. package/dist/fp/automationState.d.ts +135 -0
  141. package/dist/fp/automationState.js +206 -0
  142. package/dist/fp/automationState.js.map +1 -0
  143. package/dist/fp/automationUtils.d.ts +31 -0
  144. package/dist/fp/automationUtils.js +61 -0
  145. package/dist/fp/automationUtils.js.map +1 -0
  146. package/dist/fp/brandedTypes.d.ts +32 -0
  147. package/dist/fp/brandedTypes.js +41 -0
  148. package/dist/fp/brandedTypes.js.map +1 -0
  149. package/dist/fp/commandDescription.d.ts +18 -0
  150. package/dist/fp/commandDescription.js +125 -0
  151. package/dist/fp/commandDescription.js.map +1 -0
  152. package/dist/fp/extensionSnapshot.d.ts +10 -0
  153. package/dist/fp/extensionSnapshot.js +14 -0
  154. package/dist/fp/extensionSnapshot.js.map +1 -0
  155. package/dist/fp/index.d.ts +8 -0
  156. package/dist/fp/index.js +9 -0
  157. package/dist/fp/index.js.map +1 -0
  158. package/dist/fp/interpreterContext.d.ts +69 -0
  159. package/dist/fp/interpreterContext.js +56 -0
  160. package/dist/fp/interpreterContext.js.map +1 -0
  161. package/dist/fp/policyParser.d.ts +16 -0
  162. package/dist/fp/policyParser.js +334 -0
  163. package/dist/fp/policyParser.js.map +1 -0
  164. package/dist/fp/result.d.ts +38 -0
  165. package/dist/fp/result.js +57 -0
  166. package/dist/fp/result.js.map +1 -0
  167. package/dist/fp/tokenBucket.d.ts +27 -0
  168. package/dist/fp/tokenBucket.js +36 -0
  169. package/dist/fp/tokenBucket.js.map +1 -0
  170. package/dist/index.d.ts +2 -0
  171. package/dist/index.js +1465 -0
  172. package/dist/index.js.map +1 -0
  173. package/dist/instructionsUtils.d.ts +17 -0
  174. package/dist/instructionsUtils.js +38 -0
  175. package/dist/instructionsUtils.js.map +1 -0
  176. package/dist/lockfile.d.ts +16 -0
  177. package/dist/lockfile.js +172 -0
  178. package/dist/lockfile.js.map +1 -0
  179. package/dist/logger.d.ts +16 -0
  180. package/dist/logger.js +68 -0
  181. package/dist/logger.js.map +1 -0
  182. package/dist/oauth.d.ts +105 -0
  183. package/dist/oauth.js +880 -0
  184. package/dist/oauth.js.map +1 -0
  185. package/dist/orchestrator/childBridgeClient.d.ts +33 -0
  186. package/dist/orchestrator/childBridgeClient.js +321 -0
  187. package/dist/orchestrator/childBridgeClient.js.map +1 -0
  188. package/dist/orchestrator/childBridgeRegistry.d.ts +67 -0
  189. package/dist/orchestrator/childBridgeRegistry.js +297 -0
  190. package/dist/orchestrator/childBridgeRegistry.js.map +1 -0
  191. package/dist/orchestrator/index.d.ts +3 -0
  192. package/dist/orchestrator/index.js +3 -0
  193. package/dist/orchestrator/index.js.map +1 -0
  194. package/dist/orchestrator/orchestratorBridge.d.ts +32 -0
  195. package/dist/orchestrator/orchestratorBridge.js +412 -0
  196. package/dist/orchestrator/orchestratorBridge.js.map +1 -0
  197. package/dist/orchestrator/orchestratorConfig.d.ts +11 -0
  198. package/dist/orchestrator/orchestratorConfig.js +85 -0
  199. package/dist/orchestrator/orchestratorConfig.js.map +1 -0
  200. package/dist/orchestrator/orchestratorTools.d.ts +16 -0
  201. package/dist/orchestrator/orchestratorTools.js +272 -0
  202. package/dist/orchestrator/orchestratorTools.js.map +1 -0
  203. package/dist/patchworkCli.d.ts +15 -0
  204. package/dist/patchworkCli.js +41 -0
  205. package/dist/patchworkCli.js.map +1 -0
  206. package/dist/patchworkConfig.d.ts +28 -0
  207. package/dist/patchworkConfig.js +30 -0
  208. package/dist/patchworkConfig.js.map +1 -0
  209. package/dist/plugin.d.ts +106 -0
  210. package/dist/plugin.js +31 -0
  211. package/dist/plugin.js.map +1 -0
  212. package/dist/pluginLoader.d.ts +44 -0
  213. package/dist/pluginLoader.js +357 -0
  214. package/dist/pluginLoader.js.map +1 -0
  215. package/dist/pluginWatcher.d.ts +24 -0
  216. package/dist/pluginWatcher.js +139 -0
  217. package/dist/pluginWatcher.js.map +1 -0
  218. package/dist/preToolUseHook.d.ts +10 -0
  219. package/dist/preToolUseHook.js +57 -0
  220. package/dist/preToolUseHook.js.map +1 -0
  221. package/dist/probe.d.ts +35 -0
  222. package/dist/probe.js +143 -0
  223. package/dist/probe.js.map +1 -0
  224. package/dist/prompts.d.ts +27 -0
  225. package/dist/prompts.js +1680 -0
  226. package/dist/prompts.js.map +1 -0
  227. package/dist/quickTaskPresets.d.ts +64 -0
  228. package/dist/quickTaskPresets.js +156 -0
  229. package/dist/quickTaskPresets.js.map +1 -0
  230. package/dist/recipes/compiler.d.ts +44 -0
  231. package/dist/recipes/compiler.js +140 -0
  232. package/dist/recipes/compiler.js.map +1 -0
  233. package/dist/recipes/installer.d.ts +25 -0
  234. package/dist/recipes/installer.js +62 -0
  235. package/dist/recipes/installer.js.map +1 -0
  236. package/dist/recipes/parser.d.ts +18 -0
  237. package/dist/recipes/parser.js +160 -0
  238. package/dist/recipes/parser.js.map +1 -0
  239. package/dist/recipes/scheduler.d.ts +45 -0
  240. package/dist/recipes/scheduler.js +110 -0
  241. package/dist/recipes/scheduler.js.map +1 -0
  242. package/dist/recipes/schema.d.ts +71 -0
  243. package/dist/recipes/schema.js +11 -0
  244. package/dist/recipes/schema.js.map +1 -0
  245. package/dist/recipesHttp.d.ts +63 -0
  246. package/dist/recipesHttp.js +183 -0
  247. package/dist/recipesHttp.js.map +1 -0
  248. package/dist/resources.d.ts +33 -0
  249. package/dist/resources.js +266 -0
  250. package/dist/resources.js.map +1 -0
  251. package/dist/riskTier.d.ts +40 -0
  252. package/dist/riskTier.js +142 -0
  253. package/dist/riskTier.js.map +1 -0
  254. package/dist/runLog.d.ts +90 -0
  255. package/dist/runLog.js +143 -0
  256. package/dist/runLog.js.map +1 -0
  257. package/dist/server.d.ts +160 -0
  258. package/dist/server.js +1244 -0
  259. package/dist/server.js.map +1 -0
  260. package/dist/sessionCheckpoint.d.ts +37 -0
  261. package/dist/sessionCheckpoint.js +123 -0
  262. package/dist/sessionCheckpoint.js.map +1 -0
  263. package/dist/streamableHttp.d.ts +86 -0
  264. package/dist/streamableHttp.js +702 -0
  265. package/dist/streamableHttp.js.map +1 -0
  266. package/dist/telemetry.d.ts +18 -0
  267. package/dist/telemetry.js +95 -0
  268. package/dist/telemetry.js.map +1 -0
  269. package/dist/tools/activityLog.d.ts +140 -0
  270. package/dist/tools/activityLog.js +204 -0
  271. package/dist/tools/activityLog.js.map +1 -0
  272. package/dist/tools/auditDependencies.d.ts +67 -0
  273. package/dist/tools/auditDependencies.js +298 -0
  274. package/dist/tools/auditDependencies.js.map +1 -0
  275. package/dist/tools/batchLsp.d.ts +262 -0
  276. package/dist/tools/batchLsp.js +328 -0
  277. package/dist/tools/batchLsp.js.map +1 -0
  278. package/dist/tools/blame-utils.d.ts +30 -0
  279. package/dist/tools/blame-utils.js +60 -0
  280. package/dist/tools/blame-utils.js.map +1 -0
  281. package/dist/tools/bridgeDoctor.d.ts +78 -0
  282. package/dist/tools/bridgeDoctor.js +542 -0
  283. package/dist/tools/bridgeDoctor.js.map +1 -0
  284. package/dist/tools/bridgeStatus.d.ts +122 -0
  285. package/dist/tools/bridgeStatus.js +250 -0
  286. package/dist/tools/bridgeStatus.js.map +1 -0
  287. package/dist/tools/cancelClaudeTask.d.ts +48 -0
  288. package/dist/tools/cancelClaudeTask.js +56 -0
  289. package/dist/tools/cancelClaudeTask.js.map +1 -0
  290. package/dist/tools/checkDocumentDirty.d.ts +56 -0
  291. package/dist/tools/checkDocumentDirty.js +74 -0
  292. package/dist/tools/checkDocumentDirty.js.map +1 -0
  293. package/dist/tools/clipboard.d.ts +80 -0
  294. package/dist/tools/clipboard.js +211 -0
  295. package/dist/tools/clipboard.js.map +1 -0
  296. package/dist/tools/closeTabs.d.ts +84 -0
  297. package/dist/tools/closeTabs.js +97 -0
  298. package/dist/tools/closeTabs.js.map +1 -0
  299. package/dist/tools/codeLens.d.ts +50 -0
  300. package/dist/tools/codeLens.js +47 -0
  301. package/dist/tools/codeLens.js.map +1 -0
  302. package/dist/tools/contextBundle.d.ts +75 -0
  303. package/dist/tools/contextBundle.js +218 -0
  304. package/dist/tools/contextBundle.js.map +1 -0
  305. package/dist/tools/createIssueFromAIComment.d.ts +75 -0
  306. package/dist/tools/createIssueFromAIComment.js +119 -0
  307. package/dist/tools/createIssueFromAIComment.js.map +1 -0
  308. package/dist/tools/ctxGetTaskContext.d.ts +103 -0
  309. package/dist/tools/ctxGetTaskContext.js +274 -0
  310. package/dist/tools/ctxGetTaskContext.js.map +1 -0
  311. package/dist/tools/ctxQueryTraces.d.ts +142 -0
  312. package/dist/tools/ctxQueryTraces.js +194 -0
  313. package/dist/tools/ctxQueryTraces.js.map +1 -0
  314. package/dist/tools/ctxSaveTrace.d.ts +87 -0
  315. package/dist/tools/ctxSaveTrace.js +94 -0
  316. package/dist/tools/ctxSaveTrace.js.map +1 -0
  317. package/dist/tools/debug.d.ts +206 -0
  318. package/dist/tools/debug.js +234 -0
  319. package/dist/tools/debug.js.map +1 -0
  320. package/dist/tools/decorations.d.ts +130 -0
  321. package/dist/tools/decorations.js +160 -0
  322. package/dist/tools/decorations.js.map +1 -0
  323. package/dist/tools/detectUnusedCode.d.ts +78 -0
  324. package/dist/tools/detectUnusedCode.js +173 -0
  325. package/dist/tools/detectUnusedCode.js.map +1 -0
  326. package/dist/tools/documentLinks.d.ts +62 -0
  327. package/dist/tools/documentLinks.js +55 -0
  328. package/dist/tools/documentLinks.js.map +1 -0
  329. package/dist/tools/editText.d.ts +108 -0
  330. package/dist/tools/editText.js +318 -0
  331. package/dist/tools/editText.js.map +1 -0
  332. package/dist/tools/enrichCommit.d.ts +89 -0
  333. package/dist/tools/enrichCommit.js +201 -0
  334. package/dist/tools/enrichCommit.js.map +1 -0
  335. package/dist/tools/enrichStackTrace.d.ts +121 -0
  336. package/dist/tools/enrichStackTrace.js +194 -0
  337. package/dist/tools/enrichStackTrace.js.map +1 -0
  338. package/dist/tools/explainDiagnostic.d.ts +137 -0
  339. package/dist/tools/explainDiagnostic.js +230 -0
  340. package/dist/tools/explainDiagnostic.js.map +1 -0
  341. package/dist/tools/explainSymbol.d.ts +119 -0
  342. package/dist/tools/explainSymbol.js +177 -0
  343. package/dist/tools/explainSymbol.js.map +1 -0
  344. package/dist/tools/fileOperations.d.ts +186 -0
  345. package/dist/tools/fileOperations.js +330 -0
  346. package/dist/tools/fileOperations.js.map +1 -0
  347. package/dist/tools/fileWatcher.d.ts +107 -0
  348. package/dist/tools/fileWatcher.js +121 -0
  349. package/dist/tools/fileWatcher.js.map +1 -0
  350. package/dist/tools/findFiles.d.ts +65 -0
  351. package/dist/tools/findFiles.js +142 -0
  352. package/dist/tools/findFiles.js.map +1 -0
  353. package/dist/tools/findRelatedTests.d.ts +83 -0
  354. package/dist/tools/findRelatedTests.js +196 -0
  355. package/dist/tools/findRelatedTests.js.map +1 -0
  356. package/dist/tools/fixAllLintErrors.d.ts +66 -0
  357. package/dist/tools/fixAllLintErrors.js +128 -0
  358. package/dist/tools/fixAllLintErrors.js.map +1 -0
  359. package/dist/tools/foldingRanges.d.ts +50 -0
  360. package/dist/tools/foldingRanges.js +51 -0
  361. package/dist/tools/foldingRanges.js.map +1 -0
  362. package/dist/tools/formatAndSave.d.ts +57 -0
  363. package/dist/tools/formatAndSave.js +87 -0
  364. package/dist/tools/formatAndSave.js.map +1 -0
  365. package/dist/tools/formatDocument.d.ts +61 -0
  366. package/dist/tools/formatDocument.js +144 -0
  367. package/dist/tools/formatDocument.js.map +1 -0
  368. package/dist/tools/generateAPIDocumentation.d.ts +62 -0
  369. package/dist/tools/generateAPIDocumentation.js +249 -0
  370. package/dist/tools/generateAPIDocumentation.js.map +1 -0
  371. package/dist/tools/generateTests.d.ts +75 -0
  372. package/dist/tools/generateTests.js +226 -0
  373. package/dist/tools/generateTests.js.map +1 -0
  374. package/dist/tools/getAIComments.d.ts +79 -0
  375. package/dist/tools/getAIComments.js +93 -0
  376. package/dist/tools/getAIComments.js.map +1 -0
  377. package/dist/tools/getAnalyticsReport.d.ts +102 -0
  378. package/dist/tools/getAnalyticsReport.js +137 -0
  379. package/dist/tools/getAnalyticsReport.js.map +1 -0
  380. package/dist/tools/getArchitectureContext.d.ts +85 -0
  381. package/dist/tools/getArchitectureContext.js +135 -0
  382. package/dist/tools/getArchitectureContext.js.map +1 -0
  383. package/dist/tools/getBufferContent.d.ts +80 -0
  384. package/dist/tools/getBufferContent.js +207 -0
  385. package/dist/tools/getBufferContent.js.map +1 -0
  386. package/dist/tools/getChangeImpact.d.ts +76 -0
  387. package/dist/tools/getChangeImpact.js +184 -0
  388. package/dist/tools/getChangeImpact.js.map +1 -0
  389. package/dist/tools/getClaudeTaskStatus.d.ts +87 -0
  390. package/dist/tools/getClaudeTaskStatus.js +89 -0
  391. package/dist/tools/getClaudeTaskStatus.js.map +1 -0
  392. package/dist/tools/getCodeCoverage.d.ts +86 -0
  393. package/dist/tools/getCodeCoverage.js +237 -0
  394. package/dist/tools/getCodeCoverage.js.map +1 -0
  395. package/dist/tools/getCommitsForIssue.d.ts +98 -0
  396. package/dist/tools/getCommitsForIssue.js +106 -0
  397. package/dist/tools/getCommitsForIssue.js.map +1 -0
  398. package/dist/tools/getCurrentSelection.d.ts +123 -0
  399. package/dist/tools/getCurrentSelection.js +113 -0
  400. package/dist/tools/getCurrentSelection.js.map +1 -0
  401. package/dist/tools/getDebugState.d.ts +140 -0
  402. package/dist/tools/getDebugState.js +109 -0
  403. package/dist/tools/getDebugState.js.map +1 -0
  404. package/dist/tools/getDependencyTree.d.ts +59 -0
  405. package/dist/tools/getDependencyTree.js +207 -0
  406. package/dist/tools/getDependencyTree.js.map +1 -0
  407. package/dist/tools/getDiagnostics.d.ts +108 -0
  408. package/dist/tools/getDiagnostics.js +371 -0
  409. package/dist/tools/getDiagnostics.js.map +1 -0
  410. package/dist/tools/getDiffFromHandoff.d.ts +89 -0
  411. package/dist/tools/getDiffFromHandoff.js +163 -0
  412. package/dist/tools/getDiffFromHandoff.js.map +1 -0
  413. package/dist/tools/getDocumentSymbols.d.ts +74 -0
  414. package/dist/tools/getDocumentSymbols.js +177 -0
  415. package/dist/tools/getDocumentSymbols.js.map +1 -0
  416. package/dist/tools/getFileTree.d.ts +66 -0
  417. package/dist/tools/getFileTree.js +131 -0
  418. package/dist/tools/getFileTree.js.map +1 -0
  419. package/dist/tools/getGitDiff.d.ts +50 -0
  420. package/dist/tools/getGitDiff.js +73 -0
  421. package/dist/tools/getGitDiff.js.map +1 -0
  422. package/dist/tools/getGitHotspots.d.ts +88 -0
  423. package/dist/tools/getGitHotspots.js +145 -0
  424. package/dist/tools/getGitHotspots.js.map +1 -0
  425. package/dist/tools/getGitLog.d.ts +62 -0
  426. package/dist/tools/getGitLog.js +87 -0
  427. package/dist/tools/getGitLog.js.map +1 -0
  428. package/dist/tools/getGitStatus.d.ts +72 -0
  429. package/dist/tools/getGitStatus.js +126 -0
  430. package/dist/tools/getGitStatus.js.map +1 -0
  431. package/dist/tools/getImportTree.d.ts +73 -0
  432. package/dist/tools/getImportTree.js +223 -0
  433. package/dist/tools/getImportTree.js.map +1 -0
  434. package/dist/tools/getImportedSignatures.d.ts +62 -0
  435. package/dist/tools/getImportedSignatures.js +255 -0
  436. package/dist/tools/getImportedSignatures.js.map +1 -0
  437. package/dist/tools/getOpenEditors.d.ts +62 -0
  438. package/dist/tools/getOpenEditors.js +126 -0
  439. package/dist/tools/getOpenEditors.js.map +1 -0
  440. package/dist/tools/getPRTemplate.d.ts +68 -0
  441. package/dist/tools/getPRTemplate.js +187 -0
  442. package/dist/tools/getPRTemplate.js.map +1 -0
  443. package/dist/tools/getProjectContext.d.ts +114 -0
  444. package/dist/tools/getProjectContext.js +344 -0
  445. package/dist/tools/getProjectContext.js.map +1 -0
  446. package/dist/tools/getProjectInfo.d.ts +51 -0
  447. package/dist/tools/getProjectInfo.js +325 -0
  448. package/dist/tools/getProjectInfo.js.map +1 -0
  449. package/dist/tools/getSecurityAdvisories.d.ts +105 -0
  450. package/dist/tools/getSecurityAdvisories.js +472 -0
  451. package/dist/tools/getSecurityAdvisories.js.map +1 -0
  452. package/dist/tools/getSessionUsage.d.ts +58 -0
  453. package/dist/tools/getSessionUsage.js +57 -0
  454. package/dist/tools/getSessionUsage.js.map +1 -0
  455. package/dist/tools/getSymbolHistory.d.ts +157 -0
  456. package/dist/tools/getSymbolHistory.js +256 -0
  457. package/dist/tools/getSymbolHistory.js.map +1 -0
  458. package/dist/tools/getToolCapabilities.d.ts +69 -0
  459. package/dist/tools/getToolCapabilities.js +298 -0
  460. package/dist/tools/getToolCapabilities.js.map +1 -0
  461. package/dist/tools/getTypeSignature.d.ts +70 -0
  462. package/dist/tools/getTypeSignature.js +132 -0
  463. package/dist/tools/getTypeSignature.js.map +1 -0
  464. package/dist/tools/getWorkspaceFolders.d.ts +58 -0
  465. package/dist/tools/getWorkspaceFolders.js +69 -0
  466. package/dist/tools/getWorkspaceFolders.js.map +1 -0
  467. package/dist/tools/getWorkspaceSettings.d.ts +44 -0
  468. package/dist/tools/getWorkspaceSettings.js +70 -0
  469. package/dist/tools/getWorkspaceSettings.js.map +1 -0
  470. package/dist/tools/git-utils.d.ts +16 -0
  471. package/dist/tools/git-utils.js +46 -0
  472. package/dist/tools/git-utils.js.map +1 -0
  473. package/dist/tools/gitHistory.d.ts +110 -0
  474. package/dist/tools/gitHistory.js +167 -0
  475. package/dist/tools/gitHistory.js.map +1 -0
  476. package/dist/tools/gitWrite.d.ts +612 -0
  477. package/dist/tools/gitWrite.js +983 -0
  478. package/dist/tools/gitWrite.js.map +1 -0
  479. package/dist/tools/github/actions.d.ts +152 -0
  480. package/dist/tools/github/actions.js +195 -0
  481. package/dist/tools/github/actions.js.map +1 -0
  482. package/dist/tools/github/index.d.ts +3 -0
  483. package/dist/tools/github/index.js +4 -0
  484. package/dist/tools/github/index.js.map +1 -0
  485. package/dist/tools/github/issues.d.ts +281 -0
  486. package/dist/tools/github/issues.js +340 -0
  487. package/dist/tools/github/issues.js.map +1 -0
  488. package/dist/tools/github/pr.d.ts +433 -0
  489. package/dist/tools/github/pr.js +588 -0
  490. package/dist/tools/github/pr.js.map +1 -0
  491. package/dist/tools/github/shared.d.ts +4 -0
  492. package/dist/tools/github/shared.js +12 -0
  493. package/dist/tools/github/shared.js.map +1 -0
  494. package/dist/tools/handoffNote.d.ts +106 -0
  495. package/dist/tools/handoffNote.js +232 -0
  496. package/dist/tools/handoffNote.js.map +1 -0
  497. package/dist/tools/headless/lspClient.d.ts +26 -0
  498. package/dist/tools/headless/lspClient.js +221 -0
  499. package/dist/tools/headless/lspClient.js.map +1 -0
  500. package/dist/tools/headless/lspFallback.d.ts +28 -0
  501. package/dist/tools/headless/lspFallback.js +122 -0
  502. package/dist/tools/headless/lspFallback.js.map +1 -0
  503. package/dist/tools/hoverAtCursor.d.ts +54 -0
  504. package/dist/tools/hoverAtCursor.js +68 -0
  505. package/dist/tools/hoverAtCursor.js.map +1 -0
  506. package/dist/tools/httpClient.d.ts +141 -0
  507. package/dist/tools/httpClient.js +486 -0
  508. package/dist/tools/httpClient.js.map +1 -0
  509. package/dist/tools/index.d.ts +49 -0
  510. package/dist/tools/index.js +672 -0
  511. package/dist/tools/index.js.map +1 -0
  512. package/dist/tools/inlayHints.d.ts +81 -0
  513. package/dist/tools/inlayHints.js +76 -0
  514. package/dist/tools/inlayHints.js.map +1 -0
  515. package/dist/tools/issueRefs.d.ts +14 -0
  516. package/dist/tools/issueRefs.js +27 -0
  517. package/dist/tools/issueRefs.js.map +1 -0
  518. package/dist/tools/jumpToFirstError.d.ts +63 -0
  519. package/dist/tools/jumpToFirstError.js +124 -0
  520. package/dist/tools/jumpToFirstError.js.map +1 -0
  521. package/dist/tools/launchQuickTask.d.ts +76 -0
  522. package/dist/tools/launchQuickTask.js +170 -0
  523. package/dist/tools/launchQuickTask.js.map +1 -0
  524. package/dist/tools/linters/biome.d.ts +2 -0
  525. package/dist/tools/linters/biome.js +44 -0
  526. package/dist/tools/linters/biome.js.map +1 -0
  527. package/dist/tools/linters/cargo.d.ts +2 -0
  528. package/dist/tools/linters/cargo.js +45 -0
  529. package/dist/tools/linters/cargo.js.map +1 -0
  530. package/dist/tools/linters/eslint.d.ts +2 -0
  531. package/dist/tools/linters/eslint.js +59 -0
  532. package/dist/tools/linters/eslint.js.map +1 -0
  533. package/dist/tools/linters/govet.d.ts +2 -0
  534. package/dist/tools/linters/govet.js +37 -0
  535. package/dist/tools/linters/govet.js.map +1 -0
  536. package/dist/tools/linters/pyright.d.ts +2 -0
  537. package/dist/tools/linters/pyright.js +34 -0
  538. package/dist/tools/linters/pyright.js.map +1 -0
  539. package/dist/tools/linters/ruff.d.ts +2 -0
  540. package/dist/tools/linters/ruff.js +30 -0
  541. package/dist/tools/linters/ruff.js.map +1 -0
  542. package/dist/tools/linters/types.d.ts +16 -0
  543. package/dist/tools/linters/types.js +2 -0
  544. package/dist/tools/linters/types.js.map +1 -0
  545. package/dist/tools/linters/typescript.d.ts +2 -0
  546. package/dist/tools/linters/typescript.js +38 -0
  547. package/dist/tools/linters/typescript.js.map +1 -0
  548. package/dist/tools/listClaudeTasks.d.ts +84 -0
  549. package/dist/tools/listClaudeTasks.js +88 -0
  550. package/dist/tools/listClaudeTasks.js.map +1 -0
  551. package/dist/tools/listTerminals.d.ts +55 -0
  552. package/dist/tools/listTerminals.js +78 -0
  553. package/dist/tools/listTerminals.js.map +1 -0
  554. package/dist/tools/lsp.d.ts +1086 -0
  555. package/dist/tools/lsp.js +1339 -0
  556. package/dist/tools/lsp.js.map +1 -0
  557. package/dist/tools/navigateToSymbolByName.d.ts +56 -0
  558. package/dist/tools/navigateToSymbolByName.js +170 -0
  559. package/dist/tools/navigateToSymbolByName.js.map +1 -0
  560. package/dist/tools/openDiff.d.ts +66 -0
  561. package/dist/tools/openDiff.js +126 -0
  562. package/dist/tools/openDiff.js.map +1 -0
  563. package/dist/tools/openFile.d.ts +69 -0
  564. package/dist/tools/openFile.js +129 -0
  565. package/dist/tools/openFile.js.map +1 -0
  566. package/dist/tools/openInBrowser.d.ts +55 -0
  567. package/dist/tools/openInBrowser.js +129 -0
  568. package/dist/tools/openInBrowser.js.map +1 -0
  569. package/dist/tools/organizeImports.d.ts +56 -0
  570. package/dist/tools/organizeImports.js +115 -0
  571. package/dist/tools/organizeImports.js.map +1 -0
  572. package/dist/tools/performanceReport.d.ts +133 -0
  573. package/dist/tools/performanceReport.js +218 -0
  574. package/dist/tools/performanceReport.js.map +1 -0
  575. package/dist/tools/planPersistence.d.ts +306 -0
  576. package/dist/tools/planPersistence.js +485 -0
  577. package/dist/tools/planPersistence.js.map +1 -0
  578. package/dist/tools/previewEdit.d.ts +107 -0
  579. package/dist/tools/previewEdit.js +270 -0
  580. package/dist/tools/previewEdit.js.map +1 -0
  581. package/dist/tools/recentTracesDigest.d.ts +35 -0
  582. package/dist/tools/recentTracesDigest.js +98 -0
  583. package/dist/tools/recentTracesDigest.js.map +1 -0
  584. package/dist/tools/refactorAnalyze.d.ts +78 -0
  585. package/dist/tools/refactorAnalyze.js +141 -0
  586. package/dist/tools/refactorAnalyze.js.map +1 -0
  587. package/dist/tools/refactorExtractFunction.d.ts +52 -0
  588. package/dist/tools/refactorExtractFunction.js +121 -0
  589. package/dist/tools/refactorExtractFunction.js.map +1 -0
  590. package/dist/tools/refactorPreview.d.ts +75 -0
  591. package/dist/tools/refactorPreview.js +93 -0
  592. package/dist/tools/refactorPreview.js.map +1 -0
  593. package/dist/tools/replaceBlock.d.ts +62 -0
  594. package/dist/tools/replaceBlock.js +125 -0
  595. package/dist/tools/replaceBlock.js.map +1 -0
  596. package/dist/tools/resumeClaudeTask.d.ts +75 -0
  597. package/dist/tools/resumeClaudeTask.js +149 -0
  598. package/dist/tools/resumeClaudeTask.js.map +1 -0
  599. package/dist/tools/runClaudeTask.d.ts +97 -0
  600. package/dist/tools/runClaudeTask.js +224 -0
  601. package/dist/tools/runClaudeTask.js.map +1 -0
  602. package/dist/tools/runCommand.d.ts +82 -0
  603. package/dist/tools/runCommand.js +101 -0
  604. package/dist/tools/runCommand.js.map +1 -0
  605. package/dist/tools/runTests.d.ts +146 -0
  606. package/dist/tools/runTests.js +315 -0
  607. package/dist/tools/runTests.js.map +1 -0
  608. package/dist/tools/saveDocument.d.ts +50 -0
  609. package/dist/tools/saveDocument.js +73 -0
  610. package/dist/tools/saveDocument.js.map +1 -0
  611. package/dist/tools/screenshot.d.ts +23 -0
  612. package/dist/tools/screenshot.js +43 -0
  613. package/dist/tools/screenshot.js.map +1 -0
  614. package/dist/tools/screenshotAndAnnotate.d.ts +103 -0
  615. package/dist/tools/screenshotAndAnnotate.js +192 -0
  616. package/dist/tools/screenshotAndAnnotate.js.map +1 -0
  617. package/dist/tools/searchAndReplace.d.ts +108 -0
  618. package/dist/tools/searchAndReplace.js +281 -0
  619. package/dist/tools/searchAndReplace.js.map +1 -0
  620. package/dist/tools/searchTools.d.ts +61 -0
  621. package/dist/tools/searchTools.js +85 -0
  622. package/dist/tools/searchTools.js.map +1 -0
  623. package/dist/tools/searchWorkspace.d.ts +99 -0
  624. package/dist/tools/searchWorkspace.js +189 -0
  625. package/dist/tools/searchWorkspace.js.map +1 -0
  626. package/dist/tools/selectionRanges.d.ts +58 -0
  627. package/dist/tools/selectionRanges.js +61 -0
  628. package/dist/tools/selectionRanges.js.map +1 -0
  629. package/dist/tools/semanticTokens.d.ts +87 -0
  630. package/dist/tools/semanticTokens.js +86 -0
  631. package/dist/tools/semanticTokens.js.map +1 -0
  632. package/dist/tools/setActiveWorkspaceFolder.d.ts +41 -0
  633. package/dist/tools/setActiveWorkspaceFolder.js +38 -0
  634. package/dist/tools/setActiveWorkspaceFolder.js.map +1 -0
  635. package/dist/tools/signatureHelp.d.ts +86 -0
  636. package/dist/tools/signatureHelp.js +79 -0
  637. package/dist/tools/signatureHelp.js.map +1 -0
  638. package/dist/tools/spawnWorkspace.d.ts +103 -0
  639. package/dist/tools/spawnWorkspace.js +268 -0
  640. package/dist/tools/spawnWorkspace.js.map +1 -0
  641. package/dist/tools/stackTraceParser.d.ts +43 -0
  642. package/dist/tools/stackTraceParser.js +139 -0
  643. package/dist/tools/stackTraceParser.js.map +1 -0
  644. package/dist/tools/terminal.d.ts +352 -0
  645. package/dist/tools/terminal.js +670 -0
  646. package/dist/tools/terminal.js.map +1 -0
  647. package/dist/tools/testRunners/cargoTest.d.ts +2 -0
  648. package/dist/tools/testRunners/cargoTest.js +129 -0
  649. package/dist/tools/testRunners/cargoTest.js.map +1 -0
  650. package/dist/tools/testRunners/goTest.d.ts +2 -0
  651. package/dist/tools/testRunners/goTest.js +108 -0
  652. package/dist/tools/testRunners/goTest.js.map +1 -0
  653. package/dist/tools/testRunners/pytest.d.ts +2 -0
  654. package/dist/tools/testRunners/pytest.js +135 -0
  655. package/dist/tools/testRunners/pytest.js.map +1 -0
  656. package/dist/tools/testRunners/types.d.ts +18 -0
  657. package/dist/tools/testRunners/types.js +2 -0
  658. package/dist/tools/testRunners/types.js.map +1 -0
  659. package/dist/tools/testRunners/vitestJest.d.ts +3 -0
  660. package/dist/tools/testRunners/vitestJest.js +215 -0
  661. package/dist/tools/testRunners/vitestJest.js.map +1 -0
  662. package/dist/tools/testTraceToSource.d.ts +80 -0
  663. package/dist/tools/testTraceToSource.js +206 -0
  664. package/dist/tools/testTraceToSource.js.map +1 -0
  665. package/dist/tools/transaction.d.ts +243 -0
  666. package/dist/tools/transaction.js +309 -0
  667. package/dist/tools/transaction.js.map +1 -0
  668. package/dist/tools/typeHierarchy.d.ts +77 -0
  669. package/dist/tools/typeHierarchy.js +86 -0
  670. package/dist/tools/typeHierarchy.js.map +1 -0
  671. package/dist/tools/utils.d.ts +124 -0
  672. package/dist/tools/utils.js +566 -0
  673. package/dist/tools/utils.js.map +1 -0
  674. package/dist/tools/vscodeCommands.d.ts +90 -0
  675. package/dist/tools/vscodeCommands.js +112 -0
  676. package/dist/tools/vscodeCommands.js.map +1 -0
  677. package/dist/tools/vscodeTasks.d.ts +102 -0
  678. package/dist/tools/vscodeTasks.js +110 -0
  679. package/dist/tools/vscodeTasks.js.map +1 -0
  680. package/dist/tools/watchDiagnostics.d.ts +64 -0
  681. package/dist/tools/watchDiagnostics.js +270 -0
  682. package/dist/tools/watchDiagnostics.js.map +1 -0
  683. package/dist/tools/workspaceSettings.d.ts +57 -0
  684. package/dist/tools/workspaceSettings.js +80 -0
  685. package/dist/tools/workspaceSettings.js.map +1 -0
  686. package/dist/transport.d.ts +207 -0
  687. package/dist/transport.js +1272 -0
  688. package/dist/transport.js.map +1 -0
  689. package/dist/version.d.ts +13 -0
  690. package/dist/version.js +31 -0
  691. package/dist/version.js.map +1 -0
  692. package/dist/wsUtils.d.ts +8 -0
  693. package/dist/wsUtils.js +54 -0
  694. package/dist/wsUtils.js.map +1 -0
  695. package/package.json +118 -0
  696. package/scripts/gen-claude-desktop-config.sh +124 -0
  697. package/scripts/gen-mcp-config.sh +390 -0
  698. package/scripts/install-extension.sh +106 -0
  699. package/scripts/mcp-stdio-shim.cjs +482 -0
  700. package/scripts/postinstall.mjs +68 -0
  701. package/scripts/start-all.sh +502 -0
  702. package/scripts/start-orchestrator.sh +186 -0
  703. package/scripts/start-remote.sh +126 -0
  704. package/scripts/start-vps.sh +116 -0
  705. package/templates/CLAUDE.bridge.md +125 -0
  706. package/templates/automation-policies/security-first.json +46 -0
  707. package/templates/automation-policies/strict-lint.json +41 -0
  708. package/templates/automation-policies/test-driven.json +54 -0
  709. package/templates/automation-policy.example.json +105 -0
  710. package/templates/bridge-tools.md +111 -0
  711. package/templates/dispatch-context.md +33 -0
  712. package/templates/managed-agent/code-review-agent.md +50 -0
  713. package/templates/managed-agent/managed-agent-mcp.json +102 -0
  714. package/templates/recipes/ambient-journal.yaml +11 -0
  715. package/templates/recipes/daily-status.yaml +21 -0
  716. package/templates/recipes/lint-on-save.yaml +13 -0
  717. package/templates/recipes/stale-branches.yaml +18 -0
  718. package/templates/recipes/watch-failing-tests.yaml +15 -0
  719. package/templates/scheduled-tasks/dependency-audit/SKILL.md +77 -0
  720. package/templates/scheduled-tasks/health-check/SKILL.md +73 -0
  721. package/templates/scheduled-tasks/nightly-review/SKILL.md +69 -0
@@ -0,0 +1,1253 @@
1
+ import { WebSocket } from "ws";
2
+ import { BRIDGE_PROTOCOL_VERSION } from "./version.js";
3
+ import { safeSend, waitForDrain } from "./wsUtils.js";
4
+ /** Thrown when an extension request times out — distinguishable from "no results" (null). */
5
+ export class ExtensionTimeoutError extends Error {
6
+ method;
7
+ constructor(method) {
8
+ super(`Extension request ${method} timed out`);
9
+ this.name = "ExtensionTimeoutError";
10
+ this.method = method;
11
+ }
12
+ }
13
+ const REQUEST_TIMEOUT = 10_000;
14
+ export class ExtensionClient {
15
+ logger;
16
+ ws = null;
17
+ connected = false;
18
+ pendingRequests = new Map();
19
+ nextId = 0;
20
+ // Windowed circuit breaker — opens only if ≥3 timeouts occur within 30 seconds.
21
+ // A single slow LSP response no longer trips the breaker; sustained failure does.
22
+ static CIRCUIT_WINDOW_MS = 30_000;
23
+ static CIRCUIT_THRESHOLD = 3;
24
+ extensionSuspendedUntil = 0;
25
+ extensionFailureTimes = []; // timestamps of recent timeouts
26
+ extensionHalfOpen = false;
27
+ _circuitOpenCount = 0;
28
+ _lastCircuitOpenedAt = null;
29
+ // State pushed by extension via notifications
30
+ latestDiagnostics = new Map();
31
+ /** Parallel timestamp map — tracks when each file's diagnostics were last updated. */
32
+ latestDiagnosticsUpdatedAt = new Map();
33
+ static DIAGNOSTICS_TTL_MS = 3_600_000; // 1 hour
34
+ latestSelection = null;
35
+ latestActiveFile = null;
36
+ lastDiagnosticsUpdate = 0;
37
+ // AI comment cache (pushed by extension)
38
+ latestAIComments = new Map();
39
+ // Debug state (pushed by extension via notifications)
40
+ latestDebugState = null;
41
+ // LSP readiness state — pushed by extension when language servers finish indexing.
42
+ // Used by lspWithRetry to skip retry delays for languages known to be ready.
43
+ lspReadyLanguages = new Set();
44
+ // Connection quality — round-trip latency pushed by extension via rttUpdate notification.
45
+ lastRttMs = null;
46
+ // Extension package version reported in hello (npm package, not protocol version)
47
+ extensionPackageVersion = null;
48
+ // Callbacks for forwarding notifications to Claude Code
49
+ onDiagnosticsChanged = null;
50
+ onAICommentsChanged = null;
51
+ onFileChanged = null;
52
+ onExtensionDisconnected = null;
53
+ onDebugSessionChanged = null;
54
+ // Listener set for diagnostics (used by watchDiagnostics long-poll)
55
+ diagnosticsListeners = new Set();
56
+ constructor(logger) {
57
+ this.logger = logger;
58
+ }
59
+ /** Invoke a callback safely — log and swallow errors to prevent bridge crash */
60
+ safeCallback(fn, ...args) {
61
+ if (!fn)
62
+ return;
63
+ try {
64
+ fn(...args);
65
+ }
66
+ catch (err) {
67
+ this.logger.error(`Callback error: ${err instanceof Error ? err.message : String(err)}`);
68
+ }
69
+ }
70
+ handleExtensionConnection(ws) {
71
+ // Replace existing connection
72
+ if (this.ws) {
73
+ this.logger.info("Replacing existing extension connection");
74
+ // Null out this.ws *before* rejectAllPending so that any synchronous retry
75
+ // triggered from a reject handler sees "not connected" and backs off,
76
+ // rather than accidentally sending on the old (about-to-be-terminated) socket.
77
+ const oldWs = this.ws;
78
+ this.ws = null;
79
+ this.connected = false;
80
+ this.rejectAllPending("Extension reconnected");
81
+ oldWs.removeAllListeners();
82
+ if (oldWs.readyState === WebSocket.OPEN) {
83
+ oldWs.terminate();
84
+ }
85
+ // Clear stale diagnostics listeners — the old socket's close event will
86
+ // never fire (listeners removed above), so handleDisconnect won't run for
87
+ // it. Without this, stale watchDiagnostics closures accumulate unboundedly
88
+ // across reconnects and fire on every diagnostic update.
89
+ this.diagnosticsListeners.clear();
90
+ }
91
+ this.ws = ws;
92
+ this.connected = true;
93
+ // Don't clear cached diagnostics/selection/file state — extension pushes
94
+ // fresh data on connect, so stale values are harmless for a few seconds.
95
+ // Clear LSP readiness — extension will re-send on reconnect
96
+ this.lspReadyLanguages.clear();
97
+ // Reset circuit breaker — fresh connection deserves a clean slate
98
+ this.extensionSuspendedUntil = 0;
99
+ this.extensionFailureTimes = [];
100
+ this.extensionHalfOpen = false;
101
+ this.lastRttMs = null;
102
+ this.logger.info("Extension client connected");
103
+ ws.on("message", (data) => {
104
+ try {
105
+ const msg = JSON.parse(data.toString("utf-8"));
106
+ // Response to our request
107
+ if (msg.id !== undefined && msg.id !== null && !msg.method) {
108
+ const pending = this.pendingRequests.get(msg.id);
109
+ if (pending) {
110
+ this.pendingRequests.delete(msg.id);
111
+ clearTimeout(pending.timer);
112
+ if (msg.error) {
113
+ pending.reject(new Error(msg.error.message));
114
+ }
115
+ else {
116
+ pending.resolve(msg.result);
117
+ }
118
+ }
119
+ else {
120
+ this.logger.debug(`Orphaned extension response for id=${msg.id} (likely timed out)`);
121
+ }
122
+ return;
123
+ }
124
+ // Push notification from extension
125
+ if (msg.method) {
126
+ this.handleNotification(msg.method, msg.params);
127
+ }
128
+ }
129
+ catch (err) {
130
+ this.logger.error(`Extension message parse error: ${err}`);
131
+ }
132
+ });
133
+ // Guard against double close/error cleanup
134
+ let disconnected = false;
135
+ const handleDisconnect = (reason) => {
136
+ if (disconnected)
137
+ return;
138
+ disconnected = true;
139
+ this.connected = false;
140
+ this.ws = null;
141
+ this.rejectAllPending(reason);
142
+ // Clear diagnostics listeners — stale watchDiagnostics closures must not
143
+ // accumulate across reconnects (they hold references to old AbortSignals).
144
+ this.diagnosticsListeners.clear();
145
+ // Clear LSP readiness — language servers may need to re-index after reconnect
146
+ this.lspReadyLanguages.clear();
147
+ this.logger.info(`Extension disconnected: ${reason}`);
148
+ this.safeCallback(this.onExtensionDisconnected);
149
+ };
150
+ ws.on("close", () => handleDisconnect("Connection closed"));
151
+ ws.on("error", (err) => {
152
+ this.logger.error(`Extension WS error: ${err.message}`);
153
+ // Set disconnected flag before terminate() so any synchronous close event
154
+ // emitted by terminate() sees the guard as already set.
155
+ handleDisconnect(`Error: ${err.message}`);
156
+ ws.terminate();
157
+ });
158
+ }
159
+ handleNotification(method, params) {
160
+ if (typeof params !== "object" || params === null) {
161
+ this.logger.debug(`Ignoring notification ${method} with invalid params`);
162
+ return;
163
+ }
164
+ const p = params;
165
+ switch (method) {
166
+ case "extension/diagnosticsChanged": {
167
+ const file = p.file;
168
+ const diagnostics = p.diagnostics;
169
+ if (typeof file !== "string" || !Array.isArray(diagnostics)) {
170
+ this.logger.debug("Ignoring malformed diagnosticsChanged notification");
171
+ return;
172
+ }
173
+ let sanitized;
174
+ if (diagnostics.length === 0) {
175
+ this.latestDiagnostics.delete(file);
176
+ this.latestDiagnosticsUpdatedAt.delete(file);
177
+ sanitized = [];
178
+ }
179
+ else {
180
+ // Sanitize: extract only known-safe fields to prevent prototype pollution
181
+ // when these objects are later spread or merged.
182
+ const safe = diagnostics.map((d) => {
183
+ if (typeof d !== "object" || d === null)
184
+ return d;
185
+ const diag = d;
186
+ return {
187
+ file: typeof diag.file === "string"
188
+ ? diag.file.slice(0, 4096)
189
+ : undefined,
190
+ line: typeof diag.line === "number" ? diag.line : undefined,
191
+ column: typeof diag.column === "number" ? diag.column : undefined,
192
+ endLine: typeof diag.endLine === "number" ? diag.endLine : undefined,
193
+ endColumn: typeof diag.endColumn === "number" ? diag.endColumn : undefined,
194
+ severity: typeof diag.severity === "string"
195
+ ? diag.severity.slice(0, 32)
196
+ : undefined,
197
+ message: typeof diag.message === "string"
198
+ ? diag.message.slice(0, 4096)
199
+ : undefined,
200
+ source: typeof diag.source === "string"
201
+ ? diag.source.slice(0, 256)
202
+ : undefined,
203
+ code: typeof diag.code === "string" || typeof diag.code === "number"
204
+ ? diag.code
205
+ : undefined,
206
+ };
207
+ });
208
+ sanitized = safe;
209
+ this.latestDiagnostics.set(file, sanitized);
210
+ this.latestDiagnosticsUpdatedAt.set(file, Date.now());
211
+ // Cap diagnostics cache at 500 entries
212
+ if (this.latestDiagnostics.size > 500) {
213
+ const firstKey = this.latestDiagnostics.keys().next().value;
214
+ if (firstKey !== undefined) {
215
+ this.latestDiagnostics.delete(firstKey);
216
+ this.latestDiagnosticsUpdatedAt.delete(firstKey);
217
+ }
218
+ }
219
+ }
220
+ // Update timestamp and forward sanitized data to Claude Code
221
+ this.lastDiagnosticsUpdate = Date.now();
222
+ this.safeCallback(this.onDiagnosticsChanged, file, sanitized);
223
+ for (const fn of [...this.diagnosticsListeners])
224
+ this.safeCallback(fn, file, sanitized);
225
+ break;
226
+ }
227
+ case "extension/selectionChanged": {
228
+ if (typeof p.file !== "string" ||
229
+ typeof p.startLine !== "number" ||
230
+ typeof p.endLine !== "number" ||
231
+ typeof p.startColumn !== "number" ||
232
+ typeof p.endColumn !== "number" ||
233
+ typeof p.selectedText !== "string") {
234
+ this.logger.debug("Ignoring malformed selectionChanged notification");
235
+ return;
236
+ }
237
+ this.latestSelection = {
238
+ file: p.file.slice(0, 4096),
239
+ startLine: p.startLine,
240
+ endLine: p.endLine,
241
+ startColumn: p.startColumn,
242
+ endColumn: p.endColumn,
243
+ selectedText: p.selectedText.slice(0, 65536),
244
+ };
245
+ break;
246
+ }
247
+ case "extension/activeFileChanged": {
248
+ if (typeof p.file !== "string") {
249
+ this.logger.debug("Ignoring malformed activeFileChanged notification");
250
+ return;
251
+ }
252
+ this.latestActiveFile = p.file.slice(0, 4096);
253
+ break;
254
+ }
255
+ case "extension/aiCommentsChanged": {
256
+ const comments = p.comments;
257
+ if (!Array.isArray(comments)) {
258
+ this.logger.debug("Ignoring malformed aiCommentsChanged notification");
259
+ return;
260
+ }
261
+ // Valid severity values — defined once outside the loop.
262
+ const VALID_SEVERITIES = new Set([
263
+ "fix",
264
+ "todo",
265
+ "question",
266
+ "warn",
267
+ "task",
268
+ ]);
269
+ this.latestAIComments.clear();
270
+ for (const c of comments) {
271
+ const entry = c;
272
+ if (typeof entry.file !== "string")
273
+ continue;
274
+ // Sanitize: extract only known-safe fields (mirrors diagnostics sanitization)
275
+ // to prevent prototype pollution if the extension sends unexpected properties.
276
+ const safe = {
277
+ file: entry.file,
278
+ line: typeof entry.line === "number" ? entry.line : 0,
279
+ comment: typeof entry.comment === "string" ? entry.comment : "",
280
+ syntax: typeof entry.syntax === "string" ? entry.syntax : "",
281
+ fullLine: typeof entry.fullLine === "string" ? entry.fullLine : "",
282
+ ...(typeof entry.severity === "string" &&
283
+ VALID_SEVERITIES.has(entry.severity) && {
284
+ severity: entry.severity,
285
+ }),
286
+ };
287
+ const existing = this.latestAIComments.get(safe.file) || [];
288
+ existing.push(safe);
289
+ this.latestAIComments.set(safe.file, existing);
290
+ }
291
+ // Cap at 200 files
292
+ if (this.latestAIComments.size > 200) {
293
+ const firstKey = this.latestAIComments.keys().next().value;
294
+ if (firstKey !== undefined)
295
+ this.latestAIComments.delete(firstKey);
296
+ }
297
+ // Collect the sanitized entries from latestAIComments and pass those
298
+ // to the callback — not the raw wire data (BUG-5 fix)
299
+ const sanitizedComments = [];
300
+ for (const entries of this.latestAIComments.values()) {
301
+ sanitizedComments.push(...entries);
302
+ }
303
+ this.safeCallback(this.onAICommentsChanged, sanitizedComments);
304
+ break;
305
+ }
306
+ case "extension/fileChanged": {
307
+ const id = p.id;
308
+ const type = p.type;
309
+ const file = p.file;
310
+ if (typeof id === "string" &&
311
+ typeof type === "string" &&
312
+ typeof file === "string") {
313
+ this.safeCallback(this.onFileChanged, id, type, file);
314
+ }
315
+ break;
316
+ }
317
+ case "extension/hello": {
318
+ const extVer = typeof p.extensionVersion === "string"
319
+ ? p.extensionVersion
320
+ : "unknown";
321
+ const pkgVer = typeof p.packageVersion === "string" ? p.packageVersion : null;
322
+ if (pkgVer)
323
+ this.extensionPackageVersion = pkgVer;
324
+ this.logger.info(`Extension hello: protocolVersion=${extVer}${pkgVer ? `, packageVersion=${pkgVer}` : ""}`);
325
+ const extMajor = Number.parseInt(extVer.split(".")[0] ?? "", 10);
326
+ const bridgeMajor = Number.parseInt(BRIDGE_PROTOCOL_VERSION.split(".")[0] ?? "", 10);
327
+ if (Number.isNaN(extMajor)) {
328
+ this.logger.debug(`Extension protocol version "${extVer}" is not a recognized semver format, skipping version check`);
329
+ }
330
+ else if (extMajor !== bridgeMajor) {
331
+ this.logger.warn(`Extension protocol major version mismatch: bridge=${BRIDGE_PROTOCOL_VERSION}, extension=${extVer}. Consider updating.`);
332
+ }
333
+ break;
334
+ }
335
+ case "extension/lspReady": {
336
+ const languageId = p.languageId;
337
+ if (typeof languageId !== "string" || languageId.length > 64) {
338
+ this.logger.debug("Ignoring malformed lspReady notification");
339
+ return;
340
+ }
341
+ this.lspReadyLanguages.add(languageId);
342
+ this.logger.info(`LSP ready: ${languageId}`);
343
+ break;
344
+ }
345
+ case "extension/fileSaved":
346
+ this.logger.debug(`[extensionClient] received extension/fileSaved for: ${typeof p.file === "string" ? p.file : "(unknown)"}`);
347
+ if (typeof p.file === "string") {
348
+ this.safeCallback(this.onFileChanged, p.file, "save", p.file);
349
+ }
350
+ break;
351
+ case "extension/debugSessionChanged": {
352
+ if (typeof p.hasActiveSession !== "boolean") {
353
+ this.logger.debug("Ignoring malformed debugSessionChanged notification");
354
+ return;
355
+ }
356
+ // Extract only known-safe fields rather than casting the raw wire object.
357
+ const raw = p;
358
+ const state = {
359
+ hasActiveSession: typeof raw.hasActiveSession === "boolean"
360
+ ? raw.hasActiveSession
361
+ : false,
362
+ isPaused: typeof raw.isPaused === "boolean" ? raw.isPaused : false,
363
+ sessionId: typeof raw.sessionId === "string" ? raw.sessionId : undefined,
364
+ sessionName: typeof raw.sessionName === "string" ? raw.sessionName : undefined,
365
+ sessionType: typeof raw.sessionType === "string" ? raw.sessionType : undefined,
366
+ pausedAt: raw.pausedAt &&
367
+ typeof raw.pausedAt === "object" &&
368
+ !Array.isArray(raw.pausedAt)
369
+ ? {
370
+ file: typeof raw.pausedAt.file ===
371
+ "string"
372
+ ? raw.pausedAt
373
+ .file
374
+ : "",
375
+ line: typeof raw.pausedAt.line ===
376
+ "number"
377
+ ? raw.pausedAt
378
+ .line
379
+ : 0,
380
+ column: typeof raw.pausedAt.column ===
381
+ "number"
382
+ ? raw.pausedAt
383
+ .column
384
+ : 0,
385
+ }
386
+ : undefined,
387
+ callStack: Array.isArray(raw.callStack)
388
+ ? raw.callStack.reduce((acc, f) => {
389
+ if (typeof f === "object" && f !== null && !Array.isArray(f)) {
390
+ const frame = f;
391
+ acc.push({
392
+ id: typeof frame.id === "number" ? frame.id : 0,
393
+ name: typeof frame.name === "string" ? frame.name : "",
394
+ file: typeof frame.file === "string" ? frame.file : "",
395
+ line: typeof frame.line === "number" ? frame.line : 0,
396
+ column: typeof frame.column === "number" ? frame.column : 0,
397
+ });
398
+ }
399
+ return acc;
400
+ }, [])
401
+ : undefined,
402
+ scopes: Array.isArray(raw.scopes)
403
+ ? raw.scopes.reduce((acc, s) => {
404
+ if (typeof s === "object" && s !== null && !Array.isArray(s)) {
405
+ const scope = s;
406
+ acc.push({
407
+ name: typeof scope.name === "string" ? scope.name : "",
408
+ variables: Array.isArray(scope.variables)
409
+ ? scope.variables.reduce((vacc, v) => {
410
+ if (typeof v === "object" && v !== null) {
411
+ const vv = v;
412
+ vacc.push({
413
+ name: typeof vv.name === "string" ? vv.name : "",
414
+ value: typeof vv.value === "string" ? vv.value : "",
415
+ type: typeof vv.type === "string" ? vv.type : "",
416
+ });
417
+ }
418
+ return vacc;
419
+ }, [])
420
+ : [],
421
+ });
422
+ }
423
+ return acc;
424
+ }, [])
425
+ : undefined,
426
+ breakpoints: Array.isArray(raw.breakpoints)
427
+ ? raw.breakpoints.reduce((acc, b) => {
428
+ if (typeof b === "object" && b !== null) {
429
+ const bp = b;
430
+ acc.push({
431
+ file: typeof bp.file === "string" ? bp.file : "",
432
+ line: typeof bp.line === "number" ? bp.line : 0,
433
+ condition: typeof bp.condition === "string"
434
+ ? bp.condition
435
+ : undefined,
436
+ enabled: typeof bp.enabled === "boolean" ? bp.enabled : true,
437
+ });
438
+ }
439
+ return acc;
440
+ }, [])
441
+ : [],
442
+ };
443
+ this.latestDebugState = state;
444
+ this.safeCallback(this.onDebugSessionChanged, state);
445
+ break;
446
+ }
447
+ case "extension/rttUpdate": {
448
+ const latencyMs = p.latencyMs;
449
+ if (typeof latencyMs === "number" &&
450
+ latencyMs >= 0 &&
451
+ latencyMs < 10_000) {
452
+ this.lastRttMs = latencyMs;
453
+ }
454
+ break;
455
+ }
456
+ default:
457
+ this.logger.debug(`Unknown extension notification: ${method}`);
458
+ }
459
+ }
460
+ static MAX_PENDING_REQUESTS = 100;
461
+ async request(method, params, timeoutMs, signal) {
462
+ if (this.pendingRequests.size >= ExtensionClient.MAX_PENDING_REQUESTS) {
463
+ throw new Error(`Too many pending extension requests (${ExtensionClient.MAX_PENDING_REQUESTS})`);
464
+ }
465
+ // Exponential backoff — fast-fail if extension is repeatedly timing out
466
+ const now = Date.now();
467
+ if (now < this.extensionSuspendedUntil) {
468
+ throw new ExtensionTimeoutError(method);
469
+ }
470
+ // Half-open: backoff expired but failures still in window — allow one probe through.
471
+ // Only applicable after the circuit has actually opened (extensionSuspendedUntil > 0).
472
+ if (this.extensionSuspendedUntil > 0 &&
473
+ this.extensionFailureTimes.length > 0 &&
474
+ !this.extensionHalfOpen) {
475
+ this.extensionHalfOpen = true;
476
+ this.logger.debug(`Extension circuit breaker half-open — probing with ${method}`);
477
+ }
478
+ if (!this.connected || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
479
+ throw new Error("Extension not connected");
480
+ }
481
+ if (signal?.aborted) {
482
+ throw new Error("Request aborted");
483
+ }
484
+ // Wait for backpressure to clear before sending
485
+ await waitForDrain(this.ws, this.logger, "Extension backpressure");
486
+ // Re-check after drain wait — socket may have closed or circuit breaker may have tripped
487
+ if (!this.connected || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
488
+ throw new Error("Extension disconnected during drain wait");
489
+ }
490
+ // NOTE: This guard is the only safety net for requests that disconnect during waitForDrain.
491
+ // At this point the request is not yet in pendingRequests, so rejectAllPending() would not catch it.
492
+ if (Date.now() < this.extensionSuspendedUntil) {
493
+ throw new ExtensionTimeoutError(method);
494
+ }
495
+ const timeout = timeoutMs ?? REQUEST_TIMEOUT;
496
+ const id = this.nextId++;
497
+ if (this.nextId >= Number.MAX_SAFE_INTEGER)
498
+ this.nextId = 0;
499
+ const inner = new Promise((resolve, reject) => {
500
+ let settled = false;
501
+ const settle = (fn) => {
502
+ if (settled)
503
+ return;
504
+ settled = true;
505
+ fn();
506
+ };
507
+ const timer = setTimeout(() => {
508
+ this.pendingRequests.delete(id);
509
+ signal?.removeEventListener("abort", onAbort);
510
+ this.logger.warn(`Extension request ${method} timed out after ${timeout}ms`);
511
+ settle(() => reject(new ExtensionTimeoutError(method)));
512
+ }, timeout);
513
+ // Re-check abort after the async drain wait — signal may have fired during that gap.
514
+ // If already aborted, addEventListener would never fire so we must check here.
515
+ if (signal?.aborted) {
516
+ clearTimeout(timer);
517
+ settle(() => reject(new Error("Request aborted")));
518
+ return;
519
+ }
520
+ // Wire AbortSignal to cancel the pending request.
521
+ // IMPORTANT: pendingRequests.set must happen before addEventListener so that
522
+ // if the signal fires synchronously during addEventListener (or in the tiny
523
+ // window between the two calls), onAbort finds the entry and deletes it
524
+ // cleanly. Without this ordering the entry would be inserted after onAbort
525
+ // ran, leaving an orphaned entry with no timeout and no resolution path.
526
+ const onAbort = () => {
527
+ this.pendingRequests.delete(id);
528
+ clearTimeout(timer);
529
+ settle(() => reject(new Error("Request aborted")));
530
+ };
531
+ this.pendingRequests.set(id, {
532
+ resolve: (value) => {
533
+ signal?.removeEventListener("abort", onAbort);
534
+ settle(() => resolve(value));
535
+ },
536
+ reject: (reason) => {
537
+ signal?.removeEventListener("abort", onAbort);
538
+ settle(() => reject(reason));
539
+ },
540
+ timer,
541
+ removeAbortListener: signal
542
+ ? () => signal.removeEventListener("abort", onAbort)
543
+ : undefined,
544
+ });
545
+ if (signal) {
546
+ signal.addEventListener("abort", onAbort, { once: true });
547
+ }
548
+ const data = JSON.stringify({ jsonrpc: "2.0", id, method, params });
549
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
550
+ try {
551
+ this.ws.send(data);
552
+ }
553
+ catch (err) {
554
+ this.pendingRequests.delete(id);
555
+ clearTimeout(timer);
556
+ signal?.removeEventListener("abort", onAbort);
557
+ settle(() => reject(new Error(`Failed to send extension request: ${err}`)));
558
+ }
559
+ }
560
+ else {
561
+ this.pendingRequests.delete(id);
562
+ clearTimeout(timer);
563
+ signal?.removeEventListener("abort", onAbort);
564
+ settle(() => reject(new Error("Extension disconnected before send")));
565
+ }
566
+ });
567
+ try {
568
+ const result = await inner;
569
+ // Success — reset circuit breaker and half-open state
570
+ if (this.extensionFailureTimes.length > 0) {
571
+ this.logger.info("Extension backoff reset — connection recovered", {
572
+ openCount: this._circuitOpenCount,
573
+ });
574
+ }
575
+ this.extensionFailureTimes = [];
576
+ this.extensionSuspendedUntil = 0;
577
+ this.extensionHalfOpen = false;
578
+ return result;
579
+ }
580
+ catch (err) {
581
+ if (err instanceof ExtensionTimeoutError) {
582
+ this.extensionHalfOpen = false;
583
+ // Sliding window: prune failures older than CIRCUIT_WINDOW_MS, then record this one
584
+ const now = Date.now();
585
+ this.extensionFailureTimes = this.extensionFailureTimes.filter((t) => now - t < ExtensionClient.CIRCUIT_WINDOW_MS);
586
+ this.extensionFailureTimes.push(now);
587
+ const failures = this.extensionFailureTimes.length;
588
+ this.logger.warn(`Extension timed out (${failures} failure${failures === 1 ? "" : "s"} in ${ExtensionClient.CIRCUIT_WINDOW_MS / 1_000}s window)`);
589
+ // Only open the circuit after CIRCUIT_THRESHOLD failures in the window.
590
+ // A single slow LSP response no longer trips the breaker.
591
+ if (failures >= ExtensionClient.CIRCUIT_THRESHOLD) {
592
+ // Full jitter (AWS-recommended): random in [1, cap] — prevents rhythmic
593
+ // retry storms when bridge and extension restart simultaneously.
594
+ const capMs = Math.min(1_000 * 2 ** (failures - 1), 60_000);
595
+ const backoffMs = Math.floor(Math.random() * capMs) + 1;
596
+ this.extensionSuspendedUntil = now + backoffMs;
597
+ this._circuitOpenCount++;
598
+ this._lastCircuitOpenedAt = new Date();
599
+ this.logger.warn(`Extension circuit open (${failures} failures) — suspending for ${Math.round(backoffMs / 100) / 10}s`, { openCount: this._circuitOpenCount });
600
+ // Fast-fail all other in-flight requests immediately when the circuit
601
+ // opens. Without this, each queued request waits its own REQUEST_TIMEOUT
602
+ // (10s) independently, so a tool handler chaining N extension calls
603
+ // would hang for up to N×10s after the extension becomes unresponsive.
604
+ // The timed-out request itself is already removed from pendingRequests
605
+ // by its timer callback before reaching this catch block, so calling
606
+ // rejectAllPending here cannot double-reject it.
607
+ this.rejectAllPending(`Extension circuit open after ${failures} failures — fast-failing pending requests`);
608
+ }
609
+ }
610
+ throw err;
611
+ }
612
+ }
613
+ /** Like request() but returns null on disconnect instead of rejecting.
614
+ * ExtensionTimeoutError is NOT caught — callers must handle it to distinguish
615
+ * timeout from genuine "no results" (null). */
616
+ async requestOrNull(method, params, timeoutMs, signal) {
617
+ return this.request(method, params, timeoutMs, signal).catch((err) => {
618
+ if (err instanceof ExtensionTimeoutError)
619
+ throw err;
620
+ return null;
621
+ });
622
+ }
623
+ rejectAllPending(reason) {
624
+ for (const [_id, pending] of this.pendingRequests) {
625
+ clearTimeout(pending.timer);
626
+ pending.removeAbortListener?.();
627
+ pending.reject(new Error(reason));
628
+ }
629
+ this.pendingRequests.clear();
630
+ }
631
+ /**
632
+ /**
633
+ * Detect an extension error-object response. Extension handlers by convention
634
+ * return `{ error: string }` (sometimes with `success: false`) on failure
635
+ * instead of throwing. This predicate identifies that shape so tryRequest
636
+ * can convert it to null.
637
+ */
638
+ isErrorResponse(r) {
639
+ if (r === null || typeof r !== "object")
640
+ return false;
641
+ const obj = r;
642
+ if ("error" in obj && typeof obj.error === "string")
643
+ return true;
644
+ if (obj.success === false && typeof obj.error === "string")
645
+ return true;
646
+ return false;
647
+ }
648
+ /**
649
+ * Safer shorthand for requestOrNull — converts extension error-object responses
650
+ * (see isErrorResponse) to null so consumers can distinguish "no result" from
651
+ * "corrupt data". Use this for any handler whose success and failure paths
652
+ * return different shapes. Does NOT guarantee the happy-path shape matches T;
653
+ * for that, do a runtime shape check in the client method.
654
+ */
655
+ async tryRequest(method, params, timeout, signal) {
656
+ const raw = await this.requestOrNull(method, params, timeout, signal);
657
+ if (this.isErrorResponse(raw))
658
+ return null;
659
+ return raw;
660
+ }
661
+ /**
662
+ * Shape-validated request — handles shape-wrap bugs where the extension
663
+ * handler returns a different structural shape than the client expects
664
+ * (e.g. `{ folders, count }` vs `WorkspaceFolder[]`).
665
+ *
666
+ * Combines error-object detection from tryRequest with a runtime validator.
667
+ * If `validate` returns null, the response shape did not match and the
668
+ * client should return null — preventing silent data corruption downstream.
669
+ *
670
+ * Use for any handler whose success shape is non-trivial. Prefer over
671
+ * proxy<T> when the response is an object with specific required fields.
672
+ */
673
+ async validatedRequest(method, params, validate, timeout, signal) {
674
+ const raw = await this.requestOrNull(method, params, timeout, signal);
675
+ if (raw === null)
676
+ return null;
677
+ if (this.isErrorResponse(raw))
678
+ return null;
679
+ return validate(raw);
680
+ }
681
+ async getDiagnostics(file) {
682
+ return this.tryRequest("extension/getDiagnostics", { file });
683
+ }
684
+ async getSelection() {
685
+ // Extension returns { error: "No active editor" } when no editor is active.
686
+ return this.tryRequest("extension/getSelection");
687
+ }
688
+ async getOpenFiles() {
689
+ return this.tryRequest("extension/getOpenFiles");
690
+ }
691
+ async isDirty(file) {
692
+ return this.tryRequest("extension/isDirty", { file });
693
+ }
694
+ // handler returns { content, isDirty, languageId, lineCount, version, source } | { success:false, error } — validatedRequest
695
+ async getFileContent(file) {
696
+ return this.validatedRequest("extension/getFileContent", { file }, (r) => {
697
+ const o = r;
698
+ return typeof o.content === "string" ? r : null;
699
+ });
700
+ }
701
+ // handler returns true (boolean) on success — bare requestOrNull, caller checks === true
702
+ async openFile(file, line) {
703
+ const result = await this.requestOrNull("extension/openFile", {
704
+ file,
705
+ line,
706
+ });
707
+ return result === true;
708
+ }
709
+ async saveFile(file) {
710
+ // Extension handler returns `true` on successful save,
711
+ // `{ success: false, error: "..." }` on error paths.
712
+ // Normalize both shapes so the caller can distinguish "saved ok" from
713
+ // "not saved + here is why" from "no response".
714
+ const result = await this.requestOrNull("extension/saveFile", { file });
715
+ if (result === true)
716
+ return { saved: true };
717
+ if (result === null)
718
+ return null;
719
+ if (typeof result === "object") {
720
+ const r = result;
721
+ return {
722
+ saved: r.success === true,
723
+ ...(typeof r.error === "string" && { error: r.error }),
724
+ };
725
+ }
726
+ return null;
727
+ }
728
+ async closeTab(file) {
729
+ // Extension returns { success: true, promptedToSave } on close,
730
+ // { success: false, error } when the tab cannot be found.
731
+ const result = await this.requestOrNull("extension/closeTab", { file });
732
+ if (result === null || typeof result !== "object")
733
+ return null;
734
+ const r = result;
735
+ return {
736
+ success: r.success === true,
737
+ ...(typeof r.promptedToSave === "boolean" && {
738
+ promptedToSave: r.promptedToSave,
739
+ }),
740
+ ...(typeof r.error === "string" && { error: r.error }),
741
+ };
742
+ }
743
+ async getAIComments() {
744
+ return this.tryRequest("extension/getAIComments");
745
+ }
746
+ // --- LSP Semantic Features ---
747
+ // handler returns array of { file, line, column, endLine, endColumn } | null — tryRequest
748
+ async goToDefinition(file, line, column, signal) {
749
+ return this.tryRequest("extension/goToDefinition", { file, line, column }, undefined, signal);
750
+ }
751
+ // handler returns { references: [...], count } — validated
752
+ async findReferences(file, line, column, signal) {
753
+ return this.validatedRequest("extension/findReferences", { file, line, column }, (r) => {
754
+ const o = r;
755
+ return Array.isArray(o.references) ? r : null;
756
+ }, undefined, signal);
757
+ }
758
+ // handler returns { found: true, implementations, count } | null — validatedRequest
759
+ async findImplementations(file, line, column, signal) {
760
+ return this.validatedRequest("extension/findImplementations", { file, line, column }, (r) => {
761
+ const o = r;
762
+ return typeof o.found === "boolean" ? r : null;
763
+ }, undefined, signal);
764
+ }
765
+ // handler returns { found: true, locations } | null — validatedRequest
766
+ async goToTypeDefinition(file, line, column, signal) {
767
+ return this.validatedRequest("extension/goToTypeDefinition", { file, line, column }, (r) => {
768
+ const o = r;
769
+ return typeof o.found === "boolean" ? r : null;
770
+ }, undefined, signal);
771
+ }
772
+ // handler returns { found: true, locations } | null — validatedRequest
773
+ async goToDeclaration(file, line, column, signal) {
774
+ return this.validatedRequest("extension/goToDeclaration", { file, line, column }, (r) => {
775
+ const o = r;
776
+ return typeof o.found === "boolean" ? r : null;
777
+ }, undefined, signal);
778
+ }
779
+ // handler returns { contents: string[], range? } | null — validated
780
+ async getHover(file, line, column, signal) {
781
+ return this.validatedRequest("extension/getHover", { file, line, column }, (r) => {
782
+ const o = r;
783
+ return Array.isArray(o.contents) ? r : null;
784
+ }, undefined, signal);
785
+ }
786
+ // handler returns { actions: [{title, kind?, isPreferred}] } — validated
787
+ async getCodeActions(file, startLine, startColumn, endLine, endColumn, signal) {
788
+ return this.validatedRequest("extension/getCodeActions", { file, startLine, startColumn, endLine, endColumn }, (r) => {
789
+ const o = r;
790
+ return Array.isArray(o.actions) ? r : null;
791
+ }, undefined, signal);
792
+ }
793
+ // handler returns { applied: boolean, title?, command?, error?, available? } — validatedRequest
794
+ async applyCodeAction(file, startLine, startColumn, endLine, endColumn, actionTitle, signal) {
795
+ return this.validatedRequest("extension/applyCodeAction", { file, startLine, startColumn, endLine, endColumn, actionTitle }, (r) => {
796
+ const o = r;
797
+ return typeof o.applied === "boolean" ? r : null;
798
+ }, undefined, signal);
799
+ }
800
+ // handler returns { title, changes, ... } | { error } — tryRequest (error-obj on failure)
801
+ async previewCodeAction(file, startLine, startColumn, endLine, endColumn, actionTitle, signal) {
802
+ return this.tryRequest("extension/previewCodeAction", { file, startLine, startColumn, endLine, endColumn, actionTitle }, 15_000, signal);
803
+ }
804
+ // handler returns { success, newName?, affectedFiles?, totalEdits?, error? } — rich contract, caller needs success field
805
+ async renameSymbol(file, line, column, newName, signal) {
806
+ // Rename can be slow on large projects
807
+ return this.validatedRequest("extension/renameSymbol", { file, line, column, newName }, (r) => {
808
+ const o = r;
809
+ return typeof o.success === "boolean" ? r : null;
810
+ }, 15_000, signal);
811
+ }
812
+ async searchSymbols(query, maxResults, signal) {
813
+ return this.requestOrNull("extension/searchSymbols", { query, maxResults }, undefined, signal);
814
+ }
815
+ // handler returns { canRename: boolean, range?, placeholder?, reason? } — validatedRequest
816
+ async prepareRename(file, line, column, signal) {
817
+ return this.validatedRequest("extension/prepareRename", { file, line, column }, (r) => {
818
+ const o = r;
819
+ return typeof o.canRename === "boolean" ? r : null;
820
+ }, undefined, signal);
821
+ }
822
+ // handler returns { formatted: boolean, editCount? } or { formatted: false, reason? } — validated
823
+ async formatRange(file, startLine, endLine, signal) {
824
+ return this.validatedRequest("extension/formatRange", { file, startLine, endLine }, (r) => {
825
+ const o = r;
826
+ return typeof o.formatted === "boolean" ? r : null;
827
+ }, undefined, signal);
828
+ }
829
+ // handler returns { activeSignature, activeParameter, signatures } | null — tryRequest
830
+ async signatureHelp(file, line, column, signal) {
831
+ return this.tryRequest("extension/signatureHelp", { file, line, column }, undefined, signal);
832
+ }
833
+ // handler returns { ranges: [...] } — validatedRequest
834
+ async foldingRanges(file, signal) {
835
+ return this.validatedRequest("extension/foldingRanges", { file }, (r) => {
836
+ const o = r;
837
+ return Array.isArray(o.ranges) ? r : null;
838
+ }, undefined, signal);
839
+ }
840
+ // handler returns { ranges: [...] } — validatedRequest
841
+ async selectionRanges(file, line, column, signal) {
842
+ return this.validatedRequest("extension/selectionRanges", { file, line, column }, (r) => {
843
+ const o = r;
844
+ return Array.isArray(o.ranges) ? r : null;
845
+ }, undefined, signal);
846
+ }
847
+ // handler returns { watching: true, id, pattern } — tryRequest
848
+ async watchFiles(id, pattern) {
849
+ return this.tryRequest("extension/watchFiles", { id, pattern });
850
+ }
851
+ // handler returns { unwatched: true, id } — tryRequest
852
+ async unwatchFiles(id) {
853
+ return this.tryRequest("extension/unwatchFiles", { id });
854
+ }
855
+ async captureScreenshot() {
856
+ return this.validatedRequest("extension/captureScreenshot", {}, (r) => {
857
+ const o = r;
858
+ return typeof o.base64 === "string" && typeof o.mimeType === "string"
859
+ ? r
860
+ : null;
861
+ });
862
+ }
863
+ // --- Terminal Features ---
864
+ // handler returns { terminals, count, outputCaptureAvailable } — validatedRequest
865
+ async listTerminals() {
866
+ return this.validatedRequest("extension/listTerminals", undefined, (r) => {
867
+ const o = r;
868
+ return Array.isArray(o.terminals) ? r : null;
869
+ });
870
+ }
871
+ // handler returns { available: boolean, ... } — validatedRequest
872
+ async getTerminalOutput(name, index, lines) {
873
+ return this.validatedRequest("extension/getTerminalOutput", { name, index, lines }, (r) => {
874
+ const o = r;
875
+ return typeof o.available === "boolean" ? r : null;
876
+ });
877
+ }
878
+ // handler returns { success: boolean, terminalName? } — validatedRequest
879
+ async disposeTerminal(name, index) {
880
+ return this.validatedRequest("extension/disposeTerminal", { name, index }, (r) => {
881
+ const o = r;
882
+ return typeof o.success === "boolean" ? r : null;
883
+ });
884
+ }
885
+ // --- File Operations ---
886
+ // handler returns { success: boolean, filePath, isDirectory, created } | { success: false, error } — validatedRequest
887
+ async createFile(filePath, content, isDirectory, overwrite, openAfterCreate) {
888
+ return this.validatedRequest("extension/createFile", { filePath, content, isDirectory, overwrite, openAfterCreate }, (r) => {
889
+ const o = r;
890
+ return typeof o.success === "boolean" ? r : null;
891
+ });
892
+ }
893
+ // handler returns { success: boolean, filePath, deleted } | { success: false, error } — validatedRequest
894
+ async deleteFile(filePath, recursive, useTrash) {
895
+ return this.validatedRequest("extension/deleteFile", { filePath, recursive, useTrash }, (r) => {
896
+ const o = r;
897
+ return typeof o.success === "boolean" ? r : null;
898
+ });
899
+ }
900
+ // handler returns { success: boolean, oldPath, newPath, renamed } | { success: false, error } — validatedRequest
901
+ async renameFile(oldPath, newPath, overwrite) {
902
+ return this.validatedRequest("extension/renameFile", { oldPath, newPath, overwrite }, (r) => {
903
+ const o = r;
904
+ return typeof o.success === "boolean" ? r : null;
905
+ });
906
+ }
907
+ // --- Text Editing ---
908
+ // handler returns { success: boolean, editCount, saved } | { success: false, error } — validatedRequest
909
+ async editText(filePath, edits, save) {
910
+ return this.validatedRequest("extension/editText", { filePath, edits, save }, (r) => {
911
+ const o = r;
912
+ return typeof o.success === "boolean" ? r : null;
913
+ });
914
+ }
915
+ // handler returns { success: boolean, saved, source } | { success: false, error } — validatedRequest
916
+ async replaceBlock(filePath, oldContent, newContent, save) {
917
+ return this.validatedRequest("extension/replaceBlock", { filePath, oldContent, newContent, save }, (r) => {
918
+ const o = r;
919
+ return typeof o.success === "boolean" ? r : null;
920
+ });
921
+ }
922
+ // handler returns { symbols: FlatSymbol[], count } — validated
923
+ async getDocumentSymbols(file, signal) {
924
+ return this.validatedRequest("extension/getDocumentSymbols", { file }, (r) => {
925
+ const o = r;
926
+ return Array.isArray(o.symbols) ? r : null;
927
+ }, undefined, signal);
928
+ }
929
+ // handler returns { symbol, incoming?, outgoing? } | null — validated
930
+ async getCallHierarchy(file, line, column, direction, maxResults, signal) {
931
+ return this.validatedRequest("extension/getCallHierarchy", { file, line, column, direction, maxResults }, (r) => {
932
+ const o = r;
933
+ return typeof o.symbol === "object" && o.symbol !== null ? r : null;
934
+ }, 15_000, signal);
935
+ }
936
+ // --- Code Actions (format, fix, organize) ---
937
+ async formatDocument(file) {
938
+ // Extension returns { error: "..." } when the format command fails.
939
+ // tryRequest unwraps to null so consumers' `!== null` check falls through
940
+ // to their CLI formatter fallback instead of reporting false success.
941
+ return this.tryRequest("extension/formatDocument", { file }, 15_000);
942
+ }
943
+ async fixAllLintErrors(file) {
944
+ // Extension returns { error: "..." } on command failure. tryRequest
945
+ // unwraps to null so consumers fall through to their CLI fallback.
946
+ return this.tryRequest("extension/fixAllLintErrors", { file }, 15_000);
947
+ }
948
+ // handler returns { success: true, actionsApplied } | { error } — tryRequest (error-obj on failure)
949
+ async organizeImports(file) {
950
+ return this.tryRequest("extension/organizeImports", { file }, 15_000);
951
+ }
952
+ // --- Terminal Control ---
953
+ // handler returns { success: true, name, index } — validatedRequest
954
+ async createTerminal(name, cwd, env, show) {
955
+ return this.validatedRequest("extension/createTerminal", { name, cwd, env, show }, (r) => {
956
+ const o = r;
957
+ return typeof o.success === "boolean" ? r : null;
958
+ });
959
+ }
960
+ // handler returns { success: boolean, terminalName? } | { success: false, error, availableTerminals } — validatedRequest
961
+ async sendTerminalCommand(text, name, index, addNewline) {
962
+ return this.validatedRequest("extension/sendTerminalCommand", { text, name, index, addNewline }, (r) => {
963
+ const o = r;
964
+ return typeof o.success === "boolean" ? r : null;
965
+ });
966
+ }
967
+ // handler returns { matched: boolean, matchedLine?, elapsed?, terminalName, timedOut?, error? } — validatedRequest
968
+ async waitForTerminalOutput(pattern, name, index, timeoutMs) {
969
+ const requestTimeout = (timeoutMs ?? 30_000) + 5_000;
970
+ return this.validatedRequest("extension/waitForTerminalOutput", { pattern, name, index, timeoutMs }, (r) => {
971
+ const o = r;
972
+ return typeof o.matched === "boolean" ? r : null;
973
+ }, requestTimeout);
974
+ }
975
+ // handler returns { success: boolean, exitCode?, output, terminalName, timedOut?, error? } — validatedRequest
976
+ async executeInTerminal(command, name, index, timeoutMs, show) {
977
+ // Add 5s overhead beyond the command timeout so the bridge doesn't cut the extension off early
978
+ const requestTimeout = (timeoutMs ?? 30_000) + 5_000;
979
+ return this.validatedRequest("extension/executeInTerminal", { command, name, index, timeoutMs, show }, (r) => {
980
+ const o = r;
981
+ return typeof o.success === "boolean" ? r : null;
982
+ }, requestTimeout);
983
+ }
984
+ // --- Debug ---
985
+ async getDebugState() {
986
+ return this.validatedRequest("extension/getDebugState", {}, (r) => typeof r.hasActiveSession === "boolean"
987
+ ? r
988
+ : null);
989
+ }
990
+ // handler returns { result, type?, variablesReference? } — validatedRequest
991
+ async evaluateInDebugger(expression, frameId, context, signal) {
992
+ return this.validatedRequest("extension/evaluateInDebugger", { expression, frameId, context }, (r) => {
993
+ const o = r;
994
+ return "result" in o ? r : null;
995
+ }, 15_000, signal);
996
+ }
997
+ // handler returns { set: number, file } — validatedRequest
998
+ async setDebugBreakpoints(file, breakpoints, signal) {
999
+ return this.validatedRequest("extension/setDebugBreakpoints", { file, breakpoints }, (r) => {
1000
+ const o = r;
1001
+ return typeof o.set === "number" ? r : null;
1002
+ }, undefined, signal);
1003
+ }
1004
+ // handler returns { started: boolean } — validatedRequest
1005
+ async startDebugging(configName, signal) {
1006
+ return this.validatedRequest("extension/startDebugging", { configName }, (r) => {
1007
+ const o = r;
1008
+ return typeof o.started === "boolean" ? r : null;
1009
+ }, 15_000, signal);
1010
+ }
1011
+ // handler returns { stopped: boolean, message? } | { stopped: false, error } — validatedRequest
1012
+ async stopDebugging(signal) {
1013
+ return this.validatedRequest("extension/stopDebugging", undefined, (r) => {
1014
+ const o = r;
1015
+ return typeof o.stopped === "boolean" ? r : null;
1016
+ }, undefined, signal);
1017
+ }
1018
+ // --- Decorations ---
1019
+ // handler returns { applied: number, editorsUpdated: number } — validatedRequest
1020
+ async setDecorations(id, file, decorations) {
1021
+ return this.validatedRequest("extension/setDecorations", { id, file, decorations }, (r) => {
1022
+ const o = r;
1023
+ return typeof o.applied === "number" ? r : null;
1024
+ });
1025
+ }
1026
+ // handler returns { cleared: number } — validatedRequest
1027
+ async clearDecorations(id) {
1028
+ return this.validatedRequest("extension/clearDecorations", { id }, (r) => {
1029
+ const o = r;
1030
+ return typeof o.cleared === "number" ? r : null;
1031
+ });
1032
+ }
1033
+ // --- VS Code Commands ---
1034
+ // handler returns { result, _warning? } — validatedRequest
1035
+ async executeVSCodeCommand(command, args) {
1036
+ return this.validatedRequest("extension/executeVSCodeCommand", { command, args }, (r) => {
1037
+ const o = r;
1038
+ return "result" in o ? r : null;
1039
+ });
1040
+ }
1041
+ async listVSCodeCommands(filter) {
1042
+ return this.validatedRequest("extension/listVSCodeCommands", { filter }, (r) => {
1043
+ const o = r;
1044
+ return Array.isArray(o.commands)
1045
+ ? r
1046
+ : null;
1047
+ });
1048
+ }
1049
+ // --- Workspace Settings ---
1050
+ // handler returns { section, settings } — validatedRequest
1051
+ async getWorkspaceSettings(section, target) {
1052
+ return this.validatedRequest("extension/getWorkspaceSettings", { section, target }, (r) => {
1053
+ const o = r;
1054
+ return typeof o.settings === "object" && o.settings !== null ? r : null;
1055
+ });
1056
+ }
1057
+ // handler returns { set: true, key, target } — validatedRequest
1058
+ async setWorkspaceSetting(key, value, target) {
1059
+ return this.validatedRequest("extension/setWorkspaceSetting", { key, value, target }, (r) => {
1060
+ const o = r;
1061
+ return typeof o.set === "boolean" ? r : null;
1062
+ });
1063
+ }
1064
+ // --- Clipboard ---
1065
+ // handler returns { text, byteLength, truncated } — validatedRequest
1066
+ async readClipboard() {
1067
+ return this.validatedRequest("extension/readClipboard", undefined, (r) => {
1068
+ const o = r;
1069
+ return typeof o.text === "string" ? r : null;
1070
+ });
1071
+ }
1072
+ // handler returns { written: boolean, byteLength? } | { written: false, error } — validatedRequest
1073
+ async writeClipboard(text) {
1074
+ return this.validatedRequest("extension/writeClipboard", { text }, (r) => {
1075
+ const o = r;
1076
+ return typeof o.written === "boolean" ? r : null;
1077
+ });
1078
+ }
1079
+ // --- Inlay Hints ---
1080
+ // handler returns { hints: [...], count, capped? } — validated
1081
+ async getInlayHints(file, startLine, endLine) {
1082
+ return this.validatedRequest("extension/getInlayHints", { file, startLine, endLine }, (r) => {
1083
+ const o = r;
1084
+ return Array.isArray(o.hints) ? r : null;
1085
+ });
1086
+ }
1087
+ // --- Type Hierarchy ---
1088
+ // handler returns { found: boolean, root?, supertypes, subtypes, direction } — validated
1089
+ async getTypeHierarchy(file, line, column, direction, maxResults, signal) {
1090
+ return this.validatedRequest("extension/getTypeHierarchy", { file, line, column, direction, maxResults }, (r) => {
1091
+ const o = r;
1092
+ return typeof o.found === "boolean" ? r : null;
1093
+ }, 15_000, signal);
1094
+ }
1095
+ // --- Code Lens ---
1096
+ // handler returns { lenses: [...], count } — validated
1097
+ async getCodeLens(file, signal) {
1098
+ return this.validatedRequest("extension/getCodeLens", { file }, (r) => {
1099
+ const o = r;
1100
+ return Array.isArray(o.lenses) ? r : null;
1101
+ }, undefined, signal);
1102
+ }
1103
+ // --- Semantic Tokens ---
1104
+ // handler returns { tokens: [...], count, capped, legend: { tokenTypes, tokenModifiers } } — validated
1105
+ async getSemanticTokens(file, startLine, endLine, maxTokens, signal) {
1106
+ return this.validatedRequest("extension/getSemanticTokens", { file, startLine, endLine, maxTokens }, (r) => {
1107
+ const o = r;
1108
+ return Array.isArray(o.tokens) ? r : null;
1109
+ }, 15_000, signal);
1110
+ }
1111
+ // --- Document Links ---
1112
+ async getDocumentLinks(file, signal) {
1113
+ return this.requestOrNull("extension/getDocumentLinks", { file }, undefined, signal);
1114
+ }
1115
+ // --- Tasks ---
1116
+ // handler returns { tasks: [...] } — validatedRequest
1117
+ async listTasks(type) {
1118
+ return this.validatedRequest("extension/listTasks", type ? { type } : undefined, (r) => {
1119
+ const o = r;
1120
+ return Array.isArray(o.tasks) ? r : null;
1121
+ }, 15_000);
1122
+ }
1123
+ // handler returns { success: boolean, name, exitCode? } | { success: false, error, timedOut? } — validatedRequest
1124
+ async runTask(name, type, timeoutMs) {
1125
+ const requestTimeout = (timeoutMs ?? 60_000) + 5_000;
1126
+ return this.validatedRequest("extension/runTask", { name, type, timeoutMs }, (r) => {
1127
+ const o = r;
1128
+ return typeof o.success === "boolean" ? r : null;
1129
+ }, requestTimeout);
1130
+ }
1131
+ // --- Workspace Folders ---
1132
+ async getWorkspaceFolders() {
1133
+ // Extension returns { folders: [...], count: N } — unwrap to array.
1134
+ // Back-compat with legacy extension versions that returned the array directly.
1135
+ return this.validatedRequest("extension/getWorkspaceFolders", undefined, (raw) => {
1136
+ if (typeof raw === "object" && raw !== null && "folders" in raw) {
1137
+ const folders = raw.folders;
1138
+ return Array.isArray(folders) ? folders : null;
1139
+ }
1140
+ return Array.isArray(raw) ? raw : null;
1141
+ });
1142
+ }
1143
+ // --- Notebook ---
1144
+ // handler not yet implemented in extension — bare requestOrNull returns null until implemented
1145
+ async getNotebookCells(file) {
1146
+ return this.requestOrNull("extension/getNotebookCells", { file });
1147
+ }
1148
+ // handler not yet implemented in extension — bare requestOrNull returns null until implemented
1149
+ async runNotebookCell(file, cellIndex, timeoutMs) {
1150
+ const requestTimeout = (timeoutMs ?? 30_000) + 5_000;
1151
+ return this.requestOrNull("extension/runNotebookCell", { file, cellIndex, timeoutMs }, requestTimeout);
1152
+ }
1153
+ // handler not yet implemented in extension — bare requestOrNull returns null until implemented
1154
+ async getNotebookOutput(file, cellIndex) {
1155
+ return this.requestOrNull("extension/getNotebookOutput", {
1156
+ file,
1157
+ cellIndex,
1158
+ });
1159
+ }
1160
+ // --- Diagnostics Helpers ---
1161
+ getCachedDiagnostics(file) {
1162
+ const now = Date.now();
1163
+ const ttl = ExtensionClient.DIAGNOSTICS_TTL_MS;
1164
+ if (file) {
1165
+ const updatedAt = this.latestDiagnosticsUpdatedAt.get(file);
1166
+ if (updatedAt !== undefined && now - updatedAt > ttl) {
1167
+ this.latestDiagnostics.delete(file);
1168
+ this.latestDiagnosticsUpdatedAt.delete(file);
1169
+ return [];
1170
+ }
1171
+ return this.latestDiagnostics.get(file) ?? [];
1172
+ }
1173
+ // Evict stale entries across entire map on a full scan
1174
+ for (const [f, updatedAt] of this.latestDiagnosticsUpdatedAt) {
1175
+ if (now - updatedAt > ttl) {
1176
+ this.latestDiagnostics.delete(f);
1177
+ this.latestDiagnosticsUpdatedAt.delete(f);
1178
+ }
1179
+ }
1180
+ return Array.from(this.latestDiagnostics.values()).flat();
1181
+ }
1182
+ addDiagnosticsListener(listener) {
1183
+ this.diagnosticsListeners.add(listener);
1184
+ return () => this.diagnosticsListeners.delete(listener);
1185
+ }
1186
+ /** Notify the extension about Claude Code connection state changes */
1187
+ notifyClaudeConnectionState(connected, stats) {
1188
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
1189
+ return;
1190
+ void safeSend(this.ws, JSON.stringify({
1191
+ jsonrpc: "2.0",
1192
+ method: "bridge/claudeConnectionChanged",
1193
+ params: { connected, ...(stats !== undefined && { stats }) },
1194
+ }), this.logger);
1195
+ }
1196
+ /** Push a Claude task output chunk to the VS Code output channel. Best-effort. */
1197
+ notifyTaskOutput(taskId, chunk) {
1198
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
1199
+ return;
1200
+ void safeSend(this.ws, JSON.stringify({
1201
+ jsonrpc: "2.0",
1202
+ method: "bridge/claudeTaskOutput",
1203
+ params: { taskId, chunk },
1204
+ }), this.logger);
1205
+ }
1206
+ /** Push a Claude task completion status to the VS Code output channel. Best-effort. */
1207
+ notifyTaskDone(taskId, status) {
1208
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
1209
+ return;
1210
+ void safeSend(this.ws, JSON.stringify({
1211
+ jsonrpc: "2.0",
1212
+ method: "bridge/claudeTaskOutput",
1213
+ params: { taskId, done: true, status },
1214
+ }), this.logger);
1215
+ }
1216
+ getCircuitBreakerState() {
1217
+ const now = Date.now();
1218
+ // Prune stale entries so callers always see the active window count,
1219
+ // not the raw array which may include expired timestamps.
1220
+ const activeFailures = this.extensionFailureTimes.filter((t) => now - t < ExtensionClient.CIRCUIT_WINDOW_MS);
1221
+ return {
1222
+ suspended: now < this.extensionSuspendedUntil,
1223
+ suspendedUntil: this.extensionSuspendedUntil,
1224
+ failures: activeFailures.length,
1225
+ openCount: this._circuitOpenCount,
1226
+ lastOpenedAt: this._lastCircuitOpenedAt?.toISOString() ?? null,
1227
+ };
1228
+ }
1229
+ isConnected() {
1230
+ return this.connected;
1231
+ }
1232
+ /**
1233
+ * Push a server-initiated notification to the connected extension.
1234
+ * Non-fatal: silently drops if extension is not connected or send fails.
1235
+ */
1236
+ sendPush(method, params) {
1237
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
1238
+ return;
1239
+ void safeSend(this.ws, JSON.stringify({ jsonrpc: "2.0", method, params }), this.logger);
1240
+ }
1241
+ disconnect() {
1242
+ if (this.ws) {
1243
+ // Remove listeners before closing so the async "close" event does not
1244
+ // re-trigger handleDisconnect → onExtensionDisconnected during shutdown.
1245
+ this.ws.removeAllListeners();
1246
+ this.ws.close(1000, "Bridge shutting down");
1247
+ this.ws = null;
1248
+ }
1249
+ this.connected = false;
1250
+ this.rejectAllPending("Bridge shutting down");
1251
+ }
1252
+ }
1253
+ //# sourceMappingURL=extensionClient.js.map