agent-tower 0.5.1 → 0.5.2-beta.1
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/mcp/http-client.d.ts +4 -1
- package/dist/mcp/http-client.d.ts.map +1 -1
- package/dist/mcp/http-client.js +2 -2
- package/dist/mcp/http-client.js.map +1 -1
- package/dist/mcp/tools/workspaces.d.ts.map +1 -1
- package/dist/mcp/tools/workspaces.js +4 -1
- package/dist/mcp/tools/workspaces.js.map +1 -1
- package/dist/mcp/types.d.ts +3 -0
- package/dist/mcp/types.d.ts.map +1 -1
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.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/git.d.ts.map +1 -1
- package/dist/routes/git.js +6 -1
- package/dist/routes/git.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/system.d.ts.map +1 -1
- package/dist/routes/system.js +21 -5
- package/dist/routes/system.js.map +1 -1
- package/dist/routes/tasks.js +2 -2
- package/dist/routes/tasks.js.map +1 -1
- package/dist/routes/workspaces.d.ts.map +1 -1
- package/dist/routes/workspaces.js +11 -4
- package/dist/routes/workspaces.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 +416 -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 +157 -0
- package/dist/services/__tests__/workspace.service.test.js.map +1 -1
- package/dist/services/commit-message.service.d.ts.map +1 -1
- package/dist/services/commit-message.service.js +3 -0
- package/dist/services/commit-message.service.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 +4 -3
- package/dist/services/project.service.js.map +1 -1
- package/dist/services/session-manager.d.ts +55 -1
- package/dist/services/session-manager.d.ts.map +1 -1
- package/dist/services/session-manager.js +133 -41
- package/dist/services/session-manager.js.map +1 -1
- package/dist/services/task-cleanup.service.d.ts +48 -0
- package/dist/services/task-cleanup.service.d.ts.map +1 -0
- package/dist/services/task-cleanup.service.js +209 -0
- package/dist/services/task-cleanup.service.js.map +1 -0
- package/dist/services/task.service.d.ts +19 -7
- package/dist/services/task.service.d.ts.map +1 -1
- package/dist/services/task.service.js +139 -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-kind.d.ts +13 -0
- package/dist/services/workspace-kind.d.ts.map +1 -0
- package/dist/services/workspace-kind.js +16 -0
- package/dist/services/workspace-kind.js.map +1 -0
- package/dist/services/workspace.service.d.ts +26 -0
- package/dist/services/workspace.service.d.ts.map +1 -1
- package/dist/services/workspace.service.js +173 -23
- package/dist/services/workspace.service.js.map +1 -1
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -1
- package/dist/web/assets/AgentDemoPage-CkhVcgZo.js +1 -0
- package/dist/web/assets/{DemoPage-DeXHpe0f.js → DemoPage-dkmp8Vvn.js} +3 -3
- package/dist/web/assets/GeneralSettingsPage-B4DH6g3D.js +1 -0
- package/dist/web/assets/MemberAvatar-pwugXGL4.js +1 -0
- package/dist/web/assets/NotificationSettingsPage-C-bPU_ZE.js +1 -0
- package/dist/web/assets/ProfileSettingsPage-Dlr6BmHb.js +3 -0
- package/dist/web/assets/ProjectKanbanPage-7oEfjjWV.js +89 -0
- package/dist/web/assets/ProjectSettingsPage-DZZksq5G.js +2 -0
- package/dist/web/assets/ProviderSettingsPage-BWN7CEG6.js +54 -0
- package/dist/web/assets/SettingsSection-C-sMhXpf.js +1 -0
- package/dist/web/assets/TeamSettingsPage-924xpocx.js +1 -0
- package/dist/web/assets/arrow-left-UHjQiY5K.js +1 -0
- package/dist/web/assets/button-CUTjpRqw.js +1 -0
- package/dist/web/assets/check-CPkZgPjx.js +1 -0
- package/dist/web/assets/chevron-down-Bby7sJEv.js +1 -0
- package/dist/web/assets/chevron-right-DCC0lyoB.js +1 -0
- package/dist/web/assets/{chevron-up-xy9sRgGr.js → chevron-up-BnCoaejn.js} +1 -1
- package/dist/web/assets/{circle-check-D53Ur01f.js → circle-check-BscClK07.js} +1 -1
- package/dist/web/assets/{code-block-OCS4YCEC-BHTzJkrx.js → code-block-OCS4YCEC-DkMlYSza.js} +1 -1
- package/dist/web/assets/confirm-dialog-wsb35VpE.js +1 -0
- package/dist/web/assets/folder-picker-B-X_nrS1.js +1 -0
- package/dist/web/assets/index-4hNWw0yi.css +1 -0
- package/dist/web/assets/index-CUJoWIuo.js +13 -0
- package/dist/web/assets/loader-circle-Cul4BuAa.js +1 -0
- package/dist/web/assets/log-adapter-CtvxzS4j.js +1 -0
- package/dist/web/assets/{mermaid-NOHMQCX5-BSPQ7bmi.js → mermaid-NOHMQCX5-DoPzf-UA.js} +53 -53
- package/dist/web/assets/message-square-C7Q71jFj.js +1 -0
- package/dist/web/assets/modal-25-qs8P5.js +1 -0
- package/dist/web/assets/{pencil-zfyDXHtM.js → pencil-CaJR6Dqm.js} +1 -1
- package/dist/web/assets/rotate-ccw-Bvz7590n.js +1 -0
- package/dist/web/assets/{select-CEtSClHi.js → select-CzeTYLO4.js} +1 -1
- package/dist/web/assets/upload-_2T21rVP.js +1 -0
- package/dist/web/assets/{use-profiles-BrCaq-M0.js → use-profiles-BaCwGP06.js} +1 -1
- package/dist/web/assets/{use-providers-BmGR6zlI.js → use-providers-Bwl7R5Ql.js} +1 -1
- package/dist/web/index.html +2 -2
- package/node_modules/@agent-tower/shared/dist/types.d.ts +10 -0
- package/node_modules/@agent-tower/shared/dist/types.d.ts.map +1 -1
- package/node_modules/@agent-tower/shared/dist/types.js +6 -0
- package/node_modules/@agent-tower/shared/dist/types.js.map +1 -1
- package/node_modules/@prisma/client/.prisma/client/edge.js +24 -5
- package/node_modules/@prisma/client/.prisma/client/index-browser.js +19 -0
- package/node_modules/@prisma/client/.prisma/client/index.d.ts +1652 -142
- package/node_modules/@prisma/client/.prisma/client/index.js +24 -5
- package/node_modules/@prisma/client/.prisma/client/package.json +1 -1
- package/node_modules/@prisma/client/.prisma/client/schema.prisma +29 -1
- package/node_modules/@prisma/client/.prisma/client/wasm.js +19 -0
- package/package.json +1 -1
- package/prisma/migrations/20260603000000_add_task_cleanup_jobs/migration.sql +21 -0
- package/prisma/migrations/20260609000000_add_workspace_kind_working_dir/migration.sql +7 -0
- package/prisma/schema.prisma +29 -1
- package/dist/web/assets/AgentDemoPage-C41Npg-J.js +0 -1
- package/dist/web/assets/GeneralSettingsPage-ROIAXf_I.js +0 -1
- package/dist/web/assets/MemberAvatar-Bgb9cNkt.js +0 -1
- package/dist/web/assets/NotificationSettingsPage-BSFFB_Xk.js +0 -1
- package/dist/web/assets/ProfileSettingsPage-n6J_nKUb.js +0 -3
- package/dist/web/assets/ProjectKanbanPage-BUwfUX3X.js +0 -90
- package/dist/web/assets/ProjectSettingsPage-Dv5Ab2MY.js +0 -2
- package/dist/web/assets/ProviderSettingsPage-Ckp_pVCi.js +0 -54
- package/dist/web/assets/TeamSettingsPage-DjlyHMct.js +0 -1
- package/dist/web/assets/button-_UA8cNod.js +0 -1
- package/dist/web/assets/chevron-down-qLfxqOnz.js +0 -1
- package/dist/web/assets/chevron-right-SY4_v7-h.js +0 -1
- package/dist/web/assets/confirm-dialog-D9WQU47x.js +0 -1
- package/dist/web/assets/folder-picker-DPW13OyX.js +0 -1
- package/dist/web/assets/index-BjIvVVfi.css +0 -1
- package/dist/web/assets/index-BsrxN6e4.js +0 -13
- package/dist/web/assets/loader-circle-CPz4Agyr.js +0 -1
- package/dist/web/assets/log-adapter-i7kNXXup.js +0 -1
- package/dist/web/assets/modal-BLPS1r7Q.js +0 -1
- package/dist/web/assets/upload-hdDyQIIu.js +0 -1
- package/dist/web/assets/utils-CkSf8FUe.js +0 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { prisma } from '../utils/index.js';
|
|
2
|
+
import { WorktreeManager } from '../git/worktree.manager.js';
|
|
3
|
+
import { isWorktreeWorkspace } from './workspace-kind.js';
|
|
4
|
+
export const TaskCleanupJobStatus = {
|
|
5
|
+
PENDING: 'PENDING',
|
|
6
|
+
RUNNING: 'RUNNING',
|
|
7
|
+
COMPLETED: 'COMPLETED',
|
|
8
|
+
FAILED: 'FAILED',
|
|
9
|
+
};
|
|
10
|
+
const MAX_JOB_ATTEMPTS = 5;
|
|
11
|
+
const RETRY_DELAYS_MS = [
|
|
12
|
+
30_000,
|
|
13
|
+
2 * 60_000,
|
|
14
|
+
10 * 60_000,
|
|
15
|
+
30 * 60_000,
|
|
16
|
+
60 * 60_000,
|
|
17
|
+
];
|
|
18
|
+
function parseSnapshot(payload) {
|
|
19
|
+
const parsed = JSON.parse(payload);
|
|
20
|
+
if (!parsed.taskId || !parsed.projectId || !parsed.project?.repoPath || !Array.isArray(parsed.workspaces)) {
|
|
21
|
+
throw new Error('Invalid task cleanup snapshot');
|
|
22
|
+
}
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
25
|
+
export function getTaskCleanupRetryDelayMs(attempts) {
|
|
26
|
+
return RETRY_DELAYS_MS[Math.min(Math.max(attempts - 1, 0), RETRY_DELAYS_MS.length - 1)];
|
|
27
|
+
}
|
|
28
|
+
function toErrorMessage(error) {
|
|
29
|
+
return error instanceof Error ? error.message : String(error);
|
|
30
|
+
}
|
|
31
|
+
function isMissingCleanupTableError(error) {
|
|
32
|
+
return Boolean(error &&
|
|
33
|
+
typeof error === 'object' &&
|
|
34
|
+
'code' in error &&
|
|
35
|
+
error.code === 'P2021');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Persistent worker for resources left behind by fast task deletion.
|
|
39
|
+
*/
|
|
40
|
+
export class TaskCleanupService {
|
|
41
|
+
sessionManager;
|
|
42
|
+
running = false;
|
|
43
|
+
timer = null;
|
|
44
|
+
constructor(sessionManager) {
|
|
45
|
+
this.sessionManager = sessionManager;
|
|
46
|
+
}
|
|
47
|
+
start(intervalMs = 30_000) {
|
|
48
|
+
if (this.timer)
|
|
49
|
+
return;
|
|
50
|
+
this.timer = setInterval(() => {
|
|
51
|
+
this.processDueJobs().catch((error) => {
|
|
52
|
+
console.error('[TaskCleanupService] processDueJobs failed:', error);
|
|
53
|
+
});
|
|
54
|
+
}, intervalMs);
|
|
55
|
+
this.processDueJobs().catch((error) => {
|
|
56
|
+
console.error('[TaskCleanupService] startup cleanup failed:', error);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
stop() {
|
|
60
|
+
if (!this.timer)
|
|
61
|
+
return;
|
|
62
|
+
clearInterval(this.timer);
|
|
63
|
+
this.timer = null;
|
|
64
|
+
}
|
|
65
|
+
trigger() {
|
|
66
|
+
this.processDueJobs().catch((error) => {
|
|
67
|
+
console.error('[TaskCleanupService] triggered cleanup failed:', error);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async processDueJobs(limit = 5) {
|
|
71
|
+
if (this.running)
|
|
72
|
+
return 0;
|
|
73
|
+
this.running = true;
|
|
74
|
+
try {
|
|
75
|
+
let processed = 0;
|
|
76
|
+
while (processed < limit) {
|
|
77
|
+
const job = await this.claimNextJob();
|
|
78
|
+
if (!job)
|
|
79
|
+
break;
|
|
80
|
+
await this.processJob(job.id);
|
|
81
|
+
processed++;
|
|
82
|
+
}
|
|
83
|
+
return processed;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
this.running = false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async processJob(jobId) {
|
|
90
|
+
const job = await prisma.taskCleanupJob.findUnique({ where: { id: jobId } });
|
|
91
|
+
if (!job)
|
|
92
|
+
return;
|
|
93
|
+
try {
|
|
94
|
+
const snapshot = parseSnapshot(job.payload);
|
|
95
|
+
await this.cleanupSnapshot(snapshot);
|
|
96
|
+
await prisma.$transaction([
|
|
97
|
+
prisma.task.deleteMany({ where: { id: snapshot.taskId } }),
|
|
98
|
+
prisma.taskCleanupJob.update({
|
|
99
|
+
where: { id: jobId },
|
|
100
|
+
data: {
|
|
101
|
+
status: TaskCleanupJobStatus.COMPLETED,
|
|
102
|
+
lastError: null,
|
|
103
|
+
nextRetryAt: null,
|
|
104
|
+
completedAt: new Date(),
|
|
105
|
+
},
|
|
106
|
+
}),
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
await this.recordFailure(jobId, job.attempts, toErrorMessage(error));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async claimNextJob() {
|
|
114
|
+
const now = new Date();
|
|
115
|
+
let job;
|
|
116
|
+
try {
|
|
117
|
+
job = await prisma.taskCleanupJob.findFirst({
|
|
118
|
+
where: {
|
|
119
|
+
OR: [
|
|
120
|
+
{ status: TaskCleanupJobStatus.PENDING },
|
|
121
|
+
{
|
|
122
|
+
status: TaskCleanupJobStatus.RUNNING,
|
|
123
|
+
attempts: { lt: MAX_JOB_ATTEMPTS },
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
status: TaskCleanupJobStatus.FAILED,
|
|
127
|
+
attempts: { lt: MAX_JOB_ATTEMPTS },
|
|
128
|
+
OR: [{ nextRetryAt: null }, { nextRetryAt: { lte: now } }],
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
orderBy: [{ createdAt: 'asc' }],
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (error) {
|
|
136
|
+
if (isMissingCleanupTableError(error)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
throw error;
|
|
140
|
+
}
|
|
141
|
+
if (!job)
|
|
142
|
+
return null;
|
|
143
|
+
try {
|
|
144
|
+
return await prisma.taskCleanupJob.update({
|
|
145
|
+
where: { id: job.id },
|
|
146
|
+
data: {
|
|
147
|
+
status: TaskCleanupJobStatus.RUNNING,
|
|
148
|
+
attempts: { increment: 1 },
|
|
149
|
+
startedAt: now,
|
|
150
|
+
lastError: null,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async cleanupSnapshot(snapshot) {
|
|
159
|
+
const worktreeManager = new WorktreeManager(snapshot.project.repoPath);
|
|
160
|
+
for (const workspace of snapshot.workspaces) {
|
|
161
|
+
for (const session of workspace.sessions) {
|
|
162
|
+
try {
|
|
163
|
+
await this.sessionManager.stop(session.id, { skipTeamRunReconcile: true });
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
console.warn(`[TaskCleanupService] failed to stop session ${session.id}:`, toErrorMessage(error));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const workspace of snapshot.workspaces) {
|
|
171
|
+
if (!isWorktreeWorkspace(workspace))
|
|
172
|
+
continue;
|
|
173
|
+
if (!workspace.worktreePath)
|
|
174
|
+
continue;
|
|
175
|
+
const result = await worktreeManager.remove(workspace.worktreePath);
|
|
176
|
+
if (result.status === 'unregistered') {
|
|
177
|
+
throw new Error(`Workspace ${workspace.id} path is unregistered or unsafe to remove: ${result.path}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (const workspace of snapshot.workspaces) {
|
|
181
|
+
if (!isWorktreeWorkspace(workspace))
|
|
182
|
+
continue;
|
|
183
|
+
const result = await worktreeManager.deleteBranchIfSafe(workspace.branchName, {
|
|
184
|
+
protectedBranches: [snapshot.project.mainBranch, workspace.baseBranch],
|
|
185
|
+
});
|
|
186
|
+
if (result.status === 'failed') {
|
|
187
|
+
throw new Error(`Failed to delete branch ${result.branchName}: ${result.reason ?? 'unknown error'}`);
|
|
188
|
+
}
|
|
189
|
+
if (result.status === 'checked_out') {
|
|
190
|
+
throw new Error(`Branch ${result.branchName} is checked out: ${result.reason ?? 'unknown location'}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (snapshot.workspaces.some((workspace) => isWorktreeWorkspace(workspace))) {
|
|
194
|
+
await worktreeManager.prune();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
async recordFailure(jobId, attempts, message) {
|
|
198
|
+
const retryable = attempts < MAX_JOB_ATTEMPTS;
|
|
199
|
+
await prisma.taskCleanupJob.update({
|
|
200
|
+
where: { id: jobId },
|
|
201
|
+
data: {
|
|
202
|
+
status: TaskCleanupJobStatus.FAILED,
|
|
203
|
+
lastError: message,
|
|
204
|
+
nextRetryAt: retryable ? new Date(Date.now() + getTaskCleanupRetryDelayMs(attempts)) : null,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
//# 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
|
*/
|
|
@@ -51,6 +53,8 @@ export declare class TaskService {
|
|
|
51
53
|
branchName: string;
|
|
52
54
|
baseBranch: string | null;
|
|
53
55
|
worktreePath: string;
|
|
56
|
+
workspaceKind: string;
|
|
57
|
+
workingDir: string;
|
|
54
58
|
commitMessage: string | null;
|
|
55
59
|
previewTarget: string | null;
|
|
56
60
|
hibernatedAt: Date | null;
|
|
@@ -60,6 +64,7 @@ export declare class TaskService {
|
|
|
60
64
|
description: string | null;
|
|
61
65
|
createdAt: Date;
|
|
62
66
|
updatedAt: Date;
|
|
67
|
+
deletedAt: Date | null;
|
|
63
68
|
title: string;
|
|
64
69
|
status: string;
|
|
65
70
|
priority: number;
|
|
@@ -101,6 +106,8 @@ export declare class TaskService {
|
|
|
101
106
|
branchName: string;
|
|
102
107
|
baseBranch: string | null;
|
|
103
108
|
worktreePath: string;
|
|
109
|
+
workspaceKind: string;
|
|
110
|
+
workingDir: string;
|
|
104
111
|
commitMessage: string | null;
|
|
105
112
|
previewTarget: string | null;
|
|
106
113
|
hibernatedAt: Date | null;
|
|
@@ -110,6 +117,7 @@ export declare class TaskService {
|
|
|
110
117
|
description: string | null;
|
|
111
118
|
createdAt: Date;
|
|
112
119
|
updatedAt: Date;
|
|
120
|
+
deletedAt: Date | null;
|
|
113
121
|
title: string;
|
|
114
122
|
status: string;
|
|
115
123
|
priority: number;
|
|
@@ -126,6 +134,7 @@ export declare class TaskService {
|
|
|
126
134
|
description: string | null;
|
|
127
135
|
createdAt: Date;
|
|
128
136
|
updatedAt: Date;
|
|
137
|
+
deletedAt: Date | null;
|
|
129
138
|
title: string;
|
|
130
139
|
status: string;
|
|
131
140
|
priority: number;
|
|
@@ -140,6 +149,7 @@ export declare class TaskService {
|
|
|
140
149
|
description: string | null;
|
|
141
150
|
createdAt: Date;
|
|
142
151
|
updatedAt: Date;
|
|
152
|
+
deletedAt: Date | null;
|
|
143
153
|
title: string;
|
|
144
154
|
status: string;
|
|
145
155
|
priority: number;
|
|
@@ -155,6 +165,7 @@ export declare class TaskService {
|
|
|
155
165
|
description: string | null;
|
|
156
166
|
createdAt: Date;
|
|
157
167
|
updatedAt: Date;
|
|
168
|
+
deletedAt: Date | null;
|
|
158
169
|
title: string;
|
|
159
170
|
status: string;
|
|
160
171
|
priority: number;
|
|
@@ -170,6 +181,7 @@ export declare class TaskService {
|
|
|
170
181
|
description: string | null;
|
|
171
182
|
createdAt: Date;
|
|
172
183
|
updatedAt: Date;
|
|
184
|
+
deletedAt: Date | null;
|
|
173
185
|
title: string;
|
|
174
186
|
status: string;
|
|
175
187
|
priority: number;
|
|
@@ -177,13 +189,12 @@ export declare class TaskService {
|
|
|
177
189
|
projectId: string;
|
|
178
190
|
}>;
|
|
179
191
|
/**
|
|
180
|
-
*
|
|
192
|
+
* 快速删除任务
|
|
181
193
|
*
|
|
182
|
-
* 1.
|
|
183
|
-
* 2.
|
|
184
|
-
* 3.
|
|
185
|
-
* 4.
|
|
186
|
-
* 5. 通过 EventBus 通知前端实时删除
|
|
194
|
+
* 1. 原子标记 Task 为 deletedAt,使普通列表立即隐藏
|
|
195
|
+
* 2. 保存后台清理快照
|
|
196
|
+
* 3. 通过 EventBus 通知前端实时删除
|
|
197
|
+
* 4. 后台 worker 再停止 Session、删除 worktree/branch,并最终硬删除 Task
|
|
187
198
|
*/
|
|
188
199
|
delete(id: string): Promise<boolean>;
|
|
189
200
|
/**
|
|
@@ -210,6 +221,7 @@ export declare class TaskService {
|
|
|
210
221
|
description: string | null;
|
|
211
222
|
createdAt: Date;
|
|
212
223
|
updatedAt: Date;
|
|
224
|
+
deletedAt: Date | null;
|
|
213
225
|
title: string;
|
|
214
226
|
status: string;
|
|
215
227
|
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;
|
|
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;AAO/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;IA0IvB;;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,9 @@
|
|
|
1
1
|
import { prisma } from '../utils/index.js';
|
|
2
2
|
import { TaskStatus, SessionStatus, SessionPurpose, WorkspaceStatus } from '../types/index.js';
|
|
3
|
+
import { getWorkspaceWorkingDir } from './workspace-kind.js';
|
|
3
4
|
import { NotFoundError, ValidationError, InvalidStateTransitionError, } from '../errors.js';
|
|
4
|
-
import { WorktreeManager } from '../git/worktree.manager.js';
|
|
5
5
|
import { ensureProjectIsMutable } from './project-guards.js';
|
|
6
|
+
import { defaultTeamLockService } from './team-lock.service.js';
|
|
6
7
|
/**
|
|
7
8
|
* 合法的状态流转规则
|
|
8
9
|
* 看板拖拽场景下允许任意状态互转,状态变更无危险副作用
|
|
@@ -14,6 +15,8 @@ const VALID_TRANSITIONS = {
|
|
|
14
15
|
[TaskStatus.DONE]: [TaskStatus.TODO, TaskStatus.IN_PROGRESS, TaskStatus.IN_REVIEW, TaskStatus.CANCELLED],
|
|
15
16
|
[TaskStatus.CANCELLED]: [TaskStatus.TODO, TaskStatus.IN_PROGRESS, TaskStatus.IN_REVIEW, TaskStatus.DONE],
|
|
16
17
|
};
|
|
18
|
+
const CANCELLABLE_DELETE_WORK_REQUEST_STATUSES = ['PENDING_APPROVAL', 'QUEUED', 'STARTED'];
|
|
19
|
+
const CANCELLABLE_DELETE_INVOCATION_STATUSES = ['QUEUED', 'RUNNING', 'SESSION_ENDED', 'WAITING_ROOM_REPLY'];
|
|
17
20
|
function normalizeTaskTitle(title) {
|
|
18
21
|
const normalized = title.trim();
|
|
19
22
|
if (normalized.length === 0) {
|
|
@@ -24,9 +27,11 @@ function normalizeTaskTitle(title) {
|
|
|
24
27
|
export class TaskService {
|
|
25
28
|
eventBus;
|
|
26
29
|
sessionManager;
|
|
27
|
-
|
|
30
|
+
cleanupService;
|
|
31
|
+
constructor(eventBus, sessionManager, cleanupService) {
|
|
28
32
|
this.eventBus = eventBus;
|
|
29
33
|
this.sessionManager = sessionManager;
|
|
34
|
+
this.cleanupService = cleanupService;
|
|
30
35
|
}
|
|
31
36
|
/**
|
|
32
37
|
* 获取项目的任务列表(支持按状态过滤和分页)
|
|
@@ -42,7 +47,7 @@ export class TaskService {
|
|
|
42
47
|
const page = Math.max(1, params.page || 1);
|
|
43
48
|
const limit = Math.min(1000, Math.max(1, params.limit || 200));
|
|
44
49
|
const skip = (page - 1) * limit;
|
|
45
|
-
const where = { projectId };
|
|
50
|
+
const where = { projectId, deletedAt: null };
|
|
46
51
|
if (params.status) {
|
|
47
52
|
where.status = params.status;
|
|
48
53
|
}
|
|
@@ -84,8 +89,8 @@ export class TaskService {
|
|
|
84
89
|
* 获取任务详情
|
|
85
90
|
*/
|
|
86
91
|
async findById(id) {
|
|
87
|
-
const task = await prisma.task.
|
|
88
|
-
where: { id },
|
|
92
|
+
const task = await prisma.task.findFirst({
|
|
93
|
+
where: { id, deletedAt: null },
|
|
89
94
|
include: { workspaces: { include: { sessions: { where: { purpose: { not: SessionPurpose.COMMIT_MSG } } } } } },
|
|
90
95
|
});
|
|
91
96
|
if (!task) {
|
|
@@ -110,7 +115,7 @@ export class TaskService {
|
|
|
110
115
|
ensureProjectIsMutable(project, 'create tasks');
|
|
111
116
|
// 自动计算 position
|
|
112
117
|
const maxPosition = await prisma.task.aggregate({
|
|
113
|
-
where: { projectId, status: TaskStatus.TODO },
|
|
118
|
+
where: { projectId, status: TaskStatus.TODO, deletedAt: null },
|
|
114
119
|
_max: { position: true },
|
|
115
120
|
});
|
|
116
121
|
return prisma.task.create({
|
|
@@ -131,8 +136,8 @@ export class TaskService {
|
|
|
131
136
|
...input,
|
|
132
137
|
...(input.title !== undefined ? { title: normalizeTaskTitle(input.title) } : {}),
|
|
133
138
|
};
|
|
134
|
-
const task = await prisma.task.
|
|
135
|
-
where: { id },
|
|
139
|
+
const task = await prisma.task.findFirst({
|
|
140
|
+
where: { id, deletedAt: null },
|
|
136
141
|
include: { project: true },
|
|
137
142
|
});
|
|
138
143
|
if (!task) {
|
|
@@ -149,8 +154,8 @@ export class TaskService {
|
|
|
149
154
|
* 更新后通过 EventBus 发射 task:updated 事件,通知前端实时更新
|
|
150
155
|
*/
|
|
151
156
|
async updateStatus(id, status) {
|
|
152
|
-
const task = await prisma.task.
|
|
153
|
-
where: { id },
|
|
157
|
+
const task = await prisma.task.findFirst({
|
|
158
|
+
where: { id, deletedAt: null },
|
|
154
159
|
include: { project: true },
|
|
155
160
|
});
|
|
156
161
|
if (!task) {
|
|
@@ -169,7 +174,7 @@ export class TaskService {
|
|
|
169
174
|
}
|
|
170
175
|
// 切换状态时自动计算新列的 position
|
|
171
176
|
const maxPosition = await prisma.task.aggregate({
|
|
172
|
-
where: { projectId: task.projectId, status },
|
|
177
|
+
where: { projectId: task.projectId, status, deletedAt: null },
|
|
173
178
|
_max: { position: true },
|
|
174
179
|
});
|
|
175
180
|
const updated = await prisma.task.update({
|
|
@@ -188,8 +193,8 @@ export class TaskService {
|
|
|
188
193
|
* 如果同时传了 status,会进行状态流转并通知前端
|
|
189
194
|
*/
|
|
190
195
|
async updatePosition(id, position, status) {
|
|
191
|
-
const task = await prisma.task.
|
|
192
|
-
where: { id },
|
|
196
|
+
const task = await prisma.task.findFirst({
|
|
197
|
+
where: { id, deletedAt: null },
|
|
193
198
|
include: { project: true },
|
|
194
199
|
});
|
|
195
200
|
if (!task) {
|
|
@@ -215,69 +220,140 @@ export class TaskService {
|
|
|
215
220
|
return updated;
|
|
216
221
|
}
|
|
217
222
|
/**
|
|
218
|
-
*
|
|
223
|
+
* 快速删除任务
|
|
219
224
|
*
|
|
220
|
-
* 1.
|
|
221
|
-
* 2.
|
|
222
|
-
* 3.
|
|
223
|
-
* 4.
|
|
224
|
-
* 5. 通过 EventBus 通知前端实时删除
|
|
225
|
+
* 1. 原子标记 Task 为 deletedAt,使普通列表立即隐藏
|
|
226
|
+
* 2. 保存后台清理快照
|
|
227
|
+
* 3. 通过 EventBus 通知前端实时删除
|
|
228
|
+
* 4. 后台 worker 再停止 Session、删除 worktree/branch,并最终硬删除 Task
|
|
225
229
|
*/
|
|
226
230
|
async delete(id) {
|
|
227
|
-
const
|
|
231
|
+
const deletedAt = new Date();
|
|
232
|
+
const taskForGuard = await prisma.task.findUnique({
|
|
228
233
|
where: { id },
|
|
229
|
-
include: {
|
|
230
|
-
project: true,
|
|
231
|
-
workspaces: {
|
|
232
|
-
include: { sessions: true },
|
|
233
|
-
},
|
|
234
|
-
},
|
|
234
|
+
include: { project: true },
|
|
235
235
|
});
|
|
236
|
-
if (!
|
|
236
|
+
if (!taskForGuard || taskForGuard.deletedAt) {
|
|
237
237
|
throw new NotFoundError('Task', id);
|
|
238
238
|
}
|
|
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);
|
|
239
|
+
ensureProjectIsMutable(taskForGuard.project, 'delete tasks');
|
|
240
|
+
const marked = await prisma.task.updateMany({
|
|
241
|
+
where: {
|
|
242
|
+
id,
|
|
243
|
+
deletedAt: null,
|
|
244
|
+
project: { archivedAt: null },
|
|
245
|
+
},
|
|
246
|
+
data: { deletedAt },
|
|
247
|
+
});
|
|
248
|
+
if (marked.count === 0) {
|
|
249
|
+
const current = await prisma.task.findUnique({
|
|
250
|
+
where: { id },
|
|
251
|
+
include: { project: true },
|
|
252
|
+
});
|
|
253
|
+
if (!current || current.deletedAt) {
|
|
254
|
+
throw new NotFoundError('Task', id);
|
|
260
255
|
}
|
|
256
|
+
ensureProjectIsMutable(current.project, 'delete tasks');
|
|
257
|
+
throw new NotFoundError('Task', id);
|
|
261
258
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
259
|
+
let deleteResult;
|
|
260
|
+
let cleanupJobCreated = false;
|
|
261
|
+
try {
|
|
262
|
+
const taskForCleanup = await prisma.task.findUnique({
|
|
263
|
+
where: { id },
|
|
264
|
+
include: {
|
|
265
|
+
project: true,
|
|
266
|
+
teamRun: {
|
|
267
|
+
include: {
|
|
268
|
+
invocations: {
|
|
269
|
+
where: { status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES } },
|
|
270
|
+
select: { id: true },
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
workspaces: {
|
|
275
|
+
include: { sessions: true },
|
|
276
|
+
},
|
|
277
|
+
},
|
|
266
278
|
});
|
|
267
|
-
if (
|
|
268
|
-
|
|
279
|
+
if (!taskForCleanup) {
|
|
280
|
+
throw new NotFoundError('Task', id);
|
|
269
281
|
}
|
|
270
|
-
|
|
271
|
-
|
|
282
|
+
ensureProjectIsMutable(taskForCleanup.project, 'delete tasks');
|
|
283
|
+
const snapshot = {
|
|
284
|
+
taskId: taskForCleanup.id,
|
|
285
|
+
projectId: taskForCleanup.projectId,
|
|
286
|
+
project: {
|
|
287
|
+
repoPath: taskForCleanup.project.repoPath,
|
|
288
|
+
mainBranch: taskForCleanup.project.mainBranch,
|
|
289
|
+
},
|
|
290
|
+
workspaces: taskForCleanup.workspaces.map((workspace) => ({
|
|
291
|
+
id: workspace.id,
|
|
292
|
+
worktreePath: workspace.worktreePath,
|
|
293
|
+
workingDir: getWorkspaceWorkingDir(workspace),
|
|
294
|
+
workspaceKind: workspace.workspaceKind,
|
|
295
|
+
branchName: workspace.branchName,
|
|
296
|
+
baseBranch: workspace.baseBranch,
|
|
297
|
+
sessions: workspace.sessions
|
|
298
|
+
.filter((session) => session.status === SessionStatus.PENDING || session.status === SessionStatus.RUNNING)
|
|
299
|
+
.map((session) => ({ id: session.id })),
|
|
300
|
+
})),
|
|
301
|
+
};
|
|
302
|
+
const teamRunId = taskForCleanup.teamRun?.id;
|
|
303
|
+
const cancelledInvocationIds = taskForCleanup.teamRun?.invocations.map((invocation) => invocation.id) ?? [];
|
|
304
|
+
await prisma.$transaction(async (tx) => {
|
|
305
|
+
await tx.taskCleanupJob.create({
|
|
306
|
+
data: {
|
|
307
|
+
taskId: taskForCleanup.id,
|
|
308
|
+
projectId: taskForCleanup.projectId,
|
|
309
|
+
payload: JSON.stringify(snapshot),
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
if (teamRunId) {
|
|
313
|
+
await tx.workRequest.updateMany({
|
|
314
|
+
where: {
|
|
315
|
+
teamRunId,
|
|
316
|
+
status: { in: CANCELLABLE_DELETE_WORK_REQUEST_STATUSES },
|
|
317
|
+
},
|
|
318
|
+
data: { status: 'CANCELLED' },
|
|
319
|
+
});
|
|
320
|
+
await tx.agentInvocation.updateMany({
|
|
321
|
+
where: {
|
|
322
|
+
teamRunId,
|
|
323
|
+
status: { in: CANCELLABLE_DELETE_INVOCATION_STATUSES },
|
|
324
|
+
},
|
|
325
|
+
data: {
|
|
326
|
+
status: 'CANCELLED',
|
|
327
|
+
nextRoomReplyReminderAt: null,
|
|
328
|
+
},
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
cleanupJobCreated = true;
|
|
333
|
+
deleteResult = {
|
|
334
|
+
projectId: taskForCleanup.projectId,
|
|
335
|
+
cancelledInvocationIds,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
if (!cleanupJobCreated) {
|
|
340
|
+
await prisma.task.updateMany({
|
|
341
|
+
where: { id, deletedAt },
|
|
342
|
+
data: { deletedAt: null },
|
|
343
|
+
}).catch(() => {
|
|
344
|
+
// If rollback fails, leave the task hidden rather than masking the original error.
|
|
345
|
+
});
|
|
272
346
|
}
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
for (const invocationId of deleteResult.cancelledInvocationIds) {
|
|
350
|
+
defaultTeamLockService.releaseByOwner(invocationId);
|
|
273
351
|
}
|
|
274
|
-
// 4. 级联删除数据库记录
|
|
275
|
-
await prisma.task.delete({ where: { id } });
|
|
276
|
-
// 5. 通知前端
|
|
277
352
|
this.eventBus.emit('task:deleted', {
|
|
278
353
|
taskId: id,
|
|
279
|
-
projectId:
|
|
354
|
+
projectId: deleteResult.projectId,
|
|
280
355
|
});
|
|
356
|
+
this.cleanupService?.trigger();
|
|
281
357
|
return true;
|
|
282
358
|
}
|
|
283
359
|
/**
|
|
@@ -292,7 +368,7 @@ export class TaskService {
|
|
|
292
368
|
}
|
|
293
369
|
const counts = await prisma.task.groupBy({
|
|
294
370
|
by: ['status'],
|
|
295
|
-
where: { projectId },
|
|
371
|
+
where: { projectId, deletedAt: null },
|
|
296
372
|
_count: { id: true },
|
|
297
373
|
});
|
|
298
374
|
const stats = {
|
|
@@ -335,8 +411,8 @@ export class TaskService {
|
|
|
335
411
|
* 4. 通知前端 — 用户可重新派发 Agent(会创建新 Worktree)
|
|
336
412
|
*/
|
|
337
413
|
async retry(id) {
|
|
338
|
-
const task = await prisma.task.
|
|
339
|
-
where: { id },
|
|
414
|
+
const task = await prisma.task.findFirst({
|
|
415
|
+
where: { id, deletedAt: null },
|
|
340
416
|
include: {
|
|
341
417
|
project: true,
|
|
342
418
|
workspaces: {
|