feat: mensajes progresivos en Matrix con ProgressReporter

Implementa la Fase 2 del issue 0036: mensajes de progreso en tiempo real
que muestran al usuario que herramientas esta usando el agente claude-code.

- SendMarkdownGetID en shell/matrix/client.go: envia mensaje y retorna
  el event ID para editarlo despues
- EditMessage en shell/matrix/client.go: edita un mensaje existente
  usando m.replace (m.relates_to con rel_type=m.replace)
- ProgressReporter en shell/effects/progress.go (NEW): recibe streaming
  events y actualiza un mensaje unico en Matrix mostrando el progreso
  (e.g. "🔧 Bash: ls -la" → "🔧 Read: file.go" → " Completado")
- Rate limiter integrado: max 1 edit/segundo para no saturar el homeserver
- Conectado en devagents/handler.go: cuando provider=claude-code y
  streaming+show_tool_progress habilitados, crea ProgressReporter y
  pasa StreamFunc al CompletionRequest
- MatrixSender interface actualizada con los nuevos metodos
- 10 tests nuevos para ProgressReporter, todos los existentes pasan

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 22:58:03 +00:00
parent 1bdf9344a2
commit 45bd258be1
8 changed files with 482 additions and 8 deletions
+45
View File
@@ -286,6 +286,51 @@ func (c *Client) SendMarkdown(ctx context.Context, roomID, markdown string) erro
return err
}
// SendMarkdownGetID sends a formatted (Markdown) message to a room and returns
// the event ID of the sent message. Useful for later editing via EditMessage.
func (c *Client) SendMarkdownGetID(ctx context.Context, roomID, markdown string) (string, error) {
html := mdToHTML(markdown)
content := &event.MessageEventContent{
MsgType: event.MsgText,
Body: markdown,
Format: event.FormatHTML,
FormattedBody: html,
}
resp, err := c.raw.SendMessageEvent(ctx, id.RoomID(roomID), event.EventMessage, content)
if err != nil {
return "", err
}
return resp.EventID.String(), nil
}
// EditMessage edits a previously sent message in a room using m.replace relation.
// originalEventID is the event ID of the message to replace.
// The new content is rendered from markdown.
func (c *Client) EditMessage(ctx context.Context, roomID, originalEventID, markdown string) error {
html := mdToHTML(markdown)
// Matrix spec: m.new_content holds the replacement, m.relates_to with
// rel_type=m.replace points to the original event.
content := &event.MessageEventContent{
MsgType: event.MsgText,
Body: "* " + markdown, // per spec: prefix with "* " for fallback
Format: event.FormatHTML,
FormattedBody: "* " + html,
RelatesTo: &event.RelatesTo{
Type: event.RelReplace,
EventID: id.EventID(originalEventID),
},
NewContent: &event.MessageEventContent{
MsgType: event.MsgText,
Body: markdown,
Format: event.FormatHTML,
FormattedBody: html,
},
}
_, err := c.raw.SendMessageEvent(ctx, id.RoomID(roomID), event.EventMessage, content)
return err
}
// mdToHTML converts a Markdown string to HTML using goldmark with full extensions.
var mdParser = goldmark.New(
goldmark.WithExtensions(