source dump of claude code
at main 100 lines 2.9 kB view raw
1// Shared logic for stopping a running task. 2// Used by TaskStopTool (LLM-invoked) and SDK stop_task control request. 3 4import type { AppState } from '../state/AppState.js' 5import type { TaskStateBase } from '../Task.js' 6import { getTaskByType } from '../tasks.js' 7import { emitTaskTerminatedSdk } from '../utils/sdkEventQueue.js' 8import { isLocalShellTask } from './LocalShellTask/guards.js' 9 10export class StopTaskError extends Error { 11 constructor( 12 message: string, 13 public readonly code: 'not_found' | 'not_running' | 'unsupported_type', 14 ) { 15 super(message) 16 this.name = 'StopTaskError' 17 } 18} 19 20type StopTaskContext = { 21 getAppState: () => AppState 22 setAppState: (f: (prev: AppState) => AppState) => void 23} 24 25type StopTaskResult = { 26 taskId: string 27 taskType: string 28 command: string | undefined 29} 30 31/** 32 * Look up a task by ID, validate it is running, kill it, and mark it as notified. 33 * 34 * Throws {@link StopTaskError} when the task cannot be stopped (not found, 35 * not running, or unsupported type). Callers can inspect `error.code` to 36 * distinguish the failure reason. 37 */ 38export async function stopTask( 39 taskId: string, 40 context: StopTaskContext, 41): Promise<StopTaskResult> { 42 const { getAppState, setAppState } = context 43 const appState = getAppState() 44 const task = appState.tasks?.[taskId] as TaskStateBase | undefined 45 46 if (!task) { 47 throw new StopTaskError(`No task found with ID: ${taskId}`, 'not_found') 48 } 49 50 if (task.status !== 'running') { 51 throw new StopTaskError( 52 `Task ${taskId} is not running (status: ${task.status})`, 53 'not_running', 54 ) 55 } 56 57 const taskImpl = getTaskByType(task.type) 58 if (!taskImpl) { 59 throw new StopTaskError( 60 `Unsupported task type: ${task.type}`, 61 'unsupported_type', 62 ) 63 } 64 65 await taskImpl.kill(taskId, setAppState) 66 67 // Bash: suppress the "exit code 137" notification (noise). Agent tasks: don't 68 // suppress — the AbortError catch sends a notification carrying 69 // extractPartialResult(agentMessages), which is the payload not noise. 70 if (isLocalShellTask(task)) { 71 let suppressed = false 72 setAppState(prev => { 73 const prevTask = prev.tasks[taskId] 74 if (!prevTask || prevTask.notified) { 75 return prev 76 } 77 suppressed = true 78 return { 79 ...prev, 80 tasks: { 81 ...prev.tasks, 82 [taskId]: { ...prevTask, notified: true }, 83 }, 84 } 85 }) 86 // Suppressing the XML notification also suppresses print.ts's parsed 87 // task_notification SDK event — emit it directly so SDK consumers see 88 // the task close. 89 if (suppressed) { 90 emitTaskTerminatedSdk(taskId, 'stopped', { 91 toolUseId: task.toolUseId, 92 summary: task.description, 93 }) 94 } 95 } 96 97 const command = isLocalShellTask(task) ? task.command : task.description 98 99 return { taskId, taskType: task.type, command } 100}