forked from admin/VolcanicAgenticCore
Skill directories may now declare prompt or watcher jobs as files (cron or one-off at). The file is the source of truth: the scheduler re-reads every chat's jobs/ every minute, around agent runs and before /jobs and /skills, and schedules, re-plans or removes them. Ids are <skill>/<job>; run state lives in .volcanic/scheduler/skill-jobs-state.json, never in jobs.json. Watchers run from the skill directory with VOLCANIC_WORKSPACE and VOLCANIC_SKILL_DIR. Broken files are shown with the reason in /jobs, /skills, schedule list and the new <skill_jobs> prompt section; cancel answers with the file to edit. Every part of a skill directory is optional; file reading and frontmatter parsing move to Abstractions.
20 lines
1.2 KiB
C#
20 lines
1.2 KiB
C#
namespace Volcanic.AgenticCore.Extensions.Scheduler;
|
|
|
|
/// <summary>The texts a fire sends (spec §4 table).</summary>
|
|
internal static class JobMessages
|
|
{
|
|
public static string Prompt(ScheduledJob job, DateTimeOffset? lateDue, TimeZoneInfo zone) =>
|
|
$"{Header(job)}\n" + (lateDue is { } due ? $"This was due at {JobFormat.DayTime(due, zone)}; the bot was offline.\n" : "") + job.Text;
|
|
|
|
public static string WatcherMatch(ScheduledJob job, string output) => $"{Header(job)} The watcher script printed:\n{output}";
|
|
|
|
public static string Failure(ScheduledJob job, JobResult result) =>
|
|
$"⚠️ Job {Label(job)} failed ({JobFormat.FailureReason(result)}):\n```\n{(string.IsNullOrWhiteSpace(result.StderrTail) ? "(no output on stderr)" : result.StderrTail)}\n```";
|
|
|
|
private static string Header(ScheduledJob job) => $"[Scheduled job {Label(job)}]";
|
|
|
|
/// <summary><c>"Name" (id)</c>; a skill job: <c>"skill/job" (skill job, .volcanic/skills/skill/jobs/job.md)</c>, so the agent knows which file to edit.</summary>
|
|
private static string Label(ScheduledJob job) =>
|
|
job.Skill is { } skill ? $"\"{job.Id}\" (skill job, {skill.File})" : $"\"{job.Name}\" ({job.Id})";
|
|
}
|