@whyshu/argent-engine 0.0.0-stage → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (305) hide show
  1. package/MANIFEST.json +307 -0
  2. package/engine/VERSION +1 -0
  3. package/engine/agents/catalog.json +454 -0
  4. package/engine/configs/_aliases.proposed.json +651 -0
  5. package/engine/configs/_migration-plan.proposed.json +899 -0
  6. package/engine/configs/account.json +272 -0
  7. package/engine/configs/advanced.json +205 -0
  8. package/engine/configs/host.json +149 -0
  9. package/engine/configs/hr.json +27 -0
  10. package/engine/configs/notify.json +264 -0
  11. package/engine/core/cli/argent.mjs +277 -0
  12. package/engine/core/contracts/argent.contracts.json +1065 -0
  13. package/engine/core/contracts/verbs.schema.json +2539 -0
  14. package/engine/core/lib/argent-datadir.mjs +223 -0
  15. package/engine/core/lib/audit.mjs +187 -0
  16. package/engine/core/lib/claim.mjs +258 -0
  17. package/engine/core/lib/config-catalog.mjs +1172 -0
  18. package/engine/core/lib/doctor-customer.mjs +577 -0
  19. package/engine/core/lib/flow-list.mjs +138 -0
  20. package/engine/core/lib/flow-registry.mjs +48 -0
  21. package/engine/core/lib/init.mjs +247 -0
  22. package/engine/core/lib/ledger.mjs +124 -0
  23. package/engine/core/lib/license-activate.mjs +813 -0
  24. package/engine/core/lib/license-device.mjs +314 -0
  25. package/engine/core/lib/license-file.mjs +349 -0
  26. package/engine/core/lib/license-guard.mjs +123 -0
  27. package/engine/core/lib/license-ops.mjs +1645 -0
  28. package/engine/core/lib/license-verdict.mjs +133 -0
  29. package/engine/core/lib/license.mjs +577 -0
  30. package/engine/core/lib/notify/dedup.mjs +101 -0
  31. package/engine/core/lib/notify/feishu.mjs +38 -0
  32. package/engine/core/lib/notify/index.mjs +95 -0
  33. package/engine/core/lib/notify/verb.mjs +327 -0
  34. package/engine/core/lib/notify/webhook.mjs +135 -0
  35. package/engine/core/lib/notify/wecom.mjs +36 -0
  36. package/engine/core/lib/python.mjs +145 -0
  37. package/engine/core/lib/sop.mjs +116 -0
  38. package/engine/core/lib/versions.mjs +205 -0
  39. package/engine/core/mcp/client.mjs +67 -0
  40. package/engine/core/mcp/http.mjs +138 -0
  41. package/engine/core/mcp/server.mjs +702 -0
  42. package/engine/scripts/_engine_rules_probe.py +15 -0
  43. package/engine/scripts/analyze-full-day.py +568 -0
  44. package/engine/scripts/archive/feishu-bridge.js +246 -0
  45. package/engine/scripts/argent-agents.mjs +527 -0
  46. package/engine/scripts/argent-capabilities.mjs +776 -0
  47. package/engine/scripts/argent-catalog.mjs +186 -0
  48. package/engine/scripts/argent-datadir.py +62 -0
  49. package/engine/scripts/argent-doctor.mjs +624 -0
  50. package/engine/scripts/argent-env-write.mjs +302 -0
  51. package/engine/scripts/argent-schedule.mjs +620 -0
  52. package/engine/scripts/argent-setup-check.mjs +369 -0
  53. package/engine/scripts/argent-upgrade.mjs +1046 -0
  54. package/engine/scripts/base-bootstrap.sh +575 -0
  55. package/engine/scripts/billing-mode-daily.sh +12 -0
  56. package/engine/scripts/build-engine-package.mjs +123 -0
  57. package/engine/scripts/capability-packs.lock.json +484 -0
  58. package/engine/scripts/capability_pack.py +1242 -0
  59. package/engine/scripts/check-1.0-criteria.mjs +355 -0
  60. package/engine/scripts/check-agent-catalog.mjs +141 -0
  61. package/engine/scripts/check-billing-mode.sh +377 -0
  62. package/engine/scripts/check-child-sessions.mjs +83 -0
  63. package/engine/scripts/check-config-catalog.mjs +677 -0
  64. package/engine/scripts/check-docs-drift.mjs +522 -0
  65. package/engine/scripts/check-dsh-web-expose.sh +98 -0
  66. package/engine/scripts/check-judgment-drift.mjs +403 -0
  67. package/engine/scripts/check-license.mjs +199 -0
  68. package/engine/scripts/check-model-auth.py +443 -0
  69. package/engine/scripts/check-pack-closure.mjs +661 -0
  70. package/engine/scripts/check-portable-symbols.mjs +116 -0
  71. package/engine/scripts/check-preset-landing.mjs +124 -0
  72. package/engine/scripts/check-production-home.mjs +602 -0
  73. package/engine/scripts/check-role-mapping.mjs +461 -0
  74. package/engine/scripts/check-sample-redaction.mjs +320 -0
  75. package/engine/scripts/check-skill-classification.mjs +188 -0
  76. package/engine/scripts/check-sop-consistency.mjs +431 -0
  77. package/engine/scripts/check-test-env-safety.mjs +627 -0
  78. package/engine/scripts/check-version-consistency.mjs +452 -0
  79. package/engine/scripts/ci-run.sh +558 -0
  80. package/engine/scripts/config-explain.mjs +278 -0
  81. package/engine/scripts/configure-provider.rb +29 -0
  82. package/engine/scripts/customer-config-migrate.mjs +179 -0
  83. package/engine/scripts/customer-preflight.py +892 -0
  84. package/engine/scripts/customer-readiness.sh +134 -0
  85. package/engine/scripts/daily-report-inbox.py +242 -0
  86. package/engine/scripts/daily-report-reconcile.mjs +312 -0
  87. package/engine/scripts/daily-report.mjs +1310 -0
  88. package/engine/scripts/daily-report.sh +46 -0
  89. package/engine/scripts/data/lip-product-dict.json +306 -0
  90. package/engine/scripts/deliver-customer.mjs +932 -0
  91. package/engine/scripts/delivery-faces.txt +31 -0
  92. package/engine/scripts/desktop-trial.sh +146 -0
  93. package/engine/scripts/dev-window.sh +260 -0
  94. package/engine/scripts/drill-clean-machine.sh +315 -0
  95. package/engine/scripts/dsh-api-client.mjs +215 -0
  96. package/engine/scripts/dsh-backup-scope.sh +83 -0
  97. package/engine/scripts/dsh-offline-pack.mjs +77 -0
  98. package/engine/scripts/dsh-overlay-pin.sh +87 -0
  99. package/engine/scripts/dsh-plane.mjs +153 -0
  100. package/engine/scripts/dsh-portforward.py +223 -0
  101. package/engine/scripts/dsh-preflight.mjs +179 -0
  102. package/engine/scripts/dsh-release-watch-daily.sh +19 -0
  103. package/engine/scripts/dsh-release-watch.mjs +458 -0
  104. package/engine/scripts/dsh-remote-check.sh +99 -0
  105. package/engine/scripts/dsh-restart.sh +179 -0
  106. package/engine/scripts/dsh-restore.sh +111 -0
  107. package/engine/scripts/dsh-scope-rewire.mjs +803 -0
  108. package/engine/scripts/dsh-snapshot.mjs +116 -0
  109. package/engine/scripts/dsh-upgrade-readiness.mjs +165 -0
  110. package/engine/scripts/dsh-upgrade.mjs +612 -0
  111. package/engine/scripts/dsh-url.sh +86 -0
  112. package/engine/scripts/dsh-web-launch.mjs +155 -0
  113. package/engine/scripts/ensure-store-windows.py +509 -0
  114. package/engine/scripts/entitlements.py +458 -0
  115. package/engine/scripts/env_probe.py +411 -0
  116. package/engine/scripts/feishu-card.mjs +252 -0
  117. package/engine/scripts/fix-skill-frontmatter.py +209 -0
  118. package/engine/scripts/flow-auto-retry.py +454 -0
  119. package/engine/scripts/flow-requirements.json +720 -0
  120. package/engine/scripts/flow-scaffold.py +275 -0
  121. package/engine/scripts/flow-timers-plist.sh +86 -0
  122. package/engine/scripts/flow-timers.sh +182 -0
  123. package/engine/scripts/flow-trigger-wake.mjs +284 -0
  124. package/engine/scripts/flow-trigger.sh +571 -0
  125. package/engine/scripts/flow_billing.py +354 -0
  126. package/engine/scripts/flow_capability_stats.py +314 -0
  127. package/engine/scripts/flow_case.py +194 -0
  128. package/engine/scripts/flow_claim.py +497 -0
  129. package/engine/scripts/flow_config.py +1024 -0
  130. package/engine/scripts/flow_failure.py +588 -0
  131. package/engine/scripts/flow_learn.py +105 -0
  132. package/engine/scripts/flow_ledger.py +235 -0
  133. package/engine/scripts/flow_policy.py +238 -0
  134. package/engine/scripts/flow_preflight.py +339 -0
  135. package/engine/scripts/flow_registry.py +341 -0
  136. package/engine/scripts/flow_retry.py +304 -0
  137. package/engine/scripts/flow_return_to_ops.py +191 -0
  138. package/engine/scripts/flow_source.py +315 -0
  139. package/engine/scripts/flow_sources/__init__.py +53 -0
  140. package/engine/scripts/flow_sources/cases_json.py +106 -0
  141. package/engine/scripts/flow_sources/common.py +428 -0
  142. package/engine/scripts/flow_sources/feishu_base.py +273 -0
  143. package/engine/scripts/flow_stale.py +185 -0
  144. package/engine/scripts/flow_status_reconcile.py +344 -0
  145. package/engine/scripts/flow_triage.py +849 -0
  146. package/engine/scripts/flow_write_verify.py +496 -0
  147. package/engine/scripts/fulfillment.js +586 -0
  148. package/engine/scripts/gen-agent-bundles.mjs +276 -0
  149. package/engine/scripts/gen-preset-rows.mjs +259 -0
  150. package/engine/scripts/init-ml.sh +2275 -0
  151. package/engine/scripts/install-internal-role.sh +190 -0
  152. package/engine/scripts/install-license.mjs +641 -0
  153. package/engine/scripts/install-one-shot.mjs +665 -0
  154. package/engine/scripts/integration_contract.py +220 -0
  155. package/engine/scripts/integrations/wenyao/adapter.py +98 -0
  156. package/engine/scripts/integrations/xzh/adapter.py +75 -0
  157. package/engine/scripts/invoice-mapping.example.json +40 -0
  158. package/engine/scripts/invoice-table.sh +194 -0
  159. package/engine/scripts/invoice_carrier_notice.py +279 -0
  160. package/engine/scripts/invoice_fail_closeout.py +297 -0
  161. package/engine/scripts/invoice_lip_runner.py +1508 -0
  162. package/engine/scripts/invoice_lip_runner.py.bak-20260922-1710-freready +1090 -0
  163. package/engine/scripts/invoice_ready_audit.py +191 -0
  164. package/engine/scripts/invoice_runner.py +675 -0
  165. package/engine/scripts/invoice_stageb.py +516 -0
  166. package/engine/scripts/launchd/com.whyshu.argent-bridge.plist.example +42 -0
  167. package/engine/scripts/launchd/com.whyshu.fulfillment-poll.plist.example +29 -0
  168. package/engine/scripts/lease.py +377 -0
  169. package/engine/scripts/lib/agent-install.mjs +649 -0
  170. package/engine/scripts/lib/argent-datadir.mjs +16 -0
  171. package/engine/scripts/lib/argent_datadir.py +262 -0
  172. package/engine/scripts/lib/argent_filelock.py +507 -0
  173. package/engine/scripts/lib/bootcheck-wiring.mjs +510 -0
  174. package/engine/scripts/lib/cdp_ports.py +197 -0
  175. package/engine/scripts/lib/customer-env.mjs +102 -0
  176. package/engine/scripts/lib/daily-report-core.mjs +517 -0
  177. package/engine/scripts/lib/dep-declaration.mjs +81 -0
  178. package/engine/scripts/lib/deps-migrate.mjs +310 -0
  179. package/engine/scripts/lib/dsh-host-resolve.mjs +109 -0
  180. package/engine/scripts/lib/dsh-scope.mjs +500 -0
  181. package/engine/scripts/lib/dsh-snapshot-path.mjs +35 -0
  182. package/engine/scripts/lib/dsh-versions.mjs +96 -0
  183. package/engine/scripts/lib/dsh-web-probe.mjs +214 -0
  184. package/engine/scripts/lib/is-direct-run.mjs +61 -0
  185. package/engine/scripts/lib/patch-compose.mjs +241 -0
  186. package/engine/scripts/lib/patch-target.mjs +35 -0
  187. package/engine/scripts/lib/plugin-lifecycle.mjs +128 -0
  188. package/engine/scripts/lib/plugin-packages.mjs +332 -0
  189. package/engine/scripts/lib/preset-compat.mjs +127 -0
  190. package/engine/scripts/lib/preset-install.mjs +575 -0
  191. package/engine/scripts/lib/preset-mechanism.mjs +56 -0
  192. package/engine/scripts/lib/preset-sync.mjs +59 -0
  193. package/engine/scripts/lib/profile-retire.mjs +97 -0
  194. package/engine/scripts/lib/python-runtime.mjs +190 -0
  195. package/engine/scripts/lib/registry.mjs +72 -0
  196. package/engine/scripts/lib/repo-root.sh +56 -0
  197. package/engine/scripts/lib/schedule-mechanism.mjs +659 -0
  198. package/engine/scripts/lib/schedule-probe.mjs +835 -0
  199. package/engine/scripts/lib/scope-pointer.mjs +164 -0
  200. package/engine/scripts/lib/settings-panels.mjs +816 -0
  201. package/engine/scripts/lib/stdio_utf8.py +86 -0
  202. package/engine/scripts/lib/wake-auth.mjs +392 -0
  203. package/engine/scripts/lib/webdriver_ipc.py +264 -0
  204. package/engine/scripts/lib/zstd-frames.mjs +104 -0
  205. package/engine/scripts/license-e2e-drill.mjs +426 -0
  206. package/engine/scripts/license-gate.mjs +245 -0
  207. package/engine/scripts/lip_backlog.py +171 -0
  208. package/engine/scripts/lip_table.py +185 -0
  209. package/engine/scripts/local-usage.mjs +214 -0
  210. package/engine/scripts/migrate-providers.mjs +127 -0
  211. package/engine/scripts/migrate-skills.sh +65 -0
  212. package/engine/scripts/ml-oauth-setup.mjs +550 -0
  213. package/engine/scripts/ml-store-ids.json +26 -0
  214. package/engine/scripts/ml_artifact_attribution.py +333 -0
  215. package/engine/scripts/ml_capacity_probe.py +1158 -0
  216. package/engine/scripts/ml_hub_status.py +1556 -0
  217. package/engine/scripts/ml_schema_check.py +115 -0
  218. package/engine/scripts/ml_store_ids.py +139 -0
  219. package/engine/scripts/npm-publish-argent.mjs +499 -0
  220. package/engine/scripts/npm-publish-argent.sh +20 -0
  221. package/engine/scripts/orchestrator.py +13811 -0
  222. package/engine/scripts/pack-plugins.mjs +140 -0
  223. package/engine/scripts/patches/dsh-llm-deepseek--transport-diagnostics.patch +297 -0
  224. package/engine/scripts/patches/dsh-session--append-message-shape.patch +15 -0
  225. package/engine/scripts/patches/dsh-session--tool-result-block-017.patch +28 -0
  226. package/engine/scripts/patches/dsh-session-format-v0-to-v1--permission-preset-origin.patch +11 -0
  227. package/engine/scripts/patches/dsh-session-format-v3-to-v4--turn-outside-replace.patch +20 -0
  228. package/engine/scripts/patches/dsh-session-persistence-jsonl--worker-bundled-migrators.patch +39 -0
  229. package/engine/scripts/patches/dsh-tools--render-return-shape.patch +50 -0
  230. package/engine/scripts/patches/verify-bundled-migrators.mjs +79 -0
  231. package/engine/scripts/patches/verify-hardening.mjs +167 -0
  232. package/engine/scripts/patches/verify-session-migration.mjs +149 -0
  233. package/engine/scripts/patches/verify-transport-diagnostics.mjs +227 -0
  234. package/engine/scripts/plan-role-rename.mjs +125 -0
  235. package/engine/scripts/plugin-bootcheck.mjs +451 -0
  236. package/engine/scripts/plugin-composition.json +15 -0
  237. package/engine/scripts/poc-mcp-host.mjs +455 -0
  238. package/engine/scripts/poll-fulfillment.sh +293 -0
  239. package/engine/scripts/poll-invoice.sh +74 -0
  240. package/engine/scripts/profile-doctor.mjs +686 -0
  241. package/engine/scripts/provider-select.sh +70 -0
  242. package/engine/scripts/provider-switch.sh +146 -0
  243. package/engine/scripts/provider-sync-target.check.mjs +64 -0
  244. package/engine/scripts/python-runtime.sh +78 -0
  245. package/engine/scripts/query_runner.py +310 -0
  246. package/engine/scripts/release-argent.sh +574 -0
  247. package/engine/scripts/repair-unresolved-tools.mjs +178 -0
  248. package/engine/scripts/run-tests.sh +256 -0
  249. package/engine/scripts/self-sign-license.mjs +179 -0
  250. package/engine/scripts/session-health-scan.mjs +63 -0
  251. package/engine/scripts/session-repair-tool-result-content.mjs +181 -0
  252. package/engine/scripts/setup.sh +22 -0
  253. package/engine/scripts/skill-install.mjs +629 -0
  254. package/engine/scripts/sku_translate.py +148 -0
  255. package/engine/scripts/sync-plugins.sh +90 -0
  256. package/engine/scripts/sync-presets.sh +27 -0
  257. package/engine/scripts/sync-scripts.sh +445 -0
  258. package/engine/scripts/telemetry.py +873 -0
  259. package/engine/scripts/ui-copy-count.mjs +220 -0
  260. package/engine/scripts/usage-anchor.sh +136 -0
  261. package/engine/scripts/usage_report.py +531 -0
  262. package/engine/scripts/validate-preset-schema.mjs +208 -0
  263. package/engine/scripts/vertical-init.mjs +323 -0
  264. package/engine/scripts/vertical-lint.mjs +482 -0
  265. package/engine/scripts/wenyao-browser.sh +73 -0
  266. package/engine/scripts/wenyao-fixture-export.py +116 -0
  267. package/engine/scripts/wenyao-invoice-writeback.mjs +540 -0
  268. package/engine/scripts/wenyao-login.sh +34 -0
  269. package/engine/scripts/wenyao-template-map.json +292 -0
  270. package/engine/scripts/wenyao_attach.py +131 -0
  271. package/engine/scripts/wenyao_download_template.py +144 -0
  272. package/engine/scripts/wenyao_fill_template.py +568 -0
  273. package/engine/scripts/wenyao_login.py +145 -0
  274. package/engine/scripts/wenyao_portal.py +633 -0
  275. package/engine/scripts/wenyao_recon.py +230 -0
  276. package/engine/scripts/wenyao_run.py +503 -0
  277. package/engine/scripts/wenyao_shadow_diff.py +160 -0
  278. package/engine/scripts/wenyao_skill_contract.py +156 -0
  279. package/engine/scripts/wenyao_template_fetch.py +175 -0
  280. package/engine/scripts/win/dsh-web-autostart.ps1 +372 -0
  281. package/engine/scripts/ziniao-auth-probe.sh +268 -0
  282. package/engine/scripts/ziniao-keepalive.sh +216 -0
  283. package/engine/scripts/ziniao-post-upgrade-check.sh +120 -0
  284. package/engine/scripts/ziniao-windows.sh +153 -0
  285. package/engine/version.mjs +1 -0
  286. package/engine/verticals/README.md +103 -0
  287. package/engine/verticals/_engine-words.baseline.txt +237 -0
  288. package/engine/verticals/_engine-words.json +19 -0
  289. package/engine/verticals/mercadolibre/README.md +43 -0
  290. package/engine/verticals/mercadolibre/config.json +709 -0
  291. package/engine/verticals/mercadolibre/flows.json +706 -0
  292. package/engine/verticals/mercadolibre/manifest.json +538 -0
  293. package/engine/verticals/mercadolibre/migrate-machine-state.mjs +138 -0
  294. package/engine/verticals/mercadolibre/preflight.py +143 -0
  295. package/engine/verticals/mercadolibre/prompts/daily-report.failure.md +69 -0
  296. package/engine/verticals/mercadolibre/prompts/daily-report.md +47 -0
  297. package/engine/verticals/mercadolibre/prompts/daily-report.scheduled.md +79 -0
  298. package/engine/verticals/mercadolibre/prompts/full.failure.md +125 -0
  299. package/engine/verticals/mercadolibre/prompts/full.md +91 -0
  300. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.failure.md +88 -0
  301. package/engine/verticals/mercadolibre/prompts/wenyao-invoice.md +64 -0
  302. package/engine/verticals/mercadolibre/prompts/xzh-invoice.failure.md +91 -0
  303. package/engine/verticals/mercadolibre/prompts/xzh-invoice.md +46 -0
  304. package/package.json +21 -4
  305. package/README.md +0 -3
@@ -0,0 +1,455 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * poc-mcp-host.mjs —— **第二宿主 PoC:极简 MCP stdio 宿主**(2026-10-05)
4
+ *
5
+ * ══════════════════════════════════════════════════════════════════════════
6
+ * 一、它要回答的问题(`docs/Argent-1.0-退出判据与版本规则.md` §2.4 **H1**)
7
+ * ══════════════════════════════════════════════════════════════════════════
8
+ * H1 = 「第二个宿主的 L2 六项接口跑通」= 1.0 唯一真"平台"判据。此前**没有任何非 `dsh web`
9
+ * 宿主**六项全绿 ⇒ 本文这个宿主(**不是 dsh**,只是一个 stdio JSON-RPC 客户端)就是被实跑的
10
+ * "第二个宿主":它把 Argent 内核(`core/mcp/server.mjs`)挂上去,逐条量出六项里**哪些真的可达**。
11
+ *
12
+ * 分层口径(**只引用不复述**):`docs/Argent-宿主适配契约.md` = 分层与宿主适配的唯一权威,
13
+ * 六项接口定义见其 §2、能力×宿主矩阵见其 §3。本脚本**不是**那份矩阵的第二份真源 —— 它只打印
14
+ * **实测事实**(initialize / tools/list / tools/call / 版本查询),六项可达性的权威口径以契约 §3 为准。
15
+ *
16
+ * ══════════════════════════════════════════════════════════════════════════
17
+ * 二、纪律(PoC 的硬约束)
18
+ * ══════════════════════════════════════════════════════════════════════════
19
+ * · **只读**:`READ_ONLY_TOOLS` 白名单**硬编码**在代码里(不是配置),请求写类 tool(`argent_claim` /
20
+ * `argent_writeback` / `argent_license` 之外的激活…)⇒ **拒绝调用**并 exit 2。
21
+ * 影子类(`*_shadow`)虽然"不写真台账",但会 `cpSync` 整个数据目录 / 造临时文件 ⇒ **也不在白名单**。
22
+ * · **零副作用**:本脚本自己不建临时文件、不写日志、不碰数据目录(只 spawn 子进程 + 读 stdout)。
23
+ * 证明方式是**外部**的:跑前跑后各拍一次真实数据目录快照再比对 ——
24
+ * `node scripts/check-test-env-safety.mjs --snapshot <f>` → 跑本脚本 → `--verify <f>`(三层判据)。
25
+ * · **子进程环境**:默认继承调用方 `process.env`(宿主就该继承),但**强制** `ARGENT_TELEMETRY_OFF=1`
26
+ * —— PoC 绝不能因为被跑了一次就把事件推到生产告警。要真发遥测得显式 `--allow-telemetry`。
27
+ * · **不依赖 dsh**:本脚本只用 `node:` 内置模块;`env -i` + 临时 HOME 下也能起(见 `--help` 的用法示例)。
28
+ *
29
+ * ══════════════════════════════════════════════════════════════════════════
30
+ * 三、用法与退出码
31
+ * ```
32
+ * node scripts/poc-mcp-host.mjs # 人读摘要(默认 3 个只读工具)
33
+ * node scripts/poc-mcp-host.mjs --json # 机器可读证据(逐条判据 + 耗时)
34
+ * node scripts/poc-mcp-host.mjs --tools argent_version,argent_license
35
+ * node scripts/poc-mcp-host.mjs --expect-tools 14 # tools/list 与冻结口径比对(默认 14)
36
+ * node scripts/poc-mcp-host.mjs --json --full # 证据不瘦身(默认截断长值/长数组,便于安全外贴)
37
+ * env -i HOME=$(mktemp -d) PATH=/usr/bin:/bin:<node 目录> node scripts/poc-mcp-host.mjs --json # 隔离启动
38
+ * ```
39
+ * 退出码:`0` 判据全过 / `1` 有判据不过 / `2` 用法错误(含**请求了写类 tool**)。
40
+ */
41
+ import { spawn } from 'node:child_process'
42
+ import { readFileSync } from 'node:fs'
43
+ import { homedir } from 'node:os'
44
+ import { dirname, join, resolve } from 'node:path'
45
+ import { fileURLToPath } from 'node:url'
46
+ // 数据目录**唯一判据**(内核件;宿主也照它取,⛔ 不自己拼链)—— 只用于输出脱敏,不做任何业务判断
47
+ import { resolveDataDir } from '../core/lib/argent-datadir.mjs'
48
+
49
+ const HERE = dirname(fileURLToPath(import.meta.url))
50
+ const REPO = resolve(HERE, '..')
51
+ const DEFAULT_SERVER = join(REPO, 'core', 'mcp', 'server.mjs')
52
+ const CLI = join(REPO, 'core', 'cli', 'argent.mjs')
53
+ const CLIENT_INFO = { name: 'poc-mcp-host', version: '0.1.0' }
54
+ const PROTOCOL_VERSION = '2024-11-05'
55
+
56
+ /**
57
+ * **只读 tool 白名单**(硬编码 = 代码级门禁,不是配置)。
58
+ * 判据:该 tool 的 handler 只 `readFileSync` / 跑只读脚本(`--scan` / `--json` / `status`),
59
+ * 不写数据目录、不写生产表、不认领、不产生影子目录。逐个复核于 2026-10-05:
60
+ * · `argent_version` —— 读 `VERSION` + `dsh --version`(子进程,只读)
61
+ * · `argent_license` —— `core/lib/license.mjs#status` ⇒ `scripts/check-license.mjs`(只读,无 write 调用点)
62
+ * · `argent_ledger` —— 读 `flow-ledger.jsonl`
63
+ * · `argent_report` / `argent_failures` —— `scripts/flow_failure.py`(分类 / `--scan`,只读)
64
+ * · `argent_sop` —— `core/lib/sop.mjs`(读 skill 目录 + 提示词)
65
+ * · `argent_doctor` / `argent_preflight` —— `scripts/flow_preflight.py`(门禁体检,不写表、不认领)
66
+ * · `argent_writeback_audit` —— 读 `invoice-writeback/audit.jsonl`
67
+ * ⛔ **不在**白名单:`argent_claim` / `argent_writeback`(写类)、`argent_claim_shadow` /
68
+ * `argent_writeback_shadow`(会 cpSync 数据目录 / 造临时文件 ⇒ 不是"零副作用")。
69
+ */
70
+ const READ_ONLY_TOOLS = new Set([
71
+ 'argent_version', 'argent_license', 'argent_ledger', 'argent_report', 'argent_failures',
72
+ 'argent_sop', 'argent_doctor', 'argent_preflight', 'argent_writeback_audit',
73
+ ])
74
+ const DEFAULT_TOOLS = ['argent_version', 'argent_license', 'argent_failures']
75
+ /** 各只读 tool 的**最小**入参(不含任何业务词:本文件属 `scripts/`,engine-words 门禁盯着)。 */
76
+ const DEFAULT_ARGS = {
77
+ argent_version: {},
78
+ argent_license: {},
79
+ argent_failures: {},
80
+ argent_report: { error: 'timeout' },
81
+ argent_ledger: { tail: 3 },
82
+ argent_writeback_audit: { tail: 2 },
83
+ }
84
+ /** 冻结口径(门禁 `scripts/check-judgment-drift.mjs` 自报 MCP tools 数);`--expect-tools` 可覆盖。 */
85
+ const FROZEN_TOOLS_COUNT = 14
86
+
87
+ function usage() {
88
+ return readFileSync(fileURLToPath(import.meta.url), 'utf8').split('*/')[0].replace(/^\/\*\*?/, '').trim()
89
+ }
90
+
91
+ function parseArgs(argv) {
92
+ const o = {
93
+ server: DEFAULT_SERVER, tools: [...DEFAULT_TOOLS], timeoutMs: 30000,
94
+ expectTools: FROZEN_TOOLS_COUNT, json: false, cli: true, telemetryOff: true, full: false,
95
+ }
96
+ for (let i = 0; i < argv.length; i++) {
97
+ const a = argv[i]
98
+ const val = () => {
99
+ const v = argv[i + 1]
100
+ if (v === undefined || v.startsWith('--')) throw new Error(`${a} 缺值`)
101
+ i++
102
+ return v
103
+ }
104
+ if (a === '--json') { o.json = true; continue }
105
+ if (a === '--full') { o.full = true; continue }
106
+ if (a === '--no-cli') { o.cli = false; continue }
107
+ if (a === '--allow-telemetry') { o.telemetryOff = false; continue }
108
+ if (a === '--server') { o.server = resolve(val()); continue }
109
+ if (a === '--timeout') { o.timeoutMs = Number(val()); continue }
110
+ if (a === '--expect-tools') { o.expectTools = Number(val()); continue }
111
+ if (a === '--tools') { o.tools = val().split(',').map((s) => s.trim()).filter(Boolean); continue }
112
+ if (a === '-h' || a === '--help') { console.log(usage()); process.exit(0) }
113
+ throw new Error(`未知参数:${a}`)
114
+ }
115
+ if (!Number.isFinite(o.timeoutMs) || o.timeoutMs <= 0) throw new Error('--timeout 必须是正数')
116
+ if (!Number.isFinite(o.expectTools) || o.expectTools < 0) throw new Error('--expect-tools 必须是非负数')
117
+ return o
118
+ }
119
+
120
+ // ── 脱敏:把真实家目录/数据目录/仓库路径换成占位符,并遮蔽令牌形态 ─────────────────────
121
+ // 数据目录**只认内核唯一判据**(`core/lib/argent-datadir.mjs`)—— ⛔ 不在这里拼 `~/.dsh/argent`:
122
+ // 那正是"第二份判据",会踩门禁 `scripts/tests/argent-datadir.test.mjs`【3】(2026-10-05 实测踩到)。
123
+ const DATA_DIR = resolveDataDir({ env: process.env }).dir
124
+ const SECRET_RE = /\b(eyJ[A-Za-z0-9._-]{10,}|sk-[A-Za-z0-9._-]{8,})\b/g
125
+ function redact(value) {
126
+ const home = homedir()
127
+ const subs = [
128
+ [REPO, '<REPO>'],
129
+ [DATA_DIR, '<ARGENT_DATA_DIR>'],
130
+ // 家目录是 `/` 或空 ⇒ 不做这条替换(否则每个路径都会被切碎)
131
+ [home && home !== '/' && home.length > 1 ? home : '', '<HOME>'],
132
+ ]
133
+ const walk = (v) => {
134
+ if (typeof v === 'string') {
135
+ let s = v
136
+ for (const [from, to] of subs) if (from) s = s.split(from).join(to)
137
+ return s.replace(SECRET_RE, '<REDACTED>')
138
+ }
139
+ if (Array.isArray(v)) return v.map(walk)
140
+ if (v && typeof v === 'object') {
141
+ const out = {}
142
+ for (const [k, x] of Object.entries(v)) out[k] = walk(x)
143
+ return out
144
+ }
145
+ return v
146
+ }
147
+ return walk(value)
148
+ }
149
+ const clip = (s, n = 220) => (String(s).length > n ? `${String(s).slice(0, n)}…(截断,共 ${String(s).length} 字)` : String(s))
150
+
151
+ /**
152
+ * 输出**瘦身**(默认开;`--full` 关):超长字符串截断、超长数组只留前 N 项。
153
+ * 为什么默认开:证据要能贴进回报/工单,而 `argent_failures`/`argent_ledger` 的载荷里可能带成片业务原文
154
+ * ⇒ 默认只带"够判读"的量(结构完整、逐字段仍在,只是长值被截)。要看全量给 `--full`。
155
+ */
156
+ function shrink(value, { maxStr = 300, maxArr = 20 } = {}) {
157
+ const walk = (v) => {
158
+ if (typeof v === 'string') return clip(v, maxStr)
159
+ if (Array.isArray(v)) {
160
+ const head = v.slice(0, maxArr).map(walk)
161
+ return v.length > maxArr ? [...head, `…共 ${v.length} 项(要看全量加 --full)`] : head
162
+ }
163
+ if (v && typeof v === 'object') {
164
+ const out = {}
165
+ for (const [k, x] of Object.entries(v)) out[k] = walk(x)
166
+ return out
167
+ }
168
+ return v
169
+ }
170
+ return walk(value)
171
+ }
172
+ /** 证据出口:先瘦身(默认)再脱敏 —— 两者都只作用于**副本**,不影响判定用的原值。 */
173
+ const present = (value, { full = false } = {}) => redact(full ? value : shrink(value))
174
+
175
+ /**
176
+ * 起 MCP server 子进程 + 极简 JSON-RPC 客户端(每行一条消息;id 配对;超时即抛)。
177
+ * 只用 `node:` 内置模块 ⇒ 本文件在任何 Node ≥ 18 上都能跑(证明"不依赖 dsh 宿主")。
178
+ */
179
+ function connect({ serverPath, env, timeoutMs }) {
180
+ const child = spawn(process.execPath, [serverPath], { stdio: ['pipe', 'pipe', 'pipe'], env })
181
+ let buf = ''
182
+ let stderr = ''
183
+ let nextId = 1
184
+ const pending = new Map()
185
+ const done = { exited: false, code: null }
186
+
187
+ child.stdout.setEncoding('utf8')
188
+ child.stdout.on('data', (chunk) => {
189
+ buf += chunk
190
+ let i
191
+ while ((i = buf.indexOf('\n')) >= 0) {
192
+ const line = buf.slice(0, i).trim()
193
+ buf = buf.slice(i + 1)
194
+ if (!line) continue
195
+ let msg = null
196
+ try { msg = JSON.parse(line) } catch { continue }
197
+ if (msg.id !== undefined && pending.has(msg.id)) {
198
+ const p = pending.get(msg.id)
199
+ pending.delete(msg.id)
200
+ clearTimeout(p.timer)
201
+ p.resolve(msg)
202
+ }
203
+ }
204
+ })
205
+ child.stderr.setEncoding('utf8')
206
+ child.stderr.on('data', (c) => { stderr += c })
207
+ child.on('exit', (code) => { done.exited = true; done.code = code })
208
+
209
+ const request = (method, params) => new Promise((res, rej) => {
210
+ const id = nextId++
211
+ const timer = setTimeout(() => {
212
+ pending.delete(id)
213
+ rej(new Error(`${method} 超时(${timeoutMs}ms)—— server stderr 末尾:${clip(stderr.slice(-300))}`))
214
+ }, timeoutMs)
215
+ pending.set(id, { resolve: res, timer })
216
+ child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', id, method, params })}\n`)
217
+ })
218
+ const notify = (method, params) => child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', method, params })}\n`)
219
+
220
+ return {
221
+ request, notify,
222
+ stderr: () => stderr,
223
+ child,
224
+ close: () => new Promise((res) => {
225
+ if (done.exited) return res(done.code)
226
+ child.once('exit', (code) => res(code))
227
+ child.stdin.end() // server 见 stdin 结束即 exit 0(`process.stdin.on('end')`)
228
+ setTimeout(() => { try { child.kill('SIGKILL') } catch { /* 已退 */ } }, 5000).unref?.()
229
+ }),
230
+ }
231
+ }
232
+
233
+ /** 拆 `tools/call` 的响应:content[0].text 里是 JSON 字符串(MCP 既有形状)。 */
234
+ function unpack(msg) {
235
+ const text = msg?.result?.content?.[0]?.text
236
+ if (typeof text !== 'string') return { ok: false, parseError: '响应里没有 content[0].text', raw: msg }
237
+ try { const v = JSON.parse(text); return { ok: true, value: v } } catch (e) { return { ok: false, parseError: `content JSON 解析失败:${e.message}`, text } }
238
+ }
239
+
240
+ /** 跑一条只读 CLI 命令(`--json`),拿机器可读结果;失败不抛(宿主应看到诊断而不是崩)。 */
241
+ function runCli(args, { env, timeoutMs }) {
242
+ return new Promise((res) => {
243
+ const child = spawn(process.execPath, [CLI, ...args], { env, stdio: ['ignore', 'pipe', 'pipe'] })
244
+ let out = ''
245
+ let err = ''
246
+ const timer = setTimeout(() => { try { child.kill('SIGKILL') } catch { /* noop */ } }, timeoutMs)
247
+ child.stdout.setEncoding('utf8')
248
+ child.stdout.on('data', (c) => { out += c })
249
+ child.stderr.setEncoding('utf8')
250
+ child.stderr.on('data', (c) => { err += c })
251
+ child.on('exit', (code) => {
252
+ clearTimeout(timer)
253
+ let json = null
254
+ try { json = JSON.parse(out) } catch { /* 非 JSON */ }
255
+ res({ code, json, out: out.trim(), err: err.trim() })
256
+ })
257
+ })
258
+ }
259
+
260
+ async function main() {
261
+ const opt = parseArgs(process.argv.slice(2))
262
+
263
+ // ── 写类 tool 硬拒绝(在任何子进程起来**之前**)────────────────────────────────
264
+ const refused = opt.tools.filter((t) => !READ_ONLY_TOOLS.has(t))
265
+ if (refused.length) {
266
+ console.error(`❌ 拒绝调用非只读 tool:${refused.join(', ')}`)
267
+ console.error(` 白名单(只读):${[...READ_ONLY_TOOLS].join(', ')}`)
268
+ console.error(' ⛔ 本 PoC 的纪律是零副作用:写类 tool(含 *_shadow:会 cpSync 数据目录/造临时文件)一律不调。')
269
+ process.exit(2)
270
+ }
271
+ if (!opt.tools.length) { console.error('❌ --tools 不能为空'); process.exit(2) }
272
+
273
+ const env = { ...process.env }
274
+ if (opt.telemetryOff) env.ARGENT_TELEMETRY_OFF = '1'
275
+ const childEnvKeys = Object.keys(env).sort()
276
+
277
+ const report = {
278
+ host: { kind: 'mcp-stdio', client: CLIENT_INFO, hostIsDsh: false, node: process.version, argv: process.argv.slice(2) },
279
+ server: { path: present(opt.server, opt), exists: true, envKeys: childEnvKeys.length, telemetryOff: opt.telemetryOff },
280
+ checks: {},
281
+ readonlyTools: opt.tools,
282
+ verdict: { pass: false, reasons: [] },
283
+ }
284
+
285
+ try { readFileSync(opt.server) } catch (e) {
286
+ report.server.exists = false
287
+ report.verdict.reasons.push(`server 文件读不到:${e.message}`)
288
+ return finish(report, opt)
289
+ }
290
+
291
+ const c = connect({ serverPath: opt.server, env, timeoutMs: opt.timeoutMs })
292
+ try {
293
+ // ── 判据① initialize(protocolVersion / serverInfo / capabilities)────────────
294
+ const t0 = Date.now()
295
+ const init = await c.request('initialize', {
296
+ protocolVersion: PROTOCOL_VERSION,
297
+ capabilities: { tools: {} },
298
+ clientInfo: CLIENT_INFO,
299
+ })
300
+ const ir = init.result || {}
301
+ report.checks.initialize = {
302
+ ok: Boolean(ir.serverInfo && ir.protocolVersion),
303
+ elapsedMs: Date.now() - t0,
304
+ protocolVersion: ir.protocolVersion || null,
305
+ serverInfo: ir.serverInfo || null,
306
+ capabilities: ir.capabilities || null,
307
+ error: init.error || null,
308
+ }
309
+ if (!report.checks.initialize.ok) report.verdict.reasons.push('① initialize 没回 serverInfo/protocolVersion')
310
+ c.notify('notifications/initialized', {})
311
+
312
+ // ── ping(协议活性;顺带证明"服务端在 listen")────────────────────────────────
313
+ const ping = await c.request('ping', {})
314
+ report.checks.ping = { ok: ping.error === undefined, error: ping.error || null }
315
+
316
+ // ── 判据② tools/list(数量必须与冻结口径一致)────────────────────────────────
317
+ const tl = await c.request('tools/list', {})
318
+ const tools = tl.result?.tools || []
319
+ const names = tools.map((t) => t.name)
320
+ const countOk = opt.expectTools === 0 || tools.length === opt.expectTools
321
+ report.checks.toolsList = {
322
+ ok: Array.isArray(tl.result?.tools) && countOk,
323
+ count: tools.length,
324
+ expected: opt.expectTools || null,
325
+ names,
326
+ schemasPresent: tools.every((t) => t && t.inputSchema && typeof t.inputSchema === 'object'),
327
+ error: tl.error || null,
328
+ }
329
+ if (!Array.isArray(tl.result?.tools)) report.verdict.reasons.push('② tools/list 没有返回 tools 数组')
330
+ else if (!countOk) report.verdict.reasons.push(`② tools/list 数量 ${tools.length} ≠ 冻结口径 ${opt.expectTools}`)
331
+
332
+ // ── 判据③ 只读 tool 实调(逐个贴脱敏结果)────────────────────────────────────
333
+ report.checks.toolCalls = []
334
+ for (const name of opt.tools) {
335
+ const t1 = Date.now()
336
+ const msg = await c.request('tools/call', { name, arguments: DEFAULT_ARGS[name] || {} })
337
+ const un = unpack(msg)
338
+ const value = un.ok ? un.value : null
339
+ // 三层分开判(**不把"业务结论为否"当成"调用失败"** —— 那会把真实判定链误报成红):
340
+ // · callOk「调用真跑通」= RPC 没报错 ∧ handler 没标 isError ∧ 载荷结构化;
341
+ // · degraded「能力降级」= 载荷自报 `python_unavailable` / `code:2`(**是**能力缺口,**算红**);
342
+ // · verdict:`ok`(业务判 true,或该 tool 本就没有 `ok` 字段——如 `argent_version` 只回事实)
343
+ // / `judged`(业务显式判 false,如本机未装许可 ⇒ 判定链照样走通)/ `degraded` / `failed`。
344
+ const rpcOk = msg.error === undefined && msg.result !== undefined
345
+ const isError = msg.result?.isError === true
346
+ const shapeOk = un.ok && value !== null && typeof value === 'object'
347
+ const callOk = Boolean(rpcOk && !isError && shapeOk)
348
+ const degraded = shapeOk && (value.reason === 'python_unavailable' || value.code === 2)
349
+ ? redact({ reason: value.reason || null, code: value.code ?? null, error: clip(value.error || '', 200) })
350
+ : null
351
+ const verdict = !callOk ? 'failed' : (degraded ? 'degraded' : (value.ok === false ? 'judged' : 'ok'))
352
+ report.checks.toolCalls.push({
353
+ name,
354
+ ok: callOk && !degraded, // 判据③ 只数「真跑通且没降级」的
355
+ callOk, verdict, rpcOk, isError, elapsedMs: Date.now() - t1,
356
+ result: value === null ? { parseError: un.parseError, text: clip(un.text || '') } : present(value, opt),
357
+ degraded,
358
+ })
359
+ }
360
+ const okCalls = report.checks.toolCalls.filter((x) => x.ok).length
361
+ const judgedCalls = report.checks.toolCalls.filter((x) => x.verdict === 'judged').length
362
+ report.checks.readonlyCalls = {
363
+ ok: okCalls >= 2,
364
+ okCount: okCalls,
365
+ judgedCount: judgedCalls, // 其中"业务判否但判定链走通"的(**不是**失败)
366
+ total: report.checks.toolCalls.length,
367
+ required: 2,
368
+ degradedCount: report.checks.toolCalls.filter((x) => x.degraded).length,
369
+ }
370
+ if (okCalls < 2) report.verdict.reasons.push(`③ 只读 tool 真调成功 ${okCalls} 个(判据要求 ≥ 2)`)
371
+
372
+ // ── 判据④ 版本/能力可查(CLI 面;MCP 面的等价物 = `argent_version` tool)────────
373
+ if (opt.cli) {
374
+ const v = await runCli(['version', '--json'], { env, timeoutMs: opt.timeoutMs })
375
+ const h = await runCli(['--help', '--json'], { env, timeoutMs: opt.timeoutMs })
376
+ report.checks.cliCapability = {
377
+ ok: v.code === 0 && h.code === 0 && Boolean(h.json?.verbs),
378
+ version: { code: v.code, argent: v.json?.argent ?? null, raw: clip(v.out, 160) },
379
+ help: { code: h.code, verbs: (h.json?.verbs || []).map((x) => x.name ?? x), verbCount: (h.json?.verbs || []).length },
380
+ }
381
+ if (!report.checks.cliCapability.ok) report.verdict.reasons.push('④ 版本/能力查询(CLI --json)失败')
382
+ } else {
383
+ report.checks.cliCapability = { ok: true, skipped: true }
384
+ }
385
+ } catch (e) {
386
+ report.verdict.reasons.push(`RPC 层失败:${e.message}`)
387
+ } finally {
388
+ report.server.exitCode = await c.close()
389
+ report.server.stderrTail = clip(c.stderr().trim().slice(-400))
390
+ }
391
+
392
+ // ── 六项接口的**实测可达性**(PoC 判定;权威矩阵列见契约 §3,本块只记"这个宿主量到了什么")
393
+ const entryOk = report.checks.toolsList?.ok === true && report.checks.readonlyCalls?.ok === true
394
+ const versionOk = report.checks.cliCapability?.ok === true
395
+ report.hostSurface = [
396
+ { face: '① 入口/工具暴露', status: entryOk ? '✅' : '❌', how: `initialize + tools/list(${report.checks.toolsList?.count ?? '?'}) + tools/call(成功 ${report.checks.readonlyCalls?.okCount ?? 0} 个只读)` },
397
+ { face: '② 设置面板', status: '—', why: 'stdio 宿主**无 UI**(设计性缺失)', substitute: `配置文件(数据目录下的 *.json/*.env)+ CLI 只读查询;宿主自己解析` },
398
+ { face: '③ 通知通道', status: '—', why: '本宿主无投递通道(设计性缺失)', substitute: 'CLI `notify` verb + `core/lib/notify/*`(宿主负责把事实转成自己的通道)' },
399
+ { face: '④ 会话/权限', status: '—', why: 'stdio 无会话概念、无权限 UI(设计性缺失)', substitute: '宿主进程自身的权限模型(本 PoC = 调用者的 uid/umask)' },
400
+ { face: '⑤ 后台/定时', status: '—', why: '宿主无 scheduler(设计性缺失)', substitute: '系统定时器(launchd/systemd)+ `scripts/flow-timers.sh`' },
401
+ { face: '⑥ 升级/版本', status: versionOk ? '✅' : '❌', how: `CLI \`version --json\`(${report.checks.cliCapability?.version?.argent ?? '?'})+ \`--help --json\`(verb ${report.checks.cliCapability?.help?.verbCount ?? '?'})+ MCP \`argent_version\`` },
402
+ ]
403
+
404
+ report.verdict.pass = report.verdict.reasons.length === 0
405
+ return finish(report, opt)
406
+ }
407
+
408
+ function finish(report, opt) {
409
+ if (opt.json) {
410
+ console.log(JSON.stringify(report, null, 2))
411
+ } else {
412
+ printHuman(report)
413
+ }
414
+ process.exit(report.verdict.pass ? 0 : 1)
415
+ }
416
+
417
+ function printHuman(r) {
418
+ const mark = (b) => (b ? '✅' : '❌')
419
+ console.log('\n【MCP 宿主 PoC】宿主 = 本脚本(极简 stdio JSON-RPC 客户端,**不是 dsh**)')
420
+ console.log(` server = ${r.server.path}(node ${r.host.node};子进程 env ${r.server.envKeys} 键;遥测 ${r.server.telemetryOff ? 'OFF' : 'ON⚠️'})`)
421
+ const i = r.checks.initialize || {}
422
+ console.log(`\n ① initialize ${mark(i.ok)} protocolVersion=${i.protocolVersion} serverInfo=${JSON.stringify(i.serverInfo)} capabilities=${JSON.stringify(i.capabilities)} (${i.elapsedMs}ms)`)
423
+ if (r.checks.ping) console.log(` ping ${mark(r.checks.ping.ok)}`)
424
+ const t = r.checks.toolsList || {}
425
+ console.log(` ② tools/list ${mark(t.ok)} ${t.count} 个(冻结口径 ${t.expected ?? '—'})inputSchema 齐备=${mark(t.schemasPresent)}`)
426
+ console.log(` ${(t.names || []).join(', ')}`)
427
+ const rc = r.checks.readonlyCalls || {}
428
+ console.log(` ③ 只读 tool 实调 ${mark(rc.ok)} 真跑通 ${rc.okCount}/${rc.total}(判据 ≥${rc.required};其中"业务判否但判定链走通" ${rc.judgedCount ?? 0} 个;降级 ${rc.degradedCount ?? 0} 个)`)
429
+ const MARK = { ok: '✅', judged: '🟡', degraded: '🔴', failed: '❌' }
430
+ const NOTE = {
431
+ ok: '业务判 true',
432
+ judged: '业务判 false —— **判定链走通**(不是调用失败;本机真实状态)',
433
+ degraded: '能力降级(诚实诊断,非崩溃)',
434
+ failed: '调用失败',
435
+ }
436
+ for (const c of r.checks.toolCalls || []) {
437
+ console.log(` · ${c.name.padEnd(24)} ${MARK[c.verdict] || '❓'} ${String(c.elapsedMs).padStart(5)}ms ${NOTE[c.verdict] || ''}`)
438
+ console.log(` ${clip(JSON.stringify(c.result), 220)}`)
439
+ if (c.degraded) console.log(` ↳ reason=${c.degraded.reason} code=${c.degraded.code} ${clip(c.degraded.error || '', 140)}`)
440
+ }
441
+ const cc = r.checks.cliCapability || {}
442
+ console.log(` ④ 版本/能力 ${mark(cc.ok)}${cc.skipped ? '(--no-cli 跳过)' : ` CLI version=${cc.version?.argent} | --help --json verb ${cc.help?.verbCount} 个`}`)
443
+ console.log('\n 六项接口可达性(实测;权威口径见 docs/Argent-宿主适配契约.md §3 矩阵)')
444
+ for (const s of r.hostSurface || []) {
445
+ console.log(` ${s.status} ${s.face} ${s.how || `— ${s.why}`}`)
446
+ if (s.substitute) console.log(` 替代:${s.substitute}`)
447
+ }
448
+ console.log(`\n 结论:${r.verdict.pass ? '✅ PoC 判据全过' : `❌ 未过 —— ${r.verdict.reasons.join(';')}`}`)
449
+ console.log(' 零副作用证明(外部判据):跑前 `node scripts/check-test-env-safety.mjs --snapshot <f>`,跑后 `--verify <f>`\n')
450
+ }
451
+
452
+ main().catch((e) => {
453
+ console.error(`❌ ${e.message}`)
454
+ process.exit(2)
455
+ })