fix(e2b): close proven provider boundary gaps

This commit is contained in:
Tianyi Cui
2026-07-29 22:04:55 +08:00
parent 917a7493f7
commit 677541d123
16 changed files with 167 additions and 70 deletions

View File

@@ -116,6 +116,7 @@ class FakeSandbox {
environmentRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
processGroupId = '4242\n'
exitStatus = ''
statusReads = 0
readonly processGroupReads: string[] = []
afterStatusRead: (() => void) | undefined
beforeProbe: (() => void) | undefined
@@ -222,6 +223,7 @@ class FakeSandbox {
this.statusError = undefined
throw error
}
this.statusReads += 1
this.afterStatusRead?.()
return this.exitStatus
},
@@ -415,9 +417,10 @@ describe('E2BSubprocessHandle', () => {
expect(command).not.toContain('explicit-secret')
expect(command).not.toContain('hyphen-value')
expect(command).not.toContain('${!dsh_e2b_name}')
expect(fake.commandsSeen).toContain(
'set -o pipefail; printf \'%s\' "$PWD" | base64 -w 0; printf \'\\n\'; env -0 | base64 -w 0',
)
const environmentProbe = fake.commandsSeen.find(value => value.includes('env -0 | base64'))
expect(environmentProbe).toContain('getent passwd "$(id -u)"')
expect(environmentProbe).toContain('test -n "$dsh_e2b_home" -a -d "$dsh_e2b_home"')
expect(environmentProbe).not.toContain('"$PWD"')
expect(command).toContain('mapfile -d')
expect(command).toContain('dsh_e2b_node="$(command -v node)"')
expect(command).toContain('"$dsh_e2b_env_bin" -i "$dsh_e2b_node" -e')
@@ -535,11 +538,13 @@ describe('E2BSubprocessHandle', () => {
await new Promise(resolve => setTimeout(resolve, 50))
expect(settled).toBe(false)
expect(fake.handle.disconnects).toBe(0)
expect(fake.statusReads).toBe(0)
await fake.stdout('complete protocol frame')
fake.finish()
await expect(handle.done).resolves.toEqual({ exitCode: 0, signal: null })
expect(output).toBe('complete protocol frame')
expect(fake.statusReads).toBe(1)
})
it('accepts clean encoder completion inside the output-drain grace', async () => {

View File

@@ -102,6 +102,9 @@ class FakeTerminalSandbox {
sendError: unknown
commandFailure: unknown
makeDirRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
sendInputRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
foregroundRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
signalRequest: ((signal: AbortSignal | undefined) => Promise<void>) | undefined
sessionGroupsFailure: unknown
foregroundFailure: unknown
termFailure: unknown
@@ -180,6 +183,8 @@ class FakeTerminalSandbox {
return { exitCode: 0, stdout: this.sessionId, stderr: '' }
}
if (command.startsWith('ps -o tpgid=')) {
await this.foregroundRequest?.(options?.signal)
options?.signal?.throwIfAborted()
if (this.foregroundFailure !== undefined) throw this.foregroundFailure
return { exitCode: 0, stdout: this.foreground, stderr: '' }
}
@@ -197,6 +202,10 @@ class FakeTerminalSandbox {
this.handle.fail(143)
}
}
if (command.startsWith('kill -INT -- ')) {
await this.signalRequest?.(options?.signal)
options?.signal?.throwIfAborted()
}
if (command.startsWith('kill -KILL -- ') && this.clearOnKill) this.groups = []
return { exitCode: 0, stdout: '', stderr: '' }
},
@@ -211,6 +220,8 @@ class FakeTerminalSandbox {
return this.handle.asHandle()
},
sendInput: async (pid: number, data: Uint8Array, options?: { signal?: AbortSignal }): Promise<void> => {
options?.signal?.throwIfAborted()
await this.sendInputRequest?.(options?.signal)
options?.signal?.throwIfAborted()
this.inputs.push({ pid, data: Buffer.from(data) })
if (this.sendError !== undefined) throw this.sendError
@@ -257,6 +268,19 @@ function spec(overrides: Partial<SubprocessTerminalSpawnSpec> = {}): SubprocessT
}
}
function holdRequestUntilAbort(started: PromiseWithResolvers<AbortSignal>) {
return async (signal: AbortSignal | undefined): Promise<void> => {
if (signal === undefined) throw new Error('expected an operation signal')
signal.throwIfAborted()
started.resolve(signal)
await new Promise<void>((_resolve, reject) => {
signal.addEventListener('abort', () => {
reject(signal.reason instanceof Error ? signal.reason : new Error(String(signal.reason)))
}, { once: true })
})
}
}
describe('E2B terminal allocation', () => {
it('hides bootstrap-shell bytes and preserves requested-shell bytes across the output boundary', async () => {
const fake = new FakeTerminalSandbox()
@@ -544,6 +568,43 @@ describe('E2B terminal allocation', () => {
})
describe('E2B terminal lifecycle', () => {
it('aborts and joins in-flight terminal operations before cleanup', async () => {
const fake = new FakeTerminalSandbox()
const terminal = await spawnE2BTerminal(runtime(fake), spec(), '/runtime/in-flight-operations')
const writeStarted = Promise.withResolvers<AbortSignal>()
const inspectStarted = Promise.withResolvers<AbortSignal>()
const signalStarted = Promise.withResolvers<AbortSignal>()
fake.sendInputRequest = holdRequestUntilAbort(writeStarted)
let foregroundRequests = 0
fake.foregroundRequest = async (signal) => {
foregroundRequests += 1
if (foregroundRequests === 1) await holdRequestUntilAbort(inspectStarted)(signal)
}
let signalCompleted = false
fake.signalRequest = async (operationSignal) => {
await holdRequestUntilAbort(signalStarted)(operationSignal)
signalCompleted = true
}
const write = terminal.write('late input')
const inspect = terminal.inspectForeground()
await Promise.all([writeStarted.promise, inspectStarted.promise])
const signal = terminal.signalForeground('SIGINT')
await signalStarted.promise
const terminating = terminal.terminate()
await expect(write).rejects.toThrow('terminal is terminating')
await expect(inspect).rejects.toThrow('terminal is terminating')
await expect(signal).rejects.toThrow('terminal is terminating')
await terminating
expect(signalCompleted).toBe(false)
expect(fake.inputs).toHaveLength(1)
const commandCount = fake.commands.length
await expect(terminal.write('after termination')).rejects.toThrow('terminal is terminating')
await expect(terminal.inspectForeground()).rejects.toThrow('terminal is terminating')
await expect(terminal.signalForeground('SIGINT')).rejects.toThrow('terminal is terminating')
expect(fake.commands).toHaveLength(commandCount)
})
it('maps ordinary exits, closes output, and reports an absent foreground after exit', async () => {
const fake = new FakeTerminalSandbox()
fake.groups = []