@@ -20,7 +20,7 @@ import (
2020 "github.com/BackendStack21/kode/internal/resource"
2121 "github.com/BackendStack21/kode/internal/session"
2222 "github.com/BackendStack21/kode/internal/skills"
23- "github.com/BackendStack21/kode/internal/ws "
23+ golangws "golang.org/x/net/websocket "
2424)
2525
2626//go:embed ui/index.html
@@ -76,7 +76,12 @@ Flags:
7676
7777 mux := http .NewServeMux ()
7878 mux .HandleFunc ("/" , handleStatic ())
79- mux .HandleFunc ("/ws" , handleWebSocket (store , resourceReg , resolved , systemMessage ))
79+ mux .Handle ("/ws" , & golangws.Server {
80+ Handshake : func (* golangws.Config , * http.Request ) error { return nil },
81+ Handler : func (conn * golangws.Conn ) {
82+ handleWS (store , resourceReg , resolved , systemMessage , conn )
83+ },
84+ })
8085 mux .HandleFunc ("/api/resources" , handleResourceSearch (resourceReg ))
8186 mux .HandleFunc ("/api/sessions" , handleSessionList (store ))
8287
@@ -174,102 +179,92 @@ type wsClientMsg struct {
174179
175180// ── WebSocket Handler ──────────────────────────────────────────────────
176181
177- func handleWebSocket (store * session.Store , resources * resource.Registry , resolved config.ResolvedConfig , system string ) http.HandlerFunc {
178- return func (w http.ResponseWriter , r * http.Request ) {
179- conn , err := ws .Upgrade (w , r )
180- if err != nil {
181- http .Error (w , err .Error (), http .StatusBadRequest )
182- return
183- }
184- defer conn .Close ()
182+ func handleWS (store * session.Store , resources * resource.Registry , resolved config.ResolvedConfig , system string , conn * golangws.Conn ) {
183+ defer conn .Close ()
185184
186- // Create ONE agent per WebSocket connection — provides buffer
187- // continuity across turns within the same session.
188- agent , sandboxCleanup , mcpCleanup , approver , err := newServeAgent (resolved , system , func (v any ) error {
189- writeWSJSON (conn , v )
190- return nil
191- })
192- if err != nil {
193- writeWSError (conn , fmt .Sprintf ("agent: %v" , err ))
194- return
195- }
196- defer agent .Close ()
197- if sandboxCleanup != nil {
198- defer sandboxCleanup ()
199- }
200- if mcpCleanup != nil {
201- defer mcpCleanup ()
202- }
185+ // Create ONE agent per WebSocket connection — provides buffer
186+ // continuity across turns within the same session.
187+ agent , sandboxCleanup , mcpCleanup , approver , err := newServeAgent (resolved , system , func (v any ) error {
188+ writeWSJSON (conn , v )
189+ return nil
190+ })
191+ if err != nil {
192+ writeWSError (conn , fmt .Sprintf ("agent: %v" , err ))
193+ return
194+ }
195+ defer agent .Close ()
196+ if sandboxCleanup != nil {
197+ defer sandboxCleanup ()
198+ }
199+ if mcpCleanup != nil {
200+ defer mcpCleanup ()
201+ }
203202
204- ctx , cancel := signal .NotifyContext (r . Context (), os .Interrupt )
205- defer cancel ()
203+ ctx , cancel := signal .NotifyContext (context . Background (), os .Interrupt )
204+ defer cancel ()
206205
207- // Track the current session across WebSocket messages
208- var currentSession * session.Session
206+ // Track the current session across WebSocket messages
207+ var currentSession * session.Session
209208
210- for {
211- opcode , data , err := conn .ReadMessage ()
212- if err != nil {
213- break
214- }
215- if opcode != ws .OpText {
216- continue
217- }
209+ for {
210+ var data []byte
211+ if err := golangws .Message .Receive (conn , & data ); err != nil {
212+ break
213+ }
218214
219- // Peek at the message type without full unmarshal
220- var msgType struct {
221- Type string `json:"type"`
222- }
223- if err := json .Unmarshal (data , & msgType ); err != nil {
224- continue
225- }
215+ // Peek at the message type without full unmarshal
216+ var msgType struct {
217+ Type string `json:"type"`
218+ }
219+ if err := json .Unmarshal (data , & msgType ); err != nil {
220+ continue
221+ }
226222
227- // Handle approval responses separately (non-blocking, from the browser)
228- if msgType .Type == "approval_response" {
229- var resp approvalResponse
230- if err := json .Unmarshal (data , & resp ); err == nil {
231- approver .HandleResponse (resp .ID , resp .Action )
232- }
233- continue
223+ // Handle approval responses separately (non-blocking, from the browser)
224+ if msgType .Type == "approval_response" {
225+ var resp approvalResponse
226+ if err := json .Unmarshal (data , & resp ); err == nil {
227+ approver .HandleResponse (resp .ID , resp .Action )
234228 }
229+ continue
230+ }
235231
236- // Only process prompt messages
237- if msgType .Type != "prompt" {
238- continue
239- }
232+ // Only process prompt messages
233+ if msgType .Type != "prompt" {
234+ continue
235+ }
240236
241- var msg wsClientMsg
242- if err := json .Unmarshal (data , & msg ); err != nil {
243- writeWSError (conn , "invalid JSON" )
244- continue
245- }
237+ var msg wsClientMsg
238+ if err := json .Unmarshal (data , & msg ); err != nil {
239+ writeWSError (conn , "invalid JSON" )
240+ continue
241+ }
246242
247- if msg .Content == "" {
248- continue
249- }
243+ if msg .Content == "" {
244+ continue
245+ }
250246
251- // Handle session switch mid-connection (new conversation)
252- if msg .SessionID != "" && (currentSession == nil || currentSession .ID != msg .SessionID ) {
253- sess , err := store .Load (msg .SessionID )
254- if err == nil {
255- currentSession = sess
256- // Restore buffer from the resumed session
257- if mm := agent .Memory (); mm != nil && len (sess .Buffer ) > 0 {
258- mm .RestoreBuffer (sess .Buffer )
259- }
247+ // Handle session switch mid-connection (new conversation)
248+ if msg .SessionID != "" && (currentSession == nil || currentSession .ID != msg .SessionID ) {
249+ sess , err := store .Load (msg .SessionID )
250+ if err == nil {
251+ currentSession = sess
252+ // Restore buffer from the resumed session
253+ if mm := agent .Memory (); mm != nil && len (sess .Buffer ) > 0 {
254+ mm .RestoreBuffer (sess .Buffer )
260255 }
261256 }
262-
263- // Run prompt — passes the persistent agent for buffer continuity
264- currentSession = handlePrompt (ctx , conn , store , resources , resolved , agent , currentSession , msg .Content , msg .SessionID )
265257 }
266258
267- // WebSocket disconnected — extract episode if enough turns
268- if currentSession != nil {
269- if mm := agent .Memory (); mm != nil {
270- msgStrs := makeSessionMessageStrings (currentSession )
271- mm .OnSessionEnd (currentSession .ID , currentSession .Turns , msgStrs )
272- }
259+ // Run prompt — passes the persistent agent for buffer continuity
260+ currentSession = handlePrompt (ctx , conn , store , resources , resolved , agent , currentSession , msg .Content , msg .SessionID )
261+ }
262+
263+ // WebSocket disconnected — extract episode if enough turns
264+ if currentSession != nil {
265+ if mm := agent .Memory (); mm != nil {
266+ msgStrs := makeSessionMessageStrings (currentSession )
267+ mm .OnSessionEnd (currentSession .ID , currentSession .Turns , msgStrs )
273268 }
274269 }
275270}
@@ -281,7 +276,7 @@ func handleWebSocket(store *session.Store, resources *resource.Registry, resolve
281276// Returns the updated session (may be a new session for first prompts).
282277func handlePrompt (
283278 ctx context.Context ,
284- conn * ws .Conn ,
279+ conn * golangws .Conn ,
285280 store * session.Store ,
286281 resources * resource.Registry ,
287282 resolved config.ResolvedConfig ,
@@ -434,7 +429,7 @@ func handlePrompt(
434429// ── WebSocket Stream Writer ─────────────────────────────────────────────
435430
436431type wsStreamWriter struct {
437- conn * ws .Conn
432+ conn * golangws .Conn
438433}
439434
440435func (w * wsStreamWriter ) Write (p []byte ) (int , error ) {
@@ -448,15 +443,15 @@ func (w *wsStreamWriter) Write(p []byte) (int, error) {
448443
449444// ── WS Helpers ─────────────────────────────────────────────────────────
450445
451- func writeWSJSON (conn * ws .Conn , data any ) {
446+ func writeWSJSON (conn * golangws .Conn , data any ) {
452447 payload , err := json .Marshal (data )
453448 if err != nil {
454449 return
455450 }
456- conn . WriteMessage ( ws . OpText , payload )
451+ golangws . Message . Send ( conn , string ( payload ) )
457452}
458453
459- func writeWSError (conn * ws .Conn , msg string ) {
454+ func writeWSError (conn * golangws .Conn , msg string ) {
460455 writeWSJSON (conn , map [string ]string {"type" : "error" , "message" : msg })
461456}
462457
@@ -488,8 +483,7 @@ func handleSessionList(store *session.Store) http.HandlerFunc {
488483 return func (w http.ResponseWriter , r * http.Request ) {
489484 sessions , err := store .List (50 )
490485 if err != nil {
491- http .Error (w , err .Error (), http .StatusInternalServerError )
492- return
486+ sessions = []session.Session {}
493487 }
494488 if sessions == nil {
495489 sessions = []session.Session {}
@@ -544,4 +538,4 @@ func openInBrowser(url string) {
544538 return
545539 }
546540 }
547- }
541+ }
0 commit comments