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.
Files changed (231) hide show
  1. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/PKG-INFO +2 -2
  2. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/README.md +1 -1
  3. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/pyproject.toml +1 -1
  4. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/__init__.py +1 -1
  5. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/defaults.env +3 -0
  6. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/__init__.py +1 -1
  7. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/booking_poll.py +8 -0
  8. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/booking_summary.py +2 -0
  9. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/bundled_skill.md +22 -22
  10. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/calendar_preference.py +7 -2
  11. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/flow.py +206 -14
  12. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/payment_helpers.py +25 -2
  13. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/reschedule.py +2 -0
  14. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/runtime_prepare.py +76 -1
  15. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/session.py +7 -0
  16. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/support_report.py +24 -0
  17. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/conftest.py +2 -0
  18. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_ucp_skill_flow_e2e.py +104 -0
  19. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_flow_reschedule.py +16 -0
  20. booking_agent_cli-0.9.1/tests/integration/test_resume_after_restart.py +240 -0
  21. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_cancel_result.py +83 -24
  22. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/ucp_checkout_store.py +7 -1
  23. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/.gitignore +0 -0
  24. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/authorization.py +0 -0
  25. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/backend/__init__.py +0 -0
  26. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/backend/live.py +0 -0
  27. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/__init__.py +0 -0
  28. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/filter.py +0 -0
  29. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/google.py +0 -0
  30. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/oauth_listener.py +0 -0
  31. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/calendar/scope.py +0 -0
  32. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/circuit_breaker.py +0 -0
  33. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/config.py +0 -0
  34. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/discovery.py +0 -0
  35. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/faults.py +0 -0
  36. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/headless.py +0 -0
  37. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/http_deadlines.py +0 -0
  38. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/http_retry.py +0 -0
  39. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/http_trace.py +0 -0
  40. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/merchant_pacing.py +0 -0
  41. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/models/__init__.py +0 -0
  42. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/models/platform.py +0 -0
  43. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/models/wire.py +0 -0
  44. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/namespace.py +0 -0
  45. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/observability.py +0 -0
  46. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/platform_profile.py +0 -0
  47. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/pop.py +0 -0
  48. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/profile_cache.py +0 -0
  49. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/registry_url.py +0 -0
  50. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/rest_client.py +0 -0
  51. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/secure_file.py +0 -0
  52. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/settings.py +0 -0
  53. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/signals.py +0 -0
  54. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/step_timing.py +0 -0
  55. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/ucp_capabilities.py +0 -0
  56. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/ucp_checkout.py +0 -0
  57. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/ucp_negotiation.py +0 -0
  58. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/ucp_profile.py +0 -0
  59. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent/usp_methods.py +0 -0
  60. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/activity_log.py +0 -0
  61. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/calendar_connection.py +0 -0
  62. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/calendar_event.py +0 -0
  63. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/catalog.py +0 -0
  64. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/checkout_prereq.py +0 -0
  65. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/__init__.py +0 -0
  66. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/config.py +0 -0
  67. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/lowlevel.py +0 -0
  68. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/runtime.py +0 -0
  69. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/commands/update.py +0 -0
  70. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/config_loader.py +0 -0
  71. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/engine.py +0 -0
  72. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/fault_report_schema.py +0 -0
  73. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/fault_reports.py +0 -0
  74. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/flow_timings.py +0 -0
  75. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/gates.py +0 -0
  76. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/geo_eligibility.py +0 -0
  77. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/geocode.py +0 -0
  78. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/identity.py +0 -0
  79. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/install_integrity.py +0 -0
  80. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/link_cli.py +0 -0
  81. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/main.py +0 -0
  82. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/output.py +0 -0
  83. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/payment_token.py +0 -0
  84. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/provenance.py +0 -0
  85. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/runtime.py +0 -0
  86. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/search_area.py +0 -0
  87. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/skill_bundle.py +0 -0
  88. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/skill_integrity.py +0 -0
  89. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/support_bundle_schema.py +0 -0
  90. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/timezone_context.py +0 -0
  91. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/src/booking_agent_cli/updates.py +0 -0
  92. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/.env.tests +0 -0
  93. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/__init__.py +0 -0
  94. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_cancel_reschedule_e2e.py +0 -0
  95. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_latency_soak.py +0 -0
  96. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_staging_registry_integration.py +0 -0
  97. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_timezone_booking_e2e.py +0 -0
  98. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/test_ucp_native_e2e.py +0 -0
  99. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/e2e/ucp_native.py +0 -0
  100. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/__init__.py +0 -0
  101. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_activity_log.py +0 -0
  102. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_at_rest_hygiene.py +0 -0
  103. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_booking_rejection.py +0 -0
  104. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_bulk_cancel.py +0 -0
  105. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_calendar_connection.py +0 -0
  106. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_calendar_connector.py +0 -0
  107. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_calendar_flow.py +0 -0
  108. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_calendar_preference.py +0 -0
  109. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_checkout_signals.py +0 -0
  110. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_circuit_breaker.py +0 -0
  111. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_cli_enforced_gates.py +0 -0
  112. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_config.py +0 -0
  113. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_correlation_flow.py +0 -0
  114. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_declined_spt.py +0 -0
  115. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_discover_registry_rejection.py +0 -0
  116. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_flow_evidence.py +0 -0
  117. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_flow_gates.py +0 -0
  118. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_flow_step_timings.py +0 -0
  119. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_google_calendar_oauth.py +0 -0
  120. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_home_visit_address.py +0 -0
  121. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_http_trace.py +0 -0
  122. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_identity_logout.py +0 -0
  123. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_install_integrity.py +0 -0
  124. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_lazy_oauth.py +0 -0
  125. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_link_auth.py +0 -0
  126. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_link_cli_spt.py +0 -0
  127. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_namespace.py +0 -0
  128. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_oauth_listener.py +0 -0
  129. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_oauth_metadata.py +0 -0
  130. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_platform_key_pop.py +0 -0
  131. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_platform_profile.py +0 -0
  132. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_pop_keystore.py +0 -0
  133. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_profile_cache.py +0 -0
  134. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_regression_index.py +0 -0
  135. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_release_hold_uses_delete.py +0 -0
  136. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_request_retry_semantics.py +0 -0
  137. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_runtime_prepare.py +0 -0
  138. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_search_area.py +0 -0
  139. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_session_compat.py +0 -0
  140. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_session_retention.py +0 -0
  141. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_skill_integrity.py +0 -0
  142. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_skill_portability.py +0 -0
  143. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_support_report.py +0 -0
  144. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_timezone_context.py +0 -0
  145. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_checkout_pop.py +0 -0
  146. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_checkout_proof_wireup.py +0 -0
  147. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_checkout_validate.py +0 -0
  148. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_ucp_profile.py +0 -0
  149. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_update_command.py +0 -0
  150. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/integration/test_wheel_contents.py +0 -0
  151. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/SOURCE +0 -0
  152. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/acp_booking_extension.json +0 -0
  153. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/availability.json +0 -0
  154. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/booking.json +0 -0
  155. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/calendar_freebusy.json +0 -0
  156. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/catalog.json +0 -0
  157. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/paid_bookings.json +0 -0
  158. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/profile.json +0 -0
  159. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/registry.json +0 -0
  160. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/rest_common.json +0 -0
  161. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/usp.json +0 -0
  162. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/waitlist.json +0 -0
  163. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/spec_schemas/webhook_event.json +0 -0
  164. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/__init__.py +0 -0
  165. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/fixtures/__init__.py +0 -0
  166. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/fixtures/profiles.py +0 -0
  167. {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
  168. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/fixtures/services.py +0 -0
  169. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/__init__.py +0 -0
  170. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/backend.py +0 -0
  171. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/link_cli.py +0 -0
  172. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/mock_calendar.py +0 -0
  173. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/pop_business.py +0 -0
  174. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/pop_transport.py +0 -0
  175. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/mock/ucp_server.py +0 -0
  176. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/offline_stack/__init__.py +0 -0
  177. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/support/offline_stack/stack.py +0 -0
  178. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/__init__.py +0 -0
  179. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_authorization_policy.py +0 -0
  180. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_booking_poll_credentials.py +0 -0
  181. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_booking_request_error.py +0 -0
  182. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_calendar_filter.py +0 -0
  183. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_catalog.py +0 -0
  184. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_check_version.py +0 -0
  185. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_conditional_writes.py +0 -0
  186. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_delivery_address.py +0 -0
  187. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_discovery.py +0 -0
  188. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_fault_reports.py +0 -0
  189. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_geo_eligibility.py +0 -0
  190. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_geocode.py +0 -0
  191. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_headless.py +0 -0
  192. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_http_deadlines.py +0 -0
  193. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_http_retry.py +0 -0
  194. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_live_search_registry.py +0 -0
  195. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_merchant_pacing.py +0 -0
  196. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_mock_support.py +0 -0
  197. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_money_path_events.py +0 -0
  198. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_nonce_from_pop_error_body.py +0 -0
  199. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_observability.py +0 -0
  200. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_outbound_member_names.py +0 -0
  201. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_package_layering.py +0 -0
  202. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_pay_at_service.py +0 -0
  203. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_provenance.py +0 -0
  204. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_registry_search_filters.py +0 -0
  205. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_registry_search_pricing.py +0 -0
  206. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_registry_search_responses.py +0 -0
  207. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_registry_url.py +0 -0
  208. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_release_notes.py +0 -0
  209. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_rest_client.py +0 -0
  210. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_scoped_method_authorization.py +0 -0
  211. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_session_credentials.py +0 -0
  212. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_spec_conformance.py +0 -0
  213. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_step_timing.py +0 -0
  214. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_ucp_capabilities.py +0 -0
  215. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_ucp_complete_outcome.py +0 -0
  216. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_ucp_create_price_guard.py +0 -0
  217. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_ucp_negotiation.py +0 -0
  218. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_updates.py +0 -0
  219. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/unit/test_wire_model_conformance.py +0 -0
  220. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/001-jcs-nested-buyer.json +0 -0
  221. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/002-jcs-meta-only.json +0 -0
  222. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/003-jcs-unicode-empty-meta.json +0 -0
  223. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/004-proof-issuance-no-ath.json +0 -0
  224. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/005-proof-presentation-ath-nonce.json +0 -0
  225. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/006-cross-key-replay.json +0 -0
  226. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/007-downgrade-cnf-without-proof.json +0 -0
  227. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/008-replayed-jti.json +0 -0
  228. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/009-aud-mismatch.json +0 -0
  229. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/010-alg-none.json +0 -0
  230. {booking_agent_cli-0.9.0 → booking_agent_cli-0.9.1}/tests/vectors/pop/README.md +0 -0
  231. {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.0
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.0/booking-agent/skills/booking-agent/SKILL.md
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.0/booking-agent/skills/booking-agent/SKILL.md
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
  ```
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "booking-agent-cli"
3
- version = "0.9.0"
3
+ version = "0.9.1"
4
4
  description = "The Booking Agent CLI: a stateful USP booking flow with hard gates, plus its USP protocol library."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -3,4 +3,4 @@
3
3
  Ships in the booking-agent-cli wheel and never imports ``booking_agent_cli``.
4
4
  """
5
5
 
6
- __version__ = "0.9.0"
6
+ __version__ = "0.9.1"
@@ -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
 
@@ -1,3 +1,3 @@
1
1
  """booking_agent_cli: typer-based stateful CLI for USP booking flows."""
2
2
 
3
- __version__ = "0.9.0"
3
+ __version__ = "0.9.1"
@@ -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")
@@ -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.0
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.0"
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.0/booking-agent/skills/booking-agent/SKILL.md
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.0" --no-deps --only-binary :all: -d "$dir"
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.0-py3-none-any.whl"
251
- uv tool install --force "$dir/booking_agent_cli-0.9.0-py3-none-any.whl"
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.0` or
258
- `pipx install booking-agent-cli==0.9.0`; the CLI checks its own files against the signed release
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`. 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
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.0`, from the frontmatter above), so the CLI can tell when these instructions and the
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.0 \
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**: run `booking-agent flow calendar skip --session-only` without asking the user
620
- about the calendar, then continue to availability.
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` (always use these for the
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 `book poll --wait` and report the result. Never create a payment and skip
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` (`pay
802
- ucp-complete`). If `merchant_quote.total_changed`, get approval and add `--total-confirmed`.
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
- Nothing needs configuring. `booking-agent config show` lists every setting and where it came from.
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.
@@ -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
- config_loader.load()
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 _payment_captured(session) or session.payment.order_id:
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 not None:
7613
- return (
7614
- SimpleNamespace(
7615
- id=booking_context["booking_id"],
7616
- status=BookingStatus(booking_context["booking_status"]),
7617
- ),
7618
- None,
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,
@@ -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
- order_id = validate_ucp_checkout_completed(result) if isinstance(result, dict) else ""
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