@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,539 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 效应解译器测试(effect_interpreter.v1,issue #16 采纳,ADR-18;纯离线零网络):
|
|
3
|
+
* 1. 注册表封闭性:未登记效应 → 结构化拒绝面(不抛错,declined=unknown-effect)
|
|
4
|
+
* 2. 指数退避 withRetry:500→1000→2000 封顶链;累计记账;非瞬时失败不重试
|
|
5
|
+
* 3. 断路器三态:closed→(连续失败达阈)→open→(冷却)→half-open 单探测→成功回 closed/失败再开
|
|
6
|
+
* 4. 生产解译器×瞬时失败:重试后成功(trace.attempts/backoff 记账,evidence 行)
|
|
7
|
+
* 5. FlyAI×Sentinel:上游说不永不重试(attempts=1);连续失败计入熔断,熔断后零执行成本
|
|
8
|
+
* 6. 断路拒绝面:不执行、不抛错、平铺失败观察;冷却后 half-open 单探测成功 → closed
|
|
9
|
+
* 7. mock 解译器:夹具回放确定性;未登记夹具=结构化拒绝
|
|
10
|
+
* 8. SESSION 通道策略:永不重试、不熔断(风控红线),verdict 原样透传
|
|
11
|
+
* 9. 真实 handler 离线冒烟:hbcli 不可达 → 静态包降级(永不抛错,证据链非空)
|
|
12
|
+
* 11. HBCLI×timeout(2026-09-02 迪拜 session:冷启动建后端 session 超 30s):
|
|
13
|
+
* timeout 类失败重试 1 次即恢复;ENOENT/退码类仍永不重试(切换不是重试)
|
|
14
|
+
*
|
|
15
|
+
* 运行: cd ts && npx tsx scripts/effect-tests.ts
|
|
16
|
+
*/
|
|
17
|
+
import assert from 'node:assert/strict';
|
|
18
|
+
import { getEventListeners } from 'node:events';
|
|
19
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
20
|
+
import { tmpdir } from 'node:os';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { backoffDelayMs, CircuitBreaker, withRetry } from '../capabilities/resilience.js';
|
|
23
|
+
import { declinedObservation, makeMockInterpreter, makeProductionInterpreter, __resetEffectBreakersForTest, } from '../capabilities/effect.js';
|
|
24
|
+
const sleep0 = async () => { }; // 回退即时放行(确定性,不真睡)
|
|
25
|
+
let clock = 1_000_000;
|
|
26
|
+
const now = () => clock;
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// 1. 注册表封闭性:未登记效应 → 结构化拒绝(不抛错)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
const interp = makeProductionInterpreter({ sleep: sleep0, now });
|
|
31
|
+
const badItp = await interp({ effect: 'CTRIP_DIRECT_QUERY', params: {} });
|
|
32
|
+
assert.equal(badItp.result, null, '未登记效应 result=null');
|
|
33
|
+
assert.equal(badItp.trace.declined, 'unknown-effect');
|
|
34
|
+
assert.equal(badItp.trace.attempts, 0, '未登记效应零执行');
|
|
35
|
+
const badObs = declinedObservation('CTRIP_DIRECT_QUERY', badItp.trace);
|
|
36
|
+
assert.equal(badObs.ok, false);
|
|
37
|
+
assert.match(badObs.summary, /未登记效应/, '拒绝面人话 summary');
|
|
38
|
+
assert.match(badObs.evidence, /\[效应:CTRIP_DIRECT_QUERY@/, '拒绝面带证据链');
|
|
39
|
+
console.log('1. 注册表封闭性:未登记效应 → 结构化拒绝面(不抛错)OK');
|
|
40
|
+
// Host cancellation is an explicit outcome, not an unregistered-effect diagnosis.
|
|
41
|
+
const preCancelled = new AbortController();
|
|
42
|
+
preCancelled.abort();
|
|
43
|
+
let cancelledDispatches = 0;
|
|
44
|
+
const cancelledInterpreter = makeProductionInterpreter({
|
|
45
|
+
breakers: new Map(),
|
|
46
|
+
handlers: { ANYTHING_SEARCH: async () => { cancelledDispatches++; return { ok: true }; } },
|
|
47
|
+
});
|
|
48
|
+
const cancelled = await cancelledInterpreter({ effect: 'ANYTHING_SEARCH', params: { signal: preCancelled.signal } });
|
|
49
|
+
assert.equal(cancelled.result, null);
|
|
50
|
+
assert.equal(cancelled.trace.declined, 'aborted');
|
|
51
|
+
assert.equal(cancelled.trace.attempts, 0);
|
|
52
|
+
assert.equal(cancelledDispatches, 0);
|
|
53
|
+
const cancelledObservation = declinedObservation('ANYTHING_SEARCH', cancelled.trace);
|
|
54
|
+
assert.equal(cancelledObservation.ok, false);
|
|
55
|
+
assert.match(cancelledObservation.summary, /已取消/);
|
|
56
|
+
assert.doesNotMatch(cancelledObservation.summary, /未登记|断路器/);
|
|
57
|
+
assert.match(cancelledObservation.evidence, /@abort@/);
|
|
58
|
+
console.log('1b. 宿主取消明确呈现已取消,零派发、不误报未登记效应 OK');
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// 2. 指数退避:500→1000→2000 封顶;累计记账;非瞬时失败不重试
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
assert.equal(backoffDelayMs({ baseDelayMs: 500, maxDelayMs: 8000 }, 1), 500);
|
|
63
|
+
assert.equal(backoffDelayMs({ baseDelayMs: 500, maxDelayMs: 8000 }, 2), 1000);
|
|
64
|
+
assert.equal(backoffDelayMs({ baseDelayMs: 500, maxDelayMs: 8000 }, 4), 4000);
|
|
65
|
+
assert.equal(backoffDelayMs({ baseDelayMs: 500, maxDelayMs: 2000 }, 6), 2000, '封顶');
|
|
66
|
+
const retried = [];
|
|
67
|
+
const rt = await withRetry(async () => ({ ok: false }), { maxAttempts: 4, baseDelayMs: 500, maxDelayMs: 2000, isRetryable: () => true, sleep: sleep0 }, (_failN, delayMs) => retried.push(delayMs));
|
|
68
|
+
assert.deepEqual(retried, [500, 1000, 2000], '退避序列 500/1000/2000');
|
|
69
|
+
assert.equal(rt.attempts, 4, '重试到上限');
|
|
70
|
+
assert.equal(rt.backoffMs, 3500, '累计回退记账');
|
|
71
|
+
const noRetry = await withRetry(async () => ({ ok: false, verdict: 'error', error: '{"message":"SentinelBlockException"}' }), { maxAttempts: 3, baseDelayMs: 500, maxDelayMs: 2000, isRetryable: () => false, sleep: sleep0 });
|
|
72
|
+
assert.equal(noRetry.attempts, 1, '瞬时判定说不重试就 1 次');
|
|
73
|
+
// 2a. 退避等待生命周期:正常 timer、取消与注入 sleep 都移除 abort listener。
|
|
74
|
+
const normalBackoffController = new AbortController();
|
|
75
|
+
let normalBackoffCalls = 0;
|
|
76
|
+
const normalBackoff = await withRetry(async () => ({ ok: ++normalBackoffCalls > 1 }), { maxAttempts: 2, baseDelayMs: 5, maxDelayMs: 5, signal: normalBackoffController.signal });
|
|
77
|
+
assert.equal(normalBackoff.attempts, 2, '真实 timer 等待后继续下一次尝试');
|
|
78
|
+
assert.equal(normalBackoff.aborted, false, '正常等待不是取消');
|
|
79
|
+
assert.equal(getEventListeners(normalBackoffController.signal, 'abort').length, 0, '正常 timer 完成后移除 abort listener');
|
|
80
|
+
const cancelledBackoffController = new AbortController();
|
|
81
|
+
let cancelledBackoffCalls = 0;
|
|
82
|
+
let cancelTimer;
|
|
83
|
+
let cancelledBackoff;
|
|
84
|
+
try {
|
|
85
|
+
cancelledBackoff = await withRetry(async () => { cancelledBackoffCalls += 1; return { ok: false }; }, { maxAttempts: 2, baseDelayMs: 100, maxDelayMs: 100, signal: cancelledBackoffController.signal }, () => { cancelTimer = setTimeout(() => cancelledBackoffController.abort(), 1); });
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
if (cancelTimer !== undefined)
|
|
89
|
+
clearTimeout(cancelTimer);
|
|
90
|
+
}
|
|
91
|
+
assert.equal(cancelledBackoff.attempts, 1, 'backoff 取消不派发下一次尝试');
|
|
92
|
+
assert.equal(cancelledBackoff.aborted, true, 'backoff 取消返回 aborted');
|
|
93
|
+
assert.equal(cancelledBackoffCalls, 1, '取消不是第二次失败');
|
|
94
|
+
assert.equal(getEventListeners(cancelledBackoffController.signal, 'abort').length, 0, '取消后移除 abort listener');
|
|
95
|
+
const injectedSleepController = new AbortController();
|
|
96
|
+
let injectedSleepCalls = 0;
|
|
97
|
+
const injectedSleep = await withRetry(async () => ({ ok: false }), {
|
|
98
|
+
maxAttempts: 2,
|
|
99
|
+
baseDelayMs: 1,
|
|
100
|
+
maxDelayMs: 1,
|
|
101
|
+
signal: injectedSleepController.signal,
|
|
102
|
+
sleep: async () => { injectedSleepCalls += 1; injectedSleepController.abort(); },
|
|
103
|
+
});
|
|
104
|
+
assert.equal(injectedSleepCalls, 1, '退避使用注入 sleep');
|
|
105
|
+
assert.equal(injectedSleep.aborted, true, '注入 sleep 中取消仍终止重试');
|
|
106
|
+
assert.equal(injectedSleep.attempts, 1, '注入 sleep 取消不派发下一次尝试');
|
|
107
|
+
assert.equal(getEventListeners(injectedSleepController.signal, 'abort').length, 0, '注入 sleep 取消后移除 abort listener');
|
|
108
|
+
const rejectedSleepController = new AbortController();
|
|
109
|
+
const sleepFailure = new Error('synthetic sleep failure');
|
|
110
|
+
await assert.rejects(withRetry(async () => ({ ok: false }), {
|
|
111
|
+
maxAttempts: 2,
|
|
112
|
+
baseDelayMs: 1,
|
|
113
|
+
maxDelayMs: 1,
|
|
114
|
+
signal: rejectedSleepController.signal,
|
|
115
|
+
sleep: async () => { throw sleepFailure; },
|
|
116
|
+
}), (error) => error === sleepFailure, '注入 sleep rejection 保持原异常约定');
|
|
117
|
+
assert.equal(getEventListeners(rejectedSleepController.signal, 'abort').length, 0, '注入 sleep rejection 后移除 abort listener');
|
|
118
|
+
console.log('2. 指数退避(封顶链/累计记账/非瞬时止步)OK');
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// 3. 断路器三态(时钟注入确定性)
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
const br = new CircuitBreaker({ failureThreshold: 2, openMs: 60_000, now });
|
|
123
|
+
assert.equal(br.canAttempt().state, 'closed');
|
|
124
|
+
br.onFailure();
|
|
125
|
+
br.onFailure();
|
|
126
|
+
assert.equal(br.state(), 'open', '连续 2 失败 → open');
|
|
127
|
+
assert.equal(br.canAttempt().allowed, false, 'open 中拒绝');
|
|
128
|
+
clock += 61_000;
|
|
129
|
+
assert.equal(br.state(), 'half-open', '冷却满 → half-open');
|
|
130
|
+
const gate = br.canAttempt();
|
|
131
|
+
assert.equal(gate.allowed, true, 'half-open 放行单探测');
|
|
132
|
+
assert.equal(br.canAttempt().allowed, false, '探测在途拒并发');
|
|
133
|
+
br.onSuccess();
|
|
134
|
+
assert.equal(br.state(), 'closed', '探测成功 → closed');
|
|
135
|
+
br.onFailure();
|
|
136
|
+
br.onFailure();
|
|
137
|
+
clock += 61_000;
|
|
138
|
+
assert.equal(br.state(), 'half-open');
|
|
139
|
+
assert.equal(br.canAttempt().allowed, true);
|
|
140
|
+
br.onFailure();
|
|
141
|
+
assert.equal(br.state(), 'open', '探测失败 → 重新 open(冷却重启)');
|
|
142
|
+
console.log('3. 断路器三态(closed→open→half-open 单探测→双向收敛)OK');
|
|
143
|
+
// 3a. 半开探测所有权:旧 closed 调用的取消/完成不能释放或修改新一代探测。
|
|
144
|
+
const raceClock = { value: 100 };
|
|
145
|
+
const raceNow = () => raceClock.value;
|
|
146
|
+
let resolveOldClosed;
|
|
147
|
+
let resolveOldClosedCompletion;
|
|
148
|
+
let resolveHalfOpenProbe;
|
|
149
|
+
let oldClosedStarted;
|
|
150
|
+
let oldClosedCompletionStarted;
|
|
151
|
+
let halfOpenProbeStarted;
|
|
152
|
+
const oldClosedReady = new Promise((resolve) => { oldClosedStarted = resolve; });
|
|
153
|
+
const oldClosedCompletionReady = new Promise((resolve) => { oldClosedCompletionStarted = resolve; });
|
|
154
|
+
const halfOpenProbeReady = new Promise((resolve) => { halfOpenProbeStarted = resolve; });
|
|
155
|
+
let secondProbeCalled = false;
|
|
156
|
+
const raceInterpreter = makeProductionInterpreter({
|
|
157
|
+
sleep: sleep0,
|
|
158
|
+
now: raceNow,
|
|
159
|
+
breakers: new Map(),
|
|
160
|
+
handlers: {
|
|
161
|
+
HBCLI_HOTEL_SEARCH: async (params) => {
|
|
162
|
+
if (params.testCase === 'old-closed') {
|
|
163
|
+
oldClosedStarted();
|
|
164
|
+
return new Promise((resolve) => { resolveOldClosed = resolve; });
|
|
165
|
+
}
|
|
166
|
+
if (params.testCase === 'old-closed-completion') {
|
|
167
|
+
oldClosedCompletionStarted();
|
|
168
|
+
return new Promise((resolve) => { resolveOldClosedCompletion = resolve; });
|
|
169
|
+
}
|
|
170
|
+
if (params.testCase === 'half-open-probe') {
|
|
171
|
+
halfOpenProbeStarted();
|
|
172
|
+
return new Promise((resolve) => { resolveHalfOpenProbe = resolve; });
|
|
173
|
+
}
|
|
174
|
+
if (params.testCase === 'second-probe') {
|
|
175
|
+
secondProbeCalled = true;
|
|
176
|
+
return { via: 'hbcli-realtime' };
|
|
177
|
+
}
|
|
178
|
+
return { via: 'hbcli-error', error: 'synthetic permanent failure' };
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
const oldClosedController = new AbortController();
|
|
183
|
+
const oldClosed = raceInterpreter({ effect: 'HBCLI_HOTEL_SEARCH', params: { testCase: 'old-closed', signal: oldClosedController.signal } });
|
|
184
|
+
await oldClosedReady;
|
|
185
|
+
const oldClosedCompletion = raceInterpreter({ effect: 'HBCLI_HOTEL_SEARCH', params: { testCase: 'old-closed-completion' } });
|
|
186
|
+
await oldClosedCompletionReady;
|
|
187
|
+
for (let i = 0; i < 3; i++)
|
|
188
|
+
await raceInterpreter({ effect: 'HBCLI_HOTEL_SEARCH', params: { testCase: `failure-${i}` } });
|
|
189
|
+
const raceBlocked = await raceInterpreter({ effect: 'HBCLI_HOTEL_SEARCH', params: { testCase: 'blocked' } });
|
|
190
|
+
assert.equal(raceBlocked.trace.declined, 'circuit-open', '旧请求在途时仍可进入 open');
|
|
191
|
+
raceClock.value = 100_000;
|
|
192
|
+
const halfOpenProbe = raceInterpreter({ effect: 'HBCLI_HOTEL_SEARCH', params: { testCase: 'half-open-probe' } });
|
|
193
|
+
await halfOpenProbeReady;
|
|
194
|
+
oldClosedController.abort();
|
|
195
|
+
resolveOldClosed({ via: 'hbcli-error', error: 'aborted by host signal' });
|
|
196
|
+
const cancelledOldClosed = await oldClosed;
|
|
197
|
+
assert.equal(cancelledOldClosed.trace.attempts, 1, '旧 closed 请求取消仍只执行一次');
|
|
198
|
+
assert.equal(cancelledOldClosed.trace.declined, 'aborted');
|
|
199
|
+
assert.match(declinedObservation('HBCLI_HOTEL_SEARCH', cancelledOldClosed.trace).summary, /已取消/);
|
|
200
|
+
resolveOldClosedCompletion({ via: 'hbcli-realtime' });
|
|
201
|
+
const completedOldClosed = await oldClosedCompletion;
|
|
202
|
+
assert.equal(completedOldClosed.trace.breaker, 'half-open', '旧 closed 请求完成不能修改新半开探测');
|
|
203
|
+
const secondProbe = await raceInterpreter({ effect: 'HBCLI_HOTEL_SEARCH', params: { testCase: 'second-probe' } });
|
|
204
|
+
assert.equal(secondProbe.trace.declined, 'circuit-open', '旧 closed 请求取消不能释放新一代半开探测');
|
|
205
|
+
assert.equal(secondProbeCalled, false, '新一代探测仍由原探测独占');
|
|
206
|
+
resolveHalfOpenProbe({ via: 'hbcli-realtime' });
|
|
207
|
+
const settledHalfOpenProbe = await halfOpenProbe;
|
|
208
|
+
assert.equal(settledHalfOpenProbe.trace.breaker, 'closed', '自身半开探测成功后恢复 closed');
|
|
209
|
+
// 3b. 探测令牌必须跨 generation 隔离:旧令牌不能释放新探测;失败仍保持 open。
|
|
210
|
+
const ownershipClock = { value: 1_000 };
|
|
211
|
+
const ownership = new CircuitBreaker({ failureThreshold: 1, openMs: 10, now: () => ownershipClock.value });
|
|
212
|
+
ownership.onFailure();
|
|
213
|
+
ownershipClock.value += 10;
|
|
214
|
+
const firstProbe = ownership.canAttempt();
|
|
215
|
+
assert.equal(firstProbe.allowed, true);
|
|
216
|
+
ownership.releaseProbe(firstProbe.token);
|
|
217
|
+
const secondGenerationProbe = ownership.canAttempt();
|
|
218
|
+
assert.equal(secondGenerationProbe.allowed, true, '自身取消释放后允许下一代探测');
|
|
219
|
+
ownership.releaseProbe(firstProbe.token);
|
|
220
|
+
assert.equal(ownership.canAttempt().allowed, false, '旧 generation 令牌不能释放新探测');
|
|
221
|
+
ownership.onFailure(secondGenerationProbe.token);
|
|
222
|
+
assert.equal(ownership.state(), 'open', '真实半开失败重新熔断');
|
|
223
|
+
assert.equal(ownership.canAttempt().allowed, false, '真实半开失败后仍拒绝并发探测');
|
|
224
|
+
console.log('3a/3b. 半开探测所有权(旧 closed 取消隔离/取消可重试/跨 generation/失败再熔断)OK');
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// 4. 生产解译器×瞬时失败:重试后成功,trace 记账(WEATHER 免费源策略 2 次上限)
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
let flaky = 0;
|
|
229
|
+
const ip = makeProductionInterpreter({
|
|
230
|
+
sleep: sleep0,
|
|
231
|
+
breakers: new Map(),
|
|
232
|
+
handlers: {
|
|
233
|
+
WEATHER_FORECAST: async () => {
|
|
234
|
+
flaky += 1;
|
|
235
|
+
return flaky < 2
|
|
236
|
+
? { ok: false, via: 'open-meteo-error', evidence: '[实时API:open-meteo@error]', latencyMs: 1, error: 'socket hang up' }
|
|
237
|
+
: { ok: true, via: 'open-meteo', evidence: '[实时API:open-meteo@ts]', latencyMs: 2, daily: [] };
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
const itp4 = await ip({ effect: 'WEATHER_FORECAST', params: { latitude: 25.6, longitude: 100.2 } });
|
|
242
|
+
assert.equal(itp4.trace.attempts, 2, '瞬时失败重试第 2 次成功(免费源上限 2)');
|
|
243
|
+
assert.equal(itp4.trace.backoffMs, 400, '回退按 API 策略记账');
|
|
244
|
+
assert.ok(itp4.result.ok, '成功 observation 原样透传');
|
|
245
|
+
assert.match(itp4.trace.evidence[0] ?? '', /attempts=2 backoff=400ms breaker=closed/, 'trace 证据行');
|
|
246
|
+
console.log('4. 生产解译器×瞬时失败:重试后成功 + trace 记账 OK');
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// 5. FlyAI×Sentinel:不重试,但连续失败计入熔断
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
const sentinelResult = {
|
|
251
|
+
ok: false, via: 'flyai-error', verdict: 'error', kind: 'flight', latencyMs: 5,
|
|
252
|
+
evidence: '[实时API:flyai@error@ts] parse failed(SentinelBlockException)',
|
|
253
|
+
error: 'failed to parse flyai output as JSON (incomplete FlyAI JSON object): {"message":"SentinelBlockException"}',
|
|
254
|
+
};
|
|
255
|
+
let sentinelCalls = 0;
|
|
256
|
+
const flyaiBreakers = new Map();
|
|
257
|
+
const ipS = makeProductionInterpreter({
|
|
258
|
+
sleep: sleep0,
|
|
259
|
+
breakers: flyaiBreakers,
|
|
260
|
+
now,
|
|
261
|
+
handlers: { FLYAI_SEARCH: async () => { sentinelCalls += 1; return structuredClone(sentinelResult); } },
|
|
262
|
+
});
|
|
263
|
+
const q = { kind: 'flight', origin: '上海', destination: '丽江', depDate: '2026-10-01' };
|
|
264
|
+
await ipS({ effect: 'FLYAI_SEARCH', params: q });
|
|
265
|
+
await ipS({ effect: 'FLYAI_SEARCH', params: q });
|
|
266
|
+
assert.equal(sentinelCalls, 2, '每次调用只打一次上游(Sentinel 不重试)');
|
|
267
|
+
const itp3 = await ipS({ effect: 'FLYAI_SEARCH', params: q });
|
|
268
|
+
assert.equal(itp3.trace.attempts, 1);
|
|
269
|
+
assert.equal(itp3.trace.breaker, 'open', '连续 3 次失败(FlyAI 阈值 3)→ 断路器开启');
|
|
270
|
+
assert.equal(sentinelCalls, 3);
|
|
271
|
+
const itp4th = await ipS({ effect: 'FLYAI_SEARCH', params: q });
|
|
272
|
+
assert.equal(itp4th.trace.declined, 'circuit-open', '第 4 次:熔断中零执行成本拒绝');
|
|
273
|
+
assert.equal(itp4th.result, null);
|
|
274
|
+
assert.equal(sentinelCalls, 3, '熔断后不再打上游');
|
|
275
|
+
console.log('5. FlyAI×Sentinel:永不重试 + 连环失败触发熔断保护配额 OK');
|
|
276
|
+
// #517: adapter-owned local termination is explicit terminal state. The
|
|
277
|
+
// effect policy must honor retryable=false even when a stale timeout verdict is
|
|
278
|
+
// present, while a classified transient result still receives one retry.
|
|
279
|
+
let localDeadlineCalls = 0;
|
|
280
|
+
const localDeadline = makeProductionInterpreter({
|
|
281
|
+
breakers: new Map(), sleep: sleep0,
|
|
282
|
+
handlers: {
|
|
283
|
+
FLYAI_SEARCH: async () => {
|
|
284
|
+
localDeadlineCalls += 1;
|
|
285
|
+
return { ok: false, via: 'flyai-error', verdict: 'timeout', localTermination: 'deadline', retryable: false, kind: 'flight', latencyMs: 1, evidence: 'local deadline' };
|
|
286
|
+
},
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
const localDeadlineOutcome = await localDeadline({ effect: 'FLYAI_SEARCH', params: q });
|
|
290
|
+
assert.equal(localDeadlineOutcome.trace.attempts, 1, 'local deadline 明确 terminal 只能一次');
|
|
291
|
+
assert.equal(localDeadlineCalls, 1);
|
|
292
|
+
let classifiedTransientCalls = 0;
|
|
293
|
+
const classifiedTransient = makeProductionInterpreter({
|
|
294
|
+
breakers: new Map(), sleep: sleep0,
|
|
295
|
+
handlers: {
|
|
296
|
+
FLYAI_SEARCH: async () => {
|
|
297
|
+
classifiedTransientCalls += 1;
|
|
298
|
+
return classifiedTransientCalls === 1
|
|
299
|
+
? { ok: false, via: 'flyai-error', verdict: 'error', retryable: true, kind: 'flight', latencyMs: 1, evidence: 'HTTP 500' }
|
|
300
|
+
: { ok: true, via: 'flyai', verdict: 'miss', kind: 'flight', latencyMs: 1, evidence: 'empty itemList', options: [] };
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
});
|
|
304
|
+
const classifiedTransientOutcome = await classifiedTransient({ effect: 'FLYAI_SEARCH', params: q });
|
|
305
|
+
assert.equal(classifiedTransientOutcome.trace.attempts, 2, 'HTTP500 retryable=true 应重试一次');
|
|
306
|
+
assert.equal(classifiedTransientCalls, 2);
|
|
307
|
+
console.log('5b. FlyAI retry policy 只接受显式 retryable=true,本地终止/timeout=false 单次 OK');
|
|
308
|
+
// ---------------------------------------------------------------------------
|
|
309
|
+
// 6. 断路拒绝面 + 冷却后 half-open 单探测成功恢复 closed
|
|
310
|
+
// ---------------------------------------------------------------------------
|
|
311
|
+
const declined = declinedObservation('FLYAI_SEARCH', itp4th.trace);
|
|
312
|
+
assert.equal(declined.ok, false);
|
|
313
|
+
assert.match(declined.summary, /断路器开启/, '拒绝面指引「不要立即重试」,不伪装成 miss');
|
|
314
|
+
clock += 120_000; // 冷却满(FlyAI 60s)→ half-open
|
|
315
|
+
const ipOk = makeProductionInterpreter({
|
|
316
|
+
sleep: sleep0, breakers: flyaiBreakers, now, // 复用同一断路器 Map——状态在 Map 里存活
|
|
317
|
+
handlers: { FLYAI_SEARCH: async () => ({ ok: true, via: 'flyai', verdict: 'hit', kind: 'flight', latencyMs: 1, evidence: 'e', options: [] }) },
|
|
318
|
+
});
|
|
319
|
+
const itp6 = await ipOk({ effect: 'FLYAI_SEARCH', params: { kind: 'flight' } });
|
|
320
|
+
assert.equal(itp6.trace.breaker, 'closed', 'half-open 单探测成功 → closed 恢复');
|
|
321
|
+
assert.ok(itp6.result != null && !itp6.trace.declined);
|
|
322
|
+
console.log('6. 断路拒绝面(结构化/零执行)+ 冷却后单探测恢复 closed OK');
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
// 7. mock 解译器:夹具回放确定性(CI 无网)
|
|
325
|
+
// ---------------------------------------------------------------------------
|
|
326
|
+
const mock = makeMockInterpreter({
|
|
327
|
+
FLYAI_SEARCH: { ok: true, via: 'flyai', verdict: 'hit', kind: 'flight', latencyMs: 0, evidence: '[实时API:flyai@2026-08-29T00:00:00Z] 2/2 flight options', options: [{ no: '9C6617', name: '吉祥航空', depDateTime: '2026-10-01 07:55', arrDateTime: '2026-10-01 11:20', depStation: '浦东T2', arrStation: '丽江三义', durationMin: 205, price: 580 }] },
|
|
328
|
+
});
|
|
329
|
+
const mockFx = { effect: 'FLYAI_SEARCH', params: { kind: 'flight', origin: '上海', destination: '丽江', depDate: '2026-10-01' } };
|
|
330
|
+
const mockA = await mock(mockFx);
|
|
331
|
+
const mockB = await mock(mockFx);
|
|
332
|
+
assert.deepEqual(mockA.result, mockB.result, '同一夹具两次回放同值');
|
|
333
|
+
assert.equal(mockA.result.verdict, 'hit');
|
|
334
|
+
assert.match(mockA.trace.evidence[0] ?? '', /\[效应:mock@.*夹具回放/, 'mock trace 标注');
|
|
335
|
+
const mockMiss = await mock({ effect: 'WEATHER_FORECAST', params: {} });
|
|
336
|
+
assert.equal(mockMiss.trace.declined, 'unknown-effect', 'mock 未登记夹具 = 拒绝面');
|
|
337
|
+
console.log('7. mock 解译器(夹具回放/未登记拒绝/确定性)OK');
|
|
338
|
+
// ---------------------------------------------------------------------------
|
|
339
|
+
// 8. SESSION 通道策略:永不重试、不熔断(风控红线),一次调用一次透传
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
let sessionCalls = 0;
|
|
342
|
+
const ipSess = makeProductionInterpreter({
|
|
343
|
+
sleep: sleep0,
|
|
344
|
+
breakers: new Map(),
|
|
345
|
+
handlers: {
|
|
346
|
+
SESSION_FLIGHT_SEARCH: async () => {
|
|
347
|
+
sessionCalls += 1;
|
|
348
|
+
return { ok: false, via: 'session-ctrip-flight-error', verdict: 'challenged', evidence: '[会话:ctrip-flight@error@ts] 风控命中', latencyMs: 9, error: 'challenge' };
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
const sessQ = { from: '上海', to: '丽江', date: '2026-10-01' };
|
|
353
|
+
const itp8 = await ipSess({ effect: 'SESSION_FLIGHT_SEARCH', params: sessQ });
|
|
354
|
+
assert.equal(itp8.result.verdict, 'challenged', '渠道 verdict 原样透传');
|
|
355
|
+
assert.equal(itp8.trace.attempts, 1, 'SESSION 永不重试(挑战=红线)');
|
|
356
|
+
assert.equal(itp8.trace.breaker, 'off', 'SESSION 不参与断路器');
|
|
357
|
+
for (let i = 0; i < 5; i++)
|
|
358
|
+
await ipSess({ effect: 'SESSION_FLIGHT_SEARCH', params: sessQ });
|
|
359
|
+
const itp8z = await ipSess({ effect: 'SESSION_FLIGHT_SEARCH', params: sessQ });
|
|
360
|
+
assert.ok(itp8z.trace.breaker === 'off' && !itp8z.trace.declined, 'SESSION 无断路拒绝(治理在节律闸与授权闸)');
|
|
361
|
+
assert.equal(sessionCalls, 7);
|
|
362
|
+
console.log('8. SESSION 通道策略(永不重试/不熔断/verdict 透传)OK');
|
|
363
|
+
// ---------------------------------------------------------------------------
|
|
364
|
+
// 9. 真实 handler 离线冒烟:hbcli 显式不可达 → 静态包降级(永不抛错,零网络)
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
__resetEffectBreakersForTest();
|
|
367
|
+
const tmp = await mkdtemp(join(tmpdir(), 'effect-test-'));
|
|
368
|
+
const itp9 = await interp({
|
|
369
|
+
effect: 'HBCLI_HOTEL_SEARCH',
|
|
370
|
+
// 显式不存在的自定义 bin:hbcliBinCandidates 对自定义名不回退已知安装位 → ENOENT → 静态包降级
|
|
371
|
+
params: { destination: '大理', hbcliBin: join(tmp, 'no-such-hbcli'), fallbackPath: join(import.meta.dirname, '..', '..', 'data', 'hotels_2026.json') },
|
|
372
|
+
});
|
|
373
|
+
const hb9 = itp9.result;
|
|
374
|
+
assert.ok(hb9 != null, '真实 handler 降级仍返回 observation');
|
|
375
|
+
assert.ok(typeof hb9.summary === 'string' && hb9.summary.length > 0, '降级带人话 summary');
|
|
376
|
+
assert.ok(itp9.trace.evidence[0]?.startsWith('[效应:HBCLI_HOTEL_SEARCH@'), '解译层证据行');
|
|
377
|
+
await rm(tmp, { recursive: true, force: true });
|
|
378
|
+
console.log(`9. 真实 handler 离线冒烟(HBCLI 静态包降级,summary=${(hb9.summary ?? '').slice(0, 40)}…)OK`);
|
|
379
|
+
// ---------------------------------------------------------------------------
|
|
380
|
+
// 10. M0 预订链读效应:HBCLI_HOTEL_RATES / HBCLI_CHECK_AVAIL
|
|
381
|
+
// (注入 handler 验策略面;真实 handler 离线冒烟验价格面 fail-closed;mock 夹具)
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// 10a. 注册进生产解译器:注入 handler 派发 / 永不重试 / 失败计熔断 / 三连错开闸
|
|
384
|
+
let ratesDispatches = 0;
|
|
385
|
+
const ipM0 = makeProductionInterpreter({
|
|
386
|
+
sleep: sleep0,
|
|
387
|
+
breakers: new Map(),
|
|
388
|
+
handlers: {
|
|
389
|
+
HBCLI_HOTEL_RATES: async () => {
|
|
390
|
+
ratesDispatches += 1;
|
|
391
|
+
return { via: 'hbcli-realtime', exitCode: 0, result: { rooms: [] }, evidence: '[实时API:hbcli@ts]', latencyMs: 1, rates: { rooms: [] }, summary: 'ok' };
|
|
392
|
+
},
|
|
393
|
+
HBCLI_CHECK_AVAIL: async () => ({ via: 'hbcli-error', exitCode: 1, result: null, evidence: '[实时API:hbcli@error@ts]', latencyMs: 1, error: 'boom', avail: null, summary: 'err' }),
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
const itp10 = await ipM0({ effect: 'HBCLI_HOTEL_RATES', params: { hotelId: '900000001', checkIn: '2026-09-05', checkOut: '2026-09-07', adults: 2 } });
|
|
397
|
+
assert.equal(itp10.trace.attempts, 1, 'RATES 永不重试(候选路径切换不是重试)');
|
|
398
|
+
assert.ok(itp10.result != null && !itp10.trace.declined, 'RATES 命中注入 handler,observation 透传');
|
|
399
|
+
assert.ok(itp10.trace.evidence[0]?.startsWith('[效应:HBCLI_HOTEL_RATES@'), '解译层证据行');
|
|
400
|
+
const itp10b = await ipM0({ effect: 'HBCLI_CHECK_AVAIL', params: { ratePkgId: 'rp-1' } });
|
|
401
|
+
assert.equal(itp10b.result?.via, 'hbcli-error', '渠道失败观察原样透传(平铺失败面,isFailure 计熔断)');
|
|
402
|
+
await ipM0({ effect: 'HBCLI_CHECK_AVAIL', params: { ratePkgId: 'rp-2' } });
|
|
403
|
+
await ipM0({ effect: 'HBCLI_CHECK_AVAIL', params: { ratePkgId: 'rp-3' } });
|
|
404
|
+
const itp10c = await ipM0({ effect: 'HBCLI_CHECK_AVAIL', params: { ratePkgId: 'rp-4' } });
|
|
405
|
+
assert.equal(itp10c.trace.declined, 'circuit-open', '3 连错熔断保护(同 HBCLI 族策略)');
|
|
406
|
+
assert.equal(ratesDispatches, 1, '成功通道不受失败通道熔断影响(per-effect 隔离)');
|
|
407
|
+
console.log('10a. 预订链读效应注册(派发/永不重试/熔断族/per-effect 隔离)OK');
|
|
408
|
+
// 10b. 真实 handler 离线冒烟:不可达 bin → 诚实失败,价格面无静态降级(fail-closed)
|
|
409
|
+
__resetEffectBreakersForTest();
|
|
410
|
+
const tmp10 = await mkdtemp(join(tmpdir(), 'effect-m0-'));
|
|
411
|
+
const itp10d = await interp({
|
|
412
|
+
effect: 'HBCLI_HOTEL_RATES',
|
|
413
|
+
// 自定义不存在的 bin:候选路径不回退已知安装位 → ENOENT → 诚实失败(hotel-list 才有静态包降级)
|
|
414
|
+
params: { hotelId: '900000001', hbcliBin: join(tmp10, 'no-such-hbcli') },
|
|
415
|
+
});
|
|
416
|
+
const hr10 = itp10d.result;
|
|
417
|
+
assert.ok(hr10 != null, '不可达仍返回 observation(永不抛错)');
|
|
418
|
+
assert.equal(hr10.via, 'hbcli-error', '未伪装成功');
|
|
419
|
+
assert.match(hr10.summary ?? '', /不可用/, 'summary 明示不可用');
|
|
420
|
+
assert.ok(!/静态包/.test(hr10.summary ?? '') || /无静态降级/.test(hr10.summary ?? ''), '价格面不降级到静态包');
|
|
421
|
+
const itp10e = await interp({ effect: 'HBCLI_CHECK_AVAIL', params: { ratePkgId: 'rp-x', hbcliBin: join(tmp10, 'no-such-hbcli') } });
|
|
422
|
+
assert.equal(itp10e.result?.via, 'hbcli-error', 'check-avail 同口径诚实失败');
|
|
423
|
+
await rm(tmp10, { recursive: true, force: true });
|
|
424
|
+
console.log('10b. 真实 handler 离线冒烟(价格面 fail-closed,无静态降级)OK');
|
|
425
|
+
// 10c. mock 解译器夹具回放(预订链 CI 形态;录制形态 = 渠道 observation 原样)
|
|
426
|
+
const mock10 = makeMockInterpreter({
|
|
427
|
+
HBCLI_HOTEL_RATES: { via: 'hbcli-realtime', exitCode: 0, result: { rooms: [{ ratePkgId: 'rp-fx-1', roomTypeName: 'Deluxe King' }] }, evidence: '[实时API:hbcli@2026-08-30T00:00:00Z]', latencyMs: 1800, rates: { rooms: [{ ratePkgId: 'rp-fx-1', roomTypeName: 'Deluxe King' }] }, summary: 'hotel 900000001:hbcli 实时报价(2026-09-05 → 2026-09-07)' },
|
|
428
|
+
HBCLI_CHECK_AVAIL: { via: 'hbcli-realtime', exitCode: 0, result: { ratePkgId: 'rp-fx-1', avail: true }, evidence: '[实时API:hbcli@2026-08-30T00:00:01Z]', latencyMs: 900, avail: { ratePkgId: 'rp-fx-1', avail: true }, summary: 'ratePkg rp-fx-1:hbcli 实时验价(库存与价格以后端为准)' },
|
|
429
|
+
});
|
|
430
|
+
const m10a = await mock10({ effect: 'HBCLI_HOTEL_RATES', params: { hotelId: '900000001' } });
|
|
431
|
+
const m10b = await mock10({ effect: 'HBCLI_CHECK_AVAIL', params: { ratePkgId: 'rp-fx-1' } });
|
|
432
|
+
assert.equal(m10a.result.rates?.rooms?.length, 1, 'RATES 夹具回放');
|
|
433
|
+
assert.equal(m10b.result.avail?.avail, true, 'CHECK_AVAIL 夹具回放');
|
|
434
|
+
assert.match(m10a.trace.evidence[0] ?? '', /\[效应:mock@.*夹具回放/, 'mock trace 标注');
|
|
435
|
+
console.log('10c. mock 解译器夹具回放(预订链 CI 形态)OK');
|
|
436
|
+
// ---------------------------------------------------------------------------
|
|
437
|
+
// 11. HBCLI×timeout 冷启动重试(2026-09-02 迪拜 session 复盘:hotel-list 首调
|
|
438
|
+
// 30s 超时,LLM 手动立即重试即成功——上游建后端 session 可超时;策略改为
|
|
439
|
+
// 「仅 timeout 类重试 1 次」,ENOENT/退码类维持「切换不是重试」)
|
|
440
|
+
// ---------------------------------------------------------------------------
|
|
441
|
+
__resetEffectBreakersForTest();
|
|
442
|
+
let flakyCalls = 0;
|
|
443
|
+
const ipTimeout = makeProductionInterpreter({
|
|
444
|
+
sleep: sleep0,
|
|
445
|
+
breakers: new Map(),
|
|
446
|
+
handlers: {
|
|
447
|
+
HBCLI_HOTEL_SEARCH: async () => {
|
|
448
|
+
flakyCalls += 1;
|
|
449
|
+
if (flakyCalls === 1)
|
|
450
|
+
return { via: 'hbcli-error', exitCode: -1, result: null, evidence: '[实时API:hbcli@timeout@ts]', latencyMs: 1, error: 'timeout after 30000ms' };
|
|
451
|
+
return { via: 'hbcli-realtime', exitCode: 0, result: { hotels: [] }, evidence: '[实时API:hbcli@ts]', latencyMs: 1 };
|
|
452
|
+
},
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
const itp11 = await ipTimeout({ effect: 'HBCLI_HOTEL_SEARCH', params: { destination: '迪拜' } });
|
|
456
|
+
assert.equal(itp11.trace.attempts, 2, `timeout 重试一次(attempts=2),实际 ${itp11.trace.attempts}`);
|
|
457
|
+
assert.equal(itp11.result?.via, 'hbcli-realtime', '重试后恢复实时');
|
|
458
|
+
assert.equal(itp11.trace.breaker, 'closed', '恢复即成功,断路不计数');
|
|
459
|
+
let hardCalls = 0;
|
|
460
|
+
const ipEnoent = makeProductionInterpreter({
|
|
461
|
+
sleep: sleep0,
|
|
462
|
+
breakers: new Map(),
|
|
463
|
+
handlers: {
|
|
464
|
+
HBCLI_HOTEL_SEARCH: async () => {
|
|
465
|
+
hardCalls += 1;
|
|
466
|
+
return { via: 'hbcli-error', exitCode: -1, result: null, evidence: '[实时API:hbcli@spawn_error@ts]', latencyMs: 1, error: 'spawn hbcli ENOENT' };
|
|
467
|
+
},
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
const itp11b = await ipEnoent({ effect: 'HBCLI_HOTEL_SEARCH', params: { destination: '迪拜' } });
|
|
471
|
+
assert.equal(itp11b.trace.attempts, 1, 'ENOENT 类永不重试(切换不是重试契约不变)');
|
|
472
|
+
console.log('11. HBCLI×timeout 冷启动重试(timeout→attempts=2 恢复实时;ENOENT→attempts=1)OK');
|
|
473
|
+
// ---------------------------------------------------------------------------
|
|
474
|
+
// 12. D-23 收尾(issue #115):六渠道入注册表——策略表行存在 + 横切语义正确
|
|
475
|
+
// (anything/web/github/video/agent_reach/session_login;没有策略表行就没有效应)
|
|
476
|
+
// ---------------------------------------------------------------------------
|
|
477
|
+
__resetEffectBreakersForTest();
|
|
478
|
+
// 12a. 六效应注册在案:handler 注入 → 原样透传,trace.channel 与策略表一致
|
|
479
|
+
const sixChannels = [
|
|
480
|
+
['ANYTHING_SEARCH', 'cli', { ok: true, via: 'hbcli-anything', verdict: 'hit', evidence: 'e', latencyMs: 1 }],
|
|
481
|
+
['WEB_READ', 'api', { ok: true, via: 'r.jina.ai', evidence: 'e', latencyMs: 1 }],
|
|
482
|
+
['GITHUB_SEARCH', 'cli', { ok: true, verdict: 'found', evidence: 'e', latencyMs: 1 }],
|
|
483
|
+
['VIDEO_SUBTITLE', 'cli', { ok: true, verdict: 'found', evidence: 'e', latencyMs: 1 }],
|
|
484
|
+
['AGENT_REACH', 'cli', { ok: true, verdict: 'found', channel: 'v2ex', method: 'read', evidence: 'e', latencyMs: 1 }],
|
|
485
|
+
['SESSION_LOGIN', 'browser', { ok: true, via: 'session-login', verdict: 'pending', evidence: 'e', latencyMs: 1 }],
|
|
486
|
+
];
|
|
487
|
+
for (const [fx, channel, obs] of sixChannels) {
|
|
488
|
+
const ip = makeProductionInterpreter({ sleep: sleep0, breakers: new Map(), handlers: { [fx]: async () => obs } });
|
|
489
|
+
const out = await ip({ effect: fx, params: {} });
|
|
490
|
+
assert.equal(out.result, obs, `${fx} 透传原样`);
|
|
491
|
+
assert.equal(out.trace.channel, channel, `${fx} trace.channel=${channel}(策略表行在案)`);
|
|
492
|
+
assert.equal(out.trace.attempts, 1, `${fx} 成功单次执行`);
|
|
493
|
+
}
|
|
494
|
+
console.log('12a. 六渠道入表(透传 + trace.channel 与策略表一致)OK');
|
|
495
|
+
// 12b. 永不重试族:AGENT_REACH / SESSION_LOGIN handler 恒抛错 → attempts=1(透传面不放大上游消耗)
|
|
496
|
+
for (const fx of ['AGENT_REACH', 'SESSION_LOGIN']) {
|
|
497
|
+
const ip = makeProductionInterpreter({ sleep: sleep0, breakers: new Map(), handlers: { [fx]: async () => { throw new Error('upstream says no'); } } });
|
|
498
|
+
const out = await ip({ effect: fx, params: {} });
|
|
499
|
+
assert.equal(out.result, null, `${fx} 抛错 → result=null`);
|
|
500
|
+
assert.equal(out.trace.attempts, 1, `${fx} 永不重试(attempts=1)`);
|
|
501
|
+
}
|
|
502
|
+
console.log('12b. AGENT_REACH/SESSION_LOGIN 永不重试(反射桥透传/浏览器风控红线)OK');
|
|
503
|
+
// 12c. WEB_READ 瞬时抖动重试 1 次(throw → 成功,attempts=2);GITHUB timeout 重试 / not-installed 不重试
|
|
504
|
+
__resetEffectBreakersForTest();
|
|
505
|
+
let webCalls = 0;
|
|
506
|
+
const ipWeb = makeProductionInterpreter({
|
|
507
|
+
sleep: sleep0, breakers: new Map(),
|
|
508
|
+
handlers: { WEB_READ: async () => { webCalls += 1; if (webCalls === 1)
|
|
509
|
+
throw new Error('socket hang up'); return { ok: true, via: 'r.jina.ai', evidence: 'e', latencyMs: 1 }; } },
|
|
510
|
+
});
|
|
511
|
+
const outWeb = await ipWeb({ effect: 'WEB_READ', params: { url: 'https://example.com' } });
|
|
512
|
+
assert.equal(outWeb.trace.attempts, 2, `WEB_READ 瞬时抖动重试 1 次,实际 ${outWeb.trace.attempts}`);
|
|
513
|
+
let ghTimeoutCalls = 0;
|
|
514
|
+
const ipGhTimeout = makeProductionInterpreter({
|
|
515
|
+
sleep: sleep0, breakers: new Map(),
|
|
516
|
+
handlers: { GITHUB_SEARCH: async () => { ghTimeoutCalls += 1; if (ghTimeoutCalls === 1)
|
|
517
|
+
return { ok: false, verdict: 'timeout', evidence: 'e', latencyMs: 1, stdout: '', stderr: '' }; return { ok: true, verdict: 'found', evidence: 'e', latencyMs: 1 }; } },
|
|
518
|
+
});
|
|
519
|
+
const outGhT = await ipGhTimeout({ effect: 'GITHUB_SEARCH', params: { query: 'x' } });
|
|
520
|
+
assert.equal(outGhT.trace.attempts, 2, 'GITHUB timeout 类重试 1 次(冷启动同 HBCLI 实况)');
|
|
521
|
+
let ghNiCalls = 0;
|
|
522
|
+
const ipGhNi = makeProductionInterpreter({
|
|
523
|
+
sleep: sleep0, breakers: new Map(),
|
|
524
|
+
handlers: { GITHUB_SEARCH: async () => { ghNiCalls += 1; return { ok: false, verdict: 'not-installed', evidence: 'e', latencyMs: 0 }; } },
|
|
525
|
+
});
|
|
526
|
+
const outGhNi = await ipGhNi({ effect: 'GITHUB_SEARCH', params: { query: 'x' } });
|
|
527
|
+
assert.equal(outGhNi.trace.attempts, 1, 'not-installed 是配置态(needs-setup 族)不重试');
|
|
528
|
+
console.log('12c. WEB_READ/GITHUB/VIDEO 策略行(timeout 重试/配置态不重试)OK');
|
|
529
|
+
// 12d. mock 面:新效应夹具回放 + 未登记夹具结构化拒绝(与既有效应同语义)
|
|
530
|
+
__resetEffectBreakersForTest();
|
|
531
|
+
const mockIp = makeMockInterpreter({ ANYTHING_SEARCH: { ok: true, via: 'hbcli-anything', verdict: 'hit', evidence: '[实时API:hbcli-anything@ts]', latencyMs: 1 } });
|
|
532
|
+
const outMock = await mockIp({ effect: 'ANYTHING_SEARCH', params: { keyword: '大理' } });
|
|
533
|
+
assert.equal(outMock.result.verdict, 'hit', 'mock 夹具回放原样');
|
|
534
|
+
assert.equal(outMock.trace.channel, 'mock', 'mock 通道标注');
|
|
535
|
+
const outMockBad = await mockIp({ effect: 'SESSION_LOGIN', params: {} });
|
|
536
|
+
assert.equal(outMockBad.result, null, '未登记夹具 → result=null');
|
|
537
|
+
assert.equal(outMockBad.trace.declined, 'unknown-effect', '未登记夹具 → 结构化拒绝');
|
|
538
|
+
console.log('12d. mock 夹具回放/未登记拒绝(新效应同语义)OK');
|
|
539
|
+
console.log('EFFECT INTERPRETER TESTS: 12/12 OK(effect_interpreter.v1:注册表封闭/退避链/断路三态/Sentinel 不重试/mock 夹具/SESSION 红线/真实降级/M0 预订链读效应/HBCLI timeout 重试/D-23 六渠道入表,纯离线)');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// TS 引擎直接跑洱海金标准用例,打印与 Python 相同结构的结果
|
|
1
2
|
import { readFile } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { parseCandidate, parseRequest } from '../src/model.js';
|
|
@@ -9,6 +10,3 @@ const t0 = Date.now();
|
|
|
9
10
|
const result = await solve(req, candidates);
|
|
10
11
|
console.log(`(TS 引擎总耗时含 WASM init: ${Date.now() - t0}ms, recommended=${result['recommended']})\n`);
|
|
11
12
|
console.log(result['answer_md']);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/engine-run.ts
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TS 引擎金标准断言(与 py/tests/test_engine.py 同一套洱海用例)。
|
|
3
|
+
* 运行(在 ts/ 下):npx tsx scripts/engine-tests.ts
|
|
4
|
+
*/
|
|
1
5
|
import assert from 'node:assert/strict';
|
|
2
6
|
import { readFile } from 'node:fs/promises';
|
|
3
7
|
import { join } from 'node:path';
|
|
@@ -7,39 +11,41 @@ const payload = JSON.parse(await readFile(join('..', 'data', 'golden_erhai.json'
|
|
|
7
11
|
const req = parseRequest(payload['request']);
|
|
8
12
|
const candidates = payload['candidates'].map(parseCandidate);
|
|
9
13
|
const result = await solve(req, candidates);
|
|
10
|
-
const verdicts = Object.fromEntries(result.verdicts.map(
|
|
11
|
-
|
|
12
|
-
v
|
|
13
|
-
]));
|
|
14
|
+
const verdicts = Object.fromEntries(result.verdicts.map(v => [v['candidate_id'], v]));
|
|
15
|
+
// 1. 洱海在周末窗口不可行,unsat core 必须点名 duration
|
|
14
16
|
assert.equal(verdicts['dali']['feasible'], false);
|
|
15
17
|
assert.ok(verdicts['dali']['unsat_core'].includes('duration'));
|
|
18
|
+
// 2. 憧憬不被拒绝:进 wish pool,带成行条件
|
|
16
19
|
const wp = verdicts['dali']['wish_pool'];
|
|
17
20
|
assert.ok(wp, 'dali wish pool exists');
|
|
18
21
|
assert.ok(wp['conditions']['days'] >= 5);
|
|
19
22
|
assert.ok('budget_cny' in wp['conditions']);
|
|
20
23
|
assert.ok(wp['conditions']['best_months'].length > 0);
|
|
24
|
+
// 3. 最小修改建议必须写明代价
|
|
21
25
|
assert.ok(verdicts['dali']['suggestions'].length > 0);
|
|
22
|
-
assert.ok(verdicts['dali']['suggestions'].some((s)=>s['text'].includes('天')));
|
|
26
|
+
assert.ok(verdicts['dali']['suggestions'].some((s) => s['text'].includes('天')));
|
|
27
|
+
// 4. 千岛湖可行且被推荐
|
|
23
28
|
assert.equal(verdicts['qiandao']['feasible'], true);
|
|
24
29
|
assert.equal(result.recommended, 'qiandao');
|
|
30
|
+
// 5. 全成本六要素齐全
|
|
25
31
|
const t = verdicts['qiandao']['true_cost'];
|
|
26
32
|
assert.ok(t['money_cny'] <= 3000);
|
|
27
33
|
assert.ok(Number(String(t['wake']).replace(':', '')) >= 630);
|
|
28
34
|
assert.ok(t['energy_arrival_pct'] >= 40);
|
|
29
35
|
assert.ok(t['usable_hours'] >= requiredUsableHours(req.motivation));
|
|
36
|
+
// 6. 太湖也可行且更便宜(同一可行口径内)
|
|
30
37
|
assert.equal(verdicts['taihu']['feasible'], true);
|
|
31
38
|
assert.ok(verdicts['taihu']['true_cost']['money_cny'] <= verdicts['qiandao']['true_cost']['money_cny']);
|
|
39
|
+
// 7. 回答不空手而归
|
|
32
40
|
assert.ok(result.answer_md.includes('千岛湖'));
|
|
33
41
|
assert.ok(result.answer_md.includes('下一次出发'));
|
|
34
42
|
assert.ok(result.answer_md.includes('待你决定的两个问题'));
|
|
43
|
+
// 8. 预算差一口气的场景:报告差额而非拒绝
|
|
35
44
|
const tight = structuredClone(payload);
|
|
36
45
|
tight['request']['budget_cny'] = 900;
|
|
37
46
|
const tightReq = parseRequest(tight['request']);
|
|
38
|
-
const v = await solveCandidate(candidates.find(
|
|
47
|
+
const v = await solveCandidate(candidates.find(c => c.id === 'qiandao'), tightReq);
|
|
39
48
|
assert.equal(v.feasible, false);
|
|
40
49
|
assert.ok(v.unsatCore.includes('budget'));
|
|
41
|
-
assert.ok(v.suggestions.some(
|
|
50
|
+
assert.ok(v.suggestions.some(s => s.text.includes('预算提高到')));
|
|
42
51
|
console.log('TS ENGINE TESTS: 8/8 OK');
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
//# sourceURL=/Users/bytedance/work/gotry/ts/scripts/engine-tests.ts
|