@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
package/ts/src/unified.ts CHANGED
@@ -7,8 +7,12 @@
7
7
 
8
8
  import { checkConnectivity } from '../scripts/skeleton-check.ts'
9
9
  import type { Candidate, Choice, MotivationProfile, Service, TransferMode, TravelRequest, TrueCost } from './model.ts'
10
- import { evaluateChoice, minToHhmm, hhmmToMin, requiredUsableHours, trueCostToDict, LATEST_ARRIVE_STAY_MIN } from './model.ts'
10
+ import { evaluateChoice, minToHhmm, hhmmToMin, requiredUsableHours, trueCostToDict, LATEST_ARRIVE_STAY_MIN, doorToDoorFromMove, NonpositiveDurationError } from './model.ts'
11
11
  import type { LegReport } from './journey.ts'
12
+ import { withZ3 } from './z3-shared.ts'
13
+ import { t as i18nT } from './i18n.ts'
14
+ import { resolveOffsetForLocalDate, describeResolveFailure, isKnownZoneLoose, readWallParts, wallPartsToIso } from './tz-resolver.ts'
15
+ import { FLIGHT_PACK_VERSION } from './flight-pack-contract.ts'
12
16
 
13
17
  export interface AnchorsSpec {
14
18
  arriveByMin?: number
@@ -34,6 +38,9 @@ export interface MoveSpecTS {
34
38
  tzOffsetMin?: number
35
39
  /** M-1:出发地 UTC 偏移(工作窗口换算用) */
36
40
  originTzOffsetMin?: number
41
+ /** Issue #343 v2:dep/arr 的 IANA zone(从 option.date + 服务 HH:MM 解析;只读镜像,不算术)。 */
42
+ ianaDepZone?: string
43
+ ianaArrZone?: string
37
44
  }
38
45
 
39
46
  /** M-1:旅行者工作窗口(家时区) */
@@ -42,6 +49,14 @@ export interface WorkWindowSpec {
42
49
  startMin: number
43
50
  endMin: number
44
51
  workdays?: number[]
52
+ /** Issue #343 v2:home IANA zone;overrides numeric `homeTzOffsetMin` when set.
53
+ * Used by `workWindowBlocks` to derive a date-sensitive home offset instead of a
54
+ * single numeric per-trip shift(原 M-1 数值写法).Mutually exclusive with
55
+ * `homeTzOffsetMin` at v2 parse time;不在同一 pack 里同时给两值。 */
56
+ homeZone?: string
57
+ /** Issue #343 v2:可选,home 工作窗口的本地表达(hh:mm);缺省回退 start/end 的 min-of-day。 */
58
+ startHHmm?: string
59
+ endHHmm?: string
45
60
  }
46
61
 
47
62
  export interface StaySpecTS {
@@ -55,6 +70,8 @@ export interface StaySpecTS {
55
70
  export interface SegmentOptionTS {
56
71
  id: string
57
72
  label: string
73
+ /** 候选级日期;缺省时使用所属 segment.date */
74
+ date?: string
58
75
  move?: MoveSpecTS
59
76
  stay?: StaySpecTS
60
77
  score?: number
@@ -83,20 +100,13 @@ export interface JourneySpecTS {
83
100
  workWindow?: WorkWindowSpec
84
101
  /** 骨架层开关(§7-1):true 时对带 route 提示的段做通航性三值标注 */
85
102
  skeletonHub?: boolean
103
+ /** Internal only; symbol-keyed so JSON/public tool schemas never expose pack version. */
104
+ readonly [FLIGHT_PACK_VERSION]?: 1 | 2
86
105
  }
87
106
 
107
+ // Z3 运行时收敛到 z3-shared(单一 WASM 实例 + 单一 Context + 会话级互斥)——
108
+ // 此前模块级 z3Promise 三份并存是 WASM race/OOM 的根因(见 z3-shared.ts 头注)。
88
109
  /* eslint-disable @typescript-eslint/no-explicit-any */
89
- let z3Promise: Promise<any> | null = null
90
-
91
- async function getZ3(): Promise<any> {
92
- // 延迟到首次实际调用 solveUnified 才加载:避免 dsh 加载 GoTry 模块时启动
93
- // WASM worker,引起 worker 线程内存冲突(z3-built.wasm 多线程 unsafe)。
94
- if (!z3Promise) {
95
- const { init } = await import('z3-solver')
96
- z3Promise = (async () => (await init()).Context('main'))()
97
- }
98
- return z3Promise
99
- }
100
110
 
101
111
  /** 航班数据包(data/flights_2026.json 形态)→ 段链 */
102
112
  const SEGMENT_ROUTES: Record<string, string> = {
@@ -107,6 +117,13 @@ function routeHint(id: string): string | undefined {
107
117
  return SEGMENT_ROUTES[id]
108
118
  }
109
119
 
120
+ // Issue #620 注:数据包缺 buffer_min/origin_transfer_min/dest_transfer_min 时,
121
+ // 这里的 `Number(undefined)` = NaN 会一路流到求解层。NaN **不在 parse 边界补 0**
122
+ // (补零等于替用户编接驳时间,ADR-10 翻译不造数),也不在这里整包拒收
123
+ // (data/yunnan-pack.json 的 yn0 真值按 meta.reconcil 仍待 founder 校准)。
124
+ // 防线在 solveUnifiedInner 的 assertZ3IntegerInputs:进 Z3 之前逐字段点名,
125
+ // 以显式 solver_error 返回,绝不冒充「不可行」。
126
+
110
127
  // ---- 适配器:旧候选用例(洱海形态:request + candidates)→ 单 choice 段 -------------
111
128
  // 与 py unified.py segments_from_candidate 逐行对齐
112
129
 
@@ -116,6 +133,7 @@ export function segmentsFromCandidate(req: TravelRequest, candidates: Candidate[
116
133
  const options: SegmentOptionTS[] = candidates.map(c => ({
117
134
  id: c.id,
118
135
  label: c.name,
136
+ date: c.date,
119
137
  score: c.imageryMatch,
120
138
  bestMonths: c.bestMonths,
121
139
  minDays: c.minDaysForPurpose,
@@ -151,6 +169,17 @@ export function segmentsFromCandidate(req: TravelRequest, candidates: Candidate[
151
169
  }
152
170
 
153
171
  export function parseFlightPackToSpec(pack: Record<string, unknown>): JourneySpecTS {
172
+ // Issue #343 v2:IANA path;v1(无 version 字段或 version===1)走原数值口径。
173
+ const version = pack['version'] === undefined ? 1 : Number(pack['version'])
174
+ if (version === 2) return parseFlightPackToSpecV2(pack)
175
+ if (version !== 1) {
176
+ throw Object.assign(new Error(`parseFlightPackToSpec: unsupported pack version ${version}`), { code: 'unsupported_pack_version' })
177
+ }
178
+ return parseFlightPackToSpecV1(pack)
179
+ }
180
+
181
+ /** v1 数值口径(path 保留为 export,便于 §7-1/§6 直接复用与差分) */
182
+ export function parseFlightPackToSpecV1(pack: Record<string, unknown>): JourneySpecTS {
154
183
  const legs = (pack['legs'] as Array<Record<string, unknown>>).map(l => ({
155
184
  id: String(l['id']),
156
185
  role: ((l['services'] as unknown[]).length === 1 ? 'fixed' : 'choice') as 'fixed' | 'choice',
@@ -184,6 +213,7 @@ export function parseFlightPackToSpec(pack: Record<string, unknown>): JourneySpe
184
213
  endMin: Number(ww['end_min']),
185
214
  workdays: (ww['workdays'] as number[] | undefined) ?? [0, 1, 2, 3, 4],
186
215
  } : undefined,
216
+ [FLIGHT_PACK_VERSION]: 1,
187
217
  }
188
218
  // M-1:班次的星期标注挂到 Option(缺省=不受工作窗口约束)
189
219
  for (const l of pack['legs'] as Array<Record<string, unknown>>) {
@@ -198,31 +228,235 @@ export function parseFlightPackToSpec(pack: Record<string, unknown>): JourneySpe
198
228
  return spec
199
229
  }
200
230
 
231
+ /** Issue #343 v2:IANA zone + 当地日期解析;数值 tz_* 字段不可用(混合形态解析时拒收)。 */
232
+ export function parseFlightPackToSpecV2(pack: Record<string, unknown>): JourneySpecTS {
233
+ const legsRaw = pack['legs'] as Array<Record<string, unknown>>
234
+ if (!Array.isArray(legsRaw) || legsRaw.length === 0) {
235
+ throw Object.assign(new Error('parseFlightPackToSpecV2: legs missing/empty'), { code: 'v2_legs_missing' })
236
+ }
237
+ const meta = (pack['meta'] ?? {}) as Record<string, unknown>
238
+ const ww = meta['work_window'] as Record<string, unknown> | undefined
239
+
240
+ const legs: SegmentTS[] = []
241
+ for (const l of legsRaw) {
242
+ // v2 不允许混合数值 tz_offset_min/origin_tz_offset_min(早 fail)
243
+ if ('tz_offset_min' in l || 'origin_tz_offset_min' in l) {
244
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: leg ${l['id']} has legacy tz_offset_min/origin_tz_offset_min;v2 pack must use iana_*_zone + *_local_date`), { code: 'v2_legacy_offset_mixed' })
245
+ }
246
+ const legDate = l['date'] ? String(l['date']) : ''
247
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(legDate)) {
248
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: leg ${l['id']} requires ISO date`), { code: 'v2_leg_date_missing' })
249
+ }
250
+ const ianaDepZone = String(l['iana_dep_zone'] ?? '')
251
+ const ianaArrZone = String(l['iana_arr_zone'] ?? '')
252
+ if (!ianaDepZone || !ianaArrZone) {
253
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: leg ${l['id']} needs iana_dep_zone and iana_arr_zone`), { code: 'v2_zone_missing' })
254
+ }
255
+ const options: SegmentOptionTS[] = (l['services'] as Array<Record<string, unknown>>).map(sv => {
256
+ const svcDepDate = sv['dep_local_date'] ? String(sv['dep_local_date']) : legDate
257
+ const svcArrDate = sv['arr_local_date'] ? String(sv['arr_local_date']) : legDate
258
+ const depZone = sv['iana_dep_zone'] ? String(sv['iana_dep_zone']) : ianaDepZone
259
+ const arrZone = sv['iana_arr_zone'] ? String(sv['iana_arr_zone']) : ianaArrZone
260
+ const depR = resolveOffsetForLocalDate(depZone, svcDepDate, String(sv['dep']))
261
+ if (!depR.ok) {
262
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: dep zone/date failure: ${describeResolveFailure(depR)}`), { code: 'v2_dep_resolve_failed' })
263
+ }
264
+ const arrR = resolveOffsetForLocalDate(arrZone, svcArrDate, String(sv['arr']))
265
+ if (!arrR.ok) {
266
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: arr zone/date failure: ${describeResolveFailure(arrR)}`), { code: 'v2_arr_resolve_failed' })
267
+ }
268
+ // Issue #343 root 复核:d2d canonical 输入必须在 parse 边界校验严格 chronologically after,
269
+ // 否则把病态数据喂给 model.doorToDoorFromMove → throw NonpositiveDurationError → 被
270
+ // solveUnified WASM guard 误报为 wasm_runtime_error。
271
+ if (arrR.utcMs <= depR.utcMs) {
272
+ throw Object.assign(new Error(
273
+ `parseFlightPackToSpecV2: service ${String(sv['id'])} arrival instant (${new Date(arrR.utcMs).toISOString()}) must be strictly after departure instant (${new Date(depR.utcMs).toISOString()})`,
274
+ ), { code: 'v2_arr_not_after_dep' })
275
+ }
276
+ // tzOffsetMin = arr - dep(positive = 抵达地更靠东)
277
+ const tzOffsetMin = arrR.offsetMin - depR.offsetMin
278
+ return {
279
+ id: String(sv['id']),
280
+ label: `${String(sv['id'])} ${String(l['note'] ?? '').slice(0, 18)}`,
281
+ date: svcDepDate,
282
+ move: {
283
+ hub: String(l['hub'] ?? ''),
284
+ services: [{
285
+ id: String(sv['id']),
286
+ depMin: hhmmToMin(String(sv['dep'])),
287
+ arrMin: hhmmToMin(String(sv['arr'])),
288
+ priceCny: Number(sv['price_cny']),
289
+ // Issue #343 v2:把"墙上时刻"投影成真实 UTC instant 留给 evaluate / workWindow 用;
290
+ // d2d 公式走 (arrUtcMs - depUtcMs)/60000,跨日 / DST / 反向日界线都正确。
291
+ depUtcMs: depR.utcMs,
292
+ arrUtcMs: arrR.utcMs,
293
+ }],
294
+ bufferMin: Number(l['buffer_min']),
295
+ originTransferMin: Number(l['origin_transfer_min']),
296
+ destTransferMin: Number(l['dest_transfer_min']),
297
+ redEye: Boolean(l['red_eye']),
298
+ redEyeDurationMin: Number(sv['red_eye_duration_min'] ?? l['red_eye_duration_min'] ?? 0),
299
+ groundRecoveryMin: l['ground_recovery_min'] ? Number(l['ground_recovery_min']) : undefined,
300
+ tzOffsetMin,
301
+ originTzOffsetMin: depR.offsetMin,
302
+ ianaDepZone: depZone,
303
+ ianaArrZone: arrZone,
304
+ },
305
+ depWeekday: weekdayFromYmd(svcDepDate),
306
+ }
307
+ })
308
+
309
+ const role = ((l['services'] as unknown[]).length === 1 ? 'fixed' : 'choice') as 'fixed' | 'choice'
310
+ legs.push({
311
+ id: String(l['id']),
312
+ role,
313
+ note: l['note'] ? String(l['note']) : undefined,
314
+ date: legDate,
315
+ anchors: { arriveByMin: l['arrive_by'] ? hhmmToMin(String(l['arrive_by'])) : undefined },
316
+ route: routeHint(l['id'] as string),
317
+ options,
318
+ })
319
+ }
320
+
321
+ // work_window:home_zone 必需(v2 的 home 偏移走 IANA,数值字段不再权威);同时给两值报 reject。
322
+ let workWindow: WorkWindowSpec | undefined
323
+ if (ww) {
324
+ const hasHomeZone = Boolean(ww['home_zone'])
325
+ const hasHomeNum = ww['home_tz_offset_min'] !== undefined
326
+ if (hasHomeZone && hasHomeNum) {
327
+ throw Object.assign(new Error('parseFlightPackToSpecV2: work_window cannot combine home_zone and home_tz_offset_min'), { code: 'v2_ww_conflict' })
328
+ }
329
+ if (!hasHomeZone) {
330
+ throw Object.assign(new Error('parseFlightPackToSpecV2: work_window.home_zone is required for v2'), { code: 'v2_ww_home_zone_required' })
331
+ }
332
+ // 在 v2 parse 边界即时验证 home_zone 是已知 IANA zone(root counterexample #3:不许 fail-open)。
333
+ // 用 tz-resolver 的 isKnownZoneLoose:接受 Intl.DateTimeFormat 能识别的合法 alias(US/Pacific / ROK / Etc/GMT+8)。
334
+ const homeZone = String(ww['home_zone'])
335
+ if (!isKnownZoneLoose(homeZone)) {
336
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: work_window.home_zone '${homeZone}' is not a known IANA zone`), { code: 'v2_home_zone_unknown' })
337
+ }
338
+ const startHHmm = ww['start'] ? String(ww['start']) : undefined
339
+ const endHHmm = ww['end'] ? String(ww['end']) : undefined
340
+ const startMin = ww['start_min'] !== undefined ? Number(ww['start_min']) : (startHHmm ? hhmmToMin(startHHmm) : hhmmToMin('10:00'))
341
+ const endMin = ww['end_min'] !== undefined ? Number(ww['end_min']) : (endHHmm ? hhmmToMin(endHHmm) : hhmmToMin('19:00'))
342
+ // numeric homeTzOffsetMin 仅占位:在 v2 工作窗口过滤时按 home_zone + dep_date 重算;预填 leg-date 占位 0
343
+ workWindow = {
344
+ homeTzOffsetMin: 0,
345
+ startMin,
346
+ endMin,
347
+ workdays: (ww['workdays'] as number[] | undefined) ?? [0, 1, 2, 3, 4],
348
+ homeZone,
349
+ startHHmm,
350
+ endHHmm,
351
+ }
352
+ }
353
+
354
+ return { segments: legs, workWindow, [FLIGHT_PACK_VERSION]: 2 }
355
+ }
356
+
201
357
  const WEEKDAY_IDX: Record<string, number> = { mon: 0, tue: 1, wed: 2, thu: 3, fri: 4, sat: 5, sun: 6 }
202
358
  const WEEKDAY_CN: Record<string, string> = { mon: '一', tue: '二', wed: '三', thu: '四', fri: '五', sat: '六', sun: '日' }
203
359
 
204
- /** M-1:工作日的工作窗口内起飞 → 排除理由;否则 null(与 py _work_window_blocks 对齐)。 */
360
+ /** YYYY-MM-DD → mon/tue/wed/thu/fri/sat/sun;v2 路径给 option 标 depWeekday 用。 */
361
+ export function weekdayFromYmd(ymd: string): string {
362
+ const m = ymd.match(/^(\d{4})-(\d{2})-(\d{2})$/)
363
+ if (!m) throw new Error(`weekdayFromYmd: bad ymd ${ymd}`)
364
+ const dow = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]))).getUTCDay()
365
+ return ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][dow]!
366
+ }
367
+
368
+ /** M-1:工作日的工作窗口内起飞 → 排除理由;否则 null(与 py _work_window_blocks 对齐)。
369
+ *
370
+ * Issue #343 v2:home zone 存在时,必须把"出发真实 UTC instant"投影到 home zone
371
+ * 取该 zone 的 date/weekday/HH:MM(root counterexample #2:Tokyo 06:30 ≈ LA 14:30 prev day);
372
+ * 不再用 dep-local date 直接当 home date。
373
+ *
374
+ * root 评审复核:`depUtcMs` 是已知确定的 instant,直接 `readWallParts(homeZone, depUtcMs)`
375
+ * 取墙时分量 — 不要把 projected wall time 再喂给 `resolveOffsetForLocalDate` 重做 ±14h 扫描,
376
+ * 否则秋回退日 01:30 会被错误判为 overlap_ambiguous 而 fail-open(已知 instant 永远唯一)。
377
+ *
378
+ * dep/arr 的"墙上本地输入"(leg-level `dep`/`arr` 字段)由 parseFlightPackToSpecV2 在
379
+ * 解析时已用 `resolveOffsetForLocalDate` 拒过 gap/overlap,所以 leg 进 solver 时
380
+ * 两个 instant 一定 strict-after 且偏移已知。
381
+ *
382
+ * home zone 未知时由 v2 parse 边界直接抛 v2_home_zone_unknown 拒绝;
383
+ * 这里兜底只在 v1 legacy 路径出现。
384
+ */
205
385
  function workWindowBlocks(spec: JourneySpecTS, option: SegmentOptionTS): string | null {
206
386
  const ww = spec.workWindow
207
387
  if (!ww || !option.move || !option.depWeekday) return null
208
- if (!((ww.workdays ?? [0, 1, 2, 3, 4]).includes(WEEKDAY_IDX[option.depWeekday]))) return null
209
- const dep = option.move.services[0].depMin
210
- const shift = (option.move.originTzOffsetMin ?? 480) - ww.homeTzOffsetMin
211
- const start = ww.startMin + shift, end = ww.endMin + shift
212
- if (start <= dep && dep <= end) {
388
+
389
+ const svc = option.move.services[0]
390
+ const dep = svc.depMin
391
+ const homeZone = ww.homeZone
392
+
393
+ let homeOffsetMin = ww.homeTzOffsetMin
394
+ let homeDepMin: number | undefined
395
+ let homeDepWeekday: string | undefined
396
+
397
+ if (homeZone) {
398
+ if (typeof svc.depUtcMs !== 'number') {
399
+ // v2 路径必然填了 depUtcMs;没填 = 数据病,按 red flag 排除该 option。
400
+ return i18nT('un.workwindow_unresolvable_reason', {
401
+ reason: `home zone ${homeZone} requires UTC dep instant (v2 path bug)`,
402
+ })
403
+ }
404
+ // 已知 instant 直接投影到 home zone 的墙时 — 无歧义,不必也不应重走 ±14h 扫描。
405
+ const homeParts = readWallParts(homeZone, svc.depUtcMs)
406
+ const homeIso = wallPartsToIso(homeParts)
407
+ homeDepMin = hhmmToMin(homeIso.hhmm)
408
+ homeDepWeekday = weekdayFromYmd(homeIso.ymd)
409
+ }
410
+
411
+ const effectiveDep = homeDepMin ?? dep
412
+ const effectiveWeekday = homeDepWeekday ?? option.depWeekday
413
+ if (!((ww.workdays ?? [0, 1, 2, 3, 4]).includes(WEEKDAY_IDX[effectiveWeekday]))) return null
414
+
415
+ // v2:startMin/endMin 与 homeDepMin 都是 home-zone 墙时分量(已 IANA 归一),
416
+ // 直接比较;不再做 originTzOffsetMin 偏移(root 复核该 shift 是 v1 数值残留,会让窗口错位)。
417
+ // v1 路径(无 homeZone, 走数值 homeTzOffsetMin)保留 shift 兼容旧 spec。
418
+ let start: number, end: number
419
+ if (homeZone) {
420
+ start = ww.startMin
421
+ end = ww.endMin
422
+ } else {
423
+ const shift = (option.move.originTzOffsetMin ?? 480) - homeOffsetMin
424
+ start = ww.startMin + shift
425
+ end = ww.endMin + shift
426
+ }
427
+ if (start <= effectiveDep && effectiveDep <= end) {
213
428
  const hhmm = (m: number) => `${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`
214
- return `周${WEEKDAY_CN[option.depWeekday]} ${hhmm(dep)} 起飞落在工作窗口(当地 ${hhmm(start)}-${hhmm(end)})内`
429
+ return i18nT('un.workwindow_reason', {
430
+ wd: WEEKDAY_CN[effectiveWeekday] ?? '', dep: hhmm(effectiveDep), start: hhmm(start), end: hhmm(end),
431
+ })
215
432
  }
216
433
  return null
217
434
  }
218
435
 
219
- /** D-5:时区感知的段核算(与 py unified._evaluate_option_move 对齐)。 */
436
+ /** D-5:时区感知的段核算(与 py unified._evaluate_option_move 对齐)。
437
+ *
438
+ * Issue #343 d2d 算术唯一权威入口 = model.ts 的 `doorToDoorFromMove`。
439
+ * v2 路径会传 Service.depUtcMs/arrUtcMs(由 parseFlightPackToSpecV2 用 tz-resolver 投出),
440
+ * 此时 d2d 用真实 UTC instant 差 — 跨日 / 反向日界线 / DST 切换都正确;v2 arr<=dep 抛
441
+ * NonpositiveDurationError → 该 option 当作不可行(由 solveUnifiedInner 转 unsat_core)。
442
+ * v1 数值口径无 UTC 字段,走老 (arrMin - depMin) - tzOffsetMin;字节不变。
443
+ */
220
444
  function evaluateOptionMove(segId: string, mv: MoveSpecTS): LegReport & { d2d_min?: number } {
221
445
  const svc = mv.services[0]
446
+ let d2dMin: number
447
+ try {
448
+ d2dMin = doorToDoorFromMove(svc, mv).doorToDoorMin
449
+ } catch (e) {
450
+ if (e instanceof NonpositiveDurationError) {
451
+ // d2d 病态输入 → 把这条 option 抛红 flag;这里走结构化 reject 而非吞错。
452
+ throw Object.assign(new Error(`evaluateOptionMove ${segId}/${svc.id}: ${e.message}`), {
453
+ code: e.code,
454
+ kind: e.kind,
455
+ })
456
+ }
457
+ throw e
458
+ }
222
459
  const wake = svc.depMin - mv.bufferMin - mv.originTransferMin
223
- // 真实时长 = (到达−出发) − 时差;EK329: 215−(−240)=455min=7h35m
224
- const trueFlight = (svc.arrMin - svc.depMin) - (mv.tzOffsetMin ?? 0)
225
- const d2d = mv.bufferMin + mv.originTransferMin + trueFlight + mv.destTransferMin
226
460
  const wakeDisplay = wake < 0 ? `${minToHhmm(wake + 1440)}(前一日)` : minToHhmm(wake)
227
461
  const arriveStay = svc.arrMin + mv.destTransferMin
228
462
 
@@ -238,7 +472,7 @@ function evaluateOptionMove(segId: string, mv: MoveSpecTS): LegReport & { d2d_mi
238
472
  if (wake < 5 * 60) energy -= 30
239
473
  else if (wake < 6 * 60) energy -= 25
240
474
  if (arriveStay > 21 * 60) energy -= 10
241
- if (d2d > 6 * 60) energy -= 10
475
+ if (d2dMin > 6 * 60) energy -= 10
242
476
  energy = Math.max(0, energy)
243
477
  }
244
478
  return {
@@ -247,44 +481,109 @@ function evaluateOptionMove(segId: string, mv: MoveSpecTS): LegReport & { d2d_mi
247
481
  wake: wakeDisplay,
248
482
  wakeMin: wake,
249
483
  arrive_stay: minToHhmm(arriveStay),
250
- door_to_door: `${Math.floor(d2d / 60)}h${String(d2d % 60).padStart(2, '0')}m`,
251
- d2d_min: d2d,
484
+ door_to_door: `${Math.floor(d2dMin / 60)}h${String(d2dMin % 60).padStart(2, '0')}m`,
485
+ d2d_min: d2dMin,
252
486
  energy_pct: Math.round(energy),
253
487
  price_cny: svc.priceCny,
254
488
  }
255
489
  }
256
490
 
257
- /** 航班链形态求解:按 Option 选择,锚点命名约束,core 剥竖线(D-2 修复) */
258
- export async function solveUnified(spec: JourneySpecTS): Promise<{
491
+ function releaseZ3(resource: { release?: () => void } | null | undefined): void {
492
+ if (typeof resource?.release === 'function') resource.release()
493
+ }
494
+
495
+ /**
496
+ * Issue #620:求解器自身失败的显式类别。**不是 unsat_core**——判定缺席,不是「不可行」。
497
+ * 机器 token 只住在 `code` 里,不得渲染进任何用户面「冲突」文案。
498
+ */
499
+ export type SolverErrorInfo = {
500
+ /** `solver_input_not_integer` = spec 里有非整数进不了 Z3 整数编码(确定性,可定位到字段);
501
+ * `solver_runtime_error` = z3-solver / WASM 运行时异常(含真正的内存/竞态形态)。 */
502
+ code: 'solver_input_not_integer' | 'solver_runtime_error'
503
+ message: string
504
+ }
505
+
506
+ export interface UnifiedSolveResult {
507
+ /** `solver_error` 存在时,`feasible: false` 只表示「没有产出可行解」,
508
+ * **不表示「不可行」**——那一轮根本没有判定。读者必须先看 `solver_error`。 */
259
509
  feasible: boolean
260
510
  money_cny?: number
261
- legs?: Array<LegReport & { leg: string }>
511
+ legs?: Array<LegReport & { leg: string; d2d_min?: number }>
262
512
  red_flags?: string[]
263
513
  unsat_core?: string[]
264
514
  suggestions?: Array<{ relax: string; money_cny: number }>
265
515
  work_window_exclusions?: Array<{ segment: string; option: string; reason: string }>
266
516
  skeleton_notes?: string[]
267
- }> {
268
- // WASM 防护:如果 z3-solver 加载或求解触发 memory access 错误,不让异常穿透到进程层把 dsh 杀掉。
269
- // 候选形态走 solveChoiceSegment 不经过这里——这里是显式航班链路径,用户量较少。
517
+ solver_error?: SolverErrorInfo
518
+ }
519
+
520
+ /** Issue #620:进 Z3 前的输入类型闸。`Int.val(NaN|Infinity|小数)` 在 WASM 里是
521
+ * `Assertion failed`,不是一个可解释的判定——所以在构造 AST 之前抛可定位的类型化错误。 */
522
+ export class SolverInputError extends Error {
523
+ readonly code = 'solver_input_not_integer' as const
524
+ constructor(message: string) {
525
+ super(message)
526
+ this.name = 'SolverInputError'
527
+ }
528
+ }
529
+
530
+ /** 把所有将要变成 `Int.val(...)` 的数验成有限整数;违例逐项点名(段/候选/字段)。 */
531
+ function assertZ3IntegerInputs(spec: JourneySpecTS, wakeFloorMin: number): void {
532
+ const bad: string[] = []
533
+ const check = (where: string, value: unknown): void => {
534
+ // NaN/Infinity 经 JSON.stringify 会变成 "null",诊断信息就丢了——数值一律按原文印。
535
+ if (!Number.isInteger(value)) bad.push(`${where}=${typeof value === 'number' ? String(value) : JSON.stringify(value)}`)
536
+ }
537
+ check('wakeFloorMin', wakeFloorMin)
538
+ if (spec.budgetCny !== undefined) check('budgetCny', spec.budgetCny)
539
+ for (const seg of spec.segments) {
540
+ if (seg.anchors?.arriveByMin !== undefined) check(`${seg.id}.anchors.arriveByMin`, seg.anchors.arriveByMin)
541
+ if (seg.anchors?.departAfterMin !== undefined) check(`${seg.id}.anchors.departAfterMin`, seg.anchors.departAfterMin)
542
+ // 段级 wake/arriveStay 编码只取 options[0].move 的接驳口径(见下方 o0)。
543
+ const o0 = seg.options[0]?.move
544
+ if (o0) {
545
+ check(`${seg.id}.bufferMin`, o0.bufferMin)
546
+ check(`${seg.id}.originTransferMin`, o0.originTransferMin)
547
+ check(`${seg.id}.destTransferMin`, o0.destTransferMin)
548
+ }
549
+ for (const opt of seg.options) {
550
+ const svc = opt.move?.services[0]
551
+ if (!svc) {
552
+ bad.push(`${seg.id}/${opt.id}.services[0]=missing`)
553
+ continue
554
+ }
555
+ check(`${seg.id}/${opt.id}.depMin`, svc.depMin)
556
+ check(`${seg.id}/${opt.id}.arrMin`, svc.arrMin)
557
+ check(`${seg.id}/${opt.id}.priceCny`, svc.priceCny)
558
+ }
559
+ }
560
+ if (bad.length > 0) {
561
+ throw new SolverInputError(
562
+ `solveUnified: Z3 整数编码只接受有限整数,下列输入不是(issue #620):${bad.slice(0, 8).join('、')}`
563
+ + (bad.length > 8 ? `(共 ${bad.length} 处)` : ''),
564
+ )
565
+ }
566
+ }
567
+
568
+ /** 航班链形态求解:按 Option 选择,锚点命名约束,core 剥竖线(D-2 修复) */
569
+ export async function solveUnified(spec: JourneySpecTS): Promise<UnifiedSolveResult> {
570
+ // 求解器失败防护:z3-solver 加载/求解异常不让它穿透到进程层把 dsh 杀掉。
571
+ // 但也**不冒充判定**(issue #620):崩溃以 solver_error 显式返回,unsat_core 保持缺席,
572
+ // 否则「引擎故障」会被下游当成「你的行程不可行」,理由位置上还放着机器 token。
573
+ // 候选形态走 solveChoiceSegment 不经过这里——这里是显式航班链路径。
270
574
  try {
271
575
  return await solveUnifiedInner(spec)
272
576
  } catch (e) {
273
- console.error('[gotry] solveUnified failed (likely wasm thread race):', (e as Error).message?.slice(0, 200))
274
- return { feasible: false, unsat_core: ['wasm_runtime_error'], red_flags: ['WASM 求解器异常,建议下次用候选形态(枚举)重试'] }
577
+ const err = e instanceof Error ? e : new Error(String(e))
578
+ const code: SolverErrorInfo['code'] = err instanceof SolverInputError ? err.code : 'solver_runtime_error'
579
+ const message = (err.message ?? '').slice(0, 200)
580
+ // 措辞纪律:不再写「likely wasm thread race」——#620 证明它也可能是确定性的输入错误。
581
+ console.error(`[gotry] solveUnified aborted — ${code}(求解器失败,不是「不可行」判定):`, message)
582
+ return { feasible: false, solver_error: { code, message } }
275
583
  }
276
584
  }
277
585
 
278
- async function solveUnifiedInner(spec: JourneySpecTS): Promise<{
279
- feasible: boolean
280
- money_cny?: number
281
- legs?: Array<LegReport & { leg: string }>
282
- red_flags?: string[]
283
- unsat_core?: string[]
284
- suggestions?: Array<{ relax: string; money_cny: number }>
285
- work_window_exclusions?: Array<{ segment: string; option: string; reason: string }>
286
- skeleton_notes?: string[]
287
- }> {
586
+ async function solveUnifiedInner(spec: JourneySpecTS): Promise<UnifiedSolveResult> {
288
587
  // M-1:求解前的工作窗口确定性预过滤(与 py 对齐),排除理由入记录
289
588
  // 骨架层(§7-1):三值语义标注——枢纽间否定只降权不排除(骨架滞后会错杀 EK329)
290
589
  const skeletonNotes: string[] = []
@@ -310,9 +609,16 @@ async function solveUnifiedInner(spec: JourneySpecTS): Promise<{
310
609
  }
311
610
  }
312
611
 
313
- const z3 = await getZ3()
612
+ // Issue #620:类型闸在 Z3 会话之外先跑——非整数输入不许进 WASM(进去就是
613
+ // `Assertion failed`,还会污染共享实例)。抛 SolverInputError,由 solveUnified 归类。
614
+ const wakeFloorPre = spec.defaultWakeFloorMin ?? hhmmToMin('06:00')
615
+ assertZ3IntegerInputs(spec, wakeFloorPre)
616
+
617
+ // Z3 会话进互斥门(骨架/预过滤等网络段留在门外):solveUnifiedInner 的判定段
618
+ // 从 here 到 return 全部独占共享实例。
619
+ return withZ3('unified.solveUnifiedInner', async z3 => {
314
620
  const { Bool, If, Int, Solver, Sum } = z3
315
- const wakeFloor = spec.defaultWakeFloorMin ?? hhmmToMin('06:00')
621
+ const wakeFloor = wakeFloorPre
316
622
 
317
623
  const allSels: Record<string, any[]> = {}
318
624
  const assertions: Record<string, any> = {}
@@ -348,50 +654,77 @@ async function solveUnifiedInner(spec: JourneySpecTS): Promise<{
348
654
  }
349
655
 
350
656
  const s = new Solver()
351
- for (const sel of Object.values(allSels)) s.add(exactlyOne(sel))
352
- for (const [name, expr] of Object.entries(assertions)) s.addAndTrack(expr, name)
353
-
354
- const report = async (model: any): Promise<Array<LegReport & { leg: string }>> => {
355
- const out: Array<LegReport & { leg: string }> = []
356
- for (const seg of spec.segments) {
357
- for (let i = 0; i < allSels[seg.id].length; i++) {
358
- const v = await maybeAwait(model.eval(allSels[seg.id][i], true))
359
- if (String(v) !== 'true') continue
360
- const o = seg.options[i]
361
- out.push({ leg: seg.id, ...evaluateOptionMove(seg.id, o.move!) })
362
- break
363
- }
657
+ let sOpen = true
658
+ const closeS = () => {
659
+ if (sOpen) {
660
+ releaseZ3(s)
661
+ sOpen = false
364
662
  }
365
- return out
366
- }
367
-
368
- async function maybeAwait<T>(v: T | Promise<T>): Promise<T> {
369
- return v instanceof Promise ? await v : v
370
663
  }
664
+ try {
665
+ for (const sel of Object.values(allSels)) s.add(exactlyOne(sel))
666
+ for (const [name, expr] of Object.entries(assertions)) s.addAndTrack(expr, name)
667
+
668
+ const report = async (model: any): Promise<Array<LegReport & { leg: string }>> => {
669
+ const out: Array<LegReport & { leg: string }> = []
670
+ for (const seg of spec.segments) {
671
+ for (let i = 0; i < allSels[seg.id].length; i++) {
672
+ const v = await maybeAwait(model.eval(allSels[seg.id][i], true))
673
+ if (String(v) !== 'true') continue
674
+ const o = seg.options[i]
675
+ out.push({ leg: seg.id, ...evaluateOptionMove(seg.id, o.move!) })
676
+ break
677
+ }
678
+ }
679
+ return out
680
+ }
371
681
 
372
- if (String(await s.check()) !== 'unsat') {
373
- const legs = await report(s.model())
374
- const money = legs.reduce((a, l) => a + l.price_cny, 0)
375
- const redFlags = legs
376
- .filter(l => spec.segments.find(sg => sg.id === l.leg)?.options.some(o => o.move?.redEye) && l.energy_pct < 50)
377
- .map(l => `${l.leg} 落地精力仅 ${l.energy_pct}%(红眼后直奔事务,当日不宜安排重要会议)`)
378
- return { feasible: true, money_cny: money, legs, red_flags: redFlags, work_window_exclusions: exclusions, skeleton_notes: skeletonNotes.length ? skeletonNotes : undefined }
379
- }
682
+ async function maybeAwait<T>(v: T | Promise<T>): Promise<T> {
683
+ return v instanceof Promise ? await v : v
684
+ }
380
685
 
381
- const core = coreOf(s).sort()
382
- const suggestions: Array<{ relax: string; money_cny: number }> = []
383
- for (const name of core) {
384
- const s2 = new Solver()
385
- for (const sel of Object.values(allSels)) s2.add(exactlyOne(sel))
386
- for (const [n2, expr] of Object.entries(assertions)) {
387
- if (n2 !== name) s2.addAndTrack(expr, n2)
686
+ if (String(await s.check()) !== 'unsat') {
687
+ const model = s.model()
688
+ try {
689
+ const legs = await report(model)
690
+ const money = legs.reduce((a, l) => a + l.price_cny, 0)
691
+ const redFlags = legs
692
+ .filter(l => spec.segments.find(sg => sg.id === l.leg)?.options.some(o => o.move?.redEye) && l.energy_pct < 50)
693
+ .map(l => i18nT('un.redflag_redeye', { leg: l.leg, pct: l.energy_pct }))
694
+ return { feasible: true, money_cny: money, legs, red_flags: redFlags, work_window_exclusions: exclusions, skeleton_notes: skeletonNotes.length ? skeletonNotes : undefined }
695
+ } finally {
696
+ releaseZ3(model)
697
+ }
388
698
  }
389
- if (String(await s2.check()) !== 'unsat') {
390
- const legs = await report(s2.model())
391
- suggestions.push({ relax: name, money_cny: legs.reduce((a, l) => a + l.price_cny, 0) })
699
+
700
+ const core = coreOf(s).sort()
701
+ const suggestions: Array<{ relax: string; money_cny: number }> = []
702
+ closeS()
703
+ for (const name of core) {
704
+ const s2 = new Solver()
705
+ try {
706
+ for (const sel of Object.values(allSels)) s2.add(exactlyOne(sel))
707
+ for (const [n2, expr] of Object.entries(assertions)) {
708
+ if (n2 !== name) s2.addAndTrack(expr, n2)
709
+ }
710
+ if (String(await s2.check()) !== 'unsat') {
711
+ const model = s2.model()
712
+ try {
713
+ const legs = await report(model)
714
+ suggestions.push({ relax: name, money_cny: legs.reduce((a, l) => a + l.price_cny, 0) })
715
+ } finally {
716
+ releaseZ3(model)
717
+ }
718
+ }
719
+ } finally {
720
+ releaseZ3(s2)
721
+ }
392
722
  }
723
+ return { feasible: false, unsat_core: core, suggestions }
724
+ } finally {
725
+ closeS()
393
726
  }
394
- return { feasible: false, unsat_core: core, suggestions }
727
+ })
395
728
  }
396
729
 
397
730
  // ---- 候选形态求解(枚举过滤,与 py solve_choice_segment 对齐) --------------------
@@ -504,7 +837,7 @@ export function solveChoiceSegment(spec: JourneySpecTS, req: TravelRequest): Rec
504
837
  conds['budget_cny'] = Math.min(...budgetDropped.map(c => c.cost.moneyCny))
505
838
  }
506
839
  if (opt.bestMonths?.length) conds['best_months'] = opt.bestMonths
507
- wish = { name: opt.label, conditions: conds, reason: `${windowDays} 天窗口装不下(目的需 ${minDays} 天)` }
840
+ wish = { name: opt.label, conditions: conds, reason: i18nT('sg.wish_reason_window', { days: windowDays, minDays }) }
508
841
  }
509
842
  verdicts.push({
510
843
  candidate_id: opt.id, name: opt.label, feasible: false, imagery_match: opt.score ?? 0,
@@ -524,57 +857,63 @@ function renderCandidateMarkdown(req: TravelRequest, verdicts: Array<Record<stri
524
857
  const feasible = verdicts.filter(v => v['feasible']).sort((a, b) => (b['imagery_match'] as number) - (a['imagery_match'] as number))
525
858
  const parked = verdicts.filter(v => !v['feasible'])
526
859
  const lines: string[] = []
527
- lines.push(`> 憧憬:${req.note}`)
528
- lines.push(`> 已识别约束:窗口 ${req.windowDays} 天 | 预算 ¥${req.budgetCny} | `
529
- + `动机(休整改写需求 ${requiredUsableHours(req.motivation).toFixed(1)}h 有效休整)`)
860
+ lines.push(i18nT('md.header', { note: req.note }))
861
+ lines.push(i18nT('md.constraints', {
862
+ days: req.windowDays, budget: req.budgetCny,
863
+ hours: requiredUsableHours(req.motivation).toFixed(1),
864
+ }))
530
865
  lines.push('')
531
866
  for (const v of parked) {
532
867
  const core = (v['unsat_core'] as string[]) ?? []
533
- lines.push(`**${v['name']}:现在不行**——冲突约束:${core.join('、')}。`)
868
+ lines.push(i18nT('md.infeasible', { name: String(v['name']), core: core.join('、') }))
534
869
  const sug = ((v['suggestions'] as Array<Record<string, unknown>>) ?? [])[0]
535
- if (sug) lines.push(`- 放宽 ${sug['relax']}:约 ¥${sug['resulting_money_cny']}`)
870
+ if (sug) lines.push(i18nT('md.relax_one', { relax: String(sug['relax']), money: String(sug['resulting_money_cny'] ?? '') }))
536
871
  const wish = v['wish_pool'] as Record<string, unknown> | null
537
872
  if (wish) {
538
873
  const conds = wish['conditions'] as Record<string, unknown>
539
- const budgetNote = conds['budget_cny'] ? `、约 ¥${conds['budget_cny']}` : ''
874
+ const budgetNote = conds['budget_cny'] ? i18nT('md.wish_budget', { cny: String(conds['budget_cny']) }) : ''
540
875
  const months = conds['best_months'] as number[] | undefined
541
- const season = months ? `,${months} 月最佳` : ''
542
- lines.push(`- 已放入「下一次出发」清单:需要 ${conds['days']} 天${budgetNote}${season}`)
876
+ const season = months ? i18nT('md.wish_season', { months: String(months) }) : ''
877
+ lines.push(i18nT('md.wish_pool', { days: String(conds['days']), budgetNote, season }))
543
878
  }
544
879
  }
545
880
  lines.push('')
546
881
  for (const v of feasible) {
547
882
  const ch = v['chosen'] as Record<string, unknown>
548
- const t = v['true_cost'] as Record<string, unknown>
549
- lines.push(`**${v['name']}:可行**`
550
- + `(${ch['out_service']} 出发,${ch['out_transfer']} 接驳,`
551
- + `起床 ${t['wake']},到达精力 ${t['energy_arrival_pct']}%,`
552
- + `门到门 ${t['door_to_door_out']},有效休整 ${t['usable_hours']}h,共 ¥${t['money_cny']})`)
883
+ const tc = v['true_cost'] as Record<string, unknown>
884
+ lines.push(i18nT('md.feasible_unified', {
885
+ name: String(v['name']), svc: String(ch['out_service']), mode: String(ch['out_transfer']),
886
+ wake: String(tc['wake']), energy: String(tc['energy_arrival_pct']), d2d: String(tc['door_to_door_out']),
887
+ hours: String(tc['usable_hours']), money: String(tc['money_cny']),
888
+ }))
553
889
  }
554
890
  if (feasible.length) {
555
891
  lines.push('')
556
892
  const best = feasible[0]
557
893
  const alt = feasible[1]
558
- lines.push(`**建议:${best['name']}**(意象匹配 ${((best['imagery_match'] as number) * 100).toFixed(0)}%)。`
559
- + (alt ? `备选:${alt['name']}(¥${(alt['true_cost'] as Record<string, unknown>)['money_cny']},匹配 ${((alt['imagery_match'] as number) * 100).toFixed(0)}%)。` : ''))
894
+ lines.push(i18nT('md.recommend', { best: String(best['name']), match: ((best['imagery_match'] as number) * 100).toFixed(0) })
895
+ + (alt ? i18nT('md.alt', {
896
+ name: String(alt['name']), money: String((alt['true_cost'] as Record<string, unknown>)['money_cny']),
897
+ match: ((alt['imagery_match'] as number) * 100).toFixed(0),
898
+ }) : ''))
560
899
  }
561
900
  lines.push('')
562
- lines.push('**待你决定的两个问题**:')
901
+ lines.push(i18nT('md.decide_two'))
563
902
  if (feasible.length >= 2) {
564
- lines.push(`1. ${feasible[0]['name']} 还是 ${feasible[1]['name']}?(前者更贴意象,后者更省)`)
903
+ lines.push(i18nT('md.q_choice', { a: String(feasible[0]['name']), b: String(feasible[1]['name']) }))
565
904
  } else if (feasible.length === 1) {
566
- lines.push(`1. 就去 ${feasible[0]['name']} 吗?`)
905
+ lines.push(i18nT('md.q_single', { name: String(feasible[0]['name']) }))
567
906
  } else {
568
- lines.push('1. 所有候选都不可行——考虑放宽哪条约束?')
907
+ lines.push(i18nT('md.q_none'))
569
908
  }
570
909
  const p0 = parked[0]
571
910
  const p0wish = p0?.['wish_pool'] as Record<string, unknown> | null
572
911
  if (p0wish) {
573
912
  const conds = p0wish['conditions'] as Record<string, unknown>
574
- lines.push(`2. 把 ${p0!['name']} 留给「下一次出发」(${conds['days']} 天起),这次先去可行的?`)
913
+ lines.push(i18nT('md.q_wish', { name: String(p0!['name']), days: String(conds['days']) }))
575
914
  } else if (feasible.length) {
576
915
  const ch = feasible[0]['chosen'] as Record<string, unknown>
577
- lines.push(`2. 出发班次选 ${ch['out_service']} 还是更晚的?`)
916
+ lines.push(i18nT('md.q_depart', { id: String(ch['out_service']), dep: '' }))
578
917
  }
579
918
  return lines.join('\n')
580
919
  }