openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
66 lines (65 loc) • 3.17 kB
JavaScript
//#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 };