@whyshu/argent-engine 0.0.0-stage → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MANIFEST.json +341 -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 +576 -0
- package/engine/scripts/billing-mode-daily.sh +13 -0
- package/engine/scripts/build-engine-package.mjs +126 -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 +378 -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/collect-evidence.mjs +42 -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 +135 -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 +47 -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 +261 -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/final-check.mjs +43 -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 +735 -0
- package/engine/scripts/flow-scaffold.py +275 -0
- package/engine/scripts/flow-timers-plist.sh +87 -0
- package/engine/scripts/flow-timers.sh +184 -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 +1025 -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 +2278 -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 +195 -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.sh +18 -0
- package/engine/scripts/lib/argent_datadir.py +285 -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 +295 -0
- package/engine/scripts/poll-invoice.sh +76 -0
- package/engine/scripts/profile-doctor.mjs +686 -0
- package/engine/scripts/provider-select.sh +71 -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/retire-inventory.mjs +40 -0
- package/engine/scripts/run-tests.sh +257 -0
- package/engine/scripts/s1-audit.mjs +85 -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 +137 -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 +74 -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 +35 -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 +217 -0
- package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
- package/engine/scripts/ziniao-windows.sh +154 -0
- package/engine/skills/README.md +86 -0
- package/engine/skills/argent-dev/SKILL.md +41 -0
- package/engine/skills/internal.json +37 -0
- package/engine/skills/lktop-pm/SKILL.md +35 -0
- package/engine/skills/lktopapp-dev/SKILL.md +34 -0
- package/engine/skills/ml-config-guide/SKILL.md +50 -0
- package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
- package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
- package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
- package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
- package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
- package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
- package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
- package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
- package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
- package/engine/skills/ml-invoice/SKILL.md +506 -0
- package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
- package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
- package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
- package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
- package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
- package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
- package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
- package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
- package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
- package/engine/skills/pet-dev/SKILL.md +42 -0
- package/engine/skills/whyshu-pm/SKILL.md +35 -0
- package/engine/skills/whyshu-web-dev/SKILL.md +50 -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 +738 -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 +22 -4
- package/requirements.txt +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* deps-migrate —— 存量机器「遗留 `file:` 依赖 ⇒ npm 名」的**唯一实现**(0.6.0 W8,2026-10-14)
|
|
4
|
+
*
|
|
5
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
6
|
+
* 它解决什么(现场形态,2026-10-03 实测)
|
|
7
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
8
|
+
* 存量客户机的 profile `package.json` 里,Argent 插件是按**本机路径**装的:
|
|
9
|
+
* `"argent-core": "file:/private/var/folders/…/tmp.yHwjWMOjNV/repo/plugins/argent-core"`
|
|
10
|
+
* 那个临时目录早没了 ⇒ 依赖永远解析不到;而 npm 名(`@whyshu/argent-core@1.2.3`)是**随机器走**的。
|
|
11
|
+
* 本模块把这件事做成一条**可干跑、可回滚、幂等**的迁移:
|
|
12
|
+
*
|
|
13
|
+
* | 纪律 | 判据 |
|
|
14
|
+
* |---|---|
|
|
15
|
+
* | **默认干跑** | `plan()` 是纯函数(零 IO 副作用);只有 `apply()` 才写盘 |
|
|
16
|
+
* | **先备份** | 写前 `cp` 到 `<数据目录>/backup/<名>.<UTC 时间戳>` + **sha256** + **逐字回滚命令** |
|
|
17
|
+
* | **写后读回** | 写完立刻重新解析并断言目标形态(⛔ 不"写完就算成功") |
|
|
18
|
+
* | **幂等** | 已经迁过的机器 ⇒ `plan().changed === 0` ⇒ **零写入**(连 mtime 都不动) |
|
|
19
|
+
* | **⛔ 不裸跑包管理器** | 默认**不跑**;要跑必须显式 `--allow-package-manager`(AGENTS 红线:会把 profile 弄坏) |
|
|
20
|
+
* | **审计** | 每次真写追加一行 JSONL(谁/何时/改了什么/备份在哪/回滚命令) |
|
|
21
|
+
*
|
|
22
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
23
|
+
* 边界(⛔ 改本文件前先读)
|
|
24
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
25
|
+
* 1. **只碰已知 Argent 插件**(判据 = `scripts/lib/plugin-packages.mjs#pluginIdOf`,**唯一真源**)。
|
|
26
|
+
* 别的 `file:`、别人的 scope、官方 `@deepseek-ai/*` ⇒ 一律 `unknown`/`keep`,⛔ 不越权。
|
|
27
|
+
* 2. **只迁"顶层包目录"形态**(`file:…/plugins/<id>` / `file:…/packs-cache/<id>.tgz`)。
|
|
28
|
+
* **子路径导出**(`file:…/plugins/argent-core/llm-whyshu`)在 npm 上**没有对应包**
|
|
29
|
+
* (它是 `argent-core` 的子导出)⇒ 必须**原样留着**(⛔ 迁了就是断链)。
|
|
30
|
+
* 3. **不改版本语义**:目标版本 = **本机克隆/包留档里那个真实版本**(读不到 ⇒ 该条判 `blocked`,
|
|
31
|
+
* ⛔ 不编一个版本号)。壳/智能体用**精确版本**,与安装器 `npmSpecOf()` 同口径。
|
|
32
|
+
* 4. **两条线共用**:升级器(`scripts/argent-upgrade.mjs migrate-deps`)与安装器的
|
|
33
|
+
* 依赖键迁移**都调这里的纯函数** —— ⛔ 不许在别处写第二份"file: 怎么迁"。
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { createHash } from 'node:crypto'
|
|
37
|
+
import { copyFileSync, existsSync, mkdirSync, appendFileSync, readFileSync, writeFileSync } from 'node:fs'
|
|
38
|
+
import { hostname, userInfo } from 'node:os'
|
|
39
|
+
import { dirname, join } from 'node:path'
|
|
40
|
+
import { pluginIdOf, PLUGIN_PACKAGES, npmNameOf, npmSpecOf, readPluginVersion, pluginPackageDir } from './plugin-packages.mjs'
|
|
41
|
+
|
|
42
|
+
const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v)
|
|
43
|
+
export const sha256 = (text) => createHash('sha256').update(text).digest('hex')
|
|
44
|
+
|
|
45
|
+
/** 一条依赖的形态判据(**纯**:只按字符串判,⛔ 不读盘)。 */
|
|
46
|
+
export function classifySpec(spec) {
|
|
47
|
+
const s = String(spec ?? '').trim()
|
|
48
|
+
if (!s) return { kind: 'empty' }
|
|
49
|
+
if (!s.startsWith('file:')) return { kind: 'npm', spec: s }
|
|
50
|
+
const p = s.slice('file:'.length).replace(/\\/g, '/').replace(/\/+$/, '')
|
|
51
|
+
const m = p.match(/(?:^|\/)(plugins|packs-cache)\/([^/]+?)(?:\.tgz)?$/)
|
|
52
|
+
if (!m) {
|
|
53
|
+
// `file:…/plugins/<id>/<子路径>`(子导出)或任意别的路径
|
|
54
|
+
const sub = p.match(/(?:^|\/)plugins\/([^/]+)\/.+$/)
|
|
55
|
+
if (sub) return { kind: 'file-subpath', spec: s, base: sub[1], path: p }
|
|
56
|
+
return { kind: 'file-other', spec: s, path: p }
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
kind: m[1] === 'packs-cache' ? 'file-tgz' : 'file-plugin',
|
|
60
|
+
spec: s, base: m[2], path: p,
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 算迁移计划(**纯函数**:只读调用方传进来的 `pkg`;版本从 `versionOf` 回调拿)。
|
|
66
|
+
*
|
|
67
|
+
* @param {object} opts
|
|
68
|
+
* @param {object} opts.pkg profile 的 `package.json`(已解析)
|
|
69
|
+
* @param {(id:string)=>string} [opts.versionOf] 取某插件的**真实版本**(读不到 ⇒ '')
|
|
70
|
+
* @param {(id:string)=>boolean} [opts.knownId] 该 id 是不是已知 Argent 插件(缺省用 pluginIdOf)
|
|
71
|
+
* @returns {{ok:boolean, changed:number, entries:Array, deps:object, blocked:string[], notes:string[]}}
|
|
72
|
+
*/
|
|
73
|
+
export function planDepsMigration({ pkg, versionOf = () => '', knownId = null, source = 'profile' } = {}) {
|
|
74
|
+
const deps = isObj(pkg && pkg.dependencies) ? pkg.dependencies : {}
|
|
75
|
+
const entries = []
|
|
76
|
+
const blocked = []
|
|
77
|
+
const notes = []
|
|
78
|
+
if (!isObj(pkg && pkg.dependencies)) {
|
|
79
|
+
notes.push('profile 的 `dependencies` 不是对象(或不存在)⇒ 无可迁移之物')
|
|
80
|
+
return { ok: true, changed: 0, entries, deps: { ...deps }, blocked, notes, source }
|
|
81
|
+
}
|
|
82
|
+
const out = { ...deps }
|
|
83
|
+
for (const [key, spec] of Object.entries(deps)) {
|
|
84
|
+
const c = classifySpec(spec)
|
|
85
|
+
const id = knownId ? (knownId(key) ? knownId(key) : null) : pluginIdOf(key)
|
|
86
|
+
const row = { key, spec: String(spec), kind: c.kind, id: id || null, action: 'keep', to: null, why: '' }
|
|
87
|
+
|
|
88
|
+
if (c.kind === 'file-subpath') {
|
|
89
|
+
row.action = 'keep'
|
|
90
|
+
row.why = `子路径导出(${c.base} 的子路径)在 npm 上没有对应包 ⇒ 原样留着(迁了就是断链)`
|
|
91
|
+
entries.push(row); continue
|
|
92
|
+
}
|
|
93
|
+
if (c.kind === 'file-other' || c.kind === 'empty') {
|
|
94
|
+
row.action = 'keep'
|
|
95
|
+
row.why = c.kind === 'empty' ? '空值 ⇒ 不动' : '`file:` 指向的既不是插件目录也不是包留档 ⇒ ⛔ 不越权'
|
|
96
|
+
entries.push(row); continue
|
|
97
|
+
}
|
|
98
|
+
if (!id) {
|
|
99
|
+
row.action = c.kind === 'npm' ? 'already-npm' : 'keep'
|
|
100
|
+
row.why = c.kind === 'npm'
|
|
101
|
+
? '已是非 `file:` 规格且不是已知 Argent 插件 ⇒ 原样(⛔ 不越权)'
|
|
102
|
+
: `\`file:\` 的 base 不是已知 Argent 插件(${c.base})⇒ ⛔ 不越权改`
|
|
103
|
+
entries.push(row); continue
|
|
104
|
+
}
|
|
105
|
+
const npm = npmNameOf(id)
|
|
106
|
+
const needNpmSpec = c.kind === 'file-plugin' || c.kind === 'file-tgz'
|
|
107
|
+
if (!needNpmSpec) {
|
|
108
|
+
// 非 `file:`(npm 范围 / tarball 之外的写法):**只迁键**(spec 原样 —— 来源不是本模块的事)
|
|
109
|
+
if (key === npm) { row.action = 'already-npm'; row.why = `键已是 npm 名(${npm})⇒ 无需迁移`; entries.push(row); continue }
|
|
110
|
+
delete out[key]
|
|
111
|
+
out[npm] = String(spec)
|
|
112
|
+
row.action = 'migrate'
|
|
113
|
+
row.to = `${npm}(spec 原样:${String(spec)})`
|
|
114
|
+
row.renamed = true
|
|
115
|
+
row.why = `裸名键 ⇒ npm 名(spec 原样保留;来源不是本次迁移的对象)`
|
|
116
|
+
entries.push(row); continue
|
|
117
|
+
}
|
|
118
|
+
if (key === npm) {
|
|
119
|
+
row.action = 'already-npm'
|
|
120
|
+
row.why = `键已是 npm 名(${npm})⇒ 无需迁移`
|
|
121
|
+
entries.push(row); continue
|
|
122
|
+
}
|
|
123
|
+
const ver = String(versionOf(id) || '')
|
|
124
|
+
if (!ver) {
|
|
125
|
+
row.action = 'blocked'
|
|
126
|
+
row.why = `读不到 ${id} 的**真实版本**(本机克隆/包留档里都没有)⇒ ⛔ 不编版本号;先补克隆或包留档`
|
|
127
|
+
blocked.push(`${key} → ${npm}:${row.why}`)
|
|
128
|
+
entries.push(row); continue
|
|
129
|
+
}
|
|
130
|
+
const to = npmSpecOf(id, ver)
|
|
131
|
+
if (!to) {
|
|
132
|
+
row.action = 'blocked'
|
|
133
|
+
row.why = `拼不出 npm 规格(${id})`
|
|
134
|
+
blocked.push(`${key}:${row.why}`)
|
|
135
|
+
entries.push(row); continue
|
|
136
|
+
}
|
|
137
|
+
row.action = 'migrate'
|
|
138
|
+
row.to = to
|
|
139
|
+
row.why = `遗留 ${c.kind}(${c.path})⇒ npm 名 ${to}(版本取自本机真实包)`
|
|
140
|
+
if (key !== npm) {
|
|
141
|
+
delete out[key]
|
|
142
|
+
out[npm] = to
|
|
143
|
+
row.renamed = true
|
|
144
|
+
row.why += ';**键同时改名**(裸名 ⇒ npm 名)'
|
|
145
|
+
} else {
|
|
146
|
+
out[key] = to
|
|
147
|
+
}
|
|
148
|
+
entries.push(row)
|
|
149
|
+
}
|
|
150
|
+
const changed = entries.filter((e) => e.action === 'migrate').length
|
|
151
|
+
if (!changed) notes.push('没有需要迁移的依赖 ⇒ 计划为空(幂等:这一步**零写入**)')
|
|
152
|
+
return { ok: blocked.length === 0, changed, entries, deps: out, blocked, notes, source }
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** 序列化 profile `package.json`(与仓库既有写盘口径一致:2 空格 + 末尾换行)。 */
|
|
156
|
+
export const serializePkg = (pkg, deps) => `${JSON.stringify({ ...pkg, dependencies: deps }, null, 2)}\n`
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 备份文件名(UTC 时间戳 + 原文件名;与 `backupFile()` 同风格)。
|
|
160
|
+
* ⚠️ `reason` 会被 **slug 化**(非 `[A-Za-z0-9._-]` ⇒ `-`):备份名会被写进**回滚命令**,
|
|
161
|
+
* 带空白/箭头/中文的文件名在 shell 里是另一个坑(`cp -p "a b" c` 一旦有人少写引号就炸)。
|
|
162
|
+
*/
|
|
163
|
+
export function backupName(file, now = new Date(), reason = '') {
|
|
164
|
+
const ts = now.toISOString().replace(/[-:]/g, '').replace(/\..+$/, '').replace('T', '-')
|
|
165
|
+
const slug = String(reason).replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^-+|-+$/g, '')
|
|
166
|
+
const tail = slug ? `.${slug}` : ''
|
|
167
|
+
return `${file.split('/').pop()}.deps-migrate-${ts}${tail}`
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* 干跑/真跑的统一渲染(**CLI 与审计共用**;⛔ 别处不再拼一遍)。
|
|
172
|
+
* @returns {{lines:string[], rollback:string, summary:string}}
|
|
173
|
+
*/
|
|
174
|
+
export function renderPlan(plan, { profilePkgPath = '', apply = false, runPackageManager = false } = {}) {
|
|
175
|
+
const lines = []
|
|
176
|
+
const mig = plan.entries.filter((e) => e.action === 'migrate')
|
|
177
|
+
const kee = plan.entries.filter((e) => e.action === 'keep')
|
|
178
|
+
const blk = plan.entries.filter((e) => e.action === 'blocked')
|
|
179
|
+
lines.push(`${apply ? '执行' : '干跑(dry-run)'}:将改 ${mig.length} 条依赖|不动 ${kee.length} 条|受阻 ${blk.length} 条`)
|
|
180
|
+
for (const e of plan.entries) {
|
|
181
|
+
if (e.action === 'migrate') lines.push(` → ${e.key}:${e.spec} ⇒ ${e.to}`)
|
|
182
|
+
else if (e.action === 'blocked') lines.push(` ✗ ${e.key}:${e.why}`)
|
|
183
|
+
else if (e.action === 'keep') lines.push(` · ${e.key}:保持(${e.why})`)
|
|
184
|
+
}
|
|
185
|
+
lines.push(`包管理器:${runPackageManager ? '**会跑**(--allow-package-manager 显式开启)' : '不跑(默认;⛔ 裸跑 pnpm 会把 profile 弄坏)'}`)
|
|
186
|
+
lines.push(`影响面(face):profile package.json${profilePkgPath ? `(${profilePkgPath})` : ''}`
|
|
187
|
+
+ `${runPackageManager ? ' + node_modules(包管理器装上/更新后同步)' : '(node_modules 不变 —— 未跑包管理器,落位仍是旧来源)'}`)
|
|
188
|
+
const rollback = profilePkgPath
|
|
189
|
+
? `cp -p "${profilePkgPath}.deps-migrate-<时间戳>" "${profilePkgPath}" # 或:cp -p "<备份路径>" "${profilePkgPath}"`
|
|
190
|
+
: '(没有 profile package.json ⇒ 无需回滚)'
|
|
191
|
+
return { lines, rollback, summary: `${mig.length} 条待迁 / ${blk.length} 条受阻` }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 真跑:**先备份 → 改 manifest → 读回验收 → 审计**(幂等:`changed === 0` ⇒ 立刻返回,零写入)。
|
|
196
|
+
*
|
|
197
|
+
* ⛔ 本函数**不跑任何包管理器**(那是调用方在显式开关下的职责)。
|
|
198
|
+
* @returns {{ok:boolean, wrote:boolean, backup:string, backupSha:string, rollback:string,
|
|
199
|
+
* audit:string, verify:string, error:string}}
|
|
200
|
+
*/
|
|
201
|
+
export function applyDepsMigration({
|
|
202
|
+
pkgPath, pkg, plan, auditPath, backupDir, now = new Date(), reason = 'W8 file:→npm',
|
|
203
|
+
fs = { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, appendFileSync, dirname },
|
|
204
|
+
by = '',
|
|
205
|
+
/**
|
|
206
|
+
* 读回验收用的**重算器**(可选)。⚠️ 必须与产出 `plan` 的那次用**同一套判据**
|
|
207
|
+
* (同样的 `knownId`/`versionOf`)—— 否则"缺版本"这类条目在两套判据下结论不同,
|
|
208
|
+
* 验收会**假红**(实测:夹具里没传 knownId ⇒ 重算时裸键也算待迁 ⇒ 明明迁对了却判失败)。
|
|
209
|
+
* 缺省 = `(pkg) => planDepsMigration({ pkg })`(仅"spec 还是不是 `file:`"这一层判据)。
|
|
210
|
+
*/
|
|
211
|
+
replan = null,
|
|
212
|
+
} = {}) {
|
|
213
|
+
const result = { ok: false, wrote: false, backup: '', backupSha: '', rollback: '', audit: '', verify: '', error: '' }
|
|
214
|
+
if (!plan || plan.changed === 0) {
|
|
215
|
+
result.ok = true
|
|
216
|
+
result.verify = 'nothing-to-do(幂等:零写入)'
|
|
217
|
+
return result
|
|
218
|
+
}
|
|
219
|
+
if (plan.blocked && plan.blocked.length) {
|
|
220
|
+
result.error = `有 ${plan.blocked.length} 条受阻(${plan.blocked[0]})⇒ ⛔ 拒绝半截迁移(要么全可迁、要么先补证据)`
|
|
221
|
+
return result
|
|
222
|
+
}
|
|
223
|
+
const before = fs.readFileSync(pkgPath, 'utf8')
|
|
224
|
+
if (!backupDir) { result.error = '没有备份目录(backupDir 必填)⇒ ⛔ 不写'; return result }
|
|
225
|
+
const backup = join(backupDir, backupName(pkgPath, now, reason))
|
|
226
|
+
try {
|
|
227
|
+
fs.mkdirSync(dirname(backup), { recursive: true })
|
|
228
|
+
fs.copyFileSync(pkgPath, backup)
|
|
229
|
+
} catch (e) {
|
|
230
|
+
result.error = `备份失败(${String((e && e.message) || e)})⇒ ⛔ 不写`
|
|
231
|
+
return result
|
|
232
|
+
}
|
|
233
|
+
result.backup = backup
|
|
234
|
+
result.backupSha = sha256(before)
|
|
235
|
+
result.rollback = `cp -p "${backup}" "${pkgPath}"`
|
|
236
|
+
const text = serializePkg(pkg, plan.deps)
|
|
237
|
+
try {
|
|
238
|
+
fs.writeFileSync(pkgPath, text)
|
|
239
|
+
} catch (e) {
|
|
240
|
+
result.error = `写盘失败(${String((e && e.message) || e)});备份在 ${backup}`
|
|
241
|
+
return result
|
|
242
|
+
}
|
|
243
|
+
// ── 读回验收(⛔ 不"写完就算成功")──────────────────────────────────────────
|
|
244
|
+
let after = null
|
|
245
|
+
try { after = fs.readFileSync(pkgPath, 'utf8') } catch (e) { result.error = `写后读回失败(${String((e && e.message) || e)})`; return result }
|
|
246
|
+
let re = null
|
|
247
|
+
try {
|
|
248
|
+
re = (typeof replan === 'function' ? replan : (doc) => planDepsMigration({ pkg: doc }))(JSON.parse(after))
|
|
249
|
+
} catch (e) {
|
|
250
|
+
result.error = `写后读回解析失败(${String((e && e.message) || e)})⇒ 逐字节回滚:${result.rollback}`
|
|
251
|
+
try { fs.writeFileSync(pkgPath, before) } catch { /* 回滚失败已由 error 表达 */ }
|
|
252
|
+
return result
|
|
253
|
+
}
|
|
254
|
+
if (!re || re.changed !== 0) {
|
|
255
|
+
result.error = `写后读回验收失败:重算仍显示 ${re ? re.changed : '?'} 条待迁 ⇒ 逐字节回滚:${result.rollback}`
|
|
256
|
+
try { fs.writeFileSync(pkgPath, before) } catch { /* 回滚失败已由 error 表达 */ }
|
|
257
|
+
return result
|
|
258
|
+
}
|
|
259
|
+
// ⛔ 第二条判据(**内容真的变了**):写入被静默吞掉 / 写成同一份字节 ⇒ 不算迁移成功。
|
|
260
|
+
// 只看"重算待迁 0 条"是不够的:**判据本身依赖磁盘内容** ⇒ 内容没变时那条判据会**自洽地通过**。
|
|
261
|
+
if (sha256(after) === result.backupSha) {
|
|
262
|
+
result.error = `写后读回:文件**逐字节未变**(写入被静默吞掉)⇒ 不算迁移成功;回滚/排查:${result.rollback}`
|
|
263
|
+
return result
|
|
264
|
+
}
|
|
265
|
+
result.verify = `写后读回:重算待迁 0 条 ∧ 内容已变(before sha256 ${result.backupSha.slice(0, 12)}…)`
|
|
266
|
+
result.wrote = true
|
|
267
|
+
if (auditPath) {
|
|
268
|
+
try {
|
|
269
|
+
fs.mkdirSync(dirname(auditPath), { recursive: true })
|
|
270
|
+
const rec = {
|
|
271
|
+
ts: now.toISOString(),
|
|
272
|
+
tool: 'deps-migrate',
|
|
273
|
+
reason,
|
|
274
|
+
pkgPath,
|
|
275
|
+
backup: result.backup,
|
|
276
|
+
backupSha256: result.backupSha,
|
|
277
|
+
rollback: result.rollback,
|
|
278
|
+
changed: plan.entries.filter((e) => e.action === 'migrate').map((e) => ({ from: `${e.key}=${e.spec}`, to: e.to })),
|
|
279
|
+
kept: plan.entries.filter((e) => e.action === 'keep').map((e) => e.key),
|
|
280
|
+
packageManager: false,
|
|
281
|
+
by: by || `${(() => { try { return userInfo().username } catch { return 'unknown' } })()}@${hostname()}`,
|
|
282
|
+
}
|
|
283
|
+
if (!fs.existsSync(auditPath)) fs.appendFileSync(auditPath, `${JSON.stringify({ _doc: '依赖迁移审计(W8):谁在什么时候把 file: 依赖迁成了 npm 名。JSONL 追加;与 schedule-audit.jsonl 同风格。' })}\n`)
|
|
284
|
+
fs.appendFileSync(auditPath, `${JSON.stringify(rec)}\n`)
|
|
285
|
+
result.audit = auditPath
|
|
286
|
+
} catch (e) {
|
|
287
|
+
result.error = `审计追加失败(${String((e && e.message) || e)})—— 迁移本身已成功并已验收`
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
result.ok = true
|
|
291
|
+
return result
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** 取某插件的**真实版本**(克隆里的 `plugins/<id>/package.json`;读不到 ⇒ '',⛔ 不编)。 */
|
|
295
|
+
export function realVersionOf(repoRoot, id) {
|
|
296
|
+
if (!repoRoot) return ''
|
|
297
|
+
const dir = pluginPackageDir(repoRoot, id)
|
|
298
|
+
if (!dir || !existsSync(dir)) return ''
|
|
299
|
+
return readPluginVersion(repoRoot, id) || ''
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** 已知 id 判据(**写侧严格**:只认裸名/本仓 npm 名,⛔ 不认路径形态与别的 scope)。 */
|
|
303
|
+
export function knownPluginId(key) {
|
|
304
|
+
const s = String(key ?? '').trim()
|
|
305
|
+
if (!s) return null
|
|
306
|
+
for (const p of PLUGIN_PACKAGES) {
|
|
307
|
+
if (s === p.id || s === p.npm) return p.id
|
|
308
|
+
}
|
|
309
|
+
return null
|
|
310
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 宿主(dsh)**解析链** —— 唯一真源(纯函数,可离线测试;2026-10-12 用户决定)。
|
|
3
|
+
*
|
|
4
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
5
|
+
* 一、为什么有它
|
|
6
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
7
|
+
* 用户 2026-10-12 定的三条政策:
|
|
8
|
+
* ① **彻底删掉 `--install-host`**:Argent ⛔ 不代装宿主(宿主迭代频繁、副本维护不动;装不上客户会
|
|
9
|
+
* 找我们,而我们解决不了)。理由写进设计 §8.4 的实现注记。
|
|
10
|
+
* ② 被拒时给的是 dsh **官方**运行方式(官方 README「运行」):首选
|
|
11
|
+
* **`npx @deepseek-ai/dsh web`**(通过 npm 运行),备选**从源码运行**。
|
|
12
|
+
* ③ **npx 形态是官方首选 ⇒ 必须被识别为"有宿主"**(旧口径"npx 不算已安装宿主"**作废**)。
|
|
13
|
+
*
|
|
14
|
+
* 解析链(顺序即优先级;**确定且可解释**,每一步都能在输出里看到):
|
|
15
|
+
* PATH 上的 `dsh` → `DSH_BIN` → **npx 缓存形态** → 都没有 ⇒ 调用方拒绝 + 官方指引。
|
|
16
|
+
*
|
|
17
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
* 二、npx 缓存形态长什么样
|
|
19
|
+
* ══════════════════════════════════════════════════════════════════════════
|
|
20
|
+
* `npx @deepseek-ai/dsh@<版本> web` 跑过之后,npm 会把整棵依赖树留在自己的 `_npx` 缓存里:
|
|
21
|
+
* `<npm cache>/_npx/<hash>/node_modules/@deepseek-ai/dsh/package.json` ← 这里读 version
|
|
22
|
+
* 所以判据是**读一个 package.json**(纯只读、不联网、不执行 npx)——⛔ 不跑 `npx --version`:
|
|
23
|
+
* 那可能触发一次真实下载(慢、且把"零网络"变成假的),而版本号在文件里就有。
|
|
24
|
+
*
|
|
25
|
+
* 多个候选时的选择规则(`NPX_PICK_RULE`,调用方必须**打印选了哪个**):
|
|
26
|
+
* ① 只认能读出的 `@deepseek-ai/dsh`(坏 JSON / 别的包 ⇒ 跳过,不猜);
|
|
27
|
+
* ② **最近使用优先** = `package.json` 的 mtime 最新(npx 每次跑会刷新它 ⇒ 最接近"你实际在跑的
|
|
28
|
+
* 那一个");③ 同刻(mtime 相同)按**路径升序** —— 保证结果与目录遍历顺序无关、可复现。
|
|
29
|
+
* ⚠️ 刻意**不**按"版本号最大"或"优先窗口内版本"挑:客户实际在跑哪个版本,就该按哪个版本判门禁
|
|
30
|
+
* (否则我们会在一台实际跑着窗口外版本的机器上"装成功",且客户不会知道)。
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 一个 package.json 对象是不是 dsh 主包(判据只认 name + 非空 version)。
|
|
35
|
+
* @param {any} obj
|
|
36
|
+
* @returns {boolean}
|
|
37
|
+
*/
|
|
38
|
+
export function isDshPackageJson(obj) {
|
|
39
|
+
return Boolean(obj) && typeof obj === 'object'
|
|
40
|
+
&& obj.name === '@deepseek-ai/dsh'
|
|
41
|
+
&& typeof obj.version === 'string' && obj.version.trim() !== ''
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* npx 缓存候选目录(按查序返回,已去重)。
|
|
46
|
+
* · `npm_config_cache` / `NPM_CONFIG_CACHE`(npm 自己的覆盖)⇒ `<cache>/_npx`
|
|
47
|
+
* · `~/.npm/_npx`(macOS/Linux 与 npm 默认)
|
|
48
|
+
* · `%LOCALAPPDATA%\npm-cache\_npx`(Windows 默认)
|
|
49
|
+
* @param {{home?: string, env?: Record<string,string|undefined>}} o
|
|
50
|
+
* @returns {string[]}
|
|
51
|
+
*/
|
|
52
|
+
export function npxCacheDirs({ home = '', env = {} } = {}) {
|
|
53
|
+
const out = []
|
|
54
|
+
const push = (p) => { const v = String(p || '').replace(/[\\/]+$/, ''); if (v && !out.includes(v)) out.push(v) }
|
|
55
|
+
const cache = env.npm_config_cache || env.NPM_CONFIG_CACHE || ''
|
|
56
|
+
if (cache) push(`${cache}/_npx`)
|
|
57
|
+
if (home) push(`${home}/.npm/_npx`)
|
|
58
|
+
const la = env.LOCALAPPDATA || env.LocalAppData || ''
|
|
59
|
+
if (la) push(`${la}/npm-cache/_npx`)
|
|
60
|
+
return out
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 扫缓存目录,返回所有"看起来装过 dsh"的候选(**只读**;坏 JSON / 别的包一律跳过,不猜)。
|
|
65
|
+
* @param {string[]} dirs npxCacheDirs() 的产物
|
|
66
|
+
* @param {{exists:(p:string)=>boolean, readdir:(p:string)=>string[], read:(p:string)=>string, stat:(p:string)=>{mtimeMs:number}}} io
|
|
67
|
+
* @returns {Array<{pkgDir:string, pkgJson:string, version:string, mtimeMs:number, cacheDir:string, hash:string}>}
|
|
68
|
+
*/
|
|
69
|
+
export function scanNpxCache(dirs, io) {
|
|
70
|
+
const out = []
|
|
71
|
+
for (const dir of dirs || []) {
|
|
72
|
+
if (!dir || !io.exists(dir)) continue
|
|
73
|
+
let hashes = []
|
|
74
|
+
try { hashes = io.readdir(dir) } catch { continue }
|
|
75
|
+
for (const hash of [...hashes].sort()) {
|
|
76
|
+
const pkgDir = `${dir}/${hash}/node_modules/@deepseek-ai/dsh`
|
|
77
|
+
const pkgJson = `${pkgDir}/package.json`
|
|
78
|
+
if (!io.exists(pkgJson)) continue
|
|
79
|
+
let meta = null
|
|
80
|
+
try { meta = JSON.parse(io.read(pkgJson)) } catch { continue } // 坏 JSON ⇒ 跳过(不猜版本)
|
|
81
|
+
if (!isDshPackageJson(meta)) continue
|
|
82
|
+
let mtimeMs = 0
|
|
83
|
+
try { mtimeMs = Number(io.stat(pkgJson).mtimeMs) || 0 } catch { mtimeMs = 0 }
|
|
84
|
+
out.push({ pkgDir, pkgJson, version: String(meta.version).trim(), mtimeMs, cacheDir: dir, hash })
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return out
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 确定性挑选(规则见文件头 `NPX_PICK_RULE`):mtime 最新优先,同刻按路径升序。
|
|
92
|
+
* ⛔ 不做"优先窗口内版本"的二次挑选 —— 这台机器实际在跑哪个版本,就按哪个版本判门禁。
|
|
93
|
+
* @param {Array<object>} cands scanNpxCache() 的产物
|
|
94
|
+
* @returns {{chosen: object|null, considered: Array<{version:string,pkgDir:string,mtimeMs:number}>}}
|
|
95
|
+
*/
|
|
96
|
+
export function pickNpxHost(cands) {
|
|
97
|
+
const list = [...(cands || [])].sort((a, b) => {
|
|
98
|
+
if (b.mtimeMs !== a.mtimeMs) return b.mtimeMs - a.mtimeMs
|
|
99
|
+
return a.pkgDir < b.pkgDir ? -1 : a.pkgDir > b.pkgDir ? 1 : 0
|
|
100
|
+
})
|
|
101
|
+
return {
|
|
102
|
+
chosen: list.length ? list[0] : null,
|
|
103
|
+
considered: list.map((c) => ({ version: c.version, pkgDir: c.pkgDir, mtimeMs: c.mtimeMs })),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 选择规则的**人话**(调用方打印它 ⇒ "确定且可解释");⛔ 别在别处再写第二份措辞。 */
|
|
108
|
+
export const NPX_PICK_RULE = 'npx 缓存里可能有多个 dsh:按「最近使用优先(package.json mtime 最新),同刻按路径升序」'
|
|
109
|
+
+ '选定一个(⛔ 不按版本号挑:你这台机器实际在跑哪个版本,就按哪个版本判门禁)'
|