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.
Files changed (142) hide show
  1. package/README.md +21 -12
  2. package/bin/ec.js +29 -0
  3. package/dist/agents/baseagent-normalize.js +19 -0
  4. package/dist/agents/claude-runner.js +108 -46
  5. package/dist/agents/codex-runner.js +13 -14
  6. package/dist/agents/gemini-runner.js +15 -17
  7. package/dist/agents/kit-renderer.js +281 -0
  8. package/dist/agents/resolve.js +134 -0
  9. package/dist/aun/aid/agentmd.js +186 -0
  10. package/dist/aun/aid/client.js +134 -0
  11. package/dist/aun/aid/identity.js +159 -0
  12. package/dist/aun/aid/index.js +3 -0
  13. package/dist/aun/aid/lifecycle-log.js +33 -0
  14. package/dist/aun/aid/types.js +1 -0
  15. package/dist/aun/aid/validation.js +21 -0
  16. package/dist/aun/msg/group.js +293 -0
  17. package/dist/aun/msg/index.js +4 -0
  18. package/dist/aun/msg/p2p.js +147 -0
  19. package/dist/aun/msg/payload-type.js +27 -0
  20. package/dist/aun/msg/upload.js +98 -0
  21. package/dist/aun/outbox.js +138 -0
  22. package/dist/aun/rpc/caller.js +42 -0
  23. package/dist/aun/rpc/connection.js +34 -0
  24. package/dist/aun/rpc/index.js +2 -0
  25. package/dist/aun/storage/download.js +29 -0
  26. package/dist/aun/storage/index.js +3 -0
  27. package/dist/aun/storage/manage.js +10 -0
  28. package/dist/aun/storage/upload.js +35 -0
  29. package/dist/channels/aun.js +1340 -349
  30. package/dist/channels/dingtalk.js +59 -5
  31. package/dist/channels/feishu.js +381 -32
  32. package/dist/channels/qqbot.js +68 -12
  33. package/dist/channels/wechat.js +63 -4
  34. package/dist/channels/wecom.js +59 -5
  35. package/dist/cli/agent.js +800 -0
  36. package/dist/cli/bench.js +1219 -0
  37. package/dist/cli/index.js +4513 -0
  38. package/dist/{utils → cli}/init-channel.js +211 -621
  39. package/dist/cli/init.js +178 -0
  40. package/dist/cli/link-rules.js +245 -0
  41. package/dist/cli/net-check.js +640 -0
  42. package/dist/cli/watch-msg.js +589 -0
  43. package/dist/config-store.js +645 -0
  44. package/dist/core/{agent-loader.js → baseagent-loader.js} +6 -12
  45. package/dist/core/channel-loader.js +176 -12
  46. package/dist/core/command-handler.js +883 -848
  47. package/dist/core/evolagent-registry.js +191 -371
  48. package/dist/core/evolagent.js +202 -238
  49. package/dist/core/interaction-router.js +52 -5
  50. package/dist/core/message/im-renderer.js +486 -0
  51. package/dist/core/message/items-formatter.js +68 -0
  52. package/dist/core/message/message-bridge.js +109 -56
  53. package/dist/core/message/message-log.js +93 -0
  54. package/dist/core/message/message-processor.js +430 -212
  55. package/dist/core/message/message-queue.js +13 -6
  56. package/dist/core/permission.js +116 -11
  57. package/dist/core/session/adapters/codex-session-file-adapter.js +24 -2
  58. package/dist/core/session/session-fs-store.js +230 -0
  59. package/dist/core/session/session-manager.js +740 -777
  60. package/dist/core/session/session-mapper.js +87 -0
  61. package/dist/core/trigger/manager.js +122 -0
  62. package/dist/core/trigger/parser.js +128 -0
  63. package/dist/core/trigger/scheduler.js +224 -0
  64. package/dist/data/error-dict.json +118 -0
  65. package/dist/eck/baseagent-caps.js +18 -0
  66. package/dist/eck/detect.js +47 -0
  67. package/dist/eck/init.js +77 -0
  68. package/dist/eck/rules-loader.js +28 -0
  69. package/dist/index.js +560 -283
  70. package/dist/ipc.js +49 -0
  71. package/dist/net-check.js +640 -0
  72. package/dist/paths.js +73 -9
  73. package/dist/types.js +8 -2
  74. package/dist/utils/aid-lifecycle-log.js +33 -0
  75. package/dist/utils/atomic-write.js +89 -0
  76. package/dist/utils/channel-helpers.js +46 -0
  77. package/dist/utils/cross-platform.js +17 -26
  78. package/dist/utils/error-utils.js +10 -2
  79. package/dist/utils/instance-registry.js +434 -0
  80. package/dist/utils/log-writer.js +217 -0
  81. package/dist/utils/logger.js +34 -77
  82. package/dist/utils/media-cache.js +23 -0
  83. package/dist/utils/npm-ops.js +163 -0
  84. package/dist/utils/process-introspect.js +122 -0
  85. package/dist/utils/stats.js +192 -0
  86. package/dist/watch-msg.js +544 -0
  87. package/evolclaw-install-aun.md +127 -47
  88. package/kits/docs/GUIDE.md +20 -0
  89. package/kits/docs/INDEX.md +52 -0
  90. package/kits/docs/aun/CHEATSHEET.md +17 -0
  91. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  92. package/kits/docs/channels/aun.md +25 -0
  93. package/kits/docs/channels/feishu.md +27 -0
  94. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  95. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  96. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  97. package/kits/docs/eck_templates/runtime.template.md +19 -0
  98. package/kits/docs/evolclaw/AGENT_CMD.md +31 -0
  99. package/kits/docs/evolclaw/MSG_GROUP.md +30 -0
  100. package/kits/docs/evolclaw/MSG_PRIVATE.md +25 -0
  101. package/kits/docs/evolclaw/self-summary.md +29 -0
  102. package/kits/docs/evolclaw/tools.md +25 -0
  103. package/kits/docs/identity/AID_PROFILE_SPEC.md +27 -0
  104. package/kits/docs/identity/PATH_OPS.md +16 -0
  105. package/kits/docs/identity/ROLE_DETAIL.md +20 -0
  106. package/kits/docs/identity/identity-tools.md +26 -0
  107. package/kits/docs/path-registry.md +43 -0
  108. package/kits/eck_manifest.json +95 -0
  109. package/kits/rules/01-overview.md +120 -0
  110. package/kits/rules/02-navigation.md +75 -0
  111. package/kits/rules/03-identity.md +34 -0
  112. package/kits/rules/04-relation.md +49 -0
  113. package/kits/rules/05-venue.md +45 -0
  114. package/kits/rules/06-channel.md +43 -0
  115. package/kits/templates/system-fragments/baseagent.md +2 -0
  116. package/kits/templates/system-fragments/channel.md +10 -0
  117. package/kits/templates/system-fragments/identity.md +12 -0
  118. package/kits/templates/system-fragments/relation.md +9 -0
  119. package/kits/templates/system-fragments/runtime.md +19 -0
  120. package/kits/templates/system-fragments/venue.md +5 -0
  121. package/package.json +10 -6
  122. package/data/evolclaw.sample.json +0 -60
  123. package/dist/agents/templates.js +0 -122
  124. package/dist/channels/aun-ops.js +0 -275
  125. package/dist/cli.js +0 -2178
  126. package/dist/config.js +0 -591
  127. package/dist/core/agent-registry.js +0 -450
  128. package/dist/core/evolagent-schema.js +0 -72
  129. package/dist/core/message/stream-flusher.js +0 -238
  130. package/dist/core/message/thought-emitter.js +0 -162
  131. package/dist/core/reload-hooks.js +0 -87
  132. package/dist/prompts/templates.js +0 -122
  133. package/dist/templates/prompts.md +0 -104
  134. package/dist/templates/skills.md +0 -66
  135. package/dist/utils/channel-fingerprint.js +0 -59
  136. package/dist/utils/error-dict.js +0 -63
  137. package/dist/utils/format.js +0 -32
  138. package/dist/utils/init.js +0 -645
  139. package/dist/utils/migrate-project.js +0 -122
  140. package/dist/utils/reload-hooks.js +0 -87
  141. package/dist/utils/stats-collector.js +0 -99
  142. 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
+ }