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

@@ -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 = []