@gobing-ai/knowledge-kit 0.1.10 → 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 (46) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/dist/index.js +128 -25
  3. package/package.json +1 -1
  4. package/plugins/kk/plugin.json +1 -1
  5. package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
  6. package/plugins/kk/scripts/daily-stages.ts +1057 -108
  7. package/plugins/kk/scripts/kk-workflow-stages.ts +76 -10
  8. package/plugins/kk/scripts/safety-artifact-check.ts +235 -0
  9. package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +168 -121
  10. package/plugins/kk/workflows/kk-daily-safety.yaml +129 -50
  11. package/plugins/publishings/bilibili-pub/dist/index.js +2 -1
  12. package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
  13. package/plugins/publishings/cnblogs-pub/dist/index.js +2 -1
  14. package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
  15. package/plugins/publishings/csdn-pub/dist/index.js +2 -1
  16. package/plugins/publishings/csdn-pub/dist/profile-check.js +2 -1
  17. package/plugins/publishings/infoq-pub/dist/index.js +2 -1
  18. package/plugins/publishings/infoq-pub/dist/profile-check.js +2 -1
  19. package/plugins/publishings/jike-pub/dist/index.js +2 -1
  20. package/plugins/publishings/jike-pub/dist/profile-check.js +2 -1
  21. package/plugins/publishings/juejin-pub/dist/index.js +2 -1
  22. package/plugins/publishings/juejin-pub/dist/profile-check.js +2 -1
  23. package/plugins/publishings/podcast-pub/dist/index.js +46 -8
  24. package/plugins/publishings/podcast-pub/src/index.ts +62 -5
  25. package/plugins/publishings/shipinhao-pub/dist/index.js +2 -1
  26. package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
  27. package/plugins/publishings/sspai-pub/dist/index.js +2 -1
  28. package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
  29. package/plugins/publishings/substack-pub/dist/index.js +2 -1
  30. package/plugins/publishings/substack-pub/dist/profile-check.js +2 -1
  31. package/plugins/publishings/surfdash-pub/dist/index.js +460 -52
  32. package/plugins/publishings/surfdash-pub/src/digest-check.ts +85 -19
  33. package/plugins/publishings/surfdash-pub/src/index.ts +589 -64
  34. package/plugins/publishings/wechatmp-pub/dist/index.js +933 -98
  35. package/plugins/publishings/wechatmp-pub/dist/profile-check.js +60 -3
  36. package/plugins/publishings/wechatmp-pub/src/flow.ts +1125 -85
  37. package/plugins/publishings/wechatmp-pub/src/index.ts +112 -30
  38. package/plugins/publishings/wechatmp-pub/src/page.ts +105 -0
  39. package/plugins/publishings/weibo-pub/dist/index.js +2 -1
  40. package/plugins/publishings/weibo-pub/dist/profile-check.js +2 -1
  41. package/plugins/publishings/x-pub/dist/index.js +2 -1
  42. package/plugins/publishings/x-pub/dist/profile-check.js +2 -1
  43. package/plugins/publishings/xhs-pub/dist/index.js +2 -1
  44. package/plugins/publishings/xhs-pub/dist/profile-check.js +2 -1
  45. package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +2 -1
  46. package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +2 -1
@@ -1,3 +1,4 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import { readFileSync } from 'node:fs';
2
3
  import { readFile } from 'node:fs/promises';
3
4
  import { basename, dirname, extname } from 'node:path';
@@ -306,29 +307,72 @@ function shapedEpisode(episode: EpisodeMapping, fields: PreparedEpisodeFields):
306
307
  };
307
308
  }
308
309
 
310
+ /**
311
+ * Canonical JSON for the create request fingerprint. The API's idempotency window is only 24h,
312
+ * so the key must be stable across a process restart while remaining different for a changed body.
313
+ * Object-key ordering is normalized because callers build the same logical payload in more than
314
+ * one branch (hosted audio and the draft half of local-audio publishing).
315
+ */
316
+ function canonicalJson(value: unknown): string {
317
+ if (value === null || value === undefined) return 'null';
318
+ if (typeof value === 'string' || typeof value === 'boolean') return JSON.stringify(value);
319
+ if (typeof value === 'number') return Number.isFinite(value) ? JSON.stringify(value) : 'null';
320
+ if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`;
321
+ if (typeof value === 'object') {
322
+ const entries = Object.entries(value as Record<string, unknown>)
323
+ .filter(([, entry]) => entry !== undefined)
324
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0));
325
+ return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(',')}}`;
326
+ }
327
+ return JSON.stringify(String(value));
328
+ }
329
+
330
+ /** RFC 4122 UUIDv5-shaped key; microfeed validates Idempotency-Key as a UUID. */
331
+ function deterministicUuid(name: string): string {
332
+ // The URL namespace is only a stable namespace marker; the payload remains the name input.
333
+ const namespace = Buffer.from('6ba7b8119dad11d180b400c04fd430c8', 'hex');
334
+ const digest = createHash('sha1').update(namespace).update(name, 'utf8').digest();
335
+ digest[6] = ((digest[6] ?? 0) & 0x0f) | 0x50;
336
+ digest[8] = ((digest[8] ?? 0) & 0x3f) | 0x80;
337
+ const hex = digest.subarray(0, 16).toString('hex');
338
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
339
+ }
340
+
341
+ /**
342
+ * Derives the caller-supplied create key from the exact request body and its primary media.
343
+ * Local audio uses content identity rather than its temporary path, so a resumed run can replay
344
+ * the same draft even when the workspace path changes.
345
+ */
346
+ function createItemIdempotencyKey(body: MicrofeedItemInput, mediaIdentity: string): string {
347
+ return deterministicUuid(canonicalJson({ mediaIdentity, payload: body }));
348
+ }
349
+
309
350
  /** First createItem carries the slug. A 409 retries once as `{slug}-2`, then publishes with no slug. */
310
351
  async function createEpisodeItem(
311
352
  client: MicrofeedClient,
312
353
  body: MicrofeedItemInput,
313
354
  slug: string | undefined,
314
355
  warnings: string[],
356
+ mediaIdentity: string,
315
357
  ): Promise<string> {
358
+ const create = (input: MicrofeedItemInput): Promise<string> =>
359
+ client.createItem(input, createItemIdempotencyKey(input, mediaIdentity));
316
360
  const withSlug = (itemSlug: string): MicrofeedItemInput => {
317
361
  const micro = (body._microfeed as Record<string, unknown> | undefined) ?? {};
318
362
  return { ...body, _microfeed: { ...micro, slug: itemSlug } };
319
363
  };
320
- if (!slug) return client.createItem(body, crypto.randomUUID());
364
+ if (!slug) return create(body);
321
365
  try {
322
- return await client.createItem(withSlug(slug), crypto.randomUUID());
366
+ return await create(withSlug(slug));
323
367
  } catch (error: unknown) {
324
368
  if (!(error instanceof MicrofeedRequestError) || error.status !== 409) throw error;
325
369
  const retry = `${slug}-2`;
326
370
  try {
327
- return await client.createItem(withSlug(retry), crypto.randomUUID());
371
+ return await create(withSlug(retry));
328
372
  } catch (retryError: unknown) {
329
373
  if (!(retryError instanceof MicrofeedRequestError) || retryError.status !== 409) throw retryError;
330
374
  warn(`slug "${slug}" conflicted (HTTP 409); publishing without a custom slug`, warnings);
331
- return client.createItem(body, crypto.randomUUID());
375
+ return create(body);
332
376
  }
333
377
  }
334
378
  }
@@ -464,6 +508,15 @@ async function publishEpisode(
464
508
  const seo = mergeSocial(fields.seo, social);
465
509
  episode = shapedEpisode(episode, fields);
466
510
 
511
+ let localAudioBytes: Uint8Array | undefined;
512
+ let mediaIdentity: string;
513
+ if (episode.audio.kind === 'url') {
514
+ mediaIdentity = `audio-url:${episode.audio.url}\0${episode.audio.mimeType}`;
515
+ } else {
516
+ localAudioBytes = new Uint8Array(await readFile(episode.audio.path));
517
+ mediaIdentity = `audio-file-sha256:${createHash('sha256').update(localAudioBytes).digest('hex')}\0${episode.audio.mimeType}`;
518
+ }
519
+
467
520
  if (episode.audio.kind === 'url') {
468
521
  const itemId = await createEpisodeItem(
469
522
  client,
@@ -476,6 +529,7 @@ async function publishEpisode(
476
529
  },
477
530
  fields.slug,
478
531
  fields.warnings,
532
+ mediaIdentity,
479
533
  );
480
534
  // Best-effort canonical-URL enrichment: the episode is already published, so a failed
481
535
  // follow-up GET (or an unparsable response) falls back to publicItemUrl's own chain.
@@ -506,10 +560,13 @@ async function publishEpisode(
506
560
  },
507
561
  fields.slug,
508
562
  fields.warnings,
563
+ mediaIdentity,
509
564
  );
510
565
 
511
566
  // Phase 2-3: presign against the draft, then PUT the raw bytes.
512
- const bytes = new Uint8Array(await readFile(episode.audio.path));
567
+ // The bytes were read before create so their digest can make a resumed create idempotent.
568
+ const bytes = localAudioBytes;
569
+ if (!bytes) return fail('local audio identity was not prepared before item creation');
513
570
  const prepared = await client.prepareUpload({
514
571
  category: 'audio',
515
572
  full_local_file_path: episode.audio.path,
@@ -20440,9 +20440,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
20440
20440
  // ../../packages/publish-harness/src/bot-fs.ts
20441
20441
  import {
20442
20442
  chmodSync,
20443
+ closeSync,
20443
20444
  copyFileSync,
20444
20445
  lstatSync,
20445
20446
  mkdirSync as mkdirSync2,
20447
+ openSync,
20446
20448
  readdirSync as readdirSync2,
20447
20449
  realpathSync as realpathSync2,
20448
20450
  rmdirSync
@@ -20687,7 +20689,6 @@ class BotError extends Error {
20687
20689
  this.code = code;
20688
20690
  }
20689
20691
  }
20690
-
20691
20692
  // ../../packages/publish-harness/src/profile-dir.ts
20692
20693
  import { mkdir as mkdir2, stat } from "fs/promises";
20693
20694
  async function ensureProfileDir2(profileDir, headless) {
@@ -19847,9 +19847,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
19847
19847
  // ../../packages/publish-harness/src/bot-fs.ts
19848
19848
  import {
19849
19849
  chmodSync,
19850
+ closeSync,
19850
19851
  copyFileSync,
19851
19852
  lstatSync,
19852
19853
  mkdirSync as mkdirSync2,
19854
+ openSync,
19853
19855
  readdirSync as readdirSync2,
19854
19856
  realpathSync as realpathSync2,
19855
19857
  rmdirSync
@@ -20101,7 +20103,6 @@ class BotError extends Error {
20101
20103
  this.code = code;
20102
20104
  }
20103
20105
  }
20104
-
20105
20106
  // ../../packages/publish-harness/src/index.ts
20106
20107
  import { homedir as homedir4 } from "os";
20107
20108
  import { join as join4, resolve as resolve4 } from "path";
@@ -20464,9 +20464,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
20464
20464
  // ../../packages/publish-harness/src/bot-fs.ts
20465
20465
  import {
20466
20466
  chmodSync,
20467
+ closeSync,
20467
20468
  copyFileSync,
20468
20469
  lstatSync,
20469
20470
  mkdirSync as mkdirSync2,
20471
+ openSync,
20470
20472
  readdirSync as readdirSync2,
20471
20473
  realpathSync as realpathSync2,
20472
20474
  rmdirSync
@@ -20711,7 +20713,6 @@ class BotError extends Error {
20711
20713
  this.code = code;
20712
20714
  }
20713
20715
  }
20714
-
20715
20716
  // ../../packages/publish-harness/src/profile-dir.ts
20716
20717
  import { mkdir as mkdir2, stat } from "fs/promises";
20717
20718
  async function ensureProfileDir2(profileDir, headless) {
@@ -19847,9 +19847,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
19847
19847
  // ../../packages/publish-harness/src/bot-fs.ts
19848
19848
  import {
19849
19849
  chmodSync,
19850
+ closeSync,
19850
19851
  copyFileSync,
19851
19852
  lstatSync,
19852
19853
  mkdirSync as mkdirSync2,
19854
+ openSync,
19853
19855
  readdirSync as readdirSync2,
19854
19856
  realpathSync as realpathSync2,
19855
19857
  rmdirSync
@@ -20101,7 +20103,6 @@ class BotError extends Error {
20101
20103
  this.code = code;
20102
20104
  }
20103
20105
  }
20104
-
20105
20106
  // ../../packages/publish-harness/src/index.ts
20106
20107
  import { homedir as homedir4 } from "os";
20107
20108
  import { join as join4, resolve as resolve4 } from "path";
@@ -20458,9 +20458,11 @@ import { isAbsolute, join as join4, relative, resolve as resolve4, sep } from "p
20458
20458
  // ../../packages/publish-harness/src/bot-fs.ts
20459
20459
  import {
20460
20460
  chmodSync,
20461
+ closeSync,
20461
20462
  copyFileSync,
20462
20463
  lstatSync,
20463
20464
  mkdirSync as mkdirSync2,
20465
+ openSync,
20464
20466
  readdirSync as readdirSync2,
20465
20467
  realpathSync as realpathSync2,
20466
20468
  rmdirSync
@@ -20705,7 +20707,6 @@ class BotError extends Error {
20705
20707
  this.code = code;
20706
20708
  }
20707
20709
  }
20708
-
20709
20710
  // ../../packages/publish-harness/src/profile-dir.ts
20710
20711
  import { mkdir as mkdir2, stat } from "fs/promises";
20711
20712
  async function ensureProfileDir2(profileDir, headless) {
@@ -20119,9 +20119,11 @@ import { isAbsolute, join as join3, relative, resolve as resolve3, sep } from "p
20119
20119
  // ../../packages/publish-harness/src/bot-fs.ts
20120
20120
  import {
20121
20121
  chmodSync,
20122
+ closeSync,
20122
20123
  copyFileSync,
20123
20124
  lstatSync,
20124
20125
  mkdirSync as mkdirSync2,
20126
+ openSync,
20125
20127
  readdirSync as readdirSync2,
20126
20128
  realpathSync as realpathSync2,
20127
20129
  rmdirSync
@@ -20373,7 +20375,6 @@ class BotError extends Error {
20373
20375
  this.code = code;
20374
20376
  }
20375
20377
  }
20376
-
20377
20378
  // ../../packages/publish-harness/src/index.ts
20378
20379
  import { homedir as homedir4 } from "os";
20379
20380
  import { join as join4, resolve as resolve4 } from "path";