draft-board 0.1.0-beta.0
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.
- package/app/backend/.env.example +9 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_patch.txt +195 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_stat.txt +6 -0
- package/app/backend/CURL_EXAMPLES.md +335 -0
- package/app/backend/ENV_SETUP.md +65 -0
- package/app/backend/alembic/env.py +71 -0
- package/app/backend/alembic/script.py.mako +28 -0
- package/app/backend/alembic/versions/001_initial_schema.py +104 -0
- package/app/backend/alembic/versions/002_add_jobs_table.py +52 -0
- package/app/backend/alembic/versions/003_add_workspace_table.py +48 -0
- package/app/backend/alembic/versions/004_add_evidence_table.py +56 -0
- package/app/backend/alembic/versions/005_add_verification_commands.py +32 -0
- package/app/backend/alembic/versions/006_add_planner_lock_table.py +39 -0
- package/app/backend/alembic/versions/007_add_revision_review_tables.py +126 -0
- package/app/backend/alembic/versions/008_add_revision_idempotency_and_traceability.py +52 -0
- package/app/backend/alembic/versions/009_add_job_health_fields.py +46 -0
- package/app/backend/alembic/versions/010_add_review_comment_line_content.py +36 -0
- package/app/backend/alembic/versions/011_add_analysis_cache.py +47 -0
- package/app/backend/alembic/versions/012_add_boards_table.py +102 -0
- package/app/backend/alembic/versions/013_add_ticket_blocking.py +45 -0
- package/app/backend/alembic/versions/014_add_agent_sessions.py +220 -0
- package/app/backend/alembic/versions/015_add_ticket_sort_order.py +33 -0
- package/app/backend/alembic/versions/03220f0b93ae_add_pr_fields_to_ticket.py +49 -0
- package/app/backend/alembic/versions/0c2d89fff3b1_seed_board_configs_from_yaml.py +206 -0
- package/app/backend/alembic/versions/3348e5cf54c1_add_merge_checklist_table.py +67 -0
- package/app/backend/alembic/versions/357c780ee445_add_goal_status.py +34 -0
- package/app/backend/alembic/versions/553340b7e26c_add_autonomy_fields_to_goal.py +65 -0
- package/app/backend/alembic/versions/774dc335c679_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/7b307e847cbd_merge_heads.py +23 -0
- package/app/backend/alembic/versions/82ecd978cc70_add_missing_indexes.py +48 -0
- package/app/backend/alembic/versions/8ef5054dc280_add_normalized_log_entries.py +173 -0
- package/app/backend/alembic/versions/8f3e2bd8ea3b_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/9d17f0698d3b_add_config_column_to_boards_table.py +30 -0
- package/app/backend/alembic/versions/add_agent_conversation_history.py +72 -0
- package/app/backend/alembic/versions/add_job_variant.py +34 -0
- package/app/backend/alembic/versions/add_performance_indexes.py +95 -0
- package/app/backend/alembic/versions/add_repos_and_board_repos.py +174 -0
- package/app/backend/alembic/versions/add_session_id_to_jobs.py +27 -0
- package/app/backend/alembic/versions/add_sqlite_backend_tables.py +104 -0
- package/app/backend/alembic/versions/b10fb0b62240_add_diff_content_to_revisions.py +34 -0
- package/app/backend/alembic.ini +89 -0
- package/app/backend/app/__init__.py +3 -0
- package/app/backend/app/data_dir.py +85 -0
- package/app/backend/app/database.py +70 -0
- package/app/backend/app/database_sync.py +64 -0
- package/app/backend/app/dependencies/__init__.py +5 -0
- package/app/backend/app/dependencies/auth.py +80 -0
- package/app/backend/app/dependencies.py +43 -0
- package/app/backend/app/exceptions.py +178 -0
- package/app/backend/app/executors/__init__.py +1 -0
- package/app/backend/app/executors/adapters/__init__.py +1 -0
- package/app/backend/app/executors/adapters/aider.py +152 -0
- package/app/backend/app/executors/adapters/amazon_q.py +103 -0
- package/app/backend/app/executors/adapters/amp.py +123 -0
- package/app/backend/app/executors/adapters/claude.py +177 -0
- package/app/backend/app/executors/adapters/cline.py +127 -0
- package/app/backend/app/executors/adapters/codex.py +167 -0
- package/app/backend/app/executors/adapters/copilot.py +202 -0
- package/app/backend/app/executors/adapters/cursor.py +87 -0
- package/app/backend/app/executors/adapters/droid.py +123 -0
- package/app/backend/app/executors/adapters/gemini.py +132 -0
- package/app/backend/app/executors/adapters/goose.py +131 -0
- package/app/backend/app/executors/adapters/opencode.py +123 -0
- package/app/backend/app/executors/adapters/qwen.py +123 -0
- package/app/backend/app/executors/plugins/__init__.py +1 -0
- package/app/backend/app/executors/registry.py +202 -0
- package/app/backend/app/executors/spec.py +226 -0
- package/app/backend/app/main.py +486 -0
- package/app/backend/app/middleware/__init__.py +13 -0
- package/app/backend/app/middleware/idempotency.py +426 -0
- package/app/backend/app/middleware/rate_limit.py +312 -0
- package/app/backend/app/middleware/security_headers.py +43 -0
- package/app/backend/app/middleware/timeout.py +37 -0
- package/app/backend/app/models/__init__.py +56 -0
- package/app/backend/app/models/agent_conversation_history.py +56 -0
- package/app/backend/app/models/agent_session.py +127 -0
- package/app/backend/app/models/analysis_cache.py +49 -0
- package/app/backend/app/models/base.py +9 -0
- package/app/backend/app/models/board.py +79 -0
- package/app/backend/app/models/board_repo.py +68 -0
- package/app/backend/app/models/cost_budget.py +42 -0
- package/app/backend/app/models/enums.py +40 -0
- package/app/backend/app/models/evidence.py +132 -0
- package/app/backend/app/models/goal.py +102 -0
- package/app/backend/app/models/idempotency_entry.py +30 -0
- package/app/backend/app/models/job.py +163 -0
- package/app/backend/app/models/job_queue.py +39 -0
- package/app/backend/app/models/kv_store.py +28 -0
- package/app/backend/app/models/merge_checklist.py +87 -0
- package/app/backend/app/models/normalized_log.py +100 -0
- package/app/backend/app/models/planner_lock.py +43 -0
- package/app/backend/app/models/rate_limit_entry.py +25 -0
- package/app/backend/app/models/repo.py +66 -0
- package/app/backend/app/models/review_comment.py +91 -0
- package/app/backend/app/models/review_summary.py +69 -0
- package/app/backend/app/models/revision.py +130 -0
- package/app/backend/app/models/ticket.py +223 -0
- package/app/backend/app/models/ticket_event.py +83 -0
- package/app/backend/app/models/user.py +47 -0
- package/app/backend/app/models/workspace.py +71 -0
- package/app/backend/app/redis_client.py +119 -0
- package/app/backend/app/routers/__init__.py +29 -0
- package/app/backend/app/routers/agents.py +296 -0
- package/app/backend/app/routers/auth.py +94 -0
- package/app/backend/app/routers/board.py +885 -0
- package/app/backend/app/routers/dashboard.py +351 -0
- package/app/backend/app/routers/debug.py +528 -0
- package/app/backend/app/routers/evidence.py +96 -0
- package/app/backend/app/routers/executors.py +324 -0
- package/app/backend/app/routers/goals.py +574 -0
- package/app/backend/app/routers/jobs.py +448 -0
- package/app/backend/app/routers/maintenance.py +172 -0
- package/app/backend/app/routers/merge.py +360 -0
- package/app/backend/app/routers/planner.py +537 -0
- package/app/backend/app/routers/pull_requests.py +382 -0
- package/app/backend/app/routers/repos.py +263 -0
- package/app/backend/app/routers/revisions.py +939 -0
- package/app/backend/app/routers/settings.py +267 -0
- package/app/backend/app/routers/tickets.py +2003 -0
- package/app/backend/app/routers/webhooks.py +143 -0
- package/app/backend/app/routers/websocket.py +249 -0
- package/app/backend/app/schemas/__init__.py +109 -0
- package/app/backend/app/schemas/board.py +87 -0
- package/app/backend/app/schemas/common.py +33 -0
- package/app/backend/app/schemas/evidence.py +87 -0
- package/app/backend/app/schemas/goal.py +90 -0
- package/app/backend/app/schemas/job.py +97 -0
- package/app/backend/app/schemas/merge.py +139 -0
- package/app/backend/app/schemas/planner.py +500 -0
- package/app/backend/app/schemas/repo.py +187 -0
- package/app/backend/app/schemas/review.py +137 -0
- package/app/backend/app/schemas/revision.py +114 -0
- package/app/backend/app/schemas/ticket.py +238 -0
- package/app/backend/app/schemas/ticket_event.py +72 -0
- package/app/backend/app/schemas/workspace.py +19 -0
- package/app/backend/app/services/__init__.py +31 -0
- package/app/backend/app/services/agent_memory_service.py +223 -0
- package/app/backend/app/services/agent_registry.py +346 -0
- package/app/backend/app/services/agent_session_manager.py +318 -0
- package/app/backend/app/services/agent_session_service.py +219 -0
- package/app/backend/app/services/agent_tools.py +379 -0
- package/app/backend/app/services/auth_service.py +98 -0
- package/app/backend/app/services/autonomy_service.py +380 -0
- package/app/backend/app/services/board_repo_service.py +201 -0
- package/app/backend/app/services/board_service.py +326 -0
- package/app/backend/app/services/cleanup_service.py +1085 -0
- package/app/backend/app/services/config_service.py +908 -0
- package/app/backend/app/services/context_gatherer.py +557 -0
- package/app/backend/app/services/cost_tracking_service.py +293 -0
- package/app/backend/app/services/cursor_log_normalizer.py +536 -0
- package/app/backend/app/services/delivery_pipeline.py +440 -0
- package/app/backend/app/services/executor_service.py +634 -0
- package/app/backend/app/services/git_host/__init__.py +11 -0
- package/app/backend/app/services/git_host/factory.py +87 -0
- package/app/backend/app/services/git_host/github.py +270 -0
- package/app/backend/app/services/git_host/gitlab.py +194 -0
- package/app/backend/app/services/git_host/protocol.py +75 -0
- package/app/backend/app/services/git_merge_simple.py +346 -0
- package/app/backend/app/services/git_ops.py +384 -0
- package/app/backend/app/services/github_service.py +233 -0
- package/app/backend/app/services/goal_service.py +113 -0
- package/app/backend/app/services/job_service.py +423 -0
- package/app/backend/app/services/job_watchdog_service.py +424 -0
- package/app/backend/app/services/langchain_adapter.py +122 -0
- package/app/backend/app/services/llm_provider_clients.py +351 -0
- package/app/backend/app/services/llm_service.py +285 -0
- package/app/backend/app/services/log_normalizer.py +342 -0
- package/app/backend/app/services/log_stream_service.py +276 -0
- package/app/backend/app/services/merge_checklist_service.py +264 -0
- package/app/backend/app/services/merge_service.py +784 -0
- package/app/backend/app/services/orchestrator_log.py +84 -0
- package/app/backend/app/services/planner_service.py +1662 -0
- package/app/backend/app/services/planner_tick_sync.py +1040 -0
- package/app/backend/app/services/queued_message_service.py +156 -0
- package/app/backend/app/services/reliability_wrapper.py +389 -0
- package/app/backend/app/services/repo_discovery_service.py +318 -0
- package/app/backend/app/services/review_service.py +334 -0
- package/app/backend/app/services/revision_service.py +389 -0
- package/app/backend/app/services/safe_autopilot.py +510 -0
- package/app/backend/app/services/sqlite_worker.py +372 -0
- package/app/backend/app/services/task_dispatch.py +135 -0
- package/app/backend/app/services/ticket_generation_service.py +1781 -0
- package/app/backend/app/services/ticket_service.py +486 -0
- package/app/backend/app/services/udar_planner_service.py +1007 -0
- package/app/backend/app/services/webhook_service.py +126 -0
- package/app/backend/app/services/workspace_service.py +465 -0
- package/app/backend/app/services/worktree_file_service.py +92 -0
- package/app/backend/app/services/worktree_validator.py +213 -0
- package/app/backend/app/sqlite_kv.py +278 -0
- package/app/backend/app/state_machine.py +128 -0
- package/app/backend/app/templates/__init__.py +5 -0
- package/app/backend/app/templates/registry.py +243 -0
- package/app/backend/app/utils/__init__.py +5 -0
- package/app/backend/app/utils/artifact_reader.py +87 -0
- package/app/backend/app/utils/circuit_breaker.py +229 -0
- package/app/backend/app/utils/db_retry.py +136 -0
- package/app/backend/app/utils/ignored_fields.py +123 -0
- package/app/backend/app/utils/validators.py +54 -0
- package/app/backend/app/websocket/__init__.py +5 -0
- package/app/backend/app/websocket/manager.py +179 -0
- package/app/backend/app/websocket/state_tracker.py +113 -0
- package/app/backend/app/worker.py +3190 -0
- package/app/backend/calculator_tickets.json +40 -0
- package/app/backend/canary_tests.sh +591 -0
- package/app/backend/celerybeat-schedule +0 -0
- package/app/backend/celerybeat-schedule-shm +0 -0
- package/app/backend/celerybeat-schedule-wal +0 -0
- package/app/backend/logs/.gitkeep +3 -0
- package/app/backend/multiplication_division_implementation_tickets.json +55 -0
- package/app/backend/multiplication_division_tickets.json +42 -0
- package/app/backend/pyproject.toml +45 -0
- package/app/backend/requirements-dev.txt +8 -0
- package/app/backend/requirements.txt +20 -0
- package/app/backend/run.sh +30 -0
- package/app/backend/run_with_logs.sh +10 -0
- package/app/backend/scientific_calculator_tickets.json +40 -0
- package/app/backend/scripts/extract_openapi.py +21 -0
- package/app/backend/scripts/seed_demo.py +187 -0
- package/app/backend/setup_demo_review.py +302 -0
- package/app/backend/test_actual_parse.py +41 -0
- package/app/backend/test_agent_streaming.py +61 -0
- package/app/backend/test_parse.py +51 -0
- package/app/backend/test_streaming.py +51 -0
- package/app/backend/test_subprocess_streaming.py +50 -0
- package/app/backend/tests/__init__.py +1 -0
- package/app/backend/tests/conftest.py +46 -0
- package/app/backend/tests/test_auth.py +341 -0
- package/app/backend/tests/test_autonomy_service.py +391 -0
- package/app/backend/tests/test_cleanup_service_safety.py +417 -0
- package/app/backend/tests/test_middleware.py +279 -0
- package/app/backend/tests/test_planner_providers.py +290 -0
- package/app/backend/tests/test_planner_unblock.py +183 -0
- package/app/backend/tests/test_revision_invariants.py +618 -0
- package/app/backend/tests/test_sqlite_kv.py +290 -0
- package/app/backend/tests/test_sqlite_worker.py +353 -0
- package/app/backend/tests/test_task_dispatch.py +100 -0
- package/app/backend/tests/test_ticket_validation.py +304 -0
- package/app/backend/tests/test_udar_agent.py +693 -0
- package/app/backend/tests/test_webhook_service.py +184 -0
- package/app/backend/tickets_output.json +59 -0
- package/app/backend/user_management_tickets.json +50 -0
- package/app/backend/uvicorn.log +0 -0
- package/app/draft.yaml +313 -0
- package/app/frontend/dist/assets/index-LcjCczu5.js +155 -0
- package/app/frontend/dist/assets/index-_FP_279e.css +1 -0
- package/app/frontend/dist/index.html +14 -0
- package/app/frontend/dist/vite.svg +1 -0
- package/app/frontend/package.json +101 -0
- package/bin/cli.js +527 -0
- package/package.json +37 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
"""Router for executor management and listing."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
6
|
+
from sqlalchemy import select
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
8
|
+
|
|
9
|
+
from app.database import get_db
|
|
10
|
+
from app.executors.registry import ExecutorRegistry
|
|
11
|
+
from app.models.board import Board
|
|
12
|
+
from app.services.config_service import DraftConfig, deep_merge_dicts
|
|
13
|
+
|
|
14
|
+
router = APIRouter(prefix="/executors", tags=["executors"])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.get("/available", response_model=list[dict[str, Any]])
|
|
18
|
+
async def list_available_executors():
|
|
19
|
+
"""List all available executors (both installed and not installed).
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List of executor metadata with availability status
|
|
23
|
+
"""
|
|
24
|
+
try:
|
|
25
|
+
# Get all registered executors via public API
|
|
26
|
+
all_executors = []
|
|
27
|
+
|
|
28
|
+
for metadata in ExecutorRegistry.list_all():
|
|
29
|
+
adapter = ExecutorRegistry.get(metadata.name)
|
|
30
|
+
is_available = await adapter.is_available()
|
|
31
|
+
|
|
32
|
+
executor_dict = {
|
|
33
|
+
"name": metadata.name,
|
|
34
|
+
"display_name": metadata.display_name,
|
|
35
|
+
"version": metadata.version,
|
|
36
|
+
"capabilities": [cap.value for cap in metadata.capabilities],
|
|
37
|
+
"config_schema": metadata.config_schema,
|
|
38
|
+
"documentation_url": metadata.documentation_url,
|
|
39
|
+
"author": metadata.author,
|
|
40
|
+
"license": metadata.license,
|
|
41
|
+
"available": is_available,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
all_executors.append(executor_dict)
|
|
45
|
+
|
|
46
|
+
return all_executors
|
|
47
|
+
|
|
48
|
+
except Exception as e:
|
|
49
|
+
raise HTTPException(
|
|
50
|
+
status_code=500, detail=f"Failed to list executors: {str(e)}"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@router.get("/{executor_name}/metadata", response_model=dict[str, Any])
|
|
55
|
+
async def get_executor_metadata(executor_name: str):
|
|
56
|
+
"""Get metadata for a specific executor.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
executor_name: Name of the executor
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
Executor metadata
|
|
63
|
+
"""
|
|
64
|
+
try:
|
|
65
|
+
adapter = ExecutorRegistry.get(executor_name)
|
|
66
|
+
metadata = adapter.get_metadata()
|
|
67
|
+
is_available = await adapter.is_available()
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
"name": metadata.name,
|
|
71
|
+
"display_name": metadata.display_name,
|
|
72
|
+
"version": metadata.version,
|
|
73
|
+
"capabilities": [cap.value for cap in metadata.capabilities],
|
|
74
|
+
"config_schema": metadata.config_schema,
|
|
75
|
+
"documentation_url": metadata.documentation_url,
|
|
76
|
+
"author": metadata.author,
|
|
77
|
+
"license": metadata.license,
|
|
78
|
+
"available": is_available,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
except ValueError as e:
|
|
82
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise HTTPException(status_code=500, detail=f"Failed to get metadata: {str(e)}")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@router.get("/{executor_name}/models", response_model=list[dict[str, str]])
|
|
88
|
+
async def list_executor_models(executor_name: str):
|
|
89
|
+
"""List available models for a given executor type.
|
|
90
|
+
|
|
91
|
+
Returns a list of model options that can be used with this executor.
|
|
92
|
+
"""
|
|
93
|
+
# Model options per executor type
|
|
94
|
+
models_by_executor: dict[str, list[dict[str, str]]] = {
|
|
95
|
+
"claude": [
|
|
96
|
+
{
|
|
97
|
+
"id": "auto",
|
|
98
|
+
"name": "Auto (recommended)",
|
|
99
|
+
"description": "Automatically select the best model",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"id": "claude-sonnet-4-20250514",
|
|
103
|
+
"name": "Claude Sonnet 4",
|
|
104
|
+
"description": "Fast and capable",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"id": "claude-opus-4-20250514",
|
|
108
|
+
"name": "Claude Opus 4",
|
|
109
|
+
"description": "Most capable model",
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
"cursor-agent": [
|
|
113
|
+
{
|
|
114
|
+
"id": "auto",
|
|
115
|
+
"name": "Auto (recommended)",
|
|
116
|
+
"description": "Automatically select the best model",
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
"cursor": [
|
|
120
|
+
{
|
|
121
|
+
"id": "auto",
|
|
122
|
+
"name": "Auto (recommended)",
|
|
123
|
+
"description": "Uses Cursor IDE model selection",
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
models = models_by_executor.get(executor_name)
|
|
129
|
+
if models is None:
|
|
130
|
+
raise HTTPException(
|
|
131
|
+
status_code=404,
|
|
132
|
+
detail=f"Unknown executor: {executor_name}",
|
|
133
|
+
)
|
|
134
|
+
return models
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@router.get("/{executor_name}/available")
|
|
138
|
+
async def check_executor_available(executor_name: str):
|
|
139
|
+
"""Check if a specific executor is available (installed).
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
executor_name: Name of the executor
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Dict with availability status
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
adapter = ExecutorRegistry.get(executor_name)
|
|
149
|
+
is_available = await adapter.is_available()
|
|
150
|
+
|
|
151
|
+
return {"name": executor_name, "available": is_available}
|
|
152
|
+
|
|
153
|
+
except ValueError as e:
|
|
154
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
155
|
+
except Exception as e:
|
|
156
|
+
raise HTTPException(
|
|
157
|
+
status_code=500, detail=f"Failed to check availability: {str(e)}"
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@router.get("/{executor_name}/setup")
|
|
162
|
+
async def get_executor_setup(executor_name: str):
|
|
163
|
+
"""Get setup instructions and availability diagnostics for an executor.
|
|
164
|
+
|
|
165
|
+
Returns detailed information about whether the executor is installed,
|
|
166
|
+
what issues exist, and how to set it up.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
executor_name: Name of the executor
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
Dict with availability diagnostics and setup instructions
|
|
173
|
+
"""
|
|
174
|
+
try:
|
|
175
|
+
adapter = ExecutorRegistry.get(executor_name)
|
|
176
|
+
diagnostics = await adapter.check_availability()
|
|
177
|
+
metadata = adapter.get_metadata()
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
"name": metadata.name,
|
|
181
|
+
"display_name": metadata.display_name,
|
|
182
|
+
**diagnostics,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
except ValueError as e:
|
|
186
|
+
raise HTTPException(status_code=404, detail=str(e))
|
|
187
|
+
except Exception as e:
|
|
188
|
+
raise HTTPException(status_code=500, detail=f"Failed to check setup: {str(e)}")
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
async def _resolve_board_for_executors(db: AsyncSession, board_id: str | None) -> Board:
|
|
192
|
+
"""Resolve a board by ID, or fall back to the first board."""
|
|
193
|
+
if board_id:
|
|
194
|
+
result = await db.execute(select(Board).where(Board.id == board_id))
|
|
195
|
+
board = result.scalar_one_or_none()
|
|
196
|
+
if not board:
|
|
197
|
+
raise HTTPException(status_code=404, detail=f"Board not found: {board_id}")
|
|
198
|
+
return board
|
|
199
|
+
|
|
200
|
+
result = await db.execute(select(Board).limit(1))
|
|
201
|
+
board = result.scalar_one_or_none()
|
|
202
|
+
if not board:
|
|
203
|
+
raise HTTPException(
|
|
204
|
+
status_code=400,
|
|
205
|
+
detail="No boards exist. Create a board first.",
|
|
206
|
+
)
|
|
207
|
+
return board
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
@router.get("/profiles", response_model=list[dict[str, Any]])
|
|
211
|
+
async def list_executor_profiles(
|
|
212
|
+
board_id: str | None = Query(
|
|
213
|
+
None, description="Board ID (uses first board if omitted)"
|
|
214
|
+
),
|
|
215
|
+
db: AsyncSession = Depends(get_db),
|
|
216
|
+
):
|
|
217
|
+
"""List all configured executor profiles from board config (DB).
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
List of executor profile configurations
|
|
221
|
+
"""
|
|
222
|
+
board = await _resolve_board_for_executors(db, board_id)
|
|
223
|
+
config = DraftConfig.from_board_config(board.config)
|
|
224
|
+
profiles = config.executor_profiles
|
|
225
|
+
|
|
226
|
+
return [
|
|
227
|
+
{
|
|
228
|
+
"name": profile.name,
|
|
229
|
+
"executor_type": profile.executor_type,
|
|
230
|
+
"timeout": profile.timeout,
|
|
231
|
+
"extra_flags": profile.extra_flags,
|
|
232
|
+
"model": profile.model,
|
|
233
|
+
"env": profile.env,
|
|
234
|
+
}
|
|
235
|
+
for profile in profiles.values()
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@router.get("/profiles/{profile_name}", response_model=dict[str, Any])
|
|
240
|
+
async def get_executor_profile(
|
|
241
|
+
profile_name: str,
|
|
242
|
+
board_id: str | None = Query(
|
|
243
|
+
None, description="Board ID (uses first board if omitted)"
|
|
244
|
+
),
|
|
245
|
+
db: AsyncSession = Depends(get_db),
|
|
246
|
+
):
|
|
247
|
+
"""Get a specific executor profile by name.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
profile_name: Name of the profile
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Executor profile configuration
|
|
254
|
+
"""
|
|
255
|
+
board = await _resolve_board_for_executors(db, board_id)
|
|
256
|
+
config = DraftConfig.from_board_config(board.config)
|
|
257
|
+
profile = config.executor_profiles.get(profile_name)
|
|
258
|
+
|
|
259
|
+
if not profile:
|
|
260
|
+
raise HTTPException(
|
|
261
|
+
status_code=404,
|
|
262
|
+
detail=f"Executor profile '{profile_name}' not found",
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
"name": profile.name,
|
|
267
|
+
"executor_type": profile.executor_type,
|
|
268
|
+
"timeout": profile.timeout,
|
|
269
|
+
"extra_flags": profile.extra_flags,
|
|
270
|
+
"model": profile.model,
|
|
271
|
+
"env": profile.env,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@router.put("/profiles", response_model=list[dict[str, Any]])
|
|
276
|
+
async def save_executor_profiles(
|
|
277
|
+
profiles: list[dict[str, Any]],
|
|
278
|
+
board_id: str | None = Query(
|
|
279
|
+
None, description="Board ID (uses first board if omitted)"
|
|
280
|
+
),
|
|
281
|
+
db: AsyncSession = Depends(get_db),
|
|
282
|
+
):
|
|
283
|
+
"""Save executor profiles to board config (DB).
|
|
284
|
+
|
|
285
|
+
Replaces all profiles with the provided list.
|
|
286
|
+
"""
|
|
287
|
+
board = await _resolve_board_for_executors(db, board_id)
|
|
288
|
+
|
|
289
|
+
# Build profiles dict for storage
|
|
290
|
+
profiles_dict: dict[str, Any] = {}
|
|
291
|
+
for p in profiles:
|
|
292
|
+
name = p.get("name", "").strip()
|
|
293
|
+
if not name:
|
|
294
|
+
continue
|
|
295
|
+
entry: dict[str, Any] = {}
|
|
296
|
+
if p.get("executor_type"):
|
|
297
|
+
entry["executor_type"] = p["executor_type"]
|
|
298
|
+
if p.get("timeout"):
|
|
299
|
+
entry["timeout"] = int(p["timeout"])
|
|
300
|
+
if p.get("extra_flags"):
|
|
301
|
+
entry["extra_flags"] = p["extra_flags"]
|
|
302
|
+
if p.get("model"):
|
|
303
|
+
entry["model"] = p["model"]
|
|
304
|
+
if p.get("env"):
|
|
305
|
+
entry["env"] = p["env"]
|
|
306
|
+
profiles_dict[name] = entry
|
|
307
|
+
|
|
308
|
+
existing = board.config or {}
|
|
309
|
+
board.config = deep_merge_dicts(existing, {"executor_profiles": profiles_dict})
|
|
310
|
+
await db.commit()
|
|
311
|
+
await db.refresh(board)
|
|
312
|
+
|
|
313
|
+
config = DraftConfig.from_board_config(board.config)
|
|
314
|
+
return [
|
|
315
|
+
{
|
|
316
|
+
"name": prof.name,
|
|
317
|
+
"executor_type": prof.executor_type,
|
|
318
|
+
"timeout": prof.timeout,
|
|
319
|
+
"extra_flags": prof.extra_flags,
|
|
320
|
+
"model": prof.model,
|
|
321
|
+
"env": prof.env,
|
|
322
|
+
}
|
|
323
|
+
for prof in config.executor_profiles.values()
|
|
324
|
+
]
|