mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
refactor(e2b): keep provider layer substrate-only
This commit is contained in:
@@ -26,6 +26,8 @@ function remoteEnvironmentEntries(raw: string): Array<readonly [string, string]>
|
||||
* @returns the complete NUL-delimited UTF-8 environment.
|
||||
*/
|
||||
export async function readRemoteEnvironment(sandbox: Sandbox, signal?: AbortSignal): Promise<string> {
|
||||
// TODO(e2b-replace-environment): Remove this ambient probe when E2B can start
|
||||
// a command with a replacement environment instead of merged overrides.
|
||||
const result = await sandbox.commands.run(
|
||||
'set -o pipefail; printf \'%s\' "$PWD" | base64 -w 0; printf \'\\n\'; env -0 | base64 -w 0',
|
||||
{ envs: e2bControlEnvs(), ...(signal === undefined ? {} : { signal }) },
|
||||
|
||||
@@ -61,8 +61,7 @@ export class E2BSubprocessService extends SubprocessService {
|
||||
}))
|
||||
}
|
||||
for (const terminal of terminals) {
|
||||
terminal.terminate()
|
||||
pending.push(terminal.waitForExit().then(() => { this.terminals.delete(terminal) }))
|
||||
pending.push(terminal.terminate().then(() => { this.terminals.delete(terminal) }))
|
||||
}
|
||||
for (const cleanup of failedTerminalSetupCleanups) {
|
||||
pending.push(cleanup().then(() => { this.failedTerminalSetupCleanups.delete(cleanup) }))
|
||||
@@ -125,7 +124,9 @@ export class E2BSubprocessService extends SubprocessService {
|
||||
await handle.waitForExit()
|
||||
this.live.delete(handle)
|
||||
}
|
||||
void handle.done.then(release, release).catch(() => {})
|
||||
void handle.done.then(release, release).catch((_automaticReleaseFailure: unknown) => {
|
||||
// Retain the handle so service disposal can retry its cleanup transaction.
|
||||
})
|
||||
return handle
|
||||
}
|
||||
|
||||
@@ -158,16 +159,17 @@ export class E2BSubprocessService extends SubprocessService {
|
||||
)
|
||||
this.terminals.add(terminal)
|
||||
if (this.isDisposing()) {
|
||||
terminal.terminate()
|
||||
await terminal.waitForExit()
|
||||
await terminal.terminate()
|
||||
this.terminals.delete(terminal)
|
||||
throw new Error('subprocess-e2b: service disposed during terminal setup')
|
||||
}
|
||||
const release = async (): Promise<void> => {
|
||||
await terminal.waitForExit()
|
||||
await terminal.terminate()
|
||||
this.terminals.delete(terminal)
|
||||
}
|
||||
void terminal.done.then(release, release).catch(() => {})
|
||||
void terminal.done.then(release, release).catch((_automaticReleaseFailure: unknown) => {
|
||||
// Retain the terminal so service disposal can retry its cleanup transaction.
|
||||
})
|
||||
return terminal
|
||||
} finally {
|
||||
this.terminalSetups.delete(setup.promise)
|
||||
|
||||
@@ -726,7 +726,9 @@ export class E2BSubprocessHandle implements SubprocessHandle {
|
||||
// A spill mode is a collect mode, so construction always created its reader.
|
||||
const size = (reader as E2BOutputReader).size
|
||||
if (this.outputDrainExpired || size <= mode.maxBytes || size > mode.spill.maxBytes) {
|
||||
removals.push(sandbox.files.remove(path).catch(() => {}))
|
||||
removals.push(sandbox.files.remove(path).catch((_adapterPrivateSpillRemovalFailure: unknown) => {
|
||||
// The command outcome is authoritative; a retained sandbox tolerates private residue.
|
||||
}))
|
||||
}
|
||||
}
|
||||
collect(this.spec.stdio.stdout, this.stdoutReader, this.paths.stdout)
|
||||
|
||||
@@ -12,7 +12,6 @@ import {
|
||||
quoteE2BShellArg,
|
||||
} from '@deepseek-ai/dsh-e2b'
|
||||
import type { CommandHandle, CommandResult, Sandbox } from '@deepseek-ai/dsh-e2b'
|
||||
import { SubprocessTerminalLifecycle } from '@deepseek-ai/dsh-subprocess'
|
||||
import type {
|
||||
SubprocessOutcome,
|
||||
SubprocessTerminalForeground,
|
||||
@@ -345,7 +344,7 @@ export class E2BTerminalHandle implements SubprocessTerminalHandle {
|
||||
readonly done: Promise<SubprocessOutcome>
|
||||
|
||||
private topLevelExited = false
|
||||
private readonly lifecycle: SubprocessTerminalLifecycle
|
||||
private cleanup: Promise<void> | undefined
|
||||
private terminationSignal: NodeJS.Signals | null = null
|
||||
|
||||
constructor(
|
||||
@@ -357,21 +356,17 @@ export class E2BTerminalHandle implements SubprocessTerminalHandle {
|
||||
private readonly controlEnvs: Record<string, string>,
|
||||
private readonly stateDir: string,
|
||||
private readonly graceMs: number,
|
||||
signal?: AbortSignal,
|
||||
) {
|
||||
this.pid = handle.pid
|
||||
this.done = this.waitForCommand()
|
||||
this.lifecycle = new SubprocessTerminalLifecycle({
|
||||
done: this.done,
|
||||
cleanup: () => this.closeOnce(),
|
||||
signal,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO(e2b-pgid-identity): Replace retained numeric PTY/session ids when E2B
|
||||
// exposes identity-bound input, foreground-signal, and cleanup operations.
|
||||
/** @inheritdoc */
|
||||
async write(data: Uint8Array): Promise<void> {
|
||||
async write(data: string): Promise<void> {
|
||||
if (this.topLevelExited) throw new Error('terminal process has exited')
|
||||
await this.sandbox.pty.sendInput(this.pid, data)
|
||||
await this.sandbox.pty.sendInput(this.pid, Buffer.from(data, 'utf8'))
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
@@ -413,13 +408,14 @@ export class E2BTerminalHandle implements SubprocessTerminalHandle {
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
terminate(): void {
|
||||
this.lifecycle.terminate()
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async waitForExit(signal?: AbortSignal): Promise<boolean> {
|
||||
return await this.lifecycle.waitForExit(signal)
|
||||
terminate(): Promise<void> {
|
||||
if (this.cleanup !== undefined) return this.cleanup
|
||||
const cleanup = this.closeOnce()
|
||||
this.cleanup = cleanup
|
||||
void cleanup.catch((_cleanupFailure: unknown) => {
|
||||
this.cleanup = undefined
|
||||
})
|
||||
return cleanup
|
||||
}
|
||||
|
||||
private async waitForCommand(): Promise<SubprocessOutcome> {
|
||||
@@ -474,7 +470,11 @@ export class E2BTerminalHandle implements SubprocessTerminalHandle {
|
||||
} catch (error: unknown) {
|
||||
if (!(error instanceof SandboxNotFoundError)) throw error
|
||||
}
|
||||
await this.sandbox.files.remove(this.stateDir).catch(() => {})
|
||||
try {
|
||||
await this.sandbox.files.remove(this.stateDir)
|
||||
} catch (_adapterPrivateStateRemovalFailure) {
|
||||
// The terminal is quiescent; a retained sandbox tolerates private residue.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,7 +558,6 @@ export async function spawnE2BTerminal(
|
||||
controlEnvs,
|
||||
stateDir,
|
||||
spec.graceMs,
|
||||
spec.signal,
|
||||
)
|
||||
} catch (error: unknown) {
|
||||
output.destroy()
|
||||
|
||||
Reference in New Issue
Block a user