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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/README.md +172 -217
  2. package/README.zh-CN.md +222 -0
  3. package/bin/gotry-backend.js +53 -0
  4. package/bin/gotry-booking-copilot.js +53 -0
  5. package/bin/gotry-bootstrap.js +1744 -0
  6. package/bin/gotry-inner.js +733 -83
  7. package/bin/gotry-process-liveness.d.ts +27 -0
  8. package/bin/gotry-process-liveness.js +97 -0
  9. package/bin/gotry-runtime-resolution.d.ts +27 -0
  10. package/bin/gotry-runtime-resolution.js +49 -0
  11. package/bin/gotry.js +1 -1
  12. package/client/client.js +203 -0
  13. package/cordis.gotry-patch.yml +80 -49
  14. package/data/airline-airports.json +39 -0
  15. package/data/stations-12306-verify.json +524 -0
  16. package/dist/capabilities/agent-reach-deep.js +45 -122
  17. package/dist/capabilities/agent-reach.js +101 -146
  18. package/dist/capabilities/anything.js +135 -94
  19. package/dist/capabilities/artifacts.js +370 -0
  20. package/dist/capabilities/channel-health.js +131 -0
  21. package/dist/capabilities/channel-registry.js +256 -0
  22. package/dist/capabilities/doctor.js +546 -0
  23. package/dist/capabilities/effect.js +394 -0
  24. package/dist/capabilities/external-event.js +149 -0
  25. package/dist/capabilities/fact-log.js +55 -0
  26. package/dist/capabilities/flyai-config.js +337 -0
  27. package/dist/capabilities/flyai.js +968 -0
  28. package/dist/capabilities/fx-contract.js +293 -0
  29. package/dist/capabilities/geo-atlas.js +246 -0
  30. package/dist/capabilities/ground-transfer.js +509 -0
  31. package/dist/capabilities/hbcli.js +260 -123
  32. package/dist/capabilities/hotelbyte-transaction.js +194 -0
  33. package/dist/capabilities/incident-log.js +131 -61
  34. package/dist/capabilities/itinerary-artifact.js +190 -0
  35. package/dist/capabilities/itinerary-deck-artifact.js +194 -0
  36. package/dist/capabilities/itinerary-deck-export.js +339 -0
  37. package/dist/capabilities/lavish-local.js +924 -0
  38. package/dist/capabilities/model-override.js +38 -0
  39. package/dist/capabilities/opensky.js +54 -43
  40. package/dist/capabilities/resilience.js +191 -0
  41. package/dist/capabilities/session/action-cache.js +126 -0
  42. package/dist/capabilities/session/adapters/ctrip-flight.js +141 -0
  43. package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
  44. package/dist/capabilities/session/adapters/dida-portal.js +274 -0
  45. package/dist/capabilities/session/adapters/meituan-local.js +54 -0
  46. package/dist/capabilities/session/adapters/rail-12306.js +342 -0
  47. package/dist/capabilities/session/benchmark.js +189 -0
  48. package/dist/capabilities/session/bridge-ledger.js +224 -0
  49. package/dist/capabilities/session/extension-bridge.js +636 -0
  50. package/dist/capabilities/session/extension-channel.js +109 -0
  51. package/dist/capabilities/session/extension-distribution.js +230 -0
  52. package/dist/capabilities/session/extract.js +50 -0
  53. package/dist/capabilities/session/golden-score.js +114 -0
  54. package/dist/capabilities/session/health-watch.js +215 -0
  55. package/dist/capabilities/session/read-guard.js +107 -0
  56. package/dist/capabilities/session/static-flight-golden.js +150 -0
  57. package/dist/capabilities/session/transport.js +109 -0
  58. package/dist/capabilities/session/wizard.js +117 -0
  59. package/dist/capabilities/session-consent.js +178 -0
  60. package/dist/capabilities/session-login.js +170 -0
  61. package/dist/capabilities/session-search.js +686 -0
  62. package/dist/capabilities/spawn-bounded.js +203 -0
  63. package/dist/capabilities/sponsor-plugin.js +341 -0
  64. package/dist/capabilities/visa-policy.js +140 -0
  65. package/dist/capabilities/weather.js +243 -138
  66. package/dist/data/session-golden-20.json +25 -0
  67. package/dist/data/sf-golden-manifest.json +103 -0
  68. package/dist/data/sf-static-routes.json +91 -0
  69. package/dist/scripts/action-cache-tests.js +72 -0
  70. package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
  71. package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
  72. package/dist/scripts/agent-reach-deep-tests.js +30 -40
  73. package/dist/scripts/agent-reach-error-tests.js +354 -0
  74. package/dist/scripts/agent-reach-tests.js +26 -18
  75. package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
  76. package/dist/scripts/anything-tests.js +116 -60
  77. package/dist/scripts/artifact-client-contract-tests.js +220 -0
  78. package/dist/scripts/artifacts-capability-tests.js +727 -0
  79. package/dist/scripts/async-collect.js +68 -10
  80. package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
  81. package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
  82. package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
  83. package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
  84. package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
  85. package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
  86. package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
  87. package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
  88. package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
  89. package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
  90. package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
  91. package/dist/scripts/booking-copilot-dsh-warmer-proof.js +123 -0
  92. package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
  93. package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
  94. package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
  95. package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
  96. package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
  97. package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
  98. package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
  99. package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
  100. package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
  101. package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
  102. package/dist/scripts/booking-executor-tests.js +80 -0
  103. package/dist/scripts/booking-recovery-chain-tests.js +182 -0
  104. package/dist/scripts/booking-saga-tests.js +125 -0
  105. package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
  106. package/dist/scripts/bootstrap-tests.js +1479 -0
  107. package/dist/scripts/bridge-events-tests.js +239 -0
  108. package/dist/scripts/bridge-ledger-tests.js +428 -0
  109. package/dist/scripts/build-changelog.js +238 -0
  110. package/dist/scripts/build-metrics-report.js +331 -0
  111. package/dist/scripts/changelog-tests.js +118 -0
  112. package/dist/scripts/channel-probe-tests.js +103 -0
  113. package/dist/scripts/channel-probe.js +169 -0
  114. package/dist/scripts/channel-registry-tests.js +208 -0
  115. package/dist/scripts/companion-tests.js +67 -0
  116. package/dist/scripts/dida-cookie-probe.js +87 -0
  117. package/dist/scripts/dida-runner-stop-tests.js +311 -0
  118. package/dist/scripts/dida-sw-probe.js +114 -0
  119. package/dist/scripts/diff-test.js +17 -12
  120. package/dist/scripts/doctor-tests.js +564 -0
  121. package/dist/scripts/dsh-artifact-e2e.js +279 -0
  122. package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
  123. package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
  124. package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
  125. package/dist/scripts/dsh-runtime-closure.js +162 -0
  126. package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
  127. package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
  128. package/dist/scripts/dsh-target-closure-proof.js +178 -0
  129. package/dist/scripts/effect-tests.js +539 -0
  130. package/dist/scripts/engine-run.js +1 -3
  131. package/dist/scripts/engine-tests.js +16 -10
  132. package/dist/scripts/evaluation-cadence-tests.js +174 -0
  133. package/dist/scripts/evaluation-contract-tests.js +268 -0
  134. package/dist/scripts/extension-distribution-cli.js +39 -0
  135. package/dist/scripts/extension-distribution-tests.js +443 -0
  136. package/dist/scripts/extension-tests.js +988 -0
  137. package/dist/scripts/external-event-tests.js +350 -0
  138. package/dist/scripts/fact-gate-tests.js +869 -0
  139. package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
  140. package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
  141. package/dist/scripts/flight-malformed-tests.js +159 -0
  142. package/dist/scripts/flyai-setup-tests.js +228 -0
  143. package/dist/scripts/flyai-setup-tool-tests.js +54 -0
  144. package/dist/scripts/flyai-tests.js +692 -0
  145. package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
  146. package/dist/scripts/fx-contract-tests.js +201 -0
  147. package/dist/scripts/g5-guard-tests.js +131 -0
  148. package/dist/scripts/g5-guard.js +189 -0
  149. package/dist/scripts/gen-lunar-anchors.js +106 -0
  150. package/dist/scripts/geo-atlas-tests.js +283 -0
  151. package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
  152. package/dist/scripts/gotry-try-demo-tests.js +79 -0
  153. package/dist/scripts/gotry-try-demo.js +49 -0
  154. package/dist/scripts/ground-transfer-tests.js +956 -0
  155. package/dist/scripts/hbcli-e2e-tests.js +159 -0
  156. package/dist/scripts/hbcli-live-optin-tests.js +96 -0
  157. package/dist/scripts/hbcli-release-contract-tests.js +122 -0
  158. package/dist/scripts/hbcli-tests.js +121 -69
  159. package/dist/scripts/health-watch-cli.js +73 -0
  160. package/dist/scripts/hotel-date-gate-tests.js +496 -0
  161. package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
  162. package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
  163. package/dist/scripts/i18n-tests.js +41 -0
  164. package/dist/scripts/incident-tests.js +281 -87
  165. package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
  166. package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
  167. package/dist/scripts/issue-271-liveness-tests.js +569 -0
  168. package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
  169. package/dist/scripts/issue-289-model-retry-tests.js +441 -0
  170. package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
  171. package/dist/scripts/issue-338-home-city-e2e.js +577 -0
  172. package/dist/scripts/issue343-real-entry-e2e.js +160 -0
  173. package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
  174. package/dist/scripts/itinerary-artifact-tests.js +368 -0
  175. package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
  176. package/dist/scripts/itinerary-deck-export-tests.js +552 -0
  177. package/dist/scripts/itinerary-deck-tests.js +435 -0
  178. package/dist/scripts/itinerary-html-tests.js +426 -0
  179. package/dist/scripts/journey-tests.js +22 -27
  180. package/dist/scripts/kernel-manifest-evidence.js +109 -0
  181. package/dist/scripts/kernel-manifest-gate.js +126 -0
  182. package/dist/scripts/kernel-manifest-tests.js +212 -0
  183. package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
  184. package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
  185. package/dist/scripts/kernel-manifest.js +369 -0
  186. package/dist/scripts/lavish-local-tests.js +1226 -0
  187. package/dist/scripts/lavish-product-tools-tests.js +537 -0
  188. package/dist/scripts/ledger-repair-apply-tests.js +316 -0
  189. package/dist/scripts/ledger-repair-plan-tests.js +375 -0
  190. package/dist/scripts/ledger-tests.js +480 -0
  191. package/dist/scripts/ledger-workflow-crash.js +32 -0
  192. package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
  193. package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
  194. package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
  195. package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
  196. package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
  197. package/dist/scripts/memory-capture-tests.js +242 -61
  198. package/dist/scripts/memory-decay-tests.js +49 -0
  199. package/dist/scripts/memory-lifecycle-tests.js +657 -0
  200. package/dist/scripts/memory-lifecycle.js +196 -0
  201. package/dist/scripts/memory-metrics.js +29 -31
  202. package/dist/scripts/memory-value-report.js +631 -0
  203. package/dist/scripts/memory-value-tests.js +328 -0
  204. package/dist/scripts/metrics-report-tests.js +151 -0
  205. package/dist/scripts/model-override-e2e.js +137 -0
  206. package/dist/scripts/nightly-evidence-tests.js +161 -0
  207. package/dist/scripts/nightly-evidence.js +286 -0
  208. package/dist/scripts/nudge-digest.js +43 -42
  209. package/dist/scripts/observability-tests.js +381 -0
  210. package/dist/scripts/onboarding-tests.js +161 -0
  211. package/dist/scripts/opensky-check.js +18 -13
  212. package/dist/scripts/opensky-tests.js +28 -28
  213. package/dist/scripts/persona-surface-guard-tests.js +49 -0
  214. package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
  215. package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
  216. package/dist/scripts/price-drift-tests.js +340 -0
  217. package/dist/scripts/price-drift-watch.js +447 -0
  218. package/dist/scripts/probe-poi-tests.js +53 -45
  219. package/dist/scripts/product-metrics.js +483 -0
  220. package/dist/scripts/publish-preverify.js +110 -0
  221. package/dist/scripts/realtime-pricing-tests.js +76 -0
  222. package/dist/scripts/recall-tests.js +513 -0
  223. package/dist/scripts/replay-async.js +20 -18
  224. package/dist/scripts/replay-real.js +18 -18
  225. package/dist/scripts/replay.js +52 -82
  226. package/dist/scripts/session-attach-diagnose.js +31 -0
  227. package/dist/scripts/session-attach-poc.js +62 -0
  228. package/dist/scripts/session-benchmark.js +180 -0
  229. package/dist/scripts/session-dida-live-e2e.js +199 -0
  230. package/dist/scripts/session-extract-tests.js +60 -0
  231. package/dist/scripts/session-link-tests.js +478 -0
  232. package/dist/scripts/session-login.js +29 -0
  233. package/dist/scripts/session-tests.js +862 -0
  234. package/dist/scripts/session-zones-tests.js +358 -0
  235. package/dist/scripts/sf-live-benchmark.js +367 -0
  236. package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
  237. package/dist/scripts/sf-live-cli-tests.js +92 -0
  238. package/dist/scripts/sf-live-options.js +46 -0
  239. package/dist/scripts/sf-manifest-tests.js +30 -0
  240. package/dist/scripts/sf-manifest.js +33 -0
  241. package/dist/scripts/sf-soft-score-tests.js +67 -0
  242. package/dist/scripts/sf-summary-tests.js +324 -0
  243. package/dist/scripts/sf-summary.js +525 -0
  244. package/dist/scripts/share-tests.js +432 -0
  245. package/dist/scripts/skeleton-check.js +16 -14
  246. package/dist/scripts/skeleton-integration-test.js +28 -11
  247. package/dist/scripts/skills-contract-tests.js +51 -46
  248. package/dist/scripts/smoke-session-gate-tests.js +21 -0
  249. package/dist/scripts/smoke.js +703 -219
  250. package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
  251. package/dist/scripts/sponsor-reuse-tests.js +262 -0
  252. package/dist/scripts/state-cli-tests.js +261 -0
  253. package/dist/scripts/state-cli.js +585 -0
  254. package/dist/scripts/state-repair-cli.js +66 -0
  255. package/dist/scripts/state-repair-tests.js +140 -0
  256. package/dist/scripts/static-golden-tests.js +165 -0
  257. package/dist/scripts/time-eval-tests.js +210 -192
  258. package/dist/scripts/travel-timeline-tests.js +59 -0
  259. package/dist/scripts/turn-handoff-collect-tests.js +169 -0
  260. package/dist/scripts/turn-handoff-collect.js +161 -0
  261. package/dist/scripts/turn-policy-tests.js +55 -0
  262. package/dist/scripts/typed-contract-canary.js +176 -0
  263. package/dist/scripts/tz-resolver-sanity.js +50 -0
  264. package/dist/scripts/tz-resolver-tests.js +584 -0
  265. package/dist/scripts/unified-tests.js +23 -19
  266. package/dist/scripts/visa-policy-tests.js +62 -0
  267. package/dist/scripts/weather-tests.js +276 -41
  268. package/dist/scripts/wish-channel-gate-tests.js +54 -0
  269. package/dist/scripts/write-gate-tests.js +522 -0
  270. package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
  271. package/dist/scripts/z3-lifecycle-tests.js +170 -0
  272. package/dist/scripts/z3-race-repeat-tests.js +39 -0
  273. package/dist/scripts/z3-race-tests.js +41 -0
  274. package/dist/src/artifact-gate.js +829 -0
  275. package/dist/src/backend/kernel.js +91 -0
  276. package/dist/src/backend/modules/booking-copilot.js +92 -0
  277. package/dist/src/backend/modules/booking-executor.js +124 -0
  278. package/dist/src/backend/modules/session-search.js +551 -0
  279. package/dist/src/benchmark-agent-conformance.js +578 -0
  280. package/dist/src/benchmark-environment-bridge.js +515 -0
  281. package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
  282. package/dist/src/benchmark-tool-isolation.js +151 -0
  283. package/dist/src/bookable-facts.js +533 -0
  284. package/dist/src/booking-saga.js +97 -0
  285. package/dist/src/booking-surface/availability-policy.js +697 -0
  286. package/dist/src/booking-surface/booking-intent.js +43 -0
  287. package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
  288. package/dist/src/booking-surface/canonical-schema.js +116 -0
  289. package/dist/src/booking-surface/contracts.js +75 -0
  290. package/dist/src/booking-surface/dsh-planner.js +1854 -0
  291. package/dist/src/booking-surface/dsh-plugin.js +378 -0
  292. package/dist/src/booking-surface/dsh-worker.js +98 -0
  293. package/dist/src/booking-surface/error-codes.js +231 -0
  294. package/dist/src/booking-surface/index.js +9 -0
  295. package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
  296. package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
  297. package/dist/src/booking-surface/profile.js +24 -0
  298. package/dist/src/booking-surface/recovery-chain.js +367 -0
  299. package/dist/src/booking-surface/runtime.js +1968 -0
  300. package/dist/src/booking-surface/server.js +674 -0
  301. package/dist/src/booking-surface/startup.js +168 -0
  302. package/dist/src/booking-surface/validation.js +261 -0
  303. package/dist/src/bridge.js +20 -13
  304. package/dist/src/companions.js +86 -0
  305. package/dist/src/contracts.js +21 -43
  306. package/dist/src/dsh-llm.js +97 -111
  307. package/dist/src/engine.js +276 -193
  308. package/dist/src/evaluation-cadence.js +97 -0
  309. package/dist/src/evaluation-contracts.js +283 -0
  310. package/dist/src/flight-pack-adapter.js +54 -0
  311. package/dist/src/flight-pack-contract.js +2 -0
  312. package/dist/src/flyai-setup-tool.js +70 -0
  313. package/dist/src/gotry-backend.js +125 -0
  314. package/dist/src/hotel-date-gate.js +118 -0
  315. package/dist/src/i18n.js +88 -0
  316. package/dist/src/index.js +2305 -731
  317. package/dist/src/itinerary-deck.js +139 -0
  318. package/dist/src/itinerary-doc-shared.js +661 -0
  319. package/dist/src/itinerary-html.js +131 -0
  320. package/dist/src/journey.js +155 -108
  321. package/dist/src/lavish-tools.js +400 -0
  322. package/dist/src/ledger-repair-apply.js +538 -0
  323. package/dist/src/ledger-repair-plan.js +463 -0
  324. package/dist/src/loop.js +358 -162
  325. package/dist/src/memory-capture.js +180 -32
  326. package/dist/src/memory-decay.js +47 -0
  327. package/dist/src/memory-lifecycle.js +1310 -0
  328. package/dist/src/memory-utility.js +27 -40
  329. package/dist/src/mock-llm.js +36 -61
  330. package/dist/src/model.js +81 -36
  331. package/dist/src/realtime-pricing.js +103 -0
  332. package/dist/src/recall/card.js +74 -0
  333. package/dist/src/recall/evaluator.js +140 -0
  334. package/dist/src/recall/integration.js +17 -0
  335. package/dist/src/recall/tick.js +166 -0
  336. package/dist/src/session-link/plan-it-action.js +48 -0
  337. package/dist/src/session-link/session-link.js +248 -0
  338. package/dist/src/session-zones.js +507 -0
  339. package/dist/src/share/adapters.js +51 -0
  340. package/dist/src/share/share-consent.js +76 -0
  341. package/dist/src/share/share-deck.js +58 -0
  342. package/dist/src/share/share-token.js +146 -0
  343. package/dist/src/slot-spec.js +84 -106
  344. package/dist/src/state-ledger.js +767 -0
  345. package/dist/src/state-repair.js +299 -0
  346. package/dist/src/subagent-job-id-guard.js +58 -0
  347. package/dist/src/time-anchor.js +231 -33
  348. package/dist/src/tool-packet.js +20 -8
  349. package/dist/src/travel-slots.js +58 -35
  350. package/dist/src/travel-timeline.js +59 -0
  351. package/dist/src/turn-deadline.js +287 -0
  352. package/dist/src/turn-policy.js +118 -0
  353. package/dist/src/tz-resolver.js +143 -0
  354. package/dist/src/unified.js +538 -360
  355. package/dist/src/wish-pool.js +42 -12
  356. package/dist/src/write-gate.js +495 -0
  357. package/dist/src/z3-shared.js +284 -0
  358. package/extension/README.md +21 -0
  359. package/extension/README.zh-CN.md +21 -0
  360. package/extension/background.js +463 -0
  361. package/extension/content-bridge.js +270 -0
  362. package/extension/content-main.js +111 -0
  363. package/extension/manifest.json +60 -0
  364. package/package.json +407 -14
  365. package/schemas/booking.intent.schema.json +193 -0
  366. package/schemas/booking.surface.schema.json +2622 -0
  367. package/ts/capabilities/agent-reach-deep.ts +114 -0
  368. package/ts/capabilities/agent-reach.ts +198 -0
  369. package/ts/capabilities/anything.ts +255 -0
  370. package/ts/capabilities/artifacts.ts +428 -0
  371. package/ts/capabilities/fact-log.ts +54 -0
  372. package/ts/capabilities/flyai-config.ts +388 -0
  373. package/ts/capabilities/flyai.ts +1274 -0
  374. package/ts/capabilities/hbcli.ts +253 -71
  375. package/ts/capabilities/incident-log.ts +88 -59
  376. package/ts/capabilities/itinerary-artifact.ts +214 -0
  377. package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
  378. package/ts/capabilities/itinerary-deck-export.ts +378 -0
  379. package/ts/capabilities/opensky.ts +121 -0
  380. package/ts/capabilities/session/action-cache.ts +148 -0
  381. package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
  382. package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
  383. package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
  384. package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
  385. package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
  386. package/ts/capabilities/session/benchmark.ts +276 -0
  387. package/ts/capabilities/session/bridge-ledger.ts +333 -0
  388. package/ts/capabilities/session/extension-bridge.ts +794 -0
  389. package/ts/capabilities/session/extension-channel.ts +144 -0
  390. package/ts/capabilities/session/extension-distribution.ts +266 -0
  391. package/ts/capabilities/session/extract.ts +54 -0
  392. package/ts/capabilities/session/golden-score.ts +139 -0
  393. package/ts/capabilities/session/health-watch.ts +264 -0
  394. package/ts/capabilities/session/read-guard.ts +141 -0
  395. package/ts/capabilities/session/static-flight-golden.ts +209 -0
  396. package/ts/capabilities/session/transport.ts +146 -0
  397. package/ts/capabilities/session/wizard.ts +140 -0
  398. package/ts/capabilities/session-consent.ts +211 -0
  399. package/ts/capabilities/session-login.ts +208 -0
  400. package/ts/capabilities/session-search.ts +893 -0
  401. package/ts/capabilities/weather.ts +330 -0
  402. package/ts/dsh-runtime/vendor/README.md +68 -0
  403. package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
  404. package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
  405. package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
  406. package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
  407. package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
  408. package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
  409. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
  410. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
  411. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
  412. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
  413. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
  414. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
  415. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
  416. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
  417. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
  418. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
  419. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
  420. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
  421. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
  422. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
  423. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
  424. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
  425. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
  426. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
  427. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
  428. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
  429. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
  430. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
  431. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
  432. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
  433. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
  434. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
  435. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
  436. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
  437. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
  438. package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
  439. package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
  440. package/ts/package.json +296 -3
  441. package/ts/scripts/async-collect.ts +91 -0
  442. package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
  443. package/ts/scripts/skeleton-check.ts +1 -1
  444. package/ts/scripts/skeleton-integration-test.ts +23 -1
  445. package/ts/scripts/state-cli.ts +568 -0
  446. package/ts/scripts/turn-handoff-collect.ts +178 -0
  447. package/ts/src/artifact-gate.ts +991 -0
  448. package/ts/src/benchmark-agent-conformance.ts +619 -0
  449. package/ts/src/benchmark-environment-bridge.ts +538 -0
  450. package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
  451. package/ts/src/benchmark-tool-isolation.ts +166 -0
  452. package/ts/src/bookable-facts.ts +837 -0
  453. package/ts/src/booking-saga.ts +133 -0
  454. package/ts/src/booking-surface/availability-policy.ts +522 -0
  455. package/ts/src/booking-surface/booking-intent.ts +86 -0
  456. package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
  457. package/ts/src/booking-surface/canonical-schema.js +116 -0
  458. package/ts/src/booking-surface/contracts.ts +475 -0
  459. package/ts/src/booking-surface/dsh-planner.ts +1949 -0
  460. package/ts/src/booking-surface/dsh-plugin.js +378 -0
  461. package/ts/src/booking-surface/dsh-worker.ts +110 -0
  462. package/ts/src/booking-surface/error-codes.ts +241 -0
  463. package/ts/src/booking-surface/index.ts +9 -0
  464. package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
  465. package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
  466. package/ts/src/booking-surface/profile.ts +42 -0
  467. package/ts/src/booking-surface/recovery-chain.ts +409 -0
  468. package/ts/src/booking-surface/runtime.ts +1793 -0
  469. package/ts/src/booking-surface/server.ts +733 -0
  470. package/ts/src/booking-surface/startup.ts +201 -0
  471. package/ts/src/booking-surface/validation.ts +217 -0
  472. package/ts/src/companions.ts +113 -0
  473. package/ts/src/contracts.ts +13 -3
  474. package/ts/src/dsh-llm.ts +44 -11
  475. package/ts/src/flyai-setup-tool.ts +77 -0
  476. package/ts/src/index.ts +2107 -224
  477. package/ts/src/itinerary-deck.ts +189 -0
  478. package/ts/src/itinerary-doc-shared.ts +741 -0
  479. package/ts/src/itinerary-html.ts +171 -0
  480. package/ts/src/loop.ts +278 -40
  481. package/ts/src/memory-capture.ts +283 -0
  482. package/ts/src/memory-decay.ts +55 -0
  483. package/ts/src/memory-utility.ts +70 -0
  484. package/ts/src/mock-llm.ts +4 -8
  485. package/ts/src/model.ts +88 -2
  486. package/ts/src/recall/card.ts +106 -0
  487. package/ts/src/recall/evaluator.ts +170 -0
  488. package/ts/src/recall/integration.ts +35 -0
  489. package/ts/src/recall/tick.ts +213 -0
  490. package/ts/src/session-link/plan-it-action.ts +78 -0
  491. package/ts/src/session-link/session-link.ts +275 -0
  492. package/ts/src/share/adapters.ts +93 -0
  493. package/ts/src/share/share-consent.ts +105 -0
  494. package/ts/src/share/share-deck.ts +85 -0
  495. package/ts/src/share/share-token.ts +166 -0
  496. package/ts/src/slot-spec.ts +172 -0
  497. package/ts/src/state-ledger.ts +912 -0
  498. package/ts/src/subagent-job-id-guard.ts +72 -0
  499. package/ts/src/time-anchor.ts +338 -0
  500. package/ts/src/tool-packet.ts +32 -0
  501. package/ts/src/travel-slots.ts +219 -0
  502. package/ts/src/travel-timeline.ts +83 -0
  503. package/ts/src/turn-deadline.ts +366 -0
  504. package/ts/src/turn-policy.ts +154 -0
  505. package/ts/src/unified.ts +360 -89
  506. package/ts/src/wish-pool.ts +79 -0
@@ -0,0 +1,214 @@
1
+ /**
2
+ * 行程 HTML 产物生成入口(issue #442,父 #438)。
3
+ *
4
+ * 与纯渲染器(ts/src/itinerary-html.ts)的分工:渲染器只把「显式行程 + 已选事实」
5
+ * 投影成单文件 HTML;本模块是**唯一的产品生成路径**——从当前配置状态根的
6
+ * 事实注册表按 id 选事实、跑渲染器、把产物独占落盘到会话工作目录。
7
+ *
8
+ * 三条硬纪律:
9
+ * 1. 事实只从 <stateRoot>/gotry-state/bookable-facts.jsonl 注册表取:入参只收
10
+ * fact_ids,调用方自带的事实对象一律不接收;未知/重复/超量 id 显式拒绝,
11
+ * 登记行畸形则由渲染器的运行时校验拒绝(不静默丢弃后仍宣称成功);
12
+ * 2. 只新建文件:basename 限定 gotry-itinerary-<ASCII 安全 token>.html,父目录必须是宿主
13
+ * 显式给出的**绝对**会话工作目录(缺失/空白/相对一律拒绝,绝不回落 `.` 或进程 cwd),
14
+ * realpath 化并排除 .git/node_modules 后,再以 O_CREAT|O_EXCL('wx')独占创建,
15
+ * 绝不覆盖既有文件、绝不跟随符号链接改写其目标;
16
+ * 3. 失败即拒绝、零写入:输入非法/事实未注册/渲染被拒/文件名被占用都返回结构化
17
+ * 错误,不落任何字节(校验全部发生在打开文件之前)。
18
+ */
19
+
20
+ import { randomBytes } from 'node:crypto'
21
+ import { open, realpath, stat, unlink } from 'node:fs/promises'
22
+ import { isAbsolute, join, sep } from 'node:path'
23
+
24
+ import { loadFactRegistry } from './fact-log.ts'
25
+ import { ITINERARY_HTML_LIMITS, renderItineraryHtml } from '../src/itinerary-html.ts'
26
+
27
+ /** 产物文件名契约:固定前缀 + ASCII 安全 token + .html(无路径分隔符、无前导点) */
28
+ export const ITINERARY_ARTIFACT_BASENAME_RE = /^gotry-itinerary-[A-Za-z0-9][A-Za-z0-9._-]{0,63}\.html$/
29
+ /** 随机默认名(仅 crypto 十六进制;显式 basename 优先) */
30
+ export const ITINERARY_ARTIFACT_RANDOM_BYTES = 8
31
+ const RANDOM_ATTEMPTS = 3
32
+ const DIR_DENY = ['.git', 'node_modules']
33
+ const ID_SAMPLE = 3
34
+
35
+ export interface ItineraryArtifactDeps {
36
+ /** 事实注册表根(config.stateRoot);工具面固定传当前配置,不接受调用方覆盖 */
37
+ stateRoot: string
38
+ /** 会话工作目录:必须是宿主显式给出的绝对路径;缺失/空白/相对一律拒绝(绝不回落进程 cwd) */
39
+ cwd: string
40
+ }
41
+
42
+ export type ItineraryArtifactResult =
43
+ | {
44
+ ok: true
45
+ /** 落盘后的真实绝对路径(realpath 结果,供展示「最终落点」) */
46
+ path: string
47
+ basename: string
48
+ bytes: number
49
+ fact_ids: string[]
50
+ /** 本产物永不给整体「已验证」结论,只有逐条事实自带的可下单标注 */
51
+ overall_verified: false
52
+ }
53
+ | { ok: false; error: string; hint?: string; errors?: string[] }
54
+
55
+ function isRecord(value: unknown): value is Record<string, unknown> {
56
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
57
+ }
58
+
59
+ function hasDeniedSegment(p: string): boolean {
60
+ return p.split(sep).some(seg => DIR_DENY.includes(seg))
61
+ }
62
+
63
+ function fsErrorText(err: unknown): string {
64
+ const code = (err as NodeJS.ErrnoException | null)?.code
65
+ switch (code) {
66
+ case 'EACCES':
67
+ case 'EPERM': return '无写入权限'
68
+ case 'ENOSPC': return '磁盘空间不足'
69
+ case 'EROFS': return '只读文件系统'
70
+ case 'ENOTDIR': return '路径中某段不是目录'
71
+ case 'ENOENT': return '目录不存在'
72
+ case 'ENAMETOOLONG': return '文件名过长'
73
+ default: return typeof code === 'string' ? `文件系统错误 ${code}` : '文件系统错误'
74
+ }
75
+ }
76
+
77
+ /** fact_ids 输入收窄:必填数组、逐条非空短字符串、无重复、不超渲染器容量 */
78
+ function takeFactIds(value: unknown): { ids: string[] } | { error: string; hint?: string } {
79
+ if (value === undefined || value === null) {
80
+ return { error: 'fact_ids 必填(字符串数组,可为空数组)', hint: 'id 来自本次会话 exact-date 工具结果记录的注册表;不接受调用方自带事实' }
81
+ }
82
+ if (!Array.isArray(value)) return { error: 'fact_ids 必须是字符串数组' }
83
+ if (value.length > ITINERARY_HTML_LIMITS.facts) {
84
+ return { error: `fact_ids 超出容量上限(${ITINERARY_HTML_LIMITS.facts} 条,实测 ${value.length} 条)` }
85
+ }
86
+ const ids: string[] = []
87
+ const seen = new Set<string>()
88
+ const duplicates: string[] = []
89
+ for (const [i, raw] of value.entries()) {
90
+ if (typeof raw !== 'string' || raw.length === 0) return { error: `fact_ids[${i}] 必须是非空字符串` }
91
+ if (raw.length > ITINERARY_HTML_LIMITS.factIdChars) {
92
+ return { error: `fact_ids[${i}] 超出 id 长度上限(${ITINERARY_HTML_LIMITS.factIdChars} 字符)` }
93
+ }
94
+ if (seen.has(raw)) {
95
+ duplicates.push(raw)
96
+ continue
97
+ }
98
+ seen.add(raw)
99
+ ids.push(raw)
100
+ }
101
+ if (duplicates.length > 0) {
102
+ return { error: `fact_ids 有重复 id:${duplicates.slice(0, ID_SAMPLE).join('、')}${duplicates.length > ID_SAMPLE ? ` 等 ${duplicates.length} 条` : ''}`, hint: '同一条事实只能选一次;重复选择说明调用方对证据面不确定' }
103
+ }
104
+ return { ids }
105
+ }
106
+
107
+ function takeBasename(value: unknown): { basename: string } | { error: string; hint?: string } {
108
+ if (value === undefined || value === null)
109
+ return { basename: `gotry-itinerary-${randomBytes(ITINERARY_ARTIFACT_RANDOM_BYTES).toString('hex')}.html` }
110
+ if (typeof value !== 'string' || value.length === 0) return { error: 'basename 必须是字符串' }
111
+ if (value.includes('/') || value.includes('\\') || value.includes('..')) {
112
+ return { error: 'basename 只能是文件名,不能含路径分隔符或 ..', hint: '形如 gotry-itinerary-2027-thailand.html' }
113
+ }
114
+ if (!ITINERARY_ARTIFACT_BASENAME_RE.test(value)) {
115
+ return { error: 'basename 不符合命名契约', hint: '必须是 gotry-itinerary-<ASCII 字母/数字/._->.html(前缀后 1-64 字符)' }
116
+ }
117
+ return { basename: value }
118
+ }
119
+
120
+ /** 会话工作目录:必须显式且为绝对路径(缺失/空白/相对一律拒绝,绝不回落 '.' 或 process.cwd()),
121
+ * 按宿主原值解析(trim 只判全空白、绝不归一化路径),已存在、可 realpath、是目录,且不含 .git/node_modules 段 */
122
+ async function resolveWritableDir(cwd: unknown): Promise<{ dir: string } | { error: string; hint?: string }> {
123
+ if (typeof cwd !== 'string' || cwd.trim() === '') {
124
+ return { error: '会话工作目录缺失:拒绝在未知目录写产物', hint: '写产物必须有宿主显式给出的绝对会话工作目录,不做任何猜测性回落' }
125
+ }
126
+ // trim 只用于「是否全空白」判定:真实路径一律按宿主原值使用,绝不归一化——
127
+ // 尾随空格可能是真实目录名的一部分,归一化会写到同名兄弟目录
128
+ if (!isAbsolute(cwd)) {
129
+ return { error: `会话工作目录必须是绝对路径:${cwd}`, hint: '相对路径会随进程工作目录漂移,拒绝写入' }
130
+ }
131
+ const canonical = await realpath(cwd).catch(() => null)
132
+ if (!canonical) return { error: `会话工作目录不存在或无法解析:${cwd}`, hint: '本工具只在真实存在的会话工作目录顶层新建文件' }
133
+ if (hasDeniedSegment(canonical)) {
134
+ return { error: '会话工作目录位于受限段(.git/node_modules)内,拒绝写入', hint: `解析后:${canonical}` }
135
+ }
136
+ const st = await stat(canonical).catch(() => null)
137
+ if (!st?.isDirectory()) return { error: `会话工作目录不是目录:${canonical}` }
138
+ return { dir: canonical }
139
+ }
140
+
141
+ /** 独占创建:wx(O_CREAT|O_EXCL)既拒绝覆盖既有文件,也拒绝落在指向别处的符号链接上 */
142
+ async function writeExclusive(dir: string, basename: string, html: string): Promise<{ path: string; bytes: number } | { error: string; hint?: string }> {
143
+ const target = join(dir, basename)
144
+ const bytes = Buffer.byteLength(html, 'utf8')
145
+ let handle
146
+ try {
147
+ handle = await open(target, 'wx', 0o644)
148
+ } catch (err) {
149
+ if ((err as NodeJS.ErrnoException | null)?.code === 'EEXIST') {
150
+ return { error: `目标文件已存在,拒绝覆盖:${basename}`, hint: '换一个 basename(不传则自动生成随机名);本工具永不覆盖既有产物' }
151
+ }
152
+ return { error: `无法在会话工作目录新建文件:${fsErrorText(err)}`, hint: `目标目录:${dir}` }
153
+ }
154
+ try {
155
+ await handle.writeFile(html, 'utf-8')
156
+ } catch (err) {
157
+ await handle.close().catch(() => { /* 关闭失败不再覆盖原始写错误 */ })
158
+ await unlink(target).catch(() => { /* 清理半成品失败不改变「本次生成失败」的结论 */ })
159
+ return { error: `写入产物失败:${fsErrorText(err)}`, hint: `目标文件已清理:${target}` }
160
+ }
161
+ await handle.close()
162
+ const finalPath = await realpath(target).catch(() => target)
163
+ return { path: finalPath, bytes }
164
+ }
165
+
166
+ /**
167
+ * 生成一份行程 HTML 产物。写入唯一目标 = 会话工作目录顶层的新文件;
168
+ * 事实唯一来源 = deps.stateRoot 的事实注册表。
169
+ */
170
+ export async function generateItineraryArtifact(input: unknown, deps: ItineraryArtifactDeps): Promise<ItineraryArtifactResult> {
171
+ if (!isRecord(input)) return { ok: false, error: 'input 必须是对象 {title, itinerary, fact_ids, basename?}' }
172
+
173
+ const ids = takeFactIds(input.fact_ids)
174
+ if ('error' in ids) return { ok: false, error: ids.error, hint: ids.hint }
175
+ const named = takeBasename(input.basename)
176
+ if ('error' in named) return { ok: false, error: named.error, hint: named.hint }
177
+
178
+ const registry = await loadFactRegistry(deps.stateRoot)
179
+ const byId = new Map(registry.map(f => [f.fact_id, f]))
180
+ const missing = ids.ids.filter(id => !byId.has(id))
181
+ if (missing.length > 0) {
182
+ return {
183
+ ok: false,
184
+ error: `事实 id 未在当前注册表:${missing.slice(0, ID_SAMPLE).join('、')}${missing.length > ID_SAMPLE ? ` 等 ${missing.length} 条` : ''}`,
185
+ hint: 'fact_ids 只能取自本会话 exact-date 工具结果的注册表;不接受调用方自带事实对象,也不从 Markdown 猜事实',
186
+ }
187
+ }
188
+ // 顺序保留调用方选择;登记行畸形不在选择阶段放宽——交给渲染器运行时校验显式拒绝
189
+ const selected = ids.ids.map(id => byId.get(id)!)
190
+
191
+ const rendered = renderItineraryHtml({ title: input.title, itinerary: input.itinerary, facts: selected })
192
+ if (!rendered.ok) {
193
+ return {
194
+ ok: false,
195
+ error: '渲染被拒:行程或事实不满足渲染契约,未写入任何文件',
196
+ errors: rendered.errors,
197
+ hint: '按 errors 修正显式行程结构或换用合法登记的事实;本工具不会静默截断核心行程',
198
+ }
199
+ }
200
+
201
+ const dir = await resolveWritableDir(deps.cwd)
202
+ if ('error' in dir) return { ok: false, error: dir.error, hint: dir.hint }
203
+
204
+ for (let attempt = 0; attempt < (input.basename === undefined || input.basename === null ? RANDOM_ATTEMPTS : 1); attempt++) {
205
+ const basename = attempt === 0 ? named.basename : `gotry-itinerary-${randomBytes(ITINERARY_ARTIFACT_RANDOM_BYTES).toString('hex')}.html`
206
+ const written = await writeExclusive(dir.dir, basename, rendered.html)
207
+ if ('error' in written) {
208
+ if (attempt + 1 < RANDOM_ATTEMPTS && input.basename === undefined) continue // 随机名撞车:换名重试
209
+ return { ok: false, error: written.error, hint: written.hint }
210
+ }
211
+ return { ok: true, path: written.path, basename, bytes: written.bytes, fact_ids: ids.ids, overall_verified: false }
212
+ }
213
+ return { ok: false, error: '随机文件名多次撞车,未写入文件' }
214
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * 行程 deck 产物生成入口(issue #566,Phase B 切片 2;研究决策见
3
+ * docs/research/karpo-deck-web-research.md 决策 #1;deck 渲染契约见
4
+ * docs/design/itinerary-deck-renderer.md)。
5
+ *
6
+ * 与单页入口 itinerary-artifact.ts 完全对称的产品路径:渲染器只把「显式行程
7
+ * + 已选事实」投影成单文件 deck HTML;本模块是 deck 形态的**唯一产品生成
8
+ * 路径**——从当前配置状态根的事实注册表按 id 选事实、跑 deck 渲染器、把
9
+ * 产物独占落盘到会话工作目录。
10
+ *
11
+ * 三条硬纪律(继承自单页入口,verbatim):
12
+ * 1. 事实只从 <stateRoot>/gotry-state/bookable-facts.jsonl 注册表取:入参只收
13
+ * fact_ids,调用方自带的事实对象一律不接收;未知/重复/超量 id 显式拒绝,
14
+ * 登记行畸形则由 deck 渲染器的运行时校验拒绝(不静默丢弃后仍宣称成功);
15
+ * 2. 只新建文件:basename 限定 gotry-deck-<ASCII 安全 token>.html,父目录必须是宿主
16
+ * 显式给出的**绝对**会话工作目录(缺失/空白/相对一律拒绝,绝不回落 `.` 或进程 cwd),
17
+ * realpath 化并排除 .git/node_modules 后,再以 O_CREAT|O_EXCL('wx')独占创建,
18
+ * 绝不覆盖既有文件、绝不跟随符号链接改写其目标;
19
+ * 3. 失败即拒绝、零写入:输入非法/事实未注册/渲染被拒/文件名被占用都返回结构化
20
+ * 错误,不落任何字节(校验全部发生在打开文件之前)。
21
+ */
22
+
23
+ import { randomBytes } from 'node:crypto'
24
+ import { open, realpath, stat, unlink } from 'node:fs/promises'
25
+ import { isAbsolute, join, sep } from 'node:path'
26
+
27
+ import { loadFactRegistry } from './fact-log.ts'
28
+ import { ITINERARY_DOC_LIMITS } from '../src/itinerary-doc-shared.ts'
29
+ import { renderItineraryDeck } from '../src/itinerary-deck.ts'
30
+
31
+ /** 产物文件名契约:固定前缀 + ASCII 安全 token + .html(无路径分隔符、无前导点) */
32
+ export const ITINERARY_DECK_ARTIFACT_BASENAME_RE = /^gotry-deck-[A-Za-z0-9][A-Za-z0-9._-]{0,63}\.html$/
33
+ /** 随机默认名(仅 crypto 十六进制;显式 basename 优先) */
34
+ export const ITINERARY_DECK_ARTIFACT_RANDOM_BYTES = 8
35
+ const RANDOM_ATTEMPTS = 3
36
+ const DIR_DENY = ['.git', 'node_modules']
37
+ const ID_SAMPLE = 3
38
+
39
+ export interface ItineraryDeckArtifactDeps {
40
+ /** 事实注册表根(config.stateRoot);工具面固定传当前配置,不接受调用方覆盖 */
41
+ stateRoot: string
42
+ /** 会话工作目录:必须是宿主显式给出的绝对路径;缺失/空白/相对一律拒绝(绝不回落进程 cwd) */
43
+ cwd: string
44
+ }
45
+
46
+ export type ItineraryDeckArtifactResult =
47
+ | {
48
+ ok: true
49
+ /** 落盘后的真实绝对路径(realpath 结果,供展示「最终落点」) */
50
+ path: string
51
+ basename: string
52
+ bytes: number
53
+ fact_ids: string[]
54
+ /** 本产物永不给整体「已验证」结论,只有逐条事实自带的可下单标注 */
55
+ overall_verified: false
56
+ }
57
+ | { ok: false; error: string; hint?: string; errors?: string[] }
58
+
59
+ function isRecord(value: unknown): value is Record<string, unknown> {
60
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
61
+ }
62
+
63
+ function hasDeniedSegment(p: string): boolean {
64
+ return p.split(sep).some(seg => DIR_DENY.includes(seg))
65
+ }
66
+
67
+ function fsErrorText(err: unknown): string {
68
+ const code = (err as NodeJS.ErrnoException | null)?.code
69
+ switch (code) {
70
+ case 'EACCES':
71
+ case 'EPERM': return '无写入权限'
72
+ case 'ENOSPC': return '磁盘空间不足'
73
+ case 'EROFS': return '只读文件系统'
74
+ case 'ENOTDIR': return '路径中某段不是目录'
75
+ case 'ENOENT': return '目录不存在'
76
+ case 'ENAMETOOLONG': return '文件名过长'
77
+ default: return typeof code === 'string' ? `文件系统错误 ${code}` : '文件系统错误'
78
+ }
79
+ }
80
+
81
+ /** fact_ids 输入收窄:必填数组、逐条非空短字符串、无重复、不超渲染器容量 */
82
+ function takeFactIds(value: unknown): { ids: string[] } | { error: string; hint?: string } {
83
+ if (value === undefined || value === null) {
84
+ return { error: 'fact_ids 必填(字符串数组,可为空数组)', hint: 'id 来自本次会话 exact-date 工具结果记录的注册表;不接受调用方自带事实' }
85
+ }
86
+ if (!Array.isArray(value)) return { error: 'fact_ids 必须是字符串数组' }
87
+ if (value.length > ITINERARY_DOC_LIMITS.facts) {
88
+ return { error: `fact_ids 超出容量上限(${ITINERARY_DOC_LIMITS.facts} 条,实测 ${value.length} 条)` }
89
+ }
90
+ const ids: string[] = []
91
+ const seen = new Set<string>()
92
+ const duplicates: string[] = []
93
+ for (const [i, raw] of value.entries()) {
94
+ if (typeof raw !== 'string' || raw.length === 0) return { error: `fact_ids[${i}] 必须是非空字符串` }
95
+ if (raw.length > ITINERARY_DOC_LIMITS.factIdChars) {
96
+ return { error: `fact_ids[${i}] 超出 id 长度上限(${ITINERARY_DOC_LIMITS.factIdChars} 字符)` }
97
+ }
98
+ if (seen.has(raw)) {
99
+ duplicates.push(raw)
100
+ continue
101
+ }
102
+ seen.add(raw)
103
+ ids.push(raw)
104
+ }
105
+ if (duplicates.length > 0) {
106
+ return { error: `fact_ids 有重复 id:${duplicates.slice(0, ID_SAMPLE).join('、')}${duplicates.length > ID_SAMPLE ? ` 等 ${duplicates.length} 条` : ''}`, hint: '同一条事实只能选一次;重复选择说明调用方对证据面不确定' }
107
+ }
108
+ return { ids }
109
+ }
110
+
111
+ function takeBasename(value: unknown): { basename: string } | { error: string; hint?: string } {
112
+ if (value === undefined || value === null)
113
+ return { basename: `gotry-deck-${randomBytes(ITINERARY_DECK_ARTIFACT_RANDOM_BYTES).toString('hex')}.html` }
114
+ if (typeof value !== 'string' || value.length === 0) return { error: 'basename 必须是字符串' }
115
+ if (value.includes('/') || value.includes('\\') || value.includes('..')) {
116
+ return { error: 'basename 只能是文件名,不能含路径分隔符或 ..', hint: '形如 gotry-deck-2027-thailand.html' }
117
+ }
118
+ if (!ITINERARY_DECK_ARTIFACT_BASENAME_RE.test(value)) {
119
+ return { error: 'basename 不符合命名契约', hint: '必须是 gotry-deck-<ASCII 字母/数字/._->.html(前缀后 1-64 字符)' }
120
+ }
121
+ return { basename: value }
122
+ }
123
+
124
+ /** 会话工作目录:必须显式且为绝对路径(缺失/空白/相对一律拒绝,绝不回落 '.' 或 process.cwd()),
125
+ * 按宿主原值解析(trim 只判全空白、绝不归一化路径),已存在、可 realpath、是目录,且不含 .git/node_modules 段 */
126
+ async function resolveWritableDir(cwd: unknown): Promise<{ dir: string } | { error: string; hint?: string }> {
127
+ if (typeof cwd !== 'string' || cwd.trim() === '') {
128
+ return { error: '会话工作目录缺失:拒绝在未知目录写产物', hint: '写产物必须有宿主显式给出的绝对会话工作目录,不做任何猜测性回落' }
129
+ }
130
+ // trim 只用于「是否全空白」判定:真实路径一律按宿主原值使用,绝不归一化——
131
+ // 尾随空格可能是真实目录名的一部分,归一化会写到同名兄弟目录
132
+ if (!isAbsolute(cwd)) {
133
+ return { error: `会话工作目录必须是绝对路径:${cwd}`, hint: '相对路径会随进程工作目录漂移,拒绝写入' }
134
+ }
135
+ const canonical = await realpath(cwd).catch(() => null)
136
+ if (!canonical) return { error: `会话工作目录不存在或无法解析:${cwd}`, hint: '本工具只在真实存在的会话工作目录顶层新建文件' }
137
+ if (hasDeniedSegment(canonical)) {
138
+ return { error: '会话工作目录位于受限段(.git/node_modules)内,拒绝写入', hint: `解析后:${canonical}` }
139
+ }
140
+ const st = await stat(canonical).catch(() => null)
141
+ if (!st?.isDirectory()) return { error: `会话工作目录不是目录:${canonical}` }
142
+ return { dir: canonical }
143
+ }
144
+
145
+ /** 独占创建:wx(O_CREAT|O_EXCL)既拒绝覆盖既有文件,也拒绝落在指向别处的符号链接上 */
146
+ async function writeExclusive(dir: string, basename: string, html: string): Promise<{ path: string; bytes: number } | { error: string; hint?: string }> {
147
+ const target = join(dir, basename)
148
+ const bytes = Buffer.byteLength(html, 'utf8')
149
+ let handle
150
+ try {
151
+ handle = await open(target, 'wx', 0o644)
152
+ } catch (err) {
153
+ if ((err as NodeJS.ErrnoException | null)?.code === 'EEXIST') {
154
+ return { error: `目标文件已存在,拒绝覆盖:${basename}`, hint: '换一个 basename(不传则自动生成随机名);本工具永不覆盖既有产物' }
155
+ }
156
+ return { error: `无法在会话工作目录新建文件:${fsErrorText(err)}`, hint: `目标目录:${dir}` }
157
+ }
158
+ try {
159
+ await handle.writeFile(html, 'utf-8')
160
+ } catch (err) {
161
+ await handle.close().catch(() => { /* 关闭失败不再覆盖原始写错误 */ })
162
+ await unlink(target).catch(() => { /* 清理半成品失败不改变「本次生成失败」的结论 */ })
163
+ return { error: `写入产物失败:${fsErrorText(err)}`, hint: `目标文件已清理:${target}` }
164
+ }
165
+ await handle.close()
166
+ const finalPath = await realpath(target).catch(() => target)
167
+ return { path: finalPath, bytes }
168
+ }
169
+
170
+ /**
171
+ * 生成一份行程 deck HTML 产物。写入唯一目标 = 会话工作目录顶层的新文件;
172
+ * 事实唯一来源 = deps.stateRoot 的事实注册表。
173
+ */
174
+ export async function generateItineraryDeckArtifact(input: unknown, deps: ItineraryDeckArtifactDeps): Promise<ItineraryDeckArtifactResult> {
175
+ if (!isRecord(input)) return { ok: false, error: 'input 必须是对象 {title, itinerary, fact_ids, basename?}' }
176
+
177
+ const ids = takeFactIds(input.fact_ids)
178
+ if ('error' in ids) return { ok: false, error: ids.error, hint: ids.hint }
179
+ const named = takeBasename(input.basename)
180
+ if ('error' in named) return { ok: false, error: named.error, hint: named.hint }
181
+
182
+ const registry = await loadFactRegistry(deps.stateRoot)
183
+ const byId = new Map(registry.map(f => [f.fact_id, f]))
184
+ const missing = ids.ids.filter(id => !byId.has(id))
185
+ if (missing.length > 0) {
186
+ return {
187
+ ok: false,
188
+ error: `事实 id 未在当前注册表:${missing.slice(0, ID_SAMPLE).join('、')}${missing.length > ID_SAMPLE ? ` 等 ${missing.length} 条` : ''}`,
189
+ hint: 'fact_ids 只能取自本会话 exact-date 工具结果的注册表;不接受调用方自带事实对象,也不从 Markdown 猜事实',
190
+ }
191
+ }
192
+ // 顺序保留调用方选择;登记行畸形不在选择阶段放宽——交给 deck 渲染器运行时校验显式拒绝
193
+ const selected = ids.ids.map(id => byId.get(id)!)
194
+
195
+ const rendered = renderItineraryDeck({ title: input.title, itinerary: input.itinerary, facts: selected })
196
+ if (!rendered.ok) {
197
+ return {
198
+ ok: false,
199
+ error: '渲染被拒:行程或事实不满足渲染契约,未写入任何文件',
200
+ errors: rendered.errors,
201
+ hint: '按 errors 修正显式行程结构或换用合法登记的事实;本工具不会静默截断核心行程',
202
+ }
203
+ }
204
+
205
+ const dir = await resolveWritableDir(deps.cwd)
206
+ if ('error' in dir) return { ok: false, error: dir.error, hint: dir.hint }
207
+
208
+ for (let attempt = 0; attempt < (input.basename === undefined || input.basename === null ? RANDOM_ATTEMPTS : 1); attempt++) {
209
+ const basename = attempt === 0 ? named.basename : `gotry-deck-${randomBytes(ITINERARY_DECK_ARTIFACT_RANDOM_BYTES).toString('hex')}.html`
210
+ const written = await writeExclusive(dir.dir, basename, rendered.html)
211
+ if ('error' in written) {
212
+ if (attempt + 1 < RANDOM_ATTEMPTS && input.basename === undefined) continue // 随机名撞车:换名重试
213
+ return { ok: false, error: written.error, hint: written.hint }
214
+ }
215
+ return { ok: true, path: written.path, basename, bytes: written.bytes, fact_ids: ids.ids, overall_verified: false }
216
+ }
217
+ return { ok: false, error: '随机文件名多次撞车,未写入文件' }
218
+ }