@gloablehive/celphone-wechat-plugin 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/INSTALL.md +231 -0
  2. package/README.md +259 -0
  3. package/dist/index-simple.js +9 -0
  4. package/dist/index.d.ts +16 -0
  5. package/dist/index.js +77 -0
  6. package/dist/mock-server.d.ts +6 -0
  7. package/dist/mock-server.js +203 -0
  8. package/dist/openclaw.plugin.json +96 -0
  9. package/dist/setup-entry.d.ts +9 -0
  10. package/dist/setup-entry.js +8 -0
  11. package/dist/src/cache/compactor.d.ts +36 -0
  12. package/dist/src/cache/compactor.js +154 -0
  13. package/dist/src/cache/extractor.d.ts +48 -0
  14. package/dist/src/cache/extractor.js +120 -0
  15. package/dist/src/cache/index.d.ts +15 -0
  16. package/dist/src/cache/index.js +16 -0
  17. package/dist/src/cache/indexer.d.ts +41 -0
  18. package/dist/src/cache/indexer.js +262 -0
  19. package/dist/src/cache/manager.d.ts +113 -0
  20. package/dist/src/cache/manager.js +271 -0
  21. package/dist/src/cache/message-queue.d.ts +59 -0
  22. package/dist/src/cache/message-queue.js +147 -0
  23. package/dist/src/cache/saas-connector.d.ts +94 -0
  24. package/dist/src/cache/saas-connector.js +289 -0
  25. package/dist/src/cache/syncer.d.ts +60 -0
  26. package/dist/src/cache/syncer.js +177 -0
  27. package/dist/src/cache/types.d.ts +198 -0
  28. package/dist/src/cache/types.js +43 -0
  29. package/dist/src/cache/writer.d.ts +81 -0
  30. package/dist/src/cache/writer.js +461 -0
  31. package/dist/src/channel.d.ts +65 -0
  32. package/dist/src/channel.js +334 -0
  33. package/dist/src/client.d.ts +280 -0
  34. package/dist/src/client.js +248 -0
  35. package/index-simple.ts +11 -0
  36. package/index.ts +89 -0
  37. package/mock-server.ts +237 -0
  38. package/openclaw.plugin.json +98 -0
  39. package/package.json +37 -0
  40. package/setup-entry.ts +10 -0
  41. package/src/channel.ts +398 -0
  42. package/src/client.ts +412 -0
  43. package/test-cache.ts +260 -0
  44. package/test-integration.ts +319 -0
  45. package/tsconfig.json +22 -0
@@ -0,0 +1,319 @@
1
+ /**
2
+ * Integration Test for WeChat Channel Plugin
3
+ * Tests the full flow: webhook -> cache -> response
4
+ *
5
+ * Run: npx tsx test-integration.ts
6
+ */
7
+
8
+ import * as fs from 'fs/promises';
9
+ import * as path from 'path';
10
+ import {
11
+ createCacheManager,
12
+ WeChatAccount,
13
+ WeChatMessage,
14
+ } from './src/cache/index.js';
15
+ import { handleInboundMessage } from './src/channel.js';
16
+
17
+ const TEST_CACHE_PATH = '/tmp/wechat-integration-test';
18
+ const ENCODING = 'utf-8';
19
+
20
+ async function cleanup() {
21
+ try {
22
+ await fs.rm(TEST_CACHE_PATH, { recursive: true, force: true });
23
+ } catch {}
24
+ }
25
+
26
+ /**
27
+ * Test 1: Simulate full webhook -> cache flow
28
+ */
29
+ async function testWebhookFlow() {
30
+ console.log('\nšŸ“‹ Test 1: Webhook -> Cache Flow');
31
+
32
+ const accounts: WeChatAccount[] = [
33
+ {
34
+ accountId: 'wechat-service-001',
35
+ wechatAccountId: 'wp-wechat-001',
36
+ wechatId: 'wxid_cs001',
37
+ nickName: 'å®¢ęœå°å¾®',
38
+ enabled: true,
39
+ },
40
+ ];
41
+
42
+ const manager = createCacheManager({
43
+ basePath: TEST_CACHE_PATH,
44
+ accounts,
45
+ });
46
+
47
+ await manager.init();
48
+ console.log('āœ… Cache manager initialized');
49
+
50
+ // Simulate Webhook payload (like what WorkPhone would send)
51
+ const webhookPayload = {
52
+ event: 'message',
53
+ accountId: 'wechat-service-001',
54
+ wechatAccountId: 'wp-wechat-001',
55
+ message: {
56
+ messageId: 'webhook-msg-001',
57
+ msgSvrId: 'msg-svr-12345',
58
+ fromUser: 'wxid_customer001',
59
+ toUser: 'wxid_cs001',
60
+ content: 'ä½ å„½ļ¼Œęˆ‘ęƒ³å’ØčÆ¢ä½ ä»¬ēš„äŗ§å“',
61
+ type: 1, // text
62
+ timestamp: Date.now(),
63
+ isSelf: false,
64
+ },
65
+ };
66
+
67
+ console.log('šŸ“„ Simulated webhook payload:', JSON.stringify(webhookPayload, null, 2));
68
+
69
+ // Process through handleInboundMessage (simulated - no actual OpenClaw API)
70
+ // In real usage, this would be called from the HTTP route handler
71
+ const message: WeChatMessage = {
72
+ messageId: webhookPayload.message.messageId,
73
+ msgSvrId: webhookPayload.message.msgSvrId,
74
+ accountId: webhookPayload.accountId,
75
+ conversationType: 'friend',
76
+ conversationId: webhookPayload.message.fromUser,
77
+ senderId: webhookPayload.message.fromUser,
78
+ content: webhookPayload.message.content,
79
+ messageType: webhookPayload.message.type,
80
+ timestamp: webhookPayload.message.timestamp,
81
+ isSelf: webhookPayload.message.isSelf,
82
+ direction: 'inbound',
83
+ };
84
+
85
+ await manager.onMessage(message);
86
+ console.log('āœ… Message processed through cache');
87
+
88
+ // Verify files created
89
+ const convFile = path.join(
90
+ TEST_CACHE_PATH,
91
+ 'accounts/wechat-service-001/friends/wxid_customer001/memory/2026-04.md'
92
+ );
93
+
94
+ const content = await fs.readFile(convFile, ENCODING);
95
+ console.log('šŸ“„ Cached conversation file:');
96
+ console.log(content.slice(0, 600) + '...');
97
+
98
+ return manager;
99
+ }
100
+
101
+ /**
102
+ * Test 2: Customer profile from webhook
103
+ */
104
+ async function testProfileFromWebhook(manager: any) {
105
+ console.log('\nšŸ“‹ Test 2: Customer Profile from Webhook');
106
+
107
+ // Simulate receiving customer info from webhook or after message
108
+ await manager.updateProfile('wechat-service-001', 'wxid_customer001', {
109
+ remark: 'ęŽå…ˆē”Ÿ-潜在客户',
110
+ tags: ['潜在客户', '咨询中'],
111
+ customFields: {
112
+ source: 'å®˜ē½‘å’ØčÆ¢',
113
+ interest: 'åŸŗē”€å„—é¤',
114
+ },
115
+ conversationSummary: '咨询产品俔息',
116
+ });
117
+
118
+ console.log('āœ… Profile updated from webhook context');
119
+
120
+ const profile = await manager.getProfile('wechat-service-001', 'wxid_customer001');
121
+ console.log('šŸ“„ Retrieved profile:');
122
+ console.log(JSON.stringify(profile, null, 2));
123
+ }
124
+
125
+ /**
126
+ * Test 3: Agent responds -> cache updated
127
+ */
128
+ async function testAgentResponse(manager: any) {
129
+ console.log('\nšŸ“‹ Test 3: Agent Response -> Cache Updated');
130
+
131
+ // Simulate agent sending a response
132
+ const responseMessage: WeChatMessage = {
133
+ messageId: 'webhook-msg-002',
134
+ msgSvrId: 'msg-svr-12346',
135
+ accountId: 'wechat-service-001',
136
+ conversationType: 'friend',
137
+ conversationId: 'wxid_customer001',
138
+ senderId: 'wxid_cs001',
139
+ content: 'ę‚Øå„½ļ¼ęˆ‘ä»¬ēš„åŸŗē”€å„—é¤ę˜Æ 999 元/å¹“ļ¼ŒåŒ…å«ę‰€ęœ‰ę øåæƒåŠŸčƒ½ć€‚čÆ·é—®ę‚Øęƒ³äŗ†č§£å“Ŗę–¹é¢ēš„čÆ¦ęƒ…ļ¼Ÿ',
140
+ messageType: 1,
141
+ timestamp: Date.now() + 5000,
142
+ isSelf: true,
143
+ direction: 'outbound',
144
+ };
145
+
146
+ await manager.onMessage(responseMessage);
147
+ console.log('āœ… Agent response cached');
148
+
149
+ // Verify both messages in conversation
150
+ const convFile = path.join(
151
+ TEST_CACHE_PATH,
152
+ 'accounts/wechat-service-001/friends/wxid_customer001/memory/2026-04.md'
153
+ );
154
+
155
+ const content = await fs.readFile(convFile, ENCODING);
156
+ const customerMsgCount = (content.match(/\*\*\[wxid_customer001\]/g) || []).length;
157
+ const agentMsgCount = (content.match(/\*\*\[ꈑ\]/g) || []).length;
158
+
159
+ console.log(`šŸ“Š Message count - Customer: ${customerMsgCount}, Agent: ${agentMsgCount}`);
160
+ }
161
+
162
+ /**
163
+ * Test 4: MEMORY.md index
164
+ */
165
+ async function testMemoryIndex() {
166
+ console.log('\nšŸ“‹ Test 4: MEMORY.md Index');
167
+
168
+ const globalIndex = path.join(TEST_CACHE_PATH, 'MEMORY.md');
169
+ const globalContent = await fs.readFile(globalIndex, ENCODING);
170
+ console.log('šŸ“„ Global MEMORY.md:');
171
+ console.log(globalContent);
172
+
173
+ const accountIndex = path.join(TEST_CACHE_PATH, 'accounts/wechat-service-001/MEMORY.md');
174
+ const accountContent = await fs.readFile(accountIndex, ENCODING);
175
+ console.log('šŸ“„ Account MEMORY.md:');
176
+ console.log(accountContent.slice(0, 400) + '...');
177
+ }
178
+
179
+ /**
180
+ * Test 5: Query by agent (simulated)
181
+ */
182
+ async function testAgentQuery(manager: any) {
183
+ console.log('\nšŸ“‹ Test 5: Agent Query - Get Customer History');
184
+
185
+ // Agent wants to know conversation history with this customer
186
+ const profile = await manager.getProfile('wechat-service-001', 'wxid_customer001');
187
+
188
+ console.log('šŸ“„ Agent queries customer profile:');
189
+ console.log('- 备注:', profile?.remark);
190
+ console.log('- 标签:', profile?.tags);
191
+ console.log('- ę„ęŗ:', profile?.customFields?.source);
192
+ console.log('- ę„å‘:', profile?.customFields?.interest);
193
+
194
+ // Agent gets connection status
195
+ const status = manager.getConnectionStatus();
196
+ console.log('šŸ“„ SAAS connection status:', status?.isOnline ?? false);
197
+
198
+ console.log('āœ… Agent can query customer info from cache');
199
+ }
200
+
201
+ /**
202
+ * Test 6: OpenClaw binding simulation
203
+ */
204
+ async function testBindingSimulation() {
205
+ console.log('\nšŸ“‹ Test 6: OpenClaw Binding Simulation');
206
+
207
+ // This simulates how OpenClaw would route messages
208
+ const bindingConfig = {
209
+ channels: {
210
+ 'celphone-wechat': {
211
+ accounts: [
212
+ {
213
+ accountId: 'wechat-service-001',
214
+ wechatAccountId: 'wp-wechat-001',
215
+ wechatId: 'wxid_cs001',
216
+ nickName: 'å®¢ęœå°å¾®',
217
+ },
218
+ {
219
+ accountId: 'wechat-service-002',
220
+ wechatAccountId: 'wp-wechat-002',
221
+ wechatId: 'wxid_sales01',
222
+ nickName: 'é”€å”®å°ēŽ‹',
223
+ },
224
+ ],
225
+ },
226
+ },
227
+ bindings: [
228
+ {
229
+ agentId: 'service-frontdesk',
230
+ match: {
231
+ channel: 'celphone-wechat',
232
+ accountId: 'wechat-service-001',
233
+ },
234
+ },
235
+ {
236
+ agentId: 'service-sales',
237
+ match: {
238
+ channel: 'celphone-wechat',
239
+ accountId: 'wechat-service-002',
240
+ },
241
+ },
242
+ ],
243
+ };
244
+
245
+ console.log('šŸ“‹ Binding config:', JSON.stringify(bindingConfig, null, 2));
246
+
247
+ // Simulate routing decision
248
+ const testMessage = {
249
+ channel: 'celphone-wechat',
250
+ accountId: 'wechat-service-001',
251
+ };
252
+
253
+ const matchedBinding = bindingConfig.bindings.find(
254
+ b => b.match.channel === testMessage.channel && b.match.accountId === testMessage.accountId
255
+ );
256
+
257
+ console.log('šŸ“‹ Message from account:', testMessage.accountId);
258
+ console.log('šŸ“‹ Routed to agent:', matchedBinding?.agentId);
259
+
260
+ console.log('āœ… Binding simulation complete');
261
+ }
262
+
263
+ async function runIntegrationTests() {
264
+ console.log('šŸ”¬ WeChat Channel Plugin Integration Tests');
265
+ console.log('='.repeat(60));
266
+
267
+ await cleanup();
268
+
269
+ try {
270
+ // Test 1: Webhook flow
271
+ const manager = await testWebhookFlow();
272
+
273
+ // Test 2: Profile from webhook
274
+ await testProfileFromWebhook(manager);
275
+
276
+ // Test 3: Agent response
277
+ await testAgentResponse(manager);
278
+
279
+ // Test 4: Memory index
280
+ await testMemoryIndex();
281
+
282
+ // Test 5: Agent query
283
+ await testAgentQuery(manager);
284
+
285
+ // Test 6: Binding simulation
286
+ await testBindingSimulation();
287
+
288
+ console.log('\n' + '='.repeat(60));
289
+ console.log('šŸŽ‰ All integration tests passed!');
290
+ console.log('\nšŸ“ Test cache location:', TEST_CACHE_PATH);
291
+
292
+ // List all created files
293
+ console.log('\nšŸ“‚ Created files:');
294
+ const allFiles = await walkDir(TEST_CACHE_PATH);
295
+ allFiles.forEach(f => console.log(' ', f.replace(TEST_CACHE_PATH, '')));
296
+
297
+ } catch (error) {
298
+ console.error('\nāŒ Integration test failed:', error);
299
+ process.exit(1);
300
+ }
301
+ }
302
+
303
+ async function walkDir(dir: string): Promise<string[]> {
304
+ const files: string[] = [];
305
+ try {
306
+ const entries = await fs.readdir(dir, { withFileTypes: true });
307
+ for (const entry of entries) {
308
+ const fullPath = path.join(dir, entry.name);
309
+ if (entry.isDirectory()) {
310
+ files.push(...await walkDir(fullPath));
311
+ } else {
312
+ files.push(fullPath);
313
+ }
314
+ }
315
+ } catch {}
316
+ return files;
317
+ }
318
+
319
+ runIntegrationTests();
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./dist",
8
+ "rootDir": ".",
9
+ "declaration": false,
10
+ "strict": false,
11
+ "noImplicitAny": false,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "resolveJsonModule": true,
16
+ "allowSyntheticDefaultImports": true,
17
+ "noEmit": false,
18
+ "noEmitOnError": false
19
+ },
20
+ "include": ["*.ts", "src/**/*.ts"],
21
+ "exclude": ["node_modules", "dist", "test*.ts"]
22
+ }