Review follow-up (tianyicui): plan mode and the sandbox are orthogonal
AXES, not just orthogonal state — entering plan must not change what the
sandbox enforces, matching Codex's separation of Plan/Default
collaboration presets from sandbox and approval settings.
ModeDefinition.access, the bash/resolve-mode clamp, and both cap-derived
guards are removed; a ModeDefinition is exactly { section }, and a mode
now carries only its guidance section plus the exit_plan_mode review.
The bash seam's resolveMode + waterfall go with their only listener:
dsh-bash and dsh-tool-bash revert to master byte-for-byte, and the
dsh-mode → dsh-bash dependency edge is gone. A deployment that wants
kernel-enforced read-only planning pairs the mode picker with the
independent sandbox-mode option, in either order.
The RFC archives this as the second removed enforcement shape (after
the interim allowlist) with the same restart trigger — effects
self-declaration; the orthogonality FAQ now answers with the two-axis
rule. The plan example demonstrates the axes side by side, and the
re-recorded fixtures pin the guidance-only section.
85 lines
2.9 KiB
TypeScript
85 lines
2.9 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { Context } from 'cordis'
|
|
import { BashExecutor } from '@deepseek-ai/dsh-bash'
|
|
import type { BashExecRequest, BashExecSpec, BashProcess, BashProcessRead, BashRunResult } from '@deepseek-ai/dsh-bash'
|
|
|
|
/**
|
|
* Minimal concrete executor: canned foreground results, a hand-built process
|
|
* handle. The seam is TASK-FREE (start returns a {@link BashProcess} handle;
|
|
* task semantics live in `ctx.tasks`), so this stub is all an implementation
|
|
* owes the abstract class.
|
|
*/
|
|
class StubExecutor extends BashExecutor {
|
|
resolve(request: BashExecRequest): BashExecSpec {
|
|
return {
|
|
command: request.command,
|
|
workdir: request.workdir ?? '/stub',
|
|
timeoutMs: request.timeoutMs ?? 1000,
|
|
stdoutMaxBytes: request.stdoutMaxBytes ?? 64_000,
|
|
...request.signal ? { signal: request.signal } : {},
|
|
sandboxMode: request.sandboxMode,
|
|
}
|
|
}
|
|
|
|
async run(spec: BashExecSpec): Promise<BashRunResult> {
|
|
return {
|
|
exitCode: 0,
|
|
signal: null,
|
|
timedOut: false,
|
|
aborted: false,
|
|
timeoutMs: spec.timeoutMs,
|
|
stdout: { text: 'ok', truncated: false },
|
|
stderr: { text: '', truncated: false },
|
|
}
|
|
}
|
|
|
|
start(): BashProcess {
|
|
const proc: BashProcess = {
|
|
status: 'running',
|
|
exitCode: null,
|
|
signal: null,
|
|
done: Promise.resolve(),
|
|
readOutput: (): BashProcessRead => ({ delta: '', lossy: false }),
|
|
kill: (): boolean => {
|
|
if (proc.status !== 'running') return false
|
|
proc.status = 'killed'
|
|
return true
|
|
},
|
|
}
|
|
return proc
|
|
}
|
|
}
|
|
|
|
describe('BashExecutor service seam', () => {
|
|
it('a concrete subclass registers as ctx.bash and serves the abstract API', async () => {
|
|
const ctx = new Context()
|
|
await ctx.plugin(StubExecutor)
|
|
const spec = ctx.bash.resolve({ command: 'echo hi' })
|
|
expect(spec).toEqual({ command: 'echo hi', workdir: '/stub', timeoutMs: 1000, stdoutMaxBytes: 64_000, sandboxMode: undefined })
|
|
|
|
const result = await ctx.bash.run(spec)
|
|
expect(result.exitCode).toBe(0)
|
|
expect(result.stdout.text).toBe('ok')
|
|
|
|
const proc = ctx.bash.start(spec)
|
|
expect(proc.status).toBe('running')
|
|
expect(proc.readOutput()).toEqual({ delta: '', lossy: false })
|
|
expect(proc.kill()).toBe(true)
|
|
expect(proc.kill()).toBe(false) // already settled → no-op
|
|
await proc.done
|
|
})
|
|
|
|
it('reports no default sandbox mode from the task-free base seam', async () => {
|
|
const ctx = new Context()
|
|
await ctx.plugin(StubExecutor)
|
|
expect(ctx.bash.sandboxMode).toBeUndefined()
|
|
})
|
|
|
|
it('loading a second implementation throws (one bash service per context — cordis standard)', async () => {
|
|
const ctx = new Context()
|
|
await ctx.plugin(StubExecutor)
|
|
class SecondExecutor extends StubExecutor {}
|
|
await expect(ctx.plugin(SecondExecutor)).rejects.toThrow(/service "bash" has been registered/)
|
|
})
|
|
})
|