evolclaw 3.1.5 → 3.1.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.
- package/CHANGELOG.md +68 -3
- package/dist/agents/claude-runner.js +69 -24
- package/dist/agents/kit-renderer.js +78 -321
- package/dist/agents/manifest-engine.js +243 -0
- package/dist/agents/message-renderer.js +112 -0
- package/dist/aun/aid/agentmd.js +10 -3
- package/dist/aun/msg/group.js +2 -2
- package/dist/channels/aun.js +154 -18
- package/dist/channels/dingtalk.js +1 -1
- package/dist/channels/feishu.js +31 -9
- package/dist/channels/qqbot.js +1 -1
- package/dist/channels/wechat.js +1 -1
- package/dist/channels/wecom.js +1 -1
- package/dist/cli/agent.js +10 -11
- package/dist/cli/bench.js +1 -5
- package/dist/cli/help.js +8 -0
- package/dist/cli/index.js +91 -128
- package/dist/cli/init.js +37 -21
- package/dist/cli/link-rules.js +1 -7
- package/dist/cli/model.js +231 -6
- package/dist/config-store.js +1 -22
- package/dist/core/command-handler.js +181 -48
- package/dist/core/evolagent.js +0 -18
- package/dist/core/message/im-renderer.js +9 -20
- package/dist/core/message/message-bridge.js +9 -10
- package/dist/core/message/message-processor.js +188 -39
- package/dist/core/message/message-queue.js +15 -1
- package/dist/core/relation/peer-identity.js +23 -11
- package/dist/core/trigger/parser.js +4 -4
- package/dist/core/trigger/scheduler.js +43 -13
- package/dist/index.js +102 -52
- package/dist/ipc.js +1 -1
- package/dist/utils/error-utils.js +6 -0
- package/dist/utils/process-introspect.js +7 -5
- package/kits/docs/INDEX.md +4 -8
- package/kits/docs/context-assembly.md +1 -0
- package/kits/docs/evolclaw/INDEX.md +43 -0
- package/kits/docs/evolclaw/group.md +13 -6
- package/kits/docs/evolclaw/model.md +51 -0
- package/kits/docs/evolclaw/msg.md +5 -0
- package/kits/docs/venues/group.md +13 -1
- package/kits/eck_manifest.json +9 -0
- package/kits/eck_message_manifest.json +14 -0
- package/kits/rules/06-channel.md +5 -1
- package/kits/templates/message-fragments/item.md +2 -0
- package/kits/templates/system-fragments/baseagent.md +7 -1
- package/kits/templates/system-fragments/channel.md +7 -5
- package/kits/templates/system-fragments/commands.md +19 -0
- package/kits/templates/system-fragments/session.md +12 -0
- package/kits/templates/system-fragments/venue.md +15 -0
- package/package.json +3 -3
package/dist/cli/model.js
CHANGED
|
@@ -10,17 +10,15 @@
|
|
|
10
10
|
* 与对话内 slash(/model /setmodel /effort /baseagent)互不影响。
|
|
11
11
|
* 设计见 docs/model-command-design.md。
|
|
12
12
|
*/
|
|
13
|
-
import { isHelpFlag, wantsHelp } from './help.js';
|
|
13
|
+
import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
|
|
14
14
|
import { ModelScopeError, normalizePeer, determineScope, activeBaseagent, readScope, writeScope, clearScope, resolveEffectiveModel, } from '../core/model/model-scope.js';
|
|
15
|
+
import { loadDefaults, loadAgent } from '../config-store.js';
|
|
16
|
+
import { resolveAnthropicConfig } from '../agents/resolve.js';
|
|
15
17
|
import { getCatalog, getModelInfo } from '../core/model/model-catalog.js';
|
|
16
18
|
const ALL_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max', 'auto'];
|
|
17
19
|
const SCOPE_LABEL = {
|
|
18
20
|
global: '全局', agent: 'agent级', relation: '关系级',
|
|
19
21
|
};
|
|
20
|
-
function getArgValue(args, flag) {
|
|
21
|
-
const idx = args.indexOf(flag);
|
|
22
|
-
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : undefined;
|
|
23
|
-
}
|
|
24
22
|
/** 输出 JSON 并退出(success=false 时 exit 1)。 */
|
|
25
23
|
function emit(formatJson, payload, textFn) {
|
|
26
24
|
if (formatJson) {
|
|
@@ -77,6 +75,7 @@ Commands:
|
|
|
77
75
|
use <model-id> 设置模型(作用域由 --self/--peer 决定)
|
|
78
76
|
reset 清除指定作用域的设置,回落上一级
|
|
79
77
|
effort <level> 设置推理强度(low|medium|high|xhigh|max|auto)
|
|
78
|
+
check 诊断网关连通性与模型可用性(分阶段输出进度)
|
|
80
79
|
|
|
81
80
|
作用域(由参数决定,越具体越优先:关系 > agent > 全局):
|
|
82
81
|
(无参数) 全局默认 → defaults.json
|
|
@@ -99,7 +98,8 @@ Options:
|
|
|
99
98
|
evolclaw model use opus
|
|
100
99
|
evolclaw model use deepseek-v4-pro --self bot.agentid.pub --peer alice.agentid.pub
|
|
101
100
|
evolclaw model effort high --self bot.agentid.pub
|
|
102
|
-
evolclaw model reset --self bot.agentid.pub --peer alice.agentid.pub
|
|
101
|
+
evolclaw model reset --self bot.agentid.pub --peer alice.agentid.pub
|
|
102
|
+
evolclaw model check --self bot.agentid.pub`;
|
|
103
103
|
async function dispatch(sub, args, formatJson) {
|
|
104
104
|
switch (sub) {
|
|
105
105
|
case 'list': return await cmdList(args, formatJson);
|
|
@@ -108,6 +108,7 @@ async function dispatch(sub, args, formatJson) {
|
|
|
108
108
|
case 'use': return await cmdUse(args, formatJson);
|
|
109
109
|
case 'reset': return await cmdReset(args, formatJson);
|
|
110
110
|
case 'effort': return await cmdEffort(args, formatJson);
|
|
111
|
+
case 'check': return await cmdCheck(args, formatJson);
|
|
111
112
|
default:
|
|
112
113
|
fail(formatJson, 'UNKNOWN_SUBCOMMAND', `未知子命令: ${sub}(model --help 查看用法)`);
|
|
113
114
|
}
|
|
@@ -313,6 +314,230 @@ async function cmdReset(args, formatJson) {
|
|
|
313
314
|
return `✓ 已清除 ${SCOPE_LABEL[scope]} 设置,回落上一级(该范围所有会话下条消息生效)`;
|
|
314
315
|
});
|
|
315
316
|
}
|
|
317
|
+
/** 带超时的 fetch,返回 Response 或 null(超时/失败)。 */
|
|
318
|
+
async function fetchWithTimeout(url, opts, timeoutMs) {
|
|
319
|
+
const controller = new AbortController();
|
|
320
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
321
|
+
try {
|
|
322
|
+
const resp = await fetch(url, { ...opts, signal: controller.signal });
|
|
323
|
+
clearTimeout(timer);
|
|
324
|
+
return resp;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
clearTimeout(timer);
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/** 解析网关 baseUrl 和 apiKey(复用 resolveCreds 逻辑,但从 CLI 层调用)。 */
|
|
332
|
+
function resolveGatewayCreds(self) {
|
|
333
|
+
try {
|
|
334
|
+
const defaults = loadDefaults();
|
|
335
|
+
const agentCfg = self ? loadAgent(self) : null;
|
|
336
|
+
const block = agentCfg?.baseagents || defaults?.baseagents || {};
|
|
337
|
+
const claudeCfg = block.claude || {};
|
|
338
|
+
const r = resolveAnthropicConfig({ agents: { claude: claudeCfg } }, claudeCfg);
|
|
339
|
+
return { baseUrl: r.baseUrl, apiKey: r.apiKey };
|
|
340
|
+
}
|
|
341
|
+
catch {
|
|
342
|
+
return {};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/** 对单个模型发一次最小探测请求,返回延迟 ms 或错误信息。
|
|
346
|
+
* 按网关风格顺序探测:Anthropic Messages API → OpenAI chat completions → 仅列表校验
|
|
347
|
+
*/
|
|
348
|
+
async function probeModel(baseUrl, apiKey, modelId, timeoutMs) {
|
|
349
|
+
const base = baseUrl.replace(/\/+$/, '');
|
|
350
|
+
const authHeaders = apiKey
|
|
351
|
+
? { 'x-api-key': apiKey, Authorization: `Bearer ${apiKey}` }
|
|
352
|
+
: {};
|
|
353
|
+
// 风格 1:Anthropic Messages API
|
|
354
|
+
{
|
|
355
|
+
const t0 = Date.now();
|
|
356
|
+
const resp = await fetchWithTimeout(`${base}/v1/messages`, {
|
|
357
|
+
method: 'POST',
|
|
358
|
+
headers: { 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', ...authHeaders },
|
|
359
|
+
body: JSON.stringify({ model: modelId, max_tokens: 5, messages: [{ role: 'user', content: 'ok' }] }),
|
|
360
|
+
}, timeoutMs);
|
|
361
|
+
const ms = Date.now() - t0;
|
|
362
|
+
if (resp && resp.ok)
|
|
363
|
+
return { ok: true, ms, style: 'anthropic' };
|
|
364
|
+
if (resp && resp.status >= 400 && resp.status < 500) {
|
|
365
|
+
// 4xx 说明网关支持此风格,但模型不可用
|
|
366
|
+
let errMsg = `HTTP ${resp.status}`;
|
|
367
|
+
try {
|
|
368
|
+
const j = await resp.json();
|
|
369
|
+
errMsg = j?.error?.message || j?.detail || j?.message || errMsg;
|
|
370
|
+
}
|
|
371
|
+
catch { /* ignore */ }
|
|
372
|
+
return { ok: false, ms, error: String(errMsg).slice(0, 80), style: 'anthropic' };
|
|
373
|
+
}
|
|
374
|
+
// 非 4xx(超时/连接失败/5xx)→ 继续尝试下一种风格
|
|
375
|
+
}
|
|
376
|
+
// 风格 2:OpenAI chat completions
|
|
377
|
+
{
|
|
378
|
+
const t0 = Date.now();
|
|
379
|
+
const resp = await fetchWithTimeout(`${base}/v1/chat/completions`, {
|
|
380
|
+
method: 'POST',
|
|
381
|
+
headers: { 'Content-Type': 'application/json', ...authHeaders },
|
|
382
|
+
body: JSON.stringify({ model: modelId, max_tokens: 5, messages: [{ role: 'user', content: 'ok' }] }),
|
|
383
|
+
}, timeoutMs);
|
|
384
|
+
const ms = Date.now() - t0;
|
|
385
|
+
if (resp && resp.ok) {
|
|
386
|
+
// 校验响应体有 choices 字段,否则可能是网关假装成功
|
|
387
|
+
try {
|
|
388
|
+
const j = await resp.json();
|
|
389
|
+
if (j?.choices || j?.id)
|
|
390
|
+
return { ok: true, ms, style: 'openai' };
|
|
391
|
+
}
|
|
392
|
+
catch { /* ignore */ }
|
|
393
|
+
}
|
|
394
|
+
if (resp && resp.status >= 400 && resp.status < 500) {
|
|
395
|
+
let errMsg = `HTTP ${resp.status}`;
|
|
396
|
+
try {
|
|
397
|
+
const j = await resp.json();
|
|
398
|
+
errMsg = j?.error?.message || j?.detail || j?.message || errMsg;
|
|
399
|
+
}
|
|
400
|
+
catch { /* ignore */ }
|
|
401
|
+
return { ok: false, ms, error: String(errMsg).slice(0, 80), style: 'openai' };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
// 两种风格都探测不到:无法判断可用性
|
|
405
|
+
return { ok: false, ms: 0, error: '网关不支持标准探测(无法确认可用性)', style: 'unknown' };
|
|
406
|
+
}
|
|
407
|
+
async function cmdCheck(args, formatJson) {
|
|
408
|
+
if (wantsHelp(args)) {
|
|
409
|
+
console.log(HELP);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
const sel = parseSelector(args, formatJson);
|
|
413
|
+
const ba = activeBaseagent(sel.self);
|
|
414
|
+
const steps = [];
|
|
415
|
+
const log = (step) => {
|
|
416
|
+
steps.push(step);
|
|
417
|
+
if (!formatJson) {
|
|
418
|
+
const icon = step.ok ? '✓' : '✗';
|
|
419
|
+
const ms = step.ms !== undefined ? ` (${step.ms}ms)` : '';
|
|
420
|
+
console.log(`[${icon}] ${step.label.padEnd(14)} ${step.detail}${ms}`);
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
const { baseUrl, apiKey } = resolveGatewayCreds(sel.self);
|
|
424
|
+
// ── 阶段 1:DNS + 连通性 ──────────────────────────────────────────
|
|
425
|
+
if (!formatJson)
|
|
426
|
+
console.log('\n网关诊断\n' + '─'.repeat(50));
|
|
427
|
+
if (!baseUrl) {
|
|
428
|
+
log({ label: '网关地址', ok: false, detail: '未配置自定义网关(baseUrl),使用官方 Anthropic 端点' });
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
// DNS + TCP:用一次 HEAD 请求探测
|
|
432
|
+
const t0 = Date.now();
|
|
433
|
+
const pingResp = await fetchWithTimeout(baseUrl.replace(/\/+$/, '') + '/v1/models', {
|
|
434
|
+
method: 'GET',
|
|
435
|
+
headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
|
|
436
|
+
}, 4000);
|
|
437
|
+
const pingMs = Date.now() - t0;
|
|
438
|
+
if (!pingResp) {
|
|
439
|
+
log({ label: '网关连通', ok: false, detail: `${baseUrl} 无法连接(DNS/TCP 失败)`, ms: pingMs });
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
log({ label: '网关连通', ok: true, detail: baseUrl, ms: pingMs });
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
// ── 阶段 2:认证 + 模型列表 ─────────────────────────────────────
|
|
446
|
+
if (!formatJson)
|
|
447
|
+
console.log('');
|
|
448
|
+
const cat = await getCatalog(sel.self, ba);
|
|
449
|
+
const modelIds = cat.models.filter(m => m.owned_by !== 'alias').map(m => m.id);
|
|
450
|
+
if (cat.source === 'mock') {
|
|
451
|
+
log({ label: '模型列表', ok: false, detail: `无法获取远端列表,使用内置 mock(${modelIds.length} 个)` });
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
log({ label: '模型列表', ok: true, detail: `来源: ${cat.source},共 ${modelIds.length} 个模型` });
|
|
455
|
+
}
|
|
456
|
+
// ── 阶段 3:当前配置模型 ──────────────────────────────────────────
|
|
457
|
+
if (!formatJson)
|
|
458
|
+
console.log('');
|
|
459
|
+
const resolved = resolveEffectiveModel(sel, ba);
|
|
460
|
+
const configuredModel = resolved.model;
|
|
461
|
+
if (!configuredModel) {
|
|
462
|
+
log({ label: '配置模型', ok: true, detail: '未配置(将使用 base agent 默认模型)' });
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
log({ label: '配置模型', ok: true, detail: `${configuredModel}(来源: ${resolved.source ?? '未知'})` });
|
|
466
|
+
}
|
|
467
|
+
// ── 阶段 4:模型可用性探测 ────────────────────────────────────────
|
|
468
|
+
// 仅在有自定义网关时才做 API 探测(官方端点跳过,避免消耗 token)
|
|
469
|
+
const probeResults = [];
|
|
470
|
+
if (baseUrl) {
|
|
471
|
+
if (!formatJson)
|
|
472
|
+
console.log('\n模型可用性探测\n' + '─'.repeat(50));
|
|
473
|
+
// 别名解析:从目录里找别名对应的完整 ID
|
|
474
|
+
const aliasMap = new Map(); // alias → full id
|
|
475
|
+
for (const entry of cat.models) {
|
|
476
|
+
if (entry.owned_by === 'alias') {
|
|
477
|
+
// 找目录中与别名前缀匹配的完整 ID
|
|
478
|
+
const match = cat.models.find(m => m.owned_by !== 'alias' && m.id.includes(entry.id));
|
|
479
|
+
if (match)
|
|
480
|
+
aliasMap.set(entry.id, match.id);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
const resolveId = (id) => aliasMap.get(id) ?? id;
|
|
484
|
+
// 优先探测当前配置的模型(解析别名),其余为非别名 ID
|
|
485
|
+
const configResolved = configuredModel ? resolveId(configuredModel) : undefined;
|
|
486
|
+
const allProbeIds = [
|
|
487
|
+
...(configResolved ? [{ display: configuredModel, probe: configResolved }] : []),
|
|
488
|
+
...modelIds
|
|
489
|
+
.filter(id => id !== configResolved)
|
|
490
|
+
.map(id => ({ display: id, probe: id })),
|
|
491
|
+
];
|
|
492
|
+
// 轮次 1:配置模型 + 1 个额外(并发 2,快速验证主要路径)
|
|
493
|
+
const round1 = allProbeIds.slice(0, 2);
|
|
494
|
+
const r1 = await Promise.all(round1.map(({ display, probe }) => probeModel(baseUrl, apiKey, probe, 5000).then(r => ({ id: display, displayId: display, ...r }))));
|
|
495
|
+
probeResults.push(...r1);
|
|
496
|
+
if (!formatJson)
|
|
497
|
+
r1.forEach(r => log({ label: r.id.slice(0, 20), ok: r.ok, detail: r.ok ? '可用' : (r.error ?? '不可用'), ms: r.ms }));
|
|
498
|
+
// 轮次 2:剩余,2 个一组(避免触发限速)
|
|
499
|
+
const rest = allProbeIds.slice(2);
|
|
500
|
+
for (let i = 0; i < rest.length; i += 2) {
|
|
501
|
+
const batch = rest.slice(i, i + 2);
|
|
502
|
+
const br = await Promise.all(batch.map(({ display, probe }) => probeModel(baseUrl, apiKey, probe, 5000).then(r => ({ id: display, displayId: display, ...r }))));
|
|
503
|
+
probeResults.push(...br);
|
|
504
|
+
if (!formatJson)
|
|
505
|
+
br.forEach(r => log({ label: r.id.slice(0, 20), ok: r.ok, detail: r.ok ? '可用' : (r.error ?? '不可用'), ms: r.ms }));
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
if (!formatJson)
|
|
510
|
+
console.log('\n(跳过模型探测:未配置自定义网关)');
|
|
511
|
+
}
|
|
512
|
+
// ── 汇总 ─────────────────────────────────────────────────────────
|
|
513
|
+
const availableModels = probeResults.filter(r => r.ok).map(r => r.id);
|
|
514
|
+
const configModelOk = configuredModel ? probeResults.find(r => r.id === configuredModel)?.ok : undefined;
|
|
515
|
+
if (!formatJson) {
|
|
516
|
+
console.log('\n' + '─'.repeat(50));
|
|
517
|
+
if (configuredModel && configModelOk === false) {
|
|
518
|
+
console.log(`⚠️ 当前配置的模型 ${configuredModel} 不可用`);
|
|
519
|
+
if (availableModels.length > 0) {
|
|
520
|
+
console.log(` 可用模型:${availableModels.slice(0, 3).join('、')}${availableModels.length > 3 ? ` 等 ${availableModels.length} 个` : ''}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
else if (configuredModel && configModelOk === true) {
|
|
524
|
+
console.log(`✓ 当前配置的模型 ${configuredModel} 可用`);
|
|
525
|
+
}
|
|
526
|
+
else if (!baseUrl) {
|
|
527
|
+
console.log(`✓ 使用官方 Anthropic 端点,无需探测`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
else {
|
|
531
|
+
emit(formatJson, {
|
|
532
|
+
ok: true,
|
|
533
|
+
steps,
|
|
534
|
+
configuredModel: configuredModel ?? null,
|
|
535
|
+
configModelAvailable: configModelOk ?? null,
|
|
536
|
+
availableModels,
|
|
537
|
+
catalogSource: cat.source,
|
|
538
|
+
}, () => '');
|
|
539
|
+
}
|
|
540
|
+
}
|
|
316
541
|
export async function cmdModel(args) {
|
|
317
542
|
const sub = args[0];
|
|
318
543
|
const formatJson = getArgValue(args, '--format') === 'json';
|
package/dist/config-store.js
CHANGED
|
@@ -163,7 +163,7 @@ export function autoMigrateIfNeeded() {
|
|
|
163
163
|
active_baseagent: oldConfig.agents?.defaultAgent || 'claude',
|
|
164
164
|
baseagents: {},
|
|
165
165
|
models: oldConfig.models,
|
|
166
|
-
projects: oldConfig.projects ? { defaultPath: oldConfig.projects.defaultPath
|
|
166
|
+
projects: oldConfig.projects ? { defaultPath: oldConfig.projects.defaultPath } : undefined,
|
|
167
167
|
chatmode: oldConfig.chatmode,
|
|
168
168
|
show_activities: oldConfig.showActivities,
|
|
169
169
|
flush_delay: oldConfig.flushDelay,
|
|
@@ -594,7 +594,6 @@ export async function migrateProject(oldPath, newPath) {
|
|
|
594
594
|
claudeHistoryUpdated: false,
|
|
595
595
|
codexUpdated: 0,
|
|
596
596
|
evolclawDbUpdated: 0,
|
|
597
|
-
evolclawConfigUpdated: false,
|
|
598
597
|
directoryMoved: false,
|
|
599
598
|
};
|
|
600
599
|
const oldAbs = path.resolve(oldPath);
|
|
@@ -687,25 +686,5 @@ export async function migrateProject(oldPath, newPath) {
|
|
|
687
686
|
}
|
|
688
687
|
catch { /* fs not accessible */ }
|
|
689
688
|
}
|
|
690
|
-
// 7. 更新各 self-agent config.json 的 projects.list
|
|
691
|
-
try {
|
|
692
|
-
const { agents } = loadAllAgents();
|
|
693
|
-
for (const cfg of agents) {
|
|
694
|
-
if (!cfg.projects?.list)
|
|
695
|
-
continue;
|
|
696
|
-
let changed = false;
|
|
697
|
-
for (const [k, v] of Object.entries(cfg.projects.list)) {
|
|
698
|
-
if (v === oldAbs) {
|
|
699
|
-
cfg.projects.list[k] = newAbs;
|
|
700
|
-
changed = true;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
if (changed) {
|
|
704
|
-
saveAgent(cfg);
|
|
705
|
-
result.evolclawConfigUpdated = true;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
catch { /* agents not accessible */ }
|
|
710
689
|
return result;
|
|
711
690
|
}
|