mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-06-13 04:48:09 -07:00
feat: implement session-scoped scheduled tasks and /loop command
This commit is contained in:
@@ -32,6 +32,7 @@ import { docsCommand } from '../ui/commands/docsCommand.js';
|
||||
import { directoryCommand } from '../ui/commands/directoryCommand.js';
|
||||
import { editorCommand } from '../ui/commands/editorCommand.js';
|
||||
import { extensionsCommand } from '../ui/commands/extensionsCommand.js';
|
||||
import { loopCommand } from '../ui/commands/loopCommand.js';
|
||||
import { footerCommand } from '../ui/commands/footerCommand.js';
|
||||
import { helpCommand } from '../ui/commands/helpCommand.js';
|
||||
import { shortcutsCommand } from '../ui/commands/shortcutsCommand.js';
|
||||
@@ -155,6 +156,7 @@ export class BuiltinCommandLoader implements ICommandLoader {
|
||||
]
|
||||
: [extensionsCommand(this.config?.getEnableExtensionReloading())]),
|
||||
helpCommand,
|
||||
loopCommand,
|
||||
footerCommand,
|
||||
shortcutsCommand,
|
||||
...(this.config?.getEnableHooksUI() ? [hooksCommand] : []),
|
||||
|
||||
@@ -83,7 +83,9 @@ import {
|
||||
logBillingEvent,
|
||||
ApiKeyUpdatedEvent,
|
||||
type InjectionSource,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
cronSchedulerService,
|
||||
type ScheduledTask} from '@google/gemini-cli-core';
|
||||
import { validateAuthMethod } from '../config/auth.js';
|
||||
import process from 'node:process';
|
||||
import { useHistory } from './hooks/useHistoryManager.js';
|
||||
@@ -1211,6 +1213,16 @@ Logging in with Google... Restarting Gemini CLI to continue.
|
||||
isMcpReady,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const handleTaskDue = (task: ScheduledTask) => {
|
||||
addMessage(task.prompt);
|
||||
};
|
||||
cronSchedulerService.on('task_due', handleTaskDue);
|
||||
return () => {
|
||||
cronSchedulerService.off('task_due', handleTaskDue);
|
||||
};
|
||||
}, [addMessage]);
|
||||
|
||||
cancelHandlerRef.current = useCallback(
|
||||
(shouldRestorePrompt: boolean = true) => {
|
||||
if (isToolAwaitingConfirmation(pendingHistoryItems)) {
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { loopCommand } from './loopCommand.js';
|
||||
import { createMockCommandContext } from '../../test-utils/mockCommandContext.js';
|
||||
import { MessageType } from '../types.js';
|
||||
import { cronSchedulerService } from '@google/gemini-cli-core';
|
||||
|
||||
vi.mock('@google/gemini-cli-core', async (importOriginal) => {
|
||||
const actual =
|
||||
await importOriginal<typeof import('@google/gemini-cli-core')>();
|
||||
return {
|
||||
...actual,
|
||||
cronSchedulerService: {
|
||||
scheduleTask: vi.fn(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('loopCommand', () => {
|
||||
let mockContext: ReturnType<typeof createMockCommandContext>;
|
||||
|
||||
beforeEach(() => {
|
||||
mockContext = createMockCommandContext();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should print an error if no args are provided', async () => {
|
||||
mockContext.invocation!.args = ' ';
|
||||
await loopCommand.action!(mockContext, '');
|
||||
|
||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
type: MessageType.INFO,
|
||||
text: expect.stringContaining('Please provide a prompt'),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should default to 10m if no interval is provided', async () => {
|
||||
mockContext.invocation!.args = 'check the build';
|
||||
vi.mocked(cronSchedulerService.scheduleTask).mockReturnValue('abc12345');
|
||||
|
||||
await loopCommand.action!(mockContext, '');
|
||||
|
||||
expect(cronSchedulerService.scheduleTask).toHaveBeenCalledWith(
|
||||
'10m',
|
||||
'check the build',
|
||||
true,
|
||||
);
|
||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: expect.stringContaining('every 10m'),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should parse leading interval', async () => {
|
||||
mockContext.invocation!.args = '5m check the build';
|
||||
vi.mocked(cronSchedulerService.scheduleTask).mockReturnValue('def56789');
|
||||
|
||||
await loopCommand.action!(mockContext, '');
|
||||
|
||||
expect(cronSchedulerService.scheduleTask).toHaveBeenCalledWith(
|
||||
'5m',
|
||||
'check the build',
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle scheduling errors', async () => {
|
||||
mockContext.invocation!.args = 'invalid check the build';
|
||||
vi.mocked(cronSchedulerService.scheduleTask).mockImplementation(() => {
|
||||
throw new Error('Invalid format');
|
||||
});
|
||||
|
||||
await loopCommand.action!(mockContext, '');
|
||||
|
||||
expect(mockContext.ui.addItem).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
text: expect.stringContaining(
|
||||
'Failed to schedule task: Invalid format',
|
||||
),
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { CommandKind, type SlashCommand } from './types.js';
|
||||
import { cronSchedulerService } from '@google/gemini-cli-core';
|
||||
import { MessageType } from '../types.js';
|
||||
|
||||
export const loopCommand: SlashCommand = {
|
||||
name: 'loop',
|
||||
kind: CommandKind.BUILT_IN,
|
||||
description: 'Schedules a repeating prompt (e.g., /loop 5m check the build)',
|
||||
autoExecute: true,
|
||||
action: async (context) => {
|
||||
const args = context.invocation?.args?.trim() || '';
|
||||
if (!args) {
|
||||
context.ui.addItem({
|
||||
type: MessageType.INFO,
|
||||
text: 'Please provide a prompt to loop. Example: /loop 5m check the build',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Default to 10 minutes if no interval is provided
|
||||
let intervalString = '10m';
|
||||
|
||||
// Check if the first word is an interval
|
||||
const match = args.match(/^(\d+[smhd])\s+(.*)/i);
|
||||
let prompt = args;
|
||||
|
||||
if (match) {
|
||||
intervalString = match[1].toLowerCase();
|
||||
prompt = match[2].trim();
|
||||
}
|
||||
|
||||
try {
|
||||
const id = cronSchedulerService.scheduleTask(
|
||||
intervalString,
|
||||
prompt,
|
||||
true,
|
||||
);
|
||||
context.ui.addItem({
|
||||
type: MessageType.INFO,
|
||||
text: `Scheduled recurring task \`${id}\` to run \`${prompt}\` every ${intervalString}.`,
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
context.ui.addItem({
|
||||
type: MessageType.INFO,
|
||||
text: `Failed to schedule task: ${message}`,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -78,6 +78,11 @@ import { shouldAttemptBrowserLaunch } from '../utils/browser.js';
|
||||
import type { MCPOAuthConfig } from '../mcp/oauth-provider.js';
|
||||
import { ideContextStore } from '../ide/ideContext.js';
|
||||
import { WriteTodosTool } from '../tools/write-todos.js';
|
||||
import {
|
||||
ScheduleTaskTool,
|
||||
ListTasksTool,
|
||||
CancelTaskTool,
|
||||
} from '../tools/cronTools.js';
|
||||
import {
|
||||
StandardFileSystemService,
|
||||
type FileSystemService,
|
||||
@@ -3439,6 +3444,15 @@ export class Config implements McpContext, AgentLoopContext {
|
||||
maybeRegister(AskUserTool, () =>
|
||||
registry.registerTool(new AskUserTool(this.messageBus)),
|
||||
);
|
||||
maybeRegister(ScheduleTaskTool, () =>
|
||||
registry.registerTool(new ScheduleTaskTool(this.messageBus)),
|
||||
);
|
||||
maybeRegister(ListTasksTool, () =>
|
||||
registry.registerTool(new ListTasksTool(this.messageBus)),
|
||||
);
|
||||
maybeRegister(CancelTaskTool, () =>
|
||||
registry.registerTool(new CancelTaskTool(this.messageBus)),
|
||||
);
|
||||
if (this.getUseWriteTodos()) {
|
||||
maybeRegister(WriteTodosTool, () =>
|
||||
registry.registerTool(new WriteTodosTool(this.messageBus)),
|
||||
|
||||
@@ -137,6 +137,7 @@ export * from './services/sessionSummaryUtils.js';
|
||||
export * from './services/contextManager.js';
|
||||
export * from './services/trackerService.js';
|
||||
export * from './services/trackerTypes.js';
|
||||
export * from './services/cronSchedulerService.js';
|
||||
export * from './services/keychainService.js';
|
||||
export * from './services/keychainTypes.js';
|
||||
export * from './skills/skillManager.js';
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
||||
import {
|
||||
CronSchedulerService,
|
||||
type ScheduledTask,
|
||||
} from './cronSchedulerService.js';
|
||||
|
||||
describe('CronSchedulerService', () => {
|
||||
let service: CronSchedulerService;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
service = new CronSchedulerService();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
service.stop();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('should parse intervals and schedule a task', () => {
|
||||
const id = service.scheduleTask('5m', 'test prompt');
|
||||
const tasks = service.listTasks();
|
||||
|
||||
expect(tasks).toHaveLength(1);
|
||||
expect(tasks[0].id).toBe(id);
|
||||
expect(tasks[0].prompt).toBe('test prompt');
|
||||
expect(tasks[0].intervalMs).toBe(5 * 60 * 1000);
|
||||
});
|
||||
|
||||
it('should throw on invalid interval', () => {
|
||||
expect(() => service.scheduleTask('invalid', 'test prompt')).toThrow(
|
||||
/Invalid interval format/,
|
||||
);
|
||||
});
|
||||
|
||||
it('should emit event when task is due', () => {
|
||||
const callback = vi.fn();
|
||||
service.on('task_due', callback);
|
||||
|
||||
service.scheduleTask('10s', 'test prompt');
|
||||
|
||||
// Advance 9 seconds, shouldn't fire
|
||||
vi.advanceTimersByTime(9000);
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
|
||||
// Advance 1 more second, should fire
|
||||
vi.advanceTimersByTime(1000);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
const taskArg = callback.mock.calls[0][0] as ScheduledTask;
|
||||
expect(taskArg.prompt).toBe('test prompt');
|
||||
});
|
||||
|
||||
it('should handle recurring tasks correctly', () => {
|
||||
const callback = vi.fn();
|
||||
service.on('task_due', callback);
|
||||
|
||||
service.scheduleTask('10s', 'test prompt', true);
|
||||
|
||||
// First run
|
||||
vi.advanceTimersByTime(10000);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Second run
|
||||
vi.advanceTimersByTime(10000);
|
||||
expect(callback).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should handle one-shot tasks correctly', () => {
|
||||
const callback = vi.fn();
|
||||
service.on('task_due', callback);
|
||||
|
||||
service.scheduleTask('10s', 'test prompt', false);
|
||||
|
||||
// First run
|
||||
vi.advanceTimersByTime(10000);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(service.listTasks()).toHaveLength(0); // Task should be removed
|
||||
|
||||
// Advance again, shouldn't fire
|
||||
vi.advanceTimersByTime(10000);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should cancel a task', () => {
|
||||
const id = service.scheduleTask('10s', 'test prompt');
|
||||
expect(service.listTasks()).toHaveLength(1);
|
||||
|
||||
service.cancelTask(id);
|
||||
expect(service.listTasks()).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { EventEmitter } from 'node:events';
|
||||
|
||||
export interface ScheduledTask {
|
||||
id: string;
|
||||
intervalMs: number | null; // null if one-shot (though this basic implementation focuses on recurring)
|
||||
prompt: string;
|
||||
createdAt: number;
|
||||
nextRunAt: number;
|
||||
isRecurring: boolean;
|
||||
}
|
||||
|
||||
export class CronSchedulerService extends EventEmitter {
|
||||
private tasks: Map<string, ScheduledTask> = new Map();
|
||||
private tickInterval: NodeJS.Timeout | null = null;
|
||||
private isRunning = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the background interval that checks for due tasks.
|
||||
*/
|
||||
start() {
|
||||
if (this.isRunning) return;
|
||||
this.isRunning = true;
|
||||
this.tickInterval = setInterval(() => this.tick(), 1000);
|
||||
// Don't prevent process exit if only the scheduler is running
|
||||
this.tickInterval.unref();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the background interval.
|
||||
*/
|
||||
stop() {
|
||||
this.isRunning = false;
|
||||
if (this.tickInterval) {
|
||||
clearInterval(this.tickInterval);
|
||||
this.tickInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedules a new task.
|
||||
* @param intervalString An interval string like "5m", "10s", "2h".
|
||||
* @param prompt The prompt to execute.
|
||||
* @param isRecurring Whether it's a recurring task or one-shot.
|
||||
* @returns The generated task ID.
|
||||
*/
|
||||
scheduleTask(
|
||||
intervalString: string,
|
||||
prompt: string,
|
||||
isRecurring: boolean = true,
|
||||
): string {
|
||||
const intervalMs = this.parseInterval(intervalString);
|
||||
if (!intervalMs) {
|
||||
throw new Error(
|
||||
`Invalid interval format: ${intervalString}. Supported formats: 10s, 5m, 2h.`,
|
||||
);
|
||||
}
|
||||
|
||||
const id = Math.random().toString(36).substring(2, 10); // 8-character ID
|
||||
const now = Date.now();
|
||||
|
||||
const task: ScheduledTask = {
|
||||
id,
|
||||
intervalMs,
|
||||
prompt,
|
||||
createdAt: now,
|
||||
nextRunAt: now + intervalMs,
|
||||
isRecurring,
|
||||
};
|
||||
|
||||
this.tasks.set(id, task);
|
||||
|
||||
if (!this.isRunning) {
|
||||
this.start();
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all active scheduled tasks.
|
||||
*/
|
||||
listTasks(): ScheduledTask[] {
|
||||
return Array.from(this.tasks.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a scheduled task by ID.
|
||||
*/
|
||||
cancelTask(id: string): boolean {
|
||||
return this.tasks.delete(id);
|
||||
}
|
||||
|
||||
private tick() {
|
||||
const now = Date.now();
|
||||
for (const [id, task] of this.tasks.entries()) {
|
||||
if (now >= task.nextRunAt) {
|
||||
// Emit the event so the REPL can pick it up
|
||||
this.emit('task_due', task);
|
||||
|
||||
if (task.isRecurring && task.intervalMs) {
|
||||
// Calculate next run time
|
||||
task.nextRunAt = now + task.intervalMs;
|
||||
} else {
|
||||
// One-shot, remove it
|
||||
this.tasks.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-stop if no tasks
|
||||
if (this.tasks.size === 0) {
|
||||
this.stop();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses strings like "10s", "5m", "2h", "1d" into milliseconds.
|
||||
*/
|
||||
private parseInterval(interval: string): number | null {
|
||||
const match = interval.match(/^(\d+)([smhd])$/);
|
||||
if (!match) return null;
|
||||
|
||||
const value = parseInt(match[1], 10);
|
||||
const unit = match[2];
|
||||
|
||||
switch (unit) {
|
||||
case 's':
|
||||
return value * 1000;
|
||||
case 'm':
|
||||
return value * 60 * 1000;
|
||||
case 'h':
|
||||
return value * 60 * 60 * 1000;
|
||||
case 'd':
|
||||
return value * 24 * 60 * 60 * 1000;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const cronSchedulerService = new CronSchedulerService();
|
||||
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {
|
||||
BaseDeclarativeTool,
|
||||
BaseToolInvocation,
|
||||
Kind,
|
||||
type ToolResult,
|
||||
} from './tools.js';
|
||||
import { cronSchedulerService } from '../services/cronSchedulerService.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import { Type, type FunctionDeclaration } from '@google/genai';
|
||||
|
||||
// --- Declarations ---
|
||||
|
||||
export const SCHEDULE_TASK_TOOL_NAME = 'schedule_task';
|
||||
export const LIST_TASKS_TOOL_NAME = 'list_scheduled_tasks';
|
||||
export const CANCEL_TASK_TOOL_NAME = 'cancel_scheduled_task';
|
||||
|
||||
export const SCHEDULE_TASK_DECLARATION: FunctionDeclaration = {
|
||||
name: SCHEDULE_TASK_TOOL_NAME,
|
||||
description: 'Schedules a prompt to run after a specified interval.',
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
interval: {
|
||||
type: Type.STRING,
|
||||
description: 'Interval string like "10s", "5m", "1h".',
|
||||
},
|
||||
prompt: {
|
||||
type: Type.STRING,
|
||||
description: 'The prompt to run when the task triggers.',
|
||||
},
|
||||
recurring: {
|
||||
type: Type.BOOLEAN,
|
||||
description: 'Whether the task should run repeatedly.',
|
||||
},
|
||||
},
|
||||
required: ['interval', 'prompt'],
|
||||
},
|
||||
};
|
||||
|
||||
export const LIST_TASKS_DECLARATION: FunctionDeclaration = {
|
||||
name: LIST_TASKS_TOOL_NAME,
|
||||
description: 'Lists all currently scheduled tasks.',
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {},
|
||||
},
|
||||
};
|
||||
|
||||
export const CANCEL_TASK_DECLARATION: FunctionDeclaration = {
|
||||
name: CANCEL_TASK_TOOL_NAME,
|
||||
description: 'Cancels a scheduled task by ID.',
|
||||
parameters: {
|
||||
type: Type.OBJECT,
|
||||
properties: {
|
||||
id: {
|
||||
type: Type.STRING,
|
||||
description: 'The ID of the task to cancel.',
|
||||
},
|
||||
},
|
||||
required: ['id'],
|
||||
},
|
||||
};
|
||||
|
||||
// --- Invocations ---
|
||||
|
||||
interface ScheduleTaskParams {
|
||||
interval: string;
|
||||
prompt: string;
|
||||
recurring?: boolean;
|
||||
}
|
||||
|
||||
class ScheduleTaskInvocation extends BaseToolInvocation<
|
||||
ScheduleTaskParams,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
params: ScheduleTaskParams,
|
||||
messageBus: MessageBus,
|
||||
toolName: string,
|
||||
) {
|
||||
super(params, messageBus, toolName);
|
||||
}
|
||||
|
||||
override getDescription(): string {
|
||||
return `Schedule task: ${this.params.prompt} (Interval: ${this.params.interval})`;
|
||||
}
|
||||
|
||||
async execute(): Promise<ToolResult> {
|
||||
try {
|
||||
const isRecurring = this.params.recurring !== false;
|
||||
const id = cronSchedulerService.scheduleTask(
|
||||
this.params.interval,
|
||||
this.params.prompt,
|
||||
isRecurring,
|
||||
);
|
||||
return {
|
||||
llmContent: `Task scheduled successfully. ID: ${id}`,
|
||||
returnDisplay: `Task scheduled successfully. ID: ${id}`,
|
||||
};
|
||||
} catch (e: unknown) {
|
||||
const message = e instanceof Error ? e.message : String(e);
|
||||
return {
|
||||
llmContent: `Error scheduling task: ${message}`,
|
||||
returnDisplay: `Error scheduling task: ${message}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
override async getConfirmationDetails() {
|
||||
return false as const;
|
||||
}
|
||||
}
|
||||
|
||||
class ListTasksInvocation extends BaseToolInvocation<object, ToolResult> {
|
||||
constructor(params: object, messageBus: MessageBus, toolName: string) {
|
||||
super(params, messageBus, toolName);
|
||||
}
|
||||
|
||||
override getDescription(): string {
|
||||
return 'List scheduled tasks';
|
||||
}
|
||||
|
||||
async execute(): Promise<ToolResult> {
|
||||
const tasks = cronSchedulerService.listTasks();
|
||||
if (tasks.length === 0) {
|
||||
return {
|
||||
llmContent: 'No scheduled tasks.',
|
||||
returnDisplay: 'No scheduled tasks.',
|
||||
};
|
||||
}
|
||||
const lines = tasks.map(
|
||||
(t) =>
|
||||
`- ID: ${t.id}, Interval: ${t.intervalMs}ms, Prompt: "${t.prompt}", Recurring: ${t.isRecurring}`,
|
||||
);
|
||||
return { llmContent: lines.join('\n'), returnDisplay: lines.join('\n') };
|
||||
}
|
||||
|
||||
override async getConfirmationDetails() {
|
||||
return false as const;
|
||||
}
|
||||
}
|
||||
|
||||
interface CancelTaskParams {
|
||||
id: string;
|
||||
}
|
||||
|
||||
class CancelTaskInvocation extends BaseToolInvocation<
|
||||
CancelTaskParams,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(
|
||||
params: CancelTaskParams,
|
||||
messageBus: MessageBus,
|
||||
toolName: string,
|
||||
) {
|
||||
super(params, messageBus, toolName);
|
||||
}
|
||||
|
||||
override getDescription(): string {
|
||||
return `Cancel task ID: ${this.params.id}`;
|
||||
}
|
||||
|
||||
async execute(): Promise<ToolResult> {
|
||||
const success = cronSchedulerService.cancelTask(this.params.id);
|
||||
if (success) {
|
||||
return {
|
||||
llmContent: `Task ${this.params.id} cancelled successfully.`,
|
||||
returnDisplay: `Task ${this.params.id} cancelled successfully.`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
llmContent: `Task ${this.params.id} not found.`,
|
||||
returnDisplay: `Task ${this.params.id} not found.`,
|
||||
};
|
||||
}
|
||||
|
||||
override async getConfirmationDetails() {
|
||||
return false as const;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tools ---
|
||||
|
||||
export class ScheduleTaskTool extends BaseDeclarativeTool<
|
||||
ScheduleTaskParams,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(messageBus: MessageBus) {
|
||||
super(
|
||||
SCHEDULE_TASK_TOOL_NAME,
|
||||
'ScheduleTask',
|
||||
SCHEDULE_TASK_DECLARATION.description ?? '',
|
||||
Kind.Other,
|
||||
SCHEDULE_TASK_DECLARATION.parameters,
|
||||
messageBus,
|
||||
);
|
||||
}
|
||||
|
||||
protected createInvocation(
|
||||
params: ScheduleTaskParams,
|
||||
messageBus: MessageBus,
|
||||
): ScheduleTaskInvocation {
|
||||
return new ScheduleTaskInvocation(params, messageBus, this.name);
|
||||
}
|
||||
}
|
||||
|
||||
export class ListTasksTool extends BaseDeclarativeTool<object, ToolResult> {
|
||||
constructor(messageBus: MessageBus) {
|
||||
super(
|
||||
LIST_TASKS_TOOL_NAME,
|
||||
'ListTasks',
|
||||
LIST_TASKS_DECLARATION.description ?? '',
|
||||
Kind.Other,
|
||||
LIST_TASKS_DECLARATION.parameters,
|
||||
messageBus,
|
||||
);
|
||||
}
|
||||
|
||||
protected createInvocation(
|
||||
params: object,
|
||||
messageBus: MessageBus,
|
||||
): ListTasksInvocation {
|
||||
return new ListTasksInvocation(params, messageBus, this.name);
|
||||
}
|
||||
}
|
||||
|
||||
export class CancelTaskTool extends BaseDeclarativeTool<
|
||||
CancelTaskParams,
|
||||
ToolResult
|
||||
> {
|
||||
constructor(messageBus: MessageBus) {
|
||||
super(
|
||||
CANCEL_TASK_TOOL_NAME,
|
||||
'CancelTask',
|
||||
CANCEL_TASK_DECLARATION.description ?? '',
|
||||
Kind.Other,
|
||||
CANCEL_TASK_DECLARATION.parameters,
|
||||
messageBus,
|
||||
);
|
||||
}
|
||||
|
||||
protected createInvocation(
|
||||
params: CancelTaskParams,
|
||||
messageBus: MessageBus,
|
||||
): CancelTaskInvocation {
|
||||
return new CancelTaskInvocation(params, messageBus, this.name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user