@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.
- package/INSTALL.md +231 -0
- package/README.md +259 -0
- package/dist/index-simple.js +9 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +77 -0
- package/dist/mock-server.d.ts +6 -0
- package/dist/mock-server.js +203 -0
- package/dist/openclaw.plugin.json +96 -0
- package/dist/setup-entry.d.ts +9 -0
- package/dist/setup-entry.js +8 -0
- package/dist/src/cache/compactor.d.ts +36 -0
- package/dist/src/cache/compactor.js +154 -0
- package/dist/src/cache/extractor.d.ts +48 -0
- package/dist/src/cache/extractor.js +120 -0
- package/dist/src/cache/index.d.ts +15 -0
- package/dist/src/cache/index.js +16 -0
- package/dist/src/cache/indexer.d.ts +41 -0
- package/dist/src/cache/indexer.js +262 -0
- package/dist/src/cache/manager.d.ts +113 -0
- package/dist/src/cache/manager.js +271 -0
- package/dist/src/cache/message-queue.d.ts +59 -0
- package/dist/src/cache/message-queue.js +147 -0
- package/dist/src/cache/saas-connector.d.ts +94 -0
- package/dist/src/cache/saas-connector.js +289 -0
- package/dist/src/cache/syncer.d.ts +60 -0
- package/dist/src/cache/syncer.js +177 -0
- package/dist/src/cache/types.d.ts +198 -0
- package/dist/src/cache/types.js +43 -0
- package/dist/src/cache/writer.d.ts +81 -0
- package/dist/src/cache/writer.js +461 -0
- package/dist/src/channel.d.ts +65 -0
- package/dist/src/channel.js +334 -0
- package/dist/src/client.d.ts +280 -0
- package/dist/src/client.js +248 -0
- package/index-simple.ts +11 -0
- package/index.ts +89 -0
- package/mock-server.ts +237 -0
- package/openclaw.plugin.json +98 -0
- package/package.json +37 -0
- package/setup-entry.ts +10 -0
- package/src/channel.ts +398 -0
- package/src/client.ts +412 -0
- package/test-cache.ts +260 -0
- package/test-integration.ts +319 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Writer - File writing with monthly partition
|
|
3
|
+
*
|
|
4
|
+
* Aligned with Claude Code memory system:
|
|
5
|
+
* - YAML frontmatter for metadata
|
|
6
|
+
* - Markdown content
|
|
7
|
+
* - Monthly file partitioning
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'fs/promises';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
import { getMonthlyFilePath, } from './types.js';
|
|
12
|
+
// ========== Constants ==========
|
|
13
|
+
const ENCODING = 'utf-8';
|
|
14
|
+
// ========== Frontmatter Helpers ==========
|
|
15
|
+
function generateConversationFrontmatter(wechatId, accountId, accountWechatId, subtype, messageCount) {
|
|
16
|
+
const now = new Date().toISOString();
|
|
17
|
+
return {
|
|
18
|
+
name: `与 ${wechatId} 的对话`,
|
|
19
|
+
description: `与 ${wechatId} 的对话记录`,
|
|
20
|
+
type: 'conversation',
|
|
21
|
+
subtype,
|
|
22
|
+
wechatId,
|
|
23
|
+
accountId,
|
|
24
|
+
accountWechatId,
|
|
25
|
+
lastContact: now,
|
|
26
|
+
messageCount,
|
|
27
|
+
createdAt: now,
|
|
28
|
+
updatedAt: now,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function generateUserProfileFrontmatter(wechatId, accountId, tags) {
|
|
32
|
+
const now = new Date().toISOString();
|
|
33
|
+
return {
|
|
34
|
+
name: `${wechatId} 用户画像`,
|
|
35
|
+
description: `用户画像信息`,
|
|
36
|
+
type: 'user',
|
|
37
|
+
wechatId,
|
|
38
|
+
accountId,
|
|
39
|
+
tags,
|
|
40
|
+
addedAt: now,
|
|
41
|
+
lastContact: now,
|
|
42
|
+
createdAt: now,
|
|
43
|
+
updatedAt: now,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function frontmatterToString(frontmatter) {
|
|
47
|
+
const lines = ['---'];
|
|
48
|
+
for (const [key, value] of Object.entries(frontmatter)) {
|
|
49
|
+
if (Array.isArray(value)) {
|
|
50
|
+
lines.push(`${key}: [${value.map(v => `"${v}"`).join(', ')}]`);
|
|
51
|
+
}
|
|
52
|
+
else if (typeof value === 'object' && value !== null) {
|
|
53
|
+
lines.push(`${key}: ${JSON.stringify(value)}`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
lines.push(`${key}: ${value}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
lines.push('---');
|
|
60
|
+
return lines.join('\n');
|
|
61
|
+
}
|
|
62
|
+
// ========== Conversation File Operations ==========
|
|
63
|
+
/**
|
|
64
|
+
* Get or create conversation directory
|
|
65
|
+
*/
|
|
66
|
+
export async function ensureConversationDir(basePath, accountId, subtype, conversationId) {
|
|
67
|
+
const dir = subtype === 'friend'
|
|
68
|
+
? `${basePath}/accounts/${accountId}/friends/${conversationId}/memory`
|
|
69
|
+
: `${basePath}/accounts/${accountId}/chatrooms/${conversationId}/memory`;
|
|
70
|
+
await fs.mkdir(dir, { recursive: true });
|
|
71
|
+
return dir;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Get monthly conversation file path
|
|
75
|
+
*/
|
|
76
|
+
export function getConversationFilePath(basePath, accountId, subtype, conversationId, date) {
|
|
77
|
+
const monthlyFile = getMonthlyFilePath(conversationId, date);
|
|
78
|
+
const dir = subtype === 'friend'
|
|
79
|
+
? `${basePath}/accounts/${accountId}/friends/${conversationId}/memory`
|
|
80
|
+
: `${basePath}/accounts/${accountId}/chatrooms/${conversationId}/memory`;
|
|
81
|
+
return path.join(dir, monthlyFile);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Check if conversation file exists
|
|
85
|
+
*/
|
|
86
|
+
export async function conversationFileExists(filePath) {
|
|
87
|
+
try {
|
|
88
|
+
await fs.access(filePath);
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Read conversation file
|
|
97
|
+
*/
|
|
98
|
+
export async function readConversationFile(filePath) {
|
|
99
|
+
try {
|
|
100
|
+
return await fs.readFile(filePath, ENCODING);
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
throw new Error(`Failed to read conversation file: ${error}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create new conversation file with frontmatter
|
|
108
|
+
*/
|
|
109
|
+
export async function createConversationFile(filePath, wechatId, accountId, accountWechatId, subtype) {
|
|
110
|
+
const frontmatter = generateConversationFrontmatter(wechatId, accountId, accountWechatId, subtype, 0);
|
|
111
|
+
const content = frontmatterToString(frontmatter) + '\n\n# Conversation\n\n';
|
|
112
|
+
await fs.writeFile(filePath, content, ENCODING);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Append message to conversation file
|
|
116
|
+
*/
|
|
117
|
+
export async function appendMessageToConversation(filePath, message, remark) {
|
|
118
|
+
let content;
|
|
119
|
+
try {
|
|
120
|
+
content = await fs.readFile(filePath, ENCODING);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// File doesn't exist, create it
|
|
124
|
+
content = '';
|
|
125
|
+
}
|
|
126
|
+
// Parse existing frontmatter
|
|
127
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
128
|
+
let frontmatter;
|
|
129
|
+
let existingContent = content;
|
|
130
|
+
if (frontmatterMatch) {
|
|
131
|
+
// Parse existing frontmatter
|
|
132
|
+
const fmLines = frontmatterMatch[1].split('\n');
|
|
133
|
+
const fm = {};
|
|
134
|
+
for (const line of fmLines) {
|
|
135
|
+
const [key, ...valueParts] = line.split(':');
|
|
136
|
+
if (key && valueParts.length) {
|
|
137
|
+
const value = valueParts.join(':').trim();
|
|
138
|
+
if (value.startsWith('[')) {
|
|
139
|
+
// Array
|
|
140
|
+
fm[key.trim()] = value.slice(1, -1).split(',').map(v => v.trim().replace(/"/g, ''));
|
|
141
|
+
}
|
|
142
|
+
else if (value === 'true' || value === 'false') {
|
|
143
|
+
fm[key.trim()] = value === 'true';
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
fm[key.trim()] = value.replace(/"/g, '');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
frontmatter = fm;
|
|
151
|
+
frontmatter.messageCount = (frontmatter.messageCount || 0) + 1;
|
|
152
|
+
frontmatter.lastContact = new Date(message.timestamp).toISOString();
|
|
153
|
+
frontmatter.updatedAt = new Date().toISOString();
|
|
154
|
+
if (remark) {
|
|
155
|
+
frontmatter.name = `与 ${remark} (${frontmatter.wechatId}) 的对话`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
// Create new frontmatter
|
|
160
|
+
frontmatter = generateConversationFrontmatter(message.conversationId, message.accountId, '', message.conversationType, 1);
|
|
161
|
+
}
|
|
162
|
+
// Build message block
|
|
163
|
+
const time = new Date(message.timestamp);
|
|
164
|
+
const timeStr = `[${time.toISOString().split('T')[0]}]`;
|
|
165
|
+
const sender = message.isSelf ? '我' : (remark || message.senderId);
|
|
166
|
+
const direction = message.isSelf ? '' : ''; // 可以添加方向标记
|
|
167
|
+
const messageBlock = `\n${timeStr}\n\n**[${sender}]**: ${message.content}\n`;
|
|
168
|
+
// Rebuild file
|
|
169
|
+
const newContent = frontmatterToString(frontmatter) + '\n\n' + messageBlock + '\n' + existingContent.replace(/^---[\s\S]*?---\n\n/, '');
|
|
170
|
+
await fs.writeFile(filePath, newContent, ENCODING);
|
|
171
|
+
}
|
|
172
|
+
// ========== Profile File Operations ==========
|
|
173
|
+
/**
|
|
174
|
+
* Get profile file path
|
|
175
|
+
*/
|
|
176
|
+
export function getProfileFilePath(basePath, accountId, wechatId) {
|
|
177
|
+
return `${basePath}/accounts/${accountId}/profiles/${wechatId}.md`;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Create or update profile file
|
|
181
|
+
*/
|
|
182
|
+
export async function writeProfile(filePath, profile) {
|
|
183
|
+
const frontmatter = {
|
|
184
|
+
name: `${profile.remark || profile.wechatId} 用户画像`,
|
|
185
|
+
description: `用户画像信息 - ${profile.tags.join(', ')}`,
|
|
186
|
+
type: 'user',
|
|
187
|
+
wechatId: profile.wechatId,
|
|
188
|
+
accountId: profile.accountId,
|
|
189
|
+
tags: profile.tags,
|
|
190
|
+
addedAt: profile.addedAt,
|
|
191
|
+
lastContact: profile.lastContact,
|
|
192
|
+
createdAt: profile.addedAt,
|
|
193
|
+
updatedAt: new Date().toISOString(),
|
|
194
|
+
};
|
|
195
|
+
let content = frontmatterToString(frontmatter) + '\n\n';
|
|
196
|
+
// Add profile sections
|
|
197
|
+
content += '## 基础信息\n';
|
|
198
|
+
if (profile.remark)
|
|
199
|
+
content += `- 备注: ${profile.remark}\n`;
|
|
200
|
+
if (profile.nickName)
|
|
201
|
+
content += `- 昵称: ${profile.nickName}\n`;
|
|
202
|
+
if (profile.avatarUrl)
|
|
203
|
+
content += `- 头像: ${profile.avatarUrl}\n`;
|
|
204
|
+
content += '\n## 画像标签\n';
|
|
205
|
+
for (const tag of profile.tags) {
|
|
206
|
+
content += `- ${tag}\n`;
|
|
207
|
+
}
|
|
208
|
+
if (profile.customFields && Object.keys(profile.customFields).length > 0) {
|
|
209
|
+
content += '\n## 自定义字段\n';
|
|
210
|
+
for (const [key, value] of Object.entries(profile.customFields)) {
|
|
211
|
+
content += `- ${key}: ${value}\n`;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (profile.conversationSummary) {
|
|
215
|
+
content += '\n## 对话摘要\n' + profile.conversationSummary + '\n';
|
|
216
|
+
}
|
|
217
|
+
if (profile.preferenceHints) {
|
|
218
|
+
content += '\n## 偏好提示\n' + profile.preferenceHints + '\n';
|
|
219
|
+
}
|
|
220
|
+
if (profile.riskFlags && profile.riskFlags.length > 0) {
|
|
221
|
+
content += '\n## 风险标记\n';
|
|
222
|
+
for (const flag of profile.riskFlags) {
|
|
223
|
+
content += `- ${flag}\n`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
await fs.writeFile(filePath, content, ENCODING);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Read profile file
|
|
230
|
+
*/
|
|
231
|
+
export async function readProfile(filePath) {
|
|
232
|
+
try {
|
|
233
|
+
const content = await fs.readFile(filePath, ENCODING);
|
|
234
|
+
// Parse frontmatter
|
|
235
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
236
|
+
if (!fmMatch)
|
|
237
|
+
return null;
|
|
238
|
+
const fmLines = fmMatch[1].split('\n');
|
|
239
|
+
const profile = {
|
|
240
|
+
tags: [],
|
|
241
|
+
riskFlags: [],
|
|
242
|
+
customFields: {},
|
|
243
|
+
};
|
|
244
|
+
for (const line of fmLines) {
|
|
245
|
+
const colonIdx = line.indexOf(':');
|
|
246
|
+
if (colonIdx === -1)
|
|
247
|
+
continue;
|
|
248
|
+
const key = line.slice(0, colonIdx).trim();
|
|
249
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
250
|
+
switch (key) {
|
|
251
|
+
case 'wechatId':
|
|
252
|
+
profile.wechatId = value.replace(/"/g, '');
|
|
253
|
+
break;
|
|
254
|
+
case 'accountId':
|
|
255
|
+
profile.accountId = value.replace(/"/g, '');
|
|
256
|
+
break;
|
|
257
|
+
case 'remark':
|
|
258
|
+
profile.remark = value.replace(/"/g, '');
|
|
259
|
+
break;
|
|
260
|
+
case 'nickName':
|
|
261
|
+
profile.nickName = value.replace(/"/g, '');
|
|
262
|
+
break;
|
|
263
|
+
case 'tags':
|
|
264
|
+
profile.tags = value.slice(1, -1).split(',').map(v => v.trim().replace(/"/g, ''));
|
|
265
|
+
break;
|
|
266
|
+
case 'addedAt':
|
|
267
|
+
profile.addedAt = value.replace(/"/g, '');
|
|
268
|
+
break;
|
|
269
|
+
case 'lastContact':
|
|
270
|
+
profile.lastContact = value.replace(/"/g, '');
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return profile;
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Update profile fields
|
|
282
|
+
*/
|
|
283
|
+
export async function updateProfileFields(filePath, updates) {
|
|
284
|
+
const existing = await readProfile(filePath);
|
|
285
|
+
if (!existing) {
|
|
286
|
+
// Create new profile
|
|
287
|
+
const newProfile = {
|
|
288
|
+
wechatId: updates.wechatId || '',
|
|
289
|
+
accountId: updates.accountId || '',
|
|
290
|
+
remark: updates.remark || '',
|
|
291
|
+
nickName: updates.nickName || '',
|
|
292
|
+
tags: updates.tags || [],
|
|
293
|
+
customFields: updates.customFields || {},
|
|
294
|
+
riskFlags: updates.riskFlags || [],
|
|
295
|
+
addedAt: new Date().toISOString(),
|
|
296
|
+
lastContact: new Date().toISOString(),
|
|
297
|
+
};
|
|
298
|
+
await writeProfile(filePath, newProfile);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// Merge updates
|
|
302
|
+
const updated = {
|
|
303
|
+
...existing,
|
|
304
|
+
...updates,
|
|
305
|
+
lastContact: new Date().toISOString(),
|
|
306
|
+
updatedAt: new Date().toISOString(),
|
|
307
|
+
};
|
|
308
|
+
await writeProfile(filePath, updated);
|
|
309
|
+
}
|
|
310
|
+
// ========== Session Memory Operations ==========
|
|
311
|
+
/**
|
|
312
|
+
* Get session memory file path
|
|
313
|
+
*/
|
|
314
|
+
export function getSessionMemoryPath(basePath, accountId, conversationId) {
|
|
315
|
+
return `${basePath}/accounts/${accountId}/friends/${conversationId}/session.md`;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Write session memory (aligned with Claude Code 10-section template)
|
|
319
|
+
*/
|
|
320
|
+
export async function writeSessionMemory(filePath, session) {
|
|
321
|
+
const content = `# Session Memory - ${session.sessionTitle}
|
|
322
|
+
|
|
323
|
+
## Session Title
|
|
324
|
+
_${session.sessionTitle}_
|
|
325
|
+
|
|
326
|
+
## Current State
|
|
327
|
+
_${session.currentState}_
|
|
328
|
+
|
|
329
|
+
## Task specification
|
|
330
|
+
_${session.taskSpecification}_
|
|
331
|
+
|
|
332
|
+
## Files and Functions
|
|
333
|
+
_${session.filesAndFunctions}_
|
|
334
|
+
|
|
335
|
+
## Workflow
|
|
336
|
+
_${session.workflow}_
|
|
337
|
+
|
|
338
|
+
## Errors & Corrections
|
|
339
|
+
_${session.errorsAndCorrections}_
|
|
340
|
+
|
|
341
|
+
## Codebase and System Documentation
|
|
342
|
+
_${session.codebaseDocumentation}_
|
|
343
|
+
|
|
344
|
+
## Learnings
|
|
345
|
+
_${session.learnings}_
|
|
346
|
+
|
|
347
|
+
## Key results
|
|
348
|
+
_${session.keyResults}_
|
|
349
|
+
|
|
350
|
+
## Worklog
|
|
351
|
+
_${session.worklog}_
|
|
352
|
+
`;
|
|
353
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
354
|
+
await fs.writeFile(filePath, content, ENCODING);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Read session memory
|
|
358
|
+
*/
|
|
359
|
+
export async function readSessionMemory(filePath) {
|
|
360
|
+
try {
|
|
361
|
+
const content = await fs.readFile(filePath, ENCODING);
|
|
362
|
+
// Parse sections
|
|
363
|
+
const session = {
|
|
364
|
+
sessionTitle: extractSection(content, 'Session Title') || '',
|
|
365
|
+
currentState: extractSection(content, 'Current State') || '',
|
|
366
|
+
taskSpecification: extractSection(content, 'Task specification') || '',
|
|
367
|
+
filesAndFunctions: extractSection(content, 'Files and Functions') || '',
|
|
368
|
+
workflow: extractSection(content, 'Workflow') || '',
|
|
369
|
+
errorsAndCorrections: extractSection(content, 'Errors & Corrections') || '',
|
|
370
|
+
codebaseDocumentation: extractSection(content, 'Codebase and System Documentation') || '',
|
|
371
|
+
learnings: extractSection(content, 'Learnings') || '',
|
|
372
|
+
keyResults: extractSection(content, 'Key results') || '',
|
|
373
|
+
worklog: extractSection(content, 'Worklog') || '',
|
|
374
|
+
};
|
|
375
|
+
return session;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return null;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function extractSection(content, sectionName) {
|
|
382
|
+
const regex = new RegExp(`## ${sectionName}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
383
|
+
const match = content.match(regex);
|
|
384
|
+
return match ? match[1].trim().replace(/^_|_$/g, '') : '';
|
|
385
|
+
}
|
|
386
|
+
// ========== Summary Operations ==========
|
|
387
|
+
/**
|
|
388
|
+
* Get summary file path
|
|
389
|
+
*/
|
|
390
|
+
export function getSummaryPath(basePath, accountId, subtype, conversationId, yearMonth) {
|
|
391
|
+
return `${basePath}/accounts/${accountId}/${subtype === 'friend' ? 'friends' : 'chatrooms'}/${conversationId}/memory/summaries/${yearMonth}-summary.md`;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Write AI summary
|
|
395
|
+
*/
|
|
396
|
+
export async function writeSummary(filePath, summary, keyPoints, learnings, actionItems) {
|
|
397
|
+
const now = new Date().toISOString();
|
|
398
|
+
const content = `---
|
|
399
|
+
name: 对话摘要
|
|
400
|
+
description: AI 生成的对话摘要
|
|
401
|
+
type: conversation
|
|
402
|
+
summary: true
|
|
403
|
+
generatedAt: ${now}
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
# 对话摘要
|
|
407
|
+
|
|
408
|
+
${summary}
|
|
409
|
+
|
|
410
|
+
## 关键点
|
|
411
|
+
|
|
412
|
+
${keyPoints.map(p => `- ${p}`).join('\n')}
|
|
413
|
+
|
|
414
|
+
## 学习到的信息
|
|
415
|
+
|
|
416
|
+
${learnings}
|
|
417
|
+
|
|
418
|
+
${actionItems && actionItems.length > 0 ? `## 待跟进事项
|
|
419
|
+
|
|
420
|
+
${actionItems.map(p => `- ${p}`).join('\n')}` : ''}
|
|
421
|
+
`;
|
|
422
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
423
|
+
await fs.writeFile(filePath, content, ENCODING);
|
|
424
|
+
}
|
|
425
|
+
// ========== Utility Functions ==========
|
|
426
|
+
/**
|
|
427
|
+
* Ensure directory exists
|
|
428
|
+
*/
|
|
429
|
+
export async function ensureDir(dirPath) {
|
|
430
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Delete file if exists
|
|
434
|
+
*/
|
|
435
|
+
export async function deleteFile(filePath) {
|
|
436
|
+
try {
|
|
437
|
+
await fs.unlink(filePath);
|
|
438
|
+
}
|
|
439
|
+
catch (error) {
|
|
440
|
+
if (error.code !== 'ENOENT') {
|
|
441
|
+
throw error;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* List files in directory
|
|
447
|
+
*/
|
|
448
|
+
export async function listFiles(dirPath, pattern = '*') {
|
|
449
|
+
try {
|
|
450
|
+
const files = await fs.readdir(dirPath);
|
|
451
|
+
if (pattern === '*') {
|
|
452
|
+
return files;
|
|
453
|
+
}
|
|
454
|
+
// Simple glob matching
|
|
455
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
456
|
+
return files.filter(f => regex.test(f));
|
|
457
|
+
}
|
|
458
|
+
catch {
|
|
459
|
+
return [];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkPhone WeChat Channel Plugin for OpenClaw
|
|
3
|
+
*
|
|
4
|
+
* This channel plugin connects OpenClaw to WorkPhone's WeChat API,
|
|
5
|
+
* enabling sending and receiving WeChat messages through the WorkPhone platform.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT - Human Account Model:
|
|
8
|
+
* Unlike Telegram/WhatsApp bots, this connects to a real human WeChat account.
|
|
9
|
+
* The agent communicates with ALL friends and groups under that WeChat account,
|
|
10
|
+
* not just one DM context. This is "human mode" vs "bot mode".
|
|
11
|
+
*
|
|
12
|
+
* Includes Local Cache:
|
|
13
|
+
* - Per-account, per-user/conversation MD files
|
|
14
|
+
* - YAML frontmatter (aligned with Claude Code)
|
|
15
|
+
* - MEMORY.md indexing
|
|
16
|
+
* - 4-layer compression
|
|
17
|
+
* - AI summary extraction
|
|
18
|
+
* - SAAS connectivity + offline fallback
|
|
19
|
+
* - Cloud sync
|
|
20
|
+
*/
|
|
21
|
+
import type { OpenClawConfig } from "openclaw/plugin-sdk/core";
|
|
22
|
+
import { type WebhookPayload } from "./client.js";
|
|
23
|
+
export interface CelPhoneWeChatResolvedAccount {
|
|
24
|
+
accountId: string | null;
|
|
25
|
+
apiKey: string;
|
|
26
|
+
baseUrl: string;
|
|
27
|
+
wechatAccountId: string;
|
|
28
|
+
wechatId: string;
|
|
29
|
+
nickName: string;
|
|
30
|
+
allowFrom: string[];
|
|
31
|
+
dmPolicy: string | undefined;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create the channel plugin
|
|
35
|
+
*/
|
|
36
|
+
export declare const celPhoneWeChatPlugin: import("openclaw/plugin-sdk/core").ChannelPlugin<CelPhoneWeChatResolvedAccount, unknown, unknown>;
|
|
37
|
+
/**
|
|
38
|
+
* Helper function to handle inbound webhook messages
|
|
39
|
+
* This should be called from your HTTP route handler
|
|
40
|
+
*
|
|
41
|
+
* Integrates with Cache Manager for:
|
|
42
|
+
* - Local MD file caching
|
|
43
|
+
* - User profile storage
|
|
44
|
+
* - Session memory
|
|
45
|
+
* - Cloud sync
|
|
46
|
+
* - Offline fallback
|
|
47
|
+
*/
|
|
48
|
+
export declare function handleInboundMessage(api: any, payload: WebhookPayload, cfg?: OpenClawConfig): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* Get user profile from cache (for agent use)
|
|
51
|
+
*/
|
|
52
|
+
export declare function getUserProfile(accountId: string, wechatId: string): Promise<any>;
|
|
53
|
+
/**
|
|
54
|
+
* Update user profile in cache
|
|
55
|
+
*/
|
|
56
|
+
export declare function updateUserProfile(accountId: string, wechatId: string, updates: any): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* Get SAAS connection status
|
|
59
|
+
*/
|
|
60
|
+
export declare function getConnectionStatus(): import("./cache/types.js").ConnectionStatus;
|
|
61
|
+
/**
|
|
62
|
+
* Check if system is online (SAAS connected)
|
|
63
|
+
*/
|
|
64
|
+
export declare function isOnline(): boolean;
|
|
65
|
+
export default celPhoneWeChatPlugin;
|