Converts gadget-tasks from a duplicated codebase with direct MongoDB access to a headless IDE client that uses gadget-code's REST API and Socket.IO protocol to submit and process task prompts through the existing pipeline. Deleted (no longer needed): - gadget-tasks/src/models/ (5 Mongoose models — duplicated from gadget-code) - gadget-tasks/data/prompts/ (11 prompt templates — duplicated from gadget-code) - gadget-tasks/src/services/executor.ts (629-line AWL loop — duplicated from gadget-drone) - gadget-tasks/src/services/ai.ts (direct AI API calls — drones do this now) - gadget-tasks/src/services/workspace.ts (workspace management — drones do this now) Added: - gadget-tasks/src/services/platform.ts — REST API + Socket.IO headless IDE client with session lock, workspace mode, prompt submission, work order tracking Rewritten: - gadget-tasks/src/gadget-tasks.ts — new startup: user login → auth → drone selection → Socket.IO connect → schedule tasks (no MongoDB) - gadget-tasks/src/services/scheduler.ts — delegates to PlatformService.executeTask() - gadget-tasks/src/config/env.ts — platform.baseUrl config, no mongodb/google gadget-code changes: - Added PATCH /api/v1/projects/:projectId/tasks/:taskId/lastRun route - Added ProjectService.updateTaskLastRun() for atomic task field update Config changes: - GadgetTasksConfig: replaced mongodb with platform.baseUrl, removed google.cse - Dependencies: removed mongoose, @gadget/ai, @gadget/ai-toolbox, dayjs, simple-git, nanoid; added socket.io-client, @inquirer/prompts
134 lines
3.2 KiB
TypeScript
134 lines
3.2 KiB
TypeScript
// src/services/scheduler.ts
|
|
// Copyright (C) 2026 Rob Colbert <rob.colbert@openplatform.us>
|
|
// Licensed under the Apache License, Version 2.0
|
|
|
|
import env from "../config/env.ts";
|
|
import { CronJob } from "cron";
|
|
import type { IProjectTask, IProject } from "@gadget/api";
|
|
import { GadgetService } from "../lib/service.ts";
|
|
import PlatformService from "./platform.ts";
|
|
|
|
class SchedulerService extends GadgetService {
|
|
private jobs: Map<string, CronJob> = new Map();
|
|
private _activeCount: number = 0;
|
|
|
|
get name(): string {
|
|
return "SchedulerService";
|
|
}
|
|
get slug(): string {
|
|
return "svc:scheduler";
|
|
}
|
|
|
|
get activeCount(): number {
|
|
return this._activeCount;
|
|
}
|
|
|
|
get scheduledCount(): number {
|
|
return this.jobs.size;
|
|
}
|
|
|
|
async start(): Promise<void> {
|
|
this.log.info("started");
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
for (const [taskId, job] of this.jobs) {
|
|
job.stop();
|
|
this.log.info("stopped cron job", { taskId });
|
|
}
|
|
this.jobs.clear();
|
|
this.log.info("stopped");
|
|
}
|
|
|
|
/**
|
|
* Schedules a task by creating a CronJob.
|
|
* Key: task._id (string)
|
|
*/
|
|
scheduleTask(task: IProjectTask, project: IProject): void {
|
|
// If already scheduled, unschedule first
|
|
const taskId = String(task._id);
|
|
if (this.jobs.has(taskId)) {
|
|
this.unscheduleTask(taskId);
|
|
}
|
|
|
|
try {
|
|
const job = new CronJob(
|
|
task.crontab, // cron expression
|
|
async () => {
|
|
await this.executeTask(task, project);
|
|
},
|
|
null, // onComplete
|
|
true, // start
|
|
env.timezone, // timeZone
|
|
);
|
|
|
|
this.jobs.set(taskId, job);
|
|
this.log.info("task scheduled", {
|
|
taskId,
|
|
taskName: task.name,
|
|
crontab: task.crontab,
|
|
projectSlug: project.slug,
|
|
});
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
this.log.error("failed to schedule task", {
|
|
taskId,
|
|
crontab: task.crontab,
|
|
error: err.message,
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a scheduled task.
|
|
*/
|
|
unscheduleTask(taskId: string): void {
|
|
const job = this.jobs.get(taskId);
|
|
if (job) {
|
|
job.stop();
|
|
this.jobs.delete(taskId);
|
|
this.log.info("task unscheduled", { taskId });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute a task with concurrency control.
|
|
* Delegates to PlatformService.executeTask() which handles
|
|
* the full headless IDE pipeline.
|
|
*/
|
|
private async executeTask(task: IProjectTask, project: IProject): Promise<void> {
|
|
// Check concurrency
|
|
if (this._activeCount >= env.concurrency) {
|
|
this.log.warn("task skipped: concurrency limit reached", {
|
|
taskId: task._id,
|
|
taskName: task.name,
|
|
activeCount: this._activeCount,
|
|
maxConcurrency: env.concurrency,
|
|
});
|
|
return;
|
|
}
|
|
|
|
this._activeCount++;
|
|
try {
|
|
this.log.info("executing task", {
|
|
taskId: task._id,
|
|
taskName: task.name,
|
|
projectSlug: project.slug,
|
|
activeCount: this._activeCount,
|
|
});
|
|
|
|
await PlatformService.executeTask(task, project);
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
this.log.error("task execution failed", {
|
|
taskId: task._id,
|
|
error: err.message,
|
|
});
|
|
} finally {
|
|
this._activeCount--;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new SchedulerService();
|