Parcourir la source

feat: 修改兜底模式样式,增加线程间隔设置和相关UI组件

QLHazyCoder il y a 4 mois
Parent
commit
8a37872336
4 fichiers modifiés avec 336 ajouts et 12 suppressions
  1. 67 1
      background.js
  2. 84 0
      sidepanel/sidepanel.css
  3. 25 8
      sidepanel/sidepanel.html
  4. 160 3
      sidepanel/sidepanel.js

+ 67 - 1
background.js

@@ -50,7 +50,7 @@ initializeSessionStorageAccess();
 // 状态管理(chrome.storage.session + chrome.storage.local)
 // ============================================================
 
-const PERSISTED_SETTING_DEFAULTS = {
+const LEGACY_PERSISTED_SETTING_DEFAULTS = {
   panelMode: 'cpa', // Step 1 / Step 9 的来源模式:cpa | sub2api。
   vpsUrl: '', // VPS 面板地址,可手动填写。
   vpsPassword: '', // VPS 面板登录密码,可手动填写。
@@ -61,6 +61,18 @@ const PERSISTED_SETTING_DEFAULTS = {
   sub2apiGroupName: DEFAULT_SUB2API_GROUP_NAME, // SUB2API 创建账号时绑定的分组名。
   customPassword: '', // 自定义账号密码;留空时由程序自动生成随机密码。
   autoRunSkipFailures: false, // 自动运行遇到失败步骤后,是否继续执行后续流程。
+  autoRunFallbackThreadIntervalMinutes: 0, // 兜底模式下,两轮线程之间的等待分钟数。
+  autoRunDelayEnabled: false, // 自动运行是否启用启动前倒计时。
+  autoRunDelayMinutes: 30, // 自动运行倒计时分钟数。
+  autoStepDelaySeconds: null, // 自动运行每一步执行前的额外等待秒数;0 或空表示不延迟。
+  mailProvider: '163', // 验证码邮箱来源(163 / 163-vip / qq / inbucket)。
+  autoRunSkipFailures: false, // 自动运行遇到失败步骤后,是否继续执行后续流程。
+  autoRunDelayEnabled: false, // 自动运行是否启用启动前倒计时。
+  autoRunDelayMinutes: 30, // 自动运行倒计时分钟数。
+  autoStepDelaySeconds: null, // 自动运行每一步执行前的额外等待秒数;0 或空表示不延迟。
+  mailProvider: '163', // 验证码邮箱来源(163 / 163-vip / qq / inbucket)。
+  autoRunSkipFailures: false, // 自动运行遇到失败步骤后,是否继续执行后续流程。
+  autoRunFallbackThreadIntervalMinutes: 0, // 兜底模式下,两轮线程之间的等待分钟数。
   autoRunDelayEnabled: false, // 自动运行是否启用启动前倒计时。
   autoRunDelayMinutes: 30, // 自动运行倒计时分钟数。
   autoStepDelaySeconds: null, // 自动运行每一步执行前的额外等待秒数;0 或空表示不延迟。
@@ -73,6 +85,30 @@ const PERSISTED_SETTING_DEFAULTS = {
   hotmailAccounts: [],
 };
 
+const PERSISTED_SETTING_DEFAULTS = {
+  panelMode: 'cpa',
+  vpsUrl: '',
+  vpsPassword: '',
+  localCpaStep9Mode: DEFAULT_LOCAL_CPA_STEP9_MODE,
+  sub2apiUrl: DEFAULT_SUB2API_URL,
+  sub2apiEmail: '',
+  sub2apiPassword: '',
+  sub2apiGroupName: DEFAULT_SUB2API_GROUP_NAME,
+  customPassword: '',
+  autoRunSkipFailures: false,
+  autoRunFallbackThreadIntervalMinutes: 0,
+  autoRunDelayEnabled: false,
+  autoRunDelayMinutes: 30,
+  autoStepDelaySeconds: null,
+  mailProvider: '163',
+  emailGenerator: 'duck',
+  inbucketHost: '',
+  inbucketMailbox: '',
+  cloudflareDomain: '',
+  cloudflareDomains: [],
+  hotmailAccounts: [],
+};
+
 const PERSISTED_SETTING_KEYS = Object.keys(PERSISTED_SETTING_DEFAULTS);
 const SETTINGS_EXPORT_SCHEMA_VERSION = 1;
 const SETTINGS_EXPORT_FILENAME_PREFIX = 'multipage-settings';
@@ -123,6 +159,23 @@ function normalizeAutoRunDelayMinutes(value) {
   );
 }
 
+function normalizeAutoRunFallbackThreadIntervalMinutes(value) {
+  const rawValue = String(value ?? '').trim();
+  if (!rawValue) {
+    return 0;
+  }
+
+  const numeric = Number(rawValue);
+  if (!Number.isFinite(numeric)) {
+    return 0;
+  }
+
+  return Math.min(
+    AUTO_RUN_DELAY_MAX_MINUTES,
+    Math.max(0, Math.floor(numeric))
+  );
+}
+
 function normalizeAutoStepDelaySeconds(value, fallback = null) {
   const rawValue = String(value ?? '').trim();
   if (!rawValue) {
@@ -256,6 +309,8 @@ function normalizePersistentSettingValue(key, value) {
     case 'autoRunSkipFailures':
     case 'autoRunDelayEnabled':
       return Boolean(value);
+    case 'autoRunFallbackThreadIntervalMinutes':
+      return normalizeAutoRunFallbackThreadIntervalMinutes(value);
     case 'autoRunDelayMinutes':
       return normalizeAutoRunDelayMinutes(value);
     case 'autoStepDelaySeconds':
@@ -3435,6 +3490,7 @@ async function autoRunLoop(totalRuns, options = {}) {
         vpsPassword: prevState.vpsPassword,
         customPassword: prevState.customPassword,
         autoRunSkipFailures: prevState.autoRunSkipFailures,
+        autoRunFallbackThreadIntervalMinutes: prevState.autoRunFallbackThreadIntervalMinutes,
         autoRunDelayEnabled: prevState.autoRunDelayEnabled,
         autoRunDelayMinutes: prevState.autoRunDelayMinutes,
         autoStepDelaySeconds: prevState.autoStepDelaySeconds,
@@ -3481,6 +3537,16 @@ async function autoRunLoop(totalRuns, options = {}) {
       successfulRuns += 1;
       autoRunCurrentRun = successfulRuns;
       await addLog(`=== 目标 ${successfulRuns}/${totalRuns} 轮已完成(第 ${attemptRuns} 次尝试成功)===`, 'ok');
+      const fallbackThreadIntervalMinutes = normalizeAutoRunFallbackThreadIntervalMinutes(
+        (await getState()).autoRunFallbackThreadIntervalMinutes
+      );
+      if (autoRunSkipFailures && totalRuns > 1 && successfulRuns < totalRuns && fallbackThreadIntervalMinutes > 0) {
+        await addLog(
+          `兜底模式:第 ${successfulRuns}/${totalRuns} 轮已完成,等待 ${fallbackThreadIntervalMinutes} 分钟后再启动下一轮新线程。`,
+          'info'
+        );
+        await sleepWithStop(fallbackThreadIntervalMinutes * 60 * 1000);
+      }
       continue;
     } catch (err) {
       if (isStopError(err)) {

+ 84 - 0
sidepanel/sidepanel.css

@@ -721,6 +721,72 @@ header {
   flex-shrink: 0;
 }
 
+.fallback-inline {
+  justify-content: space-between;
+}
+
+.fallback-thread-interval {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-left: auto;
+}
+
+.toggle-switch {
+  position: relative;
+  display: inline-flex;
+  align-items: center;
+  flex: 0 0 auto;
+  cursor: pointer;
+}
+
+.toggle-switch > span:not(.toggle-switch-track) {
+  display: none;
+}
+
+.toggle-switch input {
+  position: absolute;
+  opacity: 0;
+  width: 1px;
+  height: 1px;
+  pointer-events: none;
+}
+
+.toggle-switch-track {
+  width: 38px;
+  height: 22px;
+  border-radius: 999px;
+  border: 1px solid var(--border);
+  background: var(--bg-elevated);
+  display: inline-flex;
+  align-items: center;
+  padding: 2px;
+  transition: background var(--transition), border-color var(--transition), box-shadow var(--transition);
+}
+
+.toggle-switch-thumb {
+  width: 16px;
+  height: 16px;
+  border-radius: 50%;
+  background: var(--bg-base);
+  box-shadow: var(--shadow-sm);
+  transition: transform var(--transition), background var(--transition);
+}
+
+.toggle-switch input:checked + .toggle-switch-track {
+  background: var(--blue-soft);
+  border-color: var(--blue);
+}
+
+.toggle-switch input:checked + .toggle-switch-track .toggle-switch-thumb {
+  transform: translateX(16px);
+  background: var(--blue);
+}
+
+.toggle-switch input:disabled + .toggle-switch-track {
+  opacity: 0.55;
+}
+
 .auto-delay-inline {
   justify-content: space-between;
   gap: 12px;
@@ -1196,6 +1262,24 @@ header {
   margin-bottom: 14px;
 }
 
+.modal-option-row {
+  margin-bottom: 14px;
+}
+
+.modal-option-label {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  color: var(--text-secondary);
+  cursor: pointer;
+}
+
+.modal-option-label input[type="checkbox"] {
+  accent-color: var(--blue);
+  cursor: pointer;
+}
+
 .modal-actions {
   display: flex;
   justify-content: flex-end;

+ 25 - 8
sidepanel/sidepanel.html

@@ -172,12 +172,24 @@
           <button id="btn-fetch-email" class="btn btn-outline btn-sm data-inline-btn" type="button">获取</button>
         </div>
       </div>
-      <div class="data-row data-check-row">
+      <div class="data-row">
         <span class="data-label">兜底</span>
-        <label class="data-check" for="input-auto-skip-failures">
-          <input type="checkbox" id="input-auto-skip-failures" />
-          <span>出现错误无法继续时,直接丢弃当前线程并新开一轮,直到补足目标运行次数</span>
-        </label>
+        <div class="data-inline fallback-inline">
+          <label class="toggle-switch" for="input-auto-skip-failures">
+            <input type="checkbox" id="input-auto-skip-failures" />
+            <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">线程间隔</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"
+                min="0" max="1440" step="1" title="兜底模式下,两轮线程之间的等待分钟数" />
+              <span class="data-unit">分钟</span>
+            </div>
+          </div>
+        </div>
       </div>
       <div class="data-row">
         <span class="data-label">延迟</span>
@@ -202,8 +214,7 @@
           </div>
         </div>
       </div>
-    </div>
-    <div class="data-row">
+      <div class="data-row">
       <span class="data-label">OAuth</span>
       <span id="display-oauth-url" class="data-value mono truncate">等待中...</span>
     </div>
@@ -359,6 +370,12 @@
         <button id="btn-auto-start-close" class="modal-close" type="button" aria-label="关闭">×</button>
       </div>
       <p id="auto-start-message" class="modal-message">检测到已有流程进度,选择继续当前还是重新开始。</p>
+      <div id="modal-option-row" class="modal-option-row" hidden>
+        <label class="modal-option-label" for="modal-option-input">
+          <input type="checkbox" id="modal-option-input" />
+          <span id="modal-option-text">不再提示</span>
+        </label>
+      </div>
       <div class="modal-actions">
         <button id="btn-auto-start-cancel" class="btn btn-ghost btn-sm" type="button">取消</button>
         <button id="btn-auto-start-restart" class="btn btn-outline btn-sm" type="button">重新开始</button>
@@ -373,4 +390,4 @@
   <script src="sidepanel.js"></script>
 </body>
 
-</html>
+</html>

+ 160 - 3
sidepanel/sidepanel.js

@@ -83,12 +83,16 @@ const inputCfDomain = document.getElementById('input-cf-domain');
 const btnCfDomainMode = document.getElementById('btn-cf-domain-mode');
 const inputRunCount = document.getElementById('input-run-count');
 const inputAutoSkipFailures = document.getElementById('input-auto-skip-failures');
+const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('input-auto-skip-failures-thread-interval-minutes');
 const inputAutoDelayEnabled = document.getElementById('input-auto-delay-enabled');
 const inputAutoDelayMinutes = document.getElementById('input-auto-delay-minutes');
 const inputAutoStepDelaySeconds = document.getElementById('input-auto-step-delay-seconds');
 const autoStartModal = document.getElementById('auto-start-modal');
 const autoStartTitle = autoStartModal?.querySelector('.modal-title');
 const autoStartMessage = document.getElementById('auto-start-message');
+const modalOptionRow = document.getElementById('modal-option-row');
+const modalOptionInput = document.getElementById('modal-option-input');
+const modalOptionText = document.getElementById('modal-option-text');
 const btnAutoStartClose = document.getElementById('btn-auto-start-close');
 const btnAutoStartCancel = document.getElementById('btn-auto-start-cancel');
 const btnAutoStartRestart = document.getElementById('btn-auto-start-restart');
@@ -109,9 +113,13 @@ const SKIPPABLE_STEPS = new Set([1, 2, 3, 4, 5, 6, 7, 8, 9]);
 const AUTO_DELAY_MIN_MINUTES = 1;
 const AUTO_DELAY_MAX_MINUTES = 1440;
 const AUTO_DELAY_DEFAULT_MINUTES = 30;
+const AUTO_FALLBACK_THREAD_INTERVAL_MIN_MINUTES = 0;
+const AUTO_FALLBACK_THREAD_INTERVAL_MAX_MINUTES = 1440;
+const AUTO_FALLBACK_THREAD_INTERVAL_DEFAULT_MINUTES = 0;
 const AUTO_STEP_DELAY_MIN_SECONDS = 0;
 const AUTO_STEP_DELAY_MAX_SECONDS = 600;
 const DEFAULT_LOCAL_CPA_STEP9_MODE = 'submit';
+const AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY = 'multipage-auto-skip-failures-prompt-dismissed';
 
 let latestState = null;
 let currentAutoRun = {
@@ -128,6 +136,7 @@ let settingsAutoSaveTimer = null;
 let cloudflareDomainEditMode = false;
 let modalChoiceResolver = null;
 let currentModalActions = [];
+let modalResultBuilder = null;
 let scheduledCountdownTimer = null;
 let hotmailActionInFlight = false;
 let hotmailListExpanded = false;
@@ -198,6 +207,17 @@ function dismissToast(toast) {
   toast.addEventListener('animationend', () => toast.remove());
 }
 
+function resetActionModalOption() {
+  if (!modalOptionRow || !modalOptionInput || !modalOptionText) {
+    return;
+  }
+
+  modalOptionRow.hidden = true;
+  modalOptionInput.checked = false;
+  modalOptionInput.disabled = false;
+  modalOptionText.textContent = '不再提示';
+}
+
 function resetActionModalButtons() {
   const buttons = [btnAutoStartCancel, btnAutoStartRestart, btnAutoStartContinue];
   buttons.forEach((button) => {
@@ -224,18 +244,40 @@ function configureActionModalButton(button, action) {
   button.onclick = () => resolveModalChoice(action.id);
 }
 
+function configureActionModalOption(option) {
+  if (!modalOptionRow || !modalOptionInput || !modalOptionText) {
+    return;
+  }
+
+  if (!option) {
+    resetActionModalOption();
+    return;
+  }
+
+  modalOptionRow.hidden = false;
+  modalOptionInput.checked = Boolean(option.checked);
+  modalOptionInput.disabled = Boolean(option.disabled);
+  modalOptionText.textContent = option.label || '不再提示';
+}
+
 function resolveModalChoice(choice) {
+  const optionChecked = Boolean(modalOptionInput?.checked);
+  const result = typeof modalResultBuilder === 'function'
+    ? modalResultBuilder(choice, { optionChecked })
+    : choice;
   if (modalChoiceResolver) {
-    modalChoiceResolver(choice);
+    modalChoiceResolver(result);
     modalChoiceResolver = null;
   }
+  modalResultBuilder = null;
   resetActionModalButtons();
+  resetActionModalOption();
   if (autoStartModal) {
     autoStartModal.hidden = true;
   }
 }
 
-function openActionModal({ title, message, actions }) {
+function openActionModal({ title, message, actions, option, buildResult }) {
   if (!autoStartModal) {
     return Promise.resolve(null);
   }
@@ -244,12 +286,15 @@ function openActionModal({ title, message, actions }) {
     resolveModalChoice(null);
   }
 
+  resetActionModalButtons();
   autoStartTitle.textContent = title;
   autoStartMessage.textContent = message;
   currentModalActions = actions || [];
+  modalResultBuilder = typeof buildResult === 'function' ? buildResult : null;
   configureActionModalButton(btnAutoStartCancel, currentModalActions[0]);
   configureActionModalButton(btnAutoStartRestart, currentModalActions[1]);
   configureActionModalButton(btnAutoStartContinue, currentModalActions[2]);
+  configureActionModalOption(option);
   autoStartModal.hidden = false;
 
   return new Promise((resolve) => {
@@ -285,6 +330,42 @@ async function openConfirmModal({ title, message, confirmLabel = '确认', confi
   return choice === 'confirm';
 }
 
+function isAutoSkipFailuresPromptDismissed() {
+  return localStorage.getItem(AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY) === '1';
+}
+
+function setAutoSkipFailuresPromptDismissed(dismissed) {
+  if (dismissed) {
+    localStorage.setItem(AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY, '1');
+  } else {
+    localStorage.removeItem(AUTO_SKIP_FAILURES_PROMPT_DISMISSED_STORAGE_KEY);
+  }
+}
+
+async function openAutoSkipFailuresConfirmModal() {
+  const result = await openActionModal({
+    title: '兜底说明',
+    message: '开启后,当某一轮失败且无法继续时,会直接放弃当前线程,重新开新一轮,直到补足目标运行次数。线程间隔只在开启兜底且运行次数大于 1 时生效。',
+    actions: [
+      { id: null, label: '取消', variant: 'btn-ghost' },
+      { id: 'confirm', label: '确认开启', variant: 'btn-primary' },
+    ],
+    option: {
+      label: '不再提示',
+      checked: false,
+    },
+    buildResult: (choice, meta) => ({
+      choice,
+      optionChecked: Boolean(meta?.optionChecked),
+    }),
+  });
+
+  return {
+    confirmed: result?.choice === 'confirm',
+    dismissPrompt: Boolean(result?.optionChecked),
+  };
+}
+
 function updateConfigMenuControls() {
   const disabled = configActionInFlight || settingsSaveInFlight;
   const importLocked = disabled
@@ -450,6 +531,23 @@ function normalizeAutoDelayMinutes(value) {
   return Math.min(AUTO_DELAY_MAX_MINUTES, Math.max(AUTO_DELAY_MIN_MINUTES, Math.floor(numeric)));
 }
 
+function normalizeAutoRunThreadIntervalMinutes(value) {
+  const rawValue = String(value ?? '').trim();
+  if (!rawValue) {
+    return AUTO_FALLBACK_THREAD_INTERVAL_DEFAULT_MINUTES;
+  }
+
+  const numeric = Number(rawValue);
+  if (!Number.isFinite(numeric)) {
+    return AUTO_FALLBACK_THREAD_INTERVAL_DEFAULT_MINUTES;
+  }
+
+  return Math.min(
+    AUTO_FALLBACK_THREAD_INTERVAL_MAX_MINUTES,
+    Math.max(AUTO_FALLBACK_THREAD_INTERVAL_MIN_MINUTES, Math.floor(numeric))
+  );
+}
+
 function normalizeAutoStepDelaySeconds(value) {
   const rawValue = String(value ?? '').trim();
   if (!rawValue) {
@@ -469,6 +567,19 @@ function formatAutoStepDelayInputValue(value) {
   return normalized === null ? '' : String(normalized);
 }
 
+function getRunCountValue() {
+  return Math.min(50, Math.max(1, parseInt(inputRunCount.value, 10) || 1));
+}
+
+function updateFallbackThreadIntervalInputState() {
+  if (!inputAutoSkipFailuresThreadIntervalMinutes) {
+    return;
+  }
+
+  const enabled = inputAutoSkipFailures.checked && getRunCountValue() > 1;
+  inputAutoSkipFailuresThreadIntervalMinutes.disabled = !enabled;
+}
+
 function updateAutoDelayInputState() {
   const scheduled = isAutoRunScheduledPhase();
   inputAutoDelayEnabled.disabled = scheduled;
@@ -637,6 +748,7 @@ function collectSettingsPayload() {
     cloudflareDomain: selectedCloudflareDomain,
     cloudflareDomains: domains,
     autoRunSkipFailures: inputAutoSkipFailures.checked,
+    autoRunFallbackThreadIntervalMinutes: normalizeAutoRunThreadIntervalMinutes(inputAutoSkipFailuresThreadIntervalMinutes.value),
     autoRunDelayEnabled: inputAutoDelayEnabled.checked,
     autoRunDelayMinutes: normalizeAutoDelayMinutes(inputAutoDelayMinutes.value),
     autoStepDelaySeconds: normalizeAutoStepDelaySeconds(inputAutoStepDelaySeconds.value),
@@ -818,6 +930,7 @@ function applyAutoRunStatus(payload = currentAutoRun) {
   }
 
   updateAutoDelayInputState();
+  updateFallbackThreadIntervalInputState();
   syncScheduledCountdownTicker();
   updateStopButtonState(scheduled || paused || locked || Object.values(getStepStatuses()).some(status => status === 'running'));
   updateConfigMenuControls();
@@ -879,6 +992,7 @@ function applySettingsState(state) {
   renderCloudflareDomainOptions(state?.cloudflareDomain || '');
   setCloudflareDomainEditMode(false, { clearInput: true });
   inputAutoSkipFailures.checked = Boolean(state?.autoRunSkipFailures);
+  inputAutoSkipFailuresThreadIntervalMinutes.value = String(normalizeAutoRunThreadIntervalMinutes(state?.autoRunFallbackThreadIntervalMinutes));
   inputAutoDelayEnabled.checked = Boolean(state?.autoRunDelayEnabled);
   inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(state?.autoRunDelayMinutes));
   inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(state?.autoStepDelaySeconds);
@@ -889,6 +1003,7 @@ function applySettingsState(state) {
   applyAutoRunStatus(state);
   markSettingsDirty(false);
   updateAutoDelayInputState();
+  updateFallbackThreadIntervalInputState();
   updatePanelModeUI();
   updateMailProviderUI();
   updateButtonStates();
@@ -2384,8 +2499,40 @@ inputInbucketHost.addEventListener('blur', () => {
   saveSettings({ silent: true }).catch(() => { });
 });
 
-inputAutoSkipFailures.addEventListener('change', () => {
+inputRunCount.addEventListener('input', () => {
+  updateFallbackThreadIntervalInputState();
+});
+inputRunCount.addEventListener('blur', () => {
+  inputRunCount.value = String(getRunCountValue());
+  updateFallbackThreadIntervalInputState();
+});
+
+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);
+    }
+  }
+
+  updateFallbackThreadIntervalInputState();
+  markSettingsDirty(true);
+  saveSettings({ silent: true }).catch(() => { });
+});
+
+inputAutoSkipFailuresThreadIntervalMinutes.addEventListener('input', () => {
   markSettingsDirty(true);
+  scheduleSettingsAutoSave();
+});
+inputAutoSkipFailuresThreadIntervalMinutes.addEventListener('blur', () => {
+  inputAutoSkipFailuresThreadIntervalMinutes.value = String(
+    normalizeAutoRunThreadIntervalMinutes(inputAutoSkipFailuresThreadIntervalMinutes.value)
+  );
   saveSettings({ silent: true }).catch(() => { });
 });
 
@@ -2507,6 +2654,10 @@ chrome.runtime.onMessage.addListener((message) => {
           inputEmail.value = getCurrentHotmailEmail();
         }
       }
+      if (message.payload.autoRunSkipFailures !== undefined) {
+        inputAutoSkipFailures.checked = Boolean(message.payload.autoRunSkipFailures);
+        updateFallbackThreadIntervalInputState();
+      }
       if (message.payload.autoRunDelayEnabled !== undefined) {
         inputAutoDelayEnabled.checked = Boolean(message.payload.autoRunDelayEnabled);
         updateAutoDelayInputState();
@@ -2514,6 +2665,12 @@ chrome.runtime.onMessage.addListener((message) => {
       if (message.payload.autoRunDelayMinutes !== undefined) {
         inputAutoDelayMinutes.value = String(normalizeAutoDelayMinutes(message.payload.autoRunDelayMinutes));
       }
+      if (message.payload.autoRunFallbackThreadIntervalMinutes !== undefined) {
+        inputAutoSkipFailuresThreadIntervalMinutes.value = String(
+          normalizeAutoRunThreadIntervalMinutes(message.payload.autoRunFallbackThreadIntervalMinutes)
+        );
+        updateFallbackThreadIntervalInputState();
+      }
       if (message.payload.autoStepDelaySeconds !== undefined) {
         inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(message.payload.autoStepDelaySeconds);
       }