Bold questions

This commit is contained in:
Ian Gulliver
2026-10-14 19:35:34 -07:00
parent ab4c3d2c6f
commit 1ea055682b
54 changed files with 3696 additions and 762 deletions
+30 -1
View File
@@ -34,6 +34,7 @@ type Options struct {
ForkSession bool
GitName string
GitEmail string
Guidance string
Hooks map[string][]HookMatcher
MCPServers []*mcp.Server
Transcript io.Writer
@@ -127,6 +128,27 @@ func (e Event) Blocks() ([]Block, error) {
return blocks, nil
}
func (e Event) TextDelta() (string, error) {
msg := struct {
ParentToolUseID *string `json:"parent_tool_use_id"`
Event struct {
Type string `json:"type"`
Delta struct {
Type string `json:"type"`
Text string `json:"text"`
} `json:"delta"`
} `json:"event"`
}{}
err := json.Unmarshal(e.Raw, &msg)
if err != nil {
return "", err
}
if msg.ParentToolUseID != nil || msg.Event.Type != "content_block_delta" || msg.Event.Delta.Type != "text_delta" {
return "", nil
}
return msg.Event.Delta.Text, nil
}
func (e Event) Result() (Result, error) {
res := Result{}
err := json.Unmarshal(e.Raw, &res)
@@ -250,6 +272,7 @@ func (s *Session) buildArgs(opts Options) ([]string, error) {
"--output-format", "stream-json",
"--input-format", "stream-json",
"--verbose",
"--include-partial-messages",
"--permission-mode", "bypassPermissions",
"--allow-dangerously-skip-permissions",
"--settings", string(settings),
@@ -260,6 +283,10 @@ func (s *Session) buildArgs(opts Options) ([]string, error) {
args = append(args, "--model", opts.Model)
}
if opts.Guidance != "" {
args = append(args, "--append-system-prompt", opts.Guidance)
}
if opts.SessionID != "" {
args = append(args, "--session-id="+opts.SessionID)
}
@@ -464,7 +491,9 @@ func (s *Session) handleLine(line []byte) {
if env.Type == "control_response" {
s.learnScrub(env.Response)
}
s.record("recv", line)
if env.Type != "stream_event" {
s.record("recv", line)
}
s.writeMu.Unlock()
switch env.Type {
+46 -14
View File
@@ -14,7 +14,6 @@ import (
"g.fc.run/misc/byre/internal/claude"
"g.fc.run/misc/byre/internal/mcp"
"g.fc.run/misc/byre/internal/report"
)
const (
@@ -23,25 +22,30 @@ const (
gitEmail = "test@byre.invalid"
)
func TestCommitIdentityHooksAndReport(t *testing.T) {
func TestCommitIdentityHooksAndMCP(t *testing.T) {
t.Parallel()
ctx := testContext(t)
repo := scratchRepo(t)
reports := []report.Report{}
finished := 0
mu := sync.Mutex{}
gate := &report.Gate{OnReport: func(r report.Report) {
mu.Lock()
defer mu.Unlock()
reports = append(reports, r)
}}
finish := mcp.Tool{
Name: "finish",
Description: "Call this once the task is complete.",
InputSchema: json.RawMessage(`{"type": "object", "properties": {}}`),
Handler: func(ctx context.Context, args json.RawMessage) (string, error) {
mu.Lock()
defer mu.Unlock()
finished++
return "noted", nil
},
}
tools := []string{}
s := start(t, ctx, claude.Options{
Cwd: repo,
MCPServers: []*mcp.Server{{Name: "byre", Version: "0.0.0", Tools: []mcp.Tool{gate.Tool()}}},
MCPServers: []*mcp.Server{{Name: "byre", Version: "0.0.0", Tools: []mcp.Tool{finish}}},
Hooks: map[string][]claude.HookMatcher{
"Stop": {{Func: gate.StopHook}},
"PostToolUse": {{Func: func(ctx context.Context, input json.RawMessage) (map[string]any, error) {
in := struct {
ToolName string `json:"tool_name"`
@@ -58,15 +62,15 @@ func TestCommitIdentityHooksAndReport(t *testing.T) {
},
})
send(t, s, `Create a file hello.txt containing exactly the word hi, then git add and git commit it with the message "add hello".`)
send(t, s, `Create a file hello.txt containing exactly the word hi, then git add and git commit it with the message "add hello", then call the finish tool.`)
waitResult(t, ctx, s, nil)
mu.Lock()
defer mu.Unlock()
t.Logf("tools=%v reports=%d nudges=%d", tools, len(reports), gate.Nudges())
t.Logf("tools=%v finished=%d", tools, finished)
if len(reports) == 0 {
t.Fatalf("report was never called")
if finished == 0 {
t.Fatalf("the MCP finish tool was never called")
}
if len(tools) == 0 {
t.Fatalf("PostToolUse hook never fired")
@@ -118,6 +122,34 @@ func TestStopHookBlocks(t *testing.T) {
}
}
func TestTextDeltas(t *testing.T) {
t.Parallel()
ctx := testContext(t)
s := start(t, ctx, claude.Options{Cwd: scratchRepo(t)})
send(t, s, "Without using any tools, count from one to twenty in words, separated by spaces.")
deltas := []string{}
text := waitResult(t, ctx, s, func(ev claude.Event) {
if ev.Type != "stream_event" {
return
}
d, err := ev.TextDelta()
if err != nil {
t.Fatalf("parse stream event: %v", err)
}
if d != "" {
deltas = append(deltas, d)
}
})
if len(deltas) < 2 {
t.Fatalf("expected several text deltas, got %d", len(deltas))
}
if strings.Join(deltas, "") != text {
t.Fatalf("deltas %q don't add up to the final text %q", deltas, text)
}
}
func TestInterrupt(t *testing.T) {
t.Parallel()
ctx := testContext(t)
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long