booking-agent-cli 0.10.0__tar.gz → 0.10.2__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.10.0 → booking_agent_cli-0.10.2}/PKG-INFO +2 -2
  2. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/README.md +1 -1
  3. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/pyproject.toml +1 -1
  4. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/__init__.py +1 -1
  5. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/__init__.py +1 -1
  6. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/bundled_skill.md +20 -22
  7. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/calendar_preference.py +16 -4
  8. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/flow.py +69 -35
  9. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/runtime_prepare.py +84 -1
  10. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_cli_enforced_gates.py +50 -0
  11. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_resume_after_restart.py +158 -0
  12. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/.gitignore +0 -0
  13. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/authorization.py +0 -0
  14. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/backend/__init__.py +0 -0
  15. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/backend/live.py +0 -0
  16. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/__init__.py +0 -0
  17. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/filter.py +0 -0
  18. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/google.py +0 -0
  19. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/oauth_listener.py +0 -0
  20. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/scope.py +0 -0
  21. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/circuit_breaker.py +0 -0
  22. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/config.py +0 -0
  23. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/defaults.env +0 -0
  24. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/discovery.py +0 -0
  25. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/faults.py +0 -0
  26. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/headless.py +0 -0
  27. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/http_deadlines.py +0 -0
  28. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/http_retry.py +0 -0
  29. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/http_trace.py +0 -0
  30. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/merchant_pacing.py +0 -0
  31. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/models/__init__.py +0 -0
  32. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/models/platform.py +0 -0
  33. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/models/wire.py +0 -0
  34. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/namespace.py +0 -0
  35. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/observability.py +0 -0
  36. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/platform_profile.py +0 -0
  37. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/pop.py +0 -0
  38. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/profile_cache.py +0 -0
  39. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/registry_url.py +0 -0
  40. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/rest_client.py +0 -0
  41. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/secure_file.py +0 -0
  42. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/settings.py +0 -0
  43. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/signals.py +0 -0
  44. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/step_timing.py +0 -0
  45. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/ucp_capabilities.py +0 -0
  46. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/ucp_checkout.py +0 -0
  47. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/ucp_negotiation.py +0 -0
  48. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/ucp_profile.py +0 -0
  49. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent/usp_methods.py +0 -0
  50. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/activity_log.py +0 -0
  51. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/booking_poll.py +0 -0
  52. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/booking_summary.py +0 -0
  53. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/calendar_connection.py +0 -0
  54. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/calendar_event.py +0 -0
  55. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/catalog.py +0 -0
  56. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/checkout_prereq.py +0 -0
  57. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/__init__.py +0 -0
  58. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/config.py +0 -0
  59. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/lowlevel.py +0 -0
  60. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/runtime.py +0 -0
  61. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/update.py +0 -0
  62. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/config_loader.py +0 -0
  63. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/engine.py +0 -0
  64. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/fault_report_schema.py +0 -0
  65. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/fault_reports.py +0 -0
  66. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/flow_timings.py +0 -0
  67. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/gates.py +0 -0
  68. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/geo_eligibility.py +0 -0
  69. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/geocode.py +0 -0
  70. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/identity.py +0 -0
  71. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/install_integrity.py +0 -0
  72. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/link_cli.py +0 -0
  73. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/main.py +0 -0
  74. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/output.py +0 -0
  75. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/payment_helpers.py +0 -0
  76. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/payment_token.py +0 -0
  77. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/provenance.py +0 -0
  78. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/reschedule.py +0 -0
  79. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/runtime.py +0 -0
  80. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/search_area.py +0 -0
  81. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/session.py +0 -0
  82. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/skill_bundle.py +0 -0
  83. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/skill_integrity.py +0 -0
  84. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/support_bundle_schema.py +0 -0
  85. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/support_report.py +0 -0
  86. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/timezone_context.py +0 -0
  87. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/src/booking_agent_cli/updates.py +0 -0
  88. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/.env.tests +0 -0
  89. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/conftest.py +0 -0
  90. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/e2e/__init__.py +0 -0
  91. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/e2e/test_cancel_reschedule_e2e.py +0 -0
  92. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/e2e/test_latency_soak.py +0 -0
  93. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/e2e/test_staging_registry_integration.py +0 -0
  94. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/e2e/test_timezone_booking_e2e.py +0 -0
  95. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/e2e/test_ucp_native_e2e.py +0 -0
  96. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/e2e/test_ucp_skill_flow_e2e.py +0 -0
  97. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/e2e/ucp_native.py +0 -0
  98. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/__init__.py +0 -0
  99. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_activity_log.py +0 -0
  100. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_at_rest_hygiene.py +0 -0
  101. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_booking_rejection.py +0 -0
  102. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_bulk_cancel.py +0 -0
  103. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_calendar_connection.py +0 -0
  104. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_calendar_connector.py +0 -0
  105. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_calendar_flow.py +0 -0
  106. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_calendar_preference.py +0 -0
  107. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_checkout_signals.py +0 -0
  108. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_circuit_breaker.py +0 -0
  109. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_config.py +0 -0
  110. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_correlation_flow.py +0 -0
  111. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_declined_spt.py +0 -0
  112. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_discover_registry_rejection.py +0 -0
  113. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_flow_evidence.py +0 -0
  114. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_flow_gates.py +0 -0
  115. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_flow_reschedule.py +0 -0
  116. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_flow_step_timings.py +0 -0
  117. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_google_calendar_oauth.py +0 -0
  118. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_home_visit_address.py +0 -0
  119. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_http_trace.py +0 -0
  120. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_identity_logout.py +0 -0
  121. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_install_integrity.py +0 -0
  122. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_lazy_oauth.py +0 -0
  123. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_link_auth.py +0 -0
  124. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_link_cli_spt.py +0 -0
  125. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_namespace.py +0 -0
  126. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_oauth_listener.py +0 -0
  127. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_oauth_metadata.py +0 -0
  128. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_platform_key_pop.py +0 -0
  129. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_platform_profile.py +0 -0
  130. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_pop_keystore.py +0 -0
  131. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_profile_cache.py +0 -0
  132. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_regression_index.py +0 -0
  133. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_release_hold_uses_delete.py +0 -0
  134. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_request_retry_semantics.py +0 -0
  135. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_runtime_prepare.py +0 -0
  136. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_search_area.py +0 -0
  137. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_session_compat.py +0 -0
  138. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_session_retention.py +0 -0
  139. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_skill_integrity.py +0 -0
  140. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_skill_portability.py +0 -0
  141. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_support_report.py +0 -0
  142. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_timezone_context.py +0 -0
  143. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_cancel_result.py +0 -0
  144. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_checkout_pop.py +0 -0
  145. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_checkout_proof_wireup.py +0 -0
  146. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_checkout_validate.py +0 -0
  147. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_profile.py +0 -0
  148. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_update_command.py +0 -0
  149. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/integration/test_wheel_contents.py +0 -0
  150. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/SOURCE +0 -0
  151. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/acp_booking_extension.json +0 -0
  152. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/availability.json +0 -0
  153. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/booking.json +0 -0
  154. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/calendar_freebusy.json +0 -0
  155. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/catalog.json +0 -0
  156. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/paid_bookings.json +0 -0
  157. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/profile.json +0 -0
  158. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/registry.json +0 -0
  159. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/rest_common.json +0 -0
  160. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/usp.json +0 -0
  161. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/waitlist.json +0 -0
  162. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/spec_schemas/webhook_event.json +0 -0
  163. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/__init__.py +0 -0
  164. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/fixtures/__init__.py +0 -0
  165. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/fixtures/profiles.py +0 -0
  166. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/fixtures/provenance/pypi_attestations-0.0.30-py3-none-any.whl.provenance.json +0 -0
  167. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/fixtures/services.py +0 -0
  168. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/mock/__init__.py +0 -0
  169. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/mock/backend.py +0 -0
  170. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/mock/link_cli.py +0 -0
  171. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/mock/mock_calendar.py +0 -0
  172. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/mock/pop_business.py +0 -0
  173. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/mock/pop_transport.py +0 -0
  174. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/mock/ucp_checkout_store.py +0 -0
  175. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/mock/ucp_server.py +0 -0
  176. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/offline_stack/__init__.py +0 -0
  177. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/support/offline_stack/stack.py +0 -0
  178. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/__init__.py +0 -0
  179. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_authorization_policy.py +0 -0
  180. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_booking_poll_credentials.py +0 -0
  181. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_booking_request_error.py +0 -0
  182. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_calendar_filter.py +0 -0
  183. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_catalog.py +0 -0
  184. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_check_version.py +0 -0
  185. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_conditional_writes.py +0 -0
  186. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_delivery_address.py +0 -0
  187. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_discovery.py +0 -0
  188. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_fault_reports.py +0 -0
  189. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_geo_eligibility.py +0 -0
  190. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_geocode.py +0 -0
  191. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_headless.py +0 -0
  192. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_http_deadlines.py +0 -0
  193. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_http_retry.py +0 -0
  194. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_live_search_registry.py +0 -0
  195. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_merchant_pacing.py +0 -0
  196. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_mock_support.py +0 -0
  197. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_money_path_events.py +0 -0
  198. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_nonce_from_pop_error_body.py +0 -0
  199. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_observability.py +0 -0
  200. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_outbound_member_names.py +0 -0
  201. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_package_layering.py +0 -0
  202. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_pay_at_service.py +0 -0
  203. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_provenance.py +0 -0
  204. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_registry_search_filters.py +0 -0
  205. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_registry_search_pricing.py +0 -0
  206. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_registry_search_responses.py +0 -0
  207. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_registry_url.py +0 -0
  208. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_release_notes.py +0 -0
  209. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_rest_client.py +0 -0
  210. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_scoped_method_authorization.py +0 -0
  211. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_session_credentials.py +0 -0
  212. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_spec_conformance.py +0 -0
  213. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_step_timing.py +0 -0
  214. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_ucp_capabilities.py +0 -0
  215. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_ucp_complete_outcome.py +0 -0
  216. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_ucp_create_price_guard.py +0 -0
  217. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_ucp_negotiation.py +0 -0
  218. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_updates.py +0 -0
  219. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/unit/test_wire_model_conformance.py +0 -0
  220. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/001-jcs-nested-buyer.json +0 -0
  221. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/002-jcs-meta-only.json +0 -0
  222. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/003-jcs-unicode-empty-meta.json +0 -0
  223. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/004-proof-issuance-no-ath.json +0 -0
  224. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/005-proof-presentation-ath-nonce.json +0 -0
  225. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/006-cross-key-replay.json +0 -0
  226. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/007-downgrade-cnf-without-proof.json +0 -0
  227. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/008-replayed-jti.json +0 -0
  228. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/009-aud-mismatch.json +0 -0
  229. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/010-alg-none.json +0 -0
  230. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/tests/vectors/pop/README.md +0 -0
  231. {booking_agent_cli-0.10.0 → booking_agent_cli-0.10.2}/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.10.0
3
+ Version: 0.10.2
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.2/booking-agent/skills/booking-agent/SKILL.md
81
+ curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.4/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.2/booking-agent/skills/booking-agent/SKILL.md
39
+ curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.4/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.10.0"
3
+ version = "0.10.2"
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.10.0"
6
+ __version__ = "0.10.2"
@@ -1,3 +1,3 @@
1
1
  """booking_agent_cli: typer-based stateful CLI for USP booking flows."""
2
2
 
3
- __version__ = "0.10.0"
3
+ __version__ = "0.10.2"
@@ -1,5 +1,5 @@
1
1
  ---
2
- version: 0.57.2
2
+ version: 0.57.4
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.10.0"
22
+ package: "booking-agent-cli==0.10.2"
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.2/booking-agent/skills/booking-agent/SKILL.md
223
+ curl -fsSLO https://raw.githubusercontent.com/wix/booking-agent/skill-v0.57.4/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,20 +245,20 @@ before installing:
245
245
 
246
246
  ```bash
247
247
  dir="$HOME/.booking-agent/verified-wheels"
248
- uvx pip download "booking-agent-cli==0.10.0" --no-deps --only-binary :all: -d "$dir"
248
+ uvx pip download "booking-agent-cli==0.10.2" --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.10.0-py3-none-any.whl"
251
- uv tool install --force "$dir/booking_agent_cli-0.10.0-py3-none-any.whl"
250
+ "$dir/booking_agent_cli-0.10.2-py3-none-any.whl"
251
+ uv tool install --force "$dir/booking_agent_cli-0.10.2-py3-none-any.whl"
252
252
 
253
253
  booking-agent --help
254
254
  ```
255
255
 
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.10.0` or
258
- `pipx install booking-agent-cli==0.10.0`; the CLI checks its own files against the signed release
256
+ Install only a wheel whose verify step passed. If `uvx` is not
257
+ available, install the same pinned release with `uv tool install booking-agent-cli==0.10.2` or
258
+ `pipx install booking-agent-cli==0.10.2`; 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
- Harnesses that read the `install` block in this skill's metadata do this for you. Do not go on
261
+ Harnesses that read this skill's `install` block do this for you. Do not go on
262
262
  until `booking-agent --help` runs. If it still does not, run the commands above yourself; if that
263
263
  is not possible in this runtime, ask the user to install the skill, then try again. To develop the
264
264
  CLI from a checkout instead, see the [Booking Agent README](https://github.com/wix/booking-agent/blob/master/booking-agent/README.md).
@@ -281,13 +281,13 @@ folder that survives between conversations, re-run the same init with `--persist
281
281
  path of that folder>`: the CLI links saved bookings and the Link sign-in into it, merging what each
282
282
  folder holds (booking files by session, the newer copy of anything else) and deleting nothing. If
283
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`.
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
284
+ If it answers `persistence_folder_not_durable` (temporary, memory-backed, or unwritable), name the
285
+ folder the runtime really keeps, or use `--no-persistent-root`.
286
+ `persistence_setup_failed` and `persistence_flags_conflict` say what to change. Use the same folder every time; never guess a path, ask the user, or mention it. A host
288
287
  that sets `BOOKING_AGENT_PERSISTENT_ROOT` or `BOOKING_AGENT_PERSISTENCE=none` is never asked. If the
289
288
  user is asked to sign in or reconnect right after a restart, run `booking-agent runtime doctor` and
290
- follow its `next_command`. For extra cache folders the runtime asks you to keep, run `booking-agent
289
+ follow its `next_command`. If `flow status` or `flow init` answers `saved_state_not_linked`, run its `next_command` once, then
290
+ `flow status`; never answer it with `--no-persistent-root` or a new booking. For extra cache folders the runtime asks you to keep, run `booking-agent
291
291
  runtime prepare --persistent-root <folder> --link <runtime folder>=<persistent folder>`.
292
292
 
293
293
  **Sign the user in to Link**, as described in [Link sign-in](#link-sign-in-automatic). It works
@@ -296,9 +296,8 @@ with no booking in progress. Do not wait for the user to offer to sign in.
296
296
  **Pass `--skill-path` and `--skill-version` on every `flow init`**: the path you loaded this file
297
297
  from, and the version from the frontmatter above.
298
298
 
299
- Nothing needs configuring for a basic booking: every setting has a shipped default. The user
300
- never configures anything for the calendar either: you work out at runtime how this runtime can
301
- reach Google Calendar (see [the calendar gate](#step-3-select-business-and-resolve-calendar-gate)).
299
+ Nothing needs configuring: every setting has a shipped default, and the user never configures the
300
+ calendar: you work out at runtime how this runtime can reach Google Calendar (see [the calendar gate](#step-3-select-business-and-resolve-calendar-gate)).
302
301
  **Never** write tokens, keys, or personal details into any env file. `booking-agent config show`
303
302
  prints each effective setting and where it came from; `booking-agent config init` creates
304
303
  `~/.booking-agent/.env` for an operator to edit.
@@ -306,9 +305,8 @@ prints each effective setting and where it came from; `booking-agent config init
306
305
  ### Updates (only with the user's yes)
307
306
 
308
307
  The CLI decides whether a newer version is worth mentioning; you never decide from version
309
- numbers or release pages. Pass this skill's version on every `flow init` (`--skill-version
310
- 0.57.2`, from the frontmatter above), so the CLI can tell when these instructions and the
311
- installed CLI no longer match.
308
+ numbers or release pages. The `--skill-version` you pass on every `flow init` lets the CLI tell when these instructions
309
+ and the installed CLI no longer match.
312
310
 
313
311
  - When a command returns `awaiting_user: update_consent`, ask the question in `prompt` in plain
314
312
  words, using `data.user_summary` as the reason, and **STOP**. On yes, run `next_command`
@@ -470,7 +468,7 @@ confirmation email or on the business's page (`booking_url`). Do not invent a lo
470
468
  **Step 1b — Create a new session:**
471
469
 
472
470
  ```bash
473
- booking-agent flow init --user-request "<user's exact request>" --skill-version 0.57.2 \
471
+ booking-agent flow init --user-request "<user's exact request>" --skill-version 0.57.4 \
474
472
  --skill-path "<absolute path of the SKILL.md you loaded>"
475
473
  ```
476
474
 
@@ -85,6 +85,18 @@ def _pref_applicable(
85
85
  return False
86
86
 
87
87
 
88
+ _AT_OR_BEFORE_CALENDAR_GATE = frozenset({"INIT", "PREREQ_OK", "BUSINESS_SELECTED", "CALENDAR_GATE"})
89
+ # Phases where the user has already chosen a time: a calendar reconnect then changes nothing
90
+ # about the booking, and must never send it back to choose a time again.
91
+ CALENDAR_RECONNECTED_LATE_PHASES = frozenset({"SLOT_SELECTED", "BUYER_RESOLVED"})
92
+
93
+
94
+ def _reach_calendar_gate(session: Session) -> None:
95
+ """Move to the calendar gate only from before it; a later phase keeps its place."""
96
+ if session.flow_phase in _AT_OR_BEFORE_CALENDAR_GATE:
97
+ transition(session, to="CALENDAR_GATE")
98
+
99
+
88
100
  def try_apply_remembered_calendar_gate(
89
101
  session: Session,
90
102
  connection: Dict[str, Any],
@@ -101,7 +113,7 @@ def try_apply_remembered_calendar_gate(
101
113
  session.calendar_remembered_applied = True
102
114
  session.calendar_state = "skipped"
103
115
  session.calendar_mode = CALENDAR_MODE_NONE
104
- transition(session, to="CALENDAR_GATE")
116
+ _reach_calendar_gate(session)
105
117
  save_session(session)
106
118
  return RememberedCalendarResult(
107
119
  applied=True,
@@ -118,7 +130,7 @@ def try_apply_remembered_calendar_gate(
118
130
  session.calendar_remembered_applied = True
119
131
  session.calendar_state = "connected"
120
132
  session.calendar_mode = mode
121
- transition(session, to="CALENDAR_GATE")
133
+ _reach_calendar_gate(session)
122
134
  save_session(session)
123
135
  if mode == CALENDAR_MODE_EVENTS:
124
136
  prompt = (
@@ -148,7 +160,7 @@ def complete_calendar_gate_with_preference(
148
160
  session.calendar_gate_completed = True
149
161
  session.calendar_state = state
150
162
  session.calendar_mode = mode
151
- transition(session, to="CALENDAR_GATE")
163
+ _reach_calendar_gate(session)
152
164
  save_session(session)
153
165
  save_calendar_preference(state=state, mode=mode)
154
166
 
@@ -163,5 +175,5 @@ def skip_calendar_gate_session_only(session: Session, *, unasked: bool = False)
163
175
  session.calendar_state = "skipped"
164
176
  session.calendar_mode = CALENDAR_MODE_NONE
165
177
  session.calendar_skipped_unasked = unasked
166
- transition(session, to="CALENDAR_GATE")
178
+ _reach_calendar_gate(session)
167
179
  save_session(session)
@@ -726,6 +726,19 @@ def _resolve_persistence(persistent_root: Optional[str], no_persistent_root: boo
726
726
  )
727
727
  raise typer.Exit(code=1)
728
728
  return
729
+ if persistence_resolution() in (None, "acknowledged"):
730
+ from ..runtime_prepare import find_unlinked_state, saved_state_not_linked_message
731
+
732
+ unlinked = find_unlinked_state()
733
+ if unlinked is not None:
734
+ emit_error(
735
+ "INIT",
736
+ code="saved_state_not_linked",
737
+ message=saved_state_not_linked_message(unlinked),
738
+ next_command=f"booking-agent runtime prepare --persistent-root {unlinked}",
739
+ data={"saved_state_folder": str(unlinked)},
740
+ )
741
+ raise typer.Exit(code=1)
729
742
  if no_persistent_root:
730
743
  acknowledge_no_persistence()
731
744
  return
@@ -768,6 +781,18 @@ def _unfinished_similar_sessions(store: Any, user_request: str) -> List[Dict[str
768
781
  def status() -> None:
769
782
  session = load_session()
770
783
  if session is None:
784
+ from ..runtime_prepare import find_unlinked_state, saved_state_not_linked_message
785
+
786
+ unlinked = find_unlinked_state()
787
+ if unlinked is not None:
788
+ emit_error(
789
+ "INIT",
790
+ code="saved_state_not_linked",
791
+ message=saved_state_not_linked_message(unlinked),
792
+ next_command=f"booking-agent runtime prepare --persistent-root {unlinked}",
793
+ data={"saved_state_folder": str(unlinked)},
794
+ )
795
+ raise typer.Exit(code=1)
771
796
  emit_error(
772
797
  "INIT",
773
798
  code="no_session",
@@ -805,6 +830,17 @@ def status() -> None:
805
830
  "Still waiting for your approval in Link. The booking finishes as soon as "
806
831
  "it goes through."
807
832
  )
833
+ elif session.flow_phase == "BUYER_RESOLVED" and not session.booking.id:
834
+ # Saved while waiting for the user's yes. A new conversation cannot see the recap the
835
+ # old one showed, so status hands it back instead of a `flow book` that would be refused.
836
+ awaiting = "booking_confirmation"
837
+ next_cmd = "booking-agent flow buyer resolve"
838
+ prompt = (
839
+ "A booking is waiting for the user's yes and this conversation has not shown them "
840
+ "the summary. Do not start over, search again, or list times. Run next_command: "
841
+ "it shows the same summary again and tells you what to ask. The user's next reply "
842
+ "answers only that question."
843
+ )
808
844
  elif session.flow_phase == "PREREQ_OK" and session.discover_services:
809
845
  # Results were already found (possibly in an earlier conversation): hand them back
810
846
  # instead of making the agent search again and risk a reordered list.
@@ -3472,6 +3508,19 @@ def _require_calendar_offer(session: Session) -> None:
3472
3508
  raise typer.Exit(code=1)
3473
3509
 
3474
3510
 
3511
+ def _after_calendar(session: Session, prompt: str) -> tuple:
3512
+ """Prompt and next command after a calendar step: a late reconnect resumes the booking."""
3513
+ from ..calendar_preference import CALENDAR_RECONNECTED_LATE_PHASES
3514
+
3515
+ if session.flow_phase in CALENDAR_RECONNECTED_LATE_PHASES:
3516
+ return (
3517
+ f"{prompt} The time is already chosen: do not list times again. Continue the "
3518
+ "booking without asking the user again.",
3519
+ "booking-agent flow buyer resolve",
3520
+ )
3521
+ return f"{prompt} Ready to query availability.", "booking-agent flow availability"
3522
+
3523
+
3475
3524
  def _emit_calendar_unavailable(session: Session, status: dict, mode: str, *, data: Optional[dict]) -> None:
3476
3525
  session.calendar_technical_fallback = True
3477
3526
  skip_calendar_gate_session_only(session, unasked=True)
@@ -3481,17 +3530,12 @@ def _emit_calendar_unavailable(session: Session, status: dict, mode: str, *, dat
3481
3530
  out["calendar_mode"] = CALENDAR_MODE_NONE
3482
3531
  out["calendar_checking_unavailable"] = True
3483
3532
  out["stale_calendar_mode"] = mode
3484
- emit(_agent_out(
3485
- session,
3486
- prompt=(
3487
- "The saved Google Calendar access is no longer valid and reconnecting is not "
3488
- "available on this device, so calendar checking is unavailable for this "
3489
- "booking. Tell the user that in one plain sentence, then continue. Ready to "
3490
- "query availability."
3491
- ),
3492
- next_command="booking-agent flow availability",
3493
- data=out,
3533
+ prompt, next_command = _after_calendar(session, (
3534
+ "The saved Google Calendar access is no longer valid and reconnecting is not "
3535
+ "available on this device, so calendar checking is unavailable for this "
3536
+ "booking. Tell the user that in one plain sentence, then continue."
3494
3537
  ))
3538
+ emit(_agent_out(session, prompt=prompt, next_command=next_command, data=out))
3495
3539
 
3496
3540
 
3497
3541
  def _emit_calendar_reconnect_if_stale(
@@ -3706,13 +3750,14 @@ def calendar_skip(
3706
3750
  _stop_pending_sign_in()
3707
3751
  if session_only:
3708
3752
  skip_calendar_gate_session_only(session, unasked=no_route)
3753
+ prompt, next_command = _after_calendar(session, (
3754
+ "Continuing this booking without calendar checking; the saved calendar "
3755
+ "preference is unchanged."
3756
+ ))
3709
3757
  emit(_agent_out(
3710
3758
  session,
3711
- prompt=(
3712
- "Continuing this booking without calendar checking; the saved calendar "
3713
- "preference is unchanged. Ready to query availability."
3714
- ),
3715
- next_command="booking-agent flow availability",
3759
+ prompt=prompt,
3760
+ next_command=next_command,
3716
3761
  data={"calendar_state": "skipped", "calendar_mode": CALENDAR_MODE_NONE},
3717
3762
  ))
3718
3763
  return
@@ -3721,11 +3766,8 @@ def calendar_skip(
3721
3766
  state="skipped",
3722
3767
  mode=CALENDAR_MODE_NONE,
3723
3768
  )
3724
- emit(_agent_out(
3725
- session,
3726
- prompt="Calendar gate satisfied (user opted out). Ready to query availability.",
3727
- next_command="booking-agent flow availability",
3728
- ))
3769
+ prompt, next_command = _after_calendar(session, "Calendar gate satisfied (user opted out).")
3770
+ emit(_agent_out(session, prompt=prompt, next_command=next_command))
3729
3771
 
3730
3772
 
3731
3773
  @calendar_app.command("connect")
@@ -3799,20 +3841,9 @@ def _emit_calendar_connected(session: Session, *, mode: str, data: dict) -> None
3799
3841
  if mode == CALENDAR_MODE_EVENTS
3800
3842
  else "Calendar connected for conflict checking (free/busy only)."
3801
3843
  )
3802
- if session.flow_phase == "SLOT_SELECTED":
3803
- # Reconnected late (after the slot was chosen): pick the booking up where it stopped.
3804
- emit(_agent_out(
3805
- session,
3806
- prompt=f"{prompt} Continue the booking without asking the user again.",
3807
- next_command="booking-agent flow buyer resolve",
3808
- data={**data, "mode": mode},
3809
- ))
3810
- return
3844
+ prompt, next_command = _after_calendar(session, prompt)
3811
3845
  emit(_agent_out(
3812
- session,
3813
- prompt=f"{prompt} Ready to query availability.",
3814
- next_command="booking-agent flow availability",
3815
- data={**data, "mode": mode},
3846
+ session, prompt=prompt, next_command=next_command, data={**data, "mode": mode},
3816
3847
  ))
3817
3848
 
3818
3849
 
@@ -5202,12 +5233,15 @@ def buyer_resolve(
5202
5233
  """Run `link-cli user-info retrieve` and store the Buyer in session."""
5203
5234
  session = must_load()
5204
5235
  try:
5205
- require_phase(session, "SLOT_SELECTED", code="no_slot_selected",
5236
+ # BUYER_RESOLVED is allowed again: a new conversation (after a restart) or a corrected
5237
+ # note must be able to show the same recap, since no booking exists yet in that phase.
5238
+ require_phase(session, "SLOT_SELECTED", "BUYER_RESOLVED", code="no_slot_selected",
5206
5239
  next_command="booking-agent flow slots select --slot-id <id> --user-confirmed")
5207
5240
  _resync_link_auth_gate(session)
5208
5241
  require_link_auth_gate(session)
5209
5242
  except GateError as e:
5210
5243
  raise _handle_gate(session, e)
5244
+ recap_shown_again = session.flow_phase == "BUYER_RESOLVED"
5211
5245
 
5212
5246
  from ..link_cli import (
5213
5247
  LinkCliUserInfoError,
@@ -5252,7 +5286,7 @@ def buyer_resolve(
5252
5286
  session.booking_notes = " ".join(notes.split())[:BOOKING_NOTES_MAX_CHARS] or None
5253
5287
  _ensure_buyer_timezone(session, link_cli_bin=link_cli_bin)
5254
5288
  dual = _selected_slot_zones_differ(session)
5255
- notice_needed = dual and not session.timezone_difference_shown
5289
+ notice_needed = dual and (recap_shown_again or not session.timezone_difference_shown)
5256
5290
  if notice_needed:
5257
5291
  session.timezone_difference_shown = True
5258
5292
  transition(session, to="BUYER_RESOLVED")
@@ -17,7 +17,7 @@ import sys
17
17
  import tempfile
18
18
  import time
19
19
  from pathlib import Path
20
- from typing import Dict, List, Optional, Tuple
20
+ from typing import Callable, Dict, List, Optional, Tuple
21
21
 
22
22
  from booking_agent import settings
23
23
  from booking_agent.secure_file import ensure_private_dir, write_private_text
@@ -353,6 +353,11 @@ def diagnose() -> Dict[str, object]:
353
353
  findings.append({"kind": kind, "path": str(target / sub), "severity": "problem"})
354
354
  else:
355
355
  findings.append({"kind": "not_persistent", "path": str(state), "severity": "info"})
356
+ unlinked = find_unlinked_state()
357
+ if unlinked is not None:
358
+ problems = True
359
+ root_hint = str(unlinked)
360
+ findings.append({"kind": "saved_state_not_linked", "path": root_hint, "severity": "problem"})
356
361
  if state_info["linked_to"] and link_info["linked_to"]:
357
362
  if Path(str(state_info["linked_to"])).parent != Path(str(link_info["linked_to"])).parent:
358
363
  problems = True
@@ -457,6 +462,84 @@ def persistence_resolution() -> Optional[str]:
457
462
  return None
458
463
 
459
464
 
465
+ _TERMINAL_PHASES = frozenset({"CONFIRMED", "FAILED"})
466
+ _SCAN_DEPTH_NAMES = 2
467
+
468
+
469
+ def _safe(check: Callable[[], bool]) -> bool:
470
+ """A filesystem test that treats an unreadable path as absent (``Path.is_file`` raises on EACCES)."""
471
+ try:
472
+ return bool(check())
473
+ except OSError:
474
+ return False
475
+
476
+
477
+ def _holds_unfinished_booking(session_file: Path) -> bool:
478
+ try:
479
+ raw = json.loads(session_file.read_text())
480
+ except (OSError, ValueError):
481
+ return False
482
+ sessions = raw.get("sessions") if isinstance(raw, dict) else None
483
+ return any(
484
+ isinstance(s, dict) and s.get("flow_phase") not in _TERMINAL_PHASES
485
+ for s in sessions or []
486
+ )
487
+
488
+
489
+ def find_unlinked_state() -> Optional[Path]:
490
+ """A persistent root that holds an unfinished booking this runtime has not linked.
491
+
492
+ A restart can wipe the runtime's own folders (and the symlinks into the persistent one),
493
+ leaving the saved booking and Link sign-in intact but invisible: the CLI then sees "no
494
+ session" and an agent starts over. Looks beside the working folder, its parents, and the
495
+ home folder, at most two levels down, for a folder this CLI prepared earlier (it holds
496
+ ``booking-agent/.usp-session.json`` and a boot marker or the sign-in folder).
497
+ """
498
+ home = settings.booking_agent_home()
499
+ if _safe(home.is_symlink) or _safe((home / _SESSION_FILE).is_file):
500
+ return None
501
+ try:
502
+ bases = [Path.cwd(), *Path.cwd().parents, Path.home()]
503
+ except (OSError, RuntimeError):
504
+ return None
505
+ seen = set()
506
+ for base in bases:
507
+ if base in seen:
508
+ continue
509
+ seen.add(base)
510
+ level = [base]
511
+ for _ in range(_SCAN_DEPTH_NAMES + 1):
512
+ nxt = []
513
+ for folder in level:
514
+ state = folder / STATE_SUBDIR
515
+ session_file = state / _SESSION_FILE
516
+ if (
517
+ _safe(session_file.is_file)
518
+ and (_safe((folder / _BOOT_FILE).is_file) or _safe((folder / LINK_SUBDIR).is_dir))
519
+ and _holds_unfinished_booking(session_file)
520
+ ):
521
+ return folder
522
+ try:
523
+ nxt.extend(
524
+ child for child in sorted(folder.iterdir())
525
+ if _safe(child.is_dir) and not _safe(child.is_symlink)
526
+ and child.name not in ("node_modules", ".git", ".venv", "proc", "sys")
527
+ )
528
+ except OSError:
529
+ continue
530
+ level = nxt if len(nxt) <= 200 else []
531
+ return None
532
+
533
+
534
+ def saved_state_not_linked_message(root: Path) -> str:
535
+ return (
536
+ f"A saved booking and Link sign-in exist in {root}, but this runtime is not linked to "
537
+ "them (a restart cleared the links). Do not start over and do not use "
538
+ "--no-persistent-root: run next_command once, then continue with `booking-agent flow "
539
+ "status`."
540
+ )
541
+
542
+
460
543
  def acknowledge_no_persistence() -> None:
461
544
  home = ensure_private_dir(settings.booking_agent_home())
462
545
  write_private_text(home / _ACK_FILE, time.strftime("%Y-%m-%d\n", time.gmtime()))
@@ -658,3 +658,53 @@ def test_compliance_flags_a_booking_that_skipped_a_confirmation(change, failure)
658
658
  failures = _skill_compliance_failures(session)
659
659
 
660
660
  assert any(f.startswith(failure) for f in failures), failures
661
+
662
+
663
+ # ---------------------------------------------------------------------------
664
+ # A new conversation resumes a booking that is waiting for the user's yes
665
+ # ---------------------------------------------------------------------------
666
+
667
+ def test_status_hands_back_the_summary_step_for_a_booking_waiting_for_the_yes(
668
+ runner, isolated_session, monkeypatch,
669
+ ):
670
+ _direct_booking_to_recap(runner, monkeypatch)
671
+
672
+ body = _stdout_json(_invoke(runner, "flow", "status"))
673
+
674
+ assert body["phase"] == "BUYER_RESOLVED"
675
+ assert body["awaiting_user"] == "booking_confirmation"
676
+ assert body["next_command"] == "booking-agent flow buyer resolve"
677
+ assert "start over" in body["prompt"]
678
+
679
+
680
+ def test_the_summary_can_be_shown_again_after_a_restart_and_then_confirmed(
681
+ runner, isolated_session, monkeypatch,
682
+ ):
683
+ first = _direct_booking_to_recap(runner, monkeypatch)
684
+
685
+ again = _invoke(runner, "flow", "buyer", "resolve")
686
+
687
+ assert again.exit_code == 0, again.stdout + again.stderr
688
+ body = _stdout_json(again)
689
+ assert body["awaiting_user"] == "booking_confirmation"
690
+ assert body["data"]["booking_recap_lines"] == first["data"]["booking_recap_lines"]
691
+ assert load_session().recap_confirmed_digest is None
692
+ r = _invoke(runner, *_BOOK_CONFIRMED)
693
+ assert r.exit_code == 0, r.stdout + r.stderr
694
+ assert load_session().booking.id
695
+
696
+
697
+ def test_the_next_command_after_a_changed_summary_actually_runs(
698
+ runner, isolated_session, monkeypatch,
699
+ ):
700
+ _direct_booking_to_recap(runner, monkeypatch)
701
+ session = load_session()
702
+ session.recap_digest = "0000000000000000"
703
+ save_session(session)
704
+ refused = _stdout_json(_invoke(runner, *_BOOK_CONFIRMED))
705
+ assert refused["next_command"].startswith("booking-agent flow buyer resolve")
706
+
707
+ r = _invoke(runner, "flow", "buyer", "resolve")
708
+
709
+ assert r.exit_code == 0, r.stdout + r.stderr
710
+ assert _stdout_json(r)["awaiting_user"] == "booking_confirmation"