@kafca/agentdock 0.1.43 → 0.1.45

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 (119) hide show
  1. package/README.md +77 -11
  2. package/bin/agentdock.mjs +5 -5
  3. package/dist/renderer/assets/{Badge-CbG6yRSm.js → Badge-BIojwD6s.js} +1 -1
  4. package/dist/renderer/assets/{Chat-BvNNWGGX.js → Chat-wjBR3XEX.js} +1 -1
  5. package/dist/renderer/assets/{ChatMarkdown-Ct-PvJB8.js → ChatMarkdown-DqszsPB_.js} +1 -1
  6. package/dist/renderer/assets/Config-BDCHs5A2.js +21 -0
  7. package/dist/renderer/assets/{CronList-Dj_3muVG.js → CronList-CEWQYTov.js} +1 -1
  8. package/dist/renderer/assets/Dashboard-DrWftfk3.js +26 -0
  9. package/dist/renderer/assets/{EmptyState-Bn2Qh14U.js → EmptyState-BqCx1oWr.js} +1 -1
  10. package/dist/renderer/assets/{KnowledgeDetail-Bsz6Ou41.js → KnowledgeDetail-BwK8Gpfa.js} +2 -2
  11. package/dist/renderer/assets/{KnowledgeHome-Cgj4XFXn.js → KnowledgeHome-hhDTZMDO.js} +3 -3
  12. package/dist/renderer/assets/{Logs-Dp-NF6GN.js → Logs-C9xPEhFT.js} +1 -1
  13. package/dist/renderer/assets/{Modal-B6YPPBN6.js → Modal-D2U4r2V0.js} +1 -1
  14. package/dist/renderer/assets/{MonitorList-xEZWg460.js → MonitorList-CayKbfwJ.js} +1 -1
  15. package/dist/renderer/assets/{Page-Bn7RjGOS.js → Page-urf5GcT_.js} +1 -1
  16. package/dist/renderer/assets/ProjectDetail-B1GyAr7a.js +11 -0
  17. package/dist/renderer/assets/{ProjectList-BDk5_gqY.js → ProjectList-BoipQypE.js} +1 -1
  18. package/dist/renderer/assets/{Select-xshfMIEQ.js → Select-CAscWpy5.js} +1 -1
  19. package/dist/renderer/assets/{SessionChat-BkHy90z3.js → SessionChat-BAVy5221.js} +1 -1
  20. package/dist/renderer/assets/{SessionList-BZyXT92L.js → SessionList-BZpuMvh0.js} +1 -1
  21. package/dist/renderer/assets/{ThreadChat-DUOIyXrS.js → ThreadChat-JX7m9biG.js} +1 -1
  22. package/dist/renderer/assets/Workspace-CFW8otjU.js +14 -0
  23. package/dist/renderer/assets/{arrow-left-C54ZI-AS.js → arrow-left-7HrNuS-V.js} +1 -1
  24. package/dist/renderer/assets/{book-open-Dl7GO0zn.js → book-open-BDoAu82s.js} +1 -1
  25. package/dist/renderer/assets/{check-CiqgtrOL.js → check-DGlyq4xX.js} +1 -1
  26. package/dist/renderer/assets/{chevron-down-DTL2OuVh.js → chevron-down-Bb_3EHq9.js} +1 -1
  27. package/dist/renderer/assets/{circle-DaVKfa3P.js → circle-DIpqZnYw.js} +1 -1
  28. package/dist/renderer/assets/desktop-Ci1fnRDR.js +1 -0
  29. package/dist/renderer/assets/{desktop-C6q6wBae.js → desktop-Do_UrC-o.js} +5 -5
  30. package/dist/renderer/assets/en-CmAr_0e6.js +1 -0
  31. package/dist/renderer/assets/es-Cs9x52ca.js +1 -0
  32. package/dist/renderer/assets/{funnel-CrmnLMJI.js → funnel-Bbf0ERLP.js} +1 -1
  33. package/dist/renderer/assets/{heart-BMfQI7y_.js → heart-CGqU-qfw.js} +1 -1
  34. package/dist/renderer/assets/{index-nreiKY1f.js → index-Df-Trovt.js} +2 -2
  35. package/dist/renderer/assets/{index-HPNb1zfx.js → index-DwhTjLA1.js} +36 -36
  36. package/dist/renderer/assets/index-RaTga2BC.css +11 -0
  37. package/dist/renderer/assets/ja-DtvtBRTW.js +1 -0
  38. package/dist/renderer/assets/{pencil-wTQ4l465.js → pencil-CHiy1MvK.js} +1 -1
  39. package/dist/renderer/assets/{play-BK--MXmw.js → play-CGYnmC4Q.js} +1 -1
  40. package/dist/renderer/assets/{plug-BIx9fjip.js → plug-Mexu4gyV.js} +1 -1
  41. package/dist/renderer/assets/{plus-Dn7JFWQ8.js → plus-B_Q5-Oj7.js} +1 -1
  42. package/dist/renderer/assets/projects-C6bbkC-O.js +1 -0
  43. package/dist/renderer/assets/{save-CC3zOSWb.js → save-CJ_QFPw0.js} +1 -1
  44. package/dist/renderer/assets/{search-CDTy9Czu.js → search-D8Dkhsyq.js} +1 -1
  45. package/dist/renderer/assets/{server-DtQk3t2a.js → server-Cd96R7WL.js} +1 -1
  46. package/dist/renderer/assets/{session-utils-4ep5D66T.js → session-utils-BNUbQBfF.js} +1 -1
  47. package/dist/renderer/assets/{sessions-Dor1LteH.js → sessions-BACfV8s1.js} +1 -1
  48. package/dist/renderer/assets/{shield-check-Cz2LpU6r.js → shield-check-Cnn-yQUA.js} +1 -1
  49. package/dist/renderer/assets/{trash-2-C2EmWFZV.js → trash-2-oWCxwQ9D.js} +1 -1
  50. package/dist/renderer/assets/{user-I8B-cs5A.js → user-BzE9BwsY.js} +1 -1
  51. package/dist/renderer/assets/{wifi-off-CPevTQJn.js → wifi-off-DHxnNkdY.js} +1 -1
  52. package/dist/renderer/assets/zh-CcJ9vBZj.js +1 -0
  53. package/dist/renderer/assets/zh-TW-elI72AC1.js +1 -0
  54. package/dist/renderer/index.html +2 -2
  55. package/dist-electron/electron/main.js +26 -1
  56. package/dist-electron/packages/core-sdk/src/index.js +28 -0
  57. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-backend.js +111 -62
  58. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-session-coordinator.js +80 -7
  59. package/dist-electron/services/local-ai-core/src/acp/local-core-acp-transport.js +78 -5
  60. package/dist-electron/services/local-ai-core/src/acp/store/external-store.js +98 -0
  61. package/dist-electron/services/local-ai-core/src/acp/store/local-core-acp-store.js +33 -0
  62. package/dist-electron/services/local-ai-core/src/acp/store/model-provider-store.js +88 -0
  63. package/dist-electron/services/local-ai-core/src/acp/store/schema.js +43 -0
  64. package/dist-electron/services/local-ai-core/src/agents/pi/launch.js +2 -119
  65. package/dist-electron/services/local-ai-core/src/agents/pi/provider-materializer.js +148 -0
  66. package/dist-electron/services/local-ai-core/src/channel/lark/config.js +2 -0
  67. package/dist-electron/services/local-ai-core/src/channel/lark/inbound.js +114 -0
  68. package/dist-electron/services/local-ai-core/src/channel/lark/local-core-lark-gateway.js +117 -75
  69. package/dist-electron/services/local-ai-core/src/channel/shared/session-command-runtime.js +4 -2
  70. package/dist-electron/services/local-ai-core/src/channel/shared/thread-routing.js +34 -0
  71. package/dist-electron/services/local-ai-core/src/channel/weixin/local-core-weixin-gateway.js +151 -47
  72. package/dist-electron/services/local-ai-core/src/execution/agent-execution-backend.js +56 -0
  73. package/dist-electron/services/local-ai-core/src/kernel/bootstrap.js +18 -57
  74. package/dist-electron/services/local-ai-core/src/kernel/local-core-errors.js +255 -0
  75. package/dist-electron/services/local-ai-core/src/plugins/builtin/catalog.js +78 -0
  76. package/dist-electron/services/local-ai-core/src/router/workspace-route-config.js +3 -1
  77. package/dist-electron/services/local-ai-core/src/router/workspace-router.js +64 -9
  78. package/dist-electron/services/local-ai-core/src/runtime/agent-runtime-detector.js +5 -2
  79. package/dist-electron/services/local-ai-core/src/runtime/config-migration.js +152 -0
  80. package/dist-electron/services/local-ai-core/src/runtime/deployment-diagnostics.js +175 -0
  81. package/dist-electron/services/local-ai-core/src/runtime/local-core-controller.js +330 -3
  82. package/dist-electron/services/local-ai-core/src/runtime/local-core-runtime-state.js +11 -2
  83. package/dist-electron/services/local-ai-core/src/runtime/provider-config-migration.js +32 -0
  84. package/dist-electron/services/local-ai-core/src/runtime/runtime-detection-service.js +76 -8
  85. package/dist-electron/services/local-ai-core/src/runtime/server-routes.js +62 -0
  86. package/dist-electron/services/local-ai-core/src/runtime/server.js +708 -4
  87. package/dist-electron/services/local-ai-core/src/runtime/standalone.js +13 -3
  88. package/dist-electron/services/local-ai-core/src/sandbox/opensandbox-client.js +113 -0
  89. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-config.js +273 -0
  90. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-manager.js +215 -0
  91. package/dist-electron/services/local-ai-core/src/sandbox/sandbox-stdio-proxy.js +188 -0
  92. package/dist-electron/services/local-ai-core/src/thread/slash-command-registry.js +3 -0
  93. package/dist-electron/services/local-ai-core/src/thread/thread-command-service.js +32 -0
  94. package/dist-electron/services/local-ai-core/src/thread/thread-slash-command-dispatcher.js +71 -0
  95. package/dist-electron/shared/desktop.js +85 -1
  96. package/dist-electron/tests/contracts/architecture-docs.test.js +83 -0
  97. package/dist-electron/tests/electron/plugin-kernel.test.js +225 -18
  98. package/dist-electron/tests/electron/runtime-detection-service.test.js +57 -5
  99. package/dist-electron/tests/electron/sandbox-config.test.js +541 -0
  100. package/dist-electron/tests/electron/workspace-task-store.test.js +257 -0
  101. package/dist-electron/tests/integration/channel-gateways.test.js +32 -0
  102. package/dist-electron/tests/integration/lark-gateway.test.js +1977 -0
  103. package/dist-electron/tests/integration/local-core-acp-progress.test.js +1772 -0
  104. package/dist-electron/tests/integration/local-core-refactor.test.js +775 -5156
  105. package/dist-electron/tests/integration/local-core-routes.test.js +238 -0
  106. package/dist-electron/tests/integration/weixin-gateway.test.js +927 -0
  107. package/package.json +2 -3
  108. package/dist/renderer/assets/Config-lQX2qVVV.js +0 -16
  109. package/dist/renderer/assets/Dashboard-eh_RhGYb.js +0 -26
  110. package/dist/renderer/assets/ProjectDetail-Cv_jIE61.js +0 -11
  111. package/dist/renderer/assets/Workspace-Dl80fNEC.js +0 -14
  112. package/dist/renderer/assets/desktop-jaAiayNB.js +0 -1
  113. package/dist/renderer/assets/en-D2SkCrhG.js +0 -1
  114. package/dist/renderer/assets/es-C8UBPhxM.js +0 -1
  115. package/dist/renderer/assets/index-CaQZSRbg.css +0 -11
  116. package/dist/renderer/assets/ja-ChF4vbBL.js +0 -1
  117. package/dist/renderer/assets/projects-brJZgwEJ.js +0 -1
  118. package/dist/renderer/assets/zh-DIcdHycM.js +0 -1
  119. package/dist/renderer/assets/zh-TW-BtRwreY_.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
+ });