Fix execution workspace runtime lifecycle
This commit is contained in:
@@ -139,6 +139,20 @@ export type ResolvedWorkspaceForRun = {
|
||||
warnings: string[];
|
||||
};
|
||||
|
||||
type ProjectWorkspaceCandidate = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export function prioritizeProjectWorkspaceCandidatesForRun<T extends ProjectWorkspaceCandidate>(
|
||||
rows: T[],
|
||||
preferredWorkspaceId: string | null | undefined,
|
||||
): T[] {
|
||||
if (!preferredWorkspaceId) return rows;
|
||||
const preferredIndex = rows.findIndex((row) => row.id === preferredWorkspaceId);
|
||||
if (preferredIndex <= 0) return rows;
|
||||
return [rows[preferredIndex]!, ...rows.slice(0, preferredIndex), ...rows.slice(preferredIndex + 1)];
|
||||
}
|
||||
|
||||
function readNonEmptyString(value: unknown): string | null {
|
||||
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
||||
}
|
||||
@@ -537,18 +551,25 @@ export function heartbeatService(db: Db) {
|
||||
): Promise<ResolvedWorkspaceForRun> {
|
||||
const issueId = readNonEmptyString(context.issueId);
|
||||
const contextProjectId = readNonEmptyString(context.projectId);
|
||||
const issueProjectId = issueId
|
||||
const contextProjectWorkspaceId = readNonEmptyString(context.projectWorkspaceId);
|
||||
const issueProjectRef = issueId
|
||||
? await db
|
||||
.select({ projectId: issues.projectId })
|
||||
.select({
|
||||
projectId: issues.projectId,
|
||||
projectWorkspaceId: issues.projectWorkspaceId,
|
||||
})
|
||||
.from(issues)
|
||||
.where(and(eq(issues.id, issueId), eq(issues.companyId, agent.companyId)))
|
||||
.then((rows) => rows[0]?.projectId ?? null)
|
||||
.then((rows) => rows[0] ?? null)
|
||||
: null;
|
||||
const issueProjectId = issueProjectRef?.projectId ?? null;
|
||||
const preferredProjectWorkspaceId =
|
||||
issueProjectRef?.projectWorkspaceId ?? contextProjectWorkspaceId ?? null;
|
||||
const resolvedProjectId = issueProjectId ?? contextProjectId;
|
||||
const useProjectWorkspace = opts?.useProjectWorkspace !== false;
|
||||
const workspaceProjectId = useProjectWorkspace ? resolvedProjectId : null;
|
||||
|
||||
const projectWorkspaceRows = workspaceProjectId
|
||||
const unorderedProjectWorkspaceRows = workspaceProjectId
|
||||
? await db
|
||||
.select()
|
||||
.from(projectWorkspaces)
|
||||
@@ -560,6 +581,10 @@ export function heartbeatService(db: Db) {
|
||||
)
|
||||
.orderBy(asc(projectWorkspaces.createdAt), asc(projectWorkspaces.id))
|
||||
: [];
|
||||
const projectWorkspaceRows = prioritizeProjectWorkspaceCandidatesForRun(
|
||||
unorderedProjectWorkspaceRows,
|
||||
preferredProjectWorkspaceId,
|
||||
);
|
||||
|
||||
const workspaceHints = projectWorkspaceRows.map((workspace) => ({
|
||||
workspaceId: workspace.id,
|
||||
@@ -569,11 +594,22 @@ export function heartbeatService(db: Db) {
|
||||
}));
|
||||
|
||||
if (projectWorkspaceRows.length > 0) {
|
||||
const preferredWorkspace = preferredProjectWorkspaceId
|
||||
? projectWorkspaceRows.find((workspace) => workspace.id === preferredProjectWorkspaceId) ?? null
|
||||
: null;
|
||||
const missingProjectCwds: string[] = [];
|
||||
let hasConfiguredProjectCwd = false;
|
||||
let preferredWorkspaceWarning: string | null = null;
|
||||
if (preferredProjectWorkspaceId && !preferredWorkspace) {
|
||||
preferredWorkspaceWarning =
|
||||
`Selected project workspace "${preferredProjectWorkspaceId}" is not available on this project.`;
|
||||
}
|
||||
for (const workspace of projectWorkspaceRows) {
|
||||
const projectCwd = readNonEmptyString(workspace.cwd);
|
||||
if (!projectCwd || projectCwd === REPO_ONLY_CWD_SENTINEL) {
|
||||
if (preferredWorkspace?.id === workspace.id) {
|
||||
preferredWorkspaceWarning = `Selected project workspace "${workspace.name}" has no local cwd configured.`;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
hasConfiguredProjectCwd = true;
|
||||
@@ -590,15 +626,22 @@ export function heartbeatService(db: Db) {
|
||||
repoUrl: workspace.repoUrl,
|
||||
repoRef: workspace.repoRef,
|
||||
workspaceHints,
|
||||
warnings: [],
|
||||
warnings: preferredWorkspaceWarning ? [preferredWorkspaceWarning] : [],
|
||||
};
|
||||
}
|
||||
if (preferredWorkspace?.id === workspace.id) {
|
||||
preferredWorkspaceWarning =
|
||||
`Selected project workspace path "${projectCwd}" is not available yet.`;
|
||||
}
|
||||
missingProjectCwds.push(projectCwd);
|
||||
}
|
||||
|
||||
const fallbackCwd = resolveDefaultAgentWorkspaceDir(agent.id);
|
||||
await fs.mkdir(fallbackCwd, { recursive: true });
|
||||
const warnings: string[] = [];
|
||||
if (preferredWorkspaceWarning) {
|
||||
warnings.push(preferredWorkspaceWarning);
|
||||
}
|
||||
if (missingProjectCwds.length > 0) {
|
||||
const firstMissing = missingProjectCwds[0];
|
||||
const extraMissingCount = Math.max(0, missingProjectCwds.length - 1);
|
||||
@@ -1464,6 +1507,7 @@ export function heartbeatService(db: Db) {
|
||||
},
|
||||
issue: issueRef,
|
||||
workspace: executionWorkspace,
|
||||
executionWorkspaceId: persistedExecutionWorkspace?.id ?? issueRef?.executionWorkspaceId ?? null,
|
||||
config: resolvedConfig,
|
||||
adapterEnv,
|
||||
onLog,
|
||||
|
||||
Reference in New Issue
Block a user