uclone-x 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. {uclone_x-0.2.1 → uclone_x-0.2.2}/CHANGELOG.md +89 -0
  2. {uclone_x-0.2.1 → uclone_x-0.2.2}/PKG-INFO +3 -1
  3. {uclone_x-0.2.1 → uclone_x-0.2.2}/pyproject.toml +7 -1
  4. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/__init__.py +1 -1
  5. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/base.py +49 -13
  6. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/models.py +7 -0
  7. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/prompts.py +16 -0
  8. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/a2a.py +5 -1
  9. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/acp.py +6 -1
  10. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/bootstrap.py +132 -22
  11. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/eval.py +3 -1
  12. uclone_x-0.2.2/src/uclone_x/cli/commands/key.py +207 -0
  13. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/llm.py +67 -0
  14. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/loop.py +8 -1
  15. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/media.py +4 -0
  16. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/room.py +23 -4
  17. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/run.py +54 -9
  18. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/main.py +2 -0
  19. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/environment_install.py +1 -1
  20. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/errors.py +21 -0
  21. uclone_x-0.2.2/src/uclone_x/llm/connectors/factory.py +362 -0
  22. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/ollama.py +61 -24
  23. uclone_x-0.2.2/src/uclone_x/llm/connectors/saved_choice.py +346 -0
  24. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/context_window.py +70 -28
  25. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/models.py +4 -1
  26. uclone_x-0.2.2/src/uclone_x/personas/guardian.yaml +37 -0
  27. uclone_x-0.2.2/src/uclone_x/personas/pioneer.yaml +31 -0
  28. uclone_x-0.2.2/src/uclone_x/personas/scout.yaml +23 -0
  29. uclone_x-0.2.2/src/uclone_x/personas/writer.yaml +23 -0
  30. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/models.py +9 -3
  31. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/shells/acp/server.py +10 -2
  32. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/image.py +336 -18
  33. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/app.py +137 -23
  34. uclone_x-0.2.2/src/uclone_x/ui_build_inputs.sha256 +1 -0
  35. uclone_x-0.2.1/src/uclone_x/ui_static/assets/index-DOOa_O0r.js → uclone_x-0.2.2/src/uclone_x/ui_static/assets/index-B26a-pug.js +142 -142
  36. uclone_x-0.2.1/src/uclone_x/ui_static/assets/index-B0hbgNk5.css → uclone_x-0.2.2/src/uclone_x/ui_static/assets/index-uisJBy0U.css +1 -1
  37. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/index.html +2 -2
  38. uclone_x-0.2.1/src/uclone_x/llm/connectors/factory.py +0 -155
  39. uclone_x-0.2.1/src/uclone_x/personas/guardian.yaml +0 -20
  40. uclone_x-0.2.1/src/uclone_x/personas/pioneer.yaml +0 -20
  41. uclone_x-0.2.1/src/uclone_x/personas/scout.yaml +0 -16
  42. uclone_x-0.2.1/src/uclone_x/personas/writer.yaml +0 -18
  43. uclone_x-0.2.1/src/uclone_x/ui_build_inputs.sha256 +0 -1
  44. {uclone_x-0.2.1 → uclone_x-0.2.2}/.gitignore +0 -0
  45. {uclone_x-0.2.1 → uclone_x-0.2.2}/LICENSE +0 -0
  46. {uclone_x-0.2.1 → uclone_x-0.2.2}/NOTICE +0 -0
  47. {uclone_x-0.2.1 → uclone_x-0.2.2}/README.md +0 -0
  48. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/a2a/__init__.py +0 -0
  49. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/a2a/discovery.py +0 -0
  50. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/a2a/http_transport.py +0 -0
  51. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/a2a/in_memory.py +0 -0
  52. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/a2a/models.py +0 -0
  53. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/a2a/protocols.py +0 -0
  54. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/a2a/wire.py +0 -0
  55. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/acp/__init__.py +0 -0
  56. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/acp/conformance.py +0 -0
  57. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/adapters/__init__.py +0 -0
  58. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/adapters/uclone2/__init__.py +0 -0
  59. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/adapters/uclone2/adk_content.py +0 -0
  60. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/__init__.py +0 -0
  61. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/bootstrap.py +0 -0
  62. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/composition.py +0 -0
  63. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/grounding.py +0 -0
  64. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/hooks/__init__.py +0 -0
  65. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/hooks/artifact_validation.py +0 -0
  66. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/hooks/models.py +0 -0
  67. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/hooks/permission.py +0 -0
  68. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/hooks/protocols.py +0 -0
  69. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/hooks/runner.py +0 -0
  70. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/hooks/script_runner.py +0 -0
  71. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/loop/__init__.py +0 -0
  72. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/loop/models.py +0 -0
  73. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/loop/parser.py +0 -0
  74. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/loop/runner.py +0 -0
  75. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/loop/scheduler.py +0 -0
  76. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/memory.py +0 -0
  77. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/persona_registry.py +0 -0
  78. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/persona_store.py +0 -0
  79. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/planner.py +0 -0
  80. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/protocols.py +0 -0
  81. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/request_record.py +0 -0
  82. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/session.py +0 -0
  83. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/terminology.py +0 -0
  84. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/text_tool_calls.py +0 -0
  85. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/agent/turn_trace.py +0 -0
  86. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/__init__.py +0 -0
  87. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/agent_memory.py +0 -0
  88. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/bundle_freshness.py +0 -0
  89. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/changed_scope.py +0 -0
  90. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/__init__.py +0 -0
  91. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/dev.py +0 -0
  92. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/ontology.py +0 -0
  93. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/report.py +0 -0
  94. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/session_dev.py +0 -0
  95. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/skill.py +0 -0
  96. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/commands/ui.py +0 -0
  97. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/environment_provenance.py +0 -0
  98. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/cli/quality_gate.py +0 -0
  99. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/code_intel/__init__.py +0 -0
  100. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/code_intel/ast_parser.py +0 -0
  101. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/code_intel/models.py +0 -0
  102. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/code_intel/protocols.py +0 -0
  103. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/code_intel/scip.py +0 -0
  104. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/code_intel/symbol_graph.py +0 -0
  105. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/config/default_models.yaml +0 -0
  106. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/__init__.py +0 -0
  107. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/agent_home.py +0 -0
  108. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/capability.py +0 -0
  109. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/client_driver.py +0 -0
  110. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/diagnostic_report.py +0 -0
  111. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/failure_journal.py +0 -0
  112. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/host.py +0 -0
  113. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/immutable.py +0 -0
  114. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/log_inspector.py +0 -0
  115. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/log_offset.py +0 -0
  116. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/log_writer.py +0 -0
  117. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/logging_setup.py +0 -0
  118. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/provenance.py +0 -0
  119. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/secrets.py +0 -0
  120. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/session_diagnostics.py +0 -0
  121. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/session_store.py +0 -0
  122. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/tool_results.py +0 -0
  123. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/core/workspace.py +0 -0
  124. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/engine/__init__.py +0 -0
  125. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/engine/event_bus.py +0 -0
  126. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/engine/mattermost_bridge.py +0 -0
  127. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/engine/protocols.py +0 -0
  128. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/evaluation/__init__.py +0 -0
  129. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/evaluation/answerer.py +0 -0
  130. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/evaluation/loader.py +0 -0
  131. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/evaluation/protocols.py +0 -0
  132. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/__init__.py +0 -0
  133. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/budget.py +0 -0
  134. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/compactor.py +0 -0
  135. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/__init__.py +0 -0
  136. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/anthropic.py +0 -0
  137. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/base.py +0 -0
  138. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/gemini.py +0 -0
  139. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/mock.py +0 -0
  140. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/ollama_embedder.py +0 -0
  141. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/openai.py +0 -0
  142. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/connectors/vllm.py +0 -0
  143. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/protocols.py +0 -0
  144. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/llm/router.py +0 -0
  145. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/log/__init__.py +0 -0
  146. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/log/file_allocator.py +0 -0
  147. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/log/reader.py +0 -0
  148. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/memory/__init__.py +0 -0
  149. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/memory/models.py +0 -0
  150. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/memory/retrieval.py +0 -0
  151. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/memory/store.py +0 -0
  152. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/memory/tools.py +0 -0
  153. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/memory/vector_store.py +0 -0
  154. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ontology/__init__.py +0 -0
  155. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ontology/engine.py +0 -0
  156. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ontology/justification.py +0 -0
  157. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ontology/models.py +0 -0
  158. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ontology/protocols.py +0 -0
  159. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ontology/reasoner.py +0 -0
  160. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ontology/rules.py +0 -0
  161. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/personas/artist.png +0 -0
  162. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/personas/artist.yaml +0 -0
  163. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/personas/clone.png +0 -0
  164. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/personas/clone.yaml +0 -0
  165. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/personas/guardian.png +0 -0
  166. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/personas/pioneer.png +0 -0
  167. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/personas/scout.png +0 -0
  168. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/personas/writer.png +0 -0
  169. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/__init__.py +0 -0
  170. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/knowledge.py +0 -0
  171. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/knowledge_store.py +0 -0
  172. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/orchestrator.py +0 -0
  173. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/protocols.py +0 -0
  174. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/resolver.py +0 -0
  175. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/selectors.py +0 -0
  176. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/service.py +0 -0
  177. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/store.py +0 -0
  178. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/room/turn_summary.py +0 -0
  179. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/sandbox/__init__.py +0 -0
  180. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/sandbox/models.py +0 -0
  181. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/sandbox/path_validator.py +0 -0
  182. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/sandbox/protocols.py +0 -0
  183. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/sandbox/workspace_runner.py +0 -0
  184. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/shells/a2a_server.py +0 -0
  185. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/shells/acp/__init__.py +0 -0
  186. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/shells/acp/driver.py +0 -0
  187. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/shells/acp/models.py +0 -0
  188. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/shells/entry.py +0 -0
  189. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/shells/ui_process.py +0 -0
  190. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/skills/__init__.py +0 -0
  191. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/skills/auditor.py +0 -0
  192. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/skills/models.py +0 -0
  193. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/skills/protocols.py +0 -0
  194. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/skills/synthesizer.py +0 -0
  195. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/__init__.py +0 -0
  196. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/exporter.py +0 -0
  197. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/langfuse.py +0 -0
  198. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/metrics.py +0 -0
  199. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/models.py +0 -0
  200. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/otel_sdk.py +0 -0
  201. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/otlp.py +0 -0
  202. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/protocols.py +0 -0
  203. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/telemetry/tracer.py +0 -0
  204. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/__init__.py +0 -0
  205. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/base.py +0 -0
  206. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/__init__.py +0 -0
  207. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/character.py +0 -0
  208. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/comfy_client.py +0 -0
  209. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/comfy_image_tool.py +0 -0
  210. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/filesystem.py +0 -0
  211. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/install.py +0 -0
  212. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/mcp_loader.py +0 -0
  213. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/media_registry.py +0 -0
  214. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/plan.py +0 -0
  215. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/shell.py +0 -0
  216. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/skill_loader.py +0 -0
  217. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/subagent.py +0 -0
  218. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/tool_results.py +0 -0
  219. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/builtin/web.py +0 -0
  220. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/client.py +0 -0
  221. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/mcp_manager.py +0 -0
  222. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/models.py +0 -0
  223. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/outcome.py +0 -0
  224. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/protocols.py +0 -0
  225. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/registry.py +0 -0
  226. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/scoped_registry.py +0 -0
  227. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/tools/tool_scoper.py +0 -0
  228. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/__init__.py +0 -0
  229. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/build_inputs.py +0 -0
  230. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/clones.py +0 -0
  231. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/knowledge.py +0 -0
  232. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/room_dock.py +0 -0
  233. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/rooms.py +0 -0
  234. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/server.py +0 -0
  235. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui/single_flight.py +0 -0
  236. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  237. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  238. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  239. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  240. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  241. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  242. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  243. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  244. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  245. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  246. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  247. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  248. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  249. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  250. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  251. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  252. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  253. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  254. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  255. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  256. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  257. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  258. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  259. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  260. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  261. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  262. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  263. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  264. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  265. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  266. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  267. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  268. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  269. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  270. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  271. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  272. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  273. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  274. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  275. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  276. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  277. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  278. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  279. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  280. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  281. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  282. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  283. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  284. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  285. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  286. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  287. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  288. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  289. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  290. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  291. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  292. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  293. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  294. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  295. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/favicon.ico +0 -0
  296. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/logo.png +0 -0
  297. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/logoWhite.png +0 -0
  298. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/logo_uclone_short.png +0 -0
  299. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/uclone_logo_1024x1024.png +0 -0
  300. {uclone_x-0.2.1 → uclone_x-0.2.2}/src/uclone_x/ui_static/uclone_logo_circle.png +0 -0
@@ -13,6 +13,95 @@ names does.
13
13
  Versions follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Each date is
14
14
  the date that version was published.
15
15
 
16
+ ## [0.2.2] - 2026-09-24
17
+
18
+ ### Added
19
+
20
+ - `ucx install` and `ucx start` now save the model they set up as the default, so
21
+ `ucx run`, `ucx room`, `ucx loop`, `ucx acp` and `ucx a2a` use it without `--provider`
22
+ or `--model`, and say which saved model they are using (`ucx acp` and `ucx a2a` on
23
+ stderr). A model already saved, for example in the dashboard's Settings, is kept; when
24
+ it differs from the one setup just prepared, setup says both and how to switch. A
25
+ dashboard that was started with no model picks up a default saved later.
26
+ - `ucx llm use <model>` (with `--provider` and `--base-url`) changes the saved default.
27
+ It is the same setting as the dashboard's Settings. Command-line flags and environment
28
+ variables such as `LLM_PROVIDER`, `OLLAMA_MODEL` or a provider's API key variable still
29
+ come first, and `ucx llm use` says when one of them is set and takes priority.
30
+ - In Settings, the API key field for OpenAI, Anthropic and Gemini links to the page where
31
+ that provider issues keys, warns when a key does not look like one for the chosen
32
+ provider or confirms that its format is right, and removes spaces and one pair of quotes
33
+ from the ends of a pasted key.
34
+ - `ucx key list` shows whether `GEMINI_API_KEY` (or `GOOGLE_API_KEY`),
35
+ `ANTHROPIC_API_KEY` and `OPENAI_API_KEY` are set. `ucx key setup` opens the provider's
36
+ key page and writes the key you paste to a `.env` file: the nearest one in this folder
37
+ or above it, looking no higher than the top of the enclosing git checkout, where it
38
+ creates one if none is found. Outside a git checkout, it creates one in this folder if
39
+ none is found above. UClone-X does not read that file itself; load it into your shell,
40
+ or enter the key in Settings, for the key to be used.
41
+ - Every clone may load the skills approved for it, without `load_skill` in its tool list,
42
+ and Scout can read web pages as well as search.
43
+
44
+ ### Changed
45
+
46
+ - Ollama requests now ask for a 16K-token context window, unless `context_limit` or
47
+ `OLLAMA_CONTEXT_LENGTH` sets another, instead of leaving the size to Ollama, whose own
48
+ default was 4K on the machine the README's figures come from. A clone's request with
49
+ tool results then fits. It costs memory: for `qwen3:8b`, about 1.8 GB more than a 4K
50
+ window (an estimate, not a measurement).
51
+ - The built-in clones Scout, Pioneer, Guardian and Writer have rewritten instructions,
52
+ now written entirely in English; they still answer in the language you write in. With
53
+ a Qwen model, clones are also told not to use tools that change files or other state
54
+ for advice, brainstorming or general reasoning unless you ask for that change, to use
55
+ read-only search and inspection tools when an answer needs evidence from the workspace
56
+ or the web, and to answer in the language of the question. They are told that accuracy
57
+ requirements alone are no reason to decline a reasonable request, to follow your
58
+ instructions and style without imposing their own judgments, to give substantive
59
+ answers, and not to add moral lectures, unsolicited caveats or boilerplate disclaimers.
60
+ - The conversation list shows compact times ("now", "5m", "1d", "2w"), and a row's
61
+ rename and delete buttons appear when you point at the row or move keyboard focus to
62
+ it, so titles have more room.
63
+
64
+ ### Fixed
65
+
66
+ - Local image generation on an NVIDIA card now runs on the GPU in half precision, where
67
+ 0.2.1 ran it on the CPU. The engine tries CUDA, then Apple's MPS, then the CPU;
68
+ `ucx media status` names the device it will use, and image results name the GPU.
69
+ - When less than about 10 GB of GPU memory is free (an estimate), for example because
70
+ Ollama is keeping a model loaded on the same card, the image engine keeps SDXL's parts
71
+ in system memory, moves each onto the card only while it runs, and decodes the image in
72
+ tiles. This needs `accelerate`, which is now installed with the image engine. It is
73
+ slower and can still run out of memory; if it does, the image request fails with a
74
+ plain message and the memory is released, and when `accelerate` is missing the message
75
+ says how to add it.
76
+ - Under WSL2, the free GPU memory used for that decision now counts memory held by other
77
+ processes (read with NVML or `nvidia-smi`), so the image engine switches to the
78
+ low-memory mode when another program holds the card. When neither can be read, it uses
79
+ the low-memory mode.
80
+ - A model that Ollama cannot give tools to (for example `deepseek-r1:14b`) now ends the
81
+ turn with a short message naming the model and suggesting one that can use tools, such
82
+ as `qwen3:8b`, plus where to change it: `--model` for `ucx run`,
83
+ `ucx room retry <id> --model <model>` in a room, and Settings in the dashboard and in an
84
+ ACP client. The dashboard gives that remedy instead of offering a Retry that would fail
85
+ the same way.
86
+ - The one-line install now offers the image engine, which the published package
87
+ includes; 0.2.1 skipped it. When the installer reports success but the engine's core
88
+ packages (Pillow, diffusers, torch and transformers) cannot be imported, it says so
89
+ instead of reporting the engine installed. That check does not cover `accelerate`.
90
+ - Setting up Ollama runs its official installer up to 3 times when the download is cut
91
+ off, with a message between attempts. An attempt that runs out the 15-minute limit is
92
+ not repeated.
93
+ - A saved API key is used only with the provider it was saved for, and changing provider
94
+ with `ucx llm use` keeps it.
95
+
96
+ ### Compatibility
97
+
98
+ - `settings.json` written by 0.2.2 can carry `llm_api_key_provider`, which 0.2.1
99
+ ignores: 0.2.1 applies a saved API key to whichever provider is saved, and a Settings
100
+ save in 0.2.1 removes the field. 0.2.1's terminal commands also do not read the saved
101
+ default.
102
+ - A conversation that contains a turn refused because its model cannot use tools records
103
+ a reason 0.2.1 does not know, so 0.2.1 may not open it.
104
+
16
105
  ## [0.2.1] - 2026-09-24
17
106
 
18
107
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: uclone-x
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Next-Generation Event-Driven AI Agent Core & High-Performance Collaboration Framework
5
5
  Author-email: Kenny Lim <kennylim@uclone.net>
6
6
  License-Expression: Apache-2.0
@@ -21,6 +21,7 @@ Requires-Dist: pydantic-settings>=2.2.0
21
21
  Requires-Dist: pydantic>=2.7.0
22
22
  Requires-Dist: pyyaml>=6.0.1
23
23
  Provides-Extra: all
24
+ Requires-Dist: accelerate>=0.31.0; extra == 'all'
24
25
  Requires-Dist: anthropic>=0.25.0; extra == 'all'
25
26
  Requires-Dist: diffusers>=0.31.0; extra == 'all'
26
27
  Requires-Dist: fastapi>=0.110.0; extra == 'all'
@@ -71,6 +72,7 @@ Requires-Dist: anthropic>=0.25.0; extra == 'llm'
71
72
  Requires-Dist: google-genai>=0.1.0; extra == 'llm'
72
73
  Requires-Dist: openai>=1.25.0; extra == 'llm'
73
74
  Provides-Extra: media
75
+ Requires-Dist: accelerate>=0.31.0; extra == 'media'
74
76
  Requires-Dist: diffusers>=0.31.0; extra == 'media'
75
77
  Requires-Dist: pillow>=10.0.0; extra == 'media'
76
78
  Requires-Dist: torch>=2.2.0; extra == 'media'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "uclone-x"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Next-Generation Event-Driven AI Agent Core & High-Performance Collaboration Framework"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -101,8 +101,14 @@ ontology = [
101
101
  # `ucx media status` reporting ready for an engine that would fail on `import torch`.
102
102
  # The floors here are the same ones `IN_PROCESS_REQUIREMENTS` checks at runtime, so a
103
103
  # beginner who installs this extra and a beginner who follows the hint end up equivalent.
104
+ #
105
+ # `accelerate` is what `enable_model_cpu_offload` needs. It is not a runtime requirement
106
+ # (MPS and CPU never offload, and a CUDA card with room loads the whole pipeline), so it is
107
+ # not in `IN_PROCESS_REQUIREMENTS`; it is here so an NVIDIA card whose memory is shared
108
+ # with a resident Ollama model can still run SDXL instead of running out of memory.
104
109
  media = [
105
110
  "pillow>=10.0.0",
111
+ "accelerate>=0.31.0",
106
112
  "diffusers>=0.31.0",
107
113
  "torch>=2.2.0",
108
114
  "transformers>=4.40.0",
@@ -1,5 +1,5 @@
1
1
  """UClone-X: Next-Generation Event-Driven AI Agent Core & Collaboration Framework."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.2.2"
4
4
  __author__ = "Kenny Lim <kennylim@uclone.net>"
5
5
  __all__ = ["__version__"]
@@ -13,7 +13,7 @@ from dataclasses import dataclass, field
13
13
  from datetime import UTC, datetime
14
14
  from enum import Enum
15
15
  from pathlib import Path
16
- from typing import Any, Final, Literal, cast
16
+ from typing import Any, Final, Literal, TypeVar, cast
17
17
 
18
18
  from uclone_x.agent.grounding import describe, unsupported_specifics
19
19
  from uclone_x.agent.hooks import (
@@ -101,6 +101,7 @@ from uclone_x.errors import (
101
101
  LLMConnectorNotConfiguredError,
102
102
  LLMStreamInterruptedError,
103
103
  LLMTimeoutError,
104
+ ModelLacksToolSupportError,
104
105
  PathTraversalError,
105
106
  SessionMutationDuringTurnError,
106
107
  SessionStoreNotConfiguredError,
@@ -246,24 +247,61 @@ class _StreamProgress:
246
247
  return "".join(self.thinking_chunks) if self.thinking_chunks else None
247
248
 
248
249
 
249
- def _is_provider_timeout(exc: BaseException) -> bool:
250
- """Whether a turn failed because a provider ran past the ceiling the caller set.
250
+ _ChainedError = TypeVar("_ChainedError", bound=BaseException)
251
+
252
+
253
+ def _in_cause_chain(exc: BaseException, kind: type[_ChainedError]) -> _ChainedError | None:
254
+ """The first exception of type `kind` in `exc`'s `__cause__` chain, `exc` included.
251
255
 
252
256
  The chain is walked rather than the exception tested directly, because the streaming
253
257
  path does not deliver the connector's error itself: `_invoke_model` raises
254
258
  `LLMStreamInterruptedError` with the provider's error chained as `__cause__`. Testing
255
- only the outermost type would report a cut-off stream as an ordinary failure, which is
256
- the distinction this function exists to make.
259
+ only the outermost type would report a cut-off stream as an ordinary failure.
257
260
  """
258
261
  seen = exc
259
262
  for _ in range(_MAX_CAUSE_DEPTH):
260
- if isinstance(seen, LLMTimeoutError):
261
- return True
263
+ if isinstance(seen, kind):
264
+ return seen
262
265
  cause = seen.__cause__
263
266
  if cause is None:
264
- return False
267
+ return None
265
268
  seen = cause
266
- return False
269
+ return None
270
+
271
+
272
+ def _is_provider_timeout(exc: BaseException) -> bool:
273
+ """Whether a turn failed because a provider ran past the ceiling the caller set."""
274
+ return _in_cause_chain(exc, LLMTimeoutError) is not None
275
+
276
+
277
+ def _model_lacking_tools(exc: BaseException) -> ModelLacksToolSupportError | None:
278
+ """The refusal of a model that cannot take tools, if that is why a turn failed.
279
+
280
+ The error the turn reports when it is found: its message is written for the user,
281
+ while the `LLMStreamInterruptedError` wrapping it names classes and a status line.
282
+ """
283
+ return _in_cause_chain(exc, ModelLacksToolSupportError)
284
+
285
+
286
+ def _turn_failure(
287
+ exc: BaseException, stop_reason: TurnStopReason, agent_id: str
288
+ ) -> tuple[TurnStopReason, str]:
289
+ """The stop reason and `error` a turn that raised `exc` ends with, logged as it deserves.
290
+
291
+ A function of its own because `execute_turn` is at the edge of what the type checker
292
+ can analyse, and each branch added inline pushes it over.
293
+ """
294
+ if _is_provider_timeout(exc):
295
+ stop_reason = "provider_timeout"
296
+ lacking_tools = _model_lacking_tools(exc)
297
+ if lacking_tools is None:
298
+ logger.exception("Error executing turn for agent %s", agent_id)
299
+ return stop_reason, str(exc)
300
+ # A choice of model, not a fault in the code. With no handler set up, Python prints
301
+ # WARNING and above -- traceback included -- on the user's terminal, beside the plain
302
+ # sentence the CLI already shows.
303
+ logger.info("Turn for agent %s refused: %s", agent_id, lacking_tools)
304
+ return "model_without_tools", str(lacking_tools)
267
305
 
268
306
 
269
307
  class _AnchorWriter(Enum):
@@ -4530,9 +4568,7 @@ class BaseAgent(BaseAgentProtocol):
4530
4568
  # Named before anything else reads it. `error` carries the same fact as
4531
4569
  # prose, and prose is not something a caller can branch on without
4532
4570
  # guessing at a provider's wording (#1277).
4533
- if _is_provider_timeout(exc):
4534
- stop_reason = "provider_timeout"
4535
- logger.exception("Error executing turn for agent %s", self.agent_id)
4571
+ stop_reason, failure = _turn_failure(exc, stop_reason, self.agent_id)
4536
4572
  req_model = self._config.llm_config.model_name
4537
4573
 
4538
4574
  # Execute ON_ERROR hook
@@ -4626,7 +4662,7 @@ class BaseAgent(BaseAgentProtocol):
4626
4662
  tool_executions=tuple(tool_executions), # ran before the failure
4627
4663
  tool_executions_complete=not tools_unreported, # a failure mid-step
4628
4664
  is_completed=False,
4629
- error=str(exc),
4665
+ error=failure,
4630
4666
  stop_reason=stop_reason,
4631
4667
  correlation_id=correlation_id,
4632
4668
  provenance=failover_provenance,
@@ -116,6 +116,7 @@ BASE_PERSONA_TOOLS: tuple[str, ...] = (
116
116
  "file_search",
117
117
  "directory_list",
118
118
  "tool_result_read",
119
+ "load_skill",
119
120
  )
120
121
 
121
122
 
@@ -440,6 +441,7 @@ TurnStopReason = Literal[
440
441
  "step_results_over_window",
441
442
  "budget_exceeded",
442
443
  "provider_timeout",
444
+ "model_without_tools",
443
445
  "cancelled",
444
446
  ]
445
447
  """How a turn's step run ended: the vocabulary of `TURN_END.stop_reason` and `TurnResult`.
@@ -458,6 +460,11 @@ connector's ceiling and a probe whose host was not running both reached the repo
458
460
  distribution. A consumer that must not count a cut-off turn as the model's failure needs
459
461
  a field it can test, not a phrase it can grep. It is not a refusal -- `turn_refusal`
460
462
  leaves it `None` -- because a retry at a longer ceiling is exactly the right response.
463
+
464
+ `model_without_tools` is a provider's refusal of the chosen model because it cannot take
465
+ tool definitions, which every clone turn sends. Its `error` is a plain sentence naming the
466
+ model and the remedy, written to be shown as is. It is a refusal: a retry on the same model
467
+ is refused the same way, so `turn_refusal` maps it to `RoomTurnRefusal.MODEL_WITHOUT_TOOLS`.
461
468
  """
462
469
 
463
470
 
@@ -203,11 +203,26 @@ HERMES_STEERABILITY_POLICY: Final[str] = (
203
203
  "and respond naturally in the language in which the user communicates."
204
204
  )
205
205
 
206
+ QWEN_STEERABILITY_POLICY: Final[str] = (
207
+ "For Qwen runtime models, accuracy constraints govern precision and tool routing; on their own they are not a reason "
208
+ "to decline an otherwise reasonable request. "
209
+ "Tool Calling Discipline: Do not invoke file modification or state-changing action tools for conversational advice, brainstorming, or general reasoning. "
210
+ "Actively use read-only search and inspection tools when grounded evidence from the workspace or web is required, and invoke modification tools only when explicitly commanded. "
211
+ "Language Matching: Always respond fluently and consistently in the exact language used by the user's prompt. "
212
+ "Respect User Agency: Treat the user as an autonomous decision-maker, and follow their "
213
+ "instructions and style requests without imposing personal or ideological judgments. "
214
+ "Substantive Answers: Provide detailed, substantive answers that directly address the prompt. "
215
+ "No Moralizing: Do not add patronizing commentary, moral lectures, unsolicited caveats, or boilerplate disclaimers. "
216
+ "Tone Adaptation: Match the tone, intensity, and creative or stylistic direction requested by the user, "
217
+ "and respond naturally in the language in which the user communicates."
218
+ )
219
+
206
220
  #: Families whose framing departs from `STEERABILITY_POLICY`. A family absent from this
207
221
  #: mapping gets the default — that is a declared fallback, not a silent one, and
208
222
  #: `steerability_policy_for` is the single place it is resolved.
209
223
  _FAMILY_STEERABILITY: Final[Mapping[ModelFamily, str]] = {
210
224
  ModelFamily.HERMES: HERMES_STEERABILITY_POLICY,
225
+ ModelFamily.QWEN: QWEN_STEERABILITY_POLICY,
211
226
  }
212
227
 
213
228
 
@@ -306,6 +321,7 @@ __all__ = [
306
321
  "HONEST_REPORTING",
307
322
  "IDENTITY_GROUNDING",
308
323
  "IMAGE_GENERATION",
324
+ "QWEN_STEERABILITY_POLICY",
309
325
  "STEERABILITY_POLICY",
310
326
  "TASK_CAPABILITIES",
311
327
  "CapabilityFragment",
@@ -82,11 +82,15 @@ def start_a2a_server(
82
82
  from uclone_x.agent.composition import HostDependencies, compose_agent
83
83
  from uclone_x.agent.session import SessionStore
84
84
  from uclone_x.cli.agent_memory import memory_for_agent_id
85
- from uclone_x.llm.connectors.factory import create_llm_connector
85
+ from uclone_x.llm.connectors.factory import create_llm_connector, saved_choice_notice
86
86
  from uclone_x.shells.a2a_server import A2AServer
87
87
  from uclone_x.telemetry import TelemetryTracer
88
88
  from uclone_x.tools.registry import create_default_registry
89
89
 
90
+ saved_notice = saved_choice_notice()
91
+ if saved_notice is not None:
92
+ # stderr, as ACP does: what the server says to its caller stays on its own channel.
93
+ Console(stderr=True).print(saved_notice, markup=False, highlight=False)
90
94
  llm = create_llm_connector(fallback_to_mock=True)
91
95
  memory = memory_for_agent_id(agent_config.agent_id)
92
96
 
@@ -18,7 +18,7 @@ from uclone_x.agent.persona_store import DEFAULT_PERSONA_NAME
18
18
  from uclone_x.agent.session import SessionStore
19
19
  from uclone_x.cli.agent_memory import memory_for_agent_id
20
20
  from uclone_x.engine.event_bus import EventBus
21
- from uclone_x.llm.connectors.factory import create_llm_connector
21
+ from uclone_x.llm.connectors.factory import create_llm_connector, saved_choice_notice
22
22
  from uclone_x.shells.acp import ACPServer
23
23
  from uclone_x.shells.acp.server import SessionAgentFactory
24
24
  from uclone_x.telemetry import TelemetryTracer
@@ -30,6 +30,8 @@ acp_app = typer.Typer(
30
30
  no_args_is_help=True,
31
31
  )
32
32
  console = Console()
33
+ # ACP speaks over stdout, so anything said to the person goes to stderr.
34
+ err_console = Console(stderr=True)
33
35
 
34
36
 
35
37
  def session_agent_factory(config: AgentConfig, host: HostDependencies) -> SessionAgentFactory:
@@ -80,6 +82,9 @@ def start_acp_server(agent_id: str | None = None, persona: str = DEFAULT_PERSONA
80
82
  agent_config = agent_config_for_persona(persona_def, agent_id=agent_id)
81
83
 
82
84
  bus = EventBus()
85
+ saved_notice = saved_choice_notice()
86
+ if saved_notice is not None:
87
+ err_console.print(saved_notice, markup=False, highlight=False)
83
88
  llm = create_llm_connector(fallback_to_mock=True)
84
89
  store = SessionStore()
85
90
  tracer = TelemetryTracer()
@@ -370,6 +370,24 @@ def ollama_installer_env() -> dict[str, str]:
370
370
  return env
371
371
 
372
372
 
373
+ #: How many times the official Ollama installer is run before giving up. Its download is
374
+ #: a single ~1 GB archive with no resume, and a reset connection at 95% (measured on a Mac
375
+ #: on a flaky network: `curl: (56) Recv failure: Connection reset by peer`) otherwise
376
+ #: ends the whole setup at "not runnable".
377
+ OLLAMA_INSTALL_ATTEMPTS: Final = 3
378
+ #: Seconds to wait before running the installer again, multiplied by the attempt number.
379
+ OLLAMA_INSTALL_RETRY_DELAY_S: float = 5.0
380
+
381
+
382
+ def _ollama_runnable() -> tuple[str, str] | None:
383
+ """``(binary, version)`` when an ollama can be found and run, else ``None``."""
384
+ binary = ollama_binary()
385
+ version = ollama_version(binary)
386
+ if binary is None or version is None:
387
+ return None
388
+ return binary, version
389
+
390
+
373
391
  def install_ollama_platform() -> bool:
374
392
  """Install Ollama, then verify by running it — never by trusting the exit code.
375
393
 
@@ -380,30 +398,50 @@ def install_ollama_platform() -> bool:
380
398
  on the measured Mac mini (its `sudo ln` fails) while leaving a perfectly usable app
381
399
  behind, so exit code and reality disagree in both directions and only one of them can
382
400
  be measured.
401
+
402
+ The official installer is run up to ``OLLAMA_INSTALL_ATTEMPTS`` times while the binary
403
+ is still not runnable afterwards, because the usual cause is a download cut off part
404
+ way. A run that hits the 15-minute timeout is not repeated: another 15 minutes on the
405
+ same connection is not a remedy, and the person has already waited.
383
406
  """
384
407
  os_type = platform.system()
385
408
  if os_type == "Darwin" and shutil.which("brew"):
386
409
  console.print("[bold cyan]📦 Installing Ollama via Homebrew...[/bold cyan]")
387
410
  subprocess.run(["brew", "install", "ollama"], check=False, stdin=subprocess.DEVNULL)
411
+ found = _ollama_runnable()
388
412
  elif os_type in ("Darwin", "Linux"):
389
- console.print("[bold cyan]📦 Installing Ollama via official install script...[/bold cyan]")
390
- try:
391
- subprocess.run(
392
- "curl -fsSL https://ollama.com/install.sh | sh",
393
- shell=True,
394
- check=False,
395
- stdin=subprocess.DEVNULL,
396
- env=ollama_installer_env(),
397
- timeout=900,
398
- )
399
- except subprocess.TimeoutExpired:
413
+ found = None
414
+ for attempt in range(1, OLLAMA_INSTALL_ATTEMPTS + 1):
415
+ if attempt == 1:
416
+ console.print(
417
+ "[bold cyan]📦 Installing Ollama via official install script...[/bold cyan]"
418
+ )
419
+ try:
420
+ subprocess.run(
421
+ "curl -fsSL https://ollama.com/install.sh | sh",
422
+ shell=True,
423
+ check=False,
424
+ stdin=subprocess.DEVNULL,
425
+ env=ollama_installer_env(),
426
+ timeout=900,
427
+ )
428
+ except subprocess.TimeoutExpired:
429
+ console.print(
430
+ "[bold red]✖ The Ollama installer did not finish in 15 minutes.[/bold red]"
431
+ )
432
+ return False
433
+ except Exception as exc:
434
+ console.print(f"[bold red]✖ Could not run the Ollama installer: {exc}[/bold red]")
435
+ return False
436
+ found = _ollama_runnable()
437
+ if found is not None or attempt == OLLAMA_INSTALL_ATTEMPTS:
438
+ break
400
439
  console.print(
401
- "[bold red]✖ The Ollama installer did not finish in 15 minutes.[/bold red]"
440
+ f"[yellow]The Ollama download did not finish, which usually means the "
441
+ f"connection dropped. Trying again ({attempt + 1} of "
442
+ f"{OLLAMA_INSTALL_ATTEMPTS})...[/yellow]"
402
443
  )
403
- return False
404
- except Exception as exc:
405
- console.print(f"[bold red]✖ Could not run the Ollama installer: {exc}[/bold red]")
406
- return False
444
+ time.sleep(OLLAMA_INSTALL_RETRY_DELAY_S * attempt)
407
445
  else:
408
446
  console.print(
409
447
  f"[bold red]✖ No automated Ollama install for {escape(os_type)}. "
@@ -411,14 +449,13 @@ def install_ollama_platform() -> bool:
411
449
  )
412
450
  return False
413
451
 
414
- binary = ollama_binary()
415
- version = ollama_version(binary)
416
- if binary is None or version is None:
452
+ if found is None:
417
453
  console.print(
418
454
  "[bold red]✖ Ollama is still not runnable after the install.[/bold red] "
419
455
  "Looked on PATH and at " + escape(", ".join(OLLAMA_APP_BINARIES)) + "."
420
456
  )
421
457
  return False
458
+ binary, version = found
422
459
  console.print(f"[bold green]✔ Ollama ready:[/bold green] {escape(binary)} ({escape(version)})")
423
460
  return True
424
461
 
@@ -675,9 +712,9 @@ def diffusers_install_command() -> list[str] | None:
675
712
  that stops at `diffusers` leaves the engine unable to generate (#1095).
676
713
  """
677
714
  from uclone_x.core.environment_install import installer_command
678
- from uclone_x.tools.builtin.image import IN_PROCESS_REQUIREMENTS
715
+ from uclone_x.tools.builtin.image import IN_PROCESS_INSTALL_REQUIREMENTS
679
716
 
680
- packages = [requirement for _, requirement, _ in IN_PROCESS_REQUIREMENTS]
717
+ packages = [requirement for _, requirement, _ in IN_PROCESS_INSTALL_REQUIREMENTS]
681
718
  return installer_command(*packages)
682
719
 
683
720
 
@@ -1223,6 +1260,76 @@ class LocalSetupResult(NamedTuple):
1223
1260
  return [part.summary_line() for part in (self.llm, self.image) if part is not None]
1224
1261
 
1225
1262
 
1263
+ def _same_address(saved: str | None, prepared: str | None) -> bool:
1264
+ """Whether a saved Ollama address reaches the daemon setup prepared.
1265
+
1266
+ No saved address means the connector resolves the usual one, which is the one setup
1267
+ checked, so it counts as the same.
1268
+ """
1269
+ if saved is None or prepared is None:
1270
+ return True
1271
+ return saved.rstrip("/") == prepared.rstrip("/")
1272
+
1273
+
1274
+ def remember_local_llm(result: LlmSetup) -> None:
1275
+ """Save the model setup just made ready as the default, unless one is already saved.
1276
+
1277
+ Without this, `ucx install --yes` printed `setup llm: ready model=...` and the next
1278
+ `ucx run` refused with "No LLM provider is configured": nothing setup did was anywhere
1279
+ the terminal commands looked. The choice goes to the file the dashboard's Settings
1280
+ already keep, so there is one saved choice for every head. A choice already saved --
1281
+ by the person in Settings, or by an earlier install -- is kept; setup only fills in
1282
+ what is missing.
1283
+
1284
+ What is printed is read back from the file after the write, not assumed from it: when
1285
+ the saved default is not what setup just prepared, the person is told both, and how to
1286
+ switch. A failure to save is reported and does not fail the setup: the model is still
1287
+ ready, and `--provider`/`--model` or the dashboard still reach it.
1288
+ """
1289
+ from uclone_x.llm.connectors.saved_choice import read_saved_choice, remember_choice_if_unset
1290
+
1291
+ model = escape(result.model)
1292
+ try:
1293
+ written, _before = remember_choice_if_unset(
1294
+ provider="ollama", model=result.model, base_url=result.endpoint
1295
+ )
1296
+ saved = read_saved_choice()
1297
+ except (OSError, ValueError):
1298
+ console.print(
1299
+ "[yellow]Could not save this model as the default, so `ucx run` will not pick "
1300
+ "it up on its own. Choose it in the dashboard's Settings, or pass "
1301
+ f"--provider ollama --model {model}.[/yellow]"
1302
+ )
1303
+ return
1304
+ if saved is None:
1305
+ return
1306
+ switch = (
1307
+ f"To use {model} instead, run `ucx llm use {model}` or choose it in the "
1308
+ "dashboard's Settings."
1309
+ )
1310
+ if saved.provider != "ollama" or saved.model not in (None, result.model):
1311
+ what = escape(saved.model or saved.provider)
1312
+ if saved.provider != "ollama":
1313
+ what = f"{what} ({escape(saved.provider)})"
1314
+ console.print(
1315
+ f"[yellow]{what} is already saved as the default model, so it stays the "
1316
+ f"default rather than {model}, which setup just prepared. {switch}[/yellow]"
1317
+ )
1318
+ elif not _same_address(saved.base_url, result.endpoint):
1319
+ console.print(
1320
+ f"[yellow]The saved default asks for {model} at {escape(saved.base_url or '')}, "
1321
+ f"but setup prepared it at {escape(result.endpoint)}. To use this one, run "
1322
+ f"`ucx llm use {model} --base-url {escape(result.endpoint)}`.[/yellow]"
1323
+ )
1324
+ elif written:
1325
+ console.print(
1326
+ f"[green]✔ Saved {model} as the default model for `ucx run`, "
1327
+ "rooms and the dashboard.[/green]"
1328
+ )
1329
+ else:
1330
+ console.print(f"[dim]Kept {model}, the model already saved as the default.[/dim]")
1331
+
1332
+
1226
1333
  def run_local_setup(
1227
1334
  *,
1228
1335
  llm: bool = True,
@@ -1238,8 +1345,11 @@ def run_local_setup(
1238
1345
  copy of this sequence inline; two copies of "prepare the local models" is how one of
1239
1346
  them silently stops matching the other.
1240
1347
  """
1348
+ llm_result = setup_local_llm(interactive=interactive, assume_yes=assume_yes) if llm else None
1349
+ if llm_result is not None and llm_result.ready:
1350
+ remember_local_llm(llm_result)
1241
1351
  return LocalSetupResult(
1242
- llm=(setup_local_llm(interactive=interactive, assume_yes=assume_yes) if llm else None),
1352
+ llm=llm_result,
1243
1353
  image=(
1244
1354
  setup_local_image(
1245
1355
  interactive=interactive,
@@ -124,7 +124,9 @@ def eval_run(
124
124
  ] = "all",
125
125
  model: Annotated[
126
126
  str | None,
127
- typer.Option("--model", "-m", help="Target model identifier override (e.g. qwen3:1.7b)"),
127
+ typer.Option(
128
+ "--model", "-m", help="Target model identifier override (e.g. qwen3:8b, hermes3:8b)"
129
+ ),
128
130
  ] = None,
129
131
  provider: Annotated[
130
132
  str | None,