diff --git a/.gitignore b/.gitignore index 6e825af..3a78f85 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ anvilweb .claude .kiro *.elc +Assist diff --git a/README.md b/README.md index dad12cd..df7a894 100644 --- a/README.md +++ b/README.md @@ -205,7 +205,7 @@ Type `Assist` in Acme and middle-click to open the `/AnviLLM/` session manager. ### Commands -**Main window** (`/AnviLLM/`) tag: `Get Attach Stop Restart Kill Alias Context Daemon Sandbox` +**Main window** (`/AnviLLM/`) tag: `Get Attach Stop Restart Kill Alias Context Log Daemon Sandbox` | Command | Description | |---------|-------------| @@ -217,6 +217,7 @@ Type `Assist` in Acme and middle-click to open the `/AnviLLM/` session manager. | `Attach ` | Open tmux terminal | | `Alias ` | Name a session | | `Context ` | Edit context (prepended to prompts) | +| `Log ` | View session log | | `Daemon` | Manage anvilsrv daemon (start/stop/status) | | `Sandbox` | Configure sandbox settings | diff --git a/cmd/Assist/main.go b/cmd/Assist/main.go index 6819ba9..c5ac18d 100644 --- a/cmd/Assist/main.go +++ b/cmd/Assist/main.go @@ -92,7 +92,7 @@ func main() { defer w.CloseFiles() w.Name(windowName) - w.Write("tag", []byte("Get Attach Stop Restart Kill Alias Context Daemon Sandbox ")) + w.Write("tag", []byte("Get Attach Stop Restart Kill Alias Context Log Daemon Sandbox ")) refreshList(w) w.Ctl("clean") @@ -221,6 +221,19 @@ func main() { if err := openContextWindow(sess); err != nil { fmt.Fprintf(os.Stderr, "Error opening context window: %v\n", err) } + case "Log": + if arg == "" { + fmt.Fprintf(os.Stderr, "Usage: Log \n") + continue + } + sess, err := getSession(arg) + if err != nil { + fmt.Fprintf(os.Stderr, "Session not found: %s\n", arg) + continue + } + if err := openLogWindow(sess); err != nil { + fmt.Fprintf(os.Stderr, "Error opening log window: %v\n", err) + } case "Sandbox": if err := openSandboxWindow(); err != nil { fmt.Fprintf(os.Stderr, "Error opening sandbox window: %v\n", err) @@ -592,6 +605,57 @@ func openContextWindow(sess *SessionInfo) error { return nil } +func openLogWindow(sess *SessionInfo) error { + go func() { + displayName := sess.Alias + if displayName == "" { + displayName = sess.ID + } + + w, err := acme.New() + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating log window: %v\n", err) + return + } + defer w.CloseFiles() + + w.Name(fmt.Sprintf("/AnviLLM/Log.%s", displayName)) + w.Ctl("clean") + + // Stream log using external command + logPath := fmt.Sprintf("agent/%s/log", sess.ID) + cmd := exec.Command("9p", "read", logPath) + + stdout, err := cmd.StdoutPipe() + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating pipe: %v\n", err) + return + } + + if err := cmd.Start(); err != nil { + fmt.Fprintf(os.Stderr, "Error starting 9p read: %v\n", err) + return + } + + // Stream output to window + buf := make([]byte, 8192) + for { + n, err := stdout.Read(buf) + if n > 0 { + w.Addr("$") + w.Write("data", buf[:n]) + w.Ctl("clean") + } + if err != nil { + break + } + } + + cmd.Wait() + }() + return nil +} + func handleContextWindow(w *acme.Win, sess *SessionInfo) { defer w.CloseFiles()