evolclaw 2.8.3 → 3.0.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 (105) hide show
  1. package/README.md +21 -12
  2. package/dist/agents/claude-runner.js +102 -38
  3. package/dist/agents/codex-runner.js +11 -14
  4. package/dist/agents/gemini-runner.js +10 -12
  5. package/dist/agents/resolve.js +134 -0
  6. package/dist/agents/templates.js +3 -3
  7. package/dist/aun/aid/agentmd.js +186 -0
  8. package/dist/aun/aid/client.js +134 -0
  9. package/dist/aun/aid/identity.js +131 -0
  10. package/dist/aun/aid/index.js +3 -0
  11. package/dist/aun/aid/types.js +1 -0
  12. package/dist/aun/aid/validation.js +21 -0
  13. package/dist/aun/msg/group.js +291 -0
  14. package/dist/aun/msg/index.js +4 -0
  15. package/dist/aun/msg/p2p.js +144 -0
  16. package/dist/aun/msg/payload-type.js +27 -0
  17. package/dist/aun/msg/upload.js +98 -0
  18. package/dist/aun/outbox.js +138 -0
  19. package/dist/aun/rpc/caller.js +42 -0
  20. package/dist/aun/rpc/connection.js +34 -0
  21. package/dist/aun/rpc/index.js +2 -0
  22. package/dist/aun/storage/download.js +29 -0
  23. package/dist/aun/storage/index.js +3 -0
  24. package/dist/aun/storage/manage.js +10 -0
  25. package/dist/aun/storage/upload.js +35 -0
  26. package/dist/channels/aun.js +1051 -288
  27. package/dist/channels/dingtalk.js +58 -5
  28. package/dist/channels/feishu.js +266 -30
  29. package/dist/channels/qqbot.js +67 -12
  30. package/dist/channels/wechat.js +61 -4
  31. package/dist/channels/wecom.js +58 -5
  32. package/dist/cli/agent.js +800 -0
  33. package/dist/cli/index.js +4253 -0
  34. package/dist/{utils → cli}/init-channel.js +211 -621
  35. package/dist/cli/init.js +178 -0
  36. package/dist/config-store.js +613 -0
  37. package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
  38. package/dist/core/channel-loader.js +162 -11
  39. package/dist/core/command-handler.js +858 -847
  40. package/dist/core/evolagent-registry.js +191 -371
  41. package/dist/core/evolagent.js +203 -234
  42. package/dist/core/interaction-router.js +52 -5
  43. package/dist/core/message/im-renderer.js +480 -0
  44. package/dist/core/message/items-formatter.js +61 -0
  45. package/dist/core/message/message-bridge.js +104 -56
  46. package/dist/core/message/message-log.js +91 -0
  47. package/dist/core/message/message-processor.js +309 -142
  48. package/dist/core/message/message-queue.js +3 -3
  49. package/dist/core/permission.js +21 -8
  50. package/dist/core/session/adapters/codex-session-file-adapter.js +24 -2
  51. package/dist/core/session/session-fs-store.js +230 -0
  52. package/dist/core/session/session-manager.js +704 -775
  53. package/dist/core/session/session-mapper.js +87 -0
  54. package/dist/core/trigger/manager.js +122 -0
  55. package/dist/core/trigger/parser.js +128 -0
  56. package/dist/core/trigger/scheduler.js +224 -0
  57. package/dist/{templates → data}/prompts.md +34 -1
  58. package/dist/index.js +431 -275
  59. package/dist/ipc.js +49 -0
  60. package/dist/paths.js +82 -9
  61. package/dist/types.js +8 -2
  62. package/dist/utils/atomic-write.js +79 -0
  63. package/dist/utils/channel-helpers.js +46 -0
  64. package/dist/utils/cross-platform.js +0 -18
  65. package/dist/utils/instance-registry.js +433 -0
  66. package/dist/utils/log-writer.js +216 -0
  67. package/dist/utils/logger.js +24 -77
  68. package/dist/utils/media-cache.js +23 -0
  69. package/dist/utils/{upgrade.js → npm-ops.js} +52 -21
  70. package/dist/utils/process-introspect.js +144 -0
  71. package/dist/utils/stats.js +192 -0
  72. package/dist/watch-msg.js +529 -0
  73. package/evolclaw-install-aun.md +114 -46
  74. package/kits/aun/meta.md +25 -0
  75. package/kits/aun/role.md +25 -0
  76. package/kits/channels/aun.md +25 -0
  77. package/kits/evolclaw/commands.md +31 -0
  78. package/kits/evolclaw/identity-tools.md +26 -0
  79. package/kits/evolclaw/self-summary.md +29 -0
  80. package/kits/evolclaw/tools.md +25 -0
  81. package/kits/templates/group.md +20 -0
  82. package/kits/templates/private.md +9 -0
  83. package/kits/templates/system-fragments/personal-context.md +3 -0
  84. package/kits/templates/system-fragments/self-intro.md +5 -0
  85. package/kits/templates/system-fragments/speaker-intro.md +5 -0
  86. package/kits/templates/system-fragments/venue-intro.md +5 -0
  87. package/package.json +7 -5
  88. package/data/evolclaw.sample.json +0 -60
  89. package/dist/channels/aun-ops.js +0 -275
  90. package/dist/cli.js +0 -2178
  91. package/dist/config.js +0 -591
  92. package/dist/core/agent-registry.js +0 -450
  93. package/dist/core/evolagent-schema.js +0 -72
  94. package/dist/core/message/stream-flusher.js +0 -238
  95. package/dist/core/message/thought-emitter.js +0 -162
  96. package/dist/core/reload-hooks.js +0 -87
  97. package/dist/prompts/templates.js +0 -122
  98. package/dist/templates/skills.md +0 -66
  99. package/dist/utils/channel-fingerprint.js +0 -59
  100. package/dist/utils/error-dict.js +0 -63
  101. package/dist/utils/format.js +0 -32
  102. package/dist/utils/init.js +0 -645
  103. package/dist/utils/migrate-project.js +0 -122
  104. package/dist/utils/reload-hooks.js +0 -87
  105. package/dist/utils/stats-collector.js +0 -99
@@ -1,6 +1,6 @@
1
1
  import path from 'path';
2
2
  import { logger } from '../../utils/logger.js';
3
- const DEFAULT_AGENT_NAME = '[default]';
3
+ const DEFAULT_AGENT_NAME = '<unknown>';
4
4
  export class MessageQueue {
5
5
  queues = new Map();
6
6
  processing = new Set();
@@ -92,7 +92,7 @@ export class MessageQueue {
92
92
  // 单聊:保留中断行为
93
93
  logger.debug(`[Queue] ${queueKey} is processing, triggering interrupt`);
94
94
  this.eventBus?.publish({
95
- type: 'message:interrupted',
95
+ type: 'task:interrupted',
96
96
  sessionId: sessionKey,
97
97
  reason: 'new_message',
98
98
  agentName: this.processingAgent.get(queueKey),
@@ -276,7 +276,7 @@ export class MessageQueue {
276
276
  const sessionKey = this.currentSessionKey.split('::')[0];
277
277
  logger.info(`[Queue] Recalled active message ${messageId}, interrupting session ${sessionKey}`);
278
278
  this.eventBus?.publish({
279
- type: 'message:interrupted',
279
+ type: 'task:interrupted',
280
280
  sessionId: sessionKey,
281
281
  reason: 'recalled',
282
282
  agentName: this.processingAgent.get(this.currentSessionKey),
@@ -1,4 +1,6 @@
1
1
  import path from 'path';
2
+ import { renderActionAsText } from './interaction-router.js';
3
+ import { buildEnvelope, sendInteractionPayload } from './message/message-processor.js';
2
4
  // 危险命令黑名单(正则表达式)
3
5
  const DANGEROUS_PATTERNS = [
4
6
  // Unix
@@ -192,29 +194,40 @@ export class PermissionGateway {
192
194
  },
193
195
  channelId: context?.channelId || '',
194
196
  sessionId,
197
+ initiatorId: context?.userId,
198
+ fallback: { command: 'perm' },
195
199
  };
196
- // 尝试富交互
200
+ // 尝试富交互(走统一 adapter.send 入口)
197
201
  let interactionSent = false;
198
- if (context?.adapter?.sendInteraction && context.channelId) {
202
+ if (context?.adapter && context.channelId) {
199
203
  try {
200
- const result = await context.adapter.sendInteraction(context.channelId, interaction, context.replyContext);
204
+ const envelope = buildEnvelope({
205
+ taskId: context.taskId,
206
+ channel: context.channel ?? context.adapter.channelName,
207
+ channelId: context.channelId,
208
+ agentName: context.agentName,
209
+ chatmode: context.chatmode,
210
+ replyContext: context.replyContext,
211
+ });
212
+ const fallbackText = `🔐 权限请求 - ${toolName}\n${displaySummary}${reasonLine}\n回复 /perm allow 同意 / /perm always 始终允许 / /perm deny 拒绝`;
213
+ const result = await sendInteractionPayload(context.adapter, envelope, interaction, fallbackText, context.replyContext);
201
214
  interactionSent = !!result;
202
215
  }
203
216
  catch (err) {
204
- // sendInteraction 失败,fallback 到文本
217
+ // sendInteractionPayload 已内部捕获,但保险起见再 try/catch
205
218
  }
206
219
  }
207
220
  // fallback 到文本
208
221
  if (!interactionSent) {
209
- await sendPrompt(`🔐 权限请求\n工具:${toolName}\n操作:${displaySummary}${reasonLine}\n\n回复 /perm allow 本次允许 | always 始终允许 | deny 拒绝`);
222
+ await sendPrompt(renderActionAsText(interaction));
210
223
  }
211
224
  return new Promise((resolve) => {
212
225
  this.pending.set(requestId, { sessionId, toolName, resolve, timer: setTimeout(() => { }, 0) });
213
- // 如果发了交互卡片,同时注册到 InteractionRouter
214
- if (interactionSent && context?.interactionRouter) {
226
+ // 注册到 InteractionRouter(卡片和文本降级都注册,统一路由)
227
+ if (context?.interactionRouter) {
215
228
  context.interactionRouter.register(requestId, sessionId, (action) => {
216
229
  this.resolvePermission(sessionId, requestId, action);
217
- });
230
+ }, { initiatorId: context?.userId, fallbackCommand: 'perm' });
218
231
  }
219
232
  });
220
233
  }
@@ -3,12 +3,31 @@
3
3
  *
4
4
  * Reads Codex thread data from ~/.codex/state_*.sqlite (read-only)
5
5
  * and Codex rollout JSONL files for detailed session info.
6
+ *
7
+ * `node:sqlite` 是 Node 22.5+ 的实验性内置模块。低版本 Node 上懒加载会失败,
8
+ * adapter 自动降级到只读 rollout JSONL —— `checkExists/getFileInfo/readFirstMessage/
9
+ * readLastUserMessage` 仍可工作;`scanCliSessions/listSdkSessions` 因依赖 DB 索引
10
+ * 会返回空数组。
6
11
  */
7
- import { DatabaseSync } from 'node:sqlite';
12
+ import { createRequire } from 'module';
8
13
  import { logger } from '../../../utils/logger.js';
9
14
  import path from 'path';
10
15
  import fs from 'fs';
11
16
  import os from 'os';
17
+ const requireFromHere = createRequire(import.meta.url);
18
+ let sqliteModule; // undefined = not tried, null = unavailable
19
+ function loadSqlite() {
20
+ if (sqliteModule !== undefined)
21
+ return sqliteModule;
22
+ try {
23
+ sqliteModule = requireFromHere('node:sqlite');
24
+ }
25
+ catch (e) {
26
+ logger.warn(`[CodexAdapter] node:sqlite unavailable (Node < 22.5?): ${e?.message || e}. Codex session listing falls back to rollout JSONL.`);
27
+ sqliteModule = null;
28
+ }
29
+ return sqliteModule;
30
+ }
12
31
  export class CodexSessionFileAdapter {
13
32
  agentId = 'codex';
14
33
  db = null;
@@ -39,11 +58,14 @@ export class CodexSessionFileAdapter {
39
58
  if (this.dbInitialized)
40
59
  return this.db;
41
60
  this.dbInitialized = true;
61
+ const sqlite = loadSqlite();
62
+ if (!sqlite)
63
+ return null;
42
64
  const dbPath = this.resolveStateDbPath();
43
65
  if (!dbPath)
44
66
  return null;
45
67
  try {
46
- this.db = new DatabaseSync(dbPath, { readOnly: true });
68
+ this.db = new sqlite.DatabaseSync(dbPath, { readOnly: true });
47
69
  logger.debug(`[CodexAdapter] Opened state DB: ${dbPath}`);
48
70
  }
49
71
  catch (error) {
@@ -0,0 +1,230 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ // 文件系统非法字符(Windows 最严格):< > : " / \ | ? *
4
+ // 还有控制字符 0x00-0x1F。我们把这些字符编码为 %XX(hex 大写)。
5
+ // `%` 本身也要转义为 %25,保证可逆。
6
+ const UNSAFE_CHARS_RE = /[<>:"/\\|?*\x00-\x1F%]/g;
7
+ function encodeSegment(s) {
8
+ return s.replace(UNSAFE_CHARS_RE, ch => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'));
9
+ }
10
+ function decodeSegment(s) {
11
+ return s.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
12
+ }
13
+ /**
14
+ * 计算 chat 目录的完整路径。
15
+ * - AUN:sessionsDir/aun/<urlEncode(selfId)>/<urlEncode(channelId)>/
16
+ * - 其它:sessionsDir/<channelType>/<urlEncode(channelId)>/
17
+ *
18
+ * 注:channelType 自身不编码(限定枚举值,不含非法字符)。
19
+ */
20
+ export function chatDirPath(sessionsDir, channelType, channelId, selfId) {
21
+ if (channelType === 'aun') {
22
+ const self = selfId || '_unknown';
23
+ return path.join(sessionsDir, 'aun', encodeSegment(self), encodeSegment(channelId));
24
+ }
25
+ return path.join(sessionsDir, channelType, encodeSegment(channelId));
26
+ }
27
+ /** 解码目录段(用于扫描时把目录名还原为原始 channelId/selfId) */
28
+ export function decodeDirSegment(seg) {
29
+ return decodeSegment(seg);
30
+ }
31
+ export function generateSessionId(now) {
32
+ const ts = now ?? Date.now();
33
+ const d = new Date(ts);
34
+ const yyyy = d.getFullYear();
35
+ const mm = String(d.getMonth() + 1).padStart(2, '0');
36
+ const dd = String(d.getDate()).padStart(2, '0');
37
+ return `meta_${yyyy}${mm}${dd}_${ts}`;
38
+ }
39
+ export function formatTimestamp(epochMs) {
40
+ const d = new Date(epochMs);
41
+ const yyyy = d.getFullYear();
42
+ const mo = String(d.getMonth() + 1).padStart(2, '0');
43
+ const dd = String(d.getDate()).padStart(2, '0');
44
+ const hh = String(d.getHours()).padStart(2, '0');
45
+ const mi = String(d.getMinutes()).padStart(2, '0');
46
+ const ss = String(d.getSeconds()).padStart(2, '0');
47
+ return `${yyyy}-${mo}-${dd} ${hh}:${mi}:${ss}`;
48
+ }
49
+ export function atomicWriteJson(filePath, data) {
50
+ const tmpPath = filePath + '.tmp';
51
+ const content = JSON.stringify(data, null, 2) + '\n';
52
+ const fd = fs.openSync(tmpPath, 'w');
53
+ fs.writeSync(fd, content);
54
+ fs.fsyncSync(fd);
55
+ fs.closeSync(fd);
56
+ try {
57
+ fs.unlinkSync(filePath);
58
+ }
59
+ catch (e) {
60
+ if (e.code !== 'ENOENT')
61
+ throw e;
62
+ }
63
+ fs.renameSync(tmpPath, filePath);
64
+ }
65
+ export function appendJsonl(filePath, record) {
66
+ const line = JSON.stringify(record) + '\n';
67
+ const fd = fs.openSync(filePath, 'a');
68
+ fs.writeSync(fd, line);
69
+ fs.fsyncSync(fd);
70
+ fs.closeSync(fd);
71
+ }
72
+ export function readJsonFile(filePath) {
73
+ try {
74
+ const content = fs.readFileSync(filePath, 'utf-8');
75
+ return JSON.parse(content);
76
+ }
77
+ catch (e) {
78
+ if (e.code === 'ENOENT')
79
+ return undefined;
80
+ if (e instanceof SyntaxError)
81
+ return undefined;
82
+ throw e;
83
+ }
84
+ }
85
+ export function readLastJsonlLine(filePath) {
86
+ try {
87
+ const content = fs.readFileSync(filePath, 'utf-8');
88
+ const lines = content.trimEnd().split('\n');
89
+ for (let i = lines.length - 1; i >= 0; i--) {
90
+ const line = lines[i].trim();
91
+ if (!line)
92
+ continue;
93
+ try {
94
+ return JSON.parse(line);
95
+ }
96
+ catch {
97
+ continue;
98
+ }
99
+ }
100
+ return undefined;
101
+ }
102
+ catch (e) {
103
+ if (e.code === 'ENOENT')
104
+ return undefined;
105
+ throw e;
106
+ }
107
+ }
108
+ export function readAllJsonlLines(filePath) {
109
+ try {
110
+ const content = fs.readFileSync(filePath, 'utf-8');
111
+ const results = [];
112
+ for (const line of content.split('\n')) {
113
+ const trimmed = line.trim();
114
+ if (!trimmed)
115
+ continue;
116
+ try {
117
+ results.push(JSON.parse(trimmed));
118
+ }
119
+ catch { /* skip corrupt line */ }
120
+ }
121
+ return results;
122
+ }
123
+ catch (e) {
124
+ if (e.code === 'ENOENT')
125
+ return [];
126
+ throw e;
127
+ }
128
+ }
129
+ /**
130
+ * 扫描所有 chat 目录。
131
+ * 顶层是 channelType 目录;aun 下面再有一层 selfId 目录。
132
+ * 返回每个 chat 的:channelType、selfId(仅 aun 有)、channelId、dirPath。
133
+ */
134
+ export function scanChatDirs(sessionsDir) {
135
+ const results = [];
136
+ let typeEntries;
137
+ try {
138
+ typeEntries = fs.readdirSync(sessionsDir, { withFileTypes: true });
139
+ }
140
+ catch (e) {
141
+ if (e.code === 'ENOENT')
142
+ return [];
143
+ throw e;
144
+ }
145
+ for (const typeEntry of typeEntries) {
146
+ if (!typeEntry.isDirectory())
147
+ continue;
148
+ const channelType = typeEntry.name;
149
+ const typeDir = path.join(sessionsDir, channelType);
150
+ if (channelType === 'aun') {
151
+ // aun 下还有一层 selfId
152
+ let selfEntries;
153
+ try {
154
+ selfEntries = fs.readdirSync(typeDir, { withFileTypes: true });
155
+ }
156
+ catch {
157
+ continue;
158
+ }
159
+ for (const selfEntry of selfEntries) {
160
+ if (!selfEntry.isDirectory())
161
+ continue;
162
+ const selfDir = path.join(typeDir, selfEntry.name);
163
+ let chatEntries;
164
+ try {
165
+ chatEntries = fs.readdirSync(selfDir, { withFileTypes: true });
166
+ }
167
+ catch {
168
+ continue;
169
+ }
170
+ for (const chatEntry of chatEntries) {
171
+ if (!chatEntry.isDirectory())
172
+ continue;
173
+ results.push({
174
+ channelType,
175
+ selfId: decodeSegment(selfEntry.name),
176
+ channelId: decodeSegment(chatEntry.name),
177
+ dirPath: path.join(selfDir, chatEntry.name),
178
+ });
179
+ }
180
+ }
181
+ }
182
+ else {
183
+ // 通用 channel:sessionsDir/{channelType}/{encodedChannelId}/
184
+ let chatEntries;
185
+ try {
186
+ chatEntries = fs.readdirSync(typeDir, { withFileTypes: true });
187
+ }
188
+ catch {
189
+ continue;
190
+ }
191
+ for (const chatEntry of chatEntries) {
192
+ if (!chatEntry.isDirectory())
193
+ continue;
194
+ results.push({
195
+ channelType,
196
+ selfId: null,
197
+ channelId: decodeSegment(chatEntry.name),
198
+ dirPath: path.join(typeDir, chatEntry.name),
199
+ });
200
+ }
201
+ }
202
+ }
203
+ return results;
204
+ }
205
+ export function scanMetaFiles(chatDir) {
206
+ try {
207
+ const entries = fs.readdirSync(chatDir);
208
+ return entries
209
+ .filter(f => f.startsWith('meta_') && f.endsWith('.jsonl'))
210
+ .sort();
211
+ }
212
+ catch (e) {
213
+ if (e.code === 'ENOENT')
214
+ return [];
215
+ throw e;
216
+ }
217
+ }
218
+ export function ensureChatDir(sessionsDir, channelType, channelId, selfId) {
219
+ const dir = chatDirPath(sessionsDir, channelType, channelId, selfId);
220
+ fs.mkdirSync(dir, { recursive: true });
221
+ fs.mkdirSync(path.join(dir, '_threads'), { recursive: true });
222
+ fs.mkdirSync(path.join(dir, '_trash'), { recursive: true });
223
+ return dir;
224
+ }
225
+ export function readThreadIndex(chatDir) {
226
+ return readJsonFile(path.join(chatDir, '_threads', 'thread-index.json')) || {};
227
+ }
228
+ export function writeThreadIndex(chatDir, index) {
229
+ atomicWriteJson(path.join(chatDir, '_threads', 'thread-index.json'), index);
230
+ }