@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 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 (浼佷笟寰俊) Message Sender - Send messages to WeWork
3
- * 閫氳繃 WebSocket aibot_respond_msg 鍙戦€侊紝闇€閫忎紶 req_id
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 - 娑堟伅鍥炶皟鐨?req_id锛岀敤浜?WebSocket 鍥炲
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
- * 涓诲姩鎺ㄩ€佹枃鏈紙鐢ㄤ簬鍚姩/鍏抽棴閫氱煡绛夛紝鏃犻渶 req_id锛?
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 - 鍏煎 MessageSender 鐨?threadCtx锛涜嫢涓?string 鍒欎綔涓?reqId 浣跨敤
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 (浼佷笟寰俊) Message Sender - Send messages to WeWork
3
- * 閫氳繃 WebSocket aibot_respond_msg 鍙戦€侊紝闇€閫忎紶 req_id
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
- /** 褰撳墠鍚屾澶勭悊涓殑 req_id锛堜粎鐢ㄤ簬 commandHandler 绛夊悓姝ヨ皟鐢級 */
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: '...', title: 'Thinking' },
29
- streaming: { icon: '...', title: 'Streaming' },
30
- done: { icon: '[ok]', title: 'Done' },
31
- error: { icon: '[error]', title: 'Error' },
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 += `_姝e湪鎬濊€冿紝璇风◢鍊?.._\n\n馃挱 **鍑嗗涓?*\n\n`;
51
+ message += `_正在思考,请稍候..._\n\n[thinking] **准备中**\n\n`;
61
52
  }
62
53
  if (note) {
63
- message += `---\n\n馃挕 **${note}**`;
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 - 娑堟伅鍥炶皟鐨?req_id锛岀敤浜?WebSocket 鍥炲
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
- // Send final stream message to finish the stream
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_*(缁?${i + 1}/${parts.length})*_`;
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
- * 涓诲姩鎺ㄩ€佹枃鏈紙鐢ㄤ簬鍚姩/鍏抽棴閫氱煡绛夛紝鏃犻渶 req_id锛?
198
+ * 主动推送文本,用于启动/关闭通知等场景,无需 req_id
219
199
  */
220
200
  export async function sendProactiveTextReply(chatId, text) {
221
- const message = formatWeWorkMessage('馃摙 open-im', text, 'done');
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 - 鍏煎 MessageSender 鐨?threadCtx锛涜嫢涓?string 鍒欎綔涓?reqId 浣跨敤
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('馃摙 open-im', text, 'done');
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, "Generated image saved at: " + imagePath);
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, "Generated image saved at: " + imagePath);
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('鉂?閿欒', error, 'error');
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.1",
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"