mirror of
https://github.com/sipeed/NanoKVM.git
synced 2026-09-11 00:22:56 -05:00
Add an MCP Streamable HTTP server with token authentication, screenshot capture, keyboard, and mouse tools. Introduce shared control-mode and input-control coordination so MCP, PicoClaw, and local HID paths serialize ownership safely. Integrate PicoClaw gateway/runtime control handoff with PID-managed startup and focused unit coverage.
106 lines
2.2 KiB
Go
106 lines
2.2 KiB
Go
package picoclaw
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gorilla/websocket"
|
|
)
|
|
|
|
var (
|
|
sessionManagerOnce sync.Once
|
|
sessionManager *SessionManager
|
|
)
|
|
|
|
func GetSessionManager() *SessionManager {
|
|
sessionManagerOnce.Do(func() {
|
|
sessionManager = &SessionManager{
|
|
sessions: make(map[string]*GatewaySession),
|
|
}
|
|
})
|
|
|
|
return sessionManager
|
|
}
|
|
|
|
func (m *SessionManager) Register(sessionID string, downstream *websocket.Conn) (*GatewaySession, *PicoclawError) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if existing, ok := m.sessions[sessionID]; ok {
|
|
switch existing.State {
|
|
case SessionStateClosing, SessionStateClosed:
|
|
delete(m.sessions, sessionID)
|
|
default:
|
|
err := newPicoclawError(CodePicoclawLockHeld, "session is already connected")
|
|
err.SessionID = sessionID
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
now := time.Now()
|
|
session := &GatewaySession{
|
|
SessionID: sessionID,
|
|
State: SessionStateCreated,
|
|
Downstream: downstream,
|
|
CreatedAt: now,
|
|
UpdatedAt: now,
|
|
}
|
|
m.sessions[sessionID] = session
|
|
return session, nil
|
|
}
|
|
|
|
func (m *SessionManager) Get(sessionID string) (*GatewaySession, bool) {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
session, ok := m.sessions[sessionID]
|
|
return session, ok
|
|
}
|
|
|
|
func (m *SessionManager) SetState(sessionID string, state SessionState) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if session, ok := m.sessions[sessionID]; ok {
|
|
session.State = state
|
|
session.UpdatedAt = time.Now()
|
|
}
|
|
}
|
|
|
|
func (m *SessionManager) AttachUpstream(sessionID string, upstream *websocket.Conn) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if session, ok := m.sessions[sessionID]; ok {
|
|
session.Upstream = upstream
|
|
session.UpdatedAt = time.Now()
|
|
}
|
|
}
|
|
|
|
func (m *SessionManager) AttachDownstream(sessionID string, downstream *websocket.Conn) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
|
|
if session, ok := m.sessions[sessionID]; ok {
|
|
session.Downstream = downstream
|
|
session.UpdatedAt = time.Now()
|
|
}
|
|
}
|
|
|
|
func (m *SessionManager) Remove(sessionID string) {
|
|
m.mu.Lock()
|
|
defer m.mu.Unlock()
|
|
delete(m.sessions, sessionID)
|
|
}
|
|
|
|
func (m *SessionManager) Snapshot() []*GatewaySession {
|
|
m.mu.RLock()
|
|
defer m.mu.RUnlock()
|
|
|
|
sessions := make([]*GatewaySession, 0, len(m.sessions))
|
|
for _, session := range m.sessions {
|
|
sessions = append(sessions, session)
|
|
}
|
|
return sessions
|
|
}
|