From 3b2a4ba27a330ec0c069c11ed626abfc767bd311 Mon Sep 17 00:00:00 2001 From: Shreya Keshive Date: Fri, 12 Dec 2025 12:39:15 -0500 Subject: [PATCH] refactor(ide ext): Update port file name + switch to 1-based index for characters + remove truncation text (#10501) --- package-lock.json | 41 +++++---- .../vscode-ide-companion/src/diff-manager.ts | 20 +---- .../src/ide-server.test.ts | 89 +++++-------------- .../vscode-ide-companion/src/ide-server.ts | 85 +++++++----------- .../src/open-files-manager.test.ts | 4 +- .../src/open-files-manager.ts | 5 +- 6 files changed, 88 insertions(+), 156 deletions(-) diff --git a/package-lock.json b/package-lock.json index e2ccb9b6a4..bac0b44308 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2427,6 +2427,7 @@ "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.2", @@ -2607,6 +2608,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -2640,6 +2642,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.0.1.tgz", "integrity": "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, @@ -3008,6 +3011,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.0.1.tgz", "integrity": "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" @@ -3041,6 +3045,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-metrics/-/sdk-metrics-2.0.1.tgz", "integrity": "sha512-wf8OaJoSnujMAHWR3g+/hGvNcsC16rf9s1So4JlMiFaFHiE4HpIA3oUh+uWZQ7CNuK8gVW/pQSkgoa5HkkOl0g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" @@ -3093,6 +3098,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.0.1.tgz", "integrity": "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", @@ -4303,6 +4309,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4590,6 +4597,7 @@ "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.35.0", "@typescript-eslint/types": "8.35.0", @@ -5513,6 +5521,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5948,8 +5957,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/array-includes": { "version": "3.1.9", @@ -7213,7 +7221,6 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", - "peer": true, "dependencies": { "safe-buffer": "5.2.1" }, @@ -8229,6 +8236,7 @@ "integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -8818,7 +8826,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -8828,7 +8835,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -8838,7 +8844,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -9092,7 +9097,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -9111,7 +9115,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -9120,15 +9123,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/finalhandler/node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -10340,6 +10341,7 @@ "resolved": "https://registry.npmjs.org/@jrichman/ink/-/ink-6.4.6.tgz", "integrity": "sha512-QHl6l1cl3zPCaRMzt9TUbTX6Q5SzvkGEZDDad0DmSf5SPmT1/90k6pGPejEvDCJprkitwObXpPaTWGHItqsy4g==", "license": "MIT", + "peer": true, "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.1", "ansi-escapes": "^7.0.0", @@ -13447,8 +13449,7 @@ "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/path-type": { "version": "3.0.0", @@ -14019,6 +14020,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -14029,6 +14031,7 @@ "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" @@ -16247,6 +16250,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16411,7 +16415,8 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true, - "license": "0BSD" + "license": "0BSD", + "peer": true }, "node_modules/tsx": { "version": "4.20.3", @@ -16419,6 +16424,7 @@ "integrity": "sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" @@ -16603,6 +16609,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -16765,7 +16772,6 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4.0" } @@ -16821,6 +16827,7 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -16937,6 +16944,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -16950,6 +16958,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -17656,6 +17665,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -18217,6 +18227,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/packages/vscode-ide-companion/src/diff-manager.ts b/packages/vscode-ide-companion/src/diff-manager.ts index cf2dcb31b4..362049e924 100644 --- a/packages/vscode-ide-companion/src/diff-manager.ts +++ b/packages/vscode-ide-companion/src/diff-manager.ts @@ -6,7 +6,7 @@ import { IdeDiffAcceptedNotificationSchema, - IdeDiffClosedNotificationSchema, + IdeDiffRejectedNotificationSchema, } from '@google/gemini-cli-core/src/ide/types.js'; import { type JSONRPCNotification } from '@modelcontextprotocol/sdk/types.js'; import * as path from 'node:path'; @@ -134,7 +134,7 @@ export class DiffManager { /** * Closes an open diff view for a specific file. */ - async closeDiff(filePath: string, suppressNotification = false) { + async closeDiff(filePath: string) { let uriToClose: vscode.Uri | undefined; for (const [uriString, diffInfo] of this.diffDocuments.entries()) { if (diffInfo.originalFilePath === filePath) { @@ -147,18 +147,6 @@ export class DiffManager { const rightDoc = await vscode.workspace.openTextDocument(uriToClose); const modifiedContent = rightDoc.getText(); await this.closeDiffEditor(uriToClose); - if (!suppressNotification) { - this.onDidChangeEmitter.fire( - IdeDiffClosedNotificationSchema.parse({ - jsonrpc: '2.0', - method: 'ide/diffClosed', - params: { - filePath, - content: modifiedContent, - }, - }), - ); - } return modifiedContent; } return; @@ -204,9 +192,9 @@ export class DiffManager { await this.closeDiffEditor(rightDocUri); this.onDidChangeEmitter.fire( - IdeDiffClosedNotificationSchema.parse({ + IdeDiffRejectedNotificationSchema.parse({ jsonrpc: '2.0', - method: 'ide/diffClosed', + method: 'ide/diffRejected', params: { filePath: diffInfo.originalFilePath, content: modifiedContent, diff --git a/packages/vscode-ide-companion/src/ide-server.test.ts b/packages/vscode-ide-companion/src/ide-server.test.ts index 3fee4f6a17..95f1c27a63 100644 --- a/packages/vscode-ide-companion/src/ide-server.test.ts +++ b/packages/vscode-ide-companion/src/ide-server.test.ts @@ -27,6 +27,7 @@ vi.mock('node:fs/promises', () => ({ writeFile: vi.fn(() => Promise.resolve(undefined)), unlink: vi.fn(() => Promise.resolve(undefined)), chmod: vi.fn(() => Promise.resolve(undefined)), + mkdir: vi.fn(() => Promise.resolve(undefined)), })); vi.mock('node:os', async (importOriginal) => { @@ -136,28 +137,23 @@ describe('IDEServer', () => { const port = getPortFromMock(replaceMock); const expectedPortFile = path.join( '/tmp', - `gemini-ide-server-${port}.json`, - ); - const expectedPpidPortFile = path.join( - '/tmp', - `gemini-ide-server-${process.ppid}.json`, + 'gemini', + 'ide', + `gemini-ide-server-${process.ppid}-${port}.json`, ); const expectedContent = JSON.stringify({ port: parseInt(port, 10), workspacePath: expectedWorkspacePaths, - ppid: process.ppid, authToken: 'test-auth-token', }); + expect(fs.mkdir).toHaveBeenCalledWith(path.join('/tmp', 'gemini', 'ide'), { + recursive: true, + }); expect(fs.writeFile).toHaveBeenCalledWith( expectedPortFile, expectedContent, ); - expect(fs.writeFile).toHaveBeenCalledWith( - expectedPpidPortFile, - expectedContent, - ); expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600); - expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600); }); it('should set a single folder path', async () => { @@ -174,28 +170,20 @@ describe('IDEServer', () => { const port = getPortFromMock(replaceMock); const expectedPortFile = path.join( '/tmp', - `gemini-ide-server-${port}.json`, - ); - const expectedPpidPortFile = path.join( - '/tmp', - `gemini-ide-server-${process.ppid}.json`, + 'gemini', + 'ide', + `gemini-ide-server-${process.ppid}-${port}.json`, ); const expectedContent = JSON.stringify({ port: parseInt(port, 10), workspacePath: '/foo/bar', - ppid: process.ppid, authToken: 'test-auth-token', }); expect(fs.writeFile).toHaveBeenCalledWith( expectedPortFile, expectedContent, ); - expect(fs.writeFile).toHaveBeenCalledWith( - expectedPpidPortFile, - expectedContent, - ); expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600); - expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600); }); it('should set an empty string if no folders are open', async () => { @@ -212,28 +200,20 @@ describe('IDEServer', () => { const port = getPortFromMock(replaceMock); const expectedPortFile = path.join( '/tmp', - `gemini-ide-server-${port}.json`, - ); - const expectedPpidPortFile = path.join( - '/tmp', - `gemini-ide-server-${process.ppid}.json`, + 'gemini', + 'ide', + `gemini-ide-server-${process.ppid}-${port}.json`, ); const expectedContent = JSON.stringify({ port: parseInt(port, 10), workspacePath: '', - ppid: process.ppid, authToken: 'test-auth-token', }); expect(fs.writeFile).toHaveBeenCalledWith( expectedPortFile, expectedContent, ); - expect(fs.writeFile).toHaveBeenCalledWith( - expectedPpidPortFile, - expectedContent, - ); expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600); - expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600); }); it('should update the path when workspace folders change', async () => { @@ -268,28 +248,20 @@ describe('IDEServer', () => { const port = getPortFromMock(replaceMock); const expectedPortFile = path.join( '/tmp', - `gemini-ide-server-${port}.json`, - ); - const expectedPpidPortFile = path.join( - '/tmp', - `gemini-ide-server-${process.ppid}.json`, + 'gemini', + 'ide', + `gemini-ide-server-${process.ppid}-${port}.json`, ); const expectedContent = JSON.stringify({ port: parseInt(port, 10), workspacePath: expectedWorkspacePaths, - ppid: process.ppid, authToken: 'test-auth-token', }); expect(fs.writeFile).toHaveBeenCalledWith( expectedPortFile, expectedContent, ); - expect(fs.writeFile).toHaveBeenCalledWith( - expectedPpidPortFile, - expectedContent, - ); expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600); - expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600); // Simulate removing a folder vscodeMock.workspace.workspaceFolders = [{ uri: { fsPath: '/baz/qux' } }]; @@ -302,38 +274,31 @@ describe('IDEServer', () => { const expectedContent2 = JSON.stringify({ port: parseInt(port, 10), workspacePath: '/baz/qux', - ppid: process.ppid, authToken: 'test-auth-token', }); expect(fs.writeFile).toHaveBeenCalledWith( expectedPortFile, expectedContent2, ); - expect(fs.writeFile).toHaveBeenCalledWith( - expectedPpidPortFile, - expectedContent2, - ); expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600); - expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600); }); it('should clear env vars and delete port file on stop', async () => { await ideServer.start(mockContext); const replaceMock = mockContext.environmentVariableCollection.replace; const port = getPortFromMock(replaceMock); - const portFile = path.join('/tmp', `gemini-ide-server-${port}.json`); - const ppidPortFile = path.join( + const portFile = path.join( '/tmp', - `gemini-ide-server-${process.ppid}.json`, + 'gemini', + 'ide', + `gemini-ide-server-${process.ppid}-${port}.json`, ); expect(fs.writeFile).toHaveBeenCalledWith(portFile, expect.any(String)); - expect(fs.writeFile).toHaveBeenCalledWith(ppidPortFile, expect.any(String)); await ideServer.stop(); expect(mockContext.environmentVariableCollection.clear).toHaveBeenCalled(); expect(fs.unlink).toHaveBeenCalledWith(portFile); - expect(fs.unlink).toHaveBeenCalledWith(ppidPortFile); }); it.skipIf(process.platform !== 'win32')( @@ -356,28 +321,20 @@ describe('IDEServer', () => { const port = getPortFromMock(replaceMock); const expectedPortFile = path.join( '/tmp', - `gemini-ide-server-${port}.json`, - ); - const expectedPpidPortFile = path.join( - '/tmp', - `gemini-ide-server-${process.ppid}.json`, + 'gemini', + 'ide', + `gemini-ide-server-${process.ppid}-${port}.json`, ); const expectedContent = JSON.stringify({ port: parseInt(port, 10), workspacePath: expectedWorkspacePaths, - ppid: process.ppid, authToken: 'test-auth-token', }); expect(fs.writeFile).toHaveBeenCalledWith( expectedPortFile, expectedContent, ); - expect(fs.writeFile).toHaveBeenCalledWith( - expectedPpidPortFile, - expectedContent, - ); expect(fs.chmod).toHaveBeenCalledWith(expectedPortFile, 0o600); - expect(fs.chmod).toHaveBeenCalledWith(expectedPpidPortFile, 0o600); }, ); diff --git a/packages/vscode-ide-companion/src/ide-server.ts b/packages/vscode-ide-companion/src/ide-server.ts index 943e68f8fe..f29bae29fd 100644 --- a/packages/vscode-ide-companion/src/ide-server.ts +++ b/packages/vscode-ide-companion/src/ide-server.ts @@ -43,9 +43,8 @@ const IDE_AUTH_TOKEN_ENV_VAR = 'GEMINI_CLI_IDE_AUTH_TOKEN'; interface WritePortAndWorkspaceArgs { context: vscode.ExtensionContext; port: number; - portFile: string; - ppidPortFile: string; authToken: string; + portFile: string | undefined; log: (message: string) => void; } @@ -53,7 +52,6 @@ async function writePortAndWorkspace({ context, port, portFile, - ppidPortFile, authToken, log, }: WritePortAndWorkspaceArgs): Promise { @@ -76,23 +74,21 @@ async function writePortAndWorkspace({ authToken, ); + if (!portFile) { + log('Missing portFile, cannot write port and workspace info.'); + return; + } + const content = JSON.stringify({ port, workspacePath, - ppid: process.ppid, authToken, }); log(`Writing port file to: ${portFile}`); - log(`Writing ppid port file to: ${ppidPortFile}`); try { - await Promise.all([ - fs.writeFile(portFile, content).then(() => fs.chmod(portFile, 0o600)), - fs - .writeFile(ppidPortFile, content) - .then(() => fs.chmod(ppidPortFile, 0o600)), - ]); + await fs.writeFile(portFile, content).then(() => fs.chmod(portFile, 0o600)); } catch (err) { const message = err instanceof Error ? err.message : String(err); log(`Failed to write port to file: ${message}`); @@ -121,7 +117,7 @@ export class IDEServer { private context: vscode.ExtensionContext | undefined; private log: (message: string) => void; private portFile: string | undefined; - private ppidPortFile: string | undefined; + private port: number | undefined; private authToken: string | undefined; private transports: { [sessionId: string]: StreamableHTTPServerTransport } = @@ -344,26 +340,28 @@ export class IDEServer { const address = (this.server as HTTPServer).address(); if (address && typeof address !== 'string') { this.port = address.port; - this.portFile = path.join( - os.tmpdir(), - `gemini-ide-server-${this.port}.json`, - ); - this.ppidPortFile = path.join( - os.tmpdir(), - `gemini-ide-server-${process.ppid}.json`, - ); this.log(`IDE server listening on http://127.0.0.1:${this.port}`); - - if (this.authToken) { - await writePortAndWorkspace({ - context, - port: this.port, - portFile: this.portFile, - ppidPortFile: this.ppidPortFile, - authToken: this.authToken, - log: this.log, - }); + let portFile: string | undefined; + try { + const portDir = path.join(os.tmpdir(), 'gemini', 'ide'); + await fs.mkdir(portDir, { recursive: true }); + portFile = path.join( + portDir, + `gemini-ide-server-${process.ppid}-${this.port}.json`, + ); + this.portFile = portFile; + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + this.log(`Failed to create IDE port file: ${message}`); } + + await writePortAndWorkspace({ + context, + port: this.port, + portFile: this.portFile, + authToken: this.authToken ?? '', + log: this.log, + }); } resolve(); }); @@ -392,19 +390,11 @@ export class IDEServer { } async syncEnvVars(): Promise { - if ( - this.context && - this.server && - this.port && - this.portFile && - this.ppidPortFile && - this.authToken - ) { + if (this.context && this.server && this.port && this.authToken) { await writePortAndWorkspace({ context: this.context, port: this.port, portFile: this.portFile, - ppidPortFile: this.ppidPortFile, authToken: this.authToken, log: this.log, }); @@ -437,13 +427,6 @@ export class IDEServer { // Ignore errors if the file doesn't exist. } } - if (this.ppidPortFile) { - try { - await fs.unlink(this.ppidPortFile); - } catch (_err) { - // Ignore errors if the file doesn't exist. - } - } } } @@ -477,15 +460,9 @@ const createMcpServer = ( description: '(IDE Tool) Close an open diff view for a specific file.', inputSchema: CloseDiffRequestSchema.shape, }, - async ({ - filePath, - suppressNotification, - }: z.infer) => { + async ({ filePath }: z.infer) => { log(`Received closeDiff request for filePath: ${filePath}`); - const content = await diffManager.closeDiff( - filePath, - suppressNotification, - ); + const content = await diffManager.closeDiff(filePath); const response = { content: content ?? undefined }; return { content: [ diff --git a/packages/vscode-ide-companion/src/open-files-manager.test.ts b/packages/vscode-ide-companion/src/open-files-manager.test.ts index 0b1ada822f..b6dc46f6a4 100644 --- a/packages/vscode-ide-companion/src/open-files-manager.test.ts +++ b/packages/vscode-ide-companion/src/open-files-manager.test.ts @@ -316,7 +316,7 @@ describe('OpenFilesManager', () => { await vi.advanceTimersByTimeAsync(100); const file = manager.state.workspaceState!.openFiles![0]; - expect(file.cursor).toEqual({ line: 11, character: 20 }); + expect(file.cursor).toEqual({ line: 11, character: 21 }); }); it('updates the selected text on selection change', async () => { @@ -355,7 +355,7 @@ describe('OpenFilesManager', () => { const manager = new OpenFilesManager(context); const uri = getUri('/test/file1.txt'); const longText = 'a'.repeat(20000); - const truncatedText = longText.substring(0, 16384) + '... [TRUNCATED]'; + const truncatedText = longText.substring(0, 16384); const selection = { active: { line: 10, character: 20 }, diff --git a/packages/vscode-ide-companion/src/open-files-manager.ts b/packages/vscode-ide-companion/src/open-files-manager.ts index c8023bf418..3fae487ad3 100644 --- a/packages/vscode-ide-companion/src/open-files-manager.ts +++ b/packages/vscode-ide-companion/src/open-files-manager.ts @@ -149,15 +149,14 @@ export class OpenFilesManager { file.cursor = editor.selection.active ? { line: editor.selection.active.line + 1, - character: editor.selection.active.character, + character: editor.selection.active.character + 1, } : undefined; let selectedText: string | undefined = editor.document.getText(editor.selection) || undefined; if (selectedText && selectedText.length > MAX_SELECTED_TEXT_LENGTH) { - selectedText = - selectedText.substring(0, MAX_SELECTED_TEXT_LENGTH) + '... [TRUNCATED]'; + selectedText = selectedText.substring(0, MAX_SELECTED_TEXT_LENGTH); } file.selectedText = selectedText; }