feat: switch auto run to per-round retry with resilient resume summary

- 合并 PR #47 的核心改动:自动运行按设定总轮数逐轮执行,单轮失败时按轮重试并输出汇总日志
- 本地补充修复:恢复轮次摘要持久化、修正重试等待期间 stop 收尾、fresh attempt 清理过期 tab/url 运行态
- 影响范围:background auto orchestration、sidepanel auto-run settings and status
This commit is contained in:
QLHazyCoder
2026-04-14 01:37:24 +08:00
3 changed files with 450 additions and 54 deletions
+438 -10
View File
@@ -39,6 +39,8 @@ const DEFAULT_SUB2API_REDIRECT_URI = 'http://localhost:1455/auth/callback';
const AUTO_RUN_ALARM_NAME = 'scheduled-auto-run';
const AUTO_RUN_DELAY_MIN_MINUTES = 1;
const AUTO_RUN_DELAY_MAX_MINUTES = 1440;
const AUTO_RUN_RETRY_DELAY_MS = 3000;
const AUTO_RUN_MAX_RETRIES_PER_ROUND = 3;
const AUTO_STEP_DELAY_MIN_ALLOWED_SECONDS = 0;
const AUTO_STEP_DELAY_MAX_ALLOWED_SECONDS = 600;
const LEGACY_AUTO_STEP_DELAY_KEYS = ['autoStepRandomDelayMinSeconds', 'autoStepRandomDelayMaxSeconds'];
@@ -60,7 +62,7 @@ const PERSISTED_SETTING_DEFAULTS = {
sub2apiPassword: '',
sub2apiGroupName: DEFAULT_SUB2API_GROUP_NAME,
customPassword: '',
autoRunSkipFailures: false,
autoRunSkipFailures: true,
autoRunFallbackThreadIntervalMinutes: 0,
autoRunDelayEnabled: false,
autoRunDelayMinutes: 30,
@@ -107,6 +109,7 @@ const DEFAULT_STATE = {
autoRunCurrentRun: 0, // 自动运行当前执行到第几轮。
autoRunTotalRuns: 1, // 自动运行计划总轮数。
autoRunAttemptRun: 0, // 当前轮次的重试序号。
autoRunRoundSummaries: [], // 自动运行轮次摘要。
scheduledAutoRunAt: null, // 自动运行计划启动时间戳。
scheduledAutoRunPlan: null, // 自动运行计划参数快照。
signupVerificationRequestedAt: null,
@@ -195,7 +198,7 @@ function normalizeScheduledAutoRunPlan(plan) {
return {
totalRuns: normalizeRunCount(plan.totalRuns),
autoRunSkipFailures: Boolean(plan.autoRunSkipFailures),
autoRunSkipFailures: true,
mode: plan.mode === 'continue' ? 'continue' : 'restart',
};
}
@@ -2388,8 +2391,8 @@ async function launchScheduledAutoRun(trigger = 'alarm') {
: '倒计时结束,自动运行开始执行。',
'info'
);
autoRunLoop(plan.totalRuns, {
autoRunSkipFailures: plan.autoRunSkipFailures,
startAutoRunLoop(plan.totalRuns, {
autoRunSkipFailures: true,
mode: plan.mode,
});
return true;
@@ -2701,10 +2704,10 @@ async function handleMessage(message, sender) {
throw new Error('已有自动运行倒计时计划,请先取消或立即开始。');
}
const totalRuns = normalizeRunCount(message.payload?.totalRuns || 1);
const autoRunSkipFailures = Boolean(message.payload?.autoRunSkipFailures);
const autoRunSkipFailures = true;
const mode = message.payload?.mode === 'continue' ? 'continue' : 'restart';
await setState({ autoRunSkipFailures });
autoRunLoop(totalRuns, { autoRunSkipFailures, mode }); // fire-and-forget
startAutoRunLoop(totalRuns, { autoRunSkipFailures, mode });
return { ok: true };
}
@@ -2713,7 +2716,7 @@ async function handleMessage(message, sender) {
const totalRuns = normalizeRunCount(message.payload?.totalRuns || 1);
return await scheduleAutoRun(totalRuns, {
delayMinutes: message.payload?.delayMinutes,
autoRunSkipFailures: Boolean(message.payload?.autoRunSkipFailures),
autoRunSkipFailures: true,
mode: message.payload?.mode,
});
}
@@ -2740,7 +2743,11 @@ async function handleMessage(message, sender) {
if (message.payload.email) {
await setEmailState(message.payload.email);
}
resumeAutoRun(); // fire-and-forget
resumeAutoRun().catch((error) => {
handleAutoRunLoopUnhandledError(error).catch((handlerError) => {
console.error(LOG_PREFIX, 'Failed to finalize resume error:', handlerError);
});
});
return { ok: true };
}
@@ -3429,7 +3436,7 @@ async function runAutoSequenceFromStep(startStep, context = {}) {
}
// Outer loop: keep retrying until the target number of successful runs is reached.
async function autoRunLoop(totalRuns, options = {}) {
async function legacyAutoRunLoop(totalRuns, options = {}) {
if (autoRunActive) {
await addLog('自动运行已在进行中', 'warn');
return;
@@ -3661,7 +3668,7 @@ async function waitForResume() {
});
}
async function resumeAutoRun() {
async function legacyResumeAutoRun() {
throwIfStopped();
const state = await getState();
if (!state.email) {
@@ -3698,6 +3705,427 @@ async function resumeAutoRun() {
return true;
}
function createAutoRunRoundSummary(round) {
return {
round,
status: 'pending',
attempts: 0,
failureReasons: [],
finalFailureReason: '',
};
}
function normalizeAutoRunRoundSummary(summary, round) {
const base = createAutoRunRoundSummary(round);
if (!summary || typeof summary !== 'object') {
return base;
}
const status = String(summary.status || '').trim().toLowerCase();
return {
round,
status: ['pending', 'success', 'failed'].includes(status) ? status : base.status,
attempts: Math.max(0, Math.floor(Number(summary.attempts) || 0)),
failureReasons: Array.isArray(summary.failureReasons)
? summary.failureReasons.map((item) => String(item || '').trim()).filter(Boolean)
: [],
finalFailureReason: String(summary.finalFailureReason || '').trim(),
};
}
function buildAutoRunRoundSummaries(totalRuns, rawSummaries = []) {
return Array.from({ length: totalRuns }, (_, index) => {
return normalizeAutoRunRoundSummary(rawSummaries[index], index + 1);
});
}
function serializeAutoRunRoundSummaries(totalRuns, roundSummaries = []) {
return buildAutoRunRoundSummaries(totalRuns, roundSummaries).map((summary) => ({
...summary,
failureReasons: [...summary.failureReasons],
}));
}
function getAutoRunRoundRetryCount(summary) {
return Math.max(0, Number(summary?.attempts || 0) - 1);
}
function formatAutoRunFailureReasons(reasons = []) {
if (!Array.isArray(reasons) || !reasons.length) {
return '未知错误';
}
const counts = new Map();
for (const reason of reasons) {
const normalized = String(reason || '').trim() || '未知错误';
counts.set(normalized, (counts.get(normalized) || 0) + 1);
}
return Array.from(counts.entries())
.map(([reason, count]) => (count > 1 ? `${reason}${count}次)` : reason))
.join('');
}
async function logAutoRunFinalSummary(totalRuns, roundSummaries = []) {
const summaries = buildAutoRunRoundSummaries(totalRuns, roundSummaries);
const successRounds = summaries.filter((item) => item.status === 'success');
const failedRounds = summaries.filter((item) => item.status === 'failed');
const pendingRounds = summaries.filter((item) => item.status === 'pending');
await addLog('=== 自动运行汇总 ===', failedRounds.length ? 'warn' : 'ok');
await addLog(
`总轮数:${totalRuns};成功:${successRounds.length};失败:${failedRounds.length};未完成:${pendingRounds.length}`,
failedRounds.length ? 'warn' : 'ok'
);
if (successRounds.length) {
await addLog(
`成功轮次:${successRounds
.map((item) => `${item.round} 轮(重试 ${getAutoRunRoundRetryCount(item)} 次)`)
.join('')}`,
'ok'
);
}
if (failedRounds.length) {
await addLog(
`失败轮次:${failedRounds
.map((item) => {
const retryCount = getAutoRunRoundRetryCount(item);
const finalReason = item.finalFailureReason || item.failureReasons[item.failureReasons.length - 1] || '未知错误';
const reasonSummary = formatAutoRunFailureReasons(item.failureReasons);
return `${item.round} 轮(重试 ${retryCount} 次,最终原因:${finalReason};失败记录:${reasonSummary}`;
})
.join('')}`,
'error'
);
}
if (pendingRounds.length) {
await addLog(
`未完成轮次:${pendingRounds.map((item) => `${item.round}`).join('')}`,
'warn'
);
}
}
async function handleAutoRunLoopUnhandledError(error) {
console.error(LOG_PREFIX, 'Auto run loop crashed:', error);
if (!isStopError(error)) {
await addLog(`自动运行异常终止:${getErrorMessage(error) || '未知错误'}`, 'error');
}
autoRunActive = false;
await broadcastAutoRunStatus('stopped', {
currentRun: autoRunCurrentRun,
totalRuns: autoRunTotalRuns,
attemptRun: autoRunAttemptRun,
});
clearStopRequest();
}
function startAutoRunLoop(totalRuns, options = {}) {
autoRunLoop(totalRuns, options).catch((error) => {
handleAutoRunLoopUnhandledError(error).catch((handlerError) => {
console.error(LOG_PREFIX, 'Failed to finalize auto run error:', handlerError);
});
});
}
async function autoRunLoop(totalRuns, options = {}) {
if (autoRunActive) {
await addLog('自动运行已在进行中', 'warn');
return;
}
clearStopRequest();
autoRunActive = true;
autoRunTotalRuns = totalRuns;
autoRunCurrentRun = 0;
autoRunAttemptRun = 0;
const autoRunSkipFailures = true;
const initialMode = options.mode === 'continue' ? 'continue' : 'restart';
const resumeCurrentRun = Number.isInteger(options.resumeCurrentRun) && options.resumeCurrentRun > 0
? Math.min(totalRuns, options.resumeCurrentRun)
: 1;
const resumeAttemptRun = Number.isInteger(options.resumeAttemptRun) && options.resumeAttemptRun > 0
? Math.min(AUTO_RUN_MAX_RETRIES_PER_ROUND + 1, options.resumeAttemptRun)
: 1;
let continueCurrentOnFirstAttempt = initialMode === 'continue';
let forceFreshTabsNextRun = false;
let stoppedEarly = false;
const roundSummaries = buildAutoRunRoundSummaries(totalRuns, options.resumeRoundSummaries);
if (continueCurrentOnFirstAttempt && resumeCurrentRun > 1) {
for (let round = 1; round < resumeCurrentRun; round += 1) {
const summary = roundSummaries[round - 1];
if (summary.status === 'pending') {
summary.status = 'success';
if (!summary.attempts) {
summary.attempts = 1;
}
}
}
}
let successfulRuns = roundSummaries.filter((item) => item.status === 'success').length;
const initialState = await getState();
const initialPhase = continueCurrentOnFirstAttempt && getRunningSteps(initialState.stepStatuses).length
? 'waiting_step'
: 'running';
await setState({
autoRunSkipFailures,
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
...getAutoRunStatusPayload(initialPhase, {
currentRun: continueCurrentOnFirstAttempt ? resumeCurrentRun : 0,
totalRuns,
attemptRun: continueCurrentOnFirstAttempt ? resumeAttemptRun : 0,
}),
});
for (let targetRun = resumeCurrentRun; targetRun <= totalRuns; targetRun += 1) {
const roundSummary = roundSummaries[targetRun - 1];
let attemptRun = continueCurrentOnFirstAttempt && targetRun === resumeCurrentRun ? resumeAttemptRun : 1;
let reuseExistingProgress = continueCurrentOnFirstAttempt && targetRun === resumeCurrentRun;
while (attemptRun <= AUTO_RUN_MAX_RETRIES_PER_ROUND + 1) {
autoRunCurrentRun = targetRun;
autoRunAttemptRun = attemptRun;
roundSummary.attempts = attemptRun;
let startStep = 1;
let useExistingProgress = false;
if (reuseExistingProgress) {
let currentState = await getState();
if (getRunningSteps(currentState.stepStatuses).length) {
currentState = await waitForRunningStepsToFinish({
currentRun: targetRun,
totalRuns,
attemptRun,
});
}
const resumeStep = getFirstUnfinishedStep(currentState.stepStatuses);
if (resumeStep && hasSavedProgress(currentState.stepStatuses)) {
startStep = resumeStep;
useExistingProgress = true;
} else if (hasSavedProgress(currentState.stepStatuses)) {
await addLog('检测到当前流程已处理完成,本轮将改为从步骤 1 重新开始。', 'info');
}
}
if (!useExistingProgress) {
const prevState = await getState();
const keepSettings = {
vpsUrl: prevState.vpsUrl,
vpsPassword: prevState.vpsPassword,
customPassword: prevState.customPassword,
autoRunSkipFailures: prevState.autoRunSkipFailures,
autoRunFallbackThreadIntervalMinutes: prevState.autoRunFallbackThreadIntervalMinutes,
autoRunDelayEnabled: prevState.autoRunDelayEnabled,
autoRunDelayMinutes: prevState.autoRunDelayMinutes,
autoStepDelaySeconds: prevState.autoStepDelaySeconds,
mailProvider: prevState.mailProvider,
emailGenerator: prevState.emailGenerator,
emailPrefix: prevState.emailPrefix,
inbucketHost: prevState.inbucketHost,
inbucketMailbox: prevState.inbucketMailbox,
cloudflareDomain: prevState.cloudflareDomain,
cloudflareDomains: prevState.cloudflareDomains,
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
tabRegistry: {},
sourceLastUrls: {},
...getAutoRunStatusPayload('running', { currentRun: targetRun, totalRuns, attemptRun }),
};
await resetState();
await setState(keepSettings);
chrome.runtime.sendMessage({ type: 'AUTO_RUN_RESET' }).catch(() => { });
await sleepWithStop(500);
} else {
await setState({
autoRunSkipFailures,
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
...getAutoRunStatusPayload('running', { currentRun: targetRun, totalRuns, attemptRun }),
});
}
if (forceFreshTabsNextRun) {
await addLog(`上一轮尝试已放弃,当前开始第 ${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试。`, 'warn');
forceFreshTabsNextRun = false;
}
try {
throwIfStopped();
await broadcastAutoRunStatus('running', {
currentRun: targetRun,
totalRuns,
attemptRun,
});
await runAutoSequenceFromStep(startStep, {
targetRun,
totalRuns,
attemptRuns: attemptRun,
continued: useExistingProgress,
});
roundSummary.status = 'success';
roundSummary.finalFailureReason = '';
successfulRuns += 1;
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await addLog(`=== 第 ${targetRun}/${totalRuns} 轮完成(第 ${attemptRun} 次尝试成功)===`, 'ok');
break;
} catch (err) {
if (isStopError(err)) {
stoppedEarly = true;
await addLog(`${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
await broadcastAutoRunStatus('stopped', {
currentRun: targetRun,
totalRuns,
attemptRun,
});
break;
}
const reason = getErrorMessage(err);
roundSummary.failureReasons.push(reason);
const canRetry = attemptRun <= AUTO_RUN_MAX_RETRIES_PER_ROUND;
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
if (canRetry) {
const retryIndex = attemptRun;
if (isRestartCurrentAttemptError(err)) {
await addLog(`${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试需要整轮重开:${reason}`, 'warn');
} else {
await addLog(`${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试失败:${reason}`, 'error');
}
cancelPendingCommands('当前尝试已放弃。');
await broadcastStopToContentScripts();
await broadcastAutoRunStatus('retrying', {
currentRun: targetRun,
totalRuns,
attemptRun,
});
forceFreshTabsNextRun = true;
await addLog(
`自动重试:${Math.round(AUTO_RUN_RETRY_DELAY_MS / 1000)} 秒后开始第 ${targetRun}/${totalRuns} 轮第 ${attemptRun + 1} 次尝试(第 ${retryIndex}/${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试)。`,
'warn'
);
try {
await sleepWithStop(AUTO_RUN_RETRY_DELAY_MS);
} catch (sleepError) {
if (isStopError(sleepError)) {
stoppedEarly = true;
await addLog(`${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
await broadcastAutoRunStatus('stopped', {
currentRun: targetRun,
totalRuns,
attemptRun,
});
break;
}
throw sleepError;
}
attemptRun += 1;
reuseExistingProgress = false;
continue;
}
roundSummary.status = 'failed';
roundSummary.finalFailureReason = reason;
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await addLog(`${targetRun}/${totalRuns} 轮最终失败:${reason}`, 'error');
await addLog(`${targetRun}/${totalRuns} 轮已达到 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试上限,继续下一轮。`, 'warn');
cancelPendingCommands('当前轮已达到重试上限。');
await broadcastStopToContentScripts();
forceFreshTabsNextRun = true;
break;
} finally {
reuseExistingProgress = false;
continueCurrentOnFirstAttempt = false;
}
}
if (stoppedEarly) {
break;
}
}
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
});
await logAutoRunFinalSummary(totalRuns, roundSummaries);
if (stopRequested || stoppedEarly) {
await addLog(`=== 已停止,完成 ${successfulRuns}/${autoRunTotalRuns} 轮 ===`, 'warn');
await broadcastAutoRunStatus('stopped', {
currentRun: autoRunCurrentRun,
totalRuns: autoRunTotalRuns,
attemptRun: autoRunAttemptRun,
});
} else {
await addLog(`=== 全部 ${autoRunTotalRuns} 轮已执行完成,成功 ${successfulRuns} 轮 ===`, 'ok');
await broadcastAutoRunStatus('complete', {
currentRun: autoRunTotalRuns,
totalRuns: autoRunTotalRuns,
attemptRun: autoRunAttemptRun,
});
}
autoRunActive = false;
await setState({
autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
...getAutoRunStatusPayload(stopRequested || stoppedEarly ? 'stopped' : 'complete', {
currentRun: stopRequested || stoppedEarly ? autoRunCurrentRun : autoRunTotalRuns,
totalRuns: autoRunTotalRuns,
attemptRun: autoRunAttemptRun,
}),
});
clearStopRequest();
}
async function resumeAutoRun() {
throwIfStopped();
const state = await getState();
if (!state.email) {
await addLog('无法继续:当前没有邮箱地址,请先在侧边栏填写邮箱。', 'error');
return false;
}
const resumedInMemory = await resumeAutoRunIfWaitingForEmail({ silent: true });
if (resumedInMemory) {
return true;
}
if (!isAutoRunPausedState(state)) {
return false;
}
if (autoRunActive) {
return false;
}
const totalRuns = state.autoRunTotalRuns || 1;
const currentRun = state.autoRunCurrentRun || 1;
const attemptRun = state.autoRunAttemptRun || 1;
await addLog('检测到自动流程暂停上下文已丢失,正在从当前进度恢复自动运行...', 'warn');
startAutoRunLoop(totalRuns, {
autoRunSkipFailures: true,
mode: 'continue',
resumeCurrentRun: currentRun,
resumeAttemptRun: attemptRun,
resumeRoundSummaries: state.autoRunRoundSummaries,
});
return true;
}
// ============================================================
// Step 1: Get OAuth Link
// ============================================================
+5 -4
View File
@@ -198,15 +198,16 @@
</div>
</div>
<div class="data-row">
<span class="data-label">兜底</span>
<span class="data-label">自动重试</span>
<div class="data-inline fallback-inline">
<label class="toggle-switch" for="input-auto-skip-failures">
<input type="checkbox" id="input-auto-skip-failures" />
<label class="toggle-switch" for="input-auto-skip-failures" title="Auto retry restarts the current round from step 1 after a step failure.">
<input type="checkbox" id="input-auto-skip-failures" checked />
<span class="toggle-switch-track" aria-hidden="true">
<span class="toggle-switch-thumb"></span>
</span>
</label>
<div class="fallback-thread-interval">
<span class="auto-delay-caption">Retry the current round from step 1 after a short delay.</span>
<div class="fallback-thread-interval" style="display:none;">
<span class="auto-delay-caption">线程间隔</span>
<div class="auto-delay-controls">
<input type="number" id="input-auto-skip-failures-thread-interval-minutes" class="data-input auto-delay-input" value="0"
+7 -40
View File
@@ -662,8 +662,7 @@ function updateFallbackThreadIntervalInputState() {
return;
}
const enabled = inputAutoSkipFailures.checked && getRunCountValue() > 1;
inputAutoSkipFailuresThreadIntervalMinutes.disabled = !enabled;
inputAutoSkipFailuresThreadIntervalMinutes.disabled = true;
}
function updateAutoDelayInputState() {
@@ -834,7 +833,7 @@ function collectSettingsPayload() {
inbucketMailbox: inputInbucketMailbox.value.trim(),
cloudflareDomain: selectedCloudflareDomain,
cloudflareDomains: domains,
autoRunSkipFailures: inputAutoSkipFailures.checked,
autoRunSkipFailures: true,
autoRunFallbackThreadIntervalMinutes: normalizeAutoRunThreadIntervalMinutes(inputAutoSkipFailuresThreadIntervalMinutes.value),
autoRunDelayEnabled: inputAutoDelayEnabled.checked,
autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
@@ -979,7 +978,7 @@ function applyAutoRunStatus(payload = currentAutoRun) {
btnAutoRun.disabled = currentAutoRun.autoRunning;
btnFetchEmail.disabled = locked || usesGeneratedAliasMailProvider(selectMailProvider.value);
inputEmail.disabled = locked;
inputAutoSkipFailures.disabled = scheduled;
inputAutoSkipFailures.disabled = true;
if (currentAutoRun.totalRuns > 0) {
inputRunCount.value = String(currentAutoRun.totalRuns);
@@ -1079,7 +1078,7 @@ function applySettingsState(state) {
inputInbucketMailbox.value = state?.inbucketMailbox || '';
renderCloudflareDomainOptions(state?.cloudflareDomain || '');
setCloudflareDomainEditMode(false, { clearInput: true });
inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
inputAutoSkipFailures.checked = true;
inputAutoSkipFailuresThreadIntervalMinutes.value = String(normalizeAutoRunThreadIntervalMinutes(state?.autoRunFallbackThreadIntervalMinutes));
inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
@@ -2566,25 +2565,6 @@ btnAutoRun.addEventListener('click', async () => {
mode = choice;
}
const autoRunSkipFailures = inputAutoSkipFailures.checked;
const fallbackThreadIntervalMinutes = normalizeAutoRunThreadIntervalMinutes(
inputAutoSkipFailuresThreadIntervalMinutes.value
);
inputAutoSkipFailuresThreadIntervalMinutes.value = String(fallbackThreadIntervalMinutes);
if (
shouldWarnAutoRunFallbackRisk(totalRuns, autoRunSkipFailures)
&& !isAutoRunFallbackRiskPromptDismissed()
) {
const result = await openAutoRunFallbackRiskConfirmModal(totalRuns, fallbackThreadIntervalMinutes);
if (!result.confirmed) {
return;
}
if (result.dismissPrompt) {
setAutoRunFallbackRiskPromptDismissed(true);
}
}
btnAutoRun.disabled = true;
inputRunCount.disabled = true;
const delayEnabled = inputAutoDelayEnabled.checked;
@@ -2599,7 +2579,7 @@ btnAutoRun.addEventListener('click', async () => {
payload: {
totalRuns,
delayMinutes,
autoRunSkipFailures,
autoRunSkipFailures: true,
mode,
},
});
@@ -2869,21 +2849,8 @@ inputRunCount.addEventListener('blur', () => {
});
inputAutoSkipFailures.addEventListener('change', async () => {
if (inputAutoSkipFailures.checked && !isAutoSkipFailuresPromptDismissed()) {
const result = await openAutoSkipFailuresConfirmModal();
if (!result.confirmed) {
inputAutoSkipFailures.checked = false;
updateFallbackThreadIntervalInputState();
return;
}
if (result.dismissPrompt) {
setAutoSkipFailuresPromptDismissed(true);
}
}
inputAutoSkipFailures.checked = true;
updateFallbackThreadIntervalInputState();
markSettingsDirty(true);
saveSettings({ silent: true }).catch(() => { });
});
inputAutoSkipFailuresThreadIntervalMinutes.addEventListener('input', () => {
@@ -3016,7 +2983,7 @@ chrome.runtime.onMessage.addListener((message) => {
}
}
if (message.payload.autoRunSkipFailures !== undefined) {
inputAutoSkipFailures.checked = Boolean(message.payload.autoRunSkipFailures);
inputAutoSkipFailures.checked = true;
updateFallbackThreadIntervalInputState();
}
if (message.payload.autoRunDelayEnabled !== undefined) {