@xiaodye/runeclaw 1.0.5 → 1.0.8

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 (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +158 -84
  3. package/dist/index.js +106 -19
  4. 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,73 @@
1
+ <div align="center">
2
+
3
+ <img src="https://github.com/xiaodye/runeclaw/blob/main/assets/runeclaw-logo.svg" alt="RuneClaw" width="480" />
4
+
1
5
  # RuneClaw
2
6
 
3
- RuneClaw 是一个基于 TypeScript 的本地 AI Agent CLI。它把模型调用、文件工具、RAG、记忆、MCP、子 Agent、定时任务、插件和飞书通道放在同一个运行时里,适合做可迭代的个人 Agent 实验。
7
+ **一个运行在本地的 Super Agent,通过命令行工具进行交互,也可接飞书、带记忆与 RAG、可插拔工具/插件、能派发子 Agent,核心能力对齐 OpenClaw。**
4
8
 
5
- <img width="1688" height="1070" alt="image" src="https://github.com/user-attachments/assets/2f999223-5e7f-4f7d-aabd-c6b78cc8087a" />
9
+ 模型调用 · 文件工具 · RAG · 记忆 · MCP · 子 Agent · 定时任务 · 插件 · 飞书通道
6
10
 
7
- ## 特性
11
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
12
+ [![Node.js 22+](https://img.shields.io/badge/Node.js%2022%2B-5FA04E?style=flat&logo=nodedotjs&logoColor=white)](https://nodejs.org)
13
+ [![pnpm](https://img.shields.io/badge/pnpm-10.x-F69220?style=flat&logo=pnpm&logoColor=white)](https://pnpm.io)
14
+ [![React Ink](https://img.shields.io/badge/React%20Ink-61DAFB?style=flat&logo=react&logoColor=black)](https://github.com/vadimdemedes/ink)
15
+ [![Vercel AI SDK](https://img.shields.io/badge/Vercel%20AI%20SDK-000000?style=flat&logo=vercel&logoColor=white)](https://ai-sdk.dev)
16
+ [![SQLite](https://img.shields.io/badge/SQLite-003B57?style=flat&logo=sqlite&logoColor=white)](https://sqlite.org)
17
+ [![license](https://img.shields.io/badge/license-MIT-yellow)](#许可证)
8
18
 
9
- - DeepSeek 兼容模型接入,默认使用 `deepseek-v4-flash`
10
- - 交互式 CLI,支持多轮对话和工具调用
11
- - 本地文件工具:读取、写入、编辑、列目录、grep、glob
12
- - Shell 工具:可执行命令并返回输出
13
- - 记忆系统:跨会话保存、读取、搜索记忆
14
- - RAG 知识库:启动时加 `--rag` 自动导入 `docs/`,也支持手动导入其它文档
15
- - MCP 接入:可挂载外部工具,当前默认有 GitHub mock 示例
16
- - 子 Agent:支持任务分发与执行记录查看
17
- - 定时任务:支持查看任务与执行日志
18
- - 插件、技能、飞书 Channel、上下文视图、用量统计、循环检测
19
+ </div>
19
20
 
20
- ## 环境要求
21
+ ---
22
+
23
+ RuneClaw 是一个基于 TypeScript 的本地 AI Agent CLI。它把模型调用、文件工具、RAG、记忆、MCP、子 Agent、定时任务、插件和飞书通道放在同一个运行时里,适合做可迭代的个人 Agent 实验。
24
+
25
+ <img src="https://github.com/xiaodye/runeclaw/blob/main/assets/image.png" alt="RuneClaw 运行截图" width="100%" />
26
+
27
+ ## ✨ 特性
28
+
29
+ | 能力 | 说明 |
30
+ | -------------- | ------------------------------------------------------------ |
31
+ | 🤖 模型接入 | DeepSeek 兼容模型,默认使用 `deepseek-v4-flash` |
32
+ | 💬 交互式 CLI | 多轮对话 + 工具调用,基于 Ink 的终端 UI |
33
+ | 📂 文件工具 | 读取、写入、编辑、列目录、grep、glob |
34
+ | 🐚 Shell 工具 | 执行命令并返回输出,带安全钩子与审计日志 |
35
+ | 🧠 记忆系统 | 跨会话保存、读取、搜索记忆 |
36
+ | 📚 RAG 知识库 | SQLite 向量 + 全文检索,启动时加 `--rag` 自动导入 `docs/` |
37
+ | 🔌 MCP 接入 | 可挂载外部工具,当前默认有 GitHub mock 示例 |
38
+ | 🪆 子 Agent | 任务分发与执行记录查看 |
39
+ | ⏰ 定时任务 | 查看 cron 任务与执行日志 |
40
+ | 🧩 插件 / 技能 | 动态加载与卸载,飞书 Channel、上下文视图、用量统计、循环检测 |
41
+
42
+ ## 🏗️ 架构总览
43
+
44
+ ```mermaid
45
+ flowchart LR
46
+ U([用户]) -->|终端 / 飞书| CLI[CLI 入口]
47
+ CLI --> AG[Agent 循环<br/>重试 · 循环检测]
48
+ AG --> CTX[上下文管理<br/>prompt 组装 · 压缩]
49
+ AG --> TOOLS[工具系统]
50
+ AG --> SUB[子 Agent 调度]
51
+
52
+ TOOLS --> FILE[文件 / Shell]
53
+ TOOLS --> MEM[(记忆<br/>MEMORY.md)]
54
+ TOOLS --> RAG[(Rag 知识库<br/>SQLite)]
55
+ TOOLS --> MCP[MCP 客户端]
56
+ TOOLS --> WEB[Web 搜索]
57
+
58
+ AG --- CH[飞书 Channel]
59
+ AG --- CRON[定时任务]
60
+ AG --- PLG[插件 / 技能]
61
+ ```
62
+
63
+ ## 📦 环境要求
21
64
 
22
65
  - Node.js 22+
23
66
  - `pnpm` 10.x
24
67
 
25
- ## 安装
68
+ ## 🚀 快速开始
69
+
70
+ ### 1. 安装
26
71
 
27
72
  ```bash
28
73
  pnpm install
@@ -48,23 +93,24 @@ pnpm link --global
48
93
  > 不想注册全局命令的话,也可以用 `pnpm exec runeclaw` 直接运行。
49
94
  > 修改源码后重新执行 `pnpm build` 即可让全局命令生效。
50
95
 
51
- ## 快速开始
52
-
53
- 首次运行建议先初始化配置:
96
+ ### 2. 初始化并启动
54
97
 
55
98
  ```bash
56
- runeclaw init
99
+ runeclaw init # 运行初始化向导,生成 runeclaw.config.json 与 .env
100
+ runeclaw # 启动交互式 Agent
57
101
  ```
58
102
 
59
- 然后启动:
103
+ 如果你已经有完整的配置文件,也可以直接启动。
104
+
105
+ ### 3. 从 npm 安装(发布后)
60
106
 
61
107
  ```bash
108
+ npm install -g @xiaodye/runeclaw
109
+ runeclaw init
62
110
  runeclaw
63
111
  ```
64
112
 
65
- 如果你已经有完整的配置文件,也可以直接启动。
66
-
67
- `runeclaw` 支持以下命令:
113
+ ### CLI 命令
68
114
 
69
115
  | 命令 | 说明 |
70
116
  | -------------------------------- | ----------------------------------------------------- |
@@ -74,35 +120,79 @@ runeclaw
74
120
  | `runeclaw help` / `--help` | 查看帮助 |
75
121
  | `runeclaw version` / `--version` | 查看版本号 |
76
122
 
77
- ## 发布到 npm
123
+ ## ⌨️ 会话内常用命令
78
124
 
79
- 包名是 `@xiaodye/runeclaw`,`publishConfig.access: public` 已配置好(scoped 包默认私有,必须显式公开)。
125
+ <details open>
126
+ <summary><b>基础</b></summary>
80
127
 
81
- 一键发布(`scripts/release.mjs` 自动完成:**版本号 +1 → 官方源发布 → 输出结果**):
128
+ | 命令 | 说明 |
129
+ | ---------- | ------------------------------ |
130
+ | `/exit` | 退出 |
131
+ | `/context` | 查看上下文占用 |
132
+ | `/usage` | 查看 token 和成本统计 |
133
+ | `status` | 查看当前消息、记忆和知识库状态 |
82
134
 
83
- ```bash
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
- ```
135
+ </details>
88
136
 
89
- > 说明:
90
- >
91
- > - 发布用 `npm publish --registry https://registry.npmjs.org/`,命令行参数优先级最高,能覆盖任何 `.npmrc` 里的镜像源配置(镜像源只读,无法发布),也无需改动全局 npm 配置;
92
- > - `npm publish` 的 `prepublishOnly` 会自动执行 `pnpm build`,无需手动构建;
93
- > - 首次发布前需要先 `npm login`。
137
+ <details>
138
+ <summary><b>记忆</b></summary>
94
139
 
95
- 发布后其他人可以这样安装使用:
140
+ | 命令 | 说明 |
141
+ | ------------------------- | ------------ |
142
+ | `/memory` | 查看记忆列表 |
143
+ | `/memory search <关键词>` | 搜索记忆 |
96
144
 
97
- ```bash
98
- npm install -g @xiaodye/runeclaw
99
- runeclaw init
100
- runeclaw
101
- ```
145
+ </details>
102
146
 
103
- > 发布包只包含 `dist/`(`files` 字段已限制),不会带源码、`.env`、知识库等运行时数据;用户安装后在自己的目录里运行 `runeclaw init` 生成配置。
147
+ <details>
148
+ <summary><b>知识库</b></summary>
104
149
 
105
- ## 配置说明
150
+ | 命令 | 说明 |
151
+ | --------------- | -------------------- |
152
+ | `/rag` | 查看知识库状态 |
153
+ | `ingest <path>` | 导入本地文档到知识库 |
154
+
155
+ </details>
156
+
157
+ <details>
158
+ <summary><b>技能与插件</b></summary>
159
+
160
+ | 命令 | 说明 |
161
+ | --------------------------- | -------- |
162
+ | `/skill` 或 `/skill list` | 查看技能 |
163
+ | `/skill load <name>` | 激活技能 |
164
+ | `/skill unload <name>` | 卸载技能 |
165
+ | `/plugin` 或 `/plugin list` | 查看插件 |
166
+ | `/plugin load <name>` | 加载插件 |
167
+ | `/plugin unload <name>` | 卸载插件 |
168
+
169
+ </details>
170
+
171
+ <details>
172
+ <summary><b>通道、定时任务、子 Agent</b></summary>
173
+
174
+ | 命令 | 说明 |
175
+ | -------------- | --------------------- |
176
+ | `/channel` | 查看已注册通道 |
177
+ | `/cron` | 查看定时任务 |
178
+ | `/cron logs` | 查看最近执行日志 |
179
+ | `/agents` | 查看子 Agent 执行记录 |
180
+ | `/role [角色]` | 查看或切换角色 |
181
+
182
+ </details>
183
+
184
+ <details>
185
+ <summary><b>调试</b></summary>
186
+
187
+ | 命令 | 说明 |
188
+ | ------------------------- | --------------- |
189
+ | `sim` 或 `模拟长对话` | 注入测试对话 |
190
+ | `defend` 或 `执行防线` | 应用上下文防线 |
191
+ | `/cache on`、`/cache off` | 切换 cache 模拟 |
192
+
193
+ </details>
194
+
195
+ ## ⚙️ 配置说明
106
196
 
107
197
  初始化后会生成两个文件:
108
198
 
@@ -143,51 +233,35 @@ SERPER_API_KEY=
143
233
 
144
234
  如果你手动编辑配置,也建议继续使用 DeepSeek 的 OpenAI-compatible 接入方式。
145
235
 
146
- ## 常用命令
147
-
148
- 启动后可以直接输入这些命令:
149
-
150
- ### 基础
151
-
152
- - `/exit` 退出
153
- - `/context` 查看上下文占用
154
- - `/usage` 查看 token 和成本统计
155
- - `status` 查看当前消息、记忆和知识库状态
236
+ ## 📤 发布到 npm
156
237
 
157
- ### 记忆
158
-
159
- - `/memory` 查看记忆列表
160
- - `/memory search <关键词>` 搜索记忆
161
-
162
- ### 知识库
163
-
164
- - `/rag` 查看知识库状态
165
- - `ingest <path>` 导入本地文档到知识库
238
+ 包名是 `@xiaodye/runeclaw`,`publishConfig.access: public` 已配置好(scoped 包默认私有,必须显式公开)。
166
239
 
167
- ### 技能与插件
240
+ 一键发布(`scripts/release.mjs` 自动完成:**版本号 +1 → 官方源发布 → 输出结果**):
168
241
 
169
- - `/skill` 或 `/skill list` 查看技能
170
- - `/skill load <name>` 激活技能
171
- - `/skill unload <name>` 卸载技能
172
- - `/plugin` 或 `/plugin list` 查看插件
173
- - `/plugin load <name>` 加载插件
174
- - `/plugin unload <name>` 卸载插件
242
+ ```bash
243
+ pnpm run release # 默认补丁号 +1(1.0.0 → 1.0.1)
244
+ pnpm run release -- minor # 次版本号 +1(1.0.0 → 1.1.0)
245
+ pnpm run release -- major # 主版本号 +1(1.0.0 → 2.0.0)
246
+ ```
175
247
 
176
- ### 通道、定时任务、子 Agent
248
+ > 说明:
249
+ >
250
+ > - 发布用 `npm publish --registry https://registry.npmjs.org/`,命令行参数优先级最高,能覆盖任何 `.npmrc` 里的镜像源配置(镜像源只读,无法发布),也无需改动全局 npm 配置;
251
+ > - `npm publish` 的 `prepublishOnly` 会自动执行 `pnpm build`,无需手动构建;
252
+ > - 首次发布前需要先 `npm login`。
177
253
 
178
- - `/channel` 查看已注册通道
179
- - `/cron` 查看定时任务
180
- - `/cron logs` 查看最近执行日志
181
- - `/agents` 查看子 Agent 执行记录
182
- - `/role [角色]` 查看或切换角色
254
+ 发布后其他人可以这样安装使用:
183
255
 
184
- ### 调试
256
+ ```bash
257
+ npm install -g @xiaodye/runeclaw
258
+ runeclaw init
259
+ runeclaw
260
+ ```
185
261
 
186
- - `sim` 或 `模拟长对话` 注入测试对话
187
- - `defend` 或 `执行防线` 应用上下文防线
188
- - `/cache on`、`/cache off` 切换 cache 模拟
262
+ > 发布包只包含 `dist/`(`files` 字段已限制),不会带源码、`.env`、知识库等运行时数据;用户安装后在自己的目录里运行 `runeclaw init` 生成配置。
189
263
 
190
- ## Codex PR 审查机器人
264
+ ## 🤖 Codex PR 审查机器人
191
265
 
192
266
  仓库通过 `openai/codex-action` 自动审查同仓库分支提交的非草稿 PR。机器人会在 PR 创建、重新打开、转为可审查状态或推送新提交时运行,并持续更新同一条中文审查评论。
193
267
 
@@ -199,7 +273,7 @@ OPENAI_API_KEY=your-openai-api-key
199
273
 
200
274
  出于安全考虑,workflow 不处理来自 fork 的 PR,也不会向 Codex 授予仓库写权限。配置见 `.github/workflows/codex-pr-review.yml`。
201
275
 
202
- ## 项目结构
276
+ ## 🗂️ 项目结构
203
277
 
204
278
  ```text
205
279
  src/
@@ -219,7 +293,7 @@ src/
219
293
  tools/ 内置工具与 MCP 适配
220
294
  ```
221
295
 
222
- ## 说明
296
+ ## 📝 说明
223
297
 
224
298
  - 启动时默认**不**自动导入知识库;加 `--rag`(或 `pnpm start:rag`)才会导入 `docs/` 下的 `.md` 文档
225
299
  - 当前项目里的飞书配置只应通过 `.env` 提供,不要把 secret 写进 `runeclaw.config.json`
@@ -227,4 +301,4 @@ src/
227
301
 
228
302
  ## 许可证
229
303
 
230
- ISC
304
+ [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
- { comment: "\u6A21\u578B API \u57FA\u7840\u5730\u5740\uFF08OpenAI \u517C\u5BB9\uFF09", key: "LLM_API_BASE", value: answers.baseURL },
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
- { comment: "\u98DE\u4E66\u673A\u5668\u4EBA App Secret", key: "FEISHU_APP_SECRET", value: answers.feishuAppSecret }
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
- ["DashScope Key (\u5411\u91CF\u5316)", answers.dashscopeKey ? "\uFF08\u5DF2\u586B\u5199\uFF09" : "\uFF08\u7559\u7A7A\uFF0C\u4F7F\u7528\u672C\u5730 mock \u5411\u91CF\u5316\uFF09"],
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(` ${c.gray}\u21B3 \u7ED3\u679C ${part.toolName}:${c.reset} ${c.dim}${preview}${c.reset}`);
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(` ${c.yellow}\u26A0 [\u91CD\u8BD5] \u7B2C ${attempt}/${MAX_RETRIES} \u6B21\uFF0C${delay}ms \u540E...${c.reset}`);
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(` [${tag}] ${c.dim}${detail} tokens \xB7 \u672C\u6B65 $${stepRecord.cost.toFixed(5)}${c.reset}`);
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(` ${c.yellow}[Token] ~${estimateMessageTokens(messages)} \u8D85\u9884\u7B97\uFF0C\u89E6\u53D1\u538B\u7F29...${c.reset}`);
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 = 15;
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
- await this.larkClient.im.message.create({
4895
- params: { receive_id_type: "chat_id" },
4896
- data: {
4897
- receive_id: message.channelId,
4898
- msg_type: "text",
4899
- content: JSON.stringify({ text: message.text })
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(readFileSync4(new URL("../package.json", import.meta.url), "utf-8"));
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.5",
3
+ "version": "1.0.8",
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": "ISC",
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
  }