feat: claude one-shot port from nanobot python codebase (v0.1.4.post4)
This commit is contained in:
122
src/agent/tools/cron.ts
Normal file
122
src/agent/tools/cron.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import type { CronService } from '../../cron/service.ts';
|
||||
import type { CronJob, CronPayload, CronSchedule } from '../../cron/types.ts';
|
||||
import { strArg } from './base.ts';
|
||||
import type { Tool } from './base.ts';
|
||||
|
||||
export class CronTool implements Tool {
|
||||
readonly name = 'cron';
|
||||
readonly description = `Manage scheduled tasks (cron jobs). Actions:
|
||||
- list: list all jobs
|
||||
- add: create a new job (requires name, schedule, message)
|
||||
- remove: delete a job by id
|
||||
- enable/disable: toggle a job
|
||||
- run: execute a job immediately
|
||||
- status: summary of all jobs`;
|
||||
|
||||
readonly parameters = {
|
||||
action: {
|
||||
type: 'string',
|
||||
enum: ['list', 'add', 'remove', 'enable', 'disable', 'run', 'status'],
|
||||
description: 'Action to perform.',
|
||||
},
|
||||
id: { type: 'string', description: 'Job ID (for remove/enable/disable/run).' },
|
||||
name: { type: 'string', description: 'Human-readable job name (for add).' },
|
||||
message: { type: 'string', description: 'Message to inject when the job runs (for add).' },
|
||||
schedule: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Schedule definition. One of: {kind:"at",atMs:number}, {kind:"every",everyMs:number}, {kind:"cron",expr:string,tz?:string}',
|
||||
},
|
||||
deleteAfterRun: { type: 'boolean', description: 'Delete job after first execution (for add).' },
|
||||
};
|
||||
readonly required = ['action'];
|
||||
|
||||
private _service: CronService;
|
||||
|
||||
constructor(service: CronService) {
|
||||
this._service = service;
|
||||
}
|
||||
|
||||
async execute(args: Record<string, unknown>): Promise<string> {
|
||||
const action = strArg(args, 'action');
|
||||
|
||||
switch (action) {
|
||||
case 'list':
|
||||
return this._list();
|
||||
case 'status':
|
||||
return this._service.status();
|
||||
case 'add':
|
||||
return this._add(args);
|
||||
case 'remove': {
|
||||
const id = strArg(args, 'id');
|
||||
if (!id) return 'Error: id is required for remove.';
|
||||
return this._service.removeJob(id) ? `Job ${id} removed.` : `Error: job ${id} not found.`;
|
||||
}
|
||||
case 'enable': {
|
||||
const id = strArg(args, 'id');
|
||||
if (!id) return 'Error: id is required for enable.';
|
||||
return this._service.enableJob(id, true) ? `Job ${id} enabled.` : `Error: job ${id} not found.`;
|
||||
}
|
||||
case 'disable': {
|
||||
const id = strArg(args, 'id');
|
||||
if (!id) return 'Error: id is required for disable.';
|
||||
return this._service.enableJob(id, false) ? `Job ${id} disabled.` : `Error: job ${id} not found.`;
|
||||
}
|
||||
case 'run': {
|
||||
const id = strArg(args, 'id');
|
||||
if (!id) return 'Error: id is required for run.';
|
||||
return this._service.runJob(id);
|
||||
}
|
||||
default:
|
||||
return `Error: unknown action "${action}". Valid: list, add, remove, enable, disable, run, status.`;
|
||||
}
|
||||
}
|
||||
|
||||
private _list(): string {
|
||||
const jobs = this._service.listJobs();
|
||||
if (jobs.length === 0) return 'No cron jobs.';
|
||||
return jobs
|
||||
.map((j: CronJob) => {
|
||||
const schedule = this._fmtSchedule(j.schedule);
|
||||
const next = j.state.nextRunAtMs ? new Date(j.state.nextRunAtMs).toISOString() : 'N/A';
|
||||
return `${j.id} [${j.enabled ? 'ON' : 'OFF'}] "${j.name}" ${schedule} next=${next}`;
|
||||
})
|
||||
.join('\n');
|
||||
}
|
||||
|
||||
private _fmtSchedule(s: CronSchedule): string {
|
||||
if (s.kind === 'at') return `at ${new Date(s.atMs).toISOString()}`;
|
||||
if (s.kind === 'every') return `every ${s.everyMs}ms`;
|
||||
return `cron(${s.expr}${s.tz ? ` ${s.tz}` : ''})`;
|
||||
}
|
||||
|
||||
private _add(args: Record<string, unknown>): string {
|
||||
const name = strArg(args, 'name').trim();
|
||||
const message = strArg(args, 'message').trim();
|
||||
if (!name) return 'Error: name is required for add.';
|
||||
if (!message) return 'Error: message is required for add.';
|
||||
|
||||
const rawSchedule = args['schedule'];
|
||||
if (!rawSchedule || typeof rawSchedule !== 'object') {
|
||||
return 'Error: schedule is required for add. Format: {kind:"cron",expr:"0 9 * * *"} or {kind:"every",everyMs:60000} or {kind:"at",atMs:1234567890}';
|
||||
}
|
||||
|
||||
const id = `job_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
|
||||
const payload: CronPayload = { kind: 'agent_turn', message, deliver: false };
|
||||
const schedule = rawSchedule as CronSchedule;
|
||||
|
||||
try {
|
||||
const job = this._service.addJob({
|
||||
id,
|
||||
name,
|
||||
enabled: true,
|
||||
schedule,
|
||||
payload,
|
||||
deleteAfterRun: Boolean(args['deleteAfterRun']),
|
||||
});
|
||||
return `Job created: ${job.id} "${job.name}"`;
|
||||
} catch (err) {
|
||||
return `Error creating job: ${String(err)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user