@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,378 @@
1
+ /**
2
+ * 行程 deck 静态导出 bundle(issue #568,Phase B 切片 3a;研究决策见
3
+ * docs/research/karpo-deck-web-research.md 决策 #1 与
4
+ * docs/design/itinerary-deck-renderer.md;QR 真矩阵编码独立为 issue #569 切片 3b)。
5
+ *
6
+ * 与切片 2 单页入口(`gotry_itinerary_render`)/切片 2 deck 入口
7
+ * (`gotry_itinerary_deck_render`)的产品分工:那两个入口把产物写到 dsh 会话
8
+ * 工作目录顶层(用于 chat 内嵌);本入口把 deck 写到**宿主显式给出的
9
+ * target_dir** 下的一个「分享 bundle」——`basename.html` + `manifest.json` +
10
+ * `qr.svg`(qrcode 库真矩阵,#569),ready for 任意静态托管。
11
+ *
12
+ * 三条硬纪律(继承自单页入口 + deck 入口,verbatim):
13
+ * 1. 事实只从 <stateRoot>/gotry-state/bookable-facts.jsonl 注册表取;
14
+ * 2. target_dir 必须是宿主显式给出的**绝对**目录,realpath 化并排除
15
+ * .git/node_modules 后,以 `O_CREAT|O_EXCL` 独占创建 bundle 三件——
16
+ * 既有 bundle 文件任何一件存在都拒绝覆盖;
17
+ * 3. 失败即拒绝、零写入:输入非法/事实未注册/渲染被拒/basename 非法/
18
+ * bundle 任一文件已被占用都返回结构化错误,不开文件、不落字节。
19
+ */
20
+
21
+ import { createHash, randomBytes } from 'node:crypto'
22
+ import { lstat, open, realpath, stat } from 'node:fs/promises'
23
+ import { isAbsolute, join, sep } from 'node:path'
24
+
25
+ import QRCode from 'qrcode'
26
+
27
+ import { loadFactRegistry } from './fact-log.ts'
28
+ import { ITINERARY_DOC_LIMITS } from '../src/itinerary-doc-shared.ts'
29
+ import {
30
+ type BookableFact,
31
+ type Bookability,
32
+ type EvidenceTier,
33
+ } from '../src/bookable-facts.ts'
34
+ import { renderItineraryDeck } from '../src/itinerary-deck.ts'
35
+
36
+ /** 产物 basename 契约:固定前缀 + ASCII 安全 token(后缀固定 .html/.manifest.json/.qr.svg,basename 不含 .) */
37
+ export const ITINERARY_DECK_EXPORT_BASENAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/
38
+ /** bundle 三件文件名 — 三件都带 basename 前缀(同一 target_dir 下多 bundle 不互撞) */
39
+ export const ITINERARY_DECK_EXPORT_HTML_SUFFIX = '.html'
40
+ export const ITINERARY_DECK_EXPORT_MANIFEST_SUFFIX = '.manifest.json'
41
+ export const ITINERARY_DECK_EXPORT_QR_SUFFIX = '.qr.svg'
42
+ /** 保留未带后缀的别名(向后兼容:导出 manifest.json / qr.svg 单件名给老消费者) */
43
+ export const ITINERARY_DECK_EXPORT_MANIFEST_NAME = 'manifest.json'
44
+ export const ITINERARY_DECK_EXPORT_QR_NAME = 'qr.svg'
45
+ /** 随机默认 bundle basename(仅 crypto 十六进制;显式 basename 优先) */
46
+ export const ITINERARY_DECK_EXPORT_RANDOM_BYTES = 8
47
+ /** 无 target_url 时 QR 编码的字面本地占位串(扫码得到说明文本而非死链;#569) */
48
+ export const ITINERARY_DECK_EXPORT_LOCAL_MARKER = '<local bundle; not yet hosted>'
49
+ const RANDOM_ATTEMPTS = 3
50
+ const DIR_DENY = ['.git', 'node_modules']
51
+ const ID_SAMPLE = 3
52
+ const SOURCE_TAG_SAMPLE = 12
53
+ const TARGET_URL_MAX = 2048
54
+
55
+ export interface ItineraryDeckExportDeps {
56
+ /** 事实注册表根(config.stateRoot);工具面固定传当前配置,不接受调用方覆盖 */
57
+ stateRoot: string
58
+ /** bundle 写入目录:必须是宿主显式给出的绝对路径;缺失/空白/相对一律拒绝 */
59
+ targetDir: string
60
+ /** 当前时刻来源(测试可注入;运行时由工具面传入 new Date()) */
61
+ now?: () => Date
62
+ }
63
+
64
+ export type ItineraryDeckExportResult =
65
+ | {
66
+ ok: true
67
+ /** bundle 真实目录绝对路径(realpath 结果) */
68
+ target_dir: string
69
+ basename: string
70
+ files: { html: string; manifest: string; qr: string }
71
+ bytes: number
72
+ fact_ids: string[]
73
+ overall_verified: false
74
+ }
75
+ | { ok: false; error: string; hint?: string; errors?: string[] }
76
+
77
+ function isRecord(value: unknown): value is Record<string, unknown> {
78
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
79
+ }
80
+
81
+ function hasDeniedSegment(p: string): boolean {
82
+ return p.split(sep).some(seg => DIR_DENY.includes(seg))
83
+ }
84
+
85
+ function fsErrorText(err: unknown): string {
86
+ const code = (err as NodeJS.ErrnoException | null)?.code
87
+ switch (code) {
88
+ case 'EACCES':
89
+ case 'EPERM': return '无写入权限'
90
+ case 'ENOSPC': return '磁盘空间不足'
91
+ case 'EROFS': return '只读文件系统'
92
+ case 'ENOTDIR': return '路径中某段不是目录'
93
+ case 'ENOENT': return '目录不存在'
94
+ case 'ENAMETOOLONG': return '文件名过长'
95
+ default: return typeof code === 'string' ? `文件系统错误 ${code}` : '文件系统错误'
96
+ }
97
+ }
98
+
99
+ /** fact_ids 输入收窄:必填数组、逐条非空短字符串、无重复、不超渲染器容量 */
100
+ function takeFactIds(value: unknown): { ids: string[] } | { error: string; hint?: string } {
101
+ if (value === undefined || value === null) {
102
+ return { error: 'fact_ids 必填(字符串数组,可为空数组)', hint: 'id 来自本次会话 exact-date 工具结果记录的注册表;不接受调用方自带事实' }
103
+ }
104
+ if (!Array.isArray(value)) return { error: 'fact_ids 必须是字符串数组' }
105
+ if (value.length > ITINERARY_DOC_LIMITS.facts) {
106
+ return { error: `fact_ids 超出容量上限(${ITINERARY_DOC_LIMITS.facts} 条,实测 ${value.length} 条)` }
107
+ }
108
+ const ids: string[] = []
109
+ const seen = new Set<string>()
110
+ const duplicates: string[] = []
111
+ for (const [i, raw] of value.entries()) {
112
+ if (typeof raw !== 'string' || raw.length === 0) return { error: `fact_ids[${i}] 必须是非空字符串` }
113
+ if (raw.length > ITINERARY_DOC_LIMITS.factIdChars) {
114
+ return { error: `fact_ids[${i}] 超出 id 长度上限(${ITINERARY_DOC_LIMITS.factIdChars} 字符)` }
115
+ }
116
+ if (seen.has(raw)) {
117
+ duplicates.push(raw)
118
+ continue
119
+ }
120
+ seen.add(raw)
121
+ ids.push(raw)
122
+ }
123
+ if (duplicates.length > 0) {
124
+ return { error: `fact_ids 有重复 id:${duplicates.slice(0, ID_SAMPLE).join('、')}${duplicates.length > ID_SAMPLE ? ` 等 ${duplicates.length} 条` : ''}`, hint: '同一条事实只能选一次;重复选择说明调用方对证据面不确定' }
125
+ }
126
+ return { ids }
127
+ }
128
+
129
+ function takeBasename(value: unknown): { basename: string } | { error: string; hint?: string } {
130
+ if (value === undefined || value === null)
131
+ return { basename: `export-${randomBytes(ITINERARY_DECK_EXPORT_RANDOM_BYTES).toString('hex')}` }
132
+ if (typeof value !== 'string' || value.length === 0) return { error: 'basename 必须是字符串' }
133
+ if (value.includes('/') || value.includes('\\') || value.includes('..')) {
134
+ return { error: 'basename 只能是文件 stem,不能含路径分隔符或 ..', hint: '形如 2027-thailand-phuket(用于生成 2027-thailand-phuket.html + manifest.json + qr.svg)' }
135
+ }
136
+ if (!ITINERARY_DECK_EXPORT_BASENAME_RE.test(value)) {
137
+ return { error: 'basename 不符合命名契约', hint: '必须是字母/数字开头,后接字母/数字/._-(1-64 字符)' }
138
+ }
139
+ return { basename: value }
140
+ }
141
+
142
+ function takeTargetUrl(value: unknown): { targetUrl: string | undefined } | { error: string } {
143
+ if (value === undefined || value === null) return { targetUrl: undefined }
144
+ if (typeof value !== 'string' || value.length === 0) return { error: 'target_url 必须是字符串(可省略)' }
145
+ if (value.length > TARGET_URL_MAX) {
146
+ return { error: `target_url 超出长度上限(${TARGET_URL_MAX} 字符)` }
147
+ }
148
+ return { targetUrl: value }
149
+ }
150
+
151
+ /** target_dir:必须显式且为绝对路径(缺失/空白/相对一律拒绝,绝不回落 '.' 或 process.cwd()),
152
+ * 按宿主原值解析(trim 只判全空白、绝不归一化路径),已存在、可 realpath、是目录,且不含 .git/node_modules 段;
153
+ * **拒绝符号链接**(防止「跟随 symlink 写到 host 未授权目录」——realpath 会解析 symlink,所以必须在 realpath 之前用 lstat 显式拒) */
154
+ async function resolveWritableDir(targetDir: unknown): Promise<{ dir: string } | { error: string; hint?: string }> {
155
+ if (typeof targetDir !== 'string' || targetDir.trim() === '') {
156
+ return { error: 'target_dir 缺失:拒绝在未知目录写产物', hint: '本工具必须有宿主显式给出的绝对目录,不做任何猜测性回落' }
157
+ }
158
+ if (!isAbsolute(targetDir)) {
159
+ return { error: `target_dir 必须是绝对路径:${targetDir}`, hint: '相对路径会随进程工作目录漂移,拒绝写入' }
160
+ }
161
+ // lstat 在 symlink 上返回 isSymbolicLink()=true 而非跟随;realpath 之前的这层检查是关键:
162
+ // 允许存在目录,但拒绝把 symlink 静默重定向到别处
163
+ const lst = await lstat(targetDir).catch(() => null)
164
+ if (!lst) return { error: `target_dir 不存在或无法访问:${targetDir}`, hint: '本工具只在真实存在的目录里写 bundle' }
165
+ if (lst.isSymbolicLink()) {
166
+ return { error: `target_dir 是符号链接,拒绝跟随写入:${targetDir}`, hint: '宿主给出真实目录路径,不要用 symlink 指向其他目录(本工具拒绝跟随)' }
167
+ }
168
+ const canonical = await realpath(targetDir).catch(() => null)
169
+ if (!canonical) return { error: `target_dir 无法解析:${targetDir}` }
170
+ if (hasDeniedSegment(canonical)) {
171
+ return { error: 'target_dir 位于受限段(.git/node_modules)内,拒绝写入', hint: `解析后:${canonical}` }
172
+ }
173
+ const st = await stat(canonical).catch(() => null)
174
+ if (!st?.isDirectory()) return { error: `target_dir 不是目录:${canonical}` }
175
+ return { dir: canonical }
176
+ }
177
+
178
+ /** bundle 命名规则:basename → 三件文件名(<basename>.html / <basename>.manifest.json / <basename>.qr.svg) */
179
+ function bundleFileNames(basename: string): { html: string; manifest: string; qr: string } {
180
+ return {
181
+ html: `${basename}${ITINERARY_DECK_EXPORT_HTML_SUFFIX}`,
182
+ manifest: `${basename}${ITINERARY_DECK_EXPORT_MANIFEST_SUFFIX}`,
183
+ qr: `${basename}${ITINERARY_DECK_EXPORT_QR_SUFFIX}`,
184
+ }
185
+ }
186
+
187
+ /** 预检:bundle 三件文件必须全部尚未存在 —— 任一存在则整体拒绝(零字节) */
188
+ async function assertBundleSlot(dir: string, names: { html: string; manifest: string; qr: string }): Promise<{ ok: true } | { error: string; hint?: string }> {
189
+ for (const n of [names.html, names.manifest, names.qr]) {
190
+ try {
191
+ const st = await stat(join(dir, n))
192
+ if (st.isFile()) return { error: `bundle 文件已存在,拒绝覆盖:${n}`, hint: '换一个 basename 或清空目标目录;本工具永不覆盖既有 bundle' }
193
+ } catch (err) {
194
+ if ((err as NodeJS.ErrnoException | null)?.code !== 'ENOENT') {
195
+ return { error: `无法访问 bundle 文件:${fsErrorText(err)}`, hint: `目标目录:${dir}` }
196
+ }
197
+ // ENOENT 是预期的(slot 干净)
198
+ }
199
+ }
200
+ return { ok: true }
201
+ }
202
+
203
+ /** 独占创建:wx(O_CREAT|O_EXCL)既拒绝覆盖既有文件,也拒绝落在指向别处的符号链接上 */
204
+ async function writeExclusive(path: string, body: string): Promise<{ bytes: number } | { error: string; hint?: string }> {
205
+ const bytes = Buffer.byteLength(body, 'utf8')
206
+ let handle
207
+ try {
208
+ handle = await open(path, 'wx', 0o644)
209
+ } catch (err) {
210
+ if ((err as NodeJS.ErrnoException | null)?.code === 'EEXIST') {
211
+ return { error: `bundle 文件已存在,拒绝覆盖:${path}` }
212
+ }
213
+ return { error: `无法在 target_dir 新建 bundle 文件:${fsErrorText(err)}`, hint: `目标路径:${path}` }
214
+ }
215
+ try {
216
+ await handle.writeFile(body, 'utf-8')
217
+ } catch (err) {
218
+ await handle.close().catch(() => { /* 关闭失败不再覆盖原始写错误 */ })
219
+ return { error: `写入 bundle 文件失败:${fsErrorText(err)}`, hint: `目标文件:${path}` }
220
+ }
221
+ await handle.close()
222
+ return { bytes }
223
+ }
224
+
225
+ /** 构造 manifest:v1 schema,记录 deck 字节/sha256、事实计数 + 来源标签、证据链分布、target_url、local_only、share_intent。
226
+ * local_only 是 #569 的显式指示字段(v1 兼容附加字段,不改 schema 名):无 target_url 时为 true——
227
+ * 消费方据此读出「本地 bundle,无线上目的地」,绝不靠 target_url 字段缺失来暗示。 */
228
+ function buildManifest(
229
+ basename: string,
230
+ facts: BookableFact[],
231
+ exportedAt: Date,
232
+ targetUrl: string | undefined,
233
+ ): Record<string, unknown> {
234
+ const byKind: Record<string, number> = { flight: 0, train: 0, hotel: 0, policy: 0 }
235
+ const sourceTags: string[] = []
236
+ const byTier: Partial<Record<EvidenceTier, number>> = {}
237
+ const byBookability: Partial<Record<Bookability, number>> = {}
238
+ let unverified = 0
239
+ const seenTags = new Set<string>()
240
+ for (const f of facts) {
241
+ byKind[f.kind] = (byKind[f.kind] ?? 0) + 1
242
+ const tag = `[${f.source}]`
243
+ if (!seenTags.has(tag)) {
244
+ seenTags.add(tag)
245
+ sourceTags.push(tag)
246
+ }
247
+ // policy 事实无 tier/bookability(tier/bookability 只描述「可下单」语义);按 kind 区分聚合
248
+ if (f.kind !== 'policy') {
249
+ byTier[f.tier] = (byTier[f.tier] ?? 0) + 1
250
+ byBookability[f.bookability] = (byBookability[f.bookability] ?? 0) + 1
251
+ if (f.bookability === 'unverified') unverified += 1
252
+ }
253
+ }
254
+ return {
255
+ schema: 'gotry_deck_manifest.v1',
256
+ exported_at: exportedAt.toISOString(),
257
+ deck: { basename: `${basename}${ITINERARY_DECK_EXPORT_HTML_SUFFIX}`, sha256: '', bytes: 0 },
258
+ facts: { total: facts.length, by_kind: byKind, source_tags: sourceTags.slice(0, SOURCE_TAG_SAMPLE) },
259
+ evidence_chain: { unverified, by_tier: byTier, by_bookability: byBookability },
260
+ target_url: targetUrl,
261
+ local_only: targetUrl === undefined,
262
+ share_intent: { qr: 'generated', qr_path: `${basename}${ITINERARY_DECK_EXPORT_QR_SUFFIX}` },
263
+ }
264
+ }
265
+
266
+ /** sha256 + bytes 在 manifest 写盘前用真实 html 填回,避免 hash of empty buffer 的占位 */
267
+ function stampManifest(manifest: Record<string, unknown>, html: string): Record<string, unknown> {
268
+ const sha256 = createHash('sha256').update(Buffer.from(html, 'utf8')).digest('hex')
269
+ const bytes = Buffer.byteLength(html, 'utf8')
270
+ const deck = manifest.deck as Record<string, unknown>
271
+ return { ...manifest, deck: { ...deck, sha256, bytes } }
272
+ }
273
+
274
+ /** qr.svg 真矩阵编码(issue #569,Phase B 切片 3b):用 npm `qrcode` 库直接渲染 SVG,
275
+ * 编码 target_url;无 target_url 时编码字面标记 ITINERARY_DECK_EXPORT_LOCAL_MARKER
276
+ * (扫码得到一段说明文本而非死链;manifest.target_url 同时缺省、manifest.local_only=true,
277
+ * 消费方据 manifest 字段判断是否 hosted,不靠扫码内容)。
278
+ * error correction level 默认 M(15% 冗余,适合带 logo / 抗打印 / 二维码扫描容错);
279
+ * margin=2(ISO 标准 4 模块静默区中的 2——可扫描 + 视觉紧凑;#569 T2 验收:payload ×
280
+ * 128/240/480px 全矩阵经独立解码链还原精确 payload,无证据要求调整)。
281
+ * SVG 是自包含 XML,可在任意现代浏览器/扫码 app 直接渲染。 */
282
+ async function qrSvgFor(payload: string): Promise<string> {
283
+ return QRCode.toString(payload, {
284
+ type: 'svg',
285
+ errorCorrectionLevel: 'M',
286
+ margin: 2,
287
+ })
288
+ }
289
+
290
+ /**
291
+ * 生成一份 deck 静态导出 bundle。写入唯一目标 = target_dir 顶层三个文件;
292
+ * 事实唯一来源 = deps.stateRoot 的事实注册表;basename 走 ASCII 契约。
293
+ */
294
+ export async function generateItineraryDeckExport(input: unknown, deps: ItineraryDeckExportDeps): Promise<ItineraryDeckExportResult> {
295
+ if (!isRecord(input)) return { ok: false, error: 'input 必须是对象 {title, itinerary, fact_ids, target_dir?, target_url?, basename?}' }
296
+
297
+ const ids = takeFactIds(input.fact_ids)
298
+ if ('error' in ids) return { ok: false, error: ids.error, hint: ids.hint }
299
+ const named = takeBasename(input.basename)
300
+ if ('error' in named) return { ok: false, error: named.error, hint: named.hint }
301
+ const url = takeTargetUrl(input.target_url)
302
+ if ('error' in url) return { ok: false, error: url.error }
303
+
304
+ const registry = await loadFactRegistry(deps.stateRoot)
305
+ const byId = new Map(registry.map(f => [f.fact_id, f]))
306
+ const missing = ids.ids.filter(id => !byId.has(id))
307
+ if (missing.length > 0) {
308
+ return {
309
+ ok: false,
310
+ error: `事实 id 未在当前注册表:${missing.slice(0, ID_SAMPLE).join('、')}${missing.length > ID_SAMPLE ? ` 等 ${missing.length} 条` : ''}`,
311
+ hint: 'fact_ids 只能取自本会话 exact-date 工具结果的注册表;不接受调用方自带事实对象,也不从 Markdown 猜事实',
312
+ }
313
+ }
314
+ // 顺序保留调用方选择;登记行畸形不在选择阶段放宽——交给 deck 渲染器运行时校验显式拒绝
315
+ const selected = ids.ids.map(id => byId.get(id)!)
316
+
317
+ const rendered = renderItineraryDeck({ title: input.title, itinerary: input.itinerary, facts: selected })
318
+ if (!rendered.ok) {
319
+ return {
320
+ ok: false,
321
+ error: '渲染被拒:行程或事实不满足渲染契约,未写入任何文件',
322
+ errors: rendered.errors,
323
+ hint: '按 errors 修正显式行程结构或换用合法登记的事实;本工具不会静默截断核心行程',
324
+ }
325
+ }
326
+
327
+ const dir = await resolveWritableDir(deps.targetDir)
328
+ if ('error' in dir) return { ok: false, error: dir.error, hint: dir.hint }
329
+
330
+ const files = bundleFileNames(named.basename)
331
+ const slot = await assertBundleSlot(dir.dir, files)
332
+ if ('error' in slot) return { ok: false, error: slot.error, hint: slot.hint }
333
+
334
+ const exportedAt = (deps.now ?? (() => new Date()))()
335
+ const manifest = stampManifest(
336
+ buildManifest(named.basename, selected, exportedAt, url.targetUrl),
337
+ rendered.html,
338
+ )
339
+
340
+ // QR **先于任何写盘**生成(fail-fast 零写入):qrcode 库对超容量输入会 reject
341
+ // (QR v40-M 字节模式上限 2331 字节;2048 个 CJK 字符约 2409 字节即超),
342
+ // 若先生成 HTML/manifest 再遇 QR reject 会留下半成品 bundle 且重试撞 EEXIST。
343
+ const qrTarget = url.targetUrl && url.targetUrl.length > 0
344
+ ? url.targetUrl
345
+ : ITINERARY_DECK_EXPORT_LOCAL_MARKER
346
+ let qrSvg: string
347
+ try {
348
+ qrSvg = await qrSvgFor(qrTarget)
349
+ } catch (err) {
350
+ return {
351
+ ok: false,
352
+ error: `QR 编码失败(未写入任何文件):${(err as Error)?.message ?? String(err)}`,
353
+ hint: `target_url 过长(QR v40-M 字节模式上限约 2331 字节;实测 UTF-8 ${Buffer.byteLength(qrTarget, 'utf8')} 字节)——缩短 target_url 后重试`,
354
+ }
355
+ }
356
+
357
+ // 写盘顺序:HTML → manifest(含 sha256 已填)→ qr.svg(已在上方预生成)
358
+ const htmlPath = join(dir.dir, files.html)
359
+ const manifestPath = join(dir.dir, files.manifest)
360
+ const qrPath = join(dir.dir, files.qr)
361
+ const writtenHtml = await writeExclusive(htmlPath, rendered.html)
362
+ if ('error' in writtenHtml) return { ok: false, error: writtenHtml.error, hint: writtenHtml.hint }
363
+ const writtenManifest = await writeExclusive(manifestPath, JSON.stringify(manifest, null, 2))
364
+ if ('error' in writtenManifest) return { ok: false, error: writtenManifest.error, hint: writtenManifest.hint }
365
+ const writtenQr = await writeExclusive(qrPath, qrSvg)
366
+ if ('error' in writtenQr) return { ok: false, error: writtenQr.error, hint: writtenQr.hint }
367
+
368
+ const totalBytes = writtenHtml.bytes + writtenManifest.bytes + writtenQr.bytes
369
+ return {
370
+ ok: true,
371
+ target_dir: dir.dir,
372
+ basename: named.basename,
373
+ files: { html: htmlPath, manifest: manifestPath, qr: qrPath },
374
+ bytes: totalBytes,
375
+ fact_ids: ids.ids,
376
+ overall_verified: false,
377
+ }
378
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * OpenSky 实时观测桥(免费匿名,~400 credits/天,~4 req/s burst):
3
+ * 查某 callsign 的飞机是否在 ADS-B 网络的当前观测里——航线层的实时印证。
4
+ *
5
+ * 契约(与 weather.ts / hbcli.ts 同构):
6
+ * - 永不抛错:网络失败/超时/解析失败一律降级返回;
7
+ * - 三值语义:命中=强肯定;查空≠证伪(ADS-B 覆盖有限,降权不排除);服务不可用=无结论;
8
+ * - 证据链:成功 [实时API:opensky@ts];失败 [实时API:opensky@error@ts]。
9
+ *
10
+ * 接口面(2026-08 实际可达,匿名路径):
11
+ * GET /api/states/all — 当前全球 ADS-B 状态,~10000 架
12
+ * GET /api/states/all?icao24=<hex> — 单飞机
13
+ * /api/flights/airport 历史查 = 需鉴权(匿名 403)
14
+ *
15
+ * 数据意义:与 OpenFlights 骨架(通航性)+ 静态班次包(班期时刻) 三值互补——
16
+ * OpenFlights = 历史上是否通航过(offline)
17
+ * flights_2026.json = 调研估算的班次时刻
18
+ * OpenSky = 该 callsign 的飞机「现在」是否在观测网络里(实时刻正飞)
19
+ *
20
+ * v0.0.1-rc.3 +: 之前是 scripts/opensky-check.ts 一次性 CLI(M2 时设计为 7 天窗
21
+ * 历史校验,但匿名路径实际不可达),现升级为能力层封装 + dsh 插件工具。
22
+ */
23
+
24
+ export interface FlightLiveQuery {
25
+ /** 航班号/呼号(如 EK329 / CA985);大小写都行 */
26
+ callsign: string
27
+ /** 调用方所属机场 ICAO 四字码(用于地理命中),不传只返回 obs 状态 */
28
+ airport?: string
29
+ /** 超时(ms);默认 10_000 */
30
+ timeoutMs?: number
31
+ }
32
+
33
+ export interface FlightHit {
34
+ icao24: string
35
+ callsign: string | null
36
+ /** OpenSky states/all 用 last_contact 列作为时间戳代理 */
37
+ lastSeen: number
38
+ firstSeen: number
39
+ /** 调用方传入的机场(OpenSky states/all 不返回) */
40
+ estArrivalAirport: string | null
41
+ }
42
+
43
+ export interface FlightVerifyResult {
44
+ verdict: 'observed' | 'not_observed' | 'unavailable'
45
+ callsign: string
46
+ airport: string | null
47
+ sampleSize: number
48
+ hits?: FlightHit[]
49
+ evidence: string
50
+ via: 'opensky' | 'opensky-error'
51
+ latencyMs: number
52
+ error?: string
53
+ }
54
+
55
+ const BASE = 'https://opensky-network.org/api'
56
+
57
+ async function fetchOpenSky(url: string, timeoutMs: number): Promise<{ ok: boolean; data?: unknown; error?: string }> {
58
+ const ctrl = new AbortController()
59
+ const timer = setTimeout(() => ctrl.abort(), timeoutMs)
60
+ try {
61
+ const res = await fetch(url, {
62
+ signal: ctrl.signal,
63
+ headers: { Accept: 'application/json', 'User-Agent': 'gotry/0.0.1-rc.3 (+https://github.com/Danceiny/gotry)' },
64
+ })
65
+ if (!res.ok) return { ok: false, error: `opensky HTTP ${res.status}${res.status === 429 ? ' (rate limited, 400 credits/day anonymous)' : ''}` }
66
+ return { ok: true, data: await res.json() }
67
+ } catch (e) {
68
+ return { ok: false, error: (e as Error).message.slice(0, 200) }
69
+ } finally {
70
+ clearTimeout(timer)
71
+ }
72
+ }
73
+
74
+ const upper = (s: string): string => s.trim().toUpperCase()
75
+
76
+ export async function verifyFlight(query: FlightLiveQuery): Promise<FlightVerifyResult> {
77
+ const started = Date.now()
78
+ const ts = new Date().toISOString()
79
+ const cs = upper(query.callsign)
80
+ const airport = query.airport ? upper(query.airport) : null
81
+ const r = await fetchOpenSky(`${BASE}/states/all`, query.timeoutMs ?? 10_000)
82
+ const latencyMs = Date.now() - started
83
+
84
+ if (!r.ok) {
85
+ return {
86
+ verdict: 'unavailable',
87
+ callsign: cs, airport,
88
+ sampleSize: 0, hits: [],
89
+ evidence: `[实时API:opensky@error@${ts}] ${r.error}`,
90
+ via: 'opensky-error',
91
+ latencyMs, error: r.error,
92
+ }
93
+ }
94
+
95
+ // /api/states/all → { time, states: Array<17 元素数组> }
96
+ // [0 icao24, 1 callsign, 2 origin_country, 3 time_position, 4 last_contact,
97
+ // 5 lon, 6 lat, 7 baro_altitude, 8 on_ground, ...]
98
+ const rawStates: unknown[][] = ((r.data ?? {}) as { states?: unknown[][] }).states ?? []
99
+ const observed: FlightHit[] = rawStates
100
+ .filter(row => ((row[1] ?? '') as string).trim().toUpperCase().includes(cs))
101
+ .map(row => ({
102
+ icao24: String(row[0] ?? ''),
103
+ callsign: ((row[1] ?? '') as string).trim() || null,
104
+ lastSeen: Number(row[4] ?? 0),
105
+ firstSeen: Number(row[4] ?? 0),
106
+ estArrivalAirport: airport,
107
+ }))
108
+
109
+ const verdict: FlightVerifyResult['verdict'] = observed.length > 0 ? 'observed' : 'not_observed'
110
+ const evidence = observed.length > 0
111
+ ? `[实时API:opensky@${ts}] ✅ 全球 ADS-B 当前观测命中 ${cs} (${observed.length} 架)`
112
+ : `[实时API:opensky@${ts}] ○ 当前 ADS-B 全球观测列表未见 ${cs}(ADS-B 覆盖有限,不作否定结论)`
113
+
114
+ return {
115
+ verdict, callsign: cs, airport,
116
+ sampleSize: rawStates.length,
117
+ hits: observed, evidence,
118
+ via: 'opensky',
119
+ latencyMs,
120
+ }
121
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * action-cache 本地自愈层(RFC docs/rfc/user-session-data-rfc.md §3.2 / §2.3;Stagehand 云端缓存的本地化):
3
+ *
4
+ * 目的:会话检索的查询模式高度重复(同站点同类型搜索),首跑用 LLM/规则定位,后续走
5
+ * 缓存的确定性载荷零成本重放;站点改版 → DOM 指纹失配 → miss → 重新定位并回写。
6
+ *
7
+ * 设计原则(Stagehand「错误的缓存点击比慢更糟」的被动失效):
8
+ * - key = site + action + 参数模板(%from%/%to%/%date% 变量化,同型查询共享条目);
9
+ * - 每条目绑 domFingerprint(快照/接口形状的 sha256 指纹);lookup 时现场指纹与条目不符 = miss(即使 key 命中);
10
+ * - miss 即回写(记录新定位),过期(默认 48h)与容量(每 site 默认 50 条,LRU)被动淘汰;
11
+ * - 纯函数 + JSON 文件持久化(隔离 stateRoot;损坏文件按空缓存处理,永不抛错)。
12
+ *
13
+ * P2 切片:模块+确定性测试先行;消费方(P2 美团适配器/a11y 兜底)下一切片接线。
14
+ */
15
+
16
+ import { createHash } from 'node:crypto'
17
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync } from 'node:fs'
18
+ import { dirname, join } from 'node:path'
19
+
20
+ export interface ActionCacheEntry {
21
+ /** site:action:参数化模板 key,如 ctrip-flight:search:%from%->%to%:%date% */
22
+ key: string
23
+ /** 建条目时的 DOM/接口形状指纹;lookup 现场指纹必须一致才命中 */
24
+ domFingerprint: string
25
+ /** 确定性重放载荷(消费方自定义:URL 模板/selector 序列/解析器名) */
26
+ locator: Record<string, string>
27
+ createdAt: string
28
+ lastHitAt: string
29
+ hits: number
30
+ }
31
+
32
+ export interface ActionCacheOptions {
33
+ /** 条目 TTL ms,默认 48h(对齐 Stagehand) */
34
+ ttlMs?: number
35
+ /** 每站点条目上限(LRU 淘汰),默认 50 */
36
+ maxEntriesPerSite?: number
37
+ /** 覆盖「现在」(测试注入) */
38
+ now?: () => Date
39
+ }
40
+
41
+ /** 把具体查询参数变量化为模板 key:日期→%date%,from/to 语义序在前,其余参数按字典序尾随 */
42
+ export function templateKey(site: string, action: string, params: Record<string, string>): string {
43
+ const val = (k: string): string => (k === 'date' ? '%date%' : (params[k] ?? '').trim())
44
+ const head = ['from', 'to', 'date'].filter((k) => k in params).map(val)
45
+ const tail = Object.keys(params).filter((k) => !['from', 'to', 'date'].includes(k)).sort().map(val)
46
+ return `${site}:${action}:${[...head, ...tail].join('|')}`
47
+ }
48
+
49
+ /** 指纹:快照/接口形状文本 → sha256 前 16 位(微改版即失配——宁可 miss 不可错点) */
50
+ export function fingerprint(shapeText: string): string {
51
+ return createHash('sha256').update(shapeText).digest('hex').slice(0, 16)
52
+ }
53
+
54
+ export class ActionCache {
55
+ private entries = new Map<string, ActionCacheEntry>()
56
+ private loaded = false
57
+ private readonly ttlMs: number
58
+ private readonly maxPerSite: number
59
+ private readonly now: () => Date
60
+
61
+ constructor(private readonly filePath: string, opts: ActionCacheOptions = {}) {
62
+ this.ttlMs = opts.ttlMs ?? 48 * 3600_000
63
+ this.maxPerSite = opts.maxEntriesPerSite ?? 50
64
+ this.now = opts.now ?? (() => new Date())
65
+ }
66
+
67
+ private load(): void {
68
+ if (this.loaded) return
69
+ this.loaded = true
70
+ try {
71
+ if (existsSync(this.filePath)) {
72
+ const raw = JSON.parse(readFileSync(this.filePath, 'utf8')) as ActionCacheEntry[]
73
+ for (const e of raw) {
74
+ if (e?.key && e.domFingerprint && e.locator) this.entries.set(e.key, e)
75
+ }
76
+ }
77
+ } catch { /* 损坏文件按空缓存处理 */ }
78
+ }
79
+
80
+ private persist(): void {
81
+ try {
82
+ mkdirSync(dirname(this.filePath), { recursive: true })
83
+ const tmp = this.filePath + '.tmp'
84
+ writeFileSync(tmp, JSON.stringify([...this.entries.values()], null, 0), 'utf8')
85
+ writeFileSync(this.filePath, readFileSync(tmp, 'utf8'), 'utf8')
86
+ try { require('node:fs').rmSync(tmp) } catch { /* best effort */ }
87
+ } catch { /* 持久化失败不阻塞内存路径 */ }
88
+ }
89
+
90
+ /** 站点前缀条目数(LRU 淘汰用) */
91
+ private siteEntries(site: string): ActionCacheEntry[] {
92
+ return [...this.entries.values()].filter((e) => e.key.startsWith(site + ':'))
93
+ }
94
+
95
+ /** 命中三条件:key 存在 + 未过期 + 指纹一致;命中计 hits 并惰性落盘 */
96
+ lookup(key: string, domFingerprint: string, site: string): ActionCacheEntry | null {
97
+ this.load()
98
+ const e = this.entries.get(key)
99
+ if (!e) return null
100
+ if (this.now().getTime() - Date.parse(e.lastHitAt || e.createdAt) > this.ttlMs) {
101
+ this.entries.delete(key)
102
+ this.persist()
103
+ return null
104
+ }
105
+ if (e.domFingerprint !== domFingerprint) return null // 改版:被动失效,等 record 回写
106
+ e.hits += 1
107
+ e.lastHitAt = this.now().toISOString()
108
+ this.persist()
109
+ void site
110
+ return e
111
+ }
112
+
113
+ /** miss 后回写(重新定位的产物);同 key 覆盖;超容 LRU 淘汰最旧 */
114
+ record(key: string, domFingerprint: string, locator: Record<string, string>, site: string): ActionCacheEntry {
115
+ this.load()
116
+ const ts = this.now().toISOString()
117
+ const prev = this.entries.get(key)
118
+ this.entries.set(key, {
119
+ key, domFingerprint, locator,
120
+ createdAt: prev?.createdAt ?? ts,
121
+ lastHitAt: ts,
122
+ hits: prev?.hits ?? 0,
123
+ })
124
+ const siteList = this.siteEntries(site)
125
+ if (siteList.length > this.maxPerSite) {
126
+ const victim = siteList.sort((a, b) => Date.parse(a.lastHitAt) - Date.parse(b.lastHitAt))[0]
127
+ if (victim) this.entries.delete(victim.key)
128
+ }
129
+ this.persist()
130
+ return this.entries.get(key)!
131
+ }
132
+
133
+ invalidate(key: string): void {
134
+ this.load()
135
+ this.entries.delete(key)
136
+ this.persist()
137
+ }
138
+
139
+ size(site?: string): number {
140
+ this.load()
141
+ return site ? this.siteEntries(site).length : this.entries.size
142
+ }
143
+ }
144
+
145
+ /** 便捷入口:隔离 stateRoot 下的缓存文件路径 */
146
+ export function actionCachePath(stateRoot: string): string {
147
+ return join(stateRoot, 'session-action-cache.json')
148
+ }