feat(daemon+store+api): verdict硬闸+执行期锁+conflict优先+schema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ export interface ProjectRow {
|
||||
concurrency: number; max_retries: number; timeout_ms: number;
|
||||
status: string; created_at: string;
|
||||
last_sync_at: string | null; logo: string | null; sort_order: number;
|
||||
checks: string | null; auto_approve_plan: number; auto_approve_exec: number;
|
||||
}
|
||||
export interface TaskRow {
|
||||
id: string; project_id: string; parent_id: string | null; depth: number;
|
||||
@@ -41,6 +42,9 @@ export function rowToProject(r: ProjectRow): Project {
|
||||
concurrency: r.concurrency, maxRetries: r.max_retries ?? 2, timeoutMs: r.timeout_ms ?? 1_800_000,
|
||||
status: r.status as Project['status'], createdAt: r.created_at,
|
||||
lastSyncAt: r.last_sync_at, logo: r.logo, sortOrder: r.sort_order,
|
||||
checks: r.checks ?? null,
|
||||
autoApprovePlan: Boolean(r.auto_approve_plan),
|
||||
autoApproveExec: Boolean(r.auto_approve_exec),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,10 @@ CREATE TABLE IF NOT EXISTS projects (
|
||||
created_at TEXT NOT NULL,
|
||||
last_sync_at TEXT, -- 最近一次 todo.json 同步时间
|
||||
logo TEXT, -- 自定义 logo(URL / 仓库内相对路径;null=自动)
|
||||
sort_order INTEGER NOT NULL DEFAULT 0 -- 侧栏排序(小在前)
|
||||
sort_order INTEGER NOT NULL DEFAULT 0, -- 侧栏排序(小在前)
|
||||
checks TEXT, -- 分项检查命令 JSON(如 {"lint":"npm run lint"})
|
||||
auto_approve_plan INTEGER NOT NULL DEFAULT 0, -- 全 easy 子任务时跳过 plan_review(0=关)
|
||||
auto_approve_exec INTEGER NOT NULL DEFAULT 0 -- 双复审 approve 后跳过 exec_review(0=关)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
|
||||
+19
-2
@@ -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);
|
||||
}
|
||||
|
||||
/** 任务是否有在途 run(status='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(
|
||||
|
||||
Reference in New Issue
Block a user