@colinagent/hulala-bot 0.3.1 → 0.3.2
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/README.md +236 -96
- package/dist/plugin.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,11 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
把这个机器上的 DeepSeek Harness agent 接到 **Telegram** 和 **飞书 / Lark** 上。手机发一句话,本机的 Host 执行,回复流式回到同一个对话;`/cwd` 切换这个对话挂载哪个会话——和 TUI 里切换会话是同一件事。
|
|
4
4
|
|
|
5
|
-
DSH 原生和呼啦啦都用同一种方式接:**装成 Host 插件**,进 profile,随 Host 起落。
|
|
5
|
+
DSH 原生和呼啦啦都用同一种方式接:**装成 Host 插件**,进 profile,随 Host 起落。npm 上显示的就是这个文件。
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 让 AI 帮你配
|
|
10
|
+
|
|
11
|
+
这个文件就是给 AI 看的说明书:命令、配置项、期望输出、报错原文都写在里面。把下面这段发给你的 AI(Claude Code、Cursor、ChatGPT 都行),它会照着做:
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
帮我配置 hulala-bot(一个把 Telegram / 飞书接到本机 DeepSeek Harness 的插件)。
|
|
15
|
+
请先完整读 https://github.com/colinagent/hulala-bot 的 README,然后按顺序执行:
|
|
16
|
+
|
|
17
|
+
1. 检查环境:node -v(>=22.6)、pnpm -v、hulala --version
|
|
18
|
+
2. 按「安装」一节装插件(呼啦啦用 hulala install bot;没有这条命令就用 hulala plugin)
|
|
19
|
+
3. 按「配置」一节写 config.json;凭据只进系统钥匙串,不要让我把 token 贴进聊天,也不要写进 config.json
|
|
20
|
+
4. 重启 Host
|
|
21
|
+
5. 按「验证清单」逐条确认,每一步把实际输出贴给我
|
|
22
|
+
6. 任何一步失败,按「故障排查」的原文匹配原因,不要猜
|
|
23
|
+
|
|
24
|
+
规则:不要发明 README 之外的命令、配置项、权限或事件;缺信息就停下来问我。
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
给 AI 的三条硬规则,README 里也有对应依据:
|
|
28
|
+
|
|
29
|
+
1. **凭据只进钥匙串**,配置文件里只放 `tokenRef` / `appSecretRef`(`<service>/<account>`)。
|
|
30
|
+
2. **允许名单不能为空**:空名单会被拒绝启动,这不是可以跳过的步骤。
|
|
31
|
+
3. **改完配置必须重启 Host**:插件在启动时读取配置,没有热加载。
|
|
32
|
+
|
|
33
|
+
---
|
|
6
34
|
|
|
7
35
|
## 它是什么
|
|
8
36
|
|
|
9
|
-
**一个 Host
|
|
37
|
+
**一个 Host 插件**,不 patch DSH 一行代码。它跑在 Host 进程里,直接调宿主服务:
|
|
10
38
|
|
|
11
39
|
```
|
|
12
40
|
ctx.sessionController.list / create / prompt / follow → 会话与流式
|
|
@@ -14,108 +42,154 @@ ctx.workspaceController.create → 工作目录
|
|
|
14
42
|
ctx.on('approval/request', (req, next) => …) → 审批
|
|
15
43
|
```
|
|
16
44
|
|
|
17
|
-
没有端口、token、cookie、WebSocket
|
|
45
|
+
没有端口、token、cookie、WebSocket(飞书那条长连接是飞书自己的事件通道,不是本机服务)。会话历史、cwd、模型、权限以 Host 为真源;这里只保存「哪个对话挂了哪个会话」。
|
|
18
46
|
|
|
19
|
-
|
|
47
|
+
**一份代码,两个渠道**:`src/channel.ts` 是渠道契约,`src/telegram/`、`src/lark/` 各自实现,`src/bridge.ts` 的会话逻辑两边共用。两个渠道可以同时开,各自有允许名单和挂载表;Telegram 的对话和飞书的对话是两个独立会话,不合并身份。
|
|
20
48
|
|
|
21
|
-
|
|
49
|
+
---
|
|
22
50
|
|
|
23
51
|
## 安装
|
|
24
52
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
### 呼啦啦(一条命令)
|
|
53
|
+
先检查环境:
|
|
28
54
|
|
|
29
55
|
```sh
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
hulala
|
|
33
|
-
# 重启 Host
|
|
56
|
+
node -v # 需要 >= 22.6
|
|
57
|
+
pnpm -v # 安装走 pnpm;没有就先装
|
|
58
|
+
hulala --version
|
|
34
59
|
```
|
|
35
60
|
|
|
36
|
-
###
|
|
61
|
+
### 呼啦啦
|
|
37
62
|
|
|
38
63
|
```sh
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
64
|
+
hulala install bot
|
|
65
|
+
# 老版本没有这条命令时,等价写法:
|
|
66
|
+
hulala plugin --profile tui add @colinagent/hulala-bot
|
|
42
67
|
```
|
|
43
68
|
|
|
44
|
-
|
|
69
|
+
`hulala install bot` 只是把 profile 名(`tui`)和包名填好的快捷方式,安装仍由 DSH 的 `runPluginCommand` 完成。
|
|
45
70
|
|
|
46
|
-
|
|
71
|
+
### DSH(Web / Desktop / TUI)
|
|
47
72
|
|
|
48
73
|
```sh
|
|
49
|
-
|
|
50
|
-
hulala plugin --profile tui exec hulala-bot --init --no-input \
|
|
51
|
-
--telegram-token-ref telegram/bot-token \
|
|
52
|
-
--telegram-allowed-user 123456789 \
|
|
53
|
-
--default-cwd ~/hulala-workspace/workspace
|
|
54
|
-
|
|
55
|
-
# 飞书 / Lark
|
|
56
|
-
hulala plugin --profile tui exec hulala-bot --init --no-input \
|
|
57
|
-
--lark-app-id cli_0123456789abcdef \
|
|
58
|
-
--lark-app-secret-ref lark/app-secret \
|
|
59
|
-
--lark-allowed-user ou_0123456789abcdef0123456789abcdef
|
|
74
|
+
dsh plugin --profile tui add @colinagent/hulala-bot
|
|
60
75
|
```
|
|
61
76
|
|
|
62
|
-
|
|
77
|
+
**期望输出**:最后一行出现 `profile tui: @deepseek-ai/dsh-base, @colinagent/hulala-bot` 和 `Restart the Host for the change to take effect.`
|
|
63
78
|
|
|
64
79
|
装完核对:
|
|
65
80
|
|
|
66
81
|
```sh
|
|
67
|
-
hulala plugin --profile tui list
|
|
82
|
+
hulala plugin --profile tui list # 依赖里有 @colinagent/hulala-bot
|
|
68
83
|
```
|
|
69
84
|
|
|
70
85
|
卸载:`hulala plugin --profile tui remove @colinagent/hulala-bot`,然后重启 Host。
|
|
71
86
|
|
|
72
|
-
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 配置
|
|
90
|
+
|
|
91
|
+
配置写在 `$DSH_HOME/config/hulala-bot/config.json`:
|
|
92
|
+
|
|
93
|
+
| Host | 实际路径 |
|
|
94
|
+
|---|---|
|
|
95
|
+
| 呼啦啦 | `~/.hulala/harness/config/hulala-bot/config.json` |
|
|
96
|
+
| DSH | `~/.dsh/config/hulala-bot/config.json` |
|
|
97
|
+
|
|
98
|
+
用 `--init` 写,不要手写整个文件(它会校验、合并、设置 `0600` 权限):
|
|
73
99
|
|
|
74
|
-
|
|
100
|
+
```sh
|
|
101
|
+
# 呼啦啦
|
|
102
|
+
hulala plugin --profile tui exec hulala-bot --init
|
|
103
|
+
|
|
104
|
+
# DSH
|
|
105
|
+
dsh plugin --profile tui exec hulala-bot --init
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
有终端时它会逐项提问;没有终端(或加 `--no-input`)时用参数。**两条渠道可以只配一条**,也可以都配。
|
|
75
109
|
|
|
76
110
|
### Telegram
|
|
77
111
|
|
|
78
|
-
|
|
79
|
-
2. 你自己的 Telegram user id(可以问 [@userinfobot](https://t.me/userinfobot))。
|
|
80
|
-
3. 网络到不了 `api.telegram.org` 时配 `proxy`(见下)。
|
|
112
|
+
先准备:跟 [@BotFather](https://t.me/BotFather) 说 `/newbot` 拿 token;自己的 user id 可以问 [@userinfobot](https://t.me/userinfobot)。
|
|
81
113
|
|
|
82
|
-
token
|
|
114
|
+
token 放系统钥匙串(**不要**写进 config.json):
|
|
83
115
|
|
|
84
116
|
```bash
|
|
85
|
-
|
|
86
|
-
|
|
117
|
+
# macOS
|
|
118
|
+
security add-generic-password -U -s telegram -a bot-token -w '123456:ABC…'
|
|
119
|
+
# Linux
|
|
120
|
+
secret-tool store --label 'telegram bot' service telegram account bot-token
|
|
87
121
|
```
|
|
88
122
|
|
|
89
|
-
|
|
123
|
+
写配置:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
hulala plugin --profile tui exec hulala-bot --init --no-input \
|
|
127
|
+
--telegram-token-ref telegram/bot-token \
|
|
128
|
+
--telegram-allowed-user 123456789 \
|
|
129
|
+
--default-cwd ~/hulala-workspace/workspace
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
国内网络需要代理时加 `--proxy http://127.0.0.1:7897`(只支持 HTTP/HTTPS,SOCKS5 会在启动前报错)。
|
|
133
|
+
|
|
134
|
+
也可以临时用环境变量代替钥匙串:`TELEGRAM_BOT_TOKEN=…`,或配置里的 `tokenEnv` 指定变量名。
|
|
90
135
|
|
|
91
136
|
### 飞书 / Lark
|
|
92
137
|
|
|
93
|
-
|
|
138
|
+
配置由你在开发者后台完成,插件这边只填 app id 和允许名单。
|
|
94
139
|
|
|
95
|
-
1. 在 [飞书开放平台](https://open.feishu.cn)
|
|
96
|
-
2.
|
|
97
|
-
3.
|
|
98
|
-
4. **事件与回调 → 订阅方式:使用长连接接收事件**。订阅 `im.message.receive_v1`,并添加卡片回调 `card.action.trigger
|
|
99
|
-
5.
|
|
140
|
+
1. 在 [飞书开放平台](https://open.feishu.cn)(国际版 [Lark](https://open.larksuite.com))建一个**企业自建应用**,拿到 App ID(`cli_` + 16 位十六进制)和 App Secret。
|
|
141
|
+
2. 开启**机器人能力**。
|
|
142
|
+
3. 权限至少:`im:message`、`im:message:send_as_bot`。后台会按实际接口提示还缺哪一项。
|
|
143
|
+
4. **事件与回调 → 订阅方式:使用长连接接收事件**。订阅 `im.message.receive_v1`,并添加卡片回调 `card.action.trigger`。**不要**配 webhook 地址。
|
|
144
|
+
5. 发布版本,把可用范围设成你自己(或团队),然后在飞书里单聊机器人或把它拉进群(群里必须 @ 它)。
|
|
100
145
|
|
|
101
|
-
App secret
|
|
146
|
+
App secret 放钥匙串:
|
|
102
147
|
|
|
103
148
|
```bash
|
|
104
149
|
security add-generic-password -U -s lark -a app-secret -w '…'
|
|
105
150
|
```
|
|
106
151
|
|
|
107
|
-
|
|
152
|
+
写配置:
|
|
153
|
+
|
|
154
|
+
```sh
|
|
155
|
+
hulala plugin --profile tui exec hulala-bot --init --no-input \
|
|
156
|
+
--lark-app-id cli_0123456789abcdef \
|
|
157
|
+
--lark-app-secret-ref lark/app-secret \
|
|
158
|
+
--lark-allowed-user ou_0123456789abcdef0123456789abcdef
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
国际版加 `--lark-domain lark`(Open API 指向 `open.larksuite.com`)。
|
|
108
162
|
|
|
109
|
-
|
|
110
|
-
`ignored chat=oc_… from=ou_… (not in allowlist)`,把 `ou_…` 填进 `allowedUsers` 再重启。
|
|
163
|
+
**不知道自己的 open id**:先随便给机器人发一条消息,然后看日志里的
|
|
111
164
|
|
|
112
|
-
|
|
165
|
+
```
|
|
166
|
+
ignored chat=oc_… from=ou_… (not in allowlist)
|
|
167
|
+
```
|
|
113
168
|
|
|
114
|
-
|
|
169
|
+
把 `ou_…` 填进允许名单(重跑 `--init --lark-allowed-user ou_…`),重启 Host。
|
|
115
170
|
|
|
116
|
-
|
|
171
|
+
### `--init` 参数
|
|
117
172
|
|
|
118
|
-
|
|
173
|
+
| 参数 | 说明 |
|
|
174
|
+
|---|---|
|
|
175
|
+
| `--telegram-token-ref <service/account>` | 钥匙串引用,默认示例 `telegram/bot-token` |
|
|
176
|
+
| `--telegram-token-env <NAME>` | 改从环境变量读 token |
|
|
177
|
+
| `--telegram-allowed-user <id[,id]>` | 允许的 Telegram user id(可重复) |
|
|
178
|
+
| `--telegram-allowed-chat <id[,id]>` | 允许的 chat id |
|
|
179
|
+
| `--proxy <http://…>` | Telegram 走的 HTTP/HTTPS 代理 |
|
|
180
|
+
| `--parse-mode markdown\|plain` | Telegram 回复格式,`markdown` 失败会自动退回纯文本 |
|
|
181
|
+
| `--lark-app-id <cli_…>` | 飞书应用 App ID |
|
|
182
|
+
| `--lark-app-secret-ref <service/account>` | app secret 的钥匙串引用 |
|
|
183
|
+
| `--lark-app-secret-env <NAME>` | 改从环境变量读 app secret |
|
|
184
|
+
| `--lark-allowed-user <ou_…[,ou_…]>` | 允许的 open id |
|
|
185
|
+
| `--lark-allowed-chat <oc_…[,oc_…]>` | 允许的 chat id |
|
|
186
|
+
| `--lark-domain feishu\|lark` | 国内飞书(默认)或国际版 |
|
|
187
|
+
| `--default-cwd <path>` | 第一次说话时在哪个目录建会话 |
|
|
188
|
+
| `--no-input` | 不提问;缺参数直接报错 |
|
|
189
|
+
|
|
190
|
+
`--init` **按渠道合并**:只改它拿到的那条渠道,另一条和已有的允许名单/未知字段原样保留。所以补一个代理、加一个用户都不用重写整个文件。
|
|
191
|
+
|
|
192
|
+
### 文件结构
|
|
119
193
|
|
|
120
194
|
```json
|
|
121
195
|
{
|
|
@@ -141,47 +215,111 @@ security add-generic-password -U -s lark -a app-secret -w '…'
|
|
|
141
215
|
| 字段 | 说明 |
|
|
142
216
|
|---|---|
|
|
143
217
|
| `defaultCwd` | 第一次说话时在哪个目录建会话 |
|
|
144
|
-
| `streamIntervalMs` |
|
|
218
|
+
| `streamIntervalMs` | 两次流式更新的最小间隔,默认 800ms(飞书卡片有频率限制,别调太低) |
|
|
145
219
|
|
|
146
220
|
`telegram`:
|
|
147
221
|
|
|
148
222
|
| 字段 | 说明 |
|
|
149
223
|
|---|---|
|
|
150
224
|
| `tokenRef` / `tokenEnv` | 钥匙串引用或环境变量名 |
|
|
151
|
-
| `allowedUsers` | Telegram user id
|
|
225
|
+
| `allowedUsers` | Telegram user id;与 `allowedChats` 都为空时拒绝启动 |
|
|
152
226
|
| `allowedChats` | chat id(群/频道场景) |
|
|
153
|
-
| `parseMode` | `markdown
|
|
154
|
-
| `proxy` | HTTP/HTTPS
|
|
155
|
-
| `baseUrl` |
|
|
227
|
+
| `parseMode` | `markdown`(默认,失败自动回退)或 `plain` |
|
|
228
|
+
| `proxy` | HTTP/HTTPS CONNECT 代理;不读 `HTTPS_PROXY`,只认这一项 |
|
|
229
|
+
| `baseUrl` | 只给测试用 |
|
|
156
230
|
| `pollTimeoutSeconds` | 长轮询等待秒数,默认 25 |
|
|
157
231
|
|
|
158
232
|
`lark`:
|
|
159
233
|
|
|
160
234
|
| 字段 | 说明 |
|
|
161
235
|
|---|---|
|
|
162
|
-
| `appId` | `cli_` + 16
|
|
236
|
+
| `appId` | `cli_` + 16 位十六进制;格式不对在连网前就报错 |
|
|
163
237
|
| `appSecretRef` / `appSecretEnv` | 钥匙串引用或环境变量名 |
|
|
164
|
-
| `domain` | `feishu`(默认)或 `lark
|
|
165
|
-
| `allowedUsers` |
|
|
166
|
-
| `allowedChats` |
|
|
238
|
+
| `domain` | `feishu`(默认)或 `lark` |
|
|
239
|
+
| `allowedUsers` | open id(`ou_…`);与 `allowedChats` 都为空时拒绝启动 |
|
|
240
|
+
| `allowedChats` | chat id(`oc_…`) |
|
|
167
241
|
| `baseUrl` | 只给测试用 |
|
|
168
242
|
|
|
169
|
-
|
|
243
|
+
环境变量:`HULALA_BOT_CONFIG`(配置文件路径)、`HULALA_BOT_STATE`(状态目录)、`HULALA_BOT_LOCK_DIR`(锁目录)、`TELEGRAM_BOT_TOKEN`、`LARK_APP_SECRET`。
|
|
244
|
+
|
|
245
|
+
---
|
|
170
246
|
|
|
171
|
-
|
|
247
|
+
## 重启与验证
|
|
172
248
|
|
|
173
|
-
|
|
249
|
+
配置和插件都只在 Host 启动时读取,**改完必须重启**:
|
|
174
250
|
|
|
175
|
-
|
|
176
|
-
|
|
251
|
+
```sh
|
|
252
|
+
hulala host stop
|
|
253
|
+
hulala host start
|
|
254
|
+
# DSH:重启你自己的 dsh 进程
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### 验证清单
|
|
258
|
+
|
|
259
|
+
```sh
|
|
260
|
+
# 1. 插件在 profile 里
|
|
261
|
+
hulala plugin --profile tui list
|
|
262
|
+
# 期望:@deepseek-ai/dsh-base, @colinagent/hulala-bot
|
|
177
263
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
264
|
+
# 2. Host 起来了
|
|
265
|
+
hulala host status
|
|
266
|
+
# 期望:"state": "running"
|
|
181
267
|
|
|
182
|
-
|
|
268
|
+
# 3. 插件真的加载了
|
|
269
|
+
grep "bot starting inside the Host" ~/.hulala/logs/host.log | tail -1
|
|
270
|
+
# 期望:bot starting inside the Host; channels telegram
|
|
271
|
+
# (配了飞书就是 channels telegram, lark)
|
|
183
272
|
|
|
184
|
-
|
|
273
|
+
# 4. 连接锁只有一个持有者
|
|
274
|
+
ls ~/.hulala-bot/*.lock
|
|
275
|
+
# 期望:每条渠道一个 .lock;内容是本机 Host 的 pid
|
|
276
|
+
|
|
277
|
+
# 5. 给机器人发一句话
|
|
278
|
+
# 期望:先出现占位消息,然后被流式替换成回答;TUI 里能看到同一个会话
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
飞书额外确认:
|
|
282
|
+
|
|
283
|
+
```sh
|
|
284
|
+
# 日志里有没有收到事件(首次配置时会有 ignored … not in allowlist)
|
|
285
|
+
tail -f ~/.hulala/harness/config/hulala-bot/logs/bridge-$(date +%F).log
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
日志位置:`~/.hulala/harness/config/hulala-bot/logs/`(DSH 在 `~/.dsh/config/hulala-bot/logs/`)。
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
## 故障排查
|
|
293
|
+
|
|
294
|
+
先看这两个地方,90% 的问题能直接定位:
|
|
295
|
+
|
|
296
|
+
```sh
|
|
297
|
+
tail -50 ~/.hulala/harness/config/hulala-bot/logs/bridge-$(date +%F).log
|
|
298
|
+
grep -i "hulala-bot" ~/.hulala/logs/host.log | tail -20
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
| 症状 / 报错原文 | 原因 | 处理 |
|
|
302
|
+
|---|---|---|
|
|
303
|
+
| `no channel configured: add a "telegram" or "lark" section` | 配置文件不存在或两段都没有 | 跑 `--init`,或检查 `HULALA_BOT_CONFIG` 指向的路径 |
|
|
304
|
+
| `refusing to start Telegram with an empty allowlist` | 允许名单为空(拒绝是设计行为) | 加 `--telegram-allowed-user` |
|
|
305
|
+
| `no Telegram bot token: set tokenRef …` | 钥匙串里没有条目,或引用名写错 | `security find-generic-password -s telegram -a bot-token -w` 确认;名字要和 `tokenRef` 一致 |
|
|
306
|
+
| `lark.appId must look like cli_<16 hex>` | App ID 复制错/不是自建应用 | 回开发者后台复制 `cli_` + 16 位十六进制 |
|
|
307
|
+
| `no Lark app secret: set lark.appSecretRef …` | 同上,secret 没进钥匙串 | 存 `lark/app-secret`,或设 `LARK_APP_SECRET` |
|
|
308
|
+
| `this bot is already connected from another Host (pid …)` | 另一个 Host(DSH 或旧进程)正在用同一 token/app id | 停掉那个进程;确认它已退出后删掉报错里给出的 `.lock` |
|
|
309
|
+
| Telegram 报 `getUpdates: Conflict: … (409)` | 两个进程在轮询同一个 bot token | 同上;另外检查有没有旧的独立桥进程 |
|
|
310
|
+
| `proxy CONNECT failed: 403` | 代理拒绝 CONNECT | 检查代理是否允许该目标/是否要认证(`http://user:pass@host:port`) |
|
|
311
|
+
| `connect ECONNREFUSED 127.0.0.1:7897` | 代理端口没开 | 启动 Clash/Surge,或改 `proxy` |
|
|
312
|
+
| 飞书机器人完全不回话 | ① 订阅方式不是「长连接」② 没订阅 `im.message.receive_v1` ③ 没开机器人能力 ④ 可用范围不含你 ⑤ App Secret 错 | 按这五条逐项查;secret 错会在日志里出现 `99991663` 或 `tenant_access_token` 相关错误 |
|
|
313
|
+
| 飞书私聊正常、群里不回 | 群里必须 @ 机器人 | @ 它再发;命令前可以带 @ |
|
|
314
|
+
| 飞书审批按钮没反应 | 没添加 `card.action.trigger` 回调 | 后台补上这个回调,重启 Host |
|
|
315
|
+
| 流式很慢 / 频繁限流 | `streamIntervalMs` 低于平台限制 | 调到 800 以上(飞书卡片更新有频率限制) |
|
|
316
|
+
| 回复出现 `☑️ 已处理` | 这个审批已经在另一端处理过 | 正常,不是错误 |
|
|
317
|
+
| 日志里 `ignored … (not in allowlist)` | 发送者不在名单里 | 用日志里的 id 补进允许名单并重启 |
|
|
318
|
+
| 重启后行为没变 | 插件只在启动时读配置 | 确认进程真的重启了(`hulala host status` 的 pid 变了) |
|
|
319
|
+
|
|
320
|
+
调试用的日志行会带渠道名前缀:`telegram`、`lark`。凭据在写日志前会被替换成 `<secret>`,不会出现在文件里。
|
|
321
|
+
|
|
322
|
+
---
|
|
185
323
|
|
|
186
324
|
## 命令
|
|
187
325
|
|
|
@@ -189,7 +327,7 @@ security add-generic-password -U -s lark -a app-secret -w '…'
|
|
|
189
327
|
|
|
190
328
|
| 命令 | 行为 |
|
|
191
329
|
|---|---|
|
|
192
|
-
| `/cwd` |
|
|
330
|
+
| `/cwd` | 列出最近用过的目录,点一个就挂上去 |
|
|
193
331
|
| `/cwd <path>` | 换挂载:该目录下最近的会话,没有就新建 |
|
|
194
332
|
| `/new` | 在当前目录新建会话 |
|
|
195
333
|
| `/sessions` | 最近 10 个会话,`*` 表示当前 |
|
|
@@ -198,50 +336,52 @@ security add-generic-password -U -s lark -a app-secret -w '…'
|
|
|
198
336
|
| `/stop` | 停止当前回合 |
|
|
199
337
|
| `/help` | 上面这些 |
|
|
200
338
|
|
|
201
|
-
直接发文字就是提问。第一次说话会自动在 `defaultCwd`
|
|
339
|
+
直接发文字就是提问。第一次说话会自动在 `defaultCwd` 里建会话并挂上。
|
|
340
|
+
|
|
341
|
+
审批会以按钮出现(Telegram inline keyboard / 飞书卡片按钮),只有「允许这一次 / 拒绝」。请求里没有命令行文本,只有工具名与理由。
|
|
342
|
+
|
|
343
|
+
---
|
|
202
344
|
|
|
203
345
|
## 两个渠道的差异
|
|
204
346
|
|
|
205
347
|
| | Telegram | 飞书 / Lark |
|
|
206
348
|
|---|---|---|
|
|
207
349
|
| 传输 | Bot API 长轮询 | 长连接(WebSocket) |
|
|
208
|
-
| 流式 | 反复编辑同一条消息 | 反复 patch
|
|
209
|
-
| 按钮 | inline keyboard | 卡片 `action`
|
|
350
|
+
| 流式 | 反复编辑同一条消息 | 反复 patch 同一张卡片(飞书不能编辑文本消息) |
|
|
351
|
+
| 按钮 | inline keyboard | 卡片 `action` + `card.action.trigger` |
|
|
210
352
|
| 置顶状态栏 | 有(`📍 <目录>`) | 无;用 `/status` 看 |
|
|
211
353
|
| "正在输入" | 有 | 飞书没有这个 API |
|
|
212
354
|
| 单条上限 | 4096(内部按 4000 切) | 4000 字符一张卡片,超了续卡片 |
|
|
213
|
-
| 群聊 | 直接收 | 必须 @
|
|
214
|
-
|
|
215
|
-
## 行为细节
|
|
216
|
-
|
|
217
|
-
- **一条回合一条消息**:回合开始发占位,之后反复编辑;回合结束用会话日志里的正式文本替换。超长续消息,并在代码块边界处断开、补齐围栏。
|
|
218
|
-
- **工具调用**:还没有文本时显示 `· bash …`,让手机知道在干什么。
|
|
219
|
-
- **编辑退避**:连续 3 次编辑失败就关掉流式,回合结束一次性发送;限流按服务器给的等待时间退避。
|
|
220
|
-
- **入站去重**:Telegram 的 update 在 ack 丢失时会重投 24 小时,飞书长连接重连也可能重放,同一条消息不会喂给 agent 两次。
|
|
221
|
-
- **审批**:卡片只有「允许这一次 / 拒绝」。请求里没有命令行文本,只有工具名与理由;要显示具体命令需要按 `callId` 去会话日志取。跨端取消只在 Host 内的客户端之间生效,插件模式下手机上的卡片不会被远程收走(另一端点过之后会显示「已在其他端处理」)。
|
|
222
|
-
- **提问**:`ask_user_question` 会在手机上显示问题和选项,但只做通知——回答请回到 TUI/Desktop。
|
|
355
|
+
| 群聊 | 直接收 | 必须 @ 机器人 |
|
|
223
356
|
|
|
224
|
-
|
|
357
|
+
两个渠道可以同时开:各自挂各自的会话,审批同时出现在两边,先点的生效,另一边显示「已在其他端处理」。
|
|
225
358
|
|
|
226
|
-
|
|
227
|
-
- 消息正文、工具输出、贴出的路径都会经过 Telegram / 飞书的服务器。敏感仓库请用 `allowedUsers` 限制到私人会话,并考虑关掉流式预览。
|
|
228
|
-
- 这个插件不给 Host 增加任何权限:它能做的事,TUI 也能做。
|
|
229
|
-
- 权限策略仍由 Host 的 permission preset 决定,这里不改写、不绕过。
|
|
359
|
+
---
|
|
230
360
|
|
|
231
|
-
##
|
|
361
|
+
## 状态与安全
|
|
232
362
|
|
|
233
|
-
|
|
363
|
+
- **状态目录**:`$DSH_HOME/config/hulala-bot/`,里面有 `config.json`、`bindings.json`(挂载表)、`receipts/`(去重账本,24h TTL)、`logs/`,权限 `0600`/`0700`。
|
|
364
|
+
- **连接锁**:`~/.hulala-bot/<凭据摘要>.lock`,不在状态目录里,所以 DSH 和呼啦啦同时装插件时也只有一个在连。
|
|
365
|
+
- **凭据**:只进钥匙串(或环境变量),日志写之前替换成 `<secret>`。
|
|
366
|
+
- **边界**:白名单是唯一口令;消息正文、工具输出、贴出的路径都会经过 Telegram / 飞书的服务器。插件不给 Host 增加权限——它能做的事,TUI 也能做;权限仍由 Host 的 preset 决定。
|
|
367
|
+
- **跨端取消**:插件模式下,另一端已经处理过的审批不会从这边被远程收走,只会在下次交互时显示「已在其他端处理」。
|
|
234
368
|
|
|
235
|
-
|
|
369
|
+
---
|
|
236
370
|
|
|
237
371
|
## 开发
|
|
238
372
|
|
|
239
373
|
```bash
|
|
240
374
|
node --test "tests/*.test.ts" # 106 个用例
|
|
375
|
+
bun run typecheck
|
|
376
|
+
bun run build # dist/index.mjs + dist/plugin.mjs
|
|
241
377
|
node scripts/spike-host.ts <dsh-bin> <dsh-home> <profile> # 对真 Host 跑一轮
|
|
242
378
|
```
|
|
243
379
|
|
|
244
|
-
|
|
380
|
+
需要 Node ≥ 22.6。飞书的 REST 用注入的 `fetch` 测,长连接用假 transport 测,测试不需要租户、不需要网络。
|
|
381
|
+
|
|
382
|
+
版本约束:`engines.dsh` 声明 DSH 兼容范围(当前 `>=0.2.2-rc.1 <0.3.0`),装载器在 import 之前校验,不满足就跳过并写进 `skippedBundles`;`@deepseek-ai/dsh-*` 必须是 peerDependencies,避免出现第二份注册表。
|
|
383
|
+
|
|
384
|
+
更细的架构与坑:见仓库里的 `AGENTS.md`。
|
|
245
385
|
|
|
246
386
|
## 许可
|
|
247
387
|
|
package/dist/plugin.mjs
CHANGED
|
@@ -245,7 +245,7 @@ async function larkConfig(raw, env, file) {
|
|
|
245
245
|
if (!/^cli_[0-9a-fA-F]{16}$/.test(appId)) {
|
|
246
246
|
throw new Error(`lark.appId must look like cli_<16 hex>, not ${JSON.stringify(appId)} (from ${file})`);
|
|
247
247
|
}
|
|
248
|
-
const appSecret = await resolveSecret(raw, env, "appSecret");
|
|
248
|
+
const appSecret = await resolveSecret(raw, env, "appSecret") ?? env.LARK_APP_SECRET;
|
|
249
249
|
if (appSecret === undefined || appSecret.trim() === "") {
|
|
250
250
|
throw new Error(`no Lark app secret: set lark.appSecretRef in ${file}, or LARK_APP_SECRET in the environment`);
|
|
251
251
|
}
|