@wu529778790/open-im 1.6.1-beta.1 → 1.6.1-beta.2
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/cli.js +25 -0
- package/dist/wework/message-sender.d.ts +8 -34
- package/dist/wework/message-sender.js +22 -65
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -75,6 +75,29 @@ async function cmdStop() {
|
|
|
75
75
|
console.log("\nopen-im stopped.");
|
|
76
76
|
console.log(` pid: ${result.pid}`);
|
|
77
77
|
}
|
|
78
|
+
async function cmdRestart() {
|
|
79
|
+
const status = getManagerStatus();
|
|
80
|
+
if (status.pid) {
|
|
81
|
+
await stopBackgroundService();
|
|
82
|
+
const stopped = await stopManagerProcess();
|
|
83
|
+
console.log("\nopen-im stopped.");
|
|
84
|
+
console.log(` pid: ${stopped.pid}`);
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log("open-im is not running in the background. Starting a new instance.");
|
|
88
|
+
}
|
|
89
|
+
if (!(await ensureConfigured("start"))) {
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
const { updated } = await checkAndUpdate();
|
|
93
|
+
if (updated) {
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
const child = await startManagerProcess(process.cwd());
|
|
97
|
+
console.log("\nopen-im restarted in the background.");
|
|
98
|
+
console.log(` pid: ${child.pid}`);
|
|
99
|
+
console.log(` config page: ${getWebConfigUrl()}`);
|
|
100
|
+
}
|
|
78
101
|
async function cmdInit() {
|
|
79
102
|
console.log("\nopen-im CLI setup\n");
|
|
80
103
|
const saved = await ensureConfigured("init");
|
|
@@ -101,6 +124,7 @@ Usage: open-im <command>
|
|
|
101
124
|
Commands:
|
|
102
125
|
start Run the full app in the background and serve the local config page
|
|
103
126
|
stop Stop the full app
|
|
127
|
+
restart Restart the full app in the background
|
|
104
128
|
init Run CLI setup
|
|
105
129
|
dev Run in the foreground for debugging
|
|
106
130
|
|
|
@@ -117,6 +141,7 @@ const cmd = process.argv[2];
|
|
|
117
141
|
const commands = {
|
|
118
142
|
start: cmdStart,
|
|
119
143
|
stop: cmdStop,
|
|
144
|
+
restart: cmdRestart,
|
|
120
145
|
init: cmdInit,
|
|
121
146
|
dev: cmdDev,
|
|
122
147
|
};
|
|
@@ -1,56 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WeWork (
|
|
3
|
-
*
|
|
2
|
+
* WeWork (企业微信/WeCom) Message Sender
|
|
3
|
+
* 通过 WebSocket `aibot_respond_msg` 发送消息,并透传 `req_id`
|
|
4
4
|
*/
|
|
5
5
|
export declare function setCurrentReqId(reqId: string | null): void;
|
|
6
6
|
type MessageStatus = 'thinking' | 'streaming' | 'done' | 'error';
|
|
7
7
|
/**
|
|
8
|
-
* Send thinking message to WeWork
|
|
9
|
-
* Returns a stream ID that can be used for updates
|
|
10
|
-
* @param reqId
|
|
8
|
+
* Send thinking message to WeWork.
|
|
9
|
+
* Returns a stream ID that can be used for updates.
|
|
10
|
+
* @param reqId 消息回调里的 `req_id`,用于通过 WebSocket 回复
|
|
11
11
|
*/
|
|
12
12
|
export declare function sendThinkingMessage(chatId: string, _replyToMessageId: string | undefined, toolId?: string, reqId?: string): Promise<string>;
|
|
13
|
-
/**
|
|
14
|
-
* Update existing message in WeWork
|
|
15
|
-
* Note: WeWork doesn't support message editing, so we send new stream messages
|
|
16
|
-
*/
|
|
17
13
|
export declare function updateMessage(chatId: string, streamId: string, content: string, status: MessageStatus, note?: string, toolId?: string, reqId?: string): Promise<void>;
|
|
18
|
-
/**
|
|
19
|
-
* Send final messages to WeWork (handle long content)
|
|
20
|
-
*/
|
|
21
14
|
export declare function sendFinalMessages(chatId: string, streamId: string, fullContent: string, note: string, toolId?: string, reqId?: string): Promise<void>;
|
|
22
15
|
/**
|
|
23
|
-
*
|
|
16
|
+
* 主动推送文本,用于启动/关闭通知等场景,无需 req_id。
|
|
24
17
|
*/
|
|
25
18
|
export declare function sendProactiveTextReply(chatId: string, text: string): Promise<void>;
|
|
26
19
|
/**
|
|
27
|
-
* Send simple text reply to WeWork
|
|
28
|
-
* @param threadCtxOrReqId
|
|
20
|
+
* Send simple text reply to WeWork.
|
|
21
|
+
* @param threadCtxOrReqId 兼容 MessageSender 的 threadCtx;若为 string 则作为 reqId 使用
|
|
29
22
|
*/
|
|
30
23
|
export declare function sendTextReply(chatId: string, text: string, threadCtxOrReqId?: import('../shared/types.js').ThreadContext | string): Promise<void>;
|
|
31
|
-
/**
|
|
32
|
-
* Send permission card with action buttons (for permission prompts)
|
|
33
|
-
* Note: WeWork doesn't support interactive cards, so we send text with instructions
|
|
34
|
-
*/
|
|
35
24
|
export declare function sendPermissionCard(chatId: string, requestId: string, toolName: string, toolInput: string, reqId?: string): Promise<void>;
|
|
36
|
-
/**
|
|
37
|
-
* Send mode switch card
|
|
38
|
-
*/
|
|
39
25
|
export declare function sendModeCard(chatId: string, _userId: string, currentMode: string, reqId?: string): Promise<void>;
|
|
40
|
-
/**
|
|
41
|
-
* Send image reply
|
|
42
|
-
*/
|
|
43
26
|
export declare function sendImageReply(chatId: string, imagePath: string): Promise<void>;
|
|
44
|
-
/**
|
|
45
|
-
* Send directory selection (not supported in WeWork, use text instead)
|
|
46
|
-
*/
|
|
47
27
|
export declare function sendDirectorySelection(chatId: string, currentDir: string, _userId: string): Promise<void>;
|
|
48
|
-
/**
|
|
49
|
-
* Start typing indicator (WeWork doesn't support this)
|
|
50
|
-
*/
|
|
51
28
|
export declare function startTypingLoop(_chatId: string): () => void;
|
|
52
|
-
/**
|
|
53
|
-
* Send error message
|
|
54
|
-
*/
|
|
55
29
|
export declare function sendErrorMessage(chatId: string, error: string, reqId?: string): Promise<void>;
|
|
56
30
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* WeWork (
|
|
3
|
-
*
|
|
2
|
+
* WeWork (企业微信/WeCom) Message Sender
|
|
3
|
+
* 通过 WebSocket `aibot_respond_msg` 发送消息,并透传 `req_id`
|
|
4
4
|
*/
|
|
5
5
|
import { sendText, sendStream, sendStreamWithItems, sendProactiveMessage } from './client.js';
|
|
6
6
|
import { createLogger } from '../logger.js';
|
|
@@ -11,7 +11,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
11
11
|
const log = createLogger('WeWorkSender');
|
|
12
12
|
const STREAM_SEND_INTERVAL_MS = 900;
|
|
13
13
|
const STREAM_SAFE_TTL_MS = 5 * 60 * 1000;
|
|
14
|
-
/**
|
|
14
|
+
/** 当前同步处理中的 req_id,仅用于 commandHandler 等同步调用。 */
|
|
15
15
|
let currentReqId = null;
|
|
16
16
|
export function setCurrentReqId(reqId) {
|
|
17
17
|
currentReqId = reqId;
|
|
@@ -25,31 +25,22 @@ function getReqId(explicitReqId) {
|
|
|
25
25
|
return id;
|
|
26
26
|
}
|
|
27
27
|
const STATUS_CONFIG = {
|
|
28
|
-
thinking: { icon: '
|
|
29
|
-
streaming: { icon: '
|
|
30
|
-
done: { icon: '[
|
|
31
|
-
error: { icon: '[error]', title: '
|
|
28
|
+
thinking: { icon: '[thinking]', title: '思考中' },
|
|
29
|
+
streaming: { icon: '[streaming]', title: '输出中' },
|
|
30
|
+
done: { icon: '[done]', title: '完成' },
|
|
31
|
+
error: { icon: '[error]', title: '错误' },
|
|
32
32
|
};
|
|
33
33
|
function getToolTitle(toolId, status) {
|
|
34
34
|
const name = getAIToolDisplayName(toolId);
|
|
35
35
|
const statusText = STATUS_CONFIG[status].title;
|
|
36
36
|
return status === 'done' ? name : `${name} - ${statusText}`;
|
|
37
37
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Generate unique request ID
|
|
40
|
-
*/
|
|
41
38
|
function generateReqId() {
|
|
42
39
|
return `${Date.now()}-${randomBytes(8).toString('hex')}`;
|
|
43
40
|
}
|
|
44
|
-
/**
|
|
45
|
-
* Generate unique stream ID for WeWork streaming responses
|
|
46
|
-
*/
|
|
47
41
|
function generateStreamId() {
|
|
48
42
|
return `${Date.now()}-${randomBytes(8).toString('hex')}`;
|
|
49
43
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Format message for WeWork (markdown-like format)
|
|
52
|
-
*/
|
|
53
44
|
function formatWeWorkMessage(title, content, status, note) {
|
|
54
45
|
const statusConfig = STATUS_CONFIG[status];
|
|
55
46
|
let message = `${statusConfig.icon} **${title}**\n\n`;
|
|
@@ -57,10 +48,10 @@ function formatWeWorkMessage(title, content, status, note) {
|
|
|
57
48
|
message += `${content}\n\n`;
|
|
58
49
|
}
|
|
59
50
|
else if (status === 'thinking') {
|
|
60
|
-
message += `_
|
|
51
|
+
message += `_正在思考,请稍候..._\n\n[thinking] **准备中**\n\n`;
|
|
61
52
|
}
|
|
62
53
|
if (note) {
|
|
63
|
-
message += `---\n\n
|
|
54
|
+
message += `---\n\n[note] **${note}**`;
|
|
64
55
|
}
|
|
65
56
|
return message;
|
|
66
57
|
}
|
|
@@ -120,9 +111,9 @@ async function flushStreamUpdate(streamId, state) {
|
|
|
120
111
|
}
|
|
121
112
|
}
|
|
122
113
|
/**
|
|
123
|
-
* Send thinking message to WeWork
|
|
124
|
-
* Returns a stream ID that can be used for updates
|
|
125
|
-
* @param reqId
|
|
114
|
+
* Send thinking message to WeWork.
|
|
115
|
+
* Returns a stream ID that can be used for updates.
|
|
116
|
+
* @param reqId 消息回调里的 `req_id`,用于通过 WebSocket 回复
|
|
126
117
|
*/
|
|
127
118
|
export async function sendThinkingMessage(chatId, _replyToMessageId, toolId = 'claude', reqId) {
|
|
128
119
|
const streamId = generateStreamId();
|
|
@@ -130,9 +121,7 @@ export async function sendThinkingMessage(chatId, _replyToMessageId, toolId = 'c
|
|
|
130
121
|
const content = formatWeWorkMessage(title, '', 'thinking');
|
|
131
122
|
try {
|
|
132
123
|
log.info(`Sending thinking message to user ${chatId}, streamId=${streamId}`);
|
|
133
|
-
// Store initial stream state
|
|
134
124
|
getOrCreateStreamState(streamId, chatId);
|
|
135
|
-
// Send initial stream message (not finished)
|
|
136
125
|
sendStream(getReqId(reqId), streamId, content, false);
|
|
137
126
|
log.info(`Thinking message sent: ${streamId}`);
|
|
138
127
|
return streamId;
|
|
@@ -142,10 +131,6 @@ export async function sendThinkingMessage(chatId, _replyToMessageId, toolId = 'c
|
|
|
142
131
|
throw err;
|
|
143
132
|
}
|
|
144
133
|
}
|
|
145
|
-
/**
|
|
146
|
-
* Update existing message in WeWork
|
|
147
|
-
* Note: WeWork doesn't support message editing, so we send new stream messages
|
|
148
|
-
*/
|
|
149
134
|
export async function updateMessage(chatId, streamId, content, status, note, toolId = 'claude', reqId) {
|
|
150
135
|
const title = getToolTitle(toolId, status);
|
|
151
136
|
const message = formatWeWorkMessage(title, content, status, note);
|
|
@@ -167,14 +152,10 @@ export async function updateMessage(chatId, streamId, content, status, note, too
|
|
|
167
152
|
throw err;
|
|
168
153
|
}
|
|
169
154
|
}
|
|
170
|
-
/**
|
|
171
|
-
* Send final messages to WeWork (handle long content)
|
|
172
|
-
*/
|
|
173
155
|
export async function sendFinalMessages(chatId, streamId, fullContent, note, toolId = 'claude', reqId) {
|
|
174
156
|
const title = getToolTitle(toolId, 'done');
|
|
175
157
|
const parts = splitLongContent(fullContent, MAX_WEWORK_MESSAGE_LENGTH);
|
|
176
|
-
|
|
177
|
-
const finalMessage = formatWeWorkMessage(title, parts[0], 'done', parts.length > 1 ? `鍐呭杈冮暱锛屽凡鍒嗘鍙戦€?(1/${parts.length})` : note);
|
|
158
|
+
const finalMessage = formatWeWorkMessage(title, parts[0], 'done', parts.length > 1 ? `内容较长,已分段发送 (1/${parts.length})` : note);
|
|
178
159
|
try {
|
|
179
160
|
const state = streamStates.get(streamId);
|
|
180
161
|
const shouldFallbackToText = !!state && (state.expired || Date.now() - state.createdAt >= STREAM_SAFE_TTL_MS);
|
|
@@ -197,10 +178,9 @@ export async function sendFinalMessages(chatId, streamId, fullContent, note, too
|
|
|
197
178
|
log.info(`Final stream expired, sent text fallback instead: streamId=${streamId}`);
|
|
198
179
|
}
|
|
199
180
|
streamStates.delete(streamId);
|
|
200
|
-
// Send remaining parts as separate messages
|
|
201
181
|
for (let i = 1; i < parts.length; i++) {
|
|
202
182
|
try {
|
|
203
|
-
const partContent = `${parts[i]}\n\n_*(
|
|
183
|
+
const partContent = `${parts[i]}\n\n_*(续 ${i + 1}/${parts.length})*_`;
|
|
204
184
|
const partMessage = formatWeWorkMessage(title, partContent, 'done', i === parts.length - 1 ? note : undefined);
|
|
205
185
|
sendText(getReqId(reqId), partMessage);
|
|
206
186
|
log.info(`Final message part ${i + 1}/${parts.length} sent`);
|
|
@@ -215,10 +195,10 @@ export async function sendFinalMessages(chatId, streamId, fullContent, note, too
|
|
|
215
195
|
}
|
|
216
196
|
}
|
|
217
197
|
/**
|
|
218
|
-
*
|
|
198
|
+
* 主动推送文本,用于启动/关闭通知等场景,无需 req_id。
|
|
219
199
|
*/
|
|
220
200
|
export async function sendProactiveTextReply(chatId, text) {
|
|
221
|
-
const message = formatWeWorkMessage('
|
|
201
|
+
const message = formatWeWorkMessage('[open-im]', text, 'done');
|
|
222
202
|
try {
|
|
223
203
|
sendProactiveMessage(chatId, message);
|
|
224
204
|
log.info(`Proactive text sent to user ${chatId}`);
|
|
@@ -228,14 +208,12 @@ export async function sendProactiveTextReply(chatId, text) {
|
|
|
228
208
|
}
|
|
229
209
|
}
|
|
230
210
|
/**
|
|
231
|
-
* Send simple text reply to WeWork
|
|
232
|
-
* @param threadCtxOrReqId
|
|
211
|
+
* Send simple text reply to WeWork.
|
|
212
|
+
* @param threadCtxOrReqId 兼容 MessageSender 的 threadCtx;若为 string 则作为 reqId 使用
|
|
233
213
|
*/
|
|
234
214
|
export async function sendTextReply(chatId, text, threadCtxOrReqId) {
|
|
235
|
-
const message = formatWeWorkMessage('
|
|
236
|
-
// 鏄惧紡浼犻€掔殑 reqId锛堢敤浜庡吋瀹?MessageSender 鎺ュ彛锛?
|
|
215
|
+
const message = formatWeWorkMessage('[open-im]', text, 'done');
|
|
237
216
|
const explicitReqId = typeof threadCtxOrReqId === 'string' ? threadCtxOrReqId : undefined;
|
|
238
|
-
// 鍥為€€鍒板綋鍓嶈姹傜殑 reqId锛堝湪 handleEvent 涓€氳繃 setCurrentReqId 璁剧疆锛?
|
|
239
217
|
const effectiveReqId = explicitReqId ?? currentReqId;
|
|
240
218
|
try {
|
|
241
219
|
sendText(getReqId(effectiveReqId ?? undefined), message);
|
|
@@ -245,10 +223,6 @@ export async function sendTextReply(chatId, text, threadCtxOrReqId) {
|
|
|
245
223
|
log.error('Failed to send text reply:', err);
|
|
246
224
|
}
|
|
247
225
|
}
|
|
248
|
-
/**
|
|
249
|
-
* Send permission card with action buttons (for permission prompts)
|
|
250
|
-
* Note: WeWork doesn't support interactive cards, so we send text with instructions
|
|
251
|
-
*/
|
|
252
226
|
export async function sendPermissionCard(chatId, requestId, toolName, toolInput, reqId) {
|
|
253
227
|
const message = [
|
|
254
228
|
'[Permission Request]',
|
|
@@ -273,9 +247,6 @@ export async function sendPermissionCard(chatId, requestId, toolName, toolInput,
|
|
|
273
247
|
log.error('Failed to send permission card:', err);
|
|
274
248
|
}
|
|
275
249
|
}
|
|
276
|
-
/**
|
|
277
|
-
* Send mode switch card
|
|
278
|
-
*/
|
|
279
250
|
export async function sendModeCard(chatId, _userId, currentMode, reqId) {
|
|
280
251
|
const { MODE_LABELS } = await import('../permission-mode/types.js');
|
|
281
252
|
const label = MODE_LABELS[currentMode] || currentMode;
|
|
@@ -298,14 +269,11 @@ export async function sendModeCard(chatId, _userId, currentMode, reqId) {
|
|
|
298
269
|
log.error('Failed to send mode card:', err);
|
|
299
270
|
}
|
|
300
271
|
}
|
|
301
|
-
/**
|
|
302
|
-
* Send image reply
|
|
303
|
-
*/
|
|
304
272
|
export async function sendImageReply(chatId, imagePath) {
|
|
305
273
|
try {
|
|
306
274
|
const reqId = getReqId();
|
|
307
275
|
if (!reqId) {
|
|
308
|
-
await sendTextReply(chatId,
|
|
276
|
+
await sendTextReply(chatId, `Generated image saved at: ${imagePath}`);
|
|
309
277
|
return;
|
|
310
278
|
}
|
|
311
279
|
const imageBuffer = await readFile(imagePath);
|
|
@@ -320,28 +288,17 @@ export async function sendImageReply(chatId, imagePath) {
|
|
|
320
288
|
}
|
|
321
289
|
catch (err) {
|
|
322
290
|
log.warn('Failed to send native WeWork image reply, falling back to text path:', err);
|
|
323
|
-
await sendTextReply(chatId,
|
|
291
|
+
await sendTextReply(chatId, `Generated image saved at: ${imagePath}`);
|
|
324
292
|
}
|
|
325
293
|
}
|
|
326
|
-
/**
|
|
327
|
-
* Send directory selection (not supported in WeWork, use text instead)
|
|
328
|
-
*/
|
|
329
294
|
export async function sendDirectorySelection(chatId, currentDir, _userId) {
|
|
330
295
|
await sendTextReply(chatId, `Current directory: ${currentDir}\n\nUse \`/cd <directory>\` to switch.`);
|
|
331
296
|
}
|
|
332
|
-
/**
|
|
333
|
-
* Start typing indicator (WeWork doesn't support this)
|
|
334
|
-
*/
|
|
335
297
|
export function startTypingLoop(_chatId) {
|
|
336
|
-
// WeWork doesn't have a typing indicator like Telegram
|
|
337
|
-
// Return a no-op function
|
|
338
298
|
return () => { };
|
|
339
299
|
}
|
|
340
|
-
/**
|
|
341
|
-
* Send error message
|
|
342
|
-
*/
|
|
343
300
|
export async function sendErrorMessage(chatId, error, reqId) {
|
|
344
|
-
const message = formatWeWorkMessage('
|
|
301
|
+
const message = formatWeWorkMessage('错误', error, 'error');
|
|
345
302
|
try {
|
|
346
303
|
sendText(getReqId(reqId), message);
|
|
347
304
|
log.info(`Error message sent to user ${chatId}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wu529778790/open-im",
|
|
3
|
-
"version": "1.6.1-beta.
|
|
3
|
+
"version": "1.6.1-beta.2",
|
|
4
4
|
"description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, Cursor)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"init": "tsx src/cli.ts init",
|
|
19
19
|
"start": "node dist/cli.js start",
|
|
20
20
|
"stop": "node dist/cli.js stop",
|
|
21
|
+
"restart": "node dist/cli.js restart",
|
|
21
22
|
"test": "vitest run",
|
|
22
23
|
"test:watch": "vitest watch",
|
|
23
24
|
"prepublishOnly": "npm run build"
|