Commit Graph
983 Commits
Author SHA1 Message Date
AnanovoandGitHub b28088bfe3 feat(web): show timestamps in conversation outline (#1111) 2026-07-27 19:52:33 +08:00
AnanovoandGitHub 5e8515c1a5 feat(web): add image preview navigation (#1100) 2026-07-27 19:27:45 +08:00
AnanovoandGitHub 24a2656a3b fix(web): center active scratchlist icon (#1105) 2026-07-27 19:27:20 +08:00
AnanovoandGitHub 4a63418103 feat(web): share conversation turns as images (#1047) 2026-07-27 19:26:38 +08:00
Junmo KimandGitHub e52443f4cf feat(web): global word-wrap toggle for code and diff views (#985)
* refactor(web): add per-line shiki line-splitting helper

* feat(web): add global word-wrap toggle for code, markdown, and diff views

* feat(web): add a shaded gutter background behind line numbers

* fix(web): make DiffView compact rows follow the global wrap setting
2026-07-27 19:09:59 +08:00
Aqi77suandGitHub d53e25700d fix(web): prefer live Hub connectivity over navigator status (#1051)
* fix(web): trust live hub connectivity over navigator status

* fix(web): align offline and reconnecting banner precedence

* test(web): provide i18n context to banner tests
2026-07-27 17:02:37 +08:00
weishu f4be735cb5 fix(hub,web): stop machines from showing raw id prefixes as names
Hub: getOrCreateMachine now merges incoming machine-owned metadata over
the stored row (first-write-wins previously kept rows registered without
a host name nameless forever; hub-only fields like displayName survive).

Web: session-list machine labels are cached in localStorage so machines
whose row is gone or whose query has not loaded yet keep their last
known name instead of flickering to the 8-char id prefix.
2026-07-27 13:10:45 +08:00
Junmo KimandGitHub bb5275a333 fix: recover Codex resume ID from stored messages (#1180)
* refactor(hub): generalize recovered ID helpers

* fix(hub): recover Codex resume ID from messages

* fix(web): allow Codex message recovery
2026-07-27 12:59:57 +08:00
d115f8d960 fix(web): stop touch taps from double-firing session navigation (#1185)
useLongPress binds both touch and mouse handlers. After a tap, touch
browsers emit compatibility mouse events (~300ms later) that the page did
not preventDefault, so onClick fired twice: once from touchend, once from
the synthesized mouseup. On the wide tablet sidebar layout the list stays
under the finger, so the second onClick lands on whatever row slid into
that position and navigates to the wrong session.

preventDefault() on touchend for every handled tap, and additionally
swallow mouse events that arrive within 700ms of a touch so browsers that
still dispatch the compatibility sequence cannot re-trigger onClick.

Based on the fix by RiriAgent in the fork (commits 1bbfcc20, 5e3d135a).

Co-authored-by: RiriAgent <39219425+RiriAgent@users.noreply.github.com>
2026-07-27 12:59:40 +08:00
weishuandGitHub 909aaca09d fix(cli): gate main package release on platform packages being live (#1187)
- prepare-npm-packages: exit(1) instead of warn-and-continue when a
  platform binary is missing, so a broken build aborts the release
- release-all: after publishing platform packages, poll npm view for
  every @twsxtd/hapi-<platform> package until it matches the release
  version (10min timeout, 15s interval) before publishing the main
  package

Fixes #1149
2026-07-27 12:59:02 +08:00
weishuandGitHub 54bddd9db1 fix(hub,web): query Codex models via machine RPC instead of session cwd (#1186)
SessionChat fetched Codex models through the session-scoped endpoint,
so the CLI listed models in the session process cwd, where a missing
directory or project-level Codex config could skew or break the result.
Use the machine-scoped endpoint (already used by NewSession) and drop
the now-unused session route and RPC plumbing.

Fixes #1072
2026-07-27 12:58:22 +08:00
weishuandGitHub da6f4cc5b0 fix(web): keep session preview fold while searching (#1183)
Searching the session list forced every directory group to expand all
sessions (expanded: isFiltering) and hid the Show more button, so the
user's per-group preview fold was ignored during filtering. Stop
overriding the preview state while filtering and keep the Show more /
Show less control available.

Fixes #1068
2026-07-27 12:57:51 +08:00
e4cc3916c8 fix(cursor): CreatePlan Yes accepts and continues task (nested outcome + plan→execute) (#1097)
* fix(cursor): nest ACP extension outcome so plan approvals aren't cancelled

Cursor's ACP blocking extension methods (cursor/ask_question,
cursor/create_plan) expect the JSON-RPC result to nest the outcome under
an `outcome` key, e.g. `{ outcome: { outcome: "accepted" } }`. The
adapter returned it flat (`{ outcome: "accepted" }`), so Cursor read
`response.outcome.outcome` as undefined and fell back to a cancellation
— an approved plan was relayed to the agent as `User cancelled`, making
plan mode unusable over HAPI for Cursor sessions.

Wrap every ask_question / create_plan response in the nested envelope
and add regression tests asserting the exact wire shape for the
affirmative approval -> CreatePlan path (plus approved_for_session,
reject, and abort).

Fixes #79

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cursor): continue task after CreatePlan Yes (plan→execute)

Nested ACP accept alone is not a complete fix: Yes unblocked create_plan
but the prompt turn still ended with the plan "done" and no execution.
Mirror Claude ExitPlanMode: on accept, leave plan/ask for an executable
mode and queue a continue prompt so the original user task keeps going.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cursor): sync runCursor enqueue mode after CreatePlan accept

Codex Major on #1097: setPermissionMode alone left runCursor's
currentPermissionMode stale, so the next user message could re-enter
plan/ask after Yes. Notify onPermissionModeChanged from CursorSession
so the enqueue source of truth stays aligned with the session.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 12:57:31 +08:00
311e0cef55 fix(web): exit scratchlist mode after successful promote-to-queue send (#960)
* fix(web): exit scratchlist mode after successful promote-to-queue (#959)

After Send to queue accepts, call onExitScratchlistMode so the operator
can continue normal chat. Rejected sends keep mode on. Unit + Playwright
smoke coverage.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): add execStartedAt/execCompletedAt to ToolCard test mock

Upstream ChatToolCall gained exec timestamps; ToolCard.test.ts mock
was missing them and broke CI typecheck after rebase onto main.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
2026-07-27 12:56:02 +08:00
weishu 2e54d9fdff feat(web): replace machine tree level with filter chips in session list
Single-machine users no longer expand a redundant machine layer; with
multiple machines a chip filter bar (persisted, with hover health popup)
replaces the collapsible machine headers. Directory groups now render
top-level with machine-name suffixes when unfiltered. Also removes the
redundant session/project count header text.
2026-07-27 11:55:43 +08:00
weishu 8297383dc0 Release version 0.24.0 2026-07-27 10:22:58 +08:00
weishu 042ffced8d fix(kimi): sync native local session titles 2026-07-27 10:19:25 +08:00
AnanovoandGitHub 84323496c6 fix(web): clarify settings heading hierarchy (#1177) 2026-07-27 07:38:50 +08:00
SSU-WEI HUANGandGitHub 500407c6b1 fix(codex): show catalog-default Fast tier (#1179)
* fix(codex): show catalog-default Fast tier

* fix(web): show inherited Fast tier in header
2026-07-27 07:38:24 +08:00
Junmo KimandGitHub deb5d63695 fix(cli,web): group orphaned subagent trace by parentToolUseId (#1175) 2026-07-26 23:01:46 +08:00
AnanovoandGitHub d90bde0b88 feat(web): show tool execution timing (#1140) 2026-07-26 15:08:40 +08:00
SSU-WEI HUANGandGitHub 44390af35c fix(web): stabilize message timestamps (#1152) 2026-07-26 15:08:15 +08:00
AnanovoandGitHub 351ebafe54 fix(web): connect circular action icon arrowheads (#1154) 2026-07-26 15:07:52 +08:00
SSU-WEI HUANGandGitHub 73bc45c1ae fix(codex): clear completed safety review prompt (#1156) 2026-07-26 15:07:24 +08:00
AnanovoandGitHub 75e803f359 fix(web): align session agent icons with text (#1159) 2026-07-26 15:05:31 +08:00
Haoqing WangandGitHub 51c2b4b3f5 fix(web): stop voice backend detection from raising the error banner (#1164) 2026-07-26 15:04:59 +08:00
TEEKandGitHub 31ad7a6616 fix(web): reasoning overflow and settings label spacing (#1166) 2026-07-26 15:04:39 +08:00
King StarandGitHub dbb063c112 fix(cli): preserve active Claude model on local handoff (#1168) 2026-07-26 15:04:12 +08:00
Junmo KimandGitHub 60d7489a59 fix(hub): archive active sessions with stale metadata (#1170) 2026-07-26 15:03:24 +08:00
SSU-WEI HUANGandGitHub c305c5ce31 fix(web): keep session sidebar stable after selection (#1173) 2026-07-26 15:02:52 +08:00
SSU-WEI HUANGandGitHub bd5e87898a feat(codex): support proactive /agent mode (#1172) 2026-07-26 15:01:53 +08:00
Haoqing WangandGitHub 6bddc9d044 fix(cli): stop internal agent events from leaking into chat as raw JSON (#1165)
* fix(cli): drop unknown SDK message types instead of passing them through

The SDK-to-log converter's switch had a fail-open default that stamped any
unrecognized SDK message with transcript base fields (parentUuid/sessionId/
userType) and forwarded it. Claude Code emits a tool_progress heartbeat every
30s for long-running tools, so a single slow Bash call flooded the chat: the
web normalizer matches no known shape for those records and falls back to
rendering the raw envelope as message text.

Gate the switch on an explicit allowlist instead, bailing before the uuid is
allocated so a dropped event cannot advance sidechain/parent tracking -- the
heartbeats share one parent_tool_use_id and were overwriting the pointer a
subagent's next real message parents to. This matches the local launcher,
which already enforces the same allowlist via RawJSONLinesSchema.safeParse.

The default branch stays as a fail-closed guard so adding a type to the
allowlist without a matching case drops the message rather than leaking it.

* fix(cli): re-check reassembled text for internal event JSON at flush boundary

isInternalEventJson was only applied per incoming chunk. In delta mode
(OpenCode) every chunk is a fragment, so none of them parses as JSON on its
own and the filter never fires; the pieces accumulate and flushText emits the
reassembled envelope verbatim. The dedupe path has the same hole whenever two
chunks share no overlap.

Check again in flushText, which is the first point the complete text exists,
and tolerate surrounding whitespace so an envelope preceded by a newline is
not waved through by the leading-'{' fast path.

Genuine assistant output that happens to be JSON is unaffected: the matcher
still requires the specific { type: 'output', data: { parentUuid, sessionId,
userType } } envelope shape.

* fix(cli): fail closed on unrecognized agent message in converter

convertAgentMessage's exhaustiveness default returned the message object
itself at runtime. The never binding makes the branch unreachable today, but
every caller forwards a non-null result straight into the chat stream, so the
failure mode if it were ever reached is a raw object on screen. Keep the
compile-time check, return null at runtime.

* test(cli): cover command_lifecycle, a second unknown type seen leaking

Observed in the same session after tool_progress. The allowlist already
covered it with no code change, which is the argument for gating on known
types rather than adding a case per offender.
2026-07-26 02:46:29 +08:00
weishu 8eac26726b Release version 0.23.4 2026-07-24 11:06:10 +08:00
NightWatcher314andGitHub 5300e1ee39 feat(codex): support file mentions (#774)
* feat(codex): support file mentions

* fix(codex): quote file mention paths with spaces

* fix(codex): keep punctuation outside file mentions

* fix(codex): avoid parsing literal at-mentions

* fix(codex): scope file mention autocomplete
2026-07-24 11:00:48 +08:00
SSU-WEI HUANGandGitHub 40c1789357 fix(acp): sync native agent session titles (#1028)
* fix(acp): sync native session titles

* fix(acp): keep title refresh off turn path

* fix: reconcile native ACP titles with skill lookup

* fix: preserve OpenCode image tool instruction
2026-07-24 10:59:47 +08:00
AnanovoandGitHub 72476b9cea fix(web): align session sidebar content widths (#1098) 2026-07-24 10:59:06 +08:00
AnanovoandGitHub 224ae07438 feat(web): customize composer toolbar layout (#1101)
* feat(web): customize composer toolbar layout

* fix(web): reorder toolbar across split boundary

* fix(web): match toolbar focus and visual order

* fix(web): support accessible toolbar reordering
2026-07-24 10:58:46 +08:00
AnanovoandGitHub 33015b67db feat(web): add conversation outline search (#1102)
* feat(web): add conversation outline search

* fix(web): keep outline close action accessible
2026-07-24 10:58:23 +08:00
AnanovoandGitHub df36cec01e feat(web): sort file search results (#1109) 2026-07-24 10:58:07 +08:00
AnanovoandGitHub ee5b0239cb fix(web): prevent narrow session tooltips (#1114) 2026-07-24 10:57:32 +08:00
AnanovoandGitHub 8c1d5057d7 feat(web): remember new-session model and effort (#1116)
* feat(web): remember new-session model and effort

* fix(web): wait for launch preference validation

* fix(web): wait for cwd preference validation
2026-07-24 10:57:12 +08:00
af8d160364 feat(cli): export HAPI_SESSION_ID into wrapped agent env (self-targeting) (#1121)
* feat(cli): export HAPI_SESSION_ID into wrapped agent env

Publish the hub session id into process.env at session bootstrap so every
downstream agent spawn inherits it. HAPI runs one hub session per CLI process
(the runner forks a fresh hapi child per session; local is 1:1) and every
flavor's agent spawn derives its child env from process.env, so a single seam
covers claude / codex / cursor / gemini / opencode / kimi / grok / pi -
runner-spawned and local - plus future flavors, without touching each launcher.

Agents can read HAPI_SESSION_ID to self-target their own hub session over REST
or shell helpers without listing /api/sessions. Prefer the MCP display_image
tool for inline media when available; HAPI_SESSION_ID is the deterministic
fallback for non-MCP tooling.

Closes #1119

Co-authored-by: Cursor <cursoragent@cursor.com>

* feat(scripts): self-target hapi-display-image via HAPI_SESSION_ID

Teach the in-tree shell helper to use $HAPI_SESSION_ID for path-only /
self invocations: GET /api/sessions/:id directly instead of listing
/api/sessions. Explicit session prefixes keep the previous list path.

Gives #1119 a tangible now benefit - the tool that forced the wasteful
list-and-reverse-lookup dance no longer needs it inside a wrapped session.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cli): defer HAPI_SESSION_ID export until lazy Codex materializes

The provisional lazy-session id was exported at bootstrap before the hub
row existed, so path-only self-targeting (GET /api/sessions/:id) could
404 while materialization was still pending. Export on onMaterialized
instead, and await materialize in buildHapiMcpBridge before starting the
MCP server / spawning Codex so the agent inherits an id the hub can
resolve (and so hapiMcpUrl is persisted, not only local pending state).

Addresses Codex review Major on #1121.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 10:56:59 +08:00
AnanovoandGitHub 0834dc098c feat(web): indicate session activity in date picker (#1103)
* feat(web): indicate session activity in date picker

* fix(web): expose session activity to assistive tech
2026-07-24 10:56:20 +08:00
KorenKritaandGitHub 735ccda168 fix(pi): report authoritative context usage (#1106) 2026-07-24 10:56:03 +08:00
AnanovoandGitHub b2ec09bd0c fix(web): preserve composer attachments across session switches (#1110)
* fix(web): preserve composer attachments across session switches

Persist composer files per session and restore completed uploads without
uploading them again. Clear attachment drafts alongside text after send and
cover restoration, isolation, and adapter reuse with regression tests.

Fixes #465

* fix(web): keep cleared attachment drafts tombstoned

Retain an empty in-memory cache entry until the queued IndexedDB delete completes so a fast remount cannot restore stale files. Add regression coverage for the clear/remount race.

* fix(web): defer attachment restore for inactive sessions

Only restore or clear attachment drafts while the session attachment adapter is available. Preserve saved files when an inactive session mounts without attachment support and add regression coverage.
2026-07-24 10:55:42 +08:00
AnanovoandGitHub 3021c9cba0 fix(web): zero-pad message and session timestamps (#1112) 2026-07-24 10:55:20 +08:00
TEEKandGitHub 81934cf354 fix(web): use dedicated split breakpoint for compact tablets (#1141)
* fix(web): use dedicated split breakpoint for compact tablets

Some compact Android tablets (e.g. OPPO Pad mini) report a landscape
CSS viewport below Tailwind's `lg` (1024px) despite having enough
physical screen space, so the sessions layout fell back to a single
column. Add a dedicated `split` breakpoint at 920px and use it for the
sessions split layout and the sidebar width/resize CSS, leaving the
global `lg` breakpoint (and all other pages) untouched.

* fix(web): cap sidebar width against viewport on compact split

A persisted sidebar width (up to 600px from resizing on desktop) could
shrink the detail pane to 316px at the new 920px split breakpoint, below
the previous 1024px worst case of 420px. Cap the sidebar width at
min(var(--sidebar-w), calc(100vw - 424px)) so the detail pane keeps at
least 420px down to 920px, with no effect on desktop.

* fix(web): seed sidebar drag from rendered width

When the compact-split viewport cap renders the sidebar narrower than the
persisted width, dragging the handle to shrink it had a dead zone until
the stored width fell below the rendered width. Seed the drag from the
sidebar's rendered width so it responds immediately; unchanged on desktop
where rendered and stored widths match.
2026-07-24 10:54:21 +08:00
3b025a69cc feat(web): surface Mermaid parse/render failure reason in fallback (#1145)
The mermaid fallback previously showed only the raw source with no reason,
making a supposedly-valid diagram that fails in HAPI (but passes the Mermaid
CLI) impossible to diagnose from the running UI. Every diagnostic was
swallowed: setParseErrorHandler no-op, suppressErrorRendering, parse with
suppressErrors returning false, and an empty catch.

renderMermaidSvg now returns a { svg, error } outcome. On a failed
suppressErrors parse it re-parses once (no side effects: parse-error handler
is a no-op, suppressErrorRendering stays on) purely to capture the thrown
reason; render() failures capture their message too. The fallback surfaces
that reason plus a data-mermaid-error attribute for automation, while keeping
the raw source verbatim. The failure notice is gated on an actual error so the
async load window does not flash a false 'could not render' banner.

Preserves the #785/#813 hardening (no error-SVG injection, no crash, no new
XSS surface). Addresses the weak-feedback half of #1117.

Refs #1117

Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 10:53:45 +08:00
f46e7301e8 Peer #1120: file-path autolink ergonomics (#1142)
* fix(web): autolink markdown links, inline code, and .mmd file paths in chat

Chat autolinking previously only worked for bare file paths in plain text.
Fancier markdown forms silently produced dead links:

- COMMON_FILE_EXTENSIONS omitted common agent-cited types (mmd, puml, rst,
  csv, ini, etc.), so bare diagram.mmd never linked.
- inlineCode nodes were never processed, so `path/to/file.md` never linked.
- explicit [label](relative/file.md) links kept a raw relative URL that the
  SPA router treated as a dead route under /sessions/.

Changes:
- Expand COMMON_FILE_EXTENSIONS with justified doc/diagram/config/lang exts;
  deliberately exclude TLD-lookalikes (org/com/io) to avoid domain false
  positives.
- Autolink inlineCode nodes whose ENTIRE value is a single path pattern match
  (whitespace-free, allowlisted ext), wrapping an inlineCode child to keep
  monospace. Real code snippets are left untouched.
- Rewrite explicit markdown links whose target is a repo-relative allowlisted
  file path into hapi-file: hrefs (aligns with #1113). Preserves label.

Security invariants preserved: shouldLinkPath still rejects abs / ~/ / ../ /
Windows-drive / scheme:// paths; scheme-bearing link urls are left for the
deny-scheme layer; deny-scheme handling untouched.

Refs tiann/hapi#1120

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): don't rewrite explicit links in standalone markdown preview

Codex review (#1142): rewriteFileLinkNode ran on the standalone file-preview
surface too, but that surface has no HappyChatContext so the shared `A` anchor
collapses hapi-file: links to plain text (returns props.children when !chat).
That turned an explicit [label](file.md) link in a README preview from an
anchor into plain text.

Gate explicit-link rewriting behind a rewriteExplicitLinks option (default on
for chat) and disable it for the standalone renderer via new
MARKDOWN_PLUGINS_STANDALONE(_WITH_BREAKS) arrays. Bare-path and inlineCode
autolinks are kept — they were already inert on the standalone surface, so no
behavior change there.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 10:53:35 +08:00
b7f1f4390f feat(web): copy session reference from context menu (#1144)
* feat(web): add Copy reference to session context menu

Refs tiann/hapi#950

Adds a More actions item that copies a cross-session citation
(see session "title" (/sessions/id) for context) instead of a
bare share URL.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): sanitize session titles in copy-reference text

JSON-escape titles and collapse whitespace so arbitrary session
names cannot inject prompt text into cross-session citations.

Addresses Codex review on tiann/hapi#951.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-24 10:53:07 +08:00