101 lines
2.8 KiB
TypeScript
101 lines
2.8 KiB
TypeScript
// Shared logic for stopping a running task.
|
|
// Used by TaskStopTool (LLM-invoked) and SDK stop_task control request.
|
|
|
|
import type { AppState } from '../state/AppState.js'
|
|
import type { TaskStateBase } from '../Task.js'
|
|
import { getTaskByType } from '../tasks.js'
|
|
import { emitTaskTerminatedSdk } from '../utils/sdkEventQueue.js'
|
|
import { isLocalShellTask } from './LocalShellTask/guards.js'
|
|
|
|
export class StopTaskError extends Error {
|
|
constructor(
|
|
message: string,
|
|
public readonly code: 'not_found' | 'not_running' | 'unsupported_type',
|
|
) {
|
|
super(message)
|
|
this.name = 'StopTaskError'
|
|
}
|
|
}
|
|
|
|
type StopTaskContext = {
|
|
getAppState: () => AppState
|
|
setAppState: (f: (prev: AppState) => AppState) => void
|
|
}
|
|
|
|
type StopTaskResult = {
|
|
taskId: string
|
|
taskType: string
|
|
command: string | undefined
|
|
}
|
|
|
|
/**
|
|
* Look up a task by ID, validate it is running, kill it, and mark it as notified.
|
|
*
|
|
* Throws {@link StopTaskError} when the task cannot be stopped (not found,
|
|
* not running, or unsupported type). Callers can inspect `error.code` to
|
|
* distinguish the failure reason.
|
|
*/
|
|
export async function stopTask(
|
|
taskId: string,
|
|
context: StopTaskContext,
|
|
): Promise<StopTaskResult> {
|
|
const { getAppState, setAppState } = context
|
|
const appState = getAppState()
|
|
const task = appState.tasks?.[taskId] as TaskStateBase | undefined
|
|
|
|
if (!task) {
|
|
throw new StopTaskError(`No task found with ID: ${taskId}`, 'not_found')
|
|
}
|
|
|
|
if (task.status !== 'running') {
|
|
throw new StopTaskError(
|
|
`Task ${taskId} is not running (status: ${task.status})`,
|
|
'not_running',
|
|
)
|
|
}
|
|
|
|
const taskImpl = getTaskByType(task.type)
|
|
if (!taskImpl) {
|
|
throw new StopTaskError(
|
|
`Unsupported task type: ${task.type}`,
|
|
'unsupported_type',
|
|
)
|
|
}
|
|
|
|
await taskImpl.kill(taskId, setAppState)
|
|
|
|
// Bash: suppress the "exit code 137" notification (noise). Agent tasks: don't
|
|
// suppress — the AbortError catch sends a notification carrying
|
|
// extractPartialResult(agentMessages), which is the payload not noise.
|
|
if (isLocalShellTask(task)) {
|
|
let suppressed = false
|
|
setAppState(prev => {
|
|
const prevTask = prev.tasks[taskId]
|
|
if (!prevTask || prevTask.notified) {
|
|
return prev
|
|
}
|
|
suppressed = true
|
|
return {
|
|
...prev,
|
|
tasks: {
|
|
...prev.tasks,
|
|
[taskId]: { ...prevTask, notified: true },
|
|
},
|
|
}
|
|
})
|
|
// Suppressing the XML notification also suppresses print.ts's parsed
|
|
// task_notification SDK event — emit it directly so SDK consumers see
|
|
// the task close.
|
|
if (suppressed) {
|
|
emitTaskTerminatedSdk(taskId, 'stopped', {
|
|
toolUseId: task.toolUseId,
|
|
summary: task.description,
|
|
})
|
|
}
|
|
}
|
|
|
|
const command = isLocalShellTask(task) ? task.command : task.description
|
|
|
|
return { taskId, taskType: task.type, command }
|
|
}
|