@sunnoy/wecom 2.2.0 → 2.3.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 (44) hide show
  1. package/README.md +2 -1
  2. package/index.js +13 -2
  3. package/openclaw.plugin.json +137 -1
  4. package/package.json +2 -3
  5. package/skills/wecom-contact-lookup/SKILL.md +167 -0
  6. package/skills/wecom-doc-manager/SKILL.md +106 -0
  7. package/skills/wecom-doc-manager/references/api-create-doc.md +56 -0
  8. package/skills/wecom-doc-manager/references/api-edit-doc-content.md +68 -0
  9. package/skills/wecom-doc-manager/references/api-export-document.md +88 -0
  10. package/skills/wecom-edit-todo/SKILL.md +254 -0
  11. package/skills/wecom-get-todo-detail/SKILL.md +148 -0
  12. package/skills/wecom-get-todo-list/SKILL.md +132 -0
  13. package/skills/wecom-meeting-create/SKILL.md +163 -0
  14. package/skills/wecom-meeting-create/references/example-full.md +30 -0
  15. package/skills/wecom-meeting-create/references/example-reminder.md +46 -0
  16. package/skills/wecom-meeting-create/references/example-security.md +22 -0
  17. package/skills/wecom-meeting-manage/SKILL.md +141 -0
  18. package/skills/wecom-meeting-query/SKILL.md +335 -0
  19. package/skills/wecom-preflight/SKILL.md +103 -0
  20. package/skills/wecom-schedule/SKILL.md +164 -0
  21. package/skills/wecom-schedule/references/api-check-availability.md +56 -0
  22. package/skills/wecom-schedule/references/api-create-schedule.md +38 -0
  23. package/skills/wecom-schedule/references/api-get-schedule-detail.md +81 -0
  24. package/skills/wecom-schedule/references/api-update-schedule.md +30 -0
  25. package/skills/wecom-schedule/references/ref-reminders.md +24 -0
  26. package/skills/wecom-smartsheet-data/SKILL.md +76 -0
  27. package/skills/wecom-smartsheet-data/references/api-get-records.md +61 -0
  28. package/skills/wecom-smartsheet-data/references/cell-value-formats.md +120 -0
  29. package/skills/wecom-smartsheet-schema/SKILL.md +96 -0
  30. package/skills/wecom-smartsheet-schema/references/field-types.md +43 -0
  31. package/wecom/accounts.js +2 -0
  32. package/wecom/callback-inbound.js +9 -5
  33. package/wecom/channel-plugin.js +65 -1
  34. package/wecom/constants.js +18 -7
  35. package/wecom/image-studio-tool.js +764 -0
  36. package/wecom/mcp-tool.js +660 -0
  37. package/wecom/parent-resolver.js +26 -0
  38. package/wecom/plugin-config.js +484 -0
  39. package/wecom/target.js +3 -2
  40. package/wecom/welcome-messages-file.js +155 -0
  41. package/wecom/workspace-template.js +40 -4
  42. package/wecom/ws-monitor.js +186 -12
  43. package/skills/wecom-doc/SKILL.md +0 -363
  44. package/skills/wecom-doc/references/doc-api.md +0 -224
@@ -36,6 +36,7 @@ import {
36
36
  } from "./constants.js";
37
37
  import { uploadAndSendMedia } from "./media-uploader.js";
38
38
  import { getExtendedMediaLocalRoots } from "./openclaw-compat.js";
39
+ import { extractParentAgentId } from "./parent-resolver.js";
39
40
  import { sendWsMessage, startWsMonitor } from "./ws-monitor.js";
40
41
  import { getWsClient } from "./ws-state.js";
41
42
 
@@ -262,6 +263,7 @@ export const wecomChannelPlugin = {
262
263
  websocketUrl: { type: "string" },
263
264
  sendThinkingMessage: { type: "boolean" },
264
265
  welcomeMessage: { type: "string" },
266
+ welcomeMessagesFile: { type: "string" },
265
267
  dmPolicy: { enum: ["pairing", "allowlist", "open", "disabled"] },
266
268
  allowFrom: { type: "array", items: { type: "string" } },
267
269
  groupPolicy: { enum: ["open", "allowlist", "disabled"] },
@@ -291,6 +293,10 @@ export const wecomChannelPlugin = {
291
293
  secret: { label: "Secret", sensitive: true },
292
294
  websocketUrl: { label: "WebSocket URL", placeholder: DEFAULT_WS_URL },
293
295
  welcomeMessage: { label: "Welcome Message" },
296
+ welcomeMessagesFile: {
297
+ label: "Welcome Messages File",
298
+ placeholder: "welcome-messages.json",
299
+ },
294
300
  "agent.corpSecret": { sensitive: true, label: "Application Secret" },
295
301
  "agent.replyFormat": { label: "Reply Format", placeholder: "text" },
296
302
  "agent.callback.token": { label: "Callback Token" },
@@ -404,6 +410,7 @@ export const wecomChannelPlugin = {
404
410
  try {
405
411
  if (!target.toParty && !target.toTag) {
406
412
  const wsTarget = target.chatId || target.toUser || to;
413
+ logger.debug(`[wecom] sendText: trying WS accountId=${resolvedAccountId} target=${wsTarget}`);
407
414
  return await sendWsMessage({
408
415
  to: wsTarget,
409
416
  content: text,
@@ -411,9 +418,17 @@ export const wecomChannelPlugin = {
411
418
  });
412
419
  }
413
420
  } catch (error) {
414
- logger.warn(`[wecom] WS sendText failed, falling back to Agent API: ${error.message}`);
421
+ logger.warn(`[wecom] WS sendText failed (accountId=${resolvedAccountId}), falling back: ${error.message}`);
422
+ }
423
+
424
+ // Webhook fallback for accounts without Agent API (e.g. WS bot mode)
425
+ const account = resolveAccount(cfg, resolvedAccountId);
426
+ if (!account?.agentCredentials && account?.config?.webhooks?.default) {
427
+ logger.debug(`[wecom] sendText: Agent API unavailable, using webhook fallback accountId=${resolvedAccountId}`);
428
+ return sendViaWebhook({ cfg, accountId: resolvedAccountId, webhookName: "default", text });
415
429
  }
416
430
 
431
+ logger.debug(`[wecom] sendText: trying Agent API accountId=${resolvedAccountId}`);
417
432
  return sendViaAgent({
418
433
  cfg,
419
434
  accountId: resolvedAccountId,
@@ -631,6 +646,55 @@ export const wecomChannelPlugin = {
631
646
  };
632
647
  },
633
648
  },
649
+ hooks: {
650
+ /**
651
+ * Ensure announce delivery uses a valid WeCom channel accountId.
652
+ *
653
+ * When a dynamic agent (e.g. wecom-yoyo-dm-xxx) spawns a sub-agent,
654
+ * the announce delivery may reference the dynamic agent ID as accountId.
655
+ * This hook resolves it to the actual WeCom account (e.g. yoyo) so the
656
+ * outbound sendText can find valid WS/Agent API credentials.
657
+ */
658
+ subagent_delivery_target: async (event, ctx) => {
659
+ const origin = event.requesterOrigin;
660
+ if (!origin?.channel || origin.channel !== CHANNEL_ID) return;
661
+
662
+ const cfg = ctx?.cfg ?? getOpenclawConfig();
663
+
664
+ // Check whether current accountId already resolves to a valid account
665
+ const currentAccount = resolveAccount(cfg, origin.accountId);
666
+ if (currentAccount?.enabled) return;
667
+
668
+ // Try to extract the base account from a dynamic agent ID
669
+ const baseId = extractParentAgentId(origin.accountId);
670
+ if (baseId && baseId !== origin.accountId) {
671
+ const baseAccount = resolveAccount(cfg, baseId);
672
+ if (baseAccount?.enabled) {
673
+ logger.info(`[wecom] subagent_delivery_target: ${origin.accountId} → ${baseId}`);
674
+ return { origin: { ...origin, accountId: baseId } };
675
+ }
676
+ }
677
+
678
+ // Fallback to default account
679
+ const defaultId = resolveDefaultAccountId(cfg);
680
+ if (defaultId && defaultId !== origin.accountId) {
681
+ logger.info(`[wecom] subagent_delivery_target: fallback → ${defaultId}`);
682
+ return { origin: { ...origin, accountId: defaultId } };
683
+ }
684
+ },
685
+
686
+ subagent_spawned: async (event) => {
687
+ logger.info(
688
+ `[wecom] subagent spawned: child=${event.childSessionKey} requester=${event.requesterSessionKey}`,
689
+ );
690
+ },
691
+
692
+ subagent_ended: async (event) => {
693
+ logger.info(
694
+ `[wecom] subagent ended: target=${event.targetSessionKey} reason=${event.reason} outcome=${event.outcome}`,
695
+ );
696
+ },
697
+ },
634
698
  };
635
699
 
636
700
  export const wecomChannelPluginTesting = {};
@@ -26,6 +26,10 @@ export const REQID_FLUSH_DEBOUNCE_MS = 1_000;
26
26
  export const PENDING_REPLY_TTL_MS = 5 * 60 * 1000;
27
27
  export const PENDING_REPLY_MAX_SIZE = 50;
28
28
 
29
+ // WeCom stream messages expire ~6 minutes after creation. Rotate the stream
30
+ // before hitting that hard limit so the user never sees a dead stream.
31
+ export const STREAM_MAX_LIFETIME_MS = 5 * 60 * 1000;
32
+
29
33
  export const IMAGE_MAX_BYTES = 10 * 1024 * 1024;
30
34
  export const VIDEO_MAX_BYTES = 10 * 1024 * 1024;
31
35
  export const VOICE_MAX_BYTES = 2 * 1024 * 1024;
@@ -43,7 +47,8 @@ export const DEFAULT_WELCOME_MESSAGES = [
43
47
  "/compact 压缩对话",
44
48
  "/help 帮助",
45
49
  "/status 查看状态",
46
- "/reasoning stream 打开思考动画",
50
+ "你可以让我生成和编辑图片了",
51
+ "你可以用语音跟我对话",
47
52
  ].join("\n"),
48
53
  [
49
54
  "终于唤醒我啦,我已经准备就绪!😄",
@@ -53,7 +58,8 @@ export const DEFAULT_WELCOME_MESSAGES = [
53
58
  "/compact 压缩对话",
54
59
  "/help 帮助",
55
60
  "/status 查看状态",
56
- "/reasoning stream 打开思考动画",
61
+ "你可以让我生成和编辑图片了",
62
+ "你可以用语音跟我对话",
57
63
  ].join("\n"),
58
64
  [
59
65
  "欢迎回来,准备开始今天的工作吧!✨",
@@ -63,7 +69,8 @@ export const DEFAULT_WELCOME_MESSAGES = [
63
69
  "/compact 压缩对话",
64
70
  "/help 帮助",
65
71
  "/status 查看状态",
66
- "/reasoning stream 打开思考动画",
72
+ "你可以让我生成和编辑图片了",
73
+ "你可以用语音跟我对话",
67
74
  ].join("\n"),
68
75
  [
69
76
  "嗨,我已经在线!🤖",
@@ -73,7 +80,8 @@ export const DEFAULT_WELCOME_MESSAGES = [
73
80
  "/compact 压缩对话",
74
81
  "/help 帮助",
75
82
  "/status 查看状态",
76
- "/reasoning stream 打开思考动画",
83
+ "你可以让我生成和编辑图片了",
84
+ "你可以用语音跟我对话",
77
85
  ].join("\n"),
78
86
  [
79
87
  "今天也一起高效开工吧!🚀",
@@ -83,7 +91,8 @@ export const DEFAULT_WELCOME_MESSAGES = [
83
91
  "/compact 压缩对话",
84
92
  "/help 帮助",
85
93
  "/status 查看状态",
86
- "/reasoning stream 打开思考动画",
94
+ "你可以让我生成和编辑图片了",
95
+ "你可以用语音跟我对话",
87
96
  ].join("\n"),
88
97
  [
89
98
  "叮咚,你的数字助手已就位!🎉",
@@ -93,7 +102,8 @@ export const DEFAULT_WELCOME_MESSAGES = [
93
102
  "/compact 压缩对话",
94
103
  "/help 帮助",
95
104
  "/status 查看状态",
96
- "/reasoning stream 打开思考动画",
105
+ "你可以让我生成和编辑图片了",
106
+ "你可以用语音跟我对话",
97
107
  ].join("\n"),
98
108
  [
99
109
  "灵感加载完成,随时可以开聊!💡",
@@ -103,7 +113,8 @@ export const DEFAULT_WELCOME_MESSAGES = [
103
113
  "/compact 压缩对话",
104
114
  "/help 帮助",
105
115
  "/status 查看状态",
106
- "/reasoning stream 打开思考动画",
116
+ "你可以让我生成和编辑图片了",
117
+ "你可以用语音跟我对话",
107
118
  ].join("\n"),
108
119
  ];
109
120
  export const DEFAULT_WELCOME_MESSAGE = DEFAULT_WELCOME_MESSAGES[0];