UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

66 lines (65 loc) 3.17 kB
//#region src/agents/subagent-recovery-state.ts const SUBAGENT_RECOVERY_MAX_AUTOMATIC_ATTEMPTS = 2; const SUBAGENT_RECOVERY_REWEDGE_WINDOW_MS = 2 * 6e4; function isRecentRecoveryAttempt(entry, now) { const lastAttemptAt = entry.subagentRecovery?.lastAttemptAt; return typeof lastAttemptAt === "number" && Number.isFinite(lastAttemptAt) && now - lastAttemptAt <= SUBAGENT_RECOVERY_REWEDGE_WINDOW_MS; } /** Returns true when recovery has been tombstoned for a session entry. */ function isSubagentRecoveryWedgedEntry(entry) { if (!entry || typeof entry !== "object") return false; const recovery = entry.subagentRecovery; return typeof recovery?.wedgedAt === "number" && Number.isFinite(recovery.wedgedAt) && recovery.wedgedAt > 0; } /** Formats the operator-facing reason for a wedged recovery entry. */ function formatSubagentRecoveryWedgedReason(entry) { return entry.subagentRecovery?.wedgedReason?.trim() || "subagent orphan recovery is tombstoned for this session"; } /** Checks whether automatic orphan recovery may run for this session entry. */ function evaluateSubagentRecoveryGate(entry, now) { if (isSubagentRecoveryWedgedEntry(entry)) return { allowed: false, reason: formatSubagentRecoveryWedgedReason(entry), shouldMarkWedged: false }; const previousAttempts = isRecentRecoveryAttempt(entry, now) ? Math.max(0, entry.subagentRecovery?.automaticAttempts ?? 0) : 0; if (previousAttempts >= SUBAGENT_RECOVERY_MAX_AUTOMATIC_ATTEMPTS) return { allowed: false, reason: `subagent orphan recovery blocked after ${previousAttempts} rapid accepted resume attempts; run "openclaw tasks maintenance --apply" or "openclaw doctor --fix" to reconcile it`, shouldMarkWedged: true }; return { allowed: true, nextAttempt: previousAttempts + 1 }; } /** Records one accepted automatic orphan-recovery attempt. */ function markSubagentRecoveryAttempt(params) { params.entry.subagentRecovery = { automaticAttempts: Math.max(1, params.attempt), lastAttemptAt: params.now, lastRunId: params.runId }; } /** Tombstones automatic recovery until maintenance or doctor clears the state. */ function markSubagentRecoveryWedged(params) { params.entry.abortedLastRun = false; params.entry.subagentRecovery = { ...params.entry.subagentRecovery, automaticAttempts: Math.max(params.entry.subagentRecovery?.automaticAttempts ?? 0, SUBAGENT_RECOVERY_MAX_AUTOMATIC_ATTEMPTS), lastAttemptAt: params.entry.subagentRecovery?.lastAttemptAt ?? params.now, ...params.runId ? { lastRunId: params.runId } : {}, wedgedAt: params.now, wedgedReason: params.reason }; params.entry.updatedAt = params.now; } /** Clears stale abort state when a wedged entry should no longer look runnable. */ function clearWedgedSubagentRecoveryAbort(entry, now) { if (!isSubagentRecoveryWedgedEntry(entry) || entry.abortedLastRun !== true) return false; entry.abortedLastRun = false; entry.updatedAt = now; return true; } //#endregion export { markSubagentRecoveryAttempt as a, isSubagentRecoveryWedgedEntry as i, evaluateSubagentRecoveryGate as n, markSubagentRecoveryWedged as o, formatSubagentRecoveryWedgedReason as r, clearWedgedSubagentRecoveryAbort as t };