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.
- package/LICENSE +21 -0
- package/README.bridge.md +352 -0
- package/README.md +72 -0
- package/deploy/README.md +172 -0
- package/deploy/bootstrap-new-vps.sh +364 -0
- package/deploy/claude-ide-bridge.service.template +67 -0
- package/deploy/claude-ide-bridge@.service +31 -0
- package/deploy/ecosystem.config.js.example +36 -0
- package/deploy/install-vps-service.sh +240 -0
- package/deploy/nginx-claude-bridge.conf.template +129 -0
- package/dist/activityLog.d.ts +112 -0
- package/dist/activityLog.js +399 -0
- package/dist/activityLog.js.map +1 -0
- package/dist/activityTypes.d.ts +28 -0
- package/dist/activityTypes.js +9 -0
- package/dist/activityTypes.js.map +1 -0
- package/dist/adapters/base.d.ts +78 -0
- package/dist/adapters/base.js +14 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude.d.ts +18 -0
- package/dist/adapters/claude.js +276 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/gemini.d.ts +17 -0
- package/dist/adapters/gemini.js +218 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/grok.d.ts +7 -0
- package/dist/adapters/grok.js +21 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +37 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/local.d.ts +7 -0
- package/dist/adapters/local.js +22 -0
- package/dist/adapters/local.js.map +1 -0
- package/dist/adapters/openai.d.ts +22 -0
- package/dist/adapters/openai.js +284 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/adapters/sse.d.ts +13 -0
- package/dist/adapters/sse.js +58 -0
- package/dist/adapters/sse.js.map +1 -0
- package/dist/analyticsAggregator.d.ts +28 -0
- package/dist/analyticsAggregator.js +133 -0
- package/dist/analyticsAggregator.js.map +1 -0
- package/dist/analyticsPrefs.d.ts +9 -0
- package/dist/analyticsPrefs.js +50 -0
- package/dist/analyticsPrefs.js.map +1 -0
- package/dist/analyticsSend.d.ts +12 -0
- package/dist/analyticsSend.js +34 -0
- package/dist/analyticsSend.js.map +1 -0
- package/dist/approvalHttp.d.ts +46 -0
- package/dist/approvalHttp.js +370 -0
- package/dist/approvalHttp.js.map +1 -0
- package/dist/approvalQueue.d.ts +49 -0
- package/dist/approvalQueue.js +84 -0
- package/dist/approvalQueue.js.map +1 -0
- package/dist/automation.d.ts +675 -0
- package/dist/automation.js +1038 -0
- package/dist/automation.js.map +1 -0
- package/dist/bridge.d.ts +85 -0
- package/dist/bridge.js +1535 -0
- package/dist/bridge.js.map +1 -0
- package/dist/bridgeLockDiscovery.d.ts +11 -0
- package/dist/bridgeLockDiscovery.js +49 -0
- package/dist/bridgeLockDiscovery.js.map +1 -0
- package/dist/bridgeToken.d.ts +22 -0
- package/dist/bridgeToken.js +114 -0
- package/dist/bridgeToken.js.map +1 -0
- package/dist/bridgeToolsRules.d.ts +20 -0
- package/dist/bridgeToolsRules.js +79 -0
- package/dist/bridgeToolsRules.js.map +1 -0
- package/dist/ccPermissions.d.ts +59 -0
- package/dist/ccPermissions.js +163 -0
- package/dist/ccPermissions.js.map +1 -0
- package/dist/claudeDriver.d.ts +129 -0
- package/dist/claudeDriver.js +459 -0
- package/dist/claudeDriver.js.map +1 -0
- package/dist/claudeMdPatch.d.ts +29 -0
- package/dist/claudeMdPatch.js +164 -0
- package/dist/claudeMdPatch.js.map +1 -0
- package/dist/claudeOrchestrator.d.ts +171 -0
- package/dist/claudeOrchestrator.js +591 -0
- package/dist/claudeOrchestrator.js.map +1 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +158 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/marketplace.d.ts +11 -0
- package/dist/commands/marketplace.js +120 -0
- package/dist/commands/marketplace.js.map +1 -0
- package/dist/commands/patchworkInit.d.ts +14 -0
- package/dist/commands/patchworkInit.js +155 -0
- package/dist/commands/patchworkInit.js.map +1 -0
- package/dist/commands/task.d.ts +14 -0
- package/dist/commands/task.js +289 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/tokenEfficiency.d.ts +9 -0
- package/dist/commands/tokenEfficiency.js +211 -0
- package/dist/commands/tokenEfficiency.js.map +1 -0
- package/dist/commands/tools.d.ts +28 -0
- package/dist/commands/tools.js +326 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commitIssueLinkLog.d.ts +77 -0
- package/dist/commitIssueLinkLog.js +142 -0
- package/dist/commitIssueLinkLog.js.map +1 -0
- package/dist/companions/registry.d.ts +12 -0
- package/dist/companions/registry.js +71 -0
- package/dist/companions/registry.js.map +1 -0
- package/dist/config.d.ts +105 -0
- package/dist/config.js +720 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +16 -0
- package/dist/crypto.js +34 -0
- package/dist/crypto.js.map +1 -0
- package/dist/dashboard.d.ts +12 -0
- package/dist/dashboard.js +149 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/decisionTraceLog.d.ts +77 -0
- package/dist/decisionTraceLog.js +147 -0
- package/dist/decisionTraceLog.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +34 -0
- package/dist/errors.js.map +1 -0
- package/dist/extensionClient.d.ts +279 -0
- package/dist/extensionClient.js +1253 -0
- package/dist/extensionClient.js.map +1 -0
- package/dist/fileLock.d.ts +36 -0
- package/dist/fileLock.js +121 -0
- package/dist/fileLock.js.map +1 -0
- package/dist/fp/activityAnalytics.d.ts +39 -0
- package/dist/fp/activityAnalytics.js +111 -0
- package/dist/fp/activityAnalytics.js.map +1 -0
- package/dist/fp/async.d.ts +48 -0
- package/dist/fp/async.js +60 -0
- package/dist/fp/async.js.map +1 -0
- package/dist/fp/automationInterpreter.d.ts +37 -0
- package/dist/fp/automationInterpreter.js +523 -0
- package/dist/fp/automationInterpreter.js.map +1 -0
- package/dist/fp/automationProgram.d.ts +89 -0
- package/dist/fp/automationProgram.js +29 -0
- package/dist/fp/automationProgram.js.map +1 -0
- package/dist/fp/automationState.d.ts +135 -0
- package/dist/fp/automationState.js +206 -0
- package/dist/fp/automationState.js.map +1 -0
- package/dist/fp/automationUtils.d.ts +31 -0
- package/dist/fp/automationUtils.js +61 -0
- package/dist/fp/automationUtils.js.map +1 -0
- package/dist/fp/brandedTypes.d.ts +32 -0
- package/dist/fp/brandedTypes.js +41 -0
- package/dist/fp/brandedTypes.js.map +1 -0
- package/dist/fp/commandDescription.d.ts +18 -0
- package/dist/fp/commandDescription.js +125 -0
- package/dist/fp/commandDescription.js.map +1 -0
- package/dist/fp/extensionSnapshot.d.ts +10 -0
- package/dist/fp/extensionSnapshot.js +14 -0
- package/dist/fp/extensionSnapshot.js.map +1 -0
- package/dist/fp/index.d.ts +8 -0
- package/dist/fp/index.js +9 -0
- package/dist/fp/index.js.map +1 -0
- package/dist/fp/interpreterContext.d.ts +69 -0
- package/dist/fp/interpreterContext.js +56 -0
- package/dist/fp/interpreterContext.js.map +1 -0
- package/dist/fp/policyParser.d.ts +16 -0
- package/dist/fp/policyParser.js +334 -0
- package/dist/fp/policyParser.js.map +1 -0
- package/dist/fp/result.d.ts +38 -0
- package/dist/fp/result.js +57 -0
- package/dist/fp/result.js.map +1 -0
- package/dist/fp/tokenBucket.d.ts +27 -0
- package/dist/fp/tokenBucket.js +36 -0
- package/dist/fp/tokenBucket.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1465 -0
- package/dist/index.js.map +1 -0
- package/dist/instructionsUtils.d.ts +17 -0
- package/dist/instructionsUtils.js +38 -0
- package/dist/instructionsUtils.js.map +1 -0
- package/dist/lockfile.d.ts +16 -0
- package/dist/lockfile.js +172 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +68 -0
- package/dist/logger.js.map +1 -0
- package/dist/oauth.d.ts +105 -0
- package/dist/oauth.js +880 -0
- package/dist/oauth.js.map +1 -0
- package/dist/orchestrator/childBridgeClient.d.ts +33 -0
- package/dist/orchestrator/childBridgeClient.js +321 -0
- package/dist/orchestrator/childBridgeClient.js.map +1 -0
- package/dist/orchestrator/childBridgeRegistry.d.ts +67 -0
- package/dist/orchestrator/childBridgeRegistry.js +297 -0
- package/dist/orchestrator/childBridgeRegistry.js.map +1 -0
- package/dist/orchestrator/index.d.ts +3 -0
- package/dist/orchestrator/index.js +3 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/orchestratorBridge.d.ts +32 -0
- package/dist/orchestrator/orchestratorBridge.js +412 -0
- package/dist/orchestrator/orchestratorBridge.js.map +1 -0
- package/dist/orchestrator/orchestratorConfig.d.ts +11 -0
- package/dist/orchestrator/orchestratorConfig.js +85 -0
- package/dist/orchestrator/orchestratorConfig.js.map +1 -0
- package/dist/orchestrator/orchestratorTools.d.ts +16 -0
- package/dist/orchestrator/orchestratorTools.js +272 -0
- package/dist/orchestrator/orchestratorTools.js.map +1 -0
- package/dist/patchworkCli.d.ts +15 -0
- package/dist/patchworkCli.js +41 -0
- package/dist/patchworkCli.js.map +1 -0
- package/dist/patchworkConfig.d.ts +28 -0
- package/dist/patchworkConfig.js +30 -0
- package/dist/patchworkConfig.js.map +1 -0
- package/dist/plugin.d.ts +106 -0
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -0
- package/dist/pluginLoader.d.ts +44 -0
- package/dist/pluginLoader.js +357 -0
- package/dist/pluginLoader.js.map +1 -0
- package/dist/pluginWatcher.d.ts +24 -0
- package/dist/pluginWatcher.js +139 -0
- package/dist/pluginWatcher.js.map +1 -0
- package/dist/preToolUseHook.d.ts +10 -0
- package/dist/preToolUseHook.js +57 -0
- package/dist/preToolUseHook.js.map +1 -0
- package/dist/probe.d.ts +35 -0
- package/dist/probe.js +143 -0
- package/dist/probe.js.map +1 -0
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +1680 -0
- package/dist/prompts.js.map +1 -0
- package/dist/quickTaskPresets.d.ts +64 -0
- package/dist/quickTaskPresets.js +156 -0
- package/dist/quickTaskPresets.js.map +1 -0
- package/dist/recipes/compiler.d.ts +44 -0
- package/dist/recipes/compiler.js +140 -0
- package/dist/recipes/compiler.js.map +1 -0
- package/dist/recipes/installer.d.ts +25 -0
- package/dist/recipes/installer.js +62 -0
- package/dist/recipes/installer.js.map +1 -0
- package/dist/recipes/parser.d.ts +18 -0
- package/dist/recipes/parser.js +160 -0
- package/dist/recipes/parser.js.map +1 -0
- package/dist/recipes/scheduler.d.ts +45 -0
- package/dist/recipes/scheduler.js +110 -0
- package/dist/recipes/scheduler.js.map +1 -0
- package/dist/recipes/schema.d.ts +71 -0
- package/dist/recipes/schema.js +11 -0
- package/dist/recipes/schema.js.map +1 -0
- package/dist/recipesHttp.d.ts +63 -0
- package/dist/recipesHttp.js +183 -0
- package/dist/recipesHttp.js.map +1 -0
- package/dist/resources.d.ts +33 -0
- package/dist/resources.js +266 -0
- package/dist/resources.js.map +1 -0
- package/dist/riskTier.d.ts +40 -0
- package/dist/riskTier.js +142 -0
- package/dist/riskTier.js.map +1 -0
- package/dist/runLog.d.ts +90 -0
- package/dist/runLog.js +143 -0
- package/dist/runLog.js.map +1 -0
- package/dist/server.d.ts +160 -0
- package/dist/server.js +1244 -0
- package/dist/server.js.map +1 -0
- package/dist/sessionCheckpoint.d.ts +37 -0
- package/dist/sessionCheckpoint.js +123 -0
- package/dist/sessionCheckpoint.js.map +1 -0
- package/dist/streamableHttp.d.ts +86 -0
- package/dist/streamableHttp.js +702 -0
- package/dist/streamableHttp.js.map +1 -0
- package/dist/telemetry.d.ts +18 -0
- package/dist/telemetry.js +95 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tools/activityLog.d.ts +140 -0
- package/dist/tools/activityLog.js +204 -0
- package/dist/tools/activityLog.js.map +1 -0
- package/dist/tools/auditDependencies.d.ts +67 -0
- package/dist/tools/auditDependencies.js +298 -0
- package/dist/tools/auditDependencies.js.map +1 -0
- package/dist/tools/batchLsp.d.ts +262 -0
- package/dist/tools/batchLsp.js +328 -0
- package/dist/tools/batchLsp.js.map +1 -0
- package/dist/tools/blame-utils.d.ts +30 -0
- package/dist/tools/blame-utils.js +60 -0
- package/dist/tools/blame-utils.js.map +1 -0
- package/dist/tools/bridgeDoctor.d.ts +78 -0
- package/dist/tools/bridgeDoctor.js +542 -0
- package/dist/tools/bridgeDoctor.js.map +1 -0
- package/dist/tools/bridgeStatus.d.ts +122 -0
- package/dist/tools/bridgeStatus.js +250 -0
- package/dist/tools/bridgeStatus.js.map +1 -0
- package/dist/tools/cancelClaudeTask.d.ts +48 -0
- package/dist/tools/cancelClaudeTask.js +56 -0
- package/dist/tools/cancelClaudeTask.js.map +1 -0
- package/dist/tools/checkDocumentDirty.d.ts +56 -0
- package/dist/tools/checkDocumentDirty.js +74 -0
- package/dist/tools/checkDocumentDirty.js.map +1 -0
- package/dist/tools/clipboard.d.ts +80 -0
- package/dist/tools/clipboard.js +211 -0
- package/dist/tools/clipboard.js.map +1 -0
- package/dist/tools/closeTabs.d.ts +84 -0
- package/dist/tools/closeTabs.js +97 -0
- package/dist/tools/closeTabs.js.map +1 -0
- package/dist/tools/codeLens.d.ts +50 -0
- package/dist/tools/codeLens.js +47 -0
- package/dist/tools/codeLens.js.map +1 -0
- package/dist/tools/contextBundle.d.ts +75 -0
- package/dist/tools/contextBundle.js +218 -0
- package/dist/tools/contextBundle.js.map +1 -0
- package/dist/tools/createIssueFromAIComment.d.ts +75 -0
- package/dist/tools/createIssueFromAIComment.js +119 -0
- package/dist/tools/createIssueFromAIComment.js.map +1 -0
- package/dist/tools/ctxGetTaskContext.d.ts +103 -0
- package/dist/tools/ctxGetTaskContext.js +274 -0
- package/dist/tools/ctxGetTaskContext.js.map +1 -0
- package/dist/tools/ctxQueryTraces.d.ts +142 -0
- package/dist/tools/ctxQueryTraces.js +194 -0
- package/dist/tools/ctxQueryTraces.js.map +1 -0
- package/dist/tools/ctxSaveTrace.d.ts +87 -0
- package/dist/tools/ctxSaveTrace.js +94 -0
- package/dist/tools/ctxSaveTrace.js.map +1 -0
- package/dist/tools/debug.d.ts +206 -0
- package/dist/tools/debug.js +234 -0
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/decorations.d.ts +130 -0
- package/dist/tools/decorations.js +160 -0
- package/dist/tools/decorations.js.map +1 -0
- package/dist/tools/detectUnusedCode.d.ts +78 -0
- package/dist/tools/detectUnusedCode.js +173 -0
- package/dist/tools/detectUnusedCode.js.map +1 -0
- package/dist/tools/documentLinks.d.ts +62 -0
- package/dist/tools/documentLinks.js +55 -0
- package/dist/tools/documentLinks.js.map +1 -0
- package/dist/tools/editText.d.ts +108 -0
- package/dist/tools/editText.js +318 -0
- package/dist/tools/editText.js.map +1 -0
- package/dist/tools/enrichCommit.d.ts +89 -0
- package/dist/tools/enrichCommit.js +201 -0
- package/dist/tools/enrichCommit.js.map +1 -0
- package/dist/tools/enrichStackTrace.d.ts +121 -0
- package/dist/tools/enrichStackTrace.js +194 -0
- package/dist/tools/enrichStackTrace.js.map +1 -0
- package/dist/tools/explainDiagnostic.d.ts +137 -0
- package/dist/tools/explainDiagnostic.js +230 -0
- package/dist/tools/explainDiagnostic.js.map +1 -0
- package/dist/tools/explainSymbol.d.ts +119 -0
- package/dist/tools/explainSymbol.js +177 -0
- package/dist/tools/explainSymbol.js.map +1 -0
- package/dist/tools/fileOperations.d.ts +186 -0
- package/dist/tools/fileOperations.js +330 -0
- package/dist/tools/fileOperations.js.map +1 -0
- package/dist/tools/fileWatcher.d.ts +107 -0
- package/dist/tools/fileWatcher.js +121 -0
- package/dist/tools/fileWatcher.js.map +1 -0
- package/dist/tools/findFiles.d.ts +65 -0
- package/dist/tools/findFiles.js +142 -0
- package/dist/tools/findFiles.js.map +1 -0
- package/dist/tools/findRelatedTests.d.ts +83 -0
- package/dist/tools/findRelatedTests.js +196 -0
- package/dist/tools/findRelatedTests.js.map +1 -0
- package/dist/tools/fixAllLintErrors.d.ts +66 -0
- package/dist/tools/fixAllLintErrors.js +128 -0
- package/dist/tools/fixAllLintErrors.js.map +1 -0
- package/dist/tools/foldingRanges.d.ts +50 -0
- package/dist/tools/foldingRanges.js +51 -0
- package/dist/tools/foldingRanges.js.map +1 -0
- package/dist/tools/formatAndSave.d.ts +57 -0
- package/dist/tools/formatAndSave.js +87 -0
- package/dist/tools/formatAndSave.js.map +1 -0
- package/dist/tools/formatDocument.d.ts +61 -0
- package/dist/tools/formatDocument.js +144 -0
- package/dist/tools/formatDocument.js.map +1 -0
- package/dist/tools/generateAPIDocumentation.d.ts +62 -0
- package/dist/tools/generateAPIDocumentation.js +249 -0
- package/dist/tools/generateAPIDocumentation.js.map +1 -0
- package/dist/tools/generateTests.d.ts +75 -0
- package/dist/tools/generateTests.js +226 -0
- package/dist/tools/generateTests.js.map +1 -0
- package/dist/tools/getAIComments.d.ts +79 -0
- package/dist/tools/getAIComments.js +93 -0
- package/dist/tools/getAIComments.js.map +1 -0
- package/dist/tools/getAnalyticsReport.d.ts +102 -0
- package/dist/tools/getAnalyticsReport.js +137 -0
- package/dist/tools/getAnalyticsReport.js.map +1 -0
- package/dist/tools/getArchitectureContext.d.ts +85 -0
- package/dist/tools/getArchitectureContext.js +135 -0
- package/dist/tools/getArchitectureContext.js.map +1 -0
- package/dist/tools/getBufferContent.d.ts +80 -0
- package/dist/tools/getBufferContent.js +207 -0
- package/dist/tools/getBufferContent.js.map +1 -0
- package/dist/tools/getChangeImpact.d.ts +76 -0
- package/dist/tools/getChangeImpact.js +184 -0
- package/dist/tools/getChangeImpact.js.map +1 -0
- package/dist/tools/getClaudeTaskStatus.d.ts +87 -0
- package/dist/tools/getClaudeTaskStatus.js +89 -0
- package/dist/tools/getClaudeTaskStatus.js.map +1 -0
- package/dist/tools/getCodeCoverage.d.ts +86 -0
- package/dist/tools/getCodeCoverage.js +237 -0
- package/dist/tools/getCodeCoverage.js.map +1 -0
- package/dist/tools/getCommitsForIssue.d.ts +98 -0
- package/dist/tools/getCommitsForIssue.js +106 -0
- package/dist/tools/getCommitsForIssue.js.map +1 -0
- package/dist/tools/getCurrentSelection.d.ts +123 -0
- package/dist/tools/getCurrentSelection.js +113 -0
- package/dist/tools/getCurrentSelection.js.map +1 -0
- package/dist/tools/getDebugState.d.ts +140 -0
- package/dist/tools/getDebugState.js +109 -0
- package/dist/tools/getDebugState.js.map +1 -0
- package/dist/tools/getDependencyTree.d.ts +59 -0
- package/dist/tools/getDependencyTree.js +207 -0
- package/dist/tools/getDependencyTree.js.map +1 -0
- package/dist/tools/getDiagnostics.d.ts +108 -0
- package/dist/tools/getDiagnostics.js +371 -0
- package/dist/tools/getDiagnostics.js.map +1 -0
- package/dist/tools/getDiffFromHandoff.d.ts +89 -0
- package/dist/tools/getDiffFromHandoff.js +163 -0
- package/dist/tools/getDiffFromHandoff.js.map +1 -0
- package/dist/tools/getDocumentSymbols.d.ts +74 -0
- package/dist/tools/getDocumentSymbols.js +177 -0
- package/dist/tools/getDocumentSymbols.js.map +1 -0
- package/dist/tools/getFileTree.d.ts +66 -0
- package/dist/tools/getFileTree.js +131 -0
- package/dist/tools/getFileTree.js.map +1 -0
- package/dist/tools/getGitDiff.d.ts +50 -0
- package/dist/tools/getGitDiff.js +73 -0
- package/dist/tools/getGitDiff.js.map +1 -0
- package/dist/tools/getGitHotspots.d.ts +88 -0
- package/dist/tools/getGitHotspots.js +145 -0
- package/dist/tools/getGitHotspots.js.map +1 -0
- package/dist/tools/getGitLog.d.ts +62 -0
- package/dist/tools/getGitLog.js +87 -0
- package/dist/tools/getGitLog.js.map +1 -0
- package/dist/tools/getGitStatus.d.ts +72 -0
- package/dist/tools/getGitStatus.js +126 -0
- package/dist/tools/getGitStatus.js.map +1 -0
- package/dist/tools/getImportTree.d.ts +73 -0
- package/dist/tools/getImportTree.js +223 -0
- package/dist/tools/getImportTree.js.map +1 -0
- package/dist/tools/getImportedSignatures.d.ts +62 -0
- package/dist/tools/getImportedSignatures.js +255 -0
- package/dist/tools/getImportedSignatures.js.map +1 -0
- package/dist/tools/getOpenEditors.d.ts +62 -0
- package/dist/tools/getOpenEditors.js +126 -0
- package/dist/tools/getOpenEditors.js.map +1 -0
- package/dist/tools/getPRTemplate.d.ts +68 -0
- package/dist/tools/getPRTemplate.js +187 -0
- package/dist/tools/getPRTemplate.js.map +1 -0
- package/dist/tools/getProjectContext.d.ts +114 -0
- package/dist/tools/getProjectContext.js +344 -0
- package/dist/tools/getProjectContext.js.map +1 -0
- package/dist/tools/getProjectInfo.d.ts +51 -0
- package/dist/tools/getProjectInfo.js +325 -0
- package/dist/tools/getProjectInfo.js.map +1 -0
- package/dist/tools/getSecurityAdvisories.d.ts +105 -0
- package/dist/tools/getSecurityAdvisories.js +472 -0
- package/dist/tools/getSecurityAdvisories.js.map +1 -0
- package/dist/tools/getSessionUsage.d.ts +58 -0
- package/dist/tools/getSessionUsage.js +57 -0
- package/dist/tools/getSessionUsage.js.map +1 -0
- package/dist/tools/getSymbolHistory.d.ts +157 -0
- package/dist/tools/getSymbolHistory.js +256 -0
- package/dist/tools/getSymbolHistory.js.map +1 -0
- package/dist/tools/getToolCapabilities.d.ts +69 -0
- package/dist/tools/getToolCapabilities.js +298 -0
- package/dist/tools/getToolCapabilities.js.map +1 -0
- package/dist/tools/getTypeSignature.d.ts +70 -0
- package/dist/tools/getTypeSignature.js +132 -0
- package/dist/tools/getTypeSignature.js.map +1 -0
- package/dist/tools/getWorkspaceFolders.d.ts +58 -0
- package/dist/tools/getWorkspaceFolders.js +69 -0
- package/dist/tools/getWorkspaceFolders.js.map +1 -0
- package/dist/tools/getWorkspaceSettings.d.ts +44 -0
- package/dist/tools/getWorkspaceSettings.js +70 -0
- package/dist/tools/getWorkspaceSettings.js.map +1 -0
- package/dist/tools/git-utils.d.ts +16 -0
- package/dist/tools/git-utils.js +46 -0
- package/dist/tools/git-utils.js.map +1 -0
- package/dist/tools/gitHistory.d.ts +110 -0
- package/dist/tools/gitHistory.js +167 -0
- package/dist/tools/gitHistory.js.map +1 -0
- package/dist/tools/gitWrite.d.ts +612 -0
- package/dist/tools/gitWrite.js +983 -0
- package/dist/tools/gitWrite.js.map +1 -0
- package/dist/tools/github/actions.d.ts +152 -0
- package/dist/tools/github/actions.js +195 -0
- package/dist/tools/github/actions.js.map +1 -0
- package/dist/tools/github/index.d.ts +3 -0
- package/dist/tools/github/index.js +4 -0
- package/dist/tools/github/index.js.map +1 -0
- package/dist/tools/github/issues.d.ts +281 -0
- package/dist/tools/github/issues.js +340 -0
- package/dist/tools/github/issues.js.map +1 -0
- package/dist/tools/github/pr.d.ts +433 -0
- package/dist/tools/github/pr.js +588 -0
- package/dist/tools/github/pr.js.map +1 -0
- package/dist/tools/github/shared.d.ts +4 -0
- package/dist/tools/github/shared.js +12 -0
- package/dist/tools/github/shared.js.map +1 -0
- package/dist/tools/handoffNote.d.ts +106 -0
- package/dist/tools/handoffNote.js +232 -0
- package/dist/tools/handoffNote.js.map +1 -0
- package/dist/tools/headless/lspClient.d.ts +26 -0
- package/dist/tools/headless/lspClient.js +221 -0
- package/dist/tools/headless/lspClient.js.map +1 -0
- package/dist/tools/headless/lspFallback.d.ts +28 -0
- package/dist/tools/headless/lspFallback.js +122 -0
- package/dist/tools/headless/lspFallback.js.map +1 -0
- package/dist/tools/hoverAtCursor.d.ts +54 -0
- package/dist/tools/hoverAtCursor.js +68 -0
- package/dist/tools/hoverAtCursor.js.map +1 -0
- package/dist/tools/httpClient.d.ts +141 -0
- package/dist/tools/httpClient.js +486 -0
- package/dist/tools/httpClient.js.map +1 -0
- package/dist/tools/index.d.ts +49 -0
- package/dist/tools/index.js +672 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/inlayHints.d.ts +81 -0
- package/dist/tools/inlayHints.js +76 -0
- package/dist/tools/inlayHints.js.map +1 -0
- package/dist/tools/issueRefs.d.ts +14 -0
- package/dist/tools/issueRefs.js +27 -0
- package/dist/tools/issueRefs.js.map +1 -0
- package/dist/tools/jumpToFirstError.d.ts +63 -0
- package/dist/tools/jumpToFirstError.js +124 -0
- package/dist/tools/jumpToFirstError.js.map +1 -0
- package/dist/tools/launchQuickTask.d.ts +76 -0
- package/dist/tools/launchQuickTask.js +170 -0
- package/dist/tools/launchQuickTask.js.map +1 -0
- package/dist/tools/linters/biome.d.ts +2 -0
- package/dist/tools/linters/biome.js +44 -0
- package/dist/tools/linters/biome.js.map +1 -0
- package/dist/tools/linters/cargo.d.ts +2 -0
- package/dist/tools/linters/cargo.js +45 -0
- package/dist/tools/linters/cargo.js.map +1 -0
- package/dist/tools/linters/eslint.d.ts +2 -0
- package/dist/tools/linters/eslint.js +59 -0
- package/dist/tools/linters/eslint.js.map +1 -0
- package/dist/tools/linters/govet.d.ts +2 -0
- package/dist/tools/linters/govet.js +37 -0
- package/dist/tools/linters/govet.js.map +1 -0
- package/dist/tools/linters/pyright.d.ts +2 -0
- package/dist/tools/linters/pyright.js +34 -0
- package/dist/tools/linters/pyright.js.map +1 -0
- package/dist/tools/linters/ruff.d.ts +2 -0
- package/dist/tools/linters/ruff.js +30 -0
- package/dist/tools/linters/ruff.js.map +1 -0
- package/dist/tools/linters/types.d.ts +16 -0
- package/dist/tools/linters/types.js +2 -0
- package/dist/tools/linters/types.js.map +1 -0
- package/dist/tools/linters/typescript.d.ts +2 -0
- package/dist/tools/linters/typescript.js +38 -0
- package/dist/tools/linters/typescript.js.map +1 -0
- package/dist/tools/listClaudeTasks.d.ts +84 -0
- package/dist/tools/listClaudeTasks.js +88 -0
- package/dist/tools/listClaudeTasks.js.map +1 -0
- package/dist/tools/listTerminals.d.ts +55 -0
- package/dist/tools/listTerminals.js +78 -0
- package/dist/tools/listTerminals.js.map +1 -0
- package/dist/tools/lsp.d.ts +1086 -0
- package/dist/tools/lsp.js +1339 -0
- package/dist/tools/lsp.js.map +1 -0
- package/dist/tools/navigateToSymbolByName.d.ts +56 -0
- package/dist/tools/navigateToSymbolByName.js +170 -0
- package/dist/tools/navigateToSymbolByName.js.map +1 -0
- package/dist/tools/openDiff.d.ts +66 -0
- package/dist/tools/openDiff.js +126 -0
- package/dist/tools/openDiff.js.map +1 -0
- package/dist/tools/openFile.d.ts +69 -0
- package/dist/tools/openFile.js +129 -0
- package/dist/tools/openFile.js.map +1 -0
- package/dist/tools/openInBrowser.d.ts +55 -0
- package/dist/tools/openInBrowser.js +129 -0
- package/dist/tools/openInBrowser.js.map +1 -0
- package/dist/tools/organizeImports.d.ts +56 -0
- package/dist/tools/organizeImports.js +115 -0
- package/dist/tools/organizeImports.js.map +1 -0
- package/dist/tools/performanceReport.d.ts +133 -0
- package/dist/tools/performanceReport.js +218 -0
- package/dist/tools/performanceReport.js.map +1 -0
- package/dist/tools/planPersistence.d.ts +306 -0
- package/dist/tools/planPersistence.js +485 -0
- package/dist/tools/planPersistence.js.map +1 -0
- package/dist/tools/previewEdit.d.ts +107 -0
- package/dist/tools/previewEdit.js +270 -0
- package/dist/tools/previewEdit.js.map +1 -0
- package/dist/tools/recentTracesDigest.d.ts +35 -0
- package/dist/tools/recentTracesDigest.js +98 -0
- package/dist/tools/recentTracesDigest.js.map +1 -0
- package/dist/tools/refactorAnalyze.d.ts +78 -0
- package/dist/tools/refactorAnalyze.js +141 -0
- package/dist/tools/refactorAnalyze.js.map +1 -0
- package/dist/tools/refactorExtractFunction.d.ts +52 -0
- package/dist/tools/refactorExtractFunction.js +121 -0
- package/dist/tools/refactorExtractFunction.js.map +1 -0
- package/dist/tools/refactorPreview.d.ts +75 -0
- package/dist/tools/refactorPreview.js +93 -0
- package/dist/tools/refactorPreview.js.map +1 -0
- package/dist/tools/replaceBlock.d.ts +62 -0
- package/dist/tools/replaceBlock.js +125 -0
- package/dist/tools/replaceBlock.js.map +1 -0
- package/dist/tools/resumeClaudeTask.d.ts +75 -0
- package/dist/tools/resumeClaudeTask.js +149 -0
- package/dist/tools/resumeClaudeTask.js.map +1 -0
- package/dist/tools/runClaudeTask.d.ts +97 -0
- package/dist/tools/runClaudeTask.js +224 -0
- package/dist/tools/runClaudeTask.js.map +1 -0
- package/dist/tools/runCommand.d.ts +82 -0
- package/dist/tools/runCommand.js +101 -0
- package/dist/tools/runCommand.js.map +1 -0
- package/dist/tools/runTests.d.ts +146 -0
- package/dist/tools/runTests.js +315 -0
- package/dist/tools/runTests.js.map +1 -0
- package/dist/tools/saveDocument.d.ts +50 -0
- package/dist/tools/saveDocument.js +73 -0
- package/dist/tools/saveDocument.js.map +1 -0
- package/dist/tools/screenshot.d.ts +23 -0
- package/dist/tools/screenshot.js +43 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/screenshotAndAnnotate.d.ts +103 -0
- package/dist/tools/screenshotAndAnnotate.js +192 -0
- package/dist/tools/screenshotAndAnnotate.js.map +1 -0
- package/dist/tools/searchAndReplace.d.ts +108 -0
- package/dist/tools/searchAndReplace.js +281 -0
- package/dist/tools/searchAndReplace.js.map +1 -0
- package/dist/tools/searchTools.d.ts +61 -0
- package/dist/tools/searchTools.js +85 -0
- package/dist/tools/searchTools.js.map +1 -0
- package/dist/tools/searchWorkspace.d.ts +99 -0
- package/dist/tools/searchWorkspace.js +189 -0
- package/dist/tools/searchWorkspace.js.map +1 -0
- package/dist/tools/selectionRanges.d.ts +58 -0
- package/dist/tools/selectionRanges.js +61 -0
- package/dist/tools/selectionRanges.js.map +1 -0
- package/dist/tools/semanticTokens.d.ts +87 -0
- package/dist/tools/semanticTokens.js +86 -0
- package/dist/tools/semanticTokens.js.map +1 -0
- package/dist/tools/setActiveWorkspaceFolder.d.ts +41 -0
- package/dist/tools/setActiveWorkspaceFolder.js +38 -0
- package/dist/tools/setActiveWorkspaceFolder.js.map +1 -0
- package/dist/tools/signatureHelp.d.ts +86 -0
- package/dist/tools/signatureHelp.js +79 -0
- package/dist/tools/signatureHelp.js.map +1 -0
- package/dist/tools/spawnWorkspace.d.ts +103 -0
- package/dist/tools/spawnWorkspace.js +268 -0
- package/dist/tools/spawnWorkspace.js.map +1 -0
- package/dist/tools/stackTraceParser.d.ts +43 -0
- package/dist/tools/stackTraceParser.js +139 -0
- package/dist/tools/stackTraceParser.js.map +1 -0
- package/dist/tools/terminal.d.ts +352 -0
- package/dist/tools/terminal.js +670 -0
- package/dist/tools/terminal.js.map +1 -0
- package/dist/tools/testRunners/cargoTest.d.ts +2 -0
- package/dist/tools/testRunners/cargoTest.js +129 -0
- package/dist/tools/testRunners/cargoTest.js.map +1 -0
- package/dist/tools/testRunners/goTest.d.ts +2 -0
- package/dist/tools/testRunners/goTest.js +108 -0
- package/dist/tools/testRunners/goTest.js.map +1 -0
- package/dist/tools/testRunners/pytest.d.ts +2 -0
- package/dist/tools/testRunners/pytest.js +135 -0
- package/dist/tools/testRunners/pytest.js.map +1 -0
- package/dist/tools/testRunners/types.d.ts +18 -0
- package/dist/tools/testRunners/types.js +2 -0
- package/dist/tools/testRunners/types.js.map +1 -0
- package/dist/tools/testRunners/vitestJest.d.ts +3 -0
- package/dist/tools/testRunners/vitestJest.js +215 -0
- package/dist/tools/testRunners/vitestJest.js.map +1 -0
- package/dist/tools/testTraceToSource.d.ts +80 -0
- package/dist/tools/testTraceToSource.js +206 -0
- package/dist/tools/testTraceToSource.js.map +1 -0
- package/dist/tools/transaction.d.ts +243 -0
- package/dist/tools/transaction.js +309 -0
- package/dist/tools/transaction.js.map +1 -0
- package/dist/tools/typeHierarchy.d.ts +77 -0
- package/dist/tools/typeHierarchy.js +86 -0
- package/dist/tools/typeHierarchy.js.map +1 -0
- package/dist/tools/utils.d.ts +124 -0
- package/dist/tools/utils.js +566 -0
- package/dist/tools/utils.js.map +1 -0
- package/dist/tools/vscodeCommands.d.ts +90 -0
- package/dist/tools/vscodeCommands.js +112 -0
- package/dist/tools/vscodeCommands.js.map +1 -0
- package/dist/tools/vscodeTasks.d.ts +102 -0
- package/dist/tools/vscodeTasks.js +110 -0
- package/dist/tools/vscodeTasks.js.map +1 -0
- package/dist/tools/watchDiagnostics.d.ts +64 -0
- package/dist/tools/watchDiagnostics.js +270 -0
- package/dist/tools/watchDiagnostics.js.map +1 -0
- package/dist/tools/workspaceSettings.d.ts +57 -0
- package/dist/tools/workspaceSettings.js +80 -0
- package/dist/tools/workspaceSettings.js.map +1 -0
- package/dist/transport.d.ts +207 -0
- package/dist/transport.js +1272 -0
- package/dist/transport.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +31 -0
- package/dist/version.js.map +1 -0
- package/dist/wsUtils.d.ts +8 -0
- package/dist/wsUtils.js +54 -0
- package/dist/wsUtils.js.map +1 -0
- package/package.json +118 -0
- package/scripts/gen-claude-desktop-config.sh +124 -0
- package/scripts/gen-mcp-config.sh +390 -0
- package/scripts/install-extension.sh +106 -0
- package/scripts/mcp-stdio-shim.cjs +482 -0
- package/scripts/postinstall.mjs +68 -0
- package/scripts/start-all.sh +502 -0
- package/scripts/start-orchestrator.sh +186 -0
- package/scripts/start-remote.sh +126 -0
- package/scripts/start-vps.sh +116 -0
- package/templates/CLAUDE.bridge.md +125 -0
- package/templates/automation-policies/security-first.json +46 -0
- package/templates/automation-policies/strict-lint.json +41 -0
- package/templates/automation-policies/test-driven.json +54 -0
- package/templates/automation-policy.example.json +105 -0
- package/templates/bridge-tools.md +111 -0
- package/templates/dispatch-context.md +33 -0
- package/templates/managed-agent/code-review-agent.md +50 -0
- package/templates/managed-agent/managed-agent-mcp.json +102 -0
- package/templates/recipes/ambient-journal.yaml +11 -0
- package/templates/recipes/daily-status.yaml +21 -0
- package/templates/recipes/lint-on-save.yaml +13 -0
- package/templates/recipes/stale-branches.yaml +18 -0
- package/templates/recipes/watch-failing-tests.yaml +15 -0
- package/templates/scheduled-tasks/dependency-audit/SKILL.md +77 -0
- package/templates/scheduled-tasks/health-check/SKILL.md +73 -0
- 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
|