booking-agent-cli 0.10.1__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.1 → booking_agent_cli-0.10.2}/PKG-INFO +2 -2
  2. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/README.md +1 -1
  3. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/pyproject.toml +1 -1
  4. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/__init__.py +1 -1
  5. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/__init__.py +1 -1
  6. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/bundled_skill.md +20 -22
  7. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/calendar_preference.py +16 -4
  8. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/flow.py +53 -33
  9. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/runtime_prepare.py +84 -1
  10. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_resume_after_restart.py +158 -0
  11. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/.gitignore +0 -0
  12. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/authorization.py +0 -0
  13. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/backend/__init__.py +0 -0
  14. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/backend/live.py +0 -0
  15. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/__init__.py +0 -0
  16. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/filter.py +0 -0
  17. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/google.py +0 -0
  18. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/oauth_listener.py +0 -0
  19. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/calendar/scope.py +0 -0
  20. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/circuit_breaker.py +0 -0
  21. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/config.py +0 -0
  22. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/defaults.env +0 -0
  23. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/discovery.py +0 -0
  24. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/faults.py +0 -0
  25. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/headless.py +0 -0
  26. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/http_deadlines.py +0 -0
  27. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/http_retry.py +0 -0
  28. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/http_trace.py +0 -0
  29. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/merchant_pacing.py +0 -0
  30. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/models/__init__.py +0 -0
  31. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/models/platform.py +0 -0
  32. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/models/wire.py +0 -0
  33. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/namespace.py +0 -0
  34. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/observability.py +0 -0
  35. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/platform_profile.py +0 -0
  36. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/pop.py +0 -0
  37. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/profile_cache.py +0 -0
  38. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/registry_url.py +0 -0
  39. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/rest_client.py +0 -0
  40. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/secure_file.py +0 -0
  41. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/settings.py +0 -0
  42. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/signals.py +0 -0
  43. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/step_timing.py +0 -0
  44. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/ucp_capabilities.py +0 -0
  45. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/ucp_checkout.py +0 -0
  46. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/ucp_negotiation.py +0 -0
  47. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/ucp_profile.py +0 -0
  48. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent/usp_methods.py +0 -0
  49. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/activity_log.py +0 -0
  50. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/booking_poll.py +0 -0
  51. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/booking_summary.py +0 -0
  52. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/calendar_connection.py +0 -0
  53. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/calendar_event.py +0 -0
  54. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/catalog.py +0 -0
  55. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/checkout_prereq.py +0 -0
  56. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/__init__.py +0 -0
  57. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/config.py +0 -0
  58. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/lowlevel.py +0 -0
  59. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/runtime.py +0 -0
  60. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/commands/update.py +0 -0
  61. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/config_loader.py +0 -0
  62. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/engine.py +0 -0
  63. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/fault_report_schema.py +0 -0
  64. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/fault_reports.py +0 -0
  65. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/flow_timings.py +0 -0
  66. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/gates.py +0 -0
  67. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/geo_eligibility.py +0 -0
  68. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/geocode.py +0 -0
  69. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/identity.py +0 -0
  70. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/install_integrity.py +0 -0
  71. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/link_cli.py +0 -0
  72. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/main.py +0 -0
  73. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/output.py +0 -0
  74. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/payment_helpers.py +0 -0
  75. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/payment_token.py +0 -0
  76. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/provenance.py +0 -0
  77. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/reschedule.py +0 -0
  78. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/runtime.py +0 -0
  79. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/search_area.py +0 -0
  80. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/session.py +0 -0
  81. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/skill_bundle.py +0 -0
  82. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/skill_integrity.py +0 -0
  83. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/support_bundle_schema.py +0 -0
  84. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/support_report.py +0 -0
  85. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/timezone_context.py +0 -0
  86. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/src/booking_agent_cli/updates.py +0 -0
  87. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/.env.tests +0 -0
  88. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/conftest.py +0 -0
  89. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/e2e/__init__.py +0 -0
  90. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/e2e/test_cancel_reschedule_e2e.py +0 -0
  91. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/e2e/test_latency_soak.py +0 -0
  92. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/e2e/test_staging_registry_integration.py +0 -0
  93. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/e2e/test_timezone_booking_e2e.py +0 -0
  94. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/e2e/test_ucp_native_e2e.py +0 -0
  95. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/e2e/test_ucp_skill_flow_e2e.py +0 -0
  96. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/e2e/ucp_native.py +0 -0
  97. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/__init__.py +0 -0
  98. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_activity_log.py +0 -0
  99. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_at_rest_hygiene.py +0 -0
  100. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_booking_rejection.py +0 -0
  101. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_bulk_cancel.py +0 -0
  102. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_calendar_connection.py +0 -0
  103. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_calendar_connector.py +0 -0
  104. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_calendar_flow.py +0 -0
  105. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_calendar_preference.py +0 -0
  106. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_checkout_signals.py +0 -0
  107. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_circuit_breaker.py +0 -0
  108. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_cli_enforced_gates.py +0 -0
  109. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_config.py +0 -0
  110. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_correlation_flow.py +0 -0
  111. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_declined_spt.py +0 -0
  112. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_discover_registry_rejection.py +0 -0
  113. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_flow_evidence.py +0 -0
  114. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_flow_gates.py +0 -0
  115. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_flow_reschedule.py +0 -0
  116. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_flow_step_timings.py +0 -0
  117. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_google_calendar_oauth.py +0 -0
  118. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_home_visit_address.py +0 -0
  119. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_http_trace.py +0 -0
  120. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_identity_logout.py +0 -0
  121. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_install_integrity.py +0 -0
  122. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_lazy_oauth.py +0 -0
  123. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_link_auth.py +0 -0
  124. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_link_cli_spt.py +0 -0
  125. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_namespace.py +0 -0
  126. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_oauth_listener.py +0 -0
  127. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_oauth_metadata.py +0 -0
  128. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_platform_key_pop.py +0 -0
  129. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_platform_profile.py +0 -0
  130. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_pop_keystore.py +0 -0
  131. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_profile_cache.py +0 -0
  132. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_regression_index.py +0 -0
  133. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_release_hold_uses_delete.py +0 -0
  134. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_request_retry_semantics.py +0 -0
  135. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_runtime_prepare.py +0 -0
  136. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_search_area.py +0 -0
  137. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_session_compat.py +0 -0
  138. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_session_retention.py +0 -0
  139. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_skill_integrity.py +0 -0
  140. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_skill_portability.py +0 -0
  141. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_support_report.py +0 -0
  142. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_timezone_context.py +0 -0
  143. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_cancel_result.py +0 -0
  144. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_checkout_pop.py +0 -0
  145. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_checkout_proof_wireup.py +0 -0
  146. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_checkout_validate.py +0 -0
  147. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_ucp_profile.py +0 -0
  148. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_update_command.py +0 -0
  149. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/integration/test_wheel_contents.py +0 -0
  150. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/SOURCE +0 -0
  151. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/acp_booking_extension.json +0 -0
  152. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/availability.json +0 -0
  153. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/booking.json +0 -0
  154. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/calendar_freebusy.json +0 -0
  155. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/catalog.json +0 -0
  156. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/paid_bookings.json +0 -0
  157. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/profile.json +0 -0
  158. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/registry.json +0 -0
  159. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/rest_common.json +0 -0
  160. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/usp.json +0 -0
  161. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/waitlist.json +0 -0
  162. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/spec_schemas/webhook_event.json +0 -0
  163. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/__init__.py +0 -0
  164. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/fixtures/__init__.py +0 -0
  165. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/fixtures/profiles.py +0 -0
  166. {booking_agent_cli-0.10.1 → 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.1 → booking_agent_cli-0.10.2}/tests/support/fixtures/services.py +0 -0
  168. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/mock/__init__.py +0 -0
  169. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/mock/backend.py +0 -0
  170. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/mock/link_cli.py +0 -0
  171. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/mock/mock_calendar.py +0 -0
  172. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/mock/pop_business.py +0 -0
  173. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/mock/pop_transport.py +0 -0
  174. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/mock/ucp_checkout_store.py +0 -0
  175. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/mock/ucp_server.py +0 -0
  176. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/offline_stack/__init__.py +0 -0
  177. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/support/offline_stack/stack.py +0 -0
  178. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/__init__.py +0 -0
  179. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_authorization_policy.py +0 -0
  180. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_booking_poll_credentials.py +0 -0
  181. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_booking_request_error.py +0 -0
  182. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_calendar_filter.py +0 -0
  183. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_catalog.py +0 -0
  184. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_check_version.py +0 -0
  185. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_conditional_writes.py +0 -0
  186. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_delivery_address.py +0 -0
  187. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_discovery.py +0 -0
  188. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_fault_reports.py +0 -0
  189. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_geo_eligibility.py +0 -0
  190. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_geocode.py +0 -0
  191. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_headless.py +0 -0
  192. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_http_deadlines.py +0 -0
  193. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_http_retry.py +0 -0
  194. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_live_search_registry.py +0 -0
  195. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_merchant_pacing.py +0 -0
  196. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_mock_support.py +0 -0
  197. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_money_path_events.py +0 -0
  198. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_nonce_from_pop_error_body.py +0 -0
  199. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_observability.py +0 -0
  200. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_outbound_member_names.py +0 -0
  201. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_package_layering.py +0 -0
  202. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_pay_at_service.py +0 -0
  203. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_provenance.py +0 -0
  204. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_registry_search_filters.py +0 -0
  205. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_registry_search_pricing.py +0 -0
  206. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_registry_search_responses.py +0 -0
  207. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_registry_url.py +0 -0
  208. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_release_notes.py +0 -0
  209. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_rest_client.py +0 -0
  210. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_scoped_method_authorization.py +0 -0
  211. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_session_credentials.py +0 -0
  212. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_spec_conformance.py +0 -0
  213. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_step_timing.py +0 -0
  214. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_ucp_capabilities.py +0 -0
  215. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_ucp_complete_outcome.py +0 -0
  216. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_ucp_create_price_guard.py +0 -0
  217. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_ucp_negotiation.py +0 -0
  218. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_updates.py +0 -0
  219. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/unit/test_wire_model_conformance.py +0 -0
  220. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/001-jcs-nested-buyer.json +0 -0
  221. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/002-jcs-meta-only.json +0 -0
  222. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/003-jcs-unicode-empty-meta.json +0 -0
  223. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/004-proof-issuance-no-ath.json +0 -0
  224. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/005-proof-presentation-ath-nonce.json +0 -0
  225. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/006-cross-key-replay.json +0 -0
  226. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/007-downgrade-cnf-without-proof.json +0 -0
  227. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/008-replayed-jti.json +0 -0
  228. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/009-aud-mismatch.json +0 -0
  229. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/010-alg-none.json +0 -0
  230. {booking_agent_cli-0.10.1 → booking_agent_cli-0.10.2}/tests/vectors/pop/README.md +0 -0
  231. {booking_agent_cli-0.10.1 → 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.1
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.3/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.3/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.1"
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.1"
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.1"
3
+ __version__ = "0.10.2"
@@ -1,5 +1,5 @@
1
1
  ---
2
- version: 0.57.3
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.1"
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.3/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.1" --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.1-py3-none-any.whl"
251
- uv tool install --force "$dir/booking_agent_cli-0.10.1-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.1` or
258
- `pipx install booking-agent-cli==0.10.1`; 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.3`, 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.3 \
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",
@@ -3483,6 +3508,19 @@ def _require_calendar_offer(session: Session) -> None:
3483
3508
  raise typer.Exit(code=1)
3484
3509
 
3485
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
+
3486
3524
  def _emit_calendar_unavailable(session: Session, status: dict, mode: str, *, data: Optional[dict]) -> None:
3487
3525
  session.calendar_technical_fallback = True
3488
3526
  skip_calendar_gate_session_only(session, unasked=True)
@@ -3492,17 +3530,12 @@ def _emit_calendar_unavailable(session: Session, status: dict, mode: str, *, dat
3492
3530
  out["calendar_mode"] = CALENDAR_MODE_NONE
3493
3531
  out["calendar_checking_unavailable"] = True
3494
3532
  out["stale_calendar_mode"] = mode
3495
- emit(_agent_out(
3496
- session,
3497
- prompt=(
3498
- "The saved Google Calendar access is no longer valid and reconnecting is not "
3499
- "available on this device, so calendar checking is unavailable for this "
3500
- "booking. Tell the user that in one plain sentence, then continue. Ready to "
3501
- "query availability."
3502
- ),
3503
- next_command="booking-agent flow availability",
3504
- 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."
3505
3537
  ))
3538
+ emit(_agent_out(session, prompt=prompt, next_command=next_command, data=out))
3506
3539
 
3507
3540
 
3508
3541
  def _emit_calendar_reconnect_if_stale(
@@ -3717,13 +3750,14 @@ def calendar_skip(
3717
3750
  _stop_pending_sign_in()
3718
3751
  if session_only:
3719
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
+ ))
3720
3757
  emit(_agent_out(
3721
3758
  session,
3722
- prompt=(
3723
- "Continuing this booking without calendar checking; the saved calendar "
3724
- "preference is unchanged. Ready to query availability."
3725
- ),
3726
- next_command="booking-agent flow availability",
3759
+ prompt=prompt,
3760
+ next_command=next_command,
3727
3761
  data={"calendar_state": "skipped", "calendar_mode": CALENDAR_MODE_NONE},
3728
3762
  ))
3729
3763
  return
@@ -3732,11 +3766,8 @@ def calendar_skip(
3732
3766
  state="skipped",
3733
3767
  mode=CALENDAR_MODE_NONE,
3734
3768
  )
3735
- emit(_agent_out(
3736
- session,
3737
- prompt="Calendar gate satisfied (user opted out). Ready to query availability.",
3738
- next_command="booking-agent flow availability",
3739
- ))
3769
+ prompt, next_command = _after_calendar(session, "Calendar gate satisfied (user opted out).")
3770
+ emit(_agent_out(session, prompt=prompt, next_command=next_command))
3740
3771
 
3741
3772
 
3742
3773
  @calendar_app.command("connect")
@@ -3810,20 +3841,9 @@ def _emit_calendar_connected(session: Session, *, mode: str, data: dict) -> None
3810
3841
  if mode == CALENDAR_MODE_EVENTS
3811
3842
  else "Calendar connected for conflict checking (free/busy only)."
3812
3843
  )
3813
- if session.flow_phase == "SLOT_SELECTED":
3814
- # Reconnected late (after the slot was chosen): pick the booking up where it stopped.
3815
- emit(_agent_out(
3816
- session,
3817
- prompt=f"{prompt} Continue the booking without asking the user again.",
3818
- next_command="booking-agent flow buyer resolve",
3819
- data={**data, "mode": mode},
3820
- ))
3821
- return
3844
+ prompt, next_command = _after_calendar(session, prompt)
3822
3845
  emit(_agent_out(
3823
- session,
3824
- prompt=f"{prompt} Ready to query availability.",
3825
- next_command="booking-agent flow availability",
3826
- data={**data, "mode": mode},
3846
+ session, prompt=prompt, next_command=next_command, data={**data, "mode": mode},
3827
3847
  ))
3828
3848
 
3829
3849
 
@@ -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()))
@@ -238,3 +238,161 @@ def test_doctor_says_why_a_restart_is_not_shown(isolated_session, monkeypatch, t
238
238
  persistence = rp.diagnose()["persistence"]
239
239
  assert persistence["survived_restart"] is True
240
240
  assert persistence["survived_restart_basis"] == "second_runtime_read_the_saved_booking"
241
+
242
+
243
+ # ---------------------------------------------------------------------------
244
+ # A restart cleared the links to the saved state: the booking is still there
245
+ # ---------------------------------------------------------------------------
246
+
247
+ def _restart_that_clears_the_links(tmp_path, monkeypatch, *, marker=True):
248
+ """Move the saved booking into a persistent root, then start a runtime with empty folders.
249
+
250
+ Hosted replay of 0.10.0 on Base44 Superagent: the sandbox was wiped except the saved
251
+ folder, the new runtime saw "no session", and the assistant started over.
252
+ """
253
+ import shutil
254
+
255
+ root = tmp_path / "app" / ".agents" / "state"
256
+ (root / "booking-agent").mkdir(parents=True)
257
+ shutil.move(str(tmp_path / ".usp-session.json"), str(root / "booking-agent" / ".usp-session.json"))
258
+ if marker:
259
+ (root / ".booking-agent-boot").write_text("{}")
260
+ conversation = tmp_path / "app" / "conversations" / "c1"
261
+ conversation.mkdir(parents=True)
262
+ runtime_home = tmp_path / "rt" / ".booking-agent"
263
+ monkeypatch.setenv("BOOKING_AGENT_SESSION_DIR", str(runtime_home))
264
+ monkeypatch.setenv("HOME", str(tmp_path / "rt"))
265
+ monkeypatch.setattr(
266
+ "booking_agent_cli.runtime_prepare.link_cli_config_dir", lambda: tmp_path / "rt" / "link",
267
+ )
268
+ monkeypatch.chdir(conversation)
269
+ return root
270
+
271
+
272
+ def test_status_points_at_saved_state_this_runtime_is_not_linked_to(
273
+ runner, isolated_session, monkeypatch, tmp_path,
274
+ ):
275
+ _at_summary(runner, monkeypatch)
276
+ root = _restart_that_clears_the_links(tmp_path, monkeypatch)
277
+
278
+ out = _stdout_json(_invoke(runner, "flow", "status"))
279
+
280
+ assert out["data"]["error"]["code"] == "saved_state_not_linked"
281
+ assert out["next_command"] == f"booking-agent runtime prepare --persistent-root {root}"
282
+
283
+
284
+ def test_init_will_not_start_over_beside_saved_state(
285
+ runner, isolated_session, monkeypatch, tmp_path,
286
+ ):
287
+ _at_summary(runner, monkeypatch)
288
+ root = _restart_that_clears_the_links(tmp_path, monkeypatch)
289
+ monkeypatch.delenv("BOOKING_AGENT_PERSISTENCE") # the test default declares "none"
290
+
291
+ for flag in ("--no-persistent-root", None):
292
+ args = ["flow", "init", "--user-request", "AC repair", "--skill-version", "0.0.0"]
293
+ if flag:
294
+ args.append(flag)
295
+ r = _invoke(runner, *args)
296
+ assert r.exit_code == 1, r.stdout
297
+ out = _stdout_json(r)
298
+ assert out["data"]["error"]["code"] == "saved_state_not_linked"
299
+ assert out["next_command"].endswith(str(root))
300
+
301
+
302
+ def test_linking_the_folder_brings_the_waiting_booking_back(
303
+ runner, isolated_session, monkeypatch, tmp_path,
304
+ ):
305
+ _at_summary(runner, monkeypatch)
306
+ root = _restart_that_clears_the_links(tmp_path, monkeypatch)
307
+ from booking_agent_cli.main import app
308
+
309
+ r = runner.invoke(app, ["runtime", "prepare", "--persistent-root", str(root)])
310
+ assert r.exit_code == 0, r.stdout
311
+
312
+ out = _stdout_json(_invoke(runner, "flow", "status"))
313
+
314
+ assert out["phase"] == "BUYER_RESOLVED"
315
+ assert out["awaiting_user"] == "booking_confirmation"
316
+ assert out["next_command"] == "booking-agent flow buyer resolve"
317
+
318
+
319
+ def test_doctor_reports_saved_state_that_is_not_linked(
320
+ runner, isolated_session, monkeypatch, tmp_path,
321
+ ):
322
+ _at_summary(runner, monkeypatch)
323
+ root = _restart_that_clears_the_links(tmp_path, monkeypatch)
324
+ from booking_agent_cli.main import app
325
+
326
+ r = runner.invoke(app, ["runtime", "doctor"])
327
+ out = _stdout_json(r)
328
+
329
+ kinds = {f["kind"]: f for f in out["data"]["findings"]}
330
+ assert kinds["saved_state_not_linked"]["path"] == str(root)
331
+ assert out["data"]["healthy"] is False
332
+ assert out["next_command"] == f"booking-agent runtime prepare --persistent-root {root}"
333
+
334
+
335
+ def test_a_folder_this_cli_never_prepared_is_not_mistaken_for_saved_state(
336
+ runner, isolated_session, monkeypatch, tmp_path,
337
+ ):
338
+ _at_summary(runner, monkeypatch)
339
+ _restart_that_clears_the_links(tmp_path, monkeypatch, marker=False)
340
+
341
+ out = _stdout_json(_invoke(runner, "flow", "status"))
342
+
343
+ assert out["data"]["error"]["code"] == "no_session"
344
+
345
+
346
+ # ---------------------------------------------------------------------------
347
+ # Reconnecting the calendar after a restart must not drop the chosen time
348
+ # ---------------------------------------------------------------------------
349
+
350
+ def test_a_calendar_step_after_the_summary_keeps_the_chosen_time(
351
+ runner, isolated_session, monkeypatch,
352
+ ):
353
+ """Hosted replay of 0.10.1: `calendar connect` at the summary sent the booking back to
354
+ CALENDAR_GATE, so the slot and summary were lost and times were listed again."""
355
+ slot_id = _at_summary(runner, monkeypatch)
356
+
357
+ r = _invoke(runner, "flow", "calendar", "skip", "--session-only")
358
+
359
+ assert r.exit_code == 0, r.stdout + r.stderr
360
+ out = _stdout_json(r)
361
+ assert out["phase"] == "BUYER_RESOLVED"
362
+ assert out["next_command"] == "booking-agent flow buyer resolve"
363
+ assert "do not list times again" in out["prompt"]
364
+ from booking_agent_cli.session import load_session
365
+
366
+ assert load_session().slot_id == slot_id
367
+ again = _invoke(runner, "flow", "buyer", "resolve")
368
+ assert again.exit_code == 0, again.stdout + again.stderr
369
+ assert _stdout_json(again)["awaiting_user"] == "booking_confirmation"
370
+
371
+
372
+ def test_the_calendar_gate_still_leads_to_availability_early_in_the_booking(
373
+ runner, isolated_session,
374
+ ):
375
+ _init(runner)
376
+ _complete_checkout_mode_setup(runner)
377
+
378
+ out = _stdout_json(_skip_calendar(runner, "--session-only"))
379
+
380
+ assert out["phase"] == "CALENDAR_GATE"
381
+ assert out["next_command"] == "booking-agent flow availability"
382
+
383
+
384
+ def test_looking_for_saved_state_survives_a_folder_it_cannot_read(
385
+ runner, isolated_session, monkeypatch, tmp_path,
386
+ ):
387
+ """CI runners have folders the user cannot read; `Path.is_file` raises on those."""
388
+ from pathlib import Path
389
+
390
+ import booking_agent_cli.runtime_prepare as rp
391
+
392
+ def _denied(self, *args, **kwargs):
393
+ raise PermissionError(13, "Permission denied", str(self))
394
+
395
+ monkeypatch.setattr(Path, "is_file", _denied)
396
+ monkeypatch.setattr(Path, "is_dir", _denied)
397
+
398
+ assert rp.find_unlinked_state() is None