@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,315 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# drill-clean-machine.sh —— **干净机器一条命令**的隔离演练(D1/D2 的可复现证据;2026-10-05)
|
|
3
|
+
#
|
|
4
|
+
# 用途:把 `scripts/install-one-shot.mjs` 放进一个**与真实机器无关的夹具**里真跑一遍,
|
|
5
|
+
# 并按"真实目录零变化"哨兵自证**没有**碰过 `~/.dsh/**`、`~/.argent-dsh/**`。
|
|
6
|
+
#
|
|
7
|
+
# 隔离构造(三条,缺一不可):
|
|
8
|
+
# ① `HOME` 指向 `$FIX/home`(全新空目录)⇒ `~/.dsh`、`~/.argent-dsh`、`~/.pnpm` 全落在夹具里;
|
|
9
|
+
# ② 子进程一律 `env -i`(**极简环境**:只给 HOME/PATH,清掉 DSH_HOME/ARGENT_DATA_DIR/密钥等实例级开关);
|
|
10
|
+
# ③ PATH 只含**夹具 bin**(node/git/dsh 的软链)+ 系统目录 ⇒ 包管理器**不在** PATH 上
|
|
11
|
+
# (演练刻意不跑 pnpm —— 装配走安装器的 `--from-packs` 离线面)。
|
|
12
|
+
# 能力来源 = **离线包目录**(`capability_pack.py --build` 现场构建),不依赖 `~/.argent-dsh` 克隆。
|
|
13
|
+
#
|
|
14
|
+
# 演练内容(六跑):
|
|
15
|
+
# A. dry-run ⇒ 退出码 0,且夹具**零写入**(dry-run 语义)
|
|
16
|
+
# B. --apply(无许可) ⇒ 退出码 0,装配 + 技能同步真跑;许可段 skipped("未装许可"不算失败)
|
|
17
|
+
# C. 重跑 B ⇒ 退出码 0,逐段结论与 B 一致(**幂等**)
|
|
18
|
+
# D. --only license --apply(夹具许可)⇒ 退出码 0,许可真落到夹具数据目录
|
|
19
|
+
# F. 在线装配面(本地镜像克隆 file://<仓库>、零网络、无包管理器 ⇒ 安装器既有"直拷"回退)
|
|
20
|
+
# E. 多包一起装(full,invoice)⇒ **预期退出码 0**(2026-10-09 起):注册表投影在计划阶段与落盘阶段
|
|
21
|
+
# 用**同一个**判据豁免(`isRegistryProjection()`),落盘后盘上注册表 = 两份投影的并集。
|
|
22
|
+
# 收尾:哨兵比对(真实目录零变化)+ 夹具树/清单快照
|
|
23
|
+
#
|
|
24
|
+
# 用法:
|
|
25
|
+
# bash scripts/drill-clean-machine.sh [--keep] [--dir <夹具根>] [--caps <单个能力>] [--timeout <秒>]
|
|
26
|
+
# `--caps` 可给**多个**(逗号分隔):同批发布的包本就该一起装(跨包共用引擎文件必须同版);
|
|
27
|
+
# E 步仍单独用全新树演示一次"两个包一起装"的完整判据(退出码 + 装机清单 + 注册表并集)。
|
|
28
|
+
# 退出码:0 四跑与哨兵均符合预期 | 1 有不符合预期项(逐条点名)| 2 用法/环境错误
|
|
29
|
+
set -uo pipefail
|
|
30
|
+
|
|
31
|
+
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
32
|
+
KEEP=0
|
|
33
|
+
FIX=""
|
|
34
|
+
CAPS="full"
|
|
35
|
+
GUARD_CAPS="full,invoice" # E 步:多包一起装(回归判据,见文档 §E)
|
|
36
|
+
TIMEOUT=1800
|
|
37
|
+
while [ $# -gt 0 ]; do
|
|
38
|
+
case "$1" in
|
|
39
|
+
--keep) KEEP=1; shift ;;
|
|
40
|
+
--dir) FIX="$2"; shift 2 ;;
|
|
41
|
+
--caps) CAPS="$2"; shift 2 ;;
|
|
42
|
+
--timeout) TIMEOUT="$2"; shift 2 ;;
|
|
43
|
+
-h|--help) sed -n '2,26p' "$0"; exit 0 ;;
|
|
44
|
+
*) echo "✗ 未知参数:$1" >&2; exit 2 ;;
|
|
45
|
+
esac
|
|
46
|
+
done
|
|
47
|
+
case "$CAPS" in
|
|
48
|
+
*,*) : ;; # 多能力(逗号分隔)自 2026-10-09 起支持:一起装是**推荐**路径
|
|
49
|
+
'') echo "✗ --caps 不能为空" >&2; exit 2 ;;
|
|
50
|
+
esac
|
|
51
|
+
[ -n "$FIX" ] || FIX="$(mktemp -d "${TMPDIR:-/tmp}/argent-drill.XXXXXX")"
|
|
52
|
+
FIX="$(cd "$FIX" && pwd -P)"
|
|
53
|
+
mkdir -p "$FIX/bin" "$FIX/home" "$FIX/packs"
|
|
54
|
+
|
|
55
|
+
ONE="$REPO/scripts/install-one-shot.mjs"
|
|
56
|
+
[ -f "$ONE" ] || { echo "✗ 缺 $ONE" >&2; exit 2; }
|
|
57
|
+
|
|
58
|
+
FAIL=0
|
|
59
|
+
note() { printf '%s\n' "$*"; }
|
|
60
|
+
ok() { printf ' ✅ %s\n' "$*"; }
|
|
61
|
+
bad() { printf ' ✗ %s\n' "$*"; FAIL=1; }
|
|
62
|
+
warn() { printf ' ⚠️ %s\n' "$*"; }
|
|
63
|
+
hdr() { printf '\n── %s\n' "$*"; }
|
|
64
|
+
|
|
65
|
+
# ── 夹具 bin:只放 node / git / dsh(**不放** pnpm/npm/corepack)──────────────
|
|
66
|
+
for t in node git dsh; do
|
|
67
|
+
p="$(command -v "$t" 2>/dev/null || true)"
|
|
68
|
+
[ -n "$p" ] && ln -sf "$p" "$FIX/bin/$t"
|
|
69
|
+
done
|
|
70
|
+
MINPATH="$FIX/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
71
|
+
RUN=(env -i "HOME=$FIX/home" "PATH=$MINPATH" "TMPDIR=$FIX/tmp" \
|
|
72
|
+
"ARGENT_REPO_URL=file://$REPO" # 在线面用**本地镜像**克隆(不是 whyshu 私服):零网络
|
|
73
|
+
"ARGENT_SKIP_MODEL_PROBE=1") # 联网凭证探针在夹具里没有 Key ⇒ 用安装器既有逃逸阀(它会打警告)
|
|
74
|
+
mkdir -p "$FIX/tmp"
|
|
75
|
+
|
|
76
|
+
note "═══════════════════════════════════════════════════"
|
|
77
|
+
note " 干净机器演练夹具:$FIX"
|
|
78
|
+
note " HOME=$FIX/home(隔离)| PATH=$MINPATH"
|
|
79
|
+
note " node=$(node --version 2>/dev/null)|git=$(git --version 2>/dev/null | awk '{print $3}')|dsh=$(command -v dsh || echo 无)"
|
|
80
|
+
note " ⛔ 包管理器不在 PATH(演练刻意不跑 pnpm)"
|
|
81
|
+
note "═══════════════════════════════════════════════════"
|
|
82
|
+
|
|
83
|
+
# ── 哨兵(真实目录)────────────────────────────────────────────────────────
|
|
84
|
+
# 立场:演练**只读**真实目录。哨兵盯"本次演练最可能误写的面",逐条 sha 比对。
|
|
85
|
+
# 刻意**不**盯会话/日志(常驻 dsh web 在写,盘它必然恒红,属噪声不属判据)。
|
|
86
|
+
# 真实数据目录:**只调内核的唯一判据**(`core/lib/argent-datadir.mjs#resolveDataDir()`),
|
|
87
|
+
# 本脚本里**不写第二份**(`ARGENT_DATA_DIR`/`DSH_HOME` 一律清掉 ⇒ 解析出的就是"本机默认位置")。
|
|
88
|
+
# 为什么哨兵也必须这样:门禁 scripts/tests/argent-datadir-parity.test.mjs【4】对 .sh 里的
|
|
89
|
+
# `$HOME/.dsh/argent` 式硬编码判据**判红**(2026-10-01 桌面端事故形态)。
|
|
90
|
+
# 用 `env -i` 清掉整份环境(**不点名任何**实例级开关变量 —— 点名它本身就是"第二份三级链",
|
|
91
|
+
# 会被同一条门禁的 `ARGENT_DATA_DIR` 规则判红)。
|
|
92
|
+
REAL_DATA_DIR="$(env -i "HOME=$HOME" "PATH=$PATH" node -e \
|
|
93
|
+
'import(process.argv[1]).then(m=>process.stdout.write(m.resolveDataDir().dir))' \
|
|
94
|
+
"$REPO/core/lib/argent-datadir.mjs" 2>/dev/null)"
|
|
95
|
+
[ -n "$REAL_DATA_DIR" ] || { echo "✗ 解析不出真实数据目录(缺 core/lib/argent-datadir.mjs?)—— 哨兵无法自证,演练中止" >&2; exit 2; }
|
|
96
|
+
|
|
97
|
+
sentinel() {
|
|
98
|
+
local out="$1"
|
|
99
|
+
{
|
|
100
|
+
echo "# $(date -u +%FT%TZ) 真实目录哨兵(演练前后必须逐字节一致)|数据目录=${REAL_DATA_DIR}"
|
|
101
|
+
for f in "$REAL_DATA_DIR/install.json" "$REAL_DATA_DIR/license.json" \
|
|
102
|
+
"$REAL_DATA_DIR/license.secret" "$REAL_DATA_DIR/skin.json" \
|
|
103
|
+
"$REAL_DATA_DIR/gateway.json" "$REAL_DATA_DIR/roles.json" \
|
|
104
|
+
"$HOME/.dsh/settings.yaml" "$HOME/.dsh/.credentials.yaml" \
|
|
105
|
+
"$HOME/.dsh/profiles/web/package.json" "$HOME/.dsh/profiles/web/cordis.patch.yml"; do
|
|
106
|
+
if [ -f "$f" ]; then printf '%s %s\n' "$(shasum -a 256 "$f" | cut -c1-16)" "${f/#$HOME/~}"; else printf 'MISSING %s\n' "${f/#$HOME/~}"; fi
|
|
107
|
+
done
|
|
108
|
+
for d in "$HOME/.dsh/skills" "$HOME/.dsh/.agent-presets"; do
|
|
109
|
+
if [ -d "$d" ]; then
|
|
110
|
+
printf 'DIR %s files=%s hash=%s\n' "${d/#$HOME/~}" \
|
|
111
|
+
"$(find "$d" -type f | wc -l | tr -d ' ')" \
|
|
112
|
+
"$(find "$d" -type f | LC_ALL=C sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | cut -c1-16)"
|
|
113
|
+
else printf 'DIR-MISSING %s\n' "${d/#$HOME/~}"; fi
|
|
114
|
+
done
|
|
115
|
+
if [ -d "$HOME/.argent-dsh" ]; then
|
|
116
|
+
printf 'ARGENT-DSH head=%s dirty=%s files=%s\n' \
|
|
117
|
+
"$(git -C "$HOME/.argent-dsh" rev-parse --short HEAD 2>/dev/null || echo 非git)" \
|
|
118
|
+
"$(git -C "$HOME/.argent-dsh" status --porcelain 2>/dev/null | wc -l | tr -d ' ')" \
|
|
119
|
+
"$(find "$HOME/.argent-dsh" -type f -not -path '*/.git/*' | wc -l | tr -d ' ')"
|
|
120
|
+
else printf 'ARGENT-DSH MISSING\n'; fi
|
|
121
|
+
} > "$out" 2>/dev/null
|
|
122
|
+
}
|
|
123
|
+
sentinel "$FIX/sentinel-before.txt"
|
|
124
|
+
node "$REPO/scripts/check-test-env-safety.mjs" --snapshot "$FIX/sentinel-datadir-before.json" >/dev/null 2>&1 || true
|
|
125
|
+
note ""
|
|
126
|
+
note "哨兵已拍:$FIX/sentinel-before.txt(+ 真实数据目录三层快照)"
|
|
127
|
+
|
|
128
|
+
# ── 离线包(能力来源;不依赖克隆)───────────────────────────────────────────
|
|
129
|
+
# 演练主线可多包(`--caps a,b`):自 2026-10-09 起"一起装"是支持路径(E 步另有显式判据)。
|
|
130
|
+
BUILD_CAPS="$CAPS,$GUARD_CAPS" # 去重后逐个构建(重复目录会撞 --force 守卫)
|
|
131
|
+
hdr "构建离线能力包(主线 --caps ${CAPS};E 步多包 ${GUARD_CAPS})"
|
|
132
|
+
PACKARGS=""
|
|
133
|
+
GUARDPACKARGS=""
|
|
134
|
+
OLDIFS="$IFS"; IFS=','
|
|
135
|
+
BUILT=","
|
|
136
|
+
for cap in $BUILD_CAPS; do
|
|
137
|
+
IFS="$OLDIFS"
|
|
138
|
+
case "$BUILT" in *",$cap,"*) IFS=','; continue ;; esac
|
|
139
|
+
if [ -n "$cap" ]; then
|
|
140
|
+
if python3 "$REPO/scripts/capability_pack.py" --build "$cap" --out-dir "$FIX/packs/$cap" >"$FIX/packs/$cap.build.log" 2>&1; then
|
|
141
|
+
ok "$cap → $FIX/packs/${cap}($(tail -1 "$FIX/packs/$cap.build.log" | sed 's/^ *//'))"
|
|
142
|
+
BUILT="$BUILT$cap,"
|
|
143
|
+
else
|
|
144
|
+
bad "$cap 打包失败,见 $FIX/packs/$cap.build.log"; tail -5 "$FIX/packs/$cap.build.log"
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
IFS=','
|
|
148
|
+
done
|
|
149
|
+
IFS="$OLDIFS"
|
|
150
|
+
IFS=','
|
|
151
|
+
for cap in $CAPS; do
|
|
152
|
+
IFS="$OLDIFS"; [ -n "$cap" ] || { IFS=','; continue; }
|
|
153
|
+
if [ -z "$PACKARGS" ]; then PACKARGS="$FIX/packs/$cap"; else PACKARGS="$PACKARGS,$FIX/packs/$cap"; fi
|
|
154
|
+
IFS=','
|
|
155
|
+
done
|
|
156
|
+
for cap in $GUARD_CAPS; do
|
|
157
|
+
IFS="$OLDIFS"; [ -n "$cap" ] || { IFS=','; continue; }
|
|
158
|
+
if [ -z "$GUARDPACKARGS" ]; then GUARDPACKARGS="$FIX/packs/$cap"; else GUARDPACKARGS="$GUARDPACKARGS,$FIX/packs/$cap"; fi
|
|
159
|
+
IFS=','
|
|
160
|
+
done
|
|
161
|
+
IFS="$OLDIFS"
|
|
162
|
+
[ "$FAIL" = 0 ] || { echo "✗ 打包失败,演练中止"; exit 1; }
|
|
163
|
+
|
|
164
|
+
one() { # one <标签> <期望退出码> <参数…>
|
|
165
|
+
local label="$1" want="$2"; shift 2
|
|
166
|
+
local t0 t1
|
|
167
|
+
t0=$(date +%s)
|
|
168
|
+
"${RUN[@]}" node "$ONE" "$@" >"$FIX/run-$label.out" 2>&1
|
|
169
|
+
local rc=$?
|
|
170
|
+
t1=$(date +%s)
|
|
171
|
+
if [ "$rc" = "$want" ]; then ok "$label 退出码 ${rc}(期望 ${want},耗时 $((t1-t0))s)"
|
|
172
|
+
else bad "$label 退出码 ${rc}(期望 ${want})—— 见 $FIX/run-$label.out"; tail -25 "$FIX/run-$label.out"; fi
|
|
173
|
+
return 0
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
BASE=(--tree "$FIX/home/.argent-dsh" --dsh-home "$FIX/home/.dsh" --from-packs "$PACKARGS" --timeout "$TIMEOUT")
|
|
177
|
+
|
|
178
|
+
# ── A. dry-run ─────────────────────────────────────────────────────────────
|
|
179
|
+
hdr "A. dry-run(默认;必须零写入)"
|
|
180
|
+
one A 0 "${BASE[@]}" --report "$FIX/report-A.json"
|
|
181
|
+
if [ -e "$FIX/home/.dsh" ] || [ -e "$FIX/home/.argent-dsh" ]; then
|
|
182
|
+
bad "dry-run 竟然写了夹具($FIX/home 下出现 .dsh/.argent-dsh)"
|
|
183
|
+
else ok "dry-run 零写入(夹具 home 下没有 .dsh / .argent-dsh)"; fi
|
|
184
|
+
|
|
185
|
+
# ── B. --apply(无许可)────────────────────────────────────────────────────
|
|
186
|
+
hdr "B. --apply(无 --license-file ⇒ 许可段 skipped,不算失败)"
|
|
187
|
+
one B 0 "${BASE[@]}" --apply --report "$FIX/report-B.json"
|
|
188
|
+
[ -f "$FIX/home/.dsh/argent/install.json" ] && ok "装机清单在位:$FIX/home/.dsh/argent/install.json" \
|
|
189
|
+
|| bad "装机清单不在位(--from-packs 装配判据)"
|
|
190
|
+
[ -d "$FIX/home/.dsh/skills" ] && ok "技能已同步到夹具:$FIX/home/.dsh/skills($(find "$FIX/home/.dsh/skills" -maxdepth 1 -mindepth 1 | wc -l | tr -d ' ') 项)" \
|
|
191
|
+
|| bad "技能目录没生成(技能同步段判据)"
|
|
192
|
+
TREE_HASH_B="$( { [ -d "$FIX/home/.argent-dsh" ] && find "$FIX/home/.argent-dsh" -type f; } | LC_ALL=C sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | cut -c1-16)"
|
|
193
|
+
|
|
194
|
+
# ── C. 重跑(幂等)─────────────────────────────────────────────────────────
|
|
195
|
+
hdr "C. 重跑 --apply(幂等:逐段结论必须与 B 一致)"
|
|
196
|
+
one C 0 "${BASE[@]}" --apply --report "$FIX/report-C.json"
|
|
197
|
+
python3 - "$FIX/report-B.json" "$FIX/report-C.json" <<'PY' && ok "B/C 逐段状态一致(幂等)" || bad "B/C 逐段状态不一致(幂等被破)"
|
|
198
|
+
import json,sys
|
|
199
|
+
b=json.load(open(sys.argv[1])); c=json.load(open(sys.argv[2]))
|
|
200
|
+
sb={s['id']:s['status'] for s in b['steps']}; sc={s['id']:s['status'] for s in c['steps']}
|
|
201
|
+
if sb==sc: print(' B/C 状态:',sb)
|
|
202
|
+
else:
|
|
203
|
+
print(' B:',sb); print(' C:',sc); sys.exit(1)
|
|
204
|
+
PY
|
|
205
|
+
TREE_HASH_C="$( { [ -d "$FIX/home/.argent-dsh" ] && find "$FIX/home/.argent-dsh" -type f; } | LC_ALL=C sort | xargs shasum -a 256 2>/dev/null | shasum -a 256 | cut -c1-16)"
|
|
206
|
+
[ "$TREE_HASH_B" = "$TREE_HASH_C" ] && ok "重跑后目标树逐字节不变(${TREE_HASH_C})" \
|
|
207
|
+
|| warn "重跑后目标树有变化($TREE_HASH_B → ${TREE_HASH_C})—— 逐条比对是 churn 还是缺陷"
|
|
208
|
+
|
|
209
|
+
# ── D. 只跑许可段(夹具许可)───────────────────────────────────────────────
|
|
210
|
+
hdr "D. --only license --apply(夹具密钥自签的许可;验的是「许可段能不能真装」)"
|
|
211
|
+
SECRET="$FIX/fixture-license-secret"
|
|
212
|
+
printf '%s' 'drill-fixture-secret-not-a-real-key' > "$SECRET"; chmod 600 "$SECRET"
|
|
213
|
+
if "${RUN[@]}" node "$REPO/scripts/self-sign-license.mjs" --user drill-fixture --secret-file "$SECRET" \
|
|
214
|
+
--out "$FIX/fixture-license.json" --sign-v2 --device-from-local --cap full --seats 1 --days 30 \
|
|
215
|
+
>"$FIX/selfsign.out" 2>&1; then
|
|
216
|
+
ok "夹具许可已自签:$FIX/fixture-license.json(夹具密钥,⛔ 不是任何真实环境的密钥)"
|
|
217
|
+
else bad "夹具许可自签失败,见 $FIX/selfsign.out"; tail -5 "$FIX/selfsign.out"; fi
|
|
218
|
+
one D 0 --tree "$FIX/home/.argent-dsh" --dsh-home "$FIX/home/.dsh" \
|
|
219
|
+
--license-file "$FIX/fixture-license.json" --license-secret-file "$SECRET" \
|
|
220
|
+
--only license --apply --report "$FIX/report-D.json"
|
|
221
|
+
[ -f "$FIX/home/.dsh/argent/license.json" ] && ok "许可已落到夹具数据目录(真实数据目录不动)" \
|
|
222
|
+
|| bad "许可没落盘(许可段判据)"
|
|
223
|
+
|
|
224
|
+
# ── F. 在线装配面(本地镜像克隆;零网络;无包管理器 ⇒ 走安装器既有"直拷"回退)──────
|
|
225
|
+
hdr "F. 在线装配面(ARGENT_REPO_URL=file://<仓库>,零网络;无包管理器 ⇒ 直拷回退)"
|
|
226
|
+
note " ⛔ 全程不跑 pnpm:夹具 PATH 里没有 pnpm/npm/corepack ⇒ 安装器走它自己的回退分支。"
|
|
227
|
+
note " ⚠️ 该回退曾**不可达**(ensurePnpm 的 \`npm install -g pnpm\` 不在 try 里 ⇒ spawn npm ENOENT 未捕获崩),"
|
|
228
|
+
note " 2026-10-05 已修(install-argent.mjs,见文档「失败与修法」)。"
|
|
229
|
+
one F 0 --tree "$FIX/home-f/.argent-dsh" --dsh-home "$FIX/home-f/.dsh" --profile web \
|
|
230
|
+
--skip-provider --no-venv --apply --report "$FIX/report-F.json"
|
|
231
|
+
[ -f "$FIX/home-f/.dsh/profiles/web/cordis.patch.yml" ] && ok "在线面产物:profile patch 在位(profiles/web/cordis.patch.yml)" \
|
|
232
|
+
|| bad "在线面产物缺失:profiles/web/cordis.patch.yml"
|
|
233
|
+
[ -d "$FIX/home-f/.dsh/.agent-presets" ] && ok "在线面产物:角色已落位(.agent-presets/$(ls "$FIX/home-f/.dsh/.agent-presets" | head -3 | tr '\n' ' '))" \
|
|
234
|
+
|| warn "在线面没落 preset(机制可能不是目录式;见 report-F.json)"
|
|
235
|
+
[ -d "$FIX/home-f/.argent-dsh/plugins" ] && ok "在线面克隆到夹具树:$FIX/home-f/.argent-dsh($(find "$FIX/home-f/.argent-dsh" -maxdepth 1 -mindepth 1 | wc -l | tr -d ' ') 个顶层条目)" \
|
|
236
|
+
|| bad "在线面没克隆出树"
|
|
237
|
+
|
|
238
|
+
# ── E. 多包一起装(2026-10-09 起**支持**,本步验证它真能装成)────────────────
|
|
239
|
+
hdr "E. 多包一起装 --from-packs ${GUARD_CAPS}(期望退出码 0;注册表 = 两份投影的并集)"
|
|
240
|
+
# ⚠ 必须用**全新**的树/home:B 已把主线能力铺好,复用会让安装器走"升级"路径,测不到"首次多包装"。
|
|
241
|
+
one E 0 --tree "$FIX/home-e/.argent-dsh" --dsh-home "$FIX/home-e/.dsh" --profile web \
|
|
242
|
+
--from-packs "$GUARDPACKARGS" --apply --report "$FIX/report-E.json"
|
|
243
|
+
# 判据三条:① 不再报"多包校验失败";② 装机清单里两个能力都在且 integrity=verified;③ 盘上注册表是并集
|
|
244
|
+
if grep -q "多包校验失败" "$FIX/run-E.out"; then
|
|
245
|
+
bad "多包一起装仍被拦下(见 $FIX/run-E.out);拦下原因:$(grep -m1 '多包校验失败' "$FIX/run-E.out" | sed 's/^ *· *//')"
|
|
246
|
+
else
|
|
247
|
+
ok "不再报「多包校验失败」(注册表投影已被判据豁免:计划阶段与落盘阶段同一个 isRegistryProjection())"
|
|
248
|
+
fi
|
|
249
|
+
python3 - "$FIX/home-e/.dsh/argent/install.json" "$FIX/home-e/.argent-dsh/scripts/flow-requirements.json" <<'PY'
|
|
250
|
+
import json, os, sys
|
|
251
|
+
ij, reg = sys.argv[1], sys.argv[2]
|
|
252
|
+
if not os.path.exists(ij):
|
|
253
|
+
print(f' ✗ 装机清单不在位:{ij}'); sys.exit(1)
|
|
254
|
+
doc = json.load(open(ij))
|
|
255
|
+
caps = {c['id']: c.get('integrity') for c in doc.get('capabilities', [])}
|
|
256
|
+
want = {'full', 'invoice'}
|
|
257
|
+
if want <= set(caps):
|
|
258
|
+
print(f" ✅ 装机清单两个能力都在({', '.join(f'{k}={v}' for k, v in sorted(caps.items()))})")
|
|
259
|
+
else:
|
|
260
|
+
print(f" ✗ 装机清单缺能力:{sorted(want - set(caps))}(实际 {sorted(caps)})"); sys.exit(1)
|
|
261
|
+
if all(caps[k] == 'verified' for k in want):
|
|
262
|
+
print(' ✅ 两个能力的 integrity 都是 verified')
|
|
263
|
+
else:
|
|
264
|
+
print(f" ✗ integrity 不是全 verified:{caps}"); sys.exit(1)
|
|
265
|
+
if not os.path.exists(reg):
|
|
266
|
+
print(f' ✗ 盘上注册表不在位:{reg}'); sys.exit(1)
|
|
267
|
+
d = json.load(open(reg))
|
|
268
|
+
c = sorted(d.get('capabilities') or {})
|
|
269
|
+
if {'full', 'invoice'} <= set(c):
|
|
270
|
+
print(f" ✅ 盘上注册表 = 两份投影的并集(capabilities:{', '.join(c)})")
|
|
271
|
+
else:
|
|
272
|
+
print(f" ✗ 注册表缺能力条目(实际 {c})—— 并集没写成"); sys.exit(1)
|
|
273
|
+
PY
|
|
274
|
+
[ "$?" = 0 ] || bad "E 步判据未过(装机清单 / 注册表并集,见上)"
|
|
275
|
+
|
|
276
|
+
# ── 哨兵比对 ───────────────────────────────────────────────────────────────
|
|
277
|
+
hdr "哨兵:真实目录零变化"
|
|
278
|
+
sentinel "$FIX/sentinel-after.txt"
|
|
279
|
+
tail -n +2 "$FIX/sentinel-before.txt" > "$FIX/sentinel-before.body"
|
|
280
|
+
tail -n +2 "$FIX/sentinel-after.txt" > "$FIX/sentinel-after.body"
|
|
281
|
+
if diff -u "$FIX/sentinel-before.body" "$FIX/sentinel-after.body" > "$FIX/sentinel.diff" 2>&1; then
|
|
282
|
+
ok "真实目录哨兵逐字节一致($FIX/sentinel-before.txt)"
|
|
283
|
+
else
|
|
284
|
+
bad "真实目录哨兵有变化(本次演练本应零写入):"; cat "$FIX/sentinel.diff"
|
|
285
|
+
fi
|
|
286
|
+
if node "$REPO/scripts/check-test-env-safety.mjs" --verify "$FIX/sentinel-datadir-before.json" >"$FIX/sentinel-datadir.out" 2>&1; then
|
|
287
|
+
ok "真实数据目录三层判据(关键文件/新增文件/配置内容)无本次演练引起的改动"
|
|
288
|
+
else
|
|
289
|
+
warn "真实数据目录有变化(可能是常驻服务的正常写入;逐条判断):"; tail -12 "$FIX/sentinel-datadir.out"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
# ── 汇总 ───────────────────────────────────────────────────────────────────
|
|
293
|
+
hdr "汇总"
|
|
294
|
+
python3 - "$FIX" <<'PY'
|
|
295
|
+
import json,os,sys
|
|
296
|
+
fix=sys.argv[1]
|
|
297
|
+
for tag in ('A','B','C','D','E','F'):
|
|
298
|
+
p=os.path.join(fix,f'report-{tag}.json')
|
|
299
|
+
if not os.path.exists(p): print(f' {tag}: (无报告)'); continue
|
|
300
|
+
d=json.load(open(p))
|
|
301
|
+
st={s['id']:s['status'] for s in d['steps']}
|
|
302
|
+
print(f" {tag}: ok={d['ok']} mode={d['mode']} 耗时={d['elapsedSeconds']}s 逐段={st}")
|
|
303
|
+
print(f" 装配={d['segments']['install']}")
|
|
304
|
+
print(f" 技能={d['segments']['skills']}")
|
|
305
|
+
print(f" 许可={d['segments']['license']}")
|
|
306
|
+
print(f" 自检={d['selfCheck']}")
|
|
307
|
+
PY
|
|
308
|
+
note ""
|
|
309
|
+
if [ "$FAIL" = 0 ]; then
|
|
310
|
+
note "✅ 演练全部符合预期(夹具:${FIX})"
|
|
311
|
+
else
|
|
312
|
+
note "✗ 演练有不符合预期项(夹具:${FIX})—— 逐条见上"
|
|
313
|
+
fi
|
|
314
|
+
if [ "$KEEP" = 0 ] && [ "$FAIL" = 0 ]; then rm -rf "$FIX"; note "(夹具已清理;要留证据加 --keep)"; else note "(夹具保留:${FIX})"; fi
|
|
315
|
+
exit "$FAIL"
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* dsh-api-client.mjs — dsh `/api` 最小客户端(2026-09-12 协议摸底产出)
|
|
4
|
+
*
|
|
5
|
+
* 用途:给「Argent 手机 APP」做底座验证——证明**外部客户端**能直接驱动 dsh:
|
|
6
|
+
* 会话列表、消息分页、流式订阅。全程**只读**(不 prompt / 不 create / 不 fork)。
|
|
7
|
+
*
|
|
8
|
+
* 协议(从 dsh 源码与 typert 描述符实测得出,详见 docs/dsh-api-protocol.md)
|
|
9
|
+
* ────────────────────────────────────────────────────────────────────────
|
|
10
|
+
* · 一元方法:`POST /api/<namespace>/<method>`,`Content-Type: application/json`
|
|
11
|
+
* body: { type:"client-request", rpcId:"<任意串>", method:"<ns>/<method>", payload:{ args:{...} } }
|
|
12
|
+
* resp: { type:"server-response", rpcId, result:{ ok:true, value } | { ok:false, error:{code,message,details} } }
|
|
13
|
+
* · **args 的字段名按方法而定**:`session/list` → `_request`;`session/page` → `request`;
|
|
14
|
+
* `session/modelCatalog` → 无参。写错会得到 `gateway/arguments-invalid` 并**告诉你缺哪个字段**。
|
|
15
|
+
* · 流式方法(`mode:'stream'`,如 `session/follow`、`session/control`)**不能**走上面的 POST
|
|
16
|
+
* (会报 `gateway/signature-invalid`),要走 **WebSocket `/api/remote.mux`**:
|
|
17
|
+
* 发 { type:"open", streamId, endpoint, payload }
|
|
18
|
+
* 收 { type:"item", value } … 直到终止帧 / { type:"error", error }
|
|
19
|
+
* · 鉴权:浏览器用 `/?token=<43字符>` 换 `dsh-auth-*` cookie(30 天);本脚本直接带 cookie。
|
|
20
|
+
* · 围栏:Host 必须是回环,或在 dsh 的 `--trusted-host` 名单里(否则 `/api` 一律 403)。
|
|
21
|
+
*
|
|
22
|
+
* 用法
|
|
23
|
+
* ────
|
|
24
|
+
* node scripts/dsh-api-client.mjs list
|
|
25
|
+
* node scripts/dsh-api-client.mjs page <sessionId> [throughSeq] [maxMessages]
|
|
26
|
+
* node scripts/dsh-api-client.mjs follow <sessionId> [--seconds 8]
|
|
27
|
+
* node scripts/dsh-api-client.mjs catalog
|
|
28
|
+
* 环境变量:DSH_BASE(默认 http://127.0.0.1:3080)、DSH_TOKEN(显式 token;缺省走**来源链** ——
|
|
29
|
+
* 见 `scripts/lib/wake-auth.mjs`:凭据签名 cookie → 日志落点链(DSH_WEB_LOG → plist → $DSH_HOME →
|
|
30
|
+
* ~/Library/Logs → /tmp 兜底)。⛔ 不再只读写死的 `/tmp/dsh-web.log`(日志被系统清理/被运维挪走即失效)
|
|
31
|
+
*/
|
|
32
|
+
import { readFileSync } from 'node:fs'
|
|
33
|
+
import { dirname, join } from 'node:path'
|
|
34
|
+
import { acquireWakeAuth, authFailureText, authSources } from './lib/wake-auth.mjs'
|
|
35
|
+
|
|
36
|
+
const BASE = process.env.DSH_BASE || 'http://127.0.0.1:3080'
|
|
37
|
+
const WS_BASE = BASE.replace(/^http/, 'ws')
|
|
38
|
+
|
|
39
|
+
let COOKIE = null
|
|
40
|
+
/**
|
|
41
|
+
* 取鉴权 cookie:**来源链与唤醒端同一份**(`scripts/lib/wake-auth.mjs`,唯一真源)。
|
|
42
|
+
*
|
|
43
|
+
* ⑩a(2026-10-08):本函数此前硬编码 `/tmp/dsh-web.log` —— 而该文件会被系统清理(进程还握着已
|
|
44
|
+
* unlink 的 inode)、运维也可能按 wake-auth 的建议把日志挪到稳定目录 ⇒ 客户端**静默失效**
|
|
45
|
+
* (Windows 上更是恒失效:根本没有 /tmp)。改走同一条链 + 先验后用(每个来源都真打一次服务)。
|
|
46
|
+
*/
|
|
47
|
+
async function authenticate() {
|
|
48
|
+
if (COOKIE) return COOKIE
|
|
49
|
+
const sources = []
|
|
50
|
+
const tok = String(process.env.DSH_TOKEN || '').trim()
|
|
51
|
+
if (tok) {
|
|
52
|
+
sources.push({ id: 'env-token', label: 'DSH_TOKEN 环境变量', obtain: async () => ({ status: 'ok', token: tok }) })
|
|
53
|
+
}
|
|
54
|
+
sources.push(...authSources({ base: BASE }))
|
|
55
|
+
const r = await acquireWakeAuth({ sources, base: BASE })
|
|
56
|
+
if (!r.ok) {
|
|
57
|
+
throw new Error(authFailureText({ tried: r.tried, base: BASE })
|
|
58
|
+
+ (r.unreachable ? `\n 服务不可达:${r.unreachable}` : ''))
|
|
59
|
+
}
|
|
60
|
+
COOKIE = `${r.cookie.name}=${r.cookie.value}`
|
|
61
|
+
return COOKIE
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** 一元 RPC。 */
|
|
65
|
+
async function call(endpoint, args, { timeoutMs = 30000 } = {}) {
|
|
66
|
+
const cookie = await authenticate()
|
|
67
|
+
const body = JSON.stringify({
|
|
68
|
+
type: 'client-request',
|
|
69
|
+
rpcId: `c-${Math.random().toString(36).slice(2, 10)}`,
|
|
70
|
+
method: endpoint,
|
|
71
|
+
payload: { args: args ?? {} },
|
|
72
|
+
})
|
|
73
|
+
const ac = new AbortController()
|
|
74
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs)
|
|
75
|
+
try {
|
|
76
|
+
const res = await fetch(`${BASE}/api/${endpoint}`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: { 'content-type': 'application/json', cookie },
|
|
79
|
+
body,
|
|
80
|
+
signal: ac.signal,
|
|
81
|
+
})
|
|
82
|
+
const text = await res.text()
|
|
83
|
+
if (res.status !== 200) throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`)
|
|
84
|
+
const env = JSON.parse(text)
|
|
85
|
+
const r = env.result
|
|
86
|
+
if (!r?.ok) throw new Error(`${r?.error?.code || 'unknown'}: ${r?.error?.message || text.slice(0, 200)}`)
|
|
87
|
+
return r.value
|
|
88
|
+
} finally { clearTimeout(timer) }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** WebSocket 实现:Node 内置的 **不能带 Cookie 头**(/api 用 cookie 鉴权)⇒ 优先用 ws 包。 */
|
|
92
|
+
async function loadWs() {
|
|
93
|
+
// 先按常规解析(脚本自己所在的 node_modules)
|
|
94
|
+
try { const m = await import('ws'); return m.default ?? m } catch { /* 继续 */ }
|
|
95
|
+
// 再借 dsh 全局安装里的 ws(`import('<dir>')` 不支持目录,必须走 createRequire)。
|
|
96
|
+
// 2026-09-28:候选根目录**动态探测**,不再写死 homebrew —— 客户机 node 官方包布局下
|
|
97
|
+
// /opt/homebrew/... 不存在,写死会让这里的回落整条失效。
|
|
98
|
+
try {
|
|
99
|
+
const { createRequire } = await import('node:module')
|
|
100
|
+
const { scopeCandidates } = await import('./lib/dsh-scope.mjs')
|
|
101
|
+
for (const scope of scopeCandidates()) {
|
|
102
|
+
const root = dirname(dirname(scope)) // <...>/lib/node_modules
|
|
103
|
+
for (const pkg of ['ws', '@deepseek-ai/dsh/node_modules/ws']) {
|
|
104
|
+
try { return createRequire(join(root, pkg, 'package.json'))('ws') } catch { /* 下一个候选 */ }
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} catch { /* 继续 */ }
|
|
108
|
+
return null
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** 流式 RPC:WebSocket /api/remote.mux(帧:{type:"open",streamId,endpoint,payload})。 */
|
|
112
|
+
async function stream(endpoint, args, { seconds = 8, onItem } = {}) {
|
|
113
|
+
const cookie = await authenticate()
|
|
114
|
+
const WS = await loadWs()
|
|
115
|
+
if (!WS) throw new Error('流式需要 `ws` 包(Node 内置 WebSocket 无法携带 Cookie 头)')
|
|
116
|
+
const ws = new WS(`${WS_BASE}/api/remote.mux`, { headers: { cookie } })
|
|
117
|
+
const streamId = `s-${Math.random().toString(36).slice(2, 10)}`
|
|
118
|
+
let opened = false
|
|
119
|
+
return await new Promise((resolve, reject) => {
|
|
120
|
+
const done = (v) => { try { ws.close() } catch {} ; resolve(v) }
|
|
121
|
+
ws.on('open', () => {
|
|
122
|
+
ws.send(JSON.stringify({ type: 'open', streamId, endpoint, payload: { args: args ?? {} } }))
|
|
123
|
+
opened = true
|
|
124
|
+
})
|
|
125
|
+
ws.on('message', (data) => {
|
|
126
|
+
let frame
|
|
127
|
+
try { frame = JSON.parse(String(data)) } catch { return }
|
|
128
|
+
if (frame.type === 'item') { onItem?.(frame.value); return }
|
|
129
|
+
if (frame.type === 'error') { reject(new Error(`${frame.error?.code}: ${frame.error?.message}`)); return }
|
|
130
|
+
done(frame) // 终止帧
|
|
131
|
+
})
|
|
132
|
+
ws.on('unexpected-response', (_req, res) => reject(new Error(`WS 被拒:HTTP ${res.statusCode}(401=缺 cookie,403=Host 未信任)`)))
|
|
133
|
+
ws.on('error', (e) => reject(new Error(`WS 错误:${e?.message || 'unknown'}`)))
|
|
134
|
+
ws.on('close', () => resolve({ closed: true, opened }))
|
|
135
|
+
setTimeout(() => done({ timedOut: true, opened }), seconds * 1000)
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ───────────────────────────── CLI ─────────────────────────────
|
|
140
|
+
|
|
141
|
+
const [cmd, a1, a2, a3] = process.argv.slice(2)
|
|
142
|
+
|
|
143
|
+
if (cmd === 'list') {
|
|
144
|
+
const v = await call('session/list', { _request: {} })
|
|
145
|
+
const items = v.items ?? []
|
|
146
|
+
console.log(`会话 ${items.length} 个:`)
|
|
147
|
+
for (const s of items.slice(0, 25)) {
|
|
148
|
+
const t = s.projections?.values?.title || '(无标题)'
|
|
149
|
+
console.log(` ${s.sessionId} ${s.running ? '▶ 运行中' : '· 空闲'} ${t}`)
|
|
150
|
+
}
|
|
151
|
+
} else if (cmd === 'page') {
|
|
152
|
+
if (!a1) throw new Error('用法:page <sessionId> [throughSeq] [maxMessages]')
|
|
153
|
+
const list = await call('session/list', { _request: {} })
|
|
154
|
+
const meta = list.items.find((s) => s.sessionId === a1)
|
|
155
|
+
const throughSeq = a2 !== undefined ? Number(a2) : (meta?.projections?.asOfSeq ?? -1)
|
|
156
|
+
const v = await call('session/page', {
|
|
157
|
+
request: { address: { kind: 'session', sessionId: a1 }, throughSeq, maxMessages: a3 ? Number(a3) : 10 },
|
|
158
|
+
})
|
|
159
|
+
console.log(`throughSeq=${throughSeq} 记录 ${v.records?.length ?? 0} 条 hasMore=${v.hasMore}`)
|
|
160
|
+
for (const rec of v.records ?? []) {
|
|
161
|
+
const e = rec.event ?? {}
|
|
162
|
+
const d = e.data ?? {}
|
|
163
|
+
const kind = e.type === 'assistant/message' ? `assistant(${d.message?.content?.map((c) => c.type).join('+') || ''})`
|
|
164
|
+
: e.type === 'tool/call' ? `tool:${d.name}`
|
|
165
|
+
: e.type
|
|
166
|
+
console.log(` seq=${e.seq} turn=${d.turn ?? '-'} ${kind}`)
|
|
167
|
+
}
|
|
168
|
+
} else if (cmd === 'follow') {
|
|
169
|
+
if (!a1) throw new Error('用法:follow <sessionId> [--seconds N]')
|
|
170
|
+
const sec = process.argv.includes('--seconds') ? Number(process.argv[process.argv.indexOf('--seconds') + 1]) : 8
|
|
171
|
+
console.log(`订阅 session/follow ${sec}s(只读,不发 prompt)…`)
|
|
172
|
+
const r = await stream('session/follow', {
|
|
173
|
+
request: { address: { kind: 'session', sessionId: a1 }, maxMessages: 1 },
|
|
174
|
+
}, { seconds: sec, onItem: (v) => console.log(' 帧:', JSON.stringify(v).slice(0, 160)) })
|
|
175
|
+
console.log(' 结束:', JSON.stringify(r))
|
|
176
|
+
} else if (cmd === 'create') {
|
|
177
|
+
// 写路径①:新建会话(默认用 minimal preset,只有一个 shell 工具,最安全)
|
|
178
|
+
const preset = process.argv.includes('--preset') ? process.argv[process.argv.indexOf('--preset') + 1] : 'minimal'
|
|
179
|
+
const cwd = process.argv.includes('--cwd') ? process.argv[process.argv.indexOf('--cwd') + 1] : '/tmp'
|
|
180
|
+
const v = await call('session/create', { request: { cwd, agentPreset: preset } })
|
|
181
|
+
console.log('已创建会话:', JSON.stringify(v))
|
|
182
|
+
} else if (cmd === 'prompt') {
|
|
183
|
+
// 写路径②:发消息(会真跑一轮 agent)+ 可选跟随流式
|
|
184
|
+
if (!a1 || !a2) throw new Error('用法:prompt <sessionId> "<文本>" [--follow N]')
|
|
185
|
+
const v = await call('session/prompt', {
|
|
186
|
+
request: {
|
|
187
|
+
requestId: `req-${Math.random().toString(36).slice(2, 10)}`,
|
|
188
|
+
sessionId: a1,
|
|
189
|
+
mode: 'queue',
|
|
190
|
+
content: [{ type: 'text', text: a2 }],
|
|
191
|
+
clientTimeZone: 'Asia/Shanghai',
|
|
192
|
+
},
|
|
193
|
+
})
|
|
194
|
+
console.log('已接受:', JSON.stringify(v))
|
|
195
|
+
if (process.argv.includes('--follow')) {
|
|
196
|
+
const sec = Number(process.argv[process.argv.indexOf('--follow') + 1]) || 30
|
|
197
|
+
console.log(`跟随流式 ${sec}s …`)
|
|
198
|
+
await stream('session/follow', {
|
|
199
|
+
request: { address: { kind: 'session', sessionId: a1 }, maxMessages: 1 },
|
|
200
|
+
}, { seconds: sec, onItem: (x) => {
|
|
201
|
+
const e = x?.event ?? x
|
|
202
|
+
if (e?.type) console.log(` ← ${e.type}${e.data?.name ? ':' + e.data.name : ''}`)
|
|
203
|
+
} })
|
|
204
|
+
}
|
|
205
|
+
} else if (cmd === 'cancel') {
|
|
206
|
+
if (!a1) throw new Error('用法:cancel <sessionId>')
|
|
207
|
+
console.log('已取消:', JSON.stringify(await call('session/cancel', { request: { sessionId: a1 } })))
|
|
208
|
+
} else if (cmd === 'catalog') {
|
|
209
|
+
const v = await call('session/modelCatalog', {})
|
|
210
|
+
console.log('默认模型:', JSON.stringify(v.default))
|
|
211
|
+
for (const g of v.groups ?? []) console.log(` ${g.id} (${g.name}): ${g.models.map((m) => m.id).join(', ')}`)
|
|
212
|
+
} else {
|
|
213
|
+
console.log(readFileSync(new URL(import.meta.url), 'utf8').split('用法')[1]?.slice(0, 400) || '')
|
|
214
|
+
process.exit(2)
|
|
215
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# dsh-backup-scope —— 给**当前默认入口树**打一份整树备份(P5/P6,2026-09-29)
|
|
3
|
+
#
|
|
4
|
+
# 为什么需要它:升级失败要能物理回滚,前提是"旧树还在"。升级脚本(dsh-upgrade.mjs)
|
|
5
|
+
# 自己也备份,但它备份到 /tmp/... 且是**替换前**那一刻;/tmp 会被系统清理、也不便于人找。
|
|
6
|
+
# 本脚本把备份落到持久位置(`~/.dsh/argent/backup/dsh-<版本>-<日期>/scope`),
|
|
7
|
+
# 并按 `<root>/lib/node_modules/@deepseek-ai` 的布局**顺带摆好**(可直接当侧车树用 ——
|
|
8
|
+
# 2026-09-29 实测:普通目录里的整树备份 `node scope/dsh/lib/bin.js` 会 ERR_MODULE_NOT_FOUND)。
|
|
9
|
+
#
|
|
10
|
+
# 用法:
|
|
11
|
+
# bash scripts/dsh-backup-scope.sh # 备份当前默认入口(全局)树
|
|
12
|
+
# bash scripts/dsh-backup-scope.sh --scope <树> --label <名>
|
|
13
|
+
# bash scripts/dsh-backup-scope.sh --list # 看已有备份
|
|
14
|
+
# 退出码:0 成功;2 参数/环境错误。**只读源树,不重启任何服务。**
|
|
15
|
+
set -uo pipefail
|
|
16
|
+
DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
|
|
17
|
+
BACKUP_ROOT="$DSH_HOME/argent/backup"
|
|
18
|
+
SCOPE_DEFAULT="/opt/homebrew/lib/node_modules/@deepseek-ai"
|
|
19
|
+
SCOPE=""; LABEL=""; MODE=backup
|
|
20
|
+
|
|
21
|
+
while [ $# -gt 0 ]; do
|
|
22
|
+
case "$1" in
|
|
23
|
+
--list) MODE=list; shift;;
|
|
24
|
+
--scope) SCOPE="${2:-}"; shift 2;;
|
|
25
|
+
--label) LABEL="${2:-}"; shift 2;;
|
|
26
|
+
-h|--help) sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'; exit 0;;
|
|
27
|
+
*) echo "未知参数:$1(--help)" >&2; exit 2;;
|
|
28
|
+
esac
|
|
29
|
+
done
|
|
30
|
+
|
|
31
|
+
if [ "$MODE" = list ]; then
|
|
32
|
+
echo "备份根:$BACKUP_ROOT"
|
|
33
|
+
for d in "$BACKUP_ROOT"/*/; do
|
|
34
|
+
[ -d "$d" ] || continue
|
|
35
|
+
tree="$d/scope"
|
|
36
|
+
if [ -f "$tree/dsh/lib/bin.js" ]; then
|
|
37
|
+
n=$(ls -1 "$tree" 2>/dev/null | wc -l | tr -d ' ')
|
|
38
|
+
echo " ✅ $(basename "$d")($n 个包,${tree})"
|
|
39
|
+
else
|
|
40
|
+
echo " ⚠️ $(basename "$d")(缺 scope/dsh/lib/bin.js)"
|
|
41
|
+
fi
|
|
42
|
+
done
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
[ -n "$SCOPE" ] || SCOPE="$SCOPE_DEFAULT"
|
|
47
|
+
[ -d "$SCOPE" ] || { echo "✗ 源树不存在:$SCOPE" >&2; exit 2; }
|
|
48
|
+
[ -f "$SCOPE/dsh/lib/bin.js" ] || { echo "✗ 源树不像 dsh 安装(缺 dsh/lib/bin.js):$SCOPE" >&2; exit 2; }
|
|
49
|
+
|
|
50
|
+
if [ -z "$LABEL" ]; then
|
|
51
|
+
ver="$(node -e 'try{const p=require(process.argv[1]+"/dsh/package.json");process.stdout.write(p.version||"unknown")}catch{process.stdout.write("unknown")}' "$SCOPE" 2>/dev/null || echo unknown)"
|
|
52
|
+
LABEL="dsh-${ver}-$(date +%Y%m%d-%H%M%S)"
|
|
53
|
+
fi
|
|
54
|
+
DEST="$BACKUP_ROOT/$LABEL/lib/node_modules/@deepseek-ai"
|
|
55
|
+
[ -e "$DEST" ] && { echo "✗ 目标已存在:${DEST}(换 --label)" >&2; exit 2; }
|
|
56
|
+
|
|
57
|
+
echo "备份:$SCOPE"
|
|
58
|
+
echo " → ${DEST}"
|
|
59
|
+
echo " (布局 = <root>/lib/node_modules/@deepseek-ai ⇒ 可直接当侧车/救援树;另建 scope 软链兼容旧引用)"
|
|
60
|
+
mkdir -p "$DEST"
|
|
61
|
+
# 用 cp -R(保留符号链接语义:scope 里可能有指向包内的相对链接);失败即停,不留半份
|
|
62
|
+
if ! cp -R "$SCOPE/." "$DEST/"; then
|
|
63
|
+
echo "✗ 复制失败 ⇒ 删除半份备份" >&2
|
|
64
|
+
rm -rf "$BACKUP_ROOT/$LABEL"
|
|
65
|
+
exit 2
|
|
66
|
+
fi
|
|
67
|
+
n=$(ls -1 "$DEST" | wc -l | tr -d ' ')
|
|
68
|
+
[ -f "$DEST/dsh/lib/bin.js" ] || { echo "✗ 备份不完整(缺 dsh/lib/bin.js)" >&2; exit 2; }
|
|
69
|
+
# 落一个说明文件,免得日后没人知道它是什么
|
|
70
|
+
cat > "$BACKUP_ROOT/$LABEL/README.txt" <<EOF
|
|
71
|
+
dsh 整树备份
|
|
72
|
+
来源:$SCOPE
|
|
73
|
+
时间:$(date '+%F %T')
|
|
74
|
+
包数:$n
|
|
75
|
+
布局:lib/node_modules/@deepseek-ai(**可直接运行**的形态:<root>/lib/node_modules/ 下才算数)
|
|
76
|
+
scope 是指向它的软链(兼容旧文档/脚本里的 .../scope 引用)
|
|
77
|
+
用途:升级失败时回滚(dsh-restore.sh --to <本目录/lib/node_modules/@deepseek-ai>),
|
|
78
|
+
或直接当侧车/救援树
|
|
79
|
+
校验:ls "\$(dirname "$DEST")/scope" | wc -l # 应为 $n
|
|
80
|
+
EOF
|
|
81
|
+
ln -sfn "lib/node_modules/@deepseek-ai" "$BACKUP_ROOT/$LABEL/scope" # 兼容旧引用
|
|
82
|
+
echo " ✅ 完成($n 个包)"
|
|
83
|
+
echo " 验证:node scripts/dsh-upgrade-readiness.mjs --target <目标版本>"
|