




























@@ -64,6 +64,7 @@ import type { TaskRecord, TaskRegistrySummary, TaskStatus } from "./task-registr
6464const log = createSubsystemLogger("tasks/task-registry-maintenance");
6565const TASK_RECONCILE_GRACE_MS = 5 * 60_000;
6666const CHILDLESS_CODEX_NATIVE_RECONCILE_GRACE_MS = 30 * 60_000;
67+const TASK_STALE_RUNNING_MS = 30 * 60_000;
6768const TASK_RETENTION_MS = 7 * 24 * 60 * 60_000;
6869const TASK_SWEEP_INTERVAL_MS = 60_000;
6970@@ -162,6 +163,28 @@ export type TaskRegistryMaintenanceSummary = {
162163pruned: number;
163164};
164165166+export type TaskRegistryMaintenanceTaskDiagnostic = {
167+taskId: string;
168+runtime: TaskRecord["runtime"];
169+status: TaskRecord["status"];
170+decision: "retained" | "would_reconcile";
171+reason:
172+| "active_cli_run"
173+| "backing_session_missing"
174+| "backing_session_present"
175+| "cron_runtime_not_authoritative"
176+| "lost_grace_pending"
177+| "subagent_recovery_wedged";
178+detail?: string;
179+ageMs: number;
180+childSessionKey?: string;
181+runId?: string;
182+};
183+184+export type TaskRegistryMaintenanceDiagnostics = {
185+staleRunningTasks: TaskRegistryMaintenanceTaskDiagnostic[];
186+};
187+165188type CronExecutionId = {
166189jobId: string;
167190startedAt: number;
@@ -562,6 +585,10 @@ function resolveCleanupAfter(task: TaskRecord): number {
562585return terminalAt + TASK_RETENTION_MS;
563586}
564587588+function taskReferenceAt(task: TaskRecord): number {
589+return task.lastEventAt ?? task.startedAt ?? task.createdAt;
590+}
591+565592function getNormalizedTaskChildSessionKey(task: TaskRecord): string | undefined {
566593return normalizeOptionalString(task.childSessionKey);
567594}
@@ -952,6 +979,72 @@ export function previewTaskRegistryMaintenance(): TaskRegistryMaintenanceSummary
952979return { reconciled, recovered, cleanupStamped, pruned };
953980}
954981982+function explainActiveTaskRetention(params: {
983+task: TaskRecord;
984+now: number;
985+context: BackingSessionLookupContext;
986+}): Pick<TaskRegistryMaintenanceTaskDiagnostic, "decision" | "reason" | "detail"> {
987+if (!hasLostGraceExpired(params.task, params.now)) {
988+return { decision: "retained", reason: "lost_grace_pending" };
989+}
990+if (params.task.runtime === "subagent") {
991+const entry = findTaskSessionEntry(params.task, params.context);
992+if (entry && isSubagentRecoveryWedgedEntry(entry)) {
993+return {
994+decision: "would_reconcile",
995+reason: "subagent_recovery_wedged",
996+detail: formatSubagentRecoveryWedgedReason(entry),
997+};
998+}
999+}
1000+if (!hasBackingSession(params.task, params.context)) {
1001+return { decision: "would_reconcile", reason: "backing_session_missing" };
1002+}
1003+if (
1004+params.task.runtime === "cron" &&
1005+!taskRegistryMaintenanceRuntime.isCronRuntimeAuthoritative()
1006+) {
1007+return { decision: "retained", reason: "cron_runtime_not_authoritative" };
1008+}
1009+if (params.task.runtime === "cli" && hasActiveCliRun(params.task)) {
1010+return { decision: "retained", reason: "active_cli_run" };
1011+}
1012+return { decision: "retained", reason: "backing_session_present" };
1013+}
1014+1015+export function getTaskRegistryMaintenanceDiagnostics(): TaskRegistryMaintenanceDiagnostics {
1016+taskRegistryMaintenanceRuntime.ensureTaskRegistryReady();
1017+const now = Date.now();
1018+const cronRecoveryContext = createCronRecoveryContext();
1019+const backingSessionContext = createBackingSessionLookupContext();
1020+const staleRunningTasks: TaskRegistryMaintenanceTaskDiagnostic[] = [];
1021+for (const task of taskRegistryMaintenanceRuntime.listTaskRecords()) {
1022+if (task.status !== "running") {
1023+continue;
1024+}
1025+const ageMs = Math.max(0, now - taskReferenceAt(task));
1026+if (ageMs < TASK_STALE_RUNNING_MS) {
1027+continue;
1028+}
1029+if (resolveDurableCronTaskRecovery(task, cronRecoveryContext)) {
1030+continue;
1031+}
1032+const decision = explainActiveTaskRetention({ task, now, context: backingSessionContext });
1033+staleRunningTasks.push({
1034+taskId: task.taskId,
1035+runtime: task.runtime,
1036+status: task.status,
1037+decision: decision.decision,
1038+reason: decision.reason,
1039+ ageMs,
1040+ ...(decision.detail ? { detail: decision.detail } : {}),
1041+ ...(task.childSessionKey ? { childSessionKey: task.childSessionKey } : {}),
1042+ ...(task.runId ? { runId: task.runId } : {}),
1043+});
1044+}
1045+return { staleRunningTasks };
1046+}
1047+9551048/**
9561049 * Yield control back to the event loop so that pending I/O callbacks,
9571050 * timers, and incoming requests can be processed between batches of
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。