evolclaw 3.1.2 → 3.1.4
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 +38 -0
- package/README.md +2 -6
- 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 +1 -1
- package/dist/agents/codex-runner.js +75 -19
- package/dist/agents/gemini-runner.js +0 -2
- package/dist/agents/kit-renderer.js +85 -22
- package/dist/aun/aid/agentmd.js +67 -74
- package/dist/aun/aid/client.js +22 -7
- package/dist/aun/aid/identity.js +314 -28
- package/dist/aun/aid/index.js +2 -2
- package/dist/aun/rpc/connection.js +8 -10
- package/dist/channels/aun.js +53 -41
- package/dist/cli/agent.js +28 -28
- package/dist/cli/bench.js +8 -14
- package/dist/cli/help.js +23 -0
- package/dist/cli/index.js +398 -73
- package/dist/cli/init-channel.js +2 -3
- package/dist/cli/init.js +13 -6
- package/dist/cli/link-rules.js +2 -1
- package/dist/cli/net-check.js +10 -11
- package/dist/core/command-handler.js +621 -541
- package/dist/core/evolagent.js +31 -0
- package/dist/core/message/im-renderer.js +10 -0
- package/dist/core/message/message-bridge.js +123 -24
- package/dist/core/message/message-processor.js +61 -31
- package/dist/core/relation/peer-identity.js +64 -21
- package/dist/core/session/session-manager.js +191 -44
- package/dist/core/trigger/manager.js +37 -0
- package/dist/index.js +4 -1
- package/dist/paths.js +87 -16
- package/dist/utils/npm-ops.js +18 -11
- package/kits/eck_manifest.json +9 -9
- package/kits/rules/02-navigation.md +1 -0
- package/kits/rules/05-venue.md +2 -2
- package/kits/rules/06-channel.md +2 -18
- package/kits/templates/system-fragments/baseagent.md +8 -2
- package/kits/templates/system-fragments/channel.md +20 -8
- package/kits/templates/system-fragments/identity.md +5 -6
- package/kits/templates/system-fragments/relation.md +10 -5
- package/kits/templates/system-fragments/session.md +20 -0
- package/kits/templates/system-fragments/venue.md +5 -3
- package/package.json +4 -2
- package/dist/net-check.js +0 -640
- package/dist/watch-msg.js +0 -544
- package/kits/templates/system-fragments/runtime.md +0 -19
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v3.1.4 (2026-05-27)
|
|
4
|
+
|
|
5
|
+
### New Features
|
|
6
|
+
|
|
7
|
+
- **CLI help 系统重构 + ECK 注入** — 全新 help 子命令体系,ECK(EvolClaw Kit)注入机制重构,AID identity 管理命令
|
|
8
|
+
- **Menu 协议五动词拆分** — menu 协议拆分为 list/query/options/update/action 五个动词,命令收敛至 baseagent/session
|
|
9
|
+
|
|
10
|
+
### Improvements
|
|
11
|
+
|
|
12
|
+
- **npm-ops 去 CLI 依赖** — checkLatestVersion 改用 HTTP fetch 直接查 registry API,去除对 npm CLI 的运行时依赖
|
|
13
|
+
- **agent.md 适配 SDK 0.3.3** — 统一 aun_path 到 EVOLCLAW_HOME,适配最新 SDK 版本
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- **Session channelType 缺失修复** — 修复 channelType 缺失导致 chat dir 无法解析 + isBackgroundSession 同步化
|
|
18
|
+
- **SessionManager 查询路径 NPE** — 修复缺失 channelType 时的空指针异常
|
|
19
|
+
|
|
20
|
+
## v3.1.3 (2026-05-26)
|
|
21
|
+
|
|
22
|
+
### New Features
|
|
23
|
+
|
|
24
|
+
- **Menu 协议拆分为 5 动词** — `menu.list` / `menu.query` / `menu.options` / `menu.update` / `menu.action`,response 统一 `error.code` 透传(`NO_ACTIVE_SESSION` / `MISSING_VALUE` / ...),`name → cmd` 由 bridge 内部映射,AUN 白名单接纳全部 `menu.*` 类型
|
|
25
|
+
- **xhigh effort 档位** — Claude/Codex 新增 `xhigh` 推理强度档位,Codex 通过 `codex debug models` 动态拉取模型目录与各模型支持的 effort 列表
|
|
26
|
+
- **结构化 menu options 字段** — session 子菜单返回 `preview`(首条消息预览)/ `lastActive` / `agentSessionId` / `turns` 扩展字段,所有可选项菜单返回 `selected` 标记当前值
|
|
27
|
+
|
|
28
|
+
### Improvements
|
|
29
|
+
|
|
30
|
+
- **命令收敛** — 移除 `/plist` `/project` `/bind` `/agentmd` `/p` 别名;`/agent` 改为 EvolAgent 管理命令;baseagent 切换统一为 `/baseagent`(别名 `/base`);`/aid` `/rpc` `/storage` `/evolagent` 改为 ctl 专属
|
|
31
|
+
- **execMenu 拆分** — `command-handler` 拆出 `execMenuQuery` / `execMenuUpdate` / `execMenuAction` 三入口,无活跃会话时多项 fallback 到 evolagent config(`/pwd` / `/chatmode` / `/dispatch` 等)
|
|
32
|
+
- **EvolAgent 持久化方法** — 新增 `setActiveBaseagent` / `setChatmodePrivate` / `setDispatch`,配置变更直接落盘
|
|
33
|
+
- **Codex 实例每次重建** — 通过 env 注入 `EVOLCLAW_SESSION_ID`,仅首轮拼接 `systemPromptAppend`,避免 resume 时重复污染历史
|
|
34
|
+
- **Agent plugin 启用判定** — `isEnabled` 改为按 `baseagents.<name>` 配置启用,Codex 额外检测 `@openai/codex-sdk` 是否可用,`init` / `agent new` CLI 同步该判定
|
|
35
|
+
- **context-too-long 提示文案** — 按 agent 是否支持 compact 区分提示,retry 后仍超长时清理 renderer 中混入的错误文本(新增 `IMRenderer.stripContextError`)
|
|
36
|
+
|
|
37
|
+
### Bug Fixes
|
|
38
|
+
|
|
39
|
+
- **Codex resume 历史污染** — systemPromptAppend 在 resume 轮次重复拼接到 prompt 头部,导致系统提示被反复写入对话历史
|
|
40
|
+
|
|
3
41
|
## v3.1.2 (2026-05-25)
|
|
4
42
|
|
|
5
43
|
### Improvements
|
package/README.md
CHANGED
|
@@ -243,14 +243,11 @@ evolclaw/
|
|
|
243
243
|
|
|
244
244
|
### 管理员级命令(Admin+ 可用)
|
|
245
245
|
|
|
246
|
-
|
|
246
|
+
**项目**:
|
|
247
247
|
- `/pwd` - 显示当前项目路径
|
|
248
|
-
- `/plist` - 列出所有项目(显示会话空闲时间)
|
|
249
|
-
- `/p <name|path>` - 切换项目(保留会话历史)
|
|
250
|
-
- `/bind <path>` - 绑定新项目目录
|
|
251
248
|
|
|
252
249
|
**Agent 与模型**:
|
|
253
|
-
- `/
|
|
250
|
+
- `/baseagent [name]` - 查看或切换 Agent 后端(claude / codex / gemini)(别名 `/base`)
|
|
254
251
|
- `/model [model]` - 查看或切换模型
|
|
255
252
|
- `/effort [level]` - 查看或切换推理强度(low / medium / high / max / auto)
|
|
256
253
|
- `/perm [mode]` - 查看或切换权限模式(auto / edit / default / readonly)
|
|
@@ -272,7 +269,6 @@ evolclaw/
|
|
|
272
269
|
- `/file <文件路径>` - 发送文件给用户
|
|
273
270
|
- `/restart` - 重启服务(自愈机制)
|
|
274
271
|
- `/repair` - 检查并修复会话
|
|
275
|
-
- `/agentmd [put|set]` - 管理 AUN agent.md(仅 AUN 渠道)
|
|
276
272
|
|
|
277
273
|
## 技术栈
|
|
278
274
|
|
|
Binary file
|
|
@@ -1094,7 +1094,7 @@ export class AgentRunner {
|
|
|
1094
1094
|
export class ClaudeAgentPlugin {
|
|
1095
1095
|
name = 'claude';
|
|
1096
1096
|
isEnabled(agent) {
|
|
1097
|
-
return agent.
|
|
1097
|
+
return !!agent.config.baseagents?.claude;
|
|
1098
1098
|
}
|
|
1099
1099
|
createAgent(agent, callbacks) {
|
|
1100
1100
|
const override = agent.config.baseagents?.claude;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { resolveOpenaiConfig } from './resolve.js';
|
|
9
9
|
import { logger } from '../utils/logger.js';
|
|
10
|
+
import { execFileSync } from 'child_process';
|
|
10
11
|
import fs from 'fs';
|
|
11
12
|
import path from 'path';
|
|
12
13
|
import os from 'os';
|
|
@@ -17,13 +18,58 @@ const MIME_EXT = {
|
|
|
17
18
|
'image/gif': '.gif',
|
|
18
19
|
'image/webp': '.webp',
|
|
19
20
|
};
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
const CODEX_CATALOG_FALLBACK = [
|
|
22
|
+
{ slug: 'gpt-5.5', efforts: ['low', 'medium', 'high', 'xhigh'] },
|
|
23
|
+
{ slug: 'gpt-5.4', efforts: ['low', 'medium', 'high', 'xhigh'] },
|
|
24
|
+
{ slug: 'gpt-5.4-mini', efforts: ['low', 'medium', 'high', 'xhigh'] },
|
|
25
|
+
{ slug: 'gpt-5.3-codex', efforts: ['low', 'medium', 'high', 'xhigh'] },
|
|
26
|
+
{ slug: 'gpt-5.2', efforts: ['low', 'medium', 'high', 'xhigh'] },
|
|
27
|
+
];
|
|
28
|
+
let codexCatalogCache = null;
|
|
29
|
+
export function isCodexSdkAvailable() {
|
|
30
|
+
try {
|
|
31
|
+
import.meta.resolve('@openai/codex-sdk');
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function fetchCodexCatalog() {
|
|
39
|
+
if (codexCatalogCache)
|
|
40
|
+
return codexCatalogCache;
|
|
41
|
+
try {
|
|
42
|
+
const output = execFileSync('codex', ['debug', 'models'], {
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
timeout: 5000,
|
|
45
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
46
|
+
});
|
|
47
|
+
const catalog = JSON.parse(output);
|
|
48
|
+
const models = catalog.models
|
|
49
|
+
.filter(m => m.visibility === 'list')
|
|
50
|
+
.map(m => ({
|
|
51
|
+
slug: m.slug,
|
|
52
|
+
efforts: (m.supported_reasoning_levels || []).map(l => l.effort),
|
|
53
|
+
}));
|
|
54
|
+
if (models.length > 0) {
|
|
55
|
+
codexCatalogCache = models;
|
|
56
|
+
return models;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
logger.debug(`[CodexRunner] Failed to fetch model catalog, using fallback: ${e}`);
|
|
61
|
+
}
|
|
62
|
+
return CODEX_CATALOG_FALLBACK;
|
|
63
|
+
}
|
|
64
|
+
export function getCodexEfforts(model) {
|
|
65
|
+
const catalog = fetchCodexCatalog();
|
|
66
|
+
const entry = catalog.find(m => m.slug === model);
|
|
67
|
+
return entry?.efforts ?? catalog[0]?.efforts ?? ['low', 'medium', 'high'];
|
|
68
|
+
}
|
|
22
69
|
// ── Codex Runner ──
|
|
23
70
|
export class CodexRunner {
|
|
24
71
|
name = 'codex';
|
|
25
72
|
capabilities = { clear: false, compact: false, fork: false };
|
|
26
|
-
codex = null;
|
|
27
73
|
codexModule = null;
|
|
28
74
|
model;
|
|
29
75
|
effort;
|
|
@@ -39,21 +85,25 @@ export class CodexRunner {
|
|
|
39
85
|
this.effort = this.resolvedConfig.effort;
|
|
40
86
|
this.onSessionIdUpdate = callbacks.onSessionIdUpdate;
|
|
41
87
|
}
|
|
42
|
-
async ensureCodex() {
|
|
43
|
-
if (!this.
|
|
88
|
+
async ensureCodex(sessionId) {
|
|
89
|
+
if (!this.codexModule) {
|
|
44
90
|
const { requireOptional } = await import('../utils/npm-ops.js');
|
|
45
91
|
this.codexModule = await requireOptional('@openai/codex-sdk');
|
|
46
|
-
this.codex = new this.codexModule.Codex({
|
|
47
|
-
apiKey: this.resolvedConfig.apiKey,
|
|
48
|
-
baseUrl: this.resolvedConfig.baseUrl,
|
|
49
|
-
});
|
|
50
92
|
}
|
|
51
|
-
|
|
93
|
+
const codex = new this.codexModule.Codex({
|
|
94
|
+
apiKey: this.resolvedConfig.apiKey,
|
|
95
|
+
baseUrl: this.resolvedConfig.baseUrl,
|
|
96
|
+
env: {
|
|
97
|
+
...process.env,
|
|
98
|
+
EVOLCLAW_SESSION_ID: sessionId,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
return { codex, mod: this.codexModule };
|
|
52
102
|
}
|
|
53
103
|
// ── ModelSwitcher ──
|
|
54
104
|
setModel(model) { this.model = model; }
|
|
55
105
|
getModel() { return this.model; }
|
|
56
|
-
listModels() { return
|
|
106
|
+
listModels() { return fetchCodexCatalog().map(m => m.slug); }
|
|
57
107
|
// ── Effort ──
|
|
58
108
|
setEffort(effort) { this.effort = effort; }
|
|
59
109
|
getEffort() { return this.effort; }
|
|
@@ -94,10 +144,14 @@ export class CodexRunner {
|
|
|
94
144
|
}
|
|
95
145
|
// ── Core: runQuery ──
|
|
96
146
|
async runQuery(sessionId, prompt, projectPath, initialAgentSessionId, images, systemPromptAppend, sessionManager) {
|
|
97
|
-
|
|
98
|
-
process.env.EVOLCLAW_SESSION_ID = sessionId;
|
|
99
|
-
const { codex } = await this.ensureCodex();
|
|
147
|
+
const { codex } = await this.ensureCodex(sessionId);
|
|
100
148
|
let agentSessionId = initialAgentSessionId || this.activeSessions.get(sessionId);
|
|
149
|
+
let fullPrompt = prompt;
|
|
150
|
+
// Only inject system context on the first turn; resumed Codex threads already
|
|
151
|
+
// have that context in history and repeating it will pollute the conversation.
|
|
152
|
+
if (systemPromptAppend && !agentSessionId) {
|
|
153
|
+
fullPrompt = prompt + '\n\n--- [SYSTEM_PROMPT_END] ---\n' + systemPromptAppend;
|
|
154
|
+
}
|
|
101
155
|
const threadOptions = {
|
|
102
156
|
workingDirectory: projectPath,
|
|
103
157
|
model: this.model,
|
|
@@ -116,7 +170,7 @@ export class CodexRunner {
|
|
|
116
170
|
let input;
|
|
117
171
|
if (images?.length) {
|
|
118
172
|
const tmpDir = os.tmpdir();
|
|
119
|
-
const parts = [{ type: 'text', text:
|
|
173
|
+
const parts = [{ type: 'text', text: fullPrompt }];
|
|
120
174
|
for (let i = 0; i < images.length; i++) {
|
|
121
175
|
const img = images[i];
|
|
122
176
|
const ext = MIME_EXT[img.mimeType || ''] || '.jpg';
|
|
@@ -129,7 +183,7 @@ export class CodexRunner {
|
|
|
129
183
|
logger.info(`[CodexRunner] Attached ${images.length} image(s) as local_image`);
|
|
130
184
|
}
|
|
131
185
|
else {
|
|
132
|
-
input =
|
|
186
|
+
input = fullPrompt;
|
|
133
187
|
}
|
|
134
188
|
const { events } = await thread.runStreamed(input, { signal: controller.signal });
|
|
135
189
|
// 包装为 AgentEvent 流
|
|
@@ -302,10 +356,10 @@ export class CodexRunner {
|
|
|
302
356
|
export class CodexAgentPlugin {
|
|
303
357
|
name = 'codex';
|
|
304
358
|
isEnabled(agent) {
|
|
305
|
-
if (agent.baseagent !== 'codex')
|
|
306
|
-
return false;
|
|
307
359
|
if (!agent.config.baseagents?.codex)
|
|
308
360
|
return false;
|
|
361
|
+
if (!isCodexSdkAvailable())
|
|
362
|
+
return false;
|
|
309
363
|
try {
|
|
310
364
|
const override = agent.config.baseagents.codex;
|
|
311
365
|
const syntheticConfig = { agents: { codex: override } };
|
|
@@ -317,8 +371,10 @@ export class CodexAgentPlugin {
|
|
|
317
371
|
}
|
|
318
372
|
}
|
|
319
373
|
createAgent(agent, callbacks) {
|
|
374
|
+
if (!isCodexSdkAvailable()) {
|
|
375
|
+
throw new Error('Missing optional dependency @openai/codex-sdk');
|
|
376
|
+
}
|
|
320
377
|
const override = agent.config.baseagents?.codex;
|
|
321
|
-
const syntheticConfig = { agents: { codex: override } };
|
|
322
378
|
const merged = {
|
|
323
379
|
agents: { codex: { ...(override || {}) } },
|
|
324
380
|
};
|
|
@@ -407,8 +407,6 @@ export class GeminiRunner {
|
|
|
407
407
|
export class GeminiAgentPlugin {
|
|
408
408
|
name = 'gemini';
|
|
409
409
|
isEnabled(agent) {
|
|
410
|
-
if (agent.baseagent !== 'gemini')
|
|
411
|
-
return false;
|
|
412
410
|
const geminiCfg = agent.config.baseagents?.gemini;
|
|
413
411
|
if (!geminiCfg)
|
|
414
412
|
return false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { kitsDir, eckDebugDir, resolveRoot } from '../paths.js';
|
|
3
|
+
import { kitsDir, eckDebugDir, resolveRoot, getPackageRoot } from '../paths.js';
|
|
4
4
|
import { logger } from '../utils/logger.js';
|
|
5
5
|
// ── Param descriptions (for debug output) ──
|
|
6
6
|
const PARAM_DESCRIPTIONS = {
|
|
@@ -14,21 +14,60 @@ const PARAM_DESCRIPTIONS = {
|
|
|
14
14
|
peerId: '对端在该渠道的原生 ID',
|
|
15
15
|
peerKey: '对端跨渠道唯一标识(channel#urlEncode(peerId))',
|
|
16
16
|
peerName: '对端显示名',
|
|
17
|
-
peerRole: '
|
|
17
|
+
peerRole: '对端角色(owner/admin/guest/anonymous)',
|
|
18
|
+
peerType: '对端类型(human/agent)',
|
|
18
19
|
groupId: '群组 ID(群聊时)',
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
project: '
|
|
20
|
+
chatType: '聊天类型(private=私聊 / group=群聊 / null=本地开发)',
|
|
21
|
+
channel: '渠道类型(aun/feishu/wechat/dingtalk/qqbot/wecom)',
|
|
22
|
+
venueUid: '场所唯一标识(预留)',
|
|
23
|
+
capabilities: '当前渠道支持的能力列表',
|
|
24
|
+
project: '当前项目目录名',
|
|
25
|
+
sessionId: 'evolclaw 会话 ID',
|
|
24
26
|
sessionName: '会话名称',
|
|
25
|
-
|
|
27
|
+
sessionCreatedAt: '会话创建时间(ISO)',
|
|
28
|
+
threadId: '话题 ID(多话题路由时)',
|
|
29
|
+
chatMode: '会话模式(interactive=同步交互 / proactive=主动推送)',
|
|
26
30
|
readonly: '是否只读模式',
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
baseAgent: 'base agent 规范值(claude/codex/gemini/hermes)',
|
|
32
|
+
baseAgentName: 'base agent 显示名',
|
|
33
|
+
baseAgentModel: 'base agent 使用的模型',
|
|
34
|
+
agentSessionId: 'base agent 会话 ID',
|
|
31
35
|
};
|
|
36
|
+
function buildPathMappings(vars) {
|
|
37
|
+
const pkgRoot = getPackageRoot();
|
|
38
|
+
const evolHome = String(vars['EVOLCLAW_HOME'] || resolveRoot());
|
|
39
|
+
const selfAid = vars['selfAid'] ? String(vars['selfAid']) : '';
|
|
40
|
+
const currentProject = vars['CURRENT_PROJECT'] ? String(vars['CURRENT_PROJECT']) : '';
|
|
41
|
+
const mappings = [
|
|
42
|
+
{ prefix: path.join(pkgRoot, 'kits', 'rules'), alias: '$KITS_RULES' },
|
|
43
|
+
{ prefix: path.join(pkgRoot, 'kits', 'templates', 'system-fragments'), alias: '$KITS_FRAGMENTS' },
|
|
44
|
+
{ prefix: path.join(pkgRoot, 'kits', 'templates'), alias: '$KITS_TEMPLATES' },
|
|
45
|
+
{ prefix: path.join(pkgRoot, 'kits', 'docs'), alias: '$KITS_DOCS' },
|
|
46
|
+
{ prefix: path.join(pkgRoot, 'kits'), alias: '$KITS' },
|
|
47
|
+
{ prefix: pkgRoot, alias: '$PACKAGE_ROOT' },
|
|
48
|
+
];
|
|
49
|
+
if (selfAid) {
|
|
50
|
+
mappings.push({ prefix: path.join(evolHome, 'agents', selfAid), alias: '$AGENT_DIR' });
|
|
51
|
+
}
|
|
52
|
+
mappings.push({ prefix: evolHome, alias: '$EVOLCLAW_HOME' });
|
|
53
|
+
if (currentProject) {
|
|
54
|
+
mappings.push({ prefix: currentProject, alias: '$CURRENT_PROJECT' });
|
|
55
|
+
}
|
|
56
|
+
// Sort by prefix length descending so longer (more specific) paths match first
|
|
57
|
+
mappings.sort((a, b) => b.prefix.length - a.prefix.length);
|
|
58
|
+
return mappings;
|
|
59
|
+
}
|
|
60
|
+
function shortenPath(filePath, mappings) {
|
|
61
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
62
|
+
for (const { prefix, alias } of mappings) {
|
|
63
|
+
const normalizedPrefix = prefix.replace(/\\/g, '/');
|
|
64
|
+
if (normalized.startsWith(normalizedPrefix)) {
|
|
65
|
+
const rest = normalized.slice(normalizedPrefix.length);
|
|
66
|
+
return alias + rest;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return filePath;
|
|
70
|
+
}
|
|
32
71
|
// ── Cache ──
|
|
33
72
|
let _manifestCache = null;
|
|
34
73
|
const _sessionPathCache = new Map();
|
|
@@ -49,6 +88,8 @@ export function renderKitSections(ctx) {
|
|
|
49
88
|
loadKitManifest();
|
|
50
89
|
const sections = _manifestCache;
|
|
51
90
|
const fileParts = [];
|
|
91
|
+
const fragmentParts = [];
|
|
92
|
+
const pathMappings = buildPathMappings(ctx.vars);
|
|
52
93
|
for (const section of sections) {
|
|
53
94
|
if (section.enabled === false)
|
|
54
95
|
continue;
|
|
@@ -62,14 +103,20 @@ export function renderKitSections(ctx) {
|
|
|
62
103
|
if (!content.trim())
|
|
63
104
|
continue;
|
|
64
105
|
const label = section.description ? `${section.id} — ${section.description}` : section.id;
|
|
65
|
-
|
|
106
|
+
const displayPath = shortenPath(filePath, pathMappings);
|
|
107
|
+
const part = `Contenu de ${displayPath} (${label}):\n\n${content.trimEnd()}`;
|
|
108
|
+
fileParts.push(part);
|
|
109
|
+
if (section.needsInjection) {
|
|
110
|
+
fragmentParts.push(part);
|
|
111
|
+
}
|
|
66
112
|
}
|
|
67
113
|
}
|
|
68
114
|
if (fileParts.length === 0)
|
|
69
115
|
return '';
|
|
70
116
|
const body = fileParts.join('\n\n');
|
|
71
117
|
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
|
-
|
|
118
|
+
const fragmentsOutput = fragmentParts.length > 0 ? fragmentParts.join('\n\n') : '';
|
|
119
|
+
writeDebugFiles(ctx, output, fragmentsOutput);
|
|
73
120
|
return output;
|
|
74
121
|
}
|
|
75
122
|
export function cleanEckDebug() {
|
|
@@ -233,14 +280,27 @@ function isTruthy(val) {
|
|
|
233
280
|
}
|
|
234
281
|
// CHUNK_CONTINUE_6
|
|
235
282
|
// ── Template rendering ──
|
|
283
|
+
function resolveConditions(template, vars) {
|
|
284
|
+
// Find innermost {{?...}}...{{/}} block (no nested {{? inside) and resolve it.
|
|
285
|
+
// Repeat until no blocks remain.
|
|
286
|
+
const inner = /\{\{\?(\w+)(?:(!=|=)([^}]*))?\}\}([^]*?)\{\{\/\}\}/;
|
|
287
|
+
let result = template;
|
|
288
|
+
let prev;
|
|
289
|
+
do {
|
|
290
|
+
prev = result;
|
|
291
|
+
result = result.replace(inner, (_match, key, op, value, body) => {
|
|
292
|
+
if (op === '=')
|
|
293
|
+
return String(vars[key]) === value ? body : '';
|
|
294
|
+
if (op === '!=')
|
|
295
|
+
return String(vars[key]) !== value ? body : '';
|
|
296
|
+
return isTruthy(vars[key]) ? body : '';
|
|
297
|
+
});
|
|
298
|
+
} while (result !== prev);
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
236
301
|
function renderTemplate(template, vars) {
|
|
237
|
-
// Pass 1:
|
|
238
|
-
let result = template
|
|
239
|
-
if (value !== undefined) {
|
|
240
|
-
return String(vars[key]) === value ? body : '';
|
|
241
|
-
}
|
|
242
|
-
return isTruthy(vars[key]) ? body : '';
|
|
243
|
-
});
|
|
302
|
+
// Pass 1: resolve nested conditionals inside-out
|
|
303
|
+
let result = resolveConditions(template, vars);
|
|
244
304
|
// Pass 2: variable substitution {{key}}
|
|
245
305
|
result = result.replace(/\{\{(\w+)\}\}/g, (_match, key) => {
|
|
246
306
|
const val = vars[key];
|
|
@@ -261,7 +321,7 @@ function getSessionCache(sessionId) {
|
|
|
261
321
|
return cache;
|
|
262
322
|
}
|
|
263
323
|
// ── Debug output ──
|
|
264
|
-
function writeDebugFiles(ctx, output) {
|
|
324
|
+
function writeDebugFiles(ctx, output, fragmentsOutput) {
|
|
265
325
|
const now = new Date();
|
|
266
326
|
const ts = now.toISOString().replace(/[T:.]/g, '-').slice(0, 19);
|
|
267
327
|
const dir = eckDebugDir();
|
|
@@ -278,4 +338,7 @@ function writeDebugFiles(ctx, output) {
|
|
|
278
338
|
};
|
|
279
339
|
fs.writeFile(path.join(dir, `vars-${ts}.json`), JSON.stringify(varsData, null, 2), () => { });
|
|
280
340
|
fs.writeFile(path.join(dir, `context-${ts}.md`), output, () => { });
|
|
341
|
+
if (fragmentsOutput) {
|
|
342
|
+
fs.writeFile(path.join(dir, `fragments-${ts}.md`), fragmentsOutput, () => { });
|
|
343
|
+
}
|
|
281
344
|
}
|
package/dist/aun/aid/agentmd.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { agentMdPath, aidLocalDir } from '../../paths.js';
|
|
3
|
+
import { getAunClient, createAunClient } from './client.js';
|
|
4
|
+
import { agentMdPath, aidLocalDir, resolveRoot } from '../../paths.js';
|
|
6
5
|
export function buildInitialAgentMd(opts) {
|
|
7
6
|
const agentName = opts.aid.split('.')[0];
|
|
8
7
|
const agentType = opts.type || 'ai';
|
|
@@ -83,71 +82,37 @@ async function verifyContent(content, aid, certPem, client) {
|
|
|
83
82
|
* Create a bare AUNClient (no createAid) for read-only operations.
|
|
84
83
|
*/
|
|
85
84
|
async function createBareClient(aunPath) {
|
|
86
|
-
|
|
87
|
-
const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
88
|
-
const clientOpts = { aun_path: aunPath, debug: false };
|
|
89
|
-
if (fs.existsSync(caCertPath))
|
|
90
|
-
clientOpts.root_ca_path = caCertPath;
|
|
91
|
-
return new AUNClient(clientOpts);
|
|
85
|
+
return createAunClient({ aunPath });
|
|
92
86
|
}
|
|
93
87
|
export async function agentmdGet(aid, opts) {
|
|
94
|
-
const aunPath = opts?.aunPath ??
|
|
95
|
-
const localPath = agentMdPath(aid);
|
|
96
|
-
// === Path A: local agent.md exists ===
|
|
97
|
-
if (fs.existsSync(localPath)) {
|
|
98
|
-
const content = fs.readFileSync(localPath, 'utf-8');
|
|
99
|
-
if (!opts?.withVerification)
|
|
100
|
-
return content;
|
|
101
|
-
// Verify local content
|
|
102
|
-
const client = opts?.client ?? await createBareClient(aunPath);
|
|
103
|
-
const ownClient = !opts?.client;
|
|
104
|
-
try {
|
|
105
|
-
const certPem = await obtainCertPem(aid, aunPath, client);
|
|
106
|
-
const verification = await verifyContent(content, aid, certPem, client);
|
|
107
|
-
if (verification.status !== 'invalid') {
|
|
108
|
-
return { content, verification };
|
|
109
|
-
}
|
|
110
|
-
// Fallback: local invalid → try remote
|
|
111
|
-
try {
|
|
112
|
-
const remote = await client.auth.downloadAgentMd(aid);
|
|
113
|
-
if (remote) {
|
|
114
|
-
const remoteVerification = await verifyContent(remote, aid, certPem, client);
|
|
115
|
-
if (remoteVerification.status === 'verified') {
|
|
116
|
-
fs.writeFileSync(localPath, remote, 'utf-8');
|
|
117
|
-
return { content: remote, verification: remoteVerification };
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
catch { /* remote fetch failed, return local invalid result */ }
|
|
122
|
-
return { content, verification };
|
|
123
|
-
}
|
|
124
|
-
finally {
|
|
125
|
-
if (ownClient)
|
|
126
|
-
try {
|
|
127
|
-
await client.close();
|
|
128
|
-
}
|
|
129
|
-
catch { /* ignore */ }
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
// === Path B: no local agent.md → download from remote ===
|
|
88
|
+
const aunPath = opts?.aunPath ?? resolveRoot();
|
|
133
89
|
const client = opts?.client ?? await createBareClient(aunPath);
|
|
134
90
|
const ownClient = !opts?.client;
|
|
91
|
+
const localPath = agentMdPath(aid);
|
|
135
92
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
93
|
+
// Try SDK fetch (auto-saves locally + verifies signature)
|
|
94
|
+
let content;
|
|
95
|
+
let verification;
|
|
96
|
+
try {
|
|
97
|
+
const info = await client.fetchAgentMd(aid);
|
|
98
|
+
content = info.content;
|
|
99
|
+
const sig = info.signature ?? {};
|
|
100
|
+
const status = sig.status === 'verified' ? 'verified' : sig.status === 'unsigned' ? 'unsigned' : 'invalid';
|
|
101
|
+
verification = { status, ...(sig.reason ? { reason: String(sig.reason) } : {}) };
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
// Network failed — fall back to local file (verify signature via SDK if requested)
|
|
105
|
+
if (!fs.existsSync(localPath))
|
|
106
|
+
throw err;
|
|
107
|
+
content = fs.readFileSync(localPath, 'utf-8');
|
|
108
|
+
if (opts?.withVerification) {
|
|
109
|
+
const certPem = await obtainCertPem(aid, aunPath, client);
|
|
110
|
+
verification = await verifyContent(content, aid, certPem, client);
|
|
111
|
+
}
|
|
143
112
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const aidDir = aidLocalDir(aid);
|
|
148
|
-
fs.mkdirSync(aidDir, { recursive: true });
|
|
149
|
-
fs.writeFileSync(path.join(aidDir, 'agent.md'), raw, 'utf-8');
|
|
150
|
-
return { content: raw, verification };
|
|
113
|
+
if (!opts?.withVerification)
|
|
114
|
+
return content;
|
|
115
|
+
return { content: content, verification: verification ?? { status: 'unsigned' } };
|
|
151
116
|
}
|
|
152
117
|
finally {
|
|
153
118
|
if (ownClient)
|
|
@@ -158,24 +123,52 @@ export async function agentmdGet(aid, opts) {
|
|
|
158
123
|
}
|
|
159
124
|
}
|
|
160
125
|
/**
|
|
161
|
-
* Upload agent.md: auto-sign + upload
|
|
126
|
+
* Upload agent.md: write to local file → publishAgentMd (auto-sign + upload).
|
|
162
127
|
*/
|
|
163
128
|
export async function agentmdPut(content, opts) {
|
|
164
|
-
const aunPath = opts.aunPath ??
|
|
129
|
+
const aunPath = opts.aunPath ?? resolveRoot();
|
|
165
130
|
const client = opts.client ?? await getAunClient(opts.aid, { aunPath });
|
|
166
131
|
const ownClient = !opts.client;
|
|
132
|
+
const dir = aidLocalDir(opts.aid);
|
|
133
|
+
const filePath = path.join(dir, 'agent.md');
|
|
134
|
+
const existed = fs.existsSync(filePath);
|
|
167
135
|
try {
|
|
168
|
-
let signed;
|
|
169
|
-
try {
|
|
170
|
-
signed = await client.auth.signAgentMd(content);
|
|
171
|
-
}
|
|
172
|
-
catch {
|
|
173
|
-
signed = content;
|
|
174
|
-
}
|
|
175
|
-
await client.auth.uploadAgentMd(signed);
|
|
176
|
-
const dir = aidLocalDir(opts.aid);
|
|
177
136
|
fs.mkdirSync(dir, { recursive: true });
|
|
178
|
-
fs.writeFileSync(
|
|
137
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
138
|
+
await client.publishAgentMd();
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
if (!existed)
|
|
142
|
+
try {
|
|
143
|
+
fs.unlinkSync(filePath);
|
|
144
|
+
}
|
|
145
|
+
catch { /* ignore */ }
|
|
146
|
+
throw e;
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
if (ownClient)
|
|
150
|
+
try {
|
|
151
|
+
await client.close();
|
|
152
|
+
}
|
|
153
|
+
catch { /* ignore */ }
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Check if agent.md is up-to-date (30-day cache), fetch if changed.
|
|
158
|
+
* Returns changed=true + content when a new version was downloaded.
|
|
159
|
+
*/
|
|
160
|
+
export async function agentmdSync(aid, opts) {
|
|
161
|
+
const client = opts?.client ?? await createBareClient();
|
|
162
|
+
const ownClient = !opts?.client;
|
|
163
|
+
try {
|
|
164
|
+
const state = await client.checkAgentMd(aid, 30);
|
|
165
|
+
if (!state.in_sync || !state.local_found) {
|
|
166
|
+
const info = await client.fetchAgentMd(aid);
|
|
167
|
+
return { changed: true, content: info.content };
|
|
168
|
+
}
|
|
169
|
+
const localPath = agentMdPath(aid);
|
|
170
|
+
const content = fs.existsSync(localPath) ? fs.readFileSync(localPath, 'utf-8') : undefined;
|
|
171
|
+
return { changed: false, content };
|
|
179
172
|
}
|
|
180
173
|
finally {
|
|
181
174
|
if (ownClient)
|
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, 3, 3];
|
|
29
28
|
export const AUN_CORE_SDK_PKG = '@agentunion/fastaun';
|
|
30
29
|
// ==================== SDK & Environment ====================
|
|
31
30
|
function compareVersion(a, min) {
|
|
@@ -114,15 +113,31 @@ export async function downloadCaRoot(aunPath, gatewayUrl, indent = '') {
|
|
|
114
113
|
return false;
|
|
115
114
|
}
|
|
116
115
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
/**
|
|
117
|
+
* 统一构造 AUNClient:自动绑 root_ca_path + setAgentMdPath(aidsDir())。
|
|
118
|
+
* 不做 createAid / authenticate / connect,调用方按需续作。
|
|
119
|
+
*
|
|
120
|
+
* 所有 new AUNClient 调用都应走此工厂,避免 SDK 默认把 agent.md 写到
|
|
121
|
+
* {aun_path}/AgentMDs(默认目录)。
|
|
122
|
+
*/
|
|
123
|
+
export async function createAunClient(opts = {}) {
|
|
124
|
+
const { aunPath: defaultAunPath, aidsDir } = await import('../../paths.js');
|
|
125
|
+
const aunPath = opts.aunPath ?? defaultAunPath();
|
|
120
126
|
const caCertPath = path.join(aunPath, 'CA', 'root', 'root.crt');
|
|
121
127
|
const { AUNClient } = await import('@agentunion/fastaun');
|
|
122
|
-
const clientOpts = { aun_path: aunPath, debug: false };
|
|
128
|
+
const clientOpts = { aun_path: aunPath, debug: opts.debug ?? false };
|
|
123
129
|
if (fs.existsSync(caCertPath))
|
|
124
130
|
clientOpts.root_ca_path = caCertPath;
|
|
125
|
-
|
|
131
|
+
if (opts.encryptionSeed)
|
|
132
|
+
clientOpts.encryption_seed = opts.encryptionSeed;
|
|
133
|
+
const client = opts.aunSdkLog !== undefined
|
|
134
|
+
? new AUNClient(clientOpts, opts.aunSdkLog)
|
|
135
|
+
: new AUNClient(clientOpts);
|
|
136
|
+
client.setAgentMdPath(aidsDir());
|
|
137
|
+
return client;
|
|
138
|
+
}
|
|
139
|
+
export async function getAunClient(aid, opts) {
|
|
140
|
+
const client = await createAunClient({ aunPath: opts?.aunPath });
|
|
126
141
|
await client.auth.createAid({ aid });
|
|
127
142
|
return client;
|
|
128
143
|
}
|