- Parse optional http:// scheme prefix on worker specs so a scheme-typed
IP is stored cleanly and its remove button works.
- Delete workers by their stored DB host/port instead of re-parsing the
rendered URL, and surface add/remove errors in the UI.
- Default new builds to git URL instead of zip upload.
- Show worker hostname/IP from agent status in the address column.
- Always include DB host/port in worker API output.
Replace the CROSS_PY_WORKERS env list with a DB-backed worker store so agents
can be added/removed (and their online status verified) from the browser.
- db.py: add workers table + list_workers/add_worker/remove_worker.
- workers.py: configured_workers() reads the DB instead of settings.
- app.py: POST /api/workers (add, probes reachability) and
DELETE /api/workers/<port>/<host>; startup log uses DB worker count.
- UI: Workers card with add form, per-worker live status + OS/CPU, remove.
- settings.py: drop CROSS_PY_WORKERS/_get_json_list; DB is single source.
- README/DESIGN updated to describe UI-managed workers.
The .modal CSS display:flex override the hidden attribute, so the empty job
modal was always visible on load and closeModal() could not hide it - the
'non-dismissable empty error'. Add .modal[hidden] { display:none } so the
hidden attribute wins.
The blanket errorhandler(Exception) also converted normal HTTP 404s (e.g.
/favicon.ico, /apple-touch-icon-*.png requested by the browser) into 500s,
flooding the log. Return HTTPException instances unchanged so only genuine
500s are logged as errors.
The controller ran silently and surfaced errors as opaque/empty bodies,
making it impossible to diagnose failures from either the UI or the server.
- Configure Python logging (timestamps, levels) and log job lifecycle events
(created / dispatched / done / failed) plus startup and dispatch warnings.
- Add a catch-all error handler so unhandled exceptions return a JSON error
with the exception type+message instead of an empty response.
- Remove the frontend submit bug that hand-set multipart Content-Type without
a boundary (and built an opts object it never used); post the FormData
directly so the browser sets the correct content type + boundary.
- Add ctrl/ package: Flask app (waitress), SQLite job store, background
scheduler that probes configured workers, dispatches via /upload and
/checkout_git, fetches artifacts, and serves a single-page UI
- Supporting API: /api/jobs (create/list/detail/cancel), /api/workers,
/api/capabilities, artifact download, and SSE log stream
- Workers keep the existing HTTP API; add /progress/<job_id> endpoint and
per-step build tracking for live status
- Retire agent_manager.py and the cross-py-builder CLI; controller UI is
the primary frontend
- Record design in DESIGN.md; add waitress dependency
Note: /progress is exposed on the worker but the synchronous /upload and
/checkout endpoints block until a build completes, so per-step worker
progress is not yet relayed live in the UI. Live streaming needs an
async-start worker endpoint as a follow-up.