@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,70 @@
1
+ #!/usr/bin/env bash
2
+ # provider-select —— 在**两个并存 provider** 之间切换「默认 provider」(2026-09-30,Argent 0.3)
3
+ #
4
+ # ## 为什么不是改 baseURL
5
+ # 旧脚本 provider-switch.sh 直接改写 `llm-deepseek.baseURL` ⇒ **劫持官方直连 provider**
6
+ # (provider id 仍叫 deepseek-official,实际指向问述)。用户纠正后改为:
7
+ # · `llm-deepseek` 实例 = **真·官方**(api.deepseek.com / DEEPSEEK_API_KEY)
8
+ # · `llm-whyshu` 实例 = **问述代理**(whyshu.com/api/argent/v1 / WHYSHU_API_KEY,provider id=whyshu)
9
+ # 本脚本只切 `agent-default-model.config.provider`(用户仍可在设置页自由选择,二者并存)。
10
+ #
11
+ # 用法:
12
+ # bash scripts/provider-select.sh status # 当前默认 provider + 两个 provider 的地址
13
+ # bash scripts/provider-select.sh whyshu # 默认走问述代理(扣积分)
14
+ # bash scripts/provider-select.sh deepseek # 默认走官方直连(自带 Key,不扣积分)
15
+ set -uo pipefail
16
+ PATCH="${ARGENT_CORDIS_PATCH:-$HOME/.dsh/profiles/web/cordis.patch.yml}"
17
+ PY="${ARGENT_PY:-$HOME/.dsh/argent/venv/bin/python3}"; [ -x "$PY" ] || PY=python3
18
+
19
+ [ -f "$PATCH" ] || { echo "✗ 找不到组合补丁:$PATCH" >&2; exit 2; }
20
+
21
+ "$PY" - "$PATCH" "${1:-status}" <<'PY'
22
+ import sys, yaml, pathlib, shutil, datetime
23
+ path, action = sys.argv[1], sys.argv[2]
24
+ p = pathlib.Path(path)
25
+ doc = yaml.safe_load(p.read_text(encoding='utf-8')) or []
26
+
27
+ def entry(eid):
28
+ return next((e for e in doc if isinstance(e, dict) and e.get('id') == eid), None)
29
+
30
+ def provider_of(eid):
31
+ e = entry(eid) or {}
32
+ return ((e.get('config') or {}).get('apiKeyEnv') or '').replace('_API_KEY', '')
33
+
34
+ default = entry('agent-default-model') or {}
35
+ cur = (default.get('config') or {}).get('provider') or '(未设置)'
36
+ urls = {}
37
+ for eid in ('llm-deepseek', 'llm-whyshu'):
38
+ e = entry(eid)
39
+ urls[eid] = (e or {}).get('name', '(缺失)')
40
+
41
+ if action == 'status':
42
+ print(f" 当前默认 provider: {cur}")
43
+ print(f" provider 实例: llm-deepseek → {urls['llm-deepseek']}(官方直连,{provider_of('llm-deepseek')})")
44
+ print(f" llm-whyshu → {urls['llm-whyshu']}(问述代理,{provider_of('llm-whyshu')})")
45
+ print(f" 语义: provider=whyshu ⇒ 扣积分;provider=deepseek-official ⇒ 官方直连不扣积分")
46
+ print(f" 提示: 两者**并存**,用户也可在设置页按会话/按 agent 选择;改默认值需重启生效")
47
+ sys.exit(0)
48
+
49
+ want = {'whyshu': 'whyshu', 'deepseek': 'deepseek-official', 'deepseek-official': 'deepseek-official'}.get(action)
50
+ if not want:
51
+ print(f"✗ 未知动作:{action}(可用 status|whyshu|deepseek)", file=sys.stderr); sys.exit(2)
52
+ if cur == want:
53
+ print(f" · 默认 provider 已是 {want}(无需改动)"); sys.exit(0)
54
+
55
+ shutil.copy2(p, str(p) + f".bak-select-{datetime.datetime.now():%Y%m%d-%H%M%S}")
56
+ lines = p.read_text(encoding='utf-8').split('\n')
57
+ for i, l in enumerate(lines):
58
+ if l.strip() == '- id: agent-default-model':
59
+ for j in range(i, min(i + 8, len(lines))):
60
+ if lines[j].strip().startswith('provider:'):
61
+ indent = lines[j][:len(lines[j]) - len(lines[j].lstrip())]
62
+ lines[j] = f"{indent}provider: {want}"
63
+ break
64
+ break
65
+ else:
66
+ print('✗ 组合里找不到 agent-default-model 条目', file=sys.stderr); sys.exit(2)
67
+ p.write_text('\n'.join(lines), encoding='utf-8')
68
+ yaml.safe_load(p.read_text(encoding='utf-8')) # 回读校验:YAML 必须仍合法
69
+ print(f" ✅ 默认 provider: {cur} → {want}(已备份;**重启 dsh 后生效**:bash scripts/dsh-restart.sh)")
70
+ PY
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env bash
2
+ # ============================================================
3
+ # Argent 计费模式切换
4
+ # 问述积分(whyshu 代理)↔ DeepSeek 官方直连(API Key)
5
+ #
6
+ # 用法:
7
+ # bash provider-switch.sh whyshu # 切到问述积分(llm-deepseek 段)
8
+ # bash provider-switch.sh deepseek # 切到 DeepSeek 官方直连(删 llm-deepseek 段)
9
+ # bash provider-switch.sh status # 查看当前模式
10
+ #
11
+ # 原理: dsh 的 llm-deepseek 段覆盖 deepseek-official adapter 的 baseURL
12
+ # (settings.yaml 热加载,新请求即时生效,无需重启)
13
+ # ============================================================
14
+ set -euo pipefail
15
+
16
+ SETTINGS="${SETTINGS:-$HOME/.dsh/settings.yaml}"
17
+ MODE="${1:-}"
18
+
19
+ # 缺 settings.yaml = 无代理段 = 官方直连(正常状态,不是错误)——2026-09-29 修:
20
+ # 此前 status 会直接 FileNotFoundError 崩掉,让人误以为"配置坏了"。
21
+ if [ "$MODE" = "status" ] && [ ! -f "$SETTINGS" ]; then
22
+ echo "当前模式:DeepSeek 官方直连($SETTINGS 不存在 ⇒ 无代理段,不扣积分)"
23
+ exit 0
24
+ fi
25
+
26
+ python3 - "$SETTINGS" "$MODE" <<'PY'
27
+ import re
28
+ import sys
29
+ import json
30
+ import os
31
+ import urllib.request
32
+
33
+ path, mode = sys.argv[1], sys.argv[2]
34
+ # 缺文件=官方直连(正常状态):容错读成空串,让 whyshu 分支能把代理段**写出来**、
35
+ # deepseek 分支把代理段清掉。2026-09-29 实测:无条件 open() ⇒ 一旦 settings.yaml 不存在,
36
+ # 连「切回积分模式」都会 FileNotFoundError 崩掉(用户报「积分计费模式使用不了」的直接原因)。
37
+ text = open(path, encoding="utf-8").read() if os.path.exists(path) else ""
38
+
39
+ # 与 configure-provider.rb 保持一致的代理段(幂等写入)
40
+ # 模型清单**不再写死**(2026-09-22 用户反馈"积分模式下选不到新模型"):
41
+ # 从前端点的 /models 取"可用且已计价"的清单(服务端已按上游∩费率过滤),取不到才用下面的兜底。
42
+ MODELS_URL = os.environ.get("ARGENT_MODELS_URL", "https://whyshu.com/api/argent/v1/models")
43
+ FALLBACK_MODELS = ["deepseek-flash", "deepseek-v4-pro"] # 规范名优先(V4.1-Flash) # 兜底里**不放**上游已下线的 deepseek-chat
44
+
45
+ DISPLAY = {
46
+ "deepseek-flash": "DeepSeek V4.1 Flash(问述代理)",
47
+ "deepseek-v4-flash": "DeepSeek V4 Flash(问述代理,旧名)",
48
+ "deepseek-v4-pro": "DeepSeek V4 Pro(问述代理)",
49
+ }
50
+
51
+
52
+ def read_key() -> str:
53
+ """取账号 Key:环境变量 → dsh 凭据 refs 段(**只读**结构化解析)→ ~/.dsh/argent/whyshu.env。
54
+
55
+ 为什么需要(2026-09-22 实测):代理商 Key 平时在 dsh 的凭据库里、**不在环境变量**里,
56
+ 于是本脚本取清单时被 401 挡回、静默走兜底 —— 清单就永远不会自动跟随上游。
57
+ 注意:只做**读取**,绝不写凭据文件(写必须走 dsh 官方接口,见 argent-core)。
58
+ """
59
+ if os.environ.get("WHYSHU_API_KEY"):
60
+ return os.environ["WHYSHU_API_KEY"]
61
+ cred = os.path.expanduser(os.environ.get("DSH_HOME", "~/.dsh") + "/.credentials.yaml")
62
+ try:
63
+ in_refs, out = False, ""
64
+ for line in open(cred, encoding="utf-8"):
65
+ if line.startswith("refs:"):
66
+ in_refs = True; continue
67
+ if in_refs and line and not line[0].isspace():
68
+ break
69
+ if in_refs and line.strip().startswith("WHYSHU_API_KEY:"):
70
+ out = line.split(":", 1)[1].strip().strip('"').strip("'")
71
+ break
72
+ if out:
73
+ return out
74
+ except OSError:
75
+ pass
76
+ env_file = os.path.expanduser("~/.dsh/argent/whyshu.env")
77
+ try:
78
+ for line in open(env_file, encoding="utf-8"):
79
+ if line.startswith("WHYSHU_API_KEY="):
80
+ return line.split("=", 1)[1].strip()
81
+ except OSError:
82
+ pass
83
+ return ""
84
+
85
+
86
+ def fetch_models() -> list:
87
+ """取当前可用且已计价的模型 id(失败则回落 FALLBACK_MODELS,不静默给空清单)。"""
88
+ key = read_key()
89
+ try:
90
+ req = urllib.request.Request(MODELS_URL, headers={"Authorization": f"Bearer {key}"})
91
+ with urllib.request.urlopen(req, timeout=15) as r:
92
+ doc = json.loads(r.read().decode("utf-8") or "{}")
93
+ ids = [m.get("id") for m in (doc.get("data") or [])
94
+ if m.get("id") and m.get("available", True) and m.get("priced", True)]
95
+ return ids or FALLBACK_MODELS
96
+ except Exception as e: # noqa: BLE001
97
+ print(f" ⚠️ 取模型清单失败({e}),用兜底 {FALLBACK_MODELS}", file=sys.stderr)
98
+ return FALLBACK_MODELS
99
+
100
+
101
+ def build_block(models: list) -> str:
102
+ # ⚠️ contextWindow 与 maxTokens **成对自洽**(2026-10-02):compaction-basic 的
103
+ # resolveCompactSpec 要求 contextWindow - maxTokens - headroomTokens(默认 65536) > 0,
104
+ # 否则抛 TargetPressureConfigError 并在 agent/pre-step 被静默吞掉 ⇒ **压缩彻底不跑**。
105
+ # 旧值 131072/131072 正是这种失能形态(余量为 0);代理窗口 131072 ⇒ 输出上限取 32768。
106
+ out = ["llm-deepseek:", " baseURL: https://whyshu.com/api/argent/v1",
107
+ " apiKeyEnv: WHYSHU_API_KEY", " models:"]
108
+ for mid in models:
109
+ name = DISPLAY.get(mid, f"{mid}(问述代理)")
110
+ out += [f" - id: {mid}", f" name: {name}",
111
+ " contextWindow: 131072", " maxTokens: 32768"]
112
+ return "\n".join(out) + "\n"
113
+
114
+
115
+ BLOCK = build_block(fetch_models())
116
+
117
+ TOP_LEVEL = re.compile(r"(?m)^[a-zA-Z_][a-zA-Z0-9_-]*:")
118
+ HAS = "llm-deepseek" in text
119
+
120
+
121
+ def remove_block(s):
122
+ # 删除 llm-deepseek 顶层段(含子行,直到下一个顶层键或文件尾)
123
+ m = TOP_LEVEL.search(s, s.find("llm-deepseek") + len("llm-deepseek"))
124
+ head = s[: s.find("llm-deepseek")]
125
+ tail = s[m.start() :] if m else ""
126
+ return head.rstrip() + "\n" + tail
127
+
128
+
129
+ if mode == "whyshu":
130
+ if HAS:
131
+ print("已在问述积分模式(whyshu 代理)")
132
+ else:
133
+ open(path, "a", encoding="utf-8").write("\n" + BLOCK)
134
+ print("已切换到问述积分(whyshu 代理)——新请求即时生效")
135
+ elif mode == "deepseek":
136
+ if HAS:
137
+ open(path, "w", encoding="utf-8").write(remove_block(text))
138
+ print("已切换到 DeepSeek 官方直连(api.deepseek.com + DEEPSEEK_API_KEY)——新请求即时生效")
139
+ else:
140
+ print("已在 DeepSeek 官方直连模式")
141
+ elif mode == "status":
142
+ print("问述积分(whyshu 代理)" if HAS else "DeepSeek 官方直连")
143
+ else:
144
+ print("用法: provider-switch.sh [whyshu|deepseek|status]", file=sys.stderr)
145
+ sys.exit(1)
146
+ PY
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * provider 同步目标 —— **诊断脚本**(2026-09-30):钉住「模型清单只写 llm-whyshu,官方段不被改写」
4
+ *
5
+ * ⚠️ 为何不放在 scripts/tests/(门禁):本脚本用 node 内置 createServer 做 stub,实测在同一进程里
6
+ * installer 的 fetch 打不到该 stub(未定位到根因);而同场景的 **python stub 版本手工验证通过**:
7
+ * · `--sync-models` 退出 0;· stub 模型写入 llm-whyshu;· 官方段逐字保留。
8
+ * 因此降级为诊断脚本,不占门禁;待定位 stub 差异后再升级为测试。
9
+ *
10
+ * 背景:旧安装器把 `/api/argent/v1/models` 的清单写进 `llm-deepseek.models`,而该实例是**官方直连**
11
+ * (provider id `deepseek-official`)⇒ 等于用问述的模型表污染官方 provider。用户纠正后:
12
+ * · `llm-deepseek` = 真·官方(api.deepseek.com / DEEPSEEK_API_KEY)—— **安装器不得改写它**
13
+ * · `llm-whyshu` = 问述代理 —— 模型清单写这里
14
+ * 本测试用**本地 stub 端点**跑 `install-argent.mjs --sync-models`,断言:
15
+ * ① 退出 0;② llm-whyshu.models 被写入 stub 给的模型;③ **官方段逐字保留**(baseURL/模型都不变)。
16
+ */
17
+ import { execFileSync } from 'node:child_process'
18
+ import { createServer } from 'node:http'
19
+ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
20
+ import { tmpdir } from 'node:os'
21
+ import { dirname, join, resolve } from 'node:path'
22
+ import { fileURLToPath } from 'node:url'
23
+
24
+ const here = dirname(fileURLToPath(import.meta.url))
25
+ const ROOT = resolve(here, '..', '..')
26
+ let pass = 0
27
+ let fail = 0
28
+ const ok = (c, n) => { c ? (pass++, console.log(` ✅ ${n}`)) : (fail++, console.log(` ❌ ${n}`)) }
29
+
30
+ const stub = createServer((req, res) => {
31
+ res.writeHead(200, { 'Content-Type': 'application/json' })
32
+ res.end(JSON.stringify({ data: [{ id: 'stub-model-a' }, { id: 'stub-model-b' }], source: 'stub' }))
33
+ })
34
+ await new Promise((r) => stub.listen(0, '127.0.0.1', r))
35
+ const base = `http://127.0.0.1:${stub.address().port}/api/argent/v1`
36
+
37
+ const home = mkdtempSync(join(tmpdir(), 'provsync-'))
38
+ writeFileSync(join(home, 'settings.yaml'), [
39
+ 'agent-default-model:', ' provider: deepseek-official', '',
40
+ 'llm-deepseek:', ' baseURL: https://api.deepseek.com', ' apiKeyEnv: DEEPSEEK_API_KEY', ' models:',
41
+ ' - id: official-keep', ' name: OfficialKeep', ' contextWindow: 1', ' maxTokens: 1', '',
42
+ 'llm-whyshu:', ` baseURL: ${base}`, ' apiKeyEnv: WHYSHU_API_KEY', ' models:',
43
+ ' - id: old-model', ' name: Old', ' contextWindow: 1', ' maxTokens: 1', '',
44
+ ].join('\n'))
45
+
46
+ let code = 0
47
+ try {
48
+ execFileSync('node', [join(ROOT, 'install-argent.mjs'), '--dsh-home', home, '--sync-models'], {
49
+ encoding: 'utf8', env: { ...process.env, ARGENT_SKIP_MODEL_PROBE: '1', WHYSHU_API_KEY: 'stub' }, stdio: 'pipe',
50
+ })
51
+ } catch (e) { code = e.status ?? 1 }
52
+
53
+ const after = readFileSync(join(home, 'settings.yaml'), 'utf8')
54
+ const whys = (after.match(/llm-whyshu:[\s\S]*?(?=\n\S|$)/) || [''])[0]
55
+ ok(code === 0, `--sync-models 退出 0(实际 ${code})`)
56
+ ok(/stub-model-a/.test(whys) && /stub-model-b/.test(whys), 'llm-whyshu.models 写入 stub 给的模型')
57
+ ok(!/old-model/.test(whys), 'llm-whyshu 的旧清单被替换(不是追加)')
58
+ ok(/llm-deepseek:\n baseURL: https:\/\/api\.deepseek\.com/.test(after) && /official-keep/.test(after),
59
+ '**官方段 llm-deepseek 逐字保留**(baseURL 与模型未被改写)')
60
+
61
+ stub.close()
62
+ rmSync(home, { recursive: true, force: true })
63
+ console.log(`\n${fail === 0 ? '✅' : '❌'} provider-sync-target: ${pass} 通过 / ${fail} 失败`)
64
+ process.exit(fail === 0 ? 0 : 1)
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # python-runtime.sh —— 流程引擎的 Python 运行时(持久 venv)运维入口。
3
+ #
4
+ # 为什么单独有它(2026-09-22 重启事故):
5
+ # 引擎入口(flow-trigger.sh 等)需要一个**持久**的解释器(含 openpyxl/pyyaml/pillow/playwright/selenium)。
6
+ # 历史上默认值指向开发机的 `/tmp/pw-venv`,而 **/tmp 重启即被清空** ⇒ 客户机重启后定时器
7
+ # "照跑但什么也不做"(exit 0 静默空转,日志/台账都看不出异常)。装机流程现在会建
8
+ # `<DSH_HOME>/argent/venv`,本脚本是它的检查/修复入口(客户机排障用)。
9
+ #
10
+ # 用法:
11
+ # bash scripts/python-runtime.sh check # 体检:路径/版本/五个库(缺谁报谁,exit 非 0)
12
+ # bash scripts/python-runtime.sh ensure # 建/修(调安装器的 --venv-only;需要网络装包)
13
+ # bash scripts/python-runtime.sh path # 打印解析到的解释器路径(给脚本/CI 用)
14
+ set -uo pipefail
15
+ REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
16
+ # venv 布局**按平台**(P0-③ 2026-10-08):POSIX 是 `bin/python3`,Windows 是 `Scripts\python.exe`。
17
+ # 此前这里写死 `bin/python3` ⇒ 在 Windows 的 Git Bash 里 check 恒红("缺少持久 venv"),
18
+ # 而 venv 其实好好地在 `Scripts\` 下。
19
+ # ⚠️ 与 scripts/lib/python-runtime.mjs#venvLayout(安装器侧的唯一真源)同一口径。
20
+ case "$(uname -s 2>/dev/null || echo unknown)" in
21
+ MINGW*|MSYS*|CYGWIN*) VENV_BIN="${DSH_HOME:-$HOME/.dsh}/argent/venv/Scripts"; VENV_PY="$VENV_BIN/python.exe" ;;
22
+ *) VENV_BIN="${DSH_HOME:-$HOME/.dsh}/argent/venv/bin"; VENV_PY="$VENV_BIN/python3" ;;
23
+ esac
24
+ # P0-1(2026-10-08):体检项 4 → 5(补 selenium)。理由:官方唯一推荐的驱动档位
25
+ # (ZINIAO_MODE=selenium)此前在 venv 里**没装** ⇒ 选型开关静默撒谎、该档位从未运行过一次。
26
+ # ⚠️ 本清单必须与 install-argent.mjs 的 pythonRuntimePlan().pkgs 和
27
+ # scripts/lib/python-runtime.mjs 的 venvUsable(pkgs=…) 保持一致(缺一就会"体检红但 ensure 空转")。
28
+ PKGS=(openpyxl yaml PIL playwright selenium)
29
+ # 交付纪律:本清单变更必须与"客户机 venv 补装 selenium(bash scripts/python-runtime.sh ensure)"
30
+ # 同批落地,否则客户机体检会先变红(4/5)。
31
+
32
+ cmd_check() {
33
+ local rc=0
34
+ if [ ! -x "$VENV_PY" ]; then
35
+ echo " ❌ 缺少持久 venv:$VENV_PY"
36
+ echo " ↳ 修:bash scripts/python-runtime.sh ensure"
37
+ return 1
38
+ fi
39
+ echo " ✅ 解释器:${VENV_PY}($("$VENV_PY" -V 2>&1))"
40
+ for m in "${PKGS[@]}"; do
41
+ if "$VENV_PY" -c "import ${m}" 2>/dev/null; then
42
+ echo " ✅ 依赖 ${m}"
43
+ elif [ "$m" = "selenium" ]; then
44
+ # selenium 与其它四项不同:缺它**不会**让流程跑不动,而是让官方推荐档位失效
45
+ # (ZINIAO_MODE=selenium ⇒ 显著告警并回退 Playwright)。文案必须说实话。
46
+ echo " ❌ 依赖 selenium 缺失(官方推荐驱动档位不可用:设了 selenium 会显著告警并回退 Playwright)"; rc=1
47
+ else
48
+ echo " ❌ 依赖 ${m} 缺失(浏览器/发票流程会失败)"; rc=1
49
+ fi
50
+ done
51
+ [ "$rc" -eq 0 ] || echo " ↳ 修:bash scripts/python-runtime.sh ensure"
52
+ return $rc
53
+ }
54
+
55
+ cmd_ensure() {
56
+ if [ ! -f "$REPO/install-argent.mjs" ]; then
57
+ echo " ❌ 找不到 install-argent.mjs(本脚本需在 argent-dsh 仓库内运行)" >&2; return 2
58
+ fi
59
+ node "$REPO/install-argent.mjs" --venv-only --dsh-home "${DSH_HOME:-$HOME/.dsh}" || return $?
60
+ }
61
+
62
+ cmd_path() {
63
+ # 与入口脚本同一套候选(顺序必须一致,否则会出现"检查过了但流程用的是别的解释器")
64
+ local c
65
+ for c in "${ARGENT_PY:-}" "$VENV_PY" "${REPO:-}/.venv/bin/python3" "$(command -v python3 2>/dev/null || true)"; do
66
+ [ -n "$c" ] && [ -x "$c" ] || continue
67
+ "$c" -c 'import openpyxl, yaml' 2>/dev/null || continue
68
+ printf '%s\n' "$c"; return 0
69
+ done
70
+ echo "❌ 没有可用解释器(需 openpyxl+yaml)" >&2; return 1
71
+ }
72
+
73
+ case "${1:-}" in
74
+ check) cmd_check ;;
75
+ ensure) cmd_ensure ;;
76
+ path) cmd_path ;;
77
+ *) sed -n '2,18p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 2 ;;
78
+ esac
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env python3
2
+ """ml-conversation CDP 查询执行器(在 playwright venv 下运行)。
3
+
4
+ 插件进程(hermes/argent 环境)本身没有 playwright,因此本脚本由
5
+ tools.py 通过 subprocess 调用 playwright venv 的 python3 执行,
6
+ import orchestrator 的公共函数(PlaywrightClient / JS_EXTRACT_SHIPMENTS /
7
+ load_config / _discover_cdp_ports / _resolve_store)完成 CDP 查询,
8
+ 避免双份维护连接逻辑。
9
+
10
+ 用法:
11
+ query_runner.py --mode ports
12
+ 仅检测紫鸟进程/CDP 端口/店铺窗口在线状态(无浏览器操作)
13
+ query_runner.py --mode shipment --shipment-id 74053018 [--store 店名] [--orchestrator PATH]
14
+ 连接紫鸟 CDP → 打开独立新标签 → 查询 inbounds 货物状态(只读)
15
+ query_runner.py --mode store --store 店名 [--check-login 1] [--orchestrator PATH]
16
+ 检测指定店铺窗口/CDP 端口在线 + 可选登录态探测(只读导航)
17
+
18
+ 输出: stdout 单行 JSON;进度日志走 stderr(与 orchestrator 约定一致)。
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import asyncio
25
+ import importlib.util
26
+ import json
27
+ import os
28
+ import sys
29
+ from pathlib import Path
30
+ from typing import Any, Optional
31
+
32
+ # 数据目录:**唯一判据**在 `scripts/lib/argent_datadir.py`(三级链,与 JS 内核同语义)。
33
+ # 本脚本既在仓库 `scripts/` 下跑、也在安装克隆 `~/.argent-dsh/scripts/`(plugin scriptsDir)
34
+ # 下跑 —— 两处布局都带 `lib/`(sync-scripts.sh 整目录 rsync)⇒ 相对本文件取 lib 即可。
35
+ sys.path.insert(0, str(Path(__file__).resolve().parent / 'lib'))
36
+ from argent_datadir import resolve_data_dir # noqa: E402 —— 勿在别处复制判据
37
+
38
+
39
+ def _default_orchestrator() -> Path:
40
+ """定位 orchestrator.py(与 tools.py 的候选顺序一致,双副本通用)。"""
41
+ env = os.environ.get("ML_ORCHESTRATOR_PATH")
42
+ if env:
43
+ p = Path(env).expanduser()
44
+ if p.is_file():
45
+ return p
46
+ here = Path(__file__).resolve()
47
+ # .../plugins/ml-conversation/scripts/query_runner.py → 上溯 4 级到 profile home
48
+ profile_home = here.parents[3]
49
+ candidates = [
50
+ profile_home / "scripts/orchestrator.py",
51
+ profile_home / "roles/ml-fulfillment/scripts/orchestrator.py",
52
+ profile_home / "skills/mercadolibre/ml-fulfillment-sop/scripts/fulfillment_orchestrator.py",
53
+ Path.home() / "Code/argent-ml-seller/scripts/orchestrator.py",
54
+ Path.home() / "Code/argent-ml/roles/ml-fulfillment/scripts/orchestrator.py",
55
+ Path.home() / "Code/argent-ml/skills/mercadolibre/ml-fulfillment-sop/scripts/fulfillment_orchestrator.py",
56
+ ]
57
+ for c in candidates:
58
+ if c.is_file():
59
+ return c
60
+ return candidates[0]
61
+
62
+
63
+ def _load_orchestrator(path: Optional[str] = None):
64
+ """从文件路径加载 orchestrator 模块(不要求已安装为包)。"""
65
+ p = Path(path).expanduser() if path else _default_orchestrator()
66
+ if not p.is_file():
67
+ raise FileNotFoundError(f"orchestrator.py 不存在: {p}")
68
+ spec = importlib.util.spec_from_file_location("ml_orchestrator", p)
69
+ mod = importlib.util.module_from_spec(spec)
70
+ sys.modules["ml_orchestrator"] = mod
71
+ spec.loader.exec_module(mod)
72
+ return mod
73
+
74
+
75
+ # ────────────────────────────────────────────────
76
+ # ports:只读检测,不碰浏览器
77
+ # ────────────────────────────────────────────────
78
+
79
+ def _store_port_map(orchestrator) -> dict[str, int]:
80
+ """店铺名 → CDP 端口。优先 cdp-port-map.json(v0.6.5+ 格式 storeId → {store_name, env_name, cdp_port},
81
+ 兼容旧格式 店铺名 → 端口),其次 stores.json 的 cdp_port。"""
82
+ mapping: dict[str, int] = {}
83
+ port_map_json = Path(resolve_data_dir()['dir']) / "cdp-port-map.json"
84
+ try:
85
+ data = json.loads(port_map_json.read_text(encoding="utf-8"))
86
+ if isinstance(data, dict):
87
+ for k, v in data.items():
88
+ if isinstance(v, dict) and str(v.get("cdp_port", "")).isdigit():
89
+ mapping[str(v.get("store_name") or k)] = int(v["cdp_port"])
90
+ elif isinstance(v, (int, str)) and str(v).isdigit():
91
+ mapping[str(k)] = int(v)
92
+ except Exception:
93
+ pass
94
+ try:
95
+ stores = orchestrator._load_stores()
96
+ for name, info in stores.items():
97
+ port = info.get("cdp_port")
98
+ if str(port).isdigit():
99
+ mapping.setdefault(str(name), int(port))
100
+ except Exception:
101
+ pass
102
+ return mapping
103
+
104
+
105
+ def _detect_ports(orchestrator) -> list[int]:
106
+ try:
107
+ return orchestrator._discover_cdp_ports()
108
+ except Exception:
109
+ return []
110
+
111
+
112
+ def _detect_ziniao_running() -> bool:
113
+ try:
114
+ import subprocess
115
+ # 跨平台紫鸟客户端进程:Mac: ziniao/Ziniao;Win: ziniaob(旧名 ziniaobro 兼容);
116
+ # 排除 ziniao-cli 自身(命令行含 "ziniao")
117
+ out = subprocess.run(["pgrep", "-f", "ziniao|ziniaob"],
118
+ capture_output=True, text=True, timeout=5)
119
+ for pid in out.stdout.split():
120
+ try:
121
+ cmd = subprocess.run(["ps", "-p", pid, "-o", "command="],
122
+ capture_output=True, text=True, timeout=3).stdout
123
+ if cmd and "ziniao-cli" not in cmd and "pgrep" not in cmd:
124
+ return True
125
+ except Exception:
126
+ continue
127
+ return False
128
+ except Exception:
129
+ return False
130
+
131
+
132
+ async def _run_ports(orchestrator) -> dict[str, Any]:
133
+ mapping = _store_port_map(orchestrator)
134
+ ports = _detect_ports(orchestrator)
135
+ stores = []
136
+ for name, port in sorted(mapping.items()):
137
+ stores.append({
138
+ "store": name,
139
+ "cdp_port": port,
140
+ "window_online": port in ports,
141
+ })
142
+ _client_running = _detect_ziniao_running()
143
+ _online = sum(1 for s in stores if s["window_online"])
144
+ return {
145
+ "ok": True,
146
+ # ⑫(2026-09-23 角色交接):这个键**只表示客户端进程在跑**,不代表店铺窗口可用
147
+ # (实测 {"ziniao_running": true, …, "stores_online": 1})⇒ 极易被读成"环境就绪"。
148
+ # 为兼容既有消费者保留它,但紧跟两个**语义明确**的字段,并附一句口径说明。
149
+ "ziniao_running": _client_running,
150
+ "ziniao_client_running": _client_running,
151
+ "ziniao_running_meaning": "仅指紫鸟**客户端进程**在跑;窗口是否可用看 stores_online",
152
+ "cdp_ports": sorted(ports),
153
+ "stores": stores,
154
+ "stores_total": len(stores),
155
+ "stores_online": _online,
156
+ }
157
+
158
+
159
+ # ────────────────────────────────────────────────
160
+ # shipment:查询 inbounds 货物状态(只读)
161
+ # ────────────────────────────────────────────────
162
+
163
+ def _pick_cdp_url(orchestrator, store_name: Optional[str], store_map: dict[str, int],
164
+ ports: list[int]) -> str:
165
+ """选 CDP 端口:指定店铺 → 该店映射端口;否则自动发现第一个可用端口。"""
166
+ if store_name:
167
+ port = store_map.get(store_name)
168
+ if port:
169
+ return f"http://127.0.0.1:{port}"
170
+ if ports:
171
+ return f"http://127.0.0.1:{ports[0]}"
172
+ cfg = orchestrator.load_config()
173
+ return cfg.cdp_url
174
+
175
+
176
+ async def _run_shipment(orchestrator, shipment_id: str,
177
+ store_name: Optional[str]) -> dict[str, Any]:
178
+ store_map = _store_port_map(orchestrator)
179
+ ports = _detect_ports(orchestrator)
180
+ cdp_url = _pick_cdp_url(orchestrator, store_name, store_map, ports)
181
+
182
+ cfg = orchestrator.load_config()
183
+ client = orchestrator.PlaywrightClient(cfg, cdp_url=cdp_url)
184
+ await client.connect(step=0)
185
+ try:
186
+ url = f"{orchestrator.INBOUNDS_URL}?query={shipment_id}"
187
+ await client.navigate(url, step=0, wait_after=2.0)
188
+ out = await client.evaluate(orchestrator.JS_EXTRACT_SHIPMENTS, step=0)
189
+ try:
190
+ data = json.loads(out)
191
+ except Exception:
192
+ data = {"shipments": []}
193
+ shipments = data.get("shipments", [])
194
+ # 登录态探测:URL 落到登录域或页面含登录关键词
195
+ page_url = client.page.url or ""
196
+ body = ""
197
+ try:
198
+ body = await client.page.evaluate(
199
+ "() => document.body ? document.body.innerText.slice(0, 300) : ''")
200
+ except Exception:
201
+ pass
202
+ login_hint = (
203
+ "login" in page_url.lower()
204
+ or "iniciar sesión" in (body or "").lower()
205
+ )
206
+ match = next((s for s in shipments if s.get("id") == shipment_id), None)
207
+ return {
208
+ "ok": True,
209
+ "shipment_id": shipment_id,
210
+ "cdp_url": cdp_url,
211
+ "store_hint": store_name,
212
+ "found": match is not None,
213
+ "status": (match or {}).get("status", ""),
214
+ "raw": match,
215
+ "page_url": page_url[:200],
216
+ "login_hint": login_hint,
217
+ "results_count": len(shipments),
218
+ }
219
+ finally:
220
+ try:
221
+ await client.close()
222
+ except Exception:
223
+ pass
224
+
225
+
226
+ # ────────────────────────────────────────────────
227
+ # store:店铺窗口在线 + 可选登录态探测(只读导航)
228
+ # ────────────────────────────────────────────────
229
+
230
+ async def _run_store(orchestrator, store_name: str, check_login: bool) -> dict[str, Any]:
231
+ store_map = _store_port_map(orchestrator)
232
+ ports = _detect_ports(orchestrator)
233
+ port = store_map.get(store_name)
234
+ online = port in ports if port else False
235
+
236
+ result: dict[str, Any] = {
237
+ "ok": True,
238
+ "ziniao_running": _detect_ziniao_running(),
239
+ "store": store_name,
240
+ "cdp_port": port,
241
+ "window_online": online,
242
+ }
243
+ if not check_login or not online or not port:
244
+ return result
245
+
246
+ cfg = orchestrator.load_config()
247
+ client = orchestrator.PlaywrightClient(cfg, cdp_url=f"http://127.0.0.1:{port}")
248
+ await client.connect(step=0)
249
+ try:
250
+ await client.navigate(orchestrator.INBOUNDS_URL, step=0, wait_after=1.0)
251
+ page_url = client.page.url or ""
252
+ body = ""
253
+ try:
254
+ body = await client.page.evaluate(
255
+ "() => document.body ? document.body.innerText.slice(0, 300) : ''")
256
+ except Exception:
257
+ pass
258
+ result["logged_in"] = not (
259
+ "login" in page_url.lower()
260
+ or "iniciar sesión" in (body or "").lower()
261
+ )
262
+ result["page_url"] = page_url[:200]
263
+ finally:
264
+ try:
265
+ await client.close()
266
+ except Exception:
267
+ pass
268
+ return result
269
+
270
+
271
+ async def _main(argv: list[str]) -> int:
272
+ parser = argparse.ArgumentParser(description="ml-conversation CDP 查询执行器")
273
+ parser.add_argument("--mode", required=True,
274
+ choices=["ports", "shipment", "store"])
275
+ parser.add_argument("--shipment-id", default="")
276
+ parser.add_argument("--store", default="")
277
+ parser.add_argument("--check-login", default="0")
278
+ parser.add_argument("--orchestrator", default="")
279
+ args = parser.parse_args(argv)
280
+
281
+ try:
282
+ orchestrator = _load_orchestrator(args.orchestrator or None)
283
+ except Exception as exc:
284
+ print(json.dumps({"ok": False, "error": f"orchestrator 加载失败: {exc}"},
285
+ ensure_ascii=False))
286
+ return 1
287
+
288
+ try:
289
+ if args.mode == "ports":
290
+ result = await _run_ports(orchestrator)
291
+ elif args.mode == "shipment":
292
+ if not args.shipment_id.strip():
293
+ raise ValueError("shipment-id 必填")
294
+ result = await _run_shipment(orchestrator, args.shipment_id.strip(),
295
+ args.store.strip() or None)
296
+ else:
297
+ if not args.store.strip():
298
+ raise ValueError("store 必填")
299
+ result = await _run_store(orchestrator, args.store.strip(),
300
+ args.check_login.strip() in ("1", "true", "yes"))
301
+ print(json.dumps(result, ensure_ascii=False))
302
+ return 0
303
+ except Exception as exc:
304
+ print(json.dumps({"ok": False, "error": f"{type(exc).__name__}: {exc}"},
305
+ ensure_ascii=False))
306
+ return 1
307
+
308
+
309
+ if __name__ == "__main__":
310
+ sys.exit(asyncio.run(_main(sys.argv[1:])))