@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,197 @@
1
+ #!/usr/bin/env python3
2
+ """cdp_ports —— 「本机 CDP 调试端口」的**唯一实现**:发现 / 归属 / 页面探针(**只读**)。
3
+
4
+ ## 为什么有这个模块(2026-10-11 生产现场:真判据是端口,不是云端清单)
5
+
6
+ 客户机 FULL 触发器被认领前环境预检挡下("Bridge 正常但没有任何店铺")的恢复窗口里,逐步量到:
7
+
8
+ | 事实 | 实测 |
9
+ |---|---|
10
+ | ① 客户端自带的清单命令 `store list` | ✅ 能列出 6 个店铺(CLI 通道正常) |
11
+ | ② **WebDriver API 的 `getBrowserList`** | ❌ `statusCode=0, data=None`(**0 个店铺**)⇒ `startBrowser` 报 `-10004 browserId或browserOauth缺失` ⇒ **API 开窗这条路当场不可用** |
12
+ | ③ 改用 **CLI 开窗** | ✅ `✓ 店铺已打开` ⇒ 窗口真开出来了、CDP 可连 |
13
+ | ④ 但 `getRunningInfo`(上一批那条"本地已运行窗口"回退的判据)**也是 0** | ❌ ⇒ **那条回退在这个现场无效**(窗口明明在跑) |
14
+ | ⑤ 真正救回来的判据 | `lsof` 找客户端监听的端口 → 逐个 `GET /json/version` 校验 → 按 **`--user-data-dir=…/chrome_<store_id>`** 归属到具体店铺:实测 `[13018, 27893]`(1 店 → 27893、2 店 → 13018) |
15
+
16
+ ⇒ 判"环境能不能干活"时,**端口 + 归属**是比两个云端/API 清单都更贴近事实的证据。
17
+ 那段判据原本只写在 `scripts/orchestrator.py` 里;本模块把它抽出来,让**编排器**与
18
+ **认领前环境预检**(`scripts/env_probe.py`)**共用同一份**(⛔ 全仓库不得出现第二份端口发现/归属实现,
19
+ 回归 `scripts/tests/env-probe-cdp-port-fallback.test.py` 有静态判据钉住)。
20
+
21
+ ## 只读纪律(本模块的硬边界)
22
+ - 只做三件事:`GET /json/version`、`GET /json/list`、`lsof`/`ps` **只读查询**。
23
+ - ⛔ 不含任何生命周期动作(开/关窗口、内核更新、授权、清缓存…),⛔ 不写任何文件、⛔ 不读凭据。
24
+ - ⛔ **不猜**:归属确认不到就返回 `None`(调用方**不得**取 `ports[0]` 当目标店铺)。
25
+
26
+ ## 分层与命名
27
+ L1 执行内核(`scripts/`)⇒ 只 import 标准库;⛔ 不 import 宿主模块(`@deepseek-ai/*`、`cordis`、
28
+ `dsh-*`、`plugins/argent-*`)。门禁:`scripts/tests/kernel-purity.test.mjs`。
29
+ ⚠️ 文件名与正文**刻意不带任何业务线词汇**(门禁 `scripts/vertical-lint.mjs` 的 engine-words 档:
30
+ 引擎文件不得新增业务词,⛔ 不抬基线)——"是哪个厂商的客户端"由调用方决定:
31
+ **客户端进程名**(`lsof` 行里的匹配子串)由调用方**按参数**传进来,本模块不认识任何厂商。
32
+ """
33
+ from __future__ import annotations
34
+ import json
35
+ import re
36
+ import subprocess
37
+ import urllib.request
38
+ from typing import Any, Callable, Optional, Sequence
39
+
40
+ # 进程名匹配不上时的**已知端口段兜底**(逐个 `/json/version` 试)——与既有实现逐字一致。
41
+ DEFAULT_PORT_RANGE = (52420, 52461)
42
+ # 店铺 user-data 目录前缀:客户端把每个店铺的窗口数据放在 `…/userdata/<前缀><store_id>`(实测可靠)。
43
+ USER_DATA_DIR_PREFIX = "chrome_"
44
+
45
+
46
+ def _default_fetch(url: str, timeout: float) -> str:
47
+ """本机回环只读取文本(测试注入点:`fetch(url, timeout)`)。"""
48
+ with urllib.request.urlopen(url, timeout=timeout) as resp: # noqa: S310(本机回环只读)
49
+ return resp.read().decode("utf-8", "replace")
50
+
51
+
52
+ def is_cdp_port(port: int, timeout: float = 1.0) -> bool:
53
+ """`GET /json/version` 可读且含 `Browser` ⇒ 这是**浏览器调试端口**(排除客户端本地 API 端口)。"""
54
+ try:
55
+ with urllib.request.urlopen(
56
+ f"http://127.0.0.1:{int(port)}/json/version", timeout=timeout) as r: # noqa: S310
57
+ return b"Browser" in r.read(200)
58
+ except Exception: # noqa: BLE001
59
+ return False
60
+
61
+
62
+ def client_ports(process_markers: Sequence[str], timeout: float = 10.0) -> list[int]:
63
+ """客户端进程**监听**的本机端口(`lsof -i -P -n` 按进程名子串过滤)⇒ 逐个 `/json/version` 校验。
64
+
65
+ `process_markers` = 进程名匹配子串(大小写不敏感;空序列 ⇒ 匹配不到任何进程 ⇒ 返回 `[]`,
66
+ 由调用方决定是否接受"只剩端口段兜底"的结论)。
67
+ """
68
+ markers = tuple(str(m).lower() for m in (process_markers or ()) if str(m).strip())
69
+ if not markers:
70
+ return []
71
+ ports: list[int] = []
72
+ try:
73
+ out = subprocess.run(["lsof", "-i", "-P", "-n"],
74
+ capture_output=True, text=True, timeout=timeout)
75
+ for line in (out.stdout or "").splitlines():
76
+ low = line.lower()
77
+ if any(m in low for m in markers) and "LISTEN" in line:
78
+ m = re.search(r"127\.0\.0\.1:(\d+)", line)
79
+ if m:
80
+ ports.append(int(m.group(1)))
81
+ ports = [p for p in sorted(set(ports)) if is_cdp_port(p)]
82
+ except Exception: # noqa: BLE001
83
+ ports = []
84
+ return ports
85
+
86
+
87
+ def discover_ports(process_markers: Sequence[str], *,
88
+ port_range: tuple[int, int] = DEFAULT_PORT_RANGE,
89
+ timeout: float = 1.0,
90
+ range_timeout: float = 0.5,
91
+ lsof_timeout: float = 10.0) -> list[int]:
92
+ """跨平台 CDP 端口发现:先按客户端进程名找监听端口(逐个 `/json/version` 校验),
93
+ **一个都没匹配到**时回退已知端口段(`port_range` 半开区间)逐个试。
94
+
95
+ ⛔ 只读;⛔ 不猜归属(那一步是 `match_port_by_user_data_dir`)。
96
+ """
97
+ ports = client_ports(process_markers, timeout=lsof_timeout)
98
+ if not ports:
99
+ for p in range(int(port_range[0]), int(port_range[1])):
100
+ if is_cdp_port(p, timeout=range_timeout):
101
+ ports.append(p)
102
+ return sorted(set(ports))
103
+
104
+
105
+ def listener_commands(port: int, *, lsof_timeout: float = 10.0,
106
+ ps_timeout: float = 5.0) -> list[str]:
107
+ """监听该端口的进程**命令行**(`lsof -iTCP:<port> -sTCP:LISTEN` 拿 pid → `ps` 拿 command)。
108
+
109
+ 任何异常/取不到 ⇒ `[]`("查不到"由调用方按"没命中"处理,⛔ 不猜)。
110
+ """
111
+ try:
112
+ lines = subprocess.run(["lsof", f"-iTCP:{int(port)}", "-sTCP:LISTEN"],
113
+ capture_output=True, text=True,
114
+ timeout=lsof_timeout).stdout or ""
115
+ except Exception: # noqa: BLE001
116
+ return []
117
+ out: list[str] = []
118
+ for line in lines.splitlines():
119
+ parts = line.split()
120
+ if len(parts) < 2:
121
+ continue
122
+ try:
123
+ pid = int(parts[1])
124
+ except ValueError:
125
+ continue
126
+ try:
127
+ cmd = subprocess.run(["ps", "-p", str(pid), "-o", "command="],
128
+ capture_output=True, text=True,
129
+ timeout=ps_timeout).stdout or ""
130
+ except Exception: # noqa: BLE001
131
+ continue
132
+ if cmd.strip():
133
+ out.append(cmd)
134
+ return out
135
+
136
+
137
+ def match_port_by_user_data_dir(store_id: str, ports: Sequence[int], *,
138
+ dir_prefix: str = USER_DATA_DIR_PREFIX,
139
+ lsof_timeout: float = 10.0,
140
+ ps_timeout: float = 5.0) -> Optional[int]:
141
+ """**归属判据**:进程命令行里含 `--user-data-dir=…/<dir_prefix><store_id>` ⇒ 该端口属于这家店。
142
+
143
+ 按 `ports` 的**给定顺序**取第一个命中的(与既有实现逐字同序);一个都不命中 ⇒ `None`
144
+ (调用方**不得**退化成"取 `ports[0]`"——多店会错配到别人家的窗口)。
145
+ """
146
+ if not store_id:
147
+ return None
148
+ needle = f"{dir_prefix}{store_id}"
149
+ for port in ports:
150
+ cmds = listener_commands(port, lsof_timeout=lsof_timeout, ps_timeout=ps_timeout)
151
+ if any(needle in cmd for cmd in cmds):
152
+ return int(port)
153
+ return None
154
+
155
+
156
+ def page_targets(port: int, timeout: float = 2.0,
157
+ fetch: Optional[Callable[[str, float], str]] = None) -> list[dict[str, Any]]:
158
+ """只读 `GET http://127.0.0.1:<port>/json/list` ⇒ 可附加的 `type=="page"` 目标。
159
+
160
+ 「**页面可达**」判据用它:`/json/version` 只证明浏览器进程在,**不证明有可驱动的标签页**
161
+ (实测过"进程在、目标被关掉"的形态)。⛔ 任何异常都吞成 `[]`("查不到"绝不当"可达")。
162
+ """
163
+ getter = fetch or _default_fetch
164
+ try:
165
+ raw = getter(f"http://127.0.0.1:{int(port)}/json/list", timeout)
166
+ items = json.loads(raw or "[]")
167
+ except Exception: # noqa: BLE001
168
+ return []
169
+ if not isinstance(items, list):
170
+ return []
171
+ return [t for t in items
172
+ if isinstance(t, dict)
173
+ and str(t.get("type") or "") == "page"
174
+ and str(t.get("webSocketDebuggerUrl") or "")]
175
+
176
+
177
+ def browser_version(port: int, timeout: float = 2.0,
178
+ fetch: Optional[Callable[[str, float], str]] = None) -> str:
179
+ """`GET /json/version` 的 `Browser` 值 → **版本号**(`Chrome/142.0.7444.168` → `142.0.7444.168`)。
180
+
181
+ 只用于把证据行写成与窗口清单同一形状(`店名:端口@内核(handle=…)`);取不到 ⇒ `''`。
182
+ """
183
+ getter = fetch or _default_fetch
184
+ try:
185
+ doc = json.loads(getter(f"http://127.0.0.1:{int(port)}/json/version", timeout) or "{}")
186
+ browser = str((doc or {}).get("Browser") or "")
187
+ except Exception: # noqa: BLE001
188
+ return ""
189
+ return browser.rsplit("/", 1)[-1].strip()
190
+
191
+
192
+ def target_handle(targets: Sequence[dict[str, Any]]) -> str:
193
+ """首个 page 目标的 `id`(CDP 附着的句柄);没有 ⇒ `''`。"""
194
+ for t in (targets or ()):
195
+ if isinstance(t, dict) and t.get("id"):
196
+ return str(t["id"])
197
+ return ""
@@ -0,0 +1,102 @@
1
+ /**
2
+ * 客户机 env 键迁移(**纯函数 + 依赖注入**,可离线测试)。
3
+ *
4
+ * 由来(2026-09-28 客户机 P9 预检 6 红):客户机的 `~/.dsh/argent/fulfillment.env` 是旧一代
5
+ * (缺 `FULL_LARK_PROFILE`)⇒ 流程读客户 Base 时身份解析不出来 ⇒ 91403/99991672,
6
+ * 整条 FULL 流程读不了表。而"该用哪个身份"**必须实测**:各机器 lark-cli 的 profile 命名
7
+ * 并不一致(开发机叫 `lip-customer`,客户机用 appId `cli_aaf7cafb66b85ccc`),
8
+ * 且同名不同 app 的坑真实存在(客户机的 `ml-fulfillment` 指向缺 base:record:read 的 app)。
9
+ *
10
+ * 所以:**逐个候选身份真读一次表**,谁读得通就用谁;一个都读不通就响亮失败、绝不猜。
11
+ */
12
+ import { spawnSync } from 'node:child_process'
13
+
14
+ /**
15
+ * 列出本机 lark-cli 的候选身份(profile 名)。
16
+ * @param {(cmd: string, args: string[]) => {status: number, stdout: string}} run
17
+ * @returns {string[]}
18
+ */
19
+ export function listLarkProfiles(run) {
20
+ const r = run('lark-cli', ['profile', 'list'])
21
+ if (r.status !== 0) return []
22
+ try {
23
+ const arr = JSON.parse(r.stdout)
24
+ if (!Array.isArray(arr)) return []
25
+ return arr.map((p) => p.name || p.appId).filter(Boolean)
26
+ } catch {
27
+ return []
28
+ }
29
+ }
30
+
31
+ /**
32
+ * 探测哪个身份能读指定的 base/table(窄投影 1 列 1 条,只读)。
33
+ * @returns {{ profile: string|null, tried: {profile: string, ok: boolean, code?: number, msg?: string}[] }}
34
+ */
35
+ export function findBaseReader({ run, baseToken, tableId, fieldId, profiles }) {
36
+ const tried = []
37
+ for (const profile of profiles) {
38
+ const r = run('lark-cli', [
39
+ '--profile', profile, 'base', '+record-list',
40
+ '--base-token', baseToken, '--table-id', tableId,
41
+ '--as', 'bot', '--format', 'json', '--field-id', fieldId, '--page-size', '1',
42
+ ])
43
+ if (r.status === 0) {
44
+ tried.push({ profile, ok: true })
45
+ return { profile, tried }
46
+ }
47
+ let code
48
+ let msg = ''
49
+ try { const j = JSON.parse(r.stdout); code = j?.error?.code; msg = j?.error?.subtype || j?.error?.message || '' } catch { msg = (r.stdout || '').slice(0, 80) }
50
+ tried.push({ profile, ok: false, code, msg })
51
+ }
52
+ return { profile: null, tried }
53
+ }
54
+
55
+ /** 默认 runner(真实进程;测试注入假 runner)。 */
56
+ export function realRun(cmd, args) {
57
+ const r = spawnSync(cmd, args, { encoding: 'utf8', timeout: 60000 })
58
+ return { status: r.status ?? 1, stdout: `${r.stdout || ''}${r.stderr || ''}` }
59
+ }
60
+
61
+ /**
62
+ * 解析 env 文本为键值(保留注释与空行由调用方用 applyEnvKeys 处理)。
63
+ */
64
+ export function parseEnvKeys(text) {
65
+ const out = {}
66
+ for (const line of String(text).split('\n')) {
67
+ const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/.exec(line)
68
+ if (m) out[m[1]] = m[2]
69
+ }
70
+ return out
71
+ }
72
+
73
+ /**
74
+ * 把缺失的键**追加**到 env 文本末尾(不动已有键、不动注释;已有键默认不覆盖)。
75
+ * @returns {{ text: string, added: string[], skipped: {key: string, reason: string}[] }}
76
+ */
77
+ export function applyEnvKeys(text, keys, { force = false } = {}) {
78
+ const existing = parseEnvKeys(text)
79
+ const added = []
80
+ const skipped = []
81
+ const lines = String(text).replace(/\n*$/, '\n').split('\n')
82
+ const insert = []
83
+ for (const [key, value] of Object.entries(keys)) {
84
+ if (value === undefined || value === null || value === '') { skipped.push({ key, reason: '无可用取值(探测未命中)' }); continue }
85
+ if (existing[key] !== undefined && !force) { skipped.push({ key, reason: `已存在(${existing[key]})` }); continue }
86
+ if (existing[key] !== undefined && force) {
87
+ const idx = lines.findIndex((l) => new RegExp(`^\\s*${key}\\s*=`).test(l))
88
+ lines[idx] = `${key}=${value}`
89
+ added.push(key)
90
+ continue
91
+ }
92
+ insert.push(`${key}=${value}`)
93
+ added.push(key)
94
+ }
95
+ if (insert.length) {
96
+ // 插在最后一个非空行之后,避免落进注释块
97
+ let at = lines.length
98
+ while (at > 0 && lines[at - 1].trim() === '') at--
99
+ lines.splice(at, 0, ...insert)
100
+ }
101
+ return { text: lines.join('\n'), added, skipped }
102
+ }