@wu529778790/open-im 1.10.9-beta.6 → 1.10.9-beta.8
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,6 +9,11 @@
|
|
|
9
9
|
* 认证:ANTHROPIC_API_KEY 或 CLAUDE_CODE_OAUTH_TOKEN
|
|
10
10
|
*/
|
|
11
11
|
import type { ToolAdapter, RunCallbacks, RunOptions, RunHandle } from './tool-adapter.interface.js';
|
|
12
|
+
/**
|
|
13
|
+
* 标记某个 workDir 下的用户显式执行了 /new,下次创建会话时跳过 auto-resume。
|
|
14
|
+
* 由命令处理器在 /new 时调用。
|
|
15
|
+
*/
|
|
16
|
+
export declare function markSkipAutoResume(workDir: string): void;
|
|
12
17
|
interface ClaudeSessionMeta {
|
|
13
18
|
sessionId: string;
|
|
14
19
|
mtime: number;
|
|
@@ -58,20 +58,53 @@ function workDirToProjectPath(workDir) {
|
|
|
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
|
+
*
|
|
62
70
|
* 注意:仅支持 macOS/Linux,Windows 上会静默返回 false
|
|
63
71
|
*/
|
|
64
|
-
function isCliSessionActive(sessionId) {
|
|
72
|
+
function isCliSessionActive(sessionId, sessionFilePath) {
|
|
65
73
|
try {
|
|
66
74
|
// macOS/Linux: 用 ps 搜索包含该 sessionId 的 claude 进程
|
|
67
75
|
// -F 固定字符串匹配,避免正则意外;-- 防止 sessionId 被误认为 flag
|
|
68
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 });
|
|
69
|
-
|
|
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;
|
|
70
95
|
}
|
|
71
96
|
catch {
|
|
72
97
|
return false;
|
|
73
98
|
}
|
|
74
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* 标记某个 workDir 下的用户显式执行了 /new,下次创建会话时跳过 auto-resume。
|
|
102
|
+
* 由命令处理器在 /new 时调用。
|
|
103
|
+
*/
|
|
104
|
+
export function markSkipAutoResume(workDir) {
|
|
105
|
+
skipAutoResumeFor.add(workDir);
|
|
106
|
+
log.info(`Marked skip auto-resume for workDir: ${workDir}`);
|
|
107
|
+
}
|
|
75
108
|
/**
|
|
76
109
|
* 扫描 ~/.claude/projects/<encoded-path>/ 找到最新的 CLI session。
|
|
77
110
|
* 只返回 JSONL 文件(排除子目录如 subagents/),按修改时间倒序。
|
|
@@ -154,6 +187,9 @@ function validateSessionFile(filePath, expectedSessionId) {
|
|
|
154
187
|
// 存储所有活跃的 SDKSession 对象,key 为 sessionId
|
|
155
188
|
// 使用 Map 而不是 Set,因为我们需要通过 sessionId 获取 session
|
|
156
189
|
const activeSessions = new Map();
|
|
190
|
+
// 用户显式执行 /new 后标记跳过 auto-resume,避免立即接回旧 CLI session
|
|
191
|
+
// key 格式: `${workDir}\0${sessionIdPrefix}`(用 workDir 区分不同项目目录)
|
|
192
|
+
const skipAutoResumeFor = new Set();
|
|
157
193
|
// 记录每个 session 创建/恢复时的 workDir,防止跨 workDir 复用已固定 cwd 的子进程
|
|
158
194
|
const sessionWorkDirs = new Map();
|
|
159
195
|
// 存储正在进行的流式迭代器,用于中断
|
|
@@ -354,30 +390,37 @@ async function getOrCreateSession(sessionId, workDir, model, permissionMode, onS
|
|
|
354
390
|
// 没有指定 sessionId 时,尝试自动恢复 Claude Code CLI 的最新 session
|
|
355
391
|
// 实现手机/电脑无缝切换:同目录下默认共享同一个对话
|
|
356
392
|
if (!sessionId) {
|
|
357
|
-
|
|
358
|
-
if (
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
session = unstable_v2_resumeSession(latest.sessionId, sessionOptions);
|
|
369
|
-
activeSessions.set(latest.sessionId, session);
|
|
370
|
-
sessionWorkDirs.set(latest.sessionId, workDir);
|
|
371
|
-
sessionLastUsed.set(latest.sessionId, Date.now());
|
|
372
|
-
log.info(`Successfully auto-resumed CLI session: ${latest.sessionId}`);
|
|
373
|
-
return { session, sessionId: latest.sessionId };
|
|
374
|
-
}
|
|
375
|
-
catch (err) {
|
|
376
|
-
log.warn(`Failed to auto-resume CLI session ${latest.sessionId}, skipping auto-resume: ${err}`);
|
|
377
|
-
}
|
|
393
|
+
// 用户显式 /new 后跳过 auto-resume,创建真正的新会话
|
|
394
|
+
if (skipAutoResumeFor.has(workDir)) {
|
|
395
|
+
skipAutoResumeFor.delete(workDir);
|
|
396
|
+
log.info(`Skipping auto-resume for workDir ${workDir} (user explicit /new)`);
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
const latest = findLatestClaudeSession(workDir);
|
|
400
|
+
if (latest) {
|
|
401
|
+
// 安全检查:如果 CLI 正在使用该 session,不能接管
|
|
402
|
+
if (isCliSessionActive(latest.sessionId, latest.filePath)) {
|
|
403
|
+
log.info(`CLI is actively using session ${latest.sessionId}, skipping auto-resume`);
|
|
378
404
|
}
|
|
379
405
|
else {
|
|
380
|
-
|
|
406
|
+
// 验证文件内容一致性
|
|
407
|
+
if (validateSessionFile(latest.filePath, latest.sessionId)) {
|
|
408
|
+
try {
|
|
409
|
+
log.info(`Auto-resuming latest CLI session: ${latest.sessionId}`);
|
|
410
|
+
session = unstable_v2_resumeSession(latest.sessionId, sessionOptions);
|
|
411
|
+
activeSessions.set(latest.sessionId, session);
|
|
412
|
+
sessionWorkDirs.set(latest.sessionId, workDir);
|
|
413
|
+
sessionLastUsed.set(latest.sessionId, Date.now());
|
|
414
|
+
log.info(`Successfully auto-resumed CLI session: ${latest.sessionId}`);
|
|
415
|
+
return { session, sessionId: latest.sessionId };
|
|
416
|
+
}
|
|
417
|
+
catch (err) {
|
|
418
|
+
log.warn(`Failed to auto-resume CLI session ${latest.sessionId}, skipping auto-resume: ${err}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
log.warn(`Session file validation failed for ${latest.sessionId}, skipping`);
|
|
423
|
+
}
|
|
381
424
|
}
|
|
382
425
|
}
|
|
383
426
|
}
|
package/dist/commands/handler.js
CHANGED
|
@@ -2,6 +2,7 @@ import { resolvePlatformAiCommand } from '../config.js';
|
|
|
2
2
|
import { escapePathForMarkdown } from '../shared/utils.js';
|
|
3
3
|
import { TERMINAL_ONLY_COMMANDS } from '../constants.js';
|
|
4
4
|
import { createLogger } from '../logger.js';
|
|
5
|
+
import { markSkipAutoResume } from '../adapters/claude-sdk-adapter.js';
|
|
5
6
|
const log = createLogger('Commands');
|
|
6
7
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
7
8
|
import { execFile } from 'node:child_process';
|
|
@@ -135,6 +136,8 @@ export class CommandHandler {
|
|
|
135
136
|
}
|
|
136
137
|
async handleNew(chatId, userId) {
|
|
137
138
|
this.deps.requestQueue.cancelUser(userId);
|
|
139
|
+
const workDir = this.deps.sessionManager.getWorkDir(userId);
|
|
140
|
+
markSkipAutoResume(workDir);
|
|
138
141
|
const ok = this.deps.sessionManager.newSession(userId);
|
|
139
142
|
await this.replySender().sendTextReply(chatId, ok
|
|
140
143
|
? '✅ AI 会话已重置,下一条消息将使用全新上下文。'
|