merge: wave 2 executor (01-02) into main

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-25 23:46:16 -04:00
1 parent a5b8fcf9d3
commit aa1a78d7d9
1081 files changed
+445341

No files matched your search

@@ -0,0 +1,22 @@
const AGENT_URL_KEY_DELIM_RE = /[^a-z0-9]+/g;
const AGENT_URL_KEY_TRIM_RE = /^-+|-+$/g;
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
export function isUuidLike(value: string | null | undefined): boolean {
if (typeof value !== "string") return false;
return UUID_RE.test(value.trim());
}
export function normalizeAgentUrlKey(value: string | null | undefined): string | null {
if (typeof value !== "string") return null;
const normalized = value
.trim()
.toLowerCase()
.replace(AGENT_URL_KEY_DELIM_RE, "-")
.replace(AGENT_URL_KEY_TRIM_RE, "");
return normalized.length > 0 ? normalized : null;
}
export function deriveAgentUrlKey(name: string | null | undefined, fallback?: string | null): string {
return normalizeAgentUrlKey(name) ?? normalizeAgentUrlKey(fallback) ?? "agent";
}
@@ -0,0 +1,20 @@
export const API_PREFIX = "/api";
export const API = {
health: `${API_PREFIX}/health`,
companies: `${API_PREFIX}/companies`,
agents: `${API_PREFIX}/agents`,
projects: `${API_PREFIX}/projects`,
issues: `${API_PREFIX}/issues`,
goals: `${API_PREFIX}/goals`,
approvals: `${API_PREFIX}/approvals`,
secrets: `${API_PREFIX}/secrets`,
costs: `${API_PREFIX}/costs`,
activity: `${API_PREFIX}/activity`,
dashboard: `${API_PREFIX}/dashboard`,
sidebarBadges: `${API_PREFIX}/sidebar-badges`,
invites: `${API_PREFIX}/invites`,
joinRequests: `${API_PREFIX}/join-requests`,
members: `${API_PREFIX}/members`,
admin: `${API_PREFIX}/admin`,
} as const;
@@ -0,0 +1,178 @@
import { z } from "zod";
import {
AUTH_BASE_URL_MODES,
DEPLOYMENT_EXPOSURES,
DEPLOYMENT_MODES,
SECRET_PROVIDERS,
STORAGE_PROVIDERS,
} from "./constants.js";
export const configMetaSchema = z.object({
version: z.literal(1),
updatedAt: z.string(),
source: z.enum(["onboard", "configure", "doctor"]),
});
export const llmConfigSchema = z.object({
provider: z.enum(["claude", "openai"]),
apiKey: z.string().optional(),
});
export const databaseBackupConfigSchema = z.object({
enabled: z.boolean().default(true),
intervalMinutes: z.number().int().min(1).max(7 * 24 * 60).default(60),
retentionDays: z.number().int().min(1).max(3650).default(30),
dir: z.string().default("~/.paperclip/instances/default/data/backups"),
});
export const databaseConfigSchema = z.object({
mode: z.enum(["embedded-postgres", "postgres"]).default("embedded-postgres"),
connectionString: z.string().optional(),
embeddedPostgresDataDir: z.string().default("~/.paperclip/instances/default/db"),
embeddedPostgresPort: z.number().int().min(1).max(65535).default(54329),
backup: databaseBackupConfigSchema.default({
enabled: true,
intervalMinutes: 60,
retentionDays: 30,
dir: "~/.paperclip/instances/default/data/backups",
}),
});
export const loggingConfigSchema = z.object({
mode: z.enum(["file", "cloud"]),
logDir: z.string().default("~/.paperclip/instances/default/logs"),
});
export const serverConfigSchema = z.object({
deploymentMode: z.enum(DEPLOYMENT_MODES).default("local_trusted"),
exposure: z.enum(DEPLOYMENT_EXPOSURES).default("private"),
host: z.string().default("127.0.0.1"),
port: z.number().int().min(1).max(65535).default(3100),
allowedHostnames: z.array(z.string().min(1)).default([]),
serveUi: z.boolean().default(true),
});
export const authConfigSchema = z.object({
baseUrlMode: z.enum(AUTH_BASE_URL_MODES).default("auto"),
publicBaseUrl: z.string().url().optional(),
disableSignUp: z.boolean().default(false),
});
export const storageLocalDiskConfigSchema = z.object({
baseDir: z.string().default("~/.paperclip/instances/default/data/storage"),
});
export const storageS3ConfigSchema = z.object({
bucket: z.string().min(1).default("paperclip"),
region: z.string().min(1).default("us-east-1"),
endpoint: z.string().optional(),
prefix: z.string().default(""),
forcePathStyle: z.boolean().default(false),
});
export const storageConfigSchema = z.object({
provider: z.enum(STORAGE_PROVIDERS).default("local_disk"),
localDisk: storageLocalDiskConfigSchema.default({
baseDir: "~/.paperclip/instances/default/data/storage",
}),
s3: storageS3ConfigSchema.default({
bucket: "paperclip",
region: "us-east-1",
prefix: "",
forcePathStyle: false,
}),
});
export const secretsLocalEncryptedConfigSchema = z.object({
keyFilePath: z.string().default("~/.paperclip/instances/default/secrets/master.key"),
});
export const secretsConfigSchema = z.object({
provider: z.enum(SECRET_PROVIDERS).default("local_encrypted"),
strictMode: z.boolean().default(false),
localEncrypted: secretsLocalEncryptedConfigSchema.default({
keyFilePath: "~/.paperclip/instances/default/secrets/master.key",
}),
});
export const paperclipConfigSchema = z
.object({
$meta: configMetaSchema,
llm: llmConfigSchema.optional(),
database: databaseConfigSchema,
logging: loggingConfigSchema,
server: serverConfigSchema,
auth: authConfigSchema.default({
baseUrlMode: "auto",
disableSignUp: false,
}),
storage: storageConfigSchema.default({
provider: "local_disk",
localDisk: {
baseDir: "~/.paperclip/instances/default/data/storage",
},
s3: {
bucket: "paperclip",
region: "us-east-1",
prefix: "",
forcePathStyle: false,
},
}),
secrets: secretsConfigSchema.default({
provider: "local_encrypted",
strictMode: false,
localEncrypted: {
keyFilePath: "~/.paperclip/instances/default/secrets/master.key",
},
}),
})
.superRefine((value, ctx) => {
if (value.server.deploymentMode === "local_trusted") {
if (value.server.exposure !== "private") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "server.exposure must be private when deploymentMode is local_trusted",
path: ["server", "exposure"],
});
}
return;
}
if (value.auth.baseUrlMode === "explicit" && !value.auth.publicBaseUrl) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "auth.publicBaseUrl is required when auth.baseUrlMode is explicit",
path: ["auth", "publicBaseUrl"],
});
}
if (value.server.exposure === "public" && value.auth.baseUrlMode !== "explicit") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "auth.baseUrlMode must be explicit when deploymentMode=authenticated and exposure=public",
path: ["auth", "baseUrlMode"],
});
}
if (value.server.exposure === "public" && !value.auth.publicBaseUrl) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "auth.publicBaseUrl is required when deploymentMode=authenticated and exposure=public",
path: ["auth", "publicBaseUrl"],
});
}
});
export type PaperclipConfig = z.infer<typeof paperclipConfigSchema>;
export type LlmConfig = z.infer<typeof llmConfigSchema>;
export type DatabaseConfig = z.infer<typeof databaseConfigSchema>;
export type LoggingConfig = z.infer<typeof loggingConfigSchema>;
export type ServerConfig = z.infer<typeof serverConfigSchema>;
export type StorageConfig = z.infer<typeof storageConfigSchema>;
export type StorageLocalDiskConfig = z.infer<typeof storageLocalDiskConfigSchema>;
export type StorageS3Config = z.infer<typeof storageS3ConfigSchema>;
export type SecretsConfig = z.infer<typeof secretsConfigSchema>;
export type SecretsLocalEncryptedConfig = z.infer<typeof secretsLocalEncryptedConfigSchema>;
export type AuthConfig = z.infer<typeof authConfigSchema>;
export type ConfigMeta = z.infer<typeof configMetaSchema>;
export type DatabaseBackupConfig = z.infer<typeof databaseBackupConfigSchema>;
@@ -0,0 +1,689 @@
export const COMPANY_STATUSES = ["active", "paused", "archived"] as const;
export type CompanyStatus = (typeof COMPANY_STATUSES)[number];
export const DEPLOYMENT_MODES = ["local_trusted", "authenticated"] as const;
export type DeploymentMode = (typeof DEPLOYMENT_MODES)[number];
export const DEPLOYMENT_EXPOSURES = ["private", "public"] as const;
export type DeploymentExposure = (typeof DEPLOYMENT_EXPOSURES)[number];
export const AUTH_BASE_URL_MODES = ["auto", "explicit"] as const;
export type AuthBaseUrlMode = (typeof AUTH_BASE_URL_MODES)[number];
export const AGENT_STATUSES = [
"active",
"paused",
"idle",
"running",
"error",
"pending_approval",
"terminated",
] as const;
export type AgentStatus = (typeof AGENT_STATUSES)[number];
export const AGENT_ADAPTER_TYPES = [
"process",
"http",
"claude_local",
"codex_local",
"opencode_local",
"pi_local",
"cursor",
"openclaw_gateway",
"hermes_local",
] as const;
export type AgentAdapterType = (typeof AGENT_ADAPTER_TYPES)[number];
export const AGENT_ROLES = [
"ceo",
"cto",
"cmo",
"cfo",
"engineer",
"designer",
"pm",
"qa",
"devops",
"researcher",
"general",
] as const;
export type AgentRole = (typeof AGENT_ROLES)[number];
export const AGENT_ROLE_LABELS: Record<AgentRole, string> = {
ceo: "CEO",
cto: "CTO",
cmo: "CMO",
cfo: "CFO",
engineer: "Engineer",
designer: "Designer",
pm: "PM",
qa: "QA",
devops: "DevOps",
researcher: "Researcher",
general: "General",
};
export const AGENT_ICON_NAMES = [
"bot",
"cpu",
"brain",
"zap",
"rocket",
"code",
"terminal",
"shield",
"eye",
"search",
"wrench",
"hammer",
"lightbulb",
"sparkles",
"star",
"heart",
"flame",
"bug",
"cog",
"database",
"globe",
"lock",
"mail",
"message-square",
"file-code",
"git-branch",
"package",
"puzzle",
"target",
"wand",
"atom",
"circuit-board",
"radar",
"swords",
"telescope",
"microscope",
"crown",
"gem",
"hexagon",
"pentagon",
"fingerprint",
] as const;
export type AgentIconName = (typeof AGENT_ICON_NAMES)[number];
export const ISSUE_STATUSES = [
"backlog",
"todo",
"in_progress",
"in_review",
"done",
"blocked",
"cancelled",
] as const;
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];
export const GOAL_STATUSES = ["planned", "active", "achieved", "cancelled"] as const;
export type GoalStatus = (typeof GOAL_STATUSES)[number];
export const PROJECT_STATUSES = [
"backlog",
"planned",
"in_progress",
"completed",
"cancelled",
] 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];
export const PROJECT_COLORS = [
"#6366f1", // indigo
"#8b5cf6", // violet
"#ec4899", // pink
"#ef4444", // red
"#f97316", // orange
"#eab308", // yellow
"#22c55e", // green
"#14b8a6", // teal
"#06b6d4", // cyan
"#3b82f6", // blue
] as const;
export const APPROVAL_TYPES = ["hire_agent", "approve_ceo_strategy", "budget_override_required"] as const;
export type ApprovalType = (typeof APPROVAL_TYPES)[number];
export const APPROVAL_STATUSES = [
"pending",
"revision_requested",
"approved",
"rejected",
"cancelled",
] as const;
export type ApprovalStatus = (typeof APPROVAL_STATUSES)[number];
export const SECRET_PROVIDERS = [
"local_encrypted",
"aws_secrets_manager",
"gcp_secret_manager",
"vault",
] as const;
export type SecretProvider = (typeof SECRET_PROVIDERS)[number];
export const STORAGE_PROVIDERS = ["local_disk", "s3"] as const;
export type StorageProvider = (typeof STORAGE_PROVIDERS)[number];
export const BILLING_TYPES = [
"metered_api",
"subscription_included",
"subscription_overage",
"credits",
"fixed",
"unknown",
] as const;
export type BillingType = (typeof BILLING_TYPES)[number];
export const FINANCE_EVENT_KINDS = [
"inference_charge",
"platform_fee",
"credit_purchase",
"credit_refund",
"credit_expiry",
"byok_fee",
"gateway_overhead",
"log_storage_charge",
"logpush_charge",
"provisioned_capacity_charge",
"training_charge",
"custom_model_import_charge",
"custom_model_storage_charge",
"manual_adjustment",
] as const;
export type FinanceEventKind = (typeof FINANCE_EVENT_KINDS)[number];
export const FINANCE_DIRECTIONS = ["debit", "credit"] as const;
export type FinanceDirection = (typeof FINANCE_DIRECTIONS)[number];
export const FINANCE_UNITS = [
"input_token",
"output_token",
"cached_input_token",
"request",
"credit_usd",
"credit_unit",
"model_unit_minute",
"model_unit_hour",
"gb_month",
"train_token",
"unknown",
] as const;
export type FinanceUnit = (typeof FINANCE_UNITS)[number];
export const BUDGET_SCOPE_TYPES = ["company", "agent", "project"] as const;
export type BudgetScopeType = (typeof BUDGET_SCOPE_TYPES)[number];
export const BUDGET_METRICS = ["billed_cents"] as const;
export type BudgetMetric = (typeof BUDGET_METRICS)[number];
export const BUDGET_WINDOW_KINDS = ["calendar_month_utc", "lifetime"] as const;
export type BudgetWindowKind = (typeof BUDGET_WINDOW_KINDS)[number];
export const BUDGET_THRESHOLD_TYPES = ["soft", "hard"] as const;
export type BudgetThresholdType = (typeof BUDGET_THRESHOLD_TYPES)[number];
export const BUDGET_INCIDENT_STATUSES = ["open", "resolved", "dismissed"] as const;
export type BudgetIncidentStatus = (typeof BUDGET_INCIDENT_STATUSES)[number];
export const BUDGET_INCIDENT_RESOLUTION_ACTIONS = [
"keep_paused",
"raise_budget_and_resume",
] as const;
export type BudgetIncidentResolutionAction = (typeof BUDGET_INCIDENT_RESOLUTION_ACTIONS)[number];
export const HEARTBEAT_INVOCATION_SOURCES = [
"timer",
"assignment",
"on_demand",
"automation",
] as const;
export type HeartbeatInvocationSource = (typeof HEARTBEAT_INVOCATION_SOURCES)[number];
export const WAKEUP_TRIGGER_DETAILS = ["manual", "ping", "callback", "system"] as const;
export type WakeupTriggerDetail = (typeof WAKEUP_TRIGGER_DETAILS)[number];
export const WAKEUP_REQUEST_STATUSES = [
"queued",
"deferred_issue_execution",
"claimed",
"coalesced",
"skipped",
"completed",
"failed",
"cancelled",
] as const;
export type WakeupRequestStatus = (typeof WAKEUP_REQUEST_STATUSES)[number];
export const HEARTBEAT_RUN_STATUSES = [
"queued",
"running",
"succeeded",
"failed",
"cancelled",
"timed_out",
] as const;
export type HeartbeatRunStatus = (typeof HEARTBEAT_RUN_STATUSES)[number];
export const LIVE_EVENT_TYPES = [
"heartbeat.run.queued",
"heartbeat.run.status",
"heartbeat.run.event",
"heartbeat.run.log",
"agent.status",
"activity.logged",
"plugin.ui.updated",
"plugin.worker.crashed",
"plugin.worker.restarted",
] as const;
export type LiveEventType = (typeof LIVE_EVENT_TYPES)[number];
export const PRINCIPAL_TYPES = ["user", "agent"] as const;
export type PrincipalType = (typeof PRINCIPAL_TYPES)[number];
export const MEMBERSHIP_STATUSES = ["pending", "active", "suspended"] as const;
export type MembershipStatus = (typeof MEMBERSHIP_STATUSES)[number];
export const INSTANCE_USER_ROLES = ["instance_admin"] as const;
export type InstanceUserRole = (typeof INSTANCE_USER_ROLES)[number];
export const INVITE_TYPES = ["company_join", "bootstrap_ceo"] as const;
export type InviteType = (typeof INVITE_TYPES)[number];
export const INVITE_JOIN_TYPES = ["human", "agent", "both"] as const;
export type InviteJoinType = (typeof INVITE_JOIN_TYPES)[number];
export const JOIN_REQUEST_TYPES = ["human", "agent"] as const;
export type JoinRequestType = (typeof JOIN_REQUEST_TYPES)[number];
export const JOIN_REQUEST_STATUSES = ["pending_approval", "approved", "rejected"] as const;
export type JoinRequestStatus = (typeof JOIN_REQUEST_STATUSES)[number];
export const PERMISSION_KEYS = [
"agents:create",
"users:invite",
"users:manage_permissions",
"tasks:assign",
"tasks:assign_scope",
"joins:approve",
] as const;
export type PermissionKey = (typeof PERMISSION_KEYS)[number];
// ---------------------------------------------------------------------------
// Plugin System — see doc/plugins/PLUGIN_SPEC.md for the full specification
// ---------------------------------------------------------------------------
/**
* The current version of the Plugin API contract.
*
* Increment this value whenever a breaking change is made to the plugin API
* so that the host can reject incompatible plugin manifests.
*
* @see PLUGIN_SPEC.md §4 — Versioning
*/
export const PLUGIN_API_VERSION = 1 as const;
/**
* Lifecycle statuses for an installed plugin.
*
* State machine: installed → ready | error, ready → disabled | error | upgrade_pending | uninstalled,
* disabled → ready | uninstalled, error → ready | uninstalled,
* upgrade_pending → ready | error | uninstalled, uninstalled → installed (reinstall).
*
* @see {@link PluginStatus} — inferred union type
* @see PLUGIN_SPEC.md §21.3 `plugins.status`
*/
export const PLUGIN_STATUSES = [
"installed",
"ready",
"disabled",
"error",
"upgrade_pending",
"uninstalled",
] as const;
export type PluginStatus = (typeof PLUGIN_STATUSES)[number];
/**
* Plugin classification categories. A plugin declares one or more categories
* in its manifest to describe its primary purpose.
*
* @see PLUGIN_SPEC.md §6.2
*/
export const PLUGIN_CATEGORIES = [
"connector",
"workspace",
"automation",
"ui",
] as const;
export type PluginCategory = (typeof PLUGIN_CATEGORIES)[number];
/**
* Named permissions the host grants to a plugin. Plugins declare required
* capabilities in their manifest; the host enforces them at runtime via the
* plugin capability validator.
*
* Grouped into: Data Read, Data Write, Plugin State, Runtime/Integration,
* Agent Tools, and UI.
*
* @see PLUGIN_SPEC.md §15 — Capability Model
*/
export const PLUGIN_CAPABILITIES = [
// Data Read
"companies.read",
"projects.read",
"project.workspaces.read",
"issues.read",
"issue.comments.read",
"issue.documents.read",
"agents.read",
"goals.read",
"goals.create",
"goals.update",
"activity.read",
"costs.read",
// Data Write
"issues.create",
"issues.update",
"issue.comments.create",
"issue.documents.write",
"agents.pause",
"agents.resume",
"agents.invoke",
"agent.sessions.create",
"agent.sessions.list",
"agent.sessions.send",
"agent.sessions.close",
"activity.log.write",
"metrics.write",
// Plugin State
"plugin.state.read",
"plugin.state.write",
// Runtime / Integration
"events.subscribe",
"events.emit",
"jobs.schedule",
"webhooks.receive",
"http.outbound",
"secrets.read-ref",
// Agent Tools
"agent.tools.register",
// UI
"instance.settings.register",
"ui.sidebar.register",
"ui.page.register",
"ui.detailTab.register",
"ui.dashboardWidget.register",
"ui.commentAnnotation.register",
"ui.action.register",
] as const;
export type PluginCapability = (typeof PLUGIN_CAPABILITIES)[number];
/**
* UI extension slot types. Each slot type corresponds to a mount point in the
* Paperclip UI where plugin components can be rendered.
*
* @see PLUGIN_SPEC.md §19 — UI Extension Model
*/
export const PLUGIN_UI_SLOT_TYPES = [
"page",
"detailTab",
"taskDetailView",
"dashboardWidget",
"sidebar",
"sidebarPanel",
"projectSidebarItem",
"globalToolbarButton",
"toolbarButton",
"contextMenuItem",
"commentAnnotation",
"commentContextMenuItem",
"settingsPage",
] as const;
export type PluginUiSlotType = (typeof PLUGIN_UI_SLOT_TYPES)[number];
/**
* Reserved company-scoped route segments that plugin page routes may not claim.
*
* These map to first-class host pages under `/:companyPrefix/...`.
*/
export const PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTS = [
"dashboard",
"onboarding",
"companies",
"company",
"settings",
"plugins",
"org",
"agents",
"projects",
"issues",
"goals",
"approvals",
"costs",
"activity",
"inbox",
"design-guide",
"tests",
] as const;
export type PluginReservedCompanyRouteSegment =
(typeof PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTS)[number];
/**
* Launcher placement zones describe where a plugin-owned launcher can appear
* in the host UI. These are intentionally aligned with current slot surfaces
* so manifest authors can describe launch intent without coupling to a single
* component implementation detail.
*/
export const PLUGIN_LAUNCHER_PLACEMENT_ZONES = [
"page",
"detailTab",
"taskDetailView",
"dashboardWidget",
"sidebar",
"sidebarPanel",
"projectSidebarItem",
"globalToolbarButton",
"toolbarButton",
"contextMenuItem",
"commentAnnotation",
"commentContextMenuItem",
"settingsPage",
] as const;
export type PluginLauncherPlacementZone = (typeof PLUGIN_LAUNCHER_PLACEMENT_ZONES)[number];
/**
* Launcher action kinds describe what the launcher does when activated.
*/
export const PLUGIN_LAUNCHER_ACTIONS = [
"navigate",
"openModal",
"openDrawer",
"openPopover",
"performAction",
"deepLink",
] as const;
export type PluginLauncherAction = (typeof PLUGIN_LAUNCHER_ACTIONS)[number];
/**
* Optional size hints the host can use when rendering plugin-owned launcher
* destinations such as overlays, drawers, or full page handoffs.
*/
export const PLUGIN_LAUNCHER_BOUNDS = [
"inline",
"compact",
"default",
"wide",
"full",
] as const;
export type PluginLauncherBounds = (typeof PLUGIN_LAUNCHER_BOUNDS)[number];
/**
* Render environments describe the container a launcher expects after it is
* activated. The current host may map these to concrete UI primitives.
*/
export const PLUGIN_LAUNCHER_RENDER_ENVIRONMENTS = [
"hostInline",
"hostOverlay",
"hostRoute",
"external",
"iframe",
] as const;
export type PluginLauncherRenderEnvironment =
(typeof PLUGIN_LAUNCHER_RENDER_ENVIRONMENTS)[number];
/**
* Entity types that a `detailTab` UI slot can attach to.
*
* @see PLUGIN_SPEC.md §19.3 — Detail Tabs
*/
export const PLUGIN_UI_SLOT_ENTITY_TYPES = [
"project",
"issue",
"agent",
"goal",
"run",
"comment",
] as const;
export type PluginUiSlotEntityType = (typeof PLUGIN_UI_SLOT_ENTITY_TYPES)[number];
/**
* Scope kinds for plugin state storage. Determines the granularity at which
* a plugin stores key-value state data.
*
* @see PLUGIN_SPEC.md §21.3 `plugin_state.scope_kind`
*/
export const PLUGIN_STATE_SCOPE_KINDS = [
"instance",
"company",
"project",
"project_workspace",
"agent",
"issue",
"goal",
"run",
] as const;
export type PluginStateScopeKind = (typeof PLUGIN_STATE_SCOPE_KINDS)[number];
/** Statuses for a plugin's scheduled job definition. */
export const PLUGIN_JOB_STATUSES = [
"active",
"paused",
"failed",
] as const;
export type PluginJobStatus = (typeof PLUGIN_JOB_STATUSES)[number];
/** Statuses for individual job run executions. */
export const PLUGIN_JOB_RUN_STATUSES = [
"pending",
"queued",
"running",
"succeeded",
"failed",
"cancelled",
] as const;
export type PluginJobRunStatus = (typeof PLUGIN_JOB_RUN_STATUSES)[number];
/** What triggered a particular job run. */
export const PLUGIN_JOB_RUN_TRIGGERS = [
"schedule",
"manual",
"retry",
] as const;
export type PluginJobRunTrigger = (typeof PLUGIN_JOB_RUN_TRIGGERS)[number];
/** Statuses for inbound webhook deliveries. */
export const PLUGIN_WEBHOOK_DELIVERY_STATUSES = [
"pending",
"success",
"failed",
] as const;
export type PluginWebhookDeliveryStatus = (typeof PLUGIN_WEBHOOK_DELIVERY_STATUSES)[number];
/**
* Core domain event types that plugins can subscribe to via the
* `events.subscribe` capability.
*
* @see PLUGIN_SPEC.md §16 — Event System
*/
export const PLUGIN_EVENT_TYPES = [
"company.created",
"company.updated",
"project.created",
"project.updated",
"project.workspace_created",
"project.workspace_updated",
"project.workspace_deleted",
"issue.created",
"issue.updated",
"issue.comment.created",
"agent.created",
"agent.updated",
"agent.status_changed",
"agent.run.started",
"agent.run.finished",
"agent.run.failed",
"agent.run.cancelled",
"goal.created",
"goal.updated",
"approval.created",
"approval.decided",
"cost_event.created",
"activity.logged",
] as const;
export type PluginEventType = (typeof PLUGIN_EVENT_TYPES)[number];
/**
* Error codes returned by the plugin bridge when a UI → worker call fails.
*
* @see PLUGIN_SPEC.md §19.7 — Error Propagation Through The Bridge
*/
export const PLUGIN_BRIDGE_ERROR_CODES = [
"WORKER_UNAVAILABLE",
"CAPABILITY_DENIED",
"WORKER_ERROR",
"TIMEOUT",
"UNKNOWN",
] as const;
export type PluginBridgeErrorCode = (typeof PLUGIN_BRIDGE_ERROR_CODES)[number];
@@ -0,0 +1,582 @@
export {
COMPANY_STATUSES,
DEPLOYMENT_MODES,
DEPLOYMENT_EXPOSURES,
AUTH_BASE_URL_MODES,
AGENT_STATUSES,
AGENT_ADAPTER_TYPES,
AGENT_ROLES,
AGENT_ROLE_LABELS,
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,
APPROVAL_STATUSES,
SECRET_PROVIDERS,
STORAGE_PROVIDERS,
BILLING_TYPES,
FINANCE_EVENT_KINDS,
FINANCE_DIRECTIONS,
FINANCE_UNITS,
BUDGET_SCOPE_TYPES,
BUDGET_METRICS,
BUDGET_WINDOW_KINDS,
BUDGET_THRESHOLD_TYPES,
BUDGET_INCIDENT_STATUSES,
BUDGET_INCIDENT_RESOLUTION_ACTIONS,
HEARTBEAT_INVOCATION_SOURCES,
HEARTBEAT_RUN_STATUSES,
WAKEUP_TRIGGER_DETAILS,
WAKEUP_REQUEST_STATUSES,
LIVE_EVENT_TYPES,
PRINCIPAL_TYPES,
MEMBERSHIP_STATUSES,
INSTANCE_USER_ROLES,
INVITE_TYPES,
INVITE_JOIN_TYPES,
JOIN_REQUEST_TYPES,
JOIN_REQUEST_STATUSES,
PERMISSION_KEYS,
PLUGIN_API_VERSION,
PLUGIN_STATUSES,
PLUGIN_CATEGORIES,
PLUGIN_CAPABILITIES,
PLUGIN_UI_SLOT_TYPES,
PLUGIN_UI_SLOT_ENTITY_TYPES,
PLUGIN_LAUNCHER_PLACEMENT_ZONES,
PLUGIN_LAUNCHER_ACTIONS,
PLUGIN_LAUNCHER_BOUNDS,
PLUGIN_LAUNCHER_RENDER_ENVIRONMENTS,
PLUGIN_STATE_SCOPE_KINDS,
PLUGIN_JOB_STATUSES,
PLUGIN_JOB_RUN_STATUSES,
PLUGIN_JOB_RUN_TRIGGERS,
PLUGIN_WEBHOOK_DELIVERY_STATUSES,
PLUGIN_EVENT_TYPES,
PLUGIN_BRIDGE_ERROR_CODES,
type CompanyStatus,
type DeploymentMode,
type DeploymentExposure,
type AuthBaseUrlMode,
type AgentStatus,
type AgentAdapterType,
type AgentRole,
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,
type SecretProvider,
type StorageProvider,
type BillingType,
type FinanceEventKind,
type FinanceDirection,
type FinanceUnit,
type BudgetScopeType,
type BudgetMetric,
type BudgetWindowKind,
type BudgetThresholdType,
type BudgetIncidentStatus,
type BudgetIncidentResolutionAction,
type HeartbeatInvocationSource,
type HeartbeatRunStatus,
type WakeupTriggerDetail,
type WakeupRequestStatus,
type LiveEventType,
type PrincipalType,
type MembershipStatus,
type InstanceUserRole,
type InviteType,
type InviteJoinType,
type JoinRequestType,
type JoinRequestStatus,
type PermissionKey,
type PluginStatus,
type PluginCategory,
type PluginCapability,
type PluginUiSlotType,
type PluginUiSlotEntityType,
type PluginLauncherPlacementZone,
type PluginLauncherAction,
type PluginLauncherBounds,
type PluginLauncherRenderEnvironment,
type PluginStateScopeKind,
type PluginJobStatus,
type PluginJobRunStatus,
type PluginJobRunTrigger,
type PluginWebhookDeliveryStatus,
type PluginEventType,
type PluginBridgeErrorCode,
} from "./constants.js";
export type {
Company,
CompanySkillSourceType,
CompanySkillTrustLevel,
CompanySkillCompatibility,
CompanySkillSourceBadge,
CompanySkillFileInventoryEntry,
CompanySkill,
CompanySkillListItem,
CompanySkillUsageAgent,
CompanySkillDetail,
CompanySkillUpdateStatus,
CompanySkillImportRequest,
CompanySkillImportResult,
CompanySkillProjectScanRequest,
CompanySkillProjectScanSkipped,
CompanySkillProjectScanConflict,
CompanySkillProjectScanResult,
CompanySkillCreateRequest,
CompanySkillFileDetail,
CompanySkillFileUpdateRequest,
AgentSkillSyncMode,
AgentSkillState,
AgentSkillOrigin,
AgentSkillEntry,
AgentSkillSnapshot,
AgentSkillSyncRequest,
InstanceExperimentalSettings,
InstanceGeneralSettings,
InstanceSettings,
Agent,
AgentAccessState,
AgentChainOfCommandEntry,
AgentDetail,
AgentPermissions,
AgentInstructionsBundleMode,
AgentInstructionsFileSummary,
AgentInstructionsFileDetail,
AgentInstructionsBundle,
AgentKeyCreated,
AgentConfigRevision,
AdapterEnvironmentCheckLevel,
AdapterEnvironmentTestStatus,
AdapterEnvironmentCheck,
AdapterEnvironmentTestResult,
AssetImage,
Project,
ProjectCodebase,
ProjectCodebaseOrigin,
ProjectGoalRef,
ProjectWorkspace,
ExecutionWorkspace,
WorkspaceRuntimeService,
WorkspaceOperation,
WorkspaceOperationPhase,
WorkspaceOperationStatus,
ExecutionWorkspaceStrategyType,
ExecutionWorkspaceMode,
ExecutionWorkspaceProviderType,
ExecutionWorkspaceStatus,
ExecutionWorkspaceStrategy,
ProjectExecutionWorkspacePolicy,
ProjectExecutionWorkspaceDefaultMode,
IssueExecutionWorkspaceSettings,
IssueWorkProduct,
IssueWorkProductType,
IssueWorkProductProvider,
IssueWorkProductStatus,
IssueWorkProductReviewState,
Issue,
IssueAssigneeAdapterOverrides,
IssueComment,
IssueDocument,
IssueDocumentSummary,
DocumentRevision,
DocumentFormat,
LegacyPlanDocument,
IssueAttachment,
IssueLabel,
Goal,
Approval,
ApprovalComment,
BudgetPolicy,
BudgetPolicySummary,
BudgetIncident,
BudgetOverview,
BudgetPolicyUpsertInput,
BudgetIncidentResolutionInput,
CostEvent,
CostSummary,
CostByAgent,
CostByProviderModel,
CostByBiller,
CostByAgentModel,
CostWindowSpendRow,
CostByProject,
FinanceEvent,
FinanceSummary,
FinanceByBiller,
FinanceByKind,
HeartbeatRun,
HeartbeatRunEvent,
AgentRuntimeState,
AgentTaskSession,
AgentWakeupRequest,
InstanceSchedulerHeartbeatAgent,
LiveEvent,
DashboardSummary,
ActivityEvent,
SidebarBadges,
CompanyMembership,
PrincipalPermissionGrant,
Invite,
JoinRequest,
InstanceUserRoleGrant,
CompanyPortabilityInclude,
CompanyPortabilityEnvInput,
CompanyPortabilityFileEntry,
CompanyPortabilityCompanyManifestEntry,
CompanyPortabilitySidebarOrder,
CompanyPortabilityAgentManifestEntry,
CompanyPortabilitySkillManifestEntry,
CompanyPortabilityProjectManifestEntry,
CompanyPortabilityProjectWorkspaceManifestEntry,
CompanyPortabilityIssueRoutineTriggerManifestEntry,
CompanyPortabilityIssueRoutineManifestEntry,
CompanyPortabilityIssueManifestEntry,
CompanyPortabilityManifest,
CompanyPortabilityExportResult,
CompanyPortabilityExportPreviewFile,
CompanyPortabilityExportPreviewResult,
CompanyPortabilitySource,
CompanyPortabilityImportTarget,
CompanyPortabilityAgentSelection,
CompanyPortabilityCollisionStrategy,
CompanyPortabilityPreviewRequest,
CompanyPortabilityPreviewAgentPlan,
CompanyPortabilityPreviewProjectPlan,
CompanyPortabilityPreviewIssuePlan,
CompanyPortabilityPreviewResult,
CompanyPortabilityAdapterOverride,
CompanyPortabilityImportRequest,
CompanyPortabilityImportResult,
CompanyPortabilityExportRequest,
EnvBinding,
AgentEnvConfig,
CompanySecret,
SecretProviderDescriptor,
Routine,
RoutineTrigger,
RoutineRun,
RoutineTriggerSecretMaterial,
RoutineDetail,
RoutineRunSummary,
RoutineExecutionIssueOrigin,
RoutineListItem,
JsonSchema,
PluginJobDeclaration,
PluginWebhookDeclaration,
PluginToolDeclaration,
PluginUiSlotDeclaration,
PluginLauncherActionDeclaration,
PluginLauncherRenderDeclaration,
PluginLauncherRenderContextSnapshot,
PluginLauncherDeclaration,
PluginMinimumHostVersion,
PluginUiDeclaration,
PaperclipPluginManifestV1,
PluginRecord,
PluginStateRecord,
PluginConfig,
PluginEntityRecord,
PluginEntityQuery,
PluginJobRecord,
PluginJobRunRecord,
PluginWebhookDeliveryRecord,
QuotaWindow,
ProviderQuotaResult,
} from "./types/index.js";
export {
instanceGeneralSettingsSchema,
patchInstanceGeneralSettingsSchema,
type PatchInstanceGeneralSettings,
instanceExperimentalSettingsSchema,
patchInstanceExperimentalSettingsSchema,
type PatchInstanceExperimentalSettings,
} from "./validators/index.js";
export {
createCompanySchema,
updateCompanySchema,
updateCompanyBrandingSchema,
type CreateCompany,
type UpdateCompany,
type UpdateCompanyBranding,
agentSkillStateSchema,
agentSkillSyncModeSchema,
agentSkillEntrySchema,
agentSkillSnapshotSchema,
agentSkillSyncSchema,
type AgentSkillSync,
createAgentSchema,
createAgentHireSchema,
updateAgentSchema,
agentInstructionsBundleModeSchema,
updateAgentInstructionsBundleSchema,
upsertAgentInstructionsFileSchema,
updateAgentInstructionsPathSchema,
createAgentKeySchema,
wakeAgentSchema,
resetAgentSessionSchema,
testAdapterEnvironmentSchema,
agentPermissionsSchema,
updateAgentPermissionsSchema,
type CreateAgent,
type CreateAgentHire,
type UpdateAgent,
type UpdateAgentInstructionsBundle,
type UpsertAgentInstructionsFile,
type UpdateAgentInstructionsPath,
type CreateAgentKey,
type WakeAgent,
type ResetAgentSession,
type TestAdapterEnvironment,
type UpdateAgentPermissions,
createProjectSchema,
updateProjectSchema,
createProjectWorkspaceSchema,
updateProjectWorkspaceSchema,
type CreateProject,
type UpdateProject,
type CreateProjectWorkspace,
type UpdateProjectWorkspace,
projectExecutionWorkspacePolicySchema,
createIssueSchema,
createIssueLabelSchema,
updateIssueSchema,
issueExecutionWorkspaceSettingsSchema,
checkoutIssueSchema,
addIssueCommentSchema,
linkIssueApprovalSchema,
createIssueAttachmentMetadataSchema,
createIssueWorkProductSchema,
updateIssueWorkProductSchema,
issueWorkProductTypeSchema,
issueWorkProductStatusSchema,
issueWorkProductReviewStateSchema,
updateExecutionWorkspaceSchema,
executionWorkspaceStatusSchema,
issueDocumentFormatSchema,
issueDocumentKeySchema,
upsertIssueDocumentSchema,
type CreateIssue,
type CreateIssueLabel,
type UpdateIssue,
type CheckoutIssue,
type AddIssueComment,
type LinkIssueApproval,
type CreateIssueAttachmentMetadata,
type CreateIssueWorkProduct,
type UpdateIssueWorkProduct,
type UpdateExecutionWorkspace,
type IssueDocumentFormat,
type UpsertIssueDocument,
createGoalSchema,
updateGoalSchema,
type CreateGoal,
type UpdateGoal,
createApprovalSchema,
upsertBudgetPolicySchema,
resolveBudgetIncidentSchema,
resolveApprovalSchema,
requestApprovalRevisionSchema,
resubmitApprovalSchema,
addApprovalCommentSchema,
type CreateApproval,
type UpsertBudgetPolicy,
type ResolveBudgetIncident,
type ResolveApproval,
type RequestApprovalRevision,
type ResubmitApproval,
type AddApprovalComment,
envBindingPlainSchema,
envBindingSecretRefSchema,
envBindingSchema,
envConfigSchema,
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,
createAssetImageMetadataSchema,
createCompanyInviteSchema,
createOpenClawInvitePromptSchema,
acceptInviteSchema,
listJoinRequestsQuerySchema,
claimJoinRequestApiKeySchema,
boardCliAuthAccessLevelSchema,
createCliAuthChallengeSchema,
resolveCliAuthChallengeSchema,
updateMemberPermissionsSchema,
updateUserCompanyAccessSchema,
type CreateCostEvent,
type CreateFinanceEvent,
type UpdateBudget,
type CreateAssetImageMetadata,
type CreateCompanyInvite,
type CreateOpenClawInvitePrompt,
type AcceptInvite,
type ListJoinRequestsQuery,
type ClaimJoinRequestApiKey,
type BoardCliAuthAccessLevel,
type CreateCliAuthChallenge,
type ResolveCliAuthChallenge,
type UpdateMemberPermissions,
type UpdateUserCompanyAccess,
companySkillSourceTypeSchema,
companySkillTrustLevelSchema,
companySkillCompatibilitySchema,
companySkillSourceBadgeSchema,
companySkillFileInventoryEntrySchema,
companySkillSchema,
companySkillListItemSchema,
companySkillUsageAgentSchema,
companySkillDetailSchema,
companySkillUpdateStatusSchema,
companySkillImportSchema,
companySkillProjectScanRequestSchema,
companySkillProjectScanSkippedSchema,
companySkillProjectScanConflictSchema,
companySkillProjectScanResultSchema,
companySkillCreateSchema,
companySkillFileDetailSchema,
companySkillFileUpdateSchema,
portabilityIncludeSchema,
portabilityEnvInputSchema,
portabilityCompanyManifestEntrySchema,
portabilitySidebarOrderSchema,
portabilityAgentManifestEntrySchema,
portabilityManifestSchema,
portabilitySourceSchema,
portabilityTargetSchema,
portabilityAgentSelectionSchema,
portabilityCollisionStrategySchema,
companyPortabilityExportSchema,
companyPortabilityPreviewSchema,
companyPortabilityImportSchema,
type CompanyPortabilityExport,
type CompanyPortabilityPreview,
type CompanyPortabilityImport,
jsonSchemaSchema,
pluginJobDeclarationSchema,
pluginWebhookDeclarationSchema,
pluginToolDeclarationSchema,
pluginUiSlotDeclarationSchema,
pluginLauncherActionDeclarationSchema,
pluginLauncherRenderDeclarationSchema,
pluginLauncherDeclarationSchema,
pluginManifestV1Schema,
installPluginSchema,
upsertPluginConfigSchema,
patchPluginConfigSchema,
updatePluginStatusSchema,
uninstallPluginSchema,
pluginStateScopeKeySchema,
setPluginStateSchema,
listPluginStateSchema,
type PluginJobDeclarationInput,
type PluginWebhookDeclarationInput,
type PluginToolDeclarationInput,
type PluginUiSlotDeclarationInput,
type PluginLauncherActionDeclarationInput,
type PluginLauncherRenderDeclarationInput,
type PluginLauncherDeclarationInput,
type PluginManifestV1Input,
type InstallPlugin,
type UpsertPluginConfig,
type PatchPluginConfig,
type UpdatePluginStatus,
type UninstallPlugin,
type PluginStateScopeKey,
type SetPluginState,
type ListPluginState,
} from "./validators/index.js";
export { API_PREFIX, API } from "./api.js";
export { normalizeAgentUrlKey, deriveAgentUrlKey, isUuidLike } from "./agent-url-key.js";
export { deriveProjectUrlKey, normalizeProjectUrlKey } from "./project-url-key.js";
export {
AGENT_MENTION_SCHEME,
PROJECT_MENTION_SCHEME,
buildAgentMentionHref,
buildProjectMentionHref,
extractAgentMentionIds,
parseAgentMentionHref,
parseProjectMentionHref,
extractProjectMentionIds,
type ParsedAgentMention,
type ParsedProjectMention,
} from "./project-mentions.js";
export {
paperclipConfigSchema,
configMetaSchema,
llmConfigSchema,
databaseBackupConfigSchema,
databaseConfigSchema,
loggingConfigSchema,
serverConfigSchema,
authConfigSchema,
secretsConfigSchema,
storageConfigSchema,
storageLocalDiskConfigSchema,
storageS3ConfigSchema,
secretsLocalEncryptedConfigSchema,
type PaperclipConfig,
type LlmConfig,
type DatabaseBackupConfig,
type DatabaseConfig,
type LoggingConfig,
type ServerConfig,
type AuthConfig,
type StorageConfig,
type StorageLocalDiskConfig,
type StorageS3Config,
type SecretsConfig,
type SecretsLocalEncryptedConfig,
type ConfigMeta,
} from "./config-schema.js";
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import {
buildAgentMentionHref,
buildProjectMentionHref,
extractAgentMentionIds,
extractProjectMentionIds,
parseAgentMentionHref,
parseProjectMentionHref,
} from "./project-mentions.js";
describe("project-mentions", () => {
it("round-trips project mentions with color metadata", () => {
const href = buildProjectMentionHref("project-123", "#336699");
expect(parseProjectMentionHref(href)).toEqual({
projectId: "project-123",
color: "#336699",
});
expect(extractProjectMentionIds(`[@Paperclip App](${href})`)).toEqual(["project-123"]);
});
it("round-trips agent mentions with icon metadata", () => {
const href = buildAgentMentionHref("agent-123", "code");
expect(parseAgentMentionHref(href)).toEqual({
agentId: "agent-123",
icon: "code",
});
expect(extractAgentMentionIds(`[@CodexCoder](${href})`)).toEqual(["agent-123"]);
});
});
@@ -0,0 +1,135 @@
export const PROJECT_MENTION_SCHEME = "project://";
export const AGENT_MENTION_SCHEME = "agent://";
const HEX_COLOR_RE = /^[0-9a-f]{6}$/i;
const HEX_COLOR_SHORT_RE = /^[0-9a-f]{3}$/i;
const HEX_COLOR_WITH_HASH_RE = /^#[0-9a-f]{6}$/i;
const HEX_COLOR_SHORT_WITH_HASH_RE = /^#[0-9a-f]{3}$/i;
const PROJECT_MENTION_LINK_RE = /\[[^\]]*]\((project:\/\/[^)\s]+)\)/gi;
const AGENT_MENTION_LINK_RE = /\[[^\]]*]\((agent:\/\/[^)\s]+)\)/gi;
const AGENT_ICON_NAME_RE = /^[a-z0-9-]+$/i;
export interface ParsedProjectMention {
projectId: string;
color: string | null;
}
export interface ParsedAgentMention {
agentId: string;
icon: string | null;
}
function normalizeHexColor(input: string | null | undefined): string | null {
if (!input) return null;
const trimmed = input.trim();
if (!trimmed) return null;
if (HEX_COLOR_WITH_HASH_RE.test(trimmed)) {
return trimmed.toLowerCase();
}
if (HEX_COLOR_RE.test(trimmed)) {
return `#${trimmed.toLowerCase()}`;
}
if (HEX_COLOR_SHORT_WITH_HASH_RE.test(trimmed)) {
const raw = trimmed.slice(1).toLowerCase();
return `#${raw[0]}${raw[0]}${raw[1]}${raw[1]}${raw[2]}${raw[2]}`;
}
if (HEX_COLOR_SHORT_RE.test(trimmed)) {
const raw = trimmed.toLowerCase();
return `#${raw[0]}${raw[0]}${raw[1]}${raw[1]}${raw[2]}${raw[2]}`;
}
return null;
}
export function buildProjectMentionHref(projectId: string, color?: string | null): string {
const trimmedProjectId = projectId.trim();
const normalizedColor = normalizeHexColor(color ?? null);
if (!normalizedColor) {
return `${PROJECT_MENTION_SCHEME}${trimmedProjectId}`;
}
return `${PROJECT_MENTION_SCHEME}${trimmedProjectId}?c=${encodeURIComponent(normalizedColor.slice(1))}`;
}
export function parseProjectMentionHref(href: string): ParsedProjectMention | null {
if (!href.startsWith(PROJECT_MENTION_SCHEME)) return null;
let url: URL;
try {
url = new URL(href);
} catch {
return null;
}
if (url.protocol !== "project:") return null;
const projectId = `${url.hostname}${url.pathname}`.replace(/^\/+/, "").trim();
if (!projectId) return null;
const color = normalizeHexColor(url.searchParams.get("c") ?? url.searchParams.get("color"));
return {
projectId,
color,
};
}
export function buildAgentMentionHref(agentId: string, icon?: string | null): string {
const trimmedAgentId = agentId.trim();
const normalizedIcon = normalizeAgentIcon(icon ?? null);
if (!normalizedIcon) {
return `${AGENT_MENTION_SCHEME}${trimmedAgentId}`;
}
return `${AGENT_MENTION_SCHEME}${trimmedAgentId}?i=${encodeURIComponent(normalizedIcon)}`;
}
export function parseAgentMentionHref(href: string): ParsedAgentMention | null {
if (!href.startsWith(AGENT_MENTION_SCHEME)) return null;
let url: URL;
try {
url = new URL(href);
} catch {
return null;
}
if (url.protocol !== "agent:") return null;
const agentId = `${url.hostname}${url.pathname}`.replace(/^\/+/, "").trim();
if (!agentId) return null;
return {
agentId,
icon: normalizeAgentIcon(url.searchParams.get("i") ?? url.searchParams.get("icon")),
};
}
export function extractProjectMentionIds(markdown: string): string[] {
if (!markdown) return [];
const ids = new Set<string>();
const re = new RegExp(PROJECT_MENTION_LINK_RE);
let match: RegExpExecArray | null;
while ((match = re.exec(markdown)) !== null) {
const parsed = parseProjectMentionHref(match[1]);
if (parsed) ids.add(parsed.projectId);
}
return [...ids];
}
export function extractAgentMentionIds(markdown: string): string[] {
if (!markdown) return [];
const ids = new Set<string>();
const re = new RegExp(AGENT_MENTION_LINK_RE);
let match: RegExpExecArray | null;
while ((match = re.exec(markdown)) !== null) {
const parsed = parseAgentMentionHref(match[1]);
if (parsed) ids.add(parsed.agentId);
}
return [...ids];
}
function normalizeAgentIcon(input: string | null | undefined): string | null {
if (!input) return null;
const trimmed = input.trim().toLowerCase();
if (!trimmed || !AGENT_ICON_NAME_RE.test(trimmed)) return null;
return trimmed;
}
@@ -0,0 +1,16 @@
const PROJECT_URL_KEY_DELIM_RE = /[^a-z0-9]+/g;
const PROJECT_URL_KEY_TRIM_RE = /^-+|-+$/g;
export function normalizeProjectUrlKey(value: string | null | undefined): string | null {
if (typeof value !== "string") return null;
const normalized = value
.trim()
.toLowerCase()
.replace(PROJECT_URL_KEY_DELIM_RE, "-")
.replace(PROJECT_URL_KEY_TRIM_RE, "");
return normalized.length > 0 ? normalized : null;
}
export function deriveProjectUrlKey(name: string | null | undefined, fallback?: string | null): string {
return normalizeProjectUrlKey(name) ?? normalizeProjectUrlKey(fallback) ?? "project";
}
@@ -0,0 +1,81 @@
import type {
AgentAdapterType,
InstanceUserRole,
InviteJoinType,
InviteType,
JoinRequestStatus,
JoinRequestType,
MembershipStatus,
PermissionKey,
PrincipalType,
} from "../constants.js";
export interface CompanyMembership {
id: string;
companyId: string;
principalType: PrincipalType;
principalId: string;
status: MembershipStatus;
membershipRole: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface PrincipalPermissionGrant {
id: string;
companyId: string;
principalType: PrincipalType;
principalId: string;
permissionKey: PermissionKey;
scope: Record<string, unknown> | null;
grantedByUserId: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface Invite {
id: string;
companyId: string | null;
inviteType: InviteType;
tokenHash: string;
allowedJoinTypes: InviteJoinType;
defaultsPayload: Record<string, unknown> | null;
expiresAt: Date;
invitedByUserId: string | null;
revokedAt: Date | null;
acceptedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
export interface JoinRequest {
id: string;
inviteId: string;
companyId: string;
requestType: JoinRequestType;
status: JoinRequestStatus;
requestIp: string;
requestingUserId: string | null;
requestEmailSnapshot: string | null;
agentName: string | null;
adapterType: AgentAdapterType | null;
capabilities: string | null;
agentDefaultsPayload: Record<string, unknown> | null;
claimSecretExpiresAt: Date | null;
claimSecretConsumedAt: Date | null;
createdAgentId: string | null;
approvedByUserId: string | null;
approvedAt: Date | null;
rejectedByUserId: string | null;
rejectedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
export interface InstanceUserRoleGrant {
id: string;
userId: string;
role: InstanceUserRole;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,13 @@
export interface ActivityEvent {
id: string;
companyId: string;
actorType: "agent" | "user" | "system";
actorId: string;
action: string;
entityType: string;
entityId: string;
agentId: string | null;
runId: string | null;
details: Record<string, unknown> | null;
createdAt: Date;
}
@@ -0,0 +1,45 @@
export type AgentSkillSyncMode = "unsupported" | "persistent" | "ephemeral";
export type AgentSkillState =
| "available"
| "configured"
| "installed"
| "missing"
| "stale"
| "external";
export type AgentSkillOrigin =
| "company_managed"
| "paperclip_required"
| "user_installed"
| "external_unknown";
export interface AgentSkillEntry {
key: string;
runtimeName: string | null;
desired: boolean;
managed: boolean;
required?: boolean;
requiredReason?: string | null;
state: AgentSkillState;
origin?: AgentSkillOrigin;
originLabel?: string | null;
locationLabel?: string | null;
readOnly?: boolean;
sourcePath?: string | null;
targetPath?: string | null;
detail?: string | null;
}
export interface AgentSkillSnapshot {
adapterType: string;
supported: boolean;
mode: AgentSkillSyncMode;
desiredSkills: string[];
entries: AgentSkillEntry[];
warnings: string[];
}
export interface AgentSkillSyncRequest {
desiredSkills: string[];
}
@@ -0,0 +1,129 @@
import type {
AgentAdapterType,
PauseReason,
AgentRole,
AgentStatus,
} from "../constants.js";
import type {
CompanyMembership,
PrincipalPermissionGrant,
} from "./access.js";
export interface AgentPermissions {
canCreateAgents: boolean;
}
export type AgentInstructionsBundleMode = "managed" | "external";
export interface AgentInstructionsFileSummary {
path: string;
size: number;
language: string;
markdown: boolean;
isEntryFile: boolean;
editable: boolean;
deprecated: boolean;
virtual: boolean;
}
export interface AgentInstructionsFileDetail extends AgentInstructionsFileSummary {
content: string;
}
export interface AgentInstructionsBundle {
agentId: string;
companyId: string;
mode: AgentInstructionsBundleMode | null;
rootPath: string | null;
managedRootPath: string;
entryFile: string;
resolvedEntryPath: string | null;
editable: boolean;
warnings: string[];
legacyPromptTemplateActive: boolean;
legacyBootstrapPromptTemplateActive: boolean;
files: AgentInstructionsFileSummary[];
}
export interface AgentAccessState {
canAssignTasks: boolean;
taskAssignSource: "explicit_grant" | "agent_creator" | "ceo_role" | "none";
membership: CompanyMembership | null;
grants: PrincipalPermissionGrant[];
}
export interface AgentChainOfCommandEntry {
id: string;
name: string;
role: AgentRole;
title: string | null;
}
export interface Agent {
id: string;
companyId: string;
name: string;
urlKey: string;
role: AgentRole;
title: string | null;
icon: string | null;
status: AgentStatus;
reportsTo: string | null;
capabilities: string | null;
adapterType: AgentAdapterType;
adapterConfig: Record<string, unknown>;
runtimeConfig: Record<string, unknown>;
budgetMonthlyCents: number;
spentMonthlyCents: number;
pauseReason: PauseReason | null;
pausedAt: Date | null;
permissions: AgentPermissions;
lastHeartbeatAt: Date | null;
metadata: Record<string, unknown> | null;
createdAt: Date;
updatedAt: Date;
}
export interface AgentDetail extends Agent {
chainOfCommand: AgentChainOfCommandEntry[];
access: AgentAccessState;
}
export interface AgentKeyCreated {
id: string;
name: string;
token: string;
createdAt: Date;
}
export interface AgentConfigRevision {
id: string;
companyId: string;
agentId: string;
createdByAgentId: string | null;
createdByUserId: string | null;
source: string;
rolledBackFromRevisionId: string | null;
changedKeys: string[];
beforeConfig: Record<string, unknown>;
afterConfig: Record<string, unknown>;
createdAt: Date;
}
export type AdapterEnvironmentCheckLevel = "info" | "warn" | "error";
export type AdapterEnvironmentTestStatus = "pass" | "warn" | "fail";
export interface AdapterEnvironmentCheck {
code: string;
level: AdapterEnvironmentCheckLevel;
message: string;
detail?: string | null;
hint?: string | null;
}
export interface AdapterEnvironmentTestResult {
adapterType: string;
status: AdapterEnvironmentTestStatus;
checks: AdapterEnvironmentCheck[];
testedAt: string;
}
@@ -0,0 +1,27 @@
import type { ApprovalStatus, ApprovalType } from "../constants.js";
export interface Approval {
id: string;
companyId: string;
type: ApprovalType;
requestedByAgentId: string | null;
requestedByUserId: string | null;
status: ApprovalStatus;
payload: Record<string, unknown>;
decisionNote: string | null;
decidedByUserId: string | null;
decidedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
export interface ApprovalComment {
id: string;
companyId: string;
approvalId: string;
authorAgentId: string | null;
authorUserId: string | null;
body: string;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,16 @@
export interface AssetImage {
assetId: string;
companyId: string;
provider: string;
objectKey: string;
contentType: string;
byteSize: number;
sha256: string;
originalFilename: string | null;
createdByAgentId: string | null;
createdByUserId: string | null;
createdAt: Date;
updatedAt: Date;
contentPath: string;
}
@@ -0,0 +1,99 @@
import type {
BudgetIncidentResolutionAction,
BudgetIncidentStatus,
BudgetMetric,
BudgetScopeType,
BudgetThresholdType,
BudgetWindowKind,
PauseReason,
} from "../constants.js";
export interface BudgetPolicy {
id: string;
companyId: string;
scopeType: BudgetScopeType;
scopeId: string;
metric: BudgetMetric;
windowKind: BudgetWindowKind;
amount: number;
warnPercent: number;
hardStopEnabled: boolean;
notifyEnabled: boolean;
isActive: boolean;
createdByUserId: string | null;
updatedByUserId: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface BudgetPolicySummary {
policyId: string;
companyId: string;
scopeType: BudgetScopeType;
scopeId: string;
scopeName: string;
metric: BudgetMetric;
windowKind: BudgetWindowKind;
amount: number;
observedAmount: number;
remainingAmount: number;
utilizationPercent: number;
warnPercent: number;
hardStopEnabled: boolean;
notifyEnabled: boolean;
isActive: boolean;
status: "ok" | "warning" | "hard_stop";
paused: boolean;
pauseReason: PauseReason | null;
windowStart: Date;
windowEnd: Date;
}
export interface BudgetIncident {
id: string;
companyId: string;
policyId: string;
scopeType: BudgetScopeType;
scopeId: string;
scopeName: string;
metric: BudgetMetric;
windowKind: BudgetWindowKind;
windowStart: Date;
windowEnd: Date;
thresholdType: BudgetThresholdType;
amountLimit: number;
amountObserved: number;
status: BudgetIncidentStatus;
approvalId: string | null;
approvalStatus: string | null;
resolvedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
export interface BudgetOverview {
companyId: string;
policies: BudgetPolicySummary[];
activeIncidents: BudgetIncident[];
pausedAgentCount: number;
pausedProjectCount: number;
pendingApprovalCount: number;
}
export interface BudgetPolicyUpsertInput {
scopeType: BudgetScopeType;
scopeId: string;
metric?: BudgetMetric;
windowKind?: BudgetWindowKind;
amount: number;
warnPercent?: number;
hardStopEnabled?: boolean;
notifyEnabled?: boolean;
isActive?: boolean;
}
export interface BudgetIncidentResolutionInput {
action: BudgetIncidentResolutionAction;
amount?: number;
decisionNote?: string | null;
}
@@ -0,0 +1,309 @@
export interface CompanyPortabilityInclude {
company: boolean;
agents: boolean;
projects: boolean;
issues: boolean;
skills: boolean;
}
export interface CompanyPortabilityEnvInput {
key: string;
description: string | null;
agentSlug: string | null;
kind: "secret" | "plain";
requirement: "required" | "optional";
defaultValue: string | null;
portability: "portable" | "system_dependent";
}
export type CompanyPortabilityFileEntry =
| string
| {
encoding: "base64";
data: string;
contentType?: string | null;
};
export interface CompanyPortabilityCompanyManifestEntry {
path: string;
name: string;
description: string | null;
brandColor: string | null;
logoPath: string | null;
requireBoardApprovalForNewAgents: boolean;
}
export interface CompanyPortabilitySidebarOrder {
agents: string[];
projects: string[];
}
export interface CompanyPortabilityProjectManifestEntry {
slug: string;
name: string;
path: string;
description: string | null;
ownerAgentSlug: string | null;
leadAgentSlug: string | null;
targetDate: string | null;
color: string | null;
status: string | null;
executionWorkspacePolicy: Record<string, unknown> | null;
workspaces: CompanyPortabilityProjectWorkspaceManifestEntry[];
metadata: Record<string, unknown> | null;
}
export interface CompanyPortabilityProjectWorkspaceManifestEntry {
key: string;
name: string;
sourceType: string | null;
repoUrl: string | null;
repoRef: string | null;
defaultRef: string | null;
visibility: string | null;
setupCommand: string | null;
cleanupCommand: string | null;
metadata: Record<string, unknown> | null;
isPrimary: boolean;
}
export interface CompanyPortabilityIssueRoutineTriggerManifestEntry {
kind: string;
label: string | null;
enabled: boolean;
cronExpression: string | null;
timezone: string | null;
signingMode: string | null;
replayWindowSec: number | null;
}
export interface CompanyPortabilityIssueRoutineManifestEntry {
concurrencyPolicy: string | null;
catchUpPolicy: string | null;
triggers: CompanyPortabilityIssueRoutineTriggerManifestEntry[];
}
export interface CompanyPortabilityIssueManifestEntry {
slug: string;
identifier: string | null;
title: string;
path: string;
projectSlug: string | null;
projectWorkspaceKey: string | null;
assigneeAgentSlug: string | null;
description: string | null;
recurring: boolean;
routine: CompanyPortabilityIssueRoutineManifestEntry | null;
legacyRecurrence: Record<string, unknown> | null;
status: string | null;
priority: string | null;
labelIds: string[];
billingCode: string | null;
executionWorkspaceSettings: Record<string, unknown> | null;
assigneeAdapterOverrides: Record<string, unknown> | null;
metadata: Record<string, unknown> | null;
}
export interface CompanyPortabilityAgentManifestEntry {
slug: string;
name: string;
path: string;
skills: string[];
role: string;
title: string | null;
icon: string | null;
capabilities: string | null;
reportsToSlug: string | null;
adapterType: string;
adapterConfig: Record<string, unknown>;
runtimeConfig: Record<string, unknown>;
permissions: Record<string, unknown>;
budgetMonthlyCents: number;
metadata: Record<string, unknown> | null;
}
export interface CompanyPortabilitySkillManifestEntry {
key: string;
slug: string;
name: string;
path: string;
description: string | null;
sourceType: string;
sourceLocator: string | null;
sourceRef: string | null;
trustLevel: string | null;
compatibility: string | null;
metadata: Record<string, unknown> | null;
fileInventory: Array<{
path: string;
kind: string;
}>;
}
export interface CompanyPortabilityManifest {
schemaVersion: number;
generatedAt: string;
source: {
companyId: string;
companyName: string;
} | null;
includes: CompanyPortabilityInclude;
company: CompanyPortabilityCompanyManifestEntry | null;
sidebar: CompanyPortabilitySidebarOrder | null;
agents: CompanyPortabilityAgentManifestEntry[];
skills: CompanyPortabilitySkillManifestEntry[];
projects: CompanyPortabilityProjectManifestEntry[];
issues: CompanyPortabilityIssueManifestEntry[];
envInputs: CompanyPortabilityEnvInput[];
}
export interface CompanyPortabilityExportResult {
rootPath: string;
manifest: CompanyPortabilityManifest;
files: Record<string, CompanyPortabilityFileEntry>;
warnings: string[];
paperclipExtensionPath: string;
}
export interface CompanyPortabilityExportPreviewFile {
path: string;
kind: "company" | "agent" | "skill" | "project" | "issue" | "extension" | "readme" | "other";
}
export interface CompanyPortabilityExportPreviewResult {
rootPath: string;
manifest: CompanyPortabilityManifest;
files: Record<string, CompanyPortabilityFileEntry>;
fileInventory: CompanyPortabilityExportPreviewFile[];
counts: {
files: number;
agents: number;
skills: number;
projects: number;
issues: number;
};
warnings: string[];
paperclipExtensionPath: string;
}
export type CompanyPortabilitySource =
| {
type: "inline";
rootPath?: string | null;
files: Record<string, CompanyPortabilityFileEntry>;
}
| {
type: "github";
url: string;
};
export type CompanyPortabilityImportTarget =
| {
mode: "new_company";
newCompanyName?: string | null;
}
| {
mode: "existing_company";
companyId: string;
};
export type CompanyPortabilityAgentSelection = "all" | string[];
export type CompanyPortabilityCollisionStrategy = "rename" | "skip" | "replace";
export interface CompanyPortabilityPreviewRequest {
source: CompanyPortabilitySource;
include?: Partial<CompanyPortabilityInclude>;
target: CompanyPortabilityImportTarget;
agents?: CompanyPortabilityAgentSelection;
collisionStrategy?: CompanyPortabilityCollisionStrategy;
nameOverrides?: Record<string, string>;
selectedFiles?: string[];
}
export interface CompanyPortabilityPreviewAgentPlan {
slug: string;
action: "create" | "update" | "skip";
plannedName: string;
existingAgentId: string | null;
reason: string | null;
}
export interface CompanyPortabilityPreviewProjectPlan {
slug: string;
action: "create" | "update" | "skip";
plannedName: string;
existingProjectId: string | null;
reason: string | null;
}
export interface CompanyPortabilityPreviewIssuePlan {
slug: string;
action: "create" | "skip";
plannedTitle: string;
reason: string | null;
}
export interface CompanyPortabilityPreviewResult {
include: CompanyPortabilityInclude;
targetCompanyId: string | null;
targetCompanyName: string | null;
collisionStrategy: CompanyPortabilityCollisionStrategy;
selectedAgentSlugs: string[];
plan: {
companyAction: "none" | "create" | "update";
agentPlans: CompanyPortabilityPreviewAgentPlan[];
projectPlans: CompanyPortabilityPreviewProjectPlan[];
issuePlans: CompanyPortabilityPreviewIssuePlan[];
};
manifest: CompanyPortabilityManifest;
files: Record<string, CompanyPortabilityFileEntry>;
envInputs: CompanyPortabilityEnvInput[];
warnings: string[];
errors: string[];
}
export interface CompanyPortabilityAdapterOverride {
adapterType: string;
adapterConfig?: Record<string, unknown>;
}
export interface CompanyPortabilityImportRequest extends CompanyPortabilityPreviewRequest {
adapterOverrides?: Record<string, CompanyPortabilityAdapterOverride>;
}
export interface CompanyPortabilityImportResult {
company: {
id: string;
name: string;
action: "created" | "updated" | "unchanged";
};
agents: {
slug: string;
id: string | null;
action: "created" | "updated" | "skipped";
name: string;
reason: string | null;
}[];
projects: {
slug: string;
id: string | null;
action: "created" | "updated" | "skipped";
name: string;
reason: string | null;
}[];
envInputs: CompanyPortabilityEnvInput[];
warnings: string[];
}
export interface CompanyPortabilityExportRequest {
include?: Partial<CompanyPortabilityInclude>;
agents?: string[];
skills?: string[];
projects?: string[];
issues?: string[];
projectIssues?: string[];
selectedFiles?: string[];
expandReferencedSkills?: boolean;
sidebarOrder?: Partial<CompanyPortabilitySidebarOrder>;
}
@@ -0,0 +1,152 @@
export type CompanySkillSourceType = "local_path" | "github" | "url" | "catalog" | "skills_sh";
export type CompanySkillTrustLevel = "markdown_only" | "assets" | "scripts_executables";
export type CompanySkillCompatibility = "compatible" | "unknown" | "invalid";
export type CompanySkillSourceBadge = "paperclip" | "github" | "local" | "url" | "catalog" | "skills_sh";
export interface CompanySkillFileInventoryEntry {
path: string;
kind: "skill" | "markdown" | "reference" | "script" | "asset" | "other";
}
export interface CompanySkill {
id: string;
companyId: string;
key: string;
slug: string;
name: string;
description: string | null;
markdown: string;
sourceType: CompanySkillSourceType;
sourceLocator: string | null;
sourceRef: string | null;
trustLevel: CompanySkillTrustLevel;
compatibility: CompanySkillCompatibility;
fileInventory: CompanySkillFileInventoryEntry[];
metadata: Record<string, unknown> | null;
createdAt: Date;
updatedAt: Date;
}
export interface CompanySkillListItem {
id: string;
companyId: string;
key: string;
slug: string;
name: string;
description: string | null;
sourceType: CompanySkillSourceType;
sourceLocator: string | null;
sourceRef: string | null;
trustLevel: CompanySkillTrustLevel;
compatibility: CompanySkillCompatibility;
fileInventory: CompanySkillFileInventoryEntry[];
createdAt: Date;
updatedAt: Date;
attachedAgentCount: number;
editable: boolean;
editableReason: string | null;
sourceLabel: string | null;
sourceBadge: CompanySkillSourceBadge;
sourcePath: string | null;
}
export interface CompanySkillUsageAgent {
id: string;
name: string;
urlKey: string;
adapterType: string;
desired: boolean;
actualState: string | null;
}
export interface CompanySkillDetail extends CompanySkill {
attachedAgentCount: number;
usedByAgents: CompanySkillUsageAgent[];
editable: boolean;
editableReason: string | null;
sourceLabel: string | null;
sourceBadge: CompanySkillSourceBadge;
sourcePath: string | null;
}
export interface CompanySkillUpdateStatus {
supported: boolean;
reason: string | null;
trackingRef: string | null;
currentRef: string | null;
latestRef: string | null;
hasUpdate: boolean;
}
export interface CompanySkillImportRequest {
source: string;
}
export interface CompanySkillImportResult {
imported: CompanySkill[];
warnings: string[];
}
export interface CompanySkillProjectScanRequest {
projectIds?: string[];
workspaceIds?: string[];
}
export interface CompanySkillProjectScanSkipped {
projectId: string;
projectName: string;
workspaceId: string | null;
workspaceName: string | null;
path: string | null;
reason: string;
}
export interface CompanySkillProjectScanConflict {
slug: string;
key: string;
projectId: string;
projectName: string;
workspaceId: string;
workspaceName: string;
path: string;
existingSkillId: string;
existingSkillKey: string;
existingSourceLocator: string | null;
reason: string;
}
export interface CompanySkillProjectScanResult {
scannedProjects: number;
scannedWorkspaces: number;
discovered: number;
imported: CompanySkill[];
updated: CompanySkill[];
skipped: CompanySkillProjectScanSkipped[];
conflicts: CompanySkillProjectScanConflict[];
warnings: string[];
}
export interface CompanySkillCreateRequest {
name: string;
slug?: string | null;
description?: string | null;
markdown?: string | null;
}
export interface CompanySkillFileDetail {
skillId: string;
path: string;
kind: CompanySkillFileInventoryEntry["kind"];
content: string;
language: string | null;
markdown: boolean;
editable: boolean;
}
export interface CompanySkillFileUpdateRequest {
path: string;
content: string;
}
@@ -0,0 +1,20 @@
import type { CompanyStatus, PauseReason } from "../constants.js";
export interface Company {
id: string;
name: string;
description: string | null;
status: CompanyStatus;
pauseReason: PauseReason | null;
pausedAt: Date | null;
issuePrefix: string;
issueCounter: number;
budgetMonthlyCents: number;
spentMonthlyCents: number;
requireBoardApprovalForNewAgents: boolean;
brandColor: string | null;
logoAssetId: string | null;
logoUrl: string | null;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,113 @@
import type { BillingType } from "../constants.js";
export interface CostEvent {
id: string;
companyId: string;
agentId: string;
issueId: string | null;
projectId: string | null;
goalId: string | null;
heartbeatRunId: string | null;
billingCode: string | null;
provider: string;
biller: string;
billingType: BillingType;
model: string;
inputTokens: number;
cachedInputTokens: number;
outputTokens: number;
costCents: number;
occurredAt: Date;
createdAt: Date;
}
export interface CostSummary {
companyId: string;
spendCents: number;
budgetCents: number;
utilizationPercent: number;
}
export interface CostByAgent {
agentId: string;
agentName: string | null;
agentStatus: string | null;
costCents: number;
inputTokens: number;
cachedInputTokens: number;
outputTokens: number;
apiRunCount: number;
subscriptionRunCount: number;
subscriptionCachedInputTokens: number;
subscriptionInputTokens: number;
subscriptionOutputTokens: number;
}
export interface CostByProviderModel {
provider: string;
biller: string;
billingType: BillingType;
model: string;
costCents: number;
inputTokens: number;
cachedInputTokens: number;
outputTokens: number;
apiRunCount: number;
subscriptionRunCount: number;
subscriptionCachedInputTokens: number;
subscriptionInputTokens: number;
subscriptionOutputTokens: number;
}
export interface CostByBiller {
biller: string;
costCents: number;
inputTokens: number;
cachedInputTokens: number;
outputTokens: number;
apiRunCount: number;
subscriptionRunCount: number;
subscriptionCachedInputTokens: number;
subscriptionInputTokens: number;
subscriptionOutputTokens: number;
providerCount: number;
modelCount: number;
}
/** per-agent breakdown by provider + model, for identifying token-hungry agents */
export interface CostByAgentModel {
agentId: string;
agentName: string | null;
provider: string;
biller: string;
billingType: BillingType;
model: string;
costCents: number;
inputTokens: number;
cachedInputTokens: number;
outputTokens: number;
}
/** spend per provider for a fixed rolling time window */
export interface CostWindowSpendRow {
provider: string;
biller: string;
/** duration label, e.g. "5h", "24h", "7d" */
window: string;
/** rolling window duration in hours */
windowHours: number;
costCents: number;
inputTokens: number;
cachedInputTokens: number;
outputTokens: number;
}
/** cost attributed to a project via heartbeat run → activity log → issue → project chain */
export interface CostByProject {
projectId: string | null;
projectName: string | null;
costCents: number;
inputTokens: number;
cachedInputTokens: number;
outputTokens: number;
}
@@ -0,0 +1,27 @@
export interface DashboardSummary {
companyId: string;
agents: {
active: number;
running: number;
paused: number;
error: number;
};
tasks: {
open: number;
inProgress: number;
blocked: number;
done: number;
};
costs: {
monthSpendCents: number;
monthBudgetCents: number;
monthUtilizationPercent: number;
};
pendingApprovals: number;
budgets: {
activeIncidents: number;
pendingApprovals: number;
pausedAgents: number;
pausedProjects: number;
};
}
@@ -0,0 +1,60 @@
import type { AgentAdapterType, FinanceDirection, FinanceEventKind, FinanceUnit } from "../constants.js";
export interface FinanceEvent {
id: string;
companyId: string;
agentId: string | null;
issueId: string | null;
projectId: string | null;
goalId: string | null;
heartbeatRunId: string | null;
costEventId: string | null;
billingCode: string | null;
description: string | null;
eventKind: FinanceEventKind;
direction: FinanceDirection;
biller: string;
provider: string | null;
executionAdapterType: AgentAdapterType | null;
pricingTier: string | null;
region: string | null;
model: string | null;
quantity: number | null;
unit: FinanceUnit | null;
amountCents: number;
currency: string;
estimated: boolean;
externalInvoiceId: string | null;
metadataJson: Record<string, unknown> | null;
occurredAt: Date;
createdAt: Date;
}
export interface FinanceSummary {
companyId: string;
debitCents: number;
creditCents: number;
netCents: number;
estimatedDebitCents: number;
eventCount: number;
}
export interface FinanceByBiller {
biller: string;
debitCents: number;
creditCents: number;
netCents: number;
estimatedDebitCents: number;
eventCount: number;
kindCount: number;
}
export interface FinanceByKind {
eventKind: FinanceEventKind;
debitCents: number;
creditCents: number;
netCents: number;
estimatedDebitCents: number;
eventCount: number;
billerCount: number;
}
@@ -0,0 +1,14 @@
import type { GoalLevel, GoalStatus } from "../constants.js";
export interface Goal {
id: string;
companyId: string;
title: string;
description: string | null;
level: GoalLevel;
status: GoalStatus;
parentId: string | null;
ownerAgentId: string | null;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,130 @@
import type {
AgentRole,
AgentStatus,
HeartbeatInvocationSource,
HeartbeatRunStatus,
WakeupTriggerDetail,
WakeupRequestStatus,
} from "../constants.js";
export interface HeartbeatRun {
id: string;
companyId: string;
agentId: string;
invocationSource: HeartbeatInvocationSource;
triggerDetail: WakeupTriggerDetail | null;
status: HeartbeatRunStatus;
startedAt: Date | null;
finishedAt: Date | null;
error: string | null;
wakeupRequestId: string | null;
exitCode: number | null;
signal: string | null;
usageJson: Record<string, unknown> | null;
resultJson: Record<string, unknown> | null;
sessionIdBefore: string | null;
sessionIdAfter: string | null;
logStore: string | null;
logRef: string | null;
logBytes: number | null;
logSha256: string | null;
logCompressed: boolean;
stdoutExcerpt: string | null;
stderrExcerpt: string | null;
errorCode: string | null;
externalRunId: string | null;
processPid: number | null;
processStartedAt: Date | null;
retryOfRunId: string | null;
processLossRetryCount: number;
contextSnapshot: Record<string, unknown> | null;
createdAt: Date;
updatedAt: Date;
}
export interface HeartbeatRunEvent {
id: number;
companyId: string;
runId: string;
agentId: string;
seq: number;
eventType: string;
stream: "system" | "stdout" | "stderr" | null;
level: "info" | "warn" | "error" | null;
color: string | null;
message: string | null;
payload: Record<string, unknown> | null;
createdAt: Date;
}
export interface AgentRuntimeState {
agentId: string;
companyId: string;
adapterType: string;
sessionId: string | null;
sessionDisplayId?: string | null;
sessionParamsJson?: Record<string, unknown> | null;
stateJson: Record<string, unknown>;
lastRunId: string | null;
lastRunStatus: string | null;
totalInputTokens: number;
totalOutputTokens: number;
totalCachedInputTokens: number;
totalCostCents: number;
lastError: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface AgentTaskSession {
id: string;
companyId: string;
agentId: string;
adapterType: string;
taskKey: string;
sessionParamsJson: Record<string, unknown> | null;
sessionDisplayId: string | null;
lastRunId: string | null;
lastError: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface AgentWakeupRequest {
id: string;
companyId: string;
agentId: string;
source: HeartbeatInvocationSource;
triggerDetail: WakeupTriggerDetail | null;
reason: string | null;
payload: Record<string, unknown> | null;
status: WakeupRequestStatus;
coalescedCount: number;
requestedByActorType: "user" | "agent" | "system" | null;
requestedByActorId: string | null;
idempotencyKey: string | null;
runId: string | null;
requestedAt: Date;
claimedAt: Date | null;
finishedAt: Date | null;
error: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface InstanceSchedulerHeartbeatAgent {
id: string;
companyId: string;
companyName: string;
companyIssuePrefix: string;
agentName: string;
agentUrlKey: string;
role: AgentRole;
title: string | null;
status: AgentStatus;
adapterType: string;
intervalSec: number;
heartbeatEnabled: boolean;
schedulerActive: boolean;
lastHeartbeatAt: Date | null;
}
@@ -0,0 +1,194 @@
export type { Company } from "./company.js";
export type { InstanceExperimentalSettings, InstanceGeneralSettings, InstanceSettings } from "./instance.js";
export type {
CompanySkillSourceType,
CompanySkillTrustLevel,
CompanySkillCompatibility,
CompanySkillSourceBadge,
CompanySkillFileInventoryEntry,
CompanySkill,
CompanySkillListItem,
CompanySkillUsageAgent,
CompanySkillDetail,
CompanySkillUpdateStatus,
CompanySkillImportRequest,
CompanySkillImportResult,
CompanySkillProjectScanRequest,
CompanySkillProjectScanSkipped,
CompanySkillProjectScanConflict,
CompanySkillProjectScanResult,
CompanySkillCreateRequest,
CompanySkillFileDetail,
CompanySkillFileUpdateRequest,
} from "./company-skill.js";
export type {
AgentSkillSyncMode,
AgentSkillState,
AgentSkillOrigin,
AgentSkillEntry,
AgentSkillSnapshot,
AgentSkillSyncRequest,
} from "./adapter-skills.js";
export type {
Agent,
AgentAccessState,
AgentChainOfCommandEntry,
AgentDetail,
AgentPermissions,
AgentInstructionsBundleMode,
AgentInstructionsFileSummary,
AgentInstructionsFileDetail,
AgentInstructionsBundle,
AgentKeyCreated,
AgentConfigRevision,
AdapterEnvironmentCheckLevel,
AdapterEnvironmentTestStatus,
AdapterEnvironmentCheck,
AdapterEnvironmentTestResult,
} from "./agent.js";
export type { AssetImage } from "./asset.js";
export type { Project, ProjectCodebase, ProjectCodebaseOrigin, ProjectGoalRef, ProjectWorkspace } from "./project.js";
export type {
ExecutionWorkspace,
WorkspaceRuntimeService,
ExecutionWorkspaceStrategyType,
ExecutionWorkspaceMode,
ExecutionWorkspaceProviderType,
ExecutionWorkspaceStatus,
ExecutionWorkspaceStrategy,
ProjectExecutionWorkspacePolicy,
ProjectExecutionWorkspaceDefaultMode,
IssueExecutionWorkspaceSettings,
} from "./workspace-runtime.js";
export type {
WorkspaceOperation,
WorkspaceOperationPhase,
WorkspaceOperationStatus,
} from "./workspace-operation.js";
export type {
IssueWorkProduct,
IssueWorkProductType,
IssueWorkProductProvider,
IssueWorkProductStatus,
IssueWorkProductReviewState,
} from "./work-product.js";
export type {
Issue,
IssueAssigneeAdapterOverrides,
IssueComment,
IssueDocument,
IssueDocumentSummary,
DocumentRevision,
DocumentFormat,
LegacyPlanDocument,
IssueAncestor,
IssueAncestorProject,
IssueAncestorGoal,
IssueAttachment,
IssueLabel,
} from "./issue.js";
export type { Goal } from "./goal.js";
export type { Approval, ApprovalComment } from "./approval.js";
export type {
BudgetPolicy,
BudgetPolicySummary,
BudgetIncident,
BudgetOverview,
BudgetPolicyUpsertInput,
BudgetIncidentResolutionInput,
} from "./budget.js";
export type {
SecretProvider,
SecretVersionSelector,
EnvPlainBinding,
EnvSecretRefBinding,
EnvBinding,
AgentEnvConfig,
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 {
HeartbeatRun,
HeartbeatRunEvent,
AgentRuntimeState,
AgentTaskSession,
AgentWakeupRequest,
InstanceSchedulerHeartbeatAgent,
} from "./heartbeat.js";
export type { LiveEvent } from "./live.js";
export type { DashboardSummary } from "./dashboard.js";
export type { ActivityEvent } from "./activity.js";
export type { SidebarBadges } from "./sidebar-badges.js";
export type {
CompanyMembership,
PrincipalPermissionGrant,
Invite,
JoinRequest,
InstanceUserRoleGrant,
} from "./access.js";
export type { QuotaWindow, ProviderQuotaResult } from "./quota.js";
export type {
CompanyPortabilityInclude,
CompanyPortabilityEnvInput,
CompanyPortabilityFileEntry,
CompanyPortabilityCompanyManifestEntry,
CompanyPortabilitySidebarOrder,
CompanyPortabilityAgentManifestEntry,
CompanyPortabilitySkillManifestEntry,
CompanyPortabilityProjectManifestEntry,
CompanyPortabilityProjectWorkspaceManifestEntry,
CompanyPortabilityIssueRoutineTriggerManifestEntry,
CompanyPortabilityIssueRoutineManifestEntry,
CompanyPortabilityIssueManifestEntry,
CompanyPortabilityManifest,
CompanyPortabilityExportResult,
CompanyPortabilityExportPreviewFile,
CompanyPortabilityExportPreviewResult,
CompanyPortabilitySource,
CompanyPortabilityImportTarget,
CompanyPortabilityAgentSelection,
CompanyPortabilityCollisionStrategy,
CompanyPortabilityPreviewRequest,
CompanyPortabilityPreviewAgentPlan,
CompanyPortabilityPreviewProjectPlan,
CompanyPortabilityPreviewIssuePlan,
CompanyPortabilityPreviewResult,
CompanyPortabilityAdapterOverride,
CompanyPortabilityImportRequest,
CompanyPortabilityImportResult,
CompanyPortabilityExportRequest,
} from "./company-portability.js";
export type {
JsonSchema,
PluginJobDeclaration,
PluginWebhookDeclaration,
PluginToolDeclaration,
PluginUiSlotDeclaration,
PluginLauncherActionDeclaration,
PluginLauncherRenderDeclaration,
PluginLauncherRenderContextSnapshot,
PluginLauncherDeclaration,
PluginMinimumHostVersion,
PluginUiDeclaration,
PaperclipPluginManifestV1,
PluginRecord,
PluginStateRecord,
PluginConfig,
PluginEntityRecord,
PluginEntityQuery,
PluginJobRecord,
PluginJobRunRecord,
PluginWebhookDeliveryRecord,
} from "./plugin.js";
@@ -0,0 +1,16 @@
export interface InstanceGeneralSettings {
censorUsernameInLogs: boolean;
}
export interface InstanceExperimentalSettings {
enableIsolatedWorkspaces: boolean;
autoRestartDevServerWhenIdle: boolean;
}
export interface InstanceSettings {
id: string;
general: InstanceGeneralSettings;
experimental: InstanceExperimentalSettings;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,177 @@
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";
import type { IssueWorkProduct } from "./work-product.js";
export interface IssueAncestorProject {
id: string;
name: string;
description: string | null;
status: string;
goalId: string | null;
workspaces: ProjectWorkspace[];
primaryWorkspace: ProjectWorkspace | null;
}
export interface IssueAncestorGoal {
id: string;
title: string;
description: string | null;
level: string;
status: string;
}
export interface IssueAncestor {
id: string;
identifier: string | null;
title: string;
description: string | null;
status: string;
priority: string;
assigneeAgentId: string | null;
assigneeUserId: string | null;
projectId: string | null;
goalId: string | null;
project: IssueAncestorProject | null;
goal: IssueAncestorGoal | null;
}
export interface IssueLabel {
id: string;
companyId: string;
name: string;
color: string;
createdAt: Date;
updatedAt: Date;
}
export interface IssueAssigneeAdapterOverrides {
adapterConfig?: Record<string, unknown>;
useProjectWorkspace?: boolean;
}
export type DocumentFormat = "markdown";
export interface IssueDocumentSummary {
id: string;
companyId: string;
issueId: string;
key: string;
title: string | null;
format: DocumentFormat;
latestRevisionId: string | null;
latestRevisionNumber: number;
createdByAgentId: string | null;
createdByUserId: string | null;
updatedByAgentId: string | null;
updatedByUserId: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface IssueDocument extends IssueDocumentSummary {
body: string;
}
export interface DocumentRevision {
id: string;
companyId: string;
documentId: string;
issueId: string;
key: string;
revisionNumber: number;
body: string;
changeSummary: string | null;
createdByAgentId: string | null;
createdByUserId: string | null;
createdAt: Date;
}
export interface LegacyPlanDocument {
key: "plan";
body: string;
source: "issue_description";
}
export interface Issue {
id: string;
companyId: string;
projectId: string | null;
projectWorkspaceId: string | null;
goalId: string | null;
parentId: string | null;
ancestors?: IssueAncestor[];
title: string;
description: string | null;
status: IssueStatus;
priority: IssuePriority;
assigneeAgentId: string | null;
assigneeUserId: string | null;
checkoutRunId: string | null;
executionRunId: string | null;
executionAgentNameKey: string | null;
executionLockedAt: Date | null;
createdByAgentId: string | null;
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;
executionWorkspaceId: string | null;
executionWorkspacePreference: string | null;
executionWorkspaceSettings: IssueExecutionWorkspaceSettings | null;
startedAt: Date | null;
completedAt: Date | null;
cancelledAt: Date | null;
hiddenAt: Date | null;
labelIds?: string[];
labels?: IssueLabel[];
planDocument?: IssueDocument | null;
documentSummaries?: IssueDocumentSummary[];
legacyPlanDocument?: LegacyPlanDocument | null;
project?: Project | null;
goal?: Goal | null;
currentExecutionWorkspace?: ExecutionWorkspace | null;
workProducts?: IssueWorkProduct[];
mentionedProjects?: Project[];
myLastTouchAt?: Date | null;
lastExternalCommentAt?: Date | null;
isUnreadForMe?: boolean;
createdAt: Date;
updatedAt: Date;
}
export interface IssueComment {
id: string;
companyId: string;
issueId: string;
authorAgentId: string | null;
authorUserId: string | null;
body: string;
createdAt: Date;
updatedAt: Date;
}
export interface IssueAttachment {
id: string;
companyId: string;
issueId: string;
issueCommentId: string | null;
assetId: string;
provider: string;
objectKey: string;
contentType: string;
byteSize: number;
sha256: string;
originalFilename: string | null;
createdByAgentId: string | null;
createdByUserId: string | null;
createdAt: Date;
updatedAt: Date;
contentPath: string;
}
@@ -0,0 +1,9 @@
import type { LiveEventType } from "../constants.js";
export interface LiveEvent {
id: number;
companyId: string;
type: LiveEventType;
createdAt: string;
payload: Record<string, unknown>;
}
@@ -0,0 +1,489 @@
import type {
PluginStatus,
PluginCategory,
PluginCapability,
PluginUiSlotType,
PluginUiSlotEntityType,
PluginStateScopeKind,
PluginLauncherPlacementZone,
PluginLauncherAction,
PluginLauncherBounds,
PluginLauncherRenderEnvironment,
} from "../constants.js";
// ---------------------------------------------------------------------------
// JSON Schema placeholder – plugins declare config schemas as JSON Schema
// ---------------------------------------------------------------------------
/**
* A JSON Schema object used for plugin config schemas and tool parameter schemas.
* Plugins provide these as plain JSON Schema compatible objects.
*/
export type JsonSchema = Record<string, unknown>;
// ---------------------------------------------------------------------------
// Manifest sub-types — nested declarations within PaperclipPluginManifestV1
// ---------------------------------------------------------------------------
/**
* Declares a scheduled job a plugin can run.
*
* @see PLUGIN_SPEC.md §17 — Scheduled Jobs
*/
export interface PluginJobDeclaration {
/** Stable identifier for this job, unique within the plugin. */
jobKey: string;
/** Human-readable name shown in the operator UI. */
displayName: string;
/** Optional description of what the job does. */
description?: string;
/** Cron expression for the schedule (e.g. "star/15 star star star star" or "0 * * * *"). */
schedule?: string;
}
/**
* Declares a webhook endpoint the plugin can receive.
* Route: `POST /api/plugins/:pluginId/webhooks/:endpointKey`
*
* @see PLUGIN_SPEC.md §18 — Webhooks
*/
export interface PluginWebhookDeclaration {
/** Stable identifier for this endpoint, unique within the plugin. */
endpointKey: string;
/** Human-readable name shown in the operator UI. */
displayName: string;
/** Optional description of what this webhook handles. */
description?: string;
}
/**
* Declares an agent tool contributed by the plugin. Tools are namespaced
* by plugin ID at runtime (e.g. `linear:search-issues`).
*
* Requires the `agent.tools.register` capability.
*
* @see PLUGIN_SPEC.md §11 — Agent Tools
*/
export interface PluginToolDeclaration {
/** Tool name, unique within the plugin. Namespaced by plugin ID at runtime. */
name: string;
/** Human-readable name shown to agents and in the UI. */
displayName: string;
/** Description provided to the agent so it knows when to use this tool. */
description: string;
/** JSON Schema describing the tool's input parameters. */
parametersSchema: JsonSchema;
}
/**
* Declares a UI extension slot the plugin fills with a React component.
*
* @see PLUGIN_SPEC.md §19 — UI Extension Model
*/
export interface PluginUiSlotDeclaration {
/** The type of UI mount point (page, detailTab, taskDetailView, toolbarButton, etc.). */
type: PluginUiSlotType;
/** Unique slot identifier within the plugin. */
id: string;
/** Human-readable name shown in navigation or tab labels. */
displayName: string;
/** Which export name in the UI bundle provides this component. */
exportName: string;
/**
* Entity targets for context-sensitive slots.
* Required for `detailTab`, `taskDetailView`, and `contextMenuItem`.
*/
entityTypes?: PluginUiSlotEntityType[];
/**
* Optional company-scoped route segment for page slots.
* Example: `kitchensink` becomes `/:companyPrefix/kitchensink`.
*/
routePath?: string;
/**
* Optional ordering hint within a slot surface. Lower numbers appear first.
* Defaults to host-defined ordering if omitted.
*/
order?: number;
}
/**
* Describes the action triggered by a plugin launcher surface.
*/
export interface PluginLauncherActionDeclaration {
/** What kind of launch behavior the host should perform. */
type: PluginLauncherAction;
/**
* Stable target identifier or URL. The meaning depends on `type`
* (for example a route, tab key, action key, or external URL).
*/
target: string;
/** Optional arbitrary parameters passed along to the target. */
params?: Record<string, unknown>;
}
/**
* Optional render metadata for the destination opened by a launcher.
*/
export interface PluginLauncherRenderDeclaration {
/** High-level container the launcher expects the host to use. */
environment: PluginLauncherRenderEnvironment;
/** Optional size hint for the destination surface. */
bounds?: PluginLauncherBounds;
}
/**
* Serializable runtime snapshot of the host launcher/container environment.
*/
export interface PluginLauncherRenderContextSnapshot {
/** The current launcher/container environment selected by the host. */
environment: PluginLauncherRenderEnvironment | null;
/** Launcher id that opened this surface, if any. */
launcherId: string | null;
/** Current host-applied bounds hint for the environment, if any. */
bounds: PluginLauncherBounds | null;
}
/**
* Declares a plugin launcher surface independent of the low-level slot
* implementation that mounts it.
*/
export interface PluginLauncherDeclaration {
/** Stable identifier for this launcher, unique within the plugin. */
id: string;
/** Human-readable label shown for the launcher. */
displayName: string;
/** Optional description for operator-facing docs or future UI affordances. */
description?: string;
/** Where in the host UI this launcher should be placed. */
placementZone: PluginLauncherPlacementZone;
/** Optional export name in the UI bundle when the launcher has custom UI. */
exportName?: string;
/**
* Optional entity targeting for context-sensitive launcher zones.
* Reuses the same entity union as UI slots for consistency.
*/
entityTypes?: PluginUiSlotEntityType[];
/** Optional ordering hint within the placement zone. */
order?: number;
/** What should happen when the launcher is activated. */
action: PluginLauncherActionDeclaration;
/** Optional render/container hints for the launched destination. */
render?: PluginLauncherRenderDeclaration;
}
/**
* Lower-bound semver requirement for the Paperclip host.
*
* The host should reject installation when its running version is lower than
* the declared minimum.
*/
export type PluginMinimumHostVersion = string;
/**
* Groups plugin UI declarations that are served from the shared UI bundle
* root declared in `entrypoints.ui`.
*/
export interface PluginUiDeclaration {
/** UI extension slots this plugin fills. */
slots?: PluginUiSlotDeclaration[];
/** Declarative launcher metadata for host-mounted plugin entry points. */
launchers?: PluginLauncherDeclaration[];
}
// ---------------------------------------------------------------------------
// Plugin Manifest V1
// ---------------------------------------------------------------------------
/**
* The manifest shape every plugin package must export.
* See PLUGIN_SPEC.md §10.1 for the normative definition.
*/
export interface PaperclipPluginManifestV1 {
/** Globally unique plugin identifier (e.g. `"acme.linear-sync"`). Must be lowercase alphanumeric with dots, hyphens, or underscores. */
id: string;
/** Plugin API version. Must be `1` for the current spec. */
apiVersion: 1;
/** Semver version of the plugin package (e.g. `"1.2.0"`). */
version: string;
/** Human-readable name (max 100 chars). */
displayName: string;
/** Short description (max 500 chars). */
description: string;
/** Author name (max 200 chars). May include email in angle brackets, e.g. `"Jane Doe <jane@example.com>"`. */
author: string;
/** One or more categories classifying this plugin. */
categories: PluginCategory[];
/**
* Minimum host version required (semver lower bound).
* Preferred generic field for new manifests.
*/
minimumHostVersion?: PluginMinimumHostVersion;
/**
* Legacy alias for `minimumHostVersion`.
* Kept for backwards compatibility with existing manifests and docs.
*/
minimumPaperclipVersion?: PluginMinimumHostVersion;
/** Capabilities this plugin requires from the host. Enforced at runtime. */
capabilities: PluginCapability[];
/** Entrypoint paths relative to the package root. */
entrypoints: {
/** Path to the worker entrypoint (required). */
worker: string;
/** Path to the UI bundle directory (required when `ui.slots` is declared). */
ui?: string;
};
/** JSON Schema for operator-editable instance configuration. */
instanceConfigSchema?: JsonSchema;
/** Scheduled jobs this plugin declares. Requires `jobs.schedule` capability. */
jobs?: PluginJobDeclaration[];
/** Webhook endpoints this plugin declares. Requires `webhooks.receive` capability. */
webhooks?: PluginWebhookDeclaration[];
/** Agent tools this plugin contributes. Requires `agent.tools.register` capability. */
tools?: PluginToolDeclaration[];
/**
* Legacy top-level launcher declarations.
* Prefer `ui.launchers` for new manifests.
*/
launchers?: PluginLauncherDeclaration[];
/** UI bundle declarations. Requires `entrypoints.ui` when populated. */
ui?: PluginUiDeclaration;
}
// ---------------------------------------------------------------------------
// Plugin Record – represents a row in the `plugins` table
// ---------------------------------------------------------------------------
/**
* Domain type for an installed plugin as persisted in the `plugins` table.
* See PLUGIN_SPEC.md §21.3 for the schema definition.
*/
export interface PluginRecord {
/** UUID primary key. */
id: string;
/** Unique key derived from `manifest.id`. Used for lookups. */
pluginKey: string;
/** npm package name (e.g. `"@acme/plugin-linear"`). */
packageName: string;
/** Installed semver version. */
version: string;
/** Plugin API version from the manifest. */
apiVersion: number;
/** Plugin categories from the manifest. */
categories: PluginCategory[];
/** Full manifest snapshot persisted at install/upgrade time. */
manifestJson: PaperclipPluginManifestV1;
/** Current lifecycle status. */
status: PluginStatus;
/** Deterministic load order (null if not yet assigned). */
installOrder: number | null;
/** Resolved package path for local-path installs; used to find worker entrypoint. */
packagePath: string | null;
/** Most recent error message, or operator-provided disable reason. */
lastError: string | null;
/** Timestamp when the plugin was first installed. */
installedAt: Date;
/** Timestamp of the most recent status or metadata change. */
updatedAt: Date;
}
// ---------------------------------------------------------------------------
// Plugin State – represents a row in the `plugin_state` table
// ---------------------------------------------------------------------------
/**
* Domain type for a single scoped key-value entry in the `plugin_state` table.
* Plugins read and write these entries through `ctx.state` in the SDK.
*
* The five-part composite key `(pluginId, scopeKind, scopeId, namespace, stateKey)`
* uniquely identifies a state entry.
*
* @see PLUGIN_SPEC.md §21.3 — `plugin_state`
*/
export interface PluginStateRecord {
/** UUID primary key. */
id: string;
/** FK to `plugins.id`. */
pluginId: string;
/** Granularity of the scope. */
scopeKind: PluginStateScopeKind;
/**
* UUID or text identifier for the scoped object.
* `null` for `instance` scope (no associated entity).
*/
scopeId: string | null;
/**
* Sub-namespace within the scope to avoid key collisions.
* Defaults to `"default"` if not explicitly set by the plugin.
*/
namespace: string;
/** The key for this state entry within the namespace. */
stateKey: string;
/** Stored JSON value. May be any JSON-serializable type. */
valueJson: unknown;
/** Timestamp of the most recent write. */
updatedAt: Date;
}
// ---------------------------------------------------------------------------
// Plugin Config – represents a row in the `plugin_config` table
// ---------------------------------------------------------------------------
/**
* Domain type for a plugin's instance configuration as persisted in the
* `plugin_config` table.
* See PLUGIN_SPEC.md §21.3 for the schema definition.
*/
export interface PluginConfig {
/** UUID primary key. */
id: string;
/** FK to `plugins.id`. Unique — each plugin has at most one config row. */
pluginId: string;
/** Operator-provided configuration values (validated against `instanceConfigSchema`). */
configJson: Record<string, unknown>;
/** Most recent config validation error, if any. */
lastError: string | null;
/** Timestamp when the config row was created. */
createdAt: Date;
/** Timestamp of the most recent config update. */
updatedAt: Date;
}
/**
* Query filter for `ctx.entities.list`.
*/
export interface PluginEntityQuery {
/** Optional filter by entity type (e.g. 'project', 'issue'). */
entityType?: string;
/** Optional filter by external system identifier. */
externalId?: string;
/** Maximum number of records to return. Defaults to 100. */
limit?: number;
/** Number of records to skip. Defaults to 0. */
offset?: number;
}
// ---------------------------------------------------------------------------
// Plugin Entity – represents a row in the `plugin_entities` table
// ---------------------------------------------------------------------------
/**
* Domain type for an external entity mapping as persisted in the `plugin_entities` table.
*/
export interface PluginEntityRecord {
/** UUID primary key. */
id: string;
/** FK to `plugins.id`. */
pluginId: string;
/** Plugin-defined entity type. */
entityType: string;
/** Scope where this entity lives. */
scopeKind: PluginStateScopeKind;
/** UUID or text identifier for the scoped object. */
scopeId: string | null;
/** External identifier in the remote system. */
externalId: string | null;
/** Human-readable title. */
title: string | null;
/** Optional status string. */
status: string | null;
/** Full entity data blob. */
data: Record<string, unknown>;
/** ISO 8601 creation timestamp. */
createdAt: Date;
/** ISO 8601 last-updated timestamp. */
updatedAt: Date;
}
// ---------------------------------------------------------------------------
// Plugin Job – represents a row in the `plugin_jobs` table
// ---------------------------------------------------------------------------
/**
* Domain type for a registered plugin job as persisted in the `plugin_jobs` table.
*/
export interface PluginJobRecord {
/** UUID primary key. */
id: string;
/** FK to `plugins.id`. */
pluginId: string;
/** Job key matching the manifest declaration. */
jobKey: string;
/** Cron expression for the schedule. */
schedule: string;
/** Current job status. */
status: "active" | "paused" | "failed";
/** Last time the job was executed. */
lastRunAt: Date | null;
/** Next scheduled execution time. */
nextRunAt: Date | null;
/** ISO 8601 creation timestamp. */
createdAt: Date;
/** ISO 8601 last-updated timestamp. */
updatedAt: Date;
}
// ---------------------------------------------------------------------------
// Plugin Job Run – represents a row in the `plugin_job_runs` table
// ---------------------------------------------------------------------------
/**
* Domain type for a job execution history record.
*/
export interface PluginJobRunRecord {
/** UUID primary key. */
id: string;
/** FK to `plugin_jobs.id`. */
jobId: string;
/** FK to `plugins.id`. */
pluginId: string;
/** What triggered this run. */
trigger: "schedule" | "manual" | "retry";
/** Current run status. */
status: "pending" | "queued" | "running" | "succeeded" | "failed" | "cancelled";
/** Run duration in milliseconds. */
durationMs: number | null;
/** Error message if the run failed. */
error: string | null;
/** Run logs. */
logs: string[];
/** ISO 8601 start timestamp. */
startedAt: Date | null;
/** ISO 8601 finish timestamp. */
finishedAt: Date | null;
/** ISO 8601 creation timestamp. */
createdAt: Date;
}
// ---------------------------------------------------------------------------
// Plugin Webhook Delivery – represents a row in the `plugin_webhook_deliveries` table
// ---------------------------------------------------------------------------
/**
* Domain type for an inbound webhook delivery record.
*/
export interface PluginWebhookDeliveryRecord {
/** UUID primary key. */
id: string;
/** FK to `plugins.id`. */
pluginId: string;
/** Webhook endpoint key matching the manifest. */
webhookKey: string;
/** External identifier from the remote system. */
externalId: string | null;
/** Delivery status. */
status: "pending" | "success" | "failed";
/** Processing duration in milliseconds. */
durationMs: number | null;
/** Error message if processing failed. */
error: string | null;
/** Webhook payload. */
payload: Record<string, unknown>;
/** Webhook headers. */
headers: Record<string, string>;
/** ISO 8601 start timestamp. */
startedAt: Date | null;
/** ISO 8601 finish timestamp. */
finishedAt: Date | null;
/** ISO 8601 creation timestamp. */
createdAt: Date;
}
@@ -0,0 +1,72 @@
import type { PauseReason, ProjectStatus } from "../constants.js";
import type { ProjectExecutionWorkspacePolicy, WorkspaceRuntimeService } from "./workspace-runtime.js";
export type ProjectWorkspaceSourceType = "local_path" | "git_repo" | "remote_managed" | "non_git_path";
export type ProjectWorkspaceVisibility = "default" | "advanced";
export interface ProjectGoalRef {
id: string;
title: string;
}
export interface ProjectWorkspace {
id: string;
companyId: string;
projectId: string;
name: string;
sourceType: ProjectWorkspaceSourceType;
cwd: string | null;
repoUrl: string | null;
repoRef: string | null;
defaultRef: string | null;
visibility: ProjectWorkspaceVisibility;
setupCommand: string | null;
cleanupCommand: string | null;
remoteProvider: string | null;
remoteWorkspaceRef: string | null;
sharedWorkspaceKey: string | null;
metadata: Record<string, unknown> | null;
isPrimary: boolean;
runtimeServices?: WorkspaceRuntimeService[];
createdAt: Date;
updatedAt: Date;
}
export type ProjectCodebaseOrigin = "local_folder" | "managed_checkout";
export interface ProjectCodebase {
workspaceId: string | null;
repoUrl: string | null;
repoRef: string | null;
defaultRef: string | null;
repoName: string | null;
localFolder: string | null;
managedFolder: string;
effectiveLocalFolder: string;
origin: ProjectCodebaseOrigin;
}
export interface Project {
id: string;
companyId: string;
urlKey: string;
/** @deprecated Use goalIds / goals instead */
goalId: string | null;
goalIds: string[];
goals: ProjectGoalRef[];
name: string;
description: string | null;
status: ProjectStatus;
leadAgentId: string | null;
targetDate: string | null;
color: string | null;
pauseReason: PauseReason | null;
pausedAt: Date | null;
executionWorkspacePolicy: ProjectExecutionWorkspacePolicy | null;
codebase: ProjectCodebase;
workspaces: ProjectWorkspace[];
primaryWorkspace: ProjectWorkspace | null;
archivedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,26 @@
/** a single rate-limit or usage window returned by a provider quota API */
export interface QuotaWindow {
/** human label, e.g. "5h", "7d", "Sonnet 7d", "Credits" */
label: string;
/** percent of the window already consumed (0-100), null when not reported */
usedPercent: number | null;
/** iso timestamp when this window resets, null when not reported */
resetsAt: string | null;
/** free-form value label for credit-style windows, e.g. "$4.20 remaining" */
valueLabel: string | null;
/** optional supporting text, e.g. reset details or provider-specific notes */
detail?: string | null;
}
/** result for one provider from the quota-windows endpoint */
export interface ProviderQuotaResult {
/** provider slug, e.g. "anthropic", "openai" */
provider: string;
/** source label when the provider reports where the quota data came from */
source?: string | null;
/** true when the fetch succeeded and windows is populated */
ok: boolean;
/** error message when ok is false */
error?: string;
windows: QuotaWindow[];
}
@@ -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;
}
@@ -0,0 +1,43 @@
export type SecretProvider =
| "local_encrypted"
| "aws_secrets_manager"
| "gcp_secret_manager"
| "vault";
export type SecretVersionSelector = number | "latest";
export interface EnvPlainBinding {
type: "plain";
value: string;
}
export interface EnvSecretRefBinding {
type: "secret_ref";
secretId: string;
version?: SecretVersionSelector;
}
// Backward-compatible: legacy plaintext string values are still accepted.
export type EnvBinding = string | EnvPlainBinding | EnvSecretRefBinding;
export type AgentEnvConfig = Record<string, EnvBinding>;
export interface CompanySecret {
id: string;
companyId: string;
name: string;
provider: SecretProvider;
externalRef: string | null;
latestVersion: number;
description: string | null;
createdByAgentId: string | null;
createdByUserId: string | null;
createdAt: Date;
updatedAt: Date;
}
export interface SecretProviderDescriptor {
id: SecretProvider;
label: string;
requiresExternalRef: boolean;
}
@@ -0,0 +1,6 @@
export interface SidebarBadges {
inbox: number;
approvals: number;
failedRuns: number;
joinRequests: number;
}
@@ -0,0 +1,55 @@
export type IssueWorkProductType =
| "preview_url"
| "runtime_service"
| "pull_request"
| "branch"
| "commit"
| "artifact"
| "document";
export type IssueWorkProductProvider =
| "paperclip"
| "github"
| "vercel"
| "s3"
| "custom";
export type IssueWorkProductStatus =
| "active"
| "ready_for_review"
| "approved"
| "changes_requested"
| "merged"
| "closed"
| "failed"
| "archived"
| "draft";
export type IssueWorkProductReviewState =
| "none"
| "needs_board_review"
| "approved"
| "changes_requested";
export interface IssueWorkProduct {
id: string;
companyId: string;
projectId: string | null;
issueId: string;
executionWorkspaceId: string | null;
runtimeServiceId: string | null;
type: IssueWorkProductType;
provider: IssueWorkProductProvider | string;
externalId: string | null;
title: string;
url: string | null;
status: IssueWorkProductStatus | string;
reviewState: IssueWorkProductReviewState;
isPrimary: boolean;
healthStatus: "unknown" | "healthy" | "unhealthy";
summary: string | null;
metadata: Record<string, unknown> | null;
createdByRunId: string | null;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,31 @@
export type WorkspaceOperationPhase =
| "worktree_prepare"
| "workspace_provision"
| "workspace_teardown"
| "worktree_cleanup";
export type WorkspaceOperationStatus = "running" | "succeeded" | "failed" | "skipped";
export interface WorkspaceOperation {
id: string;
companyId: string;
executionWorkspaceId: string | null;
heartbeatRunId: string | null;
phase: WorkspaceOperationPhase;
command: string | null;
cwd: string | null;
status: WorkspaceOperationStatus;
exitCode: number | null;
logStore: string | null;
logRef: string | null;
logBytes: number | null;
logSha256: string | null;
logCompressed: boolean;
stdoutExcerpt: string | null;
stderrExcerpt: string | null;
metadata: Record<string, unknown> | null;
startedAt: Date;
finishedAt: Date | null;
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,117 @@
export type ExecutionWorkspaceStrategyType =
| "project_primary"
| "git_worktree"
| "adapter_managed"
| "cloud_sandbox";
export type ProjectExecutionWorkspaceDefaultMode =
| "shared_workspace"
| "isolated_workspace"
| "operator_branch"
| "adapter_default";
export type ExecutionWorkspaceMode =
| "inherit"
| "shared_workspace"
| "isolated_workspace"
| "operator_branch"
| "reuse_existing"
| "agent_default";
export type ExecutionWorkspaceProviderType =
| "local_fs"
| "git_worktree"
| "adapter_managed"
| "cloud_sandbox";
export type ExecutionWorkspaceStatus =
| "active"
| "idle"
| "in_review"
| "archived"
| "cleanup_failed";
export interface ExecutionWorkspaceStrategy {
type: ExecutionWorkspaceStrategyType;
baseRef?: string | null;
branchTemplate?: string | null;
worktreeParentDir?: string | null;
provisionCommand?: string | null;
teardownCommand?: string | null;
}
export interface ProjectExecutionWorkspacePolicy {
enabled: boolean;
defaultMode?: ProjectExecutionWorkspaceDefaultMode;
allowIssueOverride?: boolean;
defaultProjectWorkspaceId?: string | null;
workspaceStrategy?: ExecutionWorkspaceStrategy | null;
workspaceRuntime?: Record<string, unknown> | null;
branchPolicy?: Record<string, unknown> | null;
pullRequestPolicy?: Record<string, unknown> | null;
runtimePolicy?: Record<string, unknown> | null;
cleanupPolicy?: Record<string, unknown> | null;
}
export interface IssueExecutionWorkspaceSettings {
mode?: ExecutionWorkspaceMode;
workspaceStrategy?: ExecutionWorkspaceStrategy | null;
workspaceRuntime?: Record<string, unknown> | null;
}
export interface ExecutionWorkspace {
id: string;
companyId: string;
projectId: string;
projectWorkspaceId: string | null;
sourceIssueId: string | null;
mode: Exclude<ExecutionWorkspaceMode, "inherit" | "reuse_existing" | "agent_default"> | "adapter_managed" | "cloud_sandbox";
strategyType: ExecutionWorkspaceStrategyType;
name: string;
status: ExecutionWorkspaceStatus;
cwd: string | null;
repoUrl: string | null;
baseRef: string | null;
branchName: string | null;
providerType: ExecutionWorkspaceProviderType;
providerRef: string | null;
derivedFromExecutionWorkspaceId: string | null;
lastUsedAt: Date;
openedAt: Date;
closedAt: Date | null;
cleanupEligibleAt: Date | null;
cleanupReason: string | null;
metadata: Record<string, unknown> | null;
createdAt: Date;
updatedAt: Date;
}
export interface WorkspaceRuntimeService {
id: string;
companyId: string;
projectId: string | null;
projectWorkspaceId: string | null;
executionWorkspaceId: string | null;
issueId: string | null;
scopeType: "project_workspace" | "execution_workspace" | "run" | "agent";
scopeId: string | null;
serviceName: string;
status: "starting" | "running" | "stopped" | "failed";
lifecycle: "shared" | "ephemeral";
reuseKey: string | null;
command: string | null;
cwd: string | null;
port: number | null;
url: string | null;
provider: "local_process" | "adapter_managed";
providerRef: string | null;
ownerAgentId: string | null;
startedByRunId: string | null;
lastUsedAt: Date;
startedAt: Date;
stoppedAt: Date | null;
stopPolicy: Record<string, unknown> | null;
healthStatus: "unknown" | "healthy" | "unhealthy";
createdAt: Date;
updatedAt: Date;
}
@@ -0,0 +1,92 @@
import { z } from "zod";
import {
AGENT_ADAPTER_TYPES,
INVITE_JOIN_TYPES,
JOIN_REQUEST_STATUSES,
JOIN_REQUEST_TYPES,
PERMISSION_KEYS,
} from "../constants.js";
export const createCompanyInviteSchema = z.object({
allowedJoinTypes: z.enum(INVITE_JOIN_TYPES).default("both"),
defaultsPayload: z.record(z.string(), z.unknown()).optional().nullable(),
agentMessage: z.string().max(4000).optional().nullable(),
});
export type CreateCompanyInvite = z.infer<typeof createCompanyInviteSchema>;
export const createOpenClawInvitePromptSchema = z.object({
agentMessage: z.string().max(4000).optional().nullable(),
});
export type CreateOpenClawInvitePrompt = z.infer<
typeof createOpenClawInvitePromptSchema
>;
export const acceptInviteSchema = z.object({
requestType: z.enum(JOIN_REQUEST_TYPES),
agentName: z.string().min(1).max(120).optional(),
adapterType: z.enum(AGENT_ADAPTER_TYPES).optional(),
capabilities: z.string().max(4000).optional().nullable(),
agentDefaultsPayload: z.record(z.string(), z.unknown()).optional().nullable(),
// OpenClaw join compatibility fields accepted at top level.
responsesWebhookUrl: z.string().max(4000).optional().nullable(),
responsesWebhookMethod: z.string().max(32).optional().nullable(),
responsesWebhookHeaders: z.record(z.string(), z.unknown()).optional().nullable(),
paperclipApiUrl: z.string().max(4000).optional().nullable(),
webhookAuthHeader: z.string().max(4000).optional().nullable(),
});
export type AcceptInvite = z.infer<typeof acceptInviteSchema>;
export const listJoinRequestsQuerySchema = z.object({
status: z.enum(JOIN_REQUEST_STATUSES).optional(),
requestType: z.enum(JOIN_REQUEST_TYPES).optional(),
});
export type ListJoinRequestsQuery = z.infer<typeof listJoinRequestsQuerySchema>;
export const claimJoinRequestApiKeySchema = z.object({
claimSecret: z.string().min(16).max(256),
});
export type ClaimJoinRequestApiKey = z.infer<typeof claimJoinRequestApiKeySchema>;
export const boardCliAuthAccessLevelSchema = z.enum([
"board",
"instance_admin_required",
]);
export type BoardCliAuthAccessLevel = z.infer<typeof boardCliAuthAccessLevelSchema>;
export const createCliAuthChallengeSchema = z.object({
command: z.string().min(1).max(240),
clientName: z.string().max(120).optional().nullable(),
requestedAccess: boardCliAuthAccessLevelSchema.default("board"),
requestedCompanyId: z.string().uuid().optional().nullable(),
});
export type CreateCliAuthChallenge = z.infer<typeof createCliAuthChallengeSchema>;
export const resolveCliAuthChallengeSchema = z.object({
token: z.string().min(16).max(256),
});
export type ResolveCliAuthChallenge = z.infer<typeof resolveCliAuthChallengeSchema>;
export const updateMemberPermissionsSchema = z.object({
grants: z.array(
z.object({
permissionKey: z.enum(PERMISSION_KEYS),
scope: z.record(z.string(), z.unknown()).optional().nullable(),
}),
),
});
export type UpdateMemberPermissions = z.infer<typeof updateMemberPermissionsSchema>;
export const updateUserCompanyAccessSchema = z.object({
companyIds: z.array(z.string().uuid()).default([]),
});
export type UpdateUserCompanyAccess = z.infer<typeof updateUserCompanyAccessSchema>;
@@ -0,0 +1,55 @@
import { z } from "zod";
export const agentSkillStateSchema = z.enum([
"available",
"configured",
"installed",
"missing",
"stale",
"external",
]);
export const agentSkillOriginSchema = z.enum([
"company_managed",
"paperclip_required",
"user_installed",
"external_unknown",
]);
export const agentSkillSyncModeSchema = z.enum([
"unsupported",
"persistent",
"ephemeral",
]);
export const agentSkillEntrySchema = z.object({
key: z.string().min(1),
runtimeName: z.string().min(1).nullable(),
desired: z.boolean(),
managed: z.boolean(),
required: z.boolean().optional(),
requiredReason: z.string().nullable().optional(),
state: agentSkillStateSchema,
origin: agentSkillOriginSchema.optional(),
originLabel: z.string().nullable().optional(),
locationLabel: z.string().nullable().optional(),
readOnly: z.boolean().optional(),
sourcePath: z.string().nullable().optional(),
targetPath: z.string().nullable().optional(),
detail: z.string().nullable().optional(),
});
export const agentSkillSnapshotSchema = z.object({
adapterType: z.string().min(1),
supported: z.boolean(),
mode: agentSkillSyncModeSchema,
desiredSkills: z.array(z.string().min(1)),
entries: z.array(agentSkillEntrySchema),
warnings: z.array(z.string()),
});
export const agentSkillSyncSchema = z.object({
desiredSkills: z.array(z.string().min(1)),
});
export type AgentSkillSync = z.infer<typeof agentSkillSyncSchema>;
@@ -0,0 +1,127 @@
import { z } from "zod";
import {
AGENT_ADAPTER_TYPES,
AGENT_ICON_NAMES,
AGENT_ROLES,
AGENT_STATUSES,
} from "../constants.js";
import { envConfigSchema } from "./secret.js";
export const agentPermissionsSchema = z.object({
canCreateAgents: z.boolean().optional().default(false),
});
export const agentInstructionsBundleModeSchema = z.enum(["managed", "external"]);
export const updateAgentInstructionsBundleSchema = z.object({
mode: agentInstructionsBundleModeSchema.optional(),
rootPath: z.string().trim().min(1).nullable().optional(),
entryFile: z.string().trim().min(1).optional(),
clearLegacyPromptTemplate: z.boolean().optional().default(false),
});
export type UpdateAgentInstructionsBundle = z.infer<typeof updateAgentInstructionsBundleSchema>;
export const upsertAgentInstructionsFileSchema = z.object({
path: z.string().trim().min(1),
content: z.string(),
clearLegacyPromptTemplate: z.boolean().optional().default(false),
});
export type UpsertAgentInstructionsFile = z.infer<typeof upsertAgentInstructionsFileSchema>;
const adapterConfigSchema = z.record(z.unknown()).superRefine((value, ctx) => {
const envValue = value.env;
if (envValue === undefined) return;
const parsed = envConfigSchema.safeParse(envValue);
if (!parsed.success) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "adapterConfig.env must be a map of valid env bindings",
path: ["env"],
});
}
});
export const createAgentSchema = z.object({
name: z.string().min(1),
role: z.enum(AGENT_ROLES).optional().default("general"),
title: z.string().optional().nullable(),
icon: z.enum(AGENT_ICON_NAMES).optional().nullable(),
reportsTo: z.string().uuid().optional().nullable(),
capabilities: z.string().optional().nullable(),
desiredSkills: z.array(z.string().min(1)).optional(),
adapterType: z.enum(AGENT_ADAPTER_TYPES).optional().default("process"),
adapterConfig: adapterConfigSchema.optional().default({}),
runtimeConfig: z.record(z.unknown()).optional().default({}),
budgetMonthlyCents: z.number().int().nonnegative().optional().default(0),
permissions: agentPermissionsSchema.optional(),
metadata: z.record(z.unknown()).optional().nullable(),
});
export type CreateAgent = z.infer<typeof createAgentSchema>;
export const createAgentHireSchema = createAgentSchema.extend({
sourceIssueId: z.string().uuid().optional().nullable(),
sourceIssueIds: z.array(z.string().uuid()).optional(),
});
export type CreateAgentHire = z.infer<typeof createAgentHireSchema>;
export const updateAgentSchema = createAgentSchema
.omit({ permissions: true })
.partial()
.extend({
permissions: z.never().optional(),
replaceAdapterConfig: z.boolean().optional(),
status: z.enum(AGENT_STATUSES).optional(),
spentMonthlyCents: z.number().int().nonnegative().optional(),
});
export type UpdateAgent = z.infer<typeof updateAgentSchema>;
export const updateAgentInstructionsPathSchema = z.object({
path: z.string().trim().min(1).nullable(),
adapterConfigKey: z.string().trim().min(1).optional(),
});
export type UpdateAgentInstructionsPath = z.infer<typeof updateAgentInstructionsPathSchema>;
export const createAgentKeySchema = z.object({
name: z.string().min(1).default("default"),
});
export type CreateAgentKey = z.infer<typeof createAgentKeySchema>;
export const wakeAgentSchema = z.object({
source: z.enum(["timer", "assignment", "on_demand", "automation"]).optional().default("on_demand"),
triggerDetail: z.enum(["manual", "ping", "callback", "system"]).optional(),
reason: z.string().optional().nullable(),
payload: z.record(z.unknown()).optional().nullable(),
idempotencyKey: z.string().optional().nullable(),
forceFreshSession: z.preprocess(
(value) => (value === null ? undefined : value),
z.boolean().optional().default(false),
),
});
export type WakeAgent = z.infer<typeof wakeAgentSchema>;
export const resetAgentSessionSchema = z.object({
taskKey: z.string().min(1).optional().nullable(),
});
export type ResetAgentSession = z.infer<typeof resetAgentSessionSchema>;
export const testAdapterEnvironmentSchema = z.object({
adapterConfig: adapterConfigSchema.optional().default({}),
});
export type TestAdapterEnvironment = z.infer<typeof testAdapterEnvironmentSchema>;
export const updateAgentPermissionsSchema = z.object({
canCreateAgents: z.boolean(),
canAssignTasks: z.boolean(),
});
export type UpdateAgentPermissions = z.infer<typeof updateAgentPermissionsSchema>;
@@ -0,0 +1,37 @@
import { z } from "zod";
import { APPROVAL_TYPES } from "../constants.js";
export const createApprovalSchema = z.object({
type: z.enum(APPROVAL_TYPES),
requestedByAgentId: z.string().uuid().optional().nullable(),
payload: z.record(z.unknown()),
issueIds: z.array(z.string().uuid()).optional(),
});
export type CreateApproval = z.infer<typeof createApprovalSchema>;
export const resolveApprovalSchema = z.object({
decisionNote: z.string().optional().nullable(),
decidedByUserId: z.string().optional().default("board"),
});
export type ResolveApproval = z.infer<typeof resolveApprovalSchema>;
export const requestApprovalRevisionSchema = z.object({
decisionNote: z.string().optional().nullable(),
decidedByUserId: z.string().optional().default("board"),
});
export type RequestApprovalRevision = z.infer<typeof requestApprovalRevisionSchema>;
export const resubmitApprovalSchema = z.object({
payload: z.record(z.unknown()).optional(),
});
export type ResubmitApproval = z.infer<typeof resubmitApprovalSchema>;
export const addApprovalCommentSchema = z.object({
body: z.string().min(1),
});
export type AddApprovalComment = z.infer<typeof addApprovalCommentSchema>;
@@ -0,0 +1,14 @@
import { z } from "zod";
export const createAssetImageMetadataSchema = z.object({
namespace: z
.string()
.trim()
.min(1)
.max(120)
.regex(/^[a-zA-Z0-9/_-]+$/)
.optional(),
});
export type CreateAssetImageMetadata = z.infer<typeof createAssetImageMetadataSchema>;
@@ -0,0 +1,37 @@
import { z } from "zod";
import {
BUDGET_INCIDENT_RESOLUTION_ACTIONS,
BUDGET_METRICS,
BUDGET_SCOPE_TYPES,
BUDGET_WINDOW_KINDS,
} from "../constants.js";
export const upsertBudgetPolicySchema = z.object({
scopeType: z.enum(BUDGET_SCOPE_TYPES),
scopeId: z.string().uuid(),
metric: z.enum(BUDGET_METRICS).optional().default("billed_cents"),
windowKind: z.enum(BUDGET_WINDOW_KINDS).optional().default("calendar_month_utc"),
amount: z.number().int().nonnegative(),
warnPercent: z.number().int().min(1).max(99).optional().default(80),
hardStopEnabled: z.boolean().optional().default(true),
notifyEnabled: z.boolean().optional().default(true),
isActive: z.boolean().optional().default(true),
});
export type UpsertBudgetPolicy = z.infer<typeof upsertBudgetPolicySchema>;
export const resolveBudgetIncidentSchema = z.object({
action: z.enum(BUDGET_INCIDENT_RESOLUTION_ACTIONS),
amount: z.number().int().nonnegative().optional(),
decisionNote: z.string().optional().nullable(),
}).superRefine((value, ctx) => {
if (value.action === "raise_budget_and_resume" && typeof value.amount !== "number") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "amount is required when raising a budget",
path: ["amount"],
});
}
});
export type ResolveBudgetIncident = z.infer<typeof resolveBudgetIncidentSchema>;
@@ -0,0 +1,236 @@
import { z } from "zod";
export const portabilityIncludeSchema = z
.object({
company: z.boolean().optional(),
agents: z.boolean().optional(),
projects: z.boolean().optional(),
issues: z.boolean().optional(),
skills: z.boolean().optional(),
})
.partial();
export const portabilityEnvInputSchema = z.object({
key: z.string().min(1),
description: z.string().nullable(),
agentSlug: z.string().min(1).nullable(),
kind: z.enum(["secret", "plain"]),
requirement: z.enum(["required", "optional"]),
defaultValue: z.string().nullable(),
portability: z.enum(["portable", "system_dependent"]),
});
export const portabilityFileEntrySchema = z.union([
z.string(),
z.object({
encoding: z.literal("base64"),
data: z.string(),
contentType: z.string().min(1).optional().nullable(),
}),
]);
export const portabilityCompanyManifestEntrySchema = z.object({
path: z.string().min(1),
name: z.string().min(1),
description: z.string().nullable(),
brandColor: z.string().nullable(),
logoPath: z.string().nullable(),
requireBoardApprovalForNewAgents: z.boolean(),
});
export const portabilitySidebarOrderSchema = z.object({
agents: z.array(z.string().min(1)).default([]),
projects: z.array(z.string().min(1)).default([]),
});
export const portabilityAgentManifestEntrySchema = z.object({
slug: z.string().min(1),
name: z.string().min(1),
path: z.string().min(1),
skills: z.array(z.string().min(1)).default([]),
role: z.string().min(1),
title: z.string().nullable(),
icon: z.string().nullable(),
capabilities: z.string().nullable(),
reportsToSlug: z.string().min(1).nullable(),
adapterType: z.string().min(1),
adapterConfig: z.record(z.unknown()),
runtimeConfig: z.record(z.unknown()),
permissions: z.record(z.unknown()),
budgetMonthlyCents: z.number().int().nonnegative(),
metadata: z.record(z.unknown()).nullable(),
});
export const portabilitySkillManifestEntrySchema = z.object({
key: z.string().min(1),
slug: z.string().min(1),
name: z.string().min(1),
path: z.string().min(1),
description: z.string().nullable(),
sourceType: z.string().min(1),
sourceLocator: z.string().nullable(),
sourceRef: z.string().nullable(),
trustLevel: z.string().nullable(),
compatibility: z.string().nullable(),
metadata: z.record(z.unknown()).nullable(),
fileInventory: z.array(z.object({
path: z.string().min(1),
kind: z.string().min(1),
})).default([]),
});
export const portabilityProjectManifestEntrySchema = z.object({
slug: z.string().min(1),
name: z.string().min(1),
path: z.string().min(1),
description: z.string().nullable(),
ownerAgentSlug: z.string().min(1).nullable(),
leadAgentSlug: z.string().min(1).nullable(),
targetDate: z.string().nullable(),
color: z.string().nullable(),
status: z.string().nullable(),
executionWorkspacePolicy: z.record(z.unknown()).nullable(),
workspaces: z.array(z.object({
key: z.string().min(1),
name: z.string().min(1),
sourceType: z.string().nullable(),
repoUrl: z.string().nullable(),
repoRef: z.string().nullable(),
defaultRef: z.string().nullable(),
visibility: z.string().nullable(),
setupCommand: z.string().nullable(),
cleanupCommand: z.string().nullable(),
metadata: z.record(z.unknown()).nullable(),
isPrimary: z.boolean(),
})).default([]),
metadata: z.record(z.unknown()).nullable(),
});
export const portabilityIssueRoutineTriggerManifestEntrySchema = z.object({
kind: z.string().min(1),
label: z.string().nullable(),
enabled: z.boolean(),
cronExpression: z.string().nullable(),
timezone: z.string().nullable(),
signingMode: z.string().nullable(),
replayWindowSec: z.number().int().nullable(),
});
export const portabilityIssueRoutineManifestEntrySchema = z.object({
concurrencyPolicy: z.string().nullable(),
catchUpPolicy: z.string().nullable(),
triggers: z.array(portabilityIssueRoutineTriggerManifestEntrySchema).default([]),
});
export const portabilityIssueManifestEntrySchema = z.object({
slug: z.string().min(1),
identifier: z.string().min(1).nullable(),
title: z.string().min(1),
path: z.string().min(1),
projectSlug: z.string().min(1).nullable(),
projectWorkspaceKey: z.string().min(1).nullable(),
assigneeAgentSlug: z.string().min(1).nullable(),
description: z.string().nullable(),
recurring: z.boolean().default(false),
routine: portabilityIssueRoutineManifestEntrySchema.nullable(),
legacyRecurrence: z.record(z.unknown()).nullable(),
status: z.string().nullable(),
priority: z.string().nullable(),
labelIds: z.array(z.string().min(1)).default([]),
billingCode: z.string().nullable(),
executionWorkspaceSettings: z.record(z.unknown()).nullable(),
assigneeAdapterOverrides: z.record(z.unknown()).nullable(),
metadata: z.record(z.unknown()).nullable(),
});
export const portabilityManifestSchema = z.object({
schemaVersion: z.number().int().positive(),
generatedAt: z.string().datetime(),
source: z
.object({
companyId: z.string().uuid(),
companyName: z.string().min(1),
})
.nullable(),
includes: z.object({
company: z.boolean(),
agents: z.boolean(),
projects: z.boolean(),
issues: z.boolean(),
skills: z.boolean(),
}),
company: portabilityCompanyManifestEntrySchema.nullable(),
sidebar: portabilitySidebarOrderSchema.nullable(),
agents: z.array(portabilityAgentManifestEntrySchema),
skills: z.array(portabilitySkillManifestEntrySchema).default([]),
projects: z.array(portabilityProjectManifestEntrySchema).default([]),
issues: z.array(portabilityIssueManifestEntrySchema).default([]),
envInputs: z.array(portabilityEnvInputSchema).default([]),
});
export const portabilitySourceSchema = z.discriminatedUnion("type", [
z.object({
type: z.literal("inline"),
rootPath: z.string().min(1).optional().nullable(),
files: z.record(portabilityFileEntrySchema),
}),
z.object({
type: z.literal("github"),
url: z.string().url(),
}),
]);
export const portabilityTargetSchema = z.discriminatedUnion("mode", [
z.object({
mode: z.literal("new_company"),
newCompanyName: z.string().min(1).optional().nullable(),
}),
z.object({
mode: z.literal("existing_company"),
companyId: z.string().uuid(),
}),
]);
export const portabilityAgentSelectionSchema = z.union([
z.literal("all"),
z.array(z.string().min(1)),
]);
export const portabilityCollisionStrategySchema = z.enum(["rename", "skip", "replace"]);
export const companyPortabilityExportSchema = z.object({
include: portabilityIncludeSchema.optional(),
agents: z.array(z.string().min(1)).optional(),
skills: z.array(z.string().min(1)).optional(),
projects: z.array(z.string().min(1)).optional(),
issues: z.array(z.string().min(1)).optional(),
projectIssues: z.array(z.string().min(1)).optional(),
selectedFiles: z.array(z.string().min(1)).optional(),
expandReferencedSkills: z.boolean().optional(),
sidebarOrder: portabilitySidebarOrderSchema.partial().optional(),
});
export type CompanyPortabilityExport = z.infer<typeof companyPortabilityExportSchema>;
export const companyPortabilityPreviewSchema = z.object({
source: portabilitySourceSchema,
include: portabilityIncludeSchema.optional(),
target: portabilityTargetSchema,
agents: portabilityAgentSelectionSchema.optional(),
collisionStrategy: portabilityCollisionStrategySchema.optional(),
nameOverrides: z.record(z.string().min(1), z.string().min(1)).optional(),
selectedFiles: z.array(z.string().min(1)).optional(),
});
export type CompanyPortabilityPreview = z.infer<typeof companyPortabilityPreviewSchema>;
export const portabilityAdapterOverrideSchema = z.object({
adapterType: z.string().min(1),
adapterConfig: z.record(z.unknown()).optional(),
});
export const companyPortabilityImportSchema = companyPortabilityPreviewSchema.extend({
adapterOverrides: z.record(z.string().min(1), portabilityAdapterOverrideSchema).optional(),
});
export type CompanyPortabilityImport = z.infer<typeof companyPortabilityImportSchema>;
@@ -0,0 +1,135 @@
import { z } from "zod";
export const companySkillSourceTypeSchema = z.enum(["local_path", "github", "url", "catalog", "skills_sh"]);
export const companySkillTrustLevelSchema = z.enum(["markdown_only", "assets", "scripts_executables"]);
export const companySkillCompatibilitySchema = z.enum(["compatible", "unknown", "invalid"]);
export const companySkillSourceBadgeSchema = z.enum(["paperclip", "github", "local", "url", "catalog", "skills_sh"]);
export const companySkillFileInventoryEntrySchema = z.object({
path: z.string().min(1),
kind: z.enum(["skill", "markdown", "reference", "script", "asset", "other"]),
});
export const companySkillSchema = z.object({
id: z.string().uuid(),
companyId: z.string().uuid(),
key: z.string().min(1),
slug: z.string().min(1),
name: z.string().min(1),
description: z.string().nullable(),
markdown: z.string(),
sourceType: companySkillSourceTypeSchema,
sourceLocator: z.string().nullable(),
sourceRef: z.string().nullable(),
trustLevel: companySkillTrustLevelSchema,
compatibility: companySkillCompatibilitySchema,
fileInventory: z.array(companySkillFileInventoryEntrySchema).default([]),
metadata: z.record(z.unknown()).nullable(),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
});
export const companySkillListItemSchema = companySkillSchema.extend({
attachedAgentCount: z.number().int().nonnegative(),
editable: z.boolean(),
editableReason: z.string().nullable(),
sourceLabel: z.string().nullable(),
sourceBadge: companySkillSourceBadgeSchema,
});
export const companySkillUsageAgentSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
urlKey: z.string().min(1),
adapterType: z.string().min(1),
desired: z.boolean(),
actualState: z.string().nullable(),
});
export const companySkillDetailSchema = companySkillSchema.extend({
attachedAgentCount: z.number().int().nonnegative(),
usedByAgents: z.array(companySkillUsageAgentSchema).default([]),
editable: z.boolean(),
editableReason: z.string().nullable(),
sourceLabel: z.string().nullable(),
sourceBadge: companySkillSourceBadgeSchema,
});
export const companySkillUpdateStatusSchema = z.object({
supported: z.boolean(),
reason: z.string().nullable(),
trackingRef: z.string().nullable(),
currentRef: z.string().nullable(),
latestRef: z.string().nullable(),
hasUpdate: z.boolean(),
});
export const companySkillImportSchema = z.object({
source: z.string().min(1),
});
export const companySkillProjectScanRequestSchema = z.object({
projectIds: z.array(z.string().uuid()).optional(),
workspaceIds: z.array(z.string().uuid()).optional(),
});
export const companySkillProjectScanSkippedSchema = z.object({
projectId: z.string().uuid(),
projectName: z.string().min(1),
workspaceId: z.string().uuid().nullable(),
workspaceName: z.string().nullable(),
path: z.string().nullable(),
reason: z.string().min(1),
});
export const companySkillProjectScanConflictSchema = z.object({
slug: z.string().min(1),
key: z.string().min(1),
projectId: z.string().uuid(),
projectName: z.string().min(1),
workspaceId: z.string().uuid(),
workspaceName: z.string().min(1),
path: z.string().min(1),
existingSkillId: z.string().uuid(),
existingSkillKey: z.string().min(1),
existingSourceLocator: z.string().nullable(),
reason: z.string().min(1),
});
export const companySkillProjectScanResultSchema = z.object({
scannedProjects: z.number().int().nonnegative(),
scannedWorkspaces: z.number().int().nonnegative(),
discovered: z.number().int().nonnegative(),
imported: z.array(companySkillSchema),
updated: z.array(companySkillSchema),
skipped: z.array(companySkillProjectScanSkippedSchema),
conflicts: z.array(companySkillProjectScanConflictSchema),
warnings: z.array(z.string()),
});
export const companySkillCreateSchema = z.object({
name: z.string().min(1),
slug: z.string().min(1).nullable().optional(),
description: z.string().nullable().optional(),
markdown: z.string().nullable().optional(),
});
export const companySkillFileDetailSchema = z.object({
skillId: z.string().uuid(),
path: z.string().min(1),
kind: z.enum(["skill", "markdown", "reference", "script", "asset", "other"]),
content: z.string(),
language: z.string().nullable(),
markdown: z.boolean(),
editable: z.boolean(),
});
export const companySkillFileUpdateSchema = z.object({
path: z.string().min(1),
content: z.string(),
});
export type CompanySkillImport = z.infer<typeof companySkillImportSchema>;
export type CompanySkillProjectScan = z.infer<typeof companySkillProjectScanRequestSchema>;
export type CompanySkillCreate = z.infer<typeof companySkillCreateSchema>;
export type CompanySkillFileUpdate = z.infer<typeof companySkillFileUpdateSchema>;
@@ -0,0 +1,44 @@
import { z } from "zod";
import { COMPANY_STATUSES } from "../constants.js";
const logoAssetIdSchema = z.string().uuid().nullable().optional();
const brandColorSchema = z.string().regex(/^#[0-9a-fA-F]{6}$/).nullable().optional();
export const createCompanySchema = z.object({
name: z.string().min(1),
description: z.string().optional().nullable(),
budgetMonthlyCents: z.number().int().nonnegative().optional().default(0),
});
export type CreateCompany = z.infer<typeof createCompanySchema>;
export const updateCompanySchema = createCompanySchema
.partial()
.extend({
status: z.enum(COMPANY_STATUSES).optional(),
spentMonthlyCents: z.number().int().nonnegative().optional(),
requireBoardApprovalForNewAgents: z.boolean().optional(),
brandColor: brandColorSchema,
logoAssetId: logoAssetIdSchema,
});
export type UpdateCompany = z.infer<typeof updateCompanySchema>;
export const updateCompanyBrandingSchema = z
.object({
name: z.string().min(1).optional(),
description: z.string().nullable().optional(),
brandColor: brandColorSchema,
logoAssetId: logoAssetIdSchema,
})
.strict()
.refine(
(value) =>
value.name !== undefined
|| value.description !== undefined
|| value.brandColor !== undefined
|| value.logoAssetId !== undefined,
"At least one branding field must be provided",
);
export type UpdateCompanyBranding = z.infer<typeof updateCompanyBrandingSchema>;
@@ -0,0 +1,31 @@
import { z } from "zod";
import { BILLING_TYPES } from "../constants.js";
export const createCostEventSchema = z.object({
agentId: z.string().uuid(),
issueId: z.string().uuid().optional().nullable(),
projectId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
heartbeatRunId: z.string().uuid().optional().nullable(),
billingCode: z.string().optional().nullable(),
provider: z.string().min(1),
biller: z.string().min(1).optional(),
billingType: z.enum(BILLING_TYPES).optional().default("unknown"),
model: z.string().min(1),
inputTokens: z.number().int().nonnegative().optional().default(0),
cachedInputTokens: z.number().int().nonnegative().optional().default(0),
outputTokens: z.number().int().nonnegative().optional().default(0),
costCents: z.number().int().nonnegative(),
occurredAt: z.string().datetime(),
}).transform((value) => ({
...value,
biller: value.biller ?? value.provider,
}));
export type CreateCostEvent = z.infer<typeof createCostEventSchema>;
export const updateBudgetSchema = z.object({
budgetMonthlyCents: z.number().int().nonnegative(),
});
export type UpdateBudget = z.infer<typeof updateBudgetSchema>;
@@ -0,0 +1,18 @@
import { z } from "zod";
export const executionWorkspaceStatusSchema = z.enum([
"active",
"idle",
"in_review",
"archived",
"cleanup_failed",
]);
export const updateExecutionWorkspaceSchema = z.object({
status: executionWorkspaceStatusSchema.optional(),
cleanupEligibleAt: z.string().datetime().optional().nullable(),
cleanupReason: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
}).strict();
export type UpdateExecutionWorkspace = z.infer<typeof updateExecutionWorkspaceSchema>;
@@ -0,0 +1,34 @@
import { z } from "zod";
import { AGENT_ADAPTER_TYPES, FINANCE_DIRECTIONS, FINANCE_EVENT_KINDS, FINANCE_UNITS } from "../constants.js";
export const createFinanceEventSchema = z.object({
agentId: z.string().uuid().optional().nullable(),
issueId: z.string().uuid().optional().nullable(),
projectId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
heartbeatRunId: z.string().uuid().optional().nullable(),
costEventId: z.string().uuid().optional().nullable(),
billingCode: z.string().optional().nullable(),
description: z.string().max(500).optional().nullable(),
eventKind: z.enum(FINANCE_EVENT_KINDS),
direction: z.enum(FINANCE_DIRECTIONS).optional().default("debit"),
biller: z.string().min(1),
provider: z.string().min(1).optional().nullable(),
executionAdapterType: z.enum(AGENT_ADAPTER_TYPES).optional().nullable(),
pricingTier: z.string().min(1).optional().nullable(),
region: z.string().min(1).optional().nullable(),
model: z.string().min(1).optional().nullable(),
quantity: z.number().int().nonnegative().optional().nullable(),
unit: z.enum(FINANCE_UNITS).optional().nullable(),
amountCents: z.number().int().nonnegative(),
currency: z.string().length(3).optional().default("USD"),
estimated: z.boolean().optional().default(false),
externalInvoiceId: z.string().optional().nullable(),
metadataJson: z.record(z.string(), z.unknown()).optional().nullable(),
occurredAt: z.string().datetime(),
}).transform((value) => ({
...value,
currency: value.currency.toUpperCase(),
}));
export type CreateFinanceEvent = z.infer<typeof createFinanceEventSchema>;
@@ -0,0 +1,17 @@
import { z } from "zod";
import { GOAL_LEVELS, GOAL_STATUSES } from "../constants.js";
export const createGoalSchema = z.object({
title: z.string().min(1),
description: z.string().optional().nullable(),
level: z.enum(GOAL_LEVELS).optional().default("task"),
status: z.enum(GOAL_STATUSES).optional().default("planned"),
parentId: z.string().uuid().optional().nullable(),
ownerAgentId: z.string().uuid().optional().nullable(),
});
export type CreateGoal = z.infer<typeof createGoalSchema>;
export const updateGoalSchema = createGoalSchema.partial();
export type UpdateGoal = z.infer<typeof updateGoalSchema>;
@@ -0,0 +1,281 @@
export {
instanceGeneralSettingsSchema,
patchInstanceGeneralSettingsSchema,
type InstanceGeneralSettings,
type PatchInstanceGeneralSettings,
instanceExperimentalSettingsSchema,
patchInstanceExperimentalSettingsSchema,
type InstanceExperimentalSettings,
type PatchInstanceExperimentalSettings,
} from "./instance.js";
export {
upsertBudgetPolicySchema,
resolveBudgetIncidentSchema,
type UpsertBudgetPolicy,
type ResolveBudgetIncident,
} from "./budget.js";
export {
createCompanySchema,
updateCompanySchema,
updateCompanyBrandingSchema,
type CreateCompany,
type UpdateCompany,
type UpdateCompanyBranding,
} from "./company.js";
export {
companySkillSourceTypeSchema,
companySkillTrustLevelSchema,
companySkillCompatibilitySchema,
companySkillSourceBadgeSchema,
companySkillFileInventoryEntrySchema,
companySkillSchema,
companySkillListItemSchema,
companySkillUsageAgentSchema,
companySkillDetailSchema,
companySkillUpdateStatusSchema,
companySkillImportSchema,
companySkillProjectScanRequestSchema,
companySkillProjectScanSkippedSchema,
companySkillProjectScanConflictSchema,
companySkillProjectScanResultSchema,
companySkillCreateSchema,
companySkillFileDetailSchema,
companySkillFileUpdateSchema,
type CompanySkillImport,
type CompanySkillProjectScan,
type CompanySkillCreate,
type CompanySkillFileUpdate,
} from "./company-skill.js";
export {
agentSkillStateSchema,
agentSkillSyncModeSchema,
agentSkillEntrySchema,
agentSkillSnapshotSchema,
agentSkillSyncSchema,
type AgentSkillSync,
} from "./adapter-skills.js";
export {
portabilityIncludeSchema,
portabilityEnvInputSchema,
portabilityCompanyManifestEntrySchema,
portabilitySidebarOrderSchema,
portabilityAgentManifestEntrySchema,
portabilitySkillManifestEntrySchema,
portabilityManifestSchema,
portabilitySourceSchema,
portabilityTargetSchema,
portabilityAgentSelectionSchema,
portabilityCollisionStrategySchema,
companyPortabilityExportSchema,
companyPortabilityPreviewSchema,
companyPortabilityImportSchema,
type CompanyPortabilityExport,
type CompanyPortabilityPreview,
type CompanyPortabilityImport,
} from "./company-portability.js";
export {
createAgentSchema,
createAgentHireSchema,
updateAgentSchema,
agentInstructionsBundleModeSchema,
updateAgentInstructionsBundleSchema,
upsertAgentInstructionsFileSchema,
updateAgentInstructionsPathSchema,
createAgentKeySchema,
wakeAgentSchema,
resetAgentSessionSchema,
testAdapterEnvironmentSchema,
agentPermissionsSchema,
updateAgentPermissionsSchema,
type CreateAgent,
type CreateAgentHire,
type UpdateAgent,
type UpdateAgentInstructionsBundle,
type UpsertAgentInstructionsFile,
type UpdateAgentInstructionsPath,
type CreateAgentKey,
type WakeAgent,
type ResetAgentSession,
type TestAdapterEnvironment,
type UpdateAgentPermissions,
} from "./agent.js";
export {
createProjectSchema,
updateProjectSchema,
createProjectWorkspaceSchema,
updateProjectWorkspaceSchema,
projectExecutionWorkspacePolicySchema,
type CreateProject,
type UpdateProject,
type CreateProjectWorkspace,
type UpdateProjectWorkspace,
type ProjectExecutionWorkspacePolicy,
} from "./project.js";
export {
createIssueSchema,
createIssueLabelSchema,
updateIssueSchema,
issueExecutionWorkspaceSettingsSchema,
checkoutIssueSchema,
addIssueCommentSchema,
linkIssueApprovalSchema,
createIssueAttachmentMetadataSchema,
issueDocumentFormatSchema,
issueDocumentKeySchema,
upsertIssueDocumentSchema,
type CreateIssue,
type CreateIssueLabel,
type UpdateIssue,
type IssueExecutionWorkspaceSettings,
type CheckoutIssue,
type AddIssueComment,
type LinkIssueApproval,
type CreateIssueAttachmentMetadata,
type IssueDocumentFormat,
type UpsertIssueDocument,
} from "./issue.js";
export {
createIssueWorkProductSchema,
updateIssueWorkProductSchema,
issueWorkProductTypeSchema,
issueWorkProductStatusSchema,
issueWorkProductReviewStateSchema,
type CreateIssueWorkProduct,
type UpdateIssueWorkProduct,
} from "./work-product.js";
export {
updateExecutionWorkspaceSchema,
executionWorkspaceStatusSchema,
type UpdateExecutionWorkspace,
} from "./execution-workspace.js";
export {
createGoalSchema,
updateGoalSchema,
type CreateGoal,
type UpdateGoal,
} from "./goal.js";
export {
createApprovalSchema,
resolveApprovalSchema,
requestApprovalRevisionSchema,
resubmitApprovalSchema,
addApprovalCommentSchema,
type CreateApproval,
type ResolveApproval,
type RequestApprovalRevision,
type ResubmitApproval,
type AddApprovalComment,
} from "./approval.js";
export {
envBindingPlainSchema,
envBindingSecretRefSchema,
envBindingSchema,
envConfigSchema,
createSecretSchema,
rotateSecretSchema,
updateSecretSchema,
type CreateSecret,
type RotateSecret,
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,
type CreateCostEvent,
type UpdateBudget,
} from "./cost.js";
export {
createFinanceEventSchema,
type CreateFinanceEvent,
} from "./finance.js";
export {
createAssetImageMetadataSchema,
type CreateAssetImageMetadata,
} from "./asset.js";
export {
createCompanyInviteSchema,
createOpenClawInvitePromptSchema,
acceptInviteSchema,
listJoinRequestsQuerySchema,
claimJoinRequestApiKeySchema,
boardCliAuthAccessLevelSchema,
createCliAuthChallengeSchema,
resolveCliAuthChallengeSchema,
updateMemberPermissionsSchema,
updateUserCompanyAccessSchema,
type CreateCompanyInvite,
type CreateOpenClawInvitePrompt,
type AcceptInvite,
type ListJoinRequestsQuery,
type ClaimJoinRequestApiKey,
type BoardCliAuthAccessLevel,
type CreateCliAuthChallenge,
type ResolveCliAuthChallenge,
type UpdateMemberPermissions,
type UpdateUserCompanyAccess,
} from "./access.js";
export {
jsonSchemaSchema,
pluginJobDeclarationSchema,
pluginWebhookDeclarationSchema,
pluginToolDeclarationSchema,
pluginUiSlotDeclarationSchema,
pluginLauncherActionDeclarationSchema,
pluginLauncherRenderDeclarationSchema,
pluginLauncherDeclarationSchema,
pluginManifestV1Schema,
installPluginSchema,
upsertPluginConfigSchema,
patchPluginConfigSchema,
updatePluginStatusSchema,
uninstallPluginSchema,
pluginStateScopeKeySchema,
setPluginStateSchema,
listPluginStateSchema,
type PluginJobDeclarationInput,
type PluginWebhookDeclarationInput,
type PluginToolDeclarationInput,
type PluginUiSlotDeclarationInput,
type PluginLauncherActionDeclarationInput,
type PluginLauncherRenderDeclarationInput,
type PluginLauncherDeclarationInput,
type PluginManifestV1Input,
type InstallPlugin,
type UpsertPluginConfig,
type PatchPluginConfig,
type UpdatePluginStatus,
type UninstallPlugin,
type PluginStateScopeKey,
type SetPluginState,
type ListPluginState,
} from "./plugin.js";
@@ -0,0 +1,19 @@
import { z } from "zod";
export const instanceGeneralSettingsSchema = z.object({
censorUsernameInLogs: z.boolean().default(false),
}).strict();
export const patchInstanceGeneralSettingsSchema = instanceGeneralSettingsSchema.partial();
export const instanceExperimentalSettingsSchema = z.object({
enableIsolatedWorkspaces: z.boolean().default(false),
autoRestartDevServerWhenIdle: z.boolean().default(false),
}).strict();
export const patchInstanceExperimentalSettingsSchema = instanceExperimentalSettingsSchema.partial();
export type InstanceGeneralSettings = z.infer<typeof instanceGeneralSettingsSchema>;
export type PatchInstanceGeneralSettings = z.infer<typeof patchInstanceGeneralSettingsSchema>;
export type InstanceExperimentalSettings = z.infer<typeof instanceExperimentalSettingsSchema>;
export type PatchInstanceExperimentalSettings = z.infer<typeof patchInstanceExperimentalSettingsSchema>;
@@ -0,0 +1,122 @@
import { z } from "zod";
import { ISSUE_PRIORITIES, ISSUE_STATUSES } from "../constants.js";
const executionWorkspaceStrategySchema = z
.object({
type: z.enum(["project_primary", "git_worktree", "adapter_managed", "cloud_sandbox"]).optional(),
baseRef: z.string().optional().nullable(),
branchTemplate: z.string().optional().nullable(),
worktreeParentDir: z.string().optional().nullable(),
provisionCommand: z.string().optional().nullable(),
teardownCommand: z.string().optional().nullable(),
})
.strict();
export const issueExecutionWorkspaceSettingsSchema = z
.object({
mode: z.enum(["inherit", "shared_workspace", "isolated_workspace", "operator_branch", "reuse_existing", "agent_default"]).optional(),
workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(),
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
})
.strict();
export const issueAssigneeAdapterOverridesSchema = z
.object({
adapterConfig: z.record(z.unknown()).optional(),
useProjectWorkspace: z.boolean().optional(),
})
.strict();
export const createIssueSchema = z.object({
projectId: z.string().uuid().optional().nullable(),
projectWorkspaceId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
parentId: z.string().uuid().optional().nullable(),
title: z.string().min(1),
description: z.string().optional().nullable(),
status: z.enum(ISSUE_STATUSES).optional().default("backlog"),
priority: z.enum(ISSUE_PRIORITIES).optional().default("medium"),
assigneeAgentId: z.string().uuid().optional().nullable(),
assigneeUserId: z.string().optional().nullable(),
requestDepth: z.number().int().nonnegative().optional().default(0),
billingCode: z.string().optional().nullable(),
assigneeAdapterOverrides: issueAssigneeAdapterOverridesSchema.optional().nullable(),
executionWorkspaceId: z.string().uuid().optional().nullable(),
executionWorkspacePreference: z.enum([
"inherit",
"shared_workspace",
"isolated_workspace",
"operator_branch",
"reuse_existing",
"agent_default",
]).optional().nullable(),
executionWorkspaceSettings: issueExecutionWorkspaceSettingsSchema.optional().nullable(),
labelIds: z.array(z.string().uuid()).optional(),
});
export type CreateIssue = z.infer<typeof createIssueSchema>;
export const createIssueLabelSchema = z.object({
name: z.string().trim().min(1).max(48),
color: z.string().regex(/^#(?:[0-9a-fA-F]{6})$/, "Color must be a 6-digit hex value"),
});
export type CreateIssueLabel = z.infer<typeof createIssueLabelSchema>;
export const updateIssueSchema = createIssueSchema.partial().extend({
comment: z.string().min(1).optional(),
reopen: z.boolean().optional(),
hiddenAt: z.string().datetime().nullable().optional(),
});
export type UpdateIssue = z.infer<typeof updateIssueSchema>;
export type IssueExecutionWorkspaceSettings = z.infer<typeof issueExecutionWorkspaceSettingsSchema>;
export const checkoutIssueSchema = z.object({
agentId: z.string().uuid(),
expectedStatuses: z.array(z.enum(ISSUE_STATUSES)).nonempty(),
});
export type CheckoutIssue = z.infer<typeof checkoutIssueSchema>;
export const addIssueCommentSchema = z.object({
body: z.string().min(1),
reopen: z.boolean().optional(),
interrupt: z.boolean().optional(),
});
export type AddIssueComment = z.infer<typeof addIssueCommentSchema>;
export const linkIssueApprovalSchema = z.object({
approvalId: z.string().uuid(),
});
export type LinkIssueApproval = z.infer<typeof linkIssueApprovalSchema>;
export const createIssueAttachmentMetadataSchema = z.object({
issueCommentId: z.string().uuid().optional().nullable(),
});
export type CreateIssueAttachmentMetadata = z.infer<typeof createIssueAttachmentMetadataSchema>;
export const ISSUE_DOCUMENT_FORMATS = ["markdown"] as const;
export const issueDocumentFormatSchema = z.enum(ISSUE_DOCUMENT_FORMATS);
export const issueDocumentKeySchema = z
.string()
.trim()
.min(1)
.max(64)
.regex(/^[a-z0-9][a-z0-9_-]*$/, "Document key must be lowercase letters, numbers, _ or -");
export const upsertIssueDocumentSchema = z.object({
title: z.string().trim().max(200).nullable().optional(),
format: issueDocumentFormatSchema,
body: z.string().max(524288),
changeSummary: z.string().trim().max(500).nullable().optional(),
baseRevisionId: z.string().uuid().nullable().optional(),
});
export type IssueDocumentFormat = z.infer<typeof issueDocumentFormatSchema>;
export type UpsertIssueDocument = z.infer<typeof upsertIssueDocumentSchema>;
@@ -0,0 +1,670 @@
import { z } from "zod";
import {
PLUGIN_STATUSES,
PLUGIN_CATEGORIES,
PLUGIN_CAPABILITIES,
PLUGIN_UI_SLOT_TYPES,
PLUGIN_UI_SLOT_ENTITY_TYPES,
PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTS,
PLUGIN_LAUNCHER_PLACEMENT_ZONES,
PLUGIN_LAUNCHER_ACTIONS,
PLUGIN_LAUNCHER_BOUNDS,
PLUGIN_LAUNCHER_RENDER_ENVIRONMENTS,
PLUGIN_STATE_SCOPE_KINDS,
} from "../constants.js";
// ---------------------------------------------------------------------------
// JSON Schema placeholder – a permissive validator for JSON Schema objects
// ---------------------------------------------------------------------------
/**
* Permissive validator for JSON Schema objects. Accepts any `Record<string, unknown>`
* that contains at least a `type`, `$ref`, or composition keyword (`oneOf`/`anyOf`/`allOf`).
* Empty objects are also accepted.
*
* Used to validate `instanceConfigSchema` and `parametersSchema` fields in the
* plugin manifest without fully parsing JSON Schema.
*
* @see PLUGIN_SPEC.md §10.1 — Manifest shape
*/
export const jsonSchemaSchema = z.record(z.unknown()).refine(
(val) => {
// Must have a "type" field if non-empty, or be a valid JSON Schema object
if (Object.keys(val).length === 0) return true;
return typeof val.type === "string" || val.$ref !== undefined || val.oneOf !== undefined || val.anyOf !== undefined || val.allOf !== undefined;
},
{ message: "Must be a valid JSON Schema object (requires at least a 'type', '$ref', or composition keyword)" },
);
// ---------------------------------------------------------------------------
// Manifest sub-type schemas
// ---------------------------------------------------------------------------
/**
* Validates a {@link PluginJobDeclaration} — a scheduled job declared in the
* plugin manifest. Requires `jobKey` and `displayName`; `description` and
* `schedule` (cron expression) are optional.
*
* @see PLUGIN_SPEC.md §17 — Scheduled Jobs
*/
/**
* Validates a cron expression has exactly 5 whitespace-separated fields,
* each containing only valid cron characters (digits, *, /, -, ,).
*
* Valid tokens per field: *, N, N-M, N/S, * /S, N-M/S, and comma-separated lists.
*/
const CRON_FIELD_PATTERN = /^(\*(?:\/[0-9]+)?|[0-9]+(?:-[0-9]+)?(?:\/[0-9]+)?)(?:,(\*(?:\/[0-9]+)?|[0-9]+(?:-[0-9]+)?(?:\/[0-9]+)?))*$/;
function isValidCronExpression(expression: string): boolean {
const trimmed = expression.trim();
if (!trimmed) return false;
const fields = trimmed.split(/\s+/);
if (fields.length !== 5) return false;
return fields.every((f) => CRON_FIELD_PATTERN.test(f));
}
export const pluginJobDeclarationSchema = z.object({
jobKey: z.string().min(1),
displayName: z.string().min(1),
description: z.string().optional(),
schedule: z.string().refine(
(val) => isValidCronExpression(val),
{ message: "schedule must be a valid 5-field cron expression (e.g. '*/15 * * * *')" },
).optional(),
});
export type PluginJobDeclarationInput = z.infer<typeof pluginJobDeclarationSchema>;
/**
* Validates a {@link PluginWebhookDeclaration} — a webhook endpoint declared
* in the plugin manifest. Requires `endpointKey` and `displayName`.
*
* @see PLUGIN_SPEC.md §18 — Webhooks
*/
export const pluginWebhookDeclarationSchema = z.object({
endpointKey: z.string().min(1),
displayName: z.string().min(1),
description: z.string().optional(),
});
export type PluginWebhookDeclarationInput = z.infer<typeof pluginWebhookDeclarationSchema>;
/**
* Validates a {@link PluginToolDeclaration} — an agent tool contributed by the
* plugin. Requires `name`, `displayName`, `description`, and a valid
* `parametersSchema`. Requires the `agent.tools.register` capability.
*
* @see PLUGIN_SPEC.md §11 — Agent Tools
*/
export const pluginToolDeclarationSchema = z.object({
name: z.string().min(1),
displayName: z.string().min(1),
description: z.string().min(1),
parametersSchema: jsonSchemaSchema,
});
export type PluginToolDeclarationInput = z.infer<typeof pluginToolDeclarationSchema>;
/**
* Validates a {@link PluginUiSlotDeclaration} — a UI extension slot the plugin
* fills with a React component. Includes `superRefine` checks for slot-specific
* requirements such as `entityTypes` for context-sensitive slots.
*
* @see PLUGIN_SPEC.md §19 — UI Extension Model
*/
export const pluginUiSlotDeclarationSchema = z.object({
type: z.enum(PLUGIN_UI_SLOT_TYPES),
id: z.string().min(1),
displayName: z.string().min(1),
exportName: z.string().min(1),
entityTypes: z.array(z.enum(PLUGIN_UI_SLOT_ENTITY_TYPES)).optional(),
routePath: z.string().regex(/^[a-z0-9][a-z0-9-]*$/, {
message: "routePath must be a lowercase single-segment slug (letters, numbers, hyphens)",
}).optional(),
order: z.number().int().optional(),
}).superRefine((value, ctx) => {
// context-sensitive slots require explicit entity targeting.
const entityScopedTypes = ["detailTab", "taskDetailView", "contextMenuItem", "commentAnnotation", "commentContextMenuItem", "projectSidebarItem"];
if (
entityScopedTypes.includes(value.type)
&& (!value.entityTypes || value.entityTypes.length === 0)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `${value.type} slots require at least one entityType`,
path: ["entityTypes"],
});
}
// projectSidebarItem only makes sense for entityType "project".
if (value.type === "projectSidebarItem" && value.entityTypes && !value.entityTypes.includes("project")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "projectSidebarItem slots require entityTypes to include \"project\"",
path: ["entityTypes"],
});
}
// commentAnnotation only makes sense for entityType "comment".
if (value.type === "commentAnnotation" && value.entityTypes && !value.entityTypes.includes("comment")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "commentAnnotation slots require entityTypes to include \"comment\"",
path: ["entityTypes"],
});
}
// commentContextMenuItem only makes sense for entityType "comment".
if (value.type === "commentContextMenuItem" && value.entityTypes && !value.entityTypes.includes("comment")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "commentContextMenuItem slots require entityTypes to include \"comment\"",
path: ["entityTypes"],
});
}
if (value.routePath && value.type !== "page") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "routePath is only supported for page slots",
path: ["routePath"],
});
}
if (value.routePath && PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTS.includes(value.routePath as (typeof PLUGIN_RESERVED_COMPANY_ROUTE_SEGMENTS)[number])) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `routePath "${value.routePath}" is reserved by the host`,
path: ["routePath"],
});
}
});
export type PluginUiSlotDeclarationInput = z.infer<typeof pluginUiSlotDeclarationSchema>;
const entityScopedLauncherPlacementZones = [
"detailTab",
"taskDetailView",
"contextMenuItem",
"commentAnnotation",
"commentContextMenuItem",
"projectSidebarItem",
] as const;
const launcherBoundsByEnvironment: Record<
(typeof PLUGIN_LAUNCHER_RENDER_ENVIRONMENTS)[number],
readonly (typeof PLUGIN_LAUNCHER_BOUNDS)[number][]
> = {
hostInline: ["inline", "compact", "default"],
hostOverlay: ["compact", "default", "wide", "full"],
hostRoute: ["default", "wide", "full"],
external: [],
iframe: ["compact", "default", "wide", "full"],
};
/**
* Validates the action payload for a declarative plugin launcher.
*/
export const pluginLauncherActionDeclarationSchema = z.object({
type: z.enum(PLUGIN_LAUNCHER_ACTIONS),
target: z.string().min(1),
params: z.record(z.unknown()).optional(),
}).superRefine((value, ctx) => {
if (value.type === "performAction" && value.target.includes("/")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "performAction launchers must target an action key, not a route or URL",
path: ["target"],
});
}
if (value.type === "navigate" && /^https?:\/\//.test(value.target)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "navigate launchers must target a host route, not an absolute URL",
path: ["target"],
});
}
});
export type PluginLauncherActionDeclarationInput =
z.infer<typeof pluginLauncherActionDeclarationSchema>;
/**
* Validates optional render hints for a plugin launcher destination.
*/
export const pluginLauncherRenderDeclarationSchema = z.object({
environment: z.enum(PLUGIN_LAUNCHER_RENDER_ENVIRONMENTS),
bounds: z.enum(PLUGIN_LAUNCHER_BOUNDS).optional(),
}).superRefine((value, ctx) => {
if (!value.bounds) {
return;
}
const supportedBounds = launcherBoundsByEnvironment[value.environment];
if (!supportedBounds.includes(value.bounds)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `bounds "${value.bounds}" is not supported for render environment "${value.environment}"`,
path: ["bounds"],
});
}
});
export type PluginLauncherRenderDeclarationInput =
z.infer<typeof pluginLauncherRenderDeclarationSchema>;
/**
* Validates declarative launcher metadata in a plugin manifest.
*/
export const pluginLauncherDeclarationSchema = z.object({
id: z.string().min(1),
displayName: z.string().min(1),
description: z.string().optional(),
placementZone: z.enum(PLUGIN_LAUNCHER_PLACEMENT_ZONES),
exportName: z.string().min(1).optional(),
entityTypes: z.array(z.enum(PLUGIN_UI_SLOT_ENTITY_TYPES)).optional(),
order: z.number().int().optional(),
action: pluginLauncherActionDeclarationSchema,
render: pluginLauncherRenderDeclarationSchema.optional(),
}).superRefine((value, ctx) => {
if (
entityScopedLauncherPlacementZones.some((zone) => zone === value.placementZone)
&& (!value.entityTypes || value.entityTypes.length === 0)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `${value.placementZone} launchers require at least one entityType`,
path: ["entityTypes"],
});
}
if (
value.placementZone === "projectSidebarItem"
&& value.entityTypes
&& !value.entityTypes.includes("project")
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "projectSidebarItem launchers require entityTypes to include \"project\"",
path: ["entityTypes"],
});
}
if (value.action.type === "performAction" && value.render) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "performAction launchers cannot declare render hints",
path: ["render"],
});
}
if (
["openModal", "openDrawer", "openPopover"].includes(value.action.type)
&& !value.render
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `${value.action.type} launchers require render metadata`,
path: ["render"],
});
}
if (value.action.type === "openModal" && value.render?.environment === "hostInline") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "openModal launchers cannot use the hostInline render environment",
path: ["render", "environment"],
});
}
if (
value.action.type === "openDrawer"
&& value.render
&& !["hostOverlay", "iframe"].includes(value.render.environment)
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "openDrawer launchers must use hostOverlay or iframe render environments",
path: ["render", "environment"],
});
}
if (value.action.type === "openPopover" && value.render?.environment === "hostRoute") {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "openPopover launchers cannot use the hostRoute render environment",
path: ["render", "environment"],
});
}
});
export type PluginLauncherDeclarationInput = z.infer<typeof pluginLauncherDeclarationSchema>;
// ---------------------------------------------------------------------------
// Plugin Manifest V1 schema
// ---------------------------------------------------------------------------
/**
* Zod schema for {@link PaperclipPluginManifestV1} — the complete runtime
* validator for plugin manifests read at install time.
*
* Field-level constraints (see PLUGIN_SPEC.md §10.1 for the normative rules):
*
* | Field | Type | Constraints |
* |--------------------------|------------|----------------------------------------------|
* | `id` | string | `^[a-z0-9][a-z0-9._-]*$` |
* | `apiVersion` | literal 1 | must equal `PLUGIN_API_VERSION` |
* | `version` | string | semver (`\d+\.\d+\.\d+`) |
* | `displayName` | string | 1–100 chars |
* | `description` | string | 1–500 chars |
* | `author` | string | 1–200 chars |
* | `categories` | enum[] | at least one; values from PLUGIN_CATEGORIES |
* | `minimumHostVersion` | string? | semver lower bound if present, no leading `v`|
* | `minimumPaperclipVersion`| string? | legacy alias of `minimumHostVersion` |
* | `capabilities` | enum[] | at least one; values from PLUGIN_CAPABILITIES|
* | `entrypoints.worker` | string | min 1 char |
* | `entrypoints.ui` | string? | required when `ui.slots` is declared |
*
* Cross-field rules enforced via `superRefine`:
* - `entrypoints.ui` required when `ui.slots` declared
* - `agent.tools.register` capability required when `tools` declared
* - `jobs.schedule` capability required when `jobs` declared
* - `webhooks.receive` capability required when `webhooks` declared
* - duplicate `jobs[].jobKey` values are rejected
* - duplicate `webhooks[].endpointKey` values are rejected
* - duplicate `tools[].name` values are rejected
* - duplicate `ui.slots[].id` values are rejected
*
* @see PLUGIN_SPEC.md §10.1 — Manifest shape
* @see {@link PaperclipPluginManifestV1} — the inferred TypeScript type
*/
export const pluginManifestV1Schema = z.object({
id: z.string().min(1).regex(
/^[a-z0-9][a-z0-9._-]*$/,
"Plugin id must start with a lowercase alphanumeric and contain only lowercase letters, digits, dots, hyphens, or underscores",
),
apiVersion: z.literal(1),
version: z.string().min(1).regex(
/^\d+\.\d+\.\d+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/,
"Version must follow semver (e.g. 1.0.0 or 1.0.0-beta.1)",
),
displayName: z.string().min(1).max(100),
description: z.string().min(1).max(500),
author: z.string().min(1).max(200),
categories: z.array(z.enum(PLUGIN_CATEGORIES)).min(1),
minimumHostVersion: z.string().regex(
/^\d+\.\d+\.\d+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/,
"minimumHostVersion must follow semver (e.g. 1.0.0)",
).optional(),
minimumPaperclipVersion: z.string().regex(
/^\d+\.\d+\.\d+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/,
"minimumPaperclipVersion must follow semver (e.g. 1.0.0)",
).optional(),
capabilities: z.array(z.enum(PLUGIN_CAPABILITIES)).min(1),
entrypoints: z.object({
worker: z.string().min(1),
ui: z.string().min(1).optional(),
}),
instanceConfigSchema: jsonSchemaSchema.optional(),
jobs: z.array(pluginJobDeclarationSchema).optional(),
webhooks: z.array(pluginWebhookDeclarationSchema).optional(),
tools: z.array(pluginToolDeclarationSchema).optional(),
launchers: z.array(pluginLauncherDeclarationSchema).optional(),
ui: z.object({
slots: z.array(pluginUiSlotDeclarationSchema).min(1).optional(),
launchers: z.array(pluginLauncherDeclarationSchema).optional(),
}).optional(),
}).superRefine((manifest, ctx) => {
// ── Entrypoint ↔ UI slot consistency ──────────────────────────────────
// Plugins that declare UI slots must also declare a UI entrypoint so the
// host knows where to load the bundle from (PLUGIN_SPEC.md §10.1).
const hasUiSlots = (manifest.ui?.slots?.length ?? 0) > 0;
const hasUiLaunchers = (manifest.ui?.launchers?.length ?? 0) > 0;
if ((hasUiSlots || hasUiLaunchers) && !manifest.entrypoints.ui) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "entrypoints.ui is required when ui.slots or ui.launchers are declared",
path: ["entrypoints", "ui"],
});
}
if (
manifest.minimumHostVersion
&& manifest.minimumPaperclipVersion
&& manifest.minimumHostVersion !== manifest.minimumPaperclipVersion
) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "minimumHostVersion and minimumPaperclipVersion must match when both are declared",
path: ["minimumHostVersion"],
});
}
// ── Capability ↔ feature declaration consistency ───────────────────────
// The host enforces capabilities at install and runtime. A plugin must
// declare every capability it needs up-front; silently having more features
// than capabilities would cause runtime rejections.
// tools require agent.tools.register (PLUGIN_SPEC.md §11)
if (manifest.tools && manifest.tools.length > 0) {
if (!manifest.capabilities.includes("agent.tools.register")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Capability 'agent.tools.register' is required when tools are declared",
path: ["capabilities"],
});
}
}
// jobs require jobs.schedule (PLUGIN_SPEC.md §17)
if (manifest.jobs && manifest.jobs.length > 0) {
if (!manifest.capabilities.includes("jobs.schedule")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Capability 'jobs.schedule' is required when jobs are declared",
path: ["capabilities"],
});
}
}
// webhooks require webhooks.receive (PLUGIN_SPEC.md §18)
if (manifest.webhooks && manifest.webhooks.length > 0) {
if (!manifest.capabilities.includes("webhooks.receive")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Capability 'webhooks.receive' is required when webhooks are declared",
path: ["capabilities"],
});
}
}
// ── Uniqueness checks ──────────────────────────────────────────────────
// Duplicate keys within a plugin's own manifest are always a bug. The host
// would not know which declaration takes precedence, so we reject early.
// job keys must be unique within the plugin (used as identifiers in the DB)
if (manifest.jobs) {
const jobKeys = manifest.jobs.map((j) => j.jobKey);
const duplicates = jobKeys.filter((key, i) => jobKeys.indexOf(key) !== i);
if (duplicates.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Duplicate job keys: ${[...new Set(duplicates)].join(", ")}`,
path: ["jobs"],
});
}
}
// webhook endpoint keys must be unique within the plugin (used in routes)
if (manifest.webhooks) {
const endpointKeys = manifest.webhooks.map((w) => w.endpointKey);
const duplicates = endpointKeys.filter((key, i) => endpointKeys.indexOf(key) !== i);
if (duplicates.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Duplicate webhook endpoint keys: ${[...new Set(duplicates)].join(", ")}`,
path: ["webhooks"],
});
}
}
// tool names must be unique within the plugin (namespaced at runtime)
if (manifest.tools) {
const toolNames = manifest.tools.map((t) => t.name);
const duplicates = toolNames.filter((name, i) => toolNames.indexOf(name) !== i);
if (duplicates.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Duplicate tool names: ${[...new Set(duplicates)].join(", ")}`,
path: ["tools"],
});
}
}
// UI slot ids must be unique within the plugin (namespaced at runtime)
if (manifest.ui) {
if (manifest.ui.slots) {
const slotIds = manifest.ui.slots.map((s) => s.id);
const duplicates = slotIds.filter((id, i) => slotIds.indexOf(id) !== i);
if (duplicates.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Duplicate UI slot ids: ${[...new Set(duplicates)].join(", ")}`,
path: ["ui", "slots"],
});
}
}
}
// launcher ids must be unique within the plugin
const allLaunchers = [
...(manifest.launchers ?? []),
...(manifest.ui?.launchers ?? []),
];
if (allLaunchers.length > 0) {
const launcherIds = allLaunchers.map((launcher) => launcher.id);
const duplicates = launcherIds.filter((id, i) => launcherIds.indexOf(id) !== i);
if (duplicates.length > 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Duplicate launcher ids: ${[...new Set(duplicates)].join(", ")}`,
path: manifest.ui?.launchers ? ["ui", "launchers"] : ["launchers"],
});
}
}
});
export type PluginManifestV1Input = z.infer<typeof pluginManifestV1Schema>;
// ---------------------------------------------------------------------------
// Plugin installation / registration request
// ---------------------------------------------------------------------------
/**
* Schema for installing (registering) a plugin.
* The server receives the packageName and resolves the manifest from the
* installed package.
*/
export const installPluginSchema = z.object({
packageName: z.string().min(1),
version: z.string().min(1).optional(),
/** Set by loader for local-path installs so the worker can be resolved. */
packagePath: z.string().min(1).optional(),
});
export type InstallPlugin = z.infer<typeof installPluginSchema>;
// ---------------------------------------------------------------------------
// Plugin config (instance configuration) schemas
// ---------------------------------------------------------------------------
/**
* Schema for creating or updating a plugin's instance configuration.
* configJson is validated permissively here; runtime validation against
* the plugin's instanceConfigSchema is done at the service layer.
*/
export const upsertPluginConfigSchema = z.object({
configJson: z.record(z.unknown()),
});
export type UpsertPluginConfig = z.infer<typeof upsertPluginConfigSchema>;
/**
* Schema for partially updating a plugin's instance configuration.
* Allows a partial merge of config values.
*/
export const patchPluginConfigSchema = z.object({
configJson: z.record(z.unknown()),
});
export type PatchPluginConfig = z.infer<typeof patchPluginConfigSchema>;
// ---------------------------------------------------------------------------
// Plugin status update
// ---------------------------------------------------------------------------
/**
* Schema for updating a plugin's lifecycle status. Used by the lifecycle
* manager to persist state transitions.
*
* @see {@link PLUGIN_STATUSES} for the valid status values
*/
export const updatePluginStatusSchema = z.object({
status: z.enum(PLUGIN_STATUSES),
lastError: z.string().nullable().optional(),
});
export type UpdatePluginStatus = z.infer<typeof updatePluginStatusSchema>;
// ---------------------------------------------------------------------------
// Plugin uninstall
// ---------------------------------------------------------------------------
/** Schema for the uninstall request. `removeData` controls hard vs soft delete. */
export const uninstallPluginSchema = z.object({
removeData: z.boolean().optional().default(false),
});
export type UninstallPlugin = z.infer<typeof uninstallPluginSchema>;
// ---------------------------------------------------------------------------
// Plugin state (key-value storage) schemas
// ---------------------------------------------------------------------------
/**
* Schema for a plugin state scope key — identifies the exact location where
* state is stored. Used by the `ctx.state.get()`, `ctx.state.set()`, and
* `ctx.state.delete()` SDK methods.
*
* @see PLUGIN_SPEC.md §21.3 `plugin_state`
*/
export const pluginStateScopeKeySchema = z.object({
scopeKind: z.enum(PLUGIN_STATE_SCOPE_KINDS),
scopeId: z.string().min(1).optional(),
namespace: z.string().min(1).optional(),
stateKey: z.string().min(1),
});
export type PluginStateScopeKey = z.infer<typeof pluginStateScopeKeySchema>;
/**
* Schema for setting a plugin state value.
*/
export const setPluginStateSchema = z.object({
scopeKind: z.enum(PLUGIN_STATE_SCOPE_KINDS),
scopeId: z.string().min(1).optional(),
namespace: z.string().min(1).optional(),
stateKey: z.string().min(1),
/** JSON-serializable value to store. */
value: z.unknown(),
});
export type SetPluginState = z.infer<typeof setPluginStateSchema>;
/**
* Schema for querying plugin state entries. All fields are optional to allow
* flexible list queries (e.g. all state for a plugin within a scope).
*/
export const listPluginStateSchema = z.object({
scopeKind: z.enum(PLUGIN_STATE_SCOPE_KINDS).optional(),
scopeId: z.string().min(1).optional(),
namespace: z.string().min(1).optional(),
});
export type ListPluginState = z.infer<typeof listPluginStateSchema>;
@@ -0,0 +1,114 @@
import { z } from "zod";
import { PROJECT_STATUSES } from "../constants.js";
const executionWorkspaceStrategySchema = z
.object({
type: z.enum(["project_primary", "git_worktree", "adapter_managed", "cloud_sandbox"]).optional(),
baseRef: z.string().optional().nullable(),
branchTemplate: z.string().optional().nullable(),
worktreeParentDir: z.string().optional().nullable(),
provisionCommand: z.string().optional().nullable(),
teardownCommand: z.string().optional().nullable(),
})
.strict();
export const projectExecutionWorkspacePolicySchema = z
.object({
enabled: z.boolean(),
defaultMode: z.enum(["shared_workspace", "isolated_workspace", "operator_branch", "adapter_default"]).optional(),
allowIssueOverride: z.boolean().optional(),
defaultProjectWorkspaceId: z.string().uuid().optional().nullable(),
workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(),
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
branchPolicy: z.record(z.unknown()).optional().nullable(),
pullRequestPolicy: z.record(z.unknown()).optional().nullable(),
runtimePolicy: z.record(z.unknown()).optional().nullable(),
cleanupPolicy: z.record(z.unknown()).optional().nullable(),
})
.strict();
const projectWorkspaceSourceTypeSchema = z.enum(["local_path", "git_repo", "remote_managed", "non_git_path"]);
const projectWorkspaceVisibilitySchema = z.enum(["default", "advanced"]);
const projectWorkspaceFields = {
name: z.string().min(1).optional(),
sourceType: projectWorkspaceSourceTypeSchema.optional(),
cwd: z.string().min(1).optional().nullable(),
repoUrl: z.string().url().optional().nullable(),
repoRef: z.string().optional().nullable(),
defaultRef: z.string().optional().nullable(),
visibility: projectWorkspaceVisibilitySchema.optional(),
setupCommand: z.string().optional().nullable(),
cleanupCommand: z.string().optional().nullable(),
remoteProvider: z.string().optional().nullable(),
remoteWorkspaceRef: z.string().optional().nullable(),
sharedWorkspaceKey: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
};
function validateProjectWorkspace(value: Record<string, unknown>, ctx: z.RefinementCtx) {
const sourceType = value.sourceType ?? "local_path";
const hasCwd = typeof value.cwd === "string" && value.cwd.trim().length > 0;
const hasRepo = typeof value.repoUrl === "string" && value.repoUrl.trim().length > 0;
const hasRemoteRef = typeof value.remoteWorkspaceRef === "string" && value.remoteWorkspaceRef.trim().length > 0;
if (sourceType === "remote_managed") {
if (!hasRemoteRef && !hasRepo) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Remote-managed workspace requires remoteWorkspaceRef or repoUrl.",
path: ["remoteWorkspaceRef"],
});
}
return;
}
if (!hasCwd && !hasRepo) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Workspace requires at least one of cwd or repoUrl.",
path: ["cwd"],
});
}
}
export const createProjectWorkspaceSchema = z.object({
...projectWorkspaceFields,
isPrimary: z.boolean().optional().default(false),
}).superRefine(validateProjectWorkspace);
export type CreateProjectWorkspace = z.infer<typeof createProjectWorkspaceSchema>;
export const updateProjectWorkspaceSchema = z.object({
...projectWorkspaceFields,
isPrimary: z.boolean().optional(),
}).partial();
export type UpdateProjectWorkspace = z.infer<typeof updateProjectWorkspaceSchema>;
const projectFields = {
/** @deprecated Use goalIds instead */
goalId: z.string().uuid().optional().nullable(),
goalIds: z.array(z.string().uuid()).optional(),
name: z.string().min(1),
description: z.string().optional().nullable(),
status: z.enum(PROJECT_STATUSES).optional().default("backlog"),
leadAgentId: z.string().uuid().optional().nullable(),
targetDate: z.string().optional().nullable(),
color: z.string().optional().nullable(),
executionWorkspacePolicy: projectExecutionWorkspacePolicySchema.optional().nullable(),
archivedAt: z.string().datetime().optional().nullable(),
};
export const createProjectSchema = z.object({
...projectFields,
workspace: createProjectWorkspaceSchema.optional(),
});
export type CreateProject = z.infer<typeof createProjectSchema>;
export const updateProjectSchema = z.object(projectFields).partial();
export type UpdateProject = z.infer<typeof updateProjectSchema>;
export type ProjectExecutionWorkspacePolicy = z.infer<typeof projectExecutionWorkspacePolicySchema>;
@@ -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>;
@@ -0,0 +1,47 @@
import { z } from "zod";
import { SECRET_PROVIDERS } from "../constants.js";
export const envBindingPlainSchema = z.object({
type: z.literal("plain"),
value: z.string(),
});
export const envBindingSecretRefSchema = z.object({
type: z.literal("secret_ref"),
secretId: z.string().uuid(),
version: z.union([z.literal("latest"), z.number().int().positive()]).optional(),
});
// Backward-compatible union that accepts legacy inline values.
export const envBindingSchema = z.union([
z.string(),
envBindingPlainSchema,
envBindingSecretRefSchema,
]);
export const envConfigSchema = z.record(envBindingSchema);
export const createSecretSchema = z.object({
name: z.string().min(1),
provider: z.enum(SECRET_PROVIDERS).optional(),
value: z.string().min(1),
description: z.string().optional().nullable(),
externalRef: z.string().optional().nullable(),
});
export type CreateSecret = z.infer<typeof createSecretSchema>;
export const rotateSecretSchema = z.object({
value: z.string().min(1),
externalRef: z.string().optional().nullable(),
});
export type RotateSecret = z.infer<typeof rotateSecretSchema>;
export const updateSecretSchema = z.object({
name: z.string().min(1).optional(),
description: z.string().optional().nullable(),
externalRef: z.string().optional().nullable(),
});
export type UpdateSecret = z.infer<typeof updateSecretSchema>;
@@ -0,0 +1,54 @@
import { z } from "zod";
export const issueWorkProductTypeSchema = z.enum([
"preview_url",
"runtime_service",
"pull_request",
"branch",
"commit",
"artifact",
"document",
]);
export const issueWorkProductStatusSchema = z.enum([
"active",
"ready_for_review",
"approved",
"changes_requested",
"merged",
"closed",
"failed",
"archived",
"draft",
]);
export const issueWorkProductReviewStateSchema = z.enum([
"none",
"needs_board_review",
"approved",
"changes_requested",
]);
export const createIssueWorkProductSchema = z.object({
projectId: z.string().uuid().optional().nullable(),
executionWorkspaceId: z.string().uuid().optional().nullable(),
runtimeServiceId: z.string().uuid().optional().nullable(),
type: issueWorkProductTypeSchema,
provider: z.string().min(1),
externalId: z.string().optional().nullable(),
title: z.string().min(1),
url: z.string().url().optional().nullable(),
status: issueWorkProductStatusSchema.default("active"),
reviewState: issueWorkProductReviewStateSchema.optional().default("none"),
isPrimary: z.boolean().optional().default(false),
healthStatus: z.enum(["unknown", "healthy", "unhealthy"]).optional().default("unknown"),
summary: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
createdByRunId: z.string().uuid().optional().nullable(),
});
export type CreateIssueWorkProduct = z.infer<typeof createIssueWorkProductSchema>;
export const updateIssueWorkProductSchema = createIssueWorkProductSchema.partial();
export type UpdateIssueWorkProduct = z.infer<typeof updateIssueWorkProductSchema>;