mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-22 16:51:28 +02:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Operators need a way to inspect files and work products created by agents across a company without opening each issue one by one. > - The existing issue detail surfaces already show attachments and outputs, but there was no company-level artifacts index or search-result affordance for artifact-like records. > - The backend needed a company-scoped artifacts projection API that preserves issue/run attribution and safe links back to source records. > - The UI needed a first-class Artifacts page, sidebar entry, reusable artifact cards, and deep-link handling that keeps company prefixes intact. > - This pull request adds the company artifacts API and page, then wires artifacts into search and issue output surfaces. > - The benefit is a single place to browse, filter, and open generated work products and attachments while preserving company boundaries. ## Linked Issues or Issue Description Fixes #7622. Feature request fields: - Problem/motivation: company operators need a consolidated artifacts surface for attachments and work products produced by agents. - Proposed solution: add a company-scoped artifacts projection endpoint, a board Artifacts route, reusable cards, sidebar navigation, and artifact search integration. - Alternatives considered: keep artifact discovery only on individual issue pages; that forces operators to know the source issue before finding generated outputs. - Roadmap alignment: checked `ROADMAP.md`; this is a focused board UI/API improvement and does not duplicate a listed roadmap item. ## What Changed - Added shared artifact types and validators. - Added a company-scoped artifact projection service/API with tests for attachment/work-product attribution. - Added Artifacts board UI route, API client, sidebar link, cards, filters, and storybook coverage. - Added artifact result handling to company search and issue output/deep-link flows. - Rebased the branch onto the latest `public-gh/master` state and resolved the route-test conflict by preserving both upstream team-catalog coverage and artifact route coverage. - Fixed a local Sidebar test helper so it no longer depends on a runtime-undefined `React.act` export in this dependency install. ## Verification - `pnpm --filter @paperclipai/ui exec vitest run src/components/artifacts/ArtifactCard.test.tsx src/api/artifacts.test.ts src/lib/company-routes.test.ts` - `pnpm --filter @paperclipai/ui exec vitest run src/pages/Artifacts.test.tsx src/pages/Search.test.tsx src/components/Sidebar.test.tsx` - `pnpm exec vitest run server/src/__tests__/company-artifacts-service.test.ts server/src/__tests__/company-search-service.test.ts server/src/__tests__/company-search-rate-limit-routes.test.ts server/src/__tests__/issue-agent-mutation-ownership-routes.test.ts` - Confirmed the PR diff does not include `pnpm-lock.yaml` or `.github/workflows/*`. - Duplicate search: no open PRs or issues found for `artifact page ArtifactCard` in `paperclipai/paperclip`. Screenshots are intentionally omitted per the internal task instruction not to add design screenshots or images to this PR unless they are specifically part of the work. I also attempted browser capture in this runner, but `agent-browser` failed to launch Chrome and Playwright Chromium is missing `libatk-1.0.so.0`. ## Risks - Low-to-medium risk: this adds a new API projection and UI surface, so attribution/link regressions could affect artifact navigation. - Company scoping is covered in the new service/API tests. - No database migrations are included. - No lockfile or workflow changes are included. > For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and discuss it in `#dev` before opening the PR. Feature PRs that overlap with planned core work may need to be redirected — check the roadmap first. See `CONTRIBUTING.md`. ## Model Used OpenAI Codex, GPT-5 coding agent with tool use and local command execution. Exact hosted model identifier is not exposed in this 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 checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [ ] If this change affects the UI, I have included before/after screenshots (intentionally omitted per task instruction; browser capture unavailable in this runner) - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
4389 lines
134 KiB
TypeScript
4389 lines
134 KiB
TypeScript
import { Router } from "express";
|
|
import { z } from "zod";
|
|
import {
|
|
// Agent
|
|
createAgentSchema,
|
|
createAgentHireSchema,
|
|
updateAgentSchema,
|
|
updateAgentPermissionsSchema,
|
|
updateAgentInstructionsPathSchema,
|
|
updateAgentInstructionsBundleSchema,
|
|
upsertAgentInstructionsFileSchema,
|
|
createAgentKeySchema,
|
|
wakeAgentSchema,
|
|
resetAgentSessionSchema,
|
|
agentSkillSyncSchema,
|
|
testAdapterEnvironmentSchema,
|
|
// Issue
|
|
createIssueSchema,
|
|
updateIssueSchema,
|
|
createIssueLabelSchema,
|
|
addIssueCommentSchema,
|
|
checkoutIssueSchema,
|
|
linkIssueApprovalSchema,
|
|
createIssueWorkProductSchema,
|
|
updateIssueWorkProductSchema,
|
|
upsertIssueDocumentSchema,
|
|
restoreIssueDocumentRevisionSchema,
|
|
upsertIssueFeedbackVoteSchema,
|
|
// Project
|
|
createProjectSchema,
|
|
updateProjectSchema,
|
|
createProjectWorkspaceSchema,
|
|
updateProjectWorkspaceSchema,
|
|
// Company
|
|
createCompanySchema,
|
|
updateCompanySchema,
|
|
updateCompanyBrandingSchema,
|
|
companyArtifactsQuerySchema,
|
|
companyArtifactsResponseSchema,
|
|
// Routine
|
|
createRoutineSchema,
|
|
updateRoutineSchema,
|
|
createRoutineTriggerSchema,
|
|
updateRoutineTriggerSchema,
|
|
rotateRoutineTriggerSecretSchema,
|
|
runRoutineSchema,
|
|
// Goal
|
|
createGoalSchema,
|
|
updateGoalSchema,
|
|
// Secret
|
|
createSecretSchema,
|
|
updateSecretSchema,
|
|
rotateSecretSchema,
|
|
// Approval
|
|
createApprovalSchema,
|
|
resolveApprovalSchema,
|
|
requestApprovalRevisionSchema,
|
|
resubmitApprovalSchema,
|
|
addApprovalCommentSchema,
|
|
// Cost / budget
|
|
createCostEventSchema,
|
|
createFinanceEventSchema,
|
|
updateBudgetSchema,
|
|
upsertBudgetPolicySchema,
|
|
resolveBudgetIncidentSchema,
|
|
// Sidebar
|
|
upsertSidebarOrderPreferenceSchema,
|
|
// Execution workspaces
|
|
updateExecutionWorkspaceSchema,
|
|
workspaceRuntimeControlTargetSchema,
|
|
// Environments
|
|
createEnvironmentSchema,
|
|
updateEnvironmentSchema,
|
|
probeEnvironmentConfigSchema,
|
|
// Company skills
|
|
companySkillCreateSchema,
|
|
companySkillFileUpdateSchema,
|
|
companySkillImportSchema,
|
|
companySkillProjectScanRequestSchema,
|
|
// Issue tree
|
|
createIssueTreeHoldSchema,
|
|
previewIssueTreeControlSchema,
|
|
releaseIssueTreeHoldSchema,
|
|
// Issue interactions
|
|
createIssueThreadInteractionSchema,
|
|
createChildIssueSchema,
|
|
acceptIssueThreadInteractionSchema,
|
|
rejectIssueThreadInteractionSchema,
|
|
respondIssueThreadInteractionSchema,
|
|
// Auth / profile
|
|
updateCurrentUserProfileSchema,
|
|
// Company portability (legacy routes)
|
|
companyPortabilityExportSchema,
|
|
companyPortabilityPreviewSchema,
|
|
companyPortabilityImportSchema,
|
|
// Access / membership
|
|
acceptInviteSchema,
|
|
createCompanyInviteSchema,
|
|
createOpenClawInvitePromptSchema,
|
|
claimJoinRequestApiKeySchema,
|
|
createCliAuthChallengeSchema,
|
|
resolveCliAuthChallengeSchema,
|
|
createBoardApiKeySchema,
|
|
updateCompanyMemberSchema,
|
|
updateCompanyMemberWithPermissionsSchema,
|
|
archiveCompanyMemberSchema,
|
|
updateMemberPermissionsSchema,
|
|
updateUserCompanyAccessSchema,
|
|
// Instance settings
|
|
patchInstanceGeneralSettingsSchema,
|
|
patchInstanceExperimentalSettingsSchema,
|
|
issueGraphLivenessAutoRecoveryRequestSchema,
|
|
// Resource memberships
|
|
updateResourceMembershipSchema,
|
|
// Document annotations
|
|
createDocumentAnnotationCommentSchema,
|
|
createDocumentAnnotationThreadSchema,
|
|
updateDocumentAnnotationThreadSchema,
|
|
// Issue recovery and decomposition
|
|
createAcceptedPlanDecompositionSchema,
|
|
resolveIssueRecoveryActionSchema,
|
|
cancelIssueThreadInteractionSchema,
|
|
// Secret provider configs and remote import
|
|
createSecretProviderConfigSchema,
|
|
updateSecretProviderConfigSchema,
|
|
secretProviderConfigDiscoveryPreviewSchema,
|
|
remoteSecretImportPreviewSchema,
|
|
remoteSecretImportSchema,
|
|
} from "@paperclipai/shared";
|
|
|
|
type JsonSchema = Record<string, unknown>;
|
|
type OpenApiResponse = Record<string, unknown>;
|
|
type OpenApiPathRegistration = {
|
|
method: string;
|
|
path: string;
|
|
request?: {
|
|
params?: z.ZodTypeAny;
|
|
query?: z.ZodTypeAny;
|
|
body?: {
|
|
content: Record<string, { schema: unknown }>;
|
|
required?: boolean;
|
|
};
|
|
};
|
|
responses?: Record<string, OpenApiResponse>;
|
|
[key: string]: unknown;
|
|
};
|
|
|
|
const zodTypeName = (schema: z.ZodTypeAny) => schema._def.typeName as string;
|
|
|
|
function unwrapSchema(schema: z.ZodTypeAny): z.ZodTypeAny {
|
|
const typeName = zodTypeName(schema);
|
|
if (typeName === "ZodOptional" || typeName === "ZodDefault" || typeName === "ZodCatch") {
|
|
return unwrapSchema(schema._def.innerType);
|
|
}
|
|
if (typeName === "ZodEffects") {
|
|
return unwrapSchema(schema._def.schema);
|
|
}
|
|
return schema;
|
|
}
|
|
|
|
function isOptionalSchema(schema: z.ZodTypeAny): boolean {
|
|
const typeName = zodTypeName(schema);
|
|
if (typeName === "ZodOptional" || typeName === "ZodDefault" || typeName === "ZodCatch") {
|
|
return true;
|
|
}
|
|
if (typeName === "ZodEffects") {
|
|
return isOptionalSchema(schema._def.schema);
|
|
}
|
|
if (typeName === "ZodNullable") {
|
|
return isOptionalSchema(schema._def.innerType);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function applyStringChecks(jsonSchema: JsonSchema, checks: Array<Record<string, unknown>>) {
|
|
for (const check of checks) {
|
|
if (check.kind === "min") jsonSchema.minLength = check.value;
|
|
if (check.kind === "max") jsonSchema.maxLength = check.value;
|
|
if (check.kind === "email") jsonSchema.format = "email";
|
|
if (check.kind === "url") jsonSchema.format = "uri";
|
|
if (check.kind === "uuid") jsonSchema.format = "uuid";
|
|
if (check.kind === "datetime") jsonSchema.format = "date-time";
|
|
if (check.kind === "regex" && check.regex instanceof RegExp) {
|
|
jsonSchema.pattern = check.regex.source;
|
|
}
|
|
}
|
|
}
|
|
|
|
function applyNumberChecks(jsonSchema: JsonSchema, checks: Array<Record<string, unknown>>) {
|
|
for (const check of checks) {
|
|
if (check.kind === "int") jsonSchema.type = "integer";
|
|
if (check.kind === "min") {
|
|
jsonSchema.minimum = check.value;
|
|
if (!check.inclusive) jsonSchema.exclusiveMinimum = true;
|
|
}
|
|
if (check.kind === "max") {
|
|
jsonSchema.maximum = check.value;
|
|
if (!check.inclusive) jsonSchema.exclusiveMaximum = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
function zodToOpenApiSchema(schema: z.ZodTypeAny): JsonSchema {
|
|
const unwrapped = unwrapSchema(schema);
|
|
const typeName = zodTypeName(unwrapped);
|
|
|
|
if (typeName === "ZodString") {
|
|
const jsonSchema: JsonSchema = { type: "string" };
|
|
applyStringChecks(jsonSchema, unwrapped._def.checks ?? []);
|
|
return jsonSchema;
|
|
}
|
|
|
|
if (typeName === "ZodNumber") {
|
|
const jsonSchema: JsonSchema = { type: "number" };
|
|
applyNumberChecks(jsonSchema, unwrapped._def.checks ?? []);
|
|
return jsonSchema;
|
|
}
|
|
|
|
if (typeName === "ZodBoolean") return { type: "boolean" };
|
|
if (typeName === "ZodDate") return { type: "string", format: "date-time" };
|
|
if (typeName === "ZodAny" || typeName === "ZodUnknown") return {};
|
|
|
|
if (typeName === "ZodLiteral") {
|
|
const value = unwrapped._def.value;
|
|
return { type: typeof value, enum: [value] };
|
|
}
|
|
|
|
if (typeName === "ZodEnum") {
|
|
return { type: "string", enum: unwrapped._def.values };
|
|
}
|
|
|
|
if (typeName === "ZodNativeEnum") {
|
|
const values = Object.values(unwrapped._def.values).filter(
|
|
(value) => typeof value === "string" || typeof value === "number",
|
|
);
|
|
return { enum: Array.from(new Set(values)) };
|
|
}
|
|
|
|
if (typeName === "ZodArray") {
|
|
return { type: "array", items: zodToOpenApiSchema(unwrapped._def.type) };
|
|
}
|
|
|
|
if (typeName === "ZodRecord") {
|
|
return {
|
|
type: "object",
|
|
additionalProperties: zodToOpenApiSchema(unwrapped._def.valueType),
|
|
};
|
|
}
|
|
|
|
if (typeName === "ZodNullable") {
|
|
return { ...zodToOpenApiSchema(unwrapped._def.innerType), nullable: true };
|
|
}
|
|
|
|
if (typeName === "ZodUnion") {
|
|
return { oneOf: unwrapped._def.options.map((option: z.ZodTypeAny) => zodToOpenApiSchema(option)) };
|
|
}
|
|
|
|
if (typeName === "ZodDiscriminatedUnion") {
|
|
return {
|
|
oneOf: Array.from(unwrapped._def.options.values()).map((option) =>
|
|
zodToOpenApiSchema(option as z.ZodTypeAny),
|
|
),
|
|
};
|
|
}
|
|
|
|
if (typeName === "ZodIntersection") {
|
|
return {
|
|
allOf: [
|
|
zodToOpenApiSchema(unwrapped._def.left),
|
|
zodToOpenApiSchema(unwrapped._def.right),
|
|
],
|
|
};
|
|
}
|
|
|
|
if (typeName === "ZodObject") {
|
|
const shape = unwrapped._def.shape();
|
|
const properties: Record<string, JsonSchema> = {};
|
|
const required: string[] = [];
|
|
for (const [key, value] of Object.entries(shape)) {
|
|
const propertySchema = value as z.ZodTypeAny;
|
|
properties[key] = zodToOpenApiSchema(propertySchema);
|
|
if (!isOptionalSchema(propertySchema)) required.push(key);
|
|
}
|
|
const jsonSchema: JsonSchema = { type: "object", properties };
|
|
if (required.length > 0) jsonSchema.required = required;
|
|
return jsonSchema;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
function normalizeContent(content: Record<string, { schema: unknown }>) {
|
|
return Object.fromEntries(
|
|
Object.entries(content).map(([contentType, media]) => [
|
|
contentType,
|
|
{
|
|
...media,
|
|
schema: isZodSchema(media.schema)
|
|
? zodToOpenApiSchema(media.schema)
|
|
: media.schema,
|
|
},
|
|
]),
|
|
);
|
|
}
|
|
|
|
function isZodSchema(value: unknown): value is z.ZodTypeAny {
|
|
return Boolean(
|
|
value &&
|
|
typeof value === "object" &&
|
|
"_def" in value &&
|
|
typeof (value as z.ZodTypeAny).safeParse === "function",
|
|
);
|
|
}
|
|
|
|
function normalizeResponses(responses: Record<string, OpenApiResponse> = {}) {
|
|
return Object.fromEntries(
|
|
Object.entries(responses).map(([status, response]) => {
|
|
const content = response.content as Record<string, { schema: unknown }> | undefined;
|
|
return [
|
|
status,
|
|
content
|
|
? {
|
|
...response,
|
|
content: normalizeContent(content),
|
|
}
|
|
: response,
|
|
];
|
|
}),
|
|
);
|
|
}
|
|
|
|
function parametersFromSchema(schema: z.ZodTypeAny, location: "path" | "query") {
|
|
const objectSchema = unwrapSchema(schema);
|
|
if (zodTypeName(objectSchema) !== "ZodObject") return [];
|
|
const shape = objectSchema._def.shape();
|
|
return Object.entries(shape).map(([name, value]) => ({
|
|
name,
|
|
in: location,
|
|
required: location === "path" ? true : !isOptionalSchema(value as z.ZodTypeAny),
|
|
schema: zodToOpenApiSchema(value as z.ZodTypeAny),
|
|
}));
|
|
}
|
|
|
|
class OpenAPIRegistry {
|
|
private readonly schemas: Record<string, JsonSchema> = {};
|
|
private readonly paths: Array<OpenApiPathRegistration> = [];
|
|
|
|
register(name: string, schema: z.ZodTypeAny) {
|
|
this.schemas[name] = zodToOpenApiSchema(schema);
|
|
return { $ref: `#/components/schemas/${name}` };
|
|
}
|
|
|
|
registerPath(pathRegistration: OpenApiPathRegistration) {
|
|
this.paths.push(pathRegistration);
|
|
}
|
|
|
|
buildPaths() {
|
|
const paths: Record<string, Record<string, unknown>> = {};
|
|
for (const { method, path, request, responses, ...operation } of this.paths) {
|
|
const normalizedOperation: Record<string, unknown> = {
|
|
...operation,
|
|
responses: normalizeResponses(responses),
|
|
};
|
|
if (request?.params) {
|
|
normalizedOperation.parameters = parametersFromSchema(request.params, "path");
|
|
}
|
|
if (request?.query) {
|
|
normalizedOperation.parameters = [
|
|
...((normalizedOperation.parameters as unknown[]) ?? []),
|
|
...parametersFromSchema(request.query, "query"),
|
|
];
|
|
}
|
|
if (request?.body) {
|
|
normalizedOperation.requestBody = {
|
|
...request.body,
|
|
content: normalizeContent(request.body.content),
|
|
};
|
|
}
|
|
paths[path] ??= {};
|
|
paths[path][method] = normalizedOperation;
|
|
}
|
|
return paths;
|
|
}
|
|
|
|
buildComponents() {
|
|
return { schemas: this.schemas };
|
|
}
|
|
}
|
|
|
|
const registry = new OpenAPIRegistry();
|
|
|
|
// ─── Common schemas ──────────────────────────────────────────────────────────
|
|
|
|
const ErrorSchema = registry.register(
|
|
"Error",
|
|
z.object({ error: z.string() }),
|
|
);
|
|
|
|
const responses = {
|
|
ok: (schema: z.ZodTypeAny = z.record(z.unknown())) => ({
|
|
description: "Success",
|
|
content: { "application/json": { schema } },
|
|
}),
|
|
noContent: { description: "No content" },
|
|
badRequest: {
|
|
description: "Bad request",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
unauthorized: {
|
|
description: "Unauthorized",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
forbidden: {
|
|
description: "Forbidden",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
notFound: {
|
|
description: "Not found",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
unprocessable: {
|
|
description: "Unprocessable entity",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
serverError: {
|
|
description: "Internal server error",
|
|
content: { "application/json": { schema: ErrorSchema } },
|
|
},
|
|
};
|
|
|
|
const jsonBody = (schema: z.ZodTypeAny) => ({
|
|
content: { "application/json": { schema } },
|
|
required: true as const,
|
|
});
|
|
|
|
const r = responses;
|
|
|
|
function paramsSchemaFromPath(routePath: string): z.ZodObject<z.ZodRawShape> | undefined {
|
|
const names = [...routePath.matchAll(/\{([A-Za-z0-9_]+)\}/g)].map((match) => match[1]);
|
|
if (names.length === 0) return undefined;
|
|
const shape: z.ZodRawShape = {};
|
|
for (const name of names) {
|
|
shape[name] = z.string();
|
|
}
|
|
return z.object(shape);
|
|
}
|
|
|
|
function registerCurrentRoute(input: {
|
|
method: string;
|
|
path: string;
|
|
tags: string[];
|
|
summary: string;
|
|
query?: z.ZodTypeAny;
|
|
body?: z.ZodTypeAny;
|
|
responses?: Record<string, OpenApiResponse>;
|
|
}) {
|
|
const params = paramsSchemaFromPath(input.path);
|
|
const request = params || input.query || input.body
|
|
? {
|
|
...(params ? { params } : {}),
|
|
...(input.query ? { query: input.query } : {}),
|
|
...(input.body ? { body: jsonBody(input.body) } : {}),
|
|
}
|
|
: undefined;
|
|
registry.registerPath({
|
|
method: input.method,
|
|
path: input.path,
|
|
tags: input.tags,
|
|
summary: input.summary,
|
|
...(request ? { request } : {}),
|
|
responses: input.responses ?? { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
}
|
|
|
|
type OpenApiAuthLevel =
|
|
| "public"
|
|
| "authenticated"
|
|
| "board"
|
|
| "instance_admin";
|
|
|
|
const BOARD_SESSION_AUTH_SCHEME = "BoardSessionAuth";
|
|
const BOARD_API_KEY_AUTH_SCHEME = "BoardApiKeyAuth";
|
|
const AGENT_BEARER_AUTH_SCHEME = "AgentBearerAuth";
|
|
|
|
function securityRequirement(name: string): Record<string, string[]> {
|
|
return { [name]: [] };
|
|
}
|
|
|
|
const BOARD_SECURITY: Array<Record<string, string[]>> = [
|
|
securityRequirement(BOARD_SESSION_AUTH_SCHEME),
|
|
securityRequirement(BOARD_API_KEY_AUTH_SCHEME),
|
|
];
|
|
|
|
const AUTHENTICATED_SECURITY: Array<Record<string, string[]>> = [
|
|
...BOARD_SECURITY,
|
|
securityRequirement(AGENT_BEARER_AUTH_SCHEME),
|
|
];
|
|
|
|
const PUBLIC_OPERATIONS = new Set([
|
|
"GET /api/health",
|
|
"GET /api/openapi.json",
|
|
"GET /api/board-claim/{token}",
|
|
"POST /api/cli-auth/challenges",
|
|
"GET /api/cli-auth/challenges/{id}",
|
|
"POST /api/cli-auth/challenges/{id}/cancel",
|
|
"GET /api/invites/{token}",
|
|
"GET /api/invites/{token}/logo",
|
|
"GET /api/invites/{token}/onboarding",
|
|
"GET /api/invites/{token}/onboarding.txt",
|
|
"GET /api/invites/{token}/skills/index",
|
|
"GET /api/invites/{token}/skills/{skillName}",
|
|
"GET /api/invites/{token}/test-resolution",
|
|
"POST /api/invites/{token}/accept",
|
|
"POST /api/join-requests/{requestId}/claim-api-key",
|
|
]);
|
|
|
|
const BOARD_ONLY_PREFIXES = [
|
|
"/api/auth/",
|
|
"/api/admin/",
|
|
"/api/cloud-upstreams",
|
|
"/api/plugins",
|
|
"/api/instance/",
|
|
];
|
|
|
|
const BOARD_ONLY_OPERATIONS = new Set([
|
|
"GET /api/companies",
|
|
"POST /api/companies",
|
|
"GET /api/companies/stats",
|
|
"GET /api/companies/issues",
|
|
"POST /api/board-claim/{token}/claim",
|
|
"GET /api/cli-auth/me",
|
|
"POST /api/companies/{companyId}/invites",
|
|
"GET /api/companies/{companyId}/invites",
|
|
"POST /api/companies/{companyId}/openclaw/invite-prompt",
|
|
"GET /api/companies/{companyId}/join-requests",
|
|
"POST /api/companies/{companyId}/join-requests/{requestId}/approve",
|
|
"POST /api/companies/{companyId}/join-requests/{requestId}/reject",
|
|
"GET /api/companies/{companyId}/members",
|
|
"PATCH /api/companies/{companyId}/members/{memberId}",
|
|
"PATCH /api/companies/{companyId}/members/{memberId}/role-and-grants",
|
|
"POST /api/companies/{companyId}/members/{memberId}/archive",
|
|
"PATCH /api/companies/{companyId}/members/{memberId}/permissions",
|
|
"GET /api/companies/{companyId}/user-directory",
|
|
"GET /api/board-api-keys",
|
|
"POST /api/board-api-keys",
|
|
"DELETE /api/board-api-keys/{keyId}",
|
|
"POST /api/bootstrap/claim",
|
|
"GET /api/companies/{companyId}/resource-memberships/me",
|
|
"PUT /api/companies/{companyId}/resource-memberships/me/agents/{agentId}",
|
|
"PUT /api/companies/{companyId}/resource-memberships/me/projects/{projectId}",
|
|
"GET /api/companies/{companyId}/secret-provider-configs",
|
|
"POST /api/companies/{companyId}/secret-provider-configs",
|
|
"GET /api/companies/{companyId}/secret-providers/health",
|
|
"POST /api/companies/{companyId}/secret-provider-configs/discovery/preview",
|
|
"GET /api/secret-provider-configs/{id}",
|
|
"PATCH /api/secret-provider-configs/{id}",
|
|
"DELETE /api/secret-provider-configs/{id}",
|
|
"POST /api/secret-provider-configs/{id}/default",
|
|
"POST /api/secret-provider-configs/{id}/health",
|
|
"POST /api/companies/{companyId}/secrets/remote-import",
|
|
"POST /api/companies/{companyId}/secrets/remote-import/preview",
|
|
"GET /api/secrets/{id}/usage",
|
|
"GET /api/secrets/{id}/access-events",
|
|
"POST /api/health/dev-server/restart",
|
|
"POST /api/issues/{id}/interactions/{interactionId}/accept",
|
|
"POST /api/issues/{id}/interactions/{interactionId}/reject",
|
|
"POST /api/issues/{id}/interactions/{interactionId}/respond",
|
|
]);
|
|
|
|
const INSTANCE_ADMIN_OPERATIONS = new Set([
|
|
"POST /api/companies",
|
|
"POST /api/plugins/install",
|
|
"POST /api/instance/database-backups",
|
|
"POST /api/admin/users/{userId}/promote-instance-admin",
|
|
"POST /api/admin/users/{userId}/demote-instance-admin",
|
|
"PUT /api/admin/users/{userId}/company-access",
|
|
]);
|
|
|
|
const CREATED_OPERATIONS = new Set([
|
|
"POST /api/adapters/install",
|
|
"POST /api/companies/{companyId}/agent-hires",
|
|
"POST /api/companies/{companyId}/agents",
|
|
"POST /api/agents/{id}/keys",
|
|
"POST /api/companies/{companyId}/approvals",
|
|
"POST /api/approvals/{id}/comments",
|
|
"POST /api/companies/{companyId}/assets/images",
|
|
"POST /api/companies/{companyId}/logo",
|
|
"POST /api/cli-auth/challenges",
|
|
"POST /api/board-api-keys",
|
|
"POST /api/companies",
|
|
"POST /api/companies/{companyId}/invites",
|
|
"POST /api/companies/{companyId}/openclaw/invite-prompt",
|
|
"POST /api/companies/{companyId}/cost-events",
|
|
"POST /api/companies/{companyId}/finance-events",
|
|
"POST /api/companies/{companyId}/secret-provider-configs",
|
|
"POST /api/companies/{companyId}/environments",
|
|
"POST /api/companies/{companyId}/goals",
|
|
"POST /api/companies/{companyId}/labels",
|
|
"POST /api/issues/{id}/documents/{key}/annotations",
|
|
"POST /api/issues/{id}/documents/{key}/annotations/{threadId}/comments",
|
|
"POST /api/issues/{id}/work-products",
|
|
"POST /api/issues/{id}/low-trust/promotions",
|
|
"POST /api/issues/{id}/approvals",
|
|
"POST /api/companies/{companyId}/issues",
|
|
"POST /api/issues/{id}/children",
|
|
"POST /api/issues/{id}/interactions",
|
|
"POST /api/issues/{id}/comments",
|
|
"POST /api/companies/{companyId}/issues/{issueId}/attachments",
|
|
"POST /api/companies/{companyId}/projects",
|
|
"POST /api/projects/{id}/workspaces",
|
|
"POST /api/companies/{companyId}/routines",
|
|
"POST /api/routines/{id}/triggers",
|
|
"POST /api/companies/{companyId}/secrets",
|
|
"POST /api/companies/{companyId}/skills",
|
|
"POST /api/companies/{companyId}/skills/import",
|
|
"POST /api/join-requests/{requestId}/claim-api-key",
|
|
"POST /api/admin/users/{userId}/promote-instance-admin",
|
|
"POST /api/plugins/install",
|
|
"POST /api/instance/database-backups",
|
|
]);
|
|
|
|
const ACCEPTED_OPERATIONS = new Set([
|
|
"POST /api/companies/import",
|
|
"POST /api/health/dev-server/restart",
|
|
"POST /api/invites/{token}/accept",
|
|
]);
|
|
|
|
const FORBIDDEN_RESPONSE = {
|
|
description: "Forbidden",
|
|
content: {
|
|
"application/json": {
|
|
schema: { $ref: "#/components/schemas/Error" },
|
|
},
|
|
},
|
|
};
|
|
|
|
function operationKey(method: string, path: string) {
|
|
return `${method.toUpperCase()} ${path}`;
|
|
}
|
|
|
|
function isBoardOnlyOperation(method: string, path: string) {
|
|
const key = operationKey(method, path);
|
|
if (BOARD_ONLY_OPERATIONS.has(key)) return true;
|
|
return BOARD_ONLY_PREFIXES.some((prefix) => path.startsWith(prefix));
|
|
}
|
|
|
|
function resolveOperationAuthLevel(method: string, path: string): OpenApiAuthLevel {
|
|
const key = operationKey(method, path);
|
|
if (PUBLIC_OPERATIONS.has(key)) return "public";
|
|
if (INSTANCE_ADMIN_OPERATIONS.has(key)) return "instance_admin";
|
|
if (isBoardOnlyOperation(method, path)) return "board";
|
|
return "authenticated";
|
|
}
|
|
|
|
function applyOperationStatusOverride(
|
|
operation: Record<string, unknown>,
|
|
fromStatus: string,
|
|
toStatus: string,
|
|
) {
|
|
const responses = operation.responses as Record<string, unknown> | undefined;
|
|
if (!responses || !responses[fromStatus] || responses[toStatus]) return;
|
|
responses[toStatus] = responses[fromStatus];
|
|
delete responses[fromStatus];
|
|
}
|
|
|
|
function applyDocumentFixups(document: any): any {
|
|
document.components ??= {};
|
|
document.components.securitySchemes = {
|
|
[BOARD_SESSION_AUTH_SCHEME]: {
|
|
type: "apiKey",
|
|
in: "cookie",
|
|
name: "paperclip_session",
|
|
description:
|
|
"Board session cookie in authenticated mode. Paperclip uses Better Auth; cookie transport may vary by deployment.",
|
|
},
|
|
[BOARD_API_KEY_AUTH_SCHEME]: {
|
|
type: "http",
|
|
scheme: "bearer",
|
|
bearerFormat: "Board API Key",
|
|
description: "Board API key presented in the Authorization bearer header.",
|
|
},
|
|
[AGENT_BEARER_AUTH_SCHEME]: {
|
|
type: "http",
|
|
scheme: "bearer",
|
|
bearerFormat: "Agent API Key or Agent JWT",
|
|
description:
|
|
"Agent API key or Paperclip-issued local agent JWT presented in the Authorization bearer header.",
|
|
},
|
|
};
|
|
document.security = AUTHENTICATED_SECURITY;
|
|
|
|
for (const [path, pathItem] of Object.entries(document.paths ?? {})) {
|
|
for (const [method, operation] of Object.entries(pathItem as Record<string, any>)) {
|
|
const authLevel = resolveOperationAuthLevel(method, path);
|
|
if (authLevel === "public") {
|
|
operation.security = [];
|
|
} else if (authLevel === "authenticated") {
|
|
operation.security = AUTHENTICATED_SECURITY;
|
|
} else {
|
|
operation.security = BOARD_SECURITY;
|
|
}
|
|
|
|
operation["x-paperclip-authorization"] =
|
|
authLevel === "instance_admin"
|
|
? { actor: "board", instanceAdmin: true }
|
|
: authLevel === "board"
|
|
? { actor: "board" }
|
|
: authLevel === "authenticated"
|
|
? { actor: "board_or_agent" }
|
|
: { actor: "public" };
|
|
|
|
const key = operationKey(method, path);
|
|
if (authLevel !== "public") {
|
|
const responses = (operation.responses ??= {}) as Record<string, unknown>;
|
|
if (!responses["403"]) {
|
|
responses["403"] = FORBIDDEN_RESPONSE;
|
|
}
|
|
}
|
|
if (CREATED_OPERATIONS.has(key)) {
|
|
applyOperationStatusOverride(operation, "200", "201");
|
|
}
|
|
if (ACCEPTED_OPERATIONS.has(key)) {
|
|
applyOperationStatusOverride(operation, "200", "202");
|
|
}
|
|
}
|
|
}
|
|
|
|
return document;
|
|
}
|
|
|
|
// ─── Health ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/health",
|
|
tags: ["health"],
|
|
summary: "Health check",
|
|
responses: {
|
|
200: r.ok(z.object({
|
|
status: z.enum(["ok", "unhealthy"]),
|
|
version: z.string().optional(),
|
|
deploymentMode: z.string().optional(),
|
|
bootstrapStatus: z.enum(["ready", "bootstrap_pending"]).optional(),
|
|
bootstrapInviteActive: z.boolean().optional(),
|
|
})),
|
|
503: { description: "Service unavailable", content: { "application/json": { schema: ErrorSchema } } },
|
|
},
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/openapi.json",
|
|
tags: ["health"],
|
|
summary: "Get the generated OpenAPI document",
|
|
responses: { 200: r.ok() },
|
|
});
|
|
|
|
// ─── Companies ───────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies",
|
|
tags: ["companies"],
|
|
summary: "List companies",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies",
|
|
tags: ["companies"],
|
|
summary: "Create a company",
|
|
request: { body: jsonBody(createCompanySchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/stats",
|
|
tags: ["companies"],
|
|
summary: "Company stats",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}",
|
|
tags: ["companies"],
|
|
summary: "Get a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/artifacts",
|
|
tags: ["companies"],
|
|
summary: "List company artifacts",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
query: companyArtifactsQuerySchema,
|
|
},
|
|
responses: {
|
|
200: {
|
|
description: "Company artifact projection",
|
|
content: {
|
|
"application/json": {
|
|
schema: companyArtifactsResponseSchema,
|
|
},
|
|
},
|
|
},
|
|
401: r.unauthorized,
|
|
},
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}",
|
|
tags: ["companies"],
|
|
summary: "Update a company",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(updateCompanySchema.partial()),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/branding",
|
|
tags: ["companies"],
|
|
summary: "Update company branding",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(updateCompanyBrandingSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/archive",
|
|
tags: ["companies"],
|
|
summary: "Archive a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/companies/{companyId}",
|
|
tags: ["companies"],
|
|
summary: "Delete a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/feedback-traces",
|
|
tags: ["companies"],
|
|
summary: "List company feedback traces",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/exports",
|
|
tags: ["companies"],
|
|
summary: "Export company data",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/exports/preview",
|
|
tags: ["companies"],
|
|
summary: "Preview company export",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/imports/preview",
|
|
tags: ["companies"],
|
|
summary: "Preview company import",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/imports/apply",
|
|
tags: ["companies"],
|
|
summary: "Apply company import",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Teams Catalog ──────────────────────────────────────────────────────────
|
|
|
|
for (const route of [
|
|
["get", "/api/teams/catalog", "List catalog teams"],
|
|
["get", "/api/teams/catalog/{catalogId}/files", "Get catalog team file"],
|
|
["get", "/api/teams/catalog/{catalogId}", "Get catalog team"],
|
|
["get", "/api/companies/{companyId}/teams/catalog/installed", "List installed catalog teams"],
|
|
["post", "/api/companies/{companyId}/teams/catalog/{catalogId}/preview", "Preview catalog team install"],
|
|
["post", "/api/companies/{companyId}/teams/catalog/{catalogId}/install", "Install catalog team"],
|
|
] as const) {
|
|
registerCurrentRoute({
|
|
method: route[0],
|
|
path: route[1],
|
|
tags: ["teams"],
|
|
summary: route[2],
|
|
});
|
|
}
|
|
|
|
// ─── Agents ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/agents",
|
|
tags: ["agents"],
|
|
summary: "List agents in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/agents",
|
|
tags: ["agents"],
|
|
summary: "Create an agent",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createAgentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/agent-hires",
|
|
tags: ["agents"],
|
|
summary: "Hire an agent",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createAgentHireSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/agent-configurations",
|
|
tags: ["agents"],
|
|
summary: "List agent configurations for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/org",
|
|
tags: ["agents"],
|
|
summary: "Get org chart data",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/me",
|
|
tags: ["agents"],
|
|
summary: "Get the current agent",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/me/inbox-lite",
|
|
tags: ["agents"],
|
|
summary: "Get current agent inbox (lite)",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/me/inbox/mine",
|
|
tags: ["agents"],
|
|
summary: "Get current agent assigned inbox items",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}",
|
|
tags: ["agents"],
|
|
summary: "Get an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{id}",
|
|
tags: ["agents"],
|
|
summary: "Update an agent",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateAgentSchema.omit({ permissions: true })),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/agents/{id}",
|
|
tags: ["agents"],
|
|
summary: "Delete an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{id}/permissions",
|
|
tags: ["agents"],
|
|
summary: "Update agent permissions",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateAgentPermissionsSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{id}/instructions-path",
|
|
tags: ["agents"],
|
|
summary: "Update agent instructions path",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateAgentInstructionsPathSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/instructions-bundle",
|
|
tags: ["agents"],
|
|
summary: "Get agent instructions bundle",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{id}/instructions-bundle",
|
|
tags: ["agents"],
|
|
summary: "Update agent instructions bundle",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateAgentInstructionsBundleSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/instructions-bundle/file",
|
|
tags: ["agents"],
|
|
summary: "Get agent instructions file",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/agents/{id}/instructions-bundle/file",
|
|
tags: ["agents"],
|
|
summary: "Upsert agent instructions file",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(upsertAgentInstructionsFileSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/agents/{id}/instructions-bundle/file",
|
|
tags: ["agents"],
|
|
summary: "Delete agent instructions file",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/configuration",
|
|
tags: ["agents"],
|
|
summary: "Get agent configuration",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/config-revisions",
|
|
tags: ["agents"],
|
|
summary: "List agent config revisions",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/config-revisions/{revisionId}",
|
|
tags: ["agents"],
|
|
summary: "Get an agent config revision",
|
|
request: { params: z.object({ id: z.string(), revisionId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/config-revisions/{revisionId}/rollback",
|
|
tags: ["agents"],
|
|
summary: "Roll back to a config revision",
|
|
request: { params: z.object({ id: z.string(), revisionId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/runtime-state",
|
|
tags: ["agents"],
|
|
summary: "Get agent runtime state",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/runtime-state/reset-session",
|
|
tags: ["agents"],
|
|
summary: "Reset agent session",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resetAgentSessionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/task-sessions",
|
|
tags: ["agents"],
|
|
summary: "List agent task sessions",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/skills",
|
|
tags: ["agents"],
|
|
summary: "List agent skills",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/skills/sync",
|
|
tags: ["agents"],
|
|
summary: "Sync desired skills onto an agent configuration",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(agentSkillSyncSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/agents/{id}/keys",
|
|
tags: ["agents"],
|
|
summary: "List agent API keys",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/keys",
|
|
tags: ["agents"],
|
|
summary: "Create an agent API key",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createAgentKeySchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/agents/{id}/keys/{keyId}",
|
|
tags: ["agents"],
|
|
summary: "Delete an agent API key",
|
|
request: { params: z.object({ id: z.string(), keyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/wakeup",
|
|
tags: ["agents"],
|
|
summary: "Wake up an agent",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(wakeAgentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/pause",
|
|
tags: ["agents"],
|
|
summary: "Pause an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/resume",
|
|
tags: ["agents"],
|
|
summary: "Resume an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/terminate",
|
|
tags: ["agents"],
|
|
summary: "Terminate an agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/instance/scheduler-heartbeats",
|
|
tags: ["agents"],
|
|
summary: "List scheduler heartbeats",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Adapters ────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/adapters/{type}/models",
|
|
tags: ["adapters"],
|
|
summary: "List models for an adapter type",
|
|
request: { params: z.object({ companyId: z.string(), type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/adapters/{type}/detect-model",
|
|
tags: ["adapters"],
|
|
summary: "Detect active model for an adapter",
|
|
request: { params: z.object({ companyId: z.string(), type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/adapters/{type}/test-environment",
|
|
tags: ["adapters"],
|
|
summary: "Validate adapter environment access for a company",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), type: z.string() }),
|
|
body: jsonBody(testAdapterEnvironmentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Issues ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/issues",
|
|
tags: ["issues"],
|
|
summary: "List issues in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/issues",
|
|
tags: ["issues"],
|
|
summary: "Create an issue",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createIssueSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}",
|
|
tags: ["issues"],
|
|
summary: "Get an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/issues/{id}",
|
|
tags: ["issues"],
|
|
summary: "Update an issue",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateIssueSchema.partial()),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}",
|
|
tags: ["issues"],
|
|
summary: "Delete an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/heartbeat-context",
|
|
tags: ["issues"],
|
|
summary: "Get issue heartbeat context",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/work-products",
|
|
tags: ["issues"],
|
|
summary: "List issue work products",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/work-products",
|
|
tags: ["issues"],
|
|
summary: "Create an issue work product",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createIssueWorkProductSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/work-products/{id}",
|
|
tags: ["issues"],
|
|
summary: "Update a work product",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateIssueWorkProductSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/work-products/{id}",
|
|
tags: ["issues"],
|
|
summary: "Delete a work product",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/documents",
|
|
tags: ["issues"],
|
|
summary: "List issue documents",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/documents/{key}",
|
|
tags: ["issues"],
|
|
summary: "Get an issue document",
|
|
request: { params: z.object({ id: z.string(), key: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/issues/{id}/documents/{key}",
|
|
tags: ["issues"],
|
|
summary: "Upsert an issue document",
|
|
request: {
|
|
params: z.object({ id: z.string(), key: z.string() }),
|
|
body: jsonBody(upsertIssueDocumentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/documents/{key}",
|
|
tags: ["issues"],
|
|
summary: "Delete an issue document",
|
|
request: { params: z.object({ id: z.string(), key: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/documents/{key}/revisions",
|
|
tags: ["issues"],
|
|
summary: "List issue document revisions",
|
|
request: { params: z.object({ id: z.string(), key: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/documents/{key}/revisions/{revisionId}/restore",
|
|
tags: ["issues"],
|
|
summary: "Restore a document revision",
|
|
request: {
|
|
params: z.object({ id: z.string(), key: z.string(), revisionId: z.string() }),
|
|
body: jsonBody(restoreIssueDocumentRevisionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/comments",
|
|
tags: ["issues"],
|
|
summary: "List issue comments",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/comments",
|
|
tags: ["issues"],
|
|
summary: "Add a comment to an issue",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(addIssueCommentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/comments/{commentId}",
|
|
tags: ["issues"],
|
|
summary: "Delete an issue comment",
|
|
request: { params: z.object({ id: z.string(), commentId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/approvals",
|
|
tags: ["issues"],
|
|
summary: "List issue approvals",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/approvals",
|
|
tags: ["issues"],
|
|
summary: "Link an approval to an issue",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(linkIssueApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/approvals/{approvalId}",
|
|
tags: ["issues"],
|
|
summary: "Unlink an approval from an issue",
|
|
request: { params: z.object({ id: z.string(), approvalId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/checkout",
|
|
tags: ["issues"],
|
|
summary: "Check out an issue",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(checkoutIssueSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/release",
|
|
tags: ["issues"],
|
|
summary: "Release an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/read",
|
|
tags: ["issues"],
|
|
summary: "Mark an issue as read",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/read",
|
|
tags: ["issues"],
|
|
summary: "Mark an issue as unread",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/inbox-archive",
|
|
tags: ["issues"],
|
|
summary: "Archive issue from inbox",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/issues/{id}/inbox-archive",
|
|
tags: ["issues"],
|
|
summary: "Un-archive issue from inbox",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/feedback-votes",
|
|
tags: ["issues"],
|
|
summary: "List issue feedback votes",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/feedback-votes",
|
|
tags: ["issues"],
|
|
summary: "Upsert a feedback vote",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(upsertIssueFeedbackVoteSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/feedback-traces",
|
|
tags: ["issues"],
|
|
summary: "List issue feedback traces",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/feedback-traces/{traceId}",
|
|
tags: ["issues"],
|
|
summary: "Get a feedback trace",
|
|
request: { params: z.object({ traceId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/feedback-traces/{traceId}/bundle",
|
|
tags: ["issues"],
|
|
summary: "Get a feedback trace bundle",
|
|
request: { params: z.object({ traceId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/attachments",
|
|
tags: ["issues"],
|
|
summary: "List issue attachments",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/labels",
|
|
tags: ["issues"],
|
|
summary: "List labels in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/labels",
|
|
tags: ["issues"],
|
|
summary: "Create a label",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createIssueLabelSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/labels/{labelId}",
|
|
tags: ["issues"],
|
|
summary: "Delete a label",
|
|
request: { params: z.object({ labelId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Projects ────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/projects",
|
|
tags: ["projects"],
|
|
summary: "List projects in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/projects",
|
|
tags: ["projects"],
|
|
summary: "Create a project",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createProjectSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/projects/{id}",
|
|
tags: ["projects"],
|
|
summary: "Get a project",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/projects/{id}",
|
|
tags: ["projects"],
|
|
summary: "Update a project",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateProjectSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/projects/{id}",
|
|
tags: ["projects"],
|
|
summary: "Delete a project",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/projects/{id}/workspaces",
|
|
tags: ["projects"],
|
|
summary: "List project workspaces",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/projects/{id}/workspaces",
|
|
tags: ["projects"],
|
|
summary: "Create a project workspace",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createProjectWorkspaceSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/projects/{id}/workspaces/{workspaceId}",
|
|
tags: ["projects"],
|
|
summary: "Update a project workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), workspaceId: z.string() }),
|
|
body: jsonBody(updateProjectWorkspaceSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/projects/{id}/workspaces/{workspaceId}",
|
|
tags: ["projects"],
|
|
summary: "Delete a project workspace",
|
|
request: { params: z.object({ id: z.string(), workspaceId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Routines ────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/routines",
|
|
tags: ["routines"],
|
|
summary: "List routines in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/routines",
|
|
tags: ["routines"],
|
|
summary: "Create a routine",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createRoutineSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/routines/{id}",
|
|
tags: ["routines"],
|
|
summary: "Get a routine",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/routines/{id}",
|
|
tags: ["routines"],
|
|
summary: "Update a routine",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateRoutineSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/routines/{id}/runs",
|
|
tags: ["routines"],
|
|
summary: "List runs for a routine",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/routines/{id}/run",
|
|
tags: ["routines"],
|
|
summary: "Manually run a routine",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(runRoutineSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/routines/{id}/triggers",
|
|
tags: ["routines"],
|
|
summary: "Create a routine trigger",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createRoutineTriggerSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/routine-triggers/{id}",
|
|
tags: ["routines"],
|
|
summary: "Update a routine trigger",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateRoutineTriggerSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/routine-triggers/{id}",
|
|
tags: ["routines"],
|
|
summary: "Delete a routine trigger",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/routine-triggers/{id}/rotate-secret",
|
|
tags: ["routines"],
|
|
summary: "Rotate a routine trigger secret",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(rotateRoutineTriggerSecretSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/routine-triggers/public/{publicId}/fire",
|
|
tags: ["routines"],
|
|
summary: "Fire a public routine trigger",
|
|
request: { params: z.object({ publicId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Goals ───────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/goals",
|
|
tags: ["goals"],
|
|
summary: "List goals in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/goals",
|
|
tags: ["goals"],
|
|
summary: "Create a goal",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createGoalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/goals/{id}",
|
|
tags: ["goals"],
|
|
summary: "Get a goal",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/goals/{id}",
|
|
tags: ["goals"],
|
|
summary: "Update a goal",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateGoalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/goals/{id}",
|
|
tags: ["goals"],
|
|
summary: "Delete a goal",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Secrets ─────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/secret-providers",
|
|
tags: ["secrets"],
|
|
summary: "List secret providers",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/secrets",
|
|
tags: ["secrets"],
|
|
summary: "List secrets in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/secrets",
|
|
tags: ["secrets"],
|
|
summary: "Create a secret",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createSecretSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/secrets/{id}",
|
|
tags: ["secrets"],
|
|
summary: "Update a secret",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateSecretSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/secrets/{id}/rotate",
|
|
tags: ["secrets"],
|
|
summary: "Rotate a secret",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(rotateSecretSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/secrets/{id}",
|
|
tags: ["secrets"],
|
|
summary: "Delete a secret",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Approvals ───────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/approvals",
|
|
tags: ["approvals"],
|
|
summary: "List approvals in a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/approvals",
|
|
tags: ["approvals"],
|
|
summary: "Create an approval",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/approvals/{id}",
|
|
tags: ["approvals"],
|
|
summary: "Get an approval",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/approvals/{id}/issues",
|
|
tags: ["approvals"],
|
|
summary: "List issues linked to an approval",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/approve",
|
|
tags: ["approvals"],
|
|
summary: "Approve an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resolveApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/reject",
|
|
tags: ["approvals"],
|
|
summary: "Reject an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resolveApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/request-revision",
|
|
tags: ["approvals"],
|
|
summary: "Request revision on an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(requestApprovalRevisionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/resubmit",
|
|
tags: ["approvals"],
|
|
summary: "Resubmit an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resubmitApprovalSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/approvals/{id}/comments",
|
|
tags: ["approvals"],
|
|
summary: "List approval comments",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/approvals/{id}/comments",
|
|
tags: ["approvals"],
|
|
summary: "Add a comment to an approval",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(addApprovalCommentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Costs ───────────────────────────────────────────────────────────────────
|
|
|
|
const costSummaryPaths = [
|
|
"summary", "by-agent", "by-agent-model", "by-provider",
|
|
"by-biller", "by-project", "finance-summary", "finance-by-biller",
|
|
"finance-by-kind", "finance-events", "window-spend", "quota-windows",
|
|
] as const;
|
|
|
|
for (const segment of costSummaryPaths) {
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: `/api/companies/{companyId}/costs/${segment}`,
|
|
tags: ["costs"],
|
|
summary: `Cost report: ${segment}`,
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
}
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/cost-events",
|
|
tags: ["costs"],
|
|
summary: "Record a cost event",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createCostEventSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/finance-events",
|
|
tags: ["costs"],
|
|
summary: "Record a finance event",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createFinanceEventSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/budgets/policies",
|
|
tags: ["costs"],
|
|
summary: "Create or update a budget policy",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(upsertBudgetPolicySchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/budget-incidents/{incidentId}/resolve",
|
|
tags: ["costs"],
|
|
summary: "Resolve a budget incident",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), incidentId: z.string() }),
|
|
body: jsonBody(resolveBudgetIncidentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/budgets/overview",
|
|
tags: ["costs"],
|
|
summary: "Get budget overview",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/budgets",
|
|
tags: ["costs"],
|
|
summary: "Update company budget",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(updateBudgetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/agents/{agentId}/budgets",
|
|
tags: ["costs"],
|
|
summary: "Update agent budget",
|
|
request: {
|
|
params: z.object({ agentId: z.string() }),
|
|
body: jsonBody(updateBudgetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Activity ────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/activity",
|
|
tags: ["activity"],
|
|
summary: "List company activity",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/activity",
|
|
tags: ["activity"],
|
|
summary: "Create an activity entry",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
actorType: z.enum(["agent", "user", "system", "plugin"]).optional(),
|
|
actorId: z.string().min(1),
|
|
action: z.string().min(1),
|
|
entityType: z.string().min(1),
|
|
entityId: z.string().min(1),
|
|
agentId: z.string().uuid().optional().nullable(),
|
|
details: z.record(z.unknown()).optional().nullable(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/activity",
|
|
tags: ["activity"],
|
|
summary: "List activity for an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/runs",
|
|
tags: ["activity"],
|
|
summary: "List runs for an issue",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}/issues",
|
|
tags: ["activity"],
|
|
summary: "List issues for a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Dashboard ───────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/dashboard",
|
|
tags: ["dashboard"],
|
|
summary: "Get dashboard data",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Sidebar ─────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/sidebar-badges",
|
|
tags: ["sidebar"],
|
|
summary: "Get sidebar badge counts",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/sidebar-preferences/me",
|
|
tags: ["sidebar"],
|
|
summary: "Get current user sidebar preferences",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/sidebar-preferences/me",
|
|
tags: ["sidebar"],
|
|
summary: "Update current user sidebar preferences",
|
|
request: { body: jsonBody(upsertSidebarOrderPreferenceSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/sidebar-preferences/me",
|
|
tags: ["sidebar"],
|
|
summary: "Get sidebar preferences for company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/companies/{companyId}/sidebar-preferences/me",
|
|
tags: ["sidebar"],
|
|
summary: "Update sidebar preferences for company",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(upsertSidebarOrderPreferenceSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Inbox dismissals ────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/inbox-dismissals",
|
|
tags: ["inbox"],
|
|
summary: "List inbox dismissals",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/inbox-dismissals",
|
|
tags: ["inbox"],
|
|
summary: "Create an inbox dismissal",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
itemKey: z.string().trim().min(1).regex(/^(approval|join|run):.+$/, "Unsupported inbox item key"),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Instance settings ────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/instance/settings/general",
|
|
tags: ["instance"],
|
|
summary: "Get general instance settings",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/instance/settings/general",
|
|
tags: ["instance"],
|
|
summary: "Update general instance settings",
|
|
request: { body: jsonBody(patchInstanceGeneralSettingsSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/instance/settings/experimental",
|
|
tags: ["instance"],
|
|
summary: "Get experimental instance settings",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/instance/settings/experimental",
|
|
tags: ["instance"],
|
|
summary: "Update experimental instance settings",
|
|
request: { body: jsonBody(patchInstanceExperimentalSettingsSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Access / invites / members ───────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/invites",
|
|
tags: ["access"],
|
|
summary: "List company invites",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/invites",
|
|
tags: ["access"],
|
|
summary: "Create a company invite",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createCompanyInviteSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/join-requests",
|
|
tags: ["access"],
|
|
summary: "List company join requests",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/join-requests/{requestId}/approve",
|
|
tags: ["access"],
|
|
summary: "Approve a company join request",
|
|
request: { params: z.object({ companyId: z.string(), requestId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/join-requests/{requestId}/reject",
|
|
tags: ["access"],
|
|
summary: "Reject a company join request",
|
|
request: { params: z.object({ companyId: z.string(), requestId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/invites/{inviteId}/revoke",
|
|
tags: ["access"],
|
|
summary: "Revoke an invite",
|
|
request: { params: z.object({ inviteId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}",
|
|
tags: ["access"],
|
|
summary: "Get an invite by token",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/invites/{token}/accept",
|
|
tags: ["access"],
|
|
summary: "Accept an invite and create or replay a join request",
|
|
request: {
|
|
params: z.object({ token: z.string() }),
|
|
body: jsonBody(acceptInviteSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/members",
|
|
tags: ["access"],
|
|
summary: "List company members",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/members/{memberId}",
|
|
tags: ["access"],
|
|
summary: "Update a company member status or role",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), memberId: z.string() }),
|
|
body: jsonBody(updateCompanyMemberSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/members/{memberId}/role-and-grants",
|
|
tags: ["access"],
|
|
summary: "Update a company member role and explicit grants",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), memberId: z.string() }),
|
|
body: jsonBody(updateCompanyMemberWithPermissionsSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/members/{memberId}/archive",
|
|
tags: ["access"],
|
|
summary: "Archive a company member",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), memberId: z.string() }),
|
|
body: jsonBody(archiveCompanyMemberSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/members/{memberId}/permissions",
|
|
tags: ["access"],
|
|
summary: "Update explicit company member permissions",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), memberId: z.string() }),
|
|
body: jsonBody(updateMemberPermissionsSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/user-directory",
|
|
tags: ["access"],
|
|
summary: "Get company user directory",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/cli-auth/me",
|
|
tags: ["access"],
|
|
summary: "Get current CLI auth session",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/openclaw/invite-prompt",
|
|
tags: ["access"],
|
|
summary: "Create an OpenClaw invite prompt bundle",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createOpenClawInvitePromptSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/cli-auth/challenges",
|
|
tags: ["access"],
|
|
summary: "Create a CLI auth challenge",
|
|
request: { body: jsonBody(createCliAuthChallengeSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/cli-auth/challenges/{id}/approve",
|
|
tags: ["access"],
|
|
summary: "Approve a CLI auth challenge",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resolveCliAuthChallengeSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/cli-auth/challenges/{id}/cancel",
|
|
tags: ["access"],
|
|
summary: "Cancel a CLI auth challenge",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(resolveCliAuthChallengeSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/cli-auth/revoke-current",
|
|
tags: ["access"],
|
|
summary: "Revoke current CLI auth session",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/skills/available",
|
|
tags: ["access"],
|
|
summary: "List available skills",
|
|
responses: { 200: r.ok() },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/skills/index",
|
|
tags: ["access"],
|
|
summary: "Get skills index",
|
|
responses: { 200: r.ok() },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/skills/{skillName}",
|
|
tags: ["access"],
|
|
summary: "Get a skill by name",
|
|
request: { params: z.object({ skillName: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/join-requests/{requestId}/claim-api-key",
|
|
tags: ["access"],
|
|
summary: "Claim the initial API key for an approved agent join request",
|
|
request: {
|
|
params: z.object({ requestId: z.string() }),
|
|
body: jsonBody(claimJoinRequestApiKeySchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 403: r.forbidden, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/admin/users",
|
|
tags: ["admin"],
|
|
summary: "List all users (admin)",
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
// ─── Auth / profile ──────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/auth/get-session",
|
|
tags: ["auth"],
|
|
summary: "Get current session",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/auth/profile",
|
|
tags: ["auth"],
|
|
summary: "Get current user profile",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/auth/profile",
|
|
tags: ["auth"],
|
|
summary: "Update current user profile",
|
|
request: { body: jsonBody(updateCurrentUserProfileSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/users/{userSlug}/profile",
|
|
tags: ["auth"],
|
|
summary: "Get a user profile within a company",
|
|
request: { params: z.object({ companyId: z.string(), userSlug: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Heartbeat runs ──────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/heartbeat-runs",
|
|
tags: ["runs"],
|
|
summary: "List heartbeat runs for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/live-runs",
|
|
tags: ["runs"],
|
|
summary: "List live runs for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{issueId}/live-runs",
|
|
tags: ["runs"],
|
|
summary: "List live runs for an issue",
|
|
request: { params: z.object({ issueId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{issueId}/active-run",
|
|
tags: ["runs"],
|
|
summary: "Get active run for an issue",
|
|
request: { params: z.object({ issueId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}",
|
|
tags: ["runs"],
|
|
summary: "Get a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/heartbeat-runs/{runId}/cancel",
|
|
tags: ["runs"],
|
|
summary: "Cancel a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/heartbeat-runs/{runId}/watchdog-decisions",
|
|
tags: ["runs"],
|
|
summary: "Submit watchdog decisions for a run",
|
|
request: {
|
|
params: z.object({ runId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
decision: z.enum(["snooze", "continue", "dismissed_false_positive"]),
|
|
evaluationIssueId: z.string().optional().nullable(),
|
|
reason: z.string().optional().nullable(),
|
|
snoozedUntil: z.string().datetime().optional().nullable(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}/events",
|
|
tags: ["runs"],
|
|
summary: "Get events for a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}/log",
|
|
tags: ["runs"],
|
|
summary: "Get log for a heartbeat run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/heartbeat-runs/{runId}/workspace-operations",
|
|
tags: ["runs"],
|
|
summary: "List workspace operations for a run",
|
|
request: { params: z.object({ runId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/workspace-operations/{operationId}/log",
|
|
tags: ["runs"],
|
|
summary: "Get log for a workspace operation",
|
|
request: { params: z.object({ operationId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Agent runs & heartbeat ───────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/approve",
|
|
tags: ["agents"],
|
|
summary: "Approve a pending agent action",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/heartbeat/invoke",
|
|
tags: ["agents"],
|
|
summary: "Invoke agent heartbeat",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/agents/{id}/claude-login",
|
|
tags: ["agents"],
|
|
summary: "Trigger Claude login for agent",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Issue interactions & tree ───────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/interactions",
|
|
tags: ["issues"],
|
|
summary: "List issue thread interactions",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions",
|
|
tags: ["issues"],
|
|
summary: "Create an issue thread interaction",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createIssueThreadInteractionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions/{interactionId}/accept",
|
|
tags: ["issues"],
|
|
summary: "Accept an issue thread interaction",
|
|
request: {
|
|
params: z.object({ id: z.string(), interactionId: z.string() }),
|
|
body: jsonBody(acceptIssueThreadInteractionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions/{interactionId}/reject",
|
|
tags: ["issues"],
|
|
summary: "Reject an issue thread interaction",
|
|
request: {
|
|
params: z.object({ id: z.string(), interactionId: z.string() }),
|
|
body: jsonBody(rejectIssueThreadInteractionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions/{interactionId}/respond",
|
|
tags: ["issues"],
|
|
summary: "Answer questions on an issue thread interaction",
|
|
request: {
|
|
params: z.object({ id: z.string(), interactionId: z.string() }),
|
|
body: jsonBody(respondIssueThreadInteractionSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/children",
|
|
tags: ["issues"],
|
|
summary: "Create child issues",
|
|
request: { params: z.object({ id: z.string() }), body: jsonBody(createChildIssueSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/admin/force-release",
|
|
tags: ["issues"],
|
|
summary: "Force-release an issue (admin)",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/tree-control/state",
|
|
tags: ["issues"],
|
|
summary: "Get issue tree control state",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/tree-control/preview",
|
|
tags: ["issues"],
|
|
summary: "Preview issue tree control changes",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(previewIssueTreeControlSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/tree-holds",
|
|
tags: ["issues"],
|
|
summary: "List issue tree holds",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/tree-holds",
|
|
tags: ["issues"],
|
|
summary: "Create an issue tree hold",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(createIssueTreeHoldSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/tree-holds/{holdId}",
|
|
tags: ["issues"],
|
|
summary: "Get an issue tree hold",
|
|
request: { params: z.object({ id: z.string(), holdId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/issues/{id}/tree-holds/{holdId}/release",
|
|
tags: ["issues"],
|
|
summary: "Release an issue tree hold",
|
|
request: {
|
|
params: z.object({ id: z.string(), holdId: z.string() }),
|
|
body: jsonBody(releaseIssueTreeHoldSchema),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Attachments ──────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/issues/{issueId}/attachments",
|
|
tags: ["assets"],
|
|
summary: "Upload an attachment to an issue",
|
|
request: { params: z.object({ companyId: z.string(), issueId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/attachments/{attachmentId}/content",
|
|
tags: ["assets"],
|
|
summary: "Download attachment content",
|
|
request: { params: z.object({ attachmentId: z.string() }) },
|
|
responses: { 200: { description: "File content" }, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/attachments/{attachmentId}",
|
|
tags: ["assets"],
|
|
summary: "Delete an attachment",
|
|
request: { params: z.object({ attachmentId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Assets ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/assets/images",
|
|
tags: ["assets"],
|
|
summary: "Upload an image asset",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/logo",
|
|
tags: ["assets"],
|
|
summary: "Upload company logo",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/assets/{assetId}/content",
|
|
tags: ["assets"],
|
|
summary: "Download asset content",
|
|
request: { params: z.object({ assetId: z.string() }) },
|
|
responses: { 200: { description: "File content" }, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Company skills ───────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/skills",
|
|
tags: ["skills"],
|
|
summary: "List skills for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/skills/{skillId}",
|
|
tags: ["skills"],
|
|
summary: "Get a company skill",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/skills/{skillId}/update-status",
|
|
tags: ["skills"],
|
|
summary: "Get skill update status",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/skills/{skillId}/files",
|
|
tags: ["skills"],
|
|
summary: "List skill files",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/skills",
|
|
tags: ["skills"],
|
|
summary: "Create a company skill",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(companySkillCreateSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/companies/{companyId}/skills/{skillId}/files",
|
|
tags: ["skills"],
|
|
summary: "Update a skill file",
|
|
request: {
|
|
params: z.object({ companyId: z.string(), skillId: z.string() }),
|
|
body: jsonBody(companySkillFileUpdateSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/skills/import",
|
|
tags: ["skills"],
|
|
summary: "Import a skill",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(companySkillImportSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/skills/scan-projects",
|
|
tags: ["skills"],
|
|
summary: "Scan project for skills",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(companySkillProjectScanRequestSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/skills/{skillId}/install-update",
|
|
tags: ["skills"],
|
|
summary: "Install a skill update",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/companies/{companyId}/skills/{skillId}",
|
|
tags: ["skills"],
|
|
summary: "Delete a company skill",
|
|
request: { params: z.object({ companyId: z.string(), skillId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Execution workspaces ─────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/execution-workspaces",
|
|
tags: ["execution-workspaces"],
|
|
summary: "List execution workspaces for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/execution-workspaces/{id}",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Get an execution workspace",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/execution-workspaces/{id}/close-readiness",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Check close-readiness of a workspace",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/execution-workspaces/{id}/workspace-operations",
|
|
tags: ["execution-workspaces"],
|
|
summary: "List workspace operations",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/execution-workspaces/{id}",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Update an execution workspace",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateExecutionWorkspaceSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/execution-workspaces/{id}/runtime-services/{action}",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Control a runtime service in a workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), action: z.string() }),
|
|
body: jsonBody(workspaceRuntimeControlTargetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/execution-workspaces/{id}/runtime-commands/{action}",
|
|
tags: ["execution-workspaces"],
|
|
summary: "Run a runtime command in a workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), action: z.string() }),
|
|
body: jsonBody(workspaceRuntimeControlTargetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Environments ─────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/environments",
|
|
tags: ["environments"],
|
|
summary: "List environments for a company",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/environments/capabilities",
|
|
tags: ["environments"],
|
|
summary: "Get environment capabilities",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/environments",
|
|
tags: ["environments"],
|
|
summary: "Create an environment",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(createEnvironmentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/environments/{id}",
|
|
tags: ["environments"],
|
|
summary: "Get an environment",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/environments/{id}/leases",
|
|
tags: ["environments"],
|
|
summary: "List leases for an environment",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/environment-leases/{leaseId}",
|
|
tags: ["environments"],
|
|
summary: "Get an environment lease",
|
|
request: { params: z.object({ leaseId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/environments/{id}",
|
|
tags: ["environments"],
|
|
summary: "Update an environment",
|
|
request: {
|
|
params: z.object({ id: z.string() }),
|
|
body: jsonBody(updateEnvironmentSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/environments/{id}",
|
|
tags: ["environments"],
|
|
summary: "Delete an environment",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/environments/{id}/probe",
|
|
tags: ["environments"],
|
|
summary: "Probe an environment",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/environments/probe-config",
|
|
tags: ["environments"],
|
|
summary: "Probe environment config",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(probeEnvironmentConfigSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Adapters (full) ──────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/adapters",
|
|
tags: ["adapters"],
|
|
summary: "List all adapters",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/adapters/install",
|
|
tags: ["adapters"],
|
|
summary: "Install an adapter",
|
|
request: {
|
|
body: jsonBody(z.object({
|
|
packageName: z.string(),
|
|
isLocalPath: z.boolean().optional(),
|
|
version: z.string().optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/adapters/{type}",
|
|
tags: ["adapters"],
|
|
summary: "Enable or disable an adapter",
|
|
request: {
|
|
params: z.object({ type: z.string() }),
|
|
body: jsonBody(z.object({ disabled: z.boolean() })),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "patch",
|
|
path: "/api/adapters/{type}/override",
|
|
tags: ["adapters"],
|
|
summary: "Pause or resume an adapter's override of a builtin",
|
|
request: {
|
|
params: z.object({ type: z.string() }),
|
|
body: jsonBody(z.object({ paused: z.boolean() })),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/adapters/{type}",
|
|
tags: ["adapters"],
|
|
summary: "Delete an adapter",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/adapters/{type}/reload",
|
|
tags: ["adapters"],
|
|
summary: "Reload an adapter",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/adapters/{type}/reinstall",
|
|
tags: ["adapters"],
|
|
summary: "Reinstall an adapter",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/adapters/{type}/config-schema",
|
|
tags: ["adapters"],
|
|
summary: "Get adapter config schema",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Plugins ──────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins",
|
|
tags: ["plugins"],
|
|
summary: "List installed plugins",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/examples",
|
|
tags: ["plugins"],
|
|
summary: "List example plugins",
|
|
responses: { 200: r.ok() },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/ui-contributions",
|
|
tags: ["plugins"],
|
|
summary: "List plugin UI contributions",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/tools",
|
|
tags: ["plugins"],
|
|
summary: "List plugin tools",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/tools/execute",
|
|
tags: ["plugins"],
|
|
summary: "Execute a plugin tool",
|
|
request: {
|
|
body: jsonBody(z.object({
|
|
tool: z.string(),
|
|
parameters: z.record(z.unknown()).optional(),
|
|
runContext: z.object({
|
|
agentId: z.string(),
|
|
runId: z.string(),
|
|
companyId: z.string(),
|
|
projectId: z.string(),
|
|
}),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/install",
|
|
tags: ["plugins"],
|
|
summary: "Install a plugin",
|
|
request: {
|
|
body: jsonBody(z.object({
|
|
packageName: z.string(),
|
|
version: z.string().optional(),
|
|
isLocalPath: z.boolean().optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}",
|
|
tags: ["plugins"],
|
|
summary: "Get a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "delete",
|
|
path: "/api/plugins/{pluginId}",
|
|
tags: ["plugins"],
|
|
summary: "Delete a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/enable",
|
|
tags: ["plugins"],
|
|
summary: "Enable a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/disable",
|
|
tags: ["plugins"],
|
|
summary: "Disable a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/health",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin health",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/logs",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin logs",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/upgrade",
|
|
tags: ["plugins"],
|
|
summary: "Upgrade a plugin",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/config",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin config",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/config",
|
|
tags: ["plugins"],
|
|
summary: "Set plugin config",
|
|
request: {
|
|
params: z.object({ pluginId: z.string() }),
|
|
body: jsonBody(z.object({ configJson: z.record(z.unknown()) })),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/config/test",
|
|
tags: ["plugins"],
|
|
summary: "Test plugin config",
|
|
request: {
|
|
params: z.object({ pluginId: z.string() }),
|
|
body: jsonBody(z.object({ configJson: z.record(z.unknown()) })),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/jobs",
|
|
tags: ["plugins"],
|
|
summary: "List plugin jobs",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/jobs/{jobId}/runs",
|
|
tags: ["plugins"],
|
|
summary: "List runs for a plugin job",
|
|
request: { params: z.object({ pluginId: z.string(), jobId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/jobs/{jobId}/trigger",
|
|
tags: ["plugins"],
|
|
summary: "Trigger a plugin job",
|
|
request: { params: z.object({ pluginId: z.string(), jobId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/webhooks/{endpointKey}",
|
|
tags: ["plugins"],
|
|
summary: "Deliver an external webhook payload to a plugin",
|
|
request: {
|
|
params: z.object({ pluginId: z.string(), endpointKey: z.string() }),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/dashboard",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin dashboard data",
|
|
request: { params: z.object({ pluginId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/bridge/data",
|
|
tags: ["plugins"],
|
|
summary: "Send data via plugin bridge",
|
|
request: {
|
|
params: z.object({ pluginId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
key: z.string(),
|
|
companyId: z.string().optional(),
|
|
params: z.record(z.unknown()).optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/bridge/action",
|
|
tags: ["plugins"],
|
|
summary: "Send action via plugin bridge",
|
|
request: {
|
|
params: z.object({ pluginId: z.string() }),
|
|
body: jsonBody(z.object({
|
|
key: z.string(),
|
|
companyId: z.string().optional(),
|
|
params: z.record(z.unknown()).optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/data/{key}",
|
|
tags: ["plugins"],
|
|
summary: "Get plugin data by key (URL-keyed bridge)",
|
|
request: {
|
|
params: z.object({ pluginId: z.string(), key: z.string() }),
|
|
body: jsonBody(z.object({
|
|
companyId: z.string().optional(),
|
|
params: z.record(z.unknown()).optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/plugins/{pluginId}/actions/{key}",
|
|
tags: ["plugins"],
|
|
summary: "Invoke a plugin action (URL-keyed bridge)",
|
|
request: {
|
|
params: z.object({ pluginId: z.string(), key: z.string() }),
|
|
body: jsonBody(z.object({
|
|
companyId: z.string().optional(),
|
|
params: z.record(z.unknown()).optional(),
|
|
})),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Instance database backups ────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/instance/database-backups",
|
|
tags: ["instance"],
|
|
summary: "Trigger a database backup",
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
// ─── LLM text endpoints ───────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/llms/agent-configuration.txt",
|
|
tags: ["llms"],
|
|
summary: "Get agent configuration as plain text (for LLM context)",
|
|
responses: { 200: { description: "Plain text agent configuration" }, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/llms/agent-configuration/{adapterType}.txt",
|
|
tags: ["llms"],
|
|
summary: "Get agent configuration for a specific adapter type",
|
|
request: { params: z.object({ adapterType: z.string() }) },
|
|
responses: { 200: { description: "Plain text agent configuration" }, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/llms/agent-icons.txt",
|
|
tags: ["llms"],
|
|
summary: "Get agent icon names as plain text",
|
|
responses: { 200: { description: "Plain text icon list" }, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Issues (legacy / misc) ───────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues",
|
|
tags: ["issues"],
|
|
summary: "Legacy — returns error directing to /api/companies/{companyId}/issues",
|
|
responses: { 400: r.badRequest },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/issues/{id}/comments/{commentId}",
|
|
tags: ["issues"],
|
|
summary: "Get a single issue comment",
|
|
request: { params: z.object({ id: z.string(), commentId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Org chart images ─────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/org.svg",
|
|
tags: ["companies"],
|
|
summary: "Get org chart as SVG",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: { description: "SVG image" }, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/org.png",
|
|
tags: ["companies"],
|
|
summary: "Get org chart as PNG",
|
|
request: { params: z.object({ companyId: z.string() }) },
|
|
responses: { 200: { description: "PNG image" }, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Company portability (legacy routes) ─────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/companies/issues",
|
|
tags: ["companies"],
|
|
summary: "Legacy — returns error directing to correct issues path",
|
|
responses: { 400: r.badRequest },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/export",
|
|
tags: ["companies"],
|
|
summary: "Export a company (legacy singular form)",
|
|
request: {
|
|
params: z.object({ companyId: z.string() }),
|
|
body: jsonBody(companyPortabilityExportSchema),
|
|
},
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/import/preview",
|
|
tags: ["companies"],
|
|
summary: "Preview a company import (legacy route)",
|
|
request: { body: jsonBody(companyPortabilityPreviewSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/companies/import",
|
|
tags: ["companies"],
|
|
summary: "Apply a company import (legacy route)",
|
|
request: { body: jsonBody(companyPortabilityImportSchema) },
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Board claim & CLI auth ───────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/board-claim/{token}",
|
|
tags: ["access"],
|
|
summary: "Get board claim details by token",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/board-claim/{token}/claim",
|
|
tags: ["access"],
|
|
summary: "Claim a board token",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/cli-auth/challenges/{id}",
|
|
tags: ["access"],
|
|
summary: "Get a CLI auth challenge",
|
|
request: { params: z.object({ id: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
// ─── Invite onboarding ────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/logo",
|
|
tags: ["access"],
|
|
summary: "Get company logo for an invite",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: { description: "Image file" }, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/onboarding",
|
|
tags: ["access"],
|
|
summary: "Get onboarding data for an invite",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/onboarding.txt",
|
|
tags: ["access"],
|
|
summary: "Get onboarding instructions as plain text",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: { description: "Plain text onboarding instructions" }, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/skills/index",
|
|
tags: ["access"],
|
|
summary: "Get skills index for an invite",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/skills/{skillName}",
|
|
tags: ["access"],
|
|
summary: "Get a skill by name for an invite",
|
|
request: { params: z.object({ token: z.string(), skillName: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/invites/{token}/test-resolution",
|
|
tags: ["access"],
|
|
summary: "Test invite token resolution",
|
|
request: { params: z.object({ token: z.string() }) },
|
|
responses: { 200: r.ok(), 404: r.notFound },
|
|
});
|
|
|
|
// ─── Admin ────────────────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/admin/users/{userId}/company-access",
|
|
tags: ["admin"],
|
|
summary: "Get company access for a user (admin)",
|
|
request: { params: z.object({ userId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "put",
|
|
path: "/api/admin/users/{userId}/company-access",
|
|
tags: ["admin"],
|
|
summary: "Set company access for a user (admin)",
|
|
request: {
|
|
params: z.object({ userId: z.string() }),
|
|
body: jsonBody(updateUserCompanyAccessSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/admin/users/{userId}/promote-instance-admin",
|
|
tags: ["admin"],
|
|
summary: "Promote a user to instance admin",
|
|
request: { params: z.object({ userId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden, 404: r.notFound },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/admin/users/{userId}/demote-instance-admin",
|
|
tags: ["admin"],
|
|
summary: "Demote a user from instance admin",
|
|
request: { params: z.object({ userId: z.string() }) },
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 403: r.forbidden, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Project workspace runtime ────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/projects/{id}/workspaces/{workspaceId}/runtime-services/{action}",
|
|
tags: ["projects"],
|
|
summary: "Control a runtime service in a project workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), workspaceId: z.string(), action: z.string() }),
|
|
body: jsonBody(workspaceRuntimeControlTargetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registry.registerPath({
|
|
method: "post",
|
|
path: "/api/projects/{id}/workspaces/{workspaceId}/runtime-commands/{action}",
|
|
tags: ["projects"],
|
|
summary: "Run a runtime command in a project workspace",
|
|
request: {
|
|
params: z.object({ id: z.string(), workspaceId: z.string(), action: z.string() }),
|
|
body: jsonBody(workspaceRuntimeControlTargetSchema),
|
|
},
|
|
responses: { 200: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
// ─── Plugin bridge stream ─────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/plugins/{pluginId}/bridge/stream/{channel}",
|
|
tags: ["plugins"],
|
|
summary: "Subscribe to a plugin bridge SSE stream",
|
|
request: { params: z.object({ pluginId: z.string(), channel: z.string() }) },
|
|
responses: {
|
|
200: { description: "Server-sent event stream (text/event-stream)" },
|
|
401: r.unauthorized,
|
|
},
|
|
});
|
|
|
|
// ─── Plugin UI static ─────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/_plugins/{pluginId}/ui/{filePath}",
|
|
tags: ["plugins"],
|
|
summary: "Serve plugin UI static file",
|
|
request: { params: z.object({ pluginId: z.string(), filePath: z.string() }) },
|
|
responses: { 200: { description: "Static file content" }, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Adapter UI parser ────────────────────────────────────────────────────────
|
|
|
|
registry.registerPath({
|
|
method: "get",
|
|
path: "/api/adapters/{type}/ui-parser.js",
|
|
tags: ["adapters"],
|
|
summary: "Get adapter UI parser script",
|
|
request: { params: z.object({ type: z.string() }) },
|
|
responses: { 200: { description: "JavaScript file" }, 404: r.notFound },
|
|
});
|
|
|
|
// ─── Current route coverage ─────────────────────────────────────────────────
|
|
|
|
registerCurrentRoute({
|
|
method: "get",
|
|
path: "/api/adapters/{type}",
|
|
tags: ["adapters"],
|
|
summary: "Get adapter registration details",
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "get",
|
|
path: "/api/companies/{companyId}/adapters/{type}/model-profiles",
|
|
tags: ["adapters"],
|
|
summary: "List adapter model profiles for a company",
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/health/dev-server/restart",
|
|
tags: ["health"],
|
|
summary: "Request a managed dev-server restart",
|
|
responses: { 202: r.ok(), 403: r.forbidden, 404: r.notFound, 409: { description: "Restart is not required" } },
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/bootstrap/claim",
|
|
tags: ["access"],
|
|
summary: "Claim first instance admin from a browser session",
|
|
responses: { 200: r.ok(), 401: r.unauthorized, 404: r.notFound, 409: { description: "Instance admin already claimed" } },
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "get",
|
|
path: "/api/board-api-keys",
|
|
tags: ["access"],
|
|
summary: "List board API keys",
|
|
responses: { 200: r.ok(), 401: r.unauthorized },
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/board-api-keys",
|
|
tags: ["access"],
|
|
summary: "Create a named board API key",
|
|
body: createBoardApiKeySchema,
|
|
responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized },
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "delete",
|
|
path: "/api/board-api-keys/{keyId}",
|
|
tags: ["access"],
|
|
summary: "Revoke a board API key",
|
|
});
|
|
|
|
for (const route of [
|
|
["get", "/api/companies/import/jobs/{jobId}", "Get company import job status"],
|
|
["get", "/api/companies/{companyId}/search", "Search company data"],
|
|
["get", "/api/companies/{companyId}/issues/count", "Count issues in a company"],
|
|
] as const) {
|
|
registerCurrentRoute({
|
|
method: route[0],
|
|
path: route[1],
|
|
tags: ["companies"],
|
|
summary: route[2],
|
|
});
|
|
}
|
|
|
|
registerCurrentRoute({
|
|
method: "get",
|
|
path: "/api/issues/{id}/cost-summary",
|
|
tags: ["costs"],
|
|
summary: "Get issue cost summary",
|
|
});
|
|
|
|
for (const route of [
|
|
["get", "/api/companies/{companyId}/resource-memberships/me", "List current user's resource memberships"],
|
|
["put", "/api/companies/{companyId}/resource-memberships/me/agents/{agentId}", "Join or leave an agent resource"],
|
|
["put", "/api/companies/{companyId}/resource-memberships/me/projects/{projectId}", "Join or leave a project resource"],
|
|
] as const) {
|
|
registerCurrentRoute({
|
|
method: route[0],
|
|
path: route[1],
|
|
tags: ["resource-memberships"],
|
|
summary: route[2],
|
|
...(route[0] === "put" ? { body: updateResourceMembershipSchema } : {}),
|
|
});
|
|
}
|
|
|
|
const cloudCompanyQuerySchema = z.object({
|
|
companyId: z.string().min(1),
|
|
});
|
|
const cloudCompanyBodySchema = z.object({
|
|
companyId: z.string().min(1),
|
|
});
|
|
const cloudConnectStartSchema = z.object({
|
|
companyId: z.string().min(1),
|
|
remoteUrl: z.string().min(1),
|
|
redirectUri: z.string().min(1),
|
|
});
|
|
const cloudConnectFinishSchema = z.object({
|
|
pendingConnectionId: z.string().min(1),
|
|
code: z.string().min(1),
|
|
state: z.string().min(1),
|
|
});
|
|
const cloudPushRunSchema = cloudCompanyBodySchema.extend({
|
|
retryOfRunId: z.string().optional(),
|
|
});
|
|
const cloudPushRunActivationSchema = cloudCompanyBodySchema.extend({
|
|
entityType: z.enum(["agents", "routines", "monitors"]),
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "get",
|
|
path: "/api/cloud-upstreams",
|
|
tags: ["cloud-upstreams"],
|
|
summary: "List cloud upstream connections",
|
|
query: cloudCompanyQuerySchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/cloud-upstreams/connect/start",
|
|
tags: ["cloud-upstreams"],
|
|
summary: "Start a cloud upstream connection",
|
|
body: cloudConnectStartSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/cloud-upstreams/connect/finish",
|
|
tags: ["cloud-upstreams"],
|
|
summary: "Finish a cloud upstream connection",
|
|
body: cloudConnectFinishSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/cloud-upstreams/{connectionId}/push-runs/preview",
|
|
tags: ["cloud-upstreams"],
|
|
summary: "Preview a cloud upstream push run",
|
|
body: cloudCompanyBodySchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/cloud-upstreams/{connectionId}/push-runs",
|
|
tags: ["cloud-upstreams"],
|
|
summary: "Create a cloud upstream push run",
|
|
body: cloudPushRunSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "get",
|
|
path: "/api/cloud-upstreams/{connectionId}/push-runs/{runId}",
|
|
tags: ["cloud-upstreams"],
|
|
summary: "Get a cloud upstream push run",
|
|
query: cloudCompanyQuerySchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/cloud-upstreams/{connectionId}/push-runs/{runId}/cancel",
|
|
tags: ["cloud-upstreams"],
|
|
summary: "Cancel a cloud upstream push run",
|
|
body: cloudCompanyBodySchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/cloud-upstreams/{connectionId}/push-runs/{runId}/activation",
|
|
tags: ["cloud-upstreams"],
|
|
summary: "Activate cloud upstream push run entities",
|
|
body: cloudPushRunActivationSchema,
|
|
});
|
|
|
|
for (const route of [
|
|
["get", "/api/companies/{companyId}/secret-providers/health", "Check configured secret providers"],
|
|
["get", "/api/companies/{companyId}/secret-provider-configs", "List secret provider configurations"],
|
|
["get", "/api/secret-provider-configs/{id}", "Get a secret provider configuration"],
|
|
["delete", "/api/secret-provider-configs/{id}", "Delete a secret provider configuration"],
|
|
["post", "/api/secret-provider-configs/{id}/default", "Set the default secret provider configuration"],
|
|
["post", "/api/secret-provider-configs/{id}/health", "Check a secret provider configuration"],
|
|
["get", "/api/secrets/{id}/usage", "Get secret usage"],
|
|
["get", "/api/secrets/{id}/access-events", "List secret access events"],
|
|
] as const) {
|
|
registerCurrentRoute({
|
|
method: route[0],
|
|
path: route[1],
|
|
tags: ["secrets"],
|
|
summary: route[2],
|
|
});
|
|
}
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/secret-provider-configs",
|
|
tags: ["secrets"],
|
|
summary: "Create a secret provider configuration",
|
|
body: createSecretProviderConfigSchema,
|
|
responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "patch",
|
|
path: "/api/secret-provider-configs/{id}",
|
|
tags: ["secrets"],
|
|
summary: "Update a secret provider configuration",
|
|
body: updateSecretProviderConfigSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/secret-provider-configs/discovery/preview",
|
|
tags: ["secrets"],
|
|
summary: "Preview secret provider discovery",
|
|
body: secretProviderConfigDiscoveryPreviewSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/secrets/remote-import/preview",
|
|
tags: ["secrets"],
|
|
summary: "Preview remote secret import",
|
|
body: remoteSecretImportPreviewSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/companies/{companyId}/secrets/remote-import",
|
|
tags: ["secrets"],
|
|
summary: "Import remote secrets",
|
|
body: remoteSecretImportSchema,
|
|
});
|
|
|
|
for (const route of [
|
|
["get", "/api/skills/catalog", "List catalog skills"],
|
|
["get", "/api/skills/catalog/{catalogId}", "Get a catalog skill"],
|
|
["get", "/api/skills/catalog/{catalogId}/files", "List catalog skill files"],
|
|
["post", "/api/companies/{companyId}/skills/install-catalog", "Install a catalog skill"],
|
|
["post", "/api/companies/{companyId}/skills/{skillId}/audit", "Audit a company skill"],
|
|
["post", "/api/companies/{companyId}/skills/{skillId}/reset", "Reset a company skill"],
|
|
] as const) {
|
|
registerCurrentRoute({
|
|
method: route[0],
|
|
path: route[1],
|
|
tags: ["skills"],
|
|
summary: route[2],
|
|
...(route[0] === "post" ? { body: z.record(z.unknown()).optional() } : {}),
|
|
});
|
|
}
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/instance/settings/experimental/issue-graph-liveness-auto-recovery/preview",
|
|
tags: ["instance-settings"],
|
|
summary: "Preview issue graph liveness auto-recovery",
|
|
body: issueGraphLivenessAutoRecoveryRequestSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/instance/settings/experimental/issue-graph-liveness-auto-recovery/run",
|
|
tags: ["instance-settings"],
|
|
summary: "Run issue graph liveness auto-recovery",
|
|
body: issueGraphLivenessAutoRecoveryRequestSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "get",
|
|
path: "/api/issues/{id}/accepted-plan-decompositions",
|
|
tags: ["issues"],
|
|
summary: "List accepted plan decompositions",
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/issues/{id}/accepted-plan-decompositions",
|
|
tags: ["issues"],
|
|
summary: "Create accepted plan decomposition child issues",
|
|
body: createAcceptedPlanDecompositionSchema,
|
|
});
|
|
|
|
for (const route of [
|
|
["get", "/api/issues/{id}/documents/{key}/annotations", "List document annotation threads"],
|
|
["get", "/api/issues/{id}/documents/{key}/annotations/{threadId}", "Get a document annotation thread"],
|
|
["post", "/api/issues/{id}/documents/{key}/lock", "Lock an issue document"],
|
|
["post", "/api/issues/{id}/documents/{key}/unlock", "Unlock an issue document"],
|
|
] as const) {
|
|
registerCurrentRoute({
|
|
method: route[0],
|
|
path: route[1],
|
|
tags: ["issues"],
|
|
summary: route[2],
|
|
});
|
|
}
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/issues/{id}/documents/{key}/annotations",
|
|
tags: ["issues"],
|
|
summary: "Create a document annotation thread",
|
|
body: createDocumentAnnotationThreadSchema,
|
|
responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/issues/{id}/documents/{key}/annotations/{threadId}/comments",
|
|
tags: ["issues"],
|
|
summary: "Add a document annotation comment",
|
|
body: createDocumentAnnotationCommentSchema,
|
|
responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 404: r.notFound },
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/issues/{id}/low-trust/promotions",
|
|
tags: ["issues"],
|
|
summary: "Promote quarantined low-trust output",
|
|
body: z.object({
|
|
sourceArtifactKind: z.enum(["comment", "document", "work_product", "issue"]),
|
|
sourceArtifactId: z.string().uuid(),
|
|
title: z.string().trim().min(1).max(200),
|
|
summary: z.string().trim().min(1).max(8_000),
|
|
}),
|
|
responses: { 201: r.ok(), 400: r.badRequest, 401: r.unauthorized, 403: r.forbidden, 404: r.notFound, 422: r.unprocessable },
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "patch",
|
|
path: "/api/issues/{id}/documents/{key}/annotations/{threadId}",
|
|
tags: ["issues"],
|
|
summary: "Update a document annotation thread",
|
|
body: updateDocumentAnnotationThreadSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "get",
|
|
path: "/api/issues/{id}/recovery-actions",
|
|
tags: ["issues"],
|
|
summary: "List issue recovery actions",
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/issues/{id}/recovery-actions/resolve",
|
|
tags: ["issues"],
|
|
summary: "Resolve an issue recovery action",
|
|
body: resolveIssueRecoveryActionSchema,
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/issues/{id}/scheduled-retry/retry-now",
|
|
tags: ["issues"],
|
|
summary: "Retry a scheduled issue run now",
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/issues/{id}/monitor/check-now",
|
|
tags: ["issues"],
|
|
summary: "Run an issue monitor check now",
|
|
});
|
|
|
|
registerCurrentRoute({
|
|
method: "post",
|
|
path: "/api/issues/{id}/interactions/{interactionId}/cancel",
|
|
tags: ["issues"],
|
|
summary: "Cancel an issue question interaction",
|
|
body: cancelIssueThreadInteractionSchema,
|
|
});
|
|
|
|
for (const route of [
|
|
["get", "/api/routines/{id}/revisions", "List routine revisions"],
|
|
["post", "/api/routines/{id}/revisions/{revisionId}/restore", "Restore a routine revision"],
|
|
] as const) {
|
|
registerCurrentRoute({
|
|
method: route[0],
|
|
path: route[1],
|
|
tags: ["routines"],
|
|
summary: route[2],
|
|
});
|
|
}
|
|
|
|
const pluginLocalFolderRequestSchema = z.object({
|
|
path: z.string().min(1),
|
|
access: z.enum(["read", "readWrite"]).optional(),
|
|
requiredDirectories: z.array(z.string()).optional(),
|
|
requiredFiles: z.array(z.string()).optional(),
|
|
});
|
|
|
|
for (const route of [
|
|
["get", "/api/plugins/{pluginId}/companies/{companyId}/local-folders", "List plugin local folders"],
|
|
["get", "/api/plugins/{pluginId}/companies/{companyId}/local-folders/{folderKey}/status", "Get plugin local folder status"],
|
|
["post", "/api/plugins/{pluginId}/companies/{companyId}/local-folders/{folderKey}/validate", "Validate a plugin local folder"],
|
|
["put", "/api/plugins/{pluginId}/companies/{companyId}/local-folders/{folderKey}", "Save a plugin local folder"],
|
|
] as const) {
|
|
registerCurrentRoute({
|
|
method: route[0],
|
|
path: route[1],
|
|
tags: ["plugins"],
|
|
summary: route[2],
|
|
...(route[0] === "post" || route[0] === "put" ? { body: pluginLocalFolderRequestSchema } : {}),
|
|
});
|
|
}
|
|
|
|
// ─── Spec builder ─────────────────────────────────────────────────────────────
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
export function buildOpenApiDocument(): any {
|
|
return applyDocumentFixups({
|
|
openapi: "3.0.0",
|
|
info: {
|
|
title: "Paperclip API",
|
|
version: "1.0.0",
|
|
description: "REST API for the Paperclip AI agent management platform",
|
|
},
|
|
servers: [{ url: "/" }],
|
|
components: registry.buildComponents(),
|
|
paths: registry.buildPaths(),
|
|
});
|
|
}
|
|
|
|
export const buildOpenApiSpec = buildOpenApiDocument;
|
|
|
|
export function openApiRoutes() {
|
|
const router = Router();
|
|
router.get("/openapi.json", (_req, res) => {
|
|
res.json(buildOpenApiDocument());
|
|
});
|
|
return router;
|
|
}
|