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.
Files changed (104) hide show
  1. package/README.md +1 -1
  2. package/bin/ec.js +29 -0
  3. package/dist/agents/baseagent-normalize.js +19 -0
  4. package/dist/agents/claude-runner.js +47 -12
  5. package/dist/agents/codex-runner.js +2 -0
  6. package/dist/agents/gemini-runner.js +9 -9
  7. package/dist/agents/kit-renderer.js +281 -0
  8. package/dist/aun/aid/identity.js +28 -0
  9. package/dist/aun/aid/index.js +1 -1
  10. package/dist/aun/aid/lifecycle-log.js +33 -0
  11. package/dist/aun/msg/group.js +3 -1
  12. package/dist/aun/msg/p2p.js +42 -1
  13. package/dist/channels/aun.js +427 -146
  14. package/dist/channels/dingtalk.js +3 -1
  15. package/dist/channels/feishu.js +128 -7
  16. package/dist/channels/qqbot.js +3 -1
  17. package/dist/channels/wechat.js +4 -1
  18. package/dist/channels/wecom.js +3 -1
  19. package/dist/cli/bench.js +1219 -0
  20. package/dist/cli/index.js +418 -40
  21. package/dist/cli/init.js +3 -4
  22. package/dist/cli/link-rules.js +245 -0
  23. package/dist/cli/net-check.js +640 -0
  24. package/dist/cli/watch-msg.js +666 -0
  25. package/dist/config-store.js +82 -5
  26. package/dist/core/channel-loader.js +23 -10
  27. package/dist/core/command-handler.js +127 -99
  28. package/dist/core/evolagent.js +5 -10
  29. package/dist/core/message/im-renderer.js +93 -48
  30. package/dist/core/message/items-formatter.js +11 -4
  31. package/dist/core/message/message-bridge.js +11 -2
  32. package/dist/core/message/message-log.js +8 -1
  33. package/dist/core/message/message-processor.js +194 -127
  34. package/dist/core/message/message-queue.js +10 -3
  35. package/dist/core/permission.js +95 -3
  36. package/dist/core/relation/peer-identity.js +161 -0
  37. package/dist/core/session/session-manager.js +103 -65
  38. package/dist/core/trigger/manager.js +16 -0
  39. package/dist/core/trigger/parser.js +110 -0
  40. package/dist/core/trigger/scheduler.js +7 -1
  41. package/dist/data/error-dict.json +118 -0
  42. package/dist/eck/baseagent-caps.js +18 -0
  43. package/dist/eck/detect.js +47 -0
  44. package/dist/eck/init.js +77 -0
  45. package/dist/eck/rules-loader.js +28 -0
  46. package/dist/index.js +186 -19
  47. package/dist/net-check.js +640 -0
  48. package/dist/paths.js +31 -40
  49. package/dist/utils/aid-lifecycle-log.js +33 -0
  50. package/dist/utils/atomic-write.js +10 -0
  51. package/dist/utils/cross-platform.js +17 -8
  52. package/dist/utils/error-utils.js +27 -15
  53. package/dist/utils/instance-registry.js +6 -5
  54. package/dist/utils/log-writer.js +2 -1
  55. package/dist/utils/logger.js +10 -0
  56. package/dist/utils/npm-ops.js +35 -3
  57. package/dist/utils/process-introspect.js +16 -38
  58. package/dist/utils/stats.js +216 -2
  59. package/dist/watch-msg.js +26 -11
  60. package/evolclaw-install-aun.md +14 -2
  61. package/kits/docs/GUIDE.md +20 -0
  62. package/kits/docs/INDEX.md +52 -0
  63. package/kits/docs/aun/CHEATSHEET.md +17 -0
  64. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  65. package/kits/docs/channels/feishu.md +27 -0
  66. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  67. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  68. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  69. package/kits/docs/eck_templates/runtime.template.md +19 -0
  70. package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
  71. package/kits/docs/evolclaw/MSG_PRIVATE.md +72 -0
  72. package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
  73. package/kits/docs/identity/PATH_OPS.md +16 -0
  74. package/kits/docs/identity/ROLE_DETAIL.md +20 -0
  75. package/kits/docs/path-registry.md +43 -0
  76. package/kits/eck_manifest.json +95 -0
  77. package/kits/rules/01-overview.md +120 -0
  78. package/kits/rules/02-navigation.md +75 -0
  79. package/kits/rules/03-identity.md +34 -0
  80. package/kits/rules/04-relation.md +49 -0
  81. package/kits/rules/05-venue.md +45 -0
  82. package/kits/rules/06-channel.md +73 -0
  83. package/kits/templates/system-fragments/baseagent.md +2 -0
  84. package/kits/templates/system-fragments/channel.md +10 -0
  85. package/kits/templates/system-fragments/identity.md +12 -0
  86. package/kits/templates/system-fragments/relation.md +9 -0
  87. package/kits/templates/system-fragments/runtime.md +19 -0
  88. package/kits/templates/system-fragments/venue.md +5 -0
  89. package/package.json +7 -5
  90. package/dist/agents/templates.js +0 -122
  91. package/dist/data/prompts.md +0 -137
  92. package/kits/aun/meta.md +0 -25
  93. package/kits/aun/role.md +0 -25
  94. package/kits/templates/group.md +0 -20
  95. package/kits/templates/private.md +0 -9
  96. package/kits/templates/system-fragments/personal-context.md +0 -3
  97. package/kits/templates/system-fragments/self-intro.md +0 -5
  98. package/kits/templates/system-fragments/speaker-intro.md +0 -5
  99. package/kits/templates/system-fragments/venue-intro.md +0 -5
  100. /package/kits/{channels → docs/channels}/aun.md +0 -0
  101. /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
  102. /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
  103. /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
  104. /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|all]` - 群聊分发模式(仅 @ 响应或广播)
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 && !hasTextDelta) {
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 (full):\n${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 !!agent.config.baseagents?.claude;
1097
+ return agent.baseagent === 'claude';
1063
1098
  }
1064
1099
  createAgent(agent, callbacks) {
1065
1100
  const override = agent.config.baseagents?.claude;
@@ -302,6 +302,8 @@ export class CodexRunner {
302
302
  export class CodexAgentPlugin {
303
303
  name = 'codex';
304
304
  isEnabled(agent) {
305
+ if (agent.baseagent !== 'codex')
306
+ return false;
305
307
  if (!agent.config.baseagents?.codex)
306
308
  return false;
307
309
  try {
@@ -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 (!agent.config.baseagents?.gemini)
410
+ if (agent.baseagent !== 'gemini')
410
411
  return false;
411
- try {
412
- const override = agent.config.baseagents.gemini;
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
+ }
@@ -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
+ }
@@ -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
+ }
@@ -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 result = await conn.call('group.send', { group_id: args.groupId, payload });
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,
@@ -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
- const result = await conn.call('message.send', { to: args.to, payload });
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,