@whyshu/argent-engine 0.6.0 → 0.6.1

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 (67) hide show
  1. package/MANIFEST.json +63 -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 +25 -25
  8. package/engine/scripts/check-billing-mode.sh +2 -1
  9. package/engine/scripts/collect-evidence.mjs +42 -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/final-check.mjs +43 -0
  14. package/engine/scripts/flow-requirements.json +24 -9
  15. package/engine/scripts/flow-timers-plist.sh +2 -1
  16. package/engine/scripts/flow-timers.sh +3 -1
  17. package/engine/scripts/flow_config.py +2 -1
  18. package/engine/scripts/init-ml.sh +5 -2
  19. package/engine/scripts/invoice-table.sh +2 -1
  20. package/engine/scripts/lib/argent-datadir.sh +18 -0
  21. package/engine/scripts/lib/argent_datadir.py +23 -0
  22. package/engine/scripts/poll-fulfillment.sh +4 -2
  23. package/engine/scripts/poll-invoice.sh +4 -2
  24. package/engine/scripts/provider-select.sh +2 -1
  25. package/engine/scripts/release-argent.sh +1 -1
  26. package/engine/scripts/retire-inventory.mjs +40 -0
  27. package/engine/scripts/run-tests.sh +2 -1
  28. package/engine/scripts/s1-audit.mjs +85 -0
  29. package/engine/scripts/usage-anchor.sh +3 -2
  30. package/engine/scripts/wenyao-browser.sh +3 -2
  31. package/engine/scripts/wenyao-login.sh +3 -2
  32. package/engine/scripts/ziniao-keepalive.sh +2 -1
  33. package/engine/scripts/ziniao-post-upgrade-check.sh +2 -1
  34. package/engine/scripts/ziniao-windows.sh +2 -1
  35. package/engine/skills/README.md +86 -0
  36. package/engine/skills/argent-dev/SKILL.md +41 -0
  37. package/engine/skills/internal.json +37 -0
  38. package/engine/skills/lktop-pm/SKILL.md +35 -0
  39. package/engine/skills/lktopapp-dev/SKILL.md +34 -0
  40. package/engine/skills/ml-config-guide/SKILL.md +50 -0
  41. package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
  42. package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
  43. package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
  44. package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
  45. package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
  46. package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
  47. package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
  48. package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
  49. package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
  50. package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
  51. package/engine/skills/ml-invoice/SKILL.md +506 -0
  52. package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
  53. package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
  54. package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
  55. package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
  56. package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
  57. package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
  58. package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
  59. package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
  60. package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
  61. package/engine/skills/pet-dev/SKILL.md +42 -0
  62. package/engine/skills/whyshu-pm/SKILL.md +35 -0
  63. package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
  64. package/engine/version.mjs +1 -1
  65. package/engine/verticals/mercadolibre/flows.json +37 -5
  66. package/package.json +3 -2
  67. package/requirements.txt +5 -0
@@ -0,0 +1,178 @@
1
+ ---
2
+ id: ml-fulfillment-sop
3
+ name: ml-fulfillment-sop
4
+ title: 美客多 FULL 货件管理
5
+ version: 2.4.1
6
+ category: 产品运营
7
+ tags: [FULL, 货件, 发货, 库存, 预约, 箱唛, 标签]
8
+ difficulty: ⭐⭐⭐
9
+ changelog:
10
+ - version: 2.4.1
11
+ date: 2026-08-05
12
+ changes:
13
+ - 选择器大小写修复:React 动态 ID _r_/_R_ 双写覆盖,数量输入框改用 ID 前缀匹配(非 type=number)
14
+ - 导航栏误触修复:checkbox 限定 main 容器,排除 #nav-header-user-switch
15
+ - 货件号改为从 DOM 标题提取(弃用 URL inbound 编号)
16
+ - version: 2.4.0
17
+ date: 2026-08-04
18
+ changes:
19
+ - FULL 货件编排器支持多店铺并行:STORE_MAP 店铺映射,每店独立 CDP 端口
20
+ - 下载目录改为 ziniaobrowserdatas/{店铺名}/ 按店区分
21
+ - version: 2.3.0
22
+ date: 2026-08-03
23
+ changes:
24
+ - 浏览器控制层重写为 Playwright CDP 方案(替代 ziniao-cli subprocess)
25
+ - 总结 Andes React 交互规则:PointerEvent 全序列 / robustClick / 日历灰圈定位
26
+ - version: 2.0.0
27
+ date: 2026-08-03
28
+ changes:
29
+ - 当前基线:8 步 SOP 完整流程(v0.5.0 Profile Distribution 快照)
30
+ requires:
31
+ - ziniao-cli >= 1.0.0
32
+ - mercadolibre-store-auth
33
+ - feishu-config
34
+ triggers:
35
+ - pattern: 货件|FULL|创建货件|发货|预约|箱唛|标签|仓库|Enviar productos
36
+ entry: scripts/fulfillment.js
37
+ output:
38
+ format: markdown
39
+ push_to: [any]
40
+ push:
41
+ enabled: true
42
+ type: card
43
+ template: completion
44
+ metadata:
45
+ hermes:
46
+ requires_toolsets: [browser, terminal]
47
+ requires_tools: [terminal]
48
+ config:
49
+ - key: fulfillment.stores_json_path
50
+ description: 店铺映射 stores.json 路径(key = 紫鸟环境名 = 飞书「店铺名称」单选选项)
51
+ default: ~/.hermes/scripts/stores.json
52
+ prompt: 店铺映射 stores.json 路径
53
+ - key: fulfillment.log_file
54
+ description: 本次运行日志文件路径(当前由 FULFILLMENT_LOG_FILE 环境变量注入)
55
+ prompt: 货件编排运行日志文件路径
56
+ - key: fulfillment.feishu_table_id
57
+ description: 飞书多维表格 Table ID(当前由 FEISHU_TABLE_ID 环境变量提供)
58
+ prompt: 飞书多维表格 Table ID
59
+ - key: fulfillment.feishu_user_id
60
+ description: 飞书用户 ID,用于消息推送(当前由 FEISHU_USER_ID 环境变量提供)
61
+ prompt: 飞书用户 ID
62
+ - key: fulfillment.ml_store_id
63
+ description: 默认店铺 ID(当前由 ML_STORE_ID 环境变量提供)
64
+ prompt: 默认店铺 ID
65
+ - key: fulfillment.ml_store_name
66
+ description: 默认店铺名(当前由 ML_STORE_NAME 环境变量提供)
67
+ prompt: 默认店铺名
68
+ - key: fulfillment.fields.status
69
+ description: 飞书表格「状态」字段名
70
+ default: 状态
71
+ prompt: 「状态」字段名
72
+ - key: fulfillment.fields.ready
73
+ description: 飞书表格「就绪」字段名
74
+ default: 就绪
75
+ prompt: 「就绪」字段名
76
+ - key: fulfillment.fields.current_step
77
+ description: 飞书表格「当前步骤」字段名
78
+ default: 当前步骤
79
+ prompt: 「当前步骤」字段名
80
+ - key: fulfillment.fields.shipment_id
81
+ description: 飞书表格「货件号」字段名
82
+ default: 货件号
83
+ prompt: 「货件号」字段名
84
+ description: "美客多美客多 FULL 货件管理技能(Argent on DSH)——FULL,货件,发货,库存,预约,箱唛,标签。原 hermes 版 v2.4.1。"
85
+ ---
86
+
87
+ # 美客多 FULL 货件管理
88
+
89
+ 基于标准化 SOP(美客多货件创建标准化SOP.xlsx)的 8 步操作流程。
90
+
91
+ ## 前置知识
92
+
93
+ 执行前先阅读 `references/` 下的经验沉淀,避免已知陷阱:
94
+
95
+ - `references/selectors.md` — 当前有效 DOM 选择器集合与数据提取模式(含 2026-08-05 Playwright CDP 验证版)
96
+ - `references/andes-components.md` — Andes React 组件交互规则:PointerEvent 全序列、robustClick、日历定位
97
+ - `references/pitfalls.md` — 已知陷阱与修复清单:导航栏误触、ID 大小写、PDF 前缀、下载目录等
98
+ - `references/cdp-port-mapping.md` — 多店铺 CDP 端口策略:动态发现、缓存、store 操作规范
99
+ - `references/multi-window-concurrency.md` — 🆕 紫鸟多窗口并发实测(2026-08-06):跨窗口隔离、同窗口共享状态、flock 店铺粒度互斥锁设计
100
+
101
+ ## 操作警告与触发生效条件
102
+
103
+ 步骤 3/4/5/7/8 会修改卖家后台数据——每步执行前必须确认用户已批准。步骤 1/2/6 为只读操作,可直接执行。
104
+ 触发条件:用户要求「创建货件」「发 FULL」「FULL 发货」;需要「下载标签」「打印箱唛」;或要「取消预约」。
105
+
106
+ ## 执行流程
107
+
108
+ ### 步骤 1:前期准备 [只读 — 直接执行]
109
+ 导航到 FULL 管理页,检查:货件列表中是否有「Vencido」(过期)货件、「En preparación」货件数量、库存限制警告。
110
+ 输出当前 FULL 状态摘要。禁止超库容创建,下架/违规 Listing 禁止创建货件。
111
+
112
+ ### 步骤 2:进入货件创建入口 [只读 — 直接执行]
113
+ 导航到 FULL 管理页,点击「Enviar productos」按钮。确认进入官方正品后台页面。
114
+
115
+ ### 步骤 3:选择产品与数量 [⚠️ 写操作 — 需用户批准]
116
+ 搜索 SKU → 输入数量(不超系统限制)→ 核对后「Continuar」→ 弹窗确认「Continuar con mi plan actual」。
117
+ 用户需提供 SKU 列表 + 各 SKU 数量。失败处理:超时停止重复提交,等 2 分钟核查后台状态;无有效货件则核对 SKU/数量重新创建;产品异常修正后操作。
118
+
119
+ ### 步骤 4:货件预约时间 [⚠️ 写操作 — 需用户批准]
120
+ 选择配送方式「Vehículo particular」→ 选 30 天之后的时间 →「Continuar」。禁止修改仓库地址。
121
+
122
+ ### 步骤 5:包装确认 [⚠️ 写操作 — 需用户批准]
123
+ 勾选确认框 → 确认「Confirmo que el producto tiene un empaque correcto」→「Continuar」。超时禁止重复提交,先核查后台状态。
124
+
125
+ ### 步骤 6:标签下载 [只读 — 直接执行]
126
+ 勾选标签 → 选择「1 seleccionado de 1」→「Descargar etiquetas」→「Descargar」。
127
+ 命名规则:`Código ML` + `SKU`(例:`YNWE47995+HW-MX-026-01`)。标签贴单品外包装,不可与箱唛混贴。
128
+
129
+ ### 步骤 7:打印箱唛 [⚠️ 写操作 — 需用户批准]
130
+ 选择「Bultos sin agrupar en pallets」→ 填写箱数 → 下载箱唛。
131
+ 命名规则:`货件号` + `SKU`(例:`71667033+HW-MX-026-01`)。
132
+ ⚠️ 进不了箱数页时**不要**先怀疑"门户改版/脚本缺陷":失败路径会**只读读页面**给出归因结论
133
+ (① 尺寸分级阻塞=运营改商品 ② 前置未完成=运营在门户完成 ③ 都不是才是疑似改版)。
134
+ 观察点与判据见 `references/selectors.md` §「商品尺寸页(units)与尺寸分级阻塞」。
135
+
136
+ ### 步骤 8:取消预约时间 [⚠️ 写操作 — 需用户批准]
137
+ 点击「Editar」→「Cancelar reserva」→ 确认「Cancelar cita」。预约仅为下载箱唛使用,下载完成后必须取消。
138
+
139
+ ---
140
+
141
+ ## 异常处理
142
+
143
+ | 异常问题 | 核心原因 | 解决方案 |
144
+ |----------|----------|----------|
145
+ | 货件创建超时/失败 | 网络延迟、超库容、产品异常 | 停止重复提交,等 2 分钟核查后台;无有效货件则核对 SKU/数量重新创建;产品异常修正 Listing |
146
+ | 货件拆分(大小件混装) | 常规件与大件同批创建 | 按系统生成的多个货件 ID 分开打包、单独贴标、分别入仓,禁止混装 |
147
+ | 标签无法下载/条码失效 | 货件状态异常、系统缓存 | 刷新页面重新下载,确保条码可正常扫描入库 |
148
+ | 步骤 3-5 超时 | 页面加载慢、网络波动 | 禁止重复提交;先核查后台状态确认是否已生成货件 |
149
+ | 步骤 7 进不了箱数页(未见「Agrupa tu stock」) | ① 商品被判**超大件**而该货件空间只收中小件(⛔ 运营侧,脚本无解)② hub 前置项未完成 ③ 真·门户改版 | **先读失败 reason 里的归因结论**:① ⇒ 运营修正商品包装尺寸或改走其他渠道(⛔ 不派 ml-dev);② ⇒ 运营在门户完成;③ ⇒ 才交 ml-dev。详见 `references/pitfalls.md` §「步骤7 被误判成门户改版」 |
150
+
151
+ ## 作业规范与禁止事项
152
+
153
+ 详见脚本 forbidden 命令输出的禁止事项清单。核心原则:严格按系统推荐库容创建,所有操作留存货件 ID/创建时间/发货时间。
154
+
155
+ ## 操作摘要格式
156
+
157
+ 完成全部步骤后输出操作摘要:
158
+
159
+ ```json
160
+ {
161
+ "shipment_id": "71667033",
162
+ "created_at": "2026-07-19T15:30:00",
163
+ "skus": ["HW-MX-026-01"],
164
+ "total_units": 90,
165
+ "appointment": "2026-08-19T09:00:00",
166
+ "appointment_cancelled": true,
167
+ "labels_downloaded": true,
168
+ "box_labels_printed": true,
169
+ "status": "completed",
170
+ "warnings": []
171
+ }
172
+ ```
173
+
174
+ ## 复盘归档
175
+
176
+ 每次完成货件操作后同步输出日报存档:货件 ID、创建时间、SKU 清单、数量、异常状态。详见 #7 店铺日报机制。
177
+ 如需撤回货件请在 FULL 管理页操作「Cancelar envío」(仅限未发送状态),已发送货件联系平台客服并提供货件 ID。
178
+ 每单操作日志包含完整操作记录截图,存档至对应店铺日报文档,便于追溯复盘。
@@ -0,0 +1,69 @@
1
+ # Andes React 组件交互规则
2
+
3
+ MercadoLibre 后台基于 Andes 设计系统(React)。原生 `.click()` 对多数 Andes 受控组件无效,必须模拟完整鼠标事件序列。
4
+
5
+ ## 核心原则
6
+
7
+ - **PointerEvent / MouseEvent 全序列是触发 React onChange 的唯一可靠方式**
8
+ - React 受控组件监听合成事件(synthetic events),直接赋值 `.value` 或调用 `.click()` 不会更新内部状态
9
+ - 事件必须 `bubbles: true`,部分场景还需 `cancelable: true, view: window`
10
+
11
+ ## 各组件可靠交互模式
12
+
13
+ ### Checkbox
14
+
15
+ ```javascript
16
+ cb.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));
17
+ ```
18
+
19
+ - DOM `checked` 属性不会更新,但 React 内部状态会正确切换
20
+ - 勾选是否生效以 UI 变化(如按钮启用)为准,不要读 DOM checked
21
+
22
+ ### Number input(数量输入)
23
+
24
+ ```javascript
25
+ el.click();
26
+ el.focus();
27
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
28
+ setter.call(el, '90');
29
+ el.dispatchEvent(new Event('input', { bubbles: true }));
30
+ ```
31
+
32
+ - 直接 `el.value = x` 不触发 React onChange,必须用 nativeSetter + 手动派发 input 事件
33
+ - 注意:数量输入框不是 `type="number"`,定位用 `input[id^="_r_"], input[id^="_R_"]`
34
+
35
+ ### robustClick(通用按钮/元素)
36
+
37
+ ```javascript
38
+ function rc(el) {
39
+ el.scrollIntoView();
40
+ el.focus();
41
+ el.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
42
+ el.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
43
+ el.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true, view: window }));
44
+ }
45
+ ```
46
+
47
+ - 用于:配送方式下拉、时间槽、翻月按钮、Confirmar 等
48
+ - 日历格子点击(`div.day`)rc() 可能仍无效,需人工介入或特殊处理
49
+
50
+ ## 日历交互
51
+
52
+ ### 灰圈定位 30 天后
53
+
54
+ - 用 `div.day--current` 定位今天,再数 31 格定位 30 天后
55
+ - 避免依赖绝对日期文本(月份翻页后失效)
56
+
57
+ ### 双月视图同日区分
58
+
59
+ - 日历双月视图可能同时渲染两个相同日期,用 `parentRow.rowIndex` 区分所在行
60
+ - 先确定目标行,再在该行内点击目标日期
61
+
62
+ ### next_month 按钮
63
+
64
+ - `next_month` 普通 `.click()` 无效,必须用 robustClick(mousedown + mouseup + click 全序列)
65
+
66
+ ## 验证要点
67
+
68
+ - 每次改动交互代码后,用真实页面验证 React 状态是否更新(按钮启用/文本变化),不能只看 DOM 属性
69
+ - UI 改版后优先复查本文件与 `selectors.md` 的对应关系
@@ -0,0 +1,35 @@
1
+ # 多店铺 CDP 端口策略
2
+
3
+ 紫鸟(ziniao)浏览器多店铺自动化时的端口分配、发现与缓存机制。
4
+
5
+ ## 架构
6
+
7
+ - 紫鸟单进程单页面:同一时间只能打开一个店铺页面
8
+ - 多店铺通过 profile 切换实现,每店一个独立 profile
9
+
10
+ ## 跨平台客户端进程名
11
+
12
+ - macOS:`/Applications/ziniao.app`,进程名 `ziniao` / `Ziniao`
13
+ - Windows:进程名 `ziniaob`(旧名 `ziniaobro` 兼容)
14
+ - **不要写死单一进程名**——检测同时匹配 `ziniao|Ziniao|ziniaob|ziniaobro`
15
+
16
+ ## 端口发现
17
+
18
+ - 每店独立 CDP 端口,**不硬编码**(实测为动态端口,如 50566/50876/54074,不一定是 52420+)
19
+ - 跨平台发现:`lsof -i -P -n` 过滤紫鸟进程名(Mac ziniao/Ziniao;Win ziniaob)监听端口,
20
+ 逐端口 `curl /json/version` 验证(响应含 `"Browser"` 才是 CDP 端口,排除 9480/9481 本地 API)
21
+ - 无进程匹配时回退已知端口段探测(52420+ 递增)
22
+ - 发现结果缓存到 `~/.hermes/scripts/cdp-port-map.json`
23
+ (v0.6.5+ 格式:`storeId → {store_name, env_name, cdp_port}` 三方一致映射,
24
+ 店铺名/环境名来自 `ziniao-cli store list`;兼容旧格式 `店铺名 → 端口`)
25
+
26
+ ## 窗口策略
27
+
28
+ - 去掉 `--headless`(显式 `--headless=false`),每店独立可见窗口,便于人工核查与排查问题
29
+ - 客户端运行中但无店铺窗口时:`ziniao-cli store open --id <storeId> --headless=false` 自动打开
30
+ (替代人工双击),失败时保留人工兜底提示
31
+
32
+ ## store 操作规范
33
+
34
+ - `store open` / `store close` 必须用 `--id` 不用 `--name`(name 可能重复或变更)
35
+ - 禁止 `store close` 触发 ML 安全检测:关闭动作要干净利落,避免被平台风控(频繁切换/异常关闭会触发)
@@ -0,0 +1,136 @@
1
+ # 阿鑫 FULL 货件管理表(飞书多维表格)Schema
2
+
3
+ > **只读快照**:本文档由导出脚本从线上 Base 只读导出,禁止通过本文档反向写入线上。
4
+ > **用途**:base-bootstrap 脚本与编排器引用的权威字段/视图/状态机定义。
5
+
6
+ ## 元信息
7
+
8
+ | 项 | 值 |
9
+ |---|---|
10
+ | 来源 Base | 阿鑫(FULL 货件编排专用 Base) |
11
+ | 表名 | FULL任务 |
12
+ | Table ID | `tblwBSv4rxaFnIxm` |
13
+ | app_token | 见 `~/.hermes/scripts/fulfillment.env` → `FEISHU_BASE_TOKEN`(勿提交仓库) |
14
+ | 表 revision | 2763 |
15
+ | 记录数 | 58(快照时) |
16
+ | 快照时间 | 2026-08-13 |
17
+
18
+ ## 字段清单(共 12 个)
19
+
20
+ | # | 字段名 | 字段 ID | 类型(ui_type / type) | 主字段 | 选项/配置 | 必填 |
21
+ |---|--------|---------|------------------------|--------|-----------|------|
22
+ | 1 | SKU | `fldx5peEAX` | Text / 1 | ✅ | — | ✅(记录标题) |
23
+ | 2 | 状态 | `fldLjQKlr5` | SingleSelect / 3 | | 见「状态机」 | — |
24
+ | 3 | 店铺名称 | `fld6vUp5mr` | SingleSelect / 3 | | 3店-主账号 / 2店-子账号 / 1店-子账号 | — |
25
+ | 4 | 品名 | `fld6Ay5cvp` | Text / 1 | | — | — |
26
+ | 5 | 数量 | `fldIBKQj0V` | Number / 2 | | formatter `0.00`;API 返回字符串如 `"240"` | — |
27
+ | 6 | 箱数 | `fldxp1xnfA` | Number / 2 | | formatter `0.00`;API 返回字符串如 `"12"` | — |
28
+ | 7 | 就绪 | `fldcni87iZ` | Checkbox / 7 | | 布尔;编排器捡取门票 | — |
29
+ | 8 | 创建时间 | `fldpcgXkzO` | DateTime / 5 | | `auto_fill: true`(创建记录自动填),formatter `yyyy/MM/dd`;API 返回毫秒时间戳 | — |
30
+ | 9 | 当前步骤 | `fld82Zzg5b` | Text / 1 | | 进度描述;完成=`全部完成`;失败=`失败:原因` | — |
31
+ | 10 | 货件号 | `fldoibvZgN` | Text / 1 | | 美客多货件号(如 `7XXXXXXX`) | — |
32
+ | 11 | 产品标签 | `fldt8g1LcG` | Attachment / 17 | | PDF 附件,命名见下 | — |
33
+ | 12 | 箱唛 | `fldytVtXmv` | Attachment / 17 | | PDF 附件,命名见下 | — |
34
+
35
+ > **必填说明**:飞书 API 不提供强制必填标记。唯一隐含必填为主字段 **SKU**(记录标题,创建记录时必须提供);其余字段无 API 层强制约束。
36
+
37
+ ## 状态机(字段「状态」fldLjQKlr5)
38
+
39
+ | 选项值 | 选项 ID | 语义 |
40
+ |--------|---------|------|
41
+ | `Pending` | `optZSDbHaC` | 待处理:新建或网络错误重置后,等待编排器捡取 |
42
+ | `运行中` | `optwb7MTYx` | 编排器正在执行;**运行中 + 就绪=true = 上次中断残留**(异常未走失败分支),下轮续跑 |
43
+ | `已完成` | `opt39QFyK3` | 全部步骤完成,`当前步骤=全部完成`,`就绪=false` |
44
+ | `失败` | `optdoxY3b1` | 执行失败,需人工介入;`就绪=false`,poll 不再捡取 |
45
+
46
+ > ⚠️ 线上**没有**「已取消」选项;取消预约(步骤8)不改变记录状态。
47
+
48
+ ### 店铺名称选项(fld6vUp5mr)
49
+
50
+ | 选项值 | 选项 ID |
51
+ |--------|---------|
52
+ | `3店-主账号` | `optdndyDnT` |
53
+ | `2店-子账号` | `optiUA9Roi` |
54
+ | `1店-子账号` | `optL5KTamz` |
55
+
56
+ ### 状态流转
57
+
58
+ ```
59
+ Pending --编排器捡取(就绪=true)--> 运行中 --全部完成--> 已完成 (就绪=false)
60
+ │
61
+ └--任意异常--> 失败 (就绪=false, 当前步骤=失败:原因)
62
+ 网络类错误重置(timeout/ERR_CONNECTION_CLOSED/紫鸟修复页):
63
+ 状态→Pending、就绪→true、当前步骤→空;货件号保留不动(有货件号则从步骤4续传)
64
+ ```
65
+
66
+ 来源:`scripts/orchestrator.py`(旧名 `fulfillment_orchestrator.py`,2026-09-21 校正)`FeishuClient.list_pending` / `_fail_record` / `_reset_for_retry` 与 `scripts/poll-fulfillment.sh` 同口径。
67
+
68
+ ## 字段语义(编排器视角)
69
+
70
+ | 字段 | 语义 |
71
+ |------|------|
72
+ | 就绪(Checkbox) | **执行门票**:人工在表格勾选 = 允许编排器执行该记录;编排器开始执行后置 `状态=运行中`,完成/失败后自动置 `就绪=false` |
73
+ | 当前步骤(Text) | 实时进度;完成=`全部完成`(终态判定 `全部完成` / `全部完成(已有终态)`);失败=`失败:{原因}` |
74
+ | 货件号(Text) | 有值 → 下轮跳过步骤3(选品)从步骤4(预约)续传;无值 → 从头跑 |
75
+ | 店铺名称(SingleSelect) | 决定使用哪个紫鸟环境(key = stores.json 店铺映射 key) |
76
+
77
+ ## 视图配置(共 1 个)
78
+
79
+ | 视图名 | view_id | 类型 | 筛选 | 排序 |
80
+ |--------|---------|------|------|------|
81
+ | 表格 | `vewxvmdDrF` | grid | 无(property=null) | 无(默认) |
82
+
83
+ ## 记录结构示例(脱敏)
84
+
85
+ ```json
86
+ {
87
+ "record_id": "recXXXXX(打码)",
88
+ "fields": {
89
+ "SKU": "HW-MX-XXXX",
90
+ "状态": "已完成",
91
+ "店铺名称": "1店-子账号",
92
+ "品名": "某产品(打码)",
93
+ "数量": "240",
94
+ "箱数": "12",
95
+ "就绪": false,
96
+ "创建时间": 1785772800000,
97
+ "当前步骤": "全部完成",
98
+ "货件号": "7XXXXXXX",
99
+ "产品标签": [
100
+ {
101
+ "file_token": "RXXXXX(打码)",
102
+ "name": "产品标+{SKU}+{货件号}+{品名}+{店铺}.pdf",
103
+ "size": 95527,
104
+ "type": "application/pdf",
105
+ "url": "https://open.feishu.cn/open-apis/drive/v1/medias/{file_token}/download"
106
+ }
107
+ ],
108
+ "箱唛": [
109
+ {
110
+ "file_token": "RXXXXX(打码)",
111
+ "name": "{货件号}+{箱数}箱+{店铺}.pdf",
112
+ "size": 220813,
113
+ "type": "application/pdf"
114
+ }
115
+ ]
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## 附件命名规范(可作 bootstrap 校验正则)
121
+
122
+ - 产品标签:`产品标+{SKU}+{货件号}+{品名}+{店铺}.pdf`
123
+ - 箱唛:`{货件号}+{箱数}箱+{品名}.pdf`(用户 2026-09-15 明确口径;此前误写"{店铺}",已更正)
124
+
125
+ ## 只读 API 备忘(lark-cli)
126
+
127
+ ```bash
128
+ source ~/.hermes/scripts/fulfillment.env
129
+ # 字段列表(表为 BOT 所建,BOT 保留可管理 full_access——默认 bot 身份即可)
130
+ lark-cli api GET "/open-apis/bitable/v1/apps/$FEISHU_BASE_TOKEN/tables/$FEISHU_TABLE_ID/fields" --as bot --format json --page-all -q '.data.items'
131
+ # 视图列表 / 视图详情(含筛选排序 property)
132
+ lark-cli api GET "/open-apis/bitable/v1/apps/$FEISHU_BASE_TOKEN/tables/$FEISHU_TABLE_ID/views" --as bot --format json --page-all -q '.data.items'
133
+ lark-cli api GET "/open-apis/bitable/v1/apps/$FEISHU_BASE_TOKEN/tables/$FEISHU_TABLE_ID/views/vewxvmdDrF" --as bot --format json
134
+ # 记录(bot 身份可用 base:record:read)
135
+ lark-cli api GET "/open-apis/bitable/v1/apps/$FEISHU_BASE_TOKEN/tables/$FEISHU_TABLE_ID/records" --as bot --format json --params '{"page_size": 3}'
136
+ ```
@@ -0,0 +1,69 @@
1
+ # 紫鸟多窗口并发与互斥锁设计(2026-08-06 实测)
2
+
3
+ > 背景:多个 SKILL(#10 FULL 货件、#7 日报等)都依赖紫鸟浏览器操作店铺。若 #10 长期占用窗口,其他 SKILL 可能冲突。本文记录实测结论与锁设计方案。
4
+
5
+ ## 冲突本质
6
+
7
+ - 紫鸟 = 一个店铺一个窗口(一个 CDP 端口),同一窗口同一时刻只能有一个控制方有效驱动
8
+ - #10 FULL:Playwright `connect_over_cdp` 长流程(3-10 分钟,写操作多)
9
+ - #7 日报:`ziniao-cli page visit/content` 秒级只读
10
+ - 同一店铺窗口并发 → 导航互相打断、页面状态被踩踏
11
+
12
+ ## 实测结论(2026-08-06,3 窗口在线:50566/50788/50876)
13
+
14
+ ### 测试 A:双窗口 CDP 并发
15
+ 两个 Playwright 客户端同时 `connect_over_cdp` 连接不同店铺端口,各自导航独立页面:
16
+
17
+ | 观察项 | 结果 |
18
+ |---|---|
19
+ | 同时连接 | ✅ 成功(0.2s) |
20
+ | 各自导航(A→resumen,B→inbounds)| ✅ A 导航后 B 页面保持原状 |
21
+ | 标题独立读取 | ✅ 互不影响 |
22
+
23
+ **结论:不同店铺窗口 = 完全隔离,可并行操作。**
24
+
25
+ ### 测试 B:ziniao-cli vs CDP 跨窗口
26
+ `ziniao-cli page visit` 操作 1店 窗口期间,2店 窗口的 CDP 会话全程观察:
27
+
28
+ | 观察项 | 结果 |
29
+ |---|---|
30
+ | 2店 CDP URL 是否变化 | ✅ 纹丝不动(保持 resumen) |
31
+ | ziniao-cli 导航后 CDP 连 1店 窗口 | ✅ 可见导航结果(URL=inbounds) |
32
+ | 1店 CDP 继续接管导航 | ✅ 正常(可再导航到 resumen) |
33
+
34
+ **结论:**
35
+ 1. ziniao-cli 操作 A 窗口不影响 B 窗口的 CDP 会话(跨窗口零干扰)
36
+ 2. ziniao-cli 与 Playwright CDP 操作**同一窗口时共享同一浏览器页面状态**(互相可见导航)
37
+ 3. 一个工具操作完释放后,另一个工具可正常接管(无残留锁定)
38
+
39
+ ## 锁设计(flock 店铺粒度互斥锁)
40
+
41
+ ```bash
42
+ # 锁文件:每店铺一把(storeId 见 STORE_MAP)
43
+ # /tmp/ziniao-<storeId>.lock
44
+ # 拿锁(阻塞等待,适合 Agent 交互场景):
45
+ flock /tmp/ziniao-27477945046190.lock -c "python3 orchestrator.py --mode full --allow-write"
46
+ # 拿锁(非阻塞,适合 cron 场景——拿不到跳过等下一轮):
47
+ flock -n /tmp/ziniao-27477945046190.lock -c "..." || echo "窗口被占用,跳过本轮"
48
+ ```
49
+
50
+ ### 设计要点
51
+
52
+ | 项 | 决策 | 原因 |
53
+ |---|---|---|
54
+ | 锁粒度 | **按店铺**(3 店 3 把锁)| 跨窗口实测隔离,不同店铺可并行 |
55
+ | 覆盖工具 | **ziniao-cli + Playwright CDP 都要拿锁** | 同窗口共享页面状态,两种工具都改它 |
56
+ | 等待策略 | cron 场景 `-n` 非阻塞跳过;交互场景阻塞等待 | cron 5 分钟一轮,跳过等下轮即可 |
57
+ | 释放 | flock 进程退出自动释放 | 无需手动 unlock,异常退出也不死锁 |
58
+
59
+ ### 需加锁的位置
60
+
61
+ - #10 FULL:`fulfillment_orchestrator.py`(Playwright CDP)
62
+ - #7 日报:extract 步骤(ziniao-cli page visit/content)
63
+ - 未来所有依赖紫鸟浏览器的 SKILL(#14 等):统一在脚本入口拿锁
64
+
65
+ ### 预期效果
66
+
67
+ - FULL 占用 1店 10 分钟时,2店/3店 日报完全不受影响(并行)
68
+ - 同一店铺任务天然串行,不踩踏
69
+ - 锁失败(窗口占用)→ cron 下一轮自动重试,无需人工介入
@@ -0,0 +1,77 @@
1
+ # 已知陷阱与修复(踩坑清单)
2
+
3
+ 按出现频率排序的经验教训。改动脚本或排障时先对照本清单,避免重复踩坑。
4
+
5
+ ## 导航栏误触
6
+
7
+ - 现象:checkbox 选择器命中顶部导航栏的用户切换组件(`#nav-header-user-switch`),勾选错误元素
8
+ - 修复:checkbox 限定到 `main` 容器内查找;`_scope` 参数只对 checkbox 生效
9
+
10
+ ## 货件号 ≠ URL 编号
11
+
12
+ - 现象:从 URL(inbound 编号)提取货件号,与实际货件号不一致
13
+ - 修复:货件号从 DOM 标题元素提取,不使用 URL inbound 编号
14
+
15
+ ## React 动态 ID 大小写
16
+
17
+ - 现象:动态 ID 大小写不稳定(`_r_` vs `_R_`),选择器漏匹配
18
+ - 修复:CSS `[id^="_r_"]` 区分大小写,需同时覆盖两种写法;数量输入框不是 `type="number"`
19
+
20
+ ## 产品标 PDF 前缀
21
+
22
+ - 现象:下载的产品标文件名带 `Envio-` 前缀,按 `*Etiquetas-de-productos.pdf` 匹配时漏掉
23
+ - 修复:文件名模式用 `Envio-*-Etiquetas-de-productos.pdf`
24
+
25
+ ## 下载目录
26
+
27
+ - 下载目录为 `ziniaobrowserdatas/{店铺名}/`,多店铺时按店铺名区分,勿混用
28
+
29
+ ## PDF 命名规则
30
+
31
+ - 产品标:`产品标 + SKU + ML码 + 品名 + 店名`
32
+ - 箱唛:`货件号 + {箱数}箱 + 品名`(用户 2026-09-15 明确口径;此前误写"店名",已更正)
33
+
34
+ ## 排除无关 PDF
35
+
36
+ - 排除 listado / Descargar / preparation 产品列表等非目标 PDF 文件
37
+
38
+ ## 货件终态跳过
39
+
40
+ - 货件终态 `Reserva cancelada` 应跳过,不再继续处理
41
+
42
+ ## wait_for_url 时序
43
+
44
+ - `wait_for_url` 在 hub-v2 之后提取,避免过早读取旧页面内容
45
+
46
+ ## 页面切换等待
47
+
48
+ - 页面切换用旋转蒙层(spinner)等待替代固定 sleep,避免竞态与超时
49
+
50
+ ## ⛔ 步骤7「未见 Agrupa tu stock」被误判成「门户改版」(2026-10-12 事故:同一票连错 5 次)
51
+
52
+ - **现场**:货件 `79301358` / SKU `HW-MX-162` / 1 店,步骤7 **连续 5 次**失败,文案一律
53
+ 「页面未到箱数填写页:2 轮自愈+重导航后仍未见「Agrupa tu stock」」。
54
+ - **错在哪**:这句话**只说"没看到什么",不说"页面到底是什么样"**。角色据此把它刻度成
55
+ 「**脚本缺陷/门户改版**(`Agrupa tu stock` 入口缺失)⇒ 需 ml-dev 修复选择器」——
56
+ **误导**:ml-dev 无从下手(元素一个都没变),票还一直占着槽位。
57
+ - **真因**:商品被判**超大件**(units 页 `size-dimensions-tag` = `EXTRAGRANDE`、
58
+ `volume-pill` = `11,712 cm 3`),而该货件空间**只收中小件**(原文「Pequeños y medianos —
59
+ Este espacio incluye a los productos Pequeños y medianos.」)⇒ 库存输入框
60
+ (`aria-label="Stock a enviar"`,值 200)被平台渲染成 **disabled** ⇒ 到不了箱数填写页。
61
+ hub 第 5 项「Prepara bultos y/o pallets con tus productos e identifícalos」恒 `done=False`,
62
+ 紧随其后「Debes completar las tareas anteriores para preparar e identificar.」。
63
+ - **⛔ 脚本侧无解**:运营实测把那个 disabled 输入框**强制启用 + 重输 + 点 Guardar**
64
+ (units 页 `Guardar`)**没有任何效果**(hub 项不变)。正确动作只有运营侧两个:
65
+ **修正商品包装尺寸**(改到该空间接受的分级)**或改走其他渠道/货件**。
66
+ - **已修**(判据缺失 ⇒ 报错误导):步骤7 失败路径先做一次**只读归因探测**
67
+ (hub-v2 + units 页,只 `navigate` + `evaluate`),命中即改抛**可执行结论**:
68
+ ① 尺寸分级阻塞(带 tag/体积/空间原文)② 前置未完成(列未完成项原文)③ 都不是 ⇒
69
+ 保留旧文案 + 补证据(`Agrupa tu stock` 缺失 / 当时 URL / 自愈轮数,供 ml-dev 排查真正的改版)。
70
+ 判据是**纯函数** `scripts/ml_hub_status.py#step7_block_verdict`(离线可回归)。
71
+ 观察点登记见 `references/selectors.md` §「商品尺寸页(units)与尺寸分级阻塞」。
72
+ - **教训(可推广)**:**"没看到预期元素"不是结论,是问题**。凡失败文案只说"未见 X",
73
+ 下一个人(或下一个角色)就只能**猜**页面长什么样 —— 猜出来的"门户改版/脚本缺陷"
74
+ 会把票推给**根本修不了它的人**。失败文案必须**先读页面、再写结论**,并且带**原文**。
75
+ - **口径变化(有意)**:新文案含「需运营…」⇒ 分类从 `needs_agent`(唤醒角色去猜)
76
+ 变为 `needs_human`(落卡等人、运营改商品)。⛔ 这是本事故要的结果,不是副作用。
77
+