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.
- devsper/__init__.py +14 -0
- devsper/agents/a2a/__init__.py +27 -0
- devsper/agents/a2a/client.py +126 -0
- devsper/agents/a2a/discovery.py +24 -0
- devsper/agents/a2a/server.py +128 -0
- devsper/agents/a2a/tool_adapter.py +68 -0
- devsper/agents/a2a/types.py +49 -0
- devsper/agents/agent.py +602 -0
- devsper/agents/critic.py +80 -0
- devsper/agents/message_bus.py +124 -0
- devsper/agents/roles.py +181 -0
- devsper/agents/run_agent.py +78 -0
- devsper/analytics/__init__.py +5 -0
- devsper/analytics/tool_analytics.py +78 -0
- devsper/audit/__init__.py +5 -0
- devsper/audit/logger.py +214 -0
- devsper/bus/__init__.py +29 -0
- devsper/bus/backends/__init__.py +5 -0
- devsper/bus/backends/base.py +38 -0
- devsper/bus/backends/memory.py +55 -0
- devsper/bus/backends/redis.py +146 -0
- devsper/bus/message.py +56 -0
- devsper/bus/schema_version.py +3 -0
- devsper/bus/topics.py +19 -0
- devsper/cache/__init__.py +6 -0
- devsper/cache/embedding_index.py +98 -0
- devsper/cache/hashing.py +24 -0
- devsper/cache/store.py +153 -0
- devsper/cache/task_cache.py +191 -0
- devsper/cli/__init__.py +6 -0
- devsper/cli/commands/reg.py +733 -0
- devsper/cli/github_oauth.py +157 -0
- devsper/cli/init.py +637 -0
- devsper/cli/main.py +2956 -0
- devsper/cli/run_progress.py +103 -0
- devsper/cli/ui/__init__.py +65 -0
- devsper/cli/ui/components.py +94 -0
- devsper/cli/ui/errors.py +104 -0
- devsper/cli/ui/logging.py +120 -0
- devsper/cli/ui/onboarding.py +102 -0
- devsper/cli/ui/progress.py +43 -0
- devsper/cli/ui/run_view.py +308 -0
- devsper/cli/ui/theme.py +40 -0
- devsper/cluster/__init__.py +29 -0
- devsper/cluster/election.py +84 -0
- devsper/cluster/local.py +97 -0
- devsper/cluster/node_info.py +77 -0
- devsper/cluster/registry.py +71 -0
- devsper/cluster/router.py +117 -0
- devsper/cluster/state_backend.py +105 -0
- devsper/compliance/__init__.py +5 -0
- devsper/compliance/pii.py +147 -0
- devsper/config/__init__.py +52 -0
- devsper/config/config_loader.py +121 -0
- devsper/config/defaults.py +77 -0
- devsper/config/resolver.py +342 -0
- devsper/config/schema.py +237 -0
- devsper/credentials/__init__.py +19 -0
- devsper/credentials/cli.py +197 -0
- devsper/credentials/migration.py +124 -0
- devsper/credentials/store.py +142 -0
- devsper/dashboard/__init__.py +9 -0
- devsper/dashboard/dashboard.py +87 -0
- devsper/dev/__init__.py +25 -0
- devsper/dev/builder.py +195 -0
- devsper/dev/debugger.py +95 -0
- devsper/dev/repo_index.py +138 -0
- devsper/dev/sandbox.py +203 -0
- devsper/dev/scaffold.py +122 -0
- devsper/embeddings/__init__.py +5 -0
- devsper/embeddings/service.py +36 -0
- devsper/explainability/__init__.py +14 -0
- devsper/explainability/decision_tree.py +104 -0
- devsper/explainability/rationale.py +38 -0
- devsper/explainability/simulation.py +56 -0
- devsper/hitl/__init__.py +13 -0
- devsper/hitl/approval.py +160 -0
- devsper/hitl/escalation.py +95 -0
- devsper/intelligence/__init__.py +9 -0
- devsper/intelligence/adaptation.py +88 -0
- devsper/intelligence/analysis/__init__.py +19 -0
- devsper/intelligence/analysis/analyzer.py +71 -0
- devsper/intelligence/analysis/cost_estimator.py +66 -0
- devsper/intelligence/analysis/formatter.py +103 -0
- devsper/intelligence/analysis/run_report.py +402 -0
- devsper/intelligence/learning_engine.py +92 -0
- devsper/intelligence/strategies/__init__.py +23 -0
- devsper/intelligence/strategies/base.py +14 -0
- devsper/intelligence/strategies/code_analysis_strategy.py +33 -0
- devsper/intelligence/strategies/data_science_strategy.py +33 -0
- devsper/intelligence/strategies/document_pipeline_strategy.py +33 -0
- devsper/intelligence/strategies/experiment_strategy.py +33 -0
- devsper/intelligence/strategies/research_strategy.py +34 -0
- devsper/intelligence/strategy_selector.py +84 -0
- devsper/intelligence/synthesis.py +132 -0
- devsper/intelligence/task_optimizer.py +92 -0
- devsper/knowledge/__init__.py +5 -0
- devsper/knowledge/extractor.py +204 -0
- devsper/knowledge/knowledge_graph.py +184 -0
- devsper/knowledge/query.py +285 -0
- devsper/memory/__init__.py +35 -0
- devsper/memory/consolidation.py +138 -0
- devsper/memory/embeddings.py +60 -0
- devsper/memory/memory_index.py +97 -0
- devsper/memory/memory_router.py +62 -0
- devsper/memory/memory_store.py +221 -0
- devsper/memory/memory_types.py +54 -0
- devsper/memory/namespaces.py +45 -0
- devsper/memory/scoring.py +77 -0
- devsper/memory/summarizer.py +52 -0
- devsper/nodes/__init__.py +5 -0
- devsper/nodes/controller.py +449 -0
- devsper/nodes/rpc.py +127 -0
- devsper/nodes/single.py +161 -0
- devsper/nodes/worker.py +506 -0
- devsper/orchestration/__init__.py +19 -0
- devsper/orchestration/meta_planner.py +239 -0
- devsper/orchestration/priority_queue.py +61 -0
- devsper/plugins/__init__.py +19 -0
- devsper/plugins/marketplace/__init__.py +0 -0
- devsper/plugins/plugin_loader.py +70 -0
- devsper/plugins/plugin_registry.py +34 -0
- devsper/plugins/registry.py +83 -0
- devsper/protocols/__init__.py +6 -0
- devsper/providers/__init__.py +17 -0
- devsper/providers/anthropic.py +84 -0
- devsper/providers/base.py +75 -0
- devsper/providers/complexity_router.py +94 -0
- devsper/providers/gemini.py +36 -0
- devsper/providers/github.py +180 -0
- devsper/providers/model_router.py +40 -0
- devsper/providers/openai.py +105 -0
- devsper/providers/router/__init__.py +21 -0
- devsper/providers/router/backends/__init__.py +19 -0
- devsper/providers/router/backends/anthropic_backend.py +111 -0
- devsper/providers/router/backends/custom_backend.py +138 -0
- devsper/providers/router/backends/gemini_backend.py +89 -0
- devsper/providers/router/backends/github_backend.py +165 -0
- devsper/providers/router/backends/ollama_backend.py +104 -0
- devsper/providers/router/backends/openai_backend.py +142 -0
- devsper/providers/router/backends/vllm_backend.py +35 -0
- devsper/providers/router/base.py +60 -0
- devsper/providers/router/factory.py +92 -0
- devsper/providers/router/legacy.py +101 -0
- devsper/providers/router/router.py +135 -0
- devsper/reasoning/__init__.py +12 -0
- devsper/reasoning/graph.py +59 -0
- devsper/reasoning/nodes.py +20 -0
- devsper/reasoning/store.py +67 -0
- devsper/runtime/__init__.py +12 -0
- devsper/runtime/health.py +88 -0
- devsper/runtime/replay.py +53 -0
- devsper/runtime/replay_engine.py +142 -0
- devsper/runtime/run_history.py +204 -0
- devsper/runtime/telemetry.py +116 -0
- devsper/runtime/visualize.py +58 -0
- devsper/sandbox/__init__.py +13 -0
- devsper/sandbox/sandbox.py +161 -0
- devsper/swarm/checkpointer.py +65 -0
- devsper/swarm/executor.py +558 -0
- devsper/swarm/map_reduce.py +44 -0
- devsper/swarm/planner.py +197 -0
- devsper/swarm/prefetcher.py +91 -0
- devsper/swarm/scheduler.py +153 -0
- devsper/swarm/speculation.py +47 -0
- devsper/swarm/swarm.py +562 -0
- devsper/tools/__init__.py +33 -0
- devsper/tools/base.py +29 -0
- devsper/tools/code_intelligence/__init__.py +13 -0
- devsper/tools/code_intelligence/api_surface_extractor.py +73 -0
- devsper/tools/code_intelligence/architecture_analyzer.py +65 -0
- devsper/tools/code_intelligence/codebase_indexer.py +71 -0
- devsper/tools/code_intelligence/dependency_graph_builder.py +67 -0
- devsper/tools/code_intelligence/design_pattern_detector.py +62 -0
- devsper/tools/code_intelligence/large_function_detector.py +68 -0
- devsper/tools/code_intelligence/module_responsibility_mapper.py +56 -0
- devsper/tools/code_intelligence/parallel_codebase_analysis.py +44 -0
- devsper/tools/code_intelligence/refactor_candidate_detector.py +81 -0
- devsper/tools/code_intelligence/repository_semantic_index.py +61 -0
- devsper/tools/code_intelligence/test_coverage_estimator.py +62 -0
- devsper/tools/coding/__init__.py +12 -0
- devsper/tools/coding/analyze_code_complexity.py +48 -0
- devsper/tools/coding/dependency_analyzer.py +42 -0
- devsper/tools/coding/extract_functions.py +38 -0
- devsper/tools/coding/format_python.py +50 -0
- devsper/tools/coding/generate_docstrings.py +40 -0
- devsper/tools/coding/generate_unit_tests.py +42 -0
- devsper/tools/coding/lint_python.py +51 -0
- devsper/tools/coding/refactor_function.py +41 -0
- devsper/tools/coding/repo_structure_map.py +54 -0
- devsper/tools/coding/run_python.py +53 -0
- devsper/tools/data/__init__.py +12 -0
- devsper/tools/data/column_type_detection.py +64 -0
- devsper/tools/data/csv_summary.py +52 -0
- devsper/tools/data/dataframe_filter.py +51 -0
- devsper/tools/data/dataframe_groupby.py +47 -0
- devsper/tools/data/dataframe_stats.py +38 -0
- devsper/tools/data/dataset_sampling.py +55 -0
- devsper/tools/data/dataset_schema.py +45 -0
- devsper/tools/data/json_pretty_print.py +37 -0
- devsper/tools/data/json_query.py +46 -0
- devsper/tools/data/missing_value_report.py +47 -0
- devsper/tools/data_science/__init__.py +13 -0
- devsper/tools/data_science/correlation_heatmap.py +72 -0
- devsper/tools/data_science/dataset_bias_detector.py +49 -0
- devsper/tools/data_science/dataset_distribution_report.py +64 -0
- devsper/tools/data_science/dataset_drift_detector.py +64 -0
- devsper/tools/data_science/dataset_outlier_detector.py +65 -0
- devsper/tools/data_science/dataset_profile.py +76 -0
- devsper/tools/data_science/distributed_dataset_processor.py +54 -0
- devsper/tools/data_science/feature_engineering_suggestions.py +69 -0
- devsper/tools/data_science/feature_importance_estimator.py +82 -0
- devsper/tools/data_science/model_input_validator.py +59 -0
- devsper/tools/data_science/time_series_analyzer.py +57 -0
- devsper/tools/documents/__init__.py +11 -0
- devsper/tools/documents/_docproc.py +56 -0
- devsper/tools/documents/document_to_markdown.py +29 -0
- devsper/tools/documents/extract_document_images.py +39 -0
- devsper/tools/documents/extract_document_text.py +29 -0
- devsper/tools/documents/extract_equations.py +36 -0
- devsper/tools/documents/extract_tables.py +47 -0
- devsper/tools/documents/summarize_document.py +42 -0
- devsper/tools/documents/write_latex_document.py +133 -0
- devsper/tools/documents/write_markdown_document.py +89 -0
- devsper/tools/documents/write_word_document.py +149 -0
- devsper/tools/experiments/__init__.py +13 -0
- devsper/tools/experiments/bootstrap_estimator.py +54 -0
- devsper/tools/experiments/experiment_report_generator.py +50 -0
- devsper/tools/experiments/experiment_tracker.py +36 -0
- devsper/tools/experiments/grid_search_runner.py +50 -0
- devsper/tools/experiments/model_benchmark_runner.py +45 -0
- devsper/tools/experiments/monte_carlo_experiment.py +38 -0
- devsper/tools/experiments/parameter_sweep_runner.py +51 -0
- devsper/tools/experiments/result_comparator.py +58 -0
- devsper/tools/experiments/simulation_runner.py +43 -0
- devsper/tools/experiments/statistical_significance_test.py +56 -0
- devsper/tools/experiments/swarm_map_reduce.py +42 -0
- devsper/tools/filesystem/__init__.py +12 -0
- devsper/tools/filesystem/append_file.py +42 -0
- devsper/tools/filesystem/file_hash.py +40 -0
- devsper/tools/filesystem/file_line_count.py +36 -0
- devsper/tools/filesystem/file_metadata.py +38 -0
- devsper/tools/filesystem/file_preview.py +55 -0
- devsper/tools/filesystem/find_large_files.py +50 -0
- devsper/tools/filesystem/list_directory.py +39 -0
- devsper/tools/filesystem/read_file.py +35 -0
- devsper/tools/filesystem/search_files.py +60 -0
- devsper/tools/filesystem/write_file.py +41 -0
- devsper/tools/flagship/__init__.py +15 -0
- devsper/tools/flagship/distributed_document_analysis.py +77 -0
- devsper/tools/flagship/docproc_corpus_pipeline.py +91 -0
- devsper/tools/flagship/repository_semantic_map.py +99 -0
- devsper/tools/flagship/research_graph_builder.py +111 -0
- devsper/tools/flagship/swarm_experiment_runner.py +86 -0
- devsper/tools/knowledge/__init__.py +10 -0
- devsper/tools/knowledge/citation_graph_builder.py +69 -0
- devsper/tools/knowledge/concept_frequency_analyzer.py +74 -0
- devsper/tools/knowledge/corpus_builder.py +66 -0
- devsper/tools/knowledge/cross_document_entity_linker.py +71 -0
- devsper/tools/knowledge/document_corpus_summary.py +68 -0
- devsper/tools/knowledge/document_topic_extractor.py +58 -0
- devsper/tools/knowledge/knowledge_graph_extractor.py +58 -0
- devsper/tools/knowledge/timeline_extractor.py +59 -0
- devsper/tools/math/__init__.py +12 -0
- devsper/tools/math/calculate_expression.py +52 -0
- devsper/tools/math/correlation.py +44 -0
- devsper/tools/math/distribution_summary.py +39 -0
- devsper/tools/math/histogram.py +53 -0
- devsper/tools/math/linear_regression.py +47 -0
- devsper/tools/math/matrix_multiply.py +38 -0
- devsper/tools/math/mean_std.py +35 -0
- devsper/tools/math/monte_carlo_simulation.py +43 -0
- devsper/tools/math/polynomial_fit.py +40 -0
- devsper/tools/math/random_sample.py +36 -0
- devsper/tools/mcp/__init__.py +23 -0
- devsper/tools/mcp/adapter.py +53 -0
- devsper/tools/mcp/client.py +235 -0
- devsper/tools/mcp/discovery.py +53 -0
- devsper/tools/memory/__init__.py +16 -0
- devsper/tools/memory/delete_memory.py +25 -0
- devsper/tools/memory/list_memory.py +34 -0
- devsper/tools/memory/search_memory.py +36 -0
- devsper/tools/memory/store_memory.py +47 -0
- devsper/tools/memory/summarize_memory.py +41 -0
- devsper/tools/memory/tag_memory.py +47 -0
- devsper/tools/pipelines.py +92 -0
- devsper/tools/registry.py +39 -0
- devsper/tools/research/__init__.py +12 -0
- devsper/tools/research/arxiv_download.py +55 -0
- devsper/tools/research/arxiv_search.py +58 -0
- devsper/tools/research/citation_extractor.py +35 -0
- devsper/tools/research/duckduckgo_search.py +42 -0
- devsper/tools/research/paper_metadata_extractor.py +45 -0
- devsper/tools/research/paper_summarizer.py +41 -0
- devsper/tools/research/research_question_generator.py +39 -0
- devsper/tools/research/topic_cluster.py +46 -0
- devsper/tools/research/web_search.py +47 -0
- devsper/tools/research/wikipedia_lookup.py +50 -0
- devsper/tools/research_advanced/__init__.py +14 -0
- devsper/tools/research_advanced/citation_context_extractor.py +60 -0
- devsper/tools/research_advanced/literature_review_generator.py +79 -0
- devsper/tools/research_advanced/methodology_extractor.py +58 -0
- devsper/tools/research_advanced/paper_contribution_extractor.py +50 -0
- devsper/tools/research_advanced/paper_dataset_identifier.py +49 -0
- devsper/tools/research_advanced/paper_method_comparator.py +62 -0
- devsper/tools/research_advanced/paper_similarity_search.py +69 -0
- devsper/tools/research_advanced/paper_trend_analyzer.py +69 -0
- devsper/tools/research_advanced/parallel_document_analyzer.py +56 -0
- devsper/tools/research_advanced/research_gap_finder.py +71 -0
- devsper/tools/research_advanced/research_topic_mapper.py +69 -0
- devsper/tools/research_advanced/swarm_literature_review.py +58 -0
- devsper/tools/scoring/__init__.py +52 -0
- devsper/tools/scoring/report.py +44 -0
- devsper/tools/scoring/scorer.py +39 -0
- devsper/tools/scoring/selector.py +61 -0
- devsper/tools/scoring/store.py +267 -0
- devsper/tools/selector.py +130 -0
- devsper/tools/system/__init__.py +12 -0
- devsper/tools/system/cpu_usage.py +22 -0
- devsper/tools/system/disk_usage.py +35 -0
- devsper/tools/system/environment_variables.py +29 -0
- devsper/tools/system/memory_usage.py +23 -0
- devsper/tools/system/pip_install.py +44 -0
- devsper/tools/system/pip_search.py +29 -0
- devsper/tools/system/process_list.py +34 -0
- devsper/tools/system/python_package_list.py +40 -0
- devsper/tools/system/run_shell_command.py +51 -0
- devsper/tools/system/system_info.py +26 -0
- devsper/tools/tool_runner.py +122 -0
- devsper/tui/__init__.py +5 -0
- devsper/tui/activity_feed_view.py +73 -0
- devsper/tui/adaptive_tasks_view.py +75 -0
- devsper/tui/agent_role_view.py +35 -0
- devsper/tui/app.py +395 -0
- devsper/tui/dashboard_screen.py +290 -0
- devsper/tui/dev_view.py +99 -0
- devsper/tui/inject_screen.py +73 -0
- devsper/tui/knowledge_graph_view.py +46 -0
- devsper/tui/layout.py +43 -0
- devsper/tui/logs_view.py +83 -0
- devsper/tui/memory_view.py +58 -0
- devsper/tui/performance_view.py +33 -0
- devsper/tui/reasoning_graph_view.py +39 -0
- devsper/tui/results_view.py +139 -0
- devsper/tui/swarm_view.py +37 -0
- devsper/tui/task_detail_screen.py +55 -0
- devsper/tui/task_view.py +103 -0
- devsper/types/event.py +97 -0
- devsper/types/exceptions.py +21 -0
- devsper/types/swarm.py +41 -0
- devsper/types/task.py +80 -0
- devsper/upgrade/__init__.py +21 -0
- devsper/upgrade/changelog.py +124 -0
- devsper/upgrade/cli.py +145 -0
- devsper/upgrade/installer.py +103 -0
- devsper/upgrade/notifier.py +52 -0
- devsper/upgrade/version_check.py +121 -0
- devsper/utils/event_logger.py +88 -0
- devsper/utils/http.py +43 -0
- devsper/utils/models.py +54 -0
- devsper/visualization/__init__.py +5 -0
- devsper/visualization/dag_export.py +67 -0
- devsper/workflow/__init__.py +18 -0
- devsper/workflow/conditions.py +157 -0
- devsper/workflow/context.py +108 -0
- devsper/workflow/loader.py +156 -0
- devsper/workflow/resolver.py +109 -0
- devsper/workflow/runner.py +562 -0
- devsper/workflow/schema.py +63 -0
- devsper/workflow/validator.py +128 -0
- devsper-2.1.6.dist-info/METADATA +346 -0
- devsper-2.1.6.dist-info/RECORD +375 -0
- devsper-2.1.6.dist-info/WHEEL +4 -0
- devsper-2.1.6.dist-info/entry_points.txt +3 -0
- devsper-2.1.6.dist-info/licenses/LICENSE +639 -0
devsper/tui/dev_view.py
ADDED
|
@@ -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")
|
devsper/tui/logs_view.py
ADDED
|
@@ -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)
|