@gobing-ai/knowledge-kit 0.1.14 → 0.1.15

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 (76) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +796 -176
  3. package/package.json +1 -1
  4. package/plugins/generations/content-gen/dist/index.js +2 -2
  5. package/plugins/generations/core-facts-gen/dist/index.js +2 -2
  6. package/plugins/generations/daily-article-gen/dist/index.js +2 -2
  7. package/plugins/generations/dailynews-gen/dist/index.js +72 -9
  8. package/plugins/generations/dailynews-gen/src/index.ts +23 -1
  9. package/plugins/generations/dailynews-gen/src/script-plan.ts +103 -5
  10. package/plugins/generations/episode-plan-gen/dist/index.js +2 -2
  11. package/plugins/generations/gemini-tts/dist/index.js +2 -2
  12. package/plugins/generations/image-gen/dist/index.js +2 -2
  13. package/plugins/generations/news-report-gen/dist/index.js +2 -2
  14. package/plugins/generations/voice-gen/dist/index.js +2 -2
  15. package/plugins/generations/voxcpm-tts/dist/index.js +2 -2
  16. package/plugins/ingestions/aihot-ingest/dist/index.js +2 -2
  17. package/plugins/ingestions/horizon-ingest/dist/index.js +2 -2
  18. package/plugins/ingestions/last30days-ingest/dist/index.js +2 -2
  19. package/plugins/ingestions/web-search/dist/index.js +2 -2
  20. package/plugins/kk/plugin.json +1 -1
  21. package/plugins/kk/scripts/daily-scheduled-run.ts +4 -0
  22. package/plugins/kk/scripts/daily-stages.ts +476 -12
  23. package/plugins/kk/scripts/kk-workflow-stages.ts +20 -4
  24. package/plugins/kk/skills/content-judge/SKILL.md +5 -4
  25. package/plugins/kk/skills/content-judge/references/rubrics.md +14 -0
  26. package/plugins/kk/skills/news-editorial/SKILL.md +122 -0
  27. package/plugins/kk/skills/news-editorial/references/selection.md +47 -0
  28. package/plugins/kk/skills/news-editorial/references/style-contract.zh.md +99 -0
  29. package/plugins/kk/skills/publish/SKILL.md +3 -3
  30. package/plugins/kk/skills/web-automation/SKILL.md +229 -77
  31. package/plugins/kk/skills/web-automation/references/browser-launch.md +127 -36
  32. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +4 -6
  33. package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +183 -222
  34. package/plugins/kk/workflows/kk-daily-publish3.yaml +16 -8
  35. package/plugins/publishings/bilibili-pub/dist/index.js +5 -3
  36. package/plugins/publishings/bilibili-pub/dist/profile-check.js +3 -1
  37. package/plugins/publishings/cnblogs-pub/dist/index.js +5 -3
  38. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +3 -1
  39. package/plugins/publishings/csdn-pub/dist/index.js +5 -3
  40. package/plugins/publishings/csdn-pub/dist/profile-check.js +5 -3
  41. package/plugins/publishings/emdash-pub/dist/index.js +2 -2
  42. package/plugins/publishings/infoq-pub/dist/index.js +5 -3
  43. package/plugins/publishings/infoq-pub/dist/profile-check.js +5 -3
  44. package/plugins/publishings/jike-pub/dist/index.js +5 -3
  45. package/plugins/publishings/jike-pub/dist/profile-check.js +5 -3
  46. package/plugins/publishings/juejin-pub/dist/index.js +5 -3
  47. package/plugins/publishings/juejin-pub/dist/profile-check.js +5 -3
  48. package/plugins/publishings/linkedin-pub/dist/index.js +5 -3
  49. package/plugins/publishings/linkedin-pub/dist/profile-check.js +5 -3
  50. package/plugins/publishings/medium-pub/dist/index.js +2 -2
  51. package/plugins/publishings/podcast-pub/dist/index.js +147 -4
  52. package/plugins/publishings/podcast-pub/src/index.ts +71 -2
  53. package/plugins/publishings/podcast-pub/src/map.ts +3 -0
  54. package/plugins/publishings/podcast-pub/src/transcript.ts +195 -0
  55. package/plugins/publishings/qiita-pub/dist/index.js +2 -2
  56. package/plugins/publishings/shipinhao-pub/dist/index.js +5 -3
  57. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +3 -1
  58. package/plugins/publishings/sspai-pub/dist/index.js +5 -3
  59. package/plugins/publishings/sspai-pub/dist/profile-check.js +3 -1
  60. package/plugins/publishings/substack-pub/dist/index.js +5 -3
  61. package/plugins/publishings/substack-pub/dist/profile-check.js +5 -3
  62. package/plugins/publishings/surfdash-pub/dist/index.js +2 -2
  63. package/plugins/publishings/threads-pub/dist/index.js +2 -2
  64. package/plugins/publishings/wechatmp-pub/dist/index.js +52 -33
  65. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +5 -3
  66. package/plugins/publishings/wechatmp-pub/src/flow.ts +70 -33
  67. package/plugins/publishings/weibo-pub/dist/index.js +5 -3
  68. package/plugins/publishings/weibo-pub/dist/profile-check.js +5 -3
  69. package/plugins/publishings/x-pub/dist/index.js +5 -3
  70. package/plugins/publishings/x-pub/dist/profile-check.js +5 -3
  71. package/plugins/publishings/xhs-pub/dist/index.js +5 -3
  72. package/plugins/publishings/xhs-pub/dist/profile-check.js +5 -3
  73. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +5 -3
  74. package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +3 -1
  75. package/plugins/publishings/zenn-pub/dist/index.js +2 -2
  76. package/plugins/kk/scripts/daily-voice-style.zh.md +0 -70
@@ -12317,11 +12317,11 @@ var PluginManifestSchemaBase = looseObject({
12317
12317
  });
12318
12318
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
12319
12319
  if (manifest.browserSession !== undefined) {
12320
- if (manifest.kind !== "publish") {
12320
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
12321
12321
  ctx.addIssue({
12322
12322
  code: "custom",
12323
12323
  path: ["browserSession"],
12324
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
12324
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
12325
12325
  });
12326
12326
  }
12327
12327
  if (manifest.check === undefined) {
@@ -12225,11 +12225,11 @@ var PluginManifestSchemaBase = looseObject({
12225
12225
  });
12226
12226
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
12227
12227
  if (manifest.browserSession !== undefined) {
12228
- if (manifest.kind !== "publish") {
12228
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
12229
12229
  ctx.addIssue({
12230
12230
  code: "custom",
12231
12231
  path: ["browserSession"],
12232
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
12232
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
12233
12233
  });
12234
12234
  }
12235
12235
  if (manifest.check === undefined) {
@@ -20073,11 +20073,11 @@ var PluginManifestSchemaBase = looseObject({
20073
20073
  });
20074
20074
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
20075
20075
  if (manifest.browserSession !== undefined) {
20076
- if (manifest.kind !== "publish") {
20076
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
20077
20077
  ctx.addIssue({
20078
20078
  code: "custom",
20079
20079
  path: ["browserSession"],
20080
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
20080
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
20081
20081
  });
20082
20082
  }
20083
20083
  if (manifest.check === undefined) {
@@ -20620,10 +20620,12 @@ function sha256Hex(value) {
20620
20620
  var DEFAULT_BOT_PROFILE_ROOT = join4(homedir4(), ".config", "kk", "browser-profiles");
20621
20621
  class BotError extends Error {
20622
20622
  code;
20623
- constructor(code, message) {
20623
+ details;
20624
+ constructor(code, message, details = {}) {
20624
20625
  super(message);
20625
20626
  this.name = "BotError";
20626
20627
  this.code = code;
20628
+ this.details = details;
20627
20629
  }
20628
20630
  }
20629
20631
  // ../../packages/publish-harness/src/profile-dir.ts
@@ -23237,17 +23239,19 @@ async function enrichArticleDraft(page, content, stepTimeoutMs) {
23237
23239
  const enrichWarnings = await enrichArticle(page, meta);
23238
23240
  const audio = audioBlockOf(content);
23239
23241
  let audioFailed = false;
23242
+ let coverSeeded = false;
23240
23243
  if (audio !== null) {
23241
- const audioWarning = await insertAudioMaterial(page, audio);
23242
- if (audioWarning !== "") {
23243
- enrichWarnings.push(audioWarning);
23244
+ const audioResult = await insertAudioMaterial(page, audio);
23245
+ coverSeeded = audioResult.coverSeeded;
23246
+ if (audioResult.warning !== "") {
23247
+ enrichWarnings.push(audioResult.warning);
23244
23248
  audioFailed = true;
23245
23249
  }
23246
23250
  }
23247
23251
  const coverWarnings = [];
23248
23252
  if (coverSourceOf(content) === "library" && audio !== null) {
23249
- if (audioFailed) {
23250
- 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");
23253
+ if (audioFailed && !coverSeeded) {
23254
+ coverWarnings.push("cover: \u97F3\u9891\u4E0A\u4F20\u672A\u6210\u529F\u4E14\u56FE\u7247\u5E93\u5C1A\u65E0\u8BF7\u6C42\u5C01\u9762\uFF0C\u5DF2\u8DF3\u8FC7\u56FE\u7247\u5E93\u5C01\u9762");
23251
23255
  } else {
23252
23256
  const cover = await setCover(page, "library", { stepTimeoutMs: Math.min(stepTimeoutMs, 8000) }, { binding: audio.coverPath === "" ? undefined : await coverLibraryName(audio.coverPath) });
23253
23257
  if (cover !== null)
@@ -24142,6 +24146,11 @@ function escapeHtml(text) {
24142
24146
  }
24143
24147
  async function insertAudioMaterial(page, input) {
24144
24148
  const { title, audioPath, coverPath } = input;
24149
+ let libraryHasCover = false;
24150
+ const result = (warning) => ({
24151
+ warning,
24152
+ coverSeeded: libraryHasCover
24153
+ });
24145
24154
  const CARET = `(async () => {
24146
24155
  const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('\u6807\u9898'));
24147
24156
  if (!pm || !pm.firstChild) return;
@@ -24211,11 +24220,11 @@ async function insertAudioMaterial(page, input) {
24211
24220
  const upload = async () => {
24212
24221
  const uploadControl = await trySelectors(page, UPLOAD_AUDIO);
24213
24222
  if (!uploadControl.found)
24214
- return "\u97F3\u9891: \u4E0A\u4F20\u97F3\u9891 control not found in the audio dialog";
24223
+ return result("\u97F3\u9891: \u4E0A\u4F20\u97F3\u9891 control not found in the audio dialog");
24215
24224
  await clickSelector(page, uploadControl.selector ?? UPLOAD_AUDIO[0] ?? "text=\u4E0A\u4F20\u97F3\u9891", 8000);
24216
24225
  const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT, { visible: false });
24217
24226
  if (!audioInput.found)
24218
- return "\u97F3\u9891: no audio file input appeared";
24227
+ return result("\u97F3\u9891: no audio file input appeared");
24219
24228
  await uploadFile(asPlaywrightPage(page), audioInput.selector ?? AUDIO_UPLOAD_INPUT.join(", "), audioPath, {
24220
24229
  timeoutMs: 60000
24221
24230
  });
@@ -24231,10 +24240,10 @@ async function insertAudioMaterial(page, input) {
24231
24240
  if (hasChooser) {
24232
24241
  const libraryReady = await waitUntil(async () => Boolean(await page.evaluate(`!document.querySelector('#js_image_dialog_list_wrp .weui-desktop-loading')`)), 5000);
24233
24242
  if (!libraryReady)
24234
- return "\u97F3\u9891: \u5C01\u9762\u7D20\u6750\u5E93\u52A0\u8F7D\u672A\u5B8C\u6210\uFF0C\u672A\u4E0A\u4F20\u91CD\u590D\u5C01\u9762";
24243
+ return result("\u97F3\u9891: \u5C01\u9762\u7D20\u6750\u5E93\u52A0\u8F7D\u672A\u5B8C\u6210\uFF0C\u672A\u4E0A\u4F20\u91CD\u590D\u5C01\u9762");
24235
24244
  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`));
24236
24245
  if (existing > 1)
24237
- return "\u97F3\u9891: \u5185\u5BB9\u7ED1\u5B9A\u5C01\u9762\u5B58\u5728\u591A\u4E2A\u540C\u540D\u7D20\u6750\uFF0C\u672A\u731C\u6D4B\u9009\u62E9";
24246
+ return result("\u97F3\u9891: \u5185\u5BB9\u7ED1\u5B9A\u5C01\u9762\u5B58\u5728\u591A\u4E2A\u540C\u540D\u7D20\u6750\uFF0C\u672A\u731C\u6D4B\u9009\u62E9");
24238
24247
  }
24239
24248
  let uploadPath = coverPath;
24240
24249
  let temporaryDir;
@@ -24263,14 +24272,24 @@ async function insertAudioMaterial(page, input) {
24263
24272
  return row.classList.contains('selected');
24264
24273
  })()`)), 30000);
24265
24274
  if (!selected)
24266
- 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";
24275
+ return result("\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");
24276
+ libraryHasCover = true;
24267
24277
  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);
24268
24278
  await clickSelector(page, next, 8000);
24269
24279
  const finish = await findSelector(page, [`${chooser} button:has-text("\u5B8C\u6210"):not(.weui-desktop-btn_disabled)`], "\u97F3\u9891\u5C01\u9762 \u5B8C\u6210", 30000);
24270
24280
  await clickSelector(page, finish, 8000);
24271
- const closed = await waitUntil(async () => !await page.locator(chooser).first().isVisible(), 8000);
24272
- if (!closed)
24273
- return "\u97F3\u9891: \u5C01\u9762\u9009\u62E9\u5668\u5B8C\u6210\u540E\u672A\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58\u97F3\u9891";
24281
+ let closed = await waitUntil(async () => !await page.locator(chooser).first().isVisible(), 25000);
24282
+ if (!closed) {
24283
+ const confirm = await tryFindSelector(page, [`${chooser} button:has-text("\u786E\u5B9A")`], 5000);
24284
+ if (confirm !== undefined) {
24285
+ await clickSelector(page, confirm, 8000);
24286
+ closed = await waitUntil(async () => !await page.locator(chooser).first().isVisible(), 1e4);
24287
+ }
24288
+ }
24289
+ if (!closed) {
24290
+ const inventory = await openDialogInventory(page);
24291
+ return result(`\u97F3\u9891: \u5C01\u9762\u9009\u62E9\u5668\u5B8C\u6210\u540E\u672A\u5173\u95ED\uFF0C\u672A\u4FDD\u5B58\u97F3\u9891${inventory ? ` (dialogs: ${inventory})` : ""}`);
24292
+ }
24274
24293
  }
24275
24294
  } finally {
24276
24295
  if (temporaryDir !== undefined) {
@@ -24285,10 +24304,10 @@ async function insertAudioMaterial(page, input) {
24285
24304
  await fillSelector(page, titleInput.selector ?? AUDIO_TITLE_INPUT.join(", "), title);
24286
24305
  const agreed = String(await page.evaluate(AGREE));
24287
24306
  if (agreed !== "checked")
24288
- return `\u97F3\u9891: \u670D\u52A1\u89C4\u5219\u52FE\u9009\u5931\u8D25 (${agreed})`;
24307
+ return result(`\u97F3\u9891: \u670D\u52A1\u89C4\u5219\u52FE\u9009\u5931\u8D25 (${agreed})`);
24289
24308
  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);
24290
24309
  await clickSelector(page, save, 8000);
24291
- return null;
24310
+ return result("");
24292
24311
  };
24293
24312
  const matchOnce = async () => {
24294
24313
  try {
@@ -24314,57 +24333,57 @@ async function insertAudioMaterial(page, input) {
24314
24333
  await waitForVisible(page, AUDIO_DIALOG, AUDIO_DIALOG_TIMEOUT_MS);
24315
24334
  let match = await awaitMatch();
24316
24335
  if (match === "missing") {
24317
- const uploadWarning = await upload();
24318
- if (uploadWarning !== null) {
24336
+ const uploadResult = await upload();
24337
+ if (uploadResult.warning !== "") {
24319
24338
  await page.evaluate(CANCEL);
24320
- return uploadWarning;
24339
+ return result(uploadResult.warning);
24321
24340
  }
24322
24341
  await waitForVisible(page, AUDIO_DIALOG, AUDIO_DIALOG_TIMEOUT_MS);
24323
24342
  match = await awaitMatch();
24324
24343
  if (match === "missing") {
24325
24344
  await page.evaluate(CANCEL);
24326
- return `\u97F3\u9891: \u4FDD\u5B58\u540E\u7D20\u6750\u5217\u8868\u672A\u51FA\u73B0\u300C${title}\u300D\uFF0C\u672A\u63D2\u5165\u97F3\u9891`;
24345
+ return result(`\u97F3\u9891: \u4FDD\u5B58\u540E\u7D20\u6750\u5217\u8868\u672A\u51FA\u73B0\u300C${title}\u300D\uFF0C\u672A\u63D2\u5165\u97F3\u9891`);
24327
24346
  }
24328
24347
  if (match === "error") {
24329
24348
  await page.evaluate(CANCEL);
24330
- return "\u97F3\u9891: \u4FDD\u5B58\u540E\u7D20\u6750\u5339\u914D\u63A2\u6D4B\u5931\u8D25\uFF0C\u672A\u63D2\u5165\u97F3\u9891";
24349
+ return result("\u97F3\u9891: \u4FDD\u5B58\u540E\u7D20\u6750\u5339\u914D\u63A2\u6D4B\u5931\u8D25\uFF0C\u672A\u63D2\u5165\u97F3\u9891");
24331
24350
  }
24332
24351
  } else if (match === "error") {
24333
24352
  await page.evaluate(CANCEL);
24334
- return "\u97F3\u9891: \u7D20\u6750\u5339\u914D\u63A2\u6D4B\u5931\u8D25\uFF0C\u672A\u63D2\u5165\u97F3\u9891";
24353
+ return result("\u97F3\u9891: \u7D20\u6750\u5339\u914D\u63A2\u6D4B\u5931\u8D25\uFF0C\u672A\u63D2\u5165\u97F3\u9891");
24335
24354
  }
24336
24355
  let identity = "";
24337
24356
  try {
24338
24357
  const parsed = JSON.parse(match);
24339
24358
  if (parsed.state !== "checked") {
24340
24359
  await page.evaluate(CANCEL);
24341
- return `\u97F3\u9891: \u7D20\u6750\u300C${title}\u300D${parsed.state === "ambiguous" ? "\u5217\u8868\u5339\u914D\u5230\u591A\u4E2A\u540C\u540D\u6761\u76EE\uFF0C\u8EAB\u4EFD\u4E0D\u552F\u4E00" : parsed.state === "nobox" ? "\u884C\u6CA1\u6709\u52FE\u9009\u6846" : "\u672A\u9009\u4E2D"}\uFF0C\u672A\u63D2\u5165\u97F3\u9891`;
24360
+ return result(`\u97F3\u9891: \u7D20\u6750\u300C${title}\u300D${parsed.state === "ambiguous" ? "\u5217\u8868\u5339\u914D\u5230\u591A\u4E2A\u540C\u540D\u6761\u76EE\uFF0C\u8EAB\u4EFD\u4E0D\u552F\u4E00" : parsed.state === "nobox" ? "\u884C\u6CA1\u6709\u52FE\u9009\u6846" : "\u672A\u9009\u4E2D"}\uFF0C\u672A\u63D2\u5165\u97F3\u9891`);
24342
24361
  }
24343
24362
  identity = parsed.identity ?? "";
24344
24363
  } catch {
24345
24364
  await page.evaluate(CANCEL);
24346
- return "\u97F3\u9891: \u7D20\u6750\u5339\u914D\u63A2\u6D4B\u8FD4\u56DE\u672A\u77E5\u7ED3\u679C\uFF0C\u672A\u63D2\u5165\u97F3\u9891";
24365
+ return result("\u97F3\u9891: \u7D20\u6750\u5339\u914D\u63A2\u6D4B\u8FD4\u56DE\u672A\u77E5\u7ED3\u679C\uFF0C\u672A\u63D2\u5165\u97F3\u9891");
24347
24366
  }
24348
24367
  if (await page.evaluate(INSERT) !== true) {
24349
24368
  await page.evaluate(CANCEL);
24350
- return "\u97F3\u9891: \u63D2\u5165\u6309\u94AE\u672A\u627E\u5230\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165";
24369
+ return result("\u97F3\u9891: \u63D2\u5165\u6309\u94AE\u672A\u627E\u5230\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165");
24351
24370
  }
24352
24371
  await page.waitForTimeout?.(2000);
24353
24372
  let verdict;
24354
24373
  try {
24355
24374
  verdict = JSON.parse(String(await page.evaluate(VERIFY)));
24356
24375
  } catch {
24357
- return "\u97F3\u9891: \u5DF2\u63D2\u5165\u4F46\u65E0\u6CD5\u8BFB\u53D6\u6B63\u6587\u72B6\u6001\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5";
24376
+ return result("\u97F3\u9891: \u5DF2\u63D2\u5165\u4F46\u65E0\u6CD5\u8BFB\u53D6\u6B63\u6587\u72B6\u6001\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5");
24358
24377
  }
24359
24378
  if (verdict.state === "absent" || verdict.state === "noeditor")
24360
- return "\u97F3\u9891: \u63D2\u5165\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5";
24379
+ return result("\u97F3\u9891: \u63D2\u5165\u672A\u751F\u6548\uFF0C\u8BF7\u624B\u52A8\u68C0\u67E5");
24361
24380
  if (verdict.state !== "top")
24362
- return "\u97F3\u9891: \u5DF2\u63D2\u5165\u4F46\u672A\u5728\u6587\u9996\uFF0C\u8BF7\u624B\u52A8\u8C03\u6574\u4F4D\u7F6E";
24381
+ return result("\u97F3\u9891: \u5DF2\u63D2\u5165\u4F46\u672A\u5728\u6587\u9996\uFF0C\u8BF7\u624B\u52A8\u8C03\u6574\u4F4D\u7F6E");
24363
24382
  if (identity === "" || (verdict.identity ?? "") === "" || verdict.identity !== identity)
24364
- return `\u97F3\u9891: \u5DF2\u63D2\u5165\u6587\u9996\u4F46\u65E0\u6CD5\u6838\u5BF9\u7D20\u6750\u8EAB\u4EFD\uFF08selected=${identity || "\u672A\u8BFB\u5230"} node=${(verdict.identity ?? "").slice(0, 60) || "\u672A\u8BFB\u5230"}\uFF09\uFF0C\u8BF7\u624B\u52A8\u6838\u5BF9`;
24365
- return "";
24383
+ return result(`\u97F3\u9891: \u5DF2\u63D2\u5165\u6587\u9996\u4F46\u65E0\u6CD5\u6838\u5BF9\u7D20\u6750\u8EAB\u4EFD\uFF08selected=${identity || "\u672A\u8BFB\u5230"} node=${(verdict.identity ?? "").slice(0, 60) || "\u672A\u8BFB\u5230"}\uFF09\uFF0C\u8BF7\u624B\u52A8\u6838\u5BF9`);
24384
+ return result("");
24366
24385
  } catch (error) {
24367
- return `\u97F3\u9891: ${error instanceof Error ? error.message.slice(0, 120) : "\u81EA\u52A8\u63D2\u5165\u5931\u8D25"}\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165`;
24386
+ return result(`\u97F3\u9891: ${error instanceof Error ? error.message.slice(0, 120) : "\u81EA\u52A8\u63D2\u5165\u5931\u8D25"}\uFF0C\u8BF7\u624B\u52A8\u63D2\u5165`);
24368
24387
  }
24369
24388
  }
24370
24389
 
@@ -20272,10 +20272,12 @@ function sha256Hex(value) {
20272
20272
  var DEFAULT_BOT_PROFILE_ROOT = join3(homedir3(), ".config", "kk", "browser-profiles");
20273
20273
  class BotError extends Error {
20274
20274
  code;
20275
- constructor(code, message) {
20275
+ details;
20276
+ constructor(code, message, details = {}) {
20276
20277
  super(message);
20277
20278
  this.name = "BotError";
20278
20279
  this.code = code;
20280
+ this.details = details;
20279
20281
  }
20280
20282
  }
20281
20283
  // ../../packages/publish-harness/src/index.ts
@@ -20670,11 +20672,11 @@ var PluginManifestSchemaBase = looseObject({
20670
20672
  });
20671
20673
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
20672
20674
  if (manifest.browserSession !== undefined) {
20673
- if (manifest.kind !== "publish") {
20675
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
20674
20676
  ctx.addIssue({
20675
20677
  code: "custom",
20676
20678
  path: ["browserSession"],
20677
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
20679
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
20678
20680
  });
20679
20681
  }
20680
20682
  if (manifest.check === undefined) {
@@ -1197,10 +1197,12 @@ export async function enrichArticleDraft(
1197
1197
  const enrichWarnings = await enrichArticle(page, meta);
1198
1198
  const audio = audioBlockOf(content);
1199
1199
  let audioFailed = false;
1200
+ let coverSeeded = false;
1200
1201
  if (audio !== null) {
1201
- const audioWarning = await insertAudioMaterial(page, audio);
1202
- if (audioWarning !== '') {
1203
- enrichWarnings.push(audioWarning);
1202
+ const audioResult = await insertAudioMaterial(page, audio);
1203
+ coverSeeded = audioResult.coverSeeded;
1204
+ if (audioResult.warning !== '') {
1205
+ enrichWarnings.push(audioResult.warning);
1204
1206
  audioFailed = true;
1205
1207
  }
1206
1208
  }
@@ -1209,8 +1211,8 @@ export async function enrichArticleDraft(
1209
1211
  // leaves the library without the requested cover, so the dependent picker must not run).
1210
1212
  const coverWarnings: string[] = [];
1211
1213
  if (coverSourceOf(content) === 'library' && audio !== null) {
1212
- if (audioFailed) {
1213
- coverWarnings.push('cover: 音频上传未成功,图片库不保证已有请求封面,已跳过图片库封面');
1214
+ if (audioFailed && !coverSeeded) {
1215
+ coverWarnings.push('cover: 音频上传未成功且图片库尚无请求封面,已跳过图片库封面');
1214
1216
  } else {
1215
1217
  const cover = await setCover(
1216
1218
  page,
@@ -2581,8 +2583,16 @@ function escapeHtml(text: string): string {
2581
2583
  export async function insertAudioMaterial(
2582
2584
  page: WechatPage,
2583
2585
  input: { title: string; audioPath: string; coverPath: string },
2584
- ): Promise<string> {
2586
+ ): Promise<{ warning: string; coverSeeded: boolean }> {
2585
2587
  const { title, audioPath, coverPath } = input;
2588
+ // The library demonstrably holds the episode cover once the picker verified the unique same-name
2589
+ // row (0260 R1 refined 2026-11-09): failures AFTER that point must not suppress the deferred
2590
+ // library cover — the skip guard is only honest for failures BEFORE the cover committed.
2591
+ let libraryHasCover = false;
2592
+ const result = (warning: string): { warning: string; coverSeeded: boolean } => ({
2593
+ warning,
2594
+ coverSeeded: libraryHasCover,
2595
+ });
2586
2596
  const CARET = `(async () => {
2587
2597
  const pm = [...document.querySelectorAll('.ProseMirror')].find((e) => !(e.getAttribute('data-placeholder') ?? '').includes('标题'));
2588
2598
  if (!pm || !pm.firstChild) return;
@@ -2655,15 +2665,15 @@ export async function insertAudioMaterial(
2655
2665
  return JSON.stringify({ state: 'top', identity });
2656
2666
  })()`;
2657
2667
 
2658
- /** Upload the audio + its cover, acknowledge the rules, and 保存. Returns a warning or null. */
2659
- const upload = async (): Promise<string | null> => {
2668
+ /** Upload the audio + its cover, acknowledge the rules, and 保存. Returns a structured outcome. */
2669
+ const upload = async (): Promise<{ warning: string; coverSeeded: boolean }> => {
2660
2670
  const uploadControl = await trySelectors(page, UPLOAD_AUDIO);
2661
- if (!uploadControl.found) return '音频: 上传音频 control not found in the audio dialog';
2671
+ if (!uploadControl.found) return result('音频: 上传音频 control not found in the audio dialog');
2662
2672
  // Click the winner, never a joined union: `text=…, css…` mixes selector engines and
2663
2673
  // Playwright parses the whole string as CSS — zero matches (DOM fixture 0260).
2664
2674
  await clickSelector(page, uploadControl.selector ?? UPLOAD_AUDIO[0] ?? 'text=上传音频', 8_000);
2665
2675
  const audioInput = await trySelectors(page, AUDIO_UPLOAD_INPUT, { visible: false });
2666
- if (!audioInput.found) return '音频: no audio file input appeared';
2676
+ if (!audioInput.found) return result('音频: no audio file input appeared');
2667
2677
  await uploadFile(asPlaywrightPage(page), audioInput.selector ?? AUDIO_UPLOAD_INPUT.join(', '), audioPath, {
2668
2678
  timeoutMs: 60_000,
2669
2679
  });
@@ -2687,13 +2697,13 @@ export async function insertAudioMaterial(
2687
2697
  ),
2688
2698
  5_000,
2689
2699
  );
2690
- if (!libraryReady) return '音频: 封面素材库加载未完成,未上传重复封面';
2700
+ if (!libraryReady) return result('音频: 封面素材库加载未完成,未上传重复封面');
2691
2701
  existing = Number(
2692
2702
  await page.evaluate(
2693
2703
  `([...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`,
2694
2704
  ),
2695
2705
  );
2696
- if (existing > 1) return '音频: 内容绑定封面存在多个同名素材,未猜测选择';
2706
+ if (existing > 1) return result('音频: 内容绑定封面存在多个同名素材,未猜测选择');
2697
2707
  }
2698
2708
  let uploadPath = coverPath;
2699
2709
  let temporaryDir: string | undefined;
@@ -2730,7 +2740,10 @@ export async function insertAudioMaterial(
2730
2740
  ),
2731
2741
  30_000,
2732
2742
  );
2733
- if (!selected) return '音频: 上传封面未出现唯一已选中的同名图片,未提交封面';
2743
+ if (!selected) return result('音频: 上传封面未出现唯一已选中的同名图片,未提交封面');
2744
+ // The picker just proved the library holds the unique same-name cover; every
2745
+ // failure past this point leaves the cover in the 图片库 (cover rescue valid).
2746
+ libraryHasCover = true;
2734
2747
  const next = await findSelector(
2735
2748
  page,
2736
2749
  [`${chooser} button:has-text("下一步"):not(.weui-desktop-btn_disabled)`],
@@ -2745,11 +2758,31 @@ export async function insertAudioMaterial(
2745
2758
  30_000,
2746
2759
  );
2747
2760
  await clickSelector(page, finish, 8_000);
2748
- const closed = await waitUntil(
2761
+ // Hardened like the 播客 walk: 完成 may bounce a crop-confirm page; wait long,
2762
+ // retry via 确定, and only then abandon the audio save.
2763
+ let closed = await waitUntil(
2749
2764
  async () => !(await page.locator(chooser).first().isVisible()),
2750
- 8_000,
2765
+ 25_000,
2751
2766
  );
2752
- if (!closed) return '音频: 封面选择器完成后未关闭,未保存音频';
2767
+ if (!closed) {
2768
+ const confirm = await tryFindSelector(page, [`${chooser} button:has-text("确定")`], 5_000);
2769
+ if (confirm !== undefined) {
2770
+ await clickSelector(page, confirm, 8_000);
2771
+ closed = await waitUntil(
2772
+ async () => !(await page.locator(chooser).first().isVisible()),
2773
+ 10_000,
2774
+ );
2775
+ }
2776
+ }
2777
+ if (!closed) {
2778
+ // Live 2026-11-09 this stall is real — capture WHAT is on screen (the
2779
+ // confirm dialog may not nest under the chooser selector, which would
2780
+ // explain the 确定 retry missing it) before giving up on the audio save.
2781
+ const inventory = await openDialogInventory(page);
2782
+ return result(
2783
+ `音频: 封面选择器完成后未关闭,未保存音频${inventory ? ` (dialogs: ${inventory})` : ''}`,
2784
+ );
2785
+ }
2753
2786
  }
2754
2787
  } finally {
2755
2788
  if (temporaryDir !== undefined) {
@@ -2762,7 +2795,7 @@ export async function insertAudioMaterial(
2762
2795
  const titleInput = await trySelectors(page, AUDIO_TITLE_INPUT);
2763
2796
  if (titleInput.found) await fillSelector(page, titleInput.selector ?? AUDIO_TITLE_INPUT.join(', '), title);
2764
2797
  const agreed = String(await page.evaluate(AGREE));
2765
- if (agreed !== 'checked') return `音频: 服务规则勾选失败 (${agreed})`;
2798
+ if (agreed !== 'checked') return result(`音频: 服务规则勾选失败 (${agreed})`);
2766
2799
  const save = await findSelector(
2767
2800
  page,
2768
2801
  UPLOAD_SAVE.map((selector) => `${selector}:not(.weui-desktop-btn_disabled):not([disabled])`),
@@ -2770,7 +2803,7 @@ export async function insertAudioMaterial(
2770
2803
  90_000,
2771
2804
  );
2772
2805
  await clickSelector(page, save, 8_000);
2773
- return null;
2806
+ return result('');
2774
2807
  };
2775
2808
 
2776
2809
  /** One MATCH read; a throwing probe stays 'error' (unknown, never treated as missing). */
@@ -2804,24 +2837,24 @@ export async function insertAudioMaterial(
2804
2837
  if (match === 'missing') {
2805
2838
  // No reusable material: upload one, then POLL until the new row actually exists — never
2806
2839
  // assume the upload auto-selected a default row (task 0260 AC2).
2807
- const uploadWarning = await upload();
2808
- if (uploadWarning !== null) {
2840
+ const uploadResult = await upload();
2841
+ if (uploadResult.warning !== '') {
2809
2842
  await page.evaluate(CANCEL);
2810
- return uploadWarning;
2843
+ return result(uploadResult.warning);
2811
2844
  }
2812
2845
  await waitForVisible(page, AUDIO_DIALOG, AUDIO_DIALOG_TIMEOUT_MS);
2813
2846
  match = await awaitMatch();
2814
2847
  if (match === 'missing') {
2815
2848
  await page.evaluate(CANCEL);
2816
- return `音频: 保存后素材列表未出现「${title}」,未插入音频`;
2849
+ return result(`音频: 保存后素材列表未出现「${title}」,未插入音频`);
2817
2850
  }
2818
2851
  if (match === 'error') {
2819
2852
  await page.evaluate(CANCEL);
2820
- return '音频: 保存后素材匹配探测失败,未插入音频';
2853
+ return result('音频: 保存后素材匹配探测失败,未插入音频');
2821
2854
  }
2822
2855
  } else if (match === 'error') {
2823
2856
  await page.evaluate(CANCEL);
2824
- return '音频: 素材匹配探测失败,未插入音频';
2857
+ return result('音频: 素材匹配探测失败,未插入音频');
2825
2858
  }
2826
2859
  // Parse the match; anything but a unique ticked row stays a warning (never first-item).
2827
2860
  let identity = '';
@@ -2829,32 +2862,36 @@ export async function insertAudioMaterial(
2829
2862
  const parsed = JSON.parse(match) as { state?: string; identity?: string };
2830
2863
  if (parsed.state !== 'checked') {
2831
2864
  await page.evaluate(CANCEL);
2832
- return `音频: 素材「${title}」${parsed.state === 'ambiguous' ? '列表匹配到多个同名条目,身份不唯一' : parsed.state === 'nobox' ? '行没有勾选框' : '未选中'},未插入音频`;
2865
+ return result(
2866
+ `音频: 素材「${title}」${parsed.state === 'ambiguous' ? '列表匹配到多个同名条目,身份不唯一' : parsed.state === 'nobox' ? '行没有勾选框' : '未选中'},未插入音频`,
2867
+ );
2833
2868
  }
2834
2869
  identity = parsed.identity ?? '';
2835
2870
  } catch {
2836
2871
  await page.evaluate(CANCEL);
2837
- return '音频: 素材匹配探测返回未知结果,未插入音频';
2872
+ return result('音频: 素材匹配探测返回未知结果,未插入音频');
2838
2873
  }
2839
2874
  if ((await page.evaluate(INSERT)) !== true) {
2840
2875
  await page.evaluate(CANCEL);
2841
- return '音频: 插入按钮未找到,请手动插入';
2876
+ return result('音频: 插入按钮未找到,请手动插入');
2842
2877
  }
2843
2878
  await page.waitForTimeout?.(2_000);
2844
2879
  let verdict: { state?: string; identity?: string };
2845
2880
  try {
2846
2881
  verdict = JSON.parse(String(await page.evaluate(VERIFY))) as { state?: string; identity?: string };
2847
2882
  } catch {
2848
- return '音频: 已插入但无法读取正文状态,请手动检查';
2883
+ return result('音频: 已插入但无法读取正文状态,请手动检查');
2849
2884
  }
2850
- if (verdict.state === 'absent' || verdict.state === 'noeditor') return '音频: 插入未生效,请手动检查';
2851
- if (verdict.state !== 'top') return '音频: 已插入但未在文首,请手动调整位置';
2885
+ if (verdict.state === 'absent' || verdict.state === 'noeditor') return result('音频: 插入未生效,请手动检查');
2886
+ if (verdict.state !== 'top') return result('音频: 已插入但未在文首,请手动调整位置');
2852
2887
  // Identity readback (0260 R2): success requires the selected identity to reappear on the
2853
2888
  // inserted node; an unreadable identity is an unverifiable warning, never a silent pass.
2854
2889
  if (identity === '' || (verdict.identity ?? '') === '' || verdict.identity !== identity)
2855
- return `音频: 已插入文首但无法核对素材身份(selected=${identity || '未读到'} node=${(verdict.identity ?? '').slice(0, 60) || '未读到'}),请手动核对`;
2856
- return '';
2890
+ return result(
2891
+ `音频: 已插入文首但无法核对素材身份(selected=${identity || '未读到'} node=${(verdict.identity ?? '').slice(0, 60) || '未读到'}),请手动核对`,
2892
+ );
2893
+ return result('');
2857
2894
  } catch (error) {
2858
- return `音频: ${error instanceof Error ? error.message.slice(0, 120) : '自动插入失败'},请手动插入`;
2895
+ return result(`音频: ${error instanceof Error ? error.message.slice(0, 120) : '自动插入失败'},请手动插入`);
2859
2896
  }
2860
2897
  }
@@ -20073,11 +20073,11 @@ var PluginManifestSchemaBase = looseObject({
20073
20073
  });
20074
20074
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
20075
20075
  if (manifest.browserSession !== undefined) {
20076
- if (manifest.kind !== "publish") {
20076
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
20077
20077
  ctx.addIssue({
20078
20078
  code: "custom",
20079
20079
  path: ["browserSession"],
20080
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
20080
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
20081
20081
  });
20082
20082
  }
20083
20083
  if (manifest.check === undefined) {
@@ -20701,10 +20701,12 @@ function sha256Hex(value) {
20701
20701
  var DEFAULT_BOT_PROFILE_ROOT = join4(homedir4(), ".config", "kk", "browser-profiles");
20702
20702
  class BotError extends Error {
20703
20703
  code;
20704
- constructor(code, message) {
20704
+ details;
20705
+ constructor(code, message, details = {}) {
20705
20706
  super(message);
20706
20707
  this.name = "BotError";
20707
20708
  this.code = code;
20709
+ this.details = details;
20708
20710
  }
20709
20711
  }
20710
20712
  // ../../packages/publish-harness/src/profile-dir.ts
@@ -20369,10 +20369,12 @@ function normalizeSessionIdentity(value) {
20369
20369
  }
20370
20370
  class BotError extends Error {
20371
20371
  code;
20372
- constructor(code, message) {
20372
+ details;
20373
+ constructor(code, message, details = {}) {
20373
20374
  super(message);
20374
20375
  this.name = "BotError";
20375
20376
  this.code = code;
20377
+ this.details = details;
20376
20378
  }
20377
20379
  }
20378
20380
  // ../../packages/publish-harness/src/index.ts
@@ -20945,11 +20947,11 @@ var PluginManifestSchemaBase = looseObject({
20945
20947
  });
20946
20948
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
20947
20949
  if (manifest.browserSession !== undefined) {
20948
- if (manifest.kind !== "publish") {
20950
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
20949
20951
  ctx.addIssue({
20950
20952
  code: "custom",
20951
20953
  path: ["browserSession"],
20952
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
20954
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
20953
20955
  });
20954
20956
  }
20955
20957
  if (manifest.check === undefined) {
@@ -20074,11 +20074,11 @@ var PluginManifestSchemaBase = looseObject({
20074
20074
  });
20075
20075
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
20076
20076
  if (manifest.browserSession !== undefined) {
20077
- if (manifest.kind !== "publish") {
20077
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
20078
20078
  ctx.addIssue({
20079
20079
  code: "custom",
20080
20080
  path: ["browserSession"],
20081
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
20081
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
20082
20082
  });
20083
20083
  }
20084
20084
  if (manifest.check === undefined) {
@@ -20726,10 +20726,12 @@ function sha256Hex(value) {
20726
20726
  var DEFAULT_BOT_PROFILE_ROOT = join4(homedir4(), ".config", "kk", "browser-profiles");
20727
20727
  class BotError extends Error {
20728
20728
  code;
20729
- constructor(code, message) {
20729
+ details;
20730
+ constructor(code, message, details = {}) {
20730
20731
  super(message);
20731
20732
  this.name = "BotError";
20732
20733
  this.code = code;
20734
+ this.details = details;
20733
20735
  }
20734
20736
  }
20735
20737
  // ../../packages/publish-harness/src/profile-dir.ts
@@ -20369,10 +20369,12 @@ function normalizeSessionIdentity(value) {
20369
20369
  }
20370
20370
  class BotError extends Error {
20371
20371
  code;
20372
- constructor(code, message) {
20372
+ details;
20373
+ constructor(code, message, details = {}) {
20373
20374
  super(message);
20374
20375
  this.name = "BotError";
20375
20376
  this.code = code;
20377
+ this.details = details;
20376
20378
  }
20377
20379
  }
20378
20380
  // ../../packages/publish-harness/src/index.ts
@@ -20945,11 +20947,11 @@ var PluginManifestSchemaBase = looseObject({
20945
20947
  });
20946
20948
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
20947
20949
  if (manifest.browserSession !== undefined) {
20948
- if (manifest.kind !== "publish") {
20950
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
20949
20951
  ctx.addIssue({
20950
20952
  code: "custom",
20951
20953
  path: ["browserSession"],
20952
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
20954
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
20953
20955
  });
20954
20956
  }
20955
20957
  if (manifest.check === undefined) {
@@ -20073,11 +20073,11 @@ var PluginManifestSchemaBase = looseObject({
20073
20073
  });
20074
20074
  var PluginManifestSchema = PluginManifestSchemaBase.superRefine((manifest, ctx) => {
20075
20075
  if (manifest.browserSession !== undefined) {
20076
- if (manifest.kind !== "publish") {
20076
+ if (manifest.kind !== "publish" && manifest.kind !== "ingestion") {
20077
20077
  ctx.addIssue({
20078
20078
  code: "custom",
20079
20079
  path: ["browserSession"],
20080
- message: `browserSession requires kind "publish", not "${manifest.kind}"`
20080
+ message: `browserSession requires kind "publish" or "ingestion", not "${manifest.kind}"`
20081
20081
  });
20082
20082
  }
20083
20083
  if (manifest.check === undefined) {
@@ -20701,10 +20701,12 @@ function sha256Hex(value) {
20701
20701
  var DEFAULT_BOT_PROFILE_ROOT = join4(homedir4(), ".config", "kk", "browser-profiles");
20702
20702
  class BotError extends Error {
20703
20703
  code;
20704
- constructor(code, message) {
20704
+ details;
20705
+ constructor(code, message, details = {}) {
20705
20706
  super(message);
20706
20707
  this.name = "BotError";
20707
20708
  this.code = code;
20709
+ this.details = details;
20708
20710
  }
20709
20711
  }
20710
20712
  // ../../packages/publish-harness/src/profile-dir.ts