@danceiny/gotry 0.0.1-rc.8 → 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 (506) hide show
  1. package/README.md +172 -217
  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 -49
  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 +968 -0
  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 +126 -0
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +141 -0
  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 +54 -0
  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 +50 -0
  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 +107 -0
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +109 -0
  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 +686 -0
  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 +72 -0
  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 +68 -10
  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 +67 -0
  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 +480 -0
  191. package/dist/scripts/ledger-workflow-crash.js +32 -0
  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 +49 -0
  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 +29 -31
  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 +43 -42
  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 +31 -0
  227. package/dist/scripts/session-attach-poc.js +62 -0
  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 +60 -0
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +29 -0
  233. package/dist/scripts/session-tests.js +862 -0
  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 +703 -219
  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 +261 -0
  253. package/dist/scripts/state-cli.js +585 -0
  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 +59 -0
  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 +86 -0
  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 +2305 -731
  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 +358 -162
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +47 -0
  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 +767 -0
  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 +59 -0
  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 +538 -360
  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 +296 -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 +2107 -224
  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 +278 -40
  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 +360 -89
  506. package/ts/src/wish-pool.ts +79 -0
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
@@ -28,10 +32,15 @@ export interface MoveSpecTS {
28
32
  destTransferMin: number
29
33
  redEye?: boolean
30
34
  redEyeDurationMin?: number
35
+ /** D-6:红眼航段落地后接驳(机场→住处/办公室)的乘车补眠时长(分钟);机上已算,落地接驳未算 */
36
+ groundRecoveryMin?: number
31
37
  /** D-5:目的地相对出发地的时差(KMG-BKK=+60,DXB-SZX=-240) */
32
38
  tzOffsetMin?: number
33
39
  /** M-1:出发地 UTC 偏移(工作窗口换算用) */
34
40
  originTzOffsetMin?: number
41
+ /** Issue #343 v2:dep/arr 的 IANA zone(从 option.date + 服务 HH:MM 解析;只读镜像,不算术)。 */
42
+ ianaDepZone?: string
43
+ ianaArrZone?: string
35
44
  }
36
45
 
37
46
  /** M-1:旅行者工作窗口(家时区) */
@@ -40,6 +49,14 @@ export interface WorkWindowSpec {
40
49
  startMin: number
41
50
  endMin: number
42
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
43
60
  }
44
61
 
45
62
  export interface StaySpecTS {
@@ -53,6 +70,8 @@ export interface StaySpecTS {
53
70
  export interface SegmentOptionTS {
54
71
  id: string
55
72
  label: string
73
+ /** 候选级日期;缺省时使用所属 segment.date */
74
+ date?: string
56
75
  move?: MoveSpecTS
57
76
  stay?: StaySpecTS
58
77
  score?: number
@@ -81,20 +100,13 @@ export interface JourneySpecTS {
81
100
  workWindow?: WorkWindowSpec
82
101
  /** 骨架层开关(§7-1):true 时对带 route 提示的段做通航性三值标注 */
83
102
  skeletonHub?: boolean
103
+ /** Internal only; symbol-keyed so JSON/public tool schemas never expose pack version. */
104
+ readonly [FLIGHT_PACK_VERSION]?: 1 | 2
84
105
  }
85
106
 
107
+ // Z3 运行时收敛到 z3-shared(单一 WASM 实例 + 单一 Context + 会话级互斥)——
108
+ // 此前模块级 z3Promise 三份并存是 WASM race/OOM 的根因(见 z3-shared.ts 头注)。
86
109
  /* eslint-disable @typescript-eslint/no-explicit-any */
87
- let z3Promise: Promise<any> | null = null
88
-
89
- async function getZ3(): Promise<any> {
90
- // 延迟到首次实际调用 solveUnified 才加载:避免 dsh 加载 GoTry 模块时启动
91
- // WASM worker,引起 worker 线程内存冲突(z3-built.wasm 多线程 unsafe)。
92
- if (!z3Promise) {
93
- const { init } = await import('z3-solver')
94
- z3Promise = (async () => (await init()).Context('main'))()
95
- }
96
- return z3Promise
97
- }
98
110
 
99
111
  /** 航班数据包(data/flights_2026.json 形态)→ 段链 */
100
112
  const SEGMENT_ROUTES: Record<string, string> = {
@@ -114,6 +126,7 @@ export function segmentsFromCandidate(req: TravelRequest, candidates: Candidate[
114
126
  const options: SegmentOptionTS[] = candidates.map(c => ({
115
127
  id: c.id,
116
128
  label: c.name,
129
+ date: c.date,
117
130
  score: c.imageryMatch,
118
131
  bestMonths: c.bestMonths,
119
132
  minDays: c.minDaysForPurpose,
@@ -149,6 +162,17 @@ export function segmentsFromCandidate(req: TravelRequest, candidates: Candidate[
149
162
  }
150
163
 
151
164
  export function parseFlightPackToSpec(pack: Record<string, unknown>): JourneySpecTS {
165
+ // Issue #343 v2:IANA path;v1(无 version 字段或 version===1)走原数值口径。
166
+ const version = pack['version'] === undefined ? 1 : Number(pack['version'])
167
+ if (version === 2) return parseFlightPackToSpecV2(pack)
168
+ if (version !== 1) {
169
+ throw Object.assign(new Error(`parseFlightPackToSpec: unsupported pack version ${version}`), { code: 'unsupported_pack_version' })
170
+ }
171
+ return parseFlightPackToSpecV1(pack)
172
+ }
173
+
174
+ /** v1 数值口径(path 保留为 export,便于 §7-1/§6 直接复用与差分) */
175
+ export function parseFlightPackToSpecV1(pack: Record<string, unknown>): JourneySpecTS {
152
176
  const legs = (pack['legs'] as Array<Record<string, unknown>>).map(l => ({
153
177
  id: String(l['id']),
154
178
  role: ((l['services'] as unknown[]).length === 1 ? 'fixed' : 'choice') as 'fixed' | 'choice',
@@ -182,6 +206,7 @@ export function parseFlightPackToSpec(pack: Record<string, unknown>): JourneySpe
182
206
  endMin: Number(ww['end_min']),
183
207
  workdays: (ww['workdays'] as number[] | undefined) ?? [0, 1, 2, 3, 4],
184
208
  } : undefined,
209
+ [FLIGHT_PACK_VERSION]: 1,
185
210
  }
186
211
  // M-1:班次的星期标注挂到 Option(缺省=不受工作窗口约束)
187
212
  for (const l of pack['legs'] as Array<Record<string, unknown>>) {
@@ -196,44 +221,251 @@ export function parseFlightPackToSpec(pack: Record<string, unknown>): JourneySpe
196
221
  return spec
197
222
  }
198
223
 
224
+ /** Issue #343 v2:IANA zone + 当地日期解析;数值 tz_* 字段不可用(混合形态解析时拒收)。 */
225
+ export function parseFlightPackToSpecV2(pack: Record<string, unknown>): JourneySpecTS {
226
+ const legsRaw = pack['legs'] as Array<Record<string, unknown>>
227
+ if (!Array.isArray(legsRaw) || legsRaw.length === 0) {
228
+ throw Object.assign(new Error('parseFlightPackToSpecV2: legs missing/empty'), { code: 'v2_legs_missing' })
229
+ }
230
+ const meta = (pack['meta'] ?? {}) as Record<string, unknown>
231
+ const ww = meta['work_window'] as Record<string, unknown> | undefined
232
+
233
+ const legs: SegmentTS[] = []
234
+ for (const l of legsRaw) {
235
+ // v2 不允许混合数值 tz_offset_min/origin_tz_offset_min(早 fail)
236
+ if ('tz_offset_min' in l || 'origin_tz_offset_min' in l) {
237
+ 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' })
238
+ }
239
+ const legDate = l['date'] ? String(l['date']) : ''
240
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(legDate)) {
241
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: leg ${l['id']} requires ISO date`), { code: 'v2_leg_date_missing' })
242
+ }
243
+ const ianaDepZone = String(l['iana_dep_zone'] ?? '')
244
+ const ianaArrZone = String(l['iana_arr_zone'] ?? '')
245
+ if (!ianaDepZone || !ianaArrZone) {
246
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: leg ${l['id']} needs iana_dep_zone and iana_arr_zone`), { code: 'v2_zone_missing' })
247
+ }
248
+ const options: SegmentOptionTS[] = (l['services'] as Array<Record<string, unknown>>).map(sv => {
249
+ const svcDepDate = sv['dep_local_date'] ? String(sv['dep_local_date']) : legDate
250
+ const svcArrDate = sv['arr_local_date'] ? String(sv['arr_local_date']) : legDate
251
+ const depZone = sv['iana_dep_zone'] ? String(sv['iana_dep_zone']) : ianaDepZone
252
+ const arrZone = sv['iana_arr_zone'] ? String(sv['iana_arr_zone']) : ianaArrZone
253
+ const depR = resolveOffsetForLocalDate(depZone, svcDepDate, String(sv['dep']))
254
+ if (!depR.ok) {
255
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: dep zone/date failure: ${describeResolveFailure(depR)}`), { code: 'v2_dep_resolve_failed' })
256
+ }
257
+ const arrR = resolveOffsetForLocalDate(arrZone, svcArrDate, String(sv['arr']))
258
+ if (!arrR.ok) {
259
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: arr zone/date failure: ${describeResolveFailure(arrR)}`), { code: 'v2_arr_resolve_failed' })
260
+ }
261
+ // Issue #343 root 复核:d2d canonical 输入必须在 parse 边界校验严格 chronologically after,
262
+ // 否则把病态数据喂给 model.doorToDoorFromMove → throw NonpositiveDurationError → 被
263
+ // solveUnified WASM guard 误报为 wasm_runtime_error。
264
+ if (arrR.utcMs <= depR.utcMs) {
265
+ throw Object.assign(new Error(
266
+ `parseFlightPackToSpecV2: service ${String(sv['id'])} arrival instant (${new Date(arrR.utcMs).toISOString()}) must be strictly after departure instant (${new Date(depR.utcMs).toISOString()})`,
267
+ ), { code: 'v2_arr_not_after_dep' })
268
+ }
269
+ // tzOffsetMin = arr - dep(positive = 抵达地更靠东)
270
+ const tzOffsetMin = arrR.offsetMin - depR.offsetMin
271
+ return {
272
+ id: String(sv['id']),
273
+ label: `${String(sv['id'])} ${String(l['note'] ?? '').slice(0, 18)}`,
274
+ date: svcDepDate,
275
+ move: {
276
+ hub: String(l['hub'] ?? ''),
277
+ services: [{
278
+ id: String(sv['id']),
279
+ depMin: hhmmToMin(String(sv['dep'])),
280
+ arrMin: hhmmToMin(String(sv['arr'])),
281
+ priceCny: Number(sv['price_cny']),
282
+ // Issue #343 v2:把"墙上时刻"投影成真实 UTC instant 留给 evaluate / workWindow 用;
283
+ // d2d 公式走 (arrUtcMs - depUtcMs)/60000,跨日 / DST / 反向日界线都正确。
284
+ depUtcMs: depR.utcMs,
285
+ arrUtcMs: arrR.utcMs,
286
+ }],
287
+ bufferMin: Number(l['buffer_min']),
288
+ originTransferMin: Number(l['origin_transfer_min']),
289
+ destTransferMin: Number(l['dest_transfer_min']),
290
+ redEye: Boolean(l['red_eye']),
291
+ redEyeDurationMin: Number(sv['red_eye_duration_min'] ?? l['red_eye_duration_min'] ?? 0),
292
+ groundRecoveryMin: l['ground_recovery_min'] ? Number(l['ground_recovery_min']) : undefined,
293
+ tzOffsetMin,
294
+ originTzOffsetMin: depR.offsetMin,
295
+ ianaDepZone: depZone,
296
+ ianaArrZone: arrZone,
297
+ },
298
+ depWeekday: weekdayFromYmd(svcDepDate),
299
+ }
300
+ })
301
+
302
+ const role = ((l['services'] as unknown[]).length === 1 ? 'fixed' : 'choice') as 'fixed' | 'choice'
303
+ legs.push({
304
+ id: String(l['id']),
305
+ role,
306
+ note: l['note'] ? String(l['note']) : undefined,
307
+ date: legDate,
308
+ anchors: { arriveByMin: l['arrive_by'] ? hhmmToMin(String(l['arrive_by'])) : undefined },
309
+ route: routeHint(l['id'] as string),
310
+ options,
311
+ })
312
+ }
313
+
314
+ // work_window:home_zone 必需(v2 的 home 偏移走 IANA,数值字段不再权威);同时给两值报 reject。
315
+ let workWindow: WorkWindowSpec | undefined
316
+ if (ww) {
317
+ const hasHomeZone = Boolean(ww['home_zone'])
318
+ const hasHomeNum = ww['home_tz_offset_min'] !== undefined
319
+ if (hasHomeZone && hasHomeNum) {
320
+ throw Object.assign(new Error('parseFlightPackToSpecV2: work_window cannot combine home_zone and home_tz_offset_min'), { code: 'v2_ww_conflict' })
321
+ }
322
+ if (!hasHomeZone) {
323
+ throw Object.assign(new Error('parseFlightPackToSpecV2: work_window.home_zone is required for v2'), { code: 'v2_ww_home_zone_required' })
324
+ }
325
+ // 在 v2 parse 边界即时验证 home_zone 是已知 IANA zone(root counterexample #3:不许 fail-open)。
326
+ // 用 tz-resolver 的 isKnownZoneLoose:接受 Intl.DateTimeFormat 能识别的合法 alias(US/Pacific / ROK / Etc/GMT+8)。
327
+ const homeZone = String(ww['home_zone'])
328
+ if (!isKnownZoneLoose(homeZone)) {
329
+ throw Object.assign(new Error(`parseFlightPackToSpecV2: work_window.home_zone '${homeZone}' is not a known IANA zone`), { code: 'v2_home_zone_unknown' })
330
+ }
331
+ const startHHmm = ww['start'] ? String(ww['start']) : undefined
332
+ const endHHmm = ww['end'] ? String(ww['end']) : undefined
333
+ const startMin = ww['start_min'] !== undefined ? Number(ww['start_min']) : (startHHmm ? hhmmToMin(startHHmm) : hhmmToMin('10:00'))
334
+ const endMin = ww['end_min'] !== undefined ? Number(ww['end_min']) : (endHHmm ? hhmmToMin(endHHmm) : hhmmToMin('19:00'))
335
+ // numeric homeTzOffsetMin 仅占位:在 v2 工作窗口过滤时按 home_zone + dep_date 重算;预填 leg-date 占位 0
336
+ workWindow = {
337
+ homeTzOffsetMin: 0,
338
+ startMin,
339
+ endMin,
340
+ workdays: (ww['workdays'] as number[] | undefined) ?? [0, 1, 2, 3, 4],
341
+ homeZone,
342
+ startHHmm,
343
+ endHHmm,
344
+ }
345
+ }
346
+
347
+ return { segments: legs, workWindow, [FLIGHT_PACK_VERSION]: 2 }
348
+ }
349
+
199
350
  const WEEKDAY_IDX: Record<string, number> = { mon: 0, tue: 1, wed: 2, thu: 3, fri: 4, sat: 5, sun: 6 }
200
351
  const WEEKDAY_CN: Record<string, string> = { mon: '一', tue: '二', wed: '三', thu: '四', fri: '五', sat: '六', sun: '日' }
201
352
 
202
- /** M-1:工作日的工作窗口内起飞 → 排除理由;否则 null(与 py _work_window_blocks 对齐)。 */
353
+ /** YYYY-MM-DD → mon/tue/wed/thu/fri/sat/sun;v2 路径给 option 标 depWeekday 用。 */
354
+ export function weekdayFromYmd(ymd: string): string {
355
+ const m = ymd.match(/^(\d{4})-(\d{2})-(\d{2})$/)
356
+ if (!m) throw new Error(`weekdayFromYmd: bad ymd ${ymd}`)
357
+ const dow = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]))).getUTCDay()
358
+ return ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'][dow]!
359
+ }
360
+
361
+ /** M-1:工作日的工作窗口内起飞 → 排除理由;否则 null(与 py _work_window_blocks 对齐)。
362
+ *
363
+ * Issue #343 v2:home zone 存在时,必须把"出发真实 UTC instant"投影到 home zone
364
+ * 取该 zone 的 date/weekday/HH:MM(root counterexample #2:Tokyo 06:30 ≈ LA 14:30 prev day);
365
+ * 不再用 dep-local date 直接当 home date。
366
+ *
367
+ * root 评审复核:`depUtcMs` 是已知确定的 instant,直接 `readWallParts(homeZone, depUtcMs)`
368
+ * 取墙时分量 — 不要把 projected wall time 再喂给 `resolveOffsetForLocalDate` 重做 ±14h 扫描,
369
+ * 否则秋回退日 01:30 会被错误判为 overlap_ambiguous 而 fail-open(已知 instant 永远唯一)。
370
+ *
371
+ * dep/arr 的"墙上本地输入"(leg-level `dep`/`arr` 字段)由 parseFlightPackToSpecV2 在
372
+ * 解析时已用 `resolveOffsetForLocalDate` 拒过 gap/overlap,所以 leg 进 solver 时
373
+ * 两个 instant 一定 strict-after 且偏移已知。
374
+ *
375
+ * home zone 未知时由 v2 parse 边界直接抛 v2_home_zone_unknown 拒绝;
376
+ * 这里兜底只在 v1 legacy 路径出现。
377
+ */
203
378
  function workWindowBlocks(spec: JourneySpecTS, option: SegmentOptionTS): string | null {
204
379
  const ww = spec.workWindow
205
380
  if (!ww || !option.move || !option.depWeekday) return null
206
- if (!((ww.workdays ?? [0, 1, 2, 3, 4]).includes(WEEKDAY_IDX[option.depWeekday]))) return null
207
- const dep = option.move.services[0].depMin
208
- const shift = (option.move.originTzOffsetMin ?? 480) - ww.homeTzOffsetMin
209
- const start = ww.startMin + shift, end = ww.endMin + shift
210
- if (start <= dep && dep <= end) {
381
+
382
+ const svc = option.move.services[0]
383
+ const dep = svc.depMin
384
+ const homeZone = ww.homeZone
385
+
386
+ let homeOffsetMin = ww.homeTzOffsetMin
387
+ let homeDepMin: number | undefined
388
+ let homeDepWeekday: string | undefined
389
+
390
+ if (homeZone) {
391
+ if (typeof svc.depUtcMs !== 'number') {
392
+ // v2 路径必然填了 depUtcMs;没填 = 数据病,按 red flag 排除该 option。
393
+ return i18nT('un.workwindow_unresolvable_reason', {
394
+ reason: `home zone ${homeZone} requires UTC dep instant (v2 path bug)`,
395
+ })
396
+ }
397
+ // 已知 instant 直接投影到 home zone 的墙时 — 无歧义,不必也不应重走 ±14h 扫描。
398
+ const homeParts = readWallParts(homeZone, svc.depUtcMs)
399
+ const homeIso = wallPartsToIso(homeParts)
400
+ homeDepMin = hhmmToMin(homeIso.hhmm)
401
+ homeDepWeekday = weekdayFromYmd(homeIso.ymd)
402
+ }
403
+
404
+ const effectiveDep = homeDepMin ?? dep
405
+ const effectiveWeekday = homeDepWeekday ?? option.depWeekday
406
+ if (!((ww.workdays ?? [0, 1, 2, 3, 4]).includes(WEEKDAY_IDX[effectiveWeekday]))) return null
407
+
408
+ // v2:startMin/endMin 与 homeDepMin 都是 home-zone 墙时分量(已 IANA 归一),
409
+ // 直接比较;不再做 originTzOffsetMin 偏移(root 复核该 shift 是 v1 数值残留,会让窗口错位)。
410
+ // v1 路径(无 homeZone, 走数值 homeTzOffsetMin)保留 shift 兼容旧 spec。
411
+ let start: number, end: number
412
+ if (homeZone) {
413
+ start = ww.startMin
414
+ end = ww.endMin
415
+ } else {
416
+ const shift = (option.move.originTzOffsetMin ?? 480) - homeOffsetMin
417
+ start = ww.startMin + shift
418
+ end = ww.endMin + shift
419
+ }
420
+ if (start <= effectiveDep && effectiveDep <= end) {
211
421
  const hhmm = (m: number) => `${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`
212
- return `周${WEEKDAY_CN[option.depWeekday]} ${hhmm(dep)} 起飞落在工作窗口(当地 ${hhmm(start)}-${hhmm(end)})内`
422
+ return i18nT('un.workwindow_reason', {
423
+ wd: WEEKDAY_CN[effectiveWeekday] ?? '', dep: hhmm(effectiveDep), start: hhmm(start), end: hhmm(end),
424
+ })
213
425
  }
214
426
  return null
215
427
  }
216
428
 
217
- /** D-5:时区感知的段核算(与 py unified._evaluate_option_move 对齐)。 */
429
+ /** D-5:时区感知的段核算(与 py unified._evaluate_option_move 对齐)。
430
+ *
431
+ * Issue #343 d2d 算术唯一权威入口 = model.ts 的 `doorToDoorFromMove`。
432
+ * v2 路径会传 Service.depUtcMs/arrUtcMs(由 parseFlightPackToSpecV2 用 tz-resolver 投出),
433
+ * 此时 d2d 用真实 UTC instant 差 — 跨日 / 反向日界线 / DST 切换都正确;v2 arr<=dep 抛
434
+ * NonpositiveDurationError → 该 option 当作不可行(由 solveUnifiedInner 转 unsat_core)。
435
+ * v1 数值口径无 UTC 字段,走老 (arrMin - depMin) - tzOffsetMin;字节不变。
436
+ */
218
437
  function evaluateOptionMove(segId: string, mv: MoveSpecTS): LegReport & { d2d_min?: number } {
219
438
  const svc = mv.services[0]
439
+ let d2dMin: number
440
+ try {
441
+ d2dMin = doorToDoorFromMove(svc, mv).doorToDoorMin
442
+ } catch (e) {
443
+ if (e instanceof NonpositiveDurationError) {
444
+ // d2d 病态输入 → 把这条 option 抛红 flag;这里走结构化 reject 而非吞错。
445
+ throw Object.assign(new Error(`evaluateOptionMove ${segId}/${svc.id}: ${e.message}`), {
446
+ code: e.code,
447
+ kind: e.kind,
448
+ })
449
+ }
450
+ throw e
451
+ }
220
452
  const wake = svc.depMin - mv.bufferMin - mv.originTransferMin
221
- // 真实时长 = (到达−出发) − 时差;EK329: 215−(−240)=455min=7h35m
222
- const trueFlight = (svc.arrMin - svc.depMin) - (mv.tzOffsetMin ?? 0)
223
- const d2d = mv.bufferMin + mv.originTransferMin + trueFlight + mv.destTransferMin
224
453
  const wakeDisplay = wake < 0 ? `${minToHhmm(wake + 1440)}(前一日)` : minToHhmm(wake)
225
454
  const arriveStay = svc.arrMin + mv.destTransferMin
226
455
 
227
456
  let energy: number
228
457
  if (mv.redEye && (mv.redEyeDurationMin ?? 0) > 0) {
229
458
  const sleepH = ((mv.redEyeDurationMin ?? 0) - 60) / 60
230
- energy = Math.max(30, Math.min(75, 30 + 8 * sleepH))
459
+ // D-6 校准:机上睡眠上限 75;落地接驳(destTransferMin)乘车补眠回血(1h≈+5%,上限 80)
460
+ const groundH = (mv.groundRecoveryMin ?? mv.destTransferMin ?? 0) / 60
461
+ const recovery = Math.min(5, 5 * groundH)
462
+ energy = Math.max(30, Math.min(75 + recovery, 80, 30 + 8 * sleepH + recovery))
231
463
  } else {
232
464
  energy = 100 - 2 * 8
233
465
  if (wake < 5 * 60) energy -= 30
234
466
  else if (wake < 6 * 60) energy -= 25
235
467
  if (arriveStay > 21 * 60) energy -= 10
236
- if (d2d > 6 * 60) energy -= 10
468
+ if (d2dMin > 6 * 60) energy -= 10
237
469
  energy = Math.max(0, energy)
238
470
  }
239
471
  return {
@@ -242,18 +474,22 @@ function evaluateOptionMove(segId: string, mv: MoveSpecTS): LegReport & { d2d_mi
242
474
  wake: wakeDisplay,
243
475
  wakeMin: wake,
244
476
  arrive_stay: minToHhmm(arriveStay),
245
- door_to_door: `${Math.floor(d2d / 60)}h${String(d2d % 60).padStart(2, '0')}m`,
246
- d2d_min: d2d,
477
+ door_to_door: `${Math.floor(d2dMin / 60)}h${String(d2dMin % 60).padStart(2, '0')}m`,
478
+ d2d_min: d2dMin,
247
479
  energy_pct: Math.round(energy),
248
480
  price_cny: svc.priceCny,
249
481
  }
250
482
  }
251
483
 
484
+ function releaseZ3(resource: { release?: () => void } | null | undefined): void {
485
+ if (typeof resource?.release === 'function') resource.release()
486
+ }
487
+
252
488
  /** 航班链形态求解:按 Option 选择,锚点命名约束,core 剥竖线(D-2 修复) */
253
489
  export async function solveUnified(spec: JourneySpecTS): Promise<{
254
490
  feasible: boolean
255
491
  money_cny?: number
256
- legs?: Array<LegReport & { leg: string }>
492
+ legs?: Array<LegReport & { leg: string; d2d_min?: number }>
257
493
  red_flags?: string[]
258
494
  unsat_core?: string[]
259
495
  suggestions?: Array<{ relax: string; money_cny: number }>
@@ -273,7 +509,7 @@ export async function solveUnified(spec: JourneySpecTS): Promise<{
273
509
  async function solveUnifiedInner(spec: JourneySpecTS): Promise<{
274
510
  feasible: boolean
275
511
  money_cny?: number
276
- legs?: Array<LegReport & { leg: string }>
512
+ legs?: Array<LegReport & { leg: string; d2d_min?: number }>
277
513
  red_flags?: string[]
278
514
  unsat_core?: string[]
279
515
  suggestions?: Array<{ relax: string; money_cny: number }>
@@ -305,7 +541,9 @@ async function solveUnifiedInner(spec: JourneySpecTS): Promise<{
305
541
  }
306
542
  }
307
543
 
308
- const z3 = await getZ3()
544
+ // Z3 会话进互斥门(骨架/预过滤等网络段留在门外):solveUnifiedInner 的判定段
545
+ // 从 here 到 return 全部独占共享实例。
546
+ return withZ3('unified.solveUnifiedInner', async z3 => {
309
547
  const { Bool, If, Int, Solver, Sum } = z3
310
548
  const wakeFloor = spec.defaultWakeFloorMin ?? hhmmToMin('06:00')
311
549
 
@@ -343,50 +581,77 @@ async function solveUnifiedInner(spec: JourneySpecTS): Promise<{
343
581
  }
344
582
 
345
583
  const s = new Solver()
346
- for (const sel of Object.values(allSels)) s.add(exactlyOne(sel))
347
- for (const [name, expr] of Object.entries(assertions)) s.addAndTrack(expr, name)
348
-
349
- const report = async (model: any): Promise<Array<LegReport & { leg: string }>> => {
350
- const out: Array<LegReport & { leg: string }> = []
351
- for (const seg of spec.segments) {
352
- for (let i = 0; i < allSels[seg.id].length; i++) {
353
- const v = await maybeAwait(model.eval(allSels[seg.id][i], true))
354
- if (String(v) !== 'true') continue
355
- const o = seg.options[i]
356
- out.push({ leg: seg.id, ...evaluateOptionMove(seg.id, o.move!) })
357
- break
358
- }
584
+ let sOpen = true
585
+ const closeS = () => {
586
+ if (sOpen) {
587
+ releaseZ3(s)
588
+ sOpen = false
359
589
  }
360
- return out
361
- }
362
-
363
- async function maybeAwait<T>(v: T | Promise<T>): Promise<T> {
364
- return v instanceof Promise ? await v : v
365
590
  }
591
+ try {
592
+ for (const sel of Object.values(allSels)) s.add(exactlyOne(sel))
593
+ for (const [name, expr] of Object.entries(assertions)) s.addAndTrack(expr, name)
594
+
595
+ const report = async (model: any): Promise<Array<LegReport & { leg: string }>> => {
596
+ const out: Array<LegReport & { leg: string }> = []
597
+ for (const seg of spec.segments) {
598
+ for (let i = 0; i < allSels[seg.id].length; i++) {
599
+ const v = await maybeAwait(model.eval(allSels[seg.id][i], true))
600
+ if (String(v) !== 'true') continue
601
+ const o = seg.options[i]
602
+ out.push({ leg: seg.id, ...evaluateOptionMove(seg.id, o.move!) })
603
+ break
604
+ }
605
+ }
606
+ return out
607
+ }
366
608
 
367
- if (String(await s.check()) !== 'unsat') {
368
- const legs = await report(s.model())
369
- const money = legs.reduce((a, l) => a + l.price_cny, 0)
370
- const redFlags = legs
371
- .filter(l => spec.segments.find(sg => sg.id === l.leg)?.options.some(o => o.move?.redEye) && l.energy_pct < 50)
372
- .map(l => `${l.leg} 落地精力仅 ${l.energy_pct}%(红眼后直奔事务,当日不宜安排重要会议)`)
373
- return { feasible: true, money_cny: money, legs, red_flags: redFlags, work_window_exclusions: exclusions, skeleton_notes: skeletonNotes.length ? skeletonNotes : undefined }
374
- }
609
+ async function maybeAwait<T>(v: T | Promise<T>): Promise<T> {
610
+ return v instanceof Promise ? await v : v
611
+ }
375
612
 
376
- const core = coreOf(s).sort()
377
- const suggestions: Array<{ relax: string; money_cny: number }> = []
378
- for (const name of core) {
379
- const s2 = new Solver()
380
- for (const sel of Object.values(allSels)) s2.add(exactlyOne(sel))
381
- for (const [n2, expr] of Object.entries(assertions)) {
382
- if (n2 !== name) s2.addAndTrack(expr, n2)
613
+ if (String(await s.check()) !== 'unsat') {
614
+ const model = s.model()
615
+ try {
616
+ const legs = await report(model)
617
+ const money = legs.reduce((a, l) => a + l.price_cny, 0)
618
+ const redFlags = legs
619
+ .filter(l => spec.segments.find(sg => sg.id === l.leg)?.options.some(o => o.move?.redEye) && l.energy_pct < 50)
620
+ .map(l => i18nT('un.redflag_redeye', { leg: l.leg, pct: l.energy_pct }))
621
+ return { feasible: true, money_cny: money, legs, red_flags: redFlags, work_window_exclusions: exclusions, skeleton_notes: skeletonNotes.length ? skeletonNotes : undefined }
622
+ } finally {
623
+ releaseZ3(model)
624
+ }
383
625
  }
384
- if (String(await s2.check()) !== 'unsat') {
385
- const legs = await report(s2.model())
386
- suggestions.push({ relax: name, money_cny: legs.reduce((a, l) => a + l.price_cny, 0) })
626
+
627
+ const core = coreOf(s).sort()
628
+ const suggestions: Array<{ relax: string; money_cny: number }> = []
629
+ closeS()
630
+ for (const name of core) {
631
+ const s2 = new Solver()
632
+ try {
633
+ for (const sel of Object.values(allSels)) s2.add(exactlyOne(sel))
634
+ for (const [n2, expr] of Object.entries(assertions)) {
635
+ if (n2 !== name) s2.addAndTrack(expr, n2)
636
+ }
637
+ if (String(await s2.check()) !== 'unsat') {
638
+ const model = s2.model()
639
+ try {
640
+ const legs = await report(model)
641
+ suggestions.push({ relax: name, money_cny: legs.reduce((a, l) => a + l.price_cny, 0) })
642
+ } finally {
643
+ releaseZ3(model)
644
+ }
645
+ }
646
+ } finally {
647
+ releaseZ3(s2)
648
+ }
387
649
  }
650
+ return { feasible: false, unsat_core: core, suggestions }
651
+ } finally {
652
+ closeS()
388
653
  }
389
- return { feasible: false, unsat_core: core, suggestions }
654
+ })
390
655
  }
391
656
 
392
657
  // ---- 候选形态求解(枚举过滤,与 py solve_choice_segment 对齐) --------------------
@@ -499,7 +764,7 @@ export function solveChoiceSegment(spec: JourneySpecTS, req: TravelRequest): Rec
499
764
  conds['budget_cny'] = Math.min(...budgetDropped.map(c => c.cost.moneyCny))
500
765
  }
501
766
  if (opt.bestMonths?.length) conds['best_months'] = opt.bestMonths
502
- wish = { name: opt.label, conditions: conds, reason: `${windowDays} 天窗口装不下(目的需 ${minDays} 天)` }
767
+ wish = { name: opt.label, conditions: conds, reason: i18nT('sg.wish_reason_window', { days: windowDays, minDays }) }
503
768
  }
504
769
  verdicts.push({
505
770
  candidate_id: opt.id, name: opt.label, feasible: false, imagery_match: opt.score ?? 0,
@@ -519,57 +784,63 @@ function renderCandidateMarkdown(req: TravelRequest, verdicts: Array<Record<stri
519
784
  const feasible = verdicts.filter(v => v['feasible']).sort((a, b) => (b['imagery_match'] as number) - (a['imagery_match'] as number))
520
785
  const parked = verdicts.filter(v => !v['feasible'])
521
786
  const lines: string[] = []
522
- lines.push(`> 憧憬:${req.note}`)
523
- lines.push(`> 已识别约束:窗口 ${req.windowDays} 天 | 预算 ¥${req.budgetCny} | `
524
- + `动机(休整改写需求 ${requiredUsableHours(req.motivation).toFixed(1)}h 有效休整)`)
787
+ lines.push(i18nT('md.header', { note: req.note }))
788
+ lines.push(i18nT('md.constraints', {
789
+ days: req.windowDays, budget: req.budgetCny,
790
+ hours: requiredUsableHours(req.motivation).toFixed(1),
791
+ }))
525
792
  lines.push('')
526
793
  for (const v of parked) {
527
794
  const core = (v['unsat_core'] as string[]) ?? []
528
- lines.push(`**${v['name']}:现在不行**——冲突约束:${core.join('、')}。`)
795
+ lines.push(i18nT('md.infeasible', { name: String(v['name']), core: core.join('、') }))
529
796
  const sug = ((v['suggestions'] as Array<Record<string, unknown>>) ?? [])[0]
530
- if (sug) lines.push(`- 放宽 ${sug['relax']}:约 ¥${sug['resulting_money_cny']}`)
797
+ if (sug) lines.push(i18nT('md.relax_one', { relax: String(sug['relax']), money: String(sug['resulting_money_cny'] ?? '') }))
531
798
  const wish = v['wish_pool'] as Record<string, unknown> | null
532
799
  if (wish) {
533
800
  const conds = wish['conditions'] as Record<string, unknown>
534
- const budgetNote = conds['budget_cny'] ? `、约 ¥${conds['budget_cny']}` : ''
801
+ const budgetNote = conds['budget_cny'] ? i18nT('md.wish_budget', { cny: String(conds['budget_cny']) }) : ''
535
802
  const months = conds['best_months'] as number[] | undefined
536
- const season = months ? `,${months} 月最佳` : ''
537
- lines.push(`- 已放入「下一次出发」清单:需要 ${conds['days']} 天${budgetNote}${season}`)
803
+ const season = months ? i18nT('md.wish_season', { months: String(months) }) : ''
804
+ lines.push(i18nT('md.wish_pool', { days: String(conds['days']), budgetNote, season }))
538
805
  }
539
806
  }
540
807
  lines.push('')
541
808
  for (const v of feasible) {
542
809
  const ch = v['chosen'] as Record<string, unknown>
543
- const t = v['true_cost'] as Record<string, unknown>
544
- lines.push(`**${v['name']}:可行**`
545
- + `(${ch['out_service']} 出发,${ch['out_transfer']} 接驳,`
546
- + `起床 ${t['wake']},到达精力 ${t['energy_arrival_pct']}%,`
547
- + `门到门 ${t['door_to_door_out']},有效休整 ${t['usable_hours']}h,共 ¥${t['money_cny']})`)
810
+ const tc = v['true_cost'] as Record<string, unknown>
811
+ lines.push(i18nT('md.feasible_unified', {
812
+ name: String(v['name']), svc: String(ch['out_service']), mode: String(ch['out_transfer']),
813
+ wake: String(tc['wake']), energy: String(tc['energy_arrival_pct']), d2d: String(tc['door_to_door_out']),
814
+ hours: String(tc['usable_hours']), money: String(tc['money_cny']),
815
+ }))
548
816
  }
549
817
  if (feasible.length) {
550
818
  lines.push('')
551
819
  const best = feasible[0]
552
820
  const alt = feasible[1]
553
- lines.push(`**建议:${best['name']}**(意象匹配 ${((best['imagery_match'] as number) * 100).toFixed(0)}%)。`
554
- + (alt ? `备选:${alt['name']}(¥${(alt['true_cost'] as Record<string, unknown>)['money_cny']},匹配 ${((alt['imagery_match'] as number) * 100).toFixed(0)}%)。` : ''))
821
+ lines.push(i18nT('md.recommend', { best: String(best['name']), match: ((best['imagery_match'] as number) * 100).toFixed(0) })
822
+ + (alt ? i18nT('md.alt', {
823
+ name: String(alt['name']), money: String((alt['true_cost'] as Record<string, unknown>)['money_cny']),
824
+ match: ((alt['imagery_match'] as number) * 100).toFixed(0),
825
+ }) : ''))
555
826
  }
556
827
  lines.push('')
557
- lines.push('**待你决定的两个问题**:')
828
+ lines.push(i18nT('md.decide_two'))
558
829
  if (feasible.length >= 2) {
559
- lines.push(`1. ${feasible[0]['name']} 还是 ${feasible[1]['name']}?(前者更贴意象,后者更省)`)
830
+ lines.push(i18nT('md.q_choice', { a: String(feasible[0]['name']), b: String(feasible[1]['name']) }))
560
831
  } else if (feasible.length === 1) {
561
- lines.push(`1. 就去 ${feasible[0]['name']} 吗?`)
832
+ lines.push(i18nT('md.q_single', { name: String(feasible[0]['name']) }))
562
833
  } else {
563
- lines.push('1. 所有候选都不可行——考虑放宽哪条约束?')
834
+ lines.push(i18nT('md.q_none'))
564
835
  }
565
836
  const p0 = parked[0]
566
837
  const p0wish = p0?.['wish_pool'] as Record<string, unknown> | null
567
838
  if (p0wish) {
568
839
  const conds = p0wish['conditions'] as Record<string, unknown>
569
- lines.push(`2. 把 ${p0!['name']} 留给「下一次出发」(${conds['days']} 天起),这次先去可行的?`)
840
+ lines.push(i18nT('md.q_wish', { name: String(p0!['name']), days: String(conds['days']) }))
570
841
  } else if (feasible.length) {
571
842
  const ch = feasible[0]['chosen'] as Record<string, unknown>
572
- lines.push(`2. 出发班次选 ${ch['out_service']} 还是更晚的?`)
843
+ lines.push(i18nT('md.q_depart', { id: String(ch['out_service']), dep: '' }))
573
844
  }
574
845
  return lines.join('\n')
575
846
  }