@wu529778790/open-im 1.11.1-beta.17 → 1.11.1-beta.19
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/dist/clawbot/client.d.ts +1 -1
- package/dist/clawbot/client.js +71 -3
- package/dist/clawbot/event-handler.d.ts +1 -1
- package/dist/clawbot/event-handler.js +9 -3
- package/dist/config-web.js +15 -0
- package/dist/logger.d.ts +5 -0
- package/dist/logger.js +21 -0
- package/dist/platform/handle-text-flow.js +3 -1
- package/package.json +1 -1
package/dist/clawbot/client.d.ts
CHANGED
|
@@ -10,5 +10,5 @@
|
|
|
10
10
|
import type { Config } from '../config.js';
|
|
11
11
|
import type { ClawBotState } from './types.js';
|
|
12
12
|
export declare function getChannelState(): ClawBotState;
|
|
13
|
-
export declare function initClawbot(config: Config, eventHandler: (chatId: string, msgId: string, content: string) => Promise<void>, onStateChange?: (state: ClawBotState) => void): Promise<void>;
|
|
13
|
+
export declare function initClawbot(config: Config, eventHandler: (chatId: string, msgId: string, content: string, imagePaths?: string[]) => Promise<void>, onStateChange?: (state: ClawBotState) => void): Promise<void>;
|
|
14
14
|
export declare function stopClawbot(): void;
|
package/dist/clawbot/client.js
CHANGED
|
@@ -12,6 +12,7 @@ import { createLogger } from '../logger.js';
|
|
|
12
12
|
import { jitteredDelay, isFatalReconnectError, SLOW_PROBE_MS } from '../shared/reconnect.js';
|
|
13
13
|
import { cacheContextToken } from './message-sender.js';
|
|
14
14
|
import { setClawbotContextToken, clearClawbotContextToken } from '../shared/active-chats.js';
|
|
15
|
+
import { decryptAes256CbcMedia, saveBufferMedia, createMediaTargetPath } from '../shared/media-storage.js';
|
|
15
16
|
import { CLAWBOT_POLL_INTERVAL_MS } from '../constants.js';
|
|
16
17
|
const log = createLogger('ClawBot');
|
|
17
18
|
const RECONNECT_DELAYS_MS = [3000, 5000, 10000, 20000, 30000];
|
|
@@ -110,6 +111,8 @@ function startPolling() {
|
|
|
110
111
|
}
|
|
111
112
|
// Process messages
|
|
112
113
|
const messages = res.messages ?? [];
|
|
114
|
+
// Step 1: Extract valid USER messages and cache context tokens
|
|
115
|
+
const userMessages = [];
|
|
113
116
|
for (const msg of messages) {
|
|
114
117
|
if (signal.aborted)
|
|
115
118
|
break;
|
|
@@ -120,7 +123,6 @@ function startPolling() {
|
|
|
120
123
|
continue;
|
|
121
124
|
const chatId = msg.from_user_id ?? '';
|
|
122
125
|
const msgId = String(msg.message_id ?? msg.seq ?? '');
|
|
123
|
-
const content = extracted;
|
|
124
126
|
if (!chatId) {
|
|
125
127
|
log.warn('ClawBot message missing from_user_id, skipping');
|
|
126
128
|
continue;
|
|
@@ -130,10 +132,34 @@ function startPolling() {
|
|
|
130
132
|
cacheContextToken(chatId, msg.context_token);
|
|
131
133
|
setClawbotContextToken(msg.context_token);
|
|
132
134
|
}
|
|
133
|
-
|
|
135
|
+
// Extract and download images from message
|
|
136
|
+
const imagePaths = await extractImages(msg);
|
|
137
|
+
userMessages.push({ chatId, msgId, content: extracted, imagePaths: imagePaths.length > 0 ? imagePaths : undefined });
|
|
138
|
+
}
|
|
139
|
+
// Step 2: Aggregate consecutive messages from the same user
|
|
140
|
+
// ClawBot splits image+text into separate messages; combine them
|
|
141
|
+
const aggregated = [];
|
|
142
|
+
for (const m of userMessages) {
|
|
143
|
+
const last = aggregated[aggregated.length - 1];
|
|
144
|
+
if (last && last.chatId === m.chatId) {
|
|
145
|
+
// Same user — merge content and image paths
|
|
146
|
+
last.content = `${last.content}\n${m.content}`;
|
|
147
|
+
if (m.imagePaths?.length) {
|
|
148
|
+
last.imagePaths = [...(last.imagePaths ?? []), ...m.imagePaths];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
aggregated.push({ chatId: m.chatId, msgId: m.msgId, content: m.content, imagePaths: m.imagePaths });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Step 3: Dispatch aggregated messages
|
|
156
|
+
for (const m of aggregated) {
|
|
157
|
+
if (signal.aborted)
|
|
158
|
+
break;
|
|
159
|
+
log.info(`ClawBot message: chatId=${m.chatId}, msgId=${m.msgId}, content="${m.content.substring(0, 100)}", images=${m.imagePaths?.length ?? 0}`);
|
|
134
160
|
if (messageHandler) {
|
|
135
161
|
try {
|
|
136
|
-
await messageHandler(chatId, msgId, content);
|
|
162
|
+
await messageHandler(m.chatId, m.msgId, m.content, m.imagePaths);
|
|
137
163
|
}
|
|
138
164
|
catch (err) {
|
|
139
165
|
log.error('Error in ClawBot message handler:', err);
|
|
@@ -212,6 +238,48 @@ function stopWatchdog() {
|
|
|
212
238
|
watchdogTimer = null;
|
|
213
239
|
}
|
|
214
240
|
}
|
|
241
|
+
/**
|
|
242
|
+
* Extract and download images from a ClawBot message's item_list.
|
|
243
|
+
* Images are encrypted with AES and hosted on CDN.
|
|
244
|
+
*/
|
|
245
|
+
async function extractImages(msg) {
|
|
246
|
+
if (!msg.item_list?.length)
|
|
247
|
+
return [];
|
|
248
|
+
const paths = [];
|
|
249
|
+
for (const item of msg.item_list) {
|
|
250
|
+
if (item.type !== 2 /* MessageItemType.IMAGE */)
|
|
251
|
+
continue;
|
|
252
|
+
const media = item.image_item?.media;
|
|
253
|
+
if (!media?.cdn_url)
|
|
254
|
+
continue;
|
|
255
|
+
try {
|
|
256
|
+
// Download from CDN
|
|
257
|
+
const response = await fetch(media.cdn_url, { signal: AbortSignal.timeout(30_000) });
|
|
258
|
+
if (!response.ok) {
|
|
259
|
+
log.warn(`Image download failed: HTTP ${response.status}`);
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
263
|
+
// Decrypt if AES key provided
|
|
264
|
+
let decrypted;
|
|
265
|
+
if (media.aes_key) {
|
|
266
|
+
decrypted = decryptAes256CbcMedia(buffer, media.aes_key);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
decrypted = buffer;
|
|
270
|
+
}
|
|
271
|
+
// Save to disk
|
|
272
|
+
const targetPath = createMediaTargetPath('.jpg', `clawbot-${Date.now()}`);
|
|
273
|
+
await saveBufferMedia(decrypted, targetPath);
|
|
274
|
+
paths.push(targetPath);
|
|
275
|
+
log.info(`ClawBot image saved: ${targetPath}`);
|
|
276
|
+
}
|
|
277
|
+
catch (err) {
|
|
278
|
+
log.warn('Failed to process ClawBot image:', err);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return paths;
|
|
282
|
+
}
|
|
215
283
|
/**
|
|
216
284
|
* Extract text content from an iLink message's item_list.
|
|
217
285
|
* Returns the first text item found, or a placeholder for media types.
|
|
@@ -7,6 +7,6 @@ export interface ClawBotEventHandlerHandle {
|
|
|
7
7
|
stop: () => void;
|
|
8
8
|
runningTasks: Map<string, import('../shared/ai-task.js').TaskRunState>;
|
|
9
9
|
getRunningTaskCount: () => number;
|
|
10
|
-
handleEvent: (chatId: string, msgId: string, content: string) => Promise<void>;
|
|
10
|
+
handleEvent: (chatId: string, msgId: string, content: string, imagePaths?: string[]) => Promise<void>;
|
|
11
11
|
}
|
|
12
12
|
export declare function setupClawbotHandlers(config: Config, sessionManager: SessionManager): ClawBotEventHandlerHandle;
|
|
@@ -33,8 +33,8 @@ export function setupClawbotHandlers(config, sessionManager) {
|
|
|
33
33
|
return () => { };
|
|
34
34
|
},
|
|
35
35
|
};
|
|
36
|
-
async function handleEvent(chatId, msgId, content) {
|
|
37
|
-
log.info(`[handleEvent] chatId=${chatId}, msgId=${msgId}, content="${content.substring(0, 100)}"`);
|
|
36
|
+
async function handleEvent(chatId, msgId, content, imagePaths) {
|
|
37
|
+
log.info(`[handleEvent] chatId=${chatId}, msgId=${msgId}, content="${content.substring(0, 100)}", images=${imagePaths?.length ?? 0}`);
|
|
38
38
|
const userId = chatId;
|
|
39
39
|
const text = content.trim();
|
|
40
40
|
const msgIdSender = {
|
|
@@ -61,11 +61,17 @@ export function setupClawbotHandlers(config, sessionManager) {
|
|
|
61
61
|
},
|
|
62
62
|
}),
|
|
63
63
|
});
|
|
64
|
+
// Prepend image paths to prompt so Claude can read them with its Read tool
|
|
65
|
+
let enrichedText = text;
|
|
66
|
+
if (imagePaths?.length) {
|
|
67
|
+
const imgRefs = imagePaths.map(p => `[用户发送了图片: ${p}]`).join('\n');
|
|
68
|
+
enrichedText = `${imgRefs}\n${text}`;
|
|
69
|
+
}
|
|
64
70
|
await handleTextFlow({
|
|
65
71
|
platform: 'clawbot',
|
|
66
72
|
userId,
|
|
67
73
|
chatId,
|
|
68
|
-
text,
|
|
74
|
+
text: enrichedText,
|
|
69
75
|
ctx,
|
|
70
76
|
handleAIRequest,
|
|
71
77
|
sendTextReply: (c, t) => sendTextReply(c, t),
|
package/dist/config-web.js
CHANGED
|
@@ -1095,6 +1095,21 @@ export async function startWebConfigServer(options) {
|
|
|
1095
1095
|
json(response, 200, { platforms, serviceStatus: getServiceStatus() }, request);
|
|
1096
1096
|
return;
|
|
1097
1097
|
}
|
|
1098
|
+
if (request.method === "GET" && requestUrl.pathname === "/api/metrics") {
|
|
1099
|
+
const file = loadFileConfig();
|
|
1100
|
+
const platforms = getHealthPlatformSnapshot(file);
|
|
1101
|
+
const status = getServiceStatus();
|
|
1102
|
+
const uptime = process.uptime();
|
|
1103
|
+
const mem = process.memoryUsage();
|
|
1104
|
+
json(response, 200, {
|
|
1105
|
+
uptime: Math.round(uptime),
|
|
1106
|
+
memory: { rss: mem.rss, heapUsed: mem.heapUsed, heapTotal: mem.heapTotal },
|
|
1107
|
+
service: { running: status.running, pid: status.pid },
|
|
1108
|
+
platforms: Object.entries(platforms).map(([k, v]) => ({ name: k, configured: v.configured, enabled: v.enabled })),
|
|
1109
|
+
timestamp: new Date().toISOString(),
|
|
1110
|
+
}, request);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1098
1113
|
if (request.method === "POST" && requestUrl.pathname === "/api/service/start") {
|
|
1099
1114
|
try {
|
|
1100
1115
|
const config = loadConfig();
|
package/dist/logger.d.ts
CHANGED
|
@@ -23,6 +23,11 @@ export declare function createLogger(tag: string): {
|
|
|
23
23
|
debug: (msg: string, ...args: unknown[]) => void;
|
|
24
24
|
infoEvent: (event: string, data?: Record<string, unknown>, msg?: string) => void;
|
|
25
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* Audit log — records user interactions for debugging and compliance.
|
|
28
|
+
* Always enabled, writes to audit.log.
|
|
29
|
+
*/
|
|
30
|
+
export declare function auditLog(platform: string, userId: string, action: string, detail?: Record<string, unknown>): void;
|
|
26
31
|
export declare function emitStructuredEvent(tag: string, event: string, data?: Record<string, unknown>, level?: LogLevel, msg?: string): void;
|
|
27
32
|
export declare function shutdownLoggerTelemetry(): Promise<void>;
|
|
28
33
|
export declare function closeLogger(): Promise<void>;
|
package/dist/logger.js
CHANGED
|
@@ -12,6 +12,7 @@ let logDir = DEFAULT_LOG_DIR;
|
|
|
12
12
|
let minLevel = LOG_LEVELS.DEBUG;
|
|
13
13
|
let logStream;
|
|
14
14
|
let eventsStream;
|
|
15
|
+
let auditStream;
|
|
15
16
|
let telemetryEnabled = false;
|
|
16
17
|
let telemetryStatsTimer = null;
|
|
17
18
|
let lastTelemetryStatsSignature = '';
|
|
@@ -77,6 +78,12 @@ export function initLogger(dirOrOpts, level, telemetry) {
|
|
|
77
78
|
mkdirSync(logDir, { recursive: true });
|
|
78
79
|
rotateOldLogs();
|
|
79
80
|
logStream = createWriteStream(join(logDir, getLogFileName()), { flags: 'a' });
|
|
81
|
+
// Audit log — always enabled, separate file
|
|
82
|
+
if (auditStream) {
|
|
83
|
+
auditStream.end();
|
|
84
|
+
auditStream = undefined;
|
|
85
|
+
}
|
|
86
|
+
auditStream = createWriteStream(join(logDir, 'audit.log'), { flags: 'a' });
|
|
80
87
|
telemetryEnabled = !!tel?.enabled;
|
|
81
88
|
if (telemetryStatsTimer) {
|
|
82
89
|
clearInterval(telemetryStatsTimer);
|
|
@@ -135,6 +142,20 @@ export function createLogger(tag) {
|
|
|
135
142
|
infoEvent: (event, data, msg) => emitStructuredEvent(tag, event, data, 'INFO', msg),
|
|
136
143
|
};
|
|
137
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Audit log — records user interactions for debugging and compliance.
|
|
147
|
+
* Always enabled, writes to audit.log.
|
|
148
|
+
*/
|
|
149
|
+
export function auditLog(platform, userId, action, detail) {
|
|
150
|
+
const entry = {
|
|
151
|
+
ts: new Date().toISOString(),
|
|
152
|
+
platform,
|
|
153
|
+
userId,
|
|
154
|
+
action,
|
|
155
|
+
...detail,
|
|
156
|
+
};
|
|
157
|
+
auditStream?.write(JSON.stringify(entry) + '\n');
|
|
158
|
+
}
|
|
138
159
|
export function emitStructuredEvent(tag, event, data, level = 'INFO', msg = '') {
|
|
139
160
|
if (!telemetryEnabled)
|
|
140
161
|
return;
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { setActiveChatId } from '../shared/active-chats.js';
|
|
19
19
|
import { setChatUser } from '../shared/chat-user-map.js';
|
|
20
|
-
import { createLogger } from '../logger.js';
|
|
20
|
+
import { createLogger, auditLog } from '../logger.js';
|
|
21
21
|
import { handleEnqueueResult, DEFAULT_QUEUE_FULL_MESSAGE, DEFAULT_QUEUED_MESSAGE } from '../shared/utils.js';
|
|
22
22
|
const log = createLogger('TextFlow');
|
|
23
23
|
/** Default access-denied message. */
|
|
@@ -79,6 +79,8 @@ export async function handleTextFlow(params) {
|
|
|
79
79
|
if (!text) {
|
|
80
80
|
return true;
|
|
81
81
|
}
|
|
82
|
+
// Audit: log user interaction
|
|
83
|
+
auditLog(platform, userId, 'message', { text: text.substring(0, 200) });
|
|
82
84
|
// 6. Enqueue AI request
|
|
83
85
|
if (customEnqueue) {
|
|
84
86
|
// Platform-specific enqueue (e.g., WorkBuddy with extra context)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/open-im",
|
|
3
|
-
"version": "1.11.1-beta.
|
|
3
|
+
"version": "1.11.1-beta.19",
|
|
4
4
|
"description": "Your AI coding assistant, in every chat app. Multi-platform IM bridge for Claude Code, Codex, and CodeBuddy.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|