diff --git a/playground/web/go.mod b/playground/web/go.mod deleted file mode 100644 index e7bfaef..0000000 --- a/playground/web/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module cw_session_web - -go 1.25.0 diff --git a/playground/web/index.html b/playground/web/index.html deleted file mode 100644 index d5b496c..0000000 --- a/playground/web/index.html +++ /dev/null @@ -1,153 +0,0 @@ - - - - - -claude_session · chat caliente - - - -
- -

claude_session · chat caliente

- - sesión viva · con memoria · ~2.7s/msg -
- -
-
-
sistema
Sesión claude caliente y persistente. Mantiene contexto entre mensajes (pruébalo: pregunta algo y luego refiérete a ello). Cada respuesta llega del SSE de la red, exacta, en ~2-3s. "Nueva conversación" reinicia la sesión sin memoria. -
-
- -
- - -
-
Un único daemon claude_session vivo detrás. Mensajes secuenciales. La memoria persiste hasta que reinicies.
- - - - diff --git a/playground/web/server.go b/playground/web/server.go deleted file mode 100644 index 283e93d..0000000 --- a/playground/web/server.go +++ /dev/null @@ -1,198 +0,0 @@ -// Command server is a browser chat over a single hot claude_session daemon. Unlike -// the claude_pipe/claude_wire chats (which spawn a fresh one-shot process per -// message), this server launches ONE claude_session at boot and keeps it alive, so -// the conversation has memory across turns and each message answers in ~2-3s. -// -// Per message it writes {"cmd":"send","prompt":...} to the daemon's stdin and -// relays the daemon's NDJSON (text_delta + result) to the browser as SSE. A -// /restart endpoint sends {"cmd":"restart"} to start a fresh conversation. -// -// The daemon's stdout is read only while holding a mutex, so requests are -// serialized (a chat is sequential anyway). -// -// Run: -// -// cd apps/claude_session && go build -o claude_session . -// cd playground && go run ./web # http://localhost:8101 -package main - -import ( - "bufio" - _ "embed" - "encoding/json" - "flag" - "fmt" - "io" - "log" - "net/http" - "os" - "os/exec" - "path/filepath" - "sync" - "time" -) - -//go:embed index.html -var indexHTML []byte - -type daemonEvent struct { - Type string `json:"type"` - Text string `json:"text"` - Result string `json:"result"` - Message string `json:"message"` -} - -var ( - mu sync.Mutex - stdin io.Writer - stdout *bufio.Scanner - session *exec.Cmd -) - -func main() { - port := flag.String("port", "8101", "port to listen on") - bin := flag.String("session", "/home/enmanuel/fn_registry/apps/claude_session/claude_session", "claude_session binary") - cwd := flag.String("cwd", "/home/enmanuel/fn_registry", "cwd for the claude session") - flag.Parse() - - abs, err := filepath.Abs(*bin) - if err != nil || !fileExists(abs) { - log.Fatalf("claude_session binary not found at %s — build it first (cd .. && go build -o claude_session .)", abs) - } - - if err := startDaemon(abs, *cwd); err != nil { - log.Fatalf("start daemon: %v", err) - } - - http.HandleFunc("/", handleIndex) - http.HandleFunc("/chat", handleChat) - http.HandleFunc("/restart", handleRestart) - - log.Printf("claude_session web chat → http://localhost:%s (daemon=%s cwd=%s)", *port, abs, *cwd) - log.Fatal(http.ListenAndServe(":"+*port, nil)) -} - -// startDaemon launches claude_session and blocks until its initial {"type":"ready"}. -func startDaemon(bin, cwd string) error { - cmd := exec.Command(bin, "--cwd", cwd) - cmd.Stderr = os.Stderr - in, err := cmd.StdinPipe() - if err != nil { - return err - } - out, err := cmd.StdoutPipe() - if err != nil { - return err - } - if err := cmd.Start(); err != nil { - return err - } - sc := bufio.NewScanner(out) - sc.Buffer(make([]byte, 1024*1024), 1024*1024) - stdin, stdout, session = in, sc, cmd - - log.Printf("waiting for claude session to warm up...") - for sc.Scan() { - var ev daemonEvent - if json.Unmarshal(sc.Bytes(), &ev) == nil && ev.Type == "ready" { - log.Printf("claude session ready") - return nil - } - } - return fmt.Errorf("daemon exited before ready") -} - -func handleIndex(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/" { - http.NotFound(w, r) - return - } - w.Header().Set("Content-Type", "text/html; charset=utf-8") - _, _ = w.Write(indexHTML) -} - -// handleChat sends one prompt to the live daemon and relays its events as SSE. -func handleChat(w http.ResponseWriter, r *http.Request) { - prompt := r.URL.Query().Get("prompt") - if prompt == "" { - http.Error(w, "missing prompt", http.StatusBadRequest) - return - } - flusher, ok := w.(http.Flusher) - if !ok { - http.Error(w, "streaming unsupported", http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "text/event-stream") - w.Header().Set("Cache-Control", "no-cache") - w.Header().Set("X-Accel-Buffering", "no") - - mu.Lock() - defer mu.Unlock() - - cmd, _ := json.Marshal(map[string]string{"cmd": "send", "prompt": prompt}) - if _, err := fmt.Fprintln(stdin, string(cmd)); err != nil { - sse(w, flusher, "error", fmt.Sprintf(`{"message":%q}`, err.Error())) - return - } - - // Relay daemon events until result, then consume the trailing ready. - gotResult := false - for stdout.Scan() { - line := stdout.Bytes() - var ev daemonEvent - if json.Unmarshal(line, &ev) != nil { - continue - } - switch ev.Type { - case "text_delta", "result", "error": - sse(w, flusher, "", string(line)) - if ev.Type == "result" || ev.Type == "error" { - gotResult = true - } - case "ready": - if gotResult { - sse(w, flusher, "done", "{}") - return - } - } - } - sse(w, flusher, "error", `{"message":"daemon stream ended"}`) -} - -// handleRestart tells the daemon to start a fresh conversation. -func handleRestart(w http.ResponseWriter, r *http.Request) { - mu.Lock() - defer mu.Unlock() - - if _, err := fmt.Fprintln(stdin, `{"cmd":"restart"}`); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - deadline := time.Now().Add(30 * time.Second) - for stdout.Scan() { - var ev daemonEvent - if json.Unmarshal(stdout.Bytes(), &ev) == nil && ev.Type == "ready" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ok":true}`)) - return - } - if time.Now().After(deadline) { - break - } - } - http.Error(w, "restart timeout", http.StatusGatewayTimeout) -} - -func sse(w http.ResponseWriter, f http.Flusher, event, data string) { - if event != "" { - fmt.Fprintf(w, "event: %s\n", event) - } - fmt.Fprintf(w, "data: %s\n\n", data) - f.Flush() -} - -func fileExists(p string) bool { - _, err := os.Stat(p) - return err == nil -}