@wu529778790/open-im 1.0.3-beta.0 → 1.0.3-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.
@@ -95,6 +95,29 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
95
95
  }
96
96
  }, timeoutMs);
97
97
  }
98
+ // stderr 截断:只保留首 4KB + 尾 6KB,减少 I/O 和内存
99
+ const MAX_STDERR_HEAD = 4 * 1024;
100
+ const MAX_STDERR_TAIL = 6 * 1024;
101
+ let stderrHead = "";
102
+ let stderrTail = "";
103
+ let stderrTotal = 0;
104
+ let stderrHeadFull = false;
105
+ child.stderr?.on("data", (chunk) => {
106
+ const text = chunk.toString();
107
+ stderrTotal += text.length;
108
+ if (!stderrHeadFull) {
109
+ const room = MAX_STDERR_HEAD - stderrHead.length;
110
+ if (room > 0) {
111
+ stderrHead += text.slice(0, room);
112
+ if (stderrHead.length >= MAX_STDERR_HEAD)
113
+ stderrHeadFull = true;
114
+ }
115
+ }
116
+ stderrTail += text;
117
+ if (stderrTail.length > MAX_STDERR_TAIL) {
118
+ stderrTail = stderrTail.slice(-MAX_STDERR_TAIL);
119
+ }
120
+ });
98
121
  const rl = createInterface({ input: child.stdout });
99
122
  rl.on("line", (line) => {
100
123
  const event = parseStreamLine(line);
@@ -172,7 +195,22 @@ export function runClaude(cliPath, prompt, sessionId, workDir, callbacks, option
172
195
  clearTimeout(timeoutHandle);
173
196
  if (!completed) {
174
197
  if (exitCode !== null && exitCode !== 0) {
175
- callbacks.onError(`Claude CLI exited with code ${exitCode}`);
198
+ let errMsg = "";
199
+ if (stderrTotal > 0) {
200
+ if (!stderrHeadFull) {
201
+ errMsg = stderrHead;
202
+ }
203
+ else if (stderrTotal <= MAX_STDERR_HEAD + MAX_STDERR_TAIL) {
204
+ errMsg = stderrHead + stderrTail.slice(stderrTail.length - (stderrTotal - MAX_STDERR_HEAD));
205
+ }
206
+ else {
207
+ errMsg =
208
+ stderrHead +
209
+ `\n\n... (省略 ${stderrTotal - MAX_STDERR_HEAD - MAX_STDERR_TAIL} 字节) ...\n\n` +
210
+ stderrTail;
211
+ }
212
+ }
213
+ callbacks.onError(errMsg || `Claude CLI exited with code ${exitCode}`);
176
214
  }
177
215
  else {
178
216
  callbacks.onComplete({
@@ -126,6 +126,29 @@ export class ClaudeProcessPool {
126
126
  const toolStats = {};
127
127
  const pendingToolInputs = new Map();
128
128
  const startTime = Date.now();
129
+ // stderr 截断:只保留首 4KB + 尾 6KB
130
+ const MAX_STDERR_HEAD = 4 * 1024;
131
+ const MAX_STDERR_TAIL = 6 * 1024;
132
+ let stderrHead = "";
133
+ let stderrTail = "";
134
+ let stderrTotal = 0;
135
+ let stderrHeadFull = false;
136
+ child.stderr?.on("data", (chunk) => {
137
+ const text = chunk.toString();
138
+ stderrTotal += text.length;
139
+ if (!stderrHeadFull) {
140
+ const room = MAX_STDERR_HEAD - stderrHead.length;
141
+ if (room > 0) {
142
+ stderrHead += text.slice(0, room);
143
+ if (stderrHead.length >= MAX_STDERR_HEAD)
144
+ stderrHeadFull = true;
145
+ }
146
+ }
147
+ stderrTail += text;
148
+ if (stderrTail.length > MAX_STDERR_TAIL) {
149
+ stderrTail = stderrTail.slice(-MAX_STDERR_TAIL);
150
+ }
151
+ });
129
152
  const rl = createInterface({ input: child.stdout });
130
153
  rl.on("line", (line) => {
131
154
  const event = parseStreamLine(line);
@@ -203,9 +226,26 @@ export class ClaudeProcessPool {
203
226
  this.activeProcesses.delete(key);
204
227
  resolved = true;
205
228
  if (exitCode !== null && exitCode !== 0) {
206
- const errorMsg = `Claude CLI exited with code ${exitCode}`;
207
- callbacks.onError(errorMsg);
208
- reject(new Error(errorMsg));
229
+ let errorMsg = "";
230
+ if (stderrTotal > 0) {
231
+ if (!stderrHeadFull) {
232
+ errorMsg = stderrHead;
233
+ }
234
+ else if (stderrTotal <= MAX_STDERR_HEAD + MAX_STDERR_TAIL) {
235
+ errorMsg =
236
+ stderrHead +
237
+ stderrTail.slice(stderrTail.length - (stderrTotal - MAX_STDERR_HEAD));
238
+ }
239
+ else {
240
+ errorMsg =
241
+ stderrHead +
242
+ `\n\n... (省略 ${stderrTotal - MAX_STDERR_HEAD - MAX_STDERR_TAIL} 字节) ...\n\n` +
243
+ stderrTail;
244
+ }
245
+ }
246
+ const msg = errorMsg || `Claude CLI exited with code ${exitCode}`;
247
+ callbacks.onError(msg);
248
+ reject(new Error(msg));
209
249
  }
210
250
  // If exitCode is 0 and we haven't resolved yet, the result was already sent
211
251
  // via the extractResult handler. This is just cleanup.
@@ -5,8 +5,9 @@ export declare const IMAGE_DIR: string;
5
5
  export declare const READ_ONLY_TOOLS: string[];
6
6
  export declare const TERMINAL_ONLY_COMMANDS: Set<string>;
7
7
  export declare const DEDUP_TTL_MS: number;
8
- export declare const THROTTLE_MS = 200;
9
- /** Telegram 编辑消息节流:600ms,避免 API 限速且更流畅 */
10
- export declare const TELEGRAM_THROTTLE_MS = 600;
8
+ /** 飞书流式更新节流:≥200ms 以满足单条消息 5 QPS 频控,避免触发 delete 回退(撤回提示) */
9
+ export declare const FEISHU_THROTTLE_MS = 200;
10
+ /** Telegram 编辑消息节流:200ms(open-im 默认值) */
11
+ export declare const TELEGRAM_THROTTLE_MS = 200;
11
12
  export declare const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
12
13
  export declare const MAX_FEISHU_MESSAGE_LENGTH = 4000;
package/dist/constants.js CHANGED
@@ -1,20 +1,42 @@
1
- import { join } from 'node:path';
2
- import { homedir, tmpdir } from 'node:os';
3
- export const APP_HOME = join(homedir(), '.open-im');
1
+ import { join } from "node:path";
2
+ import { homedir, tmpdir } from "node:os";
3
+ export const APP_HOME = join(homedir(), ".open-im");
4
4
  /** 优雅关闭 HTTP 端口(stop 命令通过此端口触发 shutdown) */
5
5
  export const SHUTDOWN_PORT = 39281;
6
- export const IMAGE_DIR = join(tmpdir(), 'open-im-images');
6
+ export const IMAGE_DIR = join(tmpdir(), "open-im-images");
7
7
  export const READ_ONLY_TOOLS = [
8
- 'Read', 'Glob', 'Grep', 'WebFetch', 'WebSearch', 'Task', 'TodoRead',
8
+ "Read",
9
+ "Glob",
10
+ "Grep",
11
+ "WebFetch",
12
+ "WebSearch",
13
+ "Task",
14
+ "TodoRead",
9
15
  ];
10
16
  export const TERMINAL_ONLY_COMMANDS = new Set([
11
- '/context', '/rewind', '/resume', '/copy', '/export', '/config',
12
- '/init', '/memory', '/permissions', '/theme', '/vim', '/statusline',
13
- '/terminal-setup', '/debug', '/tasks', '/mcp', '/teleport', '/add-dir',
17
+ "/context",
18
+ "/rewind",
19
+ "/resume",
20
+ "/copy",
21
+ "/export",
22
+ "/config",
23
+ "/init",
24
+ "/memory",
25
+ "/permissions",
26
+ "/theme",
27
+ "/vim",
28
+ "/statusline",
29
+ "/terminal-setup",
30
+ "/debug",
31
+ "/tasks",
32
+ "/mcp",
33
+ "/teleport",
34
+ "/add-dir",
14
35
  ]);
15
36
  export const DEDUP_TTL_MS = 5 * 60 * 1000;
16
- export const THROTTLE_MS = 200;
17
- /** Telegram 编辑消息节流:600ms,避免 API 限速且更流畅 */
18
- export const TELEGRAM_THROTTLE_MS = 600;
37
+ /** 飞书流式更新节流:≥200ms 以满足单条消息 5 QPS 频控,避免触发 delete 回退(撤回提示) */
38
+ export const FEISHU_THROTTLE_MS = 200;
39
+ /** Telegram 编辑消息节流:200ms(open-im 默认值) */
40
+ export const TELEGRAM_THROTTLE_MS = 200;
19
41
  export const MAX_TELEGRAM_MESSAGE_LENGTH = 4000;
20
42
  export const MAX_FEISHU_MESSAGE_LENGTH = 4000;
@@ -10,7 +10,7 @@ import { CommandHandler } from '../commands/handler.js';
10
10
  import { getAdapter } from '../adapters/registry.js';
11
11
  import { runAITask } from '../shared/ai-task.js';
12
12
  import { startTaskCleanup } from '../shared/task-cleanup.js';
13
- import { THROTTLE_MS, IMAGE_DIR } from '../constants.js';
13
+ import { FEISHU_THROTTLE_MS, IMAGE_DIR } from '../constants.js';
14
14
  import { setActiveChatId } from '../shared/active-chats.js';
15
15
  import { setChatUser } from '../shared/chat-user-map.js';
16
16
  import { createLogger } from '../logger.js';
@@ -138,7 +138,7 @@ export function setupFeishuHandlers(config, sessionManager) {
138
138
  const stopTyping = startTypingLoop(chatId);
139
139
  const taskKey = `${userId}:${msgId}`;
140
140
  await runAITask({ config, sessionManager }, { userId, chatId, workDir, sessionId, convId, platform: 'feishu', taskKey }, prompt, toolAdapter, {
141
- throttleMs: THROTTLE_MS,
141
+ throttleMs: FEISHU_THROTTLE_MS,
142
142
  streamUpdate: async (content, toolNote) => {
143
143
  const note = toolNote ? '输出中...\n' + toolNote : '输出中...';
144
144
  try {
@@ -311,7 +311,6 @@ export async function sendFinalMessages(chatId, messageId, fullContent, note, to
311
311
  // If content fits in one message, try patch for smooth transition
312
312
  if (parts.length === 1) {
313
313
  const cardContent = createFeishuCard(getToolTitle(toolId, 'done'), fullContent, 'done');
314
- // Try to use patch API for in-place update
315
314
  try {
316
315
  const resp = await client.im.message.patch({
317
316
  path: { message_id: messageId },
@@ -341,7 +340,7 @@ export async function sendFinalMessages(chatId, messageId, fullContent, note, to
341
340
  catch (err) {
342
341
  log.warn('Failed to delete old message:', err);
343
342
  }
344
- // Send new messages
343
+ // Send new messages (split when content exceeds limit)
345
344
  for (let i = 0; i < parts.length; i++) {
346
345
  try {
347
346
  const partContent = i === 0 ? parts[0] : `${parts[i]}\n\n_*(续 ${i + 1}/${parts.length})*_`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.0.3-beta.0",
3
+ "version": "1.0.3-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",