booking-agent-cli 0.6.0__tar.gz → 0.7.0__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.6.0 → booking_agent_cli-0.7.0}/PKG-INFO +2 -2
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/README.md +1 -1
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/pyproject.toml +1 -1
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/__init__.py +1 -1
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/defaults.env +3 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/__init__.py +1 -1
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/bundled_skill.md +25 -26
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/commands/flow.py +89 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/runtime_prepare.py +200 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/support_report.py +9 -8
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/conftest.py +11 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_regression_index.py +10 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_runtime_prepare.py +244 -1
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_skill_portability.py +1 -1
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/.gitignore +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/authorization.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/backend/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/backend/live.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/calendar/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/calendar/filter.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/calendar/google.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/calendar/oauth_listener.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/calendar/scope.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/circuit_breaker.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/config.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/discovery.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/headless.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/http_deadlines.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/http_retry.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/http_trace.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/merchant_pacing.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/models/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/models/platform.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/models/wire.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/namespace.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/observability.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/platform_profile.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/pop.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/profile_cache.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/registry_url.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/rest_client.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/secure_file.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/settings.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/signals.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/step_timing.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/ucp_capabilities.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/ucp_checkout.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/ucp_negotiation.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/ucp_profile.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent/usp_methods.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/activity_log.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/booking_poll.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/booking_summary.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/calendar_connection.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/calendar_event.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/calendar_preference.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/catalog.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/checkout_prereq.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/commands/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/commands/config.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/commands/lowlevel.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/commands/runtime.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/commands/update.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/config_loader.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/engine.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/flow_timings.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/geo_eligibility.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/geocode.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/identity.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/install_integrity.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/link_cli.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/main.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/output.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/payment_helpers.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/payment_token.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/provenance.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/reschedule.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/runtime.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/search_area.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/session.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/skill_bundle.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/skill_integrity.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/support_bundle_schema.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/timezone_context.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/updates.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/.env.tests +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/e2e/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/e2e/test_cancel_reschedule_e2e.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/e2e/test_latency_soak.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/e2e/test_staging_registry_integration.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/e2e/test_timezone_booking_e2e.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/e2e/test_ucp_native_e2e.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/e2e/test_ucp_skill_flow_e2e.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/e2e/ucp_native.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_activity_log.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_at_rest_hygiene.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_booking_rejection.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_bulk_cancel.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_calendar_connection.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_calendar_connector.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_calendar_flow.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_calendar_preference.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_checkout_signals.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_circuit_breaker.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_config.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_correlation_flow.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_declined_spt.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_discover_registry_rejection.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_flow_evidence.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_flow_gates.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_flow_reschedule.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_flow_step_timings.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_google_calendar_oauth.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_home_visit_address.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_http_trace.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_identity_logout.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_install_integrity.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_lazy_oauth.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_link_auth.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_link_cli_spt.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_namespace.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_oauth_listener.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_oauth_metadata.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_platform_key_pop.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_platform_profile.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_pop_keystore.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_profile_cache.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_release_hold_uses_delete.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_request_retry_semantics.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_search_area.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_session_compat.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_session_retention.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_skill_integrity.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_support_report.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_timezone_context.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_ucp_cancel_result.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_ucp_checkout_pop.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_ucp_checkout_proof_wireup.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_ucp_checkout_validate.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_ucp_profile.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_update_command.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_wheel_contents.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/SOURCE +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/acp_booking_extension.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/availability.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/booking.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/calendar_freebusy.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/catalog.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/paid_bookings.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/profile.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/registry.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/rest_common.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/usp.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/waitlist.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/spec_schemas/webhook_event.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/fixtures/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/fixtures/profiles.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/fixtures/provenance/pypi_attestations-0.0.30-py3-none-any.whl.provenance.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/fixtures/services.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/mock/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/mock/backend.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/mock/link_cli.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/mock/mock_calendar.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/mock/pop_business.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/mock/pop_transport.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/mock/ucp_checkout_store.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/mock/ucp_server.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/offline_stack/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/support/offline_stack/stack.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/__init__.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_authorization_policy.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_booking_poll_credentials.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_booking_request_error.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_calendar_filter.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_catalog.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_check_version.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_conditional_writes.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_delivery_address.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_discovery.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_geo_eligibility.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_geocode.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_headless.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_http_deadlines.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_http_retry.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_live_search_registry.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_merchant_pacing.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_mock_support.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_money_path_events.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_nonce_from_pop_error_body.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_observability.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_outbound_member_names.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_package_layering.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_provenance.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_registry_search_filters.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_registry_search_pricing.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_registry_search_responses.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_registry_url.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_release_notes.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_rest_client.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_scoped_method_authorization.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_session_credentials.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_spec_conformance.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_step_timing.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_ucp_capabilities.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_ucp_complete_outcome.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_ucp_create_price_guard.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_ucp_negotiation.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_updates.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/unit/test_wire_model_conformance.py +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/001-jcs-nested-buyer.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/002-jcs-meta-only.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/003-jcs-unicode-empty-meta.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/004-proof-issuance-no-ath.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/005-proof-presentation-ath-nonce.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/006-cross-key-replay.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/007-downgrade-cnf-without-proof.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/008-replayed-jti.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/009-aud-mismatch.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/010-alg-none.json +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/vectors/pop/README.md +0 -0
- {booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/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.
|
|
3
|
+
Version: 0.7.0
|
|
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.
|
|
81
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.55.0/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.
|
|
39
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.55.0/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
|
```
|
|
@@ -17,6 +17,9 @@ BOOKING_AGENT_SESSION_DIR=~/.booking-agent
|
|
|
17
17
|
# Absolute folder a host keeps between runs. When set, booking-agent links its state and the
|
|
18
18
|
# Link sign-in into it by itself before every command.
|
|
19
19
|
# BOOKING_AGENT_PERSISTENT_ROOT=
|
|
20
|
+
# Set to `none` by a host that keeps this runtime's files between conversations itself, so
|
|
21
|
+
# `flow init` does not ask the agent how state is kept.
|
|
22
|
+
# BOOKING_AGENT_PERSISTENCE=
|
|
20
23
|
BOOKING_AGENT_MAX_SESSIONS=30
|
|
21
24
|
LINK_CLI_BIN=link-cli
|
|
22
25
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 0.
|
|
2
|
+
version: 0.55.0
|
|
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.
|
|
22
|
+
package: "booking-agent-cli==0.7.0"
|
|
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.
|
|
223
|
+
curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.55.0/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.
|
|
248
|
+
uvx pip download "booking-agent-cli==0.7.0" --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.
|
|
251
|
-
uv tool install --force "$dir/booking_agent_cli-0.
|
|
250
|
+
"$dir/booking_agent_cli-0.7.0-py3-none-any.whl"
|
|
251
|
+
uv tool install --force "$dir/booking_agent_cli-0.7.0-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.
|
|
258
|
-
`pipx install booking-agent-cli==0.
|
|
257
|
+
available, install the same pinned release with `uv tool install booking-agent-cli==0.7.0` or
|
|
258
|
+
`pipx install booking-agent-cli==0.7.0`; 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
|
|
@@ -275,22 +275,21 @@ npm install -g @stripe/link-cli
|
|
|
275
275
|
|
|
276
276
|
### After installing
|
|
277
277
|
|
|
278
|
-
**Keep state between conversations.**
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
fails, continue the booking without it.
|
|
278
|
+
**Keep state between conversations.** `flow init` asks how this runtime keeps its files and refuses
|
|
279
|
+
(`persistence_unresolved`) until you answer. Look at what the runtime offers. If it gives you a
|
|
280
|
+
folder that survives between conversations, re-run the same init with `--persistent-root <absolute
|
|
281
|
+
path of that folder>`: the CLI links saved bookings and the Link sign-in into it, merging what each
|
|
282
|
+
folder holds (booking files by session, the newer copy of anything else) and deleting nothing. If
|
|
283
|
+
the runtime keeps its files anyway, or gives no such folder, re-run it with `--no-persistent-root`.
|
|
284
|
+
If it answers `persistence_folder_not_durable` the folder failed a basic check (a temporary or
|
|
285
|
+
memory-backed place, or not writable): name the folder the runtime really keeps, or use
|
|
286
|
+
`--no-persistent-root`. It sends you back at most twice and then uses the folder as given.
|
|
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
|
|
289
|
+
that sets `BOOKING_AGENT_PERSISTENT_ROOT` or `BOOKING_AGENT_PERSISTENCE=none` is never asked. If the
|
|
290
|
+
user is asked to sign in or reconnect right after a restart, run `booking-agent runtime doctor` and
|
|
291
|
+
follow its `next_command`. For extra cache folders the runtime asks you to keep, run `booking-agent
|
|
292
|
+
runtime prepare --persistent-root <folder> --link <runtime folder>=<persistent folder>`.
|
|
294
293
|
|
|
295
294
|
**Sign the user in to Link**, as described in [Link sign-in](#link-sign-in-automatic). It works
|
|
296
295
|
with no booking in progress. Do not wait for the user to offer to sign in.
|
|
@@ -309,7 +308,7 @@ prints each effective setting and where it came from; `booking-agent config init
|
|
|
309
308
|
|
|
310
309
|
The CLI decides whether a newer version is worth mentioning; you never decide from version
|
|
311
310
|
numbers or release pages. Pass this skill's version on every `flow init` (`--skill-version
|
|
312
|
-
0.
|
|
311
|
+
0.55.0`, from the frontmatter above), so the CLI can tell when these instructions and the
|
|
313
312
|
installed CLI no longer match.
|
|
314
313
|
|
|
315
314
|
- When a command returns `awaiting_user: update_consent`, ask the question in `prompt` in plain
|
|
@@ -471,7 +470,7 @@ confirmation email or on the business's page (`booking_url`). Do not invent a lo
|
|
|
471
470
|
**Step 1b — Create a new session:**
|
|
472
471
|
|
|
473
472
|
```bash
|
|
474
|
-
booking-agent flow init --user-request "<user's exact request>" --skill-version 0.
|
|
473
|
+
booking-agent flow init --user-request "<user's exact request>" --skill-version 0.55.0 \
|
|
475
474
|
--skill-path "<absolute path of the SKILL.md you loaded>"
|
|
476
475
|
```
|
|
477
476
|
|
|
@@ -399,8 +399,17 @@ def init(
|
|
|
399
399
|
capability: Optional[List[str]] = typer.Option(
|
|
400
400
|
None, "--capability", help="A capability the user's request needs; repeatable.",
|
|
401
401
|
),
|
|
402
|
+
persistent_root: Optional[str] = typer.Option(
|
|
403
|
+
None, "--persistent-root",
|
|
404
|
+
help="Absolute folder this runtime keeps between conversations; state is linked into it.",
|
|
405
|
+
),
|
|
406
|
+
no_persistent_root: bool = typer.Option(
|
|
407
|
+
False, "--no-persistent-root",
|
|
408
|
+
help="This runtime keeps its files between conversations anyway, or has no such folder.",
|
|
409
|
+
),
|
|
402
410
|
) -> None:
|
|
403
411
|
"""Initialize a new booking session."""
|
|
412
|
+
_resolve_persistence(persistent_root, no_persistent_root)
|
|
404
413
|
store = load_store()
|
|
405
414
|
# Before the session checks: a skill the agent points us at is checked whatever the
|
|
406
415
|
# flow state, so a changed file is never masked by session_active.
|
|
@@ -613,6 +622,86 @@ def _upcoming_duplicate_bookings(store: Any, user_request: str) -> List[Dict[str
|
|
|
613
622
|
return matches
|
|
614
623
|
|
|
615
624
|
|
|
625
|
+
def _resolve_persistence(persistent_root: Optional[str], no_persistent_root: bool) -> None:
|
|
626
|
+
"""Make the agent say how this runtime keeps saved state, before any state is written.
|
|
627
|
+
|
|
628
|
+
Skipping the setup used to be silent: nothing failed until the next restart lost the
|
|
629
|
+
booking, the calendar link, and the sign-in. The agent, not the CLI, can see what the runtime
|
|
630
|
+
offers, so it must answer with a folder or an explicit "no folder"; the CLI never guesses one.
|
|
631
|
+
"""
|
|
632
|
+
from ..runtime_prepare import (
|
|
633
|
+
MAX_CORRECTIONS,
|
|
634
|
+
PrepareError,
|
|
635
|
+
acknowledge_no_persistence,
|
|
636
|
+
clear_attempts,
|
|
637
|
+
corrections_so_far,
|
|
638
|
+
durability_problem,
|
|
639
|
+
persistence_resolution,
|
|
640
|
+
prepare,
|
|
641
|
+
record_accepted_despite,
|
|
642
|
+
record_correction,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
if persistent_root and no_persistent_root:
|
|
646
|
+
emit_error(
|
|
647
|
+
"INIT",
|
|
648
|
+
code="persistence_flags_conflict",
|
|
649
|
+
message="Pass either --persistent-root or --no-persistent-root, not both.",
|
|
650
|
+
)
|
|
651
|
+
raise typer.Exit(code=1)
|
|
652
|
+
if persistent_root:
|
|
653
|
+
problem = durability_problem(persistent_root)
|
|
654
|
+
if problem and corrections_so_far() < MAX_CORRECTIONS:
|
|
655
|
+
used = record_correction()
|
|
656
|
+
emit_error(
|
|
657
|
+
"INIT",
|
|
658
|
+
code="persistence_folder_not_durable",
|
|
659
|
+
message=(
|
|
660
|
+
f"That folder cannot be relied on to survive a restart: {problem}. Nothing "
|
|
661
|
+
"was moved. Re-run the same init command with the folder this runtime "
|
|
662
|
+
"really keeps between conversations, or with --no-persistent-root if it keeps "
|
|
663
|
+
f"its files anyway. (Correction {used} of {MAX_CORRECTIONS}; after that the "
|
|
664
|
+
"folder you name is used as given so the booking is never stuck.)"
|
|
665
|
+
),
|
|
666
|
+
)
|
|
667
|
+
raise typer.Exit(code=1)
|
|
668
|
+
if problem:
|
|
669
|
+
record_accepted_despite(problem)
|
|
670
|
+
else:
|
|
671
|
+
clear_attempts()
|
|
672
|
+
try:
|
|
673
|
+
prepare(persistent_root, [])
|
|
674
|
+
except (PrepareError, OSError) as exc:
|
|
675
|
+
emit_error(
|
|
676
|
+
"INIT",
|
|
677
|
+
code="persistence_setup_failed",
|
|
678
|
+
message=(
|
|
679
|
+
f"Saved state could not be linked into that folder ({exc}). Re-run the same "
|
|
680
|
+
"init command with the folder this runtime really keeps between "
|
|
681
|
+
"conversations, or with --no-persistent-root to go on without it."
|
|
682
|
+
),
|
|
683
|
+
)
|
|
684
|
+
raise typer.Exit(code=1)
|
|
685
|
+
return
|
|
686
|
+
if no_persistent_root:
|
|
687
|
+
acknowledge_no_persistence()
|
|
688
|
+
return
|
|
689
|
+
if persistence_resolution() is None:
|
|
690
|
+
emit_error(
|
|
691
|
+
"INIT",
|
|
692
|
+
code="persistence_unresolved",
|
|
693
|
+
message=(
|
|
694
|
+
"Saved bookings and the Link sign-in are not in a folder this runtime keeps "
|
|
695
|
+
"between conversations, so a restart would lose them. Look at what this runtime "
|
|
696
|
+
"offers. If it provides a folder that survives restarts, re-run this same init "
|
|
697
|
+
"command with --persistent-root <absolute path of that folder>. If its files are "
|
|
698
|
+
"kept anyway, or it offers no such folder, re-run it with --no-persistent-root. "
|
|
699
|
+
"Decide from the runtime, never ask the user, and never guess a path."
|
|
700
|
+
),
|
|
701
|
+
)
|
|
702
|
+
raise typer.Exit(code=1)
|
|
703
|
+
|
|
704
|
+
|
|
616
705
|
def _unfinished_similar_sessions(store: Any, user_request: str) -> List[Dict[str, Any]]:
|
|
617
706
|
"""Unfinished sessions whose request covers most of this one's key terms, newest first."""
|
|
618
707
|
wanted = _request_terms(user_request)
|
{booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/src/booking_agent_cli/runtime_prepare.py
RENAMED
|
@@ -14,6 +14,7 @@ import json
|
|
|
14
14
|
import os
|
|
15
15
|
import shutil
|
|
16
16
|
import sys
|
|
17
|
+
import tempfile
|
|
17
18
|
import time
|
|
18
19
|
from pathlib import Path
|
|
19
20
|
from typing import Dict, List, Optional, Tuple
|
|
@@ -25,6 +26,16 @@ STATE_SUBDIR = "booking-agent"
|
|
|
25
26
|
LINK_SUBDIR = "link-cli"
|
|
26
27
|
_LINK_CLI_APP = "link-cli-nodejs"
|
|
27
28
|
ROOT_ENV = "BOOKING_AGENT_PERSISTENT_ROOT"
|
|
29
|
+
# A host that keeps this runtime's files itself declares so with ``BOOKING_AGENT_PERSISTENCE=none``.
|
|
30
|
+
PERSISTENCE_ENV = "BOOKING_AGENT_PERSISTENCE"
|
|
31
|
+
_ACK_FILE = ".persistence-none"
|
|
32
|
+
_ATTEMPTS_FILE = ".persistence-attempts"
|
|
33
|
+
_BOOT_FILE = ".booking-agent-boot"
|
|
34
|
+
# How many times the CLI sends the agent back after a folder fails the checks. The next one is
|
|
35
|
+
# accepted, so a stubborn agent can never leave a booking stuck on this question.
|
|
36
|
+
MAX_CORRECTIONS = 2
|
|
37
|
+
_TEMP_ROOTS = ("/tmp", "/var/tmp", "/dev/shm", "/run")
|
|
38
|
+
_MEMORY_FILESYSTEMS = frozenset({"tmpfs", "ramfs", "devtmpfs"})
|
|
28
39
|
_SESSION_FILE = ".usp-session.json"
|
|
29
40
|
# Order a booking moves through, used to keep the further-along copy of the same session.
|
|
30
41
|
_PHASE_ORDER = (
|
|
@@ -311,6 +322,7 @@ def prepare(persistent_root: str, raw_links: List[str]) -> List[Dict[str, str]]:
|
|
|
311
322
|
results.append(link_one(runtime, persistent, private=private))
|
|
312
323
|
if lifted and results:
|
|
313
324
|
results[0].update(lifted)
|
|
325
|
+
touch_boot_marker(root)
|
|
314
326
|
return results
|
|
315
327
|
|
|
316
328
|
|
|
@@ -365,7 +377,21 @@ def diagnose() -> Dict[str, object]:
|
|
|
365
377
|
"kind": "configured_root_not_applied", "path": str(state), "severity": "problem",
|
|
366
378
|
})
|
|
367
379
|
root_hint = root_hint or configured
|
|
380
|
+
persistence: Dict[str, object] = {"folder": root_hint or None, "survived_restart": None}
|
|
381
|
+
if root_hint:
|
|
382
|
+
persistence["survived_restart"] = survived_restart(Path(root_hint))
|
|
383
|
+
if persistence["survived_restart"] is False:
|
|
384
|
+
findings.append({
|
|
385
|
+
"kind": "folder_not_yet_shown_to_survive_a_restart",
|
|
386
|
+
"path": root_hint, "severity": "info",
|
|
387
|
+
})
|
|
388
|
+
despite = _read_attempts().get("accepted_despite")
|
|
389
|
+
if despite:
|
|
390
|
+
findings.append({
|
|
391
|
+
"kind": "folder_accepted_despite_checks", "path": str(despite), "severity": "info",
|
|
392
|
+
})
|
|
368
393
|
return {
|
|
394
|
+
"persistence": persistence,
|
|
369
395
|
"configured_root": configured,
|
|
370
396
|
"state": state_info,
|
|
371
397
|
"link_sign_in": link_info,
|
|
@@ -396,3 +422,177 @@ def auto_prepare() -> Optional[List[Dict[str, str]]]:
|
|
|
396
422
|
return prepare(root, [])
|
|
397
423
|
except (PrepareError, OSError):
|
|
398
424
|
return None
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def persistence_resolution() -> Optional[str]:
|
|
428
|
+
"""Why ``flow init`` may go on without asking how this runtime keeps state, or None.
|
|
429
|
+
|
|
430
|
+
``linked``: saved state already lives in a folder the CLI linked it into.
|
|
431
|
+
``host_declared``: the host set ``BOOKING_AGENT_PERSISTENCE=none``.
|
|
432
|
+
``acknowledged``: an agent already said this runtime keeps its files (kept beside the state,
|
|
433
|
+
so a fresh runtime that lost the state is asked again).
|
|
434
|
+
"""
|
|
435
|
+
home = settings.booking_agent_home()
|
|
436
|
+
if home.is_symlink():
|
|
437
|
+
return "linked"
|
|
438
|
+
if (settings.get(PERSISTENCE_ENV) or "").strip().lower() == "none":
|
|
439
|
+
return "host_declared"
|
|
440
|
+
if (home / _ACK_FILE).is_file():
|
|
441
|
+
return "acknowledged"
|
|
442
|
+
return None
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def acknowledge_no_persistence() -> None:
|
|
446
|
+
home = ensure_private_dir(settings.booking_agent_home())
|
|
447
|
+
write_private_text(home / _ACK_FILE, time.strftime("%Y-%m-%d\n", time.gmtime()))
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
# ---------------------------------------------------------------------------
|
|
451
|
+
# Checks on the folder an agent names, and the record of how often it was sent back
|
|
452
|
+
# ---------------------------------------------------------------------------
|
|
453
|
+
|
|
454
|
+
def ephemeral_roots() -> List[Path]:
|
|
455
|
+
"""Folders whose contents are not kept between runs, resolved."""
|
|
456
|
+
roots = [Path(p) for p in _TEMP_ROOTS] + [Path(tempfile.gettempdir())]
|
|
457
|
+
resolved = []
|
|
458
|
+
for root in roots:
|
|
459
|
+
try:
|
|
460
|
+
resolved.append(root.resolve())
|
|
461
|
+
except OSError:
|
|
462
|
+
continue
|
|
463
|
+
return resolved
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def fs_type(path: Path) -> Optional[str]:
|
|
467
|
+
"""Filesystem type holding ``path`` (Linux only; None when it cannot be told)."""
|
|
468
|
+
try:
|
|
469
|
+
target = path.resolve()
|
|
470
|
+
best, best_type = "", None
|
|
471
|
+
with open("/proc/self/mountinfo", encoding="utf-8") as f:
|
|
472
|
+
for line in f:
|
|
473
|
+
left, _, right = line.partition(" - ")
|
|
474
|
+
fields = left.split()
|
|
475
|
+
if len(fields) < 5 or not right:
|
|
476
|
+
continue
|
|
477
|
+
mount = fields[4].replace("\\040", " ")
|
|
478
|
+
if (str(target) == mount or str(target).startswith(mount.rstrip("/") + "/")) and len(mount) > len(best):
|
|
479
|
+
best, best_type = mount, right.split()[0]
|
|
480
|
+
return best_type
|
|
481
|
+
except OSError:
|
|
482
|
+
return None
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
def durability_problem(raw: str) -> Optional[str]:
|
|
486
|
+
"""Why the folder an agent named cannot be a place that survives restarts, or None.
|
|
487
|
+
|
|
488
|
+
Only what can be known now: a path that cannot work, or one the OS itself says is not kept.
|
|
489
|
+
A folder on ordinary disk that the host simply never restores cannot be told apart here.
|
|
490
|
+
"""
|
|
491
|
+
try:
|
|
492
|
+
path = _absolute(raw, what="The folder")
|
|
493
|
+
except PrepareError as exc:
|
|
494
|
+
return str(exc)
|
|
495
|
+
try:
|
|
496
|
+
resolved = path.resolve()
|
|
497
|
+
except OSError:
|
|
498
|
+
return "the path cannot be resolved"
|
|
499
|
+
if resolved == Path(resolved.anchor):
|
|
500
|
+
return "it is the filesystem root"
|
|
501
|
+
for root in ephemeral_roots():
|
|
502
|
+
if resolved == root or root in resolved.parents:
|
|
503
|
+
return f"it is under {root}, which is cleared between runs"
|
|
504
|
+
kind = fs_type(resolved)
|
|
505
|
+
if kind in _MEMORY_FILESYSTEMS:
|
|
506
|
+
return f"it is on {kind}, which lives in memory"
|
|
507
|
+
ancestor = resolved
|
|
508
|
+
while not ancestor.exists() and ancestor != ancestor.parent:
|
|
509
|
+
ancestor = ancestor.parent
|
|
510
|
+
if not ancestor.is_dir():
|
|
511
|
+
return f"{ancestor} is a file, not a folder"
|
|
512
|
+
if not os.access(ancestor, os.W_OK | os.X_OK):
|
|
513
|
+
return f"{ancestor} is not writable"
|
|
514
|
+
return None
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _attempts_path() -> Path:
|
|
518
|
+
return settings.booking_agent_home() / _ATTEMPTS_FILE
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _read_attempts() -> Dict[str, object]:
|
|
522
|
+
try:
|
|
523
|
+
raw = json.loads(_attempts_path().read_text())
|
|
524
|
+
return raw if isinstance(raw, dict) else {}
|
|
525
|
+
except (OSError, ValueError):
|
|
526
|
+
return {}
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def corrections_so_far() -> int:
|
|
530
|
+
value = _read_attempts().get("rejected")
|
|
531
|
+
return value if isinstance(value, int) else 0
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def record_correction() -> int:
|
|
535
|
+
"""Count one more time the agent was sent back; returns the new count."""
|
|
536
|
+
n = corrections_so_far() + 1
|
|
537
|
+
ensure_private_dir(settings.booking_agent_home())
|
|
538
|
+
write_private_text(_attempts_path(), json.dumps({"rejected": n}))
|
|
539
|
+
return n
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def record_accepted_despite(problem: str) -> None:
|
|
543
|
+
ensure_private_dir(settings.booking_agent_home())
|
|
544
|
+
# The count starts over: this folder was used as given, and a later bad folder is a new
|
|
545
|
+
# decision that gets its own corrections instead of being waved through.
|
|
546
|
+
write_private_text(
|
|
547
|
+
_attempts_path(),
|
|
548
|
+
json.dumps({"rejected": 0, "accepted_despite": problem}),
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def clear_attempts() -> None:
|
|
553
|
+
try:
|
|
554
|
+
_attempts_path().unlink()
|
|
555
|
+
except OSError:
|
|
556
|
+
pass
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def current_boot_id() -> Optional[str]:
|
|
560
|
+
try:
|
|
561
|
+
return Path("/proc/sys/kernel/random/boot_id").read_text().strip() or None
|
|
562
|
+
except OSError:
|
|
563
|
+
return None
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def touch_boot_marker(root: Path) -> None:
|
|
567
|
+
"""Remember which boot first used this folder, so a later boot proves it survived a restart."""
|
|
568
|
+
boot = current_boot_id()
|
|
569
|
+
if boot is None:
|
|
570
|
+
return
|
|
571
|
+
marker = root / _BOOT_FILE
|
|
572
|
+
try:
|
|
573
|
+
previous = json.loads(marker.read_text())
|
|
574
|
+
except (OSError, ValueError):
|
|
575
|
+
previous = {}
|
|
576
|
+
first = previous.get("first") or boot
|
|
577
|
+
data = {"first": first, "last": boot, "survived": bool(previous.get("survived")) or first != boot}
|
|
578
|
+
try:
|
|
579
|
+
write_private_text(marker, json.dumps(data))
|
|
580
|
+
except OSError:
|
|
581
|
+
pass
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def survived_restart(root: Path) -> Optional[bool]:
|
|
585
|
+
"""True once this folder has been seen across two boots; None when boots cannot be told.
|
|
586
|
+
|
|
587
|
+
Never a false "yes": the marker lives in the folder, so seeing it again means the folder
|
|
588
|
+
came back. It can stay False after a real restart when sandboxes share one kernel (many
|
|
589
|
+
containers report the host's boot id), so False means "not shown", not "lost".
|
|
590
|
+
"""
|
|
591
|
+
try:
|
|
592
|
+
previous = json.loads((root / _BOOT_FILE).read_text())
|
|
593
|
+
except (OSError, ValueError):
|
|
594
|
+
return None
|
|
595
|
+
boot = current_boot_id()
|
|
596
|
+
if boot is None:
|
|
597
|
+
return None
|
|
598
|
+
return bool(previous.get("survived")) or previous.get("first") != boot
|
|
@@ -109,14 +109,15 @@ SKILL_PROSE_CODES = frozenset({
|
|
|
109
109
|
"discover_radius_required", "link_address_not_found", "missing_network_id_for_spt",
|
|
110
110
|
"payment_already_captured", "payment_authentication_unsupported",
|
|
111
111
|
"payment_captured_booking_unconfirmed", "payment_declined", "payment_failed",
|
|
112
|
-
"payment_not_processed", "payment_unrecoverable", "
|
|
113
|
-
"
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"
|
|
119
|
-
"
|
|
112
|
+
"payment_not_processed", "payment_unrecoverable", "persistence_flags_conflict",
|
|
113
|
+
"persistence_folder_not_durable", "persistence_setup_failed", "persistence_unresolved",
|
|
114
|
+
"reschedule_at_business", "reschedule_booking_not_active", "reschedule_failed",
|
|
115
|
+
"reschedule_fee_unsupported", "reschedule_price_changed", "reschedule_price_unknown",
|
|
116
|
+
"reschedule_same_slot", "reschedule_unauthorized", "search_area_input_invalid",
|
|
117
|
+
"search_area_not_user_confirmed", "search_area_unconfirmed", "service_address_unconfirmed",
|
|
118
|
+
"service_details_changed", "session_active", "session_not_found", "slot_not_selected",
|
|
119
|
+
"spend_request_not_approved", "spend_request_replacement_required",
|
|
120
|
+
"timezone_confirmation_required", "ucp_complete_failed",
|
|
120
121
|
})
|
|
121
122
|
EXPECTED_CODES = SKILL_TABLE_CODES | SKILL_PROSE_CODES
|
|
122
123
|
# The business is still working; seeing these again is progress, not a failed step.
|
|
@@ -13,6 +13,17 @@ def _reset_paid_bookings():
|
|
|
13
13
|
reset_paid()
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
@pytest.fixture(autouse=True)
|
|
17
|
+
def _host_keeps_files(monkeypatch):
|
|
18
|
+
"""Tests are a host that keeps its own files; the init gate has its own tests."""
|
|
19
|
+
monkeypatch.setenv("BOOKING_AGENT_PERSISTENCE", "none")
|
|
20
|
+
from booking_agent_cli import runtime_prepare
|
|
21
|
+
|
|
22
|
+
# Test folders live under the system temp dir; the durability checks have their own tests.
|
|
23
|
+
monkeypatch.setattr(runtime_prepare, "ephemeral_roots", lambda: [])
|
|
24
|
+
monkeypatch.setattr(runtime_prepare, "fs_type", lambda _path: None)
|
|
25
|
+
|
|
26
|
+
|
|
16
27
|
@pytest.fixture
|
|
17
28
|
def runner() -> CliRunner:
|
|
18
29
|
return CliRunner()
|
{booking_agent_cli-0.6.0 → booking_agent_cli-0.7.0}/tests/integration/test_regression_index.py
RENAMED
|
@@ -22,6 +22,16 @@ REGRESSIONS = {
|
|
|
22
22
|
("test_runtime_prepare.py", "test_the_recorded_hosted_layout_is_repaired_without_losing_the_real_booking"),
|
|
23
23
|
("test_runtime_prepare.py", "test_the_further_along_copy_of_a_session_wins_and_no_booking_is_lost"),
|
|
24
24
|
],
|
|
25
|
+
"setting up persistence could be skipped without any error": [
|
|
26
|
+
("test_runtime_prepare.py", "test_init_refuses_until_the_agent_says_how_state_is_kept"),
|
|
27
|
+
("test_runtime_prepare.py", "test_a_folder_the_agent_names_is_set_up_and_then_not_asked_again"),
|
|
28
|
+
("test_runtime_prepare.py", "test_saying_there_is_no_folder_is_remembered_beside_the_state"),
|
|
29
|
+
],
|
|
30
|
+
"an agent named a folder that cannot survive, or insisted on one and got the flow stuck": [
|
|
31
|
+
("test_runtime_prepare.py", "test_a_temporary_folder_is_turned_back_and_nothing_moves"),
|
|
32
|
+
("test_runtime_prepare.py", "test_the_agent_is_sent_back_twice_and_the_third_time_is_accepted"),
|
|
33
|
+
("test_runtime_prepare.py", "test_a_later_boot_shows_the_folder_survived_a_restart"),
|
|
34
|
+
],
|
|
25
35
|
"assistant had to remember to set up persistence": [
|
|
26
36
|
("test_runtime_prepare.py", "test_the_named_persistent_folder_is_prepared_before_any_command"),
|
|
27
37
|
],
|