@danceiny/gotry 0.0.1-rc.8 → 0.2.0-rc.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +172 -217
- 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 +1744 -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 -49
- 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 +135 -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 +546 -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 +968 -0
- package/dist/capabilities/fx-contract.js +293 -0
- package/dist/capabilities/geo-atlas.js +246 -0
- package/dist/capabilities/ground-transfer.js +509 -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/resilience.js +191 -0
- package/dist/capabilities/session/action-cache.js +126 -0
- package/dist/capabilities/session/adapters/ctrip-flight.js +141 -0
- 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 +54 -0
- 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 +50 -0
- 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 +107 -0
- package/dist/capabilities/session/static-flight-golden.js +150 -0
- package/dist/capabilities/session/transport.js +109 -0
- 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 +686 -0
- 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 +72 -0
- 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-tests.js +116 -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 +68 -10
- 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 +123 -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 +1479 -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/companion-tests.js +67 -0
- 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 +564 -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-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 +131 -0
- package/dist/scripts/g5-guard.js +189 -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 +480 -0
- package/dist/scripts/ledger-workflow-crash.js +32 -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 +49 -0
- package/dist/scripts/memory-lifecycle-tests.js +657 -0
- package/dist/scripts/memory-lifecycle.js +196 -0
- package/dist/scripts/memory-metrics.js +29 -31
- 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 +161 -0
- package/dist/scripts/nightly-evidence.js +286 -0
- package/dist/scripts/nudge-digest.js +43 -42
- 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/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 +20 -18
- package/dist/scripts/replay-real.js +18 -18
- package/dist/scripts/replay.js +52 -82
- package/dist/scripts/session-attach-diagnose.js +31 -0
- package/dist/scripts/session-attach-poc.js +62 -0
- 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 +60 -0
- package/dist/scripts/session-link-tests.js +478 -0
- package/dist/scripts/session-login.js +29 -0
- package/dist/scripts/session-tests.js +862 -0
- package/dist/scripts/session-zones-tests.js +358 -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 +703 -219
- package/dist/scripts/spawn-bounded-abort-tests.js +241 -0
- package/dist/scripts/sponsor-reuse-tests.js +262 -0
- package/dist/scripts/state-cli-tests.js +261 -0
- package/dist/scripts/state-cli.js +585 -0
- 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 +210 -192
- package/dist/scripts/travel-timeline-tests.js +59 -0
- 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 +23 -19
- 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 +41 -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/companions.js +86 -0
- package/dist/src/contracts.js +21 -43
- package/dist/src/dsh-llm.js +97 -111
- 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 +2305 -731
- 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 +358 -162
- package/dist/src/memory-capture.js +180 -32
- package/dist/src/memory-decay.js +47 -0
- 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/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-zones.js +507 -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 +767 -0
- 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 +59 -0
- 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 +538 -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 +255 -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 +296 -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 +568 -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 +13 -3
- package/ts/src/dsh-llm.ts +44 -11
- package/ts/src/flyai-setup-tool.ts +77 -0
- package/ts/src/index.ts +2107 -224
- 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 +278 -40
- 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 +912 -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 +360 -89
- package/ts/src/wish-pool.ts +79 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扩展车道客户端(RFC §2.2 通道 C,2026-08-29 PRIMARY;issue #21 传输层方案 B)。
|
|
3
|
+
*
|
|
4
|
+
* 三种 job 封装:search(被动嗅探 batchSearch)/ open-login(登录入口置前台)/
|
|
5
|
+
* cookie-names(票据 cookie **名字**,协议面不存在值字段)。verdict 映射纯函数
|
|
6
|
+
* `classifyBridgeFailure` 供回归覆盖:
|
|
7
|
+
* - extension-not-connected → 'needs-extension'(waiting no-spend,一次性安装指引;
|
|
8
|
+
* **不静默回退 CDP**——回退即重新引入逐连接权限框,正是本通道要消灭的摩擦);
|
|
9
|
+
* - bridge-unavailable(端口池全占等环境故障)→ 'error'(非用户门)。
|
|
10
|
+
* 检索页无回包不是车道失败:调用侧判为 sniff_timeout/error,页标题命挑战则判 challenged。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { getOrCreateSessionBridge, needsExtensionSummary, type ExtensionJobResult, type SessionJobHandle, type SniffBodies } from './extension-bridge.ts'
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* needs-extension 用户门文案(D-24 自适应,issue #117):按本地通道是否落位自动跳过
|
|
17
|
+
* 开发者模式/本地通道指引——商店版用户只看到商店一键装与「已装即可」提示。
|
|
18
|
+
* 旧静态常量已退役:静态文案无法感知安装状态,正是 D-24 残量的病灶。
|
|
19
|
+
*/
|
|
20
|
+
export function needsExtensionHint(): string {
|
|
21
|
+
return needsExtensionSummary()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type BridgeFailureKind = 'bridge-unavailable' | 'extension-not-connected' | 'timeout' | 'job-error'
|
|
25
|
+
|
|
26
|
+
export interface BridgeFailure {
|
|
27
|
+
ok: false
|
|
28
|
+
kind: BridgeFailureKind
|
|
29
|
+
summary: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 桥故障 → 用户门 verdict 纯函数(测试锚点):只有「扩展未连接」是用户门(waiting),其余降级 error */
|
|
33
|
+
export function classifyBridgeFailure(kind: BridgeFailureKind): 'needs-extension' | 'error' {
|
|
34
|
+
return kind === 'extension-not-connected' ? 'needs-extension' : 'error'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SessionJobHandleRef {
|
|
38
|
+
ok: true
|
|
39
|
+
bridge: SessionJobHandle
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function bridgeReady(b: { ok: true; bridge: SessionJobHandle } | { ok: false; summary: string }): b is SessionJobHandleRef {
|
|
43
|
+
return b.ok === true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function failedJob(result: ExtensionJobResult): BridgeFailure | null {
|
|
47
|
+
if (result.ok === true) return null
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
kind: 'job-error',
|
|
51
|
+
summary: typeof result.error === 'string' && result.error.length > 0
|
|
52
|
+
? result.error.slice(0, 200)
|
|
53
|
+
: '扩展作业失败且未提供原因',
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 桥解析(2026-09-11):gotry-backend 服务形态注入进程内队列;桌面形态缺省走 loopback 懒单例 */
|
|
58
|
+
async function resolveBridge(injected?: SessionJobHandle): Promise<SessionJobHandleRef | { ok: false; summary: string }> {
|
|
59
|
+
if (injected) return { ok: true, bridge: injected }
|
|
60
|
+
return getOrCreateSessionBridge()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** cookie-names job:票据 cookie 名存在性(名字级;协议不含值) */
|
|
64
|
+
export async function extensionCookieNames(q: { site: string; domain: string; ticketNames: string[]; timeoutMs?: number }, bridgeHandle?: SessionJobHandle): Promise<{ ok: true; tickets: string[]; origin?: string; clientId?: string } | BridgeFailure> {
|
|
65
|
+
const bridge = await resolveBridge(bridgeHandle)
|
|
66
|
+
if (!bridgeReady(bridge)) return { ok: false, kind: 'bridge-unavailable', summary: bridge.summary }
|
|
67
|
+
const outcome = await bridge.bridge.submit(
|
|
68
|
+
{ kind: 'cookie-names', site: q.site, timeoutMs: q.timeoutMs ?? 8_000 },
|
|
69
|
+
{ timeoutMs: q.timeoutMs ?? 8_000 },
|
|
70
|
+
)
|
|
71
|
+
if (!outcome.ok) return { ok: false, kind: outcome.reason, summary: outcome.summary }
|
|
72
|
+
const failure = failedJob(outcome.result)
|
|
73
|
+
if (failure) return failure
|
|
74
|
+
const names = Array.isArray(outcome.result.names) ? outcome.result.names.filter((n): n is string => typeof n === 'string') : []
|
|
75
|
+
// 红线自证:隧道面只有名字(extension-tests §红线断言协议面无值字段)
|
|
76
|
+
return { ok: true, tickets: names, ...(outcome.origin ? { origin: outcome.origin } : {}), ...(outcome.clientId ? { clientId: outcome.clientId } : {}) }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 登录入口置前台打开(#34 纪律:标签留给用户,扩展侧不 close) */
|
|
80
|
+
export async function extensionOpenLogin(q: { site: string; url: string; timeoutMs?: number }, bridgeHandle?: SessionJobHandle): Promise<{ ok: true; opened: boolean } | BridgeFailure> {
|
|
81
|
+
const bridge = await resolveBridge(bridgeHandle)
|
|
82
|
+
if (!bridgeReady(bridge)) return { ok: false, kind: 'bridge-unavailable', summary: bridge.summary }
|
|
83
|
+
const outcome = await bridge.bridge.submit({ kind: 'open-login', site: q.site, url: q.url }, { timeoutMs: q.timeoutMs ?? 15_000 })
|
|
84
|
+
if (!outcome.ok) return { ok: false, kind: outcome.reason, summary: outcome.summary }
|
|
85
|
+
const failure = failedJob(outcome.result)
|
|
86
|
+
if (failure) return failure
|
|
87
|
+
if (!outcome.result.opened) return { ok: false, kind: 'job-error', summary: outcome.result.error ?? '登录入口打开失败' }
|
|
88
|
+
return { ok: true, opened: true }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface ExtensionSearchOutcome {
|
|
92
|
+
ok: true
|
|
93
|
+
/** NETWORK_HINTS 命中的响应原文;未命中/超时为空 */
|
|
94
|
+
body: string
|
|
95
|
+
/** Exact NETWORK_HINTS response URL selected for body;未命中/超时为空 */
|
|
96
|
+
url: string
|
|
97
|
+
/** 页标题(challenged 判定素材) */
|
|
98
|
+
title: string
|
|
99
|
+
/** 到点未见 hints 命中(站点无回包/风控拦截页) */
|
|
100
|
+
timedOut: boolean
|
|
101
|
+
/** multiCollect(dida 推荐流):分桶回包体;旧扩展/非 multiCollect 缺省 */
|
|
102
|
+
bodies?: SniffBodies
|
|
103
|
+
/** 页面级挑战标记(content-bridge 按 DOM 判定;网关侧不得用供应商响应体扫「验证」——会把酒店名误判成风控) */
|
|
104
|
+
challenge?: boolean
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 检索 job:后台标签打开 entry,等 content hook 的嗅探回包;页无响应=timedOut(非车道失败) */
|
|
108
|
+
export async function extensionSearchJob(
|
|
109
|
+
q: { site: string; url: string; timeoutMs?: number; multiCollect?: boolean; query?: Record<string, unknown>; preferredOrigin?: string; preferredClientId?: string },
|
|
110
|
+
bridgeHandle?: SessionJobHandle,
|
|
111
|
+
): Promise<ExtensionSearchOutcome | BridgeFailure> {
|
|
112
|
+
const bridge = await resolveBridge(bridgeHandle)
|
|
113
|
+
if (!bridgeReady(bridge)) return { ok: false, kind: 'bridge-unavailable', summary: bridge.summary }
|
|
114
|
+
const timeoutMs = q.timeoutMs ?? 30_000
|
|
115
|
+
const outcome = await bridge.bridge.submit(
|
|
116
|
+
{ kind: 'search', site: q.site, url: q.url, timeoutMs, ...(q.multiCollect ? { multiCollect: true } : {}), ...(q.query ? { query: q.query } : {}) },
|
|
117
|
+
{ timeoutMs: timeoutMs + 10_000, ...(q.preferredOrigin ? { preferredOrigin: q.preferredOrigin } : {}), ...(q.preferredClientId ? { preferredClientId: q.preferredClientId } : {}) },
|
|
118
|
+
)
|
|
119
|
+
if (!outcome.ok) return { ok: false, kind: outcome.reason, summary: outcome.summary }
|
|
120
|
+
// An exhausted page sniff still carries the page title and challenge hint.
|
|
121
|
+
// Let the caller inspect them before classifying the empty body.
|
|
122
|
+
if (outcome.result.ok === false && outcome.result.timeout === true) {
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
body: '',
|
|
126
|
+
url: '',
|
|
127
|
+
title: typeof outcome.result.title === 'string' ? outcome.result.title : '',
|
|
128
|
+
timedOut: true,
|
|
129
|
+
...(outcome.result.challenge === true ? { challenge: true } : {}),
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const failure = failedJob(outcome.result)
|
|
133
|
+
if (failure) return failure
|
|
134
|
+
const bodies = outcome.result.bodies
|
|
135
|
+
return {
|
|
136
|
+
ok: true,
|
|
137
|
+
...(outcome.result.challenge === true ? { challenge: true } : {}),
|
|
138
|
+
body: typeof outcome.result.body === 'string' ? outcome.result.body : '',
|
|
139
|
+
url: typeof outcome.result.url === 'string' ? outcome.result.url : '',
|
|
140
|
+
title: typeof outcome.result.title === 'string' ? outcome.result.title : '',
|
|
141
|
+
timedOut: outcome.result.timeout === true,
|
|
142
|
+
...(bodies && typeof bodies === 'object' ? { bodies } : {}),
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 扩展产物分发(issue #21 分发通道,ADR-21;founder 2026-08-30「产物下载和安装也得做成更好的
|
|
3
|
+
* 用户体验,可以用 github 作为分发渠道」)。
|
|
4
|
+
*
|
|
5
|
+
* 平台硬约束(诚实前置):Chrome 禁止普通用户从任意 URL 安装打包 CRX——GitHub 只能改善
|
|
6
|
+
* 「下载」(版本化/回滚/与 npm 发版节奏解耦),改善不了「安装」;「开发者模式加载已解压」
|
|
7
|
+
* 那 3 次点击只有上架 Chrome Web Store 才能消掉(商店版自动更新,见 extension-webstore-submission.md)。
|
|
8
|
+
* manifest 固定 key ⇒ 双通道是同一个扩展 ID(olpgkofjhhiiiahdkkbcninhjmegghfe),端口池/
|
|
9
|
+
* host_permissions 不随通道漂移——本模块用 key 钉住这一不变量。
|
|
10
|
+
*
|
|
11
|
+
* 单一职责:① 产物源解析(bundled 默认离线确定性 | github 显式 opt-in);
|
|
12
|
+
* ② GitHub Releases 下载链:dist-manifest → tar.gz → SHA256 校验 → tar 解压 → key 钉扎校验
|
|
13
|
+
* → 版本比较 → 原子交换 ~/.gotry/extension;
|
|
14
|
+
* ③ 失败语义:github 源任何失败(无网/404/校验不过/key 漂移)→ 结构化 fallback bundled,
|
|
15
|
+
* 绝不挡 setup(安装外部产物永远不挡 gotry 本体——bootstrap 契约)。
|
|
16
|
+
*
|
|
17
|
+
* 信任边界(诚实声明):TLS 传输 + 同源 dist-manifest 哈希完整性——防截断/损坏/误传资产,
|
|
18
|
+
* 不防仓库本身被攻陷(无独立 pinning;独立 pin 需把哈希烘进 npm 包,与「扩展更新与 npm
|
|
19
|
+
* 发版解耦」目标冲突,明确不做)。
|
|
20
|
+
*
|
|
21
|
+
* 零新 npm 依赖:fetch 走 Node ≥22 全局;解压走平台原生 `tar -xzf`(macOS/Linux/Windows10+
|
|
22
|
+
* 自带);SHA256 走 node:crypto。spawn/fetch 可注入(离线测试)。
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { spawn } from 'node:child_process'
|
|
26
|
+
import { createHash } from 'node:crypto'
|
|
27
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
28
|
+
import { join } from 'node:path'
|
|
29
|
+
|
|
30
|
+
/** Releases 稳定资产名(每版覆盖同名上传;latest/download 永久链只认稳定名)。
|
|
31
|
+
* 与 scripts/package-extension.mjs 的资产名必须逐字一致——extension-distribution-tests 防漂移。 */
|
|
32
|
+
export const DIST_ASSET_TARBALL = 'gotry-session-bridge.tar.gz'
|
|
33
|
+
export const DIST_ASSET_STORE_ZIP = 'gotry-session-bridge-store.zip'
|
|
34
|
+
export const DIST_ASSET_MANIFEST = 'extension-dist-manifest.json'
|
|
35
|
+
|
|
36
|
+
export const DEFAULT_RELEASE_BASE = 'https://github.com/Danceiny/gotry/releases'
|
|
37
|
+
|
|
38
|
+
export interface ExtensionDistManifest {
|
|
39
|
+
version: string
|
|
40
|
+
tarball: string
|
|
41
|
+
tarballSha256: string
|
|
42
|
+
zip: string
|
|
43
|
+
zipSha256: string
|
|
44
|
+
builtFromCommit?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface GithubSourceOptions {
|
|
48
|
+
/** Releases 基址(测试/镜像可指回环);默认 GitHub 官方 */
|
|
49
|
+
releaseBase?: string
|
|
50
|
+
/** 注入 fetch(离线测试);默认全局 fetch */
|
|
51
|
+
fetch?: typeof fetch
|
|
52
|
+
/** 注入 spawn(离线测试拦截 tar);默认 child_process.spawn */
|
|
53
|
+
spawnFn?: typeof spawn
|
|
54
|
+
/** 下载/解压超时(默认 60s,有界) */
|
|
55
|
+
timeoutMs?: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type ExtensionInstallAction = 'installed' | 'up-to-date' | 'fallback-bundled'
|
|
59
|
+
|
|
60
|
+
export interface ExtensionInstallResult {
|
|
61
|
+
ok: boolean
|
|
62
|
+
action: ExtensionInstallAction
|
|
63
|
+
/** 远端 dist-manifest 声明的版本(action=up-to-date 时也带回,供比较报告) */
|
|
64
|
+
version?: string
|
|
65
|
+
/** 交换前 dest 里已就位的版本(无则 undefined) */
|
|
66
|
+
previousVersion?: string
|
|
67
|
+
error?: string
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 纯函数:三段数字版本比较(a<b 负 / 相等 0 / a>b 正;非数字段按 0 处理,长度差补 0) */
|
|
71
|
+
export function compareVersions(a: string, b: string): number {
|
|
72
|
+
const pa = String(a).split('.').map((s) => Number.parseInt(s, 10) || 0)
|
|
73
|
+
const pb = String(b).split('.').map((s) => Number.parseInt(s, 10) || 0)
|
|
74
|
+
const n = Math.max(pa.length, pb.length)
|
|
75
|
+
for (let i = 0; i < n; i += 1) {
|
|
76
|
+
const d = (pa[i] ?? 0) - (pb[i] ?? 0)
|
|
77
|
+
if (d !== 0) return d
|
|
78
|
+
}
|
|
79
|
+
return 0
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** 纯函数:Releases latest/download 永久链(稳定资产名) */
|
|
83
|
+
export function distAssetUrls(releaseBase: string = DEFAULT_RELEASE_BASE): { tarball: string; storeZip: string; manifest: string } {
|
|
84
|
+
const base = releaseBase.replace(/\/+$/, '')
|
|
85
|
+
return {
|
|
86
|
+
tarball: `${base}/latest/download/${DIST_ASSET_TARBALL}`,
|
|
87
|
+
storeZip: `${base}/latest/download/${DIST_ASSET_STORE_ZIP}`,
|
|
88
|
+
manifest: `${base}/latest/download/${DIST_ASSET_MANIFEST}`,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const SHA256_RE = /^[0-9a-f]{64}$/
|
|
93
|
+
|
|
94
|
+
/** 纯函数:dist-manifest 解析,fail-closed(坏 JSON/缺字段/资产名漂移/哈希非 64-hex 一律拒绝) */
|
|
95
|
+
export function parseDistManifest(raw: string): ExtensionDistManifest {
|
|
96
|
+
let obj: Record<string, unknown>
|
|
97
|
+
try {
|
|
98
|
+
obj = JSON.parse(raw) as Record<string, unknown>
|
|
99
|
+
} catch {
|
|
100
|
+
throw new Error('dist-manifest 不是合法 JSON')
|
|
101
|
+
}
|
|
102
|
+
const version = obj.version
|
|
103
|
+
const tarball = obj.tarball
|
|
104
|
+
const tarballSha256 = obj.tarballSha256
|
|
105
|
+
const zip = obj.zip
|
|
106
|
+
const zipSha256 = obj.zipSha256
|
|
107
|
+
// Chrome manifest version 形态:1-4 段点分整数(扩展版本跟随 gotry 主版本
|
|
108
|
+
// 0.0.1-rc.N → 四段投影 0.0.1.N,展示名走 version_name 原样)
|
|
109
|
+
if (typeof version !== 'string' || !/^\d+(\.\d+){1,3}$/.test(version)) throw new Error('dist-manifest.version 缺失或非 1-4 段点分整数')
|
|
110
|
+
if (tarball !== DIST_ASSET_TARBALL) throw new Error(`dist-manifest.tarball 资产名漂移(期望 ${DIST_ASSET_TARBALL})`)
|
|
111
|
+
if (zip !== DIST_ASSET_STORE_ZIP) throw new Error(`dist-manifest.zip 资产名漂移(期望 ${DIST_ASSET_STORE_ZIP})`)
|
|
112
|
+
if (typeof tarballSha256 !== 'string' || !SHA256_RE.test(tarballSha256)) throw new Error('dist-manifest.tarballSha256 非 64-hex')
|
|
113
|
+
if (typeof zipSha256 !== 'string' || !SHA256_RE.test(zipSha256)) throw new Error('dist-manifest.zipSha256 非 64-hex')
|
|
114
|
+
return {
|
|
115
|
+
version,
|
|
116
|
+
tarball,
|
|
117
|
+
tarballSha256,
|
|
118
|
+
zip,
|
|
119
|
+
zipSha256,
|
|
120
|
+
builtFromCommit: typeof obj.builtFromCommit === 'string' ? obj.builtFromCommit : undefined,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export function sha256Hex(bytes: Uint8Array | string): string {
|
|
125
|
+
return createHash('sha256').update(bytes).digest('hex')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** 读取扩展 manifest 的 key(固定 key ⇒ 固定扩展 ID;分发通道 ID 不变量的锚点) */
|
|
129
|
+
export function readExtensionKey(extensionDir: string): string | undefined {
|
|
130
|
+
const p = join(extensionDir, 'manifest.json')
|
|
131
|
+
if (!existsSync(p)) return undefined
|
|
132
|
+
try {
|
|
133
|
+
const m = JSON.parse(readFileSync(p, 'utf8')) as { key?: unknown }
|
|
134
|
+
return typeof m.key === 'string' ? m.key : undefined
|
|
135
|
+
} catch {
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function readExtensionVersion(extensionDir: string): string | undefined {
|
|
141
|
+
const p = join(extensionDir, 'manifest.json')
|
|
142
|
+
if (!existsSync(p)) return undefined
|
|
143
|
+
try {
|
|
144
|
+
const m = JSON.parse(readFileSync(p, 'utf8')) as { version?: unknown }
|
|
145
|
+
return typeof m.version === 'string' ? m.version : undefined
|
|
146
|
+
} catch {
|
|
147
|
+
return undefined
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function runTar(args: string[], spawnFn: typeof spawn, timeoutMs: number): Promise<{ ok: boolean; error?: string }> {
|
|
152
|
+
return new Promise((resolve) => {
|
|
153
|
+
const child = spawnFn('tar', args, { stdio: 'ignore' })
|
|
154
|
+
let done = false
|
|
155
|
+
const timer = setTimeout(() => {
|
|
156
|
+
if (!done) {
|
|
157
|
+
done = true
|
|
158
|
+
try {
|
|
159
|
+
child.kill('SIGKILL')
|
|
160
|
+
} catch {
|
|
161
|
+
/* ignore */
|
|
162
|
+
}
|
|
163
|
+
resolve({ ok: false, error: `tar 超时(>${timeoutMs}ms)` })
|
|
164
|
+
}
|
|
165
|
+
}, timeoutMs)
|
|
166
|
+
child.on('error', (e) => {
|
|
167
|
+
if (!done) {
|
|
168
|
+
done = true
|
|
169
|
+
clearTimeout(timer)
|
|
170
|
+
resolve({ ok: false, error: `tar 不可用:${e.message}` })
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
child.on('close', (code) => {
|
|
174
|
+
if (!done) {
|
|
175
|
+
done = true
|
|
176
|
+
clearTimeout(timer)
|
|
177
|
+
resolve(code === 0 ? { ok: true } : { ok: false, error: `tar exit ${code}` })
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function fetchBytes(url: string, fetchFn: typeof fetch, timeoutMs: number): Promise<{ ok: true; bytes: Uint8Array } | { ok: false; error: string }> {
|
|
184
|
+
try {
|
|
185
|
+
const res = await fetchFn(url, { signal: AbortSignal.timeout(timeoutMs) })
|
|
186
|
+
if (!res.ok) return { ok: false, error: `HTTP ${res.status} ${url}` }
|
|
187
|
+
const buf = new Uint8Array(await res.arrayBuffer())
|
|
188
|
+
return { ok: true, bytes: buf }
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return { ok: false, error: `下载失败 ${(e as Error).message}` }
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface InstallFromGithubOptions extends GithubSourceOptions {
|
|
195
|
+
/** 落位目录(默认 ~/.gotry/extension 由调用方决定,本层不猜) */
|
|
196
|
+
destDir: string
|
|
197
|
+
/** 钉扎参照:bundled extension/ 目录(其 manifest key 是 ID 不变量的基准;缺省只做版本校验) */
|
|
198
|
+
pinnedSourceDir?: string
|
|
199
|
+
/** 只报告不落盘(up-to-date/将安装哪个版本;不下载 tarball 之外的任何东西) */
|
|
200
|
+
checkOnly?: boolean
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* GitHub Releases → ~/.gotry/extension 安装链。
|
|
205
|
+
* 任何失败都返回 { ok:false, action:'fallback-bundled' }——调用方显式降级 bundled,
|
|
206
|
+
* 本函数不抛出(bootstrap 契约:安装外部产物永远不挡 gotry 本体)。
|
|
207
|
+
*/
|
|
208
|
+
export async function installExtensionFromGithub(opts: InstallFromGithubOptions): Promise<ExtensionInstallResult> {
|
|
209
|
+
const timeoutMs = opts.timeoutMs ?? 60_000
|
|
210
|
+
const fetchFn = opts.fetch ?? fetch
|
|
211
|
+
const spawnFn = opts.spawnFn ?? spawn
|
|
212
|
+
const urls = distAssetUrls(opts.releaseBase)
|
|
213
|
+
const fail = (error: string): ExtensionInstallResult => ({ ok: false, action: 'fallback-bundled', error })
|
|
214
|
+
|
|
215
|
+
const mRes = await fetchBytes(urls.manifest, fetchFn, timeoutMs)
|
|
216
|
+
if (!mRes.ok) return fail(mRes.error)
|
|
217
|
+
let manifest: ExtensionDistManifest
|
|
218
|
+
try {
|
|
219
|
+
manifest = parseDistManifest(new TextDecoder().decode(mRes.bytes))
|
|
220
|
+
} catch (e) {
|
|
221
|
+
return fail(`dist-manifest 校验失败:${(e as Error).message}`)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const previousVersion = readExtensionVersion(opts.destDir)
|
|
225
|
+
if (previousVersion != null && compareVersions(manifest.version, previousVersion) <= 0) {
|
|
226
|
+
return { ok: true, action: 'up-to-date', version: manifest.version, previousVersion }
|
|
227
|
+
}
|
|
228
|
+
if (opts.checkOnly) {
|
|
229
|
+
return { ok: true, action: 'up-to-date', version: manifest.version, previousVersion } // check-only 语义:报告远端版本,不落盘
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const tRes = await fetchBytes(urls.tarball, fetchFn, timeoutMs)
|
|
233
|
+
if (!tRes.ok) return fail(tRes.error)
|
|
234
|
+
const actual = sha256Hex(tRes.bytes)
|
|
235
|
+
if (actual !== manifest.tarballSha256) return fail(`SHA256 不符(期望 ${manifest.tarballSha256.slice(0, 12)}… 实得 ${actual.slice(0, 12)}…)`)
|
|
236
|
+
|
|
237
|
+
// 落盘 → tar 解压 → key 钉扎 → 原子交换;incoming 目录同盘保证 rename 原子性
|
|
238
|
+
const incoming = `${opts.destDir}.incoming-${process.pid}-${Date.now()}`
|
|
239
|
+
const tarPath = `${incoming}.tar.gz`
|
|
240
|
+
try {
|
|
241
|
+
rmSync(incoming, { recursive: true, force: true })
|
|
242
|
+
mkdirSync(incoming, { recursive: true })
|
|
243
|
+
writeFileSync(tarPath, tRes.bytes)
|
|
244
|
+
// 包内含顶层目录 gotry-session-bridge/;--strip-components 落到 incoming 根
|
|
245
|
+
const ext = await runTar(['-xzf', tarPath, '-C', incoming, '--strip-components', '1'], spawnFn, timeoutMs)
|
|
246
|
+
if (!ext.ok) return fail(ext.error!)
|
|
247
|
+
|
|
248
|
+
const incomingKey = readExtensionKey(incoming)
|
|
249
|
+
if (incomingKey == null) return fail('解压产物缺 manifest.json/key(非法包)')
|
|
250
|
+
if (opts.pinnedSourceDir) {
|
|
251
|
+
const pinnedKey = readExtensionKey(opts.pinnedSourceDir)
|
|
252
|
+
if (pinnedKey != null && incomingKey !== pinnedKey) return fail('扩展 key 漂移(通道产物与 bundled 不同 ID——拒绝安装,防端口池/host 权限漂移)')
|
|
253
|
+
}
|
|
254
|
+
const incomingVersion = readExtensionVersion(incoming)
|
|
255
|
+
if (incomingVersion !== manifest.version) return fail(`解压产物版本(${incomingVersion})与 dist-manifest(${manifest.version})不符`)
|
|
256
|
+
|
|
257
|
+
rmSync(opts.destDir, { recursive: true, force: true })
|
|
258
|
+
cpSync(incoming, opts.destDir, { recursive: true })
|
|
259
|
+
return { ok: true, action: 'installed', version: manifest.version, previousVersion }
|
|
260
|
+
} catch (e) {
|
|
261
|
+
return fail(`落位失败 ${(e as Error).message}`)
|
|
262
|
+
} finally {
|
|
263
|
+
rmSync(incoming, { recursive: true, force: true })
|
|
264
|
+
rmSync(tarPath, { force: true })
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* a11y 快照兜底抽取器(RFC §2.3 第②层:站点无干净 XHR JSON 时,从可访问性树快照抽结构化条目)。
|
|
3
|
+
* 纯函数:输入 Playwright ariaSnapshot/domSnapshot 文本,输出归一化条目(role/name/ref),
|
|
4
|
+
* 供确定性规则或 LLM 按 schema 消费;不做任何页面交互。
|
|
5
|
+
*
|
|
6
|
+
* 辑律(ADR-12 同款边界):抽取只认结构(行首 role 与缩进),不做开放式语义猜测;
|
|
7
|
+
* 页面文本是不可信输入(RFC §3.5)——本层只归形状,语义判定在上游 schema 处。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface A11yEntry {
|
|
11
|
+
/** 行首 role,如 button / textbox / link / heading / listitem */
|
|
12
|
+
role: string
|
|
13
|
+
/** 括号内 accessible name(去引号,截断 200) */
|
|
14
|
+
name: string
|
|
15
|
+
/** 快照行号(0 基,回指原快照) */
|
|
16
|
+
line: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Playwright aria snapshot 行形如:
|
|
21
|
+
* `- button "搜索" [ref=s1e5]`
|
|
22
|
+
* `- heading "上海到丽江" [level=1]`
|
|
23
|
+
* 提取 role/name;无 name 的行(name='')保留占位(计数/结构比对仍可用)。
|
|
24
|
+
*/
|
|
25
|
+
export function extractA11yEntries(snapshotText: string, opts: { maxEntries?: number } = {}): A11yEntry[] {
|
|
26
|
+
const cap = opts.maxEntries ?? 500
|
|
27
|
+
const out: A11yEntry[] = []
|
|
28
|
+
const lines = snapshotText.split('\n')
|
|
29
|
+
for (let i = 0; i < lines.length && out.length < cap; i++) {
|
|
30
|
+
const m = /^[\s*-]*\s*([a-zA-Z][\w-]*)(?:\s+"((?:[^"\\]|\\.)*)")?/.exec(lines[i] ?? '')
|
|
31
|
+
if (!m) continue
|
|
32
|
+
const name = (m[2] ?? '').replace(/\\"/g, '"').slice(0, 200)
|
|
33
|
+
out.push({ role: m[1]!.toLowerCase(), name, line: i })
|
|
34
|
+
}
|
|
35
|
+
return out
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** DOM 提交件过滤入口(RFC §3.3-②):从条目集中剔除命中提交黑名单的可交互件,返回保留集 */
|
|
39
|
+
export function filterSubmitEntries<T extends { role: string; name: string }>(entries: T[], isSubmit: (name: string) => boolean): T[] {
|
|
40
|
+
const interactive = new Set(['button', 'link', 'menuitem', 'option'])
|
|
41
|
+
return entries.filter((e) => !(interactive.has(e.role) && isSubmit(e.name)))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 名称相似度粗筛(自愈定位的候选排序底座;0=不同串,1=包含) */
|
|
45
|
+
export function nameAffinity(a: string, b: string): number {
|
|
46
|
+
const x = a.trim(); const y = b.trim()
|
|
47
|
+
if (!x || !y) return 0
|
|
48
|
+
if (x === y) return 1
|
|
49
|
+
if (x.includes(y) || y.includes(x)) return 0.8
|
|
50
|
+
const sa = new Set(x); const sb = new Set(y)
|
|
51
|
+
let common = 0
|
|
52
|
+
for (const ch of sa) if (sb.has(ch)) common += 1
|
|
53
|
+
return common / Math.max(sa.size, sb.size)
|
|
54
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SESSION_FIELD_ACCURACY_THRESHOLD,
|
|
3
|
+
type SessionComparableRecord,
|
|
4
|
+
} from './benchmark.ts'
|
|
5
|
+
|
|
6
|
+
export interface GoldenBand {
|
|
7
|
+
query_id: string
|
|
8
|
+
from: string
|
|
9
|
+
to: string
|
|
10
|
+
date: string
|
|
11
|
+
window_dep_local: { earliest: string; latest: string }
|
|
12
|
+
window_arr_local: { earliest: string; latest: string }
|
|
13
|
+
duration_min: { min: number; max: number }
|
|
14
|
+
price_cny: { min: number; max: number }
|
|
15
|
+
transport_hint: string
|
|
16
|
+
known_flights: string[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GoldenSoftScore {
|
|
20
|
+
pass: boolean
|
|
21
|
+
total: number
|
|
22
|
+
correct: number
|
|
23
|
+
accuracy: number
|
|
24
|
+
missing: string[]
|
|
25
|
+
incorrect: string[]
|
|
26
|
+
golden_source: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function parseHHmm(value: string): number {
|
|
30
|
+
const [hours, minutes] = value.split(':')
|
|
31
|
+
return parseInt(hours!, 10) * 60 + parseInt(minutes!, 10)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function parseLocalMinutes(value: string): number | null {
|
|
35
|
+
const match = value.match(/(\d{2}):(\d{2})/)
|
|
36
|
+
if (!match) return null
|
|
37
|
+
return parseInt(match[1]!, 10) * 60 + parseInt(match[2]!, 10)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseLocalDate(value: string): string | null {
|
|
41
|
+
return value.match(/^(\d{4}-\d{2}-\d{2})[T ]/)?.[1] ?? null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function scoreSessionAgainstGoldenBand(
|
|
45
|
+
session: SessionComparableRecord,
|
|
46
|
+
band: GoldenBand,
|
|
47
|
+
goldenSource: string,
|
|
48
|
+
): GoldenSoftScore {
|
|
49
|
+
const missing: string[] = []
|
|
50
|
+
const hardIncorrect: string[] = []
|
|
51
|
+
const segment = session.route_segments[0]
|
|
52
|
+
|
|
53
|
+
if (!session.query_id) missing.push('query_id')
|
|
54
|
+
else if (session.query_id !== band.query_id) hardIncorrect.push('query_id')
|
|
55
|
+
if (!session.currency) missing.push('currency')
|
|
56
|
+
else if (session.currency !== 'CNY') hardIncorrect.push('currency')
|
|
57
|
+
if (!session.source) missing.push('source')
|
|
58
|
+
else if (session.source !== 'ctrip-flight') hardIncorrect.push('source')
|
|
59
|
+
if (!session.verdict) missing.push('verdict')
|
|
60
|
+
else if (session.verdict !== 'hit') hardIncorrect.push('verdict')
|
|
61
|
+
|
|
62
|
+
if (!segment) {
|
|
63
|
+
missing.push(
|
|
64
|
+
'route_segments[0].from',
|
|
65
|
+
'route_segments[0].to',
|
|
66
|
+
'route_segments[0].departure_at',
|
|
67
|
+
'route_segments[0].arrival_at',
|
|
68
|
+
'route_segments[0].transport_number',
|
|
69
|
+
)
|
|
70
|
+
} else {
|
|
71
|
+
if (!segment.from) missing.push('route_segments[0].from')
|
|
72
|
+
else if (segment.from !== band.from) hardIncorrect.push('route_segments[0].from')
|
|
73
|
+
if (!segment.to) missing.push('route_segments[0].to')
|
|
74
|
+
else if (segment.to !== band.to) hardIncorrect.push('route_segments[0].to')
|
|
75
|
+
if (!segment.departure_at) missing.push('route_segments[0].departure_at')
|
|
76
|
+
else if (parseLocalDate(segment.departure_at) !== band.date) {
|
|
77
|
+
hardIncorrect.push('route_segments[0].departure_at.date')
|
|
78
|
+
}
|
|
79
|
+
if (!segment.arrival_at) missing.push('route_segments[0].arrival_at')
|
|
80
|
+
else if (parseLocalDate(segment.arrival_at) !== band.date) {
|
|
81
|
+
hardIncorrect.push('route_segments[0].arrival_at.date')
|
|
82
|
+
}
|
|
83
|
+
if (!segment.transport_number) missing.push('route_segments[0].transport_number')
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const softChecks: Array<{ path: string; ok: boolean }> = []
|
|
87
|
+
if (segment?.departure_at) {
|
|
88
|
+
const minutes = parseLocalMinutes(segment.departure_at)
|
|
89
|
+
softChecks.push({
|
|
90
|
+
path: 'route_segments[0].departure_at',
|
|
91
|
+
ok: minutes !== null
|
|
92
|
+
&& minutes >= parseHHmm(band.window_dep_local.earliest)
|
|
93
|
+
&& minutes <= parseHHmm(band.window_dep_local.latest),
|
|
94
|
+
})
|
|
95
|
+
}
|
|
96
|
+
if (segment?.arrival_at) {
|
|
97
|
+
const minutes = parseLocalMinutes(segment.arrival_at)
|
|
98
|
+
softChecks.push({
|
|
99
|
+
path: 'route_segments[0].arrival_at',
|
|
100
|
+
ok: minutes !== null
|
|
101
|
+
&& minutes >= parseHHmm(band.window_arr_local.earliest)
|
|
102
|
+
&& minutes <= parseHHmm(band.window_arr_local.latest),
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
if (segment?.transport_number) {
|
|
106
|
+
const actual = segment.transport_number.toUpperCase()
|
|
107
|
+
softChecks.push({
|
|
108
|
+
path: 'route_segments[0].transport_number',
|
|
109
|
+
ok: band.known_flights.some((flight) => actual.startsWith(flight.toUpperCase()))
|
|
110
|
+
|| band.known_flights.some((flight) => actual.includes(flight.toUpperCase().slice(0, 2))),
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
if (Number.isFinite(session.price) && session.price > 0) {
|
|
114
|
+
const margin = band.price_cny.max * 0.15
|
|
115
|
+
softChecks.push({
|
|
116
|
+
path: 'price',
|
|
117
|
+
ok: session.price >= band.price_cny.min - margin
|
|
118
|
+
&& session.price <= band.price_cny.max + margin,
|
|
119
|
+
})
|
|
120
|
+
} else missing.push('price')
|
|
121
|
+
|
|
122
|
+
const softIncorrect = softChecks.filter((check) => !check.ok).map((check) => check.path)
|
|
123
|
+
const incorrect = [...hardIncorrect, ...softIncorrect]
|
|
124
|
+
// 合同字段固定为 13,缺字段不能靠缩小分母抬高准确率。
|
|
125
|
+
const total = 13
|
|
126
|
+
const correct = Math.max(0, total - new Set(missing).size - incorrect.length)
|
|
127
|
+
const accuracy = total > 0 ? correct / total : 0
|
|
128
|
+
return {
|
|
129
|
+
pass: missing.length === 0
|
|
130
|
+
&& hardIncorrect.length === 0
|
|
131
|
+
&& accuracy >= SESSION_FIELD_ACCURACY_THRESHOLD,
|
|
132
|
+
total,
|
|
133
|
+
correct,
|
|
134
|
+
accuracy,
|
|
135
|
+
missing: Array.from(new Set(missing)),
|
|
136
|
+
incorrect,
|
|
137
|
+
golden_source: goldenSource,
|
|
138
|
+
}
|
|
139
|
+
}
|