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.
- package/CHANGELOG.md +428 -0
- package/README.md +3 -7
- package/SKILLS.md +311 -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 +59 -10
- package/dist/aun/aid/agentmd.js +50 -27
- package/dist/aun/aid/client.js +5 -11
- package/dist/aun/aid/identity.js +32 -13
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/msg/group.js +1 -0
- package/dist/aun/msg/p2p.js +15 -2
- package/dist/aun/msg/upload.js +57 -18
- package/dist/aun/rpc/connection.js +3 -0
- package/dist/channels/aun.js +122 -48
- package/dist/channels/dingtalk.js +1 -0
- package/dist/channels/feishu.js +5 -4
- package/dist/channels/qqbot.js +1 -0
- package/dist/channels/wechat.js +1 -0
- package/dist/channels/wecom.js +1 -0
- package/dist/cli/agent.js +142 -40
- package/dist/cli/index.js +103 -58
- package/dist/cli/init-channel.js +4 -2
- package/dist/cli/init.js +55 -26
- package/dist/cli/watch-msg.js +3 -1
- package/dist/config-store.js +22 -1
- package/dist/core/channel-loader.js +4 -4
- package/dist/core/command-handler.js +626 -538
- package/dist/core/evolagent-registry.js +45 -9
- package/dist/core/evolagent.js +35 -4
- package/dist/core/message/im-renderer.js +14 -4
- package/dist/core/message/message-bridge.js +149 -25
- package/dist/core/message/message-processor.js +45 -38
- package/dist/core/session/session-fs-store.js +23 -0
- package/dist/core/session/session-manager.js +188 -42
- package/dist/index.js +15 -17
- package/dist/paths.js +35 -0
- package/dist/utils/cross-platform.js +2 -1
- package/kits/docs/INDEX.md +6 -0
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/02-navigation.md +1 -0
- package/kits/rules/06-channel.md +2 -18
- package/kits/templates/system-fragments/baseagent.md +2 -2
- package/kits/templates/system-fragments/channel.md +18 -9
- package/kits/templates/system-fragments/eckruntime.md +14 -0
- package/kits/templates/system-fragments/identity.md +5 -6
- package/kits/templates/system-fragments/relation.md +7 -5
- package/kits/templates/system-fragments/venue.md +2 -3
- package/package.json +5 -2
- 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.
|
|
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 = {
|
|
@@ -22,13 +22,48 @@ const PARAM_DESCRIPTIONS = {
|
|
|
22
22
|
venueUid: 'venue 唯一标识',
|
|
23
23
|
project: '当前项目目录名(由 CURRENT_PROJECT 派生)',
|
|
24
24
|
sessionName: '会话名称',
|
|
25
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}}
|
|
238
|
-
let result = template.replace(/\{\{\?(\w+)(
|
|
239
|
-
if (
|
|
240
|
-
return String(vars[key])
|
|
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
|
}
|