@yaoqi10012/wechat-kf 0.3.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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/dist/accounts.d.ts +38 -0
  3. package/dist/accounts.js +247 -0
  4. package/dist/accounts.js.map +1 -0
  5. package/dist/api.d.ts +58 -0
  6. package/dist/api.js +200 -0
  7. package/dist/api.js.map +1 -0
  8. package/dist/bot.d.ts +37 -0
  9. package/dist/bot.js +672 -0
  10. package/dist/bot.js.map +1 -0
  11. package/dist/channel.d.ts +14 -0
  12. package/dist/channel.js +320 -0
  13. package/dist/channel.js.map +1 -0
  14. package/dist/config-schema.d.ts +56 -0
  15. package/dist/config-schema.js +39 -0
  16. package/dist/config-schema.js.map +1 -0
  17. package/dist/constants.d.ts +41 -0
  18. package/dist/constants.js +51 -0
  19. package/dist/constants.js.map +1 -0
  20. package/dist/crypto.d.ts +18 -0
  21. package/dist/crypto.js +81 -0
  22. package/dist/crypto.js.map +1 -0
  23. package/dist/fs-utils.d.ts +7 -0
  24. package/dist/fs-utils.js +13 -0
  25. package/dist/fs-utils.js.map +1 -0
  26. package/dist/monitor.d.ts +31 -0
  27. package/dist/monitor.js +80 -0
  28. package/dist/monitor.js.map +1 -0
  29. package/dist/outbound.d.ts +32 -0
  30. package/dist/outbound.js +411 -0
  31. package/dist/outbound.js.map +1 -0
  32. package/dist/reply-dispatcher.d.ts +36 -0
  33. package/dist/reply-dispatcher.js +216 -0
  34. package/dist/reply-dispatcher.js.map +1 -0
  35. package/dist/runtime.d.ts +12 -0
  36. package/dist/runtime.js +23 -0
  37. package/dist/runtime.js.map +1 -0
  38. package/dist/send-utils.d.ts +52 -0
  39. package/dist/send-utils.js +217 -0
  40. package/dist/send-utils.js.map +1 -0
  41. package/dist/token.d.ts +8 -0
  42. package/dist/token.js +61 -0
  43. package/dist/token.js.map +1 -0
  44. package/dist/types.d.ts +236 -0
  45. package/dist/types.js +3 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/unicode-format.d.ts +26 -0
  48. package/dist/unicode-format.js +157 -0
  49. package/dist/unicode-format.js.map +1 -0
  50. package/dist/webhook.d.ts +22 -0
  51. package/dist/webhook.js +148 -0
  52. package/dist/webhook.js.map +1 -0
  53. package/dist/wechat-kf-directives.d.ts +157 -0
  54. package/dist/wechat-kf-directives.js +576 -0
  55. package/dist/wechat-kf-directives.js.map +1 -0
  56. package/openclaw.plugin.json +31 -0
  57. package/package.json +92 -0
package/dist/crypto.js ADDED
@@ -0,0 +1,81 @@
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
+ import { logTag } from "./constants.js";
10
+ export function deriveAesKey(encodingAESKey) {
11
+ if (encodingAESKey.length !== 43) {
12
+ throw new Error(`${logTag()} EncodingAESKey must be 43 characters, got ${encodingAESKey.length}`);
13
+ }
14
+ const key = Buffer.from(`${encodingAESKey}=`, "base64");
15
+ if (key.length !== 32) {
16
+ throw new Error(`${logTag()} derived AES key must be 32 bytes, got ${key.length}`);
17
+ }
18
+ return key;
19
+ }
20
+ /** SHA1 signature verification */
21
+ export function computeSignature(token, timestamp, nonce, encrypt) {
22
+ const items = [token, timestamp, nonce, encrypt].sort();
23
+ return createHash("sha1").update(items.join("")).digest("hex");
24
+ }
25
+ export function verifySignature(token, timestamp, nonce, encrypt, expectedSignature) {
26
+ const actual = Buffer.from(computeSignature(token, timestamp, nonce, encrypt), "utf8");
27
+ const expected = Buffer.from(expectedSignature, "utf8");
28
+ if (actual.length !== expected.length)
29
+ return false;
30
+ return timingSafeEqual(actual, expected);
31
+ }
32
+ /** Decrypt an encrypted message from WeChat callback */
33
+ export function decrypt(encodingAESKey, encrypted) {
34
+ const aesKey = deriveAesKey(encodingAESKey);
35
+ const iv = aesKey.subarray(0, 16);
36
+ const decipher = createDecipheriv("aes-256-cbc", aesKey, iv);
37
+ decipher.setAutoPadding(false);
38
+ const decrypted = Buffer.concat([decipher.update(encrypted, "base64"), decipher.final()]);
39
+ // Remove PKCS#7 padding — validate ALL N padding bytes equal N
40
+ const pad = decrypted[decrypted.length - 1];
41
+ if (pad < 1 || pad > 32 || pad > decrypted.length) {
42
+ throw new Error(`${logTag()} invalid PKCS#7 padding`);
43
+ }
44
+ for (let i = 1; i <= pad; i++) {
45
+ if (decrypted[decrypted.length - i] !== pad) {
46
+ throw new Error(`${logTag()} invalid PKCS#7 padding`);
47
+ }
48
+ }
49
+ const content = decrypted.subarray(0, decrypted.length - pad);
50
+ // Parse: random(16) + msg_len(4, big-endian) + msg + receiverId
51
+ if (content.length < 20) {
52
+ throw new Error(`${logTag()} decrypted content too short`);
53
+ }
54
+ const msgLen = content.readUInt32BE(16);
55
+ if (msgLen < 0 || 20 + msgLen > content.length) {
56
+ throw new Error(`${logTag()} invalid message length in decrypted content`);
57
+ }
58
+ const message = content.subarray(20, 20 + msgLen).toString("utf8");
59
+ const receiverId = content.subarray(20 + msgLen).toString("utf8");
60
+ return { message, receiverId };
61
+ }
62
+ /** Encrypt a message for WeChat callback response */
63
+ export function encrypt(encodingAESKey, message, receiverId) {
64
+ const aesKey = deriveAesKey(encodingAESKey);
65
+ const iv = aesKey.subarray(0, 16);
66
+ const random = randomBytes(16);
67
+ const msgBuf = Buffer.from(message, "utf8");
68
+ const receiverBuf = Buffer.from(receiverId, "utf8");
69
+ const msgLenBuf = Buffer.alloc(4);
70
+ msgLenBuf.writeUInt32BE(msgBuf.length, 0);
71
+ const plaintext = Buffer.concat([random, msgLenBuf, msgBuf, receiverBuf]);
72
+ // PKCS#7 padding to 32-byte blocks
73
+ const blockSize = 32;
74
+ const padLen = blockSize - (plaintext.length % blockSize);
75
+ const padding = Buffer.alloc(padLen, padLen);
76
+ const padded = Buffer.concat([plaintext, padding]);
77
+ const cipher = createCipheriv("aes-256-cbc", aesKey, iv);
78
+ cipher.setAutoPadding(false);
79
+ return Buffer.concat([cipher.update(padded), cipher.final()]).toString("base64");
80
+ }
81
+ //# 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,31 @@
1
+ /**
2
+ * Shared context manager for WeChat KF plugin
3
+ *
4
+ * Provides a rendezvous point between the "default" account (which sets up
5
+ * enterprise-level shared infrastructure) and per-kfId accounts (which need
6
+ * the shared crypto config and BotContext to start polling).
7
+ */
8
+ import type { BotContext } from "./bot.js";
9
+ export type SharedContext = {
10
+ callbackToken: string;
11
+ encodingAESKey: string;
12
+ corpId: string;
13
+ appSecret: string;
14
+ webhookPath: string;
15
+ botCtx: BotContext;
16
+ };
17
+ export declare function setPairingKfId(externalUserId: string, openKfId: string): void;
18
+ export declare function getPairingKfId(externalUserId: string): string | undefined;
19
+ /** Set the shared context. Resolves any pending waitForSharedContext calls. */
20
+ export declare function setSharedContext(ctx: SharedContext): void;
21
+ /** Get the shared context, or null if not yet set. */
22
+ export declare function getSharedContext(): SharedContext | null;
23
+ /**
24
+ * Wait until the shared context is set.
25
+ * Rejects if the signal aborts before the context is ready.
26
+ */
27
+ export declare function waitForSharedContext(signal?: AbortSignal): Promise<SharedContext>;
28
+ /** Clear the shared context (used during shutdown). */
29
+ export declare function clearSharedContext(): void;
30
+ /** Reset all module-level state. @internal For testing only. */
31
+ export declare function _reset(): void;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Shared context manager for WeChat KF plugin
3
+ *
4
+ * Provides a rendezvous point between the "default" account (which sets up
5
+ * enterprise-level shared infrastructure) and per-kfId accounts (which need
6
+ * the shared crypto config and BotContext to start polling).
7
+ */
8
+ // ── Module-level state ──
9
+ let sharedCtx = null;
10
+ let readyResolve = null;
11
+ let readyPromise = null;
12
+ /** Cache: externalUserId → openKfId for pairing approval notifications. */
13
+ const pairingKfIdCache = new Map();
14
+ export function setPairingKfId(externalUserId, openKfId) {
15
+ pairingKfIdCache.set(externalUserId, openKfId);
16
+ }
17
+ export function getPairingKfId(externalUserId) {
18
+ return pairingKfIdCache.get(externalUserId);
19
+ }
20
+ function ensureReadyPromise() {
21
+ if (!readyPromise) {
22
+ readyPromise = new Promise((resolve) => {
23
+ readyResolve = resolve;
24
+ });
25
+ }
26
+ return readyPromise;
27
+ }
28
+ /** Set the shared context. Resolves any pending waitForSharedContext calls. */
29
+ export function setSharedContext(ctx) {
30
+ sharedCtx = ctx;
31
+ // Resolve waiting callers
32
+ if (readyResolve) {
33
+ readyResolve();
34
+ readyResolve = null;
35
+ }
36
+ }
37
+ /** Get the shared context, or null if not yet set. */
38
+ export function getSharedContext() {
39
+ return sharedCtx;
40
+ }
41
+ /**
42
+ * Wait until the shared context is set.
43
+ * Rejects if the signal aborts before the context is ready.
44
+ */
45
+ export function waitForSharedContext(signal) {
46
+ // Already available — fast path
47
+ if (sharedCtx)
48
+ return Promise.resolve(sharedCtx);
49
+ // Already aborted
50
+ if (signal?.aborted) {
51
+ return Promise.reject(new DOMException("The operation was aborted.", "AbortError"));
52
+ }
53
+ const ready = ensureReadyPromise();
54
+ return new Promise((resolve, reject) => {
55
+ const onReady = () => {
56
+ signal?.removeEventListener("abort", onAbort);
57
+ resolve(sharedCtx);
58
+ };
59
+ const onAbort = () => {
60
+ reject(new DOMException("The operation was aborted.", "AbortError"));
61
+ };
62
+ signal?.addEventListener("abort", onAbort, { once: true });
63
+ ready.then(onReady);
64
+ });
65
+ }
66
+ /** Clear the shared context (used during shutdown). */
67
+ export function clearSharedContext() {
68
+ sharedCtx = null;
69
+ readyPromise = null;
70
+ readyResolve = null;
71
+ pairingKfIdCache.clear();
72
+ }
73
+ /** Reset all module-level state. @internal For testing only. */
74
+ export function _reset() {
75
+ sharedCtx = null;
76
+ readyResolve = null;
77
+ readyPromise = null;
78
+ pairingKfIdCache.clear();
79
+ }
80
+ //# sourceMappingURL=monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"monitor.js","sourceRoot":"","sources":["../src/monitor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,2BAA2B;AAE3B,IAAI,SAAS,GAAyB,IAAI,CAAC;AAC3C,IAAI,YAAY,GAAwB,IAAI,CAAC;AAC7C,IAAI,YAAY,GAAyB,IAAI,CAAC;AAE9C,2EAA2E;AAC3E,MAAM,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;AAEnD,MAAM,UAAU,cAAc,CAAC,cAAsB,EAAE,QAAgB;IACrE,gBAAgB,CAAC,GAAG,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,cAAsB;IACnD,OAAO,gBAAgB,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,kBAAkB;IACzB,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,YAAY,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YAC3C,YAAY,GAAG,OAAO,CAAC;QACzB,CAAC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,gBAAgB,CAAC,GAAkB;IACjD,SAAS,GAAG,GAAG,CAAC;IAChB,0BAA0B;IAC1B,IAAI,YAAY,EAAE,CAAC;QACjB,YAAY,EAAE,CAAC;QACf,YAAY,GAAG,IAAI,CAAC;IACtB,CAAC;AACH,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,gBAAgB;IAC9B,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,gCAAgC;IAChC,IAAI,SAAS;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEjD,kBAAkB;IAClB,IAAI,MAAM,EAAE,OAAO,EAAE,CAAC;QACpB,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,4BAA4B,EAAE,YAAY,CAAC,CAAC,CAAC;IACtF,CAAC;IAED,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IAEnC,OAAO,IAAI,OAAO,CAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACpD,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,EAAE,mBAAmB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC9C,OAAO,CAAC,SAAU,CAAC,CAAC;QACtB,CAAC,CAAC;QACF,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,MAAM,CAAC,IAAI,YAAY,CAAC,4BAA4B,EAAE,YAAY,CAAC,CAAC,CAAC;QACvE,CAAC,CAAC;QAEF,MAAM,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,kBAAkB;IAChC,SAAS,GAAG,IAAI,CAAC;IACjB,YAAY,GAAG,IAAI,CAAC;IACpB,YAAY,GAAG,IAAI,CAAC;IACpB,gBAAgB,CAAC,KAAK,EAAE,CAAC;AAC3B,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,MAAM;IACpB,SAAS,GAAG,IAAI,CAAC;IACjB,YAAY,GAAG,IAAI,CAAC;IACpB,YAAY,GAAG,IAAI,CAAC;IACpB,gBAAgB,CAAC,KAAK,EAAE,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Outbound message adapter for WeChat KF (framework-driven direct delivery)
3
+ *
4
+ * Responsibility:
5
+ * This module implements the OpenClaw `ChannelPlugin.outbound` interface and
6
+ * is called by the framework when the agent produces a final reply.
7
+ *
8
+ * Text is first converted from markdown to Unicode formatting (formatText),
9
+ * then chunked by UTF-8 byte length via `chunkTextByUtf8Bytes` (WeChat API
10
+ * enforces a 2048-byte limit on text.content). Framework auto-chunking is
11
+ * disabled (`chunker: null`) because it would chunk *before* formatting,
12
+ * causing post-format expansion to exceed the limit.
13
+ *
14
+ * For media, the framework's `loadWebMedia` handles all URL formats
15
+ * (HTTP, file://, local paths, MEDIA: prefix, ~), then the buffer is
16
+ * uploaded to WeChat and sent using `uploadAndSendMedia` from `send-utils.ts`.
17
+ *
18
+ * WeChat KF session limits:
19
+ * The API enforces a 48-hour / 5-message limit per session window.
20
+ * Once a customer sends a message, the agent may reply with up to 5 messages
21
+ * within 48 hours. After that, sending returns errcode 95026.
22
+ * This module detects that error and logs a clear warning rather than
23
+ * propagating a generic failure.
24
+ *
25
+ * Counterpart:
26
+ * `reply-dispatcher.ts` handles the *other* outbound path: typing-aware
27
+ * streaming replies dispatched internally by `bot.ts`.
28
+ *
29
+ * accountId = openKfId (dynamically discovered)
30
+ */
31
+ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
32
+ export declare const wechatKfOutbound: ChannelOutboundAdapter;