evolclaw 2.8.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -12
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent-normalize.js +19 -0
- package/dist/agents/claude-runner.js +108 -46
- package/dist/agents/codex-runner.js +13 -14
- package/dist/agents/gemini-runner.js +15 -17
- package/dist/agents/kit-renderer.js +281 -0
- package/dist/agents/resolve.js +134 -0
- package/dist/aun/aid/agentmd.js +186 -0
- package/dist/aun/aid/client.js +134 -0
- package/dist/aun/aid/identity.js +159 -0
- package/dist/aun/aid/index.js +3 -0
- package/dist/aun/aid/lifecycle-log.js +33 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/msg/group.js +293 -0
- package/dist/aun/msg/index.js +4 -0
- package/dist/aun/msg/p2p.js +147 -0
- package/dist/aun/msg/payload-type.js +27 -0
- package/dist/aun/msg/upload.js +98 -0
- package/dist/aun/outbox.js +138 -0
- package/dist/aun/rpc/caller.js +42 -0
- package/dist/aun/rpc/connection.js +34 -0
- package/dist/aun/rpc/index.js +2 -0
- package/dist/aun/storage/download.js +29 -0
- package/dist/aun/storage/index.js +3 -0
- package/dist/aun/storage/manage.js +10 -0
- package/dist/aun/storage/upload.js +35 -0
- package/dist/channels/aun.js +1340 -349
- package/dist/channels/dingtalk.js +59 -5
- package/dist/channels/feishu.js +381 -32
- package/dist/channels/qqbot.js +68 -12
- package/dist/channels/wechat.js +63 -4
- package/dist/channels/wecom.js +59 -5
- package/dist/cli/agent.js +800 -0
- package/dist/cli/bench.js +1219 -0
- package/dist/cli/index.js +4513 -0
- package/dist/{utils → cli}/init-channel.js +211 -621
- package/dist/cli/init.js +178 -0
- package/dist/cli/link-rules.js +245 -0
- package/dist/cli/net-check.js +640 -0
- package/dist/cli/watch-msg.js +589 -0
- package/dist/config-store.js +645 -0
- package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
- package/dist/core/channel-loader.js +176 -12
- package/dist/core/command-handler.js +883 -848
- package/dist/core/evolagent-registry.js +191 -371
- package/dist/core/evolagent.js +202 -238
- package/dist/core/interaction-router.js +52 -5
- package/dist/core/message/im-renderer.js +486 -0
- package/dist/core/message/items-formatter.js +68 -0
- package/dist/core/message/message-bridge.js +109 -56
- package/dist/core/message/message-log.js +93 -0
- package/dist/core/message/message-processor.js +430 -212
- package/dist/core/message/message-queue.js +13 -6
- package/dist/core/permission.js +116 -11
- package/dist/core/session/adapters/codex-session-file-adapter.js +24 -2
- package/dist/core/session/session-fs-store.js +230 -0
- package/dist/core/session/session-manager.js +740 -777
- package/dist/core/session/session-mapper.js +87 -0
- package/dist/core/trigger/manager.js +122 -0
- package/dist/core/trigger/parser.js +128 -0
- package/dist/core/trigger/scheduler.js +224 -0
- package/dist/data/error-dict.json +118 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +560 -283
- package/dist/ipc.js +49 -0
- package/dist/net-check.js +640 -0
- package/dist/paths.js +73 -9
- package/dist/types.js +8 -2
- package/dist/utils/aid-lifecycle-log.js +33 -0
- package/dist/utils/atomic-write.js +89 -0
- package/dist/utils/channel-helpers.js +46 -0
- package/dist/utils/cross-platform.js +17 -26
- package/dist/utils/error-utils.js +10 -2
- package/dist/utils/instance-registry.js +434 -0
- package/dist/utils/log-writer.js +217 -0
- package/dist/utils/logger.js +34 -77
- package/dist/utils/media-cache.js +23 -0
- package/dist/utils/npm-ops.js +163 -0
- package/dist/utils/process-introspect.js +122 -0
- package/dist/utils/stats.js +192 -0
- package/dist/watch-msg.js +544 -0
- package/evolclaw-install-aun.md +127 -47
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +52 -0
- package/kits/docs/aun/CHEATSHEET.md +17 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/aun.md +25 -0
- package/kits/docs/channels/feishu.md +27 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolclaw/AGENT_CMD.md +31 -0
- package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
- package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
- package/kits/docs/evolclaw/self-summary.md +29 -0
- package/kits/docs/evolclaw/tools.md +25 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +20 -0
- package/kits/docs/identity/identity-tools.md +26 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/eck_manifest.json +95 -0
- package/kits/rules/01-overview.md +120 -0
- package/kits/rules/02-navigation.md +75 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +49 -0
- package/kits/rules/05-venue.md +45 -0
- package/kits/rules/06-channel.md +43 -0
- package/kits/templates/system-fragments/baseagent.md +2 -0
- package/kits/templates/system-fragments/channel.md +10 -0
- package/kits/templates/system-fragments/identity.md +12 -0
- package/kits/templates/system-fragments/relation.md +9 -0
- package/kits/templates/system-fragments/runtime.md +19 -0
- package/kits/templates/system-fragments/venue.md +5 -0
- package/package.json +10 -6
- package/data/evolclaw.sample.json +0 -60
- package/dist/agents/templates.js +0 -122
- package/dist/channels/aun-ops.js +0 -275
- package/dist/cli.js +0 -2178
- package/dist/config.js +0 -591
- package/dist/core/agent-registry.js +0 -450
- package/dist/core/evolagent-schema.js +0 -72
- package/dist/core/message/stream-flusher.js +0 -238
- package/dist/core/message/thought-emitter.js +0 -162
- package/dist/core/reload-hooks.js +0 -87
- package/dist/prompts/templates.js +0 -122
- package/dist/templates/prompts.md +0 -104
- package/dist/templates/skills.md +0 -66
- package/dist/utils/channel-fingerprint.js +0 -59
- package/dist/utils/error-dict.js +0 -63
- package/dist/utils/format.js +0 -32
- package/dist/utils/init.js +0 -645
- package/dist/utils/migrate-project.js +0 -122
- package/dist/utils/reload-hooks.js +0 -87
- package/dist/utils/stats-collector.js +0 -99
- package/dist/utils/upgrade.js +0 -100
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { kitsDir, eckDebugDir, resolveRoot } from '../paths.js';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
5
|
+
// ── Param descriptions (for debug output) ──
|
|
6
|
+
const PARAM_DESCRIPTIONS = {
|
|
7
|
+
EVOLCLAW_HOME: '用户数据根目录',
|
|
8
|
+
PACKAGE_ROOT: 'evolclaw 包根目录',
|
|
9
|
+
CURRENT_PROJECT: '当前项目完整路径',
|
|
10
|
+
selfAid: '当前 agent 的 AID',
|
|
11
|
+
selfName: '当前 agent 的显示名',
|
|
12
|
+
hasPersona: '是否有 persona 内容',
|
|
13
|
+
hasWorkingMemory: '是否有 working memory',
|
|
14
|
+
peerId: '对端在该渠道的原生 ID',
|
|
15
|
+
peerKey: '对端跨渠道唯一标识(channel#urlEncode(peerId))',
|
|
16
|
+
peerName: '对端显示名',
|
|
17
|
+
peerRole: '对端角色',
|
|
18
|
+
groupId: '群组 ID(群聊时)',
|
|
19
|
+
scene: '场景类型',
|
|
20
|
+
chatType: '聊天类型',
|
|
21
|
+
channel: '当前渠道',
|
|
22
|
+
venueUid: 'venue 唯一标识',
|
|
23
|
+
project: '当前项目目录名(由 CURRENT_PROJECT 派生)',
|
|
24
|
+
sessionName: '会话名称',
|
|
25
|
+
sessionMode: '会话模式',
|
|
26
|
+
readonly: '是否只读模式',
|
|
27
|
+
canSendFile: '当前渠道是否支持发文件',
|
|
28
|
+
capabilities: '渠道能力列表',
|
|
29
|
+
baseAgent: '当前 base agent 规范值(claude/codex/gemini/hermes)',
|
|
30
|
+
baseAgentName: '当前 base agent 显示名',
|
|
31
|
+
};
|
|
32
|
+
// ── Cache ──
|
|
33
|
+
let _manifestCache = null;
|
|
34
|
+
const _sessionPathCache = new Map();
|
|
35
|
+
// ── Public API ──
|
|
36
|
+
export function loadKitManifest() {
|
|
37
|
+
_manifestCache = loadAndMergeManifest();
|
|
38
|
+
logger.info(`[KitRenderer] Loaded manifest: ${_manifestCache.length} sections`);
|
|
39
|
+
}
|
|
40
|
+
export function invalidateKitCache() {
|
|
41
|
+
_manifestCache = null;
|
|
42
|
+
_sessionPathCache.clear();
|
|
43
|
+
}
|
|
44
|
+
export function invalidateSessionCache(sessionId) {
|
|
45
|
+
_sessionPathCache.delete(sessionId);
|
|
46
|
+
}
|
|
47
|
+
export function renderKitSections(ctx) {
|
|
48
|
+
if (!_manifestCache)
|
|
49
|
+
loadKitManifest();
|
|
50
|
+
const sections = _manifestCache;
|
|
51
|
+
const fileParts = [];
|
|
52
|
+
for (const section of sections) {
|
|
53
|
+
if (section.enabled === false)
|
|
54
|
+
continue;
|
|
55
|
+
if (!evaluateWhen(section.when, ctx.vars))
|
|
56
|
+
continue;
|
|
57
|
+
const files = loadSectionFiles(section, ctx);
|
|
58
|
+
if (files.length === 0)
|
|
59
|
+
continue;
|
|
60
|
+
for (const [filePath, rawContent] of files) {
|
|
61
|
+
const content = section.needsInjection ? renderTemplate(rawContent, ctx.vars) : rawContent;
|
|
62
|
+
if (!content.trim())
|
|
63
|
+
continue;
|
|
64
|
+
const label = section.description ? `${section.id} — ${section.description}` : section.id;
|
|
65
|
+
fileParts.push(`Contenu de ${filePath} (${label}):\n\n${content.trimEnd()}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (fileParts.length === 0)
|
|
69
|
+
return '';
|
|
70
|
+
const body = fileParts.join('\n\n');
|
|
71
|
+
const output = `<system-reminder>\nEvolClaw Context Kit documents are shown below.\n\n${body}\n\nIMPORTANT: Use this context when it affects the current interaction.\n</system-reminder>`;
|
|
72
|
+
writeDebugFiles(ctx, output);
|
|
73
|
+
return output;
|
|
74
|
+
}
|
|
75
|
+
export function cleanEckDebug() {
|
|
76
|
+
const dir = eckDebugDir();
|
|
77
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
78
|
+
try {
|
|
79
|
+
for (const f of fs.readdirSync(dir)) {
|
|
80
|
+
const fp = path.join(dir, f);
|
|
81
|
+
try {
|
|
82
|
+
if (fs.statSync(fp).mtimeMs < cutoff)
|
|
83
|
+
fs.unlinkSync(fp);
|
|
84
|
+
}
|
|
85
|
+
catch { /* skip */ }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { /* dir doesn't exist yet */ }
|
|
89
|
+
}
|
|
90
|
+
// CHUNK_CONTINUE_2
|
|
91
|
+
// ── Manifest loading ──
|
|
92
|
+
function loadAndMergeManifest() {
|
|
93
|
+
const kitsPath = path.join(kitsDir(), 'eck_manifest.json');
|
|
94
|
+
const eckPath = path.join(resolveRoot(), 'eck', 'eck_manifest.json');
|
|
95
|
+
let base;
|
|
96
|
+
try {
|
|
97
|
+
base = JSON.parse(fs.readFileSync(kitsPath, 'utf-8'));
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
logger.error(`[KitRenderer] Failed to load kits/eck_manifest.json: ${err}`);
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
if (!fs.existsSync(eckPath)) {
|
|
104
|
+
return sortSections(base.sections);
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const override = JSON.parse(fs.readFileSync(eckPath, 'utf-8'));
|
|
108
|
+
if (override.mode === 'replace') {
|
|
109
|
+
return sortSections(override.sections);
|
|
110
|
+
}
|
|
111
|
+
const merged = new Map();
|
|
112
|
+
for (const s of base.sections)
|
|
113
|
+
merged.set(s.id, { ...s });
|
|
114
|
+
for (const s of override.sections) {
|
|
115
|
+
const existing = merged.get(s.id);
|
|
116
|
+
if (existing) {
|
|
117
|
+
merged.set(s.id, { ...existing, ...s });
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
merged.set(s.id, s);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return sortSections([...merged.values()]);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
logger.warn(`[KitRenderer] Failed to load eck override, using kits only: ${err}`);
|
|
127
|
+
return sortSections(base.sections);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function sortSections(sections) {
|
|
131
|
+
return sections.slice().sort((a, b) => a.order - b.order);
|
|
132
|
+
}
|
|
133
|
+
// ── Section content loading ──
|
|
134
|
+
function loadSectionFiles(section, ctx) {
|
|
135
|
+
if (section.type === 'file' && section.file) {
|
|
136
|
+
const result = loadFileSection(section.file, ctx);
|
|
137
|
+
return result ? [result] : [];
|
|
138
|
+
}
|
|
139
|
+
if (section.type === 'directory' && section.path) {
|
|
140
|
+
return loadDirectorySection(section.path, section.pattern, ctx);
|
|
141
|
+
}
|
|
142
|
+
return [];
|
|
143
|
+
}
|
|
144
|
+
function loadFileSection(filePath, ctx) {
|
|
145
|
+
const resolved = resolvePath(filePath, ctx);
|
|
146
|
+
if (!resolved)
|
|
147
|
+
return null;
|
|
148
|
+
const sessionCache = getSessionCache(ctx.sessionId);
|
|
149
|
+
if (sessionCache.has(resolved))
|
|
150
|
+
return [resolved, sessionCache.get(resolved)];
|
|
151
|
+
try {
|
|
152
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
153
|
+
sessionCache.set(resolved, content);
|
|
154
|
+
return [resolved, content];
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function loadDirectorySection(dirPath, pattern, ctx) {
|
|
161
|
+
const resolved = resolvePath(dirPath, ctx);
|
|
162
|
+
if (!resolved)
|
|
163
|
+
return [];
|
|
164
|
+
return readDirectoryFiles(resolved, pattern).map(([name, content]) => [path.join(resolved, name), content]);
|
|
165
|
+
}
|
|
166
|
+
// ── Path resolution ──
|
|
167
|
+
function resolvePath(rawPath, ctx) {
|
|
168
|
+
let resolved = rawPath.replace(/\$([A-Z_]+)/g, (_, name) => {
|
|
169
|
+
const val = ctx.vars[name];
|
|
170
|
+
if (val === undefined || val === null || val === false || val === '')
|
|
171
|
+
return '';
|
|
172
|
+
return String(val);
|
|
173
|
+
});
|
|
174
|
+
resolved = resolved.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
175
|
+
const val = ctx.vars[key];
|
|
176
|
+
if (val === undefined || val === null || val === false || val === '')
|
|
177
|
+
return '';
|
|
178
|
+
return String(val);
|
|
179
|
+
});
|
|
180
|
+
if (!resolved || resolved.includes('$') || resolved.includes('{{'))
|
|
181
|
+
return null;
|
|
182
|
+
if (!fs.existsSync(resolved))
|
|
183
|
+
return null;
|
|
184
|
+
return resolved;
|
|
185
|
+
}
|
|
186
|
+
// CHUNK_CONTINUE_5
|
|
187
|
+
// ── Directory reading ──
|
|
188
|
+
function readDirectoryFiles(dirPath, pattern) {
|
|
189
|
+
const glob = pattern || '*.md';
|
|
190
|
+
try {
|
|
191
|
+
const files = fs.readdirSync(dirPath)
|
|
192
|
+
.filter(f => matchGlob(f, glob))
|
|
193
|
+
.sort();
|
|
194
|
+
return files.map(f => {
|
|
195
|
+
const content = fs.readFileSync(path.join(dirPath, f), 'utf-8');
|
|
196
|
+
return [f, content];
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function matchGlob(filename, pattern) {
|
|
204
|
+
const regex = pattern
|
|
205
|
+
.replace(/\./g, '\\.')
|
|
206
|
+
.replace(/\*/g, '.*')
|
|
207
|
+
.replace(/\{([^}]+)\}/g, (_, alts) => `(${alts.split(',').join('|')})`);
|
|
208
|
+
return new RegExp(`^${regex}$`).test(filename);
|
|
209
|
+
}
|
|
210
|
+
// ── When condition evaluation ──
|
|
211
|
+
function evaluateWhen(when, vars) {
|
|
212
|
+
if (when === 'always')
|
|
213
|
+
return true;
|
|
214
|
+
if (when.var !== undefined) {
|
|
215
|
+
const val = vars[when.var];
|
|
216
|
+
if (when.eq !== undefined)
|
|
217
|
+
return val === when.eq;
|
|
218
|
+
if (when.neq !== undefined)
|
|
219
|
+
return val !== when.neq;
|
|
220
|
+
if (when.in !== undefined)
|
|
221
|
+
return when.in.includes(val);
|
|
222
|
+
if (when.nin !== undefined)
|
|
223
|
+
return !when.nin.includes(val);
|
|
224
|
+
}
|
|
225
|
+
if (when.any)
|
|
226
|
+
return when.any.some(k => isTruthy(vars[k]));
|
|
227
|
+
if (when.all)
|
|
228
|
+
return when.all.every(k => isTruthy(vars[k]));
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
function isTruthy(val) {
|
|
232
|
+
return val !== undefined && val !== null && val !== false && val !== '' && val !== 0;
|
|
233
|
+
}
|
|
234
|
+
// CHUNK_CONTINUE_6
|
|
235
|
+
// ── Template rendering ──
|
|
236
|
+
function renderTemplate(template, vars) {
|
|
237
|
+
// Pass 1: conditional sections {{?key=value}}...{{/}} and {{?key}}...{{/}}
|
|
238
|
+
let result = template.replace(/\{\{\?(\w+)(?:=([^}]*))?\}\}([\s\S]*?)\{\{\/\}\}/g, (_match, key, value, body) => {
|
|
239
|
+
if (value !== undefined) {
|
|
240
|
+
return String(vars[key]) === value ? body : '';
|
|
241
|
+
}
|
|
242
|
+
return isTruthy(vars[key]) ? body : '';
|
|
243
|
+
});
|
|
244
|
+
// Pass 2: variable substitution {{key}}
|
|
245
|
+
result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
246
|
+
const val = vars[key];
|
|
247
|
+
if (!isTruthy(val))
|
|
248
|
+
return '';
|
|
249
|
+
return String(val);
|
|
250
|
+
});
|
|
251
|
+
// Pass 3: remove blank lines
|
|
252
|
+
return result.split('\n').filter(line => line.trim() !== '').join('\n');
|
|
253
|
+
}
|
|
254
|
+
// ── Session cache helper ──
|
|
255
|
+
function getSessionCache(sessionId) {
|
|
256
|
+
let cache = _sessionPathCache.get(sessionId);
|
|
257
|
+
if (!cache) {
|
|
258
|
+
cache = new Map();
|
|
259
|
+
_sessionPathCache.set(sessionId, cache);
|
|
260
|
+
}
|
|
261
|
+
return cache;
|
|
262
|
+
}
|
|
263
|
+
// ── Debug output ──
|
|
264
|
+
function writeDebugFiles(ctx, output) {
|
|
265
|
+
const now = new Date();
|
|
266
|
+
const ts = now.toISOString().replace(/[T:.]/g, '-').slice(0, 19);
|
|
267
|
+
const dir = eckDebugDir();
|
|
268
|
+
const varsData = {
|
|
269
|
+
timestamp: now.toISOString(),
|
|
270
|
+
sessionId: ctx.sessionId,
|
|
271
|
+
params: Object.entries(ctx.vars)
|
|
272
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
273
|
+
.map(([name, value]) => ({
|
|
274
|
+
name,
|
|
275
|
+
value,
|
|
276
|
+
description: PARAM_DESCRIPTIONS[name] || '',
|
|
277
|
+
})),
|
|
278
|
+
};
|
|
279
|
+
fs.writeFile(path.join(dir, `vars-${ts}.json`), JSON.stringify(varsData, null, 2), () => { });
|
|
280
|
+
fs.writeFile(path.join(dir, `context-${ts}.md`), output, () => { });
|
|
281
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseagent credential resolvers.
|
|
3
|
+
*
|
|
4
|
+
* 输入是 Config 形态(`config.agents.<baseagent>` + override)。启动期由 index.ts
|
|
5
|
+
* 从 primaryAgent.config.baseagents 构造一个 syntheticConfig 喂入;各 plugin 的
|
|
6
|
+
* createAgent 也各自构造 syntheticConfig。
|
|
7
|
+
*/
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
import { commandExists } from '../utils/cross-platform.js';
|
|
12
|
+
function loadClaudeSettings() {
|
|
13
|
+
try {
|
|
14
|
+
const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
|
|
15
|
+
if (fs.existsSync(settingsPath)) {
|
|
16
|
+
return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
catch { }
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
export function resolveAnthropicConfig(config, override) {
|
|
23
|
+
const settings = loadClaudeSettings();
|
|
24
|
+
const isPlaceholder = (v) => !v || v.includes('your-') || v.includes('placeholder');
|
|
25
|
+
const overrideApiKey = isPlaceholder(override?.apiKey) ? undefined : override?.apiKey;
|
|
26
|
+
const globalApiKey = isPlaceholder(config.agents?.claude?.apiKey) ? undefined : config.agents?.claude?.apiKey;
|
|
27
|
+
const apiKey = overrideApiKey
|
|
28
|
+
|| globalApiKey
|
|
29
|
+
|| process.env.ANTHROPIC_AUTH_TOKEN
|
|
30
|
+
|| settings.env?.ANTHROPIC_AUTH_TOKEN;
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new Error('No API key found. Set one of: baseagents.claude.apiKey (per-agent or defaults), env ANTHROPIC_AUTH_TOKEN, or ~/.claude/settings.json env.ANTHROPIC_AUTH_TOKEN');
|
|
33
|
+
}
|
|
34
|
+
const isPlaceholderUrl = (v) => !v || v.includes('api.anthropic.com');
|
|
35
|
+
const overrideBaseUrl = isPlaceholderUrl(override?.baseUrl) ? undefined : override?.baseUrl;
|
|
36
|
+
const globalBaseUrl = isPlaceholderUrl(config.agents?.claude?.baseUrl) ? undefined : config.agents?.claude?.baseUrl;
|
|
37
|
+
const baseUrl = overrideBaseUrl
|
|
38
|
+
|| globalBaseUrl
|
|
39
|
+
|| process.env.ANTHROPIC_BASE_URL
|
|
40
|
+
|| settings.env?.ANTHROPIC_BASE_URL;
|
|
41
|
+
const model = override?.model
|
|
42
|
+
|| config.agents?.claude?.model
|
|
43
|
+
|| settings.model
|
|
44
|
+
|| 'sonnet';
|
|
45
|
+
const effort = override?.effort
|
|
46
|
+
|| config.agents?.claude?.effort
|
|
47
|
+
|| settings.effortLevel
|
|
48
|
+
|| undefined;
|
|
49
|
+
const pickExec = (v) => (!v || v.includes('your-') || v.includes('placeholder')) ? undefined : v;
|
|
50
|
+
const pathToClaudeCodeExecutable = pickExec(override?.pathToClaudeCodeExecutable)
|
|
51
|
+
|| pickExec(config.agents?.claude?.pathToClaudeCodeExecutable);
|
|
52
|
+
return { apiKey, baseUrl, model, effort, pathToClaudeCodeExecutable };
|
|
53
|
+
}
|
|
54
|
+
function loadCodexSettings() {
|
|
55
|
+
try {
|
|
56
|
+
const authPath = path.join(os.homedir(), '.codex', 'auth.json');
|
|
57
|
+
let apiKey;
|
|
58
|
+
if (fs.existsSync(authPath)) {
|
|
59
|
+
const auth = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
|
|
60
|
+
apiKey = auth.OPENAI_API_KEY;
|
|
61
|
+
}
|
|
62
|
+
const configPath = path.join(os.homedir(), '.codex', 'config.toml');
|
|
63
|
+
let model;
|
|
64
|
+
let baseUrl;
|
|
65
|
+
if (fs.existsSync(configPath)) {
|
|
66
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
67
|
+
const modelMatch = content.match(/^model\s*=\s*"([^"]+)"/m);
|
|
68
|
+
if (modelMatch)
|
|
69
|
+
model = modelMatch[1];
|
|
70
|
+
const providerMatch = content.match(/^model_provider\s*=\s*"([^"]+)"/m);
|
|
71
|
+
if (providerMatch) {
|
|
72
|
+
const provider = providerMatch[1];
|
|
73
|
+
const baseUrlMatch = content.match(new RegExp(`\\[model_providers\\.${provider}\\][\\s\\S]*?base_url\\s*=\\s*"([^"]+)"`, 'm'));
|
|
74
|
+
if (baseUrlMatch)
|
|
75
|
+
baseUrl = baseUrlMatch[1];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return { apiKey, baseUrl, model };
|
|
79
|
+
}
|
|
80
|
+
catch { }
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
export function resolveOpenaiConfig(config, override) {
|
|
84
|
+
const codexSettings = loadCodexSettings();
|
|
85
|
+
const isPlaceholder = (v) => !v || v.includes('your-') || v.includes('placeholder');
|
|
86
|
+
const overrideApiKey = isPlaceholder(override?.apiKey) ? undefined : override?.apiKey;
|
|
87
|
+
const globalApiKey = isPlaceholder(config.agents?.codex?.apiKey) ? undefined : config.agents?.codex?.apiKey;
|
|
88
|
+
const apiKey = overrideApiKey
|
|
89
|
+
|| globalApiKey
|
|
90
|
+
|| process.env.OPENAI_API_KEY
|
|
91
|
+
|| codexSettings.apiKey;
|
|
92
|
+
if (!apiKey) {
|
|
93
|
+
throw new Error('No OpenAI API key found. Set one of: baseagents.codex.apiKey (per-agent or defaults), env OPENAI_API_KEY, or ~/.codex/auth.json');
|
|
94
|
+
}
|
|
95
|
+
const isPlaceholderUrl = (v) => !v || v.includes('api.openai.com');
|
|
96
|
+
const overrideBaseUrl = isPlaceholderUrl(override?.baseUrl) ? undefined : override?.baseUrl;
|
|
97
|
+
const globalBaseUrl = isPlaceholderUrl(config.agents?.codex?.baseUrl) ? undefined : config.agents?.codex?.baseUrl;
|
|
98
|
+
const baseUrl = overrideBaseUrl
|
|
99
|
+
|| globalBaseUrl
|
|
100
|
+
|| process.env.OPENAI_BASE_URL
|
|
101
|
+
|| codexSettings.baseUrl
|
|
102
|
+
|| undefined;
|
|
103
|
+
const model = override?.model
|
|
104
|
+
|| config.agents?.codex?.model
|
|
105
|
+
|| codexSettings.model
|
|
106
|
+
|| 'gpt-5.2-codex';
|
|
107
|
+
const effort = override?.effort
|
|
108
|
+
|| override?.reasoning
|
|
109
|
+
|| config.agents?.codex?.effort
|
|
110
|
+
|| config.agents?.codex?.reasoning
|
|
111
|
+
|| undefined;
|
|
112
|
+
return { apiKey, baseUrl, model, effort };
|
|
113
|
+
}
|
|
114
|
+
export function resolveGoogleConfig(config, override) {
|
|
115
|
+
const googleCfg = config.agents?.gemini;
|
|
116
|
+
const isPlaceholder = (v) => !v || v.includes('your-') || v.includes('placeholder');
|
|
117
|
+
let cliPath = override?.cliPath || googleCfg?.cliPath || '';
|
|
118
|
+
if (!cliPath) {
|
|
119
|
+
cliPath = commandExists('gemini') ? 'gemini' : '';
|
|
120
|
+
}
|
|
121
|
+
const model = override?.model || googleCfg?.model || 'gemini-2.5-flash';
|
|
122
|
+
const overrideApiKey = isPlaceholder(override?.apiKey) ? undefined : override?.apiKey;
|
|
123
|
+
const globalApiKey = isPlaceholder(googleCfg?.apiKey) ? undefined : googleCfg?.apiKey;
|
|
124
|
+
const apiKey = overrideApiKey
|
|
125
|
+
|| globalApiKey
|
|
126
|
+
|| process.env.GEMINI_API_KEY
|
|
127
|
+
|| process.env.GOOGLE_API_KEY
|
|
128
|
+
|| undefined;
|
|
129
|
+
const mode = override?.mode || googleCfg?.mode || 'cli';
|
|
130
|
+
const useVertex = override?.useVertex ?? googleCfg?.useVertex ?? false;
|
|
131
|
+
const project = override?.project || googleCfg?.project || process.env.GOOGLE_CLOUD_PROJECT || undefined;
|
|
132
|
+
const location = override?.location || googleCfg?.location || process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
|
|
133
|
+
return { cliPath, model, apiKey, mode, useVertex, project, location };
|
|
134
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { getAunClient } from './client.js';
|
|
5
|
+
export function buildInitialAgentMd(opts) {
|
|
6
|
+
const agentName = opts.aid.split('.')[0];
|
|
7
|
+
const agentType = opts.type || 'ai';
|
|
8
|
+
return `---\naid: "${opts.aid}"\nname: "${agentName}"\ntype: "${agentType}"\nversion: "1.0.0"\ndescription: ""\ntags:\n - evolclaw\n---\n`;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the gateway URL for an AID via .well-known discovery.
|
|
12
|
+
*/
|
|
13
|
+
async function discoverGateway(aid) {
|
|
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;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Verify agent.md content using SDK.
|
|
62
|
+
*/
|
|
63
|
+
async function verifyContent(content, aid, certPem, client) {
|
|
64
|
+
if (!content.includes('AUN-SIGNATURE')) {
|
|
65
|
+
return { status: 'unsigned' };
|
|
66
|
+
}
|
|
67
|
+
if (!certPem) {
|
|
68
|
+
return { status: 'invalid', reason: 'certificate not available' };
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const result = await client.auth.verifyAgentMd(content, { aid, certPem });
|
|
72
|
+
if (result.status === 'verified' || result.verified) {
|
|
73
|
+
return { status: 'verified' };
|
|
74
|
+
}
|
|
75
|
+
return { status: 'invalid', reason: result.reason };
|
|
76
|
+
}
|
|
77
|
+
catch (e) {
|
|
78
|
+
return { status: 'invalid', reason: `verify error: ${String(e.message || e).slice(0, 100)}` };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Create a bare AUNClient (no createAid) for read-only operations.
|
|
83
|
+
*/
|
|
84
|
+
async function createBareClient(aunPath) {
|
|
85
|
+
const { AUNClient } = await import('@agentunion/fastaun');
|
|
86
|
+
const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
87
|
+
const clientOpts = { aun_path: aunPath, debug: false };
|
|
88
|
+
if (fs.existsSync(caCertPath))
|
|
89
|
+
clientOpts.root_ca_path = caCertPath;
|
|
90
|
+
return new AUNClient(clientOpts);
|
|
91
|
+
}
|
|
92
|
+
export async function agentmdGet(aid, opts) {
|
|
93
|
+
const aunPath = opts?.aunPath ?? path.join(os.homedir(), '.aun');
|
|
94
|
+
const localPath = path.join(aunPath, 'AIDs', aid, 'agent.md');
|
|
95
|
+
// === Path A: local agent.md exists ===
|
|
96
|
+
if (fs.existsSync(localPath)) {
|
|
97
|
+
const content = fs.readFileSync(localPath, 'utf-8');
|
|
98
|
+
if (!opts?.withVerification)
|
|
99
|
+
return content;
|
|
100
|
+
// Verify local content
|
|
101
|
+
const client = opts?.client ?? await createBareClient(aunPath);
|
|
102
|
+
const ownClient = !opts?.client;
|
|
103
|
+
try {
|
|
104
|
+
const certPem = await obtainCertPem(aid, aunPath, client);
|
|
105
|
+
const verification = await verifyContent(content, aid, certPem, client);
|
|
106
|
+
if (verification.status !== 'invalid') {
|
|
107
|
+
return { content, verification };
|
|
108
|
+
}
|
|
109
|
+
// Fallback: local invalid → try remote
|
|
110
|
+
try {
|
|
111
|
+
const remote = await client.auth.downloadAgentMd(aid);
|
|
112
|
+
if (remote) {
|
|
113
|
+
const remoteVerification = await verifyContent(remote, aid, certPem, client);
|
|
114
|
+
if (remoteVerification.status === 'verified') {
|
|
115
|
+
fs.writeFileSync(localPath, remote, 'utf-8');
|
|
116
|
+
return { content: remote, verification: remoteVerification };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch { /* remote fetch failed, return local invalid result */ }
|
|
121
|
+
return { content, verification };
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
if (ownClient)
|
|
125
|
+
try {
|
|
126
|
+
await client.close();
|
|
127
|
+
}
|
|
128
|
+
catch { /* ignore */ }
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// === Path B: no local agent.md → download from remote ===
|
|
132
|
+
const client = opts?.client ?? await createBareClient(aunPath);
|
|
133
|
+
const ownClient = !opts?.client;
|
|
134
|
+
try {
|
|
135
|
+
const raw = await client.auth.downloadAgentMd(aid);
|
|
136
|
+
if (!opts?.withVerification) {
|
|
137
|
+
// Persist without verification
|
|
138
|
+
const aidDir = path.join(aunPath, 'AIDs', aid);
|
|
139
|
+
fs.mkdirSync(aidDir, { recursive: true });
|
|
140
|
+
fs.writeFileSync(path.join(aidDir, 'agent.md'), raw, 'utf-8');
|
|
141
|
+
return raw;
|
|
142
|
+
}
|
|
143
|
+
const certPem = await obtainCertPem(aid, aunPath, client);
|
|
144
|
+
const verification = await verifyContent(raw, aid, certPem, client);
|
|
145
|
+
// Persist to local
|
|
146
|
+
const aidDir = path.join(aunPath, 'AIDs', aid);
|
|
147
|
+
fs.mkdirSync(aidDir, { recursive: true });
|
|
148
|
+
fs.writeFileSync(path.join(aidDir, 'agent.md'), raw, 'utf-8');
|
|
149
|
+
return { content: raw, verification };
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
if (ownClient)
|
|
153
|
+
try {
|
|
154
|
+
await client.close();
|
|
155
|
+
}
|
|
156
|
+
catch { /* ignore */ }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Upload agent.md: auto-sign + upload + sync to local file.
|
|
161
|
+
*/
|
|
162
|
+
export async function agentmdPut(content, opts) {
|
|
163
|
+
const aunPath = opts.aunPath ?? path.join(os.homedir(), '.aun');
|
|
164
|
+
const client = opts.client ?? await getAunClient(opts.aid, { aunPath });
|
|
165
|
+
const ownClient = !opts.client;
|
|
166
|
+
try {
|
|
167
|
+
let signed;
|
|
168
|
+
try {
|
|
169
|
+
signed = await client.auth.signAgentMd(content);
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
signed = content;
|
|
173
|
+
}
|
|
174
|
+
await client.auth.uploadAgentMd(signed);
|
|
175
|
+
const aidDir = path.join(aunPath, 'AIDs', opts.aid);
|
|
176
|
+
fs.mkdirSync(aidDir, { recursive: true });
|
|
177
|
+
fs.writeFileSync(path.join(aidDir, 'agent.md'), signed, 'utf-8');
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
if (ownClient)
|
|
181
|
+
try {
|
|
182
|
+
await client.close();
|
|
183
|
+
}
|
|
184
|
+
catch { /* ignore */ }
|
|
185
|
+
}
|
|
186
|
+
}
|