Move questions and reviews into the transcript; agents answer findings

This commit is contained in:
Ian Gulliver
2026-10-19 09:14:21 -07:00
parent eb7e0a13c4
commit 27ff0f457b
21 changed files with 903 additions and 801 deletions
+68 -3
View File
@@ -364,9 +364,11 @@ func findingsMessage(r api.Review) string {
res = *r.Result
}
b := &strings.Builder{}
fmt.Fprintf(b, "A reviewer looked at snapshot #%d of your work and raised the findings below. Reviewers can be wrong or miss context: check each one against the code and your brief, act on the ones that hold up, and in your final message say briefly which you acted on and which you're leaving and why.", r.SnapshotN)
fmt.Fprintf(b, "A reviewer looked at snapshot #%d of your work and raised the findings below. Reviewers can be wrong or miss context: check each one against the code and your brief. "+
"Before you change anything, answer each finding with respond_to_finding (snapshot %d and the finding's number): fixing if it holds up and you'll act on it, ignoring with your reason if you're leaving it. "+
"The human sees your answers on the findings and can overrule an ignore.", r.SnapshotN, r.SnapshotN)
findings, _ := res["findings"].([]any)
for _, f := range findings {
for i, f := range findings {
fm, _ := f.(map[string]any)
where := ""
if p, ok := fm["path"].(string); ok && p != "" {
@@ -376,11 +378,74 @@ func findingsMessage(r api.Review) string {
}
where = " (`" + where + "`)"
}
fmt.Fprintf(b, "\n\n**[%v] %v**%s\n\n%s", fm["severity"], fm["title"], where, strings.TrimSpace(fmt.Sprint(fm["detail"])))
fmt.Fprintf(b, "\n\n**Finding %d: [%v] %v**%s\n\n%s", i+1, fm["severity"], fm["title"], where, strings.TrimSpace(fmt.Sprint(fm["detail"])))
}
return b.String()
}
type findingResponseArgs struct {
Snapshot int `json:"snapshot"`
Finding int `json:"finding"`
Response api.FindingResponseResponse `json:"response"`
Reason string `json:"reason"`
}
func (m *Manager) respondToFindingTool(l *live) mcp.Tool {
return mcp.Tool{
Name: "respond_to_finding",
Description: "Answer one finding of a review of your work, as soon as you've decided what to do about it: fixing when you'll act on it, ignoring when you're leaving it. " +
"The human sees your answer on the finding; an ignore keeps the human's fix button, so they can overrule you. Call it again to change your answer.",
InputSchema: json.RawMessage(`{
"type": "object",
"properties": {
"snapshot": {"type": "integer", "description": "The reviewed snapshot's number."},
"finding": {"type": "integer", "description": "The finding's number in the review, from 1."},
"response": {"type": "string", "enum": ["fixing", "ignoring"]},
"reason": {"type": "string", "description": "One line: what you'll do about it, or why you're leaving it."}
},
"required": ["snapshot", "finding", "response", "reason"]
}`),
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
args := findingResponseArgs{}
err := json.Unmarshal(raw, &args)
if err != nil {
return "", fmt.Errorf("malformed respond_to_finding call: %w", err)
}
if !args.Response.Valid() {
return "", fmt.Errorf("%w: response must be fixing or ignoring", ErrInvalid)
}
if args.Finding < 1 {
return "", fmt.Errorf("%w: finding numbers start at 1", ErrInvalid)
}
reason := strings.TrimSpace(args.Reason)
if reason == "" {
return "", fmt.Errorf("%w: reason must not be empty", ErrInvalid)
}
reviews, err := m.store.ListReviews(ctx, l.taskID)
if err != nil {
return "", err
}
reviewID := ""
for _, r := range reviews {
if r.SnapshotN == args.Snapshot && r.State == api.ReviewStateDone {
reviewID = r.Id
}
}
if reviewID == "" {
return "", fmt.Errorf("%w: snapshot %d has no finished review", ErrInvalid, args.Snapshot)
}
err = m.store.SetFindingResponse(ctx, reviewID, args.Finding-1, api.FindingResponse{Response: args.Response, Reason: reason}, agentActor)
if errors.Is(err, store.ErrNotFound) {
return "", fmt.Errorf("%w: the review of snapshot %d has no finding %d", ErrInvalid, args.Snapshot, args.Finding)
}
if err != nil {
return "", err
}
return fmt.Sprintf("Recorded: %s finding %d of snapshot %d's review.", args.Response, args.Finding, args.Snapshot), nil
},
}
}
type reviewImage struct {
entry map[string]any
contentType string