Skip to content

Service-per-origin cutover: delete /service/ proxy, apps own their origins - #342

Open
amirmohsen-am wants to merge 4 commits into
imbue-ai:mainfrom
amirmohsen-am:amir/forwarding-redesign
Open

Service-per-origin cutover: delete /service/ proxy, apps own their origins#342
amirmohsen-am wants to merge 4 commits into
imbue-ai:mainfrom
amirmohsen-am:amir/forwarding-redesign

Conversation

@amirmohsen-am

@amirmohsen-am amirmohsen-am commented Jul 28, 2026

Copy link
Copy Markdown

Service-per-origin cutover: delete the /service/ proxy, own-origin apps

Template side of the forwarding redesign (blueprint/forwarding-redesign/plan-forwarding-redesign.md in mngr; pairs with the mngr PR on the same branch name). Every registered service now owns a real browser origin; nothing proxies or rewrites app traffic anymore.

Deleted

  • system_interface proxy stack: proxy.py, service_dispatcher.py, their tests, and the register_service_routes wiring — service-worker bootstrap, HTML/<base> rewriting, WS shim, cookie-path rewriting are all gone.

Changed

  • Frontend derives each service's origin from location.host at render time (src/origin.ts): local hosts prefix the service label (http://<name>.agent-<hex>.localhost:8421/), shared hosts swap the first -- token (https://<name>--<host>--<user>.<domain>/). Layouts persist serviceName, never origins — portable across hosts and shares.
  • Browser-fleet API calls go through a same-origin backend passthrough (GET/POST /api/browsers) instead of the deleted proxy path.
  • forward_port.py validates DNS-safe service names (lowercase alnum + single hyphens; no underscores; no agent- prefix; not localhost) with a new test suite.
  • Supervisord program + registered service renamed system_interfacesystem-interface (DNS-safe); oom-priority band, docs, and the dead ROOT_PATH=/service/browser env updated/removed.
  • layout.py / layout_ops.py speak service coordinates instead of /service/ paths. A panel's real URL now depends on the viewer's host (local vs share), which server-side code can't know — so it refers to panels as service:<name>[?query] and the frontend resolves that to a real origin at render time. When these scripts read URLs back out of persisted layout state, they extract the service name from the hostname (handles both the local and shared host shapes, with a guard so external hosts containing -- aren't mistaken for services).
  • Skills/docs rewritten for own-origin apps (build-app, update-app, service-processes, preview scripts): root-absolute URLs, WebSockets, cookies, and service workers work unmodified; the preview wrapper no longer dodges an HTML rewriter.

Validation

Frontend build/lint/vitest green; root pytest and system_interface pytest green (remaining failures verified pre-existing/environmental). Real-workspace e2e (local + Cloudflare share, including agent-installed openvscode with its own service worker and webviews) documented in the mngr repo at blueprint/forwarding-redesign/validation-results.md.

Future TODO

  • Wire frontend vitest into CI (currently a manual gate; only npm run build runs in CI).
  • build-app guidance for multi-origin apps that can self-host their sandbox origins (e.g. openvscode --webview-external-endpoint={{uuid}}.<name>.<workspace-host>) once shared wildcard certs land connector-side.
  • Per-service cross-site-cookie opt-in flag at registration (threads to the connector's Access app settings; see the mngr PR's security note — never global).

🤖 Generated with Claude Code

amirmohsen-am and others added 3 commits July 28, 2026 10:32
…ename system-interface

Every registered service owns a browser origin (local:
<name>.agent-<hex>.localhost; shared: <name>--<host>--<user>.<domain>).
The system_interface proxy/service_dispatcher and all four transforms are
deleted; the frontend derives service origins from location.host at render
time (layouts persist serviceName, not origins); browser-fleet API calls go
through a same-origin /api/browsers passthrough; forward_port validates
DNS-safe names; the supervisord service is renamed system-interface; docs
and skills describe the own-origin model.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The browser service's docstrings still described the deleted /service/
proxy and the removed ROOT_PATH env (now-dead read dropped). Layout URL
parsers now require both '--' separators of a share hostname so an
external panel host that merely contains '--' can't masquerade as a
service (with regression tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erviceUrl

- The two Python copies of the origin->service-name parse (layout_ops.py
  and the stdlib-only scripts/layout.py, which cannot import the package)
  now cross-reference each other and are pinned by a drift test in
  layout_ops_test.py.
- layout.py's _service_name_from_ref delegates to _service_ref_parts
  instead of re-implementing the ref grammar scan.
- preview_wrapper_server's inline sibling-origin JS cites origin.ts as the
  owner of the origin scheme.
- getServiceUrl was a bare alias of deriveServiceOrigin; inlined at its
  six call sites.
- The build-app scaffold now reserves "localhost" (forward_port.py rejects
  it at registration, so the scaffold could mint an unregistrable app) and
  a drift test asserts scaffold-accepted names stay a subset of
  forward_port's registration rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment on lines +21 to +34
export function deriveServiceOrigin(
serviceName: string,
host: string = window.location.host,
protocol: string = window.location.protocol,
): string {
// Shared (Cloudflare) hosts are ``<name>--<host>--<user>.<domain>``: the
// sibling service's hostname swaps the text before the first ``--``.
if (host.includes("--")) {
return `${protocol}//${serviceName}${host.substring(host.indexOf("--"))}/`;
}
// Local workspace hosts (``agent-<hex>.localhost[:port]``) nest the
// service as a subdomain label.
return `${protocol}//${serviceName}.${host}/`;
}

@amirmohsen-am amirmohsen-am Jul 28, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This can be brittle to determine if host is Cloudflare only by if (host.includes("--")) but we can bundle the change when updating remote_service_connector and include the origin in the http headers from cloudflare to make it cleaner

The rename existed only for DNS-label hygiene, but underscore hostname
labels work everywhere this scheme needs them: Cloudflare DNS minted
system_interface--<host>--<user> share hostnames before this redesign,
Chromium/Electron accept underscore hostnames, and the forwarder's SNI
cert minting + strict hostname verification pass for
system_interface.agent-<hex>.localhost. Keeping the name avoids
breaking resumed snapshots whose apps.toml predates the PR and shrinks
the diff (supervisord, oom bands, recovery probe, docs all revert).

forward_port.py's registration rule now allows underscores (consecutive
hyphens stay rejected -- '--' is the share-hostname separator); the
build-app scaffold stays kebab-only for new apps, still a strict subset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant