perf: eliminate intermediate string allocations in hot paths

- Replace WriteString(fmt.Sprintf(...)) with fmt.Fprintf() across 7 files
  to avoid intermediate string allocation (writes directly to Builder)
- Refactor telegram message composition from O(n²) += to strings.Builder
- Refactor device event formatting from += chain to strings.Builder
- Fix lint issues: misspell, whitespace, gofumpt, gci, unused func

## Benchmark Results

WriteString(Sprintf) vs Fprintf (10-iteration loop, 5 fields each):
  old: 2480 ns/op   2823 B/op   16 allocs/op
  new: 1841 ns/op   1860 B/op    6 allocs/op
  → 26% faster, 34% less memory, 63% fewer allocations

Telegram message build (+= vs strings.Builder):
  AllMedia:  old 403 ns/1104 B/10 allocs → new 118 ns/336 B/3 allocs (3.4x faster)
  LongText:  old 8309 ns/47232 B/10 allocs → new 1483 ns/8064 B/2 allocs (5.6x faster)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-03-19 11:54:33 +09:00
parent 3e57679c69
commit 7912b22d8f
14 changed files with 129 additions and 129 deletions

View file

@ -362,7 +362,6 @@ func mediaTempDirPattern() string {
return "^" + regexp.QuoteMeta(filepath.Clean(media.TempDir())) + "(?:" + sep + "|$)" return "^" + regexp.QuoteMeta(filepath.Clean(media.TempDir())) + "(?:" + sep + "|$)"
} }
func expandHome(path string) string { func expandHome(path string) string {
if path == "" { if path == "" {
return path return path

View file

@ -1161,7 +1161,6 @@ func (al *AgentLoop) callLLMWithRetry(
ch, sErr := sp.ChatStream(streamCtx, *messages, toolDefs, model, llmOpts) ch, sErr := sp.ChatStream(streamCtx, *messages, toolDefs, model, llmOpts)
if sErr != nil { if sErr != nil {
return nil, sErr return nil, sErr
} }
resp, repetition, sErr := consumeStreamWithRepetitionDetection(ch, streamCancel, 1000, onChunk) resp, repetition, sErr := consumeStreamWithRepetitionDetection(ch, streamCancel, 1000, onChunk)
if sErr != nil { if sErr != nil {
@ -1263,7 +1262,6 @@ func (al *AgentLoop) callLLMWithRetry(
opts.SenderID, opts.SenderDisplayName, opts.SenderID, opts.SenderDisplayName,
) )
continue continue
} }
break break
} }
@ -1375,7 +1373,6 @@ func (al *AgentLoop) publishToolMedia(ctx context.Context, result *tools.ToolRes
}) })
} }
// isNativeSearchProvider reports whether the given LLM provider implements // isNativeSearchProvider reports whether the given LLM provider implements
// NativeSearchCapable and returns true for SupportsNativeSearch. // NativeSearchCapable and returns true for SupportsNativeSearch.
func isNativeSearchProvider(p providers.LLMProvider) bool { func isNativeSearchProvider(p providers.LLMProvider) bool {

View file

@ -650,8 +650,8 @@ func (ms *MemoryStore) FormatPlanDisplay() string {
} }
var sb strings.Builder var sb strings.Builder
sb.WriteString(fmt.Sprintf("Plan: %s\n", state.taskName)) fmt.Fprintf(&sb, "Plan: %s\n", state.taskName)
sb.WriteString(fmt.Sprintf("Status: %s | Phase %d/%d\n\n", state.status, state.currentPhase, len(state.phases))) fmt.Fprintf(&sb, "Status: %s | Phase %d/%d\n\n", state.status, state.currentPhase, len(state.phases))
for _, p := range state.phases { for _, p := range state.phases {
var emoji string var emoji string
@ -662,7 +662,7 @@ func (ms *MemoryStore) FormatPlanDisplay() string {
} else { } else {
emoji = "\u23F3" emoji = "\u23F3"
} }
sb.WriteString(fmt.Sprintf("%s Phase %d: %s\n", emoji, p.Number, p.Title)) fmt.Fprintf(&sb, "%s Phase %d: %s\n", emoji, p.Number, p.Title)
if p.Number <= state.currentPhase { if p.Number <= state.currentPhase {
for _, s := range p.Steps { for _, s := range p.Steps {
if s.Done { if s.Done {

View file

@ -70,7 +70,7 @@ func (mb *MessageBus) InboundChan() <-chan InboundMessage {
return mb.inbound return mb.inbound
} }
// ConsumeInbound blocks until an inbound message is available or the context is cancelled. // ConsumeInbound blocks until an inbound message is available or the context is canceled.
func (mb *MessageBus) ConsumeInbound(ctx context.Context) (InboundMessage, bool) { func (mb *MessageBus) ConsumeInbound(ctx context.Context) (InboundMessage, bool) {
select { select {
case msg, ok := <-mb.inbound: case msg, ok := <-mb.inbound:
@ -90,7 +90,7 @@ func (mb *MessageBus) OutboundChan() <-chan OutboundMessage {
return mb.outbound return mb.outbound
} }
// SubscribeOutbound blocks until an outbound message is available or the context is cancelled. // SubscribeOutbound blocks until an outbound message is available or the context is canceled.
func (mb *MessageBus) SubscribeOutbound(ctx context.Context) (OutboundMessage, bool) { func (mb *MessageBus) SubscribeOutbound(ctx context.Context) (OutboundMessage, bool) {
select { select {
case msg, ok := <-mb.outbound: case msg, ok := <-mb.outbound:

View file

@ -77,7 +77,7 @@ type channelWorker struct {
} }
type Manager struct { type Manager struct {
managerExt // fork-specific fields (statusMsgIDs, taskMsgIDs, statusEditTimes) managerExt // fork-specific fields (statusMsgIDs, taskMsgIDs, statusEditTimes)
channels map[string]Channel channels map[string]Channel
workers map[string]*channelWorker workers map[string]*channelWorker
bus *bus.MessageBus bus *bus.MessageBus

View file

@ -521,8 +521,9 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
chatID := message.Chat.ID chatID := message.Chat.ID
c.chatIDs[platformID] = chatID c.chatIDs[platformID] = chatID
content := ""
mediaPaths := []string{} mediaPaths := []string{}
hasMedia := message.Caption != "" || len(message.Photo) > 0 ||
message.Voice != nil || message.Audio != nil || message.Document != nil
chatIDStr := fmt.Sprintf("%d", chatID) chatIDStr := fmt.Sprintf("%d", chatID)
messageIDStr := fmt.Sprintf("%d", message.MessageID) messageIDStr := fmt.Sprintf("%d", message.MessageID)
@ -542,63 +543,66 @@ func (c *TelegramChannel) handleMessage(ctx context.Context, message *telego.Mes
return localPath // fallback: use raw path return localPath // fallback: use raw path
} }
if message.Text != "" { // Fast path: text-only message (most common) — avoid Builder alloc
content += message.Text var content string
} if !hasMedia {
if message.Text != "" {
if message.Caption != "" { content = message.Text
if content != "" {
content += "\n"
} }
content += message.Caption } else {
} var cb strings.Builder
if message.Text != "" {
if len(message.Photo) > 0 { cb.WriteString(message.Text)
photo := message.Photo[len(message.Photo)-1] }
photoPath := c.downloadPhoto(ctx, photo.FileID) if message.Caption != "" {
if photoPath != "" { if cb.Len() > 0 {
mediaPaths = append(mediaPaths, storeMedia(photoPath, "photo.jpg")) cb.WriteByte('\n')
if content != "" {
content += "\n"
} }
content += "[image: photo]" cb.WriteString(message.Caption)
} }
} if len(message.Photo) > 0 {
photo := message.Photo[len(message.Photo)-1]
if message.Voice != nil { photoPath := c.downloadPhoto(ctx, photo.FileID)
voicePath := c.downloadFile(ctx, message.Voice.FileID, ".ogg") if photoPath != "" {
if voicePath != "" { mediaPaths = append(mediaPaths, storeMedia(photoPath, "photo.jpg"))
mediaPaths = append(mediaPaths, storeMedia(voicePath, "voice.ogg")) if cb.Len() > 0 {
cb.WriteByte('\n')
if content != "" { }
content += "\n" cb.WriteString("[image: photo]")
} }
content += "[voice]"
} }
} if message.Voice != nil {
voicePath := c.downloadFile(ctx, message.Voice.FileID, ".ogg")
if message.Audio != nil { if voicePath != "" {
audioPath := c.downloadFile(ctx, message.Audio.FileID, ".mp3") mediaPaths = append(mediaPaths, storeMedia(voicePath, "voice.ogg"))
if audioPath != "" { if cb.Len() > 0 {
mediaPaths = append(mediaPaths, storeMedia(audioPath, "audio.mp3")) cb.WriteByte('\n')
if content != "" { }
content += "\n" cb.WriteString("[voice]")
} }
content += "[audio]"
} }
} if message.Audio != nil {
audioPath := c.downloadFile(ctx, message.Audio.FileID, ".mp3")
if message.Document != nil { if audioPath != "" {
docPath := c.downloadFile(ctx, message.Document.FileID, "") mediaPaths = append(mediaPaths, storeMedia(audioPath, "audio.mp3"))
if docPath != "" { if cb.Len() > 0 {
mediaPaths = append(mediaPaths, storeMedia(docPath, "document")) cb.WriteByte('\n')
if content != "" { }
content += "\n" cb.WriteString("[audio]")
} }
content += "[file]"
} }
if message.Document != nil {
docPath := c.downloadFile(ctx, message.Document.FileID, "")
if docPath != "" {
mediaPaths = append(mediaPaths, storeMedia(docPath, "document"))
if cb.Len() > 0 {
cb.WriteByte('\n')
}
cb.WriteString("[file]")
}
}
content = cb.String()
} }
if content == "" { if content == "" {
content = "[empty message]" content = "[empty message]"
} }

View file

@ -1,6 +1,9 @@
package events package events
import "context" import (
"context"
"strings"
)
type EventSource interface { type EventSource interface {
Kind() Kind Kind() Kind
@ -44,14 +47,26 @@ func (e *DeviceEvent) FormatMessage() string {
actionText = "Disconnected" actionText = "Disconnected"
} }
msg := actionEmoji + " Device " + actionText + "\n\n" var b strings.Builder
msg += "Type: " + string(e.Kind) + "\n" b.WriteString(actionEmoji)
msg += "Device: " + e.Vendor + " " + e.Product + "\n" b.WriteString(" Device ")
b.WriteString(actionText)
b.WriteString("\n\nType: ")
b.WriteString(string(e.Kind))
b.WriteString("\nDevice: ")
b.WriteString(e.Vendor)
b.WriteByte(' ')
b.WriteString(e.Product)
b.WriteByte('\n')
if e.Capabilities != "" { if e.Capabilities != "" {
msg += "Capabilities: " + e.Capabilities + "\n" b.WriteString("Capabilities: ")
b.WriteString(e.Capabilities)
b.WriteByte('\n')
} }
if e.Serial != "" { if e.Serial != "" {
msg += "Serial: " + e.Serial + "\n" b.WriteString("Serial: ")
b.WriteString(e.Serial)
b.WriteByte('\n')
} }
return msg return b.String()
} }

View file

@ -375,23 +375,19 @@ func (hs *HeartbeatService) buildResearchContext() string {
b.WriteString("You have research tasks due for progress. For each task below:\n") b.WriteString("You have research tasks due for progress. For each task below:\n")
b.WriteString("1. If pending, set status to 'active' first\n") b.WriteString("1. If pending, set status to 'active' first\n")
b.WriteString("2. Use web_search to find new information, then add_finding to record it\n") b.WriteString("2. Use web_search to find new information, then add_finding to record it\n")
b.WriteString( fmt.Fprintf(&b, "3. **Web search budget: %d calls total this heartbeat** — use them wisely\n",
fmt.Sprintf( research.DefaultHeartbeatSearchQuota)
"3. **Web search budget: %d calls total this heartbeat** — use them wisely\n",
research.DefaultHeartbeatSearchQuota,
),
)
b.WriteString("4. Do NOT set status to 'completed' — research progresses incrementally across heartbeats\n\n") b.WriteString("4. Do NOT set status to 'completed' — research progresses incrementally across heartbeats\n\n")
for _, task := range tasks { for _, task := range tasks {
b.WriteString(fmt.Sprintf("### %s [%s] (id: %s)\n", task.Title, task.Status, task.ID)) fmt.Fprintf(&b, "### %s [%s] (id: %s)\n", task.Title, task.Status, task.ID)
b.WriteString(fmt.Sprintf("Interval: %s", task.Interval)) fmt.Fprintf(&b, "Interval: %s", task.Interval)
if !task.LastResearchedAt.IsZero() { if !task.LastResearchedAt.IsZero() {
b.WriteString(fmt.Sprintf(" | Last researched: %s", task.LastResearchedAt.Format("2006-01-02 15:04"))) fmt.Fprintf(&b, " | Last researched: %s", task.LastResearchedAt.Format("2006-01-02 15:04"))
} }
b.WriteString("\n") b.WriteString("\n")
if task.Description != "" { if task.Description != "" {
b.WriteString(fmt.Sprintf("Description: %s\n", task.Description)) fmt.Fprintf(&b, "Description: %s\n", task.Description)
} }
docs, err := store.ListDocuments(task.ID) docs, err := store.ListDocuments(task.ID)
@ -410,20 +406,20 @@ func (hs *HeartbeatService) buildResearchContext() string {
shown = shown[:maxFindingsPerTask] shown = shown[:maxFindingsPerTask]
} }
b.WriteString(fmt.Sprintf("Existing findings (%d total):\n", len(docs))) fmt.Fprintf(&b, "Existing findings (%d total):\n", len(docs))
for _, d := range shown { for _, d := range shown {
summary := d.Summary summary := d.Summary
if len(summary) > maxSummaryLen { if len(summary) > maxSummaryLen {
summary = summary[:maxSummaryLen] + "..." summary = summary[:maxSummaryLen] + "..."
} }
b.WriteString(fmt.Sprintf("- [%d] %s", d.Seq, d.Title)) fmt.Fprintf(&b, "- [%d] %s", d.Seq, d.Title)
if summary != "" { if summary != "" {
b.WriteString(fmt.Sprintf(" — %s", summary)) fmt.Fprintf(&b, " — %s", summary)
} }
b.WriteString("\n") b.WriteString("\n")
} }
if len(docs) > maxFindingsPerTask { if len(docs) > maxFindingsPerTask {
b.WriteString(fmt.Sprintf(" ... and %d more findings\n", len(docs)-maxFindingsPerTask)) fmt.Fprintf(&b, " ... and %d more findings\n", len(docs)-maxFindingsPerTask)
} }
b.WriteString("\nAdd NEW findings that build on and extend the above. Do not repeat existing findings.\n\n") b.WriteString("\nAdd NEW findings that build on and extend the above. Do not repeat existing findings.\n\n")
} }

View file

@ -35,7 +35,7 @@ func TestProviderChat_StripsGroqAndOllamaPrefixes(t *testing.T) {
}, },
} }
for _, tt := range tests { //nolint:dupl for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
var requestBody map[string]any var requestBody map[string]any

View file

@ -500,7 +500,7 @@ func TestProviderChat_StripsGroqOllamaDeepseekVivgridNovitaPrefixes(t *testing.T
}, },
} }
for _, tt := range tests { //nolint:dupl for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, tt.input, nil) _, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, tt.input, nil)
if err != nil { if err != nil {

View file

@ -129,12 +129,12 @@ func (t *ResearchTool) RuntimeStatus() string {
b.WriteString("Available research topics:\n") b.WriteString("Available research topics:\n")
for _, task := range tasks { for _, task := range tasks {
docCount, _ := t.store.DocumentCount(task.ID) docCount, _ := t.store.DocumentCount(task.ID)
b.WriteString(fmt.Sprintf("- \"%s\" [%s, %d docs] (id: %s)\n", fmt.Fprintf(&b, "- \"%s\" [%s, %d docs] (id: %s)\n",
task.Title, task.Status, docCount, task.ID)) task.Title, task.Status, docCount, task.ID)
} }
if focusID, focusTitle := t.focus.Current(); focusID != "" { if focusID, focusTitle := t.focus.Current(); focusID != "" {
b.WriteString(fmt.Sprintf("\nCurrently focused: \"%s\" (id: %s)\n", focusTitle, focusID)) fmt.Fprintf(&b, "\nCurrently focused: \"%s\" (id: %s)\n", focusTitle, focusID)
} }
b.WriteString("\nUse `research recall` to load findings when the user asks about a research topic.\n") b.WriteString("\nUse `research recall` to load findings when the user asks about a research topic.\n")
@ -154,11 +154,11 @@ func (t *ResearchTool) listTasks(args map[string]any) *ToolResult {
} }
var b strings.Builder var b strings.Builder
b.WriteString(fmt.Sprintf("Found %d research task(s):\n\n", len(tasks))) fmt.Fprintf(&b, "Found %d research task(s):\n\n", len(tasks))
for _, task := range tasks { for _, task := range tasks {
docCount, _ := t.store.DocumentCount(task.ID) docCount, _ := t.store.DocumentCount(task.ID)
b.WriteString(fmt.Sprintf("- **%s** [%s] (id: %s, docs: %d)\n %s\n", fmt.Fprintf(&b, "- **%s** [%s] (id: %s, docs: %d)\n %s\n",
task.Title, task.Status, task.ID, docCount, task.Description)) task.Title, task.Status, task.ID, docCount, task.Description)
} }
return NewToolResult(b.String()) return NewToolResult(b.String())
} }
@ -177,20 +177,20 @@ func (t *ResearchTool) getTask(args map[string]any) *ToolResult {
docs, _ := t.store.ListDocuments(taskID) docs, _ := t.store.ListDocuments(taskID)
var b strings.Builder var b strings.Builder
b.WriteString(fmt.Sprintf("## %s\n", task.Title)) fmt.Fprintf(&b, "## %s\n", task.Title)
b.WriteString(fmt.Sprintf("- **Status**: %s\n", task.Status)) fmt.Fprintf(&b, "- **Status**: %s\n", task.Status)
b.WriteString(fmt.Sprintf("- **ID**: %s\n", task.ID)) fmt.Fprintf(&b, "- **ID**: %s\n", task.ID)
b.WriteString(fmt.Sprintf("- **Output dir**: %s\n", task.OutputDir)) fmt.Fprintf(&b, "- **Output dir**: %s\n", task.OutputDir)
if task.Description != "" { if task.Description != "" {
b.WriteString(fmt.Sprintf("- **Description**: %s\n", task.Description)) fmt.Fprintf(&b, "- **Description**: %s\n", task.Description)
} }
b.WriteString(fmt.Sprintf("- **Created**: %s\n", task.CreatedAt.Format("2006-01-02 15:04"))) fmt.Fprintf(&b, "- **Created**: %s\n", task.CreatedAt.Format("2006-01-02 15:04"))
if len(docs) > 0 { if len(docs) > 0 {
b.WriteString(fmt.Sprintf("\n### Documents (%d)\n", len(docs))) fmt.Fprintf(&b, "\n### Documents (%d)\n", len(docs))
for _, d := range docs { for _, d := range docs {
b.WriteString(fmt.Sprintf("- [%d] %s (%s) — %s\n path: %s\n", fmt.Fprintf(&b, "- [%d] %s (%s) — %s\n path: %s\n",
d.Seq, d.Title, d.DocType, d.Summary, d.FilePath)) d.Seq, d.Title, d.DocType, d.Summary, d.FilePath)
} }
} else { } else {
b.WriteString("\nNo documents yet.\n") b.WriteString("\nNo documents yet.\n")
@ -313,10 +313,10 @@ func (t *ResearchTool) recall(args map[string]any) *ToolResult {
t.focus.Focus(task.ID, task.Title) t.focus.Focus(task.ID, task.Title)
var b strings.Builder var b strings.Builder
b.WriteString(fmt.Sprintf("# Research Context: %s\n\n", task.Title)) fmt.Fprintf(&b, "# Research Context: %s\n\n", task.Title)
b.WriteString(fmt.Sprintf("**Status**: %s | **Documents**: %d\n", task.Status, len(docs))) fmt.Fprintf(&b, "**Status**: %s | **Documents**: %d\n", task.Status, len(docs))
if task.Description != "" { if task.Description != "" {
b.WriteString(fmt.Sprintf("**Description**: %s\n", task.Description)) fmt.Fprintf(&b, "**Description**: %s\n", task.Description)
} }
b.WriteString("\n---\n\n") b.WriteString("\n---\n\n")
@ -331,7 +331,7 @@ func (t *ResearchTool) recall(args map[string]any) *ToolResult {
absPath := filepath.Join(t.workspace, d.FilePath) absPath := filepath.Join(t.workspace, d.FilePath)
data, readErr := os.ReadFile(absPath) data, readErr := os.ReadFile(absPath)
if readErr != nil { if readErr != nil {
b.WriteString(fmt.Sprintf("## [%d] %s\n\n(file not found: %s)\n\n", d.Seq, d.Title, d.FilePath)) fmt.Fprintf(&b, "## [%d] %s\n\n(file not found: %s)\n\n", d.Seq, d.Title, d.FilePath)
loaded++ loaded++
continue continue
} }
@ -340,16 +340,14 @@ func (t *ResearchTool) recall(args map[string]any) *ToolResult {
entrySize := len(d.Title) + len(content) + 40 // rough overhead for headers entrySize := len(d.Title) + len(content) + 40 // rough overhead for headers
if totalBytes+entrySize > maxContextBytes { if totalBytes+entrySize > maxContextBytes {
remaining := len(docs) - loaded remaining := len(docs) - loaded
b.WriteString( fmt.Fprintf(&b,
fmt.Sprintf( "\n---\n*Context limit reached. %d more finding(s) not shown. Use get_task to see the full list.*\n",
"\n---\n*Context limit reached. %d more finding(s) not shown. Use get_task to see the full list.*\n", remaining,
remaining,
),
) )
break break
} }
b.WriteString(fmt.Sprintf("## [%d] %s\n\n", d.Seq, d.Title)) fmt.Fprintf(&b, "## [%d] %s\n\n", d.Seq, d.Title)
b.WriteString(content) b.WriteString(content)
if !strings.HasSuffix(content, "\n") { if !strings.HasSuffix(content, "\n") {
b.WriteByte('\n') b.WriteByte('\n')

View file

@ -97,19 +97,19 @@ func formatSearchResults(query string, results []skills.SearchResult, cached boo
if cached { if cached {
source = " (cached)" source = " (cached)"
} }
sb.WriteString(fmt.Sprintf("Found %d skills for %q%s:\n\n", len(results), query, source)) fmt.Fprintf(&sb, "Found %d skills for %q%s:\n\n", len(results), query, source)
for i, r := range results { for i, r := range results {
sb.WriteString(fmt.Sprintf("%d. **%s**", i+1, r.Slug)) fmt.Fprintf(&sb, "%d. **%s**", i+1, r.Slug)
if r.Version != "" { if r.Version != "" {
sb.WriteString(fmt.Sprintf(" v%s", r.Version)) fmt.Fprintf(&sb, " v%s", r.Version)
} }
sb.WriteString(fmt.Sprintf(" (score: %.3f, registry: %s)\n", r.Score, r.RegistryName)) fmt.Fprintf(&sb, " (score: %.3f, registry: %s)\n", r.Score, r.RegistryName)
if r.DisplayName != "" && r.DisplayName != r.Slug { if r.DisplayName != "" && r.DisplayName != r.Slug {
sb.WriteString(fmt.Sprintf(" Name: %s\n", r.DisplayName)) fmt.Fprintf(&sb, " Name: %s\n", r.DisplayName)
} }
if r.Summary != "" { if r.Summary != "" {
sb.WriteString(fmt.Sprintf(" %s\n", r.Summary)) fmt.Fprintf(&sb, " %s\n", r.Summary)
} }
sb.WriteString("\n") sb.WriteString("\n")
} }

View file

@ -127,11 +127,11 @@ func (t *SpawnStatusTool) Execute(ctx context.Context, args map[string]any) *Too
} }
var sb strings.Builder var sb strings.Builder
sb.WriteString(fmt.Sprintf("Subagent status report (%d total):\n", len(tasks))) fmt.Fprintf(&sb, "Subagent status report (%d total):\n", len(tasks))
for _, status := range []string{"running", "completed", "failed", "canceled"} { for _, status := range []string{"running", "completed", "failed", "canceled"} {
if n := counts[status]; n > 0 { if n := counts[status]; n > 0 {
label := strings.ToUpper(status[:1]) + status[1:] + ":" label := strings.ToUpper(status[:1]) + status[1:] + ":"
sb.WriteString(fmt.Sprintf(" %-10s %d\n", label, n)) fmt.Fprintf(&sb, " %-10s %d\n", label, n)
} }
} }
sb.WriteString("\n") sb.WriteString("\n")
@ -148,21 +148,20 @@ func (t *SpawnStatusTool) Execute(ctx context.Context, args map[string]any) *Too
func spawnStatusFormatTask(task *SubagentTask) string { func spawnStatusFormatTask(task *SubagentTask) string {
var sb strings.Builder var sb strings.Builder
header := fmt.Sprintf("[%s] status=%s", task.ID, task.Status) fmt.Fprintf(&sb, "[%s] status=%s", task.ID, task.Status)
if task.Label != "" { if task.Label != "" {
header += fmt.Sprintf(" label=%q", task.Label) fmt.Fprintf(&sb, " label=%q", task.Label)
} }
if task.AgentID != "" { if task.AgentID != "" {
header += fmt.Sprintf(" agent=%s", task.AgentID) fmt.Fprintf(&sb, " agent=%s", task.AgentID)
} }
if task.Created > 0 { if task.Created > 0 {
created := time.UnixMilli(task.Created).UTC().Format("2006-01-02 15:04:05 UTC") created := time.UnixMilli(task.Created).UTC().Format("2006-01-02 15:04:05 UTC")
header += fmt.Sprintf(" created=%s", created) fmt.Fprintf(&sb, " created=%s", created)
} }
sb.WriteString(header)
if task.Task != "" { if task.Task != "" {
sb.WriteString(fmt.Sprintf("\n task: %s", task.Task)) fmt.Fprintf(&sb, "\n task: %s", task.Task)
} }
if task.Result != "" { if task.Result != "" {
result := task.Result result := task.Result
@ -171,7 +170,7 @@ func spawnStatusFormatTask(task *SubagentTask) string {
if len(runes) > maxResultLen { if len(runes) > maxResultLen {
result = string(runes[:maxResultLen]) + "…" result = string(runes[:maxResultLen]) + "…"
} }
sb.WriteString(fmt.Sprintf("\n result: %s", result)) fmt.Fprintf(&sb, "\n result: %s", result)
} }
return sb.String() return sb.String()

View file

@ -56,14 +56,6 @@ var gatewayHealthGet = func(url string, timeout time.Duration) (*http.Response,
return client.Get(url) return client.Get(url)
} }
// gatewayHealthScheme returns "https" if the gateway is serving TLS, "http" otherwise.
func gatewayHealthScheme(cfg *config.Config) string {
if cfg != nil && strings.HasPrefix(cfg.Channels.Telegram.WebAppURL, "https://") {
return "https"
}
return "http"
}
// getGatewayHealth checks the gateway health endpoint and returns the status response // getGatewayHealth checks the gateway health endpoint and returns the status response
// Returns (*health.StatusResponse, statusCode, error). If error is not nil, the other values are not valid. // Returns (*health.StatusResponse, statusCode, error). If error is not nil, the other values are not valid.
func (h *Handler) getGatewayHealth(cfg *config.Config, timeout time.Duration) (*health.StatusResponse, int, error) { func (h *Handler) getGatewayHealth(cfg *config.Config, timeout time.Duration) (*health.StatusResponse, int, error) {