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
2464f63eb7
commit
b7505dcb0f
2 changed files with 53 additions and 17 deletions
|
|
@ -1060,6 +1060,7 @@ const (
|
||||||
displayPastEntries = 3 // number of compact 1-line past entries
|
displayPastEntries = 3 // number of compact 1-line past entries
|
||||||
displayErrorLines = 3 // content lines inside the error code block
|
displayErrorLines = 3 // content lines inside the error code block
|
||||||
maxLatestWidth = 70 // rune limit for the latest entry command (~2 Telegram lines)
|
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.
|
// 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()
|
defer task.mu.Unlock()
|
||||||
|
|
||||||
var sb strings.Builder
|
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 != "" {
|
if workspace != "" {
|
||||||
project := workspace
|
project := workspace
|
||||||
if idx := strings.LastIndex(workspace, "/"); idx >= 0 {
|
if idx := strings.LastIndex(workspace, "/"); idx >= 0 {
|
||||||
|
|
@ -1097,10 +1102,12 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
||||||
project = workspace[idx+1:]
|
project = workspace[idx+1:]
|
||||||
}
|
}
|
||||||
if project != "" {
|
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) ---
|
// --- Task entries region (displayPastEntries + 4 lines) ---
|
||||||
entries := task.toolLog
|
entries := task.toolLog
|
||||||
|
|
@ -1135,13 +1142,10 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
||||||
// Latest entry: always exactly 4 lines
|
// Latest entry: always exactly 4 lines
|
||||||
if latest != nil {
|
if latest != nil {
|
||||||
// Line 1-2: command (truncated to ~2 Telegram lines)
|
// Line 1-2: command (truncated to ~2 Telegram lines)
|
||||||
var lb strings.Builder
|
prefix := latest.Name
|
||||||
lb.WriteString(latest.Name)
|
|
||||||
if latest.ArgsSnip != "" {
|
if latest.ArgsSnip != "" {
|
||||||
lb.WriteByte(' ')
|
prefix += " " + latest.ArgsSnip
|
||||||
lb.WriteString(latest.ArgsSnip)
|
|
||||||
}
|
}
|
||||||
prefix := lb.String()
|
|
||||||
if runes := []rune(prefix); len(runes) > maxLatestWidth {
|
if runes := []rune(prefix); len(runes) > maxLatestWidth {
|
||||||
sb.WriteString(string(runes[:maxLatestWidth-1]))
|
sb.WriteString(string(runes[:maxLatestWidth-1]))
|
||||||
sb.WriteString("\u2026\n")
|
sb.WriteString("\u2026\n")
|
||||||
|
|
@ -1150,7 +1154,9 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
||||||
sb.WriteByte('\n')
|
sb.WriteByte('\n')
|
||||||
}
|
}
|
||||||
// Line 3: result
|
// Line 3: result
|
||||||
fmt.Fprintf(&sb, " %s\n", latest.Result)
|
sb.WriteString(" ")
|
||||||
|
sb.WriteString(latest.Result)
|
||||||
|
sb.WriteByte('\n')
|
||||||
} else {
|
} else {
|
||||||
sb.WriteString("\u23F3 waiting...\n")
|
sb.WriteString("\u23F3 waiting...\n")
|
||||||
sb.WriteString("\u2800\n")
|
sb.WriteString("\u2800\n")
|
||||||
|
|
@ -1160,7 +1166,7 @@ func buildRichStatus(task *activeTask, isBackground bool, workspace string) stri
|
||||||
sb.WriteString("\u2800\n")
|
sb.WriteString("\u2800\n")
|
||||||
|
|
||||||
// --- Error region (separator + code fence with displayErrorLines) ---
|
// --- 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")
|
sb.WriteString("```\n")
|
||||||
|
|
||||||
errEntry := task.lastError
|
errEntry := task.lastError
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,8 @@ func (c *TelegramChannel) EditStatus(ctx context.Context, msg bus.OutboundMessag
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil // no placeholder → nothing to edit
|
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)
|
_, err = c.bot.EditMessageText(ctx, editMsg)
|
||||||
return err
|
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
|
// Check if we already have a message for this task
|
||||||
if existingMsgID, ok := c.taskStatuses.Load(msg.TaskID); ok {
|
if existingMsgID, ok := c.taskStatuses.Load(msg.TaskID); ok {
|
||||||
// Edit existing task status message
|
// 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)
|
_, err = c.bot.EditMessageText(ctx, editMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.DebugCF("telegram", "EditTaskStatus edit failed", map[string]interface{}{
|
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
|
// 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)
|
sent, err := c.bot.SendMessage(ctx, tgMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -982,9 +985,36 @@ func wrapByDisplayWidth(s string, maxWidth int) []string {
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var htmlEscaper = strings.NewReplacer("&", "&", "<", "<", ">", ">")
|
||||||
|
|
||||||
func escapeHTML(text string) string {
|
func escapeHTML(text string) string {
|
||||||
text = strings.ReplaceAll(text, "&", "&")
|
return htmlEscaper.Replace(text)
|
||||||
text = strings.ReplaceAll(text, "<", "<")
|
}
|
||||||
text = strings.ReplaceAll(text, ">", ">")
|
|
||||||
return 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