openclaw-weixin 3.1.6 → 3.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/CHANGELOG.md +29 -0
- package/CHANGELOG_EN.md +38 -0
- package/NOTICE +2 -1
- package/README.md +12 -17
- package/README_EN.md +14 -20
- package/assets/icon.png +0 -0
- package/dist/src/auth/accounts.js +12 -15
- package/dist/src/auth/accounts.js.map +1 -1
- package/dist/src/channel.js +31 -109
- package/dist/src/channel.js.map +1 -1
- package/dist/src/config/block-streaming.js +8 -0
- package/dist/src/config/block-streaming.js.map +1 -0
- package/dist/src/config/config-schema.js +10 -0
- package/dist/src/config/config-schema.js.map +1 -1
- package/dist/src/messaging/inbound-turn.js +102 -0
- package/dist/src/messaging/inbound-turn.js.map +1 -0
- package/dist/src/messaging/outbound-hooks.js +34 -2
- package/dist/src/messaging/outbound-hooks.js.map +1 -1
- package/dist/src/messaging/process-message.js +101 -196
- package/dist/src/messaging/process-message.js.map +1 -1
- package/dist/src/monitor/monitor.js +1 -1
- package/docs/en/architecture.md +22 -8
- package/docs/en/guide.md +77 -15
- package/docs/zh-CN/architecture.md +18 -8
- package/docs/zh-CN/guide.md +72 -13
- package/openclaw.plugin.json +6 -2
- package/package.json +8 -14
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function resolveWeixinBlockStreamingEnabled(cfg, accountId, fallbackAccountId) {
|
|
2
|
+
const section = cfg.channels?.["openclaw-weixin"];
|
|
3
|
+
return (section?.accounts?.[accountId]?.blockStreaming ??
|
|
4
|
+
(fallbackAccountId ? section?.accounts?.[fallbackAccountId]?.blockStreaming : undefined) ??
|
|
5
|
+
section?.blockStreaming ??
|
|
6
|
+
true);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=block-streaming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"block-streaming.js","sourceRoot":"","sources":["../../../src/config/block-streaming.ts"],"names":[],"mappings":"AAOA,MAAM,UAAU,kCAAkC,CAChD,GAAmB,EACnB,SAAiB,EACjB,iBAA0B;IAE1B,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC,iBAAiB,CAA2C,CAAC;IAC5F,OAAO,CACL,OAAO,EAAE,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE,cAAc;QAC9C,CAAC,iBAAiB,CAAC,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,iBAAiB,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,SAAS,CAAC;QACxF,OAAO,EAAE,cAAc;QACvB,IAAI,CACL,CAAC;AACJ,CAAC"}
|
|
@@ -9,6 +9,10 @@ const weixinAccountProperties = {
|
|
|
9
9
|
baseUrl: { type: "string", default: DEFAULT_BASE_URL },
|
|
10
10
|
cdnBaseUrl: { type: "string", default: CDN_BASE_URL },
|
|
11
11
|
routeTag: { type: "number" },
|
|
12
|
+
blockStreaming: {
|
|
13
|
+
type: "boolean",
|
|
14
|
+
description: "Send completed text blocks before the final reply.",
|
|
15
|
+
},
|
|
12
16
|
};
|
|
13
17
|
const weixinAccountSchema = {
|
|
14
18
|
type: "object",
|
|
@@ -24,6 +28,11 @@ const replyProgressMessagesSchema = {
|
|
|
24
28
|
default: true,
|
|
25
29
|
description: "Send structured tool-call progress messages.",
|
|
26
30
|
};
|
|
31
|
+
const blockStreamingSchema = {
|
|
32
|
+
type: "boolean",
|
|
33
|
+
default: true,
|
|
34
|
+
description: "Send completed text blocks before the final reply.",
|
|
35
|
+
};
|
|
27
36
|
/** Top-level weixin config schema (token is stored in credentials file, not config). */
|
|
28
37
|
const weixinChannelConfigJsonSchema = {
|
|
29
38
|
type: "object",
|
|
@@ -37,6 +46,7 @@ const weixinChannelConfigJsonSchema = {
|
|
|
37
46
|
additionalProperties: weixinAccountSchema,
|
|
38
47
|
},
|
|
39
48
|
botAgent: botAgentSchema,
|
|
49
|
+
blockStreaming: blockStreamingSchema,
|
|
40
50
|
replyProgressMessages: replyProgressMessagesSchema,
|
|
41
51
|
/** ISO 8601; bumped on each successful login to refresh gateway config from disk. */
|
|
42
52
|
channelConfigUpdatedAt: { type: "string" },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config-schema.js","sourceRoot":"","sources":["../../../src/config/config-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,2CAA2C,CAAC;AAEzF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAErE,MAAM,cAAc,GAAG;IACrB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;CACvC,CAAC;AAEX,MAAM,uBAAuB,GAAG;IAC9B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IAC5B,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE;IACtD,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;IACrD,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;
|
|
1
|
+
{"version":3,"file":"config-schema.js","sourceRoot":"","sources":["../../../src/config/config-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,4BAA4B,EAAE,MAAM,2CAA2C,CAAC;AAEzF,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAErE,MAAM,cAAc,GAAG;IACrB,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;CACvC,CAAC;AAEX,MAAM,uBAAuB,GAAG;IAC9B,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IACxB,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;IAC5B,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,gBAAgB,EAAE;IACtD,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;IACrD,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;IAC5B,cAAc,EAAE;QACd,IAAI,EAAE,SAAS;QACf,WAAW,EAAE,oDAAoD;KAClE;CACO,CAAC;AAEX,MAAM,mBAAmB,GAAG;IAC1B,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,IAAI;IAC1B,UAAU,EAAE,uBAAuB;CAC3B,CAAC;AAEX,MAAM,cAAc,GAAG;IACrB,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,+DAA+D;CACpE,CAAC;AAEX,MAAM,2BAA2B,GAAG;IAClC,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,8CAA8C;CACnD,CAAC;AAEX,MAAM,oBAAoB,GAAG;IAC3B,IAAI,EAAE,SAAS;IACf,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,oDAAoD;CACzD,CAAC;AAEX,wFAAwF;AACxF,MAAM,6BAA6B,GAAG;IACpC,IAAI,EAAE,QAAQ;IACd,oBAAoB,EAAE,IAAI;IAC1B,UAAU,EAAE;QACV,GAAG,uBAAuB;QAC1B,iFAAiF;QACjF,QAAQ,EAAE,cAAc;QACxB,QAAQ,EAAE;YACR,IAAI,EAAE,QAAQ;YACd,oBAAoB,EAAE,mBAAmB;SAC1C;QACD,QAAQ,EAAE,cAAc;QACxB,cAAc,EAAE,oBAAoB;QACpC,qBAAqB,EAAE,2BAA2B;QAClD,qFAAqF;QACrF,sBAAsB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;KAC3C;CACO,CAAC;AAEX,MAAM,CAAC,MAAM,yBAAyB,GAAG,4BAA4B,CAAC,6BAA6B,CAAC,CAAC"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { sendWeixinWithHooks } from "./outbound-hooks.js";
|
|
2
|
+
/** Only the two public inbound contracts are supported; a failed dispatch is never replayed. */
|
|
3
|
+
export async function dispatchWeixinInboundTurn(params) {
|
|
4
|
+
const { channelRuntime, route, onReplyAdmitted, onReplyDeferred, onDeferredComplete, ...turn } = params;
|
|
5
|
+
const inbound = channelRuntime.inbound;
|
|
6
|
+
if (!inbound) {
|
|
7
|
+
throw new Error("weixin: host inbound runtime is missing");
|
|
8
|
+
}
|
|
9
|
+
let admitted = false;
|
|
10
|
+
const admit = () => {
|
|
11
|
+
if (admitted)
|
|
12
|
+
return;
|
|
13
|
+
admitted = true;
|
|
14
|
+
onReplyAdmitted?.();
|
|
15
|
+
};
|
|
16
|
+
const defer = () => {
|
|
17
|
+
onReplyDeferred?.();
|
|
18
|
+
admit();
|
|
19
|
+
};
|
|
20
|
+
const onAgentRunStart = turn.replyOptions?.onAgentRunStart;
|
|
21
|
+
const replyOptions = {
|
|
22
|
+
...turn.replyOptions,
|
|
23
|
+
onAgentRunStart: (...args) => {
|
|
24
|
+
admit();
|
|
25
|
+
return onAgentRunStart?.(...args);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
if ("dispatch" in inbound) {
|
|
29
|
+
if (typeof inbound.dispatch !== "function") {
|
|
30
|
+
throw new Error("weixin: host inbound.dispatch is not callable");
|
|
31
|
+
}
|
|
32
|
+
const delivery = { ...turn.delivery, observeMessageSent: true };
|
|
33
|
+
const routedReplyOptions = {
|
|
34
|
+
...replyOptions,
|
|
35
|
+
turnAdoptionLifecycle: {
|
|
36
|
+
onAdopted: admit,
|
|
37
|
+
onDeferred: defer,
|
|
38
|
+
onSettled: onDeferredComplete,
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
return await inbound.dispatch({
|
|
42
|
+
...turn,
|
|
43
|
+
route,
|
|
44
|
+
delivery,
|
|
45
|
+
replyOptions: routedReplyOptions,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (typeof inbound.dispatchReply !== "function" ||
|
|
49
|
+
typeof channelRuntime.session?.resolveStorePath !== "function" ||
|
|
50
|
+
typeof channelRuntime.session?.recordInboundSession !== "function" ||
|
|
51
|
+
typeof channelRuntime.reply?.dispatchReplyWithBufferedBlockDispatcher !== "function") {
|
|
52
|
+
throw new Error("weixin: host legacy inbound contract is incomplete");
|
|
53
|
+
}
|
|
54
|
+
const to = turn.ctxPayload.To;
|
|
55
|
+
if (typeof to !== "string") {
|
|
56
|
+
throw new Error("weixin: inbound reply target is missing");
|
|
57
|
+
}
|
|
58
|
+
const hookContext = {
|
|
59
|
+
to,
|
|
60
|
+
accountId: turn.accountId,
|
|
61
|
+
runId: turn.replyOptions?.runId,
|
|
62
|
+
sessionKey: route.sessionKey,
|
|
63
|
+
};
|
|
64
|
+
const { preparePayload, ...rawDelivery } = turn.delivery;
|
|
65
|
+
const delivery = {
|
|
66
|
+
...rawDelivery,
|
|
67
|
+
deliver: async (payload, info) => {
|
|
68
|
+
// Old helpers do not preserve SDK payload metadata across a preparePayload clone.
|
|
69
|
+
// Prepare only inside provider delivery so the host keeps its original payload.
|
|
70
|
+
const prepared = preparePayload ? await preparePayload(payload, info) : payload;
|
|
71
|
+
if (prepared === null)
|
|
72
|
+
return { visibleReplySent: false };
|
|
73
|
+
return sendWeixinWithHooks({
|
|
74
|
+
...hookContext,
|
|
75
|
+
text: prepared.text ?? "",
|
|
76
|
+
mediaUrl: prepared.mediaUrl ?? prepared.mediaUrls?.[0],
|
|
77
|
+
}, (text) => rawDelivery.deliver({ ...prepared, text }, info));
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
const legacyReplyOptions = {
|
|
81
|
+
...replyOptions,
|
|
82
|
+
queuedFollowupLifecycle: { onEnqueued: defer, onComplete: onDeferredComplete },
|
|
83
|
+
onTurnAdopted: admit,
|
|
84
|
+
};
|
|
85
|
+
return await inbound.dispatchReply({
|
|
86
|
+
...turn,
|
|
87
|
+
agentId: route.agentId,
|
|
88
|
+
routeSessionKey: route.sessionKey,
|
|
89
|
+
storePath: channelRuntime.session.resolveStorePath(turn.cfg.session?.store, { agentId: route.agentId }),
|
|
90
|
+
recordInboundSession: channelRuntime.session.recordInboundSession,
|
|
91
|
+
dispatchReplyWithBufferedBlockDispatcher: channelRuntime.reply.dispatchReplyWithBufferedBlockDispatcher,
|
|
92
|
+
dispatcherOptions: {
|
|
93
|
+
...turn.dispatcherOptions,
|
|
94
|
+
// Replace the legacy SDK's text-only modifier; our provider wrapper owns
|
|
95
|
+
// message_sending after preparation, including captionless media.
|
|
96
|
+
beforeDeliver: turn.dispatcherOptions?.beforeDeliver ?? ((payload) => payload),
|
|
97
|
+
},
|
|
98
|
+
delivery,
|
|
99
|
+
replyOptions: legacyReplyOptions,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=inbound-turn.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inbound-turn.js","sourceRoot":"","sources":["../../../src/messaging/inbound-turn.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,8 +3,34 @@ import { getGlobalHookRunner } from "openclaw/plugin-sdk/plugin-runtime";
|
|
|
3
3
|
import { logger } from "../util/logger.js";
|
|
4
4
|
import { redactError } from "../util/redact.js";
|
|
5
5
|
const CHANNEL_ID = "openclaw-weixin";
|
|
6
|
+
/** Local sends share cancellation and settlement; host-managed sends bypass this wrapper. */
|
|
7
|
+
export async function sendWeixinWithHooks(params, send) {
|
|
8
|
+
const sending = await applyWeixinMessageSendingHook(params);
|
|
9
|
+
if (sending.cancelled) {
|
|
10
|
+
logger.info("outbound: cancelled by message_sending hook");
|
|
11
|
+
return { visibleReplySent: false };
|
|
12
|
+
}
|
|
13
|
+
let result;
|
|
14
|
+
try {
|
|
15
|
+
result = await send(sending.text);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
emitWeixinMessageSent({ ...params, content: sending.text, success: false, error: redactError(error) });
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
if (result?.visibleReplySent !== false) {
|
|
22
|
+
emitWeixinMessageSent({
|
|
23
|
+
...params,
|
|
24
|
+
content: sending.text,
|
|
25
|
+
success: true,
|
|
26
|
+
messageId: result?.messageIds?.find((id) => id.trim()),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
6
31
|
/**
|
|
7
|
-
*
|
|
32
|
+
* Local hook boundary for legacy inbound replies and independent debug sends.
|
|
33
|
+
* Host-managed outbound adapters and routed inbound delivery must not call it.
|
|
8
34
|
* Returns the (possibly modified) text content plus a cancelled flag.
|
|
9
35
|
* Hook errors are caught and logged — sending proceeds regardless.
|
|
10
36
|
*/
|
|
@@ -23,7 +49,11 @@ export async function applyWeixinMessageSendingHook(params) {
|
|
|
23
49
|
runId: params.runId,
|
|
24
50
|
...(params.mediaUrl ? { mediaUrls: [params.mediaUrl] } : {}),
|
|
25
51
|
},
|
|
26
|
-
}, {
|
|
52
|
+
}, {
|
|
53
|
+
channelId: CHANNEL_ID,
|
|
54
|
+
accountId: params.accountId,
|
|
55
|
+
...(params.sessionKey ? { sessionKey: params.sessionKey } : {}),
|
|
56
|
+
});
|
|
27
57
|
if (hookResult?.cancel) {
|
|
28
58
|
return { cancelled: true, text: params.text };
|
|
29
59
|
}
|
|
@@ -53,6 +83,8 @@ export function emitWeixinMessageSent(params) {
|
|
|
53
83
|
accountId: params.accountId,
|
|
54
84
|
conversationId: params.to,
|
|
55
85
|
runId: params.runId,
|
|
86
|
+
sessionKey: params.sessionKey,
|
|
87
|
+
messageId: params.messageId,
|
|
56
88
|
});
|
|
57
89
|
fireAndForgetHook(Promise.resolve(hookRunner.runMessageSent(toPluginMessageSentEvent(canonical), toPluginMessageContext(canonical))), "weixin: message_sent plugin hook failed");
|
|
58
90
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outbound-hooks.js","sourceRoot":"","sources":["../../../src/messaging/outbound-hooks.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outbound-hooks.js","sourceRoot":"","sources":["../../../src/messaging/outbound-hooks.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,6 +9,7 @@ import { MessageItemType, TypingStatus } from "../api/types.js";
|
|
|
9
9
|
import { loadWeixinAccount } from "../auth/accounts.js";
|
|
10
10
|
import { readFrameworkAllowFromList } from "../auth/pairing.js";
|
|
11
11
|
import { downloadRemoteImageToTemp } from "../cdn/upload.js";
|
|
12
|
+
import { resolveWeixinBlockStreamingEnabled } from "../config/block-streaming.js";
|
|
12
13
|
import { resolveReplyProgressMessagesEnabled } from "../config/reply-progress.js";
|
|
13
14
|
import { downloadMediaFromItem } from "../media/media-download.js";
|
|
14
15
|
import { logger } from "../util/logger.js";
|
|
@@ -16,8 +17,9 @@ import { redactError, redactToken } from "../util/redact.js";
|
|
|
16
17
|
import { isDebugMode } from "./debug-mode.js";
|
|
17
18
|
import { sendWeixinErrorNotice } from "./error-notice.js";
|
|
18
19
|
import { getContextTokenFromMsgContext, isMediaItem, weixinMessageToMsgContext } from "./inbound.js";
|
|
20
|
+
import { dispatchWeixinInboundTurn } from "./inbound-turn.js";
|
|
19
21
|
import { StreamingMarkdownFilter } from "./markdown-filter.js";
|
|
20
|
-
import {
|
|
22
|
+
import { sendWeixinWithHooks } from "./outbound-hooks.js";
|
|
21
23
|
import { WeixinReplyProgressSender } from "./reply-progress-sender.js";
|
|
22
24
|
import { sendMessageWeixin } from "./send.js";
|
|
23
25
|
import { sendWeixinMediaFile } from "./send-media.js";
|
|
@@ -140,7 +142,6 @@ export async function processOneMessage(full, deps) {
|
|
|
140
142
|
const ctx = weixinMessageToMsgContext(full, deps.accountId, mediaOpts);
|
|
141
143
|
// --- Framework command authorization ---
|
|
142
144
|
const rawBody = textBody.trim() || (ctx.Body?.trim() ?? "");
|
|
143
|
-
ctx.CommandBody = rawBody;
|
|
144
145
|
const { senderAllowedForCommands, commandAuthorized } = await resolveSenderCommandAuthorizationWithRuntime({
|
|
145
146
|
cfg: deps.config,
|
|
146
147
|
rawBody,
|
|
@@ -169,108 +170,76 @@ export async function processOneMessage(full, deps) {
|
|
|
169
170
|
logger.info(`authorization: dropping message from=${redactToken(senderId)} outcome=${directDmOutcome}`);
|
|
170
171
|
return;
|
|
171
172
|
}
|
|
172
|
-
ctx.CommandAuthorized = commandAuthorized;
|
|
173
173
|
logger.debug(`authorization: senderId=${redactToken(senderId, 6)} commandAuthorized=${String(commandAuthorized)} senderAllowed=${String(senderAllowedForCommands)}`);
|
|
174
174
|
if (debug) {
|
|
175
175
|
debugTrace.push("── 鉴权 ──", `│ auth: cmdAuthorized=${String(commandAuthorized)} senderAllowed=${String(senderAllowedForCommands)}`);
|
|
176
176
|
debugTs.preDispatch = Date.now();
|
|
177
177
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// the correct session (matching the dmScope from config) instead of falling back
|
|
183
|
-
// to agent:main:main.
|
|
184
|
-
ctx.SessionKey = route.sessionKey;
|
|
185
|
-
const storePath = deps.channelRuntime.session.resolveStorePath(deps.config.session?.store, {
|
|
178
|
+
if (typeof deps.channelRuntime.inbound?.buildContext !== "function") {
|
|
179
|
+
throw new Error("weixin: host inbound.buildContext is missing");
|
|
180
|
+
}
|
|
181
|
+
const contextRoute = {
|
|
186
182
|
agentId: route.agentId,
|
|
183
|
+
routeSessionKey: route.sessionKey,
|
|
184
|
+
...("dmScope" in route ? { dmScope: route.dmScope } : {}),
|
|
185
|
+
};
|
|
186
|
+
const finalized = deps.channelRuntime.inbound.buildContext({
|
|
187
|
+
channel: "openclaw-weixin",
|
|
188
|
+
accountId: deps.accountId,
|
|
189
|
+
from: ctx.From,
|
|
190
|
+
sender: { id: senderId },
|
|
191
|
+
conversation: { kind: "direct", id: ctx.To },
|
|
192
|
+
route: contextRoute,
|
|
193
|
+
reply: { to: ctx.To },
|
|
194
|
+
message: { body: ctx.Body, rawBody: ctx.Body, bodyForAgent: ctx.Body, commandBody: rawBody },
|
|
195
|
+
messageId: ctx.MessageSid,
|
|
196
|
+
timestamp: ctx.Timestamp,
|
|
197
|
+
access: { commands: { authorized: commandAuthorized === true } },
|
|
198
|
+
media: ctx.MediaPath || ctx.MediaUrl
|
|
199
|
+
? [{ path: ctx.MediaPath, url: ctx.MediaUrl, contentType: ctx.MediaType }]
|
|
200
|
+
: undefined,
|
|
187
201
|
});
|
|
188
|
-
|
|
189
|
-
...ctx,
|
|
190
|
-
BodyForAgent: ctx.Body,
|
|
191
|
-
});
|
|
192
|
-
logger.info(`inbound: from=${redactToken(finalized.From)} to=${redactToken(finalized.To)} bodyLen=${(finalized.Body ?? "").length} hasMedia=${Boolean(finalized.MediaPath ?? finalized.MediaUrl)}`);
|
|
193
|
-
await deps.channelRuntime.session.recordInboundSession({
|
|
194
|
-
storePath,
|
|
195
|
-
sessionKey: route.sessionKey,
|
|
196
|
-
ctx: finalized,
|
|
197
|
-
updateLastRoute: {
|
|
198
|
-
sessionKey: route.mainSessionKey,
|
|
199
|
-
channel: "openclaw-weixin",
|
|
200
|
-
to: ctx.To,
|
|
201
|
-
accountId: deps.accountId,
|
|
202
|
-
},
|
|
203
|
-
onRecordError: (err) => deps.errLog(`recordInboundSession: ${redactError(err)}`),
|
|
204
|
-
});
|
|
205
|
-
logger.debug(`recordInboundSession: done sessionKey=${redactToken(route.sessionKey, 6)}`);
|
|
202
|
+
logger.info(`inbound: from=${redactToken(finalized.From)} to=${redactToken(finalized.To)} bodyLen=${(finalized.Body ?? "").length} hasMedia=${Boolean(ctx.MediaPath ?? ctx.MediaUrl)}`);
|
|
206
203
|
const contextToken = getContextTokenFromMsgContext(ctx);
|
|
207
204
|
const runId = randomUUID();
|
|
208
|
-
const
|
|
209
|
-
? new WeixinReplyProgressSender({
|
|
210
|
-
runId,
|
|
211
|
-
to: ctx.To,
|
|
212
|
-
accountId: deps.accountId,
|
|
213
|
-
opts: {
|
|
214
|
-
baseUrl: deps.baseUrl,
|
|
215
|
-
token: deps.token,
|
|
216
|
-
contextToken,
|
|
217
|
-
},
|
|
218
|
-
})
|
|
219
|
-
: undefined;
|
|
205
|
+
const sendOptions = { baseUrl: deps.baseUrl, token: deps.token, contextToken, runId };
|
|
220
206
|
const humanDelay = deps.channelRuntime.reply.resolveHumanDelayConfig(deps.config, route.agentId);
|
|
221
207
|
const typingTicket = deps.typingTicket;
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
208
|
+
const updateTyping = async (status) => {
|
|
209
|
+
if (typingTicket) {
|
|
210
|
+
await sendTyping({
|
|
225
211
|
baseUrl: deps.baseUrl,
|
|
226
212
|
token: deps.token,
|
|
227
|
-
body: {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
stop: typingTicket
|
|
235
|
-
? () => sendTyping({
|
|
236
|
-
baseUrl: deps.baseUrl,
|
|
237
|
-
token: deps.token,
|
|
238
|
-
body: {
|
|
239
|
-
ilink_user_id: ctx.To,
|
|
240
|
-
typing_ticket: typingTicket,
|
|
241
|
-
status: TypingStatus.CANCEL,
|
|
242
|
-
},
|
|
243
|
-
})
|
|
244
|
-
: async () => { },
|
|
213
|
+
body: { ilink_user_id: ctx.To, typing_ticket: typingTicket, status },
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const typingCallbacks = createTypingCallbacks({
|
|
218
|
+
start: () => updateTyping(TypingStatus.TYPING),
|
|
219
|
+
stop: () => updateTyping(TypingStatus.CANCEL),
|
|
245
220
|
onStartError: (err) => deps.log(`[weixin] typing send error: ${redactError(err)}`),
|
|
246
221
|
onStopError: (err) => deps.log(`[weixin] typing cancel error: ${redactError(err)}`),
|
|
247
222
|
keepaliveIntervalMs: 5000,
|
|
248
223
|
});
|
|
224
|
+
const replyProgressSender = resolveReplyProgressMessagesEnabled(deps.config)
|
|
225
|
+
? new WeixinReplyProgressSender({
|
|
226
|
+
runId,
|
|
227
|
+
to: ctx.To,
|
|
228
|
+
accountId: deps.accountId,
|
|
229
|
+
opts: sendOptions,
|
|
230
|
+
})
|
|
231
|
+
: undefined;
|
|
249
232
|
/** Delivery records populated synchronously at deliver() entry, safe to read in finally. */
|
|
250
233
|
const debugDeliveries = [];
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
|
|
234
|
+
const delivery = {
|
|
235
|
+
preparePayload: (payload) => {
|
|
236
|
+
const filter = new StreamingMarkdownFilter();
|
|
237
|
+
return { ...payload, text: filter.feed(payload.text ?? "") + filter.flush() };
|
|
238
|
+
},
|
|
254
239
|
deliver: async (payload) => {
|
|
255
|
-
const
|
|
256
|
-
let text = (() => {
|
|
257
|
-
const f = new StreamingMarkdownFilter();
|
|
258
|
-
return f.feed(rawText) + f.flush();
|
|
259
|
-
})();
|
|
240
|
+
const text = payload.text ?? "";
|
|
260
241
|
const mediaUrl = payload.mediaUrl ?? payload.mediaUrls?.[0];
|
|
261
242
|
logger.info(`outbound: to=${redactToken(ctx.To)} contextToken=${redactToken(contextToken)} textLen=${text.length} mediaUrl=${mediaUrl ? "present" : "none"}`);
|
|
262
|
-
const sendingResult = await applyWeixinMessageSendingHook({
|
|
263
|
-
to: ctx.To,
|
|
264
|
-
text,
|
|
265
|
-
accountId: deps.accountId,
|
|
266
|
-
mediaUrl,
|
|
267
|
-
runId,
|
|
268
|
-
});
|
|
269
|
-
if (sendingResult.cancelled) {
|
|
270
|
-
logger.info(`outbound: cancelled by message_sending hook to=${redactToken(ctx.To)}`);
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
text = sendingResult.text;
|
|
274
243
|
if (debug) {
|
|
275
244
|
debugDeliveries.push({
|
|
276
245
|
textLen: text.length,
|
|
@@ -279,76 +248,39 @@ export async function processOneMessage(full, deps) {
|
|
|
279
248
|
});
|
|
280
249
|
}
|
|
281
250
|
try {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
288
|
-
else if (!path.isAbsolute(mediaUrl)) {
|
|
289
|
-
filePath = path.resolve(mediaUrl);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
filePath = mediaUrl;
|
|
293
|
-
}
|
|
294
|
-
logger.debug("outbound: local media path resolved");
|
|
295
|
-
}
|
|
296
|
-
else if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
|
|
251
|
+
const remote = mediaUrl?.startsWith("http://") || mediaUrl?.startsWith("https://");
|
|
252
|
+
let result;
|
|
253
|
+
if (mediaUrl && (!mediaUrl.includes("://") || mediaUrl.startsWith("file://") || remote)) {
|
|
254
|
+
let filePath = mediaUrl;
|
|
255
|
+
if (remote) {
|
|
297
256
|
logger.debug("outbound: downloading remote media");
|
|
298
257
|
filePath = await downloadRemoteImageToTemp(mediaUrl, MEDIA_OUTBOUND_TEMP_DIR);
|
|
299
258
|
logger.debug("outbound: remote media downloaded");
|
|
300
259
|
}
|
|
301
|
-
else {
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
opts: {
|
|
307
|
-
baseUrl: deps.baseUrl,
|
|
308
|
-
token: deps.token,
|
|
309
|
-
contextToken,
|
|
310
|
-
runId,
|
|
311
|
-
},
|
|
312
|
-
});
|
|
313
|
-
emitWeixinMessageSent({ to: ctx.To, content: text, success: true, accountId: deps.accountId, runId });
|
|
314
|
-
logger.info(`outbound: text sent to=${redactToken(ctx.To)}`);
|
|
315
|
-
return;
|
|
260
|
+
else if (mediaUrl.startsWith("file://")) {
|
|
261
|
+
filePath = new URL(mediaUrl).pathname;
|
|
262
|
+
}
|
|
263
|
+
else if (!path.isAbsolute(mediaUrl)) {
|
|
264
|
+
filePath = path.resolve(mediaUrl);
|
|
316
265
|
}
|
|
317
|
-
await sendWeixinMediaFile({
|
|
266
|
+
result = await sendWeixinMediaFile({
|
|
318
267
|
filePath,
|
|
319
268
|
to: ctx.To,
|
|
320
269
|
text,
|
|
321
|
-
opts:
|
|
270
|
+
opts: sendOptions,
|
|
322
271
|
cdnBaseUrl: deps.cdnBaseUrl,
|
|
323
272
|
});
|
|
324
|
-
emitWeixinMessageSent({ to: ctx.To, content: text, success: true, accountId: deps.accountId, runId });
|
|
325
273
|
logger.info(`outbound: media sent OK to=${redactToken(ctx.To)}`);
|
|
326
274
|
}
|
|
327
275
|
else {
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
text,
|
|
332
|
-
opts: {
|
|
333
|
-
baseUrl: deps.baseUrl,
|
|
334
|
-
token: deps.token,
|
|
335
|
-
contextToken,
|
|
336
|
-
runId,
|
|
337
|
-
},
|
|
338
|
-
});
|
|
339
|
-
emitWeixinMessageSent({ to: ctx.To, content: text, success: true, accountId: deps.accountId, runId });
|
|
276
|
+
if (mediaUrl)
|
|
277
|
+
logger.warn("outbound: unrecognized media URL scheme, sending text only");
|
|
278
|
+
result = await sendMessageWeixin({ to: ctx.To, text, opts: sendOptions });
|
|
340
279
|
logger.info(`outbound: text sent OK to=${redactToken(ctx.To)}`);
|
|
341
280
|
}
|
|
281
|
+
return { messageIds: [result.messageId], content: text };
|
|
342
282
|
}
|
|
343
283
|
catch (err) {
|
|
344
|
-
emitWeixinMessageSent({
|
|
345
|
-
to: ctx.To,
|
|
346
|
-
content: text,
|
|
347
|
-
success: false,
|
|
348
|
-
error: redactError(err),
|
|
349
|
-
accountId: deps.accountId,
|
|
350
|
-
runId,
|
|
351
|
-
});
|
|
352
284
|
logger.error(`outbound: FAILED to=${redactToken(ctx.To)} hasMedia=${Boolean(mediaUrl)} err=${redactError(err)}`);
|
|
353
285
|
throw err;
|
|
354
286
|
}
|
|
@@ -376,42 +308,46 @@ export async function processOneMessage(full, deps) {
|
|
|
376
308
|
errLog: deps.errLog,
|
|
377
309
|
});
|
|
378
310
|
},
|
|
379
|
-
}
|
|
311
|
+
};
|
|
380
312
|
let queuedFollowup = false;
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
313
|
+
try {
|
|
314
|
+
logger.debug(`inbound.dispatch: starting agentId=${redactToken(route.agentId, 6)}`);
|
|
315
|
+
await dispatchWeixinInboundTurn({
|
|
316
|
+
channelRuntime: deps.channelRuntime,
|
|
317
|
+
channel: "openclaw-weixin",
|
|
318
|
+
accountId: deps.accountId,
|
|
319
|
+
cfg: deps.config,
|
|
320
|
+
route,
|
|
321
|
+
ctxPayload: finalized,
|
|
322
|
+
record: {
|
|
323
|
+
updateLastRoute: {
|
|
324
|
+
sessionKey: route.mainSessionKey,
|
|
325
|
+
channel: "openclaw-weixin",
|
|
326
|
+
to: ctx.To,
|
|
327
|
+
accountId: deps.accountId,
|
|
328
|
+
},
|
|
329
|
+
onRecordError: (err) => deps.errLog(`recordInboundSession: ${redactError(err)}`),
|
|
330
|
+
},
|
|
331
|
+
delivery,
|
|
332
|
+
dispatcherOptions: { humanDelay, typingCallbacks },
|
|
333
|
+
replyOptions: {
|
|
334
|
+
...(replyProgressSender?.replyOptions ?? {}),
|
|
335
|
+
runId,
|
|
336
|
+
disableBlockStreaming: !resolveWeixinBlockStreamingEnabled(deps.config, routeAccountId, deps.accountId),
|
|
337
|
+
},
|
|
338
|
+
onReplyAdmitted: deps.onReplyAdmitted,
|
|
339
|
+
onReplyDeferred: () => {
|
|
387
340
|
queuedFollowup = true;
|
|
388
|
-
deps.onReplyAdmitted?.();
|
|
389
341
|
},
|
|
390
|
-
|
|
391
|
-
},
|
|
392
|
-
onAgentRunStart: () => deps.onReplyAdmitted?.(),
|
|
393
|
-
onTurnAdopted: deps.onReplyAdmitted,
|
|
394
|
-
disableBlockStreaming: true,
|
|
395
|
-
};
|
|
396
|
-
logger.debug(`dispatchReplyFromConfig: starting agentId=${redactToken(route.agentId, 6)}`);
|
|
397
|
-
try {
|
|
398
|
-
await deps.channelRuntime.reply.withReplyDispatcher({
|
|
399
|
-
dispatcher,
|
|
400
|
-
run: () => deps.channelRuntime.reply.dispatchReplyFromConfig({
|
|
401
|
-
ctx: finalized,
|
|
402
|
-
cfg: deps.config,
|
|
403
|
-
dispatcher,
|
|
404
|
-
replyOptions: dispatchReplyOptions,
|
|
405
|
-
}),
|
|
342
|
+
onDeferredComplete: () => void replyProgressSender?.finalize(),
|
|
406
343
|
});
|
|
407
|
-
logger.debug(`
|
|
344
|
+
logger.debug(`inbound.dispatch: done agentId=${redactToken(route.agentId, 6)}`);
|
|
408
345
|
}
|
|
409
346
|
catch (err) {
|
|
410
|
-
logger.error(`
|
|
347
|
+
logger.error(`inbound.dispatch: error agentId=${redactToken(route.agentId)} err=${redactError(err)}`);
|
|
411
348
|
throw err;
|
|
412
349
|
}
|
|
413
350
|
finally {
|
|
414
|
-
markDispatchIdle();
|
|
415
351
|
if (!queuedFollowup)
|
|
416
352
|
await replyProgressSender?.finalize();
|
|
417
353
|
logger.info(`debug-check: account=${redactToken(deps.accountId)} debug=${String(debug)} hasContextToken=${Boolean(contextToken)}`);
|
|
@@ -436,45 +372,14 @@ export async function processOneMessage(full, deps) {
|
|
|
436
372
|
debugTrace.push("── 耗时 ──", `├ 平台→插件: ${platformDelay}`, `├ 入站处理(auth+route+media): ${inboundProcessMs}ms (mediaDownload: ${mediaDownloadMs}ms)`, `├ AI生成+回复: ${aiMs}ms`, `├ 总耗时: ${totalTime}`, `└ eventTime: ${eventTs > 0 ? new Date(eventTs).toISOString() : "N/A"}`);
|
|
437
373
|
const timingText = `⏱ Debug 全链路\n${debugTrace.join("\n")}`;
|
|
438
374
|
logger.info(`debug-timing: sending to=${redactToken(ctx.To)}`);
|
|
439
|
-
let debugSentContent;
|
|
440
375
|
try {
|
|
441
|
-
const
|
|
442
|
-
to: ctx.To,
|
|
443
|
-
text: timingText,
|
|
444
|
-
accountId: deps.accountId,
|
|
445
|
-
runId,
|
|
376
|
+
const result = await sendWeixinWithHooks({ to: ctx.To, text: timingText, accountId: deps.accountId, runId }, async (text) => {
|
|
377
|
+
await sendMessageWeixin({ to: ctx.To, text, opts: sendOptions });
|
|
446
378
|
});
|
|
447
|
-
if (
|
|
448
|
-
logger.info(`debug-timing: cancelled by message_sending hook to=${redactToken(ctx.To)}`);
|
|
449
|
-
}
|
|
450
|
-
else {
|
|
451
|
-
debugSentContent = debugSendingResult.text;
|
|
452
|
-
await sendMessageWeixin({
|
|
453
|
-
to: ctx.To,
|
|
454
|
-
text: debugSentContent,
|
|
455
|
-
opts: { baseUrl: deps.baseUrl, token: deps.token, contextToken, runId },
|
|
456
|
-
});
|
|
457
|
-
emitWeixinMessageSent({
|
|
458
|
-
to: ctx.To,
|
|
459
|
-
content: debugSentContent,
|
|
460
|
-
success: true,
|
|
461
|
-
accountId: deps.accountId,
|
|
462
|
-
runId,
|
|
463
|
-
});
|
|
379
|
+
if (result?.visibleReplySent !== false)
|
|
464
380
|
logger.info(`debug-timing: sent OK`);
|
|
465
|
-
}
|
|
466
381
|
}
|
|
467
382
|
catch (debugErr) {
|
|
468
|
-
if (debugSentContent !== undefined) {
|
|
469
|
-
emitWeixinMessageSent({
|
|
470
|
-
to: ctx.To,
|
|
471
|
-
content: debugSentContent,
|
|
472
|
-
success: false,
|
|
473
|
-
error: redactError(debugErr),
|
|
474
|
-
accountId: deps.accountId,
|
|
475
|
-
runId,
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
383
|
logger.error(`debug-timing: send FAILED err=${redactError(debugErr)}`);
|
|
479
384
|
}
|
|
480
385
|
}
|