Since #1383 renamed the helper to storageUsagePie.ts, the same directory
contained both StorageUsagePie.tsx and storageUsagePie.ts. On case-insensitive
filesystems (macOS), extensionless imports of the component resolve to the
helper because '.ts' is tried before '.tsx' and 'StorageUsagePie.ts' matches
'storageUsagePie.ts' case-insensitively, breaking typecheck and component
tests on macOS. Linux CI is unaffected.
Renames the helper (and its test) to storageUsageSlices.ts, which cannot
collide, and updates the two importers. Fixes#1482.
* feat(cursor): bump hub thinking on ACP harness wake
When Cursor resumes after idle (notify_on_output / mid-idle ACP activity
or a permission request), flip thinking via the existing session-alive
keepalive so the hub list matches reality. Fixes#1470.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): emit thinking true/false edges for ACP harness wake
Address Codex Major on #1487: activity listener now reports idle as
false, and the launcher only keepalives on actual thinking transitions
so streamed chunks do not spam session-alive.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cursor): reattach activity thinking listener after session/new remap
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
The first test in claudeRemote.test.ts imports the full remote-module
graph and intermittently exceeds vitest's default 5s timeout on loaded
CI runners, failing PRs that touch no cli/ files. Give that one test a
15s per-test timeout; verified: typecheck exit 0, claudeRemote suite
6/6 pass (~2s cold), full suite 223/224 files pass (runner.integration
fails identically on pristine base in this sandbox).
Fixes#1491
Pi finalizes a failed assistant message with stopReason 'error' and
errorMessage on both message_end and turn_end, but PiMessageAccumulator
only handled text/thinking deltas, so the error was silently dropped and
the web showed nothing (spinner stop + partial text at most).
Emit an AgentMessage {type:'error'} once per failed message (message_end
wins, turn_end is the safety net for Pi builds that skip message_end);
the web already renders AGENT_MESSAGE_PAYLOAD_TYPE error payloads as
system messages. User-initiated aborts stay silent.
Closes#1478
* feat(a2a): P3 AGENT_NOTIFY_SUMMARY → work-graph status ingest
Land A0 events/event_links substrate (aligned with #1374 contracts) plus
thin P3 ingest so opt-in notify footers become durable work_ad rows.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(a2a): address #1467 cold pass-1 Majors on notify ingest
Populate expires_at from message ts+TTL, persist message timestamps on
insert, pin append-only ledger survival, and defer project column to P4.
* test(hub): cover notify ingest via syncEngine message-received
Pins the display-never-gates-capture invariant on the real handleRealtimeEvent
wiring path (cold pass-1 minor 12).
* fix(a2a): address #1467 Sol cold Majors on notify ingest
Support AGY agy_message text extraction, bind audit principal to session id,
map stalled→blocked (never self-stale), and cap work-graph POST bodies.
* fix(a2a): validate work-graph creates at store insert (Sol S4)
Notify ingest built WorkGraphEventCreate and inserted without schema
bounds; oversized footer summaries could land past HTTP Zod limits.
Validate WorkGraphEventCreateSchema in insertWorkGraphEvent, clamp
untrusted notify strings at elevation, and regress 2049-char summaries.
* fix(a2a): reject fractional work-graph list limit
Bot Minor: limit=1.5 passed Number.isFinite and hit SQLite LIMIT as a
non-integer, surfacing as 500. Require Number.isInteger before query.
* fix(a2a): keep notify elevation inside payload budget
Drop duplicated notify_summary nesting that could exceed the 32 KiB
payload_json cap after per-field clamps, and measure the cap in UTF-8
bytes via TextEncoder instead of UTF-16 string.length.
* fix(a2a): clamp notify footer fields by UTF-8 bytes
Per-field string.length clamps still let CJK footers exceed the UTF-8
payload_json budget and silent-drop elevation. Truncate action/project/
agent/summary (and tags) to the ledger byte caps before insert.
* fix(a2a): clamp notify payload fields by JSON-escaped UTF-8
Raw UTF-8 clamps still let backslash-heavy footers expand past the
payload_json budget after JSON.stringify. Budget the escaped form so
elevation inserts instead of silent-dropping.
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(opencode): report why a prompt failed instead of pointing at logs
The provider's own explanation already reaches this process: the ACP
transport rejects session/prompt with the JSON-RPC error message
verbatim. The launcher caught it, logged it, and handed the user a fixed
"OpenCode prompt failed. Check logs for details." — a remote user is by
definition not at the machine holding those logs, so a rate-limited
session simply stopped with no stated reason.
Only the message is used; that channel carries no response headers,
cookies or body. It is unbounded though (a 20KB provider body produced a
20,210-character message), so it is capped at the same 200 characters
the compaction bridge already applies to provider text, and the JSON-RPC
"Internal error: " wrapper is stripped. A failure with nothing readable
to say still renders the sentence it always did.
* feat(opencode): surface upstream retries from the agent event stream
A provider rate limit leaves OpenCode retrying indefinitely, and it
announces that on exactly one channel: its own server event stream.
Measured against a provider stubbed to answer 429 — 40 minutes, 85
retries, zero ACP notifications, zero stderr bytes, session/prompt never
settling. HAPI showed a session that looked like it was thinking and
said nothing else.
Subscribes to that stream once the ACP session id is known and reports
retries as the same api_error system message Claude sessions already
use, so the web timeline folds a run of them into one block whose
attempt count climbs. The reason rides along in the payload, and the
presentation now appends it to "Retrying..." when an agent supplies one;
sessions that supply nothing render exactly as before.
Not every attempt is announced. The backoff tops out at 30 seconds and
OpenCode does not give up, so a session held against a daily quota would
otherwise persist two messages a minute for as long as it is left
running. The first few attempts are reported, then only attempt numbers
that are powers of two, which needs no clock to decide.
The subscription must be scoped with ?directory=: without it the
endpoint delivers heartbeats and no session events at all, with neither
an error nor a 404 to notice. Its session.error event is deliberately
not read — it carries the Authorization header, cookies and the full
response body verbatim, and the same failure already reaches the user
stripped to a message through the ACP prompt error.
Delegated turns are not covered: OpenCode's subagent tool runs them in a
child session with its own id, and this follows only the one it was
opened for.
No countdown is rendered, though the payload offers one: a timeline
block outlives the turn it describes. Turn state is left alone; a
retrying session really is busy.
* fix(opencode): only relay prompt failures OpenCode itself reported
AcpStdioTransport flattens a JSON-RPC error response to
new Error(response.error.message), so a rejected session/prompt is
indistinguishable by shape from an error the transport built locally.
Its process-close error appends up to 4KB of raw subprocess stderr to
the message, which the formatter would then have published to the hub
and every connected client.
Requiring the "Internal error: " wrapper OpenCode puts on every service
failure turns this into an allowlist: an unrecognised rejection renders
the sentence this path rendered before rather than whatever it happened
to contain. Retry text is collapsed to one line before it is judged,
since a whitespace-only message is truthy and embedded newlines break
the one-line contract the helper states.
* fix(web): align and remember Codex-family permission mode
* fix(web): restore family permission preferences for all agents
* fix(web): scope legacy yolo migration to Codex
* fix(pi): sync native session name
* fix(pi): sync live native renames via session_info_changed
Pi emits session_info_changed on /name and set_session_name. Share the
native-title sync callback between get_state startup/resume and the live
rename event so HAPI metadata.summary.text mirrors Pi's authoritative
session name without a change_title flow. Explicit HAPI/web renames
(metadata.name) keep precedence per sessionTitle.ts.
* fix(agy): sync native Anti-Gravity conversation titles (#1439)
Read the Anti-Gravity CLI's conversation_summaries.db title for the
active brain UUID and mirror it into HAPI session metadata.summary,
reusing the existing native-title normalizer so placeholder/empty
titles are ignored and a user-defined metadata.name stays preferred
by the web title logic.
The existing AGY scanner polls every 5s while a brain is known, so the
title follows native generation/rename without PTY parsing. Lookup is
best-effort: missing/locked DB or schema drift degrades to no-op.
* test(agy): cover delayed/renamed native title polling and cleanup stop
Addresses HAPI Bot review suggestion on #1476: scanner re-reads the
native title on later scans (null -> generated -> renamed) and stops
reading after cleanup.
* fix(agy): preserve PTY running status
* fix(agent): let a trailing idle marker win over a same-chunk busy marker
runAgentPty treated any busy marker in a chunk as authoritative, so a
chunk carrying both 'Generating' and the idle footer discarded the idle
marker. With the silence watchdog disabled (agy), nothing re-armed
inputReady and the session stayed 'running' forever. Compare marker
positions: the last one in the chunk reflects the final repaint.
Adds lastMarkerIndex for string and non-global RegExp markers and a
null-watchdog regression with both markers in one ANSI-decorated chunk.
* fix(agent): scan the full chunk and keep busy-run completion bookkeeping
- Scan promptBuffer + data before truncating to PROMPT_BUFFER_SIZE, so a
busy marker followed by more than 4096 bytes in one callback is still
seen; otherwise the run never completes and a pending web delivery can
block subsequent messages.
- When an idle marker wins over a same-chunk busy marker, keep the busy
confirmation so completeAgentRun fires for the run that just ended.
- lastMarkerIndex now clones non-global RegExps with the g flag and scans
the original string, preserving anchors and terminating on zero-width
matches (previously /$/ looped forever).
* fix(cli): skip invalid set_mode for interactive Copilot sessions
applyInitialAgentMode() unconditionally sent session/set_mode with
modeId 'interactive', which the Copilot ACP server rejects (Invalid
mode 'interactive' - supported: agent/plan/autopilot), killing every
default-mode Copilot session at startup.
- Short-circuit applyAgentMode('interactive') as a no-op success,
mirroring buildCopilotAcpArgs which already omits --mode at spawn
- Classify 'Invalid mode' responses as unsupported runtime switching so
server-side mode rejections degrade gracefully (restart to apply)
Verified: bun typecheck + bun run test (full suite green).
* fix(cli): map Copilot interactive mode to ACP agent mode
Address review findings: the previous no-op for interactive left the
backend in Plan/Autopilot after a runtime switch while HAPI reported
Interactive, and classifying 'Invalid mode' responses permanently
disabled switching for valid modes too.
- Map interactive -> the ACP 'agent' mode id (the spawn default) so
session/set_mode always receives a valid mode and runtime switches
back to Interactive stay in sync with the backend
- Keep 'Invalid mode' out of the capability-loss classifier: it is a
per-mode rejection, not evidence that set_mode is unavailable
* fix(cli): remap bracketed Cursor wires onto bare/SKU catalogs
#1271 only handled the grok-4.5 legacy alias family. Persisted wires like
gpt-5.3-codex[fast=false] still failed agent --model on resume against
today's bare ACP catalogs. Remap non-legacy wires to bare bases / CLI
SKUs (and nearest configOption wires), and prefer spawn-safe ids in
session state.
Fixes#1428
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): harden Cursor stale-model remap against silent wrong picks
Address Codex Major review on #1430: prefer spawn-safe ids even on exact
bracket cache hits, require explicit wire params when ranking configOption
candidates, and stop downgrading unavailable CLI SKU variants.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): preserve desired Cursor variant across spawn-safe remap
Do not overwrite session.model with the bare/SKU spawn id before ACP
apply, and keep spawn-safe requested ids in wireIdForCursorSessionState
so apply does not re-poison hub state with bracket wires.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): remap-retry Cursor session/new on stale model reject
Initialize and session/load already retried once after Cannot use this
model; session/new had the same failure mode with an empty shared cache.
Mirror the one-shot remap+respawn path and cover it with a launcher test.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): resolve Cursor apply against ACP option values first
Spawn-safe remap prefers bare/SKU ids, but set_config_option may only
accept full bracket wires. Resolve against the model option list before
falling back to metadata so apply does not pick a rejected bare base.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): fail Cursor launch when remapped spawn cannot restore model
When --model was remapped for spawn, restoring the original desired
variant via ACP is mandatory. Soft-failing left sessions running on the
wrong model. Cover success and hard-fail paths in launcher tests.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Dedicated C0 chords for Jed Emacs save (C-x C-s); sticky Ctrl+softkey is unreliable on mobile IME. Keeps Ctrl+L.
Fixes#1459
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): keep project pin inside groups below In progress
Revert #1432's section lift: Pin in project stays first inside its
folder and among directory groups, but must not promote whole folders
above In progress. Restore Settings copy to match.
Fixes#1457
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): align pin-in-progress Settings copy with exclusions
Describe unpinned floaters only, keep global pins above In progress,
and avoid implying every working session or project-pin folders float.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Durable project pins were losing to unpinned In progress floaters.
Render pin-containing directory groups between the global pinned band
and the In progress section; leave project-pinned rows inside groups.
Closes#1431
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(shared): parse AGENT_NOTIFY_SUMMARY with glued last-line prose
Agents sometimes omit the newline before the footer. Accept an optional
prefix on the last non-empty line when it still ends with a well-formed
AGENT_NOTIFY_SUMMARY JSON payload. Mid-body quotes stay ignored.
Fixes#1425
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(shared): prefer first valid AGENT_NOTIFY_SUMMARY JSON match
lastIndexOf broke footers whose string values mentioned the token.
Scan left-to-right and accept the first occurrence with parseable JSON.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): preserve drafts across inactive session resume
* fix(web): address resumed draft review findings
* fix(web): preserve drafts during hydration resume
* fix(web): reset resume cache when session lifecycle changes
Clear resolvedSessionRef when session id or active flag changes so a same-id resume that later archives again cannot skip a required new resume.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): gate inactive attachments on resumability
Skip the attachment adapter and disable drops when an inactive session cannot resume, so file picks do not become stuck error attachments.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): avoid source-session 404 during reopen handoff
When reopen merges into a different id, skip invalidating the source session detail so draft transfer can finish before navigation without flashing Session unavailable.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): include in-flight attachments in inactive resume handoff
Pass the selected file into draft transfer and coalesce concurrent multi-file drops so resume navigation cannot drop attachments that were never published to the live snapshot.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): harden inactive draft handoff edge cases
Re-check attachment cancellation after resume, always copy from the source draft instead of a stale target snapshot, and force a fresh getSession after optimistic seeding.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): prevent passive resume and preserve staggered handoffs
Restore attachments only on active sessions so draft hydration cannot resume an archived session, and remember completed handoff targets so slower multi-file adds append instead of overwriting.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): avoid empty inactive snapshots shadowing attachment drafts
When attachment restoration is disabled, persist composer text without publishing an empty live snapshot so reopen can still transfer IndexedDB attachments.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(web): cover inactive draft archive-switch-reopen lifecycle
Add a component-level harness that exercises HappyComposer snapshot rules with useComposerDraft and transferComposerDraft across archive, session switch, and reopen to a new id.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): resolve SessionChat conflict markers for draft handoff
Keep upstream send-message types and the inactive-resume handoffComposerDraft import so typecheck no longer dies on committed merge markers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: retrigger CI after conflict-marker fix
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: retry CI after Actions outage (Meta daily)
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: retrigger CI and PR review on current tip
Re-queue Test and Codex PR Review after the conflict-marker fix and Actions outage.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): do not overwrite stored attachments from inactive composer
Skip attachment draft persistence on unmount when restoration is disabled, so a failed inactive file pick cannot replace hidden IndexedDB attachments with a partial visible list.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): merge inactive picks into stored attachment drafts
When restoration is disabled, persist visible pending attachments by merging them with IndexedDB instead of clearing the live snapshot alone, so a failed resume still keeps newly selected files for the next reopen.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): harden inactive draft persist for failed picks
Keep pathless attachment ids stable, serialize inactive merge/writes, drop removed visible picks from IndexedDB, and await pending persist before transfer/reopen.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): navigate after cancel during inactive resume handoff
If resume already merged into a new session id, still call onSessionResolved without the cancelled file so draft transfer + navigation leave the deleted source route.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): harden inactive draft handoff cancel and persist
Carry a live isCancelled predicate through resume handoff so mid-transfer
removes drop the file from the source snapshot, persist inactive blobs only
on attachment revision (not keystrokes), and keep upload metadata on
same-target staggered appends.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): await durable draft move and tombstone source
Cross-session transfer now awaits an atomic IndexedDB put+delete via
moveDraftAttachments, clears the source text draft, and skips unmount
re-persist once the source id is marked handed off.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): re-sample cancel after draft drain; fail closed on move
moveDraftAttachments resolves attachments only after awaiting queued
writes, and rolls cache back + throws when a real IndexedDB transaction
fails so transfer cannot clear the source or mark a handoff on loss.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): re-sample composer text after awaited draft move
Keystrokes during the IndexedDB drain keep updating the source draft;
re-read live/persisted text after the move resolves before clearing the
source and writing the target snapshot.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): navigate after resume even if draft move fails
Hub reopen can delete the source row before local IndexedDB finishes.
Catch transfer failures, still copy text best-effort, and always navigate
via transferComposerDraftThenNavigate / handoff onNavigable.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): harden draft move prune and in-flight persist
Delete the source row before put/prune so a full retention store cannot
evict an unrelated draft, and buffer inactive persist edits during an
in-flight transfer onto the target instead of recreating the source.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): close inactive draft remount and transfer races
Reset visibility bookkeeping on inactive remount, install the transfer
barrier before the first await, and await a durable corrective target
write when late edits land during an in-flight move.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): unbreak draft transfer typecheck and remaining races
Re-read pendingTransfers.latest after awaits (TS2339), propagate real
IndexedDB write failures on corrective same-target moves, and restore only
missing stored attachment ids when same-id resume already has a visible pick.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): stabilize late draft edits and multi-file resume
Repeat corrective target writes until pending.latest is unchanged across
the await, sync keystrokes into the transfer barrier, and keep one resume
promise per inactive adapter instance so staggered files join the same
handoff after navigation.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): keep IDB attachments when typing during transfer
Track inactive keystrokes on pending.latestText instead of inventing
pending.latest with an empty attachment list that would overwrite
hidden stored files during a cross-session move.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): fail closed when draft attachment reads fail
Cross-session transfer now requests throwOnError for IndexedDB reads so a
transient empty result cannot delete the source draft during the durable move.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): fail closed on inactive merge reads and keep target text
Strict-read every inactive attachment RMW merge so a transient IDB error
cannot publish a partial live snapshot, and copy composer text to the
resumed id before aborting when attachment transfer reads fail.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): keep resumed session active after stale detail fetch
Reapply optimistic active after background getSession settles so a lagging
inactive REST snapshot cannot flip the target composer back, and catch
strict inactive persist rejections at fire-and-forget call sites.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): prune attachments cancelled during durable draft move
Always rewrite the target after the cross-session move so isCancelled
flips during the IndexedDB commit window still drop the file, and prune
again in handoff before navigation when the batch reports cancellation.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): stabilize multi-file cancel across draft corrections
Treat the pending cancellation set as part of post-move stability so a
sibling remove mid-corrective rewrite is re-sampled before navigation,
and loop handoff same-target rewrites until that set stops changing.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): transfer inactive drafts before text-only resume send
Await onSessionResolved so composer draft handoff completes before the
send mutation, and route text-only auto-resume through
transferComposerDraftThenNavigate like upload/reopen paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): transfer Files reopen drafts and respect SSE active
Wire the Files-page reopen path through transferComposerDraftThenNavigate,
and refresh resumed session detail only when the cached active flag still
matches so a mid-flight inactive SSE transition is not resurrected.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): defer send until hidden inactive drafts hydrate
After text-only resume transfers stored attachments, skip the mutation
when the target draft still has files the inactive composer never
exposed, so clearDraftsAfterSend cannot wipe them after a text-only POST.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): defer any attachment resume and expose stored drafts
Always defer cross-session send when visible or stored attachments exist
so source-scoped upload paths are not POSTed, and surface IndexedDB
attachment presence to disable scheduling on inactive composers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): resume same-id drafts and block pending schedule races
Return resumed independently of session id so same-row PTY/Pi resumes
still defer for hidden files, route attachment-only Send through an
explicit resume callback, and treat incomplete inactive hydration as
schedule-blocking.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): preserve send text during deferred draft handoff
Pass the immutable submitted text into draft transfer so assistant-ui clearing the composer while resume is in flight cannot drop text from deferred hidden-attachment sends.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: HeavyGee <133152184+heavygee@users.noreply.github.com>
* feat(cli): cross-flavor inline image display via MCP and ACP
Share display_image prompt across MCP-bridge flavors (Cursor, Gemini,
Kimi, Codex, Claude, OpenCode), auto-approve the tool in
buildHapiMcpBridge, handle ACP image content blocks, and harden
generated-image registration with content sniffing.
Closes tiann/hapi#956
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): render generated-image cards reliably in chat
Keep object URLs stable across refetch, upscale tiny inline images,
fetch generated-image bytes with cache no-store (avoid empty 304 bodies),
and load hapiMcpUrl from per-session API in hapi-display-image tooling.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli+web): display_video MCP for inline mp4/webm (#956)
Add display_video alongside display_image, video MIME sniffing with avif
guard, web GeneratedImageCard video player, and hapi-display-image auto-routing.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cli+web): cross-flavor display_video parity with images (#956)
Share display_video prompts across MCP-bridge flavors, auto-approve the
tool, register mp4/webm via path sniffing, render inline video in web on
the existing generated-image RPC path, and restore robust media card fetch.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): ACP image ordering and inline media source provenance
Flush buffered assistant text before async generated_image emit from ACP
image blocks (PR #958 review Major). Add optional source metadata on
generated-image wire messages (ingress, flavor, toolCallId, toolName) for
MCP, ACP, and Codex tool-result paths. Seeds artifact-event follow-up #966.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli+web): address PR #958 review Majors on media order and stale blobs
Queue ACP session updates and await async image registration before later
events; clear GeneratedImageCard blob state when imageId changes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): await ACP queue after late-drain before turn_complete
Straggler session/update during drainLateBuffers can queue async image
registration; re-await sessionUpdateQueue so generated_image is not emitted
after turn_complete.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(scripts): route AVIF ftyp brands to display_image in helper
Match server-side detectImageMimeType so .avif files are not sent to
display_video and rejected as unsupported video.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(#956): agent inline-media doctor and discovery fixes
- hapi doctor inline-media: probe bridges, print per-session inline commands
- Expose hapiMcpUrl on session list summaries (stops false "no MCP" scans)
- Helper script: match cursorSessionId prefixes; HAPI_SESSION_ID path-only mode
- ACP bridge prompt: shell fallback + HAPI session id vs agent id rule
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): allow immutable cache for generated media blobs
Drop cache: no-store on generated-image fetch so browser can reuse hub
immutable responses; on 304 re-read via force-cache (#927, PR review).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(#956): Cursor native MCP overlay; drop user-turn bridge prepend
Cursor ACP ignores session/new mcpServers. Write .cursor/mcp.json and
run agent mcp enable hapi instead. Remove HAPI_MCP_BRIDGE_PROMPT from
user turns on ACP remotes; enrich MCP tool descriptions for discovery.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): preserve non-HAPI mcp.json keys on Cursor overlay cleanup
Cleanup only removes or restores the hapi MCP entry instead of rewriting
the full pre-session snapshot, so concurrent edits to other servers survive.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): handle generated_image in Grok ACP launcher switch
Upstream Grok launcher exhaustiveness broke after AgentMessage gained
generated_image for cross-flavor inline media.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): rebase fallout for display_video + OpenCode skill lookup
Gate display_video in the STDIO bridge, restore OpenCode first-prompt
TITLE_INSTRUCTION (skill_lookup), and update tool-list test expectations.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): leave user-owned hapi MCP entry alone on overlay cleanup
Only undo mcpServers.hapi when it still matches the exact entry this
session installed; concurrent Cursor/user edits of that key survive.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): exact-match auto-approve for display_image and display_video
Move media tools off substring name/id hints onto the exact-name set so
forged lookalike tools are not approved in default permission mode.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): drop dead Cursor bridge prompt; put guidance in MCP descriptions
Cursor must not get a user-turn media prepend (prompt-taint). Remove unused
HAPI_MCP_BRIDGE_PROMPT_CURSOR and embed DISPLAY_*_PROMPT_CURSOR in the
display_image/display_video MCP tool descriptions instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): require user approval for display_image and display_video
Those tools read arbitrary local paths into chat; keep them on MCP
approval_mode prompt and out of default-mode auto-approve exact names.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: re-trigger Codex PR review after provider 503
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): ignore URI-only ACP image blocks that read local disk
Passive ACP agentMessageChunk handling must not load file:// or bare
paths; local media goes through prompt-gated display_image/display_video.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): allowlist MP4 ftyp brands for video sniffing
Reject HEIC/HEIF and other non-video ISO-BMFF containers instead of
treating every non-AVIF ftyp as video/mp4.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): keep Cursor ACP startup if MCP overlay fails
Wrap installCursorMcpOverlay so a malformed project .cursor/mcp.json
cannot abort the session; continue without inline media tools.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): fail doctor inline-media when checks fail
Exit non-zero whenever required checks fail, even if an active
hapiMcpUrl bridge is present.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): expect display_video when change_title is disabled
Native ACP title mode still exposes display_image and display_video;
update startHappyServer test after rebase onto 0.23.4.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): keep ACP title sync synchronous outside media queue
session_info_update title forwarding (#1028) must not wait on the
async message-handler queue used for inline media ordering.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): sniff media headers only; advertise OpenCode display_video
Read 16 bytes for detectMediaTool instead of the whole file, and include
hapi_display_video in OPENCODE_NATIVE_TOOL_INSTRUCTION for remote ACP.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): show Cursor generate_image inline in HAPI chat
cursor/generate_image only emitted a tool card; register filePath or
base64 imageData into generatedImages and emit generated_image so the
web chat card renders (issue #956 / swear01 report).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): ignore path-only Cursor generate_image reads
Path-only filePath registration bypassed permission-gated display_image /
display_video MCP tools. Keep base64 imageData only; local paths must go
through MCP approval.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): gate inline media base64 length before decode
Reject oversized ACP/Cursor base64 payloads by character count so the
CLI never allocates past the 25 MB generated-image cap.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): require EBML DocType webm for inline video sniff
Bare EBML magic matches Matroska/MKV too; only accept DocType webm.
Also restore annotated Playwright cursor in annotatedVideoUseOption.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(scripts): read 128-byte header for WebM DocType sniff
detectMediaTool only loaded 16 bytes, so EBML DocType webm was often
missing and valid WebM files fell through to display_image.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): compare generated-image source by value in reconcile
Wire normalization allocates a fresh source object each pass; reference
equality forced media-card recomputation on every reload/SSE refresh.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): inject Cursor MCP enable for overlay unit tests
installCursorMcpOverlay always spawned `agent mcp enable`; tests now pass
a noop so the suite never shells out to a real Cursor binary.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): shell-quote doctor inline-media helper command
Paths and session prefixes with spaces/metacharacters broke the copied
snippet; JSON.stringify each interpolated argument.
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(cli): fix doctor inline-media quote path expectation
Repo root from scriptPath is three levels up (cli/), not the parent of cli.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli/web): per-session Cursor MCP overlay id and bound tiny-image scale
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): redact generate_image base64 from logs and fix doctor MCP ids
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): harden inline-media doctor for packaged installs and hub headers
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): lock Cursor mcp.json updates and bound ACP media filenames
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): preserve mcp.json mode and token-scoped overlay locks
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): publish Cursor MCP lock owners via link(2) and treat EPERM as alive
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): fail closed on stale MCP locks; keep concurrent mcp.json top-level keys
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): roll back Cursor MCP overlay when agent mcp enable fails
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): await ACP session queue in suppressUpdatesDuring tests
#958 queues handleUpdate for media registration; upstream compact tests
assumed sync delivery after restore.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): capture ACP handler at enqueue; keep display_video manual
Close two Major review findings on #958: suppress queue leak after
restore, and Claude --allowedTools auto-approving local-path video.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: write through symlinked mcp.json; lazy-load inline video
Preserve user Cursor MCP symlinks on atomic overlay writes, and require
explicit Load video before fetching large generated-video blobs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): use valid TerminalToolDisplayMode in media card test
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(scripts): require unique session prefix in display helper
Reject ambiguous prefix matches so images/videos cannot land in the
wrong HAPI chat when multiple agent session ids share a prefix.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): always cleanup Cursor MCP overlay on teardown
Run overlay cleanup in finally so cancelAll/disconnect failures cannot
leave a dead hapi-<sessionId> entry in .cursor/mcp.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): handle Copilot generated_image; refuse MCP symlinks
Unblock typecheck after Antigravity/Copilot merge, and fail closed when
.cursor/mcp.json or .cursor is a project-controlled symlink.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: abort restore deliveryMode; prune dead Cursor MCP overlays
Unblock web typecheck after steer merge, and recover orphaned hapi-*
mcp.json entries via HAPI_MCP_OVERLAY_PID ownership stamps.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): recover dead-PID Cursor MCP overlay locks
Token-matched unlock so a crash mid-lock no longer permanently disables
inline media; keep live-owner waits identity-safe.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): serialize Cursor MCP stale-lock recovery
Acquire an exclusive recovery lock before token-matched unlink so two
recoverers cannot remove a successor's live mcp.json lock.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): fail closed on stale Cursor MCP overlay locks
Withdraw racy auto-recovery: pathname check-then-unlink/rename can steal
a successor lock. Stale locks throw with an explicit rm hint instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): drop duplicate deliveryMode on abort restore
Merge left both steer and queue; keep queue so retries after abort
do not re-bind to a later Pi turn.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): bound inline media reads on open fd
Close TOCTOU between pathname size check and readFile for display_image /
display_video and registerGeneratedImageFromPath. Also preserve non-PID
env edits on Cursor MCP overlay cleanup.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): repair happyMcpStdioBridge test syntax after merge
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): write Cursor MCP overlay to ~/.cursor, not the project
Keep ephemeral hapi-<sessionId> bridges out of the checked-out tree so
agents cannot git-add a live loopback URL. Tests inject mcpConfigDir.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): point Cursor MCP diagnostics at ~/.cursor/mcp.json
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(media): add audio and file display
---------
Co-authored-by: HeavyGee <133152184+heavygee@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
* refactor(agy): make sweepAgyHookCarriers async via fs/promises
Convert the carrier sweep's directory scan (readdir/lstat/readFile/rm)
from sync fs calls to fs/promises, so it no longer blocks the event
loop while it runs. The call site in runAgy.ts still awaits it in
place, so this preserves the current blocking-before-hook-server
ordering exactly -- only the mechanism changes.
Also adds racing-safety tests proving a carrier created after the
readdir() snapshot was taken (or written by this session's own
prepareAgyHookCarrier() while a sweep is in flight) is never examined,
since decoupling the call site (next commit) makes that interleaving
possible for the first time.
* perf(agy): decouple carrier sweep from session startup
sweepAgyHookCarriers is a backup path -- normal teardown already
removes a carrier via cleanupAgyHookCarrier, so sweep only matters
after a crash. There is no reason for it to delay this session's own
startup (hook server, carrier prep, PTY spawn).
Now that it is async (previous commit), fire it without awaiting it
instead of blocking on it before startHookServer. It runs concurrently
with this session's own prepareAgyHookCarrier() call further down;
the previous commit's racing-safety tests prove that interleaving is
safe. Still guaranteed to never throw or leave an unhandled rejection.
* refactor(agy): add a process-lifetime cache for carrier scope
The upcoming macOS/Windows identity probes are async child-process
calls (ioreg/sysctl, reg query), but writeOwnerMetadata is called
synchronously from prepareAgyHookCarrier -- including from
agyPtyLauncher.ts's respawn path, which must stay synchronous per its
fail-closed contract. A warm cache lets that synchronous call read a
value computed ahead of time instead of needing to await a probe.
Adds computeLocalCarrierScopeAsync (platform dispatcher, Linux-only
for now -- delegates to the exact same sync read computeLocalCarrierScope
already does, so no platform's observable output changes here),
warmCarrierScope (populates the cache once, including caching a
failed probe so it is never retried), and resolveLocalCarrierScope
(used by sweepAgyHookCarriers; bypasses the cache for any non-default
probe so the many existing custom-probe tests keep forcing a fresh
computation per call). writeOwnerMetadata reads the cache first and
falls back to the existing synchronous Linux computation when the
cache was never warmed -- unchanged behavior wherever nothing calls
warmCarrierScope yet.
* feat(agy): enable carrier sweep on macOS
computeLocalCarrierScope was Linux-only, so sweepAgyHookCarriers was a
no-op on macOS -- only crash leftovers there ever accumulated (normal
teardown still works via cleanupAgyHookCarrier), but they accumulated
forever.
Adds a strong-identity scope for macOS: IOPlatformUUID (ioreg) +
kern.bootsessionuuid (sysctl), the macOS analogue of Linux's boot_id
-- NOT kern.boottime, which a live re-measurement showed drifts by
over a second across 8 days on the same boot (recomputed from
NTP-adjusted wall clock time under the hood). The probe runs by
absolute path via execFile (never a shell, never PATH-dependent) with
a 2s timeout; a failed or timed-out probe still resolves to undefined,
preserving sweepAgyHookCarriers's existing "cannot identify -> preserve
everything" contract unchanged.
Windows is deliberately not enabled. MachineGuid -- the obvious
candidate for a win32 scope -- is a machine identifier, not a
PID-space identifier: it is written once at OS install time and is
NOT regenerated by cloning a disk image (that is exactly what sysprep
exists to fix). Two clones of the same image sharing a HAPI_HOME (SMB
share, sync folder, shared VM folder) would compute the identical
win32:<guid> scope while having completely independent PID spaces,
so sweep could delete a live session's carrier and its
--dangerously-skip-permissions approval bridge with it. No cheap
per-boot alternative exists on Windows: no boot-id registry key,
Get-CimInstance's LastBootUpTime measured 1.4-2.5s per call (an order
of magnitude too slow for identity plumbing), and net
statistics/systeminfo output is locale-dependent. computeLocalCarrierScopeAsync's
docstring records the full reasoning and the bar for re-enabling it
(a boot-scoped, not machine-scoped, identifier cheaper than CIM).
Windows carriers keep accumulating exactly as before this change --
this is a purely additive capability for macOS.
Wires warmCarrierScope into runAgy.ts's PTY setup: fired without
awaiting it right after the sweep call (so the probe cost -- two
child processes on macOS -- overlaps with hook server startup instead
of adding to it), then awaited immediately before prepareAgyHookCarrier()
so its synchronous owner.json write reads a warm cache. agyPtyLauncher.ts's
respawn path needs no equivalent wiring: it always runs in the same,
by-then-warm process.
* feat(settings): onboard hub transcription provider credentials in UI
Env-only keys made dictation invisible; Settings can now add/edit/clear
hub-side credentials (masked), with env still winning as override.
Refs tiann/hapi#1384.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(settings): onboard voice-assistant backends alongside dictation
Same Settings credential surface now covers ElevenLabs, Gemini Live, and
Qwen Realtime (alias env pairs), not only transcription providers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): address PR #1392 Major credential onboard findings
Alias env locks, non-destructive Save (omit empty fields), and
owner-only settings.json permissions for hub-stored provider secrets.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): harden credential onboard for second-pass Majors
Owner-namespace gate, stage-then-sync env after persist, and
per-field OpenAI-compatible editability under mixed env locks.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): serialize settings RMW and clear partial compatible creds
Per-file settings lock for concurrent credential PUTs, and Clear shown
for partial OpenAI-compatible entries (key/url/model alone).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): serialize all settings writers via updateSettings
Route credentials, relay auth, generators, server settings, and CLI
token persistence through a locked RMW helper; reset Clear form state.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): share cross-process settings lock with CLI
Extract withSettingsFileLock for hub+CLI, keep owner-only 0o600
rewrites, and race hub credential updates against CLI-style writers.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): keep UI secrets out of process.env; PID-own settings locks
Settings-backed provider credentials now live in an in-memory overlay
(getProviderEnvironment) so tunnel/ACP/Codex children do not inherit them.
Settings file locks record pid+token and only reclaim dead or legacy locks.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): never reclaim ownerless settings lock sidecars
wx creates the lock path before the owner JSON is visible; unlinking
null owners let a waiter steal a live acquisition and collide on
settings.json.tmp (CI ENOENT). Only reclaim parsed owners with dead PIDs.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): reclaim dead locks via rename; clean up failed publishes
Stale reclaim renames the sidecar to a unique break path and re-verifies
the expected dead owner before deleting it, so a loser cannot unlink a
successor's live lock. Failed owner writes unlink the wx sidecar.
Reclaim uses a sync owner read so contenders do not all observe one
dead owner across an await and race the exclusive create.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): reclaim dead locks under exclusive reaper sidecar
Stale reclaim now takes a fixed settings.json.lock.reap lock, re-validates
pid+token, then unlinks — so a delayed contender cannot move a successor's
live lock aside. Also document providerCredentials in settings.schema.json.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): fail closed on corrupt CLI settings; backoff busy reaper
CLI updateSettings now uses a strict read that rejects invalid JSON
instead of treating errors as {}, which could wipe providerCredentials.
Settings lock reclaim sleeps when another process holds .reap so retries
are not burned synchronously.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): publish locks via candidate+link; fix CLI vitest hoist
Acquire settings locks by writing a complete candidate then linkSync to
the fixed path so a crash cannot leave an empty live sidecar. Fix the
CLI persistence regression test to create its temp dir inside vi.hoisted.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): replace bespoke lock with proper-lockfile; hide tenant creds UI
Codex kept finding crash windows in hand-rolled lock sidecars. Switch the
shared settings lock to proper-lockfile's mkdir + mtime lease. Hide the
owner-only credentials editor from non-default namespaces on the voice page.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(settings): adapt sessionSummaryContract to outcome updateSettings
Rebase onto main brought #1376 unique tmp + outcome-shaped writers;
wire sessionSummaryContract and the write-failure credential test to match.
Co-authored-by: Cursor <cursoragent@cursor.com>
* chore: retrigger CI after rebase onto upstream/main
Empty commit — Meta reported no checks on da0c6c258 after tip-forward rebase.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(web): interactive storage usage pie in Settings
Show relative DB/WAL/SHM share beside absolute sizes on the
Storage settings page, with hover/legend selection and no new
chart dependency.
Fixes#1382
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): lead Settings Storage with relative-share donut
Put the interactive chart above exact byte rows so mobile users
see proportions first and scroll for drill-down sizes.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): drop duplicate Exact sizes table on Storage
Keep one interactive donut + legend (bytes/%) beside or below the
chart; total and path stay as non-redundant footer lines.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): storage pie legend roving tabindex + empty footers
Address Codex review: one Tab stop via roving tabindex, total outside
the listbox, and keep total/path when all slices are zero.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(web): opt-in needs-attention session list filter
Add a default-off sidebar filter (and Settings → Display toggle) that
keeps permission/input/unread rows while excluding background-only busy
work, mirroring the active-only preference pattern from #903.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): make needs-attention filter a transient sidebar toggle
Drop the Settings → Display preference and localStorage persistence.
This is an inbox lens: toggle in the session list, rows fall away as
they're seen, and a reload clears the filter.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): unread-only sidebar lens, not needs-attention
Rename the transient toggle to Unread only and filter solely on
updatedAt > lastSeenAt. Permission/input attention stays on row dots;
this is not Overseer review inbox.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): use unread-dot shape for unread filter toggle
Match the session-row unread indicator instead of an inbox tray so
Overseer can own inbox chrome later without competing metaphors.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): apply unread filter after machine scope
Keep machineFilters derived from the unfiltered sidebar set so a
persisted machine selection is not cleared when that machine has no
unread rows. Unread intersects with the selected machine instead.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): snapshot last-seen once for unread filter pass
Avoid N localStorage read/parse calls while the unread lens is on by
loading the store once per memoized filter pass.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): collapse expanded composer after send
* fix(web): wait for send result before collapsing composer
* fix(web): match composer collapse to send settlement
* feat: settings toggle for AGENT_NOTIFY_SUMMARY contract injection
Add a hub-persisted, default-off Settings control so operators can opt agents into emitting the trailing AGENT_NOTIFY_SUMMARY line. Propagate the resolved flag on CLI session bootstrap and inject at call time for Claude, Codex, OpenCode, and Grok (Cursor still unsupported).
Closes#1375
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: lock hub settings RMW and restore abort deliveryMode
Serialize settings.json updates with the shared .lock protocol and unique
temp files so the new hub toggle cannot clobber CLI/relay fields under
concurrency. Also supply deliveryMode on abort send-error restore so web
typecheck (and CI) pass.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): wrap SettingsGeneralPage tests with QueryClientProvider
The hub-settings toggle uses TanStack Query; the settings page suite
was rendering without a QueryClient and blew up CI.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): defer summary-contract toggle until settings load
Avoid rendering an interactive false switch while the hub GET is still
in flight, which could overwrite an enabled preference on early click.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): clarify Grok coverage for summary-contract toggle
Local Grok has no instruction inject path; settings copy now matches
remote-only Grok support (and still excludes Cursor).
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Normalize usage input at parse time, mark inclusive producers, rebuild derived usage indexes, and preserve valid primary usage when cache partitions are malformed.
Fixes#1389
* fix(web): show truncated query on collapsed session search
Collapsed SessionListSearch only showed an indicator dot for active
filters, hiding the terms operators need. Render a compact truncated
query chip when text filter is set (Fixestiann/hapi#1356).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(web): use chat-user chip colors for active search chip
--app-link/15 washes to near-gray in default light mode (link is
near-black). Use --app-chat-user-chip-* so the collapsed query chip
stays a visible blue highlight when text truncates away.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>