fix: 修复 Queue 已读投影和取消传播

This commit is contained in:
Codex
2026-06-09 01:55:42 +08:00
parent 424058e40b
commit 88ed2c1791
7 changed files with 132 additions and 21 deletions
+26 -6
View File
@@ -1,4 +1,4 @@
import type { BackendProfile, BackendTurnResult, CommandRecord, CreateCommandInput, CreateQueueTaskInput, CreateRunInput, FailureKind, JsonRecord, ListGcExpiredSessionsInput, QueueAttemptRef, QueueCommanderSnapshot, QueueReadCursorRecord, QueueStats, QueueTaskListResult, QueueTaskRecord, QueueTaskState, RunEvent, RunnerJobRecord, RunnerRecord, RunRecord, SessionEventPage, SessionListResult, SessionListState, SessionReadCursorRecord, SessionRecord, SessionRef, SessionStoragePatch, SessionSummary, TerminalStatus, UpsertSessionInput } from "../common/types.js";
import type { BackendProfile, BackendTurnResult, CommandRecord, CreateCommandInput, CreateQueueTaskInput, CreateRunInput, FailureKind, JsonRecord, ListGcExpiredSessionsInput, QueueAttemptRef, QueueCommanderSnapshot, QueueReadCursorRecord, QueueStats, QueueTaskListResult, QueueTaskRecord, QueueTaskState, QueueTaskSummary, RunEvent, RunnerJobRecord, RunnerRecord, RunRecord, SessionEventPage, SessionListResult, SessionListState, SessionReadCursorRecord, SessionRecord, SessionRef, SessionStoragePatch, SessionSummary, TerminalStatus, UpsertSessionInput } from "../common/types.js";
import { AgentRunError } from "../common/errors.js";
import { newId, nowIso, stableHash } from "../common/validation.js";
import { redactJson } from "../common/redaction.js";
@@ -55,7 +55,7 @@ export interface AgentRunStore {
cancelQueueTask(taskId: string, reason?: string): MaybePromise<QueueTaskRecord>;
markQueueTaskRead(taskId: string, readerId: string): MaybePromise<QueueReadCursorRecord>;
queueStats(queue?: string): MaybePromise<QueueStats>;
queueCommander(queue?: string): MaybePromise<QueueCommanderSnapshot>;
queueCommander(queue?: string, readerId?: string | null): MaybePromise<QueueCommanderSnapshot>;
backends(): MaybePromise<JsonRecord[]>;
close?(): MaybePromise<void>;
}
@@ -495,8 +495,11 @@ export class MemoryAgentRunStore implements AgentRunStore {
cancelQueueTask(taskId: string, reason = "cancel requested"): QueueTaskRecord {
const task = this.getQueueTask(taskId);
if (isTerminalQueueTaskState(task.state)) return task;
if (task.latestAttempt?.commandId) this.cancelCommand(task.latestAttempt.commandId, reason);
else if (task.latestAttempt?.runId) this.cancelRun(task.latestAttempt.runId, reason);
const at = nowIso();
const next: QueueTaskRecord = { ...task, state: "cancelled", version: this.nextQueueVersion(), updatedAt: at, cancelledAt: at, cancelReason: reason };
const latestAttempt = task.latestAttempt ? { ...task.latestAttempt, state: "cancelled" as const } : null;
const next: QueueTaskRecord = { ...task, state: "cancelled", latestAttempt, version: this.nextQueueVersion(), updatedAt: at, cancelledAt: at, cancelReason: reason };
this.queueTasks.set(taskId, next);
return next;
}
@@ -512,10 +515,15 @@ export class MemoryAgentRunStore implements AgentRunStore {
return buildQueueStats(Array.from(this.queueTasks.values()).filter((task) => !queue || task.queue === queue), queue ?? null);
}
queueCommander(queue?: string): QueueCommanderSnapshot {
const items = Array.from(this.queueTasks.values()).filter((task) => !queue || task.queue === queue).sort(queueTaskSort).slice(0, 20);
queueCommander(queue?: string, readerId: string | null = null): QueueCommanderSnapshot {
const tasks = Array.from(this.queueTasks.values()).filter((task) => !queue || task.queue === queue);
const generatedAt = nowIso();
return { queue: queue ?? null, stats: buildQueueStats(Array.from(this.queueTasks.values()).filter((task) => !queue || task.queue === queue), queue ?? null, generatedAt), items, generatedAt };
const items = tasks
.map((task) => buildQueueTaskSummary(task, readerId, readerId ? this.queueReadCursors.get(queueReadKey(task.id, readerId)) ?? null : null))
.filter((task) => queueTaskMatchesCommander(task, readerId))
.sort(queueTaskSort)
.slice(0, 20);
return { queue: queue ?? null, readerId, stats: buildQueueStats(tasks, queue ?? null, generatedAt), items, generatedAt };
}
backends(): JsonRecord[] {
@@ -686,6 +694,18 @@ export function buildSessionSummary(record: SessionRecord, readerId: string | nu
return { ...record, sessionPath: `${record.tenantId}/${record.projectId}/${record.sessionId}`, readerId, readCursor, attentionState, unread, active };
}
export function buildQueueTaskSummary(record: QueueTaskRecord, readerId: string | null, readCursor: QueueReadCursorRecord | null): QueueTaskSummary {
const active = !isTerminalQueueTaskState(record.state);
const unread = !active && (!readCursor || readCursor.taskVersion < record.version);
const attentionState = active ? "active" : unread ? "unread" : "read";
return { ...record, readerId, readCursor, attentionState, unread, active };
}
export function queueTaskMatchesCommander(task: QueueTaskSummary, readerId: string | null): boolean {
if (!readerId) return true;
return task.active || task.unread;
}
export function sessionMatchesListState(session: SessionSummary, state: SessionListState): boolean {
if (state === "all") return true;
if (state === "default") return session.active || session.unread;