@tencent-weixin/openclaw-weixin 2.1.5 → 2.1.7
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 +11 -0
- package/CHANGELOG.zh_CN.md +11 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/channel.ts +9 -3
- package/src/messaging/markdown-filter.ts +92 -62
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
This project follows the [Keep a Changelog](https://keepachangelog.com/) format.
|
|
6
6
|
|
|
7
|
+
## [2.1.7] - 2026-04-07
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Plugin registration re-entrance:** Lazy-import `monitorWeixinProvider` inside `startAccount` in `channel.ts` to avoid pulling in the monitor → process-message → command-auth chain at plugin registration time, which could re-enter the plugin/provider registry before the account starts.
|
|
12
|
+
- **Initialization side effect:** Lazy-import `resolveSenderCommandAuthorizationWithRuntime` / `resolveDirectDmAuthorizationOutcome` in `process-message.ts` to prevent `ensureContextWindowCacheLoaded` from being triggered during module initialization, which caused `loadOpenClawPlugins` re-entrance.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- **Tool-call outbound path:** `sendWeixinOutbound` now applies `StreamingMarkdownFilter` to the outbound text, consistent with the model-output path in `process-message`.
|
|
17
|
+
|
|
7
18
|
## [2.1.4] - 2026-04-03
|
|
8
19
|
|
|
9
20
|
### Changed
|
package/CHANGELOG.zh_CN.md
CHANGED
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
本项目遵循 [Keep a Changelog](https://keepachangelog.com/) 格式。
|
|
6
6
|
|
|
7
|
+
## [2.1.7] - 2026-04-07
|
|
8
|
+
|
|
9
|
+
### 修复
|
|
10
|
+
|
|
11
|
+
- **插件注册重入:** `channel.ts` 中将 `monitorWeixinProvider` 改为在 `startAccount` 内部懒加载(`await import(...)`),避免插件注册阶段提前拉取 monitor → process-message → command-auth 依赖链,导致 plugin/provider registry 重入。
|
|
12
|
+
- **初始化副作用:** `process-message.ts` 中将 `resolveSenderCommandAuthorizationWithRuntime` / `resolveDirectDmAuthorizationOutcome` 改为懒加载,避免模块初始化时触发宿主的 `ensureContextWindowCacheLoaded` 副作用,进而导致 `loadOpenClawPlugins` 重入。
|
|
13
|
+
|
|
14
|
+
### 变更
|
|
15
|
+
|
|
16
|
+
- **tool-call 外发路径:** `sendWeixinOutbound` 现在对发送文本应用 `StreamingMarkdownFilter`,与 `process-message` 中的 model-output 路径保持一致。
|
|
17
|
+
|
|
7
18
|
## [2.1.4] - 2026-04-03
|
|
8
19
|
|
|
9
20
|
### 变更
|
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/channel.ts
CHANGED
|
@@ -24,9 +24,11 @@ import {
|
|
|
24
24
|
waitForWeixinLogin,
|
|
25
25
|
} from "./auth/login-qr.js";
|
|
26
26
|
import type { WeixinQrStartResult, WeixinQrWaitResult } from "./auth/login-qr.js";
|
|
27
|
-
|
|
27
|
+
// Lazy-imported inside startAccount to avoid pulling in the monitor -> process-message ->
|
|
28
|
+
// command-auth chain during plugin registration, which can re-enter plugin/provider registry
|
|
29
|
+
// resolution before the account actually starts.
|
|
28
30
|
import { sendWeixinMediaFile } from "./messaging/send-media.js";
|
|
29
|
-
import { sendMessageWeixin } from "./messaging/send.js";
|
|
31
|
+
import { sendMessageWeixin, StreamingMarkdownFilter } from "./messaging/send.js";
|
|
30
32
|
import { downloadRemoteImageToTemp } from "./cdn/upload.js";
|
|
31
33
|
|
|
32
34
|
/** Returns true when mediaUrl refers to a local filesystem path (absolute or relative). */
|
|
@@ -119,7 +121,10 @@ async function sendWeixinOutbound(params: {
|
|
|
119
121
|
if (!params.contextToken) {
|
|
120
122
|
aLog.warn(`sendWeixinOutbound: contextToken missing for to=${params.to}, sending without context`);
|
|
121
123
|
}
|
|
122
|
-
const
|
|
124
|
+
const f = new StreamingMarkdownFilter();
|
|
125
|
+
const rawText = params.text ?? "";
|
|
126
|
+
const filteredText = f.feed(rawText) + f.flush();
|
|
127
|
+
const result = await sendMessageWeixin({ to: params.to, text: filteredText, opts: {
|
|
123
128
|
baseUrl: account.baseUrl,
|
|
124
129
|
token: account.token,
|
|
125
130
|
contextToken: params.contextToken,
|
|
@@ -383,6 +388,7 @@ export const weixinPlugin: ChannelPlugin<ResolvedWeixinAccount> = {
|
|
|
383
388
|
const logPath = aLog.getLogFilePath();
|
|
384
389
|
ctx.log?.info?.(`[${account.accountId}] weixin logs: ${logPath}`);
|
|
385
390
|
|
|
391
|
+
const { monitorWeixinProvider } = await import("./monitor/monitor.js");
|
|
386
392
|
return monitorWeixinProvider({
|
|
387
393
|
baseUrl: account.baseUrl,
|
|
388
394
|
cdnBaseUrl: account.cdnBaseUrl,
|
|
@@ -6,22 +6,9 @@
|
|
|
6
6
|
* holding back the minimum characters needed for pattern disambiguation
|
|
7
7
|
* (e.g. a trailing `*` that might become `***`).
|
|
8
8
|
*
|
|
9
|
-
* Constructs passed through (not filtered):
|
|
10
|
-
* - Code fences (```)
|
|
11
|
-
* - Inline code (`)
|
|
12
|
-
* - Tables (|...|)
|
|
13
|
-
* - Horizontal rules (---, ***, ___)
|
|
14
|
-
* - Bold (**)
|
|
15
|
-
* - Italic/bold-italic wrapping non-CJK content
|
|
16
|
-
*
|
|
17
|
-
* Constructs filtered (markers stripped, content kept):
|
|
18
|
-
* - Italic/bold-italic wrapping CJK content
|
|
19
|
-
* - Headings H5/H6 (#####, ######)
|
|
20
|
-
* - Images () — removed entirely
|
|
21
|
-
*
|
|
22
9
|
* States:
|
|
23
10
|
* - **sol** (start-of-line): checks for line-start patterns (```, >, #####, indent)
|
|
24
|
-
* - **body**: scans for inline patterns (![, ~~, ***) and outputs safe chars
|
|
11
|
+
* - **body**: scans for inline patterns (`, ![, ~~, ***) and outputs safe chars
|
|
25
12
|
* - **fence**: inside a fenced code block, passes through until closing ```
|
|
26
13
|
* - **inline**: accumulating content inside an inline marker pair
|
|
27
14
|
*/
|
|
@@ -29,7 +16,7 @@ export class StreamingMarkdownFilter {
|
|
|
29
16
|
private buf = "";
|
|
30
17
|
private fence = false;
|
|
31
18
|
private sol = true;
|
|
32
|
-
private inl: { type: "image" | "bold3" | "italic" | "ubold3" | "uitalic"; acc: string } | null = null;
|
|
19
|
+
private inl: { type: "code" | "image" | "strike" | "bold3" | "italic" | "ubold3" | "uitalic" | "table"; acc: string } | null = null;
|
|
33
20
|
|
|
34
21
|
feed(delta: string): string {
|
|
35
22
|
this.buf += delta;
|
|
@@ -58,32 +45,26 @@ export class StreamingMarkdownFilter {
|
|
|
58
45
|
}
|
|
59
46
|
|
|
60
47
|
if (eof && this.inl) {
|
|
61
|
-
|
|
62
|
-
|
|
48
|
+
if (this.inl.type === "table") {
|
|
49
|
+
out += StreamingMarkdownFilter.extractTableRow(this.inl.acc);
|
|
50
|
+
} else {
|
|
51
|
+
const markers: Record<string, string> = { code: "`", image: "![", strike: "~~", bold3: "***", italic: "*", ubold3: "___", uitalic: "_" };
|
|
52
|
+
out += (markers[this.inl.type] ?? "") + this.inl.acc;
|
|
53
|
+
}
|
|
63
54
|
this.inl = null;
|
|
64
55
|
}
|
|
65
56
|
return out;
|
|
66
57
|
}
|
|
67
58
|
|
|
68
|
-
/** Inside a code fence: pass content
|
|
59
|
+
/** Inside a code fence: pass content through, watch for closing ``` at SOL. */
|
|
69
60
|
private pumpFence(eof: boolean): string {
|
|
70
61
|
if (this.sol) {
|
|
71
62
|
if (this.buf.length < 3 && !eof) return "";
|
|
72
63
|
if (this.buf.startsWith("```")) {
|
|
64
|
+
this.fence = false;
|
|
73
65
|
const nl = this.buf.indexOf("\n", 3);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const line = this.buf.slice(0, nl + 1);
|
|
77
|
-
this.buf = this.buf.slice(nl + 1);
|
|
78
|
-
this.sol = true;
|
|
79
|
-
return line;
|
|
80
|
-
}
|
|
81
|
-
if (eof) {
|
|
82
|
-
this.fence = false;
|
|
83
|
-
const line = this.buf;
|
|
84
|
-
this.buf = "";
|
|
85
|
-
return line;
|
|
86
|
-
}
|
|
66
|
+
this.buf = nl !== -1 ? this.buf.slice(nl + 1) : "";
|
|
67
|
+
this.sol = true;
|
|
87
68
|
return "";
|
|
88
69
|
}
|
|
89
70
|
this.sol = false;
|
|
@@ -112,18 +93,10 @@ export class StreamingMarkdownFilter {
|
|
|
112
93
|
if (b[0] === "`") {
|
|
113
94
|
if (b.length < 3 && !eof) return "";
|
|
114
95
|
if (b.startsWith("```")) {
|
|
96
|
+
this.fence = true;
|
|
115
97
|
const nl = b.indexOf("\n", 3);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const line = b.slice(0, nl + 1);
|
|
119
|
-
this.buf = b.slice(nl + 1);
|
|
120
|
-
this.sol = true;
|
|
121
|
-
return line;
|
|
122
|
-
}
|
|
123
|
-
if (eof) {
|
|
124
|
-
this.buf = "";
|
|
125
|
-
return b;
|
|
126
|
-
}
|
|
98
|
+
this.buf = nl !== -1 ? b.slice(nl + 1) : "";
|
|
99
|
+
this.sol = true;
|
|
127
100
|
return "";
|
|
128
101
|
}
|
|
129
102
|
this.sol = false;
|
|
@@ -131,6 +104,8 @@ export class StreamingMarkdownFilter {
|
|
|
131
104
|
}
|
|
132
105
|
|
|
133
106
|
if (b[0] === ">") {
|
|
107
|
+
if (b.length < 2 && !eof) return "";
|
|
108
|
+
this.buf = b.length >= 2 && b[1] === " " ? b.slice(2) : b.slice(1);
|
|
134
109
|
this.sol = false;
|
|
135
110
|
return "";
|
|
136
111
|
}
|
|
@@ -148,6 +123,13 @@ export class StreamingMarkdownFilter {
|
|
|
148
123
|
return "";
|
|
149
124
|
}
|
|
150
125
|
|
|
126
|
+
if (b[0] === "|") {
|
|
127
|
+
this.buf = b.slice(1);
|
|
128
|
+
this.inl = { type: "table", acc: "" };
|
|
129
|
+
this.sol = false;
|
|
130
|
+
return "";
|
|
131
|
+
}
|
|
132
|
+
|
|
151
133
|
if (b[0] === " " || b[0] === "\t") {
|
|
152
134
|
if (b.search(/[^ \t]/) === -1 && !eof) return "";
|
|
153
135
|
this.sol = false;
|
|
@@ -163,13 +145,9 @@ export class StreamingMarkdownFilter {
|
|
|
163
145
|
let count = 0;
|
|
164
146
|
for (let k = 0; k < j; k++) if (b[k] === ch) count++;
|
|
165
147
|
if (count >= 3) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
return b.slice(0, j + 1);
|
|
170
|
-
}
|
|
171
|
-
this.buf = "";
|
|
172
|
-
return b;
|
|
148
|
+
this.buf = j < b.length ? b.slice(j + 1) : "";
|
|
149
|
+
this.sol = true;
|
|
150
|
+
return "";
|
|
173
151
|
}
|
|
174
152
|
}
|
|
175
153
|
this.sol = false;
|
|
@@ -192,15 +170,23 @@ export class StreamingMarkdownFilter {
|
|
|
192
170
|
this.sol = true;
|
|
193
171
|
return out;
|
|
194
172
|
}
|
|
173
|
+
if (c === "`") {
|
|
174
|
+
out += this.buf.slice(0, i);
|
|
175
|
+
this.buf = this.buf.slice(i + 1);
|
|
176
|
+
this.inl = { type: "code", acc: "" };
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
195
179
|
if (c === "!" && i + 1 < this.buf.length && this.buf[i + 1] === "[") {
|
|
196
180
|
out += this.buf.slice(0, i);
|
|
197
181
|
this.buf = this.buf.slice(i + 2);
|
|
198
182
|
this.inl = { type: "image", acc: "" };
|
|
199
183
|
return out;
|
|
200
184
|
}
|
|
201
|
-
if (c === "~") {
|
|
202
|
-
i
|
|
203
|
-
|
|
185
|
+
if (c === "~" && i + 1 < this.buf.length && this.buf[i + 1] === "~") {
|
|
186
|
+
out += this.buf.slice(0, i);
|
|
187
|
+
this.buf = this.buf.slice(i + 2);
|
|
188
|
+
this.inl = { type: "strike", acc: "" };
|
|
189
|
+
return out;
|
|
204
190
|
}
|
|
205
191
|
if (c === "*") {
|
|
206
192
|
if (i + 2 < this.buf.length && this.buf[i + 1] === "*" && this.buf[i + 2] === "*") {
|
|
@@ -251,6 +237,7 @@ export class StreamingMarkdownFilter {
|
|
|
251
237
|
else if (this.buf.endsWith("__")) hold = 2;
|
|
252
238
|
else if (this.buf.endsWith("*")) hold = 1;
|
|
253
239
|
else if (this.buf.endsWith("_")) hold = 1;
|
|
240
|
+
else if (this.buf.endsWith("~")) hold = 1;
|
|
254
241
|
else if (this.buf.endsWith("!")) hold = 1;
|
|
255
242
|
}
|
|
256
243
|
out += this.buf.slice(0, this.buf.length - hold);
|
|
@@ -265,14 +252,41 @@ export class StreamingMarkdownFilter {
|
|
|
265
252
|
this.buf = "";
|
|
266
253
|
|
|
267
254
|
switch (this.inl.type) {
|
|
255
|
+
case "code": {
|
|
256
|
+
const idx = this.inl.acc.indexOf("`");
|
|
257
|
+
if (idx !== -1) {
|
|
258
|
+
const content = this.inl.acc.slice(0, idx);
|
|
259
|
+
this.buf = this.inl.acc.slice(idx + 1);
|
|
260
|
+
this.inl = null;
|
|
261
|
+
return content;
|
|
262
|
+
}
|
|
263
|
+
const nl = this.inl.acc.indexOf("\n");
|
|
264
|
+
if (nl !== -1) {
|
|
265
|
+
const r = "`" + this.inl.acc.slice(0, nl + 1);
|
|
266
|
+
this.buf = this.inl.acc.slice(nl + 1);
|
|
267
|
+
this.inl = null;
|
|
268
|
+
this.sol = true;
|
|
269
|
+
return r;
|
|
270
|
+
}
|
|
271
|
+
return "";
|
|
272
|
+
}
|
|
273
|
+
case "strike": {
|
|
274
|
+
const idx = this.inl.acc.indexOf("~~");
|
|
275
|
+
if (idx !== -1) {
|
|
276
|
+
const content = this.inl.acc.slice(0, idx);
|
|
277
|
+
this.buf = this.inl.acc.slice(idx + 2);
|
|
278
|
+
this.inl = null;
|
|
279
|
+
return content;
|
|
280
|
+
}
|
|
281
|
+
return "";
|
|
282
|
+
}
|
|
268
283
|
case "bold3": {
|
|
269
284
|
const idx = this.inl.acc.indexOf("***");
|
|
270
285
|
if (idx !== -1) {
|
|
271
286
|
const content = this.inl.acc.slice(0, idx);
|
|
272
287
|
this.buf = this.inl.acc.slice(idx + 3);
|
|
273
288
|
this.inl = null;
|
|
274
|
-
|
|
275
|
-
return `***${content}***`;
|
|
289
|
+
return content;
|
|
276
290
|
}
|
|
277
291
|
return "";
|
|
278
292
|
}
|
|
@@ -282,8 +296,7 @@ export class StreamingMarkdownFilter {
|
|
|
282
296
|
const content = this.inl.acc.slice(0, idx);
|
|
283
297
|
this.buf = this.inl.acc.slice(idx + 3);
|
|
284
298
|
this.inl = null;
|
|
285
|
-
|
|
286
|
-
return `___${content}___`;
|
|
299
|
+
return content;
|
|
287
300
|
}
|
|
288
301
|
return "";
|
|
289
302
|
}
|
|
@@ -304,8 +317,7 @@ export class StreamingMarkdownFilter {
|
|
|
304
317
|
const content = this.inl.acc.slice(0, j);
|
|
305
318
|
this.buf = this.inl.acc.slice(j + 1);
|
|
306
319
|
this.inl = null;
|
|
307
|
-
|
|
308
|
-
return `*${content}*`;
|
|
320
|
+
return content;
|
|
309
321
|
}
|
|
310
322
|
}
|
|
311
323
|
return "";
|
|
@@ -327,8 +339,7 @@ export class StreamingMarkdownFilter {
|
|
|
327
339
|
const content = this.inl.acc.slice(0, j);
|
|
328
340
|
this.buf = this.inl.acc.slice(j + 1);
|
|
329
341
|
this.inl = null;
|
|
330
|
-
|
|
331
|
-
return `_${content}_`;
|
|
342
|
+
return content;
|
|
332
343
|
}
|
|
333
344
|
}
|
|
334
345
|
return "";
|
|
@@ -351,11 +362,30 @@ export class StreamingMarkdownFilter {
|
|
|
351
362
|
}
|
|
352
363
|
return "";
|
|
353
364
|
}
|
|
365
|
+
case "table": {
|
|
366
|
+
const nl = this.inl.acc.indexOf("\n");
|
|
367
|
+
if (nl !== -1) {
|
|
368
|
+
const line = this.inl.acc.slice(0, nl);
|
|
369
|
+
this.buf = this.inl.acc.slice(nl + 1);
|
|
370
|
+
this.inl = null;
|
|
371
|
+
this.sol = true;
|
|
372
|
+
const row = StreamingMarkdownFilter.extractTableRow(line);
|
|
373
|
+
return row ? row + "\n" : "";
|
|
374
|
+
}
|
|
375
|
+
return "";
|
|
376
|
+
}
|
|
354
377
|
}
|
|
355
378
|
return "";
|
|
356
379
|
}
|
|
357
380
|
|
|
358
|
-
|
|
359
|
-
|
|
381
|
+
/** Extract cell contents from a table row, or return "" for separator rows. */
|
|
382
|
+
private static extractTableRow(line: string): string {
|
|
383
|
+
if (/^[\s|:\-]+$/.test(line) && line.includes("-")) return "";
|
|
384
|
+
const parts = line.split("|").map(c => c.trim());
|
|
385
|
+
const cells = parts.slice(
|
|
386
|
+
parts[0] === "" ? 1 : 0,
|
|
387
|
+
parts[parts.length - 1] === "" ? parts.length - 1 : parts.length,
|
|
388
|
+
);
|
|
389
|
+
return cells.join("\t");
|
|
360
390
|
}
|
|
361
391
|
}
|