mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-20 16:53:12 -07:00
delete task tool
This commit is contained in:
@@ -76,6 +76,7 @@ import {
|
||||
TrackerListTasksTool,
|
||||
TrackerAddDependencyTool,
|
||||
TrackerVisualizeTool,
|
||||
TrackerDeleteTaskTool,
|
||||
} from '../tools/trackerTools.js';
|
||||
import {
|
||||
logRipgrepFallback,
|
||||
@@ -2907,6 +2908,11 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
maybeRegister(TrackerVisualizeTool, () =>
|
||||
registry.registerTool(new TrackerVisualizeTool(this, this._messageBus)),
|
||||
);
|
||||
maybeRegister(TrackerDeleteTaskTool, () =>
|
||||
registry.registerTool(
|
||||
new TrackerDeleteTaskTool(this, this._messageBus),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Register Subagents as Tools
|
||||
|
||||
@@ -2877,7 +2877,7 @@ Operate using a **Research -> Strategy -> Execution** lifecycle. For the Executi
|
||||
# TASK MANAGEMENT PROTOCOL
|
||||
You are operating with a persistent file-based task tracking system located at \`.tracker/tasks/\`. You must adhere to the following rules:
|
||||
|
||||
1. **NO IN-MEMORY LISTS**: Do not maintain a mental list of tasks or write markdown checkboxes in the chat. Use the provided tools (\`tracker_create_task\`, \`tracker_list_tasks\`, \`tracker_update_task\`) for all state management.
|
||||
1. **NO IN-MEMORY LISTS**: Do not maintain a mental list of tasks or write markdown checkboxes in the chat. Use the provided tools (\`tracker_create_task\`, \`tracker_list_tasks\`, \`tracker_update_task\`, \`tracker_delete_task\`) for all state management.
|
||||
2. **IMMEDIATE DECOMPOSITION**: Upon receiving a task, evaluate its functional complexity and scope. If the request involves more than a single atomic modification, or necessitates research before execution, you MUST immediately decompose it into discrete entries using \`tracker_create_task\`.
|
||||
3. **IGNORE FORMATTING BIAS**: Trigger the protocol based on the **objective complexity** of the goal, regardless of whether the user provided a structured list or a single block of text/paragraph. "Paragraph-style" goals that imply multiple actions are multi-step projects and MUST be tracked.
|
||||
4. **PLAN MODE INTEGRATION**: If an approved plan exists, you MUST use the \`tracker_create_task\` tool to decompose it into discrete tasks before writing any code. Maintain a bidirectional understanding between the plan document and the task graph.
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
TRACKER_CREATE_TASK_TOOL_NAME,
|
||||
TRACKER_LIST_TASKS_TOOL_NAME,
|
||||
TRACKER_UPDATE_TASK_TOOL_NAME,
|
||||
TRACKER_DELETE_TASK_TOOL_NAME,
|
||||
} from '../tools/tool-names.js';
|
||||
import type { HierarchicalMemory } from '../config/memory.js';
|
||||
import { DEFAULT_CONTEXT_FILENAME } from '../tools/memoryTool.js';
|
||||
@@ -478,12 +479,13 @@ export function renderTaskTracker(): string {
|
||||
const trackerCreate = formatToolName(TRACKER_CREATE_TASK_TOOL_NAME);
|
||||
const trackerList = formatToolName(TRACKER_LIST_TASKS_TOOL_NAME);
|
||||
const trackerUpdate = formatToolName(TRACKER_UPDATE_TASK_TOOL_NAME);
|
||||
const trackerDelete = formatToolName(TRACKER_DELETE_TASK_TOOL_NAME);
|
||||
|
||||
return `
|
||||
# TASK MANAGEMENT PROTOCOL
|
||||
You are operating with a persistent file-based task tracking system located at \`.tracker/tasks/\`. You must adhere to the following rules:
|
||||
|
||||
1. **NO IN-MEMORY LISTS**: Do not maintain a mental list of tasks or write markdown checkboxes in the chat. Use the provided tools (${trackerCreate}, ${trackerList}, ${trackerUpdate}) for all state management.
|
||||
1. **NO IN-MEMORY LISTS**: Do not maintain a mental list of tasks or write markdown checkboxes in the chat. Use the provided tools (${trackerCreate}, ${trackerList}, ${trackerUpdate}, ${trackerDelete}) for all state management.
|
||||
2. **IMMEDIATE DECOMPOSITION**: Upon receiving a task, evaluate its functional complexity and scope. If the request involves more than a single atomic modification, or necessitates research before execution, you MUST immediately decompose it into discrete entries using ${trackerCreate}.
|
||||
3. **IGNORE FORMATTING BIAS**: Trigger the protocol based on the **objective complexity** of the goal, regardless of whether the user provided a structured list or a single block of text/paragraph. "Paragraph-style" goals that imply multiple actions are multi-step projects and MUST be tracked.
|
||||
4. **PLAN MODE INTEGRATION**: If an approved plan exists, you MUST use the ${trackerCreate} tool to decompose it into discrete tasks before writing any code. Maintain a bidirectional understanding between the plan document and the task graph.
|
||||
|
||||
@@ -139,4 +139,73 @@ describe('TrackerService', () => {
|
||||
service.updateTask(taskA.id, { dependencies: [taskB.id] }),
|
||||
).rejects.toThrow(/Circular dependency detected/);
|
||||
});
|
||||
|
||||
it('should delete a task if no other tasks depend on it', async () => {
|
||||
const task = await service.createTask({
|
||||
title: 'Task to be deleted',
|
||||
description: 'Will be deleted',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
await service.deleteTask(task.id);
|
||||
const retrieved = await service.getTask(task.id);
|
||||
expect(retrieved).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw when deleting a non-existent task', async () => {
|
||||
await expect(service.deleteTask('nonexistent')).rejects.toThrow(
|
||||
/Task with ID nonexistent not found/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should prevent deleting a task that is a dependency for another task', async () => {
|
||||
const dep = await service.createTask({
|
||||
title: 'Dependency',
|
||||
description: 'Used by main',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
const main = await service.createTask({
|
||||
title: 'Main Task',
|
||||
description: 'Depends on dep',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [dep.id],
|
||||
});
|
||||
|
||||
await expect(service.deleteTask(dep.id)).rejects.toThrow(
|
||||
new RegExp(
|
||||
`Cannot delete task ${dep.id} because it is a dependency of other tasks: ${main.id}`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it('should prevent deleting a task that has children', async () => {
|
||||
const parent = await service.createTask({
|
||||
title: 'Parent',
|
||||
description: 'Has child',
|
||||
type: TaskType.EPIC,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
});
|
||||
|
||||
const child = await service.createTask({
|
||||
title: 'Child Task',
|
||||
description: 'Has parent',
|
||||
type: TaskType.TASK,
|
||||
status: TaskStatus.OPEN,
|
||||
dependencies: [],
|
||||
parentId: parent.id,
|
||||
});
|
||||
|
||||
await expect(service.deleteTask(parent.id)).rejects.toThrow(
|
||||
new RegExp(
|
||||
`Cannot delete task ${parent.id} because it has child tasks: ${child.id}`,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,6 +186,35 @@ export class TrackerService {
|
||||
return updatedTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a task by ID.
|
||||
*/
|
||||
async deleteTask(id: string): Promise<void> {
|
||||
await this.ensureInitialized();
|
||||
const task = await this.getTask(id);
|
||||
if (!task) {
|
||||
throw new Error(`Task with ID ${id} not found.`);
|
||||
}
|
||||
|
||||
// Prevent deletion if other tasks depend on this one or have it as parent
|
||||
const allTasks = await this.listTasks();
|
||||
const dependents = allTasks.filter((t) => t.dependencies.includes(id));
|
||||
if (dependents.length > 0) {
|
||||
throw new Error(
|
||||
`Cannot delete task ${id} because it is a dependency of other tasks: ${dependents.map((t) => t.id).join(', ')}.`,
|
||||
);
|
||||
}
|
||||
const children = allTasks.filter((t) => t.parentId === id);
|
||||
if (children.length > 0) {
|
||||
throw new Error(
|
||||
`Cannot delete task ${id} because it has child tasks: ${children.map((t) => t.id).join(', ')}.`,
|
||||
);
|
||||
}
|
||||
|
||||
const taskPath = path.join(this.tasksDir, `${id}.json`);
|
||||
await fs.unlink(taskPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a task to disk.
|
||||
*/
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
TRACKER_LIST_TASKS_TOOL_NAME,
|
||||
TRACKER_ADD_DEPENDENCY_TOOL_NAME,
|
||||
TRACKER_VISUALIZE_TOOL_NAME,
|
||||
TRACKER_DELETE_TASK_TOOL_NAME,
|
||||
} from '../tool-names.js';
|
||||
|
||||
export const TRACKER_CREATE_TASK_DEFINITION: ToolDefinition = {
|
||||
@@ -159,3 +160,20 @@ export const TRACKER_VISUALIZE_DEFINITION: ToolDefinition = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const TRACKER_DELETE_TASK_DEFINITION: ToolDefinition = {
|
||||
base: {
|
||||
name: TRACKER_DELETE_TASK_TOOL_NAME,
|
||||
description: 'Deletes a task from the tracker.',
|
||||
parametersJsonSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'The 6-character hex ID of the task to delete.',
|
||||
},
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -166,6 +166,7 @@ export const TRACKER_GET_TASK_TOOL_NAME = 'tracker_get_task';
|
||||
export const TRACKER_LIST_TASKS_TOOL_NAME = 'tracker_list_tasks';
|
||||
export const TRACKER_ADD_DEPENDENCY_TOOL_NAME = 'tracker_add_dependency';
|
||||
export const TRACKER_VISUALIZE_TOOL_NAME = 'tracker_visualize';
|
||||
export const TRACKER_DELETE_TASK_TOOL_NAME = 'tracker_delete_task';
|
||||
|
||||
/**
|
||||
* Mapping of legacy tool names to their current names.
|
||||
@@ -225,6 +226,7 @@ export const ALL_BUILTIN_TOOL_NAMES = [
|
||||
TRACKER_LIST_TASKS_TOOL_NAME,
|
||||
TRACKER_ADD_DEPENDENCY_TOOL_NAME,
|
||||
TRACKER_VISUALIZE_TOOL_NAME,
|
||||
TRACKER_DELETE_TASK_TOOL_NAME,
|
||||
GET_INTERNAL_DOCS_TOOL_NAME,
|
||||
ENTER_PLAN_MODE_TOOL_NAME,
|
||||
EXIT_PLAN_MODE_TOOL_NAME,
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
TrackerUpdateTaskTool,
|
||||
TrackerVisualizeTool,
|
||||
TrackerAddDependencyTool,
|
||||
TrackerDeleteTaskTool,
|
||||
} from './trackerTools.js';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
@@ -142,4 +143,35 @@ describe('Tracker Tools Integration', () => {
|
||||
expect(vizResult.llmContent).toContain('Child Task');
|
||||
expect(vizResult.llmContent).toContain(childId);
|
||||
});
|
||||
|
||||
it('deletes a task', async () => {
|
||||
const createTool = new TrackerCreateTaskTool(config, messageBus);
|
||||
const deleteTool = new TrackerDeleteTaskTool(config, messageBus);
|
||||
|
||||
// Create Task to delete
|
||||
await createTool.buildAndExecute(
|
||||
{
|
||||
title: 'Delete Me',
|
||||
description: '...',
|
||||
type: TaskType.TASK,
|
||||
},
|
||||
getSignal(),
|
||||
);
|
||||
|
||||
const tasks = await config.getTrackerService().listTasks();
|
||||
const taskId = tasks.find((t) => t.title === 'Delete Me')!.id;
|
||||
|
||||
// Delete Task
|
||||
const deleteResult = await deleteTool.buildAndExecute(
|
||||
{
|
||||
id: taskId,
|
||||
},
|
||||
getSignal(),
|
||||
);
|
||||
|
||||
expect(deleteResult.llmContent).toContain(`Deleted task ${taskId}`);
|
||||
|
||||
const task = await config.getTrackerService().getTask(taskId);
|
||||
expect(task).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
TRACKER_LIST_TASKS_DEFINITION,
|
||||
TRACKER_UPDATE_TASK_DEFINITION,
|
||||
TRACKER_VISUALIZE_DEFINITION,
|
||||
TRACKER_DELETE_TASK_DEFINITION,
|
||||
} from './definitions/trackerTools.js';
|
||||
import { resolveToolDeclaration } from './definitions/resolver.js';
|
||||
import {
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
TRACKER_LIST_TASKS_TOOL_NAME,
|
||||
TRACKER_UPDATE_TASK_TOOL_NAME,
|
||||
TRACKER_VISUALIZE_TOOL_NAME,
|
||||
TRACKER_DELETE_TASK_TOOL_NAME,
|
||||
} from './tool-names.js';
|
||||
import type { ToolResult } from './tools.js';
|
||||
import { BaseDeclarativeTool, BaseToolInvocation, Kind } from './tools.js';
|
||||
@@ -604,3 +606,82 @@ export class TrackerVisualizeTool extends BaseDeclarativeTool<
|
||||
return resolveToolDeclaration(TRACKER_VISUALIZE_DEFINITION, modelId);
|
||||
}
|
||||
}
|
||||
|
||||
// --- tracker_delete_task ---
|
||||
|
||||
interface DeleteTaskParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
class TrackerDeleteTaskInvocation extends BaseToolInvocation<
|
||||
DeleteTaskParams,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
private readonly config: Config,
|
||||
params: DeleteTaskParams,
|
||||
messageBus: MessageBus,
|
||||
toolName: string,
|
||||
) {
|
||||
super(params, messageBus, toolName);
|
||||
}
|
||||
|
||||
private get service() {
|
||||
return this.config.getTrackerService();
|
||||
}
|
||||
getDescription(): string {
|
||||
return `Deleting task ${this.params.id}`;
|
||||
}
|
||||
|
||||
override async execute(_signal: AbortSignal): Promise<ToolResult> {
|
||||
try {
|
||||
await this.service.deleteTask(this.params.id);
|
||||
return {
|
||||
llmContent: `Deleted task ${this.params.id}.`,
|
||||
returnDisplay: `Deleted task ${this.params.id}.`,
|
||||
};
|
||||
} catch (error) {
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
llmContent: `Error deleting task: ${errorMessage}`,
|
||||
returnDisplay: 'Failed to delete task.',
|
||||
error: {
|
||||
message: errorMessage,
|
||||
type: ToolErrorType.EXECUTION_FAILED,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TrackerDeleteTaskTool extends BaseDeclarativeTool<
|
||||
DeleteTaskParams,
|
||||
ToolResult
|
||||
> {
|
||||
static readonly Name = TRACKER_DELETE_TASK_TOOL_NAME;
|
||||
constructor(
|
||||
private config: Config,
|
||||
messageBus: MessageBus,
|
||||
) {
|
||||
super(
|
||||
TrackerDeleteTaskTool.Name,
|
||||
'Delete Task',
|
||||
TRACKER_DELETE_TASK_DEFINITION.base.description!,
|
||||
Kind.Edit,
|
||||
TRACKER_DELETE_TASK_DEFINITION.base.parametersJsonSchema,
|
||||
messageBus,
|
||||
);
|
||||
}
|
||||
protected createInvocation(params: DeleteTaskParams, messageBus: MessageBus) {
|
||||
return new TrackerDeleteTaskInvocation(
|
||||
this.config,
|
||||
params,
|
||||
messageBus,
|
||||
this.name,
|
||||
);
|
||||
}
|
||||
override getSchema(modelId?: string) {
|
||||
return resolveToolDeclaration(TRACKER_DELETE_TASK_DEFINITION, modelId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user