* feat: add message-level conversation fork and rewind Expose native Codex/Grok/Claude history controls through hub REST+RPC and web ConfirmDialog actions, without file rewind or composed forks. Also reconcile the duplicate hub V14→V15 migration so typecheck can pass. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: hydrate fork transcript and consume Claude --fork-session Forked HAPI children now copy the source transcript prefix so navigation is not a blank thread, and Claude drops --fork-session after the first launch so relaunches do not branch again. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: harden fork/rewind concurrency and durable history points Skip pending scheduled rows when hydrating fork transcripts, serialize fork/rewind per session, and persist conversation history points/indexes across existing-session bootstrap and Grok relaunches. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: close remaining fork/rewind races and UI anchoring Block sends and scheduled maturation while history actions run, order fork prefixes by invocation time, inherit history locators into children, and only offer Fork current on the live tail boundary. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: address remaining fork/rewind bot findings Materialize Claude --fork-session before the first child prompt, validate HAPI history boundaries before native RPC, expose forkCurrent on a latest user boundary, fully demote unsupported conversationHistory capabilities, and fix the truncate test setup order. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: align fork-current ids and Claude fork bootstrap Compare the latest fork boundary in assistant-ui threadMessageId space, spawn Claude forks with the persisted session mode, and preserve forkedFrom across existing-session bootstrap. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: close fork/rewind consistency holes at the contract layer Hold the source history lock until Claude child binds a distinct native id, persist Codex localId→turnId locators, and mark/block diverged sessions when native rewind outruns HAPI truncate. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: use Codex stable lastTurnId for historical fork Map HAPI's exclusive boundary to the previous turn's inclusive lastTurnId so native fork context matches the hydrated transcript. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: require exact Grok native resume for fork children Reject newSession fallback when forkedFrom is set, and keep the hub history lock until the child binds the forked grokSessionId. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: kill active fork children before failed-fork cleanup Bind/readiness failures can leave the child process running; deleteSession rejects active rows, so terminate first then remove the HAPI session. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: close remaining fork lock, hydrate, and todos gaps Reject mode switches during history actions, batch-copy fork transcripts in one SQLite transaction, and rebuild todos after fork hydrate / rewind truncate. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: allow Codex historical fork before the first turn Use experimental beforeTurnId when there is no previous turn for the stable inclusive lastTurnId boundary. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: mark Grok history busy immediately after dequeue Hub idle checks clear once messages-consumed fires; hold the busy flag across permission sync and rewind-points lookup before prompt starts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix: encode copied conversation history content * fix(web): hide local conversation history actions * style(codex): remove trailing whitespace * fix(fork): preserve children when cleanup is unconfirmed * fix(history): confirm cleanup and guard rewind divergence * fix(history): probe capabilities before advertising --------- Co-authored-by: Cursor <cursoragent@cursor.com>
hapi-web
React Mini App / PWA for monitoring and controlling hapi sessions.
What it does
- Session list with status, pending approvals, todos, and summaries.
- Chat view with streaming updates and message sending.
- Permission approval and denial workflows.
- Permission mode and model selection.
- Machine list and remote session spawn.
- File browser and git status/diff views.
- PWA install prompt and offline banner.
Runtime behavior
- When opened inside Telegram, auth uses Telegram WebApp init data.
- When opened in a normal browser, you can log in with
CLI_API_TOKEN:<namespace>(orCLI_API_TOKENfor the default namespace). - The login screen includes a top-right hub picker; if unset, the app uses the same origin it was loaded from.
- Live updates come from the hub via SSE.
Routes
See src/router.tsx for route definitions.
/- Redirect to /sessions./sessions- Session list./sessions/$sessionId- Chat interface./sessions/new- Create new session./sessions/$sessionId/files- File browser with git status./sessions/$sessionId/file- File viewer with diff support./sessions/$sessionId/terminal- Terminal interface./settings- Settings category hub (mobile) and responsive master-detail shell./settings/general- Language preferences./settings/display- Appearance, typography, colors, and session list preferences./settings/chat- Message input, tool card, and conversation color preferences./settings/voice- Everyday voice assistant preferences./settings/voice/voices- Full-page voice picker./settings/voice/advanced- Voice persona, tuning, and diagnostics./settings/about- Application links and version information.
Features
Session list (src/components/SessionList.tsx)
- Active/inactive status indicator.
- Session title from name, summary, or path.
- Todo progress display.
- Pending permission request count.
- Agent flavor label (claude/codex/gemini).
- Model mode display.
Chat interface (src/components/SessionChat.tsx)
- Message thread with infinite scroll.
- Composer for sending messages.
- Permission mode toggle (default/acceptEdits/auto/bypassPermissions/plan).
- Model selection (default/sonnet/sonnet[1m]/opus/opus[1m]).
- Session abort and mode switch controls.
- Context size display.
- Per-session scratchlist (
src/components/AssistantChat/ScratchlistPanel.tsx)- Workbench panel for held notes/drafts; distinct from the queue.
- Add/delete/reorder entries; promote to composer (copy) or queue (send).
- Persists across reloads via
localStoragekeyed per session. - Keyboard shortcut: Ctrl/Cmd+Shift+S to focus the add-input.
File browser (src/routes/sessions/files.tsx)
- Git status view (staged/unstaged files).
- File search with ripgrep.
- Navigate to file viewer.
File viewer (src/routes/sessions/file.tsx)
- File content display with syntax highlighting.
- Staged/unstaged diff view.
Terminal (src/routes/sessions/terminal.tsx)
- Remote terminal via xterm.js
- Real-time via Socket.IO
- Resize handling
Voice assistant
- ElevenLabs integration (@elevenlabs/react)
- Real-time voice control
New session (src/components/NewSession/)
Modular session creation:
- Machine selector
- Directory input with recent paths
- Agent type selector
- Model selector
- Permission mode toggle (YOLO mode)
Authentication
See src/hooks/useAuth.ts and src/hooks/useAuthSource.ts.
- Telegram Mini App: Uses initData from WebApp SDK.
- Browser: Uses CLI_API_TOKEN from login prompt.
- JWT tokens with auto-refresh.
Data fetching
See src/hooks/queries/ for query hooks and src/hooks/mutations/ for mutations.
- Sessions, messages, machines via TanStack Query.
- Git status and file operations.
- Optimistic updates for message sending.
Real-time updates
See src/hooks/useSSE.ts.
- SSE connection to
/api/events. - Session/message/machine update events.
- Automatic cache invalidation on events.
Stack
React 19 + Vite + TanStack Router/Query + Tailwind + @assistant-ui/react + xterm.js + @elevenlabs/react + socket.io-client + workbox + shiki.
Source structure
src/router.tsx- Route definitions.src/components/- UI components.src/hooks/- Data fetching and state hooks.src/api/client.ts- API client.src/types/api.ts- Type definitions.
Development
From the repo root:
bun install
bun run dev:web
If testing in Telegram, set:
HAPI_PUBLIC_URLto the public HTTPS URL of the dev server.CORS_ORIGINSto include the dev server origin.
Tests
Unit tests run under vitest + jsdom:
bun run test:web
End-to-end browser tests for the scratchlist component (real Chromium, real
inert focus blocking, real localStorage round-trips) live at the repo root
under e2e/:
bun run test:e2e # headless
bun run test:e2e:ui # Playwright UI mode (debug)
The spec drives a Vite-served fixture page (web/e2e-fixtures/scratchlist-fixture.html)
that mounts the production ScratchlistPanel in isolation, so no hub /
auth / socket setup is required.
Build
bun run build:web
The built assets land in web/dist and are served by hapi-hub. The single executable can embed these assets.
Standalone hosting
You can host web/dist on a static host (GitHub Pages, Cloudflare Pages) and point it at any hapi hub:
- Build the web app. If your static host uses a subpath, set the Vite base:
bun run build:web -- --base /<repo>/
- Deploy
web/distto your static host. - Set hub CORS to allow the static origin (
HAPI_PUBLIC_URLorCORS_ORIGINS). - Open the static site, click the top-right Hub button on the login screen, and enter the hapi hub origin.
Clear the hub override in the same dialog to return to same-origin behavior.