@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,203 @@
1
+ /**
2
+ * Mock WorkPhone API Server for Testing
3
+ *
4
+ * 模拟 WorkPhone 的 API 端点,用于本地测试
5
+ */
6
+ import express from 'express';
7
+ import { createServer } from 'http';
8
+ const app = express();
9
+ app.use(express.json());
10
+ // 配置
11
+ const MOCK_CONFIG = {
12
+ apiKey: 'test-api-key-12345',
13
+ wechatAccountId: 'mock-wechat-001',
14
+ wechatId: 'wxid_mock001',
15
+ baseUrl: 'http://localhost:18999',
16
+ };
17
+ // 存储消息
18
+ const messages = [];
19
+ const friends = [
20
+ { wechatId: 'wxid_customer001', remark: '客户A', nickname: '张三' },
21
+ { wechatId: 'wxid_customer002', remark: '客户B', nickname: '李四' },
22
+ { wechatId: 'wxid_customer003', remark: '潜在客户', nickname: '王五' },
23
+ ];
24
+ // ============ API 端点 ============
25
+ // 健康检查
26
+ app.get('/health', (req, res) => {
27
+ res.json({ status: 'ok' });
28
+ });
29
+ // 获取账号列表
30
+ app.get('/api/v1/wechat/accounts', (req, res) => {
31
+ const auth = req.headers.authorization;
32
+ if (auth !== `Bearer ${MOCK_CONFIG.apiKey}`) {
33
+ return res.status(401).json({ error: 'unauthorized' });
34
+ }
35
+ res.json({
36
+ data: [
37
+ {
38
+ accountId: MOCK_CONFIG.wechatAccountId,
39
+ wechatId: MOCK_CONFIG.wechatId,
40
+ nickname: '测试客服',
41
+ status: 'online',
42
+ }
43
+ ]
44
+ });
45
+ });
46
+ // 获取好友列表
47
+ app.get(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/friends`, (req, res) => {
48
+ const auth = req.headers.authorization;
49
+ if (auth !== `Bearer ${MOCK_CONFIG.apiKey}`) {
50
+ return res.status(401).json({ error: 'unauthorized' });
51
+ }
52
+ res.json({ data: friends });
53
+ });
54
+ // 发送好友消息
55
+ app.post(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/messages/friend`, (req, res) => {
56
+ const auth = req.headers.authorization;
57
+ if (auth !== `Bearer ${MOCK_CONFIG.apiKey}`) {
58
+ return res.status(401).json({ error: 'unauthorized' });
59
+ }
60
+ const { friendWechatId, content, type } = req.body;
61
+ console.log(`[Mock] 发送消息给 ${friendWechatId}: ${content}`);
62
+ const messageId = `msg_${Date.now()}`;
63
+ messages.push({
64
+ messageId,
65
+ friendWechatId,
66
+ content,
67
+ type,
68
+ timestamp: Date.now(),
69
+ direction: 'outbound',
70
+ });
71
+ res.json({
72
+ code: 0,
73
+ message: 'success',
74
+ data: {
75
+ messageId,
76
+ msgSvrId: `svr_${Date.now()}`,
77
+ }
78
+ });
79
+ });
80
+ // 发送群消息
81
+ app.post(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/messages/chatroom`, (req, res) => {
82
+ const auth = req.headers.authorization;
83
+ if (auth !== `Bearer ${MOCK_CONFIG.apiKey}`) {
84
+ return res.status(401).json({ error: 'unauthorized' });
85
+ }
86
+ const { chatroomId, content, type, atUsers } = req.body;
87
+ console.log(`[Mock] 发送消息到群 ${chatroomId}: ${content}`);
88
+ const messageId = `msg_${Date.now()}`;
89
+ messages.push({
90
+ messageId,
91
+ chatroomId,
92
+ content,
93
+ type,
94
+ atUsers,
95
+ timestamp: Date.now(),
96
+ direction: 'outbound',
97
+ });
98
+ res.json({
99
+ code: 0,
100
+ message: 'success',
101
+ data: {
102
+ messageId,
103
+ msgSvrId: `svr_${Date.now()}`,
104
+ }
105
+ });
106
+ });
107
+ // Webhook 配置(返回当前 webhook 地址)
108
+ app.get(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/webhook`, (req, res) => {
109
+ res.json({
110
+ data: {
111
+ url: 'http://localhost:18999/webhook',
112
+ enabled: true,
113
+ }
114
+ });
115
+ });
116
+ // 设置 webhook
117
+ app.put(`/api/v1/wechat/accounts/${MOCK_CONFIG.wechatAccountId}/webhook`, (req, res) => {
118
+ const { url } = req.body;
119
+ console.log(`[Mock] Webhook 设置为: ${url}`);
120
+ res.json({ code: 0, message: 'success' });
121
+ });
122
+ // ============ Webhook 模拟触发 ============
123
+ // 模拟收到好友消息
124
+ app.post('/mock/trigger/friend-message', (req, res) => {
125
+ const { wechatId, content } = req.body;
126
+ const payload = {
127
+ event: 'message',
128
+ accountId: MOCK_CONFIG.wechatAccountId,
129
+ wechatAccountId: MOCK_CONFIG.wechatAccountId,
130
+ message: {
131
+ messageId: `in_msg_${Date.now()}`,
132
+ msgSvrId: `svr_${Date.now()}`,
133
+ fromUser: wechatId || 'wxid_customer001',
134
+ toUser: MOCK_CONFIG.wechatId,
135
+ content: content || '你好,这是测试消息',
136
+ type: 1,
137
+ timestamp: Date.now(),
138
+ isSelf: false,
139
+ }
140
+ };
141
+ // 回调(如果配置了 webhook)
142
+ // 实际使用时会让用户配置真实的 webhook URL
143
+ res.json({
144
+ code: 0,
145
+ message: 'triggered',
146
+ payload
147
+ });
148
+ });
149
+ // 模拟收到群消息
150
+ app.post('/mock/trigger/chatroom-message', (req, res) => {
151
+ const { chatroomId, content } = req.body;
152
+ const payload = {
153
+ event: 'message',
154
+ accountId: MOCK_CONFIG.wechatAccountId,
155
+ wechatAccountId: MOCK_CONFIG.wechatAccountId,
156
+ message: {
157
+ messageId: `in_msg_${Date.now()}`,
158
+ msgSvrId: `svr_${Date.now()}`,
159
+ fromUser: 'wxid_customer001',
160
+ chatroomId: chatroomId || '123456789@chatroom',
161
+ content: content || '大家好',
162
+ type: 1,
163
+ timestamp: Date.now(),
164
+ isSelf: false,
165
+ }
166
+ };
167
+ res.json({
168
+ code: 0,
169
+ message: 'triggered',
170
+ payload
171
+ });
172
+ });
173
+ // 获取消息历史
174
+ app.get('/mock/messages', (req, res) => {
175
+ res.json({ data: messages });
176
+ });
177
+ // 启动服务器
178
+ const PORT = 18999;
179
+ const server = createServer(app);
180
+ server.listen(PORT, () => {
181
+ console.log(`
182
+ ╔═══════════════════════════════════════════════════════════╗
183
+ ║ Mock WorkPhone API Server ║
184
+ ╠═══════════════════════════════════════════════════════════╣
185
+ ║ Base URL: http://localhost:${PORT} ║
186
+ ║ API Key: ${MOCK_CONFIG.apiKey} ║
187
+ ║ WeChat Account: ${MOCK_CONFIG.wechatAccountId} ║
188
+ ╠═══════════════════════════════════════════════════════════╣
189
+ ║ 测试端点: ║
190
+ ║ - POST /mock/trigger/friend-message 模拟好友消息 ║
191
+ ║ - POST /mock/trigger/chatroom-message 模拟群消息 ║
192
+ ║ - GET /mock/messages 查看消息历史 ║
193
+ ╚═══════════════════════════════════════════════════════════╝
194
+ `);
195
+ });
196
+ // 优雅关闭
197
+ process.on('SIGINT', () => {
198
+ console.log('\n[Mock] 关闭服务器...');
199
+ server.close(() => {
200
+ console.log('[Mock] 服务器已关闭');
201
+ process.exit(0);
202
+ });
203
+ });
@@ -0,0 +1,96 @@
1
+ {
2
+ "$schema": "https://openclaw.ai/schema/plugin.json",
3
+ "id": "celphone-wechat",
4
+ "name": "WorkPhone WeChat",
5
+ "version": "1.0.0",
6
+ "description": "Connect OpenClaw to WorkPhone WeChat API for sending and receiving WeChat messages",
7
+ "author": {
8
+ "name": "GolaBeHive",
9
+ "url": "https://github.com/golabehive"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/golabehive/channels/celphone-wechat-plugin"
14
+ },
15
+ "keywords": ["openclaw", "channel", "wechat", "workphone", "messaging"],
16
+ "license": "MIT",
17
+ "type": "channel",
18
+ "capabilities": {
19
+ "inbound": {
20
+ "message": true,
21
+ "friend_request": true,
22
+ "group_invite": false
23
+ },
24
+ "outbound": {
25
+ "text": true,
26
+ "media": true,
27
+ "link": true,
28
+ "location": true,
29
+ "contact": true
30
+ },
31
+ "features": {
32
+ "pairing": false,
33
+ "reactions": false,
34
+ "editing": false,
35
+ "deleting": false,
36
+ "threads": true
37
+ }
38
+ },
39
+ "configSchema": {
40
+ "type": "object",
41
+ "properties": {
42
+ "apiKey": {
43
+ "type": "string",
44
+ "description": "WorkPhone API key for authentication",
45
+ "secret": true
46
+ },
47
+ "baseUrl": {
48
+ "type": "string",
49
+ "description": "WorkPhone API base URL",
50
+ "default": "https://api.workphone.example.com"
51
+ },
52
+ "accountId": {
53
+ "type": "string",
54
+ "description": "WorkPhone account ID"
55
+ },
56
+ "wechatAccountId": {
57
+ "type": "string",
58
+ "description": "WeChat account ID to use (from WorkPhone) - REQUIRED"
59
+ },
60
+ "wechatId": {
61
+ "type": "string",
62
+ "description": "The actual WeChat ID (wxid_xxx) for this account"
63
+ },
64
+ "nickName": {
65
+ "type": "string",
66
+ "description": "Display name for this WeChat account"
67
+ },
68
+ "allowFrom": {
69
+ "type": "array",
70
+ "items": { "type": "string" },
71
+ "description": "List of WeChat IDs that can DM the agent",
72
+ "default": []
73
+ },
74
+ "dmSecurity": {
75
+ "type": "string",
76
+ "enum": ["allowlist", "blocklist", "allowall"],
77
+ "description": "DM security policy",
78
+ "default": "allowlist"
79
+ },
80
+ "webhookSecret": {
81
+ "type": "string",
82
+ "description": "Secret for verifying webhook requests",
83
+ "secret": true
84
+ }
85
+ },
86
+ "required": ["apiKey", "wechatAccountId"]
87
+ },
88
+ "permissions": {
89
+ "channels": ["celphone-wechat"],
90
+ "http": {
91
+ "outbound": ["*"],
92
+ "inbound": ["/celphone-wechat/webhook"]
93
+ }
94
+ },
95
+ "apiVersion": "1.0.0"
96
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Setup Entry Point
3
+ *
4
+ * Lightweight entry for loading during onboarding/config when channel is disabled
5
+ */
6
+ declare const _default: {
7
+ plugin: import("openclaw/plugin-sdk/core").ChannelPlugin<import("./src/channel.js").CelPhoneWeChatResolvedAccount, unknown, unknown>;
8
+ };
9
+ export default _default;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Setup Entry Point
3
+ *
4
+ * Lightweight entry for loading during onboarding/config when channel is disabled
5
+ */
6
+ import { defineSetupPluginEntry } from "openclaw/plugin-sdk/core";
7
+ import { celPhoneWeChatPlugin } from "./src/channel.js";
8
+ export default defineSetupPluginEntry(celPhoneWeChatPlugin);
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Compactor - 4-layer compression system
3
+ *
4
+ * Aligned with Claude Code compression:
5
+ * - Layer 1: Microcompact (clean old tool results)
6
+ * - Layer 2: Auto-compact (context threshold)
7
+ * - Layer 3: Full compact (Fork Agent summary)
8
+ * - Layer 4: Session compact (use session.md)
9
+ */
10
+ import type { CompactConfig } from './types.js';
11
+ import { DEFAULT_COMPACT_CONFIG } from './types.js';
12
+ /**
13
+ * Layer 1: Microcompact - clean old messages, keep recent N intact
14
+ */
15
+ export declare function microCompact(filePath: string, keepCount?: number): Promise<boolean>;
16
+ /**
17
+ * Layer 2: Auto-compact - check if context threshold exceeded
18
+ */
19
+ export declare function shouldAutoCompact(config: CompactConfig, tokenEstimate: number): boolean;
20
+ /**
21
+ * Layer 3: Full compact - generate summary via LLM
22
+ */
23
+ export declare function fullCompact(conversationPath: string, summary: string): Promise<boolean>;
24
+ /**
25
+ * Layer 4: Session compact - use session.md as summary
26
+ */
27
+ export declare function getSessionSummary(sessionPath: string): string | null;
28
+ /**
29
+ * Determine which compaction layer to use
30
+ */
31
+ export declare function runCompaction(config: CompactConfig, conversationPath: string, tokenEstimate: number, sessionPath?: string): Promise<'none' | 'micro' | 'auto' | 'full' | 'session'>;
32
+ /**
33
+ * Get token estimate for conversation
34
+ */
35
+ export declare function estimateConversationTokens(conversationPath: string): Promise<number>;
36
+ export { CompactConfig, DEFAULT_COMPACT_CONFIG };
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Compactor - 4-layer compression system
3
+ *
4
+ * Aligned with Claude Code compression:
5
+ * - Layer 1: Microcompact (clean old tool results)
6
+ * - Layer 2: Auto-compact (context threshold)
7
+ * - Layer 3: Full compact (Fork Agent summary)
8
+ * - Layer 4: Session compact (use session.md)
9
+ */
10
+ import { DEFAULT_COMPACT_CONFIG, } from './types.js';
11
+ import * as fs from 'fs/promises';
12
+ const ENCODING = 'utf-8';
13
+ const CONTENT_CLEARED = '[Old content cleared]';
14
+ /**
15
+ * Layer 1: Microcompact - clean old messages, keep recent N intact
16
+ */
17
+ export async function microCompact(filePath, keepCount = 10) {
18
+ try {
19
+ const content = await fs.readFile(filePath, ENCODING);
20
+ const lines = content.split('\n');
21
+ // Find message blocks (lines starting with **[timestamp])
22
+ const messageLines = [];
23
+ for (let i = 0; i < lines.length; i++) {
24
+ if (lines[i].match(/^\*\*\[/)) {
25
+ messageLines.push(i);
26
+ }
27
+ }
28
+ if (messageLines.length <= keepCount) {
29
+ return false; // Nothing to compact
30
+ }
31
+ // Clear old messages
32
+ const toKeep = messageLines.slice(-keepCount);
33
+ const toClear = messageLines.slice(0, -keepCount);
34
+ let newContent = lines.slice();
35
+ for (const idx of toClear) {
36
+ // Find the end of this message block
37
+ let endIdx = idx + 1;
38
+ while (endIdx < lines.length && lines[endIdx].trim() !== '' && !lines[endIdx].match(/^\*\*\[/)) {
39
+ endIdx++;
40
+ }
41
+ // Replace with cleared marker
42
+ newContent[idx] = CONTENT_CLEARED;
43
+ for (let i = idx + 1; i < endIdx; i++) {
44
+ newContent[i] = '';
45
+ }
46
+ }
47
+ await fs.writeFile(filePath, newContent.filter(l => l !== '').join('\n'), ENCODING);
48
+ return true;
49
+ }
50
+ catch (error) {
51
+ console.error('[Compactor] Microcompact failed:', error);
52
+ return false;
53
+ }
54
+ }
55
+ /**
56
+ * Layer 2: Auto-compact - check if context threshold exceeded
57
+ */
58
+ export function shouldAutoCompact(config, tokenEstimate) {
59
+ return tokenEstimate >= config.autoCompactThreshold;
60
+ }
61
+ /**
62
+ * Layer 3: Full compact - generate summary via LLM
63
+ */
64
+ export async function fullCompact(conversationPath, summary) {
65
+ try {
66
+ const compactedPath = conversationPath.replace('.md', '.compacted.md');
67
+ // Read existing content
68
+ const content = await fs.readFile(conversationPath, ENCODING);
69
+ // Write compacted version with summary
70
+ const compactedContent = `---
71
+ name: 压缩后的对话
72
+ description: 已压缩的对话记录
73
+ type: conversation
74
+ compacted: true
75
+ ---
76
+
77
+ # 对话摘要
78
+
79
+ ${summary}
80
+
81
+ ---
82
+
83
+ # 原始记录(已压缩)
84
+
85
+ ${content.slice(0, 5000)}... (see original file for full content)
86
+ `;
87
+ await fs.writeFile(compactedPath, compactedContent, ENCODING);
88
+ return true;
89
+ }
90
+ catch (error) {
91
+ console.error('[Compactor] Full compact failed:', error);
92
+ return false;
93
+ }
94
+ }
95
+ /**
96
+ * Layer 4: Session compact - use session.md as summary
97
+ */
98
+ export function getSessionSummary(sessionPath) {
99
+ // Would read session.md and extract key information
100
+ // This is the fastest - no LLM needed
101
+ try {
102
+ const content = require('fs').readFileSync(sessionPath, ENCODING);
103
+ // Extract key sections
104
+ const learnings = extractSection(content, '## Learnings');
105
+ const keyResults = extractSection(content, '## Key results');
106
+ const currentState = extractSection(content, '## Current State');
107
+ return [currentState, learnings, keyResults].filter(Boolean).join('\n\n');
108
+ }
109
+ catch {
110
+ return null;
111
+ }
112
+ }
113
+ function extractSection(content, section) {
114
+ const regex = new RegExp(`${section}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`);
115
+ const match = content.match(regex);
116
+ return match ? match[1].trim() : null;
117
+ }
118
+ /**
119
+ * Determine which compaction layer to use
120
+ */
121
+ export async function runCompaction(config, conversationPath, tokenEstimate, sessionPath) {
122
+ // Layer 4: Session compact (fastest, preferred)
123
+ if (sessionPath && tokenEstimate >= config.sessionCompactThreshold) {
124
+ const sessionSummary = getSessionSummary(sessionPath);
125
+ if (sessionSummary) {
126
+ return 'session';
127
+ }
128
+ }
129
+ // Layer 3: Full compact
130
+ if (tokenEstimate >= config.fullCompactThreshold) {
131
+ return 'full';
132
+ }
133
+ // Layer 2: Auto compact
134
+ if (tokenEstimate >= config.autoCompactThreshold) {
135
+ return 'auto';
136
+ }
137
+ // Layer 1: Micro compact
138
+ const result = await microCompact(conversationPath, config.microCompactKeep);
139
+ return result ? 'micro' : 'none';
140
+ }
141
+ /**
142
+ * Get token estimate for conversation
143
+ */
144
+ export async function estimateConversationTokens(conversationPath) {
145
+ try {
146
+ const content = await fs.readFile(conversationPath, ENCODING);
147
+ // Rough estimate: 4 characters per token
148
+ return Math.ceil(content.length / 4);
149
+ }
150
+ catch {
151
+ return 0;
152
+ }
153
+ }
154
+ export { DEFAULT_COMPACT_CONFIG };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * AI Summary Extractor - Extract memories from conversations
3
+ *
4
+ * Aligned with Claude Code extractMemories:
5
+ * - Fork Agent execution
6
+ * - Tool permission whitelist
7
+ * - Trigger on conversation milestones
8
+ */
9
+ import { ExtractionResult, WeChatMessage, ConversationSubtype } from './types.js';
10
+ export interface ExtractorConfig {
11
+ enabled: boolean;
12
+ triggerAfterMessages: number;
13
+ triggerAfterTokens: number;
14
+ maxTurns: number;
15
+ }
16
+ /**
17
+ * Check if extraction should be triggered
18
+ */
19
+ export declare function shouldExtract(config: ExtractorConfig, messageCount: number, tokenEstimate: number): boolean;
20
+ /**
21
+ * Build extraction prompt for AI
22
+ */
23
+ export declare function buildExtractionPrompt(conversationId: string, messages: WeChatMessage[], subtype: ConversationSubtype): string;
24
+ /**
25
+ * Parse extraction result from AI response
26
+ */
27
+ export declare function parseExtractionResult(response: string): ExtractionResult | null;
28
+ /**
29
+ * Simulate extraction (placeholder for actual LLM call)
30
+ * In real implementation, this would call the LLM via Fork Agent
31
+ */
32
+ export declare function extractWithLLM(prompt: string, apiKey?: string): Promise<ExtractionResult | null>;
33
+ /**
34
+ * Validate tool is allowed (aligned with Claude Code)
35
+ */
36
+ export declare function isToolAllowed(toolName: string): boolean;
37
+ /**
38
+ * Validate bash command is allowed (read-only)
39
+ */
40
+ export declare function isBashCommandAllowed(command: string): boolean;
41
+ /**
42
+ * Get extraction trigger status
43
+ */
44
+ export declare function getExtractionStatus(config: ExtractorConfig, lastExtractionMessageCount: number, currentMessageCount: number): {
45
+ shouldExtract: boolean;
46
+ messagesSinceLastExtraction: number;
47
+ };
48
+ export declare const DEFAULT_EXTRACTOR_CONFIG: ExtractorConfig;
@@ -0,0 +1,120 @@
1
+ /**
2
+ * AI Summary Extractor - Extract memories from conversations
3
+ *
4
+ * Aligned with Claude Code extractMemories:
5
+ * - Fork Agent execution
6
+ * - Tool permission whitelist
7
+ * - Trigger on conversation milestones
8
+ */
9
+ // ========== Constants ==========
10
+ const ALLOWED_TOOLS = ['Read', 'Grep', 'Glob', 'Edit', 'Write'];
11
+ const ALLOWED_BASH_COMMANDS = ['ls', 'find', 'grep', 'cat', 'stat', 'wc', 'head', 'tail'];
12
+ /**
13
+ * Check if extraction should be triggered
14
+ */
15
+ export function shouldExtract(config, messageCount, tokenEstimate) {
16
+ if (!config.enabled)
17
+ return false;
18
+ return (messageCount >= config.triggerAfterMessages ||
19
+ tokenEstimate >= config.triggerAfterTokens);
20
+ }
21
+ /**
22
+ * Build extraction prompt for AI
23
+ */
24
+ export function buildExtractionPrompt(conversationId, messages, subtype) {
25
+ const messageList = messages
26
+ .slice(-20) // Last 20 messages
27
+ .map(m => {
28
+ const sender = m.isSelf ? '我' : (subtype === 'friend' ? '对方' : '群成员');
29
+ return `[${new Date(m.timestamp).toLocaleString()}] ${sender}: ${m.content}`;
30
+ })
31
+ .join('\n');
32
+ return `## 任务
33
+
34
+ 分析以下与 ${conversationId} 的对话,提取关键信息并生成摘要。
35
+
36
+ ## 对话内容
37
+
38
+ ${messageList}
39
+
40
+ ## 输出要求
41
+
42
+ 请返回以下格式的 JSON:
43
+
44
+ {
45
+ "summary": "对话摘要 (2-3句话)",
46
+ "keyPoints": ["关键点1", "关键点2", "关键点3"],
47
+ "userProfileUpdate": {
48
+ "tags": ["如果发现新标签"],
49
+ "preferenceHints": "用户偏好提示",
50
+ "riskFlags": ["如果发现风险标记"]
51
+ },
52
+ "learnings": "从对话中学到的信息",
53
+ "actionItems": ["待跟进事项1", "待跟进事项2"]
54
+ }
55
+
56
+ 只返回 JSON,不要其他内容。`;
57
+ }
58
+ /**
59
+ * Parse extraction result from AI response
60
+ */
61
+ export function parseExtractionResult(response) {
62
+ try {
63
+ // Try to extract JSON from response
64
+ const jsonMatch = response.match(/\{[\s\S]*\}/);
65
+ if (!jsonMatch)
66
+ return null;
67
+ const result = JSON.parse(jsonMatch[0]);
68
+ return {
69
+ summary: result.summary || '',
70
+ keyPoints: result.keyPoints || [],
71
+ userProfileUpdate: result.userProfileUpdate || {},
72
+ learnings: result.learnings || '',
73
+ actionItems: result.actionItems || [],
74
+ };
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ /**
81
+ * Simulate extraction (placeholder for actual LLM call)
82
+ * In real implementation, this would call the LLM via Fork Agent
83
+ */
84
+ export async function extractWithLLM(prompt, apiKey) {
85
+ // Placeholder: In real implementation, this would:
86
+ // 1. Create Fork Agent with limited tools
87
+ // 2. Run with the prompt
88
+ // 3. Parse result
89
+ console.log('[Extractor] Would run extraction with prompt:', prompt.substring(0, 100) + '...');
90
+ // Return null for now - real implementation would call LLM
91
+ return null;
92
+ }
93
+ /**
94
+ * Validate tool is allowed (aligned with Claude Code)
95
+ */
96
+ export function isToolAllowed(toolName) {
97
+ return ALLOWED_TOOLS.includes(toolName);
98
+ }
99
+ /**
100
+ * Validate bash command is allowed (read-only)
101
+ */
102
+ export function isBashCommandAllowed(command) {
103
+ const cmd = command.split(' ')[0];
104
+ return ALLOWED_BASH_COMMANDS.includes(cmd);
105
+ }
106
+ /**
107
+ * Get extraction trigger status
108
+ */
109
+ export function getExtractionStatus(config, lastExtractionMessageCount, currentMessageCount) {
110
+ const messagesSinceLastExtraction = currentMessageCount - lastExtractionMessageCount;
111
+ const shouldExtract = shouldExtract(config, messagesSinceLastExtraction, 0 // Would calculate token estimate
112
+ );
113
+ return { shouldExtract, messagesSinceLastExtraction };
114
+ }
115
+ export const DEFAULT_EXTRACTOR_CONFIG = {
116
+ enabled: true,
117
+ triggerAfterMessages: 10,
118
+ triggerAfterTokens: 5000,
119
+ maxTurns: 5,
120
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Cache Module Exports
3
+ *
4
+ * All cache-related modules for WeChat Channel Plugin
5
+ */
6
+ export * from './types.js';
7
+ export * from './writer.js';
8
+ export * from './indexer.js';
9
+ export * from './extractor.js';
10
+ export * from './compactor.js';
11
+ export * from './syncer.js';
12
+ export * from './saas-connector.js';
13
+ export * from './message-queue.js';
14
+ export * from './manager.js';
15
+ export { createCacheManager, CacheManager } from './manager.js';