evolclaw 3.0.0 → 3.1.0

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 (99) 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 +7 -9
  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 +4 -1
  13. package/dist/channels/aun.js +353 -125
  14. package/dist/channels/dingtalk.js +2 -1
  15. package/dist/channels/feishu.js +118 -5
  16. package/dist/channels/qqbot.js +2 -1
  17. package/dist/channels/wechat.js +3 -1
  18. package/dist/channels/wecom.js +2 -1
  19. package/dist/cli/bench.js +1219 -0
  20. package/dist/cli/index.js +279 -19
  21. package/dist/cli/link-rules.js +245 -0
  22. package/dist/cli/net-check.js +640 -0
  23. package/dist/cli/watch-msg.js +589 -0
  24. package/dist/config-store.js +37 -5
  25. package/dist/core/channel-loader.js +23 -10
  26. package/dist/core/command-handler.js +46 -22
  27. package/dist/core/evolagent.js +5 -10
  28. package/dist/core/message/im-renderer.js +50 -44
  29. package/dist/core/message/items-formatter.js +11 -4
  30. package/dist/core/message/message-bridge.js +7 -2
  31. package/dist/core/message/message-log.js +2 -0
  32. package/dist/core/message/message-processor.js +150 -99
  33. package/dist/core/message/message-queue.js +10 -3
  34. package/dist/core/permission.js +95 -3
  35. package/dist/core/session/session-manager.js +98 -64
  36. package/dist/core/trigger/scheduler.js +1 -1
  37. package/dist/data/error-dict.json +118 -0
  38. package/dist/eck/baseagent-caps.js +18 -0
  39. package/dist/eck/detect.js +47 -0
  40. package/dist/eck/init.js +77 -0
  41. package/dist/eck/rules-loader.js +28 -0
  42. package/dist/index.js +137 -16
  43. package/dist/net-check.js +640 -0
  44. package/dist/paths.js +31 -40
  45. package/dist/utils/aid-lifecycle-log.js +33 -0
  46. package/dist/utils/atomic-write.js +10 -0
  47. package/dist/utils/cross-platform.js +17 -8
  48. package/dist/utils/error-utils.js +10 -2
  49. package/dist/utils/instance-registry.js +6 -5
  50. package/dist/utils/log-writer.js +2 -1
  51. package/dist/utils/logger.js +10 -0
  52. package/dist/utils/npm-ops.js +35 -3
  53. package/dist/utils/process-introspect.js +16 -38
  54. package/dist/watch-msg.js +26 -11
  55. package/evolclaw-install-aun.md +14 -2
  56. package/kits/docs/GUIDE.md +20 -0
  57. package/kits/docs/INDEX.md +52 -0
  58. package/kits/docs/aun/CHEATSHEET.md +17 -0
  59. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  60. package/kits/docs/channels/feishu.md +27 -0
  61. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  62. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  63. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  64. package/kits/docs/eck_templates/runtime.template.md +19 -0
  65. package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
  66. package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
  67. package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
  68. package/kits/docs/identity/PATH_OPS.md +16 -0
  69. package/kits/docs/identity/ROLE_DETAIL.md +20 -0
  70. package/kits/docs/path-registry.md +43 -0
  71. package/kits/eck_manifest.json +95 -0
  72. package/kits/rules/01-overview.md +120 -0
  73. package/kits/rules/02-navigation.md +75 -0
  74. package/kits/rules/03-identity.md +34 -0
  75. package/kits/rules/04-relation.md +49 -0
  76. package/kits/rules/05-venue.md +45 -0
  77. package/kits/rules/06-channel.md +43 -0
  78. package/kits/templates/system-fragments/baseagent.md +2 -0
  79. package/kits/templates/system-fragments/channel.md +10 -0
  80. package/kits/templates/system-fragments/identity.md +12 -0
  81. package/kits/templates/system-fragments/relation.md +9 -0
  82. package/kits/templates/system-fragments/runtime.md +19 -0
  83. package/kits/templates/system-fragments/venue.md +5 -0
  84. package/package.json +7 -5
  85. package/dist/agents/templates.js +0 -122
  86. package/dist/data/prompts.md +0 -137
  87. package/kits/aun/meta.md +0 -25
  88. package/kits/aun/role.md +0 -25
  89. package/kits/templates/group.md +0 -20
  90. package/kits/templates/private.md +0 -9
  91. package/kits/templates/system-fragments/personal-context.md +0 -3
  92. package/kits/templates/system-fragments/self-intro.md +0 -5
  93. package/kits/templates/system-fragments/speaker-intro.md +0 -5
  94. package/kits/templates/system-fragments/venue-intro.md +0 -5
  95. /package/kits/{channels → docs/channels}/aun.md +0 -0
  96. /package/kits/{evolclaw/commands.md → docs/evolclaw/AGENT_CMD.md} +0 -0
  97. /package/kits/{evolclaw → docs/evolclaw}/self-summary.md +0 -0
  98. /package/kits/{evolclaw → docs/evolclaw}/tools.md +0 -0
  99. /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
+ }
@@ -494,7 +494,6 @@ export class AgentRunner {
494
494
  * 所有 SDK 特有的事件类型引用封装在此方法内
495
495
  */
496
496
  async *transformStream(sdkStream, sessionId) {
497
- let hasTextDelta = false;
498
497
  let lastSessionId;
499
498
  // tool_use_id → tool_name 映射,用于从 SDKUserMessage 的 tool_result 块中还原工具名
500
499
  const toolUseNames = new Map();
@@ -505,11 +504,6 @@ export class AgentRunner {
505
504
  this.updateSessionId(sessionId, event.session_id);
506
505
  yield { type: 'session_id', sessionId: event.session_id };
507
506
  }
508
- // text_delta → text
509
- if (event.type === 'text_delta' && event.text) {
510
- hasTextDelta = true;
511
- yield { type: 'text', text: event.text };
512
- }
513
507
  // system: compact_boundary → compact
514
508
  if (event.type === 'system' && event.subtype === 'compact_boundary') {
515
509
  yield { type: 'compact', preTokens: event.compact_metadata?.pre_tokens || 0 };
@@ -536,7 +530,7 @@ export class AgentRunner {
536
530
  toolUseNames.set(content.id, content.name);
537
531
  yield { type: 'tool_use', name: content.name, input: content.input, callId: content.id };
538
532
  }
539
- else if (content.type === 'text' && content.text && !hasTextDelta) {
533
+ else if (content.type === 'text' && content.text) {
540
534
  yield { type: 'text', text: content.text };
541
535
  }
542
536
  }
@@ -589,7 +583,11 @@ export class AgentRunner {
589
583
  costUsd: event.total_cost_usd,
590
584
  terminalReason: event.terminal_reason,
591
585
  sessionTitle: event.session_title,
586
+ numTurns: event.num_turns,
587
+ usage: event.usage,
592
588
  };
589
+ // result 是 SDK 流的终结事件,不再等待后续(防止 interrupt 后流不关闭导致挂起)
590
+ return;
593
591
  }
594
592
  }
595
593
  }
@@ -762,7 +760,7 @@ export class AgentRunner {
762
760
  const sdkPermissionMode = this.toSdkPermissionMode();
763
761
  logger.info(`[AgentRunner] runQuery model=${this.model} effort=${this.effort ?? 'auto'} permMode=${this.permissionMode} sdkMode=${sdkPermissionMode}`);
764
762
  if (systemPromptAppend) {
765
- logger.info(`[AgentRunner] systemPromptAppend (full):\n${systemPromptAppend}`);
763
+ logger.info(`[AgentRunner] systemPromptAppend: ${systemPromptAppend.length} chars`);
766
764
  }
767
765
  else {
768
766
  logger.info(`[AgentRunner] systemPromptAppend: none`);
@@ -1059,7 +1057,7 @@ export class AgentRunner {
1059
1057
  export class ClaudeAgentPlugin {
1060
1058
  name = 'claude';
1061
1059
  isEnabled(agent) {
1062
- return !!agent.config.baseagents?.claude;
1060
+ return agent.baseagent === 'claude';
1063
1061
  }
1064
1062
  createAgent(agent, callbacks) {
1065
1063
  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,
@@ -29,7 +29,10 @@ export async function msgSend(args) {
29
29
  break;
30
30
  }
31
31
  }
32
- const result = await conn.call('message.send', { to: args.to, payload });
32
+ const sendParams = { to: args.to, payload };
33
+ // Default: plaintext. Set encrypt: true to enable E2EE.
34
+ sendParams.encrypt = args.encrypt === true;
35
+ const result = await conn.call('message.send', sendParams);
33
36
  return {
34
37
  ok: true,
35
38
  message_id: result?.message_id,