File size: 3,935 Bytes
064bfd6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | import { z } from 'zod/v4'
import type { TaskStateBase } from '../../Task.js'
import { buildTool, type ToolDef } from '../../Tool.js'
import { stopTask } from '../../tasks/stopTask.js'
import { lazySchema } from '../../utils/lazySchema.js'
import { jsonStringify } from '../../utils/slowOperations.js'
import { DESCRIPTION, TASK_STOP_TOOL_NAME } from './prompt.js'
import { renderToolResultMessage, renderToolUseMessage } from './UI.js'
const inputSchema = lazySchema(() =>
z.strictObject({
task_id: z
.string()
.optional()
.describe('The ID of the background task to stop'),
// shell_id is accepted for backward compatibility with the deprecated KillShell tool
shell_id: z.string().optional().describe('Deprecated: use task_id instead'),
}),
)
type InputSchema = ReturnType<typeof inputSchema>
const outputSchema = lazySchema(() =>
z.object({
message: z.string().describe('Status message about the operation'),
task_id: z.string().describe('The ID of the task that was stopped'),
task_type: z.string().describe('The type of the task that was stopped'),
// Optional: tool outputs are persisted to transcripts and replayed on --resume
// without re-validation, so sessions from before this field was added lack it.
command: z
.string()
.optional()
.describe('The command or description of the stopped task'),
}),
)
type OutputSchema = ReturnType<typeof outputSchema>
export type Output = z.infer<OutputSchema>
export const TaskStopTool = buildTool({
name: TASK_STOP_TOOL_NAME,
searchHint: 'kill a running background task',
// KillShell is the deprecated name - kept as alias for backward compatibility
// with existing transcripts and SDK users
aliases: ['KillShell'],
maxResultSizeChars: 100_000,
userFacingName: () => (process.env.USER_TYPE === 'ant' ? '' : 'Stop Task'),
get inputSchema(): InputSchema {
return inputSchema()
},
get outputSchema(): OutputSchema {
return outputSchema()
},
shouldDefer: true,
isConcurrencySafe() {
return true
},
toAutoClassifierInput(input) {
return input.task_id ?? input.shell_id ?? ''
},
async validateInput({ task_id, shell_id }, { getAppState }) {
// Support both task_id and shell_id (deprecated KillShell compat)
const id = task_id ?? shell_id
if (!id) {
return {
result: false,
message: 'Missing required parameter: task_id',
errorCode: 1,
}
}
const appState = getAppState()
const task = appState.tasks?.[id] as TaskStateBase | undefined
if (!task) {
return {
result: false,
message: `No task found with ID: ${id}`,
errorCode: 1,
}
}
if (task.status !== 'running') {
return {
result: false,
message: `Task ${id} is not running (status: ${task.status})`,
errorCode: 3,
}
}
return { result: true }
},
async description() {
return `Stop a running background task by ID`
},
async prompt() {
return DESCRIPTION
},
mapToolResultToToolResultBlockParam(output, toolUseID) {
return {
tool_use_id: toolUseID,
type: 'tool_result',
content: jsonStringify(output),
}
},
renderToolUseMessage,
renderToolResultMessage,
async call(
{ task_id, shell_id },
{ getAppState, setAppState, abortController },
) {
// Support both task_id and shell_id (deprecated KillShell compat)
const id = task_id ?? shell_id
if (!id) {
throw new Error('Missing required parameter: task_id')
}
const result = await stopTask(id, {
getAppState,
setAppState,
})
return {
data: {
message: `Successfully stopped task: ${result.taskId} (${result.command})`,
task_id: result.taskId,
task_type: result.taskType,
command: result.command,
},
}
},
} satisfies ToolDef<InputSchema, Output>)
|