feat: mejorar ProgressReporter con deteccion de pasos del pipeline
El ProgressReporter ahora muestra mensajes legibles cuando detecta comandos conocidos del pipeline de creacion de agentes: - create-full.sh → "📦 Creando agente: scaffold, build, register..." - health-check.sh → "🏥 Verificando health check..." - notify-developer.sh → "📨 Enviando bienvenida a developers..." - restart.sh / start.sh → "🔄 Reiniciando launcher..." - go build → "🔨 Compilando..." - go test → "🧪 Ejecutando tests..." - Edit/Write → "✏️ Editando: <archivo>" - Read → "📖 Leyendo: <archivo>" - Glob/Grep → "🔍 Buscando: <patron>" Incluye contador de pasos visible ("Paso N — <descripcion>") para que el usuario pueda seguir el progreso. Si no reconoce el comando, usa el formato generico anterior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
+81
-14
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -15,16 +16,19 @@ import (
|
|||||||
// emits streaming events (tool_use, text, result).
|
// emits streaming events (tool_use, text, result).
|
||||||
//
|
//
|
||||||
// It rate-limits edits to at most one per second to avoid flooding the
|
// It rate-limits edits to at most one per second to avoid flooding the
|
||||||
// homeserver.
|
// homeserver. When it recognises well-known pipeline commands (e.g.
|
||||||
|
// create-full.sh, health-check.sh) it shows a human-readable step name
|
||||||
|
// instead of the raw command.
|
||||||
type ProgressReporter struct {
|
type ProgressReporter struct {
|
||||||
sender MatrixSender
|
sender MatrixSender
|
||||||
roomID string
|
roomID string
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
eventID string // Matrix event ID of the progress message (empty until first send)
|
eventID string // Matrix event ID of the progress message (empty until first send)
|
||||||
lastEdit time.Time // timestamp of last edit, for rate limiting
|
lastEdit time.Time // timestamp of last edit, for rate limiting
|
||||||
minInterval time.Duration
|
minInterval time.Duration
|
||||||
|
step int // visible step counter (incremented on each tool_use)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProgressReporter creates a ProgressReporter that sends progress updates
|
// NewProgressReporter creates a ProgressReporter that sends progress updates
|
||||||
@@ -53,16 +57,12 @@ func (p *ProgressReporter) handleEvent(evt coretypes.StreamEvent) {
|
|||||||
|
|
||||||
switch evt.Kind {
|
switch evt.Kind {
|
||||||
case coretypes.StreamToolUse:
|
case coretypes.StreamToolUse:
|
||||||
// Show which tool is being used
|
p.mu.Lock()
|
||||||
input := evt.ToolInput
|
p.step++
|
||||||
if len(input) > 60 {
|
step := p.step
|
||||||
input = input[:57] + "..."
|
p.mu.Unlock()
|
||||||
}
|
|
||||||
if input != "" {
|
markdown = formatToolEvent(step, evt.ToolName, evt.ToolInput)
|
||||||
markdown = fmt.Sprintf("\U0001f527 *%s*: `%s`", evt.ToolName, input)
|
|
||||||
} else {
|
|
||||||
markdown = fmt.Sprintf("\U0001f527 *%s*", evt.ToolName)
|
|
||||||
}
|
|
||||||
|
|
||||||
case coretypes.StreamResult:
|
case coretypes.StreamResult:
|
||||||
// Final result — no need to update progress; the handler will send the actual reply
|
// Final result — no need to update progress; the handler will send the actual reply
|
||||||
@@ -86,6 +86,73 @@ func (p *ProgressReporter) handleEvent(evt coretypes.StreamEvent) {
|
|||||||
p.updateMessage(markdown)
|
p.updateMessage(markdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pipelineHint describes a well-known command pattern and its human-readable label.
|
||||||
|
type pipelineHint struct {
|
||||||
|
substr string // substring to match in the tool input
|
||||||
|
emoji string
|
||||||
|
label string
|
||||||
|
}
|
||||||
|
|
||||||
|
// pipelineHints maps well-known pipeline commands to friendly labels.
|
||||||
|
// Order matters: first match wins.
|
||||||
|
var pipelineHints = []pipelineHint{
|
||||||
|
{"create-full.sh", "\U0001f4e6", "Creando agente: scaffold, build, register, E2EE, avatar..."},
|
||||||
|
{"health-check.sh", "\U0001f3e5", "Verificando health check..."},
|
||||||
|
{"notify-developer.sh", "\U0001f4e8", "Enviando bienvenida a developers..."},
|
||||||
|
{"restart.sh", "\U0001f504", "Reiniciando launcher..."},
|
||||||
|
{"start.sh", "\U0001f504", "Arrancando launcher..."},
|
||||||
|
{"go build", "\U0001f528", "Compilando..."},
|
||||||
|
{"go test", "\U0001f9ea", "Ejecutando tests..."},
|
||||||
|
}
|
||||||
|
|
||||||
|
// formatToolEvent returns a human-readable markdown line for a streaming tool event.
|
||||||
|
// If the tool/input matches a well-known pipeline pattern, a friendly label is shown;
|
||||||
|
// otherwise falls back to a generic format.
|
||||||
|
func formatToolEvent(step int, toolName, toolInput string) string {
|
||||||
|
prefix := fmt.Sprintf("**Paso %d** \u2014 ", step)
|
||||||
|
|
||||||
|
// Check pipeline hints for Bash commands
|
||||||
|
if toolName == "Bash" {
|
||||||
|
for _, h := range pipelineHints {
|
||||||
|
if strings.Contains(toolInput, h.substr) {
|
||||||
|
return prefix + h.emoji + " " + h.label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Generic Bash command
|
||||||
|
input := truncateInput(toolInput, 50)
|
||||||
|
return prefix + "\U0001f527 `" + input + "`"
|
||||||
|
}
|
||||||
|
|
||||||
|
// File operation tools with agent path detection
|
||||||
|
if toolName == "Edit" || toolName == "Write" {
|
||||||
|
file := truncateInput(toolInput, 60)
|
||||||
|
return prefix + "\u270f\ufe0f Editando: `" + file + "`"
|
||||||
|
}
|
||||||
|
if toolName == "Read" {
|
||||||
|
file := truncateInput(toolInput, 60)
|
||||||
|
return prefix + "\U0001f4d6 Leyendo: `" + file + "`"
|
||||||
|
}
|
||||||
|
if toolName == "Glob" || toolName == "Grep" {
|
||||||
|
input := truncateInput(toolInput, 50)
|
||||||
|
return prefix + "\U0001f50d Buscando: `" + input + "`"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic fallback
|
||||||
|
if toolInput != "" {
|
||||||
|
input := truncateInput(toolInput, 50)
|
||||||
|
return prefix + "\U0001f527 *" + toolName + "*: `" + input + "`"
|
||||||
|
}
|
||||||
|
return prefix + "\U0001f527 *" + toolName + "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncateInput shortens a string for display, appending "..." if truncated.
|
||||||
|
func truncateInput(s string, maxLen int) string {
|
||||||
|
if len(s) <= maxLen {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[:maxLen-3] + "..."
|
||||||
|
}
|
||||||
|
|
||||||
// updateMessage sends or edits the progress message, respecting rate limits.
|
// updateMessage sends or edits the progress message, respecting rate limits.
|
||||||
func (p *ProgressReporter) updateMessage(markdown string) {
|
func (p *ProgressReporter) updateMessage(markdown string) {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
|
|||||||
Reference in New Issue
Block a user