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,99 @@
1
+ """
2
+ TUI Dev View: repository tree, test results, file changes.
3
+
4
+ Shown in dashboard when working with the autonomous application builder.
5
+ """
6
+
7
+ from pathlib import Path
8
+
9
+ from textual.widgets import Static
10
+ from textual.reactive import reactive
11
+
12
+
13
+ def _tree_for_path(root: Path, max_depth: int = 4, max_entries: int = 80) -> list[str]:
14
+ """Build a simple tree of relative paths under root."""
15
+ lines: list[str] = []
16
+ root = root.resolve()
17
+ if not root.exists() or not root.is_dir():
18
+ return ["(no directory)"]
19
+
20
+ def walk(p: Path, prefix: str, depth: int) -> None:
21
+ if len(lines) >= max_entries or depth > max_depth:
22
+ return
23
+ try:
24
+ entries = sorted(p.iterdir(), key=lambda x: (not x.is_dir(), x.name.lower()))
25
+ for i, e in enumerate(entries):
26
+ if e.name.startswith(".") or e.name == "__pycache__":
27
+ continue
28
+ if len(lines) >= max_entries:
29
+ return
30
+ is_last = i == len(entries) - 1
31
+ branch = "└── " if is_last else "├── "
32
+ lines.append(prefix + branch + e.name)
33
+ if e.is_dir():
34
+ ext = " " if is_last else "│ "
35
+ walk(e, prefix + ext, depth + 1)
36
+ except OSError:
37
+ pass
38
+
39
+ walk(root, "", 0)
40
+ return lines if lines else ["(empty)"]
41
+
42
+
43
+ class DevView(Static):
44
+ """
45
+ Panel showing repository tree, test results, and file changes for build output.
46
+ """
47
+
48
+ repo_path: reactive[str | None] = reactive(None)
49
+ test_results: reactive[str] = reactive("")
50
+ file_changes: reactive[list[str]] = reactive(list)
51
+
52
+ def __init__(self, *args, **kwargs) -> None:
53
+ super().__init__(*args, **kwargs)
54
+ self._repo_path: str | None = None
55
+ self._test_results = ""
56
+ self._file_changes: list[str] = []
57
+
58
+ def set_repo_path(self, path: str | None) -> None:
59
+ self._repo_path = path
60
+ self.repo_path = path
61
+
62
+ def set_test_results(self, text: str) -> None:
63
+ self._test_results = text
64
+ self.test_results = text
65
+
66
+ def set_file_changes(self, changes: list[str]) -> None:
67
+ self._file_changes = changes
68
+ self.file_changes = changes
69
+
70
+ def watch_repo_path(self, path: str | None) -> None:
71
+ self._repo_path = path
72
+ self._refresh()
73
+
74
+ def watch_test_results(self, text: str) -> None:
75
+ self._test_results = text
76
+ self._refresh()
77
+
78
+ def watch_file_changes(self, changes: list[str]) -> None:
79
+ self._file_changes = changes or []
80
+ self._refresh()
81
+
82
+ def _refresh(self) -> None:
83
+ lines = ["Dev — Build output", ""]
84
+ if self._repo_path:
85
+ root = Path(self._repo_path)
86
+ lines.append("Repository tree:")
87
+ lines.extend(_tree_for_path(root))
88
+ lines.append("")
89
+ else:
90
+ lines.append("Repository: (none — run 'devsper build \"app\"' to generate)")
91
+ lines.append("")
92
+
93
+ lines.append("Test results:")
94
+ lines.append(self._test_results[:1500] if self._test_results else "(no results)")
95
+ lines.append("")
96
+ lines.append("File changes:")
97
+ for c in (self._file_changes or [])[:20]:
98
+ lines.append(f" {c}")
99
+ self.update("\n".join(lines))
@@ -0,0 +1,73 @@
1
+ """
2
+ Overlay screen: inject a note to the swarm (stored as high-priority memory).
3
+ """
4
+
5
+ from datetime import datetime, timezone
6
+
7
+ from textual.app import ComposeResult
8
+ from textual.containers import Container
9
+ from textual.screen import ModalScreen
10
+ from textual.widgets import Button, Input, Static
11
+
12
+ from devsper.types.event import Event, events
13
+ from devsper.memory.memory_store import get_default_store, generate_memory_id
14
+ from devsper.memory.memory_types import MemoryRecord, MemoryType
15
+
16
+
17
+ class InjectScreen(ModalScreen[None]):
18
+ """Modal: 'Inject note to swarm' input; on Submit store as episodic + user_injection and close."""
19
+
20
+ BINDINGS = [("escape", "cancel")]
21
+
22
+ def compose(self) -> ComposeResult:
23
+ with Container(id="inject-container"):
24
+ yield Static("Inject note to swarm:", id="inject-label")
25
+ yield Input(placeholder="Type your message...", id="inject-input")
26
+ with Container(id="inject-buttons"):
27
+ yield Button("Submit", variant="primary", id="inject-submit")
28
+ yield Button("Cancel", id="inject-cancel")
29
+
30
+ def on_mount(self) -> None:
31
+ self.query_one("#inject-input", Input).focus()
32
+
33
+ def on_button_pressed(self, event: Button.Pressed) -> None:
34
+ if event.button.id == "inject-submit":
35
+ self._submit()
36
+ else:
37
+ self.dismiss(None)
38
+
39
+ def on_input_submitted(self, event: Input.Submitted) -> None:
40
+ if event.input.id == "inject-input":
41
+ self._submit()
42
+
43
+ def _submit(self) -> None:
44
+ inp = self.query_one("#inject-input", Input)
45
+ message = (inp.value or "").strip()
46
+ if not message:
47
+ return
48
+ store = get_default_store()
49
+ record = MemoryRecord(
50
+ id=generate_memory_id(),
51
+ memory_type=MemoryType.EPISODIC,
52
+ source_task="user_injection",
53
+ content=message,
54
+ tags=["user_injection"],
55
+ )
56
+ store.store(record)
57
+ log_path = getattr(self.app, "_event_log_path", None)
58
+ if log_path:
59
+ try:
60
+ ev = Event(
61
+ timestamp=datetime.now(timezone.utc),
62
+ type=events.USER_INJECTION,
63
+ payload={"message": message[:500]},
64
+ )
65
+ with open(log_path, "a", encoding="utf-8") as f:
66
+ f.write(ev.model_dump_json() + "\n")
67
+ except OSError:
68
+ pass
69
+ self.dismiss(None)
70
+ self.notify("📌 Injected. Subsequent agent calls will see this note.", severity="information")
71
+
72
+ def action_cancel(self) -> None:
73
+ self.dismiss(None)
@@ -0,0 +1,46 @@
1
+ """
2
+ Knowledge graph viewer: list entities and relationships from the knowledge graph.
3
+ Builds KG from default memory store on demand.
4
+ """
5
+
6
+ from textual.widgets import Static
7
+
8
+
9
+ class KnowledgeGraphView(Static):
10
+ """Shows entities and edges from the knowledge graph (built from memory)."""
11
+
12
+ def __init__(self, *args, **kwargs) -> None:
13
+ super().__init__(*args, **kwargs)
14
+ self._text = "(knowledge graph)\n\nRefresh to build from memory."
15
+
16
+ def load_from_memory(self, limit_entities: int = 40, limit_edges: int = 30) -> None:
17
+ """Build KG from default store and display entities + edges."""
18
+ try:
19
+ from devsper.knowledge.knowledge_graph import KnowledgeGraph
20
+ from devsper.memory.memory_store import get_default_store
21
+
22
+ store = get_default_store()
23
+ kg = KnowledgeGraph(store=store)
24
+ kg.build_from_memory()
25
+ g = kg.graph
26
+ entities = []
27
+ for nid, data in g.nodes(data=True):
28
+ kind = data.get("kind", "?")
29
+ label = (data.get("label") or nid)[:60]
30
+ entities.append(f" {nid[:40]} [{kind}] {label}")
31
+ edges = []
32
+ for u, v, data in list(g.edges(data=True))[:limit_edges]:
33
+ et = data.get("type", "")
34
+ edges.append(f" {u[:25]} --[{et}]--> {v[:25]}")
35
+ if not entities and not edges:
36
+ self._text = "(no entities)\n\nAdd memory and refresh."
37
+ else:
38
+ self._text = "Entities:\n" + "\n".join(entities[:limit_entities])
39
+ if edges:
40
+ self._text += "\n\nEdges:\n" + "\n".join(edges)
41
+ except Exception as e:
42
+ self._text = f"(error: {e})"
43
+ self.update(self._text)
44
+
45
+ def on_mount(self) -> None:
46
+ self.update(self._text)
devsper/tui/layout.py ADDED
@@ -0,0 +1,43 @@
1
+ """
2
+ TUI layout: output-first. One main view = prompt + full-height output.
3
+
4
+ No side panels on main screen (no scrolling in tiny boxes). Dashboard (d) for tasks/logs/memory.
5
+ """
6
+
7
+ from textual.app import ComposeResult
8
+ from textual.containers import Container, Vertical
9
+ from textual.widgets import Input, Static
10
+
11
+ from devsper.tui.results_view import ResultsView
12
+
13
+
14
+ class PromptInput(Input):
15
+ """Input that yields focus on Escape so r / d / q work."""
16
+
17
+ def on_key(self, event):
18
+ if event.key == "escape":
19
+ try:
20
+ self.app.set_focus(self.app.query_one("#results-view"))
21
+ except Exception:
22
+ pass
23
+ event.prevent_default().stop()
24
+
25
+
26
+ class devsperLayout(Static):
27
+ """Main view: compact branding → prompt → one large Output (rest of screen)."""
28
+
29
+ def compose(self) -> ComposeResult:
30
+ with Container(id="branding"):
31
+ yield Static("devsper — Distributed AI Swarm Runtime", id="logo-line")
32
+ with Container(id="prompt-box"):
33
+ yield PromptInput(
34
+ placeholder="Ask anything... e.g. Summarize swarm intelligence in one paragraph.",
35
+ id="prompt-input",
36
+ )
37
+ yield Static(
38
+ "Esc unfocus input • Enter or r run • o output • q quit",
39
+ id="action-hints",
40
+ )
41
+ with Vertical(id="output-container"):
42
+ yield Static("Response", classes="output-title")
43
+ yield ResultsView(id="results-view")
@@ -0,0 +1,83 @@
1
+ """
2
+ Logs panel: stream events from EventLog.
3
+
4
+ Polls event log file(s) or uses in-memory events. Displays event type and payload summary.
5
+ """
6
+
7
+ import os
8
+ from pathlib import Path
9
+
10
+ from textual.widgets import Static
11
+
12
+
13
+ class LogsView(Static):
14
+ """Displays event log stream: event type, task_id, etc."""
15
+
16
+ def __init__(self, *args, **kwargs) -> None:
17
+ super().__init__(*args, **kwargs)
18
+ self._log_path: str | None = None
19
+ self._events_folder: str = ".devsper/events"
20
+ self._max_lines: int = 100
21
+ self._last_count: int = 0
22
+
23
+ def set_log_path(self, path: str | None) -> None:
24
+ """Set the event log file path to read from."""
25
+ self._log_path = path
26
+
27
+ def set_events_folder(self, folder: str) -> None:
28
+ """Set folder to search for latest events_*.jsonl."""
29
+ self._events_folder = folder
30
+
31
+ def _latest_log_path(self) -> str | None:
32
+ if self._log_path and os.path.exists(self._log_path):
33
+ return self._log_path
34
+ if not os.path.isdir(self._events_folder):
35
+ alt = ".devsper/events"
36
+ if os.path.isdir(alt):
37
+ self._events_folder = alt
38
+ else:
39
+ return None
40
+ files = list(Path(self._events_folder).glob("events_*.jsonl"))
41
+ if not files:
42
+ return None
43
+ files.sort(key=lambda p: p.stat().st_mtime, reverse=True)
44
+ return str(files[0])
45
+
46
+ def _read_events(self) -> list[str]:
47
+ """Read events and return list of display lines."""
48
+ path = self._latest_log_path()
49
+ if not path:
50
+ return [
51
+ "No logs yet.",
52
+ "",
53
+ "↑ Run a task (prompt above, then Enter or r);",
54
+ " events will appear here.",
55
+ ]
56
+ lines = []
57
+ try:
58
+ from devsper.types.event import Event
59
+
60
+ with open(path, "r") as f:
61
+ for line in f:
62
+ line = line.strip()
63
+ if not line:
64
+ continue
65
+ try:
66
+ ev = Event.model_validate_json(line)
67
+ payload = ev.payload or {}
68
+ task_id = payload.get("task_id", "")
69
+ extra = f" {task_id}" if task_id else ""
70
+ lines.append(f"{ev.type.value}{extra}")
71
+ except Exception:
72
+ lines.append(line[:80])
73
+ except Exception as e:
74
+ lines.append(f"(error: {e})")
75
+ return lines[-self._max_lines :] if len(lines) > self._max_lines else lines
76
+
77
+ def refresh_logs(self) -> None:
78
+ """Re-read event log and update display."""
79
+ lines = self._read_events()
80
+ self.update("\n".join(lines) if lines else "(no events)")
81
+
82
+ def on_mount(self) -> None:
83
+ self.refresh_logs()
@@ -0,0 +1,58 @@
1
+ """
2
+ Memory panel: list memory entries with tags and summaries.
3
+ """
4
+
5
+ from textual.widgets import Static
6
+
7
+
8
+ class MemoryView(Static):
9
+ """Displays memory entries: id, tags, content summary."""
10
+
11
+ def __init__(self, *args, **kwargs) -> None:
12
+ super().__init__(*args, **kwargs)
13
+ self._entries: list[dict] = []
14
+
15
+ def set_entries(self, entries: list[dict]) -> None:
16
+ """Update entries. Each dict: id, tags (list), summary or content (str)."""
17
+ self._entries = entries
18
+ self._refresh_display()
19
+
20
+ def load_from_store(self, limit: int = 20, namespace: str | None = None) -> None:
21
+ """Load from default memory store. If namespace is set, filter by namespace tag."""
22
+ try:
23
+ from devsper.memory.memory_store import get_default_store
24
+
25
+ store = get_default_store()
26
+ records = store.list_memory(limit=limit * 2 if namespace else limit)
27
+ if namespace:
28
+ from devsper.memory.namespaces import filter_by_namespace
29
+ records = filter_by_namespace(records, namespace)[:limit]
30
+ self._entries = [
31
+ {
32
+ "id": r.id[:8] + "…" if len(r.id) > 8 else r.id,
33
+ "tags": r.tags or [],
34
+ "summary": (r.content or "")[:120]
35
+ + ("…" if len(r.content or "") > 120 else ""),
36
+ }
37
+ for r in records
38
+ ]
39
+ except Exception as e:
40
+ self._entries = [{"id": "error", "tags": [], "summary": str(e)}]
41
+ self._refresh_display()
42
+
43
+ def _refresh_display(self) -> None:
44
+ if not self._entries:
45
+ self.update("(no memory entries)\n\n[m] refresh memory")
46
+ return
47
+ lines = []
48
+ for e in self._entries:
49
+ eid = e.get("id", "?")
50
+ tags = e.get("tags", [])
51
+ summary = e.get("summary", e.get("content", ""))[:100]
52
+ tag_str = ", ".join(tags[:5]) if tags else "-"
53
+ lines.append(f"{eid} [{tag_str}]")
54
+ lines.append(f" {summary}")
55
+ self.update("\n".join(lines))
56
+
57
+ def on_mount(self) -> None:
58
+ self.load_from_store()
@@ -0,0 +1,33 @@
1
+ """Dashboard panel: speculative tasks, cache hits, tool usage stats."""
2
+
3
+ from textual.widgets import Static
4
+
5
+
6
+ class PerformanceView(Static):
7
+ """Shows speculative status, cache stats, and tool usage summary."""
8
+
9
+ def __init__(self, *args, **kwargs) -> None:
10
+ super().__init__("", *args, **kwargs)
11
+
12
+ def set_stats(
13
+ self,
14
+ speculative_enabled: bool = False,
15
+ speculative_count: int = 0,
16
+ cache_entries: int = 0,
17
+ tool_stats: list[dict] | None = None,
18
+ ) -> None:
19
+ lines = [
20
+ "Speculative: " + ("on" if speculative_enabled else "off"),
21
+ f"Speculative tasks: {speculative_count}",
22
+ f"Cache entries: {cache_entries}",
23
+ "Tools:",
24
+ ]
25
+ if tool_stats:
26
+ for s in tool_stats[:8]:
27
+ lines.append(
28
+ f" {s['tool_name']}: n={s['count']} "
29
+ f"ok%={s['success_rate']:.0f} lat={s['avg_latency_ms']}ms"
30
+ )
31
+ else:
32
+ lines.append(" (no data)")
33
+ self.update("\n".join(lines))
@@ -0,0 +1,39 @@
1
+ """
2
+ Reasoning graph viewer: show reasoning nodes from the current run's reasoning store.
3
+ """
4
+
5
+ from textual.widgets import Static
6
+
7
+
8
+ class ReasoningGraphView(Static):
9
+ """Displays reasoning nodes (agent_id, task_id, content preview) from ReasoningStore."""
10
+
11
+ def __init__(self, *args, **kwargs) -> None:
12
+ super().__init__(*args, **kwargs)
13
+ self._reasoning_store = None
14
+
15
+ def set_reasoning_store(self, store) -> None:
16
+ """Set the ReasoningStore to display (e.g. from app or swarm)."""
17
+ self._reasoning_store = store
18
+
19
+ def load_from_store(self) -> None:
20
+ """Load nodes from the configured store and update display."""
21
+ if self._reasoning_store is None:
22
+ self.update("(Reasoning graph)\n\nNo reasoning store for this run.")
23
+ return
24
+ try:
25
+ nodes = self._reasoning_store.query_nodes(limit=30)
26
+ if not nodes:
27
+ self.update("(Reasoning graph)\n\nNo reasoning nodes yet.")
28
+ return
29
+ lines = ["(Reasoning graph)", ""]
30
+ for n in nodes[:20]:
31
+ preview = (n.content or "")[:80].replace("\n", " ")
32
+ if len(n.content or "") > 80:
33
+ preview += "..."
34
+ lines.append(f" {n.id} [{n.agent_id}] task={n.task_id}")
35
+ lines.append(f" {preview}")
36
+ lines.append("")
37
+ self.update("\n".join(lines))
38
+ except Exception as e:
39
+ self.update(f"(Reasoning graph)\n\nError: {e}")
@@ -0,0 +1,139 @@
1
+ """
2
+ Response panel: LLM-style. Shows "You asked" + cleaned response (real content only).
3
+ """
4
+
5
+ import re
6
+ from textual.widgets import Static
7
+
8
+
9
+ def _collapse_long_tool_results(text: str, max_keep: int = 120) -> str:
10
+ """Replace long 'Tool result (name): ...' blocks with [output omitted] so we don't dump code/file contents."""
11
+ if not text or "Tool result (" not in text:
12
+ return text
13
+ pattern = re.compile(
14
+ r"(Tool result\s*\(\s*\w+\s*\)\s*:\s*\n?)(.*?)(?=Tool result\s*\(\s*\w+\s*\)\s*:|Response:\n|\Z)",
15
+ re.DOTALL | re.IGNORECASE,
16
+ )
17
+ def repl(m):
18
+ prefix, content = m.group(1), (m.group(2) or "").strip()
19
+ if len(content) <= max_keep and content.count("\n") <= 1:
20
+ return m.group(0)
21
+ return prefix + "[output omitted]\n"
22
+ return pattern.sub(repl, text)
23
+
24
+
25
+ def _extract_real_content(text: str) -> str:
26
+ """Keep only the actual answer: remove all agent boilerplate."""
27
+ if not text or not text.strip():
28
+ return ""
29
+ out = text.strip()
30
+ out = _collapse_long_tool_results(out)
31
+ out = re.sub(
32
+ r"Completed:\s*You are an AI worker in a distributed system\.?\s*",
33
+ "",
34
+ out,
35
+ flags=re.IGNORECASE,
36
+ )
37
+ out = re.sub(r"Task:\s*\n\s*[^\n]*", "", out, flags=re.IGNORECASE)
38
+ out = re.sub(r"^Task:\s*$", "", out, flags=re.MULTILINE | re.IGNORECASE)
39
+ out = re.sub(
40
+ r"RELEVANT MEMORY\s*\([^)]*\)\s*:?\s*\n(?:\s*-\s*[^\n]*\n?)*",
41
+ "",
42
+ out,
43
+ flags=re.IGNORECASE,
44
+ )
45
+ out = re.sub(
46
+ r"RELEVANT MEMORY\s*\n(?:\s*-\s*[^\n]*\n?)*",
47
+ "",
48
+ out,
49
+ flags=re.IGNORECASE,
50
+ )
51
+ out = re.sub(r"^RELEVANT MEMORY\s*$", "", out, flags=re.MULTILINE | re.IGNORECASE)
52
+ out = re.sub(r"\(previous research notes[^)]*\)", "", out, flags=re.IGNORECASE)
53
+ out = re.sub(
54
+ r"^(?:First|Second|Third|Fourth|Fifth|Sixth)\s+step\s*$",
55
+ "",
56
+ out,
57
+ flags=re.MULTILINE | re.IGNORECASE,
58
+ )
59
+ out = re.sub(r"^\.\.\.\s*$", "", out, flags=re.MULTILINE)
60
+ out = re.sub(r"^—\s*$", "", out, flags=re.MULTILINE)
61
+ out = re.sub(r"^\s*-\s*[a-f0-9]{8}[^\n]*$", "", out, flags=re.MULTILINE)
62
+ out = re.sub(r"\n{3,}", "\n\n", out)
63
+ return out.strip()
64
+
65
+
66
+ SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
67
+
68
+ class ResultsView(Static):
69
+ """LLM-style: shows last question + cleaned response (real content only)."""
70
+
71
+ def __init__(self, *args, **kwargs) -> None:
72
+ super().__init__(*args, **kwargs)
73
+ self._prompt = ""
74
+ self._response = ""
75
+ self._loading = False
76
+ self._loading_message = "Running…"
77
+ self._spinner_index = 0
78
+
79
+ def set_loading(self, running: bool, message: str = "Running…") -> None:
80
+ self._loading = running
81
+ self._loading_message = message or "Running…"
82
+ self._refresh_display()
83
+
84
+ def tick_loading(self) -> None:
85
+ if not self._loading:
86
+ return
87
+ self._spinner_index = (self._spinner_index + 1) % len(SPINNER)
88
+ self._refresh_display()
89
+
90
+ def set_exchange(self, prompt: str, response: str) -> None:
91
+ """Set the last user prompt and the cleaned response (LLM-style)."""
92
+ self._prompt = (prompt or "").strip()
93
+ raw = (response or "").strip()
94
+ cleaned = _extract_real_content(raw)
95
+ self._response = cleaned if cleaned else ""
96
+ self._refresh_display()
97
+
98
+ def set_output(self, text: str, strip_boilerplate: bool = True) -> None:
99
+ """Legacy: set response only (no prompt)."""
100
+ raw = (text or "").strip()
101
+ cleaned = _extract_real_content(raw) if strip_boilerplate else raw
102
+ self._response = cleaned if cleaned else raw
103
+ self._refresh_display()
104
+
105
+ def clear_output(self) -> None:
106
+ self._prompt = ""
107
+ self._response = ""
108
+ self._refresh_display()
109
+
110
+ def _refresh_display(self) -> None:
111
+ if self._loading:
112
+ parts = []
113
+ if self._prompt:
114
+ parts.append(f"You asked: {self._prompt}\n")
115
+ parts.append(f"{SPINNER[self._spinner_index]} {self._loading_message}")
116
+ self.update("\n".join(parts))
117
+ return
118
+ if not self._response and not self._prompt:
119
+ self.update(
120
+ "Ask something above and press Enter or r to run.\n\n"
121
+ "Your response will appear here."
122
+ )
123
+ return
124
+ parts = []
125
+ if self._prompt:
126
+ parts.append(f"You asked: {self._prompt}\n")
127
+ parts.append(
128
+ self._response
129
+ if self._response
130
+ else "No substantive output yet. Set OPENAI_API_KEY (or another provider) and run again for the full response."
131
+ )
132
+ self.update("\n".join(parts))
133
+
134
+ @property
135
+ def can_focus(self) -> bool:
136
+ return True
137
+
138
+ def on_mount(self) -> None:
139
+ self._refresh_display()
@@ -0,0 +1,37 @@
1
+ """
2
+ Swarm graph panel: ASCII DAG from scheduler.
3
+
4
+ Uses runtime.visualize.visualize_scheduler_dag when scheduler is set.
5
+ """
6
+
7
+ from textual.widgets import Static
8
+
9
+
10
+ class SwarmView(Static):
11
+ """Displays DAG visualization of the task graph."""
12
+
13
+ def __init__(self, *args, **kwargs) -> None:
14
+ super().__init__(*args, **kwargs)
15
+ self._dag_text = "(no DAG)\n\nRun swarm to see graph."
16
+
17
+ def set_scheduler(self, scheduler: object | None) -> None:
18
+ """Update DAG from scheduler. Pass None to clear."""
19
+ if scheduler is None:
20
+ self._dag_text = "(no DAG)\n\nRun swarm to see graph."
21
+ self.update(self._dag_text)
22
+ return
23
+ try:
24
+ from devsper.runtime.visualize import visualize_scheduler_dag
25
+
26
+ self._dag_text = visualize_scheduler_dag(scheduler)
27
+ except Exception as e:
28
+ self._dag_text = f"(error: {e})"
29
+ self.update(self._dag_text)
30
+
31
+ def set_dag_text(self, text: str) -> None:
32
+ """Set DAG content directly (e.g. for demo)."""
33
+ self._dag_text = text
34
+ self.update(text)
35
+
36
+ def on_mount(self) -> None:
37
+ self.update(self._dag_text)