Merge branch 'master' into codex/jsonl-zstd-persistence

This commit is contained in:
Tianyi Cui
2026-07-20 19:52:47 +08:00
committed by GitHub
389 changed files with 15325 additions and 8527 deletions

View File

@@ -23,15 +23,16 @@
"license": "BSD-3-Clause",
"peerDependencies": {
"@deepseek-ai/dsh-agent": "^0.0.1",
"@deepseek-ai/dsh-user-approval": "^0.0.1",
"@deepseek-ai/dsh-bash": "^0.0.1",
"@deepseek-ai/dsh-home": "^0.0.1",
"@deepseek-ai/dsh-llm": "^0.0.1",
"@deepseek-ai/dsh-session-persistence": "^0.0.1",
"@deepseek-ai/dsh-sandbox": "^0.0.1",
"@deepseek-ai/dsh-sandbox-policy": "^0.0.1",
"@deepseek-ai/dsh-system-prompt": "^0.0.1",
"@deepseek-ai/dsh-tasks": "^0.0.1",
"@deepseek-ai/dsh-tools": "^0.0.1",
"@deepseek-ai/dsh-user-approval": "^0.0.1",
"cordis": "^4.0.0-rc.7"
},
"dependencies": {
@@ -47,6 +48,7 @@
"@deepseek-ai/dsh-home": "workspace:^",
"@deepseek-ai/dsh-llm": "workspace:^",
"@deepseek-ai/dsh-sandbox": "workspace:^",
"@deepseek-ai/dsh-sandbox-policy": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-persistence": "workspace:^",
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
@@ -54,6 +56,7 @@
"@deepseek-ai/dsh-tasks": "workspace:^",
"@deepseek-ai/dsh-tool-tasks": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"@deepseek-ai/dsh-user-approval": "workspace:^",
"cordis": "^4.0.0-rc.7"
}
}

View File

@@ -15,12 +15,13 @@ import { defineTool } from '@deepseek-ai/dsh-tools'
import type { GenericCallView, TerminalCallView, ToolExecution, ToolResult, ToolResultView } from '@deepseek-ai/dsh-tools'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type {} from '@deepseek-ai/dsh-session-persistence'
import { assertNever } from '@deepseek-ai/dsh-llm'
import type {} from '@deepseek-ai/dsh-system-prompt'
import type {} from '@deepseek-ai/dsh-tasks'
import type {} from '@deepseek-ai/dsh-user-approval'
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
import { DSH_ENV_PREFIX, effectiveSandboxMode } from '@deepseek-ai/dsh-bash'
import { ESCALATION_TARGETS, approveEscalation, validateEscalationArgs } from '@deepseek-ai/dsh-sandbox'
import { effectiveSandboxMode } from '@deepseek-ai/dsh-sandbox-policy'
import { DSH_ENV_PREFIX } from '@deepseek-ai/dsh-bash'
import type { DshEnvironment, DshEnvironmentKey } from '@deepseek-ai/dsh-bash'
import { DSH_HOME_ENV, resolveDshHome } from '@deepseek-ai/dsh-home'
import { processOutcome } from './background.ts'
@@ -226,24 +227,11 @@ function validateBashArgs(args: BashToolArgs): void {
if (args.timeoutMs !== undefined && (!Number.isFinite(args.timeoutMs) || args.timeoutMs <= 0)) {
throw new Error(`invalid timeoutMs: expected a positive number, got ${JSON.stringify(args.timeoutMs)}`)
}
if (args.sandbox_permissions !== undefined && args.justification === undefined) {
throw new Error('invalid escalation: sandbox_permissions requires a justification')
}
if (args.justification !== undefined && args.sandbox_permissions === undefined) {
throw new Error('invalid escalation: justification is only valid together with sandbox_permissions')
}
if (args.justification !== undefined && args.justification.trim().length === 0) {
throw new Error('invalid justification: expected a non-empty sentence')
}
// The escalation pairing (sandbox_permissions ⇔ justification, non-empty) is
// the shared rule both enforcing families validate identically.
validateEscalationArgs(args.sandbox_permissions, args.justification)
}
const WIDER_MODES: Record<string, readonly SandboxMode[]> = {
'read-only': ['workspace-write', 'danger-full-access'],
'workspace-write': ['danger-full-access'],
}
const ESCALATION_TARGETS: readonly SandboxMode[] = ['workspace-write', 'danger-full-access']
function bashDescription(backgroundEnabled: boolean, escalationModes: readonly SandboxMode[]): string {
const background = backgroundEnabled
? 'Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`.'
@@ -346,35 +334,32 @@ export function apply(ctx: Context, config: Config = {}): void {
const sessionOverride = (exec: ToolExecution): SandboxMode | undefined =>
defaultMode === undefined || exec.agent === undefined ? undefined : effectiveSandboxMode(exec.agent.session.events)
const approveEscalation = async (mode: string, justification: string, exec: ToolExecution): Promise<SandboxMode> => {
/**
* Resolve a sandbox-escalation request through `ctx.approval` BEFORE
* anything executes, delegating the shared fail-closed sequence (strict
* widening, channel resolution, outcome mapping) to
* {@link approveEscalation}. This tool contributes only the composition
* guard (the fields are unadvertised without a sandboxing executor, yet
* schema validation checks advertised keys only, so an unadvertised
* `sandbox_permissions` still reaches execute) and the approval ingredients
* — the seam is consumed opportunistically (`ctx.get`) so a deployment
* without it degrades per call.
*/
const approveBashEscalation = (mode: string, justification: string, exec: ToolExecution): Promise<SandboxMode> => {
if (escalationModes.length === 0) {
throw new Error('sandbox_permissions is not available in this composition (no sandboxing executor to escalate)')
}
const effectiveMode = (sessionOverride(exec) ?? defaultMode) as SandboxMode
if (!(WIDER_MODES[effectiveMode] ?? []).includes(mode as SandboxMode)) {
throw new Error(`sandbox escalation to "${mode}" is not strictly wider than this call's current "${effectiveMode}" mode`)
}
const approval = ctx.get('approval')
if (approval === undefined) {
throw new Error(`sandbox escalation to "${mode}" requires approval, but no approval service is composed`)
}
if (exec.agent === undefined) {
throw new Error(`sandbox escalation to "${mode}" requires approval, but the call has no agent to route it through`)
}
const outcome = await approval.request({
agent: exec.agent,
toolName: 'bash',
callId: exec.callId,
reason: `escalate sandbox to ${mode}: ${justification}`,
...exec.signal ? { signal: exec.signal } : {},
})
switch (outcome) {
case 'allowed-once': return mode as SandboxMode
case 'rejected': throw new Error(`the user rejected escalating this command to "${mode}"`)
case 'cancelled': throw new Error(`approval for escalating to "${mode}" was cancelled`)
case 'unavailable': throw new Error(`sandbox escalation to "${mode}" requires approval, but no approval channel is available`)
default: return assertNever(outcome, 'ApprovalOutcome')
}
return approveEscalation(
{ requestedMode: mode, justification, effectiveMode, subject: 'command' },
{
approver: ctx.get('approval'),
agent: exec.agent,
callId: exec.callId,
toolName: 'bash',
...exec.signal ? { signal: exec.signal } : {},
},
)
}
// Cross-call guidance belongs in the prompt rather than one-call schema prose.
@@ -417,7 +402,7 @@ export function apply(ctx: Context, config: Config = {}): void {
validateBashArgs(args)
// Description is display metadata; workdir defaults to the caller's session.
const sandboxMode = args.sandbox_permissions !== undefined && args.justification !== undefined
? await approveEscalation(args.sandbox_permissions, args.justification, exec)
? await approveBashEscalation(args.sandbox_permissions, args.justification, exec)
: sessionOverride(exec)
const workdir = resolveWorkdir(args.workdir, exec)
const dshEnv = bashEnv.collect(exec)

View File

@@ -6,6 +6,7 @@
import type { BashProcessRead, BashRunResult, BashSandboxInfo, CollectedOutput } from '@deepseek-ai/dsh-bash'
import type { SandboxMode } from '@deepseek-ai/dsh-sandbox'
import { escalationHintMarker, sandboxDenialMarker } from '@deepseek-ai/dsh-sandbox'
/** Append the truncation notice (with the full-output spill path) to a stream's text. */
function streamText(output: CollectedOutput): string {
@@ -42,10 +43,10 @@ export function renderResult(
const markers: string[] = []
// Keep the exit marker last because parseExitStatus anchors there.
if (result.sandbox?.denied) {
markers.push(`[sandbox: file access denied under ${result.sandbox.mode} mode]`)
markers.push(sandboxDenialMarker(result.sandbox.mode))
// Hint only when the composition exposes escalation, before the final exit marker.
if (escalationModes.length > 0) {
markers.push('[sandbox: escalation available — retry this exact command once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]')
markers.push(escalationHintMarker('command'))
}
}
// A command may trap SIGTERM and exit 0 after timeout; still report interruption.
@@ -84,9 +85,9 @@ export function renderProcessRead(
if (sandbox?.runnerFailed) {
notices.push(`[sandbox: the sandbox runner itself failed under ${sandbox.mode} mode — the command did not run; this is a sandbox problem, not a command failure]`)
} else if (sandbox?.denied) {
notices.push(`[sandbox: file access denied under ${sandbox.mode} mode]`)
notices.push(sandboxDenialMarker(sandbox.mode))
if (escalationModes.length > 0) {
notices.push('[sandbox: escalation available — retry this exact command once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]')
notices.push(escalationHintMarker('command'))
}
}
if (notices.length === 0) return read.delta

View File

@@ -181,7 +181,7 @@ async function setupSandboxed(withApproval = false) {
function sandboxAgent(mode?: 'read-only' | 'workspace-write' | 'danger-full-access', ctx?: Context): Agent {
const events: Array<{ type: string; data?: Record<string, unknown> }> = [{ type: 'turn/start' }]
if (mode !== undefined) events.push({ type: 'bash/sandbox-mode', data: { mode } })
if (mode !== undefined) events.push({ type: 'sandbox/mode', data: { mode } })
const id = SessionId('sandbox-session')
return {
id,
@@ -553,7 +553,7 @@ describe('sandbox escalation through the generic task producer', () => {
const malformed = sandboxAgent()
;(malformed.session.events as unknown as Array<{ type: string; data: { mode: string } }>).push({
type: 'bash/sandbox-mode',
type: 'sandbox/mode',
data: { mode: 'unknown-mode' },
})
expect(text(await call(ctx, 'bash', escalate, malformed))).toContain('not strictly wider')
@@ -610,7 +610,7 @@ describe('sandbox escalation through the generic task producer', () => {
const { ctx } = await setupSandboxed(true)
ctx.approval.request = () => Promise.resolve('rogue' as ApprovalOutcome)
const result = await call(ctx, 'bash', escalate, sandboxAgent())
expect(text(result)).toContain('unreachable variant in ApprovalOutcome')
expect(text(result)).toContain('unreachable variant in EscalationOutcome')
})
})

View File

@@ -46,6 +46,9 @@
},
{
"path": "../../sandbox/sandbox"
},
{
"path": "../../sandbox/sandbox-policy"
}
]
}