@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -219
- package/README.zh-CN.md +222 -0
- package/bin/gotry-backend.js +53 -0
- package/bin/gotry-booking-copilot.js +53 -0
- package/bin/gotry-bootstrap.js +1788 -0
- package/bin/gotry-inner.js +733 -83
- package/bin/gotry-process-liveness.d.ts +27 -0
- package/bin/gotry-process-liveness.js +97 -0
- package/bin/gotry-runtime-resolution.d.ts +27 -0
- package/bin/gotry-runtime-resolution.js +49 -0
- package/bin/gotry.js +1 -1
- package/client/client.js +203 -0
- package/cordis.gotry-patch.yml +80 -56
- package/data/airline-airports.json +39 -0
- package/data/stations-12306-verify.json +524 -0
- package/dist/capabilities/agent-reach-deep.js +45 -122
- package/dist/capabilities/agent-reach.js +101 -146
- package/dist/capabilities/anything.js +161 -94
- package/dist/capabilities/artifacts.js +370 -0
- package/dist/capabilities/channel-health.js +131 -0
- package/dist/capabilities/channel-registry.js +256 -0
- package/dist/capabilities/doctor.js +553 -0
- package/dist/capabilities/effect.js +394 -0
- package/dist/capabilities/external-event.js +149 -0
- package/dist/capabilities/fact-log.js +55 -0
- package/dist/capabilities/flyai-config.js +337 -0
- package/dist/capabilities/flyai.js +947 -110
- package/dist/capabilities/fx-contract.js +293 -0
- package/dist/capabilities/geo-atlas.js +246 -0
- package/dist/capabilities/ground-transfer.js +676 -0
- package/dist/capabilities/hbcli.js +260 -123
- package/dist/capabilities/hotelbyte-transaction.js +194 -0
- package/dist/capabilities/incident-log.js +131 -61
- package/dist/capabilities/itinerary-artifact.js +190 -0
- package/dist/capabilities/itinerary-deck-artifact.js +194 -0
- package/dist/capabilities/itinerary-deck-export.js +339 -0
- package/dist/capabilities/lavish-local.js +924 -0
- package/dist/capabilities/model-override.js +38 -0
- package/dist/capabilities/opensky.js +54 -43
- package/dist/capabilities/provider-detail-sanitize.js +64 -0
- package/dist/capabilities/resilience.js +191 -0
- package/dist/capabilities/route-provider-conformance.js +341 -0
- package/dist/capabilities/session/action-cache.js +50 -44
- package/dist/capabilities/session/adapters/ctrip-flight.js +120 -62
- package/dist/capabilities/session/adapters/ctrip-hotel.js +222 -0
- package/dist/capabilities/session/adapters/dida-portal.js +274 -0
- package/dist/capabilities/session/adapters/meituan-local.js +35 -54
- package/dist/capabilities/session/adapters/rail-12306.js +342 -0
- package/dist/capabilities/session/benchmark.js +189 -0
- package/dist/capabilities/session/bridge-ledger.js +224 -0
- package/dist/capabilities/session/extension-bridge.js +636 -0
- package/dist/capabilities/session/extension-channel.js +109 -0
- package/dist/capabilities/session/extension-distribution.js +230 -0
- package/dist/capabilities/session/extract.js +31 -21
- package/dist/capabilities/session/golden-score.js +114 -0
- package/dist/capabilities/session/health-watch.js +215 -0
- package/dist/capabilities/session/read-guard.js +75 -24
- package/dist/capabilities/session/static-flight-golden.js +150 -0
- package/dist/capabilities/session/transport.js +97 -83
- package/dist/capabilities/session/wizard.js +117 -0
- package/dist/capabilities/session-consent.js +178 -0
- package/dist/capabilities/session-login.js +170 -0
- package/dist/capabilities/session-search.js +635 -44
- package/dist/capabilities/spawn-bounded.js +203 -0
- package/dist/capabilities/sponsor-plugin.js +341 -0
- package/dist/capabilities/visa-policy.js +140 -0
- package/dist/capabilities/weather.js +243 -138
- package/dist/data/session-golden-20.json +25 -0
- package/dist/data/sf-golden-manifest.json +103 -0
- package/dist/data/sf-static-routes.json +91 -0
- package/dist/scripts/action-cache-tests.js +24 -59
- package/dist/scripts/agent-planning-turn-deadline-e2e.js +242 -0
- package/dist/scripts/agent-planning-turn-deadline-tests.js +185 -0
- package/dist/scripts/agent-reach-deep-tests.js +30 -40
- package/dist/scripts/agent-reach-error-tests.js +354 -0
- package/dist/scripts/agent-reach-tests.js +26 -18
- package/dist/scripts/agent-reach-wrapper-tests.js +50 -50
- package/dist/scripts/anything-path-measure-tests.js +133 -0
- package/dist/scripts/anything-path-measure.js +150 -0
- package/dist/scripts/anything-tests.js +250 -60
- package/dist/scripts/artifact-client-contract-tests.js +220 -0
- package/dist/scripts/artifacts-capability-tests.js +727 -0
- package/dist/scripts/async-collect.js +50 -11
- package/dist/scripts/benchmark-environment-bridge-e2e.js +741 -0
- package/dist/scripts/benchmark-environment-bridge-tests.js +1452 -0
- package/dist/scripts/booking-copilot-availability-ledger-binding-tests.js +126 -0
- package/dist/scripts/booking-copilot-availability-policy-tests.js +265 -0
- package/dist/scripts/booking-copilot-bin-proof-tests.js +66 -0
- package/dist/scripts/booking-copilot-cleanup-proof-helper.js +76 -0
- package/dist/scripts/booking-copilot-crossrepo-fixture-server.js +144 -0
- package/dist/scripts/booking-copilot-dsh-core-proof-tests.js +424 -0
- package/dist/scripts/booking-copilot-dsh-planner-proof-tests.js +2839 -0
- package/dist/scripts/booking-copilot-dsh-plugin-proof-tests.js +309 -0
- package/dist/scripts/booking-copilot-dsh-readiness-proof.js +238 -0
- package/dist/scripts/booking-copilot-dsh-warmer-proof.js +126 -0
- package/dist/scripts/booking-copilot-event-sequence-concurrency-proof-tests.js +156 -0
- package/dist/scripts/booking-copilot-gap-code-contract-proof-tests.js +70 -0
- package/dist/scripts/booking-copilot-intent-proof-tests.js +422 -0
- package/dist/scripts/booking-copilot-operation-ledger-concurrency-proof-tests.js +184 -0
- package/dist/scripts/booking-copilot-planner-live-probe.js +95 -0
- package/dist/scripts/booking-copilot-proof-runtime.js +42 -0
- package/dist/scripts/booking-copilot-receipt-ledger-concurrency-proof-tests.js +210 -0
- package/dist/scripts/booking-copilot-runtime-proof-tests.js +2349 -0
- package/dist/scripts/booking-copilot-server-proof-tests.js +784 -0
- package/dist/scripts/booking-copilot-startup-proof-tests.js +94 -0
- package/dist/scripts/booking-executor-tests.js +80 -0
- package/dist/scripts/booking-recovery-chain-tests.js +182 -0
- package/dist/scripts/booking-saga-tests.js +125 -0
- package/dist/scripts/booking-surface-contract-proof-tests.js +212 -0
- package/dist/scripts/bootstrap-tests.js +1549 -0
- package/dist/scripts/bridge-events-tests.js +239 -0
- package/dist/scripts/bridge-ledger-tests.js +428 -0
- package/dist/scripts/build-changelog.js +238 -0
- package/dist/scripts/build-metrics-report.js +331 -0
- package/dist/scripts/changelog-tests.js +118 -0
- package/dist/scripts/channel-probe-tests.js +103 -0
- package/dist/scripts/channel-probe.js +169 -0
- package/dist/scripts/channel-registry-tests.js +208 -0
- package/dist/scripts/city-scenario-tier-tests.js +234 -0
- package/dist/scripts/companion-tests.js +25 -70
- package/dist/scripts/dida-cookie-probe.js +87 -0
- package/dist/scripts/dida-runner-stop-tests.js +311 -0
- package/dist/scripts/dida-sw-probe.js +114 -0
- package/dist/scripts/diff-test.js +17 -12
- package/dist/scripts/doctor-tests.js +600 -0
- package/dist/scripts/drill-multiuser-ledger-tests.js +1076 -0
- package/dist/scripts/drill-sdk-descendant-cleanup-tests.js +606 -0
- package/dist/scripts/drill-w2a-sensor-tests.js +788 -0
- package/dist/scripts/dsh-artifact-e2e.js +279 -0
- package/dist/scripts/dsh-artifact-web-e2e.js +675 -0
- package/dist/scripts/dsh-http-proxy-sse-proof.js +90 -0
- package/dist/scripts/dsh-llm-tests.js +437 -0
- package/dist/scripts/dsh-runtime-closure-tests.js +182 -0
- package/dist/scripts/dsh-runtime-closure.js +162 -0
- package/dist/scripts/dsh-session-v3-migration-proof.js +109 -0
- package/dist/scripts/dsh-subprocess-local-proof.js +150 -0
- package/dist/scripts/dsh-target-closure-proof.js +178 -0
- package/dist/scripts/effect-tests.js +539 -0
- package/dist/scripts/engine-run.js +1 -3
- package/dist/scripts/engine-tests.js +16 -10
- package/dist/scripts/evaluation-cadence-tests.js +174 -0
- package/dist/scripts/evaluation-contract-tests.js +268 -0
- package/dist/scripts/extension-distribution-cli.js +39 -0
- package/dist/scripts/extension-distribution-tests.js +443 -0
- package/dist/scripts/extension-tests.js +988 -0
- package/dist/scripts/external-event-tests.js +350 -0
- package/dist/scripts/fact-gate-tests.js +869 -0
- package/dist/scripts/fixtures/extension-bridge-unref-child.mjs +21 -0
- package/dist/scripts/flight-hotel-anchor-363-registered-e2e.js +169 -0
- package/dist/scripts/flight-malformed-tests.js +159 -0
- package/dist/scripts/flyai-setup-tests.js +228 -0
- package/dist/scripts/flyai-setup-tool-tests.js +54 -0
- package/dist/scripts/flyai-tests.js +692 -0
- package/dist/scripts/flyai-tool-registration-contract-tests.js +91 -0
- package/dist/scripts/fx-contract-tests.js +201 -0
- package/dist/scripts/g5-guard-tests.js +150 -0
- package/dist/scripts/g5-guard.js +193 -0
- package/dist/scripts/gen-lunar-anchors.js +106 -0
- package/dist/scripts/geo-atlas-tests.js +283 -0
- package/dist/scripts/gotry-backend-kernel-tests.js +206 -0
- package/dist/scripts/gotry-try-demo-tests.js +79 -0
- package/dist/scripts/gotry-try-demo.js +49 -0
- package/dist/scripts/ground-transfer-tests.js +956 -0
- package/dist/scripts/hbcli-e2e-tests.js +159 -0
- package/dist/scripts/hbcli-live-optin-tests.js +96 -0
- package/dist/scripts/hbcli-release-contract-tests.js +122 -0
- package/dist/scripts/hbcli-tests.js +121 -69
- package/dist/scripts/health-watch-cli.js +73 -0
- package/dist/scripts/hotel-date-gate-tests.js +496 -0
- package/dist/scripts/hotelbyte-reconcile-tests.js +141 -0
- package/dist/scripts/hotelbyte-spawn-e2e-tests.js +373 -0
- package/dist/scripts/i18n-tests.js +41 -0
- package/dist/scripts/incident-tests.js +281 -87
- package/dist/scripts/issue-194-job-id-guard-tests.js +106 -0
- package/dist/scripts/issue-233-cancel-refund-commission-tests.js +392 -0
- package/dist/scripts/issue-271-liveness-tests.js +569 -0
- package/dist/scripts/issue-289-model-retry-real-tests.js +296 -0
- package/dist/scripts/issue-289-model-retry-tests.js +441 -0
- package/dist/scripts/issue-289-web-retry-e2e.js +630 -0
- package/dist/scripts/issue-338-home-city-e2e.js +577 -0
- package/dist/scripts/issue343-real-entry-e2e.js +160 -0
- package/dist/scripts/issue436-persisted-routing-e2e.js +247 -0
- package/dist/scripts/itinerary-artifact-tests.js +368 -0
- package/dist/scripts/itinerary-deck-artifact-tests.js +416 -0
- package/dist/scripts/itinerary-deck-export-tests.js +552 -0
- package/dist/scripts/itinerary-deck-tests.js +435 -0
- package/dist/scripts/itinerary-html-tests.js +426 -0
- package/dist/scripts/journey-tests.js +22 -27
- package/dist/scripts/kernel-manifest-evidence.js +109 -0
- package/dist/scripts/kernel-manifest-gate.js +126 -0
- package/dist/scripts/kernel-manifest-tests.js +212 -0
- package/dist/scripts/kernel-manifest-trace-entry.js +7 -0
- package/dist/scripts/kernel-manifest-trace-hook.mjs +28 -0
- package/dist/scripts/kernel-manifest.js +369 -0
- package/dist/scripts/lavish-local-tests.js +1226 -0
- package/dist/scripts/lavish-product-tools-tests.js +537 -0
- package/dist/scripts/ledger-repair-apply-tests.js +316 -0
- package/dist/scripts/ledger-repair-plan-tests.js +375 -0
- package/dist/scripts/ledger-tests.js +382 -246
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- package/dist/scripts/m3-cohort-tests.js +714 -0
- package/dist/scripts/m3-cohort.js +310 -0
- package/dist/scripts/managed-dsh-cleanup-diagnostic-proof.js +184 -0
- package/dist/scripts/managed-dsh-close-failure-proof.js +172 -0
- package/dist/scripts/managed-dsh-run-port-proof.js +153 -0
- package/dist/scripts/managed-dsh-terminal-proof.js +252 -0
- package/dist/scripts/map-tools-vendor-package-proof.js +365 -0
- package/dist/scripts/memory-capture-tests.js +242 -61
- package/dist/scripts/memory-decay-tests.js +18 -52
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +24 -12
- package/dist/scripts/memory-value-report.js +631 -0
- package/dist/scripts/memory-value-tests.js +328 -0
- package/dist/scripts/metrics-report-tests.js +151 -0
- package/dist/scripts/model-override-e2e.js +137 -0
- package/dist/scripts/nightly-evidence-tests.js +203 -0
- package/dist/scripts/nightly-evidence.js +303 -0
- package/dist/scripts/nudge-digest.js +39 -28
- package/dist/scripts/observability-tests.js +381 -0
- package/dist/scripts/onboarding-tests.js +161 -0
- package/dist/scripts/opensky-check.js +18 -13
- package/dist/scripts/opensky-tests.js +28 -28
- package/dist/scripts/outcome-projection-tests.js +427 -0
- package/dist/scripts/persona-sim-tests.js +603 -0
- package/dist/scripts/persona-sim.js +1081 -0
- package/dist/scripts/persona-surface-guard-tests.js +49 -0
- package/dist/scripts/pnpm-dsh-closure-proof.js +17 -0
- package/dist/scripts/policy-anchor-359-registered-e2e.js +214 -0
- package/dist/scripts/price-drift-tests.js +340 -0
- package/dist/scripts/price-drift-watch.js +447 -0
- package/dist/scripts/probe-poi-tests.js +53 -45
- package/dist/scripts/product-metrics.js +483 -0
- package/dist/scripts/publish-preverify.js +110 -0
- package/dist/scripts/realtime-pricing-tests.js +76 -0
- package/dist/scripts/recall-tests.js +513 -0
- package/dist/scripts/replay-async.js +22 -18
- package/dist/scripts/replay-real.js +18 -18
- package/dist/scripts/replay.js +54 -82
- package/dist/scripts/route-provider-conformance-tests.js +391 -0
- package/dist/scripts/session-attach-diagnose.js +20 -20
- package/dist/scripts/session-attach-poc.js +34 -37
- package/dist/scripts/session-benchmark.js +180 -0
- package/dist/scripts/session-dida-live-e2e.js +199 -0
- package/dist/scripts/session-extract-tests.js +14 -35
- package/dist/scripts/session-link-tests.js +478 -0
- package/dist/scripts/session-login.js +27 -46
- package/dist/scripts/session-tests.js +796 -96
- package/dist/scripts/session-zone-crash.js +49 -0
- package/dist/scripts/session-zone-ledger-tests.js +432 -0
- package/dist/scripts/session-zone-observation-tests.js +492 -0
- package/dist/scripts/session-zone-observe.js +135 -0
- package/dist/scripts/session-zone-wiring-tests.js +566 -0
- package/dist/scripts/session-zones-tests.js +374 -0
- package/dist/scripts/sf-live-benchmark.js +367 -0
- package/dist/scripts/sf-live-challenge-stop-tests.js +453 -0
- package/dist/scripts/sf-live-cli-tests.js +92 -0
- package/dist/scripts/sf-live-options.js +46 -0
- package/dist/scripts/sf-manifest-tests.js +30 -0
- package/dist/scripts/sf-manifest.js +33 -0
- package/dist/scripts/sf-soft-score-tests.js +67 -0
- package/dist/scripts/sf-summary-tests.js +324 -0
- package/dist/scripts/sf-summary.js +525 -0
- package/dist/scripts/share-tests.js +432 -0
- package/dist/scripts/skeleton-check.js +16 -14
- package/dist/scripts/skeleton-integration-test.js +28 -11
- package/dist/scripts/skills-contract-tests.js +51 -46
- package/dist/scripts/smoke-session-gate-tests.js +21 -0
- package/dist/scripts/smoke.js +690 -246
- package/dist/scripts/spawn-bounded-abort-tests.js +244 -0
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +226 -101
- package/dist/scripts/state-cli.js +565 -198
- package/dist/scripts/state-repair-cli.js +66 -0
- package/dist/scripts/state-repair-tests.js +140 -0
- package/dist/scripts/static-golden-tests.js +165 -0
- package/dist/scripts/time-eval-tests.js +212 -192
- package/dist/scripts/travel-timeline-tests.js +29 -93
- package/dist/scripts/turn-handoff-collect-tests.js +169 -0
- package/dist/scripts/turn-handoff-collect.js +161 -0
- package/dist/scripts/turn-policy-tests.js +55 -0
- package/dist/scripts/typed-contract-canary.js +176 -0
- package/dist/scripts/tz-resolver-sanity.js +50 -0
- package/dist/scripts/tz-resolver-tests.js +584 -0
- package/dist/scripts/unified-tests.js +60 -20
- package/dist/scripts/visa-policy-tests.js +62 -0
- package/dist/scripts/weather-tests.js +276 -41
- package/dist/scripts/wish-channel-gate-tests.js +54 -0
- package/dist/scripts/write-gate-tests.js +522 -0
- package/dist/scripts/z3-lifecycle-fault-tests.js +188 -0
- package/dist/scripts/z3-lifecycle-tests.js +170 -0
- package/dist/scripts/z3-race-repeat-tests.js +39 -0
- package/dist/scripts/z3-race-tests.js +46 -0
- package/dist/src/artifact-gate.js +829 -0
- package/dist/src/backend/kernel.js +91 -0
- package/dist/src/backend/modules/booking-copilot.js +92 -0
- package/dist/src/backend/modules/booking-executor.js +124 -0
- package/dist/src/backend/modules/session-search.js +551 -0
- package/dist/src/benchmark-agent-conformance.js +578 -0
- package/dist/src/benchmark-environment-bridge.js +515 -0
- package/dist/src/benchmark-headless-child-diagnostics.js +166 -0
- package/dist/src/benchmark-tool-isolation.js +151 -0
- package/dist/src/bookable-facts.js +533 -0
- package/dist/src/booking-saga.js +97 -0
- package/dist/src/booking-surface/availability-policy.js +697 -0
- package/dist/src/booking-surface/booking-intent.js +43 -0
- package/dist/src/booking-surface/cancel-refund-commission.js +379 -0
- package/dist/src/booking-surface/canonical-schema.js +116 -0
- package/dist/src/booking-surface/contracts.js +75 -0
- package/dist/src/booking-surface/dsh-planner.js +1854 -0
- package/dist/src/booking-surface/dsh-plugin.js +378 -0
- package/dist/src/booking-surface/dsh-worker.js +98 -0
- package/dist/src/booking-surface/error-codes.js +231 -0
- package/dist/src/booking-surface/index.js +9 -0
- package/dist/src/booking-surface/managed-dsh-cleanup-diagnostic.js +97 -0
- package/dist/src/booking-surface/managed-dsh-run-port.js +244 -0
- package/dist/src/booking-surface/profile.js +24 -0
- package/dist/src/booking-surface/recovery-chain.js +367 -0
- package/dist/src/booking-surface/runtime.js +1968 -0
- package/dist/src/booking-surface/server.js +674 -0
- package/dist/src/booking-surface/startup.js +168 -0
- package/dist/src/booking-surface/validation.js +261 -0
- package/dist/src/bridge.js +20 -13
- package/dist/src/city-scenario-tier.js +378 -0
- package/dist/src/companions.js +48 -74
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +171 -115
- package/dist/src/engine.js +276 -193
- package/dist/src/evaluation-cadence.js +97 -0
- package/dist/src/evaluation-contracts.js +283 -0
- package/dist/src/flight-pack-adapter.js +54 -0
- package/dist/src/flight-pack-contract.js +2 -0
- package/dist/src/flyai-setup-tool.js +70 -0
- package/dist/src/gotry-backend.js +125 -0
- package/dist/src/hotel-date-gate.js +118 -0
- package/dist/src/i18n.js +88 -0
- package/dist/src/index.js +2385 -881
- package/dist/src/itinerary-deck.js +139 -0
- package/dist/src/itinerary-doc-shared.js +661 -0
- package/dist/src/itinerary-html.js +131 -0
- package/dist/src/journey.js +155 -108
- package/dist/src/lavish-tools.js +400 -0
- package/dist/src/ledger-repair-apply.js +538 -0
- package/dist/src/ledger-repair-plan.js +463 -0
- package/dist/src/loop.js +331 -161
- package/dist/src/m3-cohort.js +1577 -0
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +30 -14
- package/dist/src/memory-lifecycle.js +1310 -0
- package/dist/src/memory-utility.js +27 -40
- package/dist/src/mock-llm.js +36 -61
- package/dist/src/model.js +81 -36
- package/dist/src/outcome-projection.js +746 -0
- package/dist/src/realtime-pricing.js +103 -0
- package/dist/src/recall/card.js +74 -0
- package/dist/src/recall/evaluator.js +140 -0
- package/dist/src/recall/integration.js +17 -0
- package/dist/src/recall/tick.js +166 -0
- package/dist/src/session-link/plan-it-action.js +48 -0
- package/dist/src/session-link/session-link.js +248 -0
- package/dist/src/session-zone-ledger.js +312 -0
- package/dist/src/session-zone-observation.js +354 -0
- package/dist/src/session-zone-wiring.js +658 -0
- package/dist/src/session-zones.js +510 -0
- package/dist/src/share/adapters.js +51 -0
- package/dist/src/share/share-consent.js +76 -0
- package/dist/src/share/share-deck.js +58 -0
- package/dist/src/share/share-token.js +146 -0
- package/dist/src/slot-spec.js +84 -106
- package/dist/src/state-ledger.js +696 -524
- package/dist/src/state-repair.js +299 -0
- package/dist/src/subagent-job-id-guard.js +58 -0
- package/dist/src/time-anchor.js +231 -33
- package/dist/src/tool-packet.js +20 -8
- package/dist/src/travel-slots.js +58 -35
- package/dist/src/travel-timeline.js +44 -63
- package/dist/src/turn-deadline.js +287 -0
- package/dist/src/turn-policy.js +118 -0
- package/dist/src/tz-resolver.js +143 -0
- package/dist/src/unified.js +600 -360
- package/dist/src/wish-pool.js +42 -12
- package/dist/src/write-gate.js +495 -0
- package/dist/src/z3-shared.js +284 -0
- package/extension/README.md +21 -0
- package/extension/README.zh-CN.md +21 -0
- package/extension/background.js +463 -0
- package/extension/content-bridge.js +270 -0
- package/extension/content-main.js +111 -0
- package/extension/manifest.json +60 -0
- package/package.json +407 -14
- package/schemas/booking.intent.schema.json +193 -0
- package/schemas/booking.surface.schema.json +2622 -0
- package/ts/capabilities/agent-reach-deep.ts +114 -0
- package/ts/capabilities/agent-reach.ts +198 -0
- package/ts/capabilities/anything.ts +282 -0
- package/ts/capabilities/artifacts.ts +428 -0
- package/ts/capabilities/fact-log.ts +54 -0
- package/ts/capabilities/flyai-config.ts +388 -0
- package/ts/capabilities/flyai.ts +1274 -0
- package/ts/capabilities/hbcli.ts +253 -71
- package/ts/capabilities/incident-log.ts +88 -59
- package/ts/capabilities/itinerary-artifact.ts +214 -0
- package/ts/capabilities/itinerary-deck-artifact.ts +218 -0
- package/ts/capabilities/itinerary-deck-export.ts +378 -0
- package/ts/capabilities/opensky.ts +121 -0
- package/ts/capabilities/session/action-cache.ts +148 -0
- package/ts/capabilities/session/adapters/ctrip-flight.ts +181 -0
- package/ts/capabilities/session/adapters/ctrip-hotel.ts +238 -0
- package/ts/capabilities/session/adapters/dida-portal.ts +338 -0
- package/ts/capabilities/session/adapters/meituan-local.ts +70 -0
- package/ts/capabilities/session/adapters/rail-12306.ts +392 -0
- package/ts/capabilities/session/benchmark.ts +276 -0
- package/ts/capabilities/session/bridge-ledger.ts +333 -0
- package/ts/capabilities/session/extension-bridge.ts +794 -0
- package/ts/capabilities/session/extension-channel.ts +144 -0
- package/ts/capabilities/session/extension-distribution.ts +266 -0
- package/ts/capabilities/session/extract.ts +54 -0
- package/ts/capabilities/session/golden-score.ts +139 -0
- package/ts/capabilities/session/health-watch.ts +264 -0
- package/ts/capabilities/session/read-guard.ts +141 -0
- package/ts/capabilities/session/static-flight-golden.ts +209 -0
- package/ts/capabilities/session/transport.ts +146 -0
- package/ts/capabilities/session/wizard.ts +140 -0
- package/ts/capabilities/session-consent.ts +211 -0
- package/ts/capabilities/session-login.ts +208 -0
- package/ts/capabilities/session-search.ts +893 -0
- package/ts/capabilities/weather.ts +330 -0
- package/ts/dsh-runtime/vendor/README.md +68 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/LICENSE +21 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.en.md +405 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/README.md +404 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/client/client.js +1584 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/cordis.patch.yml +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/icon.svg +5 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/amap.js +595 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/nominatim.js +63 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/osrm.js +58 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/clients/photon.js +55 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-file.js +87 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config-route.js +131 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/config.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/geo.js +232 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/index.js +120 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/settings-ns.js +40 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/staticmap-route.js +115 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/geocode.js +127 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/poi.js +84 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/tools/routes.js +267 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/amap.d.ts +90 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/nominatim.d.ts +17 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/osrm.d.ts +15 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/clients/photon.d.ts +22 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-file.d.ts +33 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config-route.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/config.d.ts +23 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/geo.d.ts +61 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/index.d.ts +18 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/settings-ns.d.ts +14 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/staticmap-route.d.ts +31 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/geocode.d.ts +11 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/poi.d.ts +9 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/tools/routes.d.ts +75 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types/types.d.ts +68 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/lib/types.js +16 -0
- package/ts/dsh-runtime/vendor/dsh-map-tools/package.json +97 -0
- package/ts/package.json +293 -3
- package/ts/scripts/async-collect.ts +91 -0
- package/ts/scripts/map-tools-vendor-package-proof.ts +404 -0
- package/ts/scripts/skeleton-check.ts +1 -1
- package/ts/scripts/skeleton-integration-test.ts +23 -1
- package/ts/scripts/state-cli.ts +586 -0
- package/ts/scripts/turn-handoff-collect.ts +178 -0
- package/ts/src/artifact-gate.ts +991 -0
- package/ts/src/benchmark-agent-conformance.ts +619 -0
- package/ts/src/benchmark-environment-bridge.ts +538 -0
- package/ts/src/benchmark-headless-child-diagnostics.ts +184 -0
- package/ts/src/benchmark-tool-isolation.ts +166 -0
- package/ts/src/bookable-facts.ts +837 -0
- package/ts/src/booking-saga.ts +133 -0
- package/ts/src/booking-surface/availability-policy.ts +522 -0
- package/ts/src/booking-surface/booking-intent.ts +86 -0
- package/ts/src/booking-surface/cancel-refund-commission.ts +488 -0
- package/ts/src/booking-surface/canonical-schema.js +116 -0
- package/ts/src/booking-surface/contracts.ts +475 -0
- package/ts/src/booking-surface/dsh-planner.ts +1949 -0
- package/ts/src/booking-surface/dsh-plugin.js +378 -0
- package/ts/src/booking-surface/dsh-worker.ts +110 -0
- package/ts/src/booking-surface/error-codes.ts +241 -0
- package/ts/src/booking-surface/index.ts +9 -0
- package/ts/src/booking-surface/managed-dsh-cleanup-diagnostic.ts +151 -0
- package/ts/src/booking-surface/managed-dsh-run-port.ts +273 -0
- package/ts/src/booking-surface/profile.ts +42 -0
- package/ts/src/booking-surface/recovery-chain.ts +409 -0
- package/ts/src/booking-surface/runtime.ts +1793 -0
- package/ts/src/booking-surface/server.ts +733 -0
- package/ts/src/booking-surface/startup.ts +201 -0
- package/ts/src/booking-surface/validation.ts +217 -0
- package/ts/src/companions.ts +113 -0
- package/ts/src/contracts.ts +17 -3
- package/ts/src/dsh-llm.ts +152 -17
- package/ts/src/flyai-setup-tool.ts +77 -0
- package/ts/src/index.ts +2123 -200
- package/ts/src/itinerary-deck.ts +189 -0
- package/ts/src/itinerary-doc-shared.ts +741 -0
- package/ts/src/itinerary-html.ts +171 -0
- package/ts/src/loop.ts +234 -26
- package/ts/src/memory-capture.ts +283 -0
- package/ts/src/memory-decay.ts +55 -0
- package/ts/src/memory-utility.ts +70 -0
- package/ts/src/mock-llm.ts +4 -8
- package/ts/src/model.ts +88 -2
- package/ts/src/recall/card.ts +106 -0
- package/ts/src/recall/evaluator.ts +170 -0
- package/ts/src/recall/integration.ts +35 -0
- package/ts/src/recall/tick.ts +213 -0
- package/ts/src/session-link/plan-it-action.ts +78 -0
- package/ts/src/session-link/session-link.ts +275 -0
- package/ts/src/share/adapters.ts +93 -0
- package/ts/src/share/share-consent.ts +105 -0
- package/ts/src/share/share-deck.ts +85 -0
- package/ts/src/share/share-token.ts +166 -0
- package/ts/src/slot-spec.ts +172 -0
- package/ts/src/state-ledger.ts +1166 -0
- package/ts/src/subagent-job-id-guard.ts +72 -0
- package/ts/src/time-anchor.ts +338 -0
- package/ts/src/tool-packet.ts +32 -0
- package/ts/src/travel-slots.ts +219 -0
- package/ts/src/travel-timeline.ts +83 -0
- package/ts/src/turn-deadline.ts +366 -0
- package/ts/src/turn-policy.ts +154 -0
- package/ts/src/unified.ts +444 -105
- package/ts/src/wish-pool.ts +79 -0
- package/dist/scripts/session-attach-wait.js +0 -41
|
@@ -1,23 +1,552 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话检索编排层(RFC §3.2):transport → adapter entry → networkHint 嗅探 → 解析 → ReadGuard → 证据链。
|
|
3
|
+
*
|
|
4
|
+
* 传输车道(2026-08-29 定案,RFC §2.2):**扩展桥为默认**——一次性安装的 MV3 扩展
|
|
5
|
+
* (GoTry Session Bridge)在自己标签页被动嗅探 batchSearch,零 Chrome 系统弹窗;
|
|
6
|
+
* cdp(attach 日常 Chrome,Chrome 144+ 每连接弹权限框)降为显式后备
|
|
7
|
+
* (`GOTRY_SESSION_TRANSPORT=cdp` opt-in,诊断/测试用);persistent 仅测试。
|
|
8
|
+
*
|
|
9
|
+
* 证据链(L4 增补):[会话:ctrip-flight@ts] / [会话:ctrip-hotel@ts] = 用户本人会话内实时检索,非官方 API;
|
|
10
|
+
* 风控命中(verdict='challenged')= degraded,绝不重试、绝不绕过(合规支柱②)。
|
|
11
|
+
* 节律(§3.4):同站点 ≥30s 间隔 + 单调冷却;超间隔返回 verdict='cooldown'。
|
|
12
|
+
* 永不抛错;扩展车道 fail-closed(桥/扩展不可用即 verdict,零花费);测试/巡检用隔离 profile 与 stateRoot。
|
|
13
|
+
*/
|
|
1
14
|
import { openSession } from './session/transport.js';
|
|
2
|
-
import {
|
|
15
|
+
import { extensionCookieNames, extensionSearchJob, classifyBridgeFailure, needsExtensionHint } from './session/extension-channel.js';
|
|
16
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
17
|
+
import { dirname } from 'node:path';
|
|
18
|
+
import { randomUUID } from 'node:crypto';
|
|
19
|
+
import { buildEntryUrl, NETWORK_HINTS, parseBatchSearchResult, LOGIN_COOKIE_NAMES, SITE_DOMAIN } from './session/adapters/ctrip-flight.js';
|
|
20
|
+
import { buildHotelEntryUrl, parseCtripHotelList, HOTEL_SITE_HOST } from './session/adapters/ctrip-hotel.js';
|
|
21
|
+
import { buildTrainEntryUrl, parseLeftTicketQueryResult, resolveTrainQueryTelecodes, validateTrainQueryResponseUrl } from './session/adapters/rail-12306.js';
|
|
22
|
+
import { buildDidaEntryUrl, parseDidaRates, parseDidaSearchCache, parseDidaRecommendHotels, parseDidaPrices, DIDA_NETWORK_HINTS, DIDA_SITE_DOMAIN, DIDA_LOGIN_COOKIE_NAMES } from './session/adapters/dida-portal.js';
|
|
23
|
+
import { EXTENSION_STORE_URL } from './session/extension-bridge.js';
|
|
24
|
+
/** Flight evidence can be persisted by the benchmark; extension-supplied error text is untrusted. */
|
|
25
|
+
function flightBridgeFailureSummary(kind) {
|
|
26
|
+
switch (kind) {
|
|
27
|
+
case 'extension-not-connected': return needsExtensionHint();
|
|
28
|
+
case 'bridge-unavailable': return '扩展桥或端口池不可用;请检查本机服务后重试';
|
|
29
|
+
case 'timeout': return '扩展作业超时;请稍后重试';
|
|
30
|
+
case 'job-error': return '扩展作业失败;请检查扩展连接后重试';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** 节律闸(§3.4):同站点最小间隔;导出仅测试用 */
|
|
3
34
|
const MIN_INTERVAL_MS = 30_000;
|
|
4
35
|
const lastCallAt = new Map();
|
|
5
36
|
export function __resetRateLimiterForTest() {
|
|
6
37
|
lastCallAt.clear();
|
|
7
38
|
}
|
|
39
|
+
let trainSessionTransportForTest;
|
|
40
|
+
/** Isolated CDP collector seam; production uses openSession and never installs this. */
|
|
41
|
+
export function __setTrainSessionTransportForTest(factory) {
|
|
42
|
+
trainSessionTransportForTest = factory ?? undefined;
|
|
43
|
+
}
|
|
8
44
|
const CHALLENGE_RE = /验证|滑块|captcha|verify/i;
|
|
9
|
-
|
|
45
|
+
/** 把 transport 文案漂移收敛为稳定的产品 verdict;纯函数供回归覆盖。 */
|
|
46
|
+
export function classifyTransportFailure(summary, cdpMode) {
|
|
47
|
+
return cdpMode && /日常 Chrome 未开调试端口|cdp attach 失败/.test(summary)
|
|
48
|
+
? 'needs-attach'
|
|
49
|
+
: 'error';
|
|
50
|
+
}
|
|
51
|
+
/** 传输车道解析(纯函数,测试锚点):persistent=隔离 profile(测试自检);cdp=显式 opt-in;扩展=默认 */
|
|
52
|
+
export function resolveTransportMode(profileDir) {
|
|
53
|
+
if (profileDir !== undefined)
|
|
54
|
+
return 'persistent';
|
|
55
|
+
return (process.env.GOTRY_SESSION_TRANSPORT ?? '').trim().toLowerCase() === 'cdp' ? 'cdp' : 'extension';
|
|
56
|
+
}
|
|
57
|
+
/** Live browser transport is an explicit opt-in for smoke/full-suite callers. */
|
|
58
|
+
export function sessionLiveEnabled(env = process.env) {
|
|
59
|
+
return env.GOTRY_SESSION_LIVE === '1';
|
|
60
|
+
}
|
|
61
|
+
/** Deterministic offline observation used when the live session gate is closed. */
|
|
62
|
+
export function offlineSessionFlightResult() {
|
|
63
|
+
return {
|
|
64
|
+
ok: false,
|
|
65
|
+
via: 'session-ctrip-flight-error',
|
|
66
|
+
evidence: '[会话:ctrip-flight@offline] GOTRY_SESSION_LIVE!=1; live transport not invoked',
|
|
67
|
+
latencyMs: 0,
|
|
68
|
+
verdict: 'error',
|
|
69
|
+
error: 'GOTRY_SESSION_LIVE=1 required for live session transport',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Gate a caller before it can invoke the browser/session transport. */
|
|
73
|
+
export async function gatedSessionFlightSearch(q, search = sessionFlightSearch, env = process.env) {
|
|
74
|
+
if (!sessionLiveEnabled(env))
|
|
75
|
+
return offlineSessionFlightResult();
|
|
76
|
+
return search(q);
|
|
77
|
+
}
|
|
78
|
+
/** 扩展车道 job 审计(ReadGuard 审计同款 JSONL,kind 区分;auditPath 缺省不落盘) */
|
|
79
|
+
export function appendExtensionAudit(auditPath, entry) {
|
|
80
|
+
if (!auditPath)
|
|
81
|
+
return;
|
|
82
|
+
try {
|
|
83
|
+
const record = { ts: new Date().toISOString(), ...entry, url: entry.url.slice(0, 400) };
|
|
84
|
+
mkdirSync(dirname(auditPath), { recursive: true });
|
|
85
|
+
appendFileSync(auditPath, JSON.stringify(record) + '\n');
|
|
86
|
+
}
|
|
87
|
+
catch { /* 审计失败不阻塞检索 */ }
|
|
88
|
+
}
|
|
89
|
+
/** 酒店城市码表未收录时的人话指引(纯函数,测试锚点) */
|
|
90
|
+
export function hotelCityUnresolvedHint(cities) {
|
|
91
|
+
return `城市 ${cities.join('/')} 不在携程酒店城市码表——先 web 搜「hotels.ctrip.com ${cities[0] ?? ''} 酒店」拿到 list 页 URL 里的 city= 数字,带 cityId 重试`;
|
|
92
|
+
}
|
|
93
|
+
export async function sessionHotelSearch(q) {
|
|
10
94
|
const started = Date.now();
|
|
11
95
|
const ts = new Date().toISOString();
|
|
12
|
-
const site = 'ctrip-
|
|
13
|
-
const err = (verdict, error)=>({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
96
|
+
const site = 'ctrip-hotel';
|
|
97
|
+
const err = (verdict, error) => ({
|
|
98
|
+
ok: false, via: 'session-ctrip-hotel-error', evidence: `[会话:${site}@error@${ts}] ${error}`, latencyMs: Date.now() - started, verdict, error,
|
|
99
|
+
});
|
|
100
|
+
// 日期对闸(与 flyai hotel 同口径):成对且 YYYY-MM-DD;过去日期不发上游
|
|
101
|
+
if ((q.checkIn ? 1 : 0) !== (q.checkOut ? 1 : 0) || (q.checkIn && !/^\d{4}-\d{2}-\d{2}$/.test(q.checkIn))) {
|
|
102
|
+
return err('error', 'checkIn/checkOut 须成对且为 YYYY-MM-DD(未定档期可不带日期)');
|
|
103
|
+
}
|
|
104
|
+
if (q.checkIn) {
|
|
105
|
+
const now = new Date();
|
|
106
|
+
const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
|
|
107
|
+
if (q.checkIn < today || (q.checkOut ?? '') < q.checkIn) {
|
|
108
|
+
return err('error', `入住 ${q.checkIn}/退房 ${q.checkOut ?? ''} 不是未来合法区间(今天 ${today})——向用户确认日期后再查`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// 节律闸:超间隔即拒,不发起导航
|
|
112
|
+
const last = lastCallAt.get(site) ?? 0;
|
|
113
|
+
if (Date.now() - last < MIN_INTERVAL_MS) {
|
|
114
|
+
return err('cooldown', `rate limit: last call ${Date.now() - last}ms ago, min ${MIN_INTERVAL_MS}ms`);
|
|
115
|
+
}
|
|
116
|
+
lastCallAt.set(site, Date.now());
|
|
117
|
+
const entry = buildHotelEntryUrl({ to: q.to, cityId: q.cityId, checkIn: q.checkIn, checkOut: q.checkOut, adults: q.adults });
|
|
118
|
+
if (!entry.ok || !entry.url) {
|
|
119
|
+
return err('error', hotelCityUnresolvedHint(entry.unresolved ?? [q.to]));
|
|
120
|
+
}
|
|
121
|
+
const mode = resolveTransportMode(q.profileDir);
|
|
122
|
+
if (mode === 'extension') {
|
|
123
|
+
// ① 登录态快查(与机票同账号体系,票据 cookie 名一致)
|
|
124
|
+
const login = await extensionCookieNames({ site, domain: SITE_DOMAIN.replace(/^\./, ''), ticketNames: LOGIN_COOKIE_NAMES });
|
|
125
|
+
if (!login.ok) {
|
|
126
|
+
const verdict = classifyBridgeFailure(login.kind);
|
|
127
|
+
if (verdict === 'needs-extension') {
|
|
128
|
+
return { ok: false, via: 'session-ctrip-hotel-error', evidence: '[会话:ctrip-hotel-needs-extension@ts]', latencyMs: Date.now() - started, verdict, error: login.summary, installUrl: EXTENSION_STORE_URL, installAction: 'add-to-chrome' };
|
|
129
|
+
}
|
|
130
|
+
return err(verdict, login.summary);
|
|
131
|
+
}
|
|
132
|
+
if (login.tickets.length === 0 && !q.allowAnonymous) {
|
|
133
|
+
return err('needs-login', '未检出你本人登录态——调用 gotry_session_login 为用户打开携程登录入口(登录在携程官网完成;gotry 永不经手密码/验证码/cookie 值)');
|
|
134
|
+
}
|
|
135
|
+
// ② 检索 job:后台标签 + 被动嗅探(URL hint + 形状兜底;扩展零写行为)
|
|
136
|
+
const r = await extensionSearchJob({ site, url: entry.url, timeoutMs: q.timeoutMs, preferredOrigin: login.origin, preferredClientId: login.clientId });
|
|
137
|
+
appendExtensionAudit(q.auditPath, {
|
|
138
|
+
kind: 'extension-session-job', site, url: entry.url, jobId: 'search',
|
|
139
|
+
result: r.ok ? (r.timedOut ? 'timeout' : `body ${r.body.length}B title="${r.title.slice(0, 60)}"`) : `${r.kind}:${r.summary.slice(0, 120)}`,
|
|
140
|
+
});
|
|
141
|
+
if (!r.ok) {
|
|
142
|
+
const verdict = classifyBridgeFailure(r.kind);
|
|
143
|
+
if (verdict === 'needs-extension') {
|
|
144
|
+
return { ok: false, via: 'session-ctrip-hotel-error', evidence: '[会话:ctrip-hotel-needs-extension@ts]', latencyMs: Date.now() - started, verdict, error: r.summary, installUrl: EXTENSION_STORE_URL, installAction: 'add-to-chrome' };
|
|
145
|
+
}
|
|
146
|
+
return err(verdict, r.summary);
|
|
147
|
+
}
|
|
148
|
+
const title = r.title;
|
|
149
|
+
const head = r.body.slice(0, 5000);
|
|
150
|
+
if (CHALLENGE_RE.test(title + head)) {
|
|
151
|
+
return err('challenged', `风控/验证码命中(title=${title.slice(0, 60)});按红线不重试不绕过,交还用户`);
|
|
152
|
+
}
|
|
153
|
+
const hotels = parseCtripHotelList(r.body);
|
|
154
|
+
const verdict = hotels.length > 0 ? 'hit' : 'miss';
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
via: 'session-ctrip-hotel',
|
|
158
|
+
evidence: `[会话:${site}@${ts}] ${hotels.length} hotels;transport=extension(被动嗅探,零系统弹窗;扩展零写行为=物理只读)${q.allowAnonymous ? ';anonymous=自检态' : ''}`,
|
|
159
|
+
latencyMs: Date.now() - started,
|
|
160
|
+
verdict,
|
|
161
|
+
hotels,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// cdp/persistent 车道:与机票同构——挂监听等 NETWORK hint 回包(酒店用 HOTEL hints + 形状兜底)
|
|
165
|
+
const t = await openSession({ profileDir: q.profileDir, headless: q.headless, auditPath: q.auditPath, mode: mode === 'persistent' ? 'persistent' : 'cdp', newPage: true });
|
|
166
|
+
if (!t.ok) {
|
|
167
|
+
return err(classifyTransportFailure(t.summary, q.profileDir === undefined), t.summary);
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
const loggedIn = async () => {
|
|
171
|
+
const cookies = await t.browser.cookies().catch(() => []);
|
|
172
|
+
return cookies.some((c) => c.domain.includes(SITE_DOMAIN.replace(/^\./, '')) && LOGIN_COOKIE_NAMES.includes(c.name));
|
|
173
|
+
};
|
|
174
|
+
if (!(await loggedIn()) && !q.allowAnonymous) {
|
|
175
|
+
return err('needs-login', '未检出你本人登录态——调用 gotry_session_login 为用户打开携程登录入口(登录在携程官网完成;gotry 永不经手密码/验证码/cookie 值)');
|
|
176
|
+
}
|
|
177
|
+
let settled = false;
|
|
178
|
+
let body = '';
|
|
179
|
+
const heard = new Promise((resolve) => {
|
|
180
|
+
t.page.on('response', async (res) => {
|
|
181
|
+
if (settled)
|
|
182
|
+
return;
|
|
183
|
+
const u = res.url();
|
|
184
|
+
const hostHit = u.includes(HOTEL_SITE_HOST);
|
|
185
|
+
if (!hostHit)
|
|
186
|
+
return;
|
|
187
|
+
try {
|
|
188
|
+
const text = await res.text();
|
|
189
|
+
if (text && (hostHit && /hotels\.ctrip\.com\/(hotels\/api|domestic\/pc\/api)|GetHotelListBySOA|GetHotelListByCity|HotelSearch|hotelsearch/i.test(u) || text.length <= 2_000_000 && /"hotelList"|"hotelMatchInfos"|"hotelName"/.test(text))) {
|
|
190
|
+
body = text;
|
|
191
|
+
settled = true;
|
|
192
|
+
resolve();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch { /* 流式/竞态不可读则继续等下一个 */ }
|
|
196
|
+
});
|
|
197
|
+
setTimeout(() => resolve(), q.timeoutMs ?? 30_000);
|
|
198
|
+
});
|
|
199
|
+
await t.page.goto(entry.url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
200
|
+
await heard;
|
|
201
|
+
const title = await t.page.title().catch(() => '');
|
|
202
|
+
const headHtml = (await t.page.content().catch(() => '')).slice(0, 5000);
|
|
203
|
+
if (CHALLENGE_RE.test(title + headHtml)) {
|
|
204
|
+
return err('challenged', `风控/验证码命中(title=${title.slice(0, 60)});按红线不重试不绕过,交还用户`);
|
|
205
|
+
}
|
|
206
|
+
const hotels = parseCtripHotelList(body);
|
|
207
|
+
const verdict = hotels.length > 0 ? 'hit' : 'miss';
|
|
208
|
+
return {
|
|
209
|
+
ok: true,
|
|
210
|
+
via: 'session-ctrip-hotel',
|
|
211
|
+
evidence: `[会话:${site}@${ts}] ${hotels.length} hotels;guard blocked=${t.guard.blockedCount()}/${t.guard.requestCount()}${q.allowAnonymous ? ';anonymous=自检态' : ''}`,
|
|
212
|
+
latencyMs: Date.now() - started,
|
|
213
|
+
verdict,
|
|
214
|
+
hotels,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
catch (e) {
|
|
218
|
+
return err('error', e instanceof Error ? e.message.slice(0, 200) : String(e));
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
await t.close();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/** 员工侧登录指引(纯函数,测试锚点):账密永不经 gotry——酒店 platform
|
|
225
|
+
* 凭据在 hotel-be portal「自动登录」跳板完成,dida 侧人只补验证码 */
|
|
226
|
+
export function didaLoginHint() {
|
|
227
|
+
return '未检出你的 dida 登录态——在 hotel-be portal「供应商门户」页对该凭据点「自动登录」完成跳板登录(账密自动填充,你只补验证码);或调用 gotry_session_login(site=dida-portal)打开 dida 登录入口。登录在 dida 官网完成;gotry 永不经手密码/验证码/cookie 值';
|
|
228
|
+
}
|
|
229
|
+
/** dida 三类回包合并解析(推荐酒店 + 推荐价格 + 实时价;CDP 窗口收集与扩展 multiCollect 双车道同一合并语义) */
|
|
230
|
+
function mergeDidaRatesBodies(bodies) {
|
|
231
|
+
// 查询态(驱动门户目的地搜索)拿到的 SearchCache = 该城市+日期下的酒店报价面,优先级最高;
|
|
232
|
+
// 推荐流是首页口径,仅在拿不到查询态结果时兜底。
|
|
233
|
+
if (bodies.searchCache) {
|
|
234
|
+
const scoped = parseDidaSearchCache(bodies.searchCache);
|
|
235
|
+
if (scoped.length > 0)
|
|
236
|
+
return scoped;
|
|
237
|
+
}
|
|
238
|
+
const hotels = parseDidaRecommendHotels(bodies.hotels);
|
|
239
|
+
const recommendPrices = parseDidaPrices(bodies.recommendPrices);
|
|
240
|
+
const realtimeRates = parseDidaRates(bodies.realtime);
|
|
241
|
+
const pricesByHotel = new Map(recommendPrices.map((p) => [p.hotelId, p]));
|
|
242
|
+
const rates = [...realtimeRates];
|
|
243
|
+
for (const h of hotels) {
|
|
244
|
+
const p = pricesByHotel.get(h.hotelId);
|
|
245
|
+
rates.push({
|
|
246
|
+
hotelId: h.hotelId,
|
|
247
|
+
hotelName: h.name,
|
|
248
|
+
price: p?.price ?? 0,
|
|
249
|
+
...(p?.currency ? { currency: p.currency } : {}),
|
|
250
|
+
...(p?.priceDate ? { priceDate: p.priceDate } : {}),
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
return rates;
|
|
254
|
+
}
|
|
255
|
+
export async function sessionDidaSearch(q) {
|
|
256
|
+
const started = Date.now();
|
|
257
|
+
const ts = new Date().toISOString();
|
|
258
|
+
const site = 'dida-portal';
|
|
259
|
+
const err = (verdict, error) => ({
|
|
260
|
+
ok: false, via: 'session-dida-portal-error', evidence: `[会话:${site}@error@${ts}] ${error}`, latencyMs: Date.now() - started, verdict, error,
|
|
261
|
+
});
|
|
262
|
+
// 节律闸:超间隔即拒,不发起导航
|
|
263
|
+
const last = lastCallAt.get(site) ?? 0;
|
|
264
|
+
if (Date.now() - last < MIN_INTERVAL_MS) {
|
|
265
|
+
return err('cooldown', `rate limit: last call ${Date.now() - last}ms ago, min ${MIN_INTERVAL_MS}ms`);
|
|
266
|
+
}
|
|
267
|
+
lastCallAt.set(site, Date.now());
|
|
268
|
+
const entry = buildDidaEntryUrl({ entryUrl: q.entryUrl });
|
|
269
|
+
if (!entry.ok || !entry.url) {
|
|
270
|
+
return err('error', 'entry URL 必须落在 https://portal.dida.com/ 域内');
|
|
271
|
+
}
|
|
272
|
+
const mode = resolveTransportMode(q.profileDir);
|
|
273
|
+
if (mode === 'extension') {
|
|
274
|
+
// ① 登录态快查:票据 cookie 名存在性(名字级;协议面不存在值字段)。
|
|
275
|
+
// 20s 窗口:桥常在本调用前才拉起,扩展 SW 可能已按 MV3 30s 节律休眠——
|
|
276
|
+
// job 在桥上按 capability 排队等轮询者,窗口覆盖一次 alarm 唤醒周期。
|
|
277
|
+
const login = await extensionCookieNames({ site, domain: DIDA_SITE_DOMAIN, ticketNames: DIDA_LOGIN_COOKIE_NAMES, timeoutMs: 20_000 }, q.bridge);
|
|
278
|
+
if (!login.ok) {
|
|
279
|
+
const verdict = classifyBridgeFailure(login.kind);
|
|
280
|
+
if (verdict === 'needs-extension') {
|
|
281
|
+
return { ok: false, via: 'session-dida-portal-error', evidence: '[会话:dida-portal-needs-extension@ts]', latencyMs: Date.now() - started, verdict, error: login.summary, installUrl: EXTENSION_STORE_URL, installAction: 'add-to-chrome' };
|
|
282
|
+
}
|
|
283
|
+
return err(verdict, login.summary);
|
|
284
|
+
}
|
|
285
|
+
if (login.tickets.length === 0 && !q.allowAnonymous) {
|
|
286
|
+
return err('needs-login', didaLoginHint());
|
|
287
|
+
}
|
|
288
|
+
// ② 检索 job:后台标签 + 被动嗅探(URL hint + 形状兜底;扩展零写行为)。
|
|
289
|
+
// multiCollect:find 页自发 hotels+recommendPrices 双流,单首包语义丢一半(2026-09-11)
|
|
290
|
+
const r = await extensionSearchJob({ site, url: entry.url, timeoutMs: q.timeoutMs, multiCollect: true, preferredOrigin: login.origin, preferredClientId: login.clientId, ...(q.query ? { query: q.query } : {}) }, q.bridge);
|
|
291
|
+
appendExtensionAudit(q.auditPath, {
|
|
292
|
+
kind: 'extension-session-job', site, url: entry.url, jobId: 'search',
|
|
293
|
+
result: r.ok ? (r.timedOut ? 'timeout' : r.bodies ? `bodies hotels=${r.bodies.hotels?.length ?? 0}B prices=${r.bodies.recommendPrices?.length ?? 0}B` : `body ${r.body.length}B title="${r.title.slice(0, 60)}"`) : `${r.kind}:${r.summary.slice(0, 120)}`,
|
|
294
|
+
});
|
|
295
|
+
if (!r.ok) {
|
|
296
|
+
const verdict = classifyBridgeFailure(r.kind);
|
|
297
|
+
if (verdict === 'needs-extension') {
|
|
298
|
+
return { ok: false, via: 'session-dida-portal-error', evidence: '[会话:dida-portal-needs-extension@ts]', latencyMs: Date.now() - started, verdict, error: r.summary, installUrl: EXTENSION_STORE_URL, installAction: 'add-to-chrome' };
|
|
299
|
+
}
|
|
300
|
+
return err(verdict, r.summary);
|
|
301
|
+
}
|
|
302
|
+
const title = r.title ?? '';
|
|
303
|
+
// 挑战判定(2026-09-21 修正):扩展车道**只认页面侧信号**——页面标题 + content-bridge
|
|
304
|
+
// 按 DOM 判定的 challenge 标记。此前拿 `r.bodies?.hotels`(供应商响应体)扫「验证|captcha」,
|
|
305
|
+
// 会把酒店名(实测:「曼彻斯特市中心丽笙酒店,经过验证的净零酒店」)误判成风控,
|
|
306
|
+
// 导致 dida 通道恒 challenged(channel rates 永远取不到)。
|
|
307
|
+
if (r.challenge === true || CHALLENGE_RE.test(title)) {
|
|
308
|
+
return err('challenged', `风控/验证码命中(title=${title.slice(0, 60)});按红线不重试不绕过,交还用户`);
|
|
309
|
+
}
|
|
310
|
+
// multiCollect 分桶回包 → 与 CDP 车道同语义合并;旧扩展单首包 → 退化 parseDidaRates 兜底
|
|
311
|
+
const rates = r.bodies
|
|
312
|
+
? mergeDidaRatesBodies({ hotels: r.bodies.hotels ?? '', recommendPrices: r.bodies.recommendPrices ?? '', realtime: r.bodies.realtime ?? '', searchCache: r.bodies.searchCache ?? '' })
|
|
313
|
+
: parseDidaRates(r.body);
|
|
314
|
+
const verdict = rates.length > 0 ? 'hit' : 'miss';
|
|
315
|
+
return {
|
|
316
|
+
ok: true,
|
|
317
|
+
via: 'session-dida-portal',
|
|
318
|
+
evidence: `[会话:${site}@${ts}] ${rates.length} rates;transport=extension(被动嗅探,零系统弹窗;扩展零写行为=物理只读)${q.allowAnonymous ? ';anonymous=自检态' : ''}`,
|
|
319
|
+
latencyMs: Date.now() - started,
|
|
320
|
+
verdict,
|
|
321
|
+
rates,
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
// cdp/persistent 车道:与机/酒同构——挂监听等 DIDA hints 回包
|
|
325
|
+
const t = await openSession({ profileDir: q.profileDir, headless: q.headless, auditPath: q.auditPath, mode: mode === 'persistent' ? 'persistent' : 'cdp', newPage: true });
|
|
326
|
+
if (!t.ok) {
|
|
327
|
+
return err(classifyTransportFailure(t.summary, q.profileDir === undefined), t.summary);
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
const loggedIn = async () => {
|
|
331
|
+
const cookies = await t.browser.cookies().catch(() => []);
|
|
332
|
+
return cookies.some((c) => c.domain.includes(DIDA_SITE_DOMAIN) && DIDA_LOGIN_COOKIE_NAMES.includes(c.name));
|
|
333
|
+
};
|
|
334
|
+
if (!(await loggedIn()) && !q.allowAnonymous) {
|
|
335
|
+
return err('needs-login', didaLoginHint());
|
|
336
|
+
}
|
|
337
|
+
// 窗口式收集(2026-09-10 校准):find 页加载自发推荐流(酒店+价格两个接口)
|
|
338
|
+
// 与可能的实时价接口——按接口分类收 body,窗口结束统一合并解析。
|
|
339
|
+
const bodies = { hotels: '', recommendPrices: '', realtime: '' };
|
|
340
|
+
const collect = (text, url) => {
|
|
341
|
+
if (/SearchHomepageRecommendHotels/i.test(url))
|
|
342
|
+
bodies.hotels = text;
|
|
343
|
+
else if (/SearchHomepageRecommendPrices/i.test(url))
|
|
344
|
+
bodies.recommendPrices = text;
|
|
345
|
+
else if (/HotelPriceAPI\/SearchRealTime/i.test(url))
|
|
346
|
+
bodies.realtime = text;
|
|
347
|
+
};
|
|
348
|
+
const heard = new Promise((resolve) => {
|
|
349
|
+
t.page.on('response', async (res) => {
|
|
350
|
+
const u = res.url();
|
|
351
|
+
if (!DIDA_NETWORK_HINTS.some((re) => re.test(u)))
|
|
352
|
+
return;
|
|
353
|
+
try {
|
|
354
|
+
const text = await res.text();
|
|
355
|
+
if (text)
|
|
356
|
+
collect(text, u);
|
|
357
|
+
}
|
|
358
|
+
catch { /* 流式/竞态不可读则继续等下一个 */ }
|
|
359
|
+
});
|
|
360
|
+
setTimeout(resolve, q.timeoutMs ?? 30_000);
|
|
361
|
+
});
|
|
362
|
+
await t.page.goto(entry.url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
363
|
+
await heard;
|
|
364
|
+
const title = await t.page.title().catch(() => '');
|
|
365
|
+
const headHtml = (await t.page.content().catch(() => '')).slice(0, 5000);
|
|
366
|
+
if (CHALLENGE_RE.test(title + headHtml)) {
|
|
367
|
+
return err('challenged', `风控/验证码命中(title=${title.slice(0, 60)});按红线不重试不绕过,交还用户`);
|
|
368
|
+
}
|
|
369
|
+
const rates = mergeDidaRatesBodies(bodies);
|
|
370
|
+
// 有推荐酒店或有实时价即视为通道产出;纯无价酒店如实标注价格待询
|
|
371
|
+
const priced = rates.filter((r) => r.price > 0).length;
|
|
372
|
+
const verdict = rates.length > 0 ? 'hit' : 'miss';
|
|
373
|
+
return {
|
|
374
|
+
ok: true,
|
|
375
|
+
via: 'session-dida-portal',
|
|
376
|
+
evidence: `[会话:${site}@${ts}] ${rates.length} hotels(${priced} priced);guard blocked=${t.guard.blockedCount()}/${t.guard.requestCount()}${q.allowAnonymous ? ';anonymous=自检态' : ''}`,
|
|
17
377
|
latencyMs: Date.now() - started,
|
|
18
378
|
verdict,
|
|
19
|
-
|
|
379
|
+
rates,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
return err('error', e instanceof Error ? e.message.slice(0, 200) : String(e));
|
|
384
|
+
}
|
|
385
|
+
finally {
|
|
386
|
+
await t.close();
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/** 车站电报码表未收录时的人话指引(纯函数,测试锚点) */
|
|
390
|
+
export function trainStationUnresolvedHint(cities) {
|
|
391
|
+
return `城市 ${cities.join('/')} 不在 12306 城市电报码表——web 搜「12306 ${cities[0] ?? ''} 余票」拿到 kyfw 查询页 URL 里的 fs=城市,XXX 三位码,带 fromStationTelecode/toStationTelecode 重试`;
|
|
392
|
+
}
|
|
393
|
+
export async function sessionTrainSearch(q) {
|
|
394
|
+
const started = Date.now();
|
|
395
|
+
const ts = new Date().toISOString();
|
|
396
|
+
const site = 'train-12306';
|
|
397
|
+
const err = (verdict, error) => ({
|
|
398
|
+
ok: false, via: 'session-train-12306-error', evidence: `[会话:${site}@error@${ts}] ${error}`, latencyMs: Date.now() - started, verdict,
|
|
399
|
+
outcome: { kind: 'transport-runtime-error', reason: error }, error,
|
|
400
|
+
});
|
|
401
|
+
const parsedResult = (body, responseUrl, evidence, guardSuffix = '') => {
|
|
402
|
+
const outcome = parseLeftTicketQueryResult(body, entry.url, { maxItems: 30 });
|
|
403
|
+
if (outcome.kind === 'malformed') {
|
|
404
|
+
return {
|
|
405
|
+
ok: false,
|
|
406
|
+
via: 'session-train-12306-error',
|
|
407
|
+
evidence: `${evidence} typed-parse=malformed`,
|
|
408
|
+
latencyMs: Date.now() - started,
|
|
409
|
+
verdict: 'error',
|
|
410
|
+
outcome,
|
|
411
|
+
error: `12306 leftTicket/query 响应未通过 typed contract:${outcome.reason}`,
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
const request = resolveTrainQueryTelecodes({ from: q.from, to: q.to, date: q.date, fromStationTelecode: q.fromStationTelecode, toStationTelecode: q.toStationTelecode });
|
|
415
|
+
if (!request.ok) {
|
|
416
|
+
return {
|
|
417
|
+
ok: false,
|
|
418
|
+
via: 'session-train-12306-error',
|
|
419
|
+
evidence: `${evidence} typed-response-binding=unresolved`,
|
|
420
|
+
latencyMs: Date.now() - started,
|
|
421
|
+
verdict: 'error',
|
|
422
|
+
outcome,
|
|
423
|
+
trains: outcome.trains,
|
|
424
|
+
error: `12306 查询请求电报码未解析:${request.unresolved.join(',')}`,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
const response = validateTrainQueryResponseUrl(responseUrl, request.telecodes);
|
|
428
|
+
if (!response.ok) {
|
|
429
|
+
return {
|
|
430
|
+
ok: false,
|
|
431
|
+
via: 'session-train-12306-error',
|
|
432
|
+
evidence: `${evidence} typed-response-binding=invalid`,
|
|
433
|
+
latencyMs: Date.now() - started,
|
|
434
|
+
verdict: 'error',
|
|
435
|
+
outcome,
|
|
436
|
+
trains: outcome.trains,
|
|
437
|
+
error: `12306 leftTicket/query 响应未绑定本次请求:${response.reason}`,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const fetchedAt = new Date().toISOString();
|
|
441
|
+
const batchId = randomUUID();
|
|
442
|
+
const collection = {
|
|
443
|
+
requested: { from: q.from, to: q.to, date: q.date },
|
|
444
|
+
request: request.telecodes,
|
|
445
|
+
response: response.binding,
|
|
446
|
+
batchId,
|
|
447
|
+
queryId: `session:12306-train:${batchId}`,
|
|
448
|
+
fetchedAt,
|
|
449
|
+
};
|
|
450
|
+
const trains = outcome.trains;
|
|
451
|
+
return {
|
|
452
|
+
ok: true,
|
|
453
|
+
via: 'session-train-12306',
|
|
454
|
+
evidence: `${evidence} ${guardSuffix}`.trim(),
|
|
455
|
+
latencyMs: Date.now() - started,
|
|
456
|
+
verdict: outcome.kind === 'recognized-nonempty' ? 'hit' : 'miss',
|
|
457
|
+
outcome,
|
|
458
|
+
collection,
|
|
459
|
+
trains,
|
|
460
|
+
};
|
|
461
|
+
};
|
|
462
|
+
// 节律闸:超间隔即拒,不发起导航
|
|
463
|
+
const last = lastCallAt.get(site) ?? 0;
|
|
464
|
+
if (Date.now() - last < MIN_INTERVAL_MS) {
|
|
465
|
+
return err('cooldown', `rate limit: last call ${Date.now() - last}ms ago, min ${MIN_INTERVAL_MS}ms`);
|
|
466
|
+
}
|
|
467
|
+
lastCallAt.set(site, Date.now());
|
|
468
|
+
const entry = buildTrainEntryUrl({ from: q.from, to: q.to, date: q.date, fromStationTelecode: q.fromStationTelecode, toStationTelecode: q.toStationTelecode });
|
|
469
|
+
if (!entry.ok || !entry.url) {
|
|
470
|
+
return err('error', trainStationUnresolvedHint(entry.unresolved ?? [q.from, q.to]));
|
|
471
|
+
}
|
|
472
|
+
const mode = resolveTransportMode(q.profileDir);
|
|
473
|
+
if (mode === 'extension') {
|
|
474
|
+
// 无登录闸(公开查询面):直接发起检索 job
|
|
475
|
+
const r = await extensionSearchJob({ site, url: entry.url, timeoutMs: q.timeoutMs });
|
|
476
|
+
appendExtensionAudit(q.auditPath, {
|
|
477
|
+
kind: 'extension-session-job', site, url: entry.url, jobId: 'search',
|
|
478
|
+
result: r.ok ? (r.timedOut ? 'timeout' : `body ${r.body.length}B title="${r.title.slice(0, 60)}"`) : `${r.kind}:${r.summary.slice(0, 120)}`,
|
|
479
|
+
});
|
|
480
|
+
if (!r.ok) {
|
|
481
|
+
const verdict = classifyBridgeFailure(r.kind);
|
|
482
|
+
if (verdict === 'needs-extension') {
|
|
483
|
+
return { ok: false, via: 'session-train-12306-error', evidence: '[会话:train-12306-needs-extension@ts]', latencyMs: Date.now() - started, verdict, outcome: { kind: 'transport-runtime-error', reason: r.summary }, error: r.summary, installUrl: EXTENSION_STORE_URL, installAction: 'add-to-chrome' };
|
|
484
|
+
}
|
|
485
|
+
return err(verdict, r.summary);
|
|
486
|
+
}
|
|
487
|
+
const title = r.title;
|
|
488
|
+
const head = r.body.slice(0, 5000);
|
|
489
|
+
if (CHALLENGE_RE.test(title + head)) {
|
|
490
|
+
return err('challenged', `风控/验证码命中(title=${title.slice(0, 60)});按红线不重试不绕过,交还用户`);
|
|
491
|
+
}
|
|
492
|
+
return parsedResult(r.body, r.url, `[会话:${site}@${ts}] transport=extension(公开查询面,被动嗅探,零系统弹窗;扩展零写行为=物理只读)`);
|
|
493
|
+
}
|
|
494
|
+
// cdp/persistent 车道:与机/酒同构
|
|
495
|
+
const t = await (trainSessionTransportForTest
|
|
496
|
+
? trainSessionTransportForTest()
|
|
497
|
+
: openSession({ profileDir: q.profileDir, headless: q.headless, auditPath: q.auditPath, mode: mode === 'persistent' ? 'persistent' : 'cdp', newPage: true }));
|
|
498
|
+
if (!t.ok) {
|
|
499
|
+
return err(classifyTransportFailure(t.summary, q.profileDir === undefined), t.summary);
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
let settled = false;
|
|
503
|
+
let body = '';
|
|
504
|
+
let responseUrl = '';
|
|
505
|
+
const heard = new Promise((resolve) => {
|
|
506
|
+
t.page.on('response', async (res) => {
|
|
507
|
+
if (settled)
|
|
508
|
+
return;
|
|
509
|
+
const u = res.url();
|
|
510
|
+
if (!/leftTicket\/query/i.test(u))
|
|
511
|
+
return;
|
|
512
|
+
try {
|
|
513
|
+
const text = await res.text();
|
|
514
|
+
if (text) {
|
|
515
|
+
body = text;
|
|
516
|
+
responseUrl = u;
|
|
517
|
+
settled = true;
|
|
518
|
+
resolve();
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
catch { /* 流式/竞态不可读则继续等下一个 */ }
|
|
522
|
+
});
|
|
523
|
+
setTimeout(() => resolve(), q.timeoutMs ?? 25_000);
|
|
20
524
|
});
|
|
525
|
+
await t.page.goto(entry.url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
526
|
+
await heard;
|
|
527
|
+
const title = await t.page.title().catch(() => '');
|
|
528
|
+
const headHtml = (await t.page.content().catch(() => '')).slice(0, 5000);
|
|
529
|
+
if (CHALLENGE_RE.test(title + headHtml)) {
|
|
530
|
+
return err('challenged', `风控/验证码命中(title=${title.slice(0, 60)});按红线不重试不绕过,交还用户`);
|
|
531
|
+
}
|
|
532
|
+
return parsedResult(body, responseUrl, `[会话:${site}@${ts}] 12306 公开查询面`, `guard blocked=${t.guard.blockedCount()}/${t.guard.requestCount()}`);
|
|
533
|
+
}
|
|
534
|
+
catch (e) {
|
|
535
|
+
return err('error', e instanceof Error ? e.message.slice(0, 200) : String(e));
|
|
536
|
+
}
|
|
537
|
+
finally {
|
|
538
|
+
await t.close();
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
export async function sessionFlightSearch(q) {
|
|
542
|
+
const started = Date.now();
|
|
543
|
+
const ts = new Date().toISOString();
|
|
544
|
+
const site = 'ctrip-flight';
|
|
545
|
+
const err = (verdict, error, transportShape) => ({
|
|
546
|
+
ok: false, via: 'session-ctrip-flight-error', evidence: `[会话:${site}@error@${ts}] ${error}`, latencyMs: Date.now() - started, verdict, error,
|
|
547
|
+
...(transportShape ? { transportShape } : {}),
|
|
548
|
+
});
|
|
549
|
+
// 节律闸:超间隔即拒,不发起导航
|
|
21
550
|
const last = lastCallAt.get(site) ?? 0;
|
|
22
551
|
if (Date.now() - last < MIN_INTERVAL_MS) {
|
|
23
552
|
return err('cooldown', `rate limit: last call ${Date.now() - last}ms ago, min ${MIN_INTERVAL_MS}ms`);
|
|
@@ -27,69 +556,131 @@ export async function sessionFlightSearch(q) {
|
|
|
27
556
|
if (!entry.ok || !entry.url) {
|
|
28
557
|
return err('error', `unresolved entry: ${(entry.unresolved ?? []).join('/')} 不在城市码表`);
|
|
29
558
|
}
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
559
|
+
const mode = resolveTransportMode(q.profileDir);
|
|
560
|
+
if (mode === 'extension') {
|
|
561
|
+
// ① 登录态快查:票据 cookie 名存在性(免标签页,秒回;needs-login 不再先付一次导航成本)
|
|
562
|
+
const login = await extensionCookieNames({ site, domain: SITE_DOMAIN.replace(/^\./, ''), ticketNames: LOGIN_COOKIE_NAMES });
|
|
563
|
+
if (!login.ok) {
|
|
564
|
+
const verdict = classifyBridgeFailure(login.kind);
|
|
565
|
+
const summary = flightBridgeFailureSummary(login.kind);
|
|
566
|
+
if (verdict === 'needs-extension') {
|
|
567
|
+
return { ok: false, via: 'session-ctrip-flight-error', evidence: '[会话:ctrip-flight-needs-extension@ts]', latencyMs: Date.now() - started, verdict, error: summary, transportShape: 'precheck_unavailable', installUrl: EXTENSION_STORE_URL, installAction: 'add-to-chrome' };
|
|
568
|
+
}
|
|
569
|
+
return err(verdict, summary, 'precheck_unavailable');
|
|
570
|
+
}
|
|
571
|
+
// 登录态闸:用户自己的账号;匿名默认拒(allowAnonymous 仅链路自检且证据标自检态)
|
|
572
|
+
if (login.tickets.length === 0 && !q.allowAnonymous) {
|
|
573
|
+
return err('needs-login', '未检出你本人登录态——调用 gotry_session_login 为用户打开携程登录入口(登录在携程官网完成;gotry 永不经手密码/验证码/cookie 值)', 'needs_login');
|
|
574
|
+
}
|
|
575
|
+
// ② 检索 job:后台标签 + MAIN-world 被动嗅探(检索请求由站点自己发出,扩展零写行为)
|
|
576
|
+
const r = await extensionSearchJob({ site, url: entry.url, timeoutMs: q.timeoutMs, preferredOrigin: login.origin, preferredClientId: login.clientId });
|
|
577
|
+
const auditShape = (shape) => appendExtensionAudit(q.auditPath, {
|
|
578
|
+
kind: 'extension-session-job', site, url: 'https://flights.ctrip.com', jobId: 'search', result: shape,
|
|
579
|
+
});
|
|
580
|
+
if (!r.ok) {
|
|
581
|
+
auditShape('search_job_unavailable');
|
|
582
|
+
const verdict = classifyBridgeFailure(r.kind);
|
|
583
|
+
const summary = flightBridgeFailureSummary(r.kind);
|
|
584
|
+
if (verdict === 'needs-extension') {
|
|
585
|
+
return { ok: false, via: 'session-ctrip-flight-error', evidence: '[会话:ctrip-flight-needs-extension@ts]', latencyMs: Date.now() - started, verdict, error: summary, transportShape: 'search_job_unavailable', installUrl: EXTENSION_STORE_URL, installAction: 'add-to-chrome' };
|
|
586
|
+
}
|
|
587
|
+
return err(verdict, summary, 'search_job_unavailable');
|
|
588
|
+
}
|
|
589
|
+
const title = r.title;
|
|
590
|
+
const head = r.body.slice(0, 5000);
|
|
591
|
+
if (CHALLENGE_RE.test(title + head)) {
|
|
592
|
+
auditShape('challenge_page');
|
|
593
|
+
return err('challenged', '风控/验证码命中;按红线不重试不绕过,交还用户', 'challenge_page');
|
|
594
|
+
}
|
|
595
|
+
if (r.timedOut) {
|
|
596
|
+
auditShape('sniff_timeout');
|
|
597
|
+
return err('error', 'batchSearch 嗅探超时:页面未收到可解析回包;本次检索未完成', 'sniff_timeout');
|
|
598
|
+
}
|
|
599
|
+
const parsed = parseBatchSearchResult(r.body);
|
|
600
|
+
auditShape(parsed.shape);
|
|
601
|
+
if (parsed.verdict === 'error') {
|
|
602
|
+
return err('error', `batchSearch 响应形状异常(${parsed.shape};非合法空响应)`, parsed.shape);
|
|
603
|
+
}
|
|
604
|
+
const options = parsed.options;
|
|
605
|
+
const verdict = parsed.verdict === 'hit' ? 'hit' : 'miss';
|
|
606
|
+
return {
|
|
607
|
+
ok: true,
|
|
608
|
+
via: 'session-ctrip-flight',
|
|
609
|
+
evidence: `[会话:${site}@${ts}] ${options.length} options;transport=extension(被动嗅探,零系统弹窗;扩展零写行为=物理只读)${q.allowAnonymous ? ';anonymous=自检态' : ''}`,
|
|
610
|
+
latencyMs: Date.now() - started,
|
|
611
|
+
verdict,
|
|
612
|
+
transportShape: parsed.shape,
|
|
613
|
+
options,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
// 人机共治纪律:检索一律开自己的新标签页(绝不劫持用户已有页面),用完关自己的页
|
|
617
|
+
// cdp 车道(显式 GOTRY_SESSION_TRANSPORT=cdp opt-in)与 persistent(测试隔离 profile)
|
|
618
|
+
const t = await openSession({ profileDir: q.profileDir, headless: q.headless, auditPath: q.auditPath, mode: mode === 'persistent' ? 'persistent' : 'cdp', newPage: true });
|
|
36
619
|
if (!t.ok) {
|
|
37
|
-
|
|
38
|
-
|
|
620
|
+
// cdp 未开端口或握手失败 → needs-attach(一次性用户动作);persistent 启动失败仍走 error。
|
|
621
|
+
// transport 的“端口未开”在连接前返回,文案不含 `cdp attach 失败`,两种形态都要归入同一用户门禁。
|
|
622
|
+
return err(classifyTransportFailure(t.summary, q.profileDir === undefined), t.summary);
|
|
39
623
|
}
|
|
40
624
|
try {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return cookies.some((c)=>LOGIN_COOKIE_NAMES.includes(c.name));
|
|
625
|
+
// 登录态闸:用户自己的账号,不是匿名实例——匿名态按 onAnonymous 处置(默认 fail)
|
|
626
|
+
const loggedIn = async () => {
|
|
627
|
+
const cookies = await t.browser.cookies().catch(() => []);
|
|
628
|
+
return cookies.some((c) => c.domain.includes(SITE_DOMAIN.replace(/^\./, '')) && LOGIN_COOKIE_NAMES.includes(c.name));
|
|
46
629
|
};
|
|
47
|
-
if (!await loggedIn() && !q.allowAnonymous) {
|
|
48
|
-
return err('needs-login', '
|
|
630
|
+
if (!(await loggedIn()) && !q.allowAnonymous) {
|
|
631
|
+
return err('needs-login', '未检出你本人登录态——调用 gotry_session_login 为用户打开携程登录入口(登录在携程官网完成;gotry 永不经手密码/验证码/cookie 值)');
|
|
49
632
|
}
|
|
633
|
+
// 先挂监听再导航(Playwright network 模式):命中 networkHint 的第一个响应即搜索回包
|
|
50
634
|
let settled = false;
|
|
51
635
|
let body = '';
|
|
52
|
-
const heard = new Promise((resolve)=>{
|
|
53
|
-
t.page.on('response', async (res)=>{
|
|
54
|
-
if (settled)
|
|
636
|
+
const heard = new Promise((resolve) => {
|
|
637
|
+
t.page.on('response', async (res) => {
|
|
638
|
+
if (settled)
|
|
639
|
+
return;
|
|
55
640
|
const u = res.url();
|
|
56
|
-
if (!NETWORK_HINTS.some((re)=>re.test(u)))
|
|
641
|
+
if (!NETWORK_HINTS.some((re) => re.test(u)))
|
|
642
|
+
return;
|
|
57
643
|
try {
|
|
58
644
|
body = await res.text();
|
|
59
|
-
}
|
|
645
|
+
}
|
|
646
|
+
catch { /* 流式/竞态不可读则继续等下一个 */ }
|
|
60
647
|
if (body) {
|
|
61
648
|
settled = true;
|
|
62
649
|
resolve();
|
|
63
650
|
}
|
|
64
651
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
await t.page.goto(entry.url, {
|
|
68
|
-
waitUntil: 'domcontentloaded',
|
|
69
|
-
timeout: 30_000
|
|
652
|
+
// 超时也结算:miss/error 由后续判定
|
|
653
|
+
setTimeout(() => resolve(), q.timeoutMs ?? 25_000);
|
|
70
654
|
});
|
|
655
|
+
await t.page.goto(entry.url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
|
|
71
656
|
await heard;
|
|
72
|
-
const title = await t.page.title().catch(()=>'');
|
|
73
|
-
const headHtml = (await t.page.content().catch(()=>'')).slice(0, 5000);
|
|
657
|
+
const title = await t.page.title().catch(() => '');
|
|
658
|
+
const headHtml = (await t.page.content().catch(() => '')).slice(0, 5000);
|
|
74
659
|
if (CHALLENGE_RE.test(title + headHtml)) {
|
|
75
|
-
return err('challenged',
|
|
660
|
+
return err('challenged', '风控/验证码命中;按红线不重试不绕过,交还用户', 'challenge_page');
|
|
661
|
+
}
|
|
662
|
+
if (body.length === 0)
|
|
663
|
+
return err('error', 'batchSearch 嗅探超时:页面未收到可解析回包;本次检索未完成', 'sniff_timeout');
|
|
664
|
+
const parsed = parseBatchSearchResult(body);
|
|
665
|
+
if (parsed.verdict === 'error') {
|
|
666
|
+
return err('error', `batchSearch 响应形状异常(${parsed.shape};非合法空响应)`, parsed.shape);
|
|
76
667
|
}
|
|
77
|
-
const options =
|
|
78
|
-
const verdict =
|
|
668
|
+
const options = parsed.options;
|
|
669
|
+
const verdict = parsed.verdict === 'hit' ? 'hit' : 'miss';
|
|
79
670
|
return {
|
|
80
671
|
ok: true,
|
|
81
672
|
via: 'session-ctrip-flight',
|
|
82
673
|
evidence: `[会话:${site}@${ts}] ${options.length} options;guard blocked=${t.guard.blockedCount()}/${t.guard.requestCount()}${q.allowAnonymous ? ';anonymous=自检态' : ''}`,
|
|
83
674
|
latencyMs: Date.now() - started,
|
|
84
675
|
verdict,
|
|
85
|
-
|
|
676
|
+
transportShape: parsed.shape,
|
|
677
|
+
options,
|
|
86
678
|
};
|
|
87
|
-
}
|
|
679
|
+
}
|
|
680
|
+
catch (e) {
|
|
88
681
|
return err('error', e instanceof Error ? e.message.slice(0, 200) : String(e));
|
|
89
|
-
}
|
|
682
|
+
}
|
|
683
|
+
finally {
|
|
90
684
|
await t.close();
|
|
91
685
|
}
|
|
92
686
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/capabilities/session-search.ts
|