feat: fix Cursor hook delivery and improve notify workflow

Route hook notifications through /dev/tty and client_tty so OSC reaches
Ghostty when Cursor captures stdout. Add afterAgentResponse hook, hook
logging, debounce, split test modes, and use directory name for titles.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 12:46:50 +08:00
parent aa9c2b8ea5
commit 021fc3a828
20 changed files with 757 additions and 76 deletions
+59
View File
@@ -0,0 +1,59 @@
package logx
import (
"fmt"
"os"
"path/filepath"
"time"
)
func Path() string {
return filepath.Join(os.Getenv("HOME"), ".local", "state", "agent-notify", "hook.log")
}
func Append(format string, args ...any) {
path := Path()
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
return
}
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return
}
defer f.Close()
msg := fmt.Sprintf(format, args...)
fmt.Fprintf(f, "[%s] %s\n", time.Now().Format(time.RFC3339), msg)
}
func Tail(n int) ([]string, error) {
data, err := os.ReadFile(Path())
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
lines := splitLines(string(data))
if n <= 0 || n >= len(lines) {
return lines, nil
}
return lines[len(lines)-n:], nil
}
func splitLines(s string) []string {
var lines []string
start := 0
for i := 0; i < len(s); i++ {
if s[i] == '\n' {
line := s[start:i]
if line != "" {
lines = append(lines, line)
}
start = i + 1
}
}
if start < len(s) {
lines = append(lines, s[start:])
}
return lines
}
+29
View File
@@ -0,0 +1,29 @@
package logx
import (
"os"
"path/filepath"
"testing"
)
func TestAppendAndTail(t *testing.T) {
dir := t.TempDir()
t.Setenv("HOME", dir)
Append("hello %s", "world")
lines, err := Tail(10)
if err != nil {
t.Fatal(err)
}
if len(lines) != 1 {
t.Fatalf("expected 1 line, got %d", len(lines))
}
if lines[0] == "" {
t.Fatal("empty line")
}
path := filepath.Join(dir, ".local", "state", "agent-notify", "hook.log")
if _, err := os.Stat(path); err != nil {
t.Fatalf("log file not created: %v", err)
}
}