Add a Captures tab showing screen captures from the task's agent

This commit is contained in:
Ian Gulliver
2026-10-22 20:53:28 -07:00
parent de9c9a684c
commit 5fc8a88a82
5 changed files with 140 additions and 12 deletions
+40 -10
View File
@@ -169,8 +169,11 @@ func (m *Manager) codexItemCompleted(ctx context.Context, l *live, s *codex.Sess
return m.emit(ctx, l, agentActor, "agent.vendor", payload)
}
content, isError := codexToolResult(item)
err := m.emit(ctx, l, agentActor, "agent.tool_result", map[string]any{"tool_use_id": item.ID, "is_error": isError, "content": content})
content, isError, err := codexToolResult(item)
if err != nil {
return err
}
err = m.emit(ctx, l, agentActor, "agent.tool_result", map[string]any{"tool_use_id": item.ID, "is_error": isError, "content": content})
if err != nil {
return err
}
@@ -210,24 +213,51 @@ func imageAlt(prompt string) string {
return alt
}
func codexToolResult(item codex.ThreadItem) (string, bool) {
func codexToolResult(item codex.ThreadItem) (any, bool, error) {
status := codexStatus(item)
switch item.Type {
case codex.ThreadItemTypeCommandExecution:
return item.AggregatedOutput, status != "completed" || item.ExitCode != 0
return item.AggregatedOutput, status != "completed" || item.ExitCode != 0, nil
case codex.ThreadItemTypeDynamicToolCall:
texts := []string{}
blocks := []map[string]any{}
for _, c := range item.ContentItems {
texts = append(texts, c.Text)
if c.Type != codex.DynamicToolCallOutputContentItemTypeInputImage {
blocks = append(blocks, map[string]any{"type": "text", "text": c.Text})
continue
}
mime, data, ok := strings.Cut(strings.TrimPrefix(c.ImageURL, "data:"), ";base64,")
if !ok {
return nil, false, fmt.Errorf("dynamic tool image is not a base64 data url")
}
blocks = append(blocks, imageBlock(mime, data))
}
return strings.Join(texts, "\n"), !item.Success
return blocks, !item.Success, nil
case codex.ThreadItemTypeMcpToolCall:
if item.Error != nil {
return item.Error.Message, true
return item.Error.Message, true, nil
}
return string(item.Result), false
res := struct {
Content []map[string]any `json:"content"`
IsError bool `json:"isError"`
}{}
err := json.Unmarshal(item.Result, &res)
if err != nil {
return nil, false, fmt.Errorf("decode mcp tool result: %w", err)
}
for i, b := range res.Content {
if b["type"] == "image" {
mime, _ := b["mimeType"].(string)
data, _ := b["data"].(string)
res.Content[i] = imageBlock(mime, data)
}
}
return res.Content, res.IsError, nil
}
return status, status != "completed"
return status, status != "completed", nil
}
func imageBlock(mime, data string) map[string]any {
return map[string]any{"type": "image", "source": map[string]any{"type": "base64", "media_type": mime, "data": data}}
}
func (m *Manager) readCodexRateLimit() {