feat: prepare issue workspaces from connected repositories
This commit is contained in:
@@ -1,5 +1,12 @@
|
||||
import { env } from "@code/env/convex";
|
||||
import {
|
||||
makeIssueWorkspacePlan,
|
||||
transitionWorkspaceRun,
|
||||
WorkspaceStateError,
|
||||
WorkspaceValidationError,
|
||||
} from "@code/primitives/project-workspace";
|
||||
import { ConvexError, v } from "convex/values";
|
||||
import { Effect } from "effect";
|
||||
|
||||
import { mutation, query } from "./_generated/server";
|
||||
import { artifactPath } from "./artifactModel";
|
||||
@@ -17,6 +24,37 @@ const agentStatus = v.union(
|
||||
v.literal("failed")
|
||||
);
|
||||
|
||||
const workspacePlanFor = (input: {
|
||||
readonly issueBody: string;
|
||||
readonly issueId: string;
|
||||
readonly issueNumber: number;
|
||||
readonly issueTitle: string;
|
||||
readonly sourceUrl: string | undefined;
|
||||
readonly defaultBranch: string | undefined;
|
||||
}) => {
|
||||
try {
|
||||
return Effect.runSync(
|
||||
makeIssueWorkspacePlan({
|
||||
artifacts: [],
|
||||
branchName: undefined,
|
||||
checkoutPath: undefined,
|
||||
contextFiles: [],
|
||||
defaultBranch: input.defaultBranch,
|
||||
issueBody: input.issueBody,
|
||||
issueId: input.issueId,
|
||||
issueNumber: input.issueNumber,
|
||||
issueTitle: input.issueTitle,
|
||||
sourceUrl: input.sourceUrl,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceValidationError) {
|
||||
throw new ConvexError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const get = query({
|
||||
args: { issueId: v.id("projectIssues"), token: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
@@ -33,7 +71,26 @@ export const get = query({
|
||||
.query("projectArtifacts")
|
||||
.withIndex("by_project", (q) => q.eq("projectId", project._id))
|
||||
.take(25);
|
||||
return { artifacts, issue, project };
|
||||
const [source] = await ctx.db
|
||||
.query("projectSources")
|
||||
.withIndex("by_project", (q) => q.eq("projectId", project._id))
|
||||
.take(1);
|
||||
const contextDocuments = await ctx.db
|
||||
.query("projectContextDocuments")
|
||||
.withIndex("by_project", (q) => q.eq("projectId", project._id))
|
||||
.take(25);
|
||||
const run = await ctx.db
|
||||
.query("projectWorkRuns")
|
||||
.withIndex("by_issue", (q) => q.eq("issueId", issue._id))
|
||||
.unique();
|
||||
return {
|
||||
artifacts,
|
||||
contextDocuments,
|
||||
issue,
|
||||
project,
|
||||
run: run ?? null,
|
||||
source: source ?? null,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@@ -49,12 +106,38 @@ export const ensureRun = mutation({
|
||||
if (!issue) {
|
||||
throw new ConvexError("Issue not found");
|
||||
}
|
||||
const [source] = await ctx.db
|
||||
.query("projectSources")
|
||||
.withIndex("by_project", (q) => q.eq("projectId", issue.projectId))
|
||||
.take(1);
|
||||
const plan = workspacePlanFor({
|
||||
defaultBranch: source?.defaultBranch,
|
||||
issueBody: issue.body,
|
||||
issueId: String(issue._id),
|
||||
issueNumber: issue.number,
|
||||
issueTitle: issue.title,
|
||||
sourceUrl: source?.url,
|
||||
});
|
||||
const existing = await ctx.db
|
||||
.query("projectWorkRuns")
|
||||
.withIndex("by_issue", (q) => q.eq("issueId", issue._id))
|
||||
.unique();
|
||||
if (existing) {
|
||||
return existing;
|
||||
if (
|
||||
existing.baseBranch !== plan.baseBranch ||
|
||||
existing.branchName !== plan.branchName ||
|
||||
existing.checkoutPath !== plan.checkoutPath ||
|
||||
existing.sourceUrl !== plan.sourceUrl
|
||||
) {
|
||||
await ctx.db.patch("projectWorkRuns", existing._id, {
|
||||
baseBranch: plan.baseBranch,
|
||||
branchName: plan.branchName,
|
||||
checkoutPath: plan.checkoutPath,
|
||||
sourceUrl: plan.sourceUrl,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
}
|
||||
return await ctx.db.get("projectWorkRuns", existing._id);
|
||||
}
|
||||
const timestamp = Date.now();
|
||||
const actorKey = [
|
||||
@@ -66,16 +149,26 @@ export const ensureRun = mutation({
|
||||
const runId = await ctx.db.insert("projectWorkRuns", {
|
||||
actorKey,
|
||||
agentId: String(issue._id),
|
||||
baseBranch: plan.baseBranch,
|
||||
branchName: plan.branchName,
|
||||
checkoutPath: plan.checkoutPath,
|
||||
createdAt: timestamp,
|
||||
daemonId: args.daemonId,
|
||||
issueId: issue._id,
|
||||
projectId: issue.projectId,
|
||||
sourceUrl: plan.sourceUrl,
|
||||
status: "queued",
|
||||
updatedAt: timestamp,
|
||||
});
|
||||
await ctx.db.insert("projectEvents", {
|
||||
createdAt: timestamp,
|
||||
data: { actorKey, daemonId: args.daemonId },
|
||||
data: {
|
||||
actorKey,
|
||||
branchName: plan.branchName,
|
||||
checkoutPath: plan.checkoutPath,
|
||||
daemonId: args.daemonId,
|
||||
sourceUrl: plan.sourceUrl,
|
||||
},
|
||||
issueId: issue._id,
|
||||
kind: "agent.workspace.created",
|
||||
projectId: issue.projectId,
|
||||
@@ -147,6 +240,19 @@ export const setStatus = mutation({
|
||||
if (!run) {
|
||||
throw new ConvexError("Agent work run not found");
|
||||
}
|
||||
try {
|
||||
Effect.runSync(
|
||||
transitionWorkspaceRun({
|
||||
from: run.status,
|
||||
to: args.status,
|
||||
})
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof WorkspaceStateError) {
|
||||
throw new ConvexError(error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
const timestamp = Date.now();
|
||||
const terminal = args.status === "completed" || args.status === "failed";
|
||||
await ctx.db.patch("projectIssues", issue._id, {
|
||||
|
||||
Reference in New Issue
Block a user