Closes the "GUI 미존재" gap from the user's first-session requirements (REPL + workflow + GUI). v0.2 PR #1's Postgres migration made a second concurrent writer safe; v0.2 PR #2a/#2b wired durable resume; this commit ships the HTTP + browser surface that uses them. No auth, no multi-tenant, single uvicorn worker — per DR-3 boundaries. v0.3+ will add auth, multi-worker fanout, LISTEN/NOTIFY SSE upgrade. Backend - `src/my_deepagent/api/`: - `app.py` create_app() factory. lifespan stores db/config/personas/ workflows on app.state. CORS allow_origin_regex http://localhost(:port)?. /static mount + /, /{page}.html for the HTML frontend. - `models.py` — pydantic v2 DTOs (extra="forbid") for every route. Auto OpenAPI/Swagger via FastAPI's response_model. - `deps.py` — get_db / get_config / get_personas / get_workflows. - `runner.py` — start_new_run / start_resume. Pre-allocates run_id via new `WorkflowEngine.run(pre_allocated_run_id=...)` so the route returns the id immediately while the engine runs in asyncio.create_task. - `sse.py` — 0.5 s poll over run_events.seq. Emits ServerSentEvent rows; sends `event: done` and HTTP-200-closes when run hits terminal. - `routes/{runs,personas,workflows,budget}.py`: GET /api/runs (list, ?limit + ?state) GET /api/runs/{id} (detail + phases + artifacts + events) POST /api/runs (start; mock-able via runner.start_new_run) POST /api/runs/{id}/resume POST /api/runs/{id}/abort GET /api/runs/{id}/events (SSE; Last-Event-ID header + ?last_event_id) GET /api/personas GET /api/workflows GET /api/budget CLI - `cli/serve.py` mydeepagent serve [--host 127.0.0.1] [--port 8000]. Loud stderr warning if --host is not loopback (no auth = footgun). uvicorn.run(factory=True, workers=1). - `cli/main.py` serve command registered. Static frontend (vanilla HTML/JS/CSS, no build system) - index.html — runs list + budget summary - new.html — start-run form (workflow select, repo path, requirements, per-role persona override) - run.html — run detail + live SSE event log + Resume/Abort buttons - app.js — fetch + EventSource. XSS policy HARDCODED at file top: textContent only, innerHTML/insertAdjacentHTML/outerHTML forbidden. - style.css — dark theme, single file. Engine - WorkflowEngine.run(... pre_allocated_run_id: UUID|None = None). None → uuid4() (existing behavior). Set → use that UUID. Backward compatible. Tests - tests/integration/test_api_read.py (5): list empty, get 404, personas seed count (12), workflows seed (>=3), budget empty. - tests/integration/test_api_write.py (5): missing template 400, extra field 422, resume 404, abort 404, mock-runner happy path. - tests/integration/test_api_sse.py (1): seed terminal run + 3 events, drain stream, assert types present + stream closes within 3 s. - tests/integration/test_api_static.py (5): index/new/run HTML 200, app.js content-type + XSS-policy substring assertion, style.css content-type. - All fixtures use httpx ASGITransport + app.router.lifespan_context (httpx does NOT auto-trigger FastAPI lifespan) + sqlite tmp_path. Gates - ruff check + ruff format --check + mypy --strict: PASS (120 source files) - pytest non-E2E: 603 PASS (12.15 s) — +16 from new API tests - pytest E2E real OpenRouter on Postgres: PASS 60.44 s (baseline 71–122 s range; well within DR-3 acceptance threshold ≤+20%) Manual browser verification deferred to a follow-up (docker compose up, mydeepagent serve, open http://localhost:8000). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
1.8 KiB
TOML
70 lines
1.8 KiB
TOML
[project]
|
|
name = "my-deepagent"
|
|
version = "0.1.0"
|
|
description = "Add your description here"
|
|
requires-python = ">=3.12"
|
|
dependencies = [
|
|
"asyncpg>=0.30",
|
|
"psycopg[binary]>=3.2",
|
|
"alembic>=1.14",
|
|
"greenlet>=3.0",
|
|
"sqlalchemy[asyncio]>=2.0",
|
|
"fastapi>=0.115",
|
|
"uvicorn[standard]>=0.30",
|
|
"sse-starlette>=2.1",
|
|
"httpx>=0.28",
|
|
"jsonschema>=4.23",
|
|
"keyring>=25.7",
|
|
"langchain>=0.3.0,<2.0.0",
|
|
"langchain-core>=0.3.0,<2.0.0",
|
|
"langchain-openai>=0.3.0,<2.0.0",
|
|
"langgraph>=0.2.0",
|
|
"langgraph-checkpoint-postgres>=2.0.0",
|
|
"openai>=1.0.0",
|
|
"platformdirs>=4.9",
|
|
"prompt-toolkit>=3.0",
|
|
"pydantic>=2.9",
|
|
"pydantic-settings>=2.6",
|
|
"pyyaml>=6.0",
|
|
"rich>=13.9",
|
|
"structlog>=24.4",
|
|
"typer>=0.14",
|
|
"zstandard>=0.23",
|
|
"deepagents>=0.6.1,<0.7.0",
|
|
]
|
|
|
|
[project.scripts]
|
|
mydeepagent = "my_deepagent.cli.main:app"
|
|
|
|
[build-system]
|
|
requires = ["uv_build>=0.9.28,<0.10.0"]
|
|
build-backend = "uv_build"
|
|
|
|
[tool.pytest.ini_options]
|
|
asyncio_mode = "auto"
|
|
testpaths = ["tests"]
|
|
addopts = "-v --strict-markers"
|
|
markers = [
|
|
"integration: marks tests as integration tests that make real external API calls (deselect with '-m not integration')",
|
|
]
|
|
|
|
[dependency-groups]
|
|
dev = [
|
|
# aiosqlite is a TEST-ONLY dependency: production runs on Postgres
|
|
# (asyncpg, see [project.dependencies]) but the bulk of the test suite uses
|
|
# sqlite+aiosqlite tmp_path URLs for speed + isolation simplicity. Live
|
|
# Postgres validation happens via the E2E suite (real OpenRouter +
|
|
# docker-compose Postgres).
|
|
"aiosqlite>=0.20",
|
|
"mypy>=1.13",
|
|
"pre-commit>=4.0",
|
|
"pytest>=8.3",
|
|
"pytest-asyncio>=0.24",
|
|
"pytest-httpx>=0.34",
|
|
"pytest-timeout>=2.4.0",
|
|
"respx>=0.21",
|
|
"ruff>=0.8",
|
|
"types-jsonschema>=4.26.0.20260508",
|
|
"types-pyyaml>=6.0.12.20260510",
|
|
]
|