feat(mcp): 浏览器分步填报 P3——7 个 browser 工具 + 单线程 actor + 磁盘锁
填报从「一把梭」变成分步:Agent 可以开浏览器、登录、建单、按步填、看页面、关,每步有截图, 中途失败不必从头重来。提交不在本步——提交不可逆,留给 P4 的 submit_preview/submit_execute。 - src/infra/browser/actor.py(新):Playwright 单线程 actor(sync API 的 greenlet 线程亲和要求所有 调用回到创建线程)+ 会话注册表 + cases/<id>/.browser.lock(PID/started_at/heartbeat/url, 心跳 180 秒过期才允许接管;锁文件损坏按无锁处理;atexit 尽力关浏览器)。与 COM actor 分开。 - base.py:login_portal 拆成 submit_login + wait_for_portal(返回布尔不抛异常),新增 portal_reached/ human_required;_screenshot 支持 screenshot_dir 并按 001-步骤名.png 编号、返回路径、失败只告警 - src/mcp/tools/browser.py(新):browser_open/login/new_form/fill/steps/page_state/close,工具 17 → 24 · need_human 用 ok:true + logged_in:false 返回(扫码不是失败,避免宿主当错误重试或另开窗口) · 步骤顺序硬约束,跳步拒绝并用 next_action 指回该补的一步;步骤报错回传 detail.screenshot · browser_close 明确不提交不存草稿;travel 五步、normal 四步(无 subsidy) - tests/test_mcp_browser.py(新,17 例,FakeBot/FakePage 不起浏览器):锁四种情形、need_human 分支与 截图落点、重复登录幂等、未打开时各工具拒绝、类型推断与两 info 并存时拒猜、未登录拒建单、跳步拦截、 步骤推进与 remaining、未知步骤、报错截图、close 释放会话与锁、open 复用、steps 两种流程 - 文档:docs/MCP.md 工具段按 wire schema 重生成(24 工具);AGENTS/SKILL/操作指南/README/浏览器 README 同步分步流程、锁与心跳、有头默认、提交必须交人工;方案文档 §13 记录实现、取舍与未完成项 验证: - 联机跑通 open(3.1s 起有头 Chromium)→ 锁写入(pid/heartbeat)→ page_state(about:blank, 001-state.png 4721 字节)→ 重复 open 复用(already_open=true,不多开窗)→ close(锁释放、注册表清空) - 登录与填报的联机验证需要用户在场(SSO 可能扫码),本轮未做 - 门禁:ruff / format --check / mypy src / deptry / pytest 129 passed / mcp_smoke 24 工具 instructions 2109 字节。仍用 --no-verify 提交并手跑等价检查(原因见 AGENTS 提交纪律)。
This commit is contained in:
+69
-2
@@ -288,8 +288,7 @@ cases/<case_id>/
|
||||
|
||||
## 文档产物
|
||||
|
||||
Excel COM,需本机装有 Microsoft Excel;仅普通发票需要。模板查找顺序:环境变量
|
||||
`REQUISITION_TEMPLATE` → `templates/低值易耗品申购*.xls` → 项目根(旧位置);产物写 `cases/<id>/artifacts/`。
|
||||
Excel COM,需本机装有 Microsoft Excel;仅普通发票需要。模板顺序:`REQUISITION_TEMPLATE` → `templates/` → 项目根。
|
||||
|
||||
### `mcp__arb__doc_requisition`
|
||||
|
||||
@@ -303,3 +302,71 @@ Excel COM,需本机装有 Microsoft Excel;仅普通发票需要。模板查
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `exclude` | array/null | 否 | — | — |
|
||||
| `project` | null/string | 否 | — | — |
|
||||
|
||||
## 浏览器分步填报
|
||||
|
||||
需要本机已 `uv run playwright install chromium`。默认有头;归属由 `cases/<id>/.browser.lock` 记录(PID+心跳,180 秒未心跳可被接管)。**提交不在这里**:填完交人工核对。
|
||||
|
||||
### `mcp__arb__browser_open`
|
||||
|
||||
打开(或复用)该会话的浏览器。
|
||||
|
||||
默认有头:提交不可逆,要看得见在点什么。同一会话重复调用直接复用现有浏览器。
|
||||
被别的进程占用时返回 owned_by_other_process,别绕过去另开一个浏览器。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `headless` | boolean | 否 | `false` | — |
|
||||
|
||||
### `mcp__arb__browser_login`
|
||||
|
||||
登录信息门户。遇到扫码或验证码不要死等:返回 need_human,人工完成后再次调用。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `wait_seconds` | integer | 否 | `25` | — |
|
||||
|
||||
### `mcp__arb__browser_new_form`
|
||||
|
||||
进入报销系统并新建单据。kind 省略时按会话里已有的 info 推断。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `kind` | null/string | 否 | — | — |
|
||||
|
||||
### `mcp__arb__browser_fill`
|
||||
|
||||
执行一步填报。step 按 browser_new_form 返回的 steps 顺序,不能跳步。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `step` | string | 是 | — | — |
|
||||
|
||||
### `mcp__arb__browser_steps`
|
||||
|
||||
列出该会话单据类型的步骤顺序与含义(不要求浏览器已打开)。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
|
||||
### `mcp__arb__browser_page_state`
|
||||
|
||||
看当前页面:URL、标题、命中的表单标记、已完成步骤、最近截图。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
| `screenshot` | boolean | 否 | `true` | — |
|
||||
|
||||
### `mcp__arb__browser_close`
|
||||
|
||||
关闭浏览器并释放锁。不会提交也不会存草稿,填了一半的内容会丢。
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认 | 说明 |
|
||||
|------|------|------|------|------|
|
||||
| `case_id` | string | 是 | — | — |
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ last_reviewed: 2026-10-19
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `MCP.md` | MCP 工具完整契约:注册方法、返回信封、会话目录、标准流程、17 个工具的参数表 |
|
||||
| `MCP.md` | MCP 工具完整契约:注册方法、返回信封、会话目录、标准流程、24 个工具的参数表 |
|
||||
| `报销操作指南.md` | 面向最终用户的报销口径与材料准备说明 |
|
||||
|
||||
> 旧的 `API.md`(Flask 路由/SSE 文档)已随 Web 界面删除;需要历史接口形态时看 git 提交 `b2d271c`。
|
||||
|
||||
+18
-3
@@ -37,7 +37,7 @@ last_reviewed: 2026-10-19
|
||||
2. **配好** `scripts/data/config.json`(参考根目录 `config.example.json`)
|
||||
3. **要手机拍照上传时**启动收件器:`make upload`(默认 `0.0.0.0:5055`)
|
||||
|
||||
自检:`make mcp-smoke` 会真起 stdio 子进程握手并列出 17 个工具。
|
||||
自检:`make mcp-smoke` 会真起 stdio 子进程握手并列出 24 个工具。
|
||||
|
||||
---
|
||||
|
||||
@@ -73,6 +73,17 @@ last_reviewed: 2026-10-19
|
||||
| 6 | 取骨架 → 填值 → 落盘校验(试算用 `validate_info` 不落盘) | `info_skeleton` → `save_info` |
|
||||
| 7 | 普通发票生成申购单(`exclude` 剔除不参与申购的物品) | `doc_requisition` |
|
||||
| 8 | 随时复查态势;要重跑就清派生产物 | `case_status` / `case_reset` |
|
||||
| 9 | 填报(可选):开浏览器 → 登录 → 建单 → 按步填 → 复查 → 关 | `browser_open` → `browser_login` → `browser_new_form` → `browser_fill` → `browser_page_state` → `browser_close` |
|
||||
|
||||
### 填报这段要注意
|
||||
|
||||
- 浏览器默认**有头**:提交不可逆,得看得见在点什么;期间别手动点页面
|
||||
- `browser_login` 返回 `need_human` 时,人在已经打开的那个窗口里扫码或输验证码,然后**重调 `browser_login`**
|
||||
——不要另开浏览器。`cases/<id>/.browser.lock` 记着谁拥有它,返回 `owned_by_other_process` 就等对方释放
|
||||
- `travel_info` 与 `normal_info` 同时存在时,`browser_new_form` 必须显式传 `kind`(混合发票只能走普通报销)
|
||||
- 步骤不能跳:travel 是 basic/details/payment/subsidy/attachments,normal 没有 subsidy
|
||||
- 每步截图按 `001-步骤名.png` 落在 `cases/<id>/screenshots/`;失败时截图路径在返回的 `detail.screenshot`
|
||||
- **`browser_close` 不提交也不存草稿**,填了一半会丢;提交本身仍未实现(P4),必须人工核对后自己点
|
||||
|
||||
**`ok: true` 的含义有限**:`match_payments` 成功只代表贪心跑完。一对多凑额、发票总额小于支付总额、
|
||||
未匹配项都会进 `review_required`,必须逐条确认——等额发票尤其容易配错。
|
||||
@@ -170,7 +181,7 @@ travel_info.json 或 normal_info.json
|
||||
│ doc_requisition(Excel COM,仅普通发票)
|
||||
▼
|
||||
artifacts/ 申购单
|
||||
│ 浏览器填报与提交:P3/P4 建设中,当前没有工具
|
||||
│ browser_open → browser_login → browser_new_form → browser_fill(提交仍未实现,须人工)
|
||||
▼
|
||||
财务系统报销单
|
||||
```
|
||||
@@ -259,6 +270,10 @@ URL 类字段(`sso_login_url`、`portal_url`、`reimburse_url`、`reimburse_pa
|
||||
| 手机上传 403 | URL 缺令牌或令牌不对 | 用 `case_create` 返回的完整地址 |
|
||||
| 抽取字段读错(编号/卡号/金额位数) | 文本层与读图本来就会冲突 | 放大该区域重看,两路都不许默认采信 |
|
||||
| 申购单生成失败 | 未装 Excel 或 pywin32;或有弹窗未关 | 需要 Windows + Excel,人工关掉残留 Excel 窗口再试 |
|
||||
| 浏览器起不来 | 没装 Chromium | `uv run playwright install chromium` |
|
||||
| 返回 `owned_by_other_process` | 上一代 MCP 子进程还占着浏览器 | 让持有者 `browser_close`;心跳超 180 秒自动接管 |
|
||||
| 登录一直不跳转 | SSO 要扫码或有验证码 | 看返回的 `need_human` 与截图,人工完成后重调 `browser_login` |
|
||||
| 某一步 fill 失败 | 选择器没命中或页面慢 | 看返回 `detail.screenshot` 的截图,人工看一眼再重填该步 |
|
||||
|
||||
---
|
||||
|
||||
@@ -278,6 +293,6 @@ uv run python -m src.infra.documents.requisition --session cases/<case_id> --exc
|
||||
|
||||
## 相关文档
|
||||
|
||||
- [docs/MCP.md](./MCP.md) — 17 个工具的参数与返回约定
|
||||
- [docs/MCP.md](./MCP.md) — 24 个工具的参数与返回约定
|
||||
- [.agents/skills/reimbursement/SKILL.md](../.agents/skills/reimbursement/SKILL.md) — 宿主 Agent 的操作纪律
|
||||
- [AGENTS.md](../AGENTS.md) — 开发约束与踩坑记录
|
||||
|
||||
Reference in New Issue
Block a user