






















@@ -54,6 +54,8 @@ const webhookStats = {
5454const DEFAULT_STUCK_SESSION_WARN_MS = 120_000;
5555const MIN_STUCK_SESSION_WARN_MS = 1_000;
5656const MAX_STUCK_SESSION_WARN_MS = 24 * 60 * 60 * 1000;
57+const MIN_STALLED_EMBEDDED_RUN_ABORT_MS = 10 * 60_000;
58+const STALLED_EMBEDDED_RUN_ABORT_WARN_MULTIPLIER = 5;
5759const RECENT_DIAGNOSTIC_ACTIVITY_MS = 120_000;
5860const DEFAULT_LIVENESS_EVENT_LOOP_DELAY_WARN_MS = 1_000;
5961const DEFAULT_LIVENESS_EVENT_LOOP_UTILIZATION_WARN = 0.95;
@@ -82,6 +84,7 @@ type RecoverStuckSession = (params: {
8284sessionKey?: string;
8385ageMs: number;
8486queueDepth?: number;
87+allowActiveAbort?: boolean;
8588}) => void | Promise<void>;
86898790type DiagnosticLivenessSample = {
@@ -125,6 +128,7 @@ function recoverStuckSession(params: {
125128sessionKey?: string;
126129ageMs: number;
127130queueDepth?: number;
131+allowActiveAbort?: boolean;
128132}) {
129133stuckSessionRecoveryRuntimePromise ??= import("./diagnostic-stuck-session-recovery.runtime.js");
130134void stuckSessionRecoveryRuntimePromise
@@ -344,6 +348,26 @@ export function resolveStuckSessionWarnMs(config?: OpenClawConfig): number {
344348return rounded;
345349}
346350351+function resolveStalledEmbeddedRunAbortMs(stuckSessionWarnMs: number): number {
352+return Math.max(
353+MIN_STALLED_EMBEDDED_RUN_ABORT_MS,
354+stuckSessionWarnMs * STALLED_EMBEDDED_RUN_ABORT_WARN_MULTIPLIER,
355+);
356+}
357+358+function isStalledEmbeddedRunRecoveryEligible(params: {
359+classification: SessionAttentionClassification | undefined;
360+ageMs: number;
361+stuckSessionWarnMs: number;
362+}): boolean {
363+return (
364+params.classification?.eventType === "session.stalled" &&
365+params.classification.classification === "stalled_agent_run" &&
366+params.classification.activeWorkKind === "embedded_run" &&
367+params.ageMs >= resolveStalledEmbeddedRunAbortMs(params.stuckSessionWarnMs)
368+);
369+}
370+347371export function logWebhookReceived(params: {
348372channel: string;
349373updateType?: string;
@@ -594,6 +618,13 @@ export function logSessionAttention(
594618 activity,
595619staleMs: params.thresholdMs,
596620});
621+const recoveryEligible =
622+classification.recoveryEligible ||
623+isStalledEmbeddedRunRecoveryEligible({
624+ classification,
625+ageMs: params.ageMs,
626+stuckSessionWarnMs: params.thresholdMs,
627+});
597628if (classification.eventType === "session.stuck") {
598629const nextWarnAgeMs =
599630state.lastStuckWarnAgeMs === undefined
@@ -617,7 +648,7 @@ export function logSessionAttention(
617648 state.queueDepth
618649 } reason=${classification.reason} classification=${classification.classification}${
619650 classification.activeWorkKind ? ` activeWorkKind=${classification.activeWorkKind}` : ""
620- } recovery=${classification.recoveryEligible ? "checking" : "none"}`,
651+ } recovery=${recoveryEligible ? "checking" : "none"}`,
621652);
622653const baseEvent = {
623654sessionId: state.sessionId,
@@ -816,6 +847,20 @@ export function startDiagnosticHeartbeat(
816847 ageMs,
817848queueDepth: state.queueDepth,
818849});
850+} else if (
851+isStalledEmbeddedRunRecoveryEligible({
852+ classification,
853+ ageMs,
854+ stuckSessionWarnMs,
855+})
856+) {
857+void (opts?.recoverStuckSession ?? recoverStuckSession)({
858+sessionId: state.sessionId,
859+sessionKey: state.sessionKey,
860+ ageMs,
861+queueDepth: state.queueDepth,
862+allowActiveAbort: true,
863+});
819864}
820865}
821866}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。