Skip to content
Open
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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ jobs:
- run: flutter pub get
- run: flutter analyze
- run: flutter test --reporter expanded
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm test --prefix gateway
7 changes: 5 additions & 2 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: stable
flutter-version: 3.27.1
cache: true

- run: flutter pub get
Expand All @@ -31,7 +32,7 @@ jobs:
# Wipe any stale derived data / pods that may be cached. Without this,
# back-to-back runs occasionally fail with "Building a deployable iOS
# app requires a selected Development Team" even though we pass
# --no-codesign a known macos-runner cache-pollution issue.
# --no-codesign ??a known macos-runner cache-pollution issue.
- name: Clean build state
run: |
flutter clean
Expand All @@ -49,7 +50,9 @@ jobs:
run: |
set -euo pipefail
cd ios
pod install --repo-update
if [ -f Podfile ]; then
pod install --repo-update
fi
xcodebuild \
-workspace Runner.xcworkspace \
-scheme Runner \
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ migrate_working_dir/
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
Expand All @@ -45,3 +46,5 @@ app.*.map.json
/android/app/release
.docker-pub-cache/
analyze.txt
gateway/.data/
gateway/node_modules/
36 changes: 19 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
# remote_multi_agent

A Flutter mobile client for [OpenCode](https://opencode.ai). Connects to a
remote OpenCode server (running on your laptop), tails its `/event` SSE stream,
and renders the live message + tool + reasoning flow on your phone.
A Flutter mobile client for local coding agents. It connects to the gateway
running on your laptop, streams normalized agent events, and renders Codex,
Claude Code, and OpenCode sessions in one project workspace.

## Why

- OpenClaw + 一来一回式 IM bots can't show you the agent's *progress* — only
the final answer.
- OpenCode emits a structured live event stream (`message.updated`,
`message.part.updated`, etc.) that is perfect for a real-time mobile UI.
- This app is a thin client: it carries no model keys; the OpenCode server you
point it at owns provider auth.
- Agent CLIs emit progress, tool use, and final answers; the gateway normalizes
that stream into one app protocol.
- This app is a thin client: it carries no model keys; your local gateway owns
provider auth and project filesystem access.

## Architecture

```
[Phone] remote_multi_agent (this app)
HTTPS / Bearer token
```text
[Phone] remote_multi_agent (Flutter)
HTTPS / SSE
|
[Tailscale] 100.x.x.x:4096
[Laptop] opencode serve --port 4096 --hostname 0.0.0.0
AI provider of your choice (Anthropic, OpenAI, local, …)
|
[Laptop] gateway/ Node server
|
Codex CLI / Claude Code CLI / OpenCode CLI
```

The gateway in `gateway/` owns local project directories, sessions, CLI
processes, and event normalization. The Flutter app does not execute shell
commands or read project files directly.

## Run / build matrix

| Target | Where to build | How to install |
Expand Down
112 changes: 112 additions & 0 deletions gateway/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Remote Multi Agent Gateway

Local HTTP/SSE gateway for the Flutter client. It owns filesystem access and
agent execution; the app only talks to this server.

## Supported Agents

- Codex: `codex exec --json`
- Claude Code: `claude -p --output-format stream-json --verbose`
- OpenCode: `opencode serve` HTTP/SSE proxy, with `opencode run --format json`
fallback when server mode is unavailable

The gateway uses the CLI login state already configured on this machine.

## Run

```powershell
cd gateway
npm start
```

Default URL:

```text
http://127.0.0.1:4096
```

For LAN or Tailscale access:

```powershell
$env:GATEWAY_HOST='0.0.0.0'
$env:GATEWAY_PORT='4096'
npm start
```

The first gateway version has no authentication, matching
`docs/development-spec.md`. Bind to `127.0.0.1` by default, and expose
`0.0.0.0` only behind a trusted network such as Tailscale.

## Configuration

| Variable | Purpose |
| --- | --- |
| `GATEWAY_HOST` | Bind host, default `127.0.0.1`. |
| `GATEWAY_PORT` | Bind port, default `4096`. |
| `GATEWAY_DATA_FILE` | JSON store path, default `gateway/.data/store.json`. |
| `GATEWAY_DIRECTORIES` | Extra roots returned by `GET /directories`, separated by OS path delimiter. |
| `CODEX_BIN` | Override Codex executable path. |
| `CODEX_SANDBOX` | Codex sandbox mode, default `workspace-write`. |
| `CLAUDE_CODE_BIN` | Override Claude Code executable path. |
| `CLAUDE_CODE_MODELS` | Comma-separated Claude model aliases to show in the picker. |
| `CLAUDE_CODE_PERMISSION_MODE` | Optional Claude permission mode, for example `acceptEdits` or `dontAsk`. |
| `OPENCODE_BIN` | Override OpenCode executable path. |
| `OPENCODE_SERVER_URL` | Use an existing OpenCode server instead of starting `opencode serve`. |
| `OPENCODE_SERVER_PASSWORD` | Password for an existing OpenCode server; sent with OpenCode's Basic auth scheme. Gateway-started OpenCode is local-only and does not set one by default. |
| `OPENCODE_SERVER_HOST` | Host for gateway-started OpenCode server, default `127.0.0.1`. |
| `OPENCODE_SERVER_PORT` | Port for gateway-started OpenCode server, default is a free port. |
| `OPENCODE_SERVER_START_TIMEOUT_MS` | Startup wait for `opencode serve`, default `45000`. |
| `OPENCODE_DEFAULT_MODEL` | Fallback model id when the app did not choose one, default `opencode/big-pickle`. |
| `OPENCODE_MODE` | OpenCode message mode, default `build`. |

For OpenCode, the gateway creates a native OpenCode session through
`POST /session?directory=...`, stores that id as `agentSessionId`, sends turns
through `POST /session/:id/message`, and bridges the global `/event` SSE stream
back into the gateway's per-session event endpoint.

## API

The gateway implements the app contract from `docs/development-spec.md`:

```text
GET /health
GET /projects
POST /projects
GET /projects/:projectId
DELETE /projects/:projectId
GET /directories
GET /files/dirs?path=<path>
POST /files/mkdir
GET /agents
GET /agents/:agentId
GET /agents/:agentId/models
GET /agents/:agentId/commands
GET /projects/:projectId/sessions
POST /projects/:projectId/sessions
GET /sessions/:sessionId
PATCH /sessions/:sessionId
DELETE /sessions/:sessionId
GET /sessions/:sessionId/messages
POST /sessions/:sessionId/messages
POST /sessions/:sessionId/abort
GET /sessions/:sessionId/events
```

`/sessions/:sessionId/events` is SSE. Each event uses the normalized envelope:

```json
{
"type": "message.delta",
"sessionId": "session-id",
"agentId": "codex",
"timestamp": 1779177600000,
"data": {},
"raw": {}
}
```

## Test

```powershell
npm test --prefix gateway
```
14 changes: 14 additions & 0 deletions gateway/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "remote-multi-agent-gateway",
"version": "0.1.0",
"private": true,
"description": "Local gateway for Claude Code, Codex, and OpenCode CLIs.",
"type": "commonjs",
"engines": {
"node": ">=20"
},
"scripts": {
"start": "node src/index.js",
"test": "node --test"
}
}
Loading
Loading