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

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 (507) 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 +1744 -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 +135 -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 +546 -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 +509 -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/resilience.js +191 -0
  41. package/dist/capabilities/session/action-cache.js +50 -44
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  43. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  44. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  45. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  46. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  47. package/dist/capabilities/session/benchmark.js +189 -0
  48. package/dist/capabilities/session/bridge-ledger.js +224 -0
  49. package/dist/capabilities/session/extension-bridge.js +636 -0
  50. package/dist/capabilities/session/extension-channel.js +109 -0
  51. package/dist/capabilities/session/extension-distribution.js +230 -0
  52. package/dist/capabilities/session/extract.js +31 -21
  53. package/dist/capabilities/session/golden-score.js +114 -0
  54. package/dist/capabilities/session/health-watch.js +215 -0
  55. package/dist/capabilities/session/read-guard.js +75 -24
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +97 -83
  58. package/dist/capabilities/session/wizard.js +117 -0
  59. package/dist/capabilities/session-consent.js +178 -0
  60. package/dist/capabilities/session-login.js +170 -0
  61. package/dist/capabilities/session-search.js +635 -44
  62. package/dist/capabilities/spawn-bounded.js +203 -0
  63. package/dist/capabilities/sponsor-plugin.js +341 -0
  64. package/dist/capabilities/visa-policy.js +140 -0
  65. package/dist/capabilities/weather.js +243 -138
  66. package/dist/data/session-golden-20.json +25 -0
  67. package/dist/data/sf-golden-manifest.json +103 -0
  68. package/dist/data/sf-static-routes.json +91 -0
  69. package/dist/scripts/action-cache-tests.js +24 -59
  70. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  71. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  72. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  73. package/dist/scripts/agent-reach-error-tests.js +354 -0
  74. package/dist/scripts/agent-reach-tests.js +26 -18
  75. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  76. package/dist/scripts/anything-tests.js +116 -60
  77. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  78. package/dist/scripts/artifacts-capability-tests.js +727 -0
  79. package/dist/scripts/async-collect.js +50 -11
  80. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  81. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  82. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  83. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  84. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  85. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  86. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  87. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  88. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  89. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  90. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  91. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
  92. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  93. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  94. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  95. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  96. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  97. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  98. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  99. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  100. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  101. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  102. package/dist/scripts/booking-executor-tests.js +80 -0
  103. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  104. package/dist/scripts/booking-saga-tests.js +125 -0
  105. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  106. package/dist/scripts/bootstrap-tests.js +1479 -0
  107. package/dist/scripts/bridge-events-tests.js +239 -0
  108. package/dist/scripts/bridge-ledger-tests.js +428 -0
  109. package/dist/scripts/build-changelog.js +238 -0
  110. package/dist/scripts/build-metrics-report.js +331 -0
  111. package/dist/scripts/changelog-tests.js +118 -0
  112. package/dist/scripts/channel-probe-tests.js +103 -0
  113. package/dist/scripts/channel-probe.js +169 -0
  114. package/dist/scripts/channel-registry-tests.js +208 -0
  115. package/dist/scripts/companion-tests.js +25 -70
  116. package/dist/scripts/dida-cookie-probe.js +87 -0
  117. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  118. package/dist/scripts/dida-sw-probe.js +114 -0
  119. package/dist/scripts/diff-test.js +17 -12
  120. package/dist/scripts/doctor-tests.js +564 -0
  121. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  122. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  123. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  124. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  125. package/dist/scripts/dsh-runtime-closure.js +162 -0
  126. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  127. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  128. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  129. package/dist/scripts/effect-tests.js +539 -0
  130. package/dist/scripts/engine-run.js +1 -3
  131. package/dist/scripts/engine-tests.js +16 -10
  132. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  133. package/dist/scripts/evaluation-contract-tests.js +268 -0
  134. package/dist/scripts/extension-distribution-cli.js +39 -0
  135. package/dist/scripts/extension-distribution-tests.js +443 -0
  136. package/dist/scripts/extension-tests.js +988 -0
  137. package/dist/scripts/external-event-tests.js +350 -0
  138. package/dist/scripts/fact-gate-tests.js +869 -0
  139. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  140. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  141. package/dist/scripts/flight-malformed-tests.js +159 -0
  142. package/dist/scripts/flyai-setup-tests.js +228 -0
  143. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  144. package/dist/scripts/flyai-tests.js +692 -0
  145. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  146. package/dist/scripts/fx-contract-tests.js +201 -0
  147. package/dist/scripts/g5-guard-tests.js +131 -0
  148. package/dist/scripts/g5-guard.js +189 -0
  149. package/dist/scripts/gen-lunar-anchors.js +106 -0
  150. package/dist/scripts/geo-atlas-tests.js +283 -0
  151. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  152. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  153. package/dist/scripts/gotry-try-demo.js +49 -0
  154. package/dist/scripts/ground-transfer-tests.js +956 -0
  155. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  156. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  157. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  158. package/dist/scripts/hbcli-tests.js +121 -69
  159. package/dist/scripts/health-watch-cli.js +73 -0
  160. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  161. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  162. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  163. package/dist/scripts/i18n-tests.js +41 -0
  164. package/dist/scripts/incident-tests.js +281 -87
  165. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  166. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  167. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  168. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  169. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  170. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  171. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  172. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  173. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  174. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  175. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  176. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  177. package/dist/scripts/itinerary-deck-tests.js +435 -0
  178. package/dist/scripts/itinerary-html-tests.js +426 -0
  179. package/dist/scripts/journey-tests.js +22 -27
  180. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  181. package/dist/scripts/kernel-manifest-gate.js +126 -0
  182. package/dist/scripts/kernel-manifest-tests.js +212 -0
  183. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  184. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  185. package/dist/scripts/kernel-manifest.js +369 -0
  186. package/dist/scripts/lavish-local-tests.js +1226 -0
  187. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  188. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  189. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  190. package/dist/scripts/ledger-tests.js +382 -246
  191. package/dist/scripts/ledger-workflow-crash.js +10 -20
  192. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  193. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  194. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  195. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  196. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  197. package/dist/scripts/memory-capture-tests.js +242 -61
  198. package/dist/scripts/memory-decay-tests.js +18 -52
  199. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  200. package/dist/scripts/memory-lifecycle.js +196 -0
  201. package/dist/scripts/memory-metrics.js +24 -12
  202. package/dist/scripts/memory-value-report.js +631 -0
  203. package/dist/scripts/memory-value-tests.js +328 -0
  204. package/dist/scripts/metrics-report-tests.js +151 -0
  205. package/dist/scripts/model-override-e2e.js +137 -0
  206. package/dist/scripts/nightly-evidence-tests.js +161 -0
  207. package/dist/scripts/nightly-evidence.js +286 -0
  208. package/dist/scripts/nudge-digest.js +39 -28
  209. package/dist/scripts/observability-tests.js +381 -0
  210. package/dist/scripts/onboarding-tests.js +161 -0
  211. package/dist/scripts/opensky-check.js +18 -13
  212. package/dist/scripts/opensky-tests.js +28 -28
  213. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  214. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  215. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  216. package/dist/scripts/price-drift-tests.js +340 -0
  217. package/dist/scripts/price-drift-watch.js +447 -0
  218. package/dist/scripts/probe-poi-tests.js +53 -45
  219. package/dist/scripts/product-metrics.js +483 -0
  220. package/dist/scripts/publish-preverify.js +110 -0
  221. package/dist/scripts/realtime-pricing-tests.js +76 -0
  222. package/dist/scripts/recall-tests.js +513 -0
  223. package/dist/scripts/replay-async.js +20 -18
  224. package/dist/scripts/replay-real.js +18 -18
  225. package/dist/scripts/replay.js +52 -82
  226. package/dist/scripts/session-attach-diagnose.js +20 -20
  227. package/dist/scripts/session-attach-poc.js +34 -37
  228. package/dist/scripts/session-benchmark.js +180 -0
  229. package/dist/scripts/session-dida-live-e2e.js +199 -0
  230. package/dist/scripts/session-extract-tests.js +14 -35
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +27 -46
  233. package/dist/scripts/session-tests.js +796 -96
  234. package/dist/scripts/session-zones-tests.js +358 -0
  235. package/dist/scripts/sf-live-benchmark.js +367 -0
  236. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  237. package/dist/scripts/sf-live-cli-tests.js +92 -0
  238. package/dist/scripts/sf-live-options.js +46 -0
  239. package/dist/scripts/sf-manifest-tests.js +30 -0
  240. package/dist/scripts/sf-manifest.js +33 -0
  241. package/dist/scripts/sf-soft-score-tests.js +67 -0
  242. package/dist/scripts/sf-summary-tests.js +324 -0
  243. package/dist/scripts/sf-summary.js +525 -0
  244. package/dist/scripts/share-tests.js +432 -0
  245. package/dist/scripts/skeleton-check.js +16 -14
  246. package/dist/scripts/skeleton-integration-test.js +28 -11
  247. package/dist/scripts/skills-contract-tests.js +51 -46
  248. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  249. package/dist/scripts/smoke.js +690 -246
  250. package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
  251. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  252. package/dist/scripts/state-cli-tests.js +226 -101
  253. package/dist/scripts/state-cli.js +550 -199
  254. package/dist/scripts/state-repair-cli.js +66 -0
  255. package/dist/scripts/state-repair-tests.js +140 -0
  256. package/dist/scripts/static-golden-tests.js +165 -0
  257. package/dist/scripts/time-eval-tests.js +210 -192
  258. package/dist/scripts/travel-timeline-tests.js +29 -93
  259. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  260. package/dist/scripts/turn-handoff-collect.js +161 -0
  261. package/dist/scripts/turn-policy-tests.js +55 -0
  262. package/dist/scripts/typed-contract-canary.js +176 -0
  263. package/dist/scripts/tz-resolver-sanity.js +50 -0
  264. package/dist/scripts/tz-resolver-tests.js +584 -0
  265. package/dist/scripts/unified-tests.js +23 -19
  266. package/dist/scripts/visa-policy-tests.js +62 -0
  267. package/dist/scripts/weather-tests.js +276 -41
  268. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  269. package/dist/scripts/write-gate-tests.js +522 -0
  270. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  271. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  272. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  273. package/dist/scripts/z3-race-tests.js +41 -0
  274. package/dist/src/artifact-gate.js +829 -0
  275. package/dist/src/backend/kernel.js +91 -0
  276. package/dist/src/backend/modules/booking-copilot.js +92 -0
  277. package/dist/src/backend/modules/booking-executor.js +124 -0
  278. package/dist/src/backend/modules/session-search.js +551 -0
  279. package/dist/src/benchmark-agent-conformance.js +578 -0
  280. package/dist/src/benchmark-environment-bridge.js +515 -0
  281. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  282. package/dist/src/benchmark-tool-isolation.js +151 -0
  283. package/dist/src/bookable-facts.js +533 -0
  284. package/dist/src/booking-saga.js +97 -0
  285. package/dist/src/booking-surface/availability-policy.js +697 -0
  286. package/dist/src/booking-surface/booking-intent.js +43 -0
  287. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  288. package/dist/src/booking-surface/canonical-schema.js +116 -0
  289. package/dist/src/booking-surface/contracts.js +75 -0
  290. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  291. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  292. package/dist/src/booking-surface/dsh-worker.js +98 -0
  293. package/dist/src/booking-surface/error-codes.js +231 -0
  294. package/dist/src/booking-surface/index.js +9 -0
  295. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  296. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  297. package/dist/src/booking-surface/profile.js +24 -0
  298. package/dist/src/booking-surface/recovery-chain.js +367 -0
  299. package/dist/src/booking-surface/runtime.js +1968 -0
  300. package/dist/src/booking-surface/server.js +674 -0
  301. package/dist/src/booking-surface/startup.js +168 -0
  302. package/dist/src/booking-surface/validation.js +261 -0
  303. package/dist/src/bridge.js +20 -13
  304. package/dist/src/companions.js +48 -74
  305. package/dist/src/contracts.js +21 -43
  306. package/dist/src/dsh-llm.js +97 -111
  307. package/dist/src/engine.js +276 -193
  308. package/dist/src/evaluation-cadence.js +97 -0
  309. package/dist/src/evaluation-contracts.js +283 -0
  310. package/dist/src/flight-pack-adapter.js +54 -0
  311. package/dist/src/flight-pack-contract.js +2 -0
  312. package/dist/src/flyai-setup-tool.js +70 -0
  313. package/dist/src/gotry-backend.js +125 -0
  314. package/dist/src/hotel-date-gate.js +118 -0
  315. package/dist/src/i18n.js +88 -0
  316. package/dist/src/index.js +2227 -882
  317. package/dist/src/itinerary-deck.js +139 -0
  318. package/dist/src/itinerary-doc-shared.js +661 -0
  319. package/dist/src/itinerary-html.js +131 -0
  320. package/dist/src/journey.js +155 -108
  321. package/dist/src/lavish-tools.js +400 -0
  322. package/dist/src/ledger-repair-apply.js +538 -0
  323. package/dist/src/ledger-repair-plan.js +463 -0
  324. package/dist/src/loop.js +319 -160
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +30 -14
  327. package/dist/src/memory-lifecycle.js +1310 -0
  328. package/dist/src/memory-utility.js +27 -40
  329. package/dist/src/mock-llm.js +36 -61
  330. package/dist/src/model.js +81 -36
  331. package/dist/src/realtime-pricing.js +103 -0
  332. package/dist/src/recall/card.js +74 -0
  333. package/dist/src/recall/evaluator.js +140 -0
  334. package/dist/src/recall/integration.js +17 -0
  335. package/dist/src/recall/tick.js +166 -0
  336. package/dist/src/session-link/plan-it-action.js +48 -0
  337. package/dist/src/session-link/session-link.js +248 -0
  338. package/dist/src/session-zones.js +507 -0
  339. package/dist/src/share/adapters.js +51 -0
  340. package/dist/src/share/share-consent.js +76 -0
  341. package/dist/src/share/share-deck.js +58 -0
  342. package/dist/src/share/share-token.js +146 -0
  343. package/dist/src/slot-spec.js +84 -106
  344. package/dist/src/state-ledger.js +435 -516
  345. package/dist/src/state-repair.js +299 -0
  346. package/dist/src/subagent-job-id-guard.js +58 -0
  347. package/dist/src/time-anchor.js +231 -33
  348. package/dist/src/tool-packet.js +20 -8
  349. package/dist/src/travel-slots.js +58 -35
  350. package/dist/src/travel-timeline.js +44 -63
  351. package/dist/src/turn-deadline.js +287 -0
  352. package/dist/src/turn-policy.js +118 -0
  353. package/dist/src/tz-resolver.js +143 -0
  354. package/dist/src/unified.js +535 -359
  355. package/dist/src/wish-pool.js +42 -12
  356. package/dist/src/write-gate.js +495 -0
  357. package/dist/src/z3-shared.js +284 -0
  358. package/extension/README.md +21 -0
  359. package/extension/README.zh-CN.md +21 -0
  360. package/extension/background.js +463 -0
  361. package/extension/content-bridge.js +270 -0
  362. package/extension/content-main.js +111 -0
  363. package/extension/manifest.json +60 -0
  364. package/package.json +407 -14
  365. package/schemas/booking.intent.schema.json +193 -0
  366. package/schemas/booking.surface.schema.json +2622 -0
  367. package/ts/capabilities/agent-reach-deep.ts +114 -0
  368. package/ts/capabilities/agent-reach.ts +198 -0
  369. package/ts/capabilities/anything.ts +255 -0
  370. package/ts/capabilities/artifacts.ts +428 -0
  371. package/ts/capabilities/fact-log.ts +54 -0
  372. package/ts/capabilities/flyai-config.ts +388 -0
  373. package/ts/capabilities/flyai.ts +1274 -0
  374. package/ts/capabilities/hbcli.ts +253 -71
  375. package/ts/capabilities/incident-log.ts +88 -59
  376. package/ts/capabilities/itinerary-artifact.ts +214 -0
  377. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  378. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  379. package/ts/capabilities/opensky.ts +121 -0
  380. package/ts/capabilities/session/action-cache.ts +148 -0
  381. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  382. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  383. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  384. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  385. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  386. package/ts/capabilities/session/benchmark.ts +276 -0
  387. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  388. package/ts/capabilities/session/extension-bridge.ts +794 -0
  389. package/ts/capabilities/session/extension-channel.ts +144 -0
  390. package/ts/capabilities/session/extension-distribution.ts +266 -0
  391. package/ts/capabilities/session/extract.ts +54 -0
  392. package/ts/capabilities/session/golden-score.ts +139 -0
  393. package/ts/capabilities/session/health-watch.ts +264 -0
  394. package/ts/capabilities/session/read-guard.ts +141 -0
  395. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  396. package/ts/capabilities/session/transport.ts +146 -0
  397. package/ts/capabilities/session/wizard.ts +140 -0
  398. package/ts/capabilities/session-consent.ts +211 -0
  399. package/ts/capabilities/session-login.ts +208 -0
  400. package/ts/capabilities/session-search.ts +893 -0
  401. package/ts/capabilities/weather.ts +330 -0
  402. package/ts/dsh-runtime/vendor/README.md +68 -0
  403. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  404. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  405. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  406. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  407. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  408. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  409. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  410. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  411. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  412. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  413. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  414. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  415. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  416. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  417. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  418. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  419. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  420. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  421. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  422. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  423. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  424. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  425. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  426. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  427. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  428. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  440. package/ts/package.json +293 -3
  441. package/ts/scripts/async-collect.ts +91 -0
  442. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  443. package/ts/scripts/skeleton-check.ts +1 -1
  444. package/ts/scripts/skeleton-integration-test.ts +23 -1
  445. package/ts/scripts/state-cli.ts +568 -0
  446. package/ts/scripts/turn-handoff-collect.ts +178 -0
  447. package/ts/src/artifact-gate.ts +991 -0
  448. package/ts/src/benchmark-agent-conformance.ts +619 -0
  449. package/ts/src/benchmark-environment-bridge.ts +538 -0
  450. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  451. package/ts/src/benchmark-tool-isolation.ts +166 -0
  452. package/ts/src/bookable-facts.ts +837 -0
  453. package/ts/src/booking-saga.ts +133 -0
  454. package/ts/src/booking-surface/availability-policy.ts +522 -0
  455. package/ts/src/booking-surface/booking-intent.ts +86 -0
  456. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  457. package/ts/src/booking-surface/canonical-schema.js +116 -0
  458. package/ts/src/booking-surface/contracts.ts +475 -0
  459. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  460. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  461. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  462. package/ts/src/booking-surface/error-codes.ts +241 -0
  463. package/ts/src/booking-surface/index.ts +9 -0
  464. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  465. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  466. package/ts/src/booking-surface/profile.ts +42 -0
  467. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  468. package/ts/src/booking-surface/runtime.ts +1793 -0
  469. package/ts/src/booking-surface/server.ts +733 -0
  470. package/ts/src/booking-surface/startup.ts +201 -0
  471. package/ts/src/booking-surface/validation.ts +217 -0
  472. package/ts/src/companions.ts +113 -0
  473. package/ts/src/contracts.ts +13 -3
  474. package/ts/src/dsh-llm.ts +44 -11
  475. package/ts/src/flyai-setup-tool.ts +77 -0
  476. package/ts/src/index.ts +1948 -198
  477. package/ts/src/itinerary-deck.ts +189 -0
  478. package/ts/src/itinerary-doc-shared.ts +741 -0
  479. package/ts/src/itinerary-html.ts +171 -0
  480. package/ts/src/loop.ts +223 -25
  481. package/ts/src/memory-capture.ts +283 -0
  482. package/ts/src/memory-decay.ts +55 -0
  483. package/ts/src/memory-utility.ts +70 -0
  484. package/ts/src/mock-llm.ts +4 -8
  485. package/ts/src/model.ts +88 -2
  486. package/ts/src/recall/card.ts +106 -0
  487. package/ts/src/recall/evaluator.ts +170 -0
  488. package/ts/src/recall/integration.ts +35 -0
  489. package/ts/src/recall/tick.ts +213 -0
  490. package/ts/src/session-link/plan-it-action.ts +78 -0
  491. package/ts/src/session-link/session-link.ts +275 -0
  492. package/ts/src/share/adapters.ts +93 -0
  493. package/ts/src/share/share-consent.ts +105 -0
  494. package/ts/src/share/share-deck.ts +85 -0
  495. package/ts/src/share/share-token.ts +166 -0
  496. package/ts/src/slot-spec.ts +172 -0
  497. package/ts/src/state-ledger.ts +912 -0
  498. package/ts/src/subagent-job-id-guard.ts +72 -0
  499. package/ts/src/time-anchor.ts +338 -0
  500. package/ts/src/tool-packet.ts +32 -0
  501. package/ts/src/travel-slots.ts +219 -0
  502. package/ts/src/travel-timeline.ts +83 -0
  503. package/ts/src/turn-deadline.ts +366 -0
  504. package/ts/src/turn-policy.ts +154 -0
  505. package/ts/src/unified.ts +354 -88
  506. package/ts/src/wish-pool.ts +79 -0
  507. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,62 @@
1
+ /**
2
+ * 政策事实生产端 v1 测试(issue #141,D-26 收口切片;冻结泰国页 fixture,零网络):
3
+ *
4
+ * 1. extractVisaSection:「签证入境」章节抽取(免签/落地签条款在案;缺章节=null)
5
+ * 2. splitPolicyEntries:中文序号段落切分(一、二、…),短段过滤
6
+ * 3. policyFactsFromMfa:PolicyFact 形态完整(schema/fact_id/as_of/review_by=D+30/来源证据链);幂等
7
+ * 4. VISA_POLICY_FETCH 效应行:策略表行在案(永不重试+断路器护站);注入 handler 走抓取编排
8
+ * 5. listCountryPaths:洲列表页国家路径提取(仅本站相对链接)
9
+ *
10
+ * 运行(在 ts/ 下):npx tsx scripts/visa-policy-tests.ts
11
+ */
12
+ import assert from 'node:assert/strict';
13
+ import { readFileSync } from 'node:fs';
14
+ import { dirname, join } from 'node:path';
15
+ import { fileURLToPath } from 'node:url';
16
+ import { splitPolicyEntries, continentListUrl, countryPageUrl, extractVisaSection, policyFactsFromMfa, } from '../capabilities/visa-policy.js';
17
+ import { makeProductionInterpreter, __resetEffectBreakersForTest } from '../capabilities/effect.js';
18
+ const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
19
+ const FIXTURE = join(ROOT, 'data', 'fixtures', 'visa-policy-tg-sample.html');
20
+ let passed = 0;
21
+ function check(cond, msg) {
22
+ if (cond) {
23
+ passed++;
24
+ console.log(` ok - ${msg}`);
25
+ }
26
+ else {
27
+ console.error(` FAIL - ${msg}`);
28
+ process.exitCode = 1;
29
+ }
30
+ }
31
+ const html = readFileSync(FIXTURE, 'utf-8');
32
+ // 1. 章节抽取
33
+ const section = extractVisaSection(html);
34
+ check(!!section && section.includes('免签'), '签证入境章节应被抽取(免签条款在案)');
35
+ check(section.includes('2024'), '生效日期应保留(逐段溯源)');
36
+ // 2. 条目切分
37
+ const entries = splitPolicyEntries(section);
38
+ check(entries.length >= 2, `中文序号段落切分(实际 ${entries.length} 条)`);
39
+ check(entries.every(e => e.length > 30), '条目长度下限(短段过滤)');
40
+ // 3. PolicyFact 形态
41
+ const fetchedAt = '2026-09-05T00:00:00.000Z';
42
+ const facts = policyFactsFromMfa({ countryLabel: '泰国', countryPath: 'tg_647570', section: section, fetchedAt });
43
+ check(facts.length === entries.length, '事实数=条目数');
44
+ check(facts.every(f => f.kind === 'policy' && f.schema === 'gotry_bookable_fact.v1'), 'PolicyFact 形态');
45
+ check(facts.every(f => f.as_of === '2026-09-05' && f.review_by === '2026-10-05'), 'as_of 抓取日 + review_by=D+30');
46
+ check(facts.every(f => f.source.startsWith('cs-mfa@') && f.query_id.startsWith('visa-policy:cs-mfa:')), '来源证据链 cs.mfa.gov.cn');
47
+ // 幂等:同输入重跑 fact_id 稳定
48
+ const facts2 = policyFactsFromMfa({ countryLabel: '泰国', countryPath: 'tg_647570', section: section, fetchedAt });
49
+ check(facts.every((f, i) => f.fact_id === facts2[i].fact_id), 'fact_id 幂等');
50
+ // 4. URL 形态受控
51
+ assert.equal(countryPageUrl('yz_645708', 'tg_647570'), 'https://cs.mfa.gov.cn/zggmcg/ljmdd/yz_645708/tg_647570/', 'URL 形态');
52
+ assert.equal(countryPageUrl('..', 'x'), null, '路径穿越拒绝');
53
+ assert.equal(continentListUrl('..'), null, '洲路径穿越拒绝');
54
+ // 5. 效应行:VISA_POLICY_FETCH 在注册表(永不重试+断路器护站)
55
+ __resetEffectBreakersForTest();
56
+ const interp = makeProductionInterpreter({ sleep: async () => { }, breakers: new Map(), handlers: {
57
+ VISA_POLICY_FETCH: async () => ({ ok: true, via: 'cs-mfa', evidence: 'e', latencyMs: 1, facts: [], countries: ['tg_647570'] }),
58
+ } });
59
+ const out = await interp({ effect: 'VISA_POLICY_FETCH', params: { country: 'tg_647570' } });
60
+ assert.equal(out.trace.channel, 'api', 'trace.channel=api(策略表行在案)');
61
+ assert.equal(out.trace.attempts, 1, '礼貌抓取:单次执行(策略表 retry=null 生效)');
62
+ console.log(`\nVISA POLICY TESTS: ${passed} pass${process.exitCode ? ', FAIL' : ' (全绿)'}`);
@@ -1,48 +1,283 @@
1
+ /**
2
+ * 天气能力层确定性回归:受控 fetch fixture 覆盖行为和时间预算。
3
+ *
4
+ * 真实 Open-Meteo/Nominatim 观测不参与合并闸;本套件必须离线、快速、可重复。
5
+ */
1
6
  import assert from 'node:assert/strict';
2
7
  import { geocodePlace, getForecast, getClimate, wmoLabel } from '../capabilities/weather.js';
3
- const geo = await geocodePlace('大理市', {
4
- count: 10
8
+ const jsonResponse = (body, status = 200) => new Response(JSON.stringify(body), {
9
+ status, headers: { 'content-type': 'application/json' },
5
10
  });
6
- assert.equal(geo.ok, true, `geocode ok: ${geo.error ?? ''}`);
7
- assert.ok(geo.results.length > 0, '应有结果');
8
- const dali = geo.results.find((r)=>(r.admin1 ?? '').includes('云南')) ?? geo.results[0];
9
- assert.ok(Math.abs(dali.latitude - 25.6) < 0.5, `纬度应≈25.6,实际 ${dali.latitude}(${dali.name},${dali.admin1})`);
10
- assert.match(geo.evidence, /open-meteo-geo@2/, '证据链带时间戳');
11
- console.log(`1. geocode 大理市 → ${dali.latitude},${dali.longitude} (${dali.name},${dali.admin1}) OK`);
12
- const fc = await getForecast({
13
- latitude: dali.latitude,
14
- longitude: dali.longitude
15
- }, {
16
- days: 7
11
+ const geoDali = { results: [{ name: '大理市', latitude: 25.6, longitude: 100.2, country: '中国', admin1: '云南省', population: 600000, feature_code: 'PPLA' }] };
12
+ const geoPhuket = [{ name: 'Phuket', lat: '7.88', lon: '98.39', address: { country: '泰国', province: '普吉府' } }];
13
+ const forecastBody = (days) => ({
14
+ timezone: 'Asia/Shanghai',
15
+ daily: {
16
+ time: Array.from({ length: days }, (_, i) => `2026-09-${String(i + 1).padStart(2, '0')}`),
17
+ temperature_2m_max: Array.from({ length: days }, () => 30),
18
+ temperature_2m_min: Array.from({ length: days }, () => 21),
19
+ precipitation_probability_max: Array.from({ length: days }, () => 60),
20
+ weathercode: Array.from({ length: days }, () => 61),
21
+ },
17
22
  });
18
- assert.equal(fc.ok, true, `forecast ok: ${fc.error ?? ''}`);
19
- assert.equal(fc.daily.length, 7, '7 天');
20
- assert.ok(fc.daily[0].tempMaxC > -50 && fc.daily[0].tempMaxC < 60, '温度合理范围');
21
- assert.match(fc.evidence, /\[实时API:open-meteo@2/, '证据链');
22
- assert.ok(fc.latencyMs < 5000, `延迟应 <5s,实际 ${fc.latencyMs}ms`);
23
- console.log(`2. forecast 大理 7 天:${fc.daily[0].tempMinC}–${fc.daily[0].tempMaxC}°C ${wmoLabel(fc.daily[0].weatherCode)} OK`);
24
- const cl = await getClimate({
25
- latitude: dali.latitude,
26
- longitude: dali.longitude
27
- }, 8);
28
- assert.equal(cl.ok, true, `climate ok: ${cl.error ?? ''}`);
29
- assert.ok(cl.daily.length >= 28, '8 月应有 ≥28 天');
30
- assert.match(cl.evidence, /open-meteo-climate@/, '气候证据链');
31
- console.log(`3. climate 大理去年 8 月:${cl.daily.length} 天 OK`);
32
- const bad = await getForecast({
33
- latitude: 999,
34
- longitude: 0
35
- }, {
36
- timeoutMs: 10_000
23
+ const climateBodyFor = (year, month) => {
24
+ const days = new Date(Date.UTC(year, month, 0)).getUTCDate();
25
+ const monthText = String(month).padStart(2, '0');
26
+ return { daily: { time: Array.from({ length: days }, (_, i) => `${year}-${monthText}-${String(i + 1).padStart(2, '0')}`), temperature_2m_max: Array(days).fill(29), temperature_2m_min: Array(days).fill(21), weathercode: Array(days).fill(2) } };
27
+ };
28
+ const climateBody = climateBodyFor(2025, 8);
29
+ const mockFetch = (handler) => async (input, init) => jsonResponse(await handler(String(input), init?.signal ?? new AbortController().signal));
30
+ // 1. 地理编码主源命中 + schema/别名正常路径
31
+ const geo = await geocodePlace('大理市', { count: 10, fetchImpl: mockFetch(() => geoDali) });
32
+ assert.equal(geo.ok, true);
33
+ assert.equal(geo.via, 'open-meteo');
34
+ assert.ok(Math.abs(geo.results[0].latitude - 25.6) < 0.5);
35
+ assert.match(geo.evidence, /open-meteo-geo@2/);
36
+ console.log('1. geocode 主源命中 + alias/schema OK');
37
+ // 2. 零结果进入 Nominatim 兜底,并保持正确国家/坐标
38
+ const seenRequests = [];
39
+ const phuket = await geocodePlace('普吉岛', { count: 3, fetchImpl: async (input, init) => {
40
+ seenRequests.push({ url: String(input), headers: new Headers(init?.headers) });
41
+ return jsonResponse(String(input).includes('nominatim') ? geoPhuket : { results: [] });
42
+ } });
43
+ assert.equal(phuket.ok, true);
44
+ assert.equal(phuket.via, 'nominatim');
45
+ assert.match(phuket.results[0].country ?? '', /泰国/);
46
+ assert.ok(Math.abs(phuket.results[0].latitude - 8) < 0.5);
47
+ assert.match(seenRequests[0].url, /name=%E6%99%AE%E5%90%89%E5%B2%9B.*count=3.*language=zh/);
48
+ assert.match(seenRequests[1].url, /q=%E6%99%AE%E5%90%89%E5%B2%9B.*limit=3.*accept-language=zh/);
49
+ assert.match(seenRequests[1].headers.get('user-agent') ?? '', /gotry-travel-agent/);
50
+ const badAddress = await geocodePlace('坏地址', { fetchImpl: mockFetch((url) => url.includes('nominatim') ? [{ name: 'X', lat: '1', lon: '2', address: { country: {}, province: 7 } }] : { results: [] }) });
51
+ assert.equal(badAddress.ok, true);
52
+ assert.equal('country' in badAddress.results[0], false);
53
+ assert.equal('admin1' in badAddress.results[0], false);
54
+ console.log('2. geocode 兜底链 + 地名 alias OK');
55
+ // 3. 预报正常 schema
56
+ const fc = await getForecast({ latitude: 25.6, longitude: 100.2 }, { days: 7, fetchImpl: mockFetch(() => forecastBody(7)) });
57
+ assert.equal(fc.ok, true);
58
+ assert.equal(fc.daily?.length, 7);
59
+ assert.equal(fc.daily?.[0]?.weatherCode, 61);
60
+ assert.ok(fc.latencyMs < 1000);
61
+ console.log('3. forecast schema/行为 OK');
62
+ // 4. 慢 forecast 必须在受控 timeout 内 graceful fallback(不抛错)
63
+ const slow = mockFetch((_url, signal) => new Promise((_, reject) => {
64
+ const abort = () => reject(new Error('This operation was aborted'));
65
+ if (signal.aborted)
66
+ abort();
67
+ else
68
+ signal.addEventListener('abort', abort, { once: true });
69
+ setTimeout(() => reject(new Error('late response')), 250);
70
+ }));
71
+ const slowStarted = Date.now();
72
+ const slowFc = await getForecast({ latitude: 25.6, longitude: 100.2 }, { timeoutMs: 25, fetchImpl: slow });
73
+ assert.equal(slowFc.ok, false);
74
+ assert.ok(Date.now() - slowStarted < 150, 'slow forecast must respect timeout');
75
+ assert.match(slowFc.evidence, /error/);
76
+ const never = mockFetch(() => new Promise(() => undefined));
77
+ const neverStarted = Date.now();
78
+ const neverFc = await getForecast({ latitude: 25.6, longitude: 100.2 }, { timeoutMs: 20, fetchImpl: never });
79
+ assert.equal(neverFc.ok, false);
80
+ assert.ok(Date.now() - neverStarted < 150, 'hard deadline must cover a fetch that ignores abort');
81
+ const neverBody = await getForecast({ latitude: 25.6, longitude: 100.2 }, { timeoutMs: 20, fetchImpl: async () => ({ ok: true, json: () => new Promise(() => undefined) }) });
82
+ assert.equal(neverBody.ok, false, 'hard deadline must cover a body decoder that never settles');
83
+ let lateResolve;
84
+ const lateResolveResult = await getForecast({ latitude: 25.6, longitude: 100.2 }, {
85
+ timeoutMs: 10,
86
+ fetchImpl: async () => ({ ok: true, json: () => new Promise(resolve => { lateResolve = resolve; }) }),
37
87
  });
38
- assert.equal(bad.ok, false, '非法输入应返回 ok=false');
39
- assert.ok(bad.evidence.includes('error'), '降级证据标注');
40
- console.log(`4. 降级 ok=false 不抛错 OK`);
88
+ assert.equal(lateResolveResult.ok, false);
89
+ lateResolve?.(forecastBody(7));
90
+ let lateReject;
91
+ const lateRejectResult = await getForecast({ latitude: 25.6, longitude: 100.2 }, {
92
+ timeoutMs: 10,
93
+ fetchImpl: async () => ({ ok: true, json: () => new Promise((_, reject) => { lateReject = reject; }) }),
94
+ });
95
+ assert.equal(lateRejectResult.ok, false);
96
+ lateReject?.(Object.create(null));
97
+ await new Promise(resolve => setImmediate(resolve));
98
+ console.log('4. slow forecast timeout + graceful fallback OK');
99
+ // 5. 双源 fallback 的总预算:主源耗尽预算时不再发第二个请求。
100
+ let calls = 0;
101
+ const slowBoth = mockFetch((_url, signal) => {
102
+ calls += 1;
103
+ return new Promise((_, reject) => {
104
+ const abort = () => reject(new Error('network timeout'));
105
+ if (signal.aborted)
106
+ abort();
107
+ else
108
+ signal.addEventListener('abort', abort, { once: true });
109
+ });
110
+ });
111
+ const chainStarted = Date.now();
112
+ const chain = await geocodePlace('不存在的地方', { timeoutMs: 35, fetchImpl: slowBoth });
113
+ assert.equal(chain.ok, false);
114
+ assert.equal(calls, 1, 'an exhausted primary budget must not start fallback');
115
+ assert.ok(Date.now() - chainStarted < 150, 'fallback chain must share one total budget');
116
+ assert.match(chain.evidence, /open-meteo-geo@error/);
117
+ let fastPrimaryCalls = 0;
118
+ let fastFallbackCalls = 0;
119
+ const fallbackAfterFastFailure = await geocodePlace('不存在的地方', { timeoutMs: 100, fetchImpl: async (input, init) => {
120
+ if (String(input).includes('nominatim')) {
121
+ fastFallbackCalls += 1;
122
+ return jsonResponse([]);
123
+ }
124
+ fastPrimaryCalls += 1;
125
+ throw new Error('ECONNRESET');
126
+ } });
127
+ assert.equal(fallbackAfterFastFailure.ok, false);
128
+ assert.equal(fastPrimaryCalls, 1, 'primary should be attempted once');
129
+ assert.equal(fastFallbackCalls, 1, 'a fast primary failure should still attempt fallback once');
130
+ const network = await getForecast({ latitude: 25.6, longitude: 100.2 }, { fetchImpl: mockFetch(() => { throw new Error('ECONNRESET'); }) });
131
+ assert.equal(network.ok, false);
132
+ assert.match(network.error ?? '', /ECONNRESET/);
133
+ const httpError = await getForecast({ latitude: 25.6, longitude: 100.2 }, { fetchImpl: async () => jsonResponse({}, 503) });
134
+ assert.equal(httpError.ok, false);
135
+ assert.match(httpError.error ?? '', /HTTP 503/);
136
+ const malformedGeo = await geocodePlace('坏响应', { timeoutMs: 100, fetchImpl: mockFetch((url) => url.includes('nominatim') ? {} : { results: {} }) });
137
+ assert.equal(malformedGeo.ok, false);
138
+ console.log('5. slow geocode + strict fallback budget + network/HTTP errors OK');
139
+ // 6. climate schema、malformed schema、WMO mapping
140
+ const cl = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { year: 2025, fetchImpl: mockFetch(() => climateBody) });
141
+ assert.equal(cl.ok, true);
142
+ assert.equal(cl.daily?.length, 31);
143
+ const malformed = await getForecast({ latitude: 25.6, longitude: 100.2 }, { days: 7, fetchImpl: mockFetch(() => ({ daily: { time: [] } })) });
144
+ assert.equal(malformed.ok, false);
145
+ const malformedElement = await getForecast({ latitude: 25.6, longitude: 100.2 }, { days: 7, fetchImpl: mockFetch(() => ({ daily: { ...forecastBody(7).daily, temperature_2m_max: [30, 'bad', 30, 30, 30, 30, 30] } })) });
146
+ assert.equal(malformedElement.ok, false);
147
+ const wrongPrecip = await getForecast({ latitude: 25.6, longitude: 100.2 }, { days: 7, fetchImpl: mockFetch(() => ({ daily: { ...forecastBody(7).daily, precipitation_probability_max: 'bad' } })) });
148
+ assert.equal(wrongPrecip.ok, false);
149
+ const wrongPrecipObject = await getForecast({ latitude: 25.6, longitude: 100.2 }, { days: 7, fetchImpl: mockFetch(() => ({ daily: { ...forecastBody(7).daily, precipitation_probability_max: {} } })) });
150
+ assert.equal(wrongPrecipObject.ok, false);
151
+ const absentPrecip = { daily: { ...forecastBody(7).daily } };
152
+ delete absentPrecip.daily.precipitation_probability_max;
153
+ const forecastAbsentPrecip = await getForecast({ latitude: 25.6, longitude: 100.2 }, { fetchImpl: mockFetch(() => absentPrecip) });
154
+ assert.equal(forecastAbsentPrecip.ok, true);
155
+ assert.equal(forecastAbsentPrecip.daily?.[0]?.precipProbMaxPct, null);
156
+ const forecastNullPrecip = await getForecast({ latitude: 25.6, longitude: 100.2 }, { fetchImpl: mockFetch(() => ({ daily: { ...forecastBody(7).daily, precipitation_probability_max: null } })) });
157
+ assert.equal(forecastNullPrecip.ok, true);
158
+ assert.equal(forecastNullPrecip.daily?.[0]?.precipProbMaxPct, null);
159
+ const forecastValidPrecip = await getForecast({ latitude: 25.6, longitude: 100.2 }, { fetchImpl: mockFetch(() => forecastBody(7)) });
160
+ assert.equal(forecastValidPrecip.ok, true);
161
+ assert.equal(forecastValidPrecip.daily?.[0]?.precipProbMaxPct, 60);
162
+ const nullClimate = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { fetchImpl: mockFetch(() => null) });
163
+ assert.equal(nullClimate.ok, false);
164
+ const slowClimate = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { timeoutMs: 20, fetchImpl: slow });
165
+ assert.equal(slowClimate.ok, false);
166
+ const shortClimate = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { fetchImpl: mockFetch(() => ({ daily: { ...climateBody.daily, time: ['2025-08-01'], temperature_2m_max: [29], temperature_2m_min: [21], weathercode: [2] } })) });
167
+ assert.equal(shortClimate.ok, false);
168
+ for (const badDays of [0, Number.NaN, 2.5]) {
169
+ let fetches = 0;
170
+ const invalid = await getForecast({ latitude: 25.6, longitude: 100.2 }, { days: badDays, fetchImpl: mockFetch(() => { fetches += 1; return forecastBody(7); }) });
171
+ assert.equal(invalid.ok, false);
172
+ assert.equal(fetches, 0);
173
+ }
174
+ for (const badTimeout of [0, -1, Number.NaN, Number.POSITIVE_INFINITY, 2.5]) {
175
+ let forecastFetches = 0;
176
+ const invalidForecast = await getForecast({ latitude: 25.6, longitude: 100.2 }, { timeoutMs: badTimeout, fetchImpl: mockFetch(() => { forecastFetches += 1; return forecastBody(7); }) });
177
+ assert.equal(invalidForecast.ok, false);
178
+ assert.equal(forecastFetches, 0, `invalid forecast timeout ${String(badTimeout)} must not fetch`);
179
+ let climateFetches = 0;
180
+ const invalidClimateTimeout = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { timeoutMs: badTimeout, fetchImpl: mockFetch(() => { climateFetches += 1; return climateBody; }) });
181
+ assert.equal(invalidClimateTimeout.ok, false);
182
+ assert.equal(climateFetches, 0, `invalid climate timeout ${String(badTimeout)} must not fetch`);
183
+ let geoFetches = 0;
184
+ const invalidGeoTimeout = await geocodePlace('大理市', { timeoutMs: badTimeout, fetchImpl: mockFetch(() => { geoFetches += 1; return geoDali; }) });
185
+ assert.equal(invalidGeoTimeout.ok, false);
186
+ assert.equal(geoFetches, 0, `invalid geocode timeout ${String(badTimeout)} must not fetch`);
187
+ }
188
+ let cappedDays = '';
189
+ const capped = await getForecast({ latitude: 25.6, longitude: 100.2 }, { days: 99, fetchImpl: async (input) => { cappedDays = String(input); return jsonResponse(forecastBody(16)); } });
190
+ assert.equal(capped.ok, true);
191
+ assert.match(cappedDays, /forecast_days=16/);
192
+ const wrongMonth = await getClimate({ latitude: 25.6, longitude: 100.2 }, 9, { year: 2025, fetchImpl: mockFetch(() => climateBody) });
193
+ assert.equal(wrongMonth.ok, false);
194
+ const leapBody = { daily: { time: Array.from({ length: 29 }, (_, i) => `2024-02-${String(i + 1).padStart(2, '0')}`), temperature_2m_max: Array(29).fill(10), temperature_2m_min: Array(29).fill(1), weathercode: Array(29).fill(0) } };
195
+ const leap = await getClimate({ latitude: 25.6, longitude: 100.2 }, 2, { year: 2024, fetchImpl: mockFetch(() => leapBody) });
196
+ assert.equal(leap.ok, true);
197
+ const historicalMinimum = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { year: 1940, fetchImpl: mockFetch(() => climateBodyFor(1940, 8)) });
198
+ assert.equal(historicalMinimum.ok, true);
199
+ const currentYear = new Date().getUTCFullYear();
200
+ const currentClimate = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { year: currentYear, fetchImpl: mockFetch(() => climateBodyFor(currentYear, 8)) });
201
+ assert.equal(currentClimate.ok, true);
202
+ for (const badYear of [1939, 2025.5, currentYear + 1]) {
203
+ let yearFetches = 0;
204
+ const invalidYear = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { year: badYear, fetchImpl: mockFetch(() => { yearFetches += 1; return climateBody; }) });
205
+ assert.equal(invalidYear.ok, false);
206
+ assert.equal(yearFetches, 0, `invalid climate year ${String(badYear)} must not fetch`);
207
+ }
208
+ const duplicateDayBody = climateBodyFor(2025, 8);
209
+ duplicateDayBody.daily.time[1] = duplicateDayBody.daily.time[0];
210
+ const duplicateDay = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { year: 2025, fetchImpl: mockFetch(() => duplicateDayBody) });
211
+ assert.equal(duplicateDay.ok, false);
212
+ const day99Body = climateBodyFor(2025, 8);
213
+ day99Body.daily.time[0] = '2025-08-99';
214
+ const day99 = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { year: 2025, fetchImpl: mockFetch(() => day99Body) });
215
+ assert.equal(day99.ok, false);
216
+ let invalidClimateFetches = 0;
217
+ const invalidClimate = await getClimate({ latitude: 25.6, longitude: 100.2 }, 13, { year: 2025, fetchImpl: mockFetch(() => { invalidClimateFetches += 1; return climateBody; }) });
218
+ assert.equal(invalidClimate.ok, false);
219
+ assert.equal(invalidClimateFetches, 0);
220
+ let geoZeroFetches = 0;
221
+ const geoZero = await geocodePlace('大理市', { timeoutMs: 0, fetchImpl: mockFetch(() => { geoZeroFetches += 1; return geoDali; }) });
222
+ assert.equal(geoZero.ok, false);
223
+ assert.equal(geoZeroFetches, 0);
224
+ let climateZeroFetches = 0;
225
+ const climateZero = await getClimate({ latitude: 25.6, longitude: 100.2 }, 8, { timeoutMs: 0, fetchImpl: mockFetch(() => { climateZeroFetches += 1; return climateBody; }) });
226
+ assert.equal(climateZero.ok, false);
227
+ assert.equal(climateZeroFetches, 0);
228
+ let primaryResolutionFetches = 0;
229
+ const originalPerformanceNow = performance.now;
230
+ Object.defineProperty(performance, 'now', { configurable: true, value: (() => {
231
+ const values = [100, 104.5, 104.5];
232
+ return () => values.shift() ?? 104.5;
233
+ })() });
234
+ try {
235
+ const primaryResolution = await geocodePlace('剩余不足一毫秒', { timeoutMs: 5, fetchImpl: mockFetch(() => { primaryResolutionFetches += 1; return geoDali; }) });
236
+ assert.equal(primaryResolution.ok, false);
237
+ assert.equal(primaryResolutionFetches, 0, 'sub-millisecond primary budget must not fetch');
238
+ }
239
+ finally {
240
+ Object.defineProperty(performance, 'now', { configurable: true, value: originalPerformanceNow });
241
+ }
242
+ let primaryBudgetFetches = 0;
243
+ let fallbackResolutionFetches = 0;
244
+ Object.defineProperty(performance, 'now', { configurable: true, value: (() => {
245
+ const values = [200, 200, 205];
246
+ return () => values.shift() ?? 205;
247
+ })() });
248
+ try {
249
+ const fallbackResolution = await geocodePlace('兜底剩余不足一毫秒', { timeoutMs: 5, fetchImpl: async (input) => {
250
+ if (String(input).includes('nominatim')) {
251
+ fallbackResolutionFetches += 1;
252
+ return jsonResponse([]);
253
+ }
254
+ primaryBudgetFetches += 1;
255
+ return jsonResponse({ results: [] });
256
+ } });
257
+ assert.equal(fallbackResolution.ok, false);
258
+ assert.equal(primaryBudgetFetches, 1);
259
+ assert.equal(fallbackResolutionFetches, 0, 'sub-millisecond fallback budget must not fetch');
260
+ }
261
+ finally {
262
+ Object.defineProperty(performance, 'now', { configurable: true, value: originalPerformanceNow });
263
+ }
264
+ const nonErrorRejection = await getForecast({ latitude: 25.6, longitude: 100.2 }, { fetchImpl: async () => { throw Object.create(null); } });
265
+ assert.equal(nonErrorRejection.ok, false);
266
+ assert.equal(nonErrorRejection.error, 'request failed');
267
+ const symbolRejection = await getForecast({ latitude: 25.6, longitude: 100.2 }, { fetchImpl: async () => { throw Symbol('untrusted'); } });
268
+ assert.equal(symbolRejection.ok, false);
269
+ assert.equal(symbolRejection.error, 'request failed');
270
+ const originalDateNow = Date.now;
271
+ Date.now = () => 0;
272
+ try {
273
+ const rollback = await getForecast({ latitude: 25.6, longitude: 100.2 }, { timeoutMs: 20, fetchImpl: mockFetch(() => forecastBody(7)) });
274
+ assert.equal(rollback.ok, true, 'wall-clock rollback must not expand or break monotonic budget');
275
+ }
276
+ finally {
277
+ Date.now = originalDateNow;
278
+ }
41
279
  assert.equal(wmoLabel(0), '晴');
42
280
  assert.equal(wmoLabel(95), '雷暴');
43
- assert.match(wmoLabel(999), /天气码999/, '未知码回退');
44
- console.log('5. WMO 码映射 OK');
45
- console.log('\nWEATHER TESTS: 5/5 OK(Open-Meteo 真实 API,免费无 key)');
46
-
47
-
48
- //# sourceURL=/Users/bytedance/work/gotry/ts/scripts/weather-tests.ts
281
+ assert.match(wmoLabel(999), /天气码999/);
282
+ console.log('6. climate/schema guard/WMO OK');
283
+ console.log('\nWEATHER TESTS: deterministic 6/6 OK (live APIs are peripheral smoke only)');
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 愿望池通道否证测试(外部事件接缝第 2 段,issue #82 兼容方向;全离线纯函数):
3
+ * 1. conditions.channels 命名通道处于 down → 否证成行条件,即使 days/budget/month 全命中
4
+ * 2. 通道全健康(channelDown 空集)→ 不否证不加分,score/hits 与旧口径一致
5
+ * 3. ctx.channelDown 缺席(旧调用方)→ 行为与旧版完全一致(零破坏)
6
+ * 4. 畸形 channels(非数组/非字符串元素)→ 忽略,不误杀
7
+ * 5. pickNudgeWish 集成:最高分愿望被否证 → 次高分顶上(0..1 纪律不变)
8
+ *
9
+ * 运行: cd ts && npx tsx scripts/wish-channel-gate-tests.ts
10
+ */
11
+ import assert from 'node:assert/strict';
12
+ import { scoreWishMatch, pickNudgeWish } from '../src/wish-pool.js';
13
+ const base = {
14
+ wish_id: 'w-dubai',
15
+ name: '迪拜',
16
+ added_at: '2026-09-01T00:00:00Z',
17
+ conditions: { days: 5, budget_cny: 8000, best_months: [10, 11], channels: ['hbcli-hotel'] },
18
+ };
19
+ // ── 1. 命名通道 down → 否证 ─────────────────────────────────────────────────
20
+ const vetoed = scoreWishMatch(base, {
21
+ days: 10, budgetCny: 20000, month: 10,
22
+ channelDown: new Set(['hbcli-hotel']),
23
+ });
24
+ assert.equal(vetoed, null, 'days/budget/month 全命中但依赖通道 down → 否证返回 null');
25
+ // ── 2. 通道健康 → 不否证不加分(评分口径不变,/3)────────────────────────────
26
+ const healthy = scoreWishMatch(base, {
27
+ days: 10, budgetCny: 20000, month: 10,
28
+ channelDown: new Set(),
29
+ });
30
+ assert.ok(healthy && healthy.score === 3 && healthy.hits.join('+') === 'days≥5+budget≥8000+month=10', '健康时评分与旧口径一致(score=3)');
31
+ // ── 3. channelDown 缺席(旧调用方)→ 零破坏 ──────────────────────────────────
32
+ const legacy = scoreWishMatch(base, { days: 10, budgetCny: 20000, month: 10 });
33
+ assert.ok(legacy && legacy.score === 3, '不传 channelDown 的旧调用行为不变');
34
+ // ── 4. 畸形 channels 忽略 ────────────────────────────────────────────────────
35
+ const malformed = scoreWishMatch({
36
+ ...base,
37
+ conditions: { days: 5, channels: 'hbcli-hotel' },
38
+ }, { days: 10, month: 10, channelDown: new Set(['hbcli-hotel']) });
39
+ assert.ok(malformed && malformed.score === 1, '非数组 channels 被忽略,不误杀');
40
+ const mixed = scoreWishMatch({
41
+ ...base,
42
+ conditions: { days: 5, channels: [42, null, 'hbcli-hotel'] },
43
+ }, { days: 10, month: 10, channelDown: new Set(['hbcli-hotel']) });
44
+ assert.equal(mixed, null, '数组内字符串元素仍参与否证(非字符串忽略)');
45
+ // ── 5. pickNudgeWish 集成:最高分被否证 → 次高顶上 ───────────────────────────
46
+ const pool = [
47
+ { ...base, added_at: '2026-09-01T00:00:00Z' },
48
+ { wish_id: 'w-osaka', name: '大阪', added_at: '2026-09-02T00:00:00Z', conditions: { days: 5, best_months: [10] } },
49
+ ];
50
+ const picked = pickNudgeWish(pool, { days: 10, month: 10, channelDown: new Set(['hbcli-hotel']) });
51
+ assert.ok(picked && picked.wishId === 'w-osaka', `被否证的 w-dubai 退出,次高 w-osaka 顶上,实际 ${picked?.wishId}`);
52
+ const none = pickNudgeWish([base], { days: 10, month: 10, channelDown: new Set(['hbcli-hotel']) });
53
+ assert.equal(none, null, '全部被否证 → null(不硬推纪律保持)');
54
+ console.log('WISH CHANNEL GATE TESTS: 5/5 OK(否证 / 健康不加分 / 旧调用零破坏 / 畸形忽略 / 0..1 集成)');