evolclaw 3.1.1 → 3.1.3

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 (51) hide show
  1. package/CHANGELOG.md +428 -0
  2. package/README.md +3 -7
  3. package/SKILLS.md +311 -0
  4. package/dist/agents/claude-runner.js +1 -1
  5. package/dist/agents/codex-runner.js +75 -19
  6. package/dist/agents/gemini-runner.js +0 -2
  7. package/dist/agents/kit-renderer.js +59 -10
  8. package/dist/aun/aid/agentmd.js +50 -27
  9. package/dist/aun/aid/client.js +5 -11
  10. package/dist/aun/aid/identity.js +32 -13
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/msg/group.js +1 -0
  13. package/dist/aun/msg/p2p.js +15 -2
  14. package/dist/aun/msg/upload.js +57 -18
  15. package/dist/aun/rpc/connection.js +3 -0
  16. package/dist/channels/aun.js +122 -48
  17. package/dist/channels/dingtalk.js +1 -0
  18. package/dist/channels/feishu.js +5 -4
  19. package/dist/channels/qqbot.js +1 -0
  20. package/dist/channels/wechat.js +1 -0
  21. package/dist/channels/wecom.js +1 -0
  22. package/dist/cli/agent.js +142 -40
  23. package/dist/cli/index.js +103 -58
  24. package/dist/cli/init-channel.js +4 -2
  25. package/dist/cli/init.js +55 -26
  26. package/dist/cli/watch-msg.js +3 -1
  27. package/dist/config-store.js +22 -1
  28. package/dist/core/channel-loader.js +4 -4
  29. package/dist/core/command-handler.js +626 -538
  30. package/dist/core/evolagent-registry.js +45 -9
  31. package/dist/core/evolagent.js +35 -4
  32. package/dist/core/message/im-renderer.js +14 -4
  33. package/dist/core/message/message-bridge.js +149 -25
  34. package/dist/core/message/message-processor.js +45 -38
  35. package/dist/core/session/session-fs-store.js +23 -0
  36. package/dist/core/session/session-manager.js +188 -42
  37. package/dist/index.js +15 -17
  38. package/dist/paths.js +35 -0
  39. package/dist/utils/cross-platform.js +2 -1
  40. package/kits/docs/INDEX.md +6 -0
  41. package/kits/eck_manifest.json +3 -3
  42. package/kits/rules/02-navigation.md +1 -0
  43. package/kits/rules/06-channel.md +2 -18
  44. package/kits/templates/system-fragments/baseagent.md +2 -2
  45. package/kits/templates/system-fragments/channel.md +18 -9
  46. package/kits/templates/system-fragments/eckruntime.md +14 -0
  47. package/kits/templates/system-fragments/identity.md +5 -6
  48. package/kits/templates/system-fragments/relation.md +7 -5
  49. package/kits/templates/system-fragments/venue.md +2 -3
  50. package/package.json +5 -2
  51. package/kits/templates/system-fragments/runtime.md +0 -19
package/SKILLS.md ADDED
@@ -0,0 +1,311 @@
1
+ ---
2
+ name: evolclaw
3
+ version: 1.2.0
4
+ description: EvolClaw 完整使用手册 — CLI 命令参考 + 运行时控制 (ctl) + 定时触发器 (trigger)
5
+ trigger: 用户询问或需要使用 evolclaw CLI、切换模型、调整推理强度、查看运行状态、压缩上下文、检查通道健康、管理权限模式、重启服务、重连渠道、注册定时触发器等
6
+ ---
7
+
8
+ # EvolClaw
9
+
10
+ EvolClaw 是连接 Claude Agent SDK 和消息渠道(飞书 / 微信 / AUN / 钉钉 / QQ / 企业微信)的 AI Agent 网关,支持多项目会话管理。
11
+
12
+ 本文档分三部分:
13
+
14
+ 1. [CLI 命令](#cli-命令) — 终端命令参考
15
+ 2. [Ctl 运行时控制](#ctl-运行时控制) — Agent 自主管理指令(仅在 evolclaw 托管环境中可用)
16
+ 3. [Trigger 定时触发器](#trigger-定时触发器) — 设置延迟或定时任务
17
+
18
+ ---
19
+
20
+ ## CLI 命令
21
+
22
+ ### 全局选项
23
+
24
+ ```
25
+ evolclaw --version / -v / -V 查看版本号
26
+ ```
27
+
28
+ ### 服务生命周期
29
+
30
+ ```
31
+ evolclaw start 启动服务(默认命令)
32
+ evolclaw stop 停止服务
33
+ evolclaw restart 重启服务(含自动升级检查)
34
+ evolclaw status 查看运行状态、进程信息、会话统计、渠道连接状态
35
+ evolclaw restart-monitor 启动重启监控守护进程
36
+ ```
37
+
38
+ ### 日志与诊断
39
+
40
+ ```
41
+ evolclaw logs [选项]
42
+ --level error|warn 只显示指定级别及以上
43
+ --module <name> 只显示指定模块
44
+ --raw 原始输出,不着色
45
+ --no-color 禁用颜色输出
46
+
47
+ evolclaw watch 汇总监控 logs/ 下所有 .log 文件
48
+ evolclaw diagnose 诊断启动环境
49
+ ```
50
+
51
+ ### 初始化
52
+
53
+ ```
54
+ evolclaw init [渠道]
55
+
56
+ 渠道:
57
+ aun AUN 交互式配置(默认)
58
+ feishu 飞书扫码登录
59
+ wechat 微信扫码登录
60
+ dingtalk 钉钉扫码登录
61
+ qqbot QQ 机器人扫码绑定
62
+ wecom 企业微信 AI Bot 配置
63
+
64
+ 非交互式选项:
65
+ --non-interactive
66
+ --default-path <path>
67
+ --channel <name>
68
+ --aun-aid <aid>
69
+ --aun-owner <aid>
70
+
71
+ 示例:
72
+ evolclaw init
73
+ evolclaw init aun
74
+ evolclaw init --non-interactive --channel aun \
75
+ --aun-aid mybot.agentid.pub --aun-owner me.agentid.pub \
76
+ --default-path ~/projects/default
77
+ ```
78
+
79
+ ### Agent 管理
80
+
81
+ ```
82
+ evolclaw agent 列出所有 agent
83
+ evolclaw agent <name> 查看指定 agent 详情
84
+ evolclaw agent reload <name> 热重载 agent 配置
85
+
86
+ evolclaw agent new <name> [选项]
87
+ --baseagent <claude|codex|gemini|hermes> 必填
88
+ --project <absolute-path> 必填
89
+
90
+ 渠道选项(至少一个):
91
+ --aun-aid <aid> --aun-owner <aid>
92
+ --feishu-app-id <id> --feishu-app-secret <secret>
93
+ --wechat-token <token>
94
+ --wecom-bot-id <id> --wecom-secret <secret>
95
+ --dingtalk-client-id <id> --dingtalk-client-secret <secret>
96
+ --qqbot-app-id <id> --qqbot-client-secret <secret>
97
+
98
+ 行为选项:
99
+ --chatmode-private <interactive|proactive> 默认: interactive
100
+ --chatmode-group <interactive|proactive> 默认: proactive
101
+
102
+ --non-interactive
103
+
104
+ 示例:
105
+ evolclaw agent new mybot --baseagent claude \
106
+ --project ~/projects/mybot \
107
+ --aun-aid mybot.agentid.pub --aun-owner me.agentid.pub
108
+ ```
109
+
110
+ ### AID 身份管理
111
+
112
+ ```
113
+ evolclaw aid list 列出本地所有 AID
114
+ evolclaw aid show <aid> 查看 AID 详情
115
+ evolclaw aid new <aid> 创建新 AID 身份
116
+ evolclaw aid delete <aid> 删除 AID
117
+ evolclaw aid lookup <aid> 查询 AUN 网络上的 AID 信息
118
+ evolclaw aid agentmd put <aid> 上传本地 agent.md 到 AUN 网络
119
+ evolclaw aid agentmd get <aid> 从 AUN 网络获取 agent.md
120
+ ```
121
+
122
+ ### RPC 调用
123
+
124
+ ```
125
+ evolclaw rpc --as <aid> --params <json|jsonl-file>
126
+
127
+ 选项:
128
+ --as <aid> 发送方 AID(必填)
129
+ --params <value> JSON 字符串或 .jsonl 文件路径(必填)
130
+
131
+ 示例:
132
+ evolclaw rpc --as alice.agentid.pub \
133
+ --params '{"method":"message.send","params":{"to":"bob.agentid.pub","payload":{"type":"text","text":"hello"}}}'
134
+ ```
135
+
136
+ ### 存储管理
137
+
138
+ ```
139
+ evolclaw storage upload <aid> <local-path> [remote-path]
140
+ evolclaw storage download <aid> <remote-path> [local-path]
141
+ evolclaw storage ls <aid> [prefix]
142
+ evolclaw storage rm <aid> <remote-path>
143
+ evolclaw storage quota <aid>
144
+ ```
145
+
146
+ ### 项目管理
147
+
148
+ ```
149
+ evolclaw mv <old-path> <new-path> 迁移项目目录(保留会话数据)
150
+ ```
151
+
152
+ ### 环境变量
153
+
154
+ | 变量 | 默认值 | 说明 |
155
+ |------|--------|------|
156
+ | `EVOLCLAW_HOME` | `~/.evolclaw` | 数据目录 |
157
+ | `LOG_LEVEL` | `INFO` | 日志级别 |
158
+ | `MESSAGE_LOG` | `true` | 是否记录消息日志 |
159
+ | `EVENT_LOG` | `true` | 是否记录事件日志 |
160
+
161
+ ---
162
+
163
+ ## Ctl 运行时控制
164
+
165
+ 通过 `evolclaw ctl <command> [args]` 管理运行时配置。仅在 evolclaw 托管环境中可用(`EVOLCLAW_SESSION_ID` 已设置)。
166
+
167
+ ### 查询类(所有用户)
168
+
169
+ - `evolclaw ctl help` — 显示帮助
170
+ - `evolclaw ctl status` — 显示会话状态
171
+ - `evolclaw ctl check` — 检查渠道健康状态
172
+
173
+ ### 配置类(管理员)
174
+
175
+ - `evolclaw ctl model` — 查看当前模型和可选列表
176
+ - `evolclaw ctl model <model-id>` — 切换模型(如 `opus`, `sonnet`, `haiku`)
177
+ - `evolclaw ctl effort` — 查看当前推理强度
178
+ - `evolclaw ctl effort <low|medium|high|max>` — 切换推理强度
179
+ - `evolclaw ctl compact` — 压缩当前会话上下文
180
+ - `evolclaw ctl chatmode [interactive|proactive]` — 查看/切换会话模式
181
+
182
+ ### 权限类
183
+
184
+ - `evolclaw ctl perm` — 查看当前权限模式(管理员)
185
+ - `evolclaw ctl perm <mode>` — 切换权限模式(仅 owner)
186
+
187
+ ### 项目
188
+
189
+ - `evolclaw ctl bind <path>` — 注册项目目录(不切换当前会话)
190
+
191
+ ### 消息(仅 owner)
192
+
193
+ - `evolclaw ctl send "<消息>"` — 主动发送文本消息(proactive 模式)
194
+ - `evolclaw ctl file [channel] <路径>` — 发送项目内文件(仅限项目目录内)
195
+ - `evolclaw ctl activity <all|dm|owner|none>` — 查看/控制中间输出显示模式
196
+
197
+ ### 运维(仅 owner)
198
+
199
+ - `evolclaw ctl agentmd` — 查看当前 agent.md
200
+ - `evolclaw ctl agentmd put` — 发布本地 agent.md
201
+ - `evolclaw ctl agentmd set <内容>` — 直接设置 agent.md 内容
202
+ - `evolclaw ctl aid` — 列出所有 AUN 实例及连接状态
203
+ - `evolclaw ctl aid new <aid>` — 创建新 AID 并热加载(仅 AUN 通道)
204
+ - `evolclaw ctl restart` — 重启服务(中断所有会话,慎用)
205
+ - `evolclaw ctl restart <channel>` — 重连指定渠道(管理员可用)
206
+
207
+ ### 使用场景
208
+
209
+ - Agent 自主判断需要切换模型、调整配置
210
+ - 用户自然语言指示(如"切到 opus"、"压缩上下文")
211
+ - Proactive 模式下发送消息给用户(文本输出被静默丢弃,必须用 `ctl send`)
212
+
213
+ ### 注意事项
214
+
215
+ - 仅在 evolclaw 托管环境中可用(`EVOLCLAW_SESSION_ID` 已设置)
216
+ - 权限继承当前会话用户角色(owner / admin / guest)
217
+ - `compact` 不能在活跃流期间执行
218
+ - `file` 只能发送项目目录下的文件(路径越界会被拒绝)
219
+ - `restart` 会中断所有会话
220
+
221
+ ### 使用示例
222
+
223
+ ```bash
224
+ evolclaw ctl model opus # 切换到 opus
225
+ evolclaw ctl effort low # 降低推理强度
226
+ evolclaw ctl compact # 压缩上下文
227
+ evolclaw ctl status # 查看服务状态
228
+ evolclaw ctl chatmode proactive # 切换为主动模式
229
+ evolclaw ctl send "你好" # proactive 模式发送消息
230
+ ```
231
+
232
+ ---
233
+
234
+ ## Trigger 定时触发器
235
+
236
+ 通过 `/trigger` 命令设置延迟或定时任务,系统在指定时间重新激活 Agent 执行。
237
+
238
+ ### 注册
239
+
240
+ ```
241
+ /trigger set --delay <时长> --prompt "<任务内容>"
242
+ /trigger set --at <ISO时间> --prompt "<任务内容>"
243
+ /trigger set --cron <表达式> --prompt "<任务内容>"
244
+ ```
245
+
246
+ **时间格式:**
247
+ - `--delay`:`30m`、`2h`、`1d`、`2h30m`
248
+ - `--at`:ISO 格式,如 `2026-05-15T09:00`
249
+ - `--cron`:标准 cron 表达式,如 `0 9 * * *`(每天 9 点)
250
+
251
+ ### 可选参数
252
+
253
+ | 参数 | 说明 | 默认值 |
254
+ |------|------|--------|
255
+ | `--channel <实例名>` | 目标通道实例 | 当前通道 |
256
+ | `--channelid <id>` | 目标对话 ID | 当前对话 |
257
+ | `--thread <id>` | 目标 thread(与 --session 互斥) | 无 |
258
+ | `--session latest` | 续接最后活跃会话(用户可见输出) | 默认 |
259
+ | `--session silent` | 新建独立会话静默执行 | - |
260
+ | `--name <标识>` | 触发器名称 | 自动生成 |
261
+ | `--agent <名称>` | 目标 agent | 当前 agent |
262
+
263
+ ### 管理
264
+
265
+ ```
266
+ /trigger 查看活跃触发器
267
+ /trigger list 查看所有触发器(含历史)
268
+ /trigger update <名称|ID> <参数> 修改触发器
269
+ /trigger cancel <名称|ID> 取消触发器
270
+ ```
271
+
272
+ ### 修改触发器
273
+
274
+ ```
275
+ /trigger update <名称|ID> [--delay <时长>] [--at <ISO时间>] [--cron <表达式>]
276
+ [--prompt "<任务内容>"] [--name <新名称>]
277
+ [--session latest|silent] [--agent <名称>]
278
+ [--channel <实例名> --channelid <id>]
279
+ ```
280
+
281
+ 至少指定一个修改参数。未指定的字段保持不变。
282
+
283
+ ### 会话策略
284
+
285
+ - **latest**:续接已有会话,输出对用户可见。适合提醒、跟进对话。
286
+ - **silent**:新建独立 autonomous 会话,不打扰用户。适合后台清理、扫描、生成文件。
287
+
288
+ ### 权限
289
+
290
+ - 所有用户可注册触发器
291
+ - cancel 自己的触发器:按名称或 ID
292
+ - cancel 他人的触发器:需 owner/admin 权限
293
+
294
+ ### 示例
295
+
296
+ ```
297
+ /trigger set --delay 30m --prompt "检查构建状态并汇报"
298
+ /trigger set --at 2026-05-16T09:00 --prompt "生成日报" --session silent
299
+ /trigger set --cron "0 */6 * * *" --prompt "检查服务健康" --session silent --name health-check
300
+ /trigger update health-check --cron "0 */4 * * *"
301
+ /trigger update health-check --prompt "检查服务健康并清理过期日志" --name health-check-v2
302
+ /trigger cancel health-check-v2
303
+ ```
304
+
305
+ ### 注意事项
306
+
307
+ - `--thread` 与 `--session` 互斥
308
+ - `--channel` 与 `--channelid` 必须同时指定或同时省略
309
+ - delay/at 类型触发一次后自动归档;cron 类型持续触发直到 cancel
310
+ - 修改触发器时,未指定的字段保持不变
311
+ - 修改 cron 触发器的时间表达式会立即重新计算下次触发时间
@@ -1094,7 +1094,7 @@ export class AgentRunner {
1094
1094
  export class ClaudeAgentPlugin {
1095
1095
  name = 'claude';
1096
1096
  isEnabled(agent) {
1097
- return agent.baseagent === 'claude';
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
- // ── Codex 模型列表 ──
21
- const CODEX_MODELS = ['gpt-5.3-codex', 'gpt-5.2-codex', 'gpt-5-codex', 'gpt-5.2', 'gpt-5.4'];
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.codex || !this.codexModule) {
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
- return { codex: this.codex, mod: this.codexModule };
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 CODEX_MODELS; }
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
- // Agent ctl: 注入 EVOLCLAW_SESSION_ID 供子进程使用
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: prompt }];
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 = prompt;
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 = {
@@ -22,13 +22,48 @@ const PARAM_DESCRIPTIONS = {
22
22
  venueUid: 'venue 唯一标识',
23
23
  project: '当前项目目录名(由 CURRENT_PROJECT 派生)',
24
24
  sessionName: '会话名称',
25
- sessionMode: '会话模式',
25
+ chatmode: '会话模式(interactive/proactive)',
26
26
  readonly: '是否只读模式',
27
27
  canSendFile: '当前渠道是否支持发文件',
28
28
  capabilities: '渠道能力列表',
29
29
  baseAgent: '当前 base agent 规范值(claude/codex/gemini/hermes)',
30
30
  baseAgentName: '当前 base agent 显示名',
31
31
  };
32
+ function buildPathMappings(vars) {
33
+ const pkgRoot = getPackageRoot();
34
+ const evolHome = String(vars['EVOLCLAW_HOME'] || resolveRoot());
35
+ const selfAid = vars['selfAid'] ? String(vars['selfAid']) : '';
36
+ const currentProject = vars['CURRENT_PROJECT'] ? String(vars['CURRENT_PROJECT']) : '';
37
+ const mappings = [
38
+ { prefix: path.join(pkgRoot, 'kits', 'rules'), alias: '$KITS_RULES' },
39
+ { prefix: path.join(pkgRoot, 'kits', 'templates', 'system-fragments'), alias: '$KITS_FRAGMENTS' },
40
+ { prefix: path.join(pkgRoot, 'kits', 'templates'), alias: '$KITS_TEMPLATES' },
41
+ { prefix: path.join(pkgRoot, 'kits', 'docs'), alias: '$KITS_DOCS' },
42
+ { prefix: path.join(pkgRoot, 'kits'), alias: '$KITS' },
43
+ { prefix: pkgRoot, alias: '$PACKAGE_ROOT' },
44
+ ];
45
+ if (selfAid) {
46
+ mappings.push({ prefix: path.join(evolHome, 'agents', selfAid), alias: '$AGENT_DIR' });
47
+ }
48
+ mappings.push({ prefix: evolHome, alias: '$EVOLCLAW_HOME' });
49
+ if (currentProject) {
50
+ mappings.push({ prefix: currentProject, alias: '$CURRENT_PROJECT' });
51
+ }
52
+ // Sort by prefix length descending so longer (more specific) paths match first
53
+ mappings.sort((a, b) => b.prefix.length - a.prefix.length);
54
+ return mappings;
55
+ }
56
+ function shortenPath(filePath, mappings) {
57
+ const normalized = filePath.replace(/\\/g, '/');
58
+ for (const { prefix, alias } of mappings) {
59
+ const normalizedPrefix = prefix.replace(/\\/g, '/');
60
+ if (normalized.startsWith(normalizedPrefix)) {
61
+ const rest = normalized.slice(normalizedPrefix.length);
62
+ return alias + rest;
63
+ }
64
+ }
65
+ return filePath;
66
+ }
32
67
  // ── Cache ──
33
68
  let _manifestCache = null;
34
69
  const _sessionPathCache = new Map();
@@ -49,6 +84,8 @@ export function renderKitSections(ctx) {
49
84
  loadKitManifest();
50
85
  const sections = _manifestCache;
51
86
  const fileParts = [];
87
+ const fragmentParts = [];
88
+ const pathMappings = buildPathMappings(ctx.vars);
52
89
  for (const section of sections) {
53
90
  if (section.enabled === false)
54
91
  continue;
@@ -62,14 +99,20 @@ export function renderKitSections(ctx) {
62
99
  if (!content.trim())
63
100
  continue;
64
101
  const label = section.description ? `${section.id} — ${section.description}` : section.id;
65
- fileParts.push(`Contenu de ${filePath} (${label}):\n\n${content.trimEnd()}`);
102
+ const displayPath = shortenPath(filePath, pathMappings);
103
+ const part = `Contenu de ${displayPath} (${label}):\n\n${content.trimEnd()}`;
104
+ fileParts.push(part);
105
+ if (section.needsInjection) {
106
+ fragmentParts.push(part);
107
+ }
66
108
  }
67
109
  }
68
110
  if (fileParts.length === 0)
69
111
  return '';
70
112
  const body = fileParts.join('\n\n');
71
113
  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);
114
+ const fragmentsOutput = fragmentParts.length > 0 ? fragmentParts.join('\n\n') : '';
115
+ writeDebugFiles(ctx, output, fragmentsOutput);
73
116
  return output;
74
117
  }
75
118
  export function cleanEckDebug() {
@@ -234,11 +277,14 @@ function isTruthy(val) {
234
277
  // CHUNK_CONTINUE_6
235
278
  // ── Template rendering ──
236
279
  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
- }
280
+ // Pass 1: conditional sections {{?key=value}}, {{?key!=value}}, {{?key}}...{{/}}
281
+ let result = template.replace(/\{\{\?(\w+)(!=|=)([^}]*)?\}\}([\s\S]*?)\{\{\/\}\}/g, (_match, key, op, value, body) => {
282
+ if (op === '!=')
283
+ return String(vars[key]) !== value ? body : '';
284
+ return String(vars[key]) === value ? body : '';
285
+ });
286
+ // Pass 1b: truthy-only {{?key}}...{{/}}
287
+ result = result.replace(/\{\{\?(\w+)\}\}([\s\S]*?)\{\{\/\}\}/g, (_match, key, body) => {
242
288
  return isTruthy(vars[key]) ? body : '';
243
289
  });
244
290
  // Pass 2: variable substitution {{key}}
@@ -261,7 +307,7 @@ function getSessionCache(sessionId) {
261
307
  return cache;
262
308
  }
263
309
  // ── Debug output ──
264
- function writeDebugFiles(ctx, output) {
310
+ function writeDebugFiles(ctx, output, fragmentsOutput) {
265
311
  const now = new Date();
266
312
  const ts = now.toISOString().replace(/[T:.]/g, '-').slice(0, 19);
267
313
  const dir = eckDebugDir();
@@ -278,4 +324,7 @@ function writeDebugFiles(ctx, output) {
278
324
  };
279
325
  fs.writeFile(path.join(dir, `vars-${ts}.json`), JSON.stringify(varsData, null, 2), () => { });
280
326
  fs.writeFile(path.join(dir, `context-${ts}.md`), output, () => { });
327
+ if (fragmentsOutput) {
328
+ fs.writeFile(path.join(dir, `fragments-${ts}.md`), fragmentsOutput, () => { });
329
+ }
281
330
  }