let message =
`[System] Your previous turn was interrupted by a gateway reload. ` +
`Your original task was:\n\n${truncatedTask}\n\n`;
if (lastHumanMessage) {
message += `The last message from the user before the interruption was:\n\n${lastHumanMessage}\n\n`;
}
message += `Please continue where you left off.`; return message;
}
function buildRecoveryProgressPrompt(params: {
task: string;
attemptNumber: number;
maxAttempts: number;
}): string { const maxTaskLen = 160; const taskLabel =
params.task.length > maxTaskLen ? `${params.task.slice(0, maxTaskLen)}...` : params.task; return (
`A spawned subagent task was interrupted by a gateway restart or connection loss. ` +
`Automatic recovery is already in progress for"${taskLabel}" ` +
`(retry ${params.attemptNumber}/${params.maxAttempts}). ` +
`Send one brief update now in your normal voice: say the task was interrupted, ` +
`you are automatically resuming/retrying it, and you will report back when it either continues or truly fails. ` +
`Do not say the task has failed.`
);
}
let store = storeCache.get(storePath); if (!store) {
store = loadSessionStore(storePath);
storeCache.set(storePath, store);
}
const entry = store[childSessionKey]; if (!entry) {
result.skipped++; continue;
}
// Restart-aborted subagents can be marked ended with a timeout outcome // before the gateway comes back up to resume them. if ( typeof runRecord.endedAt === "number" &&
runRecord.endedAt > 0 &&
!isRestartAbortedTimeoutRun(runRecord, entry)
) {
result.skipped++; continue;
}
// Check if this session was aborted by the restart if (!entry.abortedLastRun) {
result.skipped++; continue;
}
// Resume the session with the original task context. // We intentionally do NOT clear abortedLastRun before attempting // the resume — if callGateway fails (e.g. gateway still booting), // the flag stays true so the next restart can retry. const resumeResult = await resumeOrphanedSession({
sessionKey: childSessionKey,
task: runRecord.task,
lastHumanMessage: extractMessageText(lastHumanMessage),
configChangeHint: configChangeDetected
? "\n\n[config changes from your previous run were already applied — do not re-modify openclaw.json or restart the gateway]"
: undefined,
originalRunId: runId,
originalRun: runRecord,
});
if (resumeResult.resumed) {
resumedSessionKeys.add(childSessionKey); // Only clear the aborted flag after confirmed successful resume. try {
await updateSessionStore(storePath, (currentStore) => { const current = currentStore[childSessionKey]; if (current) {
current.abortedLastRun = false;
current.updatedAt = Date.now();
currentStore[childSessionKey] = current;
}
});
} catch (err) {
log.warn(
`resume succeeded but failed to update session store for ${childSessionKey}: ${String(err)}`,
);
}
result.recovered++;
} else { // Flag stays as abortedLastRun=true so next restart can retry
log.warn(
`resume failed for ${childSessionKey}; abortedLastRun flag preserved for retry on next restart`,
);
result.failed++;
result.failedRuns.push({
runId,
childSessionKey,
error: resumeResult.error,
});
}
} catch (err) { const error = formatErrorMessage(err);
log.warn(`error processing orphaned session ${childSessionKey}: ${error}`);
result.failed++;
result.failedRuns.push({
runId,
childSessionKey,
error,
});
}
}
} catch (err) {
log.warn(`orphan recovery scan failed: ${String(err)}`); // Ensure retry logic fires for scan-level exceptions. if (result.failed === 0) {
result.failed = 1;
}
}
/** Maximum number of retry attempts for orphan recovery. */ const MAX_RECOVERY_RETRIES = 3; /** Backoff multiplier between retries (exponential). */ const RETRY_BACKOFF_MULTIPLIER = 2;
function buildRecoveryFailureMessage(params: { attempts: number; error?: string }): string { const base =
`Subagent run was interrupted by a gateway restart or connection loss. ` +
`Automatic recovery failed after ${params.attempts} attempt${params.attempts === 1 ? "" : "s"}. ` +
`Please retry.`; const detail = params.error?.trim(); if (!detail) { return base;
} return `${base} (${detail})`;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.