Export diagrams as interactive HTML from the app and an agent tool
This commit is contained in:
@@ -44,7 +44,50 @@ func (m *Manager) renderDiagramTool() mcp.Tool {
|
||||
}
|
||||
}
|
||||
|
||||
func renderDiagram(ctx context.Context, source string) ([]byte, error) {
|
||||
func (m *Manager) exportDiagramTool(l *live) mcp.Tool {
|
||||
return mcp.Tool{
|
||||
Name: "export_diagram",
|
||||
Description: "Export a diagram as a self-contained HTML page, drawn exactly as byre's app lays it out and as interactive as its full-size view: clicking a box highlights it, its arrows and its neighbors, each highlight line is a button, and it zooms and pans. " +
|
||||
"Pass the body of a ```diagram block and the file to write; show it with show_page.",
|
||||
InputSchema: json.RawMessage(`{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source": {"type": "string", "description": "The block's body, without the fence lines."},
|
||||
"path": {"type": "string", "description": "The HTML file to write, relative to your worktree, absolute, or starting with ~/."}
|
||||
},
|
||||
"required": ["source", "path"]
|
||||
}`),
|
||||
Handler: func(ctx context.Context, raw json.RawMessage) (string, error) {
|
||||
args := struct {
|
||||
Source string `json:"source"`
|
||||
Path string `json:"path"`
|
||||
}{}
|
||||
err := json.Unmarshal(raw, &args)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("malformed export_diagram call: %w", err)
|
||||
}
|
||||
full, err := l.resolve(args.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html, err := renderDiagram(ctx, args.Source, "--html")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.MkdirAll(filepath.Dir(full), 0o755)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
err = os.WriteFile(full, html, 0o644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("Wrote the diagram to %s.", args.Path), nil
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func renderDiagram(ctx context.Context, source string, flags ...string) ([]byte, error) {
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -52,7 +95,7 @@ func renderDiagram(ctx context.Context, source string) ([]byte, error) {
|
||||
app := filepath.Join(filepath.Dir(exe), "..", "MacOS", "Byre")
|
||||
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, app, "render-diagram")
|
||||
cmd := exec.CommandContext(ctx, app, append([]string{"render-diagram"}, flags...)...)
|
||||
cmd.Stdin = strings.NewReader(source)
|
||||
stderr := bytes.Buffer{}
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
@@ -33,3 +33,5 @@ highlight "Event loop" store server coord
|
||||
Keep each box to a title and one or two short detail lines, and draw only the arrows the reader needs; a picture with every dependency in it reads worse than prose.
|
||||
|
||||
Check a diagram before you send it: `render_diagram` takes the block's body and returns a PNG of what the app draws, or the reason it can't. Look for crossing or overlapping arrows and labels sitting on top of each other, and adjust the rows, `aside` or arrows until it reads cleanly.
|
||||
|
||||
To hand a diagram over as a file, `export_diagram` writes it as one self-contained HTML page with the app's exact layout and its full-size view's interactivity (click to highlight, highlight buttons, zoom and pan, light and dark mode); show it with `show_page`. The full-size view in the app has the same export as its Export HTML button.
|
||||
|
||||
@@ -867,7 +867,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tools := append([]mcp.Tool{m.askTool(l), m.cancelQuestionTool(l), m.setTitlesTool(l), m.stopMergeTool(l), m.mergeToMainTool(l), m.setTaskSummaryTool(l), m.searchClosedTool(l), m.exposePortTool(l), m.takeSnapshotTool(l), m.terminalReadTool(l), m.terminalTypeTool(l), m.bashTool(l), m.readKnowledgeTool(), m.renderDiagramTool()}, append(append(m.fileTools(l), m.subtaskTools(l)...), m.clientTools(l)...)...)
|
||||
tools := append([]mcp.Tool{m.askTool(l), m.cancelQuestionTool(l), m.setTitlesTool(l), m.stopMergeTool(l), m.mergeToMainTool(l), m.setTaskSummaryTool(l), m.searchClosedTool(l), m.exposePortTool(l), m.takeSnapshotTool(l), m.terminalReadTool(l), m.terminalTypeTool(l), m.bashTool(l), m.readKnowledgeTool(), m.renderDiagramTool(), m.exportDiagramTool(l)}, append(append(m.fileTools(l), m.subtaskTools(l)...), m.clientTools(l)...)...)
|
||||
lead := true
|
||||
if sess.Role == api.Implementer {
|
||||
g += longTitleGuidance(task)
|
||||
@@ -937,7 +937,7 @@ func (m *Manager) startLive(ctx context.Context, sess api.AgentSession, task api
|
||||
return nil, err
|
||||
}
|
||||
l.notes = "\n\n" + notes
|
||||
tools = append(append(m.leadTools(l), m.readKnowledgeTool(), m.renderDiagramTool()), m.clientShellTools()...)
|
||||
tools = append(append(m.leadTools(l), m.readKnowledgeTool(), m.renderDiagramTool(), m.exportDiagramTool(l)), m.clientShellTools()...)
|
||||
}
|
||||
env := git.IdentityEnv(l.gitName, l.gitEmail)
|
||||
servers := []*mcp.Server{{Name: "byre", Version: "0.0.0", Tools: tools}}
|
||||
|
||||
Reference in New Issue
Block a user