devsper 2.1.6__py3-none-any.whl

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 (375) hide show
  1. devsper/__init__.py +14 -0
  2. devsper/agents/a2a/__init__.py +27 -0
  3. devsper/agents/a2a/client.py +126 -0
  4. devsper/agents/a2a/discovery.py +24 -0
  5. devsper/agents/a2a/server.py +128 -0
  6. devsper/agents/a2a/tool_adapter.py +68 -0
  7. devsper/agents/a2a/types.py +49 -0
  8. devsper/agents/agent.py +602 -0
  9. devsper/agents/critic.py +80 -0
  10. devsper/agents/message_bus.py +124 -0
  11. devsper/agents/roles.py +181 -0
  12. devsper/agents/run_agent.py +78 -0
  13. devsper/analytics/__init__.py +5 -0
  14. devsper/analytics/tool_analytics.py +78 -0
  15. devsper/audit/__init__.py +5 -0
  16. devsper/audit/logger.py +214 -0
  17. devsper/bus/__init__.py +29 -0
  18. devsper/bus/backends/__init__.py +5 -0
  19. devsper/bus/backends/base.py +38 -0
  20. devsper/bus/backends/memory.py +55 -0
  21. devsper/bus/backends/redis.py +146 -0
  22. devsper/bus/message.py +56 -0
  23. devsper/bus/schema_version.py +3 -0
  24. devsper/bus/topics.py +19 -0
  25. devsper/cache/__init__.py +6 -0
  26. devsper/cache/embedding_index.py +98 -0
  27. devsper/cache/hashing.py +24 -0
  28. devsper/cache/store.py +153 -0
  29. devsper/cache/task_cache.py +191 -0
  30. devsper/cli/__init__.py +6 -0
  31. devsper/cli/commands/reg.py +733 -0
  32. devsper/cli/github_oauth.py +157 -0
  33. devsper/cli/init.py +637 -0
  34. devsper/cli/main.py +2956 -0
  35. devsper/cli/run_progress.py +103 -0
  36. devsper/cli/ui/__init__.py +65 -0
  37. devsper/cli/ui/components.py +94 -0
  38. devsper/cli/ui/errors.py +104 -0
  39. devsper/cli/ui/logging.py +120 -0
  40. devsper/cli/ui/onboarding.py +102 -0
  41. devsper/cli/ui/progress.py +43 -0
  42. devsper/cli/ui/run_view.py +308 -0
  43. devsper/cli/ui/theme.py +40 -0
  44. devsper/cluster/__init__.py +29 -0
  45. devsper/cluster/election.py +84 -0
  46. devsper/cluster/local.py +97 -0
  47. devsper/cluster/node_info.py +77 -0
  48. devsper/cluster/registry.py +71 -0
  49. devsper/cluster/router.py +117 -0
  50. devsper/cluster/state_backend.py +105 -0
  51. devsper/compliance/__init__.py +5 -0
  52. devsper/compliance/pii.py +147 -0
  53. devsper/config/__init__.py +52 -0
  54. devsper/config/config_loader.py +121 -0
  55. devsper/config/defaults.py +77 -0
  56. devsper/config/resolver.py +342 -0
  57. devsper/config/schema.py +237 -0
  58. devsper/credentials/__init__.py +19 -0
  59. devsper/credentials/cli.py +197 -0
  60. devsper/credentials/migration.py +124 -0
  61. devsper/credentials/store.py +142 -0
  62. devsper/dashboard/__init__.py +9 -0
  63. devsper/dashboard/dashboard.py +87 -0
  64. devsper/dev/__init__.py +25 -0
  65. devsper/dev/builder.py +195 -0
  66. devsper/dev/debugger.py +95 -0
  67. devsper/dev/repo_index.py +138 -0
  68. devsper/dev/sandbox.py +203 -0
  69. devsper/dev/scaffold.py +122 -0
  70. devsper/embeddings/__init__.py +5 -0
  71. devsper/embeddings/service.py +36 -0
  72. devsper/explainability/__init__.py +14 -0
  73. devsper/explainability/decision_tree.py +104 -0
  74. devsper/explainability/rationale.py +38 -0
  75. devsper/explainability/simulation.py +56 -0
  76. devsper/hitl/__init__.py +13 -0
  77. devsper/hitl/approval.py +160 -0
  78. devsper/hitl/escalation.py +95 -0
  79. devsper/intelligence/__init__.py +9 -0
  80. devsper/intelligence/adaptation.py +88 -0
  81. devsper/intelligence/analysis/__init__.py +19 -0
  82. devsper/intelligence/analysis/analyzer.py +71 -0
  83. devsper/intelligence/analysis/cost_estimator.py +66 -0
  84. devsper/intelligence/analysis/formatter.py +103 -0
  85. devsper/intelligence/analysis/run_report.py +402 -0
  86. devsper/intelligence/learning_engine.py +92 -0
  87. devsper/intelligence/strategies/__init__.py +23 -0
  88. devsper/intelligence/strategies/base.py +14 -0
  89. devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
  90. devsper/intelligence/strategies/data_science_strategy.py +33 -0
  91. devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
  92. devsper/intelligence/strategies/experiment_strategy.py +33 -0
  93. devsper/intelligence/strategies/research_strategy.py +34 -0
  94. devsper/intelligence/strategy_selector.py +84 -0
  95. devsper/intelligence/synthesis.py +132 -0
  96. devsper/intelligence/task_optimizer.py +92 -0
  97. devsper/knowledge/__init__.py +5 -0
  98. devsper/knowledge/extractor.py +204 -0
  99. devsper/knowledge/knowledge_graph.py +184 -0
  100. devsper/knowledge/query.py +285 -0
  101. devsper/memory/__init__.py +35 -0
  102. devsper/memory/consolidation.py +138 -0
  103. devsper/memory/embeddings.py +60 -0
  104. devsper/memory/memory_index.py +97 -0
  105. devsper/memory/memory_router.py +62 -0
  106. devsper/memory/memory_store.py +221 -0
  107. devsper/memory/memory_types.py +54 -0
  108. devsper/memory/namespaces.py +45 -0
  109. devsper/memory/scoring.py +77 -0
  110. devsper/memory/summarizer.py +52 -0
  111. devsper/nodes/__init__.py +5 -0
  112. devsper/nodes/controller.py +449 -0
  113. devsper/nodes/rpc.py +127 -0
  114. devsper/nodes/single.py +161 -0
  115. devsper/nodes/worker.py +506 -0
  116. devsper/orchestration/__init__.py +19 -0
  117. devsper/orchestration/meta_planner.py +239 -0
  118. devsper/orchestration/priority_queue.py +61 -0
  119. devsper/plugins/__init__.py +19 -0
  120. devsper/plugins/marketplace/__init__.py +0 -0
  121. devsper/plugins/plugin_loader.py +70 -0
  122. devsper/plugins/plugin_registry.py +34 -0
  123. devsper/plugins/registry.py +83 -0
  124. devsper/protocols/__init__.py +6 -0
  125. devsper/providers/__init__.py +17 -0
  126. devsper/providers/anthropic.py +84 -0
  127. devsper/providers/base.py +75 -0
  128. devsper/providers/complexity_router.py +94 -0
  129. devsper/providers/gemini.py +36 -0
  130. devsper/providers/github.py +180 -0
  131. devsper/providers/model_router.py +40 -0
  132. devsper/providers/openai.py +105 -0
  133. devsper/providers/router/__init__.py +21 -0
  134. devsper/providers/router/backends/__init__.py +19 -0
  135. devsper/providers/router/backends/anthropic_backend.py +111 -0
  136. devsper/providers/router/backends/custom_backend.py +138 -0
  137. devsper/providers/router/backends/gemini_backend.py +89 -0
  138. devsper/providers/router/backends/github_backend.py +165 -0
  139. devsper/providers/router/backends/ollama_backend.py +104 -0
  140. devsper/providers/router/backends/openai_backend.py +142 -0
  141. devsper/providers/router/backends/vllm_backend.py +35 -0
  142. devsper/providers/router/base.py +60 -0
  143. devsper/providers/router/factory.py +92 -0
  144. devsper/providers/router/legacy.py +101 -0
  145. devsper/providers/router/router.py +135 -0
  146. devsper/reasoning/__init__.py +12 -0
  147. devsper/reasoning/graph.py +59 -0
  148. devsper/reasoning/nodes.py +20 -0
  149. devsper/reasoning/store.py +67 -0
  150. devsper/runtime/__init__.py +12 -0
  151. devsper/runtime/health.py +88 -0
  152. devsper/runtime/replay.py +53 -0
  153. devsper/runtime/replay_engine.py +142 -0
  154. devsper/runtime/run_history.py +204 -0
  155. devsper/runtime/telemetry.py +116 -0
  156. devsper/runtime/visualize.py +58 -0
  157. devsper/sandbox/__init__.py +13 -0
  158. devsper/sandbox/sandbox.py +161 -0
  159. devsper/swarm/checkpointer.py +65 -0
  160. devsper/swarm/executor.py +558 -0
  161. devsper/swarm/map_reduce.py +44 -0
  162. devsper/swarm/planner.py +197 -0
  163. devsper/swarm/prefetcher.py +91 -0
  164. devsper/swarm/scheduler.py +153 -0
  165. devsper/swarm/speculation.py +47 -0
  166. devsper/swarm/swarm.py +562 -0
  167. devsper/tools/__init__.py +33 -0
  168. devsper/tools/base.py +29 -0
  169. devsper/tools/code_intelligence/__init__.py +13 -0
  170. devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
  171. devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
  172. devsper/tools/code_intelligence/codebase_indexer.py +71 -0
  173. devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
  174. devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
  175. devsper/tools/code_intelligence/large_function_detector.py +68 -0
  176. devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
  177. devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
  178. devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
  179. devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
  180. devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
  181. devsper/tools/coding/__init__.py +12 -0
  182. devsper/tools/coding/analyze_code_complexity.py +48 -0
  183. devsper/tools/coding/dependency_analyzer.py +42 -0
  184. devsper/tools/coding/extract_functions.py +38 -0
  185. devsper/tools/coding/format_python.py +50 -0
  186. devsper/tools/coding/generate_docstrings.py +40 -0
  187. devsper/tools/coding/generate_unit_tests.py +42 -0
  188. devsper/tools/coding/lint_python.py +51 -0
  189. devsper/tools/coding/refactor_function.py +41 -0
  190. devsper/tools/coding/repo_structure_map.py +54 -0
  191. devsper/tools/coding/run_python.py +53 -0
  192. devsper/tools/data/__init__.py +12 -0
  193. devsper/tools/data/column_type_detection.py +64 -0
  194. devsper/tools/data/csv_summary.py +52 -0
  195. devsper/tools/data/dataframe_filter.py +51 -0
  196. devsper/tools/data/dataframe_groupby.py +47 -0
  197. devsper/tools/data/dataframe_stats.py +38 -0
  198. devsper/tools/data/dataset_sampling.py +55 -0
  199. devsper/tools/data/dataset_schema.py +45 -0
  200. devsper/tools/data/json_pretty_print.py +37 -0
  201. devsper/tools/data/json_query.py +46 -0
  202. devsper/tools/data/missing_value_report.py +47 -0
  203. devsper/tools/data_science/__init__.py +13 -0
  204. devsper/tools/data_science/correlation_heatmap.py +72 -0
  205. devsper/tools/data_science/dataset_bias_detector.py +49 -0
  206. devsper/tools/data_science/dataset_distribution_report.py +64 -0
  207. devsper/tools/data_science/dataset_drift_detector.py +64 -0
  208. devsper/tools/data_science/dataset_outlier_detector.py +65 -0
  209. devsper/tools/data_science/dataset_profile.py +76 -0
  210. devsper/tools/data_science/distributed_dataset_processor.py +54 -0
  211. devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
  212. devsper/tools/data_science/feature_importance_estimator.py +82 -0
  213. devsper/tools/data_science/model_input_validator.py +59 -0
  214. devsper/tools/data_science/time_series_analyzer.py +57 -0
  215. devsper/tools/documents/__init__.py +11 -0
  216. devsper/tools/documents/_docproc.py +56 -0
  217. devsper/tools/documents/document_to_markdown.py +29 -0
  218. devsper/tools/documents/extract_document_images.py +39 -0
  219. devsper/tools/documents/extract_document_text.py +29 -0
  220. devsper/tools/documents/extract_equations.py +36 -0
  221. devsper/tools/documents/extract_tables.py +47 -0
  222. devsper/tools/documents/summarize_document.py +42 -0
  223. devsper/tools/documents/write_latex_document.py +133 -0
  224. devsper/tools/documents/write_markdown_document.py +89 -0
  225. devsper/tools/documents/write_word_document.py +149 -0
  226. devsper/tools/experiments/__init__.py +13 -0
  227. devsper/tools/experiments/bootstrap_estimator.py +54 -0
  228. devsper/tools/experiments/experiment_report_generator.py +50 -0
  229. devsper/tools/experiments/experiment_tracker.py +36 -0
  230. devsper/tools/experiments/grid_search_runner.py +50 -0
  231. devsper/tools/experiments/model_benchmark_runner.py +45 -0
  232. devsper/tools/experiments/monte_carlo_experiment.py +38 -0
  233. devsper/tools/experiments/parameter_sweep_runner.py +51 -0
  234. devsper/tools/experiments/result_comparator.py +58 -0
  235. devsper/tools/experiments/simulation_runner.py +43 -0
  236. devsper/tools/experiments/statistical_significance_test.py +56 -0
  237. devsper/tools/experiments/swarm_map_reduce.py +42 -0
  238. devsper/tools/filesystem/__init__.py +12 -0
  239. devsper/tools/filesystem/append_file.py +42 -0
  240. devsper/tools/filesystem/file_hash.py +40 -0
  241. devsper/tools/filesystem/file_line_count.py +36 -0
  242. devsper/tools/filesystem/file_metadata.py +38 -0
  243. devsper/tools/filesystem/file_preview.py +55 -0
  244. devsper/tools/filesystem/find_large_files.py +50 -0
  245. devsper/tools/filesystem/list_directory.py +39 -0
  246. devsper/tools/filesystem/read_file.py +35 -0
  247. devsper/tools/filesystem/search_files.py +60 -0
  248. devsper/tools/filesystem/write_file.py +41 -0
  249. devsper/tools/flagship/__init__.py +15 -0
  250. devsper/tools/flagship/distributed_document_analysis.py +77 -0
  251. devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
  252. devsper/tools/flagship/repository_semantic_map.py +99 -0
  253. devsper/tools/flagship/research_graph_builder.py +111 -0
  254. devsper/tools/flagship/swarm_experiment_runner.py +86 -0
  255. devsper/tools/knowledge/__init__.py +10 -0
  256. devsper/tools/knowledge/citation_graph_builder.py +69 -0
  257. devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
  258. devsper/tools/knowledge/corpus_builder.py +66 -0
  259. devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
  260. devsper/tools/knowledge/document_corpus_summary.py +68 -0
  261. devsper/tools/knowledge/document_topic_extractor.py +58 -0
  262. devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
  263. devsper/tools/knowledge/timeline_extractor.py +59 -0
  264. devsper/tools/math/__init__.py +12 -0
  265. devsper/tools/math/calculate_expression.py +52 -0
  266. devsper/tools/math/correlation.py +44 -0
  267. devsper/tools/math/distribution_summary.py +39 -0
  268. devsper/tools/math/histogram.py +53 -0
  269. devsper/tools/math/linear_regression.py +47 -0
  270. devsper/tools/math/matrix_multiply.py +38 -0
  271. devsper/tools/math/mean_std.py +35 -0
  272. devsper/tools/math/monte_carlo_simulation.py +43 -0
  273. devsper/tools/math/polynomial_fit.py +40 -0
  274. devsper/tools/math/random_sample.py +36 -0
  275. devsper/tools/mcp/__init__.py +23 -0
  276. devsper/tools/mcp/adapter.py +53 -0
  277. devsper/tools/mcp/client.py +235 -0
  278. devsper/tools/mcp/discovery.py +53 -0
  279. devsper/tools/memory/__init__.py +16 -0
  280. devsper/tools/memory/delete_memory.py +25 -0
  281. devsper/tools/memory/list_memory.py +34 -0
  282. devsper/tools/memory/search_memory.py +36 -0
  283. devsper/tools/memory/store_memory.py +47 -0
  284. devsper/tools/memory/summarize_memory.py +41 -0
  285. devsper/tools/memory/tag_memory.py +47 -0
  286. devsper/tools/pipelines.py +92 -0
  287. devsper/tools/registry.py +39 -0
  288. devsper/tools/research/__init__.py +12 -0
  289. devsper/tools/research/arxiv_download.py +55 -0
  290. devsper/tools/research/arxiv_search.py +58 -0
  291. devsper/tools/research/citation_extractor.py +35 -0
  292. devsper/tools/research/duckduckgo_search.py +42 -0
  293. devsper/tools/research/paper_metadata_extractor.py +45 -0
  294. devsper/tools/research/paper_summarizer.py +41 -0
  295. devsper/tools/research/research_question_generator.py +39 -0
  296. devsper/tools/research/topic_cluster.py +46 -0
  297. devsper/tools/research/web_search.py +47 -0
  298. devsper/tools/research/wikipedia_lookup.py +50 -0
  299. devsper/tools/research_advanced/__init__.py +14 -0
  300. devsper/tools/research_advanced/citation_context_extractor.py +60 -0
  301. devsper/tools/research_advanced/literature_review_generator.py +79 -0
  302. devsper/tools/research_advanced/methodology_extractor.py +58 -0
  303. devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
  304. devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
  305. devsper/tools/research_advanced/paper_method_comparator.py +62 -0
  306. devsper/tools/research_advanced/paper_similarity_search.py +69 -0
  307. devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
  308. devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
  309. devsper/tools/research_advanced/research_gap_finder.py +71 -0
  310. devsper/tools/research_advanced/research_topic_mapper.py +69 -0
  311. devsper/tools/research_advanced/swarm_literature_review.py +58 -0
  312. devsper/tools/scoring/__init__.py +52 -0
  313. devsper/tools/scoring/report.py +44 -0
  314. devsper/tools/scoring/scorer.py +39 -0
  315. devsper/tools/scoring/selector.py +61 -0
  316. devsper/tools/scoring/store.py +267 -0
  317. devsper/tools/selector.py +130 -0
  318. devsper/tools/system/__init__.py +12 -0
  319. devsper/tools/system/cpu_usage.py +22 -0
  320. devsper/tools/system/disk_usage.py +35 -0
  321. devsper/tools/system/environment_variables.py +29 -0
  322. devsper/tools/system/memory_usage.py +23 -0
  323. devsper/tools/system/pip_install.py +44 -0
  324. devsper/tools/system/pip_search.py +29 -0
  325. devsper/tools/system/process_list.py +34 -0
  326. devsper/tools/system/python_package_list.py +40 -0
  327. devsper/tools/system/run_shell_command.py +51 -0
  328. devsper/tools/system/system_info.py +26 -0
  329. devsper/tools/tool_runner.py +122 -0
  330. devsper/tui/__init__.py +5 -0
  331. devsper/tui/activity_feed_view.py +73 -0
  332. devsper/tui/adaptive_tasks_view.py +75 -0
  333. devsper/tui/agent_role_view.py +35 -0
  334. devsper/tui/app.py +395 -0
  335. devsper/tui/dashboard_screen.py +290 -0
  336. devsper/tui/dev_view.py +99 -0
  337. devsper/tui/inject_screen.py +73 -0
  338. devsper/tui/knowledge_graph_view.py +46 -0
  339. devsper/tui/layout.py +43 -0
  340. devsper/tui/logs_view.py +83 -0
  341. devsper/tui/memory_view.py +58 -0
  342. devsper/tui/performance_view.py +33 -0
  343. devsper/tui/reasoning_graph_view.py +39 -0
  344. devsper/tui/results_view.py +139 -0
  345. devsper/tui/swarm_view.py +37 -0
  346. devsper/tui/task_detail_screen.py +55 -0
  347. devsper/tui/task_view.py +103 -0
  348. devsper/types/event.py +97 -0
  349. devsper/types/exceptions.py +21 -0
  350. devsper/types/swarm.py +41 -0
  351. devsper/types/task.py +80 -0
  352. devsper/upgrade/__init__.py +21 -0
  353. devsper/upgrade/changelog.py +124 -0
  354. devsper/upgrade/cli.py +145 -0
  355. devsper/upgrade/installer.py +103 -0
  356. devsper/upgrade/notifier.py +52 -0
  357. devsper/upgrade/version_check.py +121 -0
  358. devsper/utils/event_logger.py +88 -0
  359. devsper/utils/http.py +43 -0
  360. devsper/utils/models.py +54 -0
  361. devsper/visualization/__init__.py +5 -0
  362. devsper/visualization/dag_export.py +67 -0
  363. devsper/workflow/__init__.py +18 -0
  364. devsper/workflow/conditions.py +157 -0
  365. devsper/workflow/context.py +108 -0
  366. devsper/workflow/loader.py +156 -0
  367. devsper/workflow/resolver.py +109 -0
  368. devsper/workflow/runner.py +562 -0
  369. devsper/workflow/schema.py +63 -0
  370. devsper/workflow/validator.py +128 -0
  371. devsper-2.1.6.dist-info/METADATA +346 -0
  372. devsper-2.1.6.dist-info/RECORD +375 -0
  373. devsper-2.1.6.dist-info/WHEEL +4 -0
  374. devsper-2.1.6.dist-info/entry_points.txt +3 -0
  375. devsper-2.1.6.dist-info/licenses/LICENSE +639 -0
@@ -0,0 +1,71 @@
1
+ """Cluster registry backed by Redis hash."""
2
+
3
+ from devsper.cluster.node_info import NodeInfo, NodeRole
4
+
5
+ REGISTRY_KEY_PREFIX = "devsper:cluster:"
6
+ REGISTRY_NODES_SUFFIX = ":nodes"
7
+ REGISTRY_TTL = 60
8
+
9
+
10
+ class ClusterRegistry:
11
+ """Backed by Redis hash: devsper:cluster:{run_id}:nodes. TTL 60s refreshed by heartbeat."""
12
+
13
+ def __init__(self, redis_client: object, run_id: str) -> None:
14
+ self._redis = redis_client
15
+ self._run_id = run_id
16
+ self._key = f"{REGISTRY_KEY_PREFIX}{run_id}{REGISTRY_NODES_SUFFIX}"
17
+
18
+ async def register(self, node: NodeInfo) -> None:
19
+ await self._redis.hset(self._key, node.node_id, node.to_json())
20
+ await self._redis.expire(self._key, REGISTRY_TTL)
21
+
22
+ async def heartbeat(self, node_id: str, updates: dict) -> None:
23
+ node = await self.get_node(node_id)
24
+ if node is None:
25
+ return
26
+ d = node.to_dict()
27
+ d.update(updates)
28
+ updated = NodeInfo.from_dict(d)
29
+ await self._redis.hset(self._key, node_id, updated.to_json())
30
+ await self._redis.expire(self._key, REGISTRY_TTL)
31
+
32
+ async def deregister(self, node_id: str) -> None:
33
+ await self._redis.hdel(self._key, node_id)
34
+
35
+ async def get_all(self) -> list[NodeInfo]:
36
+ raw = await self._redis.hgetall(self._key)
37
+ out: list[NodeInfo] = []
38
+ for _k, v in raw.items():
39
+ val = v.decode("utf-8") if isinstance(v, bytes) else v
40
+ try:
41
+ out.append(NodeInfo.from_json(val))
42
+ except Exception:
43
+ pass
44
+ return out
45
+
46
+ async def get_node(self, node_id: str) -> NodeInfo | None:
47
+ raw = await self._redis.hget(self._key, node_id)
48
+ if raw is None:
49
+ return None
50
+ val = raw.decode("utf-8") if isinstance(raw, bytes) else raw
51
+ try:
52
+ return NodeInfo.from_json(val)
53
+ except Exception:
54
+ return None
55
+
56
+ async def get_controllers(self) -> list[NodeInfo]:
57
+ all_nodes = await self.get_all()
58
+ return [
59
+ n for n in all_nodes
60
+ if n.role in (NodeRole.CONTROLLER, NodeRole.HYBRID)
61
+ ]
62
+
63
+ async def get_workers(self) -> list[NodeInfo]:
64
+ all_nodes = await self.get_all()
65
+ return [
66
+ n for n in all_nodes
67
+ if n.role in (NodeRole.WORKER, NodeRole.HYBRID)
68
+ ]
69
+
70
+ async def is_healthy(self) -> bool:
71
+ return len(await self.get_controllers()) >= 1
@@ -0,0 +1,117 @@
1
+ """Task routing with affinity: memory, tool cache, load."""
2
+
3
+ from devsper.cluster.node_info import NodeInfo
4
+ from devsper.types.task import Task
5
+
6
+
7
+ def _parse_version(version: str) -> tuple[int, int]:
8
+ """Return (major, minor) from version string like 1.10 or 1.9.0."""
9
+ parts = version.replace("-", ".").split(".")
10
+ try:
11
+ major = int(parts[0]) if parts else 0
12
+ minor = int(parts[1]) if len(parts) > 1 else 0
13
+ except (ValueError, IndexError):
14
+ major, minor = 0, 0
15
+ return (major, minor)
16
+
17
+
18
+ def _clamp(value: float, lo: float, hi: float) -> float:
19
+ return max(lo, min(hi, value))
20
+
21
+
22
+ class TaskRouter:
23
+ """Route ready tasks to the best available worker. Scoring: memory_affinity, tool_affinity, load_score."""
24
+
25
+ # Weights
26
+ MEMORY_WEIGHT = 0.35
27
+ TOOL_WEIGHT = 0.25
28
+ LOAD_WEIGHT = 0.40
29
+
30
+ def __init__(self, controller_version: str = "1.9.0") -> None:
31
+ self._controller_version = controller_version
32
+ self._ctrl_major, self._ctrl_minor = _parse_version(controller_version)
33
+ self._round_robin_index: int = 0 # spread tasks when scores tie
34
+
35
+ def route(
36
+ self,
37
+ task: Task,
38
+ workers: list[NodeInfo],
39
+ worker_stats: dict[str, dict],
40
+ ) -> NodeInfo | None:
41
+ """Score all workers; return highest-scored; when scores tie, round-robin so tasks spread."""
42
+ if not workers:
43
+ return None
44
+ eligible = [w for w in workers if self._version_compatible(w)]
45
+ if not eligible:
46
+ return None
47
+ scored = [(self._score(task, w, worker_stats.get(w.node_id, {})), w) for w in eligible]
48
+ best_score = max(s[0] for s in scored)
49
+ if best_score < 0:
50
+ return None
51
+ # Treat scores within 1e-6 as tied so we round-robin and spread (avoids one worker getting all + 429)
52
+ eps = 1e-6
53
+ tied = [w for s, w in scored if abs(s - best_score) <= eps]
54
+ # Round-robin among tied workers so multiple tasks go to different workers
55
+ idx = self._round_robin_index % len(tied)
56
+ self._round_robin_index += 1
57
+ return tied[idx]
58
+
59
+ def _version_compatible(self, worker: NodeInfo) -> bool:
60
+ maj, min_ = _parse_version(worker.version)
61
+ return (maj == self._ctrl_major and min_ == self._ctrl_minor)
62
+
63
+ def _score(
64
+ self,
65
+ task: Task,
66
+ worker: NodeInfo,
67
+ stats: dict,
68
+ ) -> float:
69
+ memory_affinity = self._memory_affinity(task, stats)
70
+ tool_affinity = self._tool_affinity(task, stats)
71
+ load_score = self._load_score(worker, stats)
72
+ return (
73
+ memory_affinity * self.MEMORY_WEIGHT
74
+ + tool_affinity * self.TOOL_WEIGHT
75
+ + load_score * self.LOAD_WEIGHT
76
+ )
77
+
78
+ def _memory_affinity(self, task: Task, stats: dict) -> float:
79
+ deps = list(task.dependencies or [])
80
+ if not deps:
81
+ return 0.0
82
+ completed_here = list(stats.get("completed_task_ids", []))
83
+ hit = sum(1 for d in deps if d in completed_here)
84
+ return hit / len(deps)
85
+
86
+ def _tool_affinity(self, task: Task, stats: dict) -> float:
87
+ tools_needed = self._tools_for_task(task)
88
+ if not tools_needed:
89
+ return 0.0
90
+ cached = set(stats.get("cached_tools", []))
91
+ hit = sum(1 for t in tools_needed if t in cached)
92
+ return hit / len(tools_needed)
93
+
94
+ def _tools_for_task(self, task: Task) -> list[str]:
95
+ try:
96
+ from devsper.tools.selector import get_tools_for_task
97
+ from devsper.tools.scoring import get_default_score_store
98
+ score_store = get_default_score_store()
99
+ except Exception:
100
+ score_store = None
101
+ try:
102
+ tools = get_tools_for_task(
103
+ task.description or "",
104
+ role=getattr(task, "role", None),
105
+ score_store=score_store,
106
+ )
107
+ return [t.name for t in tools]
108
+ except Exception:
109
+ return []
110
+
111
+ def _load_score(self, worker: NodeInfo, stats: dict) -> float:
112
+ active = int(stats.get("active_tasks", 0))
113
+ avg_duration = float(stats.get("avg_task_duration_seconds", 0.0))
114
+ max_workers = worker.max_workers or 1
115
+ weighted_load = active * avg_duration
116
+ load_ratio = weighted_load / (max_workers * 60.0)
117
+ return 1.0 - _clamp(load_ratio, 0.0, 1.0)
@@ -0,0 +1,105 @@
1
+ """Shared state backend for scheduler snapshots (Redis or filesystem)."""
2
+
3
+ import json
4
+ import os
5
+ from abc import ABC, abstractmethod
6
+
7
+
8
+ class StateBackend(ABC):
9
+ """Abstract backend for saving/loading scheduler snapshots."""
10
+
11
+ @abstractmethod
12
+ async def save_snapshot(self, run_id: str, snapshot: dict) -> None:
13
+ ...
14
+
15
+ @abstractmethod
16
+ async def load_snapshot(self, run_id: str) -> dict | None:
17
+ ...
18
+
19
+ @abstractmethod
20
+ async def delete_snapshot(self, run_id: str) -> None:
21
+ ...
22
+
23
+ @abstractmethod
24
+ async def list_snapshots(self) -> list[str]:
25
+ ...
26
+
27
+
28
+ class RedisStateBackend(StateBackend):
29
+ """Store snapshots in Redis. Key: devsper:snapshot:{run_id}, set index: devsper:snapshots."""
30
+
31
+ def __init__(self, redis_client: object) -> None:
32
+ self._redis = redis_client
33
+ self._key_prefix = "devsper:snapshot:"
34
+ self._index_key = "devsper:snapshots"
35
+
36
+ async def save_snapshot(self, run_id: str, snapshot: dict) -> None:
37
+ key = f"{self._key_prefix}{run_id}"
38
+ await self._redis.set(key, json.dumps(snapshot))
39
+ await self._redis.sadd(self._index_key, run_id)
40
+
41
+ async def load_snapshot(self, run_id: str) -> dict | None:
42
+ key = f"{self._key_prefix}{run_id}"
43
+ raw = await self._redis.get(key)
44
+ if raw is None:
45
+ return None
46
+ if isinstance(raw, bytes):
47
+ raw = raw.decode("utf-8")
48
+ return json.loads(raw)
49
+
50
+ async def delete_snapshot(self, run_id: str) -> None:
51
+ key = f"{self._key_prefix}{run_id}"
52
+ await self._redis.delete(key)
53
+ await self._redis.srem(self._index_key, run_id)
54
+
55
+ async def list_snapshots(self) -> list[str]:
56
+ members = await self._redis.smembers(self._index_key)
57
+ return [m.decode("utf-8") if isinstance(m, bytes) else str(m) for m in members]
58
+
59
+
60
+ class FilesystemStateBackend(StateBackend):
61
+ """Single-node: write snapshots to events_dir as {run_id}.snapshot.json. Atomic write."""
62
+
63
+ def __init__(self, events_dir: str) -> None:
64
+ self._events_dir = events_dir
65
+
66
+ def _path(self, run_id: str) -> str:
67
+ return os.path.join(self._events_dir, f"{run_id}.snapshot.json")
68
+
69
+ async def save_snapshot(self, run_id: str, snapshot: dict) -> None:
70
+ path = self._path(run_id)
71
+ tmp = path + ".tmp"
72
+ os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
73
+ with open(tmp, "w", encoding="utf-8") as f:
74
+ json.dump(snapshot, f, indent=0)
75
+ os.replace(tmp, path)
76
+
77
+ async def load_snapshot(self, run_id: str) -> dict | None:
78
+ path = self._path(run_id)
79
+ if not os.path.isfile(path):
80
+ return None
81
+ with open(path, "r", encoding="utf-8") as f:
82
+ return json.load(f)
83
+
84
+ async def delete_snapshot(self, run_id: str) -> None:
85
+ path = self._path(run_id)
86
+ if os.path.isfile(path):
87
+ os.remove(path)
88
+
89
+ async def list_snapshots(self) -> list[str]:
90
+ if not os.path.isdir(self._events_dir):
91
+ return []
92
+ out: list[str] = []
93
+ for name in os.listdir(self._events_dir):
94
+ if name.endswith(".snapshot.json"):
95
+ out.append(name.removesuffix(".snapshot.json"))
96
+ return out
97
+
98
+
99
+ def get_state_backend(config: object, redis_client: object | None = None) -> StateBackend:
100
+ """Return StateBackend from config. Redis if bus.backend == redis else filesystem."""
101
+ backend = getattr(getattr(config, "bus", None), "backend", "memory")
102
+ if backend == "redis" and redis_client is not None:
103
+ return RedisStateBackend(redis_client)
104
+ events_dir = getattr(config, "events_dir", ".devsper/events")
105
+ return FilesystemStateBackend(events_dir)
@@ -0,0 +1,5 @@
1
+ """Compliance: PII redaction and audit (v2.0)."""
2
+
3
+ from devsper.compliance.pii import PIIRedactor, PIIDetection, RedactionResult
4
+
5
+ __all__ = ["PIIRedactor", "PIIDetection", "RedactionResult"]
@@ -0,0 +1,147 @@
1
+ """
2
+ PIIRedactor: scan and redact PII from text before storage.
3
+ Detectors: regex (EMAIL, PHONE, SSN, CREDIT_CARD, IP, API_KEY) + optional spaCy NER (NAME, ADDRESS).
4
+ """
5
+
6
+ import re
7
+ from dataclasses import dataclass, field
8
+
9
+
10
+ @dataclass
11
+ class PIIDetection:
12
+ pii_type: str
13
+ start: int
14
+ end: int
15
+ confidence: float
16
+
17
+
18
+ @dataclass
19
+ class RedactionResult:
20
+ redacted_text: str
21
+ detections: list[PIIDetection] = field(default_factory=list)
22
+ pii_found: bool = False
23
+
24
+
25
+ # Regex patterns (non-capturing for substitution)
26
+ PATTERNS = {
27
+ "EMAIL": re.compile(
28
+ r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
29
+ re.IGNORECASE,
30
+ ),
31
+ "PHONE": re.compile(
32
+ r"\+?[\d\s\-()]{10,20}\d",
33
+ ),
34
+ "SSN": re.compile(
35
+ r"\b\d{3}[-\s]?\d{2}[-\s]?\d{4}\b",
36
+ ),
37
+ "CREDIT_CARD": re.compile(
38
+ r"\b(?:\d[-\s]*){13,19}\d\b",
39
+ ),
40
+ "IP_ADDRESS": re.compile(
41
+ r"\b(?:\d{1,3}\.){3}\d{1,3}\b|\[?[0-9a-fA-F:.]+\]?",
42
+ ),
43
+ "API_KEY": re.compile(
44
+ r"\b(?:sk|pk)[-_][a-zA-Z0-9]{20,}\b",
45
+ ),
46
+ }
47
+
48
+
49
+ def _luhn_ok(digits: str) -> bool:
50
+ s = digits.replace(" ", "").replace("-", "")
51
+ if len(s) < 13:
52
+ return False
53
+ try:
54
+ nums = [int(c) for c in s]
55
+ except ValueError:
56
+ return False
57
+ total = 0
58
+ for i, d in enumerate(reversed(nums)):
59
+ if i % 2 == 1:
60
+ d *= 2
61
+ if d > 9:
62
+ d -= 9
63
+ total += d
64
+ return total % 10 == 0
65
+
66
+
67
+ class PIIRedactor:
68
+ """Redact PII from text. Configurable pii_types; optional spaCy for NAME/ADDRESS when gdpr_mode."""
69
+
70
+ def __init__(
71
+ self,
72
+ pii_types: list[str] | None = None,
73
+ gdpr_mode: bool = False,
74
+ ) -> None:
75
+ self.pii_types = pii_types or list(PATTERNS.keys())
76
+ self.gdpr_mode = gdpr_mode
77
+ self._ner_available: bool | None = None
78
+
79
+ def _ner_detect(self, text: str) -> list[PIIDetection]:
80
+ out: list[PIIDetection] = []
81
+ if self._ner_available is False:
82
+ return out
83
+ try:
84
+ import spacy
85
+ if self._ner_available is None:
86
+ try:
87
+ nlp = spacy.load("en_core_web_sm")
88
+ except Exception:
89
+ nlp = spacy.blank("en")
90
+ setattr(self, "_ner", nlp)
91
+ self._ner_available = True
92
+ except ImportError:
93
+ self._ner_available = False
94
+ return out
95
+ nlp = getattr(self, "_ner", None)
96
+ if nlp is None:
97
+ return out
98
+ doc = nlp(text)
99
+ for ent in doc.ents:
100
+ if ent.label_ in ("PERSON", "GPE", "LOC") and (self.gdpr_mode or ent.label_ == "PERSON"):
101
+ out.append(PIIDetection(
102
+ pii_type="NAME" if ent.label_ == "PERSON" else "ADDRESS",
103
+ start=ent.start_char,
104
+ end=ent.end_char,
105
+ confidence=0.9,
106
+ ))
107
+ return out
108
+
109
+ def redact(self, text: str) -> RedactionResult:
110
+ if not text:
111
+ return RedactionResult(redacted_text="", detections=[], pii_found=False)
112
+ detections: list[PIIDetection] = []
113
+ ner_done = False
114
+ for pii_type in self.pii_types:
115
+ if pii_type not in PATTERNS and pii_type not in ("NAME", "ADDRESS"):
116
+ continue
117
+ if pii_type in ("NAME", "ADDRESS") or self.gdpr_mode:
118
+ if not ner_done:
119
+ detections.extend(self._ner_detect(text))
120
+ ner_done = True
121
+ continue
122
+ pattern = PATTERNS[pii_type]
123
+ for m in pattern.finditer(text):
124
+ snippet = m.group(0)
125
+ if pii_type == "CREDIT_CARD" and not _luhn_ok(snippet):
126
+ continue
127
+ detections.append(PIIDetection(
128
+ pii_type=pii_type,
129
+ start=m.start(),
130
+ end=m.end(),
131
+ confidence=0.95,
132
+ ))
133
+ detections.sort(key=lambda d: (d.start, -d.end))
134
+ merged: list[PIIDetection] = []
135
+ for d in detections:
136
+ if merged and d.start < merged[-1].end:
137
+ continue
138
+ merged.append(d)
139
+ result = list(text)
140
+ for d in reversed(merged):
141
+ result[d.start:d.end] = f"[REDACTED:{d.pii_type}]"
142
+ redacted_text = "".join(result)
143
+ return RedactionResult(
144
+ redacted_text=redacted_text,
145
+ detections=merged,
146
+ pii_found=len(merged) > 0,
147
+ )
@@ -0,0 +1,52 @@
1
+ """
2
+ devsper configuration: TOML + env, Pydantic-validated.
3
+
4
+ Priority: env > project config > user config > defaults.
5
+ Config locations: ./devsper.toml, ./workflow.devsper.toml, ~/.config/devsper/config.toml,
6
+ and legacy .devsper/config.toml.
7
+ """
8
+
9
+ from devsper.config.resolver import resolve_config
10
+ from devsper.config.schema import (
11
+ A2AConfig,
12
+ devsperConfigModel,
13
+ KnowledgeConfig,
14
+ MCPConfig,
15
+ MemoryConfig,
16
+ ModelsConfig,
17
+ NodesConfig,
18
+ ProviderAzureConfig,
19
+ ProvidersConfig,
20
+ SwarmConfig,
21
+ TelemetryConfig,
22
+ ToolsConfig,
23
+ )
24
+
25
+ # Backward compatibility: old code expects devsperConfig and get_config()
26
+ devsperConfig = devsperConfigModel
27
+
28
+
29
+ def get_config(config_path: str | None = None) -> devsperConfigModel:
30
+ """
31
+ Load and resolve configuration.
32
+ Returns object with .worker_model, .planner_model, .events_dir, .data_dir,
33
+ and .swarm, .models, .memory, .tools, .telemetry, .providers.
34
+ """
35
+ return resolve_config(config_path=config_path)
36
+
37
+
38
+ __all__ = [
39
+ "A2AConfig",
40
+ "get_config",
41
+ "devsperConfig",
42
+ "devsperConfigModel",
43
+ "KnowledgeConfig",
44
+ "MCPConfig",
45
+ "MemoryConfig",
46
+ "ModelsConfig",
47
+ "ProviderAzureConfig",
48
+ "ProvidersConfig",
49
+ "SwarmConfig",
50
+ "TelemetryConfig",
51
+ "ToolsConfig",
52
+ ]
@@ -0,0 +1,121 @@
1
+ """Discover and load TOML config from standard locations."""
2
+
3
+ from pathlib import Path
4
+
5
+ import tomllib
6
+
7
+
8
+ def _load_toml(path: Path) -> dict:
9
+ if not path.is_file():
10
+ return {}
11
+ try:
12
+ with open(path, "rb") as f:
13
+ return tomllib.load(f)
14
+ except Exception:
15
+ return {}
16
+
17
+
18
+ def user_config_path() -> Path:
19
+ return Path.home() / ".config" / "devsper" / "config.toml"
20
+
21
+
22
+ def project_config_paths() -> list[Path]:
23
+ """Return candidate project config paths in order: devsper.toml, workflow.devsper.toml, .devsper/config.toml (legacy)."""
24
+ cwd = Path.cwd()
25
+ paths = [
26
+ cwd / "devsper.toml",
27
+ cwd / "workflow.devsper.toml",
28
+ cwd / ".devsper" / "config.toml",
29
+ cwd.parent / "devsper.toml",
30
+ cwd.parent / "workflow.devsper.toml",
31
+ cwd.parent / ".devsper" / "config.toml",
32
+ ]
33
+ return paths
34
+
35
+
36
+ def load_user_config() -> dict:
37
+ return _load_toml(user_config_path())
38
+
39
+
40
+ def load_project_config() -> dict:
41
+ """Load first existing project config file (priority: devsper.toml > workflow.devsper.toml > .devsper/config.toml)."""
42
+ for p in project_config_paths():
43
+ data = _load_toml(p)
44
+ if data:
45
+ return data
46
+ return {}
47
+
48
+
49
+ def _extract_legacy_defaults(data: dict) -> dict:
50
+ """Map legacy [default] or top-level keys into a flat dict for merging."""
51
+ out: dict = {}
52
+ default_block = data.get("default")
53
+ if isinstance(default_block, dict):
54
+ out = dict(default_block)
55
+ # Top-level string/int/float/bool keys (legacy)
56
+ for k, v in data.items():
57
+ if k in ("default", "workflow", "swarm", "models", "memory", "tools", "telemetry", "providers"):
58
+ continue
59
+ if isinstance(v, (str, int, float, bool)):
60
+ out[k] = v
61
+ return out
62
+
63
+
64
+ def normalize_toml_to_flat(data: dict) -> dict:
65
+ """
66
+ Normalize TOML data into a structure the resolver expects.
67
+ Supports new format [swarm], [models], etc. and legacy [default] / top-level.
68
+ """
69
+ result: dict = {}
70
+ legacy = _extract_legacy_defaults(data)
71
+ if legacy:
72
+ result["events_dir"] = legacy.get("events_dir", "")
73
+ result["data_dir"] = legacy.get("data_dir", "")
74
+ result["worker_model"] = legacy.get("worker_model", "")
75
+ result["planner_model"] = legacy.get("planner_model", "")
76
+
77
+ if "swarm" in data and isinstance(data["swarm"], dict):
78
+ result["swarm"] = data["swarm"]
79
+ if "models" in data and isinstance(data["models"], dict):
80
+ result["models"] = data["models"]
81
+ elif legacy:
82
+ if legacy.get("worker_model"):
83
+ result.setdefault("models", {})["worker"] = legacy["worker_model"]
84
+ if legacy.get("planner_model"):
85
+ result.setdefault("models", {})["planner"] = legacy["planner_model"]
86
+ if "memory" in data and isinstance(data["memory"], dict):
87
+ result["memory"] = data["memory"]
88
+ if "knowledge" in data and isinstance(data["knowledge"], dict):
89
+ result["knowledge"] = data["knowledge"]
90
+ if "tools" in data and isinstance(data["tools"], dict):
91
+ result["tools"] = data["tools"]
92
+ result["tools"] = data["tools"]
93
+ if "telemetry" in data and isinstance(data["telemetry"], dict):
94
+ result["telemetry"] = data["telemetry"]
95
+ if "cache" in data and isinstance(data["cache"], dict):
96
+ result["cache"] = data["cache"]
97
+ if "bus" in data and isinstance(data["bus"], dict):
98
+ result["bus"] = data["bus"]
99
+ if "nodes" in data and isinstance(data["nodes"], dict):
100
+ result["nodes"] = data["nodes"]
101
+ if "providers" in data and isinstance(data["providers"], dict):
102
+ result["providers"] = data["providers"]
103
+ # v1.10.5: MCP and A2A
104
+ if "mcp" in data and isinstance(data["mcp"], dict):
105
+ result["mcp"] = data["mcp"]
106
+ if "a2a" in data and isinstance(data["a2a"], dict):
107
+ result["a2a"] = data["a2a"]
108
+ # v2.1: hitl and [[hitl.policies]]
109
+ if "hitl" in data and isinstance(data["hitl"], dict):
110
+ result["hitl"] = dict(data["hitl"])
111
+ policies = data["hitl"].get("policies")
112
+ if isinstance(policies, list):
113
+ result["hitl"]["policies"] = []
114
+ for p in policies:
115
+ if isinstance(p, dict):
116
+ pol = dict(p)
117
+ triggers = p.get("triggers")
118
+ if isinstance(triggers, list):
119
+ pol["triggers"] = [t if isinstance(t, dict) else {"type": "confidence_below", "threshold": 0.5} for t in triggers]
120
+ result["hitl"]["policies"].append(pol)
121
+ return result