evolclaw 3.1.3 → 3.1.5
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 +27 -0
- package/assets/.env.template +4 -0
- package/assets/config.json.template +6 -0
- package/assets/wechat-group-qr.jpeg +0 -0
- package/dist/agents/claude-runner.js +348 -156
- package/dist/agents/kit-renderer.js +211 -42
- package/dist/aun/aid/agentmd.js +75 -139
- package/dist/aun/aid/client.js +1 -14
- package/dist/aun/aid/identity.js +381 -54
- package/dist/aun/aid/index.js +3 -2
- package/dist/aun/aid/store.js +74 -0
- package/dist/aun/msg/p2p.js +26 -2
- package/dist/aun/rpc/connection.js +23 -35
- package/dist/channels/aun.js +92 -144
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +270 -190
- package/dist/channels/qqbot.js +1 -0
- package/dist/channels/wechat.js +1 -0
- package/dist/channels/wecom.js +1 -0
- package/dist/cli/agent.js +26 -27
- package/dist/cli/bench.js +45 -34
- package/dist/cli/help.js +23 -0
- package/dist/cli/index.js +538 -77
- package/dist/cli/init-channel.js +7 -4
- package/dist/cli/link-rules.js +2 -1
- package/dist/cli/model.js +324 -0
- package/dist/cli/net-check.js +138 -56
- 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/core/channel-loader.js +7 -4
- package/dist/core/command-handler.js +87 -93
- package/dist/core/evolagent-registry.js +1 -1
- package/dist/core/evolagent.js +4 -4
- package/dist/core/interaction-router.js +59 -0
- package/dist/core/message/message-bridge.js +6 -6
- package/dist/core/message/message-log.js +2 -2
- package/dist/core/message/message-processor.js +104 -118
- 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 +78 -44
- 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 +312 -251
- package/dist/core/session/session-mapper.js +9 -4
- package/dist/core/trigger/manager.js +37 -0
- package/dist/core/trigger/scheduler.js +2 -1
- package/dist/index.js +10 -3
- package/dist/ipc.js +22 -0
- package/dist/paths.js +87 -16
- package/dist/utils/npm-ops.js +18 -11
- package/kits/docs/GUIDE.md +2 -2
- package/kits/docs/INDEX.md +11 -7
- package/kits/docs/channels/aun.md +56 -17
- package/kits/docs/channels/feishu.md +41 -12
- package/kits/docs/context-assembly.md +181 -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 +82 -0
- package/kits/docs/evolclaw/msg.md +86 -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 +11 -0
- package/kits/docs/venues/private.md +10 -0
- package/kits/eck_manifest.json +75 -39
- package/kits/rules/01-overview.md +20 -10
- package/kits/rules/05-venue.md +2 -2
- package/kits/rules/06-channel.md +30 -27
- package/kits/templates/system-fragments/baseagent.md +7 -1
- package/kits/templates/system-fragments/channel.md +4 -1
- package/kits/templates/system-fragments/identity.md +4 -4
- package/kits/templates/system-fragments/relation.md +8 -5
- package/kits/templates/system-fragments/session.md +27 -0
- package/kits/templates/system-fragments/venue.md +13 -1
- package/package.json +13 -6
- package/dist/aun/aid/lifecycle-log.js +0 -33
- package/dist/net-check.js +0 -640
- package/dist/utils/aid-lifecycle-log.js +0 -33
- package/dist/watch-msg.js +0 -544
- 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
- package/kits/templates/system-fragments/eckruntime.md +0 -14
|
@@ -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 内容',
|
|
@@ -14,20 +17,28 @@ const PARAM_DESCRIPTIONS = {
|
|
|
14
17
|
peerId: '对端在该渠道的原生 ID',
|
|
15
18
|
peerKey: '对端跨渠道唯一标识(channel#urlEncode(peerId))',
|
|
16
19
|
peerName: '对端显示名',
|
|
17
|
-
peerRole: '
|
|
20
|
+
peerRole: '对端角色(owner/admin/guest/anonymous)',
|
|
21
|
+
peerType: '对端类型(human/agent)',
|
|
18
22
|
groupId: '群组 ID(群聊时)',
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
chatType: '聊天类型(private=私聊 / group=群聊 / null=本地开发)',
|
|
24
|
+
channel: '渠道类型(aun/feishu/wechat/dingtalk/qqbot/wecom)',
|
|
25
|
+
venueUid: '场所唯一标识(预留)',
|
|
26
|
+
dispatch: '群分发模式(mention=被@才响应 / broadcast=所有消息都响应)',
|
|
27
|
+
clientType: '客户端类型(desktop/web/mobile)',
|
|
28
|
+
permissionMode: '权限模式(auto/bypass/request/edit/plan/noask/readonly)',
|
|
29
|
+
capabilities: '当前渠道支持的能力列表',
|
|
30
|
+
project: '当前项目目录名',
|
|
31
|
+
sessionId: 'evolclaw 会话 ID',
|
|
24
32
|
sessionName: '会话名称',
|
|
25
|
-
|
|
33
|
+
sessionKey: '会话路由键(channelType#urlEncode(channelId)#urlEncode(threadId))',
|
|
34
|
+
sessionCreatedAt: '会话创建时间(ISO)',
|
|
35
|
+
threadId: '话题 ID(多话题路由时)',
|
|
36
|
+
chatMode: '会话模式(interactive=同步交互 / proactive=主动推送)',
|
|
26
37
|
readonly: '是否只读模式',
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
baseAgent: 'base agent 规范值(claude/codex/gemini/hermes)',
|
|
39
|
+
baseAgentName: 'base agent 显示名',
|
|
40
|
+
baseAgentModel: 'base agent 使用的模型',
|
|
41
|
+
agentSessionId: 'base agent 会话 ID',
|
|
31
42
|
};
|
|
32
43
|
function buildPathMappings(vars) {
|
|
33
44
|
const pkgRoot = getPackageRoot();
|
|
@@ -43,6 +54,9 @@ function buildPathMappings(vars) {
|
|
|
43
54
|
{ prefix: pkgRoot, alias: '$PACKAGE_ROOT' },
|
|
44
55
|
];
|
|
45
56
|
if (selfAid) {
|
|
57
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'personal'), alias: '$PERSONAL_DIR' });
|
|
58
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'relations'), alias: '$RELATIONS_DIR' });
|
|
59
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid, 'venues'), alias: '$VENUES_DIR' });
|
|
46
60
|
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid), alias: '$AGENT_DIR' });
|
|
47
61
|
}
|
|
48
62
|
mappings.push({ prefix: evolHome, alias: '$EVOLCLAW_HOME' });
|
|
@@ -86,33 +100,75 @@ export function renderKitSections(ctx) {
|
|
|
86
100
|
const fileParts = [];
|
|
87
101
|
const fragmentParts = [];
|
|
88
102
|
const pathMappings = buildPathMappings(ctx.vars);
|
|
103
|
+
const diagnostics = [];
|
|
89
104
|
for (const section of sections) {
|
|
90
|
-
|
|
105
|
+
const rawPath = section.type === 'file' ? (section.file ?? '') : (section.path ?? '');
|
|
106
|
+
const diag = {
|
|
107
|
+
id: section.id,
|
|
108
|
+
description: section.description,
|
|
109
|
+
type: section.type,
|
|
110
|
+
rawPath,
|
|
111
|
+
pattern: section.pattern,
|
|
112
|
+
needsInjection: section.needsInjection,
|
|
113
|
+
when: section.when,
|
|
114
|
+
enabled: section.enabled !== false,
|
|
115
|
+
whenPassed: false,
|
|
116
|
+
resolvedPath: null,
|
|
117
|
+
resolveStatus: 'no-path',
|
|
118
|
+
fileCount: 0,
|
|
119
|
+
used: false,
|
|
120
|
+
injected: false,
|
|
121
|
+
};
|
|
122
|
+
if (section.enabled === false) {
|
|
123
|
+
diag.resolveStatus = 'skipped-disabled';
|
|
124
|
+
diagnostics.push(diag);
|
|
91
125
|
continue;
|
|
92
|
-
|
|
126
|
+
}
|
|
127
|
+
diag.whenPassed = evaluateWhen(section.when, ctx.vars);
|
|
128
|
+
if (!diag.whenPassed) {
|
|
129
|
+
diag.resolveStatus = 'skipped-when';
|
|
130
|
+
diagnostics.push(diag);
|
|
93
131
|
continue;
|
|
132
|
+
}
|
|
133
|
+
if (rawPath) {
|
|
134
|
+
const resolveResult = resolvePathWithDiag(rawPath, ctx);
|
|
135
|
+
diag.resolvedPath = resolveResult.resolved;
|
|
136
|
+
diag.resolveStatus = resolveResult.status;
|
|
137
|
+
if (resolveResult.unresolvedTokens.length > 0)
|
|
138
|
+
diag.unresolvedTokens = resolveResult.unresolvedTokens;
|
|
139
|
+
}
|
|
94
140
|
const files = loadSectionFiles(section, ctx);
|
|
95
|
-
|
|
141
|
+
diag.fileCount = files.length;
|
|
142
|
+
if (files.length === 0) {
|
|
143
|
+
diagnostics.push(diag);
|
|
96
144
|
continue;
|
|
145
|
+
}
|
|
146
|
+
let anyUsed = false;
|
|
97
147
|
for (const [filePath, rawContent] of files) {
|
|
98
148
|
const content = section.needsInjection ? renderTemplate(rawContent, ctx.vars) : rawContent;
|
|
99
|
-
if (!content.trim())
|
|
149
|
+
if (!content.trim()) {
|
|
150
|
+
diag.emptyContent = true;
|
|
100
151
|
continue;
|
|
152
|
+
}
|
|
101
153
|
const label = section.description ? `${section.id} — ${section.description}` : section.id;
|
|
102
154
|
const displayPath = shortenPath(filePath, pathMappings);
|
|
103
155
|
const part = `Contenu de ${displayPath} (${label}):\n\n${content.trimEnd()}`;
|
|
104
156
|
fileParts.push(part);
|
|
157
|
+
anyUsed = true;
|
|
105
158
|
if (section.needsInjection) {
|
|
106
159
|
fragmentParts.push(part);
|
|
160
|
+
diag.injected = true;
|
|
107
161
|
}
|
|
108
162
|
}
|
|
163
|
+
diag.used = anyUsed;
|
|
164
|
+
diagnostics.push(diag);
|
|
109
165
|
}
|
|
110
|
-
if (fileParts.length === 0)
|
|
111
|
-
return '';
|
|
112
166
|
const body = fileParts.join('\n\n');
|
|
113
|
-
const output =
|
|
167
|
+
const output = fileParts.length > 0
|
|
168
|
+
? `<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>`
|
|
169
|
+
: '';
|
|
114
170
|
const fragmentsOutput = fragmentParts.length > 0 ? fragmentParts.join('\n\n') : '';
|
|
115
|
-
writeDebugFiles(ctx, output, fragmentsOutput);
|
|
171
|
+
writeDebugFiles(ctx, output, fragmentsOutput, diagnostics);
|
|
116
172
|
return output;
|
|
117
173
|
}
|
|
118
174
|
export function cleanEckDebug() {
|
|
@@ -208,23 +264,38 @@ function loadDirectorySection(dirPath, pattern, ctx) {
|
|
|
208
264
|
}
|
|
209
265
|
// ── Path resolution ──
|
|
210
266
|
function resolvePath(rawPath, ctx) {
|
|
211
|
-
|
|
267
|
+
const r = resolvePathWithDiag(rawPath, ctx);
|
|
268
|
+
return r.status === 'ok' ? r.resolved : null;
|
|
269
|
+
}
|
|
270
|
+
function resolvePathWithDiag(rawPath, ctx) {
|
|
271
|
+
const unresolved = [];
|
|
272
|
+
let resolved = rawPath.replace(/\$([A-Z_]+)/g, (_m, name) => {
|
|
212
273
|
const val = ctx.vars[name];
|
|
213
|
-
if (val === undefined || val === null || val === false || val === '')
|
|
274
|
+
if (val === undefined || val === null || val === false || val === '') {
|
|
275
|
+
unresolved.push(`$${name}`);
|
|
214
276
|
return '';
|
|
277
|
+
}
|
|
215
278
|
return String(val);
|
|
216
279
|
});
|
|
217
|
-
resolved = resolved.replace(/\{\{(\w+)\}\}/g, (
|
|
280
|
+
resolved = resolved.replace(/\{\{(\w+)\}\}/g, (_m, key) => {
|
|
218
281
|
const val = ctx.vars[key];
|
|
219
|
-
if (val === undefined || val === null || val === false || val === '')
|
|
282
|
+
if (val === undefined || val === null || val === false || val === '') {
|
|
283
|
+
unresolved.push(`{{${key}}}`);
|
|
220
284
|
return '';
|
|
285
|
+
}
|
|
221
286
|
return String(val);
|
|
222
287
|
});
|
|
223
|
-
if (!resolved || resolved.includes('$') || resolved.includes('{{'))
|
|
224
|
-
return null;
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
288
|
+
if (!resolved || resolved.includes('$') || resolved.includes('{{')) {
|
|
289
|
+
return { resolved: resolved || null, status: 'unresolved-vars', unresolvedTokens: unresolved };
|
|
290
|
+
}
|
|
291
|
+
if (unresolved.length > 0) {
|
|
292
|
+
// 占位符是非必需变量,但有的解析为空——视为未解析
|
|
293
|
+
return { resolved, status: 'unresolved-vars', unresolvedTokens: unresolved };
|
|
294
|
+
}
|
|
295
|
+
if (!fs.existsSync(resolved)) {
|
|
296
|
+
return { resolved, status: 'not-exist', unresolvedTokens: unresolved };
|
|
297
|
+
}
|
|
298
|
+
return { resolved, status: 'ok', unresolvedTokens: unresolved };
|
|
228
299
|
}
|
|
229
300
|
// CHUNK_CONTINUE_5
|
|
230
301
|
// ── Directory reading ──
|
|
@@ -256,10 +327,17 @@ function evaluateWhen(when, vars) {
|
|
|
256
327
|
return true;
|
|
257
328
|
if (when.var !== undefined) {
|
|
258
329
|
const val = vars[when.var];
|
|
259
|
-
if (when.eq !== undefined)
|
|
330
|
+
if (when.eq !== undefined) {
|
|
331
|
+
// 把 undefined 视作 null 的等价物,便于 manifest 用 eq:null/neq:null 表达"未注入"
|
|
332
|
+
if (when.eq === null)
|
|
333
|
+
return val === null || val === undefined;
|
|
260
334
|
return val === when.eq;
|
|
261
|
-
|
|
335
|
+
}
|
|
336
|
+
if (when.neq !== undefined) {
|
|
337
|
+
if (when.neq === null)
|
|
338
|
+
return val !== null && val !== undefined;
|
|
262
339
|
return val !== when.neq;
|
|
340
|
+
}
|
|
263
341
|
if (when.in !== undefined)
|
|
264
342
|
return when.in.includes(val);
|
|
265
343
|
if (when.nin !== undefined)
|
|
@@ -276,17 +354,27 @@ function isTruthy(val) {
|
|
|
276
354
|
}
|
|
277
355
|
// CHUNK_CONTINUE_6
|
|
278
356
|
// ── Template rendering ──
|
|
357
|
+
function resolveConditions(template, vars) {
|
|
358
|
+
// Find innermost {{?...}}...{{/}} block (no nested {{? inside) and resolve it.
|
|
359
|
+
// Repeat until no blocks remain.
|
|
360
|
+
const inner = /\{\{\?(\w+)(?:(!=|=)([^}]*))?\}\}([^]*?)\{\{\/\}\}/;
|
|
361
|
+
let result = template;
|
|
362
|
+
let prev;
|
|
363
|
+
do {
|
|
364
|
+
prev = result;
|
|
365
|
+
result = result.replace(inner, (_match, key, op, value, body) => {
|
|
366
|
+
if (op === '=')
|
|
367
|
+
return String(vars[key]) === value ? body : '';
|
|
368
|
+
if (op === '!=')
|
|
369
|
+
return String(vars[key]) !== value ? body : '';
|
|
370
|
+
return isTruthy(vars[key]) ? body : '';
|
|
371
|
+
});
|
|
372
|
+
} while (result !== prev);
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
279
375
|
function renderTemplate(template, vars) {
|
|
280
|
-
// Pass 1:
|
|
281
|
-
let result = template
|
|
282
|
-
if (op === '!=')
|
|
283
|
-
return String(vars[key]) !== value ? body : '';
|
|
284
|
-
return String(vars[key]) === value ? body : '';
|
|
285
|
-
});
|
|
286
|
-
// Pass 1b: truthy-only {{?key}}...{{/}}
|
|
287
|
-
result = result.replace(/\{\{\?(\w+)\}\}([\s\S]*?)\{\{\/\}\}/g, (_match, key, body) => {
|
|
288
|
-
return isTruthy(vars[key]) ? body : '';
|
|
289
|
-
});
|
|
376
|
+
// Pass 1: resolve nested conditionals inside-out
|
|
377
|
+
let result = resolveConditions(template, vars);
|
|
290
378
|
// Pass 2: variable substitution {{key}}
|
|
291
379
|
result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
292
380
|
const val = vars[key];
|
|
@@ -307,7 +395,7 @@ function getSessionCache(sessionId) {
|
|
|
307
395
|
return cache;
|
|
308
396
|
}
|
|
309
397
|
// ── Debug output ──
|
|
310
|
-
function writeDebugFiles(ctx, output, fragmentsOutput) {
|
|
398
|
+
function writeDebugFiles(ctx, output, fragmentsOutput, diagnostics) {
|
|
311
399
|
const now = new Date();
|
|
312
400
|
const ts = now.toISOString().replace(/[T:.]/g, '-').slice(0, 19);
|
|
313
401
|
const dir = eckDebugDir();
|
|
@@ -323,8 +411,89 @@ function writeDebugFiles(ctx, output, fragmentsOutput) {
|
|
|
323
411
|
})),
|
|
324
412
|
};
|
|
325
413
|
fs.writeFile(path.join(dir, `vars-${ts}.json`), JSON.stringify(varsData, null, 2), () => { });
|
|
326
|
-
|
|
414
|
+
if (output)
|
|
415
|
+
fs.writeFile(path.join(dir, `context-${ts}.md`), output, () => { });
|
|
327
416
|
if (fragmentsOutput) {
|
|
328
417
|
fs.writeFile(path.join(dir, `fragments-${ts}.md`), fragmentsOutput, () => { });
|
|
329
418
|
}
|
|
419
|
+
fs.writeFile(path.join(dir, `manifest-${ts}.md`), formatManifestDiagnostics(ctx, diagnostics), () => { });
|
|
420
|
+
}
|
|
421
|
+
function formatManifestDiagnostics(ctx, diagnostics) {
|
|
422
|
+
const STATUS_ICON = {
|
|
423
|
+
'ok': 'OK',
|
|
424
|
+
'unresolved-vars': 'UNRESOLVED-VARS',
|
|
425
|
+
'not-exist': 'NOT-EXIST',
|
|
426
|
+
'skipped-disabled': 'SKIPPED(disabled)',
|
|
427
|
+
'skipped-when': 'SKIPPED(when)',
|
|
428
|
+
'no-path': 'NO-PATH',
|
|
429
|
+
};
|
|
430
|
+
const used = diagnostics.filter(d => d.used).length;
|
|
431
|
+
const skippedWhen = diagnostics.filter(d => d.resolveStatus === 'skipped-when').length;
|
|
432
|
+
const errors = diagnostics.filter(d => d.resolveStatus === 'unresolved-vars' || d.resolveStatus === 'not-exist').length;
|
|
433
|
+
const lines = [];
|
|
434
|
+
lines.push(`# ECK Manifest Diagnostics`);
|
|
435
|
+
lines.push('');
|
|
436
|
+
lines.push(`- timestamp: ${new Date().toISOString()}`);
|
|
437
|
+
lines.push(`- sessionId: ${ctx.sessionId}`);
|
|
438
|
+
lines.push(`- sections total: ${diagnostics.length}`);
|
|
439
|
+
lines.push(`- sections used: ${used}`);
|
|
440
|
+
lines.push(`- sections skipped (when=false): ${skippedWhen}`);
|
|
441
|
+
lines.push(`- sections with errors (unresolved-vars/not-exist): ${errors}`);
|
|
442
|
+
lines.push('');
|
|
443
|
+
if (errors > 0) {
|
|
444
|
+
lines.push(`## Errors`);
|
|
445
|
+
lines.push('');
|
|
446
|
+
for (const d of diagnostics) {
|
|
447
|
+
if (d.resolveStatus !== 'unresolved-vars' && d.resolveStatus !== 'not-exist')
|
|
448
|
+
continue;
|
|
449
|
+
lines.push(`### ${d.id}${d.description ? ' — ' + d.description : ''}`);
|
|
450
|
+
lines.push(`- status: ${STATUS_ICON[d.resolveStatus]}`);
|
|
451
|
+
lines.push(`- type: ${d.type}`);
|
|
452
|
+
lines.push(`- raw path: \`${d.rawPath}\``);
|
|
453
|
+
lines.push(`- resolved: \`${d.resolvedPath ?? '(null)'}\``);
|
|
454
|
+
if (d.unresolvedTokens && d.unresolvedTokens.length > 0) {
|
|
455
|
+
lines.push(`- unresolved tokens: ${d.unresolvedTokens.map(t => '`' + t + '`').join(', ')}`);
|
|
456
|
+
}
|
|
457
|
+
lines.push('');
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
lines.push(`## All sections`);
|
|
461
|
+
lines.push('');
|
|
462
|
+
lines.push(`| order | id | status | type | raw path | resolved | files | used | injected |`);
|
|
463
|
+
lines.push(`|---|---|---|---|---|---|---|---|---|`);
|
|
464
|
+
diagnostics.forEach((d, idx) => {
|
|
465
|
+
const status = STATUS_ICON[d.resolveStatus];
|
|
466
|
+
const rawPath = d.rawPath ? '`' + d.rawPath + '`' : '—';
|
|
467
|
+
const resolvedShort = d.resolvedPath ? '`' + (d.resolvedPath.length > 60 ? '…' + d.resolvedPath.slice(-58) : d.resolvedPath) + '`' : '—';
|
|
468
|
+
lines.push(`| ${idx + 1} | ${d.id} | ${status} | ${d.type} | ${rawPath} | ${resolvedShort} | ${d.fileCount} | ${d.used ? 'Y' : '·'} | ${d.injected ? 'Y' : '·'} |`);
|
|
469
|
+
});
|
|
470
|
+
lines.push('');
|
|
471
|
+
// 详细列出每个 section
|
|
472
|
+
lines.push(`## Section details`);
|
|
473
|
+
lines.push('');
|
|
474
|
+
for (const d of diagnostics) {
|
|
475
|
+
lines.push(`### ${d.id}${d.description ? ' — ' + d.description : ''}`);
|
|
476
|
+
lines.push(`- status: ${STATUS_ICON[d.resolveStatus]}`);
|
|
477
|
+
lines.push(`- type: ${d.type}`);
|
|
478
|
+
if (d.rawPath)
|
|
479
|
+
lines.push(`- raw path: \`${d.rawPath}\``);
|
|
480
|
+
if (d.pattern)
|
|
481
|
+
lines.push(`- pattern: \`${d.pattern}\``);
|
|
482
|
+
if (d.when !== undefined)
|
|
483
|
+
lines.push(`- when: \`${JSON.stringify(d.when)}\` → ${d.whenPassed ? 'true' : 'false'}`);
|
|
484
|
+
lines.push(`- needsInjection: ${d.needsInjection ? 'true' : 'false'}`);
|
|
485
|
+
lines.push(`- enabled: ${d.enabled}`);
|
|
486
|
+
if (d.resolvedPath)
|
|
487
|
+
lines.push(`- resolved: \`${d.resolvedPath}\``);
|
|
488
|
+
if (d.unresolvedTokens && d.unresolvedTokens.length > 0) {
|
|
489
|
+
lines.push(`- unresolved tokens: ${d.unresolvedTokens.map(t => '`' + t + '`').join(', ')}`);
|
|
490
|
+
}
|
|
491
|
+
lines.push(`- file count: ${d.fileCount}`);
|
|
492
|
+
if (d.emptyContent)
|
|
493
|
+
lines.push(`- note: 文件存在但渲染后内容为空`);
|
|
494
|
+
lines.push(`- used in output: ${d.used ? 'yes' : 'no'}`);
|
|
495
|
+
lines.push(`- injected as fragment: ${d.injected ? 'yes' : 'no'}`);
|
|
496
|
+
lines.push('');
|
|
497
|
+
}
|
|
498
|
+
return lines.join('\n');
|
|
330
499
|
}
|
package/dist/aun/aid/agentmd.js
CHANGED
|
@@ -1,172 +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
|
-
}
|
|
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) } : {}) };
|
|
29
14
|
}
|
|
30
15
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
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).
|
|
33
18
|
*/
|
|
34
|
-
|
|
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;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Verify agent.md content using SDK.
|
|
62
|
-
*/
|
|
63
|
-
async function verifyContent(content, aid, certPem, client) {
|
|
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
|
-
const p = aunPath ?? resolveRoot();
|
|
86
|
-
const { AUNClient } = await import('@agentunion/fastaun');
|
|
87
|
-
const caCertPath = path.join(p, 'CA', 'root', 'root.crt');
|
|
88
|
-
const clientOpts = { aun_path: p, debug: false };
|
|
89
|
-
if (fs.existsSync(caCertPath))
|
|
90
|
-
clientOpts.root_ca_path = caCertPath;
|
|
91
|
-
return new AUNClient(clientOpts);
|
|
92
|
-
}
|
|
93
35
|
export async function agentmdGet(aid, opts) {
|
|
94
36
|
const aunPath = opts?.aunPath ?? resolveRoot();
|
|
37
|
+
const store = opts?.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
38
|
+
const ownStore = !opts?.store;
|
|
95
39
|
const localPath = agentMdPath(aid);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return { content, verification };
|
|
40
|
+
try {
|
|
41
|
+
let content;
|
|
42
|
+
let verification;
|
|
43
|
+
const r = await store.downloadAgentMd(aid);
|
|
44
|
+
if (r.ok) {
|
|
45
|
+
content = r.data.content;
|
|
46
|
+
verification = normalizeVerification(r.data.verification);
|
|
47
|
+
}
|
|
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}`);
|
|
109
52
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const remote = info.content;
|
|
114
|
-
if (remote) {
|
|
115
|
-
const remoteVerification = await verifyContent(remote, aid, certPem, client);
|
|
116
|
-
if (remoteVerification.status === 'verified') {
|
|
117
|
-
fs.writeFileSync(localPath, remote, 'utf-8');
|
|
118
|
-
return { content: remote, verification: remoteVerification };
|
|
119
|
-
}
|
|
120
|
-
}
|
|
53
|
+
content = fs.readFileSync(localPath, 'utf-8');
|
|
54
|
+
if (opts?.withVerification) {
|
|
55
|
+
verification = verifyLocal(store, aid, content);
|
|
121
56
|
}
|
|
122
|
-
catch { /* remote fetch failed, return local invalid result */ }
|
|
123
|
-
return { content, verification };
|
|
124
|
-
}
|
|
125
|
-
finally {
|
|
126
|
-
if (ownClient)
|
|
127
|
-
try {
|
|
128
|
-
await client.close();
|
|
129
|
-
}
|
|
130
|
-
catch { /* ignore */ }
|
|
131
57
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const ownClient = !opts?.client;
|
|
136
|
-
try {
|
|
137
|
-
const info = await client.fetchAgentMd(aid);
|
|
138
|
-
const raw = info.content;
|
|
139
|
-
if (!opts?.withVerification) {
|
|
140
|
-
return raw;
|
|
141
|
-
}
|
|
142
|
-
const certPem = await obtainCertPem(aid, aunPath, client);
|
|
143
|
-
const verification = await verifyContent(raw, aid, certPem, client);
|
|
144
|
-
return { content: raw, verification };
|
|
58
|
+
if (!opts?.withVerification)
|
|
59
|
+
return content;
|
|
60
|
+
return { content, verification: verification ?? { status: 'unsigned' } };
|
|
145
61
|
}
|
|
146
62
|
finally {
|
|
147
|
-
if (
|
|
63
|
+
if (ownStore)
|
|
148
64
|
try {
|
|
149
|
-
|
|
65
|
+
store.close();
|
|
150
66
|
}
|
|
151
67
|
catch { /* ignore */ }
|
|
152
68
|
}
|
|
153
69
|
}
|
|
154
70
|
/**
|
|
155
|
-
* 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.
|
|
156
75
|
*/
|
|
157
76
|
export async function agentmdPut(content, opts) {
|
|
158
77
|
const aunPath = opts.aunPath ?? resolveRoot();
|
|
159
|
-
const
|
|
160
|
-
const
|
|
78
|
+
const store = opts.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
79
|
+
const ownStore = !opts.store;
|
|
161
80
|
const dir = aidLocalDir(opts.aid);
|
|
162
81
|
const filePath = path.join(dir, 'agent.md');
|
|
163
82
|
const existed = fs.existsSync(filePath);
|
|
83
|
+
// 先写本地文件(与旧行为一致:本地内容更新应在上传失败时仍保留)
|
|
84
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
85
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
164
86
|
try {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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}`);
|
|
168
90
|
}
|
|
169
91
|
catch (e) {
|
|
92
|
+
// 上传失败:仅当文件原本不存在(本次新建)时回滚,避免留下孤儿文件;
|
|
93
|
+
// 已存在的文件保留新内容(旧语义)。
|
|
170
94
|
if (!existed)
|
|
171
95
|
try {
|
|
172
96
|
fs.unlinkSync(filePath);
|
|
@@ -175,34 +99,46 @@ export async function agentmdPut(content, opts) {
|
|
|
175
99
|
throw e;
|
|
176
100
|
}
|
|
177
101
|
finally {
|
|
178
|
-
if (
|
|
102
|
+
if (ownStore)
|
|
179
103
|
try {
|
|
180
|
-
|
|
104
|
+
store.close();
|
|
181
105
|
}
|
|
182
106
|
catch { /* ignore */ }
|
|
183
107
|
}
|
|
184
108
|
}
|
|
185
109
|
/**
|
|
186
|
-
* 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.
|
|
187
111
|
* Returns changed=true + content when a new version was downloaded.
|
|
112
|
+
*
|
|
113
|
+
* Note: store.checkAgentMd tracks freshness via the store's in-memory cache,
|
|
114
|
+
* so a freshly-built store reports local_found=false and will fetch.
|
|
188
115
|
*/
|
|
189
116
|
export async function agentmdSync(aid, opts) {
|
|
190
|
-
const
|
|
191
|
-
const
|
|
117
|
+
const aunPath = opts?.aunPath ?? resolveRoot();
|
|
118
|
+
const store = opts?.store ?? await getAidStore({ slotId: SLOT.cli, aunPath });
|
|
119
|
+
const ownStore = !opts?.store;
|
|
120
|
+
const localPath = agentMdPath(aid);
|
|
192
121
|
try {
|
|
193
|
-
const
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
122
|
+
const check = await store.checkAgentMd(aid, 30);
|
|
123
|
+
// In sync (cache fresh) — return local file content unchanged.
|
|
124
|
+
if (check.ok && !check.data.needs_update && check.data.local_found) {
|
|
125
|
+
const content = fs.existsSync(localPath) ? fs.readFileSync(localPath, 'utf-8') : undefined;
|
|
126
|
+
return { changed: false, content };
|
|
127
|
+
}
|
|
128
|
+
// Needs update (or check failed) — fetch fresh content.
|
|
129
|
+
// SDK's downloadAgentMd persists to disk internally (AgentMdManager.saveRecord → writeContent).
|
|
130
|
+
const fetched = await store.downloadAgentMd(aid);
|
|
131
|
+
if (fetched.ok) {
|
|
132
|
+
return { changed: true, content: fetched.data.content };
|
|
197
133
|
}
|
|
198
|
-
|
|
134
|
+
// Fetch failed (network) — fall back to local file if present.
|
|
199
135
|
const content = fs.existsSync(localPath) ? fs.readFileSync(localPath, 'utf-8') : undefined;
|
|
200
136
|
return { changed: false, content };
|
|
201
137
|
}
|
|
202
138
|
finally {
|
|
203
|
-
if (
|
|
139
|
+
if (ownStore)
|
|
204
140
|
try {
|
|
205
|
-
|
|
141
|
+
store.close();
|
|
206
142
|
}
|
|
207
143
|
catch { /* ignore */ }
|
|
208
144
|
}
|
package/dist/aun/aid/client.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import os from 'os';
|
|
4
3
|
import { fileURLToPath } from 'url';
|
|
5
4
|
import { execFileSync } from 'child_process';
|
|
6
5
|
import { isWindows } from '../../utils/cross-platform.js';
|
|
@@ -25,7 +24,7 @@ export function suppressSdkLogs() {
|
|
|
25
24
|
return; process.stderr.write(args.map(String).join(' ') + '\n'); };
|
|
26
25
|
}
|
|
27
26
|
// ==================== Constants ====================
|
|
28
|
-
export const MIN_AUN_CORE_SDK = [0,
|
|
27
|
+
export const MIN_AUN_CORE_SDK = [0, 4, 3];
|
|
29
28
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
30
29
|
// ==================== SDK & Environment ====================
|
|
31
30
|
function compareVersion(a, min) {
|
|
@@ -114,15 +113,3 @@ export async function downloadCaRoot(aunPath, gatewayUrl, indent = '') {
|
|
|
114
113
|
return false;
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
|
-
// ==================== AUNClient Factory ====================
|
|
118
|
-
export async function getAunClient(aid, opts) {
|
|
119
|
-
const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
|
|
120
|
-
const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
121
|
-
const { AUNClient } = await import('@agentunion/fastaun');
|
|
122
|
-
const clientOpts = { aun_path: aunPath, debug: false };
|
|
123
|
-
if (fs.existsSync(caCertPath))
|
|
124
|
-
clientOpts.root_ca_path = caCertPath;
|
|
125
|
-
const client = new AUNClient(clientOpts);
|
|
126
|
-
await client.auth.createAid({ aid });
|
|
127
|
-
return client;
|
|
128
|
-
}
|