// DSHWatch relay broker // Bridges DeepSeek Harness instances (DSH plugins) with phone/watch clients over WebSocket. // // Roles: // dsh - the DeepSeek Harness plugin (one per userId) // client - phone app (and through the phone app, the watch) // // Protocol is a tiny JSON envelope. See README.md. import { createServer } from 'node:http'; import { WebSocketServer } from 'ws'; import { readFileSync } from 'node:fs'; import { fileURLToPath } from 'node:url'; import path from 'node:path'; import process from 'node:process'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); function loadConfig() { const configPath = process.env.DSHWATCH_SERVER_CONFIG || path.join(__dirname, 'config.json'); let config = {}; try { config = JSON.parse(readFileSync(configPath, 'utf8')); } catch { // use defaults } return { port: Number(config.port || process.env.PORT || 8787), host: config.host || process.env.HOST || '0.0.0.0', token: config.token || process.env.DSHWATCH_TOKEN || 'change-me', heartbeatMs: Number(config.heartbeatMs || 30000) }; } const config = loadConfig(); const harnesses = new Map(); const clients = new Map(); const pending = new Map(); const lastStatus = new Map(); const wsUserId = new Map(); function bindUserId(ws, userId) { wsUserId.set(ws, userId); } function userIdOf(ws) { return wsUserId.get(ws); } function clientsFor(userId) { let set = clients.get(userId); if (!set) { set = new Set(); clients.set(userId, set); } return set; } function send(ws, obj) { if (ws.readyState === ws.OPEN) { try { ws.send(JSON.stringify(obj)); } catch { /* ignore */ } } } function broadcastToClients(userId, obj) { for (const ws of clientsFor(userId)) send(ws, obj); } function replyError(clientWs, requestId, message, code) { send(clientWs, { type: 'response', requestId, ok: false, error: { code: code || 'unreachable', message } }); } function registerHarness(userId, ws) { const prev = harnesses.get(userId); if (prev && prev !== ws) { send(prev, { type: 'notice', code: 'replaced', message: 'A newer harness connection took over this userId.' }); try { prev.close(); } catch {} } harnesses.set(userId, ws); const status = lastStatus.get(userId) || null; send(ws, { type: 'hello', role: 'dsh', online: true, status: status ? { userId: status.userId } : {} }); broadcastToClients(userId, { type: 'harness', online: true }); } function unregisterHarness(userId, ws) { if (harnesses.get(userId) === ws) { harnesses.delete(userId); broadcastToClients(userId, { type: 'harness', online: false }); } } function handleDshHello(userId, ws, msg) { bindUserId(ws, userId); if (msg.token !== config.token) { send(ws, { type: 'auth-error', message: 'invalid token' }); ws.close(); return; } ws.role = 'dsh'; registerHarness(userId, ws); } function handleClientHello(userId, ws, msg) { bindUserId(ws, userId); if (msg.token !== config.token) { send(ws, { type: 'auth-error', message: 'invalid token' }); ws.close(); return; } ws.role = 'client'; clientsFor(userId).add(ws); const harness = harnesses.get(userId); const status = lastStatus.get(userId) || null; send(ws, { type: 'hello', role: 'client', online: Boolean(harness), harness: Boolean(harness), status }); } function handleHarnessMessage(userId, ws, msg) { if (msg.type === 'response') { const req = pending.get(msg.requestId); if (req) { clearTimeout(req.timer); pending.delete(msg.requestId); send(req.clientWs, { type: 'response', requestId: req.clientRequestId, ok: msg.ok !== false, result: msg.result, error: msg.error }); } } else if (msg.type === 'event') { broadcastToClients(userId, { type: 'event', event: msg.event }); } else if (msg.type === 'status') { lastStatus.set(userId, msg.status || null); broadcastToClients(userId, { type: 'status', status: msg.status }); } else if (msg.type === 'ping') { send(ws, { type: 'pong' }); } } function handleClientMessage(userId, ws, msg) { if (msg.type === 'request') { const harness = harnesses.get(userId); if (!harness) { replyError(ws, msg.requestId, 'harness offline'); return; } const requestId = msg.requestId || ('req-' + Date.now() + '-' + Math.random().toString(16).slice(2)); if (pending.has(requestId)) { replyError(ws, msg.requestId, 'duplicate requestId', 'bad-request'); return; } const timer = setTimeout(() => { pending.delete(requestId); replyError(ws, msg.requestId, 'harness timeout'); }, Number(msg.timeoutMs) || 30000); pending.set(requestId, { userId, clientWs: ws, clientRequestId: msg.requestId, timer }); send(harness, { type: 'request', requestId, method: msg.method, params: msg.params || {} }); } else if (msg.type === 'ping') { send(ws, { type: 'pong' }); } } function handleMessage(ws, raw) { let msg; try { msg = JSON.parse(String(raw)); } catch { send(ws, { type: 'error', message: 'invalid JSON' }); return; } if (!msg || typeof msg !== 'object') return; if (msg.type === 'hello' && !ws.role) { const userId = msg.userId; if (typeof userId !== 'string' || userId.length === 0) { send(ws, { type: 'auth-error', message: 'missing userId' }); ws.close(); return; } if (msg.role === 'dsh') handleDshHello(userId, ws, msg); else handleClientHello(userId, ws, msg); return; } const userId = userIdOf(ws); if (!userId) { send(ws, { type: 'auth-error', message: 'hello required' }); ws.close(); return; } if (ws.role === 'dsh') { handleHarnessMessage(userId, ws, msg); } else if (ws.role === 'client') { handleClientMessage(userId, ws, msg); } else { send(ws, { type: 'auth-error', message: 'hello required' }); ws.close(); } } function handleConnection(ws, req) { const url = new URL(req.url || '/', 'http://localhost'); if (url.pathname !== '/ws' && url.pathname !== '/') { ws.close(4001, 'unknown path'); return; } ws.isAlive = true; ws.on('pong', () => { ws.isAlive = true; }); ws.on('message', (raw) => { try { handleMessage(ws, raw); } catch (err) { send(ws, { type: 'error', message: String(err && err.message || err) }); ws.close(); } }); ws.on('close', () => { const userId = userIdOf(ws); if (userId) { if (ws.role === 'dsh') unregisterHarness(userId, ws); else { const set = clients.get(userId); if (set) { set.delete(ws); if (set.size === 0) clients.delete(userId); } } for (const [id, req] of pending) { if (req.clientWs === ws) { clearTimeout(req.timer); pending.delete(id); } } } wsUserId.delete(ws); }); ws.on('error', () => {}); } const server = createServer((req, res) => { const url = new URL(req.url || '/', 'http://localhost'); if (url.pathname === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true, harnesses: Array.from(harnesses.entries()).map(([userId]) => ({ userId })), clients: Array.from(clients.entries()).map(([userId, set]) => ({ userId, count: set.size })) })); return; } res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('DSHWatch relay broker running. Connect clients to /ws\n'); }); const wss = new WebSocketServer({ server }); wss.on('connection', handleConnection); setInterval(() => { for (const ws of wss.clients) { if (ws.isAlive === false) { ws.terminate(); continue; } ws.isAlive = false; ws.ping(); } }, config.heartbeatMs); server.listen(config.port, config.host, () => { console.log(String('[dshwatch-server] listening on ws://' + config.host + ':' + config.port + '/ws')); console.log(String('[dshwatch-server] health: http://' + config.host + ':' + config.port + '/health')); });