@kafca/agentdock 0.1.43 → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +6 -0
  2. package/dist/renderer/assets/{Badge-CbG6yRSm.js → Badge-Qk9qHovH.js} +1 -1
  3. package/dist/renderer/assets/{Chat-BvNNWGGX.js → Chat-BK-fm0Xr.js} +1 -1
  4. package/dist/renderer/assets/{ChatMarkdown-Ct-PvJB8.js → ChatMarkdown-BKdW8OON.js} +1 -1
  5. package/dist/renderer/assets/{Config-lQX2qVVV.js → Config-B93CQGvY.js} +1 -1
  6. package/dist/renderer/assets/{CronList-Dj_3muVG.js → CronList-Z3TvSRKP.js} +1 -1
  7. package/dist/renderer/assets/{Dashboard-eh_RhGYb.js → Dashboard-Ch3QkWwf.js} +2 -2
  8. package/dist/renderer/assets/{EmptyState-Bn2Qh14U.js → EmptyState-kD9yAwtw.js} +1 -1
  9. package/dist/renderer/assets/{KnowledgeDetail-Bsz6Ou41.js → KnowledgeDetail-BAMCLrjz.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeHome-Cgj4XFXn.js → KnowledgeHome-ChvjOfrc.js} +1 -1
  11. package/dist/renderer/assets/{Logs-Dp-NF6GN.js → Logs-Cv0nth3j.js} +1 -1
  12. package/dist/renderer/assets/{Modal-B6YPPBN6.js → Modal-DwY4lecS.js} +1 -1
  13. package/dist/renderer/assets/{MonitorList-xEZWg460.js → MonitorList-JEvMoWUm.js} +1 -1
  14. package/dist/renderer/assets/{Page-Bn7RjGOS.js → Page-CnB9ePsb.js} +1 -1
  15. package/dist/renderer/assets/{ProjectDetail-Cv_jIE61.js → ProjectDetail-DJa3sMmU.js} +1 -1
  16. package/dist/renderer/assets/{ProjectList-BDk5_gqY.js → ProjectList-D6CXumR_.js} +1 -1
  17. package/dist/renderer/assets/{Select-xshfMIEQ.js → Select-zfoksLGB.js} +1 -1
  18. package/dist/renderer/assets/{SessionChat-BkHy90z3.js → SessionChat-DUTh4WoM.js} +1 -1
  19. package/dist/renderer/assets/{SessionList-BZyXT92L.js → SessionList-D9o-lUst.js} +1 -1
  20. package/dist/renderer/assets/{ThreadChat-DUOIyXrS.js → ThreadChat-BYXlT-Kc.js} +1 -1
  21. package/dist/renderer/assets/{Workspace-Dl80fNEC.js → Workspace-BrhSwRQ-.js} +1 -1
  22. package/dist/renderer/assets/{arrow-left-C54ZI-AS.js → arrow-left-CNEAuElg.js} +1 -1
  23. package/dist/renderer/assets/{book-open-Dl7GO0zn.js → book-open-BNJMhNI-.js} +1 -1
  24. package/dist/renderer/assets/{check-CiqgtrOL.js → check-BnclClRU.js} +1 -1
  25. package/dist/renderer/assets/{chevron-down-DTL2OuVh.js → chevron-down-Bh53e0O-.js} +1 -1
  26. package/dist/renderer/assets/{circle-DaVKfa3P.js → circle-ClZTWCWO.js} +1 -1
  27. package/dist/renderer/assets/{desktop-jaAiayNB.js → desktop-C5vBT0yM.js} +1 -1
  28. package/dist/renderer/assets/{funnel-CrmnLMJI.js → funnel-_5hsz0xI.js} +1 -1
  29. package/dist/renderer/assets/{heart-BMfQI7y_.js → heart-u4Q1aOqj.js} +1 -1
  30. package/dist/renderer/assets/{index-HPNb1zfx.js → index-B36eowj-.js} +2 -2
  31. package/dist/renderer/assets/{index-nreiKY1f.js → index-BNFx4uYv.js} +1 -1
  32. package/dist/renderer/assets/{pencil-wTQ4l465.js → pencil-DLh9QNmT.js} +1 -1
  33. package/dist/renderer/assets/{play-BK--MXmw.js → play-BDTk9Scm.js} +1 -1
  34. package/dist/renderer/assets/{plug-BIx9fjip.js → plug-BG0JHW6B.js} +1 -1
  35. package/dist/renderer/assets/{plus-Dn7JFWQ8.js → plus-BSc8POsz.js} +1 -1
  36. package/dist/renderer/assets/projects-DwfZwxDW.js +1 -0
  37. package/dist/renderer/assets/{save-CC3zOSWb.js → save-D5AO_G6a.js} +1 -1
  38. package/dist/renderer/assets/{search-CDTy9Czu.js → search-CniPk7Ey.js} +1 -1
  39. package/dist/renderer/assets/{server-DtQk3t2a.js → server-GsXyQAZm.js} +1 -1
  40. package/dist/renderer/assets/{session-utils-4ep5D66T.js → session-utils-BDxzgTBC.js} +1 -1
  41. package/dist/renderer/assets/{sessions-Dor1LteH.js → sessions-CLcKA1Tt.js} +1 -1
  42. package/dist/renderer/assets/{shield-check-Cz2LpU6r.js → shield-check-4i0E3hmf.js} +1 -1
  43. package/dist/renderer/assets/{trash-2-C2EmWFZV.js → trash-2-DhtOCsO5.js} +1 -1
  44. package/dist/renderer/assets/{user-I8B-cs5A.js → user-BtC_pVXV.js} +1 -1
  45. package/dist/renderer/assets/{wifi-off-CPevTQJn.js → wifi-off-BHPzPY_B.js} +1 -1
  46. package/dist/renderer/index.html +1 -1
  47. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +2 -0
  48. package/dist-electron/services/local-ai-core/src/channel/lark/inbound.js +114 -0
  49. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +52 -65
  50. package/dist-electron/services/local-ai-core/src/channel/shared/thread-routing.js +34 -0
  51. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +12 -18
  52. package/dist-electron/tests/integration/channel-gateways.test.js +32 -0
  53. package/dist-electron/tests/integration/lark-gateway.test.js +1918 -0
  54. package/dist-electron/tests/integration/local-core-acp-progress.test.js +1772 -0
  55. package/dist-electron/tests/integration/local-core-refactor.test.js +0 -4544
  56. package/dist-electron/tests/integration/local-core-routes.test.js +204 -0
  57. package/dist-electron/tests/integration/weixin-gateway.test.js +927 -0
  58. package/package.json +1 -1
  59. package/dist/renderer/assets/projects-brJZgwEJ.js +0 -1
@@ -0,0 +1,927 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const node_fs_1 = require("node:fs");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
11
+ const local_core_weixin_gateway_js_1 = require("../../services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js");
12
+ (0, node_test_1.default)('weixin channel can encrypt, upload, and send a local file', async () => {
13
+ const tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'weixin-file-send-'));
14
+ const originalFetch = globalThis.fetch;
15
+ try {
16
+ const filePath = (0, node_path_1.join)(tempDir, 'report.txt');
17
+ (0, node_fs_1.writeFileSync)(filePath, 'hello weixin');
18
+ const uploadUrlRequests = [];
19
+ const cdnUploads = [];
20
+ const sentMessages = [];
21
+ globalThis.fetch = (async (url, init) => {
22
+ const target = String(url);
23
+ if (target.endsWith('/ilink/bot/getuploadurl')) {
24
+ uploadUrlRequests.push(JSON.parse(String(init?.body || '{}')));
25
+ return {
26
+ ok: true,
27
+ json: async () => ({ ret: 0, upload_param: 'upload-param-1' }),
28
+ };
29
+ }
30
+ if (target.includes('/upload?')) {
31
+ const body = init?.body instanceof Uint8Array ? init.body : new Uint8Array();
32
+ cdnUploads.push({ url: target, size: body.byteLength });
33
+ return {
34
+ ok: true,
35
+ headers: {
36
+ get: (name) => name.toLowerCase() === 'x-encrypted-param' ? 'download-param-1' : null,
37
+ },
38
+ };
39
+ }
40
+ if (target.endsWith('/ilink/bot/sendmessage')) {
41
+ sentMessages.push(JSON.parse(String(init?.body || '{}')));
42
+ return {
43
+ ok: true,
44
+ json: async () => ({ ret: 0 }),
45
+ };
46
+ }
47
+ throw new Error(`Unexpected fetch ${target}`);
48
+ });
49
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
50
+ store: {
51
+ getPlatformThreadBinding: () => ({
52
+ workspace_id: 'default',
53
+ platform: 'weixin',
54
+ chat_id: 'user-1',
55
+ platform_user_id: 'user-1',
56
+ thread_id: 'thread-1',
57
+ last_platform_message_id: 'ctx-1',
58
+ }),
59
+ listAuthorizedUsers: () => [],
60
+ },
61
+ readConfig: async () => ({
62
+ projects: [{
63
+ name: 'default',
64
+ root: '/tmp/project',
65
+ platforms: [{
66
+ type: 'weixin',
67
+ options: {
68
+ token: 'token-1',
69
+ account_id: 'account-1',
70
+ base_url: 'https://weixin.example',
71
+ cdn_base_url: 'https://cdn.example/c2c',
72
+ },
73
+ }],
74
+ }],
75
+ }),
76
+ getWorkspaceRouter: () => ({}),
77
+ eventBus: { emit: () => { }, on: () => () => { } },
78
+ });
79
+ const internals = gateway;
80
+ internals.runtime.set('default', {
81
+ workspaceId: 'default',
82
+ enabled: true,
83
+ status: 'running',
84
+ connected: true,
85
+ accountId: 'account-1',
86
+ });
87
+ const result = await gateway.sendFile('default', {
88
+ path: filePath,
89
+ channelId: 'user-1',
90
+ participantId: 'user-1',
91
+ });
92
+ strict_1.default.equal(uploadUrlRequests.length, 1);
93
+ strict_1.default.equal(uploadUrlRequests[0]?.media_type, 3);
94
+ strict_1.default.equal(uploadUrlRequests[0]?.to_user_id, 'user-1');
95
+ strict_1.default.equal(uploadUrlRequests[0]?.rawsize, Buffer.byteLength('hello weixin'));
96
+ strict_1.default.equal(uploadUrlRequests[0]?.filesize, 16);
97
+ strict_1.default.equal(cdnUploads.length, 1);
98
+ strict_1.default.match(cdnUploads[0]?.url || '', /encrypted_query_param=upload-param-1/);
99
+ strict_1.default.equal(cdnUploads[0]?.size, 16);
100
+ strict_1.default.equal(sentMessages.length, 1);
101
+ const message = sentMessages[0]?.msg;
102
+ strict_1.default.equal(message?.to_user_id, 'user-1');
103
+ strict_1.default.equal(message?.context_token, 'ctx-1');
104
+ strict_1.default.equal(message?.item_list?.[0]?.type, 4);
105
+ strict_1.default.equal(message?.item_list?.[0]?.file_item?.file_name, 'report.txt');
106
+ strict_1.default.equal(message?.item_list?.[0]?.file_item?.len, String(Buffer.byteLength('hello weixin')));
107
+ strict_1.default.equal(message?.item_list?.[0]?.file_item?.media?.encrypt_query_param, 'download-param-1');
108
+ strict_1.default.equal(result.platform, 'weixin');
109
+ strict_1.default.equal(result.channelId, 'user-1');
110
+ strict_1.default.equal(result.fileName, 'report.txt');
111
+ strict_1.default.equal(result.fileSize, Buffer.byteLength('hello weixin'));
112
+ strict_1.default.match(result.messageId, /^openclaw-weixin-/);
113
+ strict_1.default.ok(result.fileKey);
114
+ }
115
+ finally {
116
+ globalThis.fetch = originalFetch;
117
+ (0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
118
+ }
119
+ });
120
+ (0, node_test_1.default)('weixin channel can request a QR code without platform options', async () => {
121
+ const originalFetch = globalThis.fetch;
122
+ const stateDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'weixin-channel-qr-'));
123
+ const requests = [];
124
+ globalThis.fetch = (async (input, init) => {
125
+ requests.push({
126
+ url: String(input),
127
+ headers: new Headers(init?.headers),
128
+ });
129
+ return new Response(JSON.stringify({
130
+ qrcode: 'ticket-1',
131
+ qrcode_img_content: 'https://liteapp.weixin.qq.com/q/test?qrcode=ticket-1&bot_type=3',
132
+ expired: 180,
133
+ }), {
134
+ status: 200,
135
+ headers: { 'Content-Type': 'application/json' },
136
+ });
137
+ });
138
+ try {
139
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
140
+ store: {},
141
+ readConfig: async () => ({
142
+ projects: [
143
+ {
144
+ name: 'default',
145
+ agent: { type: 'localcore-acp', providers: [] },
146
+ platforms: [{ type: 'weixin', options: { state_dir: stateDir } }],
147
+ },
148
+ ],
149
+ }),
150
+ getWorkspaceRouter: () => ({}),
151
+ eventBus: { emit: () => { }, on: () => () => { } },
152
+ });
153
+ const result = await gateway.getQrCode('default');
154
+ strict_1.default.deepEqual(result, {
155
+ ticket: 'ticket-1',
156
+ expiresIn: 180,
157
+ qrCodeUrl: 'https://liteapp.weixin.qq.com/q/test?qrcode=ticket-1&bot_type=3',
158
+ instanceId: 'default',
159
+ displayName: 'WeChat 1',
160
+ });
161
+ strict_1.default.equal(requests[0]?.url, 'https://ilinkai.weixin.qq.com/ilink/bot/get_bot_qrcode?bot_type=3');
162
+ strict_1.default.equal(requests[0]?.headers.has('Authorization'), false);
163
+ strict_1.default.equal(requests[0]?.headers.has('AuthorizationType'), false);
164
+ }
165
+ finally {
166
+ globalThis.fetch = originalFetch;
167
+ (0, node_fs_1.rmSync)(stateDir, { recursive: true, force: true });
168
+ }
169
+ });
170
+ (0, node_test_1.default)('weixin QR confirmation persists credentials and starts authenticated polling', async () => {
171
+ const originalFetch = globalThis.fetch;
172
+ const stateDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), 'weixin-channel-'));
173
+ const requests = [];
174
+ globalThis.fetch = (async (input, init) => {
175
+ const url = String(input);
176
+ requests.push({
177
+ url,
178
+ headers: new Headers(init?.headers),
179
+ });
180
+ if (url.includes('/get_qrcode_status')) {
181
+ return new Response(JSON.stringify({
182
+ status: 'confirmed',
183
+ bot_token: 'bot-token-1',
184
+ baseurl: 'https://ilinkai.weixin.qq.com',
185
+ ilink_bot_id: 'bot-1',
186
+ ilink_user_id: 'user-1',
187
+ }), {
188
+ status: 200,
189
+ headers: { 'Content-Type': 'application/json' },
190
+ });
191
+ }
192
+ return new Promise((_resolve, reject) => {
193
+ init?.signal?.addEventListener('abort', () => reject(new Error('aborted')), { once: true });
194
+ });
195
+ });
196
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
197
+ store: {
198
+ expirePendingPairings: () => { },
199
+ listPendingPairings: () => [],
200
+ listAuthorizedUsers: () => [],
201
+ },
202
+ readConfig: async () => ({
203
+ projects: [
204
+ {
205
+ name: 'default',
206
+ agent: { type: 'localcore-acp', providers: [] },
207
+ platforms: [{ type: 'weixin', options: { state_dir: stateDir } }],
208
+ },
209
+ ],
210
+ }),
211
+ getWorkspaceRouter: () => ({}),
212
+ eventBus: { emit: () => { }, on: () => () => { } },
213
+ });
214
+ try {
215
+ const result = await gateway.checkQrCodeStatus('default', 'ticket-1');
216
+ await new Promise((resolve) => setTimeout(resolve, 0));
217
+ strict_1.default.equal(result.status, 'confirmed');
218
+ const pollingRequest = requests.find((request) => request.url.endsWith('/ilink/bot/getupdates'));
219
+ strict_1.default.equal(pollingRequest?.headers.get('Authorization'), 'Bearer bot-token-1');
220
+ strict_1.default.equal(pollingRequest?.headers.get('AuthorizationType'), 'ilink_bot_token');
221
+ }
222
+ finally {
223
+ await gateway.stop();
224
+ globalThis.fetch = originalFetch;
225
+ (0, node_fs_1.rmSync)(stateDir, { recursive: true, force: true });
226
+ }
227
+ });
228
+ (0, node_test_1.default)('weixin channel keeps multiple bot instances in one workspace isolated', async () => {
229
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
230
+ store: {
231
+ expirePendingPairings: () => { },
232
+ listPendingPairings: () => [],
233
+ listAuthorizedUsers: () => [],
234
+ },
235
+ readConfig: async () => ({
236
+ projects: [
237
+ {
238
+ name: 'default',
239
+ agent: { type: 'localcore-acp', providers: [] },
240
+ platforms: [
241
+ { type: 'weixin', options: { instance_id: 'wx-a', account_id: 'account-a' } },
242
+ { type: 'weixin', options: { instance_id: 'wx-b', account_id: 'account-b' } },
243
+ ],
244
+ },
245
+ ],
246
+ }),
247
+ getWorkspaceRouter: () => ({}),
248
+ eventBus: { emit: () => { }, on: () => () => { } },
249
+ });
250
+ await gateway.refreshBindings();
251
+ const statuses = gateway.listStatuses();
252
+ strict_1.default.deepEqual(statuses.map((status) => [status.workspaceId, status.instanceId, status.appId, status.status]), [
253
+ ['default', 'wx-a', 'account-a', 'stopped'],
254
+ ['default', 'wx-b', 'account-b', 'stopped'],
255
+ ]);
256
+ });
257
+ (0, node_test_1.default)('weixin inbound message handling is idempotent by message identity', async () => {
258
+ const sentThreadMessages = [];
259
+ const users = new Map();
260
+ const threadBindings = new Map();
261
+ const bindingKey = 'default:chat-1:user-1';
262
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
263
+ store: {
264
+ expirePendingPairings: () => { },
265
+ listPendingPairings: () => [],
266
+ listPairingRequests: () => [],
267
+ listAuthorizedUsers: () => [...users.values()],
268
+ getAuthorizedUser: (_workspaceId, platformUserId) => users.get(platformUserId),
269
+ createAuthorizedUser: (user) => users.set(user.platform_user_id, user),
270
+ updateAuthorizedUserThread: (_workspaceId, platformUserId, threadId) => {
271
+ users.set(platformUserId, { ...users.get(platformUserId), thread_id: threadId });
272
+ },
273
+ getPlatformThreadBinding: () => threadBindings.get(bindingKey),
274
+ upsertPlatformThreadBinding: (binding) => threadBindings.set(bindingKey, binding),
275
+ updatePlatformThreadMessageId: (_workspaceId, _chatId, _platformUserId, messageId) => {
276
+ threadBindings.set(bindingKey, { ...threadBindings.get(bindingKey), last_platform_message_id: messageId });
277
+ },
278
+ getLatestRunForThread: () => null,
279
+ },
280
+ readConfig: async () => ({
281
+ projects: [
282
+ {
283
+ name: 'default',
284
+ agent: { type: 'localcore-acp', providers: [] },
285
+ platforms: [{ type: 'weixin', options: {} }],
286
+ },
287
+ ],
288
+ }),
289
+ getWorkspaceRouter: () => ({
290
+ createThread: async () => ({ id: 'thread-1' }),
291
+ getThreadSessionKey: (threadId) => `session:${threadId}`,
292
+ sendThreadMessage: async (_threadId, text) => {
293
+ sentThreadMessages.push(text);
294
+ return { runId: 'run-1' };
295
+ },
296
+ }),
297
+ eventBus: { emit: () => { }, on: () => () => { } },
298
+ });
299
+ const input = {
300
+ workspaceId: 'default',
301
+ platformUserId: 'user-1',
302
+ chatId: 'chat-1',
303
+ displayName: 'User',
304
+ text: 'hello',
305
+ messageId: 'msg-1',
306
+ contextToken: 'ctx-1',
307
+ };
308
+ await gateway.handleInboundMessage(input);
309
+ await gateway.handleInboundMessage(input);
310
+ strict_1.default.equal(sentThreadMessages.length, 1);
311
+ strict_1.default.match(sentThreadMessages[0] || '', /hello/);
312
+ });
313
+ (0, node_test_1.default)('weixin inbound messages create a chat binding when an authorized user has an old direct thread', async () => {
314
+ const bindings = [];
315
+ const sentThreadMessages = [];
316
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
317
+ store: {
318
+ expirePendingPairings: () => { },
319
+ listPendingPairings: () => [],
320
+ listPairingRequests: () => [],
321
+ listAuthorizedUsers: () => [],
322
+ getAuthorizedUser: () => ({
323
+ id: 'auth-1',
324
+ workspace_id: 'default',
325
+ platform: 'weixin',
326
+ platform_user_id: 'user-1',
327
+ chat_id: 'old-direct-chat',
328
+ display_name: 'User',
329
+ thread_id: 'old-thread-1',
330
+ }),
331
+ getPlatformThreadBinding: () => undefined,
332
+ updateAuthorizedUserThread: () => { },
333
+ upsertPlatformThreadBinding: (binding) => bindings.push(binding),
334
+ updatePlatformThreadMessageId: (_workspaceId, _chatId, _platformUserId, messageId) => {
335
+ bindings[0] = { ...bindings[0], last_platform_message_id: messageId };
336
+ },
337
+ getLatestRunForThread: () => null,
338
+ },
339
+ readConfig: async () => ({
340
+ projects: [
341
+ {
342
+ name: 'default',
343
+ agent: { type: 'localcore-acp', providers: [] },
344
+ platforms: [{ type: 'weixin', options: {} }],
345
+ },
346
+ ],
347
+ }),
348
+ getWorkspaceRouter: () => ({
349
+ createThread: async () => ({ id: 'group-thread-1' }),
350
+ getThreadSessionKey: (threadId) => `session:${threadId}`,
351
+ sendThreadMessage: async (threadId, text) => {
352
+ sentThreadMessages.push({ threadId, text });
353
+ return { runId: 'run-1' };
354
+ },
355
+ }),
356
+ eventBus: { emit: () => { }, on: () => () => { } },
357
+ });
358
+ await gateway.handleInboundMessage({
359
+ workspaceId: 'default',
360
+ platformUserId: 'user-1',
361
+ chatId: 'new-group-chat',
362
+ displayName: 'User',
363
+ text: 'hello',
364
+ messageId: 'msg-1',
365
+ contextToken: 'ctx-1',
366
+ });
367
+ strict_1.default.equal(bindings.length, 1);
368
+ strict_1.default.equal(bindings[0]?.platform, 'weixin');
369
+ strict_1.default.equal(bindings[0]?.chat_id, 'new-group-chat');
370
+ strict_1.default.equal(bindings[0]?.platform_user_id, 'user-1');
371
+ strict_1.default.equal(bindings[0]?.thread_id, 'group-thread-1');
372
+ strict_1.default.equal(bindings[0]?.last_platform_message_id, 'ctx-1');
373
+ strict_1.default.equal(sentThreadMessages[0]?.threadId, 'group-thread-1');
374
+ strict_1.default.match(sentThreadMessages[0]?.text || '', /hello/);
375
+ });
376
+ (0, node_test_1.default)('weixin downloaded file attachment becomes a structured file content part', () => {
377
+ const part = (0, local_core_weixin_gateway_js_1.createWeixinAttachmentContentPart)({
378
+ path: '/tmp/report.pdf',
379
+ kind: 'file',
380
+ name: 'report.pdf',
381
+ });
382
+ strict_1.default.deepEqual(part, {
383
+ type: 'file',
384
+ path: '/tmp/report.pdf',
385
+ fileName: 'report.pdf',
386
+ });
387
+ });
388
+ (0, node_test_1.default)('weixin downloaded image attachment keeps image data content part', () => {
389
+ const part = (0, local_core_weixin_gateway_js_1.createWeixinAttachmentContentPart)({
390
+ path: '/tmp/image.png',
391
+ kind: 'image',
392
+ name: 'image.png',
393
+ data: 'aW1n',
394
+ mimeType: 'image/png',
395
+ });
396
+ strict_1.default.deepEqual(part, {
397
+ type: 'image',
398
+ data: 'aW1n',
399
+ mimeType: 'image/png',
400
+ fileName: 'image.png',
401
+ });
402
+ });
403
+ (0, node_test_1.default)('weixin bridge skips duplicate rendered replies', async () => {
404
+ const originalFetch = globalThis.fetch;
405
+ const sentBodies = [];
406
+ globalThis.fetch = (async (_input, init) => {
407
+ sentBodies.push(JSON.parse(String(init?.body || '{}')));
408
+ return new Response(JSON.stringify({ errcode: 0 }), {
409
+ status: 200,
410
+ headers: { 'Content-Type': 'application/json' },
411
+ });
412
+ });
413
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
414
+ store: {
415
+ getPlatformThreadBinding: () => ({
416
+ workspace_id: 'default',
417
+ platform: 'weixin',
418
+ chat_id: 'chat-1',
419
+ platform_user_id: 'user-1',
420
+ thread_id: 'thread-1',
421
+ last_platform_message_id: 'ctx-1',
422
+ }),
423
+ },
424
+ readConfig: async () => ({
425
+ projects: [
426
+ {
427
+ name: 'default',
428
+ agent: { type: 'localcore-acp', providers: [] },
429
+ platforms: [{ type: 'weixin', options: { token: 'bot-token-1' } }],
430
+ },
431
+ ],
432
+ }),
433
+ getWorkspaceRouter: () => ({}),
434
+ eventBus: { emit: () => { }, on: () => () => { } },
435
+ });
436
+ const internals = gateway;
437
+ internals.runtime.set('default', {
438
+ workspaceId: 'default',
439
+ enabled: true,
440
+ status: 'running',
441
+ connected: true,
442
+ accountId: 'bot-1',
443
+ });
444
+ internals.threadRouting.set('session:thread-1', {
445
+ workspaceId: 'default',
446
+ platformUserId: 'user-1',
447
+ chatId: 'chat-1',
448
+ threadId: 'thread-1',
449
+ });
450
+ try {
451
+ await gateway.onBridgeEvent({ type: 'update_message', sessionKey: 'session:thread-1', content: 'same reply' });
452
+ await gateway.onBridgeEvent({ type: 'reply', sessionKey: 'session:thread-1', content: 'same reply' });
453
+ await gateway.onBridgeEvent({ type: 'typing_stop', sessionKey: 'session:thread-1' });
454
+ await gateway.onBridgeEvent({ type: 'reply', sessionKey: 'session:thread-1', content: 'same reply' });
455
+ strict_1.default.equal(sentBodies.length, 1);
456
+ strict_1.default.equal(sentBodies[0]?.msg?.item_list?.[0]?.text_item?.text, 'same reply');
457
+ }
458
+ finally {
459
+ globalThis.fetch = originalFetch;
460
+ }
461
+ });
462
+ (0, node_test_1.default)('weixin bridge keeps context replies to one truncated text message', async () => {
463
+ const originalFetch = globalThis.fetch;
464
+ const sentBodies = [];
465
+ globalThis.fetch = (async (_input, init) => {
466
+ sentBodies.push(JSON.parse(String(init?.body || '{}')));
467
+ return new Response(JSON.stringify({ errcode: 0 }), {
468
+ status: 200,
469
+ headers: { 'Content-Type': 'application/json' },
470
+ });
471
+ });
472
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
473
+ store: {
474
+ getPlatformThreadBinding: () => ({
475
+ workspace_id: 'default',
476
+ platform: 'weixin',
477
+ chat_id: 'chat-1',
478
+ platform_user_id: 'user-1',
479
+ thread_id: 'thread-1',
480
+ last_platform_message_id: 'ctx-1',
481
+ }),
482
+ },
483
+ readConfig: async () => ({
484
+ projects: [
485
+ {
486
+ name: 'default',
487
+ agent: { type: 'localcore-acp', providers: [] },
488
+ platforms: [{ type: 'weixin', options: { token: 'bot-token-1' } }],
489
+ },
490
+ ],
491
+ }),
492
+ getWorkspaceRouter: () => ({}),
493
+ eventBus: { emit: () => { }, on: () => () => { } },
494
+ });
495
+ const internals = gateway;
496
+ internals.runtime.set('default', {
497
+ workspaceId: 'default',
498
+ enabled: true,
499
+ status: 'running',
500
+ connected: true,
501
+ accountId: 'bot-1',
502
+ });
503
+ internals.threadRouting.set('session:thread-1', {
504
+ workspaceId: 'default',
505
+ platformUserId: 'user-1',
506
+ chatId: 'chat-1',
507
+ threadId: 'thread-1',
508
+ });
509
+ try {
510
+ await gateway.onBridgeEvent({
511
+ type: 'reply',
512
+ sessionKey: 'session:thread-1',
513
+ content: Array.from({ length: 80 }, (_, index) => `第 ${index + 1} 行:这是一段用于测试微信长文本切分的内容。`).join('\n\n'),
514
+ });
515
+ strict_1.default.equal(sentBodies.length, 1);
516
+ strict_1.default.equal(sentBodies[0]?.msg?.context_token, 'ctx-1');
517
+ for (const body of sentBodies) {
518
+ const text = body?.msg?.item_list?.[0]?.text_item?.text || '';
519
+ strict_1.default.ok(Buffer.byteLength(text, 'utf-8') <= 3500);
520
+ strict_1.default.match(text, /内容过长,已截断以保证微信送达/);
521
+ strict_1.default.equal(body?.base_info?.channel_version, '2.1.7');
522
+ strict_1.default.equal(body?.msg?.from_user_id, '');
523
+ strict_1.default.match(body?.msg?.client_id || '', /^openclaw-weixin-/);
524
+ }
525
+ }
526
+ finally {
527
+ globalThis.fetch = originalFetch;
528
+ }
529
+ });
530
+ (0, node_test_1.default)('weixin bridge sends protocol-compatible final reply payload', async () => {
531
+ const originalFetch = globalThis.fetch;
532
+ const sentRequests = [];
533
+ globalThis.fetch = (async (_input, init) => {
534
+ const body = JSON.parse(String(init?.body || '{}'));
535
+ sentRequests.push({ body, headers: new Headers(init?.headers) });
536
+ return new Response(JSON.stringify({ ret: 0 }), {
537
+ status: 200,
538
+ headers: { 'Content-Type': 'application/json' },
539
+ });
540
+ });
541
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
542
+ store: {
543
+ getPlatformThreadBinding: () => ({
544
+ workspace_id: 'default',
545
+ platform: 'weixin',
546
+ chat_id: 'chat-1',
547
+ platform_user_id: 'user-1',
548
+ thread_id: 'thread-1',
549
+ last_platform_message_id: 'ctx-1',
550
+ }),
551
+ },
552
+ readConfig: async () => ({
553
+ projects: [
554
+ {
555
+ name: 'default',
556
+ agent: { type: 'localcore-acp', providers: [] },
557
+ platforms: [{ type: 'weixin', options: { token: 'bot-token-1' } }],
558
+ },
559
+ ],
560
+ }),
561
+ getWorkspaceRouter: () => ({}),
562
+ eventBus: { emit: () => { }, on: () => () => { } },
563
+ });
564
+ const internals = gateway;
565
+ internals.runtime.set('default', {
566
+ workspaceId: 'default',
567
+ enabled: true,
568
+ status: 'running',
569
+ connected: true,
570
+ accountId: 'bot-1',
571
+ });
572
+ internals.threadRouting.set('session:thread-1', {
573
+ workspaceId: 'default',
574
+ platformUserId: 'user-1',
575
+ chatId: 'chat-1',
576
+ threadId: 'thread-1',
577
+ });
578
+ try {
579
+ await gateway.onBridgeEvent({ type: 'reply', sessionKey: 'session:thread-1', content: 'final reply' });
580
+ strict_1.default.equal(sentRequests.length, 1);
581
+ strict_1.default.equal(sentRequests[0]?.body?.msg?.context_token, 'ctx-1');
582
+ strict_1.default.equal(sentRequests[0]?.body?.msg?.from_user_id, '');
583
+ strict_1.default.equal(sentRequests[0]?.body?.msg?.message_state, 2);
584
+ strict_1.default.equal(sentRequests[0]?.body?.base_info?.channel_version, '2.1.7');
585
+ strict_1.default.match(sentRequests[0]?.body?.msg?.client_id || '', /^openclaw-weixin-/);
586
+ strict_1.default.equal(sentRequests[0]?.body?.msg?.item_list?.[0]?.text_item?.text, 'final reply');
587
+ strict_1.default.equal(sentRequests[0]?.headers.get('iLink-App-Id'), 'bot');
588
+ strict_1.default.equal(sentRequests[0]?.headers.get('iLink-App-ClientVersion'), '131335');
589
+ strict_1.default.equal(sentRequests[0]?.headers.get('AuthorizationType'), 'ilink_bot_token');
590
+ strict_1.default.equal(sentRequests[0]?.headers.get('Authorization'), 'Bearer bot-token-1');
591
+ }
592
+ finally {
593
+ globalThis.fetch = originalFetch;
594
+ }
595
+ });
596
+ (0, node_test_1.default)('weixin bridge sends status events in real time', async () => {
597
+ const originalFetch = globalThis.fetch;
598
+ const sentBodies = [];
599
+ globalThis.fetch = (async (_input, init) => {
600
+ sentBodies.push(JSON.parse(String(init?.body || '{}')));
601
+ return new Response(JSON.stringify({ ret: 0 }), {
602
+ status: 200,
603
+ headers: { 'Content-Type': 'application/json' },
604
+ });
605
+ });
606
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
607
+ store: {
608
+ getPlatformThreadBinding: () => ({
609
+ workspace_id: 'default',
610
+ platform: 'weixin',
611
+ chat_id: 'chat-1',
612
+ platform_user_id: 'user-1',
613
+ thread_id: 'thread-1',
614
+ last_platform_message_id: 'ctx-1',
615
+ }),
616
+ },
617
+ readConfig: async () => ({
618
+ projects: [
619
+ {
620
+ name: 'default',
621
+ agent: { type: 'localcore-acp', providers: [] },
622
+ platforms: [{ type: 'weixin', options: { token: 'bot-token-1' } }],
623
+ },
624
+ ],
625
+ }),
626
+ getWorkspaceRouter: () => ({}),
627
+ eventBus: { emit: () => { }, on: () => () => { } },
628
+ });
629
+ const internals = gateway;
630
+ internals.runtime.set('default', {
631
+ workspaceId: 'default',
632
+ enabled: true,
633
+ status: 'running',
634
+ connected: true,
635
+ accountId: 'bot-1',
636
+ });
637
+ internals.threadRouting.set('session:thread-1', {
638
+ workspaceId: 'default',
639
+ platformUserId: 'user-1',
640
+ chatId: 'chat-1',
641
+ threadId: 'thread-1',
642
+ });
643
+ try {
644
+ await gateway.onBridgeEvent({ type: 'status', sessionKey: 'session:thread-1', content: '正在检查桌面文件' });
645
+ strict_1.default.equal(sentBodies.length, 1);
646
+ strict_1.default.equal(sentBodies[0]?.msg?.context_token, 'ctx-1');
647
+ strict_1.default.equal(sentBodies[0]?.msg?.message_state, 2);
648
+ strict_1.default.equal(sentBodies[0]?.msg?.item_list?.[0]?.text_item?.text, '正在检查桌面文件');
649
+ }
650
+ finally {
651
+ globalThis.fetch = originalFetch;
652
+ }
653
+ });
654
+ (0, node_test_1.default)('weixin bridge sends tool progress in real time before final reply', async () => {
655
+ const originalFetch = globalThis.fetch;
656
+ const sentBodies = [];
657
+ globalThis.fetch = (async (_input, init) => {
658
+ sentBodies.push(JSON.parse(String(init?.body || '{}')));
659
+ return new Response(JSON.stringify({ ret: 0 }), {
660
+ status: 200,
661
+ headers: { 'Content-Type': 'application/json' },
662
+ });
663
+ });
664
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
665
+ store: {
666
+ getPlatformThreadBinding: () => ({
667
+ workspace_id: 'default',
668
+ platform: 'weixin',
669
+ chat_id: 'chat-1',
670
+ platform_user_id: 'user-1',
671
+ thread_id: 'thread-1',
672
+ last_platform_message_id: 'ctx-1',
673
+ }),
674
+ },
675
+ readConfig: async () => ({
676
+ projects: [
677
+ {
678
+ name: 'default',
679
+ agent: { type: 'localcore-acp', providers: [] },
680
+ platforms: [{ type: 'weixin', options: { token: 'bot-token-1' } }],
681
+ },
682
+ ],
683
+ }),
684
+ getWorkspaceRouter: () => ({}),
685
+ eventBus: { emit: () => { }, on: () => () => { } },
686
+ });
687
+ const internals = gateway;
688
+ internals.runtime.set('default', {
689
+ workspaceId: 'default',
690
+ enabled: true,
691
+ status: 'running',
692
+ connected: true,
693
+ accountId: 'bot-1',
694
+ });
695
+ internals.threadRouting.set('session:thread-1', {
696
+ workspaceId: 'default',
697
+ platformUserId: 'user-1',
698
+ chatId: 'chat-1',
699
+ threadId: 'thread-1',
700
+ });
701
+ try {
702
+ await gateway.onBridgeEvent({
703
+ type: 'reply',
704
+ sessionKey: 'session:thread-1',
705
+ bridgeKind: 'tool',
706
+ content: 'list desktop',
707
+ toolCall: {
708
+ name: 'list desktop',
709
+ status: 'running',
710
+ output: '',
711
+ },
712
+ });
713
+ await gateway.onBridgeEvent({ type: 'reply', sessionKey: 'session:thread-1', content: 'final reply' });
714
+ strict_1.default.equal(sentBodies.length, 2);
715
+ strict_1.default.equal(sentBodies[0]?.msg?.message_state, 2);
716
+ strict_1.default.equal(sentBodies[1]?.msg?.message_state, 2);
717
+ strict_1.default.equal(sentBodies[0]?.msg?.context_token, 'ctx-1');
718
+ strict_1.default.equal(sentBodies[1]?.msg?.context_token, 'ctx-1');
719
+ strict_1.default.equal(sentBodies[0]?.msg?.item_list?.[0]?.text_item?.text, '**处理中**\n• list desktop - running');
720
+ strict_1.default.equal(sentBodies[1]?.msg?.item_list?.[0]?.text_item?.text, 'final reply');
721
+ }
722
+ finally {
723
+ globalThis.fetch = originalFetch;
724
+ }
725
+ });
726
+ (0, node_test_1.default)('weixin bridge skips completed tool result updates but keeps final reply', async () => {
727
+ const originalFetch = globalThis.fetch;
728
+ const sentBodies = [];
729
+ globalThis.fetch = (async (_input, init) => {
730
+ sentBodies.push(JSON.parse(String(init?.body || '{}')));
731
+ return new Response(JSON.stringify({ errcode: 0 }), {
732
+ status: 200,
733
+ headers: { 'Content-Type': 'application/json' },
734
+ });
735
+ });
736
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
737
+ store: {
738
+ getPlatformThreadBinding: () => ({
739
+ workspace_id: 'default',
740
+ platform: 'weixin',
741
+ chat_id: 'chat-1',
742
+ platform_user_id: 'user-1',
743
+ thread_id: 'thread-1',
744
+ last_platform_message_id: 'ctx-1',
745
+ }),
746
+ },
747
+ readConfig: async () => ({
748
+ projects: [
749
+ {
750
+ name: 'default',
751
+ agent: { type: 'localcore-acp', providers: [] },
752
+ platforms: [{ type: 'weixin', options: { token: 'bot-token-1' } }],
753
+ },
754
+ ],
755
+ }),
756
+ getWorkspaceRouter: () => ({}),
757
+ eventBus: { emit: () => { }, on: () => () => { } },
758
+ });
759
+ const internals = gateway;
760
+ internals.runtime.set('default', {
761
+ workspaceId: 'default',
762
+ enabled: true,
763
+ status: 'running',
764
+ connected: true,
765
+ accountId: 'bot-1',
766
+ });
767
+ internals.threadRouting.set('session:thread-1', {
768
+ workspaceId: 'default',
769
+ platformUserId: 'user-1',
770
+ chatId: 'chat-1',
771
+ threadId: 'thread-1',
772
+ });
773
+ try {
774
+ await gateway.onBridgeEvent({
775
+ type: 'reply',
776
+ sessionKey: 'session:thread-1',
777
+ bridgeKind: 'tool',
778
+ content: 'Tool update completed',
779
+ toolCall: {
780
+ name: 'Tool update',
781
+ status: 'completed',
782
+ output: '/Users/mochuxian/Desktop has many files and this result should not be sent',
783
+ },
784
+ });
785
+ await gateway.onBridgeEvent({ type: 'reply', sessionKey: 'session:thread-1', content: 'final reply' });
786
+ strict_1.default.equal(sentBodies.length, 1);
787
+ strict_1.default.equal(sentBodies[0]?.msg?.message_state, 2);
788
+ strict_1.default.equal(sentBodies[0]?.msg?.item_list?.[0]?.text_item?.text, 'final reply');
789
+ }
790
+ finally {
791
+ globalThis.fetch = originalFetch;
792
+ }
793
+ });
794
+ (0, node_test_1.default)('weixin bridge keeps failed tool update status without execution details', async () => {
795
+ const originalFetch = globalThis.fetch;
796
+ const sentBodies = [];
797
+ globalThis.fetch = (async (_input, init) => {
798
+ sentBodies.push(JSON.parse(String(init?.body || '{}')));
799
+ return new Response(JSON.stringify({ errcode: 0 }), {
800
+ status: 200,
801
+ headers: { 'Content-Type': 'application/json' },
802
+ });
803
+ });
804
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
805
+ store: {
806
+ getPlatformThreadBinding: () => ({
807
+ workspace_id: 'default',
808
+ platform: 'weixin',
809
+ chat_id: 'chat-1',
810
+ platform_user_id: 'user-1',
811
+ thread_id: 'thread-1',
812
+ last_platform_message_id: 'ctx-1',
813
+ }),
814
+ },
815
+ readConfig: async () => ({
816
+ projects: [
817
+ {
818
+ name: 'default',
819
+ agent: { type: 'localcore-acp', providers: [] },
820
+ platforms: [{ type: 'weixin', options: { token: 'bot-token-1' } }],
821
+ },
822
+ ],
823
+ }),
824
+ getWorkspaceRouter: () => ({}),
825
+ eventBus: { emit: () => { }, on: () => () => { } },
826
+ });
827
+ const internals = gateway;
828
+ internals.runtime.set('default', {
829
+ workspaceId: 'default',
830
+ enabled: true,
831
+ status: 'running',
832
+ connected: true,
833
+ accountId: 'bot-1',
834
+ });
835
+ internals.threadRouting.set('session:thread-1', {
836
+ workspaceId: 'default',
837
+ platformUserId: 'user-1',
838
+ chatId: 'chat-1',
839
+ threadId: 'thread-1',
840
+ });
841
+ try {
842
+ await gateway.onBridgeEvent({
843
+ type: 'reply',
844
+ sessionKey: 'session:thread-1',
845
+ bridgeKind: 'tool',
846
+ content: 'Tool update failed',
847
+ toolCall: {
848
+ name: 'Tool update',
849
+ status: 'failed',
850
+ output: 'stack trace and command output should not be sent',
851
+ },
852
+ });
853
+ strict_1.default.equal(sentBodies.length, 1);
854
+ strict_1.default.equal(sentBodies[0]?.msg?.item_list?.[0]?.text_item?.text, '**处理中**\n• Tool update - failed');
855
+ }
856
+ finally {
857
+ globalThis.fetch = originalFetch;
858
+ }
859
+ });
860
+ (0, node_test_1.default)('weixin bridge folds progress after nine context sends and preserves final reply', async () => {
861
+ const originalFetch = globalThis.fetch;
862
+ const sentBodies = [];
863
+ globalThis.fetch = (async (_input, init) => {
864
+ sentBodies.push(JSON.parse(String(init?.body || '{}')));
865
+ return new Response(JSON.stringify({ ret: 0 }), {
866
+ status: 200,
867
+ headers: { 'Content-Type': 'application/json' },
868
+ });
869
+ });
870
+ const gateway = new local_core_weixin_gateway_js_1.LocalCoreWeixinGateway({
871
+ store: {
872
+ getPlatformThreadBinding: () => ({
873
+ workspace_id: 'default',
874
+ platform: 'weixin',
875
+ chat_id: 'chat-1',
876
+ platform_user_id: 'user-1',
877
+ thread_id: 'thread-1',
878
+ last_platform_message_id: 'ctx-1',
879
+ }),
880
+ },
881
+ readConfig: async () => ({
882
+ projects: [
883
+ {
884
+ name: 'default',
885
+ agent: { type: 'localcore-acp', providers: [] },
886
+ platforms: [{ type: 'weixin', options: { token: 'bot-token-1' } }],
887
+ },
888
+ ],
889
+ }),
890
+ getWorkspaceRouter: () => ({}),
891
+ eventBus: { emit: () => { }, on: () => () => { } },
892
+ });
893
+ const internals = gateway;
894
+ internals.runtime.set('default', {
895
+ workspaceId: 'default',
896
+ enabled: true,
897
+ status: 'running',
898
+ connected: true,
899
+ accountId: 'bot-1',
900
+ });
901
+ internals.threadRouting.set('session:thread-1', {
902
+ workspaceId: 'default',
903
+ platformUserId: 'user-1',
904
+ chatId: 'chat-1',
905
+ threadId: 'thread-1',
906
+ });
907
+ try {
908
+ for (let index = 1; index <= 12; index += 1) {
909
+ await gateway.onBridgeEvent({
910
+ type: 'reply',
911
+ sessionKey: 'session:thread-1',
912
+ bridgeKind: 'status',
913
+ content: `🔧 tool ${index}`,
914
+ });
915
+ }
916
+ await gateway.onBridgeEvent({ type: 'reply', sessionKey: 'session:thread-1', content: 'final reply' });
917
+ strict_1.default.equal(sentBodies.length, 10);
918
+ strict_1.default.equal(sentBodies[0]?.msg?.item_list?.[0]?.text_item?.text, '**处理中**\n• 🔧 tool 1');
919
+ strict_1.default.match(sentBodies[8]?.msg?.item_list?.[0]?.text_item?.text, /🔧 tool 9/);
920
+ strict_1.default.doesNotMatch(sentBodies.map((body) => body?.msg?.item_list?.[0]?.text_item?.text || '').join('\n'), /🔧 tool 10|🔧 tool 11|🔧 tool 12/);
921
+ strict_1.default.match(sentBodies[9]?.msg?.item_list?.[0]?.text_item?.text, /已省略 3 条过程消息/);
922
+ strict_1.default.match(sentBodies[9]?.msg?.item_list?.[0]?.text_item?.text, /final reply/);
923
+ }
924
+ finally {
925
+ globalThis.fetch = originalFetch;
926
+ }
927
+ });