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
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Installer detection (uv vs pip) and install execution.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Literal
|
|
11
|
+
|
|
12
|
+
PACKAGE_NAME = "devsper"
|
|
13
|
+
INSTALL_TIMEOUT = 120
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def detect_installer() -> Literal["uv", "pip"]:
|
|
17
|
+
"""
|
|
18
|
+
Check in order: DEVSPER_INSTALLER env, uv-managed env, which uv, else pip.
|
|
19
|
+
"""
|
|
20
|
+
env = os.environ.get("DEVSPER_INSTALLER", "").strip().lower()
|
|
21
|
+
if env in ("uv", "pip"):
|
|
22
|
+
return "uv" if env == "uv" else "pip"
|
|
23
|
+
|
|
24
|
+
# Check if we're in a uv-managed environment
|
|
25
|
+
cwd = Path.cwd()
|
|
26
|
+
home = Path.home()
|
|
27
|
+
while cwd != home and cwd != cwd.parent:
|
|
28
|
+
if (cwd / ".python-version").exists() or (cwd / "uv.lock").exists():
|
|
29
|
+
if shutil.which("uv"):
|
|
30
|
+
return "uv"
|
|
31
|
+
break
|
|
32
|
+
cwd = cwd.parent
|
|
33
|
+
if (home / ".python-version").exists() or (home / "uv.lock").exists():
|
|
34
|
+
if shutil.which("uv"):
|
|
35
|
+
return "uv"
|
|
36
|
+
|
|
37
|
+
# sys.executable in .venv or /uv/ often indicates uv
|
|
38
|
+
exe = sys.executable
|
|
39
|
+
if "/uv/" in exe or ".venv" in exe:
|
|
40
|
+
if shutil.which("uv"):
|
|
41
|
+
return "uv"
|
|
42
|
+
|
|
43
|
+
if shutil.which("uv"):
|
|
44
|
+
return "uv"
|
|
45
|
+
return "pip"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def get_install_command(
|
|
49
|
+
installer: Literal["uv", "pip"],
|
|
50
|
+
package: str = PACKAGE_NAME,
|
|
51
|
+
version: str | None = None,
|
|
52
|
+
) -> list[str]:
|
|
53
|
+
"""Build the install command list."""
|
|
54
|
+
spec = f"{package}=={version}" if version else package
|
|
55
|
+
if installer == "uv":
|
|
56
|
+
return ["uv", "pip", "install", "--upgrade", spec]
|
|
57
|
+
return [sys.executable, "-m", "pip", "install", "--upgrade", spec]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def perform_install(
|
|
61
|
+
installer: Literal["uv", "pip"], version: str | None = None
|
|
62
|
+
) -> tuple[bool, str]:
|
|
63
|
+
"""
|
|
64
|
+
Run the install command. Returns (success, output).
|
|
65
|
+
Timeout 120 seconds.
|
|
66
|
+
"""
|
|
67
|
+
cmd = get_install_command(installer, version=version)
|
|
68
|
+
try:
|
|
69
|
+
result = subprocess.run(
|
|
70
|
+
cmd,
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True,
|
|
73
|
+
timeout=INSTALL_TIMEOUT,
|
|
74
|
+
)
|
|
75
|
+
out = (result.stdout or "") + (result.stderr or "")
|
|
76
|
+
if result.returncode != 0:
|
|
77
|
+
return (False, out)
|
|
78
|
+
return (True, out)
|
|
79
|
+
except subprocess.TimeoutExpired:
|
|
80
|
+
return (False, "Installation timed out after 120 seconds.")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
return (False, str(e))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def verify_installation(expected_version: str) -> bool:
|
|
86
|
+
"""Run Python to get installed devsper version and compare to expected."""
|
|
87
|
+
try:
|
|
88
|
+
result = subprocess.run(
|
|
89
|
+
[
|
|
90
|
+
sys.executable,
|
|
91
|
+
"-c",
|
|
92
|
+
"import importlib.metadata; print(importlib.metadata.version('devsper'))",
|
|
93
|
+
],
|
|
94
|
+
capture_output=True,
|
|
95
|
+
text=True,
|
|
96
|
+
timeout=10,
|
|
97
|
+
)
|
|
98
|
+
if result.returncode != 0:
|
|
99
|
+
return False
|
|
100
|
+
actual = result.stdout.strip()
|
|
101
|
+
return actual == expected_version
|
|
102
|
+
except Exception:
|
|
103
|
+
return False
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Startup nag: print upgrade notice if behind, once per session; suppress during upgrade.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
|
|
10
|
+
_notified = False
|
|
11
|
+
_suppress = False
|
|
12
|
+
_console = Console(stderr=True)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def suppress_notifications() -> None:
|
|
16
|
+
"""Call when running 'devsper upgrade' so the nag does not appear."""
|
|
17
|
+
global _suppress
|
|
18
|
+
_suppress = True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_and_notify() -> None:
|
|
22
|
+
"""
|
|
23
|
+
If an update is available, print a compact Rich notice once per session.
|
|
24
|
+
Non-blocking, try/except, aim for <100ms (cache read usually).
|
|
25
|
+
"""
|
|
26
|
+
global _notified
|
|
27
|
+
if _suppress or _notified:
|
|
28
|
+
return
|
|
29
|
+
deadline = time.monotonic() + 0.1 # 100ms max
|
|
30
|
+
try:
|
|
31
|
+
from .version_check import is_update_available, get_version_diff_type
|
|
32
|
+
|
|
33
|
+
if time.monotonic() > deadline:
|
|
34
|
+
return
|
|
35
|
+
available, current, latest = is_update_available()
|
|
36
|
+
if not available or current == latest:
|
|
37
|
+
return
|
|
38
|
+
if time.monotonic() > deadline:
|
|
39
|
+
return
|
|
40
|
+
_notified = True
|
|
41
|
+
diff_type = get_version_diff_type(current, latest)
|
|
42
|
+
color: str = {"major": "red", "minor": "yellow", "patch": "green"}.get(
|
|
43
|
+
diff_type, "white"
|
|
44
|
+
)
|
|
45
|
+
msg = (
|
|
46
|
+
f"Update available: [bold]{current}[/bold] → [bold]{latest}[/bold] "
|
|
47
|
+
f"[{color}][{diff_type}][/{color}]\n"
|
|
48
|
+
"Run [bold]devsper upgrade[/bold] to update"
|
|
49
|
+
)
|
|
50
|
+
_console.print(Panel(msg, border_style="dim", padding=(0, 1)))
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Version checking: PyPI latest version, cache, semver comparison.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Literal
|
|
8
|
+
|
|
9
|
+
import httpx
|
|
10
|
+
|
|
11
|
+
PYPI_URL = "https://pypi.org/pypi/devsper/json"
|
|
12
|
+
CACHE_FILE = Path("~/.config/devsper/update_check.json").expanduser()
|
|
13
|
+
CACHE_TTL_HOURS = 24
|
|
14
|
+
PACKAGE_NAME = "devsper"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_current_version() -> str:
|
|
18
|
+
"""Read current installed version from importlib.metadata."""
|
|
19
|
+
import importlib.metadata
|
|
20
|
+
|
|
21
|
+
return importlib.metadata.version(PACKAGE_NAME)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _cache_is_fresh() -> bool:
|
|
25
|
+
if not CACHE_FILE.is_file():
|
|
26
|
+
return False
|
|
27
|
+
try:
|
|
28
|
+
data = json.loads(CACHE_FILE.read_text())
|
|
29
|
+
checked_at = data.get("checked_at", "")
|
|
30
|
+
if not checked_at:
|
|
31
|
+
return False
|
|
32
|
+
from datetime import datetime, timezone
|
|
33
|
+
|
|
34
|
+
# ISO format with Z
|
|
35
|
+
dt = datetime.fromisoformat(checked_at.replace("Z", "+00:00"))
|
|
36
|
+
now = datetime.now(timezone.utc)
|
|
37
|
+
age_hours = (now - dt).total_seconds() / 3600
|
|
38
|
+
return age_hours < CACHE_TTL_HOURS
|
|
39
|
+
except (json.JSONDecodeError, ValueError, OSError):
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _read_cached_version() -> str | None:
|
|
44
|
+
if not _cache_is_fresh():
|
|
45
|
+
return None
|
|
46
|
+
try:
|
|
47
|
+
data = json.loads(CACHE_FILE.read_text())
|
|
48
|
+
return data.get("latest")
|
|
49
|
+
except (json.JSONDecodeError, OSError):
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _write_cache(latest: str) -> None:
|
|
54
|
+
from datetime import datetime, timezone
|
|
55
|
+
|
|
56
|
+
CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
57
|
+
data = {
|
|
58
|
+
"latest": latest,
|
|
59
|
+
"checked_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
60
|
+
}
|
|
61
|
+
CACHE_FILE.write_text(json.dumps(data, indent=2))
|
|
62
|
+
try:
|
|
63
|
+
CACHE_FILE.chmod(0o600)
|
|
64
|
+
except OSError:
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_latest_version() -> str:
|
|
69
|
+
"""
|
|
70
|
+
Fetch latest version from PyPI with 24h cache.
|
|
71
|
+
On network error, return current version (fail silently).
|
|
72
|
+
"""
|
|
73
|
+
cached = _read_cached_version()
|
|
74
|
+
if cached is not None:
|
|
75
|
+
return cached
|
|
76
|
+
try:
|
|
77
|
+
resp = httpx.get(PYPI_URL, timeout=5.0)
|
|
78
|
+
resp.raise_for_status()
|
|
79
|
+
info = resp.json().get("info") or {}
|
|
80
|
+
latest = info.get("version") or get_current_version()
|
|
81
|
+
_write_cache(latest)
|
|
82
|
+
return latest
|
|
83
|
+
except (httpx.HTTPError, json.JSONDecodeError, KeyError):
|
|
84
|
+
return get_current_version()
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_semver(version: str) -> tuple[int, int, int]:
|
|
88
|
+
"""Parse '1.2.3' or '1.2.3.post1' into (1, 2, 3)."""
|
|
89
|
+
# Strip pre/post/dev suffixes for comparison
|
|
90
|
+
base = version.split("+")[0].split("-")[0]
|
|
91
|
+
parts = base.split(".")
|
|
92
|
+
major = int(parts[0]) if len(parts) > 0 else 0
|
|
93
|
+
minor = int(parts[1]) if len(parts) > 1 else 0
|
|
94
|
+
patch = int(parts[2]) if len(parts) > 2 else 0
|
|
95
|
+
return (major, minor, patch)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def get_version_diff_type(
|
|
99
|
+
current: str, latest: str
|
|
100
|
+
) -> Literal["major", "minor", "patch"]:
|
|
101
|
+
"""Compare semver tuples; return highest changed segment."""
|
|
102
|
+
c = parse_semver(current)
|
|
103
|
+
l = parse_semver(latest)
|
|
104
|
+
if l[0] != c[0]:
|
|
105
|
+
return "major"
|
|
106
|
+
if l[1] != c[1]:
|
|
107
|
+
return "minor"
|
|
108
|
+
return "patch"
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_update_available() -> tuple[bool, str, str]:
|
|
112
|
+
"""
|
|
113
|
+
Returns (available, current, latest).
|
|
114
|
+
Uses cache for latest; on network error latest may equal current.
|
|
115
|
+
"""
|
|
116
|
+
current = get_current_version()
|
|
117
|
+
latest = get_latest_version()
|
|
118
|
+
c = parse_semver(current)
|
|
119
|
+
l = parse_semver(latest)
|
|
120
|
+
available = (l[0], l[1], l[2]) > (c[0], c[1], c[2])
|
|
121
|
+
return (available, current, latest)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
|
|
5
|
+
from devsper.types.event import Event, events
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Map Event types to bus topics (v1.9). Unmapped events use event.<type_value>.
|
|
9
|
+
EVENT_TO_TOPIC = {
|
|
10
|
+
events.TASK_STARTED: "task.started",
|
|
11
|
+
events.TASK_COMPLETED: "task.completed",
|
|
12
|
+
events.TASK_FAILED: "task.failed",
|
|
13
|
+
events.TASK_CREATED: "task.ready",
|
|
14
|
+
events.AGENT_BROADCAST: "agent.broadcast",
|
|
15
|
+
events.SWARM_STARTED: "swarm.control",
|
|
16
|
+
events.SWARM_FINISHED: "swarm.control",
|
|
17
|
+
events.AGENT_STARTED: "agent.broadcast",
|
|
18
|
+
events.AGENT_FINISHED: "agent.broadcast",
|
|
19
|
+
events.EXECUTOR_STARTED: "swarm.control",
|
|
20
|
+
events.EXECUTOR_FINISHED: "swarm.control",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _event_to_bus_topic(event_type: events) -> str:
|
|
25
|
+
return EVENT_TO_TOPIC.get(event_type, f"event.{event_type.value}")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class EventLog:
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
events_folder_path: str = ".devsper/events",
|
|
32
|
+
bus: object = None,
|
|
33
|
+
run_id: str | None = None,
|
|
34
|
+
):
|
|
35
|
+
os.makedirs(events_folder_path, exist_ok=True)
|
|
36
|
+
self.log_path = os.path.join(
|
|
37
|
+
events_folder_path, f"events_{datetime.now(timezone.utc)}.jsonl"
|
|
38
|
+
)
|
|
39
|
+
self._bus = bus
|
|
40
|
+
self._run_id = run_id
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def run_id(self) -> str:
|
|
44
|
+
"""Identifier for this run (basename of log file without extension)."""
|
|
45
|
+
if self._run_id is not None:
|
|
46
|
+
return self._run_id
|
|
47
|
+
return os.path.basename(self.log_path).replace(".jsonl", "")
|
|
48
|
+
|
|
49
|
+
def append_event(self, event: Event) -> None:
|
|
50
|
+
with open(self.log_path, "a") as f:
|
|
51
|
+
f.write(event.model_dump_json() + "\n")
|
|
52
|
+
if self._bus is not None:
|
|
53
|
+
self._publish_to_bus(event)
|
|
54
|
+
|
|
55
|
+
def _publish_to_bus(self, event: Event) -> None:
|
|
56
|
+
try:
|
|
57
|
+
from devsper.bus.message import create_bus_message
|
|
58
|
+
topic = _event_to_bus_topic(event.type)
|
|
59
|
+
payload = event.to_dict()
|
|
60
|
+
msg = create_bus_message(
|
|
61
|
+
topic=topic,
|
|
62
|
+
payload=payload,
|
|
63
|
+
run_id=getattr(self, "run_id", "") or "",
|
|
64
|
+
)
|
|
65
|
+
loop = None
|
|
66
|
+
try:
|
|
67
|
+
loop = asyncio.get_running_loop()
|
|
68
|
+
except RuntimeError:
|
|
69
|
+
pass
|
|
70
|
+
if loop is not None:
|
|
71
|
+
loop.create_task(self._bus.publish(msg))
|
|
72
|
+
else:
|
|
73
|
+
try:
|
|
74
|
+
asyncio.run(self._bus.publish(msg))
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
except Exception:
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
def read_events(self) -> list[Event]:
|
|
81
|
+
if not os.path.exists(self.log_path):
|
|
82
|
+
return []
|
|
83
|
+
with open(self.log_path, "r") as f:
|
|
84
|
+
return [Event.model_validate_json(line) for line in f if line.strip()]
|
|
85
|
+
|
|
86
|
+
def clear(self) -> None:
|
|
87
|
+
if os.path.exists(self.log_path):
|
|
88
|
+
os.remove(self.log_path)
|
devsper/utils/http.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""HTTP client helpers. Respects DEVSPER_SSL_VERIFY for corp/uni networks with SSL inspection."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def format_retry_after(response: Any) -> str:
|
|
8
|
+
"""
|
|
9
|
+
Read Retry-After from response headers and return a short message for the user.
|
|
10
|
+
response: object with .headers (e.g. httpx.Response). Headers may be case-insensitive.
|
|
11
|
+
Returns e.g. " Back off: retry after 60s" or " Back off: retry after <date>" or "".
|
|
12
|
+
"""
|
|
13
|
+
if response is None:
|
|
14
|
+
return ""
|
|
15
|
+
headers = getattr(response, "headers", None)
|
|
16
|
+
if headers is None:
|
|
17
|
+
return ""
|
|
18
|
+
try:
|
|
19
|
+
raw = headers.get("retry-after")
|
|
20
|
+
except Exception:
|
|
21
|
+
raw = None
|
|
22
|
+
if raw is None:
|
|
23
|
+
return ""
|
|
24
|
+
raw = str(raw).strip()
|
|
25
|
+
if not raw:
|
|
26
|
+
return ""
|
|
27
|
+
# Integer seconds
|
|
28
|
+
try:
|
|
29
|
+
sec = int(raw)
|
|
30
|
+
return f" Back off: retry after {sec}s"
|
|
31
|
+
except ValueError:
|
|
32
|
+
pass
|
|
33
|
+
# HTTP-date or other
|
|
34
|
+
return f" Back off: Retry-After {raw}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def ssl_verify() -> bool:
|
|
38
|
+
"""
|
|
39
|
+
Return False if DEVSPER_SSL_VERIFY is 'false' or '0' (e.g. university/corp WiFi with MITM).
|
|
40
|
+
Otherwise True. Use for httpx Client(verify=ssl_verify()).
|
|
41
|
+
"""
|
|
42
|
+
v = os.environ.get("DEVSPER_SSL_VERIFY", "true").strip().lower()
|
|
43
|
+
return v not in ("false", "0", "no", "off")
|
devsper/utils/models.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Model invocation layer: generate(model_name, prompt) -> text.
|
|
3
|
+
|
|
4
|
+
Uses v2 LLMRouter when available (from config), else legacy ProviderRouter.
|
|
5
|
+
When model_name is "auto", use resolve_model(spec, task_type) first.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
|
|
10
|
+
from devsper.providers.model_router import TaskType, select_model
|
|
11
|
+
from devsper.providers.router import get_router
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def resolve_model(model_spec: str, task_type: TaskType) -> str:
|
|
15
|
+
"""If model_spec is 'auto', return model_router.select_model(task_type); else return model_spec."""
|
|
16
|
+
if (model_spec or "").strip().lower() == "auto":
|
|
17
|
+
return select_model(task_type)
|
|
18
|
+
return model_spec or "mock"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _run_async(coro):
|
|
22
|
+
"""Run coroutine from sync context (new loop or current)."""
|
|
23
|
+
try:
|
|
24
|
+
loop = asyncio.get_running_loop()
|
|
25
|
+
except RuntimeError:
|
|
26
|
+
return asyncio.run(coro)
|
|
27
|
+
import concurrent.futures
|
|
28
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
29
|
+
future = pool.submit(asyncio.run, coro)
|
|
30
|
+
return future.result()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def generate(model_name: str, prompt: str, stream: bool = False):
|
|
34
|
+
"""Call the model with the given prompt and return text output (or stream iterator if stream=True)."""
|
|
35
|
+
if not stream:
|
|
36
|
+
try:
|
|
37
|
+
from devsper.providers.router.factory import get_llm_router
|
|
38
|
+
from devsper.providers.router.base import LLMRequest
|
|
39
|
+
router = get_llm_router()
|
|
40
|
+
if router is not None:
|
|
41
|
+
req = LLMRequest(
|
|
42
|
+
model=model_name,
|
|
43
|
+
messages=[{"role": "user", "content": prompt}],
|
|
44
|
+
max_tokens=4096,
|
|
45
|
+
temperature=0.0,
|
|
46
|
+
tools=None,
|
|
47
|
+
stream=False,
|
|
48
|
+
)
|
|
49
|
+
resp = _run_async(router.route(req))
|
|
50
|
+
return resp.content
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
provider = get_router().get_provider(model_name)
|
|
54
|
+
return provider.generate(model_name, prompt, stream=stream)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Export task DAG to Mermaid or Graphviz format.
|
|
3
|
+
|
|
4
|
+
DAG is loaded from a JSON file written by the swarm run (run_id_dag.json).
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _safe_id(node_id: str) -> str:
|
|
12
|
+
"""Mermaid/Graphviz safe node id (no spaces or special chars)."""
|
|
13
|
+
return node_id.replace(" ", "_").replace("-", "_").replace(".", "_")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def load_dag(
|
|
17
|
+
events_dir: str | Path, run_id: str
|
|
18
|
+
) -> tuple[list[dict], list[tuple[str, str]]]:
|
|
19
|
+
"""
|
|
20
|
+
Load DAG from events_dir / {run_id}_dag.json.
|
|
21
|
+
Returns (nodes: [{"id", "description"}], edges: [(from_id, to_id)]).
|
|
22
|
+
"""
|
|
23
|
+
path = Path(events_dir) / f"{run_id}_dag.json"
|
|
24
|
+
if not path.is_file():
|
|
25
|
+
return [], []
|
|
26
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
27
|
+
data = json.load(f)
|
|
28
|
+
nodes = data.get("nodes", [])
|
|
29
|
+
edges = [tuple(e) for e in data.get("edges", [])]
|
|
30
|
+
return nodes, edges
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def export_mermaid(nodes: list[dict], edges: list[tuple[str, str]]) -> str:
|
|
34
|
+
"""Produce a Mermaid diagram string (flowchart)."""
|
|
35
|
+
lines = ["flowchart LR"]
|
|
36
|
+
for n in nodes:
|
|
37
|
+
nid = _safe_id(n.get("id", ""))
|
|
38
|
+
desc = (n.get("description", "") or nid).replace('"', "'")[:40]
|
|
39
|
+
lines.append(f' {nid}["{desc}"]')
|
|
40
|
+
for a, b in edges:
|
|
41
|
+
lines.append(f" {_safe_id(a)} --> {_safe_id(b)}")
|
|
42
|
+
return "\n".join(lines)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def export_graphviz(nodes: list[dict], edges: list[tuple[str, str]]) -> str:
|
|
46
|
+
"""Produce a Graphviz DOT string."""
|
|
47
|
+
lines = ["digraph G {", " rankdir=LR;"]
|
|
48
|
+
for n in nodes:
|
|
49
|
+
nid = _safe_id(n.get("id", ""))
|
|
50
|
+
desc = (n.get("description", "") or nid).replace('"', '\\"')[:40]
|
|
51
|
+
lines.append(f' {nid} [label="{desc}"];')
|
|
52
|
+
for a, b in edges:
|
|
53
|
+
lines.append(f" {_safe_id(a)} -> {_safe_id(b)};")
|
|
54
|
+
lines.append("}")
|
|
55
|
+
return "\n".join(lines)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def list_run_ids(events_dir: str | Path) -> list[str]:
|
|
59
|
+
"""List available run ids (from _dag.json files)."""
|
|
60
|
+
path = Path(events_dir)
|
|
61
|
+
if not path.is_dir():
|
|
62
|
+
return []
|
|
63
|
+
run_ids = []
|
|
64
|
+
for f in path.glob("*_dag.json"):
|
|
65
|
+
run_id = f.stem.replace("_dag", "")
|
|
66
|
+
run_ids.append(run_id)
|
|
67
|
+
return sorted(run_ids, reverse=True)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Workflow definitions: load from workflow.devsper.toml and run by name (v1.4 pipeline engine)."""
|
|
2
|
+
|
|
3
|
+
from devsper.workflow.loader import load_workflow, list_workflows
|
|
4
|
+
from devsper.workflow.runner import WorkflowRunner, run_workflow, WorkflowStepError
|
|
5
|
+
from devsper.workflow.schema import WorkflowDefinition, WorkflowStep
|
|
6
|
+
from devsper.workflow.validator import ValidationReport, validate_workflow
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"load_workflow",
|
|
10
|
+
"list_workflows",
|
|
11
|
+
"run_workflow",
|
|
12
|
+
"WorkflowRunner",
|
|
13
|
+
"WorkflowDefinition",
|
|
14
|
+
"WorkflowStep",
|
|
15
|
+
"WorkflowStepError",
|
|
16
|
+
"ValidationReport",
|
|
17
|
+
"validate_workflow",
|
|
18
|
+
]
|