@wu529778790/open-im 1.10.0 → 1.10.1-beta.0

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.
@@ -4,7 +4,7 @@ export class AccessControl {
4
4
  allowedUserIds;
5
5
  constructor(allowedUserIds) {
6
6
  this.allowedUserIds = new Set(allowedUserIds);
7
- log.info(`AccessControl initialized with ${allowedUserIds.length} allowed users:`, allowedUserIds);
7
+ log.debug(`AccessControl initialized with ${allowedUserIds.length} allowed users:`, allowedUserIds);
8
8
  }
9
9
  isAllowed(userId) {
10
10
  if (this.allowedUserIds.size === 0) {
@@ -12,7 +12,7 @@ export class AccessControl {
12
12
  return true;
13
13
  }
14
14
  const allowed = this.allowedUserIds.has(userId);
15
- log.info(`Checking user ${userId}: ${allowed ? 'ALLOWED' : 'DENIED'}`);
15
+ log.debug(`Checking user ${userId}: ${allowed ? 'ALLOWED' : 'DENIED'}`);
16
16
  return allowed;
17
17
  }
18
18
  }
@@ -79,8 +79,9 @@ cleanupInterval.unref(); // 不阻止进程退出
79
79
  * `cwd` parameter, so we must chdir before calling them. This mutex ensures
80
80
  * concurrent requests don't race on the working directory.
81
81
  *
82
- * **Limitation:** If the SDK ever supports a `cwd` option, this mutex should
83
- * be removed entirely.
82
+ * **TODO:** Remove this mutex entirely once @anthropic-ai/claude-agent-sdk
83
+ * supports a `cwd` option in createSession/resumeSession. Track upstream:
84
+ * https://github.com/anthropics/claude-agent-sdk/issues
84
85
  */
85
86
  let chdirMutex = Promise.resolve();
86
87
  function withChdirMutex(fn) {
@@ -429,7 +430,7 @@ export class ClaudeSDKAdapter {
429
430
  const errIdToClean = actualSessionId ?? pendingTempId;
430
431
  if (errIdToClean?.startsWith('pending-')) {
431
432
  activeSessions.delete(errIdToClean);
432
- log.info(`Cleaned up pending session after error: ${errIdToClean}`);
433
+ log.warn(`Cleaned up pending session after error: ${errIdToClean}`);
433
434
  }
434
435
  // If error suggests a corrupted session, remove it from cache to prevent reuse
435
436
  if (actualSessionId && isSessionCorruptionError(msg)) {
@@ -17,11 +17,3 @@ export declare function resolvePlatformCredentials(envKeys: Record<string, strin
17
17
  * Extract WorkBuddy credentials, with legacy platforms.wechat migration support.
18
18
  */
19
19
  export declare function resolveWorkBuddyFileConfig(fileConfig: FileConfig): NonNullable<FileConfig['platforms']>['workbuddy'] | undefined;
20
- /**
21
- * Check if a CLI tool is available at the given path or on PATH.
22
- */
23
- export declare function checkCliAvailable(cliPath: string, toolName: string): void;
24
- /**
25
- * Resolve Windows-specific CLI path for npm global installs.
26
- */
27
- export declare function resolveWindowsCliPath(cliName: string, configuredPath: string): string;
@@ -1,6 +1,3 @@
1
- import { accessSync, constants } from 'node:fs';
2
- import { execFileSync } from 'node:child_process';
3
- import { join, isAbsolute } from 'node:path';
4
1
  /**
5
2
  * Resolves a single credential value using the standard priority chain:
6
3
  * environment variable → platform file config → legacy file config.
@@ -37,49 +34,3 @@ export function resolveWorkBuddyFileConfig(fileConfig) {
37
34
  }
38
35
  return undefined;
39
36
  }
40
- /**
41
- * Check if a CLI tool is available at the given path or on PATH.
42
- */
43
- export function checkCliAvailable(cliPath, toolName) {
44
- if (isAbsolute(cliPath) || cliPath.includes('/') || cliPath.includes('\\')) {
45
- try {
46
- accessSync(cliPath, constants.F_OK);
47
- }
48
- catch {
49
- throw new Error(`${toolName} CLI not found at: ${cliPath}`);
50
- }
51
- }
52
- else {
53
- const checkCommand = process.platform === 'win32' ? 'where' : 'which';
54
- try {
55
- execFileSync(checkCommand, [cliPath], {
56
- stdio: 'pipe',
57
- windowsHide: process.platform === 'win32',
58
- });
59
- }
60
- catch {
61
- throw new Error(`${toolName} CLI not found on PATH: ${cliPath}`);
62
- }
63
- }
64
- }
65
- /**
66
- * Resolve Windows-specific CLI path for npm global installs.
67
- */
68
- export function resolveWindowsCliPath(cliName, configuredPath) {
69
- if (process.platform !== 'win32' || configuredPath !== cliName)
70
- return configuredPath;
71
- const npmPaths = [
72
- join(process.env.APPDATA || '', 'npm', `${cliName}.cmd`),
73
- join(process.env.LOCALAPPDATA || '', 'npm', `${cliName}.cmd`),
74
- ];
75
- for (const p of npmPaths) {
76
- try {
77
- accessSync(p, constants.F_OK);
78
- return p;
79
- }
80
- catch {
81
- /* try next */
82
- }
83
- }
84
- return configuredPath;
85
- }
@@ -50,7 +50,7 @@ export async function initFeishu(config, eventHandler) {
50
50
  eventDispatcher.register({
51
51
  'im.message.receive_v1': async (data) => {
52
52
  log.info('[EVENT] Received Feishu message event');
53
- log.info('[EVENT] Event data:', JSON.stringify(data).slice(0, 500));
53
+ log.debug('[EVENT] Event data:', JSON.stringify(data).slice(0, 500));
54
54
  try {
55
55
  await eventHandler(data);
56
56
  log.info('[EVENT] Event handler called successfully');
@@ -62,7 +62,7 @@ export async function initFeishu(config, eventHandler) {
62
62
  // 卡片按钮点击回调(权限允许/拒绝等)
63
63
  'card.action.trigger': async (data) => {
64
64
  log.info('[EVENT] Received Feishu card action event');
65
- log.info('[EVENT] Card action data:', JSON.stringify(data).slice(0, 800));
65
+ log.debug('[EVENT] Card action data:', JSON.stringify(data).slice(0, 800));
66
66
  try {
67
67
  const result = await eventHandler(data);
68
68
  return result;
@@ -76,7 +76,7 @@ export async function initFeishu(config, eventHandler) {
76
76
  // Register catch-all handler using wildcard
77
77
  eventDispatcher.register({
78
78
  '*': (data) => {
79
- log.info('Received Feishu event (catch-all):', JSON.stringify(data).slice(0, 500));
79
+ log.debug('Received Feishu event (catch-all):', JSON.stringify(data).slice(0, 500));
80
80
  // Don't call eventHandler for catch-all, let specific handlers handle it
81
81
  },
82
82
  });
@@ -165,7 +165,7 @@ export function setupFeishuHandlers(config, sessionManager) {
165
165
  return { toast: { type: 'warning', content: '未知操作' } };
166
166
  }
167
167
  async function handleEvent(data) {
168
- log.info('[handleEvent] Called with data:', JSON.stringify(data).slice(0, 800));
168
+ log.debug('[handleEvent] Called with data:', JSON.stringify(data).slice(0, 800));
169
169
  try {
170
170
  const raw = data;
171
171
  const event = (raw?.event ?? raw);
@@ -4,10 +4,6 @@
4
4
  * 统一管理权限错误码、提示消息构建、控制台输出。
5
5
  * 所有飞书发送函数遇到错误时应通过此模块检测权限问题。
6
6
  */
7
- /**
8
- * 从异常中提取飞书 API 错误码
9
- */
10
- export declare function extractFeishuErrorCode(err: unknown): number | undefined;
11
7
  /**
12
8
  * 根据错误码判断是否为权限不足
13
9
  */
@@ -16,14 +12,6 @@ export declare function isPermissionError(err: unknown): boolean;
16
12
  * 构建飞书应用权限设置页直达链接
17
13
  */
18
14
  export declare function buildPermissionUrl(appId: string): string;
19
- /**
20
- * 构建飞书开放平台应用列表页链接
21
- */
22
- export declare function buildAppListUrl(): string;
23
- /**
24
- * 构建飞书卡片用的权限指引消息(lark_md 格式)
25
- */
26
- export declare function buildPermissionGuideMessage(err: unknown, appId?: string): string;
27
15
  /**
28
16
  * 启动时输出权限要求提示(仅输出一次)
29
17
  */
@@ -27,7 +27,7 @@ const OPTIONAL_SCOPES = [
27
27
  /**
28
28
  * 从异常中提取飞书 API 错误码
29
29
  */
30
- export function extractFeishuErrorCode(err) {
30
+ function extractFeishuErrorCode(err) {
31
31
  const e = err;
32
32
  if (e?.response?.data?.code)
33
33
  return e.response.data.code;
@@ -56,14 +56,14 @@ export function buildPermissionUrl(appId) {
56
56
  /**
57
57
  * 构建飞书开放平台应用列表页链接
58
58
  */
59
- export function buildAppListUrl() {
59
+ function buildAppListUrl() {
60
60
  return 'https://open.feishu.cn/app';
61
61
  }
62
62
  // ── 消息构建 ──
63
63
  /**
64
64
  * 构建飞书卡片用的权限指引消息(lark_md 格式)
65
65
  */
66
- export function buildPermissionGuideMessage(err, appId) {
66
+ function buildPermissionGuideMessage(err, appId) {
67
67
  const code = extractFeishuErrorCode(err);
68
68
  const codeHint = code ? ` (错误码: ${code})` : '';
69
69
  const lines = [
package/dist/index.js CHANGED
@@ -155,6 +155,7 @@ export async function main() {
155
155
  let config;
156
156
  try {
157
157
  config = loadConfig();
158
+ initLogger(config.logDir, config.logLevel);
158
159
  }
159
160
  catch (err) {
160
161
  if (err instanceof Error &&
@@ -165,12 +166,12 @@ export async function main() {
165
166
  if (!saved)
166
167
  process.exit(1);
167
168
  config = loadConfig();
169
+ initLogger(config.logDir, config.logLevel);
168
170
  }
169
171
  else {
170
172
  throw err;
171
173
  }
172
174
  }
173
- initLogger(config.logDir, config.logLevel);
174
175
  loadActiveChats();
175
176
  initAdapters(config);
176
177
  // 尽早启动 shutdown 并写入 port 文件,使 open-im start 的 8s 就绪超时能通过(平台初始化可能较慢)
@@ -1,8 +1,9 @@
1
- import { execFileSync, spawn } from "node:child_process";
1
+ import { spawn } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
3
3
  import { dirname, extname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { APP_HOME } from "./constants.js";
6
+ import { isRunning } from "./service-control.js";
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
8
  const PID_FILE = join(APP_HOME, "open-im.pid");
8
9
  const READY_FILE = join(APP_HOME, "open-im.ready");
@@ -23,22 +24,6 @@ function getManagerEntry() {
23
24
  args: [join(__dirname, "manager.js")],
24
25
  };
25
26
  }
26
- function isRunning(pid) {
27
- try {
28
- if (process.platform === "win32") {
29
- const result = execFileSync("tasklist", ["/FI", `PID eq ${pid}`, "/NH"], {
30
- stdio: "pipe",
31
- windowsHide: true,
32
- }).toString();
33
- return result.includes(String(pid));
34
- }
35
- process.kill(pid, 0);
36
- return true;
37
- }
38
- catch {
39
- return false;
40
- }
41
- }
42
27
  export function getManagerPid() {
43
28
  if (!existsSync(PID_FILE))
44
29
  return null;
@@ -96,8 +96,9 @@ export function createPlatformAIRequestHandler(deps) {
96
96
  // Merge in platform callbacks (if provided)
97
97
  const mergedCallbacks = { ...defaultCallbacks };
98
98
  // Use taskCallbacksFactory if provided (has full context access)
99
+ let factoryCallbacks;
99
100
  if (taskCallbacksFactory) {
100
- const factoryCallbacks = taskCallbacksFactory({
101
+ factoryCallbacks = taskCallbacksFactory({
101
102
  chatId,
102
103
  msgId,
103
104
  taskKey,
@@ -140,7 +141,7 @@ export function createPlatformAIRequestHandler(deps) {
140
141
  mergedCallbacks.sendImage = (imagePath) => sender.sendImage(chatId, imagePath);
141
142
  }
142
143
  // Wrap extraCleanup to also call the platform's extraCleanup
143
- const platformExtraCleanup = taskCallbacks?.extraCleanup ?? taskCallbacksFactory?.({ chatId, msgId, taskKey, userId, toolId, replyToMessageId }).extraCleanup;
144
+ const platformExtraCleanup = taskCallbacks?.extraCleanup ?? factoryCallbacks?.extraCleanup;
144
145
  const originalExtraCleanup = mergedCallbacks.extraCleanup;
145
146
  mergedCallbacks.extraCleanup = () => {
146
147
  originalExtraCleanup();
@@ -1,3 +1,6 @@
1
+ export declare class QueueTimeoutError extends Error {
2
+ constructor(timeoutMs: number);
3
+ }
1
4
  export type EnqueueResult = 'running' | 'queued' | 'rejected';
2
5
  export declare class RequestQueue {
3
6
  private queues;
@@ -1,5 +1,11 @@
1
1
  import { createLogger } from '../logger.js';
2
2
  const log = createLogger('Queue');
3
+ export class QueueTimeoutError extends Error {
4
+ constructor(timeoutMs) {
5
+ super(`Task timed out after ${timeoutMs / 1000}s`);
6
+ this.name = 'QueueTimeoutError';
7
+ }
8
+ }
3
9
  const MAX_QUEUE_SIZE = 3;
4
10
  const TASK_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
5
11
  export class RequestQueue {
@@ -19,7 +25,7 @@ export class RequestQueue {
19
25
  return 'queued';
20
26
  }
21
27
  q.running = true;
22
- this.run(key, prompt, execute);
28
+ this.run(key, prompt, execute).catch(() => { });
23
29
  return 'running';
24
30
  }
25
31
  /** 清除指定用户会话的所有排队任务(不中止正在运行的任务) */
@@ -41,28 +47,34 @@ export class RequestQueue {
41
47
  const timeoutPromise = new Promise((_, reject) => {
42
48
  timer = setTimeout(() => {
43
49
  controller.abort();
44
- reject(new Error(`Task timed out after ${TASK_TIMEOUT_MS / 1000}s`));
50
+ reject(new QueueTimeoutError(TASK_TIMEOUT_MS));
45
51
  }, TASK_TIMEOUT_MS);
46
52
  });
47
53
  await Promise.race([execute(prompt, controller.signal), timeoutPromise]);
48
54
  }
49
55
  catch (err) {
50
- log.error(`Error executing task for ${key}:`, err);
56
+ if (err instanceof QueueTimeoutError) {
57
+ log.error(`Timeout executing task for ${key}: ${err.message}`);
58
+ }
59
+ else {
60
+ log.error(`Error executing task for ${key}:`, err);
61
+ }
62
+ throw err;
51
63
  }
52
64
  finally {
53
65
  if (timer)
54
66
  clearTimeout(timer);
55
- }
56
- const q = this.queues.get(key);
57
- if (!q)
58
- return;
59
- const next = q.tasks.shift();
60
- if (next) {
61
- setImmediate(() => this.run(key, next.prompt, next.execute));
62
- }
63
- else {
64
- q.running = false;
65
- this.queues.delete(key);
67
+ const q = this.queues.get(key);
68
+ if (!q)
69
+ return;
70
+ const next = q.tasks.shift();
71
+ if (next) {
72
+ setImmediate(() => this.run(key, next.prompt, next.execute).catch(() => { }));
73
+ }
74
+ else {
75
+ q.running = false;
76
+ this.queues.delete(key);
77
+ }
66
78
  }
67
79
  }
68
80
  }
package/dist/sanitize.js CHANGED
@@ -1,6 +1,7 @@
1
1
  const PATTERNS = [
2
2
  [/\b(sk|pk|bot)[-_][a-zA-Z0-9_-]{8,}/gi, (m) => (m.match(/^[a-zA-Z]+/)?.[0] || m.slice(0, 2)) + '_****'],
3
3
  [/\b(AIza|AKIA)[a-zA-Z0-9]{12,}/g, (m) => m.slice(0, 4) + '****'],
4
+ [/\b[a-zA-Z0-9]{40,}\b/g, (m) => m.slice(0, 6) + '****'],
4
5
  ];
5
6
  export function sanitize(text) {
6
7
  let result = text;
@@ -203,10 +203,10 @@ export function runAITask(deps, ctx, prompt, toolAdapter, platformAdapter) {
203
203
  sessionManager.clearSessionForConv(ctx.userId, ctx.convId, aiCommand);
204
204
  else
205
205
  sessionManager.clearActiveToolSession(ctx.userId, aiCommand);
206
- log.info(`Session reset for user ${ctx.userId} due to ${aiCommand} task error`);
206
+ log.warn(`Session reset for user ${ctx.userId} due to ${aiCommand} task error`);
207
207
  }
208
208
  else if (aiCommand === 'codex' && isUsageLimitError(error)) {
209
- log.info(`Keeping codex session for user ${ctx.userId} after usage limit error`);
209
+ log.warn(`Keeping codex session for user ${ctx.userId} after usage limit error`);
210
210
  }
211
211
  const friendlyError = hadSessionInvalid
212
212
  ? '当前 Claude 会话已失效,已自动执行 /new 重置会话,请重新发送刚才的问题。'
@@ -1,3 +1 @@
1
1
  export declare function setChatUser(chatId: string, userId: string, platform?: string): void;
2
- export declare function getUserIdByChatId(chatId: string): string | undefined;
3
- export declare function getPlatformByChatId(chatId: string): string | undefined;
@@ -20,9 +20,3 @@ export function setChatUser(chatId, userId, platform) {
20
20
  if (platform)
21
21
  chatToPlatform.set(chatId, platform);
22
22
  }
23
- export function getUserIdByChatId(chatId) {
24
- return chatToUser.get(chatId);
25
- }
26
- export function getPlatformByChatId(chatId) {
27
- return chatToPlatform.get(chatId);
28
- }
@@ -111,7 +111,7 @@ export async function initWeWork(cfg, eventHandler, onStateChange) {
111
111
  stateChangeHandler = onStateChange ?? null;
112
112
  isStopping = false;
113
113
  shouldReconnect = false;
114
- log.info(`Initializing WeWork client (botId: ${config.botId})`);
114
+ log.info('Initializing WeWork client');
115
115
  // 首次连接支持重试:单独启用企微时偶发 TLS 连接失败,加飞书后因初始化顺序有“预热”效果则稳定
116
116
  const maxAttempts = 3;
117
117
  const retryDelayMs = 1500;
@@ -251,7 +251,7 @@ export function setupWeWorkHandlers(config, sessionManager) {
251
251
  }
252
252
  }
253
253
  async function handleEvent(data) {
254
- log.info('[handleEvent] Called with data:', JSON.stringify(data).slice(0, 800));
254
+ log.debug('[handleEvent] Called with data:', JSON.stringify(data).slice(0, 800));
255
255
  const reqId = data.headers?.req_id ?? '';
256
256
  senderCtx.reqId = reqId;
257
257
  try {
@@ -332,7 +332,7 @@ export async function sendErrorMessage(chatId, error, reqId) {
332
332
  const message = formatWeWorkMessage('错误', error, 'error');
333
333
  try {
334
334
  sendText(getReqId(reqId), message);
335
- log.info(`Error message sent to user ${chatId}`);
335
+ log.warn(`Error message sent to user ${chatId}`);
336
336
  }
337
337
  catch (err) {
338
338
  log.error('Failed to send error message:', err);
@@ -131,13 +131,6 @@ export interface MessageState {
131
131
  /** 流式回复的 streamId,用于保持同一个流式回复使用相同的 streamId */
132
132
  streamId?: string;
133
133
  }
134
- export interface WeWorkResponse {
135
- headers: {
136
- req_id: string;
137
- };
138
- errcode: number;
139
- errmsg: string;
140
- }
141
134
  export interface WeWorkHttpResponseBody {
142
135
  msgtype: 'text' | 'markdown' | 'stream';
143
136
  text?: {
@@ -153,7 +153,7 @@ async function connect() {
153
153
  channelId: pc.userId ?? '', // plugin uses userId, not full channel name
154
154
  userId: pc.userId ?? '',
155
155
  })
156
- .then((res) => log.info(`WeChat KF channel registered (online): ${JSON.stringify(res)}`))
156
+ .then((res) => log.debug(`WeChat KF channel registered (online): ${JSON.stringify(res)}`))
157
157
  .catch((err) => log.warn(`registerChannel failed: ${String(err)}`));
158
158
  };
159
159
  doRegister();
@@ -176,7 +176,7 @@ async function connect() {
176
176
  channelId: pc.userId ?? '',
177
177
  userId: pc.userId ?? '',
178
178
  })
179
- .then((res) => log.info(`WeChat KF channel registered (fallback): ${JSON.stringify(res)}`))
179
+ .then((res) => log.debug(`WeChat KF channel registered (fallback): ${JSON.stringify(res)}`))
180
180
  .catch((e) => log.warn(`registerChannel failed: ${String(e)}`));
181
181
  };
182
182
  doRegister();
@@ -30,7 +30,7 @@ export async function sendErrorReply(_client, chatId, error, msgId) {
30
30
  log.warn('WorkBuddy client not available, cannot send error');
31
31
  return;
32
32
  }
33
- log.info(`Sending WorkBuddy error to chatId=${chatId}, msgId=${msgId}`);
33
+ log.warn(`Sending WorkBuddy error to chatId=${chatId}, msgId=${msgId}`);
34
34
  await client.sendPromptResponse({
35
35
  session_id: chatId,
36
36
  prompt_id: msgId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.10.0",
3
+ "version": "1.10.1-beta.0",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",