Task manager zorientowany na sieci agentowe
  • TypeScript 45.6%
  • Go 40.3%
  • HTML 11.2%
  • CSS 2.4%
  • JavaScript 0.5%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Aleksander Cynarski 0988462b0a
All checks were successful
CI / Testy (task ci) (push) Successful in 40m28s
CI / Obraz api (push) Successful in 1m15s
CI / Obraz web (push) Successful in 51s
test(web): give jsdom ranges a size so a late editor scroll cannot fail a run
Tiptap's focus() scrolls the selection into view a frame later. ProseMirror
then measures a text range, and jsdom has no Range.getClientRects. On the
Forgejo runner that frame landed inside the next test and failed the whole
panel suite with an unhandled TypeError; on a fast machine it never did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:32:29 +02:00
.ai fix: let agents reach /mcp through the web container 2026-10-13 17:11:05 +02:00
.forgejo/workflows ci: pin checkout at its full address, say where the release gate really is 2026-10-13 18:51:21 +02:00
api review: keep an issue whose spec waits for a person out of the queue 2026-10-13 14:46:04 +02:00
cmd/straylight review: serve /mcp alone and log the route, not the path (L-3) 2026-10-13 15:16:53 +02:00
deploy fix: let agents reach /mcp through the web container 2026-10-13 17:11:05 +02:00
design docs: add Straylight avatar assets 2026-10-13 06:36:53 +02:00
internal review: serve /mcp alone and log the route, not the path (L-3) 2026-10-13 15:16:53 +02:00
web test(web): give jsdom ranges a size so a late editor scroll cannot fail a run 2026-10-14 19:32:29 +02:00
.dockerignore T7: serve the panel from its own nginx container 2026-10-13 00:52:02 +02:00
.env.dev.example T6: add web, dev and ci tasks; build and verify include the panel 2026-10-13 00:43:40 +02:00
.env.example T19: serve the panel and the API from one root mux 2026-10-13 01:00:11 +02:00
.gitignore T6: add web, dev and ci tasks; build and verify include the panel 2026-10-13 00:43:40 +02:00
AGENTS.md refactor: rename para-board to Straylight 2026-10-12 23:24:36 +02:00
docker-compose.yml T19: serve the panel and the API from one root mux 2026-10-13 01:00:11 +02:00
Dockerfile build: add Dockerfile and docker-compose 2026-10-12 23:49:06 +02:00
Dockerfile.web T7: serve the panel from its own nginx container 2026-10-13 00:52:02 +02:00
go.mod T21: MCP adapter with bearer authentication, limits and the thirteen tools 2026-10-13 13:24:39 +02:00
go.sum T21: MCP adapter with bearer authentication, limits and the thirteen tools 2026-10-13 13:24:39 +02:00
README.md ci: pin checkout at its full address, say where the release gate really is 2026-10-13 18:51:21 +02:00
Taskfile.yml review: count blocked issues from the asked teams, not from every relation (T50 #1), perf task in ci (#8), CASE in sub-issue sums (#9) 2026-10-13 05:50:44 +02:00

Straylight

Self-hosted tracker zadań w stylu Linear dla agentów kodujących. Ludzie planują i zatwierdzają pracę w Web UI, agenci pobierają zadania przez MCP, a zadanie może nieść pakiet spec zgodny z pi-sdd-kit.

Projekt jest w budowie: gotowy jest rdzeń (etap 1) — REST API dla użytkowników, zespołów, projektów, zadań, relacji, komentarzy i aktywności, z logowaniem lokalnym i OIDC — oraz agenci, specyfikacje i serwer MCP (etap 2). Web UI i cykle rozwija się dalej; plan opisuje .ai/sdd/PLAN.md.

Uruchomienie

task verify            # format, vet, testy, build
export STRAYLIGHT_ADMIN_EMAIL=admin@example.com
export STRAYLIGHT_ADMIN_PASSWORD='co-najmniej-12-znakow'
./straylight migrate   # tworzy lub aktualizuje schemat
./straylight serve     # http://localhost:8080/healthz, API pod /api

Kontrakt API: api/openapi.yaml.

Zmienna Domyślnie Znaczenie
STRAYLIGHT_ADDR :8080 adres nasłuchu
STRAYLIGHT_DB straylight.db plik bazy SQLite
STRAYLIGHT_BASE_URL http://localhost:8080 publiczny origin (bez ścieżki); wymagany przy OIDC
STRAYLIGHT_ADMIN_EMAIL — e-mail konta startowego admina
STRAYLIGHT_ADMIN_PASSWORD — hasło konta startowego, min. 12 znaków; tworzy konto tylko, gdy go nie ma
STRAYLIGHT_ADMIN_PASSWORD_FILE — hasło z pliku zamiast ze zmiennej
STRAYLIGHT_ADMIN_NAME Admin nazwa konta startowego
STRAYLIGHT_LOCAL_LOGIN true logowanie hasłem włączone
STRAYLIGHT_SESSION_TTL 168h czas bezczynności, po którym sesja wygasa
STRAYLIGHT_OIDC_ISSUER, _CLIENT_ID, _CLIENT_SECRET — logowanie OIDC (np. Pocket ID); komplet trzech je włącza. Redirect URI: <BASE_URL>/api/auth/callback
STRAYLIGHT_TRUSTED_PROXIES — sieci CIDR zaufanych reverse proxy, rozdzielone przecinkami
STRAYLIGHT_AUTO_MIGRATE false migracja schematu przy starcie serve
STRAYLIGHT_LEASE_TTL 15m jak długo agent trzyma zadanie bez heartbeat (od 1m do 24h)
STRAYLIGHT_HEARTBEAT_INTERVAL 5m najkrótszy odstęp między przedłużeniami lease (od 10s); nie może przekraczać połowy STRAYLIGHT_LEASE_TTL
STRAYLIGHT_MCP_RATE 120 żądań na minutę na agenta (od 10 do 6000)

Za reverse proxy ustaw STRAYLIGHT_TRUSTED_PROXIES. Bez tego limiter prób logowania widzi wszystkich pod adresem proxy i pięć błędnych prób na minutę blokuje logowanie na dane konto wszystkim.

Agenci i MCP

Agent (Claude Code, pi) pobiera zadania z serwera MCP pod <serwer>/mcp (Streamable HTTP, bez sesji) i uwierzytelnia się własnym tokenem. Kontrakt narzędzi: api/mcp-tools.md.

  1. Utworzenie agenta. Administrator wchodzi w panelu w Ustawienia → Agenci → Nowy agent, podaje nazwę, rodzaj (claude albo pi), zdolności (etykiety zadań, które agent może brać) i zespoły. Agent widzi i zmienia tylko zadania swoich zespołów.
  2. Token. Jawny token (slg_…) pokazuje się tylko raz, w oknie po utworzeniu; potem panel zna tylko prefiks. Trzymaj go w zmiennej środowiskowej STRAYLIGHT_TOKEN, a nie w plikach konfiguracji, repozytorium ani historii powłoki. Wymiana tokenu i dezaktywacja agenta (też w panelu) działają od razu: stary token dostaje 401, a lease agenta są zwalniane.
  3. Podłączenie. Polecenia dla Claude Code i pi (zmienne STRAYLIGHT_URL i STRAYLIGHT_TOKEN, skill straylight-agent, sprawdzenie połączenia) opisuje deploy/agents/README.md; pi: deploy/agents/pi/README.md. Okno tokenu w panelu podaje te same polecenia z adresem tej instancji.
  4. Pobieranie zadań. next_task wybiera i przydziela zadanie w stanie todo (najpierw przypisane do agenta, potem wg priorytetu i wieku); agent trzyma je przez lease i przedłuża go przez heartbeat. Zadanie ze specyfikacją jest dostępne dopiero po zatwierdzeniu bramki tasks:approved przez człowieka; bramki (requirements, design, tasks) zatwierdza wyłącznie człowiek w panelu.
  5. Limity. STRAYLIGHT_LEASE_TTL i STRAYLIGHT_HEARTBEAT_INTERVAL ustawiają czas lease i najkrótszy odstęp między jego przedłużeniami; interwał musi być co najmniej 10s i nie większy niż połowa TTL, inaczej serwer nie wystartuje. STRAYLIGHT_MCP_RATE ogranicza liczbę żądań na agenta na minutę (po przekroczeniu 429 z Retry-After). Agent może utworzyć najwyżej 100 zadań na godzinę, żądanie MCP ma najwyżej 1 MiB, a po 20 błędnych tokenach z jednego adresu kolejne dostają 429. Limity liczone są w pamięci jednego procesu.

Konta lokalne i zespoły

  • Konta lokalne. Pierwszego administratora tworzą STRAYLIGHT_ADMIN_EMAIL i STRAYLIGHT_ADMIN_PASSWORD (albo …_FILE); logowanie hasłem wyłącza STRAYLIGHT_LOCAL_LOGIN=false. Administrator tworzy kolejne konta w panelu: Ustawienia → Użytkownicy → Nowy użytkownik (e-mail, nazwa, rola administrator lub członek, hasło startowe), zmienia role, dezaktywuje konta (dezaktywowane konto traci sesje) i ustawia nowe hasło. Hasło ma od 12 do 128 znaków; jego zmiana kończy pozostałe sesje. Własne hasło zmienia się z menu użytkownika (Zmień hasło).
  • Zespoły. Ustawienia → Zespoły: administrator tworzy zespół (klucz 2–5 liter A–Z, prefiks numerów zadań, np. RZL-12, niezmienny; nazwa), dodaje i usuwa członków, zmienia nazwę i archiwizuje zespół. Zarchiwizowany zespół jest tylko do odczytu, a jego zadania znikają z kolejki agentów. Członek widzi tylko zespoły, do których należy.

Uruchomienie w kontenerze

cp .env.example .env     # ustaw STRAYLIGHT_ADMIN_PASSWORD (min. 12 znaków)
task compose:up          # buduje oba obrazy i czeka, aż usługi będą zdrowe
task compose:logs
task compose:down        # dane zostają w wolumenie straylight-data

docker-compose.yml uruchamia dwie usługi:

Usługa Obraz Rola
web Dockerfile.web (nginx bez uprawnień roota) jedyne wejście: panel pod http://localhost:8080/, a /api/ i /healthz przekazuje do api
api Dockerfile (binarium Go) REST API, migracja schematu przy starcie, baza SQLite w wolumenie; bez opublikowanego portu

Panel i API są pod jednym originem, więc STRAYLIGHT_BASE_URL wskazuje adres usługi web. Konfiguracja nginx leży w deploy/nginx/. Ustawia te same nagłówki cache i bezpieczeństwa co serwer wbudowany w binarium, więc zmieniaj je razem z internal/adapter/spa.

Z Podmanem: COMPOSE="podman compose" task compose:up.

Bez kontenerów nadal działa jedno binarium: task build osadza panel i ./straylight serve serwuje go pod /. Obraz api panelu nie zawiera; w kontenerach wejściem jest web.

Trzy rzeczy przed wystawieniem poza własną maszynę:

  • Port jest publikowany tylko na 127.0.0.1, bo ruch nie jest szyfrowany. Do sieci wystawiaj go przez reverse proxy z TLS i ustaw STRAYLIGHT_BASE_URL na publiczny adres https://.
  • Kontenery dostają stałą podsieć STRAYLIGHT_SUBNET (domyślnie 172.30.42.0/24), a api domyślnie ufa jej jako STRAYLIGHT_TRUSTED_PROXIES. Dzięki temu limiter prób logowania widzi adres, z którego klient połączył się z web, a nie adres kontenera web. Nagłówek X-Forwarded-For od klienta jest zastępowany, więc nie da się nim ominąć limitu.
  • Za dodatkowym reverse proxy kontener web widzi adres tego proxy, nie przeglądarki. Limiter traktuje wtedy wszystkich jak jednego klienta i pięć błędnych prób logowania na minutę blokuje dane konto wszystkim. To samo dotyczy środowisk, w których ruch do kontenera przechodzi przez NAT (Docker Desktop, Podman na macOS).

CI i obrazy

Pipeline Forgejo Actions jest w .forgejo/workflows/ci.yml.

Zdarzenie Co się dzieje
push do dowolnej gałęzi task ci (testy Go, panel, budżety wydajności, e2e), potem budowa obu obrazów bez publikacji
push do main to samo oraz publikacja :main i :sha-<7 znaków>
tag vX.Y.Z to samo oraz publikacja :X.Y.Z i :latest

Obrazy trafiają do rejestru kontenerów tej instancji Forgejo:

git.eiac.dev/tools/straylight-api
git.eiac.dev/tools/straylight-web

Jednorazowa konfiguracja:

  1. Runner z etykietą docker (zwykły executor kontenerowy, bez dostępu do Dockera). Przy innej etykiecie zmień runs-on w obu zadaniach. Obrazy buduje Kaniko, więc runner nie potrzebuje uprawnień.
  2. Sekrety repozytorium albo organizacji (Ustawienia ▸ Actions ▸ Sekrety): REGISTRY_USER (nazwa użytkownika) i REGISTRY_TOKEN (token dostępu z zakresem write:package). Automatyczny token zadania nie ma dostępu do pakietów. Bez sekretów publikacja z main i z tagów kończy się błędem; gałęzie ich nie potrzebują.

Tag wydania musi mieć postać vX.Y.Z i wskazywać commit należący do main; inaczej pipeline kończy się błędem przed budową obrazów. Sekrety rejestru dostaje tylko krok publikacji z main i z tagów. Kaniko pobiera kod bez tokenu, więc przy prywatnym repozytorium krok budowy trzeba uzupełnić o GIT_USERNAME i GIT_PASSWORD. Go i Task są pobierane ze sprawdzeniem sumy sha256 (sumy w env workflow zmienia się razem z wersją), a akcje są przypięte do commitów.

Kontrola tagu chroni przed pomyłką, nie przed osobą z prawem zapisu: workflow jest brany z wypychanego commita, więc ktoś, kto może wypchnąć gałąź albo tag, może go też zmienić. Właściwą granicą są ustawienia repozytorium: chroniona gałąź main i chronione tagi v* (Ustawienia ▸ Gałęzie i ▸ Tagi), a token rejestru powinien mieć wyłącznie zakres write:package.

Wersję obrazu Playwright w zadaniu testów trzeba podnosić razem z @playwright/test w web/package-lock.json.

Układ

Ścieżka Zawartość
cmd/straylight composition root
internal/domain encje i reguły, tylko biblioteka standardowa
internal/usecase interaktory i porty (ports.go)
internal/adapter rest, mcp, sse, sqlite, oidc
api/ kontrakty: openapi.yaml, mcp-tools.md
web/ React SPA
.ai/ steering i specyfikacje SDD