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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (507) hide show
  1. package/README.md +172 -219
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1744 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -56
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +135 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +546 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +947 -110
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +509 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/resilience.js +191 -0
  41. package/dist/capabilities/session/action-cache.js +50 -44
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
  43. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  44. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  45. package/dist/capabilities/session/adapters/meituan-local.js +35 -54
  46. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  47. package/dist/capabilities/session/benchmark.js +189 -0
  48. package/dist/capabilities/session/bridge-ledger.js +224 -0
  49. package/dist/capabilities/session/extension-bridge.js +636 -0
  50. package/dist/capabilities/session/extension-channel.js +109 -0
  51. package/dist/capabilities/session/extension-distribution.js +230 -0
  52. package/dist/capabilities/session/extract.js +31 -21
  53. package/dist/capabilities/session/golden-score.js +114 -0
  54. package/dist/capabilities/session/health-watch.js +215 -0
  55. package/dist/capabilities/session/read-guard.js +75 -24
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +97 -83
  58. package/dist/capabilities/session/wizard.js +117 -0
  59. package/dist/capabilities/session-consent.js +178 -0
  60. package/dist/capabilities/session-login.js +170 -0
  61. package/dist/capabilities/session-search.js +635 -44
  62. package/dist/capabilities/spawn-bounded.js +203 -0
  63. package/dist/capabilities/sponsor-plugin.js +341 -0
  64. package/dist/capabilities/visa-policy.js +140 -0
  65. package/dist/capabilities/weather.js +243 -138
  66. package/dist/data/session-golden-20.json +25 -0
  67. package/dist/data/sf-golden-manifest.json +103 -0
  68. package/dist/data/sf-static-routes.json +91 -0
  69. package/dist/scripts/action-cache-tests.js +24 -59
  70. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  71. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  72. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  73. package/dist/scripts/agent-reach-error-tests.js +354 -0
  74. package/dist/scripts/agent-reach-tests.js +26 -18
  75. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  76. package/dist/scripts/anything-tests.js +116 -60
  77. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  78. package/dist/scripts/artifacts-capability-tests.js +727 -0
  79. package/dist/scripts/async-collect.js +50 -11
  80. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  81. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  82. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  83. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  84. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  85. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  86. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  87. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  88. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  89. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  90. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  91. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
  92. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  93. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  94. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  95. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  96. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  97. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  98. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  99. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  100. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  101. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  102. package/dist/scripts/booking-executor-tests.js +80 -0
  103. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  104. package/dist/scripts/booking-saga-tests.js +125 -0
  105. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  106. package/dist/scripts/bootstrap-tests.js +1479 -0
  107. package/dist/scripts/bridge-events-tests.js +239 -0
  108. package/dist/scripts/bridge-ledger-tests.js +428 -0
  109. package/dist/scripts/build-changelog.js +238 -0
  110. package/dist/scripts/build-metrics-report.js +331 -0
  111. package/dist/scripts/changelog-tests.js +118 -0
  112. package/dist/scripts/channel-probe-tests.js +103 -0
  113. package/dist/scripts/channel-probe.js +169 -0
  114. package/dist/scripts/channel-registry-tests.js +208 -0
  115. package/dist/scripts/companion-tests.js +25 -70
  116. package/dist/scripts/dida-cookie-probe.js +87 -0
  117. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  118. package/dist/scripts/dida-sw-probe.js +114 -0
  119. package/dist/scripts/diff-test.js +17 -12
  120. package/dist/scripts/doctor-tests.js +564 -0
  121. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  122. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  123. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  124. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  125. package/dist/scripts/dsh-runtime-closure.js +162 -0
  126. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  127. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  128. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  129. package/dist/scripts/effect-tests.js +539 -0
  130. package/dist/scripts/engine-run.js +1 -3
  131. package/dist/scripts/engine-tests.js +16 -10
  132. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  133. package/dist/scripts/evaluation-contract-tests.js +268 -0
  134. package/dist/scripts/extension-distribution-cli.js +39 -0
  135. package/dist/scripts/extension-distribution-tests.js +443 -0
  136. package/dist/scripts/extension-tests.js +988 -0
  137. package/dist/scripts/external-event-tests.js +350 -0
  138. package/dist/scripts/fact-gate-tests.js +869 -0
  139. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  140. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  141. package/dist/scripts/flight-malformed-tests.js +159 -0
  142. package/dist/scripts/flyai-setup-tests.js +228 -0
  143. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  144. package/dist/scripts/flyai-tests.js +692 -0
  145. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  146. package/dist/scripts/fx-contract-tests.js +201 -0
  147. package/dist/scripts/g5-guard-tests.js +131 -0
  148. package/dist/scripts/g5-guard.js +189 -0
  149. package/dist/scripts/gen-lunar-anchors.js +106 -0
  150. package/dist/scripts/geo-atlas-tests.js +283 -0
  151. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  152. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  153. package/dist/scripts/gotry-try-demo.js +49 -0
  154. package/dist/scripts/ground-transfer-tests.js +956 -0
  155. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  156. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  157. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  158. package/dist/scripts/hbcli-tests.js +121 -69
  159. package/dist/scripts/health-watch-cli.js +73 -0
  160. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  161. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  162. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  163. package/dist/scripts/i18n-tests.js +41 -0
  164. package/dist/scripts/incident-tests.js +281 -87
  165. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  166. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  167. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  168. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  169. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  170. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  171. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  172. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  173. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  174. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  175. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  176. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  177. package/dist/scripts/itinerary-deck-tests.js +435 -0
  178. package/dist/scripts/itinerary-html-tests.js +426 -0
  179. package/dist/scripts/journey-tests.js +22 -27
  180. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  181. package/dist/scripts/kernel-manifest-gate.js +126 -0
  182. package/dist/scripts/kernel-manifest-tests.js +212 -0
  183. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  184. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  185. package/dist/scripts/kernel-manifest.js +369 -0
  186. package/dist/scripts/lavish-local-tests.js +1226 -0
  187. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  188. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  189. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  190. package/dist/scripts/ledger-tests.js +382 -246
  191. package/dist/scripts/ledger-workflow-crash.js +10 -20
  192. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  193. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  194. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  195. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  196. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  197. package/dist/scripts/memory-capture-tests.js +242 -61
  198. package/dist/scripts/memory-decay-tests.js +18 -52
  199. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  200. package/dist/scripts/memory-lifecycle.js +196 -0
  201. package/dist/scripts/memory-metrics.js +24 -12
  202. package/dist/scripts/memory-value-report.js +631 -0
  203. package/dist/scripts/memory-value-tests.js +328 -0
  204. package/dist/scripts/metrics-report-tests.js +151 -0
  205. package/dist/scripts/model-override-e2e.js +137 -0
  206. package/dist/scripts/nightly-evidence-tests.js +161 -0
  207. package/dist/scripts/nightly-evidence.js +286 -0
  208. package/dist/scripts/nudge-digest.js +39 -28
  209. package/dist/scripts/observability-tests.js +381 -0
  210. package/dist/scripts/onboarding-tests.js +161 -0
  211. package/dist/scripts/opensky-check.js +18 -13
  212. package/dist/scripts/opensky-tests.js +28 -28
  213. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  214. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  215. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  216. package/dist/scripts/price-drift-tests.js +340 -0
  217. package/dist/scripts/price-drift-watch.js +447 -0
  218. package/dist/scripts/probe-poi-tests.js +53 -45
  219. package/dist/scripts/product-metrics.js +483 -0
  220. package/dist/scripts/publish-preverify.js +110 -0
  221. package/dist/scripts/realtime-pricing-tests.js +76 -0
  222. package/dist/scripts/recall-tests.js +513 -0
  223. package/dist/scripts/replay-async.js +20 -18
  224. package/dist/scripts/replay-real.js +18 -18
  225. package/dist/scripts/replay.js +52 -82
  226. package/dist/scripts/session-attach-diagnose.js +20 -20
  227. package/dist/scripts/session-attach-poc.js +34 -37
  228. package/dist/scripts/session-benchmark.js +180 -0
  229. package/dist/scripts/session-dida-live-e2e.js +199 -0
  230. package/dist/scripts/session-extract-tests.js +14 -35
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +27 -46
  233. package/dist/scripts/session-tests.js +796 -96
  234. package/dist/scripts/session-zones-tests.js +358 -0
  235. package/dist/scripts/sf-live-benchmark.js +367 -0
  236. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  237. package/dist/scripts/sf-live-cli-tests.js +92 -0
  238. package/dist/scripts/sf-live-options.js +46 -0
  239. package/dist/scripts/sf-manifest-tests.js +30 -0
  240. package/dist/scripts/sf-manifest.js +33 -0
  241. package/dist/scripts/sf-soft-score-tests.js +67 -0
  242. package/dist/scripts/sf-summary-tests.js +324 -0
  243. package/dist/scripts/sf-summary.js +525 -0
  244. package/dist/scripts/share-tests.js +432 -0
  245. package/dist/scripts/skeleton-check.js +16 -14
  246. package/dist/scripts/skeleton-integration-test.js +28 -11
  247. package/dist/scripts/skills-contract-tests.js +51 -46
  248. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  249. package/dist/scripts/smoke.js +690 -246
  250. package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
  251. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  252. package/dist/scripts/state-cli-tests.js +226 -101
  253. package/dist/scripts/state-cli.js +550 -199
  254. package/dist/scripts/state-repair-cli.js +66 -0
  255. package/dist/scripts/state-repair-tests.js +140 -0
  256. package/dist/scripts/static-golden-tests.js +165 -0
  257. package/dist/scripts/time-eval-tests.js +210 -192
  258. package/dist/scripts/travel-timeline-tests.js +29 -93
  259. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  260. package/dist/scripts/turn-handoff-collect.js +161 -0
  261. package/dist/scripts/turn-policy-tests.js +55 -0
  262. package/dist/scripts/typed-contract-canary.js +176 -0
  263. package/dist/scripts/tz-resolver-sanity.js +50 -0
  264. package/dist/scripts/tz-resolver-tests.js +584 -0
  265. package/dist/scripts/unified-tests.js +23 -19
  266. package/dist/scripts/visa-policy-tests.js +62 -0
  267. package/dist/scripts/weather-tests.js +276 -41
  268. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  269. package/dist/scripts/write-gate-tests.js +522 -0
  270. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  271. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  272. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  273. package/dist/scripts/z3-race-tests.js +41 -0
  274. package/dist/src/artifact-gate.js +829 -0
  275. package/dist/src/backend/kernel.js +91 -0
  276. package/dist/src/backend/modules/booking-copilot.js +92 -0
  277. package/dist/src/backend/modules/booking-executor.js +124 -0
  278. package/dist/src/backend/modules/session-search.js +551 -0
  279. package/dist/src/benchmark-agent-conformance.js +578 -0
  280. package/dist/src/benchmark-environment-bridge.js +515 -0
  281. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  282. package/dist/src/benchmark-tool-isolation.js +151 -0
  283. package/dist/src/bookable-facts.js +533 -0
  284. package/dist/src/booking-saga.js +97 -0
  285. package/dist/src/booking-surface/availability-policy.js +697 -0
  286. package/dist/src/booking-surface/booking-intent.js +43 -0
  287. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  288. package/dist/src/booking-surface/canonical-schema.js +116 -0
  289. package/dist/src/booking-surface/contracts.js +75 -0
  290. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  291. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  292. package/dist/src/booking-surface/dsh-worker.js +98 -0
  293. package/dist/src/booking-surface/error-codes.js +231 -0
  294. package/dist/src/booking-surface/index.js +9 -0
  295. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  296. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  297. package/dist/src/booking-surface/profile.js +24 -0
  298. package/dist/src/booking-surface/recovery-chain.js +367 -0
  299. package/dist/src/booking-surface/runtime.js +1968 -0
  300. package/dist/src/booking-surface/server.js +674 -0
  301. package/dist/src/booking-surface/startup.js +168 -0
  302. package/dist/src/booking-surface/validation.js +261 -0
  303. package/dist/src/bridge.js +20 -13
  304. package/dist/src/companions.js +48 -74
  305. package/dist/src/contracts.js +21 -43
  306. package/dist/src/dsh-llm.js +97 -111
  307. package/dist/src/engine.js +276 -193
  308. package/dist/src/evaluation-cadence.js +97 -0
  309. package/dist/src/evaluation-contracts.js +283 -0
  310. package/dist/src/flight-pack-adapter.js +54 -0
  311. package/dist/src/flight-pack-contract.js +2 -0
  312. package/dist/src/flyai-setup-tool.js +70 -0
  313. package/dist/src/gotry-backend.js +125 -0
  314. package/dist/src/hotel-date-gate.js +118 -0
  315. package/dist/src/i18n.js +88 -0
  316. package/dist/src/index.js +2227 -882
  317. package/dist/src/itinerary-deck.js +139 -0
  318. package/dist/src/itinerary-doc-shared.js +661 -0
  319. package/dist/src/itinerary-html.js +131 -0
  320. package/dist/src/journey.js +155 -108
  321. package/dist/src/lavish-tools.js +400 -0
  322. package/dist/src/ledger-repair-apply.js +538 -0
  323. package/dist/src/ledger-repair-plan.js +463 -0
  324. package/dist/src/loop.js +319 -160
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +30 -14
  327. package/dist/src/memory-lifecycle.js +1310 -0
  328. package/dist/src/memory-utility.js +27 -40
  329. package/dist/src/mock-llm.js +36 -61
  330. package/dist/src/model.js +81 -36
  331. package/dist/src/realtime-pricing.js +103 -0
  332. package/dist/src/recall/card.js +74 -0
  333. package/dist/src/recall/evaluator.js +140 -0
  334. package/dist/src/recall/integration.js +17 -0
  335. package/dist/src/recall/tick.js +166 -0
  336. package/dist/src/session-link/plan-it-action.js +48 -0
  337. package/dist/src/session-link/session-link.js +248 -0
  338. package/dist/src/session-zones.js +507 -0
  339. package/dist/src/share/adapters.js +51 -0
  340. package/dist/src/share/share-consent.js +76 -0
  341. package/dist/src/share/share-deck.js +58 -0
  342. package/dist/src/share/share-token.js +146 -0
  343. package/dist/src/slot-spec.js +84 -106
  344. package/dist/src/state-ledger.js +435 -516
  345. package/dist/src/state-repair.js +299 -0
  346. package/dist/src/subagent-job-id-guard.js +58 -0
  347. package/dist/src/time-anchor.js +231 -33
  348. package/dist/src/tool-packet.js +20 -8
  349. package/dist/src/travel-slots.js +58 -35
  350. package/dist/src/travel-timeline.js +44 -63
  351. package/dist/src/turn-deadline.js +287 -0
  352. package/dist/src/turn-policy.js +118 -0
  353. package/dist/src/tz-resolver.js +143 -0
  354. package/dist/src/unified.js +535 -359
  355. package/dist/src/wish-pool.js +42 -12
  356. package/dist/src/write-gate.js +495 -0
  357. package/dist/src/z3-shared.js +284 -0
  358. package/extension/README.md +21 -0
  359. package/extension/README.zh-CN.md +21 -0
  360. package/extension/background.js +463 -0
  361. package/extension/content-bridge.js +270 -0
  362. package/extension/content-main.js +111 -0
  363. package/extension/manifest.json +60 -0
  364. package/package.json +407 -14
  365. package/schemas/booking.intent.schema.json +193 -0
  366. package/schemas/booking.surface.schema.json +2622 -0
  367. package/ts/capabilities/agent-reach-deep.ts +114 -0
  368. package/ts/capabilities/agent-reach.ts +198 -0
  369. package/ts/capabilities/anything.ts +255 -0
  370. package/ts/capabilities/artifacts.ts +428 -0
  371. package/ts/capabilities/fact-log.ts +54 -0
  372. package/ts/capabilities/flyai-config.ts +388 -0
  373. package/ts/capabilities/flyai.ts +1274 -0
  374. package/ts/capabilities/hbcli.ts +253 -71
  375. package/ts/capabilities/incident-log.ts +88 -59
  376. package/ts/capabilities/itinerary-artifact.ts +214 -0
  377. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  378. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  379. package/ts/capabilities/opensky.ts +121 -0
  380. package/ts/capabilities/session/action-cache.ts +148 -0
  381. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  382. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  383. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  384. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  385. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  386. package/ts/capabilities/session/benchmark.ts +276 -0
  387. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  388. package/ts/capabilities/session/extension-bridge.ts +794 -0
  389. package/ts/capabilities/session/extension-channel.ts +144 -0
  390. package/ts/capabilities/session/extension-distribution.ts +266 -0
  391. package/ts/capabilities/session/extract.ts +54 -0
  392. package/ts/capabilities/session/golden-score.ts +139 -0
  393. package/ts/capabilities/session/health-watch.ts +264 -0
  394. package/ts/capabilities/session/read-guard.ts +141 -0
  395. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  396. package/ts/capabilities/session/transport.ts +146 -0
  397. package/ts/capabilities/session/wizard.ts +140 -0
  398. package/ts/capabilities/session-consent.ts +211 -0
  399. package/ts/capabilities/session-login.ts +208 -0
  400. package/ts/capabilities/session-search.ts +893 -0
  401. package/ts/capabilities/weather.ts +330 -0
  402. package/ts/dsh-runtime/vendor/README.md +68 -0
  403. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  404. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  405. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  406. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  407. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  408. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  409. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  410. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  411. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  412. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  413. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  414. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  415. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  416. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  417. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  418. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  419. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  420. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  421. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  422. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  423. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  424. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  425. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  426. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  427. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  428. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  440. package/ts/package.json +293 -3
  441. package/ts/scripts/async-collect.ts +91 -0
  442. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  443. package/ts/scripts/skeleton-check.ts +1 -1
  444. package/ts/scripts/skeleton-integration-test.ts +23 -1
  445. package/ts/scripts/state-cli.ts +568 -0
  446. package/ts/scripts/turn-handoff-collect.ts +178 -0
  447. package/ts/src/artifact-gate.ts +991 -0
  448. package/ts/src/benchmark-agent-conformance.ts +619 -0
  449. package/ts/src/benchmark-environment-bridge.ts +538 -0
  450. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  451. package/ts/src/benchmark-tool-isolation.ts +166 -0
  452. package/ts/src/bookable-facts.ts +837 -0
  453. package/ts/src/booking-saga.ts +133 -0
  454. package/ts/src/booking-surface/availability-policy.ts +522 -0
  455. package/ts/src/booking-surface/booking-intent.ts +86 -0
  456. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  457. package/ts/src/booking-surface/canonical-schema.js +116 -0
  458. package/ts/src/booking-surface/contracts.ts +475 -0
  459. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  460. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  461. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  462. package/ts/src/booking-surface/error-codes.ts +241 -0
  463. package/ts/src/booking-surface/index.ts +9 -0
  464. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  465. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  466. package/ts/src/booking-surface/profile.ts +42 -0
  467. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  468. package/ts/src/booking-surface/runtime.ts +1793 -0
  469. package/ts/src/booking-surface/server.ts +733 -0
  470. package/ts/src/booking-surface/startup.ts +201 -0
  471. package/ts/src/booking-surface/validation.ts +217 -0
  472. package/ts/src/companions.ts +113 -0
  473. package/ts/src/contracts.ts +13 -3
  474. package/ts/src/dsh-llm.ts +44 -11
  475. package/ts/src/flyai-setup-tool.ts +77 -0
  476. package/ts/src/index.ts +1948 -198
  477. package/ts/src/itinerary-deck.ts +189 -0
  478. package/ts/src/itinerary-doc-shared.ts +741 -0
  479. package/ts/src/itinerary-html.ts +171 -0
  480. package/ts/src/loop.ts +223 -25
  481. package/ts/src/memory-capture.ts +283 -0
  482. package/ts/src/memory-decay.ts +55 -0
  483. package/ts/src/memory-utility.ts +70 -0
  484. package/ts/src/mock-llm.ts +4 -8
  485. package/ts/src/model.ts +88 -2
  486. package/ts/src/recall/card.ts +106 -0
  487. package/ts/src/recall/evaluator.ts +170 -0
  488. package/ts/src/recall/integration.ts +35 -0
  489. package/ts/src/recall/tick.ts +213 -0
  490. package/ts/src/session-link/plan-it-action.ts +78 -0
  491. package/ts/src/session-link/session-link.ts +275 -0
  492. package/ts/src/share/adapters.ts +93 -0
  493. package/ts/src/share/share-consent.ts +105 -0
  494. package/ts/src/share/share-deck.ts +85 -0
  495. package/ts/src/share/share-token.ts +166 -0
  496. package/ts/src/slot-spec.ts +172 -0
  497. package/ts/src/state-ledger.ts +912 -0
  498. package/ts/src/subagent-job-id-guard.ts +72 -0
  499. package/ts/src/time-anchor.ts +338 -0
  500. package/ts/src/tool-packet.ts +32 -0
  501. package/ts/src/travel-slots.ts +219 -0
  502. package/ts/src/travel-timeline.ts +83 -0
  503. package/ts/src/turn-deadline.ts +366 -0
  504. package/ts/src/turn-policy.ts +154 -0
  505. package/ts/src/unified.ts +354 -88
  506. package/ts/src/wish-pool.ts +79 -0
  507. package/dist/scripts/session-attach-wait.js +0 -41
@@ -0,0 +1,283 @@
1
+ /**
2
+ * T1 动态记忆的合并守门层(M4 记忆域;DeerFlow 研究 T1 落地):
3
+ *
4
+ * 分工(founder 校正「正则匹配 rules 不对」后重写):
5
+ * - 事实提取 = LLM 的活。契约 (18) 已要求模型当轮把对话透露的新事实
6
+ * (预算/日期/窗口/出发地/同伴/实际经历)经 gotry_motivation_save 并入
7
+ * 画像(evidence=用户原话)——语言理解交给模型,不做第二套正则引擎。
8
+ * - 合并守门 = 代码的活。本文件只做三件代码才可靠的事:
9
+ * ① 追加不删史(evidence 永不移除,P0 反幻觉:历史依据不可篡改)
10
+ * ② 幂等(同 evidence 不重复入池)
11
+ * ③ 结构守卫(weights 存在性校验、空补丁拒绝、权重归一)
12
+ *
13
+ * 额外承载(issue #338):用户显式声明的常驻城市/默认出发地——typed
14
+ * homeCity 字段 + 纯函数 resolveDefaultOrigin(explicit origin, profile)。
15
+ * - 当前轮显式出发地永远赢(explicit origin > home city > missing)
16
+ * - homeCity 变更必须伴新 evidence(P0 与 weights/hard 同向);单条 evidence
17
+ * 可作为省略 binding 的兼容形态,多条 evidence 必须显式绑定
18
+ * - 该契约不触碰算术/求解——只读字段,不进 feasibility/evaluate 路径
19
+ *
20
+ * 持久化语义(issue #338):home-city 是 typed self-contained preference
21
+ * 记录 `{ value, evidence, updated_at }`,与全局 evidence 列表解耦——
22
+ * 一次不相关的动机补丁不会改写它的「证据指针」「更新时间」「当前值」。
23
+ * `updated_at` 由调用方在事件边界注入(账本事件行的 ts),mergeProfile
24
+ * 自身不在 fold 路径里再读时钟——保证 rebuildProjections() 与直读投影
25
+ * 永不分叉。
26
+ */
27
+
28
+ export interface HomeCityPreference {
29
+ /** string=当前默认城市,null=已显式清除(审计一行留痕) */
30
+ value: string | null
31
+ /** 锁定到本次 home-city 操作的用户原话指针(append-only evidence 池里的那条) */
32
+ evidence: string
33
+ /** 账本事件行的 ts(非折叠期再读时钟) */
34
+ updated_at: string
35
+ }
36
+
37
+ export interface ProfilePatch {
38
+ weights?: Record<string, number>
39
+ /** 新增依据(用户原话或工具证据);只增不改不删 */
40
+ evidence?: string[]
41
+ hard?: Record<string, unknown>
42
+ /**
43
+ * 持久化用户常驻城市/默认出发地(issue #338)。
44
+ * - string = 设置默认城市(用户原话指明常驻)
45
+ * - null = 显式清除(审计一行留痕,用于遗忘/迁居)
46
+ * - undefined = 字段未传,保持当前值不动
47
+ * 改值须伴至少一条新 evidence(同 weights/hard 的 P0 守卫);恰好一条非空
48
+ * evidence 时可省略 homeCityEvidence,多条 evidence 必须显式绑定。
49
+ * 进入合并前会 trim;trim 后空白 → 视为「未传」,不触发变更、不落事件。
50
+ */
51
+ homeCity?: string | null
52
+ /**
53
+ * homeCity 在本次 profile.evidence 中的 exact 用户原话绑定。
54
+ * 显式传入时必须非空、逐字出现在 evidence 且相对当前画像为新值;
55
+ * 未传时仅允许 evidence 恰好一条且非空,由该条提供 unambiguous binding;
56
+ * 唯一例外是同 value/clear state + 同 typed evidence 的重复调用(幂等)。
57
+ */
58
+ homeCityEvidence?: string
59
+ }
60
+
61
+ export interface MergedProfile {
62
+ weights: Record<string, number>
63
+ evidence: string[]
64
+ hard: Record<string, unknown>
65
+ /** 当前持久默认城市。undefined 从未保存;null 显式清除;string 当前默认。 */
66
+ homeCity?: string | null
67
+ /**
68
+ * home-city 自含记录(issue #338):value + 锁定证据 + 更新时间三件套,
69
+ * 与全局 evidence 列表解耦。缺少或不满足完整 typed contract 的画像按
70
+ * missing 处理;raw homeCity 不会成为默认值或证据来源。
71
+ */
72
+ homeCityPreference?: HomeCityPreference
73
+ }
74
+
75
+ const ISO_LIKE_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,3})?(Z|[+-]\d{2}:\d{2})$/
76
+
77
+ function isNonBlankString(value: unknown): value is string {
78
+ return typeof value === 'string' && value.trim().length > 0
79
+ }
80
+
81
+ function isValidIsoLikeTimestamp(value: unknown): value is string {
82
+ if (typeof value !== 'string') return false
83
+ const match = ISO_LIKE_TIMESTAMP.exec(value)
84
+ if (!match) return false
85
+ const year = Number(match[1])
86
+ const month = Number(match[2])
87
+ const day = Number(match[3])
88
+ const hour = Number(match[4])
89
+ const minute = Number(match[5])
90
+ const second = Number(match[6])
91
+ const zone = match[7]!
92
+ const daysInMonth = month === 2
93
+ ? (year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28)
94
+ : [31, 0, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1]
95
+ if (!daysInMonth || day < 1 || day > daysInMonth) return false
96
+ // ISO permits 24:00:00 as the end-of-day spelling; no other 24:xx value.
97
+ if (hour > 23 && !(hour === 24 && minute === 0 && second === 0)) return false
98
+ if (minute > 59 || second > 59) return false
99
+ if (zone !== 'Z' && (Number(zone.slice(1, 3)) > 23 || Number(zone.slice(4, 6)) > 59)) return false
100
+ return Number.isFinite(Date.parse(value))
101
+ }
102
+
103
+ /**
104
+ * Typed home-city preference 的唯一有效性判定。
105
+ * 不读取、不修写 raw profile;raw homeCity 若存在只作镜像一致性校验。
106
+ */
107
+ export function isValidHomeCityPreference(profile: {
108
+ homeCity?: unknown
109
+ evidence?: unknown
110
+ homeCityPreference?: unknown
111
+ } | null): boolean {
112
+ const preference = profile?.homeCityPreference
113
+ if (!preference || typeof preference !== 'object' || Array.isArray(preference)) return false
114
+ const candidate = preference as Partial<HomeCityPreference>
115
+ if (!(candidate.value === null || isNonBlankString(candidate.value))) return false
116
+ if (!isNonBlankString(candidate.evidence) || !isValidIsoLikeTimestamp(candidate.updated_at)) return false
117
+ if (!profile || !Array.isArray(profile.evidence) || !profile.evidence.some(entry => entry === candidate.evidence)) return false
118
+ if (profile && Object.prototype.hasOwnProperty.call(profile, 'homeCity') && profile.homeCity !== candidate.value) return false
119
+ return true
120
+ }
121
+
122
+ /**
123
+ * 合并守门。纯函数;不读时钟——`now` 由调用方从事件边界(appendMotivationPatch
124
+ * 写时 ISO 或 foldEvent 重放时 row.ts)注入,保证写路径与重建路径时间戳一致。
125
+ *
126
+ * 触发 homeCity 操作的同时,会原地维护 typed homeCityPreference:
127
+ * - valid new binding → 新 `{value, evidence: <homeCityEvidence exact string>, updated_at: now}`
128
+ * - unchanged → 沿用 base 的 homeCityPreference(若有);缺失 typed 字段保持缺失
129
+ * - missing/mismatched/stale binding → 返回 null,由账本跳过整次事件
130
+ */
131
+ export function mergeProfile(
132
+ current: {
133
+ weights?: Record<string, number>
134
+ evidence?: string[]
135
+ hard?: Record<string, unknown>
136
+ homeCity?: string | null
137
+ homeCityPreference?: HomeCityPreference
138
+ } | null,
139
+ patch: ProfilePatch | null,
140
+ now: string,
141
+ ): MergedProfile | null {
142
+ if (!patch) return null
143
+ const base = {
144
+ weights: (current?.weights ?? {}) as Record<string, number>,
145
+ evidence: (current?.evidence ?? []) as string[],
146
+ hard: (current?.hard ?? {}) as Record<string, unknown>,
147
+ homeCity: (current?.homeCity === undefined ? undefined : current.homeCity) as string | null | undefined,
148
+ homeCityPreference: current?.homeCityPreference as HomeCityPreference | undefined,
149
+ } // 首次保存:无档案即空档案
150
+ const newEvidence = (patch.evidence ?? []).filter(Boolean).filter(e => !base.evidence.includes(e))
151
+ const patchWeights = patch.weights
152
+ const weightsChanged = !!patchWeights && Object.keys(patchWeights).length > 0 &&
153
+ JSON.stringify(patchWeights) !== JSON.stringify(base.weights)
154
+ const hardChanged = !!patch.hard && Object.keys(patch.hard).length > 0 &&
155
+ JSON.stringify(patch.hard) !== JSON.stringify(base.hard)
156
+ // homeCity 变更判定:
157
+ // patch.homeCity === undefined → 字段未传,不视为变更
158
+ // string 先 trim;trim 后空白 → 视作「未传」(避免把空白值当清除语义或无意义值写入)
159
+ // trim 后与 base.homeCity 仍不等 → 视为变更(包括 string↔null、null↔undefined 等差异)
160
+ let patchedHomeCityInput: string | null | undefined = patch.homeCity
161
+ if (typeof patchedHomeCityInput === 'string') {
162
+ const trimmed = patchedHomeCityInput.trim()
163
+ patchedHomeCityInput = trimmed.length === 0 ? undefined : trimmed
164
+ }
165
+ const homeCityTouched = patchedHomeCityInput !== undefined
166
+ const homeCityChanged = homeCityTouched && patchedHomeCityInput !== base.homeCity
167
+
168
+ if (!homeCityTouched && patch.homeCityEvidence !== undefined) return null
169
+
170
+ // homeCity 的语义绑定必须在 profile patch 边界明确给出,不从自然语言或
171
+ // evidence 数组位置推断。唯一兼容例外:本次只有一条非空 evidence 时,
172
+ // 该条本身就是 unambiguous binding;多条 evidence 仍必须显式命名。
173
+ let homeCityEvidence: string | undefined
174
+ let homeCityEvidenceIsNew = false
175
+ if (homeCityTouched) {
176
+ const evidenceEntries = patch.evidence ?? []
177
+ const candidate = patch.homeCityEvidence !== undefined
178
+ ? patch.homeCityEvidence
179
+ : evidenceEntries.length === 1 && isNonBlankString(evidenceEntries[0])
180
+ ? evidenceEntries[0]
181
+ : undefined
182
+ const appearsInCall = isNonBlankString(candidate) && evidenceEntries.some(e => e === candidate)
183
+ homeCityEvidenceIsNew = appearsInCall && !base.evidence.includes(candidate)
184
+ const exactDuplicate = appearsInCall && !homeCityEvidenceIsNew && !homeCityChanged
185
+ && base.homeCityPreference?.value === patchedHomeCityInput
186
+ && base.homeCityPreference?.evidence === candidate
187
+ if (!appearsInCall || (!homeCityEvidenceIsNew && !exactDuplicate)) return null
188
+ homeCityEvidence = candidate
189
+ }
190
+
191
+ // A same-city/clear operation with a new bound quote is a real preference
192
+ // update even though the value itself did not change. The exact duplicate
193
+ // above remains a no-op.
194
+ const homeCityPreferenceChanged = homeCityTouched && homeCityEvidenceIsNew
195
+ if (newEvidence.length === 0 && !weightsChanged && !hardChanged && !homeCityChanged && !homeCityPreferenceChanged) return null
196
+
197
+ // 权重变更必须伴至少一条新 evidence(P0:改画像要有依据),否则拒该部分
198
+ let weights = { ...base.weights }
199
+ if (weightsChanged && newEvidence.length > 0 && patchWeights) {
200
+ weights = { ...weights, ...patchWeights }
201
+ const sum = Object.values(weights).reduce((a, b) => a + b, 0)
202
+ if (sum > 0) for (const k of Object.keys(weights)) weights[k] = Math.round((weights[k]! / sum) * 100) / 100
203
+ }
204
+
205
+ // homeCity 操作只接受上面验证过的 exact homeCityEvidence 绑定;无关 evidence
206
+ // 可以继续追加到全局池,但绝不会成为 homeCityPreference 的语义指针。
207
+ let homeCity: string | null | undefined = base.homeCity
208
+ let homeCityPreference: HomeCityPreference | undefined = base.homeCityPreference
209
+ if (homeCityPreferenceChanged) {
210
+ homeCity = patchedHomeCityInput ?? null
211
+ if (homeCityEvidence !== undefined) {
212
+ homeCityPreference = { value: homeCity, evidence: homeCityEvidence, updated_at: now }
213
+ }
214
+ }
215
+
216
+ return {
217
+ weights,
218
+ evidence: [...base.evidence, ...newEvidence],
219
+ hard: hardChanged ? { ...base.hard, ...(patch.hard ?? {}) } : { ...base.hard },
220
+ homeCity,
221
+ homeCityPreference,
222
+ }
223
+ }
224
+
225
+ /**
226
+ * 默认出发地解析契约(issue #338)。
227
+ *
228
+ * 纯函数,不读文件、不查 IP/浏览器位置/语言/时区/历史行程;不构造新 evaluate/solve 路径。
229
+ * 调用方负责传入当轮上下文(currentTripOrigin)与持久画像(profile)。
230
+ *
231
+ * 三态判决:
232
+ * - explicit 当轮显式出发地(用户当轮输入);永远赢,不被持久默认覆盖
233
+ * - home_default 持久化 homeCity;仅在当轮未指定时填补缺省(用于软排序/兜底)
234
+ * - missing 二者皆空或显式已清除;调用方须向用户澄清而非自行猜测
235
+ *
236
+ * 该契约的硬约束:
237
+ * - 不删除候选、不改变可行性判定、不进 evaluate/solve 路径
238
+ * - 「只进软排序/补缺」语义由调用方在排序/回退层使用本结果
239
+ *
240
+ * 证据来源:只接受 self-contained typed preference(issue #338)的完整合同。
241
+ * 缺字段、镜像不一致或字段 malformed 的画像一律 missing,不从 raw homeCity
242
+ * 或全局 evidence 推断默认值/原话。
243
+ */
244
+ export interface DefaultOriginResolution {
245
+ /** 解析后的出发地;null = 调用方应澄清 */
246
+ origin: string | null
247
+ /** 取值来源 */
248
+ source: 'explicit' | 'home_default' | 'missing'
249
+ /** 当前持久 homeCity(原值,便于 UI/日志/审计展示) */
250
+ homeCity: string | null | undefined
251
+ /** 锁定到本次 home-city 操作的用户原话指针;仅 valid home_default 填 */
252
+ homeCityEvidence?: string[]
253
+ /** 是否来自完整 typed preference 路径(便于 audit/UI 区分) */
254
+ homeCityPreferenceSource: 'typed' | 'none'
255
+ }
256
+
257
+ export function resolveDefaultOrigin(
258
+ currentTripOrigin: string | null | undefined,
259
+ profile: {
260
+ homeCity?: string | null
261
+ evidence?: string[]
262
+ homeCityPreference?: HomeCityPreference
263
+ } | null,
264
+ ): DefaultOriginResolution {
265
+ const trimmed = typeof currentTripOrigin === 'string' ? currentTripOrigin.trim() : ''
266
+ const validTypedPreference = isValidHomeCityPreference(profile)
267
+ const preference = validTypedPreference ? profile?.homeCityPreference as HomeCityPreference : undefined
268
+ const typedValue = preference?.value
269
+ const homeCity = validTypedPreference ? typedValue : undefined
270
+ if (trimmed) {
271
+ return { origin: trimmed, source: 'explicit', homeCity, homeCityPreferenceSource: 'none' }
272
+ }
273
+ if (validTypedPreference && typeof typedValue === 'string') {
274
+ return {
275
+ origin: typedValue.trim(),
276
+ source: 'home_default',
277
+ homeCity,
278
+ homeCityEvidence: [preference!.evidence],
279
+ homeCityPreferenceSource: 'typed',
280
+ }
281
+ }
282
+ return { origin: null, source: 'missing', homeCity, homeCityPreferenceSource: 'none' }
283
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * 时间窗衰减原语(memory-design P3):行为偏好的新鲜度置信度——
3
+ * 30/90/180/365d 分级窗口,确定性纯函数;**只降不删**(历史事件永远在,
4
+ * 衰减只作用于读时置信度);地板 0.1(记忆永不因旧而消失)。
5
+ *
6
+ * 边界(memory-design §4 P3):衰减只作用于**行为事件**(效用 sidecar 等);
7
+ * 动机权重层零衰减——动机跨年稳定是产品设计 §4 的明文(个性化的锚点),
8
+ * 本模块不提供任何作用于 motivation-profile 的 API(构造性保证)。
9
+ *
10
+ * 未来消费方(行为偏好层落地时):同原语直接复用,窗口即配置。
11
+ */
12
+
13
+ export type DecayWindow = '30d' | '90d' | '180d' | '365d' | 'stale'
14
+
15
+ /** 分级窗口:age(天)→ 因子;单调不增,地板 0.1(只降不删) */
16
+ export function windowFactor(ageDays: number): number {
17
+ if (ageDays <= 30) return 1.0
18
+ if (ageDays <= 90) return 0.75
19
+ if (ageDays <= 180) return 0.5
20
+ if (ageDays <= 365) return 0.25
21
+ return 0.1
22
+ }
23
+
24
+ /** 事件种类权重:verified_outcome 最强,recalled 最弱(自称被召回 ≠ 有用) */
25
+ export const KIND_WEIGHT = { recalled: 0.25, applied: 0.5, verified_outcome: 1.0 } as const
26
+
27
+ export interface DecayableEvent {
28
+ ts: string
29
+ kind: keyof typeof KIND_WEIGHT
30
+ }
31
+
32
+ export function ageDaysOf(ts: string, now: Date): number {
33
+ const then = new Date(ts).getTime()
34
+ if (Number.isNaN(then)) return Number.POSITIVE_INFINITY
35
+ return Math.max(0, (now.getTime() - then) / 86_400_000)
36
+ }
37
+
38
+ /** 单事件衰减分:KIND_WEIGHT × windowFactor;未知时间戳按地板(不猜) */
39
+ export function eventDecayScore(ev: DecayableEvent, now: Date): number {
40
+ const w = KIND_WEIGHT[ev.kind] ?? 0
41
+ return round4(w * windowFactor(ageDaysOf(ev.ts, now)))
42
+ }
43
+
44
+ /**
45
+ * 单条记忆的新鲜置信度 ∈ [0,1]:Σ 事件衰减分,上界 1。
46
+ * 同 wish 同窗口内事件越多置信越高(有界);旧事件只做地板贡献。
47
+ */
48
+ export function decayedConfidence(events: DecayableEvent[], now: Date): number {
49
+ const sum = events.reduce((acc, ev) => acc + eventDecayScore(ev, now), 0)
50
+ return round4(Math.min(1, sum))
51
+ }
52
+
53
+ function round4(x: number): number {
54
+ return Math.round(x * 10_000) / 10_000
55
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 记忆效用 sidecar(RFC S2,post-outcome-memory-utility-attribution 映射):
3
+ * 「被召回 ≠ 有用,自称用了 ≠ 让结果变好」——召回/使用/结果/归因四件语义独立记录,
4
+ * 确定性 reducer 只做投影;verified_outcome 只能来自 owner 确认(owner_correction tier),
5
+ * 模型永远不能自称「有用」。默认 fail-open:sidecar 缺失/损坏不阻塞主路径。
6
+ *
7
+ * 多用户 AaaS 前向兼容(RFC §6.5):append-only 事件流 + 稳定 wish_id,
8
+ * 未来账本化(CAS/receipt)是存储面替换,语义层零改造。
9
+ */
10
+
11
+ export type UtilityEventKind = 'recalled' | 'applied' | 'verified_outcome'
12
+ /** 归因只能来自 verified_outcome 事件,且必须 owner 确认(用户原话/显式选择) */
13
+ export type UtilityAttribution = 'helpful' | 'harmful' | 'neutral'
14
+
15
+ export interface MemoryUtilityEvent {
16
+ schema: 'memory_utility_observation.v0'
17
+ /** 幂等键:wish_id|kind|ctx|attribution 派生(语义派生,不用墙钟) */
18
+ event_id: string
19
+ wish_id: string
20
+ kind: UtilityEventKind
21
+ ts: string
22
+ /** 召回/确认发生的表面(turn id / 工具调用标识) */
23
+ ctx?: string
24
+ detail?: string
25
+ /** 仅 verified_outcome 携带;owner_correction tier */
26
+ attribution?: UtilityAttribution
27
+ }
28
+
29
+ export type UtilityStatus = 'unknown' | UtilityAttribution
30
+
31
+ export interface WishUtility {
32
+ wish_id: string
33
+ status: UtilityStatus
34
+ recalled: number
35
+ applied: number
36
+ verified: number
37
+ last_event_ts?: string
38
+ }
39
+
40
+ /** 事件幂等键:同 wish 同类同 ctx 同归因 = 同一事件(重放 no-op) */
41
+ export function makeEventId(ev: Omit<MemoryUtilityEvent, 'schema' | 'event_id'>): string {
42
+ return [ev.wish_id, ev.kind, ev.ctx ?? '', ev.attribution ?? ''].join('|')
43
+ }
44
+
45
+ /** 追加不重复(纯函数,不改入参):同 event_id 已存在 → no-op */
46
+ export function appendEvent(events: MemoryUtilityEvent[], ev: Omit<MemoryUtilityEvent, 'schema' | 'event_id'>): { events: MemoryUtilityEvent[]; appended: boolean } {
47
+ const full: MemoryUtilityEvent = { schema: 'memory_utility_observation.v0', event_id: makeEventId(ev), ...ev }
48
+ if (events.some(e => e.event_id === full.event_id)) return { events, appended: false }
49
+ if (ev.kind === 'verified_outcome' && !ev.attribution) {
50
+ // 无归因的结果不算归因证据:降级为 applied(六件语义不越界)
51
+ return appendEvent(events, { ...ev, kind: 'applied' })
52
+ }
53
+ return { events: [...events, full], appended: true }
54
+ }
55
+
56
+ /** 只读投影:status = 最后一次 verified_outcome 的归因;无 verified_outcome 永远 unknown */
57
+ export function projectUtility(events: MemoryUtilityEvent[]): Record<string, WishUtility> {
58
+ const out: Record<string, WishUtility> = {}
59
+ for (const ev of events) {
60
+ const w = out[ev.wish_id] ??= { wish_id: ev.wish_id, status: 'unknown', recalled: 0, applied: 0, verified: 0 }
61
+ if (ev.kind === 'recalled') w.recalled++
62
+ if (ev.kind === 'applied') w.applied++
63
+ if (ev.kind === 'verified_outcome') {
64
+ w.verified++
65
+ if (ev.attribution) w.status = ev.attribution
66
+ }
67
+ w.last_event_ts = ev.ts
68
+ }
69
+ return out
70
+ }
@@ -9,6 +9,7 @@ import type { InterviewQuestion, TravelerProfile, TripState, Turn, CalendarState
9
9
  import type { JourneySpecTS } from './unified.ts'
10
10
  import type { TravelSlotExtraction } from './travel-slots.ts'
11
11
  import { parseFlightPackToSpec } from './unified.ts'
12
+ import { mergeProfileWorkWindow } from './flight-pack-adapter.ts'
12
13
 
13
14
  interface ScriptStep {
14
15
  /** 命中条件:用户消息包含此关键词(第一条匹配生效) */
@@ -79,14 +80,9 @@ export function createMockLlm(flightPackPath?: string, slotScript: SlotScriptSte
79
80
  const { readFile } = await import('node:fs/promises')
80
81
  const pack = JSON.parse(await readFile(flightPackPath, 'utf-8'))
81
82
  const spec = parseFlightPackToSpec(pack)
82
- spec.workWindow = {
83
- homeTzOffsetMin: state.profile.workWindow.homeTzOffsetMin,
84
- startMin: state.profile.workWindow.startMin,
85
- endMin: state.profile.workWindow.endMin,
86
- workdays: state.profile.workWindow.workdays,
87
- }
88
- spec.budgetCny = 9000
89
- return spec
83
+ const merged = mergeProfileWorkWindow(spec, state.profile.workWindow)
84
+ merged.budgetCny = 9000
85
+ return merged
90
86
  },
91
87
  async extractSlots(history: Turn[]): Promise<TravelSlotExtraction | null> {
92
88
  const last = history[history.length - 1]?.text ?? ''
package/ts/src/model.ts CHANGED
@@ -19,6 +19,16 @@ export interface TransferMode {
19
19
  mode: string
20
20
  minutes: number
21
21
  priceCny: number
22
+ /**
23
+ * Per-direction overrides supplied by the ground-transfer capability when a
24
+ * driving route fact successfully resolves for outbound (A→B) and/or return
25
+ * (B→A). `minutesOut` feeds the outbound arrival stay minute;
26
+ * `minutesRet` feeds the return departure-from-stay minute. When absent
27
+ * the solver falls back to the symmetric `minutes` field, preserving
28
+ * byte-for-byte behavior for static and symmetric inputs.
29
+ */
30
+ minutesOut?: number
31
+ minutesRet?: number
22
32
  }
23
33
 
24
34
  export interface Service {
@@ -26,6 +36,14 @@ export interface Service {
26
36
  depMin: number
27
37
  arrMin: number
28
38
  priceCny: number
39
+ /**
40
+ * Issue #343 v2 路径填充(可选):service 出发/到达对应的真实 UTC instant(epoch ms)。
41
+ * 同一 service 在 arrMin - depMin ≤ 真实飞行时长 跨日 / DST 切换场景下需要这两个字段
42
+ * 才能算出正确的 (arrUtcMs - depUtcMs) / 60000。v1 数值路径不填,由 evaluateChoice/Segment 走
43
+ * 旧 `(arrMin - depMin) - tzOffsetMin` 公式,字节不变。
44
+ */
45
+ depUtcMs?: number
46
+ arrUtcMs?: number
29
47
  }
30
48
 
31
49
  export interface HubAccess {
@@ -46,6 +64,8 @@ export interface MotivationProfile {
46
64
  export interface Candidate {
47
65
  id: string
48
66
  name: string
67
+ /** 可选的明确出行日;未提供时保持旧版无日期可行性计算语义 */
68
+ date?: string
49
69
  hub: string
50
70
  bufferOutMin: number
51
71
  bufferRetMin: number
@@ -147,6 +167,7 @@ export function parseCandidate(d: Record<string, unknown>): Candidate {
147
167
  return {
148
168
  id: String(d['id']),
149
169
  name: String(d['name']),
170
+ date: typeof d['date'] === 'string' ? d['date'] : undefined,
150
171
  hub: String(d['hub']),
151
172
  bufferOutMin: Number(d['buffer_out_min'] ?? 60),
152
173
  bufferRetMin: Number(d['buffer_ret_min'] ?? 60),
@@ -161,11 +182,76 @@ export function parseCandidate(d: Record<string, unknown>): Candidate {
161
182
  }
162
183
  }
163
184
 
185
+ /** Issue #343 d2d canonical arithmetic。 */
186
+ export class NonpositiveDurationError extends Error {
187
+ /** 'arr_not_after_dep' | 'legacy_negative_offset' */
188
+ readonly kind: 'arr_not_after_dep' | 'legacy_negative_offset'
189
+ readonly code = 'nonpositive_duration'
190
+ constructor(kind: 'arr_not_after_dep' | 'legacy_negative_offset', message: string) {
191
+ super(message)
192
+ this.kind = kind
193
+ this.name = 'NonpositiveDurationError'
194
+ }
195
+ }
196
+
197
+ /**
198
+ * doorToDoorFromMove 所需的最小 move 字段。本模型层不耦合 unified.ts 的 MoveSpecTS(避免循环)。
199
+ * unified.ts evaluateOptionMove 必须用这个 helper,而不是再写一份 (arrUtcMs - depUtcMs)/60000。
200
+ */
201
+ export interface D2DMoveView {
202
+ bufferMin: number
203
+ originTransferMin: number
204
+ destTransferMin: number
205
+ /** 仅 v1 路径用;v2 路径无需。 */
206
+ tzOffsetMin?: number
207
+ }
208
+
209
+ export interface DoorToDoorParts {
210
+ /** 真实飞行时长(分);v2 = (arrUtcMs - depUtcMs)/60000,v1 = (arrMin - depMin) - tzOffsetMin */
211
+ trueFlightMin: number
212
+ /** 门到门(分) = buffer + originTransfer + trueFlight + destTransfer */
213
+ doorToDoorMin: number
214
+ }
215
+
216
+ /**
217
+ * 单段门到门时长(分)。v2 路径用真实 UTC instant(跨日 / 反向日界线 / DST 切换正确),
218
+ * 且强制 `arrUtcMs > depUtcMs` — parse 边界也会再做一次防御性 throw。
219
+ * v1 路径用墙上分钟差 - tzOffsetMin(原数值口径,字节不变,即使结果为负也照旧返回)。
220
+ *
221
+ * v2:arrUtcMs 必须严格大于 depUtcMs;否则抛 NonpositiveDurationError(arr_not_after_dep)。
222
+ * v1:沿用 legacy 公式,字节不变;不抛错。
223
+ *
224
+ * 调用方(unified.ts evaluateOptionMove)只负责拿到 doorToDoorMin 并包装 LegReport。
225
+ */
226
+ export function doorToDoorFromMove(svc: Service, mv: D2DMoveView): DoorToDoorParts {
227
+ if (typeof svc.depUtcMs === 'number' && typeof svc.arrUtcMs === 'number') {
228
+ if (svc.arrUtcMs <= svc.depUtcMs) {
229
+ throw new NonpositiveDurationError(
230
+ 'arr_not_after_dep',
231
+ `service ${svc.id}: arrUtcMs (${svc.arrUtcMs}) must be strictly greater than depUtcMs (${svc.depUtcMs})`,
232
+ )
233
+ }
234
+ const trueFlightMin = (svc.arrUtcMs - svc.depUtcMs) / 60_000
235
+ const doorToDoorMin = mv.bufferMin + mv.originTransferMin + trueFlightMin + mv.destTransferMin
236
+ return { trueFlightMin, doorToDoorMin }
237
+ }
238
+ // v1 legacy 字节不变:不校验正负,沿用原公式。
239
+ const trueFlightMin = (svc.arrMin - svc.depMin) - (mv.tzOffsetMin ?? 0)
240
+ const doorToDoorMin = mv.bufferMin + mv.originTransferMin + trueFlightMin + mv.destTransferMin
241
+ return { trueFlightMin, doorToDoorMin }
242
+ }
243
+
164
244
  export function evaluateChoice(cand: Candidate, req: TravelRequest, ch: Choice): TrueCost {
165
245
  /** 门到门全成本核算(与 Python evaluate_choice 完全一致)。 */
166
246
  const access = req.homeHubAccess[cand.hub]
247
+ const outTransferMin = typeof ch.outTransfer.minutesOut === 'number'
248
+ ? ch.outTransfer.minutesOut
249
+ : ch.outTransfer.minutes
250
+ const retTransferMin = typeof ch.retTransfer.minutesRet === 'number'
251
+ ? ch.retTransfer.minutesRet
252
+ : ch.retTransfer.minutes
167
253
  const wake = ch.outService.depMin - cand.bufferOutMin - access.toHubMin
168
- const arriveStay = ch.outService.arrMin + ch.outTransfer.minutes
254
+ const arriveStay = ch.outService.arrMin + outTransferMin
169
255
  const d2dOut = arriveStay - wake
170
256
 
171
257
  let energy = 100
@@ -179,7 +265,7 @@ export function evaluateChoice(cand: Candidate, req: TravelRequest, ch: Choice):
179
265
 
180
266
  const day1Raw = Math.max(0, DAY_END_MIN - arriveStay) / 60
181
267
  const day1 = day1Raw * (0.5 + energy / 200)
182
- const leaveStayRet = ch.retService.depMin - cand.bufferRetMin - ch.retTransfer.minutes
268
+ const leaveStayRet = ch.retService.depMin - cand.bufferRetMin - retTransferMin
183
269
  const day2 = Math.max(0, leaveStayRet - DAY2_START_MIN) / 60 * DAY2_QUALITY
184
270
  const midDays = Math.max(0, ch.days - 2)
185
271
  const usable = day1 + day2 + midDays * 8.0 * DAY2_QUALITY
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Why-now card builder(issue #577,Phase D;研究决策 §3 Phase D;
3
+ * 自检 review 修复见 PR #578——边界校验让「无信源的卡构造上不可能」成为字面真)。
4
+ *
5
+ * 纯函数:RecallTrigger → WhyNowCard(中文文案:标题 + 触发原因 + 当前值/阈值 +
6
+ * 行动建议 + source tag)。
7
+ *
8
+ * 纪律:
9
+ * - **边界强制 provenance**:buildWhyNowCard 校验 signal.source 非空 + reason 在
10
+ * 闭集,不合法**直接抛错**——「source_tag 必现」不再是注释承诺而是构造保证;
11
+ * - 文案模板是封闭词汇(标题/建议的措辞改 = 契约变更,先 review);
12
+ * - 卡片是数据(不是渲染 HTML);渲染面(deck / 对话 / SMS)各自消费;
13
+ * - renderWhyNowCardLine 必须带证据边界标记(渲染契约:每个渲染面都要展示)。
14
+ */
15
+
16
+ import { RECALL_REASON_LABEL, RECALL_REASONS, type RecallReason, type RecallTrigger } from './evaluator.ts'
17
+
18
+ export interface WhyNowCard {
19
+ /** 卡片标题(如「现在可以去了:大理 · 洱海恢复之旅」;半角冒号——代码字面量即契约) */
20
+ title: string
21
+ /** 卡片所指的 wish(结构化自指;Phase E 起必含——plan-it 深链的行动目标,
22
+ * 从 title 字符串反解不可接受) */
23
+ wish_id: string
24
+ /** 触发原因(枚举) */
25
+ reason: RecallReason
26
+ /** 触发原因的人话标签(如「假期临近」) */
27
+ reason_label: string
28
+ /** 当前值(如「2026-10-01(距今 7 天)」) */
29
+ current_value: string
30
+ /** 阈值(如「距假期 ≤14 天」) */
31
+ threshold: string
32
+ /** 行动建议(如「建议本周内规划出发」) */
33
+ action_hint: string
34
+ /** 源 tag(必现;渲染为 `[source:xxx]`) */
35
+ source_tag: string
36
+ /** 评估时刻(ISO) */
37
+ evaluated_at: string
38
+ /** 证据边界声明(恒 true;渲染面必须展示) */
39
+ evidence_boundary: true
40
+ /** 实际命中的愿望条件(评估器从 scoreWishMatch 带出的逐项 hits,如「days≥5」;
41
+ * **缺省 = 该卡未附条件命中证据**——手工构造的卡不发明证据;渲染面必须把
42
+ * 「命中部分愿望条件」与「全部出行条件已满足」区分开,后者永不主张)。 */
43
+ match_evidence?: string[]
44
+ }
45
+
46
+ const ACTION_HINT: Record<RecallReason, string> = {
47
+ holiday_proximity: '建议在假期前完成规划,锁定当前档期',
48
+ price_drop: '价格已降至阈值,建议尽快确认出发日期',
49
+ weather_window: '天气窗口已打开,适合按原计划出行',
50
+ route_new: '新班次已覆盖目标日期,可以重新评估可行性',
51
+ availability_recovered: '通道已恢复,此前不可行的方案现在可以重新查询',
52
+ }
53
+
54
+ /**
55
+ * 构造 why-now 卡:trigger → 人话卡片。
56
+ * **边界强制 provenance**:signal.source 非空 + reason 在闭集,否则抛错
57
+ * (「无信源的卡构造上不可能存在」的字面兑现——手工构造的 trigger 同样受检)。
58
+ * **证据诚实**:trigger.match_hits 是非空字符串数组时原样带上(all-or-nothing,
59
+ * 不部分裁剪、不发明);缺省/空/畸形 → 卡不带 match_evidence——手工构造的卡
60
+ * 宁可无证据,不虚构命中。
61
+ * title 格式:`现在可以去了:${wish_name}`(半角冒号)——封闭词汇,改措辞 = 契约变更。
62
+ */
63
+ export function buildWhyNowCard(trigger: RecallTrigger): WhyNowCard {
64
+ const { wish_id, wish_name, signal, evaluated_at } = trigger
65
+ if (signal === null || typeof signal !== 'object') {
66
+ throw new Error(`buildWhyNowCard:signal 必须是对象(实测 ${String(signal)})`)
67
+ }
68
+ if (typeof signal.source !== 'string' || signal.source.length === 0) {
69
+ throw new Error('buildWhyNowCard:signal.source 必须是非空字符串(无信源的卡构造上不可能存在)')
70
+ }
71
+ if (!RECALL_REASONS.includes(signal.reason as RecallReason)) {
72
+ throw new Error(`buildWhyNowCard:signal.reason 不在闭集(实测 ${String(signal.reason)};允许:${RECALL_REASONS.join(' / ')})`)
73
+ }
74
+ // 条件命中证据:all-or-nothing 校验(非空的非空字符串数组才带上;不发明、不裁剪)
75
+ const rawHits: unknown = (trigger as Partial<RecallTrigger>).match_hits
76
+ const hasEvidence = Array.isArray(rawHits) && rawHits.length > 0
77
+ && rawHits.every(h => typeof h === 'string' && h.length > 0)
78
+ return {
79
+ title: `现在可以去了:${wish_name}`,
80
+ wish_id,
81
+ reason: signal.reason,
82
+ reason_label: RECALL_REASON_LABEL[signal.reason],
83
+ current_value: signal.current_value,
84
+ threshold: signal.threshold,
85
+ action_hint: ACTION_HINT[signal.reason],
86
+ source_tag: `[source:${signal.source}]`,
87
+ evaluated_at,
88
+ evidence_boundary: true,
89
+ ...(hasEvidence ? { match_evidence: [...(rawHits as string[])] } : {}),
90
+ }
91
+ }
92
+
93
+ /** 渲染为单行中文摘要(对话/日志面用;卡片数据仍是权威)。
94
+ * 必须含证据边界标记(渲染契约:每个渲染面都要展示 evidence_boundary);
95
+ * 卡带 match_evidence 时逐项陈述命中条件,并显式声明**不主张全部出行条件已满足**
96
+ * (命中部分愿望条件只是召回资格,不是出行可行性证明)。 */
97
+ export function renderWhyNowCardLine(card: WhyNowCard): string {
98
+ const evidence = Array.isArray(card.match_evidence) && card.match_evidence.length > 0
99
+ ? card.match_evidence
100
+ : null
101
+ const conditionClause = evidence !== null ? `;命中条件:${evidence.join('+')}` : ''
102
+ const boundary = evidence !== null
103
+ ? '[证据边界:仅命中上述愿望条件,非全部出行条件已满足]'
104
+ : '[证据边界:仅证据驱动]'
105
+ return `${card.title}——${card.reason_label}(当前:${card.current_value};阈值:${card.threshold}${conditionClause})。${card.action_hint} ${card.source_tag} ${boundary}`
106
+ }