fix(e2b): close remote lifecycle gaps

This commit is contained in:
Tianyi Cui
2026-07-28 16:26:20 +08:00
parent e64d40837c
commit 3dea36f1ce
22 changed files with 568 additions and 181 deletions
+11
View File
@@ -200,6 +200,16 @@ export async function readE2BSource(
return { canonicalPath, text }
}
/**
* Encode one absolute remote Linux path as a host-independent file URI.
* @param path - Canonical POSIX path inside E2B.
* @returns The equivalent percent-encoded file URI.
*/
export function e2bFileUri(path: string): string {
if (!posix.isAbsolute(path)) throw new Error(`lsp-e2b: expected an absolute remote path, received ${JSON.stringify(path)}`)
return `file://${path.split('/').map(segment => encodeURIComponent(segment)).join('/')}`
}
/* jscpd:ignore-start -- Provider identity mirrors the seam while remote source and process ownership stay local. */
/** One pooled remote provider with an isolated server per canonical workspace. */
export class E2BLspProvider implements LspProvider {
@@ -298,6 +308,7 @@ export class E2BLspProvider implements LspProvider {
shutdownTimeoutMs: this.config.shutdownTimeoutMs,
killGraceMs: this.config.killGraceMs,
clientProcessId: null,
pathToFileUri: e2bFileUri,
}, (spec: SubprocessSpawnSpec) => {
const originalArgv = Buffer.from(JSON.stringify(spec.argv)).toString('base64')
const inner = this.subprocess.spawn({
@@ -69,6 +69,7 @@ import {
E2BLspProvider,
apply,
canonicalizeE2BWorkspace,
e2bFileUri,
readE2BSource,
} from '@deepseek-ai/dsh-lsp-e2b'
import type { LspE2BServerConfig } from '@deepseek-ai/dsh-lsp-e2b'
@@ -234,6 +235,10 @@ describe('E2BLspProvider pooling and lifecycle', () => {
await expect(current.query(query())).resolves.toEqual({ kind: 'hover', hover: { contents: 'ok' } })
expect(mockedLsp.FakeLspInstance.instances).toHaveLength(1)
expect(mockedLsp.FakeLspInstance.instances[0]?.spec).toMatchObject({ clientProcessId: null, cwd: '/workspace' })
expect(e2bFileUri('/workspace/a b#c.ts')).toBe('file:///workspace/a%20b%23c.ts')
expect(() => e2bFileUri('relative.ts')).toThrow('absolute remote path')
const pathToFileUri = mockedLsp.FakeLspInstance.instances[0]?.spec.pathToFileUri as (path: string) => string
expect(pathToFileUri('/workspace/a b#c.ts')).toBe('file:///workspace/a%20b%23c.ts')
expect(spawn).toHaveBeenCalledWith(expect.objectContaining({
argv: ['/usr/bin/node', '/workspace/.dsh-e2b/lsp-proxy.mjs', expect.any(String)],
stdio: { stdin: 'pipe', stdout: 'pipe', stderr: { maxBytes: 128 } },