Add routines automation workflows

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta
2026-03-19 08:39:24 -05:00
parent 7a652b8998
commit 8f5196f7d6
35 changed files with 25977 additions and 5 deletions

View File

@@ -122,6 +122,9 @@ export type IssueStatus = (typeof ISSUE_STATUSES)[number];
export const ISSUE_PRIORITIES = ["critical", "high", "medium", "low"] as const;
export type IssuePriority = (typeof ISSUE_PRIORITIES)[number];
export const ISSUE_ORIGIN_KINDS = ["manual", "routine_execution"] as const;
export type IssueOriginKind = (typeof ISSUE_ORIGIN_KINDS)[number];
export const GOAL_LEVELS = ["company", "team", "agent", "task"] as const;
export type GoalLevel = (typeof GOAL_LEVELS)[number];
@@ -137,6 +140,34 @@ export const PROJECT_STATUSES = [
] as const;
export type ProjectStatus = (typeof PROJECT_STATUSES)[number];
export const ROUTINE_STATUSES = ["active", "paused", "archived"] as const;
export type RoutineStatus = (typeof ROUTINE_STATUSES)[number];
export const ROUTINE_CONCURRENCY_POLICIES = ["coalesce_if_active", "always_enqueue", "skip_if_active"] as const;
export type RoutineConcurrencyPolicy = (typeof ROUTINE_CONCURRENCY_POLICIES)[number];
export const ROUTINE_CATCH_UP_POLICIES = ["skip_missed", "enqueue_missed_with_cap"] as const;
export type RoutineCatchUpPolicy = (typeof ROUTINE_CATCH_UP_POLICIES)[number];
export const ROUTINE_TRIGGER_KINDS = ["schedule", "webhook", "api"] as const;
export type RoutineTriggerKind = (typeof ROUTINE_TRIGGER_KINDS)[number];
export const ROUTINE_TRIGGER_SIGNING_MODES = ["bearer", "hmac_sha256"] as const;
export type RoutineTriggerSigningMode = (typeof ROUTINE_TRIGGER_SIGNING_MODES)[number];
export const ROUTINE_RUN_STATUSES = [
"received",
"coalesced",
"skipped",
"issue_created",
"completed",
"failed",
] as const;
export type RoutineRunStatus = (typeof ROUTINE_RUN_STATUSES)[number];
export const ROUTINE_RUN_SOURCES = ["schedule", "manual", "api", "webhook"] as const;
export type RoutineRunSource = (typeof ROUTINE_RUN_SOURCES)[number];
export const PAUSE_REASONS = ["manual", "budget", "system"] as const;
export type PauseReason = (typeof PAUSE_REASONS)[number];

View File

@@ -10,9 +10,17 @@ export {
AGENT_ICON_NAMES,
ISSUE_STATUSES,
ISSUE_PRIORITIES,
ISSUE_ORIGIN_KINDS,
GOAL_LEVELS,
GOAL_STATUSES,
PROJECT_STATUSES,
ROUTINE_STATUSES,
ROUTINE_CONCURRENCY_POLICIES,
ROUTINE_CATCH_UP_POLICIES,
ROUTINE_TRIGGER_KINDS,
ROUTINE_TRIGGER_SIGNING_MODES,
ROUTINE_RUN_STATUSES,
ROUTINE_RUN_SOURCES,
PAUSE_REASONS,
PROJECT_COLORS,
APPROVAL_TYPES,
@@ -69,9 +77,17 @@ export {
type AgentIconName,
type IssueStatus,
type IssuePriority,
type IssueOriginKind,
type GoalLevel,
type GoalStatus,
type ProjectStatus,
type RoutineStatus,
type RoutineConcurrencyPolicy,
type RoutineCatchUpPolicy,
type RoutineTriggerKind,
type RoutineTriggerSigningMode,
type RoutineRunStatus,
type RoutineRunSource,
type PauseReason,
type ApprovalType,
type ApprovalStatus,
@@ -258,6 +274,14 @@ export type {
AgentEnvConfig,
CompanySecret,
SecretProviderDescriptor,
Routine,
RoutineTrigger,
RoutineRun,
RoutineTriggerSecretMaterial,
RoutineDetail,
RoutineRunSummary,
RoutineExecutionIssueOrigin,
RoutineListItem,
JsonSchema,
PluginJobDeclaration,
PluginWebhookDeclaration,
@@ -389,9 +413,21 @@ export {
createSecretSchema,
rotateSecretSchema,
updateSecretSchema,
createRoutineSchema,
updateRoutineSchema,
createRoutineTriggerSchema,
updateRoutineTriggerSchema,
runRoutineSchema,
rotateRoutineTriggerSecretSchema,
type CreateSecret,
type RotateSecret,
type UpdateSecret,
type CreateRoutine,
type UpdateRoutine,
type CreateRoutineTrigger,
type UpdateRoutineTrigger,
type RunRoutine,
type RotateRoutineTriggerSecret,
createCostEventSchema,
createFinanceEventSchema,
updateBudgetSchema,

View File

@@ -104,6 +104,16 @@ export type {
CompanySecret,
SecretProviderDescriptor,
} from "./secrets.js";
export type {
Routine,
RoutineTrigger,
RoutineRun,
RoutineTriggerSecretMaterial,
RoutineDetail,
RoutineRunSummary,
RoutineExecutionIssueOrigin,
RoutineListItem,
} from "./routine.js";
export type { CostEvent, CostSummary, CostByAgent, CostByProviderModel, CostByBiller, CostByAgentModel, CostWindowSpendRow, CostByProject } from "./cost.js";
export type { FinanceEvent, FinanceSummary, FinanceByBiller, FinanceByKind } from "./finance.js";
export type {

View File

@@ -1,4 +1,4 @@
import type { IssuePriority, IssueStatus } from "../constants.js";
import type { IssueOriginKind, IssuePriority, IssueStatus } from "../constants.js";
import type { Goal } from "./goal.js";
import type { Project, ProjectWorkspace } from "./project.js";
import type { ExecutionWorkspace, IssueExecutionWorkspaceSettings } from "./workspace-runtime.js";
@@ -116,6 +116,9 @@ export interface Issue {
createdByUserId: string | null;
issueNumber: number | null;
identifier: string | null;
originKind?: IssueOriginKind;
originId?: string | null;
originRunId?: string | null;
requestDepth: number;
billingCode: string | null;
assigneeAdapterOverrides: IssueAssigneeAdapterOverrides | null;

View File

@@ -0,0 +1,123 @@
import type { IssueOriginKind } from "../constants.js";
export interface RoutineProjectSummary {
id: string;
name: string;
description: string | null;
status: string;
goalId?: string | null;
}
export interface RoutineAgentSummary {
id: string;
name: string;
role: string;
title: string | null;
urlKey?: string | null;
}
export interface RoutineIssueSummary {
id: string;
identifier: string | null;
title: string;
status: string;
priority: string;
updatedAt: Date;
}
export interface Routine {
id: string;
companyId: string;
projectId: string;
goalId: string | null;
parentIssueId: string | null;
title: string;
description: string | null;
assigneeAgentId: string;
priority: string;
status: string;
concurrencyPolicy: string;
catchUpPolicy: string;
createdByAgentId: string | null;
createdByUserId: string | null;
updatedByAgentId: string | null;
updatedByUserId: string | null;
lastTriggeredAt: Date | null;
lastEnqueuedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
export interface RoutineTrigger {
id: string;
companyId: string;
routineId: string;
kind: string;
label: string | null;
enabled: boolean;
cronExpression: string | null;
timezone: string | null;
nextRunAt: Date | null;
lastFiredAt: Date | null;
publicId: string | null;
secretId: string | null;
signingMode: string | null;
replayWindowSec: number | null;
lastRotatedAt: Date | null;
lastResult: string | null;
createdByAgentId: string | null;
createdByUserId: string | null;
updatedByAgentId: string | null;
updatedByUserId: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface RoutineRun {
id: string;
companyId: string;
routineId: string;
triggerId: string | null;
source: string;
status: string;
triggeredAt: Date;
idempotencyKey: string | null;
triggerPayload: Record<string, unknown> | null;
linkedIssueId: string | null;
coalescedIntoRunId: string | null;
failureReason: string | null;
completedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
export interface RoutineTriggerSecretMaterial {
webhookUrl: string;
webhookSecret: string;
}
export interface RoutineDetail extends Routine {
project: RoutineProjectSummary | null;
assignee: RoutineAgentSummary | null;
parentIssue: RoutineIssueSummary | null;
triggers: RoutineTrigger[];
recentRuns: RoutineRunSummary[];
activeIssue: RoutineIssueSummary | null;
}
export interface RoutineRunSummary extends RoutineRun {
linkedIssue: RoutineIssueSummary | null;
trigger: Pick<RoutineTrigger, "id" | "kind" | "label"> | null;
}
export interface RoutineExecutionIssueOrigin {
kind: Extract<IssueOriginKind, "routine_execution">;
routineId: string;
runId: string | null;
}
export interface RoutineListItem extends Routine {
triggers: Pick<RoutineTrigger, "id" | "kind" | "label" | "enabled" | "nextRunAt" | "lastFiredAt" | "lastResult">[];
lastRun: RoutineRunSummary | null;
activeIssue: RoutineIssueSummary | null;
}

View File

@@ -184,6 +184,21 @@ export {
type UpdateSecret,
} from "./secret.js";
export {
createRoutineSchema,
updateRoutineSchema,
createRoutineTriggerSchema,
updateRoutineTriggerSchema,
runRoutineSchema,
rotateRoutineTriggerSecretSchema,
type CreateRoutine,
type UpdateRoutine,
type CreateRoutineTrigger,
type UpdateRoutineTrigger,
type RunRoutine,
type RotateRoutineTriggerSecret,
} from "./routine.js";
export {
createCostEventSchema,
updateBudgetSchema,

View File

@@ -0,0 +1,72 @@
import { z } from "zod";
import {
ISSUE_PRIORITIES,
ROUTINE_CATCH_UP_POLICIES,
ROUTINE_CONCURRENCY_POLICIES,
ROUTINE_STATUSES,
ROUTINE_TRIGGER_SIGNING_MODES,
} from "../constants.js";
export const createRoutineSchema = z.object({
projectId: z.string().uuid(),
goalId: z.string().uuid().optional().nullable(),
parentIssueId: z.string().uuid().optional().nullable(),
title: z.string().trim().min(1).max(200),
description: z.string().optional().nullable(),
assigneeAgentId: z.string().uuid(),
priority: z.enum(ISSUE_PRIORITIES).optional().default("medium"),
status: z.enum(ROUTINE_STATUSES).optional().default("active"),
concurrencyPolicy: z.enum(ROUTINE_CONCURRENCY_POLICIES).optional().default("coalesce_if_active"),
catchUpPolicy: z.enum(ROUTINE_CATCH_UP_POLICIES).optional().default("skip_missed"),
});
export type CreateRoutine = z.infer<typeof createRoutineSchema>;
export const updateRoutineSchema = createRoutineSchema.partial();
export type UpdateRoutine = z.infer<typeof updateRoutineSchema>;
const baseTriggerSchema = z.object({
label: z.string().trim().max(120).optional().nullable(),
enabled: z.boolean().optional().default(true),
});
export const createRoutineTriggerSchema = z.discriminatedUnion("kind", [
baseTriggerSchema.extend({
kind: z.literal("schedule"),
cronExpression: z.string().trim().min(1),
timezone: z.string().trim().min(1).default("UTC"),
}),
baseTriggerSchema.extend({
kind: z.literal("webhook"),
signingMode: z.enum(ROUTINE_TRIGGER_SIGNING_MODES).optional().default("bearer"),
replayWindowSec: z.number().int().min(30).max(86_400).optional().default(300),
}),
baseTriggerSchema.extend({
kind: z.literal("api"),
}),
]);
export type CreateRoutineTrigger = z.infer<typeof createRoutineTriggerSchema>;
export const updateRoutineTriggerSchema = z.object({
label: z.string().trim().max(120).optional().nullable(),
enabled: z.boolean().optional(),
cronExpression: z.string().trim().min(1).optional().nullable(),
timezone: z.string().trim().min(1).optional().nullable(),
signingMode: z.enum(ROUTINE_TRIGGER_SIGNING_MODES).optional().nullable(),
replayWindowSec: z.number().int().min(30).max(86_400).optional().nullable(),
});
export type UpdateRoutineTrigger = z.infer<typeof updateRoutineTriggerSchema>;
export const runRoutineSchema = z.object({
triggerId: z.string().uuid().optional().nullable(),
payload: z.record(z.unknown()).optional().nullable(),
idempotencyKey: z.string().trim().max(255).optional().nullable(),
source: z.enum(["manual", "api"]).optional().default("manual"),
});
export type RunRoutine = z.infer<typeof runRoutineSchema>;
export const rotateRoutineTriggerSecretSchema = z.object({});
export type RotateRoutineTriggerSecret = z.infer<typeof rotateRoutineTriggerSecretSchema>;