@danceiny/gotry 0.0.1-rc.9 → 0.2.0-rc.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +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 -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 +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 +947 -110
- 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 +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-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 +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 +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 +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 +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 +382 -246
- package/dist/scripts/ledger-workflow-crash.js +10 -20
- 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 +161 -0
- package/dist/scripts/nightly-evidence.js +286 -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/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 +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-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 +690 -246
- 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 +226 -101
- package/dist/scripts/state-cli.js +550 -199
- 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 +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 +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 +48 -74
- 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 +2227 -882
- 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 +319 -160
- 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/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 +435 -516
- 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 +535 -359
- 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 +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 +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 +1948 -198
- 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 +223 -25
- 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 +354 -88
- package/ts/src/wish-pool.ts +79 -0
- package/dist/scripts/session-attach-wait.js +0 -41
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BFF-only HTTP/SSE seam for the embedded Booking Copilot.
|
|
3
|
+
*
|
|
4
|
+
* The browser never calls this server directly. A same-origin HotelByte BFF
|
|
5
|
+
* authenticates the actor, mints contextRef and sends a strict planner turn
|
|
6
|
+
* with a deployment-level Bearer key. No portal token field exists here.
|
|
7
|
+
*/
|
|
8
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
9
|
+
import { createServer } from 'node:http';
|
|
10
|
+
import { BOOKING_COPILOT_ACCEPTED_TURN_KINDS, BOOKING_COPILOT_INGRESS_MODES, BOOKING_READ_ACTION_KINDS, BOOKING_SURFACE_ALLOWED_ACTIONS, BOOKING_SURFACE_FEATURES, BOOKING_SURFACE_FEATURES_HEADER, BOOKING_SURFACE_SCHEMA_SHA256, BOOKING_SURFACE_SCHEMA_SHA256_HEADER, BOOKING_SURFACE_SCHEMA_VERSION, BOOKING_SURFACE_VERSION_HEADER, bookingSurfaceActionsForFeatures, } from './contracts.js';
|
|
11
|
+
import { bookingDigest, } from './runtime.js';
|
|
12
|
+
import { validateBookingSurface } from './validation.js';
|
|
13
|
+
import { extractBookingDispatchReason, normalizeBookingErrorCode, safeBookingErrorMessage } from './error-codes.js';
|
|
14
|
+
/**
|
|
15
|
+
* 单请求处理闭包(startBookingCopilotServer 的 createServer 回调原样摘出):
|
|
16
|
+
* 探活(/healthz /status)+ 唯一 turn 路由 + 鉴权 + schema 头校验。
|
|
17
|
+
* gotry-backend 的 booking-copilot 模块复用同一闭包,保证两条挂载路径行为逐字一致。
|
|
18
|
+
*/
|
|
19
|
+
export function bookingCopilotTrafficHandler(deps) {
|
|
20
|
+
const { apiKey, composition, maxBodyBytes, artifactId, ingressMode, runningIdentity } = deps;
|
|
21
|
+
return async (req, res) => {
|
|
22
|
+
const isProbe = req.method === 'GET' && (req.url === '/healthz' || req.url === '/status');
|
|
23
|
+
if (!isProbe && (req.method !== 'POST' || req.url !== '/a2a/booking-copilot/turn')) {
|
|
24
|
+
sendJson(res, 404, { error: { code: 'not_found' } });
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const auth = String(req.headers.authorization ?? '');
|
|
28
|
+
if (!safeSecretEqual(auth, `Bearer ${apiKey}`)) {
|
|
29
|
+
sendJson(res, 401, { error: { code: 'unauthorized' } });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (lifecycleFor(composition).shuttingDown) {
|
|
33
|
+
sendJson(res, 503, { error: { code: 'booking_copilot_shutting_down' } });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (isProbe) {
|
|
37
|
+
res.setHeader(BOOKING_SURFACE_VERSION_HEADER, BOOKING_SURFACE_SCHEMA_VERSION);
|
|
38
|
+
res.setHeader(BOOKING_SURFACE_SCHEMA_SHA256_HEADER, BOOKING_SURFACE_SCHEMA_SHA256);
|
|
39
|
+
res.setHeader(BOOKING_SURFACE_FEATURES_HEADER, BOOKING_SURFACE_FEATURES.join(','));
|
|
40
|
+
if (artifactId)
|
|
41
|
+
res.setHeader('X-GoTry-Artifact-ID', artifactId);
|
|
42
|
+
res.setHeader('X-GoTry-Node-Version', runningIdentity.nodeVersion);
|
|
43
|
+
res.setHeader('X-GoTry-Node-Modules-ABI', runningIdentity.nodeModulesAbi);
|
|
44
|
+
res.setHeader('X-GoTry-Release-Tuple', runningIdentity.releaseTuple);
|
|
45
|
+
if (runningIdentity.glibcVersion)
|
|
46
|
+
res.setHeader('X-GoTry-Glibc-Version', runningIdentity.glibcVersion);
|
|
47
|
+
const acceptedTurnKinds = ingressMode === 'bff-ingress-binding'
|
|
48
|
+
? [...BOOKING_COPILOT_ACCEPTED_TURN_KINDS, 'user.turn.ingress']
|
|
49
|
+
: [...BOOKING_COPILOT_ACCEPTED_TURN_KINDS];
|
|
50
|
+
const healthBody = {
|
|
51
|
+
schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION,
|
|
52
|
+
schemaSha256: BOOKING_SURFACE_SCHEMA_SHA256,
|
|
53
|
+
status: 'ready',
|
|
54
|
+
ingressMode,
|
|
55
|
+
acceptedTurnKinds,
|
|
56
|
+
features: [...BOOKING_SURFACE_FEATURES],
|
|
57
|
+
};
|
|
58
|
+
res.setHeader('X-GoTry-Ingress-Mode', ingressMode);
|
|
59
|
+
res.setHeader('X-GoTry-Accepted-Turn-Kinds', acceptedTurnKinds.join(','));
|
|
60
|
+
sendJson(res, 200, healthBody);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const schemaVersion = String(req.headers[BOOKING_SURFACE_VERSION_HEADER] ?? '');
|
|
64
|
+
const schemaHash = String(req.headers[BOOKING_SURFACE_SCHEMA_SHA256_HEADER] ?? '');
|
|
65
|
+
// `booking.surface` is the v1 compatibility line. Compatible additive
|
|
66
|
+
// releases keep this version and may have different schema bytes; the
|
|
67
|
+
// canonical payload validator below remains authoritative. Only a breaking
|
|
68
|
+
// contract line changes this version and is rejected at the version gate.
|
|
69
|
+
if (schemaVersion !== BOOKING_SURFACE_SCHEMA_VERSION) {
|
|
70
|
+
sendJson(res, 409, {
|
|
71
|
+
error: {
|
|
72
|
+
code: 'booking_surface_schema_mismatch',
|
|
73
|
+
expectedVersion: BOOKING_SURFACE_SCHEMA_VERSION,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (schemaHash && !/^[0-9a-f]{64}$/i.test(schemaHash)) {
|
|
79
|
+
sendJson(res, 400, { error: { code: 'booking_surface_schema_hash_invalid' } });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const lifecycle = lifecycleFor(composition);
|
|
83
|
+
lifecycle.acceptedRequests += 1;
|
|
84
|
+
try {
|
|
85
|
+
await handleBookingCopilotRequest(req, res, composition, maxBodyBytes, negotiatedBookingSurfaceFeatures(req));
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
finishAcceptedRequest(lifecycle);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const plannerLifecycles = new WeakMap();
|
|
93
|
+
const decisionFlightsByComposition = new WeakMap();
|
|
94
|
+
function lifecycleFor(composition) {
|
|
95
|
+
let lifecycle = plannerLifecycles.get(composition);
|
|
96
|
+
if (!lifecycle) {
|
|
97
|
+
lifecycle = {
|
|
98
|
+
active: new Map(),
|
|
99
|
+
closing: new Set(),
|
|
100
|
+
cleanupFailures: new Map(),
|
|
101
|
+
acceptedRequests: 0,
|
|
102
|
+
acceptedRequestDrainWaiters: new Set(),
|
|
103
|
+
shuttingDown: false,
|
|
104
|
+
};
|
|
105
|
+
plannerLifecycles.set(composition, lifecycle);
|
|
106
|
+
}
|
|
107
|
+
return lifecycle;
|
|
108
|
+
}
|
|
109
|
+
function finishAcceptedRequest(lifecycle) {
|
|
110
|
+
lifecycle.acceptedRequests -= 1;
|
|
111
|
+
if (lifecycle.acceptedRequests !== 0)
|
|
112
|
+
return;
|
|
113
|
+
for (const resolve of lifecycle.acceptedRequestDrainWaiters)
|
|
114
|
+
resolve();
|
|
115
|
+
lifecycle.acceptedRequestDrainWaiters.clear();
|
|
116
|
+
}
|
|
117
|
+
function drainAcceptedRequests(composition) {
|
|
118
|
+
const lifecycle = lifecycleFor(composition);
|
|
119
|
+
if (lifecycle.acceptedRequests === 0)
|
|
120
|
+
return Promise.resolve();
|
|
121
|
+
return new Promise((resolve) => lifecycle.acceptedRequestDrainWaiters.add(resolve));
|
|
122
|
+
}
|
|
123
|
+
function sessionsFor(composition) {
|
|
124
|
+
return lifecycleFor(composition).active;
|
|
125
|
+
}
|
|
126
|
+
function schedulePlannerSessionClose(composition, session) {
|
|
127
|
+
const lifecycle = lifecycleFor(composition);
|
|
128
|
+
const cleanup = Promise.resolve().then(() => session.close?.()).then(() => { lifecycle.cleanupFailures.delete(session); }, (error) => {
|
|
129
|
+
lifecycle.cleanupFailures.set(session, error);
|
|
130
|
+
// Cleanup must not replace an already-durable booking outcome. Keep the
|
|
131
|
+
// failure machine-readable and free of task/prompt/PII values. Session
|
|
132
|
+
// close promises are allowed to be sticky, so shutdown reports the
|
|
133
|
+
// original failure rather than retrying and laundering it into success.
|
|
134
|
+
console.error(JSON.stringify({ code: 'PLANNER_SESSION_CLOSE_FAILED' }));
|
|
135
|
+
});
|
|
136
|
+
lifecycle.closing.add(cleanup);
|
|
137
|
+
void cleanup.then(() => lifecycle.closing.delete(cleanup));
|
|
138
|
+
}
|
|
139
|
+
/** Detach a session from the request path and close it in the composition lifecycle. */
|
|
140
|
+
function releasePlannerSession(composition, taskId) {
|
|
141
|
+
const sessions = sessionsFor(composition);
|
|
142
|
+
const session = sessions.get(taskId);
|
|
143
|
+
if (!session)
|
|
144
|
+
return;
|
|
145
|
+
sessions.delete(taskId);
|
|
146
|
+
schedulePlannerSessionClose(composition, session);
|
|
147
|
+
}
|
|
148
|
+
async function drainPlannerSessions(composition) {
|
|
149
|
+
const lifecycle = lifecycleFor(composition);
|
|
150
|
+
for (const [taskId, session] of lifecycle.active) {
|
|
151
|
+
lifecycle.active.delete(taskId);
|
|
152
|
+
schedulePlannerSessionClose(composition, session);
|
|
153
|
+
}
|
|
154
|
+
while (lifecycle.closing.size > 0) {
|
|
155
|
+
await Promise.all([...lifecycle.closing]);
|
|
156
|
+
}
|
|
157
|
+
if (lifecycle.cleanupFailures.size > 0) {
|
|
158
|
+
throw new AggregateError([...lifecycle.cleanupFailures.values()], 'planner session cleanup failed during shutdown');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function drainDecisionFlights(composition) {
|
|
162
|
+
const flights = decisionFlightsFor(composition);
|
|
163
|
+
while (flights.size > 0) {
|
|
164
|
+
await Promise.allSettled([...flights.values()]);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Stop admitting turns and drain every planner resource owned by a composition.
|
|
169
|
+
* This is shared by the standalone listener and the unified gotry-backend
|
|
170
|
+
* module, whose listener is owned by the backend kernel.
|
|
171
|
+
*/
|
|
172
|
+
export function shutdownBookingCopilotTraffic(composition) {
|
|
173
|
+
const lifecycle = lifecycleFor(composition);
|
|
174
|
+
if (lifecycle.shutdownPromise)
|
|
175
|
+
return lifecycle.shutdownPromise;
|
|
176
|
+
lifecycle.shuttingDown = true;
|
|
177
|
+
lifecycle.shutdownPromise = (async () => {
|
|
178
|
+
await drainAcceptedRequests(composition);
|
|
179
|
+
await drainDecisionFlights(composition);
|
|
180
|
+
await drainPlannerSessions(composition);
|
|
181
|
+
})();
|
|
182
|
+
return lifecycle.shutdownPromise;
|
|
183
|
+
}
|
|
184
|
+
function decisionFlightsFor(composition) {
|
|
185
|
+
let flights = decisionFlightsByComposition.get(composition);
|
|
186
|
+
if (!flights) {
|
|
187
|
+
flights = new Map();
|
|
188
|
+
decisionFlightsByComposition.set(composition, flights);
|
|
189
|
+
}
|
|
190
|
+
return flights;
|
|
191
|
+
}
|
|
192
|
+
async function runDecisionSingleFlight(composition, key, work) {
|
|
193
|
+
const flights = decisionFlightsFor(composition);
|
|
194
|
+
const prior = flights.get(key);
|
|
195
|
+
if (prior)
|
|
196
|
+
return prior;
|
|
197
|
+
const current = work();
|
|
198
|
+
flights.set(key, current);
|
|
199
|
+
try {
|
|
200
|
+
return await current;
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
if (flights.get(key) === current)
|
|
204
|
+
flights.delete(key);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
export function runtimeIdentity() {
|
|
208
|
+
const report = process.report?.getReport?.();
|
|
209
|
+
const header = report?.header;
|
|
210
|
+
const glibcVersion = header?.glibcVersionRuntime ?? '';
|
|
211
|
+
return {
|
|
212
|
+
nodeVersion: process.version,
|
|
213
|
+
nodeModulesAbi: process.versions.modules ?? '',
|
|
214
|
+
releaseTuple: `${process.platform}-${process.arch}-${glibcVersion ? 'glibc' : 'unknown'}`,
|
|
215
|
+
glibcVersion,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function safeSecretEqual(actual, expected) {
|
|
219
|
+
const actualBytes = Buffer.from(actual);
|
|
220
|
+
const expectedBytes = Buffer.from(expected);
|
|
221
|
+
return actualBytes.length === expectedBytes.length && timingSafeEqual(actualBytes, expectedBytes);
|
|
222
|
+
}
|
|
223
|
+
function sendJson(res, status, body) {
|
|
224
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
225
|
+
res.end(JSON.stringify(body));
|
|
226
|
+
}
|
|
227
|
+
function readBody(req, maxBodyBytes) {
|
|
228
|
+
return new Promise((resolve, reject) => {
|
|
229
|
+
const chunks = [];
|
|
230
|
+
let size = 0;
|
|
231
|
+
let exceeded = false;
|
|
232
|
+
req.on('data', (chunk) => {
|
|
233
|
+
size += chunk.length;
|
|
234
|
+
if (size > maxBodyBytes) {
|
|
235
|
+
exceeded = true;
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
chunks.push(chunk);
|
|
239
|
+
});
|
|
240
|
+
req.on('end', () => exceeded ? reject(new Error('payload_too_large')) : resolve(Buffer.concat(chunks).toString('utf8')));
|
|
241
|
+
req.on('error', reject);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
function write(res, event) {
|
|
245
|
+
res.write(`event: ${event.kind}\ndata: ${JSON.stringify(event)}\n\n`);
|
|
246
|
+
res.flushHeaders();
|
|
247
|
+
res.socket?.setNoDelay(true);
|
|
248
|
+
res.socket?.uncork();
|
|
249
|
+
}
|
|
250
|
+
function flush(res) {
|
|
251
|
+
res.flushHeaders();
|
|
252
|
+
const maybeFlush = res;
|
|
253
|
+
maybeFlush.flush?.();
|
|
254
|
+
}
|
|
255
|
+
function sameCapabilities(a, b) {
|
|
256
|
+
return a.length === b.length && [...a].sort().every((kind, index) => kind === [...b].sort()[index]);
|
|
257
|
+
}
|
|
258
|
+
function exactKeys(value, allowed) {
|
|
259
|
+
return Object.keys(value).length === allowed.length && Object.keys(value).every((key) => allowed.includes(key));
|
|
260
|
+
}
|
|
261
|
+
const SURFACE_ACTION_MATRIX = BOOKING_SURFACE_ALLOWED_ACTIONS;
|
|
262
|
+
/**
|
|
263
|
+
* The authenticated BFF sends the fresh fleet-wide feature intersection in
|
|
264
|
+
* the same header that GoTry advertises on probes. Missing, malformed, and
|
|
265
|
+
* unknown values fail closed to the empty optional-feature set.
|
|
266
|
+
*/
|
|
267
|
+
function negotiatedBookingSurfaceFeatures(req) {
|
|
268
|
+
const raw = req.headers[BOOKING_SURFACE_FEATURES_HEADER];
|
|
269
|
+
if (typeof raw !== 'string' || raw.trim() === '')
|
|
270
|
+
return [];
|
|
271
|
+
const values = new Set(raw.split(',').map((value) => value.trim()).filter(Boolean));
|
|
272
|
+
return BOOKING_SURFACE_FEATURES.filter((feature) => values.has(feature));
|
|
273
|
+
}
|
|
274
|
+
function validSurfaceActions(surface, allowed) {
|
|
275
|
+
if (typeof surface !== 'string' || !Object.prototype.hasOwnProperty.call(SURFACE_ACTION_MATRIX, surface))
|
|
276
|
+
return false;
|
|
277
|
+
if (!Array.isArray(allowed) || allowed.length < 1 || new Set(allowed).size !== allowed.length)
|
|
278
|
+
return false;
|
|
279
|
+
const matrix = SURFACE_ACTION_MATRIX[surface];
|
|
280
|
+
return allowed.every((action) => typeof action === 'string'
|
|
281
|
+
&& matrix.includes(action)
|
|
282
|
+
&& BOOKING_READ_ACTION_KINDS.includes(action));
|
|
283
|
+
}
|
|
284
|
+
function validObservableOrders(value) {
|
|
285
|
+
if (!Array.isArray(value) || value.length > 100)
|
|
286
|
+
return false;
|
|
287
|
+
const orderRefs = new Set();
|
|
288
|
+
return value.every((item) => {
|
|
289
|
+
if (!item || typeof item !== 'object' || Array.isArray(item))
|
|
290
|
+
return false;
|
|
291
|
+
const candidate = item;
|
|
292
|
+
if (!exactKeys(candidate, ['orderRef', 'factRefs']) || typeof candidate.orderRef !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(candidate.orderRef) || orderRefs.has(candidate.orderRef))
|
|
293
|
+
return false;
|
|
294
|
+
if (!Array.isArray(candidate.factRefs) || candidate.factRefs.length > 64 || new Set(candidate.factRefs).size !== candidate.factRefs.length || candidate.factRefs.some((ref) => typeof ref !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(ref)))
|
|
295
|
+
return false;
|
|
296
|
+
orderRefs.add(candidate.orderRef);
|
|
297
|
+
return true;
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
function validBoundWorkspaceAuthority(workspace) {
|
|
301
|
+
return workspace.capabilities.surface === workspace.surface
|
|
302
|
+
&& validSurfaceActions(workspace.surface, workspace.capabilities.allowedActions)
|
|
303
|
+
&& (workspace.observableOrders === undefined || validObservableOrders(workspace.observableOrders));
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Old compatible BFF releases may still send the base action matrix. Fresh
|
|
307
|
+
* tasks are narrowed to the negotiated optional features instead of failing
|
|
308
|
+
* the whole turn. Existing task allowlists remain stable across a rolling
|
|
309
|
+
* deploy, while unsupported trusted projections are still removed so no new
|
|
310
|
+
* optional action can gain authority.
|
|
311
|
+
*/
|
|
312
|
+
function narrowWorkspaceOptionalAuthority(workspace, features, existingTaskActions) {
|
|
313
|
+
const negotiated = new Set(bookingSurfaceActionsForFeatures(workspace.surface, features));
|
|
314
|
+
if (existingTaskActions?.some((action) => !workspace.capabilities.allowedActions.includes(action)))
|
|
315
|
+
return null;
|
|
316
|
+
const allowedActions = existingTaskActions
|
|
317
|
+
? [...existingTaskActions]
|
|
318
|
+
: workspace.capabilities.allowedActions.filter((action) => negotiated.has(action));
|
|
319
|
+
if (allowedActions.length === 0)
|
|
320
|
+
return null;
|
|
321
|
+
const { observableOrders, ...base } = workspace;
|
|
322
|
+
return {
|
|
323
|
+
...base,
|
|
324
|
+
capabilities: { surface: workspace.surface, allowedActions },
|
|
325
|
+
...(features.includes('trusted-order-observation-v1') && observableOrders !== undefined
|
|
326
|
+
? { observableOrders: observableOrders.map((order) => ({ orderRef: order.orderRef, factRefs: [...order.factRefs] })) }
|
|
327
|
+
: {}),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
function validIngressBinding(binding, ingress) {
|
|
331
|
+
if (!binding || typeof binding !== 'object' || Array.isArray(binding))
|
|
332
|
+
return false;
|
|
333
|
+
const candidate = binding;
|
|
334
|
+
if (!exactKeys(candidate, ['taskId', 'turnId', 'contextRef', 'surface', 'allowedActions'])
|
|
335
|
+
&& !exactKeys(candidate, ['taskId', 'turnId', 'contextRef', 'surface', 'allowedActions', 'observableOrders']))
|
|
336
|
+
return false;
|
|
337
|
+
if (typeof candidate.taskId !== 'string' || typeof candidate.turnId !== 'string' || typeof candidate.contextRef !== 'string')
|
|
338
|
+
return false;
|
|
339
|
+
if (typeof candidate.surface !== 'string' || candidate.surface !== ingress.surfaceHint || !Object.prototype.hasOwnProperty.call(SURFACE_ACTION_MATRIX, candidate.surface))
|
|
340
|
+
return false;
|
|
341
|
+
return validSurfaceActions(candidate.surface, candidate.allowedActions)
|
|
342
|
+
&& (candidate.observableOrders === undefined || validObservableOrders(candidate.observableOrders));
|
|
343
|
+
}
|
|
344
|
+
function validIngressApprovalAuthority(ingress, binding) {
|
|
345
|
+
if (!ingress.request.approval)
|
|
346
|
+
return true;
|
|
347
|
+
return ingress.request.approval.taskId === binding.taskId
|
|
348
|
+
&& ingress.request.approval.contextRef === binding.contextRef;
|
|
349
|
+
}
|
|
350
|
+
function fallbackErrorEvent(task, code) {
|
|
351
|
+
return {
|
|
352
|
+
schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION,
|
|
353
|
+
eventId: `error-${task.taskId}-${task.lastSequence + 1}`,
|
|
354
|
+
taskId: task.taskId,
|
|
355
|
+
contextRef: task.contextRef,
|
|
356
|
+
sequence: task.lastSequence + 1,
|
|
357
|
+
emittedAt: new Date().toISOString(),
|
|
358
|
+
kind: 'error',
|
|
359
|
+
error: { code: normalizeBookingErrorCode(code), message: safeBookingErrorMessage(normalizeBookingErrorCode(code)), retryable: false },
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
function writeTypedError(res, composition, task, error, requestKey, includeSubmitted = false) {
|
|
363
|
+
const code = normalizeBookingErrorCode(error);
|
|
364
|
+
// Provider/adapter exceptions can contain response bodies, URLs or request
|
|
365
|
+
// context. Preserve only the closed code and coarse thrown-value class.
|
|
366
|
+
console.error('[booking-copilot] planner boundary error:', JSON.stringify({
|
|
367
|
+
code,
|
|
368
|
+
thrownType: error instanceof Error ? 'error' : typeof error,
|
|
369
|
+
}));
|
|
370
|
+
try {
|
|
371
|
+
const decision = { kind: 'error', error: { code, message: safeBookingErrorMessage(code), retryable: false } };
|
|
372
|
+
if (requestKey) {
|
|
373
|
+
const events = composition.runtime.applyDecisionBatch(task.taskId, requestKey, [decision], includeSubmitted, { suppressProgressStatuses: true });
|
|
374
|
+
for (const event of events)
|
|
375
|
+
write(res, event);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
write(res, composition.runtime.emitEvent(task.taskId, decision));
|
|
379
|
+
}
|
|
380
|
+
catch {
|
|
381
|
+
// The stream is already committed. Emit a schema-valid, non-durable
|
|
382
|
+
// typed envelope instead of silently returning an empty HTTP 200 stream.
|
|
383
|
+
write(res, fallbackErrorEvent(task, code));
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/** Handles one already-authenticated request inside the sole HTTP listener. */
|
|
387
|
+
async function handleBookingCopilotRequest(req, res, composition, maxBodyBytes, negotiatedFeatures) {
|
|
388
|
+
let turn;
|
|
389
|
+
let browserRequestKey;
|
|
390
|
+
let requestBinding;
|
|
391
|
+
let authorityFailureStatus = 403;
|
|
392
|
+
try {
|
|
393
|
+
const parsed = JSON.parse(await readBody(req, maxBodyBytes));
|
|
394
|
+
const valid = validateBookingSurface(parsed);
|
|
395
|
+
if (!valid.ok) {
|
|
396
|
+
sendJson(res, 400, { error: { code: 'invalid_booking_surface_turn', details: valid.errors } });
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (parsed.kind === 'user.turn.ingress') {
|
|
400
|
+
const ingress = parsed;
|
|
401
|
+
browserRequestKey = ingress.requestKey;
|
|
402
|
+
if (composition.ingressMode !== 'bff-ingress-binding' || !composition.ingressBinding || !composition.principal) {
|
|
403
|
+
sendJson(res, 503, { error: { code: 'trusted_ingress_binding_required', mode: composition.ingressMode ?? 'bff-bound-turn-only', acceptedTurnKinds: [...BOOKING_COPILOT_ACCEPTED_TURN_KINDS] } });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const binding = await composition.ingressBinding.bind(ingress, composition.principal);
|
|
407
|
+
if (!validIngressBinding(binding, ingress)) {
|
|
408
|
+
sendJson(res, 502, { error: { code: 'invalid_ingress_binding' } });
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (!validIngressApprovalAuthority(ingress, binding)) {
|
|
412
|
+
sendJson(res, 400, { error: { code: 'invalid_ingress_approval_authority' } });
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
const proposedBoundWorkspace = {
|
|
416
|
+
...ingress.workspace,
|
|
417
|
+
contextRef: binding.contextRef,
|
|
418
|
+
surface: binding.surface,
|
|
419
|
+
capabilities: { surface: binding.surface, allowedActions: [...binding.allowedActions] },
|
|
420
|
+
...(binding.observableOrders !== undefined ? { observableOrders: binding.observableOrders.map((order) => ({ orderRef: order.orderRef, factRefs: [...order.factRefs] })) } : {}),
|
|
421
|
+
};
|
|
422
|
+
const boundRequest = ingress.request.approval
|
|
423
|
+
? { text: ingress.request.text, approval: { ...ingress.request.approval } }
|
|
424
|
+
: { text: ingress.request.text };
|
|
425
|
+
turn = { schemaVersion: BOOKING_SURFACE_SCHEMA_VERSION, kind: 'user.turn', taskId: binding.taskId, turnId: binding.turnId, workspace: proposedBoundWorkspace, request: boundRequest };
|
|
426
|
+
requestBinding = { requestKey: ingress.requestKey, principal: composition.principal, ...(ingress.taskHandle ? { taskHandle: ingress.taskHandle } : {}) };
|
|
427
|
+
authorityFailureStatus = 502;
|
|
428
|
+
const boundValid = validateBookingSurface(turn);
|
|
429
|
+
if (!boundValid.ok) {
|
|
430
|
+
sendJson(res, 502, { error: { code: 'invalid_ingress_binding', details: boundValid.errors } });
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
else {
|
|
435
|
+
const boundTurn = parsed;
|
|
436
|
+
if (!validBoundWorkspaceAuthority(boundTurn.workspace)) {
|
|
437
|
+
sendJson(res, 403, { error: { code: 'invalid_bound_turn_authority' } });
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
turn = boundTurn;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
const code = normalizeBookingErrorCode(error);
|
|
445
|
+
sendJson(res, code === 'PAYLOAD_TOO_LARGE' ? 413 : 400, { error: { code } });
|
|
446
|
+
return;
|
|
447
|
+
}
|
|
448
|
+
let task;
|
|
449
|
+
let fresh = false;
|
|
450
|
+
let replayKey;
|
|
451
|
+
let replayEvents = null;
|
|
452
|
+
let activeDecisionKey;
|
|
453
|
+
try {
|
|
454
|
+
const existingAtAdmission = turn.taskId ? composition.runtime.resumeTask(turn.taskId) : null;
|
|
455
|
+
const narrowedWorkspace = narrowWorkspaceOptionalAuthority(turn.workspace, negotiatedFeatures, existingAtAdmission?.allowedActions);
|
|
456
|
+
if (!narrowedWorkspace) {
|
|
457
|
+
const code = authorityFailureStatus === 502 ? 'invalid_ingress_binding' : 'invalid_bound_turn_authority';
|
|
458
|
+
sendJson(res, authorityFailureStatus, { error: { code } });
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
turn = { ...turn, workspace: narrowedWorkspace };
|
|
462
|
+
fresh = turn.kind === 'action.receipt.continuation' ? false : existingAtAdmission === null;
|
|
463
|
+
if (turn.kind === 'action.receipt.continuation') {
|
|
464
|
+
replayKey = `receipt:${turn.receipt.actionId}:${composition.runtime.receiptDigest(turn.receipt)}`;
|
|
465
|
+
task = composition.runtime.continueWithReceipt(turn);
|
|
466
|
+
if (task.awaitingApproval) {
|
|
467
|
+
replayKey = composition.runtime.approvalPresentationRequestKey(turn.taskId);
|
|
468
|
+
}
|
|
469
|
+
replayEvents = composition.runtime.readDecisionBatch(turn.taskId, replayKey);
|
|
470
|
+
if (!replayEvents && task.phase === 'terminal')
|
|
471
|
+
replayEvents = composition.runtime.terminalDecisionBatch(turn.taskId, replayKey);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
const existing = existingAtAdmission;
|
|
475
|
+
if (requestBinding && turn.kind === 'user.turn')
|
|
476
|
+
composition.runtime.assertRequestBinding(browserRequestKey, turn, requestBinding);
|
|
477
|
+
const requestReplayKey = existing && browserRequestKey ? `ingress:${existing.taskId}:${browserRequestKey}` : undefined;
|
|
478
|
+
const directReplayKey = existing && turn.kind === 'user.turn' && turn.turnId ? `turn:${existing.taskId}:${turn.turnId}` : undefined;
|
|
479
|
+
const stableReplayKey = requestReplayKey ?? directReplayKey;
|
|
480
|
+
const stableReplay = stableReplayKey ? composition.runtime.readDecisionBatch(existing.taskId, stableReplayKey) : null;
|
|
481
|
+
if (stableReplay) {
|
|
482
|
+
if (existing && turn.kind === 'user.turn' && directReplayKey)
|
|
483
|
+
composition.runtime.assertTurnBinding(existing.taskId, turn);
|
|
484
|
+
task = existing;
|
|
485
|
+
replayKey = stableReplayKey;
|
|
486
|
+
replayEvents = stableReplay;
|
|
487
|
+
}
|
|
488
|
+
else if (existing?.phase === 'terminal' || existing?.phase === 'error') {
|
|
489
|
+
throw new Error('task_terminal');
|
|
490
|
+
}
|
|
491
|
+
else if (existing?.phase === 'waiting_receipt') {
|
|
492
|
+
if (turn.kind === 'user.turn' && turn.request.approval)
|
|
493
|
+
composition.runtime.assertApprovalNotConsumed(existing.taskId, turn.request.approval);
|
|
494
|
+
const workspace = turn.workspace;
|
|
495
|
+
if (workspace.contextRef !== existing.contextRef || workspace.surface !== existing.surface || workspace.revision !== existing.revision || workspace.capabilities.surface !== existing.surface || !sameCapabilities(workspace.capabilities.allowedActions, existing.allowedActions))
|
|
496
|
+
throw new Error('task_conflict:workspace_mismatch');
|
|
497
|
+
// A waiting task can only be replayed with the caller's stable turn
|
|
498
|
+
// identity. Without it, an identical sentence is not distinguishable
|
|
499
|
+
// from a new user turn and must not receive an old operation batch.
|
|
500
|
+
if (!turn.turnId || existing.lastTurnId !== turn.turnId)
|
|
501
|
+
throw new Error('receipt_required');
|
|
502
|
+
replayKey = browserRequestKey ? `ingress:${existing.taskId}:${browserRequestKey}` : `turn:${existing.taskId}:${existing.lastTurnId ?? existing.userTurnCount}`;
|
|
503
|
+
replayEvents = composition.runtime.readDecisionBatch(existing.taskId, replayKey);
|
|
504
|
+
if (!replayEvents)
|
|
505
|
+
throw new Error('receipt_required');
|
|
506
|
+
task = existing;
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
if (existing && turn.kind === 'user.turn' && turn.request.approval)
|
|
510
|
+
composition.runtime.assertApprovalNotConsumed(existing.taskId, turn.request.approval);
|
|
511
|
+
task = composition.runtime.startTask(turn, requestBinding);
|
|
512
|
+
if (turn.kind === 'user.turn' && turn.taskId) {
|
|
513
|
+
replayKey = browserRequestKey ? `ingress:${task.taskId}:${browserRequestKey}` : `turn:${task.taskId}:${turn.turnId ?? task.userTurnCount}`;
|
|
514
|
+
replayEvents = composition.runtime.readDecisionBatch(task.taskId, replayKey);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
const code = normalizeBookingErrorCode(error);
|
|
521
|
+
console.error(JSON.stringify({ code, reason: extractBookingDispatchReason(error) }));
|
|
522
|
+
sendJson(res, 409, { error: { code } });
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
res.writeHead(200, { 'content-type': 'text/event-stream; charset=utf-8', 'cache-control': 'no-cache, no-transform', connection: 'keep-alive', 'x-content-type-options': 'nosniff', [BOOKING_SURFACE_VERSION_HEADER]: BOOKING_SURFACE_SCHEMA_VERSION, [BOOKING_SURFACE_SCHEMA_SHA256_HEADER]: BOOKING_SURFACE_SCHEMA_SHA256 });
|
|
526
|
+
res.flushHeaders();
|
|
527
|
+
try {
|
|
528
|
+
if (replayEvents) {
|
|
529
|
+
const progressEvents = composition.runtime.readProgressBatch(task.taskId, replayKey ?? `turn:${task.taskId}:${task.userTurnCount}`) ?? [];
|
|
530
|
+
for (const event of progressEvents)
|
|
531
|
+
write(res, event);
|
|
532
|
+
if (progressEvents.length) {
|
|
533
|
+
flush(res);
|
|
534
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
535
|
+
}
|
|
536
|
+
if (task.phase === 'terminal' || task.phase === 'error')
|
|
537
|
+
releasePlannerSession(composition, task.taskId);
|
|
538
|
+
for (const event of replayEvents)
|
|
539
|
+
write(res, event);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
const requestKey = replayKey ?? `turn:${task.taskId}:${task.userTurnCount}`;
|
|
543
|
+
activeDecisionKey = requestKey;
|
|
544
|
+
// A concurrent identical request may arrive after the first request has
|
|
545
|
+
// committed progress but before its planner flight finishes. Re-serve
|
|
546
|
+
// the durable progress batch so it receives the same prefix instead of
|
|
547
|
+
// exposing a terminal-only tail.
|
|
548
|
+
const newlyPersistedProgress = composition.runtime.ensureProgressBatch(task.taskId, requestKey, fresh);
|
|
549
|
+
const progressEvents = newlyPersistedProgress.length > 0
|
|
550
|
+
? newlyPersistedProgress
|
|
551
|
+
: (composition.runtime.readProgressBatch(task.taskId, requestKey) ?? []);
|
|
552
|
+
const approval = turn.kind === 'user.turn' ? turn.request.approval : undefined;
|
|
553
|
+
const flightKey = JSON.stringify([task.taskId, approval ? `approval:${bookingDigest(approval)}` : requestKey]);
|
|
554
|
+
// Register or join the decision flight before yielding after the progress
|
|
555
|
+
// flush. A duplicate that observed the durable progress must never miss
|
|
556
|
+
// an about-to-finish flight and pay for a second model invocation.
|
|
557
|
+
const decisionOutcome = runDecisionSingleFlight(composition, flightKey, async () => {
|
|
558
|
+
// Cover the complementary late-join case: the prior flight may have
|
|
559
|
+
// committed its exact batch and left the in-memory map between request
|
|
560
|
+
// admission and this work starting.
|
|
561
|
+
const durableDecision = composition.runtime.readDecisionBatch(task.taskId, requestKey);
|
|
562
|
+
if (durableDecision)
|
|
563
|
+
return durableDecision;
|
|
564
|
+
if (turn.kind === 'action.receipt.continuation' && task.awaitingApproval) {
|
|
565
|
+
return composition.runtime.applyDecisionBatch(task.taskId, requestKey, [composition.runtime.approvalQuestion(task.taskId)], false);
|
|
566
|
+
}
|
|
567
|
+
const sessions = sessionsFor(composition);
|
|
568
|
+
const session = sessions.get(task.taskId) ?? composition.plannerFactory(task);
|
|
569
|
+
sessions.set(task.taskId, session);
|
|
570
|
+
const decisions = await session.next({ turn, task: composition.runtime.resumeTask(task.taskId) ?? task });
|
|
571
|
+
const plannerDecisions = decisions.length ? decisions : [{ kind: 'error', error: { code: 'PLANNER_NO_DECISION', message: 'Planner returned no typed decision.', retryable: false } }];
|
|
572
|
+
const events = composition.runtime.applyDecisionBatch(task.taskId, requestKey, plannerDecisions, fresh, { suppressProgressStatuses: true });
|
|
573
|
+
const updated = composition.runtime.resumeTask(task.taskId);
|
|
574
|
+
if (updated?.phase === 'terminal' || updated?.phase === 'error')
|
|
575
|
+
releasePlannerSession(composition, task.taskId);
|
|
576
|
+
return events;
|
|
577
|
+
}).then((events) => ({ ok: true, events }), (error) => ({ ok: false, error }));
|
|
578
|
+
for (const event of progressEvents)
|
|
579
|
+
write(res, event);
|
|
580
|
+
if (progressEvents.length) {
|
|
581
|
+
flush(res);
|
|
582
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
583
|
+
}
|
|
584
|
+
const outcome = await decisionOutcome;
|
|
585
|
+
if (!outcome.ok)
|
|
586
|
+
throw outcome.error;
|
|
587
|
+
for (const event of outcome.events)
|
|
588
|
+
write(res, event);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
catch (error) {
|
|
592
|
+
writeTypedError(res, composition, task, error, activeDecisionKey, fresh);
|
|
593
|
+
const updated = composition.runtime.resumeTask(task.taskId);
|
|
594
|
+
if (updated?.phase === 'terminal' || updated?.phase === 'error')
|
|
595
|
+
releasePlannerSession(composition, task.taskId);
|
|
596
|
+
}
|
|
597
|
+
finally {
|
|
598
|
+
res.end();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
export function startBookingCopilotServer(options) {
|
|
602
|
+
if (!options.apiKey)
|
|
603
|
+
return Promise.reject(new Error('booking_copilot_api_key_required'));
|
|
604
|
+
if (options.artifactId !== undefined && !/^[0-9a-f]{40}$/.test(options.artifactId)) {
|
|
605
|
+
return Promise.reject(new Error('booking_copilot_artifact_id_invalid'));
|
|
606
|
+
}
|
|
607
|
+
if (!options.runtime || !options.plannerFactory) {
|
|
608
|
+
return Promise.reject(new Error('booking_copilot_runtime_and_planner_required'));
|
|
609
|
+
}
|
|
610
|
+
const hasIngressBinding = Boolean(options.ingressBinding);
|
|
611
|
+
const hasPrincipal = Boolean(options.principal);
|
|
612
|
+
if (hasIngressBinding !== hasPrincipal) {
|
|
613
|
+
return Promise.reject(new Error('booking_copilot_ingress_binding_pair_required'));
|
|
614
|
+
}
|
|
615
|
+
if (hasIngressBinding && (typeof options.ingressBinding?.bind !== 'function'
|
|
616
|
+
|| typeof options.principal?.subject !== 'string' || options.principal.subject.length === 0 || options.principal.subject.length > 256
|
|
617
|
+
|| typeof options.principal?.scope !== 'string' || options.principal.scope.length === 0 || options.principal.scope.length > 256)) {
|
|
618
|
+
return Promise.reject(new Error('booking_copilot_ingress_binding_invalid'));
|
|
619
|
+
}
|
|
620
|
+
const ingressMode = options.ingressMode ?? (hasIngressBinding ? 'bff-ingress-binding' : 'bff-bound-turn-only');
|
|
621
|
+
if (!BOOKING_COPILOT_INGRESS_MODES.includes(ingressMode)) {
|
|
622
|
+
return Promise.reject(new Error('booking_copilot_ingress_mode_invalid'));
|
|
623
|
+
}
|
|
624
|
+
if (ingressMode === 'bff-ingress-binding' && !hasIngressBinding) {
|
|
625
|
+
return Promise.reject(new Error('booking_copilot_ingress_binding_required'));
|
|
626
|
+
}
|
|
627
|
+
if (ingressMode === 'bff-bound-turn-only' && hasIngressBinding) {
|
|
628
|
+
return Promise.reject(new Error('booking_copilot_ingress_mode_conflict'));
|
|
629
|
+
}
|
|
630
|
+
const composition = {
|
|
631
|
+
runtime: options.runtime,
|
|
632
|
+
plannerFactory: options.plannerFactory,
|
|
633
|
+
...(hasIngressBinding ? { ingressBinding: options.ingressBinding, principal: options.principal } : {}),
|
|
634
|
+
ingressMode,
|
|
635
|
+
};
|
|
636
|
+
const maxBodyBytes = options.maxBodyBytes ?? 1_000_000;
|
|
637
|
+
const runningIdentity = runtimeIdentity();
|
|
638
|
+
const server = createServer((req, res) => {
|
|
639
|
+
void bookingCopilotTrafficHandler({
|
|
640
|
+
apiKey: options.apiKey,
|
|
641
|
+
composition,
|
|
642
|
+
maxBodyBytes,
|
|
643
|
+
...(options.artifactId !== undefined ? { artifactId: options.artifactId } : {}),
|
|
644
|
+
ingressMode,
|
|
645
|
+
runningIdentity,
|
|
646
|
+
})(req, res);
|
|
647
|
+
});
|
|
648
|
+
let closePromise;
|
|
649
|
+
return new Promise((resolve, reject) => {
|
|
650
|
+
server.once('error', reject);
|
|
651
|
+
server.listen(options.port ?? 0, options.host ?? '127.0.0.1', () => {
|
|
652
|
+
server.off('error', reject);
|
|
653
|
+
const address = server.address();
|
|
654
|
+
const port = typeof address === 'object' && address ? address.port : (options.port ?? 0);
|
|
655
|
+
resolve({
|
|
656
|
+
server,
|
|
657
|
+
port,
|
|
658
|
+
close: () => {
|
|
659
|
+
if (closePromise)
|
|
660
|
+
return closePromise;
|
|
661
|
+
const trafficShutdown = shutdownBookingCopilotTraffic(composition);
|
|
662
|
+
closePromise = (async () => {
|
|
663
|
+
const listenerShutdown = new Promise((done, fail) => server.close((error) => error ? fail(error) : done()));
|
|
664
|
+
const results = await Promise.allSettled([listenerShutdown, trafficShutdown]);
|
|
665
|
+
const failures = results.flatMap((result) => result.status === 'rejected' ? [result.reason] : []);
|
|
666
|
+
if (failures.length > 0)
|
|
667
|
+
throw new AggregateError(failures, 'booking_copilot_server_close_failed');
|
|
668
|
+
})();
|
|
669
|
+
return closePromise;
|
|
670
|
+
},
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
}
|