fix: render status code fences as HTML in Telegram and reduce allocations
- Add statusToHTML() to convert backtick fences to <pre> tags with HTML escaping - Apply ParseMode=HTML to EditStatus/EditTaskStatus so code blocks render properly - Replace escapeHTML's chained ReplaceAll with single-pass strings.Replacer - Extract statusSeparator const to deduplicate separator literals - Replace fmt.Fprintf with strconv.Itoa+WriteString to avoid reflection - Remove intermediate strings.Builder for latest entry prefix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
54616aaa52
commit
1e3e1d48e4
2 changed files with 53 additions and 17 deletions
|
|
@ -1060,6 +1060,7 @@ const (
|
|||
displayPastEntries = 3 // number of compact 1-line past entries
|
||||
displayErrorLines = 3 // content lines inside the error code block
|
||||
maxLatestWidth = 70 // rune limit for the latest entry command (~2 Telegram lines)
|
||||
statusSeparator = "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"
|
||||
)
|
||||
|
||||
// buildRichStatus builds a fixed-height terminal-like status display.
|
||||
|
|
@ -1088,7 +1089,11 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
|||
defer task.mu.Unlock()
|
||||
|
||||
var sb strings.Builder
|
||||
fmt.Fprintf(&sb, "\U0001F504 Task in progress (%d/%d)\n", task.Iteration, task.MaxIter)
|
||||
sb.WriteString("\U0001F504 Task in progress (")
|
||||
sb.WriteString(strconv.Itoa(task.Iteration))
|
||||
sb.WriteByte('/')
|
||||
sb.WriteString(strconv.Itoa(task.MaxIter))
|
||||
sb.WriteString(")\n")
|
||||
if workspace != "" {
|
||||
project := workspace
|
||||
if idx := strings.LastIndex(workspace, "/"); idx >= 0 {
|
||||
|
|
@ -1097,10 +1102,12 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
|||
project = workspace[idx+1:]
|
||||
}
|
||||
if project != "" {
|
||||
fmt.Fprintf(&sb, "\U0001F4C2 %s\n", project)
|
||||
sb.WriteString("\U0001F4C2 ")
|
||||
sb.WriteString(project)
|
||||
sb.WriteByte('\n')
|
||||
}
|
||||
}
|
||||
sb.WriteString("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n")
|
||||
sb.WriteString(statusSeparator)
|
||||
|
||||
// --- Task entries region (displayPastEntries + 4 lines) ---
|
||||
entries := task.toolLog
|
||||
|
|
@ -1135,13 +1142,10 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
|||
// Latest entry: always exactly 4 lines
|
||||
if latest != nil {
|
||||
// Line 1-2: command (truncated to ~2 Telegram lines)
|
||||
var lb strings.Builder
|
||||
lb.WriteString(latest.Name)
|
||||
prefix := latest.Name
|
||||
if latest.ArgsSnip != "" {
|
||||
lb.WriteByte(' ')
|
||||
lb.WriteString(latest.ArgsSnip)
|
||||
prefix += " " + latest.ArgsSnip
|
||||
}
|
||||
prefix := lb.String()
|
||||
if runes := []rune(prefix); len(runes) > maxLatestWidth {
|
||||
sb.WriteString(string(runes[:maxLatestWidth-1]))
|
||||
sb.WriteString("\u2026\n")
|
||||
|
|
@ -1150,7 +1154,9 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
|||
sb.WriteByte('\n')
|
||||
}
|
||||
// Line 3: result
|
||||
fmt.Fprintf(&sb, " %s\n", latest.Result)
|
||||
sb.WriteString(" ")
|
||||
sb.WriteString(latest.Result)
|
||||
sb.WriteByte('\n')
|
||||
} else {
|
||||
sb.WriteString("\u23F3 waiting...\n")
|
||||
sb.WriteString("\u2800\n")
|
||||
|
|
@ -1160,7 +1166,7 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
|||
sb.WriteString("\u2800\n")
|
||||
|
||||
// --- Error region (separator + code fence with displayErrorLines) ---
|
||||
sb.WriteString("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n")
|
||||
sb.WriteString(statusSeparator)
|
||||
sb.WriteString("```\n")
|
||||
|
||||
errEntry := task.lastError
|
||||
|
|
|
|||
|
|
@ -179,7 +179,8 @@ func (c *TelegramChannel) EditStatus(ctx context.Context, msg bus.OutboundMessag
|
|||
if !ok {
|
||||
return nil // no placeholder → nothing to edit
|
||||
}
|
||||
editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), msg.Content)
|
||||
editMsg := tu.EditMessageText(tu.ID(chatID), pID.(int), statusToHTML(msg.Content))
|
||||
editMsg.ParseMode = telego.ModeHTML
|
||||
_, err = c.bot.EditMessageText(ctx, editMsg)
|
||||
return err
|
||||
}
|
||||
|
|
@ -196,7 +197,8 @@ func (c *TelegramChannel) EditTaskStatus(ctx context.Context, msg bus.OutboundMe
|
|||
// Check if we already have a message for this task
|
||||
if existingMsgID, ok := c.taskStatuses.Load(msg.TaskID); ok {
|
||||
// Edit existing task status message
|
||||
editMsg := tu.EditMessageText(tu.ID(chatID), existingMsgID.(int), msg.Content)
|
||||
editMsg := tu.EditMessageText(tu.ID(chatID), existingMsgID.(int), statusToHTML(msg.Content))
|
||||
editMsg.ParseMode = telego.ModeHTML
|
||||
_, err = c.bot.EditMessageText(ctx, editMsg)
|
||||
if err != nil {
|
||||
logger.DebugCF("telegram", "EditTaskStatus edit failed", map[string]interface{}{
|
||||
|
|
@ -208,7 +210,8 @@ func (c *TelegramChannel) EditTaskStatus(ctx context.Context, msg bus.OutboundMe
|
|||
}
|
||||
|
||||
// First task status message: send a new message and track it
|
||||
tgMsg := tu.Message(tu.ID(chatID), msg.Content)
|
||||
tgMsg := tu.Message(tu.ID(chatID), statusToHTML(msg.Content))
|
||||
tgMsg.ParseMode = telego.ModeHTML
|
||||
sent, err := c.bot.SendMessage(ctx, tgMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
@ -982,9 +985,36 @@ func wrapByDisplayWidth(s string, maxWidth int) []string {
|
|||
return lines
|
||||
}
|
||||
|
||||
var htmlEscaper = strings.NewReplacer("&", "&", "<", "<", ">", ">")
|
||||
|
||||
func escapeHTML(text string) string {
|
||||
text = strings.ReplaceAll(text, "&", "&")
|
||||
text = strings.ReplaceAll(text, "<", "<")
|
||||
text = strings.ReplaceAll(text, ">", ">")
|
||||
return text
|
||||
return htmlEscaper.Replace(text)
|
||||
}
|
||||
|
||||
// statusToHTML converts status message content to Telegram HTML.
|
||||
// It HTML-escapes the text and converts backtick code fences to <pre> blocks.
|
||||
func statusToHTML(content string) string {
|
||||
parts := strings.Split(content, "```")
|
||||
if len(parts) < 3 {
|
||||
return escapeHTML(content)
|
||||
}
|
||||
var sb strings.Builder
|
||||
for i, part := range parts {
|
||||
if i%2 == 0 {
|
||||
sb.WriteString(escapeHTML(part))
|
||||
} else {
|
||||
// Strip optional language tag on the opening line
|
||||
body := part
|
||||
if nl := strings.Index(body, "\n"); nl >= 0 {
|
||||
tag := strings.TrimSpace(body[:nl])
|
||||
if tag == "" || !strings.ContainsAny(tag, " \t") {
|
||||
body = body[nl+1:]
|
||||
}
|
||||
}
|
||||
sb.WriteString("<pre>")
|
||||
sb.WriteString(escapeHTML(body))
|
||||
sb.WriteString("</pre>")
|
||||
}
|
||||
}
|
||||
return sb.String()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue