mirror of
https://github.com/wu736139669/hapi.git
synced 2026-10-24 22:10:59 +00:00
c69a88afaeed454df482c283cdccfa1be2c00cc2
1209
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
c69a88afae |
fix(cursor): close ACP list-models race and false exit 143 window (#1518)
* fix(cursor): close ACP list-models race and false exit 143 window Register the agent-acp-active guard before spawn, hold it until stdio close (not bare exit), record the ACP child PID, and align lock/cache home with resolveHapiHomeDir so runner and session children agree. Richer exit attribution distinguishes live-PID transport disruption from confirmed child death. Fixes residual #1472 after #835. Co-authored-by: Cursor <cursoragent@cursor.com> * test(cursor): isolate ACP guard teardown from ~/.hapi Reset afterEach under the temp HAPI_HOME only, and restore the isolated home before teardown in the unset-HAPI_HOME case, so tests cannot wipe a live agent-acp-active lock. Use distinct child PIDs in registration tests. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): publish ACP lock pid before count Fail-closed reservation order: write pids/<hostPid> before count so concurrent reconcile cannot treat a mid-register lock as stale and clear it for list-models. Keep a short mtime grace only when pids/ is missing (mkdir gap). Regression covers mid-publish readers. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): keep empty pids/ ACP reservation fail-closed Between mkdir(pids) and the host pid writeFile, reconcile could see liveCount=0 and clear the lock. Keep that window when count is still absent and the lock is fresh; re-scan for pids published mid-reconcile. Regression hooks the mkdir/write gap. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): keep ACP lock across last-unregister publish race Write a short-lived registering marker before pids/count so empty pids with leftover count cannot erase a concurrent mid-addLockPid reservation. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): use per-pid ACP registering markers Crash/reboot must not pin list-models forever on a bare registering file; prune dead owners and only keep live registrar PIDs. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
7c4b3ab17b |
fix(cursor): stop session-list spinner flicker from ACP state_update (#1503)
* fix(cursor): stop session-list spinner flicker from ACP state_update #1487 mapped Cursor ACP state_update running/idle onto hub thinking. Cursor chatters those states while HAPI is queue-idle, so keepAlive flipped thinking every ~1-2s and the session list spinner danced. Ignore state_update for thinking; only bump on real activity, clear via prompt finally/abort. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): clear thinking on ACP idle without bumping on running Refine #1502 hotfix: ignore state_update running/requires_action (Cursor chatter caused spinner flicker) but still clear on idle so mid-idle harness wakes do not stick thinking=true forever. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cursor): stop ACP background updates from flickering thinking Ignore tool/content session updates for hub thinking (ACP allows them while idle). Drive thinking from state_update only, debounce running 750ms, and skip idle clears during an in-flight HAPI prompt so #1502 residual flicker dies. --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c1ceb83ec2 |
fix(web): fail-closed scheme-less markdown file links (#1519)
* fix(web): fail-closed scheme-less markdown file links Never paint a blue SPA dead-end for path-like hrefs after #1142. Route workspace file targets (relative, abs, ~/ when expandable) through FilePathAnchor; keep real app routes navigable; render everything else path-like as inert text. Defense in <A> plus expanded remark rewrite. Refs #1452 Co-authored-by: Cursor <cursoragent@cursor.com> * test(web): fixture for fail-closed markdown file-link dogfood Visual cases for #1452: preview blues vs inert dead paths vs SPA routes. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): harden fail-closed markdown href policy for review findings Stop rewriting POSIX abs in remark so <A> can workspace-check; tighten SPA allowlist; resolve .. before containment. * fix(web): fail-closed Windows absolute markdown file links Drive paths looked scheme-bearing and skipped containment; leave them for <A> with workspace checks. * fix(web): autolink Windows paths as raw hrefs for containment Bare/inline Windows abs become anchors without hapi-file rewrite so <A> can classify; compare containment case-insensitively. * fix(web): encode Windows file links as hapi-file-candidate Backslash paths were URI-normalized to %5C before <A>; candidate encoding preserves the path for workspace classification. * fix(web): satisfy InertMarkdownHref href type for candidate paths * fix(web): resolve ~/ against /root workspaces in markdown hrefs * fix(web): reject non-Windows hapi-file-candidate payloads * fix(web): decode percent-encoded markdown paths before containment * fix(web): fail-closed empty hapi-file-candidate hrefs * fix(web): honor Vite BASE_URL and normalize candidate scheme detection --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
291e7bc40b |
fix(test): stop runner integration suite from leaking detached process trees (#1515) (#1521)
* fix(test): stop runner integration suite from leaking detached process trees (#1515) The default CLI test run included runner.integration.test.ts, which spawns real detached runner/session process trees. A failing, timed-out, or interrupted test (or a plain runner stop) left those trees alive under PID 1 — on the Mac this accumulated ~600 Node/Bun/agent processes and several GiB of RSS over repeated runs. Test harness changes only; production runner session-preservation semantics are untouched: - Exclude runner.integration.test.ts from the default parallel unit-test suite; move it into a dedicated serial integration project (vitest.integration.config.ts, 'bun run test:integration'). The 20-session stress test is opt-in via HAPI_RUN_STRESS_TESTS=true. - Add a test-owned process/session registry (processRegistry.ts): every runner, runner-spawned session, and terminal-style child is registered immediately after spawn; afterEach/afterAll run two-stage cleanup (logical stopRunnerSession first, then bounded process-tree kill), followed by a marker sweep for agent grandchildren reparented to PID 1. - Add a per-run HAPI_TEST_MARKER env stamp + identity/secret env neutralization for test children (integrationEnv.ts) so outer HAPI/pi session variables never leak into test processes and the final audit can recognize test-owned processes by env alone. - Final suite audit in globalSetup teardown: reap anything still carrying the run marker and fail with PID/command diagnostics if anything cannot be reaped, before removing the temp home. - Regression coverage: a deliberately failing test registers a detached child and the follow-up audit must find zero test-owned processes. - CI: replace the dead .env.integration-test step with a dedicated integration job running the serial project. * refactor(test): drop unused killByChildProcess import and child field from registry * chore(test): raise integration hookTimeout to 60s for slow teardown hosts * fix(test): fail loudly when the process-table audit cannot scan; assert regression child death Bot review #1521 findings: - A failed `ps` scan (unsupported flags, buffer exhaustion, permissions) previously returned [] and silently disabled both teardown audit layers. It now throws; globalSetup teardown catches the scan error into the audit error (temp home is still removed) so the run fails visibly. - The regression audit test cleaned the leak with the reaper before asserting, and force-killed the fresh marked runner. The failing test's direct child PID is now asserted dead in afterEach right after registry cleanup (before the marker sweep), and the audit test stops its own runner gracefully before reaping. * fix(test): bound the logical cleanup phase so a hung runner cannot stall the hook Bot review #1521: stopRunnerSession carries the worker's 60s HTTP timeout (setup.ts raises HAPI_RUNNER_HTTP_TIMEOUT for the stress test), and the integration hook timeout is also 60s — N sequential stops could exhaust the hook budget before the process-tree fallback and marker sweep ran, recreating the very leak this change prevents. Logical shutdown is now parallel (Promise.allSettled over all tracked sessions) and the whole phase (stops + PID resolution) races against a 15s budget, so stage-2 tree-kill and the marker sweep always get their share of the hook window. * fix(test): bound graceful runner stop in hooks; keep credentials out of audit diagnostics Bot review #1521 (follow-up): - stopRunner()'s HTTP stop can burn the worker-wide 60s timeout on a hung-but-live runner, starving the marker sweep within the hook budget. afterEach/afterAll now race the graceful stop against a 10s bound; a runner that does not stop in time is force-reaped by the sweep (it carries the run marker) and the next beforeEach's alive-PID guard ignores any stale state file. - The env-bearing ps scan (ps eww) was also used for diagnostics, so the first 500 chars of a short-command process could print inherited credentials (CLI_API_TOKEN etc.) into teardown error logs. The scan now only identifies marked PIDs; command lines are fetched separately without 'e', falling back to '(command unavailable)' instead of the env dump. * fix(test): reap runner model-probe orphans before the zero-survivor inspection Bot review #1521 (Minor): inspect-before-reap. Applying it exposed a real race: each test's runner legitimately spawns marker-carrying children at startup (agent acp + agent --list-models model-catalog probes). Stopping the runner orphans them (ppid 1) with the run marker, so the audit test's OWN runner polluted the pure inspection with fresh probes spawned after the failing test's sweep window. - reapTestOwnedProcesses now re-kills every re-scan iteration instead of killing once and only re-scanning, so a process that survived its first SIGKILL (mid-exec) or spawned mid-kill is not given a free pass. - The regression audit test stops its runner, reaps (clearing its own legitimate orphan probes), then inspects: anything still marked is a genuine survivor the bounded reaper could not remove and fails the suite. Killable leaks from the failing test are already asserted dead in afterEach before the sweep runs. * fix(test): strictly bound the marker reaper; make per-test sweep unconditional and verified Bot review #1521 (follow-up): - The 10s reap deadline did not bound the awaited per-tree kills: each killProcessTreeByPid can wait up to 2s per PID, so several stuck processes could still exceed the 60s hook budget. Every process in a test-owned tree carries the marker (env is inherited), so tree-walking is unnecessary: the reaper now SIGKILLs every marked PID found by each scan, fire-and-forget, and re-scans every 250ms — the deadline strictly bounds the function. - The per-test sweep was skipped when the direct-child assertion failed first, and its survivors were ignored. afterEach now snapshots the regression-child state BEFORE the unconditional sweep, then verifies both the registry result and the sweep leftovers. * fix(test): replace it.fails regression with a direct assertion test Bot review #1521 (Minor): Vitest applies the it.fails expected-failure inversion after afterEach, so a broken registry assertion inside the hook would be masked as an expected failure, and the marker sweep would erase the evidence before the follow-up audit ran. The regression is now a normal test that registers a detached child at spawn time, deliberately performs NO per-test teardown, runs only the spawn-time registered cleanup, and asserts the child PID is dead. The afterEach no longer carries the registry-leak assertion (moved into the test body where it cannot be inverted); the per-test sweep assertion and the final audit test are unchanged. * fix(test): bound registry stage-2 tree-kills; require live regression fixture Bot review #1521 (follow-up): - Stage-2 killProcessTreeByPid awaits per descendant serially and can consume the whole 60s hook for a large/stuck tree. Signals are all delivered synchronously (children first) before any waiting, so racing the awaits against a 5s budget bounds the phase without skipping any kill; waitForAllDead still verifies the outcome. - The regression test could pass vacuously if its fixture exited during the startup delay (the registry exit listener would remove it before cleanup). It now asserts the child is alive before running cleanup. * fix(test): kill registered roots with bare synchronous SIGKILL, no pgrep walk Bot review #1521 (follow-up): racing the mapped killProcessTreeByPid calls against a timer does not bound the phase — evaluating the map invokes each call immediately, and each runs the recursive synchronous pgrep walk before its first await, which can consume the hook before the timer, runner stop, or marker sweep run. Stage-2 now SIGKILLs registered roots directly (fire-and-forget, no tree walk, no per-PID waits) and waits a bounded 5s for death. Descendants are reaped by the unconditional marker sweep immediately afterward — every descendant inherits the run marker, so tree-walking is unnecessary. * fix(test): drop duplicate process-death wait in registry cleanup Bot review #1521 (Minor): the duplicated waitForAllDead delayed the authoritative marker sweep by another 5s under the exact stuck-process condition the harness must handle. Keep the single bounded wait; the afterEach marker sweep remains the guarantee. |
||
|
|
11964b4b0d |
fix(a2a): stamp causing inbound on work_ad notify ingest (#1510)
* fix(a2a): stamp causing inbound on work_ad notify ingest Resolve the turn cause from the full session messages table at insert so consumers do not guess from a truncated transcript. Stamp causeMessageId, causeText, causeKind, and related_event_id, and link follows to the previous work_ad. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): ignore scheduled and client-posted work_ads in cause chain Future-scheduled inbounds are not this turn's cause. Only AGENT_NOTIFY_SUMMARY rows chain related_event_id / sticky cause, so HTTP-posted work_ads cannot steer hub-derived attribution. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): skip transcript echoes and reserve notify provenance Claude jsonl echoes remote prompts as extra role=user CLI rows; mark them and ignore them when choosing a work_ad cause. HTTP event writes cannot claim AGENT_NOTIFY_SUMMARY provenance. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): stamp transcript echo only for hub-delivered prompts Local Claude TTY prompts share isExternalUserMessage; matching pending web/telegram text keeps those rows as work_ad causes. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): match Claude queue text and bound cause message scan Register pending transcript echoes at the formatted queue boundary (and drop them on cancel). Later work_ad notifies scan after causeSeq instead of decoding the full session transcript. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): note delivered Claude batch and skip uninvoked causes Register transcript-echo text at SDK delivery after batch join and skill expansion. Keep the marker when cancel misses the queue. Cause candidates require invokedAt so queued localId rows wait. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): note parked Claude batches and chain work_ads by insert order Stamp echo markers on the pending mode-switch delivery path. Advance causeSeq past every invoked inbound in the same batch. List session work_ads by rowid so backdated transcript timestamps cannot rewind the chain. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): pick latest invoked inbound after history hydration Fork/merge copies leave old user rows without prior work_ads; choose the newest invoked inbound before the assistant. Echo markers now keep batch localIds so cancel can drop a restored-then-cancelled prompt. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): drop echo markers when a Claude batch is abandoned The three-failure launcher cap discarded the in-flight prompt without clearing pending transcript-echo text, so a later identical local prompt could be misclassified. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): replace echo markers when a restored prompt is rebatched Recoverable launch failure keeps the original marker; a later same-mode prompt joins into new delivered text. Drop overlapping localId markers so a later identical local prompt is not stamped isTranscriptEcho. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): keep notify cause chain across session merge Re-key AGENT_NOTIFY_SUMMARY work_ads onto the surviving session and bound later scans by causeCursorMessageId so merge seq-shift cannot revive an already-consumed batch inbound as the sticky cause. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): keep notify rows on live source during history merge mergeSessionHistory leaves the source socket alive. Only re-key AGENT_NOTIFY_SUMMARY work_ads when mergeSessions deletes that id. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(a2a): drop id-less echo markers on rebatch and launch drop API callers may omit localId. Replace the nameless in-flight marker when a new id-less delivery is noted, and discard by delivered text when the three-failure path abandons the batch. Do not mint hub localIds (that would change invokedAt ack semantics). Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
d396e9d6d4 |
feat(voice): curate dictation credential presets to ElevenLabs, OpenAI, Groq (#1474)
* feat(voice): curate dictation credential presets to ElevenLabs, OpenAI, Groq Groq transcription was already wired end-to-end (GROQ_API_KEY, whisper-large-v3, standard mode), but the credential onboarding panel listed five providers with no hint that Groq is supported, so mobile users could not discover it. - Curate Settings > Voice > Dictation credential presets to ElevenLabs, OpenAI, and Groq (Deepgram / OpenAI-compatible remain fully supported via env and stay listed when configured) - Name the three presets in the empty-state and manage hints (en + zh-CN) - Lock the curated list in with a web preset test, a hub route test for the Groq whisper-large-v3 proxy, and shared provider-listing coverage - Note the presets and no-restart save behavior in voice-assistant.md Verified: bun typecheck (cli+web+hub) and targeted suites pass; full test gate green except pre-existing load-sensitive runner stress tests. * fix(voice): keep legacy dictation providers manageable when configured HAPI Bot review finding (Major): curating the onboard panel to the three presets made settings-managed Deepgram / OpenAI-compatible credentials impossible to rotate or clear from the UI. - Re-add deepgram / openai-compatible to the onboard provider list conditionally when credentials exist, restoring update/clear controls - Fall back to the first preset if the selected provider leaves the list - Cover the conditional list in the preset test * fix(voice): surface partial OpenAI-compatible credentials in onboard panel HAPI Bot follow-up finding (Major): hub marks openaiCompatible.configured only when both base URL and model exist, so api-key-only or endpoint-only stored settings lost the UI path to rotate or clear them. - Gate the openai-compatible onboard entry on any stored field (base URL, model, or API key) via hasOpenAICompatibleCredentials() - Cover api-key-only / base-url-only / model-only cases in tests |
||
|
|
e6b9fd68e6 |
feat(pi): queue mid-turn messages by default; steer only via explicit per-message Steer button (#1480)
* feat(pi): queue mid-turn messages by default; steer only via explicit per-message Steer button Pi (PyAgent) was the only flavor whose ordinary composer submission while streaming bypassed the queue: the web resolved it to deliveryMode 'steer' and the CLI dispatched a native steer into the running turn immediately, with no waiting state. This makes Pi match Codex/Claude behavior (issue #1466): mid-turn messages wait in the queue by default, and the operator delivers one into the running turn with the new per-queued-message Steer button. - web: resolveMessageDeliveryMode now queues for every flavor; QueuedMessagesBar gains a Steer button (pi + thinking + remote-controlled + immediate rows) backed by a new useSteerQueuedMessage hook + api.steerMessage. - hub: POST /sessions/:id/messages/:messageId/steer -> syncEngine.steerQueuedMessage (pi-only gate, remote-only, scheduled/absent/invoked rejection) -> RPC. - cli: pi runner registers 'steer-queued-message'; a queued message is promoted into the active turn via the existing PiSteerDispatcher (target generation captured at promote time; turn-ended steers fall back to the prompt FIFO). Steers requested while the message is still preparing are deferred and promoted right after preparation completes. - Removed the now-dead Alt+Enter / touch-hold queue gesture (its only purpose was opting out of the removed automatic steer). Verified: bun typecheck; cli/hub/web/shared suites (env-dependent runner integration + kimi wire-locator flakes reproduce on pristine upstream and are unrelated to this diff). * fix(pi): preserve queued messages on rejected steers and pin the steering generation Addresses both Major findings from the HAPI Bot review of PR #1480. - steerDispatcher: a deterministic native rejection (Pi responded error) now degrades the message to the ordinary prompt FIFO instead of emitting messages-consumed. A promoted queued message must not be lost just because the steer was rejected; the hub row stays queued until the FIFO delivers it. The indeterminate-timeout path keeps its fail-closed consume + escalate behavior (a duplicate delivery would be worse). - runPi: the deferred-steer path now captures the streaming generation at RPC request time (Map<localId, generation>) instead of reading it after preparation completes, so a steer requested against turn G1 can never be injected into a turn G2 that started while the message was preparing — the dispatcher's generation-mismatch check degrades it to the FIFO. Regression coverage: negative steer response preserves the entry via the FIFO (no consume); generation rollover while preparing delivers as a normal prompt at the next settle (no steer into the new turn). Verified: bun typecheck; cli pi suites (48 tests), hub 1041, web 2301, shared 240 — all green; only the pre-existing environment-dependent runner integration test fails locally (reproduces on pristine upstream). * fix(pi): reject all scheduled steers and always clear deferred-steer bookkeeping Addresses the two Minor findings from the HAPI Bot follow-up review. - hub: steerQueuedMessage rejects every scheduled row — mature ones included — aligning the endpoint with the web UI (Steer is never offered on scheduled rows) and preserving scheduled-FIFO delivery semantics. - cli: the deferred-steer bookkeeping map is now cleared in a finally on the preparation chain, covering the early exits (cancellation before/after attachment I/O, empty prepared message, preparation failure) that previously could leave a stale generation entry behind for the session lifetime. Regression coverage: hub steer gate tests (mature scheduled row stays queued, non-pi flavor rejected) and a runPi test proving cancellation wins over a deferred steer (no steer/prompt/consume after preparation completes). Verified: bun typecheck; hub 1043 pass, cli 2421 pass (only the pre-existing environment-dependent runner integration suite fails locally), web 2301 and shared 240 unchanged since their green runs. * fix(web): reconcile stale queued rows when a steer returns invoked Addresses the remaining Minor finding from the HAPI Bot follow-up review: when the steer endpoint reports the message was already invoked and the messages-consumed SSE was missed while the row was still queued, the hook now marks the row consumed locally (mirroring useCancelQueuedMessage) so the queued bar cannot keep a stale actionable row until the next sync. Regression coverage: steer returning status 'invoked' reconciles the row via markMessagesConsumed and shows no toast. Verified: bun typecheck; web 2302 pass (hub/cli/shared unchanged since their green runs). |
||
|
|
eb7a762984 | fix(web): clarify session list status hints (#1504) | ||
|
|
173da49c84 | fix(web): remove duplicate queued composer gap (#1505) | ||
|
|
c2aa4bf171 | fix(web): align generated share watermark to bottom right (#1513) | ||
|
|
1cd4d1137a |
feat(hub,cli,web): fleet runner version governance (skew, self-upgrade, soft-fail reopen) (#1108)
* fix(hub): govern runner capabilities so Cursor reopen soft-fails on skew Hub↔runner protocol drift was reported as missing Cursor chat data when cursor-chat-store-status was unregistered. Soft-fail reopen on probe errors, advertise required machine capabilities, surface an unmissable upgrade banner, and stop-runner when a newer CLI binary is already on disk. Fixes #1084 Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web,hub): make runner skew banner dismissible; gate auto-upgrade Compact the out-of-date banner (minimize + 1h snooze + per-host Restart) so it no longer blocks the session list. Auto stop-runner on skew stays opt-in via HAPI_AUTO_UPGRADE_RUNNERS / autoUpgradeRunners (default off). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): tolerate full sessionStorage on skew banner minimize QuotaExceededError from setItem aborted minimize before React state updated, leaving the banner stuck over the session list. Persist to memory when storage fails; only enable Restart when a newer CLI is already on disk; clarify opt-in is stop-runner only, not package push. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hub): drop redundant autoUpgradeRunners; runners already self-restart CLI version handoff already reloads the runner when the on-disk binary mtime changes. Hub-driven stop-runner on skew duplicated that. Keep the skew banner and manual Restart only as a stuck/disabled-handoff escape. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(cli,hub,web): runner-only caps ads; gate Restart on supervisor Address #1108 bot Majors on the thin tip: terminal/lazy bootstraps no longer merge CURRENT_MACHINE_CAPABILITIES into the machine row (only asRunner registration does). Banner Restart refuses unsupervised hosts so stop-runner cannot leave a detached laptop offline; supervised runners advertise supervisedRestart via HAPI_RUNNER_SUPERVISED=1. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(hub,cli,web): clear sticky runner ads; docs SUPERVISED; i18n skew label Omit-means-clear on runner registration so rollback cannot leave supervisedRestart/capabilities sticky; always advertise boolean supervisedRestart from asRunner. Document HAPI_RUNNER_SUPERVISED=1 and localize MachineSelector UPDATE REQUIRED. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
0d12035674 |
feat(web): searchable session picker on Android share target (#986)
* feat(web): searchable session picker on share target (#980) Add search to the Android share-target picker so operators can attach shared content to inactive or older sessions, not just recent actives. Reuses sidebar search helpers; caps default active list with a search hint. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): defer share pending consume until session is active ShareSeedConsumer was consuming the sessionStorage transfer id on first mount even when the target session was inactive. Reopening into a new session id remounted the chat with the key already gone, dropping the shared payload. Consume and seed only after sessionActive is true. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): bind share pending transfer to target session id Deferring consume until active left a global pending slot that the next unrelated active SessionChat could claim. Bind the pending transfer to the picked session id, retarget on reopen/spawn id-swap, and only consume when the mounting session matches. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): wire SessionListSearch date-range props on share picker Upstream added required customStart/customEnd/onDateRangeChange to SessionListSearch; share /share must pass them and honor the range when filtering sessions after rebase onto v0.23.1. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): pass sessionActivityDates to share SessionListSearch Upstream SessionListSearch now requires activity dates for the date picker; without them CI typecheck fails and the PR chip stays needs_work. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): use cached machine labels in share picker search Share picker now uses useMachineLabels like SessionList so stale machine ids still match search by display name. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): align share picker with sidebar session prep and preview limit Snapshot via prepareSidebarSessions and pass useSessionPreviewLimit so search targets and fold cap match the main session list. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): wire SessionListSearch expanded props on share picker Upstream search now requires expanded/onExpandedChange; default open on the share picker so the field is available immediately. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): unblock typecheck after SessionSummary and RawSendError fields Fill new required SessionSummary watermarks in the share picker test helper, and set deliveryMode on abort-restore send errors. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): mock machines hooks in share page missing-state test SharePage now calls useMachines for picker search labels; the missing- share unit test needs those hooks stubbed without a QueryClient. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): retarget pending share on automatic session supersession When SessionDetailRoute follows supersededBySessionId A→B, rewrite the share pending target so ShareSeedConsumer on B can still claim it. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse> |
||
|
|
5d8cd9b8fa |
fix(web): exclude path-only husks from @ session mentions (#1507)
* fix(web): exclude path-only husks from @ session mentions Mention autocomplete required a real title signal (metadata.name or summary text) so sidebar-hidden stubs and path-basename husks cannot win @ queries over live named sessions (#1506). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): mention @ pool uses sidebar dedup before title filter Titled stale duplicates hidden by prepareSidebarSessions were still @-able and could outrank the live row. Align mention candidates with the visible list, then keep the #1506 title-signal exclusion. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
a0621194bb |
feat(web): native/deep-link ingest for /share (GET url, text, title) (#1413)
* feat(web): ingest GET /share?url=&text=&title= deep links Native companions that cannot POST via Web Share Target can open the same session picker by synthesizing the IndexedDB transfer client-side. When id is present, the existing SW path still wins. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): preserve share deep-link whitespace; scrub content beside id Keep GET content strings verbatim when non-empty (match POST form-data). When id is present with leftover url/text/title, replace to ?id= only so payload does not linger in the address bar. Query-param contract stays — fragments would break shipped native companions. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): ingest /share deep links via URL fragment, not query Shared url/text/title must not appear on the HTTP request line — hub Hono logger (and any access log) records path+query. Native companions open /share#url=&text=&title=; the client reads the fragment, scrubs it, and continues with the existing ?id= picker path. Query validateSearch keeps only id/error (Web Share Target redirect). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): keep /share hash ingest across StrictMode remount Capture the fragment in useState and reuse a single putShareTransfer promise so the first effect's scrub + cleanup cancel does not lose the deep-link under React.StrictMode. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(web): fetch companion fileUrl into /share transfer files Native shares cannot put binaries in the hash fragment. Companions host a one-shot CORS URL and pass fileUrl/fileName/fileType; the share page fetches bytes into the same IndexedDB files[] as Web Share Target POST. * fix(web): cap share fileUrl fetch at the composer upload ceiling Stream fileUrl downloads with Content-Length and body size checks matching MAX_UPLOAD_BYTES so a crafted deep link cannot buffer unbounded bytes into IndexedDB. Align native deep-link docs on the fileUrl hand-off vs POST. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): cast streamed fileUrl chunks to BlobPart for tsc Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
d147ae00aa |
fix(web): rename storageUsagePie helper to storageUsageSlices to avoid macOS case-collision with StorageUsagePie.tsx (#1483)
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. |
||
|
|
24e0c76717 |
feat(cursor): bump hub thinking on ACP harness wake (#1487)
* 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> |
||
|
|
2548eaf3ed |
fix(cli): raise flaky claudeRemote first-test timeout to 15s under CI load (#1493)
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 |
||
|
|
db46b4e08f |
fix(web): hide unavailable history actions and reorder message actions (#1494)
* fix(web): reorder and hide history actions * test(web): cover locked history actions |
||
|
|
b572c35e34 |
fix(pi): surface mid-turn LLM errors in web chat (#1479)
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
|
||
|
|
f63d4bb83b | fix(web): remove trailing ellipses from search placeholders (#1449) | ||
|
|
a36df44221 | fix(web): left-align confirmation dialog descriptions (#1434) | ||
|
|
ad12eeb5d6 |
feat(a2a): P3 AGENT_NOTIFY_SUMMARY → work-graph status ingest (#1467)
* 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> |
||
|
|
495fa53465 |
fix(opencode): surface upstream errors and retries (#1433)
* 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. |
||
|
|
ffd0c752ca |
fix(web): align and remember Codex-family permission mode (#1410)
* 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 |
||
|
|
044d72fdc0 | fix(web): show file metadata in preview header (#1450) | ||
|
|
ad7f2a4084 |
fix(web): keep shared-image titles in sync with renamed sessions (#1411)
* fix(web): use renamed session title in share images * ci: rerun failed checks |
||
|
|
ac5e959045 |
fix(web): return chat file previews to conversation (#1415)
* fix(web): return chat file previews to conversation * chore: retrigger pull request checks |
||
|
|
1dd7a49f42 | fix(web): keep Pi controls available during message send (#1442) | ||
|
|
427ac1ff95 |
fix(pi): sync native session name (#1454)
* 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. |
||
|
|
2a98b4425f |
fix(agy): sync native Anti-Gravity conversation titles (#1476)
* 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. |
||
|
|
bb40a4e8d0 |
fix(agy): preserve PTY running status (#1456)
* 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). |
||
|
|
e6e229c068 | fix(codex): preserve plan approval in yolo (#1420) | ||
|
|
b744414241 |
fix(cli): skip invalid set_mode for interactive Copilot sessions (#1463)
* 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
|
||
|
|
c2cc73159c | fix(codex): preserve transcript final answer ordering | ||
|
|
190293ce9e | fix(voice): preserve compatible dictation locale (#1455) | ||
|
|
fe4d51043c |
fix(cli): remap bracketed Cursor wires onto bare/SKU catalogs (#1430)
* 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> |
||
|
|
06e2507ad8 |
fix(web): add Ctrl+X / Ctrl+S to mobile terminal Control pad (#1460)
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> |
||
|
|
b8a765d4ef |
fix(web): keep project pin inside groups below In progress (#1458)
* 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> |
||
|
|
00b6e44869 |
fix(web): keep project-pin groups above In progress (#1432)
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> |
||
|
|
df958ae349 |
fix(shared): parse AGENT_NOTIFY_SUMMARY with glued last-line prose (#1426)
* 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> |
||
|
|
cde0507bad | fix(codex): harden model discovery fallback | ||
|
|
3da9f7780a |
feat(web): support project and global pinned sessions (#1115)
* feat(web): support persistent pinned sessions * fix(web): preserve project hierarchy for pinned sessions * fix(web): preserve pins during session deduplication * fix(hub): preserve pins when merging sessions * fix(hub): migrate session pins from schema v15 * chore: retrigger PR review * chore: retrigger PR review * chore: retrigger PR review * chore: retrigger PR review * fix: align pinning with schema and sidebar filters * fix(web): expose pinning in session header menu * test(hub): expect schema v17 after pin migration * fix(web): report pin action failures * chore: retrigger PR review * chore: retrigger PR review * chore: retrigger PR review * chore: retrigger PR review * fix: address pinning review regressions * fix(web): scope pin error toast to session rows * test(hub): expect schema v20 after migration * fix(hub): preserve latest source pin during merge * test(hub): expect schema v22 after pin migration * feat(web): add project and global session pin modes Support mutually exclusive project vs global pins, surface both in the session action menu, and render globally pinned sessions in a top-level sidebar group with project path labels. * fix(web): polish pinned section icon and divider alignment * fix(web): tighten pinned section title icon alignment * fix(web): restore original pinned section pin icon shape * fix(web): rename pinned section to pinned sessions * fix(hub): prefer stronger pin mode when merging sessions Keep global pins over project pins during consolidation so a project-pinned source cannot downgrade an already or concurrently global-pinned target. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): keep inactive project-pinned groups expanded Pinned sessions should stay easy to find after reload; do not hide them behind the default inactive-group collapse. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): keep project pins in directory groups --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
e95e111a5c | Release version 0.27.2 | ||
|
|
17d1ea0c75 |
fix(web): close composer settings on outside click (#1354)
* fix(web): close composer settings on outside click Co-Authored-By: Codex <noreply@anthropic.com> * fix(web): capture composer outside clicks Co-Authored-By: Codex <noreply@anthropic.com> --------- Co-authored-by: Codex <noreply@anthropic.com> |
||
|
|
bdf8da1d5a |
fix(web): preserve drafts across inactive session resume (#1378)
* 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> |
||
|
|
b7f52f58ca |
feat(media): add audio and file display (#1405)
* 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> |
||
|
|
64a85ad923 |
fix(agy): move carrier sweep off the startup path, enable on macOS (#1406)
* 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. |
||
|
|
a13b54ccc7 |
feat(web): improve mobile terminal input (#1407)
* feat(web): improve mobile terminal input Co-Authored-By: Codex <noreply@anthropic.com> * fix(web): preserve terminal IME and key activation --------- Co-authored-by: Codex <noreply@anthropic.com> |
||
|
|
e3ed80676a | fix(codex): restore transcript messages from Codex 0.147 | ||
|
|
cfca9d210d |
fix(web): prevent near-bottom chat scroll jump (#1358)
* fix(web): prevent near-bottom chat scroll jump * fix(web): preserve explicit tail scroll intent |