d68f003000
CI / Change detection (push) Has been cancelled
CI / Version drift (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Workflow RLM cache (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / npm wrapper smoke (push) Has been cancelled
CI / Mobile runtime smoke (push) Has been cancelled
CI / Workflow lint (push) Has been cancelled
CI / Documentation (push) Has been cancelled
Web Frontend / Lint & Type Check (push) Failing after 1s
Auto-close harvested PRs / close (push) Failing after 1s
cargo-deny / cargo-deny (bans licenses sources) (push) Failing after 1s
Security audit / cargo-audit (push) Failing after 1s
Sync to CNB / sync (push) Failing after 1s
cargo-deny / cargo-deny (advisories) (push) Failing after 3s
Web Frontend / Deploy to Cloudflare (push) Has been cancelled
735 lines
24 KiB
Markdown
735 lines
24 KiB
Markdown
# `codewhale remote-setup` - Tailscale-first design
|
|
|
|
Status: **design / revision**. This RFC revises the earlier cloud-first
|
|
`remote-setup` plan. Keep the accurate implementation work already present:
|
|
`codewhale remote-setup` exists today as a generate-only bundle wizard for
|
|
cloud plus chat bridge deployments, and `--apply` is still not implemented.
|
|
|
|
## Goal
|
|
|
|
Give users a guided, education-forward way to reach a local-first CodeWhale
|
|
runtime from another surface without accidentally publishing their agent.
|
|
|
|
Default posture:
|
|
|
|
1. **Local-first by default.**
|
|
2. **Tailnet-private when remote.**
|
|
3. **Public only when explicitly chosen.**
|
|
|
|
The wizard should ask:
|
|
|
|
> How do you want to reach CodeWhale?
|
|
|
|
and offer these paths, in this order:
|
|
|
|
1. This machine only (localhost)
|
|
2. Private devices with Tailscale (**Recommended**)
|
|
3. Telegram bot
|
|
4. Feishu/Lark bot
|
|
5. Weixin personal bridge
|
|
6. Public webhook / Funnel (**Advanced**)
|
|
|
|
The recommended remote answer is Tailscale Serve with the backend still bound
|
|
to `127.0.0.1`. Tailscale supplies device identity and encrypted transport.
|
|
Tailscale Funnel is public internet exposure and must stay advanced.
|
|
|
|
## Current implementation checkpoint
|
|
|
|
Verified against the codebase:
|
|
|
|
- `codewhale app-server --http` is the canonical HTTP/SSE runtime API entrypoint.
|
|
It delegates to the mature `serve --http` implementation.
|
|
- `codewhale app-server --mobile` is real and serves the phone control page at
|
|
`/mobile`.
|
|
- `--host`, `--port`, `--workers`, `--auth-token`, `--insecure-no-auth`, and
|
|
repeatable `--cors-origin` exist on `app-server --http` / `--mobile`.
|
|
- `--mobile` without `--host` binds to `0.0.0.0` by design. Use
|
|
`--host 127.0.0.1` when putting Tailscale in front of the runtime.
|
|
- `/health` and `/v1/runtime/info` are public bootstrap/supervision endpoints.
|
|
`/v1/*` control routes require the runtime bearer token unless auth is
|
|
explicitly disabled on a trusted loopback bind.
|
|
- `codewhale doctor --json` exists as the machine-readable local diagnostic.
|
|
- `codewhale remote-setup` exists, but today it is generate-only. Its current
|
|
matrix is cloud target (`lighthouse`, `azure`, `digitalocean`) x bridge
|
|
(`feishu`, `telegram`) x provider registry. It does **not** yet model
|
|
localhost, Tailscale, Weixin, or Funnel as first-class choices.
|
|
- Telegram and Feishu bridge validators exist as `npm run validate:config`.
|
|
Weixin currently has `npm run check`, but no validate-config script.
|
|
|
|
Accuracy note for the Tailscale recommendation: the requested setup uses
|
|
`app-server --http`, but the current runtime serves `/mobile` only in mobile
|
|
mode. This RFC keeps the target command shape for the recommended loopback
|
|
runtime, and documents the verified current-binary variant when the mobile page
|
|
is required:
|
|
|
|
```bash
|
|
# Runtime API only, verified:
|
|
codewhale app-server --http --host 127.0.0.1 --port 7878 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
|
|
# Runtime API plus /mobile, verified:
|
|
codewhale app-server --mobile --host 127.0.0.1 --port 7878 --auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
```
|
|
|
|
## Common runtime base
|
|
|
|
Every path starts from the same local runtime trust boundary.
|
|
|
|
```bash
|
|
CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
|
export CODEWHALE_RUNTIME_TOKEN
|
|
|
|
codewhale app-server --http \
|
|
--host 127.0.0.1 \
|
|
--port 7878 \
|
|
--auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
```
|
|
|
|
For the current binary, use `--mobile --host 127.0.0.1` instead of `--http` if
|
|
the path needs the built-in `/mobile` page.
|
|
|
|
Doctor-style local validation:
|
|
|
|
```bash
|
|
codewhale doctor --json
|
|
curl -fsS http://127.0.0.1:7878/health
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
|
|
http://127.0.0.1:7878/v1/runtime/info
|
|
```
|
|
|
|
Runtime mental model:
|
|
|
|
- Exposed by CodeWhale: only the address it binds. The recommended bind is
|
|
`127.0.0.1:7878`.
|
|
- Auth token: `CODEWHALE_RUNTIME_TOKEN`, passed as `Authorization: Bearer ...`
|
|
by clients and bridges. Legacy `DEEPSEEK_RUNTIME_TOKEN` remains a fallback.
|
|
- Provider secrets: stay in runtime configuration, not in bridge env files.
|
|
- Bridge secrets: stay in transport-specific env files.
|
|
|
|
## Guided flow
|
|
|
|
### 1. This machine only (localhost)
|
|
|
|
Use this when the TUI, SDK, browser, or local script runs on the same machine as
|
|
CodeWhale.
|
|
|
|
Setup:
|
|
|
|
```bash
|
|
CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
|
export CODEWHALE_RUNTIME_TOKEN
|
|
|
|
codewhale app-server --http \
|
|
--host 127.0.0.1 \
|
|
--port 7878 \
|
|
--auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
```
|
|
|
|
Env template:
|
|
|
|
```env
|
|
CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
|
|
CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
|
|
```
|
|
|
|
Validation:
|
|
|
|
```bash
|
|
codewhale doctor --json
|
|
curl -fsS http://127.0.0.1:7878/health
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
|
|
http://127.0.0.1:7878/v1/runtime/info
|
|
```
|
|
|
|
Trust boundary:
|
|
|
|
- Exposed: loopback only.
|
|
- Not exposed: LAN, tailnet, or public internet.
|
|
- Token used: `CODEWHALE_RUNTIME_TOKEN` for control routes; local `/health` and
|
|
`/v1/runtime/info` are public bootstrap endpoints.
|
|
|
|
### 2. Private devices with Tailscale (Recommended)
|
|
|
|
Use this to reach CodeWhale from your phone or laptop without opening a LAN or
|
|
public port. Tailscale authenticates devices in your tailnet; CodeWhale still
|
|
binds to localhost.
|
|
|
|
Target setup to feature in the wizard:
|
|
|
|
```bash
|
|
CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
|
export CODEWHALE_RUNTIME_TOKEN
|
|
|
|
codewhale app-server --http \
|
|
--host 127.0.0.1 \
|
|
--port 7878 \
|
|
--auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
|
|
tailscale serve --bg --https=443 localhost:7878
|
|
```
|
|
|
|
Then open the Tailscale Serve URL from a phone or laptop in the same tailnet.
|
|
For the current binary's mobile page, start CodeWhale with the verified mobile
|
|
variant:
|
|
|
|
```bash
|
|
codewhale app-server --mobile \
|
|
--host 127.0.0.1 \
|
|
--port 7878 \
|
|
--auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
```
|
|
|
|
Then open (put the token in the URL **fragment**, not a query param — the
|
|
`/mobile` page reads it from `location.hash`, and a fragment is never sent to
|
|
the Tailscale serving layer or to any proxy log):
|
|
|
|
```text
|
|
https://<machine>.<tailnet>.ts.net/mobile#token=<CODEWHALE_RUNTIME_TOKEN>
|
|
```
|
|
|
|
Env template:
|
|
|
|
```env
|
|
CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
|
|
CODEWHALE_RUNTIME_TOKEN=<openssl-rand-hex-32>
|
|
TAILSCALE_SERVE_TARGET=localhost:7878
|
|
TAILSCALE_SERVE_URL=https://<machine>.<tailnet>.ts.net
|
|
```
|
|
|
|
Validation:
|
|
|
|
```bash
|
|
codewhale doctor --json
|
|
curl -fsS http://127.0.0.1:7878/health
|
|
curl -fsS https://<machine>.<tailnet>.ts.net/health
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
|
|
https://<machine>.<tailnet>.ts.net/v1/runtime/info
|
|
tailscale serve status
|
|
```
|
|
|
|
Trust boundary:
|
|
|
|
- Exposed: an HTTPS endpoint reachable by devices authorized in your tailnet.
|
|
- Not exposed: the raw CodeWhale listener; it stays on `127.0.0.1`.
|
|
- Token used: Tailscale identity gates network reachability; CodeWhale still
|
|
uses `CODEWHALE_RUNTIME_TOKEN` for runtime control.
|
|
- Caveat: Tailscale Serve is private to the tailnet. Tailscale Funnel is public
|
|
internet exposure and belongs only in the advanced path below.
|
|
|
|
### 3. Telegram bot
|
|
|
|
Use this when a Telegram DM should control a local CodeWhale runtime. The bridge
|
|
uses Telegram Bot API long polling, so it does not require a public webhook URL
|
|
or inbound port.
|
|
|
|
Setup:
|
|
|
|
```bash
|
|
CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
|
export CODEWHALE_RUNTIME_TOKEN
|
|
|
|
codewhale app-server --http \
|
|
--host 127.0.0.1 \
|
|
--port 7878 \
|
|
--auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
|
|
cd integrations/telegram-bridge
|
|
npm install --omit=dev
|
|
cp .env.example .env
|
|
$EDITOR .env
|
|
npm run validate:config -- \
|
|
--env .env \
|
|
--workspace-root "$PWD/../.." \
|
|
--check-filesystem
|
|
npm start
|
|
```
|
|
|
|
Env template:
|
|
|
|
```env
|
|
TELEGRAM_BOT_TOKEN=replace-with-botfather-token
|
|
|
|
CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
|
|
CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
|
|
CODEWHALE_WORKSPACE=/path/to/workspace
|
|
# Optional override; leave blank to inherit the runtime's configured provider/model.
|
|
CODEWHALE_MODEL=
|
|
CODEWHALE_MODE=agent
|
|
CODEWHALE_ALLOW_SHELL=true # grants shell execution from the bridge; set false for text-only chat
|
|
CODEWHALE_TRUST_MODE=false
|
|
CODEWHALE_AUTO_APPROVE=false
|
|
|
|
TELEGRAM_CHAT_ALLOWLIST=
|
|
TELEGRAM_ALLOW_UNLISTED=false
|
|
TELEGRAM_ALLOW_GROUPS=false
|
|
```
|
|
|
|
First pairing:
|
|
|
|
```bash
|
|
# Temporarily in .env:
|
|
TELEGRAM_ALLOW_UNLISTED=true
|
|
```
|
|
|
|
DM the bot `/status`, copy the returned `chat_id` or `user_id` into
|
|
`TELEGRAM_CHAT_ALLOWLIST`, then set `TELEGRAM_ALLOW_UNLISTED=false` and restart
|
|
the bridge.
|
|
|
|
Validation:
|
|
|
|
```bash
|
|
codewhale doctor --json
|
|
curl -fsS http://127.0.0.1:7878/health
|
|
npm run validate:config -- \
|
|
--env .env \
|
|
--workspace-root "$PWD/../.." \
|
|
--check-filesystem
|
|
```
|
|
|
|
Trust boundary:
|
|
|
|
- Exposed: no inbound CodeWhale port. Telegram sees messages sent to the bot.
|
|
- Not exposed: CodeWhale remains on `127.0.0.1`; provider keys stay in the
|
|
runtime env, not the Telegram env.
|
|
- Tokens used: `TELEGRAM_BOT_TOKEN` for Telegram, `CODEWHALE_RUNTIME_TOKEN` for
|
|
bridge-to-runtime calls, and `TELEGRAM_CHAT_ALLOWLIST` for user/chat gating.
|
|
- Caveat: direct messages are the intended MVP control surface. Group control is
|
|
off unless `TELEGRAM_ALLOW_GROUPS=true`.
|
|
|
|
### 4. Feishu/Lark bot
|
|
|
|
Use this when a Feishu or Lark chat should control the local runtime. The bridge
|
|
uses the Lark/Feishu long-connection SDK, so the first version does not need a
|
|
public webhook URL.
|
|
|
|
Setup:
|
|
|
|
```bash
|
|
CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
|
export CODEWHALE_RUNTIME_TOKEN
|
|
|
|
codewhale app-server --http \
|
|
--host 127.0.0.1 \
|
|
--port 7878 \
|
|
--auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
|
|
cd integrations/feishu-bridge
|
|
npm install --omit=dev
|
|
cp .env.example .env
|
|
$EDITOR .env
|
|
npm run validate:config -- \
|
|
--env .env \
|
|
--workspace-root "$PWD/../.." \
|
|
--check-filesystem
|
|
npm start
|
|
```
|
|
|
|
Env template:
|
|
|
|
```env
|
|
FEISHU_APP_ID=cli_xxxxxxxxxxxxxxxx
|
|
FEISHU_APP_SECRET=replace-with-app-secret
|
|
FEISHU_DOMAIN=feishu # international Lark users: set to "lark"
|
|
|
|
CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
|
|
CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
|
|
CODEWHALE_WORKSPACE=/path/to/workspace
|
|
# Optional override; leave blank to inherit the runtime's configured provider/model.
|
|
CODEWHALE_MODEL=
|
|
CODEWHALE_MODE=agent
|
|
CODEWHALE_ALLOW_SHELL=true # grants shell execution from the bridge; set false for text-only chat
|
|
CODEWHALE_TRUST_MODE=false
|
|
CODEWHALE_AUTO_APPROVE=false
|
|
|
|
CODEWHALE_CHAT_ALLOWLIST=
|
|
CODEWHALE_ALLOW_UNLISTED=false
|
|
FEISHU_ALLOW_GROUPS=false
|
|
```
|
|
|
|
First pairing:
|
|
|
|
Temporarily set `CODEWHALE_ALLOW_UNLISTED=true`, message the app once, copy the
|
|
logged open id into `CODEWHALE_CHAT_ALLOWLIST`, then set
|
|
`CODEWHALE_ALLOW_UNLISTED=false` and restart the bridge.
|
|
|
|
Validation:
|
|
|
|
```bash
|
|
codewhale doctor --json
|
|
curl -fsS http://127.0.0.1:7878/health
|
|
npm run validate:config -- \
|
|
--env .env \
|
|
--workspace-root "$PWD/../.." \
|
|
--check-filesystem
|
|
```
|
|
|
|
Trust boundary:
|
|
|
|
- Exposed: no inbound CodeWhale port. Feishu/Lark sees messages sent to the app.
|
|
- Not exposed: CodeWhale remains on `127.0.0.1`; provider keys stay in runtime
|
|
config.
|
|
- Tokens used: `FEISHU_APP_ID` / `FEISHU_APP_SECRET` for the platform,
|
|
`CODEWHALE_RUNTIME_TOKEN` for bridge-to-runtime calls, and
|
|
`CODEWHALE_CHAT_ALLOWLIST` for chat gating.
|
|
- Caveat: group control is off unless explicitly enabled.
|
|
|
|
### 5. Weixin personal bridge
|
|
|
|
Use this when a personal Weixin account should control the local runtime by QR
|
|
login. This is not a public account webhook. The bridge initiates long polling
|
|
and does not need a public port.
|
|
|
|
Setup:
|
|
|
|
```bash
|
|
CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
|
export CODEWHALE_RUNTIME_TOKEN
|
|
|
|
codewhale app-server --http \
|
|
--host 127.0.0.1 \
|
|
--port 7878 \
|
|
--auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
|
|
cd integrations/weixin-bridge
|
|
npm install --omit=dev
|
|
cp .env.example .env
|
|
$EDITOR .env
|
|
npm run check
|
|
npm start
|
|
```
|
|
|
|
Env template:
|
|
|
|
```env
|
|
CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
|
|
CODEWHALE_RUNTIME_TOKEN=<same value used to start app-server>
|
|
CODEWHALE_WORKSPACE=/path/to/workspace
|
|
# Optional override; leave blank to inherit the runtime's configured provider/model.
|
|
CODEWHALE_MODEL=
|
|
CODEWHALE_MODE=agent
|
|
CODEWHALE_ALLOW_SHELL=true # grants shell execution from the bridge; set false for text-only chat
|
|
CODEWHALE_TRUST_MODE=false
|
|
CODEWHALE_AUTO_APPROVE=false
|
|
|
|
WEXIN_CHAT_ALLOWLIST=
|
|
WEXIN_ALLOW_UNLISTED=false
|
|
WEXIN_STATE_DIR=/var/lib/codewhale-weixin-bot-bridge
|
|
```
|
|
|
|
First pairing:
|
|
|
|
Set `WEXIN_ALLOW_UNLISTED=true`, start the bridge, scan the QR code, send
|
|
`/status`, copy the returned `user_id` into `WEXIN_CHAT_ALLOWLIST`, then set
|
|
`WEXIN_ALLOW_UNLISTED=false` and restart the bridge.
|
|
|
|
Validation:
|
|
|
|
```bash
|
|
codewhale doctor --json
|
|
curl -fsS http://127.0.0.1:7878/health
|
|
npm run check
|
|
```
|
|
|
|
Trust boundary:
|
|
|
|
- Exposed: no inbound CodeWhale port. The personal Weixin session and the
|
|
bridge state directory become sensitive local state.
|
|
- Not exposed: CodeWhale remains on `127.0.0.1`; provider keys stay in runtime
|
|
config.
|
|
- Tokens used: the scanned Weixin login/session state for platform access,
|
|
`CODEWHALE_RUNTIME_TOKEN` for bridge-to-runtime calls, and
|
|
`WEXIN_CHAT_ALLOWLIST` for user gating.
|
|
- Caveat: this is a personal-account bridge. Treat the host and state directory
|
|
like a logged-in phone session.
|
|
|
|
### 6. Public webhook / Funnel (Advanced)
|
|
|
|
Use this only when the user explicitly chooses public internet reachability,
|
|
understands that the URL can be reached outside the tailnet, and has a reason
|
|
that Tailscale Serve or long polling cannot satisfy.
|
|
|
|
Preferred advanced pattern:
|
|
|
|
```bash
|
|
CODEWHALE_RUNTIME_TOKEN="$(openssl rand -hex 32)"
|
|
export CODEWHALE_RUNTIME_TOKEN
|
|
|
|
codewhale app-server --mobile \
|
|
--host 127.0.0.1 \
|
|
--port 7878 \
|
|
--auth-token "$CODEWHALE_RUNTIME_TOKEN"
|
|
|
|
tailscale funnel --bg --https=443 localhost:7878
|
|
```
|
|
|
|
Env template:
|
|
|
|
```env
|
|
CODEWHALE_RUNTIME_URL=https://<public-name>
|
|
CODEWHALE_RUNTIME_TOKEN=<openssl-rand-hex-32>
|
|
PUBLIC_EXPOSURE_ACK=true
|
|
```
|
|
|
|
Validation:
|
|
|
|
```bash
|
|
codewhale doctor --json
|
|
curl -fsS http://127.0.0.1:7878/health
|
|
curl -fsS https://<public-name>/health
|
|
curl -fsS \
|
|
-H "Authorization: Bearer $CODEWHALE_RUNTIME_TOKEN" \
|
|
https://<public-name>/v1/runtime/info
|
|
tailscale funnel status
|
|
```
|
|
|
|
Trust boundary:
|
|
|
|
- Exposed: a public HTTPS endpoint, not just your tailnet.
|
|
- Not exposed by CodeWhale directly: the backend still binds to `127.0.0.1`,
|
|
but the fronting layer makes selected routes reachable from the internet.
|
|
- Token used: `CODEWHALE_RUNTIME_TOKEN` remains mandatory for control routes.
|
|
- Caveat: public does not mean safe. Do not use `--insecure-no-auth`, do not bind
|
|
CodeWhale to `0.0.0.0`, and do not call this the default.
|
|
|
|
## Cloud/VPS posture
|
|
|
|
Cloud/VPS is a placement choice, not a trust model. The old RFC's cloud work is
|
|
still useful, but it should sit behind the same reachability choices:
|
|
|
|
- A VPS can run the runtime bound to `127.0.0.1`.
|
|
- Recommended remote access from personal devices is still Tailscale Serve.
|
|
- Bot bridges should use long polling / long connection where available, keeping
|
|
the runtime localhost-only on the host.
|
|
- SSH tunnels remain acceptable for ad hoc validation:
|
|
|
|
```bash
|
|
ssh -L 7878:127.0.0.1:7878 <host>
|
|
```
|
|
|
|
Public inbound listeners, public webhooks, and Tailscale Funnel are advanced
|
|
choices, not the default cloud path.
|
|
|
|
## Prior art: Hermes Agent (reference only - do not copy)
|
|
|
|
Nous Research's Hermes Agent validates the table-driven part of this design.
|
|
Use it for ideas; keep CodeWhale's style: Rust core, local runtime, zero-dep
|
|
Node bridges where possible, and plain-text replies.
|
|
|
|
- `gateway/platform_registry.py` maps to our `BridgeSpec` / access-path
|
|
registry: one row per platform, with setup hints, required env, validation,
|
|
and adapter factory.
|
|
- `gateway/pairing.py` maps to our allowlist / first-pairing flow.
|
|
|
|
Telegram hardening carried forward from the original RFC:
|
|
|
|
| Edge case | In Hermes | In our Telegram bridge |
|
|
|---|---|---|
|
|
| 409 polling conflict | `_looks_like_polling_conflict` | done - poll loop backs off and warns |
|
|
| 429 `retry_after` | rate-limit handling | done - `telegramApi` honors `parameters.retry_after` |
|
|
| Forum General topic id handling | send/typing split | done - omit `message_thread_id` when id is 1 on send |
|
|
| Stale reply anchor after restart | retry without anchor | sidestepped - no `reply_to_message_id` |
|
|
| Network/connect timeout retry | network error detection | partial - generic poll-loop backoff |
|
|
| Text batching / progress edit | progress-edit tests | deferred - plain periodic chunks |
|
|
| MarkdownV2 escaping | escaping helpers | deferred - plain text |
|
|
| Webhook mode | webhook adapter | out of default scope - long polling first |
|
|
|
|
## Design principle: table-driven, like `ProviderSpec`
|
|
|
|
The provider registry is the model to preserve: adding a provider is one row.
|
|
Apply the same idea to access paths, bridges, and cloud placements so the matrix
|
|
grows by data.
|
|
|
|
```text
|
|
AccessPath x Placement x BridgeSpec + ProviderSpec
|
|
---------- --------- ---------- ------------
|
|
localhost local none deepseek / openai / ...
|
|
tailscale local/vps none provider lives in runtime.env
|
|
telegram local/vps telegram bridge is pure transport
|
|
feishu local/vps feishu bridge is pure transport
|
|
weixin local/vps weixin bridge is pure transport
|
|
funnel local/vps optional explicit public exposure
|
|
```
|
|
|
|
Clean separation:
|
|
|
|
- **Provider = runtime env.** The runtime resolves provider/model/API key from
|
|
`CODEWHALE_PROVIDER`, provider key vars, and the provider registry. Bridges do
|
|
not need provider keys.
|
|
- **Access path = reachability.** Localhost, Tailscale Serve, chat long polling,
|
|
and Funnel are separate choices with different trust boundaries.
|
|
- **Bridge = transport.** A chat bridge forwards allowed chat messages to
|
|
`http://127.0.0.1:7878` with `CODEWHALE_RUNTIME_TOKEN`.
|
|
- **Cloud = where it runs and where secrets live.** It is not permission to
|
|
open port 7878.
|
|
|
|
## Proposed command surface
|
|
|
|
Current flags are verified for the generate-only cloud/bridge wizard:
|
|
|
|
| Flag | Current status |
|
|
|---|---|
|
|
| `--cloud <lighthouse|azure|digitalocean>` | verified |
|
|
| `--bridge <telegram|feishu>` | verified |
|
|
| `--provider <slug>` | verified, provider registry-backed |
|
|
| `--out <dir>` | verified |
|
|
| `--generate-only` | verified |
|
|
| `--apply` | verified flag, but not implemented |
|
|
| `--yes` | verified flag |
|
|
| `--non-interactive` | verified flag |
|
|
|
|
Proposed Tailscale-first revision:
|
|
|
|
| Flag | Meaning |
|
|
|---|---|
|
|
| `--access <localhost|tailscale|telegram|feishu|weixin|funnel>` | Skip the reachability prompt. |
|
|
| `--placement <local|vps|lighthouse|azure|digitalocean>` | Where the runtime runs; default local. |
|
|
| `--bridge <telegram|feishu|weixin>` | Optional when `--access` implies a bridge. |
|
|
| `--provider <slug>` | Provider slug; validated against the existing provider registry. |
|
|
| `--out <dir>` | Bundle output dir. |
|
|
| `--generate-only` | Emit commands/env/runbook, do not provision. Default. |
|
|
| `--apply` | Future cloud CLI provisioning, behind confirmation. Still not implemented. |
|
|
| `--yes` | Skip final confirmation gates where safe for CI/non-interactive use. |
|
|
| `--non-interactive` | Fail instead of prompting for missing required values. |
|
|
|
|
The first prompt should be the reachability question, not the cloud question.
|
|
Tailscale should be visually marked as recommended.
|
|
|
|
## Generated bundle
|
|
|
|
The current bundle model stays useful. Extend it so the generated runbook is
|
|
access-path-first.
|
|
|
|
Files:
|
|
|
|
- `runtime.env` - provider and runtime config:
|
|
|
|
```env
|
|
CODEWHALE_PROVIDER=openai
|
|
OPENAI_API_KEY=replace-with-provider-key
|
|
# Optional override; leave blank to inherit the runtime's configured provider/model.
|
|
CODEWHALE_MODEL=
|
|
CODEWHALE_RUNTIME_TOKEN=<random>
|
|
CODEWHALE_RUNTIME_PORT=7878
|
|
CODEWHALE_RUNTIME_WORKERS=2
|
|
RUST_LOG=info
|
|
```
|
|
|
|
- `<bridge>.env` - transport only when a bridge is selected:
|
|
|
|
```env
|
|
CODEWHALE_RUNTIME_URL=http://127.0.0.1:7878
|
|
CODEWHALE_RUNTIME_TOKEN=<same random token>
|
|
CODEWHALE_WORKSPACE=/opt/whalebro
|
|
# Optional override; leave blank to inherit the runtime's configured provider/model.
|
|
CODEWHALE_MODEL=
|
|
CODEWHALE_MODE=agent
|
|
CODEWHALE_ALLOW_SHELL=true # grants shell execution from the bridge; set false for text-only chat
|
|
CODEWHALE_TRUST_MODE=false
|
|
CODEWHALE_AUTO_APPROVE=false
|
|
```
|
|
|
|
- `codewhale-runtime.service`
|
|
- optional `codewhale-<bridge>.service`
|
|
- optional cloud artifacts: `cloud-init.yaml`, `provision.sh`, `cnb.yml`, or
|
|
cloud-specific runbook steps
|
|
- `RUNBOOK.md` with:
|
|
- exact setup commands
|
|
- env template
|
|
- doctor-style validation
|
|
- first-pairing steps for bridges
|
|
- trust-boundary summary
|
|
- explicit "public exposure acknowledged" section for Funnel/webhook modes
|
|
|
|
## Auto-provision
|
|
|
|
Preserve the original safety model:
|
|
|
|
- `--generate-only` is the default.
|
|
- `--apply` is explicit and is not implemented today.
|
|
- Every command is rendered before execution.
|
|
- Secrets are not passed through shell history or argv.
|
|
- Cloud CLIs are placement helpers, not permission to open runtime ports.
|
|
|
|
Existing cloud target design remains accurate:
|
|
|
|
- Tencent Lighthouse: native plus systemd, env-file secrets, CNB-oriented plan.
|
|
- Azure VM: Docker image plus Key Vault, managed identity at boot.
|
|
- DigitalOcean Droplet: native plus systemd, env-file secrets, `doctl` plan.
|
|
|
|
All cloud plans should bind CodeWhale to `127.0.0.1` and then layer one of the
|
|
reachability paths above.
|
|
|
|
## Namespace migration: `DEEPSEEK_*` to `CODEWHALE_*`
|
|
|
|
Carry forward the convention already used in code: read `CODEWHALE_X` first,
|
|
fall back to `DEEPSEEK_X` where compatibility is needed.
|
|
|
|
Touch list from the original RFC remains valid:
|
|
|
|
1. Bridges: read `CODEWHALE_X ?? DEEPSEEK_X` for runtime URL/token, workspace,
|
|
model, mode, shell/trust/approval flags, allowlists, and timeouts. Templates
|
|
should emit `CODEWHALE_*`.
|
|
2. Deploy units: prefer `/etc/codewhale/*.env`; keep legacy path reads only for
|
|
compatibility where needed.
|
|
3. `.env.example` files and `config.example.toml`: lead with `CODEWHALE_*`,
|
|
document legacy aliases.
|
|
4. Drop DeepSeek-shaped defaults in bridge templates except where DeepSeek is
|
|
explicitly the chosen provider. Provider choice belongs in `runtime.env`.
|
|
|
|
## Tests
|
|
|
|
Existing bundle tests should stay:
|
|
|
|
- Every cloud / bridge / provider triple renders.
|
|
- Runtime and bridge env files share the same `CODEWHALE_RUNTIME_TOKEN`.
|
|
- Env files lead with `CODEWHALE_*`.
|
|
- Generated runbooks are non-empty and list the provision plan.
|
|
- Provision plans are command data and are not executed in tests.
|
|
|
|
New tests for this revision:
|
|
|
|
- Every `AccessPath` row has setup commands, env template, validation commands,
|
|
and trust-boundary copy.
|
|
- Tailscale is the recommended remote path in prompt ordering.
|
|
- Funnel/webhook mode requires an explicit advanced/public acknowledgement.
|
|
- `/mobile` docs use `app-server --mobile --host 127.0.0.1` for current binary
|
|
behavior, or clearly mark any `--http` plus `/mobile` path as proposed.
|
|
- Weixin can be documented before it is in the `remote-setup` registry, but the
|
|
wizard must mark it proposed until a `BridgeSpec` row and validation story
|
|
exist.
|
|
|
|
## Suggested sequencing
|
|
|
|
1. Revise the RFC and runbook copy to be Tailscale-first.
|
|
2. Add an access-path registry above the existing cloud/bridge/provider tables.
|
|
3. Add localhost and Tailscale generate-only bundles.
|
|
4. Add Weixin as a `BridgeSpec` row or explicitly hide it behind "proposed" in
|
|
the wizard until registry and validation support land.
|
|
5. Rework cloud bundles so placement is second and reachability is first.
|
|
6. Add Funnel/webhook only as an advanced path with explicit public-exposure
|
|
acknowledgement.
|
|
7. Implement `--apply` last, after generate-only output is reviewed.
|
|
|
|
## Command verification ledger
|
|
|
|
Verified against CodeWhale code/docs in this worktree:
|
|
|
|
- `codewhale app-server --http --host 127.0.0.1 --port 7878 --auth-token TOKEN`
|
|
- `codewhale app-server --mobile --host 127.0.0.1 --port 7878 --auth-token TOKEN`
|
|
- `codewhale doctor --json`
|
|
- `curl /health` and authenticated `curl /v1/runtime/info`
|
|
- `npm run validate:config` for Telegram and Feishu bridges
|
|
- `npm run check` for the Weixin bridge
|
|
- Existing `remote-setup` generate-only flags listed above
|
|
|
|
Marked proposed or external:
|
|
|
|
- `codewhale remote-setup --access ...` and access-path registry
|
|
- first-class Tailscale, localhost, Weixin, and Funnel choices in the wizard
|
|
- `--apply` execution
|
|
- Tailscale CLI commands (`tailscale serve ...`, `tailscale funnel ...`) are
|
|
external Tailscale commands. They are the intended RFC examples, but they are
|
|
not CodeWhale CLI flags.
|