helmd v2: Go-server som styr agent i tmux — REST+SSE, frågedetektor, fildelning, ntfy, CI-release
Some checks failed
build-and-push / build (push) Successful in 42s
release-client / build-release (push) Has been cancelled
helmd-release / build-release (push) Successful in 1m6s

- tmux som sanningskälla (agentoberoende, överlever omstart via adoption)
- API: sessions, prompt, question/answer, keys, mode, config, shares, notify, events
- frågedetektor testad mot riktiga agy 1.1.9-dumpar + mock
- integrationstest: 26 tester i isolerad debian-container (scripts/run-integration.sh)
- e2e-verifierad mot riktig agy (trust-fråga -> svar -> prompt)
- .gitea/workflows/helmd-release.yaml -> rullande helmd-latest (x64+arm64)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-05 20:54:54 +02:00
parent 3e7152a884
commit f9c79ac84e
17 changed files with 2075 additions and 0 deletions

139
helmd/README.md Normal file
View File

@@ -0,0 +1,139 @@
# helmd — agent-helm v2-servern
En **enskild statisk Go-binär** som äger kommunikationen med en
tmux-session där en CLI-agent (agy, claude, …) kör, och exponerar ett
**REST-API + SSE** som webb- och mobilklienterna pratar med.
Designen bygger på insikten från Gemini-OAuth-döden: att integrera mot
en specifik agents hook-mekanism är skört. `helmd` behandlar i stället
**tmux som sanningskälla** — den läser skärmen, känner igen frågor och
skickar tangenter. Fungerar därför med vilken TUI-agent som helst, och
överlever att helmd själv startas om (sessioner adopteras tillbaka).
```
webb-PWA / Android ──(LAN eller SSH-tunnel)──► helmd :8788 ──tmux──► agy
└──► ntfy.brasse-pc.eu (pushar till mobilen)
```
## Säkerhetsmodell
- **Ingen publik exponering.** Servern binder `127.0.0.1:8788` som
default. På det privata nätet kan man binda `0.0.0.0:8788`
(`helmd serve --listen 0.0.0.0:8788`). Utifrån: SSH-tunnel —
`ssh -L 8788:127.0.0.1:8788 <host>` — eller Android-klientens
inbyggda tunnel (steg 3 i planen).
- **Bearer-token** på alla endpoints. Genereras vid första start,
lagras `0600` i `~/.config/helmd/config.json`, visas med
`helmd token`. Skickas som `Authorization: Bearer <t>`,
`X-Api-Token: <t>` eller `?token=<t>` (för SSE/EventSource och
`<img>`-länkar).
- Delad HTML serveras med `Content-Security-Policy: sandbox` så att
ett delat dokument aldrig kan anropa API:et med klientens token.
## Kommandon
```
helmd serve [--listen host:port] [--config fil] starta servern
helmd share [--note text] [--session namn] <fil> dela en fil till klienterna
helmd token skriv ut API-token
helmd version
```
`helmd share` är **agentens verktyg för att dela filer** (html, md,
png, jpg, …) till klienterna: agenten kör kommandot i sin shell, filen
laddas upp till servern och dyker upp i klientens delningsflik.
Tillåtna filtyper styrs av `share_exts` i konfigen.
## API
Alla svar är JSON om inget annat sägs. Auth krävs överallt.
| Metod & väg | Gör |
|---|---|
| `GET /api/health` | `{ok, version, sessions}` |
| `GET /api/sessions` | lista sessioner (namn, alive, seq, pending question) |
| `POST /api/sessions` | `{name, cmd?, cwd?}` startar agent i ny tmux-session `helm-<name>`; `{name, adopt:true, tmux?}` adopterar befintlig |
| `GET /api/sessions/{n}` | en sessions tillstånd |
| `DELETE /api/sessions/{n}` | döda sessionen |
| `GET /api/sessions/{n}/screen` | pane-text; `?ansi=1` behåller färger, `?history=N` tar med N rader scrollback |
| `POST /api/sessions/{n}/prompt` | `{text, submit?:bool}` klistrar in text (radbrytningar ok) och trycker Enter |
| `GET /api/sessions/{n}/question` | `{question}` — detekterad väntande fråga eller `null` |
| `POST /api/sessions/{n}/answer` | `{option:N}` väljer alternativ N (1-baserat). Numrerade listor väljs med siffertangent, onumrerade navigeras med pil+Enter |
| `POST /api/sessions/{n}/keys` | `{keys:["Escape","Down","Enter"]}` råa tmux-tangenter |
| `POST /api/sessions/{n}/mode` | `{action:"cycle"}` skickar Shift+Tab (lägesbyte i agy/claude) |
| `GET /api/config` / `PUT /api/config` | läs/ändra agentkommando, args, workdir, poll-intervall, ntfy-inställningar (token ändras aldrig via API) |
| `GET /api/shares` / `POST /api/shares` | lista / ladda upp (multipart, fält `file`, `note`, `session`) |
| `GET /api/shares/{id}` / `…/raw` | metadata / själva filen |
| `DELETE /api/shares/{id}` | ta bort delning |
| `POST /api/notify` | `{topic?, title?, message, priority?}` → ntfy; topic måste finnas i `allowed_topics` (`agent-helm`, `claude`) |
| `GET /api/events` | SSE-ström: `screen` (seq), `question`, `session`, `share` |
### Frågedetektering
Pollern läser panen (default var 500:e ms), och `DetectQuestion`
känner igen agy:s frågeformer: godkännande-listor (`1. Yes … 4. No`),
trust-/val-listor med `>`-markör, och gamla Gemini-CLI:s inramade
pickers. Varje fråga får en stabil hash — servern ntfy:ar **en gång
per ny fråga** till topic `agent-helm` (kan stängas av med
`notify_on_question:false`).
Verifierat mot riktiga agy 1.1.9-sessioner (trust-prompt,
kommandogodkännande) och mot mock i integrationstestet.
## Konfiguration
`~/.config/helmd/config.json` (skapas vid första start; override med
`HELMD_CONFIG` eller `--config`):
```json
{
"listen": "127.0.0.1:8788",
"token": "<genereras>",
"agent_cmd": "agy",
"agent_args": [],
"workdir": "/home/brasse",
"share_dir": "~/.local/share/helmd/shares",
"share_exts": [".html", ".htm", ".md", ".txt", ".json", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".pdf"],
"poll_ms": 500,
"cols": 200, "rows": 50,
"ntfy": {
"server": "https://ntfy.brasse-pc.eu",
"topic": "agent-helm",
"allowed_topics": ["agent-helm", "claude"],
"enabled": true,
"notify_on_question": true
}
}
```
## Bygga & testa
```bash
cd helmd
go test ./... # enhetstester (frågedetektorn m.m.)
CGO_ENABLED=0 go build -o build/helmd .
bash scripts/run-integration.sh # 26 API-tester i isolerad Docker-container
```
Integrationstestet startar helmd i en `debian:stable-slim`-container
med en mock-agent (`testdata/mock-agent.sh`) som härmar agy:s båda
frågetyper, och kör hela API-ytan med curl.
## CI / release
`.gitea/workflows/helmd-release.yaml` kör på push till master som rör
`helmd/`: `go vet` + `go test`, bygger **linux x64 + arm64** statiskt
och publicerar på den rullande releasen **`helmd-latest`**
(`helmd-linux-x64`, `helmd-linux-arm64`, `checksums.txt`).
## Kända begränsningar (v1)
- `mode cycle` skickar Shift+Tab (BTab) — verifierat tangentnamn i
tmux men inte bekräftat att agy byter läge på det; justera via
`/keys` om agy använder annan tangent.
- Ingen WebSocket-terminal ännu — klienterna pollar `screen` +
lyssnar på SSE. Räcker för kort-UI:t; rå terminal-vy kommer med
webbklienten (steg 2).
- En helmd-instans per maskin/användare; flera agenter = flera
sessioner i samma helmd.