Преглед изворни кода

feat: 添加账号运行历史功能,支持独立配置与文本留档

QLHazyCoder пре 4 месеци
родитељ
комит
17bbfc6394

+ 25 - 3
background.js

@@ -151,6 +151,7 @@ const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
 const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
 const DEFAULT_HOTMAIL_REMOTE_BASE_URL = '';
 const DEFAULT_HOTMAIL_LOCAL_BASE_URL = 'http://127.0.0.1:17373';
+const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = DEFAULT_HOTMAIL_LOCAL_BASE_URL;
 const HOTMAIL_LOCAL_HELPER_TIMEOUT_MS = 45000;
 const DEFAULT_LUCKMAIL_PROJECT_CODE = 'openai';
 const DISPLAY_TIMEZONE = 'Asia/Shanghai';
@@ -212,6 +213,8 @@ const PERSISTED_SETTING_DEFAULTS = {
   emailGenerator: 'duck',
   autoDeleteUsedIcloudAlias: false,
   icloudHostPreference: 'auto',
+  accountRunHistoryTextEnabled: false,
+  accountRunHistoryHelperBaseUrl: DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL,
   emailPrefix: '',
   inbucketHost: '',
   inbucketMailbox: '',
@@ -677,6 +680,23 @@ function normalizeHotmailLocalBaseUrl(rawValue = '') {
   }
 }
 
+function normalizeAccountRunHistoryHelperBaseUrl(rawValue = '') {
+  const value = String(rawValue || '').trim();
+  if (!value) return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL;
+
+  try {
+    const parsed = new URL(value);
+    if (parsed.pathname === '/append-account-log') {
+      parsed.pathname = '';
+      parsed.search = '';
+      parsed.hash = '';
+    }
+    return normalizeHotmailLocalBaseUrl(parsed.toString());
+  } catch {
+    return normalizeHotmailLocalBaseUrl(value);
+  }
+}
+
 function getHotmailServiceSettings(state = {}) {
   return {
     mode: normalizeHotmailServiceMode(state.hotmailServiceMode),
@@ -754,9 +774,12 @@ function normalizePersistentSettingValue(key, value) {
     case 'emailGenerator':
       return normalizeEmailGenerator(value);
     case 'autoDeleteUsedIcloudAlias':
+    case 'accountRunHistoryTextEnabled':
       return Boolean(value);
     case 'icloudHostPreference':
       return normalizeIcloudHost(value) || 'auto';
+    case 'accountRunHistoryHelperBaseUrl':
+      return normalizeAccountRunHistoryHelperBaseUrl(value);
     case 'emailPrefix':
       return String(value || '').trim();
     case 'inbucketHost':
@@ -4756,12 +4779,11 @@ const AUTO_STEP_DELAYS = {
 const accountRunHistoryHelpers = self.MultiPageBackgroundAccountRunHistory?.createAccountRunHistoryHelpers({
   ACCOUNT_RUN_HISTORY_STORAGE_KEY,
   addLog,
-  buildHotmailLocalEndpoint,
+  buildLocalHelperEndpoint: (baseUrl, path) => buildHotmailLocalEndpoint(baseUrl, path),
   chrome,
   getErrorMessage,
   getState,
-  HOTMAIL_SERVICE_MODE_LOCAL,
-  normalizeHotmailLocalBaseUrl,
+  normalizeAccountRunHistoryHelperBaseUrl,
 });
 
 async function broadcastAccountRunHistoryUpdate() {

+ 6 - 8
background/account-run-history.js

@@ -5,12 +5,11 @@
     const {
       ACCOUNT_RUN_HISTORY_STORAGE_KEY = 'accountRunHistory',
       addLog,
-      buildHotmailLocalEndpoint,
+      buildLocalHelperEndpoint,
       chrome,
       getErrorMessage,
       getState,
-      HOTMAIL_SERVICE_MODE_LOCAL = 'local',
-      normalizeHotmailLocalBaseUrl,
+      normalizeAccountRunHistoryHelperBaseUrl,
     } = deps;
 
     function normalizeAccountRunHistory(records) {
@@ -75,12 +74,11 @@
     }
 
     function shouldAppendAccountRunTextFile(state = {}) {
-      const serviceMode = String(state.hotmailServiceMode || '').trim().toLowerCase();
-      if (serviceMode !== HOTMAIL_SERVICE_MODE_LOCAL) {
+      if (!Boolean(state.accountRunHistoryTextEnabled)) {
         return false;
       }
 
-      const helperBaseUrl = normalizeHotmailLocalBaseUrl(state.hotmailLocalBaseUrl);
+      const helperBaseUrl = normalizeAccountRunHistoryHelperBaseUrl(state.accountRunHistoryHelperBaseUrl);
       return Boolean(helperBaseUrl);
     }
 
@@ -97,10 +95,10 @@
         return null;
       }
 
-      const helperBaseUrl = normalizeHotmailLocalBaseUrl(state.hotmailLocalBaseUrl);
+      const helperBaseUrl = normalizeAccountRunHistoryHelperBaseUrl(state.accountRunHistoryHelperBaseUrl);
       let response;
       try {
-        response = await fetch(buildHotmailLocalEndpoint(helperBaseUrl, '/append-account-log'), {
+        response = await fetch(buildLocalHelperEndpoint(helperBaseUrl, '/append-account-log'), {
           method: 'POST',
           headers: {
             'Content-Type': 'application/json',

+ 16 - 0
sidepanel/sidepanel.html

@@ -288,6 +288,22 @@
           </div>
         </div>
       </div>
+      <div class="data-row">
+        <span class="data-label">本地留档</span>
+        <div class="data-inline">
+          <label class="toggle-switch" for="input-account-run-history-text-enabled">
+            <input type="checkbox" id="input-account-run-history-text-enabled" />
+            <span class="toggle-switch-track" aria-hidden="true">
+              <span class="toggle-switch-thumb"></span>
+            </span>
+          </label>
+          <span class="data-value">同步写入账号运行历史 txt</span>
+        </div>
+      </div>
+      <div class="data-row" id="row-account-run-history-helper-base-url" style="display:none;">
+        <span class="data-label">留档服务</span>
+        <input type="text" id="input-account-run-history-helper-base-url" class="data-input mono" placeholder="http://127.0.0.1:17373" />
+      </div>
       <div class="data-row">
       <span class="data-label">OAuth</span>
       <span id="display-oauth-url" class="data-value mono truncate">等待中...</span>

+ 68 - 0
sidepanel/sidepanel.js

@@ -164,6 +164,9 @@ const inputAutoSkipFailuresThreadIntervalMinutes = document.getElementById('inpu
 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 inputAccountRunHistoryTextEnabled = document.getElementById('input-account-run-history-text-enabled');
+const rowAccountRunHistoryHelperBaseUrl = document.getElementById('row-account-run-history-helper-base-url');
+const inputAccountRunHistoryHelperBaseUrl = document.getElementById('input-account-run-history-helper-base-url');
 const autoStartModal = document.getElementById('auto-start-modal');
 const autoStartTitle = autoStartModal?.querySelector('.modal-title');
 const autoStartMessage = document.getElementById('auto-start-message');
@@ -207,6 +210,7 @@ const LUCKMAIL_PROVIDER = 'luckmail-api';
 const DEFAULT_LUCKMAIL_BASE_URL = 'https://mails.luckyous.com';
 const DEFAULT_LUCKMAIL_EMAIL_TYPE = 'ms_graph';
 const DISPLAY_TIMEZONE = 'Asia/Shanghai';
+const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = 'http://127.0.0.1:17373';
 
 let latestState = null;
 let currentAutoRun = {
@@ -1140,6 +1144,8 @@ function collectSettingsPayload() {
     emailGenerator: selectEmailGenerator.value,
     autoDeleteUsedIcloudAlias: checkboxAutoDeleteIcloud?.checked,
     icloudHostPreference: selectIcloudHostPreference?.value || 'auto',
+    accountRunHistoryTextEnabled: Boolean(inputAccountRunHistoryTextEnabled?.checked),
+    accountRunHistoryHelperBaseUrl: normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl?.value),
     emailPrefix: inputEmailPrefix.value.trim(),
     inbucketHost: inputInbucketHost.value.trim(),
     inbucketMailbox: inputInbucketMailbox.value.trim(),
@@ -1193,6 +1199,30 @@ function normalizeHotmailServiceMode(value = '') {
     : HOTMAIL_SERVICE_MODE_LOCAL;
 }
 
+function normalizeAccountRunHistoryHelperBaseUrlValue(value = '') {
+  const trimmed = String(value || '').trim();
+  if (!trimmed) {
+    return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL;
+  }
+
+  try {
+    const parsed = new URL(trimmed);
+    if (!['http:', 'https:'].includes(parsed.protocol)) {
+      return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL;
+    }
+
+    if (parsed.pathname === '/append-account-log') {
+      parsed.pathname = '';
+      parsed.search = '';
+      parsed.hash = '';
+    }
+
+    return parsed.toString().replace(/\/$/, '');
+  } catch {
+    return DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL;
+  }
+}
+
 function getSelectedLocalCpaStep9Mode() {
   const activeButton = localCpaStep9ModeButtons.find((button) => button.classList.contains('is-active'));
   return normalizeLocalCpaStep9Mode(activeButton?.dataset.localCpaStep9Mode);
@@ -1251,6 +1281,14 @@ function setHotmailServiceMode(mode) {
   });
 }
 
+function updateAccountRunHistorySettingsUI() {
+  if (!rowAccountRunHistoryHelperBaseUrl || !inputAccountRunHistoryTextEnabled) {
+    return;
+  }
+
+  rowAccountRunHistoryHelperBaseUrl.style.display = inputAccountRunHistoryTextEnabled.checked ? '' : 'none';
+}
+
 function setSettingsCardLocked(locked) {
   if (!settingsCard) {
     return;
@@ -1518,6 +1556,12 @@ function applySettingsState(state) {
   if (checkboxAutoDeleteIcloud) {
     checkboxAutoDeleteIcloud.checked = Boolean(state?.autoDeleteUsedIcloudAlias);
   }
+  if (inputAccountRunHistoryTextEnabled) {
+    inputAccountRunHistoryTextEnabled.checked = Boolean(state?.accountRunHistoryTextEnabled);
+  }
+  if (inputAccountRunHistoryHelperBaseUrl) {
+    inputAccountRunHistoryHelperBaseUrl.value = normalizeAccountRunHistoryHelperBaseUrlValue(state?.accountRunHistoryHelperBaseUrl);
+  }
   inputEmailPrefix.value = state?.emailPrefix || '';
   inputInbucketHost.value = state?.inbucketHost || '';
   inputInbucketMailbox.value = state?.inbucketMailbox || '';
@@ -1549,6 +1593,7 @@ function applySettingsState(state) {
   markSettingsDirty(false);
   updateAutoDelayInputState();
   updateFallbackThreadIntervalInputState();
+  updateAccountRunHistorySettingsUI();
   updatePanelModeUI();
   updateMailProviderUI();
   if (isLuckmailProvider(state?.mailProvider)) {
@@ -3758,6 +3803,22 @@ inputAutoDelayMinutes.addEventListener('blur', () => {
   saveSettings({ silent: true }).catch(() => { });
 });
 
+inputAccountRunHistoryTextEnabled?.addEventListener('change', () => {
+  updateAccountRunHistorySettingsUI();
+  markSettingsDirty(true);
+  saveSettings({ silent: true }).catch(() => { });
+});
+
+inputAccountRunHistoryHelperBaseUrl?.addEventListener('input', () => {
+  markSettingsDirty(true);
+  scheduleSettingsAutoSave();
+});
+
+inputAccountRunHistoryHelperBaseUrl?.addEventListener('blur', () => {
+  inputAccountRunHistoryHelperBaseUrl.value = normalizeAccountRunHistoryHelperBaseUrlValue(inputAccountRunHistoryHelperBaseUrl.value);
+  saveSettings({ silent: true }).catch(() => { });
+});
+
 function syncAutoStepDelayInputs() {
   inputAutoStepDelaySeconds.value = formatAutoStepDelayInputValue(inputAutoStepDelaySeconds.value);
 }
@@ -3938,6 +3999,13 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
       if (message.payload.autoDeleteUsedIcloudAlias !== undefined && checkboxAutoDeleteIcloud) {
         checkboxAutoDeleteIcloud.checked = Boolean(message.payload.autoDeleteUsedIcloudAlias);
       }
+      if (message.payload.accountRunHistoryTextEnabled !== undefined && inputAccountRunHistoryTextEnabled) {
+        inputAccountRunHistoryTextEnabled.checked = Boolean(message.payload.accountRunHistoryTextEnabled);
+        updateAccountRunHistorySettingsUI();
+      }
+      if (message.payload.accountRunHistoryHelperBaseUrl !== undefined && inputAccountRunHistoryHelperBaseUrl) {
+        inputAccountRunHistoryHelperBaseUrl.value = normalizeAccountRunHistoryHelperBaseUrlValue(message.payload.accountRunHistoryHelperBaseUrl);
+      }
       if (message.payload.icloudHostPreference !== undefined && selectIcloudHostPreference) {
         const hostPreference = String(message.payload.icloudHostPreference || '').trim().toLowerCase();
         selectIcloudHostPreference.value = hostPreference === 'icloud.com'

+ 103 - 0
tests/background-account-history-settings.test.js

@@ -0,0 +1,103 @@
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+
+const source = fs.readFileSync('background.js', 'utf8');
+
+function extractFunction(name) {
+  const markers = [`async function ${name}(`, `function ${name}(`];
+  const start = markers
+    .map((marker) => source.indexOf(marker))
+    .find((index) => index >= 0);
+  if (start < 0) {
+    throw new Error(`missing function ${name}`);
+  }
+
+  let parenDepth = 0;
+  let signatureEnded = false;
+  let braceStart = -1;
+  for (let i = start; i < source.length; i += 1) {
+    const ch = source[i];
+    if (ch === '(') {
+      parenDepth += 1;
+    } else if (ch === ')') {
+      parenDepth -= 1;
+      if (parenDepth === 0) {
+        signatureEnded = true;
+      }
+    } else if (ch === '{' && signatureEnded) {
+      braceStart = i;
+      break;
+    }
+  }
+
+  let depth = 0;
+  let end = braceStart;
+  for (; end < source.length; end += 1) {
+    const ch = source[end];
+    if (ch === '{') depth += 1;
+    if (ch === '}') {
+      depth -= 1;
+      if (depth === 0) {
+        end += 1;
+        break;
+      }
+    }
+  }
+
+  return source.slice(start, end);
+}
+
+test('background account history settings are normalized independently from hotmail service mode', () => {
+  const bundle = [
+    extractFunction('normalizeHotmailLocalBaseUrl'),
+    extractFunction('normalizeAccountRunHistoryHelperBaseUrl'),
+    extractFunction('normalizePersistentSettingValue'),
+  ].join('\n');
+
+  const api = new Function(`
+const DEFAULT_HOTMAIL_LOCAL_BASE_URL = 'http://127.0.0.1:17373';
+const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = DEFAULT_HOTMAIL_LOCAL_BASE_URL;
+const DEFAULT_HOTMAIL_REMOTE_BASE_URL = '';
+const HOTMAIL_SERVICE_MODE_REMOTE = 'remote';
+const HOTMAIL_SERVICE_MODE_LOCAL = 'local';
+const PERSISTED_SETTING_DEFAULTS = {
+  autoStepDelaySeconds: null,
+  mailProvider: '163',
+};
+function normalizePanelMode(value) { return value === 'sub2api' ? 'sub2api' : 'cpa'; }
+function normalizeLocalCpaStep9Mode(value) { return value === 'bypass' ? 'bypass' : 'submit'; }
+function normalizeCpaCallbackMode(value) { return value === 'step6' ? 'step6' : 'step8'; }
+function normalizeAutoRunFallbackThreadIntervalMinutes(value) { return Number(value) || 0; }
+function normalizeAutoRunDelayMinutes(value) { return Number(value) || 30; }
+function normalizeAutoStepDelaySeconds(value) { return value == null || value === '' ? null : Number(value); }
+function normalizeMailProvider(value) { return String(value || '').trim().toLowerCase() || '163'; }
+function normalizeMail2925Mode(value) { return String(value || '').trim().toLowerCase() === 'receive' ? 'receive' : 'provide'; }
+function normalizeEmailGenerator(value) { return String(value || '').trim().toLowerCase() || 'duck'; }
+function normalizeIcloudHost(value) { const normalized = String(value || '').trim().toLowerCase(); return normalized === 'icloud.com' || normalized === 'icloud.com.cn' ? normalized : ''; }
+function normalizeHotmailServiceMode(value) { return String(value || '').trim().toLowerCase() === 'remote' ? 'remote' : 'local'; }
+function normalizeHotmailRemoteBaseUrl(value) { return String(value || '').trim(); }
+function normalizeCloudflareDomain(value) { return String(value || '').trim(); }
+function normalizeCloudflareDomains(value) { return Array.isArray(value) ? value : []; }
+function normalizeCloudflareTempEmailBaseUrl(value) { return String(value || '').trim(); }
+function normalizeCloudflareTempEmailReceiveMailbox(value) { return String(value || '').trim().toLowerCase(); }
+function normalizeCloudflareTempEmailDomain(value) { return String(value || '').trim(); }
+function normalizeCloudflareTempEmailDomains(value) { return Array.isArray(value) ? value : []; }
+function normalizeHotmailAccounts(value) { return Array.isArray(value) ? value : []; }
+${bundle}
+return {
+  normalizeAccountRunHistoryHelperBaseUrl,
+  normalizePersistentSettingValue,
+};
+  `)();
+
+  assert.equal(api.normalizePersistentSettingValue('accountRunHistoryTextEnabled', 1), true);
+  assert.equal(
+    api.normalizePersistentSettingValue('accountRunHistoryHelperBaseUrl', 'http://127.0.0.1:17373/append-account-log'),
+    'http://127.0.0.1:17373'
+  );
+  assert.equal(
+    api.normalizeAccountRunHistoryHelperBaseUrl(''),
+    'http://127.0.0.1:17373'
+  );
+});

+ 6 - 6
tests/background-account-run-history-module.test.js

@@ -31,7 +31,7 @@ test('account run history helper normalizes records and persists without helper
   const helpers = api.createAccountRunHistoryHelpers({
     ACCOUNT_RUN_HISTORY_STORAGE_KEY: 'accountRunHistory',
     addLog: async () => {},
-    buildHotmailLocalEndpoint: (baseUrl, path) => `${baseUrl}${path}`,
+    buildLocalHelperEndpoint: (baseUrl, path) => `${baseUrl}${path}`,
     chrome: {
       storage: {
         local: {
@@ -46,11 +46,10 @@ test('account run history helper normalizes records and persists without helper
     getState: async () => ({
       email: ' latest@example.com ',
       password: ' secret ',
-      hotmailServiceMode: 'remote',
-      hotmailLocalBaseUrl: '',
+      accountRunHistoryTextEnabled: false,
+      accountRunHistoryHelperBaseUrl: '',
     }),
-    HOTMAIL_SERVICE_MODE_LOCAL: 'local',
-    normalizeHotmailLocalBaseUrl: (value) => String(value || '').trim(),
+    normalizeAccountRunHistoryHelperBaseUrl: (value) => String(value || '').trim(),
   });
 
   const record = helpers.buildAccountRunHistoryRecord(
@@ -72,5 +71,6 @@ test('account run history helper normalizes records and persists without helper
   assert.equal(storedHistory.length, 2);
   assert.equal(storedHistory[1].reason, 'boom');
   assert.equal(fetchCalled, false);
-  assert.equal(helpers.shouldAppendAccountRunTextFile({ hotmailServiceMode: 'remote', hotmailLocalBaseUrl: 'http://127.0.0.1:17373' }), false);
+  assert.equal(helpers.shouldAppendAccountRunTextFile({ accountRunHistoryTextEnabled: false, accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373' }), false);
+  assert.equal(helpers.shouldAppendAccountRunTextFile({ accountRunHistoryTextEnabled: true, accountRunHistoryHelperBaseUrl: 'http://127.0.0.1:17373' }), true);
 });

+ 12 - 1
tests/sidepanel-account-run-history.test.js

@@ -55,16 +55,23 @@ test('sidepanel html contains account run history strip under log header', () =>
   assert.match(html, /id="account-run-history-meta"/);
   assert.match(html, /id="account-run-history-stats"/);
   assert.match(html, /id="account-run-history-list"/);
+  assert.match(html, /id="input-account-run-history-text-enabled"/);
+  assert.match(html, /id="input-account-run-history-helper-base-url"/);
 });
 
 test('sidepanel account run history helpers classify statuses and summarize counts', () => {
   const bundle = [
+    extractFunction('normalizeAccountRunHistoryHelperBaseUrlValue'),
     extractFunction('parseAccountRunStatus'),
     extractFunction('summarizeAccountRunHistory'),
     extractFunction('buildAccountRunHistoryDetailText'),
   ].join('\n');
 
-  const api = new Function(`${bundle}; return { parseAccountRunStatus, summarizeAccountRunHistory, buildAccountRunHistoryDetailText };`)();
+  const api = new Function(`
+const DEFAULT_ACCOUNT_RUN_HISTORY_HELPER_BASE_URL = 'http://127.0.0.1:17373';
+${bundle}
+return { normalizeAccountRunHistoryHelperBaseUrlValue, parseAccountRunStatus, summarizeAccountRunHistory, buildAccountRunHistoryDetailText };
+`)();
 
   assert.deepStrictEqual(api.parseAccountRunStatus('step7_failed'), {
     kind: 'failed',
@@ -104,4 +111,8 @@ test('sidepanel account run history helpers classify statuses and summarize coun
     api.buildAccountRunHistoryDetailText({ status: 'stopped', reason: '手动停止' }),
     '手动停止'
   );
+  assert.equal(
+    api.normalizeAccountRunHistoryHelperBaseUrlValue('http://127.0.0.1:17373/append-account-log'),
+    'http://127.0.0.1:17373'
+  );
 });

+ 6 - 2
项目完整链路说明.md

@@ -137,8 +137,10 @@
 - LuckMail API 配置
 - 自动运行默认配置
 - 账号运行历史 `accountRunHistory`
+- 账号运行历史 txt 留档开关与 helper 地址
 
-当启用了本地 Hotmail helper 时,账号运行历史还会通过 [scripts/hotmail_helper.py](c:/Users/projectf/Downloads/codex注册扩展/scripts/hotmail_helper.py) 追加写入 `data/account-run-history.txt` 文本文件,便于离线留档。
+当启用了独立的账号运行历史 txt 留档配置时,账号运行历史会通过 [scripts/hotmail_helper.py](c:/Users/projectf/Downloads/codex注册扩展/scripts/hotmail_helper.py) 追加写入 `data/account-run-history.txt` 文本文件,便于离线留档。
+这条配置链路独立于 `mailProvider` 和 Hotmail 的接码模式。
 
 ### 4.3 状态广播
 
@@ -328,6 +330,7 @@
 补充:
 
 - 本地 helper 除了收信与验证码读取,还提供账号运行历史文本追加接口。
+- 账号运行历史 txt 留档由独立配置控制,不再绑定 Hotmail 的本地助手模式。
 
 ### 7.3 LuckMail
 
@@ -395,7 +398,8 @@
 2. `normalizePersistentSettingValue`
 3. 导入导出逻辑
 4. sidepanel 表单与状态恢复
-5. 结构文档 / 开发规范是否需要更新
+5. 是否错误挂靠到无关 provider / manager 配置下
+6. 结构文档 / 开发规范是否需要更新
 
 ## 10. 文档联动规则
 

+ 3 - 1
项目开发规范(AI协作).md

@@ -90,7 +90,8 @@
 3. 导入导出
 4. state restore
 5. sidepanel UI
-6. 文档
+6. 是否挂在正确的职责域下
+7. 文档
 
 ## 4. 测试规范
 
@@ -152,6 +153,7 @@ bun test
 - 优先复用现有模块,不重复发明一套新流程
 - 共享逻辑先提公共层,再让步骤层调用
 - 代码新增后应尽量减少主文件体积,而不是只做“形式拆分”
+- 观测、留档、日志、导出这类横切能力必须优先挂在独立配置域下,不能借某个 provider 的业务模式开关隐式控制
 - 保留少量兼容型薄包装是允许的,但必须有明确目的:
   - 运行时装配
   - 测试迁移过渡

+ 7 - 6
项目文件结构说明.md

@@ -29,8 +29,8 @@
 - `microsoft-email.js`:Microsoft Graph / Outlook 邮件读取辅助模块,负责刷新令牌换 token、邮箱夹轮询和验证码提取。
 - `package.json`:仓库最小 Node 包配置,目前主要提供测试脚本定义。
 - `rules.json`:静态 DNR 规则,主要处理 iCloud 相关请求头。
-- `start-hotmail-helper.bat`:Windows 下启动本地 Hotmail helper 的脚本。
-- `start-hotmail-helper.command`:macOS 下启动本地 Hotmail helper 的脚本。
+- `start-hotmail-helper.bat`:Windows 下启动本地 helper 的脚本;当前既服务于 Hotmail 本地收信,也服务于账号运行历史文本留档
+- `start-hotmail-helper.command`:macOS 下启动本地 helper 的脚本;当前既服务于 Hotmail 本地收信,也服务于账号运行历史文本留档
 - `开发者AI开发与PR提交流程.md`:仓库现有的 AI 开发与 PR 提交流程说明。
 - `项目文件结构说明.md`:当前文件,维护整个仓库非忽略文件的结构与职责索引。
 - `项目完整链路说明.md`:面向 AI/开发者的完整功能链路说明,用于快速理解系统整体运行过程。
@@ -38,7 +38,7 @@
 
 ## `background/`
 
-- `background/account-run-history.js`:账号运行历史模块,负责将成功/失败/停止结果持久化到 `chrome.storage.local`,并在启用本地 Hotmail helper 时追加写入文本日志。
+- `background/account-run-history.js`:账号运行历史模块,负责将成功/失败/停止结果持久化到 `chrome.storage.local`,并在启用独立的本地 txt 留档配置后追加写入文本日志。
 - `background/auto-run-controller.js`:自动运行主控制器,封装多轮执行、重试、轮次摘要、线程间隔与倒计时恢复逻辑。
 - `background/generated-email-helpers.js`:生成邮箱辅助层,封装 Duck、Cloudflare、Cloudflare Temp Email、iCloud 隐私邮箱的获取逻辑。
 - `background/logging-status.js`:后台日志、步骤状态、错误信息和若干状态判断的公共工具层。
@@ -102,7 +102,7 @@
 
 ## `scripts/`
 
-- `scripts/hotmail_helper.py`:本地 Hotmail helper 服务,负责通过本地接口协助获取邮件和验证码,并可追加写入账号运行历史文本日志
+- `scripts/hotmail_helper.py`:本地 helper 服务,负责通过本地接口协助 Hotmail 获取邮件和验证码,并提供账号运行历史文本追加接口
 
 ## `sidepanel/`
 
@@ -111,7 +111,7 @@
 - `sidepanel/luckmail-manager.js`:侧边栏 LuckMail 管理器,负责邮箱列表、筛选、启停、保留与批量操作。
 - `sidepanel/sidepanel.css`:侧边栏样式文件。
 - `sidepanel/sidepanel.html`:侧边栏页面结构,当前步骤列表已改为动态容器,并在日志区标题下方预留账号运行历史摘要条带。
-- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、账号运行历史摘要渲染与广播接收。
+- `sidepanel/sidepanel.js`:侧边栏主入口脚本,负责 UI 状态同步、动态步骤渲染、按钮交互、账号运行历史摘要渲染、独立 txt 留档配置与广播接收。
 - `sidepanel/update-service.js`:侧边栏更新检查服务,负责 GitHub Releases 查询与版本展示。
 
 ## `tests/`
@@ -121,6 +121,7 @@
 - `tests/auto-step-random-delay.test.js`:测试自动运行步间延迟与旧配置键兼容解析。
 - `tests/background-auto-run-module.test.js`:测试自动运行控制器模块已接入且导出工厂。
 - `tests/background-account-run-history-module.test.js`:测试账号运行历史模块已接入、导出工厂,并能在不启用本地 helper 时正确持久化记录。
+- `tests/background-account-history-settings.test.js`:测试账号运行历史的独立配置项归一化,不再复用 Hotmail 模式作为启停条件。
 - `tests/background-generated-email-module.test.js`:测试生成邮箱辅助模块已接入且导出工厂。
 - `tests/background-icloud.test.js`:测试 iCloud 相关后台纯函数与别名收尾逻辑。
 - `tests/background-icloud-mail-provider.test.js`:测试 iCloud 邮箱 provider 配置解析。
@@ -146,7 +147,7 @@
 - `tests/luckmail-utils.test.js`:测试 LuckMail 工具层的购买记录、邮件、游标和验证码匹配逻辑。
 - `tests/microsoft-email.test.js`:测试 Microsoft 邮件拉取与验证码提取逻辑。
 - `tests/sidepanel-hotmail-manager.test.js`:测试侧边栏 Hotmail 管理器模块接线与空态渲染。
-- `tests/sidepanel-account-run-history.test.js`:测试侧边栏日志区下方的账号运行历史条带结构与状态分类摘要逻辑。
+- `tests/sidepanel-account-run-history.test.js`:测试侧边栏日志区下方的账号运行历史条带结构、独立留档配置控件与状态分类摘要逻辑。
 - `tests/sidepanel-icloud-manager.test.js`:测试侧边栏 iCloud 管理器模块接线与空态渲染。
 - `tests/sidepanel-icloud-provider.test.js`:测试侧边栏 iCloud 登录地址解析逻辑。
 - `tests/sidepanel-luckmail-manager.test.js`:测试侧边栏 LuckMail 管理器模块接线与空态渲染。