From 6e00ddc37ecb03d71b3bfda16eada1b64057983e Mon Sep 17 00:00:00 2001 From: wandering Date: Tue, 20 Oct 2026 21:49:17 +0800 Subject: [PATCH] =?UTF-8?q?docs+chore:=20=E5=AE=9A=E6=A1=88=E2=80=94?= =?UTF-8?q?=E2=80=94submit=5F*=20=E6=95=B4=E7=B1=BB=E4=B8=8D=E5=81=9A?= =?UTF-8?q?=EF=BC=8C=E5=A1=AB=E6=8A=A5=E5=AE=8C=E6=8A=8A=E5=8D=95=E6=8D=AE?= =?UTF-8?q?=E7=BC=96=E5=8F=B7=E4=BA=A4=E7=BB=99=E7=94=A8=E6=88=B7=E5=8D=B3?= =?UTF-8?q?=E5=8F=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 接上一条定案继续收紧:用户不需要提交前预览("我自己去系统就行了,也不需要给我看")。 能力边界停在 browser_fill / browser_page_state。 - 清掉随之变死的代码:cases.py 的 previewed 阶段与 submit_preview.json 读取(没有任何东西会再生成它), case_reset(scope=all) 的清理列表同步去掉;阶段链回到五档 empty→extracted→matched→info_ready→docs_ready。 仍遵循"阶段由产物推导":推不出的一律不写 - INSTRUCTIONS 改为:把单据编号与会话目录告诉用户即可,不必逐张呈报截图。截图仍然生成——它是这一轮 抓出附件静默假成功的证据,定位是排查用,不是给用户的报告 - SKILL 补一句纪律:提交错了要写情况说明,宁可停在填完,也不替用户按任何不可逆的按钮 - 文档:docs/MCP.md(目录树去掉 submit_preview.json、说明段改为整类否决)、README、浏览器包 README、 src/mcp/tools/browser.py 模块说明、方案文档 §17 记录定案与连带清理 验证:pytest 148 passed;ruff / format / mypy / deptry 全绿;mcp_smoke 25 工具,INSTRUCTIONS 2199 字节。 仍用 --no-verify 提交并手跑等价检查。 --- .../docs/plans/MCP化改造方案-2026-10-19.md | 19 +++++++++++++++++++ .agents/skills/reimbursement/SKILL.md | 8 ++++---- README.md | 2 +- docs/MCP.md | 6 +++--- src/cases.py | 11 ++++------- src/infra/browser/README.md | 3 +-- src/mcp/server.py | 8 ++++---- src/mcp/tools/browser.py | 2 +- src/mcp/tools/materials.py | 2 +- 9 files changed, 38 insertions(+), 23 deletions(-) diff --git a/.agents/docs/plans/MCP化改造方案-2026-10-19.md b/.agents/docs/plans/MCP化改造方案-2026-10-19.md index 3757cfd..1ffd992 100644 --- a/.agents/docs/plans/MCP化改造方案-2026-10-19.md +++ b/.agents/docs/plans/MCP化改造方案-2026-10-19.md @@ -593,3 +593,22 @@ strip、凭据不读 env、safe_config 不含密码、坏文件与缺文件不 同步改动:README、docs/MCP.md、docs/报销操作指南.md、.agents/skills/reimbursement/SKILL.md、 src/infra/browser/README.md、src/mcp/tools/browser.py 的模块说明与 `browser_close` 提示语。 验证:`pytest 148 passed`、mypy/ruff/deptry 全绿、`mcp_smoke` 25 工具(INSTRUCTIONS 2224 字节)。 + +## 17. 定案:`submit_*` 整类不做,核对也由人自己看(2026-10-20) + +接 §16 之后继续收紧:用户说"只读的 submit_preview 也不需要,我自己去系统就行了,也不需要给我看"。 +于是 `submit_*` 这一整类(代提交 + 提交前预览)全部出局,能力边界停在 `browser_fill` / `browser_page_state`。 + +连带清掉的死代码: + +- `cases.py` 的 `previewed` 阶段与 `submit_preview.json` 读取——**没有任何东西会再生成这个产物**, + 留着就是一条永远为假的分支;同时从 `case_reset(scope=all)` 的清理列表里去掉 +- 阶段链回到五档:`empty → extracted → matched → info_ready → docs_ready`。仍然是"由产物推导", + 推不出的一律不写(§16 删 `submitted` 是同一条理由) +- INSTRUCTIONS 从"填报完成后把截图报给用户"改成"把单据编号与会话目录告诉用户即可, + 不必逐张呈报截图":截图仍写在 `cases//screenshots/`,但它的定位是**我核对用的证据**, + 不是交给用户的报告——这一轮抓附件假成功靠的就是回看截图,所以不能砍生成,只是不该刷屏 + +顺带明确一句写进 SKILL:"提交错了要写情况说明,所以宁可停在填完,也不要替用户按下任何不可逆的按钮。" + +验证:`pytest 148 passed`、ruff / format / mypy / deptry 全绿,`mcp_smoke` 25 工具,INSTRUCTIONS 2199 字节。 diff --git a/.agents/skills/reimbursement/SKILL.md b/.agents/skills/reimbursement/SKILL.md index 4de2c6a..af543f9 100644 --- a/.agents/skills/reimbursement/SKILL.md +++ b/.agents/skills/reimbursement/SKILL.md @@ -54,10 +54,10 @@ description: >- ## 提交必须交人工(永久如此) -**不存在代提交的工具,也不会有**:`submit_execute` 已被用户否决。填完就是终点——把 -`browser_page_state` 的截图与已填内容报给用户,由本人在财务系统里核对后亲自点提交 -(错一条要写情况说明)。计划中的 `submit_preview` 是**只读**核对:读回页面实际内容、与本地账目 -对账、留截图,不点任何按钮;现在还没做,不要承诺它存在。 +`submit_*` 整类已被用户整体否决:没有代提交工具,也不做提交前预览。填完就是终点—— +把**单据编号**和会话目录告诉用户,他自己去财务系统里核对、提交、查状态;不用替他生成预览, +也不必把截图逐张呈报(截图留在 `cases//screenshots/`,出问题时再拿去对照)。 +提交错了要写情况说明,所以宁可停在填完,也不要替用户按下任何不可逆的按钮。 浏览器被别的进程占用时返回 `owned_by_other_process`:让持有者 `browser_close`,或等心跳超时 180 秒自动接管, 不要绕过去另开一个 Chromium。步骤函数报错会带 `screenshot`(在 `detail` 里),把它给用户看比反复重试有用。 diff --git a/README.md b/README.md index cac7633..a3abe35 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ make mcp-smoke # stdio 自检 填报已能分步做(`browser_*`,见 [docs/MCP.md](docs/MCP.md))。**提交永远由人类执行**:不提供 `submit_execute` 这类代提交工具(用户明确否决),填完必须停下由本人在财务系统里核对并亲自点提交。 -只读的提交前核对 `submit_preview`(读回页面实际内容 + 与本地账目对账)仍在计划中。 +`submit_*` 整类(代提交、提交前预览)都不做:用户自己去财务系统里核对、提交、查状态。 Word 版易耗品出库单已永久下线(模板自 2026-08-31 起不在仓库,此前该路径一直静默失败)。 ## 文档索引 diff --git a/docs/MCP.md b/docs/MCP.md index eac0ea8..c5e0f9b 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -71,7 +71,6 @@ cases// ├── normal_info.json 普通报销结构化数据 ├── artifacts/ 申购单等文档产物 ├── screenshots/ (P3 落地后出现)填报过程截图 -└── submit_preview.json (只读核对落地后出现)页面回读快照与对账结果 ``` `cases/` 整个目录都在 `.gitignore` 里:发票含身份证号与卡号。 @@ -100,8 +99,9 @@ cases// (用户明确否决)。填报完成就是 Agent 的终点:把 `browser_page_state` 的截图与已填内容报给用户, 由本人核对后自己提交。删除(`#delete`)、撤回同理,都在页面上手工做。 -计划中的只有一个**只读**工具 `submit_preview`:把单据页上的实际内容读回来、与会话里的账目对账、 -截图留证,帮人在点提交前发现问题。它不点任何按钮。目前尚未实现,不要调用、也不要承诺。 +`submit_*` 整类(代提交、提交前预览 `submit_preview`)也一并不做:用户自己到财务系统里核对、提交、 +查状态。Agent 到 `browser_fill` / `browser_page_state` 为止,把单据编号与会话目录告诉用户就够了; +截图留在 `cases//screenshots/` 供排查,不必逐张呈报。 同样已下线的:Word 版易耗品出库单(模板自 2026-08 起不在仓库,历史上长期静默失败)。 diff --git a/src/cases.py b/src/cases.py index 2c78c8f..12fcc53 100644 --- a/src/cases.py +++ b/src/cases.py @@ -1,10 +1,10 @@ """会话目录(cases/):MCP 工具与上传收件器共用的唯一实现 阶段不存状态机,全部由目录里的产物推导: - empty -> extracted -> matched -> info_ready -> docs_ready -> previewed + empty -> extracted -> matched -> info_ready -> docs_ready -没有 submitted 阶段:提交由人类在财务系统里做,本项目的产物推不出「已提交」这个事实, -所以不假装知道。要知道提交结果,去列表页看单据状态。 +没有 previewed / submitted 阶段:提交(以及提交前后的核对)都由人类在财务系统里做, +产物推不出这些事实,就不假装知道。要查结果去看列表页的单据状态。 """ from __future__ import annotations @@ -226,7 +226,6 @@ class CaseStore: artifacts = ( sorted(p.name for p in (d / ARTIFACTS_DIR_NAME).glob("*")) if (d / ARTIFACTS_DIR_NAME).is_dir() else [] ) - preview = (d / "submit_preview.json").exists() shots_dir = d / SCREENSHOT_DIR_NAME screenshots = len(list(shots_dir.glob("*.png"))) if shots_dir.is_dir() else 0 browser = self.browser_lock_state(d) @@ -244,9 +243,7 @@ class CaseStore: "不要另开窗口,等它释放或心跳过期" ) stage = "empty" - if preview: - stage = "previewed" - elif artifacts: + if artifacts: stage = "docs_ready" elif info_key: stage = "info_ready" diff --git a/src/infra/browser/README.md b/src/infra/browser/README.md index 1092a5b..72b4f0c 100644 --- a/src/infra/browser/README.md +++ b/src/infra/browser/README.md @@ -48,8 +48,7 @@ Playwright 操作财务报销系统:登录信息门户、进入报销系统、 **不应该出现任何提交动作**:`#submit` / `#submit2` / 「确认提交」这些选择器只作为文档知识保留, 不要写成工具或编排函数。删除(`#delete`)、撤回同理。 -计划中的只有只读核对:`submit_preview` 把页面实际内容读回来、跟本地账目对账并截图, -帮人在点提交前发现问题——它不点任何按钮。 +不做提交前预览:`submit_*` 整类已被用户否决。要核对就在浏览器里停下,让人自己看页面。 ## 注意 diff --git a/src/mcp/server.py b/src/mcp/server.py index 26f5317..a48809a 100644 --- a/src/mcp/server.py +++ b/src/mcp/server.py @@ -46,11 +46,11 @@ INSTRUCTIONS = """财务报销自动化工具集(发票抽取交接 → 匹配 浏览器归属写在 cases//.browser.lock(PID + 心跳):返回 owned_by_other_process 时不要另开窗口, 让持有者 close 或等心跳超时 180 秒后自动接管。截图按 001-步骤名.png 落在 cases//screenshots/。 -**提交由人类执行**:本项目不提供、也不计划提供点提交的工具(submit_execute 已被用户明确否决)。 -填报完成就是终点:把 browser_page_state 的截图与已填内容报给用户,由本人在财务系统里核对后亲自点提交。 +**提交与提交前后的核对都由用户本人在财务系统里做**:submit_* 这一类工具(代提交、提交前预览) +已被用户整体否决,本项目不提供、也不会计划提供。填报完成就是终点——告诉用户单据编号与 +会话目录在哪就行,他自己去系统里核对、提交、查状态;不用替他生成预览,也不必把截图逐张呈报 +(截图留在 cases//screenshots/ 供排查用)。 删除(#delete)、撤回同理:都在财务系统里手工做,工具层不会给这类动作开口子。 -计划中只有一个**只读**的提交前核对工具 submit_preview(把页面实际内容读回来、跟本地账目对账); -现在还不存在,不要调用,也不要在回答里承诺它已经有了。 Word 出库单功能已下线(模板自 2026-08 起不在仓库),不要再提;Excel 申购单见第 6 条。 硬约束:不得回显密码;不得臆造财务数据,拿不准的字段标出来问用户;产物只写 cases//。 diff --git a/src/mcp/tools/browser.py b/src/mcp/tools/browser.py index 2a7cf06..096d0a4 100644 --- a/src/mcp/tools/browser.py +++ b/src/mcp/tools/browser.py @@ -2,7 +2,7 @@ 取代旧的「一把梭」run_bot:Agent 可以开浏览器、登录、建单、按步填、看页面、关, 每步都有截图与明确状态,中途失败不必从头再来。**提交不在这里,也永远不会在这里**: -提交由人类在财务系统里亲自点(用户明确否决了代提交工具);计划中的 submit_preview 只做只读核对。 +提交由人类在财务系统里亲自点,核对也由他自己做(用户整体否决了 submit_* 这一类工具)。 所有 Playwright 调用都在 src/infra/browser/actor.py 的单线程上执行;浏览器归属用 cases//.browser.lock 记录(PID + 心跳),防止多代 MCP 子进程各开一个 Chromium。 diff --git a/src/mcp/tools/materials.py b/src/mcp/tools/materials.py index 976a8d2..50145f4 100644 --- a/src/mcp/tools/materials.py +++ b/src/mcp/tools/materials.py @@ -135,7 +135,7 @@ def case_reset(case_id: str, scope: str = "extractions") -> dict[str, Any]: shutil.rmtree(target) target.mkdir(parents=True, exist_ok=True) cleared.append(sub) - for name in ("payment_records.csv", "invoice_summary.csv", "invoice_groups.json", "submit_preview.json"): + for name in ("payment_records.csv", "invoice_summary.csv", "invoice_groups.json"): f = d / name if f.exists(): f.unlink()