evolclaw 3.0.0 → 3.1.1
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 +1 -1
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +47 -12
- package/dist/agents/codex-runner.js +2 -0
- package/dist/agents/gemini-runner.js +9 -9
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/aun/aid/identity.js +28 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/msg/group.js +3 -1
- package/dist/aun/msg/p2p.js +42 -1
- package/dist/channels/aun.js +427 -146
- package/dist/channels/dingtalk.js +3 -1
- package/dist/channels/feishu.js +128 -7
- package/dist/channels/qqbot.js +3 -1
- package/dist/channels/wechat.js +4 -1
- package/dist/channels/wecom.js +3 -1
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +418 -40
- package/dist/cli/init.js +3 -4
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +666 -0
- package/dist/config-store.js +82 -5
- package/dist/core/channel-loader.js +23 -10
- package/dist/core/command-handler.js +127 -99
- package/dist/core/evolagent.js +5 -10
- package/dist/core/message/im-renderer.js +93 -48
- package/dist/core/message/items-formatter.js +11 -4
- package/dist/core/message/message-bridge.js +11 -2
- package/dist/core/message/message-log.js +8 -1
- package/dist/core/message/message-processor.js +194 -127
- package/dist/core/message/message-queue.js +10 -3
- package/dist/core/permission.js +95 -3
- package/dist/core/relation/peer-identity.js +161 -0
- package/dist/core/session/session-manager.js +103 -65
- package/dist/core/trigger/manager.js +16 -0
- package/dist/core/trigger/parser.js +110 -0
- package/dist/core/trigger/scheduler.js +7 -1
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +186 -19
- package/dist/net-check.js +640 -0
- package/dist/paths.js +31 -40
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +10 -0
- package/dist/utils/cross-platform.js +17 -8
- package/dist/utils/error-utils.js +27 -15
- package/dist/utils/instance-registry.js +6 -5
- package/dist/utils/log-writer.js +2 -1
- package/dist/utils/logger.js +10 -0
- package/dist/utils/npm-ops.js +35 -3
- package/dist/utils/process-introspect.js +16 -38
- package/dist/utils/stats.js +216 -2
- package/dist/watch-msg.js +26 -11
- package/evolclaw-install-aun.md +14 -2
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +72 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +73 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +7 -5
- package/dist/agents/templates.js +0 -122
- package/dist/data/prompts.md +0 -137
- package/kits/aun/meta.md +0 -25
- package/kits/aun/role.md +0 -25
- package/kits/templates/group.md +0 -20
- package/kits/templates/private.md +0 -9
- package/kits/templates/system-fragments/personal-context.md +0 -3
- package/kits/templates/system-fragments/self-intro.md +0 -5
- package/kits/templates/system-fragments/speaker-intro.md +0 -5
- package/kits/templates/system-fragments/venue-intro.md +0 -5
- /package/kits/{channels → docs/channels}/aun.md +0 -0
- /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
- /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
- /package/kits/{evolclaw → docs/identity}/identity-tools.md +0 -0
package/README.md
CHANGED
|
@@ -263,7 +263,7 @@ evolclaw/
|
|
|
263
263
|
- `/check` - 系统健康检查(详情)
|
|
264
264
|
- `/activity [all|dm|owner|none]` - 查看/控制中间输出显示模式
|
|
265
265
|
- `/chatmode [interactive|proactive]` - 查看/切换会话模式
|
|
266
|
-
- `/dispatch [mention|
|
|
266
|
+
- `/dispatch [mention|broadcast]` - 群聊分发模式(仅 @ 响应或广播)
|
|
267
267
|
- `/trigger <动作> ...` - 设置/查看 AI 自主触发器(延迟/定时/周期)
|
|
268
268
|
- `/restart <channel>` - 重连指定渠道
|
|
269
269
|
|
package/bin/ec.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const repoRoot = path.resolve(here, '..');
|
|
11
|
+
const srcEntry = path.join(repoRoot, 'src', 'cli', 'index.ts');
|
|
12
|
+
const distEntry = path.join(repoRoot, 'dist', 'cli', 'index.js');
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
|
|
15
|
+
if (fs.existsSync(srcEntry)) {
|
|
16
|
+
try {
|
|
17
|
+
const tsxImport = pathToFileURL(require.resolve('tsx')).href;
|
|
18
|
+
const result = spawnSync(process.execPath, ['--import', tsxImport, srcEntry, ...args], { stdio: 'inherit' });
|
|
19
|
+
process.exit(result.status ?? (result.error ? 1 : 0));
|
|
20
|
+
} catch {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(distEntry)) {
|
|
24
|
+
const result = spawnSync(process.execPath, [distEntry, ...args], { stdio: 'inherit' });
|
|
25
|
+
process.exit(result.status ?? (result.error ? 1 : 0));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.error('ec: missing CLI entrypoint');
|
|
29
|
+
process.exit(1);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const BASEAGENT_ALIASES = {
|
|
2
|
+
claude: { canonical: 'claude', displayName: 'Claude Code' },
|
|
3
|
+
cc: { canonical: 'claude', displayName: 'Claude Code' },
|
|
4
|
+
'claude-code': { canonical: 'claude', displayName: 'Claude Code' },
|
|
5
|
+
'claude code': { canonical: 'claude', displayName: 'Claude Code' },
|
|
6
|
+
claudecode: { canonical: 'claude', displayName: 'Claude Code' },
|
|
7
|
+
codex: { canonical: 'codex', displayName: 'Codex' },
|
|
8
|
+
'codex-cli': { canonical: 'codex', displayName: 'Codex' },
|
|
9
|
+
'codex cli': { canonical: 'codex', displayName: 'Codex' },
|
|
10
|
+
gemini: { canonical: 'gemini', displayName: 'Gemini CLI' },
|
|
11
|
+
'gemini-cli': { canonical: 'gemini', displayName: 'Gemini CLI' },
|
|
12
|
+
'gemini cli': { canonical: 'gemini', displayName: 'Gemini CLI' },
|
|
13
|
+
geminicli: { canonical: 'gemini', displayName: 'Gemini CLI' },
|
|
14
|
+
hermes: { canonical: 'hermes', displayName: 'Hermes' },
|
|
15
|
+
};
|
|
16
|
+
export function normalizeBaseagent(input) {
|
|
17
|
+
const key = String(input || '').trim().toLowerCase().replace(/_/g, '-');
|
|
18
|
+
return BASEAGENT_ALIASES[key] || { canonical: 'unknown', displayName: input ? String(input) : 'Unknown' };
|
|
19
|
+
}
|
|
@@ -398,6 +398,25 @@ export class AgentRunner {
|
|
|
398
398
|
// 尝试发送交互卡片
|
|
399
399
|
let cardSent = false;
|
|
400
400
|
if (permCtx.adapter?.send) {
|
|
401
|
+
// 发送计划内容:找 plans 目录中最新修改的 .md 文件
|
|
402
|
+
if (sendPrompt) {
|
|
403
|
+
try {
|
|
404
|
+
const plansDir = path.join(process.env.HOME || '/root', '.claude', 'plans');
|
|
405
|
+
const files = fs.readdirSync(plansDir)
|
|
406
|
+
.filter((f) => f.endsWith('.md'))
|
|
407
|
+
.map((f) => ({ name: f, mtime: fs.statSync(path.join(plansDir, f)).mtimeMs }))
|
|
408
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
409
|
+
if (files.length > 0) {
|
|
410
|
+
const planContent = fs.readFileSync(path.join(plansDir, files[0].name), 'utf-8');
|
|
411
|
+
if (planContent.trim()) {
|
|
412
|
+
await sendPrompt(`📋 **计划内容**\n\n${planContent}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
catch {
|
|
417
|
+
// 读取失败不影响后续审批流程
|
|
418
|
+
}
|
|
419
|
+
}
|
|
401
420
|
const requestId = `plan-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
402
421
|
const interaction = {
|
|
403
422
|
type: 'interaction',
|
|
@@ -494,10 +513,11 @@ export class AgentRunner {
|
|
|
494
513
|
* 所有 SDK 特有的事件类型引用封装在此方法内
|
|
495
514
|
*/
|
|
496
515
|
async *transformStream(sdkStream, sessionId) {
|
|
497
|
-
let hasTextDelta = false;
|
|
498
516
|
let lastSessionId;
|
|
499
517
|
// tool_use_id → tool_name 映射,用于从 SDKUserMessage 的 tool_result 块中还原工具名
|
|
500
518
|
const toolUseNames = new Map();
|
|
519
|
+
let turnCount = 0;
|
|
520
|
+
const seenMessageIds = new Set();
|
|
501
521
|
for await (const event of sdkStream) {
|
|
502
522
|
// 提取 session_id(任意 SDK 事件都可能携带)
|
|
503
523
|
if (event.session_id && event.session_id !== lastSessionId) {
|
|
@@ -505,11 +525,6 @@ export class AgentRunner {
|
|
|
505
525
|
this.updateSessionId(sessionId, event.session_id);
|
|
506
526
|
yield { type: 'session_id', sessionId: event.session_id };
|
|
507
527
|
}
|
|
508
|
-
// text_delta → text
|
|
509
|
-
if (event.type === 'text_delta' && event.text) {
|
|
510
|
-
hasTextDelta = true;
|
|
511
|
-
yield { type: 'text', text: event.text };
|
|
512
|
-
}
|
|
513
528
|
// system: compact_boundary → compact
|
|
514
529
|
if (event.type === 'system' && event.subtype === 'compact_boundary') {
|
|
515
530
|
yield { type: 'compact', preTokens: event.compact_metadata?.pre_tokens || 0 };
|
|
@@ -529,15 +544,31 @@ export class AgentRunner {
|
|
|
529
544
|
}
|
|
530
545
|
// assistant: 提取 tool_use 和文本(仅无 text_delta 时提取文本)
|
|
531
546
|
if (event.type === 'assistant' && event.message?.content) {
|
|
547
|
+
const msgId = event.message.id;
|
|
548
|
+
if (!msgId || !seenMessageIds.has(msgId)) {
|
|
549
|
+
if (msgId)
|
|
550
|
+
seenMessageIds.add(msgId);
|
|
551
|
+
turnCount++;
|
|
552
|
+
}
|
|
553
|
+
// 统计本轮 base agent 全部输出字符数(text + tool_use input)
|
|
554
|
+
let turnOutputChars = 0;
|
|
555
|
+
for (const content of event.message.content) {
|
|
556
|
+
if (content.type === 'tool_use') {
|
|
557
|
+
const inputStr = typeof content.input === 'string' ? content.input : JSON.stringify(content.input || '');
|
|
558
|
+
turnOutputChars += inputStr.length;
|
|
559
|
+
}
|
|
560
|
+
else if (content.type === 'text' && content.text) {
|
|
561
|
+
turnOutputChars += content.text.length;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
532
564
|
for (const content of event.message.content) {
|
|
533
565
|
if (content.type === 'tool_use') {
|
|
534
|
-
// 记录 id → name 映射,供后续 tool_result 使用
|
|
535
566
|
if (content.id)
|
|
536
567
|
toolUseNames.set(content.id, content.name);
|
|
537
|
-
yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id };
|
|
568
|
+
yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id, turn: turnCount, outputTokens: turnOutputChars };
|
|
538
569
|
}
|
|
539
|
-
else if (content.type === 'text' && content.text
|
|
540
|
-
yield { type: 'text', text: content.text };
|
|
570
|
+
else if (content.type === 'text' && content.text) {
|
|
571
|
+
yield { type: 'text', text: content.text, outputTokens: turnOutputChars, turn: turnCount };
|
|
541
572
|
}
|
|
542
573
|
}
|
|
543
574
|
}
|
|
@@ -589,7 +620,11 @@ export class AgentRunner {
|
|
|
589
620
|
costUsd: event.total_cost_usd,
|
|
590
621
|
terminalReason: event.terminal_reason,
|
|
591
622
|
sessionTitle: event.session_title,
|
|
623
|
+
numTurns: event.num_turns,
|
|
624
|
+
usage: event.usage,
|
|
592
625
|
};
|
|
626
|
+
// result 是 SDK 流的终结事件,不再等待后续(防止 interrupt 后流不关闭导致挂起)
|
|
627
|
+
return;
|
|
593
628
|
}
|
|
594
629
|
}
|
|
595
630
|
}
|
|
@@ -762,7 +797,7 @@ export class AgentRunner {
|
|
|
762
797
|
const sdkPermissionMode = this.toSdkPermissionMode();
|
|
763
798
|
logger.info(`[AgentRunner] runQuery model=${this.model} effort=${this.effort ?? 'auto'} permMode=${this.permissionMode} sdkMode=${sdkPermissionMode}`);
|
|
764
799
|
if (systemPromptAppend) {
|
|
765
|
-
logger.info(`[AgentRunner] systemPromptAppend
|
|
800
|
+
logger.info(`[AgentRunner] systemPromptAppend: ${systemPromptAppend.length} chars`);
|
|
766
801
|
}
|
|
767
802
|
else {
|
|
768
803
|
logger.info(`[AgentRunner] systemPromptAppend: none`);
|
|
@@ -1059,7 +1094,7 @@ export class AgentRunner {
|
|
|
1059
1094
|
export class ClaudeAgentPlugin {
|
|
1060
1095
|
name = 'claude';
|
|
1061
1096
|
isEnabled(agent) {
|
|
1062
|
-
return
|
|
1097
|
+
return agent.baseagent === 'claude';
|
|
1063
1098
|
}
|
|
1064
1099
|
createAgent(agent, callbacks) {
|
|
1065
1100
|
const override = agent.config.baseagents?.claude;
|
|
@@ -14,6 +14,7 @@ import fs from 'fs';
|
|
|
14
14
|
import path from 'path';
|
|
15
15
|
import os from 'os';
|
|
16
16
|
import { resolveGoogleConfig } from './resolve.js';
|
|
17
|
+
import { commandExists } from '../utils/cross-platform.js';
|
|
17
18
|
import { GeminiSessionFileAdapter } from '../core/session/adapters/gemini-session-file-adapter.js';
|
|
18
19
|
import { logger } from '../utils/logger.js';
|
|
19
20
|
// Strip ANSI escape codes from Gemini CLI text output.
|
|
@@ -406,17 +407,16 @@ export class GeminiRunner {
|
|
|
406
407
|
export class GeminiAgentPlugin {
|
|
407
408
|
name = 'gemini';
|
|
408
409
|
isEnabled(agent) {
|
|
409
|
-
if (
|
|
410
|
+
if (agent.baseagent !== 'gemini')
|
|
410
411
|
return false;
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const syntheticConfig = { agents: { gemini: override } };
|
|
414
|
-
const resolved = resolveGoogleConfig(syntheticConfig, override);
|
|
415
|
-
return !!resolved.cliPath;
|
|
416
|
-
}
|
|
417
|
-
catch {
|
|
412
|
+
const geminiCfg = agent.config.baseagents?.gemini;
|
|
413
|
+
if (!geminiCfg)
|
|
418
414
|
return false;
|
|
419
|
-
|
|
415
|
+
if (geminiCfg.cliPath)
|
|
416
|
+
return true;
|
|
417
|
+
if (geminiCfg.apiKey && !geminiCfg.apiKey.includes('your-') && !geminiCfg.apiKey.includes('placeholder'))
|
|
418
|
+
return true;
|
|
419
|
+
return commandExists('gemini');
|
|
420
420
|
}
|
|
421
421
|
createAgent(agent, callbacks) {
|
|
422
422
|
const override = agent.config.baseagents?.gemini;
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { kitsDir, eckDebugDir, resolveRoot } from '../paths.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
// ── Param descriptions (for debug output) ──
|
|
6
|
+
const PARAM_DESCRIPTIONS = {
|
|
7
|
+
EVOLCLAW_HOME: '用户数据根目录',
|
|
8
|
+
PACKAGE_ROOT: 'evolclaw 包根目录',
|
|
9
|
+
CURRENT_PROJECT: '当前项目完整路径',
|
|
10
|
+
selfAid: '当前 agent 的 AID',
|
|
11
|
+
selfName: '当前 agent 的显示名',
|
|
12
|
+
hasPersona: '是否有 persona 内容',
|
|
13
|
+
hasWorkingMemory: '是否有 working memory',
|
|
14
|
+
peerId: '对端在该渠道的原生 ID',
|
|
15
|
+
peerKey: '对端跨渠道唯一标识(channel#urlEncode(peerId))',
|
|
16
|
+
peerName: '对端显示名',
|
|
17
|
+
peerRole: '对端角色',
|
|
18
|
+
groupId: '群组 ID(群聊时)',
|
|
19
|
+
scene: '场景类型',
|
|
20
|
+
chatType: '聊天类型',
|
|
21
|
+
channel: '当前渠道',
|
|
22
|
+
venueUid: 'venue 唯一标识',
|
|
23
|
+
project: '当前项目目录名(由 CURRENT_PROJECT 派生)',
|
|
24
|
+
sessionName: '会话名称',
|
|
25
|
+
sessionMode: '会话模式',
|
|
26
|
+
readonly: '是否只读模式',
|
|
27
|
+
canSendFile: '当前渠道是否支持发文件',
|
|
28
|
+
capabilities: '渠道能力列表',
|
|
29
|
+
baseAgent: '当前 base agent 规范值(claude/codex/gemini/hermes)',
|
|
30
|
+
baseAgentName: '当前 base agent 显示名',
|
|
31
|
+
};
|
|
32
|
+
// ── Cache ──
|
|
33
|
+
let _manifestCache = null;
|
|
34
|
+
const _sessionPathCache = new Map();
|
|
35
|
+
// ── Public API ──
|
|
36
|
+
export function loadKitManifest() {
|
|
37
|
+
_manifestCache = loadAndMergeManifest();
|
|
38
|
+
logger.info(`[KitRenderer] Loaded manifest: ${_manifestCache.length} sections`);
|
|
39
|
+
}
|
|
40
|
+
export function invalidateKitCache() {
|
|
41
|
+
_manifestCache = null;
|
|
42
|
+
_sessionPathCache.clear();
|
|
43
|
+
}
|
|
44
|
+
export function invalidateSessionCache(sessionId) {
|
|
45
|
+
_sessionPathCache.delete(sessionId);
|
|
46
|
+
}
|
|
47
|
+
export function renderKitSections(ctx) {
|
|
48
|
+
if (!_manifestCache)
|
|
49
|
+
loadKitManifest();
|
|
50
|
+
const sections = _manifestCache;
|
|
51
|
+
const fileParts = [];
|
|
52
|
+
for (const section of sections) {
|
|
53
|
+
if (section.enabled === false)
|
|
54
|
+
continue;
|
|
55
|
+
if (!evaluateWhen(section.when, ctx.vars))
|
|
56
|
+
continue;
|
|
57
|
+
const files = loadSectionFiles(section, ctx);
|
|
58
|
+
if (files.length === 0)
|
|
59
|
+
continue;
|
|
60
|
+
for (const [filePath, rawContent] of files) {
|
|
61
|
+
const content = section.needsInjection ? renderTemplate(rawContent, ctx.vars) : rawContent;
|
|
62
|
+
if (!content.trim())
|
|
63
|
+
continue;
|
|
64
|
+
const label = section.description ? `${section.id} — ${section.description}` : section.id;
|
|
65
|
+
fileParts.push(`Contenu de ${filePath} (${label}):\n\n${content.trimEnd()}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (fileParts.length === 0)
|
|
69
|
+
return '';
|
|
70
|
+
const body = fileParts.join('\n\n');
|
|
71
|
+
const output = `<system-reminder>\nEvolClaw Context Kit documents are shown below.\n\n${body}\n\nIMPORTANT: Use this context when it affects the current interaction.\n</system-reminder>`;
|
|
72
|
+
writeDebugFiles(ctx, output);
|
|
73
|
+
return output;
|
|
74
|
+
}
|
|
75
|
+
export function cleanEckDebug() {
|
|
76
|
+
const dir = eckDebugDir();
|
|
77
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
78
|
+
try {
|
|
79
|
+
for (const f of fs.readdirSync(dir)) {
|
|
80
|
+
const fp = path.join(dir, f);
|
|
81
|
+
try {
|
|
82
|
+
if (fs.statSync(fp).mtimeMs < cutoff)
|
|
83
|
+
fs.unlinkSync(fp);
|
|
84
|
+
}
|
|
85
|
+
catch { /* skip */ }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { /* dir doesn't exist yet */ }
|
|
89
|
+
}
|
|
90
|
+
// CHUNK_CONTINUE_2
|
|
91
|
+
// ── Manifest loading ──
|
|
92
|
+
function loadAndMergeManifest() {
|
|
93
|
+
const kitsPath = path.join(kitsDir(), 'eck_manifest.json');
|
|
94
|
+
const eckPath = path.join(resolveRoot(), 'eck', 'eck_manifest.json');
|
|
95
|
+
let base;
|
|
96
|
+
try {
|
|
97
|
+
base = JSON.parse(fs.readFileSync(kitsPath, 'utf-8'));
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logger.error(`[KitRenderer] Failed to load kits/eck_manifest.json: ${err}`);
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
if (!fs.existsSync(eckPath)) {
|
|
104
|
+
return sortSections(base.sections);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const override = JSON.parse(fs.readFileSync(eckPath, 'utf-8'));
|
|
108
|
+
if (override.mode === 'replace') {
|
|
109
|
+
return sortSections(override.sections);
|
|
110
|
+
}
|
|
111
|
+
const merged = new Map();
|
|
112
|
+
for (const s of base.sections)
|
|
113
|
+
merged.set(s.id, { ...s });
|
|
114
|
+
for (const s of override.sections) {
|
|
115
|
+
const existing = merged.get(s.id);
|
|
116
|
+
if (existing) {
|
|
117
|
+
merged.set(s.id, { ...existing, ...s });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
merged.set(s.id, s);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return sortSections([...merged.values()]);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
logger.warn(`[KitRenderer] Failed to load eck override, using kits only: ${err}`);
|
|
127
|
+
return sortSections(base.sections);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function sortSections(sections) {
|
|
131
|
+
return sections.slice().sort((a, b) => a.order - b.order);
|
|
132
|
+
}
|
|
133
|
+
// ── Section content loading ──
|
|
134
|
+
function loadSectionFiles(section, ctx) {
|
|
135
|
+
if (section.type === 'file' && section.file) {
|
|
136
|
+
const result = loadFileSection(section.file, ctx);
|
|
137
|
+
return result ? [result] : [];
|
|
138
|
+
}
|
|
139
|
+
if (section.type === 'directory' && section.path) {
|
|
140
|
+
return loadDirectorySection(section.path, section.pattern, ctx);
|
|
141
|
+
}
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
function loadFileSection(filePath, ctx) {
|
|
145
|
+
const resolved = resolvePath(filePath, ctx);
|
|
146
|
+
if (!resolved)
|
|
147
|
+
return null;
|
|
148
|
+
const sessionCache = getSessionCache(ctx.sessionId);
|
|
149
|
+
if (sessionCache.has(resolved))
|
|
150
|
+
return [resolved, sessionCache.get(resolved)];
|
|
151
|
+
try {
|
|
152
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
153
|
+
sessionCache.set(resolved, content);
|
|
154
|
+
return [resolved, content];
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function loadDirectorySection(dirPath, pattern, ctx) {
|
|
161
|
+
const resolved = resolvePath(dirPath, ctx);
|
|
162
|
+
if (!resolved)
|
|
163
|
+
return [];
|
|
164
|
+
return readDirectoryFiles(resolved, pattern).map(([name, content]) => [path.join(resolved, name), content]);
|
|
165
|
+
}
|
|
166
|
+
// ── Path resolution ──
|
|
167
|
+
function resolvePath(rawPath, ctx) {
|
|
168
|
+
let resolved = rawPath.replace(/\$([A-Z_]+)/g, (_, name) => {
|
|
169
|
+
const val = ctx.vars[name];
|
|
170
|
+
if (val === undefined || val === null || val === false || val === '')
|
|
171
|
+
return '';
|
|
172
|
+
return String(val);
|
|
173
|
+
});
|
|
174
|
+
resolved = resolved.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
175
|
+
const val = ctx.vars[key];
|
|
176
|
+
if (val === undefined || val === null || val === false || val === '')
|
|
177
|
+
return '';
|
|
178
|
+
return String(val);
|
|
179
|
+
});
|
|
180
|
+
if (!resolved || resolved.includes('$') || resolved.includes('{{'))
|
|
181
|
+
return null;
|
|
182
|
+
if (!fs.existsSync(resolved))
|
|
183
|
+
return null;
|
|
184
|
+
return resolved;
|
|
185
|
+
}
|
|
186
|
+
// CHUNK_CONTINUE_5
|
|
187
|
+
// ── Directory reading ──
|
|
188
|
+
function readDirectoryFiles(dirPath, pattern) {
|
|
189
|
+
const glob = pattern || '*.md';
|
|
190
|
+
try {
|
|
191
|
+
const files = fs.readdirSync(dirPath)
|
|
192
|
+
.filter(f => matchGlob(f, glob))
|
|
193
|
+
.sort();
|
|
194
|
+
return files.map(f => {
|
|
195
|
+
const content = fs.readFileSync(path.join(dirPath, f), 'utf-8');
|
|
196
|
+
return [f, content];
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function matchGlob(filename, pattern) {
|
|
204
|
+
const regex = pattern
|
|
205
|
+
.replace(/\./g, '\\.')
|
|
206
|
+
.replace(/\*/g, '.*')
|
|
207
|
+
.replace(/\{([^}]+)\}/g, (_, alts) => `(${alts.split(',').join('|')})`);
|
|
208
|
+
return new RegExp(`^${regex}$`).test(filename);
|
|
209
|
+
}
|
|
210
|
+
// ── When condition evaluation ──
|
|
211
|
+
function evaluateWhen(when, vars) {
|
|
212
|
+
if (when === 'always')
|
|
213
|
+
return true;
|
|
214
|
+
if (when.var !== undefined) {
|
|
215
|
+
const val = vars[when.var];
|
|
216
|
+
if (when.eq !== undefined)
|
|
217
|
+
return val === when.eq;
|
|
218
|
+
if (when.neq !== undefined)
|
|
219
|
+
return val !== when.neq;
|
|
220
|
+
if (when.in !== undefined)
|
|
221
|
+
return when.in.includes(val);
|
|
222
|
+
if (when.nin !== undefined)
|
|
223
|
+
return !when.nin.includes(val);
|
|
224
|
+
}
|
|
225
|
+
if (when.any)
|
|
226
|
+
return when.any.some(k => isTruthy(vars[k]));
|
|
227
|
+
if (when.all)
|
|
228
|
+
return when.all.every(k => isTruthy(vars[k]));
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
function isTruthy(val) {
|
|
232
|
+
return val !== undefined && val !== null && val !== false && val !== '' && val !== 0;
|
|
233
|
+
}
|
|
234
|
+
// CHUNK_CONTINUE_6
|
|
235
|
+
// ── Template rendering ──
|
|
236
|
+
function renderTemplate(template, vars) {
|
|
237
|
+
// Pass 1: conditional sections {{?key=value}}...{{/}} and {{?key}}...{{/}}
|
|
238
|
+
let result = template.replace(/\{\{\?(\w+)(?:=([^}]*))?\}\}([\s\S]*?)\{\{\/\}\}/g, (_match, key, value, body) => {
|
|
239
|
+
if (value !== undefined) {
|
|
240
|
+
return String(vars[key]) === value ? body : '';
|
|
241
|
+
}
|
|
242
|
+
return isTruthy(vars[key]) ? body : '';
|
|
243
|
+
});
|
|
244
|
+
// Pass 2: variable substitution {{key}}
|
|
245
|
+
result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
246
|
+
const val = vars[key];
|
|
247
|
+
if (!isTruthy(val))
|
|
248
|
+
return '';
|
|
249
|
+
return String(val);
|
|
250
|
+
});
|
|
251
|
+
// Pass 3: remove blank lines
|
|
252
|
+
return result.split('\n').filter(line => line.trim() !== '').join('\n');
|
|
253
|
+
}
|
|
254
|
+
// ── Session cache helper ──
|
|
255
|
+
function getSessionCache(sessionId) {
|
|
256
|
+
let cache = _sessionPathCache.get(sessionId);
|
|
257
|
+
if (!cache) {
|
|
258
|
+
cache = new Map();
|
|
259
|
+
_sessionPathCache.set(sessionId, cache);
|
|
260
|
+
}
|
|
261
|
+
return cache;
|
|
262
|
+
}
|
|
263
|
+
// ── Debug output ──
|
|
264
|
+
function writeDebugFiles(ctx, output) {
|
|
265
|
+
const now = new Date();
|
|
266
|
+
const ts = now.toISOString().replace(/[T:.]/g, '-').slice(0, 19);
|
|
267
|
+
const dir = eckDebugDir();
|
|
268
|
+
const varsData = {
|
|
269
|
+
timestamp: now.toISOString(),
|
|
270
|
+
sessionId: ctx.sessionId,
|
|
271
|
+
params: Object.entries(ctx.vars)
|
|
272
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
273
|
+
.map(([name, value]) => ({
|
|
274
|
+
name,
|
|
275
|
+
value,
|
|
276
|
+
description: PARAM_DESCRIPTIONS[name] || '',
|
|
277
|
+
})),
|
|
278
|
+
};
|
|
279
|
+
fs.writeFile(path.join(dir, `vars-${ts}.json`), JSON.stringify(varsData, null, 2), () => { });
|
|
280
|
+
fs.writeFile(path.join(dir, `context-${ts}.md`), output, () => { });
|
|
281
|
+
}
|
package/dist/aun/aid/identity.js
CHANGED
|
@@ -3,6 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import os from 'os';
|
|
4
4
|
import crypto from 'crypto';
|
|
5
5
|
import { getAunClient, downloadCaRoot } from './client.js';
|
|
6
|
+
import { resolvePaths } from '../../paths.js';
|
|
6
7
|
// ==================== Validation ====================
|
|
7
8
|
export function isValidAid(name) {
|
|
8
9
|
const labels = name.split('.');
|
|
@@ -129,3 +130,30 @@ export async function aidLookup(aid) {
|
|
|
129
130
|
return { exists: false, aid, gateway, error: String(e.message || e) };
|
|
130
131
|
}
|
|
131
132
|
}
|
|
133
|
+
function lifecycleLogPath(aid) {
|
|
134
|
+
const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
|
|
135
|
+
return path.join(resolvePaths().aidLogsDir, `${aidName}.jsonl`);
|
|
136
|
+
}
|
|
137
|
+
export function appendAidLifecycle(event) {
|
|
138
|
+
const filePath = lifecycleLogPath(event.aid);
|
|
139
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
140
|
+
fs.appendFileSync(filePath, JSON.stringify(event) + '\n');
|
|
141
|
+
}
|
|
142
|
+
export function readAidLifecycle(aid, lastN = 50) {
|
|
143
|
+
const filePath = lifecycleLogPath(aid);
|
|
144
|
+
try {
|
|
145
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
146
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
147
|
+
const events = [];
|
|
148
|
+
for (const line of lines.slice(-lastN)) {
|
|
149
|
+
try {
|
|
150
|
+
events.push(JSON.parse(line));
|
|
151
|
+
}
|
|
152
|
+
catch { }
|
|
153
|
+
}
|
|
154
|
+
return events;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
}
|
package/dist/aun/aid/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { isValidAid, aidList, aidCreate, aidShow, aidDelete, aidLookup } from './identity.js';
|
|
1
|
+
export { isValidAid, aidList, aidCreate, aidShow, aidDelete, aidLookup, appendAidLifecycle, readAidLifecycle } from './identity.js';
|
|
2
2
|
export { buildInitialAgentMd, agentmdGet, agentmdPut } from './agentmd.js';
|
|
3
3
|
export { MIN_AUN_CORE_SDK, AUN_CORE_SDK_PKG, isAunSdkVersionOk, resolveAunCoreSdkPkg, ensureAunSdk, isAunSdkReady, downloadCaRoot, getAunClient, suppressSdkLogs, } from './client.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolvePaths } from '../../paths.js';
|
|
4
|
+
function ensureDir(dir) {
|
|
5
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
6
|
+
}
|
|
7
|
+
function logPath(aid) {
|
|
8
|
+
const aidName = aid.startsWith('@') ? aid.slice(1) : aid;
|
|
9
|
+
return path.join(resolvePaths().aidLogsDir, `${aidName}.jsonl`);
|
|
10
|
+
}
|
|
11
|
+
export function appendAidLifecycle(event) {
|
|
12
|
+
const filePath = logPath(event.aid);
|
|
13
|
+
ensureDir(path.dirname(filePath));
|
|
14
|
+
fs.appendFileSync(filePath, JSON.stringify(event) + '\n');
|
|
15
|
+
}
|
|
16
|
+
export function readAidLifecycle(aid, lastN = 50) {
|
|
17
|
+
const filePath = logPath(aid);
|
|
18
|
+
try {
|
|
19
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
20
|
+
const lines = content.trim().split('\n').filter(Boolean);
|
|
21
|
+
const events = [];
|
|
22
|
+
for (const line of lines.slice(-lastN)) {
|
|
23
|
+
try {
|
|
24
|
+
events.push(JSON.parse(line));
|
|
25
|
+
}
|
|
26
|
+
catch { }
|
|
27
|
+
}
|
|
28
|
+
return events;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
package/dist/aun/msg/group.js
CHANGED
|
@@ -25,7 +25,9 @@ export async function groupSend(args) {
|
|
|
25
25
|
if (args.mentions && args.mentions.length > 0) {
|
|
26
26
|
payload.mentions = args.mentions;
|
|
27
27
|
}
|
|
28
|
-
const
|
|
28
|
+
const sendParams = { group_id: args.groupId, payload };
|
|
29
|
+
sendParams.encrypt = args.encrypt === true;
|
|
30
|
+
const result = await conn.call('group.send', sendParams);
|
|
29
31
|
return {
|
|
30
32
|
ok: true,
|
|
31
33
|
group_id: result?.group_id ?? args.groupId,
|
package/dist/aun/msg/p2p.js
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { createShortConnection } from '../rpc/index.js';
|
|
2
3
|
import { uploadFileAndBuildPayload } from './upload.js';
|
|
4
|
+
import { appendMessageLog, buildOutboundEntry } from '../../core/message/message-log.js';
|
|
5
|
+
import { chatDirPath } from '../../core/session/session-fs-store.js';
|
|
6
|
+
import { resolvePaths } from '../../paths.js';
|
|
3
7
|
export async function msgSend(args) {
|
|
4
8
|
const conn = await createShortConnection(args.from, { aunPath: args.aunPath, slotId: args.slotId });
|
|
5
9
|
try {
|
|
10
|
+
// 1. 解析对端身份(30天缓存)
|
|
11
|
+
const { agentsDir } = resolvePaths();
|
|
12
|
+
const selfAgentDir = path.join(agentsDir, args.from);
|
|
13
|
+
const { PeerIdentityCache } = await import('../../core/relation/peer-identity.js');
|
|
14
|
+
const peerIdentity = await PeerIdentityCache.resolve('aun', args.to, selfAgentDir, conn, false);
|
|
15
|
+
// 2. 决定 chatmode(遵循来源1-3)
|
|
16
|
+
// 私聊:非 human 对端 → proactive,human 对端 → interactive
|
|
17
|
+
const chatmode = peerIdentity.isAgent ? 'proactive' : 'interactive';
|
|
18
|
+
// 3. 构建 payload
|
|
6
19
|
let payload;
|
|
7
20
|
switch (args.body.mode) {
|
|
8
21
|
case 'text':
|
|
@@ -29,7 +42,35 @@ export async function msgSend(args) {
|
|
|
29
42
|
break;
|
|
30
43
|
}
|
|
31
44
|
}
|
|
32
|
-
|
|
45
|
+
// 4. 写入 payload.chatmode
|
|
46
|
+
payload.chatmode = chatmode;
|
|
47
|
+
const sendParams = { to: args.to, payload };
|
|
48
|
+
// Default: plaintext. Set encrypt: true to enable E2EE.
|
|
49
|
+
sendParams.encrypt = args.encrypt === true;
|
|
50
|
+
const result = await conn.call('message.send', sendParams);
|
|
51
|
+
// 5. 写出方向 jsonl(与 daemon 一致格式,标记 source=cli)
|
|
52
|
+
if (result?.message_id) {
|
|
53
|
+
try {
|
|
54
|
+
const sessionsDir = resolvePaths().sessionsDir;
|
|
55
|
+
const chatDir = chatDirPath(sessionsDir, 'aun', args.to, args.from);
|
|
56
|
+
const textContent = args.body.mode === 'text' ? args.body.text
|
|
57
|
+
: args.body.mode === 'link' ? `[link] ${args.body.url}`
|
|
58
|
+
: args.body.mode === 'file' ? `[file] ${args.body.filePath}`
|
|
59
|
+
: `[payload]`;
|
|
60
|
+
appendMessageLog(chatDir, buildOutboundEntry({
|
|
61
|
+
from: args.from,
|
|
62
|
+
to: args.to,
|
|
63
|
+
chatType: 'private',
|
|
64
|
+
msgId: result.message_id,
|
|
65
|
+
content: textContent,
|
|
66
|
+
encrypt: args.encrypt === true,
|
|
67
|
+
chatmode, // 使用解析出的 chatmode
|
|
68
|
+
msgType: 'text',
|
|
69
|
+
source: 'cli',
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
catch { }
|
|
73
|
+
}
|
|
33
74
|
return {
|
|
34
75
|
ok: true,
|
|
35
76
|
message_id: result?.message_id,
|