weflow-cli 1.9.0 → 1.10.0

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 (128) hide show
  1. package/ARCHITECTURE.md +62 -26
  2. package/CHANGELOG.md +948 -0
  3. package/OPERATIONS.md +65 -11
  4. package/README.en.md +14 -5
  5. package/README.md +21 -6
  6. package/bin/weflow-cli.ts +348 -39
  7. package/dist/bin/weflow-cli.js +363 -68
  8. package/dist/bin/weflow-cli.js.map +1 -1
  9. package/dist/mcp-server/index.d.ts.map +1 -1
  10. package/dist/mcp-server/index.js +19 -1
  11. package/dist/mcp-server/index.js.map +1 -1
  12. package/dist/src/core/dbPathService.d.ts +12 -0
  13. package/dist/src/core/dbPathService.d.ts.map +1 -1
  14. package/dist/src/core/dbPathService.js +20 -3
  15. package/dist/src/core/dbPathService.js.map +1 -1
  16. package/dist/src/panel/server.d.ts.map +1 -1
  17. package/dist/src/panel/server.js +35 -0
  18. package/dist/src/panel/server.js.map +1 -1
  19. package/dist/src/services/assistantService.d.ts +19 -0
  20. package/dist/src/services/assistantService.d.ts.map +1 -1
  21. package/dist/src/services/assistantService.js +20 -0
  22. package/dist/src/services/assistantService.js.map +1 -1
  23. package/dist/src/services/assistantTools.d.ts +27 -1
  24. package/dist/src/services/assistantTools.d.ts.map +1 -1
  25. package/dist/src/services/assistantTools.js +169 -35
  26. package/dist/src/services/assistantTools.js.map +1 -1
  27. package/dist/src/services/configService.d.ts +2 -0
  28. package/dist/src/services/configService.d.ts.map +1 -1
  29. package/dist/src/services/configService.js +3 -2
  30. package/dist/src/services/configService.js.map +1 -1
  31. package/dist/src/services/messageQuery.d.ts +18 -0
  32. package/dist/src/services/messageQuery.d.ts.map +1 -1
  33. package/dist/src/services/messageQuery.js +29 -0
  34. package/dist/src/services/messageQuery.js.map +1 -1
  35. package/dist/src/services/wechatClient.d.ts +125 -0
  36. package/dist/src/services/wechatClient.d.ts.map +1 -0
  37. package/dist/src/services/wechatClient.js +344 -0
  38. package/dist/src/services/wechatClient.js.map +1 -0
  39. package/dist/src/services/wechatMedia.d.ts +44 -0
  40. package/dist/src/services/wechatMedia.d.ts.map +1 -0
  41. package/dist/src/services/wechatMedia.js +155 -0
  42. package/dist/src/services/wechatMedia.js.map +1 -0
  43. package/dist/src/services/wechatMessageService.d.ts +11 -0
  44. package/dist/src/services/wechatMessageService.d.ts.map +1 -1
  45. package/dist/src/services/wechatMessageService.js +55 -11
  46. package/dist/src/services/wechatMessageService.js.map +1 -1
  47. package/dist/src/types.d.ts +4 -0
  48. package/dist/src/types.d.ts.map +1 -1
  49. package/docs/CONTACT_DB_SCHEMA.md +1085 -0
  50. package/docs/DECISIONS.md +1274 -0
  51. package/docs/EXTENDING.md +69 -7
  52. package/docs/FUNCTIONAL-IMPROVEMENT-SPEC.md +6 -0
  53. package/docs/HEALTH-CHECK.md +12 -7
  54. package/docs/JEV/345/272/224/347/224/250/346/226/275/345/267/245/346/226/207/346/241/243.md +2 -2
  55. package/docs/MCP.md +2 -2
  56. package/docs/PROJECT_STATE.md +62 -5
  57. package/docs/RELEASING.md +115 -1
  58. package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
  59. package/logo.png +0 -0
  60. package/mcp-server/index.ts +18 -3
  61. package/package.json +5 -3
  62. package/resources/js/graph3d/NOTICE.txt +35 -0
  63. package/resources/js/graph3d/d3-binarytree.min.js +2 -0
  64. package/resources/js/graph3d/d3-dispatch.min.js +2 -0
  65. package/resources/js/graph3d/d3-force-3d.min.js +2 -0
  66. package/resources/js/graph3d/d3-octree.min.js +2 -0
  67. package/resources/js/graph3d/d3-quadtree.min.js +2 -0
  68. package/resources/js/graph3d/d3-timer.min.js +2 -0
  69. package/resources/js/graph3d/layout.mjs +63 -0
  70. package/resources/js/graph3d/three.min.js +6 -0
  71. package/resources/js/graph3d/viewer2d.js +448 -0
  72. package/resources/panel/index.html +12 -0
  73. package/resources/panel/mascot-blink-1.png +0 -0
  74. package/resources/panel/mascot-blink-2.png +0 -0
  75. package/resources/panel/mascot-lift-down-1.png +0 -0
  76. package/resources/panel/mascot-lift-down-2.png +0 -0
  77. package/resources/panel/mascot-lift-down-3.png +0 -0
  78. package/resources/panel/mascot-lift-down-4.png +0 -0
  79. package/resources/panel/mascot-lift-down-5.png +0 -0
  80. package/resources/panel/mascot-lift-down-6.png +0 -0
  81. package/resources/panel/mascot-lift-up-1.png +0 -0
  82. package/resources/panel/mascot-lift-up-2.png +0 -0
  83. package/resources/panel/mascot-lift-up-3.png +0 -0
  84. package/resources/panel/mascot-lift-up-4.png +0 -0
  85. package/resources/panel/mascot-lift-up-5.png +0 -0
  86. package/resources/panel/mascot-lift-up-6.png +0 -0
  87. package/resources/panel/mascot-tickle-1.png +0 -0
  88. package/resources/panel/mascot-tickle-2.png +0 -0
  89. package/resources/panel/mascot-tickle-3.png +0 -0
  90. package/resources/panel/mascot-tickle-4.png +0 -0
  91. package/resources/panel/panel.css +111 -0
  92. package/resources/panel/renderer.js +480 -8
  93. package/scripts/_utils.py +1203 -977
  94. package/scripts/biz_daily.py +25 -10
  95. package/scripts/chat_notes.py +15 -5
  96. package/scripts/check_readme_commands.py +426 -0
  97. package/scripts/compile_wiki.py +163 -57
  98. package/scripts/contact_schema.py +339 -0
  99. package/scripts/data/wechat_emoji.json +225 -0
  100. package/scripts/fav_notes.py +4 -1
  101. package/scripts/graph_2d.py +151 -0
  102. package/scripts/graph_3d.py +646 -0
  103. package/scripts/health_check.py +163 -26
  104. package/scripts/nt_decrypt.py +3 -3
  105. package/scripts/panel_frames.py +683 -0
  106. package/scripts/pipeline.py +21 -10
  107. package/scripts/quality_eval.py +5 -0
  108. package/scripts/rag_chat.py +33 -16
  109. package/scripts/reply_debt.py +26 -6
  110. package/scripts/semantic_search.py +130 -22
  111. package/scripts/vault_rag.py +42 -16
  112. package/scripts/vault_search.py +44 -8
  113. package/scripts/wechat_emoticon.py +6 -0
  114. package/scripts/wiki_lint.py +94 -17
  115. package/src/core/dbPathService.ts +22 -4
  116. package/src/panel/server.ts +33 -0
  117. package/src/services/assistantService.ts +26 -0
  118. package/src/services/assistantTools.ts +2452 -2319
  119. package/src/services/configService.ts +5 -2
  120. package/src/services/messageQuery.ts +29 -0
  121. package/src/services/wechatClient.ts +380 -0
  122. package/src/services/wechatMedia.ts +166 -0
  123. package/src/services/wechatMessageService.ts +62 -11
  124. package/src/types.ts +4 -0
  125. package/dist/src/utils/wechatEmoji.d.ts +0 -5
  126. package/dist/src/utils/wechatEmoji.d.ts.map +0 -1
  127. package/dist/src/utils/wechatEmoji.js +0 -35
  128. package/dist/src/utils/wechatEmoji.js.map +0 -1
@@ -8,6 +8,8 @@ import path from 'path'
8
8
  import { WechatClient } from '../core/wechatClient.js'
9
9
  import { configService } from './configService.js'
10
10
  import { resolveInboundRouting } from './assistantRouting.js'
11
+ import { mediaTypeOf, saveInboundMedia, wechatMediaDir } from './wechatMedia.js'
12
+ import type { InboundMediaType } from './wechatMedia.js'
11
13
  import type { WechatOCConfig, WechatLoginSession, WechatInboundMessage, WechatMessageComponent } from '../types.js'
12
14
 
13
15
  function sleep(ms: number): Promise<void> {
@@ -64,10 +66,16 @@ export class WechatMessageService {
64
66
  private messageCallbacks: Array<(msg: WechatInboundMessage) => void> = []
65
67
  /** 服务端说过 token 失效了。**只由长轮询置位**(见 `classifyPollResult`),恢复时清掉 */
66
68
  private tokenExpired = false
69
+ /** 入站媒体是否落地。undefined = 每次去读配置(默认关,见 `wechatMedia.ts` 的边界 1) */
70
+ private mediaDownload?: boolean | string
71
+ /** 落地目录。undefined = 用 `wechatMediaDir()`(可被环境变量改,测试用临时目录) */
72
+ private mediaDirOverride?: string
67
73
 
68
74
  constructor(config: WechatOCConfig = {}) {
69
75
  this.config = config
70
76
  this.syncBuf = config.syncBuf || ''
77
+ this.mediaDownload = config.mediaDownload
78
+ this.mediaDirOverride = config.mediaDir
71
79
  this.client = new WechatClient({
72
80
  baseUrl: config.baseUrl,
73
81
  cdnBaseUrl: config.cdnBaseUrl,
@@ -225,7 +233,7 @@ export class WechatMessageService {
225
233
  const msgs: any[] = data.msgs || []
226
234
  for (const msg of msgs) {
227
235
  if (this.shutdownFlag) return
228
- const inbound = this.parseInboundMessage(msg)
236
+ const inbound = await this.parseInboundMessage(msg)
229
237
  if (inbound) {
230
238
  for (const cb of this.messageCallbacks) {
231
239
  try { cb(inbound) } catch (error: any) {
@@ -409,7 +417,40 @@ export class WechatMessageService {
409
417
 
410
418
  // ====== Private ======
411
419
 
412
- private parseInboundMessage(msg: any): WechatInboundMessage | null {
420
+ /** 入站媒体落地的开关:构造时注入的优先,其次读配置。默认关 —— 关着就不发任何网络请求 */
421
+ private mediaDownloadEnabled(): boolean {
422
+ const raw = this.mediaDownload ?? configService.get('wechatMediaDownload')
423
+ return raw === true || raw === 'true'
424
+ }
425
+
426
+ /**
427
+ * 下载一件入站媒体并落盘,返回路径;**没开这个功能、或下载失败,都返回空串**。
428
+ * 失败会往日志里写一行原因 —— "没开"和"开了但失败了"必须分得开,否则出事时两种证据全无。
429
+ */
430
+ private async inboundMediaPath(
431
+ item: any,
432
+ mediaType: InboundMediaType,
433
+ index: number,
434
+ messageId: string,
435
+ ): Promise<string> {
436
+ if (!this.mediaDownloadEnabled()) return ''
437
+ const result = await saveInboundMedia({
438
+ item,
439
+ mediaType,
440
+ index,
441
+ messageId,
442
+ dir: this.mediaDirOverride || wechatMediaDir(),
443
+ serverFileName: mediaType === 'file' ? item?.file_item?.file_name : undefined,
444
+ download: (param, key) => this.client.downloadMedia(param, key),
445
+ })
446
+ if (!result.path) {
447
+ console.warn(`入站媒体未落地(${mediaType}):${result.reason}`)
448
+ return ''
449
+ }
450
+ return result.path
451
+ }
452
+
453
+ private async parseInboundMessage(msg: any): Promise<WechatInboundMessage | null> {
413
454
  const fromUserId: string = msg.from_user_id || ''
414
455
  if (!fromUserId) return null
415
456
  const routing = resolveInboundRouting(msg, String(configService.get('wechatOcAccountId') || ''))
@@ -423,19 +464,29 @@ export class WechatMessageService {
423
464
 
424
465
  const components: WechatMessageComponent[] = []
425
466
  const itemList: any[] = msg.item_list || []
467
+ const messageId: string = msg.client_id || uuidHex()
468
+ let mediaIndex = 0
426
469
 
427
470
  for (const item of itemList) {
428
471
  const itemType = item.type as number
429
472
  if (itemType === 1 && item.text_item) {
430
473
  components.push({ type: 'plain', text: item.text_item.text || '' })
431
- } else if (itemType === 2 && item.image_item) {
432
- components.push({ type: 'image', filePath: '' }) // image download deferred
433
- } else if (itemType === 3 && item.voice_item) {
434
- components.push({ type: 'record', filePath: '' })
435
- } else if (itemType === 4 && item.file_item) {
436
- components.push({ type: 'file', name: item.file_item.file_name || '', filePath: '' })
437
- } else if (itemType === 5 && item.video_item) {
438
- components.push({ type: 'video', filePath: '' })
474
+ continue
475
+ }
476
+ const mediaType = mediaTypeOf(itemType)
477
+ // 缺节点就整项跳过(与既有行为一致:形状不对的项不进 components)
478
+ if (!mediaType || !item[`${mediaType === 'record' ? 'voice' : mediaType}_item`]) continue
479
+ mediaIndex += 1
480
+ // 落盘(默认关,关着时这里不做任何 IO)
481
+ const filePath = await this.inboundMediaPath(item, mediaType, mediaIndex, messageId)
482
+ if (mediaType === 'image') {
483
+ components.push({ type: 'image', filePath })
484
+ } else if (mediaType === 'record') {
485
+ components.push({ type: 'record', filePath })
486
+ } else if (mediaType === 'file') {
487
+ components.push({ type: 'file', name: item.file_item.file_name || '', filePath })
488
+ } else {
489
+ components.push({ type: 'video', filePath })
439
490
  }
440
491
  }
441
492
 
@@ -454,7 +505,7 @@ export class WechatMessageService {
454
505
  const messageStr = textComponents.map(c => (c as { type: 'plain'; text: string }).text).join('')
455
506
 
456
507
  return {
457
- messageId: msg.client_id || uuidHex(),
508
+ messageId,
458
509
  fromUserId,
459
510
  ...routing,
460
511
  senderNickname: msg.from_user_id || '',
package/src/types.ts CHANGED
@@ -65,6 +65,10 @@ export interface WechatOCConfig {
65
65
  apiTimeoutMs?: number
66
66
  typingKeepaliveIntervalS?: number
67
67
  typingTicketTtlS?: number
68
+ /** 入站媒体是否落地(缺省=读 `wechatMediaDownload` 配置,默认关)。见 src/services/wechatMedia.ts */
69
+ mediaDownload?: boolean | string
70
+ /** 入站媒体落地目录(缺省=output/wechat-media/,可被 WEFLOW_WECHAT_MEDIA_DIR 改) */
71
+ mediaDir?: string
68
72
  }
69
73
 
70
74
  export interface WechatLoginSession {
@@ -1,5 +0,0 @@
1
- /** Replace known WeChat face codes like [害羞] with emoji; unknown codes pass through. */
2
- export declare function convertFaces(text: string | undefined | null): string;
3
- /** Convert faces, then truncate without splitting a surrogate pair. */
4
- export declare function convertFacesTruncated(text: string | undefined | null, max: number): string;
5
- //# sourceMappingURL=wechatEmoji.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wechatEmoji.d.ts","sourceRoot":"","sources":["../../../src/utils/wechatEmoji.ts"],"names":[],"mappings":"AA4BA,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,CAGpE;AAED,uEAAuE;AACvE,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE1F"}
@@ -1,35 +0,0 @@
1
- import { readFileSync } from 'fs';
2
- import { dirname, join } from 'path';
3
- import { fileURLToPath } from 'url';
4
- const __filename = fileURLToPath(import.meta.url);
5
- const __dirname = dirname(__filename);
6
- // dist/src/utils -> package root. The table is shared with the Python side.
7
- const DATA_FILE = join(__dirname, '..', '..', '..', 'scripts', 'data', 'wechat_emoji.json');
8
- const faces = {};
9
- try {
10
- const parsed = JSON.parse(readFileSync(DATA_FILE, 'utf8'));
11
- for (const [name, emoji] of Object.entries(parsed)) {
12
- // Entries starting with "_" are documentation, not faces.
13
- if (!name.startsWith('_') && typeof emoji === 'string') {
14
- faces[name] = emoji;
15
- }
16
- }
17
- }
18
- catch {
19
- // Missing table just means faces stay as their [名称] source text.
20
- }
21
- const escapeRe = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
22
- const names = Object.keys(faces).sort((a, b) => b.length - a.length);
23
- // Longest name first so [左哼哼] can never be shadowed by a shorter prefix.
24
- const pattern = names.length > 0 ? new RegExp(`\\[(${names.map(escapeRe).join('|')})\\]`, 'g') : null;
25
- /** Replace known WeChat face codes like [害羞] with emoji; unknown codes pass through. */
26
- export function convertFaces(text) {
27
- if (!text || !pattern)
28
- return text || '';
29
- return text.replace(pattern, (match, name) => faces[name] ?? match);
30
- }
31
- /** Convert faces, then truncate without splitting a surrogate pair. */
32
- export function convertFacesTruncated(text, max) {
33
- return Array.from(convertFaces(text)).slice(0, max).join('');
34
- }
35
- //# sourceMappingURL=wechatEmoji.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"wechatEmoji.js","sourceRoot":"","sources":["../../../src/utils/wechatEmoji.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAA;AACjC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAA;AAEnC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACjD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAA;AAErC,4EAA4E;AAC5E,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,mBAAmB,CAAC,CAAA;AAE3F,MAAM,KAAK,GAA2B,EAAE,CAAA;AACxC,IAAI,CAAC;IACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,SAAS,EAAE,MAAM,CAAC,CAA4B,CAAA;IACrF,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QACnD,0DAA0D;QAC1D,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACvD,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAA;QACrB,CAAC;IACH,CAAC;AACH,CAAC;AAAC,MAAM,CAAC;IACP,iEAAiE;AACnE,CAAC;AAED,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAA;AACtF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC,CAAA;AACpE,yEAAyE;AACzE,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAErG,wFAAwF;AACxF,MAAM,UAAU,YAAY,CAAC,IAA+B;IAC1D,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,IAAI,EAAE,CAAA;IACxC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,qBAAqB,CAAC,IAA+B,EAAE,GAAW;IAChF,OAAO,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAC9D,CAAC"}