Prompt 00 — Monorepo + shared protocol (TypeScript ovunque) Crea una PR: “Monorepo bootstrap + shared protocol v1”. Obiettivo: - Impostare un monorepo con TypeScript ovunque e un pacchetto condiviso `shared/protocol` con tipi e helper per il protocollo WebSocket v1. Struttura richiesta: - /server (Node.js + TS) - /windows (Electron + TS) - /shared/protocol (TS, esporta tipi e validator base) - /android NON in TS (verrà dopo), ma lascia spazio /android Requisiti tecnici: - Usa pnpm workspaces (preferibile) oppure yarn workspaces. - In `shared/protocol` definisci: - `ProtocolVersion = 1` - tipi per tutti i messaggi Fase 1: - agent_register, agent_registered, agent_heartbeat - server_connect_request - agent_connect_response - client_login, client_login_result - client_list_devices, client_device_list - client_connect_request - client_connect_status, client_connect_result - error - un `type AnyMessage = ...` union - helper `isMessage(obj): obj is AnyMessage` con validazione minimale (no librerie pesanti; ok zod se vuoi ma tienilo semplice) - helper `makeRequestId()` (uuid v4) e `nowTs()` Output: - Workspace funzionante con `pnpm i` e build TS. - README root con comandi base. Acceptance: - `pnpm -r build` passa. - Import da `shared/protocol` funziona da server e windows (anche se windows non è ancora implementato). - Niente codice Android in questa PR. Prompt 01 — Server skeleton + healthcheck + Docker Crea una PR: “Server signaling TS skeleton + /health + Docker”. Contesto: - Monorepo esiste con `shared/protocol`. Obiettivo: - Creare `server` Node.js TypeScript che espone: - HTTP GET `/health` => 200 “ok” - WebSocket upgrade pronto (endpoints verranno dopo) Requisiti: - Framework HTTP: Fastify o Express (scegli uno e mantienilo semplice). - Logger: pino (consigliato) o console strutturata. - Config `.env` (usa dotenv). - Dockerfile per server. - Script: `pnpm --filter server dev` e `build` e `start`. Acceptance: - Avvio locale: `pnpm --filter server dev` - `curl localhost:/health` => 200 - `docker build` e `docker run` funzionano. - Nessun endpoint WS richiesto ancora (solo scaffolding). Prompt 02 — WebSocket endpoints /agent e /client + parsing robusto Crea una PR: “WS endpoints /agent e /client + parsing robusto”. Obiettivo: - Implementare WebSocket su server con due path: - ws://.../agent - ws://.../client Requisiti: - Usa libreria `ws` (preferita) integrata con il server HTTP scelto. - Per ogni connessione, identifica “kind” (agent/client) in base al path. - Parsing JSON robusto: se arriva JSON invalido o messaggio non conforme, rispondi con: { v:1, type:"error", requestId: , code:"BAD_REQUEST", message:"..." } - Non deve crashare. Acceptance: - Connessione WS a /agent e /client possibile. - Invio messaggio non JSON => ricevo error e connessione resta (o chiude con motivo, ok entrambe purché non crashi). - Logga connect/disconnect con ip e path. Prompt 03 — Auth Fase 1 (client login + agent pairingKey) Crea una PR: “Auth F1: client_login + agent_register pairingKey”. Obiettivo: - Implementare autenticazione minimale: - Client: `client_login` con username/password contro seed in env - Agent: `agent_register` con pairingKey contro seed in env, associato allo stesso userId del seed Env richieste (server): - SEED_USERNAME - SEED_PASSWORD (plaintext per ora, MVP) - SEED_USER_ID (es. "user-1") - SEED_PAIRING_KEY (string) Comportamento: - client_login ok => memorizza sessione in-memory (map ws -> userId). - richieste client senza login => error UNAUTHORIZED. - agent_register con pairingKey errata => error UNAUTHORIZED. Usa i tipi di `shared/protocol`. Acceptance: - client senza login che fa list => UNAUTHORIZED - login corretto => ok:true - agent_register con pairingKey corretto => agent_registered ok:true - agent_register con pairingKey errata => UNAUTHORIZED Prompt 04 — Presence registry + heartbeat + lastSeen Crea una PR: “Presence registry: devices online/offline + heartbeat”. Obiettivo: - Mantenere registry in memoria: devicesById: deviceId -> { deviceId, deviceName, userId, online, lastSeenIso, wsRef? } Regole: - Su agent_register: crea/aggiorna entry, set online=true, lastSeen=now, salva wsRef. - Su agent_heartbeat: aggiorna lastSeen=now (se deviceId noto), altrimenti error BAD_REQUEST. - Su WS disconnect agent: online=false, lastSeen=now, wsRef=null. Extra: - Aggiungi un cleanup interval che marca offline se non riceve heartbeat entro 90s (anche se la socket resta “mezzo morta”). Acceptance: - Simulando heartbeat si aggiorna lastSeen. - Chiudendo connessione agent => online=false. - Se heartbeat manca >90s => online=false. Prompt 05 — client_list_devices + filtro per userId Crea una PR: “client_list_devices + device list filtrata”. Obiettivo: - Implementare: - client_list_devices => server risponde client_device_list Dettagli: - Deve essere richiesto solo da client autenticato. - Ritorna SOLO device associati al userId del client. - Ogni device include: deviceId, deviceName, online, lastSeenIso. Acceptance: - Un client loggato vede la lista. - Un client non loggato riceve UNAUTHORIZED. - Se ci sono più device (simulati) appartenenti a userId diversi, ne vede solo i suoi. Prompt 06 — Connect request routing + session state + timeout expired Crea una PR: “Connect request routing + session state + timeout”. Obiettivo: - Implementare client_connect_request: - verifica auth client - verifica ownership (device.userId == client.userId) - se device offline => error DEVICE_OFFLINE - se device online => crea sessionId e status=pending - inoltra a agent `server_connect_request` con {sessionId, fromUser: username} - al client risponde `client_connect_status` status=pending Session store in memory: - sessionsById: sessionId -> { sessionId, userId, deviceId, status, createdAt, clientWsRef } Timeout: - dopo 60s se ancora pending => status=expired e invia al client `client_connect_result` status=expired Acceptance: - Richiesta verso device offline => DEVICE_OFFLINE - Richiesta verso device online => agent riceve server_connect_request e client riceve pending - Se agent non risponde entro 60s => client riceve expired Prompt 07 — Agent accept/deny -> client result Crea una PR: “agent_connect_response accept/deny -> client_connect_result”. Obiettivo: - Implementare agent_connect_response: - valida sessionId esistente - valida che session.deviceId == msg.deviceId - valida che session.status == pending - se decision accept => status=accepted - se decision deny => status=denied - invia a client `client_connect_result` con status accepted/denied Edge cases: - sessionId inesistente => error NOT_FOUND - session non pending => error CONFLICT - deviceId mismatch => error FORBIDDEN Acceptance: - Agent accept => client riceve accepted - Agent deny => client riceve denied - Risposte duplicate => CONFLICT Prompt 08 — docker-compose dev (server + nginx reverse proxy WS) Crea una PR: “Dev compose: nginx reverse proxy -> server (WS upgrade ok)”. Obiettivo: - Aggiungere `infra/docker-compose.yml` con: - server - nginx reverse proxy davanti Requisiti: - Nginx deve supportare WebSocket upgrade per /agent e /client. - In dev, niente TLS (http + ws). - Documenta in README come testare: - ws://localhost/agent - ws://localhost/client (oppure con porte diverse, ma deve essere chiaro) Acceptance: - Connessione WS tramite nginx funziona (upgrade ok). - /health raggiungibile via nginx. Prompt Windows (Electron) — Fase 1 Prompt W1 — Electron skeleton + pagine login/devices Crea una PR: “Electron TS skeleton: login page + devices page”. Obiettivo: - Creare app Electron in /windows con TypeScript. - UI minimale con due schermate: - Login (username/password, serverUrl) - Devices (placeholder lista) Requisiti: - Puoi usare React+Vite oppure Electron Forge. Scegli stack semplice e moderno. - Config serverUrl persistente (localStorage ok). Acceptance: - `pnpm --filter windows dev` avvia l’app. - Navigazione login -> devices (anche finta). Prompt W2 — WS client + client_login Crea una PR: “Windows: WS client + client_login”. Obiettivo: - Connettere Electron a ws:///client - Implementare invio client_login e gestione client_login_result usando `shared/protocol`. Requisiti: - Gestisci reconnect base (retry ogni 2s fino a successo) o almeno errore UI. - Dopo login ok => vai a schermata Devices. Acceptance: - Login corretto => ok e vai a Devices. - Login errato => mostra errore. Prompt W3 — Lista device (client_list_devices + refresh) Crea una PR: “Windows: device list + refresh/poll”. Obiettivo: - Implementare: - client_list_devices all’apertura della schermata - poll ogni 5s (o pulsante refresh) per aggiornare - Render tabella: deviceName, deviceId, online, lastSeenIso. Acceptance: - Vedo device comparire e cambiare stato online/offline. Prompt W4 — Connect request UI + stato pending/accepted/denied/expired Crea una PR: “Windows: connect request + status UI”. Obiettivo: - Aggiungere un bottone “Connect” per ogni device. - Click => invia client_connect_request. - Mostra stato: - pending (spinner o testo) - accepted / denied / expired Requisiti: - Gestisci più richieste (almeno una alla volta; se già pending, disabilita altri connect). Acceptance: - Pending appare subito. - Alla risposta dell’agent si aggiorna la UI. Prompt Android (Kotlin) — Fase 1 Prompt A1 — Foreground service + WS + deviceId persistente Crea una PR Android: “Agent: ForegroundService + WS client + deviceId persistente”. Obiettivo: - App Android Kotlin con: - ForegroundService “Remote Agent” - WebSocket OkHttp - deviceId UUID persistente in SharedPreferences - schermata settings: serverUrl, pairingKey, deviceName (persistenti) Comportamento: - Avvio app -> start service -> connect ws://serverUrl/agent - Log utile. Acceptance: - Service parte e resta in foreground con notifica. - deviceId resta uguale tra riavvii app. Prompt A2 — agent_register + heartbeat + reconnect Crea una PR Android: “Agent: agent_register + heartbeat + reconnect”. Obiettivo: - Dopo WS open => invia agent_register {deviceId, deviceName, pairingKey} - Heartbeat ogni 30s: agent_heartbeat - Reconnect automatico se WS cade (retry con backoff semplice). Acceptance: - Server mostra device online. - Stacco/riattacco rete => torna online senza riaprire app. Prompt A3 — Notifica connect_request con azioni + agent_connect_response Crea una PR Android: “Agent: notifica connect request + accept/deny”. Obiettivo: - Ricevere server_connect_request dal WS - Mostrare notifica con azioni: - ACCETTA - RIFIUTA - Click azione => invia agent_connect_response con {sessionId, deviceId, decision} Requisiti: - Deve funzionare anche con app in background. - Usa PendingIntent + BroadcastReceiver per gestire azioni. Acceptance: - Notifica appare quando il client richiede connessione. - Accept/deny inviano risposta e il client riceve l’esito.