mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
f29be07bb9f12f9acce00a8291dd477b990dccdc
36
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8b5cde7e44 |
feat(web): persist and send voice input message across session navigation (#1435) (#1436)
* feat(web): persist and send voice input message across session navigation (#1435)
* fix(web): address bot review findings for voice send across session switch (#1435)
* fix(web): resolve teardown, inactive session, send error recovery and mention serialization findings (#1435)
* fix(web): preserve draft initialText on error and handle async cross-session permission lookup (#1435)
* fix(web): preserve session draft on background failure and send via composer delivery policy (#1435)
* fix(web): send dictation directly via API and fix standard dictation language and recording support (#1435)
* trigger CI rerun for voice dictation session switch fixes (#1435)
* fix(web): prevent failed voice sends from clobbering replacement composer text (#1435)
* fix(web): prevent zero-byte recording failure from clobbering replacement composer draft (#1435)
* fix(web): prevent unmount voice transcription, clear draft on send success, and clear partial transcript (#1435)
* fix(web): stop button transcribes into composer without auto-sending; only send button triggers stopAndSend (#1435)
* ci: rerun CI for flaky claudeRemote test timeout (#1435)
* ci: retry flaky claudeRemote test (#1435)
* fix(web): guard dictation send path against attachments/schedule/connecting and protect newer drafts from late overwrite (#1435)
* fix(web): preserve Pi steer delivery mode for background dictation sends (#1435)
* fix(web): include active state in handleSend dependency array (#1435)
* fix(voice): surface MediaRecorder failures instead of transcribing partial audio
A MediaRecorder error can still be followed by dataavailable and stop
with partial bytes, so the previous onstop-only flow could transcribe
and auto-send corrupt audio. Track recorder.onerror and short-circuit
the stop handler: restore the draft and surface an error instead of
transcribing. Add a regression test for error -> dataavailable -> stop.
* fix(web): resume inactive sessions for voice dictation direct-sends
A voice dictation direct-send (mic -> send) posts the transcribed message
straight to the session, bypassing the text pipeline's inactive-session
resume. The hub rejects message POSTs to inactive sessions with 409
session_inactive, so sending to an archived session always failed (while
reopening it first made the same send succeed).
- stopAndSend now accepts { resolveSessionId, onSessionResolved } and
resolves the target session after transcription, before the POST
- HappyComposer allows direct voice sends on inactive sessions when a
resolver is provided (instead of downgrading to a plain stop), and
threads SessionPage's canonical resolveSessionId + handleSessionResolved
(the same ones the text send path uses) into the dictation hooks
- after the message is delivered to the resumed session, the UI
navigates/seeds the live session; failed sends keep the existing
draft-restore + error surfacing
- realtime dictation sends apply the same resolution
* fix(web): satisfy HappyChatContextValue in markdown-a test helper
Upstream
|
||
|
|
c258a2279e | fix(web): synchronize assistant runtime and history loading (#1893) | ||
|
|
e35c06b36a | feat(agy): add Antigravity as an interactive PTY agent (#1320) | ||
|
|
f10fbc7496 |
feat(cli): add GitHub Copilot CLI agent support via ACP (#1245)
* feat(cli): add GitHub Copilot CLI agent support via ACP Wrap `copilot --acp --stdio` for remote sessions and spawn the native TUI locally, with full hub/web integration for spawn, resume, and permissions. Fixes tiann/hapi#362 Co-Authored-By: HAPI <noreply@hapi.run> Co-authored-by: Cursor <cursoragent@cursor.com> * feat(copilot): agent modes, models, slash/file UX, local session sync Add Interactive/Plan/Autopilot (fleet is slash-only), subscription-aware model discovery, web StatusBar/permission UX, @ file mentions, and fix local Safe Yolo plus session-id locator for handoff/resume. Co-authored-by: Cursor <cursoragent@cursor.com> * chore: re-trigger Codex PR review after auth outage Co-authored-by: Cursor <cursoragent@cursor.com> * chore: retry Codex PR review Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): preserve agent mode on resume and apply via ACP set_mode Resume was dropping copilotAgentMode so Plan/Autopilot reset to interactive. Also switch local/remote mode application to --mode / session set_mode instead of slash prompts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): wake remote loop when agent mode changes Empty isolated queue tick lets setMode apply without inventing a user prompt. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): confirm mode changes before persisting Await Copilot mode changes and expose discovered models so session state reflects backend acceptance. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): guard mode discovery and slash updates Keep model probes within runner roots and preserve active sessions when mode switching is unavailable or rejected. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): preserve resume and auto semantics Deduplicate Copilot resume rows, apply Auto explicitly, and fail closed on denied permissions. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): close permission and model discovery gaps Keep write-capable commands pending in read-only mode, extend model probe RPCs, and preserve explicit model validation before session creation. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): persist runtime model and agent mode Fallback to ACP model options when direct model switching is unavailable and retain Copilot agent mode across hub restarts. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): normalize composer auto selection Use the null session sentinel for Copilot Auto so the composer selects and resets default models consistently. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(copilot): reject local permission mode changes * style(copilot): remove trailing blank line * fix(copilot): secure local config handoffs * fix(copilot): reject local agent mode slashes * fix(copilot): reject mode changes during turns * fix(copilot): consume rejected slash updates * fix(copilot): preserve thinking across slash handling * fix(copilot): stabilize async config changes * fix(copilot): roll back rejected startup model * fix(copilot): preserve cancellation and file mentions * fix(copilot): hide local permission controls * fix(deps): support clean workspace installs * test(copilot): account for spawn mode argument * fix(copilot): attribute usage to active model --------- Co-authored-by: HAPI <noreply@hapi.run> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
147102a877 | fix(web): preserve rich composer caret (#1305) | ||
|
|
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. |
||
|
|
226b2d066a |
feat(hub): native companion (FCM) push channel + device registry + pairing QR (#803)
* feat(hub): native companion (FCM) push channel + device registry
Adds opt-in FCM HTTP v1 notification delivery so a companion mobile/wearable
app can receive permission, ready, and task notifications end-to-end. The
channel is gated entirely on FCM_SERVICE_ACCOUNT_PATH + FCM_PROJECT_ID being
set; operators not running a companion see zero behavior change.
What lands:
- POST/DELETE /api/devices/register — JWT-authed FCM token registry,
upsert on (namespace, deviceId, platform), platforms `phone` | `wear`.
- Sqlite v9 → v10 migration adds `fcm_devices` (idx on namespace + token).
- FcmService — minimal HTTP v1 client, RS256 service-account JWT via
jose (dep already in tree), 5-minute access-token cache, 401 retry.
- FcmNotificationChannel — implements NotificationChannel, sends data-only
FCM (so companion can route to phone+watch surfaces). Body composition
parses an optional trailing `AGENT_NOTIFY_SUMMARY {json}` line for richer
ready summaries; truncates plain assistant text to 280 chars otherwise.
Tags each payload with `severity` (info/warning/success/error) so clients
can color/categorise the notification.
- PushNotificationChannel gains a NativeFallbackProbe — when a namespace
has at least one registered FCM device, web-push and SSE in-page toast
are skipped so the operator does not double-notify on phone+browser.
Probe is no-op when no FCM device is registered; PWA-only setups
unchanged. Branch trace gated on HAPI_NOTIFY_DEBUG=1.
- shared/src/messages.ts — `extractAssistantPlainText` (codex + Claude SDK
shapes) and `extractNotifySummary` (strict end-anchored line parser).
- hub/src/notifications/toolArgs.ts — tool-arg formatters lifted out of
telegram/sessionView (kept duplicated there in this PR; refactor of
Telegram is a follow-up).
- docs/api/native-companion-contract.md — payload + endpoints + env vars,
versioned at contract v1.
Test coverage:
- 260 hub tests pass (incl. 23 new across FCM channel, push dedup,
v10 migration, devices route).
- 60 shared tests pass (messages parsers).
Notes for reviewers:
- Reference companion implementation lives in a separate Android repo
(Kotlin, phone APK + Wear OS APK) — this PR is hub-side only.
- No new runtime deps (`jose` and `zod` already declared in hub).
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs(contract): clarify scope - companion is remote-hub client, not hub-on-phone
Adds a Scope section to the native-companion contract so anyone
implementing it knows the audience: operators running the hub on a
server who want phone/watch as a notification surface, not users
expecting a Termux-bundled hub. Mirrors the framing now in
heavygee/hapi-companion README.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs(contract): correct Scope section - hub topology is unchanged
Removes the prior framing that referenced a non-existent 'Termux
hub-on-phone' alternative. This contract describes a native client to
the same hub the PWA talks to; it does not change where the hub runs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(web): companion app pairing QR in Settings
Companion section in Settings renders a QR code encoding the deeplink
hapicompanion://bind?hub=<base>&code=<token>. Scanning it from the HAPI
companion app (Android phone or Wear OS) auto-fills the bind form and
authenticates against this hub - no manual URL/token paste.
QR is gated behind a Show button so the access token doesn't sit visible
on screen by default; a Copy link affordance and the textual deeplink
are also exposed for manual onboarding.
Adds qrcode + @types/qrcode to web/ (already a hub dep, no new resolved
package - just a workspace declaration).
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(hub): terminal QR for companion app pairing alongside PWA QR
After the existing PWA access QR is rendered on tunnel start, also print
the hapicompanion://bind?hub=...&code=... deeplink and a matching QR.
Same tunnel + token, different scheme: phones with the companion app
installed pick up the deeplink via the manifest intent filter; phones
without it ignore it and fall back to the PWA QR above.
QR rendering failure is non-fatal in both cases - the textual deeplink
above the QR is sufficient for manual paste.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(fcm): address HAPI Bot review on PR #803
Two bugs surfaced by the upstream review bot:
1) Web Push silently dropped when FCM is not actually configured.
The native-fallback probe only checked the device registry; it did
not check whether resolveFcmConfig() actually succeeded. So an
operator who previously enabled FCM, registered a phone, then later
started the hub WITHOUT FCM_SERVICE_ACCOUNT_PATH would see the probe
return true (devices still in DB) -> Web Push suppressed -> no FCM
channel registered -> notifications go to /dev/null.
Fix: extracted the probe construction into buildNativeFallbackProbe()
which short-circuits to () => false when fcmConfig is missing. Probe
never even consults the device store in the no-config branch, so
stale rows can never matter.
2) Transient FCM failures permanently unregistered devices.
sendToToken() returned a single boolean and sendToNamespace() removed
any device whose send returned false. A 429 (rate limit), 503
(server error), 401 (auth glitch), or even an ECONNREFUSED would
delete the device row, after which the user would need to re-pair to
get notifications again. The bot caught it; the fix is the obvious
one.
Fix: sendToToken() now returns 'sent' | 'invalid' | 'failed'.
- 'invalid' is reserved for the responses that genuinely indicate a
dead token: HTTP 404 with UNREGISTERED/NOT_FOUND, and HTTP 400
with INVALID_ARGUMENT explicitly referencing the token field.
- Everything else (429, 5xx, 401, 403, network errors) is 'failed'
and counts toward the failed tally without removing the device.
sendToNamespace() only calls removeDeviceByToken() on 'invalid'.
Tests: 11 new tests across two new files. fcmService.test.ts covers
all six branches (200, 404 unregistered, 429, 503, 401, network error)
plus a mixed-batch case that proves invalid tokens get removed in the
same call where transient-failure tokens survive. nativeFallbackProbe
.test.ts covers both no-config and configured branches plus the
explicit "no-config never touches the store" guarantee.
Hub test count: 273 -> 284 (all passing).
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs(contract): correct FCM visibility rule and remove unsupported event type
HAPI Bot review on PR #803 caught two contract-doc accuracy gaps:
1) Visibility rule was wrong. Doc said "FCM fires when Web Push would
fire AND client not visible via SSE", but FcmNotificationChannel
ALWAYS fires regardless of PWA visibility (deliberately - native
companion is the canonical wrist-first surface, and there is a
passing test asserting this). Companion app implementers reading
the contract would have built foreground-suppression logic and
then dropped notifications when the PWA tab was open.
2) Documented `session-completed` event doesn't exist. NotificationHub
never calls into a 'session-completed' channel method on
FcmNotificationChannel; the type would never reach a native client.
Removed from the documented enum, leaving only the three actual
events: ready, permission-request, task-notification.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs(contract): drop trailing whitespace, use blank line for paragraph break
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): persist CLI access token after Telegram bind so pairing QR works
The Settings -> Companion pairing QR reads the original CLI access token
from localStorage (hapi_access_token::<baseUrl>) so it can be encoded into
the hapicompanion://bind deeplink. For browser/CLI logins useAuthSource
already persists the token via setAccessToken, but the Telegram Mini App
bind path went through useAuth.bind() which exchanged the typed CLI token
for a JWT and never persisted it. Telegram users therefore always saw the
"signed in via Telegram..." fallback and got no usable QR.
After a successful client.bind() we now mirror useAuthSource's behavior
and write the same accessToken to the same localStorage key, restoring
parity between the two auth paths. No change for browser/CLI users.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(fcm): gate native-fallback probe on rolling FCM health
The native-fallback probe previously returned true whenever FCM was
configured AND devices were registered, which suppressed web-push for
the namespace. The HAPI Bot correctly pointed out the gap: if the FCM
pipeline silently breaks (expired service-account key, sustained 5xx,
OAuth token-fetch failure, network blackhole) the operator gets nothing
on either channel until they manually intervene.
Approach (deliberate, not the bot's exact suggested fix):
- FcmService now keeps a small rolling window (last 8 outcomes) of send
attempts and exposes `isHealthy()`. The threshold is 5+/8 failures =
unhealthy; the buffer starts empty so a freshly-booted hub is
optimistic ("innocent until proven guilty") and does not double-fire
on event #1.
- Token-fetch failure (`getFcmAccessToken` throws) now records exactly
one health-failure (not one per device), short-circuits the send
loop, and returns a result so `sendToNamespace` no longer leaks the
exception.
- `invalid` token responses are explicitly excluded from the health
buffer because they are per-device facts (rotated/uninstalled token),
not pipeline failures - FCM was reachable, it just rejected one
stale token.
- `buildNativeFallbackProbe` now optionally accepts the FcmService and
short-circuits to "let web-push fire" when health is bad, before it
even queries the device registry. The single-arg call shape is still
supported for back-compat.
Why not the bot's exact suggestion ("invert: call FCM first, fall back
on result.sent === 0"):
- Couples PushNotificationChannel to FcmService and FcmSendPayload,
reversing the clean parallel-channel architecture established earlier
in this PR.
- Treats every transient single-event failure as fallback-worthy, which
re-opens the duplicate-notification race that the suppression logic
was added to close (FCM HTTP timeout that delivers later + the web
push we sent in the meantime = two pings).
- A rolling health window only flips on sustained breakage, which is
the actual operational scenario the bot is worried about.
The wrist-first design intent ("FCM fires unconditionally, web-push is
suppressed for the same namespace") documented in
docs/api/native-companion-contract.md is preserved on the happy path.
The probe only re-enables web-push when there is concrete evidence the
native pipeline is not delivering.
Tests:
- New FcmService.isHealthy suite covers empty-buffer, threshold flip,
recovery as failures age out of the window, invalid-token exclusion,
and network-error path.
- nativeFallbackProbe gains coverage for the unhealthy-but-registered,
healthy-and-registered, and absent-fcmService (back-compat) cases.
- All 292 hub tests still pass; typecheck clean.
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor(telegram): drop duplicate tool-args formatter, use shared module
The Telegram session view had its own copy of formatToolArgumentsDetailed
identical to the one in hub/src/notifications/toolArgs.ts (already used by
the FCM channel). Replace the local copy with an import.
Removes ~70 lines of duplication, plus the now-unused MAX_TOOL_ARGS_LENGTH
constant and `truncate` import. The shared signature accepts an optional
opts arg whose default maxArgLength is 150 - matching the prior constant -
so the call site is unchanged.
Two benign upgrades come along for the ride from the shared module:
?? instead of || on field fallbacks (no real-world difference; permission
arguments never carry empty-string fields), and String(...) wrapping plus
a typeof object guard that makes non-string values render gracefully
instead of throwing into the catch block.
Hub tests: 311 pass / 0 fail. Telegram subset: 5 pass / 0 fail. typecheck
green.
Cold-reviewed by an out-of-context Claude Opus peer before push.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(fcm): require positive evidence in health window before suppressing web-push
Addresses HAPI Bot Major review on PR #803.
The previous health gate treated an empty outcome buffer as healthy
("innocent until proven guilty"). That created a silent-blackhole window
on cold start with broken FCM credentials: the push channel suppressed
SSE/Web Push for the first ~5 events while the FCM channel attempted
each delivery and recorded failures, until enough stacked to flip the
threshold. Every notification in that gap was silently lost.
New invariant: isHealthy() requires at least one successful FCM send in
the recent window (HEALTH_WINDOW=8) AND failures below threshold
(HEALTH_FAILURE_THRESHOLD=5). Both conditions are necessary; either
alone is insufficient evidence to safely suppress web-push fallback.
Trade-off: one duplicated notification per hub restart per namespace.
On the first event after restart, web-push fires alongside FCM (because
the gate has no positive evidence yet). Once FCM records that first
success, the gate engages and subsequent events are FCM-only. Worth it
for guaranteed delivery during cold-start outages.
Tests reworked to match new semantics:
- "starts UNHEALTHY with empty buffer" (was: healthy)
- "flips to healthy after first successful send" (new)
- "stays unhealthy across failures-only run" (new, exercises the exact
blackhole scenario the bot flagged)
- "flips back to unhealthy after threshold breach with prior successes"
(renamed, establishes successes first)
- "invalid tokens don't count against health" (reworked: send a mixed
batch first to establish health, then verify invalids don't flip it)
- "network errors count as failures" (reworked: establish health first)
Hub tests: 313 pass / 0 fail. typecheck green.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): bump FCM migration to V10→V11 after upstream service_tier V9→V10
Upstream/main landed sessions.service_tier at schema v10. The companion
FCM device registry now migrates at v11 so both changes compose cleanly
after the courtesy rebase onto current upstream/main.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): per-dispatch native gate instead of stale FCM probe
FCM runs before web-push; PushNotificationChannel skips web/SSE only
when the same notify() dispatch already delivered via FCM. Removes the
isHealthy()+device-row probe that could suppress web-push after warm
FCM outages.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub,web): cap notifySummary for FCM limits; fix PWA test cast
Rebase follow-up: truncate AGENT_NOTIFY_SUMMARY summary/action before
FCM data payload (bot Major). Fix usePwaUpdate.test.ts setTimeout mock
cast so bun typecheck passes on current main.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): cap all FCM notifySummary fields and task bodies
Whitelist and truncate AGENT_NOTIFY_SUMMARY auxiliary fields before
JSON serialization; cap task-notification summaries to glance limit.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): FCM fetch timeouts and cap Grep/Glob permission args
10s AbortSignal.timeout on OAuth + FCM send so sequential web-push
fallback is not blocked on hung Google endpoints; truncate Grep/Glob
pattern in permission detail formatter.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): bind FCM token to one namespace on re-pair
Delete stale fcm_devices rows sharing the same token when a native
install registers under a different namespace.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): localize Companion settings and pairing copy
Add en/zh-CN keys for the Companion section title and CompanionPairing
strings; matches locale-driven Settings pattern (bot Minor on #803).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): tighten FCM token-invalid detection and truncation edge cases
Parse FCM error JSON: only UNREGISTERED or token-field INVALID_ARGUMENT
unregister devices; generic NOT_FOUND stays transient. Guard limit<=3
in truncateReadyText so tiny action budgets cannot blow the glance cap.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): parse FcmError details.errorCode for UNREGISTERED tokens
FCM v1 often returns HTTP 404 with root NOT_FOUND plus
details[].errorCode UNREGISTERED; prune those tokens while keeping
generic project/resource NOT_FOUND transient.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): mock AppContext for About Companion pairing in settings tests
Settings About now mounts CompanionPairing via useAppContext after the
#1027 hub redesign rebase; wrap the About route test with AppContext and
Companion mocks so the suite stays green.
Co-authored-by: Cursor <cursoragent@cursor.com>
* docs(contract): point companion auth at POST /api/auth, not /api/bind
Pairing QR carries the CLI access token as `code`. /api/bind requires
Telegram initData; native companions must use /api/auth with accessToken.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): mount Companion pairing under Settings General
About is version/links only after the settings hub redesign; pairing is
setup, so keep Companion with language prefs and update the route tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
|
||
|
|
4a63418103 | feat(web): share conversation turns as images (#1047) | ||
|
|
4c76668a6c | refine message actions and metadata | ||
|
|
65e1708c78 |
feat(web): mermaid diagram lightbox on click (#741)
* feat(web): mermaid diagram lightbox on click Click rendered mermaid blocks in chat to open a zoomable full-screen viewer. Re-renders from source in the modal with the current theme. Closes #737. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): fit mermaid lightbox to viewport on open Auto-scale diagrams to fill the viewer instead of opening at intrinsic mermaid size. Reset returns to fit; zoom label is relative to fit (100%). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): fit mermaid lightbox to device screen not inner panel Use visualViewport for fit scale, full-screen pan layer, and a floating toolbar so the diagram can use the whole display. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): show mermaid lightbox by reusing inline SVG Second mermaid.render on open often left a 0×0 SVG while fit scale was computed from the loading placeholder. Reuse the inline SVG in the modal and measure viewBox with retried fit-to-screen. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): uniquify mermaid SVG ids in lightbox clone Inlining the same mermaid markup twice duplicates element ids and breaks url(#ref) resolution in the modal copy. Prefix ids and hrefs for lightbox only. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): give mermaid lightbox SVG explicit dimensions Mermaid emits width="100%" with max-width in px; that collapses to 0×0 inside the centered lightbox layer. Derive width/height from viewBox for the uniquified lightbox clone. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): render mermaid lightbox via isolated SVG data URL String id rewrites broke mermaid's embedded CSS so only labels appeared zoomed. Rasterize the inline SVG to a data-URL img instead of duplicating markup in the DOM. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): lightbox re-renders SVG for sequence diagrams Data-URL images drop or blank some mermaid diagram types (sequence). Re-render with a modal-specific id into inline SVG on a code-bg panel, and add sequence theme variables for dark/light. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): mermaid lightbox uses inline SVG in shadow DOM Reuse the inline render in an isolated shadow root so sequence CSS stays intact, and fit the viewport from viewBox dimensions instead of the loading placeholder or width="100%" layout. Co-authored-by: Cursor <cursoragent@cursor.com> * test(web): Playwright lightbox coverage per mermaid diagram type Add e2e harness and a script that opens the lightbox for each diagram kind (flowchart through kanban). Fit uses inline getBBox() so compact charts like gitGraph fill the viewport. Co-authored-by: Cursor <cursoragent@cursor.com> * test(web): bounded Playwright via webServer, fix gantt fit sizing Playwright owns Vite lifecycle (no agent-spawned dev server). Fit uses viewBox unless viewBox padding is excessive (gitGraph); wide charts use width-based coverage in e2e. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(web): gitignore Playwright test-results Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): address PR 741 bot feedback (typecheck, fit floor, gitignore) Guard lightbox open when svg is null; allow fit scale down to 0.01 while keeping 0.25 minimum for manual zoom; ignore Playwright test-results/ correctly. Co-authored-by: Cursor <cursoragent@cursor.com> * test(web): Playwright asserts click expands diagram vs inline Measure inline vs lightbox bounding box after click; require visible growth (area ratio or max dimension) plus dialog + shadow SVG content. Co-authored-by: Cursor <cursoragent@cursor.com> * test(web): Playwright against live HAPI session for mermaid lightbox Add seed script for a dedicated chat session, live hub Playwright suite (HAPI_LIVE=1), and dogfood doc. Live tests fail until driver serves shadow-DOM lightbox (catches gray-box regression on stale bundles). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): undo wrapper transform in lightbox fit; carry fit floor in zoom Resolves PR #741 review threads (HAPI Bot Major): 1. measureSvgIntrinsicSize / measureContentSize prefer intrinsic dimensions (viewBox -> width/height attrs -> img.naturalSize) before getBoundingClientRect. When the rect is the only signal, divide by scaleRef.current so the 50/200ms refit retries stop compounding with the wrapper's scale(...) transform. Large diagrams no longer jump tiny or oversize after async render completes. 2. Interactive zoom (wheel/keys/buttons/pinch) now clamps with Math.min(MIN_SCALE, baseScaleRef.current). A diagram fitted below the normal 25% floor stays reachable instead of snapping back to 25% and clipping. Zoom-out button disabled threshold uses the same min. 3. Add Vitest coverage for both helpers (intrinsic precedence, scale-aware rect fallback, divide-by-zero guard) so regressions surface without needing the full Playwright stack. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(scripts): mermaid seed refuses to wipe non-fixture sessions HAPI Bot Major (PR #741): SESSION_ID is documented as overridable, and the script unconditionally deletes every message for the target session before seeding fixtures. If pointed at a real session id, that's silent data loss. Refuse to proceed when an existing session id has a tag other than 'mermaid-lightbox-e2e'. New ids and the canonical fixture session still seed normally; real sessions throw before any DELETE runs. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): normalize mermaid svg for lightbox shadow root Mermaid emits width="100%" on every diagram. Inside a shadow root whose host has no explicit size, that collapses to zero in Chromium for most diagram types - only ones that ship pixel attrs (e.g. journey) happen to render. Operator confirmed on the live driver: every diagram except journey opened to a grey rounded square. MermaidLightboxSvg now runs normalizeMermaidSvgForStandaloneDisplay before injecting (strips width/height="100%", bakes viewBox dims as pixels) and sets :host{display:inline-block} so the host sizes to the SVG. Inline svg in chat is unchanged - only the lightbox copy is normalized. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): keep mermaid lightbox content below the toolbar Operator screenshot showed the diagram top (e.g. pie 'Pets' title) clipped behind the toolbar bar. Two causes: 1. getScreenFitSize used the full viewport height, so the fit scale sized the diagram to fill an area the toolbar overlapped. 2. The viewport (drag/zoom area) was inset-0; content centered on the full viewport center, not the visible region's center, pushing the top behind the toolbar. Measure the toolbar with a ResizeObserver, subtract its height from the fit calculation (clamped at zero), and start the viewport region below the toolbar (top: toolbarHeight). Fit scale recomputes whenever toolbar height changes. Adds Vitest coverage for getScreenFitSize reserved-top math. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): guard ResizeObserver before constructing it HAPI Bot Major (PR #741): Vitest jsdom does not polyfill ResizeObserver, so the toolbar measure effect throws ReferenceError when the existing mermaid-diagram React tests open the lightbox. Same code path is also brittle in any browser/webview without the API. Fall back to plain window 'resize' listener when ResizeObserver is absent. Toolbar height won't auto-update on element resize without it, but the lightbox still renders and the resize listener catches the common viewport-rotation case. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(scripts): live mermaid playwright wrapper runs from repo root HAPI Bot Minor (PR #741): the wrapper sets cwd to scripts/, but the test:mermaid-lightbox:live npm script lives in the repo-root package.json, so spawning npm there exited before Playwright started. Switch cwd to the repo root and drop the unused WEB_DIR constant. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): accept signed viewBox values in mermaid lightbox normalize HAPI Bot Minor (PR #741): the viewBox regex only matched digits, dots, and spaces, so a valid viewBox with negative origin (e.g. '-8 -8 640 480') returned null. normalizeMermaidSvgForStandaloneDisplay then became a no-op and left width='100%', re-introducing the zero-sized lightbox render this PR is meant to fix for the affected diagrams. Switch to the bot's suggested regex (signed numbers, single or double quotes, comma or space separators) and reject NaN parts. Adds Vitest coverage for signed origins, single quotes, comma separators, the malformed/no-viewBox null paths, and an end-to-end normalize test that fails against the old regex. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): align @playwright/test on 1.60.0 across workspaces HAPI Bot Major (PR #741): web/package.json pinned @playwright/test at 1.49.1 while the root workspace and bun.lock were on 1.60.0. The mismatch surfaced after rebasing onto upstream/main, where the root had already moved to 1.60.0 while my web devDependency lagged from an older commit. A frozen install would reject the lockfile and the new web e2e script could resolve a different Playwright than root scripts. Bump the web devDependency to 1.60.0 and regenerate bun.lock so all workspaces share one Playwright version. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): move mermaid playwright fixtures out of public HAPI Bot Minor (PR #741): the e2e and smoke fixtures lived under web/public, so Vite copied them verbatim into web/dist and the hub asset generator embedded them in production bundles. Both pages import Vite dev-only paths (/@react-refresh and /src/dev/...), so the production /mermaid-lightbox-{e2e,smoke}.html routes would 404 on those imports. Move both fixtures to web/e2e-fixtures/ to match the existing scratchlist-fixture pattern (relative ../src/dev import, served by Vite at /e2e-fixtures/...) and update the Playwright spec to hit the new path. Build now ships 112 PWA precache entries instead of 114 (both fixtures excluded from dist). Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c87f88f02f |
fix(web): disable single-dollar inline math in remark-math (#805)
* fix(web/markdown): disable single-dollar inline math in remark-math Default remark-math configuration treats $...$ as inline LaTeX, which turns any prose containing two currency amounts (e.g. "save $400 vs the $200 plan") into a KaTeX block — paragraphs collapse, whitespace is stripped, the running text is re-rendered as math symbols. Pass `singleDollarTextMath: false` to remarkMath so single $ is plain text. Block math `$$...$$` (on its own line) still renders, matching GitHub-flavored markdown semantics. Single source of truth: MARKDOWN_PLUGINS is shared by MarkdownText, Reasoning, and MarkdownRenderer — fix lands in all three surfaces. Adds 3 regression tests that drive the unified pipeline end-to-end: prose with multiple "$N" amounts produces no `class="katex"` and no `<math>` element; `$$...$$` block math still does. Co-authored-by: Cursor <cursoragent@cursor.com> * chore(web): declare unified/remark-parse/remark-rehype/hast-util-to-html The new markdown-text regression test imports these directly to drive the unified pipeline end-to-end. They were resolving via transitive deps from remark-math and rehype-katex, which is fragile — a future dep upgrade can remove the transitives and break the test. Declare them explicitly under devDependencies. No code change; lockfile records the same versions that were already installed transitively (unified@11.0.5, remark-parse@11.0.0, remark-rehype@11.1.2, hast-util-to-html@9.0.5). Addresses the HAPI Bot review finding on PR #805. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
d09168778c |
fix(web): preserve user prompt line breaks (#804)
* test: reproduce issue #794 * fix: preserve user prompt line breaks (closes #794) |
||
|
|
27e58570c3 |
chore(web): upgrade @tanstack/react-router to ^1.170.8 (#722)
* chore(web): upgrade @tanstack/react-router to ^1.170.8 Fixes QuotaExceededError in scroll restoration: upstream @tanstack/react-router >=1.145.6 wraps sessionStorage.setItem with try-catch, preventing the crash when scroll restoration cache exceeds quota. Refs: #683, #716, #721 * fix(web): adapt scrollStorageGuard to @tanstack/router-core >=1.145.6 API `scrollRestorationCache` was removed from the public exports; replace with `storageKey` import and simplify `hardResetScrollRestorationPersistedState` to a plain `removeItem`. Remove the now-stale in-memory cache sync path and its associated tests. Upstream try-catch (>=1.145.6) covers crash prevention; this guard continues to proactively prune sessionStorage. via [HAPI](https://hapi.run) Co-Authored-By: HAPI <noreply@hapi.run> --------- Co-authored-by: HAPI <noreply@hapi.run> |
||
|
|
f7a40bd573 |
feat(web): polish chat rendering and fix remote session interactions (#567)
* feat(web): polish chat rendering Refresh the web chat presentation across user messages, tool cards, code blocks, diffs, reasoning, and Mermaid diagrams.\n\nAdd focused regression coverage for bubble/status behavior, code and diff rendering, clipboard output, Mermaid theming, and message-window updates. * fix(web): stabilize chat tool rendering Preserve manual scroll anchors while older messages and tool dialogs update, and align code, diff, and tool result rendering with chat typography. Add chat font-weight settings, ignore local Playwright CLI artifacts, document the Angular commit-message convention, and cover the scroll, result, code, diff, and settings behavior with focused tests. Constraint: User requested committing all current workspace diffs with Angular-style commit messaging Tested: bun run typecheck:web && bun run test:web && git diff --check Co-authored-by: OmX <omx@oh-my-codex.dev> * style(tool-card): polish question and permission card styles Align AskUserQuestion option surfaces and permission action hierarchy with the existing tool card visual language while preserving interaction logic. Extract shared option presentation helpers and theme-driven hover/muted colors to reduce duplication. Constraint: Frontend style-only polish; preserve existing permission and answer submission behavior Rejected: Keep screenshot artifacts in the repo | they are local visual review output, not source Confidence: high Scope-risk: narrow Tested: git diff --check; bun run typecheck:web; bun run test:web; bun run build:web Not-tested: manual cross-browser visual QA beyond local Playwright inspection Co-authored-by: OmX <omx@oh-my-codex.dev> * fix(cli): keep Claude remote plan prompts actionable Handle Claude remote /plan locally so HAPI switches plan permission mode before forwarding any prompt text. This avoids Claude Code treating /plan as an unknown skill and ending with only a ready event. Constraint: Claude SDK result messages are not conversation log entries, so command handling must happen before the prompt reaches Claude.\nRejected: surfacing SDK result summaries in web chat | would expose transport-level summaries broadly instead of fixing the slash-command path.\nConfidence: high\nScope-risk: narrow\nDirective: Keep Claude remote slash commands that alter runtime mode in the CLI special-command parser.\nTested: bun test cli/src/parsers/specialCommands.test.ts; bun typecheck; git diff --check\nNot-tested: Manual GitHub-hosted runner deployment. * fix(web): polish tool result rendering * fix(web): preserve collapsed session order * fix(chat): settle initial thread scroll * fix(settings): remove chat font weight option * fix(web): remove font weight bootstrap code * chore: remove unrelated branch artifacts * test(web): update consumed message invocation test * fix(chat): cancel initial scroll settling on manual scroll --------- Co-authored-by: huhaoyu.hahahu <huhaoyu.hahahu@bytedance.com> Co-authored-by: OmX <omx@oh-my-codex.dev> |
||
|
|
813ac7fdda |
feat(web): add LaTeX math formula rendering with KaTeX (#436)
* feat(web): add LaTeX math formula rendering with KaTeX Add remark-math + rehype-katex to the markdown rendering pipeline so inline ($...$) and display ($$...$$) math formulas are rendered as proper KaTeX output in chat messages and tool results. Closes #237 * fix(web): disable single-dollar math parsing and add KaTeX to reasoning - Set singleDollarTextMath: false to prevent $HOME, $PATH etc from being misinterpreted as math formulas. Only $$...$$ (display) is parsed; inline math requires explicit \(...\) or $$...$$. - Add rehypePlugins to the reasoning renderer so math formulas render consistently across chat, tool results, and reasoning blocks. * refactor(web): use satisfies for type-safe plugin exports Replace any[] with satisfies NonNullable<MarkdownTextPrimitiveProps[...]> to preserve type safety on the shared plugin lists without needing eslint suppressions. * fix(web): enable single-dollar inline math syntax Re-enable $...$ parsing (remark-math default) so inline formulas like $E=mc^2$ render correctly. Shell variables like $HOME typically appear inside code spans/blocks which remark-math does not parse, so false positives are minimal in practice. |
||
|
|
ef87e30727 | feat(web): redesign sidebar with resizable width and 3-level hierarchy (#427) | ||
|
|
7cad11ca27 |
Add About section to settings page with version info and tests (#119)
* 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 |
||
|
|
8f5b773b23 | feat: voice assistant | ||
|
|
716a42e5e0 |
fix: enable GitHub Pages SPA routing by copying index.html to 404.html
GitHub Pages will serve 404.html when a route is not found, allowing the React app to load and handle client-side routing for SPA navigation. |
||
|
|
146cbf8db0 | feat(terminal): add Nerd Font support for Powerline themes (#29) | ||
|
|
37c6bc83d7 | chore: migrate to AGPL-3.0-only | ||
|
|
e2028b6914 | Unify protocol types and modes | ||
|
|
43519621f3 |
feat: add web push notifications support
Implement push notification system with VAPID keys, client service worker integration, and push subscription management. Includes server-side PushService for sending notifications and PushNotifier for reactive event handling, plus client-side usePushNotifications hook and service worker support. |
||
|
|
4efc263d9b |
chore: add LGPL-3.0-or-later license and update package.json licenses
- Add LICENSE file to root and cli/ directories with LGPL-3.0-or-later text - Create cli/NOTICE file with MIT attribution for happy-cli derived code - Update license field in cli/, server/, and web/ package.json to "LGPL-3.0-or-later" - Add NOTICE to cli/package.json files array for npm publishing |
||
|
|
0769d498ba |
refactor: upgrade to Tailwind CSS v4 and update dependencies
Migrate to Tailwind CSS v4 with new @tailwindcss/postcss plugin, upgrade xterm.js to v6, vite to v7, and other core dependencies for improved performance and compatibility. |
||
|
|
3765cc539b |
refactor: update dependencies and fix TypeScript instantiation depth issues
Updates ModelContextProtocol SDK and multiple dependent libraries to latest versions. Refactors TypeScript schemas to avoid instantiation depth issues by widening Zod types and using explicit type parameters. |
||
|
|
38e8fe8d02 |
refactor: remove eslint and tsx-related dependencies after bun migration
Removes dev dependencies no longer needed after migrating from tsx to bun as TypeScript runtime and removing linting toolchain. Moves workbox-window to web package dependencies where it's actually used. |
||
|
|
18e6310451 |
feat: implement web terminal feature with xterm.js and Socket.IO proxy
- Add CLI-side terminal management via Bun.Terminal with TerminalManager - Implement server-side Socket.IO proxy for terminal I/O between web and CLI - Create web terminal UI component with xterm.js and support for resize/reconnect - Add terminal route and navigation button in session chat - Include comprehensive terminal implementation plan and architecture docs |
||
|
|
3bac5c7bc6 |
feat(web): integrate TanStack React Router for client-side routing
Replace state-based screen navigation with proper URL-based routing. This includes: - New router configuration with routes for sessions, machines, and spawn pages - App context provider to share API and token across the app - useAppGoBack hook for handling browser and Telegram back navigation - Refactored App component to render outlet and use router hooks - Memory history for Telegram app, browser history for web |
||
|
|
d2ad977394 |
server: add SSE events stream for webapp
web: switch webapp updates to SSE |
||
|
|
3a7272d03d |
feat(web): integrate TanStack Query for state management
Replace manual state management with TanStack Query (React Query) for more robust server state handling. This refactoring introduces: - New hooks for queries: useSessions, useSession, useMessages, useMachines - New hooks for mutations: useSendMessage, useSessionActions, useSpawnSession - Centralized query client with optimized configuration (5s staleTime, disabled window focus refetch) - Query key factory for consistent cache invalidation - Improved message synchronization via socket events with cache updates - Optimistic updates for message sending with retry capability - Simplified App.tsx by removing manual state management logic - Integrated React Query devtools in development mode This enables automatic cache management, better error handling, and a foundation for more sophisticated data fetching patterns. |
||
|
|
3cf66983fc |
feat: optimize web bundle with custom minimal Shiki highlighter
Replaces react-shiki with a custom minimal Shiki highlighter to significantly reduce bundle size. Only loads 29 common languages and 2 themes (github-light/dark), using the JavaScript RegExp engine instead of WASM. - Reduced web bundle from 5.8MB to 2.5MB (57% reduction) - Saves 622KB by avoiding WASM runtime - Graceful fallback for unsupported languages - Removed react-shiki dependency - Added @shikijs/langs, @shikijs/themes, shiki, hast-util-to-jsx-runtime - Added light mode CSS rules for syntax highlighting |
||
|
|
fd55750a12 |
refactor: replace react-syntax-highlighter with react-shiki
Consolidate markdown rendering logic into dedicated assistant-ui components and update syntax highlighting to use Shiki with GitHub themes. Removes dependency on react-markdown and react-syntax-highlighter in favor of @assistant-ui/react-markdown with Shiki-based highlighting. |
||
|
|
532ceb5595 | feat: integrate @assistant-ui/react library with new chat components | ||
|
|
ec3a849cbd |
feat: refactor ChatInput with autocomplete, suggestion management, and settings integration
Extract word detection and suggestion logic into reusable utilities and hooks. Add FloatingOverlay and Autocomplete components for intelligent input assistance. Integrate settings panel and abort button directly into ChatInput component. Simplify SessionHeader by removing settings dialog (now in ChatInput). |
||
|
|
b4654acb92 | init |