From fe742bea59903b13f23e45f9323978d36f59c98c Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Fri, 10 Apr 2026 23:13:23 +0000 Subject: [PATCH 1/5] =?UTF-8?q?docs:=20crear=20issue=200046=20=E2=80=94=20?= =?UTF-8?q?progreso=20en=20tiempo=20real=20para=20Father=20Bot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue para que Father Bot muestre feedback visual durante la creacion de agentes. Incluye: habilitar streaming en config, mejorar formateo del ProgressReporter con nombres de pasos legibles, y tests. Tambien habilita streaming + show_tool_progress en el config de Father Bot (quick win que activa el ProgressReporter existente). Co-Authored-By: Claude Opus 4.6 (1M context) --- agents/_specials/father-bot/config.yaml | 2 + dev/issues/0046-father-bot-progress.md | 126 ++++++++++++++++++++++++ dev/issues/README.md | 1 + 3 files changed, 129 insertions(+) create mode 100644 dev/issues/0046-father-bot-progress.md diff --git a/agents/_specials/father-bot/config.yaml b/agents/_specials/father-bot/config.yaml index cbb8177..f2ba0e5 100644 --- a/agents/_specials/father-bot/config.yaml +++ b/agents/_specials/father-bot/config.yaml @@ -82,6 +82,8 @@ llm: model: "sonnet" fallback_model: "haiku" session_id: "" + streaming: true + show_tool_progress: true add_dirs: - ".claude/rules" - "agents/_template" diff --git a/dev/issues/0046-father-bot-progress.md b/dev/issues/0046-father-bot-progress.md new file mode 100644 index 0000000..3f716c2 --- /dev/null +++ b/dev/issues/0046-father-bot-progress.md @@ -0,0 +1,126 @@ +# 0046 β€” Progreso en tiempo real para Father Bot + +## Objetivo + +Cuando Father Bot crea un agente o robot, el usuario no recibe ningun feedback visual +durante 2-3 minutos (a veces mas). El ProgressReporter ya existe y funciona para agentes +con `claude-code` streaming, pero esta **deshabilitado** en el config de Father Bot. +Ademas, los mensajes de progreso actuales son crΓ­pticos ("πŸ”§ *Bash*: `./dev-scripts/...`"). + +Queremos que el usuario vea en tiempo real en que paso del pipeline se encuentra Father Bot, +con mensajes legibles como "πŸ“¦ Scaffold + Build + Register..." en vez de comandos raw. + +## Contexto + +### Lo que ya existe + +- `shell/effects/progress.go` β€” `ProgressReporter` que envia/edita un mensaje Matrix con cada + evento de streaming. Rate-limited a 1 edit/segundo. +- `devagents/handler.go:388` β€” `isStreamingEnabled()` verifica `provider == "claude-code"` + + `Streaming` + `ShowToolProgress`. Actualmente **ambos flags estan ausentes** en el config + de Father Bot β†’ streaming deshabilitado. +- `shell/llm/claudecode.go` β€” el provider ya soporta `--output-format stream-json` y parsea + eventos `StreamToolUse`, `StreamInit`, `StreamText`, `StreamResult`. + +### Evidencia en logs + +- `logs/father-bot/2026-04-09.jsonl`: respuesta de 160.7s, 25 turns, $0.50 β€” **silencio total** + para el usuario durante todo ese tiempo. +- `logs/father-bot/2026-04-10.jsonl`: proceso claude-code killed por timeout (SIGKILL), + el usuario no supo que paso hasta recibir un error generico. + +## Arquitectura + +``` +shell/effects/progress.go β€” MODIFICAR: mejorar formateo de tool events +agents/_specials/father-bot/ + config.yaml β€” MODIFICAR: habilitar streaming + show_tool_progress + prompts/system.md β€” MODIFICAR: mencionar que progreso es visible +``` + +No hay cambios en `pkg/` (puro). Todo es shell/config. + +## Tareas + +### Fase 1: Habilitar streaming (quick win) + +- [ ] 1.1 Agregar `streaming: true` y `show_tool_progress: true` en la seccion + `claude_code` del config de Father Bot + +### Fase 2: Mejorar formateo del ProgressReporter + +- [ ] 2.1 Agregar logica en `ProgressReporter.handleEvent()` para detectar patrones + conocidos en tool inputs y mostrar nombres legibles: + - `create-full.sh` β†’ "πŸ“¦ Creando agente: scaffold, build, register, E2EE, avatar..." + - `health-check.sh` β†’ "πŸ₯ Verificando health check..." + - `restart.sh` / `start.sh` β†’ "πŸ”„ Reiniciando launcher..." + - `notify-developer.sh` β†’ "πŸ“¨ Enviando bienvenida a developers..." + - `go build` β†’ "πŸ”¨ Compilando..." + - `Edit` / `Write` sobre `agents/` β†’ "✏️ Personalizando: " + - `Read` sobre `agents/` β†’ "πŸ“– Leyendo: " + - Tool generico β†’ formato actual (πŸ”§ *tool*: `input`) +- [ ] 2.2 Agregar contador de pasos visibles ("Paso N β€” ") + +### Fase 3: System prompt + +- [ ] 3.1 Actualizar `prompts/system.md` del Father Bot para mencionar que el progreso + es visible en tiempo real y que no necesita enviar mensajes intermedios manuales + +### Fase 4: Tests + +- [ ] 4.1 Tests unitarios para la logica de deteccion de patrones en ProgressReporter +- [ ] 4.2 `go build -tags goolm ./...` compila sin errores + +### Fase 5: Docs + +- [ ] 5.1 Cerrar issue, mover a completed + +## Ejemplo de uso + +Antes (sin progreso): +``` +Usuario: "Crea un bot llamado hora-bot que diga la hora" +Father Bot: [silencio durante 2-3 minutos] +Father Bot: "He creado hora-bot exitosamente..." +``` + +Despues (con progreso): +``` +Usuario: "Crea un bot llamado hora-bot que diga la hora" +Father Bot: βš™οΈ Procesando... +Father Bot: πŸ“¦ Paso 1 β€” Creando agente: scaffold, build, register, E2EE, avatar... +Father Bot: ✏️ Paso 2 β€” Personalizando: config.yaml +Father Bot: ✏️ Paso 3 β€” Personalizando: agent.go +Father Bot: ✏️ Paso 4 β€” Personalizando: prompts/system.md +Father Bot: πŸ”¨ Paso 5 β€” Compilando... +Father Bot: πŸ”„ Paso 6 β€” Reiniciando launcher... +Father Bot: πŸ₯ Paso 7 β€” Verificando health check... +Father Bot: πŸ“¨ Paso 8 β€” Enviando bienvenida a developers... +Father Bot: βœ… Completado +Father Bot: "He creado hora-bot exitosamente..." +``` + +(Todo en un unico mensaje que se edita, no mensajes separados.) + +## Decisiones de diseno + +1. **Un solo mensaje editado**: el ProgressReporter ya usa send+edit. Mantener ese patron + para no spammear el room. +2. **Deteccion por patrones**: los nombres de scripts del pipeline son estables. Usar + string matching en los inputs de tool para detectar el paso actual. +3. **Generico pero con hints**: la logica de patrones vive en el ProgressReporter (generico), + pero los patrones que detecta son especificos del pipeline de creacion. Esto es aceptable + porque el ProgressReporter ya es especifico de claude-code. +4. **No requiere cambios en pkg/**: todo es shell/config, respeta pure core / impure shell. + +## Prerequisitos + +- Issue 0036 (streaming) β€” completado +- Issue 0037 (father-bot) β€” completado + +## Riesgos + +- **Rate limiting de Matrix**: el homeserver podria limitar edits frecuentes. Mitigado por + el rate limit de 1 edit/segundo ya existente en ProgressReporter. +- **Patrones fragiles**: si se renombran los scripts del pipeline, los patrones se rompen. + Mitigado porque los scripts son estables y el fallback es el formato generico actual. diff --git a/dev/issues/README.md b/dev/issues/README.md index e92d542..ede9f0a 100644 --- a/dev/issues/README.md +++ b/dev/issues/README.md @@ -56,3 +56,4 @@ afectados y notas de implementacion. | 43 | Guardrails de seguridad para Father Bot | [0043-father-bot-security-guardrails.md](completed/0043-father-bot-security-guardrails.md) | completado | | 44 | Formalizar pipeline de creacion de agentes | [0044-formalize-agent-creation-pipeline.md](completed/0044-formalize-agent-creation-pipeline.md) | completado | | 45 | DM rooms sin E2EE en notify-developer.sh | [0045-notify-encrypted-rooms.md](completed/0045-notify-encrypted-rooms.md) | completado | +| 46 | Progreso en tiempo real para Father Bot | [0046-father-bot-progress.md](0046-father-bot-progress.md) | pendiente | From deaefb5cd3b2b1747cf80199bd67a84c4505573d Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Fri, 10 Apr 2026 23:14:34 +0000 Subject: [PATCH 2/5] feat: mejorar ProgressReporter con deteccion de pasos del pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: " - Read β†’ "πŸ“– Leyendo: " - Glob/Grep β†’ "πŸ” Buscando: " Incluye contador de pasos visible ("Paso N β€” ") 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) --- shell/effects/progress.go | 95 +++++++++++++++++++++++++++++++++------ 1 file changed, 81 insertions(+), 14 deletions(-) diff --git a/shell/effects/progress.go b/shell/effects/progress.go index b26031c..cbc4ab0 100644 --- a/shell/effects/progress.go +++ b/shell/effects/progress.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "strings" "sync" "time" @@ -15,16 +16,19 @@ import ( // emits streaming events (tool_use, text, result). // // 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 { sender MatrixSender roomID string logger *slog.Logger - mu sync.Mutex - eventID string // Matrix event ID of the progress message (empty until first send) - lastEdit time.Time // timestamp of last edit, for rate limiting + mu sync.Mutex + eventID string // Matrix event ID of the progress message (empty until first send) + lastEdit time.Time // timestamp of last edit, for rate limiting minInterval time.Duration + step int // visible step counter (incremented on each tool_use) } // NewProgressReporter creates a ProgressReporter that sends progress updates @@ -53,16 +57,12 @@ func (p *ProgressReporter) handleEvent(evt coretypes.StreamEvent) { switch evt.Kind { case coretypes.StreamToolUse: - // Show which tool is being used - input := evt.ToolInput - if len(input) > 60 { - input = input[:57] + "..." - } - if input != "" { - markdown = fmt.Sprintf("\U0001f527 *%s*: `%s`", evt.ToolName, input) - } else { - markdown = fmt.Sprintf("\U0001f527 *%s*", evt.ToolName) - } + p.mu.Lock() + p.step++ + step := p.step + p.mu.Unlock() + + markdown = formatToolEvent(step, evt.ToolName, evt.ToolInput) case coretypes.StreamResult: // 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) } +// 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. func (p *ProgressReporter) updateMessage(markdown string) { p.mu.Lock() From a6618eda047d05b3c87bc485057c35f80f3ec8ec Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Fri, 10 Apr 2026 23:14:57 +0000 Subject: [PATCH 3/5] docs: actualizar system prompt de Father Bot con nota de progreso Informa a Father Bot que el sistema muestra automaticamente progreso al usuario en tiempo real, y que no necesita enviar mensajes intermedios manuales. Esto evita duplicacion de feedback. Co-Authored-By: Claude Opus 4.6 (1M context) --- agents/_specials/father-bot/prompts/system.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/agents/_specials/father-bot/prompts/system.md b/agents/_specials/father-bot/prompts/system.md index 7023704..0c494e6 100644 --- a/agents/_specials/father-bot/prompts/system.md +++ b/agents/_specials/father-bot/prompts/system.md @@ -260,6 +260,17 @@ Reintenta hasta 3 veces con backoff si falla. Si `DEVELOPER_MATRIX_USERS` no esta configurado, se salta con warning (no bloquea). +## Progreso en tiempo real + +El sistema muestra automaticamente tus pasos al usuario mientras trabajas. Cada vez que usas una herramienta (Bash, Read, Edit, etc.), el usuario ve un mensaje de progreso que se actualiza en tiempo real en la sala de Matrix (un unico mensaje que se edita, no mensajes separados). + +Por ejemplo, cuando ejecutas `./dev-scripts/agent/create-full.sh`, el usuario ve: +``` +Paso 1 β€” πŸ“¦ Creando agente: scaffold, build, register, E2EE, avatar... +``` + +**No necesitas enviar mensajes intermedios manualmente.** El sistema lo hace por ti. Concentra tu respuesta final en el resumen completo del resultado. + ### Paso 9 β€” Reportar al usuario Confirma al usuario con: From 1d16362ca66cece29c46d95153f50045edaa508f Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Fri, 10 Apr 2026 23:16:52 +0000 Subject: [PATCH 4/5] test: tests para deteccion de pasos del pipeline en ProgressReporter Tests unitarios para formatToolEvent con todos los pipeline hints: create-full.sh, health-check.sh, notify-developer.sh, restart.sh, start.sh, go build, go test, Edit, Read, Glob, Grep, y fallback generico. Incluye tests para el contador de pasos y truncateInput. Actualiza test de integracion existente para el nuevo formato de mensajes (step counter + nombres legibles vs raw tool names). Co-Authored-By: Claude Opus 4.6 (1M context) --- shell/effects/progress_integration_test.go | 18 +- shell/effects/progress_test.go | 187 ++++++++++++++++++++- 2 files changed, 194 insertions(+), 11 deletions(-) diff --git a/shell/effects/progress_integration_test.go b/shell/effects/progress_integration_test.go index 1d4685b..f1f7460 100644 --- a/shell/effects/progress_integration_test.go +++ b/shell/effects/progress_integration_test.go @@ -69,22 +69,22 @@ func TestIntegration_StreamToProgressReporter(t *testing.T) { t.Fatalf("expected 3 edits (tool uses), got %d", len(sender.edits)) } - // First edit: Bash - if !strings.Contains(sender.edits[0], "Bash") { - t.Errorf("edit[0] should mention Bash, got %q", sender.edits[0]) + // First edit: Bash with "git status" (generic command, no pipeline hint) + if !strings.Contains(sender.edits[0], "Paso 1") { + t.Errorf("edit[0] should have step 1, got %q", sender.edits[0]) } if !strings.Contains(sender.edits[0], "git status") { t.Errorf("edit[0] should show input, got %q", sender.edits[0]) } - // Second edit: Read - if !strings.Contains(sender.edits[1], "Read") { - t.Errorf("edit[1] should mention Read, got %q", sender.edits[1]) + // Second edit: Read β†’ "Leyendo" + if !strings.Contains(sender.edits[1], "Leyendo") { + t.Errorf("edit[1] should contain 'Leyendo', got %q", sender.edits[1]) } - // Third edit: Edit - if !strings.Contains(sender.edits[2], "Edit") { - t.Errorf("edit[2] should mention Edit, got %q", sender.edits[2]) + // Third edit: Edit β†’ "Editando" + if !strings.Contains(sender.edits[2], "Editando") { + t.Errorf("edit[2] should contain 'Editando', got %q", sender.edits[2]) } // All edits should target the same event ID diff --git a/shell/effects/progress_test.go b/shell/effects/progress_test.go index 9be9338..42b7ae9 100644 --- a/shell/effects/progress_test.go +++ b/shell/effects/progress_test.go @@ -67,8 +67,9 @@ func TestProgressReporter_ToolUseEditsMessage(t *testing.T) { if len(sender.edits) != 1 { t.Fatalf("expected 1 edit, got %d", len(sender.edits)) } - if !strings.Contains(sender.edits[0], "Bash") { - t.Errorf("edit = %q, should contain tool name", sender.edits[0]) + // Should contain step number and the command + if !strings.Contains(sender.edits[0], "Paso 1") { + t.Errorf("edit = %q, should contain step number", sender.edits[0]) } if !strings.Contains(sender.edits[0], "ls -la") { t.Errorf("edit = %q, should contain tool input", sender.edits[0]) @@ -224,3 +225,185 @@ func TestProgressReporter_ToolInputTruncation(t *testing.T) { t.Error("truncated input should end with ...") } } + +// ── formatToolEvent unit tests ────────────────────────────────────────── + +func TestFormatToolEvent_PipelineHints(t *testing.T) { + tests := []struct { + name string + tool string + input string + wantSub string // substring that must be present + wantNot string // substring that must NOT be present (empty = skip) + }{ + { + name: "create-full.sh detected", + tool: "Bash", + input: "./dev-scripts/agent/create-full.sh hora-bot \"Hora Bot\"", + wantSub: "Creando agente", + }, + { + name: "health-check.sh detected", + tool: "Bash", + input: "./dev-scripts/agent/health-check.sh hora-bot", + wantSub: "health check", + }, + { + name: "notify-developer.sh detected", + tool: "Bash", + input: "./dev-scripts/agent/notify-developer.sh hora-bot agent \"Hora Bot\"", + wantSub: "bienvenida", + }, + { + name: "restart.sh detected", + tool: "Bash", + input: "./dev-scripts/server/restart.sh", + wantSub: "Reiniciando", + }, + { + name: "start.sh detected", + tool: "Bash", + input: "./dev-scripts/server/start.sh", + wantSub: "Arrancando", + }, + { + name: "go build detected", + tool: "Bash", + input: "go build -tags goolm ./...", + wantSub: "Compilando", + }, + { + name: "go test detected", + tool: "Bash", + input: "go test -tags goolm ./pkg/...", + wantSub: "tests", + }, + { + name: "generic Bash command", + tool: "Bash", + input: "cat /etc/hostname", + wantSub: "cat /etc/hostname", + }, + { + name: "Edit tool", + tool: "Edit", + input: "agents/hora-bot/config.yaml", + wantSub: "Editando", + }, + { + name: "Write tool", + tool: "Write", + input: "agents/hora-bot/prompts/system.md", + wantSub: "Editando", + }, + { + name: "Read tool", + tool: "Read", + input: "agents/hora-bot/agent.go", + wantSub: "Leyendo", + }, + { + name: "Glob tool", + tool: "Glob", + input: "agents/*/config.yaml", + wantSub: "Buscando", + }, + { + name: "Grep tool", + tool: "Grep", + input: "func Rules", + wantSub: "Buscando", + }, + { + name: "unknown tool with input", + tool: "CustomTool", + input: "some argument", + wantSub: "CustomTool", + }, + { + name: "unknown tool without input", + tool: "CustomTool", + input: "", + wantSub: "CustomTool", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := formatToolEvent(1, tt.tool, tt.input) + if !strings.Contains(got, tt.wantSub) { + t.Errorf("formatToolEvent(1, %q, %q) = %q, want substring %q", + tt.tool, tt.input, got, tt.wantSub) + } + if tt.wantNot != "" && strings.Contains(got, tt.wantNot) { + t.Errorf("formatToolEvent(1, %q, %q) = %q, should NOT contain %q", + tt.tool, tt.input, got, tt.wantNot) + } + // All outputs should have step prefix + if !strings.Contains(got, "Paso 1") { + t.Errorf("formatToolEvent output %q should contain step number", got) + } + }) + } +} + +func TestFormatToolEvent_StepCounter(t *testing.T) { + r1 := formatToolEvent(1, "Bash", "echo hello") + r5 := formatToolEvent(5, "Read", "file.go") + r12 := formatToolEvent(12, "Edit", "config.yaml") + + if !strings.Contains(r1, "Paso 1") { + t.Errorf("step 1: %q", r1) + } + if !strings.Contains(r5, "Paso 5") { + t.Errorf("step 5: %q", r5) + } + if !strings.Contains(r12, "Paso 12") { + t.Errorf("step 12: %q", r12) + } +} + +func TestProgressReporter_StepCounterIncrements(t *testing.T) { + sender := &mockProgressSender{} + pr := NewProgressReporter(sender, "!room:test", slog.Default()) + pr.minInterval = 0 + + fn := pr.StreamFunc() + fn(coretypes.StreamEvent{Kind: coretypes.StreamInit}) + fn(coretypes.StreamEvent{Kind: coretypes.StreamToolUse, ToolName: "Bash", ToolInput: "echo 1"}) + fn(coretypes.StreamEvent{Kind: coretypes.StreamToolUse, ToolName: "Read", ToolInput: "file.go"}) + fn(coretypes.StreamEvent{Kind: coretypes.StreamToolUse, ToolName: "Edit", ToolInput: "file.go"}) + + if len(sender.edits) != 3 { + t.Fatalf("expected 3 edits, got %d", len(sender.edits)) + } + if !strings.Contains(sender.edits[0], "Paso 1") { + t.Errorf("first edit should be Paso 1, got %q", sender.edits[0]) + } + if !strings.Contains(sender.edits[1], "Paso 2") { + t.Errorf("second edit should be Paso 2, got %q", sender.edits[1]) + } + if !strings.Contains(sender.edits[2], "Paso 3") { + t.Errorf("third edit should be Paso 3, got %q", sender.edits[2]) + } +} + +func TestTruncateInput(t *testing.T) { + tests := []struct { + input string + maxLen int + want string + }{ + {"short", 10, "short"}, + {"exactly10!", 10, "exactly10!"}, + {"this is longer than ten", 10, "this is..."}, + {"", 10, ""}, + } + + for _, tt := range tests { + got := truncateInput(tt.input, tt.maxLen) + if got != tt.want { + t.Errorf("truncateInput(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.want) + } + } +} From cf26061399627dff60388221ed3c835963efde0b Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Fri, 10 Apr 2026 23:17:04 +0000 Subject: [PATCH 5/5] =?UTF-8?q?docs:=20cerrar=20issue=200046=20=E2=80=94?= =?UTF-8?q?=20progreso=20en=20tiempo=20real=20para=20Father=20Bot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- dev/issues/README.md | 2 +- dev/issues/{ => completed}/0046-father-bot-progress.md | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename dev/issues/{ => completed}/0046-father-bot-progress.md (100%) diff --git a/dev/issues/README.md b/dev/issues/README.md index ede9f0a..86102d9 100644 --- a/dev/issues/README.md +++ b/dev/issues/README.md @@ -56,4 +56,4 @@ afectados y notas de implementacion. | 43 | Guardrails de seguridad para Father Bot | [0043-father-bot-security-guardrails.md](completed/0043-father-bot-security-guardrails.md) | completado | | 44 | Formalizar pipeline de creacion de agentes | [0044-formalize-agent-creation-pipeline.md](completed/0044-formalize-agent-creation-pipeline.md) | completado | | 45 | DM rooms sin E2EE en notify-developer.sh | [0045-notify-encrypted-rooms.md](completed/0045-notify-encrypted-rooms.md) | completado | -| 46 | Progreso en tiempo real para Father Bot | [0046-father-bot-progress.md](0046-father-bot-progress.md) | pendiente | +| 46 | Progreso en tiempo real para Father Bot | [0046-father-bot-progress.md](completed/0046-father-bot-progress.md) | completado | diff --git a/dev/issues/0046-father-bot-progress.md b/dev/issues/completed/0046-father-bot-progress.md similarity index 100% rename from dev/issues/0046-father-bot-progress.md rename to dev/issues/completed/0046-father-bot-progress.md