@wu529778790/open-im 1.10.9-beta.2 → 1.10.9-beta.21
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.
- package/README.md +43 -62
- package/README.zh-CN.md +43 -62
- package/dist/adapters/claude-sdk-adapter.d.ts +13 -0
- package/dist/adapters/claude-sdk-adapter.js +221 -23
- package/dist/adapters/registry.js +3 -0
- package/dist/channels/capabilities.js +5 -0
- package/dist/clawbot/client.d.ts +14 -0
- package/dist/clawbot/client.js +299 -0
- package/dist/clawbot/event-handler.d.ts +12 -0
- package/dist/clawbot/event-handler.js +85 -0
- package/dist/clawbot/message-sender.d.ts +18 -0
- package/dist/clawbot/message-sender.js +109 -0
- package/dist/clawbot/qr-login.d.ts +33 -0
- package/dist/clawbot/qr-login.js +120 -0
- package/dist/clawbot/types.d.ts +111 -0
- package/dist/clawbot/types.js +7 -0
- package/dist/codebuddy/cli-runner.js +31 -2
- package/dist/codex/cli-runner.js +28 -2
- package/dist/config/file-io.d.ts +6 -3
- package/dist/config/file-io.js +12 -7
- package/dist/config/types.d.ts +17 -1
- package/dist/config-web-page-i18n.d.ts +24 -2
- package/dist/config-web-page-i18n.js +24 -2
- package/dist/config-web.js +79 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +38 -2
- package/dist/constants.d.ts +6 -0
- package/dist/constants.js +6 -0
- package/dist/dingtalk/client.js +2 -1
- package/dist/dingtalk/event-handler.js +1 -1
- package/dist/index.js +50 -0
- package/dist/qq/client.js +7 -1
- package/dist/queue/request-queue.js +11 -10
- package/dist/setup.js +131 -3
- package/dist/shared/active-chats.d.ts +2 -2
- package/dist/shared/ai-task.d.ts +14 -0
- package/dist/shared/ai-task.js +57 -9
- package/dist/shared/process-kill.d.ts +24 -0
- package/dist/shared/process-kill.js +79 -0
- package/dist/shared/reconnect.d.ts +28 -0
- package/dist/shared/reconnect.js +56 -0
- package/dist/shared/task-cleanup.d.ts +16 -0
- package/dist/shared/task-cleanup.js +34 -1
- package/dist/telegram/client.js +7 -1
- package/dist/telemetry/telemetry-upload.js +1 -1
- package/dist/wework/client.js +17 -5
- package/dist/wework/event-handler.js +3 -0
- package/dist/workbuddy/centrifuge-client.d.ts +4 -0
- package/dist/workbuddy/centrifuge-client.js +76 -28
- package/dist/workbuddy/client.js +39 -2
- package/package.json +1 -1
- package/web/dist/assets/index-BaLTMeeF.js +57 -0
- package/web/dist/index.html +1 -1
- package/dist/config/credentials.d.ts +0 -19
- package/dist/config/credentials.js +0 -36
- package/web/dist/assets/index-B-oVSMUp.js +0 -57
|
@@ -9,7 +9,8 @@
|
|
|
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 } from 'fs';
|
|
12
|
+
import { existsSync, readFileSync, readdirSync, statSync, openSync, readSync, closeSync } from 'fs';
|
|
13
|
+
import { execSync } from 'child_process';
|
|
13
14
|
import { homedir } from 'os';
|
|
14
15
|
import { join } from 'path';
|
|
15
16
|
import { createLogger } from '../logger.js';
|
|
@@ -46,9 +47,141 @@ function loadUserPluginSettings() {
|
|
|
46
47
|
}
|
|
47
48
|
// Pre-load user plugin settings to cache Claude Code user preferences
|
|
48
49
|
loadUserPluginSettings();
|
|
50
|
+
// ── 扫描 Claude Code CLI 的最新 session,支持手机/电脑无缝切换 ──
|
|
51
|
+
/**
|
|
52
|
+
* 将 workDir(如 /Users/mac/github/open-im)转换为 Claude Code 的项目路径编码
|
|
53
|
+
* ~/.claude/projects/-Users-mac-github-open-im/
|
|
54
|
+
*/
|
|
55
|
+
function workDirToProjectPath(workDir) {
|
|
56
|
+
// Claude Code 将路径中的 / 替换为 -,leading - 保留
|
|
57
|
+
// /Users/mac/github/open-im → -Users-mac-github-open-im
|
|
58
|
+
return workDir.replace(/\//g, '-');
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
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
|
|
71
|
+
*/
|
|
72
|
+
function isCliSessionActive(sessionId, sessionFilePath) {
|
|
73
|
+
try {
|
|
74
|
+
// macOS/Linux: 用 ps 搜索包含该 sessionId 的 claude 进程
|
|
75
|
+
// 排除 open-im 自己的 SDK 子进程(路径含 claude-agent-sdk),只匹配用户交互式 CLI
|
|
76
|
+
// -F 固定字符串匹配,避免正则意外;-- 防止 sessionId 被误认为 flag
|
|
77
|
+
const result = execSync(`ps -axo pid,command 2>/dev/null | grep -v grep | grep "claude" | grep -v "claude-agent-sdk" | grep -F -- "${sessionId}" || true`, { encoding: 'utf-8', timeout: 3000 });
|
|
78
|
+
if (result.trim().length === 0)
|
|
79
|
+
return false;
|
|
80
|
+
// 进程存在,但可能只是 idle 在终端等输入。检查文件 mtime:
|
|
81
|
+
// 如果 session 文件超过 30 秒未修改,说明 CLI 没在活跃处理。
|
|
82
|
+
if (sessionFilePath) {
|
|
83
|
+
try {
|
|
84
|
+
const stat = statSync(sessionFilePath);
|
|
85
|
+
const ageMs = Date.now() - stat.mtimeMs;
|
|
86
|
+
if (ageMs > 30_000) {
|
|
87
|
+
log.info(`CLI process found for ${sessionId} but session file idle for ${Math.round(ageMs / 1000)}s, treating as inactive`);
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// stat 失败时保守地认为活跃
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 扫描 ~/.claude/projects/<encoded-path>/ 找到最新的 CLI session。
|
|
103
|
+
* 只返回 JSONL 文件(排除子目录如 subagents/),按修改时间倒序。
|
|
104
|
+
* @param homeOverride 测试用:覆盖 homedir() 的返回值
|
|
105
|
+
*/
|
|
106
|
+
export function findLatestClaudeSession(workDir, homeOverride) {
|
|
107
|
+
const projectDir = join(homeOverride ?? homedir(), '.claude', 'projects', workDirToProjectPath(workDir));
|
|
108
|
+
if (!existsSync(projectDir)) {
|
|
109
|
+
log.info(`No Claude Code project dir found: ${projectDir}`);
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const entries = readdirSync(projectDir, { withFileTypes: true });
|
|
114
|
+
const sessions = [];
|
|
115
|
+
for (const entry of entries) {
|
|
116
|
+
// 只处理 .jsonl 文件,跳过子目录(如 subagents/)和 memory 目录
|
|
117
|
+
if (!entry.isFile() || !entry.name.endsWith('.jsonl'))
|
|
118
|
+
continue;
|
|
119
|
+
const sessionId = entry.name.replace('.jsonl', '');
|
|
120
|
+
// 校验 UUID 格式
|
|
121
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/.test(sessionId))
|
|
122
|
+
continue;
|
|
123
|
+
const filePath = join(projectDir, entry.name);
|
|
124
|
+
try {
|
|
125
|
+
const stat = statSync(filePath);
|
|
126
|
+
// 跳过空文件
|
|
127
|
+
if (stat.size === 0)
|
|
128
|
+
continue;
|
|
129
|
+
sessions.push({ sessionId, mtime: stat.mtimeMs, filePath, size: stat.size });
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (sessions.length === 0) {
|
|
136
|
+
log.info(`No session files found in ${projectDir}`);
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
// 按修改时间倒序,最新的在前
|
|
140
|
+
sessions.sort((a, b) => b.mtime - a.mtime);
|
|
141
|
+
const latest = sessions[0];
|
|
142
|
+
log.info(`Found latest Claude Code session: ${latest.sessionId} (mtime: ${new Date(latest.mtime).toISOString()}, size: ${latest.size})`);
|
|
143
|
+
return latest;
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
log.warn(`Failed to scan Claude Code project dir: ${err}`);
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* 从 JSONL 文件的第一行提取 sessionId,验证文件内容与文件名一致。
|
|
152
|
+
* 只读取前 4KB,避免大文件全量读入内存。
|
|
153
|
+
*/
|
|
154
|
+
function validateSessionFile(filePath, expectedSessionId) {
|
|
155
|
+
let fd;
|
|
156
|
+
try {
|
|
157
|
+
fd = openSync(filePath, 'r');
|
|
158
|
+
const buf = Buffer.alloc(4096);
|
|
159
|
+
const bytesRead = readSync(fd, buf, 0, 4096, 0);
|
|
160
|
+
if (bytesRead === 0)
|
|
161
|
+
return false;
|
|
162
|
+
const firstLine = buf.toString('utf-8', 0, bytesRead).split('\n')[0];
|
|
163
|
+
if (!firstLine)
|
|
164
|
+
return false;
|
|
165
|
+
const firstEntry = JSON.parse(firstLine);
|
|
166
|
+
return firstEntry.sessionId === expectedSessionId;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
if (fd !== undefined) {
|
|
173
|
+
try {
|
|
174
|
+
closeSync(fd);
|
|
175
|
+
}
|
|
176
|
+
catch { /* ignore */ }
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
49
180
|
// 存储所有活跃的 SDKSession 对象,key 为 sessionId
|
|
50
181
|
// 使用 Map 而不是 Set,因为我们需要通过 sessionId 获取 session
|
|
51
182
|
const activeSessions = new Map();
|
|
183
|
+
// 记录每个 session 创建/恢复时的 workDir,防止跨 workDir 复用已固定 cwd 的子进程
|
|
184
|
+
const sessionWorkDirs = new Map();
|
|
52
185
|
// 存储正在进行的流式迭代器,用于中断
|
|
53
186
|
const activeStreams = new Set();
|
|
54
187
|
// 空闲会话清理:跟踪最后使用时间,定期清除超时会话
|
|
@@ -92,34 +225,53 @@ const cleanupInterval = setInterval(() => {
|
|
|
92
225
|
activeSessions.delete(id);
|
|
93
226
|
}
|
|
94
227
|
sessionLastUsed.delete(id);
|
|
228
|
+
sessionWorkDirs.delete(id);
|
|
95
229
|
log.info(`Cleaned up idle session (unused ${Math.round((now - lastUsed) / 60000)}min): ${id}`);
|
|
96
230
|
}
|
|
97
231
|
}
|
|
98
232
|
}, CLEANUP_INTERVAL_MS);
|
|
99
233
|
cleanupInterval.unref(); // 不阻止进程退出
|
|
100
234
|
/**
|
|
101
|
-
*
|
|
235
|
+
* 串行化进程级 process.chdir() —— 同一时刻仅一个 chdir 生效。
|
|
102
236
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
* concurrent requests don't race on the working directory.
|
|
237
|
+
* SDK V2 的 createSession/resumeSession 不接受 cwd 参数;且 send()/stream()
|
|
238
|
+
* 会以「调用时的 process.cwd()」派生 Claude Code 子进程。必须用互斥锁串行化
|
|
239
|
+
* 「整个 turn 的 cwd 切换」,否则并发多用户会让工具跑错目录。
|
|
107
240
|
*
|
|
108
|
-
* **TODO:**
|
|
109
|
-
* supports a `cwd` option in createSession/resumeSession. Track upstream:
|
|
241
|
+
* **TODO:** SDK 支持 cwd 选项后移除此锁。upstream:
|
|
110
242
|
* https://github.com/anthropics/claude-agent-sdk/issues
|
|
111
243
|
*/
|
|
112
244
|
let chdirMutex = Promise.resolve();
|
|
113
245
|
function withChdirMutex(fn) {
|
|
114
246
|
const previous = chdirMutex;
|
|
115
|
-
let
|
|
116
|
-
chdirMutex = new Promise((r) => {
|
|
117
|
-
return previous.then(() => {
|
|
247
|
+
let release;
|
|
248
|
+
chdirMutex = new Promise((r) => { release = r; });
|
|
249
|
+
return previous.then(async () => {
|
|
118
250
|
try {
|
|
119
|
-
return fn();
|
|
251
|
+
return await fn();
|
|
120
252
|
}
|
|
121
253
|
finally {
|
|
122
|
-
|
|
254
|
+
release();
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* 在持有全局 chdir 互斥锁期间,把进程 cwd 切到 workDir 执行 fn,结束后恢复。
|
|
260
|
+
* 用于包裹 session.send()+stream(),确保子进程在正确 workDir 派生。
|
|
261
|
+
*/
|
|
262
|
+
function runWithWorkDir(workDir, fn) {
|
|
263
|
+
return withChdirMutex(async () => {
|
|
264
|
+
const originalCwd = process.cwd();
|
|
265
|
+
if (workDir && workDir !== originalCwd) {
|
|
266
|
+
process.chdir(workDir);
|
|
267
|
+
}
|
|
268
|
+
try {
|
|
269
|
+
return await fn();
|
|
270
|
+
}
|
|
271
|
+
finally {
|
|
272
|
+
if (workDir && workDir !== originalCwd) {
|
|
273
|
+
process.chdir(originalCwd);
|
|
274
|
+
}
|
|
123
275
|
}
|
|
124
276
|
});
|
|
125
277
|
}
|
|
@@ -201,18 +353,21 @@ async function getOrCreateSession(sessionId, workDir, model, permissionMode, onS
|
|
|
201
353
|
process.chdir(workDir);
|
|
202
354
|
}
|
|
203
355
|
if (sessionId) {
|
|
204
|
-
// 优先复用内存中已有的 SDKSession
|
|
356
|
+
// 优先复用内存中已有的 SDKSession,避免每次都启动新进程。
|
|
357
|
+
// 仅当 workDir 与创建时一致才复用:否则子进程 cwd 已固定在旧目录,
|
|
358
|
+
// 需走 resume 重新派生(在当前 workDir 启动新子进程)。
|
|
205
359
|
const existing = activeSessions.get(sessionId);
|
|
206
|
-
if (existing) {
|
|
360
|
+
if (existing && sessionWorkDirs.get(sessionId) === workDir) {
|
|
207
361
|
log.info(`Reusing existing in-memory session: ${sessionId}`);
|
|
208
362
|
sessionLastUsed.set(sessionId, Date.now());
|
|
209
363
|
return { session: existing, sessionId };
|
|
210
364
|
}
|
|
211
|
-
//
|
|
365
|
+
// 内存中没有(或 workDir 变了),尝试通过 resume 恢复(会启动新 CLI 进程)
|
|
212
366
|
try {
|
|
213
367
|
log.info(`Attempting to resume session: ${sessionId}`);
|
|
214
368
|
session = unstable_v2_resumeSession(sessionId, sessionOptions);
|
|
215
369
|
activeSessions.set(sessionId, session);
|
|
370
|
+
sessionWorkDirs.set(sessionId, workDir);
|
|
216
371
|
sessionLastUsed.set(sessionId, Date.now());
|
|
217
372
|
log.info(`Successfully resumed session: ${sessionId}`);
|
|
218
373
|
return { session, sessionId };
|
|
@@ -222,12 +377,43 @@ async function getOrCreateSession(sessionId, workDir, model, permissionMode, onS
|
|
|
222
377
|
// 恢复失败,创建新会话
|
|
223
378
|
}
|
|
224
379
|
}
|
|
380
|
+
// 没有指定 sessionId 时,尝试自动恢复 Claude Code CLI 的最新 session
|
|
381
|
+
// 实现手机/电脑无缝切换:同目录下默认共享同一个对话
|
|
382
|
+
if (!sessionId) {
|
|
383
|
+
const latest = findLatestClaudeSession(workDir);
|
|
384
|
+
if (latest) {
|
|
385
|
+
// 检测 CLI 是否正在使用该 session(用于日志,不阻止 resume)
|
|
386
|
+
const cliActive = isCliSessionActive(latest.sessionId, latest.filePath);
|
|
387
|
+
if (cliActive) {
|
|
388
|
+
log.info(`CLI is actively using session ${latest.sessionId}, attempting resume anyway (SDK handles concurrency)`);
|
|
389
|
+
}
|
|
390
|
+
// 验证文件内容一致性
|
|
391
|
+
if (validateSessionFile(latest.filePath, latest.sessionId)) {
|
|
392
|
+
try {
|
|
393
|
+
log.info(`Auto-resuming latest CLI session: ${latest.sessionId}${cliActive ? ' (CLI active)' : ''}`);
|
|
394
|
+
session = unstable_v2_resumeSession(latest.sessionId, sessionOptions);
|
|
395
|
+
activeSessions.set(latest.sessionId, session);
|
|
396
|
+
sessionWorkDirs.set(latest.sessionId, workDir);
|
|
397
|
+
sessionLastUsed.set(latest.sessionId, Date.now());
|
|
398
|
+
log.info(`Successfully auto-resumed CLI session: ${latest.sessionId}`);
|
|
399
|
+
return { session, sessionId: latest.sessionId };
|
|
400
|
+
}
|
|
401
|
+
catch (err) {
|
|
402
|
+
log.warn(`Failed to auto-resume CLI session ${latest.sessionId}, creating new one: ${err}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
log.warn(`Session file validation failed for ${latest.sessionId}, skipping`);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
225
410
|
// 创建新会话
|
|
226
411
|
session = unstable_v2_createSession(sessionOptions);
|
|
227
412
|
// 新会话的 sessionId 需要从第一个消息中获取
|
|
228
413
|
// 暂时返回 undefined,稍后在 init 消息中获取
|
|
229
414
|
const tempId = `pending-${++sessionSeq}`;
|
|
230
415
|
activeSessions.set(tempId, session);
|
|
416
|
+
sessionWorkDirs.set(tempId, workDir);
|
|
231
417
|
sessionLastUsed.set(tempId, Date.now());
|
|
232
418
|
log.info(`Created new session (tempId: ${tempId})`);
|
|
233
419
|
return { session, sessionId: tempId, wasReused: false };
|
|
@@ -267,6 +453,7 @@ export class ClaudeSDKAdapter {
|
|
|
267
453
|
}
|
|
268
454
|
activeSessions.clear();
|
|
269
455
|
sessionLastUsed.clear();
|
|
456
|
+
sessionWorkDirs.clear();
|
|
270
457
|
}
|
|
271
458
|
/**
|
|
272
459
|
* Remove a specific session from the in-memory cache and close it.
|
|
@@ -281,6 +468,7 @@ export class ClaudeSDKAdapter {
|
|
|
281
468
|
catch { /* ignore */ }
|
|
282
469
|
activeSessions.delete(sessionId);
|
|
283
470
|
sessionLastUsed.delete(sessionId);
|
|
471
|
+
sessionWorkDirs.delete(sessionId);
|
|
284
472
|
log.info(`Explicitly removed session: ${sessionId}`);
|
|
285
473
|
}
|
|
286
474
|
}
|
|
@@ -320,12 +508,16 @@ export class ClaudeSDKAdapter {
|
|
|
320
508
|
}
|
|
321
509
|
runningSessions.add(returnedId);
|
|
322
510
|
trackedRunningId = returnedId;
|
|
323
|
-
//
|
|
324
|
-
|
|
325
|
-
//
|
|
326
|
-
const stream =
|
|
327
|
-
|
|
328
|
-
|
|
511
|
+
// 在持有 chdir 锁期间完成 send() + stream() 获取:SDK V2 会以当前
|
|
512
|
+
// process.cwd() 派生 Claude Code 子进程,必须保证此刻 cwd 为 workDir。
|
|
513
|
+
// 锁串行化后,并发多用户的子进程不会在错误的目录派生。
|
|
514
|
+
const stream = await runWithWorkDir(workDir, async () => {
|
|
515
|
+
await session.send(prompt);
|
|
516
|
+
const s = session.stream();
|
|
517
|
+
currentStream = s;
|
|
518
|
+
activeStreams.add(s);
|
|
519
|
+
return s;
|
|
520
|
+
});
|
|
329
521
|
let accumulated = '';
|
|
330
522
|
let accumulatedThinking = '';
|
|
331
523
|
const toolStats = {};
|
|
@@ -348,13 +540,19 @@ export class ClaudeSDKAdapter {
|
|
|
348
540
|
// 更新 sessionId 映射
|
|
349
541
|
// 清理 pending 临时 ID(actualSessionId 尚未赋值时用 pendingTempId)
|
|
350
542
|
const idToClean = actualSessionId ?? pendingTempId;
|
|
543
|
+
const inheritedWorkDir = idToClean ? sessionWorkDirs.get(idToClean) : undefined;
|
|
351
544
|
if (idToClean?.startsWith('pending-')) {
|
|
352
545
|
activeSessions.delete(idToClean);
|
|
353
546
|
}
|
|
354
547
|
activeSessions.set(newSessionId, session);
|
|
548
|
+
if (inheritedWorkDir !== undefined) {
|
|
549
|
+
sessionWorkDirs.set(newSessionId, inheritedWorkDir);
|
|
550
|
+
}
|
|
355
551
|
sessionLastUsed.set(newSessionId, Date.now());
|
|
356
|
-
if (idToClean)
|
|
552
|
+
if (idToClean) {
|
|
357
553
|
sessionLastUsed.delete(idToClean);
|
|
554
|
+
sessionWorkDirs.delete(idToClean);
|
|
555
|
+
}
|
|
358
556
|
// 更新 runningSessions:移除旧 ID,添加新 ID
|
|
359
557
|
if (idToClean)
|
|
360
558
|
runningSessions.delete(idToClean);
|
|
@@ -3,6 +3,7 @@ import { ClaudeSDKAdapter, configureClaudeSdkSessionIdle } from './claude-sdk-ad
|
|
|
3
3
|
import { CodexAdapter } from './codex-adapter.js';
|
|
4
4
|
import { CodeBuddyAdapter } from './codebuddy-adapter.js';
|
|
5
5
|
import { createLogger } from '../logger.js';
|
|
6
|
+
import { destroyAllLiveChildren } from '../shared/process-kill.js';
|
|
6
7
|
const log = createLogger('Registry');
|
|
7
8
|
const adapters = new Map();
|
|
8
9
|
export function initAdapters(config) {
|
|
@@ -30,5 +31,7 @@ export function getAdapter(aiCommand) {
|
|
|
30
31
|
}
|
|
31
32
|
export function cleanupAdapters() {
|
|
32
33
|
ClaudeSDKAdapter.destroy();
|
|
34
|
+
// 强制终止仍在运行的 CLI 子进程(Codex/CodeBuddy),避免僵尸 / 孤儿
|
|
35
|
+
destroyAllLiveChildren();
|
|
33
36
|
adapters.clear();
|
|
34
37
|
}
|
|
@@ -5,6 +5,7 @@ const PLATFORM_LABELS = {
|
|
|
5
5
|
wework: "企业微信",
|
|
6
6
|
dingtalk: "钉钉",
|
|
7
7
|
workbuddy: "WorkBuddy",
|
|
8
|
+
clawbot: "ClawBot",
|
|
8
9
|
};
|
|
9
10
|
export const CHANNEL_CAPABILITIES = {
|
|
10
11
|
telegram: {
|
|
@@ -31,6 +32,10 @@ export const CHANNEL_CAPABILITIES = {
|
|
|
31
32
|
inbound: { text: "native", image: "none", file: "none", voice: "none", video: "none" },
|
|
32
33
|
outbound: { streamEdit: "none", streamPush: "none", image: "none", card: "none", typing: "none" },
|
|
33
34
|
},
|
|
35
|
+
clawbot: {
|
|
36
|
+
inbound: { text: "native", image: "none", file: "none", voice: "none", video: "none" },
|
|
37
|
+
outbound: { streamEdit: "none", streamPush: "none", image: "none", card: "none", typing: "none" },
|
|
38
|
+
},
|
|
34
39
|
};
|
|
35
40
|
function listPreferredPlatforms(kind) {
|
|
36
41
|
return Object.entries(CHANNEL_CAPABILITIES)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ClawBot Client - WeChat iLink Bot API long-polling client
|
|
3
|
+
*
|
|
4
|
+
* Uses the official iLink protocol: POST + JSON body + Bearer token auth.
|
|
5
|
+
* Receives messages via long-polling ilink/bot/getupdates and dispatches
|
|
6
|
+
* them to the event handler.
|
|
7
|
+
*
|
|
8
|
+
* Reference: @tencent-weixin/openclaw-weixin, cc-wechat, claude-code-wechat-channel
|
|
9
|
+
*/
|
|
10
|
+
import type { Config } from '../config.js';
|
|
11
|
+
import type { ClawBotState } from './types.js';
|
|
12
|
+
export declare function getChannelState(): ClawBotState;
|
|
13
|
+
export declare function initClawbot(config: Config, eventHandler: (chatId: string, msgId: string, content: string) => Promise<void>, onStateChange?: (state: ClawBotState) => void): Promise<void>;
|
|
14
|
+
export declare function stopClawbot(): void;
|