Skip to content

Resolve root relative assets in static previews#2818

Merged
illegalcall merged 4 commits into
AgentWrapper:mainfrom
aprv10:fix/static-preview-asset-resolution
Jul 20, 2026
Merged

Resolve root relative assets in static previews#2818
illegalcall merged 4 commits into
AgentWrapper:mainfrom
aprv10:fix/static-preview-asset-resolution

Conversation

@aprv10

@aprv10 aprv10 commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Issue

AO serves workspace previews beneath /api/v1/sessions/<id>/preview/files/....

Root-relative references such as /assets/app.css, /images/logo.svg, ES modules, and fetch("/data.json") therefore escaped the preview namespace and resolved against the daemon API root. Imported projects and production builds using root-relative assets could render without styling or fail to load runtime resources.

Fix

Serve each static workspace preview from a session-specific *.localhost origin on the existing daemon port.

The selected entry directory is mounted as the origin root, so:

  • dist/index.html serves /assets/app.css from dist/assets/app.css.
  • Relative asset paths continue to work.
  • CSS images and fonts, ES modules, and runtime root-relative requests resolve naturally.
  • Concurrent session previews use isolated origins and cannot mix assets.
  • Explicit dev-server and file:// preview targets remain unchanged.

The existing /preview/files/... route remains available for compatibility, and the preview poller migrates stored legacy workspace-preview URLs to the new origin.

CORS now recognizes the RFC-reserved *.localhost subtree so browser module requests are accepted without allowing localhost lookalike domains.

Files affected

  • backend/internal/preview/entry.go

    • Generates DNS-safe, session-specific localhost origins.
    • Handles long and Unicode session IDs.
    • Avoids double-escaping filenames containing spaces or special characters.
  • backend/internal/preview/poller.go

    • Recognizes both legacy preview routes and isolated preview origins.
    • Migrates legacy workspace-preview URLs automatically.
  • backend/internal/httpd/router.go

    • Routes isolated preview hosts through the workspace preview handler.
  • backend/internal/httpd/controllers/sessions.go

    • Mounts the selected entry directory at the preview origin root.
    • Preserves confinement to the session workspace and existing Markdown behavior.
  • backend/internal/httpd/cors.go

    • Allows valid localhost subdomain origins while rejecting suffix lookalikes.
  • Preview, controller, poller, and CORS tests

    • Cover root-relative assets, nested entry directories, concurrent sessions, router restart, legacy migration, URL encoding, and CORS boundaries.

Validation

  • Passed: go test ./internal/preview ./internal/httpd/controllers
  • Passed: go test ./internal/httpd -run 'TestCORS'
  • Passed: go vet ./internal/preview ./internal/httpd/controllers ./internal/httpd
  • Passed: npm run frontend:typecheck
  • Passed manual Chromium smoke test covering:
    • Root-relative stylesheet loading
    • CSS-referenced images
    • Root-relative <img> assets
    • ES module execution
    • fetch("/data/message.json")
    • No browser console warnings or errors

The repository-wide go test ./... remains blocked by unrelated existing Windows/environment failures involving agent-auth fixtures, executable-bit assertions, CRLF expectations, missing sh, port-conflict handling, and stale generated OpenAPI output.

Risks and scope

  • The isolated-origin behavior applies only to AO-served workspace files. Explicit HTTP(S) development servers and file:// targets are not rewritten.
  • The implementation relies on the standards-reserved *.localhost namespace; this was manually verified in AO’s Chromium browser on Windows.
Screenshot 2026-07-19 083620

@illegalcall illegalcall left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I left inline comments on the preview-origin boundary, URL generation, migration behavior, and test coverage.

Comment thread backend/internal/preview/entry.go Outdated
Comment thread backend/internal/preview/entry.go Outdated
Comment thread backend/internal/preview/entry.go
Comment thread backend/internal/httpd/controllers/sessions.go
Comment thread backend/internal/httpd/controllers/sessions.go Outdated
Comment thread backend/internal/httpd/controllers/sessions.go Outdated
Comment thread backend/internal/preview/poller.go Outdated
@Vaibhaav-Tiwari

Copy link
Copy Markdown
Collaborator

resolve the issues pointed out @illegalcall, will double check after that

@somewherelostt

Copy link
Copy Markdown
Collaborator

Thanks for contributing to Agent Orchestrator.

This PR is being picked up by the current external contributor on-call pair:

If someone is already working on this, please continue as usual.
The on-call pair is added for visibility, tracking, and support, not to take over the work.
If you need help with review, direction, reproduction, or next steps, please tag @neversettle17-101 and @somewherelostt here.

For faster context or live questions, you can also join the AO Discord.

Join the session here:
https://discord.gg/H6ZDcUXmq

Come by if you want to see what is being built, ask questions, or just hang around with the community.

@aprv10

aprv10 commented Jul 20, 2026

Copy link
Copy Markdown
Contributor Author

@illegalcall
I addressed each concern as follows:

  1. Use the daemon’s actual bound address

    The preview poller now starts only after httpd.NewWithDeps successfully creates the server, and it receives srv.Addr().String() instead of the configured address. Preview URLs therefore use the actual listening port, including when the configured port is occupied and the daemon falls back to an ephemeral port.

    I also made address-in-use detection platform-specific because Windows reports WSAEADDRINUSE, while Unix systems report EADDRINUSE.

  2. Preserve whitespace in resolved filenames

    FileURL no longer calls TrimSpace on resolved entry paths. Once a workspace file has been resolved, its path is preserved exactly, including valid leading or trailing spaces, and URL escaping happens only once when the final URL is serialized.

  3. Enforce the complete DNS hostname limit

    Preview hostname generation now returns an explicit error when the encoded hostname would exceed the 253-character DNS limit. Host decoding also validates the total hostname length and individual label lengths.

    Boundary coverage verifies that a 253-character hostname round-trips successfully and that the next unsupported session-ID length is rejected predictably. The HTTP controller returns a structured 422 PREVIEW_SESSION_ID_UNSUPPORTED response, while the poller logs and skips unsupported IDs.

  4. Cover preview-origin error behavior

    Added coverage for:

    • unsupported methods returning 405 with Allow: GET, HEAD;
    • unknown sessions;
    • sessions without a preview entry;
    • missing preview assets;
    • structured JSON error responses and request IDs.
  5. Prevent symlink escapes from the workspace

    Both discovery and serving now open files through os.OpenRoot. This permits symlinks that remain inside the workspace but rejects symlinks that resolve outside it.

    The opened, workspace-confined file handle is passed directly to http.ServeContent, avoiding a second path lookup and the associated validation/serving race. Regression coverage verifies that neither the isolated origin nor the legacy route can serve an external file through a workspace symlink.

    Path normalization also handles both / and \ before rejecting .. segments, so traversal checks behave consistently across platforms.

  6. Share the legacy and isolated-origin serving pipeline

    The duplicated file-serving logic has been consolidated into serveWorkspacePreviewFile. Both routes now use the same confinement, regular-file validation, Markdown rendering, content-type handling, and static-file serving behavior. The isolated-origin handler retains only its origin-specific entry-directory mapping.

  7. Preserve the selected entry during migration

    Migration now extracts the stored workspace entry from:

    • isolated-origin preview URLs;
    • legacy /preview/files/... URLs;
    • previously stored relative paths.

    If that entry still exists as a confined regular file, it is preserved. Discovery is used only when the stored entry is missing or invalid. The migration test now covers a stored docs/report.html alongside index.html and verifies that migration keeps the report instead of silently switching entries.

@illegalcall
illegalcall merged commit 0cfd4da into AgentWrapper:main Jul 20, 2026
9 checks passed
xuelongmu pushed a commit to xuelongmu/agent-orchestrator that referenced this pull request Jul 20, 2026
* fix(preview): resolve root-relative static assets

* fix(preview): fix lint errors

* fix(preview): harden isolated asset serving

* fix build errors
xuelongmu pushed a commit to xuelongmu/agent-orchestrator that referenced this pull request Jul 20, 2026
* fix(preview): resolve root-relative static assets

* fix(preview): fix lint errors

* fix(preview): harden isolated asset serving

* fix build errors
xuelongmu added a commit to xuelongmu/agent-orchestrator that referenced this pull request Jul 20, 2026
Replays AgentWrapper#2818 with fork-safe OS helper integration, LAN-reachable mobile preview URLs, and released legacy path compatibility.
@xuelongmu

Copy link
Copy Markdown

Replayed onto the xuelongmu fork in PR #206, merged as a28102b. The fork integration preserves the upstream static-preview semantics and OS-specific address handling, retains downstream run-file/cancelVerification behavior, keeps Connect Mobile preview URLs LAN-reachable, and adds compatibility for released double-escaped legacy asset paths. Exact corrected head 950b2d9 passed 12/12 CI, zero unresolved threads, clean merge-tree, and independent Sol/xhigh P0/P1/P2=0.

@aprv10
aprv10 deleted the fix/static-preview-asset-resolution branch July 20, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants