b9a80dcf22
## Thinking Path > - Paperclip is the control plane for autonomous AI companies. > - V1 needs to stay local-first while also supporting shared, authenticated deployments. > - Human operators need real identities, company membership, invite flows, profile surfaces, and company-scoped access controls. > - Agents and operators also need the existing issue, inbox, workspace, approval, and plugin flows to keep working under those authenticated boundaries. > - This branch accumulated the multi-user implementation, follow-up QA fixes, workspace/runtime refinements, invite UX improvements, release-branch conflict resolution, and review hardening. > - This pull request consolidates that branch onto the current `master` branch as a single reviewable PR. > - The benefit is a complete multi-user implementation path with tests and docs carried forward without dropping existing branch work. ## What Changed - Added authenticated human-user access surfaces: auth/session routes, company user directory, profile settings, company access/member management, join requests, and invite management. - Added invite creation, invite landing, onboarding, logo/branding, invite grants, deduped join requests, and authenticated multi-user E2E coverage. - Tightened company-scoped and instance-admin authorization across board, plugin, adapter, access, issue, and workspace routes. - Added profile-image URL validation hardening, avatar preservation on name-only profile updates, and join-request uniqueness migration cleanup for pending human requests. - Added an atomic member role/status/grants update path so Company Access saves no longer leave partially updated permissions. - Improved issue chat, inbox, assignee identity rendering, sidebar/account/company navigation, workspace routing, and execution workspace reuse behavior for multi-user operation. - Added and updated server/UI tests covering auth, invites, membership, issue workspace inheritance, plugin authz, inbox/chat behavior, and multi-user flows. - Merged current `public-gh/master` into this branch, resolved all conflicts, and verified no `pnpm-lock.yaml` change is included in this PR diff. ## Verification - `pnpm exec vitest run server/src/__tests__/issues-service.test.ts ui/src/components/IssueChatThread.test.tsx ui/src/pages/Inbox.test.tsx` - `pnpm run preflight:workspace-links && pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts` - `pnpm exec vitest run server/src/__tests__/plugin-routes-authz.test.ts server/src/__tests__/workspace-runtime-service-authz.test.ts server/src/__tests__/access-validators.test.ts` - `pnpm exec vitest run server/src/__tests__/authz-company-access.test.ts server/src/__tests__/routines-routes.test.ts server/src/__tests__/sidebar-preferences-routes.test.ts server/src/__tests__/approval-routes-idempotency.test.ts server/src/__tests__/openclaw-invite-prompt-route.test.ts server/src/__tests__/agent-cross-tenant-authz-routes.test.ts server/src/__tests__/routines-e2e.test.ts` - `pnpm exec vitest run server/src/__tests__/auth-routes.test.ts ui/src/pages/CompanyAccess.test.tsx` - `pnpm --filter @paperclipai/shared typecheck && pnpm --filter @paperclipai/db typecheck && pnpm --filter @paperclipai/server typecheck` - `pnpm --filter @paperclipai/shared typecheck && pnpm --filter @paperclipai/server typecheck` - `pnpm --filter @paperclipai/ui typecheck` - `pnpm db:generate` - `npx playwright test --config tests/e2e/playwright.config.ts --list` - Confirmed branch has no uncommitted changes and is `0` commits behind `public-gh/master` before PR creation. - Confirmed no `pnpm-lock.yaml` change is staged or present in the PR diff. ## Risks - High review surface area: this PR contains the accumulated multi-user branch plus follow-up fixes, so reviewers should focus especially on company-boundary enforcement and authenticated-vs-local deployment behavior. - UI behavior changed across invites, inbox, issue chat, access settings, and sidebar navigation; no browser screenshots are included in this branch-consolidation PR. - Plugin install, upgrade, and lifecycle/config mutations now require instance-admin access, which is intentional but may change expectations for non-admin board users. - A join-request dedupe migration rejects duplicate pending human requests before creating unique indexes; deployments with unusual historical duplicates should review the migration behavior. - Company member role/status/grant saves now use a new combined endpoint; older separate endpoints remain for compatibility. - Full production build was not run locally in this heartbeat; CI should cover the full matrix. ## Model Used - OpenAI Codex coding agent, GPT-5-based model, CLI/tool-use environment. Exact deployed model identifier and context window were not exposed by the runtime. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] If this change affects the UI, I have included before/after screenshots - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] I will address all Greptile and reviewer comments before requesting merge Note on screenshots: this is a branch-consolidation PR for an already-developed multi-user branch, and no browser screenshots were captured during this heartbeat. --------- Co-authored-by: dotta <dotta@example.com> Co-authored-by: Paperclip <noreply@paperclip.ing> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
751 lines
20 KiB
TypeScript
751 lines
20 KiB
TypeScript
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 BIND_MODES = ["loopback", "lan", "tailnet", "custom"] as const;
|
|
export type BindMode = (typeof BIND_MODES)[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",
|
|
"gemini_local",
|
|
"opencode_local",
|
|
"pi_local",
|
|
"cursor",
|
|
"openclaw_gateway",
|
|
] as const;
|
|
export type AgentAdapterType = (typeof AGENT_ADAPTER_TYPES)[number] | (string & {});
|
|
|
|
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 INBOX_MINE_ISSUE_STATUSES = [
|
|
"backlog",
|
|
"todo",
|
|
"in_progress",
|
|
"in_review",
|
|
"blocked",
|
|
"done",
|
|
] as const;
|
|
export const INBOX_MINE_ISSUE_STATUS_FILTER = INBOX_MINE_ISSUE_STATUSES.join(",");
|
|
|
|
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 ISSUE_RELATION_TYPES = ["blocks"] as const;
|
|
export type IssueRelationType = (typeof ISSUE_RELATION_TYPES)[number];
|
|
|
|
export const ISSUE_EXECUTION_POLICY_MODES = ["normal", "auto"] as const;
|
|
export type IssueExecutionPolicyMode = (typeof ISSUE_EXECUTION_POLICY_MODES)[number];
|
|
|
|
export const ISSUE_EXECUTION_STAGE_TYPES = ["review", "approval"] as const;
|
|
export type IssueExecutionStageType = (typeof ISSUE_EXECUTION_STAGE_TYPES)[number];
|
|
|
|
export const ISSUE_EXECUTION_STATE_STATUSES = ["idle", "pending", "changes_requested", "completed"] as const;
|
|
export type IssueExecutionStateStatus = (typeof ISSUE_EXECUTION_STATE_STATUSES)[number];
|
|
|
|
export const ISSUE_EXECUTION_DECISION_OUTCOMES = ["approved", "changes_requested"] as const;
|
|
export type IssueExecutionDecisionOutcome = (typeof ISSUE_EXECUTION_DECISION_OUTCOMES)[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", "github_hmac", "none"] as const;
|
|
export type RoutineTriggerSigningMode = (typeof ROUTINE_TRIGGER_SIGNING_MODES)[number];
|
|
|
|
export const ROUTINE_VARIABLE_TYPES = ["text", "textarea", "number", "boolean", "select"] as const;
|
|
export type RoutineVariableType = (typeof ROUTINE_VARIABLE_TYPES)[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",
|
|
"request_board_approval",
|
|
] 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 COMPANY_MEMBERSHIP_ROLES = [
|
|
"owner",
|
|
"admin",
|
|
"operator",
|
|
"viewer",
|
|
"member",
|
|
] as const;
|
|
export type CompanyMembershipRole = (typeof COMPANY_MEMBERSHIP_ROLES)[number];
|
|
|
|
export const HUMAN_COMPANY_MEMBERSHIP_ROLES = [
|
|
"owner",
|
|
"admin",
|
|
"operator",
|
|
"viewer",
|
|
] as const;
|
|
export type HumanCompanyMembershipRole = (typeof HUMAN_COMPANY_MEMBERSHIP_ROLES)[number];
|
|
|
|
export const HUMAN_COMPANY_MEMBERSHIP_ROLE_LABELS: Record<HumanCompanyMembershipRole, string> = {
|
|
owner: "Owner",
|
|
admin: "Admin",
|
|
operator: "Operator",
|
|
viewer: "Viewer",
|
|
};
|
|
|
|
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",
|
|
"telemetry.track",
|
|
// 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];
|