@xiaodye/runeclaw 1.0.5 → 1.0.7
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/LICENSE +21 -0
- package/README.md +160 -84
- package/dist/index.js +106 -19
- package/package.json +14 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xiaodye
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,28 +1,75 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="assets/runeclaw-logo.svg" alt="RuneClaw" width="480" />
|
|
4
|
+
|
|
1
5
|
# RuneClaw
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
** 一个运行在本地的 Super Agent,通过命令行工具进行交互,也可接飞书、带记忆与 RAG、可插拔工具/插件、能派发子 Agent,核心能力对齐 OpenClaw。**
|
|
8
|
+
|
|
9
|
+
模型调用 · 文件工具 · RAG · 记忆 · MCP · 子 Agent · 定时任务 · 插件 · 飞书通道
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/@xiaodye/runeclaw)
|
|
12
|
+
[](#许可证)
|
|
4
13
|
|
|
5
|
-
|
|
14
|
+
[](https://www.typescriptlang.org)
|
|
15
|
+
[](https://nodejs.org)
|
|
16
|
+
[](https://pnpm.io)
|
|
17
|
+
[](https://github.com/vadimdemedes/ink)
|
|
18
|
+
[](https://ai-sdk.dev)
|
|
19
|
+
[](https://sqlite.org)
|
|
6
20
|
|
|
7
|
-
|
|
21
|
+
</div>
|
|
8
22
|
|
|
9
|
-
|
|
10
|
-
- 交互式 CLI,支持多轮对话和工具调用
|
|
11
|
-
- 本地文件工具:读取、写入、编辑、列目录、grep、glob
|
|
12
|
-
- Shell 工具:可执行命令并返回输出
|
|
13
|
-
- 记忆系统:跨会话保存、读取、搜索记忆
|
|
14
|
-
- RAG 知识库:启动时加 `--rag` 自动导入 `docs/`,也支持手动导入其它文档
|
|
15
|
-
- MCP 接入:可挂载外部工具,当前默认有 GitHub mock 示例
|
|
16
|
-
- 子 Agent:支持任务分发与执行记录查看
|
|
17
|
-
- 定时任务:支持查看任务与执行日志
|
|
18
|
-
- 插件、技能、飞书 Channel、上下文视图、用量统计、循环检测
|
|
23
|
+
---
|
|
19
24
|
|
|
20
|
-
|
|
25
|
+
RuneClaw 是一个基于 TypeScript 的本地 AI Agent CLI。它把模型调用、文件工具、RAG、记忆、MCP、子 Agent、定时任务、插件和飞书通道放在同一个运行时里,适合做可迭代的个人 Agent 实验。
|
|
26
|
+
|
|
27
|
+
<img src="https://github.com/user-attachments/assets/2f999223-5e7f-4f7d-aabd-c6b78cc8087a" alt="RuneClaw 运行截图" width="100%" />
|
|
28
|
+
|
|
29
|
+
## ✨ 特性
|
|
30
|
+
|
|
31
|
+
| 能力 | 说明 |
|
|
32
|
+
| -------------- | ------------------------------------------------------------ |
|
|
33
|
+
| 🤖 模型接入 | DeepSeek 兼容模型,默认使用 `deepseek-v4-flash` |
|
|
34
|
+
| 💬 交互式 CLI | 多轮对话 + 工具调用,基于 Ink 的终端 UI |
|
|
35
|
+
| 📂 文件工具 | 读取、写入、编辑、列目录、grep、glob |
|
|
36
|
+
| 🐚 Shell 工具 | 执行命令并返回输出,带安全钩子与审计日志 |
|
|
37
|
+
| 🧠 记忆系统 | 跨会话保存、读取、搜索记忆 |
|
|
38
|
+
| 📚 RAG 知识库 | SQLite 向量 + 全文检索,启动时加 `--rag` 自动导入 `docs/` |
|
|
39
|
+
| 🔌 MCP 接入 | 可挂载外部工具,当前默认有 GitHub mock 示例 |
|
|
40
|
+
| 🪆 子 Agent | 任务分发与执行记录查看 |
|
|
41
|
+
| ⏰ 定时任务 | 查看 cron 任务与执行日志 |
|
|
42
|
+
| 🧩 插件 / 技能 | 动态加载与卸载,飞书 Channel、上下文视图、用量统计、循环检测 |
|
|
43
|
+
|
|
44
|
+
## 🏗️ 架构总览
|
|
45
|
+
|
|
46
|
+
```mermaid
|
|
47
|
+
flowchart LR
|
|
48
|
+
U([用户]) -->|终端 / 飞书| CLI[CLI 入口]
|
|
49
|
+
CLI --> AG[Agent 循环<br/>重试 · 循环检测]
|
|
50
|
+
AG --> CTX[上下文管理<br/>prompt 组装 · 压缩]
|
|
51
|
+
AG --> TOOLS[工具系统]
|
|
52
|
+
AG --> SUB[子 Agent 调度]
|
|
53
|
+
|
|
54
|
+
TOOLS --> FILE[文件 / Shell]
|
|
55
|
+
TOOLS --> MEM[(记忆<br/>MEMORY.md)]
|
|
56
|
+
TOOLS --> RAG[(Rag 知识库<br/>SQLite)]
|
|
57
|
+
TOOLS --> MCP[MCP 客户端]
|
|
58
|
+
TOOLS --> WEB[Web 搜索]
|
|
59
|
+
|
|
60
|
+
AG --- CH[飞书 Channel]
|
|
61
|
+
AG --- CRON[定时任务]
|
|
62
|
+
AG --- PLG[插件 / 技能]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## 📦 环境要求
|
|
21
66
|
|
|
22
67
|
- Node.js 22+
|
|
23
68
|
- `pnpm` 10.x
|
|
24
69
|
|
|
25
|
-
##
|
|
70
|
+
## 🚀 快速开始
|
|
71
|
+
|
|
72
|
+
### 1. 安装
|
|
26
73
|
|
|
27
74
|
```bash
|
|
28
75
|
pnpm install
|
|
@@ -48,23 +95,24 @@ pnpm link --global
|
|
|
48
95
|
> 不想注册全局命令的话,也可以用 `pnpm exec runeclaw` 直接运行。
|
|
49
96
|
> 修改源码后重新执行 `pnpm build` 即可让全局命令生效。
|
|
50
97
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
首次运行建议先初始化配置:
|
|
98
|
+
### 2. 初始化并启动
|
|
54
99
|
|
|
55
100
|
```bash
|
|
56
|
-
runeclaw init
|
|
101
|
+
runeclaw init # 运行初始化向导,生成 runeclaw.config.json 与 .env
|
|
102
|
+
runeclaw # 启动交互式 Agent
|
|
57
103
|
```
|
|
58
104
|
|
|
59
|
-
|
|
105
|
+
如果你已经有完整的配置文件,也可以直接启动。
|
|
106
|
+
|
|
107
|
+
### 3. 从 npm 安装(发布后)
|
|
60
108
|
|
|
61
109
|
```bash
|
|
110
|
+
npm install -g @xiaodye/runeclaw
|
|
111
|
+
runeclaw init
|
|
62
112
|
runeclaw
|
|
63
113
|
```
|
|
64
114
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
`runeclaw` 支持以下命令:
|
|
115
|
+
### CLI 命令
|
|
68
116
|
|
|
69
117
|
| 命令 | 说明 |
|
|
70
118
|
| -------------------------------- | ----------------------------------------------------- |
|
|
@@ -74,35 +122,79 @@ runeclaw
|
|
|
74
122
|
| `runeclaw help` / `--help` | 查看帮助 |
|
|
75
123
|
| `runeclaw version` / `--version` | 查看版本号 |
|
|
76
124
|
|
|
77
|
-
##
|
|
125
|
+
## ⌨️ 会话内常用命令
|
|
78
126
|
|
|
79
|
-
|
|
127
|
+
<details open>
|
|
128
|
+
<summary><b>基础</b></summary>
|
|
80
129
|
|
|
81
|
-
|
|
130
|
+
| 命令 | 说明 |
|
|
131
|
+
| ---------- | ------------------------------ |
|
|
132
|
+
| `/exit` | 退出 |
|
|
133
|
+
| `/context` | 查看上下文占用 |
|
|
134
|
+
| `/usage` | 查看 token 和成本统计 |
|
|
135
|
+
| `status` | 查看当前消息、记忆和知识库状态 |
|
|
82
136
|
|
|
83
|
-
|
|
84
|
-
pnpm run release # 默认补丁号 +1(1.0.0 → 1.0.1)
|
|
85
|
-
pnpm run release -- minor # 次版本号 +1(1.0.0 → 1.1.0)
|
|
86
|
-
pnpm run release -- major # 主版本号 +1(1.0.0 → 2.0.0)
|
|
87
|
-
```
|
|
137
|
+
</details>
|
|
88
138
|
|
|
89
|
-
>
|
|
90
|
-
>
|
|
91
|
-
> - 发布用 `npm publish --registry https://registry.npmjs.org/`,命令行参数优先级最高,能覆盖任何 `.npmrc` 里的镜像源配置(镜像源只读,无法发布),也无需改动全局 npm 配置;
|
|
92
|
-
> - `npm publish` 的 `prepublishOnly` 会自动执行 `pnpm build`,无需手动构建;
|
|
93
|
-
> - 首次发布前需要先 `npm login`。
|
|
139
|
+
<details>
|
|
140
|
+
<summary><b>记忆</b></summary>
|
|
94
141
|
|
|
95
|
-
|
|
142
|
+
| 命令 | 说明 |
|
|
143
|
+
| ------------------------- | ------------ |
|
|
144
|
+
| `/memory` | 查看记忆列表 |
|
|
145
|
+
| `/memory search <关键词>` | 搜索记忆 |
|
|
96
146
|
|
|
97
|
-
|
|
98
|
-
npm install -g @xiaodye/runeclaw
|
|
99
|
-
runeclaw init
|
|
100
|
-
runeclaw
|
|
101
|
-
```
|
|
147
|
+
</details>
|
|
102
148
|
|
|
103
|
-
>
|
|
149
|
+
<details>
|
|
150
|
+
<summary><b>知识库</b></summary>
|
|
104
151
|
|
|
105
|
-
|
|
152
|
+
| 命令 | 说明 |
|
|
153
|
+
| --------------- | -------------------- |
|
|
154
|
+
| `/rag` | 查看知识库状态 |
|
|
155
|
+
| `ingest <path>` | 导入本地文档到知识库 |
|
|
156
|
+
|
|
157
|
+
</details>
|
|
158
|
+
|
|
159
|
+
<details>
|
|
160
|
+
<summary><b>技能与插件</b></summary>
|
|
161
|
+
|
|
162
|
+
| 命令 | 说明 |
|
|
163
|
+
| --------------------------- | -------- |
|
|
164
|
+
| `/skill` 或 `/skill list` | 查看技能 |
|
|
165
|
+
| `/skill load <name>` | 激活技能 |
|
|
166
|
+
| `/skill unload <name>` | 卸载技能 |
|
|
167
|
+
| `/plugin` 或 `/plugin list` | 查看插件 |
|
|
168
|
+
| `/plugin load <name>` | 加载插件 |
|
|
169
|
+
| `/plugin unload <name>` | 卸载插件 |
|
|
170
|
+
|
|
171
|
+
</details>
|
|
172
|
+
|
|
173
|
+
<details>
|
|
174
|
+
<summary><b>通道、定时任务、子 Agent</b></summary>
|
|
175
|
+
|
|
176
|
+
| 命令 | 说明 |
|
|
177
|
+
| -------------- | --------------------- |
|
|
178
|
+
| `/channel` | 查看已注册通道 |
|
|
179
|
+
| `/cron` | 查看定时任务 |
|
|
180
|
+
| `/cron logs` | 查看最近执行日志 |
|
|
181
|
+
| `/agents` | 查看子 Agent 执行记录 |
|
|
182
|
+
| `/role [角色]` | 查看或切换角色 |
|
|
183
|
+
|
|
184
|
+
</details>
|
|
185
|
+
|
|
186
|
+
<details>
|
|
187
|
+
<summary><b>调试</b></summary>
|
|
188
|
+
|
|
189
|
+
| 命令 | 说明 |
|
|
190
|
+
| ------------------------- | --------------- |
|
|
191
|
+
| `sim` 或 `模拟长对话` | 注入测试对话 |
|
|
192
|
+
| `defend` 或 `执行防线` | 应用上下文防线 |
|
|
193
|
+
| `/cache on`、`/cache off` | 切换 cache 模拟 |
|
|
194
|
+
|
|
195
|
+
</details>
|
|
196
|
+
|
|
197
|
+
## ⚙️ 配置说明
|
|
106
198
|
|
|
107
199
|
初始化后会生成两个文件:
|
|
108
200
|
|
|
@@ -143,51 +235,35 @@ SERPER_API_KEY=
|
|
|
143
235
|
|
|
144
236
|
如果你手动编辑配置,也建议继续使用 DeepSeek 的 OpenAI-compatible 接入方式。
|
|
145
237
|
|
|
146
|
-
##
|
|
147
|
-
|
|
148
|
-
启动后可以直接输入这些命令:
|
|
149
|
-
|
|
150
|
-
### 基础
|
|
151
|
-
|
|
152
|
-
- `/exit` 退出
|
|
153
|
-
- `/context` 查看上下文占用
|
|
154
|
-
- `/usage` 查看 token 和成本统计
|
|
155
|
-
- `status` 查看当前消息、记忆和知识库状态
|
|
238
|
+
## 📤 发布到 npm
|
|
156
239
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
- `/memory` 查看记忆列表
|
|
160
|
-
- `/memory search <关键词>` 搜索记忆
|
|
161
|
-
|
|
162
|
-
### 知识库
|
|
163
|
-
|
|
164
|
-
- `/rag` 查看知识库状态
|
|
165
|
-
- `ingest <path>` 导入本地文档到知识库
|
|
240
|
+
包名是 `@xiaodye/runeclaw`,`publishConfig.access: public` 已配置好(scoped 包默认私有,必须显式公开)。
|
|
166
241
|
|
|
167
|
-
|
|
242
|
+
一键发布(`scripts/release.mjs` 自动完成:**版本号 +1 → 官方源发布 → 输出结果**):
|
|
168
243
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
- `/plugin unload <name>` 卸载插件
|
|
244
|
+
```bash
|
|
245
|
+
pnpm run release # 默认补丁号 +1(1.0.0 → 1.0.1)
|
|
246
|
+
pnpm run release -- minor # 次版本号 +1(1.0.0 → 1.1.0)
|
|
247
|
+
pnpm run release -- major # 主版本号 +1(1.0.0 → 2.0.0)
|
|
248
|
+
```
|
|
175
249
|
|
|
176
|
-
|
|
250
|
+
> 说明:
|
|
251
|
+
>
|
|
252
|
+
> - 发布用 `npm publish --registry https://registry.npmjs.org/`,命令行参数优先级最高,能覆盖任何 `.npmrc` 里的镜像源配置(镜像源只读,无法发布),也无需改动全局 npm 配置;
|
|
253
|
+
> - `npm publish` 的 `prepublishOnly` 会自动执行 `pnpm build`,无需手动构建;
|
|
254
|
+
> - 首次发布前需要先 `npm login`。
|
|
177
255
|
|
|
178
|
-
|
|
179
|
-
- `/cron` 查看定时任务
|
|
180
|
-
- `/cron logs` 查看最近执行日志
|
|
181
|
-
- `/agents` 查看子 Agent 执行记录
|
|
182
|
-
- `/role [角色]` 查看或切换角色
|
|
256
|
+
发布后其他人可以这样安装使用:
|
|
183
257
|
|
|
184
|
-
|
|
258
|
+
```bash
|
|
259
|
+
npm install -g @xiaodye/runeclaw
|
|
260
|
+
runeclaw init
|
|
261
|
+
runeclaw
|
|
262
|
+
```
|
|
185
263
|
|
|
186
|
-
|
|
187
|
-
- `defend` 或 `执行防线` 应用上下文防线
|
|
188
|
-
- `/cache on`、`/cache off` 切换 cache 模拟
|
|
264
|
+
> 发布包只包含 `dist/`(`files` 字段已限制),不会带源码、`.env`、知识库等运行时数据;用户安装后在自己的目录里运行 `runeclaw init` 生成配置。
|
|
189
265
|
|
|
190
|
-
## Codex PR 审查机器人
|
|
266
|
+
## 🤖 Codex PR 审查机器人
|
|
191
267
|
|
|
192
268
|
仓库通过 `openai/codex-action` 自动审查同仓库分支提交的非草稿 PR。机器人会在 PR 创建、重新打开、转为可审查状态或推送新提交时运行,并持续更新同一条中文审查评论。
|
|
193
269
|
|
|
@@ -199,7 +275,7 @@ OPENAI_API_KEY=your-openai-api-key
|
|
|
199
275
|
|
|
200
276
|
出于安全考虑,workflow 不处理来自 fork 的 PR,也不会向 Codex 授予仓库写权限。配置见 `.github/workflows/codex-pr-review.yml`。
|
|
201
277
|
|
|
202
|
-
## 项目结构
|
|
278
|
+
## 🗂️ 项目结构
|
|
203
279
|
|
|
204
280
|
```text
|
|
205
281
|
src/
|
|
@@ -219,7 +295,7 @@ src/
|
|
|
219
295
|
tools/ 内置工具与 MCP 适配
|
|
220
296
|
```
|
|
221
297
|
|
|
222
|
-
## 说明
|
|
298
|
+
## 📝 说明
|
|
223
299
|
|
|
224
300
|
- 启动时默认**不**自动导入知识库;加 `--rag`(或 `pnpm start:rag`)才会导入 `docs/` 下的 `.md` 文档
|
|
225
301
|
- 当前项目里的飞书配置只应通过 `.env` 提供,不要把 secret 写进 `runeclaw.config.json`
|
|
@@ -227,4 +303,4 @@ src/
|
|
|
227
303
|
|
|
228
304
|
## 许可证
|
|
229
305
|
|
|
230
|
-
|
|
306
|
+
[MIT](LICENSE)
|
package/dist/index.js
CHANGED
|
@@ -219,13 +219,21 @@ function buildConfig(answers) {
|
|
|
219
219
|
function buildEnvContent(answers) {
|
|
220
220
|
const entries = [
|
|
221
221
|
{ comment: "\u6A21\u578B\u540D\u79F0\uFF08DeepSeek\uFF09", key: "LLM_MODEL", value: answers.model },
|
|
222
|
-
{
|
|
222
|
+
{
|
|
223
|
+
comment: "\u6A21\u578B API \u57FA\u7840\u5730\u5740\uFF08OpenAI \u517C\u5BB9\uFF09",
|
|
224
|
+
key: "LLM_API_BASE",
|
|
225
|
+
value: answers.baseURL
|
|
226
|
+
},
|
|
223
227
|
{ comment: "\u6A21\u578B API Key", key: "LLM_API_KEY", value: answers.apiKey }
|
|
224
228
|
];
|
|
225
229
|
if (answers.enableFeishu && answers.feishuAppId) {
|
|
226
230
|
entries.push(
|
|
227
231
|
{ comment: "\u98DE\u4E66\u673A\u5668\u4EBA App ID", key: "FEISHU_APP_ID", value: answers.feishuAppId },
|
|
228
|
-
{
|
|
232
|
+
{
|
|
233
|
+
comment: "\u98DE\u4E66\u673A\u5668\u4EBA App Secret",
|
|
234
|
+
key: "FEISHU_APP_SECRET",
|
|
235
|
+
value: answers.feishuAppSecret
|
|
236
|
+
}
|
|
229
237
|
);
|
|
230
238
|
}
|
|
231
239
|
if (answers.dashscopeKey) {
|
|
@@ -318,7 +326,10 @@ function ReviewList(props) {
|
|
|
318
326
|
["\u98DE\u4E66 App ID", answers.feishuAppId || "\uFF08\u7559\u7A7A\uFF09"],
|
|
319
327
|
["\u98DE\u4E66 App Secret", answers.feishuAppSecret ? "\uFF08\u5DF2\u586B\u5199\uFF09" : "\uFF08\u7559\u7A7A\uFF09"]
|
|
320
328
|
] : [],
|
|
321
|
-
[
|
|
329
|
+
[
|
|
330
|
+
"DashScope Key (\u5411\u91CF\u5316)",
|
|
331
|
+
answers.dashscopeKey ? "\uFF08\u5DF2\u586B\u5199\uFF09" : "\uFF08\u7559\u7A7A\uFF0C\u4F7F\u7528\u672C\u5730 mock \u5411\u91CF\u5316\uFF09"
|
|
332
|
+
],
|
|
322
333
|
["\u5B50 Agent \u5E76\u53D1\u6570", answers.maxConcurrent || "3"]
|
|
323
334
|
];
|
|
324
335
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: rows.map(([k, v]) => /* @__PURE__ */ jsxs(Box, { children: [
|
|
@@ -1732,7 +1743,9 @@ ${c.dim}\u2500\u2500 Step ${step} \u2500\u2500${c.reset}`);
|
|
|
1732
1743
|
case "tool-result": {
|
|
1733
1744
|
const output = typeof part.output === "string" ? part.output : JSON.stringify(part.output);
|
|
1734
1745
|
const preview = output.length > 120 ? output.slice(0, 120) + "..." : output;
|
|
1735
|
-
console.log(
|
|
1746
|
+
console.log(
|
|
1747
|
+
` ${c.gray}\u21B3 \u7ED3\u679C ${part.toolName}:${c.reset} ${c.dim}${preview}${c.reset}`
|
|
1748
|
+
);
|
|
1736
1749
|
if (lastToolCall) {
|
|
1737
1750
|
recordResult(lastToolCall.name, lastToolCall.input, part.output);
|
|
1738
1751
|
}
|
|
@@ -1746,7 +1759,9 @@ ${c.dim}\u2500\u2500 Step ${step} \u2500\u2500${c.reset}`);
|
|
|
1746
1759
|
} catch (error) {
|
|
1747
1760
|
if (attempt > MAX_RETRIES || !isRetryable(error)) throw error;
|
|
1748
1761
|
const delay = calculateDelay(attempt);
|
|
1749
|
-
console.log(
|
|
1762
|
+
console.log(
|
|
1763
|
+
` ${c.yellow}\u26A0 [\u91CD\u8BD5] \u7B2C ${attempt}/${MAX_RETRIES} \u6B21\uFF0C${delay}ms \u540E...${c.reset}`
|
|
1764
|
+
);
|
|
1750
1765
|
await sleep(delay);
|
|
1751
1766
|
hasToolCall = false;
|
|
1752
1767
|
fullText = "";
|
|
@@ -1765,10 +1780,14 @@ ${c.yellow}[\u5FAA\u73AF\u68C0\u6D4B\u89E6\u53D1\uFF0CAgent \u5DF2\u505C\u6B62]$
|
|
|
1765
1780
|
if (stepRecord && (norm.cacheReadTokens > 0 || norm.cacheWriteTokens > 0)) {
|
|
1766
1781
|
const tag = norm.cacheReadTokens > 0 ? `${c.green}\u2713 cache hit${c.reset}` : `${c.yellow}\u270E cache write${c.reset}`;
|
|
1767
1782
|
const detail = norm.cacheReadTokens > 0 ? `read ${norm.cacheReadTokens}` : `write ${norm.cacheWriteTokens}`;
|
|
1768
|
-
console.log(
|
|
1783
|
+
console.log(
|
|
1784
|
+
` [${tag}] ${c.dim}${detail} tokens \xB7 \u672C\u6B65 $${stepRecord.cost.toFixed(5)}${c.reset}`
|
|
1785
|
+
);
|
|
1769
1786
|
}
|
|
1770
1787
|
if (estimateMessageTokens(messages) > COMPACT_TOKEN_THRESHOLD) {
|
|
1771
|
-
console.log(
|
|
1788
|
+
console.log(
|
|
1789
|
+
` ${c.yellow}[Token] ~${estimateMessageTokens(messages)} \u8D85\u9884\u7B97\uFF0C\u89E6\u53D1\u538B\u7F29...${c.reset}`
|
|
1790
|
+
);
|
|
1772
1791
|
const compacted = await compactContext(model2, messages, timestamps);
|
|
1773
1792
|
messages.splice(0, messages.length, ...compacted.messages);
|
|
1774
1793
|
timestamps.clear();
|
|
@@ -1804,7 +1823,7 @@ var init_loop = __esm({
|
|
|
1804
1823
|
init_defense();
|
|
1805
1824
|
init_theme();
|
|
1806
1825
|
init_markdown();
|
|
1807
|
-
MAX_STEPS =
|
|
1826
|
+
MAX_STEPS = 30;
|
|
1808
1827
|
MAX_RETRIES = 3;
|
|
1809
1828
|
}
|
|
1810
1829
|
});
|
|
@@ -4769,7 +4788,8 @@ var init_gateway = __esm({
|
|
|
4769
4788
|
const outgoing = {
|
|
4770
4789
|
channelId: msg.channelId,
|
|
4771
4790
|
recipientId: msg.senderId,
|
|
4772
|
-
text: replyText
|
|
4791
|
+
text: replyText,
|
|
4792
|
+
messageId: msg.messageId
|
|
4773
4793
|
};
|
|
4774
4794
|
await channel.send(outgoing);
|
|
4775
4795
|
console.log(
|
|
@@ -4794,10 +4814,11 @@ var init_gateway = __esm({
|
|
|
4794
4814
|
});
|
|
4795
4815
|
|
|
4796
4816
|
// src/channels/feishu.ts
|
|
4797
|
-
var FeishuChannel;
|
|
4817
|
+
var WAIT_REACTION_EMOJI, FeishuChannel;
|
|
4798
4818
|
var init_feishu = __esm({
|
|
4799
4819
|
"src/channels/feishu.ts"() {
|
|
4800
4820
|
"use strict";
|
|
4821
|
+
WAIT_REACTION_EMOJI = "Typing";
|
|
4801
4822
|
FeishuChannel = class {
|
|
4802
4823
|
/** 通道注册名,用于网关识别和日志输出。 */
|
|
4803
4824
|
name = "feishu";
|
|
@@ -4813,6 +4834,8 @@ var init_feishu = __esm({
|
|
|
4813
4834
|
wsClient;
|
|
4814
4835
|
/** 飞书开放平台 API 客户端实例,用于发送回复。 */
|
|
4815
4836
|
larkClient;
|
|
4837
|
+
/** 消息 ID → 已添加的「稍等」表情回应 ID,完成回复后据此清除。 */
|
|
4838
|
+
reactions = /* @__PURE__ */ new Map();
|
|
4816
4839
|
/**
|
|
4817
4840
|
* 创建飞书通道实例并保存运行配置。
|
|
4818
4841
|
*
|
|
@@ -4856,11 +4879,14 @@ var init_feishu = __esm({
|
|
|
4856
4879
|
}
|
|
4857
4880
|
}
|
|
4858
4881
|
if (text && this.messageHandler) {
|
|
4882
|
+
const messageId = data.message.message_id;
|
|
4883
|
+
if (messageId) this.addWaitingReaction(messageId);
|
|
4859
4884
|
this.messageHandler({
|
|
4860
4885
|
channelId: data.message.chat_id,
|
|
4861
4886
|
senderId: data.sender.sender_id?.open_id || "unknown",
|
|
4862
4887
|
senderName: data.sender.sender_id?.open_id || "unknown",
|
|
4863
4888
|
text,
|
|
4889
|
+
messageId,
|
|
4864
4890
|
raw: data
|
|
4865
4891
|
});
|
|
4866
4892
|
}
|
|
@@ -4891,18 +4917,77 @@ var init_feishu = __esm({
|
|
|
4891
4917
|
return;
|
|
4892
4918
|
}
|
|
4893
4919
|
try {
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4920
|
+
if (message.messageId) {
|
|
4921
|
+
const card = {
|
|
4922
|
+
config: { wide_screen_mode: true },
|
|
4923
|
+
elements: [
|
|
4924
|
+
{
|
|
4925
|
+
tag: "div",
|
|
4926
|
+
text: { tag: "lark_md", content: message.text }
|
|
4927
|
+
}
|
|
4928
|
+
]
|
|
4929
|
+
};
|
|
4930
|
+
await this.larkClient.im.message.reply({
|
|
4931
|
+
path: { message_id: message.messageId },
|
|
4932
|
+
data: {
|
|
4933
|
+
content: JSON.stringify(card),
|
|
4934
|
+
msg_type: "interactive",
|
|
4935
|
+
reply_in_thread: false
|
|
4936
|
+
}
|
|
4937
|
+
});
|
|
4938
|
+
} else {
|
|
4939
|
+
await this.larkClient.im.message.create({
|
|
4940
|
+
params: { receive_id_type: "chat_id" },
|
|
4941
|
+
data: {
|
|
4942
|
+
receive_id: message.channelId,
|
|
4943
|
+
msg_type: "text",
|
|
4944
|
+
content: JSON.stringify({ text: message.text })
|
|
4945
|
+
}
|
|
4946
|
+
});
|
|
4947
|
+
}
|
|
4902
4948
|
} catch (err) {
|
|
4903
4949
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4904
4950
|
console.error(` [feishu] \u53D1\u9001\u5931\u8D25: ${msg}`);
|
|
4905
4951
|
}
|
|
4952
|
+
if (message.messageId) await this.removeWaitingReaction(message.messageId);
|
|
4953
|
+
}
|
|
4954
|
+
/**
|
|
4955
|
+
* 给指定消息添加「稍等」表情回应,并记录返回的回应 ID 以便后续清除。
|
|
4956
|
+
*
|
|
4957
|
+
* @param messageId 目标消息 ID。
|
|
4958
|
+
*/
|
|
4959
|
+
async addWaitingReaction(messageId) {
|
|
4960
|
+
if (!this.larkClient) return;
|
|
4961
|
+
try {
|
|
4962
|
+
const res = await this.larkClient.im.messageReaction.create({
|
|
4963
|
+
path: { message_id: messageId },
|
|
4964
|
+
data: { reaction_type: { emoji_type: WAIT_REACTION_EMOJI } }
|
|
4965
|
+
});
|
|
4966
|
+
const reactionId = res.data?.reaction_id;
|
|
4967
|
+
if (reactionId) this.reactions.set(messageId, reactionId);
|
|
4968
|
+
} catch (err) {
|
|
4969
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4970
|
+
console.warn(` [feishu] \u6DFB\u52A0\u300C\u7A0D\u7B49\u300D\u8868\u60C5\u5931\u8D25: ${msg}`);
|
|
4971
|
+
}
|
|
4972
|
+
}
|
|
4973
|
+
/**
|
|
4974
|
+
* 删除指定消息上的「稍等」表情回应(回复发送完成时调用)。
|
|
4975
|
+
*
|
|
4976
|
+
* @param messageId 目标消息 ID。
|
|
4977
|
+
*/
|
|
4978
|
+
async removeWaitingReaction(messageId) {
|
|
4979
|
+
const reactionId = this.reactions.get(messageId);
|
|
4980
|
+
if (!this.larkClient || !reactionId) return;
|
|
4981
|
+
try {
|
|
4982
|
+
await this.larkClient.im.messageReaction.delete({
|
|
4983
|
+
path: { message_id: messageId, reaction_id: reactionId }
|
|
4984
|
+
});
|
|
4985
|
+
} catch (err) {
|
|
4986
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4987
|
+
console.warn(` [feishu] \u6E05\u9664\u300C\u7A0D\u7B49\u300D\u8868\u60C5\u5931\u8D25: ${msg}`);
|
|
4988
|
+
} finally {
|
|
4989
|
+
this.reactions.delete(messageId);
|
|
4990
|
+
}
|
|
4906
4991
|
}
|
|
4907
4992
|
/**
|
|
4908
4993
|
* 启动本地调试 Dashboard,并暴露模拟飞书 webhook 的测试入口。
|
|
@@ -6440,7 +6525,9 @@ var HELP_TEXT = `RuneClaw \u2014 \u672C\u5730 AI Agent \u8FD0\u884C\u65F6
|
|
|
6440
6525
|
`;
|
|
6441
6526
|
function readVersion() {
|
|
6442
6527
|
try {
|
|
6443
|
-
const pkg = JSON.parse(
|
|
6528
|
+
const pkg = JSON.parse(
|
|
6529
|
+
readFileSync4(new URL("../package.json", import.meta.url), "utf-8")
|
|
6530
|
+
);
|
|
6444
6531
|
return pkg.version ?? "0.0.0";
|
|
6445
6532
|
} catch {
|
|
6446
6533
|
return "0.0.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xiaodye/runeclaw",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Local AI agent runtime for tools, memory, RAG, sub-agents, plugins, cron jobs, and channels.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
"scripts": {
|
|
18
18
|
"dev": "tsx src/index.ts",
|
|
19
19
|
"build": "node scripts/build.mjs",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"lint:fix": "eslint . --fix",
|
|
22
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\" \"scripts/**/*.{js,mjs}\" \"*.{json,mjs}\" .prettierrc",
|
|
23
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx}\" \"scripts/**/*.{js,mjs}\" \"*.{json,mjs}\" .prettierrc",
|
|
20
24
|
"start": "node dist/index.js",
|
|
21
25
|
"init": "node dist/index.js init",
|
|
22
26
|
"continue": "node dist/index.js --continue",
|
|
@@ -33,7 +37,7 @@
|
|
|
33
37
|
"automation"
|
|
34
38
|
],
|
|
35
39
|
"author": "xiaodye",
|
|
36
|
-
"license": "
|
|
40
|
+
"license": "MIT",
|
|
37
41
|
"packageManager": "pnpm@10.17.0",
|
|
38
42
|
"publishConfig": {
|
|
39
43
|
"access": "public"
|
|
@@ -60,6 +64,10 @@
|
|
|
60
64
|
"zod": "^3.25.76"
|
|
61
65
|
},
|
|
62
66
|
"devDependencies": {
|
|
67
|
+
"@eslint/js": "^10.0.1",
|
|
68
|
+
"@midscene/cli": "^1.12.2",
|
|
69
|
+
"@midscene/web": "^1.12.2",
|
|
70
|
+
"@playwright/test": "^1.62.1",
|
|
63
71
|
"@types/better-sqlite3": "^7.6.13",
|
|
64
72
|
"@types/json-schema": "^7.0.15",
|
|
65
73
|
"@types/node": "^25.9.3",
|
|
@@ -67,9 +75,12 @@
|
|
|
67
75
|
"@types/turndown": "^5.0.6",
|
|
68
76
|
"ajv": "^8.20.0",
|
|
69
77
|
"esbuild": "^0.28.1",
|
|
78
|
+
"eslint": "^10.9.1",
|
|
79
|
+
"eslint-config-prettier": "^10.1.8",
|
|
70
80
|
"json-schema-typed": "^8.0.2",
|
|
71
81
|
"prettier": "^3.9.6",
|
|
72
82
|
"tsx": "^4.22.4",
|
|
73
|
-
"typescript": "^6.0.3"
|
|
83
|
+
"typescript": "^6.0.3",
|
|
84
|
+
"typescript-eslint": "^8.69.0"
|
|
74
85
|
}
|
|
75
86
|
}
|