mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Merge remote-tracking branch 'origin/master' into cross-family-fs-sandbox
# Conflicts: # docs/config-catalog.md # docs/cordis-catalog/services.md # docs/module-graph.md # docs/rfc/implemented/feature/2026-07-06-sandbox.md # examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md # examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md # examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md # examples/acp-agent/tests/snapshots/escalation-approved/session.jsonl # examples/acp-agent/tests/snapshots/escalation-rejected/session.jsonl # examples/acp-agent/tests/snapshots/hook-cc-pretool-ask/session.jsonl # examples/acp-agent/tests/snapshots/permission-switching/session.jsonl # examples/acp-agent/tests/snapshots/permission-switching/system-prompt.golden.md # examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/skill-load/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/text-turn/tool-schemas.golden.json # examples/acp-agent/tests/snapshots/workspace-edit/system-prompt.golden.md # examples/acp-agent/tests/snapshots/workspace-edit/tool-schemas.golden.json # packages/bash/bash-sandbox/src/index.ts # packages/bash/bash-sandbox/tests/bwrap.e2e.ts # packages/bash/bash-sandbox/tests/sandbox.spec.ts # packages/bash/bash-sandbox/tests/seatbelt.e2e.ts # packages/bash/bash/src/index.ts # packages/bash/tool-bash/package.json # packages/bash/tool-bash/src/index.ts # packages/bash/tool-bash/src/render.ts # packages/bash/tool-bash/tests/tools.spec.ts # packages/bash/tool-bash/tsconfig.json # pnpm-lock.yaml # scripts/verify-package-readme-model-experience.ts
This commit is contained in:
@@ -1,23 +1,22 @@
|
||||
/**
|
||||
* The bash executor seam (`ctx.bash`): an abstract service defining what a bash backend does —
|
||||
* run commands, manage background tasks — without saying how.
|
||||
* The `ctx.bash` executor seam for foreground commands and background process
|
||||
* handles. Task ids, ownership, polling, and notices belong to
|
||||
* `@deepseek-ai/dsh-tasks`, keeping executors independent of sessions.
|
||||
* @module @deepseek-ai/dsh-bash
|
||||
*/
|
||||
|
||||
import { Context, Service } from 'cordis'
|
||||
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
|
||||
import type { BashExecRequest, BashExecSpec, BashRunResult, BashTask, BashTaskId, BashTaskListener, BashTaskRead, OwnerToken } from './types.ts'
|
||||
import type { BashExecRequest, BashExecSpec, BashProcess, BashRunResult } from './types.ts'
|
||||
|
||||
export { BashTaskId, OwnerToken } from './types.ts'
|
||||
export type {
|
||||
BashExecRequest,
|
||||
BashExecSpec,
|
||||
BashProcess,
|
||||
BashProcessRead,
|
||||
BashProcessStatus,
|
||||
BashRunResult,
|
||||
BashSandboxInfo,
|
||||
BashTask,
|
||||
BashTaskListener,
|
||||
BashTaskRead,
|
||||
BashTaskStatus,
|
||||
CollectedOutput,
|
||||
} from './types.ts'
|
||||
|
||||
@@ -28,34 +27,30 @@ declare module 'cordis' {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers one `ctx.bash` implementation. Runtime command failures resolve as
|
||||
* {@link BashRunResult}; only infrastructure failures reject. Background starts
|
||||
* return immediately without a timeout, report completion exactly once while
|
||||
* live, and remain cancellable by signal or {@link kill}. Output reads are
|
||||
* incremental and flag lost buffered data; disposal kills and awaits all tasks.
|
||||
* Abstract bash execution service. Subclass, implement the abstract methods,
|
||||
* and load the subclass as a plugin — it registers as `ctx.bash` (one
|
||||
* implementation per context; loading a second throws, which is cordis'
|
||||
* standard duplicate-service behavior).
|
||||
*
|
||||
* Implementations must honor these semantics:
|
||||
* - {@link run} rejects only for infrastructure failures. Nonzero exits,
|
||||
* timeout kills, and abort kills resolve with a {@link BashRunResult}.
|
||||
* - {@link start} returns immediately; no timeout applies to background
|
||||
* processes. `done` settles at process close and never rejects; spawn
|
||||
* failures settle as `killed` with the error on stderr.
|
||||
* - {@link BashProcess.readOutput} is incremental: consecutive reads never
|
||||
* repeat output. Lossy reads report truncation and available spill files.
|
||||
* - Disposal kills all running background processes and awaits their exit.
|
||||
*/
|
||||
export abstract class BashExecutor extends Service {
|
||||
private listeners = new Set<BashTaskListener>()
|
||||
private listenersClosed = false
|
||||
|
||||
constructor(ctx: Context) {
|
||||
super(ctx, 'bash')
|
||||
ctx.effect(() => () => {
|
||||
// Close the listener registry before subclass teardown so late task
|
||||
// completions (e.g. from kills issued during dispose) stay silent.
|
||||
this.listenersClosed = true
|
||||
this.listeners.clear()
|
||||
}, 'bash listener teardown')
|
||||
}
|
||||
|
||||
/**
|
||||
* The sandbox mode this executor confines commands under BY DEFAULT, or `undefined` when it
|
||||
* does not sandbox at all — the capability fact the tool and ACP layers read to advertise
|
||||
* sandbox controls honestly.
|
||||
* A session or call may override this default, so widening is evaluated per
|
||||
* execution rather than encoded in this getter.
|
||||
* @returns the configured default mode of a sandboxing executor;
|
||||
* `undefined` for an executor that never confines.
|
||||
* The sandbox mode this executor applies by default, or `undefined` when it
|
||||
* does not sandbox commands.
|
||||
* @returns the configured default sandbox mode, when supported.
|
||||
*/
|
||||
get sandboxMode(): SandboxMode | undefined {
|
||||
return undefined
|
||||
@@ -78,79 +73,11 @@ export abstract class BashExecutor extends Service {
|
||||
abstract run(spec: BashExecSpec): Promise<BashRunResult>
|
||||
|
||||
/**
|
||||
* Start a background task and return its handle immediately.
|
||||
* Start a background process and return its handle immediately.
|
||||
* @param spec - a resolved spec from {@link resolve}, never a raw request.
|
||||
* @returns the live task handle; completion fires {@link onTaskDone}.
|
||||
* @returns the live process handle (reads, kill, quiescence promise).
|
||||
*/
|
||||
abstract start(spec: BashExecSpec): BashTask
|
||||
|
||||
/**
|
||||
* Look up a background task by id.
|
||||
* @param id - the task id to look up.
|
||||
* @returns the tracked task, or undefined for an id this executor never issued.
|
||||
*/
|
||||
abstract get(id: BashTaskId): BashTask | undefined
|
||||
|
||||
/**
|
||||
* The opaque OWNER token recorded for a background task at {@link start} (from the {@link
|
||||
* BashExecSpec}'s `owner`), or `undefined` for an unknown id OR a known-but-ownerless task.
|
||||
* The executor stores the token without interpreting policy; keeping it here
|
||||
* lets ownership survive a consumer-plugin reload.
|
||||
* @param id - the background task id to look up ownership for.
|
||||
* @returns the token recorded at start, verbatim; undefined for an unknown
|
||||
* id or a known-but-ownerless task.
|
||||
*/
|
||||
abstract ownerOf(id: BashTaskId): OwnerToken | undefined
|
||||
|
||||
/**
|
||||
* All tracked background tasks (insertion order).
|
||||
* @returns every task this executor started, running or finished.
|
||||
*/
|
||||
abstract list(): BashTask[]
|
||||
|
||||
/**
|
||||
* Read output produced since the previous read. Throws for unknown ids.
|
||||
* @param id - the task to read from.
|
||||
* @returns the incremental read; consecutive reads never re-deliver output.
|
||||
*/
|
||||
abstract readOutput(id: BashTaskId): BashTaskRead
|
||||
|
||||
/**
|
||||
* Kill a running background task. Returns false when it had already
|
||||
* finished (no-op). Throws for unknown ids.
|
||||
* @param id - the task to kill.
|
||||
* @returns true when this call killed it, false when it had already finished.
|
||||
*/
|
||||
abstract kill(id: BashTaskId): boolean
|
||||
|
||||
/**
|
||||
* Register a background-task completion listener (disposed with the
|
||||
* calling fiber). Listeners never fire after this service is disposed.
|
||||
* @param listener - called exactly once per task completion.
|
||||
* @returns the disposer that unregisters the listener.
|
||||
*/
|
||||
onTaskDone(listener: BashTaskListener): () => void {
|
||||
const dispose = this.ctx.effect(() => {
|
||||
this.listeners.add(listener)
|
||||
return () => this.listeners.delete(listener)
|
||||
}, 'bash.onTaskDone()')
|
||||
return () => void dispose()
|
||||
}
|
||||
|
||||
/** For implementations: notify listeners that `task` completed. Listener
|
||||
* exceptions are contained (logged) — one bad listener must not reject
|
||||
* `BashTask.done` or starve the listeners after it. */
|
||||
protected notifyTaskDone(task: BashTask): void {
|
||||
if (this.listenersClosed) return
|
||||
for (const listener of this.listeners) {
|
||||
try {
|
||||
listener(task)
|
||||
} catch (error: unknown) {
|
||||
// Listener bugs are reported, never propagated into task.done.
|
||||
console.error('bash onTaskDone listener threw:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
abstract start(spec: BashExecSpec): BashProcess
|
||||
}
|
||||
|
||||
export default BashExecutor
|
||||
|
||||
Reference in New Issue
Block a user