evolclaw 3.1.4 → 3.1.6
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/CHANGELOG.md +60 -0
- package/dist/agents/claude-runner.js +398 -161
- package/dist/agents/kit-renderer.js +191 -25
- package/dist/aun/aid/agentmd.js +75 -103
- package/dist/aun/aid/client.js +1 -29
- package/dist/aun/aid/identity.js +105 -64
- package/dist/aun/aid/index.js +2 -1
- package/dist/aun/aid/store.js +74 -0
- package/dist/aun/msg/group.js +2 -2
- package/dist/aun/msg/p2p.js +26 -2
- package/dist/aun/rpc/connection.js +23 -30
- package/dist/channels/aun.js +174 -99
- package/dist/channels/dingtalk.js +2 -1
- package/dist/channels/feishu.js +301 -199
- package/dist/channels/qqbot.js +2 -1
- package/dist/channels/wechat.js +2 -1
- package/dist/channels/wecom.js +2 -1
- package/dist/cli/agent.js +21 -16
- package/dist/cli/bench.js +41 -28
- package/dist/cli/help.js +8 -0
- package/dist/cli/index.js +176 -87
- package/dist/cli/init-channel.js +5 -1
- package/dist/cli/init.js +37 -21
- package/dist/cli/link-rules.js +1 -7
- package/dist/cli/model.js +549 -0
- package/dist/cli/net-check.js +133 -50
- package/dist/cli/watch-msg.js +7 -7
- package/dist/cli/watch-web/debug-log.js +18 -0
- package/dist/cli/watch-web/server.js +306 -0
- package/dist/cli/watch-web/sources/aid.js +63 -0
- package/dist/cli/watch-web/sources/msg.js +70 -0
- package/dist/cli/watch-web/sources/session.js +638 -0
- package/dist/cli/watch-web/sources/types.js +10 -0
- package/dist/cli/watch-web/static/app.js +546 -0
- package/dist/cli/watch-web/static/index.html +54 -0
- package/dist/cli/watch-web/static/style.css +247 -0
- package/dist/config-store.js +1 -22
- package/dist/core/channel-loader.js +7 -4
- package/dist/core/command-handler.js +261 -133
- package/dist/core/evolagent-registry.js +1 -1
- package/dist/core/evolagent.js +4 -22
- package/dist/core/interaction-router.js +59 -0
- package/dist/core/message/im-renderer.js +9 -20
- package/dist/core/message/message-bridge.js +13 -9
- package/dist/core/message/message-log.js +2 -2
- package/dist/core/message/message-processor.js +211 -123
- package/dist/core/message/stream-idle-monitor.js +21 -0
- package/dist/core/model/model-catalog.js +215 -0
- package/dist/core/model/model-scope.js +250 -0
- package/dist/core/relation/peer-identity.js +58 -55
- package/dist/core/relation/peer-key.js +16 -0
- package/dist/core/session/session-fs-store.js +34 -55
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +308 -251
- package/dist/core/session/session-mapper.js +9 -4
- package/dist/core/trigger/manager.js +3 -3
- package/dist/core/trigger/parser.js +4 -4
- package/dist/core/trigger/scheduler.js +22 -7
- package/dist/index.js +61 -7
- package/dist/ipc.js +23 -1
- package/dist/utils/error-utils.js +6 -0
- package/dist/utils/process-introspect.js +7 -5
- package/kits/docs/GUIDE.md +2 -2
- package/kits/docs/INDEX.md +8 -8
- package/kits/docs/channels/aun.md +56 -17
- package/kits/docs/channels/feishu.md +41 -12
- package/kits/docs/context-assembly.md +182 -0
- package/kits/docs/evolclaw/INDEX.md +43 -0
- package/kits/docs/evolclaw/agent.md +49 -0
- package/kits/docs/evolclaw/aid.md +49 -0
- package/kits/docs/evolclaw/ctl.md +46 -0
- package/kits/docs/evolclaw/group.md +89 -0
- package/kits/docs/evolclaw/model.md +51 -0
- package/kits/docs/evolclaw/msg.md +91 -0
- package/kits/docs/evolclaw/rpc.md +35 -0
- package/kits/docs/evolclaw/storage.md +49 -0
- package/kits/docs/venues/aun-group.md +10 -0
- package/kits/docs/venues/aun-private.md +10 -0
- package/kits/docs/venues/client-desktop.md +10 -0
- package/kits/docs/venues/client-mobile.md +10 -0
- package/kits/docs/venues/feishu-group.md +13 -0
- package/kits/docs/venues/feishu-private.md +9 -0
- package/kits/docs/venues/group.md +23 -0
- package/kits/docs/venues/private.md +10 -0
- package/kits/eck_manifest.json +81 -36
- package/kits/rules/01-overview.md +20 -10
- package/kits/rules/06-channel.md +34 -27
- package/kits/templates/system-fragments/baseagent.md +7 -1
- package/kits/templates/system-fragments/channel.md +7 -5
- package/kits/templates/system-fragments/commands.md +19 -0
- package/kits/templates/system-fragments/session.md +19 -3
- package/kits/templates/system-fragments/venue.md +24 -0
- package/package.json +10 -5
- package/dist/aun/aid/lifecycle-log.js +0 -33
- package/dist/utils/aid-lifecycle-log.js +0 -33
- package/kits/docs/evolclaw/AGENT_CMD.md +0 -31
- package/kits/docs/evolclaw/MSG_GROUP.md +0 -30
- package/kits/docs/evolclaw/MSG_PRIVATE.md +0 -72
- package/kits/docs/evolclaw/tools.md +0 -25
|
@@ -7,6 +7,9 @@ const PARAM_DESCRIPTIONS = {
|
|
|
7
7
|
EVOLCLAW_HOME: '用户数据根目录',
|
|
8
8
|
PACKAGE_ROOT: 'evolclaw 包根目录',
|
|
9
9
|
CURRENT_PROJECT: '当前项目完整路径',
|
|
10
|
+
PERSONAL_DIR: '当前 agent 个人数据目录',
|
|
11
|
+
RELATIONS_DIR: '当前 agent 关系数据目录',
|
|
12
|
+
VENUES_DIR: '当前 agent 环境数据目录',
|
|
10
13
|
selfAid: '当前 agent 的 AID',
|
|
11
14
|
selfName: '当前 agent 的显示名',
|
|
12
15
|
hasPersona: '是否有 persona 内容',
|
|
@@ -16,21 +19,35 @@ const PARAM_DESCRIPTIONS = {
|
|
|
16
19
|
peerName: '对端显示名',
|
|
17
20
|
peerRole: '对端角色(owner/admin/guest/anonymous)',
|
|
18
21
|
peerType: '对端类型(human/agent)',
|
|
22
|
+
sameDevice: '对端与本端同一物理设备(E2EE 消息 proximity,仅加密消息有值)',
|
|
23
|
+
sameNetwork: '对端与本端在同一网络内',
|
|
24
|
+
sameEgressIp: '对端与本端共享同一出口 IP',
|
|
19
25
|
groupId: '群组 ID(群聊时)',
|
|
20
26
|
chatType: '聊天类型(private=私聊 / group=群聊 / null=本地开发)',
|
|
21
27
|
channel: '渠道类型(aun/feishu/wechat/dingtalk/qqbot/wecom)',
|
|
22
28
|
venueUid: '场所唯一标识(预留)',
|
|
29
|
+
dispatch: '群分发模式(mention=被@才响应 / broadcast=所有消息都响应)',
|
|
30
|
+
clientType: '客户端类型(desktop/web/mobile)',
|
|
31
|
+
permissionMode: '权限模式(auto/bypass/request/edit/plan/noask/readonly)',
|
|
23
32
|
capabilities: '当前渠道支持的能力列表',
|
|
24
33
|
project: '当前项目目录名',
|
|
25
34
|
sessionId: 'evolclaw 会话 ID',
|
|
26
35
|
sessionName: '会话名称',
|
|
36
|
+
sessionKey: '会话路由键(channelType#urlEncode(channelId)#urlEncode(threadId))',
|
|
27
37
|
sessionCreatedAt: '会话创建时间(ISO)',
|
|
38
|
+
timezone: 'IANA 时区名(把 ISO 时间戳转本地时间用,如 Asia/Shanghai)',
|
|
39
|
+
tzOffset: '当前 UTC 偏移(如 +08:00)',
|
|
40
|
+
osInfo: '操作系统及版本(如 Windows 11 Pro (win32 10.0.26200))',
|
|
28
41
|
threadId: '话题 ID(多话题路由时)',
|
|
29
42
|
chatMode: '会话模式(interactive=同步交互 / proactive=主动推送)',
|
|
30
43
|
readonly: '是否只读模式',
|
|
44
|
+
evolclawMode: 'evolclaw 运行模式(dev=源码仓库可直接修改 | install=全局安装包只读)',
|
|
31
45
|
baseAgent: 'base agent 规范值(claude/codex/gemini/hermes)',
|
|
32
46
|
baseAgentName: 'base agent 显示名',
|
|
33
|
-
baseAgentModel: 'base agent
|
|
47
|
+
baseAgentModel: 'base agent 引擎底座模型(evolclaw 作用域无配置时的兜底)',
|
|
48
|
+
effectiveModel: '当前实际生效模型(关系级 > agent级 > 全局 优先级解析结果)',
|
|
49
|
+
modelFallbackActive: 'evolclaw 配置的模型不可用,当前正在使用降级模型',
|
|
50
|
+
modelFallbackModel: '当前降级使用的 base agent 模型名',
|
|
34
51
|
agentSessionId: 'base agent 会话 ID',
|
|
35
52
|
};
|
|
36
53
|
function buildPathMappings(vars) {
|
|
@@ -47,6 +64,9 @@ function buildPathMappings(vars) {
|
|
|
47
64
|
{ prefix: pkgRoot, alias: '$PACKAGE_ROOT' },
|
|
48
65
|
];
|
|
49
66
|
if (selfAid) {
|
|
67
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'personal'), alias: '$PERSONAL_DIR' });
|
|
68
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'relations'), alias: '$RELATIONS_DIR' });
|
|
69
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'venues'), alias: '$VENUES_DIR' });
|
|
50
70
|
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid), alias: '$AGENT_DIR' });
|
|
51
71
|
}
|
|
52
72
|
mappings.push({ prefix: evolHome, alias: '$EVOLCLAW_HOME' });
|
|
@@ -90,33 +110,75 @@ export function renderKitSections(ctx) {
|
|
|
90
110
|
const fileParts = [];
|
|
91
111
|
const fragmentParts = [];
|
|
92
112
|
const pathMappings = buildPathMappings(ctx.vars);
|
|
113
|
+
const diagnostics = [];
|
|
93
114
|
for (const section of sections) {
|
|
94
|
-
|
|
115
|
+
const rawPath = section.type === 'file' ? (section.file ?? '') : (section.path ?? '');
|
|
116
|
+
const diag = {
|
|
117
|
+
id: section.id,
|
|
118
|
+
description: section.description,
|
|
119
|
+
type: section.type,
|
|
120
|
+
rawPath,
|
|
121
|
+
pattern: section.pattern,
|
|
122
|
+
needsInjection: section.needsInjection,
|
|
123
|
+
when: section.when,
|
|
124
|
+
enabled: section.enabled !== false,
|
|
125
|
+
whenPassed: false,
|
|
126
|
+
resolvedPath: null,
|
|
127
|
+
resolveStatus: 'no-path',
|
|
128
|
+
fileCount: 0,
|
|
129
|
+
used: false,
|
|
130
|
+
injected: false,
|
|
131
|
+
};
|
|
132
|
+
if (section.enabled === false) {
|
|
133
|
+
diag.resolveStatus = 'skipped-disabled';
|
|
134
|
+
diagnostics.push(diag);
|
|
95
135
|
continue;
|
|
96
|
-
|
|
136
|
+
}
|
|
137
|
+
diag.whenPassed = evaluateWhen(section.when, ctx.vars);
|
|
138
|
+
if (!diag.whenPassed) {
|
|
139
|
+
diag.resolveStatus = 'skipped-when';
|
|
140
|
+
diagnostics.push(diag);
|
|
97
141
|
continue;
|
|
142
|
+
}
|
|
143
|
+
if (rawPath) {
|
|
144
|
+
const resolveResult = resolvePathWithDiag(rawPath, ctx);
|
|
145
|
+
diag.resolvedPath = resolveResult.resolved;
|
|
146
|
+
diag.resolveStatus = resolveResult.status;
|
|
147
|
+
if (resolveResult.unresolvedTokens.length > 0)
|
|
148
|
+
diag.unresolvedTokens = resolveResult.unresolvedTokens;
|
|
149
|
+
}
|
|
98
150
|
const files = loadSectionFiles(section, ctx);
|
|
99
|
-
|
|
151
|
+
diag.fileCount = files.length;
|
|
152
|
+
if (files.length === 0) {
|
|
153
|
+
diagnostics.push(diag);
|
|
100
154
|
continue;
|
|
155
|
+
}
|
|
156
|
+
let anyUsed = false;
|
|
101
157
|
for (const [filePath, rawContent] of files) {
|
|
102
158
|
const content = section.needsInjection ? renderTemplate(rawContent, ctx.vars) : rawContent;
|
|
103
|
-
if (!content.trim())
|
|
159
|
+
if (!content.trim()) {
|
|
160
|
+
diag.emptyContent = true;
|
|
104
161
|
continue;
|
|
162
|
+
}
|
|
105
163
|
const label = section.description ? `${section.id} — ${section.description}` : section.id;
|
|
106
164
|
const displayPath = shortenPath(filePath, pathMappings);
|
|
107
165
|
const part = `Contenu de ${displayPath} (${label}):\n\n${content.trimEnd()}`;
|
|
108
166
|
fileParts.push(part);
|
|
167
|
+
anyUsed = true;
|
|
109
168
|
if (section.needsInjection) {
|
|
110
169
|
fragmentParts.push(part);
|
|
170
|
+
diag.injected = true;
|
|
111
171
|
}
|
|
112
172
|
}
|
|
173
|
+
diag.used = anyUsed;
|
|
174
|
+
diagnostics.push(diag);
|
|
113
175
|
}
|
|
114
|
-
if (fileParts.length === 0)
|
|
115
|
-
return '';
|
|
116
176
|
const body = fileParts.join('\n\n');
|
|
117
|
-
const output =
|
|
177
|
+
const output = fileParts.length > 0
|
|
178
|
+
? `<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>`
|
|
179
|
+
: '';
|
|
118
180
|
const fragmentsOutput = fragmentParts.length > 0 ? fragmentParts.join('\n\n') : '';
|
|
119
|
-
writeDebugFiles(ctx, output, fragmentsOutput);
|
|
181
|
+
writeDebugFiles(ctx, output, fragmentsOutput, diagnostics);
|
|
120
182
|
return output;
|
|
121
183
|
}
|
|
122
184
|
export function cleanEckDebug() {
|
|
@@ -212,23 +274,38 @@ function loadDirectorySection(dirPath, pattern, ctx) {
|
|
|
212
274
|
}
|
|
213
275
|
// ── Path resolution ──
|
|
214
276
|
function resolvePath(rawPath, ctx) {
|
|
215
|
-
|
|
277
|
+
const r = resolvePathWithDiag(rawPath, ctx);
|
|
278
|
+
return r.status === 'ok' ? r.resolved : null;
|
|
279
|
+
}
|
|
280
|
+
function resolvePathWithDiag(rawPath, ctx) {
|
|
281
|
+
const unresolved = [];
|
|
282
|
+
let resolved = rawPath.replace(/\$([A-Z_]+)/g, (_m, name) => {
|
|
216
283
|
const val = ctx.vars[name];
|
|
217
|
-
if (val === undefined || val === null || val === false || val === '')
|
|
284
|
+
if (val === undefined || val === null || val === false || val === '') {
|
|
285
|
+
unresolved.push(`$${name}`);
|
|
218
286
|
return '';
|
|
287
|
+
}
|
|
219
288
|
return String(val);
|
|
220
289
|
});
|
|
221
|
-
resolved = resolved.replace(/\{\{(\w+)\}\}/g, (
|
|
290
|
+
resolved = resolved.replace(/\{\{(\w+)\}\}/g, (_m, key) => {
|
|
222
291
|
const val = ctx.vars[key];
|
|
223
|
-
if (val === undefined || val === null || val === false || val === '')
|
|
292
|
+
if (val === undefined || val === null || val === false || val === '') {
|
|
293
|
+
unresolved.push(`{{${key}}}`);
|
|
224
294
|
return '';
|
|
295
|
+
}
|
|
225
296
|
return String(val);
|
|
226
297
|
});
|
|
227
|
-
if (!resolved || resolved.includes('$') || resolved.includes('{{'))
|
|
228
|
-
return null;
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
298
|
+
if (!resolved || resolved.includes('$') || resolved.includes('{{')) {
|
|
299
|
+
return { resolved: resolved || null, status: 'unresolved-vars', unresolvedTokens: unresolved };
|
|
300
|
+
}
|
|
301
|
+
if (unresolved.length > 0) {
|
|
302
|
+
// 占位符是非必需变量,但有的解析为空——视为未解析
|
|
303
|
+
return { resolved, status: 'unresolved-vars', unresolvedTokens: unresolved };
|
|
304
|
+
}
|
|
305
|
+
if (!fs.existsSync(resolved)) {
|
|
306
|
+
return { resolved, status: 'not-exist', unresolvedTokens: unresolved };
|
|
307
|
+
}
|
|
308
|
+
return { resolved, status: 'ok', unresolvedTokens: unresolved };
|
|
232
309
|
}
|
|
233
310
|
// CHUNK_CONTINUE_5
|
|
234
311
|
// ── Directory reading ──
|
|
@@ -260,10 +337,17 @@ function evaluateWhen(when, vars) {
|
|
|
260
337
|
return true;
|
|
261
338
|
if (when.var !== undefined) {
|
|
262
339
|
const val = vars[when.var];
|
|
263
|
-
if (when.eq !== undefined)
|
|
340
|
+
if (when.eq !== undefined) {
|
|
341
|
+
// 把 undefined 视作 null 的等价物,便于 manifest 用 eq:null/neq:null 表达"未注入"
|
|
342
|
+
if (when.eq === null)
|
|
343
|
+
return val === null || val === undefined;
|
|
264
344
|
return val === when.eq;
|
|
265
|
-
|
|
345
|
+
}
|
|
346
|
+
if (when.neq !== undefined) {
|
|
347
|
+
if (when.neq === null)
|
|
348
|
+
return val !== null && val !== undefined;
|
|
266
349
|
return val !== when.neq;
|
|
350
|
+
}
|
|
267
351
|
if (when.in !== undefined)
|
|
268
352
|
return when.in.includes(val);
|
|
269
353
|
if (when.nin !== undefined)
|
|
@@ -281,9 +365,10 @@ function isTruthy(val) {
|
|
|
281
365
|
// CHUNK_CONTINUE_6
|
|
282
366
|
// ── Template rendering ──
|
|
283
367
|
function resolveConditions(template, vars) {
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
|
|
368
|
+
// 只匹配**最内层** {{?...}}...{{/}} 块:body 内不允许再出现 {{? ,
|
|
369
|
+
// 否则非贪婪 ([^]*?) 会匹配到嵌套内层的 {{/}},导致外层提前闭合、残留多余 {{/}}。
|
|
370
|
+
// 逐字符负向前瞻 (?!\{\{\?) 排除嵌套起始,配合 do/while 由内向外逐层消解。
|
|
371
|
+
const inner = /\{\{\?(\w+)(?:(!=|=)([^}]*))?\}\}((?:(?!\{\{\?)[^])*?)\{\{\/\}\}/;
|
|
287
372
|
let result = template;
|
|
288
373
|
let prev;
|
|
289
374
|
do {
|
|
@@ -321,7 +406,7 @@ function getSessionCache(sessionId) {
|
|
|
321
406
|
return cache;
|
|
322
407
|
}
|
|
323
408
|
// ── Debug output ──
|
|
324
|
-
function writeDebugFiles(ctx, output, fragmentsOutput) {
|
|
409
|
+
function writeDebugFiles(ctx, output, fragmentsOutput, diagnostics) {
|
|
325
410
|
const now = new Date();
|
|
326
411
|
const ts = now.toISOString().replace(/[T:.]/g, '-').slice(0, 19);
|
|
327
412
|
const dir = eckDebugDir();
|
|
@@ -337,8 +422,89 @@ function writeDebugFiles(ctx, output, fragmentsOutput) {
|
|
|
337
422
|
})),
|
|
338
423
|
};
|
|
339
424
|
fs.writeFile(path.join(dir, `vars-${ts}.json`), JSON.stringify(varsData, null, 2), () => { });
|
|
340
|
-
|
|
425
|
+
if (output)
|
|
426
|
+
fs.writeFile(path.join(dir, `context-${ts}.md`), output, () => { });
|
|
341
427
|
if (fragmentsOutput) {
|
|
342
428
|
fs.writeFile(path.join(dir, `fragments-${ts}.md`), fragmentsOutput, () => { });
|
|
343
429
|
}
|
|
430
|
+
fs.writeFile(path.join(dir, `manifest-${ts}.md`), formatManifestDiagnostics(ctx, diagnostics), () => { });
|
|
431
|
+
}
|
|
432
|
+
function formatManifestDiagnostics(ctx, diagnostics) {
|
|
433
|
+
const STATUS_ICON = {
|
|
434
|
+
'ok': 'OK',
|
|
435
|
+
'unresolved-vars': 'UNRESOLVED-VARS',
|
|
436
|
+
'not-exist': 'NOT-EXIST',
|
|
437
|
+
'skipped-disabled': 'SKIPPED(disabled)',
|
|
438
|
+
'skipped-when': 'SKIPPED(when)',
|
|
439
|
+
'no-path': 'NO-PATH',
|
|
440
|
+
};
|
|
441
|
+
const used = diagnostics.filter(d => d.used).length;
|
|
442
|
+
const skippedWhen = diagnostics.filter(d => d.resolveStatus === 'skipped-when').length;
|
|
443
|
+
const errors = diagnostics.filter(d => d.resolveStatus === 'unresolved-vars' || d.resolveStatus === 'not-exist').length;
|
|
444
|
+
const lines = [];
|
|
445
|
+
lines.push(`# ECK Manifest Diagnostics`);
|
|
446
|
+
lines.push('');
|
|
447
|
+
lines.push(`- timestamp: ${new Date().toISOString()}`);
|
|
448
|
+
lines.push(`- sessionId: ${ctx.sessionId}`);
|
|
449
|
+
lines.push(`- sections total: ${diagnostics.length}`);
|
|
450
|
+
lines.push(`- sections used: ${used}`);
|
|
451
|
+
lines.push(`- sections skipped (when=false): ${skippedWhen}`);
|
|
452
|
+
lines.push(`- sections with errors (unresolved-vars/not-exist): ${errors}`);
|
|
453
|
+
lines.push('');
|
|
454
|
+
if (errors > 0) {
|
|
455
|
+
lines.push(`## Errors`);
|
|
456
|
+
lines.push('');
|
|
457
|
+
for (const d of diagnostics) {
|
|
458
|
+
if (d.resolveStatus !== 'unresolved-vars' && d.resolveStatus !== 'not-exist')
|
|
459
|
+
continue;
|
|
460
|
+
lines.push(`### ${d.id}${d.description ? ' — ' + d.description : ''}`);
|
|
461
|
+
lines.push(`- status: ${STATUS_ICON[d.resolveStatus]}`);
|
|
462
|
+
lines.push(`- type: ${d.type}`);
|
|
463
|
+
lines.push(`- raw path: \`${d.rawPath}\``);
|
|
464
|
+
lines.push(`- resolved: \`${d.resolvedPath ?? '(null)'}\``);
|
|
465
|
+
if (d.unresolvedTokens && d.unresolvedTokens.length > 0) {
|
|
466
|
+
lines.push(`- unresolved tokens: ${d.unresolvedTokens.map(t => '`' + t + '`').join(', ')}`);
|
|
467
|
+
}
|
|
468
|
+
lines.push('');
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
lines.push(`## All sections`);
|
|
472
|
+
lines.push('');
|
|
473
|
+
lines.push(`| order | id | status | type | raw path | resolved | files | used | injected |`);
|
|
474
|
+
lines.push(`|---|---|---|---|---|---|---|---|---|`);
|
|
475
|
+
diagnostics.forEach((d, idx) => {
|
|
476
|
+
const status = STATUS_ICON[d.resolveStatus];
|
|
477
|
+
const rawPath = d.rawPath ? '`' + d.rawPath + '`' : '—';
|
|
478
|
+
const resolvedShort = d.resolvedPath ? '`' + (d.resolvedPath.length > 60 ? '…' + d.resolvedPath.slice(-58) : d.resolvedPath) + '`' : '—';
|
|
479
|
+
lines.push(`| ${idx + 1} | ${d.id} | ${status} | ${d.type} | ${rawPath} | ${resolvedShort} | ${d.fileCount} | ${d.used ? 'Y' : '·'} | ${d.injected ? 'Y' : '·'} |`);
|
|
480
|
+
});
|
|
481
|
+
lines.push('');
|
|
482
|
+
// 详细列出每个 section
|
|
483
|
+
lines.push(`## Section details`);
|
|
484
|
+
lines.push('');
|
|
485
|
+
for (const d of diagnostics) {
|
|
486
|
+
lines.push(`### ${d.id}${d.description ? ' — ' + d.description : ''}`);
|
|
487
|
+
lines.push(`- status: ${STATUS_ICON[d.resolveStatus]}`);
|
|
488
|
+
lines.push(`- type: ${d.type}`);
|
|
489
|
+
if (d.rawPath)
|
|
490
|
+
lines.push(`- raw path: \`${d.rawPath}\``);
|
|
491
|
+
if (d.pattern)
|
|
492
|
+
lines.push(`- pattern: \`${d.pattern}\``);
|
|
493
|
+
if (d.when !== undefined)
|
|
494
|
+
lines.push(`- when: \`${JSON.stringify(d.when)}\` → ${d.whenPassed ? 'true' : 'false'}`);
|
|
495
|
+
lines.push(`- needsInjection: ${d.needsInjection ? 'true' : 'false'}`);
|
|
496
|
+
lines.push(`- enabled: ${d.enabled}`);
|
|
497
|
+
if (d.resolvedPath)
|
|
498
|
+
lines.push(`- resolved: \`${d.resolvedPath}\``);
|
|
499
|
+
if (d.unresolvedTokens && d.unresolvedTokens.length > 0) {
|
|
500
|
+
lines.push(`- unresolved tokens: ${d.unresolvedTokens.map(t => '`' + t + '`').join(', ')}`);
|
|
501
|
+
}
|
|
502
|
+
lines.push(`- file count: ${d.fileCount}`);
|
|
503
|
+
if (d.emptyContent)
|
|
504
|
+
lines.push(`- note: 文件存在但渲染后内容为空`);
|
|
505
|
+
lines.push(`- used in output: ${d.used ? 'yes' : 'no'}`);
|
|
506
|
+
lines.push(`- injected as fragment: ${d.injected ? 'yes' : 'no'}`);
|
|
507
|
+
lines.push('');
|
|
508
|
+
}
|
|
509
|
+
return lines.join('\n');
|
|
344
510
|
}
|
package/dist/aun/aid/agentmd.js
CHANGED
|
@@ -1,143 +1,96 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import {
|
|
3
|
+
import { getAidStore, loadAid, SLOT } from './store.js';
|
|
4
4
|
import { agentMdPath, aidLocalDir, resolveRoot } from '../../paths.js';
|
|
5
5
|
export function buildInitialAgentMd(opts) {
|
|
6
6
|
const agentName = opts.aid.split('.')[0];
|
|
7
7
|
const agentType = opts.type || 'ai';
|
|
8
8
|
return `---\naid: "${opts.aid}"\nname: "${agentName}"\ntype: "${agentType}"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\n---\n`;
|
|
9
9
|
}
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
try {
|
|
15
|
-
const resp = await fetch(`https://${aid}/.well-known/aun-gateway`, { redirect: 'follow' });
|
|
16
|
-
if (!resp.ok)
|
|
17
|
-
return undefined;
|
|
18
|
-
const text = await resp.text();
|
|
19
|
-
try {
|
|
20
|
-
return JSON.parse(text.trim()).gateways?.[0]?.url ?? text.trim();
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
return text.trim();
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
return undefined;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Obtain cert PEM for an AID: local first, then network.
|
|
32
|
-
* Persists fetched cert to local for future use.
|
|
33
|
-
*/
|
|
34
|
-
async function obtainCertPem(aid, aunPath, client) {
|
|
35
|
-
const localCert = path.join(aunPath, 'AIDs', aid, 'public', 'cert.pem');
|
|
36
|
-
if (fs.existsSync(localCert)) {
|
|
37
|
-
return fs.readFileSync(localCert, 'utf-8');
|
|
38
|
-
}
|
|
39
|
-
// Fetch from network via SDK's _fetchPeerCert (needs gateway)
|
|
40
|
-
if (client) {
|
|
41
|
-
try {
|
|
42
|
-
if (!client._gatewayUrl) {
|
|
43
|
-
client._gatewayUrl = await discoverGateway(aid);
|
|
44
|
-
}
|
|
45
|
-
if (client._gatewayUrl) {
|
|
46
|
-
const certPem = await client._fetchPeerCert.call(client, aid);
|
|
47
|
-
// Persist for future use
|
|
48
|
-
if (certPem) {
|
|
49
|
-
const certDir = path.join(aunPath, 'AIDs', aid, 'public');
|
|
50
|
-
fs.mkdirSync(certDir, { recursive: true });
|
|
51
|
-
fs.writeFileSync(localCert, certPem, 'utf-8');
|
|
52
|
-
}
|
|
53
|
-
return certPem;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch { /* fall through */ }
|
|
57
|
-
}
|
|
58
|
-
return undefined;
|
|
10
|
+
/** Normalize an SDK verification result to the local union type. */
|
|
11
|
+
function normalizeVerification(v) {
|
|
12
|
+
const status = v.status === 'verified' ? 'verified' : v.status === 'unsigned' ? 'unsigned' : 'invalid';
|
|
13
|
+
return { status, ...(v.reason ? { reason: String(v.reason) } : {}) };
|
|
59
14
|
}
|
|
60
15
|
/**
|
|
61
|
-
* Verify agent.md content
|
|
16
|
+
* Verify locally-cached agent.md content offline via the AID value object.
|
|
17
|
+
* Loads the peer cert through the store (no network, no private key required).
|
|
62
18
|
*/
|
|
63
|
-
|
|
19
|
+
function verifyLocal(store, aid, content) {
|
|
64
20
|
if (!content.includes('AUN-SIGNATURE')) {
|
|
65
21
|
return { status: 'unsigned' };
|
|
66
22
|
}
|
|
67
|
-
if (!certPem) {
|
|
68
|
-
return { status: 'invalid', reason: 'certificate not available' };
|
|
69
|
-
}
|
|
70
23
|
try {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
return
|
|
24
|
+
const aidObj = loadAid(store, aid);
|
|
25
|
+
const r = aidObj.verifyAgentMd(content);
|
|
26
|
+
if (!r.ok)
|
|
27
|
+
return { status: 'invalid', reason: r.error.message };
|
|
28
|
+
return normalizeVerification(r.data);
|
|
76
29
|
}
|
|
77
30
|
catch (e) {
|
|
78
|
-
|
|
31
|
+
// loadAid throws AidLoadError when the peer cert is missing/invalid.
|
|
32
|
+
return { status: 'invalid', reason: `certificate not available: ${String(e?.message || e).slice(0, 100)}` };
|
|
79
33
|
}
|
|
80
34
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Create a bare AUNClient (no createAid) for read-only operations.
|
|
83
|
-
*/
|
|
84
|
-
async function createBareClient(aunPath) {
|
|
85
|
-
return createAunClient({ aunPath });
|
|
86
|
-
}
|
|
87
35
|
export async function agentmdGet(aid, opts) {
|
|
88
36
|
const aunPath = opts?.aunPath ?? resolveRoot();
|
|
89
|
-
const
|
|
90
|
-
const
|
|
37
|
+
const store = opts?.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
38
|
+
const ownStore = !opts?.store;
|
|
91
39
|
const localPath = agentMdPath(aid);
|
|
92
40
|
try {
|
|
93
|
-
// Try SDK fetch (auto-saves locally + verifies signature)
|
|
94
41
|
let content;
|
|
95
42
|
let verification;
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
content =
|
|
99
|
-
|
|
100
|
-
const status = sig.status === 'verified' ? 'verified' : sig.status === 'unsigned' ? 'unsigned' : 'invalid';
|
|
101
|
-
verification = { status, ...(sig.reason ? { reason: String(sig.reason) } : {}) };
|
|
43
|
+
const r = await store.downloadAgentMd(aid);
|
|
44
|
+
if (r.ok) {
|
|
45
|
+
content = r.data.content;
|
|
46
|
+
verification = normalizeVerification(r.data.verification);
|
|
102
47
|
}
|
|
103
|
-
|
|
104
|
-
// Network failed — fall back to local file
|
|
105
|
-
if (!fs.existsSync(localPath))
|
|
106
|
-
throw
|
|
48
|
+
else {
|
|
49
|
+
// Network/fetch failed — fall back to local file.
|
|
50
|
+
if (!fs.existsSync(localPath)) {
|
|
51
|
+
throw new Error(`fetch agent.md failed for ${aid}: ${r.error.message}`);
|
|
52
|
+
}
|
|
107
53
|
content = fs.readFileSync(localPath, 'utf-8');
|
|
108
54
|
if (opts?.withVerification) {
|
|
109
|
-
|
|
110
|
-
verification = await verifyContent(content, aid, certPem, client);
|
|
55
|
+
verification = verifyLocal(store, aid, content);
|
|
111
56
|
}
|
|
112
57
|
}
|
|
113
58
|
if (!opts?.withVerification)
|
|
114
59
|
return content;
|
|
115
|
-
return { content
|
|
60
|
+
return { content, verification: verification ?? { status: 'unsigned' } };
|
|
116
61
|
}
|
|
117
62
|
finally {
|
|
118
|
-
if (
|
|
63
|
+
if (ownStore)
|
|
119
64
|
try {
|
|
120
|
-
|
|
65
|
+
store.close();
|
|
121
66
|
}
|
|
122
67
|
catch { /* ignore */ }
|
|
123
68
|
}
|
|
124
69
|
}
|
|
125
70
|
/**
|
|
126
|
-
* Upload agent.md: write
|
|
71
|
+
* Upload agent.md: write local file → store.uploadAgentMd (auto-auth + sign + upload).
|
|
72
|
+
*
|
|
73
|
+
* fastaun 0.4.7: uploadAgentMd moved from AUNClient to AIDStore; the store builds
|
|
74
|
+
* its own LocalTokenStore + AuthFlow internally, so no AUNClient/authenticate needed.
|
|
127
75
|
*/
|
|
128
76
|
export async function agentmdPut(content, opts) {
|
|
129
77
|
const aunPath = opts.aunPath ?? resolveRoot();
|
|
130
|
-
const
|
|
131
|
-
const
|
|
78
|
+
const store = opts.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
79
|
+
const ownStore = !opts.store;
|
|
132
80
|
const dir = aidLocalDir(opts.aid);
|
|
133
81
|
const filePath = path.join(dir, 'agent.md');
|
|
134
82
|
const existed = fs.existsSync(filePath);
|
|
83
|
+
// 先写本地文件(与旧行为一致:本地内容更新应在上传失败时仍保留)
|
|
84
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
85
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
135
86
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
87
|
+
const r = await store.uploadAgentMd(opts.aid, content);
|
|
88
|
+
if (!r.ok)
|
|
89
|
+
throw new Error(`upload agent.md failed for ${opts.aid}: ${r.error.message}`);
|
|
139
90
|
}
|
|
140
91
|
catch (e) {
|
|
92
|
+
// 上传失败:仅当文件原本不存在(本次新建)时回滚,避免留下孤儿文件;
|
|
93
|
+
// 已存在的文件保留新内容(旧语义)。
|
|
141
94
|
if (!existed)
|
|
142
95
|
try {
|
|
143
96
|
fs.unlinkSync(filePath);
|
|
@@ -146,34 +99,53 @@ export async function agentmdPut(content, opts) {
|
|
|
146
99
|
throw e;
|
|
147
100
|
}
|
|
148
101
|
finally {
|
|
149
|
-
if (
|
|
102
|
+
if (ownStore)
|
|
150
103
|
try {
|
|
151
|
-
|
|
104
|
+
store.close();
|
|
152
105
|
}
|
|
153
106
|
catch { /* ignore */ }
|
|
154
107
|
}
|
|
155
108
|
}
|
|
156
109
|
/**
|
|
157
|
-
* Check if agent.md is up-to-date (30-day
|
|
110
|
+
* Check if agent.md is up-to-date (30-day TTL), fetch if changed.
|
|
158
111
|
* Returns changed=true + content when a new version was downloaded.
|
|
112
|
+
*
|
|
113
|
+
* verification 透传 SDK 的验签结果:
|
|
114
|
+
* - downloaded(changed=true)时为 SDK downloadAgentMd 的 verification
|
|
115
|
+
* - 命中本地缓存或网络失败 fallback 时为 undefined(调用方需自行离线验签或视为未验证)
|
|
116
|
+
*
|
|
117
|
+
* Note: store.checkAgentMd tracks freshness via the store's in-memory cache,
|
|
118
|
+
* so a freshly-built store reports local_found=false and will fetch.
|
|
159
119
|
*/
|
|
160
120
|
export async function agentmdSync(aid, opts) {
|
|
161
|
-
const
|
|
162
|
-
const
|
|
121
|
+
const aunPath = opts?.aunPath ?? resolveRoot();
|
|
122
|
+
const store = opts?.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
123
|
+
const ownStore = !opts?.store;
|
|
124
|
+
const localPath = agentMdPath(aid);
|
|
163
125
|
try {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
126
|
+
const check = await store.checkAgentMd(aid, 30);
|
|
127
|
+
// In sync (cache fresh) — return local file content with cached verification status.
|
|
128
|
+
if (check.ok && !check.data.needs_update && check.data.local_found) {
|
|
129
|
+
const content = fs.existsSync(localPath) ? fs.readFileSync(localPath, 'utf-8') : undefined;
|
|
130
|
+
const verification = check.data.verify_status
|
|
131
|
+
? normalizeVerification({ status: check.data.verify_status, reason: check.data.verify_error || undefined })
|
|
132
|
+
: undefined;
|
|
133
|
+
return { changed: false, content, verification };
|
|
134
|
+
}
|
|
135
|
+
// Needs update (or check failed) — fetch fresh content.
|
|
136
|
+
// SDK's downloadAgentMd persists to disk internally (AgentMdManager.saveRecord → writeContent).
|
|
137
|
+
const fetched = await store.downloadAgentMd(aid);
|
|
138
|
+
if (fetched.ok) {
|
|
139
|
+
return { changed: true, content: fetched.data.content, verification: normalizeVerification(fetched.data.verification) };
|
|
168
140
|
}
|
|
169
|
-
|
|
141
|
+
// Fetch failed (network) — fall back to local file if present.
|
|
170
142
|
const content = fs.existsSync(localPath) ? fs.readFileSync(localPath, 'utf-8') : undefined;
|
|
171
143
|
return { changed: false, content };
|
|
172
144
|
}
|
|
173
145
|
finally {
|
|
174
|
-
if (
|
|
146
|
+
if (ownStore)
|
|
175
147
|
try {
|
|
176
|
-
|
|
148
|
+
store.close();
|
|
177
149
|
}
|
|
178
150
|
catch { /* ignore */ }
|
|
179
151
|
}
|
package/dist/aun/aid/client.js
CHANGED
|
@@ -24,7 +24,7 @@ export function suppressSdkLogs() {
|
|
|
24
24
|
return; process.stderr.write(args.map(String).join(' ') + '\n'); };
|
|
25
25
|
}
|
|
26
26
|
// ==================== Constants ====================
|
|
27
|
-
export const MIN_AUN_CORE_SDK = [0,
|
|
27
|
+
export const MIN_AUN_CORE_SDK = [0, 4, 3];
|
|
28
28
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
29
29
|
// ==================== SDK & Environment ====================
|
|
30
30
|
function compareVersion(a, min) {
|
|
@@ -113,31 +113,3 @@ export async function downloadCaRoot(aunPath, gatewayUrl, indent = '') {
|
|
|
113
113
|
return false;
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
|
-
/**
|
|
117
|
-
* 统一构造 AUNClient:自动绑 root_ca_path + setAgentMdPath(aidsDir())。
|
|
118
|
-
* 不做 createAid / authenticate / connect,调用方按需续作。
|
|
119
|
-
*
|
|
120
|
-
* 所有 new AUNClient 调用都应走此工厂,避免 SDK 默认把 agent.md 写到
|
|
121
|
-
* {aun_path}/AgentMDs(默认目录)。
|
|
122
|
-
*/
|
|
123
|
-
export async function createAunClient(opts = {}) {
|
|
124
|
-
const { aunPath: defaultAunPath, aidsDir } = await import('../../paths.js');
|
|
125
|
-
const aunPath = opts.aunPath ?? defaultAunPath();
|
|
126
|
-
const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
127
|
-
const { AUNClient } = await import('@agentunion/fastaun');
|
|
128
|
-
const clientOpts = { aun_path: aunPath, debug: opts.debug ?? false };
|
|
129
|
-
if (fs.existsSync(caCertPath))
|
|
130
|
-
clientOpts.root_ca_path = caCertPath;
|
|
131
|
-
if (opts.encryptionSeed)
|
|
132
|
-
clientOpts.encryption_seed = opts.encryptionSeed;
|
|
133
|
-
const client = opts.aunSdkLog !== undefined
|
|
134
|
-
? new AUNClient(clientOpts, opts.aunSdkLog)
|
|
135
|
-
: new AUNClient(clientOpts);
|
|
136
|
-
client.setAgentMdPath(aidsDir());
|
|
137
|
-
return client;
|
|
138
|
-
}
|
|
139
|
-
export async function getAunClient(aid, opts) {
|
|
140
|
-
const client = await createAunClient({ aunPath: opts?.aunPath });
|
|
141
|
-
await client.auth.createAid({ aid });
|
|
142
|
-
return client;
|
|
143
|
-
}
|