acp-bridge 0.0.1__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.
- acp_bridge-0.0.1/.git +1 -0
- acp_bridge-0.0.1/.github/workflows/ci.yml +26 -0
- acp_bridge-0.0.1/.github/workflows/release.yml +33 -0
- acp_bridge-0.0.1/.gitignore +8 -0
- acp_bridge-0.0.1/.kiro/specs/session-refactor/.config.kiro +1 -0
- acp_bridge-0.0.1/.kiro/specs/session-refactor/design.md +442 -0
- acp_bridge-0.0.1/.kiro/specs/session-refactor/requirements.md +125 -0
- acp_bridge-0.0.1/.kiro/specs/session-refactor/tasks.md +153 -0
- acp_bridge-0.0.1/.python-version +1 -0
- acp_bridge-0.0.1/CHANGELOG.md +36 -0
- acp_bridge-0.0.1/LICENSE +21 -0
- acp_bridge-0.0.1/PKG-INFO +116 -0
- acp_bridge-0.0.1/README.md +101 -0
- acp_bridge-0.0.1/TESTING.md +65 -0
- acp_bridge-0.0.1/agent_bridge/__init__.py +1 -0
- acp_bridge-0.0.1/agent_bridge/agent.py +283 -0
- acp_bridge-0.0.1/agent_bridge/bridge.py +409 -0
- acp_bridge-0.0.1/agent_bridge/config.py +92 -0
- acp_bridge-0.0.1/agent_bridge/feishu.py +453 -0
- acp_bridge-0.0.1/agent_bridge/handler.py +86 -0
- acp_bridge-0.0.1/agent_bridge/handler_command.py +293 -0
- acp_bridge-0.0.1/agent_bridge/handler_message.py +282 -0
- acp_bridge-0.0.1/agent_bridge/handler_permission.py +37 -0
- acp_bridge-0.0.1/agent_bridge/main.py +47 -0
- acp_bridge-0.0.1/agent_bridge/session.py +142 -0
- acp_bridge-0.0.1/agent_bridge/utils.py +16 -0
- acp_bridge-0.0.1/pyproject.toml +64 -0
- acp_bridge-0.0.1/scripts/test_feishu_integration.py +294 -0
- acp_bridge-0.0.1/tests/__init__.py +1 -0
- acp_bridge-0.0.1/tests/test_agent.py +44 -0
- acp_bridge-0.0.1/tests/test_bridge.py +266 -0
- acp_bridge-0.0.1/tests/test_config.py +220 -0
- acp_bridge-0.0.1/tests/test_feishu.py +355 -0
- acp_bridge-0.0.1/tests/test_handler.py +579 -0
- acp_bridge-0.0.1/tests/test_handler_command.py +380 -0
- acp_bridge-0.0.1/tests/test_handler_message.py +455 -0
- acp_bridge-0.0.1/tests/test_handler_permission.py +73 -0
- acp_bridge-0.0.1/tests/test_session.py +594 -0
- acp_bridge-0.0.1/tests/test_utils.py +38 -0
- acp_bridge-0.0.1/tests_e2e/__init__.py +1 -0
- acp_bridge-0.0.1/tests_e2e/test_e2e.py +658 -0
- acp_bridge-0.0.1/uv.lock +942 -0
acp_bridge-0.0.1/.git
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
gitdir: ../../.git/modules/bridge/agent-bridge
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
workflow_call:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
lint:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v6
|
|
15
|
+
- uses: astral-sh/setup-uv@v8.0.0
|
|
16
|
+
- run: uv sync --frozen
|
|
17
|
+
- run: uv run ruff check agent_bridge/ tests/
|
|
18
|
+
- run: uv run pyright agent_bridge/
|
|
19
|
+
|
|
20
|
+
test:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v6
|
|
24
|
+
- uses: astral-sh/setup-uv@v8.0.0
|
|
25
|
+
- run: uv sync --frozen
|
|
26
|
+
- run: uv run pytest tests/ -q
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
|
|
6
|
+
permissions:
|
|
7
|
+
contents: write
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
ci:
|
|
11
|
+
uses: ./.github/workflows/ci.yml
|
|
12
|
+
|
|
13
|
+
release:
|
|
14
|
+
needs: ci
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v6
|
|
18
|
+
with:
|
|
19
|
+
fetch-depth: 0
|
|
20
|
+
- uses: python-semantic-release/python-semantic-release@v9
|
|
21
|
+
id: release
|
|
22
|
+
with:
|
|
23
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
- name: Publish to PyPI
|
|
25
|
+
if: steps.release.outputs.released == 'true'
|
|
26
|
+
uses: astral-sh/setup-uv@v8.0.0
|
|
27
|
+
- name: Build and upload
|
|
28
|
+
if: steps.release.outputs.released == 'true'
|
|
29
|
+
run: |
|
|
30
|
+
uv build
|
|
31
|
+
uv publish
|
|
32
|
+
env:
|
|
33
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"specId": "af6c678b-0196-439f-9739-25dc716105b0", "workflowType": "requirements-first", "specType": "feature"}
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
# 技术设计文档:会话管理重构
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
本设计文档描述 AgentBridge 会话管理重构的技术方案。核心变更包括:
|
|
6
|
+
|
|
7
|
+
1. **自动会话创建**:移除 `#new` / `#agents` 指令,改为 @机器人 自动创建会话
|
|
8
|
+
2. **基于消息引用链的会话路由**:通过 `parent_id` 沿引用链向上查找 Root_Message,以 Root_Message 的 `message_id` 作为 Session 索引 key
|
|
9
|
+
3. **LRU + TTL 淘汰机制**:限制最大并发会话数,自动清理长时间不活跃的会话
|
|
10
|
+
4. **消息缓存与合并发送**:Session busy 时静默缓存消息,处理完成后合并发送
|
|
11
|
+
5. **单 agent 配置简化**:`[[agents]]` 数组表改为 `[agent]` 单表
|
|
12
|
+
6. **#sessions 指令增强**:显示摘要、状态、最近使用时间
|
|
13
|
+
7. **清理 Juan 遗留命名**、**完善 README**
|
|
14
|
+
|
|
15
|
+
### 设计原则
|
|
16
|
+
|
|
17
|
+
- 最小化变更范围,保持现有模块划分(handler、session、config、feishu、bridge)
|
|
18
|
+
- Session 索引 key 从原来的 `thread_ts`(即 `#new` 消息的 message_id)变为 Root_Message 的 `message_id`
|
|
19
|
+
- 飞书 API 新增 reaction 和消息历史查询能力
|
|
20
|
+
|
|
21
|
+
## 架构
|
|
22
|
+
|
|
23
|
+
### 架构
|
|
24
|
+
|
|
25
|
+
模块层级关系重构前后不变:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
FeishuEvent ← feishu.py
|
|
29
|
+
↓
|
|
30
|
+
handler.py (路由)
|
|
31
|
+
↓
|
|
32
|
+
handler_command.py / handler_message.py
|
|
33
|
+
↓
|
|
34
|
+
session.py (SessionManager)
|
|
35
|
+
agent.py (AgentManager → ACP Agent 进程)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
handler_xxx 子模块根据业务需要同时调用 session.py 和 agent.py,handler.py 只负责路由分发。
|
|
39
|
+
|
|
40
|
+
重构涉及的模块内部变更:
|
|
41
|
+
- `feishu.py`:FeishuEvent 新增 `root_id`、`is_mention_bot`、`sender_name` 字段;新增 `add_reaction()` 方法
|
|
42
|
+
- `handler.py`:路由逻辑改为基于 `root_message_id` + 是否有 Session + 是否 @机器人 + 消息类型
|
|
43
|
+
- `handler_message.py`:承担原 `#new` 的自动创建 Session 职责;busy 时静默缓存消息
|
|
44
|
+
- `handler_command.py`:移除 `#new`/`#agents`;增强 `#sessions`;指令上下文判断适配新路由
|
|
45
|
+
- `session.py`:SessionState 新增字段;SessionManager 新增 LRU/TTL 淘汰和消息缓存
|
|
46
|
+
- `bridge.py`:启动 TTL 定时检查任务;适配单 agent 配置
|
|
47
|
+
|
|
48
|
+
### 消息路由流程
|
|
49
|
+
|
|
50
|
+
```mermaid
|
|
51
|
+
flowchart TD
|
|
52
|
+
A[收到 FeishuEvent] --> B[获取 root_message_id = event.root_id or event.message_id]
|
|
53
|
+
B --> C{root_message_id 有关联 Session?}
|
|
54
|
+
C -- 是 --> D{消息类型?}
|
|
55
|
+
C -- 否 --> E{私聊 or 用户 @了机器人?}
|
|
56
|
+
E -- 是 --> F{消息是指令?}
|
|
57
|
+
E -- 否(群聊且未@) --> G[忽略消息]
|
|
58
|
+
F -- #指令,依赖Session --> H[回复 No active conversation]
|
|
59
|
+
F -- #指令,不依赖Session --> I[正常执行指令]
|
|
60
|
+
F -- 普通文本 --> K[自动创建 Session, 路由消息]
|
|
61
|
+
D -- #指令 --> L[触发指令处理, 作用于该 Session]
|
|
62
|
+
D -- 普通文本 --> N{Session busy?}
|
|
63
|
+
N -- 否 --> O[转发消息到 Agent]
|
|
64
|
+
N -- 是 --> P[静默缓存消息]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Root_Message 解析流程
|
|
68
|
+
|
|
69
|
+
需求文档中描述的逻辑是"沿 parent_id 向上查找消息引用链,定位到 Root_Message",这是业务语义上的正确描述。实现上,飞书消息事件原生包含 `root_id` 字段(即引用链根消息的 ID),因此无需递归 API 调用,直接从事件数据提取即可。
|
|
70
|
+
|
|
71
|
+
- 若消息有 `root_id`:Root_Message 即为 `root_id` 对应的消息
|
|
72
|
+
- 若消息无 `root_id`(独立消息):Root_Message 即为自身(`message_id`)
|
|
73
|
+
- 防御性处理:若异常情况下 `root_id` 为空但 `parent_id` 不为空,使用 `parent_id` 作为 fallback
|
|
74
|
+
|
|
75
|
+
不同 IM 的处理方式不同(如 Slack 有 `thread_ts`),但都在 IM 层完成解析,handler 层统一使用 `root_message_id`。
|
|
76
|
+
|
|
77
|
+
## 组件与接口
|
|
78
|
+
|
|
79
|
+
### 变更文件清单
|
|
80
|
+
|
|
81
|
+
| 文件 | 变更类型 | 说明 |
|
|
82
|
+
|------|---------|------|
|
|
83
|
+
| `src/session.py` | 重构 | SessionState 新增字段;SessionManager 增加 LRU/TTL 淘汰、消息缓存 |
|
|
84
|
+
| `src/config.py` | 修改 | `[[agents]]` → `[agent]`;新增 `max_sessions`、`session_ttl_minutes` |
|
|
85
|
+
| `src/handler.py` | 重构 | 增加 Root_Message 解析和新路由逻辑 |
|
|
86
|
+
| `src/handler_command.py` | 修改 | 移除 `#new`/`#agents`;增强 `#sessions`;调整指令上下文判断 |
|
|
87
|
+
| `src/handler_message.py` | 修改 | 支持自动创建 Session;busy 时缓存消息而非提示 |
|
|
88
|
+
| `src/feishu.py` | 扩展 | FeishuEvent 新增 `root_id`、`is_mention_bot`、`sender_name` 字段;新增 `add_reaction()` 方法 |
|
|
89
|
+
| `src/bridge.py` | 修改 | 启动 TTL 定时检查任务;适配单 agent 配置日志 |
|
|
90
|
+
| `src/main.py` | 无变更 | - |
|
|
91
|
+
| `src/utils.py` | 无变更 | - |
|
|
92
|
+
|
|
93
|
+
### SessionManager 新接口
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
class SessionManager:
|
|
97
|
+
# 新增方法
|
|
98
|
+
async def create_session_auto(
|
|
99
|
+
self, root_message_id: str, channel: str, trigger_text: str
|
|
100
|
+
) -> SessionState:
|
|
101
|
+
"""自动创建 Session(使用唯一 agent 配置),触发 LRU 淘汰检查。"""
|
|
102
|
+
|
|
103
|
+
async def touch(self, root_message_id: str):
|
|
104
|
+
"""更新 Session 的最近使用时间戳。"""
|
|
105
|
+
|
|
106
|
+
async def evict_lru(self) -> SessionState | None:
|
|
107
|
+
"""淘汰最近最少使用的空闲 Session,返回被淘汰的 Session。"""
|
|
108
|
+
|
|
109
|
+
async def evict_ttl_expired(self) -> list[SessionState]:
|
|
110
|
+
"""淘汰所有超过 TTL 的 Session,返回被淘汰的 Session 列表。"""
|
|
111
|
+
|
|
112
|
+
async def buffer_message(self, root_message_id: str, sender: str, text: str):
|
|
113
|
+
"""缓存 busy 状态下收到的消息。"""
|
|
114
|
+
|
|
115
|
+
async def flush_buffer(self, root_message_id: str) -> str | None:
|
|
116
|
+
"""取出并清空缓存的消息,按时间顺序合并,返回合并后的文本。"""
|
|
117
|
+
|
|
118
|
+
def all_busy(self) -> bool:
|
|
119
|
+
"""检查是否所有 Session 都处于 busy 状态。"""
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### FeishuConnection 新接口
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
class FeishuConnection:
|
|
126
|
+
# 新增方法
|
|
127
|
+
def add_reaction(self, message_id: str, emoji_type: str) -> bool:
|
|
128
|
+
"""在消息上添加 reaction 表情。调用 POST /open-apis/im/v1/messages/{message_id}/reactions。"""
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### handler.py 路由逻辑变更
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
async def handle_event(event, feishu, config, agent_manager, session_manager, ...):
|
|
135
|
+
# 1. Root_Message ID 直接从 FeishuEvent 获取(飞书原生支持 root_id)
|
|
136
|
+
root_message_id = event.root_id or event.message_id
|
|
137
|
+
|
|
138
|
+
# 2. 查找关联 Session
|
|
139
|
+
session = await session_manager.get_session(root_message_id)
|
|
140
|
+
|
|
141
|
+
# 3. 根据 session 存在与否 + 消息类型 + 是否 @机器人 进行路由
|
|
142
|
+
# (详见消息路由流程图)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## 数据模型
|
|
146
|
+
|
|
147
|
+
### SessionState 变更
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
@dataclass
|
|
151
|
+
class SessionState:
|
|
152
|
+
session_id: str # ACP agent 返回的会话 ID
|
|
153
|
+
channel: str # chat_id,发消息回飞书时需要
|
|
154
|
+
busy: bool = False
|
|
155
|
+
trigger_message_id: str = "" # Trigger_Message 的 message_id
|
|
156
|
+
config_options: Optional[list] = None
|
|
157
|
+
modes: Optional[dict] = None
|
|
158
|
+
models: Optional[dict] = None
|
|
159
|
+
# ---- 新增字段 ----
|
|
160
|
+
summary: str = "" # Trigger_Message 文本前 20 个字符
|
|
161
|
+
last_active: float = 0.0 # 最近使用时间戳 (time.time())
|
|
162
|
+
last_bot_message_id: str = "" # 最后一条机器人回复的 message_id(用于淘汰时添加 reaction)
|
|
163
|
+
message_buffer: list[tuple[float, str, str]] = field(default_factory=list)
|
|
164
|
+
# message_buffer 元素: (timestamp, sender_name, text)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
移除的字段:
|
|
168
|
+
- `agent_name`:单 agent 模式,直接从 `config.agent` 读取
|
|
169
|
+
- `workspace`:不再支持每 session 不同 workspace,直接从 `config.bridge.default_workspace` 读取
|
|
170
|
+
- `auto_approve`:单 agent 模式,直接从 `config.agent.auto_approve` 读取
|
|
171
|
+
- `initial_ts`:重命名为 `trigger_message_id`
|
|
172
|
+
|
|
173
|
+
### Config 变更
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
@dataclass
|
|
177
|
+
class BridgeConfig:
|
|
178
|
+
default_workspace: str = "~"
|
|
179
|
+
auto_approve: bool = False
|
|
180
|
+
allowed_users: list[str] = field(default_factory=list)
|
|
181
|
+
# ---- 新增字段 ----
|
|
182
|
+
max_sessions: int = 10
|
|
183
|
+
session_ttl_minutes: int = 60
|
|
184
|
+
|
|
185
|
+
@dataclass
|
|
186
|
+
class Config:
|
|
187
|
+
feishu: FeishuConfig
|
|
188
|
+
bridge: BridgeConfig
|
|
189
|
+
agent: AgentConfig # 从 agents: list[AgentConfig] 改为单个 agent
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 配置文件格式变更
|
|
193
|
+
|
|
194
|
+
**旧格式 (bridge.toml)**:
|
|
195
|
+
```toml
|
|
196
|
+
[feishu]
|
|
197
|
+
app_id = "..."
|
|
198
|
+
app_secret = "..."
|
|
199
|
+
|
|
200
|
+
[bridge]
|
|
201
|
+
default_workspace = "~"
|
|
202
|
+
auto_approve = false
|
|
203
|
+
|
|
204
|
+
[[agents]]
|
|
205
|
+
name = "kiro"
|
|
206
|
+
description = "Kiro CLI"
|
|
207
|
+
command = "kiro-cli"
|
|
208
|
+
args = ["acp"]
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**新格式 (bridge.toml)**:
|
|
212
|
+
```toml
|
|
213
|
+
[feishu]
|
|
214
|
+
app_id = "..."
|
|
215
|
+
app_secret = "..."
|
|
216
|
+
|
|
217
|
+
[bridge]
|
|
218
|
+
default_workspace = "~"
|
|
219
|
+
auto_approve = false
|
|
220
|
+
max_sessions = 10
|
|
221
|
+
session_ttl_minutes = 60
|
|
222
|
+
|
|
223
|
+
[agent]
|
|
224
|
+
name = "kiro"
|
|
225
|
+
description = "Kiro CLI - https://kiro.dev/cli/"
|
|
226
|
+
command = "kiro-cli"
|
|
227
|
+
args = ["acp"]
|
|
228
|
+
auto_approve = false
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### SessionManager 内部数据结构
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
class SessionManager:
|
|
235
|
+
_sessions: dict[str, SessionState] # root_message_id → SessionState
|
|
236
|
+
_config: Config
|
|
237
|
+
_lock: asyncio.Lock
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### LRU 淘汰实现
|
|
241
|
+
|
|
242
|
+
不引入额外依赖,使用 Python 标准库 `collections.OrderedDict` 替代普通 `dict` 存储 `_sessions`。每次 `touch()` 时将对应 key 移到末尾(`move_to_end`),淘汰时从头部取最久未使用的空闲 Session。
|
|
243
|
+
|
|
244
|
+
### TTL 淘汰实现
|
|
245
|
+
|
|
246
|
+
在 `bridge.py` 的主事件循环中启动一个 `asyncio.create_task` 定时任务,每 60 秒调用 `session_manager.evict_ttl_expired()`,遍历所有 Session 检查 `time.time() - session.last_active > ttl_seconds`。
|
|
247
|
+
|
|
248
|
+
### 消息缓存与合并发送
|
|
249
|
+
|
|
250
|
+
- Session busy 时,`handler_message.py` 调用 `session_manager.buffer_message()` 将消息存入 `SessionState.message_buffer`
|
|
251
|
+
- Agent 处理完成后(`_do_prompt` 的 finally 块),调用 `session_manager.flush_buffer()` 获取合并文本
|
|
252
|
+
- 合并格式:按时间顺序,每条消息格式为 `[sender]: text`,多条消息用换行分隔
|
|
253
|
+
- 合并后的文本作为一条新的 prompt 发送给 Agent
|
|
254
|
+
- `flush_buffer` 取出后即清空 `message_buffer`,避免重复发送
|
|
255
|
+
|
|
256
|
+
### 飞书 API 新增调用
|
|
257
|
+
|
|
258
|
+
1. **添加 Reaction**: `POST /open-apis/im/v1/messages/{message_id}/reactions` — 淘汰 Session 时在 Trigger_Message 和最后一条机器人回复上添加 DONE 表情
|
|
259
|
+
|
|
260
|
+
注:Root_Message 解析不需要额外 API 调用,飞书消息事件原生包含 `root_id` 字段。
|
|
261
|
+
|
|
262
|
+
### FeishuEvent 扩展
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
@dataclass
|
|
266
|
+
class FeishuEvent:
|
|
267
|
+
chat_id: str
|
|
268
|
+
message_id: str
|
|
269
|
+
parent_id: Optional[str]
|
|
270
|
+
text: str
|
|
271
|
+
files: list[FeishuFile] = field(default_factory=list)
|
|
272
|
+
# ---- 新增字段 ----
|
|
273
|
+
root_id: Optional[str] = None # 飞书原生提供的消息引用链根消息 ID
|
|
274
|
+
is_mention_bot: bool = False # 消息是否 @了机器人
|
|
275
|
+
sender_name: str = "" # 发送者名称(用于消息缓存合并时标注)
|
|
276
|
+
chat_type: str = "" # 聊天类型:p2p(私聊)或 group(群聊)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
在 `FeishuConnection._on_message_receive()` 中从事件数据提取 `root_id`、`mentions` 和 `sender` 信息。handler 层通过 `event.root_id or event.message_id` 获取 Root_Message ID,无需额外 API 调用。
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
## 正确性属性(Correctness Properties)
|
|
283
|
+
|
|
284
|
+
*正确性属性是指在系统所有合法执行中都应成立的特征或行为——本质上是对系统应做什么的形式化陈述。属性是人类可读规格说明与机器可验证正确性保证之间的桥梁。*
|
|
285
|
+
|
|
286
|
+
### Property 1: 自动创建 Session 以 Root_Message 为索引
|
|
287
|
+
|
|
288
|
+
*For any* 消息,若该消息满足以下条件之一:(a) 在群聊中 @了机器人,或 (b) 在私聊中发送,且不是指令(不以 # 或 ! 开头)、且其 Root_Message 没有关联的活跃 Session,则 SessionManager 应创建一个新 Session,其索引 key 等于 Root_Message 的 message_id,且 Session 使用配置文件中唯一 agent 的设置。
|
|
289
|
+
|
|
290
|
+
**Validates: Requirements 1.1, 1.5, 2.3**
|
|
291
|
+
|
|
292
|
+
### Property 2: 消息路由到已有 Session
|
|
293
|
+
|
|
294
|
+
*For any* 消息,若其 Root_Message 已关联一个活跃 Session,则该消息应被路由到该 Session(无论是否再次 @机器人),且不应创建新的 Session。
|
|
295
|
+
|
|
296
|
+
**Validates: Requirements 1.2, 1.8, 2.2**
|
|
297
|
+
|
|
298
|
+
### Property 3: 忽略无关消息
|
|
299
|
+
|
|
300
|
+
*For any* 消息,若该消息来自群聊、未 @机器人、不是指令、且其 Root_Message 没有关联的活跃 Session,则该消息应被忽略(不创建 Session、不发送任何回复)。
|
|
301
|
+
|
|
302
|
+
**Validates: Requirements 1.9, 2.4**
|
|
303
|
+
|
|
304
|
+
### Property 4: Session 内指令无需 @机器人
|
|
305
|
+
|
|
306
|
+
*For any* # 指令消息,若该消息位于已有 Session 的消息引用链中,则无论是否 @了机器人,该指令都应被处理并作用于该 Session。
|
|
307
|
+
|
|
308
|
+
**Validates: Requirements 1.10**
|
|
309
|
+
|
|
310
|
+
### Property 5: Session 外指令需要 @机器人(群聊)或直接发送(私聊)
|
|
311
|
+
|
|
312
|
+
*For any* # 指令消息,若该消息不在任何已有 Session 的消息引用链中,则在群聊中仅当用户 @了机器人时才触发指令处理,在私聊中无需 @机器人即可触发;对于依赖 Session 的指令应回复 "No active conversation",对于不依赖 Session 的指令应正常执行。
|
|
313
|
+
|
|
314
|
+
**Validates: Requirements 1.11**
|
|
315
|
+
|
|
316
|
+
### Property 6: Root_Message ID 提取的正确性
|
|
317
|
+
|
|
318
|
+
*For any* FeishuEvent,若事件包含 `root_id` 字段,则 Root_Message ID 应等于 `root_id`;若事件不包含 `root_id`(独立消息),则 Root_Message ID 应等于 `message_id`。
|
|
319
|
+
|
|
320
|
+
**Validates: Requirements 2.1**
|
|
321
|
+
|
|
322
|
+
### Property 7: Session 摘要为触发消息前 20 字符
|
|
323
|
+
|
|
324
|
+
*For any* 触发消息文本,自动创建的 Session 的 summary 字段应等于该文本的前 20 个字符(若文本不足 20 字符则取全部)。
|
|
325
|
+
|
|
326
|
+
**Validates: Requirements 1.6**
|
|
327
|
+
|
|
328
|
+
### Property 8: 消息缓存按时间顺序合并并保留发送者
|
|
329
|
+
|
|
330
|
+
*For any* 一组在 Session busy 期间到达的消息序列,`flush_buffer` 返回的合并文本应按消息到达时间升序排列,且每条消息的发送者信息都出现在合并结果中。
|
|
331
|
+
|
|
332
|
+
**Validates: Requirements 2.6, 2.7**
|
|
333
|
+
|
|
334
|
+
### Property 9: LRU 淘汰选择最久未使用的空闲 Session
|
|
335
|
+
|
|
336
|
+
*For any* 一组活跃 Session(数量已达 max_sessions),当需要淘汰时,被淘汰的 Session 应是所有非 busy Session 中 `last_active` 最小的那个。
|
|
337
|
+
|
|
338
|
+
**Validates: Requirements 3.2**
|
|
339
|
+
|
|
340
|
+
### Property 10: Touch 更新最近使用时间戳
|
|
341
|
+
|
|
342
|
+
*For any* Session,调用 `touch()` 后其 `last_active` 应大于等于调用前的值,且应反映当前时间。
|
|
343
|
+
|
|
344
|
+
**Validates: Requirements 3.3**
|
|
345
|
+
|
|
346
|
+
### Property 11: TTL 淘汰移除超时 Session
|
|
347
|
+
|
|
348
|
+
*For any* 一组活跃 Session 和给定的 `session_ttl_minutes` 值,`evict_ttl_expired` 应淘汰且仅淘汰那些 `time.time() - last_active > ttl_seconds` 的 Session。
|
|
349
|
+
|
|
350
|
+
**Validates: Requirements 4.3**
|
|
351
|
+
|
|
352
|
+
### Property 12: 淘汰时终止进程并添加 Reaction
|
|
353
|
+
|
|
354
|
+
*For any* 被淘汰的 Session(无论 LRU 还是 TTL),系统应终止对应的 ACP_Agent 进程,并在该 Session 的 Trigger_Message 和最后一条机器人回复消息上添加 reaction 表情。
|
|
355
|
+
|
|
356
|
+
**Validates: Requirements 3.5, 3.6, 4.4, 4.5**
|
|
357
|
+
|
|
358
|
+
### Property 13: 配置缺省值
|
|
359
|
+
|
|
360
|
+
*For any* 配置文件,若缺少 `max_sessions` 字段则默认值为 10,若缺少 `session_ttl_minutes` 字段则默认值为 60。
|
|
361
|
+
|
|
362
|
+
**Validates: Requirements 5.5**
|
|
363
|
+
|
|
364
|
+
### Property 14: #sessions 输出格式
|
|
365
|
+
|
|
366
|
+
*For any* 一组活跃 Session,执行 `#sessions` 指令的输出应包含每个 Session 的摘要(summary)、状态(busy/idle)和最近使用时间,且不包含 agent 名称。
|
|
367
|
+
|
|
368
|
+
**Validates: Requirements 6.1, 6.2**
|
|
369
|
+
|
|
370
|
+
## 错误处理
|
|
371
|
+
|
|
372
|
+
| 场景 | 处理方式 |
|
|
373
|
+
|------|---------|
|
|
374
|
+
| ACP_Agent 启动失败 | 向用户回复错误信息,不创建 Session(需求 1.7) |
|
|
375
|
+
| 所有 Session 均 busy 且达上限 | 回复 "All sessions are busy, please try again later"(需求 3.4) |
|
|
376
|
+
| 飞书 API 调用失败(add_reaction) | 记录日志,不影响淘汰流程 |
|
|
377
|
+
| 配置文件缺少 [agent] 表 | 启动时校验失败,抛出异常退出 |
|
|
378
|
+
| 配置文件包含多个 agent 配置 | 启动时校验失败,抛出异常退出 |
|
|
379
|
+
|
|
380
|
+
## 测试策略
|
|
381
|
+
|
|
382
|
+
### 双重测试方法
|
|
383
|
+
|
|
384
|
+
本项目采用单元测试 + 属性测试(Property-Based Testing)的双重策略:
|
|
385
|
+
|
|
386
|
+
- **单元测试**:验证具体示例、边界情况和错误条件
|
|
387
|
+
- **属性测试**:验证跨所有输入的通用属性
|
|
388
|
+
|
|
389
|
+
两者互补:单元测试捕获具体 bug,属性测试验证通用正确性。
|
|
390
|
+
|
|
391
|
+
### 属性测试库
|
|
392
|
+
|
|
393
|
+
使用 **Hypothesis**(Python 属性测试库),已是 Python 生态中最成熟的 PBT 框架。
|
|
394
|
+
|
|
395
|
+
需在 `pyproject.toml` 的 `[dependency-groups] dev` 中添加:
|
|
396
|
+
```toml
|
|
397
|
+
dev = [
|
|
398
|
+
"pytest>=9.0.2",
|
|
399
|
+
"pytest-asyncio>=1.3.0",
|
|
400
|
+
"hypothesis>=6.100.0",
|
|
401
|
+
]
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### 属性测试配置
|
|
405
|
+
|
|
406
|
+
- 每个属性测试至少运行 **100 次迭代**(`@settings(max_examples=100)`)
|
|
407
|
+
- 每个属性测试必须用注释标注对应的设计属性
|
|
408
|
+
- 标注格式:`# Feature: session-refactor, Property {number}: {property_text}`
|
|
409
|
+
|
|
410
|
+
### 单元测试覆盖
|
|
411
|
+
|
|
412
|
+
单元测试重点覆盖:
|
|
413
|
+
- `#new` / `#agents` 指令已移除(需求 1.3, 1.4)
|
|
414
|
+
- 配置文件解析:`[agent]` 单表、默认值、校验(需求 5.1-5.4)
|
|
415
|
+
- `init` 命令生成的样例配置格式(需求 5.3)
|
|
416
|
+
- ACP_Agent 启动失败时的错误处理(需求 1.7)
|
|
417
|
+
- 所有 Session busy 时的提示(需求 3.4)
|
|
418
|
+
- Juan 命名清理验证(需求 7.2, 7.3)
|
|
419
|
+
- README 内容检查(需求 8.1-8.5)
|
|
420
|
+
|
|
421
|
+
### 属性测试覆盖
|
|
422
|
+
|
|
423
|
+
每个 Correctness Property(Property 1-14)对应一个属性测试:
|
|
424
|
+
|
|
425
|
+
| Property | 测试文件 | 核心生成器 |
|
|
426
|
+
|----------|---------|-----------|
|
|
427
|
+
| P1: 自动创建 Session | `tests/test_session.py` | 随机消息(mention=True, non-command text, no existing session) |
|
|
428
|
+
| P2: 路由到已有 Session | `tests/test_session.py` | 随机消息 + 已有 session 的 root_message_id |
|
|
429
|
+
| P3: 忽略无关消息 | `tests/test_handler.py` | 随机消息(mention=False, no session) |
|
|
430
|
+
| P4: Session 内指令无需 @mention | `tests/test_handler.py` | 随机 # 指令 + 已有 session |
|
|
431
|
+
| P5: Session 外指令需要 @mention | `tests/test_handler.py` | 随机 # 指令 + 无 session |
|
|
432
|
+
| P6: Root_Message ID 提取 | `tests/test_feishu.py` | 随机 FeishuEvent(有/无 root_id) |
|
|
433
|
+
| P7: 摘要截断 | `tests/test_session.py` | 随机 Unicode 字符串 |
|
|
434
|
+
| P8: 消息缓存合并 | `tests/test_session.py` | 随机消息列表(含 sender 和 timestamp) |
|
|
435
|
+
| P9: LRU 淘汰 | `tests/test_session.py` | 随机 Session 集合(不同 last_active, busy 状态) |
|
|
436
|
+
| P10: Touch 时间戳 | `tests/test_session.py` | 随机 Session |
|
|
437
|
+
| P11: TTL 淘汰 | `tests/test_session.py` | 随机 Session 集合 + 随机 TTL 值 |
|
|
438
|
+
| P12: 淘汰清理 | `tests/test_session.py` | 随机被淘汰 Session |
|
|
439
|
+
| P13: 配置缺省值 | `tests/test_config.py` | 随机配置字典(随机缺少字段) |
|
|
440
|
+
| P14: #sessions 输出 | `tests/test_handler_command.py` | 随机 Session 集合 |
|
|
441
|
+
|
|
442
|
+
每个属性测试必须由单个 property-based test 实现,使用 Hypothesis 的 `@given` 装饰器。
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# 需求文档
|
|
2
|
+
|
|
3
|
+
## 简介
|
|
4
|
+
|
|
5
|
+
对 AgentBridge(飞书聊天机器人桥接服务)进行会话管理重构和功能改造。主要变更包括:移除 `#new` 指令,改为基于飞书消息的引用关系自动管理会话生命周期;引入 LRU 和定时淘汰机制控制资源占用;简化配置文件结构(取消多 agent 配置);清理历史遗留命名;完善文档。
|
|
6
|
+
|
|
7
|
+
## 术语表
|
|
8
|
+
|
|
9
|
+
- **Bridge**: AgentBridge 桥接服务主进程,负责连接飞书与 ACP agent
|
|
10
|
+
- **Session_Manager**: 会话管理器,负责会话的创建、查找、淘汰和生命周期管理
|
|
11
|
+
- **Session**: 一次用户与 agent 之间的对话上下文,包含 agent 进程句柄和消息历史
|
|
12
|
+
- **Root_Message**: 飞书消息引用链(通过 parent_id 关联)的根消息,即引用链中最顶层的那条消息,不一定是触发会话创建的消息
|
|
13
|
+
- **Trigger_Message**: 真正触发新 Session 创建的那条 @机器人 的消息;Session 通过其所在消息引用链的 Root_Message 的 message_id 来索引(而非 Trigger_Message 的 message_id),因为同一引用链下的所有消息都能沿 parent_id 找到 Root_Message;同一个 Root_Message 下只允许存在一个 Session
|
|
14
|
+
- **Thread**: 飞书中以某条消息为根的回复链(通过 parent_id 关联)
|
|
15
|
+
- **LRU_Eviction**: 基于最近最少使用策略的会话淘汰机制
|
|
16
|
+
- **TTL_Eviction**: 基于存活时间的会话定时淘汰机制
|
|
17
|
+
- **Config_File**: TOML 格式的桥接服务配置文件(bridge.toml)
|
|
18
|
+
- **ACP_Agent**: 通过 ACP 协议通信的 agent 进程(如 Kiro CLI)
|
|
19
|
+
- **Juan**: 本项目的前身开源项目,位于 ~/code/juan
|
|
20
|
+
|
|
21
|
+
## 需求
|
|
22
|
+
|
|
23
|
+
### 通用规则
|
|
24
|
+
|
|
25
|
+
1. THE Bridge 自身产生的所有提示消息(非 ACP_Agent 回复的内容)SHALL 使用英文
|
|
26
|
+
|
|
27
|
+
### 需求 1:自动会话创建(移除 #new 指令)
|
|
28
|
+
|
|
29
|
+
**用户故事:** 作为飞书用户,我希望直接 @机器人 就能开始对话,而不需要记住和输入 #new 指令,以降低使用门槛。
|
|
30
|
+
|
|
31
|
+
#### 验收标准
|
|
32
|
+
|
|
33
|
+
1. WHEN 用户在群聊中 @机器人 并发送非指令文本消息(不以 # 开头),且该消息所在的消息引用链的 Root_Message 没有已关联的 Session,THE Session_Manager SHALL 自动创建一个新 Session,以 Root_Message 的 message_id 作为 Session 的索引 key,并将该 @机器人 的消息作为 Trigger_Message
|
|
34
|
+
2. WHEN 用户在私聊(DM)中发送非指令文本消息(不以 # 开头),且该消息所在的消息引用链的 Root_Message 没有已关联的 Session,THE Session_Manager SHALL 自动创建一个新 Session,无需 @机器人
|
|
35
|
+
2. WHEN 用户引用(reply)已有 Session 所在消息引用链中的任意消息并发送文本(无需再次 @机器人),THE Session_Manager SHALL 沿 parent_id 找到 Root_Message,将该消息路由到 Root_Message 对应的已有 Session 中继续对话(前提:飞书应用已开启"接收群聊中所有消息"权限)
|
|
36
|
+
3. THE Bridge SHALL 移除 `#new` 指令的解析和处理逻辑
|
|
37
|
+
4. THE Bridge SHALL 移除 `#agents` 指令的解析和处理逻辑(因为不再有多 agent 选择)
|
|
38
|
+
5. THE Bridge SHALL 移除 `!` shell 指令功能及 `handler_shell.py` 模块(对非程序员用户过于危险)
|
|
39
|
+
5. WHEN 自动创建 Session 时,THE Session_Manager SHALL 使用 Config_File 中配置的唯一 agent 和默认 workspace 路径
|
|
40
|
+
6. WHEN 自动创建 Session 时,THE Session_Manager SHALL 记录 Trigger_Message 的文本内容前 20 个字符作为该 Session 的摘要(summary)
|
|
41
|
+
7. IF 自动创建 Session 过程中 ACP_Agent 启动失败,THEN THE Bridge SHALL 向用户回复错误信息并且不创建 Session
|
|
42
|
+
8. WHEN 用户在已有 Session 的消息引用链中再次 @机器人 发送非指令消息,THE Session_Manager SHALL 不创建新 Session,而是将该消息作为普通消息路由到已有 Session 中继续对话
|
|
43
|
+
9. WHEN 收到一条未 @机器人 的消息,且该消息来自群聊(非私聊),且该消息不属于任何已有 Session 的消息引用链(无 parent_id,或沿 parent_id 找到的 Root_Message 没有关联 Session),THE Bridge SHALL 忽略该消息
|
|
44
|
+
10. WHEN 收到一条 # 指令消息且该消息在已有 Session 的消息引用链中,THE Bridge SHALL 无需 @机器人 即可触发指令处理,并将指令作用于该 Session
|
|
45
|
+
11. WHEN 收到一条 # 指令消息且该消息不在任何已有 Session 的消息引用链中,THE Bridge SHALL 在群聊中仅当用户 @了机器人时触发指令处理,在私聊中无需 @机器人即可触发;对于依赖 Session 的指令(如 #session、#end、#cancel、#diff、#mode、#model),SHALL 回复"No active conversation";对于不依赖 Session 的指令(如 #sessions、#help),SHALL 正常执行
|
|
46
|
+
|
|
47
|
+
### 需求 2:基于消息引用关系的会话路由
|
|
48
|
+
|
|
49
|
+
**用户故事:** 作为飞书用户,我希望通过回复之前的对话消息来延续会话上下文,使多轮对话自然流畅。
|
|
50
|
+
|
|
51
|
+
#### 验收标准
|
|
52
|
+
|
|
53
|
+
1. WHEN 收到一条带有 parent_id 的消息,THE Session_Manager SHALL 沿 parent_id 向上查找消息引用链,定位到 Root_Message,并检查 Root_Message 是否关联了 Session
|
|
54
|
+
2. WHEN 通过 Root_Message 找到对应的活跃 Session,THE Bridge SHALL 将消息转发到该 Session
|
|
55
|
+
3. IF Root_Message 没有对应的活跃 Session(可能从未创建过或已被淘汰),且当前消息是 @机器人 的非指令消息,THEN THE Session_Manager SHALL 以该 Root_Message 的 message_id 为索引 key 创建新 Session,同时 THE Bridge SHALL 向用户发送提示 "New conversation started"
|
|
56
|
+
4. IF Root_Message 没有对应的活跃 Session,且当前消息未 @机器人,THEN THE Bridge SHALL 忽略该消息
|
|
57
|
+
6. WHEN Session 处于 busy 状态时收到新消息,THE Bridge SHALL 静默缓存该消息(不向群聊发送任何提示),待当前 ACP_Agent 处理完成后,将缓存的消息合并为一条发送给 ACP_Agent
|
|
58
|
+
7. WHEN 合并缓存消息时,THE Bridge SHALL 按消息到达的时间顺序拼接,保留每条消息的发送者信息
|
|
59
|
+
|
|
60
|
+
### 需求 3:LRU 会话淘汰
|
|
61
|
+
|
|
62
|
+
**用户故事:** 作为系统管理员,我希望通过 LRU 策略限制最大并发会话数,以避免 agent 进程占用过多系统资源。
|
|
63
|
+
|
|
64
|
+
#### 验收标准
|
|
65
|
+
|
|
66
|
+
1. THE Config_File SHALL 包含 `max_sessions` 配置项,用于指定最大活跃会话数
|
|
67
|
+
2. WHEN 创建新 Session 时活跃会话数已达到 `max_sessions` 上限,THE Session_Manager SHALL 淘汰最近最少使用的空闲 Session(非 busy 状态)
|
|
68
|
+
3. WHEN Session 收到新消息或产生交互时,THE Session_Manager SHALL 更新该 Session 的最近使用时间戳
|
|
69
|
+
4. WHILE 所有已有 Session 均处于 busy 状态且会话数已达上限,THE Bridge SHALL 向用户回复 "All sessions are busy, please try again later"
|
|
70
|
+
5. WHEN 淘汰一个 Session 时,THE Session_Manager SHALL 终止对应的 ACP_Agent 进程并释放资源
|
|
71
|
+
6. WHEN 淘汰一个 Session 时,THE Bridge SHALL 在该 Session 的 Trigger_Message 和最后一条机器人回复消息上添加 reaction 表情(DONE),通知用户对话已结束
|
|
72
|
+
|
|
73
|
+
**用户故事:** 作为系统管理员,我希望长时间不活跃的会话能被自动清理,以避免资源长期占用。
|
|
74
|
+
|
|
75
|
+
#### 验收标准
|
|
76
|
+
|
|
77
|
+
1. THE Config_File SHALL 包含 `session_ttl_minutes` 配置项,用于指定会话的最大空闲存活时间(单位:分钟)
|
|
78
|
+
2. THE Session_Manager SHALL 定期检查所有活跃 Session 的最近使用时间戳
|
|
79
|
+
3. WHEN 一个 Session 的空闲时间超过 `session_ttl_minutes` 配置值,THE Session_Manager SHALL 自动淘汰该 Session
|
|
80
|
+
4. WHEN 定时淘汰一个 Session 时,THE Session_Manager SHALL 终止对应的 ACP_Agent 进程并释放资源
|
|
81
|
+
5. WHEN 定时淘汰一个 Session 时,THE Bridge SHALL 在该 Session 的 Trigger_Message 和最后一条机器人回复消息上添加 reaction 表情(DONE),通知用户对话已结束
|
|
82
|
+
|
|
83
|
+
### 需求 5:简化配置文件(单 agent 模式)
|
|
84
|
+
|
|
85
|
+
**用户故事:** 作为系统管理员,我希望配置文件更简洁,只需配置一个 agent,降低配置复杂度。由于移除了 `#new` 指令,用户无法在创建会话时选择 agent,因此多 agent 配置不再有意义。
|
|
86
|
+
|
|
87
|
+
#### 验收标准
|
|
88
|
+
|
|
89
|
+
1. THE Config_File SHALL 使用 `[agent]` 单表替代原有的 `[[agents]]` 数组表来配置唯一的 ACP_Agent
|
|
90
|
+
2. THE Config_File SHALL 在 `[bridge]` 表中包含 `max_sessions` 和 `session_ttl_minutes` 配置项
|
|
91
|
+
3. THE Config_File 的 `init` 命令生成的样例配置 SHALL 使用 Kiro CLI 作为默认 agent 示例
|
|
92
|
+
4. THE Bridge SHALL 在启动时校验 Config_File 中有且仅有一个 `[agent]` 配置
|
|
93
|
+
5. WHEN Config_File 中缺少 `max_sessions` 或 `session_ttl_minutes` 配置项,THE Bridge SHALL 使用合理的默认值(max_sessions 默认 10,session_ttl_minutes 默认 60)
|
|
94
|
+
|
|
95
|
+
### 需求 6:#sessions 指令增强
|
|
96
|
+
|
|
97
|
+
**用户故事:** 作为飞书用户,我希望在查看活跃会话列表时能看到每个会话的简要内容,以便快速识别各会话。
|
|
98
|
+
|
|
99
|
+
#### 验收标准
|
|
100
|
+
|
|
101
|
+
1. WHEN 用户执行 `#sessions` 指令,THE Bridge SHALL 在每个 Session 条目中显示:摘要(Trigger_Message 文本前 20 个字符)、状态(busy/idle)、最近使用时间
|
|
102
|
+
2. WHEN 用户执行 `#sessions` 指令,THE Bridge SHALL 不再显示 agent 名称(因为只有一个 agent)
|
|
103
|
+
|
|
104
|
+
### 需求 7:移除 Juan 项目遗留命名
|
|
105
|
+
|
|
106
|
+
**用户故事:** 作为项目维护者,我希望代码和配置中不再包含来自 Juan 项目的命名痕迹,使项目具有独立的身份标识。
|
|
107
|
+
|
|
108
|
+
#### 验收标准
|
|
109
|
+
|
|
110
|
+
1. THE Config_File 的默认文件名 SHALL 从 `bridge.toml` 保持不变(确认当前无 juan 相关命名)
|
|
111
|
+
2. THE Bridge SHALL 确保所有源代码文件中不包含 "juan" 字样的变量名、注释或字符串
|
|
112
|
+
3. THE Bridge SHALL 确保 pyproject.toml 中的项目名称和描述不包含 "juan" 字样
|
|
113
|
+
4. IF 代码中存在从 Juan 项目引用过来但未适配的逻辑,THEN THE Bridge SHALL 根据 Juan 项目源码(~/code/juan)进行对照修复
|
|
114
|
+
|
|
115
|
+
### 需求 8:完善 README 文档
|
|
116
|
+
|
|
117
|
+
**用户故事:** 作为新用户或贡献者,我希望 README 包含完整的安装步骤和项目来源说明,以便快速上手和了解项目背景。
|
|
118
|
+
|
|
119
|
+
#### 验收标准
|
|
120
|
+
|
|
121
|
+
1. THE README SHALL 包含完整的安装步骤,包括 Python 版本要求、依赖安装、配置文件生成和启动命令
|
|
122
|
+
2. THE README SHALL 包含飞书应用创建和配置的基本指引,包括需要开启"接收群聊中所有消息"权限的说明
|
|
123
|
+
3. THE README SHALL 包含可用指令的说明列表(反映重构后的指令集,不含 #new、#agents 和 ! shell 指令)
|
|
124
|
+
4. THE README SHALL 根据 MIT 协议要求,明确声明本项目源自 Juan 项目(https://github.com/DiscreteTom/juan),并保留原始版权信息
|
|
125
|
+
5. THE README SHALL 包含项目简介,说明 AgentBridge 的用途和核心功能
|