Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
- "tests/**"
- "scripts/**"
- "gui/**"
- "platform/**"
- "remote-agent/**"
- ".gitattributes"
- ".npmignore"
- "package.json"
Expand All @@ -26,6 +28,8 @@ on:
- "tests/**"
- "scripts/**"
- "gui/**"
- "platform/**"
- "remote-agent/**"
- ".gitattributes"
- ".npmignore"
- "package.json"
Expand All @@ -45,6 +49,70 @@ concurrency:
cancel-in-progress: true

jobs:
remote-agent:
name: Remote Agent ${{ matrix.package }}
runs-on: ${{ matrix.os }}
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
include:
- package: linux-x64
os: ubuntu-24.04
target: x86_64-unknown-linux-musl
executable: opencodex-remote-agent
- package: linux-arm64
os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
executable: opencodex-remote-agent
- package: macos-x64
os: macos-15-intel
target: x86_64-apple-darwin
executable: opencodex-remote-agent
- package: macos-arm64
os: macos-15
target: aarch64-apple-darwin
executable: opencodex-remote-agent
- package: windows-x64
os: windows-latest
target: x86_64-pc-windows-msvc
executable: opencodex-remote-agent.exe
- package: windows-arm64
os: windows-11-arm
target: aarch64-pc-windows-msvc
executable: opencodex-remote-agent.exe
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Install musl build tools
if: ${{ startsWith(matrix.package, 'linux-') }}
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends musl-tools

- name: Install pinned Rust target
working-directory: remote-agent
run: rustup target add "${{ matrix.target }}"

- name: Format
working-directory: remote-agent
run: cargo fmt --check

- name: Clippy
working-directory: remote-agent
run: cargo clippy --locked --target "${{ matrix.target }}" --all-targets -- -D warnings

- name: Test
working-directory: remote-agent
run: cargo test --locked --target "${{ matrix.target }}"

- name: Build release binary
working-directory: remote-agent
run: cargo build --locked --release --target "${{ matrix.target }}"

- name: Verify release binary exists
shell: bash
run: test -s "remote-agent/target/${{ matrix.target }}/release/${{ matrix.executable }}"

test:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
Expand Down
124 changes: 121 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ on:
default: true
expected-sha:
description: "Immutable release commit this dispatch must publish (fail if the branch moved)"
required: false
required: true
type: string

permissions:
Expand All @@ -40,9 +40,78 @@ concurrency:
cancel-in-progress: false

jobs:
remote-agent-build:
name: Remote Agent ${{ matrix.package }}
runs-on: ${{ matrix.os }}
timeout-minutes: 25
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- package: linux-x64
os: ubuntu-24.04
target: x86_64-unknown-linux-musl
executable: opencodex-remote-agent
- package: linux-arm64
os: ubuntu-24.04-arm
target: aarch64-unknown-linux-musl
executable: opencodex-remote-agent
- package: macos-x64
os: macos-15-intel
target: x86_64-apple-darwin
executable: opencodex-remote-agent
- package: macos-arm64
os: macos-15
target: aarch64-apple-darwin
executable: opencodex-remote-agent
- package: windows-x64
os: windows-latest
target: x86_64-pc-windows-msvc
executable: opencodex-remote-agent.exe
- package: windows-arm64
os: windows-11-arm
target: aarch64-pc-windows-msvc
executable: opencodex-remote-agent.exe
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Install musl build tools
if: ${{ startsWith(matrix.package, 'linux-') }}
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends musl-tools

- name: Install pinned Rust target
working-directory: remote-agent
run: rustup target add "${{ matrix.target }}"

- name: Build release binary
working-directory: remote-agent
run: cargo build --locked --release --target "${{ matrix.target }}"

- name: Stage release binary
shell: bash
run: |
mkdir -p remote-agent-artifact
cp "remote-agent/target/${{ matrix.target }}/release/${{ matrix.executable }}" \
"remote-agent-artifact/${{ matrix.executable }}"
test -s "remote-agent-artifact/${{ matrix.executable }}"

- name: Upload unsigned build artifact
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: remote-agent-${{ matrix.package }}
path: remote-agent-artifact/${{ matrix.executable }}
if-no-files-found: error
retention-days: 1

publish:
needs: remote-agent-build
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: ${{ inputs.dry-run && 'remote-agent-dry-run' || 'remote-agent-release' }}
steps:
- name: Checkout
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
Expand All @@ -54,7 +123,8 @@ jobs:
EXPECTED_SHA: ${{ inputs.expected-sha }}
run: |
if [ -z "$EXPECTED_SHA" ]; then
echo "::warning::no expected-sha supplied; publishing whatever the branch currently points at"
echo "::error::expected-sha is required; refusing to release an unaudited branch tip"
exit 1
elif [ "$GITHUB_SHA" != "$EXPECTED_SHA" ]; then
echo "::error::branch moved after the release audit (expected ${EXPECTED_SHA}, got ${GITHUB_SHA}) — refusing to publish an unaudited commit"
exit 1
Expand Down Expand Up @@ -92,6 +162,12 @@ jobs:
- name: Install dependencies
run: bun install --frozen-lockfile

- name: Download platform Agent artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
pattern: remote-agent-*
path: remote-agent-artifacts

- name: Verify version matches package.json
env:
RELEASE_VERSION: ${{ inputs.version }}
Expand Down Expand Up @@ -243,10 +319,51 @@ jobs:
fi
fi

- name: Assemble and sign Remote Agent bundle
shell: bash
env:
REMOTE_AGENT_SIGNING_KEY: ${{ secrets.REMOTE_AGENT_SIGNING_KEY }}
DRY_RUN: ${{ inputs.dry-run }}
run: |
set -euo pipefail
signing_key="$RUNNER_TEMP/opencodex-remote-agent-signing.pem"
public_key="$RUNNER_TEMP/opencodex-remote-agent-signing.pub.pem"
cleanup() {
if [ -f "$signing_key" ] && command -v shred >/dev/null 2>&1; then
shred -u "$signing_key"
else
rm -f "$signing_key"
fi
}
trap cleanup EXIT
umask 077
expected_public_key=""
if [ "$DRY_RUN" = "true" ]; then
openssl genpkey -algorithm ED25519 -out "$signing_key"
openssl pkey -in "$signing_key" -pubout -out "$public_key"
expected_public_key="$public_key"
echo "OPENCODEX_REMOTE_AGENT_DRY_RUN_PUBLIC_KEY_FILE=$public_key" >> "$GITHUB_ENV"
else
if [ -z "$REMOTE_AGENT_SIGNING_KEY" ]; then
echo "::error::REMOTE_AGENT_SIGNING_KEY is not configured in the protected release environment"
exit 1
fi
printf '%s' "$REMOTE_AGENT_SIGNING_KEY" > "$signing_key"
fi
REMOTE_AGENT_SIGNING_KEY_FILE="$signing_key" \
REMOTE_AGENT_EXPECTED_PUBLIC_KEY_FILE="$expected_public_key" \
GITHUB_SHA="$GITHUB_SHA" \
bun scripts/remote-agent-bundle.ts assemble \
remote-agent-artifacts \
bin/remote-agent \
.tmp/remote-agent-release

- name: Publish (or dry-run)
env:
DRY_RUN: ${{ inputs.dry-run }}
NPM_DIST_TAG: ${{ inputs.tag }}
OPENCODEX_REQUIRE_REMOTE_AGENT_BUNDLE: "1"
OPENCODEX_RELEASE_DRY_RUN: ${{ inputs.dry-run }}
run: |
if [ "$DRY_RUN" = "true" ]; then
echo "::notice::DRY RUN — building + packing, not publishing"
Expand Down Expand Up @@ -452,5 +569,6 @@ jobs:
git push origin "refs/tags/${release_tag}"
fi

gh release create "$release_tag" --target "$GITHUB_SHA" --title "$release_tag" \
gh release create "$release_tag" .tmp/remote-agent-release/* \
--target "$GITHUB_SHA" --title "$release_tag" \
--notes-file "$notes_file" ${prerelease_flag:+$prerelease_flag}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
node_modules/
dist/
/remote-agent/target/
.env
*.log
.DS_Store
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,14 @@ WebSocket transport is off by default. Set `"websockets": true` only if you want

### Remote access

The dashboard now has an invite-only **Remote** page for the central `opencodexpages.me` MVP. It starts
in local `ocx gui`: approve this PC with GitHub, set a separate Remote password, reserve a hostname,
and follow Tunnel/Agent provisioning. GitHub OAuth tokens are not stored or sent to the PC. The signed
privileged Linux helper installer and Windows/macOS helpers are not shipped yet, so this is not a
general release and **does not include Super Sync**.

The LAN bind below is a separate, self-managed option and does not use the central Remote service.

By default opencodex binds to `127.0.0.1` (loopback) and requires no extra authentication.
If you set `"hostname": "0.0.0.0"` to expose the proxy on the LAN, opencodex requires a bearer token
to protect both the management API (`/api/*`) and the data-plane (`/v1/responses`,
Expand Down
3 changes: 3 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions design-qa.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Design QA — OpenCodex Remote

result: blocked

## Sources

- `platform/docs/assets/instances-reference.png` — approved Instances screen, 1488×1058.
- `platform/docs/assets/agent-onboarding-reference.png` — approved Agent onboarding, 1488×1058.
- Implementation: `platform/web/src/App.tsx`, `platform/web/src/styles.css`.

## Completed checks

- Both approved source images were opened at original resolution before implementation.
- Existing OpenCodex logo and dark design tokens were reused.
- Visible UI icons come from `@tabler/icons-react`; no custom SVG or placeholder illustration was added.
- Desktop and responsive CSS states, loading/error/empty/login/invite states, primary actions, form flow, copy actions, and destructive confirmations are implemented.
- TypeScript and production build passed after the final auth/invite handoff edits.

## Blocker

The environment had no browser tool and no previously selected browser. The Product Design workflow requires permission before using Playwright directly. Permission was requested, but the task changed to a branch handoff before a response was received. Therefore no same-viewport implementation screenshot exists and the mandatory side-by-side source/implementation comparison cannot honestly be marked passed.

## Required next pass

1. Start `VITE_REMOTE_DEMO=true bun run dev:web` in `platform/`.
2. With the user's selected browser or approved Playwright Chromium, capture 1488×1058 Instances.
3. Click New instance, reserve the demo instance, and capture 1488×1058 Install Agent.
4. Put each reference and matching implementation capture into one side-by-side image.
5. Review typography, grid, spacing, colors, icon alignment, borders, states, keyboard/focus, tablet, and mobile.
6. Fix all P0/P1/P2 findings and change only the final line below when the comparison genuinely passes.

Final result: blocked
18 changes: 18 additions & 0 deletions docs-site/src/content/docs/guides/web-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,21 @@ they expire or the proxy restarts. Only a dashboard bound to a non-loopback host
the admin token (`OPENCODEX_ADMIN_AUTH_TOKEN`, or the auto-generated
`~/.opencodex/admin-api-token` file).

## Remote private beta

The **Remote** page makes the local `ocx gui` the start of remote setup:

1. Continue with GitHub. `opencodexpages.me` handles OAuth and asks you to approve the device code.
2. Set a separate Remote password. GitHub OAuth tokens are not stored or sent to the local PC.
3. Choose a unique one-level `<name>.opencodexpages.me` address and a unique name for this computer.
4. Start the signed, unprivileged Agent from the page. It opens one outbound encrypted WSS connection;
no root access, port forwarding, or inbound listener is required. Visiting the assigned hostname
later requires both the owning GitHub account and the Remote password.

This remains an invite-only beta. Release packages carry signature-verified Agent binaries for Linux,
macOS, and Windows on x64 and arm64. Unsupported OS/architecture combinations fail honestly instead of
compiling code on the user's computer. **Super Sync is not part of this flow.**

## What you can do

| Area | What it does |
Expand All @@ -43,6 +58,7 @@ the admin token (`OPENCODEX_ADMIN_AUTH_TOKEN`, or the auto-generated
| **Providers** | Add, edit, enable/disable, and remove providers; manage OAuth account pools and API-key pools where supported. Provider Settings can disable live model discovery for endpoints with missing, slow, or oversized `/models` catalogs. For Claude (Anthropic) OAuth pools, each logged-in account shows its own 5-hour and weekly rate-limit bars (usage is per credential); a failed probe keeps the last-known bars and marks them unavailable until the next successful refresh. |
| **Add provider** | Search registry-backed presets for account login, API-key services, local servers, or a custom endpoint. |
| **Codex Auth** | Add ChatGPT/Codex pool accounts, select the next-session account, refresh 5h / weekly / 30d quotas, enable or disable quota auto-switch, set its 1–100% threshold, and configure transient-failure failover. |
| **Remote** | Connect this PC to the central GitHub identity service, set a separate E2EE password, reserve its hostname, and start its signed outbound Agent. |
| **Subagents** | Feature up to five bare native or namespaced routed models in the `spawn_agent` override list. |
| **Models** | Toggle native GPT and routed models, set provider allowlists and context caps, choose v1/base/v2, and configure the v2 thread limit. Configured providers stay visible as zero-model groups when discovery is off or returns no rows. |
| **Logs** | Auto-refresh recent requests with tokens, requested effort and (when available) effective outbound effort, resolved model, provider, status, request id, duration, and error details. The detail view includes the exact reasoning wire field when the adapter emits one. Filter by opaque conversation/session id (when the client sends one) to total tokens and estimated list-price cost for the currently loaded Logs ring. |
Expand Down Expand Up @@ -121,6 +137,8 @@ The GUI is a thin client over the proxy's JSON management API. Useful endpoints
| Endpoint | Purpose |
| --- | --- |
| `GET` / `PUT /api/settings` | Read settings or toggle Codex autostart. |
| `GET /api/remote/status` · `POST /api/remote/link` · `PUT /api/remote/password` | Read local Remote state, start device approval, and set the separate access password without exposing device or GitHub tokens to the browser. |
| `POST /api/remote/activate` · `POST /api/remote/agent/start` · `POST /api/remote/pairing-code` · `DELETE /api/remote/device` | Reserve the workspace/computer names, start the packaged Agent, use the legacy pairing path, or revoke this PC. |
| `GET /api/startup-health` | Read secret-free routing, service, shim, and restart-safety diagnostics. |
| `POST /api/startup-action` | Install the background service or Codex launcher shim through fixed, allowlisted actions. |
| `GET` / `POST /api/windows-tray` | Read or change the Windows tray installation and visible-process state. POST accepts `install`, `start`, `stop`, or `uninstall`. |
Expand Down
Loading
Loading