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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (533) hide show
  1. package/README.md +172 -219
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1788 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -56
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +161 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +553 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +947 -110
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +676 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/provider-detail-sanitize.js +64 -0
  41. package/dist/capabilities/resilience.js +191 -0
  42. package/dist/capabilities/route-provider-conformance.js +341 -0
  43. package/dist/capabilities/session/action-cache.js +50 -44
  44. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  45. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  46. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  47. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  48. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  49. package/dist/capabilities/session/benchmark.js +189 -0
  50. package/dist/capabilities/session/bridge-ledger.js +224 -0
  51. package/dist/capabilities/session/extension-bridge.js +636 -0
  52. package/dist/capabilities/session/extension-channel.js +109 -0
  53. package/dist/capabilities/session/extension-distribution.js +230 -0
  54. package/dist/capabilities/session/extract.js +31 -21
  55. package/dist/capabilities/session/golden-score.js +114 -0
  56. package/dist/capabilities/session/health-watch.js +215 -0
  57. package/dist/capabilities/session/read-guard.js +75 -24
  58. package/dist/capabilities/session/static-flight-golden.js +150 -0
  59. package/dist/capabilities/session/transport.js +97 -83
  60. package/dist/capabilities/session/wizard.js +117 -0
  61. package/dist/capabilities/session-consent.js +178 -0
  62. package/dist/capabilities/session-login.js +170 -0
  63. package/dist/capabilities/session-search.js +635 -44
  64. package/dist/capabilities/spawn-bounded.js +203 -0
  65. package/dist/capabilities/sponsor-plugin.js +341 -0
  66. package/dist/capabilities/visa-policy.js +140 -0
  67. package/dist/capabilities/weather.js +243 -138
  68. package/dist/data/session-golden-20.json +25 -0
  69. package/dist/data/sf-golden-manifest.json +103 -0
  70. package/dist/data/sf-static-routes.json +91 -0
  71. package/dist/scripts/action-cache-tests.js +24 -59
  72. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  73. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  74. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  75. package/dist/scripts/agent-reach-error-tests.js +354 -0
  76. package/dist/scripts/agent-reach-tests.js +26 -18
  77. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  78. package/dist/scripts/anything-path-measure-tests.js +133 -0
  79. package/dist/scripts/anything-path-measure.js +150 -0
  80. package/dist/scripts/anything-tests.js +250 -60
  81. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  82. package/dist/scripts/artifacts-capability-tests.js +727 -0
  83. package/dist/scripts/async-collect.js +50 -11
  84. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  85. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  86. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  87. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  88. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  89. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  90. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  91. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  92. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  93. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  94. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  95. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +126 -0
  96. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  97. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  98. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  99. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  100. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  101. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  102. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  103. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  104. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  105. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  106. package/dist/scripts/booking-executor-tests.js +80 -0
  107. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  108. package/dist/scripts/booking-saga-tests.js +125 -0
  109. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  110. package/dist/scripts/bootstrap-tests.js +1549 -0
  111. package/dist/scripts/bridge-events-tests.js +239 -0
  112. package/dist/scripts/bridge-ledger-tests.js +428 -0
  113. package/dist/scripts/build-changelog.js +238 -0
  114. package/dist/scripts/build-metrics-report.js +331 -0
  115. package/dist/scripts/changelog-tests.js +118 -0
  116. package/dist/scripts/channel-probe-tests.js +103 -0
  117. package/dist/scripts/channel-probe.js +169 -0
  118. package/dist/scripts/channel-registry-tests.js +208 -0
  119. package/dist/scripts/city-scenario-tier-tests.js +234 -0
  120. package/dist/scripts/companion-tests.js +25 -70
  121. package/dist/scripts/dida-cookie-probe.js +87 -0
  122. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  123. package/dist/scripts/dida-sw-probe.js +114 -0
  124. package/dist/scripts/diff-test.js +17 -12
  125. package/dist/scripts/doctor-tests.js +600 -0
  126. package/dist/scripts/drill-multiuser-ledger-tests.js +1076 -0
  127. package/dist/scripts/drill-sdk-descendant-cleanup-tests.js +606 -0
  128. package/dist/scripts/drill-w2a-sensor-tests.js +788 -0
  129. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  130. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  131. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  132. package/dist/scripts/dsh-llm-tests.js +437 -0
  133. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  134. package/dist/scripts/dsh-runtime-closure.js +162 -0
  135. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  136. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  137. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  138. package/dist/scripts/effect-tests.js +539 -0
  139. package/dist/scripts/engine-run.js +1 -3
  140. package/dist/scripts/engine-tests.js +16 -10
  141. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  142. package/dist/scripts/evaluation-contract-tests.js +268 -0
  143. package/dist/scripts/extension-distribution-cli.js +39 -0
  144. package/dist/scripts/extension-distribution-tests.js +443 -0
  145. package/dist/scripts/extension-tests.js +988 -0
  146. package/dist/scripts/external-event-tests.js +350 -0
  147. package/dist/scripts/fact-gate-tests.js +869 -0
  148. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  149. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  150. package/dist/scripts/flight-malformed-tests.js +159 -0
  151. package/dist/scripts/flyai-setup-tests.js +228 -0
  152. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  153. package/dist/scripts/flyai-tests.js +692 -0
  154. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  155. package/dist/scripts/fx-contract-tests.js +201 -0
  156. package/dist/scripts/g5-guard-tests.js +150 -0
  157. package/dist/scripts/g5-guard.js +193 -0
  158. package/dist/scripts/gen-lunar-anchors.js +106 -0
  159. package/dist/scripts/geo-atlas-tests.js +283 -0
  160. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  161. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  162. package/dist/scripts/gotry-try-demo.js +49 -0
  163. package/dist/scripts/ground-transfer-tests.js +956 -0
  164. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  165. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  166. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  167. package/dist/scripts/hbcli-tests.js +121 -69
  168. package/dist/scripts/health-watch-cli.js +73 -0
  169. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  170. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  171. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  172. package/dist/scripts/i18n-tests.js +41 -0
  173. package/dist/scripts/incident-tests.js +281 -87
  174. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  175. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  176. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  177. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  178. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  179. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  180. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  181. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  182. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  183. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  184. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  185. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  186. package/dist/scripts/itinerary-deck-tests.js +435 -0
  187. package/dist/scripts/itinerary-html-tests.js +426 -0
  188. package/dist/scripts/journey-tests.js +22 -27
  189. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  190. package/dist/scripts/kernel-manifest-gate.js +126 -0
  191. package/dist/scripts/kernel-manifest-tests.js +212 -0
  192. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  193. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  194. package/dist/scripts/kernel-manifest.js +369 -0
  195. package/dist/scripts/lavish-local-tests.js +1226 -0
  196. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  197. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  198. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  199. package/dist/scripts/ledger-tests.js +382 -246
  200. package/dist/scripts/ledger-workflow-crash.js +10 -20
  201. package/dist/scripts/m3-cohort-tests.js +714 -0
  202. package/dist/scripts/m3-cohort.js +310 -0
  203. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  204. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  205. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  206. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  207. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  208. package/dist/scripts/memory-capture-tests.js +242 -61
  209. package/dist/scripts/memory-decay-tests.js +18 -52
  210. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  211. package/dist/scripts/memory-lifecycle.js +196 -0
  212. package/dist/scripts/memory-metrics.js +24 -12
  213. package/dist/scripts/memory-value-report.js +631 -0
  214. package/dist/scripts/memory-value-tests.js +328 -0
  215. package/dist/scripts/metrics-report-tests.js +151 -0
  216. package/dist/scripts/model-override-e2e.js +137 -0
  217. package/dist/scripts/nightly-evidence-tests.js +203 -0
  218. package/dist/scripts/nightly-evidence.js +303 -0
  219. package/dist/scripts/nudge-digest.js +39 -28
  220. package/dist/scripts/observability-tests.js +381 -0
  221. package/dist/scripts/onboarding-tests.js +161 -0
  222. package/dist/scripts/opensky-check.js +18 -13
  223. package/dist/scripts/opensky-tests.js +28 -28
  224. package/dist/scripts/outcome-projection-tests.js +427 -0
  225. package/dist/scripts/persona-sim-tests.js +603 -0
  226. package/dist/scripts/persona-sim.js +1081 -0
  227. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  228. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  229. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  230. package/dist/scripts/price-drift-tests.js +340 -0
  231. package/dist/scripts/price-drift-watch.js +447 -0
  232. package/dist/scripts/probe-poi-tests.js +53 -45
  233. package/dist/scripts/product-metrics.js +483 -0
  234. package/dist/scripts/publish-preverify.js +110 -0
  235. package/dist/scripts/realtime-pricing-tests.js +76 -0
  236. package/dist/scripts/recall-tests.js +513 -0
  237. package/dist/scripts/replay-async.js +22 -18
  238. package/dist/scripts/replay-real.js +18 -18
  239. package/dist/scripts/replay.js +54 -82
  240. package/dist/scripts/route-provider-conformance-tests.js +391 -0
  241. package/dist/scripts/session-attach-diagnose.js +20 -20
  242. package/dist/scripts/session-attach-poc.js +34 -37
  243. package/dist/scripts/session-benchmark.js +180 -0
  244. package/dist/scripts/session-dida-live-e2e.js +199 -0
  245. package/dist/scripts/session-extract-tests.js +14 -35
  246. package/dist/scripts/session-link-tests.js +478 -0
  247. package/dist/scripts/session-login.js +27 -46
  248. package/dist/scripts/session-tests.js +796 -96
  249. package/dist/scripts/session-zone-crash.js +49 -0
  250. package/dist/scripts/session-zone-ledger-tests.js +432 -0
  251. package/dist/scripts/session-zone-observation-tests.js +492 -0
  252. package/dist/scripts/session-zone-observe.js +135 -0
  253. package/dist/scripts/session-zone-wiring-tests.js +566 -0
  254. package/dist/scripts/session-zones-tests.js +374 -0
  255. package/dist/scripts/sf-live-benchmark.js +367 -0
  256. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  257. package/dist/scripts/sf-live-cli-tests.js +92 -0
  258. package/dist/scripts/sf-live-options.js +46 -0
  259. package/dist/scripts/sf-manifest-tests.js +30 -0
  260. package/dist/scripts/sf-manifest.js +33 -0
  261. package/dist/scripts/sf-soft-score-tests.js +67 -0
  262. package/dist/scripts/sf-summary-tests.js +324 -0
  263. package/dist/scripts/sf-summary.js +525 -0
  264. package/dist/scripts/share-tests.js +432 -0
  265. package/dist/scripts/skeleton-check.js +16 -14
  266. package/dist/scripts/skeleton-integration-test.js +28 -11
  267. package/dist/scripts/skills-contract-tests.js +51 -46
  268. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  269. package/dist/scripts/smoke.js +690 -246
  270. package/dist/scripts/spawn-bounded-abort-tests.js +244 -0
  271. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  272. package/dist/scripts/state-cli-tests.js +226 -101
  273. package/dist/scripts/state-cli.js +565 -198
  274. package/dist/scripts/state-repair-cli.js +66 -0
  275. package/dist/scripts/state-repair-tests.js +140 -0
  276. package/dist/scripts/static-golden-tests.js +165 -0
  277. package/dist/scripts/time-eval-tests.js +212 -192
  278. package/dist/scripts/travel-timeline-tests.js +29 -93
  279. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  280. package/dist/scripts/turn-handoff-collect.js +161 -0
  281. package/dist/scripts/turn-policy-tests.js +55 -0
  282. package/dist/scripts/typed-contract-canary.js +176 -0
  283. package/dist/scripts/tz-resolver-sanity.js +50 -0
  284. package/dist/scripts/tz-resolver-tests.js +584 -0
  285. package/dist/scripts/unified-tests.js +60 -20
  286. package/dist/scripts/visa-policy-tests.js +62 -0
  287. package/dist/scripts/weather-tests.js +276 -41
  288. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  289. package/dist/scripts/write-gate-tests.js +522 -0
  290. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  291. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  292. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  293. package/dist/scripts/z3-race-tests.js +46 -0
  294. package/dist/src/artifact-gate.js +829 -0
  295. package/dist/src/backend/kernel.js +91 -0
  296. package/dist/src/backend/modules/booking-copilot.js +92 -0
  297. package/dist/src/backend/modules/booking-executor.js +124 -0
  298. package/dist/src/backend/modules/session-search.js +551 -0
  299. package/dist/src/benchmark-agent-conformance.js +578 -0
  300. package/dist/src/benchmark-environment-bridge.js +515 -0
  301. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  302. package/dist/src/benchmark-tool-isolation.js +151 -0
  303. package/dist/src/bookable-facts.js +533 -0
  304. package/dist/src/booking-saga.js +97 -0
  305. package/dist/src/booking-surface/availability-policy.js +697 -0
  306. package/dist/src/booking-surface/booking-intent.js +43 -0
  307. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  308. package/dist/src/booking-surface/canonical-schema.js +116 -0
  309. package/dist/src/booking-surface/contracts.js +75 -0
  310. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  311. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  312. package/dist/src/booking-surface/dsh-worker.js +98 -0
  313. package/dist/src/booking-surface/error-codes.js +231 -0
  314. package/dist/src/booking-surface/index.js +9 -0
  315. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  316. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  317. package/dist/src/booking-surface/profile.js +24 -0
  318. package/dist/src/booking-surface/recovery-chain.js +367 -0
  319. package/dist/src/booking-surface/runtime.js +1968 -0
  320. package/dist/src/booking-surface/server.js +674 -0
  321. package/dist/src/booking-surface/startup.js +168 -0
  322. package/dist/src/booking-surface/validation.js +261 -0
  323. package/dist/src/bridge.js +20 -13
  324. package/dist/src/city-scenario-tier.js +378 -0
  325. package/dist/src/companions.js +48 -74
  326. package/dist/src/contracts.js +21 -43
  327. package/dist/src/dsh-llm.js +171 -115
  328. package/dist/src/engine.js +276 -193
  329. package/dist/src/evaluation-cadence.js +97 -0
  330. package/dist/src/evaluation-contracts.js +283 -0
  331. package/dist/src/flight-pack-adapter.js +54 -0
  332. package/dist/src/flight-pack-contract.js +2 -0
  333. package/dist/src/flyai-setup-tool.js +70 -0
  334. package/dist/src/gotry-backend.js +125 -0
  335. package/dist/src/hotel-date-gate.js +118 -0
  336. package/dist/src/i18n.js +88 -0
  337. package/dist/src/index.js +2385 -881
  338. package/dist/src/itinerary-deck.js +139 -0
  339. package/dist/src/itinerary-doc-shared.js +661 -0
  340. package/dist/src/itinerary-html.js +131 -0
  341. package/dist/src/journey.js +155 -108
  342. package/dist/src/lavish-tools.js +400 -0
  343. package/dist/src/ledger-repair-apply.js +538 -0
  344. package/dist/src/ledger-repair-plan.js +463 -0
  345. package/dist/src/loop.js +331 -161
  346. package/dist/src/m3-cohort.js +1577 -0
  347. package/dist/src/memory-capture.js +180 -32
  348. package/dist/src/memory-decay.js +30 -14
  349. package/dist/src/memory-lifecycle.js +1310 -0
  350. package/dist/src/memory-utility.js +27 -40
  351. package/dist/src/mock-llm.js +36 -61
  352. package/dist/src/model.js +81 -36
  353. package/dist/src/outcome-projection.js +746 -0
  354. package/dist/src/realtime-pricing.js +103 -0
  355. package/dist/src/recall/card.js +74 -0
  356. package/dist/src/recall/evaluator.js +140 -0
  357. package/dist/src/recall/integration.js +17 -0
  358. package/dist/src/recall/tick.js +166 -0
  359. package/dist/src/session-link/plan-it-action.js +48 -0
  360. package/dist/src/session-link/session-link.js +248 -0
  361. package/dist/src/session-zone-ledger.js +312 -0
  362. package/dist/src/session-zone-observation.js +354 -0
  363. package/dist/src/session-zone-wiring.js +658 -0
  364. package/dist/src/session-zones.js +510 -0
  365. package/dist/src/share/adapters.js +51 -0
  366. package/dist/src/share/share-consent.js +76 -0
  367. package/dist/src/share/share-deck.js +58 -0
  368. package/dist/src/share/share-token.js +146 -0
  369. package/dist/src/slot-spec.js +84 -106
  370. package/dist/src/state-ledger.js +696 -524
  371. package/dist/src/state-repair.js +299 -0
  372. package/dist/src/subagent-job-id-guard.js +58 -0
  373. package/dist/src/time-anchor.js +231 -33
  374. package/dist/src/tool-packet.js +20 -8
  375. package/dist/src/travel-slots.js +58 -35
  376. package/dist/src/travel-timeline.js +44 -63
  377. package/dist/src/turn-deadline.js +287 -0
  378. package/dist/src/turn-policy.js +118 -0
  379. package/dist/src/tz-resolver.js +143 -0
  380. package/dist/src/unified.js +600 -360
  381. package/dist/src/wish-pool.js +42 -12
  382. package/dist/src/write-gate.js +495 -0
  383. package/dist/src/z3-shared.js +284 -0
  384. package/extension/README.md +21 -0
  385. package/extension/README.zh-CN.md +21 -0
  386. package/extension/background.js +463 -0
  387. package/extension/content-bridge.js +270 -0
  388. package/extension/content-main.js +111 -0
  389. package/extension/manifest.json +60 -0
  390. package/package.json +407 -14
  391. package/schemas/booking.intent.schema.json +193 -0
  392. package/schemas/booking.surface.schema.json +2622 -0
  393. package/ts/capabilities/agent-reach-deep.ts +114 -0
  394. package/ts/capabilities/agent-reach.ts +198 -0
  395. package/ts/capabilities/anything.ts +282 -0
  396. package/ts/capabilities/artifacts.ts +428 -0
  397. package/ts/capabilities/fact-log.ts +54 -0
  398. package/ts/capabilities/flyai-config.ts +388 -0
  399. package/ts/capabilities/flyai.ts +1274 -0
  400. package/ts/capabilities/hbcli.ts +253 -71
  401. package/ts/capabilities/incident-log.ts +88 -59
  402. package/ts/capabilities/itinerary-artifact.ts +214 -0
  403. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  404. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  405. package/ts/capabilities/opensky.ts +121 -0
  406. package/ts/capabilities/session/action-cache.ts +148 -0
  407. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  408. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  409. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  410. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  411. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  412. package/ts/capabilities/session/benchmark.ts +276 -0
  413. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  414. package/ts/capabilities/session/extension-bridge.ts +794 -0
  415. package/ts/capabilities/session/extension-channel.ts +144 -0
  416. package/ts/capabilities/session/extension-distribution.ts +266 -0
  417. package/ts/capabilities/session/extract.ts +54 -0
  418. package/ts/capabilities/session/golden-score.ts +139 -0
  419. package/ts/capabilities/session/health-watch.ts +264 -0
  420. package/ts/capabilities/session/read-guard.ts +141 -0
  421. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  422. package/ts/capabilities/session/transport.ts +146 -0
  423. package/ts/capabilities/session/wizard.ts +140 -0
  424. package/ts/capabilities/session-consent.ts +211 -0
  425. package/ts/capabilities/session-login.ts +208 -0
  426. package/ts/capabilities/session-search.ts +893 -0
  427. package/ts/capabilities/weather.ts +330 -0
  428. package/ts/dsh-runtime/vendor/README.md +68 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  440. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  441. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  442. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  443. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  444. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  445. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  446. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  447. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  448. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  449. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  450. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  451. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  452. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  453. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  454. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  455. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  456. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  457. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  458. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  459. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  460. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  461. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  462. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  463. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  464. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  465. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  466. package/ts/package.json +293 -3
  467. package/ts/scripts/async-collect.ts +91 -0
  468. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  469. package/ts/scripts/skeleton-check.ts +1 -1
  470. package/ts/scripts/skeleton-integration-test.ts +23 -1
  471. package/ts/scripts/state-cli.ts +586 -0
  472. package/ts/scripts/turn-handoff-collect.ts +178 -0
  473. package/ts/src/artifact-gate.ts +991 -0
  474. package/ts/src/benchmark-agent-conformance.ts +619 -0
  475. package/ts/src/benchmark-environment-bridge.ts +538 -0
  476. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  477. package/ts/src/benchmark-tool-isolation.ts +166 -0
  478. package/ts/src/bookable-facts.ts +837 -0
  479. package/ts/src/booking-saga.ts +133 -0
  480. package/ts/src/booking-surface/availability-policy.ts +522 -0
  481. package/ts/src/booking-surface/booking-intent.ts +86 -0
  482. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  483. package/ts/src/booking-surface/canonical-schema.js +116 -0
  484. package/ts/src/booking-surface/contracts.ts +475 -0
  485. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  486. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  487. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  488. package/ts/src/booking-surface/error-codes.ts +241 -0
  489. package/ts/src/booking-surface/index.ts +9 -0
  490. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  491. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  492. package/ts/src/booking-surface/profile.ts +42 -0
  493. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  494. package/ts/src/booking-surface/runtime.ts +1793 -0
  495. package/ts/src/booking-surface/server.ts +733 -0
  496. package/ts/src/booking-surface/startup.ts +201 -0
  497. package/ts/src/booking-surface/validation.ts +217 -0
  498. package/ts/src/companions.ts +113 -0
  499. package/ts/src/contracts.ts +17 -3
  500. package/ts/src/dsh-llm.ts +152 -17
  501. package/ts/src/flyai-setup-tool.ts +77 -0
  502. package/ts/src/index.ts +2123 -200
  503. package/ts/src/itinerary-deck.ts +189 -0
  504. package/ts/src/itinerary-doc-shared.ts +741 -0
  505. package/ts/src/itinerary-html.ts +171 -0
  506. package/ts/src/loop.ts +234 -26
  507. package/ts/src/memory-capture.ts +283 -0
  508. package/ts/src/memory-decay.ts +55 -0
  509. package/ts/src/memory-utility.ts +70 -0
  510. package/ts/src/mock-llm.ts +4 -8
  511. package/ts/src/model.ts +88 -2
  512. package/ts/src/recall/card.ts +106 -0
  513. package/ts/src/recall/evaluator.ts +170 -0
  514. package/ts/src/recall/integration.ts +35 -0
  515. package/ts/src/recall/tick.ts +213 -0
  516. package/ts/src/session-link/plan-it-action.ts +78 -0
  517. package/ts/src/session-link/session-link.ts +275 -0
  518. package/ts/src/share/adapters.ts +93 -0
  519. package/ts/src/share/share-consent.ts +105 -0
  520. package/ts/src/share/share-deck.ts +85 -0
  521. package/ts/src/share/share-token.ts +166 -0
  522. package/ts/src/slot-spec.ts +172 -0
  523. package/ts/src/state-ledger.ts +1166 -0
  524. package/ts/src/subagent-job-id-guard.ts +72 -0
  525. package/ts/src/time-anchor.ts +338 -0
  526. package/ts/src/tool-packet.ts +32 -0
  527. package/ts/src/travel-slots.ts +219 -0
  528. package/ts/src/travel-timeline.ts +83 -0
  529. package/ts/src/turn-deadline.ts +366 -0
  530. package/ts/src/turn-policy.ts +154 -0
  531. package/ts/src/unified.ts +444 -105
  532. package/ts/src/wish-pool.ts +79 -0
  533. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,201 @@
1
+ /** Production composition entry for GoTry's BFF-only Booking Copilot server. */
2
+
3
+ import { mkdirSync } from 'node:fs'
4
+ import { isAbsolute, resolve } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+ import { ensureLedger, type StateLedger } from '../state-ledger.ts'
7
+ import {
8
+ buildDshPlannerEnvironment,
9
+ createDshEmbeddedBookingPlanner,
10
+ type DshEmbeddedBookingPlannerHandle,
11
+ type DshEmbeddedBookingPlannerOptions,
12
+ } from './dsh-planner.ts'
13
+ import { BookingCopilotTaskRuntime } from './runtime.ts'
14
+ import { BOOKING_COPILOT_INGRESS_MODES, type BookingCopilotIngressMode, type BookingIngressBinding, type BookingIngressPrincipal } from './contracts.ts'
15
+ import {
16
+ startBookingCopilotServer,
17
+ type BookingCopilotServerHandle,
18
+ type BookingCopilotServerOptions,
19
+ } from './server.ts'
20
+
21
+ export interface BookingCopilotStartupConfig {
22
+ apiKey: string
23
+ stateRoot: string
24
+ host: string
25
+ port: number
26
+ artifactId?: string
27
+ /** Planner turn deadline in ms; the planner's own default applies when unset. */
28
+ plannerTurnTimeoutMs?: number
29
+ /** Defaults to bound-turn-only; a complete injected seam enables ingress. */
30
+ ingressMode: BookingCopilotIngressMode
31
+ }
32
+
33
+ export interface BookingCopilotStartupHandle {
34
+ port: number
35
+ close(): Promise<void>
36
+ }
37
+
38
+ export interface BookingCopilotStartupDependencies {
39
+ ensureLedger(stateRoot: string): StateLedger
40
+ runtimeFactory(ledger: StateLedger): BookingCopilotTaskRuntime
41
+ createPlanner(options: DshEmbeddedBookingPlannerOptions): Promise<DshEmbeddedBookingPlannerHandle>
42
+ /** Optional in-process BFF identity seam; standalone use is bound-turn-only. */
43
+ ingressBinding?: BookingIngressBinding
44
+ principal?: BookingIngressPrincipal
45
+ startServer(options: BookingCopilotServerOptions): Promise<BookingCopilotServerHandle>
46
+ }
47
+
48
+ const DEFAULT_DEPENDENCIES: BookingCopilotStartupDependencies = {
49
+ ensureLedger,
50
+ runtimeFactory: (ledger) => new BookingCopilotTaskRuntime(ledger),
51
+ createPlanner: createDshEmbeddedBookingPlanner,
52
+ startServer: startBookingCopilotServer,
53
+ }
54
+
55
+ function parsePort(raw: string | undefined): number {
56
+ if (raw === undefined || raw === '') return 3_082
57
+ const value = Number(raw)
58
+ if (!Number.isSafeInteger(value) || value < 0 || value > 65_535) throw new Error('booking_copilot_invalid_port')
59
+ return value
60
+ }
61
+
62
+ /** Operator-tunable planner deadline; the bounds mirror the planner's own validation. */
63
+ function parsePlannerTurnTimeout(raw: string | undefined): number | undefined {
64
+ if (raw === undefined || raw.trim() === '') return undefined
65
+ const value = Number(raw)
66
+ if (!Number.isSafeInteger(value) || value < 1 || value > 120_000) {
67
+ throw new Error('booking_copilot_planner_timeout_invalid')
68
+ }
69
+ return value
70
+ }
71
+
72
+ export function resolveBookingCopilotStartupConfig(
73
+ env: Record<string, string | undefined> = process.env,
74
+ ): BookingCopilotStartupConfig {
75
+ const apiKey = env.GOTRY_BOOKING_COPILOT_API_KEY ?? ''
76
+ if (!apiKey) throw new Error('booking_copilot_api_key_required')
77
+ const stateRoot = env.GOTRY_BOOKING_COPILOT_STATE_ROOT ?? ''
78
+ if (!stateRoot) throw new Error('booking_copilot_state_root_required')
79
+ if (!isAbsolute(stateRoot)) throw new Error('booking_copilot_state_root_must_be_absolute')
80
+ const artifactId = env.GOTRY_BOOKING_COPILOT_ARTIFACT_ID || undefined
81
+ if (artifactId !== undefined && !/^[0-9a-f]{40}$/.test(artifactId)) {
82
+ throw new Error('booking_copilot_artifact_id_invalid')
83
+ }
84
+ const rawIngressMode = env.GOTRY_BOOKING_COPILOT_INGRESS_MODE
85
+ const ingressMode = rawIngressMode === undefined || rawIngressMode === '' ? 'bff-bound-turn-only' : rawIngressMode
86
+ if (!(BOOKING_COPILOT_INGRESS_MODES as readonly string[]).includes(ingressMode)) {
87
+ throw new Error('booking_copilot_ingress_mode_invalid')
88
+ }
89
+ return {
90
+ apiKey,
91
+ stateRoot,
92
+ host: env.GOTRY_BOOKING_COPILOT_HOST || '127.0.0.1',
93
+ port: parsePort(env.GOTRY_BOOKING_COPILOT_PORT),
94
+ artifactId,
95
+ plannerTurnTimeoutMs: parsePlannerTurnTimeout(env.GOTRY_BOOKING_COPILOT_PLANNER_TIMEOUT_MS),
96
+ ingressMode: ingressMode as BookingCopilotIngressMode,
97
+ }
98
+ }
99
+
100
+ async function closeAll(
101
+ server: BookingCopilotServerHandle | undefined,
102
+ planner: DshEmbeddedBookingPlannerHandle | undefined,
103
+ ledger: StateLedger | undefined,
104
+ ): Promise<void> {
105
+ const failures: unknown[] = []
106
+ for (const close of [
107
+ server ? () => server.close() : undefined,
108
+ planner ? () => planner.close() : undefined,
109
+ ledger ? () => Promise.resolve(ledger.close()) : undefined,
110
+ ]) {
111
+ if (!close) continue
112
+ try { await close() } catch (error) { failures.push(error) }
113
+ }
114
+ if (failures.length === 1) throw failures[0]
115
+ if (failures.length > 1) throw new AggregateError(failures, 'booking_copilot_shutdown_failed')
116
+ }
117
+
118
+ /**
119
+ * Boot one process-scoped dsh core and task-scoped sessions behind the typed
120
+ * HTTP/SSE server. Only the HTTP layer receives the BFF deployment key.
121
+ */
122
+ export async function startBookingCopilotFromEnvironment(
123
+ env: Record<string, string | undefined> = process.env,
124
+ dependencies: BookingCopilotStartupDependencies = DEFAULT_DEPENDENCIES,
125
+ ): Promise<BookingCopilotStartupHandle> {
126
+ const config = resolveBookingCopilotStartupConfig(env)
127
+ const activeDependencies = dependencies
128
+ const hasIngressBinding = Boolean(activeDependencies.ingressBinding)
129
+ const hasPrincipal = Boolean(activeDependencies.principal)
130
+ if (hasIngressBinding !== hasPrincipal) throw new Error('booking_copilot_ingress_binding_pair_required')
131
+ const ingressPair = hasIngressBinding && hasPrincipal
132
+ if (ingressPair && env.GOTRY_BOOKING_COPILOT_INGRESS_MODE !== undefined && config.ingressMode === 'bff-bound-turn-only') {
133
+ throw new Error('booking_copilot_ingress_mode_conflict')
134
+ }
135
+ if (!ingressPair && config.ingressMode === 'bff-ingress-binding') throw new Error('booking_copilot_ingress_binding_required')
136
+ if (ingressPair && (typeof activeDependencies.ingressBinding?.bind !== 'function'
137
+ || typeof activeDependencies.principal?.subject !== 'string' || activeDependencies.principal.subject.length === 0 || activeDependencies.principal.subject.length > 256
138
+ || typeof activeDependencies.principal?.scope !== 'string' || activeDependencies.principal.scope.length === 0 || activeDependencies.principal.scope.length > 256)) {
139
+ throw new Error('booking_copilot_ingress_binding_invalid')
140
+ }
141
+ const activeIngressMode: BookingCopilotIngressMode = ingressPair ? 'bff-ingress-binding' : 'bff-bound-turn-only'
142
+ mkdirSync(config.stateRoot, { recursive: true })
143
+ let ledger: StateLedger | undefined
144
+ let planner: DshEmbeddedBookingPlannerHandle | undefined
145
+ let server: BookingCopilotServerHandle | undefined
146
+ try {
147
+ ledger = dependencies.ensureLedger(config.stateRoot)
148
+ const plannerOptions = {
149
+ stateRoot: config.stateRoot,
150
+ env: buildDshPlannerEnvironment(env),
151
+ ...(config.plannerTurnTimeoutMs === undefined ? {} : { turnTimeoutMs: config.plannerTurnTimeoutMs }),
152
+ }
153
+ const runtime = activeDependencies.runtimeFactory(ledger)
154
+ planner = await activeDependencies.createPlanner(plannerOptions)
155
+ server = await activeDependencies.startServer({
156
+ apiKey: config.apiKey, runtime, plannerFactory: planner.plannerFactory,
157
+ ...(ingressPair ? { ingressBinding: activeDependencies.ingressBinding, principal: activeDependencies.principal } : {}),
158
+ ingressMode: activeIngressMode,
159
+ host: config.host, port: config.port, artifactId: config.artifactId,
160
+ })
161
+ } catch (error) {
162
+ try { await closeAll(server, planner, ledger) } catch (cleanupError) {
163
+ throw new AggregateError([error, cleanupError], 'booking_copilot_startup_and_cleanup_failed')
164
+ }
165
+ throw error
166
+ }
167
+
168
+ let closed = false
169
+ return {
170
+ port: server.port,
171
+ async close() {
172
+ if (closed) return
173
+ closed = true
174
+ await closeAll(server, planner, ledger)
175
+ },
176
+ }
177
+ }
178
+
179
+ async function runCli(): Promise<void> {
180
+ const handle = await startBookingCopilotFromEnvironment()
181
+ process.stderr.write(`[gotry-booking-copilot] listening on http://127.0.0.1:${handle.port}/a2a/booking-copilot/turn\n`)
182
+ let stopping = false
183
+ const stop = () => {
184
+ if (stopping) return
185
+ stopping = true
186
+ void handle.close().then(() => process.exit(0), (error) => {
187
+ process.stderr.write(`[gotry-booking-copilot] shutdown failed: ${error instanceof Error ? error.message : String(error)}\n`)
188
+ process.exit(1)
189
+ })
190
+ }
191
+ process.once('SIGINT', stop)
192
+ process.once('SIGTERM', stop)
193
+ }
194
+
195
+ const invokedPath = process.argv[1] ? resolve(process.argv[1]) : ''
196
+ if (invokedPath === fileURLToPath(import.meta.url)) {
197
+ runCli().catch((error) => {
198
+ process.stderr.write(`[gotry-booking-copilot] startup failed: ${error instanceof Error ? error.message : String(error)}\n`)
199
+ process.exit(1)
200
+ })
201
+ }
@@ -0,0 +1,217 @@
1
+ import { readFileSync } from 'node:fs'
2
+ import { Ajv2020, type ValidateFunction } from 'ajv/dist/2020.js'
3
+ import { BOOKING_READ_ACTION_KINDS, BOOKING_BLOCKER_CODES, BOOKING_GAP_CODES } from './contracts.ts'
4
+
5
+ const schema = JSON.parse(readFileSync(new URL('../../../schemas/booking.surface.schema.json', import.meta.url), 'utf8')) as Record<string, unknown>
6
+ const intentSchema = JSON.parse(readFileSync(new URL('../../../schemas/booking.intent.schema.json', import.meta.url), 'utf8')) as Record<string, unknown>
7
+ /** Canonical schema bytes (single source of truth) for prompt/tool derivations elsewhere. */
8
+ export const bookingSurfaceSchema = schema
9
+ const ajv = new Ajv2020({ allErrors: true, strict: true })
10
+ ajv.addSchema(schema)
11
+ const validate: ValidateFunction = ajv.compile(schema)
12
+ const validateActionCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Action` })
13
+ const validateBlockerCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Blocker` })
14
+ const validateApprovalCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Approval` })
15
+ const validateEventCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Event` })
16
+ const validateIntentCanonical = ajv.compile(intentSchema)
17
+ const kinds = new Set<string>(BOOKING_READ_ACTION_KINDS)
18
+ const codes = new Set<string>(BOOKING_BLOCKER_CODES)
19
+ const gapCodes = new Set<string>(BOOKING_GAP_CODES)
20
+ const safeOpaque = (value: unknown): value is string => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(value)
21
+ const safeRequestKey = (value: unknown): value is string => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{7,127}$/.test(value) && !secret.test(value)
22
+ const secret = /(?:Bearer\s+\S|\bsk-(?!ill(?:s)?\b)[A-Za-z0-9_-]{12,}|\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/i
23
+ // `.example` is reserved for documentation, so these identifiers cannot be a
24
+ // real mailbox. Do not allowlist lookalikes on routable domains.
25
+ const technicalEmail = /^(?:schema|version)@[a-z0-9-]+\.example$/i
26
+ const currencyAmount = /\b(?:[A-Z]{3}\s*\d+(?:\.\d+)?|\d+(?:\.\d+)?\s*[A-Z]{3})\b/
27
+ const rfc3339DateTime = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|([+-])(\d{2}):(\d{2}))$/
28
+
29
+ export function isBookingDateTime(value: unknown): value is string {
30
+ if (typeof value !== 'string') return false
31
+ const match = rfc3339DateTime.exec(value)
32
+ if (!match) return false
33
+ const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match
34
+ const year = Number(yearText)
35
+ const month = Number(monthText)
36
+ const day = Number(dayText)
37
+ const hour = Number(hourText)
38
+ const minute = Number(minuteText)
39
+ const second = Number(secondText)
40
+ const offsetHour = match[9] === undefined ? 0 : Number(match[9])
41
+ const offsetMinute = match[10] === undefined ? 0 : Number(match[10])
42
+ if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) return false
43
+ const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate()
44
+ if (day < 1 || day > daysInMonth) return false
45
+ return !Number.isNaN(Date.parse(value))
46
+ }
47
+
48
+ function normalizedWords(value: string): string {
49
+ return value.normalize('NFKC').replace(/[^\p{L}\p{N}.]+/gu, ' ').trim()
50
+ }
51
+
52
+ function hasSupplierCost(value: string): boolean {
53
+ const normalized = normalizedWords(value)
54
+ const lower = normalized.toLowerCase()
55
+ return (
56
+ /\b(?:supplier|vendor|internal)\b/.test(lower) &&
57
+ /\b(?:cost|rate|price)\b/.test(lower) &&
58
+ currencyAmount.test(normalized.toUpperCase())
59
+ )
60
+ }
61
+
62
+ function hasInternationalPhone(value: string): boolean {
63
+ const normalized = value.normalize('NFKC')
64
+ for (const plusIndex of normalized.split('').flatMap((char, index) => char === '+' ? [index] : [])) {
65
+ let digits = ''
66
+ for (const char of normalized.slice(plusIndex + 1, plusIndex + 48)) {
67
+ if (/\d/u.test(char)) digits += char
68
+ else if (/\p{L}|@/u.test(char)) break
69
+ if (digits.length > 15) break
70
+ }
71
+ if (/^[1-9]\d{7,14}$/.test(digits)) return true
72
+ }
73
+ return false
74
+ }
75
+
76
+ function hasPaymentCard(value: string): boolean {
77
+ const normalized = value.normalize('NFKC')
78
+ const candidates = normalized.match(/(?:\d[ -]?){12,18}\d/g) ?? []
79
+ const explicitlyLabeled = /\b(?:card|payment|visa|mastercard)\b/i.test(normalized)
80
+ return candidates.some((candidate) => (explicitlyLabeled || /[ -]/.test(candidate)) && luhn(candidate))
81
+ }
82
+
83
+ function hasSensitiveText(value: string, opts: { allowOpaqueNumericId?: boolean } = {}): boolean {
84
+ const normalized = value.normalize('NFKC')
85
+ if (normalized.includes('@') && !technicalEmail.test(normalized.trim())) return true
86
+ return secret.test(normalized) || hasInternationalPhone(normalized) || hasSupplierCost(normalized) || (!opts.allowOpaqueNumericId && hasPaymentCard(normalized))
87
+ }
88
+ function luhn(value: string): boolean { const digits = value.replaceAll(/\D/g, ''); if (digits.length < 13 || digits.length > 19) return false; let sum = 0; for (let i = digits.length - 1, parity = 0; i >= 0; i--, parity++) { let n = Number(digits[i]); if (parity % 2 === 1) { n *= 2; if (n > 9) n -= 9 } sum += n } return sum % 10 === 0 }
89
+ const opaqueFieldNames = new Set([
90
+ 'eventId', 'taskId', 'contextRef', 'actionId', 'hotelRef', 'offerRef', 'offerVersionRef', 'checkedOfferVersionRef', 'currentOfferVersionRef', 'verifiedOfferRef',
91
+ 'orderRef', 'searchSessionRef', 'handoffRef', 'questionId', 'blockerId', 'approvalId',
92
+ 'sourceActionId', 'targetActionId', 'sourceTurnId', 'turnId', 'requestKey', 'taskHandle',
93
+ 'nonce', 'deliveryNonce', 'optionDigest', 'sourceReceiptDigest', 'valueDigest',
94
+ ])
95
+ function isOpaqueFieldPath(path: string): boolean {
96
+ const clean = path.replace(/\[\]$/g, '')
97
+ const field = clean.split('.').at(-1)
98
+ return field ? opaqueFieldNames.has(field) || field.endsWith('Ref') || field.endsWith('Refs') : false
99
+ }
100
+ const scan = (v: unknown, path = '$'): boolean => typeof v === 'string'
101
+ ? hasSensitiveText(v, { allowOpaqueNumericId: isOpaqueFieldPath(path) })
102
+ : Array.isArray(v)
103
+ ? v.some((child) => scan(child, `${path}[]`))
104
+ : !!v && typeof v === 'object'
105
+ ? Object.entries(v).some(([k,x]) => /cost|price/i.test(k) ? /supplier|internal|wholesale/i.test(String(x)) : scan(x, `${path}.${k}`))
106
+ : false
107
+ export function isSafeBookingRequestKey(value: unknown): value is string { return safeRequestKey(value) }
108
+ export type BookingSurfaceValidationResult = { ok: true } | { ok: false; errors: string[] }
109
+ function errors(v: ValidateFunction): string[] { return (v.errors ?? []).map(e => `${e.instancePath || '$'}: ${e.message ?? e.keyword}`) }
110
+ function workspaceErrors(workspace: Record<string, any> | undefined, path: string): string[] {
111
+ const e: string[] = []
112
+ if (!workspace || typeof workspace !== 'object' || Array.isArray(workspace)) return e
113
+ const loadedOffers = Array.isArray(workspace.loadedOffers) ? workspace.loadedOffers : []
114
+ const offerRefs = loadedOffers.map((offer: any) => offer?.offerRef).filter((ref: unknown): ref is string => typeof ref === 'string')
115
+ const offerVersionRefs = loadedOffers.map((offer: any) => offer?.offerVersionRef).filter((ref: unknown): ref is string => typeof ref === 'string')
116
+ if (new Set(offerRefs).size !== offerRefs.length) e.push(`${path}.loadedOffers: duplicate offerRef`)
117
+ if (new Set(offerVersionRefs).size !== offerVersionRefs.length) e.push(`${path}.loadedOffers: duplicate offerVersionRef`)
118
+ const shortlisted = Array.isArray(workspace.shortlistedOfferRefs) ? workspace.shortlistedOfferRefs : []
119
+ if (shortlisted.some((ref: unknown) => typeof ref !== 'string' || !loadedOffers.some((offer: any) => offer?.offerRef === ref))) {
120
+ e.push(`${path}.shortlistedOfferRefs: every ref must be a loaded offer`)
121
+ }
122
+ if (workspace.selectedOfferRef !== undefined && !loadedOffers.some((offer: any) => offer?.offerRef === workspace.selectedOfferRef)) {
123
+ e.push(`${path}.selectedOfferRef: loaded offer required`)
124
+ }
125
+ const verified = workspace.verifiedOffer
126
+ if (verified !== undefined) {
127
+ if (!loadedOffers.some((offer: any) => offer?.offerRef === verified.offerRef && offer?.offerVersionRef === verified.offerVersionRef)) e.push(`${path}.verifiedOffer: loaded offer version required`)
128
+ if (!safeOpaque(verified.verifiedOfferRef) || !isBookingDateTime(verified.expiresAt)) e.push(`${path}.verifiedOffer: safe ref and parseable expiry required`)
129
+ }
130
+ return e
131
+ }
132
+ function cross(value: unknown): string[] {
133
+ const e: string[] = []
134
+ if (scan(value)) e.push('$: unsafe PII/payment/supplier-cost/credential text')
135
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return e
136
+ const x = value as Record<string, any>
137
+ const semantic = x.kind === 'action.receipt.continuation' ? x.receipt : x
138
+ if (x.workspace && x.kind !== 'user.turn.ingress') e.push(...workspaceErrors(x.workspace, '$.workspace'))
139
+ if (x.schemaVersion === 'booking.surface' && Array.isArray(x.loadedOffers)) e.push(...workspaceErrors(x, '$'))
140
+ if (x.kind === 'user.turn.ingress') {
141
+ if (!safeRequestKey(x.requestKey)) e.push('$.requestKey: safe opaque request key required')
142
+ const workspace = x.workspace as Record<string, any> | undefined
143
+ e.push(...workspaceErrors(workspace, '$.workspace'))
144
+ const visibleHotels = Array.isArray(workspace?.visibleHotels) ? workspace.visibleHotels : []
145
+ const loadedOffers = Array.isArray(workspace?.loadedOffers) ? workspace.loadedOffers : []
146
+ if (workspace?.focusedHotelRef !== undefined && !visibleHotels.some((hotel: any) => hotel?.hotelRef === workspace.focusedHotelRef)) e.push('$.workspace.focusedHotelRef: visible hotel required')
147
+ const selectedOffer = workspace?.selectedOfferRef === undefined ? undefined : loadedOffers.find((offer: any) => offer?.offerRef === workspace.selectedOfferRef)
148
+ if (workspace?.selectedOfferRef !== undefined && !selectedOffer) e.push('$.workspace.selectedOfferRef: loaded offer required')
149
+ if (selectedOffer && !visibleHotels.some((hotel: any) => hotel?.hotelRef === selectedOffer.hotelRef)) e.push('$.workspace.selectedOfferRef: offer hotel must be visible')
150
+ }
151
+ if (semantic !== x && semantic?.resultContract?.hardCriteriaMet === true && semantic.resultContract.blockers?.length) e.push('$.receipt.resultContract: hardCriteriaMet cannot coexist with blockers')
152
+ if (kinds.has(x.kind)) {
153
+ const allowed: Record<string, string[]> = {
154
+ 'search.patch': ['patch'], 'search.run': [], 'results.view.patch': ['patch'],
155
+ 'hotel.focus': ['hotelRef'], 'hotel.select': ['hotelRef'],
156
+ 'offers.query': ['hotelRefs', 'criteria'], 'offers.view.patch': ['hotelRef', 'criteria'],
157
+ 'offers.compare': ['offerRefs', 'requestedCount'], 'offer.select': ['offerRef', 'offerVersionRef'],
158
+ 'offer.check': ['offerRef', 'offerVersionRef'], 'checkout.prepare': ['offerRef', 'offerVersionRef', 'verifiedOfferRef'], 'order.observe': ['orderRef'],
159
+ }
160
+ const input = x.input
161
+ if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(k => !allowed[x.kind].includes(k))) e.push('$.input: closed kind-specific input required')
162
+ }
163
+ if (x.kind === 'action.receipt' && x.resultContract?.hardCriteriaMet === true && x.resultContract.blockers?.length) e.push('$.resultContract: hardCriteriaMet cannot coexist with blockers')
164
+ if (x.kind === 'user.turn' && Array.isArray(x.request?.approval)) e.push('$.request.approval: at most one approval')
165
+ if (x.kind === 'action.receipt' || x.kind === 'action.receipt.continuation') {
166
+ const r = x.kind === 'action.receipt.continuation' ? x.receipt : x
167
+ if (r?.resultContract?.gapCodes?.some((code: unknown) => !gapCodes.has(String(code)))) e.push('$.resultContract.gapCodes: unregistered gap code')
168
+ const observation = r?.observation
169
+ if (observation?.kind === 'offer.availability') {
170
+ if (r.status === 'applied' && (observation.available !== true || !observation.verifiedOfferRef || observation.currentOfferVersionRef !== observation.checkedOfferVersionRef || observation.changedFactRefs?.length || observation.gapCodes?.length)) e.push('$.observation: applied availability requires unchanged checked/current version, verified ref, and no gaps')
171
+ if (r.status === 'applied' && (r.resultContract?.outcome !== 'complete' || r.resultContract?.hardCriteriaMet !== true || r.resultContract?.gapCodes?.length || r.resultContract?.blockers?.length)) e.push('$.resultContract: applied availability requires complete hard-criteria result without gaps or blockers')
172
+ if (r.status === 'changed' && (observation.available !== true || !observation.currentOfferVersionRef || observation.currentOfferVersionRef === observation.checkedOfferVersionRef || !observation.changedFactRefs?.length || observation.verifiedOfferRef)) e.push('$.observation: changed availability requires available replacement current version without verified ref')
173
+ if ((r.status === 'unavailable' || r.status === 'no_match') && (observation.available !== false || observation.currentOfferVersionRef || observation.verifiedOfferRef)) e.push('$.observation: negative availability must not expose current or verified version')
174
+ if (r.status !== 'applied' && observation.verifiedOfferRef) e.push('$.observation: only applied availability may expose verified ref')
175
+ }
176
+ }
177
+ const blocker = x.blocker ?? x.question?.blocker
178
+ if (blocker && !/^(searchDraft|results|offers|checkout)(\.[A-Za-z][A-Za-z0-9_]*){0,3}$/.test(blocker.criterionPath ?? '')) e.push('$.blocker.criterionPath: closed criterion path required')
179
+ if (blocker?.evidence?.factRefs && new Set(blocker.evidence.factRefs).size !== blocker.evidence.factRefs.length) e.push('$.blocker.evidence.factRefs: unique refs required')
180
+ if (x.kind === 'question' && blocker && Array.isArray(x.question?.approvalOptions)) {
181
+ for (const [i, option] of x.question.approvalOptions.entries()) {
182
+ const result = validateApprovalAgainstBlocker(option?.approval, blocker)
183
+ if (!result.ok) e.push(...result.errors.map(error => `$.question.approvalOptions[${i}].${error.slice(2)}`))
184
+ if (option?.approval?.taskId !== x.taskId) e.push(`$.question.approvalOptions[${i}].approval.taskId: must match event`)
185
+ if (option?.approval?.contextRef !== x.contextRef) e.push(`$.question.approvalOptions[${i}].approval.contextRef: must match event`)
186
+ }
187
+ }
188
+ if (kinds.has(String(x.kind)) && x.relaxationApprovalRef) {
189
+ const ref = x.relaxationApprovalRef
190
+ if (ref.contextRef !== x.contextRef) e.push('$.relaxationApprovalRef.contextRef: must match action')
191
+ if (ref.sourceRevision !== x.expectedRevision) e.push('$.relaxationApprovalRef.sourceRevision: must equal action revision')
192
+ if (ref.targetActionKind !== x.kind) e.push('$.relaxationApprovalRef.targetActionKind: must match action kind')
193
+ if (ref.targetActionId !== x.actionId) e.push('$.relaxationApprovalRef.targetActionId: must match action id')
194
+ // Expiry and one-time consumption are runtime concerns; validation checks only structure/binding.
195
+ if (!safeOpaque(ref.nonce) || !isBookingDateTime(ref.expiresAt)) e.push('$.relaxationApprovalRef: invalid expiry or nonce')
196
+ }
197
+ if (x.kind === 'question' && (!Array.isArray(x.question?.approvalOptions) || x.question.approvalOptions.length < 1 || x.question.approvalOptions.length > 2)) e.push('$.question.approvalOptions: one or two options required')
198
+ return e
199
+ }
200
+ export function validateBookingSurface(value: unknown): BookingSurfaceValidationResult { const ok = validate(value); const e = ok ? [] : errors(validate); e.push(...cross(value)); return e.length ? { ok: false, errors: e } : { ok: true } }
201
+ function fromCanonical(fn: ValidateFunction, value: unknown): BookingSurfaceValidationResult { const ok = fn(value); const e = ok ? [] : errors(fn); e.push(...cross(value)); return e.length ? { ok: false, errors: e } : { ok: true } }
202
+ export function validateBookingReadAction(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateActionCanonical, value) }
203
+ export function validateCriterionBlocker(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateBlockerCanonical, value) }
204
+ export function validateRelaxationApproval(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateApprovalCanonical, value) }
205
+ export function validateBookingSurfaceEvent(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateEventCanonical, value) }
206
+ export function validateBookingIntentProjection(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateIntentCanonical, value) }
207
+ export function validateApprovalAgainstBlocker(approval: unknown, blocker: unknown): BookingSurfaceValidationResult {
208
+ const a = approval as any
209
+ const b = blocker as any
210
+ const blockerResult = validateCriterionBlocker(b)
211
+ if (!blockerResult.ok) return blockerResult
212
+ const approvalResult = validateRelaxationApproval(a)
213
+ if (!approvalResult.ok) return approvalResult
214
+ const fields = ['blockerId', 'sourceActionId', 'sourceReceiptDigest', 'scope', 'code', 'criterionPath', 'valueDigest']
215
+ const mismatches = fields.filter(field => a[field] !== b?.[field])
216
+ return mismatches.length ? { ok: false, errors: mismatches.map(field => `$.${field}: approval does not match blocker`) } : { ok: true }
217
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * 同行人档案(memory-design M5 层,P2):同行人及其约束(行动/健康/偏好)的持久化。
3
+ *
4
+ * 立场(memory-design §1):
5
+ * - 溯源 P0:每条约束伴 evidence(用户原话),追加不删史;
6
+ * - 约束只进排序与行程结构建议,永不进硬过滤;
7
+ * - **负面清单守卫**:证件号/电话/地址等敏感字段拒收入库(需要时后端从登录态
8
+ * 填充,fail-closed)——health 存的是「高血压/晕车」这类行为约束,不是病历;
9
+ * - upsert 合并语义同 mergeProfile:追加不删史/同内容幂等。
10
+ */
11
+
12
+ export interface CompanionConstraints {
13
+ /** 行动/体力:如「走不了一段」「坐轮椅」 */
14
+ mobility?: string
15
+ /** 健康类行为约束:如「轻度高血压」「晕车」(非病历,禁止诊断性描述) */
16
+ health?: string[]
17
+ /** 偏好:如「怕吵」「爱吃辣」 */
18
+ prefs?: string[]
19
+ }
20
+
21
+ export interface CompanionProfile {
22
+ schema: 'companion_profile.v1'
23
+ /** 稳定主键:label 语义派生(去空白) */
24
+ companion_id: string
25
+ label: string
26
+ constraints: CompanionConstraints
27
+ /** 用户原话,追加不删史 */
28
+ evidence: string[]
29
+ ts: string
30
+ }
31
+
32
+ /** 负面清单:命中即拒收(红线 6 + 隐私立场) */
33
+ const SENSITIVE_PATTERNS: Array<{ re: RegExp; label: string }> = [
34
+ { re: /\d{15,17}[Xx]?/, label: '疑似证件号/卡号' },
35
+ { re: /1[3-9]\d{9}/, label: '疑似手机号' },
36
+ { re: /(护照|身份证|证件号|通行证)\s*号?\s*[::]?\s*\w+/i, label: '证件信息' },
37
+ ]
38
+
39
+ /** 守门:内容命中负面清单 → 返回拒收理由;否则 null */
40
+ export function sensitiveViolation(text: string): string | null {
41
+ for (const { re, label } of SENSITIVE_PATTERNS) {
42
+ if (re.test(text)) return `负面清单拒收:${label}不入库(需要时由后端从登录态填充)`
43
+ }
44
+ return null
45
+ }
46
+
47
+ /** 稳定主键:label 去空白 */
48
+ export function companionId(label: string): string {
49
+ return label.replace(/\s+/g, '')
50
+ }
51
+
52
+ function mergeStrArray(oldArr: string[] | undefined, newArr: string[] | undefined): { merged: string[]; added: boolean } {
53
+ const a = oldArr ?? []
54
+ let added = false
55
+ for (const x of newArr ?? []) {
56
+ if (!a.includes(x)) {
57
+ a.push(x)
58
+ added = true
59
+ }
60
+ }
61
+ return { merged: a, added }
62
+ }
63
+
64
+ /**
65
+ * upsert 合并(纯函数,不改入参):同 companion_id 按 label 归并——
66
+ * constraints 逐字段取新值(新值覆盖旧行为约束,追加数组项),evidence 追加不删史,
67
+ * 全量无新增 → 幂等 no-op(appended:false)。
68
+ */
69
+ export function upsertCompanion(
70
+ profiles: CompanionProfile[],
71
+ patch: { label: string; constraints: CompanionConstraints; evidence: string },
72
+ ): { profiles: CompanionProfile[]; appended: boolean; companionId: string; reason?: string } {
73
+ if (!patch.label?.trim()) return { profiles, appended: false, companionId: '', reason: 'label 必填' }
74
+ const violation = sensitiveViolation(JSON.stringify(patch))
75
+ if (violation) return { profiles, appended: false, companionId: '', reason: violation }
76
+ const id = companionId(patch.label)
77
+ const existing = profiles.find(p => p.companion_id === id)
78
+ if (!existing) {
79
+ const full: CompanionProfile = {
80
+ schema: 'companion_profile.v1',
81
+ companion_id: id,
82
+ label: patch.label.trim(),
83
+ constraints: {
84
+ mobility: patch.constraints.mobility,
85
+ health: patch.constraints.health ? [...patch.constraints.health] : undefined,
86
+ prefs: patch.constraints.prefs ? [...patch.constraints.prefs] : undefined,
87
+ },
88
+ evidence: [patch.evidence],
89
+ ts: new Date().toISOString(),
90
+ }
91
+ return { profiles: [...profiles, full], appended: true, companionId: id }
92
+ }
93
+ const mergedConstraints: CompanionConstraints = {
94
+ mobility: patch.constraints.mobility ?? existing.constraints.mobility,
95
+ health: mergeStrArray(existing.constraints.health, patch.constraints.health).merged,
96
+ prefs: mergeStrArray(existing.constraints.prefs, patch.constraints.prefs).merged,
97
+ }
98
+ const ev = mergeStrArray(existing.evidence, [patch.evidence])
99
+ const healthLen = mergedConstraints.health?.length ?? 0
100
+ const prefsLen = mergedConstraints.prefs?.length ?? 0
101
+ const changed =
102
+ mergedConstraints.mobility !== existing.constraints.mobility ||
103
+ healthLen !== (existing.constraints.health?.length ?? 0) ||
104
+ prefsLen !== (existing.constraints.prefs?.length ?? 0) ||
105
+ ev.added
106
+ if (!changed) return { profiles, appended: false, companionId: id }
107
+ const next = profiles.map(p =>
108
+ p.companion_id === id
109
+ ? { ...p, constraints: mergedConstraints, evidence: ev.merged, ts: new Date().toISOString() }
110
+ : p,
111
+ )
112
+ return { profiles: next, appended: true, companionId: id }
113
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Stage 1 契约层(S1,docs/stage1-top-down-design.md §2)——顶层数据与工具面契约。
2
+ * Stage 1 契约层(S1,docs/design/stage1-top-down-design.md §2)——顶层数据与工具面契约。
3
3
  *
4
4
  * 自顶向下纪律:本文件是唯一权威契约。实现(mock 循环/求解器挂载/真 LLM)都向这里对齐;
5
5
  * 契约变更需走设计文档升版,不随实现漂移。
@@ -16,8 +16,10 @@ export interface CalendarState {
16
16
  assertedWeekdays: Record<string, 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun'>
17
17
  }
18
18
 
19
- export interface WorkWindowProfile {
20
- homeTzOffsetMin: number
19
+ export interface ScheduledWorkWindowProfile {
20
+ /** Legacy v1 only; v2 pack metadata owns the IANA home zone. */
21
+ homeTzOffsetMin?: number
22
+ vacation?: false
21
23
  startMin: number
22
24
  endMin: number
23
25
  workdays: number[]
@@ -25,6 +27,14 @@ export interface WorkWindowProfile {
25
27
  evidence: string
26
28
  }
27
29
 
30
+ /** Explicit leave means this trip has no work-time restriction. */
31
+ export interface VacationWorkWindowProfile {
32
+ vacation: true
33
+ evidence?: string
34
+ }
35
+
36
+ export type WorkWindowProfile = ScheduledWorkWindowProfile | VacationWorkWindowProfile
37
+
28
38
  export interface TravelerProfile {
29
39
  workWindow?: WorkWindowProfile
30
40
  companions?: string[]
@@ -66,11 +76,15 @@ export interface TripState {
66
76
  // ---- 求解结果(unified 求解器输出的契约化引用) ----------------------------------
67
77
 
68
78
  export interface SolveResult {
79
+ /** `solver_error` 存在时,`feasible: false` 只表示「没有产出可行解」,
80
+ * **不表示「不可行」**(issue #620):那一轮求解器自己失败了,判定缺席。 */
69
81
  feasible: boolean
70
82
  money_cny?: number
71
83
  legs?: Array<Record<string, unknown> & { leg: string }>
72
84
  red_flags?: string[]
73
85
  unsat_core?: string[]
86
+ /** 求解器自身失败(非判定)。机器 token 只住 `code`,不进用户面「冲突」文案。 */
87
+ solver_error?: { code: string; message: string }
74
88
  suggestions?: Array<{ relax: string; money_cny?: number }>
75
89
  work_window_exclusions?: Array<{ segment: string; option: string; reason: string }>
76
90
  skeleton_notes?: string[]