codeksei 0.1.0 → 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.
- package/LICENSE +661 -661
- package/README.en.md +109 -47
- package/README.md +79 -58
- package/bin/cyberboss.js +1 -1
- package/package.json +86 -86
- package/scripts/open_shared_wechat_thread.sh +77 -77
- package/scripts/open_wechat_thread.sh +108 -108
- package/scripts/shared-common.js +144 -144
- package/scripts/shared-open.js +14 -14
- package/scripts/shared-start.js +5 -5
- package/scripts/shared-status.js +27 -27
- package/scripts/show_shared_status.sh +45 -45
- package/scripts/start_shared_app_server.sh +52 -52
- package/scripts/start_shared_wechat.sh +94 -94
- package/scripts/timeline-screenshot.sh +14 -14
- package/src/adapters/channel/weixin/account-store.js +99 -99
- package/src/adapters/channel/weixin/api-v2.js +50 -50
- package/src/adapters/channel/weixin/api.js +169 -169
- package/src/adapters/channel/weixin/context-token-store.js +84 -84
- package/src/adapters/channel/weixin/index.js +618 -604
- package/src/adapters/channel/weixin/legacy.js +579 -566
- package/src/adapters/channel/weixin/media-mime.js +22 -22
- package/src/adapters/channel/weixin/media-receive.js +370 -370
- package/src/adapters/channel/weixin/media-send.js +102 -102
- package/src/adapters/channel/weixin/message-utils-v2.js +282 -282
- package/src/adapters/channel/weixin/message-utils.js +199 -199
- package/src/adapters/channel/weixin/redact.js +41 -41
- package/src/adapters/channel/weixin/reminder-queue-store.js +101 -101
- package/src/adapters/channel/weixin/sync-buffer-store.js +35 -35
- package/src/adapters/runtime/codex/events.js +215 -215
- package/src/adapters/runtime/codex/index.js +109 -104
- package/src/adapters/runtime/codex/message-utils.js +95 -95
- package/src/adapters/runtime/codex/model-catalog.js +106 -106
- package/src/adapters/runtime/codex/protocol-leak-monitor.js +75 -75
- package/src/adapters/runtime/codex/rpc-client.js +339 -339
- package/src/adapters/runtime/codex/session-store.js +286 -286
- package/src/app/channel-send-file-cli.js +57 -57
- package/src/app/diary-write-cli.js +236 -88
- package/src/app/note-sync-cli.js +2 -2
- package/src/app/reminder-write-cli.js +215 -210
- package/src/app/review-cli.js +7 -5
- package/src/app/system-checkin-poller.js +64 -64
- package/src/app/system-send-cli.js +129 -129
- package/src/app/timeline-event-cli.js +28 -25
- package/src/app/timeline-screenshot-cli.js +103 -100
- package/src/core/app.js +1763 -1763
- package/src/core/branding.js +2 -1
- package/src/core/command-registry.js +381 -369
- package/src/core/config.js +30 -14
- package/src/core/default-targets.js +163 -163
- package/src/core/durable-note-schema.js +9 -8
- package/src/core/instructions-template.js +17 -16
- package/src/core/note-sync.js +8 -7
- package/src/core/path-utils.js +54 -0
- package/src/core/project-radar.js +11 -10
- package/src/core/review.js +48 -50
- package/src/core/stream-delivery.js +1162 -983
- package/src/core/system-message-dispatcher.js +68 -68
- package/src/core/system-message-queue-store.js +128 -128
- package/src/core/thread-state-store.js +96 -96
- package/src/core/timeline-screenshot-queue-store.js +134 -134
- package/src/core/timezone.js +436 -0
- package/src/core/workspace-bootstrap.js +9 -1
- package/src/index.js +148 -146
- package/src/integrations/timeline/index.js +130 -74
- package/src/integrations/timeline/state-sync.js +240 -0
- package/templates/weixin-instructions.md +12 -38
- package/templates/weixin-operations.md +29 -31
|
@@ -1,605 +1,619 @@
|
|
|
1
|
-
const crypto = require("crypto");
|
|
2
|
-
const { listWeixinAccounts, resolveSelectedAccount } = require("./account-store");
|
|
3
|
-
const { loadPersistedContextTokens, persistContextToken } = require("./context-token-store");
|
|
4
|
-
const { runV2LoginFlow } = require("./login-v2");
|
|
5
|
-
const {
|
|
6
|
-
getConfigV2,
|
|
7
|
-
getUpdatesV2,
|
|
8
|
-
sendTextV2,
|
|
9
|
-
sendTypingV2,
|
|
10
|
-
} = require("./api-v2");
|
|
11
|
-
const { createLegacyWeixinChannelAdapter } = require("./legacy");
|
|
12
|
-
const { createInboundFilter } = require("./message-utils-v2");
|
|
13
|
-
const { sendWeixinMediaFile } = require("./media-send");
|
|
14
|
-
const { loadSyncBuffer, saveSyncBuffer } = require("./sync-buffer-store");
|
|
15
|
-
|
|
16
|
-
const LONG_POLL_TIMEOUT_MS = 35_000;
|
|
17
|
-
const MAX_WEIXIN_CHUNK = 3800;
|
|
18
|
-
const SEND_MESSAGE_CHUNK_INTERVAL_MS = 350;
|
|
19
|
-
const WEIXIN_SEND_CHUNK_LIMIT = 80;
|
|
20
|
-
const WEIXIN_MAX_DELIVERY_MESSAGES = 10;
|
|
21
|
-
const SEND_RETRY_DELAYS_MS = [900, 1800];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
let
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
console.log(
|
|
155
|
-
console.log(`
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
//
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
candidate.lastIndexOf("\n"),
|
|
301
|
-
candidate.lastIndexOf("
|
|
302
|
-
candidate.lastIndexOf("
|
|
303
|
-
candidate.lastIndexOf(" ")
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const
|
|
372
|
-
if (
|
|
373
|
-
return
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
1
|
+
const crypto = require("crypto");
|
|
2
|
+
const { listWeixinAccounts, resolveSelectedAccount } = require("./account-store");
|
|
3
|
+
const { loadPersistedContextTokens, persistContextToken } = require("./context-token-store");
|
|
4
|
+
const { runV2LoginFlow } = require("./login-v2");
|
|
5
|
+
const {
|
|
6
|
+
getConfigV2,
|
|
7
|
+
getUpdatesV2,
|
|
8
|
+
sendTextV2,
|
|
9
|
+
sendTypingV2,
|
|
10
|
+
} = require("./api-v2");
|
|
11
|
+
const { createLegacyWeixinChannelAdapter } = require("./legacy");
|
|
12
|
+
const { createInboundFilter } = require("./message-utils-v2");
|
|
13
|
+
const { sendWeixinMediaFile } = require("./media-send");
|
|
14
|
+
const { loadSyncBuffer, saveSyncBuffer } = require("./sync-buffer-store");
|
|
15
|
+
|
|
16
|
+
const LONG_POLL_TIMEOUT_MS = 35_000;
|
|
17
|
+
const MAX_WEIXIN_CHUNK = 3800;
|
|
18
|
+
const SEND_MESSAGE_CHUNK_INTERVAL_MS = 350;
|
|
19
|
+
const WEIXIN_SEND_CHUNK_LIMIT = 80;
|
|
20
|
+
const WEIXIN_MAX_DELIVERY_MESSAGES = 10;
|
|
21
|
+
const SEND_RETRY_DELAYS_MS = [900, 1800];
|
|
22
|
+
const AMBIGUOUS_SEND_RETRY_DELAYS_MS = [1200];
|
|
23
|
+
|
|
24
|
+
function createWeixinChannelAdapter(config) {
|
|
25
|
+
const variant = normalizeAdapterVariant(config.weixinAdapterVariant);
|
|
26
|
+
if (variant === "legacy") {
|
|
27
|
+
return createLegacyWeixinChannelAdapter(config);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let selectedAccount = null;
|
|
31
|
+
let contextTokenCache = null;
|
|
32
|
+
const inboundFilter = createInboundFilter();
|
|
33
|
+
|
|
34
|
+
function ensureAccount() {
|
|
35
|
+
if (!selectedAccount) {
|
|
36
|
+
selectedAccount = resolveSelectedAccount(config);
|
|
37
|
+
contextTokenCache = loadPersistedContextTokens(config, selectedAccount.accountId);
|
|
38
|
+
}
|
|
39
|
+
return selectedAccount;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function ensureContextTokenCache() {
|
|
43
|
+
if (!contextTokenCache) {
|
|
44
|
+
const account = ensureAccount();
|
|
45
|
+
contextTokenCache = loadPersistedContextTokens(config, account.accountId);
|
|
46
|
+
}
|
|
47
|
+
return contextTokenCache;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function rememberContextToken(userId, contextToken) {
|
|
51
|
+
const account = ensureAccount();
|
|
52
|
+
const normalizedUserId = typeof userId === "string" ? userId.trim() : "";
|
|
53
|
+
const normalizedToken = typeof contextToken === "string" ? contextToken.trim() : "";
|
|
54
|
+
if (!normalizedUserId || !normalizedToken) {
|
|
55
|
+
return "";
|
|
56
|
+
}
|
|
57
|
+
contextTokenCache = persistContextToken(config, account.accountId, normalizedUserId, normalizedToken);
|
|
58
|
+
return normalizedToken;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function resolveContextToken(userId, explicitToken = "") {
|
|
62
|
+
const normalizedExplicitToken = typeof explicitToken === "string" ? explicitToken.trim() : "";
|
|
63
|
+
if (normalizedExplicitToken) {
|
|
64
|
+
return normalizedExplicitToken;
|
|
65
|
+
}
|
|
66
|
+
const normalizedUserId = typeof userId === "string" ? userId.trim() : "";
|
|
67
|
+
if (!normalizedUserId) {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
return ensureContextTokenCache()[normalizedUserId] || "";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sendTextChunks({ userId, text, contextToken = "", preserveBlock = false, trace = null }) {
|
|
74
|
+
const account = ensureAccount();
|
|
75
|
+
const resolvedToken = resolveContextToken(userId, contextToken);
|
|
76
|
+
if (!resolvedToken) {
|
|
77
|
+
throw new Error(`缺少 context_token,无法回复用户 ${userId}`);
|
|
78
|
+
}
|
|
79
|
+
const content = String(text || "");
|
|
80
|
+
if (!content.trim()) {
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
}
|
|
83
|
+
const sendChunks = preserveBlock
|
|
84
|
+
? splitUtf8(compactPlainTextForWeixin(content) || "已完成。", MAX_WEIXIN_CHUNK)
|
|
85
|
+
: packChunksForWeixinDelivery(
|
|
86
|
+
chunkReplyTextForWeixin(content, WEIXIN_SEND_CHUNK_LIMIT).length
|
|
87
|
+
? chunkReplyTextForWeixin(content, WEIXIN_SEND_CHUNK_LIMIT)
|
|
88
|
+
: ["已完成。"],
|
|
89
|
+
WEIXIN_MAX_DELIVERY_MESSAGES,
|
|
90
|
+
MAX_WEIXIN_CHUNK
|
|
91
|
+
);
|
|
92
|
+
const traceContext = buildWeixinTraceContext(trace, {
|
|
93
|
+
enabled: config.weixinDeliveryTrace,
|
|
94
|
+
origin: "adapter.sendText",
|
|
95
|
+
variant: "v2",
|
|
96
|
+
preserveBlock,
|
|
97
|
+
chunkTotal: sendChunks.length,
|
|
98
|
+
});
|
|
99
|
+
return sendChunks.reduce((promise, chunk, index) => promise
|
|
100
|
+
.then(() => {
|
|
101
|
+
const compactChunk = compactPlainTextForWeixin(chunk) || "已完成。";
|
|
102
|
+
const clientId = `cb-${crypto.randomUUID()}`;
|
|
103
|
+
return sendV2TextChunk({
|
|
104
|
+
baseUrl: account.baseUrl,
|
|
105
|
+
token: account.token,
|
|
106
|
+
routeTag: account.routeTag,
|
|
107
|
+
clientVersion: config.weixinProtocolClientVersion,
|
|
108
|
+
toUserId: userId,
|
|
109
|
+
text: compactChunk,
|
|
110
|
+
contextToken: resolvedToken,
|
|
111
|
+
clientId,
|
|
112
|
+
trace: {
|
|
113
|
+
...traceContext,
|
|
114
|
+
chunkIndex: index + 1,
|
|
115
|
+
chars: compactChunk.length,
|
|
116
|
+
textHash: hashTraceText(compactChunk),
|
|
117
|
+
clientId,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
})
|
|
121
|
+
.then(() => {
|
|
122
|
+
if (index < sendChunks.length - 1) {
|
|
123
|
+
return sleep(SEND_MESSAGE_CHUNK_INTERVAL_MS);
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}), Promise.resolve());
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
describe() {
|
|
131
|
+
return {
|
|
132
|
+
id: "weixin",
|
|
133
|
+
variant: "v2",
|
|
134
|
+
kind: "channel",
|
|
135
|
+
stateDir: config.stateDir,
|
|
136
|
+
baseUrl: config.weixinBaseUrl,
|
|
137
|
+
accountsDir: config.accountsDir,
|
|
138
|
+
syncBufferDir: config.syncBufferDir,
|
|
139
|
+
protocolClientVersion: config.weixinProtocolClientVersion,
|
|
140
|
+
routeTag: config.weixinRouteTag,
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
async login() {
|
|
144
|
+
await runV2LoginFlow(config);
|
|
145
|
+
},
|
|
146
|
+
printAccounts() {
|
|
147
|
+
const accounts = listWeixinAccounts(config);
|
|
148
|
+
if (!accounts.length) {
|
|
149
|
+
console.log("当前没有已保存的微信账号。先执行 `npm run login`。");
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
console.log("已保存账号:");
|
|
153
|
+
for (const account of accounts) {
|
|
154
|
+
console.log(`- ${account.accountId}`);
|
|
155
|
+
console.log(` userId: ${account.userId || "(unknown)"}`);
|
|
156
|
+
console.log(` baseUrl: ${account.baseUrl || config.weixinBaseUrl}`);
|
|
157
|
+
if (account.routeTag) {
|
|
158
|
+
console.log(` routeTag: ${account.routeTag}`);
|
|
159
|
+
}
|
|
160
|
+
console.log(` savedAt: ${account.savedAt || "(unknown)"}`);
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
resolveAccount() {
|
|
164
|
+
return ensureAccount();
|
|
165
|
+
},
|
|
166
|
+
getKnownContextTokens() {
|
|
167
|
+
return { ...ensureContextTokenCache() };
|
|
168
|
+
},
|
|
169
|
+
loadSyncBuffer() {
|
|
170
|
+
const account = ensureAccount();
|
|
171
|
+
return loadSyncBuffer(config, account.accountId);
|
|
172
|
+
},
|
|
173
|
+
saveSyncBuffer(buffer) {
|
|
174
|
+
const account = ensureAccount();
|
|
175
|
+
saveSyncBuffer(config, account.accountId, buffer);
|
|
176
|
+
},
|
|
177
|
+
rememberContextToken,
|
|
178
|
+
async getUpdates({ syncBuffer = "", timeoutMs = LONG_POLL_TIMEOUT_MS } = {}) {
|
|
179
|
+
const account = ensureAccount();
|
|
180
|
+
const response = await getUpdatesV2({
|
|
181
|
+
baseUrl: account.baseUrl,
|
|
182
|
+
token: account.token,
|
|
183
|
+
getUpdatesBuf: syncBuffer,
|
|
184
|
+
timeoutMs,
|
|
185
|
+
routeTag: account.routeTag,
|
|
186
|
+
clientVersion: config.weixinProtocolClientVersion,
|
|
187
|
+
});
|
|
188
|
+
if (typeof response?.get_updates_buf === "string" && response.get_updates_buf.trim()) {
|
|
189
|
+
this.saveSyncBuffer(response.get_updates_buf.trim());
|
|
190
|
+
}
|
|
191
|
+
const messages = Array.isArray(response?.msgs) ? response.msgs : [];
|
|
192
|
+
for (const message of messages) {
|
|
193
|
+
const userId = typeof message?.from_user_id === "string" ? message.from_user_id.trim() : "";
|
|
194
|
+
const contextToken = typeof message?.context_token === "string" ? message.context_token.trim() : "";
|
|
195
|
+
if (userId && contextToken) {
|
|
196
|
+
rememberContextToken(userId, contextToken);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return response;
|
|
200
|
+
},
|
|
201
|
+
normalizeIncomingMessage(message) {
|
|
202
|
+
const account = ensureAccount();
|
|
203
|
+
return inboundFilter.normalize(message, config, account.accountId);
|
|
204
|
+
},
|
|
205
|
+
async sendText({ userId, text, contextToken = "", preserveBlock = false, trace = null }) {
|
|
206
|
+
await sendTextChunks({ userId, text, contextToken, preserveBlock, trace });
|
|
207
|
+
},
|
|
208
|
+
async sendTyping({ userId, status = 1, contextToken = "" }) {
|
|
209
|
+
const account = ensureAccount();
|
|
210
|
+
const resolvedToken = resolveContextToken(userId, contextToken);
|
|
211
|
+
if (!resolvedToken) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
const configResponse = await getConfigV2({
|
|
215
|
+
baseUrl: account.baseUrl,
|
|
216
|
+
token: account.token,
|
|
217
|
+
routeTag: account.routeTag,
|
|
218
|
+
clientVersion: config.weixinProtocolClientVersion,
|
|
219
|
+
ilinkUserId: userId,
|
|
220
|
+
contextToken: resolvedToken,
|
|
221
|
+
}).catch(() => null);
|
|
222
|
+
const typingTicket = typeof configResponse?.typing_ticket === "string"
|
|
223
|
+
? configResponse.typing_ticket.trim()
|
|
224
|
+
: "";
|
|
225
|
+
if (!typingTicket) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
await sendTypingV2({
|
|
229
|
+
baseUrl: account.baseUrl,
|
|
230
|
+
token: account.token,
|
|
231
|
+
routeTag: account.routeTag,
|
|
232
|
+
clientVersion: config.weixinProtocolClientVersion,
|
|
233
|
+
body: {
|
|
234
|
+
ilink_user_id: userId,
|
|
235
|
+
typing_ticket: typingTicket,
|
|
236
|
+
status,
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
},
|
|
240
|
+
async sendFile({ userId, filePath, contextToken = "" }) {
|
|
241
|
+
const account = ensureAccount();
|
|
242
|
+
const resolvedToken = resolveContextToken(userId, contextToken);
|
|
243
|
+
if (!resolvedToken) {
|
|
244
|
+
throw new Error(`缺少 context_token,无法发送文件给用户 ${userId}`);
|
|
245
|
+
}
|
|
246
|
+
// Text polling/sending lives on the v2 stack, but attachments intentionally
|
|
247
|
+
// stay on the legacy media API. The original repo never moved sendFile onto
|
|
248
|
+
// v2, and live timeline screenshot failures ("getUploadUrl returned no
|
|
249
|
+
// upload_param") only appeared after we forced media onto the v2 headers.
|
|
250
|
+
// Keep this split explicit so future "cleanup" work does not silently route
|
|
251
|
+
// screenshots/files back onto the broken stack.
|
|
252
|
+
return sendWeixinMediaFile({
|
|
253
|
+
filePath,
|
|
254
|
+
to: userId,
|
|
255
|
+
contextToken: resolvedToken,
|
|
256
|
+
baseUrl: account.baseUrl,
|
|
257
|
+
token: account.token,
|
|
258
|
+
cdnBaseUrl: config.weixinCdnBaseUrl,
|
|
259
|
+
apiVariant: "legacy",
|
|
260
|
+
routeTag: account.routeTag,
|
|
261
|
+
clientVersion: config.weixinProtocolClientVersion,
|
|
262
|
+
});
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function normalizeAdapterVariant(value) {
|
|
268
|
+
const normalized = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
269
|
+
return normalized === "legacy" ? "legacy" : "v2";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function splitUtf8(text, maxRunes) {
|
|
273
|
+
const runes = Array.from(String(text || ""));
|
|
274
|
+
if (!runes.length || runes.length <= maxRunes) {
|
|
275
|
+
return [String(text || "")];
|
|
276
|
+
}
|
|
277
|
+
const chunks = [];
|
|
278
|
+
while (runes.length) {
|
|
279
|
+
chunks.push(runes.splice(0, maxRunes).join(""));
|
|
280
|
+
}
|
|
281
|
+
return chunks;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function compactPlainTextForWeixin(text) {
|
|
285
|
+
const normalized = String(text || "").replace(/\r\n/g, "\n");
|
|
286
|
+
return trimOuterBlankLines(normalized.replace(/\n\s*\n+/g, "\n"));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function chunkReplyText(text, limit = 3500) {
|
|
290
|
+
const normalized = trimOuterBlankLines(String(text || "").replace(/\r\n/g, "\n"));
|
|
291
|
+
if (!normalized.trim()) {
|
|
292
|
+
return [];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const chunks = [];
|
|
296
|
+
let remaining = normalized;
|
|
297
|
+
while (remaining.length > limit) {
|
|
298
|
+
const candidate = remaining.slice(0, limit);
|
|
299
|
+
const splitIndex = Math.max(
|
|
300
|
+
candidate.lastIndexOf("\n\n"),
|
|
301
|
+
candidate.lastIndexOf("\n"),
|
|
302
|
+
candidate.lastIndexOf("。"),
|
|
303
|
+
candidate.lastIndexOf(". "),
|
|
304
|
+
candidate.lastIndexOf(" ")
|
|
305
|
+
);
|
|
306
|
+
const cut = splitIndex > limit * 0.4 ? splitIndex + (candidate[splitIndex] === "\n" ? 0 : 1) : limit;
|
|
307
|
+
const chunk = trimOuterBlankLines(remaining.slice(0, cut));
|
|
308
|
+
if (chunk.trim()) {
|
|
309
|
+
chunks.push(chunk);
|
|
310
|
+
}
|
|
311
|
+
remaining = trimOuterBlankLines(remaining.slice(cut));
|
|
312
|
+
}
|
|
313
|
+
if (remaining) {
|
|
314
|
+
chunks.push(remaining);
|
|
315
|
+
}
|
|
316
|
+
return chunks.filter(Boolean);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function chunkReplyTextForWeixin(text, limit = 80) {
|
|
320
|
+
const normalized = trimOuterBlankLines(String(text || "").replace(/\r\n/g, "\n"));
|
|
321
|
+
if (!normalized.trim()) {
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const boundaries = collectStreamingBoundaries(normalized);
|
|
326
|
+
if (!boundaries.length) {
|
|
327
|
+
return chunkReplyText(normalized, limit);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const units = [];
|
|
331
|
+
let start = 0;
|
|
332
|
+
for (const boundary of boundaries) {
|
|
333
|
+
if (boundary <= start) {
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
const unit = trimOuterBlankLines(normalized.slice(start, boundary));
|
|
337
|
+
if (unit) {
|
|
338
|
+
units.push(unit);
|
|
339
|
+
}
|
|
340
|
+
start = boundary;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const tail = trimOuterBlankLines(normalized.slice(start));
|
|
344
|
+
if (tail) {
|
|
345
|
+
units.push(tail);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!units.length) {
|
|
349
|
+
return chunkReplyText(normalized, limit);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const chunks = [];
|
|
353
|
+
for (const unit of units) {
|
|
354
|
+
if (unit.length <= limit) {
|
|
355
|
+
chunks.push(unit);
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
chunks.push(...chunkReplyText(unit, limit));
|
|
359
|
+
}
|
|
360
|
+
return chunks.filter(Boolean);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function packChunksForWeixinDelivery(chunks, maxMessages = 10, maxChunkChars = 3800) {
|
|
364
|
+
const normalizedChunks = Array.isArray(chunks)
|
|
365
|
+
? chunks.map((chunk) => compactPlainTextForWeixin(chunk)).filter(Boolean)
|
|
366
|
+
: [];
|
|
367
|
+
if (!normalizedChunks.length) {
|
|
368
|
+
return normalizedChunks;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const groupedChunks = groupChunksWithinBudget(normalizedChunks, maxChunkChars);
|
|
372
|
+
if (groupedChunks.length <= maxMessages) {
|
|
373
|
+
return groupedChunks;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const fullText = compactPlainTextForWeixin(normalizedChunks.join("\n")) || "已完成。";
|
|
377
|
+
const hardChunks = splitUtf8(fullText, maxChunkChars);
|
|
378
|
+
if (hardChunks.length <= maxMessages) {
|
|
379
|
+
return hardChunks;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// `maxMessages` is only a spam guard. If the full reply still needs more
|
|
383
|
+
// chunks at the hard per-message budget, prefer complete delivery over
|
|
384
|
+
// silently dropping the tail.
|
|
385
|
+
return hardChunks;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function groupChunksWithinBudget(chunks, maxChunkChars) {
|
|
389
|
+
const grouped = [];
|
|
390
|
+
let current = "";
|
|
391
|
+
|
|
392
|
+
for (const rawChunk of Array.isArray(chunks) ? chunks : []) {
|
|
393
|
+
const normalizedChunk = compactPlainTextForWeixin(rawChunk);
|
|
394
|
+
if (!normalizedChunk) {
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const units = normalizedChunk.length > maxChunkChars
|
|
399
|
+
? splitUtf8(normalizedChunk, maxChunkChars)
|
|
400
|
+
: [normalizedChunk];
|
|
401
|
+
|
|
402
|
+
for (const unit of units) {
|
|
403
|
+
if (!current) {
|
|
404
|
+
current = unit;
|
|
405
|
+
continue;
|
|
406
|
+
}
|
|
407
|
+
const joined = `${current}\n${unit}`;
|
|
408
|
+
if (joined.length > maxChunkChars) {
|
|
409
|
+
grouped.push(current);
|
|
410
|
+
current = unit;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
current = joined;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (current) {
|
|
418
|
+
grouped.push(current);
|
|
419
|
+
}
|
|
420
|
+
return grouped;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function collectStreamingBoundaries(text) {
|
|
424
|
+
const boundaries = new Set();
|
|
425
|
+
|
|
426
|
+
const regex = /\n\s*\n+/g;
|
|
427
|
+
let match = regex.exec(text);
|
|
428
|
+
while (match) {
|
|
429
|
+
boundaries.add(match.index + match[0].length);
|
|
430
|
+
match = regex.exec(text);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const listRegex = /\n(?:(?:[-*])\s+|(?:\d+\.)\s+)/g;
|
|
434
|
+
match = listRegex.exec(text);
|
|
435
|
+
while (match) {
|
|
436
|
+
boundaries.add(match.index + 1);
|
|
437
|
+
match = listRegex.exec(text);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
441
|
+
const char = text[index];
|
|
442
|
+
if (!/[。!?!?]/.test(char)) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
let end = index + 1;
|
|
447
|
+
while (end < text.length && /["'”’))\]」』】]/.test(text[end])) {
|
|
448
|
+
end += 1;
|
|
449
|
+
}
|
|
450
|
+
while (end < text.length && /[\t \n]/.test(text[end])) {
|
|
451
|
+
end += 1;
|
|
452
|
+
}
|
|
453
|
+
boundaries.add(end);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return Array.from(boundaries).sort((left, right) => left - right);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
async function sendTextChunkWithRetry(send, { trace = null } = {}) {
|
|
460
|
+
let lastError = null;
|
|
461
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
462
|
+
const attemptNumber = attempt + 1;
|
|
463
|
+
try {
|
|
464
|
+
logWeixinSendTrace("attempt", {
|
|
465
|
+
...buildWeixinTraceContext(trace),
|
|
466
|
+
attempt: attemptNumber,
|
|
467
|
+
});
|
|
468
|
+
const result = await send();
|
|
469
|
+
logWeixinSendTrace("success", {
|
|
470
|
+
...buildWeixinTraceContext(trace),
|
|
471
|
+
attempt: attemptNumber,
|
|
472
|
+
});
|
|
473
|
+
return result;
|
|
474
|
+
} catch (error) {
|
|
475
|
+
lastError = error;
|
|
476
|
+
const retryDelays = getSendRetryDelaysMs(error);
|
|
477
|
+
const retryable = attempt < retryDelays.length;
|
|
478
|
+
logWeixinSendTrace("error", {
|
|
479
|
+
...buildWeixinTraceContext(trace),
|
|
480
|
+
attempt: attemptNumber,
|
|
481
|
+
retryable,
|
|
482
|
+
error: String(error?.message || error || ""),
|
|
483
|
+
});
|
|
484
|
+
if (!retryable) {
|
|
485
|
+
throw error;
|
|
486
|
+
}
|
|
487
|
+
await sleep(retryDelays[attempt]);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
throw lastError || new Error("sendText chunk failed");
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function sendV2TextChunk({
|
|
494
|
+
sendTextImpl = sendTextV2,
|
|
495
|
+
baseUrl,
|
|
496
|
+
token,
|
|
497
|
+
routeTag = "",
|
|
498
|
+
clientVersion = "",
|
|
499
|
+
toUserId,
|
|
500
|
+
text,
|
|
501
|
+
contextToken,
|
|
502
|
+
clientId = "",
|
|
503
|
+
trace = null,
|
|
504
|
+
}) {
|
|
505
|
+
const stableClientId = String(clientId || "").trim() || `cb-${crypto.randomUUID()}`;
|
|
506
|
+
return sendTextChunkWithRetry(
|
|
507
|
+
() => sendTextImpl({
|
|
508
|
+
baseUrl,
|
|
509
|
+
token,
|
|
510
|
+
routeTag,
|
|
511
|
+
clientVersion,
|
|
512
|
+
toUserId,
|
|
513
|
+
text,
|
|
514
|
+
contextToken,
|
|
515
|
+
clientId: stableClientId,
|
|
516
|
+
}),
|
|
517
|
+
{
|
|
518
|
+
trace: buildWeixinTraceContext(trace, {
|
|
519
|
+
variant: "v2",
|
|
520
|
+
clientId: stableClientId,
|
|
521
|
+
chars: String(text || "").length,
|
|
522
|
+
textHash: hashTraceText(text),
|
|
523
|
+
}),
|
|
524
|
+
}
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function getSendRetryDelaysMs(error) {
|
|
529
|
+
const message = String(error?.message || error || "");
|
|
530
|
+
// `ret=-2` is ambiguous: the first attempt may already have landed, or it may
|
|
531
|
+
// have died before the user ever saw it. Retrying with the same client_id once
|
|
532
|
+
// keeps the call idempotent enough to avoid visible truncation without turning
|
|
533
|
+
// one flaky send into a burst of duplicate bubbles.
|
|
534
|
+
if (message.includes("ret=-2")) {
|
|
535
|
+
return AMBIGUOUS_SEND_RETRY_DELAYS_MS;
|
|
536
|
+
}
|
|
537
|
+
if (message.includes("AbortError")
|
|
538
|
+
|| message.includes("aborted")
|
|
539
|
+
|| message.includes("fetch failed")
|
|
540
|
+
|| message.includes("ECONNRESET")
|
|
541
|
+
|| message.includes("ETIMEDOUT")
|
|
542
|
+
|| /http 5\d\d/.test(message)) {
|
|
543
|
+
return SEND_RETRY_DELAYS_MS;
|
|
544
|
+
}
|
|
545
|
+
return [];
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
function buildWeixinTraceContext(trace, defaults = {}) {
|
|
549
|
+
const normalizedTrace = normalizeTraceContext(trace);
|
|
550
|
+
return {
|
|
551
|
+
...defaults,
|
|
552
|
+
...normalizedTrace,
|
|
553
|
+
enabled: Boolean(normalizedTrace.enabled ?? defaults.enabled),
|
|
554
|
+
traceId: normalizeTraceText(normalizedTrace.traceId)
|
|
555
|
+
|| normalizeTraceText(defaults.traceId)
|
|
556
|
+
|| `wx-${crypto.randomUUID().slice(0, 8)}`,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
function normalizeTraceContext(trace) {
|
|
561
|
+
if (!trace || typeof trace !== "object") {
|
|
562
|
+
return {};
|
|
563
|
+
}
|
|
564
|
+
return { ...trace };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function normalizeTraceText(value) {
|
|
568
|
+
return typeof value === "string" ? value.trim() : "";
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function logWeixinSendTrace(stage, trace) {
|
|
572
|
+
if (!Boolean(trace?.enabled)) {
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
575
|
+
const parts = [
|
|
563
576
|
`[codeksei] weixin send trace stage=${stage}`,
|
|
564
|
-
`pid=${process.pid}`,
|
|
565
|
-
`trace=${trace.traceId || "(none)"}`,
|
|
566
|
-
`origin=${trace.origin || "adapter.sendText"}`,
|
|
567
|
-
`variant=${trace.variant || "v2"}`,
|
|
568
|
-
trace.threadId ? `thread=${trace.threadId}` : "",
|
|
569
|
-
`turn=${trace.turnId || "(pending)"}`,
|
|
570
|
-
trace.mode ? `mode=${trace.mode}` : "",
|
|
571
|
-
trace.trigger ? `trigger=${trace.trigger}` : "",
|
|
572
|
-
`chunk=${trace.chunkIndex || 1}/${trace.chunkTotal || 1}`,
|
|
573
|
-
`preserveBlock=${trace.preserveBlock ? "1" : "0"}`,
|
|
574
|
-
`attempt=${trace.attempt || 1}`,
|
|
575
|
-
trace.retryable === undefined ? "" : `retryable=${trace.retryable ? "1" : "0"}`,
|
|
576
|
-
`clientId=${trace.clientId || "(none)"}`,
|
|
577
|
-
`chars=${trace.chars || 0}`,
|
|
578
|
-
`hash=${trace.textHash || hashTraceText("")}`,
|
|
579
|
-
].filter(Boolean);
|
|
580
|
-
if (trace.error) {
|
|
581
|
-
parts.push(`error=${JSON.stringify(String(trace.error || ""))}`);
|
|
582
|
-
console.error(parts.join(" "));
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
console.log(parts.join(" "));
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
function hashTraceText(text) {
|
|
589
|
-
return crypto.createHash("sha1").update(String(text || ""), "utf8").digest("hex").slice(0, 12);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function trimOuterBlankLines(text) {
|
|
593
|
-
return String(text || "")
|
|
594
|
-
.replace(/^\s*\n+/g, "")
|
|
595
|
-
.replace(/\n+\s*$/g, "");
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
function sleep(ms) {
|
|
599
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
module.exports = {
|
|
603
|
-
createWeixinChannelAdapter,
|
|
604
|
-
|
|
605
|
-
|
|
577
|
+
`pid=${process.pid}`,
|
|
578
|
+
`trace=${trace.traceId || "(none)"}`,
|
|
579
|
+
`origin=${trace.origin || "adapter.sendText"}`,
|
|
580
|
+
`variant=${trace.variant || "v2"}`,
|
|
581
|
+
trace.threadId ? `thread=${trace.threadId}` : "",
|
|
582
|
+
`turn=${trace.turnId || "(pending)"}`,
|
|
583
|
+
trace.mode ? `mode=${trace.mode}` : "",
|
|
584
|
+
trace.trigger ? `trigger=${trace.trigger}` : "",
|
|
585
|
+
`chunk=${trace.chunkIndex || 1}/${trace.chunkTotal || 1}`,
|
|
586
|
+
`preserveBlock=${trace.preserveBlock ? "1" : "0"}`,
|
|
587
|
+
`attempt=${trace.attempt || 1}`,
|
|
588
|
+
trace.retryable === undefined ? "" : `retryable=${trace.retryable ? "1" : "0"}`,
|
|
589
|
+
`clientId=${trace.clientId || "(none)"}`,
|
|
590
|
+
`chars=${trace.chars || 0}`,
|
|
591
|
+
`hash=${trace.textHash || hashTraceText("")}`,
|
|
592
|
+
].filter(Boolean);
|
|
593
|
+
if (trace.error) {
|
|
594
|
+
parts.push(`error=${JSON.stringify(String(trace.error || ""))}`);
|
|
595
|
+
console.error(parts.join(" "));
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
console.log(parts.join(" "));
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function hashTraceText(text) {
|
|
602
|
+
return crypto.createHash("sha1").update(String(text || ""), "utf8").digest("hex").slice(0, 12);
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function trimOuterBlankLines(text) {
|
|
606
|
+
return String(text || "")
|
|
607
|
+
.replace(/^\s*\n+/g, "")
|
|
608
|
+
.replace(/\n+\s*$/g, "");
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function sleep(ms) {
|
|
612
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
module.exports = {
|
|
616
|
+
createWeixinChannelAdapter,
|
|
617
|
+
packChunksForWeixinDelivery,
|
|
618
|
+
sendV2TextChunk,
|
|
619
|
+
};
|