Add Claude Code stream-json client, report gate, and terminal driver
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"g.fc.run/misc/byre/internal/mcp"
|
||||
)
|
||||
|
||||
const maxNudges = 3
|
||||
|
||||
const nudge = "You must call the report tool (mcp__byre__report) before ending your turn."
|
||||
|
||||
type Report struct {
|
||||
Done []string `json:"done"`
|
||||
OpenQuestions []string `json:"open_questions"`
|
||||
NextSteps []string `json:"next_steps"`
|
||||
Risks []string `json:"risks"`
|
||||
Deviations []string `json:"deviations"`
|
||||
}
|
||||
|
||||
type Gate struct {
|
||||
OnReport func(Report)
|
||||
|
||||
mu sync.Mutex
|
||||
reported bool
|
||||
nudges int
|
||||
}
|
||||
|
||||
func (g *Gate) Reset() {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.reported = false
|
||||
g.nudges = 0
|
||||
}
|
||||
|
||||
func (g *Gate) Nudges() int {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
return g.nudges
|
||||
}
|
||||
|
||||
func (g *Gate) Tool() mcp.Tool {
|
||||
return mcp.Tool{
|
||||
Name: "report",
|
||||
Description: "Report what you did this turn. You must call this before ending every turn. List every departure from the plan in deviations.",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"done": {"type": "array", "items": {"type": "string"}},
|
||||
"open_questions": {"type": "array", "items": {"type": "string"}},
|
||||
"next_steps": {"type": "array", "items": {"type": "string"}},
|
||||
"risks": {"type": "array", "items": {"type": "string"}},
|
||||
"deviations": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
"required": ["done"]
|
||||
}`),
|
||||
Handler: g.handle,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Gate) handle(ctx context.Context, args json.RawMessage) (string, error) {
|
||||
r := Report{}
|
||||
err := json.Unmarshal(args, &r)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("malformed report: %w", err)
|
||||
}
|
||||
|
||||
if len(r.Done) == 0 {
|
||||
return "", fmt.Errorf("report must list at least one item in done")
|
||||
}
|
||||
|
||||
g.mu.Lock()
|
||||
g.reported = true
|
||||
g.mu.Unlock()
|
||||
|
||||
if g.OnReport != nil {
|
||||
g.OnReport(r)
|
||||
}
|
||||
|
||||
return "report filed", nil
|
||||
}
|
||||
|
||||
func (g *Gate) StopHook(ctx context.Context, input json.RawMessage) (map[string]any, error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if g.reported || g.nudges >= maxNudges {
|
||||
return map[string]any{}, nil
|
||||
}
|
||||
|
||||
g.nudges++
|
||||
return map[string]any{"decision": "block", "reason": nudge}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user