feat: make opencode web embeddable in iframes at a subpath#23912
Open
csillag wants to merge 1 commit intoanomalyco:devfrom
Open
feat: make opencode web embeddable in iframes at a subpath#23912csillag wants to merge 1 commit intoanomalyco:devfrom
csillag wants to merge 1 commit intoanomalyco:devfrom
Conversation
88acdbc to
77df993
Compare
csillag
added a commit
to deai-network/optio
that referenced
this pull request
Apr 23, 2026
Follow-ups from the branch-level code review. All low-risk fixes that came up during verification/review of the otherwise-ready feature. - RemoteHost.launch_opencode: read the opencode server password from a mode-0600 file in the workdir via `$(cat ...)` rather than inlining it into the command string. Previously any local user on the remote host could read the basic-auth password from the bash process's argv via `ps aux` during the launch window. Env variables (asyncssh create_process env=) would be cleaner but OpenSSH's default AcceptEnv won't forward arbitrary names, so a mode-0600 file the command substitutes at exec time is the portable fix. - RemoteHost.terminate_opencode(aggressive=True): use proc.kill() (SIGKILL) instead of proc.terminate() (SIGTERM). The spec's cancellation path expects SIGKILL; with SIGTERM an opencode that blocks on shutdown would eat the 5-second shutdown grace period. LocalHost was already correct. - LocalHost.tail_log / RemoteHost.tail_log: document why we use `tail -F -n +1` instead of the spec's `-n 0`. Fresh workdir + race- free at-least-once delivery (an earlier `-n 0` attempt silently dropped log lines that opencode wrote between subprocess spawn and our tail subscribing). - Remove unused `aiofiles>=23.0` dependency (the module docstring mentioned it but the implementation spawns a `tail` subprocess; aiofiles was never imported). Update host.py's module docstring. - AGENTS.md: add a "Where the fork lives" paragraph pointing at github.com/csillag/opencode #csillag/make-web-embeddable-in-iframes, the upstream PR anomalyco/opencode#23912, and the build command so someone picking up this branch cold can populate OPTIO_OPENCODE_BINARY_DIR without hunting for the right repo. Deferred review items (no change in this commit): remote-mode cancellation test (spec Section 9 coverage gap), routing install stderr through ctx.report_progress (nice-to-have), cosmetic file-touch idiom, demo-task inner.execute clarification, SFTP-over- SSH retry path not exercised by tests, negative-cache comment consolidation.
77df993 to
e83eb50
Compare
Author
|
Sorry for the rebase hickup, now fixed and the change is only 16 lines. |
Enables a reverse-proxy / embedding host (e.g. a parent dashboard) to serve opencode web under an arbitrary URL prefix — the built SPA, the server's CSP, and the SDK's default-server-URL resolution all have to cooperate for a same-origin iframe mount to actually work. Three orthogonal changes: 1. packages/app/vite.config.ts — `base: './'` Emits relative asset paths in the built index.html and chunk imports (e.g. `./assets/foo.js` instead of `/assets/foo.js`), so a document loaded under `/some/deep/iframe-prefix/` can resolve its own asset URLs against that prefix rather than against the origin root. No effect on direct-serve at `/`; every Vite-base subpath story just works from one source build. 2. packages/opencode/src/server/routes/ui.ts — add `'unsafe-eval'` to the embedded-UI CSP's script-src directive Something in opencode's production bundle (best guess: a workerized runtime or a lib that compiles at runtime; we haven't bisected the exact call site) exercises `eval()` / `new Function()`. The existing CSP permitted `'wasm-unsafe-eval'` but not `'unsafe-eval'`, causing the browser to block the bundle under the stricter Firefox policy when served behind the embed proxy. Allowing `'unsafe-eval'` keeps the page functional. A better long-term fix is to bisect the eval caller and remove it, then tighten CSP back; this commit is the short-term unblocker. 3. packages/app/src/entry.tsx — `getCurrentUrl` honors the localStorage defaultServerUrl override `getCurrentUrl()` was previously hard-wired to `location.origin` (in production) for the initial `servers[0]` entry, while `getDefaultUrl()` would return the localStorage-set `defaultServerUrl` when present for the `defaultServer` key. The two disagreed: the server-context's `current` server resolves via `allServers().find(key === state.active) ?? allServers()[0]`, so if `state.active` pointed at the localStorage URL but that URL wasn't in `allServers()`, the code fell back to `allServers()[0]` — i.e. `location.origin` — and control-plane requests like `/global/config` and `/global/event` bypassed the override entirely. Having `getCurrentUrl` also honor the localStorage override keeps both entries aligned and makes the override globally effective. Together these let opencode web embed inside an iframe served from a foreign origin / subpath: assets load, the SPA bundle executes, and all SDK calls (including control-plane routes) honor the configured server URL.
e83eb50 to
30bdc70
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue for this PR
No existing issue — self-motivated fix to enable embedding
opencode webinside an iframe under a reverse-proxy subpath. Happy to file one
retroactively if that's the workflow you prefer.
Type of change
What does this PR do?
Lets
opencode webwork inside an<iframe>served from a URL subpath(e.g.
/some/deep/path/<session-id>/) under a reverse proxy. Threesmall orthogonal changes, none of which affect a direct-at-root
deployment:
packages/app/vite.config.ts—base: './'. The builtindex.htmland chunk imports now use relative asset paths so a document loaded
under a subpath resolves assets against the current URL instead of
the origin root. Fixes "asset requests return the SPA fallback HTML
with the wrong MIME type" for subpath embeds.
packages/opencode/src/server/routes/ui.ts— add'unsafe-eval'tothe embedded-UI CSP's
script-src. Something in the productionbundle triggers
eval()/new Function()at runtime; the existingpolicy allows
'wasm-unsafe-eval'but not'unsafe-eval', soFirefox (stricter than Chromium here) blocks the bundle once it
runs in an iframe. I didn't bisect the specific caller — if one of
the maintainers has a guess I'm happy to chase and tighten the CSP
back; this is the short-term unblocker.
packages/app/src/entry.tsx—getCurrentUrl()now honors thelocalStorage.defaultServerUrloverride the same waygetDefaultUrl()already does. Before,servers[0].urlwashard-wired to
location.originwhiledefaultServer's key camefrom the override; the server-context resolver
(
allServers().find(key === state.active) ?? allServers()[0]) fellback to
allServers()[0]when the two keys didn't align, andcontrol-plane calls like
/global/configand/global/eventbypassed the override entirely. Aligning both entries fixes that.
The commit message on
88acdbc27has the per-change rationale in moredepth.
How did you verify your code works?
Built from source (
bun run --cwd packages/opencode build) andinstalled
opencode-linux-x64/bin/opencodeon a Linux test box. Ranthe binary in two configurations:
works, session/prompt round-trip works, provider auth works. No
behavior change vs. unmodified opencode.
path. Before these changes: asset requests resolved to the proxy's
origin root (wrong MIME type, SPA-fallback HTML), CSP blocked
evalwhen the SPA bundled code ran, and
/global/*SDK calls wentdirectly to
location.originignoring the configured server URL.After these changes: iframe mounts, the SPA bundle executes, all
SDK calls (including control-plane) route through the configured
server URL via the proxy.
Screenshots / recordings
Not attached — happy to record if it'd help.
Checklist