@pawastation/wechat-kf 0.1.2 → 0.2.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.
- package/README.md +34 -28
- package/README.zh-CN.md +34 -28
- package/dist/index.d.ts +5 -15
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/src/accounts.d.ts +2 -1
- package/dist/src/accounts.js +61 -19
- package/dist/src/accounts.js.map +1 -1
- package/dist/src/api.d.ts +31 -2
- package/dist/src/api.js +41 -13
- package/dist/src/api.js.map +1 -1
- package/dist/src/bot.d.ts +10 -8
- package/dist/src/bot.js +231 -78
- package/dist/src/bot.js.map +1 -1
- package/dist/src/channel.d.ts +7 -106
- package/dist/src/channel.js +208 -71
- package/dist/src/channel.js.map +1 -1
- package/dist/src/config-schema.d.ts +0 -6
- package/dist/src/config-schema.js +2 -7
- package/dist/src/config-schema.js.map +1 -1
- package/dist/src/constants.d.ts +20 -0
- package/dist/src/constants.js +29 -0
- package/dist/src/constants.js.map +1 -1
- package/dist/src/crypto.js +7 -6
- package/dist/src/crypto.js.map +1 -1
- package/dist/src/monitor.d.ts +27 -14
- package/dist/src/monitor.js +67 -120
- package/dist/src/monitor.js.map +1 -1
- package/dist/src/outbound.d.ts +10 -44
- package/dist/src/outbound.js +277 -92
- package/dist/src/outbound.js.map +1 -1
- package/dist/src/reply-dispatcher.d.ts +2 -6
- package/dist/src/reply-dispatcher.js +131 -32
- package/dist/src/reply-dispatcher.js.map +1 -1
- package/dist/src/runtime.d.ts +1 -119
- package/dist/src/runtime.js +2 -1
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/send-utils.d.ts +13 -0
- package/dist/src/send-utils.js +56 -4
- package/dist/src/send-utils.js.map +1 -1
- package/dist/src/token.js +7 -3
- package/dist/src/token.js.map +1 -1
- package/dist/src/types.d.ts +68 -6
- package/dist/src/webhook.d.ts +16 -16
- package/dist/src/webhook.js +92 -75
- package/dist/src/webhook.js.map +1 -1
- package/dist/src/wechat-kf-directives.d.ts +132 -9
- package/dist/src/wechat-kf-directives.js +535 -24
- package/dist/src/wechat-kf-directives.js.map +1 -1
- package/index.ts +22 -12
- package/openclaw.plugin.json +1 -3
- package/package.json +3 -2
- package/dist/src/chunk-utils.d.ts +0 -18
- package/dist/src/chunk-utils.js +0 -58
- package/dist/src/chunk-utils.js.map +0 -1
package/dist/src/constants.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
/** Channel identifier — single source of truth for the plugin ID string. */
|
|
3
|
+
export const CHANNEL_ID = "wechat-kf";
|
|
4
|
+
/** Default webhook path registered on the framework's shared gateway. */
|
|
5
|
+
export const DEFAULT_WEBHOOK_PATH = `/${CHANNEL_ID}`;
|
|
6
|
+
/** Config key prefix for this channel in OpenClaw config. */
|
|
7
|
+
export const CONFIG_KEY = `channels.${CHANNEL_ID}`;
|
|
8
|
+
/** Build a log-tag prefix: `[wechat-kf]` or `[wechat-kf:kfId]`. */
|
|
9
|
+
export function logTag(kfId) {
|
|
10
|
+
return kfId ? `[${CHANNEL_ID}:${kfId}]` : `[${CHANNEL_ID}]`;
|
|
11
|
+
}
|
|
12
|
+
/** Default state directory for cursor and kfid persistence. */
|
|
13
|
+
export function defaultStateDir() {
|
|
14
|
+
return `${homedir()}/.openclaw/state/${CHANNEL_ID}`;
|
|
15
|
+
}
|
|
16
|
+
/** Cursor file name for a given kfId. */
|
|
17
|
+
export function cursorFileName(kfId) {
|
|
18
|
+
return `${CHANNEL_ID}-cursor-${kfId}.txt`;
|
|
19
|
+
}
|
|
20
|
+
/** Persisted file name for discovered kfids. */
|
|
21
|
+
export const KFIDS_FILE = `${CHANNEL_ID}-kfids.json`;
|
|
22
|
+
/** Persisted file name for disabled kfids. */
|
|
23
|
+
export const DISABLED_KFIDS_FILE = `${CHANNEL_ID}-disabled-kfids.json`;
|
|
1
24
|
/** WeChat KF text message character limit */
|
|
2
25
|
export const WECHAT_TEXT_CHUNK_LIMIT = 2000;
|
|
3
26
|
/** Timeout for token fetch requests (ms) */
|
|
@@ -17,4 +40,10 @@ export const TOKEN_EXPIRED_CODES = new Set([40014, 42001, 40001]);
|
|
|
17
40
|
export const WECHAT_MSG_LIMIT_ERRCODE = 95026;
|
|
18
41
|
/** Timeout for downloading media from external HTTP URLs (ms) */
|
|
19
42
|
export const MEDIA_DOWNLOAD_TIMEOUT_MS = 60_000;
|
|
43
|
+
/** Max age (seconds) for inbound messages. Messages older than this are skipped. */
|
|
44
|
+
export const MAX_MESSAGE_AGE_S = 300; // 5 minutes
|
|
45
|
+
/** Format an unknown caught value for log messages (no stack traces). */
|
|
46
|
+
export function formatError(err) {
|
|
47
|
+
return err instanceof Error ? err.message : String(err);
|
|
48
|
+
}
|
|
20
49
|
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,4EAA4E;AAC5E,MAAM,CAAC,MAAM,UAAU,GAAG,WAAoB,CAAC;AAE/C,yEAAyE;AACzE,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,UAAU,EAAE,CAAC;AAErD,6DAA6D;AAC7D,MAAM,CAAC,MAAM,UAAU,GAAG,YAAY,UAAU,EAAE,CAAC;AAEnD,mEAAmE;AACnE,MAAM,UAAU,MAAM,CAAC,IAAa;IAClC,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,UAAU,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,UAAU,GAAG,CAAC;AAC9D,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,eAAe;IAC7B,OAAO,GAAG,OAAO,EAAE,oBAAoB,UAAU,EAAE,CAAC;AACtD,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,GAAG,UAAU,WAAW,IAAI,MAAM,CAAC;AAC5C,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,MAAM,UAAU,GAAG,GAAG,UAAU,aAAa,CAAC;AAErD,8CAA8C;AAC9C,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAAG,UAAU,sBAAsB,CAAC;AAEvE,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;AAEhD,oFAAoF;AACpF,MAAM,CAAC,MAAM,iBAAiB,GAAG,GAAG,CAAC,CAAC,YAAY;AAElD,yEAAyE;AACzE,MAAM,UAAU,WAAW,CAAC,GAAY;IACtC,OAAO,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC"}
|
package/dist/src/crypto.js
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
* - Plaintext format: random(16B) + msg_len(4B network order) + msg + receiveid
|
|
7
7
|
*/
|
|
8
8
|
import { createCipheriv, createDecipheriv, createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
9
|
+
import { logTag } from "./constants.js";
|
|
9
10
|
export function deriveAesKey(encodingAESKey) {
|
|
10
11
|
if (encodingAESKey.length !== 43) {
|
|
11
|
-
throw new Error(
|
|
12
|
+
throw new Error(`${logTag()} EncodingAESKey must be 43 characters, got ${encodingAESKey.length}`);
|
|
12
13
|
}
|
|
13
14
|
const key = Buffer.from(`${encodingAESKey}=`, "base64");
|
|
14
15
|
if (key.length !== 32) {
|
|
15
|
-
throw new Error(
|
|
16
|
+
throw new Error(`${logTag()} derived AES key must be 32 bytes, got ${key.length}`);
|
|
16
17
|
}
|
|
17
18
|
return key;
|
|
18
19
|
}
|
|
@@ -38,21 +39,21 @@ export function decrypt(encodingAESKey, encrypted) {
|
|
|
38
39
|
// Remove PKCS#7 padding — validate ALL N padding bytes equal N
|
|
39
40
|
const pad = decrypted[decrypted.length - 1];
|
|
40
41
|
if (pad < 1 || pad > 32 || pad > decrypted.length) {
|
|
41
|
-
throw new Error(
|
|
42
|
+
throw new Error(`${logTag()} invalid PKCS#7 padding`);
|
|
42
43
|
}
|
|
43
44
|
for (let i = 1; i <= pad; i++) {
|
|
44
45
|
if (decrypted[decrypted.length - i] !== pad) {
|
|
45
|
-
throw new Error(
|
|
46
|
+
throw new Error(`${logTag()} invalid PKCS#7 padding`);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
const content = decrypted.subarray(0, decrypted.length - pad);
|
|
49
50
|
// Parse: random(16) + msg_len(4, big-endian) + msg + receiverId
|
|
50
51
|
if (content.length < 20) {
|
|
51
|
-
throw new Error(
|
|
52
|
+
throw new Error(`${logTag()} decrypted content too short`);
|
|
52
53
|
}
|
|
53
54
|
const msgLen = content.readUInt32BE(16);
|
|
54
55
|
if (msgLen < 0 || 20 + msgLen > content.length) {
|
|
55
|
-
throw new Error(
|
|
56
|
+
throw new Error(`${logTag()} invalid message length in decrypted content`);
|
|
56
57
|
}
|
|
57
58
|
const message = content.subarray(20, 20 + msgLen).toString("utf8");
|
|
58
59
|
const receiverId = content.subarray(20 + msgLen).toString("utf8");
|
package/dist/src/crypto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/crypto.ts"],"names":[],"mappings":"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"}
|
package/dist/src/monitor.d.ts
CHANGED
|
@@ -1,18 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Shared context manager for WeChat KF plugin
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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).
|
|
6
7
|
*/
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
stateDir: string;
|
|
16
|
-
log?: Logger;
|
|
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;
|
|
17
16
|
};
|
|
18
|
-
export declare function
|
|
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;
|
package/dist/src/monitor.js
CHANGED
|
@@ -1,131 +1,78 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Shared context manager for WeChat KF plugin
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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).
|
|
6
7
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
onEvent: async () => { },
|
|
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
24
|
});
|
|
25
|
-
return dummyServer;
|
|
26
25
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Validate access token on startup
|
|
37
|
-
try {
|
|
38
|
-
await getAccessToken(corpId, appSecret);
|
|
39
|
-
log?.info(`[wechat-kf] access_token validated`);
|
|
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;
|
|
40
35
|
}
|
|
41
|
-
|
|
42
|
-
|
|
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"));
|
|
43
52
|
}
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
encodingAESKey,
|
|
50
|
-
corpId,
|
|
51
|
-
onEvent: async (kfId, syncToken) => {
|
|
52
|
-
if (!kfId) {
|
|
53
|
-
log?.error("[wechat-kf] webhook callback missing OpenKfId, ignoring");
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
await handleWebhookEvent(botCtx, kfId, syncToken);
|
|
58
|
-
}
|
|
59
|
-
catch (err) {
|
|
60
|
-
log?.error(`[wechat-kf:${kfId}] event processing error: ${err}`);
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
});
|
|
64
|
-
await new Promise((resolve, reject) => {
|
|
65
|
-
const timeout = setTimeout(() => reject(new Error(`[wechat-kf] server.listen(:${webhookPort}) timed out`)), 10_000);
|
|
66
|
-
const onError = (err) => {
|
|
67
|
-
clearTimeout(timeout);
|
|
68
|
-
reject(err);
|
|
53
|
+
const ready = ensureReadyPromise();
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
const onReady = () => {
|
|
56
|
+
signal?.removeEventListener("abort", onAbort);
|
|
57
|
+
resolve(sharedCtx);
|
|
69
58
|
};
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
clearTimeout(timeout);
|
|
73
|
-
server.removeListener("error", onError);
|
|
74
|
-
log?.info(`[wechat-kf] webhook listening on :${webhookPort}${webhookPath}`);
|
|
75
|
-
resolve();
|
|
76
|
-
});
|
|
77
|
-
});
|
|
78
|
-
// ── Polling fallback ──
|
|
79
|
-
// Poll sync_msg for each known kfid as fallback
|
|
80
|
-
const POLL_INTERVAL_MS = 30000;
|
|
81
|
-
let pollTimer = null;
|
|
82
|
-
let polling = false;
|
|
83
|
-
pollTimer = setInterval(async () => {
|
|
84
|
-
if (abortSignal?.aborted)
|
|
85
|
-
return;
|
|
86
|
-
if (polling)
|
|
87
|
-
return;
|
|
88
|
-
polling = true;
|
|
89
|
-
try {
|
|
90
|
-
const kfIds = getKnownKfIds();
|
|
91
|
-
if (kfIds.length === 0) {
|
|
92
|
-
// No kfids discovered yet, nothing to poll
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
for (const kfId of kfIds) {
|
|
96
|
-
if (abortSignal?.aborted)
|
|
97
|
-
break;
|
|
98
|
-
try {
|
|
99
|
-
log?.info(`[wechat-kf:${kfId}] polling sync_msg...`);
|
|
100
|
-
await handleWebhookEvent(botCtx, kfId, "");
|
|
101
|
-
}
|
|
102
|
-
catch (err) {
|
|
103
|
-
log?.error(`[wechat-kf:${kfId}] poll error: ${err instanceof Error ? err.stack || err.message : err}`);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
finally {
|
|
108
|
-
polling = false;
|
|
109
|
-
}
|
|
110
|
-
}, POLL_INTERVAL_MS);
|
|
111
|
-
log?.info(`[wechat-kf] polling fallback enabled (every ${POLL_INTERVAL_MS}ms)`);
|
|
112
|
-
if (abortSignal) {
|
|
113
|
-
const cleanup = () => {
|
|
114
|
-
if (pollTimer) {
|
|
115
|
-
clearInterval(pollTimer);
|
|
116
|
-
pollTimer = null;
|
|
117
|
-
}
|
|
118
|
-
server.close();
|
|
119
|
-
log?.info("[wechat-kf] webhook server + polling stopped");
|
|
59
|
+
const onAbort = () => {
|
|
60
|
+
reject(new DOMException("The operation was aborted.", "AbortError"));
|
|
120
61
|
};
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
+
pairingKfIdCache.clear();
|
|
70
|
+
}
|
|
71
|
+
/** Reset all module-level state. @internal For testing only. */
|
|
72
|
+
export function _reset() {
|
|
73
|
+
sharedCtx = null;
|
|
74
|
+
readyResolve = null;
|
|
75
|
+
readyPromise = null;
|
|
76
|
+
pairingKfIdCache.clear();
|
|
130
77
|
}
|
|
131
78
|
//# sourceMappingURL=monitor.js.map
|
package/dist/src/monitor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"monitor.js","sourceRoot":"","sources":["../../src/monitor.ts"],"names":[],"mappings":"AAAA
|
|
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,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"}
|
package/dist/src/outbound.d.ts
CHANGED
|
@@ -5,12 +5,15 @@
|
|
|
5
5
|
* This module implements the OpenClaw `ChannelPlugin.outbound` interface and
|
|
6
6
|
* is called by the framework when the agent produces a final reply.
|
|
7
7
|
*
|
|
8
|
-
* Text
|
|
9
|
-
*
|
|
8
|
+
* Text is first converted from markdown to Unicode formatting (formatText),
|
|
9
|
+
* then chunked via the runtime's `chunkTextWithMode` helper (respecting user
|
|
10
|
+
* `chunkMode` config). Framework auto-chunking is disabled (`chunker: null`)
|
|
11
|
+
* because it would chunk *before* formatting, causing post-format expansion
|
|
12
|
+
* (Unicode math-bold chars are 2 `.length` units each) to exceed the limit.
|
|
10
13
|
*
|
|
11
|
-
* For media, the
|
|
12
|
-
*
|
|
13
|
-
* `uploadAndSendMedia`
|
|
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`.
|
|
14
17
|
*
|
|
15
18
|
* WeChat KF session limits:
|
|
16
19
|
* The API enforces a 48-hour / 5-message limit per session window.
|
|
@@ -25,42 +28,5 @@
|
|
|
25
28
|
*
|
|
26
29
|
* accountId = openKfId (dynamically discovered)
|
|
27
30
|
*/
|
|
28
|
-
import type {
|
|
29
|
-
export
|
|
30
|
-
cfg: OpenClawConfig;
|
|
31
|
-
to: string;
|
|
32
|
-
text: string;
|
|
33
|
-
accountId: string;
|
|
34
|
-
};
|
|
35
|
-
export type SendMediaParams = {
|
|
36
|
-
cfg: OpenClawConfig;
|
|
37
|
-
to: string;
|
|
38
|
-
text?: string;
|
|
39
|
-
mediaUrl?: string;
|
|
40
|
-
accountId: string;
|
|
41
|
-
};
|
|
42
|
-
export type SendResult = {
|
|
43
|
-
channel: string;
|
|
44
|
-
messageId: string;
|
|
45
|
-
chatId: string;
|
|
46
|
-
};
|
|
47
|
-
export type SendPayloadParams = {
|
|
48
|
-
cfg: OpenClawConfig;
|
|
49
|
-
to: string;
|
|
50
|
-
text?: string;
|
|
51
|
-
accountId: string;
|
|
52
|
-
payload: {
|
|
53
|
-
text?: string;
|
|
54
|
-
channelData?: Record<string, unknown>;
|
|
55
|
-
[key: string]: unknown;
|
|
56
|
-
};
|
|
57
|
-
};
|
|
58
|
-
export declare const wechatKfOutbound: {
|
|
59
|
-
deliveryMode: "direct";
|
|
60
|
-
chunker: (text: string, limit: number) => string[];
|
|
61
|
-
chunkerMode: "text";
|
|
62
|
-
textChunkLimit: number;
|
|
63
|
-
sendText: ({ cfg, to, text, accountId }: SendTextParams) => Promise<SendResult>;
|
|
64
|
-
sendMedia: ({ cfg, to, text, mediaUrl, accountId }: SendMediaParams) => Promise<SendResult>;
|
|
65
|
-
sendPayload: ({ cfg, to, text, accountId, payload }: SendPayloadParams) => Promise<SendResult>;
|
|
66
|
-
};
|
|
31
|
+
import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk";
|
|
32
|
+
export declare const wechatKfOutbound: ChannelOutboundAdapter;
|