@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
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扩展健康检查 + 自动重放(issue #21 onboarding UX,P3.6 gotry-session-onboarding-goal,
|
|
3
|
+
* RFC docs/rfc/user-session-data-rfc.md §3.3)。
|
|
4
|
+
*
|
|
5
|
+
* 动机:首次 `sessionFlightSearch` 遇 `needs-extension` 时,旧版要求用户装完扩展后**自己重跑命令**;
|
|
6
|
+
* 本模块把「用户手工重跑」改成「后台有界轮询 + 扩展一就位自动重放同一 query_id」——
|
|
7
|
+
* 用户装完扩展零后续动作,gotry 自己感知到 ready 后立刻跑同一条 query。
|
|
8
|
+
*
|
|
9
|
+
* 形态:
|
|
10
|
+
* - `startExtensionHealthWatch({ timeoutMs, intervalMs, probe })` 启后台轮询。
|
|
11
|
+
* probe 是异步检测函数(默认 GET http://127.0.0.1:{port}/status,扩展 ≤30s 心跳一次,
|
|
12
|
+
* 1.5 倍容差 EXTENSION_CONNECTED_WINDOW_MS=45s;§38 已经验证扩展心跳节奏)。
|
|
13
|
+
* - 命中 ready → 调用方自动重放(query_id / args 不变;同一证据文件覆盖)。
|
|
14
|
+
* - 超时(timeoutMs 默认 120_000)→ 返回 'timeout',调用方降级回 `needs-extension` 错误面。
|
|
15
|
+
*
|
|
16
|
+
* 离线合同(run-all §40 onboarding-tests 5/5):
|
|
17
|
+
* - 三时序分支:probe 0ms ready / 中途 ready / 120s+1ms 超时;
|
|
18
|
+
* - 同 query_id 重放语义(参数透传);
|
|
19
|
+
* - 取消语义(timeoutMs=0 立即 timeout;cancel() 立即返回 'cancelled');
|
|
20
|
+
* - 重放上限(防止 watch 反复 ready/抖动导致无限重放;maxRetries 默认 1)。
|
|
21
|
+
*
|
|
22
|
+
* 纪律:
|
|
23
|
+
* - 不引 GUI 依赖;只 spawn `node:http` 探活(已经在扩展桥验证过,§38 24 断言覆盖);
|
|
24
|
+
* - 不持锁、不轮询文件、不读 dsh-runtime 共享状态;
|
|
25
|
+
* - 不污染扩展桥 single-instance;probe 走独立短连接。
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { request } from 'node:http'
|
|
29
|
+
import {
|
|
30
|
+
BRIDGE_PORTS,
|
|
31
|
+
EXTENSION_CONNECTED_WINDOW_MS,
|
|
32
|
+
type SessionBridgeOptions,
|
|
33
|
+
} from './extension-bridge.ts'
|
|
34
|
+
|
|
35
|
+
export type HealthWatchOutcome =
|
|
36
|
+
| { ready: true; attempts: number; waitedMs: number }
|
|
37
|
+
| { ready: false; reason: 'timeout' | 'cancelled'; attempts: number; waitedMs: number }
|
|
38
|
+
|
|
39
|
+
export interface HealthWatchOptions extends SessionBridgeOptions {
|
|
40
|
+
/** 最长等多久(默认 120s);0 = 立即 timeout(便于测试) */
|
|
41
|
+
timeoutMs?: number
|
|
42
|
+
/** 探活间隔(默认 5s);必须 < EXTENSION_CONNECTED_WINDOW_MS,否则抖动期漏心跳 */
|
|
43
|
+
intervalMs?: number
|
|
44
|
+
/** 探活函数:返回 true 即视为扩展就绪(测试可注入 fake probe)。
|
|
45
|
+
* 默认走双因子:① 心跳 `/status` extensionConnected === true
|
|
46
|
+
* + ② 业务探针 cookie-names 真回包(timeout 5s);二者同时通过才算真 ready。
|
|
47
|
+
* 仅心跳通过但 cookie-names 失败 = 扩展 SW 跑着但 chrome.cookies 权限缺 → 不算 ready。 */
|
|
48
|
+
probe?: () => Promise<boolean>
|
|
49
|
+
/** 最大重放次数(默认 1);防止 ready 抖动反复触发 query 重放 */
|
|
50
|
+
maxRetries?: number
|
|
51
|
+
/** 取消信号(可选)——给调用方提供立刻终止 watch 的逃生口 */
|
|
52
|
+
signal?: AbortSignal
|
|
53
|
+
/** 时间源(测试可注入 fake clock) */
|
|
54
|
+
now?: () => number
|
|
55
|
+
/**
|
|
56
|
+
* 是否启用业务探针(cookie-names)。wizard 默认 true(用户刚装扩展,首次需要双因子验证);
|
|
57
|
+
* 上线 session 检索可传 false(快速心跳足矣,业务探针会另发一次请求)。
|
|
58
|
+
*/
|
|
59
|
+
businessProbe?: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface HealthWatchController {
|
|
63
|
+
/** Promise:resolve 时扩展已 ready,可执行重放 */
|
|
64
|
+
waitReady(): Promise<HealthWatchOutcome>
|
|
65
|
+
/** 立刻取消;waitReady 立即 resolve {ready:false, reason:'cancelled'} */
|
|
66
|
+
cancel(reason?: string): void
|
|
67
|
+
/** 当前 ready 次数(供诊断/审计) */
|
|
68
|
+
readonly readyCount: number
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface InternalState {
|
|
72
|
+
cancelled: boolean
|
|
73
|
+
readyCount: number
|
|
74
|
+
resolveOutcome: ((o: HealthWatchOutcome) => void) | null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 双因子 probe(默认):心跳 + 业务探针(cookie-names)。
|
|
79
|
+
* 心跳通过只能证 SW 跑着;业务探针通过证 chrome.cookies 权限齐——二者同过才算
|
|
80
|
+
* 扩展真正 ready。wizard 必须双因子(用户刚装的扩展可能权限不全),上线 session
|
|
81
|
+
* 检索可传 `businessProbe:false` 跳业务探针(避免每次检索额外打一次 cookie-names)。
|
|
82
|
+
*
|
|
83
|
+
* 不直接 import extension-channel.ts(避免环)—用 dynamic import 懒拿。
|
|
84
|
+
*/
|
|
85
|
+
export async function combinedProbe(opts: HealthWatchOptions, businessProbe: boolean): Promise<boolean> {
|
|
86
|
+
if (!(await defaultProbe(opts))) return false
|
|
87
|
+
if (!businessProbe) return true
|
|
88
|
+
try {
|
|
89
|
+
// 动态 import 避免 health-watch ↔ extension-channel 双向环
|
|
90
|
+
const channel = await import('./extension-channel.ts')
|
|
91
|
+
const bridge = await import('./extension-bridge.ts')
|
|
92
|
+
// 双因子探针需要常驻桥(wizard 期间):keepBridge=true 透传到 bridge 单例
|
|
93
|
+
bridge.getOrCreateSessionBridge({ keepBridge: opts.keepBridge === true })
|
|
94
|
+
// 心跳已 ready(刚 defaultProbe 通过)= 扩展一定在线;cookie-names 超时给 8s,
|
|
95
|
+
// 比心跳间隔 5s 长——保证 tick 间不会撞死 cookie job
|
|
96
|
+
const cookie = await channel.extensionCookieNames({
|
|
97
|
+
site: 'ctrip-flight',
|
|
98
|
+
domain: 'ctrip.com',
|
|
99
|
+
ticketNames: ['cticket', 'uid'],
|
|
100
|
+
timeoutMs: 8_000,
|
|
101
|
+
})
|
|
102
|
+
// cookie-names.ok = true 表示扩展 SW 真能调 chrome.cookies.getAll(权限齐全);
|
|
103
|
+
// cookie-names.ok = true + names = [] 也算 ok(用户没登携程但扩展权限齐)。
|
|
104
|
+
// cookie-names.ok = false 分两类:bridge-unavailable/extension-not-connected(心跳 false 时) / timeout(扩展没回包)——
|
|
105
|
+
// 后者更可能是 SW 卡了,不应当 ready。但 wizard 必须严格:让用户明确知道权限缺或登录缺。
|
|
106
|
+
return cookie.ok
|
|
107
|
+
} catch {
|
|
108
|
+
return false
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 默认 probe:GET http://127.0.0.1:{port}/status,看 extensionConnected: true
|
|
114
|
+
* (扩展心跳 ≤30s 一刷,窗口 EXTENSION_CONNECTED_WINDOW_MS=45s 容差)。
|
|
115
|
+
* 不在端口池内挑一个试——端口是已启动桥的事实,直接拿 opts.ports[0] 或 8791。
|
|
116
|
+
* 测试可注入 fake probe 跳过网络。
|
|
117
|
+
*/
|
|
118
|
+
export async function defaultProbe(opts: HealthWatchOptions): Promise<boolean> {
|
|
119
|
+
const ports = opts.ports ?? [...BRIDGE_PORTS]
|
|
120
|
+
for (const port of ports) {
|
|
121
|
+
const ok = await new Promise<boolean>((resolve) => {
|
|
122
|
+
const req = request(
|
|
123
|
+
{ host: '127.0.0.1', port, path: '/status', method: 'GET', timeout: 2_000 },
|
|
124
|
+
(res) => {
|
|
125
|
+
if (!res.statusCode || res.statusCode >= 400) { resolve(false); return }
|
|
126
|
+
let buf = ''
|
|
127
|
+
res.setEncoding('utf8')
|
|
128
|
+
res.on('data', (c: string) => { buf += c })
|
|
129
|
+
res.on('end', () => {
|
|
130
|
+
try {
|
|
131
|
+
const payload = JSON.parse(buf) as { extensionConnected?: boolean }
|
|
132
|
+
resolve(payload.extensionConnected === true)
|
|
133
|
+
} catch { resolve(false) }
|
|
134
|
+
})
|
|
135
|
+
res.on('error', () => resolve(false))
|
|
136
|
+
},
|
|
137
|
+
)
|
|
138
|
+
req.on('error', () => resolve(false))
|
|
139
|
+
req.on('timeout', () => { req.destroy(); resolve(false) })
|
|
140
|
+
req.end()
|
|
141
|
+
})
|
|
142
|
+
if (ok) return true
|
|
143
|
+
}
|
|
144
|
+
return false
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function startExtensionHealthWatch(opts: HealthWatchOptions = {}): HealthWatchController {
|
|
148
|
+
const timeoutMs = opts.timeoutMs ?? 120_000
|
|
149
|
+
const intervalMs = opts.intervalMs ?? 5_000
|
|
150
|
+
const maxRetries = opts.maxRetries ?? 1
|
|
151
|
+
const businessProbe = opts.businessProbe ?? true
|
|
152
|
+
// 关键:wizard 期间需要常驻桥(keepBridge=true 透传到 getOrCreateSessionBridge),
|
|
153
|
+
// 否则启桥后 wizard main 一退桥就退,扩展心跳发不进来。
|
|
154
|
+
// 用 dynamic import 避免与 extension-bridge 顶层环(channel 也引 bridge,但 health-watch 引 channel/bridge 双向会死循环)
|
|
155
|
+
const probe = opts.probe ?? (async () => {
|
|
156
|
+
// 先确保桥存在 + keepBridge
|
|
157
|
+
const bridge = await import('./extension-bridge.ts')
|
|
158
|
+
const created = await bridge.getOrCreateSessionBridge({ keepBridge: opts.keepBridge === true, ports: opts.ports })
|
|
159
|
+
// The default path must keep scanning the canonical pool: another GoTry
|
|
160
|
+
// process may already own the extension-connected bridge. An explicit
|
|
161
|
+
// port override is an isolation boundary, so probe only the bridge that
|
|
162
|
+
// this process actually bound (including an ephemeral `0` request).
|
|
163
|
+
const probeOpts = created.ok && opts.ports
|
|
164
|
+
? { ...opts, ports: [created.bridge.port] }
|
|
165
|
+
: opts
|
|
166
|
+
return combinedProbe(probeOpts, businessProbe)
|
|
167
|
+
})
|
|
168
|
+
const now = opts.now ?? Date.now
|
|
169
|
+
const state: InternalState = { cancelled: false, readyCount: 0, resolveOutcome: null }
|
|
170
|
+
|
|
171
|
+
const finish = (outcome: HealthWatchOutcome): void => {
|
|
172
|
+
if (state.resolveOutcome) {
|
|
173
|
+
const resolve = state.resolveOutcome
|
|
174
|
+
state.resolveOutcome = null
|
|
175
|
+
resolve(outcome)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// 取消信号 -> 立刻 timeout-ish 终态(不进 ready 路径)
|
|
180
|
+
const onAbort = (): void => {
|
|
181
|
+
state.cancelled = true
|
|
182
|
+
finish({ ready: false, reason: 'cancelled', attempts: state.readyCount, waitedMs: 0 })
|
|
183
|
+
}
|
|
184
|
+
opts.signal?.addEventListener('abort', onAbort, { once: true })
|
|
185
|
+
|
|
186
|
+
const waitReady = (): Promise<HealthWatchOutcome> => new Promise<HealthWatchOutcome>((resolve) => {
|
|
187
|
+
if (state.cancelled) { resolve({ ready: false, reason: 'cancelled', attempts: 0, waitedMs: 0 }); return }
|
|
188
|
+
if (timeoutMs === 0) { resolve({ ready: false, reason: 'timeout', attempts: 0, waitedMs: 0 }); return }
|
|
189
|
+
state.resolveOutcome = resolve
|
|
190
|
+
const deadline = now() + timeoutMs
|
|
191
|
+
let attempts = 0
|
|
192
|
+
let waitedMs = 0
|
|
193
|
+
|
|
194
|
+
const tick = async (): Promise<void> => {
|
|
195
|
+
if (state.cancelled || state.resolveOutcome === null) return
|
|
196
|
+
attempts += 1
|
|
197
|
+
try {
|
|
198
|
+
const ready = await probe()
|
|
199
|
+
if (ready && !state.cancelled) {
|
|
200
|
+
state.readyCount += 1
|
|
201
|
+
waitedMs = timeoutMs - Math.max(0, deadline - now())
|
|
202
|
+
// readyCount > maxRetries → 视为抖动,继续等(避免一次性重放洪流)
|
|
203
|
+
if (state.readyCount <= maxRetries) {
|
|
204
|
+
finish({ ready: true, attempts, waitedMs: waitedMs > 0 ? waitedMs : 0 })
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} catch { /* probe 失败继续轮询,不挡主流程 */ }
|
|
209
|
+
waitedMs = timeoutMs - Math.max(0, deadline - now())
|
|
210
|
+
if (now() >= deadline) {
|
|
211
|
+
finish({ ready: false, reason: 'timeout', attempts, waitedMs })
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
// 间隔 = intervalMs;若剩余时间 < intervalMs 则最后一次等剩余时间
|
|
215
|
+
const remaining = deadline - now()
|
|
216
|
+
const delay = Math.min(intervalMs, Math.max(0, remaining))
|
|
217
|
+
if (delay <= 0) {
|
|
218
|
+
finish({ ready: false, reason: 'timeout', attempts, waitedMs })
|
|
219
|
+
return
|
|
220
|
+
}
|
|
221
|
+
setTimeout(tick, delay)
|
|
222
|
+
}
|
|
223
|
+
void tick()
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
waitReady,
|
|
228
|
+
cancel: () => onAbort(),
|
|
229
|
+
get readyCount() { return state.readyCount },
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 自动重放 wrapper:首次调用走 fetch(返回 needs-extension) → 启 watch →
|
|
235
|
+
* 扩展 ready → 同 query 重放。语义:用户侧**只看到一条 query 的最终结果**;
|
|
236
|
+
* 中间产物(query_id 标记 + 尝试次数)落 evidence 文件,便于离线追溯。
|
|
237
|
+
*
|
|
238
|
+
* 这是 §3.3「用户零手工重跑」的对外契约:调用方把 fetch 包成 withAutoRetryOnExtension,
|
|
239
|
+
* 用户装完扩展→自然出结果;用户不装→等 120s 后返回 needs-extension,而不是「再跑一次试试」。
|
|
240
|
+
*/
|
|
241
|
+
export async function withAutoRetryOnExtension<T>(args: {
|
|
242
|
+
fetch: () => Promise<T>
|
|
243
|
+
isExtensionNeeded: (result: T) => boolean
|
|
244
|
+
/** watch 配置(测试可注入 fake probe) */
|
|
245
|
+
watch?: HealthWatchOptions
|
|
246
|
+
/** 重放 fetcher(默认复用 fetch) */
|
|
247
|
+
retry?: () => Promise<T>
|
|
248
|
+
}): Promise<{ result: T; retried: boolean; attempts: number; outcome: HealthWatchOutcome }> {
|
|
249
|
+
const first = await args.fetch()
|
|
250
|
+
if (!args.isExtensionNeeded(first)) {
|
|
251
|
+
return { result: first, retried: false, attempts: 1, outcome: { ready: true, attempts: 0, waitedMs: 0 } }
|
|
252
|
+
}
|
|
253
|
+
const watch = startExtensionHealthWatch(args.watch ?? {})
|
|
254
|
+
const outcome = await watch.waitReady()
|
|
255
|
+
if (!outcome.ready) {
|
|
256
|
+
return { result: first, retried: false, attempts: 1, outcome }
|
|
257
|
+
}
|
|
258
|
+
const retriedResult = await (args.retry ?? args.fetch)()
|
|
259
|
+
return { result: retriedResult, retried: true, attempts: 2, outcome }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/** 留作运行时只读检查,防止扩展心跳节律假设漂移(§38 防漂移同款语义) */
|
|
263
|
+
export const HEALTH_WATCH_INTERVAL_FLOOR_MS = 1_000
|
|
264
|
+
export const HEALTH_WATCH_INTERVAL_CEIL_MS = EXTENSION_CONNECTED_WINDOW_MS - 1_000
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReadGuard:会话检索面的只读物理隔离(RFC docs/rfc/user-session-data-rfc.md §3.3)。
|
|
3
|
+
*
|
|
4
|
+
* 写不是「被禁止的行为」,是「不存在的原语」——WriteGate(L0-L4)的检索态前置:
|
|
5
|
+
* ① 网络层:route 拦截——命中硬支付模式(任意方法)或写方法+写URL模式 → abort + 审计落盘;
|
|
6
|
+
* ② DOM 层:提交类按钮文本黑名单,从快照/断言文本中剔除(模型看不到可点的提交件);
|
|
7
|
+
* ③ fail-closed:guard 未装上,session transport 不允许导航(openSession 装不上即整会话失败)。
|
|
8
|
+
*
|
|
9
|
+
* 审计:append-only JSONL(session-incidents.jsonl,对齐 incident-log 惯例),隔离 stateRoot。
|
|
10
|
+
* 注意:搜索接口多为 POST(如携程 batchSearch),因此写判定必须「方法 × URL 模式」双因子,
|
|
11
|
+
* 不能一刀切禁 POST——batchSearch 不含写词,放行。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { mkdirSync, appendFileSync } from 'node:fs'
|
|
15
|
+
import { dirname } from 'node:path'
|
|
16
|
+
|
|
17
|
+
/** 硬支付模式:任意方法(含 GET)都视为写——进支付/收银面本身就是风险 */
|
|
18
|
+
const HARD_PAY_RE = /\/(pay|payment|cashier)(\/|\?|$)/i
|
|
19
|
+
/** 驼峰复合写词(OTA 常见 /submitOrder、/createOrder):段式正则漏接,单列 */
|
|
20
|
+
const WRITE_COMPOUND_RE = /(submitorder|createorder|placeorder|cancelorder|refundorder|orderconfirm|orderpay|confirmpay)/i
|
|
21
|
+
/** 写词 URL 模式:仅在写方法(POST/PUT/DELETE/PATCH)下触发 */
|
|
22
|
+
const WRITE_URL_RE = /\/(order|orders|submit|trade|booking|book|purchase|buy|create|cancel|refund)(\/|\?|$)/i
|
|
23
|
+
const WRITE_METHODS = new Set(['POST', 'PUT', 'DELETE', 'PATCH'])
|
|
24
|
+
/** DOM 提交件文本黑名单(§3.3-②):命中即从可交互面剔除 */
|
|
25
|
+
export const SUBMIT_TEXT_RE = /下单|去支付|支付|预订|订票|购买|提交订单|确认订单/
|
|
26
|
+
|
|
27
|
+
/** 网络层写判定(纯函数,测试锚点) */
|
|
28
|
+
export function classifyRequest(method: string, url: string): 'allow' | 'block' {
|
|
29
|
+
if (HARD_PAY_RE.test(url) || WRITE_COMPOUND_RE.test(url)) return 'block'
|
|
30
|
+
if (WRITE_METHODS.has(method.toUpperCase()) && WRITE_URL_RE.test(url)) return 'block'
|
|
31
|
+
return 'allow'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** DOM 层提交件过滤(纯函数,测试锚点):true=剔除 */
|
|
35
|
+
export function isSubmitText(text: string): boolean {
|
|
36
|
+
return SUBMIT_TEXT_RE.test(text ?? '')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface GuardAuditEntry {
|
|
40
|
+
ts: string
|
|
41
|
+
kind: 'blocked-write-request'
|
|
42
|
+
method: string
|
|
43
|
+
url: string
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ReadGuardHandle {
|
|
47
|
+
/** 被拦截的写请求数(测试断言锚点) */
|
|
48
|
+
blockedCount(): number
|
|
49
|
+
/** 经手的请求总数 */
|
|
50
|
+
requestCount(): number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RoutableContext {
|
|
54
|
+
route(glob: string, handler: (route: { request(): { method(): string; url(): string }; abort(code?: string): Promise<void>; continue(): Promise<void> }) => Promise<void>): Promise<void>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 装上网络层守卫(fail-closed 的「closed」在调用方:openSession 装不上 guard 即失败,不给无 guard 的会话)。
|
|
59
|
+
* 审计落 auditPath(JSONL append);auditPath 缺省不落盘(仅内存计数)。
|
|
60
|
+
*/
|
|
61
|
+
export async function attachReadGuard(ctx: RoutableContext, auditPath?: string): Promise<ReadGuardHandle> {
|
|
62
|
+
let blocked = 0
|
|
63
|
+
let total = 0
|
|
64
|
+
await ctx.route('**/*', async (route) => {
|
|
65
|
+
const req = route.request()
|
|
66
|
+
total += 1
|
|
67
|
+
const verdict = classifyRequest(req.method(), req.url())
|
|
68
|
+
if (verdict === 'block') {
|
|
69
|
+
blocked += 1
|
|
70
|
+
if (auditPath) {
|
|
71
|
+
const entry: GuardAuditEntry = { ts: new Date().toISOString(), kind: 'blocked-write-request', method: req.method(), url: req.url().slice(0, 400) }
|
|
72
|
+
try {
|
|
73
|
+
mkdirSync(dirname(auditPath), { recursive: true })
|
|
74
|
+
appendFileSync(auditPath, JSON.stringify(entry) + '\n')
|
|
75
|
+
} catch { /* 审计失败不阻塞拦截本身 */ }
|
|
76
|
+
}
|
|
77
|
+
await route.abort('blockedbyclient')
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
await route.continue()
|
|
81
|
+
})
|
|
82
|
+
return {
|
|
83
|
+
blockedCount: () => blocked,
|
|
84
|
+
requestCount: () => total,
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* ============ puppeteer-core 适配(2026-08-28 传输层定案:Chrome 144+ 调试服务与 playwright 不兼容) ============ */
|
|
89
|
+
|
|
90
|
+
interface PpRequest {
|
|
91
|
+
method(): string
|
|
92
|
+
url(): string
|
|
93
|
+
continue(): Promise<void>
|
|
94
|
+
abort(code?: string): Promise<void>
|
|
95
|
+
}
|
|
96
|
+
interface PpPage {
|
|
97
|
+
setRequestInterception(on: boolean): Promise<void>
|
|
98
|
+
on(event: 'request', handler: (req: PpRequest) => void): void
|
|
99
|
+
}
|
|
100
|
+
interface PpBrowser {
|
|
101
|
+
pages(): Promise<PpPage[]>
|
|
102
|
+
on(event: string, handler: (target: { type(): string; page(): Promise<PpPage> }) => void): unknown
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* puppeteer 版守卫:对既有页面与新建页面全部开启请求拦截,写请求 abort + 审计。
|
|
107
|
+
* 与 playwright 版共用 classifyRequest 判定与审计条目(单一事实源)。
|
|
108
|
+
*/
|
|
109
|
+
export async function attachReadGuardPuppeteer(browser: PpBrowser, auditPath?: string): Promise<ReadGuardHandle> {
|
|
110
|
+
let blocked = 0
|
|
111
|
+
let total = 0
|
|
112
|
+
const wire = (page: PpPage): void => {
|
|
113
|
+
void (async () => {
|
|
114
|
+
try {
|
|
115
|
+
await page.setRequestInterception(true)
|
|
116
|
+
page.on('request', async (req) => {
|
|
117
|
+
total += 1
|
|
118
|
+
const verdict = classifyRequest(req.method(), req.url())
|
|
119
|
+
if (verdict === 'block') {
|
|
120
|
+
blocked += 1
|
|
121
|
+
if (auditPath) {
|
|
122
|
+
const entry: GuardAuditEntry = { ts: new Date().toISOString(), kind: 'blocked-write-request', method: req.method(), url: req.url().slice(0, 400) }
|
|
123
|
+
try {
|
|
124
|
+
mkdirSync(dirname(auditPath), { recursive: true })
|
|
125
|
+
appendFileSync(auditPath, JSON.stringify(entry) + '\n')
|
|
126
|
+
} catch { /* 审计失败不阻塞拦截本身 */ }
|
|
127
|
+
}
|
|
128
|
+
await req.abort('blockedbyclient').catch(() => { /* 目标已销毁等竞态 */ })
|
|
129
|
+
return
|
|
130
|
+
}
|
|
131
|
+
await req.continue().catch(() => { /* 竞态容忍 */ })
|
|
132
|
+
})
|
|
133
|
+
} catch { /* 已关闭的 target 容忍 */ }
|
|
134
|
+
})()
|
|
135
|
+
}
|
|
136
|
+
for (const p of await browser.pages()) wire(p)
|
|
137
|
+
browser.on('targetcreated', (t) => {
|
|
138
|
+
if (t.type() === 'page') void t.page().then(wire).catch(() => { /* ignore */ })
|
|
139
|
+
})
|
|
140
|
+
return { blockedCount: () => blocked, requestCount: () => total }
|
|
141
|
+
}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import type { SessionComparableRecord, SessionComparableSegment } from './benchmark.ts'
|
|
5
|
+
|
|
6
|
+
export type GoldenSource = 'manual' | 'flyai' | 'static'
|
|
7
|
+
|
|
8
|
+
export interface StaticFlightRouteLeg {
|
|
9
|
+
from_iata: string
|
|
10
|
+
to_iata: string
|
|
11
|
+
carrier_codes: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface StaticFlightRoute {
|
|
15
|
+
query_id: string
|
|
16
|
+
from_city: string
|
|
17
|
+
to_city: string
|
|
18
|
+
route_kind: 'direct' | 'transfer'
|
|
19
|
+
legs: StaticFlightRouteLeg[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface StaticFlightSnapshot {
|
|
23
|
+
schema_version: 'sf-static-routes.v1'
|
|
24
|
+
source: {
|
|
25
|
+
name: string
|
|
26
|
+
url: string
|
|
27
|
+
revision: string
|
|
28
|
+
license: string
|
|
29
|
+
retrieved_at: string
|
|
30
|
+
limitations: string[]
|
|
31
|
+
}
|
|
32
|
+
routes: StaticFlightRoute[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface StaticGoldenResolution {
|
|
36
|
+
requested_source: 'static'
|
|
37
|
+
effective_source: 'static-openflights+manual-band' | 'manual-golden'
|
|
38
|
+
record: SessionComparableRecord
|
|
39
|
+
fallback_reason?: string
|
|
40
|
+
estimated_fields: string[]
|
|
41
|
+
provenance: {
|
|
42
|
+
route_source: string
|
|
43
|
+
route_revision: string
|
|
44
|
+
route_license: string
|
|
45
|
+
band_source: 'sf-golden-manifest.json'
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseGoldenSource(args: string[]): GoldenSource {
|
|
50
|
+
const raw = args.find((arg) => arg.startsWith('--golden='))?.slice('--golden='.length) ?? 'manual'
|
|
51
|
+
if (raw === 'manual' || raw === 'flyai' || raw === 'static') return raw
|
|
52
|
+
throw new Error(`不支持的 golden vendor: ${raw}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function loadStaticFlightSnapshot(
|
|
56
|
+
path = join(import.meta.dirname, '..', '..', 'data', 'sf-static-routes.json'),
|
|
57
|
+
): StaticFlightSnapshot {
|
|
58
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown
|
|
59
|
+
if (!isRecord(parsed) || parsed.schema_version !== 'sf-static-routes.v1') {
|
|
60
|
+
throw new Error(`invalid static snapshot schema_version: ${String(isRecord(parsed) ? parsed.schema_version : undefined)}`)
|
|
61
|
+
}
|
|
62
|
+
const source = parsed.source
|
|
63
|
+
if (!isRecord(source)
|
|
64
|
+
|| !['name', 'url', 'revision', 'license', 'retrieved_at'].every((key) => typeof source[key] === 'string')
|
|
65
|
+
|| !Array.isArray(source.limitations)
|
|
66
|
+
|| !source.limitations.every((item) => typeof item === 'string')) {
|
|
67
|
+
throw new Error('invalid static snapshot source')
|
|
68
|
+
}
|
|
69
|
+
if (!Array.isArray(parsed.routes) || !parsed.routes.every(isStaticFlightRoute)) {
|
|
70
|
+
throw new Error('invalid static snapshot routes')
|
|
71
|
+
}
|
|
72
|
+
return parsed as unknown as StaticFlightSnapshot
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
76
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isStaticFlightRoute(value: unknown): boolean {
|
|
80
|
+
if (!isRecord(value)
|
|
81
|
+
|| typeof value.query_id !== 'string'
|
|
82
|
+
|| typeof value.from_city !== 'string'
|
|
83
|
+
|| typeof value.to_city !== 'string'
|
|
84
|
+
|| (value.route_kind !== 'direct' && value.route_kind !== 'transfer')
|
|
85
|
+
|| !Array.isArray(value.legs)
|
|
86
|
+
|| value.legs.length === 0) return false
|
|
87
|
+
return value.legs.every((leg) => isRecord(leg)
|
|
88
|
+
&& typeof leg.from_iata === 'string'
|
|
89
|
+
&& typeof leg.to_iata === 'string'
|
|
90
|
+
&& Array.isArray(leg.carrier_codes)
|
|
91
|
+
&& leg.carrier_codes.every((carrier) => typeof carrier === 'string'))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function provenance(snapshot?: StaticFlightSnapshot): StaticGoldenResolution['provenance'] {
|
|
95
|
+
return {
|
|
96
|
+
route_source: snapshot?.source.url ?? '',
|
|
97
|
+
route_revision: snapshot?.source.revision ?? '',
|
|
98
|
+
route_license: snapshot?.source.license ?? '',
|
|
99
|
+
band_source: 'sf-golden-manifest.json',
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function formatWithOffset(timestampMs: number, offset: string): string {
|
|
104
|
+
const sign = offset.startsWith('-') ? -1 : 1
|
|
105
|
+
const [hours, minutes] = offset.slice(1).split(':').map(Number)
|
|
106
|
+
const offsetMinutes = sign * ((hours ?? 0) * 60 + (minutes ?? 0))
|
|
107
|
+
const local = new Date(timestampMs + offsetMinutes * 60_000).toISOString().slice(0, 19)
|
|
108
|
+
return `${local}${offset}`
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function buildSegments(
|
|
112
|
+
query: { from: string; to: string },
|
|
113
|
+
route: StaticFlightRoute,
|
|
114
|
+
manualSegment: SessionComparableSegment,
|
|
115
|
+
): SessionComparableSegment[] | null {
|
|
116
|
+
if (route.route_kind === 'direct' && route.legs.length === 1) {
|
|
117
|
+
return [{
|
|
118
|
+
from: query.from,
|
|
119
|
+
to: query.to,
|
|
120
|
+
departure_at: manualSegment.departure_at,
|
|
121
|
+
arrival_at: manualSegment.arrival_at,
|
|
122
|
+
transport_number: `${route.legs[0]!.carrier_codes[0]}-STATIC`,
|
|
123
|
+
}]
|
|
124
|
+
}
|
|
125
|
+
if (route.route_kind !== 'transfer' || route.legs.length !== 2) return null
|
|
126
|
+
const departureMs = Date.parse(manualSegment.departure_at)
|
|
127
|
+
const arrivalMs = Date.parse(manualSegment.arrival_at)
|
|
128
|
+
if (!Number.isFinite(departureMs) || !Number.isFinite(arrivalMs) || arrivalMs <= departureMs) return null
|
|
129
|
+
const offset = manualSegment.departure_at.match(/([+-]\d{2}:\d{2})$/)?.[1] ?? '+08:00'
|
|
130
|
+
const totalMinutes = Math.floor((arrivalMs - departureMs) / 60_000)
|
|
131
|
+
if (totalMinutes < 90) return null
|
|
132
|
+
const layoverMinutes = Math.min(60, Math.floor(totalMinutes / 3))
|
|
133
|
+
const travelMinutes = totalMinutes - layoverMinutes
|
|
134
|
+
const firstTravelMinutes = Math.floor(travelMinutes / 2)
|
|
135
|
+
const secondDepartureMs = arrivalMs - (travelMinutes - firstTravelMinutes) * 60_000
|
|
136
|
+
const middle = route.legs[0]!.to_iata
|
|
137
|
+
return [
|
|
138
|
+
{
|
|
139
|
+
from: query.from,
|
|
140
|
+
to: middle,
|
|
141
|
+
departure_at: manualSegment.departure_at,
|
|
142
|
+
arrival_at: formatWithOffset(departureMs + firstTravelMinutes * 60_000, offset),
|
|
143
|
+
transport_number: `${route.legs[0]!.carrier_codes[0]}-STATIC`,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
from: middle,
|
|
147
|
+
to: query.to,
|
|
148
|
+
departure_at: formatWithOffset(secondDepartureMs, offset),
|
|
149
|
+
arrival_at: manualSegment.arrival_at,
|
|
150
|
+
transport_number: `${route.legs[1]!.carrier_codes[0]}-STATIC`,
|
|
151
|
+
},
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function resolveStaticGolden(input: {
|
|
156
|
+
query: { id: string; from: string; to: string; date: string }
|
|
157
|
+
manualRecord: SessionComparableRecord
|
|
158
|
+
snapshot?: StaticFlightSnapshot
|
|
159
|
+
snapshotError?: string
|
|
160
|
+
warn?: (message: string) => void
|
|
161
|
+
}): StaticGoldenResolution {
|
|
162
|
+
const route = input.snapshot?.routes.find((candidate) => candidate.query_id === input.query.id)
|
|
163
|
+
let fallbackReason: string | undefined
|
|
164
|
+
if (input.snapshotError) {
|
|
165
|
+
fallbackReason = `static snapshot unavailable: ${input.snapshotError}`
|
|
166
|
+
} else if (!route) {
|
|
167
|
+
fallbackReason = `static route missing for ${input.query.id}`
|
|
168
|
+
} else if (route.from_city !== input.query.from || route.to_city !== input.query.to) {
|
|
169
|
+
fallbackReason = `static route mismatch for ${input.query.id}`
|
|
170
|
+
} else if (route.legs.some((leg) => leg.carrier_codes.length === 0)) {
|
|
171
|
+
fallbackReason = `static carrier coverage missing for ${input.query.id}`
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const manualSegment = input.manualRecord.route_segments[0]
|
|
175
|
+
const segments = !fallbackReason && route && manualSegment
|
|
176
|
+
? buildSegments(input.query, route, manualSegment)
|
|
177
|
+
: null
|
|
178
|
+
if (!fallbackReason && !segments) fallbackReason = `static route cannot form comparable record for ${input.query.id}`
|
|
179
|
+
|
|
180
|
+
if (fallbackReason) {
|
|
181
|
+
input.warn?.(`[sf-live-benchmark] static vendor failed for ${input.query.id}: ${fallbackReason}; fallback=manual-golden`)
|
|
182
|
+
return {
|
|
183
|
+
requested_source: 'static',
|
|
184
|
+
effective_source: 'manual-golden',
|
|
185
|
+
record: input.manualRecord,
|
|
186
|
+
fallback_reason: fallbackReason,
|
|
187
|
+
estimated_fields: [],
|
|
188
|
+
provenance: provenance(input.snapshot),
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
requested_source: 'static',
|
|
194
|
+
effective_source: 'static-openflights+manual-band',
|
|
195
|
+
record: {
|
|
196
|
+
...input.manualRecord,
|
|
197
|
+
route_segments: segments!,
|
|
198
|
+
journey_type: route!.route_kind,
|
|
199
|
+
source: 'static-openflights+manual-band',
|
|
200
|
+
},
|
|
201
|
+
estimated_fields: [
|
|
202
|
+
'route_segments[].departure_at',
|
|
203
|
+
'route_segments[].arrival_at',
|
|
204
|
+
'route_segments[].transport_number',
|
|
205
|
+
'price',
|
|
206
|
+
],
|
|
207
|
+
provenance: provenance(input.snapshot),
|
|
208
|
+
}
|
|
209
|
+
}
|