@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.
- package/MANIFEST.json +307 -0
- package/engine/VERSION +1 -0
- package/engine/agents/catalog.json +454 -0
- package/engine/configs/_aliases.proposed.json +651 -0
- package/engine/configs/_migration-plan.proposed.json +899 -0
- package/engine/configs/account.json +272 -0
- package/engine/configs/advanced.json +205 -0
- package/engine/configs/host.json +149 -0
- package/engine/configs/hr.json +27 -0
- package/engine/configs/notify.json +264 -0
- package/engine/core/cli/argent.mjs +277 -0
- package/engine/core/contracts/argent.contracts.json +1065 -0
- package/engine/core/contracts/verbs.schema.json +2539 -0
- package/engine/core/lib/argent-datadir.mjs +223 -0
- package/engine/core/lib/audit.mjs +187 -0
- package/engine/core/lib/claim.mjs +258 -0
- package/engine/core/lib/config-catalog.mjs +1172 -0
- package/engine/core/lib/doctor-customer.mjs +577 -0
- package/engine/core/lib/flow-list.mjs +138 -0
- package/engine/core/lib/flow-registry.mjs +48 -0
- package/engine/core/lib/init.mjs +247 -0
- package/engine/core/lib/ledger.mjs +124 -0
- package/engine/core/lib/license-activate.mjs +813 -0
- package/engine/core/lib/license-device.mjs +314 -0
- package/engine/core/lib/license-file.mjs +349 -0
- package/engine/core/lib/license-guard.mjs +123 -0
- package/engine/core/lib/license-ops.mjs +1645 -0
- package/engine/core/lib/license-verdict.mjs +133 -0
- package/engine/core/lib/license.mjs +577 -0
- package/engine/core/lib/notify/dedup.mjs +101 -0
- package/engine/core/lib/notify/feishu.mjs +38 -0
- package/engine/core/lib/notify/index.mjs +95 -0
- package/engine/core/lib/notify/verb.mjs +327 -0
- package/engine/core/lib/notify/webhook.mjs +135 -0
- package/engine/core/lib/notify/wecom.mjs +36 -0
- package/engine/core/lib/python.mjs +145 -0
- package/engine/core/lib/sop.mjs +116 -0
- package/engine/core/lib/versions.mjs +205 -0
- package/engine/core/mcp/client.mjs +67 -0
- package/engine/core/mcp/http.mjs +138 -0
- package/engine/core/mcp/server.mjs +702 -0
- package/engine/scripts/_engine_rules_probe.py +15 -0
- package/engine/scripts/analyze-full-day.py +568 -0
- package/engine/scripts/archive/feishu-bridge.js +246 -0
- package/engine/scripts/argent-agents.mjs +527 -0
- package/engine/scripts/argent-capabilities.mjs +776 -0
- package/engine/scripts/argent-catalog.mjs +186 -0
- package/engine/scripts/argent-datadir.py +62 -0
- package/engine/scripts/argent-doctor.mjs +624 -0
- package/engine/scripts/argent-env-write.mjs +302 -0
- package/engine/scripts/argent-schedule.mjs +620 -0
- package/engine/scripts/argent-setup-check.mjs +369 -0
- package/engine/scripts/argent-upgrade.mjs +1046 -0
- package/engine/scripts/base-bootstrap.sh +575 -0
- package/engine/scripts/billing-mode-daily.sh +12 -0
- package/engine/scripts/build-engine-package.mjs +123 -0
- package/engine/scripts/capability-packs.lock.json +484 -0
- package/engine/scripts/capability_pack.py +1242 -0
- package/engine/scripts/check-1.0-criteria.mjs +355 -0
- package/engine/scripts/check-agent-catalog.mjs +141 -0
- package/engine/scripts/check-billing-mode.sh +377 -0
- package/engine/scripts/check-child-sessions.mjs +83 -0
- package/engine/scripts/check-config-catalog.mjs +677 -0
- package/engine/scripts/check-docs-drift.mjs +522 -0
- package/engine/scripts/check-dsh-web-expose.sh +98 -0
- package/engine/scripts/check-judgment-drift.mjs +403 -0
- package/engine/scripts/check-license.mjs +199 -0
- package/engine/scripts/check-model-auth.py +443 -0
- package/engine/scripts/check-pack-closure.mjs +661 -0
- package/engine/scripts/check-portable-symbols.mjs +116 -0
- package/engine/scripts/check-preset-landing.mjs +124 -0
- package/engine/scripts/check-production-home.mjs +602 -0
- package/engine/scripts/check-role-mapping.mjs +461 -0
- package/engine/scripts/check-sample-redaction.mjs +320 -0
- package/engine/scripts/check-skill-classification.mjs +188 -0
- package/engine/scripts/check-sop-consistency.mjs +431 -0
- package/engine/scripts/check-test-env-safety.mjs +627 -0
- package/engine/scripts/check-version-consistency.mjs +452 -0
- package/engine/scripts/ci-run.sh +558 -0
- package/engine/scripts/config-explain.mjs +278 -0
- package/engine/scripts/configure-provider.rb +29 -0
- package/engine/scripts/customer-config-migrate.mjs +179 -0
- package/engine/scripts/customer-preflight.py +892 -0
- package/engine/scripts/customer-readiness.sh +134 -0
- package/engine/scripts/daily-report-inbox.py +242 -0
- package/engine/scripts/daily-report-reconcile.mjs +312 -0
- package/engine/scripts/daily-report.mjs +1310 -0
- package/engine/scripts/daily-report.sh +46 -0
- package/engine/scripts/data/lip-product-dict.json +306 -0
- package/engine/scripts/deliver-customer.mjs +932 -0
- package/engine/scripts/delivery-faces.txt +31 -0
- package/engine/scripts/desktop-trial.sh +146 -0
- package/engine/scripts/dev-window.sh +260 -0
- package/engine/scripts/drill-clean-machine.sh +315 -0
- package/engine/scripts/dsh-api-client.mjs +215 -0
- package/engine/scripts/dsh-backup-scope.sh +83 -0
- package/engine/scripts/dsh-offline-pack.mjs +77 -0
- package/engine/scripts/dsh-overlay-pin.sh +87 -0
- package/engine/scripts/dsh-plane.mjs +153 -0
- package/engine/scripts/dsh-portforward.py +223 -0
- package/engine/scripts/dsh-preflight.mjs +179 -0
- package/engine/scripts/dsh-release-watch-daily.sh +19 -0
- package/engine/scripts/dsh-release-watch.mjs +458 -0
- package/engine/scripts/dsh-remote-check.sh +99 -0
- package/engine/scripts/dsh-restart.sh +179 -0
- package/engine/scripts/dsh-restore.sh +111 -0
- package/engine/scripts/dsh-scope-rewire.mjs +803 -0
- package/engine/scripts/dsh-snapshot.mjs +116 -0
- package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
- package/engine/scripts/dsh-upgrade.mjs +612 -0
- package/engine/scripts/dsh-url.sh +86 -0
- package/engine/scripts/dsh-web-launch.mjs +155 -0
- package/engine/scripts/ensure-store-windows.py +509 -0
- package/engine/scripts/entitlements.py +458 -0
- package/engine/scripts/env_probe.py +411 -0
- package/engine/scripts/feishu-card.mjs +252 -0
- package/engine/scripts/fix-skill-frontmatter.py +209 -0
- package/engine/scripts/flow-auto-retry.py +454 -0
- package/engine/scripts/flow-requirements.json +720 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +86 -0
- package/engine/scripts/flow-timers.sh +182 -0
- package/engine/scripts/flow-trigger-wake.mjs +284 -0
- package/engine/scripts/flow-trigger.sh +571 -0
- package/engine/scripts/flow_billing.py +354 -0
- package/engine/scripts/flow_capability_stats.py +314 -0
- package/engine/scripts/flow_case.py +194 -0
- package/engine/scripts/flow_claim.py +497 -0
- package/engine/scripts/flow_config.py +1024 -0
- package/engine/scripts/flow_failure.py +588 -0
- package/engine/scripts/flow_learn.py +105 -0
- package/engine/scripts/flow_ledger.py +235 -0
- package/engine/scripts/flow_policy.py +238 -0
- package/engine/scripts/flow_preflight.py +339 -0
- package/engine/scripts/flow_registry.py +341 -0
- package/engine/scripts/flow_retry.py +304 -0
- package/engine/scripts/flow_return_to_ops.py +191 -0
- package/engine/scripts/flow_source.py +315 -0
- package/engine/scripts/flow_sources/__init__.py +53 -0
- package/engine/scripts/flow_sources/cases_json.py +106 -0
- package/engine/scripts/flow_sources/common.py +428 -0
- package/engine/scripts/flow_sources/feishu_base.py +273 -0
- package/engine/scripts/flow_stale.py +185 -0
- package/engine/scripts/flow_status_reconcile.py +344 -0
- package/engine/scripts/flow_triage.py +849 -0
- package/engine/scripts/flow_write_verify.py +496 -0
- package/engine/scripts/fulfillment.js +586 -0
- package/engine/scripts/gen-agent-bundles.mjs +276 -0
- package/engine/scripts/gen-preset-rows.mjs +259 -0
- package/engine/scripts/init-ml.sh +2275 -0
- package/engine/scripts/install-internal-role.sh +190 -0
- package/engine/scripts/install-license.mjs +641 -0
- package/engine/scripts/install-one-shot.mjs +665 -0
- package/engine/scripts/integration_contract.py +220 -0
- package/engine/scripts/integrations/wenyao/adapter.py +98 -0
- package/engine/scripts/integrations/xzh/adapter.py +75 -0
- package/engine/scripts/invoice-mapping.example.json +40 -0
- package/engine/scripts/invoice-table.sh +194 -0
- package/engine/scripts/invoice_carrier_notice.py +279 -0
- package/engine/scripts/invoice_fail_closeout.py +297 -0
- package/engine/scripts/invoice_lip_runner.py +1508 -0
- package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
- package/engine/scripts/invoice_ready_audit.py +191 -0
- package/engine/scripts/invoice_runner.py +675 -0
- package/engine/scripts/invoice_stageb.py +516 -0
- package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
- package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
- package/engine/scripts/lease.py +377 -0
- package/engine/scripts/lib/agent-install.mjs +649 -0
- package/engine/scripts/lib/argent-datadir.mjs +16 -0
- package/engine/scripts/lib/argent_datadir.py +262 -0
- package/engine/scripts/lib/argent_filelock.py +507 -0
- package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
- package/engine/scripts/lib/cdp_ports.py +197 -0
- package/engine/scripts/lib/customer-env.mjs +102 -0
- package/engine/scripts/lib/daily-report-core.mjs +517 -0
- package/engine/scripts/lib/dep-declaration.mjs +81 -0
- package/engine/scripts/lib/deps-migrate.mjs +310 -0
- package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
- package/engine/scripts/lib/dsh-scope.mjs +500 -0
- package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
- package/engine/scripts/lib/dsh-versions.mjs +96 -0
- package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
- package/engine/scripts/lib/is-direct-run.mjs +61 -0
- package/engine/scripts/lib/patch-compose.mjs +241 -0
- package/engine/scripts/lib/patch-target.mjs +35 -0
- package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
- package/engine/scripts/lib/plugin-packages.mjs +332 -0
- package/engine/scripts/lib/preset-compat.mjs +127 -0
- package/engine/scripts/lib/preset-install.mjs +575 -0
- package/engine/scripts/lib/preset-mechanism.mjs +56 -0
- package/engine/scripts/lib/preset-sync.mjs +59 -0
- package/engine/scripts/lib/profile-retire.mjs +97 -0
- package/engine/scripts/lib/python-runtime.mjs +190 -0
- package/engine/scripts/lib/registry.mjs +72 -0
- package/engine/scripts/lib/repo-root.sh +56 -0
- package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
- package/engine/scripts/lib/schedule-probe.mjs +835 -0
- package/engine/scripts/lib/scope-pointer.mjs +164 -0
- package/engine/scripts/lib/settings-panels.mjs +816 -0
- package/engine/scripts/lib/stdio_utf8.py +86 -0
- package/engine/scripts/lib/wake-auth.mjs +392 -0
- package/engine/scripts/lib/webdriver_ipc.py +264 -0
- package/engine/scripts/lib/zstd-frames.mjs +104 -0
- package/engine/scripts/license-e2e-drill.mjs +426 -0
- package/engine/scripts/license-gate.mjs +245 -0
- package/engine/scripts/lip_backlog.py +171 -0
- package/engine/scripts/lip_table.py +185 -0
- package/engine/scripts/local-usage.mjs +214 -0
- package/engine/scripts/migrate-providers.mjs +127 -0
- package/engine/scripts/migrate-skills.sh +65 -0
- package/engine/scripts/ml-oauth-setup.mjs +550 -0
- package/engine/scripts/ml-store-ids.json +26 -0
- package/engine/scripts/ml_artifact_attribution.py +333 -0
- package/engine/scripts/ml_capacity_probe.py +1158 -0
- package/engine/scripts/ml_hub_status.py +1556 -0
- package/engine/scripts/ml_schema_check.py +115 -0
- package/engine/scripts/ml_store_ids.py +139 -0
- package/engine/scripts/npm-publish-argent.mjs +499 -0
- package/engine/scripts/npm-publish-argent.sh +20 -0
- package/engine/scripts/orchestrator.py +13811 -0
- package/engine/scripts/pack-plugins.mjs +140 -0
- package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
- package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
- package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
- package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
- package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
- package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
- package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
- package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
- package/engine/scripts/patches/verify-hardening.mjs +167 -0
- package/engine/scripts/patches/verify-session-migration.mjs +149 -0
- package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
- package/engine/scripts/plan-role-rename.mjs +125 -0
- package/engine/scripts/plugin-bootcheck.mjs +451 -0
- package/engine/scripts/plugin-composition.json +15 -0
- package/engine/scripts/poc-mcp-host.mjs +455 -0
- package/engine/scripts/poll-fulfillment.sh +293 -0
- package/engine/scripts/poll-invoice.sh +74 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +70 -0
- package/engine/scripts/provider-switch.sh +146 -0
- package/engine/scripts/provider-sync-target.check.mjs +64 -0
- package/engine/scripts/python-runtime.sh +78 -0
- package/engine/scripts/query_runner.py +310 -0
- package/engine/scripts/release-argent.sh +574 -0
- package/engine/scripts/repair-unresolved-tools.mjs +178 -0
- package/engine/scripts/run-tests.sh +256 -0
- package/engine/scripts/self-sign-license.mjs +179 -0
- package/engine/scripts/session-health-scan.mjs +63 -0
- package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
- package/engine/scripts/setup.sh +22 -0
- package/engine/scripts/skill-install.mjs +629 -0
- package/engine/scripts/sku_translate.py +148 -0
- package/engine/scripts/sync-plugins.sh +90 -0
- package/engine/scripts/sync-presets.sh +27 -0
- package/engine/scripts/sync-scripts.sh +445 -0
- package/engine/scripts/telemetry.py +873 -0
- package/engine/scripts/ui-copy-count.mjs +220 -0
- package/engine/scripts/usage-anchor.sh +136 -0
- package/engine/scripts/usage_report.py +531 -0
- package/engine/scripts/validate-preset-schema.mjs +208 -0
- package/engine/scripts/vertical-init.mjs +323 -0
- package/engine/scripts/vertical-lint.mjs +482 -0
- package/engine/scripts/wenyao-browser.sh +73 -0
- package/engine/scripts/wenyao-fixture-export.py +116 -0
- package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
- package/engine/scripts/wenyao-login.sh +34 -0
- package/engine/scripts/wenyao-template-map.json +292 -0
- package/engine/scripts/wenyao_attach.py +131 -0
- package/engine/scripts/wenyao_download_template.py +144 -0
- package/engine/scripts/wenyao_fill_template.py +568 -0
- package/engine/scripts/wenyao_login.py +145 -0
- package/engine/scripts/wenyao_portal.py +633 -0
- package/engine/scripts/wenyao_recon.py +230 -0
- package/engine/scripts/wenyao_run.py +503 -0
- package/engine/scripts/wenyao_shadow_diff.py +160 -0
- package/engine/scripts/wenyao_skill_contract.py +156 -0
- package/engine/scripts/wenyao_template_fetch.py +175 -0
- package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
- package/engine/scripts/ziniao-auth-probe.sh +268 -0
- package/engine/scripts/ziniao-keepalive.sh +216 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
- package/engine/scripts/ziniao-windows.sh +153 -0
- package/engine/version.mjs +1 -0
- package/engine/verticals/README.md +103 -0
- package/engine/verticals/_engine-words.baseline.txt +237 -0
- package/engine/verticals/_engine-words.json +19 -0
- package/engine/verticals/mercadolibre/README.md +43 -0
- package/engine/verticals/mercadolibre/config.json +709 -0
- package/engine/verticals/mercadolibre/flows.json +706 -0
- package/engine/verticals/mercadolibre/manifest.json +538 -0
- package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
- package/engine/verticals/mercadolibre/preflight.py +143 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
- package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
- package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
- package/engine/verticals/mercadolibre/prompts/full.md +91 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
- package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
- package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
- package/package.json +21 -4
- package/README.md +0 -3
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""webdriver_ipc —— 浏览器客户端 **WebDriver IPC 层**的唯一实现(只读查询面 + 调用原语)。
|
|
3
|
+
|
|
4
|
+
## 为什么有这个模块(2026-10-11)
|
|
5
|
+
认领前的环境预检(`scripts/env_probe.py`)此前只看 `store list` 的 `data.items` 是否非空;
|
|
6
|
+
而生产现场实测「云端店铺列表拉不到、但**本机店铺窗口已开且可驱动**」时,预检把环境判成不可用
|
|
7
|
+
⇒ 自动路径整轮不认领(票不派、租约不耗,但**当天 0 票处理**),而同一批票手工用
|
|
8
|
+
`orchestrator.py --cdp-url …` 跑是 6/6 成功。
|
|
9
|
+
|
|
10
|
+
修法是给预检加一条**回退判据**(列表为空 ⇒ 再看本机有没有已运行的店铺窗口)。
|
|
11
|
+
而"再查一次本机窗口"**必须复用**编排器里既有的 WebDriver 查询(`getRunningInfo`),
|
|
12
|
+
⛔ 不能新造第二个 WebDriver 客户端(两套 HTTP/重试/错误解析 = 迟早漂移)。
|
|
13
|
+
故把该层抽到这里:本模块是**唯一实现**,`orchestrator.py` 与 `env_probe.py` 都调用它。
|
|
14
|
+
|
|
15
|
+
## 只读纪律(本模块的硬边界)
|
|
16
|
+
- 本模块**只实现**只读接口:`getRunningInfo`(窗口清单)与通用 `wd_call` 原语。
|
|
17
|
+
- ⛔ 本模块**不含**任何生命周期动作:开/关窗口、内核更新、授权、清缓存等一律不在实现里
|
|
18
|
+
(调用方仍可用 `wd_call` 发它们,但"点下去"的那一步留在调用方,且各带自己的守卫与语义)。
|
|
19
|
+
- ⛔ 不写任何文件、不起子进程、不读凭据。
|
|
20
|
+
- 回归:`scripts/tests/env-probe-local-window-fallback.test.py` 的只读性断言
|
|
21
|
+
(静态 AST:本模块零写 API、零动作名;运行时:探针只发 `getRunningInfo`)。
|
|
22
|
+
|
|
23
|
+
## 分层与命名
|
|
24
|
+
L1 执行内核(`scripts/`)⇒ 只 import 标准库;⛔ 不 import 宿主模块(`@deepseek-ai/*`、
|
|
25
|
+
`cordis`、`dsh-*`、`plugins/argent-*`)。门禁:`scripts/tests/kernel-purity.test.mjs`。
|
|
26
|
+
⚠️ 文件名与正文**刻意不带任何业务线词汇**(门禁 `scripts/vertical-lint.mjs` 的 engine-words 档:
|
|
27
|
+
引擎文件不得新增业务词,⛔ 不抬基线)——"是哪个厂商的客户端"由调用方(业务线声明/编排器)决定,
|
|
28
|
+
本模块只认"客户端 WebDriver IPC 协议"这一件事。
|
|
29
|
+
"""
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
import errno as _errno
|
|
32
|
+
import json
|
|
33
|
+
import os
|
|
34
|
+
import socket
|
|
35
|
+
import sys
|
|
36
|
+
import time
|
|
37
|
+
import urllib.request
|
|
38
|
+
from typing import Any, Optional
|
|
39
|
+
|
|
40
|
+
# 默认 IPC 端口:客户端 `--run_type=web_driver --ipc_type=http --port=19515`(官方默认)。
|
|
41
|
+
DEFAULT_WD_PORT = 19515
|
|
42
|
+
|
|
43
|
+
# ── P1-d(2026-10-08):IPC **连接类**异常 1 次退避重试 ─────────────────────────────
|
|
44
|
+
# 依据:官方 `reference/prerequisites.md`(「连接接口失败 ⇒ 客户端未启动完成,轮询 updateCore
|
|
45
|
+
# 直到连接成功」/「请求超时 ⇒ 超时设置过短」)。连接被拒这类**请求肯定没送达**的瞬时故障
|
|
46
|
+
# 不该直接判失败 —— 旧行为是立刻上抛 ⇒ 调用方回退 bridge 生命周期(放大非官方路径)。
|
|
47
|
+
# ⛔ 业务错误码(statusCode != 0)**不重试**:那是内核的真实结论,重试只会白等。
|
|
48
|
+
WD_IPC_RETRY_BACKOFF = 2.0 # 重试前退避(秒)
|
|
49
|
+
# 语义**有歧义**的连接类异常(超时/连接被重置):请求可能已送达并被执行 ⇒ 对**开店动作**
|
|
50
|
+
# 不重试 —— 官方 `api-core.md:188`:连续两次开店调用会被内核视为**重启**,重试可能把
|
|
51
|
+
# 正在跑的店铺窗口关掉再开(生产双跑风险)。只有 `refused`(连接未建立 ⇒ 请求必然未送达)
|
|
52
|
+
# 才对它重试。
|
|
53
|
+
WD_IPC_AMBIGUOUS_NO_RETRY = frozenset({"startBrowser"})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def wd_err_fields(resp: Any) -> tuple[str, str]:
|
|
57
|
+
"""从 IPC 响应里取 `(err, LastError)`(P1-c,2026-10-08)。
|
|
58
|
+
|
|
59
|
+
官方陷阱 2(官方 Skill 的 `SKILL.md:111`):**优先读 `err`** —— 它是中文可读描述、
|
|
60
|
+
最贴近根因;`LastError` 是内核内部错误串(`reference/api-core.md` 的接口响应示例里就有它)。
|
|
61
|
+
旧实现**只读 `err`** ⇒ 内核窗口/代理类失败时排障信息少一半。
|
|
62
|
+
|
|
63
|
+
⛔ 纯取值:不参与任何判断、不改任何控制流;`LastError` 兼容 `lastError` 拼写。
|
|
64
|
+
"""
|
|
65
|
+
if not isinstance(resp, dict):
|
|
66
|
+
return "", ""
|
|
67
|
+
err = str(resp.get("err") or "").strip()
|
|
68
|
+
last = str(resp.get("LastError") or resp.get("lastError") or "").strip()
|
|
69
|
+
return err, last
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class WebDriverError(Exception):
|
|
73
|
+
"""WebDriver API 调用失败,携带 statusCode / err / LastError 供日志(P1-c 起带 LastError)。"""
|
|
74
|
+
|
|
75
|
+
def __init__(self, action: str, status_code: Any = None,
|
|
76
|
+
err: str = "", extra: str = "", last_error: str = "") -> None:
|
|
77
|
+
self.action = action
|
|
78
|
+
self.status_code = status_code
|
|
79
|
+
self.err = err
|
|
80
|
+
# 内核内部错误串。**优先级**:`err` 在前(官方陷阱 2:优先读 err),`LastError` 作附加
|
|
81
|
+
# 线索;`err` 为空时 LastError 照样打出来(旧行为 = 这条信息根本没有)。
|
|
82
|
+
self.last_error = last_error
|
|
83
|
+
msg = f"WebDriver {action} 失败"
|
|
84
|
+
if status_code is not None:
|
|
85
|
+
msg += f" statusCode={status_code}"
|
|
86
|
+
if err:
|
|
87
|
+
msg += f" err={err}"
|
|
88
|
+
if last_error:
|
|
89
|
+
msg += f" LastError={last_error}"
|
|
90
|
+
if extra:
|
|
91
|
+
msg += f"({extra})"
|
|
92
|
+
super().__init__(msg)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def wd_log_call(action: str, request_id: str, status_code: Any,
|
|
96
|
+
started: float, err: str = "", last_error: str = "") -> None:
|
|
97
|
+
"""每接口**一行**调用日志(P1-a,2026-10-08):`action / statusCode / 耗时ms / requestId / err`。
|
|
98
|
+
|
|
99
|
+
为什么:`requestId` 自接口层落地起就在构造,却**从不落日志** ⇒ 出问题无法与客户端侧对账;
|
|
100
|
+
也没有每接口耗时 ⇒ "哪一步慢"只能靠人翻多处日志。⛔ 只打印,不参与任何判断
|
|
101
|
+
(不放宽超时、不重试、不改返回、不改抛出语义)。
|
|
102
|
+
P1-c:`LastError`(内核内部错误串)一并落日志,与 `err` 各截 80 字。
|
|
103
|
+
"""
|
|
104
|
+
ms = int((time.time() - started) * 1000)
|
|
105
|
+
code = "调用异常" if status_code is None else status_code
|
|
106
|
+
parts = []
|
|
107
|
+
if err:
|
|
108
|
+
parts.append(f"err={err[:80]}")
|
|
109
|
+
if last_error:
|
|
110
|
+
parts.append(f"LastError={last_error[:80]}")
|
|
111
|
+
tail = (" " + " ".join(parts)) if parts else ""
|
|
112
|
+
print(f"[{time.strftime('%H:%M:%S')}] 🌐 WebDriver {action} statusCode={code} {ms}ms "
|
|
113
|
+
f"req={request_id}{tail}", file=sys.stderr, flush=True)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def wd_conn_error_kind(exc: BaseException) -> str:
|
|
117
|
+
"""连接类故障细分:`""`(非连接类) / `refused` / `timeout` / `reset` / `conn`。
|
|
118
|
+
|
|
119
|
+
⛔ 刻意**不**把裸 `OSError` 当连接类:未知 OSError 一律不重试,保持"异常原样上抛"
|
|
120
|
+
(既有语义与既有回归都依赖这一点)。判定优先看异常结构(`URLError.reason` + errno),
|
|
121
|
+
结构认不出时才回落到文本特征。
|
|
122
|
+
"""
|
|
123
|
+
reason: Any = getattr(exc, "reason", None) # urllib.error.URLError.reason
|
|
124
|
+
cands = [c for c in (reason, exc) if isinstance(c, BaseException)]
|
|
125
|
+
kinds: list[str] = []
|
|
126
|
+
for c in cands:
|
|
127
|
+
eno = getattr(c, "errno", None)
|
|
128
|
+
if isinstance(c, ConnectionRefusedError) or eno == _errno.ECONNREFUSED:
|
|
129
|
+
kinds.append("refused")
|
|
130
|
+
elif isinstance(c, (TimeoutError, socket.timeout)) or eno == _errno.ETIMEDOUT:
|
|
131
|
+
kinds.append("timeout")
|
|
132
|
+
elif isinstance(c, (ConnectionResetError, BrokenPipeError)) or \
|
|
133
|
+
eno in (_errno.ECONNRESET, _errno.EPIPE):
|
|
134
|
+
kinds.append("reset")
|
|
135
|
+
elif isinstance(c, OSError) and reason is not None:
|
|
136
|
+
kinds.append("conn") # URLError 包着的其它 OSError
|
|
137
|
+
if not (set(kinds) & {"refused", "timeout", "reset"}):
|
|
138
|
+
# 结构认不出**具体**类别时(含"URLError 包着字符串 reason"与"其它 OSError")看文本特征
|
|
139
|
+
blob = f"{reason!r} {exc}".lower()
|
|
140
|
+
text_kind = ""
|
|
141
|
+
if "refused" in blob or "econnrefused" in blob:
|
|
142
|
+
text_kind = "refused"
|
|
143
|
+
elif "reset" in blob or "broken pipe" in blob or "aborted" in blob:
|
|
144
|
+
text_kind = "reset"
|
|
145
|
+
elif "timed out" in blob or "timeout" in blob:
|
|
146
|
+
text_kind = "timeout"
|
|
147
|
+
if text_kind:
|
|
148
|
+
kinds.insert(0, text_kind)
|
|
149
|
+
for k in ("refused", "timeout", "reset", "conn"): # refused 最明确 ⇒ 优先
|
|
150
|
+
if k in kinds:
|
|
151
|
+
return k
|
|
152
|
+
return ""
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def wd_call(port: int, action: str, payload: Optional[dict] = None,
|
|
156
|
+
timeout: float = 120.0, *,
|
|
157
|
+
backoff: float = WD_IPC_RETRY_BACKOFF,
|
|
158
|
+
no_retry_actions: frozenset = WD_IPC_AMBIGUOUS_NO_RETRY) -> dict[str, Any]:
|
|
159
|
+
"""调用客户端 WebDriver HTTP API(POST JSON,UTF-8 解码 err 防乱码)。
|
|
160
|
+
|
|
161
|
+
P1-a:成功/失败各落一行 `wd_log_call`(含 requestId 与耗时);成功路径返回值、
|
|
162
|
+
失败路径抛出的异常类型与文案**逐字不变**(HTTP 异常仍原样上抛,JSON 解析异常同样原样)。
|
|
163
|
+
P1-c:日志与错误对象一并带上 `LastError`(内核内部错误串)。
|
|
164
|
+
P1-d:**连接类**异常(`refused`/`timeout`/`reset`/`conn`)退避 `backoff` 秒后
|
|
165
|
+
重试**一次**;业务错误码与其它异常**不重试**。开店动作撞到有歧义的连接类异常
|
|
166
|
+
(timeout/reset)时**不重试**(见常量注释:重试会被内核当作"重启")。
|
|
167
|
+
|
|
168
|
+
`backoff` / `no_retry_actions` 是**可注入**的:调用方(`orchestrator._wd_call`)把自家模块
|
|
169
|
+
常量按值传进来 ⇒ 常量仍可在调用方被改写(既有回归就靠这条),而 HTTP/重试逻辑只有这一份。
|
|
170
|
+
"""
|
|
171
|
+
request_id = f"{action}-{int(time.time() * 1000)}-{os.getpid()}"
|
|
172
|
+
body: dict[str, Any] = {"action": action, "requestId": request_id}
|
|
173
|
+
if payload:
|
|
174
|
+
body.update(payload)
|
|
175
|
+
req = urllib.request.Request(
|
|
176
|
+
f"http://127.0.0.1:{port}",
|
|
177
|
+
data=json.dumps(body).encode("utf-8"),
|
|
178
|
+
headers={"Content-Type": "application/json; charset=utf-8"},
|
|
179
|
+
method="POST",
|
|
180
|
+
)
|
|
181
|
+
attempt = 0
|
|
182
|
+
while True:
|
|
183
|
+
attempt += 1
|
|
184
|
+
started = time.time()
|
|
185
|
+
try:
|
|
186
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
187
|
+
raw = r.read()
|
|
188
|
+
resp = json.loads(raw.decode("utf-8"))
|
|
189
|
+
except Exception as exc: # noqa: BLE001
|
|
190
|
+
wd_log_call(action, request_id, None, started, f"{type(exc).__name__}: {exc}")
|
|
191
|
+
kind = wd_conn_error_kind(exc)
|
|
192
|
+
ambiguous = kind in ("timeout", "reset") and action in no_retry_actions
|
|
193
|
+
if kind and attempt == 1 and not ambiguous:
|
|
194
|
+
print(f"[{time.strftime('%H:%M:%S')}] ⏳ WebDriver IPC 连接类异常"
|
|
195
|
+
f"({kind},action={action})⇒ 退避 {backoff}s 重试 1 次",
|
|
196
|
+
file=sys.stderr, flush=True)
|
|
197
|
+
time.sleep(backoff)
|
|
198
|
+
continue
|
|
199
|
+
if kind and attempt == 1 and ambiguous:
|
|
200
|
+
print(f"[{time.strftime('%H:%M:%S')}] ⚠️ WebDriver {action} 撞连接类异常"
|
|
201
|
+
f"({kind})⇒ **不重试**:该异常下请求可能已送达,重试会被内核视为"
|
|
202
|
+
f"「重启」(官方 api-core.md:188)⇒ 原样上抛,交调用方与人工处置",
|
|
203
|
+
file=sys.stderr, flush=True)
|
|
204
|
+
raise
|
|
205
|
+
err, last_error = wd_err_fields(resp)
|
|
206
|
+
wd_log_call(action, request_id, resp.get("statusCode"), started, err, last_error)
|
|
207
|
+
return resp
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def wd_check(resp: dict[str, Any], action: str) -> dict[str, Any]:
|
|
211
|
+
"""statusCode!=0 → 抛 WebDriverError(**优先读 `err`**,并带上 `LastError` 供排障)。"""
|
|
212
|
+
code = resp.get("statusCode")
|
|
213
|
+
if code not in (0, "0"):
|
|
214
|
+
err, last_error = wd_err_fields(resp)
|
|
215
|
+
raise WebDriverError(action, status_code=code, err=err, last_error=last_error)
|
|
216
|
+
return resp
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# ── 只读查询面:`getRunningInfo`(免凭据、零副作用)────────────────────────────────
|
|
220
|
+
# ⛔ 本段是认领前预检回退判据的唯一来源;⛔ 不含任何开/关窗口的动作。
|
|
221
|
+
|
|
222
|
+
def running_windows(port: int, timeout: float = 30.0) -> list[dict[str, Any]]:
|
|
223
|
+
"""`getRunningInfo` → 当前已开启店铺列表(**无需凭据**)。
|
|
224
|
+
|
|
225
|
+
端口显式传入(而不是收一个 `Config`):本模块不认识编排器的配置对象,
|
|
226
|
+
这样预检(`env_probe.py`)与编排器(`orchestrator.wd_running_info`)**共用同一个查询**。
|
|
227
|
+
"""
|
|
228
|
+
resp = wd_check(wd_call(port, "getRunningInfo", timeout=timeout), "getRunningInfo")
|
|
229
|
+
return resp.get("browsers") or []
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def window_item_name(b: dict[str, Any]) -> str:
|
|
233
|
+
"""窗口条目 → 店名(downloadPath 末段优先,回退 userData 末段;都没有 ⇒ `?`)。"""
|
|
234
|
+
return (str(b.get("downloadPath") or "").rstrip("/").split("/")[-1]
|
|
235
|
+
or str(b.get("userData") or "").split("/")[-1] or "?")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def window_item_line(b: dict[str, Any]) -> str:
|
|
239
|
+
"""窗口条目 → 一行摘要 `店名:端口@内核(handle=…)`。"""
|
|
240
|
+
return (f"{window_item_name(b)}:{b.get('debuggingPort')}"
|
|
241
|
+
f"@{b.get('core_version') or '?'}(handle={b.get('mainHandle') or '?'})")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def windows_summary(items: Any) -> str:
|
|
245
|
+
"""窗口清单 → 一行摘要(`店名:端口@内核(handle=…) | …`);空/异常 ⇒ 空串。"""
|
|
246
|
+
return " | ".join(window_item_line(b) for b in (items or []) if isinstance(b, dict))
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def running_windows_probe(port: int, timeout: float = 30.0) -> dict[str, Any]:
|
|
250
|
+
"""**只读**探测"本机有没有已运行的店铺窗口" → 结构化结论(**绝不抛异常**)。
|
|
251
|
+
|
|
252
|
+
⛔ 语义边界(认领前回退判据就靠它,别把三态揉成两态):
|
|
253
|
+
· `ok=True` ⟺ 查询**成功** ∧ `count ≥ 1`(有窗口 = 可驱动);
|
|
254
|
+
· `ok=False` + `error=""` ⇒ 查询成功但**一个窗口都没有**(真·没有);
|
|
255
|
+
· `ok=False` + `error!=""` ⇒ **查不到**(超时/客户端没起/解析失败)——
|
|
256
|
+
与"没有窗口"**必须分开**:查不到时**不得**当可用(⛔ 不猜)。
|
|
257
|
+
"""
|
|
258
|
+
try:
|
|
259
|
+
items = [b for b in (running_windows(port, timeout=timeout) or []) if isinstance(b, dict)]
|
|
260
|
+
except Exception as exc: # noqa: BLE001
|
|
261
|
+
return {"ok": False, "count": 0, "summary": "",
|
|
262
|
+
"error": f"{type(exc).__name__}: {exc}", "port": port}
|
|
263
|
+
return {"ok": bool(items), "count": len(items), "summary": windows_summary(items),
|
|
264
|
+
"error": "", "port": port}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 多帧 zstd 会话日志的切帧与解码(**唯一真源**)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么必须自己切帧:dsh 的会话日志 `session.jsonl.zstd` 是**多帧拼接**的 zstd
|
|
5
|
+
* (首帧 = header 行,必须恰好一行;其后每帧若干事件行)。而 Node 的
|
|
6
|
+
* `zstdDecompressSync` / `createZstdDecompress` **只解首帧就停**(2026-09-28 实测:
|
|
7
|
+
* 65MB 的日志只解出 182 字节)⇒ 直接用它会把"只看到 header"当成"日志没问题",
|
|
8
|
+
* 门禁**假绿**、掩盖真实损坏。所以这里扫描 frame magic + 逐切片试解压来切帧:
|
|
9
|
+
* 压缩数据里偶发假 magic,解不出来的切片自动并入前一帧。
|
|
10
|
+
*
|
|
11
|
+
* 本实现原在 scripts/session-repair-tool-result-content.mjs(2026-09-18 会话毒化事故
|
|
12
|
+
* 的修复工具里实战验证过),现抽出供会话修复与升级门禁共用。
|
|
13
|
+
*/
|
|
14
|
+
import { constants, zstdCompressSync, zstdDecompressSync } from 'node:zlib'
|
|
15
|
+
import { closeSync, openSync, readSync } from 'node:fs'
|
|
16
|
+
|
|
17
|
+
export const ZSTD_MAGIC = Buffer.from([0x28, 0xb5, 0x2f, 0xfd])
|
|
18
|
+
/** 与 dsh 写帧参数一致(校验和开启)。 */
|
|
19
|
+
export const FRAME_COMPRESS_OPTS = { params: { [constants.ZSTD_c_checksumFlag]: 1 } }
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 按帧切分一个多帧 zstd 缓冲区。
|
|
23
|
+
* @param {Buffer} buf
|
|
24
|
+
* @returns {{ start: number, end: number, plain: Buffer }[]}
|
|
25
|
+
* @throws 文件不以 magic 开头 / 切分未覆盖整个文件(宁可响亮失败,也不静默截断)
|
|
26
|
+
*/
|
|
27
|
+
export function splitFrames(buf) {
|
|
28
|
+
const candidates = []
|
|
29
|
+
let at = 0
|
|
30
|
+
while ((at = buf.indexOf(ZSTD_MAGIC, at)) !== -1) { candidates.push(at); at += ZSTD_MAGIC.length }
|
|
31
|
+
if (candidates.length === 0 || candidates[0] !== 0) throw new Error('不是以 zstd 帧 magic 开头')
|
|
32
|
+
const frames = []
|
|
33
|
+
let frameStart = 0
|
|
34
|
+
for (let k = 1; k <= candidates.length; k += 1) {
|
|
35
|
+
const end = k < candidates.length ? candidates[k] : buf.length
|
|
36
|
+
if (end <= frameStart) continue
|
|
37
|
+
let plain
|
|
38
|
+
try { plain = zstdDecompressSync(buf.subarray(frameStart, end)) } catch { continue } // 假 magic
|
|
39
|
+
if (plain.length === 0 || plain[plain.length - 1] !== 0x0a) continue // 真帧必以换行结尾
|
|
40
|
+
frames.push({ start: frameStart, end, plain })
|
|
41
|
+
frameStart = end
|
|
42
|
+
if (frameStart >= buf.length) break
|
|
43
|
+
}
|
|
44
|
+
if (frameStart !== buf.length) throw new Error(`帧切分未覆盖整个文件:停在 ${frameStart}/${buf.length}`)
|
|
45
|
+
return frames
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 解出事件行(首帧是 header 行,其后每行一个事件)。
|
|
50
|
+
* @param {Buffer} buf
|
|
51
|
+
* @returns {{ frames: object[], header: string, lines: { frame: number, raw: string }[] }}
|
|
52
|
+
*/
|
|
53
|
+
export function decodeSessionLines(buf) {
|
|
54
|
+
const frames = splitFrames(buf)
|
|
55
|
+
const header = frames[0].plain.toString('utf8')
|
|
56
|
+
if (header.split('\n').length !== 2) throw new Error('首帧不是恰好一行 header')
|
|
57
|
+
const lines = []
|
|
58
|
+
for (let f = 1; f < frames.length; f += 1) {
|
|
59
|
+
const parts = frames[f].plain.toString('utf8').split('\n')
|
|
60
|
+
if (parts[parts.length - 1] !== '') throw new Error(`第 ${f} 帧不以换行结尾`)
|
|
61
|
+
parts.pop()
|
|
62
|
+
for (const raw of parts) lines.push({ frame: f, raw })
|
|
63
|
+
}
|
|
64
|
+
return { frames, header, lines }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* 把「整文件解出来的文本」编码回多帧 zstd 字节流(首帧 = 第一行 header)。
|
|
69
|
+
* @param {string} text 完整的 jsonl 文本(首行为 header)
|
|
70
|
+
* @returns {Buffer}
|
|
71
|
+
*/
|
|
72
|
+
export function encodeSessionFrames(text) {
|
|
73
|
+
const all = text.split('\n')
|
|
74
|
+
if (all[all.length - 1] === '') all.pop()
|
|
75
|
+
const [header, ...events] = all
|
|
76
|
+
const frames = [zstdCompressSync(Buffer.from(`${header}\n`, 'utf8'), FRAME_COMPRESS_OPTS)]
|
|
77
|
+
if (events.length) frames.push(zstdCompressSync(Buffer.from(`${events.join('\n')}\n`, 'utf8'), FRAME_COMPRESS_OPTS))
|
|
78
|
+
return Buffer.concat(frames)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 供诊断:单帧解压能解出多少(用来说明"内置解压只解首帧"的陷阱)。 */
|
|
82
|
+
export function firstFrameBytes(buf) {
|
|
83
|
+
return zstdDecompressSync(buf).length
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 只解**首帧**拿会话 header(廉价):扫描会话目录找子会话时用,避免为每个文件解压整个多帧日志。
|
|
88
|
+
* 首帧极小(一行 header),故只读文件前 64KB 再切第一帧。
|
|
89
|
+
* @param {string} file
|
|
90
|
+
* @returns {object|null}
|
|
91
|
+
*/
|
|
92
|
+
export function readSessionHeader(file) {
|
|
93
|
+
try {
|
|
94
|
+
const fd = openSync(file, 'r')
|
|
95
|
+
const buf = Buffer.alloc(65536)
|
|
96
|
+
const n = readSync(fd, buf, 0, buf.length, 0)
|
|
97
|
+
closeSync(fd)
|
|
98
|
+
const head = buf.subarray(0, n)
|
|
99
|
+
const first = splitFrames(head)[0]
|
|
100
|
+
if (!first) return null
|
|
101
|
+
const line = first.plain.toString('utf8').split('\n')[0]
|
|
102
|
+
return JSON.parse(line)
|
|
103
|
+
} catch { return null }
|
|
104
|
+
}
|