Files
weishu 9b299eaab7 feat(web): migrate to @assistant-ui/react 0.14 (tap 0.9.8 with update-depth fix)
- @assistant-ui/react ^0.11.53 -> ^0.14.29, react-markdown ^0.11.9 -> ^0.14.7
- resolves @assistant-ui/tap 0.9.8, which ships the upstream fix for
  bulk message prepends (per-scheduler MAX_UPDATE_DEPTH guard, PR
  assistant-ui/assistant-ui#5370) that the local patch covered for 0.3.5
- API migration: useAssistantApi -> useAui, useAssistantState -> useAuiState
  with s.* selector access; TextMessagePart type-guard for content.find;
  portable DefaultComponentsMap annotation for memoizeMarkdownComponents

Verified: tsc clean, 1762 unit tests, history-load e2e 12/12 against the
unpatched upstream scheduler.
2026-08-02 00:12:29 +08:00

8.1 KiB

AGENTS.md

Work style: telegraph; noun-phrases ok; drop grammar;

Short guide for AI agents in this repo. Prefer progressive loading: start with the root README, then package READMEs as needed.

What is HAPI?

Local-first platform for running AI coding agents (Claude Code, Codex, Gemini) with remote control via web/phone. CLI wraps agents and connects to hub; hub serves web app and handles real-time sync.

Repo layout

cli/     - CLI binary, agent wrappers, runner daemon
hub/     - HTTP API + Socket.IO + SSE + Telegram bot
web/     - React PWA for remote control
shared/  - Common types, schemas, utilities
docs/    - VitePress documentation site
website/ - Marketing site

Bun workspaces; shared consumed by cli, hub, web.

Architecture overview

┌─────────┐  Socket.IO   ┌─────────┐   SSE/REST   ┌─────────┐
│   CLI   │ ──────────── │   Hub   │ ──────────── │   Web   │
│ (agent) │              │ (server)│              │  (PWA)  │
└─────────┘              └─────────┘              └─────────┘
     │                        │                        │
     ├─ Wraps Claude/Codex    ├─ SQLite persistence   ├─ TanStack Query
     ├─ Socket.IO client      ├─ Session cache        ├─ SSE for updates
     └─ RPC handlers          ├─ RPC gateway          └─ assistant-ui
                              └─ Telegram bot

Data flow:

  1. CLI spawns agent (claude/codex/gemini), connects to hub via Socket.IO
  2. Agent events → CLI → hub (socket message event) → DB + SSE broadcast
  3. Web subscribes to SSE /api/events, receives live updates
  4. User actions → Web → hub REST API → RPC to CLI → agent

Reference docs

  • README.md - User overview, quick start
  • cli/README.md - CLI commands, config, runner
  • hub/README.md - Hub config, HTTP API, Socket.IO events
  • web/README.md - Routes, components, hooks
  • docs/guide/ - User guides (installation, how-it-works, FAQ)

Shared rules

  • No backward compatibility: breaking old formats freely
  • Prioritize Pragmatism, and Avoid Overengineering.
  • Write necessary tests ONLY.
  • TypeScript strict; no untyped code
  • Bun workspaces; run bun commands from repo root
  • Path alias @/* maps to ./src/* per package
  • Prefer 4-space indentation
  • Zod for runtime validation (schemas in shared/src/schemas.ts)

Common commands (repo root)

bun typecheck           # All packages
bun run test            # cli + hub tests
bun run dev             # hub + web concurrently
bun run build:single-exe # All-in-one binary

Key source dirs

CLI (cli/src/)

  • api/ - Hub connection (Socket.IO client, auth)
  • claude/ - Claude Code integration (wrapper, hooks)
  • codex/ - Codex mode integration
  • agent/ - Multi-agent support (Gemini via ACP)
  • runner/ - Background daemon for remote spawn
  • commands/ - CLI subcommands (auth, runner, doctor)
  • modules/ - Tool implementations (ripgrep, difftastic, git)
  • ui/ - Terminal UI (Ink components)

Hub (hub/src/)

  • web/routes/ - REST API endpoints
  • socket/ - Socket.IO setup
  • socket/handlers/cli/ - CLI event handlers (session, terminal, machine, RPC)
  • sync/ - Core logic (sessionCache, messageService, rpcGateway)
  • store/ - SQLite persistence (better-sqlite3)
  • sse/ - Server-Sent Events manager
  • telegram/ - Bot commands, callbacks
  • notifications/ - Push (VAPID) and Telegram notifications
  • config/ - Settings loading, token generation
  • visibility/ - Client visibility tracking

Web (web/src/)

  • routes/ - TanStack Router pages
  • routes/sessions/ - Session views (chat, files, terminal)
  • components/ - Reusable UI (SessionList, SessionChat, NewSession/)
  • hooks/queries/ - TanStack Query hooks
  • hooks/mutations/ - Mutation hooks
  • hooks/useSSE.ts - SSE subscription
  • api/client.ts - API client wrapper

Shared (shared/src/)

  • types.ts - Core types (Session, Message, Machine)
  • schemas.ts - Zod schemas for validation
  • socket.ts - Socket.IO event types
  • messages.ts - Message parsing utilities
  • modes.ts - Permission/model mode definitions

Pre-push self-review (agents)

Before commit/push/PR: use the pre-push-review skill (~/.cursor/skills/pre-push-review/).

  1. Mechanical: bun typecheck && bun run test (matches .github/workflows/test.yml)
  2. Logic: skim git diff origin/main...HEAD; apply .github/prompts/codex-pr-review.md as a local Major checklist (no Codex required)
  3. Style: optional

Testing

  • Test framework: Vitest (via bun run test)
  • Test files: *.test.ts next to source
  • Run: bun run test (from root) or bun run test (from package)
  • Hub tests: hub/src/**/*.test.ts
  • CLI tests: cli/src/**/*.test.ts
  • No web tests currently

Common tasks

Task Key files
Add CLI command cli/src/commands/, cli/src/index.ts
Add API endpoint hub/src/web/routes/, register in hub/src/web/index.ts
Add Socket.IO event hub/src/socket/handlers/cli/, shared/src/socket.ts
Add web route web/src/routes/, web/src/router.tsx
Add web component web/src/components/
Modify session logic hub/src/sync/sessionCache.ts, hub/src/sync/syncEngine.ts
Modify message handling hub/src/sync/messageService.ts
Add notification type hub/src/notifications/
Add shared type shared/src/types.ts, shared/src/schemas.ts

Important patterns

  • RPC: CLI registers handlers (rpc-register), hub routes requests via rpcGateway.ts
  • Versioned updates: CLI sends update-metadata/update-state with version; hub rejects stale
  • Session modes: local (terminal) vs remote (web-controlled); switchable mid-session
  • Permission modes: default, acceptEdits, auto, bypassPermissions, plan
  • Namespaces: Multi-user isolation via CLI_API_TOKEN:<namespace> suffix

Adding new web features — consider an FUE

When you ship a non-essential feature (the 20% of sessions, not the 80%), consider wrapping its affordance in the generic First-User-Experience primitive so existing users discover it without a giant always-visible UI block.

  • Hook: web/src/lib/use-fue.tsuseFue(featureId) returns { status, engage, dismiss }. Storage namespace hapi.fue.v1.<featureId> (one localStorage key per feature, isolated from any upstream onboarding flow).
  • Components: web/src/components/Fue.tsx<FueDot> (small pulsing badge for the affordance) and <FueCallout> (portal-rendered popover with title/body + "Got it" affirmative-action dismiss).

Pattern (~10 lines around the affordance):

const fue = useFue('my-feature')
const buttonRef = useRef<HTMLButtonElement>(null)
return (
    <>
        <button ref={buttonRef} onClick={() => { fue.engage(); doThing() }}>
            <Icon />
            {fue.status !== 'acknowledged' ? <FueDot pulsing={fue.status === 'unseen'} /> : null}
        </button>
        {fue.status === 'engaging' ? (
            <FueCallout
                title={t('myFeature.fueTitle')}
                body={t('myFeature.fueBody')}
                onDismiss={fue.dismiss}
                anchorRef={buttonRef}
            />
        ) : null}
    </>
)

Rules:

  • Affirmative action only: there is no auto-timeout — user dismisses by clicking "Got it" (reading speed varies).
  • The FUE dot and any feature-specific badge (e.g. an entry counter) should be mutually exclusive: onboarding signal beats inventory signal until acknowledged.
  • Storage is opt-in per-feature; if upstream ships its own onboarding for a feature, just don't wrap that affordance.

Canonical example: scratchlist toggle in web/src/components/AssistantChat/ComposerButtons.tsx (ScratchlistToggleButton).

Critical Thinking

  1. Fix root cause (not band-aid).
  2. Unsure: read more code; if still stuck, ask w/ short options.
  3. Conflicts: call out; pick safer path.
  4. Unrecognized changes: assume other agent; keep going; focus your changes. If it causes issues, stop + ask user.