Files
multica/server/migrations/042_autopilot.up.sql
Jiayuan Zhang d88fe2608e feat(autopilot): scheduled/triggered automations for AI agents (#1028)
* feat(autopilot): add scheduled/triggered automation for AI agents

Introduce the Autopilot feature — recurring automations that assign work
to AI agents on a schedule or manual trigger. Supports two execution
modes: create_issue (creates an issue for the agent to work on) and
run_only (directly enqueues an agent task without issue pollution).

Backend: migration (3 tables + 2 columns), sqlc queries, AutopilotService
with concurrency policies (skip/queue/replace), HTTP CRUD + trigger
endpoints, background cron scheduler (30s tick), event listeners for
issue→run and task→run status sync.

Frontend: types, API client methods, TanStack Query hooks with optimistic
mutations, realtime cache invalidation, list page with create dialog,
detail page with trigger management and run history, sidebar nav + routes
for both web and desktop apps.

* feat(autopilot): improve UX — trigger config, edit dialog, template gallery

- Replace raw cron input with friendly frequency tabs (Hourly/Daily/Weekdays/Weekly/Custom), time picker, and timezone dropdown defaulting to user's local timezone
- Fix Select components showing UUIDs instead of names (Base UI render function pattern)
- Add Edit button on detail page opening a unified edit dialog
- Remove project/concurrency/issue-title-template from create/edit (simplify for users)
- Add trigger configuration inline during autopilot creation
- Add template gallery on empty state (6 step-by-step workflow templates)
- Rename "Description" to "Prompt" throughout UI
- Inject autopilot run timestamp into issue description for agent date awareness
- Treat issue status "in_review" as run completion (fixes skip on next trigger)
- Make migration idempotent with IF NOT EXISTS clauses
2026-04-15 04:54:37 +08:00

78 lines
3.8 KiB
SQL

-- Autopilot: scheduled/triggered automations that assign recurring work to AI Agents.
CREATE TABLE IF NOT EXISTS autopilot (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
workspace_id UUID NOT NULL REFERENCES workspace(id) ON DELETE CASCADE,
project_id UUID REFERENCES project(id) ON DELETE SET NULL,
title TEXT NOT NULL,
description TEXT,
assignee_id UUID NOT NULL REFERENCES agent(id) ON DELETE CASCADE,
priority TEXT NOT NULL DEFAULT 'medium'
CHECK (priority IN ('urgent', 'high', 'medium', 'low', 'none')),
status TEXT NOT NULL DEFAULT 'active'
CHECK (status IN ('active', 'paused', 'archived')),
execution_mode TEXT NOT NULL DEFAULT 'create_issue'
CHECK (execution_mode IN ('create_issue', 'run_only')),
issue_title_template TEXT,
concurrency_policy TEXT NOT NULL DEFAULT 'skip'
CHECK (concurrency_policy IN ('skip', 'queue', 'replace')),
created_by_type TEXT NOT NULL CHECK (created_by_type IN ('member', 'agent')),
created_by_id UUID NOT NULL,
last_run_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_autopilot_workspace ON autopilot(workspace_id);
CREATE INDEX IF NOT EXISTS idx_autopilot_assignee ON autopilot(assignee_id);
-- Trigger: how an autopilot gets kicked off (schedule, webhook, or API).
CREATE TABLE IF NOT EXISTS autopilot_trigger (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
autopilot_id UUID NOT NULL REFERENCES autopilot(id) ON DELETE CASCADE,
kind TEXT NOT NULL CHECK (kind IN ('schedule', 'webhook', 'api')),
enabled BOOLEAN NOT NULL DEFAULT true,
cron_expression TEXT,
timezone TEXT DEFAULT 'UTC',
next_run_at TIMESTAMPTZ,
webhook_token TEXT,
label TEXT,
last_fired_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_autopilot_trigger_autopilot ON autopilot_trigger(autopilot_id);
CREATE INDEX IF NOT EXISTS idx_autopilot_trigger_next_run ON autopilot_trigger(next_run_at)
WHERE enabled = true AND kind = 'schedule';
-- Run: one execution of an autopilot.
CREATE TABLE IF NOT EXISTS autopilot_run (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
autopilot_id UUID NOT NULL REFERENCES autopilot(id) ON DELETE CASCADE,
trigger_id UUID REFERENCES autopilot_trigger(id) ON DELETE SET NULL,
source TEXT NOT NULL CHECK (source IN ('schedule', 'manual', 'webhook', 'api')),
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'issue_created', 'running', 'skipped', 'completed', 'failed')),
issue_id UUID REFERENCES issue(id) ON DELETE SET NULL,
task_id UUID REFERENCES agent_task_queue(id) ON DELETE SET NULL,
triggered_at TIMESTAMPTZ NOT NULL DEFAULT now(),
completed_at TIMESTAMPTZ,
failure_reason TEXT,
trigger_payload JSONB,
result JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_autopilot_run_autopilot ON autopilot_run(autopilot_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_autopilot_run_status ON autopilot_run(autopilot_id, status)
WHERE status IN ('pending', 'issue_created', 'running');
-- Link agent tasks to autopilot runs (same pattern as chat_session_id from migration 033).
ALTER TABLE agent_task_queue ADD COLUMN IF NOT EXISTS autopilot_run_id UUID REFERENCES autopilot_run(id) ON DELETE SET NULL;
-- Track which issues were created by an autopilot so they can be filtered in lists.
ALTER TABLE issue ADD COLUMN IF NOT EXISTS origin_type TEXT CHECK (origin_type IN ('autopilot'));
ALTER TABLE issue ADD COLUMN IF NOT EXISTS origin_id UUID;
CREATE INDEX IF NOT EXISTS idx_issue_origin ON issue(origin_type, origin_id) WHERE origin_type IS NOT NULL;