quantum-framework 0.19.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.19.0 → quantum_framework-0.20.0}/PKG-INFO +2 -2
  2. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/README.md +1 -1
  3. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/pyproject.toml +1 -1
  4. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/ast_nodes.py +5 -0
  5. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/expression_stdlib.py +7 -0
  6. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/agents/src/ast_node.py +2 -1
  7. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/knowledge_base/src/ast_node.py +1 -0
  8. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/ui_engine/src/ast_nodes.py +16 -1
  9. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/ui_engine/src/parser.py +10 -1
  10. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parser.py +28 -1
  11. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/agent_parser.py +11 -1
  12. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/knowledge_parser.py +10 -0
  13. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/llm_parser.py +15 -1
  14. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/query_parser.py +6 -0
  15. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/agent_service.py +8 -2
  16. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/component.py +4 -19
  17. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/agent_executor.py +11 -5
  18. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/knowledge_executor.py +6 -4
  19. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/llm_executor.py +98 -4
  20. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/query_executor.py +7 -0
  21. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/knowledge_service.py +40 -40
  22. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/llm_providers.py +60 -7
  23. quantum_framework-0.20.0/quantum/runtime/llm_stream.py +103 -0
  24. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_console.py +30 -0
  25. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_page_renderer.py +38 -1
  26. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_view.py +8 -1
  27. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/web_server.py +13 -0
  28. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/PKG-INFO +2 -2
  29. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/SOURCES.txt +1 -0
  30. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_agent.py +1 -1
  31. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/LICENSE +0 -0
  32. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/__init__.py +0 -0
  33. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/__init__.py +0 -0
  34. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/admin.py +0 -0
  35. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/check.py +0 -0
  36. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/__init__.py +0 -0
  37. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/build.py +0 -0
  38. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/dev.py +0 -0
  39. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/docs.py +0 -0
  40. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/lint.py +0 -0
  41. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/migrate.py +0 -0
  42. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/new.py +0 -0
  43. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/serve.py +0 -0
  44. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/commands/test.py +0 -0
  45. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/deploy.py +0 -0
  46. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/hot_reload.py +0 -0
  47. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/jobs.py +0 -0
  48. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/mq.py +0 -0
  49. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/pkg.py +0 -0
  50. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/runner.py +0 -0
  51. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/schema_plan.py +0 -0
  52. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/server_process.py +0 -0
  53. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/cli/utils.py +0 -0
  54. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/__init__.py +0 -0
  55. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/base_generator.py +0 -0
  56. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/cli.py +0 -0
  57. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/expression_transformer.py +0 -0
  58. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/javascript/__init__.py +0 -0
  59. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/javascript/generator.py +0 -0
  60. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/optimizer.py +0 -0
  61. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/python/__init__.py +0 -0
  62. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/python/generator.py +0 -0
  63. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/python/runtime.py +0 -0
  64. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/compiler/transpiler.py +0 -0
  65. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/__init__.py +0 -0
  66. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/config_env.py +0 -0
  67. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/docs_links.py +0 -0
  68. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/expression_diagnostics.py +0 -0
  69. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/expressions.py +0 -0
  70. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/agents/src/__init__.py +0 -0
  71. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/conditionals/src/__init__.py +0 -0
  72. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/conditionals/src/ast_node.py +0 -0
  73. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/data_import/src/__init__.py +0 -0
  74. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/data_import/src/ast_node.py +0 -0
  75. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/data_import/src/runtime.py +0 -0
  76. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/dump/src/__init__.py +0 -0
  77. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/dump/src/ast_node.py +0 -0
  78. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/dump/src/parser.py +0 -0
  79. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/dump/src/runtime.py +0 -0
  80. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/functions/src/__init__.py +0 -0
  81. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/functions/src/ast_node.py +0 -0
  82. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/game_engine_2d/src/__init__.py +0 -0
  83. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/game_engine_2d/src/ast_nodes.py +0 -0
  84. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/game_engine_2d/src/parser.py +0 -0
  85. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/invocation/src/__init__.py +0 -0
  86. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/invocation/src/ast_node.py +0 -0
  87. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/invocation/src/runtime.py +0 -0
  88. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/knowledge_base/src/__init__.py +0 -0
  89. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/knowledge_base/src/parser.py +0 -0
  90. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/logging/src/__init__.py +0 -0
  91. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/logging/src/ast_node.py +0 -0
  92. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/logging/src/parser.py +0 -0
  93. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/logging/src/runtime.py +0 -0
  94. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/loops/src/__init__.py +0 -0
  95. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/loops/src/ast_node.py +0 -0
  96. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/query/src/__init__.py +0 -0
  97. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/query/src/query_validators.py +0 -0
  98. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/state_management/src/__init__.py +0 -0
  99. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/state_management/src/ast_node.py +0 -0
  100. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/terminal_engine/src/__init__.py +0 -0
  101. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/terminal_engine/src/ast_nodes.py +0 -0
  102. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/terminal_engine/src/parser.py +0 -0
  103. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/testing_engine/src/__init__.py +0 -0
  104. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/testing_engine/src/ast_nodes.py +0 -0
  105. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/testing_engine/src/parser.py +0 -0
  106. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/theming/src/__init__.py +0 -0
  107. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/theming/src/ast_node.py +0 -0
  108. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/theming/src/presets.py +0 -0
  109. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/ui_engine/src/__init__.py +0 -0
  110. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/websocket/src/__init__.py +0 -0
  111. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/features/websocket/src/ast_node.py +0 -0
  112. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/guards.py +0 -0
  113. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/html_compat.py +0 -0
  114. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parser_registry.py +0 -0
  115. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/__init__.py +0 -0
  116. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/__init__.py +0 -0
  117. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/ai/team_parser.py +0 -0
  118. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/base.py +0 -0
  119. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/composition/__init__.py +0 -0
  120. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/composition/import_parser.py +0 -0
  121. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/composition/slot_parser.py +0 -0
  122. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/control_flow/__init__.py +0 -0
  123. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/control_flow/if_parser.py +0 -0
  124. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/control_flow/loop_parser.py +0 -0
  125. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/control_flow/set_parser.py +0 -0
  126. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/__init__.py +0 -0
  127. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/data_parser.py +0 -0
  128. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/invoke_parser.py +0 -0
  129. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/data/transaction_parser.py +0 -0
  130. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/events/__init__.py +0 -0
  131. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/events/dispatch_event_parser.py +0 -0
  132. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/forms/__init__.py +0 -0
  133. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/forms/action_parser.py +0 -0
  134. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/forms/flash_parser.py +0 -0
  135. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/forms/redirect_parser.py +0 -0
  136. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/functions/__init__.py +0 -0
  137. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/functions/function_parser.py +0 -0
  138. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/functions/param_parser.py +0 -0
  139. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/functions/return_parser.py +0 -0
  140. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/html/__init__.py +0 -0
  141. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/html/component_call_parser.py +0 -0
  142. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/html/html_parser.py +0 -0
  143. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/jobs/__init__.py +0 -0
  144. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/jobs/job_parser.py +0 -0
  145. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/jobs/schedule_parser.py +0 -0
  146. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/jobs/thread_parser.py +0 -0
  147. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/__init__.py +0 -0
  148. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/message_ack_parser.py +0 -0
  149. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/message_nack_parser.py +0 -0
  150. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/message_parser.py +0 -0
  151. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/queue_parser.py +0 -0
  152. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/messaging/websocket_parser.py +0 -0
  153. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/routing/__init__.py +0 -0
  154. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/routing/route_parser.py +0 -0
  155. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/__init__.py +0 -0
  156. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/pyclass_parser.py +0 -0
  157. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/pydecorator_parser.py +0 -0
  158. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/pyimport_parser.py +0 -0
  159. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/scripting/python_parser.py +0 -0
  160. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/__init__.py +0 -0
  161. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/dump_parser.py +0 -0
  162. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/file_parser.py +0 -0
  163. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/log_parser.py +0 -0
  164. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/parsers/services/mail_parser.py +0 -0
  165. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/tiers.py +0 -0
  166. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/core/xml_lines.py +0 -0
  167. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/packages/__init__.py +0 -0
  168. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/packages/manager.py +0 -0
  169. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/packages/manifest.py +0 -0
  170. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/packages/registry.py +0 -0
  171. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/packages/resolver.py +0 -0
  172. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/plugins/__init__.py +0 -0
  173. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/plugins/hooks.py +0 -0
  174. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/plugins/loader.py +0 -0
  175. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/plugins/manifest.py +0 -0
  176. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/plugins/registry.py +0 -0
  177. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/__init__.py +0 -0
  178. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/action_handler.py +0 -0
  179. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/__init__.py +0 -0
  180. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/memory_adapter.py +0 -0
  181. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/rabbitmq_adapter.py +0 -0
  182. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/redis_adapter.py +0 -0
  183. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/adapters/sqlite_adapter.py +0 -0
  184. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ast_cache.py +0 -0
  185. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/auth_service.py +0 -0
  186. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/component_composer.py +0 -0
  187. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/component_resolver.py +0 -0
  188. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/conditions.py +0 -0
  189. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/database_service.py +0 -0
  190. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/db_schema.py +0 -0
  191. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/dev_panel.py +0 -0
  192. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/email_service.py +0 -0
  193. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/error_handler.py +0 -0
  194. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/execution_context.py +0 -0
  195. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executor_registry.py +0 -0
  196. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/__init__.py +0 -0
  197. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/__init__.py +0 -0
  198. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/ai/team_executor.py +0 -0
  199. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/base.py +0 -0
  200. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/__init__.py +0 -0
  201. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/if_executor.py +0 -0
  202. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/loop_executor.py +0 -0
  203. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/redirect_executor.py +0 -0
  204. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/control_flow/set_executor.py +0 -0
  205. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/__init__.py +0 -0
  206. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/data_executor.py +0 -0
  207. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/invoke_executor.py +0 -0
  208. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/data/transaction_executor.py +0 -0
  209. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/jobs/__init__.py +0 -0
  210. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/jobs/job_executor.py +0 -0
  211. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/jobs/schedule_executor.py +0 -0
  212. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/jobs/thread_executor.py +0 -0
  213. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/__init__.py +0 -0
  214. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/message_ack_executor.py +0 -0
  215. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/message_executor.py +0 -0
  216. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/queue_executor.py +0 -0
  217. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/messaging/websocket_executor.py +0 -0
  218. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/scripting/__init__.py +0 -0
  219. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/scripting/pyclass_executor.py +0 -0
  220. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/scripting/pyimport_executor.py +0 -0
  221. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/scripting/python_executor.py +0 -0
  222. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/__init__.py +0 -0
  223. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/dump_executor.py +0 -0
  224. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/file_executor.py +0 -0
  225. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/log_executor.py +0 -0
  226. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/executors/services/mail_executor.py +0 -0
  227. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/expression_cache.py +0 -0
  228. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/file_upload_service.py +0 -0
  229. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/function_registry.py +0 -0
  230. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/game_builder.py +0 -0
  231. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/game_code_generator.py +0 -0
  232. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/game_templates.py +0 -0
  233. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/godot_code_generator.py +0 -0
  234. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/godot_templates.py +0 -0
  235. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/history.py +0 -0
  236. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/job_executor.py +0 -0
  237. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/llm_cache.py +0 -0
  238. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/llm_service.py +0 -0
  239. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/local_server.py +0 -0
  240. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/logging_setup.py +0 -0
  241. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/message_broker.py +0 -0
  242. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/message_queue_service.py +0 -0
  243. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/param_validation.py +0 -0
  244. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/pypy_compat.py +0 -0
  245. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/python_bridge.py +0 -0
  246. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/query_validators.py +0 -0
  247. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/renderer.py +0 -0
  248. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/service_container.py +0 -0
  249. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/table_params.py +0 -0
  250. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/terminal_builder.py +0 -0
  251. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/terminal_code_generator.py +0 -0
  252. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/terminal_templates.py +0 -0
  253. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/testing_builder.py +0 -0
  254. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/testing_code_generator.py +0 -0
  255. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/testing_templates.py +0 -0
  256. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_builder.py +0 -0
  257. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_desktop.py +0 -0
  258. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_form_rules.py +0 -0
  259. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_html_adapter.py +0 -0
  260. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_html_templates.py +0 -0
  261. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_mobile_adapter.py +0 -0
  262. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_mobile_templates.py +0 -0
  263. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_pager.py +0 -0
  264. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_table_edit.py +0 -0
  265. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_textual_adapter.py +0 -0
  266. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_textual_templates.py +0 -0
  267. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_tokens.py +0 -0
  268. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/ui_validator.py +0 -0
  269. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/validators.py +0 -0
  270. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/websocket_adapter.py +0 -0
  271. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/websocket_service.py +0 -0
  272. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/websocket_transport.py +0 -0
  273. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/runtime/wsgi.py +0 -0
  274. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/services.py +0 -0
  275. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum/utils/__init__.py +0 -0
  276. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/__init__.py +0 -0
  277. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/assets/quantum-admin-connectors.js +0 -0
  278. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/assets/quantum-admin.css +0 -0
  279. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/assets/quantum-source-highlight.js +0 -0
  280. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/__init__.py +0 -0
  281. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/audit_service.py +0 -0
  282. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/auth_service.py +0 -0
  283. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/component_discovery.py +0 -0
  284. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/config_generator.py +0 -0
  285. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/connector_service.py +0 -0
  286. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/crud.py +0 -0
  287. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/database.py +0 -0
  288. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/db_setup_service.py +0 -0
  289. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/deploy_service.py +0 -0
  290. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/docker_service.py +0 -0
  291. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/environment_service.py +0 -0
  292. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/git_service.py +0 -0
  293. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/health_service.py +0 -0
  294. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/job_service.py +0 -0
  295. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/log_service.py +0 -0
  296. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/main.py +0 -0
  297. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/models.py +0 -0
  298. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/pipeline_service.py +0 -0
  299. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/resource_manager.py +0 -0
  300. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/schemas.py +0 -0
  301. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/secret_manager.py +0 -0
  302. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/settings_service.py +0 -0
  303. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/template_service.py +0 -0
  304. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/test_execution_service.py +0 -0
  305. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/test_generator.py +0 -0
  306. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/webhook_service.py +0 -0
  307. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/backend/websocket_manager.py +0 -0
  308. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/_layout/AdminShell.q +0 -0
  309. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/agents.q +0 -0
  310. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/app/[name].q +0 -0
  311. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/applications.q +0 -0
  312. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/component/[...path].q +0 -0
  313. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/components.q +0 -0
  314. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/connectors.q +0 -0
  315. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/dashboard.q +0 -0
  316. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/database.q +0 -0
  317. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/features.q +0 -0
  318. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/index.q +0 -0
  319. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/jobs.q +0 -0
  320. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/login.q +0 -0
  321. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/logout.q +0 -0
  322. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/settings.q +0 -0
  323. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/source.q +0 -0
  324. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/components/admin/tests.q +0 -0
  325. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/run.py +0 -0
  326. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/__init__.py +0 -0
  327. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/_base.py +0 -0
  328. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/_component_test_generator.py +0 -0
  329. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/_yaml_editor.py +0 -0
  330. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/apps.py +0 -0
  331. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/auth.py +0 -0
  332. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/components.py +0 -0
  333. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/connectors.py +0 -0
  334. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/projects.py +0 -0
  335. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/repository.py +0 -0
  336. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/settings.py +0 -0
  337. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_admin/services/yaml_import.py +0 -0
  338. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/dependency_links.txt +0 -0
  339. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/entry_points.txt +0 -0
  340. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/requires.txt +0 -0
  341. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/quantum_framework.egg-info/top_level.txt +0 -0
  342. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/setup.cfg +0 -0
  343. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_animations.py +0 -0
  344. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_ast_cache.py +0 -0
  345. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_cli_commands.py +0 -0
  346. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_component_library.py +0 -0
  347. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_expression_cache.py +0 -0
  348. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_form_validation.py +0 -0
  349. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_game_codegen.py +0 -0
  350. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_game_e2e.py +0 -0
  351. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_game_engine.py +0 -0
  352. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_game_parser.py +0 -0
  353. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_godot_codegen.py +0 -0
  354. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_job_executor.py +0 -0
  355. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_llm_providers.py +0 -0
  356. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_message_broker.py +0 -0
  357. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_message_queue.py +0 -0
  358. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_mobile_target.py +0 -0
  359. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_multi_agent.py +0 -0
  360. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_packages.py +0 -0
  361. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_plugins.py +0 -0
  362. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_pypy_compat.py +0 -0
  363. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_python_scripting.py +0 -0
  364. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_terminal_codegen.py +0 -0
  365. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_terminal_parser.py +0 -0
  366. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_testing_codegen.py +0 -0
  367. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_testing_parser.py +0 -0
  368. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_theming.py +0 -0
  369. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_ui_html_adapter.py +0 -0
  370. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_ui_parity.py +0 -0
  371. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_ui_parser.py +0 -0
  372. {quantum_framework-0.19.0 → quantum_framework-0.20.0}/tests/test_ui_textual_adapter.py +0 -0
  373. {quantum_framework-0.19.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.19.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.19.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"}
@@ -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,21 @@ 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
+
1899
1914
  class UIHistoryNode(QuantumNode, UILayoutMixin):
1900
1915
  """<ui:history table="t" key="{row.id}" datasource="db"> — a row's change history (DB-11)."""
1901
1916
 
@@ -1926,6 +1941,6 @@ CORE_TAGS = frozenset({
1926
1941
  'window', 'hbox', 'vbox', 'grid', 'panel', 'section', 'scrollbox', 'spacer', 'rule',
1927
1942
  'header', 'footer', 'card', 'card-header', 'card-body', 'card-footer', 'tabpanel', 'tab',
1928
1943
  'text', 'badge', 'alert', 'link', 'image', 'progress',
1929
- 'table', 'column', 'list', 'item', 'pager', 'history',
1944
+ 'table', 'column', 'list', 'item', 'pager', 'history', 'stream',
1930
1945
  'form', 'formitem', 'input', 'checkbox', 'switch', 'radio', 'select', 'option', 'button',
1931
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, UIHistoryNode,
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
@@ -83,6 +83,7 @@ class UIParser:
83
83
  'column': '_parse_ui_column',
84
84
  'pager': '_parse_ui_pager',
85
85
  'history': '_parse_ui_history',
86
+ 'stream': '_parse_ui_stream',
86
87
  'list': '_parse_ui_list',
87
88
  'item': '_parse_ui_item',
88
89
  'image': '_parse_ui_image',
@@ -551,6 +552,14 @@ class UIParser:
551
552
  self._apply_layout_attrs(node, element)
552
553
  return node
553
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
+
554
563
  def _parse_ui_history(self, element: ET.Element) -> UIHistoryNode:
555
564
  node = UIHistoryNode()
556
565
  node.table = element.get('table')
@@ -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')
@@ -78,6 +78,8 @@ class ToolCall:
78
78
  return {
79
79
  "tool": self.tool,
80
80
  "args": self.args,
81
+ # IA-4: the call as a page shows it — low_stock(below=5)
82
+ "call": f"{self.tool}({', '.join(f'{k}={v}' for k, v in (self.args or {}).items())})",
81
83
  "result": self.result,
82
84
  "error": self.error,
83
85
  "duration_ms": self.duration_ms
@@ -271,7 +273,9 @@ IMPORTANT: Always respond with valid JSON in the format shown above. Nothing els
271
273
  model=model,
272
274
  endpoint=endpoint,
273
275
  provider=provider,
274
- api_key=api_key
276
+ api_key=api_key,
277
+ # the model gets what is left of the agent's timeout, not a fixed 60s
278
+ timeout=max(1, int((timeout_ms - elapsed_ms) / 1000)),
275
279
  )
276
280
 
277
281
  # Get assistant message
@@ -409,7 +413,8 @@ IMPORTANT: Always respond with valid JSON in the format shown above. Nothing els
409
413
  model: str,
410
414
  endpoint: str,
411
415
  provider: str = "auto",
412
- api_key: str = ""
416
+ api_key: str = "",
417
+ timeout: Optional[int] = None
413
418
  ) -> Dict[str, Any]:
414
419
  """
415
420
  Call LLM with messages using multi-provider support.
@@ -433,6 +438,7 @@ IMPORTANT: Always respond with valid JSON in the format shown above. Nothing els
433
438
  endpoint=endpoint if endpoint else None,
434
439
  api_key=api_key if api_key else None,
435
440
  temperature=0.1, # Low temperature for deterministic tool use
441
+ timeout=timeout,
436
442
  )
437
443
 
438
444
  # Handle response format from MultiProviderLLMService
@@ -901,27 +901,12 @@ class ComponentRuntime:
901
901
  # Extract knowledge base name from datasource
902
902
  kb_name = query_node.datasource.replace('knowledge:', '', 1)
903
903
 
904
- # Check if knowledge base failed to initialize - return empty results
904
+ # IA-5: a base that could not be built is an error here too — never an
905
+ # answer ("still loading", success=True) that the page shows as one.
905
906
  kb_meta = exec_context.get_variable(f"_knowledge_{kb_name}")
906
907
  if isinstance(kb_meta, dict) and kb_meta.get("_failed"):
907
- from quantum.runtime.database_service import QueryResult as _QR
908
- mode = query_node.mode
909
- if mode == 'rag':
910
- empty_data = [{"answer": "Knowledge base is still loading. Please refresh the page.", "sources": "", "confidence": "0"}]
911
- result = _QR(data=empty_data, column_list=['answer', 'sources', 'confidence'], execution_time=0.0, record_count=1, success=True)
912
- else:
913
- result = _QR(data=[], column_list=['content', 'relevance', 'source', 'chunk_index'], execution_time=0.0, record_count=0, success=True)
914
- result_dict = result.to_dict()
915
- exec_context.set_variable(query_node.name, result.data, scope="component")
916
- exec_context.set_variable(f"{query_node.name}_result", result_dict, scope="component")
917
- self.context[query_node.name] = result.data
918
- self.context[f"{query_node.name}_result"] = result_dict
919
- if result.data and len(result.data) == 1 and isinstance(result.data[0], dict):
920
- for field_name, field_value in result.data[0].items():
921
- dotted_key = f"{query_node.name}.{field_name}"
922
- exec_context.set_variable(dotted_key, field_value, scope="component")
923
- self.context[dotted_key] = field_value
924
- return result
908
+ raise KnowledgeError(f"knowledge base '{kb_name}' could not be built: "
909
+ f"{kb_meta.get('error', 'unknown error')}")
925
910
 
926
911
  # Get the search query from the first parameter value
927
912
  search_text = None
@@ -122,18 +122,24 @@ class AgentExecutor(BaseExecutor):
122
122
  exec_context.set_variable(node.name, result.get('result', ''), scope="component")
123
123
  exec_context.set_variable(f"{node.name}_result", result, scope="component")
124
124
 
125
- return result
126
-
127
125
  except Exception as e:
128
- error_result = {
126
+ result = {
129
127
  'success': False,
130
128
  'error': {'message': str(e)},
131
129
  'result': '',
132
130
  'iterations': 0,
133
131
  'actions': []
134
132
  }
135
- exec_context.set_variable(f"{node.name}_result", error_result, scope="component")
136
- raise ExecutorError(f"Agent execution error: {e}")
133
+ exec_context.set_variable(node.name, '', scope="component")
134
+ exec_context.set_variable(f"{node.name}_result", result, scope="component")
135
+
136
+ # IA-5: an agent that did not finish used to leave success=false and an
137
+ # empty answer on a page that went on as if nothing happened.
138
+ if not result.get('success') and getattr(node, 'on_error', 'fail') != 'continue':
139
+ message = (result.get('error') or {}).get('message') or 'the agent did not finish'
140
+ raise ExecutorError(f'Agent "{node.name}" failed: {message}. Add onerror="continue" to '
141
+ f'handle it on the page ({node.name}_result.success is false).')
142
+ return result
137
143
 
138
144
 
139
145
  def coerce_tool_args(params, tool_args: Dict[str, Any]) -> Dict[str, Any]:
@@ -105,10 +105,12 @@ class KnowledgeExecutor(BaseExecutor):
105
105
  'error': str(e),
106
106
  'name': node.name
107
107
  }, scope="component")
108
- # _execute_knowledge_query() (component.py) checks this marker to
109
- # return a graceful "still loading" result instead of raising
110
- # KnowledgeError("not found") on the next q:query for this KB.
108
+ # IA-5: a q:query on this base then fails with this reason (it used
109
+ # to answer "Knowledge base is still loading" as if that were the
110
+ # answer, with success=True).
111
111
  exec_context.set_variable(f"_knowledge_{node.name}", {
112
- '_failed': True,
112
+ '_failed': True, 'error': str(e),
113
113
  }, scope="component")
114
+ if getattr(node, 'on_error', 'fail') == 'continue':
115
+ return None
114
116
  raise ExecutorError(f"Knowledge base creation error: {e}")
@@ -5,6 +5,7 @@ Handles LLM invocation across providers (Ollama, OpenAI, Anthropic, LM Studio).
5
5
  """
6
6
 
7
7
  import json
8
+ import re
8
9
  from typing import Any, List, Dict, Type
9
10
  from quantum.runtime.executors.base import BaseExecutor, ExecutorError
10
11
  from quantum.core.ast_nodes import LLMNode
@@ -64,10 +65,49 @@ class LLMExecutor(BaseExecutor):
64
65
  if not messages:
65
66
  prompt_text = self.apply_databinding(node.prompt, context) if node.prompt else ''
66
67
 
68
+ sources = None
69
+ if getattr(node, 'knowledge', None):
70
+ # IA-6 (M5): answer from the knowledge base, citing its sources.
71
+ question = next((m['content'] for m in reversed(messages) if m['role'] == 'user'), prompt_text)
72
+ sources = self._retrieve(node, question or '', exec_context)
73
+ if not sources:
74
+ # Nothing to answer from: the model is not asked (it would
75
+ # answer from memory, uncited). The page sees found=false.
76
+ return self._store(node, {'data': '', 'success': True, 'model': model}, exec_context,
77
+ cached=False, extra={'found': False, 'sources': [], 'cited': []})
78
+ grounding = self._grounding(sources)
79
+ # Small models follow the last instruction best: repeat it with the question.
80
+ reminder = ' (Answer from the sources and cite them like [1].)'
81
+ if messages and messages[-1]['role'] == 'user':
82
+ messages[-1] = {'role': 'user', 'content': messages[-1]['content'] + reminder}
83
+ elif prompt_text is not None:
84
+ prompt_text += reminder
85
+ system_text = f'{grounding}\n\n{system_text}' if system_text else grounding
86
+ if messages:
87
+ if messages[0]['role'] == 'system':
88
+ messages[0] = {'role': 'system', 'content': f"{grounding}\n\n{messages[0]['content']}"}
89
+ else:
90
+ messages.insert(0, {'role': 'system', 'content': grounding})
91
+
67
92
  temperature = float(node.temperature) if node.temperature is not None else None
68
93
  max_tokens = int(node.max_tokens) if node.max_tokens is not None else None
69
94
  response_format = node.response_format or 'text'
70
95
 
96
+ if getattr(node, 'stream', False) and self._in_web_request():
97
+ # IA-7: the page renders now; <ui:stream> reads the answer from /_stream/<token>.
98
+ from quantum.runtime import llm_stream
99
+ chat = messages or ([{'role': 'system', 'content': system_text}] if system_text else []) + [{'role': 'user', 'content': prompt_text or ''}]
100
+ token = llm_stream.register({
101
+ 'messages': chat, 'model': model, 'provider': provider, 'endpoint': endpoint,
102
+ 'api_key': api_key, 'temperature': temperature, 'max_tokens': max_tokens,
103
+ 'timeout': node.timeout,
104
+ }, exec_context.session_vars)
105
+ extra = {'stream': f'/_stream/{token}'}
106
+ if sources is not None:
107
+ extra.update({'found': True, 'sources': sources, 'cited': []})
108
+ return self._store(node, {'data': '', 'success': True, 'model': model}, exec_context,
109
+ cached=False, extra=extra)
110
+
71
111
  cache = self.services.llm_cache if node.cache else None
72
112
  cache_key = None
73
113
  if cache is not None:
@@ -96,6 +136,7 @@ class LLMExecutor(BaseExecutor):
96
136
  temperature=temperature,
97
137
  max_tokens=max_tokens,
98
138
  response_format=response_format,
139
+ timeout=node.timeout,
99
140
  )
100
141
  else:
101
142
  response = llm.generate(
@@ -108,22 +149,74 @@ class LLMExecutor(BaseExecutor):
108
149
  temperature=temperature,
109
150
  max_tokens=max_tokens,
110
151
  response_format=response_format,
152
+ timeout=node.timeout,
111
153
  )
112
154
 
113
155
  if cache is not None and response.get('success', True):
114
156
  cache.set(cache_key, response, ttl=node.ttl)
115
157
 
116
- return self._store(node, response, exec_context, cached=False)
158
+ extra = None
159
+ if sources is not None:
160
+ extra = {'found': True, 'sources': sources,
161
+ 'cited': self._cited(str(response.get('data', '')), len(sources))}
162
+ return self._store(node, response, exec_context, cached=False, extra=extra)
117
163
 
118
164
  except Exception as e:
165
+ # IA-5: same shape as q:invoke (INV-2): <name>_result.error.message.
119
166
  exec_context.set_variable(f"{node.name}_result", {
120
167
  'success': False,
121
- 'error': str(e),
168
+ 'error': {'message': str(e)},
122
169
  'model': node.model
123
170
  }, scope="component")
124
- raise ExecutorError(f"LLM execution error: {e}")
171
+ if getattr(node, 'on_error', 'fail') == 'continue':
172
+ exec_context.set_variable(node.name, '', scope="component")
173
+ return None
174
+ raise ExecutorError(f"LLM execution error: {e}. Add onerror=\"continue\" to handle it "
175
+ f"with {node.name}_result instead.")
176
+
177
+ @staticmethod
178
+ def _in_web_request() -> bool:
179
+ """IA-7: streaming needs a browser (or the console) to read /_stream; `quantum run` waits."""
180
+ try:
181
+ from flask import has_request_context
182
+ return has_request_context()
183
+ except ImportError:
184
+ return False
125
185
 
126
- def _store(self, node: LLMNode, response: Dict[str, Any], exec_context, cached: bool) -> Any:
186
+ # -- IA-6: answers grounded in a knowledge base --------------------------------
187
+
188
+ def _retrieve(self, node: LLMNode, question: str, exec_context) -> list:
189
+ """The top chunks for the question, numbered as the model will cite them."""
190
+ name = node.knowledge
191
+ try:
192
+ meta = exec_context.get_variable(f'_knowledge_{name}')
193
+ except Exception:
194
+ meta = None
195
+ if not isinstance(meta, dict):
196
+ raise ExecutorError(f'<q:llm knowledge="{name}">: there is no <q:knowledge name="{name}"> '
197
+ f'on this page')
198
+ if meta.get('_failed'):
199
+ raise ExecutorError(f"knowledge base '{name}' could not be built: {meta.get('error')}")
200
+ hits = self.runtime.knowledge_service.search(
201
+ name, question, int(getattr(node, 'top', 4) or 4), meta.get('embed_model', 'nomic-embed-text'))
202
+ from pathlib import PurePath
203
+ return [{'n': i + 1, 'source': h.get('source') or 'unknown',
204
+ 'name': PurePath(str(h.get('source') or 'unknown').replace('\\', '/')).name,
205
+ 'text': h.get('content', ''), 'relevance': h.get('relevance')} for i, h in enumerate(hits)]
206
+
207
+ @staticmethod
208
+ def _grounding(sources: list) -> str:
209
+ numbered = '\n\n'.join(f"[{s['n']}] ({s['name']})\n{s['text']}" for s in sources)
210
+ return ('Answer using only the sources below. After each statement, cite the source it '
211
+ 'comes from by its number in brackets, like [1]. If the sources do not contain '
212
+ 'the answer, say that you do not know.\n\nSources:\n' + numbered)
213
+
214
+ @staticmethod
215
+ def _cited(text: str, count: int) -> list:
216
+ return sorted({int(n) for n in re.findall(r'\[(\d+)\]', text) if 1 <= int(n) <= count})
217
+
218
+ def _store(self, node: LLMNode, response: Dict[str, Any], exec_context, cached: bool,
219
+ extra: Dict[str, Any] = None) -> Any:
127
220
  """Unpack a provider response, store it, and return the value."""
128
221
  text = response.get('data', '')
129
222
  value = text
@@ -148,6 +241,7 @@ class LLMExecutor(BaseExecutor):
148
241
  'provider': response.get('provider'),
149
242
  'usage': response.get('usage'),
150
243
  'cached': cached,
244
+ **(extra or {}),
151
245
  }, scope="component")
152
246
 
153
247
  return value
@@ -56,6 +56,13 @@ class QueryExecutor(BaseExecutor):
56
56
  return self._execute_database_query(node, resolved_params, exec_context)
57
57
 
58
58
  except Exception as e:
59
+ if getattr(node, 'on_error', 'fail') == 'continue':
60
+ # IA-5: the page handles it — no rows, and why.
61
+ exec_context.set_variable(node.name, [], scope="component")
62
+ exec_context.set_variable(f"{node.name}_result", {
63
+ 'success': False, 'error': {'message': str(e)}, 'recordCount': 0, 'data': [],
64
+ }, scope="component")
65
+ return None
59
66
  raise ExecutorError(f"Query execution error in '{node.name}': {e}")
60
67
 
61
68
  def _resolve_params(self, node: QueryNode, context: Dict[str, Any]) -> Dict[str, Any]: