@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.
|
@@ -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
|
|
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
|
-
|
|
67
|
-
|
|
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: ${
|
|
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
|
-
|
|
128
|
-
const
|
|
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},
|
|
401
|
+
log.warn(`Failed to auto-resume CLI session ${latest.sessionId}, skipping auto-resume: ${err}`);
|
|
361
402
|
}
|
|
362
403
|
}
|
|
363
404
|
else {
|