@xiaodye/runeclaw 1.0.4 → 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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +160 -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,75 @@
1
+ <div align="center">
2
+
3
+ <img src="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。**
8
+
9
+ 模型调用 · 文件工具 · RAG · 记忆 · MCP · 子 Agent · 定时任务 · 插件 · 飞书通道
10
+
11
+ [![npm version](https://img.shields.io/npm/v/%40xiaodye/runeclaw?color=cb3837&label=npm)](https://www.npmjs.com/package/@xiaodye/runeclaw)
12
+ [![license](https://img.shields.io/badge/license-MIT-yellow)](#许可证)
4
13
 
5
- ![alt text](./assets/image.png)
14
+ [![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
15
+ [![Node.js 22+](https://img.shields.io/badge/Node.js%2022%2B-5FA04E?style=flat&logo=nodedotjs&logoColor=white)](https://nodejs.org)
16
+ [![pnpm](https://img.shields.io/badge/pnpm-10.x-F69220?style=flat&logo=pnpm&logoColor=white)](https://pnpm.io)
17
+ [![React Ink](https://img.shields.io/badge/React%20Ink-61DAFB?style=flat&logo=react&logoColor=black)](https://github.com/vadimdemedes/ink)
18
+ [![Vercel AI SDK](https://img.shields.io/badge/Vercel%20AI%20SDK-000000?style=flat&logo=vercel&logoColor=white)](https://ai-sdk.dev)
19
+ [![SQLite](https://img.shields.io/badge/SQLite-003B57?style=flat&logo=sqlite&logoColor=white)](https://sqlite.org)
6
20
 
7
- ## 特性
21
+ </div>
8
22
 
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、上下文视图、用量统计、循环检测
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
- ## 发布到 npm
125
+ ## ⌨️ 会话内常用命令
78
126
 
79
- 包名是 `@xiaodye/runeclaw`,`publishConfig.access: public` 已配置好(scoped 包默认私有,必须显式公开)。
127
+ <details open>
128
+ <summary><b>基础</b></summary>
80
129
 
81
- 一键发布(`scripts/release.mjs` 自动完成:**版本号 +1 → 官方源发布 → 输出结果**):
130
+ | 命令 | 说明 |
131
+ | ---------- | ------------------------------ |
132
+ | `/exit` | 退出 |
133
+ | `/context` | 查看上下文占用 |
134
+ | `/usage` | 查看 token 和成本统计 |
135
+ | `status` | 查看当前消息、记忆和知识库状态 |
82
136
 
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
- ```
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
- ```bash
98
- npm install -g @xiaodye/runeclaw
99
- runeclaw init
100
- runeclaw
101
- ```
147
+ </details>
102
148
 
103
- > 发布包只包含 `dist/`(`files` 字段已限制),不会带源码、`.env`、知识库等运行时数据;用户安装后在自己的目录里运行 `runeclaw init` 生成配置。
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
- - `/skill` 或 `/skill list` 查看技能
170
- - `/skill load <name>` 激活技能
171
- - `/skill unload <name>` 卸载技能
172
- - `/plugin` 或 `/plugin list` 查看插件
173
- - `/plugin load <name>` 加载插件
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
- ### 通道、定时任务、子 Agent
250
+ > 说明:
251
+ >
252
+ > - 发布用 `npm publish --registry https://registry.npmjs.org/`,命令行参数优先级最高,能覆盖任何 `.npmrc` 里的镜像源配置(镜像源只读,无法发布),也无需改动全局 npm 配置;
253
+ > - `npm publish` 的 `prepublishOnly` 会自动执行 `pnpm build`,无需手动构建;
254
+ > - 首次发布前需要先 `npm login`。
177
255
 
178
- - `/channel` 查看已注册通道
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
- - `sim` 或 `模拟长对话` 注入测试对话
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
- ISC
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
- { 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.4",
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": "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
  }