Skip to content

fix(ui): proxy canonical inbound media previews#100725

Merged
steipete merged 1 commit into
mainfrom
codex/fix-control-ui-inbound-media-preview
Jul 6, 2026
Merged

fix(ui): proxy canonical inbound media previews#100725
steipete merged 1 commit into
mainfrom
codex/fix-control-ui-inbound-media-preview

Conversation

@steipete

@steipete steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Inbound channel images persisted as media://inbound/<id> are extracted from chat history but left as an unloadable custom-scheme URL in the Control UI. The user sees only the attachment placeholder after reload. Fixes #89591 and salvages the valid intent from #90172.

Why This Change Was Made

Recognize only the raw one-segment canonical inbound URI shape at the existing renderer proxy decision, then reuse the authenticated assistant-media metadata/ticket route. The raw predicate rejects nested, dot-segment, cross-bucket, query, fragment, encoded-separator, and NUL forms before they can bypass local preview-root checks. No new public media route or core policy is added.

User Impact

Images received through Telegram, iMessage, and other channels render inline in WebUI chat history after reload. Malformed media references and ordinary local files remain blocked by the existing containment policy.

Evidence

  • Blacksmith Testbox tbx_01kwv3e6wmqvd6m1bsh7x9jcgz: chat renderer plus Gateway assistant-media contract — 320 assertions passed across UI, Gateway variants, and real HTTP ticket flow.
  • Same Testbox: all 25 mocked-Gateway real-Chromium chat flows passed; the inbound image used authenticated metadata, a scoped ticket, and decoded to a visible 1×1 image.
  • Added malformed URI coverage for nested, dot-segment, cross-bucket, query, fragment, encoded slash, and NUL inputs.
  • git diff --check passed.

Co-authored-by: Cornna [email protected]

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: S maintainer Maintainer-authored PR agents Agent runtime and tooling size: M gateway Gateway runtime cli CLI command changes size: XL and removed size: S size: M labels Jul 6, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4a7cbe865c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +734 to +735
activeRestartRequest = upgradedRequest;
forceActiveRestartExit?.();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Force update upgrades to interrupt the active drain

When a managed update SIGUSR1 arrives while a regular restart is already draining active tasks, this branch only replaces activeRestartRequest and calls forceActiveRestartExit, whose callback just re-arms the shutdown watchdog. The drain loop above is already running with the original captured restartIntent, so waitForActiveTasks/waitForActiveEmbeddedRuns keep waiting for the old deferral window (or indefinitely) until the watchdog exits with code 1, instead of cleanly reaching the update respawn path. Please make this upgrade actually interrupt/force the active drain rather than only arming the timeout.

Useful? React with 👍 / 👎.

@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Land-ready verification for 4a7cbe865c86b4627f04d487fbecc2b6f052fc03:

  • Renderer, Gateway route, and real HTTP media-ticket checks on Blacksmith Testbox tbx_01kwv3e6wmqvd6m1bsh7x9jcgz: 320 assertions passed.
  • pnpm test:ui:e2e ui/src/e2e/chat-flow.e2e.test.ts on the same Testbox: all 25 real-Chromium flows passed; the new inbound-image case verified authenticated metadata, scoped ticket propagation, and a decoded visible image.
  • Malformed-path coverage includes nested, dot-segment, cross-bucket, query, fragment, encoded-separator, and NUL refs.
  • Fresh autoreview: no actionable findings; patch correct (0.86 confidence).
  • Native exact-head hosted gate: https://github.com/openclaw/openclaw/actions/runs/28775965922
  • git diff --check: passed.

Known proof gaps: no live Telegram credential roundtrip; the browser flow uses the real Control UI with a mocked Gateway plus the separately proven real Gateway HTTP route. The replacement preserves @sweetcornna's co-author and changelog credit from #90172.

@steipete
steipete requested a review from a team as a code owner July 6, 2026 08:02
@openclaw-barnacle openclaw-barnacle Bot added channel: discord Channel integration: discord channel: telegram Channel integration: telegram scripts Repository scripts commands Command implementations extensions: openai extensions: qa-lab extensions: codex extensions: openrouter labels Jul 6, 2026
@steipete
steipete force-pushed the codex/fix-control-ui-inbound-media-preview branch from d2cd869 to 5b3e65e Compare July 6, 2026 08:14
@openclaw-barnacle openclaw-barnacle Bot removed channel: discord Channel integration: discord channel: telegram Channel integration: telegram gateway Gateway runtime cli CLI command changes scripts Repository scripts commands Command implementations agents Agent runtime and tooling extensions: openai labels Jul 6, 2026
@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs maintainer review before merge. Reviewed July 6, 2026, 4:36 AM ET / 08:36 UTC.

Summary
The branch teaches the Control UI chat renderer to proxy canonical media://inbound/<id> image MediaPath values through the existing assistant-media route and adds renderer plus E2E coverage for valid and malformed inbound refs.

PR surface: Source +23, Tests +152. Total +175 across 3 files.

Reproducibility: yes. Source inspection shows current main extracts image MediaPath values into raw image URLs but does not classify media://inbound/<id> as assistant-media proxyable, so the browser receives an unloadable custom-scheme image source.

Review metrics: none identified.

Stored data model
Persistent data-model change detected: serialized state: ui/src/e2e/chat-flow.e2e.test.ts. Confirm migration or upgrade compatibility proof before merge.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #89591
Summary: The canonical issue tracks WebUI inbound media previews; this PR is the focused current candidate fix, while the older draft PR is superseded by this replacement branch.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦞 diamond lobster
Proof: 🦞 diamond lobster
Patch quality: 🦞 diamond lobster
Result: ready for maintainer review.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Next step before merge

  • No automated repair is needed; the remaining action is maintainer review and merge after required exact-head checks finish.

Security
Cleared: No concrete security or supply-chain concern found; the diff adds no dependencies, workflows, privileged execution, or unauthenticated media route and reuses the existing ticketed assistant-media boundary.

Review details

Best possible solution:

Land this focused renderer change once required exact-head checks finish; it should supersede #90172 and close #89591.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection shows current main extracts image MediaPath values into raw image URLs but does not classify media://inbound/<id> as assistant-media proxyable, so the browser receives an unloadable custom-scheme image source.

Is this the best way to solve the issue?

Yes. Reusing the existing authenticated, ticketed assistant-media route in the renderer is narrower and safer than adding a new public inbound media route, and the PR adds malformed-ref coverage around that boundary.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against e7ca90e3afaf.

Label changes

Label changes:

  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR includes after-fix Testbox live output for the renderer, real Gateway HTTP ticket route, and real-Chromium Control UI chat flow showing a decoded visible inbound image; no contributor action is needed for proof.
  • add rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • add status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR includes after-fix Testbox live output for the renderer, real Gateway HTTP ticket route, and real-Chromium Control UI chat flow showing a decoded visible inbound image; no contributor action is needed for proof.

Label justifications:

  • P2: This is a normal-priority WebUI media preview bugfix with limited blast radius and no evidence of data loss, security bypass, crash loop, or unusable core runtime.
  • rating: 🦞 diamond lobster: Overall readiness is 🦞 diamond lobster; proof is 🦞 diamond lobster and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR. Sufficient (live_output): The PR includes after-fix Testbox live output for the renderer, real Gateway HTTP ticket route, and real-Chromium Control UI chat flow showing a decoded visible inbound image; no contributor action is needed for proof.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR includes after-fix Testbox live output for the renderer, real Gateway HTTP ticket route, and real-Chromium Control UI chat flow showing a decoded visible inbound image; no contributor action is needed for proof.
Evidence reviewed

PR surface:

Source +23, Tests +152. Total +175 across 3 files.

View PR surface stats
Area Files Added Removed Net
Source 1 23 0 +23
Tests 2 152 0 +152
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 3 175 0 +175

What I checked:

  • Repository policy read: Root AGENTS.md and ui/AGENTS.md were read fully; root PR review/proof guidance applied, and the UI-scoped locale rules were not relevant to these files. (AGENTS.md:28, e7ca90e3afaf)
  • Current-main renderer gap: Current main extracts image-like transcript MediaPath values and appends the raw path as an image URL, so a media://inbound/<id> image reaches the browser as an unloadable custom-scheme URL. (ui/src/pages/chat/components/chat-message.ts:416, e7ca90e3afaf)
  • Current-main proxy predicate: Current main only classifies file URLs, home paths, absolute paths, and Windows drive paths as assistant-media proxyable, so it does not route inbound media refs through buildAssistantAttachmentUrl. (ui/src/pages/chat/components/chat-message.ts:1225, e7ca90e3afaf)
  • Existing Gateway contract: The assistant-media handler already resolves sources through media-reference/local-root checks, requires operator auth for metadata, and returns scoped media tickets for media loads. (src/gateway/control-ui.ts:547, e7ca90e3afaf)
  • Inbound ref parser boundary: The shared parser accepts canonical media://inbound/<id> references while rejecting unsupported buckets, empty ids, nested ids, backslashes, and NULs before resolving to the inbound store. (src/media/media-reference.ts:117, e7ca90e3afaf)
  • PR implementation: The PR adds a raw one-segment inbound media predicate, excludes those refs from local path normalization, and allows only canonical inbound refs through the existing assistant-media availability flow. (ui/src/pages/chat/components/chat-message.ts:1239, 3073f7d1162d)

Likely related people:

  • steipete: GitHub path history ties this handle to recent assistant-media route work, inbound media reference resolution, and current Control UI chat renderer changes relevant to this fix. (role: assistant-media and media-reference area contributor; confidence: high; commits: eae4000c38e2, 6290ed52ff9f, a6a4140ee71a; files: src/gateway/control-ui.ts, src/media/media-reference.ts, ui/src/pages/chat/components/chat-message.ts)
  • shakkernerd: The current chat-message.ts component path appears in recent history through the Control UI architecture refactor that moved or reshaped this renderer surface. (role: recent Control UI architecture contributor; confidence: medium; commits: 65e12328aa20; files: ui/src/pages/chat/components/chat-message.ts)
  • lzyyzznl: Recent history on the same renderer file includes direct tool-result image block rendering, adjacent to transcript image handling and inline image display. (role: adjacent chat image rendering contributor; confidence: medium; commits: 8e7cd4071906; files: ui/src/pages/chat/components/chat-message.ts, ui/src/e2e/chat-flow.e2e.test.ts)
  • Syysean: Feature history for gateway attachments includes storing received attachments as canonical media://inbound/<id> refs consumed by this WebUI display path. (role: inbound media offload contributor; confidence: medium; commits: c6f2db150687; files: src/gateway/chat-attachments.ts)
  • vincentkoc: Recent history on attachment and gateway paths includes media-path normalization and attachment handling that share the inbound media invariant. (role: recent gateway/media adjacent contributor; confidence: medium; commits: ca7349b585dc, 3b663ad1c17d, 1f71e922972c; files: src/media/media-reference.ts, src/gateway/chat-attachments.ts)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.
Review history (1 earlier review cycle)
  • reviewed 2026-07-06T08:19:28.590Z sha 5b3e65e :: needs maintainer review before merge. :: none

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. P2 Normal backlog priority with limited blast radius. labels Jul 6, 2026
@steipete
steipete force-pushed the codex/fix-control-ui-inbound-media-preview branch from 5b3e65e to 1bbd24e Compare July 6, 2026 08:23
@steipete
steipete force-pushed the codex/fix-control-ui-inbound-media-preview branch from 1bbd24e to 3073f7d Compare July 6, 2026 08:28
@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. labels Jul 6, 2026
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Land-ready verification for 3073f7d1162dde292c592730609524697abaac4b:

  • Renderer, Gateway route, and real HTTP media-ticket checks on Blacksmith Testbox tbx_01kwv3e6wmqvd6m1bsh7x9jcgz: 320 assertions passed.
  • pnpm test:ui:e2e ui/src/e2e/chat-flow.e2e.test.ts on the same Testbox: all 25 real-Chromium flows passed; the new inbound-image case verified authenticated metadata, scoped ticket propagation, and a decoded visible image.
  • Malformed-path coverage includes nested, dot-segment, cross-bucket, query, fragment, encoded-separator, and NUL refs.
  • Fresh autoreview: no actionable findings; patch correct (0.86 confidence).
  • Native exact-head hosted gate: https://github.com/openclaw/openclaw/actions/runs/28778259710
  • git diff --check: passed.

Known proof gaps: no live Telegram credential roundtrip; the browser flow uses the real Control UI with a mocked Gateway plus the separately proven real Gateway HTTP route. The replacement preserves @sweetcornna's co-author credit from #90172; maintainer changelog credit is isolated in #100740 to avoid coupling this exact-head UI proof to the high-traffic release-notes hunk.

@steipete
steipete merged commit 3170bfa into main Jul 6, 2026
97 of 103 checks passed
@steipete
steipete deleted the codex/fix-control-ui-inbound-media-preview branch July 6, 2026 08:54
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: web-ui App: web-ui maintainer Maintainer-authored PR P2 Normal backlog priority with limited blast radius. proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦞 diamond lobster Very strong PR readiness with only minor maintainer review expected. size: S status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Display inbound media attachments as image previews in WebUI

1 participant