agent-tower 0.5.1-beta.2 → 0.5.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +4 -0
- package/dist/app.js.map +1 -1
- package/dist/core/container.d.ts +2 -0
- package/dist/core/container.d.ts.map +1 -1
- package/dist/core/container.js +8 -0
- package/dist/core/container.js.map +1 -1
- package/dist/executors/__tests__/codex.executor.test.js +348 -7
- package/dist/executors/__tests__/codex.executor.test.js.map +1 -1
- package/dist/executors/codex.executor.d.ts +18 -1
- package/dist/executors/codex.executor.d.ts.map +1 -1
- package/dist/executors/codex.executor.js +75 -7
- package/dist/executors/codex.executor.js.map +1 -1
- package/dist/routes/__tests__/tasks.test.js +37 -0
- package/dist/routes/__tests__/tasks.test.js.map +1 -1
- package/dist/routes/sessions.d.ts.map +1 -1
- package/dist/routes/sessions.js +12 -0
- package/dist/routes/sessions.js.map +1 -1
- package/dist/routes/tasks.js +2 -2
- package/dist/routes/tasks.js.map +1 -1
- package/dist/services/__tests__/project.service.test.d.ts +2 -0
- package/dist/services/__tests__/project.service.test.d.ts.map +1 -0
- package/dist/services/__tests__/project.service.test.js +62 -0
- package/dist/services/__tests__/project.service.test.js.map +1 -0
- package/dist/services/__tests__/session-manager.team-run.test.js +98 -0
- package/dist/services/__tests__/session-manager.team-run.test.js.map +1 -1
- package/dist/services/__tests__/task.service.test.js +352 -38
- package/dist/services/__tests__/task.service.test.js.map +1 -1
- package/dist/services/__tests__/team-reconciler.service.test.js +68 -0
- package/dist/services/__tests__/team-reconciler.service.test.js.map +1 -1
- package/dist/services/__tests__/team-run.service.test.js +47 -1
- package/dist/services/__tests__/team-run.service.test.js.map +1 -1
- package/dist/services/__tests__/team-scheduler.service.test.js +49 -0
- package/dist/services/__tests__/team-scheduler.service.test.js.map +1 -1
- package/dist/services/__tests__/workspace.service.test.js +102 -0
- package/dist/services/__tests__/workspace.service.test.js.map +1 -1
- package/dist/services/deleted-task-guard.d.ts +11 -0
- package/dist/services/deleted-task-guard.d.ts.map +1 -0
- package/dist/services/deleted-task-guard.js +10 -0
- package/dist/services/deleted-task-guard.js.map +1 -0
- package/dist/services/project.service.d.ts +1 -0
- package/dist/services/project.service.d.ts.map +1 -1
- package/dist/services/project.service.js +1 -1
- package/dist/services/project.service.js.map +1 -1
- package/dist/services/session-manager.d.ts +47 -1
- package/dist/services/session-manager.d.ts.map +1 -1
- package/dist/services/session-manager.js +124 -35
- package/dist/services/session-manager.js.map +1 -1
- package/dist/services/task-cleanup.service.d.ts +46 -0
- package/dist/services/task-cleanup.service.d.ts.map +1 -0
- package/dist/services/task-cleanup.service.js +202 -0
- package/dist/services/task-cleanup.service.js.map +1 -0
- package/dist/services/task.service.d.ts +15 -7
- package/dist/services/task.service.d.ts.map +1 -1
- package/dist/services/task.service.js +136 -63
- package/dist/services/task.service.js.map +1 -1
- package/dist/services/team-reconciler.service.d.ts.map +1 -1
- package/dist/services/team-reconciler.service.js +21 -1
- package/dist/services/team-reconciler.service.js.map +1 -1
- package/dist/services/team-run.service.d.ts.map +1 -1
- package/dist/services/team-run.service.js +15 -1
- package/dist/services/team-run.service.js.map +1 -1
- package/dist/services/team-scheduler.service.d.ts +3 -1
- package/dist/services/team-scheduler.service.d.ts.map +1 -1
- package/dist/services/team-scheduler.service.js +16 -0
- package/dist/services/team-scheduler.service.js.map +1 -1
- package/dist/services/workspace.service.d.ts +8 -0
- package/dist/services/workspace.service.d.ts.map +1 -1
- package/dist/services/workspace.service.js +70 -12
- package/dist/services/workspace.service.js.map +1 -1
- package/dist/web/assets/AgentDemoPage-CWcu7-P7.js +1 -0
- package/dist/web/assets/{DemoPage-DVt_gGQE.js → DemoPage-6hMli_sP.js} +3 -3
- package/dist/web/assets/GeneralSettingsPage-DUaFSgCY.js +1 -0
- package/dist/web/assets/MemberAvatar-S-eJAdWH.js +1 -0
- package/dist/web/assets/NotificationSettingsPage-Cl5x8F4G.js +1 -0
- package/dist/web/assets/ProfileSettingsPage-QFN9385l.js +3 -0
- package/dist/web/assets/ProjectKanbanPage-Dye8K5K1.js +89 -0
- package/dist/web/assets/ProjectSettingsPage-Do3Q1_r4.js +2 -0
- package/dist/web/assets/ProviderSettingsPage-DYOb33XQ.js +54 -0
- package/dist/web/assets/SettingsSection-D0Zzbbhp.js +1 -0
- package/dist/web/assets/TeamSettingsPage-Dq_DU1S8.js +1 -0
- package/dist/web/assets/arrow-left-D0xGRsQG.js +1 -0
- package/dist/web/assets/button-C-IDw2d8.js +1 -0
- package/dist/web/assets/check-KjbNKANU.js +1 -0
- package/dist/web/assets/chevron-down-C735lPj9.js +1 -0
- package/dist/web/assets/chevron-right-DfFiWY3R.js +1 -0
- package/dist/web/assets/{chevron-up-BkPAyFXE.js → chevron-up-DqGc0SsZ.js} +1 -1
- package/dist/web/assets/{circle-check-BmWaxH2e.js → circle-check-CSlvn06a.js} +1 -1
- package/dist/web/assets/{code-block-OCS4YCEC-U8rjjKMO.js → code-block-OCS4YCEC-BRT1Nrpy.js} +1 -1
- package/dist/web/assets/confirm-dialog-BtsxywSG.js +1 -0
- package/dist/web/assets/folder-picker-BuvfyaHB.js +1 -0
- package/dist/web/assets/index-BAz6UxmL.js +13 -0
- package/dist/web/assets/index-r8en4dlI.css +1 -0
- package/dist/web/assets/loader-circle-BLO3w-ze.js +1 -0
- package/dist/web/assets/{mermaid-NOHMQCX5-CfVlHQvQ.js → mermaid-NOHMQCX5-BLfGHrOE.js} +53 -53
- package/dist/web/assets/message-square-CIyucdyo.js +1 -0
- package/dist/web/assets/modal-D_VShm6D.js +1 -0
- package/dist/web/assets/{pencil-DvjVNKIw.js → pencil-CdJXJig-.js} +1 -1
- package/dist/web/assets/rotate-ccw-B42Jf1RW.js +1 -0
- package/dist/web/assets/{select-Xt2WadPY.js → select-kVmZCXo9.js} +1 -1
- package/dist/web/assets/{use-profiles-oZ63gfvO.js → use-profiles-Bvj_EW-7.js} +1 -1
- package/dist/web/assets/{use-providers-BVrApYJA.js → use-providers-CcFzAUyf.js} +1 -1
- package/dist/web/index.html +2 -2
- package/node_modules/@prisma/client/.prisma/client/edge.js +22 -5
- package/node_modules/@prisma/client/.prisma/client/index-browser.js +17 -0
- package/node_modules/@prisma/client/.prisma/client/index.d.ts +1534 -142
- package/node_modules/@prisma/client/.prisma/client/index.js +22 -5
- package/node_modules/@prisma/client/.prisma/client/package.json +1 -1
- package/node_modules/@prisma/client/.prisma/client/schema.prisma +23 -0
- package/node_modules/@prisma/client/.prisma/client/wasm.js +17 -0
- package/package.json +1 -1
- package/prisma/migrations/20260603000000_add_task_cleanup_jobs/migration.sql +21 -0
- package/prisma/schema.prisma +23 -0
- package/dist/web/assets/AgentDemoPage-CEWSskKz.js +0 -1
- package/dist/web/assets/GeneralSettingsPage-B7DbUzT6.js +0 -1
- package/dist/web/assets/MemberAvatar--HnaCo5f.js +0 -1
- package/dist/web/assets/NotificationSettingsPage-BklUEhB2.js +0 -1
- package/dist/web/assets/ProfileSettingsPage-Be7yYwbc.js +0 -3
- package/dist/web/assets/ProjectKanbanPage-BZR3HX3_.js +0 -90
- package/dist/web/assets/ProjectSettingsPage-Cj1h7G2-.js +0 -2
- package/dist/web/assets/ProviderSettingsPage-l244dDeI.js +0 -54
- package/dist/web/assets/TeamSettingsPage-CXp-7kqb.js +0 -1
- package/dist/web/assets/button-Cc_0rVdG.js +0 -1
- package/dist/web/assets/chevron-down-DAto5uxR.js +0 -1
- package/dist/web/assets/chevron-right-fcp8yUcf.js +0 -1
- package/dist/web/assets/confirm-dialog-D0R59Lv2.js +0 -1
- package/dist/web/assets/folder-picker-COVJdb7m.js +0 -1
- package/dist/web/assets/index-BCz-PlSG.js +0 -13
- package/dist/web/assets/index-BjIvVVfi.css +0 -1
- package/dist/web/assets/loader-circle-B0dxeUg4.js +0 -1
- package/dist/web/assets/modal-kzH1vcBa.js +0 -1
- package/dist/web/assets/upload-dtpXWuCN.js +0 -1
- package/dist/web/assets/utils-CkSf8FUe.js +0 -1
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SessionManager } from './session-manager.js';
|
|
2
|
+
export declare const TaskCleanupJobStatus: {
|
|
3
|
+
readonly PENDING: "PENDING";
|
|
4
|
+
readonly RUNNING: "RUNNING";
|
|
5
|
+
readonly COMPLETED: "COMPLETED";
|
|
6
|
+
readonly FAILED: "FAILED";
|
|
7
|
+
};
|
|
8
|
+
interface TaskCleanupSessionSnapshot {
|
|
9
|
+
id: string;
|
|
10
|
+
}
|
|
11
|
+
interface TaskCleanupWorkspaceSnapshot {
|
|
12
|
+
id: string;
|
|
13
|
+
worktreePath: string;
|
|
14
|
+
branchName: string;
|
|
15
|
+
baseBranch: string | null;
|
|
16
|
+
sessions: TaskCleanupSessionSnapshot[];
|
|
17
|
+
}
|
|
18
|
+
export interface TaskCleanupSnapshot {
|
|
19
|
+
taskId: string;
|
|
20
|
+
projectId: string;
|
|
21
|
+
project: {
|
|
22
|
+
repoPath: string;
|
|
23
|
+
mainBranch: string;
|
|
24
|
+
};
|
|
25
|
+
workspaces: TaskCleanupWorkspaceSnapshot[];
|
|
26
|
+
}
|
|
27
|
+
export declare function getTaskCleanupRetryDelayMs(attempts: number): number;
|
|
28
|
+
/**
|
|
29
|
+
* Persistent worker for resources left behind by fast task deletion.
|
|
30
|
+
*/
|
|
31
|
+
export declare class TaskCleanupService {
|
|
32
|
+
private readonly sessionManager;
|
|
33
|
+
private running;
|
|
34
|
+
private timer;
|
|
35
|
+
constructor(sessionManager: SessionManager);
|
|
36
|
+
start(intervalMs?: number): void;
|
|
37
|
+
stop(): void;
|
|
38
|
+
trigger(): void;
|
|
39
|
+
processDueJobs(limit?: number): Promise<number>;
|
|
40
|
+
processJob(jobId: string): Promise<void>;
|
|
41
|
+
private claimNextJob;
|
|
42
|
+
private cleanupSnapshot;
|
|
43
|
+
private recordFailure;
|
|
44
|
+
}
|
|
45
|
+
export {};
|
|
46
|
+
//# sourceMappingURL=task-cleanup.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-cleanup.service.d.ts","sourceRoot":"","sources":["../../src/services/task-cleanup.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,eAAO,MAAM,oBAAoB;;;;;CAKvB,CAAC;AAEX,UAAU,0BAA0B;IAClC,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,UAAU,4BAA4B;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,0BAA0B,EAAE,CAAC;CACxC;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,UAAU,EAAE,4BAA4B,EAAE,CAAC;CAC5C;AAmBD,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnE;AAeD;;GAEG;AACH,qBAAa,kBAAkB;IAIjB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAH3C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,KAAK,CAA+C;gBAE/B,cAAc,EAAE,cAAc;IAE3D,KAAK,CAAC,UAAU,SAAS,GAAG,IAAI;IAYhC,IAAI,IAAI,IAAI;IAMZ,OAAO,IAAI,IAAI;IAMT,cAAc,CAAC,KAAK,SAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAiB1C,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAyBhC,YAAY;YA6CZ,eAAe;YAuCf,aAAa;CAW5B"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { prisma } from '../utils/index.js';
|
|
2
|
+
import { WorktreeManager } from '../git/worktree.manager.js';
|
|
3
|
+
export const TaskCleanupJobStatus = {
|
|
4
|
+
PENDING: 'PENDING',
|
|
5
|
+
RUNNING: 'RUNNING',
|
|
6
|
+
COMPLETED: 'COMPLETED',
|
|
7
|
+
FAILED: 'FAILED',
|
|
8
|
+
};
|
|
9
|
+
const MAX_JOB_ATTEMPTS = 5;
|
|
10
|
+
const RETRY_DELAYS_MS = [
|
|
11
|
+
30_000,
|
|
12
|
+
2 * 60_000,
|
|
13
|
+
10 * 60_000,
|
|
14
|
+
30 * 60_000,
|
|
15
|
+
60 * 60_000,
|
|
16
|
+
];
|
|
17
|
+
function parseSnapshot(payload) {
|
|
18
|
+
const parsed = JSON.parse(payload);
|
|
19
|
+
if (!parsed.taskId || !parsed.projectId || !parsed.project?.repoPath || !Array.isArray(parsed.workspaces)) {
|
|
20
|
+
throw new Error('Invalid task cleanup snapshot');
|
|
21
|
+
}
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
export function getTaskCleanupRetryDelayMs(attempts) {
|
|
25
|
+
return RETRY_DELAYS_MS[Math.min(Math.max(attempts - 1, 0), RETRY_DELAYS_MS.length - 1)];
|
|
26
|
+
}
|
|
27
|
+
function toErrorMessage(error) {
|
|
28
|
+
return error instanceof Error ? error.message : String(error);
|
|
29
|
+
}
|
|
30
|
+
function isMissingCleanupTableError(error) {
|
|
31
|
+
return Boolean(error &&
|
|
32
|
+
typeof error === 'object' &&
|
|
33
|
+
'code' in error &&
|
|
34
|
+
error.code === 'P2021');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Persistent worker for resources left behind by fast task deletion.
|
|
38
|
+
*/
|
|
39
|
+
export class TaskCleanupService {
|
|
40
|
+
sessionManager;
|
|
41
|
+
running = false;
|
|
42
|
+
timer = null;
|
|
43
|
+
constructor(sessionManager) {
|
|
44
|
+
this.sessionManager = sessionManager;
|
|
45
|
+
}
|
|
46
|
+
start(intervalMs = 30_000) {
|
|
47
|
+
if (this.timer)
|
|
48
|
+
return;
|
|
49
|
+
this.timer = setInterval(() => {
|
|
50
|
+
this.processDueJobs().catch((error) => {
|
|
51
|
+
console.error('[TaskCleanupService] processDueJobs failed:', error);
|
|
52
|
+
});
|
|
53
|
+
}, intervalMs);
|
|
54
|
+
this.processDueJobs().catch((error) => {
|
|
55
|
+
console.error('[TaskCleanupService] startup cleanup failed:', error);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
stop() {
|
|
59
|
+
if (!this.timer)
|
|
60
|
+
return;
|
|
61
|
+
clearInterval(this.timer);
|
|
62
|
+
this.timer = null;
|
|
63
|
+
}
|
|
64
|
+
trigger() {
|
|
65
|
+
this.processDueJobs().catch((error) => {
|
|
66
|
+
console.error('[TaskCleanupService] triggered cleanup failed:', error);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async processDueJobs(limit = 5) {
|
|
70
|
+
if (this.running)
|
|
71
|
+
return 0;
|
|
72
|
+
this.running = true;
|
|
73
|
+
try {
|
|
74
|
+
let processed = 0;
|
|
75
|
+
while (processed < limit) {
|
|
76
|
+
const job = await this.claimNextJob();
|
|
77
|
+
if (!job)
|
|
78
|
+
break;
|
|
79
|
+
await this.processJob(job.id);
|
|
80
|
+
processed++;
|
|
81
|
+
}
|
|
82
|
+
return processed;
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
this.running = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async processJob(jobId) {
|
|
89
|
+
const job = await prisma.taskCleanupJob.findUnique({ where: { id: jobId } });
|
|
90
|
+
if (!job)
|
|
91
|
+
return;
|
|
92
|
+
try {
|
|
93
|
+
const snapshot = parseSnapshot(job.payload);
|
|
94
|
+
await this.cleanupSnapshot(snapshot);
|
|
95
|
+
await prisma.$transaction([
|
|
96
|
+
prisma.task.deleteMany({ where: { id: snapshot.taskId } }),
|
|
97
|
+
prisma.taskCleanupJob.update({
|
|
98
|
+
where: { id: jobId },
|
|
99
|
+
data: {
|
|
100
|
+
status: TaskCleanupJobStatus.COMPLETED,
|
|
101
|
+
lastError: null,
|
|
102
|
+
nextRetryAt: null,
|
|
103
|
+
completedAt: new Date(),
|
|
104
|
+
},
|
|
105
|
+
}),
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
await this.recordFailure(jobId, job.attempts, toErrorMessage(error));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async claimNextJob() {
|
|
113
|
+
const now = new Date();
|
|
114
|
+
let job;
|
|
115
|
+
try {
|
|
116
|
+
job = await prisma.taskCleanupJob.findFirst({
|
|
117
|
+
where: {
|
|
118
|
+
OR: [
|
|
119
|
+
{ status: TaskCleanupJobStatus.PENDING },
|
|
120
|
+
{
|
|
121
|
+
status: TaskCleanupJobStatus.RUNNING,
|
|
122
|
+
attempts: { lt: MAX_JOB_ATTEMPTS },
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
status: TaskCleanupJobStatus.FAILED,
|
|
126
|
+
attempts: { lt: MAX_JOB_ATTEMPTS },
|
|
127
|
+
OR: [{ nextRetryAt: null }, { nextRetryAt: { lte: now } }],
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
131
|
+
orderBy: [{ createdAt: 'asc' }],
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
if (isMissingCleanupTableError(error)) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
if (!job)
|
|
141
|
+
return null;
|
|
142
|
+
try {
|
|
143
|
+
return await prisma.taskCleanupJob.update({
|
|
144
|
+
where: { id: job.id },
|
|
145
|
+
data: {
|
|
146
|
+
status: TaskCleanupJobStatus.RUNNING,
|
|
147
|
+
attempts: { increment: 1 },
|
|
148
|
+
startedAt: now,
|
|
149
|
+
lastError: null,
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async cleanupSnapshot(snapshot) {
|
|
158
|
+
const worktreeManager = new WorktreeManager(snapshot.project.repoPath);
|
|
159
|
+
for (const workspace of snapshot.workspaces) {
|
|
160
|
+
for (const session of workspace.sessions) {
|
|
161
|
+
try {
|
|
162
|
+
await this.sessionManager.stop(session.id, { skipTeamRunReconcile: true });
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
console.warn(`[TaskCleanupService] failed to stop session ${session.id}:`, toErrorMessage(error));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const workspace of snapshot.workspaces) {
|
|
170
|
+
if (!workspace.worktreePath)
|
|
171
|
+
continue;
|
|
172
|
+
const result = await worktreeManager.remove(workspace.worktreePath);
|
|
173
|
+
if (result.status === 'unregistered') {
|
|
174
|
+
throw new Error(`Workspace ${workspace.id} path is unregistered or unsafe to remove: ${result.path}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const workspace of snapshot.workspaces) {
|
|
178
|
+
const result = await worktreeManager.deleteBranchIfSafe(workspace.branchName, {
|
|
179
|
+
protectedBranches: [snapshot.project.mainBranch, workspace.baseBranch],
|
|
180
|
+
});
|
|
181
|
+
if (result.status === 'failed') {
|
|
182
|
+
throw new Error(`Failed to delete branch ${result.branchName}: ${result.reason ?? 'unknown error'}`);
|
|
183
|
+
}
|
|
184
|
+
if (result.status === 'checked_out') {
|
|
185
|
+
throw new Error(`Branch ${result.branchName} is checked out: ${result.reason ?? 'unknown location'}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
await worktreeManager.prune();
|
|
189
|
+
}
|
|
190
|
+
async recordFailure(jobId, attempts, message) {
|
|
191
|
+
const retryable = attempts < MAX_JOB_ATTEMPTS;
|
|
192
|
+
await prisma.taskCleanupJob.update({
|
|
193
|
+
where: { id: jobId },
|
|
194
|
+
data: {
|
|
195
|
+
status: TaskCleanupJobStatus.FAILED,
|
|
196
|
+
lastError: message,
|
|
197
|
+
nextRetryAt: retryable ? new Date(Date.now() + getTaskCleanupRetryDelayMs(attempts)) : null,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=task-cleanup.service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"task-cleanup.service.js","sourceRoot":"","sources":["../../src/services/task-cleanup.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { TaskStatus } from '../types/index.js';
|
|
2
2
|
import type { EventBus } from '../core/event-bus.js';
|
|
3
3
|
import type { SessionManager } from './session-manager.js';
|
|
4
|
+
import type { TaskCleanupService } from './task-cleanup.service.js';
|
|
4
5
|
interface CreateTaskInput {
|
|
5
6
|
title: string;
|
|
6
7
|
description?: string;
|
|
@@ -19,7 +20,8 @@ interface FindTasksParams {
|
|
|
19
20
|
export declare class TaskService {
|
|
20
21
|
private readonly eventBus;
|
|
21
22
|
private readonly sessionManager;
|
|
22
|
-
|
|
23
|
+
private readonly cleanupService?;
|
|
24
|
+
constructor(eventBus: EventBus, sessionManager: SessionManager, cleanupService?: Pick<TaskCleanupService, "trigger"> | undefined);
|
|
23
25
|
/**
|
|
24
26
|
* 获取项目的任务列表(支持按状态过滤和分页)
|
|
25
27
|
*/
|
|
@@ -60,6 +62,7 @@ export declare class TaskService {
|
|
|
60
62
|
description: string | null;
|
|
61
63
|
createdAt: Date;
|
|
62
64
|
updatedAt: Date;
|
|
65
|
+
deletedAt: Date | null;
|
|
63
66
|
title: string;
|
|
64
67
|
status: string;
|
|
65
68
|
priority: number;
|
|
@@ -110,6 +113,7 @@ export declare class TaskService {
|
|
|
110
113
|
description: string | null;
|
|
111
114
|
createdAt: Date;
|
|
112
115
|
updatedAt: Date;
|
|
116
|
+
deletedAt: Date | null;
|
|
113
117
|
title: string;
|
|
114
118
|
status: string;
|
|
115
119
|
priority: number;
|
|
@@ -126,6 +130,7 @@ export declare class TaskService {
|
|
|
126
130
|
description: string | null;
|
|
127
131
|
createdAt: Date;
|
|
128
132
|
updatedAt: Date;
|
|
133
|
+
deletedAt: Date | null;
|
|
129
134
|
title: string;
|
|
130
135
|
status: string;
|
|
131
136
|
priority: number;
|
|
@@ -140,6 +145,7 @@ export declare class TaskService {
|
|
|
140
145
|
description: string | null;
|
|
141
146
|
createdAt: Date;
|
|
142
147
|
updatedAt: Date;
|
|
148
|
+
deletedAt: Date | null;
|
|
143
149
|
title: string;
|
|
144
150
|
status: string;
|
|
145
151
|
priority: number;
|
|
@@ -155,6 +161,7 @@ export declare class TaskService {
|
|
|
155
161
|
description: string | null;
|
|
156
162
|
createdAt: Date;
|
|
157
163
|
updatedAt: Date;
|
|
164
|
+
deletedAt: Date | null;
|
|
158
165
|
title: string;
|
|
159
166
|
status: string;
|
|
160
167
|
priority: number;
|
|
@@ -170,6 +177,7 @@ export declare class TaskService {
|
|
|
170
177
|
description: string | null;
|
|
171
178
|
createdAt: Date;
|
|
172
179
|
updatedAt: Date;
|
|
180
|
+
deletedAt: Date | null;
|
|
173
181
|
title: string;
|
|
174
182
|
status: string;
|
|
175
183
|
priority: number;
|
|
@@ -177,13 +185,12 @@ export declare class TaskService {
|
|
|
177
185
|
projectId: string;
|
|
178
186
|
}>;
|
|
179
187
|
/**
|
|
180
|
-
*
|
|
188
|
+
* 快速删除任务
|
|
181
189
|
*
|
|
182
|
-
* 1.
|
|
183
|
-
* 2.
|
|
184
|
-
* 3.
|
|
185
|
-
* 4.
|
|
186
|
-
* 5. 通过 EventBus 通知前端实时删除
|
|
190
|
+
* 1. 原子标记 Task 为 deletedAt,使普通列表立即隐藏
|
|
191
|
+
* 2. 保存后台清理快照
|
|
192
|
+
* 3. 通过 EventBus 通知前端实时删除
|
|
193
|
+
* 4. 后台 worker 再停止 Session、删除 worktree/branch,并最终硬删除 Task
|
|
187
194
|
*/
|
|
188
195
|
delete(id: string): Promise<boolean>;
|
|
189
196
|
/**
|
|
@@ -210,6 +217,7 @@ export declare class TaskService {
|
|
|
210
217
|
description: string | null;
|
|
211
218
|
createdAt: Date;
|
|
212
219
|
updatedAt: Date;
|
|
220
|
+
deletedAt: Date | null;
|
|
213
221
|
title: string;
|
|
214
222
|
status: string;
|
|
215
223
|
priority: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.service.d.ts","sourceRoot":"","sources":["../../src/services/task.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAkD,MAAM,mBAAmB,CAAC;AAM/F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"task.service.d.ts","sourceRoot":"","sources":["../../src/services/task.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAkD,MAAM,mBAAmB,CAAC;AAM/F,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,kBAAkB,EAAuB,MAAM,2BAA2B,CAAC;AAIzF,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,eAAe;IACvB,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAwBD,qBAAa,WAAW;IAEpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAC/B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;gBAFf,QAAQ,EAAE,QAAQ,EAClB,cAAc,EAAE,cAAc,EAC9B,cAAc,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,YAAA;IAGvE;;OAEG;IACG,eAAe,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,eAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAuDrE;;OAEG;IACG,QAAQ,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAazB;;;;OAIG;IACG,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe;;;;;;;;;;;;IA6BtD;;OAEG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,eAAe;;;;;;;;;;;;IAoB/C;;;OAGG;IACG,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU;;;;;;;;;;;;IA2CjD;;;OAGG;IACG,cAAc,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU;;;;;;;;;;;;IAgCtE;;;;;;;OAOG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM;IAwIvB;;OAEG;IACG,mBAAmB,CAAC,SAAS,EAAE,MAAM;;;;;;;;IAgD3C;;;;;;;OAOG;IACG,KAAK,CAAC,EAAE,EAAE,MAAM;;;;;;;;;;;;IA+CtB;;OAEG;IACH,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAGzE"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { prisma } from '../utils/index.js';
|
|
2
2
|
import { TaskStatus, SessionStatus, SessionPurpose, WorkspaceStatus } from '../types/index.js';
|
|
3
3
|
import { NotFoundError, ValidationError, InvalidStateTransitionError, } from '../errors.js';
|
|
4
|
-
import { WorktreeManager } from '../git/worktree.manager.js';
|
|
5
4
|
import { ensureProjectIsMutable } from './project-guards.js';
|
|
5
|
+
import { defaultTeamLockService } from './team-lock.service.js';
|
|
6
6
|
/**
|
|
7
7
|
* 合法的状态流转规则
|
|
8
8
|
* 看板拖拽场景下允许任意状态互转,状态变更无危险副作用
|
|
@@ -14,6 +14,8 @@ const VALID_TRANSITIONS = {
|
|
|
14
14
|
[TaskStatus.DONE]: [TaskStatus.TODO, TaskStatus.IN_PROGRESS, TaskStatus.IN_REVIEW, TaskStatus.CANCELLED],
|
|
15
15
|
[TaskStatus.CANCELLED]: [TaskStatus.TODO, TaskStatus.IN_PROGRESS, TaskStatus.IN_REVIEW, TaskStatus.DONE],
|
|
16
16
|
};
|
|
17
|
+
const CANCELLABLE_DELETE_WORK_REQUEST_STATUSES = ['PENDING_APPROVAL', 'QUEUED', 'STARTED'];
|
|
18
|
+
const CANCELLABLE_DELETE_INVOCATION_STATUSES = ['QUEUED', 'RUNNING', 'SESSION_ENDED', 'WAITING_ROOM_REPLY'];
|
|
17
19
|
function normalizeTaskTitle(title) {
|
|
18
20
|
const normalized = title.trim();
|
|
19
21
|
if (normalized.length === 0) {
|
|
@@ -24,9 +26,11 @@ function normalizeTaskTitle(title) {
|
|
|
24
26
|
export class TaskService {
|
|
25
27
|
eventBus;
|
|
26
28
|
sessionManager;
|
|
27
|
-
|
|
29
|
+
cleanupService;
|
|
30
|
+
constructor(eventBus, sessionManager, cleanupService) {
|
|
28
31
|
this.eventBus = eventBus;
|
|
29
32
|
this.sessionManager = sessionManager;
|
|
33
|
+
this.cleanupService = cleanupService;
|
|
30
34
|
}
|
|
31
35
|
/**
|
|
32
36
|
* 获取项目的任务列表(支持按状态过滤和分页)
|
|
@@ -42,7 +46,7 @@ export class TaskService {
|
|
|
42
46
|
const page = Math.max(1, params.page || 1);
|
|
43
47
|
const limit = Math.min(1000, Math.max(1, params.limit || 200));
|
|
44
48
|
const skip = (page - 1) * limit;
|
|
45
|
-
const where = { projectId };
|
|
49
|
+
const where = { projectId, deletedAt: null };
|
|
46
50
|
if (params.status) {
|
|
47
51
|
where.status = params.status;
|
|
48
52
|
}
|
|
@@ -84,8 +88,8 @@ export class TaskService {
|
|
|
84
88
|
* 获取任务详情
|
|
85
89
|
*/
|
|
86
90
|
async findById(id) {
|
|
87
|
-
const task = await prisma.task.
|
|
88
|
-
where: { id },
|
|
91
|
+
const task = await prisma.task.findFirst({
|
|
92
|
+
where: { id, deletedAt: null },
|
|
89
93
|
include: { workspaces: { include: { sessions: { where: { purpose: { not: SessionPurpose.COMMIT_MSG } } } } } },
|
|
90
94
|
});
|
|
91
95
|
if (!task) {
|
|
@@ -110,7 +114,7 @@ export class TaskService {
|
|
|
110
114
|
ensureProjectIsMutable(project, 'create tasks');
|
|
111
115
|
// 自动计算 position
|
|
112
116
|
const maxPosition = await prisma.task.aggregate({
|
|
113
|
-
where: { projectId, status: TaskStatus.TODO },
|
|
117
|
+
where: { projectId, status: TaskStatus.TODO, deletedAt: null },
|
|
114
118
|
_max: { position: true },
|
|
115
119
|
});
|
|
116
120
|
return prisma.task.create({
|
|
@@ -131,8 +135,8 @@ export class TaskService {
|
|
|
131
135
|
...input,
|
|
132
136
|
...(input.title !== undefined ? { title: normalizeTaskTitle(input.title) } : {}),
|
|
133
137
|
};
|
|
134
|
-
const task = await prisma.task.
|
|
135
|
-
where: { id },
|
|
138
|
+
const task = await prisma.task.findFirst({
|
|
139
|
+
where: { id, deletedAt: null },
|
|
136
140
|
include: { project: true },
|
|
137
141
|
});
|
|
138
142
|
if (!task) {
|
|
@@ -149,8 +153,8 @@ export class TaskService {
|
|
|
149
153
|
* 更新后通过 EventBus 发射 task:updated 事件,通知前端实时更新
|
|
150
154
|
*/
|
|
151
155
|
async updateStatus(id, status) {
|
|
152
|
-
const task = await prisma.task.
|
|
153
|
-
where: { id },
|
|
156
|
+
const task = await prisma.task.findFirst({
|
|
157
|
+
where: { id, deletedAt: null },
|
|
154
158
|
include: { project: true },
|
|
155
159
|
});
|
|
156
160
|
if (!task) {
|
|
@@ -169,7 +173,7 @@ export class TaskService {
|
|
|
169
173
|
}
|
|
170
174
|
// 切换状态时自动计算新列的 position
|
|
171
175
|
const maxPosition = await prisma.task.aggregate({
|
|
172
|
-
where: { projectId: task.projectId, status },
|
|
176
|
+
where: { projectId: task.projectId, status, deletedAt: null },
|
|
173
177
|
_max: { position: true },
|
|
174
178
|
});
|
|
175
179
|
const updated = await prisma.task.update({
|
|
@@ -188,8 +192,8 @@ export class TaskService {
|
|
|
188
192
|
* 如果同时传了 status,会进行状态流转并通知前端
|
|
189
193
|
*/
|
|
190
194
|
async updatePosition(id, position, status) {
|
|
191
|
-
const task = await prisma.task.
|
|
192
|
-
where: { id },
|
|
195
|
+
const task = await prisma.task.findFirst({
|
|
196
|
+
where: { id, deletedAt: null },
|
|
193
197
|
include: { project: true },
|
|
194
198
|
});
|
|
195
199
|
if (!task) {
|
|
@@ -215,69 +219,138 @@ export class TaskService {
|
|
|
215
219
|
return updated;
|
|
216
220
|
}
|
|
217
221
|
/**
|
|
218
|
-
*
|
|
222
|
+
* 快速删除任务
|
|
219
223
|
*
|
|
220
|
-
* 1.
|
|
221
|
-
* 2.
|
|
222
|
-
* 3.
|
|
223
|
-
* 4.
|
|
224
|
-
* 5. 通过 EventBus 通知前端实时删除
|
|
224
|
+
* 1. 原子标记 Task 为 deletedAt,使普通列表立即隐藏
|
|
225
|
+
* 2. 保存后台清理快照
|
|
226
|
+
* 3. 通过 EventBus 通知前端实时删除
|
|
227
|
+
* 4. 后台 worker 再停止 Session、删除 worktree/branch,并最终硬删除 Task
|
|
225
228
|
*/
|
|
226
229
|
async delete(id) {
|
|
227
|
-
const
|
|
230
|
+
const deletedAt = new Date();
|
|
231
|
+
const taskForGuard = await prisma.task.findUnique({
|
|
228
232
|
where: { id },
|
|
229
|
-
include: {
|
|
230
|
-
project: true,
|
|
231
|
-
workspaces: {
|
|
232
|
-
include: { sessions: true },
|
|
233
|
-
},
|
|
234
|
-
},
|
|
233
|
+
include: { project: true },
|
|
235
234
|
});
|
|
236
|
-
if (!
|
|
235
|
+
if (!taskForGuard || taskForGuard.deletedAt) {
|
|
237
236
|
throw new NotFoundError('Task', id);
|
|
238
237
|
}
|
|
239
|
-
ensureProjectIsMutable(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
try {
|
|
256
|
-
await worktreeManager.remove(workspace.worktreePath);
|
|
257
|
-
}
|
|
258
|
-
catch (err) {
|
|
259
|
-
console.warn(`[TaskService] Failed to remove worktree for workspace ${workspace.id} during task delete:`, err instanceof Error ? err.message : err);
|
|
238
|
+
ensureProjectIsMutable(taskForGuard.project, 'delete tasks');
|
|
239
|
+
const marked = await prisma.task.updateMany({
|
|
240
|
+
where: {
|
|
241
|
+
id,
|
|
242
|
+
deletedAt: null,
|
|
243
|
+
project: { archivedAt: null },
|
|
244
|
+
},
|
|
245
|
+
data: { deletedAt },
|
|
246
|
+
});
|
|
247
|
+
if (marked.count === 0) {
|
|
248
|
+
const current = await prisma.task.findUnique({
|
|
249
|
+
where: { id },
|
|
250
|
+
include: { project: true },
|
|
251
|
+
});
|
|
252
|
+
if (!current || current.deletedAt) {
|
|
253
|
+
throw new NotFoundError('Task', id);
|
|
260
254
|
}
|
|
255
|
+
ensureProjectIsMutable(current.project, 'delete tasks');
|
|
256
|
+
throw new NotFoundError('Task', id);
|
|
261
257
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
258
|
+
let deleteResult;
|
|
259
|
+
let cleanupJobCreated = false;
|
|
260
|
+
try {
|
|
261
|
+
const taskForCleanup = await prisma.task.findUnique({
|
|
262
|
+
where: { id },
|
|
263
|
+
include: {
|
|
264
|
+
project: true,
|
|
265
|
+
teamRun: {
|
|
266
|
+
include: {
|
|
267
|
+
invocations: {
|
|
268
|
+
where: { status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } },
|
|
269
|
+
select: { id: true },
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
workspaces: {
|
|
274
|
+
include: { sessions: true },
|
|
275
|
+
},
|
|
276
|
+
},
|
|
266
277
|
});
|
|
267
|
-
if (
|
|
268
|
-
|
|
278
|
+
if (!taskForCleanup) {
|
|
279
|
+
throw new NotFoundError('Task', id);
|
|
269
280
|
}
|
|
270
|
-
|
|
271
|
-
|
|
281
|
+
ensureProjectIsMutable(taskForCleanup.project, 'delete tasks');
|
|
282
|
+
const snapshot = {
|
|
283
|
+
taskId: taskForCleanup.id,
|
|
284
|
+
projectId: taskForCleanup.projectId,
|
|
285
|
+
project: {
|
|
286
|
+
repoPath: taskForCleanup.project.repoPath,
|
|
287
|
+
mainBranch: taskForCleanup.project.mainBranch,
|
|
288
|
+
},
|
|
289
|
+
workspaces: taskForCleanup.workspaces.map((workspace) => ({
|
|
290
|
+
id: workspace.id,
|
|
291
|
+
worktreePath: workspace.worktreePath,
|
|
292
|
+
branchName: workspace.branchName,
|
|
293
|
+
baseBranch: workspace.baseBranch,
|
|
294
|
+
sessions: workspace.sessions
|
|
295
|
+
.filter((session) => session.status === SessionStatus.PENDING || session.status === SessionStatus.RUNNING)
|
|
296
|
+
.map((session) => ({ id: session.id })),
|
|
297
|
+
})),
|
|
298
|
+
};
|
|
299
|
+
const teamRunId = taskForCleanup.teamRun?.id;
|
|
300
|
+
const cancelledInvocationIds = taskForCleanup.teamRun?.invocations.map((invocation) => invocation.id) ?? [];
|
|
301
|
+
await prisma.$transaction(async (tx) => {
|
|
302
|
+
await tx.taskCleanupJob.create({
|
|
303
|
+
data: {
|
|
304
|
+
taskId: taskForCleanup.id,
|
|
305
|
+
projectId: taskForCleanup.projectId,
|
|
306
|
+
payload: JSON.stringify(snapshot),
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
if (teamRunId) {
|
|
310
|
+
await tx.workRequest.updateMany({
|
|
311
|
+
where: {
|
|
312
|
+
teamRunId,
|
|
313
|
+
status: { in: CANCELLABLE_DELETE_WORK_REQUEST_STATUSES },
|
|
314
|
+
},
|
|
315
|
+
data: { status: 'CANCELLED' },
|
|
316
|
+
});
|
|
317
|
+
await tx.agentInvocation.updateMany({
|
|
318
|
+
where: {
|
|
319
|
+
teamRunId,
|
|
320
|
+
status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES },
|
|
321
|
+
},
|
|
322
|
+
data: {
|
|
323
|
+
status: 'CANCELLED',
|
|
324
|
+
nextRoomReplyReminderAt: null,
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
cleanupJobCreated = true;
|
|
330
|
+
deleteResult = {
|
|
331
|
+
projectId: taskForCleanup.projectId,
|
|
332
|
+
cancelledInvocationIds,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
catch (error) {
|
|
336
|
+
if (!cleanupJobCreated) {
|
|
337
|
+
await prisma.task.updateMany({
|
|
338
|
+
where: { id, deletedAt },
|
|
339
|
+
data: { deletedAt: null },
|
|
340
|
+
}).catch(() => {
|
|
341
|
+
// If rollback fails, leave the task hidden rather than masking the original error.
|
|
342
|
+
});
|
|
272
343
|
}
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
for (const invocationId of deleteResult.cancelledInvocationIds) {
|
|
347
|
+
defaultTeamLockService.releaseByOwner(invocationId);
|
|
273
348
|
}
|
|
274
|
-
// 4. 级联删除数据库记录
|
|
275
|
-
await prisma.task.delete({ where: { id } });
|
|
276
|
-
// 5. 通知前端
|
|
277
349
|
this.eventBus.emit('task:deleted', {
|
|
278
350
|
taskId: id,
|
|
279
|
-
projectId:
|
|
351
|
+
projectId: deleteResult.projectId,
|
|
280
352
|
});
|
|
353
|
+
this.cleanupService?.trigger();
|
|
281
354
|
return true;
|
|
282
355
|
}
|
|
283
356
|
/**
|
|
@@ -292,7 +365,7 @@ export class TaskService {
|
|
|
292
365
|
}
|
|
293
366
|
const counts = await prisma.task.groupBy({
|
|
294
367
|
by: ['status'],
|
|
295
|
-
where: { projectId },
|
|
368
|
+
where: { projectId, deletedAt: null },
|
|
296
369
|
_count: { id: true },
|
|
297
370
|
});
|
|
298
371
|
const stats = {
|
|
@@ -335,8 +408,8 @@ export class TaskService {
|
|
|
335
408
|
* 4. 通知前端 — 用户可重新派发 Agent(会创建新 Worktree)
|
|
336
409
|
*/
|
|
337
410
|
async retry(id) {
|
|
338
|
-
const task = await prisma.task.
|
|
339
|
-
where: { id },
|
|
411
|
+
const task = await prisma.task.findFirst({
|
|
412
|
+
where: { id, deletedAt: null },
|
|
340
413
|
include: {
|
|
341
414
|
project: true,
|
|
342
415
|
workspaces: {
|