databricks-tellr-app 0.4.3.dev20__tar.gz → 0.4.3.dev21__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 (198) hide show
  1. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/PKG-INFO +1 -1
  2. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/databricks_tellr_app.egg-info/PKG-INFO +1 -1
  3. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/databricks_tellr_app.egg-info/SOURCES.txt +38 -0
  4. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/pyproject.toml +1 -1
  5. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/fixtures/tour_demo_deck.json +52 -1
  6. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/main.py +20 -1
  7. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/chat.py +68 -5
  8. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/sessions.py +52 -0
  9. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/slides.py +129 -0
  10. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/tour.py +28 -0
  11. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/streaming.py +23 -0
  12. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/chat_service.py +852 -13
  13. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/deck_level_writer.py +81 -6
  14. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/job_queue.py +15 -0
  15. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/session_manager.py +332 -24
  16. databricks_tellr_app-0.4.3.dev21/src/core/skills/__init__.py +225 -0
  17. databricks_tellr_app-0.4.3.dev21/src/core/skills/architect.py +64 -0
  18. databricks_tellr_app-0.4.3.dev21/src/core/skills/build_reviewer.py +80 -0
  19. databricks_tellr_app-0.4.3.dev21/src/core/skills/builder.py +54 -0
  20. databricks_tellr_app-0.4.3.dev21/src/core/skills/data_analyst.py +38 -0
  21. databricks_tellr_app-0.4.3.dev21/src/core/skills/deck_reviewer.py +24 -0
  22. databricks_tellr_app-0.4.3.dev21/src/core/skills/fix_reviewer.py +22 -0
  23. databricks_tellr_app-0.4.3.dev21/src/core/skills/fixer.py +52 -0
  24. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/domain/slide_deck.py +10 -4
  25. databricks_tellr_app-0.4.3.dev21/src/services/agent_factory.py +150 -0
  26. databricks_tellr_app-0.4.3.dev20/src/services/agent_factory.py → databricks_tellr_app-0.4.3.dev21/src/services/agent_resolution.py +253 -145
  27. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/deck_css_aggregator.py +29 -8
  28. databricks_tellr_app-0.4.3.dev21/src/services/foreman_service.py +307 -0
  29. databricks_tellr_app-0.4.3.dev21/src/services/graph/__init__.py +6 -0
  30. databricks_tellr_app-0.4.3.dev21/src/services/graph/builder.py +265 -0
  31. databricks_tellr_app-0.4.3.dev21/src/services/graph/event_emitter.py +186 -0
  32. databricks_tellr_app-0.4.3.dev21/src/services/graph/nodes.py +2682 -0
  33. databricks_tellr_app-0.4.3.dev21/src/services/graph/routers.py +203 -0
  34. databricks_tellr_app-0.4.3.dev21/src/services/graph/state.py +307 -0
  35. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/html_to_google_slides.py +1 -1
  36. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/html_to_pptx.py +2 -2
  37. databricks_tellr_app-0.4.3.dev21/src/services/spec_sync.py +756 -0
  38. databricks_tellr_app-0.4.3.dev21/src/services/template_sections.py +512 -0
  39. databricks_tellr_app-0.4.3.dev21/src/utils/graph_safety.py +100 -0
  40. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/README.md +0 -0
  41. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/databricks_tellr_app/__init__.py +0 -0
  42. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/databricks_tellr_app/run.py +0 -0
  43. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/databricks_tellr_app.egg-info/dependency_links.txt +0 -0
  44. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/databricks_tellr_app.egg-info/requires.txt +0 -0
  45. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/databricks_tellr_app.egg-info/top_level.txt +0 -0
  46. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/setup.cfg +0 -0
  47. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/setup.py +0 -0
  48. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/__init__.py +0 -0
  49. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/__init__.py +0 -0
  50. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/_otel_bootstrap.py +0 -0
  51. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/mcp_auth.py +0 -0
  52. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/mcp_server.py +0 -0
  53. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/middleware/__init__.py +0 -0
  54. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/middleware/csrf.py +0 -0
  55. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/middleware/request_logging.py +0 -0
  56. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/middleware/security_headers.py +0 -0
  57. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/__init__.py +0 -0
  58. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/_authz.py +0 -0
  59. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/admin.py +0 -0
  60. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/admin_usage.py +0 -0
  61. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/agent_config.py +0 -0
  62. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/deck_contributors.py +0 -0
  63. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/export.py +0 -0
  64. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/feedback.py +0 -0
  65. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/google_slides.py +0 -0
  66. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/images.py +0 -0
  67. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/local_version.py +0 -0
  68. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/profiles.py +0 -0
  69. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/settings/__init__.py +0 -0
  70. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/settings/contributors.py +0 -0
  71. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/settings/deck_prompts.py +0 -0
  72. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/settings/design_systems.py +0 -0
  73. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/settings/identities.py +0 -0
  74. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/settings/slide_styles.py +0 -0
  75. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/setup.py +0 -0
  76. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/tools.py +0 -0
  77. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/verification.py +0 -0
  78. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/routes/version.py +0 -0
  79. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/__init__.py +0 -0
  80. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/agent_config.py +0 -0
  81. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/feedback.py +0 -0
  82. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/requests.py +0 -0
  83. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/responses.py +0 -0
  84. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/settings/__init__.py +0 -0
  85. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/settings/requests.py +0 -0
  86. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/schemas/settings/responses.py +0 -0
  87. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/__init__.py +0 -0
  88. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/export_job_queue.py +0 -0
  89. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/feedback_service.py +0 -0
  90. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/session_naming.py +0 -0
  91. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/slide_repository.py +0 -0
  92. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/usage_events.py +0 -0
  93. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/services/usage_service.py +0 -0
  94. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/utils/__init__.py +0 -0
  95. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/api/utils/validation.py +0 -0
  96. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/__init__.py +0 -0
  97. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/backfill_session_slides_startup.py +0 -0
  98. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/checkpointer.py +0 -0
  99. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/config_loader.py +0 -0
  100. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/context_utils.py +0 -0
  101. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/database.py +0 -0
  102. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/databricks_client.py +0 -0
  103. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/defaults.py +0 -0
  104. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/encryption.py +0 -0
  105. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/init_default_profile.py +0 -0
  106. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/lakebase.py +0 -0
  107. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/migrate_profiles_to_agent_config.py +0 -0
  108. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/mlflow_agent_spans.py +0 -0
  109. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/mlflow_tracing.py +0 -0
  110. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/otel_logging.py +0 -0
  111. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/permission_context.py +0 -0
  112. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/prompt_modules.py +0 -0
  113. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/settings_db.py +0 -0
  114. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/strip_retired_prompt_keys.py +0 -0
  115. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/core/user_context.py +0 -0
  116. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/__init__.py +0 -0
  117. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/__init__.py +0 -0
  118. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/deck_contributor.py +0 -0
  119. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/deck_review.py +0 -0
  120. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/design_system.py +0 -0
  121. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/encryption_key.py +0 -0
  122. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/feedback.py +0 -0
  123. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/genie_space.py +0 -0
  124. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/google_global_credentials.py +0 -0
  125. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/google_oauth_token.py +0 -0
  126. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/graph_checkpoint.py +0 -0
  127. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/identity.py +0 -0
  128. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/image.py +0 -0
  129. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/profile.py +0 -0
  130. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/profile_contributor.py +0 -0
  131. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/prompts.py +0 -0
  132. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/request_log.py +0 -0
  133. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/session.py +0 -0
  134. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/slide_deck_prompt.py +0 -0
  135. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/slide_style_library.py +0 -0
  136. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/usage_event.py +0 -0
  137. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/models/user_preference.py +0 -0
  138. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/database/types.py +0 -0
  139. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/domain/__init__.py +0 -0
  140. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/domain/deck_spec.py +0 -0
  141. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/domain/finding.py +0 -0
  142. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/domain/skill_io.py +0 -0
  143. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/domain/slide.py +0 -0
  144. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/__init__.py +0 -0
  145. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/agent.py +0 -0
  146. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/config_service.py +0 -0
  147. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/config_validator.py +0 -0
  148. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/converter_jail/__init__.py +0 -0
  149. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/converter_jail/ast_guard.py +0 -0
  150. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/converter_jail/codeprep.py +0 -0
  151. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/converter_jail/gslides_runner.py +0 -0
  152. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/converter_jail/jail.py +0 -0
  153. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/converter_jail/pptx_runner.py +0 -0
  154. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/converter_jail/protocol.py +0 -0
  155. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/deck_review_store.py +0 -0
  156. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/design_system_compiler.py +0 -0
  157. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/design_system_service.py +0 -0
  158. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/design_system_templates.py +0 -0
  159. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/drive_uploader.py +0 -0
  160. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/evaluation/__init__.py +0 -0
  161. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/evaluation/llm_judge.py +0 -0
  162. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/genie_service.py +0 -0
  163. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/google_slides_auth.py +0 -0
  164. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/google_slides_prompts_defaults.py +0 -0
  165. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/identity_provider.py +0 -0
  166. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/identity_providers/__init__.py +0 -0
  167. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/identity_providers/account_provider.py +0 -0
  168. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/identity_providers/local_provider.py +0 -0
  169. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/identity_providers/workspace_provider.py +0 -0
  170. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/image_service.py +0 -0
  171. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/image_tools.py +0 -0
  172. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/permission_service.py +0 -0
  173. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/pptx_from_html_huashu.py +0 -0
  174. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/pptx_from_records.py +0 -0
  175. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/pptx_prompts_defaults.py +0 -0
  176. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/profile_service.py +0 -0
  177. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/streaming_callback.py +0 -0
  178. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/tools/__init__.py +0 -0
  179. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/tools/agent_bricks_tool.py +0 -0
  180. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/tools/ds_asset_tool.py +0 -0
  181. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/tools/genie_tool.py +0 -0
  182. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/tools/mcp_tool.py +0 -0
  183. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/tools/model_endpoint_tool.py +0 -0
  184. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/tools/vector_tool.py +0 -0
  185. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/services/validator.py +0 -0
  186. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/__init__.py +0 -0
  187. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/css_utils.py +0 -0
  188. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/ds_asset_utils.py +0 -0
  189. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/error_handling.py +0 -0
  190. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/html_safety.py +0 -0
  191. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/html_utils.py +0 -0
  192. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/image_utils.py +0 -0
  193. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/js_validator.py +0 -0
  194. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/logging_config.py +0 -0
  195. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/pi_filter.py +0 -0
  196. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/slide_hash.py +0 -0
  197. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/spotlight.py +0 -0
  198. {databricks_tellr_app-0.4.3.dev20 → databricks_tellr_app-0.4.3.dev21}/src/utils/text_caps.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.3.dev20
3
+ Version: 0.4.3.dev21
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: databricks-tellr-app
3
- Version: 0.4.3.dev20
3
+ Version: 0.4.3.dev21
4
4
  Summary: Tellr application package for Databricks Apps
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -82,6 +82,14 @@ setup.py
82
82
  ./src/core/settings_db.py
83
83
  ./src/core/strip_retired_prompt_keys.py
84
84
  ./src/core/user_context.py
85
+ ./src/core/skills/__init__.py
86
+ ./src/core/skills/architect.py
87
+ ./src/core/skills/build_reviewer.py
88
+ ./src/core/skills/builder.py
89
+ ./src/core/skills/data_analyst.py
90
+ ./src/core/skills/deck_reviewer.py
91
+ ./src/core/skills/fix_reviewer.py
92
+ ./src/core/skills/fixer.py
85
93
  ./src/database/__init__.py
86
94
  ./src/database/types.py
87
95
  ./src/database/models/__init__.py
@@ -114,6 +122,7 @@ setup.py
114
122
  ./src/services/__init__.py
115
123
  ./src/services/agent.py
116
124
  ./src/services/agent_factory.py
125
+ ./src/services/agent_resolution.py
117
126
  ./src/services/config_service.py
118
127
  ./src/services/config_validator.py
119
128
  ./src/services/deck_css_aggregator.py
@@ -122,6 +131,7 @@ setup.py
122
131
  ./src/services/design_system_service.py
123
132
  ./src/services/design_system_templates.py
124
133
  ./src/services/drive_uploader.py
134
+ ./src/services/foreman_service.py
125
135
  ./src/services/genie_service.py
126
136
  ./src/services/google_slides_auth.py
127
137
  ./src/services/google_slides_prompts_defaults.py
@@ -135,7 +145,9 @@ setup.py
135
145
  ./src/services/pptx_from_records.py
136
146
  ./src/services/pptx_prompts_defaults.py
137
147
  ./src/services/profile_service.py
148
+ ./src/services/spec_sync.py
138
149
  ./src/services/streaming_callback.py
150
+ ./src/services/template_sections.py
139
151
  ./src/services/validator.py
140
152
  ./src/services/converter_jail/__init__.py
141
153
  ./src/services/converter_jail/ast_guard.py
@@ -146,6 +158,12 @@ setup.py
146
158
  ./src/services/converter_jail/protocol.py
147
159
  ./src/services/evaluation/__init__.py
148
160
  ./src/services/evaluation/llm_judge.py
161
+ ./src/services/graph/__init__.py
162
+ ./src/services/graph/builder.py
163
+ ./src/services/graph/event_emitter.py
164
+ ./src/services/graph/nodes.py
165
+ ./src/services/graph/routers.py
166
+ ./src/services/graph/state.py
149
167
  ./src/services/identity_providers/__init__.py
150
168
  ./src/services/identity_providers/account_provider.py
151
169
  ./src/services/identity_providers/local_provider.py
@@ -161,6 +179,7 @@ setup.py
161
179
  ./src/utils/css_utils.py
162
180
  ./src/utils/ds_asset_utils.py
163
181
  ./src/utils/error_handling.py
182
+ ./src/utils/graph_safety.py
164
183
  ./src/utils/html_safety.py
165
184
  ./src/utils/html_utils.py
166
185
  ./src/utils/image_utils.py
@@ -256,6 +275,14 @@ src/core/prompt_modules.py
256
275
  src/core/settings_db.py
257
276
  src/core/strip_retired_prompt_keys.py
258
277
  src/core/user_context.py
278
+ src/core/skills/__init__.py
279
+ src/core/skills/architect.py
280
+ src/core/skills/build_reviewer.py
281
+ src/core/skills/builder.py
282
+ src/core/skills/data_analyst.py
283
+ src/core/skills/deck_reviewer.py
284
+ src/core/skills/fix_reviewer.py
285
+ src/core/skills/fixer.py
259
286
  src/database/__init__.py
260
287
  src/database/types.py
261
288
  src/database/models/__init__.py
@@ -288,6 +315,7 @@ src/domain/slide_deck.py
288
315
  src/services/__init__.py
289
316
  src/services/agent.py
290
317
  src/services/agent_factory.py
318
+ src/services/agent_resolution.py
291
319
  src/services/config_service.py
292
320
  src/services/config_validator.py
293
321
  src/services/deck_css_aggregator.py
@@ -296,6 +324,7 @@ src/services/design_system_compiler.py
296
324
  src/services/design_system_service.py
297
325
  src/services/design_system_templates.py
298
326
  src/services/drive_uploader.py
327
+ src/services/foreman_service.py
299
328
  src/services/genie_service.py
300
329
  src/services/google_slides_auth.py
301
330
  src/services/google_slides_prompts_defaults.py
@@ -309,7 +338,9 @@ src/services/pptx_from_html_huashu.py
309
338
  src/services/pptx_from_records.py
310
339
  src/services/pptx_prompts_defaults.py
311
340
  src/services/profile_service.py
341
+ src/services/spec_sync.py
312
342
  src/services/streaming_callback.py
343
+ src/services/template_sections.py
313
344
  src/services/validator.py
314
345
  src/services/converter_jail/__init__.py
315
346
  src/services/converter_jail/ast_guard.py
@@ -320,6 +351,12 @@ src/services/converter_jail/pptx_runner.py
320
351
  src/services/converter_jail/protocol.py
321
352
  src/services/evaluation/__init__.py
322
353
  src/services/evaluation/llm_judge.py
354
+ src/services/graph/__init__.py
355
+ src/services/graph/builder.py
356
+ src/services/graph/event_emitter.py
357
+ src/services/graph/nodes.py
358
+ src/services/graph/routers.py
359
+ src/services/graph/state.py
323
360
  src/services/identity_providers/__init__.py
324
361
  src/services/identity_providers/account_provider.py
325
362
  src/services/identity_providers/local_provider.py
@@ -335,6 +372,7 @@ src/utils/__init__.py
335
372
  src/utils/css_utils.py
336
373
  src/utils/ds_asset_utils.py
337
374
  src/utils/error_handling.py
375
+ src/utils/graph_safety.py
338
376
  src/utils/html_safety.py
339
377
  src/utils/html_utils.py
340
378
  src/utils/image_utils.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "databricks-tellr-app"
7
- version = "0.4.3.dev20"
7
+ version = "0.4.3.dev21"
8
8
  description = "Tellr application package for Databricks Apps"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -24,5 +24,56 @@
24
24
  }
25
25
  ],
26
26
  "chat_prompt": "Create 3 slides about the benefits of AI in modern healthcare, with a title slide, key advantages, and future outlook.",
27
- "chat_reply": "Here's your deck with 3 slides! I've created:\n\n1. **Title Slide** — introducing AI in healthcare\n2. **Key Advantages** — early detection, personalized treatment, operational efficiency, and drug discovery\n3. **Future Outlook** — predictive monitoring, AI-assisted surgery, and global health equity\n\nFeel free to ask me to refine any slide, add new ones, or change the style."
27
+ "chat_reply": "Here's your deck with 3 slides! I've created:\n\n1. **Title Slide** — introducing AI in healthcare\n2. **Key Advantages** — early detection, personalized treatment, operational efficiency, and drug discovery\n3. **Future Outlook** — predictive monitoring, AI-assisted surgery, and global health equity\n\nFeel free to ask me to refine any slide, add new ones, or change the style.",
28
+ "deck_spec": {
29
+ "title": "Example Deck — AI in Healthcare",
30
+ "audience": "A first-time Tellr user taking the product tour: a general business audience, with no clinical or machine-learning background assumed.",
31
+ "purpose": "Show a new user what a finished Tellr deck looks like, on a subject that needs no domain briefing, so they can see structure and styling before they write their own prompt.",
32
+ "argument": "AI already delivers concrete benefits across diagnosis, treatment and research, and its near-term trajectory points at prevention and wider access, so it is worth engaging with now rather than watching.",
33
+ "call_to_action": "Ask the assistant to refine a slide, add a new one, or change the style: the next step of the tour.",
34
+ "narrative_arc": [
35
+ "Frame the subject: AI is already changing patient care, diagnostics and medical research, a transformation under way rather than a forecast.",
36
+ "Ground that claim in four advantages visible today: earlier disease detection, personalized treatment, operational efficiency, and faster drug discovery.",
37
+ "Extend today's gains into the near future and land on access: predictive monitoring, AI-assisted surgery, and specialist-level care reaching underserved communities."
38
+ ],
39
+ "design_contract": {
40
+ "design_system_id": null,
41
+ "template_id": null,
42
+ "slide_style_id": null
43
+ },
44
+ "resolved_data": {
45
+ "synthesis": "No live data was resolved for this deck. The tour ships fixed illustrative bytes so every user sees the same three slides; the claims are widely reported general benefits of AI in healthcare, not figures drawn from a source system.",
46
+ "figures": [],
47
+ "gaps": [
48
+ "No quantitative source: the deck cites no measured figure, so no claim in it is traceable to a dataset.",
49
+ "No organisation context: the tour deck is deliberately generic and names no customer, hospital or vendor."
50
+ ]
51
+ },
52
+ "slides": [
53
+ {
54
+ "position": 0,
55
+ "purpose": "Open the deck: name the subject and frame AI in healthcare as a transformation already under way.",
56
+ "content_brief": "Title slide. Badge reading 'AI & Healthcare', headline 'The Benefits of AI in Modern Healthcare', and a one-line standfirst naming the three areas the deck covers: patient care, diagnostics, and medical research.",
57
+ "assumes": "Nothing. This is the deck's first slide.",
58
+ "hands_off": "The reader knows the subject and expects concrete advantages next.",
59
+ "data_references": []
60
+ },
61
+ {
62
+ "position": 1,
63
+ "purpose": "Make the case with four concrete present-day advantages, so the benefit claim is specific rather than atmospheric.",
64
+ "content_brief": "Four bolded items, one explanatory clause each: early disease detection (imaging patterns a human reader can miss), personalized treatment plans (genetics, lifestyle, history), operational efficiency (scheduling, documentation, triage), and drug discovery acceleration (molecular simulation at scale).",
65
+ "assumes": "The reader has accepted the framing from the title slide.",
66
+ "hands_off": "The present-tense case is made; the reader is ready to look forward.",
67
+ "data_references": []
68
+ },
69
+ {
70
+ "position": 2,
71
+ "purpose": "Extend the argument from today's gains to the near future and close on access, so the deck ends on consequence rather than a list.",
72
+ "content_brief": "Three bolded forward-looking items: predictive health monitoring (wearables shifting medicine from reactive to proactive), AI-assisted surgery (robotics guided by real-time analysis), and global health equity (mobile diagnostics for underserved communities). Close on the italic line about healthcare becoming intelligent, personalized and accessible to all.",
73
+ "assumes": "The reader accepts the four present-day advantages from the previous slide.",
74
+ "hands_off": "The deck ends; the reader leaves with the equity claim as the takeaway.",
75
+ "data_references": []
76
+ }
77
+ ]
78
+ }
28
79
  }
@@ -51,13 +51,14 @@ _worker_task = None
51
51
  _export_worker_task = None
52
52
  _cleanup_task = None
53
53
  _timeout_task = None
54
+ _spec_sweeper_task = None
54
55
  _frontend_assets_stack: ExitStack | None = None
55
56
 
56
57
 
57
58
  @asynccontextmanager
58
59
  async def lifespan(app: FastAPI):
59
60
  """Lifespan context manager for startup/shutdown events."""
60
- global _worker_task, _export_worker_task, _cleanup_task, _timeout_task, _frontend_assets_stack
61
+ global _worker_task, _export_worker_task, _cleanup_task, _timeout_task, _spec_sweeper_task, _frontend_assets_stack
61
62
 
62
63
  # Startup
63
64
  logger.info(f"Starting AI Slide Generator API (environment: {ENVIRONMENT})")
@@ -134,6 +135,16 @@ async def lifespan(app: FastAPI):
134
135
  _cleanup_task = asyncio.create_task(request_log_cleanup_loop())
135
136
  logger.info("Request log cleanup task started")
136
137
 
138
+ # Start the spec-dirty arc-review sweeper. A PERIODIC LOOP, so it belongs
139
+ # here beside the two above and NOT in run.py::init_database: the
140
+ # pre-fork rule there is for migrations and backfills, which must run
141
+ # exactly once, and a loop started in that step would die with it. It
142
+ # runs in every uvicorn worker (UVICORN_WORKERS defaults to 4), which is
143
+ # why claim_due_marker takes an atomic lease rather than trusting a read.
144
+ from src.services.spec_sync import spec_review_sweeper_loop
145
+ _spec_sweeper_task = asyncio.create_task(spec_review_sweeper_loop())
146
+ logger.info("Spec-review arc sweeper started")
147
+
137
148
  # Recover any stuck requests from previous crashes
138
149
  try:
139
150
  recovered = await recover_stuck_requests()
@@ -185,6 +196,14 @@ async def lifespan(app: FastAPI):
185
196
  pass
186
197
  logger.info("MCP job timeout sweeper stopped")
187
198
 
199
+ if _spec_sweeper_task:
200
+ _spec_sweeper_task.cancel()
201
+ try:
202
+ await _spec_sweeper_task
203
+ except asyncio.CancelledError:
204
+ pass
205
+ logger.info("Spec-review arc sweeper stopped")
206
+
188
207
  # Tear down the FastMCP session manager's task group. Safe to call
189
208
  # unconditionally — the stack was entered unconditionally at startup.
190
209
  await mcp_lifespan_stack.aclose()
@@ -26,7 +26,7 @@ from src.api.schemas.requests import ChatRequest
26
26
  from src.api.schemas.responses import ChatResponse
27
27
  from src.api.schemas.streaming import StreamEvent, StreamEventType
28
28
  from src.api.routes._authz import _check_deck_permission_for_session
29
- from src.api.services.chat_service import get_chat_service
29
+ from src.api.services.chat_service import get_chat_service, resolve_engine_mode_or
30
30
  from src.api.services.job_queue import enqueue_job
31
31
  from src.api.services.session_manager import SessionNotFoundError, get_session_manager
32
32
  from src.core.context_utils import run_in_thread_with_context
@@ -454,6 +454,15 @@ async def send_message_streaming(
454
454
  detail="Session is currently processing another request. Please wait.",
455
455
  )
456
456
 
457
+ # ws4d D2 — the engine for this turn is resolved HERE, in the chat route,
458
+ # and passed down. Not in send_message_streaming and not in the job queue:
459
+ # MCP's enqueue_create_job drains through the same worker as POST
460
+ # /chat/async, so a resolution downstream of enqueue_job would put MCP on
461
+ # the graph. Resolution reads the database, so it runs off the event loop.
462
+ engine_mode = await asyncio.to_thread(
463
+ resolve_engine_mode_or, request.session_id
464
+ )
465
+
457
466
  async def generate_events() -> AsyncGenerator[str, None]:
458
467
  """Generate SSE events from the chat service."""
459
468
  import queue
@@ -484,6 +493,7 @@ async def send_message_streaming(
484
493
  if request.slide_context
485
494
  else None,
486
495
  image_ids=request.image_ids,
496
+ engine_mode=engine_mode,
487
497
  ):
488
498
  event_queue.put(event)
489
499
  except Exception as e:
@@ -637,6 +647,14 @@ async def submit_chat_async(
637
647
  request_id=request_id,
638
648
  )
639
649
 
650
+ # ws4d D2 — resolved AFTER the user message is persisted just above,
651
+ # because resolve_engine_mode reads the deck's EARLIEST role='user'
652
+ # message: on turn 1 that row has to exist already or the phrase in the
653
+ # very first message would resolve to monolith.
654
+ engine_mode = await asyncio.to_thread(
655
+ resolve_engine_mode_or, request.session_id
656
+ )
657
+
640
658
  # Queue for processing
641
659
  await enqueue_job(
642
660
  request_id,
@@ -648,6 +666,10 @@ async def submit_chat_async(
648
666
  ),
649
667
  "is_first_message": is_first_message,
650
668
  "image_ids": request.image_ids,
669
+ # `engine_mode`, NEVER `mode`: `mode` is already MCP's
670
+ # generate/edit flag in this same payload (mcp_server.py), whose
671
+ # values are neither "graph" nor "monolith".
672
+ "engine_mode": engine_mode,
651
673
  },
652
674
  )
653
675
 
@@ -669,18 +691,36 @@ async def submit_chat_async(
669
691
  async def poll_chat(
670
692
  request_id: str,
671
693
  after_message_id: int = Query(default=0, description="Return messages after this ID"),
694
+ slide_cursor: int = Query(
695
+ default=0, description="Lowest slide position not yet delivered"
696
+ ),
672
697
  ):
673
- """Poll for chat request status and new messages.
698
+ """Poll for chat request status, new messages, and newly released slides.
674
699
 
675
- Returns the current request status and any new messages since
676
- the last poll (based on after_message_id).
700
+ Returns the current request status, any new messages since the last poll
701
+ (based on after_message_id), and any slides the reorder buffer has released
702
+ since ``slide_cursor``.
703
+
704
+ **The slides do NOT come through ``msg_to_stream_event``.** ws4d D3 does not
705
+ persist slide-ready as a ``SessionMessage``: that converter returns a fixed
706
+ six-key dict with nowhere to put ``position``/``html``/``scripts``, so a
707
+ converter that learned the type still could not carry the data — and build
708
+ mechanics in the transcript pollute a view that is now user-visible and
709
+ clearable. This reads **committed rows** instead, via
710
+ ``SessionManager.slides_since_cursor``, which is the same reorder-buffer prefix
711
+ rule the graph applies to state on the SSE transport.
677
712
 
678
713
  Args:
679
714
  request_id: Request ID from submit_chat_async
680
715
  after_message_id: Return messages with ID greater than this
716
+ slide_cursor: Lowest slide position the caller has NOT been sent. ``0``
717
+ (the default) asks for every released slide. Positions are 0-based,
718
+ so this is inclusive — unlike ``after_message_id``, whose ids start
719
+ at 1. Hand back the ``slide_cursor`` from the previous response.
681
720
 
682
721
  Returns:
683
- Dictionary with status, events, last_message_id, and result
722
+ Dictionary with status, events, last_message_id, result, error, slides
723
+ and slide_cursor
684
724
 
685
725
  Raises:
686
726
  HTTPException: 404 if request not found
@@ -721,10 +761,33 @@ async def poll_chat(
721
761
  if m["role"] != "user"
722
762
  ]
723
763
 
764
+ # ws4d D3 — incremental slides on the polling transport. Projected into an
765
+ # explicit response shape rather than returned as-is: the row query knows
766
+ # about columns this endpoint has no business shipping, and the four keys
767
+ # below are the contract ws4e consumes.
768
+ released_slides = [
769
+ {
770
+ "position": row["position"],
771
+ "html": row["html"],
772
+ "scripts": row["scripts"],
773
+ "agent": row["agent"],
774
+ }
775
+ for row in await asyncio.to_thread(
776
+ session_manager.slides_since_cursor, session_id, slide_cursor
777
+ )
778
+ ]
779
+
724
780
  return {
725
781
  "status": chat_request["status"],
726
782
  "events": events,
727
783
  "last_message_id": messages[-1]["id"] if messages else after_message_id,
728
784
  "result": chat_request.get("result") if chat_request["status"] == "completed" else None,
729
785
  "error": chat_request.get("error_message") if chat_request["status"] == "error" else None,
786
+ "slides": released_slides,
787
+ # The next position NOT yet delivered. Unchanged when nothing was
788
+ # released, so a client that keeps handing this back never re-receives a
789
+ # slide it already has.
790
+ "slide_cursor": (
791
+ released_slides[-1]["position"] + 1 if released_slides else slide_cursor
792
+ ),
730
793
  }
@@ -22,6 +22,7 @@ from src.api.routes._authz import (
22
22
  _require_session_access,
23
23
  )
24
24
  from src.api.schemas.requests import CreateSessionRequest, DuplicateSessionRequest
25
+ from src.api.services.chat_service import get_chat_service
25
26
  from src.api.services.session_manager import (
26
27
  SessionAccessDeniedError,
27
28
  SessionNotFoundError,
@@ -704,6 +705,57 @@ async def add_message(session_id: str, request: AddMessageRequest):
704
705
  ) from e
705
706
 
706
707
 
708
+ @router.delete("/{session_id}/context")
709
+ async def clear_session_context(session_id: str):
710
+ """Clear the conversation context for a session, keeping the deck and its spec.
711
+
712
+ Drops the transcript and the graph checkpoint thread; keeps the slide deck,
713
+ ``deck_spec_json`` (the structured compaction of what was decided) and the
714
+ earliest user message, which is what engine-mode resolution reads.
715
+
716
+ Requires CAN_EDIT: the helper's default is CAN_VIEW, and taking the default
717
+ would let a **viewer** wipe another user's transcript and graph thread.
718
+
719
+ Args:
720
+ session_id: Session whose context to clear
721
+
722
+ Returns:
723
+ Clear confirmation with the number of messages deleted
724
+ """
725
+ # Permission check: require CAN_EDIT on the deck — NOT the helper's default.
726
+ await asyncio.to_thread(
727
+ _check_deck_permission_for_session, session_id, PermissionLevel.CAN_EDIT
728
+ )
729
+
730
+ try:
731
+ chat_service = get_chat_service()
732
+ result = await asyncio.to_thread(chat_service.clear_context, session_id)
733
+
734
+ logger.info(
735
+ "Session context cleared via API",
736
+ extra={
737
+ "session_id": session_id,
738
+ "deleted_messages": result.get("deleted_messages"),
739
+ },
740
+ )
741
+
742
+ return result
743
+
744
+ except SessionNotFoundError:
745
+ raise HTTPException(
746
+ status_code=404,
747
+ detail=f"Session not found: {session_id}",
748
+ )
749
+ except HTTPException:
750
+ raise
751
+ except Exception as e:
752
+ logger.error(f"Failed to clear session context: {e}", exc_info=True)
753
+ raise HTTPException(
754
+ status_code=500,
755
+ detail="Failed to clear session context",
756
+ ) from e
757
+
758
+
707
759
  @router.get("/{session_id}/slides")
708
760
  async def get_session_slides(session_id: str):
709
761
  """Get slide deck for a session.
@@ -7,6 +7,26 @@ Slide access is controlled by session permissions:
7
7
  - CAN_VIEW: Can view slides (via get_slides)
8
8
  - CAN_EDIT: Can modify slides (reorder, update, duplicate, delete)
9
9
  - CAN_MANAGE: Full control (same as owner)
10
+
11
+ The spec-dirty trigger lives in THIS module and nowhere else
12
+ ------------------------------------------------------------
13
+ Five handlers below call ``spec_sync.mark_dirty`` after their mutation commits:
14
+ insert, reorder, update, duplicate and delete. That is the whole of the trigger surface —
15
+ the LangGraph deck build calls the ``chat_service`` / ``slide_repository`` /
16
+ ``deck_level_writer`` methods these handlers wrap, and never issues an HTTP
17
+ request, so "arrived via a route" *means* "a human did this" by construction. No
18
+ ``origin=`` parameter to forget, no ContextVar to leak.
19
+ ``tests/unit/test_spec_sync_placement.py`` fails if any other module under ``src/``
20
+ references the name, and pins which routes do and do not trigger. Read
21
+ ``src/services/spec_sync.py``'s module docstring before adding or moving a call.
22
+
23
+ ``get_current_user()`` is read in the handler's own request context and the author
24
+ passed explicitly into the worker thread — never re-read inside the thread.
25
+
26
+ Deliberately NOT triggers: the two verification routes and the three version
27
+ routes (a version save does not change the live deck; a restore cancels the
28
+ pending review instead), and session-duplicate in ``sessions.py`` (a copied spec
29
+ is already correct for the HTML it carries).
10
30
  """
11
31
 
12
32
  import asyncio
@@ -28,6 +48,7 @@ from src.core.database import get_db
28
48
  from src.core.permission_context import get_permission_context # noqa: F401 — patched by integration-test fixtures
29
49
  from src.core.user_context import get_current_user # noqa: F401 — patched by integration-test fixtures
30
50
  from src.database.models.profile_contributor import PermissionLevel
51
+ from src.services.spec_sync import mark_dirty
31
52
 
32
53
  logger = logging.getLogger(__name__)
33
54
 
@@ -57,6 +78,27 @@ class SlideActionRequest(BaseModel):
57
78
  expected_version: Optional[int] = None
58
79
 
59
80
 
81
+ class InsertSlideRequest(BaseModel):
82
+ """Request to insert a new slide at a position.
83
+
84
+ ``position`` is 0-based and NOT bounded above: a position past the end appends,
85
+ which is `SlideDeck.insert_slide`'s own behaviour and is deliberately not
86
+ re-implemented here. The lower bound IS enforced — not by a pydantic
87
+ constraint, but by `chat_service.insert_slide` raising ValueError, which this
88
+ handler turns into a 400 — because `list.insert` would read a negative position
89
+ as counting backwards from the end and land the slide somewhere the caller did
90
+ not ask for.
91
+
92
+ ``html`` is optional: omitted, an empty slide is inserted for a human or the
93
+ architect to fill.
94
+ """
95
+
96
+ session_id: str
97
+ position: int
98
+ html: Optional[str] = None
99
+ expected_version: Optional[int] = None
100
+
101
+
60
102
  class UpdateVerificationRequest(BaseModel):
61
103
  """Request to update a slide's verification result."""
62
104
 
@@ -108,6 +150,83 @@ async def get_slides(
108
150
  raise HTTPException(status_code=500, detail="Internal server error")
109
151
 
110
152
 
153
+ @router.post("")
154
+ async def insert_slide(request: InsertSlideRequest, db: Session = Depends(get_db)):
155
+ """Insert a new slide, shifting every slide above the insertion point up.
156
+
157
+ Requires CAN_EDIT permission — the level is pinned by
158
+ ``tests/unit/test_authz_slides_insert.py``, not by
159
+ ``test_route_authz_coverage.py``, which checks that a route has *a* check and
160
+ cannot detect a wrong LEVEL.
161
+ Uses session locking to prevent concurrent modifications.
162
+
163
+ Args:
164
+ request: InsertSlideRequest with session_id, position and optional html
165
+
166
+ Returns:
167
+ Updated slide deck
168
+
169
+ Raises:
170
+ HTTPException: 403 if no permission, 400 for validation errors, 409 if
171
+ session busy or version conflict, 423 if another user holds the
172
+ editing lock, 500 on error
173
+ """
174
+ _require_slide_permission(request.session_id, db, PermissionLevel.CAN_EDIT)
175
+
176
+ session_manager = get_session_manager()
177
+
178
+ try:
179
+ await run_in_thread_with_context(session_manager.require_editing_lock, request.session_id)
180
+ except PermissionError as e:
181
+ raise HTTPException(status_code=423, detail=str(e))
182
+
183
+ locked = await asyncio.to_thread(
184
+ session_manager.acquire_session_lock,
185
+ request.session_id,
186
+ )
187
+ if not locked:
188
+ raise HTTPException(
189
+ status_code=409,
190
+ detail="Session is currently processing another request. Please wait.",
191
+ )
192
+
193
+ try:
194
+ chat_service = get_chat_service()
195
+ result = await asyncio.to_thread(
196
+ chat_service.insert_slide,
197
+ request.session_id,
198
+ request.position,
199
+ html=request.html,
200
+ expected_version=request.expected_version,
201
+ )
202
+
203
+ logger.info(
204
+ "Inserted slide",
205
+ extra={"position": request.position, "session_id": request.session_id},
206
+ )
207
+ # A slide was added: the deck's slide list no longer matches the spec's.
208
+ # The service gave the new position a PLACEHOLDER spec entry with no
209
+ # purpose, so this is also what schedules the description of it.
210
+ await asyncio.to_thread(mark_dirty, request.session_id, get_current_user())
211
+ return result
212
+
213
+ except VersionConflictError as e:
214
+ raise HTTPException(status_code=409, detail=str(e))
215
+ except PermissionError as e:
216
+ raise HTTPException(status_code=423, detail=str(e))
217
+ except ValueError as e:
218
+ logger.warning(f"Validation error in insert_slide: {e}")
219
+ raise HTTPException(status_code=400, detail=str(e))
220
+ except Exception as e:
221
+ logger.error(f"Failed to insert slide: {e}", exc_info=True)
222
+ raise HTTPException(status_code=500, detail="Internal server error")
223
+ finally:
224
+ await asyncio.to_thread(
225
+ session_manager.release_session_lock,
226
+ request.session_id,
227
+ )
228
+
229
+
111
230
  @router.put("/reorder")
112
231
  async def reorder_slides(request: ReorderRequest, db: Session = Depends(get_db)):
113
232
  """Reorder slides.
@@ -156,6 +275,9 @@ async def reorder_slides(request: ReorderRequest, db: Session = Depends(get_db))
156
275
  "Reordered slides",
157
276
  extra={"new_order": request.new_order, "session_id": request.session_id},
158
277
  )
278
+ # A reorder mutates the narrative arc with NO HTML change, so a
279
+ # content-hash trigger would miss it entirely.
280
+ await asyncio.to_thread(mark_dirty, request.session_id, get_current_user())
159
281
  return result
160
282
 
161
283
  except VersionConflictError as e:
@@ -224,6 +346,8 @@ async def update_slide(index: int, request: UpdateSlideRequest, db: Session = De
224
346
  "Updated slide",
225
347
  extra={"index": index, "session_id": request.session_id},
226
348
  )
349
+ # The human HTML edit — the reason the marker exists.
350
+ await asyncio.to_thread(mark_dirty, request.session_id, get_current_user())
227
351
  return result
228
352
 
229
353
  except VersionConflictError as e:
@@ -292,6 +416,8 @@ async def duplicate_slide(index: int, request: SlideActionRequest, db: Session =
292
416
  "Duplicated slide",
293
417
  extra={"index": index, "session_id": request.session_id},
294
418
  )
419
+ # A slide was added: the deck's slide list no longer matches the spec's.
420
+ await asyncio.to_thread(mark_dirty, request.session_id, get_current_user())
295
421
  return result
296
422
 
297
423
  except VersionConflictError as e:
@@ -366,6 +492,9 @@ async def delete_slide(
366
492
  "Deleted slide",
367
493
  extra={"index": index, "session_id": session_id},
368
494
  )
495
+ # A slide was removed. Note this handler takes session_id from the query
496
+ # string, not a request body.
497
+ await asyncio.to_thread(mark_dirty, session_id, get_current_user())
369
498
  return result
370
499
 
371
500
  except VersionConflictError as e: