evolclaw 3.1.1 → 3.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,6 @@ import path from 'path';
2
2
  import fs from 'fs';
3
3
  import crypto from 'crypto';
4
4
  import { hasCompact } from '../../agents/claude-runner.js';
5
- import { appendMessageLog, buildOutboundEntry } from './message-log.js';
6
5
  import { IMRenderer } from './im-renderer.js';
7
6
  import { StreamIdleMonitor } from './stream-idle-monitor.js';
8
7
  import { logger } from '../../utils/logger.js';
@@ -1161,29 +1160,8 @@ export class MessageProcessor {
1161
1160
  }
1162
1161
  // 记录完成状态 + 最后一轮回复文本(后续 complete 覆盖前序)
1163
1162
  completeResult = { isError: !!event.isError, subtype: event.subtype, errors: event.errors, terminalReason: event.terminalReason, lastReplyText, fullText: event.result || '', hasReceivedText, numTurns: event.numTurns, usage: event.usage };
1164
- // proactive 模式:每轮 LLM 调用完成后写一条 thought 到 messages.jsonl
1165
- // 这样 thought = LLM 调用轮数,而不是 chunk 数
1166
- if (session.sessionMode === 'proactive' && lastReplyText) {
1167
- try {
1168
- const chatDir = this.sessionManager.getChatDir(session);
1169
- const sessionEncrypt = this.sessionManager.getSessionEncrypt(session.id);
1170
- appendMessageLog(chatDir, buildOutboundEntry({
1171
- from: session.selfId || 'self',
1172
- to: session.metadata?.peerId ?? session.channelId,
1173
- chatType: (session.chatType ?? 'private'),
1174
- groupId: session.metadata?.groupId ?? null,
1175
- msgId: `thought-${session.id}-${Date.now()}`,
1176
- content: lastReplyText,
1177
- agent: session.agentId || null,
1178
- model: null,
1179
- durationMs: null,
1180
- encrypt: sessionEncrypt ?? undefined,
1181
- chatmode: 'proactive',
1182
- msgType: 'thought',
1183
- }));
1184
- }
1185
- catch { }
1186
- }
1163
+ // thought jsonl 写入已下沉到 aun.ts:sendThought 成功后,
1164
+ // 由那里按 LLM 输出的每个 text item 单独写一条,此处不再写。
1187
1165
  // 失败且无前置错误输出:显示 errors 摘要
1188
1166
  // 但用户主动中断(新消息打断 或 /stop 命令)时不显示错误提示
1189
1167
  // 上下文过长的错误留给外层 isPromptTooLong 触发 auto-compact,不在此处输出
@@ -146,6 +146,29 @@ export function scanChatDirs(sessionsDir) {
146
146
  if (!typeEntry.isDirectory())
147
147
  continue;
148
148
  const channelType = typeEntry.name;
149
+ // 包含 '#' 的目录是旧 channelKey 格式(如 'aun#dddd.agentid.pub#main'),
150
+ // 按通用 channel 布局扫描(sessionsDir/{channelKey}/{encodedChannelId}/),保持兼容
151
+ if (channelType.includes('#')) {
152
+ const typeDir = path.join(sessionsDir, channelType);
153
+ let chatEntries;
154
+ try {
155
+ chatEntries = fs.readdirSync(typeDir, { withFileTypes: true });
156
+ }
157
+ catch {
158
+ continue;
159
+ }
160
+ for (const chatEntry of chatEntries) {
161
+ if (!chatEntry.isDirectory())
162
+ continue;
163
+ results.push({
164
+ channelType,
165
+ selfId: null,
166
+ channelId: decodeSegment(chatEntry.name),
167
+ dirPath: path.join(typeDir, chatEntry.name),
168
+ });
169
+ }
170
+ continue;
171
+ }
149
172
  const typeDir = path.join(sessionsDir, channelType);
150
173
  if (channelType === 'aun') {
151
174
  // aun 下还有一层 selfId
@@ -540,7 +540,10 @@ export class SessionManager {
540
540
  this.writeSessionIfChanged(current.channel, current.channelId, prev, current);
541
541
  }
542
542
  getOrCreateThreadSession(channel, channelId, threadId, defaultProjectPath, metadata, name, agentId, selfId, channelType, peerType) {
543
- const chatDir = this.ensureResolvedChatDir(channel, channelId);
543
+ // 优先使用精确路径(channelType + selfId),避免 fallback 到错误目录
544
+ const chatDir = (channelType && selfId)
545
+ ? (() => { const d = chatDirPath(this.sessionsDir, channelType, channelId, selfId); fs.mkdirSync(d, { recursive: true }); fs.mkdirSync(path.join(d, '_threads'), { recursive: true }); return d; })()
546
+ : this.ensureResolvedChatDir(channelType || channel, channelId);
544
547
  const threadIndex = readThreadIndex(chatDir);
545
548
  const existingMetaId = threadIndex[threadId];
546
549
  if (existingMetaId) {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ClaudeSessionFileAdapter } from './core/session/adapters/claude-session-file-adapter.js';
2
2
  import { CodexSessionFileAdapter } from './core/session/adapters/codex-session-file-adapter.js';
3
3
  import { GeminiSessionFileAdapter } from './core/session/adapters/gemini-session-file-adapter.js';
4
- import { ensureDataDirs, resolvePaths, getPackageRoot } from './paths.js';
4
+ import { ensureDataDirs, resolvePaths, getPackageRoot, agentMdPath } from './paths.js';
5
5
  import { resolveAnthropicConfig } from './agents/resolve.js';
6
6
  import { loadDefaults, autoMigrateIfNeeded, migrateIdentitiesIfNeeded } from './config-store.js';
7
7
  import { CONFIG_SCHEMA_VERSION } from './types.js';
@@ -41,7 +41,6 @@ import { agentTriggersDir } from './paths.js';
41
41
  import { isLinkedInstall } from './utils/npm-ops.js';
42
42
  import path from 'path';
43
43
  import fs from 'fs';
44
- import os from 'os';
45
44
  import crypto from 'crypto';
46
45
  import { fileURLToPath } from 'url';
47
46
  /** 出站 payload 摘要(用于 channel-out.log) */
@@ -473,7 +472,7 @@ async function main() {
473
472
  logger.error(`[Trigger] Scheduler init failed for ${agent.aid}: ${err}`);
474
473
  }
475
474
  }
476
- // Inject primary agent's trigger scheduler into cmdHandler
475
+ // Inject primary agent's trigger scheduler as fallback (used when owning agent has no scheduler)
477
476
  const primaryAgentForTrigger = agentRegistry.runnableAgents()[0];
478
477
  if (primaryAgentForTrigger?.triggerScheduler && primaryAgentForTrigger?.triggerManager) {
479
478
  cmdHandler.setTriggerScheduler(primaryAgentForTrigger.triggerScheduler, primaryAgentForTrigger.triggerManager);
@@ -548,10 +547,10 @@ async function main() {
548
547
  if (inst.onProjectPathRequest && inst.channel.onProjectPathRequest) {
549
548
  inst.channel.onProjectPathRequest(async (channelId) => {
550
549
  // Effective default path: use the agent that owns this channel.
551
- const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelName);
550
+ const owningAgent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
552
551
  const effectiveDefault = owningAgent?.projectPath
553
552
  ?? primaryAgent.projectPath;
554
- const session = await sessionManager.getOrCreateSession(inst.adapter.channelName, channelId, effectiveDefault, undefined, undefined, undefined, undefined);
553
+ const session = await sessionManager.getOrCreateSession(inst.adapter.channelKey, channelId, effectiveDefault, undefined, undefined, undefined, undefined);
555
554
  return path.isAbsolute(session.projectPath)
556
555
  ? session.projectPath
557
556
  : path.resolve(process.cwd(), session.projectPath);
@@ -593,10 +592,10 @@ async function main() {
593
592
  }
594
593
  // Bind adapters to their owning agents and mark running
595
594
  for (const inst of channelInstances) {
596
- const agent = agentRegistry.resolveByChannel(inst.adapter.channelName);
595
+ const agent = agentRegistry.resolveByChannel(inst.adapter.channelKey);
597
596
  if (!agent || agent.status === 'error')
598
597
  continue;
599
- agent.channels.set(inst.adapter.channelName, inst.adapter);
598
+ agent.channels.set(inst.adapter.channelKey, inst.adapter);
600
599
  if (agent.status === 'stopped') {
601
600
  agent.status = 'running';
602
601
  }
@@ -611,7 +610,7 @@ async function main() {
611
610
  for (const inst of channelInstances) {
612
611
  const channelType = inst.channelType || inst.adapter.channelName;
613
612
  if (channelType === 'feishu' && 'preloadThreads' in inst.channel) {
614
- const threadIds = sessionManager.getKnownThreadIds(inst.adapter.channelName);
613
+ const threadIds = sessionManager.getKnownThreadIds(inst.adapter.channelKey);
615
614
  inst.channel.preloadThreads(threadIds);
616
615
  }
617
616
  }
@@ -643,9 +642,8 @@ async function main() {
643
642
  // 尝试从 agent.md 读取 name
644
643
  let agentName = agent.aid;
645
644
  try {
646
- const aunPath = process.env.AUN_HOME || path.join(os.homedir(), '.aun');
647
- const agentMdPath = path.join(aunPath, 'AIDs', agent.aid, 'agent.md');
648
- const content = fs.readFileSync(agentMdPath, 'utf-8');
645
+ const mdPath = agentMdPath(agent.aid);
646
+ const content = fs.readFileSync(mdPath, 'utf-8');
649
647
  const nameMatch = content.match(/^name:\s*"?([^"\n]+)/m);
650
648
  if (nameMatch)
651
649
  agentName = nameMatch[1].trim().replace(/"$/, '');
@@ -682,14 +680,14 @@ async function main() {
682
680
  continue; // 跳过同类型通道
683
681
  if (notified.has(otherType))
684
682
  continue; // 同类型已通知过
685
- const ownerId = agentRegistry.getOwner(other.adapter.channelName);
683
+ const ownerId = agentRegistry.getOwner(other.adapter.channelKey);
686
684
  if (!ownerId)
687
685
  continue;
688
686
  notified.add(otherType);
689
- const owningAgent = agentRegistry.resolveByChannel(other.adapter.channelName);
687
+ const owningAgent = agentRegistry.resolveByChannel(other.adapter.channelKey);
690
688
  const envelope = buildEnvelope({
691
689
  taskId: `system-channel-down-${crypto.randomBytes(5).toString('hex')}`,
692
- channel: other.adapter.channelName,
690
+ channel: other.adapter.channelKey,
693
691
  channelId: ownerId,
694
692
  agentName: owningAgent?.aid || 'evolclaw',
695
693
  });
@@ -780,10 +778,10 @@ async function main() {
780
778
  const replyContext = pending.rootId
781
779
  ? { replyToMessageId: pending.rootId, replyInThread: !!pending.threadId }
782
780
  : undefined;
783
- const owningAgent = agentRegistry.resolveByChannel(adapter.channelName);
781
+ const owningAgent = agentRegistry.resolveByChannel(adapter.channelKey);
784
782
  const envelope = buildEnvelope({
785
783
  taskId: `system-restart-${process.pid}`,
786
- channel: adapter.channelName,
784
+ channel: adapter.channelKey,
787
785
  channelId: pending.channelId,
788
786
  agentName: owningAgent?.aid || 'evolclaw',
789
787
  replyContext,
@@ -883,7 +881,7 @@ async function main() {
883
881
  const instances = await channelLoader.createForAgent(agent);
884
882
  for (const inst of instances) {
885
883
  registerChannelInstance(inst);
886
- agent.channels.set(inst.adapter.channelName, inst.adapter);
884
+ agent.channels.set(inst.adapter.channelKey, inst.adapter);
887
885
  channelInstances.push(inst);
888
886
  }
889
887
  agent.status = 'running';
package/dist/paths.js CHANGED
@@ -45,6 +45,16 @@ export function resolvePaths() {
45
45
  aidLogsDir: path.join(root, 'logs', 'aids'),
46
46
  };
47
47
  }
48
+ // ── AID 路径(agent.md 存放在 $EVOLCLAW_HOME/AIDs/<aid>/)──
49
+ export function aidsDir() {
50
+ return path.join(resolveRoot(), 'AIDs');
51
+ }
52
+ export function aidLocalDir(aid) {
53
+ return path.join(resolveRoot(), 'AIDs', aid);
54
+ }
55
+ export function agentMdPath(aid) {
56
+ return path.join(resolveRoot(), 'AIDs', aid, 'agent.md');
57
+ }
48
58
  // ── per-agent 路径(参数化,不进 resolvePaths() 的固定 map)──
49
59
  export function agentDir(aid) {
50
60
  return path.join(resolveRoot(), 'agents', aid);
@@ -98,6 +108,31 @@ export function ensureDataDirs() {
98
108
  fs.mkdirSync(p.outboxDir, { recursive: true });
99
109
  fs.mkdirSync(p.eckDir, { recursive: true });
100
110
  fs.mkdirSync(eckDebugDir(), { recursive: true });
111
+ fs.mkdirSync(aidsDir(), { recursive: true });
112
+ migrateAgentMdFromAun();
113
+ }
114
+ /**
115
+ * One-time migration: copy agent.md from ~/.aun/AIDs/<aid>/ to $EVOLCLAW_HOME/AIDs/<aid>/
116
+ * if the new location doesn't have it yet.
117
+ */
118
+ function migrateAgentMdFromAun() {
119
+ const aunAidsDir = path.join(os.homedir(), '.aun', 'AIDs');
120
+ const ecAids = aidsDir();
121
+ if (!fs.existsSync(aunAidsDir) || aunAidsDir === ecAids)
122
+ return;
123
+ try {
124
+ for (const entry of fs.readdirSync(aunAidsDir, { withFileTypes: true })) {
125
+ if (!entry.isDirectory())
126
+ continue;
127
+ const oldMd = path.join(aunAidsDir, entry.name, 'agent.md');
128
+ const newMd = path.join(ecAids, entry.name, 'agent.md');
129
+ if (fs.existsSync(oldMd) && !fs.existsSync(newMd)) {
130
+ fs.mkdirSync(path.join(ecAids, entry.name), { recursive: true });
131
+ fs.copyFileSync(oldMd, newMd);
132
+ }
133
+ }
134
+ }
135
+ catch { /* best-effort migration */ }
101
136
  }
102
137
  // ── kits 路径(始终从包内读取,不复制到 EVOLCLAW_HOME)──
103
138
  export function kitsDir() {
@@ -12,7 +12,8 @@ export const isWindows = process.platform === 'win32';
12
12
  * C:\Users\project -> C--Users-project
13
13
  */
14
14
  export function encodePath(projectPath) {
15
- return projectPath.replace(/[/\\:]/g, '-');
15
+ const normalized = projectPath.replace(/[/\\]+$/, '');
16
+ return normalized.replace(/[/\\:]/g, '-');
16
17
  }
17
18
  /**
18
19
  * Cross-platform process liveness check.
@@ -50,3 +50,9 @@
50
50
  | 路径注册表模板 | `eck_templates/path-registry.template.md` | 路径实例模板 |
51
51
  | 索引模板 | `eck_templates/INDEX.template.md` | agent 级索引模板 |
52
52
  | 指南模板 | `eck_templates/GUIDE.template.md` | agent 级查阅指南模板 |
53
+
54
+ ## Base Agent
55
+
56
+ | 文档 | 路径 | 说明 |
57
+ |------|------|------|
58
+ | Claude Code 日志 | `baseagent/cc-logs.md` | CC 会话日志查阅(找完整对话/工具调用/注入) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "evolclaw",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "description": "Lightweight AI Agent gateway connecting Claude Agent SDK to messaging channels (Feishu, ACP) with multi-project session management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -14,7 +14,10 @@
14
14
  "!dist/experimental/",
15
15
  "kits/",
16
16
  "!kits/.kits-version",
17
- "evolclaw-install-aun.md"
17
+ "*.md",
18
+ "!CLAUDE.md",
19
+ "!DEPLOYMENT.md",
20
+ "!ONBOARDING.md"
18
21
  ],
19
22
  "scripts": {
20
23
  "dev": "tsx watch src/index.ts",