diff --git a/cmd/gateway_consumer_helpers.go b/cmd/gateway_consumer_helpers.go index 420650a9..565d5636 100644 --- a/cmd/gateway_consumer_helpers.go +++ b/cmd/gateway_consumer_helpers.go @@ -126,7 +126,13 @@ func mediaToMarkdown(media []agent.MediaResult, cfg *config.Config) string { } // Store clean path only — no auth tokens in persisted session messages. // Frontend adds auth (Bearer header or ?ft= signed token) at render time. - fileURL := "/v1/files/" + urlPath + // Guard: if path is already a /v1/ URL (e.g. from mutated media), don't double-prefix. + var fileURL string + if strings.HasPrefix(urlPath, "v1/files/") || strings.HasPrefix(urlPath, "v1/media/") { + fileURL = "/" + strings.SplitN(urlPath, "?", 2)[0] // strip any existing query params + } else { + fileURL = "/v1/files/" + urlPath + } if strings.HasPrefix(mr.ContentType, "image/") { parts = append(parts, fmt.Sprintf("![image](%s)", fileURL)) } else { diff --git a/cmd/gateway_managed.go b/cmd/gateway_managed.go index a1e09a3c..163cee4d 100644 --- a/cmd/gateway_managed.go +++ b/cmd/gateway_managed.go @@ -179,13 +179,18 @@ func wireExtras( } // Convert media local paths → signed /v1/files/basename?ft=hash if rawMedia, ok := m["media"].([]agent.MediaResult); ok { - for i := range rawMedia { - basename := filepath.Base(rawMedia[i].Path) + // Clone slice — the original is shared with RunResult.Media; + // mutating in-place corrupts paths for downstream consumers + // (announce queue, outbound channels) that expect local paths. + signed := make([]agent.MediaResult, len(rawMedia)) + for i, mr := range rawMedia { + signed[i] = mr + basename := filepath.Base(mr.Path) url := "/v1/files/" + basename ft := httpapi.SignFileToken(url, secret, httpapi.FileTokenTTL) - rawMedia[i].Path = url + "?ft=" + ft + signed[i].Path = url + "?ft=" + ft } - m["media"] = rawMedia + m["media"] = signed } } msgBus.Broadcast(bus.Event{