auto-run-controller.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. (function attachBackgroundAutoRunController(root, factory) {
  2. root.MultiPageBackgroundAutoRunController = factory();
  3. })(typeof self !== 'undefined' ? self : globalThis, function createBackgroundAutoRunControllerModule() {
  4. function createAutoRunController(deps = {}) {
  5. const {
  6. addLog,
  7. appendAccountRunRecord,
  8. AUTO_RUN_MAX_RETRIES_PER_ROUND,
  9. AUTO_RUN_RETRY_DELAY_MS,
  10. AUTO_RUN_TIMER_KIND_BEFORE_RETRY,
  11. AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS,
  12. broadcastAutoRunStatus,
  13. broadcastStopToContentScripts,
  14. cancelPendingCommands,
  15. clearStopRequest,
  16. createAutoRunSessionId,
  17. getAutoRunStatusPayload,
  18. getErrorMessage,
  19. getFirstUnfinishedStep,
  20. getPendingAutoRunTimerPlan,
  21. getRunningSteps,
  22. getState,
  23. hasSavedProgress,
  24. isRestartCurrentAttemptError,
  25. isStopError,
  26. launchAutoRunTimerPlan,
  27. normalizeAutoRunFallbackThreadIntervalMinutes,
  28. persistAutoRunTimerPlan,
  29. resetState,
  30. runAutoSequenceFromStep,
  31. runtime,
  32. setState,
  33. sleepWithStop,
  34. throwIfAutoRunSessionStopped,
  35. waitForRunningStepsToFinish,
  36. } = deps;
  37. function createAutoRunRoundSummary(round) {
  38. return {
  39. round,
  40. status: 'pending',
  41. attempts: 0,
  42. failureReasons: [],
  43. finalFailureReason: '',
  44. };
  45. }
  46. function normalizeAutoRunRoundSummary(summary, round) {
  47. const base = createAutoRunRoundSummary(round);
  48. if (!summary || typeof summary !== 'object') {
  49. return base;
  50. }
  51. const status = String(summary.status || '').trim().toLowerCase();
  52. return {
  53. round,
  54. status: ['pending', 'success', 'failed'].includes(status) ? status : base.status,
  55. attempts: Math.max(0, Math.floor(Number(summary.attempts) || 0)),
  56. failureReasons: Array.isArray(summary.failureReasons)
  57. ? summary.failureReasons.map((item) => String(item || '').trim()).filter(Boolean)
  58. : [],
  59. finalFailureReason: String(summary.finalFailureReason || '').trim(),
  60. };
  61. }
  62. function buildAutoRunRoundSummaries(totalRuns, rawSummaries = []) {
  63. return Array.from({ length: totalRuns }, (_, index) => normalizeAutoRunRoundSummary(rawSummaries[index], index + 1));
  64. }
  65. function serializeAutoRunRoundSummaries(totalRuns, roundSummaries = []) {
  66. return buildAutoRunRoundSummaries(totalRuns, roundSummaries).map((summary) => ({
  67. ...summary,
  68. failureReasons: [...summary.failureReasons],
  69. }));
  70. }
  71. function getAutoRunRoundRetryCount(summary) {
  72. return Math.max(0, Number(summary?.attempts || 0) - 1);
  73. }
  74. function formatAutoRunFailureReasons(reasons = []) {
  75. if (!Array.isArray(reasons) || !reasons.length) {
  76. return '未知错误';
  77. }
  78. const counts = new Map();
  79. for (const reason of reasons) {
  80. const normalized = String(reason || '').trim() || '未知错误';
  81. counts.set(normalized, (counts.get(normalized) || 0) + 1);
  82. }
  83. return Array.from(counts.entries())
  84. .map(([reason, count]) => (count > 1 ? `${reason}(${count}次)` : reason))
  85. .join(';');
  86. }
  87. async function logAutoRunFinalSummary(totalRuns, roundSummaries = []) {
  88. const summaries = buildAutoRunRoundSummaries(totalRuns, roundSummaries);
  89. const successRounds = summaries.filter((item) => item.status === 'success');
  90. const failedRounds = summaries.filter((item) => item.status === 'failed');
  91. const pendingRounds = summaries.filter((item) => item.status === 'pending');
  92. await addLog('=== 自动运行汇总 ===', failedRounds.length ? 'warn' : 'ok');
  93. await addLog(
  94. `总轮数:${totalRuns};成功:${successRounds.length};失败:${failedRounds.length};未完成:${pendingRounds.length}`,
  95. failedRounds.length ? 'warn' : 'ok'
  96. );
  97. if (successRounds.length) {
  98. await addLog(
  99. `成功轮次:${successRounds
  100. .map((item) => `第 ${item.round} 轮(重试 ${getAutoRunRoundRetryCount(item)} 次)`)
  101. .join(';')}`,
  102. 'ok'
  103. );
  104. }
  105. if (failedRounds.length) {
  106. await addLog(
  107. `失败轮次:${failedRounds
  108. .map((item) => {
  109. const retryCount = getAutoRunRoundRetryCount(item);
  110. const finalReason = item.finalFailureReason || item.failureReasons[item.failureReasons.length - 1] || '未知错误';
  111. const reasonSummary = formatAutoRunFailureReasons(item.failureReasons);
  112. return `第 ${item.round} 轮(重试 ${retryCount} 次,最终原因:${finalReason};失败记录:${reasonSummary})`;
  113. })
  114. .join(';')}`,
  115. 'error'
  116. );
  117. }
  118. if (pendingRounds.length) {
  119. await addLog(
  120. `未完成轮次:${pendingRounds.map((item) => `第 ${item.round} 轮`).join(';')}`,
  121. 'warn'
  122. );
  123. }
  124. }
  125. async function skipAutoRunCountdown() {
  126. const state = await getState();
  127. const plan = getPendingAutoRunTimerPlan(state);
  128. if (!plan || state.autoRunPhase !== 'waiting_interval') {
  129. return false;
  130. }
  131. return launchAutoRunTimerPlan('manual', {
  132. expectedKinds: [
  133. AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS,
  134. AUTO_RUN_TIMER_KIND_BEFORE_RETRY,
  135. ],
  136. });
  137. }
  138. async function waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary, options = {}) {
  139. const { autoRunSkipFailures = false, roundSummaries = [] } = options;
  140. if (totalRuns <= 1 || targetRun >= totalRuns) {
  141. return false;
  142. }
  143. const fallbackThreadIntervalMinutes = normalizeAutoRunFallbackThreadIntervalMinutes(
  144. (await getState()).autoRunFallbackThreadIntervalMinutes
  145. );
  146. if (fallbackThreadIntervalMinutes <= 0) {
  147. return false;
  148. }
  149. const currentRuntime = runtime.get();
  150. const statusLabel = roundSummary?.status === 'failed' ? '失败' : '完成';
  151. await addLog(
  152. `线程间隔:第 ${targetRun}/${totalRuns} 轮已${statusLabel},等待 ${fallbackThreadIntervalMinutes} 分钟后开始下一轮。`,
  153. 'info'
  154. );
  155. await persistAutoRunTimerPlan({
  156. kind: AUTO_RUN_TIMER_KIND_BETWEEN_ROUNDS,
  157. fireAt: Date.now() + fallbackThreadIntervalMinutes * 60 * 1000,
  158. currentRun: targetRun,
  159. totalRuns,
  160. attemptRun: currentRuntime.autoRunAttemptRun,
  161. autoRunSessionId: currentRuntime.autoRunSessionId,
  162. autoRunSkipFailures,
  163. roundSummaries,
  164. countdownTitle: '线程间隔中',
  165. countdownNote: `第 ${Math.min(targetRun + 1, totalRuns)}/${totalRuns} 轮即将开始`,
  166. }, {
  167. autoRunSkipFailures,
  168. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  169. });
  170. runtime.set({ autoRunActive: false });
  171. return true;
  172. }
  173. async function waitBeforeAutoRunRetry(targetRun, totalRuns, nextAttemptRun, options = {}) {
  174. const { autoRunSkipFailures = false, roundSummaries = [] } = options;
  175. const fallbackThreadIntervalMinutes = normalizeAutoRunFallbackThreadIntervalMinutes(
  176. (await getState()).autoRunFallbackThreadIntervalMinutes
  177. );
  178. if (fallbackThreadIntervalMinutes <= 0) {
  179. return false;
  180. }
  181. await addLog(
  182. `线程间隔:等待 ${fallbackThreadIntervalMinutes} 分钟后开始第 ${targetRun}/${totalRuns} 轮第 ${nextAttemptRun} 次尝试。`,
  183. 'info'
  184. );
  185. await persistAutoRunTimerPlan({
  186. kind: AUTO_RUN_TIMER_KIND_BEFORE_RETRY,
  187. fireAt: Date.now() + fallbackThreadIntervalMinutes * 60 * 1000,
  188. currentRun: targetRun,
  189. totalRuns,
  190. attemptRun: nextAttemptRun,
  191. autoRunSessionId: runtime.get().autoRunSessionId,
  192. autoRunSkipFailures,
  193. roundSummaries,
  194. countdownTitle: '线程间隔中',
  195. countdownNote: `第 ${targetRun}/${totalRuns} 轮第 ${nextAttemptRun} 次尝试即将开始`,
  196. }, {
  197. autoRunSkipFailures,
  198. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  199. });
  200. runtime.set({ autoRunActive: false });
  201. return true;
  202. }
  203. async function handleAutoRunLoopUnhandledError(error) {
  204. const currentRuntime = runtime.get();
  205. console.error('Auto run loop crashed:', error);
  206. if (!isStopError(error)) {
  207. await addLog(`自动运行异常终止:${getErrorMessage(error) || '未知错误'}`, 'error');
  208. }
  209. runtime.set({ autoRunActive: false, autoRunSessionId: 0 });
  210. await broadcastAutoRunStatus('stopped', {
  211. currentRun: currentRuntime.autoRunCurrentRun,
  212. totalRuns: currentRuntime.autoRunTotalRuns,
  213. attemptRun: currentRuntime.autoRunAttemptRun,
  214. sessionId: 0,
  215. }, {
  216. autoRunSessionId: 0,
  217. autoRunTimerPlan: null,
  218. scheduledAutoRunPlan: null,
  219. });
  220. clearStopRequest();
  221. }
  222. function startAutoRunLoop(totalRuns, options = {}) {
  223. autoRunLoop(totalRuns, options).catch((error) => {
  224. handleAutoRunLoopUnhandledError(error).catch(() => {});
  225. });
  226. }
  227. async function autoRunLoop(totalRuns, options = {}) {
  228. let currentRuntime = runtime.get();
  229. if (currentRuntime.autoRunActive) {
  230. await addLog('自动运行已在进行中', 'warn');
  231. return;
  232. }
  233. let sessionId = Number.isInteger(options.autoRunSessionId) && options.autoRunSessionId > 0
  234. ? options.autoRunSessionId
  235. : 0;
  236. if (sessionId) {
  237. throwIfAutoRunSessionStopped(sessionId);
  238. } else {
  239. sessionId = createAutoRunSessionId();
  240. }
  241. clearStopRequest();
  242. runtime.set({
  243. autoRunActive: true,
  244. autoRunTotalRuns: totalRuns,
  245. autoRunCurrentRun: 0,
  246. autoRunAttemptRun: 0,
  247. autoRunSessionId: sessionId,
  248. });
  249. currentRuntime = runtime.get();
  250. const autoRunSkipFailures = Boolean(options.autoRunSkipFailures);
  251. const initialMode = options.mode === 'continue' ? 'continue' : 'restart';
  252. const resumeCurrentRun = Number.isInteger(options.resumeCurrentRun) && options.resumeCurrentRun > 0
  253. ? Math.min(totalRuns, options.resumeCurrentRun)
  254. : 1;
  255. const resumeAttemptRun = Number.isInteger(options.resumeAttemptRun) && options.resumeAttemptRun > 0
  256. ? Math.min(AUTO_RUN_MAX_RETRIES_PER_ROUND + 1, options.resumeAttemptRun)
  257. : 1;
  258. let continueCurrentOnFirstAttempt = initialMode === 'continue';
  259. let forceFreshTabsNextRun = false;
  260. let stoppedEarly = false;
  261. let parkedByTimer = false;
  262. const roundSummaries = buildAutoRunRoundSummaries(totalRuns, options.resumeRoundSummaries);
  263. if (continueCurrentOnFirstAttempt && resumeCurrentRun > 1) {
  264. for (let round = 1; round < resumeCurrentRun; round += 1) {
  265. const summary = roundSummaries[round - 1];
  266. if (summary.status === 'pending') {
  267. summary.status = 'success';
  268. if (!summary.attempts) {
  269. summary.attempts = 1;
  270. }
  271. }
  272. }
  273. }
  274. let successfulRuns = roundSummaries.filter((item) => item.status === 'success').length;
  275. const initialState = await getState();
  276. const initialPhase = continueCurrentOnFirstAttempt && getRunningSteps(initialState.stepStatuses).length
  277. ? 'waiting_step'
  278. : 'running';
  279. const showResumePosition = continueCurrentOnFirstAttempt || resumeCurrentRun > 1 || resumeAttemptRun > 1;
  280. await setState({
  281. autoRunSessionId: sessionId,
  282. autoRunSkipFailures,
  283. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  284. ...getAutoRunStatusPayload(initialPhase, {
  285. currentRun: showResumePosition ? resumeCurrentRun : 0,
  286. totalRuns,
  287. attemptRun: showResumePosition ? resumeAttemptRun : 0,
  288. sessionId,
  289. }),
  290. });
  291. for (let targetRun = resumeCurrentRun; targetRun <= totalRuns; targetRun += 1) {
  292. const roundSummary = roundSummaries[targetRun - 1];
  293. let roundRecordAppended = false;
  294. const resumingCurrentRound = continueCurrentOnFirstAttempt && targetRun === resumeCurrentRun;
  295. let attemptRun = resumingCurrentRound ? resumeAttemptRun : 1;
  296. let reuseExistingProgress = resumingCurrentRound;
  297. const maxAttemptsForRound = autoRunSkipFailures
  298. ? AUTO_RUN_MAX_RETRIES_PER_ROUND + 1
  299. : Math.max(1, attemptRun);
  300. while (attemptRun <= maxAttemptsForRound) {
  301. runtime.set({
  302. autoRunCurrentRun: targetRun,
  303. autoRunAttemptRun: attemptRun,
  304. });
  305. roundSummary.attempts = attemptRun;
  306. let startStep = 1;
  307. let useExistingProgress = false;
  308. if (reuseExistingProgress) {
  309. let currentState = await getState();
  310. if (getRunningSteps(currentState.stepStatuses).length) {
  311. currentState = await waitForRunningStepsToFinish({
  312. currentRun: targetRun,
  313. totalRuns,
  314. attemptRun,
  315. });
  316. }
  317. const resumeStep = getFirstUnfinishedStep(currentState.stepStatuses);
  318. if (resumeStep && hasSavedProgress(currentState.stepStatuses)) {
  319. startStep = resumeStep;
  320. useExistingProgress = true;
  321. } else if (hasSavedProgress(currentState.stepStatuses)) {
  322. await addLog('检测到当前流程已处理完成,本轮将改为从步骤 1 重新开始。', 'info');
  323. }
  324. }
  325. if (!useExistingProgress) {
  326. const prevState = await getState();
  327. const keepSettings = {
  328. vpsUrl: prevState.vpsUrl,
  329. vpsPassword: prevState.vpsPassword,
  330. customPassword: prevState.customPassword,
  331. autoRunSkipFailures: prevState.autoRunSkipFailures,
  332. autoRunFallbackThreadIntervalMinutes: prevState.autoRunFallbackThreadIntervalMinutes,
  333. autoRunDelayEnabled: prevState.autoRunDelayEnabled,
  334. autoRunDelayMinutes: prevState.autoRunDelayMinutes,
  335. autoStepDelaySeconds: prevState.autoStepDelaySeconds,
  336. mailProvider: prevState.mailProvider,
  337. emailGenerator: prevState.emailGenerator,
  338. gmailBaseEmail: prevState.gmailBaseEmail,
  339. mail2925BaseEmail: prevState.mail2925BaseEmail,
  340. emailPrefix: prevState.emailPrefix,
  341. inbucketHost: prevState.inbucketHost,
  342. inbucketMailbox: prevState.inbucketMailbox,
  343. cloudflareDomain: prevState.cloudflareDomain,
  344. cloudflareDomains: prevState.cloudflareDomains,
  345. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  346. autoRunSessionId: sessionId,
  347. tabRegistry: {},
  348. sourceLastUrls: {},
  349. ...getAutoRunStatusPayload('running', { currentRun: targetRun, totalRuns, attemptRun, sessionId }),
  350. };
  351. await resetState();
  352. await setState(keepSettings);
  353. deps.chrome.runtime.sendMessage({ type: 'AUTO_RUN_RESET' }).catch(() => { });
  354. await sleepWithStop(500);
  355. } else {
  356. await setState({
  357. autoRunSessionId: sessionId,
  358. autoRunSkipFailures,
  359. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  360. ...getAutoRunStatusPayload('running', { currentRun: targetRun, totalRuns, attemptRun, sessionId }),
  361. });
  362. }
  363. if (forceFreshTabsNextRun) {
  364. await addLog(`上一轮尝试已放弃,当前开始第 ${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试。`, 'warn');
  365. forceFreshTabsNextRun = false;
  366. }
  367. const appendRoundRecordIfNeeded = async (status, reason = '') => {
  368. if (roundRecordAppended) {
  369. return;
  370. }
  371. if (typeof appendAccountRunRecord !== 'function') {
  372. return;
  373. }
  374. const record = await appendAccountRunRecord(status, null, reason);
  375. if (record) {
  376. roundRecordAppended = true;
  377. }
  378. };
  379. try {
  380. throwIfAutoRunSessionStopped(sessionId);
  381. await broadcastAutoRunStatus('running', {
  382. currentRun: targetRun,
  383. totalRuns,
  384. attemptRun,
  385. sessionId,
  386. });
  387. await runAutoSequenceFromStep(startStep, {
  388. targetRun,
  389. totalRuns,
  390. attemptRuns: attemptRun,
  391. continued: useExistingProgress,
  392. });
  393. roundSummary.status = 'success';
  394. roundSummary.finalFailureReason = '';
  395. successfulRuns += 1;
  396. await setState({
  397. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  398. });
  399. await addLog(`=== 第 ${targetRun}/${totalRuns} 轮完成(第 ${attemptRun} 次尝试成功)===`, 'ok');
  400. break;
  401. } catch (err) {
  402. if (isStopError(err)) {
  403. stoppedEarly = true;
  404. await appendRoundRecordIfNeeded('stopped', getErrorMessage(err));
  405. await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
  406. await broadcastAutoRunStatus('stopped', {
  407. currentRun: targetRun,
  408. totalRuns,
  409. attemptRun,
  410. sessionId: 0,
  411. });
  412. break;
  413. }
  414. const reason = getErrorMessage(err);
  415. roundSummary.failureReasons.push(reason);
  416. const canRetry = autoRunSkipFailures && attemptRun < maxAttemptsForRound;
  417. await setState({
  418. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  419. });
  420. if (canRetry) {
  421. const retryIndex = attemptRun;
  422. if (isRestartCurrentAttemptError(err)) {
  423. await addLog(`第 ${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试需要整轮重开:${reason}`, 'warn');
  424. } else {
  425. await addLog(`第 ${targetRun}/${totalRuns} 轮第 ${attemptRun} 次尝试失败:${reason}`, 'error');
  426. }
  427. cancelPendingCommands('当前尝试已放弃。');
  428. await broadcastStopToContentScripts();
  429. await broadcastAutoRunStatus('retrying', {
  430. currentRun: targetRun,
  431. totalRuns,
  432. attemptRun,
  433. sessionId,
  434. });
  435. forceFreshTabsNextRun = true;
  436. await addLog(
  437. `自动重试:${Math.round(AUTO_RUN_RETRY_DELAY_MS / 1000)} 秒后开始第 ${targetRun}/${totalRuns} 轮第 ${attemptRun + 1} 次尝试(第 ${retryIndex}/${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试)。`,
  438. 'warn'
  439. );
  440. try {
  441. await sleepWithStop(AUTO_RUN_RETRY_DELAY_MS);
  442. } catch (sleepError) {
  443. if (isStopError(sleepError)) {
  444. stoppedEarly = true;
  445. await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError));
  446. await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
  447. await broadcastAutoRunStatus('stopped', {
  448. currentRun: targetRun,
  449. totalRuns,
  450. attemptRun,
  451. sessionId: 0,
  452. });
  453. break;
  454. }
  455. throw sleepError;
  456. }
  457. try {
  458. const parkedForRetry = await waitBeforeAutoRunRetry(targetRun, totalRuns, attemptRun + 1, {
  459. autoRunSkipFailures,
  460. roundSummaries,
  461. });
  462. if (parkedForRetry) {
  463. parkedByTimer = true;
  464. break;
  465. }
  466. } catch (sleepError) {
  467. if (isStopError(sleepError)) {
  468. stoppedEarly = true;
  469. await appendRoundRecordIfNeeded('stopped', getErrorMessage(sleepError));
  470. await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
  471. await broadcastAutoRunStatus('stopped', {
  472. currentRun: targetRun,
  473. totalRuns,
  474. attemptRun,
  475. sessionId: 0,
  476. });
  477. break;
  478. }
  479. throw sleepError;
  480. }
  481. attemptRun += 1;
  482. reuseExistingProgress = false;
  483. continue;
  484. }
  485. roundSummary.status = 'failed';
  486. roundSummary.finalFailureReason = reason;
  487. await setState({
  488. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  489. });
  490. await appendRoundRecordIfNeeded('failed', reason);
  491. if (!autoRunSkipFailures) {
  492. cancelPendingCommands('当前轮执行失败。');
  493. await broadcastStopToContentScripts();
  494. await addLog('自动重试未开启,自动运行将在当前失败后停止。', 'warn');
  495. stoppedEarly = true;
  496. await broadcastAutoRunStatus('stopped', {
  497. currentRun: targetRun,
  498. totalRuns,
  499. attemptRun,
  500. sessionId: 0,
  501. });
  502. break;
  503. }
  504. await addLog(`第 ${targetRun}/${totalRuns} 轮最终失败:${reason}`, 'error');
  505. await addLog(
  506. targetRun < totalRuns
  507. ? `第 ${targetRun}/${totalRuns} 轮已达到 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试上限,继续下一轮。`
  508. : `第 ${targetRun}/${totalRuns} 轮已达到 ${AUTO_RUN_MAX_RETRIES_PER_ROUND} 次重试上限,本次自动运行结束。`,
  509. 'warn'
  510. );
  511. cancelPendingCommands('当前轮已达到重试上限。');
  512. await broadcastStopToContentScripts();
  513. forceFreshTabsNextRun = true;
  514. break;
  515. } finally {
  516. reuseExistingProgress = false;
  517. continueCurrentOnFirstAttempt = false;
  518. }
  519. }
  520. if (stoppedEarly || parkedByTimer) {
  521. break;
  522. }
  523. try {
  524. const parkedForNextRound = await waitBetweenAutoRunRounds(targetRun, totalRuns, roundSummary, {
  525. autoRunSkipFailures,
  526. roundSummaries,
  527. });
  528. if (parkedForNextRound) {
  529. parkedByTimer = true;
  530. break;
  531. }
  532. } catch (sleepError) {
  533. if (isStopError(sleepError)) {
  534. stoppedEarly = true;
  535. await addLog(`第 ${targetRun}/${totalRuns} 轮已被用户停止`, 'warn');
  536. await broadcastAutoRunStatus('stopped', {
  537. currentRun: targetRun,
  538. totalRuns,
  539. attemptRun: runtime.get().autoRunAttemptRun,
  540. sessionId: 0,
  541. });
  542. break;
  543. }
  544. throw sleepError;
  545. }
  546. }
  547. if (parkedByTimer) {
  548. runtime.set({ autoRunActive: false });
  549. clearStopRequest();
  550. return;
  551. }
  552. await setState({
  553. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  554. });
  555. await logAutoRunFinalSummary(totalRuns, roundSummaries);
  556. const finalRuntime = runtime.get();
  557. if (deps.getStopRequested() || stoppedEarly) {
  558. await addLog(`=== 已停止,完成 ${successfulRuns}/${finalRuntime.autoRunTotalRuns} 轮 ===`, 'warn');
  559. await broadcastAutoRunStatus('stopped', {
  560. currentRun: finalRuntime.autoRunCurrentRun,
  561. totalRuns: finalRuntime.autoRunTotalRuns,
  562. attemptRun: finalRuntime.autoRunAttemptRun,
  563. sessionId: 0,
  564. });
  565. } else {
  566. await addLog(`=== 全部 ${finalRuntime.autoRunTotalRuns} 轮已执行完成,成功 ${successfulRuns} 轮 ===`, 'ok');
  567. await broadcastAutoRunStatus('complete', {
  568. currentRun: finalRuntime.autoRunTotalRuns,
  569. totalRuns: finalRuntime.autoRunTotalRuns,
  570. attemptRun: finalRuntime.autoRunAttemptRun,
  571. sessionId: 0,
  572. });
  573. }
  574. runtime.set({ autoRunActive: false, autoRunSessionId: 0 });
  575. const afterRuntime = runtime.get();
  576. await setState({
  577. autoRunSessionId: 0,
  578. autoRunRoundSummaries: serializeAutoRunRoundSummaries(totalRuns, roundSummaries),
  579. autoRunTimerPlan: null,
  580. scheduledAutoRunPlan: null,
  581. ...getAutoRunStatusPayload(deps.getStopRequested() || stoppedEarly ? 'stopped' : 'complete', {
  582. currentRun: deps.getStopRequested() || stoppedEarly ? afterRuntime.autoRunCurrentRun : afterRuntime.autoRunTotalRuns,
  583. totalRuns: afterRuntime.autoRunTotalRuns,
  584. attemptRun: afterRuntime.autoRunAttemptRun,
  585. sessionId: 0,
  586. }),
  587. });
  588. clearStopRequest();
  589. }
  590. return {
  591. autoRunLoop,
  592. buildAutoRunRoundSummaries,
  593. createAutoRunRoundSummary,
  594. formatAutoRunFailureReasons,
  595. getAutoRunRoundRetryCount,
  596. handleAutoRunLoopUnhandledError,
  597. logAutoRunFinalSummary,
  598. normalizeAutoRunRoundSummary,
  599. serializeAutoRunRoundSummaries,
  600. skipAutoRunCountdown,
  601. startAutoRunLoop,
  602. waitBetweenAutoRunRounds,
  603. waitBeforeAutoRunRetry,
  604. };
  605. }
  606. return {
  607. createAutoRunController,
  608. };
  609. });