evolclaw 2.0.0 → 2.0.2
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/data/evolclaw.sample.json +31 -26
- package/dist/channels/wechat.js +451 -0
- package/dist/cli.js +196 -146
- package/dist/config.js +32 -17
- package/dist/core/agent-runner.js +27 -41
- package/dist/core/command-handler.js +72 -54
- package/dist/core/message-processor.js +36 -10
- package/dist/core/message-queue.js +9 -3
- package/dist/core/session-manager.js +81 -238
- package/dist/index.js +189 -115
- package/dist/utils/init-feishu.js +261 -0
- package/dist/utils/init-wechat.js +170 -0
- package/dist/utils/init.js +120 -67
- package/dist/utils/stream-flusher.js +3 -2
- package/package.json +9 -7
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import readline from 'readline';
|
|
3
|
+
import { resolvePaths } from '../paths.js';
|
|
4
|
+
const DEFAULT_BASE_URL = 'https://ilinkai.weixin.qq.com';
|
|
5
|
+
const BOT_TYPE = '3';
|
|
6
|
+
const QR_POLL_TIMEOUT_MS = 35_000;
|
|
7
|
+
const LOGIN_TIMEOUT_MS = 480_000;
|
|
8
|
+
function ask(rl, question) {
|
|
9
|
+
return new Promise(resolve => rl.question(question, resolve));
|
|
10
|
+
}
|
|
11
|
+
async function fetchQRCode(baseUrl) {
|
|
12
|
+
const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
13
|
+
const url = `${base}ilink/bot/get_bot_qrcode?bot_type=${BOT_TYPE}`;
|
|
14
|
+
const res = await fetch(url);
|
|
15
|
+
if (!res.ok)
|
|
16
|
+
throw new Error(`QR fetch failed: ${res.status}`);
|
|
17
|
+
return (await res.json());
|
|
18
|
+
}
|
|
19
|
+
async function pollQRStatus(baseUrl, qrcode) {
|
|
20
|
+
const base = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
21
|
+
const url = `${base}ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`;
|
|
22
|
+
const controller = new AbortController();
|
|
23
|
+
const timer = setTimeout(() => controller.abort(), QR_POLL_TIMEOUT_MS);
|
|
24
|
+
try {
|
|
25
|
+
const res = await fetch(url, {
|
|
26
|
+
headers: { 'iLink-App-ClientVersion': '1' },
|
|
27
|
+
signal: controller.signal,
|
|
28
|
+
});
|
|
29
|
+
clearTimeout(timer);
|
|
30
|
+
if (!res.ok)
|
|
31
|
+
throw new Error(`QR status failed: ${res.status}`);
|
|
32
|
+
return (await res.json());
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
clearTimeout(timer);
|
|
36
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
37
|
+
return { status: 'wait' };
|
|
38
|
+
}
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export async function runWechatQrFlow() {
|
|
43
|
+
const qrResp = await fetchQRCode(DEFAULT_BASE_URL);
|
|
44
|
+
try {
|
|
45
|
+
const qrterm = await import('qrcode-terminal');
|
|
46
|
+
await new Promise(resolve => {
|
|
47
|
+
qrterm.default.generate(qrResp.qrcode_img_content, { small: true }, (qr) => {
|
|
48
|
+
console.log(qr);
|
|
49
|
+
resolve();
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
console.log(`请在浏览器中打开此链接扫码: ${qrResp.qrcode_img_content}\n`);
|
|
55
|
+
}
|
|
56
|
+
console.log('请用微信扫描上方二维码...\n');
|
|
57
|
+
const deadline = Date.now() + LOGIN_TIMEOUT_MS;
|
|
58
|
+
let scannedPrinted = false;
|
|
59
|
+
while (Date.now() < deadline) {
|
|
60
|
+
const status = await pollQRStatus(DEFAULT_BASE_URL, qrResp.qrcode);
|
|
61
|
+
switch (status.status) {
|
|
62
|
+
case 'wait':
|
|
63
|
+
process.stdout.write('.');
|
|
64
|
+
break;
|
|
65
|
+
case 'scaned':
|
|
66
|
+
if (!scannedPrinted) {
|
|
67
|
+
console.log('\n👀 已扫码,请在微信中确认...');
|
|
68
|
+
scannedPrinted = true;
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case 'expired':
|
|
72
|
+
console.error('\n二维码已过期');
|
|
73
|
+
return null;
|
|
74
|
+
case 'confirmed':
|
|
75
|
+
if (!status.ilink_bot_id || !status.bot_token) {
|
|
76
|
+
console.error('\n登录失败:服务器未返回完整信息');
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
baseUrl: status.baseurl || DEFAULT_BASE_URL,
|
|
81
|
+
token: status.bot_token,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
85
|
+
}
|
|
86
|
+
console.log('\n登录超时');
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
export async function cmdInitWechat() {
|
|
90
|
+
const p = resolvePaths();
|
|
91
|
+
if (!fs.existsSync(p.config)) {
|
|
92
|
+
console.log(`❌ 配置文件不存在,请先运行 evolclaw init`);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const config = JSON.parse(fs.readFileSync(p.config, 'utf-8'));
|
|
96
|
+
// 检查已有配置
|
|
97
|
+
if (config.channels?.wechat?.token) {
|
|
98
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
99
|
+
try {
|
|
100
|
+
const answer = (await ask(rl, '已有微信配置,是否重新登录?[y/N] ')).trim().toLowerCase();
|
|
101
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
102
|
+
console.log('已取消');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
rl.close();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
console.log('正在获取微信登录二维码...\n');
|
|
111
|
+
const qrResp = await fetchQRCode(DEFAULT_BASE_URL);
|
|
112
|
+
// 终端显示二维码
|
|
113
|
+
try {
|
|
114
|
+
const qrterm = await import('qrcode-terminal');
|
|
115
|
+
await new Promise(resolve => {
|
|
116
|
+
qrterm.default.generate(qrResp.qrcode_img_content, { small: true }, (qr) => {
|
|
117
|
+
console.log(qr);
|
|
118
|
+
resolve();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
console.log(`请在浏览器中打开此链接扫码: ${qrResp.qrcode_img_content}\n`);
|
|
124
|
+
}
|
|
125
|
+
console.log('请用微信扫描上方二维码...\n');
|
|
126
|
+
const deadline = Date.now() + LOGIN_TIMEOUT_MS;
|
|
127
|
+
let scannedPrinted = false;
|
|
128
|
+
while (Date.now() < deadline) {
|
|
129
|
+
const status = await pollQRStatus(DEFAULT_BASE_URL, qrResp.qrcode);
|
|
130
|
+
switch (status.status) {
|
|
131
|
+
case 'wait':
|
|
132
|
+
process.stdout.write('.');
|
|
133
|
+
break;
|
|
134
|
+
case 'scaned':
|
|
135
|
+
if (!scannedPrinted) {
|
|
136
|
+
console.log('\n👀 已扫码,请在微信中确认...');
|
|
137
|
+
scannedPrinted = true;
|
|
138
|
+
}
|
|
139
|
+
break;
|
|
140
|
+
case 'expired':
|
|
141
|
+
console.log('\n二维码已过期,请重新运行 evolclaw init wechat');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
break;
|
|
144
|
+
case 'confirmed': {
|
|
145
|
+
if (!status.ilink_bot_id || !status.bot_token) {
|
|
146
|
+
console.error('\n登录失败:服务器未返回完整信息');
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
// 写入配置
|
|
150
|
+
if (!config.channels)
|
|
151
|
+
config.channels = {};
|
|
152
|
+
config.channels.wechat = {
|
|
153
|
+
enabled: true,
|
|
154
|
+
baseUrl: status.baseurl || DEFAULT_BASE_URL,
|
|
155
|
+
token: status.bot_token,
|
|
156
|
+
};
|
|
157
|
+
fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
|
|
158
|
+
console.log(`\n✅ 微信连接成功!`);
|
|
159
|
+
console.log(` Bot ID: ${status.ilink_bot_id}`);
|
|
160
|
+
console.log(` User ID: ${status.ilink_user_id}`);
|
|
161
|
+
console.log(` 配置已写入: ${p.config}`);
|
|
162
|
+
console.log(`\n现在可以启动服务: evolclaw restart`);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
167
|
+
}
|
|
168
|
+
console.log('\n登录超时,请重新运行');
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
package/dist/utils/init.js
CHANGED
|
@@ -26,12 +26,19 @@ async function npmInstallGlobal(pkg) {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
async function sudoExec(cmd, args) {
|
|
29
|
+
// 让 n 安装到当前 node 所在的 prefix 目录
|
|
30
|
+
const env = { ...process.env };
|
|
31
|
+
if (cmd === 'n' && !env.N_PREFIX) {
|
|
32
|
+
const nodePrefix = process.config.variables?.node_prefix;
|
|
33
|
+
if (nodePrefix)
|
|
34
|
+
env.N_PREFIX = nodePrefix;
|
|
35
|
+
}
|
|
29
36
|
try {
|
|
30
|
-
await execFileAsync(cmd, args, { timeout: 120000 });
|
|
37
|
+
await execFileAsync(cmd, args, { timeout: 120000, env });
|
|
31
38
|
}
|
|
32
39
|
catch (e) {
|
|
33
40
|
if (e.stderr?.includes('EACCES') || e.message?.includes('EACCES') || e.code === 'EACCES') {
|
|
34
|
-
await execFileAsync('sudo', [cmd, ...args], { timeout: 120000 });
|
|
41
|
+
await execFileAsync('sudo', [cmd, ...args], { timeout: 120000, env });
|
|
35
42
|
}
|
|
36
43
|
else {
|
|
37
44
|
throw e;
|
|
@@ -49,6 +56,18 @@ async function checkEnvironment(rl) {
|
|
|
49
56
|
else {
|
|
50
57
|
console.log(` ✗ Node.js v${process.versions.node} — 需要 >= 22(node:sqlite 依赖)`);
|
|
51
58
|
// 检测 nvm
|
|
59
|
+
// 检测 bash 是否存在(nvm 和 n 都依赖 bash)
|
|
60
|
+
let hasBash = false;
|
|
61
|
+
try {
|
|
62
|
+
execFileSync('which', ['bash'], { encoding: 'utf-8' });
|
|
63
|
+
hasBash = true;
|
|
64
|
+
}
|
|
65
|
+
catch { }
|
|
66
|
+
if (!hasBash) {
|
|
67
|
+
console.log(' ⚠ 当前环境没有 bash(Alpine 容器?),无法自动升级 Node.js');
|
|
68
|
+
console.log(' → 请手动升级: apk add nodejs-current 或重建容器使用 node:22-alpine');
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
52
71
|
const hasNvm = !!process.env.NVM_DIR && fs.existsSync(process.env.NVM_DIR);
|
|
53
72
|
if (hasNvm) {
|
|
54
73
|
const answer = (await ask(rl, ' → 是否通过 nvm 升级到 Node.js 22?[Y/n] ')).trim().toLowerCase();
|
|
@@ -61,7 +80,8 @@ async function checkEnvironment(rl) {
|
|
|
61
80
|
const nvmDir = process.env.NVM_DIR;
|
|
62
81
|
const { stdout } = await execFileAsync('bash', ['-c', `source "${nvmDir}/nvm.sh" && nvm install 22 && nvm alias default 22`], { timeout: 120000 });
|
|
63
82
|
console.log(stdout.trim().split('\n').map(l => ` ${l}`).join('\n'));
|
|
64
|
-
console.log(' ✓ Node.js
|
|
83
|
+
console.log(' ✓ Node.js 升级完成');
|
|
84
|
+
console.log(' → 请打开新终端后重新运行 evolclaw init');
|
|
65
85
|
return false;
|
|
66
86
|
}
|
|
67
87
|
catch (e) {
|
|
@@ -86,7 +106,8 @@ async function checkEnvironment(rl) {
|
|
|
86
106
|
console.log(' 正在升级 Node.js...');
|
|
87
107
|
try {
|
|
88
108
|
await sudoExec('n', ['22']);
|
|
89
|
-
console.log(' ✓ Node.js
|
|
109
|
+
console.log(' ✓ Node.js 升级完成');
|
|
110
|
+
console.log(' → 请打开新终端后重新运行 evolclaw init');
|
|
90
111
|
return false;
|
|
91
112
|
}
|
|
92
113
|
catch (e) {
|
|
@@ -105,7 +126,8 @@ async function checkEnvironment(rl) {
|
|
|
105
126
|
await npmInstallGlobal('n');
|
|
106
127
|
console.log(' 正在升级 Node.js...');
|
|
107
128
|
await sudoExec('n', ['22']);
|
|
108
|
-
console.log(' ✓ Node.js
|
|
129
|
+
console.log(' ✓ Node.js 升级完成');
|
|
130
|
+
console.log(' → 请打开新终端后重新运行 evolclaw init');
|
|
109
131
|
return false;
|
|
110
132
|
}
|
|
111
133
|
catch (e) {
|
|
@@ -162,22 +184,10 @@ async function checkEnvironment(rl) {
|
|
|
162
184
|
// @anthropic-ai/claude-agent-sdk >= 0.2.75
|
|
163
185
|
let sdkAction = 'ok';
|
|
164
186
|
try {
|
|
165
|
-
|
|
187
|
+
// 用 require.resolve 找到 SDK 入口,推导 package.json 路径
|
|
166
188
|
const esmRequire = createRequire(import.meta.url);
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
catch {
|
|
171
|
-
try {
|
|
172
|
-
const globalRoot = execFileSync('npm', ['root', '-g'], { encoding: 'utf-8' }).trim();
|
|
173
|
-
const globalPath = path.join(globalRoot, '@anthropic-ai', 'claude-agent-sdk', 'package.json');
|
|
174
|
-
if (fs.existsSync(globalPath))
|
|
175
|
-
sdkPkgPath = globalPath;
|
|
176
|
-
}
|
|
177
|
-
catch { }
|
|
178
|
-
}
|
|
179
|
-
if (!sdkPkgPath)
|
|
180
|
-
throw new Error('not found');
|
|
189
|
+
const sdkEntry = esmRequire.resolve('@anthropic-ai/claude-agent-sdk');
|
|
190
|
+
const sdkPkgPath = path.join(path.dirname(sdkEntry), 'package.json');
|
|
181
191
|
const sdkPkg = JSON.parse(fs.readFileSync(sdkPkgPath, 'utf-8'));
|
|
182
192
|
const sdkVer = sdkPkg.version;
|
|
183
193
|
const parts = sdkVer.split('.').map(Number);
|
|
@@ -246,11 +256,50 @@ function setupEnvVar(home) {
|
|
|
246
256
|
}
|
|
247
257
|
console.log(' ⚠ 请重新打开终端或执行 source 使其生效');
|
|
248
258
|
}
|
|
259
|
+
// ==================== Feishu Manual Input ====================
|
|
260
|
+
async function initFeishuManual(rl, config) {
|
|
261
|
+
let appId = '';
|
|
262
|
+
while (!appId) {
|
|
263
|
+
appId = (await ask(rl, ' 飞书 App ID: ')).trim();
|
|
264
|
+
if (!appId)
|
|
265
|
+
console.log(' ⚠ 不能为空');
|
|
266
|
+
}
|
|
267
|
+
let appSecret = '';
|
|
268
|
+
while (!appSecret) {
|
|
269
|
+
appSecret = (await ask(rl, ' 飞书 App Secret: ')).trim();
|
|
270
|
+
if (!appSecret)
|
|
271
|
+
console.log(' ⚠ 不能为空');
|
|
272
|
+
}
|
|
273
|
+
console.log(' 正在验证飞书凭证...');
|
|
274
|
+
try {
|
|
275
|
+
const lark = await import('@larksuiteoapi/node-sdk');
|
|
276
|
+
const client = new lark.Client({ appId, appSecret });
|
|
277
|
+
const res = await client.auth.tenantAccessToken.internal({
|
|
278
|
+
data: { app_id: appId, app_secret: appSecret },
|
|
279
|
+
});
|
|
280
|
+
if (res.code === 0) {
|
|
281
|
+
console.log(' ✓ 飞书凭证验证通过');
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
console.log(` ✗ 飞书凭证验证失败: ${res.msg}`);
|
|
285
|
+
const answer = (await ask(rl, ' → 是否继续?[y/N] ')).trim().toLowerCase();
|
|
286
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch (e) {
|
|
292
|
+
console.log(` ⚠ 飞书凭证验证跳过: ${e.message?.slice(0, 100) || e}`);
|
|
293
|
+
}
|
|
294
|
+
config.channels.feishu.appId = appId;
|
|
295
|
+
config.channels.feishu.appSecret = appSecret;
|
|
296
|
+
config.channels.feishu.enabled = true;
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
249
299
|
// ==================== Main ====================
|
|
250
300
|
export async function cmdInit() {
|
|
251
301
|
const p = resolvePaths();
|
|
252
302
|
ensureDataDirs();
|
|
253
|
-
// 检查服务是否在运行
|
|
254
303
|
if (fs.existsSync(p.pid)) {
|
|
255
304
|
const pid = parseInt(fs.readFileSync(p.pid, 'utf-8').trim(), 10);
|
|
256
305
|
try {
|
|
@@ -278,49 +327,11 @@ export async function cmdInit() {
|
|
|
278
327
|
return;
|
|
279
328
|
}
|
|
280
329
|
console.log('📝 交互式配置\n');
|
|
281
|
-
//
|
|
282
|
-
let appId = '';
|
|
283
|
-
while (!appId) {
|
|
284
|
-
appId = (await ask(rl, ' 飞书 App ID: ')).trim();
|
|
285
|
-
if (!appId)
|
|
286
|
-
console.log(' ⚠ 不能为空');
|
|
287
|
-
}
|
|
288
|
-
// feishu.appSecret
|
|
289
|
-
let appSecret = '';
|
|
290
|
-
while (!appSecret) {
|
|
291
|
-
appSecret = (await ask(rl, ' 飞书 App Secret: ')).trim();
|
|
292
|
-
if (!appSecret)
|
|
293
|
-
console.log(' ⚠ 不能为空');
|
|
294
|
-
}
|
|
295
|
-
// 验证飞书凭证
|
|
296
|
-
console.log(' 正在验证飞书凭证...');
|
|
297
|
-
try {
|
|
298
|
-
const lark = await import('@larksuiteoapi/node-sdk');
|
|
299
|
-
const client = new lark.Client({ appId, appSecret });
|
|
300
|
-
const res = await client.auth.tenantAccessToken.internal({
|
|
301
|
-
data: { app_id: appId, app_secret: appSecret },
|
|
302
|
-
});
|
|
303
|
-
if (res.code === 0) {
|
|
304
|
-
console.log(' ✓ 飞书凭证验证通过');
|
|
305
|
-
}
|
|
306
|
-
else {
|
|
307
|
-
console.log(` ✗ 飞书凭证验证失败: ${res.msg}`);
|
|
308
|
-
const answer = (await ask(rl, ' → 是否继续?[y/N] ')).trim().toLowerCase();
|
|
309
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
310
|
-
console.log(' 已取消');
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
catch (e) {
|
|
316
|
-
console.log(` ⚠ 飞书凭证验证跳过: ${e.message?.slice(0, 100) || e}`);
|
|
317
|
-
}
|
|
318
|
-
// projects.defaultPath
|
|
330
|
+
// 通用配置
|
|
319
331
|
const defaultSuggestion = path.join(os.homedir(), 'evolclaw-project');
|
|
320
332
|
let defaultPath = (await ask(rl, ` 默认项目路径 [${defaultSuggestion}]: `)).trim();
|
|
321
|
-
if (!defaultPath)
|
|
333
|
+
if (!defaultPath)
|
|
322
334
|
defaultPath = defaultSuggestion;
|
|
323
|
-
}
|
|
324
335
|
if (defaultPath.startsWith('~/')) {
|
|
325
336
|
defaultPath = path.join(os.homedir(), defaultPath.slice(2));
|
|
326
337
|
}
|
|
@@ -331,19 +342,61 @@ export async function cmdInit() {
|
|
|
331
342
|
fs.mkdirSync(defaultPath, { recursive: true });
|
|
332
343
|
console.log(` ✓ 已创建目录: ${defaultPath}`);
|
|
333
344
|
}
|
|
334
|
-
// anthropic.model
|
|
335
345
|
const modelInput = (await ask(rl, ' 模型 [sonnet(默认)/opus/haiku]: ')).trim().toLowerCase();
|
|
336
346
|
const model = ['opus', 'haiku'].includes(modelInput) ? modelInput : 'sonnet';
|
|
337
|
-
//
|
|
347
|
+
// 渠道选择
|
|
348
|
+
console.log('\n选择消息渠道:');
|
|
349
|
+
console.log(' 1. 飞书 (Feishu)');
|
|
350
|
+
console.log(' 2. 微信 (WeChat)');
|
|
351
|
+
const channelChoice = (await ask(rl, '请选择 [1]: ')).trim() || '1';
|
|
338
352
|
const config = JSON.parse(fs.readFileSync(sampleSrc, 'utf-8'));
|
|
339
|
-
config.feishu.appId = appId;
|
|
340
|
-
config.feishu.appSecret = appSecret;
|
|
341
353
|
config.projects.defaultPath = defaultPath;
|
|
342
354
|
config.projects.list = { [path.basename(defaultPath)]: defaultPath };
|
|
343
|
-
config.anthropic.model = model;
|
|
355
|
+
config.agents.anthropic.model = model;
|
|
356
|
+
if (channelChoice === '1') {
|
|
357
|
+
console.log('\n飞书配置方式:');
|
|
358
|
+
console.log(' 1. 扫码自动注册(推荐)');
|
|
359
|
+
console.log(' 2. 手动输入 App ID/Secret');
|
|
360
|
+
const feishuMethod = (await ask(rl, '请选择 [1]: ')).trim() || '1';
|
|
361
|
+
if (feishuMethod === '1') {
|
|
362
|
+
const { runFeishuQrFlow } = await import('./init-feishu.js');
|
|
363
|
+
const result = await runFeishuQrFlow();
|
|
364
|
+
if (!result) {
|
|
365
|
+
console.log('已取消');
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
config.channels.feishu.appId = result.appId;
|
|
369
|
+
config.channels.feishu.appSecret = result.appSecret;
|
|
370
|
+
config.channels.feishu.enabled = true;
|
|
371
|
+
if (result.openId)
|
|
372
|
+
config.channels.feishu.owner = result.openId;
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
if (!await initFeishuManual(rl, config)) {
|
|
376
|
+
console.log('已取消');
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
else if (channelChoice === '2') {
|
|
382
|
+
const { runWechatQrFlow } = await import('./init-wechat.js');
|
|
383
|
+
const result = await runWechatQrFlow();
|
|
384
|
+
if (!result) {
|
|
385
|
+
console.log('已取消');
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
config.channels.wechat = {
|
|
389
|
+
enabled: true,
|
|
390
|
+
baseUrl: result.baseUrl,
|
|
391
|
+
token: result.token,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
console.log('无效选择');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
344
398
|
fs.writeFileSync(p.config, JSON.stringify(config, null, 2) + '\n');
|
|
345
399
|
console.log(`\n✓ 已创建配置文件: ${p.config}`);
|
|
346
|
-
// Setup EVOLCLAW_HOME in shell profile
|
|
347
400
|
setupEnvVar(resolveRoot());
|
|
348
401
|
}
|
|
349
402
|
finally {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { logger } from './logger.js';
|
|
1
2
|
/**
|
|
2
3
|
* 流式输出缓冲器
|
|
3
4
|
* 按时间窗口批量推送文本和活动事件
|
|
@@ -137,10 +138,10 @@ export class StreamFlusher {
|
|
|
137
138
|
const before = output;
|
|
138
139
|
output = output.replace(this.fileMarkerPattern, '').trim();
|
|
139
140
|
if (before !== output) {
|
|
140
|
-
|
|
141
|
+
logger.debug('[StreamFlusher] Removed file markers, before length:', before.length, 'after:', output.length);
|
|
141
142
|
}
|
|
142
143
|
}
|
|
143
|
-
|
|
144
|
+
logger.debug('[StreamFlusher] flush called, output length:', output.length, 'isEmpty:', !output, 'preview:', output.substring(0, 100));
|
|
144
145
|
if (output) {
|
|
145
146
|
await this.send(output, isFinal);
|
|
146
147
|
this.sentContent = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "evolclaw",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -23,16 +23,18 @@
|
|
|
23
23
|
"prepublishOnly": "npm run build && npm test"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
26
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.81",
|
|
27
27
|
"@larksuiteoapi/node-sdk": "^1.59.0",
|
|
28
|
-
"dotenv": "^
|
|
29
|
-
"image-type": "^6.0.0"
|
|
28
|
+
"dotenv": "^17.3.1",
|
|
29
|
+
"image-type": "^6.0.0",
|
|
30
|
+
"qrcode-terminal": "^0.12.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@types/node": "^
|
|
33
|
-
"@
|
|
33
|
+
"@types/node": "^25.5.0",
|
|
34
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
35
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
34
36
|
"tsx": "^4.19.0",
|
|
35
37
|
"typescript": "^5.6.0",
|
|
36
|
-
"vitest": "^
|
|
38
|
+
"vitest": "^4.1.0"
|
|
37
39
|
}
|
|
38
40
|
}
|