feat: wire hook dispatcher in main.go — SessionStart, SessionEnd, PreCompact
This commit is contained in:
@@ -15,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"somegit.dev/Owlibou/gnoma/internal/engine"
|
"somegit.dev/Owlibou/gnoma/internal/engine"
|
||||||
|
"somegit.dev/Owlibou/gnoma/internal/hook"
|
||||||
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
|
"somegit.dev/Owlibou/gnoma/internal/tool/persist"
|
||||||
gnomacfg "somegit.dev/Owlibou/gnoma/internal/config"
|
gnomacfg "somegit.dev/Owlibou/gnoma/internal/config"
|
||||||
gnomactx "somegit.dev/Owlibou/gnoma/internal/context"
|
gnomactx "somegit.dev/Owlibou/gnoma/internal/context"
|
||||||
@@ -344,6 +345,29 @@ func main() {
|
|||||||
reg.Register(agent.NewListResultsTool(store))
|
reg.Register(agent.NewListResultsTool(store))
|
||||||
reg.Register(agent.NewReadResultTool(store))
|
reg.Register(agent.NewReadResultTool(store))
|
||||||
|
|
||||||
|
// Build hook dispatcher from config.
|
||||||
|
// Streamer adapter wraps the router for prompt hooks.
|
||||||
|
// ElfSpawnFn closure wraps elfMgr for agent hooks.
|
||||||
|
hookDefs, err := hook.ParseHookDefs(cfg.Hooks)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "hook config error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
hookStreamer := &routerStreamer{router: rtr}
|
||||||
|
hookSpawnFn := hook.ElfSpawnFn(func(ctx context.Context, prompt string) (string, error) {
|
||||||
|
e, spawnErr := elfMgr.Spawn(ctx, router.TaskReview, prompt, "", 5)
|
||||||
|
if spawnErr != nil {
|
||||||
|
return "", spawnErr
|
||||||
|
}
|
||||||
|
result := e.Wait()
|
||||||
|
return result.Output, result.Error
|
||||||
|
})
|
||||||
|
dispatcher, err := hook.NewDispatcher(hookDefs, hookStreamer, hookSpawnFn, logger)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "hook dispatcher error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// Build system prompt with cwd + compact inventory summary
|
// Build system prompt with cwd + compact inventory summary
|
||||||
systemPrompt := *system
|
systemPrompt := *system
|
||||||
if cwd, err := os.Getwd(); err == nil {
|
if cwd, err := os.Getwd(); err == nil {
|
||||||
@@ -385,6 +409,9 @@ func main() {
|
|||||||
Strategy: compactStrategy,
|
Strategy: compactStrategy,
|
||||||
PrefixMessages: prefixMsgs,
|
PrefixMessages: prefixMsgs,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
|
OnPreCompact: func(msgs []message.Message) {
|
||||||
|
dispatcher.Fire(hook.PreCompact, hook.MarshalPreCompactPayload(len(msgs), 0)) //nolint:errcheck
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wire tokenizer and seed tracker with prefix cost
|
// Wire tokenizer and seed tracker with prefix cost
|
||||||
@@ -408,6 +435,7 @@ func main() {
|
|||||||
Model: *model,
|
Model: *model,
|
||||||
MaxTurns: *maxTurns,
|
MaxTurns: *maxTurns,
|
||||||
Store: store,
|
Store: store,
|
||||||
|
Hooks: dispatcher,
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -466,6 +494,14 @@ func main() {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fire SessionStart / SessionEnd lifecycle hooks.
|
||||||
|
mode := "tui"
|
||||||
|
if input != "" {
|
||||||
|
mode = "pipe"
|
||||||
|
}
|
||||||
|
dispatcher.Fire(hook.SessionStart, hook.MarshalSessionStartPayload(sessionID, mode)) //nolint:errcheck
|
||||||
|
defer dispatcher.Fire(hook.SessionEnd, hook.MarshalSessionEndPayload(sessionID, 0)) //nolint:errcheck
|
||||||
|
|
||||||
if input != "" {
|
if input != "" {
|
||||||
// Pipe mode: single input → stream to stdout
|
// Pipe mode: single input → stream to stdout
|
||||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||||
@@ -704,6 +740,25 @@ func limitedProvider(p provider.Provider, provName, modelName string, cfg *gnoma
|
|||||||
return provider.WithConcurrency(p, rl.MaxConcurrent())
|
return provider.WithConcurrency(p, rl.MaxConcurrent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// routerStreamer adapts *router.Router to the hook.Streamer interface.
|
||||||
|
// PromptExecutor needs only a simple Stream(ctx, prompt) call; this adapter
|
||||||
|
// wraps the full router.Stream signature, using TaskReview for hook evaluation.
|
||||||
|
type routerStreamer struct {
|
||||||
|
router *router.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *routerStreamer) Stream(ctx context.Context, prompt string) (stream.Stream, error) {
|
||||||
|
req := provider.Request{
|
||||||
|
Messages: []message.Message{message.NewUserText(prompt)},
|
||||||
|
}
|
||||||
|
s, decision, err := rs.router.Stream(ctx, router.Task{Type: router.TaskReview}, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
decision.Commit(0)
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
const defaultSystem = `You are gnoma, a provider-agnostic agentic coding assistant.
|
const defaultSystem = `You are gnoma, a provider-agnostic agentic coding assistant.
|
||||||
You help users with software engineering tasks by reading files, writing code, and executing commands.
|
You help users with software engineering tasks by reading files, writing code, and executing commands.
|
||||||
Be concise and direct. Use tools when needed to accomplish the task.
|
Be concise and direct. Use tools when needed to accomplish the task.
|
||||||
|
|||||||
Reference in New Issue
Block a user