@whyshu/argent-engine 0.6.0 → 0.7.0

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.
Files changed (68) hide show
  1. package/MANIFEST.json +64 -29
  2. package/engine/VERSION +1 -1
  3. package/engine/core/contracts/argent.contracts.json +1 -1
  4. package/engine/scripts/base-bootstrap.sh +2 -1
  5. package/engine/scripts/billing-mode-daily.sh +2 -1
  6. package/engine/scripts/build-engine-package.mjs +5 -2
  7. package/engine/scripts/capability-packs.lock.json +31 -31
  8. package/engine/scripts/check-billing-mode.sh +2 -1
  9. package/engine/scripts/collect-evidence.mjs +50 -0
  10. package/engine/scripts/customer-readiness.sh +2 -1
  11. package/engine/scripts/daily-report.sh +2 -1
  12. package/engine/scripts/dev-window.sh +2 -1
  13. package/engine/scripts/drill-clean-room.sh +32 -0
  14. package/engine/scripts/final-check.mjs +44 -0
  15. package/engine/scripts/flow-requirements.json +30 -10
  16. package/engine/scripts/flow-timers-plist.sh +2 -1
  17. package/engine/scripts/flow-timers.sh +3 -1
  18. package/engine/scripts/flow_config.py +2 -1
  19. package/engine/scripts/init-ml.sh +5 -2
  20. package/engine/scripts/invoice-table.sh +2 -1
  21. package/engine/scripts/lib/argent-datadir.sh +18 -0
  22. package/engine/scripts/lib/argent_datadir.py +23 -0
  23. package/engine/scripts/poll-fulfillment.sh +4 -2
  24. package/engine/scripts/poll-invoice.sh +4 -2
  25. package/engine/scripts/provider-select.sh +2 -1
  26. package/engine/scripts/release-argent.sh +1 -1
  27. package/engine/scripts/retire-inventory.mjs +40 -0
  28. package/engine/scripts/run-tests.sh +2 -1
  29. package/engine/scripts/s1-audit.mjs +85 -0
  30. package/engine/scripts/usage-anchor.sh +3 -2
  31. package/engine/scripts/wenyao-browser.sh +3 -2
  32. package/engine/scripts/wenyao-login.sh +3 -2
  33. package/engine/scripts/ziniao-keepalive.sh +2 -1
  34. package/engine/scripts/ziniao-post-upgrade-check.sh +2 -1
  35. package/engine/scripts/ziniao-windows.sh +2 -1
  36. package/engine/skills/README.md +86 -0
  37. package/engine/skills/argent-dev/SKILL.md +41 -0
  38. package/engine/skills/internal.json +37 -0
  39. package/engine/skills/lktop-pm/SKILL.md +35 -0
  40. package/engine/skills/lktopapp-dev/SKILL.md +34 -0
  41. package/engine/skills/ml-config-guide/SKILL.md +50 -0
  42. package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
  43. package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
  44. package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
  45. package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
  46. package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
  47. package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
  48. package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
  49. package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
  50. package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
  51. package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
  52. package/engine/skills/ml-invoice/SKILL.md +506 -0
  53. package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
  54. package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
  55. package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
  56. package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
  57. package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
  58. package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
  59. package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
  60. package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
  61. package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
  62. package/engine/skills/pet-dev/SKILL.md +42 -0
  63. package/engine/skills/whyshu-pm/SKILL.md +35 -0
  64. package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
  65. package/engine/version.mjs +1 -1
  66. package/engine/verticals/mercadolibre/flows.json +37 -5
  67. package/package.json +3 -2
  68. package/requirements.txt +5 -0
@@ -0,0 +1,506 @@
1
+ ---
2
+ id: ml-invoice
3
+ name: ml-invoice
4
+ title: 上海文鳐发票流程(M2 · 货代门户集成)
5
+ version: 0.3.1
6
+ category: 产品运营
7
+ tags: [发票, 文鳐, 货代, 门户, 填报, 回写, lip表, DDP唛头]
8
+ difficulty: ⭐⭐⭐⭐
9
+ triggers:
10
+ - pattern: 文鳐|发票|货代|导入订单|导出唛头|大货标签|发票状态|报销单|报关|HS编码|进仓单
11
+ - pattern: my\.ffl-tech\.com|上海文鳐|DDP批量导出唛头
12
+ description: "上海文鳐发票流程技能(Argent on DSH)——M2 首个非鑫中航货代的发票全链路:模板填报(门户版模板、只写第 2/8 行)→ 回写 lip 表「发票文件」+ 置「填写中」→ 门户 my.ffl-tech.com 导入订单/查询/勾选货件号/导出进仓单与唛头/DDP 压缩包改名 → 回写「大货标签」。含字段白名单回写器、写前备份与审计、附件上传必须传 field_id(按名解析会偶发 20008)等踩坑清单。"
13
+ changelog:
14
+ - version: 0.3.1
15
+ date: 2026-10-12
16
+ changes:
17
+ - 并发踩踏(死锁)修复:**每条票一个自己的标签页**(`connect()` 隔离 + `finally` 只关自己那个页),旧实现三条票抢同一个 page ⇒ 死循环(事故:79212271 / 79298694 / 79210359)
18
+ - 新增**每门户一把 flock**(`<数据目录>/wenyao-portal.lock`)+ **有界等待**(默认 300s,`WENYAO_PORTAL_LOCK_TIMEOUT`)+ 抢锁每 15s 打印一行;超时**明确失败**(⛔ 绝不无限等待);粒度 = 本门户(不牵连 FULL/其它流程)
19
+ - 下载目录绑定到**本次跑批自己的 context**(`browserContextId`)+ 每票独立目录 `wenyao-downloads/portal-<货件号>/`,并做下载归属校验(文件名 8 位票号 + 包内容),不符即报错
20
+ - 新增可观测:开始/结束各一行 `[门户] 本次标签页=<id 前 8>|锁=<获得/等待 Ns>|票=<货件号>`(与阶段日志同源)
21
+ - 新增回归 `scripts/tests/wenyao-portal-isolation.test.mjs`(72 项,纯夹具零网络)
22
+ - version: 0.3.0
23
+ date: 2026-10-12
24
+ changes:
25
+ - 材质英文改**三级解析**(① per-SKU 字典 `lip-sku-dict.json` → ② 通用小词典 `materialEn` → ③ 会话传入 `--material-en`);新增显式入口 `--material-en`,③ 命中时**写回字典**(`confirmed:false`,运营可复核)
26
+ - 闸门判据改口径:由「材质英文为空 ⇒ 按报关敏感字段拦」改为「**三级都无法解析**才阻断」,阻断文案指路(请翻译后用 `--material-en` 重跑)
27
+ - 明确 **HS(人工,红线) vs 材质英文(三级 + LLM 可译)** 的分界;影子对比器不再把 `S` 列当结构常量(改按 `perRow` 放行)
28
+ - 记入两份字典的存在/路径/优先级/写回规则(此前技能与文档**一个字都没提**,链路上没人知道要查它)
29
+ - version: 0.2.1
30
+ date: 2026-09-15
31
+ changes:
32
+ - 新增「切主放行闸门」:影子产物必须过 `scripts/wenyao_shadow_diff.py`(自动逐格比对,9 项回归断言)
33
+ - 更正 HS 口径:**人工维护,agent 不预填/不写候选/不自动确认**(同步 docs/ml-dev-failure-sop.md 与 ml-invoice-automation.md)
34
+ - version: 0.2.0
35
+ date: 2026-09-15
36
+ changes:
37
+ - 补「当前进度」:链路**已在本机跑通(两票闭环)但未测试完毕、未部署客户机**(部署时机由用户决定)
38
+ - 修正 ⑨「回写大货标签」状态:待落地 → **已落地**(76637122 实测回填成功)
39
+ - 记入编排器 `wenyao_run.py`(14 阶段 / `--shadow` / `--from` / 阶段 JSONL)与门户模块 `wenyao_portal.py`
40
+ - version: 0.1.0
41
+ date: 2026-09-11
42
+ changes:
43
+ - 首版:M2 首个货代「上海文鳐」发票流程(门户 my.ffl-tech.com)九阶段设计与 P0/P1 落地
44
+ - P0 填报器收口:**只写第 2、8 行**;模板以**门户下载版**为准,运营版仅作填法说明
45
+ - P1 回写器收口:字段白名单 + 记录白名单 + 写前备份 + 写后验收 + 审计 JSONL + 幂等去重
46
+ - 踩坑入册:附件上传**必须传 field_id**(按名解析偶发 20008,抖动 ~20%)、`--file` 必须 cwd 内相对路径
47
+ ---
48
+
49
+ # 上海文鳐发票流程
50
+
51
+ > 适用:美客多(MercadoLibre)FULL 货件走**货代「上海文鳐」**时的发票填报 → 门户导入 → 唛头导出 → 回写。
52
+ > 这是 **M2 首个非鑫中航货代**:文鳐**有自己的门户**,不是"多一个模板路由",而是**新门户集成**。
53
+
54
+ ## 当前进度(2026-09-15 更新)
55
+
56
+ > **结论:链路已在本机跑通(两票闭环),但尚未测试完毕,也未部署到 Huangwei 客户机**
57
+ > —— 部署时机由**用户**在测试彻底跑通后决定。(本机 `~/.dsh/argent/PAUSE_POLL` / `PAUSE_INVOICE`
58
+ > 是给鑫中航/FULL 流程用的,与文鳐无关。)
59
+
60
+ | 阶段 | 内容 | 状态 |
61
+ |---|---|---|
62
+ | **P0** | 门户版《发票模版》列映射真源 + 填报器(**只写第 2、8 行**)+ 单测;附件下载绕法 | ✅ 落地 |
63
+ | **P1** | 回写器 `scripts/wenyao-invoice-writeback.mjs`:字段白名单 + 记录白名单 + 写前整行备份 + 写后读回 + 审计 JSONL + 幂等去重(47 项守卫单测) | ✅ 落地 |
64
+ | **P2** | 门户只读侦察(选择器/接口真源 + 港前仓冲突实证)+ **真导入实测通过** | ✅ 落地 |
65
+ | **编排器** | `scripts/wenyao_run.py`:14 阶段(P1 路由 → P14 收口)+ `--shadow` 影子模式 + `--from` 断点续跑 + 每阶段 JSONL(`~/.dsh/argent/wenyao-logs/`) | ✅ 可用 |
66
+ | **门户模块** | `scripts/wenyao_portal.py`:登录态 / 按货件号查询 / 勾选 / 导入 / 导出进仓单·唛头 / 任务面板取件 / 解压改名;**并发安全**(一票一页 + 每门户有界 flock) | ✅ 可用 |
67
+ | **并发踩踏修复** | 2026-10-12 事故(三条票 `79212271/79298694/79210359` 抢同一 page ⇒ 死循环)⇒ 一票一页 + 门户阶段串行锁 + 下载归属校验;回归 `scripts/tests/wenyao-portal-isolation.test.mjs`(72 项) | ✅ 落地 |
68
+ | **端到端** | **76636923** 全链路收口「已完成」;**76637122** 完整编排跑通(导入 WY535119 → 勾选 → 进仓单 → 唛头 → DDP 取件改名 → 回写 `大货标签` → 收口) | ✅ 两票闭环 |
69
+
70
+ - **测试**:`ml-invoice-skill` 73 项 + 另三套 72/67/6,全绿。
71
+ - **踩坑**:`references/pitfalls.md` 已 14 条(含 10h 下载临时文件被 Playwright 清掉、10i 导出面板遮罩挡住后续点击)。
72
+ - **未做**:批量多票连跑、失败自动重试的长期验证、**客户机部署**(等测试跑通后由用户决定)。
73
+ > 📐 本流程将被纳入**新的 Agent 化架构**(角色掌舵 + subagent 并行 + job 脚本;文鳐为试点)——
74
+ > 设计与决策见 `docs/ml-agent-architecture.md`(触发/分诊/并行/落地步骤)。
75
+ > 本文档仍描述**当前已实现**的脚本链路,两者冲突时以架构文档的决策为准。
76
+
77
+ - **边界不变**:HS 编码等**报关敏感字段由运营/人工维护**(见 `docs/ml-invoice-automation.md`,决策 2026-08-19)
78
+ —— ⚠️ **agent 不预填、不写候选、不自动确认**;缺 HS 只在行内提示运营(2026-09-15 更正了
79
+ `docs/ml-dev-failure-sop.md` 里「HS 候选预填」的旧表述)。
80
+ ⚠️ **这条红线只管 HS 编码(`Z` 列)**:**材质英文(`S` 列)不在红线内** —— 它按下面的三级解析
81
+ 走,允许 LLM 译(政策A)。把「不预填」套到材质英文上,就是 2026-10-12 两条票被误阻的成因。
82
+
83
+ > ### 🔴 口径(2026-10-13,用户拍板):**表里填什么就照填入发票**
84
+ >
85
+ > **lip 表内的「材质」「HS 编码」= 运营已维护的结论** ⇒ 填报器**照抄进发票**(材质英文仍走三级解析)。
86
+ > ⛔ **不得**因为「材质类别与 HS 章看起来不一致」(例:材质=不锈钢 而 HS=3924900000 属第 39 章塑料)
87
+ > 而**停下等核对** —— 报关口径由运营负责,流程不代判、不阻断。
88
+ > 上面那条红线的**准确含义**只有一句:**表内 HS 为空时,agent 不得编造/预填**;表内**有值**即视为已维护。
89
+ > 若确有疑问:**在台账/卡片里留一句提示即可,⛔ 不得阻塞真跑**(2026-10-13 两条票 79389664/79389649
90
+ > 就是被这条误解卡住的:影子0/闸门0 全绿,却因"材质↔HS"停住)。
91
+
92
+ ### 材质英文(`S` 列)三级解析 —— 与 HS 的分界(2026-10-12 事故固化)
93
+
94
+ 合同(唯一权威 = `references/template-fill-contract.md`,由映射真源生成):`S` = `{材质},{材质英文}`。
95
+ **材质英文走三级解析**,唯一实现 = 填报器的 `resolve_material_en()`
96
+ (`scripts/wenyao_fill_template.py`;闸门与填充**共用这一份判据**):
97
+
98
+ | 级 | 来源 | 命中后 | 写回字典? |
99
+ |---|---|---|---|
100
+ | ① | **per-SKU 字典** `~/.dsh/argent/lip-sku-dict.json`(键 = `SKU`;**优先 `confirmed:true`** 的「材质英文」) | 直接用 | ⛔ 不写(答案已固化) |
101
+ | ② | **通用小词典** = 映射真源 `scripts/wenyao-template-map.json#materialEn`(8 条:塑料/牛津布/不锈钢/硅胶/玻璃/纸/木/金属) | 直接用 | ⛔ 不写 |
102
+ | ③ | **会话传入** `--material-en "<英文>"`(或行内 `材质英文`;**政策A:LLM 译出的可以直接用**) | 用它填 `S` 列 | ✅ 写回字典 |
103
+
104
+ > ⚠️ **D1 事故(2026-10-12)**:这两份字典在技能与文档里**一个字都没提** ⇒ 链路上没人知道要查它,
105
+ > 于是 `HW-MX-BD6-013`(字典里早有 `Iron/confirmed:true`)也被拦。**① 必须查**。
106
+
107
+ **怎么写回(③ 定音时,落到 `lip-sku-dict.json`)**:
108
+
109
+ ```json
110
+ {"<SKU>": {"材质英文": "<英文>", "confirmed": false,
111
+ "来源": "LLM 译 <YYYY-MM-DD>(政策A:材质英文可直接用,HS 不动)", "备注": ""}}
112
+ ```
113
+
114
+ - 合并进已有条目(⛔ 不丢 `品名英文`/`海关编码`);**原子写**(`tmp` + `os.replace`);同 `SKU` 同值 ⇒ **幂等跳过**;
115
+ - **⛔ 不覆盖 `confirmed:true`**;字典里已有 `confirmed:true` 条目但缺材质英文 ⇒ 只留痕、不写回(不把已确认条目降级);
116
+ - **同名不同值 ⇒ 冲突交人**:保留字典值、**不写回**、阻断重跑(与 `flow_case` 的「不覆盖 + 记冲突」同一口径);
117
+ - 审计:`~/.dsh/argent/material-en/audit.jsonl`;阶段台账:编排器 P0 行打印的 `log=<路径>`(`~/.dsh/argent/wenyao-logs/run-<货件号>-<时间>.jsonl`)。
118
+
119
+ **三级都没命中 ⇒ 阻断**(填报器 P3 **不落盘**,闸门 exit ≠ 0),文案**指路**而不是泛泛说红线:
120
+
121
+ ```
122
+ 材质「<中文材质>」未能在 SKU 字典 / 小词典命中 ⇒ 请翻译成英文后用 --material-en 重跑
123
+ (政策A:材质英文允许 LLM 译;HS 编码仍必须人工维护)
124
+ ```
125
+
126
+ ⇒ 执行者的动作就是:**把该材质译成英文**(LLM 或人工)→ 原命令加 `--material-en "<英文>"` 重跑。
127
+ 影子跑**不写字典**(只打印写回计划),真跑 `--yes` 的 P3 才写回。
128
+
129
+ 另一份字典 `<数据目录>/lip-product-dict.json`(`~/.dsh/argent/lip-product-dict.json`)是**按中文品名**
130
+ 建的翻译缓存(品名英文/材质英文/**HS 候选**),与 ① 那份**按 SKU** 的字典是两回事:
131
+ 本填报链只读 `lip-sku-dict.json`;**HS 候选一律不预填**(见上一条红线)。两份字典的既有读写方都在
132
+ `scripts/invoice_lip_runner.py`(另一条发票链),**改路径/条目形状要两处一起改**。
133
+
134
+ ### 切主放行闸门(每票必过)
135
+
136
+ 影子推演只是"没有副作用地跑一遍",**不代表产物可信**。切主前必须做**自动逐格比对**:
137
+
138
+ ```bash
139
+ # 1) 影子跑(只跑本地无副作用阶段,产出 xlsx)
140
+ python3 scripts/wenyao_run.py --shipment <货件号> --english-name "<英文品名>" --shadow
141
+ # 若 P3 报「材质「X」未能在 SKU 字典 / 小词典命中」⇒ 译成英文后加 --material-en "<英文>" 重跑同一条命令
142
+
143
+ # 2) 与**已验收通过的票**(黄金样本)逐格对比 —— 这是放行判据
144
+ python3 scripts/wenyao_shadow_diff.py --shadow <新票影子.xlsx> --golden <已验收票.xlsx>
145
+ # ✅ 退出码 0 = 可放行切主 ; ❌ 1 = 有意外差异(不要切主) ; 2 = 参数/文件问题
146
+ # 加 --json 供脚本消费;--allow/--deny 可手工覆盖列
147
+
148
+ # 3) 放行后才真跑
149
+ python3 scripts/wenyao_run.py --shipment <货件号> --english-name "..." \
150
+ --material-en "<材质英文,仅③命中时给>" --yes
151
+ ```
152
+
153
+ 判据(默认从 `scripts/wenyao-template-map.json` 推导,**不用手写名单**):
154
+
155
+ | source | 含义 | 允许不同? |
156
+ |---|---|---|
157
+ | `const` | 固定值(关联单号/币种/是否带电…共 10 列) | ❌ **必须完全相同**(不同 ⇒ 说明模板/结构被动过) |
158
+ | `field` / `llm` / `calc` / `storemap` / `image` | 随货件/行变化的值(共 22 列) | ✅ 允许不同 |
159
+ | `const` + `perRow: true` | 常量模板但**取值随行变化**(`S` 产品材质,共 1 列) | ✅ 允许不同(判据在填报器的三级解析 + 未命中即阻断) |
160
+
161
+ ⚠️ `perRow` ≠ "这一列不用管":材质英文的判据在**填报器**(三级解析),三级都解析不出来时填报器
162
+ **直接阻断、不落盘** ⇒ 能走到对比器的产物一定带非空英文。
163
+ (2026-10-12 实测:`S8` 因材质与黄金样本不同被判"意外差异" ⇒ 闸门 exit 1 误阻两条票。)
164
+
165
+ 回归测试:`node scripts/tests/wenyao-shadow-diff.test.mjs`(放行/拦截/文件缺失三种退出码 + `perRow` 放行)。
166
+
167
+ ## 0. 三条不可动摇的边界
168
+
169
+ | 边界 | 内容 |
170
+ |---|---|
171
+ | **凭据** | 只读本机 `~/.dsh/argent/wenyao.env`(600)。**禁止**写进仓库、日志、任务会话评论、飞书消息。 |
172
+ | **第三方生产门户** | `my.ffl-tech.com` 是**对方的生产系统**,登录/导入/导出都留真实痕迹 ⇒ **每次门户访问先经用户确认**;必须支持「人工过验证 → 继续」的介入点。 |
173
+ | **生产表写入** | 只写用户**指定记录**的**白名单字段**;写前备份、写后逐字段 diff、审计留痕。禁止在本机做"自动拾取"(本机是测试机,会与客户机抢同一批行)。 |
174
+
175
+ ## 1. 机器角色(先确认你在哪台机)
176
+
177
+ - **本机(chester Mac)= 开发/测试机**:`PAUSE_POLL` + `PAUSE_INVOICE` 已置位,**不抢生产行**。手工试跑要单独执行 runner。
178
+ - **生产 = Huangwei 客户机 Mac**:真实 FULL + 发票流程跑在那里。
179
+ - 本机对本仓库的改动要 **commit + `git push whyshu main`**(远端名是 `whyshu`,不是 `origin`),再在安装克隆 `~/.argent-dsh` `git pull`,技能同步到 `~/.dsh/skills/`。
180
+
181
+ ## 2. 数据在哪
182
+
183
+ | 用途 | 位置 |
184
+ |---|---|
185
+ | **生产业务表(含发票字段)** | 客户 Base `K8zjbMB9TaGKADsruYRcuKPFnAe` / 表 `tblj6CBX0JQWDLhh`(李培-箱唛工作表,435 行 / 43 字段) |
186
+ | 表所在 Base | 名称「跨境电商分岗每日工作汇报系统」,`https://fcnr02k4m3xq.feishu.cn/base/K8zjbMB9TaGKADsruYRcuKPFnAe` |
187
+ | 访问身份 | profile `lip-customer`(bot `cli_aaf7cafb66b85ccc`),**只读 + 只改用户指定数据** |
188
+ | dsh 侧正式发票表 | Base `TJrmbxuIyauNqrsJL9FclLfbnhc` / FULL任务 `tbl4vDxpCa71YLpe`(鑫中航 M1 在用,**本流程不动它**) | ⛔ 该表属旧 xzh 单表方案,2026-09-17 已废弃(勿作现行依据)
189
+ | 本地工作目录 | `~/.dsh/argent/wenyao-{template,downloads,output,fixtures}/`、`invoice-writeback/{backup,audit.jsonl}` |
190
+
191
+ **发票相关字段**:`发票状态`(未开发票/待勾选/待生成/填写中/已上传/标签已下载/已完成/失败/跳过)、`发票就绪`(checkbox)、
192
+ `发票文件`(附件)、`大货标签`(附件)、`发票错误信息`(文本)、`HS编码`、`材质`、`产品图`、`当前步骤`。
193
+
194
+ ## 2.5 编排器(一条命令跑完整链路)
195
+
196
+ ```bash
197
+ # 影子模式(**零副作用**:只读飞书 → 生成文件 → 自检 → 打印后续计划)
198
+ python3 scripts/wenyao_run.py --shipment 76637122 \
199
+ --english-name "<英文品名>" --shadow
200
+
201
+ # 真跑
202
+ python3 scripts/wenyao_run.py --shipment 76637122 --english-name "<英文品名>" --yes
203
+
204
+ # 材质英文三级都没命中时(P3 会这样阻断)⇒ 译成英文后加 --material-en 重跑(真跑会写回字典)
205
+ python3 scripts/wenyao_run.py --shipment 76637122 --english-name "<英文品名>" \
206
+ --material-en "iron" --yes
207
+
208
+ # 断点续跑(例如导入/导出已完成,只补下载与回写)
209
+ python3 scripts/wenyao_run.py --shipment 76637122 --english-name "<…>" --yes --from P12
210
+ ```
211
+
212
+ - 阶段:`P1 路由 → P2 取数 → P3 填报 → P4 自检 → P5 回写① → P6 登录 → P7 查重 → P8 导入 →
213
+ P9 勾选 → P10 进仓单 → P11 唛头 → P12 取件 → P13 回写② → P14 收口`
214
+ - 每阶段产出 JSONL 事件:`~/.dsh/argent/wenyao-logs/run-<货件号>-<时间>.jsonl`(供上板/审计)。
215
+ - 门户动作封在 `scripts/wenyao_portal.py`(选择器/接口全部来自实测)。
216
+ - **影子模式 = 编排层的 dry-run**:推演结果应与真实链路逐步一致(本次用"与已验证票逐格对比"的方式核对通过)。
217
+ - 2026-09-11 实测:**76637122** 一票从影子推演到真跑、收口全程走通(43 字段变 4 个)。
218
+
219
+ ### 2.6 并发纪律:**一票一页 + 门户阶段串行锁**(2026-10-12 事故固化)
220
+
221
+ > ⚠️ **事故(用户亲历)**:三条发票票 `79212271 / 79298694 / 79210359` 被**同时派发**后
222
+ > **抢同一个 Chrome 标签页**,互踩到"永远跑不完"(最后靠运气交叉跑完)。
223
+ > **根因**:`wenyao_portal.connect()` 每个并发执行者都**复用同一个既有 page** ⇒
224
+ > 导航/点击互相打断 ⇒ 死循环形态。**次生竞争**:`Browser.setDownloadBehavior` 是**浏览器级**的,
225
+ > 并发跑批会互相覆盖下载目录(产物落到别人目录 / 下载事件错配)。
226
+ > **口径(用户判定)**:**每条票应当用自己的一个标签页**。
227
+
228
+ **两条硬规矩**:
229
+
230
+ 1. **一票一页(隔离)**:`wenyao_portal.connect()` 默认在门户所在 context 里 `context.new_page()`
231
+ 新建**只属于本次跑批**的标签页,返回可解包的 `PortalSession`(`(browser, page, close_fn)`);
232
+ 调用方**必须** `finally: sess.close()` —— **只关自己那个页**(⛔ 不关别人/用户自己开着的页)。
233
+ ⛔ 保留既有语义:找不到门户 Chrome ⇒ 明确报错并提示 `bash scripts/wenyao-browser.sh start`。
234
+ 2. **门户阶段串行锁(有界等待)**:`P6→P14` 包在 `wenyao_portal.portal_lock()` 里 ——
235
+ **每门户一把 flock**(`<数据目录>/wenyao-portal.lock`,与 `scripts/lib/argent_filelock.py` 同源;
236
+ 粒度 = 本门户/CDP profile,⛔ **不是**全局锁,不牵连 FULL 与其它流程):
237
+ - 覆盖**会改门户状态或产出文件**的阶段(至少 P8 导入 / P9 勾选 / P10·P11 导出 / P12 取件下载);
238
+ - **有界等待**默认 300s(`WENYAO_PORTAL_LOCK_TIMEOUT` 可配),抢锁期间每 15s 打印一行
239
+ `⏳ 等待其他发票跑批释放门户(已等 Ns)`;
240
+ - 超时 ⇒ **明确失败**「门户被占用/等待超时,避免并发踩踏;请稍后重跑或提高超时」——
241
+ ⛔ **绝不允许无限等待**(无限等待正是本次的死循环形态);锁不可用同样 fail-closed(不无锁继续)。
242
+ - 锁在**开浏览器之前**拿 ⇒ 超时连页都不开("不进入门户阶段")。
243
+ 3. **下载归属**:在**持锁**前提下设置下载行为,并尽量把它收到**本次跑批自己的 context**
244
+ (`browserContextId`);每票用自己的下载目录 `wenyao-downloads/portal-<货件号>/`;
245
+ 下载后校验 `suggestedFilename`(8 位票号;8 位**日期**不算票号)+ 包内容
246
+ (DDP 包内 = `<运单号>-<货件号>_mark.pdf`)**归属本票**,不符即报错(⛔ 不静默用错文件)。
247
+ 4. **可观测**:每次跑批开始/结束各打一行(与阶段日志**同源**,都走 `wenyao_run.log()`):
248
+ `[门户] 本次标签页=<targetId 前 8 位>|锁=<获得/等待 Ns>|票=<货件号>`。
249
+ 5. **回归**:`node scripts/tests/wenyao-portal-isolation.test.mjs`(72 项:隔离/只关自己/锁串行/
250
+ 有界等待/超时非零退出/下载归属/单跑不回归/真 home 零增量,纯夹具零网络)。
251
+
252
+ ## 3. 九阶段(工作流真源)
253
+
254
+ ```
255
+ ① 模板路由(按货代选模板)
256
+ ② 填报生成发票(门户模板,只写第 2、8 行)
257
+ ③ 回写「发票文件」+ 置「发票状态=填写中」 ← 已落地并在 76636923 上验证通过
258
+ ④ 登录文鳐门户(人工过一次滑块验证,复用持久 profile)
259
+ ⑤ 导入订单(上传 ② 生成的 xlsx) ← 2026-09-11 真导入实测通过(首票 WY535082)
260
+ ⑥ 查询刷新 + 勾选对应货件号
261
+ ⑦ 导出进仓单(港前仓=华南仓)+ 导出唛头 + 导出文件
262
+ ⑧ 下载「DDP批量导出唛头」压缩包 → 解压 → 按规则改名
263
+ ⑨ 回写「大货标签」(附件)+ 状态流转 ← ✅ 已落地(76637122 实测回填成功)
264
+ ```
265
+
266
+ 每个阶段走原生 `workflow/phase` → observe 投影到任务会话(失败停在哪个 phase 一眼可见)。
267
+ **影子模式先行**:workflow 只记 phase、不改流程、不写表、不开浏览器,与手跑比对一致后再切主。
268
+
269
+ ## 4. 阶段 ② 填报契约(P0 已落地)
270
+
271
+ - **模板真源 = 门户「导入订单」弹窗里下载的《跨境电商-下单模板.xlsx》**;
272
+ 运营给的《上海文鳐发票模版.xlsx》**只是填法说明**。
273
+ 两者有列位差异(门户版多 `*是否投保`,`仓库编码` 因此落在 **AG** 列)⇒ **一律以门户版列序为准**。
274
+ - **只写两行**:第 2 行(顶部块 8 格)+ 第 8 行(明细 A..AG)。
275
+ 其余是模板本体:**第 5-6 行是门户自带的填写示例,门户要求不删 ⇒ 不动**;表头/声明行/校验一律不碰。
276
+ - **不用模板自带的预填值**:门户版 E2/F2/G2/H2 预填 `CNYTN/USLGB/东莞集货仓/墨西哥`,
277
+ 值全部按运营版说明重写 ⇒ **写值前先清空 A2..H2**。**港前仓填 `华南仓`**(覆盖模板自带的 `东莞集货仓`),
278
+ 并**不必管 G2 的单元格校验**(其下拉只列 `东莞集货仓`,保留原校验不动)。
279
+ - 方数用**精确值**,不做 Excel 式四舍五入。
280
+ - 产品图按**浮动图片**插到 **`AA8`**(数据行「*产品图片」列;锚点取自映射 `imageAnchor`,不要硬编码)。
281
+ **必须等比缩放到装得进单元格**(AA 列 ≈96px 宽、第 8 行 ≈53px 高;写死像素宽度会溢出);
282
+ openpyxl 插图需要 **Pillow**。
283
+ ⚠️ 门户模板**第 3 行是隐藏行**,部分预览器会把隐藏行折叠导致**显示行号比真实行号小 1**
284
+ (真实第 8 行显示成第 7 行)——判断行列一律以真实行号为准。
285
+
286
+ 命令:
287
+
288
+ ```bash
289
+ /tmp/pw-venv/bin/python3 scripts/wenyao_fill_template.py \
290
+ --template ~/.dsh/argent/wenyao-downloads/跨境电商-下单模板.xlsx \
291
+ --row ~/.dsh/argent/wenyao-fixtures/<货件号>.json \
292
+ --out ~/.dsh/argent/wenyao-output/<货件号>-....xlsx \
293
+ --english-name "<英文品名>" --image <产品图.png> --today <YYYY-MM-DD> [--dry-run]
294
+ ```
295
+
296
+ 逐列映射真源:`scripts/wenyao-template-map.json`(33 列 A..AG,`source ∈ field|calc|const|llm|image|storemap`)。
297
+ **可读版列契约见 `references/template-fill-contract.md`** —— 由 `scripts/wenyao_skill_contract.py` 从上述真源生成,
298
+ **勿手改**;改映射后跑 `python3 scripts/wenyao_skill_contract.py` 重新生成(`--check` 可校验是否漂移)。
299
+
300
+ ## 5. 阶段 ③ 回写契约(P1 已落地)
301
+
302
+ ```bash
303
+ # ③ 发票文件(xlsx)
304
+ node scripts/wenyao-invoice-writeback.mjs \
305
+ --record-id recXXXX --allow-record recXXXX \
306
+ --file ./<填报件>.xlsx --status 填写中 [--yes] # 默认 dry-run,--yes 才落库
307
+
308
+ # ⑨ 大货标签(唛头 pdf/zip)—— 用 --field 指定目标附件字段(白名单内),不动状态
309
+ node scripts/wenyao-invoice-writeback.mjs \
310
+ --record-id recXXXX --allow-record recXXXX --field 大货标签 \
311
+ --file ./运单-<货件号>-<箱数>箱-<中文品名>.pdf --status 填写中 [--yes]
312
+ ```
313
+
314
+ **字段级文件类型**:`发票文件` 只收 `.xlsx/.xls`;`大货标签` 只收 `.pdf/.zip`(`FIELD_FILE_PATTERN`)。
315
+
316
+ 脚本守卫(纯函数,单测 `scripts/tests/wenyao-writeback.test.mjs`):
317
+
318
+ - **字段白名单**:判据**只有一份** —— 写手 `scripts/wenyao-invoice-writeback.mjs` 的 `ALLOWED_FIELDS`(`argent writeback` 的守卫);
319
+ ⚠️ 本文刻意**不复述字段名**(复述=第二份判据,见 `core/contracts/` 的漂移门禁)。附件字段**不许当普通 CellValue 写**。
320
+ - **记录白名单**:必须显式 `--allow-record`。
321
+ - **状态机**:`已完成/已上传/标签已下载` 是终态,回写需 `--force`;同值 = noop。
322
+ - **收口口径(2026-09-11 全表实证)**:实测该表 440 行里 `发票状态` 只用过
323
+ `空 355 / 跳过 51 / 已完成 28 / 待勾选 5 / 填写中 1`——**唯一的终态就是 `已完成`**
324
+ (没有任何 `标签已下载`/`已上传`)。已完成的文鳐行画像 = `发票状态=已完成` +
325
+ `发票错误信息=空` + `发票就绪` **未勾**。
326
+ ⇒ **收口 = 推 `发票状态=已完成` + `--clear-error` 清掉过时错误信息**:
327
+ ```bash
328
+ node scripts/wenyao-invoice-writeback.mjs --record-id <rec> --allow-record <rec> \
329
+ --field 大货标签 --file ./运单-….pdf --status 已完成 --clear-error --yes
330
+ ```
331
+ - **写前整行备份**(字段名→值,43 字段)→ `~/.dsh/argent/invoice-writeback/backup/`。
332
+ - **写后读回验收** + **审计 JSONL**(含 old/new/file_token,**失败路径也写审计**)。
333
+ - **幂等**:同名同大小已在格内 ⇒ 跳过上传并自动移除重复份。
334
+
335
+ **写完必做逐字段 diff**(对备份比对),确认只有目标字段变化。
336
+ 76636923 首次实写结果:43 字段**恰好只变 2 个**(`发票状态` 跳过→填写中、`发票文件` 空→1 份),
337
+ `发票就绪` 保持未勾、`大货标签` 保持空。
338
+
339
+ > ⚠️ `跳过` **必须算作"可开始"**:老流程曾给文鳐行写死
340
+ > `发票错误信息 = "货代上海文鳐未接入发票自动化(首期仅鑫中航-美墨)"` 且状态置 `跳过`,
341
+ > 文鳐接入后这些行就是待办,不能被"跳过"挡掉。文鳐待办行 = `FULL状态=已完成 ∧ 发票状态∈{跳过,空}`(实测 19 行)。
342
+
343
+ ## 6. 阶段 ④-⑧ 门户操作
344
+
345
+ 见 `references/portal-flow.md`:登录/验证码/持久 profile、导入、查询、勾选、导出进仓单与唛头、
346
+ DDP 压缩包、改名规则,**以及 2026-09-11 只读侦察拿到的门户口径真源**
347
+ (工具栏/列表/导入弹窗的选择器、4 个后端接口、**港前仓真值**)。
348
+
349
+ - 只读侦察探针:`python3 scripts/wenyao_recon.py --toolbar|--grid|--import-dialog|--export-menu`
350
+ (连 CDP 9333 已登录的 Chrome;**只看不提交**)。
351
+ - **红线**:「导出进仓单」是 `next-menu-btn`,**点击可能真触发导出** ⇒ 侦察阶段**只 hover 不点**。
352
+ - ✅ **港前仓 = `华南仓`(已实测确认)**:门户 `warehouse/listAll` 只返回
353
+ 东莞集货仓(HNBD)/义乌(YWC)/宁波(NBC)/上海(SHC) 4 个值,**但那只是下拉预设,不是白名单**——
354
+ 2026-09-11 拿 `G2=华南仓` 的发票**真导入成功**(`成功 1 票业务,失败 0 条` ⇒ 订单 **WY535082 审单中**)。
355
+ 运营亦确认「华南仓没问题」。**教训见踩坑清单:别用下拉/接口的取值范围反推后端校验。**
356
+ - ✅ 已由门户实况证实的:现有订单 `仓库编码 = MXRC03` ⇒ 我们填的 `MXRC03` **正确**(运营版 `MXCR03` 是笔误)。
357
+
358
+ **命名规则(用户确认)**:`运单-<货件号>-<箱数>箱-<品名>`
359
+ (例 `运单-76636923-6箱-42件专业化妆刷海绵化妆包-棕色.pdf`)。
360
+
361
+ ## 7. 踩坑清单(写代码前必读)
362
+
363
+ **见 `references/pitfalls.md`** —— 按出现频率排序,每条含「现象 / 根因 / 修法 / 证据」。
364
+ 其中两条最容易反复踩:
365
+
366
+ 1. **附件上传必须传 `field_id`**;按字段名走会偶发 `20008 The user does not exist`(实测抖动 ~20%)。
367
+ 2. **`--file` / `--output` 必须是当前目录内的相对路径**;绝对路径被拒 `unsafe file path`。
368
+
369
+ ## 8. 自检清单(交付前逐条过)
370
+
371
+ - [ ] 填报件:只有第 2、8 行有值;示例行 5-6、表头、校验、声明行**逐格未变**
372
+ - [ ] `G2 == 华南仓`;`AF == 否`;`AG == MXRC03`;图片恰 1 张(浮动)
373
+ - [ ] 回写:`--allow-record` 只含目标记录;字段只在白名单内
374
+ - [ ] 写前备份已落盘;写后逐字段 diff 只显示预期字段
375
+ - [ ] 审计 JSONL 有对应行(成功/失败都要有)
376
+ - [ ] 没有把 `发票就绪` 勾上、没有动 `大货标签`(除非该阶段就该动)
377
+ - [ ] 未写任何凭据到仓库/日志/评论
378
+
379
+ ## 9. 复跑命令
380
+
381
+ ```bash
382
+ node scripts/tests/wenyao-template.test.mjs # 填报器(65 项)
383
+ node scripts/tests/wenyao-writeback.test.mjs # 回写守卫(53 项)
384
+ ```
385
+
386
+ ## 6. 派发与台账(掌舵者职责 · E3,2026-09-15)
387
+
388
+ **不再用看板**。掌舵者(`ml-invoice-wy`)按下面四步走,**不亲自搬砖**:
389
+
390
+ ```bash
391
+ # ① 取本轮计划(只读;含 need_fields/in_flight/stale/retry/needs_human 分类)
392
+ python3 ~/.argent-dsh/scripts/flow_triage.py --flow wenyao-invoice --json
393
+
394
+ # ② 在飞上限 2(超过就等,不要硬派)
395
+ python3 ~/.argent-dsh/scripts/flow_ledger.py --inflight
396
+
397
+ # ③ 对每张 to_dispatch 的票:派生**一个 subagent**(一票一个),并立刻记台账
398
+ python3 ~/.argent-dsh/scripts/flow_ledger.py --case-id wenyao-invoice__<货件号> \
399
+ --event dispatched --by gw-ml-invoice-wy
400
+
401
+ # ④ subagent 完成后由它(或掌舵者)记结果
402
+ python3 ~/.argent-dsh/scripts/flow_ledger.py --case-id wenyao-invoice__<货件号> --event done
403
+ python3 ~/.argent-dsh/scripts/flow_ledger.py --case-id wenyao-invoice__<货件号> \
404
+ --event failed --attempt 1 --error "门户登录失败"
405
+ ```
406
+
407
+ **subagent 的 prompt 必须自包含**:票数据(case 文件路径或字段)+ 「先读本技能」+ 红线(门户需用户
408
+ 确认、**HS 人工(仅 HS)**、白名单字段)+ 完成后写台账。**掌舵者只做**:取计划、限流、派发、汇总、落卡(需人时)。
409
+
410
+ **字段来源**:运营在群里补充的字段 → 用 `scripts/flow_source.py --source conversation` 建/合并 case
411
+ (幂等键 `flowId__货件号`;同名不同值**不覆盖**,记冲突交人确认)。
412
+
413
+ ### 6.1 子代理 prompt 模板(**必须照抄**,否则子代理只侦察不执行)
414
+
415
+ > 你是执行者,**不是侦察者**。第一条命令就必须是影子跑(读完本技能立刻执行,不要先做环境调研):
416
+ >
417
+ > (派发时**在 prompt 开头写票号**,例:`【76636923】影子跑` —— 否则多个子代理会话标题相同,
418
+ > 在工作台/会话列表里无法区分。2026-09-15 实测:两个子代理标题都是「你是「上海文鳐发票流程」的…」)
419
+
420
+ >
421
+ > ```bash
422
+ > /tmp/pw-venv/bin/python3 ~/.argent-dsh/scripts/wenyao_run.py --shipment <票号> \
423
+ > --english-name "<英文品名>" --shadow
424
+ > ```
425
+ >
426
+ > 然后:`wenyao_shadow_diff.py` 与黄金样本逐格比对(**放行闸门**)。
427
+ > 若影子跑在 P3 报「材质「X」未能在 SKU 字典 / 小词典命中 ⇒ 请翻译成英文后用 --material-en 重跑」
428
+ > ⇒ **把该材质译成英文**(LLM 或人工,政策A 允许),原命令**加 `--material-en "<英文>"` 重跑**
429
+ > (⛔ 不要当成"报关敏感字段不能预填"而停下 —— 那条红线**只管 HS 编码**)。
430
+ > **禁止** `--yes`、禁止写 lip 表、禁止访问门户。
431
+ > 结束必须回报三件事:**① 退出码 ② 产物路径 ③ 是否过闸门**(缺任一即视为未完成)。
432
+
433
+ ### 6.2 台账 `done` 的证据要求(2026-09-15 实测教训)
434
+
435
+ **实测事故**:掌舵者派出 2 个子代理(76636923 / 76637122),15:38:22 记 `dispatched`,
436
+ **15:40:52 就记了 `done`** —— 但两个子代理**从未执行** `wenyao_run.py --shadow`,没有任何产物。
437
+
438
+ 规则(收紧):
439
+ - 记 `done` **必须**同时具备:子代理回报的**退出码 0** + **产物路径存在**;
440
+ - 拿不到证据 ⇒ 记 `failed --attempt N --error "<原因>"`;
441
+ - 证据不完整/拿不准 ⇒ 记 `blocked` 并**落卡等人**;
442
+ - **禁止**因为"子代理已结束"就记 `done`。
443
+
444
+ ### 6.2.1 失败收口**必须写进表**(2026-10-09 缺陷 B;用户口径)
445
+
446
+ 现场(客户机生产实测 `recvvgPQgoUOwx`):跑失败后表里「发票就绪」被取消、而「发票错误信息」
447
+ **是空的**(原因只在台账)⇒ 运营看到"勾没了 + 没有任何说明",以为脚本坏了、也不知道怎么处理。
448
+ ⇒ **失败原因 + 续跑方式必须写进表**,且**表字段与台账同一串**(⛔ 不许两处各写一套):
449
+
450
+ ```bash
451
+ # 失败收口(写表「发票错误信息」+ 台账 failed/blocked;默认 dry-run,先看计划再 --yes)
452
+ python3 ~/.argent-dsh/scripts/invoice_fail_closeout.py --flow wenyao-invoice --case wenyao-invoice__<票号> \
453
+ --missing <逗号分隔的缺口列> [--clear-ready] [--yes]
454
+ # 成功收口(清空过时错误信息,避免陈旧文案误导)
455
+ python3 ~/.argent-dsh/scripts/invoice_fail_closeout.py --flow wenyao-invoice --case wenyao-invoice__<票号> \
456
+ --outcome success [--clear-ready] --yes
457
+ ```
458
+
459
+ - 文案由 `flow_failure.ops_message()` 产出(**唯一来源**):`结论:<业务类|系统类|我方缺陷类>|<原因>|动作:<怎么续跑>`;
460
+ 命令会把它**同一串**写进表与台账 `detail`,`error` 放规范原因原文;
461
+ - 取不到原因(命令行与台账都没有)⇒ 命令 **exit 2 拒绝执行**(⛔ 绝不写空说明);
462
+ - ⛔ 不要绕开本命令手写 `--error-message`(那正是"两处各写一套"的来源);裸异常栈由命令归入
463
+ **我方缺陷类**并**不回原文**;
464
+ - `--clear-ready` 只对**运营类**缺口用(取消勾选「发票就绪」⇒ 运营补齐后重勾,下一轮自动重跑,
465
+ Rule R 上限 3 次 / 冷却 30 分)。
466
+
467
+ **放行额度与上游变化(2026-10-13 口径,修「上限用尽后再也不会被拾取」)** —— 这四条与引擎
468
+ (`scripts/flow_triage.py` / `flow_claim.py`)**逐字一致**,⛔ 不许自己另说一套:
469
+
470
+ | 情形 | 引擎行为 | 你(角色/运营)该做什么 |
471
+ |---|---|---|
472
+ | 运营**改了上游数据**(本流程读取的任一列,如 `产品图`/`材质`/数量/SKU)并重勾「发票就绪」 | 额度**从 0 起算**(写一条信息性 `policy_apply{policy=release_budget_reset}`,台账里可复核),立即重派 | 什么都不用额外做;⛔ **不要**手工补 `requeued` |
473
+ | 运营**只重复勾**「发票就绪」而数据没变 | 额度照旧(重勾**不算**"改了数据") | 别再重复勾;先查为什么每次都失败 |
474
+ | 失败属**外部时序**(门户/平台侧**尚未生成**:如「标签未就绪 / 搜索命中 0 行 / 等待平台」) | **不占**运营放行额度:走独立通道(长冷却 = 声明面 `externalTiming.cooldownMin`,本线现为 120 分钟、⛔ 代码里不写死;独立更高上限),`planned` 记 `externalTiming=true`。**写进表/卡片的文案**(`scripts/flow_triage.py#external_wait_copy` / `scripts/invoice_lip_runner.py#_label_wait_text`)逐字含两句:`系统将在约 <cooldownMin> 分钟后自动再试;你也可以现在人工处理(处理完取消勾选「发票就绪」,系统就不再自动拾取)` | 两条路二选一:**等**(按上面的节奏自动再试;⛔ 不要为它补产品图/改数据 —— 补了也没用);**或现在就自己处理** —— 处理完**取消勾选**「发票就绪」,系统就不再自动拾取 |
475
+ | 外部通道也用尽 | `needs_human`(`kind=external_capped`),文案会写明"**不是运营数据问题**" | 交运维核查门户/平台侧,别再勾就绪 |
476
+
477
+ ### 6.3 掌舵者的「最短路径」约束(2026-09-15 实测教训)
478
+
479
+ **实测反例**:一次实跑把 **155+ 行日志**耗在环境侦察上(读模板机制、gaps 逻辑、脚本依赖、
480
+ 克隆版本差异……),**始终没有派发**;还因此发现"克隆缺脚本"这类本可由门禁拦下的问题。
481
+
482
+ 规则:
483
+ - **取计划 → 限流 → 派发,三步应在最少轮次完成**;
484
+ - **不要自己调研**依赖/模板/字段语义/脚本细节 —— **那是子代理的活**(它们才是执行者);
485
+ - 只有 `flow_triage` / `flow_ledger` **报错**时,才做**最小**排障(读报错 → 一次修正);
486
+ - 遇到环境类问题(脚本缺失/版本旧)⇒ **记一条台账或落卡并说明**,然后继续/中止,
487
+ **不要长时间自行排查**(先跑 `bash ~/.argent-dsh/scripts/sync-scripts.sh` 同步一次即可)。
488
+
489
+ ### 6.4 缺字段时:**在会话里直接问用户**(触发①闭环 · 2026-09-15)
490
+
491
+ `flow_triage` 的 `need_fields` **不是终点**。当用户是在**会话里**触发的(例如用户说「跑一下这票」),
492
+ 掌舵者应当**直接在会话里问用户**要那几个字段,而不是只回一句「请运营补」:
493
+
494
+ 1. 列出缺口(字段名 + 用途 + 应由谁补),**一次问全**,别挤牙膏;
495
+ 2. 用户给值后,用 **conversation 来源**写进 case(幂等合并、留 provenance、同名不同值不覆盖):
496
+ ```bash
497
+ python3 ~/.argent-dsh/scripts/flow_source.py --flow wenyao-invoice --source conversation \
498
+ --ticket <货件号> --fields-from-json /tmp/fields.json --provided-by 用户
499
+ ```
500
+ 同名不同值 ⇒ 脚本记 `conflicts` 且退出码 1 ⇒ **交用户确认**后再继续;
501
+ 3. 重新 `flow_triage` 确认该票进入 `to_dispatch`,再按 §6 四步派发;
502
+ 4. 用户当时给不全 ⇒ 记 `blocked` 并说明**还差什么**(回到第 1 步)。
503
+
504
+ **边界**:HS 编码等报关敏感字段仍是**人工维护**(系统不预填/不写候选/不自动确认);
505
+ 用户没给就不许推进开票。⚠️ **这条只管 HS 编码**:材质英文按 §「材质英文(`S` 列)三级解析」走
506
+ (三级 + LLM 可译,`--material-en`),别把 HS 的红线套到它身上。