quantum-framework 0.18.0__tar.gz → 0.20.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (373) hide show
  1. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/PKG-INFO +2 -2
  2. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/README.md +1 -1
  3. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/pyproject.toml +1 -1
  4. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/runner.py +15 -1
  5. quantum_framework-0.20.0/quantum/cli/schema_plan.py +273 -0
  6. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/ast_nodes.py +5 -0
  7. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/expression_stdlib.py +7 -0
  8. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/agents/src/ast_node.py +2 -1
  9. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/knowledge_base/src/ast_node.py +1 -0
  10. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/ui_engine/src/ast_nodes.py +34 -1
  11. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/ui_engine/src/parser.py +26 -1
  12. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parser.py +28 -1
  13. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/agent_parser.py +11 -1
  14. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/knowledge_parser.py +10 -0
  15. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/llm_parser.py +15 -1
  16. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/query_parser.py +6 -0
  17. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/action_handler.py +3 -0
  18. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/agent_service.py +8 -2
  19. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/component.py +4 -19
  20. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/database_service.py +12 -0
  21. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/agent_executor.py +11 -5
  22. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/knowledge_executor.py +6 -4
  23. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/llm_executor.py +98 -4
  24. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/query_executor.py +7 -0
  25. quantum_framework-0.20.0/quantum/runtime/history.py +203 -0
  26. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/knowledge_service.py +40 -40
  27. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/llm_providers.py +60 -7
  28. quantum_framework-0.20.0/quantum/runtime/llm_stream.py +103 -0
  29. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_console.py +30 -0
  30. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_page_renderer.py +70 -2
  31. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_view.py +18 -1
  32. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/web_server.py +24 -5
  33. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/PKG-INFO +2 -2
  34. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/SOURCES.txt +3 -0
  35. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_agent.py +1 -1
  36. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/LICENSE +0 -0
  37. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/__init__.py +0 -0
  38. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/__init__.py +0 -0
  39. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/admin.py +0 -0
  40. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/check.py +0 -0
  41. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/__init__.py +0 -0
  42. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/build.py +0 -0
  43. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/dev.py +0 -0
  44. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/docs.py +0 -0
  45. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/lint.py +0 -0
  46. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/migrate.py +0 -0
  47. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/new.py +0 -0
  48. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/serve.py +0 -0
  49. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/commands/test.py +0 -0
  50. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/deploy.py +0 -0
  51. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/hot_reload.py +0 -0
  52. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/jobs.py +0 -0
  53. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/mq.py +0 -0
  54. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/pkg.py +0 -0
  55. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/server_process.py +0 -0
  56. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/cli/utils.py +0 -0
  57. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/__init__.py +0 -0
  58. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/base_generator.py +0 -0
  59. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/cli.py +0 -0
  60. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/expression_transformer.py +0 -0
  61. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/javascript/__init__.py +0 -0
  62. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/javascript/generator.py +0 -0
  63. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/optimizer.py +0 -0
  64. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/python/__init__.py +0 -0
  65. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/python/generator.py +0 -0
  66. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/python/runtime.py +0 -0
  67. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/compiler/transpiler.py +0 -0
  68. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/__init__.py +0 -0
  69. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/config_env.py +0 -0
  70. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/docs_links.py +0 -0
  71. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/expression_diagnostics.py +0 -0
  72. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/expressions.py +0 -0
  73. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/agents/src/__init__.py +0 -0
  74. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
  75. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
  76. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/data_import/src/__init__.py +0 -0
  77. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/data_import/src/ast_node.py +0 -0
  78. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/data_import/src/runtime.py +0 -0
  79. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/dump/src/__init__.py +0 -0
  80. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/dump/src/ast_node.py +0 -0
  81. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/dump/src/parser.py +0 -0
  82. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/dump/src/runtime.py +0 -0
  83. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/functions/src/__init__.py +0 -0
  84. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/functions/src/ast_node.py +0 -0
  85. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
  86. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
  87. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
  88. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/invocation/src/__init__.py +0 -0
  89. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/invocation/src/ast_node.py +0 -0
  90. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/invocation/src/runtime.py +0 -0
  91. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
  92. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
  93. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/logging/src/__init__.py +0 -0
  94. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/logging/src/ast_node.py +0 -0
  95. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/logging/src/parser.py +0 -0
  96. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/logging/src/runtime.py +0 -0
  97. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/loops/src/__init__.py +0 -0
  98. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/loops/src/ast_node.py +0 -0
  99. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/query/src/__init__.py +0 -0
  100. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/query/src/query_validators.py +0 -0
  101. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/state_management/src/__init__.py +0 -0
  102. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
  103. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
  104. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
  105. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
  106. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
  107. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
  108. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
  109. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/theming/src/__init__.py +0 -0
  110. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/theming/src/ast_node.py +0 -0
  111. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/theming/src/presets.py +0 -0
  112. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
  113. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/websocket/src/__init__.py +0 -0
  114. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
  115. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/guards.py +0 -0
  116. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/html_compat.py +0 -0
  117. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parser_registry.py +0 -0
  118. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/__init__.py +0 -0
  119. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/__init__.py +0 -0
  120. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/team_parser.py +0 -0
  121. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/base.py +0 -0
  122. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/composition/__init__.py +0 -0
  123. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/composition/import_parser.py +0 -0
  124. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
  125. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
  126. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/control_flow/if_parser.py +0 -0
  127. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
  128. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
  129. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/__init__.py +0 -0
  130. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/data_parser.py +0 -0
  131. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/invoke_parser.py +0 -0
  132. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/transaction_parser.py +0 -0
  133. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/events/__init__.py +0 -0
  134. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
  135. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/forms/__init__.py +0 -0
  136. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/forms/action_parser.py +0 -0
  137. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
  138. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
  139. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/functions/__init__.py +0 -0
  140. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/functions/function_parser.py +0 -0
  141. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/functions/param_parser.py +0 -0
  142. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/functions/return_parser.py +0 -0
  143. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/html/__init__.py +0 -0
  144. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
  145. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/html/html_parser.py +0 -0
  146. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/jobs/__init__.py +0 -0
  147. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
  148. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
  149. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
  150. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/__init__.py +0 -0
  151. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
  152. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
  153. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
  154. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
  155. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
  156. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/routing/__init__.py +0 -0
  157. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/routing/route_parser.py +0 -0
  158. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/__init__.py +0 -0
  159. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
  160. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
  161. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
  162. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
  163. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/__init__.py +0 -0
  164. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/dump_parser.py +0 -0
  165. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/file_parser.py +0 -0
  166. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/log_parser.py +0 -0
  167. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/mail_parser.py +0 -0
  168. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/tiers.py +0 -0
  169. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/core/xml_lines.py +0 -0
  170. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/packages/__init__.py +0 -0
  171. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/packages/manager.py +0 -0
  172. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/packages/manifest.py +0 -0
  173. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/packages/registry.py +0 -0
  174. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/packages/resolver.py +0 -0
  175. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/plugins/__init__.py +0 -0
  176. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/plugins/hooks.py +0 -0
  177. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/plugins/loader.py +0 -0
  178. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/plugins/manifest.py +0 -0
  179. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/plugins/registry.py +0 -0
  180. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/__init__.py +0 -0
  181. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/__init__.py +0 -0
  182. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
  183. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
  184. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
  185. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
  186. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ast_cache.py +0 -0
  187. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/auth_service.py +0 -0
  188. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/component_composer.py +0 -0
  189. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/component_resolver.py +0 -0
  190. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/conditions.py +0 -0
  191. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/db_schema.py +0 -0
  192. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/dev_panel.py +0 -0
  193. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/email_service.py +0 -0
  194. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/error_handler.py +0 -0
  195. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/execution_context.py +0 -0
  196. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executor_registry.py +0 -0
  197. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/__init__.py +0 -0
  198. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/__init__.py +0 -0
  199. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
  200. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/base.py +0 -0
  201. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/__init__.py +0 -0
  202. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/if_executor.py +0 -0
  203. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/loop_executor.py +0 -0
  204. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/redirect_executor.py +0 -0
  205. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/set_executor.py +0 -0
  206. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/__init__.py +0 -0
  207. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/data_executor.py +0 -0
  208. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/invoke_executor.py +0 -0
  209. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
  210. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
  211. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
  212. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
  213. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
  214. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
  215. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
  216. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
  217. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
  218. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
  219. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
  220. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
  221. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
  222. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
  223. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/__init__.py +0 -0
  224. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
  225. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/file_executor.py +0 -0
  226. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/log_executor.py +0 -0
  227. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
  228. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/expression_cache.py +0 -0
  229. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/file_upload_service.py +0 -0
  230. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/function_registry.py +0 -0
  231. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/game_builder.py +0 -0
  232. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/game_code_generator.py +0 -0
  233. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/game_templates.py +0 -0
  234. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/godot_code_generator.py +0 -0
  235. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/godot_templates.py +0 -0
  236. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/job_executor.py +0 -0
  237. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/llm_cache.py +0 -0
  238. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/llm_service.py +0 -0
  239. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/local_server.py +0 -0
  240. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/logging_setup.py +0 -0
  241. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/message_broker.py +0 -0
  242. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/message_queue_service.py +0 -0
  243. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/param_validation.py +0 -0
  244. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/pypy_compat.py +0 -0
  245. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/python_bridge.py +0 -0
  246. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/query_validators.py +0 -0
  247. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/renderer.py +0 -0
  248. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/service_container.py +0 -0
  249. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/table_params.py +0 -0
  250. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/terminal_builder.py +0 -0
  251. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/terminal_code_generator.py +0 -0
  252. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/terminal_templates.py +0 -0
  253. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/testing_builder.py +0 -0
  254. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/testing_code_generator.py +0 -0
  255. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/testing_templates.py +0 -0
  256. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_builder.py +0 -0
  257. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_desktop.py +0 -0
  258. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_form_rules.py +0 -0
  259. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_html_adapter.py +0 -0
  260. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_html_templates.py +0 -0
  261. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
  262. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_mobile_templates.py +0 -0
  263. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_pager.py +0 -0
  264. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_table_edit.py +0 -0
  265. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_textual_adapter.py +0 -0
  266. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_textual_templates.py +0 -0
  267. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_tokens.py +0 -0
  268. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/ui_validator.py +0 -0
  269. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/validators.py +0 -0
  270. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/websocket_adapter.py +0 -0
  271. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/websocket_service.py +0 -0
  272. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/websocket_transport.py +0 -0
  273. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/runtime/wsgi.py +0 -0
  274. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/services.py +0 -0
  275. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum/utils/__init__.py +0 -0
  276. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/__init__.py +0 -0
  277. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/assets/quantum-admin-connectors.js +0 -0
  278. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/assets/quantum-admin.css +0 -0
  279. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/assets/quantum-source-highlight.js +0 -0
  280. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/__init__.py +0 -0
  281. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/audit_service.py +0 -0
  282. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/auth_service.py +0 -0
  283. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/component_discovery.py +0 -0
  284. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/config_generator.py +0 -0
  285. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/connector_service.py +0 -0
  286. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/crud.py +0 -0
  287. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/database.py +0 -0
  288. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/db_setup_service.py +0 -0
  289. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/deploy_service.py +0 -0
  290. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/docker_service.py +0 -0
  291. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/environment_service.py +0 -0
  292. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/git_service.py +0 -0
  293. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/health_service.py +0 -0
  294. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/job_service.py +0 -0
  295. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/log_service.py +0 -0
  296. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/main.py +0 -0
  297. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/models.py +0 -0
  298. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/pipeline_service.py +0 -0
  299. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/resource_manager.py +0 -0
  300. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/schemas.py +0 -0
  301. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/secret_manager.py +0 -0
  302. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/settings_service.py +0 -0
  303. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/template_service.py +0 -0
  304. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/test_execution_service.py +0 -0
  305. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/test_generator.py +0 -0
  306. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/webhook_service.py +0 -0
  307. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/backend/websocket_manager.py +0 -0
  308. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/_layout/AdminShell.q +0 -0
  309. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/agents.q +0 -0
  310. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/app/[name].q +0 -0
  311. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/applications.q +0 -0
  312. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/component/[...path].q +0 -0
  313. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/components.q +0 -0
  314. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/connectors.q +0 -0
  315. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/dashboard.q +0 -0
  316. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/database.q +0 -0
  317. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/features.q +0 -0
  318. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/index.q +0 -0
  319. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/jobs.q +0 -0
  320. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/login.q +0 -0
  321. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/logout.q +0 -0
  322. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/settings.q +0 -0
  323. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/source.q +0 -0
  324. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/tests.q +0 -0
  325. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/run.py +0 -0
  326. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/__init__.py +0 -0
  327. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/_base.py +0 -0
  328. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/_component_test_generator.py +0 -0
  329. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/_yaml_editor.py +0 -0
  330. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/apps.py +0 -0
  331. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/auth.py +0 -0
  332. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/components.py +0 -0
  333. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/connectors.py +0 -0
  334. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/projects.py +0 -0
  335. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/repository.py +0 -0
  336. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/settings.py +0 -0
  337. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_admin/services/yaml_import.py +0 -0
  338. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
  339. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/entry_points.txt +0 -0
  340. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/requires.txt +0 -0
  341. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/top_level.txt +0 -0
  342. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/setup.cfg +0 -0
  343. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_animations.py +0 -0
  344. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_ast_cache.py +0 -0
  345. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_cli_commands.py +0 -0
  346. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_component_library.py +0 -0
  347. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_expression_cache.py +0 -0
  348. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_form_validation.py +0 -0
  349. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_game_codegen.py +0 -0
  350. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_game_e2e.py +0 -0
  351. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_game_engine.py +0 -0
  352. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_game_parser.py +0 -0
  353. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_godot_codegen.py +0 -0
  354. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_job_executor.py +0 -0
  355. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_llm_providers.py +0 -0
  356. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_message_broker.py +0 -0
  357. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_message_queue.py +0 -0
  358. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_mobile_target.py +0 -0
  359. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_multi_agent.py +0 -0
  360. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_packages.py +0 -0
  361. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_plugins.py +0 -0
  362. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_pypy_compat.py +0 -0
  363. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_python_scripting.py +0 -0
  364. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_terminal_codegen.py +0 -0
  365. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_terminal_parser.py +0 -0
  366. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_testing_codegen.py +0 -0
  367. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_testing_parser.py +0 -0
  368. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_theming.py +0 -0
  369. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_ui_html_adapter.py +0 -0
  370. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_ui_parity.py +0 -0
  371. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_ui_parser.py +0 -0
  372. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_ui_textual_adapter.py +0 -0
  373. {quantum_framework-0.18.0 → quantum_framework-0.20.0}/tests/test_websocket.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quantum-framework
3
- Version: 0.18.0
3
+ Version: 0.20.0
4
4
  Summary: Full-stack declarative framework for building web, desktop, and mobile applications
5
5
  Author-email: Daniel Gregorio <danielgregorio2611@gmail.com>
6
6
  License: MIT
@@ -94,7 +94,7 @@ and asks the model — in the markup. There is no Python file behind it.
94
94
  An agent with its own tools, same idea:
95
95
 
96
96
  ```xml
97
- <q:agent name="calc" model="phi3" max_iterations="4">
97
+ <q:agent name="calc" model="phi3" maxIterations="4">
98
98
  <q:instruction>Use the add tool, then answer.</q:instruction>
99
99
 
100
100
  <q:tool name="add" description="Add two numbers">
@@ -44,7 +44,7 @@ and asks the model — in the markup. There is no Python file behind it.
44
44
  An agent with its own tools, same idea:
45
45
 
46
46
  ```xml
47
- <q:agent name="calc" model="phi3" max_iterations="4">
47
+ <q:agent name="calc" model="phi3" maxIterations="4">
48
48
  <q:instruction>Use the add tool, then answer.</q:instruction>
49
49
 
50
50
  <q:tool name="add" description="Add two numbers">
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "quantum-framework"
7
- version = "0.18.0"
7
+ version = "0.20.0"
8
8
  description = "Full-stack declarative framework for building web, desktop, and mobile applications"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -399,6 +399,15 @@ Examples:
399
399
  migrate_create_parser = migrate_subparsers.add_parser('create', help='Create a new migration')
400
400
  migrate_create_parser.add_argument('name', help='Migration name')
401
401
 
402
+ # M6 (DB-10): schema.sql as the source of truth
403
+ migrate_plan_parser = migrate_subparsers.add_parser(
404
+ 'plan', help='Compare schema.sql with the migrations and plan the migration')
405
+ migrate_plan_parser.add_argument('--schema', default='schema.sql', help='Schema file (default: schema.sql)')
406
+ migrate_plan_parser.add_argument('--write', metavar='NAME', help='Save the plan as migration V00N_NAME')
407
+ migrate_plan_parser.add_argument('--yes', action='store_true', help='Do not ask before writing')
408
+ migrate_plan_parser.add_argument('--allow-data-loss', action='store_true',
409
+ help='Write a plan that drops tables or columns')
410
+
402
411
  # Parse arguments
403
412
  args = parser.parse_args()
404
413
 
@@ -541,13 +550,18 @@ Examples:
541
550
  if failed:
542
551
  sys.exit(1)
543
552
 
553
+ elif args.migrate_command == 'plan':
554
+ from quantum.cli.schema_plan import run_plan
555
+ sys.exit(run_plan(Path.cwd(), args.schema, args.write, args.yes, args.allow_data_loss,
556
+ interactive=sys.stdin.isatty()))
557
+
544
558
  elif args.migrate_command == 'create':
545
559
  filepath = runner.create(args.name)
546
560
  print(f"Created migration: {filepath}")
547
561
  print(f"Also created rollback: {filepath.with_suffix('.down.sql')}")
548
562
 
549
563
  else:
550
- print("Usage: quantum migrate <status|up|down|create> [options]")
564
+ print("Usage: quantum migrate <status|up|down|create|plan> [options]")
551
565
  sys.exit(1)
552
566
 
553
567
  sys.exit(0)
@@ -0,0 +1,273 @@
1
+ """`quantum migrate plan`: a declarative schema (M6, DB-10).
2
+
3
+ You edit `schema.sql` — the tables as they should be. `quantum migrate plan`
4
+ compares it with the schema the migrations produce, shows what changes, marks
5
+ what loses data, and `--write` saves the migration (and its rollback) after
6
+ confirmation. `quantum migrate up` applies it as any other.
7
+
8
+ Both sides are built in in-memory SQLite databases — the migrations applied in
9
+ order, and schema.sql — and compared with the schema reader of `quantum check`
10
+ (quantum/runtime/db_schema.py). The development database is not read: the
11
+ plan is the same on any machine and in CI.
12
+
13
+ SQLite can add a column but not change one, so any other change to a table
14
+ rebuilds it: create the new table, copy the columns both have, drop the old,
15
+ rename. Before a plan is written it is checked: applied to the migrations'
16
+ schema it must produce exactly schema.sql's.
17
+ """
18
+
19
+ import re
20
+ import sqlite3
21
+ from dataclasses import dataclass, field
22
+ from pathlib import Path
23
+ from typing import Dict, List, Optional, Tuple
24
+
25
+ from quantum.runtime.db_schema import Column, read_schema
26
+
27
+ # Tables that belong to Quantum, not to the app's schema.
28
+ INTERNAL_TABLES = {'_migrations', 'quantum_history'}
29
+
30
+
31
+ class SchemaPlanError(Exception):
32
+ """The plan cannot be made (no schema.sql, invalid SQL, a change it cannot express)."""
33
+
34
+
35
+ @dataclass
36
+ class Step:
37
+ kind: str # add-table, drop-table, add-column, rebuild, add-index, drop-index
38
+ target: str # "posts" or "posts.summary"
39
+ detail: str = ''
40
+ sql: List[str] = field(default_factory=list)
41
+ loses_data: bool = False
42
+ warning: str = ''
43
+
44
+ def line(self) -> str:
45
+ mark = {'add-table': '+', 'add-column': '+', 'add-index': '+', 'drop-table': '-',
46
+ 'drop-index': '-', 'rebuild': '~'}.get(self.kind, '?')
47
+ text = f'{mark} {self.kind.replace("-", " ")} {self.target}'
48
+ if self.detail:
49
+ text += f' ({self.detail})'
50
+ if self.loses_data:
51
+ text += ' !! loses data'
52
+ if self.warning:
53
+ text += f' !! {self.warning}'
54
+ return text
55
+
56
+
57
+ def _database(scripts: List[Tuple[str, str]]) -> sqlite3.Connection:
58
+ conn = sqlite3.connect(':memory:')
59
+ for origin, sql in scripts:
60
+ try:
61
+ conn.executescript(sql)
62
+ except sqlite3.Error as exc:
63
+ raise SchemaPlanError(f'{origin}: {exc}') from exc
64
+ return conn
65
+
66
+
67
+ def _migration_scripts(migrations_dir: Path) -> List[Tuple[str, str]]:
68
+ if not migrations_dir.exists():
69
+ return []
70
+ files = [f for f in sorted(migrations_dir.glob('*.sql')) if not f.name.endswith('.down.sql')]
71
+ return [(f.name, f.read_text(encoding='utf-8')) for f in files if '_' in f.stem]
72
+
73
+
74
+ def _tables(conn) -> Dict[str, object]:
75
+ return {name: t for name, t in read_schema(conn).items() if name not in INTERNAL_TABLES}
76
+
77
+
78
+ def _create_sql(conn, table: str) -> str:
79
+ return conn.execute("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ?",
80
+ (table,)).fetchone()[0]
81
+
82
+
83
+ def _indexes(conn) -> Dict[str, Tuple[str, str]]:
84
+ """Explicit indexes: name -> (table, sql)."""
85
+ return {name: (table, sql) for name, table, sql in conn.execute(
86
+ "SELECT name, tbl_name, sql FROM sqlite_master WHERE type = 'index' AND sql IS NOT NULL")
87
+ if table not in INTERNAL_TABLES}
88
+
89
+
90
+ def _column_sql(column: Column) -> str:
91
+ parts = [f'"{column.name}"']
92
+ if column.type:
93
+ parts.append(column.type)
94
+ if column.not_null:
95
+ parts.append('NOT NULL')
96
+ if column.default is not None:
97
+ parts.append(f'DEFAULT {column.default}')
98
+ if column.options:
99
+ options = ', '.join("'" + o.replace("'", "''") + "'" for o in column.options)
100
+ parts.append(f'CHECK ("{column.name}" IN ({options}))')
101
+ if column.references:
102
+ parts.append(f'REFERENCES "{column.references[0]}"("{column.references[1]}")')
103
+ return ' '.join(parts)
104
+
105
+
106
+ def _differences(a: Column, b: Column) -> List[str]:
107
+ changed = []
108
+ for label, x, y in (('type', (a.type or '').upper(), (b.type or '').upper()),
109
+ ('NOT NULL', a.not_null, b.not_null), ('default', a.default, b.default),
110
+ ('primary key', a.primary_key, b.primary_key), ('CHECK', a.options, b.options),
111
+ ('references', a.references, b.references)):
112
+ if x != y:
113
+ changed.append(label)
114
+ return changed
115
+
116
+
117
+ def diff(current_db, desired_db) -> List[Step]:
118
+ """The steps that turn the first database's schema into the second's."""
119
+ current, desired = _tables(current_db), _tables(desired_db)
120
+ steps: List[Step] = []
121
+ rebuilt = set()
122
+
123
+ for name in sorted(set(desired) - set(current)):
124
+ steps.append(Step('add-table', name, sql=[_create_sql(desired_db, name) + ';']))
125
+
126
+ for name in sorted(set(current) & set(desired)):
127
+ before, after = current[name].columns, desired[name].columns
128
+ added = [c for c in after if c not in before]
129
+ removed = [c for c in before if c not in after]
130
+ changed = {c: _differences(before[c], after[c]) for c in before
131
+ if c in after and _differences(before[c], after[c])}
132
+ addable = all(not after[c].primary_key and (not after[c].not_null or after[c].default is not None)
133
+ for c in added)
134
+ if not removed and not changed and addable:
135
+ for c in added:
136
+ steps.append(Step('add-column', f'{name}.{c}', after[c].type or '',
137
+ [f'ALTER TABLE "{name}" ADD COLUMN {_column_sql(after[c])};']))
138
+ continue
139
+ if not (added or removed or changed):
140
+ continue
141
+ common = ', '.join(f'"{c}"' for c in after if c in before)
142
+ create = re.sub(r'^\s*CREATE\s+TABLE\s+("?)' + re.escape(name) + r'\1',
143
+ f'CREATE TABLE "{name}__new"', _create_sql(desired_db, name), count=1, flags=re.I)
144
+ details = [f'+ {c}' for c in added] + [f'- {c}' for c in removed] + \
145
+ [f'{c}: {", ".join(d)}' for c, d in changed.items()]
146
+ warning = ''
147
+ if any('NOT NULL' in d and after[c].not_null for c, d in changed.items()) or \
148
+ any(after[c].not_null and after[c].default is None for c in added):
149
+ warning = 'fails if existing rows have no value for a NOT NULL column'
150
+ steps.append(Step('rebuild', name, '; '.join(details), [
151
+ create + ';',
152
+ f'INSERT INTO "{name}__new" ({common}) SELECT {common} FROM "{name}";',
153
+ f'DROP TABLE "{name}";',
154
+ f'ALTER TABLE "{name}__new" RENAME TO "{name}";',
155
+ ], loses_data=bool(removed) or any('type' in d for d in changed.values()), warning=warning))
156
+ rebuilt.add(name)
157
+
158
+ for name in sorted(set(current) - set(desired)):
159
+ steps.append(Step('drop-table', name, sql=[f'DROP TABLE "{name}";'], loses_data=True))
160
+
161
+ # Indexes: a rebuilt table lost its own, so they are created again.
162
+ before_idx, after_idx = _indexes(current_db), _indexes(desired_db)
163
+ for name, (table, sql) in sorted(before_idx.items()):
164
+ if table in desired and table not in rebuilt and after_idx.get(name, (None, None))[1] != sql:
165
+ steps.append(Step('drop-index', name, sql=[f'DROP INDEX "{name}";']))
166
+ for name, (table, sql) in sorted(after_idx.items()):
167
+ if before_idx.get(name, (None, None))[1] != sql or table in rebuilt:
168
+ steps.append(Step('add-index', name, sql=[sql + ';']))
169
+ return steps
170
+
171
+
172
+ def _schema_only(conn) -> str:
173
+ """The CREATE statements of a database, tables before indexes, without its rows."""
174
+ rows = conn.execute("SELECT type, sql FROM sqlite_master WHERE sql IS NOT NULL "
175
+ "AND name NOT LIKE 'sqlite_%' ORDER BY type = 'index', rowid").fetchall()
176
+ return '\n'.join(sql + ';' for _type, sql in rows)
177
+
178
+
179
+ def _signature(conn) -> tuple:
180
+ tables = _tables(conn)
181
+ return (tuple(sorted((n, tuple(sorted((c.name, (c.type or '').upper(), c.not_null, c.default, c.primary_key,
182
+ tuple(c.options or ()), c.references)
183
+ for c in t.columns.values()))) for n, t in tables.items())),
184
+ tuple(sorted((n, s) for n, (_t, s) in _indexes(conn).items())))
185
+
186
+
187
+ @dataclass
188
+ class Plan:
189
+ steps: List[Step]
190
+ up_sql: str
191
+ down_sql: str
192
+
193
+ @property
194
+ def loses_data(self) -> bool:
195
+ return any(s.loses_data for s in self.steps)
196
+
197
+
198
+ def make_plan(schema_file: Path, migrations_dir: Path) -> Plan:
199
+ if not schema_file.exists():
200
+ raise SchemaPlanError(f'{schema_file} does not exist: write the tables as they should be, '
201
+ f'e.g. CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT NOT NULL);')
202
+ migrations = _migration_scripts(migrations_dir)
203
+ current = _database(migrations)
204
+ desired = _database([(schema_file.name, schema_file.read_text(encoding='utf-8'))])
205
+ steps = diff(current, desired)
206
+ up = '\n'.join(s for step in steps for s in step.sql)
207
+ down = '\n'.join(s for step in diff(desired, current) for s in step.sql)
208
+ if steps:
209
+ # The plan must do what it says: applied, it yields schema.sql's schema.
210
+ # Checked on the structure only — what existing rows may break is the
211
+ # step's warning, shown in the plan, not a reason to hide the plan.
212
+ check = _database([('migrations (schema only)', _schema_only(current)), ('plan', up)])
213
+ if _signature(check) != _signature(desired):
214
+ raise SchemaPlanError('the generated migration does not reproduce schema.sql — '
215
+ 'write this change as a migration by hand (quantum migrate create)')
216
+ return Plan(steps, up, down)
217
+
218
+
219
+ def next_version(migrations_dir: Path) -> str:
220
+ numbers = [int(m.group(1)) for f in migrations_dir.glob('V*.sql')
221
+ for m in [re.match(r'V(\d+)_', f.name)] if m]
222
+ return f'V{(max(numbers) + 1 if numbers else 1):03d}'
223
+
224
+
225
+ def write_plan(plan: Plan, migrations_dir: Path, name: str) -> Path:
226
+ migrations_dir.mkdir(parents=True, exist_ok=True)
227
+ slug = re.sub(r'[^a-z0-9_]+', '_', name.lower()).strip('_') or 'schema'
228
+ version = next_version(migrations_dir)
229
+ path = migrations_dir / f'{version}_{slug}.sql'
230
+ header = '-- Generated by `quantum migrate plan` from schema.sql (DB-10)\n'
231
+ summary = '\n'.join(f'-- {s.line()}' for s in plan.steps)
232
+ path.write_text(f'{header}{summary}\n\n{plan.up_sql}\n', encoding='utf-8')
233
+ (migrations_dir / f'{version}_{slug}.down.sql').write_text(
234
+ f'{header}-- rollback\n\n{plan.down_sql}\n', encoding='utf-8')
235
+ return path
236
+
237
+
238
+ def run_plan(project: Path, schema: Optional[str], write: Optional[str], yes: bool,
239
+ allow_data_loss: bool, ask=input, interactive: bool = True) -> int:
240
+ """The `quantum migrate plan` command. Returns the exit code."""
241
+ from quantum.cli.commands.migrate import _paths_migrations
242
+ migrations_dir = project / _paths_migrations(project)
243
+ schema_file = project / (schema or 'schema.sql')
244
+ try:
245
+ plan = make_plan(schema_file, migrations_dir)
246
+ except SchemaPlanError as exc:
247
+ print(f'[ERROR] {exc}')
248
+ return 1
249
+ if not plan.steps:
250
+ print(f'Nothing to do: the migrations already produce {schema_file.name}.')
251
+ return 0
252
+ print(f'Plan: {schema_file.name} vs. the migrations in {migrations_dir.name}/\n')
253
+ for step in plan.steps:
254
+ print(' ' + step.line())
255
+ print('\nSQL:\n' + plan.up_sql)
256
+ if not write:
257
+ print('\nNothing written. `quantum migrate plan --write <name>` saves it as a migration.')
258
+ return 0
259
+ if plan.loses_data and not allow_data_loss:
260
+ print('\n[REFUSED] This plan loses data (marked !!). Check it, then run again with '
261
+ '--allow-data-loss.')
262
+ return 1
263
+ if not yes:
264
+ if not interactive:
265
+ print('\n[REFUSED] Confirm with --yes (not a terminal: nobody to ask).')
266
+ return 1
267
+ answer = ask(f'\nWrite migration {next_version(migrations_dir)}_{write}? [y/N] ').strip().lower()
268
+ if answer not in ('y', 'yes'):
269
+ print('Nothing written.')
270
+ return 1
271
+ path = write_plan(plan, migrations_dir, write)
272
+ print(f'Written: {path} (and {path.stem}.down.sql). Apply with `quantum migrate up`.')
273
+ return 0
@@ -699,6 +699,7 @@ class QueryNode(QuantumNode):
699
699
  self.paginate = False # Enable automatic pagination
700
700
  self.page = None
701
701
  self.page_expr = None # DB-9: page as an expression (default {query.page})
702
+ self.on_error = 'fail' # IA-5: 'continue' hands a failure to <name>_result
702
703
  self.sortable = False # UI-13: ORDER BY the URL's ?sort=/&dir=
703
704
  self.page_size = 20
704
705
  self.timeout = None
@@ -1478,6 +1479,10 @@ class LLMNode(QuantumNode):
1478
1479
  self.prompt = None # Prompt text (with databinding)
1479
1480
  self.system = None # System prompt (optional)
1480
1481
  self.messages: List[LLMMessageNode] = [] # Chat mode messages
1482
+ self.on_error = 'fail' # IA-5: 'continue' hands a failure to <name>_result
1483
+ self.knowledge = None # IA-6: answer from this knowledge base, citing sources
1484
+ self.top = 4 # IA-6: how many sources to retrieve
1485
+ self.stream = False # IA-7: stream the answer to <ui:stream> (web requests)
1481
1486
  self.temperature = None # 0.0-2.0
1482
1487
  self.max_tokens = None # Token limit
1483
1488
  self.response_format = None # "text" or "json"
@@ -172,6 +172,12 @@ def verify_password(password: Any, hashed: Any) -> bool:
172
172
  return False
173
173
 
174
174
 
175
+ def _urlencode(v) -> str:
176
+ """EXPR-12: a value made safe to put in a URL — `q:redirect url="/?q={urlencode(q)}"`."""
177
+ from urllib.parse import quote_plus
178
+ return quote_plus('' if v is None else str(v))
179
+
180
+
175
181
  STDLIB = {
176
182
  # passwords
177
183
  'hashPassword': hash_password,
@@ -189,6 +195,7 @@ STDLIB = {
189
195
  'split': _split,
190
196
  'contains': _contains,
191
197
  'slugify': _slugify,
198
+ 'urlencode': _urlencode,
192
199
  # collections
193
200
  'len': _len,
194
201
  'first': _first,
@@ -290,7 +290,7 @@ class AgentNode(QuantumNode):
290
290
 
291
291
  Example:
292
292
  <!-- Using Ollama (default) -->
293
- <q:agent name="support" model="phi3" max_iterations="5">
293
+ <q:agent name="support" model="phi3" maxIterations="5">
294
294
  ...
295
295
  </q:agent>
296
296
 
@@ -319,6 +319,7 @@ class AgentNode(QuantumNode):
319
319
  api_key: str = "" # For cloud providers
320
320
  max_iterations: int = 10
321
321
  timeout: int = 60000 # milliseconds
322
+ on_error: str = "fail" # IA-5: "continue" hands a failure to the page
322
323
 
323
324
  instruction: Optional[AgentInstructionNode] = None
324
325
  tools: List[AgentToolNode] = field(default_factory=list)
@@ -82,6 +82,7 @@ class KnowledgeNode(QuantumNode):
82
82
  persist: bool = False # Persist ChromaDB to disk
83
83
  persist_path: Optional[str] = None # ChromaDB persistence path
84
84
  rebuild: bool = False # Force reindex
85
+ on_error: str = 'fail' # IA-5: 'continue' hands a failure to <name>_info
85
86
 
86
87
  def add_source(self, source: KnowledgeSourceNode):
87
88
  self.sources.append(source)
@@ -1896,6 +1896,39 @@ class UIPagerNode(QuantumNode, UILayoutMixin):
1896
1896
  return [] if self.for_query else ["ui:pager needs for="]
1897
1897
 
1898
1898
 
1899
+ class UIStreamNode(QuantumNode, UILayoutMixin):
1900
+ """<ui:stream for="answer"> — a q:llm answer, arriving as it is written (IA-7)."""
1901
+
1902
+ def __init__(self):
1903
+ self.for_llm: Optional[str] = None
1904
+ self.children: List[QuantumNode] = []
1905
+ self._init_layout_attrs()
1906
+
1907
+ def to_dict(self) -> Dict[str, Any]:
1908
+ return {"type": "ui_stream", "for": self.for_llm}
1909
+
1910
+ def validate(self) -> List[str]:
1911
+ return []
1912
+
1913
+
1914
+ class UIHistoryNode(QuantumNode, UILayoutMixin):
1915
+ """<ui:history table="t" key="{row.id}" datasource="db"> — a row's change history (DB-11)."""
1916
+
1917
+ def __init__(self):
1918
+ self.table: Optional[str] = None
1919
+ self.key: Optional[str] = None
1920
+ self.datasource: Optional[str] = None
1921
+ self.limit: int = 50
1922
+ self.children: List[QuantumNode] = []
1923
+ self._init_layout_attrs()
1924
+
1925
+ def to_dict(self) -> Dict[str, Any]:
1926
+ return {"type": "ui_history", "table": self.table, "key": self.key}
1927
+
1928
+ def validate(self) -> List[str]:
1929
+ return []
1930
+
1931
+
1899
1932
  def is_ui_node(node) -> bool:
1900
1933
  """A ui:* element (UI-1): markup, drawn by a renderer, never executed."""
1901
1934
  return type(node).__module__ == __name__
@@ -1908,6 +1941,6 @@ CORE_TAGS = frozenset({
1908
1941
  'window', 'hbox', 'vbox', 'grid', 'panel', 'section', 'scrollbox', 'spacer', 'rule',
1909
1942
  'header', 'footer', 'card', 'card-header', 'card-body', 'card-footer', 'tabpanel', 'tab',
1910
1943
  'text', 'badge', 'alert', 'link', 'image', 'progress',
1911
- 'table', 'column', 'list', 'item', 'pager',
1944
+ 'table', 'column', 'list', 'item', 'pager', 'history', 'stream',
1912
1945
  'form', 'formitem', 'input', 'checkbox', 'switch', 'radio', 'select', 'option', 'button',
1913
1946
  })
@@ -28,7 +28,7 @@ from .ast_nodes import (
28
28
  UIToastNode, UIToastContainerNode,
29
29
  UICarouselNode, UISlideNode,
30
30
  UIStepperNode, UIStepNode,
31
- UICalendarNode, UIDatePickerNode, UIPagerNode,
31
+ UICalendarNode, UIDatePickerNode, UIPagerNode, UIHistoryNode, UIStreamNode,
32
32
  )
33
33
  from quantum.core.features.theming.src import UIThemeNode, UIColorNode
34
34
  from quantum.core.ast_nodes import TextNode
@@ -82,6 +82,8 @@ class UIParser:
82
82
  'table': '_parse_ui_table',
83
83
  'column': '_parse_ui_column',
84
84
  'pager': '_parse_ui_pager',
85
+ 'history': '_parse_ui_history',
86
+ 'stream': '_parse_ui_stream',
85
87
  'list': '_parse_ui_list',
86
88
  'item': '_parse_ui_item',
87
89
  'image': '_parse_ui_image',
@@ -550,6 +552,29 @@ class UIParser:
550
552
  self._apply_layout_attrs(node, element)
551
553
  return node
552
554
 
555
+ def _parse_ui_stream(self, element: ET.Element) -> UIStreamNode:
556
+ node = UIStreamNode()
557
+ node.for_llm = element.get('for')
558
+ if not node.for_llm:
559
+ raise UIParseError('<ui:stream> needs for="<the q:llm name>"')
560
+ self._apply_layout_attrs(node, element)
561
+ return node
562
+
563
+ def _parse_ui_history(self, element: ET.Element) -> UIHistoryNode:
564
+ node = UIHistoryNode()
565
+ node.table = element.get('table')
566
+ node.key = element.get('key')
567
+ node.datasource = element.get('datasource')
568
+ missing = [a for a in ('table', 'key', 'datasource') if not element.get(a)]
569
+ if missing:
570
+ raise UIParseError(f'<ui:history> needs {", ".join(a + "=" for a in missing)}')
571
+ limit = element.get('limit')
572
+ if limit is not None and not limit.isdigit():
573
+ raise UIParseError(f'<ui:history limit="{limit}">: a number of entries')
574
+ node.limit = int(limit) if limit is not None else 50
575
+ self._apply_layout_attrs(node, element)
576
+ return node
577
+
553
578
  def _parse_ui_list(self, element: ET.Element) -> UIListNode:
554
579
  node = UIListNode()
555
580
  node.source = element.get('source')
@@ -480,7 +480,13 @@ class QuantumParser:
480
480
  A direct else child of a q:if is already the nested form and stays
481
481
  where it is. An else with no q:if right before it is an error.
482
482
  """
483
- for parent in list(root.iter()):
483
+ parents = list(root.iter())
484
+ # IF-4 looks at the else children as written: before any sibling-form
485
+ # else is moved into its q:if below (which is not ambiguous).
486
+ for parent in parents:
487
+ if self._get_element_name(parent) == 'if':
488
+ self._check_dangling_else(parent)
489
+ for parent in parents:
484
490
  if self._get_element_name(parent) == 'if':
485
491
  continue
486
492
  anterior = None
@@ -502,6 +508,27 @@ class QuantumParser:
502
508
  continue
503
509
  anterior = child if nome == 'if' and self._is_quantum_tag(child) else None
504
510
 
511
+ def _check_dangling_else(self, if_element: ET.Element) -> None:
512
+ """IF-4: inside a q:if, a q:else/q:elseif right after an inner </q:if> is ambiguous.
513
+
514
+ IF-1 gives it to the outer q:if (a direct child belongs to its if), but
515
+ written right after an inner if it reads as that if's else — the
516
+ sibling form. A page built that way drew the else branch of the inner
517
+ if when the OUTER condition was false. Refused, with both fixes."""
518
+ previous = None
519
+ for child in if_element:
520
+ if not isinstance(child.tag, str):
521
+ continue
522
+ name = self._get_element_name(child)
523
+ if (name in ('else', 'elseif') and self._is_quantum_tag(child) and previous is not None):
524
+ error = QuantumParseError(
525
+ f"<q:{name}> right after an inner </q:if>, inside another <q:if>, is ambiguous "
526
+ f"(IF-4): it would belong to the OUTER q:if (IF-1). If it is the inner q:if's, "
527
+ f"write it inside that q:if; if it is the outer's, put it before the inner q:if.")
528
+ error.line = getattr(child, 'sourceline', None)
529
+ raise error
530
+ previous = child if name == 'if' and self._is_quantum_tag(child) else None
531
+
505
532
  # Tags that existed and were removed, with what to use instead.
506
533
  REMOVED_TAGS = {
507
534
  'persist': ('was removed in Quantum 0.16 (SET-2): it kept variables in the browser, '
@@ -42,6 +42,15 @@ class AgentParser(BaseTagParser):
42
42
 
43
43
  if not name:
44
44
  raise ParserError("Agent requires 'name' attribute")
45
+ if element.get('max_iterations') is not None:
46
+ # The guide and the README wrote max_iterations; it was never read
47
+ # and every agent ran up to 10 steps.
48
+ raise ParserError(f'<q:agent name="{name}">: the attribute is maxIterations, '
49
+ f'not max_iterations')
50
+ on_error = self.get_attr(element, 'onerror', 'fail')
51
+ if on_error not in ('fail', 'continue'):
52
+ raise ParserError(f'<q:agent name="{name}"> onerror must be "fail" or "continue", '
53
+ f'not "{on_error}"')
45
54
 
46
55
  agent_node = AgentNode(
47
56
  name=name,
@@ -50,7 +59,8 @@ class AgentParser(BaseTagParser):
50
59
  provider=self.get_attr(element, 'provider', 'auto'),
51
60
  api_key=self.get_attr(element, 'apiKey', ''),
52
61
  max_iterations=self.get_int_attr(element, 'maxIterations', 10),
53
- timeout=self.get_int_attr(element, 'timeout', 60000)
62
+ timeout=self.get_int_attr(element, 'timeout', 60000),
63
+ on_error=on_error,
54
64
  )
55
65
 
56
66
  # Parse children
@@ -52,6 +52,11 @@ class KnowledgeParser(BaseTagParser):
52
52
  persist_path=self.get_attr(element, 'persistPath', './.quantum/knowledge'),
53
53
  rebuild=self.get_bool_attr(element, 'rebuild', False)
54
54
  )
55
+ # IA-5: a base that cannot be built stops the page unless the page handles it.
56
+ knowledge_node.on_error = self.get_attr(element, 'onerror', 'fail')
57
+ if knowledge_node.on_error not in ('fail', 'continue'):
58
+ raise ParserError(f'<q:knowledge name="{name}"> onerror must be "fail" or "continue", '
59
+ f'not "{knowledge_node.on_error}"')
55
60
 
56
61
  # Parse sources
57
62
  for child in element:
@@ -66,6 +71,11 @@ class KnowledgeParser(BaseTagParser):
66
71
  def _parse_source(self, element: ET.Element) -> KnowledgeSourceNode:
67
72
  """Parse q:source within q:knowledge."""
68
73
  source_type = self.get_attr(element, 'type', 'text')
74
+ # IA-8: type="url" was accepted and read nothing (a log line said "not
75
+ # yet implemented"); an unknown type read nothing at all.
76
+ if source_type not in ('text', 'file', 'directory', 'query'):
77
+ raise ParserError(f'<q:source type="{source_type}">: use text, file, directory or query'
78
+ + (' (url is not supported)' if source_type == 'url' else ''))
69
79
 
70
80
  source = KnowledgeSourceNode(
71
81
  source_type=source_type,
@@ -60,7 +60,21 @@ class LLMParser(BaseTagParser):
60
60
  # same attributes now work on both tags.
61
61
  llm_node.provider = self.get_attr(element, 'provider')
62
62
  llm_node.api_key = self.get_attr(element, 'apiKey')
63
- llm_node.timeout = self.get_int_attr(element, 'timeout', 30)
63
+ # Seconds to wait for the model; undeclared, the service default (60).
64
+ llm_node.timeout = self.get_int_attr(element, 'timeout', 0) or None
65
+ # M5 (IA-6): answer from a knowledge base, citing its sources.
66
+ llm_node.knowledge = self.get_attr(element, 'knowledge')
67
+ top = self.get_attr(element, 'top')
68
+ if top is not None and not str(top).isdigit():
69
+ raise ParserError(f'<q:llm name="{name}" top="{top}">: the number of sources to retrieve')
70
+ llm_node.top = int(top) if top is not None else 4
71
+ # IA-7: in a web request, the answer arrives as it is written (<ui:stream>).
72
+ llm_node.stream = self.get_bool_attr(element, 'stream', False)
73
+ # IA-5: a failure stops the page unless the page handles it.
74
+ llm_node.on_error = self.get_attr(element, 'onerror', 'fail')
75
+ if llm_node.on_error not in ('fail', 'continue'):
76
+ raise ParserError(f'<q:llm name="{name}"> onerror must be "fail" or "continue", '
77
+ f'not "{llm_node.on_error}"')
64
78
 
65
79
  # Parse children
66
80
  for child in element:
@@ -108,6 +108,12 @@ class QueryParser(BaseTagParser):
108
108
  or None
109
109
  )
110
110
  query_node.result = self.get_attr(element, 'result')
111
+ # IA-5 / DB: onerror="continue" hands a failure to the page as <name>_result.
112
+ query_node.on_error = self.get_attr(element, 'onerror', 'fail')
113
+ if query_node.on_error not in ('fail', 'continue'):
114
+ from quantum.core.parser import QuantumParseError
115
+ raise QuantumParseError(f'<q:query name="{name}"> onerror must be "fail" or "continue", '
116
+ f'not "{query_node.on_error}"')
111
117
 
112
118
  # RAG/Knowledge attributes
113
119
  query_node.mode = self.get_attr(element, 'mode')
@@ -122,9 +122,12 @@ class ActionHandler:
122
122
  context.set_variable(key, value)
123
123
 
124
124
  # 4. Execute action body
125
+ from quantum.runtime import history
126
+ history_token = history.enter(getattr(action, 'name', None), context.session_vars) # M22
125
127
  try:
126
128
  redirect_info = self._execute_action_body(action, context)
127
129
  finally:
130
+ history.leave(history_token)
128
131
  # M12: the /_dev panel shows the action's variables (no-op off debug).
129
132
  from quantum.runtime.dev_panel import note_scopes
130
133
  note_scopes(context, 'action')