feat: add paired execution freshness guard

This commit is contained in:
Eyejoker
2026-03-29 04:37:42 +09:00
parent 03ff69fdd0
commit bb30330c66
5 changed files with 500 additions and 14 deletions

View File

@@ -295,6 +295,7 @@ function createSchema(database: Database.Database): void {
service_id TEXT NOT NULL,
role TEXT NOT NULL,
workspace_id TEXT,
checkpoint_fingerprint TEXT,
status TEXT NOT NULL DEFAULT 'pending',
summary TEXT,
created_at TEXT NOT NULL,
@@ -478,6 +479,14 @@ function createSchema(database: Database.Database): void {
/* column already exists */
}
try {
database.exec(
`ALTER TABLE paired_executions ADD COLUMN checkpoint_fingerprint TEXT`,
);
} catch {
/* column already exists */
}
database.exec(`
UPDATE scheduled_tasks
SET agent_type = COALESCE(
@@ -2149,13 +2158,14 @@ export function createPairedExecution(execution: PairedExecution): void {
service_id,
role,
workspace_id,
checkpoint_fingerprint,
status,
summary,
created_at,
started_at,
completed_at
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`,
).run(
execution.id,
@@ -2163,6 +2173,7 @@ export function createPairedExecution(execution: PairedExecution): void {
execution.service_id,
execution.role,
execution.workspace_id,
execution.checkpoint_fingerprint ?? null,
execution.status,
execution.summary,
execution.created_at,
@@ -2184,7 +2195,12 @@ export function updatePairedExecution(
updates: Partial<
Pick<
PairedExecution,
'workspace_id' | 'status' | 'summary' | 'started_at' | 'completed_at'
| 'workspace_id'
| 'checkpoint_fingerprint'
| 'status'
| 'summary'
| 'started_at'
| 'completed_at'
>
>,
): void {
@@ -2195,6 +2211,10 @@ export function updatePairedExecution(
fields.push('workspace_id = ?');
values.push(updates.workspace_id);
}
if (updates.checkpoint_fingerprint !== undefined) {
fields.push('checkpoint_fingerprint = ?');
values.push(updates.checkpoint_fingerprint);
}
if (updates.status !== undefined) {
fields.push('status = ?');
values.push(updates.status);
@@ -2275,6 +2295,45 @@ export function listPairedWorkspacesForTask(taskId: string): PairedWorkspace[] {
.all(taskId) as PairedWorkspace[];
}
export function listPairedExecutionsForTask(taskId: string): PairedExecution[] {
return db
.prepare(
'SELECT * FROM paired_executions WHERE task_id = ? ORDER BY created_at, id',
)
.all(taskId) as PairedExecution[];
}
export function cancelSupersededPairedExecutions(args: {
taskId: string;
role: PairedExecution['role'];
exceptExecutionId?: string;
note?: string | null;
}): number {
const now = new Date().toISOString();
const result = db
.prepare(
`
UPDATE paired_executions
SET status = 'cancelled',
summary = COALESCE(summary, ?),
completed_at = COALESCE(completed_at, ?)
WHERE task_id = ?
AND role = ?
AND status = 'running'
AND (? IS NULL OR id <> ?)
`,
)
.run(
args.note ?? null,
now,
args.taskId,
args.role,
args.exceptExecutionId ?? null,
args.exceptExecutionId ?? null,
);
return result.changes;
}
export function createPairedApproval(
approval: Omit<PairedApproval, 'id'>,
): number {