UNPKG

aiwg

Version:

Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo

213 lines (185 loc) 5.66 kB
/** * ScheduledTaskRunner — Bridges CronScheduler events to task execution. * * Listens for cron.fired events from EventRouter and submits tasks to the * DaemonSupervisor. Built-in actions map to predefined prompts; custom * actions use the job's `prompt` field. * * @implements Plan: Daemon Starter — Scheduled Task Runner */ import { EventEmitter } from 'events'; /** * Built-in action registry — maps action names to task prompts. * Users can override these or add custom actions with a `prompt` field * in their job config. */ const BUILTIN_ACTIONS = { doctor: { prompt: 'Run `aiwg doctor` and report any issues found. If there are issues, suggest fixes.', description: 'Health check', }, 'validate-metadata': { prompt: 'Run `aiwg validate-metadata` and report any validation errors in extension metadata.', description: 'Artifact metadata audit', }, 'doc-sync': { prompt: 'Run `aiwg doc-sync code-to-docs --dry-run` and report any documentation-code drift found.', description: 'Documentation sync check', }, 'test-sync': { prompt: 'Run `npm test` and report results. If any tests fail, identify the root cause.', description: 'Test suite execution', }, }; /** * @typedef {Object} ScheduledTaskRunnerOptions * @property {import('../ralph-external/daemon-supervisor.mjs').DaemonSupervisor} supervisor * @property {import('./event-router.mjs').default} eventRouter * @property {import('../messaging/room-manager.mjs').RoomManager} [roomManager] */ export class ScheduledTaskRunner extends EventEmitter { /** @type {import('../ralph-external/daemon-supervisor.mjs').DaemonSupervisor} */ #supervisor; /** @type {import('./event-router.mjs').default} */ #eventRouter; /** @type {import('../messaging/room-manager.mjs').RoomManager|null} */ #roomManager; /** @type {Map<string, { action: string, lastRun: string|null, runCount: number }>} */ #jobHistory = new Map(); /** @type {boolean} */ #started = false; /** * @param {ScheduledTaskRunnerOptions} options */ constructor(options) { super(); this.#supervisor = options.supervisor; this.#eventRouter = options.eventRouter; this.#roomManager = options.roomManager || null; } /** * Start listening for cron events. */ start() { if (this.#started) return; this.#started = true; this.#eventRouter.on('event', (event) => { if (event.source === 'schedule' && event.type === 'cron.fired') { this.#handleCronEvent(event).catch(err => { console.error(`[scheduled-task-runner] Error handling cron event: ${err.message}`); }); } }); } /** * Stop the runner. */ stop() { this.#started = false; } /** * Handle a cron.fired event by submitting a task. * * @param {Object} event */ async #handleCronEvent(event) { const jobId = event.payload?.job_id || event.payload?.id; const action = event.payload?.action; const priority = event.payload?.priority ?? 1; if (!action) { console.warn(`[scheduled-task-runner] Cron event missing action: ${jobId}`); return; } const prompt = this.#resolvePrompt(action, event.payload); if (!prompt) { console.warn(`[scheduled-task-runner] Unknown action "${action}" for job ${jobId}`); return; } console.log(`[scheduled-task-runner] Executing job ${jobId}: ${action}`); try { const result = await this.#supervisor.submit({ prompt, priority, metadata: { source: 'scheduler', jobId, action, }, }); // Track history this.#jobHistory.set(jobId, { action, lastRun: new Date().toISOString(), runCount: (this.#jobHistory.get(jobId)?.runCount || 0) + 1, }); this.emit('job:submitted', { jobId, action, taskId: result?.loopId || result?.taskId }); // Notify notification-purpose rooms if (this.#roomManager) { const rooms = this.#roomManager.getBroadcastRooms(); if (rooms.length > 0) { this.emit('notify', { message: `Scheduled job "${jobId}" (${action}) submitted`, rooms, }); } } } catch (error) { console.error(`[scheduled-task-runner] Failed to submit job ${jobId}: ${error.message}`); this.emit('job:failed', { jobId, action, error: error.message }); } } /** * Resolve action name to a prompt string. * * @param {string} action * @param {Object} payload - Job payload (may contain custom `prompt`) * @returns {string|null} */ #resolvePrompt(action, payload) { // Custom prompt in job config takes precedence if (payload?.prompt) { return payload.prompt; } // Built-in action const builtin = BUILTIN_ACTIONS[action]; if (builtin) { return builtin.prompt; } return null; } /** * Register a custom action. * * @param {string} name * @param {Object} config * @param {string} config.prompt * @param {string} [config.description] */ registerAction(name, config) { BUILTIN_ACTIONS[name] = config; } /** * Get job execution history. * * @returns {Object[]} */ getHistory() { return [...this.#jobHistory.entries()].map(([jobId, info]) => ({ jobId, ...info, })); } /** * Get status summary. * * @returns {Object} */ getStatus() { return { started: this.#started, registeredActions: Object.keys(BUILTIN_ACTIONS), jobHistory: this.getHistory(), }; } } export default ScheduledTaskRunner;