feat: add inbox
This commit is contained in:
@@ -53,5 +53,6 @@ func RunClaude(r io.Reader, cfg config.Config, event string, w io.Writer) error
|
||||
return err
|
||||
}
|
||||
logx.Append("hook claude event=%s terminalSequence OK title=%q", event, title)
|
||||
recordInbox(cfg, "Claude", event, meta.CWD, title, body)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ func RunCursor(r io.Reader, cfg config.Config, event string, _ io.Writer) error
|
||||
return err
|
||||
}
|
||||
logx.Append("hook cursor event=%s send OK via %s title=%q", event, result.Method, title)
|
||||
recordInbox(cfg, "Cursor", event, meta.CWD, title, body)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ package hook
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/longbin/agent-notify/internal/config"
|
||||
"github.com/longbin/agent-notify/internal/inbox"
|
||||
)
|
||||
|
||||
func TestCursorStopHookDisabled(t *testing.T) {
|
||||
@@ -46,3 +48,105 @@ func TestClaudeStopHookActiveSkips(t *testing.T) {
|
||||
t.Fatalf("expected {}, got %q", out.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursorHookUploadsInboxRecord(t *testing.T) {
|
||||
stubCursorSend(t)
|
||||
var uploaded inbox.Record
|
||||
stubInbox(t,
|
||||
func(rec inbox.Record, cfg config.Config) error {
|
||||
uploaded = rec
|
||||
return nil
|
||||
},
|
||||
func(rec inbox.Record) error {
|
||||
t.Fatalf("unexpected fallback append: %+v", rec)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
cfg := config.Default()
|
||||
err := RunCursor(bytes.NewReader([]byte(`{"workspace_roots":["/tmp/proj"]}`)), cfg, "stop", &bytes.Buffer{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if uploaded.Agent != "Cursor" || uploaded.Event != "stop" || uploaded.CWD != "/tmp/proj" {
|
||||
t.Fatalf("unexpected upload record: %+v", uploaded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCursorHookFallbacksWhenInboxUploadFails(t *testing.T) {
|
||||
stubCursorSend(t)
|
||||
var fallback inbox.Record
|
||||
stubInbox(t,
|
||||
func(rec inbox.Record, cfg config.Config) error {
|
||||
return errors.New("offline")
|
||||
},
|
||||
func(rec inbox.Record) error {
|
||||
fallback = rec
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
cfg := config.Default()
|
||||
err := RunCursor(bytes.NewReader([]byte(`{"workspace_roots":["/tmp/proj"]}`)), cfg, "stop", &bytes.Buffer{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if fallback.Source != inbox.SourceFallback || fallback.Title == "" {
|
||||
t.Fatalf("unexpected fallback record: %+v", fallback)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisabledHookDoesNotRecordInbox(t *testing.T) {
|
||||
stubCursorSend(t)
|
||||
stubInbox(t,
|
||||
func(rec inbox.Record, cfg config.Config) error {
|
||||
t.Fatalf("unexpected upload: %+v", rec)
|
||||
return nil
|
||||
},
|
||||
func(rec inbox.Record) error {
|
||||
t.Fatalf("unexpected fallback: %+v", rec)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
cfg := config.Default()
|
||||
cfg.Events.Stop = false
|
||||
if err := RunCursor(bytes.NewReader([]byte(`{}`)), cfg, "stop", &bytes.Buffer{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClaudeHookUploadsInboxRecord(t *testing.T) {
|
||||
var uploaded inbox.Record
|
||||
stubInbox(t,
|
||||
func(rec inbox.Record, cfg config.Config) error {
|
||||
uploaded = rec
|
||||
return nil
|
||||
},
|
||||
func(rec inbox.Record) error {
|
||||
t.Fatalf("unexpected fallback append: %+v", rec)
|
||||
return nil
|
||||
},
|
||||
)
|
||||
|
||||
cfg := config.Default()
|
||||
var out bytes.Buffer
|
||||
if err := RunClaude(strings.NewReader(`{"stop_hook_active":false}`), cfg, "stop", &out); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if uploaded.Agent != "Claude" || uploaded.Event != "stop" || uploaded.Title == "" {
|
||||
t.Fatalf("unexpected upload record: %+v", uploaded)
|
||||
}
|
||||
}
|
||||
|
||||
func stubInbox(t *testing.T, upload func(inbox.Record, config.Config) error, appendLocal func(inbox.Record) error) {
|
||||
t.Helper()
|
||||
prevUpload := uploadInboxRecord
|
||||
prevAppend := appendInboxRecord
|
||||
uploadInboxRecord = upload
|
||||
appendInboxRecord = appendLocal
|
||||
t.Cleanup(func() {
|
||||
uploadInboxRecord = prevUpload
|
||||
appendInboxRecord = prevAppend
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/longbin/agent-notify/internal/config"
|
||||
"github.com/longbin/agent-notify/internal/inbox"
|
||||
"github.com/longbin/agent-notify/internal/logx"
|
||||
)
|
||||
|
||||
var (
|
||||
uploadInboxRecord = defaultUploadInboxRecord
|
||||
appendInboxRecord = func(rec inbox.Record) error {
|
||||
return inbox.NewStore("").Append(rec)
|
||||
}
|
||||
)
|
||||
|
||||
func recordInbox(cfg config.Config, agent, event, cwd, title, body string) {
|
||||
if !cfg.Inbox.Enabled {
|
||||
return
|
||||
}
|
||||
rec := inbox.BuildRecord(inbox.BuildInput{
|
||||
Agent: agent,
|
||||
Event: event,
|
||||
CWD: cwd,
|
||||
Title: title,
|
||||
Body: body,
|
||||
Source: inbox.SourceRemote,
|
||||
})
|
||||
if err := uploadInboxRecord(rec, cfg); err != nil {
|
||||
logx.Append("inbox upload failed: %v", err)
|
||||
if !cfg.Inbox.FallbackLocal {
|
||||
return
|
||||
}
|
||||
rec.Source = inbox.SourceFallback
|
||||
if err := appendInboxRecord(rec); err != nil {
|
||||
logx.Append("inbox fallback append failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultUploadInboxRecord(rec inbox.Record, cfg config.Config) error {
|
||||
timeout := time.Duration(cfg.Inbox.TimeoutMS) * time.Millisecond
|
||||
client := inbox.NewClient(inbox.ClientConfig{
|
||||
Socket: cfg.Inbox.RemoteSocket,
|
||||
Addr: cfg.Inbox.Addr,
|
||||
Timeout: timeout,
|
||||
})
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
return client.Upload(ctx, rec)
|
||||
}
|
||||
Reference in New Issue
Block a user