@pawastation/wechat-kf 0.1.1

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 (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +291 -0
  3. package/README.zh-CN.md +401 -0
  4. package/dist/index.d.ts +27 -0
  5. package/dist/index.js +24 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/src/accounts.d.ts +37 -0
  8. package/dist/src/accounts.js +205 -0
  9. package/dist/src/accounts.js.map +1 -0
  10. package/dist/src/api.d.ts +29 -0
  11. package/dist/src/api.js +172 -0
  12. package/dist/src/api.js.map +1 -0
  13. package/dist/src/bot.d.ts +35 -0
  14. package/dist/src/bot.js +379 -0
  15. package/dist/src/bot.js.map +1 -0
  16. package/dist/src/channel.d.ts +113 -0
  17. package/dist/src/channel.js +183 -0
  18. package/dist/src/channel.js.map +1 -0
  19. package/dist/src/chunk-utils.d.ts +18 -0
  20. package/dist/src/chunk-utils.js +58 -0
  21. package/dist/src/chunk-utils.js.map +1 -0
  22. package/dist/src/config-schema.d.ts +56 -0
  23. package/dist/src/config-schema.js +38 -0
  24. package/dist/src/config-schema.js.map +1 -0
  25. package/dist/src/constants.d.ts +19 -0
  26. package/dist/src/constants.js +20 -0
  27. package/dist/src/constants.js.map +1 -0
  28. package/dist/src/crypto.d.ts +18 -0
  29. package/dist/src/crypto.js +80 -0
  30. package/dist/src/crypto.js.map +1 -0
  31. package/dist/src/fs-utils.d.ts +7 -0
  32. package/dist/src/fs-utils.js +13 -0
  33. package/dist/src/fs-utils.js.map +1 -0
  34. package/dist/src/monitor.d.ts +18 -0
  35. package/dist/src/monitor.js +131 -0
  36. package/dist/src/monitor.js.map +1 -0
  37. package/dist/src/outbound.d.ts +66 -0
  38. package/dist/src/outbound.js +234 -0
  39. package/dist/src/outbound.js.map +1 -0
  40. package/dist/src/reply-dispatcher.d.ts +40 -0
  41. package/dist/src/reply-dispatcher.js +120 -0
  42. package/dist/src/reply-dispatcher.js.map +1 -0
  43. package/dist/src/runtime.d.ts +130 -0
  44. package/dist/src/runtime.js +22 -0
  45. package/dist/src/runtime.js.map +1 -0
  46. package/dist/src/send-utils.d.ts +30 -0
  47. package/dist/src/send-utils.js +89 -0
  48. package/dist/src/send-utils.js.map +1 -0
  49. package/dist/src/send.d.ts +7 -0
  50. package/dist/src/send.js +13 -0
  51. package/dist/src/send.js.map +1 -0
  52. package/dist/src/token.d.ts +8 -0
  53. package/dist/src/token.js +57 -0
  54. package/dist/src/token.js.map +1 -0
  55. package/dist/src/types.d.ts +173 -0
  56. package/dist/src/types.js +3 -0
  57. package/dist/src/types.js.map +1 -0
  58. package/dist/src/unicode-format.d.ts +26 -0
  59. package/dist/src/unicode-format.js +157 -0
  60. package/dist/src/unicode-format.js.map +1 -0
  61. package/dist/src/webhook.d.ts +22 -0
  62. package/dist/src/webhook.js +138 -0
  63. package/dist/src/webhook.js.map +1 -0
  64. package/dist/src/wechat-kf-directives.d.ts +34 -0
  65. package/dist/src/wechat-kf-directives.js +65 -0
  66. package/dist/src/wechat-kf-directives.js.map +1 -0
  67. package/index.ts +32 -0
  68. package/openclaw.plugin.json +31 -0
  69. package/package.json +91 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * WeChat KF ChannelPlugin implementation
3
+ *
4
+ * Dynamically discovers kfids from webhook callbacks.
5
+ * Each kfid = one accountId = one independent session.
6
+ */
7
+ import { homedir } from "node:os";
8
+ import { deleteKfId, disableKfId, enableKfId, getChannelConfig, listAccountIds, resolveAccount } from "./accounts.js";
9
+ import { wechatKfConfigSchema } from "./config-schema.js";
10
+ import { startMonitor } from "./monitor.js";
11
+ import { wechatKfOutbound } from "./outbound.js";
12
+ const meta = {
13
+ id: "wechat-kf",
14
+ label: "WeChat KF",
15
+ selectionLabel: "WeChat Customer Service (微信客服)",
16
+ docsPath: "/channels/wechat-kf",
17
+ docsLabel: "wechat-kf",
18
+ blurb: "WeCom Customer Service (企业微信客服) API channel — let WeChat users chat with your agent.",
19
+ aliases: ["wxkf"],
20
+ order: 80,
21
+ };
22
+ export const wechatKfPlugin = {
23
+ id: "wechat-kf",
24
+ meta: { ...meta },
25
+ capabilities: {
26
+ chatTypes: ["direct"],
27
+ media: true,
28
+ reactions: false,
29
+ threads: false,
30
+ polls: false,
31
+ nativeCommands: false,
32
+ blockStreaming: false,
33
+ },
34
+ agentPrompt: {
35
+ messageToolHints: () => [
36
+ "- WeChat KF: omit `target` to reply to current conversation.",
37
+ "- Supports text and media messages (image, voice, video, file).",
38
+ "- 48h reply window, max 5 replies per window.",
39
+ "- To send a rich link card, include `[[wechat_link: title | desc | url | thumbUrl]]` in your reply. Fields: title (required), desc (optional), url (required, must be https://), thumbUrl (optional, thumbnail image URL). Example: `[[wechat_link: Article Title | Brief description | https://example.com/article | https://example.com/thumb.jpg]]`",
40
+ ],
41
+ },
42
+ reload: { configPrefixes: ["channels.wechat-kf"] },
43
+ configSchema: { schema: wechatKfConfigSchema },
44
+ config: {
45
+ listAccountIds: (cfg) => listAccountIds(cfg),
46
+ resolveAccount: (cfg, accountId) => resolveAccount(cfg, accountId),
47
+ defaultAccountId: (cfg) => listAccountIds(cfg)[0] ?? "default",
48
+ setAccountEnabled: ({ cfg, accountId, enabled }) => {
49
+ // Dynamic accounts — toggle via in-memory disabled set (persisted to disk).
50
+ // Fire-and-forget: the async persist is best-effort; the in-memory state
51
+ // takes effect immediately so the framework sees the change right away.
52
+ if (enabled) {
53
+ void enableKfId(accountId);
54
+ }
55
+ else {
56
+ void disableKfId(accountId);
57
+ }
58
+ return cfg;
59
+ },
60
+ deleteAccount: ({ cfg, accountId }) => {
61
+ // Remove from discovered set and add to disabled set so it won't come
62
+ // back from future webhook callbacks. Fire-and-forget for persistence.
63
+ void deleteKfId(accountId);
64
+ return cfg;
65
+ },
66
+ isConfigured: (account) => account.configured,
67
+ describeAccount: (account) => ({
68
+ accountId: account.accountId,
69
+ enabled: account.enabled,
70
+ configured: account.configured,
71
+ corpId: account.corpId,
72
+ openKfId: account.openKfId,
73
+ }),
74
+ resolveAllowFrom: ({ cfg }) => {
75
+ const config = getChannelConfig(cfg);
76
+ return (config.allowFrom ?? []).map(String);
77
+ },
78
+ formatAllowFrom: ({ allowFrom }) => allowFrom.map((e) => e.trim()).filter(Boolean),
79
+ },
80
+ security: {
81
+ resolveDmPolicy: ({ cfg }) => {
82
+ const config = getChannelConfig(cfg);
83
+ const policy = config.dmPolicy ?? "open";
84
+ return {
85
+ policy,
86
+ allowFrom: config.allowFrom ?? [],
87
+ allowFromPath: "channels.wechat-kf.allowFrom",
88
+ approveHint: [
89
+ "To approve a WeChat KF user, add their external_userid to the allowlist:",
90
+ " openclaw config set channels.wechat-kf.allowFrom '[\"{userid}\"]'",
91
+ ].join("\n"),
92
+ normalizeEntry: (raw) => raw.replace(/^user:/i, "").trim(),
93
+ };
94
+ },
95
+ collectWarnings: ({ cfg }) => {
96
+ const config = getChannelConfig(cfg);
97
+ const policy = config.dmPolicy ?? "open";
98
+ if (policy === "open") {
99
+ return ['- WeChat KF: dmPolicy="open" — any WeChat user can chat with the agent.'];
100
+ }
101
+ return [];
102
+ },
103
+ },
104
+ setup: {
105
+ resolveAccountId: (_cfg, accountId) => accountId ?? "default",
106
+ applyAccountConfig: ({ cfg }) => {
107
+ const config = getChannelConfig(cfg);
108
+ return {
109
+ ...cfg,
110
+ channels: { ...(cfg.channels ?? {}), "wechat-kf": { ...config, enabled: true } },
111
+ };
112
+ },
113
+ },
114
+ outbound: wechatKfOutbound,
115
+ status: {
116
+ defaultRuntime: {
117
+ accountId: "default",
118
+ running: false,
119
+ lastStartAt: null,
120
+ lastStopAt: null,
121
+ lastError: null,
122
+ port: null,
123
+ },
124
+ buildChannelSummary: ({ snapshot }) => ({
125
+ configured: snapshot.configured ?? false,
126
+ running: snapshot.running ?? false,
127
+ lastStartAt: snapshot.lastStartAt ?? null,
128
+ lastError: snapshot.lastError ?? null,
129
+ port: snapshot.port ?? null,
130
+ }),
131
+ buildAccountSnapshot: ({ account, runtime, }) => ({
132
+ accountId: account.accountId,
133
+ enabled: account.enabled,
134
+ configured: account.configured,
135
+ corpId: account.corpId,
136
+ running: runtime?.running ?? false,
137
+ lastStartAt: runtime?.lastStartAt ?? null,
138
+ lastError: runtime?.lastError ?? null,
139
+ port: runtime?.port ?? null,
140
+ }),
141
+ },
142
+ gateway: {
143
+ /** Track whether the account is currently started to prevent duplicate launches. */
144
+ _started: false,
145
+ startAccount: async (ctx) => {
146
+ const self = wechatKfPlugin.gateway;
147
+ // Idempotency guard — skip if already started
148
+ if (self._started) {
149
+ ctx.log?.info("[wechat-kf] startAccount: already running, skipping duplicate call");
150
+ return;
151
+ }
152
+ const config = getChannelConfig(ctx.cfg);
153
+ const port = config.webhookPort ?? 9999;
154
+ const path = config.webhookPath ?? "/wechat-kf";
155
+ try {
156
+ self._started = true;
157
+ ctx.setStatus?.({ accountId: ctx.accountId, port, running: true, lastStartAt: new Date().toISOString() });
158
+ ctx.log?.info(`[wechat-kf] starting on :${port}${path}`);
159
+ const stateDir = ctx.runtime?.state?.resolveStateDir?.() ?? `${homedir()}/.openclaw/state/wechat-kf`;
160
+ await startMonitor({
161
+ cfg: ctx.cfg,
162
+ runtime: ctx.runtime,
163
+ abortSignal: ctx.abortSignal,
164
+ stateDir,
165
+ log: ctx.log,
166
+ });
167
+ }
168
+ catch (err) {
169
+ self._started = false;
170
+ ctx.setStatus?.({
171
+ accountId: ctx.accountId,
172
+ port,
173
+ running: false,
174
+ lastError: err instanceof Error ? err.message : String(err),
175
+ lastStopAt: new Date().toISOString(),
176
+ });
177
+ ctx.log?.error?.(`[wechat-kf] startAccount failed: ${err instanceof Error ? err.message : String(err)}`);
178
+ throw err;
179
+ }
180
+ },
181
+ },
182
+ };
183
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../../src/channel.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Text chunking utilities for WeChat KF message splitting.
3
+ *
4
+ * Splits long text into chunks that fit within the WeChat character limit,
5
+ * preferring natural boundaries (newlines, then whitespace) before hard-cutting.
6
+ */
7
+ /**
8
+ * Split `text` into chunks of at most `limit` characters.
9
+ *
10
+ * Strategy (per chunk):
11
+ * 1. If remaining text fits within `limit`, emit it as the final chunk.
12
+ * 2. Look for the last newline (`\n`) within the first `limit` characters.
13
+ * 3. Failing that, look for the last whitespace character.
14
+ * 4. Failing that, hard-cut at exactly `limit` characters.
15
+ *
16
+ * Chunks are trimmed; empty chunks are never returned.
17
+ */
18
+ export declare function chunkText(text: string, limit: number): string[];
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Text chunking utilities for WeChat KF message splitting.
3
+ *
4
+ * Splits long text into chunks that fit within the WeChat character limit,
5
+ * preferring natural boundaries (newlines, then whitespace) before hard-cutting.
6
+ */
7
+ /**
8
+ * Split `text` into chunks of at most `limit` characters.
9
+ *
10
+ * Strategy (per chunk):
11
+ * 1. If remaining text fits within `limit`, emit it as the final chunk.
12
+ * 2. Look for the last newline (`\n`) within the first `limit` characters.
13
+ * 3. Failing that, look for the last whitespace character.
14
+ * 4. Failing that, hard-cut at exactly `limit` characters.
15
+ *
16
+ * Chunks are trimmed; empty chunks are never returned.
17
+ */
18
+ export function chunkText(text, limit) {
19
+ if (limit <= 0)
20
+ return [];
21
+ if (text.length <= limit) {
22
+ const trimmed = text.trim();
23
+ return trimmed.length > 0 ? [trimmed] : [];
24
+ }
25
+ const chunks = [];
26
+ let remaining = text;
27
+ while (remaining.length > 0) {
28
+ if (remaining.length <= limit) {
29
+ const trimmed = remaining.trim();
30
+ if (trimmed.length > 0) {
31
+ chunks.push(trimmed);
32
+ }
33
+ break;
34
+ }
35
+ const window = remaining.slice(0, limit);
36
+ // Try to split at the last newline within the window
37
+ let splitIdx = window.lastIndexOf("\n");
38
+ // Fall back to last whitespace
39
+ if (splitIdx <= 0) {
40
+ splitIdx = window.lastIndexOf(" ");
41
+ }
42
+ if (splitIdx <= 0) {
43
+ splitIdx = window.lastIndexOf("\t");
44
+ }
45
+ // Hard-cut if no suitable boundary found
46
+ if (splitIdx <= 0) {
47
+ splitIdx = limit;
48
+ }
49
+ const chunk = remaining.slice(0, splitIdx).trim();
50
+ if (chunk.length > 0) {
51
+ chunks.push(chunk);
52
+ }
53
+ // Advance past the split point (skip the delimiter character when splitting on boundary)
54
+ remaining = remaining.slice(splitIdx).trimStart();
55
+ }
56
+ return chunks;
57
+ }
58
+ //# sourceMappingURL=chunk-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk-utils.js","sourceRoot":"","sources":["../../src/chunk-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,KAAa;IACnD,IAAI,KAAK,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IAC1B,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7C,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,SAAS,GAAG,IAAI,CAAC;IAErB,OAAO,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,IAAI,SAAS,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;YAC9B,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC;YACjC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACvB,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,CAAC;YACD,MAAM;QACR,CAAC;QAED,MAAM,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAEzC,qDAAqD;QACrD,IAAI,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QAExC,+BAA+B;QAC/B,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAClB,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QACrC,CAAC;QACD,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAClB,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;QACtC,CAAC;QAED,yCAAyC;QACzC,IAAI,QAAQ,IAAI,CAAC,EAAE,CAAC;YAClB,QAAQ,GAAG,KAAK,CAAC;QACnB,CAAC;QAED,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;QAED,yFAAyF;QACzF,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,SAAS,EAAE,CAAC;IACpD,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * JSON Schema for wechat-kf channel config
3
+ *
4
+ * Authoritative source: openclaw.plugin.json → configSchema.
5
+ * This runtime copy must stay in sync with the manifest.
6
+ *
7
+ * Flat enterprise-level credentials. No per-account config needed —
8
+ * kfids are discovered dynamically from webhook callbacks.
9
+ */
10
+ export declare const wechatKfConfigSchema: {
11
+ type: "object";
12
+ properties: {
13
+ enabled: {
14
+ type: "boolean";
15
+ };
16
+ corpId: {
17
+ type: "string";
18
+ description: string;
19
+ };
20
+ appSecret: {
21
+ type: "string";
22
+ description: string;
23
+ };
24
+ token: {
25
+ type: "string";
26
+ description: string;
27
+ };
28
+ encodingAESKey: {
29
+ type: "string";
30
+ description: string;
31
+ minLength: number;
32
+ maxLength: number;
33
+ };
34
+ webhookPort: {
35
+ type: "integer";
36
+ minimum: number;
37
+ maximum: number;
38
+ default: number;
39
+ };
40
+ webhookPath: {
41
+ type: "string";
42
+ default: string;
43
+ };
44
+ dmPolicy: {
45
+ type: "string";
46
+ enum: readonly ["open", "pairing", "allowlist", "disabled"];
47
+ default: string;
48
+ };
49
+ allowFrom: {
50
+ type: "array";
51
+ items: {
52
+ type: "string";
53
+ };
54
+ };
55
+ };
56
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * JSON Schema for wechat-kf channel config
3
+ *
4
+ * Authoritative source: openclaw.plugin.json → configSchema.
5
+ * This runtime copy must stay in sync with the manifest.
6
+ *
7
+ * Flat enterprise-level credentials. No per-account config needed —
8
+ * kfids are discovered dynamically from webhook callbacks.
9
+ */
10
+ export const wechatKfConfigSchema = {
11
+ type: "object",
12
+ properties: {
13
+ enabled: { type: "boolean" },
14
+ corpId: { type: "string", description: "WeCom Corp ID (企业ID)" },
15
+ appSecret: { type: "string", description: "Self-built app secret (应用密钥)" },
16
+ token: { type: "string", description: "Webhook callback token" },
17
+ encodingAESKey: {
18
+ type: "string",
19
+ description: "43-character base64 AES key",
20
+ minLength: 43,
21
+ maxLength: 43,
22
+ },
23
+ webhookPort: {
24
+ type: "integer",
25
+ minimum: 1,
26
+ maximum: 65535,
27
+ default: 9999,
28
+ },
29
+ webhookPath: { type: "string", default: "/wechat-kf" },
30
+ dmPolicy: {
31
+ type: "string",
32
+ enum: ["open", "pairing", "allowlist", "disabled"],
33
+ default: "open",
34
+ },
35
+ allowFrom: { type: "array", items: { type: "string" } },
36
+ },
37
+ };
38
+ //# sourceMappingURL=config-schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-schema.js","sourceRoot":"","sources":["../../src/config-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,IAAI,EAAE,QAAiB;IACvB,UAAU,EAAE;QACV,OAAO,EAAE,EAAE,IAAI,EAAE,SAAkB,EAAE;QACrC,MAAM,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE,WAAW,EAAE,sBAAsB,EAAE;QACxE,SAAS,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE,WAAW,EAAE,8BAA8B,EAAE;QACnF,KAAK,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE,WAAW,EAAE,wBAAwB,EAAE;QACzE,cAAc,EAAE;YACd,IAAI,EAAE,QAAiB;YACvB,WAAW,EAAE,6BAA6B;YAC1C,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,EAAE;SACd;QACD,WAAW,EAAE;YACX,IAAI,EAAE,SAAkB;YACxB,OAAO,EAAE,CAAC;YACV,OAAO,EAAE,KAAK;YACd,OAAO,EAAE,IAAI;SACd;QACD,WAAW,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE,OAAO,EAAE,YAAY,EAAE;QAC/D,QAAQ,EAAE;YACR,IAAI,EAAE,QAAiB;YACvB,IAAI,EAAE,CAAC,MAAM,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,CAAU;YAC3D,OAAO,EAAE,MAAM;SAChB;QACD,SAAS,EAAE,EAAE,IAAI,EAAE,OAAgB,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,QAAiB,EAAE,EAAE;KAC1E;CACF,CAAC"}
@@ -0,0 +1,19 @@
1
+ /** WeChat KF text message character limit */
2
+ export declare const WECHAT_TEXT_CHUNK_LIMIT = 2000;
3
+ /** Timeout for token fetch requests (ms) */
4
+ export declare const TOKEN_FETCH_TIMEOUT_MS = 15000;
5
+ /** Timeout for API POST requests (ms) */
6
+ export declare const API_POST_TIMEOUT_MS = 30000;
7
+ /** Timeout for media download/upload requests (ms) */
8
+ export declare const MEDIA_TIMEOUT_MS = 60000;
9
+ /** WeChat errcode values that indicate an expired or invalid access token */
10
+ export declare const TOKEN_EXPIRED_CODES: Set<number>;
11
+ /**
12
+ * WeChat KF errcode indicating the 48-hour / 5-message session limit has
13
+ * been exceeded. When a customer service session is inactive for 48 hours,
14
+ * or the agent has already sent 5 messages without a customer reply, the
15
+ * API returns this error.
16
+ */
17
+ export declare const WECHAT_MSG_LIMIT_ERRCODE = 95026;
18
+ /** Timeout for downloading media from external HTTP URLs (ms) */
19
+ export declare const MEDIA_DOWNLOAD_TIMEOUT_MS = 60000;
@@ -0,0 +1,20 @@
1
+ /** WeChat KF text message character limit */
2
+ export const WECHAT_TEXT_CHUNK_LIMIT = 2000;
3
+ /** Timeout for token fetch requests (ms) */
4
+ export const TOKEN_FETCH_TIMEOUT_MS = 15_000;
5
+ /** Timeout for API POST requests (ms) */
6
+ export const API_POST_TIMEOUT_MS = 30_000;
7
+ /** Timeout for media download/upload requests (ms) */
8
+ export const MEDIA_TIMEOUT_MS = 60_000;
9
+ /** WeChat errcode values that indicate an expired or invalid access token */
10
+ export const TOKEN_EXPIRED_CODES = new Set([40014, 42001, 40001]);
11
+ /**
12
+ * WeChat KF errcode indicating the 48-hour / 5-message session limit has
13
+ * been exceeded. When a customer service session is inactive for 48 hours,
14
+ * or the agent has already sent 5 messages without a customer reply, the
15
+ * API returns this error.
16
+ */
17
+ export const WECHAT_MSG_LIMIT_ERRCODE = 95026;
18
+ /** Timeout for downloading media from external HTTP URLs (ms) */
19
+ export const MEDIA_DOWNLOAD_TIMEOUT_MS = 60_000;
20
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAE5C,4CAA4C;AAC5C,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C,yCAAyC;AACzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,MAAM,CAAC;AAE1C,sDAAsD;AACtD,MAAM,CAAC,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEvC,6EAA6E;AAC7E,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;AAElE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,KAAK,CAAC;AAE9C,iEAAiE;AACjE,MAAM,CAAC,MAAM,yBAAyB,GAAG,MAAM,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * WeCom message encryption/decryption
3
+ *
4
+ * - Signature: SHA1(sort([token, timestamp, nonce, encrypt]))
5
+ * - Encryption: AES-256-CBC, key = Base64Decode(EncodingAESKey + "="), iv = key[0:16]
6
+ * - Plaintext format: random(16B) + msg_len(4B network order) + msg + receiveid
7
+ */
8
+ export declare function deriveAesKey(encodingAESKey: string): Buffer;
9
+ /** SHA1 signature verification */
10
+ export declare function computeSignature(token: string, timestamp: string, nonce: string, encrypt: string): string;
11
+ export declare function verifySignature(token: string, timestamp: string, nonce: string, encrypt: string, expectedSignature: string): boolean;
12
+ /** Decrypt an encrypted message from WeChat callback */
13
+ export declare function decrypt(encodingAESKey: string, encrypted: string): {
14
+ message: string;
15
+ receiverId: string;
16
+ };
17
+ /** Encrypt a message for WeChat callback response */
18
+ export declare function encrypt(encodingAESKey: string, message: string, receiverId: string): string;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * WeCom message encryption/decryption
3
+ *
4
+ * - Signature: SHA1(sort([token, timestamp, nonce, encrypt]))
5
+ * - Encryption: AES-256-CBC, key = Base64Decode(EncodingAESKey + "="), iv = key[0:16]
6
+ * - Plaintext format: random(16B) + msg_len(4B network order) + msg + receiveid
7
+ */
8
+ import { createCipheriv, createDecipheriv, createHash, randomBytes, timingSafeEqual } from "node:crypto";
9
+ export function deriveAesKey(encodingAESKey) {
10
+ if (encodingAESKey.length !== 43) {
11
+ throw new Error(`[wechat-kf] EncodingAESKey must be 43 characters, got ${encodingAESKey.length}`);
12
+ }
13
+ const key = Buffer.from(`${encodingAESKey}=`, "base64");
14
+ if (key.length !== 32) {
15
+ throw new Error(`[wechat-kf] derived AES key must be 32 bytes, got ${key.length}`);
16
+ }
17
+ return key;
18
+ }
19
+ /** SHA1 signature verification */
20
+ export function computeSignature(token, timestamp, nonce, encrypt) {
21
+ const items = [token, timestamp, nonce, encrypt].sort();
22
+ return createHash("sha1").update(items.join("")).digest("hex");
23
+ }
24
+ export function verifySignature(token, timestamp, nonce, encrypt, expectedSignature) {
25
+ const actual = Buffer.from(computeSignature(token, timestamp, nonce, encrypt), "utf8");
26
+ const expected = Buffer.from(expectedSignature, "utf8");
27
+ if (actual.length !== expected.length)
28
+ return false;
29
+ return timingSafeEqual(actual, expected);
30
+ }
31
+ /** Decrypt an encrypted message from WeChat callback */
32
+ export function decrypt(encodingAESKey, encrypted) {
33
+ const aesKey = deriveAesKey(encodingAESKey);
34
+ const iv = aesKey.subarray(0, 16);
35
+ const decipher = createDecipheriv("aes-256-cbc", aesKey, iv);
36
+ decipher.setAutoPadding(false);
37
+ const decrypted = Buffer.concat([decipher.update(encrypted, "base64"), decipher.final()]);
38
+ // Remove PKCS#7 padding — validate ALL N padding bytes equal N
39
+ const pad = decrypted[decrypted.length - 1];
40
+ if (pad < 1 || pad > 32 || pad > decrypted.length) {
41
+ throw new Error("[wechat-kf] invalid PKCS#7 padding");
42
+ }
43
+ for (let i = 1; i <= pad; i++) {
44
+ if (decrypted[decrypted.length - i] !== pad) {
45
+ throw new Error("[wechat-kf] invalid PKCS#7 padding");
46
+ }
47
+ }
48
+ const content = decrypted.subarray(0, decrypted.length - pad);
49
+ // Parse: random(16) + msg_len(4, big-endian) + msg + receiverId
50
+ if (content.length < 20) {
51
+ throw new Error("[wechat-kf] decrypted content too short");
52
+ }
53
+ const msgLen = content.readUInt32BE(16);
54
+ if (msgLen < 0 || 20 + msgLen > content.length) {
55
+ throw new Error("[wechat-kf] invalid message length in decrypted content");
56
+ }
57
+ const message = content.subarray(20, 20 + msgLen).toString("utf8");
58
+ const receiverId = content.subarray(20 + msgLen).toString("utf8");
59
+ return { message, receiverId };
60
+ }
61
+ /** Encrypt a message for WeChat callback response */
62
+ export function encrypt(encodingAESKey, message, receiverId) {
63
+ const aesKey = deriveAesKey(encodingAESKey);
64
+ const iv = aesKey.subarray(0, 16);
65
+ const random = randomBytes(16);
66
+ const msgBuf = Buffer.from(message, "utf8");
67
+ const receiverBuf = Buffer.from(receiverId, "utf8");
68
+ const msgLenBuf = Buffer.alloc(4);
69
+ msgLenBuf.writeUInt32BE(msgBuf.length, 0);
70
+ const plaintext = Buffer.concat([random, msgLenBuf, msgBuf, receiverBuf]);
71
+ // PKCS#7 padding to 32-byte blocks
72
+ const blockSize = 32;
73
+ const padLen = blockSize - (plaintext.length % blockSize);
74
+ const padding = Buffer.alloc(padLen, padLen);
75
+ const padded = Buffer.concat([plaintext, padding]);
76
+ const cipher = createCipheriv("aes-256-cbc", aesKey, iv);
77
+ cipher.setAutoPadding(false);
78
+ return Buffer.concat([cipher.update(padded), cipher.final()]).toString("base64");
79
+ }
80
+ //# sourceMappingURL=crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * File-system utilities — atomic write via temp file + rename.
3
+ *
4
+ * On POSIX systems, `rename()` within the same filesystem is atomic,
5
+ * so the target file is never left in a partially-written state.
6
+ */
7
+ export declare function atomicWriteFile(filePath: string, content: string, encoding?: BufferEncoding): Promise<void>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * File-system utilities — atomic write via temp file + rename.
3
+ *
4
+ * On POSIX systems, `rename()` within the same filesystem is atomic,
5
+ * so the target file is never left in a partially-written state.
6
+ */
7
+ import { rename, writeFile } from "node:fs/promises";
8
+ export async function atomicWriteFile(filePath, content, encoding = "utf8") {
9
+ const tmpPath = `${filePath}.tmp`;
10
+ await writeFile(tmpPath, content, encoding);
11
+ await rename(tmpPath, filePath); // atomic on the same filesystem
12
+ }
13
+ //# sourceMappingURL=fs-utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs-utils.js","sourceRoot":"","sources":["../../src/fs-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,OAAe,EACf,WAA2B,MAAM;IAEjC,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAClC,MAAM,SAAS,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,MAAM,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,gCAAgC;AACnE,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Monitor — starts webhook server and manages lifecycle
3
+ *
4
+ * Single webhook server for the enterprise. KfIds are discovered dynamically
5
+ * from webhook callbacks. Polling fallback iterates all known kfids.
6
+ */
7
+ import type { Server } from "node:http";
8
+ import { type Logger } from "./bot.js";
9
+ import type { PluginRuntime } from "./runtime.js";
10
+ import type { OpenClawConfig } from "./types.js";
11
+ export type MonitorContext = {
12
+ cfg: OpenClawConfig;
13
+ runtime?: PluginRuntime;
14
+ abortSignal?: AbortSignal;
15
+ stateDir: string;
16
+ log?: Logger;
17
+ };
18
+ export declare function startMonitor(ctx: MonitorContext): Promise<Server>;