@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,201 @@
|
|
|
1
|
+
/** Production composition entry for GoTry's BFF-only Booking Copilot server. */
|
|
2
|
+
|
|
3
|
+
import { mkdirSync } from 'node:fs'
|
|
4
|
+
import { isAbsolute, resolve } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { ensureLedger, type StateLedger } from '../state-ledger.ts'
|
|
7
|
+
import {
|
|
8
|
+
buildDshPlannerEnvironment,
|
|
9
|
+
createDshEmbeddedBookingPlanner,
|
|
10
|
+
type DshEmbeddedBookingPlannerHandle,
|
|
11
|
+
type DshEmbeddedBookingPlannerOptions,
|
|
12
|
+
} from './dsh-planner.ts'
|
|
13
|
+
import { BookingCopilotTaskRuntime } from './runtime.ts'
|
|
14
|
+
import { BOOKING_COPILOT_INGRESS_MODES, type BookingCopilotIngressMode, type BookingIngressBinding, type BookingIngressPrincipal } from './contracts.ts'
|
|
15
|
+
import {
|
|
16
|
+
startBookingCopilotServer,
|
|
17
|
+
type BookingCopilotServerHandle,
|
|
18
|
+
type BookingCopilotServerOptions,
|
|
19
|
+
} from './server.ts'
|
|
20
|
+
|
|
21
|
+
export interface BookingCopilotStartupConfig {
|
|
22
|
+
apiKey: string
|
|
23
|
+
stateRoot: string
|
|
24
|
+
host: string
|
|
25
|
+
port: number
|
|
26
|
+
artifactId?: string
|
|
27
|
+
/** Planner turn deadline in ms; the planner's own default applies when unset. */
|
|
28
|
+
plannerTurnTimeoutMs?: number
|
|
29
|
+
/** Defaults to bound-turn-only; a complete injected seam enables ingress. */
|
|
30
|
+
ingressMode: BookingCopilotIngressMode
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface BookingCopilotStartupHandle {
|
|
34
|
+
port: number
|
|
35
|
+
close(): Promise<void>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface BookingCopilotStartupDependencies {
|
|
39
|
+
ensureLedger(stateRoot: string): StateLedger
|
|
40
|
+
runtimeFactory(ledger: StateLedger): BookingCopilotTaskRuntime
|
|
41
|
+
createPlanner(options: DshEmbeddedBookingPlannerOptions): Promise<DshEmbeddedBookingPlannerHandle>
|
|
42
|
+
/** Optional in-process BFF identity seam; standalone use is bound-turn-only. */
|
|
43
|
+
ingressBinding?: BookingIngressBinding
|
|
44
|
+
principal?: BookingIngressPrincipal
|
|
45
|
+
startServer(options: BookingCopilotServerOptions): Promise<BookingCopilotServerHandle>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const DEFAULT_DEPENDENCIES: BookingCopilotStartupDependencies = {
|
|
49
|
+
ensureLedger,
|
|
50
|
+
runtimeFactory: (ledger) => new BookingCopilotTaskRuntime(ledger),
|
|
51
|
+
createPlanner: createDshEmbeddedBookingPlanner,
|
|
52
|
+
startServer: startBookingCopilotServer,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parsePort(raw: string | undefined): number {
|
|
56
|
+
if (raw === undefined || raw === '') return 3_082
|
|
57
|
+
const value = Number(raw)
|
|
58
|
+
if (!Number.isSafeInteger(value) || value < 0 || value > 65_535) throw new Error('booking_copilot_invalid_port')
|
|
59
|
+
return value
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Operator-tunable planner deadline; the bounds mirror the planner's own validation. */
|
|
63
|
+
function parsePlannerTurnTimeout(raw: string | undefined): number | undefined {
|
|
64
|
+
if (raw === undefined || raw.trim() === '') return undefined
|
|
65
|
+
const value = Number(raw)
|
|
66
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > 120_000) {
|
|
67
|
+
throw new Error('booking_copilot_planner_timeout_invalid')
|
|
68
|
+
}
|
|
69
|
+
return value
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function resolveBookingCopilotStartupConfig(
|
|
73
|
+
env: Record<string, string | undefined> = process.env,
|
|
74
|
+
): BookingCopilotStartupConfig {
|
|
75
|
+
const apiKey = env.GOTRY_BOOKING_COPILOT_API_KEY ?? ''
|
|
76
|
+
if (!apiKey) throw new Error('booking_copilot_api_key_required')
|
|
77
|
+
const stateRoot = env.GOTRY_BOOKING_COPILOT_STATE_ROOT ?? ''
|
|
78
|
+
if (!stateRoot) throw new Error('booking_copilot_state_root_required')
|
|
79
|
+
if (!isAbsolute(stateRoot)) throw new Error('booking_copilot_state_root_must_be_absolute')
|
|
80
|
+
const artifactId = env.GOTRY_BOOKING_COPILOT_ARTIFACT_ID || undefined
|
|
81
|
+
if (artifactId !== undefined && !/^[0-9a-f]{40}$/.test(artifactId)) {
|
|
82
|
+
throw new Error('booking_copilot_artifact_id_invalid')
|
|
83
|
+
}
|
|
84
|
+
const rawIngressMode = env.GOTRY_BOOKING_COPILOT_INGRESS_MODE
|
|
85
|
+
const ingressMode = rawIngressMode === undefined || rawIngressMode === '' ? 'bff-bound-turn-only' : rawIngressMode
|
|
86
|
+
if (!(BOOKING_COPILOT_INGRESS_MODES as readonly string[]).includes(ingressMode)) {
|
|
87
|
+
throw new Error('booking_copilot_ingress_mode_invalid')
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
apiKey,
|
|
91
|
+
stateRoot,
|
|
92
|
+
host: env.GOTRY_BOOKING_COPILOT_HOST || '127.0.0.1',
|
|
93
|
+
port: parsePort(env.GOTRY_BOOKING_COPILOT_PORT),
|
|
94
|
+
artifactId,
|
|
95
|
+
plannerTurnTimeoutMs: parsePlannerTurnTimeout(env.GOTRY_BOOKING_COPILOT_PLANNER_TIMEOUT_MS),
|
|
96
|
+
ingressMode: ingressMode as BookingCopilotIngressMode,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function closeAll(
|
|
101
|
+
server: BookingCopilotServerHandle | undefined,
|
|
102
|
+
planner: DshEmbeddedBookingPlannerHandle | undefined,
|
|
103
|
+
ledger: StateLedger | undefined,
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
const failures: unknown[] = []
|
|
106
|
+
for (const close of [
|
|
107
|
+
server ? () => server.close() : undefined,
|
|
108
|
+
planner ? () => planner.close() : undefined,
|
|
109
|
+
ledger ? () => Promise.resolve(ledger.close()) : undefined,
|
|
110
|
+
]) {
|
|
111
|
+
if (!close) continue
|
|
112
|
+
try { await close() } catch (error) { failures.push(error) }
|
|
113
|
+
}
|
|
114
|
+
if (failures.length === 1) throw failures[0]
|
|
115
|
+
if (failures.length > 1) throw new AggregateError(failures, 'booking_copilot_shutdown_failed')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Boot one process-scoped dsh core and task-scoped sessions behind the typed
|
|
120
|
+
* HTTP/SSE server. Only the HTTP layer receives the BFF deployment key.
|
|
121
|
+
*/
|
|
122
|
+
export async function startBookingCopilotFromEnvironment(
|
|
123
|
+
env: Record<string, string | undefined> = process.env,
|
|
124
|
+
dependencies: BookingCopilotStartupDependencies = DEFAULT_DEPENDENCIES,
|
|
125
|
+
): Promise<BookingCopilotStartupHandle> {
|
|
126
|
+
const config = resolveBookingCopilotStartupConfig(env)
|
|
127
|
+
const activeDependencies = dependencies
|
|
128
|
+
const hasIngressBinding = Boolean(activeDependencies.ingressBinding)
|
|
129
|
+
const hasPrincipal = Boolean(activeDependencies.principal)
|
|
130
|
+
if (hasIngressBinding !== hasPrincipal) throw new Error('booking_copilot_ingress_binding_pair_required')
|
|
131
|
+
const ingressPair = hasIngressBinding && hasPrincipal
|
|
132
|
+
if (ingressPair && env.GOTRY_BOOKING_COPILOT_INGRESS_MODE !== undefined && config.ingressMode === 'bff-bound-turn-only') {
|
|
133
|
+
throw new Error('booking_copilot_ingress_mode_conflict')
|
|
134
|
+
}
|
|
135
|
+
if (!ingressPair && config.ingressMode === 'bff-ingress-binding') throw new Error('booking_copilot_ingress_binding_required')
|
|
136
|
+
if (ingressPair && (typeof activeDependencies.ingressBinding?.bind !== 'function'
|
|
137
|
+
|| typeof activeDependencies.principal?.subject !== 'string' || activeDependencies.principal.subject.length === 0 || activeDependencies.principal.subject.length > 256
|
|
138
|
+
|| typeof activeDependencies.principal?.scope !== 'string' || activeDependencies.principal.scope.length === 0 || activeDependencies.principal.scope.length > 256)) {
|
|
139
|
+
throw new Error('booking_copilot_ingress_binding_invalid')
|
|
140
|
+
}
|
|
141
|
+
const activeIngressMode: BookingCopilotIngressMode = ingressPair ? 'bff-ingress-binding' : 'bff-bound-turn-only'
|
|
142
|
+
mkdirSync(config.stateRoot, { recursive: true })
|
|
143
|
+
let ledger: StateLedger | undefined
|
|
144
|
+
let planner: DshEmbeddedBookingPlannerHandle | undefined
|
|
145
|
+
let server: BookingCopilotServerHandle | undefined
|
|
146
|
+
try {
|
|
147
|
+
ledger = dependencies.ensureLedger(config.stateRoot)
|
|
148
|
+
const plannerOptions = {
|
|
149
|
+
stateRoot: config.stateRoot,
|
|
150
|
+
env: buildDshPlannerEnvironment(env),
|
|
151
|
+
...(config.plannerTurnTimeoutMs === undefined ? {} : { turnTimeoutMs: config.plannerTurnTimeoutMs }),
|
|
152
|
+
}
|
|
153
|
+
const runtime = activeDependencies.runtimeFactory(ledger)
|
|
154
|
+
planner = await activeDependencies.createPlanner(plannerOptions)
|
|
155
|
+
server = await activeDependencies.startServer({
|
|
156
|
+
apiKey: config.apiKey, runtime, plannerFactory: planner.plannerFactory,
|
|
157
|
+
...(ingressPair ? { ingressBinding: activeDependencies.ingressBinding, principal: activeDependencies.principal } : {}),
|
|
158
|
+
ingressMode: activeIngressMode,
|
|
159
|
+
host: config.host, port: config.port, artifactId: config.artifactId,
|
|
160
|
+
})
|
|
161
|
+
} catch (error) {
|
|
162
|
+
try { await closeAll(server, planner, ledger) } catch (cleanupError) {
|
|
163
|
+
throw new AggregateError([error, cleanupError], 'booking_copilot_startup_and_cleanup_failed')
|
|
164
|
+
}
|
|
165
|
+
throw error
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let closed = false
|
|
169
|
+
return {
|
|
170
|
+
port: server.port,
|
|
171
|
+
async close() {
|
|
172
|
+
if (closed) return
|
|
173
|
+
closed = true
|
|
174
|
+
await closeAll(server, planner, ledger)
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function runCli(): Promise<void> {
|
|
180
|
+
const handle = await startBookingCopilotFromEnvironment()
|
|
181
|
+
process.stderr.write(`[gotry-booking-copilot] listening on http://127.0.0.1:${handle.port}/a2a/booking-copilot/turn\n`)
|
|
182
|
+
let stopping = false
|
|
183
|
+
const stop = () => {
|
|
184
|
+
if (stopping) return
|
|
185
|
+
stopping = true
|
|
186
|
+
void handle.close().then(() => process.exit(0), (error) => {
|
|
187
|
+
process.stderr.write(`[gotry-booking-copilot] shutdown failed: ${error instanceof Error ? error.message : String(error)}\n`)
|
|
188
|
+
process.exit(1)
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
process.once('SIGINT', stop)
|
|
192
|
+
process.once('SIGTERM', stop)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const invokedPath = process.argv[1] ? resolve(process.argv[1]) : ''
|
|
196
|
+
if (invokedPath === fileURLToPath(import.meta.url)) {
|
|
197
|
+
runCli().catch((error) => {
|
|
198
|
+
process.stderr.write(`[gotry-booking-copilot] startup failed: ${error instanceof Error ? error.message : String(error)}\n`)
|
|
199
|
+
process.exit(1)
|
|
200
|
+
})
|
|
201
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { Ajv2020, type ValidateFunction } from 'ajv/dist/2020.js'
|
|
3
|
+
import { BOOKING_READ_ACTION_KINDS, BOOKING_BLOCKER_CODES, BOOKING_GAP_CODES } from './contracts.ts'
|
|
4
|
+
|
|
5
|
+
const schema = JSON.parse(readFileSync(new URL('../../../schemas/booking.surface.schema.json', import.meta.url), 'utf8')) as Record<string, unknown>
|
|
6
|
+
const intentSchema = JSON.parse(readFileSync(new URL('../../../schemas/booking.intent.schema.json', import.meta.url), 'utf8')) as Record<string, unknown>
|
|
7
|
+
/** Canonical schema bytes (single source of truth) for prompt/tool derivations elsewhere. */
|
|
8
|
+
export const bookingSurfaceSchema = schema
|
|
9
|
+
const ajv = new Ajv2020({ allErrors: true, strict: true })
|
|
10
|
+
ajv.addSchema(schema)
|
|
11
|
+
const validate: ValidateFunction = ajv.compile(schema)
|
|
12
|
+
const validateActionCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Action` })
|
|
13
|
+
const validateBlockerCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Blocker` })
|
|
14
|
+
const validateApprovalCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Approval` })
|
|
15
|
+
const validateEventCanonical = ajv.compile({ $ref: `${schema.$id}#/$defs/Event` })
|
|
16
|
+
const validateIntentCanonical = ajv.compile(intentSchema)
|
|
17
|
+
const kinds = new Set<string>(BOOKING_READ_ACTION_KINDS)
|
|
18
|
+
const codes = new Set<string>(BOOKING_BLOCKER_CODES)
|
|
19
|
+
const gapCodes = new Set<string>(BOOKING_GAP_CODES)
|
|
20
|
+
const safeOpaque = (value: unknown): value is string => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]*$/.test(value)
|
|
21
|
+
const safeRequestKey = (value: unknown): value is string => typeof value === 'string' && /^[A-Za-z0-9][A-Za-z0-9:._-]{7,127}$/.test(value) && !secret.test(value)
|
|
22
|
+
const secret = /(?:Bearer\s+\S|\bsk-(?!ill(?:s)?\b)[A-Za-z0-9_-]{12,}|\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)/i
|
|
23
|
+
// `.example` is reserved for documentation, so these identifiers cannot be a
|
|
24
|
+
// real mailbox. Do not allowlist lookalikes on routable domains.
|
|
25
|
+
const technicalEmail = /^(?:schema|version)@[a-z0-9-]+\.example$/i
|
|
26
|
+
const currencyAmount = /\b(?:[A-Z]{3}\s*\d+(?:\.\d+)?|\d+(?:\.\d+)?\s*[A-Z]{3})\b/
|
|
27
|
+
const rfc3339DateTime = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,9}))?(Z|([+-])(\d{2}):(\d{2}))$/
|
|
28
|
+
|
|
29
|
+
export function isBookingDateTime(value: unknown): value is string {
|
|
30
|
+
if (typeof value !== 'string') return false
|
|
31
|
+
const match = rfc3339DateTime.exec(value)
|
|
32
|
+
if (!match) return false
|
|
33
|
+
const [, yearText, monthText, dayText, hourText, minuteText, secondText] = match
|
|
34
|
+
const year = Number(yearText)
|
|
35
|
+
const month = Number(monthText)
|
|
36
|
+
const day = Number(dayText)
|
|
37
|
+
const hour = Number(hourText)
|
|
38
|
+
const minute = Number(minuteText)
|
|
39
|
+
const second = Number(secondText)
|
|
40
|
+
const offsetHour = match[9] === undefined ? 0 : Number(match[9])
|
|
41
|
+
const offsetMinute = match[10] === undefined ? 0 : Number(match[10])
|
|
42
|
+
if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) return false
|
|
43
|
+
const daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate()
|
|
44
|
+
if (day < 1 || day > daysInMonth) return false
|
|
45
|
+
return !Number.isNaN(Date.parse(value))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizedWords(value: string): string {
|
|
49
|
+
return value.normalize('NFKC').replace(/[^\p{L}\p{N}.]+/gu, ' ').trim()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function hasSupplierCost(value: string): boolean {
|
|
53
|
+
const normalized = normalizedWords(value)
|
|
54
|
+
const lower = normalized.toLowerCase()
|
|
55
|
+
return (
|
|
56
|
+
/\b(?:supplier|vendor|internal)\b/.test(lower) &&
|
|
57
|
+
/\b(?:cost|rate|price)\b/.test(lower) &&
|
|
58
|
+
currencyAmount.test(normalized.toUpperCase())
|
|
59
|
+
)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function hasInternationalPhone(value: string): boolean {
|
|
63
|
+
const normalized = value.normalize('NFKC')
|
|
64
|
+
for (const plusIndex of normalized.split('').flatMap((char, index) => char === '+' ? [index] : [])) {
|
|
65
|
+
let digits = ''
|
|
66
|
+
for (const char of normalized.slice(plusIndex + 1, plusIndex + 48)) {
|
|
67
|
+
if (/\d/u.test(char)) digits += char
|
|
68
|
+
else if (/\p{L}|@/u.test(char)) break
|
|
69
|
+
if (digits.length > 15) break
|
|
70
|
+
}
|
|
71
|
+
if (/^[1-9]\d{7,14}$/.test(digits)) return true
|
|
72
|
+
}
|
|
73
|
+
return false
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function hasPaymentCard(value: string): boolean {
|
|
77
|
+
const normalized = value.normalize('NFKC')
|
|
78
|
+
const candidates = normalized.match(/(?:\d[ -]?){12,18}\d/g) ?? []
|
|
79
|
+
const explicitlyLabeled = /\b(?:card|payment|visa|mastercard)\b/i.test(normalized)
|
|
80
|
+
return candidates.some((candidate) => (explicitlyLabeled || /[ -]/.test(candidate)) && luhn(candidate))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function hasSensitiveText(value: string, opts: { allowOpaqueNumericId?: boolean } = {}): boolean {
|
|
84
|
+
const normalized = value.normalize('NFKC')
|
|
85
|
+
if (normalized.includes('@') && !technicalEmail.test(normalized.trim())) return true
|
|
86
|
+
return secret.test(normalized) || hasInternationalPhone(normalized) || hasSupplierCost(normalized) || (!opts.allowOpaqueNumericId && hasPaymentCard(normalized))
|
|
87
|
+
}
|
|
88
|
+
function luhn(value: string): boolean { const digits = value.replaceAll(/\D/g, ''); if (digits.length < 13 || digits.length > 19) return false; let sum = 0; for (let i = digits.length - 1, parity = 0; i >= 0; i--, parity++) { let n = Number(digits[i]); if (parity % 2 === 1) { n *= 2; if (n > 9) n -= 9 } sum += n } return sum % 10 === 0 }
|
|
89
|
+
const opaqueFieldNames = new Set([
|
|
90
|
+
'eventId', 'taskId', 'contextRef', 'actionId', 'hotelRef', 'offerRef', 'offerVersionRef', 'checkedOfferVersionRef', 'currentOfferVersionRef', 'verifiedOfferRef',
|
|
91
|
+
'orderRef', 'searchSessionRef', 'handoffRef', 'questionId', 'blockerId', 'approvalId',
|
|
92
|
+
'sourceActionId', 'targetActionId', 'sourceTurnId', 'turnId', 'requestKey', 'taskHandle',
|
|
93
|
+
'nonce', 'deliveryNonce', 'optionDigest', 'sourceReceiptDigest', 'valueDigest',
|
|
94
|
+
])
|
|
95
|
+
function isOpaqueFieldPath(path: string): boolean {
|
|
96
|
+
const clean = path.replace(/\[\]$/g, '')
|
|
97
|
+
const field = clean.split('.').at(-1)
|
|
98
|
+
return field ? opaqueFieldNames.has(field) || field.endsWith('Ref') || field.endsWith('Refs') : false
|
|
99
|
+
}
|
|
100
|
+
const scan = (v: unknown, path = '$'): boolean => typeof v === 'string'
|
|
101
|
+
? hasSensitiveText(v, { allowOpaqueNumericId: isOpaqueFieldPath(path) })
|
|
102
|
+
: Array.isArray(v)
|
|
103
|
+
? v.some((child) => scan(child, `${path}[]`))
|
|
104
|
+
: !!v && typeof v === 'object'
|
|
105
|
+
? Object.entries(v).some(([k,x]) => /cost|price/i.test(k) ? /supplier|internal|wholesale/i.test(String(x)) : scan(x, `${path}.${k}`))
|
|
106
|
+
: false
|
|
107
|
+
export function isSafeBookingRequestKey(value: unknown): value is string { return safeRequestKey(value) }
|
|
108
|
+
export type BookingSurfaceValidationResult = { ok: true } | { ok: false; errors: string[] }
|
|
109
|
+
function errors(v: ValidateFunction): string[] { return (v.errors ?? []).map(e => `${e.instancePath || '$'}: ${e.message ?? e.keyword}`) }
|
|
110
|
+
function workspaceErrors(workspace: Record<string, any> | undefined, path: string): string[] {
|
|
111
|
+
const e: string[] = []
|
|
112
|
+
if (!workspace || typeof workspace !== 'object' || Array.isArray(workspace)) return e
|
|
113
|
+
const loadedOffers = Array.isArray(workspace.loadedOffers) ? workspace.loadedOffers : []
|
|
114
|
+
const offerRefs = loadedOffers.map((offer: any) => offer?.offerRef).filter((ref: unknown): ref is string => typeof ref === 'string')
|
|
115
|
+
const offerVersionRefs = loadedOffers.map((offer: any) => offer?.offerVersionRef).filter((ref: unknown): ref is string => typeof ref === 'string')
|
|
116
|
+
if (new Set(offerRefs).size !== offerRefs.length) e.push(`${path}.loadedOffers: duplicate offerRef`)
|
|
117
|
+
if (new Set(offerVersionRefs).size !== offerVersionRefs.length) e.push(`${path}.loadedOffers: duplicate offerVersionRef`)
|
|
118
|
+
const shortlisted = Array.isArray(workspace.shortlistedOfferRefs) ? workspace.shortlistedOfferRefs : []
|
|
119
|
+
if (shortlisted.some((ref: unknown) => typeof ref !== 'string' || !loadedOffers.some((offer: any) => offer?.offerRef === ref))) {
|
|
120
|
+
e.push(`${path}.shortlistedOfferRefs: every ref must be a loaded offer`)
|
|
121
|
+
}
|
|
122
|
+
if (workspace.selectedOfferRef !== undefined && !loadedOffers.some((offer: any) => offer?.offerRef === workspace.selectedOfferRef)) {
|
|
123
|
+
e.push(`${path}.selectedOfferRef: loaded offer required`)
|
|
124
|
+
}
|
|
125
|
+
const verified = workspace.verifiedOffer
|
|
126
|
+
if (verified !== undefined) {
|
|
127
|
+
if (!loadedOffers.some((offer: any) => offer?.offerRef === verified.offerRef && offer?.offerVersionRef === verified.offerVersionRef)) e.push(`${path}.verifiedOffer: loaded offer version required`)
|
|
128
|
+
if (!safeOpaque(verified.verifiedOfferRef) || !isBookingDateTime(verified.expiresAt)) e.push(`${path}.verifiedOffer: safe ref and parseable expiry required`)
|
|
129
|
+
}
|
|
130
|
+
return e
|
|
131
|
+
}
|
|
132
|
+
function cross(value: unknown): string[] {
|
|
133
|
+
const e: string[] = []
|
|
134
|
+
if (scan(value)) e.push('$: unsafe PII/payment/supplier-cost/credential text')
|
|
135
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return e
|
|
136
|
+
const x = value as Record<string, any>
|
|
137
|
+
const semantic = x.kind === 'action.receipt.continuation' ? x.receipt : x
|
|
138
|
+
if (x.workspace && x.kind !== 'user.turn.ingress') e.push(...workspaceErrors(x.workspace, '$.workspace'))
|
|
139
|
+
if (x.schemaVersion === 'booking.surface' && Array.isArray(x.loadedOffers)) e.push(...workspaceErrors(x, '$'))
|
|
140
|
+
if (x.kind === 'user.turn.ingress') {
|
|
141
|
+
if (!safeRequestKey(x.requestKey)) e.push('$.requestKey: safe opaque request key required')
|
|
142
|
+
const workspace = x.workspace as Record<string, any> | undefined
|
|
143
|
+
e.push(...workspaceErrors(workspace, '$.workspace'))
|
|
144
|
+
const visibleHotels = Array.isArray(workspace?.visibleHotels) ? workspace.visibleHotels : []
|
|
145
|
+
const loadedOffers = Array.isArray(workspace?.loadedOffers) ? workspace.loadedOffers : []
|
|
146
|
+
if (workspace?.focusedHotelRef !== undefined && !visibleHotels.some((hotel: any) => hotel?.hotelRef === workspace.focusedHotelRef)) e.push('$.workspace.focusedHotelRef: visible hotel required')
|
|
147
|
+
const selectedOffer = workspace?.selectedOfferRef === undefined ? undefined : loadedOffers.find((offer: any) => offer?.offerRef === workspace.selectedOfferRef)
|
|
148
|
+
if (workspace?.selectedOfferRef !== undefined && !selectedOffer) e.push('$.workspace.selectedOfferRef: loaded offer required')
|
|
149
|
+
if (selectedOffer && !visibleHotels.some((hotel: any) => hotel?.hotelRef === selectedOffer.hotelRef)) e.push('$.workspace.selectedOfferRef: offer hotel must be visible')
|
|
150
|
+
}
|
|
151
|
+
if (semantic !== x && semantic?.resultContract?.hardCriteriaMet === true && semantic.resultContract.blockers?.length) e.push('$.receipt.resultContract: hardCriteriaMet cannot coexist with blockers')
|
|
152
|
+
if (kinds.has(x.kind)) {
|
|
153
|
+
const allowed: Record<string, string[]> = {
|
|
154
|
+
'search.patch': ['patch'], 'search.run': [], 'results.view.patch': ['patch'],
|
|
155
|
+
'hotel.focus': ['hotelRef'], 'hotel.select': ['hotelRef'],
|
|
156
|
+
'offers.query': ['hotelRefs', 'criteria'], 'offers.view.patch': ['hotelRef', 'criteria'],
|
|
157
|
+
'offers.compare': ['offerRefs', 'requestedCount'], 'offer.select': ['offerRef', 'offerVersionRef'],
|
|
158
|
+
'offer.check': ['offerRef', 'offerVersionRef'], 'checkout.prepare': ['offerRef', 'offerVersionRef', 'verifiedOfferRef'], 'order.observe': ['orderRef'],
|
|
159
|
+
}
|
|
160
|
+
const input = x.input
|
|
161
|
+
if (!input || typeof input !== 'object' || Array.isArray(input) || Object.keys(input).some(k => !allowed[x.kind].includes(k))) e.push('$.input: closed kind-specific input required')
|
|
162
|
+
}
|
|
163
|
+
if (x.kind === 'action.receipt' && x.resultContract?.hardCriteriaMet === true && x.resultContract.blockers?.length) e.push('$.resultContract: hardCriteriaMet cannot coexist with blockers')
|
|
164
|
+
if (x.kind === 'user.turn' && Array.isArray(x.request?.approval)) e.push('$.request.approval: at most one approval')
|
|
165
|
+
if (x.kind === 'action.receipt' || x.kind === 'action.receipt.continuation') {
|
|
166
|
+
const r = x.kind === 'action.receipt.continuation' ? x.receipt : x
|
|
167
|
+
if (r?.resultContract?.gapCodes?.some((code: unknown) => !gapCodes.has(String(code)))) e.push('$.resultContract.gapCodes: unregistered gap code')
|
|
168
|
+
const observation = r?.observation
|
|
169
|
+
if (observation?.kind === 'offer.availability') {
|
|
170
|
+
if (r.status === 'applied' && (observation.available !== true || !observation.verifiedOfferRef || observation.currentOfferVersionRef !== observation.checkedOfferVersionRef || observation.changedFactRefs?.length || observation.gapCodes?.length)) e.push('$.observation: applied availability requires unchanged checked/current version, verified ref, and no gaps')
|
|
171
|
+
if (r.status === 'applied' && (r.resultContract?.outcome !== 'complete' || r.resultContract?.hardCriteriaMet !== true || r.resultContract?.gapCodes?.length || r.resultContract?.blockers?.length)) e.push('$.resultContract: applied availability requires complete hard-criteria result without gaps or blockers')
|
|
172
|
+
if (r.status === 'changed' && (observation.available !== true || !observation.currentOfferVersionRef || observation.currentOfferVersionRef === observation.checkedOfferVersionRef || !observation.changedFactRefs?.length || observation.verifiedOfferRef)) e.push('$.observation: changed availability requires available replacement current version without verified ref')
|
|
173
|
+
if ((r.status === 'unavailable' || r.status === 'no_match') && (observation.available !== false || observation.currentOfferVersionRef || observation.verifiedOfferRef)) e.push('$.observation: negative availability must not expose current or verified version')
|
|
174
|
+
if (r.status !== 'applied' && observation.verifiedOfferRef) e.push('$.observation: only applied availability may expose verified ref')
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const blocker = x.blocker ?? x.question?.blocker
|
|
178
|
+
if (blocker && !/^(searchDraft|results|offers|checkout)(\.[A-Za-z][A-Za-z0-9_]*){0,3}$/.test(blocker.criterionPath ?? '')) e.push('$.blocker.criterionPath: closed criterion path required')
|
|
179
|
+
if (blocker?.evidence?.factRefs && new Set(blocker.evidence.factRefs).size !== blocker.evidence.factRefs.length) e.push('$.blocker.evidence.factRefs: unique refs required')
|
|
180
|
+
if (x.kind === 'question' && blocker && Array.isArray(x.question?.approvalOptions)) {
|
|
181
|
+
for (const [i, option] of x.question.approvalOptions.entries()) {
|
|
182
|
+
const result = validateApprovalAgainstBlocker(option?.approval, blocker)
|
|
183
|
+
if (!result.ok) e.push(...result.errors.map(error => `$.question.approvalOptions[${i}].${error.slice(2)}`))
|
|
184
|
+
if (option?.approval?.taskId !== x.taskId) e.push(`$.question.approvalOptions[${i}].approval.taskId: must match event`)
|
|
185
|
+
if (option?.approval?.contextRef !== x.contextRef) e.push(`$.question.approvalOptions[${i}].approval.contextRef: must match event`)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (kinds.has(String(x.kind)) && x.relaxationApprovalRef) {
|
|
189
|
+
const ref = x.relaxationApprovalRef
|
|
190
|
+
if (ref.contextRef !== x.contextRef) e.push('$.relaxationApprovalRef.contextRef: must match action')
|
|
191
|
+
if (ref.sourceRevision !== x.expectedRevision) e.push('$.relaxationApprovalRef.sourceRevision: must equal action revision')
|
|
192
|
+
if (ref.targetActionKind !== x.kind) e.push('$.relaxationApprovalRef.targetActionKind: must match action kind')
|
|
193
|
+
if (ref.targetActionId !== x.actionId) e.push('$.relaxationApprovalRef.targetActionId: must match action id')
|
|
194
|
+
// Expiry and one-time consumption are runtime concerns; validation checks only structure/binding.
|
|
195
|
+
if (!safeOpaque(ref.nonce) || !isBookingDateTime(ref.expiresAt)) e.push('$.relaxationApprovalRef: invalid expiry or nonce')
|
|
196
|
+
}
|
|
197
|
+
if (x.kind === 'question' && (!Array.isArray(x.question?.approvalOptions) || x.question.approvalOptions.length < 1 || x.question.approvalOptions.length > 2)) e.push('$.question.approvalOptions: one or two options required')
|
|
198
|
+
return e
|
|
199
|
+
}
|
|
200
|
+
export function validateBookingSurface(value: unknown): BookingSurfaceValidationResult { const ok = validate(value); const e = ok ? [] : errors(validate); e.push(...cross(value)); return e.length ? { ok: false, errors: e } : { ok: true } }
|
|
201
|
+
function fromCanonical(fn: ValidateFunction, value: unknown): BookingSurfaceValidationResult { const ok = fn(value); const e = ok ? [] : errors(fn); e.push(...cross(value)); return e.length ? { ok: false, errors: e } : { ok: true } }
|
|
202
|
+
export function validateBookingReadAction(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateActionCanonical, value) }
|
|
203
|
+
export function validateCriterionBlocker(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateBlockerCanonical, value) }
|
|
204
|
+
export function validateRelaxationApproval(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateApprovalCanonical, value) }
|
|
205
|
+
export function validateBookingSurfaceEvent(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateEventCanonical, value) }
|
|
206
|
+
export function validateBookingIntentProjection(value: unknown): BookingSurfaceValidationResult { return fromCanonical(validateIntentCanonical, value) }
|
|
207
|
+
export function validateApprovalAgainstBlocker(approval: unknown, blocker: unknown): BookingSurfaceValidationResult {
|
|
208
|
+
const a = approval as any
|
|
209
|
+
const b = blocker as any
|
|
210
|
+
const blockerResult = validateCriterionBlocker(b)
|
|
211
|
+
if (!blockerResult.ok) return blockerResult
|
|
212
|
+
const approvalResult = validateRelaxationApproval(a)
|
|
213
|
+
if (!approvalResult.ok) return approvalResult
|
|
214
|
+
const fields = ['blockerId', 'sourceActionId', 'sourceReceiptDigest', 'scope', 'code', 'criterionPath', 'valueDigest']
|
|
215
|
+
const mismatches = fields.filter(field => a[field] !== b?.[field])
|
|
216
|
+
return mismatches.length ? { ok: false, errors: mismatches.map(field => `$.${field}: approval does not match blocker`) } : { ok: true }
|
|
217
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 同行人档案(memory-design M5 层,P2):同行人及其约束(行动/健康/偏好)的持久化。
|
|
3
|
+
*
|
|
4
|
+
* 立场(memory-design §1):
|
|
5
|
+
* - 溯源 P0:每条约束伴 evidence(用户原话),追加不删史;
|
|
6
|
+
* - 约束只进排序与行程结构建议,永不进硬过滤;
|
|
7
|
+
* - **负面清单守卫**:证件号/电话/地址等敏感字段拒收入库(需要时后端从登录态
|
|
8
|
+
* 填充,fail-closed)——health 存的是「高血压/晕车」这类行为约束,不是病历;
|
|
9
|
+
* - upsert 合并语义同 mergeProfile:追加不删史/同内容幂等。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export interface CompanionConstraints {
|
|
13
|
+
/** 行动/体力:如「走不了一段」「坐轮椅」 */
|
|
14
|
+
mobility?: string
|
|
15
|
+
/** 健康类行为约束:如「轻度高血压」「晕车」(非病历,禁止诊断性描述) */
|
|
16
|
+
health?: string[]
|
|
17
|
+
/** 偏好:如「怕吵」「爱吃辣」 */
|
|
18
|
+
prefs?: string[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CompanionProfile {
|
|
22
|
+
schema: 'companion_profile.v1'
|
|
23
|
+
/** 稳定主键:label 语义派生(去空白) */
|
|
24
|
+
companion_id: string
|
|
25
|
+
label: string
|
|
26
|
+
constraints: CompanionConstraints
|
|
27
|
+
/** 用户原话,追加不删史 */
|
|
28
|
+
evidence: string[]
|
|
29
|
+
ts: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 负面清单:命中即拒收(红线 6 + 隐私立场) */
|
|
33
|
+
const SENSITIVE_PATTERNS: Array<{ re: RegExp; label: string }> = [
|
|
34
|
+
{ re: /\d{15,17}[Xx]?/, label: '疑似证件号/卡号' },
|
|
35
|
+
{ re: /1[3-9]\d{9}/, label: '疑似手机号' },
|
|
36
|
+
{ re: /(护照|身份证|证件号|通行证)\s*号?\s*[::]?\s*\w+/i, label: '证件信息' },
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
/** 守门:内容命中负面清单 → 返回拒收理由;否则 null */
|
|
40
|
+
export function sensitiveViolation(text: string): string | null {
|
|
41
|
+
for (const { re, label } of SENSITIVE_PATTERNS) {
|
|
42
|
+
if (re.test(text)) return `负面清单拒收:${label}不入库(需要时由后端从登录态填充)`
|
|
43
|
+
}
|
|
44
|
+
return null
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** 稳定主键:label 去空白 */
|
|
48
|
+
export function companionId(label: string): string {
|
|
49
|
+
return label.replace(/\s+/g, '')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function mergeStrArray(oldArr: string[] | undefined, newArr: string[] | undefined): { merged: string[]; added: boolean } {
|
|
53
|
+
const a = oldArr ?? []
|
|
54
|
+
let added = false
|
|
55
|
+
for (const x of newArr ?? []) {
|
|
56
|
+
if (!a.includes(x)) {
|
|
57
|
+
a.push(x)
|
|
58
|
+
added = true
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { merged: a, added }
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* upsert 合并(纯函数,不改入参):同 companion_id 按 label 归并——
|
|
66
|
+
* constraints 逐字段取新值(新值覆盖旧行为约束,追加数组项),evidence 追加不删史,
|
|
67
|
+
* 全量无新增 → 幂等 no-op(appended:false)。
|
|
68
|
+
*/
|
|
69
|
+
export function upsertCompanion(
|
|
70
|
+
profiles: CompanionProfile[],
|
|
71
|
+
patch: { label: string; constraints: CompanionConstraints; evidence: string },
|
|
72
|
+
): { profiles: CompanionProfile[]; appended: boolean; companionId: string; reason?: string } {
|
|
73
|
+
if (!patch.label?.trim()) return { profiles, appended: false, companionId: '', reason: 'label 必填' }
|
|
74
|
+
const violation = sensitiveViolation(JSON.stringify(patch))
|
|
75
|
+
if (violation) return { profiles, appended: false, companionId: '', reason: violation }
|
|
76
|
+
const id = companionId(patch.label)
|
|
77
|
+
const existing = profiles.find(p => p.companion_id === id)
|
|
78
|
+
if (!existing) {
|
|
79
|
+
const full: CompanionProfile = {
|
|
80
|
+
schema: 'companion_profile.v1',
|
|
81
|
+
companion_id: id,
|
|
82
|
+
label: patch.label.trim(),
|
|
83
|
+
constraints: {
|
|
84
|
+
mobility: patch.constraints.mobility,
|
|
85
|
+
health: patch.constraints.health ? [...patch.constraints.health] : undefined,
|
|
86
|
+
prefs: patch.constraints.prefs ? [...patch.constraints.prefs] : undefined,
|
|
87
|
+
},
|
|
88
|
+
evidence: [patch.evidence],
|
|
89
|
+
ts: new Date().toISOString(),
|
|
90
|
+
}
|
|
91
|
+
return { profiles: [...profiles, full], appended: true, companionId: id }
|
|
92
|
+
}
|
|
93
|
+
const mergedConstraints: CompanionConstraints = {
|
|
94
|
+
mobility: patch.constraints.mobility ?? existing.constraints.mobility,
|
|
95
|
+
health: mergeStrArray(existing.constraints.health, patch.constraints.health).merged,
|
|
96
|
+
prefs: mergeStrArray(existing.constraints.prefs, patch.constraints.prefs).merged,
|
|
97
|
+
}
|
|
98
|
+
const ev = mergeStrArray(existing.evidence, [patch.evidence])
|
|
99
|
+
const healthLen = mergedConstraints.health?.length ?? 0
|
|
100
|
+
const prefsLen = mergedConstraints.prefs?.length ?? 0
|
|
101
|
+
const changed =
|
|
102
|
+
mergedConstraints.mobility !== existing.constraints.mobility ||
|
|
103
|
+
healthLen !== (existing.constraints.health?.length ?? 0) ||
|
|
104
|
+
prefsLen !== (existing.constraints.prefs?.length ?? 0) ||
|
|
105
|
+
ev.added
|
|
106
|
+
if (!changed) return { profiles, appended: false, companionId: id }
|
|
107
|
+
const next = profiles.map(p =>
|
|
108
|
+
p.companion_id === id
|
|
109
|
+
? { ...p, constraints: mergedConstraints, evidence: ev.merged, ts: new Date().toISOString() }
|
|
110
|
+
: p,
|
|
111
|
+
)
|
|
112
|
+
return { profiles: next, appended: true, companionId: id }
|
|
113
|
+
}
|
package/ts/src/contracts.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Stage 1 契约层(S1,docs/stage1-top-down-design.md §2)——顶层数据与工具面契约。
|
|
2
|
+
* Stage 1 契约层(S1,docs/design/stage1-top-down-design.md §2)——顶层数据与工具面契约。
|
|
3
3
|
*
|
|
4
4
|
* 自顶向下纪律:本文件是唯一权威契约。实现(mock 循环/求解器挂载/真 LLM)都向这里对齐;
|
|
5
5
|
* 契约变更需走设计文档升版,不随实现漂移。
|
|
@@ -16,8 +16,10 @@ export interface CalendarState {
|
|
|
16
16
|
assertedWeekdays: Record<string, 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat' | 'sun'>
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export interface
|
|
20
|
-
|
|
19
|
+
export interface ScheduledWorkWindowProfile {
|
|
20
|
+
/** Legacy v1 only; v2 pack metadata owns the IANA home zone. */
|
|
21
|
+
homeTzOffsetMin?: number
|
|
22
|
+
vacation?: false
|
|
21
23
|
startMin: number
|
|
22
24
|
endMin: number
|
|
23
25
|
workdays: number[]
|
|
@@ -25,6 +27,14 @@ export interface WorkWindowProfile {
|
|
|
25
27
|
evidence: string
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/** Explicit leave means this trip has no work-time restriction. */
|
|
31
|
+
export interface VacationWorkWindowProfile {
|
|
32
|
+
vacation: true
|
|
33
|
+
evidence?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export type WorkWindowProfile = ScheduledWorkWindowProfile | VacationWorkWindowProfile
|
|
37
|
+
|
|
28
38
|
export interface TravelerProfile {
|
|
29
39
|
workWindow?: WorkWindowProfile
|
|
30
40
|
companions?: string[]
|