@wu529778790/open-im 1.2.3 → 1.2.4-beta.1

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/README.md CHANGED
@@ -37,7 +37,6 @@ open-im start
37
37
  | `open-im init` | 初始化配置(不启动服务) |
38
38
  | `open-im start` | 后台运行,适合长期使用 |
39
39
  | `open-im stop` | 停止后台服务 |
40
- | `open-im restart` | 重启服务 |
41
40
  | `open-im dev` | 前台运行(调试模式),Ctrl+C 停止 |
42
41
 
43
42
  ## 会话说明
package/dist/cli.js CHANGED
@@ -88,14 +88,9 @@ async function validateOrSetup() {
88
88
  // ============================================================================
89
89
  // 命令处理
90
90
  // ============================================================================
91
- async function cmdStart(skipPlatformPrompt = false) {
91
+ async function cmdStart() {
92
92
  const pid = getPid();
93
- // 如果是 restart 调用,先确保旧进程完全清理
94
- if (skipPlatformPrompt && pid) {
95
- // 强制清理,即使进程看起来在运行
96
- removePid();
97
- }
98
- else if (pid && isRunning(pid)) {
93
+ if (pid && isRunning(pid)) {
99
94
  console.log(`open-im 已在后台运行 (pid=${pid})`);
100
95
  return;
101
96
  }
@@ -106,9 +101,8 @@ async function cmdStart(skipPlatformPrompt = false) {
106
101
  process.exit(1);
107
102
  }
108
103
  // 有 TTY 时在父进程让用户选择要启用的平台,再启动子进程
109
- // skipPlatformPrompt 为 true 时跳过提示(用于 restart 命令)
110
104
  let config = loadConfig();
111
- if (process.stdin.isTTY && !skipPlatformPrompt) {
105
+ if (process.stdin.isTTY) {
112
106
  const updated = await runPlatformSelectionPrompt(config);
113
107
  if (!updated) {
114
108
  console.log("已取消启动。");
@@ -170,40 +164,6 @@ async function cmdStop() {
170
164
  }
171
165
  console.log(`open-im 已停止 (pid=${pid})`);
172
166
  }
173
- async function cmdRestart() {
174
- const pid = getPid();
175
- const wasRunning = pid && isRunning(pid);
176
- if (wasRunning) {
177
- console.log(`正在重启 open-im (当前 pid=${pid})...`);
178
- console.log(' → 正在停止服务...');
179
- await cmdStop();
180
- // 等待进程完全停止
181
- for (let i = 0; i < 50; i++) {
182
- await new Promise((r) => setTimeout(r, 100));
183
- if (!isRunning(pid)) {
184
- console.log(' → 服务已停止');
185
- break;
186
- }
187
- }
188
- // 额外等待一下,确保所有资源(端口、文件句柄等)完全释放
189
- console.log(' → 等待资源释放...');
190
- await new Promise((r) => setTimeout(r, 1000));
191
- }
192
- else {
193
- if (pid) {
194
- console.log(`检测到 PID 文件存在 (pid=${pid}),但进程未运行,清理后重启...`);
195
- }
196
- else {
197
- console.log('open-im 未在后台运行,直接启动...');
198
- }
199
- removePid();
200
- }
201
- console.log(' → 正在启动服务...');
202
- // 设置环境变量,告诉 main() 函数这是 restart,需要清除旧的 sessionId
203
- process.env.OPEN_IM_RESTART = '1';
204
- await cmdStart(true); // 传递 true 跳过平台选择提示
205
- delete process.env.OPEN_IM_RESTART;
206
- }
207
167
  async function cmdInit() {
208
168
  console.log("\n━━━ open-im 配置向导 ━━━\n");
209
169
  const saved = await runInteractiveSetup();
@@ -225,7 +185,6 @@ function showHelp(exitCode = 0) {
225
185
  命令:
226
186
  start 后台运行服务
227
187
  stop 停止后台服务
228
- restart 重启服务
229
188
  init 配置向导(首次或追加配置,会覆盖已有 config.json)
230
189
  dev 前台运行(调试模式),Ctrl+C 停止
231
190
 
@@ -241,7 +200,6 @@ const cmd = process.argv[2];
241
200
  const commands = {
242
201
  start: cmdStart,
243
202
  stop: cmdStop,
244
- restart: cmdRestart,
245
203
  init: cmdInit,
246
204
  dev: main,
247
205
  };
@@ -253,37 +253,54 @@ export async function sendThinkingMessage(chatId, replyToMessageId, toolId = 'cl
253
253
  throw err;
254
254
  }
255
255
  }
256
+ // Track if patch API is working for this session
257
+ let patchApiWorking = true;
258
+ let patchFailCount = 0;
259
+ const MAX_PATCH_FAILURES_BEFORE_DISABLE = 3;
256
260
  export async function updateMessage(chatId, messageId, content, status, note, toolId = 'claude') {
257
261
  const client = getClient();
258
262
  const title = getToolTitle(toolId, status);
259
263
  const cardContent = createFeishuCard(title, content, status, note);
260
264
  // Try to use patch API for in-place update (streaming)
261
- try {
262
- const resp = await client.im.message.patch({
263
- path: { message_id: messageId },
264
- data: {
265
- content: cardContent,
266
- },
267
- });
268
- if (resp.code === 0) {
269
- log.info(`Message updated in-place: ${messageId}`);
270
- return;
265
+ // Only attempt patch if it has been working recently
266
+ if (patchApiWorking) {
267
+ try {
268
+ const resp = await client.im.message.patch({
269
+ path: { message_id: messageId },
270
+ data: {
271
+ content: cardContent,
272
+ },
273
+ });
274
+ if (resp.code === 0) {
275
+ log.debug(`✓ Patch API succeeded: ${messageId}`);
276
+ patchFailCount = 0; // Reset failure count on success
277
+ return;
278
+ }
279
+ // Patch failed with API error
280
+ patchFailCount++;
281
+ log.warn(`Patch API failed (code: ${resp.code}, msg: ${resp.msg}) - failure ${patchFailCount}/${MAX_PATCH_FAILURES_BEFORE_DISABLE}`);
282
+ if (patchFailCount >= MAX_PATCH_FAILURES_BEFORE_DISABLE) {
283
+ log.warn('Patch API disabled for this session due to repeated failures');
284
+ patchApiWorking = false;
285
+ }
286
+ }
287
+ catch (err) {
288
+ // Patch failed with network/other error
289
+ patchFailCount++;
290
+ const errorMsg = err instanceof Error ? err.message : String(err);
291
+ log.warn(`Patch API error (${patchFailCount}/${MAX_PATCH_FAILURES_BEFORE_DISABLE}): ${errorMsg}`);
292
+ if (patchFailCount >= MAX_PATCH_FAILURES_BEFORE_DISABLE) {
293
+ log.warn('Patch API disabled for this session due to repeated errors');
294
+ patchApiWorking = false;
295
+ }
271
296
  }
272
- // If patch failed with validation error, fall back to delete+create
273
- log.warn(`Patch API failed (code: ${resp.code}, msg: ${resp.msg}), falling back to delete+create`);
274
- }
275
- catch (err) {
276
- // Log but don't throw - we'll fall back to delete+create
277
- const errorMsg = err instanceof Error ? err.message : String(err);
278
- log.debug(`Patch API error: ${errorMsg}, falling back to delete+create`);
279
297
  }
280
298
  // Fallback: Delete old message and send new one
281
299
  try {
282
- log.info(`Deleting old message ${messageId}`);
283
300
  await client.im.message.delete({
284
301
  path: { message_id: messageId },
285
302
  });
286
- log.info(`Old message deleted successfully`);
303
+ log.debug(`Deleted old message for recreate: ${messageId}`);
287
304
  }
288
305
  catch (err) {
289
306
  log.warn('Failed to delete old message:', err);
@@ -298,7 +315,7 @@ export async function updateMessage(chatId, messageId, content, status, note, to
298
315
  },
299
316
  params: { receive_id_type: 'chat_id' },
300
317
  });
301
- log.info(`New message created with ID: ${resp.data?.message_id}`);
318
+ log.debug(`Created new message: ${resp.data?.message_id}`);
302
319
  }
303
320
  catch (err) {
304
321
  log.error('Failed to send new message:', err);
@@ -308,8 +325,8 @@ export async function updateMessage(chatId, messageId, content, status, note, to
308
325
  export async function sendFinalMessages(chatId, messageId, fullContent, note, toolId = 'claude') {
309
326
  const client = getClient();
310
327
  const parts = splitLongContent(fullContent, MAX_FEISHU_MESSAGE_LENGTH);
311
- // If content fits in one message, try patch for smooth transition
312
- if (parts.length === 1) {
328
+ // If content fits in one message and patch is working, try patch for smooth transition
329
+ if (parts.length === 1 && patchApiWorking) {
313
330
  const cardContent = createFeishuCard(getToolTitle(toolId, 'done'), fullContent, 'done');
314
331
  try {
315
332
  const resp = await client.im.message.patch({
@@ -319,7 +336,7 @@ export async function sendFinalMessages(chatId, messageId, fullContent, note, to
319
336
  },
320
337
  });
321
338
  if (resp.code === 0) {
322
- log.info(`Final message updated in-place: ${messageId}`);
339
+ log.info(`✓ Final message patched successfully: ${messageId}`);
323
340
  return;
324
341
  }
325
342
  log.warn(`Patch API failed (code: ${resp.code}), falling back to delete+create`);
@@ -331,11 +348,10 @@ export async function sendFinalMessages(chatId, messageId, fullContent, note, to
331
348
  }
332
349
  // Fallback: Delete old message first (for multi-part or failed patch)
333
350
  try {
334
- log.info(`Deleting old message ${messageId}`);
335
351
  await client.im.message.delete({
336
352
  path: { message_id: messageId },
337
353
  });
338
- log.info(`Old message deleted successfully`);
354
+ log.debug(`Deleted old message for final recreate: ${messageId}`);
339
355
  }
340
356
  catch (err) {
341
357
  log.warn('Failed to delete old message:', err);
package/dist/index.js CHANGED
@@ -103,11 +103,6 @@ export async function main() {
103
103
  log.info(`默认权限模式: ${defaultModeLabel} (${config.defaultPermissionMode})`);
104
104
  log.info(`启用平台: ${config.enabledPlatforms.join(", ")}`);
105
105
  const sessionManager = new SessionManager(config.claudeWorkDir, config.allowedBaseDirs);
106
- // 如果是 restart,清除所有旧的 sessionId(SDK 内部状态已失效)
107
- if (process.env.OPEN_IM_RESTART === '1') {
108
- sessionManager.clearAllSessionIds();
109
- delete process.env.OPEN_IM_RESTART;
110
- }
111
106
  let telegramHandle = null;
112
107
  let feishuHandle = null;
113
108
  let wechatHandle = null;
@@ -19,10 +19,6 @@ export declare class SessionManager {
19
19
  setModel(userId: string, model: string | undefined, threadId?: string): void;
20
20
  private resolveAndValidate;
21
21
  private load;
22
- /**
23
- * 清除所有会话的 sessionId(用于 restart 后 SDK 内部状态失效的情况)
24
- */
25
- clearAllSessionIds(): void;
26
22
  private save;
27
23
  private flushSync;
28
24
  destroy(): void;
@@ -213,21 +213,6 @@ export class SessionManager {
213
213
  /* ignore */
214
214
  }
215
215
  }
216
- /**
217
- * 清除所有会话的 sessionId(用于 restart 后 SDK 内部状态失效的情况)
218
- */
219
- clearAllSessionIds() {
220
- for (const session of this.sessions.values()) {
221
- session.sessionId = undefined;
222
- if (session.threads) {
223
- for (const t of Object.values(session.threads)) {
224
- t.sessionId = undefined;
225
- }
226
- }
227
- }
228
- this.flushSync();
229
- log.info('All sessionIds cleared for restart');
230
- }
231
216
  save() {
232
217
  if (this.saveTimer)
233
218
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.2.3",
3
+ "version": "1.2.4-beta.1",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, Cursor)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",