feat(daemon+store+api): verdict硬闸+执行期锁+conflict优先+schema

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-14 12:27:48 +08:00
parent ae9ff7a469
commit f8900de2f7
6 changed files with 83 additions and 5 deletions
+19 -2
View File
@@ -110,10 +110,11 @@ export class Store {
timeout_ms: input.timeoutMs ?? 1_800_000,
status: 'active', created_at: now(),
last_sync_at: null, logo: null, sort_order: maxOrder + 1,
checks: null, auto_approve_plan: 0, auto_approve_exec: 0,
};
this.db.prepare(
`INSERT INTO projects (id,name,repo_path,default_branch,verify_cmd,autonomy,model,concurrency,max_retries,timeout_ms,status,created_at,last_sync_at,logo,sort_order)
VALUES (@id,@name,@repo_path,@default_branch,@verify_cmd,@autonomy,@model,@concurrency,@max_retries,@timeout_ms,@status,@created_at,@last_sync_at,@logo,@sort_order)`,
`INSERT INTO projects (id,name,repo_path,default_branch,verify_cmd,autonomy,model,concurrency,max_retries,timeout_ms,status,created_at,last_sync_at,logo,sort_order,checks,auto_approve_plan,auto_approve_exec)
VALUES (@id,@name,@repo_path,@default_branch,@verify_cmd,@autonomy,@model,@concurrency,@max_retries,@timeout_ms,@status,@created_at,@last_sync_at,@logo,@sort_order,@checks,@auto_approve_plan,@auto_approve_exec)`,
).run(row);
this.emit(row.id, null, 'task.created', { kind: 'project', name: row.name });
return rowToProject(row);
@@ -394,6 +395,7 @@ export class Store {
patchTask(taskId: string, patch: PatchTaskInput): Task {
let row = this.getTaskRow(taskId);
if (!row) throw new StoreError(`任务不存在: ${taskId}`);
this.assertNotInflight(taskId);
const fields: string[] = [];
let depsChanged = false;
@@ -701,6 +703,21 @@ export class Store {
this.db.prepare(`UPDATE tasks SET next_eligible_at = ?, updated_at = ? WHERE id = ?`).run(iso, now(), taskId);
}
/** 任务是否有在途 runstatus='started'*/
hasInflightRun(taskId: string): boolean {
const row = this.db.prepare(
`SELECT COUNT(*) as cnt FROM runs WHERE task_id = ? AND status = 'started'`,
).get(taskId) as { cnt: number };
return row.cnt > 0;
}
/** 若任务有在途 run 则抛错(用于保护 user mutation*/
private assertNotInflight(taskId: string): void {
if (this.hasInflightRun(taskId)) {
throw new StoreError(`任务 ${taskId} 有在途 run,执行期间禁止修改`);
}
}
/** 项目内"有 started run 的任务" id 集合(多进程并发闸 + claimable 排除在途;executor 与 planner 通用)。 */
inflightTaskIds(projectId: string): Set<string> {
const rows = this.db.prepare(