feat(tools): require cancellation signal on every invocation
This commit is contained in:
@@ -203,7 +203,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
// PreToolUse → PreToolDecision. Codex blocks only (no allow/ask honored).
|
||||
ctx.on('tools/pre-execute', async (exec, next): Promise<PreToolDecision> => {
|
||||
const turn = lastTurn(exec.agent)
|
||||
const merged = await runPoint('PreToolUse', exec.name, preToolPayload(ctx, exec, model), { ...exec.agent ? { agent: exec.agent } : {}, turn, ...exec.signal ? { signal: exec.signal } : {} })
|
||||
const merged = await runPoint('PreToolUse', exec.name, preToolPayload(ctx, exec, model), { ...exec.agent ? { agent: exec.agent } : {}, turn, signal: exec.signal })
|
||||
/* jscpd:ignore-end */
|
||||
if (merged.decision === 'deny') return { kind: 'deny', reason: merged.reason ?? 'blocked by PreToolUse hook' }
|
||||
return next()
|
||||
@@ -213,7 +213,7 @@ export function apply(ctx: Context, config: Config): void {
|
||||
ctx.on('tools/post-execute', async (exec, result, next): Promise<PostToolDecision> => {
|
||||
const turn = lastTurn(exec.agent)
|
||||
/* jscpd:ignore-start */
|
||||
const merged = await runPoint('PostToolUse', exec.name, postToolPayload(ctx, exec, result, model), { ...exec.agent ? { agent: exec.agent } : {}, turn, ...exec.signal ? { signal: exec.signal } : {} })
|
||||
const merged = await runPoint('PostToolUse', exec.name, postToolPayload(ctx, exec, result, model), { ...exec.agent ? { agent: exec.agent } : {}, turn, signal: exec.signal })
|
||||
const context = contextFrom(merged)
|
||||
if (merged.decision === 'deny') {
|
||||
return { kind: 'block', feedback: [{ type: 'text', text: merged.reason ?? 'blocked by PostToolUse hook' }], ...context ? { additionalContexts: [context] } : {} }
|
||||
|
||||
@@ -13,6 +13,8 @@ import { LocalBashExecutor } from '@deepseek-ai/dsh-bash-local'
|
||||
import * as HooksCodex from '@deepseek-ai/dsh-hooks-codex'
|
||||
import { MockAdapter, textResponse, toolCallResponse } from '../../../core/agent-loop/tests/mock-adapter.ts'
|
||||
|
||||
const testToolSignal = new AbortController().signal
|
||||
|
||||
const dirs: string[] = []
|
||||
afterEach(() => { for (const d of dirs.splice(0)) rmSync(d, { recursive: true, force: true }) })
|
||||
function dir(): string { const d = mkdtempSync(join(tmpdir(), 'dsh-hx-cov-')); dirs.push(d); return d }
|
||||
@@ -455,7 +457,7 @@ export function defineCoverageCases(groups: CoverageGroup | readonly CoverageGro
|
||||
let ran = false
|
||||
ctx.tools.register(defineTool({ name: 'Bash', description: 'b', parameters: { command: { type: 'string' } }, async execute() { ran = true; return [{ type: 'text', text: 'x' }] } }))
|
||||
const { CallId } = await import('@deepseek-ai/dsh-llm')
|
||||
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'Bash', arguments: { command: 'x' } })
|
||||
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'Bash', arguments: { command: 'x' } })
|
||||
expect(ran).toBe(false) // denied
|
||||
expect(result.isError).toBe(true)
|
||||
})
|
||||
@@ -466,7 +468,7 @@ export function defineCoverageCases(groups: CoverageGroup | readonly CoverageGro
|
||||
const ctx = await harness(join(d, 'hooks.json'), new MockAdapter([]))
|
||||
ctx.tools.register(defineTool({ name: 'Bash', description: 'b', parameters: { command: { type: 'string' } }, async execute() { return [{ type: 'text', text: 'ok' }] } }))
|
||||
const { CallId } = await import('@deepseek-ai/dsh-llm')
|
||||
const result = await ctx.tools.execute({ callId: CallId('c1'), name: 'Bash', arguments: { command: 'x' } })
|
||||
const result = await ctx.tools.execute({ signal: testToolSignal, callId: CallId('c1'), name: 'Bash', arguments: { command: 'x' } })
|
||||
expect(result.isError).toBeFalsy()
|
||||
expect(result.additionalContexts?.[0]?.content.some(b => b.type === 'text' && b.text === 'x')).toBe(true)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user