* feat: improve spawn error handling and reporting across full stack
- Return error result instead of throwing in apiMachine spawn handler
- Add lastSpawnError field to RunnerState for persistent error tracking
- Add error awaiter system for early process exit/error detection before webhook
- Build detailed webhook failure messages with exit code, signal, and stderr tail
- Report spawn outcomes to hub via runner state updates
- Handle more spawn result types in rpcGateway with better error messages
- Display runner last spawn error in web UI (NewSession & SpawnSession)
- Extract shared formatRunnerSpawnError utility to avoid duplication
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): narrow spawnResult type check to fix TS2339 error
Use `type === 'error'` instead of `type !== 'success'` to properly
narrow the discriminated union, allowing TypeScript to infer errorMessage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: Add Claude Code Agent Teams support
- Add TeamState schemas and types for team collaboration
- Extract team state from TeamCreate, SendMessage, Task tools
- Add database migration V3→V4 for team_state storage
- Add TeamPanel component to display team members, tasks, messages
- Add team tool icons and presentation rules
- Support vite proxy configuration via VITE_HUB_PROXY env var
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix: Add timestamp protection for team_state updates
Prevent old messages from overwriting newer team state by checking
team_state_updated_at before updating.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix: Extract team tasks from Task/TaskCreate/TaskUpdate tools
- Enhance processTaskToolWithTeam to also generate task entries from
the Task tool's description field when spawning teammates
- Add processTaskCreate handler for TaskCreate tool calls
- Add processTaskUpdate handler for TaskUpdate tool calls
- Register both new tools in the extraction switch statement
This fixes the gap where the Tasks section in TeamPanel could never
populate because team task data was not being extracted from the
message stream.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* fix: Skip orphan TaskUpdate without title to prevent schema validation failure
When TaskUpdate arrives before TaskCreate (message ordering), skip inserting
incomplete tasks that lack required title field, preventing entire teamState
from being dropped by schema validation.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
* test: Add unit tests for orphan TaskUpdate handling
Verify that applyTeamStateDelta correctly skips inserting tasks without
title field (orphan TaskUpdate) while still allowing normal task creation
and updates to existing tasks.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
---------
Co-authored-by: tfq <tfq@gmail.com>
Co-authored-by: HAPI <noreply@hapi.run>
* feat(web): add appearance setting (follow system / dark / light)
Add user-facing appearance preference to the settings page, allowing
users to choose between Follow System, Dark, and Light themes. The
preference is persisted to localStorage and takes priority over
automatic detection in the existing theme pipeline.
* fix(web): update theme on cross-tab appearance change
The storage event handler only updated React state without calling
updateScheme(), leaving data-theme and useTheme subscribers stale.
* fix(web): move cross-tab appearance sync to global initializeTheme
The storage event listener was inside useAppearance(), which is only
mounted on the settings page. Other pages never received cross-tab
theme updates. Move the listener into initializeTheme() so all pages
respond to appearance changes from other tabs.
Replace hardcoded English strings with translation function calls
in SpawnSession.tsx and router.tsx. Add newSession.title key for
the NewSessionPage header to match the newSession.* namespace.
Add project-level slash command discovery with recursive nested command scanning, pass workingDirectory through slash-command handlers, and align hub/web source unions to include project commands.
* feat(cursor): add support for Cursor Agent CLI integration
- Introduced new command `hapi cursor` to start Cursor Agent sessions.
- Added functionality for resuming sessions and managing permission modes.
- Updated documentation to include Cursor Agent usage and installation instructions.
- Enhanced existing codebase to accommodate Cursor as a recognized agent flavor.
- Implemented local and remote session handling for Cursor Agent.
This update expands HAPI's capabilities by integrating support for the Cursor Agent, allowing users to leverage its features alongside existing agents.
* Remove TODO.md file as it is no longer needed following the integration of Cursor Agent CLI support. This cleanup helps streamline project documentation and reflects the completion of the associated tasks.
* feat(cursor): implement remote mode and fix --hapi-starting-mode
- Consume --hapi-starting-mode in cursor command (do not forward to agent)
- Implement cursorRemoteLauncher: spawn agent -p with stream-json, --trust
- Add cursorEventConverter for NDJSON parsing (system/assistant/tool_call/result)
- Multi-turn via --resume session_id
- Update docs: cursor supports both local and remote modes
Made-with: Cursor
* fix: type error
* fix(cursor): address PR review - model UI, sessionId metadata, duplicate flags
- HappyComposer: use isClaudeFlavor for model mode (cursor has no model modes)
- cursorLocalLauncher: call onSessionFound for resume so cursorSessionId in metadata
- cursorCommand: do not forward parsed flags to cursorArgs (avoid duplicates)
Made-with: Cursor
On iPadOS with a hardware keyboard, `isTouch` is true (via pointer:coarse)
so submitOnEnter is disabled and there's no keyboard-based way to send.
Add Shift+Enter as a send shortcut that works on all platforms without
changing existing behavior — desktop Enter-to-send is preserved.
Closes#175
via [HAPI](https://hapi.run)
Co-authored-by: HAPI <noreply@hapi.run>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(cli): support slash commands from installed plugins
Allows HAPI to automatically scan and load slash commands from installed Claude plugins (e.g. superpowers). Commands are indexed with their plugin namespace prefix.
* fix(web): add 'plugin' to SlashCommand source type
This fixes the typecheck error identified by the PR review bot.
* fix(web): update Suggestion type to include 'plugin' source
* fix(slashCommands): 修复 scoped 插件名称解析及多版本安装选择逻辑
- 使用 lastIndexOf('@') 替代 split('@')[0] 以正确解析带 scope 的插件名
- 按 lastUpdated 降序排序选择最新的安装实例
- 增加 installations 空数组边界检查
* feat(web): add built-in Nerd Font for terminal icon support
Add MesloLGLDZ Nerd Font as a built-in web font to ensure Powerlevel10k
and other Nerd Font-dependent prompts render correctly in the web terminal,
even on devices without local Nerd Fonts installed (e.g., mobile phones).
Changes:
- Add MesloLGLDZNerdFontMono-Regular.woff2 to web/public/fonts/
- Load font via FontFace API with BASE_URL support for subpath deployments
- Merge font loading and terminal creation in same useEffect to ensure
font is ready before terminal renders
Fixes#121
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: use CDN for Nerd Font instead of bundled file
Load MesloLGLDZ Nerd Font from jsDelivr CDN instead of bundling the 1.2MB
font file. This reduces the bundle size significantly while still ensuring
Nerd Font icons display correctly on all devices.
CDN URL: https://cdn.jsdelivr.net/gh/mshaugh/nerdfont-webfonts@v3.3.0/build/fonts/MesloLGLDZNerdFontMono-Regular.woff2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(web): prevent message ordering issues when switching sessions
When switching between sessions or loading paginated messages, old tool
blocks from agentState.requests were being mixed with current messages,
causing display order issues.
Root cause: reduceChatBlocks creates tool blocks for all permissions in
agentState, including those older than the current message page. These
old blocks (with earlier createdAt) would appear mixed with newer messages.
Fix:
1. In reducer.ts: Skip creating permission-only tool blocks if their
createdAt is older than the oldest message in the current view
2. In SessionChat.tsx: Clear caches synchronously in useMemo when session
changes, since useEffect runs after render
Closes#148
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
---------
Co-authored-by: tfq <tfq@gmail.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: HAPI <noreply@hapi.run>
- Add ReconnectingBanner component to show "Reconnecting..." status
- Track SSE connection state in App.tsx with onDisconnect handler
- Add onBlocked callback to useSendMessage for handling blocked send attempts
- Provide haptic feedback when send is blocked due to no API or session
- Show toast notification when message cannot be sent due to server disconnection
- Add translation keys for reconnecting message and send blocked feedback in en/zh-CN
close#125
* feat(web): add About section to settings page
- Add website link to hapi.run
- Display app version from CLI package
- Display protocol version from shared module
- Add Vitest testing setup with settings page tests
🤖 Generated with Claude Code
* test(web): add tests for website link and i18n key usage
Address residual risks mentioned in PR review:
- Test website link URL and security attributes (target, rel)
- Verify correct i18n keys are used for About section via spy
Simplify test setup by using real I18nProvider and en locale.
🤖 Generated with Claude Code
- Add Settings > Display > Font Size selector (80/90/100/110/120%)
- Persist preference (hapi-font-scale) and apply globally via --app-font-scale
- Normalize main UI typography to match Settings
Move getInputString, getInputStringAny, and truncate functions from
individual ToolCard components into a dedicated toolInputUtils module
to eliminate duplication across PermissionFooter, ToolCard, and
knownTools components.
Add ability to resume inactive sessions and automatically continue with new
session ID. Includes message merging for preserving conversation history,
UI improvements to allow sending during inactive state, and database schema
migration. close#87
Implement full support for the request_user_input tool with both Web UI and CLI
components. This includes:
- New view and footer components for request_user_input tool UI
- Tool registration in knownTools and view registries
- Nested answer format support (Record<string, { answers: string[] }>)
- Backward compatibility with flat answer format (Record<string, string[]>)
- Permission handler updates for CLI request_user_input acceptance
- Type definitions and schema updates across shared/cli/server/web packages
- Translation strings for request_user_input UI elements
- Conditional footer rendering in ToolCard for question tools
Adds support for Claude Code's microcompact_boundary system message type to display context microcompaction events with token savings information in the chat UI. Shows a package icon with formatted token count (e.g., "Context compacted (saved 32K tokens)"). close#108