Implement execution workspaces and work products

This commit is contained in:
Dotta
2026-03-13 17:12:25 -05:00
parent 9da5358bb3
commit 920bc4c70f
45 changed files with 9157 additions and 140 deletions

View File

@@ -77,12 +77,21 @@ export type {
Project,
ProjectGoalRef,
ProjectWorkspace,
ExecutionWorkspace,
WorkspaceRuntimeService,
ExecutionWorkspaceStrategyType,
ExecutionWorkspaceMode,
ExecutionWorkspaceProviderType,
ExecutionWorkspaceStatus,
ExecutionWorkspaceStrategy,
ProjectExecutionWorkspacePolicy,
ProjectExecutionWorkspaceDefaultMode,
IssueExecutionWorkspaceSettings,
IssueWorkProduct,
IssueWorkProductType,
IssueWorkProductProvider,
IssueWorkProductStatus,
IssueWorkProductReviewState,
Issue,
IssueAssigneeAdapterOverrides,
IssueComment,
@@ -172,6 +181,13 @@ export {
addIssueCommentSchema,
linkIssueApprovalSchema,
createIssueAttachmentMetadataSchema,
createIssueWorkProductSchema,
updateIssueWorkProductSchema,
issueWorkProductTypeSchema,
issueWorkProductStatusSchema,
issueWorkProductReviewStateSchema,
updateExecutionWorkspaceSchema,
executionWorkspaceStatusSchema,
type CreateIssue,
type CreateIssueLabel,
type UpdateIssue,
@@ -179,6 +195,9 @@ export {
type AddIssueComment,
type LinkIssueApproval,
type CreateIssueAttachmentMetadata,
type CreateIssueWorkProduct,
type UpdateIssueWorkProduct,
type UpdateExecutionWorkspace,
createGoalSchema,
updateGoalSchema,
type CreateGoal,

View File

@@ -12,13 +12,24 @@ export type {
export type { AssetImage } from "./asset.js";
export type { Project, ProjectGoalRef, ProjectWorkspace } from "./project.js";
export type {
ExecutionWorkspace,
WorkspaceRuntimeService,
ExecutionWorkspaceStrategyType,
ExecutionWorkspaceMode,
ExecutionWorkspaceProviderType,
ExecutionWorkspaceStatus,
ExecutionWorkspaceStrategy,
ProjectExecutionWorkspacePolicy,
ProjectExecutionWorkspaceDefaultMode,
IssueExecutionWorkspaceSettings,
} from "./workspace-runtime.js";
export type {
IssueWorkProduct,
IssueWorkProductType,
IssueWorkProductProvider,
IssueWorkProductStatus,
IssueWorkProductReviewState,
} from "./work-product.js";
export type {
Issue,
IssueAssigneeAdapterOverrides,

View File

@@ -1,7 +1,8 @@
import type { IssuePriority, IssueStatus } from "../constants.js";
import type { Goal } from "./goal.js";
import type { Project, ProjectWorkspace } from "./project.js";
import type { IssueExecutionWorkspaceSettings } from "./workspace-runtime.js";
import type { ExecutionWorkspace, IssueExecutionWorkspaceSettings } from "./workspace-runtime.js";
import type { IssueWorkProduct } from "./work-product.js";
export interface IssueAncestorProject {
id: string;
@@ -54,6 +55,7 @@ export interface Issue {
id: string;
companyId: string;
projectId: string | null;
projectWorkspaceId: string | null;
goalId: string | null;
parentId: string | null;
ancestors?: IssueAncestor[];
@@ -74,6 +76,8 @@ export interface Issue {
requestDepth: number;
billingCode: string | null;
assigneeAdapterOverrides: IssueAssigneeAdapterOverrides | null;
executionWorkspaceId: string | null;
executionWorkspacePreference: string | null;
executionWorkspaceSettings: IssueExecutionWorkspaceSettings | null;
startedAt: Date | null;
completedAt: Date | null;
@@ -83,6 +87,8 @@ export interface Issue {
labels?: IssueLabel[];
project?: Project | null;
goal?: Goal | null;
currentExecutionWorkspace?: ExecutionWorkspace | null;
workProducts?: IssueWorkProduct[];
mentionedProjects?: Project[];
myLastTouchAt?: Date | null;
lastExternalCommentAt?: Date | null;

View File

@@ -1,6 +1,9 @@
import type { ProjectStatus } from "../constants.js";
import type { ProjectExecutionWorkspacePolicy, WorkspaceRuntimeService } from "./workspace-runtime.js";
export type ProjectWorkspaceSourceType = "local_path" | "git_repo" | "remote_managed" | "non_git_path";
export type ProjectWorkspaceVisibility = "default" | "advanced";
export interface ProjectGoalRef {
id: string;
title: string;
@@ -11,9 +14,17 @@ export interface ProjectWorkspace {
companyId: string;
projectId: string;
name: string;
sourceType: ProjectWorkspaceSourceType;
cwd: string | null;
repoUrl: string | null;
repoRef: string | null;
defaultRef: string | null;
visibility: ProjectWorkspaceVisibility;
setupCommand: string | null;
cleanupCommand: string | null;
remoteProvider: string | null;
remoteWorkspaceRef: string | null;
sharedWorkspaceKey: string | null;
metadata: Record<string, unknown> | null;
isPrimary: boolean;
runtimeServices?: WorkspaceRuntimeService[];

View File

@@ -0,0 +1,55 @@
export type IssueWorkProductType =
| "preview_url"
| "runtime_service"
| "pull_request"
| "branch"
| "commit"
| "artifact"
| "document";
export type IssueWorkProductProvider =
| "paperclip"
| "github"
| "vercel"
| "s3"
| "custom";
export type IssueWorkProductStatus =
| "active"
| "ready_for_review"
| "approved"
| "changes_requested"
| "merged"
| "closed"
| "failed"
| "archived"
| "draft";
export type IssueWorkProductReviewState =
| "none"
| "needs_board_review"
| "approved"
| "changes_requested";
export interface IssueWorkProduct {
id: string;
companyId: string;
projectId: string | null;
issueId: string;
executionWorkspaceId: string | null;
runtimeServiceId: string | null;
type: IssueWorkProductType;
provider: IssueWorkProductProvider | string;
externalId: string | null;
title: string;
url: string | null;
status: IssueWorkProductStatus | string;
reviewState: IssueWorkProductReviewState;
isPrimary: boolean;
healthStatus: "unknown" | "healthy" | "unhealthy";
summary: string | null;
metadata: Record<string, unknown> | null;
createdByRunId: string | null;
createdAt: Date;
updatedAt: Date;
}

View File

@@ -1,6 +1,35 @@
export type ExecutionWorkspaceStrategyType = "project_primary" | "git_worktree";
export type ExecutionWorkspaceStrategyType =
| "project_primary"
| "git_worktree"
| "adapter_managed"
| "cloud_sandbox";
export type ExecutionWorkspaceMode = "inherit" | "project_primary" | "isolated" | "agent_default";
export type ProjectExecutionWorkspaceDefaultMode =
| "shared_workspace"
| "isolated_workspace"
| "operator_branch"
| "adapter_default";
export type ExecutionWorkspaceMode =
| "inherit"
| "shared_workspace"
| "isolated_workspace"
| "operator_branch"
| "reuse_existing"
| "agent_default";
export type ExecutionWorkspaceProviderType =
| "local_fs"
| "git_worktree"
| "adapter_managed"
| "cloud_sandbox";
export type ExecutionWorkspaceStatus =
| "active"
| "idle"
| "in_review"
| "archived"
| "cleanup_failed";
export interface ExecutionWorkspaceStrategy {
type: ExecutionWorkspaceStrategyType;
@@ -13,12 +42,14 @@ export interface ExecutionWorkspaceStrategy {
export interface ProjectExecutionWorkspacePolicy {
enabled: boolean;
defaultMode?: "project_primary" | "isolated";
defaultMode?: ProjectExecutionWorkspaceDefaultMode;
allowIssueOverride?: boolean;
defaultProjectWorkspaceId?: string | null;
workspaceStrategy?: ExecutionWorkspaceStrategy | null;
workspaceRuntime?: Record<string, unknown> | null;
branchPolicy?: Record<string, unknown> | null;
pullRequestPolicy?: Record<string, unknown> | null;
runtimePolicy?: Record<string, unknown> | null;
cleanupPolicy?: Record<string, unknown> | null;
}
@@ -28,11 +59,39 @@ export interface IssueExecutionWorkspaceSettings {
workspaceRuntime?: Record<string, unknown> | null;
}
export interface ExecutionWorkspace {
id: string;
companyId: string;
projectId: string;
projectWorkspaceId: string | null;
sourceIssueId: string | null;
mode: Exclude<ExecutionWorkspaceMode, "inherit" | "reuse_existing" | "agent_default"> | "adapter_managed" | "cloud_sandbox";
strategyType: ExecutionWorkspaceStrategyType;
name: string;
status: ExecutionWorkspaceStatus;
cwd: string | null;
repoUrl: string | null;
baseRef: string | null;
branchName: string | null;
providerType: ExecutionWorkspaceProviderType;
providerRef: string | null;
derivedFromExecutionWorkspaceId: string | null;
lastUsedAt: Date;
openedAt: Date;
closedAt: Date | null;
cleanupEligibleAt: Date | null;
cleanupReason: string | null;
metadata: Record<string, unknown> | null;
createdAt: Date;
updatedAt: Date;
}
export interface WorkspaceRuntimeService {
id: string;
companyId: string;
projectId: string | null;
projectWorkspaceId: string | null;
executionWorkspaceId: string | null;
issueId: string | null;
scopeType: "project_workspace" | "execution_workspace" | "run" | "agent";
scopeId: string | null;

View File

@@ -0,0 +1,18 @@
import { z } from "zod";
export const executionWorkspaceStatusSchema = z.enum([
"active",
"idle",
"in_review",
"archived",
"cleanup_failed",
]);
export const updateExecutionWorkspaceSchema = z.object({
status: executionWorkspaceStatusSchema.optional(),
cleanupEligibleAt: z.string().datetime().optional().nullable(),
cleanupReason: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
}).strict();
export type UpdateExecutionWorkspace = z.infer<typeof updateExecutionWorkspaceSchema>;

View File

@@ -76,6 +76,22 @@ export {
type CreateIssueAttachmentMetadata,
} from "./issue.js";
export {
createIssueWorkProductSchema,
updateIssueWorkProductSchema,
issueWorkProductTypeSchema,
issueWorkProductStatusSchema,
issueWorkProductReviewStateSchema,
type CreateIssueWorkProduct,
type UpdateIssueWorkProduct,
} from "./work-product.js";
export {
updateExecutionWorkspaceSchema,
executionWorkspaceStatusSchema,
type UpdateExecutionWorkspace,
} from "./execution-workspace.js";
export {
createGoalSchema,
updateGoalSchema,

View File

@@ -3,7 +3,7 @@ import { ISSUE_PRIORITIES, ISSUE_STATUSES } from "../constants.js";
const executionWorkspaceStrategySchema = z
.object({
type: z.enum(["project_primary", "git_worktree"]).optional(),
type: z.enum(["project_primary", "git_worktree", "adapter_managed", "cloud_sandbox"]).optional(),
baseRef: z.string().optional().nullable(),
branchTemplate: z.string().optional().nullable(),
worktreeParentDir: z.string().optional().nullable(),
@@ -14,7 +14,7 @@ const executionWorkspaceStrategySchema = z
export const issueExecutionWorkspaceSettingsSchema = z
.object({
mode: z.enum(["inherit", "project_primary", "isolated", "agent_default"]).optional(),
mode: z.enum(["inherit", "shared_workspace", "isolated_workspace", "operator_branch", "reuse_existing", "agent_default"]).optional(),
workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(),
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
})
@@ -29,6 +29,7 @@ export const issueAssigneeAdapterOverridesSchema = z
export const createIssueSchema = z.object({
projectId: z.string().uuid().optional().nullable(),
projectWorkspaceId: z.string().uuid().optional().nullable(),
goalId: z.string().uuid().optional().nullable(),
parentId: z.string().uuid().optional().nullable(),
title: z.string().min(1),
@@ -40,6 +41,15 @@ export const createIssueSchema = z.object({
requestDepth: z.number().int().nonnegative().optional().default(0),
billingCode: z.string().optional().nullable(),
assigneeAdapterOverrides: issueAssigneeAdapterOverridesSchema.optional().nullable(),
executionWorkspaceId: z.string().uuid().optional().nullable(),
executionWorkspacePreference: z.enum([
"inherit",
"shared_workspace",
"isolated_workspace",
"operator_branch",
"reuse_existing",
"agent_default",
]).optional().nullable(),
executionWorkspaceSettings: issueExecutionWorkspaceSettingsSchema.optional().nullable(),
labelIds: z.array(z.string().uuid()).optional(),
});

View File

@@ -3,7 +3,7 @@ import { PROJECT_STATUSES } from "../constants.js";
const executionWorkspaceStrategySchema = z
.object({
type: z.enum(["project_primary", "git_worktree"]).optional(),
type: z.enum(["project_primary", "git_worktree", "adapter_managed", "cloud_sandbox"]).optional(),
baseRef: z.string().optional().nullable(),
branchTemplate: z.string().optional().nullable(),
worktreeParentDir: z.string().optional().nullable(),
@@ -15,30 +15,54 @@ const executionWorkspaceStrategySchema = z
export const projectExecutionWorkspacePolicySchema = z
.object({
enabled: z.boolean(),
defaultMode: z.enum(["project_primary", "isolated"]).optional(),
defaultMode: z.enum(["shared_workspace", "isolated_workspace", "operator_branch", "adapter_default"]).optional(),
allowIssueOverride: z.boolean().optional(),
defaultProjectWorkspaceId: z.string().uuid().optional().nullable(),
workspaceStrategy: executionWorkspaceStrategySchema.optional().nullable(),
workspaceRuntime: z.record(z.unknown()).optional().nullable(),
branchPolicy: z.record(z.unknown()).optional().nullable(),
pullRequestPolicy: z.record(z.unknown()).optional().nullable(),
runtimePolicy: z.record(z.unknown()).optional().nullable(),
cleanupPolicy: z.record(z.unknown()).optional().nullable(),
})
.strict();
const projectWorkspaceSourceTypeSchema = z.enum(["local_path", "git_repo", "remote_managed", "non_git_path"]);
const projectWorkspaceVisibilitySchema = z.enum(["default", "advanced"]);
const projectWorkspaceFields = {
name: z.string().min(1).optional(),
sourceType: projectWorkspaceSourceTypeSchema.optional(),
cwd: z.string().min(1).optional().nullable(),
repoUrl: z.string().url().optional().nullable(),
repoRef: z.string().optional().nullable(),
defaultRef: z.string().optional().nullable(),
visibility: projectWorkspaceVisibilitySchema.optional(),
setupCommand: z.string().optional().nullable(),
cleanupCommand: z.string().optional().nullable(),
remoteProvider: z.string().optional().nullable(),
remoteWorkspaceRef: z.string().optional().nullable(),
sharedWorkspaceKey: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
};
export const createProjectWorkspaceSchema = z.object({
...projectWorkspaceFields,
isPrimary: z.boolean().optional().default(false),
}).superRefine((value, ctx) => {
function validateProjectWorkspace(value: Record<string, unknown>, ctx: z.RefinementCtx) {
const sourceType = value.sourceType ?? "local_path";
const hasCwd = typeof value.cwd === "string" && value.cwd.trim().length > 0;
const hasRepo = typeof value.repoUrl === "string" && value.repoUrl.trim().length > 0;
const hasRemoteRef = typeof value.remoteWorkspaceRef === "string" && value.remoteWorkspaceRef.trim().length > 0;
if (sourceType === "remote_managed") {
if (!hasRemoteRef && !hasRepo) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Remote-managed workspace requires remoteWorkspaceRef or repoUrl.",
path: ["remoteWorkspaceRef"],
});
}
return;
}
if (!hasCwd && !hasRepo) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
@@ -46,7 +70,12 @@ export const createProjectWorkspaceSchema = z.object({
path: ["cwd"],
});
}
});
}
export const createProjectWorkspaceSchema = z.object({
...projectWorkspaceFields,
isPrimary: z.boolean().optional().default(false),
}).superRefine(validateProjectWorkspace);
export type CreateProjectWorkspace = z.infer<typeof createProjectWorkspaceSchema>;

View File

@@ -0,0 +1,54 @@
import { z } from "zod";
export const issueWorkProductTypeSchema = z.enum([
"preview_url",
"runtime_service",
"pull_request",
"branch",
"commit",
"artifact",
"document",
]);
export const issueWorkProductStatusSchema = z.enum([
"active",
"ready_for_review",
"approved",
"changes_requested",
"merged",
"closed",
"failed",
"archived",
"draft",
]);
export const issueWorkProductReviewStateSchema = z.enum([
"none",
"needs_board_review",
"approved",
"changes_requested",
]);
export const createIssueWorkProductSchema = z.object({
projectId: z.string().uuid().optional().nullable(),
executionWorkspaceId: z.string().uuid().optional().nullable(),
runtimeServiceId: z.string().uuid().optional().nullable(),
type: issueWorkProductTypeSchema,
provider: z.string().min(1),
externalId: z.string().optional().nullable(),
title: z.string().min(1),
url: z.string().url().optional().nullable(),
status: issueWorkProductStatusSchema.default("active"),
reviewState: issueWorkProductReviewStateSchema.optional().default("none"),
isPrimary: z.boolean().optional().default(false),
healthStatus: z.enum(["unknown", "healthy", "unhealthy"]).optional().default("unknown"),
summary: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
createdByRunId: z.string().uuid().optional().nullable(),
});
export type CreateIssueWorkProduct = z.infer<typeof createIssueWorkProductSchema>;
export const updateIssueWorkProductSchema = createIssueWorkProductSchema.partial();
export type UpdateIssueWorkProduct = z.infer<typeof updateIssueWorkProductSchema>;