init claude-code
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
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>)
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from 'react';
|
||||
import { MessageResponse } from '../../components/MessageResponse.js';
|
||||
import { stringWidth } from '../../ink/stringWidth.js';
|
||||
import { Text } from '../../ink.js';
|
||||
import { truncateToWidthNoEllipsis } from '../../utils/format.js';
|
||||
import type { Output } from './TaskStopTool.js';
|
||||
export function renderToolUseMessage(): React.ReactNode {
|
||||
return '';
|
||||
}
|
||||
const MAX_COMMAND_DISPLAY_LINES = 2;
|
||||
const MAX_COMMAND_DISPLAY_CHARS = 160;
|
||||
function truncateCommand(command: string): string {
|
||||
const lines = command.split('\n');
|
||||
let truncated = command;
|
||||
if (lines.length > MAX_COMMAND_DISPLAY_LINES) {
|
||||
truncated = lines.slice(0, MAX_COMMAND_DISPLAY_LINES).join('\n');
|
||||
}
|
||||
if (stringWidth(truncated) > MAX_COMMAND_DISPLAY_CHARS) {
|
||||
truncated = truncateToWidthNoEllipsis(truncated, MAX_COMMAND_DISPLAY_CHARS);
|
||||
}
|
||||
return truncated.trim();
|
||||
}
|
||||
export function renderToolResultMessage(output: Output, _progressMessagesForMessage: unknown[], {
|
||||
verbose
|
||||
}: {
|
||||
verbose: boolean;
|
||||
}): React.ReactNode {
|
||||
if ("external" === 'ant') {
|
||||
return null;
|
||||
}
|
||||
const rawCommand = output.command ?? '';
|
||||
const command = verbose ? rawCommand : truncateCommand(rawCommand);
|
||||
const suffix = command !== rawCommand ? '… · stopped' : ' · stopped';
|
||||
return <MessageResponse>
|
||||
<Text>
|
||||
{command}
|
||||
{suffix}
|
||||
</Text>
|
||||
</MessageResponse>;
|
||||
}
|
||||
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIk1lc3NhZ2VSZXNwb25zZSIsInN0cmluZ1dpZHRoIiwiVGV4dCIsInRydW5jYXRlVG9XaWR0aE5vRWxsaXBzaXMiLCJPdXRwdXQiLCJyZW5kZXJUb29sVXNlTWVzc2FnZSIsIlJlYWN0Tm9kZSIsIk1BWF9DT01NQU5EX0RJU1BMQVlfTElORVMiLCJNQVhfQ09NTUFORF9ESVNQTEFZX0NIQVJTIiwidHJ1bmNhdGVDb21tYW5kIiwiY29tbWFuZCIsImxpbmVzIiwic3BsaXQiLCJ0cnVuY2F0ZWQiLCJsZW5ndGgiLCJzbGljZSIsImpvaW4iLCJ0cmltIiwicmVuZGVyVG9vbFJlc3VsdE1lc3NhZ2UiLCJvdXRwdXQiLCJfcHJvZ3Jlc3NNZXNzYWdlc0Zvck1lc3NhZ2UiLCJ2ZXJib3NlIiwicmF3Q29tbWFuZCIsInN1ZmZpeCJdLCJzb3VyY2VzIjpbIlVJLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBNZXNzYWdlUmVzcG9uc2UgfSBmcm9tICcuLi8uLi9jb21wb25lbnRzL01lc3NhZ2VSZXNwb25zZS5qcydcbmltcG9ydCB7IHN0cmluZ1dpZHRoIH0gZnJvbSAnLi4vLi4vaW5rL3N0cmluZ1dpZHRoLmpzJ1xuaW1wb3J0IHsgVGV4dCB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7IHRydW5jYXRlVG9XaWR0aE5vRWxsaXBzaXMgfSBmcm9tICcuLi8uLi91dGlscy9mb3JtYXQuanMnXG5pbXBvcnQgdHlwZSB7IE91dHB1dCB9IGZyb20gJy4vVGFza1N0b3BUb29sLmpzJ1xuXG5leHBvcnQgZnVuY3Rpb24gcmVuZGVyVG9vbFVzZU1lc3NhZ2UoKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgcmV0dXJuICcnXG59XG5cbmNvbnN0IE1BWF9DT01NQU5EX0RJU1BMQVlfTElORVMgPSAyXG5jb25zdCBNQVhfQ09NTUFORF9ESVNQTEFZX0NIQVJTID0gMTYwXG5cbmZ1bmN0aW9uIHRydW5jYXRlQ29tbWFuZChjb21tYW5kOiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCBsaW5lcyA9IGNvbW1hbmQuc3BsaXQoJ1xcbicpXG4gIGxldCB0cnVuY2F0ZWQgPSBjb21tYW5kXG5cbiAgaWYgKGxpbmVzLmxlbmd0aCA+IE1BWF9DT01NQU5EX0RJU1BMQVlfTElORVMpIHtcbiAgICB0cnVuY2F0ZWQgPSBsaW5lcy5zbGljZSgwLCBNQVhfQ09NTUFORF9ESVNQTEFZX0xJTkVTKS5qb2luKCdcXG4nKVxuICB9XG5cbiAgaWYgKHN0cmluZ1dpZHRoKHRydW5jYXRlZCkgPiBNQVhfQ09NTUFORF9ESVNQTEFZX0NIQVJTKSB7XG4gICAgdHJ1bmNhdGVkID0gdHJ1bmNhdGVUb1dpZHRoTm9FbGxpcHNpcyh0cnVuY2F0ZWQsIE1BWF9DT01NQU5EX0RJU1BMQVlfQ0hBUlMpXG4gIH1cblxuICByZXR1cm4gdHJ1bmNhdGVkLnRyaW0oKVxufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVuZGVyVG9vbFJlc3VsdE1lc3NhZ2UoXG4gIG91dHB1dDogT3V0cHV0LFxuICBfcHJvZ3Jlc3NNZXNzYWdlc0Zvck1lc3NhZ2U6IHVua25vd25bXSxcbiAgeyB2ZXJib3NlIH06IHsgdmVyYm9zZTogYm9vbGVhbiB9LFxuKTogUmVhY3QuUmVhY3ROb2RlIHtcbiAgaWYgKFwiZXh0ZXJuYWxcIiA9PT0gJ2FudCcpIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgY29uc3QgcmF3Q29tbWFuZCA9IG91dHB1dC5jb21tYW5kID8/ICcnXG4gIGNvbnN0IGNvbW1hbmQgPSB2ZXJib3NlID8gcmF3Q29tbWFuZCA6IHRydW5jYXRlQ29tbWFuZChyYXdDb21tYW5kKVxuICBjb25zdCBzdWZmaXggPSBjb21tYW5kICE9PSByYXdDb21tYW5kID8gJ+KApiDCtyBzdG9wcGVkJyA6ICcgwrcgc3RvcHBlZCdcblxuICByZXR1cm4gKFxuICAgIDxNZXNzYWdlUmVzcG9uc2U+XG4gICAgICA8VGV4dD5cbiAgICAgICAge2NvbW1hbmR9XG4gICAgICAgIHtzdWZmaXh9XG4gICAgICA8L1RleHQ+XG4gICAgPC9NZXNzYWdlUmVzcG9uc2U+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IkFBQUEsT0FBT0EsS0FBSyxNQUFNLE9BQU87QUFDekIsU0FBU0MsZUFBZSxRQUFRLHFDQUFxQztBQUNyRSxTQUFTQyxXQUFXLFFBQVEsMEJBQTBCO0FBQ3RELFNBQVNDLElBQUksUUFBUSxjQUFjO0FBQ25DLFNBQVNDLHlCQUF5QixRQUFRLHVCQUF1QjtBQUNqRSxjQUFjQyxNQUFNLFFBQVEsbUJBQW1CO0FBRS9DLE9BQU8sU0FBU0Msb0JBQW9CQSxDQUFBLENBQUUsRUFBRU4sS0FBSyxDQUFDTyxTQUFTLENBQUM7RUFDdEQsT0FBTyxFQUFFO0FBQ1g7QUFFQSxNQUFNQyx5QkFBeUIsR0FBRyxDQUFDO0FBQ25DLE1BQU1DLHlCQUF5QixHQUFHLEdBQUc7QUFFckMsU0FBU0MsZUFBZUEsQ0FBQ0MsT0FBTyxFQUFFLE1BQU0sQ0FBQyxFQUFFLE1BQU0sQ0FBQztFQUNoRCxNQUFNQyxLQUFLLEdBQUdELE9BQU8sQ0FBQ0UsS0FBSyxDQUFDLElBQUksQ0FBQztFQUNqQyxJQUFJQyxTQUFTLEdBQUdILE9BQU87RUFFdkIsSUFBSUMsS0FBSyxDQUFDRyxNQUFNLEdBQUdQLHlCQUF5QixFQUFFO0lBQzVDTSxTQUFTLEdBQUdGLEtBQUssQ0FBQ0ksS0FBSyxDQUFDLENBQUMsRUFBRVIseUJBQXlCLENBQUMsQ0FBQ1MsSUFBSSxDQUFDLElBQUksQ0FBQztFQUNsRTtFQUVBLElBQUlmLFdBQVcsQ0FBQ1ksU0FBUyxDQUFDLEdBQUdMLHlCQUF5QixFQUFFO0lBQ3RESyxTQUFTLEdBQUdWLHlCQUF5QixDQUFDVSxTQUFTLEVBQUVMLHlCQUF5QixDQUFDO0VBQzdFO0VBRUEsT0FBT0ssU0FBUyxDQUFDSSxJQUFJLENBQUMsQ0FBQztBQUN6QjtBQUVBLE9BQU8sU0FBU0MsdUJBQXVCQSxDQUNyQ0MsTUFBTSxFQUFFZixNQUFNLEVBQ2RnQiwyQkFBMkIsRUFBRSxPQUFPLEVBQUUsRUFDdEM7RUFBRUM7QUFBOEIsQ0FBckIsRUFBRTtFQUFFQSxPQUFPLEVBQUUsT0FBTztBQUFDLENBQUMsQ0FDbEMsRUFBRXRCLEtBQUssQ0FBQ08sU0FBUyxDQUFDO0VBQ2pCLElBQUksVUFBVSxLQUFLLEtBQUssRUFBRTtJQUN4QixPQUFPLElBQUk7RUFDYjtFQUVBLE1BQU1nQixVQUFVLEdBQUdILE1BQU0sQ0FBQ1QsT0FBTyxJQUFJLEVBQUU7RUFDdkMsTUFBTUEsT0FBTyxHQUFHVyxPQUFPLEdBQUdDLFVBQVUsR0FBR2IsZUFBZSxDQUFDYSxVQUFVLENBQUM7RUFDbEUsTUFBTUMsTUFBTSxHQUFHYixPQUFPLEtBQUtZLFVBQVUsR0FBRyxhQUFhLEdBQUcsWUFBWTtFQUVwRSxPQUNFLENBQUMsZUFBZTtBQUNwQixNQUFNLENBQUMsSUFBSTtBQUNYLFFBQVEsQ0FBQ1osT0FBTztBQUNoQixRQUFRLENBQUNhLE1BQU07QUFDZixNQUFNLEVBQUUsSUFBSTtBQUNaLElBQUksRUFBRSxlQUFlLENBQUM7QUFFdEIiLCJpZ25vcmVMaXN0IjpbXX0=
|
||||
@@ -0,0 +1,8 @@
|
||||
export const TASK_STOP_TOOL_NAME = 'TaskStop'
|
||||
|
||||
export const DESCRIPTION = `
|
||||
- Stops a running background task by its ID
|
||||
- Takes a task_id parameter identifying the task to stop
|
||||
- Returns a success or failure status
|
||||
- Use this tool when you need to terminate a long-running task
|
||||
`
|
||||
Reference in New Issue
Block a user