@gobing-ai/knowledge-kit 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +132 -25
  3. package/package.json +1 -1
  4. package/plugins/generations/content-gen/dist/index.js +4 -0
  5. package/plugins/generations/core-facts-gen/dist/index.js +4 -0
  6. package/plugins/generations/daily-article-gen/dist/index.js +4 -0
  7. package/plugins/generations/dailynews-gen/dist/index.js +4 -0
  8. package/plugins/generations/episode-plan-gen/dist/index.js +102 -1
  9. package/plugins/generations/episode-plan-gen/src/index.ts +2 -4
  10. package/plugins/generations/gemini-tts/dist/index.js +4 -0
  11. package/plugins/generations/image-gen/dist/index.js +4 -0
  12. package/plugins/generations/news-report-gen/dist/index.js +4 -0
  13. package/plugins/generations/voice-gen/dist/index.js +4 -0
  14. package/plugins/generations/voxcpm-tts/dist/index.js +4 -0
  15. package/plugins/ingestions/aihot-ingest/dist/index.js +4 -0
  16. package/plugins/ingestions/horizon-ingest/dist/index.js +4 -0
  17. package/plugins/ingestions/last30days-ingest/dist/index.js +11 -3
  18. package/plugins/ingestions/last30days-ingest/src/index.ts +14 -3
  19. package/plugins/ingestions/web-search/dist/index.js +4 -0
  20. package/plugins/kk/plugin.json +1 -1
  21. package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
  22. package/plugins/kk/scripts/daily-stages.ts +1083 -49
  23. package/plugins/kk/scripts/kk-workflow-stages.ts +76 -10
  24. package/plugins/kk/scripts/safety-artifact-check.ts +235 -0
  25. package/plugins/kk/scripts/sentences.ts +134 -0
  26. package/plugins/kk/scripts/series-names.ts +124 -0
  27. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +194 -113
  28. package/plugins/kk/workflows/kk-daily-safety.yaml +160 -36
  29. package/plugins/publishings/bilibili-pub/dist/index.js +6 -1
  30. package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
  31. package/plugins/publishings/cnblogs-pub/dist/index.js +6 -1
  32. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
  33. package/plugins/publishings/csdn-pub/dist/index.js +6 -1
  34. package/plugins/publishings/csdn-pub/dist/profile-check.js +6 -1
  35. package/plugins/publishings/emdash-pub/dist/index.js +4 -0
  36. package/plugins/publishings/infoq-pub/dist/index.js +6 -1
  37. package/plugins/publishings/infoq-pub/dist/profile-check.js +6 -1
  38. package/plugins/publishings/jike-pub/dist/index.js +6 -1
  39. package/plugins/publishings/jike-pub/dist/profile-check.js +6 -1
  40. package/plugins/publishings/juejin-pub/dist/index.js +6 -1
  41. package/plugins/publishings/juejin-pub/dist/profile-check.js +6 -1
  42. package/plugins/publishings/medium-pub/dist/index.js +4 -0
  43. package/plugins/publishings/podcast-pub/dist/index.js +151 -16
  44. package/plugins/publishings/podcast-pub/src/index.ts +62 -5
  45. package/plugins/publishings/podcast-pub/src/seo.ts +2 -4
  46. package/plugins/publishings/podcast-pub/src/show-notes.ts +3 -8
  47. package/plugins/publishings/qiita-pub/dist/index.js +4 -0
  48. package/plugins/publishings/shipinhao-pub/dist/index.js +6 -1
  49. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
  50. package/plugins/publishings/sspai-pub/dist/index.js +6 -1
  51. package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
  52. package/plugins/publishings/substack-pub/dist/index.js +6 -1
  53. package/plugins/publishings/substack-pub/dist/profile-check.js +6 -1
  54. package/plugins/publishings/surfdash-pub/dist/index.js +691 -63
  55. package/plugins/publishings/surfdash-pub/src/digest-check.ts +284 -0
  56. package/plugins/publishings/surfdash-pub/src/index.ts +656 -84
  57. package/plugins/publishings/surfdash-pub/src/patch.ts +44 -7
  58. package/plugins/publishings/threads-pub/dist/index.js +4 -0
  59. package/plugins/publishings/wechatmp-pub/dist/index.js +937 -98
  60. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +64 -3
  61. package/plugins/publishings/wechatmp-pub/src/flow.ts +1125 -85
  62. package/plugins/publishings/wechatmp-pub/src/index.ts +112 -30
  63. package/plugins/publishings/wechatmp-pub/src/page.ts +105 -0
  64. package/plugins/publishings/weibo-pub/dist/index.js +6 -1
  65. package/plugins/publishings/weibo-pub/dist/profile-check.js +6 -1
  66. package/plugins/publishings/x-pub/dist/index.js +6 -1
  67. package/plugins/publishings/x-pub/dist/profile-check.js +6 -1
  68. package/plugins/publishings/xhs-pub/dist/index.js +6 -1
  69. package/plugins/publishings/xhs-pub/dist/profile-check.js +6 -1
  70. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +6 -1
  71. package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +2 -1
  72. package/plugins/publishings/zenn-pub/dist/index.js +4 -0
@@ -19829,6 +19829,7 @@ var init_runtime_node_bun = __esm(() => {
19829
19829
  });
19830
19830
 
19831
19831
  // ../../plugins/publishings/wechatmp-pub/src/index.ts
19832
+ import { randomUUID as randomUUID2 } from "crypto";
19832
19833
  import { parseArgs } from "util";
19833
19834
 
19834
19835
  // ../../packages/kk-core/src/content-limits.ts
@@ -20162,6 +20163,10 @@ init_runtime_node_bun();
20162
20163
 
20163
20164
  // ../../packages/kk-core/src/render-md.ts
20164
20165
  if (false) {}
20166
+ // ../../packages/kk-core/src/sentences.ts
20167
+ var CJK_TERMINATORS = new Set(["\u3002", "\uFF01", "\uFF1F"]);
20168
+ var LATIN_TERMINATORS = new Set([".", "!", "?"]);
20169
+ var ATTACHED_CLOSERS = new Set(["\u201D", "\u2019", "\u300D", "\u300F", "\uFF09", ")", '"', "'"]);
20165
20170
  // ../../packages/publish-harness/src/index.ts
20166
20171
  import { homedir } from "os";
20167
20172
  import { join, resolve } from "path";
@@ -20369,9 +20374,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
20369
20374
  // ../../packages/publish-harness/src/bot-fs.ts
20370
20375
  import {
20371
20376
  chmodSync,
20377
+ closeSync,
20372
20378
  copyFileSync,
20373
20379
  lstatSync,
20374
20380
  mkdirSync as mkdirSync2,
20381
+ openSync,
20375
20382
  readdirSync as readdirSync2,
20376
20383
  realpathSync as realpathSync2,
20377
20384
  rmdirSync
@@ -20412,6 +20419,9 @@ function rmdir(path) {
20412
20419
  function chmod(path, mode) {
20413
20420
  chmodSync(path, mode);
20414
20421
  }
20422
+ function createExclusiveFile(path) {
20423
+ closeSync(openSync(path, "wx", 384));
20424
+ }
20415
20425
 
20416
20426
  // ../../packages/publish-harness/src/bot-transaction.ts
20417
20427
  import { homedir as homedir2 } from "os";
@@ -20616,7 +20626,6 @@ class BotError extends Error {
20616
20626
  this.code = code;
20617
20627
  }
20618
20628
  }
20619
-
20620
20629
  // ../../packages/publish-harness/src/profile-dir.ts
20621
20630
  import { mkdir as mkdir2, stat } from "fs/promises";
20622
20631
  async function ensureProfileDir2(profileDir, headless) {
@@ -20835,6 +20844,11 @@ function editorTextScript(selector) {
20835
20844
  // ../../plugins/publishings/wechatmp-pub/src/index.ts
20836
20845
  init_dist();
20837
20846
 
20847
+ // ../../plugins/publishings/wechatmp-pub/src/flow.ts
20848
+ import { randomUUID } from "crypto";
20849
+ import { tmpdir } from "os";
20850
+ import { extname, join as join6 } from "path";
20851
+
20838
20852
  // ../../plugins/publishings/wechatmp-pub/src/cgi.ts
20839
20853
  var MP_ORIGIN = "https://mp.weixin.qq.com";
20840
20854
  var FILETRANSFER_URL = `${MP_ORIGIN}/cgi-bin/filetransfer`;
@@ -22035,6 +22049,9 @@ async function prepareArticleMedia(page, session, input) {
22035
22049
  // ../../plugins/publishings/wechatmp-pub/src/page.ts
22036
22050
  var POLL_MS = 500;
22037
22051
  var STEP_TIMEOUT_MS = 30000;
22052
+ function sanitizeSessionEvidence(value) {
22053
+ return value.replace(/([?&](?:token|ticket|authkey)=)[^&\s"'<>)]*/gi, "$1[REDACTED]");
22054
+ }
22038
22055
  async function modalState(page) {
22039
22056
  try {
22040
22057
  const raw = await page.evaluate(`[...document.querySelectorAll('.weui-desktop-dialog__wrp, .weui-desktop-dialog')].some((d) => d.offsetWidth)`);
@@ -22096,9 +22113,89 @@ async function clickSelector(page, selector, timeoutMs = STEP_TIMEOUT_MS) {
22096
22113
  async function fillSelector(page, selector, value) {
22097
22114
  await page.locator(selector).first().fill(value);
22098
22115
  }
22116
+ function isArticleSave(request, origin) {
22117
+ if (request.method() !== "POST")
22118
+ return false;
22119
+ const url = new URL(request.url());
22120
+ if (url.origin !== origin || url.pathname !== "/cgi-bin/operate_appmsg")
22121
+ return false;
22122
+ if (!["create", "update", "edit"].includes(url.searchParams.get("sub") ?? ""))
22123
+ return false;
22124
+ const data = request.postData() ?? "";
22125
+ const form = new URLSearchParams(data);
22126
+ return form.has("content0") && form.has("title0") || data.includes('name="content0"') && data.includes('name="title0"');
22127
+ }
22128
+ function saveResponseSucceeded(value) {
22129
+ if (value === null || typeof value !== "object" || Array.isArray(value))
22130
+ return false;
22131
+ const body = value;
22132
+ const base = body.base_resp !== null && typeof body.base_resp === "object" ? body.base_resp : {};
22133
+ const codes = [body.ret, body.errcode, base.ret].filter((code) => code !== undefined);
22134
+ if (codes.some((code) => code !== 0 && code !== "0"))
22135
+ return false;
22136
+ if (codes.length > 0)
22137
+ return true;
22138
+ const info = body.appMsgInfo !== null && typeof body.appMsgInfo === "object" ? body.appMsgInfo : body;
22139
+ return typeof info.appMsgId === "string" || typeof info.appMsgId === "number" ? /^\d{1,12}$/.test(String(info.appMsgId)) : false;
22140
+ }
22141
+ async function saveNativeDraft(raw, selector, timeoutMs) {
22142
+ const origin = new URL(raw.url()).origin;
22143
+ const marker = `__kkNativeSave_${crypto.randomUUID()}`;
22144
+ const button = raw.locator(selector).first();
22145
+ await button.evaluate((element, key) => {
22146
+ const state = { clickedAt: 0 };
22147
+ const handler = () => {
22148
+ state.clickedAt = Date.now();
22149
+ };
22150
+ globalThis[key] = { state, element, handler };
22151
+ element.addEventListener("click", handler, { capture: true, once: true });
22152
+ }, marker);
22153
+ let deadline;
22154
+ const responseComplete = raw.waitForRequest(async (request) => {
22155
+ if (!isArticleSave(request, origin))
22156
+ return false;
22157
+ const clickedAt = await raw.evaluate(`globalThis[${JSON.stringify(marker)}]?.state?.clickedAt ?? 0`).catch(() => 0);
22158
+ if (typeof clickedAt !== "number" || clickedAt <= 0)
22159
+ return false;
22160
+ if (!await request.response())
22161
+ return false;
22162
+ return request.timing().startTime >= clickedAt;
22163
+ }, { timeout: timeoutMs }).then(async (request) => {
22164
+ const response = await request.response();
22165
+ if (response === null || response.status() < 200 || response.status() >= 300)
22166
+ throw new Error("HTTP refusal");
22167
+ if (await response.finished())
22168
+ throw new Error("Incomplete response");
22169
+ if (!saveResponseSucceeded(await response.json()))
22170
+ throw new Error("Save refused");
22171
+ });
22172
+ try {
22173
+ await Promise.race([
22174
+ Promise.all([responseComplete, button.click({ timeout: timeoutMs })]),
22175
+ new Promise((_resolve, reject) => {
22176
+ deadline = setTimeout(() => reject(new Error("Save timeout")), timeoutMs);
22177
+ })
22178
+ ]);
22179
+ } catch {
22180
+ throw new Error("Native article save was not acknowledged successfully before the deadline");
22181
+ } finally {
22182
+ if (deadline !== undefined)
22183
+ clearTimeout(deadline);
22184
+ await raw.evaluate(`(() => {
22185
+ const key = ${JSON.stringify(marker)};
22186
+ const records = globalThis;
22187
+ const value = records[key];
22188
+ if (value?.element && value.handler) value.element.removeEventListener('click', value.handler, true);
22189
+ delete records[key];
22190
+ })()`).catch(() => {
22191
+ return;
22192
+ });
22193
+ }
22194
+ }
22099
22195
  function wrapPlaywrightPage(raw) {
22100
22196
  const pw = raw;
22101
22197
  return {
22198
+ saveDraftAndWait: (selector, timeoutMs) => saveNativeDraft(raw, selector, timeoutMs),
22102
22199
  goto: (url, options) => pw.goto(url, { waitUntil: "domcontentloaded", ...options }).then(() => {
22103
22200
  return;
22104
22201
  }),
@@ -22445,12 +22542,37 @@ var DIGEST_INPUT = ["textarea#js_description", "textarea.js_desc"];
22445
22542
  var AUTHOR_INPUT = ["input#author", "input.js_author"];
22446
22543
  var IMAGE_FILE_INPUT = ['input[type="file"][accept*="image/"]', 'input[type="file"][accept*=".png"]'];
22447
22544
  var LAYOUT_CONTROL = ['button:has-text("\u4E00\u952E\u6392\u7248")', "div.ailayout-btn_wrp", "div.ailayout-btn_label"];
22448
- var LAYOUT_TARGET = ['div[role="menuitem"]:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")', 'li:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")'];
22449
- var COVER_PANEL_OPEN = ["a.js_selectCoverFromContent", ".js_cover_btn_area", "text=\u6362\u4E00\u5F20", "text=\u4FEE\u6539"];
22450
- var COVER_FROM_CONTENT = ["a.js_selectCoverFromContent", "text=\u4ECE\u6B63\u6587\u9009\u62E9"];
22451
- var COVER_FROM_LIBRARY = ["a.js_imagedialog", "text=\u4ECE\u56FE\u7247\u5E93\u9009\u62E9"];
22452
- var COVER_LIBRARY_NEXT = ['button:has-text("\u4E0B\u4E00\u6B65")'];
22453
- var COVER_CONFIRM = ['button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u8BA4")', 'button:has-text("\u5B8C\u6210")'];
22545
+ var LAYOUT_TARGET = [
22546
+ "div.edui-popup:visible .edui-pastecontainer > .edui-button > div.edui-default:nth-child(2)",
22547
+ 'div[role="menuitem"]:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")',
22548
+ 'li:has-text("\u5339\u914D\u76EE\u6807\u683C\u5F0F")'
22549
+ ];
22550
+ var LAYOUT_PREVIEW_APPLY = ['button:has-text("\u4F7F\u7528\u6B64\u6392\u7248")', 'a:has-text("\u4F7F\u7528\u6B64\u6392\u7248")', "text=\u4F7F\u7528\u6B64\u6392\u7248"];
22551
+ var LAYOUT_TARGET_MARKER = "__KK_WECHAT_LAYOUT_TARGET__";
22552
+ var LAYOUT_SAVE_STATE_MARKER = "__KK_WECHAT_LAYOUT_SAVE_STATE__";
22553
+ var LEGACY_EDITOR_READY_MARKER = "__KK_WECHAT_LEGACY_EDITOR_READY__";
22554
+ var COVER_PANEL_OPEN = [
22555
+ ".js_cover_btn_area:visible",
22556
+ "text=\u6362\u4E00\u5F20",
22557
+ "text=\u4FEE\u6539",
22558
+ "a.js_selectCoverFromContent:visible"
22559
+ ];
22560
+ var COVER_FROM_CONTENT = [
22561
+ "#js_cover_area a.js_selectCoverFromContent:visible",
22562
+ "a.js_selectCoverFromContent:visible"
22563
+ ];
22564
+ var COVER_FROM_LIBRARY = ["#js_cover_area a.js_imagedialog:visible", "a.js_imagedialog:visible"];
22565
+ var COVER_LIBRARY_NEXT = [
22566
+ '.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247")) button:has-text("\u4E0B\u4E00\u6B65")',
22567
+ 'button:has-text("\u4E0B\u4E00\u6B65")'
22568
+ ];
22569
+ var COVER_CONFIRM = [
22570
+ '.weui-desktop-dialog:visible:has(h3:has-text("\u7F16\u8F91\u5C01\u9762")) button:text-is("\u786E\u8BA4")',
22571
+ '.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247")) button:has-text("\u5B8C\u6210"):not(.weui-desktop-btn_disabled)',
22572
+ 'button:has-text("\u786E\u5B9A")',
22573
+ 'button:has-text("\u786E\u8BA4")',
22574
+ 'button:has-text("\u5B8C\u6210")'
22575
+ ];
22454
22576
  var DIALOG_DISMISS = [
22455
22577
  '.weui-desktop-dialog:visible button:has-text("\u53D6\u6D88")',
22456
22578
  ".weui-desktop-dialog__close",
@@ -22537,19 +22659,58 @@ var AUDIO_COVER_INPUT = [
22537
22659
  '.audio_dialog input[type="file"][accept*="image/"]',
22538
22660
  'input[type="file"][accept*=".png"]',
22539
22661
  '.weui-desktop-dialog input[type="file"][accept*="image"]',
22540
- '.audio_dialog input[type="file"][accept*="image"]'
22662
+ '.audio_dialog input[type="file"][accept*="image"]',
22663
+ 'input[type="file"][accept*="image/bmp"]'
22541
22664
  ];
22542
22665
  var AUDIO_TITLE_INPUT = [
22543
22666
  '.weui-desktop-dialog input[placeholder*="\u6807\u9898"]',
22544
22667
  '.audio_dialog input[placeholder*="\u6807\u9898"]',
22545
22668
  ".audio_dialog input.frm_input"
22546
22669
  ];
22547
- var UPLOAD_SAVE = ['.weui-desktop-dialog button:has-text("\u4FDD\u5B58")', 'button:has-text("\u4FDD\u5B58")'];
22670
+ var UPLOAD_SAVE = ['.audio-upload-dialog button:text-is("\u4FDD\u5B58")', '.weui-desktop-dialog button:text-is("\u4FDD\u5B58")'];
22548
22671
  var SAVE_DRAFT = ['button:has-text("\u4FDD\u5B58\u4E3A\u8349\u7A3F")', "text=\u4FDD\u5B58\u4E3A\u8349\u7A3F"];
22549
22672
  var PUBLISH_SEND = ["button.mass_send", ".mass_send", 'button:has-text("\u53D1\u8868")'];
22550
- var PUBLISH_CONFIRM = ['div.weui-desktop-dialog button:has-text("\u786E\u5B9A")', 'button:has-text("\u786E\u5B9A")'];
22673
+ var PUBLISH_DIALOG = ['div.weui-desktop-dialog:visible:has(h3.weui-desktop-dialog__title:text-is("\u53D1\u8868"))'];
22674
+ var PUBLISH_NOTIFY_TOGGLE = [
22675
+ 'div.weui-desktop-dialog:visible:has(h3.weui-desktop-dialog__title:text-is("\u53D1\u8868")) .mass_send__notify .weui-desktop-switch__input:visible'
22676
+ ];
22677
+ var PUBLISH_NOTIFY_STATE_SCRIPT = `(() => {
22678
+ const dialog = [...document.querySelectorAll('.weui-desktop-dialog')].find((candidate) => {
22679
+ const title = candidate.querySelector('h3.weui-desktop-dialog__title')?.textContent?.trim();
22680
+ const rect = candidate.getBoundingClientRect();
22681
+ return title === '\u53D1\u8868' && rect.width > 0 && rect.height > 0;
22682
+ });
22683
+ const input = dialog?.querySelector('.mass_send__notify input.weui-desktop-switch__input');
22684
+ return input instanceof HTMLInputElement ? input.checked : null;
22685
+ })()`;
22686
+ var PUBLISH_FINAL = [
22687
+ 'div.weui-desktop-dialog:visible:has(h3.weui-desktop-dialog__title:text-is("\u53D1\u8868")) .mass-send__footer button.weui-desktop-btn_primary:text-is("\u53D1\u8868")'
22688
+ ];
22689
+ var PUBLISH_VIEW_LATEST = [
22690
+ 'div.version_fail_dialog:visible a.btn_primary.js_btn:text-is("\u67E5\u770B\u65B0\u8349\u7A3F"):visible',
22691
+ 'div.weui-desktop-dialog:visible button:text-is("\u67E5\u770B\u65B0\u8349\u7A3F")',
22692
+ 'div.weui-desktop-dialog:visible a:text-is("\u67E5\u770B\u65B0\u8349\u7A3F")',
22693
+ 'div.weui-desktop-dialog:visible [role="button"]:text-is("\u67E5\u770B\u65B0\u8349\u7A3F")',
22694
+ 'div.weui-desktop-dialog:visible button:has-text("\u67E5\u770B\u65B0\u8349\u7A3F")'
22695
+ ];
22551
22696
  var PUBLISH_SUCCESS = ["\u53D1\u8868\u6210\u529F", "\u5DF2\u53D1\u8868"];
22552
- var PLATFORM_REFUSAL = ["\u5185\u5BB9\u4E0D\u5B8C\u6574\uFF0C\u65E0\u6CD5\u53D1\u8868", "\u8BF7\u8865\u5145\u5C01\u9762\u56FE", "\u672A\u8BBE\u7F6E\u5934\u50CF\u548C\u540D\u79F0", "\u516C\u4F17\u53F7\u5C1A\u672A\u5B9E\u540D"];
22697
+ var PUBLIC_SAVE_REFUSAL = ["\u5F53\u524D\u7F16\u8F91\u7684\u8349\u7A3F\u4E0D\u662F\u6700\u65B0\u7248\u672C"];
22698
+ var PUBLIC_SAVE_REFUSAL_CONTAINERS = [
22699
+ ...DEFAULT_SIGNAL_CONTAINERS,
22700
+ "div.version_fail_dialog:visible",
22701
+ "div.weui-desktop-dialog:visible"
22702
+ ];
22703
+ async function readStaleVersionRefusal(page) {
22704
+ return readScopedSignal(page, PUBLIC_SAVE_REFUSAL, PUBLIC_SAVE_REFUSAL_CONTAINERS);
22705
+ }
22706
+ var PLATFORM_REFUSAL = [
22707
+ "\u5185\u5BB9\u4E0D\u5B8C\u6574\uFF0C\u65E0\u6CD5\u53D1\u8868",
22708
+ "\u8BF7\u8865\u5145\u5C01\u9762\u56FE",
22709
+ "\u5FC5\u987B\u63D2\u5165\u4E00\u5F20\u56FE\u7247",
22710
+ "\u672A\u8BBE\u7F6E\u5934\u50CF\u548C\u540D\u79F0",
22711
+ "\u516C\u4F17\u53F7\u5C1A\u672A\u5B9E\u540D",
22712
+ "\u5F53\u524D\u7F16\u8F91\u7684\u8349\u7A3F\u4E0D\u662F\u6700\u65B0\u7248\u672C"
22713
+ ];
22553
22714
  var DRAFT_STAMP = "\u66F4\u65B0\u4E8E";
22554
22715
  var CONFIRM_TIMEOUT_MS = 5000;
22555
22716
  var TOKEN_LINK_SCRIPT = `(() => {
@@ -22559,7 +22720,7 @@ var TOKEN_LINK_SCRIPT = `(() => {
22559
22720
  return link ? (new URL(link).searchParams.get('token') ?? '') : '';
22560
22721
  })()`;
22561
22722
  function fail(error) {
22562
- return ResultSchema.parse({ ok: false, target: TARGET, error });
22723
+ return ResultSchema.parse({ ok: false, target: TARGET, error: sanitizeSessionEvidence(error) });
22563
22724
  }
22564
22725
  function sleep(ms) {
22565
22726
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -22715,21 +22876,154 @@ async function insertBody(page, deps) {
22715
22876
  const selector = await findSelector(page, BODY_BLOCK, "article body", deps.stepTimeoutMs);
22716
22877
  await pasteHtml(asPlaywrightPage(page), selector, markdownToWechatHtml(body), { timeoutMs: deps.stepTimeoutMs });
22717
22878
  }
22879
+ function nativeLayoutTargetScript() {
22880
+ return `/* ${LAYOUT_TARGET_MARKER} */ (() => {
22881
+ const popups = [...document.querySelectorAll('.edui-popup')].filter((popup) => {
22882
+ const rect = popup.getBoundingClientRect();
22883
+ const style = getComputedStyle(popup);
22884
+ return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden';
22885
+ });
22886
+ const targets = popups
22887
+ .flatMap((popup) => [...popup.querySelectorAll('.edui-pastecontainer > .edui-button > div')])
22888
+ .filter((candidate) => candidate.textContent?.trim() === '\u5339\u914D\u76EE\u6807\u683C\u5F0F');
22889
+ if (targets.length !== 1) return JSON.stringify({ state: targets.length === 0 ? 'missing' : 'ambiguous' });
22890
+ const target = targets[0];
22891
+ if (!(target instanceof HTMLElement)) return JSON.stringify({ state: 'missing' });
22892
+ target.click();
22893
+ return JSON.stringify({ state: 'clicked' });
22894
+ })()`;
22895
+ }
22896
+ function nativeLayoutSaveStateScript() {
22897
+ return `/* ${LAYOUT_SAVE_STATE_MARKER} */ (() => {
22898
+ const container = document.querySelector('#auto_save_container');
22899
+ if (!container) return 'unknown';
22900
+ if (container.textContent?.includes('\u81EA\u52A8\u4FDD\u5B58\u4E2D')) return 'saving';
22901
+ if (container.querySelector('.save_done')?.textContent?.includes('\u5DF2\u4FDD\u5B58')) return 'saved';
22902
+ return 'unknown';
22903
+ })()`;
22904
+ }
22905
+ function nativeLegacyEditorReadyScript() {
22906
+ return `/* ${LEGACY_EDITOR_READY_MARKER} */ (() => {
22907
+ const current = new URL(window.location.href);
22908
+ const canonicalLegacyRoute =
22909
+ current.origin === 'https://mp.weixin.qq.com' &&
22910
+ current.pathname === '/cgi-bin/appmsg' &&
22911
+ current.searchParams.get('t') === 'media/appmsg_edit' &&
22912
+ current.searchParams.get('action') === 'edit' &&
22913
+ (current.searchParams.get('appmsgid') ?? '').trim() !== '' &&
22914
+ current.searchParams.get('isNew') !== '1';
22915
+ if (!canonicalLegacyRoute) return 'unknown';
22916
+ const autoSaveContainer = document.querySelector('#auto_save_container');
22917
+ const legacyAutoSave = document.querySelector('#js_autosave');
22918
+ const saveSuccessMarkers = [...document.querySelectorAll(
22919
+ '#js_save_success, #js_save_success_with_ad, #js_save_success_with_ad_op',
22920
+ )];
22921
+ if (!autoSaveContainer || !legacyAutoSave || saveSuccessMarkers.length === 0) return 'unknown';
22922
+ const hidden = (node) => {
22923
+ const style = window.getComputedStyle(node);
22924
+ const rect = node.getBoundingClientRect();
22925
+ return style.display === 'none' || style.visibility === 'hidden' || rect.width === 0 || rect.height === 0;
22926
+ };
22927
+ const visibleSaveControls = [...document.querySelectorAll('button, a')].filter((node) => {
22928
+ const text = (node.textContent ?? '').replace(/[\\s\\u00a0]+/g, '').trim();
22929
+ if (text !== '\u4FDD\u5B58\u4E3A\u8349\u7A3F') return false;
22930
+ const style = window.getComputedStyle(node);
22931
+ const rect = node.getBoundingClientRect();
22932
+ return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
22933
+ });
22934
+ return autoSaveContainer.textContent?.trim() === '' &&
22935
+ hidden(legacyAutoSave) &&
22936
+ saveSuccessMarkers.every(hidden) &&
22937
+ visibleSaveControls.length === 1
22938
+ ? 'ready'
22939
+ : 'unknown';
22940
+ })()`;
22941
+ }
22942
+ async function clickNativeLayoutTarget(page, deps) {
22943
+ let state = "missing";
22944
+ const observed = await waitUntil(async () => {
22945
+ const raw = await page.evaluate(nativeLayoutTargetScript()).catch(() => null);
22946
+ if (typeof raw !== "string")
22947
+ return false;
22948
+ try {
22949
+ const parsed = JSON.parse(raw);
22950
+ if (parsed.state === "ambiguous") {
22951
+ state = "ambiguous";
22952
+ return true;
22953
+ }
22954
+ if (parsed.state === "clicked") {
22955
+ state = "clicked";
22956
+ return true;
22957
+ }
22958
+ } catch {
22959
+ state = "missing";
22960
+ }
22961
+ return false;
22962
+ }, Math.min(deps.stepTimeoutMs, 5000), deps.pollMs);
22963
+ if (!observed)
22964
+ return false;
22965
+ if (state === "ambiguous")
22966
+ throw new Error("\u4E00\u952E\u6392\u7248: \u5339\u914D\u76EE\u6807\u683C\u5F0F was ambiguous in the visible native menu \u2014 composition stopped before submission");
22967
+ return state === "clicked";
22968
+ }
22969
+ async function waitForNativeLayoutSave(page, deps) {
22970
+ let sawSaving = false;
22971
+ const saved = await waitUntil(async () => {
22972
+ const state = await page.evaluate(nativeLayoutSaveStateScript()).catch(() => "unknown");
22973
+ if (state === "saving")
22974
+ sawSaving = true;
22975
+ return sawSaving && state === "saved";
22976
+ }, Math.min(deps.stepTimeoutMs, 15000), deps.pollMs);
22977
+ if (!saved)
22978
+ throw new Error("\u4E00\u952E\u6392\u7248: native auto-save did not transition from \u81EA\u52A8\u4FDD\u5B58\u4E2D... to \u5DF2\u4FDD\u5B58 within the layout deadline \u2014 composition stopped before submission");
22979
+ }
22718
22980
  async function applyLayout(page, deps) {
22719
22981
  const control = await tryFindSelector(page, LAYOUT_CONTROL, Math.min(deps.stepTimeoutMs, 5000));
22720
22982
  if (control === undefined)
22721
22983
  return null;
22984
+ const before = deps.pages().length;
22722
22985
  try {
22723
22986
  await clickSelector(page, control, deps.stepTimeoutMs);
22724
22987
  } catch (error) {
22725
22988
  return `\u4E00\u952E\u6392\u7248: click failed \u2014 ${error instanceof Error ? error.message : String(error)}`;
22726
22989
  }
22727
- const option = await tryFindSelector(page, LAYOUT_TARGET, 5000);
22990
+ if (await clickNativeLayoutTarget(page, deps)) {
22991
+ await waitForNativeLayoutSave(page, deps);
22992
+ return null;
22993
+ }
22994
+ let layoutPage = page;
22995
+ let option;
22996
+ await waitUntil(async () => {
22997
+ for (const candidate of [...deps.pages().slice(before), page]) {
22998
+ const found = await trySelectors(candidate, [...LAYOUT_PREVIEW_APPLY, ...LAYOUT_TARGET]);
22999
+ if (found.found && found.selector) {
23000
+ layoutPage = candidate;
23001
+ option = found.selector;
23002
+ return true;
23003
+ }
23004
+ }
23005
+ return false;
23006
+ }, Math.min(deps.stepTimeoutMs, 15000));
23007
+ if (option !== undefined && LAYOUT_PREVIEW_APPLY.includes(option)) {
23008
+ const selected = option;
23009
+ await clickSelector(layoutPage, selected, deps.stepTimeoutMs);
23010
+ const closed = await waitUntil(async () => {
23011
+ if (!deps.pages().includes(layoutPage))
23012
+ return true;
23013
+ const control = layoutPage.locator(selected).first();
23014
+ return !await control.isVisible();
23015
+ }, Math.min(deps.stepTimeoutMs, 5000));
23016
+ if (!closed)
23017
+ throw new Error("\u4E00\u952E\u6392\u7248: \u4F7F\u7528\u6B64\u6392\u7248 did not close the preview \u2014 composition stopped before submission");
23018
+ return null;
23019
+ }
22728
23020
  if (option === undefined) {
22729
- return "\u4E00\u952E\u6392\u7248: the control exists but this build renders no \u5339\u914D\u76EE\u6807\u683C\u5F0F option (measured 2026-10-21 with a body in place) \u2014 layout skipped, apply \u4E00\u952E\u6392\u7248 by hand before publishing";
23021
+ if (deps.pages().length > before)
23022
+ throw new Error("\u4E00\u952E\u6392\u7248: a new preview tab opened without a recognized commit action \u2014 composition stopped before submission");
23023
+ throw new Error("\u4E00\u952E\u6392\u7248: the visible control exposed no recognized commit action \u2014 composition stopped before submission");
22730
23024
  }
22731
23025
  try {
22732
- await clickSelector(page, option, 5000);
23026
+ await clickSelector(layoutPage, option, 5000);
22733
23027
  return null;
22734
23028
  } catch (error) {
22735
23029
  return `\u4E00\u952E\u6392\u7248: \u5339\u914D\u76EE\u6807\u683C\u5F0F not applied \u2014 ${error instanceof Error ? error.message : String(error)}`;
@@ -22751,6 +23045,20 @@ async function fillOptional(page, selectors, value, what, deps) {
22751
23045
  function baseName(path) {
22752
23046
  return path.split(/[\\/]/).pop() ?? path;
22753
23047
  }
23048
+ async function coverLibraryName(path) {
23049
+ if (!await Bun.file(path).exists())
23050
+ return baseName(path);
23051
+ const hash = new Bun.CryptoHasher("sha256").update(new Uint8Array(await Bun.file(path).arrayBuffer())).digest("hex");
23052
+ return `kk-cover-${hash.slice(0, 32)}${extname(path).toLowerCase()}`;
23053
+ }
23054
+ function coverAssetIdentity(value) {
23055
+ try {
23056
+ const url = new URL(value);
23057
+ return url.hostname === "mmbiz.qpic.cn" ? `${url.hostname}${url.pathname.replace(/\/(?:0|300|640|1080)$/, "")}` : value;
23058
+ } catch {
23059
+ return value;
23060
+ }
23061
+ }
22754
23062
  async function setCover(page, source, deps, identity) {
22755
23063
  const picker = source === "library" ? COVER_FROM_LIBRARY : COVER_FROM_CONTENT;
22756
23064
  const what = source === "library" ? "\u4ECE\u56FE\u7247\u5E93\u9009\u62E9" : "\u4ECE\u6B63\u6587\u9009\u62E9";
@@ -22759,18 +23067,22 @@ async function setCover(page, source, deps, identity) {
22759
23067
  ...identity.binding ? [baseName(identity.binding)] : []
22760
23068
  ].map((name) => name.trim()).filter((name) => name.length > 0);
22761
23069
  const warn = async (message) => {
23070
+ const inventory = await openDialogInventory(page);
22762
23071
  await dismissOpenDialog(page);
22763
- return message;
23072
+ return inventory ? `${message} (dialogs: ${inventory})` : message;
22764
23073
  };
22765
23074
  try {
22766
23075
  const open = await tryFindSelector(page, COVER_PANEL_OPEN, Math.min(deps.stepTimeoutMs, 5000));
22767
23076
  if (open === undefined)
22768
23077
  return await warn("cover: the panel offers none of \u4ECE\u6B63\u6587\u9009\u62E9 / \u4ECE\u56FE\u7247\u5E93\u9009\u62E9 \u2014 the platform default (\u9ED8\u8BA4\u9996\u56FE\u4E3A\u5C01\u9762) is all this run has");
22769
23078
  await clickSelector(page, open, deps.stepTimeoutMs);
22770
- const pick = await tryFindSelector(page, picker, Math.min(deps.stepTimeoutMs, 8000));
22771
- if (pick === undefined)
22772
- return await warn(`cover: ${what} was not offered by the open cover panel`);
22773
- await clickSelector(page, pick, deps.stepTimeoutMs);
23079
+ const alreadyOpen = source === "library" && await tryFindSelector(page, ['.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247"))'], Math.min(deps.stepTimeoutMs, 1000)) !== undefined;
23080
+ if (!alreadyOpen) {
23081
+ const pick = await tryFindSelector(page, picker, Math.min(deps.stepTimeoutMs, 8000));
23082
+ if (pick === undefined)
23083
+ return await warn(`cover: ${what} was not offered by the open cover panel`);
23084
+ await clickSelector(page, pick, deps.stepTimeoutMs);
23085
+ }
22774
23086
  if (wanted.length === 0)
22775
23087
  return await warn(`cover: no requested cover file identity to match against \u2014 ${what} not driven`);
22776
23088
  const IDENTIFY = `(() => {
@@ -22781,19 +23093,43 @@ async function setCover(page, source, deps, identity) {
22781
23093
  const el = row.querySelector('[title], [data-name], [data-file_name]') ?? row;
22782
23094
  const attrs = [el.getAttribute?.('title'), el.getAttribute?.('data-name'), el.getAttribute?.('data-file_name')];
22783
23095
  const text = [...row.querySelectorAll('[class*="name" i], [class*="title" i]')].map((n) => n.textContent ?? '');
22784
- return [...attrs, ...text].map((v) => (v ?? '').trim()).find((v) => v.length > 0) ?? '';
23096
+ return [...attrs, ...text].map((v) => (v ?? '').trim()).filter((v) => v.length > 0);
22785
23097
  };
22786
- const hits = rows.filter((row) => wanted.some((w) => nameOf(row) === w));
23098
+ const hits = rows.filter((row) => wanted.some((w) => nameOf(row).includes(w)));
22787
23099
  if (hits.length === 0) return 'missing';
22788
23100
  if (hits.length > 1) return 'ambiguous';
22789
23101
  const row = hits[0];
22790
- row.querySelector('input[type=checkbox], [role=checkbox]')?.click();
22791
- row.click();
23102
+ if (!row.classList.contains('selected')) row.click();
22792
23103
  const checked = row.querySelector('input[type=checkbox], [role=checkbox]');
22793
- const src = row.querySelector('img')?.src ?? (row.querySelector('[style*="background-image"]')?.style.backgroundImage.match(/url("?([^")]+)"?)/)?.[1] ?? '');
23104
+ const css = row.querySelector('[style*="background-image"]')?.style.backgroundImage ?? '';
23105
+ const backgroundUrl = css.startsWith('url("') || css.startsWith("url('") ? css.slice(5, -2) : css.startsWith('url(') ? css.slice(4, -1) : '';
23106
+ const src = row.querySelector('img')?.src || backgroundUrl;
22794
23107
  return JSON.stringify({ picked: true, src });
22795
23108
  })()`;
22796
- const rawIdentify = String(await page.evaluate(IDENTIFY));
23109
+ let rawIdentify = "missing";
23110
+ await waitUntil(async () => {
23111
+ rawIdentify = String(await page.evaluate(IDENTIFY));
23112
+ return rawIdentify !== "missing";
23113
+ }, Math.min(deps.stepTimeoutMs, 8000));
23114
+ const paginationScript = `(() => {
23115
+ const dialog = [...document.querySelectorAll('.weui-desktop-dialog')].find(d => d.offsetWidth > 0 && d.querySelector('h3')?.textContent?.includes('\u9009\u62E9\u56FE\u7247'));
23116
+ const labels = dialog?.querySelectorAll('.weui-desktop-pagination__num');
23117
+ return JSON.stringify({ page: Number(labels?.[0]?.textContent ?? 0), total: Number(labels?.[1]?.textContent ?? 0) });
23118
+ })()`;
23119
+ for (let scanned = 0;source === "library" && rawIdentify === "missing" && scanned < 50; scanned += 1) {
23120
+ const pagination = JSON.parse(String(await page.evaluate(paginationScript)));
23121
+ if (!(Number(pagination?.page) > 0 && Number(pagination?.page) < Number(pagination?.total)))
23122
+ break;
23123
+ const nextPage = await tryFindSelector(page, [
23124
+ '.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247")) a:has-text("\u4E0B\u4E00\u9875"):visible:not(.weui-desktop-btn_disabled)'
23125
+ ], 1000);
23126
+ if (nextPage === undefined)
23127
+ break;
23128
+ await clickSelector(page, nextPage, deps.stepTimeoutMs);
23129
+ await page.waitForTimeout?.(500);
23130
+ await waitUntil(async () => Boolean(await page.evaluate(`![...document.querySelectorAll('.weui-desktop-dialog .weui-desktop-loading')].some(node => node.offsetWidth > 0)`)), Math.min(deps.stepTimeoutMs, 8000));
23131
+ rawIdentify = String(await page.evaluate(IDENTIFY));
23132
+ }
22797
23133
  if (rawIdentify === "missing")
22798
23134
  return await warn(`cover: ${what} \u672A\u6807\u51FA\u4E0E\u8BF7\u6C42\u6587\u4EF6\u540C\u540D\u7684\u7D20\u6750\uFF0C\u672A\u9009\u62E9\uFF08\u4E0D\u6309\u9996\u56FE\u731C\u6D4B\uFF09`);
22799
23135
  if (rawIdentify === "ambiguous")
@@ -22810,33 +23146,49 @@ async function setCover(page, source, deps, identity) {
22810
23146
  const confirm = await tryFindSelector(page, COVER_CONFIRM, Math.min(deps.stepTimeoutMs, 8000));
22811
23147
  if (confirm === undefined)
22812
23148
  return await warn("cover: the choice panel offered no confirm control");
23149
+ const selectedSrc = (selected.src ?? "").trim();
23150
+ const cropSources = JSON.parse(String(await page.evaluate(`(() => {
23151
+ const dialog = [...document.querySelectorAll('.weui-desktop-dialog')].find(d => d.offsetWidth > 0 && d.querySelector('h3')?.textContent?.trim() === '\u7F16\u8F91\u5C01\u9762');
23152
+ return JSON.stringify([...(dialog?.querySelectorAll('img.crop-img') ?? [])]
23153
+ .filter(img => img.offsetWidth > 0 && getComputedStyle(img).visibility !== 'hidden').map(img => img.src));
23154
+ })()`)));
23155
+ const cropBound = selectedSrc !== "" && Array.isArray(cropSources) && cropSources.some((src) => typeof src === "string" && coverAssetIdentity(src) === coverAssetIdentity(selectedSrc));
23156
+ const beforeCommit = String(await page.evaluate(`(() => {
23157
+ const css = document.querySelector('#js_cover_area .js_cover_preview_new')?.style.backgroundImage ?? '';
23158
+ return css.startsWith('url("') || css.startsWith("url('") ? css.slice(5, -2) : css.startsWith('url(') ? css.slice(4, -1) : '';
23159
+ })()`));
22813
23160
  await clickSelector(page, confirm, deps.stepTimeoutMs);
22814
23161
  const READBACK = `(() => {
22815
23162
  const area = document.querySelector('#js_cover_area');
22816
23163
  if (!area) return 'noarea';
22817
- if (area.querySelector('.icon20_common.add_cover') && area.querySelector('.icon20_common.add_cover').offsetWidth > 0) return 'empty';
22818
23164
  const preview = area.querySelector('.js_cover_preview_new');
22819
- if (!preview || preview.offsetWidth === 0) return 'nopreview';
22820
- const src = preview.querySelector('img')?.src ??
22821
- (preview.querySelector('[style*="background-image"]')?.style.backgroundImage.match(/url("?([^")]+)"?)/)?.[1] ?? '') ||
22822
- (preview.querySelector('input[type=hidden]')?.value ?? '');
23165
+ if (!preview || preview.offsetWidth === 0) {
23166
+ return area.querySelector('.icon20_common.add_cover')?.offsetWidth > 0 ? 'empty' : 'nopreview';
23167
+ }
23168
+ const css = preview.style.backgroundImage || preview.querySelector('[style*="background-image"]')?.style.backgroundImage || '';
23169
+ const backgroundUrl = css.startsWith('url("') || css.startsWith("url('") ? css.slice(5, -2) : css.startsWith('url(') ? css.slice(4, -1) : '';
23170
+ const src = preview.querySelector('img')?.src || backgroundUrl || preview.querySelector('input[type=hidden]')?.value || '';
22823
23171
  return src ? src : 'unknown';
22824
23172
  })()`;
22825
23173
  let committed = "";
22826
- for (let read = 0;read < 5 && committed === ""; read += 1) {
22827
- const raw = String(await page.evaluate(READBACK).catch(() => "error"));
22828
- if (raw !== "" && raw !== "nopreview" && raw !== "noarea") {
22829
- committed = raw;
22830
- break;
22831
- }
22832
- await page.waitForTimeout?.(600);
22833
- }
22834
- if (committed === "empty")
22835
- return await warn("cover: \u786E\u8BA4\u540E\u9762\u677F\u4ECD\u663E\u793A\u672A\u8BBE\u7F6E\u5C01\u9762\uFF0C\u63D0\u4EA4\u672A\u751F\u6548");
22836
- if (committed === "" || committed === "noarea" || committed === "error")
22837
- return await warn("cover: \u5DF2\u786E\u8BA4\u4F46\u65E0\u6CD5\u8BFB\u53D6\u5DF2\u63D0\u4EA4\u5C01\u9762\uFF0C\u8BF7\u624B\u52A8\u6838\u5BF9");
22838
- const selectedSrc = (selected.src ?? "").trim();
22839
- if (selectedSrc === "" || committed !== selectedSrc && !committed.includes(selectedSrc))
23174
+ let lastRead = "";
23175
+ const observed = await waitUntil(async () => {
23176
+ lastRead = String(await page.evaluate(READBACK).catch(() => "error"));
23177
+ if (["", "nopreview", "noarea", "empty", "unknown", "error"].includes(lastRead))
23178
+ return false;
23179
+ if (cropBound && lastRead === beforeCommit && coverAssetIdentity(lastRead) !== coverAssetIdentity(selectedSrc))
23180
+ return false;
23181
+ committed = lastRead;
23182
+ return true;
23183
+ }, Math.min(deps.stepTimeoutMs, 15000), 200);
23184
+ if (!observed)
23185
+ return await warn(lastRead === "empty" ? "cover: \u786E\u8BA4\u540E\u9762\u677F\u4ECD\u663E\u793A\u672A\u8BBE\u7F6E\u5C01\u9762\uFF0C\u63D0\u4EA4\u672A\u751F\u6548" : "cover: \u5DF2\u786E\u8BA4\u4F46\u65E0\u6CD5\u8BFB\u53D6\u5DF2\u63D0\u4EA4\u5C01\u9762\uFF0C\u8BF7\u624B\u52A8\u6838\u5BF9");
23186
+ let derivedFromBoundCrop = false;
23187
+ try {
23188
+ const url = new URL(committed);
23189
+ derivedFromBoundCrop = cropBound && committed !== beforeCommit && url.hostname === "mmbiz.qpic.cn" && ["http:", "https:"].includes(url.protocol);
23190
+ } catch {}
23191
+ if (selectedSrc === "" || coverAssetIdentity(committed) !== coverAssetIdentity(selectedSrc) && !derivedFromBoundCrop)
22840
23192
  return await warn(`cover: \u5DF2\u63D0\u4EA4\u5C01\u9762\u7684\u7D20\u6750\u8EAB\u4EFD\u4E0E\u6240\u9009\u4E0D\u4E00\u81F4\uFF08selected=${selectedSrc || "\u672A\u8BFB\u5230"} committed=${committed.slice(0, 80)}\uFF09`);
22841
23193
  return null;
22842
23194
  } catch (error) {
@@ -22862,7 +23214,7 @@ function audioBlockOf(content) {
22862
23214
  coverPath: typeof m.audioCoverPath === "string" ? m.audioCoverPath : ""
22863
23215
  };
22864
23216
  }
22865
- async function enrichArticleDraft(page, content) {
23217
+ async function enrichArticleDraft(page, content, stepTimeoutMs) {
22866
23218
  const enrichMeta = content.metadata?.wechatmp;
22867
23219
  if (enrichMeta === undefined || typeof enrichMeta !== "object" || Array.isArray(enrichMeta))
22868
23220
  return { enrichWarnings: [], coverWarnings: [] };
@@ -22882,7 +23234,7 @@ async function enrichArticleDraft(page, content) {
22882
23234
  if (audioFailed) {
22883
23235
  coverWarnings.push("cover: \u97F3\u9891\u4E0A\u4F20\u672A\u6210\u529F\uFF0C\u56FE\u7247\u5E93\u4E0D\u4FDD\u8BC1\u5DF2\u6709\u8BF7\u6C42\u5C01\u9762\uFF0C\u5DF2\u8DF3\u8FC7\u56FE\u7247\u5E93\u5C01\u9762");
22884
23236
  } else {
22885
- const cover = await setCover(page, "library", { stepTimeoutMs: 8000 }, { binding: audio.coverPath === "" ? undefined : audio.coverPath });
23237
+ const cover = await setCover(page, "library", { stepTimeoutMs: Math.min(stepTimeoutMs, 8000) }, { binding: audio.coverPath === "" ? undefined : await coverLibraryName(audio.coverPath) });
22886
23238
  if (cover !== null)
22887
23239
  coverWarnings.push(cover);
22888
23240
  }
@@ -22895,9 +23247,6 @@ async function composeArticle(input, deps, entry) {
22895
23247
  await attachImages(page, input.images, deps);
22896
23248
  const titlePath = await typeTitle(page, deps);
22897
23249
  await insertBody(page, deps);
22898
- const layout = await applyLayout(page, deps);
22899
- if (layout !== null)
22900
- warnings.push(layout);
22901
23250
  for (const warning of [
22902
23251
  await fillOptional(page, DIGEST_INPUT, input.digest ?? "", "\u6458\u8981", deps),
22903
23252
  await fillOptional(page, AUTHOR_INPUT, authorOf(deps.content), "\u4F5C\u8005", deps)
@@ -22914,8 +23263,11 @@ async function composeArticle(input, deps, entry) {
22914
23263
  if (cover !== null)
22915
23264
  warnings.push(cover);
22916
23265
  }
22917
- const { enrichWarnings, coverWarnings } = await enrichArticleDraft(page, deps.content);
23266
+ const { enrichWarnings, coverWarnings } = await enrichArticleDraft(page, deps.content, deps.stepTimeoutMs);
22918
23267
  warnings.push(...coverWarnings);
23268
+ const layout = await applyLayout(page, deps);
23269
+ if (layout !== null)
23270
+ warnings.push(layout);
22919
23271
  return { warnings, titlePath, enrichWarnings };
22920
23272
  }
22921
23273
  async function composeSticker(input, deps, entry) {
@@ -22986,7 +23338,7 @@ function baseMetadata(input, deps, entry) {
22986
23338
  artifactPaths: artifactPaths(input),
22987
23339
  entryPath: entry.path,
22988
23340
  editorCreateType: entry.createType,
22989
- editorUrl: entry.editor.url(),
23341
+ editorUrl: sanitizeSessionEvidence(entry.editor.url()),
22990
23342
  ...cover === undefined ? {} : { coverPath: cover }
22991
23343
  };
22992
23344
  }
@@ -23051,6 +23403,272 @@ async function saveDraft(input, deps, entry, composed) {
23051
23403
  }
23052
23404
  });
23053
23405
  }
23406
+ function canonicalSavedEditorUrl(value) {
23407
+ try {
23408
+ const url = new URL(value);
23409
+ if (url.origin !== "https://mp.weixin.qq.com" || url.pathname !== "/cgi-bin/appmsg")
23410
+ return null;
23411
+ if (url.searchParams.get("action") !== "edit" || url.searchParams.get("isNew") === "1")
23412
+ return null;
23413
+ if ((url.searchParams.get("appmsgid") ?? "").trim() === "")
23414
+ return null;
23415
+ return url.toString();
23416
+ } catch {
23417
+ return null;
23418
+ }
23419
+ }
23420
+ function savedEditorUrlForMetadata(value) {
23421
+ return sanitizeSessionEvidence(value);
23422
+ }
23423
+ var PUBLIC_DRAFT_SNAPSHOT_SCRIPT = `/* __KK_WECHAT_PUBLISH_SNAPSHOT__ */ (() => {
23424
+ const norm = (value) => (value ?? '').replace(/[\\s\\u00a0]+/g, ' ').trim();
23425
+ const titleNode = document.querySelector('textarea#title, textarea.js_title');
23426
+ const title = titleNode?.value ?? [...document.querySelectorAll('.ProseMirror[data-placeholder*="\u6807\u9898"]')]
23427
+ .map((node) => node.textContent ?? '').join(' ');
23428
+ const body = [...document.querySelectorAll('.ProseMirror:not([data-placeholder])')]
23429
+ .map((node) => norm(node.textContent)).join('\\n');
23430
+ const media = [
23431
+ ...document.querySelectorAll(
23432
+ '.ProseMirror mp-common-mpaudio, .ProseMirror mpvoice, .ProseMirror [data-mediaid], .ProseMirror audio, .ProseMirror video, .ProseMirror img',
23433
+ ),
23434
+ ]
23435
+ .map((node) => node.getAttribute('data-mediaid') ?? node.getAttribute('data-id') ??
23436
+ node.getAttribute('voice_encode_fileid') ?? node.getAttribute('data-src') ?? node.getAttribute('src') ??
23437
+ norm(node.textContent))
23438
+ .map(norm).filter((value) => value.length > 0);
23439
+ const area = document.querySelector('#js_cover_area');
23440
+ const coverNode = area?.querySelector('.js_cover_preview_new img, img, [style*="background-image"], input[type="hidden"]');
23441
+ const cover = coverNode?.getAttribute('src') ?? coverNode?.getAttribute('value') ??
23442
+ coverNode?.style.backgroundImage ?? '';
23443
+ const urlArea = document.querySelector('#js_article_url_area');
23444
+ const originalUrl = urlArea?.querySelector('input.js_url_checkbox')?.checked === true
23445
+ ? norm(urlArea.querySelector('.article_url_setting')?.textContent) : '';
23446
+ return JSON.stringify({ title: norm(title), body, media, cover: norm(cover), originalUrl });
23447
+ })()`;
23448
+ function normalizeSnapshotText(value) {
23449
+ return value.replace(/[\s\u00a0]+/g, " ").trim();
23450
+ }
23451
+ async function readPublicDraftSnapshot(page) {
23452
+ try {
23453
+ const raw = await page.evaluate(PUBLIC_DRAFT_SNAPSHOT_SCRIPT);
23454
+ const parsed = typeof raw === "string" ? JSON.parse(raw) : raw;
23455
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
23456
+ return null;
23457
+ const value = parsed;
23458
+ if (typeof value.title !== "string" || typeof value.body !== "string" || !Array.isArray(value.media) || value.media.some((item) => typeof item !== "string") || typeof value.cover !== "string")
23459
+ return null;
23460
+ return {
23461
+ title: normalizeSnapshotText(value.title),
23462
+ body: normalizeSnapshotText(value.body),
23463
+ media: value.media.map((item) => normalizeSnapshotText(item)),
23464
+ cover: normalizeSnapshotText(value.cover),
23465
+ originalUrl: typeof value.originalUrl === "string" ? normalizeSnapshotText(value.originalUrl) : ""
23466
+ };
23467
+ } catch {
23468
+ return null;
23469
+ }
23470
+ }
23471
+ function publicSnapshotDifferences(before, after) {
23472
+ const differences = [];
23473
+ if (before.title !== after.title)
23474
+ differences.push("title");
23475
+ if (before.body !== after.body)
23476
+ differences.push("body");
23477
+ const beforeMedia = before.media.map(snapshotMediaIdentity);
23478
+ const afterMedia = after.media.map(snapshotMediaIdentity);
23479
+ if (JSON.stringify(beforeMedia) !== JSON.stringify(afterMedia))
23480
+ differences.push("media");
23481
+ if (before.cover !== after.cover)
23482
+ differences.push("cover");
23483
+ if (before.originalUrl !== after.originalUrl)
23484
+ differences.push("originalUrl");
23485
+ return differences;
23486
+ }
23487
+ function snapshotMediaIdentity(value) {
23488
+ try {
23489
+ const url = new URL(value);
23490
+ if (url.hostname !== "mmbiz.qpic.cn")
23491
+ return value;
23492
+ const path = url.pathname.replace(/\/(?:0|300|640|1080)$/, "");
23493
+ return `${url.origin}${path}${url.search}${url.hash}`;
23494
+ } catch {
23495
+ return value;
23496
+ }
23497
+ }
23498
+ function snapshotValueIsEmpty(value) {
23499
+ const normalized = normalizeSnapshotText(value).toLowerCase();
23500
+ return normalized === "" || normalized === 'url("")' || normalized === "url('')" || normalized === "url()";
23501
+ }
23502
+ function publicSnapshotIsIncomplete(expected, actual) {
23503
+ if (!snapshotValueIsEmpty(expected.title) && snapshotValueIsEmpty(actual.title))
23504
+ return true;
23505
+ if (!snapshotValueIsEmpty(expected.body) && snapshotValueIsEmpty(actual.body))
23506
+ return true;
23507
+ if (expected.media.length > actual.media.length)
23508
+ return true;
23509
+ if (!snapshotValueIsEmpty(expected.cover) && snapshotValueIsEmpty(actual.cover))
23510
+ return true;
23511
+ if (expected.originalUrl !== "" && actual.originalUrl === "")
23512
+ return true;
23513
+ return false;
23514
+ }
23515
+ var PUBLIC_SAVED_EDITOR_RELOAD_LIMIT = 6;
23516
+ function appmsgIdFromCanonical(value) {
23517
+ const canonical = canonicalSavedEditorUrl(value);
23518
+ if (canonical === null)
23519
+ return null;
23520
+ try {
23521
+ const appmsgId = new URL(canonical).searchParams.get("appmsgid")?.trim();
23522
+ return appmsgId && appmsgId.length > 0 ? appmsgId : null;
23523
+ } catch {
23524
+ return null;
23525
+ }
23526
+ }
23527
+ async function waitForPublicEditorReadiness(page, deps) {
23528
+ let consecutiveSaved = 0;
23529
+ return waitUntil(async () => {
23530
+ const state = await page.evaluate(nativeLayoutSaveStateScript()).catch(() => "unknown");
23531
+ const legacyState = state === "saved" ? "not-needed" : await page.evaluate(nativeLegacyEditorReadyScript()).catch(() => "unknown");
23532
+ if (state === "saved" || legacyState === "ready") {
23533
+ consecutiveSaved += 1;
23534
+ return consecutiveSaved >= 2;
23535
+ }
23536
+ consecutiveSaved = 0;
23537
+ return false;
23538
+ }, Math.min(deps.stepTimeoutMs, 15000), deps.pollMs);
23539
+ }
23540
+ async function savePublicDraft(deps, entry) {
23541
+ const composed = await readPublicDraftSnapshot(entry.editor);
23542
+ if (composed === null)
23543
+ return fail("public save readiness: the composed editor snapshot (title/body/media/cover) was unreadable; no publish click attempted");
23544
+ const platformMetadata = deps.content.metadata?.wechatmp;
23545
+ const requestedUrl = platformMetadata !== null && typeof platformMetadata === "object" && "originalUrl" in platformMetadata ? platformMetadata.originalUrl : undefined;
23546
+ if (typeof requestedUrl === "string" && normalizeSnapshotText(requestedUrl) !== composed.originalUrl)
23547
+ return fail("public save readiness: requested \u539F\u6587\u94FE\u63A5 was not committed; no publish click attempted");
23548
+ const canonicalUrlBeforeSave = canonicalSavedEditorUrl(entry.editor.url());
23549
+ await sleep(deps.submitSettleMs);
23550
+ if (await closeOpenDialogs(entry.editor) !== "closed")
23551
+ return fail("public save readiness: a residual editor dialog blocked \u4FDD\u5B58\u4E3A\u8349\u7A3F; no publish click attempted");
23552
+ const saveControl = await findSelector(entry.editor, SAVE_DRAFT, "\u4FDD\u5B58\u4E3A\u8349\u7A3F control", deps.stepTimeoutMs);
23553
+ if (!entry.editor.saveDraftAndWait)
23554
+ return fail("public save readiness: the native \u4FDD\u5B58\u4E3A\u8349\u7A3F acknowledgement probe was unavailable; no publish click attempted");
23555
+ try {
23556
+ await entry.editor.saveDraftAndWait(saveControl, deps.draftTimeoutMs);
23557
+ } catch {
23558
+ return fail("public save readiness: native \u4FDD\u5B58\u4E3A\u8349\u7A3F was not acknowledged successfully; no publish click attempted");
23559
+ }
23560
+ let canonicalUrl = null;
23561
+ let refusal = null;
23562
+ const saved = await waitUntil(async () => {
23563
+ refusal = await readStaleVersionRefusal(asPlaywrightPage(entry.editor)).catch(() => null);
23564
+ if (refusal !== null)
23565
+ return true;
23566
+ canonicalUrl = canonicalSavedEditorUrl(entry.editor.url());
23567
+ return canonicalUrl !== null;
23568
+ }, deps.draftTimeoutMs, deps.pollMs);
23569
+ if (refusal !== null)
23570
+ return fail(`public save refused by ${TARGET}: ${refusal}; no publish click attempted`);
23571
+ if (!saved || canonicalUrl === null) {
23572
+ return fail(`public save readiness: \u4FDD\u5B58\u4E3A\u8349\u7A3F did not expose a canonical saved editor URL (action=edit with appmsgid, not isNew=1) within ${deps.draftTimeoutMs}ms; no publish click attempted`);
23573
+ }
23574
+ const savedCanonicalUrl = canonicalUrl;
23575
+ await sleep(Math.min(Math.max(deps.pollMs, 25), 500));
23576
+ await entry.editor.goto(savedCanonicalUrl, { timeout: deps.stepTimeoutMs });
23577
+ let reloaded = null;
23578
+ let reloadRefusal = null;
23579
+ let reloads = 0;
23580
+ let lastReloadAt = Date.now();
23581
+ const reloadIntervalMs = Math.min(Math.max(deps.pollMs * 4, 50), 2000);
23582
+ const canonicalUrlWasPreexisting = canonicalUrlBeforeSave === savedCanonicalUrl;
23583
+ await waitUntil(async () => {
23584
+ reloadRefusal = await readStaleVersionRefusal(asPlaywrightPage(entry.editor)).catch(() => null);
23585
+ if (reloadRefusal !== null)
23586
+ return true;
23587
+ if (canonicalSavedEditorUrl(entry.editor.url()) === null)
23588
+ return false;
23589
+ reloaded = await readPublicDraftSnapshot(entry.editor);
23590
+ if (reloaded === null)
23591
+ return false;
23592
+ const differences = publicSnapshotDifferences(composed, reloaded);
23593
+ if (differences.length === 0)
23594
+ return true;
23595
+ if (!publicSnapshotIsIncomplete(composed, reloaded))
23596
+ return true;
23597
+ if (reloads >= PUBLIC_SAVED_EDITOR_RELOAD_LIMIT)
23598
+ return true;
23599
+ if (Date.now() - lastReloadAt < reloadIntervalMs)
23600
+ return false;
23601
+ reloads += 1;
23602
+ lastReloadAt = Date.now();
23603
+ await entry.editor.goto(savedCanonicalUrl, { timeout: deps.stepTimeoutMs });
23604
+ return false;
23605
+ }, deps.draftTimeoutMs, deps.pollMs);
23606
+ if (reloadRefusal !== null)
23607
+ return fail(`public saved-editor refusal by ${TARGET}: ${reloadRefusal}; no publish click attempted`);
23608
+ if (reloaded === null)
23609
+ return fail(`public saved-editor readiness: the canonical editor reopened but its title/body/media/cover snapshot was unreadable within ${deps.draftTimeoutMs}ms; no publish click attempted`);
23610
+ const differences = publicSnapshotDifferences(composed, reloaded);
23611
+ if (differences.length > 0)
23612
+ return fail(`public saved editor content mismatch before publication (${differences.join(", ")}); no publish click attempted`);
23613
+ const autoSaveSettled = await waitForPublicEditorReadiness(entry.editor, deps);
23614
+ if (!autoSaveSettled)
23615
+ return fail(`public saved-editor readiness: native \u4FDD\u5B58\u72B6\u6001 did not provide two consecutive recognized readiness probes within ${Math.min(deps.stepTimeoutMs, 15000)}ms; no publish click attempted`);
23616
+ return {
23617
+ saveControl,
23618
+ canonicalUrl: savedCanonicalUrl,
23619
+ canonicalUrlForMetadata: savedEditorUrlForMetadata(savedCanonicalUrl),
23620
+ saveSignal: canonicalUrlWasPreexisting ? "persisted-snapshot" : "canonical-url-transition+snapshot",
23621
+ snapshot: composed
23622
+ };
23623
+ }
23624
+ async function recoverStaleVersion(deps, entry, saved) {
23625
+ const expectedAppmsgId = appmsgIdFromCanonical(saved.canonicalUrl);
23626
+ if (expectedAppmsgId === null)
23627
+ return fail("public stale-version recovery: the saved editor identity was not canonical; no retry attempted");
23628
+ const latest = await findUniqueVisibleSelector(entry.editor, PUBLISH_VIEW_LATEST, Math.min(CONFIRM_TIMEOUT_MS, deps.stepTimeoutMs), deps.pollMs);
23629
+ if (latest.selector === undefined)
23630
+ return fail(`public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F was ${latest.reason}; no second \u53D1\u8868 click attempted`);
23631
+ const before = deps.pages().length;
23632
+ try {
23633
+ await clickSelector(entry.editor, latest.selector, deps.stepTimeoutMs);
23634
+ } catch {
23635
+ return fail("public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F could not be clicked; no second \u53D1\u8868 click attempted");
23636
+ }
23637
+ const latestEditor = await followNewPage(deps.pages, before, Math.min(deps.stepTimeoutMs, CONFIRM_TIMEOUT_MS)) ?? entry.editor;
23638
+ let latestCanonicalUrl = null;
23639
+ const canonicalReady = await waitUntil(async () => {
23640
+ latestCanonicalUrl = canonicalSavedEditorUrl(latestEditor.url());
23641
+ if (latestCanonicalUrl === null)
23642
+ return false;
23643
+ const refusal = await readStaleVersionRefusal(asPlaywrightPage(latestEditor)).catch(() => "__unreadable__");
23644
+ return refusal === null;
23645
+ }, deps.draftTimeoutMs, deps.pollMs);
23646
+ if (!canonicalReady || latestCanonicalUrl === null)
23647
+ return fail("public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F did not expose a canonical saved editor route; no second \u53D1\u8868 click attempted");
23648
+ const actualAppmsgId = appmsgIdFromCanonical(latestCanonicalUrl);
23649
+ if (actualAppmsgId !== expectedAppmsgId)
23650
+ return fail(`public stale-version recovery: \u67E5\u770B\u65B0\u8349\u7A3F opened appmsgid=${actualAppmsgId ?? "unknown"}, expected appmsgid=${expectedAppmsgId}; no second \u53D1\u8868 click attempted`);
23651
+ let latestSnapshot = null;
23652
+ let differences = [];
23653
+ const snapshotSettled = await waitUntil(async () => {
23654
+ latestSnapshot = await readPublicDraftSnapshot(latestEditor);
23655
+ if (latestSnapshot === null)
23656
+ return false;
23657
+ differences = publicSnapshotDifferences(saved.snapshot, latestSnapshot);
23658
+ return differences.length === 0 || !publicSnapshotIsIncomplete(saved.snapshot, latestSnapshot);
23659
+ }, deps.draftTimeoutMs, deps.pollMs);
23660
+ if (!snapshotSettled || latestSnapshot === null)
23661
+ return fail("public stale-version recovery: latest editor snapshot was unreadable; no second \u53D1\u8868 click attempted");
23662
+ if (differences.length > 0)
23663
+ return fail(`public stale-version recovery: latest editor content mismatch (${differences.join(", ")}); no second \u53D1\u8868 click attempted`);
23664
+ if (!await waitForPublicEditorReadiness(latestEditor, deps))
23665
+ return fail("public stale-version recovery: latest editor did not provide two consecutive recognized readiness probes; no second \u53D1\u8868 click attempted");
23666
+ return {
23667
+ editor: latestEditor,
23668
+ canonicalUrl: latestCanonicalUrl,
23669
+ canonicalUrlForMetadata: savedEditorUrlForMetadata(latestCanonicalUrl)
23670
+ };
23671
+ }
23054
23672
  async function publishOutcome(page) {
23055
23673
  const success = await readScopedSignal(asPlaywrightPage(page), PUBLISH_SUCCESS);
23056
23674
  if (success !== null)
@@ -23060,14 +23678,112 @@ async function publishOutcome(page) {
23060
23678
  return `refused:${refusal}`;
23061
23679
  return null;
23062
23680
  }
23681
+ async function findUniqueVisibleSelector(page, selectors, timeoutMs, pollMs) {
23682
+ let reason = "missing";
23683
+ let selector;
23684
+ const found = await waitUntil(async () => {
23685
+ for (const candidate of selectors) {
23686
+ const locator = page.locator(candidate);
23687
+ const count = await locator.count().catch(() => 0);
23688
+ if (count > 1) {
23689
+ reason = "ambiguous";
23690
+ return true;
23691
+ }
23692
+ if (count !== 1)
23693
+ continue;
23694
+ const visible = await locator.first().isVisible().catch(() => false);
23695
+ if (!visible)
23696
+ continue;
23697
+ selector = candidate;
23698
+ return true;
23699
+ }
23700
+ return false;
23701
+ }, timeoutMs, pollMs);
23702
+ return found && selector !== undefined ? { selector, reason } : { reason };
23703
+ }
23704
+ async function readPublishNotifySubscribers(page) {
23705
+ try {
23706
+ const value = await page.evaluate(PUBLISH_NOTIFY_STATE_SCRIPT);
23707
+ return typeof value === "boolean" ? value : null;
23708
+ } catch {
23709
+ return null;
23710
+ }
23711
+ }
23712
+ function shouldNotifySubscribers(content) {
23713
+ const wechatmp = content.metadata?.wechatmp;
23714
+ if (wechatmp === null || typeof wechatmp !== "object" || Array.isArray(wechatmp))
23715
+ return true;
23716
+ return wechatmp.notifySubscribers !== false;
23717
+ }
23718
+ async function confirmPublicPublish(page, notifySubscribers, stepTimeoutMs, pollMs) {
23719
+ const confirmationTimeoutMs = Math.min(CONFIRM_TIMEOUT_MS, stepTimeoutMs);
23720
+ const dialog = await findUniqueVisibleSelector(page, PUBLISH_DIALOG, confirmationTimeoutMs, pollMs);
23721
+ if (dialog.selector === undefined) {
23722
+ if (dialog.reason === "missing") {
23723
+ const refusal = await readStaleVersionRefusal(asPlaywrightPage(page)).catch(() => null);
23724
+ if (refusal !== null)
23725
+ return { kind: "stale-version", refusal };
23726
+ }
23727
+ return fail(`public confirmation: the \u53D1\u8868 dialog was ${dialog.reason}; no final publish click attempted`);
23728
+ }
23729
+ let observedNotifySubscribers = null;
23730
+ if (!notifySubscribers) {
23731
+ const toggle = await findUniqueVisibleSelector(page, PUBLISH_NOTIFY_TOGGLE, confirmationTimeoutMs, pollMs);
23732
+ if (toggle.selector === undefined)
23733
+ return fail(`public confirmation: \u7FA4\u53D1\u901A\u77E5 switch was ${toggle.reason === "missing" ? "unavailable" : "ambiguous"}; no final publish click attempted`);
23734
+ const before = await readPublishNotifySubscribers(page);
23735
+ if (before === null)
23736
+ return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch state was unreadable; no final publish click attempted");
23737
+ if (before)
23738
+ await clickSelector(page, toggle.selector, stepTimeoutMs);
23739
+ const off = await waitUntil(async () => await readPublishNotifySubscribers(page) === false, confirmationTimeoutMs, pollMs);
23740
+ if (!off)
23741
+ return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch did not verify OFF; no final publish click attempted");
23742
+ }
23743
+ const final = await findUniqueVisibleSelector(page, PUBLISH_FINAL, confirmationTimeoutMs, pollMs);
23744
+ if (final.selector === undefined)
23745
+ return fail(`public confirmation: final \u53D1\u8868 button was ${final.reason}; no final publish click attempted`);
23746
+ observedNotifySubscribers = await readPublishNotifySubscribers(page);
23747
+ if (!notifySubscribers && observedNotifySubscribers !== false)
23748
+ return fail("public confirmation: \u7FA4\u53D1\u901A\u77E5 switch did not remain verifiably OFF; no final publish click attempted");
23749
+ await clickSelector(page, final.selector, stepTimeoutMs);
23750
+ return {
23751
+ finalSelector: final.selector,
23752
+ notifySubscribers: observedNotifySubscribers,
23753
+ notifySubscribersPolicy: notifySubscribers ? "preserve" : "disabled"
23754
+ };
23755
+ }
23063
23756
  async function publishContent(input, deps, entry, composed) {
23757
+ const saved = input.operation === "\u6587\u7AE0" ? await savePublicDraft(deps, entry) : undefined;
23758
+ if (saved !== undefined && "ok" in saved)
23759
+ return saved;
23760
+ const notifySubscribers = shouldNotifySubscribers(deps.content);
23761
+ let staleVersionRecoveryCount = 0;
23762
+ let finalSavedEditorUrl = saved?.canonicalUrlForMetadata;
23064
23763
  await sleep(deps.submitSettleMs);
23065
23764
  const control = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control", deps.stepTimeoutMs);
23066
23765
  const urlBefore = entry.editor.url();
23067
23766
  await clickSelector(entry.editor, control, deps.stepTimeoutMs);
23068
- const confirm = await tryFindSelector(entry.editor, PUBLISH_CONFIRM, CONFIRM_TIMEOUT_MS);
23069
- if (confirm !== undefined)
23070
- await clickSelector(entry.editor, confirm, deps.stepTimeoutMs);
23767
+ let confirmation = await confirmPublicPublish(entry.editor, notifySubscribers, deps.stepTimeoutMs, deps.pollMs);
23768
+ if ("ok" in confirmation)
23769
+ return confirmation;
23770
+ if ("kind" in confirmation) {
23771
+ if (saved === undefined)
23772
+ return fail(`public confirmation: ${confirmation.refusal}; no canonical saved-editor evidence was available for recovery; no second \u53D1\u8868 click attempted`);
23773
+ const recovered = await recoverStaleVersion(deps, entry, saved);
23774
+ if ("ok" in recovered)
23775
+ return recovered;
23776
+ entry.editor = recovered.editor;
23777
+ finalSavedEditorUrl = recovered.canonicalUrlForMetadata;
23778
+ staleVersionRecoveryCount = 1;
23779
+ const retryControl = await findSelector(entry.editor, PUBLISH_SEND, "\u53D1\u8868 control after \u67E5\u770B\u65B0\u8349\u7A3F", deps.stepTimeoutMs);
23780
+ await clickSelector(entry.editor, retryControl, deps.stepTimeoutMs);
23781
+ confirmation = await confirmPublicPublish(entry.editor, notifySubscribers, deps.stepTimeoutMs, deps.pollMs);
23782
+ if ("ok" in confirmation)
23783
+ return confirmation;
23784
+ if ("kind" in confirmation)
23785
+ return fail(`public confirmation: ${confirmation.refusal} after bounded \u67E5\u770B\u65B0\u8349\u7A3F recovery; no final publish click attempted`);
23786
+ }
23071
23787
  const outcome = await expectOutcome(() => publishOutcome(entry.editor), {
23072
23788
  timeoutMs: deps.stepTimeoutMs,
23073
23789
  label: `publish confirmation '${control}'`,
@@ -23085,7 +23801,16 @@ async function publishContent(input, deps, entry, composed) {
23085
23801
  message: `${TARGET} ${input.operation} published`,
23086
23802
  metadata: {
23087
23803
  ...baseMetadata(input, deps, entry),
23804
+ ...saved === undefined ? {} : {
23805
+ draftSaveControl: saved.saveControl,
23806
+ savedEditorUrl: finalSavedEditorUrl ?? saved.canonicalUrlForMetadata,
23807
+ draftSaveSignal: saved.saveSignal,
23808
+ staleVersionRecoveryCount
23809
+ },
23088
23810
  publishControl: control,
23811
+ finalPublishControl: confirmation.finalSelector,
23812
+ notifySubscribers: confirmation.notifySubscribers,
23813
+ notifySubscribersPolicy: confirmation.notifySubscribersPolicy,
23089
23814
  publishSignal: outcome.evidence,
23090
23815
  titlePath: composed.titlePath,
23091
23816
  ...composed.warnings.length > 0 ? { warnings: composed.warnings } : {},
@@ -23181,7 +23906,11 @@ async function runMode(input, deps) {
23181
23906
  const composed = await compose(input, deps, entry);
23182
23907
  const diagnostics = [...composed.warnings, ...composed.enrichWarnings];
23183
23908
  try {
23184
- return deps.publishStatus === "public" ? await publishContent(input, deps, entry, composed) : await saveDraft(input, deps, entry, composed);
23909
+ const result = deps.publishStatus === "public" ? await publishContent(input, deps, entry, composed) : await saveDraft(input, deps, entry, composed);
23910
+ if (!result.ok && diagnostics.length > 0) {
23911
+ return { ...result, error: `${result.error} \u2014 compose diagnostics: ${diagnostics.join(" | ")}` };
23912
+ }
23913
+ return result;
23185
23914
  } catch (error) {
23186
23915
  const message = error instanceof Error ? error.message : String(error);
23187
23916
  const blocked = await openDialogInventory(entry.editor);
@@ -23269,15 +23998,20 @@ function escapeHtml(text) {
23269
23998
  }
23270
23999
  async function insertAudioMaterial(page, input) {
23271
24000
  const { title, audioPath, coverPath } = input;
23272
- const CARET = `(() => {
24001
+ const CARET = `(async () => {
23273
24002
  const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
23274
24003
  if (!pm || !pm.firstChild) return;
24004
+ pm.focus();
23275
24005
  const r = document.createRange();
23276
- r.setStart(pm.firstChild, 0);
24006
+ // The first child can be a non-editable image atom. Select the boundary before it,
24007
+ // not a position inside that atom which ProseMirror discards on toolbar focus.
24008
+ r.setStart(pm, 0);
23277
24009
  r.collapse(true);
23278
24010
  const sel = window.getSelection();
23279
24011
  sel?.removeAllRanges();
23280
24012
  sel?.addRange(r);
24013
+ document.dispatchEvent(new Event('selectionchange'));
24014
+ await new Promise(resolve => setTimeout(resolve, 100));
23281
24015
  })()`;
23282
24016
  const norm = (value) => typeof value === "string" ? value.replace(/[\s\u00a0]+/g, " ").trim() : "";
23283
24017
  const MATCH = `(() => {
@@ -23299,8 +24033,8 @@ async function insertAudioMaterial(page, input) {
23299
24033
  return JSON.stringify({ state: 'checked', identity });
23300
24034
  })()`;
23301
24035
  const AGREE = `(() => {
23302
- const node = [...document.querySelectorAll('label, span, div')]
23303
- .find((e) => (e.textContent ?? '').includes('\u6211\u5DF2\u9605\u8BFB\u5E76\u540C\u610F') && e.children.length === 0);
24036
+ const node = [...document.querySelectorAll('.weui-desktop-dialog label, .audio_dialog label')]
24037
+ .find((e) => (e.textContent ?? '').includes('\u6211\u5DF2\u9605\u8BFB\u5E76\u540C\u610F') && e.querySelector('input[type=checkbox]'));
23304
24038
  if (!node) return 'no-label';
23305
24039
  const scope = node.closest('label') ?? node.parentElement;
23306
24040
  const cb = scope?.querySelector('input[type=checkbox]') ?? document.querySelector('input[type=checkbox].js_agree');
@@ -23323,9 +24057,10 @@ async function insertAudioMaterial(page, input) {
23323
24057
  if (!pm) return JSON.stringify({ state: 'noeditor' });
23324
24058
  const first = pm.firstElementChild;
23325
24059
  if (!first) return JSON.stringify({ state: 'absent' });
23326
- const node = first.matches('mpvoice, [data-mediaid]') ? first : first.querySelector('mpvoice, [data-mediaid]');
23327
- if (!node) return JSON.stringify({ state: 'absent' });
23328
- const identity = node.getAttribute('data-mediaid') ?? node.getAttribute('data-appid') ??
24060
+ const selector = 'mp-common-mpaudio, mpvoice, [data-mediaid]';
24061
+ const node = first.matches(selector) ? first : first.querySelector(selector);
24062
+ if (!node) return JSON.stringify({ state: pm.querySelector(selector) ? 'not-top' : 'absent' });
24063
+ const identity = node.getAttribute('data-mediaid') ?? node.getAttribute('cover') ?? node.getAttribute('voice_encode_fileid') ?? node.getAttribute('data-appid') ??
23329
24064
  node.getAttribute('data-src') ?? node.getAttribute('src') ?? '';
23330
24065
  return JSON.stringify({ state: 'top', identity });
23331
24066
  })()`;
@@ -23334,7 +24069,7 @@ async function insertAudioMaterial(page, input) {
23334
24069
  if (!uploadControl.found)
23335
24070
  return "\u97F3\u9891: \u4E0A\u4F20\u97F3\u9891 control not found in the audio dialog";
23336
24071
  await clickSelector(page, uploadControl.selector ?? UPLOAD_AUDIO[0] ?? "text=\u4E0A\u4F20\u97F3\u9891", 8000);
23337
- const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT);
24072
+ const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT, { visible: false });
23338
24073
  if (!audioInput.found)
23339
24074
  return "\u97F3\u9891: no audio file input appeared";
23340
24075
  await uploadFile(asPlaywrightPage(page), audioInput.selector ?? AUDIO_UPLOAD_INPUT.join(", "), audioPath, {
@@ -23343,11 +24078,63 @@ async function insertAudioMaterial(page, input) {
23343
24078
  const coverControl = await trySelectors(page, UPLOAD_COVER);
23344
24079
  if (coverControl.found) {
23345
24080
  await clickSelector(page, coverControl.selector ?? UPLOAD_COVER[0] ?? "text=\u4E0A\u4F20\u5C01\u9762", 8000);
23346
- const coverInput = await trySelectors(page, AUDIO_COVER_INPUT);
23347
- if (coverInput.found)
23348
- await uploadFile(asPlaywrightPage(page), coverInput.selector ?? AUDIO_COVER_INPUT.join(", "), coverPath, {
23349
- timeoutMs: 60000
23350
- });
24081
+ const coverInput = await trySelectors(page, AUDIO_COVER_INPUT, { visible: false });
24082
+ if (coverInput.found) {
24083
+ const chooser = '.weui-desktop-dialog:visible:has(h3:has-text("\u9009\u62E9\u56FE\u7247"))';
24084
+ const hasChooser = await page.locator(chooser).count() > 0;
24085
+ const coverName = hasChooser ? await coverLibraryName(coverPath) : baseName(coverPath);
24086
+ let existing = 0;
24087
+ if (hasChooser) {
24088
+ const libraryReady = await waitUntil(async () => Boolean(await page.evaluate(`!document.querySelector('#js_image_dialog_list_wrp .weui-desktop-loading')`)), 5000);
24089
+ if (!libraryReady)
24090
+ return "\u97F3\u9891: \u5C01\u9762\u7D20\u6750\u5E93\u52A0\u8F7D\u672A\u5B8C\u6210\uFF0C\u672A\u4E0A\u4F20\u91CD\u590D\u5C01\u9762";
24091
+ existing = Number(await page.evaluate(`([...document.querySelectorAll('.weui-desktop-img-picker__item')].filter(row => row.offsetWidth > 0 && row.querySelector('.weui-desktop-img-picker__img-title')?.textContent?.trim() === ${JSON.stringify(coverName)})).length`));
24092
+ if (existing > 1)
24093
+ return "\u97F3\u9891: \u5185\u5BB9\u7ED1\u5B9A\u5C01\u9762\u5B58\u5728\u591A\u4E2A\u540C\u540D\u7D20\u6750\uFF0C\u672A\u731C\u6D4B\u9009\u62E9";
24094
+ }
24095
+ let uploadPath = coverPath;
24096
+ let temporaryDir;
24097
+ if (hasChooser && existing === 0) {
24098
+ temporaryDir = join6(tmpdir(), `kk-wechat-cover-${randomUUID()}`);
24099
+ if (!mkdirExclusive(temporaryDir))
24100
+ throw new Error("Private cover upload directory already exists");
24101
+ uploadPath = join6(temporaryDir, coverName);
24102
+ }
24103
+ try {
24104
+ if (temporaryDir !== undefined)
24105
+ await createNodeFileSystem().copy(coverPath, uploadPath);
24106
+ if (existing === 0) {
24107
+ await uploadFile(asPlaywrightPage(page), coverInput.selector ?? AUDIO_COVER_INPUT.join(", "), uploadPath, {
24108
+ timeoutMs: 60000
24109
+ });
24110
+ }
24111
+ if (hasChooser) {
24112
+ const selectedName = JSON.stringify(coverName);
24113
+ const selected = await waitUntil(async () => Boolean(await page.evaluate(`(() => {
24114
+ const rows = [...document.querySelectorAll('.weui-desktop-img-picker__item')]
24115
+ .filter(row => row.offsetWidth > 0 && row.querySelector('.weui-desktop-img-picker__img-title')?.textContent?.trim() === ${selectedName});
24116
+ if (rows.length !== 1) return false;
24117
+ const row = rows[0];
24118
+ if (!row.classList.contains('selected')) row.click();
24119
+ return row.classList.contains('selected');
24120
+ })()`)), 30000);
24121
+ if (!selected)
24122
+ return "\u97F3\u9891: \u4E0A\u4F20\u5C01\u9762\u672A\u51FA\u73B0\u552F\u4E00\u5DF2\u9009\u4E2D\u7684\u540C\u540D\u56FE\u7247\uFF0C\u672A\u63D0\u4EA4\u5C01\u9762";
24123
+ const next = await findSelector(page, [`${chooser} button:has-text("\u4E0B\u4E00\u6B65"):not(.weui-desktop-btn_disabled)`], "\u97F3\u9891\u5C01\u9762 \u4E0B\u4E00\u6B65", 30000);
24124
+ await clickSelector(page, next, 8000);
24125
+ const finish = await findSelector(page, [`${chooser} button:has-text("\u5B8C\u6210"):not(.weui-desktop-btn_disabled)`], "\u97F3\u9891\u5C01\u9762 \u5B8C\u6210", 30000);
24126
+ await clickSelector(page, finish, 8000);
24127
+ const closed = await waitUntil(async () => !await page.locator(chooser).first().isVisible(), 8000);
24128
+ if (!closed)
24129
+ return "\u97F3\u9891: \u5C01\u9762\u9009\u62E9\u5668\u5B8C\u6210\u540E\u672A\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58\u97F3\u9891";
24130
+ }
24131
+ } finally {
24132
+ if (temporaryDir !== undefined) {
24133
+ await createNodeFileSystem().deleteFile(uploadPath);
24134
+ rmdir(temporaryDir);
24135
+ }
24136
+ }
24137
+ }
23351
24138
  }
23352
24139
  const titleInput = await trySelectors(page, AUDIO_TITLE_INPUT);
23353
24140
  if (titleInput.found)
@@ -23355,10 +24142,8 @@ async function insertAudioMaterial(page, input) {
23355
24142
  const agreed = String(await page.evaluate(AGREE));
23356
24143
  if (agreed !== "checked")
23357
24144
  return `\u97F3\u9891: \u670D\u52A1\u89C4\u5219\u52FE\u9009\u5931\u8D25 (${agreed})`;
23358
- const save = await trySelectors(page, UPLOAD_SAVE);
23359
- if (!save.found)
23360
- return "\u97F3\u9891: \u4E0A\u4F20\u8868\u5355\u7684\u4FDD\u5B58\u6309\u94AE\u672A\u627E\u5230";
23361
- await clickSelector(page, UPLOAD_SAVE.join(", "), 8000);
24145
+ const save = await findSelector(page, UPLOAD_SAVE.map((selector) => `${selector}:not(.weui-desktop-btn_disabled):not([disabled])`), "\u97F3\u9891\u4E0A\u4F20\u5B8C\u6210\u4E14\u5C01\u9762\u5DF2\u63D0\u4EA4\u540E\u7684\u4FDD\u5B58\u6309\u94AE", 90000);
24146
+ await clickSelector(page, save, 8000);
23362
24147
  return null;
23363
24148
  };
23364
24149
  const matchOnce = async () => {
@@ -23378,6 +24163,7 @@ async function insertAudioMaterial(page, input) {
23378
24163
  };
23379
24164
  try {
23380
24165
  await page.evaluate(CARET);
24166
+ await page.press?.(process.platform === "darwin" ? "Meta+ArrowUp" : "Control+Home");
23381
24167
  await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_TOOLBAR_TRIGGER)})?.click()`);
23382
24168
  await page.waitForTimeout?.(300);
23383
24169
  await page.evaluate(`document.querySelector(${JSON.stringify(AUDIO_MENU)})?.click()`);
@@ -23445,7 +24231,7 @@ var SUBMIT_SETTLE_MS = 1500;
23445
24231
  var DRAFT_SIGNAL_TIMEOUT_MS = 15000;
23446
24232
  var SUBMIT_ATTEMPTS = 2;
23447
24233
  function fail2(error) {
23448
- return ResultSchema.parse({ ok: false, target: TARGET, error });
24234
+ return ResultSchema.parse({ ok: false, target: TARGET, error: sanitizeSessionEvidence(error) });
23449
24235
  }
23450
24236
  function isConsoleReady(page) {
23451
24237
  return readConsoleToken(page.url()) !== undefined || page.url().includes(CONSOLE_HOME_FRAGMENT);
@@ -23456,6 +24242,25 @@ function isWechatLoggedIn(page) {
23456
24242
  function storageStatePath(profileDir) {
23457
24243
  return `${profileDir}/storage-state.json`;
23458
24244
  }
24245
+ async function persistWechatSessionState(context, profileDir) {
24246
+ const target = storageStatePath(profileDir);
24247
+ const temporary = `${target}.${process.pid}.${randomUUID2()}.tmp`;
24248
+ const fs = createNodeFileSystem();
24249
+ let reserved = false;
24250
+ try {
24251
+ createExclusiveFile(temporary);
24252
+ reserved = true;
24253
+ await context.storageState({ path: temporary });
24254
+ chmod(temporary, 384);
24255
+ await fs.rename(temporary, target);
24256
+ } finally {
24257
+ if (reserved) {
24258
+ await Promise.resolve().then(() => fs.deleteFile(temporary)).catch(() => {
24259
+ return;
24260
+ });
24261
+ }
24262
+ }
24263
+ }
23459
24264
  async function checkWechatSession(profileDir, opts = {}) {
23460
24265
  const { page, context } = await openWithState(profileDir, opts.headless ?? true);
23461
24266
  try {
@@ -23478,6 +24283,8 @@ async function openWechatLogin(opts) {
23478
24283
  headless: false,
23479
24284
  launcher: kkBrowserLauncher
23480
24285
  });
24286
+ let failed = false;
24287
+ let failure;
23481
24288
  try {
23482
24289
  const page = context.pages()[0] ?? await context.newPage();
23483
24290
  await page.goto(CONSOLE_URL);
@@ -23485,10 +24292,23 @@ async function openWechatLogin(opts) {
23485
24292
  if (!ready)
23486
24293
  throw new Error(`QR scan not completed within ${timeoutMs / 1000}s`);
23487
24294
  await page.waitForTimeout(1e4);
23488
- await context.storageState({ path: storageStatePath(opts.profileDir) });
23489
- } finally {
24295
+ try {
24296
+ await persistWechatSessionState(context, opts.profileDir);
24297
+ } catch {
24298
+ throw new Error("wechatmp authenticated session snapshot failed");
24299
+ }
24300
+ } catch (error) {
24301
+ failed = true;
24302
+ failure = error;
24303
+ }
24304
+ try {
23490
24305
  await context.close();
24306
+ } catch {
24307
+ if (!failed)
24308
+ echoError("wechatmp browser context close failed after authenticated session snapshot; saved state preserved");
23491
24309
  }
24310
+ if (failed)
24311
+ throw failure;
23492
24312
  }
23493
24313
  async function openWechatSession(profileDir, opts = {}) {
23494
24314
  const { context } = await openWithState(profileDir, opts.headless ?? false, opts.url ?? CONSOLE_URL);
@@ -23534,7 +24354,10 @@ async function openWithState(profileDir, headless, url = CONSOLE_URL) {
23534
24354
  }
23535
24355
  async function defaultOpenConsole(profileDir) {
23536
24356
  const { page, context } = await openWithState(profileDir, true);
23537
- return wrapPlaywrightContext(context, page);
24357
+ return {
24358
+ ...wrapPlaywrightContext(context, page),
24359
+ persistSession: () => persistWechatSessionState(context, profileDir)
24360
+ };
23538
24361
  }
23539
24362
 
23540
24363
  class WechatmpBrowserTransport {
@@ -23584,33 +24407,48 @@ class WechatmpBrowserTransport {
23584
24407
  } catch (error) {
23585
24408
  return fail2(error instanceof Error ? error.message : String(error));
23586
24409
  }
24410
+ let result;
23587
24411
  try {
23588
24412
  const ready = await waitUntil(() => Promise.resolve(isConsoleReady(opened.page)), this.consoleReadyTimeoutMs);
23589
24413
  if (!ready) {
23590
- return fail2(`no authenticated ${TARGET} console within ${this.consoleReadyTimeoutMs}ms \u2014 the preflight passed but the console did not report a session route (profile ${profileDir})`);
23591
- }
23592
- const title = typeof content.title === "string" ? content.title.trim() : "";
23593
- const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(opened.page, title) : { stamp: null };
23594
- return await runMode(validation.input, {
23595
- entry: opened.page,
23596
- pages: opened.pages,
23597
- newPage: opened.newPage,
23598
- content,
23599
- publishStatus,
23600
- profileDir,
23601
- driver: this.driver,
23602
- stepTimeoutMs: this.stepTimeoutMs,
23603
- pollMs: this.pollMs,
23604
- submitSettleMs: this.submitSettleMs,
23605
- draftTimeoutMs: this.draftTimeoutMs,
23606
- submitAttempts: this.submitAttempts,
23607
- draftBaseline
23608
- });
24414
+ result = fail2(`no authenticated ${TARGET} console within ${this.consoleReadyTimeoutMs}ms \u2014 the preflight passed but the console did not report a session route (profile ${profileDir})`);
24415
+ } else {
24416
+ const title = typeof content.title === "string" ? content.title.trim() : "";
24417
+ const draftBaseline = publishStatus === "draft" ? await readDraftBaseline(opened.page, title) : { stamp: null };
24418
+ result = await runMode(validation.input, {
24419
+ entry: opened.page,
24420
+ pages: opened.pages,
24421
+ newPage: opened.newPage,
24422
+ content,
24423
+ publishStatus,
24424
+ profileDir,
24425
+ driver: this.driver,
24426
+ stepTimeoutMs: this.stepTimeoutMs,
24427
+ pollMs: this.pollMs,
24428
+ submitSettleMs: this.submitSettleMs,
24429
+ draftTimeoutMs: this.draftTimeoutMs,
24430
+ submitAttempts: this.submitAttempts,
24431
+ draftBaseline
24432
+ });
24433
+ }
23609
24434
  } catch (error) {
23610
- return fail2(error instanceof Error ? error.message : String(error));
24435
+ result = fail2(error instanceof Error ? error.message : String(error));
23611
24436
  } finally {
23612
- await opened.close();
24437
+ if (opened.persistSession !== undefined) {
24438
+ try {
24439
+ if (isConsoleReady(opened.page))
24440
+ await opened.persistSession();
24441
+ } catch {
24442
+ echoError("wechatmp-pub: authenticated session snapshot failed; publication result preserved");
24443
+ }
24444
+ }
24445
+ try {
24446
+ await opened.close();
24447
+ } catch {
24448
+ echoError("wechatmp-pub: browser context close failed; publication result preserved");
24449
+ }
23613
24450
  }
24451
+ return result;
23614
24452
  }
23615
24453
  }
23616
24454
  function getPublishTransport(driver = "script") {
@@ -23663,6 +24501,7 @@ export {
23663
24501
  markdownToWechatHtml,
23664
24502
  openWechatLogin,
23665
24503
  openWechatSession,
24504
+ persistWechatSessionState,
23666
24505
  processPublishIO,
23667
24506
  runMode,
23668
24507
  storageStatePath,