@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.
Files changed (340) hide show
  1. package/MANIFEST.json +341 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +576 -0
  55. package/engine/scripts/billing-mode-daily.sh +13 -0
  56. package/engine/scripts/build-engine-package.mjs +126 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +378 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/collect-evidence.mjs +42 -0
  81. package/engine/scripts/config-explain.mjs +278 -0
  82. package/engine/scripts/configure-provider.rb +29 -0
  83. package/engine/scripts/customer-config-migrate.mjs +179 -0
  84. package/engine/scripts/customer-preflight.py +892 -0
  85. package/engine/scripts/customer-readiness.sh +135 -0
  86. package/engine/scripts/daily-report-inbox.py +242 -0
  87. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  88. package/engine/scripts/daily-report.mjs +1310 -0
  89. package/engine/scripts/daily-report.sh +47 -0
  90. package/engine/scripts/data/lip-product-dict.json +306 -0
  91. package/engine/scripts/deliver-customer.mjs +932 -0
  92. package/engine/scripts/delivery-faces.txt +31 -0
  93. package/engine/scripts/desktop-trial.sh +146 -0
  94. package/engine/scripts/dev-window.sh +261 -0
  95. package/engine/scripts/drill-clean-machine.sh +315 -0
  96. package/engine/scripts/dsh-api-client.mjs +215 -0
  97. package/engine/scripts/dsh-backup-scope.sh +83 -0
  98. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  99. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  100. package/engine/scripts/dsh-plane.mjs +153 -0
  101. package/engine/scripts/dsh-portforward.py +223 -0
  102. package/engine/scripts/dsh-preflight.mjs +179 -0
  103. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  104. package/engine/scripts/dsh-release-watch.mjs +458 -0
  105. package/engine/scripts/dsh-remote-check.sh +99 -0
  106. package/engine/scripts/dsh-restart.sh +179 -0
  107. package/engine/scripts/dsh-restore.sh +111 -0
  108. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  109. package/engine/scripts/dsh-snapshot.mjs +116 -0
  110. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  111. package/engine/scripts/dsh-upgrade.mjs +612 -0
  112. package/engine/scripts/dsh-url.sh +86 -0
  113. package/engine/scripts/dsh-web-launch.mjs +155 -0
  114. package/engine/scripts/ensure-store-windows.py +509 -0
  115. package/engine/scripts/entitlements.py +458 -0
  116. package/engine/scripts/env_probe.py +411 -0
  117. package/engine/scripts/feishu-card.mjs +252 -0
  118. package/engine/scripts/final-check.mjs +43 -0
  119. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  120. package/engine/scripts/flow-auto-retry.py +454 -0
  121. package/engine/scripts/flow-requirements.json +735 -0
  122. package/engine/scripts/flow-scaffold.py +275 -0
  123. package/engine/scripts/flow-timers-plist.sh +87 -0
  124. package/engine/scripts/flow-timers.sh +184 -0
  125. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  126. package/engine/scripts/flow-trigger.sh +571 -0
  127. package/engine/scripts/flow_billing.py +354 -0
  128. package/engine/scripts/flow_capability_stats.py +314 -0
  129. package/engine/scripts/flow_case.py +194 -0
  130. package/engine/scripts/flow_claim.py +497 -0
  131. package/engine/scripts/flow_config.py +1025 -0
  132. package/engine/scripts/flow_failure.py +588 -0
  133. package/engine/scripts/flow_learn.py +105 -0
  134. package/engine/scripts/flow_ledger.py +235 -0
  135. package/engine/scripts/flow_policy.py +238 -0
  136. package/engine/scripts/flow_preflight.py +339 -0
  137. package/engine/scripts/flow_registry.py +341 -0
  138. package/engine/scripts/flow_retry.py +304 -0
  139. package/engine/scripts/flow_return_to_ops.py +191 -0
  140. package/engine/scripts/flow_source.py +315 -0
  141. package/engine/scripts/flow_sources/__init__.py +53 -0
  142. package/engine/scripts/flow_sources/cases_json.py +106 -0
  143. package/engine/scripts/flow_sources/common.py +428 -0
  144. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  145. package/engine/scripts/flow_stale.py +185 -0
  146. package/engine/scripts/flow_status_reconcile.py +344 -0
  147. package/engine/scripts/flow_triage.py +849 -0
  148. package/engine/scripts/flow_write_verify.py +496 -0
  149. package/engine/scripts/fulfillment.js +586 -0
  150. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  151. package/engine/scripts/gen-preset-rows.mjs +259 -0
  152. package/engine/scripts/init-ml.sh +2278 -0
  153. package/engine/scripts/install-internal-role.sh +190 -0
  154. package/engine/scripts/install-license.mjs +641 -0
  155. package/engine/scripts/install-one-shot.mjs +665 -0
  156. package/engine/scripts/integration_contract.py +220 -0
  157. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  158. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  159. package/engine/scripts/invoice-mapping.example.json +40 -0
  160. package/engine/scripts/invoice-table.sh +195 -0
  161. package/engine/scripts/invoice_carrier_notice.py +279 -0
  162. package/engine/scripts/invoice_fail_closeout.py +297 -0
  163. package/engine/scripts/invoice_lip_runner.py +1508 -0
  164. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  165. package/engine/scripts/invoice_ready_audit.py +191 -0
  166. package/engine/scripts/invoice_runner.py +675 -0
  167. package/engine/scripts/invoice_stageb.py +516 -0
  168. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  169. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  170. package/engine/scripts/lease.py +377 -0
  171. package/engine/scripts/lib/agent-install.mjs +649 -0
  172. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  173. package/engine/scripts/lib/argent-datadir.sh +18 -0
  174. package/engine/scripts/lib/argent_datadir.py +285 -0
  175. package/engine/scripts/lib/argent_filelock.py +507 -0
  176. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  177. package/engine/scripts/lib/cdp_ports.py +197 -0
  178. package/engine/scripts/lib/customer-env.mjs +102 -0
  179. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  180. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  181. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  182. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  183. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  184. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  185. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  186. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  187. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  188. package/engine/scripts/lib/patch-compose.mjs +241 -0
  189. package/engine/scripts/lib/patch-target.mjs +35 -0
  190. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  191. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  192. package/engine/scripts/lib/preset-compat.mjs +127 -0
  193. package/engine/scripts/lib/preset-install.mjs +575 -0
  194. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  195. package/engine/scripts/lib/preset-sync.mjs +59 -0
  196. package/engine/scripts/lib/profile-retire.mjs +97 -0
  197. package/engine/scripts/lib/python-runtime.mjs +190 -0
  198. package/engine/scripts/lib/registry.mjs +72 -0
  199. package/engine/scripts/lib/repo-root.sh +56 -0
  200. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  201. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  202. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  203. package/engine/scripts/lib/settings-panels.mjs +816 -0
  204. package/engine/scripts/lib/stdio_utf8.py +86 -0
  205. package/engine/scripts/lib/wake-auth.mjs +392 -0
  206. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  207. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  208. package/engine/scripts/license-e2e-drill.mjs +426 -0
  209. package/engine/scripts/license-gate.mjs +245 -0
  210. package/engine/scripts/lip_backlog.py +171 -0
  211. package/engine/scripts/lip_table.py +185 -0
  212. package/engine/scripts/local-usage.mjs +214 -0
  213. package/engine/scripts/migrate-providers.mjs +127 -0
  214. package/engine/scripts/migrate-skills.sh +65 -0
  215. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  216. package/engine/scripts/ml-store-ids.json +26 -0
  217. package/engine/scripts/ml_artifact_attribution.py +333 -0
  218. package/engine/scripts/ml_capacity_probe.py +1158 -0
  219. package/engine/scripts/ml_hub_status.py +1556 -0
  220. package/engine/scripts/ml_schema_check.py +115 -0
  221. package/engine/scripts/ml_store_ids.py +139 -0
  222. package/engine/scripts/npm-publish-argent.mjs +499 -0
  223. package/engine/scripts/npm-publish-argent.sh +20 -0
  224. package/engine/scripts/orchestrator.py +13811 -0
  225. package/engine/scripts/pack-plugins.mjs +140 -0
  226. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  227. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  228. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  229. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  230. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  231. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  232. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  233. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  234. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  235. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  236. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  237. package/engine/scripts/plan-role-rename.mjs +125 -0
  238. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  239. package/engine/scripts/plugin-composition.json +15 -0
  240. package/engine/scripts/poc-mcp-host.mjs +455 -0
  241. package/engine/scripts/poll-fulfillment.sh +295 -0
  242. package/engine/scripts/poll-invoice.sh +76 -0
  243. package/engine/scripts/profile-doctor.mjs +686 -0
  244. package/engine/scripts/provider-select.sh +71 -0
  245. package/engine/scripts/provider-switch.sh +146 -0
  246. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  247. package/engine/scripts/python-runtime.sh +78 -0
  248. package/engine/scripts/query_runner.py +310 -0
  249. package/engine/scripts/release-argent.sh +574 -0
  250. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  251. package/engine/scripts/retire-inventory.mjs +40 -0
  252. package/engine/scripts/run-tests.sh +257 -0
  253. package/engine/scripts/s1-audit.mjs +85 -0
  254. package/engine/scripts/self-sign-license.mjs +179 -0
  255. package/engine/scripts/session-health-scan.mjs +63 -0
  256. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  257. package/engine/scripts/setup.sh +22 -0
  258. package/engine/scripts/skill-install.mjs +629 -0
  259. package/engine/scripts/sku_translate.py +148 -0
  260. package/engine/scripts/sync-plugins.sh +90 -0
  261. package/engine/scripts/sync-presets.sh +27 -0
  262. package/engine/scripts/sync-scripts.sh +445 -0
  263. package/engine/scripts/telemetry.py +873 -0
  264. package/engine/scripts/ui-copy-count.mjs +220 -0
  265. package/engine/scripts/usage-anchor.sh +137 -0
  266. package/engine/scripts/usage_report.py +531 -0
  267. package/engine/scripts/validate-preset-schema.mjs +208 -0
  268. package/engine/scripts/vertical-init.mjs +323 -0
  269. package/engine/scripts/vertical-lint.mjs +482 -0
  270. package/engine/scripts/wenyao-browser.sh +74 -0
  271. package/engine/scripts/wenyao-fixture-export.py +116 -0
  272. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  273. package/engine/scripts/wenyao-login.sh +35 -0
  274. package/engine/scripts/wenyao-template-map.json +292 -0
  275. package/engine/scripts/wenyao_attach.py +131 -0
  276. package/engine/scripts/wenyao_download_template.py +144 -0
  277. package/engine/scripts/wenyao_fill_template.py +568 -0
  278. package/engine/scripts/wenyao_login.py +145 -0
  279. package/engine/scripts/wenyao_portal.py +633 -0
  280. package/engine/scripts/wenyao_recon.py +230 -0
  281. package/engine/scripts/wenyao_run.py +503 -0
  282. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  283. package/engine/scripts/wenyao_skill_contract.py +156 -0
  284. package/engine/scripts/wenyao_template_fetch.py +175 -0
  285. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  286. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  287. package/engine/scripts/ziniao-keepalive.sh +217 -0
  288. package/engine/scripts/ziniao-post-upgrade-check.sh +121 -0
  289. package/engine/scripts/ziniao-windows.sh +154 -0
  290. package/engine/skills/README.md +86 -0
  291. package/engine/skills/argent-dev/SKILL.md +41 -0
  292. package/engine/skills/internal.json +37 -0
  293. package/engine/skills/lktop-pm/SKILL.md +35 -0
  294. package/engine/skills/lktopapp-dev/SKILL.md +34 -0
  295. package/engine/skills/ml-config-guide/SKILL.md +50 -0
  296. package/engine/skills/ml-fulfillment-sop/SKILL.md +178 -0
  297. package/engine/skills/ml-fulfillment-sop/references/andes-components.md +69 -0
  298. package/engine/skills/ml-fulfillment-sop/references/cdp-port-mapping.md +35 -0
  299. package/engine/skills/ml-fulfillment-sop/references/feishu-base-fulfillment-schema.md +136 -0
  300. package/engine/skills/ml-fulfillment-sop/references/multi-window-concurrency.md +69 -0
  301. package/engine/skills/ml-fulfillment-sop/references/pitfalls.md +77 -0
  302. package/engine/skills/ml-fulfillment-sop/references/selectors.md +243 -0
  303. package/engine/skills/ml-fulfillment-sop/scripts/fulfillment.js +586 -0
  304. package/engine/skills/ml-fulfillment-sop/scripts/poll-and-execute.sh +271 -0
  305. package/engine/skills/ml-fulfillment-sop/templates/report.md +55 -0
  306. package/engine/skills/ml-invoice/SKILL.md +506 -0
  307. package/engine/skills/ml-invoice/references/pitfalls.md +268 -0
  308. package/engine/skills/ml-invoice/references/portal-flow.md +360 -0
  309. package/engine/skills/ml-invoice/references/template-fill-contract.md +133 -0
  310. package/engine/skills/ml-invoice-xzh/SKILL.md +133 -0
  311. package/engine/skills/ml-invoice-xzh/references/pitfalls.md +230 -0
  312. package/engine/skills/ml-store-daily-report/SKILL.md +224 -0
  313. package/engine/skills/ml-store-daily-report/scripts/README.md +25 -0
  314. package/engine/skills/ml-store-daily-report/scripts/extract.js +691 -0
  315. package/engine/skills/ml-store-daily-report/scripts/generate.js +299 -0
  316. package/engine/skills/pet-dev/SKILL.md +42 -0
  317. package/engine/skills/whyshu-pm/SKILL.md +35 -0
  318. package/engine/skills/whyshu-web-dev/SKILL.md +50 -0
  319. package/engine/version.mjs +1 -0
  320. package/engine/verticals/README.md +103 -0
  321. package/engine/verticals/_engine-words.baseline.txt +237 -0
  322. package/engine/verticals/_engine-words.json +19 -0
  323. package/engine/verticals/mercadolibre/README.md +43 -0
  324. package/engine/verticals/mercadolibre/config.json +709 -0
  325. package/engine/verticals/mercadolibre/flows.json +738 -0
  326. package/engine/verticals/mercadolibre/manifest.json +538 -0
  327. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  328. package/engine/verticals/mercadolibre/preflight.py +143 -0
  329. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  330. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  331. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  332. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  333. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  334. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  335. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  336. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  337. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  338. package/package.json +22 -4
  339. package/requirements.txt +5 -0
  340. 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 <目标版本>"