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,267 @@
|
|
|
1
|
+
"""Router for project settings (DB-backed via Board.config)."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from sqlalchemy import select
|
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
|
+
|
|
11
|
+
from app.database import get_db
|
|
12
|
+
from app.models.board import Board
|
|
13
|
+
from app.services.config_service import DraftConfig, deep_merge_dicts
|
|
14
|
+
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
router = APIRouter(prefix="/settings", tags=["settings"])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ExecuteConfigUpdate(BaseModel):
|
|
21
|
+
"""Execute configuration update model."""
|
|
22
|
+
|
|
23
|
+
timeout: int | None = None
|
|
24
|
+
preferred_executor: str | None = None
|
|
25
|
+
executor_model: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SettingsUpdate(BaseModel):
|
|
29
|
+
"""Global settings update model."""
|
|
30
|
+
|
|
31
|
+
execute_config: ExecuteConfigUpdate | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SettingsResponse(BaseModel):
|
|
35
|
+
"""Global settings response model."""
|
|
36
|
+
|
|
37
|
+
execute_config: dict[str, Any]
|
|
38
|
+
board_id: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# --- Planner config models ---
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class PlannerConfigResponse(BaseModel):
|
|
45
|
+
"""Planner configuration response."""
|
|
46
|
+
|
|
47
|
+
model: str
|
|
48
|
+
agent_path: str
|
|
49
|
+
timeout: int
|
|
50
|
+
preferred_executor: str # From execute_config, so frontend knows the CLI type
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PlannerConfigUpdate(BaseModel):
|
|
54
|
+
"""Planner configuration update."""
|
|
55
|
+
|
|
56
|
+
model: str | None = None
|
|
57
|
+
agent_path: str | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class PlannerHealthResponse(BaseModel):
|
|
61
|
+
"""Planner health check response."""
|
|
62
|
+
|
|
63
|
+
status: str # "online" | "offline"
|
|
64
|
+
model: str
|
|
65
|
+
error: str | None = None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def _resolve_board(db: AsyncSession, board_id: str | None) -> Board:
|
|
69
|
+
"""Resolve a board by ID, or fall back to the first board."""
|
|
70
|
+
if board_id:
|
|
71
|
+
result = await db.execute(select(Board).where(Board.id == board_id))
|
|
72
|
+
board = result.scalar_one_or_none()
|
|
73
|
+
if not board:
|
|
74
|
+
raise HTTPException(status_code=404, detail=f"Board not found: {board_id}")
|
|
75
|
+
return board
|
|
76
|
+
|
|
77
|
+
result = await db.execute(select(Board).limit(1))
|
|
78
|
+
board = result.scalar_one_or_none()
|
|
79
|
+
if not board:
|
|
80
|
+
raise HTTPException(
|
|
81
|
+
status_code=400,
|
|
82
|
+
detail="No boards exist. Create a board first.",
|
|
83
|
+
)
|
|
84
|
+
return board
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@router.get("", response_model=SettingsResponse)
|
|
88
|
+
async def get_global_settings(
|
|
89
|
+
board_id: str | None = Query(
|
|
90
|
+
None, description="Board ID (uses first board if omitted)"
|
|
91
|
+
),
|
|
92
|
+
db: AsyncSession = Depends(get_db),
|
|
93
|
+
):
|
|
94
|
+
"""Get execute settings from board config (DB).
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Current execute_config from the board's config.
|
|
98
|
+
"""
|
|
99
|
+
board = await _resolve_board(db, board_id)
|
|
100
|
+
config = DraftConfig.from_board_config(board.config)
|
|
101
|
+
|
|
102
|
+
return SettingsResponse(
|
|
103
|
+
execute_config={
|
|
104
|
+
"timeout": config.execute_config.timeout,
|
|
105
|
+
"preferred_executor": config.execute_config.preferred_executor,
|
|
106
|
+
"executor_model": config.execute_config.executor_model,
|
|
107
|
+
},
|
|
108
|
+
board_id=board.id,
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@router.put("", response_model=SettingsResponse)
|
|
113
|
+
async def update_global_settings(
|
|
114
|
+
data: SettingsUpdate,
|
|
115
|
+
board_id: str | None = Query(
|
|
116
|
+
None, description="Board ID (uses first board if omitted)"
|
|
117
|
+
),
|
|
118
|
+
db: AsyncSession = Depends(get_db),
|
|
119
|
+
):
|
|
120
|
+
"""Update execute settings in board config (DB).
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
data: Settings to update (partial update supported)
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Updated settings
|
|
127
|
+
"""
|
|
128
|
+
board = await _resolve_board(db, board_id)
|
|
129
|
+
|
|
130
|
+
update_dict: dict[str, Any] = {}
|
|
131
|
+
if data.execute_config:
|
|
132
|
+
ec: dict[str, Any] = {}
|
|
133
|
+
if data.execute_config.timeout is not None:
|
|
134
|
+
ec["timeout"] = data.execute_config.timeout
|
|
135
|
+
if data.execute_config.preferred_executor is not None:
|
|
136
|
+
ec["preferred_executor"] = data.execute_config.preferred_executor
|
|
137
|
+
if data.execute_config.executor_model is not None:
|
|
138
|
+
ec["executor_model"] = data.execute_config.executor_model
|
|
139
|
+
if ec:
|
|
140
|
+
update_dict["execute_config"] = ec
|
|
141
|
+
|
|
142
|
+
if update_dict:
|
|
143
|
+
existing = board.config or {}
|
|
144
|
+
board.config = deep_merge_dicts(existing, update_dict)
|
|
145
|
+
await db.commit()
|
|
146
|
+
await db.refresh(board)
|
|
147
|
+
|
|
148
|
+
config = DraftConfig.from_board_config(board.config)
|
|
149
|
+
return SettingsResponse(
|
|
150
|
+
execute_config={
|
|
151
|
+
"timeout": config.execute_config.timeout,
|
|
152
|
+
"preferred_executor": config.execute_config.preferred_executor,
|
|
153
|
+
"executor_model": config.execute_config.executor_model,
|
|
154
|
+
},
|
|
155
|
+
board_id=board.id,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# ==================== Planner Config Endpoints ====================
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@router.get("/planner", response_model=PlannerConfigResponse)
|
|
163
|
+
async def get_planner_config(
|
|
164
|
+
board_id: str | None = Query(
|
|
165
|
+
None, description="Board ID (uses first board if omitted)"
|
|
166
|
+
),
|
|
167
|
+
db: AsyncSession = Depends(get_db),
|
|
168
|
+
):
|
|
169
|
+
"""Get current planner configuration from board config (DB)."""
|
|
170
|
+
board = await _resolve_board(db, board_id)
|
|
171
|
+
config = DraftConfig.from_board_config(board.config)
|
|
172
|
+
planner = config.planner_config
|
|
173
|
+
|
|
174
|
+
return PlannerConfigResponse(
|
|
175
|
+
model=planner.model,
|
|
176
|
+
agent_path=planner.agent_path,
|
|
177
|
+
timeout=planner.timeout,
|
|
178
|
+
preferred_executor=config.execute_config.preferred_executor,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@router.put("/planner", response_model=PlannerConfigResponse)
|
|
183
|
+
async def update_planner_config(
|
|
184
|
+
data: PlannerConfigUpdate,
|
|
185
|
+
board_id: str | None = Query(
|
|
186
|
+
None, description="Board ID (uses first board if omitted)"
|
|
187
|
+
),
|
|
188
|
+
db: AsyncSession = Depends(get_db),
|
|
189
|
+
):
|
|
190
|
+
"""Update planner model and agent_path in board config (DB)."""
|
|
191
|
+
board = await _resolve_board(db, board_id)
|
|
192
|
+
|
|
193
|
+
update_dict: dict[str, Any] = {}
|
|
194
|
+
if data.model is not None:
|
|
195
|
+
update_dict["model"] = data.model
|
|
196
|
+
# When setting cli/<executor>, auto-sync agent_path to the executor name
|
|
197
|
+
if data.model.startswith("cli/") and data.agent_path is None:
|
|
198
|
+
update_dict["agent_path"] = data.model.removeprefix("cli/")
|
|
199
|
+
if data.agent_path is not None:
|
|
200
|
+
update_dict["agent_path"] = data.agent_path
|
|
201
|
+
|
|
202
|
+
if update_dict:
|
|
203
|
+
existing = board.config or {}
|
|
204
|
+
board.config = deep_merge_dicts(existing, {"planner_config": update_dict})
|
|
205
|
+
await db.commit()
|
|
206
|
+
await db.refresh(board)
|
|
207
|
+
|
|
208
|
+
config = DraftConfig.from_board_config(board.config)
|
|
209
|
+
planner = config.planner_config
|
|
210
|
+
|
|
211
|
+
return PlannerConfigResponse(
|
|
212
|
+
model=planner.model,
|
|
213
|
+
agent_path=planner.agent_path,
|
|
214
|
+
timeout=planner.timeout,
|
|
215
|
+
preferred_executor=config.execute_config.preferred_executor,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@router.get("/planner/check", response_model=PlannerHealthResponse)
|
|
220
|
+
async def check_planner_health(
|
|
221
|
+
board_id: str | None = Query(
|
|
222
|
+
None, description="Board ID (uses first board if omitted)"
|
|
223
|
+
),
|
|
224
|
+
db: AsyncSession = Depends(get_db),
|
|
225
|
+
):
|
|
226
|
+
"""Test if the configured planner can work.
|
|
227
|
+
|
|
228
|
+
For CLI models (cli/claude): checks if the CLI binary is available.
|
|
229
|
+
For API models: makes a minimal test call to verify credentials.
|
|
230
|
+
"""
|
|
231
|
+
import shutil
|
|
232
|
+
|
|
233
|
+
board = await _resolve_board(db, board_id)
|
|
234
|
+
config = DraftConfig.from_board_config(board.config)
|
|
235
|
+
planner = config.planner_config
|
|
236
|
+
model = planner.model
|
|
237
|
+
|
|
238
|
+
# CLI mode: check if the agent binary exists
|
|
239
|
+
if model.startswith("cli/"):
|
|
240
|
+
agent_path = planner.get_agent_path()
|
|
241
|
+
found = shutil.which(agent_path)
|
|
242
|
+
if found:
|
|
243
|
+
logger.info(f"Planner CLI health check passed: {agent_path} -> {found}")
|
|
244
|
+
return PlannerHealthResponse(status="online", model=model)
|
|
245
|
+
else:
|
|
246
|
+
return PlannerHealthResponse(
|
|
247
|
+
status="offline",
|
|
248
|
+
model=model,
|
|
249
|
+
error=f"CLI not found: {agent_path}. Install it or add it to PATH.",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
# API mode: make a minimal LLM call
|
|
253
|
+
from app.services.llm_service import LLMService
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
llm = LLMService(planner)
|
|
257
|
+
llm.call_completion(
|
|
258
|
+
messages=[{"role": "user", "content": 'Reply with exactly: {"ok":true}'}],
|
|
259
|
+
max_tokens=20,
|
|
260
|
+
timeout=15,
|
|
261
|
+
json_mode=True,
|
|
262
|
+
)
|
|
263
|
+
logger.info(f"Planner API health check passed: model={model}")
|
|
264
|
+
return PlannerHealthResponse(status="online", model=model)
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.warning(f"Planner health check failed: {e}")
|
|
267
|
+
return PlannerHealthResponse(status="offline", model=model, error=str(e))
|