dfcode-remote 0.1.0__tar.gz
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.
- dfcode_remote-0.1.0/PKG-INFO +360 -0
- dfcode_remote-0.1.0/README.md +336 -0
- dfcode_remote-0.1.0/dfcode_remote/__init__.py +3 -0
- dfcode_remote-0.1.0/dfcode_remote/__main__.py +6 -0
- dfcode_remote-0.1.0/dfcode_remote/cli.py +106 -0
- dfcode_remote-0.1.0/dfcode_remote/dfcode_client/__init__.py +4 -0
- dfcode_remote-0.1.0/dfcode_remote/dfcode_client/client.py +196 -0
- dfcode_remote-0.1.0/dfcode_remote/dfcode_client/sse.py +105 -0
- dfcode_remote-0.1.0/dfcode_remote/dfcode_client/types.py +213 -0
- dfcode_remote-0.1.0/dfcode_remote/lark_client/__init__.py +19 -0
- dfcode_remote-0.1.0/dfcode_remote/lark_client/bot.py +73 -0
- dfcode_remote-0.1.0/dfcode_remote/lark_client/card_builder.py +467 -0
- dfcode_remote-0.1.0/dfcode_remote/lark_client/card_service.py +256 -0
- dfcode_remote-0.1.0/dfcode_remote/lark_client/event_listener.py +381 -0
- dfcode_remote-0.1.0/dfcode_remote/lark_client/handler.py +450 -0
- dfcode_remote-0.1.0/dfcode_remote/utils/__init__.py +4 -0
- dfcode_remote-0.1.0/dfcode_remote/utils/config.py +43 -0
- dfcode_remote-0.1.0/dfcode_remote/utils/markdown.py +53 -0
- dfcode_remote-0.1.0/dfcode_remote/utils/persistence.py +58 -0
- dfcode_remote-0.1.0/dfcode_remote.egg-info/PKG-INFO +360 -0
- dfcode_remote-0.1.0/dfcode_remote.egg-info/SOURCES.txt +34 -0
- dfcode_remote-0.1.0/dfcode_remote.egg-info/dependency_links.txt +1 -0
- dfcode_remote-0.1.0/dfcode_remote.egg-info/entry_points.txt +2 -0
- dfcode_remote-0.1.0/dfcode_remote.egg-info/requires.txt +3 -0
- dfcode_remote-0.1.0/dfcode_remote.egg-info/top_level.txt +1 -0
- dfcode_remote-0.1.0/pyproject.toml +41 -0
- dfcode_remote-0.1.0/setup.cfg +4 -0
- dfcode_remote-0.1.0/tests/test_card_builder.py +370 -0
- dfcode_remote-0.1.0/tests/test_card_service.py +46 -0
- dfcode_remote-0.1.0/tests/test_dfcode_client.py +132 -0
- dfcode_remote-0.1.0/tests/test_event_listener.py +156 -0
- dfcode_remote-0.1.0/tests/test_handler.py +209 -0
- dfcode_remote-0.1.0/tests/test_markdown.py +154 -0
- dfcode_remote-0.1.0/tests/test_persistence.py +103 -0
- dfcode_remote-0.1.0/tests/test_sse.py +120 -0
- dfcode_remote-0.1.0/tests/test_types.py +364 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dfcode-remote
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Bridge service connecting Feishu (Lark) to dfcode REST API
|
|
5
|
+
Author-email: DFcode Team <bw4199579+dfcode@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/DFcode-Team-VaaT/dfcode
|
|
8
|
+
Project-URL: Repository, https://github.com/DFcode-Team-VaaT/dfcode
|
|
9
|
+
Keywords: dfcode,feishu,lark,bridge,remote
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: lark-oapi>=1.3.0
|
|
22
|
+
Requires-Dist: httpx>=0.27.0
|
|
23
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
24
|
+
|
|
25
|
+
# DfCode Remote — Feishu Bridge
|
|
26
|
+
|
|
27
|
+
Python 服务,通过飞书 WebSocket 长连接接收用户消息,调用 dfcode 的 REST API 控制编码会话,通过 SSE 事件流获取实时更新,渲染为飞书卡片推送给用户。
|
|
28
|
+
|
|
29
|
+
## 概述
|
|
30
|
+
|
|
31
|
+
**核心优势**:dfcode 已有完整的 HTTP API + SSE 事件流 + 结构化消息数据,不需要像 remote_claude 那样用 PTY + pyte 解析终端输出。我们直接拿到 markdown、tool calls、permissions 等结构化数据。
|
|
32
|
+
|
|
33
|
+
| 维度 | remote_claude | dfcode remote |
|
|
34
|
+
| -------- | --------------------------- | --------------------------- |
|
|
35
|
+
| 数据源 | PTY + pyte 解析终端输出 | REST API + SSE 结构化数据 |
|
|
36
|
+
| 通信协议 | Unix Socket + mmap 共享内存 | HTTP + SSE |
|
|
37
|
+
| 输出解析 | ANSI 转义序列 → 组件 | MessageV2 Parts(已结构化) |
|
|
38
|
+
| 进程管理 | pty.fork() 启动 CLI | dfcode serve 独立运行 |
|
|
39
|
+
| 卡片内容 | 终端文本 + font color | Markdown + 工具调用摘要 |
|
|
40
|
+
|
|
41
|
+
## 架构图
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
飞书用户 ──→ 飞书服务器 ──→ WebSocket 长连接 ──→ packages/remote (Python)
|
|
45
|
+
│
|
|
46
|
+
┌────┴────┐
|
|
47
|
+
│ │
|
|
48
|
+
HTTP API SSE Stream
|
|
49
|
+
(控制) (监听)
|
|
50
|
+
│ │
|
|
51
|
+
└────┬────┘
|
|
52
|
+
│
|
|
53
|
+
dfcode serve :4096
|
|
54
|
+
(headless 模式)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### 数据流
|
|
58
|
+
|
|
59
|
+
1. 用户在飞书发消息 → WebSocket 事件 → `handler.py` 路由
|
|
60
|
+
2. 命令消息 → 直接处理(/attach, /list, /kill 等)
|
|
61
|
+
3. 普通消息 → `dfcode_client` 调用 `POST /session/:id/prompt_async`
|
|
62
|
+
4. SSE 事件流 `GET /event` → `event_listener.py` 实时监听
|
|
63
|
+
5. `message.part.updated` 事件 → 增量更新飞书卡片
|
|
64
|
+
6. `permission.asked` 事件 → 渲染交互按钮卡片
|
|
65
|
+
7. `question.asked` 事件 → 渲染选项按钮卡片
|
|
66
|
+
8. `session.idle` 事件 → 标记完成状态
|
|
67
|
+
|
|
68
|
+
## 快速开始
|
|
69
|
+
|
|
70
|
+
### 前置条件
|
|
71
|
+
|
|
72
|
+
- Python >= 3.10
|
|
73
|
+
- 已创建的飞书应用(自定义应用)
|
|
74
|
+
- 运行中的 dfcode serve(默认端口 4096)
|
|
75
|
+
|
|
76
|
+
### 1. 安装
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install dfcode-remote
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
从源码安装(开发模式):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cd packages/remote
|
|
86
|
+
pip install -e .
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 2. 飞书应用配置
|
|
90
|
+
|
|
91
|
+
1. 登录[飞书开放平台](https://open.feishu.cn/)
|
|
92
|
+
2. 创建自定义应用,获取 **App ID** 和 **App Secret**
|
|
93
|
+
3. 在「权限管理」中添加以下权限:
|
|
94
|
+
- `im:message` — 接收消息
|
|
95
|
+
- `im:message:send_as_bot` — 以机器人身份发送消息
|
|
96
|
+
- `im:chat` — 获取群组信息
|
|
97
|
+
- `cardkit:card` — 使用 CardKit 发送/更新卡片
|
|
98
|
+
4. 在「事件订阅」中启用以下事件(均使用**长连接**模式):
|
|
99
|
+
- `im.message.receive_v1` — 接收消息事件
|
|
100
|
+
- `card.action.trigger` — 卡片按钮点击回调(在「卡片回调」中单独配置)
|
|
101
|
+
5. 在「版本管理与发布」中发布应用
|
|
102
|
+
6. 将机器人添加到飞书群组
|
|
103
|
+
|
|
104
|
+
### 3. 配置环境变量
|
|
105
|
+
|
|
106
|
+
通过 `.env` 文件或直接设置环境变量:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
export FEISHU_APP_ID=cli_xxxxxxxxxxxx
|
|
110
|
+
export FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
111
|
+
export DFCODE_URL=http://localhost:4096
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
或复制模板:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
cp .env.example .env
|
|
118
|
+
# 编辑 .env 填入你的配置
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 4. 启动
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
# 先启动 dfcode 服务(另一个终端)
|
|
125
|
+
dfcode serve
|
|
126
|
+
# 或开发模式:bun dev serve
|
|
127
|
+
|
|
128
|
+
# 启动飞书 bridge
|
|
129
|
+
dfcode-remote
|
|
130
|
+
|
|
131
|
+
# 指定 dfcode 地址
|
|
132
|
+
dfcode-remote --dfcode-url http://host:4096
|
|
133
|
+
|
|
134
|
+
# 启用调试日志
|
|
135
|
+
dfcode-remote --debug
|
|
136
|
+
|
|
137
|
+
# 也可以用 python -m 方式运行
|
|
138
|
+
python -m dfcode_remote
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 环境变量
|
|
142
|
+
|
|
143
|
+
| 变量名 | 说明 | 默认值 | 必填 |
|
|
144
|
+
| ------------------- | ------------------------------ | ----------------------- | ---- |
|
|
145
|
+
| `FEISHU_APP_ID` | 飞书应用 App ID | - | 是 |
|
|
146
|
+
| `FEISHU_APP_SECRET` | 飞书应用 App Secret | - | 是 |
|
|
147
|
+
| `DFCODE_URL` | dfcode serve 地址 | `http://localhost:4096` | 否 |
|
|
148
|
+
| `DFCODE_DIRECTORY` | 默认工作目录 | `.` | 否 |
|
|
149
|
+
| `DFCODE_USERNAME` | dfcode 基础认证用户名 | - | 否 |
|
|
150
|
+
| `DFCODE_PASSWORD` | dfcode 基础认证密码 | - | 否 |
|
|
151
|
+
| `ALLOWED_USERS` | 允许的用户 ID 列表(逗号分隔) | - | 否 |
|
|
152
|
+
| `ENABLE_URGENT` | 任务完成时发送加急通知 | `true` | 否 |
|
|
153
|
+
|
|
154
|
+
## 支持的命令
|
|
155
|
+
|
|
156
|
+
| 命令 | 用法 | 说明 |
|
|
157
|
+
| --------- | -------------------- | ------------------------------------ | ------------------ |
|
|
158
|
+
| `/menu` | `/menu` | 显示命令帮助菜单 |
|
|
159
|
+
| `/list` | `/list` | 列出跨项目“可继续对话”的活跃会话 |
|
|
160
|
+
| `/new` | `/new [title]` | 创建新会话并绑定到当前群 |
|
|
161
|
+
| `/attach` | `/attach <序号 | session_id>` | 绑定到已有活跃会话 |
|
|
162
|
+
| `/detach` | `/detach` | 解绑当前会话 |
|
|
163
|
+
| `/status` | `/status` | 查看当前绑定会话的状态 |
|
|
164
|
+
| `/kill` | `/kill [session_id]` | 终止会话(不指定则终止当前绑定会话) |
|
|
165
|
+
|
|
166
|
+
### 使用示例
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
/new 帮我写个爬虫
|
|
170
|
+
→ ✅ 已创建并绑定会话
|
|
171
|
+
id: ses_xxx
|
|
172
|
+
dir: /path/to/project
|
|
173
|
+
|
|
174
|
+
写个 Python 脚本抓取 example.com 的标题
|
|
175
|
+
→ [卡片流式更新中...]
|
|
176
|
+
|
|
177
|
+
/list
|
|
178
|
+
→ 1. 项目A
|
|
179
|
+
id: ses_xxx
|
|
180
|
+
dir: /Users/foo/project-a
|
|
181
|
+
status: busy
|
|
182
|
+
|
|
183
|
+
/attach 1
|
|
184
|
+
→ ✅ 已绑定会话
|
|
185
|
+
id: ses_xxx
|
|
186
|
+
dir: /Users/foo/project-a
|
|
187
|
+
|
|
188
|
+
/status
|
|
189
|
+
→ id: ses_xxx
|
|
190
|
+
title: 项目A
|
|
191
|
+
dir: /Users/foo/project-a
|
|
192
|
+
status: busy
|
|
193
|
+
|
|
194
|
+
/detach
|
|
195
|
+
→ ✅ 已解绑当前会话
|
|
196
|
+
|
|
197
|
+
/attach ses_xxx
|
|
198
|
+
→ ✅ 已绑定会话 ses_xxx
|
|
199
|
+
|
|
200
|
+
/kill
|
|
201
|
+
→ ✅ 已终止会话 abc12345
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## 卡片交互
|
|
205
|
+
|
|
206
|
+
### 权限请求
|
|
207
|
+
|
|
208
|
+
当 dfcode 需要执行敏感操作(如写入文件)时,卡片会显示交互按钮:
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
🔐 write_file: /path/to/file
|
|
212
|
+
[允许] [始终允许] [拒绝]
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- **允许**:仅本次允许该操作
|
|
216
|
+
- **始终允许**:永久允许此类操作
|
|
217
|
+
- **拒绝**:拒绝该操作
|
|
218
|
+
|
|
219
|
+
点击后按钮会变为灰色(已处理状态),dfcode 继续执行。
|
|
220
|
+
|
|
221
|
+
### 问题选择
|
|
222
|
+
|
|
223
|
+
当 dfcode 需要用户选择时,卡片会显示选项按钮:
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
❓ 请选择要使用的框架:
|
|
227
|
+
[React] [Vue] [Angular]
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
点击选项后,选择会发送给 dfcode。
|
|
231
|
+
|
|
232
|
+
### 中止按钮
|
|
233
|
+
|
|
234
|
+
当会话处于 busy 状态时,卡片右下角显示中止按钮:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
⛔ 中止
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
点击后会调用 `POST /session/:id/abort` 终止当前任务。
|
|
241
|
+
|
|
242
|
+
## 事件流
|
|
243
|
+
|
|
244
|
+
`event_listener.py` 监听以下 SSE 事件并映射到卡片更新:
|
|
245
|
+
|
|
246
|
+
| SSE 事件类型 | 处理逻辑 | 卡片更新 |
|
|
247
|
+
| ---------------------------------------------------- | ------------------ | ----------------------------- |
|
|
248
|
+
| `server.connected` | 忽略 | - |
|
|
249
|
+
| `server.heartbeat` | 忽略 | - |
|
|
250
|
+
| `message.part.updated` (type=text) | 追加/更新文本内容 | 内容区 markdown 更新 |
|
|
251
|
+
| `message.part.updated` (type=tool, status=running) | 添加工具执行中面板 | 折叠面板(⏳ 图标) |
|
|
252
|
+
| `message.part.updated` (type=tool, status=completed) | 更新工具完成状态 | 折叠面板(🔧 图标)+ 输出摘要 |
|
|
253
|
+
| `message.part.updated` (type=step-finish) | 更新 token 用量 | 状态区显示 tokens/cost |
|
|
254
|
+
| `message.part.updated` (type=reasoning) | 添加推理内容 | 可折叠面板(💭 Reasoning) |
|
|
255
|
+
| `permission.asked` | 渲染权限按钮 | 交互区显示允许/拒绝按钮 |
|
|
256
|
+
| `question.asked` | 渲染问题选项 | 交互区显示选项按钮 |
|
|
257
|
+
| `session.status` (idle) | 标记完成 | 状态变为 ✅ 完成,发送通知 |
|
|
258
|
+
| `session.error` | 显示错误 | 发送错误文本消息 |
|
|
259
|
+
|
|
260
|
+
### 防抖机制
|
|
261
|
+
|
|
262
|
+
- 0.5 秒内多次事件合并为一次 `update_card` 调用
|
|
263
|
+
- 减少飞书 API 调用频率
|
|
264
|
+
|
|
265
|
+
## 卡片分裂
|
|
266
|
+
|
|
267
|
+
飞书卡片有内容块数量限制(`MAX_CARD_BLOCKS = 50`)。当内容块接近限制时:
|
|
268
|
+
|
|
269
|
+
1. 当前卡片被**冻结**(`build_frozen_card`)— 移除交互区,显示完成状态
|
|
270
|
+
2. 创建**新卡片**继续接收后续内容
|
|
271
|
+
3. 旧卡片保留历史内容,新卡片显示新内容
|
|
272
|
+
|
|
273
|
+
触发条件:
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
if state.content_block_count() >= MAX_CARD_BLOCKS - 5:
|
|
277
|
+
await self._freeze_and_split(state)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## 测试
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# 运行所有测试
|
|
284
|
+
python -m unittest discover -s tests -v
|
|
285
|
+
|
|
286
|
+
# 运行特定测试文件
|
|
287
|
+
python -m unittest tests/test_card_builder.py -v
|
|
288
|
+
python -m unittest tests/test_sse.py -v
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 测试覆盖
|
|
292
|
+
|
|
293
|
+
- `test_card_builder.py` — 卡片构建、流式卡片、冻结卡片、菜单卡片
|
|
294
|
+
- `test_markdown.py` — Markdown 转换、工具输出格式化、截断逻辑
|
|
295
|
+
- `test_persistence.py` — 绑定持久化、原子写入
|
|
296
|
+
- `test_types.py` — 数据类解析(SessionInfo, PartInfo, SSEEvent 等)
|
|
297
|
+
- `test_sse.py` — SSE 流解析、帧解析、超时处理
|
|
298
|
+
|
|
299
|
+
## 验证流程
|
|
300
|
+
|
|
301
|
+
### 冒烟测试
|
|
302
|
+
|
|
303
|
+
1. `dfcode serve` 启动 dfcode(或 `bun dev serve`)
|
|
304
|
+
2. `dfcode-remote` 启动飞书 bridge
|
|
305
|
+
3. 飞书群发 `/new` → 创建会话
|
|
306
|
+
4. 发送 "帮我在当前目录创建一个 hello.py 文件"
|
|
307
|
+
5. 观察卡片流式更新 → 权限弹窗 → 点击允许 → 完成通知
|
|
308
|
+
6. `/list` 确认会话状态
|
|
309
|
+
7. `/kill` 终止会话
|
|
310
|
+
|
|
311
|
+
## 项目结构
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
packages/remote/
|
|
315
|
+
├── pyproject.toml # pip 包配置
|
|
316
|
+
├── .env.example # 环境变量模板
|
|
317
|
+
├── README.md # 本文档
|
|
318
|
+
├── main.py # 兼容入口(委托给 dfcode_remote.cli)
|
|
319
|
+
├── dfcode_remote/
|
|
320
|
+
│ ├── __init__.py # 包版本
|
|
321
|
+
│ ├── __main__.py # python -m dfcode_remote 入口
|
|
322
|
+
│ ├── cli.py # CLI 入口:解析参数、启动 bot + SSE
|
|
323
|
+
│ ├── dfcode_client/
|
|
324
|
+
│ │ ├── client.py # httpx 异步客户端,封装 dfcode REST API
|
|
325
|
+
│ │ ├── sse.py # SSE 事件流消费者
|
|
326
|
+
│ │ └── types.py # 响应类型定义(dataclass)
|
|
327
|
+
│ ├── lark_client/
|
|
328
|
+
│ │ ├── bot.py # 飞书 WebSocket 长连接启动
|
|
329
|
+
│ │ ├── handler.py # 命令路由 + 消息处理
|
|
330
|
+
│ │ ├── card_builder.py # dfcode 消息 → 飞书卡片 JSON
|
|
331
|
+
│ │ ├── card_service.py # 飞书 CardKit API 封装
|
|
332
|
+
│ │ └── event_listener.py # SSE → 卡片更新桥接
|
|
333
|
+
│ └── utils/
|
|
334
|
+
│ ├── config.py # dotenv 配置加载
|
|
335
|
+
│ ├── markdown.py # Markdown → 飞书卡片内容转换
|
|
336
|
+
│ └── persistence.py # JSON 文件持久化(chat 绑定)
|
|
337
|
+
└── tests/ # 单元测试
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## 已知限制
|
|
341
|
+
|
|
342
|
+
1. **单会话绑定**:一个飞书群同一时间只能绑定一个 dfcode 会话(可通过 `/attach` 切换)
|
|
343
|
+
2. **无多群广播**:一个 dfcode 会话不能同时推送到多个飞书群
|
|
344
|
+
3. **无用户白名单 UI**:`ALLOWED_USERS` 需要手动配置用户 ID
|
|
345
|
+
4. **卡片块限制**:单卡片最多 50 个内容块,超出会触发卡片分裂
|
|
346
|
+
5. **图片不支持**:Markdown 中的图片会被替换为 `[alt text]` 占位符
|
|
347
|
+
6. **长文本截断**:单卡片内容超过 30000 字符会被截断
|
|
348
|
+
|
|
349
|
+
## 未来工作
|
|
350
|
+
|
|
351
|
+
- [ ] `/start <directory>` 命令 — 创建指定目录的会话
|
|
352
|
+
- [ ] `/new-group <name>` 命令 — 创建飞书群并绑定新会话
|
|
353
|
+
- [ ] 多会话同时绑定 — 一个群绑定多个会话,支持切换
|
|
354
|
+
- [ ] 会话完成时 @mention 群成员
|
|
355
|
+
- [ ] 更细粒度的权限控制(按工具类型)
|
|
356
|
+
- [ ] 图片上传支持(通过飞书图片 API)
|
|
357
|
+
|
|
358
|
+
## 许可证
|
|
359
|
+
|
|
360
|
+
MIT
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
# DfCode Remote — Feishu Bridge
|
|
2
|
+
|
|
3
|
+
Python 服务,通过飞书 WebSocket 长连接接收用户消息,调用 dfcode 的 REST API 控制编码会话,通过 SSE 事件流获取实时更新,渲染为飞书卡片推送给用户。
|
|
4
|
+
|
|
5
|
+
## 概述
|
|
6
|
+
|
|
7
|
+
**核心优势**:dfcode 已有完整的 HTTP API + SSE 事件流 + 结构化消息数据,不需要像 remote_claude 那样用 PTY + pyte 解析终端输出。我们直接拿到 markdown、tool calls、permissions 等结构化数据。
|
|
8
|
+
|
|
9
|
+
| 维度 | remote_claude | dfcode remote |
|
|
10
|
+
| -------- | --------------------------- | --------------------------- |
|
|
11
|
+
| 数据源 | PTY + pyte 解析终端输出 | REST API + SSE 结构化数据 |
|
|
12
|
+
| 通信协议 | Unix Socket + mmap 共享内存 | HTTP + SSE |
|
|
13
|
+
| 输出解析 | ANSI 转义序列 → 组件 | MessageV2 Parts(已结构化) |
|
|
14
|
+
| 进程管理 | pty.fork() 启动 CLI | dfcode serve 独立运行 |
|
|
15
|
+
| 卡片内容 | 终端文本 + font color | Markdown + 工具调用摘要 |
|
|
16
|
+
|
|
17
|
+
## 架构图
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
飞书用户 ──→ 飞书服务器 ──→ WebSocket 长连接 ──→ packages/remote (Python)
|
|
21
|
+
│
|
|
22
|
+
┌────┴────┐
|
|
23
|
+
│ │
|
|
24
|
+
HTTP API SSE Stream
|
|
25
|
+
(控制) (监听)
|
|
26
|
+
│ │
|
|
27
|
+
└────┬────┘
|
|
28
|
+
│
|
|
29
|
+
dfcode serve :4096
|
|
30
|
+
(headless 模式)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 数据流
|
|
34
|
+
|
|
35
|
+
1. 用户在飞书发消息 → WebSocket 事件 → `handler.py` 路由
|
|
36
|
+
2. 命令消息 → 直接处理(/attach, /list, /kill 等)
|
|
37
|
+
3. 普通消息 → `dfcode_client` 调用 `POST /session/:id/prompt_async`
|
|
38
|
+
4. SSE 事件流 `GET /event` → `event_listener.py` 实时监听
|
|
39
|
+
5. `message.part.updated` 事件 → 增量更新飞书卡片
|
|
40
|
+
6. `permission.asked` 事件 → 渲染交互按钮卡片
|
|
41
|
+
7. `question.asked` 事件 → 渲染选项按钮卡片
|
|
42
|
+
8. `session.idle` 事件 → 标记完成状态
|
|
43
|
+
|
|
44
|
+
## 快速开始
|
|
45
|
+
|
|
46
|
+
### 前置条件
|
|
47
|
+
|
|
48
|
+
- Python >= 3.10
|
|
49
|
+
- 已创建的飞书应用(自定义应用)
|
|
50
|
+
- 运行中的 dfcode serve(默认端口 4096)
|
|
51
|
+
|
|
52
|
+
### 1. 安装
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install dfcode-remote
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
从源码安装(开发模式):
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
cd packages/remote
|
|
62
|
+
pip install -e .
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. 飞书应用配置
|
|
66
|
+
|
|
67
|
+
1. 登录[飞书开放平台](https://open.feishu.cn/)
|
|
68
|
+
2. 创建自定义应用,获取 **App ID** 和 **App Secret**
|
|
69
|
+
3. 在「权限管理」中添加以下权限:
|
|
70
|
+
- `im:message` — 接收消息
|
|
71
|
+
- `im:message:send_as_bot` — 以机器人身份发送消息
|
|
72
|
+
- `im:chat` — 获取群组信息
|
|
73
|
+
- `cardkit:card` — 使用 CardKit 发送/更新卡片
|
|
74
|
+
4. 在「事件订阅」中启用以下事件(均使用**长连接**模式):
|
|
75
|
+
- `im.message.receive_v1` — 接收消息事件
|
|
76
|
+
- `card.action.trigger` — 卡片按钮点击回调(在「卡片回调」中单独配置)
|
|
77
|
+
5. 在「版本管理与发布」中发布应用
|
|
78
|
+
6. 将机器人添加到飞书群组
|
|
79
|
+
|
|
80
|
+
### 3. 配置环境变量
|
|
81
|
+
|
|
82
|
+
通过 `.env` 文件或直接设置环境变量:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
export FEISHU_APP_ID=cli_xxxxxxxxxxxx
|
|
86
|
+
export FEISHU_APP_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
87
|
+
export DFCODE_URL=http://localhost:4096
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
或复制模板:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cp .env.example .env
|
|
94
|
+
# 编辑 .env 填入你的配置
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 4. 启动
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# 先启动 dfcode 服务(另一个终端)
|
|
101
|
+
dfcode serve
|
|
102
|
+
# 或开发模式:bun dev serve
|
|
103
|
+
|
|
104
|
+
# 启动飞书 bridge
|
|
105
|
+
dfcode-remote
|
|
106
|
+
|
|
107
|
+
# 指定 dfcode 地址
|
|
108
|
+
dfcode-remote --dfcode-url http://host:4096
|
|
109
|
+
|
|
110
|
+
# 启用调试日志
|
|
111
|
+
dfcode-remote --debug
|
|
112
|
+
|
|
113
|
+
# 也可以用 python -m 方式运行
|
|
114
|
+
python -m dfcode_remote
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 环境变量
|
|
118
|
+
|
|
119
|
+
| 变量名 | 说明 | 默认值 | 必填 |
|
|
120
|
+
| ------------------- | ------------------------------ | ----------------------- | ---- |
|
|
121
|
+
| `FEISHU_APP_ID` | 飞书应用 App ID | - | 是 |
|
|
122
|
+
| `FEISHU_APP_SECRET` | 飞书应用 App Secret | - | 是 |
|
|
123
|
+
| `DFCODE_URL` | dfcode serve 地址 | `http://localhost:4096` | 否 |
|
|
124
|
+
| `DFCODE_DIRECTORY` | 默认工作目录 | `.` | 否 |
|
|
125
|
+
| `DFCODE_USERNAME` | dfcode 基础认证用户名 | - | 否 |
|
|
126
|
+
| `DFCODE_PASSWORD` | dfcode 基础认证密码 | - | 否 |
|
|
127
|
+
| `ALLOWED_USERS` | 允许的用户 ID 列表(逗号分隔) | - | 否 |
|
|
128
|
+
| `ENABLE_URGENT` | 任务完成时发送加急通知 | `true` | 否 |
|
|
129
|
+
|
|
130
|
+
## 支持的命令
|
|
131
|
+
|
|
132
|
+
| 命令 | 用法 | 说明 |
|
|
133
|
+
| --------- | -------------------- | ------------------------------------ | ------------------ |
|
|
134
|
+
| `/menu` | `/menu` | 显示命令帮助菜单 |
|
|
135
|
+
| `/list` | `/list` | 列出跨项目“可继续对话”的活跃会话 |
|
|
136
|
+
| `/new` | `/new [title]` | 创建新会话并绑定到当前群 |
|
|
137
|
+
| `/attach` | `/attach <序号 | session_id>` | 绑定到已有活跃会话 |
|
|
138
|
+
| `/detach` | `/detach` | 解绑当前会话 |
|
|
139
|
+
| `/status` | `/status` | 查看当前绑定会话的状态 |
|
|
140
|
+
| `/kill` | `/kill [session_id]` | 终止会话(不指定则终止当前绑定会话) |
|
|
141
|
+
|
|
142
|
+
### 使用示例
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
/new 帮我写个爬虫
|
|
146
|
+
→ ✅ 已创建并绑定会话
|
|
147
|
+
id: ses_xxx
|
|
148
|
+
dir: /path/to/project
|
|
149
|
+
|
|
150
|
+
写个 Python 脚本抓取 example.com 的标题
|
|
151
|
+
→ [卡片流式更新中...]
|
|
152
|
+
|
|
153
|
+
/list
|
|
154
|
+
→ 1. 项目A
|
|
155
|
+
id: ses_xxx
|
|
156
|
+
dir: /Users/foo/project-a
|
|
157
|
+
status: busy
|
|
158
|
+
|
|
159
|
+
/attach 1
|
|
160
|
+
→ ✅ 已绑定会话
|
|
161
|
+
id: ses_xxx
|
|
162
|
+
dir: /Users/foo/project-a
|
|
163
|
+
|
|
164
|
+
/status
|
|
165
|
+
→ id: ses_xxx
|
|
166
|
+
title: 项目A
|
|
167
|
+
dir: /Users/foo/project-a
|
|
168
|
+
status: busy
|
|
169
|
+
|
|
170
|
+
/detach
|
|
171
|
+
→ ✅ 已解绑当前会话
|
|
172
|
+
|
|
173
|
+
/attach ses_xxx
|
|
174
|
+
→ ✅ 已绑定会话 ses_xxx
|
|
175
|
+
|
|
176
|
+
/kill
|
|
177
|
+
→ ✅ 已终止会话 abc12345
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 卡片交互
|
|
181
|
+
|
|
182
|
+
### 权限请求
|
|
183
|
+
|
|
184
|
+
当 dfcode 需要执行敏感操作(如写入文件)时,卡片会显示交互按钮:
|
|
185
|
+
|
|
186
|
+
```
|
|
187
|
+
🔐 write_file: /path/to/file
|
|
188
|
+
[允许] [始终允许] [拒绝]
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
- **允许**:仅本次允许该操作
|
|
192
|
+
- **始终允许**:永久允许此类操作
|
|
193
|
+
- **拒绝**:拒绝该操作
|
|
194
|
+
|
|
195
|
+
点击后按钮会变为灰色(已处理状态),dfcode 继续执行。
|
|
196
|
+
|
|
197
|
+
### 问题选择
|
|
198
|
+
|
|
199
|
+
当 dfcode 需要用户选择时,卡片会显示选项按钮:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
❓ 请选择要使用的框架:
|
|
203
|
+
[React] [Vue] [Angular]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
点击选项后,选择会发送给 dfcode。
|
|
207
|
+
|
|
208
|
+
### 中止按钮
|
|
209
|
+
|
|
210
|
+
当会话处于 busy 状态时,卡片右下角显示中止按钮:
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
⛔ 中止
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
点击后会调用 `POST /session/:id/abort` 终止当前任务。
|
|
217
|
+
|
|
218
|
+
## 事件流
|
|
219
|
+
|
|
220
|
+
`event_listener.py` 监听以下 SSE 事件并映射到卡片更新:
|
|
221
|
+
|
|
222
|
+
| SSE 事件类型 | 处理逻辑 | 卡片更新 |
|
|
223
|
+
| ---------------------------------------------------- | ------------------ | ----------------------------- |
|
|
224
|
+
| `server.connected` | 忽略 | - |
|
|
225
|
+
| `server.heartbeat` | 忽略 | - |
|
|
226
|
+
| `message.part.updated` (type=text) | 追加/更新文本内容 | 内容区 markdown 更新 |
|
|
227
|
+
| `message.part.updated` (type=tool, status=running) | 添加工具执行中面板 | 折叠面板(⏳ 图标) |
|
|
228
|
+
| `message.part.updated` (type=tool, status=completed) | 更新工具完成状态 | 折叠面板(🔧 图标)+ 输出摘要 |
|
|
229
|
+
| `message.part.updated` (type=step-finish) | 更新 token 用量 | 状态区显示 tokens/cost |
|
|
230
|
+
| `message.part.updated` (type=reasoning) | 添加推理内容 | 可折叠面板(💭 Reasoning) |
|
|
231
|
+
| `permission.asked` | 渲染权限按钮 | 交互区显示允许/拒绝按钮 |
|
|
232
|
+
| `question.asked` | 渲染问题选项 | 交互区显示选项按钮 |
|
|
233
|
+
| `session.status` (idle) | 标记完成 | 状态变为 ✅ 完成,发送通知 |
|
|
234
|
+
| `session.error` | 显示错误 | 发送错误文本消息 |
|
|
235
|
+
|
|
236
|
+
### 防抖机制
|
|
237
|
+
|
|
238
|
+
- 0.5 秒内多次事件合并为一次 `update_card` 调用
|
|
239
|
+
- 减少飞书 API 调用频率
|
|
240
|
+
|
|
241
|
+
## 卡片分裂
|
|
242
|
+
|
|
243
|
+
飞书卡片有内容块数量限制(`MAX_CARD_BLOCKS = 50`)。当内容块接近限制时:
|
|
244
|
+
|
|
245
|
+
1. 当前卡片被**冻结**(`build_frozen_card`)— 移除交互区,显示完成状态
|
|
246
|
+
2. 创建**新卡片**继续接收后续内容
|
|
247
|
+
3. 旧卡片保留历史内容,新卡片显示新内容
|
|
248
|
+
|
|
249
|
+
触发条件:
|
|
250
|
+
|
|
251
|
+
```python
|
|
252
|
+
if state.content_block_count() >= MAX_CARD_BLOCKS - 5:
|
|
253
|
+
await self._freeze_and_split(state)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## 测试
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# 运行所有测试
|
|
260
|
+
python -m unittest discover -s tests -v
|
|
261
|
+
|
|
262
|
+
# 运行特定测试文件
|
|
263
|
+
python -m unittest tests/test_card_builder.py -v
|
|
264
|
+
python -m unittest tests/test_sse.py -v
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 测试覆盖
|
|
268
|
+
|
|
269
|
+
- `test_card_builder.py` — 卡片构建、流式卡片、冻结卡片、菜单卡片
|
|
270
|
+
- `test_markdown.py` — Markdown 转换、工具输出格式化、截断逻辑
|
|
271
|
+
- `test_persistence.py` — 绑定持久化、原子写入
|
|
272
|
+
- `test_types.py` — 数据类解析(SessionInfo, PartInfo, SSEEvent 等)
|
|
273
|
+
- `test_sse.py` — SSE 流解析、帧解析、超时处理
|
|
274
|
+
|
|
275
|
+
## 验证流程
|
|
276
|
+
|
|
277
|
+
### 冒烟测试
|
|
278
|
+
|
|
279
|
+
1. `dfcode serve` 启动 dfcode(或 `bun dev serve`)
|
|
280
|
+
2. `dfcode-remote` 启动飞书 bridge
|
|
281
|
+
3. 飞书群发 `/new` → 创建会话
|
|
282
|
+
4. 发送 "帮我在当前目录创建一个 hello.py 文件"
|
|
283
|
+
5. 观察卡片流式更新 → 权限弹窗 → 点击允许 → 完成通知
|
|
284
|
+
6. `/list` 确认会话状态
|
|
285
|
+
7. `/kill` 终止会话
|
|
286
|
+
|
|
287
|
+
## 项目结构
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
packages/remote/
|
|
291
|
+
├── pyproject.toml # pip 包配置
|
|
292
|
+
├── .env.example # 环境变量模板
|
|
293
|
+
├── README.md # 本文档
|
|
294
|
+
├── main.py # 兼容入口(委托给 dfcode_remote.cli)
|
|
295
|
+
├── dfcode_remote/
|
|
296
|
+
│ ├── __init__.py # 包版本
|
|
297
|
+
│ ├── __main__.py # python -m dfcode_remote 入口
|
|
298
|
+
│ ├── cli.py # CLI 入口:解析参数、启动 bot + SSE
|
|
299
|
+
│ ├── dfcode_client/
|
|
300
|
+
│ │ ├── client.py # httpx 异步客户端,封装 dfcode REST API
|
|
301
|
+
│ │ ├── sse.py # SSE 事件流消费者
|
|
302
|
+
│ │ └── types.py # 响应类型定义(dataclass)
|
|
303
|
+
│ ├── lark_client/
|
|
304
|
+
│ │ ├── bot.py # 飞书 WebSocket 长连接启动
|
|
305
|
+
│ │ ├── handler.py # 命令路由 + 消息处理
|
|
306
|
+
│ │ ├── card_builder.py # dfcode 消息 → 飞书卡片 JSON
|
|
307
|
+
│ │ ├── card_service.py # 飞书 CardKit API 封装
|
|
308
|
+
│ │ └── event_listener.py # SSE → 卡片更新桥接
|
|
309
|
+
│ └── utils/
|
|
310
|
+
│ ├── config.py # dotenv 配置加载
|
|
311
|
+
│ ├── markdown.py # Markdown → 飞书卡片内容转换
|
|
312
|
+
│ └── persistence.py # JSON 文件持久化(chat 绑定)
|
|
313
|
+
└── tests/ # 单元测试
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
## 已知限制
|
|
317
|
+
|
|
318
|
+
1. **单会话绑定**:一个飞书群同一时间只能绑定一个 dfcode 会话(可通过 `/attach` 切换)
|
|
319
|
+
2. **无多群广播**:一个 dfcode 会话不能同时推送到多个飞书群
|
|
320
|
+
3. **无用户白名单 UI**:`ALLOWED_USERS` 需要手动配置用户 ID
|
|
321
|
+
4. **卡片块限制**:单卡片最多 50 个内容块,超出会触发卡片分裂
|
|
322
|
+
5. **图片不支持**:Markdown 中的图片会被替换为 `[alt text]` 占位符
|
|
323
|
+
6. **长文本截断**:单卡片内容超过 30000 字符会被截断
|
|
324
|
+
|
|
325
|
+
## 未来工作
|
|
326
|
+
|
|
327
|
+
- [ ] `/start <directory>` 命令 — 创建指定目录的会话
|
|
328
|
+
- [ ] `/new-group <name>` 命令 — 创建飞书群并绑定新会话
|
|
329
|
+
- [ ] 多会话同时绑定 — 一个群绑定多个会话,支持切换
|
|
330
|
+
- [ ] 会话完成时 @mention 群成员
|
|
331
|
+
- [ ] 更细粒度的权限控制(按工具类型)
|
|
332
|
+
- [ ] 图片上传支持(通过飞书图片 API)
|
|
333
|
+
|
|
334
|
+
## 许可证
|
|
335
|
+
|
|
336
|
+
MIT
|