Haoqing WangandGitHub 3a931e3c81 fix(web): count unseen messages by rendered block, not raw message (#1255)
* fix(web): count unseen messages by rendered block, not raw message

The "N new messages" pill counted raw DecryptedMessages while the
timeline renders folded blocks, so the two never agreed. A subagent run
is dozens of sidechain messages but a single Task card; a tool_use and
its tool_result are two messages and one card; consecutive tools collapse
into one group. The pill could read "47 new messages" when scrolling down
revealed two new rows.

collectNewUnseenIds never inspected isSidechain, and it could not: the
reducer's grouping is stateful (it needs the Task tool_use before it can
map parentToolUseId), so a per-message predicate in the store cannot
reproduce it. Adding an isSidechain check there would also invert the
error for orphan sidechain messages, which tracer.ts falls back to
emitting at the top level.

Instead, drop the store's unseen bookkeeping entirely and count what the
renderer actually produced. Watermark the visible blocks when the user
scrolls away from the tail, then count the blocks past the last one they
had seen.

The count is anchor-based rather than timestamp-based because the blocks
array is not monotonic in createdAt: messages sort by invokedAt ??
createdAt, so a queued message carries an old createdAt while sitting at
the end. Anchoring also makes prepended history free, since older blocks
land before the anchor.

Known limit, documented at the call site: once the history window fills
up, mergeIntoWindow trims incoming messages off the tail, so the pill
reports 0 instead of a count. Under-reporting is preferable here, and
returning to the tail force-refetches the latest page anyway.

* fix(web): keep unseen watermark stable across optimistic id replacement

The watermark snapshotted only block.id, but that id is not stable for
the user's own messages: mergeMessages replaces an optimistic row with a
stored row that keeps localId under a new server id, and the user block
renders with the message id. Scrolling into history while an own message
was still optimistic meant its echo anchored one block earlier and bumped
the pill by one, with no new rendered row.

Track localId alongside id in the watermark and match on either.

Reported by HAPI Bot on #1255.

* fix(web): count joined assistant cards, not pre-join blocks

visibleBlocks is still not one-to-one with rendered rows: assistant-ui
joins a run of adjacent assistant-role blocks into a single card, so a
response made of reasoning + text + a tool call was reported as three new
messages instead of one, and appending another block to an in-flight
response bumped the pill without adding a row.

Walk the blocks after the anchor and only start a new row where the
assistant run breaks.

Role assignment is the part that would drift, so rather than restating it,
visibleBlockRole moves from assistant-runtime.ts to toolGroups.ts (next to
the VisibleChatBlock definition it describes) and both the runtime and the
counter import the one copy.

Reported by HAPI Bot on #1255.
2026-07-30 23:24:01 +08:00
2026-07-29 19:53:51 +08:00
2026-01-04 20:45:15 +08:00
2026-07-13 08:41:30 +08:00
2026-03-29 09:47:17 +08:00
2025-12-16 15:03:50 +08:00

HAPI

Run official Claude Code / Codex / Cursor Agent / Grok Build / OpenCode sessions locally and control them remotely through a Web / PWA / Telegram Mini App.

Why HAPI? HAPI is a local-first alternative to Happy. See Why Not Happy? for the key differences.

Features

  • Seamless Handoff - Work locally, switch to remote when needed, switch back anytime. No context loss, no session restart.
  • Native First - HAPI wraps your AI agent instead of replacing it. Same terminal, same experience, same muscle memory.
  • AFK Without Stopping - Step away from your desk? Approve AI requests from your phone with one tap.
  • Your AI, Your Choice - Claude Code, Codex, Cursor Agent, Grok Build, OpenCode—different agents, one unified workflow.
  • Terminal Anywhere - Run commands from your phone or browser, directly connected to the working machine.
  • Voice Control - Talk to your AI agent hands-free using the built-in voice assistant.
  • Workspace Browser - Opt-in via one or more hapi runner start --workspace-root <path> flags: browse scoped file trees from the web and start sessions in allowed subdirectories.

Demo

https://github.com/user-attachments/assets/38230353-94c6-4dbe-9c29-b2a2cc457546

Getting Started

npx @twsxtd/hapi hub --relay     # start hub with E2E encrypted relay
npx @twsxtd/hapi                 # run claude code

hapi server remains supported as an alias.

The terminal will display a URL and QR code. Scan the QR code with your phone or open the URL to access.

The relay uses WireGuard + TLS for end-to-end encryption. Your data is encrypted from your device to your machine.

For self-hosted options (Cloudflare Tunnel, Tailscale), see Installation

Docs

Build from source

bun install
bun run build:single-exe

Credits

HAPI means "哈皮" a Chinese transliteration of Happy. Great credit to the original project.

S
Description
GitHub 镜像: wu736139669/hapi(每日自动同步)
Readme AGPL-3.0
103 MiB
Languages
TypeScript 99.5%
CSS 0.2%
JavaScript 0.2%
HTML 0.1%