claude-ide-bridge 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +263 -0
- package/dist/activityLog.d.ts +26 -0
- package/dist/activityLog.js +76 -0
- package/dist/activityLog.js.map +1 -0
- package/dist/bridge.d.ts +19 -0
- package/dist/bridge.js +277 -0
- package/dist/bridge.js.map +1 -0
- package/dist/config.d.ts +22 -0
- package/dist/config.js +221 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +20 -0
- package/dist/errors.js.map +1 -0
- package/dist/extensionClient.d.ts +193 -0
- package/dist/extensionClient.js +698 -0
- package/dist/extensionClient.js.map +1 -0
- package/dist/fileLock.d.ts +12 -0
- package/dist/fileLock.js +30 -0
- package/dist/fileLock.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/lockfile.d.ts +12 -0
- package/dist/lockfile.js +127 -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/probe.d.ts +22 -0
- package/dist/probe.js +45 -0
- package/dist/probe.js.map +1 -0
- package/dist/server.d.ts +25 -0
- package/dist/server.js +265 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/activityLog.d.ts +39 -0
- package/dist/tools/activityLog.js +49 -0
- package/dist/tools/activityLog.js.map +1 -0
- package/dist/tools/aiComments.d.ts +26 -0
- package/dist/tools/aiComments.js +196 -0
- package/dist/tools/aiComments.js.map +1 -0
- package/dist/tools/bridgeStatus.d.ts +21 -0
- package/dist/tools/bridgeStatus.js +41 -0
- package/dist/tools/bridgeStatus.js.map +1 -0
- package/dist/tools/checkDocumentDirty.d.ts +28 -0
- package/dist/tools/checkDocumentDirty.js +61 -0
- package/dist/tools/checkDocumentDirty.js.map +1 -0
- package/dist/tools/clipboard.d.ts +50 -0
- package/dist/tools/clipboard.js +82 -0
- package/dist/tools/clipboard.js.map +1 -0
- package/dist/tools/closeTabs.d.ts +49 -0
- package/dist/tools/closeTabs.js +77 -0
- package/dist/tools/closeTabs.js.map +1 -0
- package/dist/tools/debug.d.ts +154 -0
- package/dist/tools/debug.js +248 -0
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/decorations.d.ts +92 -0
- package/dist/tools/decorations.js +150 -0
- package/dist/tools/decorations.js.map +1 -0
- package/dist/tools/diffDebugger.d.ts +62 -0
- package/dist/tools/diffDebugger.js +245 -0
- package/dist/tools/diffDebugger.js.map +1 -0
- package/dist/tools/editText.d.ts +80 -0
- package/dist/tools/editText.js +274 -0
- package/dist/tools/editText.js.map +1 -0
- package/dist/tools/fileOperations.d.ts +111 -0
- package/dist/tools/fileOperations.js +280 -0
- package/dist/tools/fileOperations.js.map +1 -0
- package/dist/tools/fileWatcher.d.ts +54 -0
- package/dist/tools/fileWatcher.js +100 -0
- package/dist/tools/fileWatcher.js.map +1 -0
- package/dist/tools/findFiles.d.ts +31 -0
- package/dist/tools/findFiles.js +119 -0
- package/dist/tools/findFiles.js.map +1 -0
- package/dist/tools/fixAllLintErrors.d.ts +29 -0
- package/dist/tools/fixAllLintErrors.js +114 -0
- package/dist/tools/fixAllLintErrors.js.map +1 -0
- package/dist/tools/flowGuardian.d.ts +61 -0
- package/dist/tools/flowGuardian.js +311 -0
- package/dist/tools/flowGuardian.js.map +1 -0
- package/dist/tools/formatDocument.d.ts +30 -0
- package/dist/tools/formatDocument.js +132 -0
- package/dist/tools/formatDocument.js.map +1 -0
- package/dist/tools/formatFile.d.ts +28 -0
- package/dist/tools/formatFile.js +110 -0
- package/dist/tools/formatFile.js.map +1 -0
- package/dist/tools/getBufferContent.d.ts +38 -0
- package/dist/tools/getBufferContent.js +100 -0
- package/dist/tools/getBufferContent.js.map +1 -0
- package/dist/tools/getCurrentSelection.d.ts +43 -0
- package/dist/tools/getCurrentSelection.js +75 -0
- package/dist/tools/getCurrentSelection.js.map +1 -0
- package/dist/tools/getDiagnostics.d.ts +38 -0
- package/dist/tools/getDiagnostics.js +204 -0
- package/dist/tools/getDiagnostics.js.map +1 -0
- package/dist/tools/getDocumentSymbols.d.ts +27 -0
- package/dist/tools/getDocumentSymbols.js +133 -0
- package/dist/tools/getDocumentSymbols.js.map +1 -0
- package/dist/tools/getFileTree.d.ts +36 -0
- package/dist/tools/getFileTree.js +111 -0
- package/dist/tools/getFileTree.js.map +1 -0
- package/dist/tools/getGitDiff.d.ts +34 -0
- package/dist/tools/getGitDiff.js +57 -0
- package/dist/tools/getGitDiff.js.map +1 -0
- package/dist/tools/getGitLog.d.ts +30 -0
- package/dist/tools/getGitLog.js +65 -0
- package/dist/tools/getGitLog.js.map +1 -0
- package/dist/tools/getGitStatus.d.ts +26 -0
- package/dist/tools/getGitStatus.js +95 -0
- package/dist/tools/getGitStatus.js.map +1 -0
- package/dist/tools/getOpenEditors.d.ts +21 -0
- package/dist/tools/getOpenEditors.js +84 -0
- package/dist/tools/getOpenEditors.js.map +1 -0
- package/dist/tools/getProjectInfo.d.ts +20 -0
- package/dist/tools/getProjectInfo.js +315 -0
- package/dist/tools/getProjectInfo.js.map +1 -0
- package/dist/tools/getToolCapabilities.d.ts +23 -0
- package/dist/tools/getToolCapabilities.js +249 -0
- package/dist/tools/getToolCapabilities.js.map +1 -0
- package/dist/tools/getWorkspaceFolders.d.ts +21 -0
- package/dist/tools/getWorkspaceFolders.js +47 -0
- package/dist/tools/getWorkspaceFolders.js.map +1 -0
- package/dist/tools/gitHistory.d.ts +78 -0
- package/dist/tools/gitHistory.js +151 -0
- package/dist/tools/gitHistory.js.map +1 -0
- package/dist/tools/gitWrite.d.ts +335 -0
- package/dist/tools/gitWrite.js +859 -0
- package/dist/tools/gitWrite.js.map +1 -0
- package/dist/tools/github/actions.d.ts +67 -0
- package/dist/tools/github/actions.js +155 -0
- package/dist/tools/github/actions.js.map +1 -0
- package/dist/tools/github/index.d.ts +4 -0
- package/dist/tools/github/index.js +5 -0
- package/dist/tools/github/index.js.map +1 -0
- package/dist/tools/github/issues.d.ts +140 -0
- package/dist/tools/github/issues.js +279 -0
- package/dist/tools/github/issues.js.map +1 -0
- package/dist/tools/github/pr.d.ts +101 -0
- package/dist/tools/github/pr.js +215 -0
- package/dist/tools/github/pr.js.map +1 -0
- package/dist/tools/github/review.d.ts +101 -0
- package/dist/tools/github/review.js +292 -0
- package/dist/tools/github/review.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/github.d.ts +308 -0
- package/dist/tools/github.js +656 -0
- package/dist/tools/github.js.map +1 -0
- package/dist/tools/hoverAtCursor.d.ts +22 -0
- package/dist/tools/hoverAtCursor.js +51 -0
- package/dist/tools/hoverAtCursor.js.map +1 -0
- package/dist/tools/httpClient.d.ts +83 -0
- package/dist/tools/httpClient.js +335 -0
- package/dist/tools/httpClient.js.map +1 -0
- package/dist/tools/index.d.ts +7 -0
- package/dist/tools/index.js +246 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/inlayHints.d.ts +38 -0
- package/dist/tools/inlayHints.js +56 -0
- package/dist/tools/inlayHints.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/lsp.d.ts +310 -0
- package/dist/tools/lsp.js +684 -0
- package/dist/tools/lsp.js.map +1 -0
- package/dist/tools/notebook.d.ts +95 -0
- package/dist/tools/notebook.js +144 -0
- package/dist/tools/notebook.js.map +1 -0
- package/dist/tools/openDiff.d.ts +41 -0
- package/dist/tools/openDiff.js +116 -0
- package/dist/tools/openDiff.js.map +1 -0
- package/dist/tools/openFile.d.ts +34 -0
- package/dist/tools/openFile.js +102 -0
- package/dist/tools/openFile.js.map +1 -0
- package/dist/tools/organizeImports.d.ts +29 -0
- package/dist/tools/organizeImports.js +64 -0
- package/dist/tools/organizeImports.js.map +1 -0
- package/dist/tools/planPersistence.d.ts +196 -0
- package/dist/tools/planPersistence.js +437 -0
- package/dist/tools/planPersistence.js.map +1 -0
- package/dist/tools/replaceBlock.d.ts +40 -0
- package/dist/tools/replaceBlock.js +105 -0
- package/dist/tools/replaceBlock.js.map +1 -0
- package/dist/tools/runCommand.d.ts +43 -0
- package/dist/tools/runCommand.js +141 -0
- package/dist/tools/runCommand.js.map +1 -0
- package/dist/tools/runTests.d.ts +32 -0
- package/dist/tools/runTests.js +160 -0
- package/dist/tools/runTests.js.map +1 -0
- package/dist/tools/saveDocument.d.ts +28 -0
- package/dist/tools/saveDocument.js +58 -0
- package/dist/tools/saveDocument.js.map +1 -0
- package/dist/tools/searchAndReplace.d.ts +50 -0
- package/dist/tools/searchAndReplace.js +203 -0
- package/dist/tools/searchAndReplace.js.map +1 -0
- package/dist/tools/searchWorkspace.d.ts +52 -0
- package/dist/tools/searchWorkspace.js +159 -0
- package/dist/tools/searchWorkspace.js.map +1 -0
- package/dist/tools/setActiveWorkspaceFolder.d.ts +28 -0
- package/dist/tools/setActiveWorkspaceFolder.js +32 -0
- package/dist/tools/setActiveWorkspaceFolder.js.map +1 -0
- package/dist/tools/tasks.d.ts +56 -0
- package/dist/tools/tasks.js +89 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/terminal.d.ts +241 -0
- package/dist/tools/terminal.js +539 -0
- package/dist/tools/terminal.js.map +1 -0
- package/dist/tools/testRunners/cargoTest.d.ts +2 -0
- package/dist/tools/testRunners/cargoTest.js +123 -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 +106 -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 +133 -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 +178 -0
- package/dist/tools/testRunners/vitestJest.js.map +1 -0
- package/dist/tools/typeHierarchy.d.ts +45 -0
- package/dist/tools/typeHierarchy.js +65 -0
- package/dist/tools/typeHierarchy.js.map +1 -0
- package/dist/tools/utils.d.ts +54 -0
- package/dist/tools/utils.js +267 -0
- package/dist/tools/utils.js.map +1 -0
- package/dist/tools/vscodeCommands.d.ts +59 -0
- package/dist/tools/vscodeCommands.js +108 -0
- package/dist/tools/vscodeCommands.js.map +1 -0
- package/dist/tools/watchDiagnostics.d.ts +32 -0
- package/dist/tools/watchDiagnostics.js +87 -0
- package/dist/tools/watchDiagnostics.js.map +1 -0
- package/dist/tools/workspaceSettings.d.ts +67 -0
- package/dist/tools/workspaceSettings.js +102 -0
- package/dist/tools/workspaceSettings.js.map +1 -0
- package/dist/tools/workspaceSnapshots.d.ts +174 -0
- package/dist/tools/workspaceSnapshots.js +474 -0
- package/dist/tools/workspaceSnapshots.js.map +1 -0
- package/dist/transport.d.ts +57 -0
- package/dist/transport.js +417 -0
- package/dist/transport.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/wsUtils.d.ts +9 -0
- package/dist/wsUtils.js +54 -0
- package/dist/wsUtils.js.map +1 -0
- package/package.json +69 -0
|
@@ -0,0 +1,698 @@
|
|
|
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
|
+
// Exponential backoff — prevents cascading 10s timeouts when extension is unresponsive
|
|
21
|
+
extensionSuspendedUntil = 0;
|
|
22
|
+
extensionFailures = 0;
|
|
23
|
+
extensionHalfOpen = false;
|
|
24
|
+
// State pushed by extension via notifications
|
|
25
|
+
latestDiagnostics = new Map();
|
|
26
|
+
latestSelection = null;
|
|
27
|
+
latestActiveFile = null;
|
|
28
|
+
lastDiagnosticsUpdate = 0;
|
|
29
|
+
// AI comment cache (pushed by extension)
|
|
30
|
+
latestAIComments = new Map();
|
|
31
|
+
// Debug state (pushed by extension via notifications)
|
|
32
|
+
latestDebugState = null;
|
|
33
|
+
// Callbacks for forwarding notifications to Claude Code
|
|
34
|
+
onDiagnosticsChanged = null;
|
|
35
|
+
onAICommentsChanged = null;
|
|
36
|
+
onFileChanged = null;
|
|
37
|
+
onExtensionDisconnected = null;
|
|
38
|
+
onDebugSessionChanged = null;
|
|
39
|
+
// Listener set for diagnostics (used by watchDiagnostics long-poll)
|
|
40
|
+
diagnosticsListeners = new Set();
|
|
41
|
+
constructor(logger) {
|
|
42
|
+
this.logger = logger;
|
|
43
|
+
}
|
|
44
|
+
/** Invoke a callback safely — log and swallow errors to prevent bridge crash */
|
|
45
|
+
safeCallback(fn, ...args) {
|
|
46
|
+
if (!fn)
|
|
47
|
+
return;
|
|
48
|
+
try {
|
|
49
|
+
fn(...args);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
this.logger.error(`Callback error: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
handleExtensionConnection(ws) {
|
|
56
|
+
// Replace existing connection
|
|
57
|
+
if (this.ws) {
|
|
58
|
+
this.logger.info("Replacing existing extension connection");
|
|
59
|
+
this.rejectAllPending("Extension reconnected");
|
|
60
|
+
this.ws.removeAllListeners();
|
|
61
|
+
if (this.ws.readyState === WebSocket.OPEN) {
|
|
62
|
+
this.ws.terminate();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
this.ws = ws;
|
|
66
|
+
this.connected = true;
|
|
67
|
+
// Don't clear cached diagnostics/selection/file state — extension pushes
|
|
68
|
+
// fresh data on connect, so stale values are harmless for a few seconds.
|
|
69
|
+
// Reset backoff — fresh connection deserves a clean slate
|
|
70
|
+
this.extensionSuspendedUntil = 0;
|
|
71
|
+
this.extensionFailures = 0;
|
|
72
|
+
this.extensionHalfOpen = false;
|
|
73
|
+
this.logger.info("Extension client connected");
|
|
74
|
+
ws.on("message", (data) => {
|
|
75
|
+
try {
|
|
76
|
+
const msg = JSON.parse(data.toString("utf-8"));
|
|
77
|
+
// Response to our request
|
|
78
|
+
if (msg.id !== undefined && msg.id !== null && !msg.method) {
|
|
79
|
+
const pending = this.pendingRequests.get(msg.id);
|
|
80
|
+
if (pending) {
|
|
81
|
+
this.pendingRequests.delete(msg.id);
|
|
82
|
+
clearTimeout(pending.timer);
|
|
83
|
+
if (msg.error) {
|
|
84
|
+
pending.reject(new Error(msg.error.message));
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
pending.resolve(msg.result);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
this.logger.debug(`Orphaned extension response for id=${msg.id} (likely timed out)`);
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Push notification from extension
|
|
96
|
+
if (msg.method) {
|
|
97
|
+
this.handleNotification(msg.method, msg.params);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
this.logger.error(`Extension message parse error: ${err}`);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// Guard against double close/error cleanup
|
|
105
|
+
let disconnected = false;
|
|
106
|
+
const handleDisconnect = (reason) => {
|
|
107
|
+
if (disconnected)
|
|
108
|
+
return;
|
|
109
|
+
disconnected = true;
|
|
110
|
+
this.connected = false;
|
|
111
|
+
this.ws = null;
|
|
112
|
+
this.rejectAllPending(reason);
|
|
113
|
+
// Clear diagnostics listeners — stale watchDiagnostics closures must not
|
|
114
|
+
// accumulate across reconnects (they hold references to old AbortSignals).
|
|
115
|
+
this.diagnosticsListeners.clear();
|
|
116
|
+
this.logger.info(`Extension disconnected: ${reason}`);
|
|
117
|
+
this.safeCallback(this.onExtensionDisconnected);
|
|
118
|
+
};
|
|
119
|
+
ws.on("close", () => handleDisconnect("Connection closed"));
|
|
120
|
+
ws.on("error", (err) => {
|
|
121
|
+
this.logger.error(`Extension WS error: ${err.message}`);
|
|
122
|
+
// Set disconnected flag before terminate() so any synchronous close event
|
|
123
|
+
// emitted by terminate() sees the guard as already set.
|
|
124
|
+
handleDisconnect(`Error: ${err.message}`);
|
|
125
|
+
ws.terminate();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
handleNotification(method, params) {
|
|
129
|
+
if (typeof params !== "object" || params === null) {
|
|
130
|
+
this.logger.debug(`Ignoring notification ${method} with invalid params`);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
const p = params;
|
|
134
|
+
switch (method) {
|
|
135
|
+
case "extension/diagnosticsChanged": {
|
|
136
|
+
const file = p.file;
|
|
137
|
+
const diagnostics = p.diagnostics;
|
|
138
|
+
if (typeof file !== "string" || !Array.isArray(diagnostics)) {
|
|
139
|
+
this.logger.debug("Ignoring malformed diagnosticsChanged notification");
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (diagnostics.length === 0) {
|
|
143
|
+
this.latestDiagnostics.delete(file);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
this.latestDiagnostics.set(file, diagnostics);
|
|
147
|
+
// Cap diagnostics cache at 500 entries
|
|
148
|
+
if (this.latestDiagnostics.size > 500) {
|
|
149
|
+
const firstKey = this.latestDiagnostics.keys().next().value;
|
|
150
|
+
if (firstKey !== undefined)
|
|
151
|
+
this.latestDiagnostics.delete(firstKey);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Update timestamp and forward to Claude Code
|
|
155
|
+
this.lastDiagnosticsUpdate = Date.now();
|
|
156
|
+
this.safeCallback(this.onDiagnosticsChanged, file, diagnostics);
|
|
157
|
+
for (const fn of [...this.diagnosticsListeners])
|
|
158
|
+
this.safeCallback(fn, file, diagnostics);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
case "extension/selectionChanged": {
|
|
162
|
+
if (typeof p.file !== "string" ||
|
|
163
|
+
typeof p.startLine !== "number" ||
|
|
164
|
+
typeof p.endLine !== "number" ||
|
|
165
|
+
typeof p.startColumn !== "number" ||
|
|
166
|
+
typeof p.endColumn !== "number" ||
|
|
167
|
+
typeof p.selectedText !== "string") {
|
|
168
|
+
this.logger.debug("Ignoring malformed selectionChanged notification");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.latestSelection = {
|
|
172
|
+
file: p.file,
|
|
173
|
+
startLine: p.startLine,
|
|
174
|
+
endLine: p.endLine,
|
|
175
|
+
startColumn: p.startColumn,
|
|
176
|
+
endColumn: p.endColumn,
|
|
177
|
+
selectedText: p.selectedText,
|
|
178
|
+
};
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case "extension/activeFileChanged": {
|
|
182
|
+
if (typeof p.file !== "string") {
|
|
183
|
+
this.logger.debug("Ignoring malformed activeFileChanged notification");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
this.latestActiveFile = p.file;
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
case "extension/aiCommentsChanged": {
|
|
190
|
+
const comments = p.comments;
|
|
191
|
+
if (!Array.isArray(comments)) {
|
|
192
|
+
this.logger.debug("Ignoring malformed aiCommentsChanged notification");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
this.latestAIComments.clear();
|
|
196
|
+
for (const c of comments) {
|
|
197
|
+
const entry = c;
|
|
198
|
+
if (typeof entry.file !== "string")
|
|
199
|
+
continue;
|
|
200
|
+
const existing = this.latestAIComments.get(entry.file) || [];
|
|
201
|
+
existing.push(entry);
|
|
202
|
+
this.latestAIComments.set(entry.file, existing);
|
|
203
|
+
}
|
|
204
|
+
// Cap at 200 files
|
|
205
|
+
if (this.latestAIComments.size > 200) {
|
|
206
|
+
const firstKey = this.latestAIComments.keys().next().value;
|
|
207
|
+
if (firstKey !== undefined)
|
|
208
|
+
this.latestAIComments.delete(firstKey);
|
|
209
|
+
}
|
|
210
|
+
this.safeCallback(this.onAICommentsChanged, comments);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case "extension/fileChanged": {
|
|
214
|
+
const id = p.id;
|
|
215
|
+
const type = p.type;
|
|
216
|
+
const file = p.file;
|
|
217
|
+
if (typeof id === "string" &&
|
|
218
|
+
typeof type === "string" &&
|
|
219
|
+
typeof file === "string") {
|
|
220
|
+
this.safeCallback(this.onFileChanged, id, type, file);
|
|
221
|
+
}
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
case "extension/hello": {
|
|
225
|
+
const extVer = typeof p.extensionVersion === "string"
|
|
226
|
+
? p.extensionVersion
|
|
227
|
+
: "unknown";
|
|
228
|
+
this.logger.info(`Extension hello: version=${extVer}`);
|
|
229
|
+
const [extMajor] = extVer.split(".").map(Number);
|
|
230
|
+
const [bridgeMajor] = BRIDGE_PROTOCOL_VERSION.split(".").map(Number);
|
|
231
|
+
if (extMajor !== bridgeMajor) {
|
|
232
|
+
this.logger.warn(`Extension major version mismatch: bridge=${BRIDGE_PROTOCOL_VERSION}, extension=${extVer}. Consider updating.`);
|
|
233
|
+
}
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
case "extension/fileSaved":
|
|
237
|
+
break;
|
|
238
|
+
case "extension/debugSessionChanged": {
|
|
239
|
+
if (typeof p.hasActiveSession !== "boolean") {
|
|
240
|
+
this.logger.debug("Ignoring malformed debugSessionChanged notification");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const state = p;
|
|
244
|
+
this.latestDebugState = state;
|
|
245
|
+
this.safeCallback(this.onDebugSessionChanged, state);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
default:
|
|
249
|
+
this.logger.debug(`Unknown extension notification: ${method}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
static MAX_PENDING_REQUESTS = 100;
|
|
253
|
+
async request(method, params, timeoutMs, signal) {
|
|
254
|
+
if (this.pendingRequests.size >= ExtensionClient.MAX_PENDING_REQUESTS) {
|
|
255
|
+
throw new Error(`Too many pending extension requests (${ExtensionClient.MAX_PENDING_REQUESTS})`);
|
|
256
|
+
}
|
|
257
|
+
// Exponential backoff — fast-fail if extension is repeatedly timing out
|
|
258
|
+
const now = Date.now();
|
|
259
|
+
if (now < this.extensionSuspendedUntil) {
|
|
260
|
+
throw new ExtensionTimeoutError(method);
|
|
261
|
+
}
|
|
262
|
+
// Half-open: backoff expired but failures recorded — allow one probe through
|
|
263
|
+
if (this.extensionFailures > 0 && !this.extensionHalfOpen) {
|
|
264
|
+
this.extensionHalfOpen = true;
|
|
265
|
+
this.logger.debug(`Extension circuit breaker half-open — probing with ${method}`);
|
|
266
|
+
}
|
|
267
|
+
if (!this.connected || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
268
|
+
throw new Error("Extension not connected");
|
|
269
|
+
}
|
|
270
|
+
if (signal?.aborted) {
|
|
271
|
+
throw new Error("Request aborted");
|
|
272
|
+
}
|
|
273
|
+
// Wait for backpressure to clear before sending
|
|
274
|
+
await waitForDrain(this.ws, this.logger, "Extension backpressure");
|
|
275
|
+
// Re-check after drain wait — socket may have closed
|
|
276
|
+
if (!this.connected || !this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
277
|
+
throw new Error("Extension disconnected during drain wait");
|
|
278
|
+
}
|
|
279
|
+
const timeout = timeoutMs ?? REQUEST_TIMEOUT;
|
|
280
|
+
const id = this.nextId++;
|
|
281
|
+
if (this.nextId >= Number.MAX_SAFE_INTEGER)
|
|
282
|
+
this.nextId = 0;
|
|
283
|
+
const inner = new Promise((resolve, reject) => {
|
|
284
|
+
let settled = false;
|
|
285
|
+
const settle = (fn) => {
|
|
286
|
+
if (settled)
|
|
287
|
+
return;
|
|
288
|
+
settled = true;
|
|
289
|
+
fn();
|
|
290
|
+
};
|
|
291
|
+
const timer = setTimeout(() => {
|
|
292
|
+
this.pendingRequests.delete(id);
|
|
293
|
+
signal?.removeEventListener("abort", onAbort);
|
|
294
|
+
this.logger.warn(`Extension request ${method} timed out after ${timeout}ms`);
|
|
295
|
+
settle(() => reject(new ExtensionTimeoutError(method)));
|
|
296
|
+
}, timeout);
|
|
297
|
+
// Re-check abort after the async drain wait — signal may have fired during that gap.
|
|
298
|
+
// If already aborted, addEventListener would never fire so we must check here.
|
|
299
|
+
if (signal?.aborted) {
|
|
300
|
+
clearTimeout(timer);
|
|
301
|
+
settle(() => reject(new Error("Request aborted")));
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
// Wire AbortSignal to cancel the pending request
|
|
305
|
+
const onAbort = () => {
|
|
306
|
+
this.pendingRequests.delete(id);
|
|
307
|
+
clearTimeout(timer);
|
|
308
|
+
settle(() => reject(new Error("Request aborted")));
|
|
309
|
+
};
|
|
310
|
+
if (signal) {
|
|
311
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
312
|
+
}
|
|
313
|
+
this.pendingRequests.set(id, {
|
|
314
|
+
resolve: (value) => {
|
|
315
|
+
signal?.removeEventListener("abort", onAbort);
|
|
316
|
+
settle(() => resolve(value));
|
|
317
|
+
},
|
|
318
|
+
reject: (reason) => {
|
|
319
|
+
signal?.removeEventListener("abort", onAbort);
|
|
320
|
+
settle(() => reject(reason));
|
|
321
|
+
},
|
|
322
|
+
timer,
|
|
323
|
+
});
|
|
324
|
+
const data = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
325
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
326
|
+
try {
|
|
327
|
+
this.ws.send(data);
|
|
328
|
+
}
|
|
329
|
+
catch (err) {
|
|
330
|
+
this.pendingRequests.delete(id);
|
|
331
|
+
clearTimeout(timer);
|
|
332
|
+
signal?.removeEventListener("abort", onAbort);
|
|
333
|
+
settle(() => reject(new Error(`Failed to send extension request: ${err}`)));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
this.pendingRequests.delete(id);
|
|
338
|
+
clearTimeout(timer);
|
|
339
|
+
signal?.removeEventListener("abort", onAbort);
|
|
340
|
+
settle(() => reject(new Error("Extension disconnected before send")));
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
try {
|
|
344
|
+
const result = await inner;
|
|
345
|
+
// Success — reset backoff and half-open state
|
|
346
|
+
if (this.extensionFailures > 0) {
|
|
347
|
+
this.logger.warn("Extension backoff reset — connection recovered");
|
|
348
|
+
}
|
|
349
|
+
this.extensionFailures = 0;
|
|
350
|
+
this.extensionSuspendedUntil = 0;
|
|
351
|
+
this.extensionHalfOpen = false;
|
|
352
|
+
return result;
|
|
353
|
+
}
|
|
354
|
+
catch (err) {
|
|
355
|
+
if (err instanceof ExtensionTimeoutError) {
|
|
356
|
+
this.extensionHalfOpen = false;
|
|
357
|
+
const failures = ++this.extensionFailures;
|
|
358
|
+
// Full jitter (AWS-recommended): random in [1, cap] — prevents rhythmic
|
|
359
|
+
// retry storms when bridge and extension restart simultaneously.
|
|
360
|
+
const capMs = Math.min(1_000 * 2 ** (failures - 1), 60_000);
|
|
361
|
+
const backoffMs = Math.floor(Math.random() * capMs) + 1;
|
|
362
|
+
this.extensionSuspendedUntil = Date.now() + backoffMs;
|
|
363
|
+
this.logger.warn(`Extension timed out (failure #${failures}) — suspending for ${Math.round(backoffMs / 100) / 10}s`);
|
|
364
|
+
}
|
|
365
|
+
throw err;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/** Like request() but returns null on disconnect instead of rejecting.
|
|
369
|
+
* ExtensionTimeoutError is NOT caught — callers must handle it to distinguish
|
|
370
|
+
* timeout from genuine "no results" (null). */
|
|
371
|
+
async requestOrNull(method, params, timeoutMs, signal) {
|
|
372
|
+
return this.request(method, params, timeoutMs, signal).catch((err) => {
|
|
373
|
+
if (err instanceof ExtensionTimeoutError)
|
|
374
|
+
throw err;
|
|
375
|
+
return null;
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
rejectAllPending(reason) {
|
|
379
|
+
for (const [_id, pending] of this.pendingRequests) {
|
|
380
|
+
clearTimeout(pending.timer);
|
|
381
|
+
pending.reject(new Error(reason));
|
|
382
|
+
}
|
|
383
|
+
this.pendingRequests.clear();
|
|
384
|
+
}
|
|
385
|
+
/** Typed shorthand for requestOrNull — returns T | null without an intermediate variable */
|
|
386
|
+
async proxy(method, params, timeout, signal) {
|
|
387
|
+
return this.requestOrNull(method, params, timeout, signal);
|
|
388
|
+
}
|
|
389
|
+
async getDiagnostics(file) {
|
|
390
|
+
return this.proxy("extension/getDiagnostics", { file });
|
|
391
|
+
}
|
|
392
|
+
async getSelection() {
|
|
393
|
+
return this.proxy("extension/getSelection");
|
|
394
|
+
}
|
|
395
|
+
async getOpenFiles() {
|
|
396
|
+
return this.proxy("extension/getOpenFiles");
|
|
397
|
+
}
|
|
398
|
+
async isDirty(file) {
|
|
399
|
+
return this.proxy("extension/isDirty", { file });
|
|
400
|
+
}
|
|
401
|
+
async getFileContent(file) {
|
|
402
|
+
return this.requestOrNull("extension/getFileContent", { file });
|
|
403
|
+
}
|
|
404
|
+
async openFile(file, line) {
|
|
405
|
+
const result = await this.requestOrNull("extension/openFile", {
|
|
406
|
+
file,
|
|
407
|
+
line,
|
|
408
|
+
});
|
|
409
|
+
return result === true;
|
|
410
|
+
}
|
|
411
|
+
async saveFile(file) {
|
|
412
|
+
const result = await this.requestOrNull("extension/saveFile", { file });
|
|
413
|
+
return result === true;
|
|
414
|
+
}
|
|
415
|
+
async closeTab(file) {
|
|
416
|
+
const result = await this.requestOrNull("extension/closeTab", { file });
|
|
417
|
+
return result === true;
|
|
418
|
+
}
|
|
419
|
+
async getAIComments() {
|
|
420
|
+
return this.proxy("extension/getAIComments");
|
|
421
|
+
}
|
|
422
|
+
// --- LSP Semantic Features ---
|
|
423
|
+
async goToDefinition(file, line, column, signal) {
|
|
424
|
+
return this.requestOrNull("extension/goToDefinition", { file, line, column }, undefined, signal);
|
|
425
|
+
}
|
|
426
|
+
async findReferences(file, line, column, signal) {
|
|
427
|
+
return this.requestOrNull("extension/findReferences", { file, line, column }, undefined, signal);
|
|
428
|
+
}
|
|
429
|
+
async getHover(file, line, column, signal) {
|
|
430
|
+
return this.requestOrNull("extension/getHover", { file, line, column }, undefined, signal);
|
|
431
|
+
}
|
|
432
|
+
async getCodeActions(file, startLine, startColumn, endLine, endColumn) {
|
|
433
|
+
return this.requestOrNull("extension/getCodeActions", {
|
|
434
|
+
file,
|
|
435
|
+
startLine,
|
|
436
|
+
startColumn,
|
|
437
|
+
endLine,
|
|
438
|
+
endColumn,
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
async applyCodeAction(file, startLine, startColumn, endLine, endColumn, actionTitle) {
|
|
442
|
+
return this.requestOrNull("extension/applyCodeAction", {
|
|
443
|
+
file,
|
|
444
|
+
startLine,
|
|
445
|
+
startColumn,
|
|
446
|
+
endLine,
|
|
447
|
+
endColumn,
|
|
448
|
+
actionTitle,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
async renameSymbol(file, line, column, newName) {
|
|
452
|
+
// Rename can be slow on large projects
|
|
453
|
+
return this.requestOrNull("extension/renameSymbol", { file, line, column, newName }, 15_000);
|
|
454
|
+
}
|
|
455
|
+
async searchSymbols(query, maxResults) {
|
|
456
|
+
return this.requestOrNull("extension/searchSymbols", { query, maxResults });
|
|
457
|
+
}
|
|
458
|
+
async watchFiles(id, pattern) {
|
|
459
|
+
return this.requestOrNull("extension/watchFiles", { id, pattern });
|
|
460
|
+
}
|
|
461
|
+
async unwatchFiles(id) {
|
|
462
|
+
return this.requestOrNull("extension/unwatchFiles", { id });
|
|
463
|
+
}
|
|
464
|
+
// --- Terminal Features ---
|
|
465
|
+
async listTerminals() {
|
|
466
|
+
return this.requestOrNull("extension/listTerminals");
|
|
467
|
+
}
|
|
468
|
+
async getTerminalOutput(name, index, lines) {
|
|
469
|
+
return this.requestOrNull("extension/getTerminalOutput", {
|
|
470
|
+
name,
|
|
471
|
+
index,
|
|
472
|
+
lines,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
async disposeTerminal(name, index) {
|
|
476
|
+
return this.requestOrNull("extension/disposeTerminal", { name, index });
|
|
477
|
+
}
|
|
478
|
+
// --- File Operations ---
|
|
479
|
+
async createFile(filePath, content, isDirectory, overwrite, openAfterCreate) {
|
|
480
|
+
return this.requestOrNull("extension/createFile", {
|
|
481
|
+
filePath,
|
|
482
|
+
content,
|
|
483
|
+
isDirectory,
|
|
484
|
+
overwrite,
|
|
485
|
+
openAfterCreate,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
async deleteFile(filePath, recursive, useTrash) {
|
|
489
|
+
return this.requestOrNull("extension/deleteFile", {
|
|
490
|
+
filePath,
|
|
491
|
+
recursive,
|
|
492
|
+
useTrash,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
async renameFile(oldPath, newPath, overwrite) {
|
|
496
|
+
return this.requestOrNull("extension/renameFile", {
|
|
497
|
+
oldPath,
|
|
498
|
+
newPath,
|
|
499
|
+
overwrite,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
// --- Text Editing ---
|
|
503
|
+
async editText(filePath, edits, save) {
|
|
504
|
+
return this.requestOrNull("extension/editText", { filePath, edits, save });
|
|
505
|
+
}
|
|
506
|
+
async replaceBlock(filePath, oldContent, newContent, save) {
|
|
507
|
+
return this.requestOrNull("extension/replaceBlock", {
|
|
508
|
+
filePath,
|
|
509
|
+
oldContent,
|
|
510
|
+
newContent,
|
|
511
|
+
save,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
async getDocumentSymbols(file) {
|
|
515
|
+
return this.requestOrNull("extension/getDocumentSymbols", { file });
|
|
516
|
+
}
|
|
517
|
+
async getCallHierarchy(file, line, column, direction, maxResults) {
|
|
518
|
+
return this.requestOrNull("extension/getCallHierarchy", { file, line, column, direction, maxResults }, 15_000);
|
|
519
|
+
}
|
|
520
|
+
// --- Code Actions (format, fix, organize) ---
|
|
521
|
+
async formatDocument(file) {
|
|
522
|
+
return this.requestOrNull("extension/formatDocument", { file }, 15_000);
|
|
523
|
+
}
|
|
524
|
+
async fixAllLintErrors(file) {
|
|
525
|
+
return this.requestOrNull("extension/fixAllLintErrors", { file }, 15_000);
|
|
526
|
+
}
|
|
527
|
+
async organizeImports(file) {
|
|
528
|
+
return this.requestOrNull("extension/organizeImports", { file }, 15_000);
|
|
529
|
+
}
|
|
530
|
+
// --- Terminal Control ---
|
|
531
|
+
async createTerminal(name, cwd, env, show) {
|
|
532
|
+
return this.requestOrNull("extension/createTerminal", {
|
|
533
|
+
name,
|
|
534
|
+
cwd,
|
|
535
|
+
env,
|
|
536
|
+
show,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
async sendTerminalCommand(text, name, index, addNewline) {
|
|
540
|
+
return this.requestOrNull("extension/sendTerminalCommand", {
|
|
541
|
+
text,
|
|
542
|
+
name,
|
|
543
|
+
index,
|
|
544
|
+
addNewline,
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
async waitForTerminalOutput(pattern, name, index, timeoutMs) {
|
|
548
|
+
const requestTimeout = (timeoutMs ?? 30_000) + 5_000;
|
|
549
|
+
return this.requestOrNull("extension/waitForTerminalOutput", { pattern, name, index, timeoutMs }, requestTimeout);
|
|
550
|
+
}
|
|
551
|
+
async executeInTerminal(command, name, index, timeoutMs, show) {
|
|
552
|
+
// Add 5s overhead beyond the command timeout so the bridge doesn't cut the extension off early
|
|
553
|
+
const requestTimeout = (timeoutMs ?? 30_000) + 5_000;
|
|
554
|
+
return this.requestOrNull("extension/executeInTerminal", { command, name, index, timeoutMs, show }, requestTimeout);
|
|
555
|
+
}
|
|
556
|
+
// --- Debug ---
|
|
557
|
+
async getDebugState() {
|
|
558
|
+
return this.proxy("extension/getDebugState");
|
|
559
|
+
}
|
|
560
|
+
async evaluateInDebugger(expression, frameId, context) {
|
|
561
|
+
return this.requestOrNull("extension/evaluateInDebugger", {
|
|
562
|
+
expression,
|
|
563
|
+
frameId,
|
|
564
|
+
context,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
async setDebugBreakpoints(file, breakpoints) {
|
|
568
|
+
return this.requestOrNull("extension/setDebugBreakpoints", {
|
|
569
|
+
file,
|
|
570
|
+
breakpoints,
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
async startDebugging(configName) {
|
|
574
|
+
return this.requestOrNull("extension/startDebugging", { configName }, 15_000);
|
|
575
|
+
}
|
|
576
|
+
async stopDebugging() {
|
|
577
|
+
return this.requestOrNull("extension/stopDebugging");
|
|
578
|
+
}
|
|
579
|
+
// --- Decorations ---
|
|
580
|
+
async setDecorations(id, file, decorations) {
|
|
581
|
+
return this.requestOrNull("extension/setDecorations", {
|
|
582
|
+
id,
|
|
583
|
+
file,
|
|
584
|
+
decorations,
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
async clearDecorations(id) {
|
|
588
|
+
return this.requestOrNull("extension/clearDecorations", { id });
|
|
589
|
+
}
|
|
590
|
+
// --- VS Code Commands ---
|
|
591
|
+
async executeVSCodeCommand(command, args) {
|
|
592
|
+
return this.requestOrNull("extension/executeVSCodeCommand", {
|
|
593
|
+
command,
|
|
594
|
+
args,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
async listVSCodeCommands(filter) {
|
|
598
|
+
return this.proxy("extension/listVSCodeCommands", { filter });
|
|
599
|
+
}
|
|
600
|
+
// --- Workspace Settings ---
|
|
601
|
+
async getWorkspaceSettings(section, target) {
|
|
602
|
+
return this.requestOrNull("extension/getWorkspaceSettings", {
|
|
603
|
+
section,
|
|
604
|
+
target,
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
async setWorkspaceSetting(key, value, target) {
|
|
608
|
+
return this.requestOrNull("extension/setWorkspaceSetting", {
|
|
609
|
+
key,
|
|
610
|
+
value,
|
|
611
|
+
target,
|
|
612
|
+
});
|
|
613
|
+
}
|
|
614
|
+
// --- Clipboard ---
|
|
615
|
+
async readClipboard() {
|
|
616
|
+
return this.requestOrNull("extension/readClipboard");
|
|
617
|
+
}
|
|
618
|
+
async writeClipboard(text) {
|
|
619
|
+
return this.requestOrNull("extension/writeClipboard", { text });
|
|
620
|
+
}
|
|
621
|
+
// --- Inlay Hints ---
|
|
622
|
+
async getInlayHints(file, startLine, endLine) {
|
|
623
|
+
return this.requestOrNull("extension/getInlayHints", {
|
|
624
|
+
file,
|
|
625
|
+
startLine,
|
|
626
|
+
endLine,
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
// --- Type Hierarchy ---
|
|
630
|
+
async getTypeHierarchy(file, line, column, direction, maxResults) {
|
|
631
|
+
return this.requestOrNull("extension/getTypeHierarchy", { file, line, column, direction, maxResults }, 15_000);
|
|
632
|
+
}
|
|
633
|
+
// --- Tasks ---
|
|
634
|
+
async listTasks() {
|
|
635
|
+
return this.requestOrNull("extension/listTasks", undefined, 15_000);
|
|
636
|
+
}
|
|
637
|
+
async runTask(name, type, timeoutMs) {
|
|
638
|
+
const requestTimeout = (timeoutMs ?? 60_000) + 5_000;
|
|
639
|
+
return this.requestOrNull("extension/runTask", { name, type, timeoutMs }, requestTimeout);
|
|
640
|
+
}
|
|
641
|
+
// --- Workspace Folders ---
|
|
642
|
+
async getWorkspaceFolders() {
|
|
643
|
+
return this.proxy("extension/getWorkspaceFolders");
|
|
644
|
+
}
|
|
645
|
+
// --- Notebook ---
|
|
646
|
+
async getNotebookCells(file) {
|
|
647
|
+
return this.requestOrNull("extension/getNotebookCells", { file });
|
|
648
|
+
}
|
|
649
|
+
async runNotebookCell(file, cellIndex, timeoutMs) {
|
|
650
|
+
const requestTimeout = (timeoutMs ?? 30_000) + 5_000;
|
|
651
|
+
return this.requestOrNull("extension/runNotebookCell", { file, cellIndex, timeoutMs }, requestTimeout);
|
|
652
|
+
}
|
|
653
|
+
async getNotebookOutput(file, cellIndex) {
|
|
654
|
+
return this.requestOrNull("extension/getNotebookOutput", {
|
|
655
|
+
file,
|
|
656
|
+
cellIndex,
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
// --- Diagnostics Helpers ---
|
|
660
|
+
getCachedDiagnostics(file) {
|
|
661
|
+
if (file)
|
|
662
|
+
return this.latestDiagnostics.get(file) ?? [];
|
|
663
|
+
return Array.from(this.latestDiagnostics.values()).flat();
|
|
664
|
+
}
|
|
665
|
+
addDiagnosticsListener(listener) {
|
|
666
|
+
this.diagnosticsListeners.add(listener);
|
|
667
|
+
return () => this.diagnosticsListeners.delete(listener);
|
|
668
|
+
}
|
|
669
|
+
/** Notify the extension about Claude Code connection state changes */
|
|
670
|
+
notifyClaudeConnectionState(connected) {
|
|
671
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
672
|
+
return;
|
|
673
|
+
void safeSend(this.ws, JSON.stringify({
|
|
674
|
+
jsonrpc: "2.0",
|
|
675
|
+
method: "bridge/claudeConnectionChanged",
|
|
676
|
+
params: { connected },
|
|
677
|
+
}), this.logger);
|
|
678
|
+
}
|
|
679
|
+
getCircuitBreakerState() {
|
|
680
|
+
return {
|
|
681
|
+
suspended: Date.now() < this.extensionSuspendedUntil,
|
|
682
|
+
suspendedUntil: this.extensionSuspendedUntil,
|
|
683
|
+
failures: this.extensionFailures,
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
isConnected() {
|
|
687
|
+
return this.connected;
|
|
688
|
+
}
|
|
689
|
+
disconnect() {
|
|
690
|
+
if (this.ws) {
|
|
691
|
+
this.ws.close(1000, "Bridge shutting down");
|
|
692
|
+
this.ws = null;
|
|
693
|
+
}
|
|
694
|
+
this.connected = false;
|
|
695
|
+
this.rejectAllPending("Bridge shutting down");
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
//# sourceMappingURL=extensionClient.js.map
|