fix(tools): close cancellation review gaps
This commit is contained in:
@@ -422,6 +422,8 @@ export function apply(ctx: Context, config: Config = {}): void {
|
||||
if (tasks === undefined) {
|
||||
throw new Error('background tasks unavailable: load @deepseek-ai/dsh-tasks and @deepseek-ai/dsh-tool-tasks')
|
||||
}
|
||||
// The caller owns cancellation until TaskService commits detached ownership.
|
||||
if (exec.signal.aborted) return []
|
||||
// Task preflight finishes before the starter can spawn a process.
|
||||
const id = tasks.start({
|
||||
kind: 'bash',
|
||||
|
||||
@@ -7,7 +7,7 @@ import { CallId } from '@deepseek-ai/dsh-llm'
|
||||
import { BashExecutor } from '@deepseek-ai/dsh-bash'
|
||||
import type { BashExecRequest, BashExecSpec, BashProcess, BashProcessRead, BashRunResult } from '@deepseek-ai/dsh-bash'
|
||||
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
||||
import ToolRegistry, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
|
||||
import ToolRegistry, { TOOL_ABORTED, TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools'
|
||||
import AgentRegistry from '@deepseek-ai/dsh-agent'
|
||||
import type { Agent } from '@deepseek-ai/dsh-agent'
|
||||
import SessionStore, { SessionId } from '@deepseek-ai/dsh-session'
|
||||
@@ -181,7 +181,11 @@ async function setupSandboxed(withApproval = false) {
|
||||
return { ctx, bash: ctx.bash as RecordingSandboxExecutor }
|
||||
}
|
||||
|
||||
function sandboxAgent(mode?: 'read-only' | 'workspace-write' | 'danger-full-access', ctx?: Context): Agent {
|
||||
function sandboxAgent(
|
||||
mode?: 'read-only' | 'workspace-write' | 'danger-full-access',
|
||||
ctx?: Context,
|
||||
onAppend?: (type: string) => void,
|
||||
): Agent {
|
||||
const events: Array<{ type: string; data?: Record<string, unknown> }> = [{ type: 'turn/start' }]
|
||||
if (mode !== undefined) events.push({ type: 'sandbox/mode', data: { mode } })
|
||||
const id = SessionId('sandbox-session')
|
||||
@@ -195,6 +199,7 @@ function sandboxAgent(mode?: 'read-only' | 'workspace-write' | 'danger-full-acce
|
||||
append: (type: string, data: Record<string, unknown>) => {
|
||||
const event = { type, data }
|
||||
events.push(event)
|
||||
onAppend?.(type)
|
||||
return event
|
||||
},
|
||||
},
|
||||
@@ -600,6 +605,29 @@ describe('sandbox escalation through the generic task producer', () => {
|
||||
expect(bash.modes).toEqual(['workspace-write', 'workspace-write'])
|
||||
})
|
||||
|
||||
it('does not publish detached work when cancellation follows the escalation grant', async () => {
|
||||
const { ctx, bash } = await setupSandboxed(true)
|
||||
const controller = new AbortController()
|
||||
const agent = sandboxAgent(undefined, ctx, (type) => {
|
||||
if (type === 'approval/decided') controller.abort()
|
||||
})
|
||||
ctx.agents.register(agent)
|
||||
ctx.on('approval/request', () => Promise.resolve<ApprovalOutcome>('allowed-once'))
|
||||
const start = vi.spyOn(bash, 'start')
|
||||
|
||||
const result = await ctx.tools.execute({
|
||||
callId: CallId('cancelled-escalation-background'),
|
||||
name: 'bash',
|
||||
arguments: { ...escalate, run_in_background: true },
|
||||
agent,
|
||||
signal: controller.signal,
|
||||
})
|
||||
|
||||
expect(result.error).toEqual({ name: 'AbortError', code: TOOL_ABORTED })
|
||||
expect(text(result)).toBe('Error: tool call aborted')
|
||||
expect(start).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses the session override for ordinary calls and evaluates widening against it', async () => {
|
||||
const { ctx, bash } = await setupSandboxed(true)
|
||||
const agent = sandboxAgent('workspace-write')
|
||||
|
||||
Reference in New Issue
Block a user