feat(serve): Franklin desktop agent server + backend (cloud sync, wallet holdings/swaps, cost-saver)#80
Conversation
A WebSocket server that drives the real interactiveSession agent loop for the desktop app / local web UI. Speaks an envelope wire protocol (agent.send / session.* / wallet.info / models.list) and maps StreamEvents to the UI: - streams text, tool steps (grouped), media artifacts - wallet.info returns address + live USDC balance - per-turn model switching via injected /model - serves generated media files over a /file route (audio/video/image) Unlike `franklin panel` (a read-only dashboard) this actually runs agent turns with the same tools, wallet, routing and signing as the CLI. New: src/webui/server.ts, src/commands/webui.ts; registers the `webui` command.
Drops the webui naming (the local agent server backs the desktop app, not just a browser UI). src/webui → src/serve, webuiCommand → serveCommand, startWebuiServer → startServer.
When the CLI is run via Electron-as-node (ELECTRON_RUN_AS_NODE=1 — how the
packaged desktop app spawns `franklin serve`), commander detected
process.versions.electron + no defaultApp and sliced argv as a packaged
electron app, treating the script path as the command ('unknown command
.../dist/index.js'). Forcing from:'node' keeps [exec, script, ...args].
…gs/swaps, cost-saver - serve/server.ts: model provider grouping, cost-saver settings, wallet spend/tokens/swaps RPCs, cloud-first history load/save, session isolation, per-tool detail - serve/cloud-sync.ts: SIWE cloud sync (wallet identity → franklin.run conversations API) - stats/swap-log.ts: record swaps to ~/.blockrun/swaps.jsonl - agent (llm/loop/streaming-executor/types): bloat-compaction + cost-saver, model fallback guards, per-tool input preview - commands/config.ts: cost-saver key; zerox tools: append swaps to the log
Code reviewFound 5 issues:
Lines 630 to 632 in b8e5074
Lines 613 to 625 in b8e5074
Franklin/src/serve/cloud-sync.ts Lines 103 to 112 in b8e5074
Lines 1159 to 1169 in b8e5074
Franklin/src/tools/zerox-base.ts Lines 554 to 556 in b8e5074 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
VickyXAI
left a comment
There was a problem hiding this comment.
Requesting changes per the review above.
Blocking (must fix before merge):
- WebSocket origin/auth check — the desktop app should pass a handshake token (or the server should verify the
Originheader), otherwise any web page can drive a trust-mode agent with spend authority. /filepath confinement — restrict to the session work dir and dropAccess-Control-Allow-Origin: *.
Both come from the same wrong assumption: loopback-only ≠ local-process-only. Browsers reach 127.0.0.1 freely.
Should also fix in this PR (small diffs):
3. Serialize cloudSync (promise-chain mutex) — wrong cloud deletes are data loss.
4. Keep the $1.00 emergency compact unconditional; apply the worthIt ROI gate only to the 15-call/$0.03 trigger.
5. Gate appendSwap in zerox-base.ts on receipt confirmation, matching the gasless tool.
…ng (#81) * feat(channels): restore Slack bot + Telegram allowlist & group @-mention gating - Slack: restore the `slack` command + channel (was uncommitted WIP) and register it in the CLI - Telegram: add TELEGRAM_ALLOWED_USERS allowlist (owner always allowed); in groups only act on @mention or reply-to-bot (mention stripped before the prompt) and ignore other group chatter; private chats unchanged * fix(telegram): one 'Working…' ping per turn instead of one per tool call * feat(telegram): per-turn tool-usage summary + /tools toggle (persisted)
- serve: gate WS upgrades + /file behind an Origin allowlist (loopback != local-process-only — any web page can reach 127.0.0.1); optional FRANKLIN_SERVE_TOKEN handshake token for defense-in-depth - serve: confine /file to media files under the work dir (+ symlink-resolved prefix check, extension allowlist); reflect vetted origins instead of ACAO * - cloud-sync: serialize sync passes via a promise queue — concurrent fire-and-forget calls interleaved on the shared lastSynced map and could delete a conversation a concurrent pass just uploaded - loop: keep the $1.00 emergency compact unconditional; the >=20% ROI gate now applies only to the 15-call trigger (the cap exists to stop runaway turns) - zerox-base: wait for the tx receipt and record to the swap log only on confirmed success, matching the gasless tool; reverted swaps now report as errors instead of '✓ executed' - slack: batch per-tool pings into one turn-end summary (same fix as 7e527e8 on Telegram); reset toolsUsed on /new in both channels - telegram: English user-facing strings for /tools toggle + tool summary - deps: declare @slack/bolt (franklin slack crashed at runtime without it)
VickyXAI
left a comment
There was a problem hiding this comment.
All five review findings addressed in 74db8bc: WS Origin allowlist + optional handshake token, /file confined to work-dir media with reflected CORS, cloudSync serialized, $1.00 emergency compact unconditional again, swap log gated on receipt confirmation. Slack/Telegram follow-ups (missing @slack/bolt dep, per-tool ping flood, English strings) fixed in the same commit. Verified live: evil-origin WS upgrade → 401, /file path escape → 403, legit media → 200.
The Franklin-agent side of the Franklin Desktop app.
What
franklin serve— local WebSocket agent server (localhost:3737) the desktop renderer connects to (renamed fromfranklin webui).history.load/save, per-conversation session isolation, per-tooldetail.~/.blockrun/swaps.jsonl; zerox tools append on success.Notes