Files
LWLT-AIBOT/findings.md
T

113 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 当前关键发现
## 2026-08-24 — ERP 异常状态链接刷新加固(已完成)
- 用户描述的是插件长时间待机后的偶发 ERP 未连接告警;当前源码已有 `chrome.alarms` 待机保活,但 `runErpSessionKeepalive()` 在 `session_expired`/`failed` 后只写入 `erpKeepaliveState`,没有恢复动作。
- 当前保活默认首次约 1 分钟、之后每 5 分钟;只查找现有 `https://lwlt.hisy.cc/*` 标签页,执行主世界带 Cookie 的只读 `GET /System/Mainlt.asp`,执行中跳过,不新开标签、不刷新页面、不写 ERP。
- `findExistingErpTabForKeepalive()` 会拒绝非完整加载或非 allowlisted origin;因此长待机后页面已被导航到登录/异常路径、页面注入链路失效或标签页状态异常时,保活只能报告 `skipped/failed/session_expired`,不能自愈。
- `readErpSessionStatus()`、`business-bridge.js` 和 popup 已能展示最近保活状态;平台/业务 operation、Schema 与 Skill 不承载此宿主维护能力,当前活动约束来自 `mappings/lifecycle.mapping.json`。
- 本轮拟保持 fail-closed:仅在无活动 ERP 执行、自动化与保活开关开启且已存在 allowlisted ERP 标签页时,执行受控同源 Mainlt 导航/刷新后再次只读探测;不自动打开新标签、不重交任务、不跨越 ERP 写入边界。
- 已实现 `performErpLinkRecovery()` 与单飞请求锁:异常时对现有 ERP 标签页执行 `chrome.tabs.reload(..., { bypassCache: true })`,等待加载完成,再复用只读 probe;2 分钟冷却内最多一次,执行中二次检查并跳过。
- `readErpSessionStatus()` 在登录/权限页面或页面注入异常时异步触发恢复,不阻塞平台 PING;桥接和平台状态新增 `session_ready`、恢复状态、恢复次数和最近恢复时间。平台在会话异常/恢复中保持告警并阻止新的任务下发,恢复失败要求人工确认,不自动重交。
- lifecycle mapping、插件 README、popup 文案和 `tools/erp-session-keepalive.test.mjs` 已同步;定向保活回归 4/4、生命周期契约 47/47、相关 JS 语法检查通过。
- 扩展已递增到 `0.5.140`;`dist/ltjt-order-assistant-0.5.140.zip` 已通过 `unzip -t`,SHA-256 为 `5d2eceb77a337849bd20bc3e03dea4c86b617c789dd196b8a47bbb33c0da0338`;`0.5.139` 已归档到 `archive/releases/2026-08-24/`。
- 完整门禁最终通过:`check:repo` 9/9、`check`、串行 `test:control-plane` 66/66、`test:legacy` 214/214、`build`、`git diff --check`;未进行真实 ERP 写入、任务重试、外部发送、重启或部署。
## 2026-08-22 — 取消产品/客户来源地强制匹配
- 用户日志中的阻断发生在 `shared_child_order_create` 的浏览器只读阶段,唯一失败项为 `product_customer_source_region`:产品关键字识别出“贵阳”,客户来源信息未命中;客户、OP、销售和领队候选本身均已唯一,且 `no_erp_write=true`。
- 当前 Schema 没有要求产品与客户来源地相同;强制关系来自插件 `inpage.js`、`team-batch-inpage.js` 的运行时来源地兼容门禁,以及客户候选的产品来源地消歧 fallback。
- 活动规则已收敛为:产品和客户分别进行 ERP 唯一解析;客户关键词在精确/唯一包含失败后仍可用客户自身来源词做唯一 fallback,但该 fallback 不读取产品;产品/客户来源地不一致不阻断 `DoInfoJH`、`DoInfoJHs` 或 `DoInfoSPs`。
- 已移除来源地兼容函数和跨产品/客户候选消歧;保留产品候选唯一、客户候选唯一、人员/领队校验、写入前门禁、明确 ERP 回执及 fresh requery。`source-region.js` 仅保留客户侧候选辅助接口。
- 扩展已递增到 `0.5.139`,平台最低版本、生命周期 mapping、运行时版本断言、业务页、mapping、夹具和说明同步;`dist/ltjt-order-assistant-0.5.139.zip` SHA-256 为 `d3ad25b6c22f5269a2fa713441f3b2aebf4cbcf3dff96bdafcef15d29d810353`。
- 上一版 `0.5.138` 已保存在 `archive/releases/2026-08-22/`;本轮未重试用户任务、未写入 ERP、未重启、未部署、未向外部渠道发送消息。
## 2026-08-20 — ERP 会话待机保活实现
- 现有插件已有执行期间的 MV3 worker/page keepalive,但没有空闲时的 ERP 会话请求;`lwlt-confirmation` Skill 明确“定时和外发”不属于业务 action,因此本次只修改插件宿主维护逻辑,不扩展 operation、Schema 或 Skill 输入契约。
- 安全实现采用 `chrome.alarms`,默认首次约 1 分钟、之后每 5 分钟触发;只查找现存且 allowlisted 的 `https://lwlt.hisy.cc/*` 标签页,不自动新开标签、不调用 `tabs.reload()`。
- 页面侧通过主世界 `fetch('/System/Mainlt.asp', { method: 'GET', credentials: 'include', cache: 'no-store' })` 发送轻量只读请求;检查最终 URL、HTTP 状态和登录/权限页面特征,结果写入本地状态并通过插件面板展示。
- 当 ERP 自动化或单独保活开关关闭、执行中任务存在、没有已加载 ERP 标签页或权限不可用时,保活跳过/失败且不触碰业务写入;浏览器或操作系统休眠会延迟 alarm,不能保证休眠期间持续请求。
- 扩展已递增到 `0.5.138`,同步 `manifest.json`、平台最低版本、生命周期 mapping、页面运行时版本和定向回归;版本化 ZIP SHA-256 为 `751dfe6022d61c7388c602089c703b88a9bfeebf67f81edacea1555545d7bcd0`。
## 2026-08-19 — AgentBus 无状态消息但任务成功(诊断进行中)
- 已确认本轮是只读诊断;工作区存在大量既有未提交改动,不能回退或覆盖。
- 当前尚未取得该次成功任务的任务 ID、渠道 ID 或运行时发送日志;先以当前契约、源码、测试和已有控制面证据定位确定性条件。
- 待核对:任务状态事件生成、回复路由是否持久化、AgentBus 渠道是否启用/握手、accepted/processing/result 是否进入 durable outbox,以及失败/重试语义。
- 登记表列出的 `agent设计规范/businesses/confirmation_export.md` 当前不存在;已转用行为注册表、`lwlt-confirmation` Skill、AgentBus 回复契约和源码作为活动规则来源。
- 当前 `control-plane/README.md` 明确用户侧只持久化发送一次 `task.progress(status=accepted)` 和一次 `task.result`;不会发送“解析完成/等待确认/进入 ERP”的中间进度,因此“processing”不是当前 AgentBus 对外消息。
- `lwlt-confirmation` Skill 与标准 operation Schema 只描述业务解析/执行事实,不承诺定时或主动外发;`lifecycle.mapping.json` 的 `scheduled=false`/`sent=false` 属于业务附件外发字段,不能单独证明 AgentBus 回执已发送或未发送。
- AgentBus 回复契约明确用户侧预期是“受理通知 + 最终返回通知”,但执行阶段、页面打开、提交中和回查等中间状态只进入平台生命周期/审计;因此本次“没有处理消息”首先是契约预期与实现定义不一致,仍需查清 accepted/result 为何也未达渠道。
- 生产数据库渠道走 durable delivery:`AgentBusListener.processInboundTask()` 将 `channel_id`、入站 frame 路由和 accepted payload 一起交给 `TaskService.ingestMessage()`;只有这些路由字段存在时,事务才插入 `agentbus_deliveries(delivery_kind='accepted')`。
- durable 模式下不会直接发送 accepted;listener 每秒/`session.ready` 后 flush outbox。只有 accepted delivery 已标记 `delivered`,且任务进入终态,才会创建 `delivery_kind='result'`;因此 accepted 没投递成功时,result 会被有意延后,而任务本身仍可继续完成。
- 手工操作台/API 任务没有 AgentBus `channel_id`/`agentBusRoute`,`recordAgentBusAcceptedDelivery()` 会直接跳过;该任务可以成功,但不会生成任何 AgentBus delivery。判断“任务来自 AgentBus”必须看 `tasks.source='agentbus'`、`tasks.channel_id` 和入站回执行。
- listener 只有在 `agentBusEnabled`、渠道 `enabled=true`、全局 WebSocket URL/渠道 key/bot address 可用并收到 `session.ready` 后才会发送;连接断开会把 `sending` 释放回 `pending`,失败按退避重试。当前源码测试覆盖 legacy 直发和数据库渠道 durable accepted/result 两种路径。
- `tasks.source` 仅允许 `manual`/`agentbus`,`tasks.channel_id` 可为空;`agentbus_deliveries` 对 accepted/result 做唯一约束,默认 `pending`,失败会重试。数据库设计本身允许“任务已成功但 delivery 未送达”的中间状态。
- `TaskService` 的执行结果路径只更新任务、写 `task_events`/通用 `outbox_events` 并发出内部 `events`;AgentBus result 不是在任务成功事务内直接发送,而由 listener 的 durable flush 后置生成。因此仅看到控制面“任务成功”日志,不等于看到 `outbound_durable_delivery_sent`。
- 最终诊断:`processing` 缺失是设计行为;accepted/result 同时缺失则不是“ERP 任务成功”能够覆盖的成功条件,优先检查该任务是否为 `source=agentbus` 且绑定 `channel_id`,再检查对应 accepted/result delivery 是否存在及其 `pending/failed/delivered` 状态。没有任务 ID 和任务时段日志,无法在“手工任务无路由”与“AgentBus delivery 未投递”之间做历史唯一归因。
- 当前只读 `/health/ready` 探针返回数据库/schema 正常,4 个启用渠道均 `connected=true`、`session_ready=true`;未执行外部发送、任务重试、重启或部署。
- 现场日志已捕获用户刚发的成功任务 `TASK-20260819102429-R13bn8w`:入站 frame 为 `wechat-task-c6540dcf293678ec2d76dfeb#1`,控制面先发送 accepted(delivery `4d447c5b-1012-437c-9e00-2854429a91d3`),后发送 completed result(delivery `e4b3db12-abc2-49b8-b97f-a1674fe876a8`),两条均有 `outbound_durable_delivery_sent`。
- 同一 result frame 之后收到 AgentBus server 的 `accepted-177` 与 `delivered-178` 确认帧;日志没有 `outbound_durable_delivery_failed`、`socket_error` 或 `socket_close` 与该任务相邻。因此“控制面未给 AgentBus 返回”已被排除;“delivered”只能证明 AgentBus WebSocket/网关接收,不证明微信客户端已经渲染。
- 该任务的 `processing` 仍不会发送,这是契约设计;本次 accepted/result 已实际发送却用户不可见,剩余故障域是 AgentBus 网关到微信适配器/会话的下游路由、事件类型渲染或会话展示。
## 2026-08-19 — confirmation_export Chrome host 权限阻断
- 两份用户粘贴日志内容结构相同但任务编号/时间不同;外部 Agent SSE 正常完成,标准 operation 校验通过,action 为 `confirmation_export`,文件类型为 `xingyou-confirm`。
- 两次都在插件浏览器执行阶段阻断,错误完全一致:`Cannot access contents of the page. Extension manifest must request permission to access the respective host.`;`agent_returned=true`、`plugin_dispatch_started=true`、`erp_write_started=false`、`no_erp_write=true`,没有 ERP 写入或导出源回执。
- 当前扩展 `manifest.json` 已递增到 `0.5.137`,仍只允许登记的 `https://lwlt.hisy.cc/*`;`background.js` 用同一固定 origin 查找/打开 ERP tab,随后通过 `chrome.scripting.executeScript` 注入页面脚本。
- `runPageAction()` 的业务动作执行使用主框架;`resolveOperationInErp()` 也使用主框架,但前置/只读探针路径有 `allFrames=true` 调用。Chrome 原始错误没有在源码中硬编码,说明需要区分固定 ERP host、重定向 host 和受限 frame 三种触发条件。
- 对固定 ERP URL 的外部只读 HEAD/重定向探测因连接超时未得到网络证据;不读取秘密配置,不重试外部业务任务。
- 修复已在后台增加 `chrome.permissions.contains` host 前检、tab origin 复核、同源导航竞态的一次安全重试和结构化权限错误;平台桥接状态现在展示 ERP 页面权限,并在权限不可用时阻止新的自动 handoff。
- 定向验证已通过:扩展/平台语法检查、operation plan 40/40、生命周期契约 47/47;`check:repo` 9/9 通过,0.5.137 ZIP 与源码逐文件一致。
## 2026-08-18 — visitor-list XLSX 转换失败日志
- 用户提供的运行记录中,外部 Agent 解析完成并进入插件只读阶段;没有 ERP 写入。
- 最终阻断原因为 `artifact_delivery_failed:visitor_xlsx_conversion_failed:conversion_failed`:游客信息已读取,但移动端 `.xlsx` 派生文件生成失败。
- 系统按 fail-closed 规则没有把原始 HTML-in-XLS `.xls` 回传 AgentBus,因此不能把前面的 `confirmed` 自动化事件当作最终成功;最终任务状态是 `blocked`。
- 当前需要继续核对转换器的输入路径、LibreOffice 调用/退出码、输出发现与 XLSX ZIP/magic 校验;在得到根因前不执行 ERP 重试。
- 已用当前 bundled `soffice` 对脱敏 HTML-in-XLS fixture 复现:输入被识别为 `Writer/Web document`,执行 `xlsx:Calc MS Excel 2007 XML` 时退出信号为 `SIGABRT`,stderr 为 `Unspecified Application Error`;输出目录仅有锁文件和临时文件,没有有效 XLSX。该崩溃是当前 `conversion_failed` 的直接根因候选。
## 2026-08-18 — 散拼母团创建写后回查假阴性修复
- 用户已人工确认本次 2027 年三个母团实际创建成功;本次错误因此确定为写后回查假阴性,而非 ERP 创建失败。
- ERP 成功响应完整返回 3 个团号,精确回查却全部为 `parent_group_not_found`;最终错误为 `erp_result_uncertain`,系统未自动重试。
- 当前 `createSplitParentLive()` 仅在 `groupNumbers.length < dates.length` 或启用事实探针时调用 `reconcileSplitParentByRequest()`。本次 3 个返回团号等于 3 个日期,导致精确回查全部失败时仍跳过按日期兜底。
- 精确回查调用 `searchMainPage()` 时只设置日期范围与团号,没有显式清空客户、产品、游客/领队和状态筛选;列表复用现有文档,因此残留筛选可能隐藏已创建行。
- 修复范围已确认:清空非目标筛选、有限延迟只读重试、任一精确团号未命中即触发按日期兜底;不得再次提交创建请求,持续缺失或歧义仍保持不确定态。
- 当前实现使用 `0/750/2000ms` 三次精确只读查询;每次查询都设置日期与团号,并清空客户、产品、游客/领队和状态筛选。
- 恢复判定现在同时检查保存响应、团号数量、精确回查完整性和事实探针;任一条件需要恢复时调用现有逐日期唯一匹配。回查行合并会让成功的日期兜底结果覆盖先前空结果,但不会覆盖歧义或缺失门禁。
- 定向测试已通过:`split-plan-reconciliation` 14/14;生命周期静态门禁 1/1。测试确认首次全空、第二次可见可收敛,持续缺失会耗尽有限预算并保持未确认,且 `createSplitParentLive()` 仍只有一次 `captureAjaxSubmit()` 保存捕获调用。
- 扩展已递增到 `0.5.135`,当前 ZIP SHA-256 为 `9f49f5529083cb1cc497bfc52c56d0142076394c034552fcdd77e34e70ce7f14`;旧 `0.5.134` 已进入日期归档。
- 完整验证中,本次回查相关代码、legacy 214/214、类型检查、构建、仓库与发布物门禁均通过。控制面唯一失败来自任务开始前已有的游客 XLSX 并行改动:`convertDocumentToXlsx()` 期待 `.xlsx`,测试假 `soffice` 仍只生成 `team.pdf`;本轮未改动该策略或测试。
## 2026-08-18 — visitor-list AgentBus XLSX 交付
- 用户日志中的源文件为 `LW-260816Z-A Visitor.xls`,源大小 9,377 字节,源 MIME 为 `application/vnd.ms-excel; Charset=UTF-8`,并带有独立 SHA-256。
- 历史 ERP 实时采集记录确认 `/System/Business/orders_Visitor.asp` 的响应虽然声明 `application/vnd.ms-excel`,但首字节是 HTML 文档头,属于 HTML-in-XLS wrapper,不是标准 BIFF/OLE `.xls`。
- 桌面 Excel 会兼容解析这种 HTML 表格;手机 Office/微信通常同时检查扩展名与文件签名,遇到“`.xls` 文件名 + HTML 内容”会提示格式不支持。这与用户“电脑能开、手机和微信打不开”的现象一致。
- 插件 `chrome-extension/ltjt-order-assistant/inpage.js` 用 `response.arrayBuffer()` 读取 ERP 响应,再计算 SHA-256/base64;没有文本转码或重写。
- 控制面 `prepareExecutionArtifacts()` 对 `visitor-list` 直接沿用源字节、源文件名和源 MIME;OSS 写入设置 `attachment` disposition 并保留 MIME。当前证据不支持平台或微信链路把文件内容损坏,问题边界在 ERP 源格式。
- 仅把扩展名改成 `.xlsx` 或只改 MIME 不能修复;要在手机/微信打开,必须生成真实 `.xlsx`(保留表格语义)或 PDF。
- 用户已确认双文件方案:原始 `.xls` 仅作为平台源归档,真实 `.xlsx` 作为移动端/AgentBus 交付文件;其他类型仍按 PDF 规则处理。
- `convertDocumentToXlsx()` 识别 HTML-in-XLS,调用 LibreOffice `Calc MS Excel 2007 XML`,校验 ZIP/XLSX magic;转换失败返回 `source_fallback`,任务层对 visitor-list fail-closed,不把原始 XLS 发送给 AgentBus。
- 游客导出报告现在同时记录 `source_artifact_count=1`、`derived_artifact_count=1`、`xlsx_count=1` 和 `delivered_artifact_count=2`;原始 artifact 标记 `artifact_role=source_archive`、`agentbus_visible=false`,XLSX 标记 `artifact_role=mobile_delivery`、`agentbus_visible=true`。
- `reply-contract.ts` 和 `agentbus.ts` 均过滤 `agentbus_visible=false`,因此用户回执文本、普通附件元数据、直连与耐久 AgentBus outbox 都只暴露 XLSX。
- 已用 artifact-tool 导入并检查真实 XLSX:工作表单元格可读取,渲染预览正常,文件头为 ZIP/XLSX;验证使用 LibreOffice 实际转换路径而非仅改扩展名。
- 本轮复现进一步确认:同一 HTML 内容临时文件名为 `.xls` 时,LibreOffice 将其识别为 `Calc document` 并成功生成 6KB 有效 XLSX;临时文件名为 `.html` 时则识别为 `Writer/Web document` 并 `SIGABRT`。修复已将 HTML 源临时扩展名固定为 `.xls`,并让回归测试断言该导入路径。
- 修复后的完整门禁全部通过:`check:repo` 9/9、类型检查、控制面 66/66、legacy 214/214、build、`git diff --check`;面板已重启,新 PID `24717` 监听 `127.0.0.1:8786`,live/ready 与 4 个 AgentBus 渠道均正常。
## 已完成的相关交付
- `confirmation_export` 已按类型交付:`visitor-list` 源 XLS 归档并生成真实 XLSX,AgentBus/微信只返回 XLSX;其余类型 PDF;转换失败回退源文件。
- 已同步控制面、Skill、输出契约、Schema、mapping、插件提示、测试、版本化 Skill/扩展包、运营 DOCX 和发布清单。
- 散拼子单导出二次查询已移除固定“收客中”过滤,仍保留母团/子单/`ddid`/`tid` 唯一核验。
- 上一轮验证全部通过:`check:repo` 9/9、`check`、控制面 61/61、legacy 208/208、`build`、`git diff --check`;Skill 官方校验和 DOCX 15 页渲染检查通过。
## 约束与证据边界
- 当前证据是脱敏的响应摘要(路径、状态、MIME、长度、内容特征、哈希),未保存真实游客资料或完整源文件;不能在本地对用户这一个 9,377 字节样本再运行 `file`/移动端实测。
- 未读取或输出秘密配置;未执行 ERP 写入、重试、重启、部署或外部发送。
- 压缩前完整 findings 已冻结到 [visitor 移动端诊断前 findings](archive/project-history/2026-08-18/findings-pre-compression-visitor-mobile-diagnosis.md);更早历史事实仍在同目录日期归档中。