@wu529778790/open-im 1.10.9-beta.5 → 1.10.9-beta.7

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.
@@ -13,6 +13,7 @@ interface ClaudeSessionMeta {
13
13
  sessionId: string;
14
14
  mtime: number;
15
15
  filePath: string;
16
+ size: number;
16
17
  }
17
18
  /**
18
19
  * 扫描 ~/.claude/projects/<encoded-path>/ 找到最新的 CLI session。
@@ -9,7 +9,7 @@
9
9
  * 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
10
10
  */
11
11
  import { unstable_v2_createSession, unstable_v2_resumeSession } from '@anthropic-ai/claude-agent-sdk';
12
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
12
+ import { existsSync, readFileSync, readdirSync, statSync, openSync, readSync, closeSync } from 'fs';
13
13
  import { execSync } from 'child_process';
14
14
  import { homedir } from 'os';
15
15
  import { join } from 'path';
@@ -53,18 +53,45 @@ loadUserPluginSettings();
53
53
  * ~/.claude/projects/-Users-mac-github-open-im/
54
54
  */
55
55
  function workDirToProjectPath(workDir) {
56
- // Claude Code 使用路径中的 / 替换为 -,去掉开头的 -
56
+ // Claude Code 将路径中的 / 替换为 -,leading - 保留
57
57
  // /Users/mac/github/open-im → -Users-mac-github-open-im
58
58
  return workDir.replace(/\//g, '-');
59
59
  }
60
60
  /**
61
- * 检查 CLI 进程是否正在使用某个 session(通过 /proc 或 ps 检测)
61
+ * 检查 CLI 进程是否**正在活跃使用**某个 session
62
+ *
63
+ * 仅当同时满足以下两个条件时返回 true:
64
+ * 1. 存在包含该 sessionId 的 claude 进程(通过 ps 检测)
65
+ * 2. session 文件在最近 30 秒内被修改过(说明 CLI 正在处理消息)
66
+ *
67
+ * 如果 CLI 只是挂在终端等待用户输入,进程仍在但文件长时间未变,
68
+ * 此时安全允许 open-im 接管 session(无缝切换)。
69
+ *
70
+ * 注意:仅支持 macOS/Linux,Windows 上会静默返回 false
62
71
  */
63
- function isCliSessionActive(sessionId) {
72
+ function isCliSessionActive(sessionId, sessionFilePath) {
64
73
  try {
65
- // macOS: 用 ps 搜索包含该 sessionId 的 claude 进程
66
- const result = execSync(`ps -axo pid,command 2>/dev/null | grep -v grep | grep "claude" | grep "${sessionId}" || true`, { encoding: 'utf-8', timeout: 3000 });
67
- return result.trim().length > 0;
74
+ // macOS/Linux: 用 ps 搜索包含该 sessionId 的 claude 进程
75
+ // -F 固定字符串匹配,避免正则意外;-- 防止 sessionId 被误认为 flag
76
+ const result = execSync(`ps -axo pid,command 2>/dev/null | grep -v grep | grep "claude" | grep -F -- "${sessionId}" || true`, { encoding: 'utf-8', timeout: 3000 });
77
+ if (result.trim().length === 0)
78
+ return false;
79
+ // 进程存在,但可能只是 idle 在终端等输入。检查文件 mtime:
80
+ // 如果 session 文件超过 30 秒未修改,说明 CLI 没在活跃处理。
81
+ if (sessionFilePath) {
82
+ try {
83
+ const stat = statSync(sessionFilePath);
84
+ const ageMs = Date.now() - stat.mtimeMs;
85
+ if (ageMs > 30_000) {
86
+ log.info(`CLI process found for ${sessionId} but session file idle for ${Math.round(ageMs / 1000)}s, treating as inactive`);
87
+ return false;
88
+ }
89
+ }
90
+ catch {
91
+ // stat 失败时保守地认为活跃
92
+ }
93
+ }
94
+ return true;
68
95
  }
69
96
  catch {
70
97
  return false;
@@ -98,7 +125,7 @@ export function findLatestClaudeSession(workDir, homeOverride) {
98
125
  // 跳过空文件
99
126
  if (stat.size === 0)
100
127
  continue;
101
- sessions.push({ sessionId, mtime: stat.mtimeMs, filePath });
128
+ sessions.push({ sessionId, mtime: stat.mtimeMs, filePath, size: stat.size });
102
129
  }
103
130
  catch {
104
131
  continue;
@@ -111,7 +138,7 @@ export function findLatestClaudeSession(workDir, homeOverride) {
111
138
  // 按修改时间倒序,最新的在前
112
139
  sessions.sort((a, b) => b.mtime - a.mtime);
113
140
  const latest = sessions[0];
114
- log.info(`Found latest Claude Code session: ${latest.sessionId} (mtime: ${new Date(latest.mtime).toISOString()}, size: ${statSync(latest.filePath).size})`);
141
+ log.info(`Found latest Claude Code session: ${latest.sessionId} (mtime: ${new Date(latest.mtime).toISOString()}, size: ${latest.size})`);
115
142
  return latest;
116
143
  }
117
144
  catch (err) {
@@ -121,11 +148,17 @@ export function findLatestClaudeSession(workDir, homeOverride) {
121
148
  }
122
149
  /**
123
150
  * 从 JSONL 文件的第一行提取 sessionId,验证文件内容与文件名一致。
151
+ * 只读取前 4KB,避免大文件全量读入内存。
124
152
  */
125
153
  function validateSessionFile(filePath, expectedSessionId) {
154
+ let fd;
126
155
  try {
127
- const content = readFileSync(filePath, 'utf-8');
128
- const firstLine = content.split('\n')[0];
156
+ fd = openSync(filePath, 'r');
157
+ const buf = Buffer.alloc(4096);
158
+ const bytesRead = readSync(fd, buf, 0, 4096, 0);
159
+ if (bytesRead === 0)
160
+ return false;
161
+ const firstLine = buf.toString('utf-8', 0, bytesRead).split('\n')[0];
129
162
  if (!firstLine)
130
163
  return false;
131
164
  const firstEntry = JSON.parse(firstLine);
@@ -134,6 +167,14 @@ function validateSessionFile(filePath, expectedSessionId) {
134
167
  catch {
135
168
  return false;
136
169
  }
170
+ finally {
171
+ if (fd !== undefined) {
172
+ try {
173
+ closeSync(fd);
174
+ }
175
+ catch { /* ignore */ }
176
+ }
177
+ }
137
178
  }
138
179
  // 存储所有活跃的 SDKSession 对象,key 为 sessionId
139
180
  // 使用 Map 而不是 Set,因为我们需要通过 sessionId 获取 session
@@ -341,7 +382,7 @@ async function getOrCreateSession(sessionId, workDir, model, permissionMode, onS
341
382
  const latest = findLatestClaudeSession(workDir);
342
383
  if (latest) {
343
384
  // 安全检查:如果 CLI 正在使用该 session,不能接管
344
- if (isCliSessionActive(latest.sessionId)) {
385
+ if (isCliSessionActive(latest.sessionId, latest.filePath)) {
345
386
  log.info(`CLI is actively using session ${latest.sessionId}, skipping auto-resume`);
346
387
  }
347
388
  else {
@@ -357,7 +398,7 @@ async function getOrCreateSession(sessionId, workDir, model, permissionMode, onS
357
398
  return { session, sessionId: latest.sessionId };
358
399
  }
359
400
  catch (err) {
360
- log.warn(`Failed to auto-resume CLI session ${latest.sessionId}, creating new one: ${err}`);
401
+ log.warn(`Failed to auto-resume CLI session ${latest.sessionId}, skipping auto-resume: ${err}`);
361
402
  }
362
403
  }
363
404
  else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wu529778790/open-im",
3
- "version": "1.10.9-beta.5",
3
+ "version": "1.10.9-beta.7",
4
4
  "description": "Multi-platform IM bridge for AI CLI tools (Claude, Codex, CodeBuddy)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",