@whyshu/argent-engine 0.0.0-stage → 0.6.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 (305) hide show
  1. package/MANIFEST.json +307 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +575 -0
  55. package/engine/scripts/billing-mode-daily.sh +12 -0
  56. package/engine/scripts/build-engine-package.mjs +123 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +377 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/config-explain.mjs +278 -0
  81. package/engine/scripts/configure-provider.rb +29 -0
  82. package/engine/scripts/customer-config-migrate.mjs +179 -0
  83. package/engine/scripts/customer-preflight.py +892 -0
  84. package/engine/scripts/customer-readiness.sh +134 -0
  85. package/engine/scripts/daily-report-inbox.py +242 -0
  86. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  87. package/engine/scripts/daily-report.mjs +1310 -0
  88. package/engine/scripts/daily-report.sh +46 -0
  89. package/engine/scripts/data/lip-product-dict.json +306 -0
  90. package/engine/scripts/deliver-customer.mjs +932 -0
  91. package/engine/scripts/delivery-faces.txt +31 -0
  92. package/engine/scripts/desktop-trial.sh +146 -0
  93. package/engine/scripts/dev-window.sh +260 -0
  94. package/engine/scripts/drill-clean-machine.sh +315 -0
  95. package/engine/scripts/dsh-api-client.mjs +215 -0
  96. package/engine/scripts/dsh-backup-scope.sh +83 -0
  97. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  98. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  99. package/engine/scripts/dsh-plane.mjs +153 -0
  100. package/engine/scripts/dsh-portforward.py +223 -0
  101. package/engine/scripts/dsh-preflight.mjs +179 -0
  102. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  103. package/engine/scripts/dsh-release-watch.mjs +458 -0
  104. package/engine/scripts/dsh-remote-check.sh +99 -0
  105. package/engine/scripts/dsh-restart.sh +179 -0
  106. package/engine/scripts/dsh-restore.sh +111 -0
  107. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  108. package/engine/scripts/dsh-snapshot.mjs +116 -0
  109. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  110. package/engine/scripts/dsh-upgrade.mjs +612 -0
  111. package/engine/scripts/dsh-url.sh +86 -0
  112. package/engine/scripts/dsh-web-launch.mjs +155 -0
  113. package/engine/scripts/ensure-store-windows.py +509 -0
  114. package/engine/scripts/entitlements.py +458 -0
  115. package/engine/scripts/env_probe.py +411 -0
  116. package/engine/scripts/feishu-card.mjs +252 -0
  117. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  118. package/engine/scripts/flow-auto-retry.py +454 -0
  119. package/engine/scripts/flow-requirements.json +720 -0
  120. package/engine/scripts/flow-scaffold.py +275 -0
  121. package/engine/scripts/flow-timers-plist.sh +86 -0
  122. package/engine/scripts/flow-timers.sh +182 -0
  123. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  124. package/engine/scripts/flow-trigger.sh +571 -0
  125. package/engine/scripts/flow_billing.py +354 -0
  126. package/engine/scripts/flow_capability_stats.py +314 -0
  127. package/engine/scripts/flow_case.py +194 -0
  128. package/engine/scripts/flow_claim.py +497 -0
  129. package/engine/scripts/flow_config.py +1024 -0
  130. package/engine/scripts/flow_failure.py +588 -0
  131. package/engine/scripts/flow_learn.py +105 -0
  132. package/engine/scripts/flow_ledger.py +235 -0
  133. package/engine/scripts/flow_policy.py +238 -0
  134. package/engine/scripts/flow_preflight.py +339 -0
  135. package/engine/scripts/flow_registry.py +341 -0
  136. package/engine/scripts/flow_retry.py +304 -0
  137. package/engine/scripts/flow_return_to_ops.py +191 -0
  138. package/engine/scripts/flow_source.py +315 -0
  139. package/engine/scripts/flow_sources/__init__.py +53 -0
  140. package/engine/scripts/flow_sources/cases_json.py +106 -0
  141. package/engine/scripts/flow_sources/common.py +428 -0
  142. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  143. package/engine/scripts/flow_stale.py +185 -0
  144. package/engine/scripts/flow_status_reconcile.py +344 -0
  145. package/engine/scripts/flow_triage.py +849 -0
  146. package/engine/scripts/flow_write_verify.py +496 -0
  147. package/engine/scripts/fulfillment.js +586 -0
  148. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  149. package/engine/scripts/gen-preset-rows.mjs +259 -0
  150. package/engine/scripts/init-ml.sh +2275 -0
  151. package/engine/scripts/install-internal-role.sh +190 -0
  152. package/engine/scripts/install-license.mjs +641 -0
  153. package/engine/scripts/install-one-shot.mjs +665 -0
  154. package/engine/scripts/integration_contract.py +220 -0
  155. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  156. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  157. package/engine/scripts/invoice-mapping.example.json +40 -0
  158. package/engine/scripts/invoice-table.sh +194 -0
  159. package/engine/scripts/invoice_carrier_notice.py +279 -0
  160. package/engine/scripts/invoice_fail_closeout.py +297 -0
  161. package/engine/scripts/invoice_lip_runner.py +1508 -0
  162. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  163. package/engine/scripts/invoice_ready_audit.py +191 -0
  164. package/engine/scripts/invoice_runner.py +675 -0
  165. package/engine/scripts/invoice_stageb.py +516 -0
  166. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  167. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  168. package/engine/scripts/lease.py +377 -0
  169. package/engine/scripts/lib/agent-install.mjs +649 -0
  170. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  171. package/engine/scripts/lib/argent_datadir.py +262 -0
  172. package/engine/scripts/lib/argent_filelock.py +507 -0
  173. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  174. package/engine/scripts/lib/cdp_ports.py +197 -0
  175. package/engine/scripts/lib/customer-env.mjs +102 -0
  176. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  177. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  178. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  179. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  180. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  181. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  182. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  183. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  184. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  185. package/engine/scripts/lib/patch-compose.mjs +241 -0
  186. package/engine/scripts/lib/patch-target.mjs +35 -0
  187. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  188. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  189. package/engine/scripts/lib/preset-compat.mjs +127 -0
  190. package/engine/scripts/lib/preset-install.mjs +575 -0
  191. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  192. package/engine/scripts/lib/preset-sync.mjs +59 -0
  193. package/engine/scripts/lib/profile-retire.mjs +97 -0
  194. package/engine/scripts/lib/python-runtime.mjs +190 -0
  195. package/engine/scripts/lib/registry.mjs +72 -0
  196. package/engine/scripts/lib/repo-root.sh +56 -0
  197. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  198. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  199. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  200. package/engine/scripts/lib/settings-panels.mjs +816 -0
  201. package/engine/scripts/lib/stdio_utf8.py +86 -0
  202. package/engine/scripts/lib/wake-auth.mjs +392 -0
  203. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  204. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  205. package/engine/scripts/license-e2e-drill.mjs +426 -0
  206. package/engine/scripts/license-gate.mjs +245 -0
  207. package/engine/scripts/lip_backlog.py +171 -0
  208. package/engine/scripts/lip_table.py +185 -0
  209. package/engine/scripts/local-usage.mjs +214 -0
  210. package/engine/scripts/migrate-providers.mjs +127 -0
  211. package/engine/scripts/migrate-skills.sh +65 -0
  212. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  213. package/engine/scripts/ml-store-ids.json +26 -0
  214. package/engine/scripts/ml_artifact_attribution.py +333 -0
  215. package/engine/scripts/ml_capacity_probe.py +1158 -0
  216. package/engine/scripts/ml_hub_status.py +1556 -0
  217. package/engine/scripts/ml_schema_check.py +115 -0
  218. package/engine/scripts/ml_store_ids.py +139 -0
  219. package/engine/scripts/npm-publish-argent.mjs +499 -0
  220. package/engine/scripts/npm-publish-argent.sh +20 -0
  221. package/engine/scripts/orchestrator.py +13811 -0
  222. package/engine/scripts/pack-plugins.mjs +140 -0
  223. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  224. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  225. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  226. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  227. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  228. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  229. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  230. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  231. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  232. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  233. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  234. package/engine/scripts/plan-role-rename.mjs +125 -0
  235. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  236. package/engine/scripts/plugin-composition.json +15 -0
  237. package/engine/scripts/poc-mcp-host.mjs +455 -0
  238. package/engine/scripts/poll-fulfillment.sh +293 -0
  239. package/engine/scripts/poll-invoice.sh +74 -0
  240. package/engine/scripts/profile-doctor.mjs +686 -0
  241. package/engine/scripts/provider-select.sh +70 -0
  242. package/engine/scripts/provider-switch.sh +146 -0
  243. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  244. package/engine/scripts/python-runtime.sh +78 -0
  245. package/engine/scripts/query_runner.py +310 -0
  246. package/engine/scripts/release-argent.sh +574 -0
  247. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  248. package/engine/scripts/run-tests.sh +256 -0
  249. package/engine/scripts/self-sign-license.mjs +179 -0
  250. package/engine/scripts/session-health-scan.mjs +63 -0
  251. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  252. package/engine/scripts/setup.sh +22 -0
  253. package/engine/scripts/skill-install.mjs +629 -0
  254. package/engine/scripts/sku_translate.py +148 -0
  255. package/engine/scripts/sync-plugins.sh +90 -0
  256. package/engine/scripts/sync-presets.sh +27 -0
  257. package/engine/scripts/sync-scripts.sh +445 -0
  258. package/engine/scripts/telemetry.py +873 -0
  259. package/engine/scripts/ui-copy-count.mjs +220 -0
  260. package/engine/scripts/usage-anchor.sh +136 -0
  261. package/engine/scripts/usage_report.py +531 -0
  262. package/engine/scripts/validate-preset-schema.mjs +208 -0
  263. package/engine/scripts/vertical-init.mjs +323 -0
  264. package/engine/scripts/vertical-lint.mjs +482 -0
  265. package/engine/scripts/wenyao-browser.sh +73 -0
  266. package/engine/scripts/wenyao-fixture-export.py +116 -0
  267. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  268. package/engine/scripts/wenyao-login.sh +34 -0
  269. package/engine/scripts/wenyao-template-map.json +292 -0
  270. package/engine/scripts/wenyao_attach.py +131 -0
  271. package/engine/scripts/wenyao_download_template.py +144 -0
  272. package/engine/scripts/wenyao_fill_template.py +568 -0
  273. package/engine/scripts/wenyao_login.py +145 -0
  274. package/engine/scripts/wenyao_portal.py +633 -0
  275. package/engine/scripts/wenyao_recon.py +230 -0
  276. package/engine/scripts/wenyao_run.py +503 -0
  277. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  278. package/engine/scripts/wenyao_skill_contract.py +156 -0
  279. package/engine/scripts/wenyao_template_fetch.py +175 -0
  280. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  281. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  282. package/engine/scripts/ziniao-keepalive.sh +216 -0
  283. package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
  284. package/engine/scripts/ziniao-windows.sh +153 -0
  285. package/engine/version.mjs +1 -0
  286. package/engine/verticals/README.md +103 -0
  287. package/engine/verticals/_engine-words.baseline.txt +237 -0
  288. package/engine/verticals/_engine-words.json +19 -0
  289. package/engine/verticals/mercadolibre/README.md +43 -0
  290. package/engine/verticals/mercadolibre/config.json +709 -0
  291. package/engine/verticals/mercadolibre/flows.json +706 -0
  292. package/engine/verticals/mercadolibre/manifest.json +538 -0
  293. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  294. package/engine/verticals/mercadolibre/preflight.py +143 -0
  295. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  296. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  297. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  298. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  299. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  300. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  301. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  302. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  303. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  304. package/package.json +21 -4
  305. package/README.md +0 -3
@@ -0,0 +1,516 @@
1
+ #!/usr/bin/env python3
2
+ """发票流程阶段 B — 鑫中航登录 + 进入单票导入页(验证用,不上传/不下载标签)
3
+ 用本机 Chrome(Playwright 独立 profile),登录态持久化复用。
4
+
5
+ 用法:
6
+ invoice_stageb.py --login 登录鑫中航 + 进入单票导入页(验证页面可达/元素定位)
7
+ invoice_stageb.py --hs "品名" 海关编码查询(hsbianma.com)
8
+ """
9
+ import json
10
+ import os
11
+ import sys
12
+ from pathlib import Path
13
+
14
+ HOME = Path.home()
15
+ # 数据目录:**唯一判据**在 `scripts/lib/argent_datadir.py`(三级链,与 JS 内核同语义)
16
+ sys.path.insert(0, str(Path(__file__).resolve().parent / 'lib'))
17
+ from argent_datadir import resolve_data_dir # noqa: E402 —— 勿在此复制判据
18
+ DATA = Path(resolve_data_dir()['dir'])
19
+ CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
20
+ PROFILE = str(DATA / "chrome-profile-xinhang")
21
+ TIMEOUT = 15000
22
+
23
+
24
+ def load_ctx():
25
+ m = json.load(open(DATA / "invoice-mapping.json"))
26
+ c = json.load(open(DATA / "config.json"))
27
+ return m, c
28
+
29
+
30
+ def browser():
31
+ from playwright.sync_api import sync_playwright
32
+ p = sync_playwright().start()
33
+ ctx = p.chromium.launch_persistent_context(
34
+ user_data_dir=PROFILE,
35
+ executable_path=CHROME,
36
+ headless=False,
37
+ viewport={"width": 1440, "height": 900},
38
+ )
39
+ return p, ctx
40
+
41
+
42
+ def login_xinhang(page, m, c):
43
+ url = m["xinzhonghang"]["url"]
44
+ user = m["xinzhonghang"]["user"]
45
+ pwd = c.get("xhInvoicePass", "")
46
+ page.goto(url, timeout=TIMEOUT, wait_until="domcontentloaded")
47
+ page.wait_for_timeout(2000)
48
+ # 会话复用探测(2026-09-08):登录 URL 固定含 /Login/(goto 即登录页),
49
+ # 直接导航 home 验证 profile 会话:能进 home = 已登录复用;被弹回 Login = 需填表。
50
+ # 避免每票/每轮重复登录(频繁登录有账号风控风险)。
51
+ base = url.replace("/Login/VueLoginView", "")
52
+ try:
53
+ page.goto(base + "/home/EleIndex?lang=cn", timeout=TIMEOUT,
54
+ wait_until="domcontentloaded")
55
+ page.wait_for_timeout(2500)
56
+ except Exception:
57
+ pass
58
+ cur = page.url or ""
59
+ if "/Login/" not in cur and ("EleIndex" in cur or "Home" in cur
60
+ or "WeclomeIndex" in cur):
61
+ print("✓ 已登录(复用会话)→", cur)
62
+ return True
63
+ # 会话失效被弹回登录页 → 走登录页填表
64
+ if "/Login/" not in cur:
65
+ try:
66
+ page.goto(url, timeout=TIMEOUT, wait_until="domcontentloaded")
67
+ page.wait_for_timeout(1500)
68
+ except Exception:
69
+ pass
70
+ # 定位账号/密码输入(Element UI 登录页:placeholder 最稳;回退可编辑输入框)
71
+ user_input = page.locator(
72
+ "input[placeholder*=账号], input[placeholder*=用户], input[placeholder*=账户], "
73
+ "input[placeholder*=用户名], input[placeholder*=账号]"
74
+ ).first
75
+ if user_input.count() == 0:
76
+ user_input = page.locator("input:not([type=password]):not([readonly])").first
77
+ pwd_input = page.locator("input[placeholder*=密码], input[type=password]").first
78
+ user_input.fill(user)
79
+ pwd_input.fill(pwd)
80
+ # 提交(登录按钮)
81
+ submit = page.locator("button[type=submit], button:has-text('登 录'), button:has-text('登录'), button:has-text('Sign in')").first
82
+ if submit.count():
83
+ submit.click()
84
+ else:
85
+ pwd_input.press("Enter")
86
+ # 等待跳转(登录成功 URL 离开 /Login/)
87
+ try:
88
+ page.wait_for_url(lambda u: "/Login/" not in u, timeout=15000)
89
+ print("✓ 登录成功 →", page.url)
90
+ return True
91
+ except Exception:
92
+ print("⚠ 未检测到跳转,当前 URL:", page.url)
93
+ return False
94
+
95
+
96
+ def step_login():
97
+ m, c = load_ctx()
98
+ p, ctx = browser()
99
+ try:
100
+ page = ctx.pages[0] if ctx.pages else ctx.new_page()
101
+ ok = login_xinhang(page, m, c)
102
+ if ok:
103
+ # 导航到「头程货运管理 → 大货订单管理」入口(探索菜单链接)
104
+ print("— 页面链接(含关键词):")
105
+ for a in page.locator("a").all()[:60]:
106
+ t = (a.inner_text() or "").strip()
107
+ href = a.get_attribute("href") or ""
108
+ if any(k in (t + href) for k in ("头程", "大货", "订单", "单票", "货运")):
109
+ print(f" [{t}] {href}")
110
+ # 尝试直接导航单票导入(常见路径探测)
111
+ for cand in ("/TruckOrder/Import", "/Import", "/BigOrder", "/CargoOrder"):
112
+ try:
113
+ page.goto(m["xinzhonghang"]["url"].replace("/Login/VueLoginView", "") + cand, timeout=8000)
114
+ page.wait_for_timeout(1000)
115
+ print(f" 尝试 {cand} → {page.url}")
116
+ if "login" not in page.url.lower():
117
+ break
118
+ except Exception:
119
+ pass
120
+ page.screenshot(path=str(DATA / "invoice-stageb-login.png"))
121
+ print("截图: ~/.dsh/argent/invoice-stageb-login.png")
122
+ finally:
123
+ p.stop()
124
+
125
+
126
+ def step_hs(name):
127
+ """hsbianma.com 海关编码查询(只读查询,取首条结果)。"""
128
+ p, ctx = browser()
129
+ try:
130
+ page = ctx.pages[0] if ctx.pages else ctx.new_page()
131
+ page.goto("https://www.hsbianma.com/", timeout=TIMEOUT, wait_until="domcontentloaded")
132
+ page.wait_for_timeout(1500)
133
+ box = page.locator("input[type=text], input[placeholder*=搜索], input[placeholder*=编码]").first
134
+ box.fill(name)
135
+ box.press("Enter")
136
+ page.wait_for_timeout(3000)
137
+ # 提取结果表(首条)
138
+ rows = page.locator("table tr").all()[:6]
139
+ for r in rows:
140
+ cells = [c.strip() for c in r.locator("td").all_inner_texts()]
141
+ if cells:
142
+ print(" | ".join(cells[:6]))
143
+ page.screenshot(path=str(DATA / "invoice-stageb-hs.png"))
144
+ finally:
145
+ p.stop()
146
+
147
+
148
+ # ── 阶段 B:鑫中航 upload + download 标签(KB-045,整合 PM 实测 v13/v27 已验证逻辑)──
149
+ # 关键事实(2026-08-29 PM 实测,勿重复摸索):
150
+ # - 搜索框 = textarea#number(class kj-textarea,placeholder「支持多个单号」)——不是 input
151
+ # - 搜索按钮 button#kjSearchBtn;搜索后校验表格数据行(排除表头/「合计」统计行)单行命中再操作
152
+ # - 勾选:目标行内 input[type=checkbox](class kj-ck-row),严禁点 kjCkAll 全选框
153
+ # - 下载弹窗 iframe /Order/SelectLableTypes,select_option(label=「大货标签」) → 点 OK(.layui-layer-btn0)
154
+ # - 下载是 popup 新窗口 /orderhebing/*_PHD.pdf(非 download 事件)→ ctx.request.get(pdf_url) 保存
155
+ # - context 必须 accept_downloads=True;登录态 profile:~/.dsh/argent/chrome-profile-xinhang
156
+ # - SOP(用户要求):上传鑫中航之前必须先回写飞书 FULL任务表「发票文件」字段
157
+ LABELS_DIR = DATA / "invoice-labels"
158
+ LABELS_DIR.mkdir(parents=True, exist_ok=True)
159
+
160
+
161
+ def browser_ac():
162
+ """带 accept_downloads 的持久化 context(登录态复用;下载标签必需)。"""
163
+ from playwright.sync_api import sync_playwright
164
+ p = sync_playwright().start()
165
+ ctx = p.chromium.launch_persistent_context(
166
+ user_data_dir=PROFILE,
167
+ executable_path=CHROME,
168
+ headless=False,
169
+ viewport={"width": 1440, "height": 900},
170
+ accept_downloads=True,
171
+ )
172
+ return p, ctx
173
+
174
+
175
+ def _click_menu(page, text):
176
+ return page.evaluate('''(text) => {
177
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
178
+ let node;
179
+ while ((node = walker.nextNode())) {
180
+ const t = (node.textContent || "").trim();
181
+ if (t.includes(text) && t.length <= 10) {
182
+ const p = node.parentElement;
183
+ (p.closest("a,li") || p).click();
184
+ return "clicked";
185
+ }
186
+ }
187
+ return "notfound";
188
+ }''', text)
189
+
190
+
191
+ def _hover_menu(page, text):
192
+ page.evaluate('''(text) => {
193
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
194
+ let node;
195
+ while ((node = walker.nextNode())) {
196
+ const t = (node.textContent || "").trim();
197
+ if (t.includes(text) && t.length <= 10) {
198
+ const p = node.parentElement;
199
+ const li = p.closest("li") || p.closest("div");
200
+ if (li) li.dispatchEvent(new MouseEvent("mouseover", {bubbles: true}));
201
+ break;
202
+ }
203
+ }
204
+ }''', text)
205
+
206
+
207
+ def _find_order_frame(page, timeout=150):
208
+ """找大货订单页 frame(含「单票导入」或「订单号码」文案)。"""
209
+ import time
210
+ t0 = time.time()
211
+ while time.time() - t0 < timeout:
212
+ for f in page.frames:
213
+ try:
214
+ body = f.evaluate("document.body ? document.body.innerText : ''")
215
+ except Exception:
216
+ body = ""
217
+ if ("单票导入" in body or "订单号码" in body) and len(body) > 1000:
218
+ return f
219
+ time.sleep(3)
220
+ return None
221
+
222
+
223
+ def _search_order(page, frame, order_no):
224
+ """搜索订单号(textarea#number,非 input)→ 返回 (数据行数, 命中数)。"""
225
+ import json
226
+ filled = frame.evaluate('''(target) => {
227
+ const out = {found: false, filled: false, value: ""};
228
+ const ta = document.querySelector("textarea#number") || document.querySelector("textarea.kj-textarea");
229
+ if (ta) {
230
+ out.found = true;
231
+ ta.focus();
232
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
233
+ setter.call(ta, target);
234
+ ta.dispatchEvent(new Event("input", {bubbles: true}));
235
+ ta.dispatchEvent(new Event("change", {bubbles: true}));
236
+ out.value = ta.value;
237
+ out.filled = true;
238
+ }
239
+ return out;
240
+ }''', order_no)
241
+ print(" 订单号码输入:", json.dumps(filled, ensure_ascii=False), flush=True)
242
+ page.wait_for_timeout(800)
243
+ clicked = frame.evaluate('''() => {
244
+ const btn = document.querySelector("button#kjSearchBtn");
245
+ if (btn) { btn.click(); return "clicked#kjSearchBtn"; }
246
+ return "notfound";
247
+ }''')
248
+ print(" 点击搜索:", clicked, flush=True)
249
+ page.wait_for_timeout(8000)
250
+ res = frame.evaluate('''(target) => {
251
+ const out = {dataRows: [], hit: 0};
252
+ document.querySelectorAll("tr").forEach(tr => {
253
+ const t = (tr.innerText || "").trim().replace(/\\s+/g, " ");
254
+ if (!t) return;
255
+ const isHeader = t.startsWith("#") || t.includes("订单号码 参考单号") || t.includes("参考单号 FBAID");
256
+ const isStat = /^合计/.test(t) || /^共\\s*\\d+\\s*条/.test(t) || t.length < 15;
257
+ if (!isHeader && !isStat) {
258
+ out.dataRows.push(t.slice(0, 300));
259
+ if (t.includes(target)) out.hit++;
260
+ }
261
+ });
262
+ return out;
263
+ }''', order_no)
264
+ print(f" 数据行={len(res['dataRows'])} 命中={res['hit']}", flush=True)
265
+ return len(res["dataRows"]), res["hit"]
266
+
267
+
268
+ def _check_target_row(frame, order_no):
269
+ """勾选目标行 checkbox(class kj-ck-row,严禁 kjCkAll)。返回是否成功。"""
270
+ return frame.evaluate('''(target) => {
271
+ const out = {found: false, checked: false};
272
+ const rows = [...document.querySelectorAll("tr")];
273
+ for (const tr of rows) {
274
+ const t = (tr.innerText || "").trim().replace(/\\s+/g, " ");
275
+ if (t.includes(target) && !t.startsWith("#")) {
276
+ const cb = tr.querySelector("input[type=checkbox]");
277
+ if (cb && !cb.id) {
278
+ if (!cb.checked) cb.click();
279
+ out.checked = cb.checked;
280
+ out.found = true;
281
+ }
282
+ break;
283
+ }
284
+ }
285
+ return out;
286
+ }''', order_no)
287
+
288
+
289
+ def download_label(order_no, save_name=None, wait_keep=15):
290
+ """按订单号搜索 → 单行校验 → 勾选 → 选大货标签模板 → 捕获 popup PDF → 保存。
291
+
292
+ 保存名默认 `运单-<order_no>.pdf`;返回保存路径或 None。
293
+ (v13 已验证逻辑入仓;PDF 经 ctx.request.get(pdf_url) 保存。)
294
+ """
295
+ import time
296
+ import traceback
297
+ m, c = load_ctx()
298
+ p, ctx = browser_ac()
299
+ try:
300
+ page = ctx.pages[0] if ctx.pages else ctx.new_page()
301
+ ok = login_xinhang(page, m, c)
302
+ print("登录:", "✓" if ok else "✗", flush=True)
303
+ if not ok:
304
+ return None
305
+ page.wait_for_timeout(3000)
306
+ menu_ok = False
307
+ for attempt in range(3):
308
+ r1 = _click_menu(page, "大货订单管理")
309
+ if r1 == "clicked":
310
+ menu_ok = True
311
+ break
312
+ _hover_menu(page, "头程运单管理")
313
+ time.sleep(1)
314
+ r2 = _click_menu(page, "大货订单管理")
315
+ if r2 == "clicked":
316
+ menu_ok = True
317
+ break
318
+ time.sleep(2)
319
+ if not menu_ok:
320
+ base = m["xinzhonghang"]["url"].replace("/Login/VueLoginView", "")
321
+ page.evaluate("location.hash = '/wuliu/KuaiJianOrderListView'")
322
+ frame = _find_order_frame(page)
323
+ if not frame:
324
+ print("✗ 订单页未就绪", flush=True)
325
+ return None
326
+ print("订单页就绪", flush=True)
327
+ page.wait_for_timeout(4000)
328
+ n_rows, n_hit = _search_order(page, frame, order_no)
329
+ if not (n_rows == 1 and n_hit == 1):
330
+ print("⚠ 未确认单行命中,不勾选不下载(安全保护)", flush=True)
331
+ return None
332
+ chk = _check_target_row(frame, order_no)
333
+ print(" 勾选目标行:", json.dumps(chk, ensure_ascii=False), flush=True)
334
+ page.wait_for_timeout(1500)
335
+ dl = frame.evaluate('''() => {
336
+ const els = [...document.querySelectorAll("button,a")];
337
+ let el = els.find(e => (e.innerText||"").trim() === "下载订单标签");
338
+ if (!el) el = els.find(e => (e.innerText||"").trim() === "下载标签");
339
+ if (el) { el.click(); return (el.innerText||"").trim(); }
340
+ return "notfound";
341
+ }''')
342
+ print(" 点击下载:", dl, flush=True)
343
+ # 弹窗 iframe 轮询等待(最长 25s——Mac mini 实测 5s 固定等待不足)
344
+ iframe_ = None
345
+ import time as _t
346
+ _t0 = _t.time()
347
+ while _t.time() - _t0 < 25:
348
+ for f in page.frames:
349
+ if "SelectLableTypes" in (f.url or ""):
350
+ iframe_ = f
351
+ break
352
+ if iframe_:
353
+ break
354
+ page.wait_for_timeout(2000)
355
+ if not iframe_:
356
+ print("⚠ 未找到 SelectLableTypes iframe", flush=True)
357
+ return None
358
+ print(" 标签类型 iframe 就绪", flush=True)
359
+ try:
360
+ iframe_.locator("select").first.select_option(label="大货标签")
361
+ print(" select_option 大货标签: OK", flush=True)
362
+ except Exception as e:
363
+ print(" select_option 失败:", str(e)[:100], flush=True)
364
+ page.wait_for_timeout(1500)
365
+ pdf_url = {"url": None}
366
+
367
+ def on_response(resp):
368
+ try:
369
+ u = resp.url
370
+ if "orderhebing" in u and ".pdf" in u.lower():
371
+ pdf_url["url"] = u
372
+ except Exception:
373
+ pass
374
+ ctx.on("response", on_response)
375
+ ok_r = frame.evaluate('''() => {
376
+ const el = document.querySelector(".layui-layer-btn0") ||
377
+ [...document.querySelectorAll("a,button")].find(e => (e.innerText||"").trim() === "OK");
378
+ if (el) { el.click(); return "clicked"; }
379
+ return "notfound";
380
+ }''')
381
+ print(" 点击 OK:", ok_r, flush=True)
382
+ page.wait_for_timeout(15000)
383
+ if not pdf_url["url"]:
384
+ print("⚠ 未捕获 PDF URL", flush=True)
385
+ return None
386
+ url = pdf_url["url"]
387
+ print(" PDF URL:", url, flush=True)
388
+ resp = ctx.request.get(url, timeout=60000)
389
+ print(" HTTP:", resp.status, flush=True)
390
+ if not resp.ok:
391
+ print(" 下载 HTTP 失败:", resp.status, flush=True)
392
+ return None
393
+ body = resp.body()
394
+ save_path = str(LABELS_DIR / (save_name or f"运单-{order_no}.pdf"))
395
+ Path(save_path).write_bytes(body)
396
+ print(" 保存:", save_path, "大小:", len(body), flush=True)
397
+ return save_path
398
+ except Exception:
399
+ traceback.print_exc()
400
+ return None
401
+ finally:
402
+ try:
403
+ p.stop()
404
+ except Exception:
405
+ pass
406
+
407
+
408
+ def upload_invoice(invoice_path, order_no=None, wait_keep=20):
409
+ """上传发票到鑫中航单票导入(v27 已验证逻辑入仓)。
410
+
411
+ 前置(SOP):调用前必须先回写飞书 FULL任务表「发票文件」字段(顺序固化)。
412
+ invoice_path: 本地 xlsx 绝对路径;order_no: 订单号(可选,仅日志)。
413
+ """
414
+ import time
415
+ import traceback
416
+ m, c = load_ctx()
417
+ p, ctx = browser()
418
+ try:
419
+ page = ctx.pages[0] if ctx.pages else ctx.new_page()
420
+ ok = login_xinhang(page, m, c)
421
+ print("登录:", "✓" if ok else "✗", flush=True)
422
+ if not ok:
423
+ return False
424
+ page.wait_for_timeout(3000)
425
+ menu_ok = False
426
+ for attempt in range(3):
427
+ r1 = _click_menu(page, "大货订单管理")
428
+ if r1 == "clicked":
429
+ menu_ok = True
430
+ break
431
+ _hover_menu(page, "头程运单管理")
432
+ time.sleep(1)
433
+ r2 = _click_menu(page, "大货订单管理")
434
+ if r2 == "clicked":
435
+ menu_ok = True
436
+ break
437
+ time.sleep(2)
438
+ if not menu_ok:
439
+ base = m["xinzhonghang"]["url"].replace("/Login/VueLoginView", "")
440
+ page.evaluate("location.hash = '/wuliu/KuaiJianOrderListView'")
441
+ frame = _find_order_frame(page)
442
+ if not frame:
443
+ print("✗ 订单页未就绪", flush=True)
444
+ return False
445
+ print("订单页就绪,开始上传", flush=True)
446
+ r = frame.evaluate('''() => {
447
+ const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
448
+ let node;
449
+ while ((node = walker.nextNode())) {
450
+ const t = (node.textContent || "").trim();
451
+ if (t.includes("单票导入")) {
452
+ const p = node.parentElement;
453
+ (p.closest("a,button,li") || p).click();
454
+ return "clicked";
455
+ }
456
+ }
457
+ return "notfound";
458
+ }''')
459
+ print(" 点击单票导入:", r, flush=True)
460
+ page.wait_for_timeout(6000)
461
+ fi = frame.locator("input[type=file]")
462
+ if fi.count():
463
+ fi.first.set_input_files(invoice_path)
464
+ print(" 已选择文件:", invoice_path, flush=True)
465
+ page.wait_for_timeout(6000)
466
+ up = frame.evaluate('''() => {
467
+ const els = [...document.querySelectorAll("a,button")];
468
+ const el = els.find(e => (e.innerText||"").trim() === "开始上传");
469
+ if (el) { el.click(); return "clicked"; }
470
+ return "notfound";
471
+ }''')
472
+ print(" 点击开始上传:", up, flush=True)
473
+ page.wait_for_timeout(15000)
474
+ st = frame.evaluate('''() => {
475
+ const out = {dialogs: []};
476
+ document.querySelectorAll(".layui-layer").forEach(d => {
477
+ out.dialogs.push((d.innerText||"").trim().replace(/\\s+/g," ").slice(0, 300));
478
+ });
479
+ return out;
480
+ }''')
481
+ print(" === 上传结果 ===", flush=True)
482
+ print(json.dumps(st, ensure_ascii=False, indent=1)[:1500], flush=True)
483
+ return True
484
+ except Exception:
485
+ traceback.print_exc()
486
+ return False
487
+ finally:
488
+ try:
489
+ p.stop()
490
+ except Exception:
491
+ pass
492
+
493
+
494
+ if __name__ == "__main__":
495
+ if "--login" in sys.argv:
496
+ step_login()
497
+ elif "--hs" in sys.argv:
498
+ i = sys.argv.index("--hs")
499
+ step_hs(sys.argv[i + 1] if len(sys.argv) > i + 1 else "背包")
500
+ elif "--download-label" in sys.argv:
501
+ i = sys.argv.index("--download-label")
502
+ order = sys.argv[i + 1] if len(sys.argv) > i + 1 else ""
503
+ if not order:
504
+ print("用法: invoice_stageb.py --download-label <订单号> [保存名]")
505
+ else:
506
+ save = sys.argv[i + 2] if len(sys.argv) > i + 2 else None
507
+ print("下载结果:", download_label(order, save))
508
+ elif "--upload" in sys.argv:
509
+ i = sys.argv.index("--upload")
510
+ path = sys.argv[i + 1] if len(sys.argv) > i + 1 else ""
511
+ if not path:
512
+ print("用法: invoice_stageb.py --upload <xlsx绝对路径>")
513
+ else:
514
+ print("上传结果:", upload_invoice(path))
515
+ else:
516
+ print(__doc__)
@@ -0,0 +1,42 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>Label</key>
6
+ <string>com.whyshu.argent-bridge</string>
7
+ <key>ProgramArguments</key>
8
+ <array>
9
+ <string>/opt/homebrew/bin/node</string>
10
+ <string>__USER_HOME__/.argent-dsh/scripts/feishu-bridge.js</string>
11
+ <string>--role</string>
12
+ <string>general</string>
13
+ </array>
14
+ <key>EnvironmentVariables</key>
15
+ <dict>
16
+ <key>PATH</key>
17
+ <string>/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
18
+ <key>HOME</key>
19
+ <string>__USER_HOME__</string>
20
+ <key>USER</key>
21
+ <string>chester</string>
22
+ <key>TMPDIR</key>
23
+ <string>/tmp</string>
24
+ <key>LANG</key>
25
+ <string>zh_CN.UTF-8</string>
26
+ <key>LC_ALL</key>
27
+ <string>zh_CN.UTF-8</string>
28
+ </dict>
29
+ <key>WorkingDirectory</key>
30
+ <string>/tmp</string>
31
+ <key>RunAtLoad</key>
32
+ <true/>
33
+ <key>KeepAlive</key>
34
+ <true/>
35
+ <key>ProcessType</key>
36
+ <string>Background</string>
37
+ <key>StandardOutPath</key>
38
+ <string>__USER_HOME__/.dsh/argent/logs/bridge.log</string>
39
+ <key>StandardErrorPath</key>
40
+ <string>__USER_HOME__/.dsh/argent/logs/bridge.log</string>
41
+ </dict>
42
+ </plist>
@@ -0,0 +1,29 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <!-- KB-086:FULL 轮询 LaunchAgent(绕开 cron /var/at/tabs 权限问题,与 invoice-poll 同机制)
4
+ 部署到 ~/Library/LaunchAgents/com.whyshu.fulfillment-poll.plist(路径按实际用户调整)后:
5
+ launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.whyshu.fulfillment-poll.plist -->
6
+ <plist version="1.0">
7
+ <dict>
8
+ <key>Label</key>
9
+ <string>com.whyshu.fulfillment-poll</string>
10
+ <key>ProgramArguments</key>
11
+ <array>
12
+ <string>/bin/bash</string>
13
+ <string>/Users/huangwei/.argent-dsh/scripts/poll-fulfillment.sh</string>
14
+ </array>
15
+ <key>StartInterval</key>
16
+ <integer>300</integer>
17
+ <!-- 2026-09-03 修复:缺此键时 launchd 在 poll 脚本退出后清理其 nohup 后台子进程
18
+ (orchestrator 批量进程启动即被 kill、batch.log 0 字节、就绪记录永不执行);
19
+ 加 AbandonProcessGroup=true 让 spawn 的 orchestrator 存活(与手动 SSH 跑 poll 行为一致) -->
20
+ <key>AbandonProcessGroup</key>
21
+ <true/>
22
+ <key>RunAtLoad</key>
23
+ <true/>
24
+ <key>StandardOutPath</key>
25
+ <string>/Users/huangwei/.dsh/argent/fulfillment-logs/launchd.out.log</string>
26
+ <key>StandardErrorPath</key>
27
+ <string>/Users/huangwei/.dsh/argent/fulfillment-logs/launchd.err.log</string>
28
+ </dict>
29
+ </plist>