Skip to content

Commit 75ff2cc

Browse files
author
linyuan.yang
committed
fail job 支持删除 重试
1 parent facbfc9 commit 75ff2cc

12 files changed

Lines changed: 125 additions & 43 deletions

File tree

packages/admin/src/components/modals/MemoryListModal.vue

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ const consolidating = ref(false)
4646
const reconciling = ref(false)
4747
const deleting = ref(false)
4848
const retryingJobId = ref<number | null>(null)
49+
const deletingJobId = ref<number | null>(null)
4950
5051
const rows = ref<MemorySummary[]>([])
5152
const jobs = ref<MemoryJob[]>([])
@@ -174,16 +175,12 @@ async function runReconcile() {
174175
}
175176
}
176177
177-
function canRetryExtractJob(job: MemoryJob): boolean {
178-
return job.type === 'extract' && job.status === 'failed'
179-
}
180-
181-
async function retryExtractJob(job: MemoryJob) {
182-
if (!memoryId.value || !canRetryExtractJob(job) || retryingJobId.value !== null) return
178+
async function retryFailedJob(job: MemoryJob) {
179+
if (!memoryId.value || job.status !== 'failed' || retryingJobId.value !== null) return
183180
retryingJobId.value = job.id
184181
try {
185-
await apiFetch(`/api/memories/${encodeURIComponent(memoryId.value)}/jobs/${job.id}/retry`, 'POST', {})
186-
show(t('memory_profiles.retry_extract_done'))
182+
await apiFetch(`/api/memories/${encodeURIComponent(memoryId.value)}/jobs/${job.id}/retry?${viewQuery.value}`, 'POST', {})
183+
show(t('memory_profiles.retry_job_done'))
187184
await refresh()
188185
} catch (e: any) {
189186
show(e.message, 'error')
@@ -192,6 +189,21 @@ async function retryExtractJob(job: MemoryJob) {
192189
}
193190
}
194191
192+
async function deleteFailedJob(job: MemoryJob) {
193+
if (!memoryId.value || job.status !== 'failed' || deletingJobId.value !== null) return
194+
if (!window.confirm(t('memory_profiles.confirm_delete_job', { id: job.id }))) return
195+
deletingJobId.value = job.id
196+
try {
197+
await apiFetch(`/api/memories/${encodeURIComponent(memoryId.value)}/jobs/${job.id}?${viewQuery.value}`, 'DELETE')
198+
show(t('memory_profiles.delete_job_done'))
199+
await loadJobs()
200+
} catch (e: any) {
201+
show(e.message, 'error')
202+
} finally {
203+
deletingJobId.value = null
204+
}
205+
}
206+
195207
async function deleteMemory(memory: MemorySummary) {
196208
if (!memoryId.value || !memory.slug || deleting.value) return
197209
if (!window.confirm(t('memory_profiles.confirm_delete_memory', { slug: memory.slug }))) return
@@ -334,13 +346,22 @@ defineExpose({ openByMemoryId })
334346
<div class="memory-job-id">#{{ job.id }}</div>
335347
<div class="memory-job-actions">
336348
<SButton
337-
v-if="canRetryExtractJob(job)"
349+
v-if="job.status === 'failed'"
338350
type="outline"
339351
size="sm"
340352
:loading="retryingJobId === job.id"
341-
@click="retryExtractJob(job)"
353+
@click="retryFailedJob(job)"
354+
>
355+
{{ t('memory_profiles.retry_job') }}
356+
</SButton>
357+
<SButton
358+
v-if="job.status === 'failed'"
359+
type="danger"
360+
size="sm"
361+
:loading="deletingJobId === job.id"
362+
@click="deleteFailedJob(job)"
342363
>
343-
{{ t('memory_profiles.retry_extract') }}
364+
{{ t('memory_profiles.delete_job') }}
344365
</SButton>
345366
<SBadge :variant="jobVariant(job.status)" size="sm">{{ jobStatusLabel(job.status) }}</SBadge>
346367
</div>

packages/admin/src/i18n/en.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,8 +539,11 @@ export default {
539539
consolidate_queued: 'Consolidation queued: #{id}',
540540
run_reconcile: 'Sync File Index',
541541
reconcile_queued: 'File index sync queued: #{id}',
542-
retry_extract: 'Retry Extract',
543-
retry_extract_done: 'Extraction retry queued',
542+
retry_job: 'Retry',
543+
retry_job_done: 'Job queued for retry',
544+
delete_job: 'Delete Record',
545+
confirm_delete_job: 'Delete failed job #{id}? This only removes the job record and does not affect saved memories.',
546+
delete_job_done: 'Failed job record deleted',
544547
delete_memory: 'Delete',
545548
confirm_delete_memory: 'Delete memory "{slug}"? The file will be moved to .archive/ and can be recovered.',
546549
delete_memory_done: 'Deleted',

packages/admin/src/i18n/zh.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -539,8 +539,11 @@ export default {
539539
consolidate_queued: '已加入整理队列:#{id}',
540540
run_reconcile: '同步文件索引',
541541
reconcile_queued: '已加入索引同步队列:#{id}',
542-
retry_extract: '重新抽取',
543-
retry_extract_done: '已重新加入抽取队列',
542+
retry_job: '重试',
543+
retry_job_done: '已重新加入任务队列',
544+
delete_job: '删除记录',
545+
confirm_delete_job: '确定删除失败任务 #{id} 吗?此操作只删除任务记录,不影响已保存的记忆。',
546+
delete_job_done: '已删除失败任务记录',
544547
delete_memory: '删除此条',
545548
confirm_delete_memory: '确定删除记忆 "{slug}" 吗?文件将移到 .archive/ 目录可恢复。',
546549
delete_memory_done: '已删除',

packages/docs-site/guide/memory.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ A **Memory Profile** defines how memories are extracted and read. Sidebar → **
3838
| Writer Prompt | Controls **what** gets extracted |
3939
| Read Prompt | How retrieved memories are formatted back into the prompt |
4040

41-
Then, in an agent → **Memory** section, toggle Memory on and pick the profile. From the Memory Profiles page you can **View Memories**, inspect pending/failed background jobs, retry failed extract jobs, queue **Clean Up Memories**, and use **Sync File Index** to rescan files and indexes.
41+
Then, in an agent → **Memory** section, toggle Memory on and pick the profile. From the Memory Profiles page you can **View Memories**, inspect pending/failed background jobs, retry or delete any failed job, queue **Clean Up Memories**, and use **Sync File Index** to rescan files and indexes.
4242

4343
## Memory vs Notes vs Wiki
4444

packages/docs-site/zh/guide/memory.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ Memory 是 Agent 的自动长期记忆。后台 **MemoryLLM** 会在每次对话
3838
| Writer 提示词 | 控制 **提取什么** |
3939
| Read 提示词 | 召回的记忆如何回填进提示词 |
4040

41-
随后在 Agent → **Memory** 区块开启 Memory 并选择 Profile。在 Memory Profiles 页面可以 **查看记忆**、检查 pending / failed 后台任务、对失败的抽取任务点 **重新抽取**,也可以用 **整理记忆** 排队合并/压缩现有记忆,用 **同步文件索引** 重新扫描文件与索引。
41+
随后在 Agent → **Memory** 区块开启 Memory 并选择 Profile。在 Memory Profiles 页面可以 **查看记忆**、检查 pending / failed 后台任务、重试或删除任意失败任务,也可以用 **整理记忆** 排队合并/压缩现有记忆,用 **同步文件索引** 重新扫描文件与索引。
4242

4343
## Memory vs Notes vs Wiki
4444

packages/sbot/src/Server/routes/memories.ts

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,38 @@ export class MemoryRoutes {
106106
}
107107
}));
108108

109-
/** 重试一条失败的抽取任务:把 failed extract job 放回 pending 队列并立即唤醒消费。 */
109+
/** 重试一条失败任务:放回 pending 队列并立即唤醒消费。 */
110110
app.post('/api/memories/:id/jobs/:jobId/retry', api(async (req) => {
111111
const memoryId = requireMemoryId(req.params.id);
112112
const jobId = Number(req.params.jobId);
113113
if (!Number.isSafeInteger(jobId) || jobId <= 0) throwBad('Invalid jobId');
114-
const retried = memoryServicePool.retryExtractJob(memoryId, jobId);
115-
if (retried == null) throw new Error(`Failed to retry memory extract job #${jobId}`);
116-
if (!retried) throwBad(`Job #${jobId} is not a failed extract job`);
117-
return { memoryId, jobId };
114+
const target = requireViewTarget(req.query.viewScope, req.query.workPath);
115+
const service = acquireMemoryService(memoryId, target);
116+
try {
117+
if (!service.retryFailedJob(jobId)) {
118+
throwBad(`Job #${jobId} is not a failed job in the selected scope`);
119+
}
120+
return { memoryId, jobId };
121+
} finally {
122+
service.release();
123+
}
124+
}));
125+
126+
/** 删除一条失败任务记录;不允许删除仍在等待或处理中的任务。 */
127+
app.delete('/api/memories/:id/jobs/:jobId', api(async (req) => {
128+
const memoryId = requireMemoryId(req.params.id);
129+
const jobId = Number(req.params.jobId);
130+
if (!Number.isSafeInteger(jobId) || jobId <= 0) throwBad('Invalid jobId');
131+
const target = requireViewTarget(req.query.viewScope, req.query.workPath);
132+
const service = acquireMemoryService(memoryId, target);
133+
try {
134+
if (!service.deleteFailedJob(jobId)) {
135+
throwBad(`Job #${jobId} is not a failed job in the selected scope`);
136+
}
137+
return { memoryId, jobId };
138+
} finally {
139+
service.release();
140+
}
118141
}));
119142

120143
/** 单条 memory 全文。 */

packages/scorpio.ai/src/Memory/Service/IMemoryService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ export interface IMemoryService {
7474
/** admin 排障:列最近的 pending+failed job(按 id DESC)。 */
7575
listPending(limit?: number): PendingMemoryJobRow[];
7676

77+
/** admin 将一条 failed job 重新放回 pending 队列。 */
78+
retryFailedJob(id: number): boolean;
79+
80+
/** admin 删除一条 failed job;pending/processing 不允许删除。 */
81+
deleteFailedJob(id: number): boolean;
82+
7783
/** admin 触发:把合并/压缩现有 memory 条目的 job 入队。 */
7884
enqueueConsolidate(): number;
7985

packages/scorpio.ai/src/Memory/Service/MemoryService.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,10 @@ export class MemoryService {
449449
return this.store.listPendingJobs(limit ?? 50, target);
450450
}
451451

452+
deleteFailedJob(id: number, target: MemoryTarget): boolean {
453+
return this.store.deleteFailedJob(id, target);
454+
}
455+
452456
processPending(): void {
453457
void this.checkJobs();
454458
}
@@ -465,8 +469,8 @@ export class MemoryService {
465469
return id;
466470
}
467471

468-
retryExtractJob(id: number): boolean {
469-
const retried = this.store.retryFailedExtractJob(id, Date.now());
472+
retryFailedJob(id: number, target: MemoryTarget): boolean {
473+
const retried = this.store.retryFailedJob(id, Date.now(), target);
470474
if (retried) void this.checkJobs();
471475
return retried;
472476
}

packages/scorpio.ai/src/Memory/Service/MemoryServicePool.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -171,20 +171,6 @@ export class MemoryServicePool {
171171
}
172172
}
173173

174-
/** 返回 null 表示触发失败,错误已在 pool 中记录;false 表示该 job 不可重试。 */
175-
retryExtractJob(memoryId: string, jobId: number): boolean | null {
176-
let service: MemoryService | undefined;
177-
try {
178-
service = this.acquireOwner(memoryId);
179-
return service.retryExtractJob(jobId);
180-
} catch (e: any) {
181-
this.logActionFailed(`重试记忆抽取 #${jobId}`, memoryId, e);
182-
return null;
183-
} finally {
184-
this.releaseQuietly(service, `重试记忆抽取 #${jobId}`, memoryId);
185-
}
186-
}
187-
188174
listWorkspaceScopes(memoryId: string): MemoryWorkspaceScope[] {
189175
const service = this.acquireOwner(memoryId);
190176
try { return service.listWorkspaceScopes(); }

packages/scorpio.ai/src/Memory/Service/MemoryServiceView.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ export abstract class MemoryServiceView implements IMemoryService {
5151
return this.owner.listPending(limit, this.target);
5252
}
5353

54+
retryFailedJob(id: number): boolean {
55+
return this.owner.retryFailedJob(id, this.target);
56+
}
57+
58+
deleteFailedJob(id: number): boolean {
59+
return this.owner.deleteFailedJob(id, this.target);
60+
}
61+
5462
enqueueConsolidate(): number {
5563
return this.owner.enqueueConsolidate(this.target);
5664
}

0 commit comments

Comments
 (0)