SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
0f821de verified
Raw History Blame Contribute Delete
2.62 kB
/**
* Generic-task adaptation for background bash process handles.
*
* @module @deepseek-ai/dsh-tool-bash/background
*/
import type { ShellProcess } from '@deepseek-ai/dsh-shell'
import type { JobHooks, JobOutcome } from '@deepseek-ai/dsh-jobs'
/**
* Map a settled background process onto the generic task-outcome vocabulary:
* `killed` stays `killed` (detail: the signal when one is known), everything
* else is `completed` with the exit code as detail. A nonzero command exit is
* reported, not failed, exactly like the foreground rendering.
* @param proc - the settled process handle.
* @returns the outcome for the `ctx.jobs` registration.
*/
export function processOutcome(proc: ShellProcess): { status: 'completed' | 'killed'; detail: string } {
// TODO(background-infrastructure-outcome): widen ShellProcess with an explicit
// infrastructure-failure outcome, then map it to task `failed`. Restricted
// runner failures expose sandbox.runnerFailed, but unconfined spawn failures
// still alias a signal-less kill; real nonzero command exits must remain
// `completed`.
if (proc.status === 'killed') {
return { status: 'killed', detail: proc.signal !== null ? `signal: ${proc.signal}` : 'killed before exit' }
}
return { status: 'completed', detail: `exit code: ${proc.exitCode ?? 0}` }
}
/**
* Adapt asynchronous shell preparation after job admission without exposing a partial process.
* @param start - starts the process with job-owned cancellation.
* @param renderOutput - consumes output from a published process.
* @returns synchronous job hooks whose completion includes preparation and process settlement.
*/
export function processJob(
start: (signal: AbortSignal) => Promise<ShellProcess>,
renderOutput: (process: ShellProcess) => string,
): JobHooks {
const controller = new AbortController()
let process: ShellProcess | undefined
const done: Promise<JobOutcome> = (async () => {
try {
process = await start(controller.signal)
try {
if (controller.signal.aborted) process.kill()
} finally {
await process.done
}
return processOutcome(process)
} catch (error: unknown) {
return {
status: controller.signal.aborted && process === undefined ? 'killed' : 'failed',
detail: error instanceof Error ? error.message : String(error),
}
}
})()
return {
cancel: (reason) => {
if (controller.signal.aborted) return
controller.abort(reason)
process?.kill()
},
done,
readOutput: () => process === undefined ? '' : renderOutput(process),
}
}