feat(tasks): support markdown scheduled-task loops in .agents/loops
Adds markdown-based scheduled-task definitions ("loops") discovered from
.agents/loops/*.md (project scope, ancestor directories up to the
worktree root) and ~/.agents/loops/*.md (user scope), mirroring the
skills discovery pattern.
File format: YAML frontmatter (name, schedule cron, enabled, model as
provider/model, optional agent/timezone) plus the markdown body as the
execution prompt. Discovery and parsing live in
scheduled-tasks/loops.js; project-config gains reconcileLoopTasks which
runs inside the project write lock on every syncProject:
- identity by task name; a loop takes over a matching task, preserving
its id and runtime state (markdown wins on conflict with JSON)
- tasks whose loopFile is gone are unscheduled; JSON tasks are never
removed
- new loops are created under deterministic loop:<scope>:<name> ids
- project scope shadows user scope on name collisions
- malformed files are skipped with a warning and never block valid ones
Runtime state stays in the project config/state store; it is never
written to the markdown files. Module documentation updated with the
file format and reconciliation rules.
Fixes #2583
This commit is contained in:
@@ -3,6 +3,7 @@ import { DateTime } from 'luxon';
|
||||
import parser from 'cron-parser';
|
||||
import { expandSnippets } from '../opencode/snippets.js';
|
||||
import { buildGoalIntroText, createSessionGoal } from '../session-goal/create.js';
|
||||
import { discoverLoops } from './loops.js';
|
||||
|
||||
const DEFAULT_GLOBAL_CONCURRENCY = 4;
|
||||
const DEFAULT_PROJECT_CONCURRENCY = 2;
|
||||
@@ -382,8 +383,19 @@ export const createScheduledTasksRuntime = (deps) => {
|
||||
|
||||
const syncProject = async (projectID) => {
|
||||
await ensureProjectPath(projectID);
|
||||
const projectPath = projectPathByID.get(projectID) || null;
|
||||
|
||||
let tasks;
|
||||
if (projectPath) {
|
||||
// Reconcile `.agents/loops` definitions with the persisted task list:
|
||||
// loop files are authoritative while present, removed files unschedule
|
||||
// their task, and runtime state is preserved (see loops.js).
|
||||
const loops = await discoverLoops(projectPath);
|
||||
tasks = await projectConfigRuntime.reconcileLoopTasks(projectID, loops);
|
||||
} else {
|
||||
tasks = await projectConfigRuntime.listScheduledTasks(projectID);
|
||||
}
|
||||
|
||||
const tasks = await projectConfigRuntime.listScheduledTasks(projectID);
|
||||
setProjectTasks(projectID, tasks);
|
||||
|
||||
for (const task of tasks) {
|
||||
|
||||
Reference in New Issue
Block a user