booking-agent-cli 0.9.0__tar.gz → 0.9.1__tar.gz
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.
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/PKG-INFO +2 -2
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/README.md +1 -1
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/pyproject.toml +1 -1
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/__init__.py +1 -1
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/defaults.env +3 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/__init__.py +1 -1
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/booking_poll.py +8 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/booking_summary.py +2 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/bundled_skill.md +22 -22
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/calendar_preference.py +7 -2
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/flow.py +206 -14
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/payment_helpers.py +25 -2
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/reschedule.py +2 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/runtime_prepare.py +76 -1
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/session.py +7 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/support_report.py +24 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/conftest.py +2 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_ucp_skill_flow_e2e.py +104 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_flow_reschedule.py +16 -0
- booking_agent_cli-0.9.1/tests/integration/test_resume_after_restart.py +240 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_cancel_result.py +83 -24
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/ucp_checkout_store.py +7 -1
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/.gitignore +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/authorization.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/backend/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/backend/live.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/filter.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/google.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/oauth_listener.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/scope.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/circuit_breaker.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/config.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/discovery.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/faults.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/headless.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/http_deadlines.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/http_retry.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/http_trace.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/merchant_pacing.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/models/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/models/platform.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/models/wire.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/namespace.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/observability.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/platform_profile.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/pop.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/profile_cache.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/registry_url.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/rest_client.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/secure_file.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/settings.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/signals.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/step_timing.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/ucp_capabilities.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/ucp_checkout.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/ucp_negotiation.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/ucp_profile.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/usp_methods.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/activity_log.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/calendar_connection.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/calendar_event.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/catalog.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/checkout_prereq.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/config.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/lowlevel.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/runtime.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/update.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/config_loader.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/engine.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/fault_report_schema.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/fault_reports.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/flow_timings.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/gates.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/geo_eligibility.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/geocode.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/identity.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/install_integrity.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/link_cli.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/main.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/output.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/payment_token.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/provenance.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/runtime.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/search_area.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/skill_bundle.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/skill_integrity.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/support_bundle_schema.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/timezone_context.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/updates.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/.env.tests +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_cancel_reschedule_e2e.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_latency_soak.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_staging_registry_integration.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_timezone_booking_e2e.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_ucp_native_e2e.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/ucp_native.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_activity_log.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_at_rest_hygiene.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_booking_rejection.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_bulk_cancel.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_calendar_connection.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_calendar_connector.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_calendar_flow.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_calendar_preference.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_checkout_signals.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_circuit_breaker.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_cli_enforced_gates.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_config.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_correlation_flow.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_declined_spt.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_discover_registry_rejection.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_flow_evidence.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_flow_gates.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_flow_step_timings.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_google_calendar_oauth.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_home_visit_address.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_http_trace.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_identity_logout.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_install_integrity.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_lazy_oauth.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_link_auth.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_link_cli_spt.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_namespace.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_oauth_listener.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_oauth_metadata.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_platform_key_pop.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_platform_profile.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_pop_keystore.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_profile_cache.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_regression_index.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_release_hold_uses_delete.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_request_retry_semantics.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_runtime_prepare.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_search_area.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_session_compat.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_session_retention.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_skill_integrity.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_skill_portability.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_support_report.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_timezone_context.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_checkout_pop.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_checkout_proof_wireup.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_checkout_validate.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_profile.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_update_command.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_wheel_contents.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/SOURCE +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/acp_booking_extension.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/availability.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/booking.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/calendar_freebusy.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/catalog.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/paid_bookings.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/profile.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/registry.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/rest_common.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/usp.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/waitlist.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/webhook_event.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/fixtures/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/fixtures/profiles.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/fixtures/provenance/pypi_attestations-0.0.30-py3-none-any.whl.provenance.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/fixtures/services.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/backend.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/link_cli.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/mock_calendar.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/pop_business.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/pop_transport.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/ucp_server.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/offline_stack/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/offline_stack/stack.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/__init__.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_authorization_policy.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_booking_poll_credentials.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_booking_request_error.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_calendar_filter.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_catalog.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_check_version.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_conditional_writes.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_delivery_address.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_discovery.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_fault_reports.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_geo_eligibility.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_geocode.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_headless.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_http_deadlines.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_http_retry.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_live_search_registry.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_merchant_pacing.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_mock_support.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_money_path_events.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_nonce_from_pop_error_body.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_observability.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_outbound_member_names.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_package_layering.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_pay_at_service.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_provenance.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_registry_search_filters.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_registry_search_pricing.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_registry_search_responses.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_registry_url.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_release_notes.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_rest_client.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_scoped_method_authorization.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_session_credentials.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_spec_conformance.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_step_timing.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_ucp_capabilities.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_ucp_complete_outcome.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_ucp_create_price_guard.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_ucp_negotiation.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_updates.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_wire_model_conformance.py +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/001-jcs-nested-buyer.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/002-jcs-meta-only.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/003-jcs-unicode-empty-meta.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/004-proof-issuance-no-ath.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/005-proof-presentation-ath-nonce.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/006-cross-key-replay.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/007-downgrade-cnf-without-proof.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/008-replayed-jti.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/009-aud-mismatch.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/010-alg-none.json +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/README.md +0 -0
- {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/SOURCE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: booking-agent-cli
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.1
|
|
4
4
|
Summary: The Booking Agent CLI: a stateful USP booking flow with hard gates, plus its USP protocol library.
|
|
5
5
|
Project-URL: Homepage, https://github.com/wix/booking-agent
|
|
6
6
|
Project-URL: Documentation, https://github.com/wix/booking-agent/blob/master/booking-agent/README.md
|
|
@@ -78,7 +78,7 @@ Each release also publishes that skill at an immutable `skill-v<version>` tag an
|
|
|
78
78
|
the release workflow, so a harness can pin and verify the exact text it follows:
|
|
79
79
|
|
|
80
80
|
```bash
|
|
81
|
-
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.
|
|
81
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.1/booking-agent/skills/booking-agent/SKILL.md
|
|
82
82
|
gh attestation verify SKILL.md --repo wix/booking-agent \
|
|
83
83
|
--signer-workflow wix/booking-agent/.github/workflows/release.yml
|
|
84
84
|
```
|
|
@@ -36,7 +36,7 @@ Each release also publishes that skill at an immutable `skill-v<version>` tag an
|
|
|
36
36
|
the release workflow, so a harness can pin and verify the exact text it follows:
|
|
37
37
|
|
|
38
38
|
```bash
|
|
39
|
-
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.
|
|
39
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.1/booking-agent/skills/booking-agent/SKILL.md
|
|
40
40
|
gh attestation verify SKILL.md --repo wix/booking-agent \
|
|
41
41
|
--signer-workflow wix/booking-agent/.github/workflows/release.yml
|
|
42
42
|
```
|
|
@@ -20,6 +20,9 @@ BOOKING_AGENT_SESSION_DIR=~/.booking-agent
|
|
|
20
20
|
# Set to `none` by a host that keeps this runtime's files between conversations itself, so
|
|
21
21
|
# `flow init` does not ask the agent how state is kept.
|
|
22
22
|
# BOOKING_AGENT_PERSISTENCE=
|
|
23
|
+
# Set to `0` to let a new conversation change a saved booking without first running
|
|
24
|
+
# `flow status`. By default it must read the saved booking once per runtime.
|
|
25
|
+
# BOOKING_AGENT_RESUME_CHECK=
|
|
23
26
|
BOOKING_AGENT_MAX_SESSIONS=30
|
|
24
27
|
LINK_CLI_BIN=link-cli
|
|
25
28
|
|
|
@@ -60,6 +60,14 @@ def apply_booking_status(session: Session, booking: Any) -> str:
|
|
|
60
60
|
session.booking.revision = revision
|
|
61
61
|
status_val = booking_status_value(booking)
|
|
62
62
|
session.booking.status = status_val
|
|
63
|
+
if getattr(session, "payment_route", None) == "pay_at_service":
|
|
64
|
+
# Settled offline at the appointment: no booking status means money was collected.
|
|
65
|
+
session.payment.status = "not_required"
|
|
66
|
+
if status_val in _SUCCESS_STATUSES:
|
|
67
|
+
transition(session, to="CONFIRMED")
|
|
68
|
+
elif status_val in _FAILURE_STATUSES:
|
|
69
|
+
transition(session, to="FAILED")
|
|
70
|
+
return status_val
|
|
63
71
|
if status_val in _SUCCESS_STATUSES:
|
|
64
72
|
session.payment.status = "paid"
|
|
65
73
|
transition(session, to="CONFIRMED")
|
{booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/booking_summary.py
RENAMED
|
@@ -356,6 +356,8 @@ def booked_message(session: Any) -> str:
|
|
|
356
356
|
link = getattr(session, "calendar_event_html_link", None)
|
|
357
357
|
if link:
|
|
358
358
|
text += f"\nAdded to your Google Calendar: {link}"
|
|
359
|
+
elif getattr(session, "calendar_skipped_unasked", False):
|
|
360
|
+
text += "\nNo calendar event was added: Google Calendar was not connected for this booking."
|
|
359
361
|
if getattr(session, "payment_route", None) == "pay_at_service":
|
|
360
362
|
text += "\n" + pay_at_service_line(session)
|
|
361
363
|
booking_url = getattr(getattr(session, "booking", None), "booking_url", None)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 0.57.
|
|
2
|
+
version: 0.57.1
|
|
3
3
|
name: booking-agent
|
|
4
4
|
description: |
|
|
5
5
|
Books time-based services via USP from a Link wallet. Use when the user wants to book, schedule, reserve, or pay for a service from a business (haircut, lesson, repair, class, appointment, consult). The `booking-agent` CLI owns the booking state machine and refuses to proceed when human gates are skipped. Sign the user in to Link on your own, right after install and at the start of every booking (`booking-agent flow link-auth connect`, then start `booking-agent flow link-auth poll` in the background and post the sign-in URL in the same turn, exactly like `pay poll`); never wait for the user to ask to sign in or to say they approved. After `booking-agent flow business use`, follow `next_command` through `use-ucp-checkout-mode`; businesses without native checkout fail closed. Payment uses `booking-agent flow pay request` (default returns immediately with the approval URL), then start `booking-agent flow pay poll` in the background and post the approval URL to the user in the same turn; the poll finishes checkout once they approve. Confirm through `booking-agent flow book poll --wait`; never start an inbound webhook. Surface the approval URL exactly once. Personal identification (name, phone, email, address) must always come from the user's Link account via `link-cli`, never from chat or placeholders. Before any location-based search, the CLI proposes the Link shipping address and the user confirms it or gives another; online services skip this. Talk to the user in plain everyday language only; never expose CLI, UCP, auth, session, or other implementation details unless they explicitly ask.
|
|
@@ -19,7 +19,7 @@ metadata:
|
|
|
19
19
|
- link-cli
|
|
20
20
|
install:
|
|
21
21
|
- kind: uv
|
|
22
|
-
package: "booking-agent-cli==0.9.
|
|
22
|
+
package: "booking-agent-cli==0.9.1"
|
|
23
23
|
bins: [booking-agent]
|
|
24
24
|
user-invocable: true
|
|
25
25
|
---
|
|
@@ -220,7 +220,7 @@ To take a fixed, published version instead of the latest, fetch it at the releas
|
|
|
220
220
|
workflow signs it under, and check the signature before you follow it:
|
|
221
221
|
|
|
222
222
|
```bash
|
|
223
|
-
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.
|
|
223
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.1/booking-agent/skills/booking-agent/SKILL.md
|
|
224
224
|
gh attestation verify SKILL.md \
|
|
225
225
|
--repo wix/booking-agent \
|
|
226
226
|
--signer-workflow wix/booking-agent/.github/workflows/release.yml
|
|
@@ -245,17 +245,17 @@ before installing:
|
|
|
245
245
|
|
|
246
246
|
```bash
|
|
247
247
|
dir="$HOME/.booking-agent/verified-wheels"
|
|
248
|
-
uvx pip download "booking-agent-cli==0.9.
|
|
248
|
+
uvx pip download "booking-agent-cli==0.9.1" --no-deps --only-binary :all: -d "$dir"
|
|
249
249
|
uvx pypi-attestations==0.0.30 verify pypi --repository https://github.com/wix/booking-agent \
|
|
250
|
-
"$dir/booking_agent_cli-0.9.
|
|
251
|
-
uv tool install --force "$dir/booking_agent_cli-0.9.
|
|
250
|
+
"$dir/booking_agent_cli-0.9.1-py3-none-any.whl"
|
|
251
|
+
uv tool install --force "$dir/booking_agent_cli-0.9.1-py3-none-any.whl"
|
|
252
252
|
|
|
253
253
|
booking-agent --help
|
|
254
254
|
```
|
|
255
255
|
|
|
256
256
|
Install only the wheel whose verify step passed, never one that failed it. If `uvx` is not
|
|
257
|
-
available, install the same pinned release with `uv tool install booking-agent-cli==0.9.
|
|
258
|
-
`pipx install booking-agent-cli==0.9.
|
|
257
|
+
available, install the same pinned release with `uv tool install booking-agent-cli==0.9.1` or
|
|
258
|
+
`pipx install booking-agent-cli==0.9.1`; the CLI checks its own files against the signed release
|
|
259
259
|
on the first `booking-agent flow init` and refuses to book or pay if they do not match.
|
|
260
260
|
|
|
261
261
|
Harnesses that read the `install` block in this skill's metadata do this for you. Do not go on
|
|
@@ -283,9 +283,8 @@ folder holds (booking files by session, the newer copy of anything else) and del
|
|
|
283
283
|
the runtime keeps its files anyway, or gives no such folder, re-run it with `--no-persistent-root`.
|
|
284
284
|
If it answers `persistence_folder_not_durable` the folder failed a basic check (a temporary or
|
|
285
285
|
memory-backed place, or not writable): name the folder the runtime really keeps, or use
|
|
286
|
-
`--no-persistent-root`.
|
|
287
|
-
`persistence_setup_failed` and `persistence_flags_conflict` say what to change. Use the same folder
|
|
288
|
-
every time, never guess a path, never ask the user, and never mention it. A host
|
|
286
|
+
`--no-persistent-root`.
|
|
287
|
+
`persistence_setup_failed` and `persistence_flags_conflict` say what to change. Use the same folder every time, never guess a path, never ask the user, and never mention it. A host
|
|
289
288
|
that sets `BOOKING_AGENT_PERSISTENT_ROOT` or `BOOKING_AGENT_PERSISTENCE=none` is never asked. If the
|
|
290
289
|
user is asked to sign in or reconnect right after a restart, run `booking-agent runtime doctor` and
|
|
291
290
|
follow its `next_command`. For extra cache folders the runtime asks you to keep, run `booking-agent
|
|
@@ -308,7 +307,7 @@ prints each effective setting and where it came from; `booking-agent config init
|
|
|
308
307
|
|
|
309
308
|
The CLI decides whether a newer version is worth mentioning; you never decide from version
|
|
310
309
|
numbers or release pages. Pass this skill's version on every `flow init` (`--skill-version
|
|
311
|
-
0.57.
|
|
310
|
+
0.57.1`, from the frontmatter above), so the CLI can tell when these instructions and the
|
|
312
311
|
installed CLI no longer match.
|
|
313
312
|
|
|
314
313
|
- When a command returns `awaiting_user: update_consent`, ask the question in `prompt` in plain
|
|
@@ -404,6 +403,7 @@ current state at any time: `booking-agent flow status`.
|
|
|
404
403
|
## Agent loop (run on every turn)
|
|
405
404
|
|
|
406
405
|
1. `booking-agent flow status` — read `phase`, `awaiting_user`, `prompt`, `next_command`, `forbidden`.
|
|
406
|
+
A new conversation starts here; the user's reply answers only the question status shows.
|
|
407
407
|
2. If `awaiting_user` is set:
|
|
408
408
|
- **`link_spend_approval`** or **`link_auth_approval`:** start the matching poll (`pay poll` or
|
|
409
409
|
`link-auth poll`) **in the background** (`run_in_background`) and, in the **same turn**, post
|
|
@@ -470,7 +470,7 @@ confirmation email or on the business's page (`booking_url`). Do not invent a lo
|
|
|
470
470
|
**Step 1b — Create a new session:**
|
|
471
471
|
|
|
472
472
|
```bash
|
|
473
|
-
booking-agent flow init --user-request "<user's exact request>" --skill-version 0.57.
|
|
473
|
+
booking-agent flow init --user-request "<user's exact request>" --skill-version 0.57.1 \
|
|
474
474
|
--skill-path "<absolute path of the SKILL.md you loaded>"
|
|
475
475
|
```
|
|
476
476
|
|
|
@@ -616,8 +616,8 @@ the user anything:
|
|
|
616
616
|
and never copy it into an env file. Look up the name each conversation; do not assume it.
|
|
617
617
|
- **`--route local-oauth`**: `data.local_oauth_configured` is true and you can relay a sign-in
|
|
618
618
|
link to the user.
|
|
619
|
-
- **Neither
|
|
620
|
-
|
|
619
|
+
- **Neither** (a listed but unconnected connector is a route): run
|
|
620
|
+
`booking-agent flow calendar skip --session-only --no-route` without asking, then continue.
|
|
621
621
|
|
|
622
622
|
The CLI stores the route and the variable name for later commands and later bookings, and it
|
|
623
623
|
never guesses one: `calendar connect` without `--route` fails with `calendar_route_required`,
|
|
@@ -777,8 +777,7 @@ address and the note from `buyer resolve` automatically. Never map or pass an ad
|
|
|
777
777
|
If it fails with `service_address_unconfirmed`, run its `next_command` and confirm the address
|
|
778
778
|
with the user.
|
|
779
779
|
|
|
780
|
-
`flow book` returns `data.merchant_quote` and `data.price_display
|
|
781
|
-
dollar total shown to the user). **Never** quote `amount_minor_units`, `price_minor_units`, or
|
|
780
|
+
`flow book` returns `data.merchant_quote` and `data.price_display`. **Never** quote `amount_minor_units`, `price_minor_units`, or
|
|
782
781
|
raw `amount` as dollars, only `price_display` or `merchant_quote.total_display`. Quote tax only
|
|
783
782
|
when `merchant_quote` includes a tax line from the merchant; never assume zero tax or warn that
|
|
784
783
|
tax might be extra when no tax line was returned. The spend request uses the merchant-advertised
|
|
@@ -794,12 +793,14 @@ nothing.
|
|
|
794
793
|
directly and `next_command` is `booking-agent flow book poll --wait`. Before running it, tell the user
|
|
795
794
|
in one short plain message what `data.booking_narration` says: the request is in, nothing was
|
|
796
795
|
charged (or the price is paid at the appointment), and you are now waiting for the business to
|
|
797
|
-
confirm. Then run
|
|
796
|
+
confirm. Then run it and report the result. Never create a payment and skip
|
|
798
797
|
Steps 6b to 6d.
|
|
799
798
|
|
|
800
799
|
**Pay-at-appointment with a checkout (`pay_at_service`).** Nothing is charged; skip `pay request`.
|
|
801
|
-
Show the amount due at the appointment and any policies, then run `next_command
|
|
802
|
-
|
|
800
|
+
Show the amount due at the appointment and any policies, then run `next_command`. If
|
|
801
|
+
`merchant_quote.total_changed`, get approval and add `--total-confirmed`. On
|
|
802
|
+
`booking_confirmation_pending` or `booking_not_confirmed`, post `user_message` and run any
|
|
803
|
+
`next_command`.
|
|
803
804
|
|
|
804
805
|
**Deposit bookings (`deposit_required`).** Before any charge, show `data.due_now_display` (the
|
|
805
806
|
deposit charged now), the amount due at the appointment, and every item in
|
|
@@ -1207,8 +1208,7 @@ Use `booking-agent flow sessions list` only to find unfinished flows or upcoming
|
|
|
1207
1208
|
|
|
1208
1209
|
## Settings
|
|
1209
1210
|
|
|
1210
|
-
|
|
1211
|
-
Two matter to a hosting surface: `BOOKING_AGENT_BUYER_DEVICE_IP` (the buyer's device IP when it
|
|
1211
|
+
Two settings matter to a host (`booking-agent config show` lists all): `BOOKING_AGENT_BUYER_DEVICE_IP` (the buyer's device IP when it
|
|
1212
1212
|
observed one) and `BOOKING_AGENT_USP_AUTH_SCOPED_CREDENTIAL` (`required` by default; see [Booking
|
|
1213
1213
|
authorization](#booking-authorization)). Faults the user never sees are reported automatically,
|
|
1214
1214
|
with no user data; `BOOKING_AGENT_FAULT_REPORTS=0` turns that off.
|
{booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/calendar_preference.py
RENAMED
|
@@ -153,10 +153,15 @@ def complete_calendar_gate_with_preference(
|
|
|
153
153
|
save_calendar_preference(state=state, mode=mode)
|
|
154
154
|
|
|
155
155
|
|
|
156
|
-
def skip_calendar_gate_session_only(session: Session) -> None:
|
|
157
|
-
"""Complete the gate as skipped for this session without persisting a store preference.
|
|
156
|
+
def skip_calendar_gate_session_only(session: Session, *, unasked: bool = False) -> None:
|
|
157
|
+
"""Complete the gate as skipped for this session without persisting a store preference.
|
|
158
|
+
|
|
159
|
+
``unasked``: the user did not choose this (no way to connect the calendar here), so the
|
|
160
|
+
booked message says no event was added.
|
|
161
|
+
"""
|
|
158
162
|
session.calendar_gate_completed = True
|
|
159
163
|
session.calendar_state = "skipped"
|
|
160
164
|
session.calendar_mode = CALENDAR_MODE_NONE
|
|
165
|
+
session.calendar_skipped_unasked = unasked
|
|
161
166
|
transition(session, to="CALENDAR_GATE")
|
|
162
167
|
save_session(session)
|
|
@@ -189,13 +189,52 @@ def _flow_setup(ctx: typer.Context) -> None:
|
|
|
189
189
|
from .. import config_loader
|
|
190
190
|
|
|
191
191
|
clear_last_http_failure()
|
|
192
|
+
config_loader.load()
|
|
192
193
|
sub = ctx.invoked_subcommand
|
|
194
|
+
_resume_check(sub)
|
|
193
195
|
if sub not in (None, "init", "sessions"):
|
|
194
196
|
session = load_session()
|
|
195
197
|
if session is not None:
|
|
196
198
|
record_command(session)
|
|
197
199
|
save_session(session)
|
|
198
|
-
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# Commands a new conversation may run before it has read the saved booking.
|
|
203
|
+
_BEFORE_RESUME_CHECK = frozenset({None, "init", "sessions", "status", "support-report"})
|
|
204
|
+
_MARKS_RESUME_CHECK = frozenset({"init", "sessions", "status"})
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _resume_check(sub: Optional[str]) -> None:
|
|
208
|
+
"""Make a runtime that did not start this booking read ``flow status`` first.
|
|
209
|
+
|
|
210
|
+
A new conversation can hold a saved booking that is waiting for the user's yes. Reading
|
|
211
|
+
the state is a CLI step so no skill text has to remember it.
|
|
212
|
+
"""
|
|
213
|
+
from .. import runtime_prepare
|
|
214
|
+
from ..session import session_path
|
|
215
|
+
|
|
216
|
+
if not runtime_prepare.resume_check_enabled():
|
|
217
|
+
return
|
|
218
|
+
path = session_path()
|
|
219
|
+
if sub in _MARKS_RESUME_CHECK:
|
|
220
|
+
runtime_prepare.mark_runtime_seen(path)
|
|
221
|
+
return
|
|
222
|
+
if sub in _BEFORE_RESUME_CHECK or runtime_prepare.runtime_has_seen(path):
|
|
223
|
+
return
|
|
224
|
+
session = load_session()
|
|
225
|
+
if session is None:
|
|
226
|
+
return
|
|
227
|
+
emit_error(
|
|
228
|
+
session.flow_phase,
|
|
229
|
+
code="resume_check_required",
|
|
230
|
+
message=(
|
|
231
|
+
"This conversation did not start this booking. Run `booking-agent flow status`, "
|
|
232
|
+
"read it, and follow its next_command. The user's reply answers only the question "
|
|
233
|
+
"status shows; never list times again or start over."
|
|
234
|
+
),
|
|
235
|
+
next_command="booking-agent flow status",
|
|
236
|
+
)
|
|
237
|
+
raise typer.Exit(code=1)
|
|
199
238
|
|
|
200
239
|
|
|
201
240
|
# ---------------------------------------------------------------------------
|
|
@@ -1233,6 +1272,15 @@ def _bulk_cancel_one(session: Session, reason: Optional[str]) -> Dict[str, Any]:
|
|
|
1233
1272
|
}
|
|
1234
1273
|
finally:
|
|
1235
1274
|
bind_session_observability(None)
|
|
1275
|
+
if status_val == _CHECKOUT_ABANDONED:
|
|
1276
|
+
return {
|
|
1277
|
+
"outcome": "canceled",
|
|
1278
|
+
"status": status_val,
|
|
1279
|
+
"cancellation": None,
|
|
1280
|
+
"refund_follow_up": None,
|
|
1281
|
+
"calendar": None,
|
|
1282
|
+
"user_message": _checkout_abandoned_message(session),
|
|
1283
|
+
}
|
|
1236
1284
|
row: Dict[str, Any] = {
|
|
1237
1285
|
"outcome": "canceled" if status_val == "canceled" else "inactive",
|
|
1238
1286
|
"status": status_val,
|
|
@@ -3426,7 +3474,7 @@ def _require_calendar_offer(session: Session) -> None:
|
|
|
3426
3474
|
|
|
3427
3475
|
def _emit_calendar_unavailable(session: Session, status: dict, mode: str, *, data: Optional[dict]) -> None:
|
|
3428
3476
|
session.calendar_technical_fallback = True
|
|
3429
|
-
skip_calendar_gate_session_only(session)
|
|
3477
|
+
skip_calendar_gate_session_only(session, unasked=True)
|
|
3430
3478
|
out = dict(data or {})
|
|
3431
3479
|
out.update(status)
|
|
3432
3480
|
out["calendar_state"] = "skipped"
|
|
@@ -3641,6 +3689,12 @@ def calendar_skip(
|
|
|
3641
3689
|
"--session-only",
|
|
3642
3690
|
help="Skip calendar checking for this booking only; keep the saved preference",
|
|
3643
3691
|
),
|
|
3692
|
+
no_route: bool = typer.Option(
|
|
3693
|
+
False,
|
|
3694
|
+
"--no-route",
|
|
3695
|
+
help="With --session-only: this runtime has no way to connect Google Calendar, so the "
|
|
3696
|
+
"user was not asked. The booked message then says no event was added.",
|
|
3697
|
+
),
|
|
3644
3698
|
) -> None:
|
|
3645
3699
|
session = must_load()
|
|
3646
3700
|
try:
|
|
@@ -3651,7 +3705,7 @@ def calendar_skip(
|
|
|
3651
3705
|
_require_calendar_offer(session)
|
|
3652
3706
|
_stop_pending_sign_in()
|
|
3653
3707
|
if session_only:
|
|
3654
|
-
skip_calendar_gate_session_only(session)
|
|
3708
|
+
skip_calendar_gate_session_only(session, unasked=no_route)
|
|
3655
3709
|
emit(_agent_out(
|
|
3656
3710
|
session,
|
|
3657
3711
|
prompt=(
|
|
@@ -4578,6 +4632,11 @@ def _adopt_response_timezone(session: Session, response_zones: Dict[str, Any]) -
|
|
|
4578
4632
|
return out
|
|
4579
4633
|
|
|
4580
4634
|
|
|
4635
|
+
_PAST_TIME_CHOICE_PHASES = frozenset({
|
|
4636
|
+
"BUYER_RESOLVED", "BOOKED", "PAYMENT_PENDING", "CONFIRMED",
|
|
4637
|
+
})
|
|
4638
|
+
|
|
4639
|
+
|
|
4581
4640
|
@app.command("availability")
|
|
4582
4641
|
def availability(
|
|
4583
4642
|
service_id: Optional[str] = typer.Option(None, "--service-id"),
|
|
@@ -4589,6 +4648,12 @@ def availability(
|
|
|
4589
4648
|
help="Single calendar day to query (YYYY-MM-DD). Sets a full-day window when "
|
|
4590
4649
|
"--from/--to are not given. Use this to honor a specific requested date.",
|
|
4591
4650
|
),
|
|
4651
|
+
change_time: bool = typer.Option(
|
|
4652
|
+
False,
|
|
4653
|
+
"--change-time",
|
|
4654
|
+
help="The user asked for a different time after the summary was shown. Drops the "
|
|
4655
|
+
"chosen slot and the summary, and lists times again.",
|
|
4656
|
+
),
|
|
4592
4657
|
) -> None:
|
|
4593
4658
|
"""Query availability for the selected business + service."""
|
|
4594
4659
|
session = must_load()
|
|
@@ -4597,6 +4662,23 @@ def availability(
|
|
|
4597
4662
|
require_calendar_gate(session)
|
|
4598
4663
|
except GateError as e:
|
|
4599
4664
|
raise _handle_gate(session, e)
|
|
4665
|
+
if session.flow_phase in _PAST_TIME_CHOICE_PHASES:
|
|
4666
|
+
if not (change_time and session.flow_phase == "BUYER_RESOLVED"):
|
|
4667
|
+
from ..engine import recommend_next
|
|
4668
|
+
|
|
4669
|
+
emit_error(
|
|
4670
|
+
session.flow_phase,
|
|
4671
|
+
code="availability_after_slot_confirmed",
|
|
4672
|
+
message=(
|
|
4673
|
+
"A time is already chosen for this booking. Do not list times again: run "
|
|
4674
|
+
"`booking-agent flow status` and follow next_command. Only if the user "
|
|
4675
|
+
"asks for a different time at the summary, add `--change-time`."
|
|
4676
|
+
),
|
|
4677
|
+
next_command=recommend_next(session) or "booking-agent flow status",
|
|
4678
|
+
)
|
|
4679
|
+
raise typer.Exit(code=1)
|
|
4680
|
+
session.slot_id = None
|
|
4681
|
+
session.slot_user_confirmed = False
|
|
4600
4682
|
|
|
4601
4683
|
if session.business.timezone and _valid_iana_timezone(session.business.timezone) is None:
|
|
4602
4684
|
emit_error(
|
|
@@ -4616,6 +4698,7 @@ def availability(
|
|
|
4616
4698
|
session.flow_phase,
|
|
4617
4699
|
code="missing_service_id",
|
|
4618
4700
|
message="--service-id is required (use the value from `flow discover`).",
|
|
4701
|
+
next_command="booking-agent flow discover",
|
|
4619
4702
|
)
|
|
4620
4703
|
raise typer.Exit(code=1)
|
|
4621
4704
|
|
|
@@ -6481,6 +6564,9 @@ def _complete_pay_at_service(
|
|
|
6481
6564
|
|
|
6482
6565
|
session.payment.order_id = str(result.get("order_id") or "") or None
|
|
6483
6566
|
session.payment.status = "not_required"
|
|
6567
|
+
save_session(session)
|
|
6568
|
+
if result.get("booking_confirmation_pending"):
|
|
6569
|
+
_await_pay_at_service_confirmation(session)
|
|
6484
6570
|
from ..flow_timings import apply_pending_timings
|
|
6485
6571
|
|
|
6486
6572
|
apply_pending_timings(session)
|
|
@@ -6491,6 +6577,64 @@ def _complete_pay_at_service(
|
|
|
6491
6577
|
_finish_post_payment_steps(session)
|
|
6492
6578
|
|
|
6493
6579
|
|
|
6580
|
+
_PAY_AT_SERVICE_CONFIRM_TIMEOUT_S = 120.0
|
|
6581
|
+
|
|
6582
|
+
|
|
6583
|
+
def _await_pay_at_service_confirmation(session: Session) -> None:
|
|
6584
|
+
"""Wait for the order to confirm a pay-at-service booking (USP 11.3.5 S6).
|
|
6585
|
+
|
|
6586
|
+
The business accepted the order and charged nothing; the booking confirms
|
|
6587
|
+
shortly after. Returns once it is confirmed and raises ``typer.Exit`` otherwise.
|
|
6588
|
+
"""
|
|
6589
|
+
business = _business_label(session)
|
|
6590
|
+
try:
|
|
6591
|
+
from booking_agent.step_timing import timed_step_sync
|
|
6592
|
+
|
|
6593
|
+
with timed_step_sync("confirm"):
|
|
6594
|
+
booking = wait_for_booking_terminal(
|
|
6595
|
+
session,
|
|
6596
|
+
deadline_ts=time.time() + _PAY_AT_SERVICE_CONFIRM_TIMEOUT_S,
|
|
6597
|
+
poll_interval=_CAPTURED_POLL_INTERVAL_S,
|
|
6598
|
+
)
|
|
6599
|
+
except Exception as e: # noqa: BLE001 - the order exists whatever the read failure was
|
|
6600
|
+
save_session(session)
|
|
6601
|
+
emit_error(
|
|
6602
|
+
session.flow_phase,
|
|
6603
|
+
code="booking_confirmation_pending",
|
|
6604
|
+
message=(
|
|
6605
|
+
f"{e} The business accepted the order and nothing was charged; the booking "
|
|
6606
|
+
"is not confirmed yet. Post user_message, then run next_command. Do not "
|
|
6607
|
+
"complete the checkout again and do not describe a payment."
|
|
6608
|
+
),
|
|
6609
|
+
user_message=(
|
|
6610
|
+
f"{business} accepted your booking request and nothing was charged. They "
|
|
6611
|
+
"haven't confirmed the appointment yet, so I'm still checking with them."
|
|
6612
|
+
),
|
|
6613
|
+
next_command="booking-agent flow book poll --wait",
|
|
6614
|
+
forbidden=["booking-agent flow pay ucp-complete", "booking-agent flow pay request"],
|
|
6615
|
+
data={"booking_id": session.booking.id, "order_id": session.payment.order_id},
|
|
6616
|
+
)
|
|
6617
|
+
raise typer.Exit(code=1)
|
|
6618
|
+
status_val = apply_booking_status(session, booking)
|
|
6619
|
+
save_session(session)
|
|
6620
|
+
if status_val in ("confirmed", "completed"):
|
|
6621
|
+
return
|
|
6622
|
+
emit_error(
|
|
6623
|
+
session.flow_phase,
|
|
6624
|
+
code="booking_not_confirmed",
|
|
6625
|
+
message=(
|
|
6626
|
+
f"The business marked the booking {status_val} instead of confirming it. Nothing "
|
|
6627
|
+
"was charged. Post user_message, then stop."
|
|
6628
|
+
),
|
|
6629
|
+
user_message=(
|
|
6630
|
+
f"{business} didn't confirm this appointment, so you're not booked. Nothing was "
|
|
6631
|
+
"charged."
|
|
6632
|
+
),
|
|
6633
|
+
data={"booking_id": session.booking.id, "status": status_val},
|
|
6634
|
+
)
|
|
6635
|
+
raise typer.Exit(code=1)
|
|
6636
|
+
|
|
6637
|
+
|
|
6494
6638
|
def _pay_ucp_complete_impl(
|
|
6495
6639
|
session: Session,
|
|
6496
6640
|
*,
|
|
@@ -7035,12 +7179,17 @@ def book_poll(
|
|
|
7035
7179
|
raise typer.Exit(code=1)
|
|
7036
7180
|
|
|
7037
7181
|
was_captured_unconfirmed = session.payment.captured_unconfirmed
|
|
7182
|
+
offline_order_waiting = (
|
|
7183
|
+
session.payment_route == "pay_at_service"
|
|
7184
|
+
and bool(session.payment.order_id)
|
|
7185
|
+
and session.flow_phase != "CONFIRMED"
|
|
7186
|
+
)
|
|
7038
7187
|
status_val = apply_booking_status(session, b)
|
|
7039
7188
|
if was_captured_unconfirmed and session.flow_phase == "CONFIRMED":
|
|
7040
7189
|
session.payment.captured_unconfirmed = False
|
|
7041
7190
|
save_session(session)
|
|
7042
7191
|
|
|
7043
|
-
if was_captured_unconfirmed and session.flow_phase == "CONFIRMED":
|
|
7192
|
+
if (was_captured_unconfirmed or offline_order_waiting) and session.flow_phase == "CONFIRMED":
|
|
7044
7193
|
_finish_post_payment_steps(session)
|
|
7045
7194
|
return
|
|
7046
7195
|
if was_captured_unconfirmed and status_val in ("canceled", "no_show"):
|
|
@@ -7130,6 +7279,21 @@ def book_cancel(
|
|
|
7130
7279
|
)
|
|
7131
7280
|
raise typer.Exit(code=1)
|
|
7132
7281
|
|
|
7282
|
+
if status_val == _CHECKOUT_ABANDONED:
|
|
7283
|
+
emit(_agent_out(
|
|
7284
|
+
session,
|
|
7285
|
+
user_message=_checkout_abandoned_message(session),
|
|
7286
|
+
prompt="The checkout was canceled before booking. Post user_message to the user as is.",
|
|
7287
|
+
data={
|
|
7288
|
+
"booking_id": bid,
|
|
7289
|
+
"status": _CHECKOUT_ABANDONED,
|
|
7290
|
+
"cancellation": None,
|
|
7291
|
+
"refund_follow_up": None,
|
|
7292
|
+
"calendar": None,
|
|
7293
|
+
},
|
|
7294
|
+
))
|
|
7295
|
+
return
|
|
7296
|
+
|
|
7133
7297
|
follow_up = settlement.get("refund_follow_up")
|
|
7134
7298
|
calendar = _remove_calendar_event_after_cancel(session) if status_val == "canceled" else None
|
|
7135
7299
|
user_message = (
|
|
@@ -7184,6 +7348,20 @@ class _CancelNeedsHuman(Exception):
|
|
|
7184
7348
|
self.handoff = handoff or {}
|
|
7185
7349
|
|
|
7186
7350
|
|
|
7351
|
+
class _CheckoutAbandoned(Exception):
|
|
7352
|
+
"""The open checkout was canceled; its never-confirmed booking stays pending."""
|
|
7353
|
+
|
|
7354
|
+
|
|
7355
|
+
_CHECKOUT_ABANDONED = "checkout_abandoned"
|
|
7356
|
+
|
|
7357
|
+
|
|
7358
|
+
def _checkout_abandoned_message(session: Session) -> str:
|
|
7359
|
+
return (
|
|
7360
|
+
f"I canceled your checkout with {_business_label(session)}. Nothing was booked "
|
|
7361
|
+
"and nothing was charged."
|
|
7362
|
+
)
|
|
7363
|
+
|
|
7364
|
+
|
|
7187
7365
|
class _CancelFailed(Exception):
|
|
7188
7366
|
"""The cancel did not go through; ``data`` still carries the buyer's way out."""
|
|
7189
7367
|
|
|
@@ -7204,6 +7382,12 @@ def _cancel_and_record(
|
|
|
7204
7382
|
http_status: Optional[int] = None
|
|
7205
7383
|
try:
|
|
7206
7384
|
booking, fee = asyncio.run(_cancel_booking_async(session, booking_id, reason))
|
|
7385
|
+
except _CheckoutAbandoned:
|
|
7386
|
+
from ..engine import transition
|
|
7387
|
+
|
|
7388
|
+
transition(session, to="FAILED")
|
|
7389
|
+
save_session(session)
|
|
7390
|
+
return _CHECKOUT_ABANDONED, {}
|
|
7207
7391
|
except _CancelNeedsHuman:
|
|
7208
7392
|
save_session(session)
|
|
7209
7393
|
raise
|
|
@@ -7343,7 +7527,7 @@ def _captured_amount_minor(session: Session, booking: Any) -> Optional[int]:
|
|
|
7343
7527
|
"""
|
|
7344
7528
|
from ..calendar_event import _payment_route
|
|
7345
7529
|
|
|
7346
|
-
if _payment_route(session) in ("free", "at_service"):
|
|
7530
|
+
if _payment_route(session) in ("free", "at_service", "pay_at_service"):
|
|
7347
7531
|
return None
|
|
7348
7532
|
payment = _record_field(booking, "payment")
|
|
7349
7533
|
booking_paid = str(_record_field(payment, "status") or "") in _CAPTURED_PAYMENT_STATUSES
|
|
@@ -7451,7 +7635,9 @@ def _cancel_needs_human_message(session: Session, booking_id: Optional[str], err
|
|
|
7451
7635
|
lines.extend(service_policy_lines(session.live_service))
|
|
7452
7636
|
for link in extract_policy_links(handoff.get("merchant_links") or session.ucp_checkout_links):
|
|
7453
7637
|
lines.append(f"{link['label']}: {link['url']}")
|
|
7454
|
-
if
|
|
7638
|
+
if session.payment_route != "pay_at_service" and (
|
|
7639
|
+
_payment_captured(session) or session.payment.order_id
|
|
7640
|
+
):
|
|
7455
7641
|
lines.append(
|
|
7456
7642
|
f"If you paid, ask {business} for a refund when you cancel, and give them the "
|
|
7457
7643
|
"references above so they can find your payment."
|
|
@@ -7551,6 +7737,8 @@ async def _manage_url_for_cancel(
|
|
|
7551
7737
|
|
|
7552
7738
|
|
|
7553
7739
|
async def _cancel_booking_async(session: Session, booking_id: str, reason: Optional[str]):
|
|
7740
|
+
if session.ucp_checkout_abandoned:
|
|
7741
|
+
raise _CheckoutAbandoned()
|
|
7554
7742
|
backend = resolve_backend()
|
|
7555
7743
|
profile = await backend.resolve_profile(session.business.profile_url)
|
|
7556
7744
|
endpoint = profile.rest_endpoint
|
|
@@ -7609,14 +7797,18 @@ async def _cancel_booking_async(session: Session, booking_id: str, reason: Optio
|
|
|
7609
7797
|
checkout,
|
|
7610
7798
|
expected_booking_id=booking_id,
|
|
7611
7799
|
)
|
|
7612
|
-
if booking_context is
|
|
7613
|
-
|
|
7614
|
-
|
|
7615
|
-
|
|
7616
|
-
|
|
7617
|
-
|
|
7618
|
-
|
|
7619
|
-
|
|
7800
|
+
if booking_context is None:
|
|
7801
|
+
# USP 7.5 lets the business leave a never-confirmed booking pending:
|
|
7802
|
+
# it holds no slot and cannot be charged, so there is nothing to cancel.
|
|
7803
|
+
session.ucp_checkout_abandoned = True
|
|
7804
|
+
raise _CheckoutAbandoned()
|
|
7805
|
+
return (
|
|
7806
|
+
SimpleNamespace(
|
|
7807
|
+
id=booking_context["booking_id"],
|
|
7808
|
+
status=BookingStatus(booking_context["booking_status"]),
|
|
7809
|
+
),
|
|
7810
|
+
None,
|
|
7811
|
+
)
|
|
7620
7812
|
|
|
7621
7813
|
manage_url = await _manage_url_for_cancel(
|
|
7622
7814
|
session,
|
{booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/payment_helpers.py
RENAMED
|
@@ -610,6 +610,7 @@ async def complete_ucp_checkout_async(
|
|
|
610
610
|
extract_order_id_from_checkout,
|
|
611
611
|
immediate_schedule_amount,
|
|
612
612
|
resolve_handler_id_for_ucp_complete,
|
|
613
|
+
validate_checkout_totals_and_links,
|
|
613
614
|
validate_completed_checkout_selected_term,
|
|
614
615
|
validate_payment_terms,
|
|
615
616
|
validate_ucp_checkout_completed,
|
|
@@ -783,8 +784,18 @@ async def complete_ucp_checkout_async(
|
|
|
783
784
|
raise _outcome_error(classification, checkout) from exc
|
|
784
785
|
raise
|
|
785
786
|
|
|
787
|
+
def _offline_awaiting_confirmation(checkout: Any) -> bool:
|
|
788
|
+
# Nothing was charged, so a completed offline checkout whose booking is not
|
|
789
|
+
# confirmed yet is an accepted order waiting for the business, never a capture.
|
|
790
|
+
if not without_instrument or not isinstance(checkout, dict):
|
|
791
|
+
return False
|
|
792
|
+
if checkout.get("status") != "completed":
|
|
793
|
+
return False
|
|
794
|
+
booking = checkout.get("booking")
|
|
795
|
+
return not (isinstance(booking, dict) and booking.get("booking_status") == "confirmed")
|
|
796
|
+
|
|
786
797
|
def _raise_if_classified(checkout: Any) -> None:
|
|
787
|
-
if not isinstance(checkout, dict):
|
|
798
|
+
if not isinstance(checkout, dict) or _offline_awaiting_confirmation(checkout):
|
|
788
799
|
return
|
|
789
800
|
classification = classify_complete_checkout(checkout)
|
|
790
801
|
if classification.outcome in (CompleteOutcome.UNCLASSIFIED, CompleteOutcome.COMPLETED):
|
|
@@ -814,11 +825,22 @@ async def complete_ucp_checkout_async(
|
|
|
814
825
|
result = await _post()
|
|
815
826
|
_raise_if_classified(result)
|
|
816
827
|
break
|
|
828
|
+
awaiting_confirmation = _offline_awaiting_confirmation(result)
|
|
817
829
|
try:
|
|
818
|
-
|
|
830
|
+
if awaiting_confirmation:
|
|
831
|
+
validate_checkout_totals_and_links(result)
|
|
832
|
+
order_id = extract_order_id_from_checkout(result)
|
|
833
|
+
if not order_id:
|
|
834
|
+
raise UcpProfileValidationError(
|
|
835
|
+
"UCP complete_checkout response missing order_id (or order.id)"
|
|
836
|
+
)
|
|
837
|
+
else:
|
|
838
|
+
order_id = validate_ucp_checkout_completed(result) if isinstance(result, dict) else ""
|
|
819
839
|
if term is not None and isinstance(result, dict):
|
|
820
840
|
validate_completed_checkout_selected_term(result, term)
|
|
821
841
|
except UcpProfileValidationError:
|
|
842
|
+
if awaiting_confirmation:
|
|
843
|
+
raise
|
|
822
844
|
if isinstance(result, dict) and result.get("status") == "completed":
|
|
823
845
|
booking_block = result.get("booking")
|
|
824
846
|
booking_status = (
|
|
@@ -852,6 +874,7 @@ async def complete_ucp_checkout_async(
|
|
|
852
874
|
"checkout_id": cid,
|
|
853
875
|
"order_id": order_id,
|
|
854
876
|
"handler_id_resolved": resolved_handler,
|
|
877
|
+
"booking_confirmation_pending": awaiting_confirmation,
|
|
855
878
|
}
|
|
856
879
|
|
|
857
880
|
|