@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.28

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 (533) hide show
  1. package/README.md +172 -219
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1788 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -56
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +161 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +553 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +947 -110
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +676 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/provider-detail-sanitize.js +64 -0
  41. package/dist/capabilities/resilience.js +191 -0
  42. package/dist/capabilities/route-provider-conformance.js +341 -0
  43. package/dist/capabilities/session/action-cache.js +50 -44
  44. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  45. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  46. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  47. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  48. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  49. package/dist/capabilities/session/benchmark.js +189 -0
  50. package/dist/capabilities/session/bridge-ledger.js +224 -0
  51. package/dist/capabilities/session/extension-bridge.js +636 -0
  52. package/dist/capabilities/session/extension-channel.js +109 -0
  53. package/dist/capabilities/session/extension-distribution.js +230 -0
  54. package/dist/capabilities/session/extract.js +31 -21
  55. package/dist/capabilities/session/golden-score.js +114 -0
  56. package/dist/capabilities/session/health-watch.js +215 -0
  57. package/dist/capabilities/session/read-guard.js +75 -24
  58. package/dist/capabilities/session/static-flight-golden.js +150 -0
  59. package/dist/capabilities/session/transport.js +97 -83
  60. package/dist/capabilities/session/wizard.js +117 -0
  61. package/dist/capabilities/session-consent.js +178 -0
  62. package/dist/capabilities/session-login.js +170 -0
  63. package/dist/capabilities/session-search.js +635 -44
  64. package/dist/capabilities/spawn-bounded.js +203 -0
  65. package/dist/capabilities/sponsor-plugin.js +341 -0
  66. package/dist/capabilities/visa-policy.js +140 -0
  67. package/dist/capabilities/weather.js +243 -138
  68. package/dist/data/session-golden-20.json +25 -0
  69. package/dist/data/sf-golden-manifest.json +103 -0
  70. package/dist/data/sf-static-routes.json +91 -0
  71. package/dist/scripts/action-cache-tests.js +24 -59
  72. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  73. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  74. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  75. package/dist/scripts/agent-reach-error-tests.js +354 -0
  76. package/dist/scripts/agent-reach-tests.js +26 -18
  77. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  78. package/dist/scripts/anything-path-measure-tests.js +133 -0
  79. package/dist/scripts/anything-path-measure.js +150 -0
  80. package/dist/scripts/anything-tests.js +250 -60
  81. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  82. package/dist/scripts/artifacts-capability-tests.js +727 -0
  83. package/dist/scripts/async-collect.js +50 -11
  84. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  85. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  86. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  87. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  88. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  89. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  90. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  91. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  92. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  93. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  94. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  95. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +126 -0
  96. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  97. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  98. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  99. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  100. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  101. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  102. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  103. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  104. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  105. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  106. package/dist/scripts/booking-executor-tests.js +80 -0
  107. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  108. package/dist/scripts/booking-saga-tests.js +125 -0
  109. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  110. package/dist/scripts/bootstrap-tests.js +1549 -0
  111. package/dist/scripts/bridge-events-tests.js +239 -0
  112. package/dist/scripts/bridge-ledger-tests.js +428 -0
  113. package/dist/scripts/build-changelog.js +238 -0
  114. package/dist/scripts/build-metrics-report.js +331 -0
  115. package/dist/scripts/changelog-tests.js +118 -0
  116. package/dist/scripts/channel-probe-tests.js +103 -0
  117. package/dist/scripts/channel-probe.js +169 -0
  118. package/dist/scripts/channel-registry-tests.js +208 -0
  119. package/dist/scripts/city-scenario-tier-tests.js +234 -0
  120. package/dist/scripts/companion-tests.js +25 -70
  121. package/dist/scripts/dida-cookie-probe.js +87 -0
  122. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  123. package/dist/scripts/dida-sw-probe.js +114 -0
  124. package/dist/scripts/diff-test.js +17 -12
  125. package/dist/scripts/doctor-tests.js +600 -0
  126. package/dist/scripts/drill-multiuser-ledger-tests.js +1076 -0
  127. package/dist/scripts/drill-sdk-descendant-cleanup-tests.js +606 -0
  128. package/dist/scripts/drill-w2a-sensor-tests.js +788 -0
  129. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  130. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  131. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  132. package/dist/scripts/dsh-llm-tests.js +437 -0
  133. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  134. package/dist/scripts/dsh-runtime-closure.js +162 -0
  135. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  136. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  137. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  138. package/dist/scripts/effect-tests.js +539 -0
  139. package/dist/scripts/engine-run.js +1 -3
  140. package/dist/scripts/engine-tests.js +16 -10
  141. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  142. package/dist/scripts/evaluation-contract-tests.js +268 -0
  143. package/dist/scripts/extension-distribution-cli.js +39 -0
  144. package/dist/scripts/extension-distribution-tests.js +443 -0
  145. package/dist/scripts/extension-tests.js +988 -0
  146. package/dist/scripts/external-event-tests.js +350 -0
  147. package/dist/scripts/fact-gate-tests.js +869 -0
  148. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  149. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  150. package/dist/scripts/flight-malformed-tests.js +159 -0
  151. package/dist/scripts/flyai-setup-tests.js +228 -0
  152. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  153. package/dist/scripts/flyai-tests.js +692 -0
  154. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  155. package/dist/scripts/fx-contract-tests.js +201 -0
  156. package/dist/scripts/g5-guard-tests.js +150 -0
  157. package/dist/scripts/g5-guard.js +193 -0
  158. package/dist/scripts/gen-lunar-anchors.js +106 -0
  159. package/dist/scripts/geo-atlas-tests.js +283 -0
  160. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  161. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  162. package/dist/scripts/gotry-try-demo.js +49 -0
  163. package/dist/scripts/ground-transfer-tests.js +956 -0
  164. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  165. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  166. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  167. package/dist/scripts/hbcli-tests.js +121 -69
  168. package/dist/scripts/health-watch-cli.js +73 -0
  169. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  170. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  171. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  172. package/dist/scripts/i18n-tests.js +41 -0
  173. package/dist/scripts/incident-tests.js +281 -87
  174. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  175. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  176. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  177. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  178. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  179. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  180. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  181. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  182. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  183. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  184. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  185. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  186. package/dist/scripts/itinerary-deck-tests.js +435 -0
  187. package/dist/scripts/itinerary-html-tests.js +426 -0
  188. package/dist/scripts/journey-tests.js +22 -27
  189. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  190. package/dist/scripts/kernel-manifest-gate.js +126 -0
  191. package/dist/scripts/kernel-manifest-tests.js +212 -0
  192. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  193. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  194. package/dist/scripts/kernel-manifest.js +369 -0
  195. package/dist/scripts/lavish-local-tests.js +1226 -0
  196. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  197. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  198. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  199. package/dist/scripts/ledger-tests.js +382 -246
  200. package/dist/scripts/ledger-workflow-crash.js +10 -20
  201. package/dist/scripts/m3-cohort-tests.js +714 -0
  202. package/dist/scripts/m3-cohort.js +310 -0
  203. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  204. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  205. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  206. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  207. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  208. package/dist/scripts/memory-capture-tests.js +242 -61
  209. package/dist/scripts/memory-decay-tests.js +18 -52
  210. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  211. package/dist/scripts/memory-lifecycle.js +196 -0
  212. package/dist/scripts/memory-metrics.js +24 -12
  213. package/dist/scripts/memory-value-report.js +631 -0
  214. package/dist/scripts/memory-value-tests.js +328 -0
  215. package/dist/scripts/metrics-report-tests.js +151 -0
  216. package/dist/scripts/model-override-e2e.js +137 -0
  217. package/dist/scripts/nightly-evidence-tests.js +203 -0
  218. package/dist/scripts/nightly-evidence.js +303 -0
  219. package/dist/scripts/nudge-digest.js +39 -28
  220. package/dist/scripts/observability-tests.js +381 -0
  221. package/dist/scripts/onboarding-tests.js +161 -0
  222. package/dist/scripts/opensky-check.js +18 -13
  223. package/dist/scripts/opensky-tests.js +28 -28
  224. package/dist/scripts/outcome-projection-tests.js +427 -0
  225. package/dist/scripts/persona-sim-tests.js +603 -0
  226. package/dist/scripts/persona-sim.js +1081 -0
  227. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  228. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  229. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  230. package/dist/scripts/price-drift-tests.js +340 -0
  231. package/dist/scripts/price-drift-watch.js +447 -0
  232. package/dist/scripts/probe-poi-tests.js +53 -45
  233. package/dist/scripts/product-metrics.js +483 -0
  234. package/dist/scripts/publish-preverify.js +110 -0
  235. package/dist/scripts/realtime-pricing-tests.js +76 -0
  236. package/dist/scripts/recall-tests.js +513 -0
  237. package/dist/scripts/replay-async.js +22 -18
  238. package/dist/scripts/replay-real.js +18 -18
  239. package/dist/scripts/replay.js +54 -82
  240. package/dist/scripts/route-provider-conformance-tests.js +391 -0
  241. package/dist/scripts/session-attach-diagnose.js +20 -20
  242. package/dist/scripts/session-attach-poc.js +34 -37
  243. package/dist/scripts/session-benchmark.js +180 -0
  244. package/dist/scripts/session-dida-live-e2e.js +199 -0
  245. package/dist/scripts/session-extract-tests.js +14 -35
  246. package/dist/scripts/session-link-tests.js +478 -0
  247. package/dist/scripts/session-login.js +27 -46
  248. package/dist/scripts/session-tests.js +796 -96
  249. package/dist/scripts/session-zone-crash.js +49 -0
  250. package/dist/scripts/session-zone-ledger-tests.js +432 -0
  251. package/dist/scripts/session-zone-observation-tests.js +492 -0
  252. package/dist/scripts/session-zone-observe.js +135 -0
  253. package/dist/scripts/session-zone-wiring-tests.js +566 -0
  254. package/dist/scripts/session-zones-tests.js +374 -0
  255. package/dist/scripts/sf-live-benchmark.js +367 -0
  256. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  257. package/dist/scripts/sf-live-cli-tests.js +92 -0
  258. package/dist/scripts/sf-live-options.js +46 -0
  259. package/dist/scripts/sf-manifest-tests.js +30 -0
  260. package/dist/scripts/sf-manifest.js +33 -0
  261. package/dist/scripts/sf-soft-score-tests.js +67 -0
  262. package/dist/scripts/sf-summary-tests.js +324 -0
  263. package/dist/scripts/sf-summary.js +525 -0
  264. package/dist/scripts/share-tests.js +432 -0
  265. package/dist/scripts/skeleton-check.js +16 -14
  266. package/dist/scripts/skeleton-integration-test.js +28 -11
  267. package/dist/scripts/skills-contract-tests.js +51 -46
  268. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  269. package/dist/scripts/smoke.js +690 -246
  270. package/dist/scripts/spawn-bounded-abort-tests.js +244 -0
  271. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  272. package/dist/scripts/state-cli-tests.js +226 -101
  273. package/dist/scripts/state-cli.js +565 -198
  274. package/dist/scripts/state-repair-cli.js +66 -0
  275. package/dist/scripts/state-repair-tests.js +140 -0
  276. package/dist/scripts/static-golden-tests.js +165 -0
  277. package/dist/scripts/time-eval-tests.js +212 -192
  278. package/dist/scripts/travel-timeline-tests.js +29 -93
  279. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  280. package/dist/scripts/turn-handoff-collect.js +161 -0
  281. package/dist/scripts/turn-policy-tests.js +55 -0
  282. package/dist/scripts/typed-contract-canary.js +176 -0
  283. package/dist/scripts/tz-resolver-sanity.js +50 -0
  284. package/dist/scripts/tz-resolver-tests.js +584 -0
  285. package/dist/scripts/unified-tests.js +60 -20
  286. package/dist/scripts/visa-policy-tests.js +62 -0
  287. package/dist/scripts/weather-tests.js +276 -41
  288. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  289. package/dist/scripts/write-gate-tests.js +522 -0
  290. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  291. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  292. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  293. package/dist/scripts/z3-race-tests.js +46 -0
  294. package/dist/src/artifact-gate.js +829 -0
  295. package/dist/src/backend/kernel.js +91 -0
  296. package/dist/src/backend/modules/booking-copilot.js +92 -0
  297. package/dist/src/backend/modules/booking-executor.js +124 -0
  298. package/dist/src/backend/modules/session-search.js +551 -0
  299. package/dist/src/benchmark-agent-conformance.js +578 -0
  300. package/dist/src/benchmark-environment-bridge.js +515 -0
  301. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  302. package/dist/src/benchmark-tool-isolation.js +151 -0
  303. package/dist/src/bookable-facts.js +533 -0
  304. package/dist/src/booking-saga.js +97 -0
  305. package/dist/src/booking-surface/availability-policy.js +697 -0
  306. package/dist/src/booking-surface/booking-intent.js +43 -0
  307. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  308. package/dist/src/booking-surface/canonical-schema.js +116 -0
  309. package/dist/src/booking-surface/contracts.js +75 -0
  310. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  311. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  312. package/dist/src/booking-surface/dsh-worker.js +98 -0
  313. package/dist/src/booking-surface/error-codes.js +231 -0
  314. package/dist/src/booking-surface/index.js +9 -0
  315. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  316. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  317. package/dist/src/booking-surface/profile.js +24 -0
  318. package/dist/src/booking-surface/recovery-chain.js +367 -0
  319. package/dist/src/booking-surface/runtime.js +1968 -0
  320. package/dist/src/booking-surface/server.js +674 -0
  321. package/dist/src/booking-surface/startup.js +168 -0
  322. package/dist/src/booking-surface/validation.js +261 -0
  323. package/dist/src/bridge.js +20 -13
  324. package/dist/src/city-scenario-tier.js +378 -0
  325. package/dist/src/companions.js +48 -74
  326. package/dist/src/contracts.js +21 -43
  327. package/dist/src/dsh-llm.js +171 -115
  328. package/dist/src/engine.js +276 -193
  329. package/dist/src/evaluation-cadence.js +97 -0
  330. package/dist/src/evaluation-contracts.js +283 -0
  331. package/dist/src/flight-pack-adapter.js +54 -0
  332. package/dist/src/flight-pack-contract.js +2 -0
  333. package/dist/src/flyai-setup-tool.js +70 -0
  334. package/dist/src/gotry-backend.js +125 -0
  335. package/dist/src/hotel-date-gate.js +118 -0
  336. package/dist/src/i18n.js +88 -0
  337. package/dist/src/index.js +2385 -881
  338. package/dist/src/itinerary-deck.js +139 -0
  339. package/dist/src/itinerary-doc-shared.js +661 -0
  340. package/dist/src/itinerary-html.js +131 -0
  341. package/dist/src/journey.js +155 -108
  342. package/dist/src/lavish-tools.js +400 -0
  343. package/dist/src/ledger-repair-apply.js +538 -0
  344. package/dist/src/ledger-repair-plan.js +463 -0
  345. package/dist/src/loop.js +331 -161
  346. package/dist/src/m3-cohort.js +1577 -0
  347. package/dist/src/memory-capture.js +180 -32
  348. package/dist/src/memory-decay.js +30 -14
  349. package/dist/src/memory-lifecycle.js +1310 -0
  350. package/dist/src/memory-utility.js +27 -40
  351. package/dist/src/mock-llm.js +36 -61
  352. package/dist/src/model.js +81 -36
  353. package/dist/src/outcome-projection.js +746 -0
  354. package/dist/src/realtime-pricing.js +103 -0
  355. package/dist/src/recall/card.js +74 -0
  356. package/dist/src/recall/evaluator.js +140 -0
  357. package/dist/src/recall/integration.js +17 -0
  358. package/dist/src/recall/tick.js +166 -0
  359. package/dist/src/session-link/plan-it-action.js +48 -0
  360. package/dist/src/session-link/session-link.js +248 -0
  361. package/dist/src/session-zone-ledger.js +312 -0
  362. package/dist/src/session-zone-observation.js +354 -0
  363. package/dist/src/session-zone-wiring.js +658 -0
  364. package/dist/src/session-zones.js +510 -0
  365. package/dist/src/share/adapters.js +51 -0
  366. package/dist/src/share/share-consent.js +76 -0
  367. package/dist/src/share/share-deck.js +58 -0
  368. package/dist/src/share/share-token.js +146 -0
  369. package/dist/src/slot-spec.js +84 -106
  370. package/dist/src/state-ledger.js +696 -524
  371. package/dist/src/state-repair.js +299 -0
  372. package/dist/src/subagent-job-id-guard.js +58 -0
  373. package/dist/src/time-anchor.js +231 -33
  374. package/dist/src/tool-packet.js +20 -8
  375. package/dist/src/travel-slots.js +58 -35
  376. package/dist/src/travel-timeline.js +44 -63
  377. package/dist/src/turn-deadline.js +287 -0
  378. package/dist/src/turn-policy.js +118 -0
  379. package/dist/src/tz-resolver.js +143 -0
  380. package/dist/src/unified.js +600 -360
  381. package/dist/src/wish-pool.js +42 -12
  382. package/dist/src/write-gate.js +495 -0
  383. package/dist/src/z3-shared.js +284 -0
  384. package/extension/README.md +21 -0
  385. package/extension/README.zh-CN.md +21 -0
  386. package/extension/background.js +463 -0
  387. package/extension/content-bridge.js +270 -0
  388. package/extension/content-main.js +111 -0
  389. package/extension/manifest.json +60 -0
  390. package/package.json +407 -14
  391. package/schemas/booking.intent.schema.json +193 -0
  392. package/schemas/booking.surface.schema.json +2622 -0
  393. package/ts/capabilities/agent-reach-deep.ts +114 -0
  394. package/ts/capabilities/agent-reach.ts +198 -0
  395. package/ts/capabilities/anything.ts +282 -0
  396. package/ts/capabilities/artifacts.ts +428 -0
  397. package/ts/capabilities/fact-log.ts +54 -0
  398. package/ts/capabilities/flyai-config.ts +388 -0
  399. package/ts/capabilities/flyai.ts +1274 -0
  400. package/ts/capabilities/hbcli.ts +253 -71
  401. package/ts/capabilities/incident-log.ts +88 -59
  402. package/ts/capabilities/itinerary-artifact.ts +214 -0
  403. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  404. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  405. package/ts/capabilities/opensky.ts +121 -0
  406. package/ts/capabilities/session/action-cache.ts +148 -0
  407. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  408. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  409. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  410. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  411. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  412. package/ts/capabilities/session/benchmark.ts +276 -0
  413. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  414. package/ts/capabilities/session/extension-bridge.ts +794 -0
  415. package/ts/capabilities/session/extension-channel.ts +144 -0
  416. package/ts/capabilities/session/extension-distribution.ts +266 -0
  417. package/ts/capabilities/session/extract.ts +54 -0
  418. package/ts/capabilities/session/golden-score.ts +139 -0
  419. package/ts/capabilities/session/health-watch.ts +264 -0
  420. package/ts/capabilities/session/read-guard.ts +141 -0
  421. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  422. package/ts/capabilities/session/transport.ts +146 -0
  423. package/ts/capabilities/session/wizard.ts +140 -0
  424. package/ts/capabilities/session-consent.ts +211 -0
  425. package/ts/capabilities/session-login.ts +208 -0
  426. package/ts/capabilities/session-search.ts +893 -0
  427. package/ts/capabilities/weather.ts +330 -0
  428. package/ts/dsh-runtime/vendor/README.md +68 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  440. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  441. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  442. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  443. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  444. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  445. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  446. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  447. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  448. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  449. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  450. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  451. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  452. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  453. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  454. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  455. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  456. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  457. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  458. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  459. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  460. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  461. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  462. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  463. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  464. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  465. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  466. package/ts/package.json +293 -3
  467. package/ts/scripts/async-collect.ts +91 -0
  468. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  469. package/ts/scripts/skeleton-check.ts +1 -1
  470. package/ts/scripts/skeleton-integration-test.ts +23 -1
  471. package/ts/scripts/state-cli.ts +586 -0
  472. package/ts/scripts/turn-handoff-collect.ts +178 -0
  473. package/ts/src/artifact-gate.ts +991 -0
  474. package/ts/src/benchmark-agent-conformance.ts +619 -0
  475. package/ts/src/benchmark-environment-bridge.ts +538 -0
  476. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  477. package/ts/src/benchmark-tool-isolation.ts +166 -0
  478. package/ts/src/bookable-facts.ts +837 -0
  479. package/ts/src/booking-saga.ts +133 -0
  480. package/ts/src/booking-surface/availability-policy.ts +522 -0
  481. package/ts/src/booking-surface/booking-intent.ts +86 -0
  482. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  483. package/ts/src/booking-surface/canonical-schema.js +116 -0
  484. package/ts/src/booking-surface/contracts.ts +475 -0
  485. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  486. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  487. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  488. package/ts/src/booking-surface/error-codes.ts +241 -0
  489. package/ts/src/booking-surface/index.ts +9 -0
  490. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  491. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  492. package/ts/src/booking-surface/profile.ts +42 -0
  493. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  494. package/ts/src/booking-surface/runtime.ts +1793 -0
  495. package/ts/src/booking-surface/server.ts +733 -0
  496. package/ts/src/booking-surface/startup.ts +201 -0
  497. package/ts/src/booking-surface/validation.ts +217 -0
  498. package/ts/src/companions.ts +113 -0
  499. package/ts/src/contracts.ts +17 -3
  500. package/ts/src/dsh-llm.ts +152 -17
  501. package/ts/src/flyai-setup-tool.ts +77 -0
  502. package/ts/src/index.ts +2123 -200
  503. package/ts/src/itinerary-deck.ts +189 -0
  504. package/ts/src/itinerary-doc-shared.ts +741 -0
  505. package/ts/src/itinerary-html.ts +171 -0
  506. package/ts/src/loop.ts +234 -26
  507. package/ts/src/memory-capture.ts +283 -0
  508. package/ts/src/memory-decay.ts +55 -0
  509. package/ts/src/memory-utility.ts +70 -0
  510. package/ts/src/mock-llm.ts +4 -8
  511. package/ts/src/model.ts +88 -2
  512. package/ts/src/recall/card.ts +106 -0
  513. package/ts/src/recall/evaluator.ts +170 -0
  514. package/ts/src/recall/integration.ts +35 -0
  515. package/ts/src/recall/tick.ts +213 -0
  516. package/ts/src/session-link/plan-it-action.ts +78 -0
  517. package/ts/src/session-link/session-link.ts +275 -0
  518. package/ts/src/share/adapters.ts +93 -0
  519. package/ts/src/share/share-consent.ts +105 -0
  520. package/ts/src/share/share-deck.ts +85 -0
  521. package/ts/src/share/share-token.ts +166 -0
  522. package/ts/src/slot-spec.ts +172 -0
  523. package/ts/src/state-ledger.ts +1166 -0
  524. package/ts/src/subagent-job-id-guard.ts +72 -0
  525. package/ts/src/time-anchor.ts +338 -0
  526. package/ts/src/tool-packet.ts +32 -0
  527. package/ts/src/travel-slots.ts +219 -0
  528. package/ts/src/travel-timeline.ts +83 -0
  529. package/ts/src/turn-deadline.ts +366 -0
  530. package/ts/src/turn-policy.ts +154 -0
  531. package/ts/src/unified.ts +444 -105
  532. package/ts/src/wish-pool.ts +79 -0
  533. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,159 @@
1
+ /**
2
+ * staicli(hbcli)全流程端到端测试:二进制 → 隔离凭证面 → 真实 UAT 取票 →
3
+ * 能力层真实 spawn → 效应解译层策略(ADR-18)→ 工具层 gotry_hotel_search。
4
+ *
5
+ * 账号与凭证纪律(红线):
6
+ * - 测试账号用 hotel-be 种子沙箱 API 账号 hotelbyte_api_demo/hotelbyte_api_demo
7
+ * (user/domain/predefined_user_demo.go,IsSandbox:true,专为 API 接入验证设计);
8
+ * 正式集成方换自己申请的 hbk_ / hbs_ 前缀凭证对。
9
+ * - 凭证写进 mkdtemp 的隔离 STAICLI_HOME,全程不读不写用户默认 ~/.staicli
10
+ * (巡检状态纪律:测试不得触碰真实用户凭证与产品状态);
11
+ * - stateRoot 同样走临时目录,结束即删。
12
+ *
13
+ * SKIP 语义(同 §17 先例:能跑则跑,环境缺前置不红):
14
+ * - hbcli 未安装 → SKIP + 安装指引(npx @danceiny/gotry setup);
15
+ * - UAT(api-test.hotelbyte.com)不可达 → SKIP + 原因。
16
+ * UAT 当前库存态(2026-08-30 实测):目的地/酒店参考数据为空,hotel-list 按名
17
+ * 查询返回业务层 404——通道/鉴权/搜索编排(供应商 provenance+correlationId)均
18
+ * 真实工作,故断言「通道真实性 + 降级诚实性」而非「有库存返回」。
19
+ *
20
+ * 运行(显式 UAT opt-in): cd ts && GOTRY_HBCLI_LIVE=1 npx tsx scripts/hbcli-e2e-tests.ts
21
+ */
22
+ import { spawnSync } from 'node:child_process';
23
+ import { mkdtempSync, rmSync } from 'node:fs';
24
+ import { tmpdir } from 'node:os';
25
+ import { join } from 'node:path';
26
+ import assert from 'node:assert/strict';
27
+ import { listDestinations, searchHotels, hbcliBinCandidates } from '../capabilities/hbcli.js';
28
+ import { makeProductionInterpreter } from '../capabilities/effect.js';
29
+ const UAT = 'https://api-test.hotelbyte.com';
30
+ /** hotel-be 种子沙箱 API 账号(公开演示用,IsSandbox;真实集成方应换自己申请的 hbk_/hbs_ 前缀凭证对) */
31
+ const SANDBOX_APP_KEY = 'hotelbyte_api_demo';
32
+ const SANDBOX_APP_SECRET = 'hotelbyte_api_demo';
33
+ const GUIDANCE = [
34
+ 'staicli(hbcli)账号配置指引:',
35
+ ' 1. 安装 CLI(若缺):npx @danceiny/gotry setup(npm 装 staicli@npmjs → PATH 的 hbcli)',
36
+ ' 2. 快速试用(沙箱演示账号,来自 hotel-be 种子,user/domain/predefined_user_demo.go):',
37
+ ' hbcli auth set-credentials --app-key hotelbyte_api_demo --app-secret hotelbyte_api_demo',
38
+ ' 3. 正式接入:向 HotelByte 申请专属 appKey/appSecret(hbk_*/hbs_*),替换第 2 步凭证;',
39
+ ' 门户模式则 hbcli auth login --username <email>(权限更大,搜索场景无必要)',
40
+ ' 4. 自检:hbcli auth whoami(api_key.configured=true 即凭证就位;has_ticket 首次查询后为 true)',
41
+ ];
42
+ function probeBin() {
43
+ for (const bin of hbcliBinCandidates('hbcli')) {
44
+ const r = spawnSync(bin, ['version'], { timeout: 10_000, stdio: 'ignore' });
45
+ if (!r.error && r.status === 0)
46
+ return bin;
47
+ }
48
+ return null;
49
+ }
50
+ async function uatReachable() {
51
+ try {
52
+ await fetch(`${UAT}/api/search/destinations`, {
53
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
54
+ signal: AbortSignal.timeout(8_000),
55
+ });
56
+ return true; // 任何 HTTP 响应(含 401)都算可达
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ async function main() {
63
+ // This guard must stay before guidance-dependent probes, binary discovery,
64
+ // network reachability, STAICLI_HOME setup, and every credential operation.
65
+ // GOTRY_SESSION_LIVE intentionally does not control this supplier path.
66
+ if (process.env.GOTRY_HBCLI_LIVE !== '1') {
67
+ console.log('SKIP: HotelByte UAT 已关闭(默认离线);如需真实 UAT 请显式设置 GOTRY_HBCLI_LIVE=1——本路径未探测 hbcli/网络/凭证');
68
+ return;
69
+ }
70
+ console.log(GUIDANCE.join('\n'));
71
+ const bin = probeBin();
72
+ if (!bin) {
73
+ console.log(`SKIP: 未安装 hbcli(候选 ${hbcliBinCandidates('hbcli').join(' / ')} 均不可执行)——npx @danceiny/gotry setup 可按官方脚本安装`);
74
+ return;
75
+ }
76
+ if (!(await uatReachable())) {
77
+ console.log(`SKIP: UAT(${UAT})不可达——全流程端到端在有网环境跑,离线降级面已由 hbcli-tests.ts(§7)覆盖`);
78
+ return;
79
+ }
80
+ // 隔离凭证面:全程不触碰用户默认 ~/.staicli
81
+ const staicliHome = mkdtempSync(join(tmpdir(), 'gotry-hbcli-e2e-'));
82
+ const stateRoot = mkdtempSync(join(tmpdir(), 'gotry-hbcli-e2e-state-'));
83
+ process.env.STAICLI_HOME = staicliHome;
84
+ const fallbackPath = join(import.meta.dirname, '..', '..', 'data', 'hotels_2026.json');
85
+ try {
86
+ // 1) 沙箱账号写入隔离凭证面(hbcli auth set-credentials)
87
+ const set = spawnSync(bin, ['auth', 'set-credentials', '--app-key', SANDBOX_APP_KEY, '--app-secret', SANDBOX_APP_SECRET], { timeout: 30_000, encoding: 'utf-8' });
88
+ assert.equal(set.status, 0, `auth set-credentials 应成功,实际 ${set.status}: ${set.stderr?.slice(0, 200)}`);
89
+ console.log(`1. 沙箱账号写入隔离 STAICLI_HOME(${staicliHome.replace(tmpdir(), '$TMPDIR')})OK`);
90
+ // 1b) whoami 自检:api_key.configured=true(配置面就位,尚未取票)
91
+ const who = spawnSync(bin, ['auth', 'whoami'], { timeout: 30_000, encoding: 'utf-8' });
92
+ assert.equal(who.status, 0, 'whoami 应 exit 0');
93
+ assert.match(who.stdout, /"configured": true/, 'whoami 应报 api_key.configured=true');
94
+ console.log('1b. hbcli auth whoami 自检(api_key.configured=true)OK');
95
+ // 2) 能力层真实 spawn:目的地列表(通道真实性——UAT 参考数据可为空,通道必须真实)
96
+ const dest = await listDestinations({ timeoutMs: 30_000 });
97
+ assert.equal(dest.via, 'hbcli-realtime', `destinations 应走实时通道,实际 ${dest.via}(${dest.error ?? ''})`);
98
+ assert.equal(dest.exitCode, 0, 'destinations 应 exit 0');
99
+ assert.match(dest.evidence, /\[实时API:hbcli@\d{4}-/, '证据链应带实时时间戳标注(L4 不变量)');
100
+ assert.ok(dest.result !== null && typeof dest.result === 'object', '实时返回应可解析为 JSON');
101
+ console.log(`2. 能力层实时通道(listDestinations → ${UAT})via=hbcli-realtime,证据链 OK`);
102
+ // 3) hotel-list 全链路:UAT 无目的地参考数据 → 业务层 404 → 降级诚实性
103
+ // (真实失败不再是「工具坏了」,而是带上游原话的显式降级;issue #24 契约)
104
+ const hotels = await searchHotels({ destination: '深圳', checkIn: '2026-09-18', checkOut: '2026-09-20', adults: 2 }, { timeoutMs: 30_000, fallbackPath });
105
+ if (hotels.via === 'hbcli-error') {
106
+ assert.ok(hotels.error, '降级结果应携带上游原话原因');
107
+ assert.match(hotels.summary, /降级/, 'summary 应显式声明降级(不伪装成实时)');
108
+ assert.match(hotels.summary, /静态包/, '降级路径应指明静态包来源');
109
+ // 静态包含深圳块(data/hotels_2026.json)→ 降级应命中并标注「非实时」
110
+ assert.ok(hotels.hotels !== null, '静态包深圳块应命中');
111
+ assert.match(hotels.summary, /非实时/, '降级产物必须显式标注非实时(L4 反伪装红线)');
112
+ console.log(`3. hotel-list 真实降级链(via=hbcli-error,原因:${String(hotels.error).slice(0, 60)}…;静态包命中+非实时标注)OK`);
113
+ }
114
+ else {
115
+ // UAT 后续补齐目的地数据时走此分支:实时返回必须带证据链
116
+ assert.equal(hotels.via, 'hbcli-realtime');
117
+ assert.match(hotels.evidence, /\[实时API:hbcli@\d{4}-/);
118
+ console.log('3. hotel-list 实时返回(UAT 已有目的地数据)证据链 OK');
119
+ }
120
+ // 4) 效应解译层(ADR-18)真实通道:HBCLI_HOTEL_SEARCH 策略=永不重试+熔断在册;
121
+ // 独立 breakers Map,不污染产品单例
122
+ const itp = await makeProductionInterpreter({ breakers: new Map() })({
123
+ effect: 'HBCLI_HOTEL_SEARCH',
124
+ params: { destination: '深圳', fallbackPath, timeoutMs: 30_000 },
125
+ });
126
+ assert.equal(itp.trace.attempts, 1, `hbcli 策略永不重试,实际 attempts=${itp.trace.attempts}`);
127
+ assert.equal(itp.trace.channel, 'cli');
128
+ assert.ok(itp.trace.evidence[0]?.startsWith('[效应:HBCLI_HOTEL_SEARCH@'), 'trace 应带解译层横切证据');
129
+ assert.ok(itp.result !== null, '解译产物应原样透传渠道 observation(含降级形态)');
130
+ const viaAfter = itp.result.via;
131
+ assert.ok(viaAfter === 'hbcli-error' || viaAfter === 'hbcli-realtime', `渠道 via 应保真,实际 ${viaAfter}`);
132
+ console.log(`4. 效应解译层(attempts=1 永不重试,breaker=${itp.trace.breaker},via=${viaAfter})OK`);
133
+ // 5) 工具层端到端:gotry_hotel_search 经效应解译器跑完整链(隔离 stateRoot)
134
+ const { apply } = await import('../src/index.js');
135
+ const registered = [];
136
+ apply({ tools: { register: (t) => registered.push(t) } }, { stateRoot, timeoutMs: 30_000, hbcliBin: 'hbcli', sessionAccess: 'ask' });
137
+ const tool = registered.find(t => t.name === 'gotry_hotel_search');
138
+ assert.ok(tool, 'gotry_hotel_search 应已注册');
139
+ const obs = await tool.execute({ destination: '深圳', checkIn: '2026-09-18', checkOut: '2026-09-20' }, null);
140
+ assert.equal(obs.ok, true, '工具面应返回 ok 包络(永不抛错契约)');
141
+ assert.ok(obs.via === 'hbcli-error' || obs.via === 'hbcli-realtime', `工具面 via 应与渠道一致,实际 ${obs.via}`);
142
+ const ev = String(obs.evidence);
143
+ if (obs.via === 'hbcli-error') {
144
+ assert.match(ev, /静态包/, '降级时证据链应标注静态包(估算必须显式)');
145
+ }
146
+ else {
147
+ assert.match(ev, /实时API:hbcli/, '实时时证据链应标注 hbcli 时间戳');
148
+ }
149
+ assert.ok(String(obs.summary).length > 0, 'summary 应存在');
150
+ console.log(`5. 工具层端到端(gotry_hotel_search → 效应解译器 → ${UAT})via=${obs.via},evidence=${ev.slice(0, 46)}… OK`);
151
+ console.log('HBCLI E2E TESTS: 5/5 OK(隔离凭证面/实时通道/降级诚实/解译策略/工具面全链)');
152
+ }
153
+ finally {
154
+ process.env.STAICLI_HOME = undefined;
155
+ rmSync(staicliHome, { recursive: true, force: true });
156
+ rmSync(stateRoot, { recursive: true, force: true });
157
+ }
158
+ }
159
+ main().catch((e) => { console.error(e); process.exitCode = 1; });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Offline supplier opt-in gate proof for issue #318.
3
+ *
4
+ * Spawn the real hbcli-e2e entrypoint with a discoverable fixture binary and
5
+ * a blocked-network/read-write trap. The default entrypoint must return before
6
+ * any probe, so the fixture and traps must all remain untouched.
7
+ *
8
+ * This test never enables GOTRY_HBCLI_LIVE and never calls a supplier endpoint.
9
+ */
10
+ import assert from 'node:assert/strict';
11
+ import { chmod, mkdir, mkdtemp, readFile, writeFile, rm } from 'node:fs/promises';
12
+ import { spawnSync } from 'node:child_process';
13
+ import { tmpdir } from 'node:os';
14
+ import { dirname, join } from 'node:path';
15
+ const root = join(import.meta.dirname, '..', '..');
16
+ const e2e = join(root, 'ts', 'scripts', 'hbcli-e2e-tests.ts');
17
+ const fixtureRoot = await mkdtemp(join(tmpdir(), 'gotry-issue-318-'));
18
+ const fixtureHome = join(fixtureRoot, 'home');
19
+ const fixtureBin = join(fixtureHome, '.local', 'bin', 'hbcli');
20
+ const counters = join(fixtureRoot, 'counters');
21
+ const launchCounter = join(counters, 'binary-launches');
22
+ const networkCounter = join(counters, 'network-attempts');
23
+ const credentialCounter = join(counters, 'credential-reads-writes');
24
+ const trap = join(fixtureRoot, 'blocked-network-and-credential-trap.mjs');
25
+ await mkdir(dirname(fixtureBin), { recursive: true });
26
+ await mkdir(counters, { recursive: true });
27
+ await writeFile(fixtureBin, `#!/bin/sh
28
+ printf '1\\n' >> "$HBCLI_LAUNCH_COUNTER"
29
+ printf 'fixture hbcli must not be started in offline mode\\n' >&2
30
+ exit 97
31
+ `);
32
+ await chmod(fixtureBin, 0o755);
33
+ await writeFile(trap, `import fs from 'node:fs'
34
+ import { appendFileSync } from 'node:fs'
35
+ import { syncBuiltinESMExports } from 'node:module'
36
+
37
+ const networkCounter = ${JSON.stringify(networkCounter)}
38
+ const credentialCounter = ${JSON.stringify(credentialCounter)}
39
+ const credentialRoot = ${JSON.stringify(fixtureHome)}
40
+ const realFetch = globalThis.fetch
41
+ globalThis.fetch = async (...args) => {
42
+ appendFileSync(networkCounter, JSON.stringify(args[0]) + '\\n')
43
+ throw new Error('blocked network in issue #318 proof')
44
+ }
45
+ const isCredentialPath = (value) => String(value).startsWith(credentialRoot + '/.staicli')
46
+ const realReadFileSync = fs.readFileSync
47
+ fs.readFileSync = function (path, ...args) {
48
+ if (isCredentialPath(path)) appendFileSync(credentialCounter, 'read\\n')
49
+ return realReadFileSync.call(this, path, ...args)
50
+ }
51
+ const realWriteFileSync = fs.writeFileSync
52
+ fs.writeFileSync = function (path, ...args) {
53
+ if (isCredentialPath(path)) appendFileSync(credentialCounter, 'write\\n')
54
+ return realWriteFileSync.call(this, path, ...args)
55
+ }
56
+ syncBuiltinESMExports()
57
+ void realFetch
58
+ `);
59
+ try {
60
+ for (const optIn of [undefined, '0', 'true', 'yes']) {
61
+ const env = {
62
+ ...process.env,
63
+ HOME: fixtureHome,
64
+ PATH: `${dirname(fixtureBin)}:${process.env.PATH ?? ''}`,
65
+ NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ''} --import=${trap}`.trim(),
66
+ HBCLI_LAUNCH_COUNTER: launchCounter,
67
+ STAICLI_HOME: join(fixtureHome, '.staicli'),
68
+ };
69
+ if (optIn === undefined)
70
+ delete env.GOTRY_HBCLI_LIVE;
71
+ else
72
+ env.GOTRY_HBCLI_LIVE = optIn;
73
+ const result = spawnSync('npx', ['--no-install', 'tsx', e2e], {
74
+ cwd: join(root, 'ts'), encoding: 'utf8', env,
75
+ });
76
+ assert.equal(result.status, 0, `offline entrypoint should exit 0 for ${String(optIn)}, stderr=${result.stderr}`);
77
+ assert.match(result.stdout, /SKIP: HotelByte UAT 已关闭/);
78
+ assert.match(result.stdout, /未探测 hbcli\/网络\/凭证/);
79
+ }
80
+ for (const [path, label] of [
81
+ [launchCounter, 'fixture binary launches'],
82
+ [networkCounter, 'network attempts'],
83
+ [credentialCounter, 'credential reads/writes'],
84
+ ]) {
85
+ let observed = '';
86
+ try {
87
+ observed = await readFile(path, 'utf8');
88
+ }
89
+ catch { /* absent means zero */ }
90
+ assert.equal(observed, '', `${label} must remain zero, observed=${observed}`);
91
+ }
92
+ console.log('HBCLI LIVE OPT-IN PROOF: real entrypoint + discoverable fixture + blocked network; unset/0/invalid => binary=0/network=0/credential=0');
93
+ }
94
+ finally {
95
+ await rm(fixtureRoot, { recursive: true, force: true });
96
+ }
@@ -0,0 +1,122 @@
1
+ /**
2
+ * #232 staicli release contract (test-only):
3
+ * - verify a caller-supplied local npm tarball against the recorded bytes;
4
+ * - inspect and execute the actual packaged hbcli entrypoint's safe help surface;
5
+ * - prove the unsupported tenant selector is rejected by the actual parser;
6
+ * - reject a one-byte tampered artifact before extraction/execution.
7
+ *
8
+ * This script never downloads, runs npm lifecycle scripts, reads credentials, or
9
+ * calls HotelByte/supplier endpoints. It does not classify supplier outcomes;
10
+ * those require recorded supplier response fixtures and the runtime adapter
11
+ * remains outside this preparation slice.
12
+ *
13
+ * Usage:
14
+ * cd ts && npx tsx scripts/hbcli-release-contract-tests.ts /path/to/staicli-0.0.3.tgz
15
+ * STAICLI_TARBALL=/path/to/staicli-0.0.3.tgz npx tsx scripts/hbcli-release-contract-tests.ts
16
+ */
17
+ import assert from 'node:assert/strict';
18
+ import { createHash } from 'node:crypto';
19
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
20
+ import { spawnSync } from 'node:child_process';
21
+ import { tmpdir } from 'node:os';
22
+ import { dirname, join, resolve } from 'node:path';
23
+ import { fileURLToPath } from 'node:url';
24
+ const manifestPath = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'fixtures', 'staicli-0.0.3.release-contract.json');
25
+ const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
26
+ const tarballPath = process.argv[2] ?? process.env.STAICLI_TARBALL;
27
+ if (!tarballPath) {
28
+ console.log('SKIP: no local staicli tarball supplied; pass argv[2] or STAICLI_TARBALL for artifact-byte verification');
29
+ process.exit(0);
30
+ }
31
+ assert.equal(manifest.schema_version, 'gotry_staicli_release_contract.v1');
32
+ assert.ok(existsSync(tarballPath), `tarball must exist: ${tarballPath}`);
33
+ function digest(algorithm, bytes) {
34
+ return createHash(algorithm).update(bytes).digest(algorithm === 'sha512' ? 'base64' : 'hex');
35
+ }
36
+ function sha256Text(text) {
37
+ return digest('sha256', Buffer.from(text));
38
+ }
39
+ function verifyTarball(path) {
40
+ const bytes = readFileSync(path);
41
+ const actualIntegrity = `sha512-${digest('sha512', bytes)}`;
42
+ assert.equal(actualIntegrity, manifest.artifact.integrity, 'tarball bytes must match the recorded npm integrity');
43
+ assert.equal(digest('sha256', bytes), manifest.artifact.sha256, 'tarball bytes must match the recorded SHA-256');
44
+ return bytes;
45
+ }
46
+ function runNode(entry, argv, home) {
47
+ mkdirSync(home, { recursive: true });
48
+ const result = spawnSync(process.execPath, [entry, ...argv], {
49
+ cwd: home,
50
+ env: {
51
+ HOME: home,
52
+ STAICLI_HOME: join(home, 'staicli'),
53
+ PATH: process.env.PATH ?? '',
54
+ },
55
+ encoding: 'utf8',
56
+ timeout: 10_000,
57
+ killSignal: 'SIGKILL',
58
+ });
59
+ return { status: result.status, signal: result.signal, stdout: result.stdout ?? '', stderr: result.stderr ?? '', error: result.error };
60
+ }
61
+ function assertRecordedCommand(name, entry, command, home) {
62
+ const result = runNode(entry, command.argv, home);
63
+ assert.equal(result.error, undefined, `${name} spawn failed: ${result.error?.message ?? 'unknown error'}`);
64
+ assert.equal(result.signal, null, `${name} exceeded the 10s subprocess bound`);
65
+ assert.equal(result.status, command.exit_code, `${name} exit code must match recorded artifact evidence`);
66
+ assert.equal(sha256Text(result.stdout), command.stdout_sha256, `${name} stdout must match recorded artifact evidence`);
67
+ assert.equal(sha256Text(result.stderr), command.stderr_sha256, `${name} stderr must match recorded artifact evidence`);
68
+ for (const line of command.required_lines)
69
+ assert.ok(result.stdout.includes(line), `${name} help must contain: ${line}`);
70
+ }
71
+ const workspace = mkdtempSync(join(tmpdir(), 'gotry-staicli-contract-'));
72
+ const unpacked = join(workspace, 'unpacked');
73
+ const tamperedPath = join(workspace, 'tampered.tgz');
74
+ const isolatedHome = join(workspace, 'home');
75
+ const extractedPackage = join(unpacked, 'package');
76
+ mkdirSync(unpacked);
77
+ try {
78
+ // 1. The recorded integrity is checked against the actual supplied tarball bytes.
79
+ const originalBytes = verifyTarball(tarballPath);
80
+ console.log(`1. tarball bytes verified (${manifest.artifact.package.name}@${manifest.artifact.package.version}, ${manifest.artifact.integrity})`);
81
+ // 2. A one-byte corruption must fail the same verification before execution.
82
+ const corrupted = Buffer.from(originalBytes);
83
+ corrupted[corrupted.length - 1] ^= 1;
84
+ writeFileSync(tamperedPath, corrupted);
85
+ assert.throws(() => verifyTarball(tamperedPath), /tarball bytes must match the recorded npm integrity/);
86
+ console.log('2. one-byte tarball corruption rejected by integrity verification');
87
+ // 3. Extract with tar only; npm is not invoked, and package scripts cannot run.
88
+ const extraction = spawnSync('tar', ['-xzf', tarballPath, '-C', unpacked], {
89
+ encoding: 'utf8',
90
+ timeout: 10_000,
91
+ killSignal: 'SIGKILL',
92
+ });
93
+ assert.equal(extraction.error, undefined, `tar spawn failed: ${extraction.error?.message ?? 'unknown error'}`);
94
+ assert.equal(extraction.signal, null, 'tar extraction exceeded the 10s subprocess bound');
95
+ assert.equal(extraction.status, 0, `tar extraction failed: ${extraction.stderr ?? ''}`);
96
+ const packageJson = JSON.parse(readFileSync(join(extractedPackage, 'package.json'), 'utf8'));
97
+ assert.equal(packageJson.name, manifest.artifact.package.name);
98
+ assert.equal(packageJson.version, manifest.artifact.package.version);
99
+ assert.equal(packageJson.main, manifest.artifact.package.main);
100
+ assert.deepEqual(packageJson.bin, manifest.artifact.package.bin);
101
+ const entry = join(extractedPackage, packageJson.bin.hbcli);
102
+ assert.ok(existsSync(entry), `recorded hbcli bin must exist: ${entry}`);
103
+ console.log(`3. packaged identity/bin verified (${packageJson.name}@${packageJson.version} -> ${packageJson.bin.hbcli})`);
104
+ // 4. Execute only safe help commands with a fresh HOME and STAICLI_HOME.
105
+ for (const [name, command] of Object.entries(manifest.safe_help)) {
106
+ assertRecordedCommand(name, entry, command, join(isolatedHome, name));
107
+ console.log(`4.${name}. actual packaged help surface verified (exit ${command.exit_code})`);
108
+ }
109
+ // 5. The actual Commander parser, not a copied schema, rejects the unsupported selector.
110
+ const selector = runNode(entry, manifest.unsupported_selector.argv, join(isolatedHome, 'selector'));
111
+ assert.equal(selector.error, undefined, `selector parser spawn failed: ${selector.error?.message ?? 'unknown error'}`);
112
+ assert.equal(selector.signal, null, 'selector parser exceeded the 10s subprocess bound');
113
+ assert.equal(selector.status, manifest.unsupported_selector.exit_code);
114
+ assert.equal(sha256Text(selector.stderr), manifest.unsupported_selector.stderr_sha256);
115
+ assert.ok(selector.stderr.includes(manifest.unsupported_selector.stderr_contains));
116
+ assert.equal(selector.stdout, '');
117
+ console.log('5. actual parser rejects --tenant-entity-id without entering a supplier action');
118
+ }
119
+ finally {
120
+ rmSync(workspace, { recursive: true, force: true });
121
+ }
122
+ console.log('HBCLI RELEASE CONTRACT TESTS: 5/5 OK (artifact bytes/help/parser/tamper; no supplier or credential access)');
@@ -1,101 +1,153 @@
1
+ /**
2
+ * hbcli 封装单元测试:用临时脚本作为假 hbcli 二进制测三条路径。
3
+ * - 路径 1: 假 hbcli 回 exit 0 + JSON → via=hbcli-realtime
4
+ * - 路径 2: 假 hbcli exit 1 → via=hbcli-error + 证据链标注 error@ts
5
+ * - 路径 3: 不存在的 hbcli → via=hbcli-error + 证据链标注 spawn_error@ts
6
+ * 运行: cd ts && node --experimental-strip-types scripts/hbcli-tests.ts
7
+ */
1
8
  import assert from 'node:assert/strict';
2
9
  import { writeFile, mkdtemp, rm } from 'node:fs/promises';
3
10
  import { tmpdir } from 'node:os';
4
11
  import { join } from 'node:path';
5
- import { callHbcliJson, searchHotels } from '../capabilities/hbcli.js';
12
+ import { callHbcliJson, searchHotels, hbcliBinCandidates, customerSendCode, customerLogin } from '../capabilities/hbcli.js';
6
13
  const tmp = await mkdtemp(join(tmpdir(), 'hbcli-test-'));
7
14
  async function fakeBin(name, code, payload) {
8
15
  const p = join(tmp, name);
9
- await writeFile(p, `#!/bin/sh\necho '${payload}'\nexit ${code}\n`, {
10
- mode: 0o755
11
- });
16
+ await writeFile(p, `#!/bin/sh\necho '${payload}'\nexit ${code}\n`, { mode: 0o755 });
12
17
  return p;
13
18
  }
14
19
  const okBin = await fakeBin('hbcli-ok', 0, '{"hotels":[{"id":"h1","name":"x"}]}');
15
20
  const failBin = await fakeBin('hbcli-fail', 1, 'certificate has expired');
21
+ // 重写:失败时把 message 写到 stderr
16
22
  const failBin2 = join(tmp, 'hbcli-fail2');
17
- await writeFile(failBin2, `#!/bin/sh\nexit 1\n`, {
18
- mode: 0o755
19
- });
20
- const r1 = await callHbcliJson([
21
- 'search',
22
- 'hotel-list',
23
- '--json'
24
- ], {
25
- hbcliBin: okBin
26
- });
23
+ await writeFile(failBin2, `#!/bin/sh\nexit 1\n`, { mode: 0o755 });
24
+ // 1. 实时成功
25
+ const r1 = await callHbcliJson(['search', 'hotel-list', '--json'], { hbcliBin: okBin });
27
26
  assert.equal(r1.via, 'hbcli-realtime', 'happy path');
28
- assert.deepEqual(r1.result, {
29
- hotels: [
30
- {
31
- id: 'h1',
32
- name: 'x'
33
- }
34
- ]
35
- });
27
+ assert.deepEqual(r1.result, { hotels: [{ id: 'h1', name: 'x' }] });
36
28
  assert.match(r1.evidence, /^\[实时API:hbcli@/);
37
29
  assert.ok(r1.latencyMs >= 0);
38
- const r2 = await callHbcliJson([
39
- 'search',
40
- 'hotel-list',
41
- '--json'
42
- ], {
43
- hbcliBin: failBin2,
44
- timeoutMs: 5000
45
- });
30
+ // 2. 实时失败(exit 1)
31
+ const r2 = await callHbcliJson(['search', 'hotel-list', '--json'], { hbcliBin: failBin2, timeoutMs: 5000 });
46
32
  assert.equal(r2.via, 'hbcli-error', 'error path');
47
33
  assert.match(r2.evidence, /\[实时API:hbcli@error@/, `evidence got: ${r2.evidence}`);
48
34
  assert.equal(r2.exitCode, 1, '失败时 exitCode 透传');
49
- assert.ok(r2.error, '有 error 字段');
50
- const r3 = await callHbcliJson([
51
- 'search',
52
- 'hotel-list',
53
- '--json'
54
- ], {
55
- hbcliBin: '/nope/hbcli',
56
- timeoutMs: 5000
57
- });
35
+ assert.equal(r2.error, 'exit 1', '空 stderr 失败仍保留 exit code 文案');
36
+ // 3. 二进制不存在
37
+ const r3 = await callHbcliJson(['search', 'hotel-list', '--json'], { hbcliBin: '/nope/hbcli', timeoutMs: 5000 });
58
38
  assert.equal(r3.via, 'hbcli-error', 'no-binary path');
59
39
  assert.match(r3.evidence, /spawn_error/, `evidence got: ${r3.evidence}`);
40
+ // 4. searchHotels 高层降级:静态包按目的地过滤命中块(issue #24 契约,不整包倾倒)
60
41
  const fallback = join(tmp, 'hotels-fallback.json');
61
- await writeFile(fallback, JSON.stringify({
62
- meta: 'fake',
63
- stays: [
64
- {
65
- id: 's1'
66
- }
67
- ]
68
- }));
42
+ await writeFile(fallback, JSON.stringify({ meta: 'fake', stays: [{ id: 's1', note: '普吉岛 workation 两周(13 晚)' }, { id: 's2', note: '曼谷周末(2 晚)' }] }));
43
+ // v0.3.0 旗标回归:上游扁平化后旗标是 --destination-name/--room-occupancies
69
44
  {
70
45
  const { writeFileSync, chmodSync } = await import('node:fs');
71
46
  const echoBin = join(tmp, 'hbcli-echo');
72
47
  writeFileSync(echoBin, '#!/bin/sh\necho "ARGS:$@"; echo \'{}\' ');
73
48
  chmodSync(echoBin, 0o755);
74
- const re = await searchHotels({
75
- destination: '普吉',
76
- adults: 2
77
- }, {
78
- hbcliBin: echoBin
79
- });
49
+ const re = await searchHotels({ destination: '普吉', adults: 2 }, { hbcliBin: echoBin });
80
50
  const argLine = JSON.stringify(re);
81
51
  if (!argLine.includes('destination-name') || !argLine.includes('room-occupancies')) {
82
52
  throw new Error(`FAIL: v0.3.0 旗标未对齐,实际 ${argLine.slice(0, 200)}`);
83
53
  }
84
- console.log('5. v0.3.0 旗标(--destination-name/--room-occupancies)对齐 OK');
85
- }const r4 = await searchHotels({
86
- destination: '普吉'
87
- }, {
88
- hbcliBin: failBin2,
89
- fallbackPath: fallback
90
- });
54
+ // 日期旗标回归(issue #195):hotel-list 支持日期,不传会把实时价退化成当前窗口价
55
+ const rd = await searchHotels({ destination: '普吉', checkIn: '2026-09-18', checkOut: '2026-09-20' }, { hbcliBin: echoBin });
56
+ const dateLine = JSON.stringify(rd);
57
+ if (!dateLine.includes('check-in') || !dateLine.includes('2026-09-18') || !dateLine.includes('check-out')) {
58
+ throw new Error(`FAIL: 日期旗标未传上游,实际 ${dateLine.slice(0, 240)}`);
59
+ }
60
+ assert.ok(rd.summary.includes('入住 2026-09-18 → 退房 2026-09-20'), 'summary 应回显日期槽位');
61
+ console.log('5. 旗标对齐(--destination-name/--room-occupancies/--check-in/--check-out)OK');
62
+ }
63
+ // 5b. 实时 summary 自带数据行(issue #195:render 只透 summary,summary 必须含 id/名称/星级/价)
64
+ {
65
+ const { writeFileSync, chmodSync } = await import('node:fs');
66
+ const listBin = join(tmp, 'hbcli-list');
67
+ writeFileSync(listBin, `#!/bin/sh
68
+ cat <<'JSON'
69
+ {"list":[{"id":461850557,"name":{"en":"Jumeirah Beach Hotel","zh":"朱美拉海滩酒店"},"star":5,"minPrice":{"amount":100,"currency":"USD"}}],"basic":{"sessionId":"s1"}}
70
+ JSON
71
+ `);
72
+ chmodSync(listBin, 0o755);
73
+ const rl = await searchHotels({ destination: '迪拜', checkIn: '2026-09-18', checkOut: '2026-09-20' }, { hbcliBin: listBin });
74
+ assert.equal(rl.via, 'hbcli-realtime');
75
+ assert.ok(rl.summary.includes('实时 1 家'), 'summary 应带家数');
76
+ assert.ok(rl.summary.includes('id=461850557'), '数据行应带 hotelId(hotel-rates 入参来源)');
77
+ assert.ok(rl.summary.includes('朱美拉海滩酒店'), '数据行应带 zh 名称');
78
+ assert.ok(rl.summary.includes('5★'), '数据行应带星级');
79
+ assert.ok(rl.summary.includes('100 USD'), '数据行应带最低价');
80
+ assert.match(rl.summary, /\[实时API:hbcli@/, 'summary 应带证据链时间戳');
81
+ assert.ok(rl.hotels !== null, 'hotels 结构化产物保留(UI/程序消费)');
82
+ console.log(`5b. 实时 summary 数据行 OK:${rl.summary.split('\n')[1]}`);
83
+ }
84
+ const r4 = await searchHotels({ destination: '普吉岛' }, { hbcliBin: failBin2, fallbackPath: fallback });
91
85
  assert.equal(r4.via, 'hbcli-error', 'searchHotels: fails to hbcli → fallback');
92
86
  assert.equal(r4.summary.includes('降级到静态包'), true, 'summary 指明降级');
93
- assert.ok(r4.hotels, 'hotels 字段填上静态包内容');
94
- await rm(tmp, {
95
- recursive: true,
96
- force: true
97
- });
98
- console.log('HBCLI TESTS: 5/5 OK (happy / error / no-binary / fallback / v0.3.0 旗标回归)');
99
-
100
-
101
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/hbcli-tests.ts
87
+ assert.deepEqual(r4.hotels, { stays: [{ id: 's1', note: '普吉岛 workation 两周(13 晚)' }] }, 'hotels 只含目的地命中的住宿块');
88
+ assert.equal(r4.summary.includes('命中 1 个住宿块'), true, 'summary 指明命中块数');
89
+ // 5. 静态包无该目的地 → hotels=null 明示无数据,不伪装成可用结果
90
+ const r5 = await searchHotels({ destination: '巴黎' }, { hbcliBin: failBin2, fallbackPath: fallback });
91
+ assert.equal(r5.hotels ?? null, null, '无目的地命中时 hotels 为 null(不整包倾倒)');
92
+ assert.match(r5.summary, /无「巴黎」住宿数据/, 'summary 明示静态包无该目的地');
93
+ // 6. ENOENT 降级原因人话化(issue #24):hbcli 未安装时降级原因应带 gotry setup 指引
94
+ const r6 = await searchHotels({ destination: '普吉' }, { hbcliBin: '/nope/hbcli', fallbackPath: fallback });
95
+ assert.equal(r6.via, 'hbcli-error', 'no-binary path');
96
+ assert.ok(r6.summary.includes('未安装 hbcli'), `summary 应人话化 ENOENT,实际 ${r6.summary}`);
97
+ assert.ok(r6.summary.includes('gotry setup'), '应带 gotry setup 安装指引');
98
+ assert.ok(r6.summary.includes('降级到静态包'), '人话化后仍指明降级到静态包');
99
+ console.log(`6. ENOENT 人话化 OK:${r6.summary}`);
100
+ // 7. 候选路径回退(gotry setup 官方脚本装到 ~/.local/bin,PATH 可能不含):
101
+ // 默认名 'hbcli' 扩展已知安装位;显式自定义名不扩展(保配置即所用的可测性)
102
+ {
103
+ const home = '/home/t';
104
+ assert.deepEqual(hbcliBinCandidates('hbcli', home), ['hbcli', '/home/t/.local/bin/hbcli', '/home/t/.staicli/current/hbcli'], '默认名应带安装位候选');
105
+ assert.deepEqual(hbcliBinCandidates('hbcli-not-on-path', home), ['hbcli-not-on-path'], '自定义名单候选(测试确定性)');
106
+ assert.deepEqual(hbcliBinCandidates('/nope/hbcli', home), ['/nope/hbcli'], '绝对路径单候选');
107
+ console.log('7. hbcliBinCandidates(~/.local/bin + ~/.staicli/current 回退)OK');
108
+ }
109
+ // 8. 客户邮箱验证码登录两步流(hotelbyte-cli auth customer-*;上游旗标契约回归)
110
+ {
111
+ const { writeFileSync, chmodSync } = await import('node:fs');
112
+ const authEchoBin = join(tmp, 'hbcli-auth-echo');
113
+ writeFileSync(authEchoBin, `#!/bin/sh
114
+ echo "ARGS:$@"
115
+ echo '{"status":"sent","expiresIn":300}'
116
+ `);
117
+ chmodSync(authEchoBin, 0o755);
118
+ const rs = await customerSendCode('guest@mail.com', { hbcliBin: authEchoBin });
119
+ const sendLine = JSON.stringify(rs);
120
+ if (!sendLine.includes('customer-send-code') || !sendLine.includes('guest@mail.com')) {
121
+ throw new Error(`FAIL: customer-send-code 旗标未对齐,实际 ${sendLine.slice(0, 200)}`);
122
+ }
123
+ assert.equal(rs.via, 'hbcli-realtime', 'send-code happy path');
124
+ assert.ok(rs.summary.includes('验证码已发送到 guest@mail.com'), 'send summary 应带邮箱');
125
+ assert.ok(rs.summary.includes('300'), 'send summary 应带有效期');
126
+ const authLoginBin = join(tmp, 'hbcli-auth-login');
127
+ writeFileSync(authLoginBin, `#!/bin/sh
128
+ echo "ARGS:$@"
129
+ echo '{"status":"logged_in","token_saved":true,"attributionBound":true}'
130
+ `);
131
+ chmodSync(authLoginBin, 0o755);
132
+ const rl2 = await customerLogin({ email: 'guest@mail.com', code: '123456', attributionToken: 'v2.u.sig' }, { hbcliBin: authLoginBin });
133
+ const loginLine = JSON.stringify(rl2);
134
+ if (!loginLine.includes('customer-login') || !loginLine.includes('--code') || !loginLine.includes('123456') || !loginLine.includes('attribution-token')) {
135
+ throw new Error(`FAIL: customer-login 旗标未对齐,实际 ${loginLine.slice(0, 240)}`);
136
+ }
137
+ assert.equal(rl2.via, 'hbcli-realtime', 'login happy path');
138
+ assert.ok(rl2.summary.includes('登录成功'), 'login summary 应报成功');
139
+ assert.ok(rl2.summary.includes('顾问归因绑定成功'), 'login summary 应带归因结果');
140
+ // 可选旗标省略契约:不传 attributionToken/ttl 时不得出现在上游 argv
141
+ const rl3 = await customerLogin({ email: 'guest@mail.com', code: '654321' }, { hbcliBin: authLoginBin });
142
+ const loginLine3 = JSON.stringify(rl3);
143
+ if (loginLine3.includes('attribution-token') || loginLine3.includes('--ttl')) {
144
+ throw new Error(`FAIL: 可选旗标不应透传,实际 ${loginLine3.slice(0, 240)}`);
145
+ }
146
+ // 失败面:验证码一次性,失败 summary 必须引导重新发码
147
+ const rf = await customerLogin({ email: 'guest@mail.com', code: '000000' }, { hbcliBin: failBin2 });
148
+ assert.equal(rf.via, 'hbcli-error', 'login error path');
149
+ assert.ok(rf.summary.includes('重新发码'), `失败 summary 应引导重新发码,实际 ${rf.summary}`);
150
+ console.log('8. customer-send-code/customer-login 旗标契约 + 一次性验证码失败面 OK');
151
+ }
152
+ await rm(tmp, { recursive: true, force: true });
153
+ console.log('HBCLI TESTS: 9/9 OK (happy / error / no-binary / fallback-filter / fallback-no-match / v0.3.0 旗标回归 / ENOENT 人话化 / 候选路径 / customer 两步流)');