Address runtime workspace review feedback

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
dotta 2026-03-30 06:48:45 -05:00
parent b3d61a7561
commit 92ebad3d42
3 changed files with 47 additions and 38 deletions

View file

@ -62,6 +62,36 @@ export const executionWorkspaceCloseGitReadinessSchema = z.object({
createdByRuntime: z.boolean(),
}).strict();
export const workspaceRuntimeServiceSchema = z.object({
id: z.string(),
companyId: z.string().uuid(),
projectId: z.string().uuid().nullable(),
projectWorkspaceId: z.string().uuid().nullable(),
executionWorkspaceId: z.string().uuid().nullable(),
issueId: z.string().uuid().nullable(),
scopeType: z.enum(["project_workspace", "execution_workspace", "run", "agent"]),
scopeId: z.string().nullable(),
serviceName: z.string(),
status: z.enum(["starting", "running", "stopped", "failed"]),
lifecycle: z.enum(["shared", "ephemeral"]),
reuseKey: z.string().nullable(),
command: z.string().nullable(),
cwd: z.string().nullable(),
port: z.number().int().nullable(),
url: z.string().nullable(),
provider: z.enum(["local_process", "adapter_managed"]),
providerRef: z.string().nullable(),
ownerAgentId: z.string().uuid().nullable(),
startedByRunId: z.string().uuid().nullable(),
lastUsedAt: z.coerce.date(),
startedAt: z.coerce.date(),
stoppedAt: z.coerce.date().nullable(),
stopPolicy: z.record(z.unknown()).nullable(),
healthStatus: z.enum(["unknown", "healthy", "unhealthy"]),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
}).strict();
export const executionWorkspaceCloseReadinessSchema = z.object({
workspaceId: z.string().uuid(),
state: executionWorkspaceCloseReadinessStateSchema,
@ -73,35 +103,7 @@ export const executionWorkspaceCloseReadinessSchema = z.object({
isSharedWorkspace: z.boolean(),
isProjectPrimaryWorkspace: z.boolean(),
git: executionWorkspaceCloseGitReadinessSchema.nullable(),
runtimeServices: z.array(z.object({
id: z.string(),
companyId: z.string().uuid(),
projectId: z.string().uuid().nullable(),
projectWorkspaceId: z.string().uuid().nullable(),
executionWorkspaceId: z.string().uuid().nullable(),
issueId: z.string().uuid().nullable(),
scopeType: z.enum(["project_workspace", "execution_workspace", "run", "agent"]),
scopeId: z.string().nullable(),
serviceName: z.string(),
status: z.enum(["starting", "running", "stopped", "failed"]),
lifecycle: z.enum(["shared", "ephemeral"]),
reuseKey: z.string().nullable(),
command: z.string().nullable(),
cwd: z.string().nullable(),
port: z.number().int().nullable(),
url: z.string().nullable(),
provider: z.enum(["local_process", "adapter_managed"]),
providerRef: z.string().nullable(),
ownerAgentId: z.string().uuid().nullable(),
startedByRunId: z.string().uuid().nullable(),
lastUsedAt: z.coerce.date(),
startedAt: z.coerce.date(),
stoppedAt: z.coerce.date().nullable(),
stopPolicy: z.record(z.unknown()).nullable(),
healthStatus: z.enum(["unknown", "healthy", "unhealthy"]),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
}).strict()),
runtimeServices: z.array(workspaceRuntimeServiceSchema),
}).strict();
export const updateExecutionWorkspaceSchema = z.object({

View file

@ -1192,6 +1192,8 @@ export function normalizeAdapterManagedRuntimeServices(input: {
async function startLocalRuntimeService(input: {
db?: Db;
runId: string;
leaseRunId?: string | null;
startedByRunId?: string | null;
agent: ExecutionWorkspaceAgentRef;
issue: ExecutionWorkspaceIssueRef | null;
workspace: RealizedExecutionWorkspace;
@ -1203,6 +1205,8 @@ async function startLocalRuntimeService(input: {
scopeType: "project_workspace" | "execution_workspace" | "run" | "agent";
scopeId: string | null;
}): Promise<RuntimeServiceRecord> {
const leaseRunId = input.leaseRunId === undefined ? input.runId : input.leaseRunId;
const startedByRunId = input.startedByRunId === undefined ? input.runId : input.startedByRunId;
const identity = resolveRuntimeServiceReuseIdentity({
service: input.service,
workspace: input.workspace,
@ -1299,7 +1303,7 @@ async function startLocalRuntimeService(input: {
provider: "local_process",
providerRef: String(adoptedRecord.pid),
ownerAgentId: input.agent.id ?? null,
startedByRunId: input.runId,
startedByRunId,
lastUsedAt: new Date().toISOString(),
startedAt: adoptedRecord.startedAt,
stoppedAt: null,
@ -1308,7 +1312,7 @@ async function startLocalRuntimeService(input: {
reused: true,
db: input.db,
child: null,
leaseRunIds: new Set([input.runId]),
leaseRunIds: leaseRunId ? new Set([leaseRunId]) : new Set(),
idleTimer: null,
envFingerprint,
serviceKey,
@ -1373,7 +1377,7 @@ async function startLocalRuntimeService(input: {
provider: "local_process",
providerRef: child.pid ? String(child.pid) : null,
ownerAgentId: input.agent.id ?? null,
startedByRunId: input.runId,
startedByRunId,
lastUsedAt: new Date().toISOString(),
startedAt: new Date().toISOString(),
stoppedAt: null,
@ -1382,7 +1386,7 @@ async function startLocalRuntimeService(input: {
reused: false,
db: input.db,
child,
leaseRunIds: new Set([input.runId]),
leaseRunIds: leaseRunId ? new Set([leaseRunId]) : new Set(),
idleTimer: null,
envFingerprint,
serviceKey,
@ -1648,9 +1652,13 @@ export async function startRuntimeServicesForWorkspaceControl(input: {
}
}
// Manually controlled services are not tied to a heartbeat run lifecycle, so they do not
// retain a run lease and never persist a startedByRunId foreign key.
const record = await startLocalRuntimeService({
db: input.db,
runId: invocationId,
leaseRunId: null,
startedByRunId: null,
agent: input.actor,
issue: input.issue,
workspace: input.workspace,
@ -1662,7 +1670,6 @@ export async function startRuntimeServicesForWorkspaceControl(input: {
scopeType,
scopeId,
});
record.startedByRunId = null;
registerRuntimeService(input.db, record);
await persistRuntimeServiceRecord(input.db, record);
refs.push(toRuntimeServiceRef(record));

View file

@ -149,8 +149,8 @@ export function ExecutionWorkspaceCloseDialog({
<section className="space-y-2">
<h3 className="text-sm font-medium">Blocking reasons</h3>
<ul className="space-y-2 text-sm text-muted-foreground">
{readiness.blockingReasons.map((reason) => (
<li key={reason} className="break-words rounded-lg border border-destructive/20 bg-destructive/5 px-3 py-2 text-destructive">
{readiness.blockingReasons.map((reason, idx) => (
<li key={`blocking-${idx}`} className="break-words rounded-lg border border-destructive/20 bg-destructive/5 px-3 py-2 text-destructive">
{reason}
</li>
))}
@ -162,8 +162,8 @@ export function ExecutionWorkspaceCloseDialog({
<section className="space-y-2">
<h3 className="text-sm font-medium">Warnings</h3>
<ul className="space-y-2 text-sm text-muted-foreground">
{readiness.warnings.map((warning) => (
<li key={warning} className="break-words rounded-lg border border-amber-500/20 bg-amber-500/5 px-3 py-2">
{readiness.warnings.map((warning, idx) => (
<li key={`warning-${idx}`} className="break-words rounded-lg border border-amber-500/20 bg-amber-500/5 px-3 py-2">
{warning}
</li>
))}