iii / worker
$worker

console

v0.1.2

Web console for iii — bundles the React UI and proxies the engine WebSocket on a single port.

  • macOS: arm64 · x64
  • Linux: arm64 · armv7 · x64
  • Windows: arm64 · x64 · x86

install

install
$iii worker add console@0.1.2

configuration

iii-config.yaml
- http_port: 3113
README.md

console

A single binary. A chat. A trace explorer. The whole iii engine in your browser.

Install: iii worker add console License: Apache 2.0 Built with Rust React 19 Vite 8

console — chat and OpenTelemetry trace explorer in a single binary

Why console

  • One port, one binary. The React UI is baked into the executable with rust-embed, and the engine WebSocket is reverse-proxied at /ws on the same origin. No CORS preflight, no side-car static server, no dist/ directory to deploy. See src/assets.rs and src/proxy.rs.
  • Live engine, live UI. Functions, models, traces, and chat sessions all stream over a single WebSocket via the iii browser SDK. Mention any registered function with @, switch models on the fly, and watch the trace appear in the panel next to you. See web/src/lib/iii-client.ts.
  • Built for production. SIGINT and SIGTERM graceful shutdown (so docker stop and kubectl delete actually drain), URL credentials redacted from logs, immutable cache headers on content-hashed assets, and a fully self-contained binary with no runtime filesystem deps. See src/main.rs.

Features

Chat

A purpose-built agentic chat UI on top of Lexical. Lives in web/src/components/chat/.

  • Three modesplan, ask, and agent toggle right in the composer
  • Live model picker — provider-grouped from models::list; static fallback (OpenAI, Anthropic, Google) when the catalog is unreachable
  • @-mentions — fuzzy-search every function registered against the engine
  • /compact slash command — collapses conversation history via context-compaction::compact_session
  • Attachments — multi-file picker with text/image previews
  • Function calls — running / pending / error cards, consecutive calls grouped, with approve/deny gating for pending approvals (approval::resolve)
  • Streaming — abortable mid-flight; thinking shimmer; collapsible thought messages
  • Markdown — GFM, code blocks with prism-react-renderer, syntax-highlighted JSON inputs and outputs
  • Conversation sidebar — create, inline rename, delete, auto-title from the first message
  • Context-usage meter — token estimate with warn / danger thresholds and a /compact nudge
  • Session ID — copyable, deep-links every conversation into the trace explorer via iii.session.id
  • Persistence — conversations, active id, last model, sidebar state — all in localStorage

Traces

Full-fledged OpenTelemetry explorer over engine::traces::* and engine::logs::list. Lives in web/src/pages/Traces/.

  • Four visualizations — waterfall (virtualized for huge traces), flame graph, service map, and a dagre-laid-out flow view via @xyflow/react
  • Rich filtering — status (ok / error / unset), time presets, sort, min/max duration, service, operation, arbitrary attribute key/value pairs, debounced free-text search
  • Group by — none, message, session, function — server-side aggregation with lazy per-group span trees
  • Span detail tabs — info, attributes, events, errors, OTel logs, context (baggage), links
  • Live polling — pause / resume, hover-deferred refreshes, 25 / 50 / 100 page sizes
  • Resizable panels — drag to resize, double-click to reset
  • Critical-path breadcrumb with cross-trace parent jump

Live catalogs

The composer's @-mentions and the model picker pull from the engine in real time.

Theming

Light and dark themes via data-theme + CSS custom properties. Persisted to localStorage with an inline init script in index.html to prevent flash-of-wrong-theme on first paint.

Worker SDK surface

console registers a single function against the engine for health probes and iii worker info smoke tests:

Function Input Output
console::status {} { http_port, engine_url, version }

Defined in src/functions/status.rs.

Install

iii worker add console

This fetches the prebuilt binary, writes a console: block into ~/.iii/config.yaml, and the engine launches the worker on the next iii start.

Quickstart

iii start                       # engine on ws://127.0.0.1:49134
console --http-port 3113        # UI + /ws proxy on :3113
open http://127.0.0.1:3113

The browser hits / for the SPA shell and upgrades /ws to the engine WebSocket — one origin, no CORS, no API base URL to configure.

Programmatic check from the SDK
use iii_sdk::{register_worker, InitOptions, TriggerRequest};
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let iii = register_worker("ws://localhost:49134", InitOptions::default());

    let result = iii.trigger(TriggerRequest {
        function_id: "console::status".into(),
        payload: json!({}),
        action: None,
        timeout_ms: Some(5_000),
    }).await?;

    println!("{result:#?}");
    Ok(())
}

Returns { http_port, engine_url, version } — useful for liveness and readiness probes.

Architecture

flowchart LR
    Browser["Browser SPA<br/>(iii-browser-sdk)"] -->|"HTTP GET /"| Console
    Browser -->|"WS /ws"| Console
    Console["console binary<br/>(axum + rust-embed)"] -->|"WebSocket"| Engine["iii engine<br/>:49134"]
    Console -. "registers console::status" .-> Engine

console is a thin HTTP server with exactly two jobs: serve the embedded SPA bundle (with appropriate cache headers) and transparently proxy /ws to the iii engine. The browser only ever talks to one origin.

Configuration

config.yaml

http_port: 3113   # port the UI + /ws are served on (default: 3113)
Key Default Description
http_port 3113 TCP port the worker binds for /, /assets/*, and /ws

CLI flags

Flag Default Description
--config ./config.yaml Path to the YAML config
--url ws://127.0.0.1:49134 iii engine WebSocket URL (DEFAULT_ENGINE_URL in src/config.rs)
--http-port from config Overrides http_port from the config file
--manifest Print the publish manifest as JSON and exit (used by the registry pipeline)

Routes

Path Behavior
GET / Embedded index.html (SPA shell, hash-routed client-side). Cache-Control: no-cache, must-revalidate
GET /assets/* Embedded JS / CSS, content-hashed filenames. Cache-Control: public, max-age=31536000, immutable
GET /ws (Upgrade) WebSocket upgrade; transparent proxy to engine_url
anything else 404 Not Found

The SPA bundle is embedded into the binary at compile time via rust-embed — the released console has no separate dist/ directory, no side-car asset server, and no runtime filesystem dependency for the UI.

Tech stack

Layer Choice
Web server axum 0.7, tokio, tokio-tungstenite
Asset embedding rust-embed 8, mime_guess
Worker SDK iii-sdk 0.12
UI framework React 19, Vite 8, TypeScript 6
Styling Tailwind CSS v4, Radix UI, class-variance-authority, lucide-react
Editor Lexical 0.44
Data fetching TanStack Query 5
Trace graphs @xyflow/react 12 + dagre, TanStack Virtual
Markdown react-markdown + remark-gfm, prism-react-renderer
Browser SDK iii-browser-sdk 0.12
Build from source (contributors only)

cargo build runs pnpm install --frozen-lockfile && pnpm build inside web/ automatically when the web/dist/ bundle is missing or stale (Node + pnpm must be on PATH). To pre-build the bundle once and skip the embedded-asset rebuild loop:

cd web && pnpm install && pnpm build && cd ..
cargo build --release

Escape hatches (see build.rs):

  • SKIP_WEB_BUILD=1 — skip the JS build step entirely; the existing web/dist/ (if any) is embedded as-is. Useful in CI when the bundle was built in a previous stage.
  • PNPM=/path/to/pnpm — override pnpm discovery.

Run the test suite:

cargo test                          # unit + manifest + e2e (e2e self-skips if `iii` isn't on PATH)
cd web && pnpm test                 # vitest
cd web && pnpm typecheck && pnpm lint

License

Apache 2.0 — see LICENSE.