* perf(app): keep composer typing responsive
The controlled web textarea pushed every keystroke through the draft store and broad composer tree before paint. Keep live text in the focused DOM input and publish draft and selection state after paint so persistence and reactive features remain intact without blocking the typed character.
* fix(app): preserve live composer state at actions
* fix(app): use live input for autocomplete clicks
* fix(app): keep submit activity atomic
* feat(agents): add active-turn steering
Let users choose whether an immediate prompt interrupts or joins the active turn. The daemon owns exact-turn admission, provider delivery, safe fallback, and canonical timeline identity while queueing remains client-side.
* fix(agents): stabilize steering history and replay QA
* fix(agents): preserve steering timeline order
* fix(agents): reconcile resumed provider history
* fix(agents): serialize steering with cancellation
* test(agents): prove Claude multi-tool steering
* test(agents): force explicit history hydration
* fix(agents): flush output before steer admission
* feat(plugins): expose Paseo API to local plugins
Plugin surfaces borrow the selected host connection while subprocess handlers receive an exclusively owned IPC session, so plugins can use normal workspace and agent operations without inventing another transport.
* feat(plugins): reload global enablement safely
Treat the global plugin switch as a live daemon setting and teach plugin automation to inspect it before acting. Enabling remains permission-gated because plugin backend code can access the daemon machine and client contributions execute inside the app.
* feat(plugins): expose plugin logs
Capture plugin stdout and stderr separately from IPC so authors can debug initialization, RPC, cleanup, and crash behavior from the app or CLI. Keep a bounded tail across lifecycle recovery while preserving structured output in the daemon log.
* fix(build): order plugin dependency builds
* test(plugins): align startup and CLI expectations
* perf(app): reduce warm workspace switch work
Retained workspaces avoided mounts but pane activity still rerendered the full composer, message input, and agent-control presentation. Keep the activity contract intact while moving focus-sensitive behavior behind small context consumers, and add a Playwright benchmark that measures rapid retained workspace switching on one browser clock.
* perf(app): accelerate retained workspace switching
Avoid repeated retained-stream layout reconciliation and unrelated workspace structure recomputation. Use the lightweight route transition while the host is already focused, while preserving targeted root POP_TO for returns from app-wide routes where it prevents duplicate hidden decks.
* perf(app): defer composer focus during workspace switches
* perf(app): retain recently used workspaces longer
Keep up to ten workspace screens warm so normal multi-workspace navigation does not repeatedly rebuild recently used timelines. Inactive screens expire after ten minutes, while the active screen remains protected and capacity eviction stays least-recently-used.
* fix(app): reconcile retained timeline geometry
* fix(app): refresh retained reading position
* feat(daemon): reload configuration without restarting
Keep config validation, reloadability classification, override handling, and atomic runtime application inside the daemon so local and remote clients share one authoritative workflow.
* fix(daemon): preserve reload resource ownership
Keep every materialized provider client under daemon-lifetime ownership and classify speech overrides from resolved providers. Restore the complete WebSocket config-store test contract exposed by CI.
* feat(app): archive finished subagents together
Make the track action clean up finished managed and provider-owned children as one operation while preserving running work. Managed archives remain sequential and provider-owned dismissal stays client-local.
* test(app): clarify subagent archive assertions
* feat(plugins): add local plugin surfaces and RPCs
Keep plugin execution scoped to its daemon while allowing one local TypeScript entry point to contribute validated RPCs and native client surfaces. Paseo owns host selection, navigation chrome, and per-installation query caches.
* feat(plugins): add Linear attachment source
* fix(nix): refresh npm dependency hash
* test(server): exclude internal sessions from Hub counts
* test(server): distinguish Hub execution sessions
* feat(plugins): make local plugins explicitly opt-in
Keep plugin loading dormant until configuration enables it, and collect unsupported examples under one directory.
* refactor(plugins): establish host boundaries
* fix(nix): refresh plugin dependency hash
* fix(plugins): complete packaging and startup rollback
* feat(plugins): add managed local lifecycle
Make trusted local plugins recoverable and authorable through daemon-owned lifecycle operations, host settings, and a capability-gated CLI. Reloads fully stop the previous plugin before starting new code, while generated projects provide strict TSX checking without changing runtime dependency ownership.
* fix(nix): refresh plugin dependency hash
* perf(timeline): resume cached history from its exact range
Warm launches were repainting cached history and then downloading the same bounded tail to rediscover that nothing changed. Persist synchronization authority only when the complete current canonical window can be encoded losslessly; otherwise retain the existing display-only fallback.
* test(timeline): hide replica setup behind E2E helper
* fix(timeline): bound persisted resume overflow
* test(timeline): cover workspace eviction catch-up order
* test(timeline): read persisted range from IndexedDB
* feat(sync): restore host directories before reconnect
Persist complete project, workspace, and active-agent replicas so every registered host can paint immediately, including offline hosts. Reconcile those replicas through monotonic per-entity cursors on the existing directory RPCs without retaining an event journal.
* fix(sync): preserve active turn identity during catch-up
Own Agent-to-wire projection beside the existing snapshot normalizer so replica persistence does not reach through directory-sync internals. This also keeps identified active turns intact when unchanged agents are folded into an incremental directory response.
* test(e2e): inspect the real IndexedDB replica
Keep cache setup, reload, and hydration coverage on the production browser storage backend after the replica moved off localStorage. Measure writes at the actual IndexedDB put boundary as well.
* fix warm reconnect cache paths
* feat(sync): persist project icons and deepen directory sync
* fix(sync): preserve project update order
* fix(cache): preserve validated replica persistence
* fix(sync): retain cached agents during catch-up
Provider catalog discovery can exceed one minute during cold startup. Increase the default deadline while preserving explicit config and environment overrides.
* fix(server): trust healthy workspace git watchers
Quiet observed workspaces were periodically scheduling full Git refreshes, saturating the daemon-wide process pool as workspace count grew. Keep only cheap observation re-ensure work, refresh ignore paths from watcher events, and detect silent metadata watchers with a one-shot canary before accepting the subscription.
* fix(server): refresh watcher ignores from git metadata
Repository exclude and Git configuration changes also affect the standard ignore set. Route those metadata events back to linked working-tree observations so their excluded directory lists stay current without periodic Git polling.
* test(server): drive ignore updates through watcher events
The integration contract now exercises the production event-driven ignore refresh instead of waiting for the removed periodic audit.
* fix(server): normalize git metadata paths on windows
File observation returns platform-native relative separators. Normalize those paths before matching Git metadata names so repository exclusion changes refresh working-tree ignores on Windows too.
* fix(server): trail concurrent ignore refreshes
Remember ignore-source events that arrive during an in-flight Git query and recompute once more before considering the working-tree observer current.
* fix(providers): make catalog refresh deadlines configurable
Provider startup can legitimately exceed a fixed short timeout when catalog discovery has substantial initialization work. Give each provider refresh one configurable deadline covering availability and the complete catalog probe, and cancel named child operations before allowing retries so timed-out work cannot accumulate in the background.
* fix(opencode): abort catalog server acquisition
A provider refresh could expire while the helper server was still starting because acquisition only tracked the activity without observing its abort signal. Reject acquisition before transferring a server reference, while leaving the manager-owned startup reusable by the next caller.
* test(file-observer): allow native burst recovery
* fix(hub): make daemon disconnect unilateral
Local relationship removal must not depend on reaching the Hub. Fence execution authority, make one bounded courtesy revocation attempt, then clear local state so status and reconnect eligibility agree immediately.
* fix(hub): preserve disconnect intent across crashes
Persist the terminal disconnect marker only while the bounded Hub notification is in flight. Startup removes that marker, so a crash cannot restore the old relationship or create a revocation retry loop.
* fix(worktrees): preserve selected base config
New worktrees were overwriting the selected ref's paseo.json with source-checkout bytes, which could create an accidental revert in an otherwise fresh workspace. Keep committed base config authoritative and warn when locally saved setup changes still need to be committed.
* fix(worktrees): seed config exclusively
* fix(projects): refresh config status on focus
* fix(subagents): preserve opened children on parent archive
Opening a managed child represents user ownership only while that client's tab remains open. Track that intent per client, detach surviving children during parent archive, and keep single and bulk tab-close behavior consistent.
* fix(subagents): mark generic tab openings
Centralize managed child tab ownership in agent navigation so list, command-center, deep-link, notification, and subagent-track openings all mark ownership before creating the tab. Use stable localized errors for lifecycle failures.
* fix(subagents): classify cold tab openings
Fetch uncached agents before deciding whether tab ownership must be recorded, so notification, desktop, and cold-route openings cannot bypass parent archive protection.
* fix(app): translate navigation fallback errors
* fix(navigation): allow offline workspace intents
* refactor(app): derive subagent ownership from open tabs
* fix(server): serialize subagent lifecycle ownership
Workspace terminal shutdown only signaled an npm wrapper, leaving Electron alive with broken output pipes. Make the signal-handling runner the terminal-owned process and route external Electron termination through the existing quit lifecycle.
* fix(worktrees): honor saved setup configuration
Project Settings writes paseo.json in the source checkout, but existing committed copies in new worktrees previously won silently. Copy the saved project config before lifecycle setup so the command shown to users is the command that runs.
* fix(worktrees): replace config symlinks safely
A selected ref can place a symlink at paseo.json, so copying over that path may overwrite a file outside the worktree. Materialize the saved source config with atomic rename semantics to replace the path entry without following it.
* feat(cli): add workspace rename command
`paseo workspace rename <workspace-id> <title>` sets a workspace's
user-visible title through the existing workspace.title.set RPC, and
`--reset` clears the override so the name reverts to the branch or
directory. The command reads the descriptor back so the output shows
what a reset reverted to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(cli): harden workspace rename against read-back and arg failures
Review findings on the rename command:
- A failed workspace list sweep after a successful title change reported
the whole command as failed. The read-back is display-only, so it now
runs outside the mutation's error path and degrades to a null name.
- Only --reset needs the descriptor read back; a set title is already the
resolved display name, so the common path no longer pages the list.
- Dropped the `"code" in error` rethrow. Nothing in that block throws a
CommandError, but DaemonRpcError always carries a `code` property even
when it is undefined, so the guard let those through to render as
UNKNOWN_ERROR with a stack instead of WORKSPACE_RENAME_FAILED.
- Commander 12 allows excess positionals, so an unquoted multi-word title
renamed to its first word and dropped the rest. The command now rejects
excess arguments.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* refactor(cli): report the rename RPC response instead of reading back
Reading the descriptor back to show what --reset reverted to was the only
reason rename needed a paged workspace sweep, and that sweep dragged in
everything around it: a failure path that could report an applied rename
as failed, a nullable name, and a bespoke warning sink to tell the two
null cases apart.
Sibling commands don't do this. `workspace archive`, `agent mode`, and
`schedule update` all report what the RPC returned and stop; the one
command that reads back is `agent update`, which has the same "applied
but reported as failed" bug. So rename now mirrors `workspace archive`.
`--reset` reports `title: null` rather than naming what it reverted to;
`paseo workspace ls` shows the resolved name. This also reverts the
`collectWorkspaces` extraction, whose only second caller was rename.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Search models across all providers from the picker root
* Store agent profiles in daemon config and apply agent config in one RPC
An agent profile is a named bundle of provider, model, mode, thinking
option, feature values and notes, stored host-wide next to terminal
profiles. It carries no system prompt: AgentSessionConfig.systemPrompt is
creation-only, so a profile holding one would apply when starting an agent
and silently do nothing when applied to a running one.
Applying a profile through the four single-field RPCs can fail halfway and
leave an agent matching no profile, so agent.config.apply takes the whole
bundle. Model runs first because a thinking option only means anything
against the model that offers it.
Old daemons parse their persisted config strictly and would drop an
unknown agentProfiles key, making a save appear to succeed and vanish, so
both capabilities are advertised on server_info for the client to gate on.
list_profiles exposes the profiles to orchestrating agents, which read the
notes to pick one and copy its values into create_agent.
* Add the agent profiles settings section
Profiles live behind one module boundary so removing the feature is a
directory plus one mount, which is what favourites never was. The picker
and composer will ask for rows and an apply function rather than learning
what a profile contains.
The edit form follows the form-model pattern instead of the terminal
profile modal's per-field state, because provider selection cascades into
which models, modes, thinking levels and features exist below it.
The section is gated on server_info.features.agentProfiles: an older daemon
drops the key on write, and a save that appears to work and then vanishes
is worse than an unavailable state.
* Document agent profiles and name the four senses of profile
The glossary reserved Profile for HostProfile, terminal profiles owned the
user-facing word, and custom-providers.md used it for provider aliases.
Agent profile is now the term, with all four cross-referenced.
data-model.md never documented terminalProfiles either, so both profile
lists are backfilled together along with the whole-list patch semantics and
why absent and empty differ.
* Pin agent profiles to the model picker and delete favourites
Applying a profile writes its values into the agent controls and is then
forgotten, so rows carry no checkmark and no selected state: after applying,
nothing is selected. Chevron still means navigate deeper, so the absence of
one is what marks a row as an action.
A running agent is its provider's process and agent.config.apply carries no
provider, so the live picker only offers profiles matching the agent's
provider rather than showing rows that cannot do what they say. The draft
path has no such limit and switches provider freely.
Draft feature values go through preferences rather than the feature
callback alone, because the draft feature hook clears local values whenever
the provider changes and a provider-switching profile would lose them.
Favourites are deleted rather than migrated: they were device-local
AsyncStorage keyed by provider and model, while profiles are host-wide
daemon config. ProviderSelectionModelRow.favoriteKey stays as row identity
and moves to its real owner; renaming it is its own commit.
* Cover agent profiles and cross-provider search with E2E specs
Model rows expressed selection only as a checkmark glyph, which assistive
tech cannot see and a spec cannot assert. Rows that draw the indicator now
carry aria-selected; profile rows deliberately carry none, because a
profile is an action rather than a selection. That is what makes "nothing
is marked selected after applying a profile" a real assertion instead of a
tautology, with the materialized model one level down as the control.
The search fixture extends claude rather than mock: provider-config.ts
only allows the six shipped providers as extends targets, so a derived
provider cannot inherit the dev-only mock provider.
* State what applying a config bundle does on partial failure
applyBundle is not a transaction and the earlier commit message implied it
was. A step that rejects aborts the rest and the steps that already applied
stay applied, which a profile omitting modelId can reach: provider setters
validate against the agent's live model, so mode can land while a feature
that model does not support is rejected.
Nothing is hidden by that. Every setter emits agent state before returning,
so what applied is streamed to the client next to the rejected response.
Rolling back would re-invoke the setters that just failed, usually against
a session that is gone, and pre-flight validation would restate each
provider's model-dependent checks where they would drift from the setters
that own them.
What sending the bundle in one request removes is the multi-round-trip
failure class: interruption between hops, and interleaving with another
client. The test already pinned this behaviour; its name now says so.
* feat(agent-profiles): refine profile discovery and appearance
Make profiles discoverable from empty and provider-scoped model pickers, keep global search stable and virtualized, and move profile settings under Agents. Replace emoji identity with a shared Lucide icon and host-color picker, while requiring concrete provider selections so profiles materialize predictably.
* fix(agent-profiles): defer profile swatch styles
Resolving Unistyles proxies at module load can freeze the temporary startup color scheme before the persisted theme is restored. Keep only style-key names at module scope and resolve the selected swatch during render.
Fix spelling and grammar errors found in a full sweep of the
repo's English markdown files.
- CONTRIBUTING.md: "contributing to to it", "accpeted", "wil"
- .github/PULL_REQUEST_TEMPLATE.md: "value provider to" instead
of "value provided to"
- docs/product.md: "vendor-lock in" instead of "vendor lock-in"
- packages/expo-two-way-audio/CONTRIBUTING.md: "prescreptive"
- packages/expo-two-way-audio/README.md: "can bee added"
- skills/paseo-committee/SKILL.md: "considered and reject"
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Development web and Electron builds now show their source branch in the expanded sidebar, including a branch icon and safe truncation. Dev launchers inject the label at build time; production builds remain unchanged.
* fix(agents): preserve permission notification callbacks
Permission requests consumed the one-shot completion watcher, so callers never heard about later approvals or the final result. Keep the watcher alive across permission checkpoints and include the actionable request payload in each notification.
* fix(agents): notify concurrent permission requests
* refactor(storage): remove chat rooms and agent loops
This is the prerequisite cleanup for the storage backend migration. Legacy wire schemas remain temporarily so mixed-version peers can still parse old messages, while daemon callers now use query-shaped AgentStorage methods that can map directly to SQL later.
* test(cli): remove retired chat and loop coverage
Keep the animated ring phase-locked across hosts and derive its knockout surface from each host's actual interaction state. Align filled status circles at 6px while preserving the larger needs-input alert.
Co-authored-by: Christoph Leiter <christoph@leiter.io>
* feat(app): add AMOLED and all four Catppuccin themes
Add an AMOLED theme (pure black background) and the four official
Catppuccin flavors (Latte light, Frappé/Macchiato/Mocha dark) to the
appearance picker. Generalize the dark-theme builder to support a light
surface variant, register the new themes with Unistyles, and patch all
registered theme keys when applying font/syntax appearance settings.
* fix(app): localize Catppuccin + AMOLED theme labels across language packs
The initial theme commit pasted the English brand/flavor labels into every
locale. Localize them following each pack's convention: transliterate
Catppuccin and the coffee-flavor names into Arabic/Russian scripts, use
katakana/hanzi equivalents in ja/zh-CN, and standard loanwords in
fr/es/pt-BR. AMOLED is a universal acronym and stays as-is except where
the pack transliterates (Arabic).
* fix(app): make Latte terminal ANSI colors readable
* test(app): cover appearance theme picker options
* refactor(app): scope theme PR to AMOLED
* refactor(app): rename AMOLED theme to Pure Black
* fix(app): refine Pure black appearance
* test(app): wait for sidebar toggle icon
* test(app): remove hover timing race
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* fix(server): archive Hub executions by workspace identity
Hub archive previously depended on backing-specific owner state and archived the agent directly, so local and reused workspaces could survive. Resolve the required workspaceId from the execution agent and let the shared workspace archive service own agent, terminal, and backing teardown.
* fix(server): preserve agent archive through workspace teardown
* test(server): keep Hub regression fixtures workspace-scoped
* fix(hub): own executions through fresh workspaces
Hub executions must own a workspace regardless of backing so archival has one lifecycle boundary for agents, terminals, and checkout cleanup. Retain the retired wire selector for parsing compatibility while removing its ownership semantics.
* fix(server): serialize archive metadata writes
* fix(server): preserve archive during reconciliation
* feat(providers): add native options and exact MCP grants
Keep provider policy in provider-owned schemas so unattended Hub runs can grant only requested tools from injected MCP servers. Preserve the native resolved sandbox policy when a workflow override is present, including configured writable roots.
* test(hub): verify real provider tool policy
* test(app): avoid pinning transient restart status
* feat(cli): add durable Hub login
Keep organization-scoped human credentials separate from daemon relationship authority. Connect now exchanges explicit or stored CLI authority for a one-time enrollment token, while logout removes human access without implicitly disconnecting automation.
* fix(cli): require TLS for remote Hub credentials
Durable Hub credentials are bearer secrets, so remote origins must not receive them over cleartext HTTP. Keep literal loopback HTTP available for local Hub development.
* fix(cli): make Hub destinations explicit
Resolve optional Hub origins through one shared precedence policy so active logins remain the default context and credentials stay origin-bound. Report the selected destination before human-facing work, keep structured output auditable, and preserve login authority until requested daemon disconnection succeeds.
Make the release document the single source of truth. ACP drift is explicit opt-in, release preparation uses one remote batch, and a heartbeat owns completion through published artifacts and store submissions. Preserve one changelog entry per beta and collapse the series on promotion.
* fix(terminal): clear activity when turns are interrupted
Some terminal agents do not emit a stop lifecycle hook after a user interrupt. Treat exact interrupt key frames as cancellation at the terminal input boundary so running state cannot stick, and clear rather than finish to avoid completion attention.
* test(terminal): use platform shell in interruption tests
Let the terminal test fixture select the platform default shell so the interruption cases reach their assertions on Windows.
* feat(server): show provider workflows in subagent track
Normalize workflow tasks into the provider-subagent contract so shared clients need no workflow awareness. Persisted nonterminal runs replay as failed because their original runtime can no longer deliver a terminal update.
* fix(server): preserve workflow replay and tool cards
* fix(server): suppress workflow notification cards
* fix(server): replay workflow child timelines
* fix(server): fail tasks when Claude exits
* fix(server): preserve workflow replay parity
* fix(server): fail tasks on Claude restart
* fix(server): hide workflow child prompts on replay
* fix: terminalize workflows on session reload
* fix(server): own workflow notification routing
* feat(server): show workflow results in detail pane
* fix(server): preserve unrestored task notifications
* fix(server): normalize replay task notifications
* fix(server): report Claude runtime death instead of sitting idle
When the Claude Code CLI process dies on its own — crash, OOM kill, host
suspend — nothing in the daemon observes it unless a turn happens to be in
flight. The query pump only reports a failure while it is draining a turn, so
an idle agent's stream just ends: the agent stays `idle`, indistinguishable
from a healthy resumable one.
Everything the agent parked inside that process dies with it. Background Bash
shells, `Monitor` watches and workflows all live in the CLI process, so the
completion notification that would have woken the agent back up never arrives.
The work is silently gone and the user finds out much later, if at all.
The stale handles are never dropped either, so writes keep going to a dead
transport for as long as the daemon lives (#2472).
Watch the spawned child at the single seam it is already handed over
(`onChildProcess`). When it exits between turns, drop the dead query/input
handles so the next write spawns a fresh process, and emit `turn_failed` —
the manager already routes a non-foreground `turn_failed` to `error` plus a
timeline entry.
A death mid-turn is left alone. The pump already waits for stderr to flush and
reports the real cause; reporting here first would replace that with a bare
exit code.
Intentional teardown stays quiet. All three sites that retire a process detach
the child handle first: `close()` sets `closed`, and `ensureQuery()` and
`handleMissingResumedConversation()` null `childProcess` before ending stdin.
Fixes#2472
* fix(server): tree-kill the retired process on missing-resume recovery
The missing-resumed-conversation path ends the child's stdin but never
tree-killed it, so MCP children of the retired claude process could outlive
it as orphans. Every other teardown path already does this; do it here too.
Reported by Greptile on #2910.
* fix(desktop): preserve browser tabs across focus changes
Electron can replace a guest WebContents when a webview is reparented, which reloads the page and can race automation against a destroyed target. Keep each browser guest on one permanent paintable surface and make user and agent resizing share one canonical viewport.
* fix(desktop): recover browser surfaces after lifecycle changes
* fix(desktop): preserve resident browser lifecycle state
* fix(desktop): seed inactive browser viewport
* Release the audio session when voice capture and playback go idle
Opening Paseo permanently paused music playing in other apps. Once the
mic had been used a single time, every subsequent foreground of the app
killed the user's audio again until the app was force-quit.
iOS AudioEngine.setupAudioSession() takes .playAndRecord/.voiceChat — a
non-mixing category — and calls setActive(true), but the only
setActive(false) was reachable from tearDown(). Nothing handed the
session back after a normal dictation turn, so iOS re-asserted the last
category the app set on every foreground. The iOS module had no
app-lifecycle handling at all, while Android already had a lifecycle
listener; that asymmetry is what made the platforms diverge. Android had
the same leak in a different shape: AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
was requested per recording and never abandoned.
Add releaseAudioSession() to both platforms and call it once capture
stops and the playback queue drains. iOS pauses the engine, calls
setActive(false, .notifyOthersOnDeactivation) — this is what tells the
other app to resume — then drops to .ambient/.mixWithOthers so any
implicit reactivation mixes instead of interrupting. The session is
re-acquired lazily on toggleRecording(true)/playPCMData, so voice keeps
working; resumeRecordingAndPlayer() re-activates before restarting the
engine, which keeps post-interruption resume intact. Releasing is
guarded on isRecording/isPlaying, and checkEngineIsRunning() now refuses
to start the engine while the session is released, since starting it
would silently re-take the session.
The JS wrapper probes the native module before calling, because a JS
bundle shipped over EAS Update can land on a binary predating this
function.
* Correct the iOS signing diagnosis in mobile-testing docs
The "No Accounts" error does not mean no Apple ID is configured. The
account can be registered in Xcode while its Xcode-Token keychain item is
missing, which Xcode reports as No Accounts and then compounds by falling
back to a stale cached profile — surfacing as a certificate mismatch that
sends you chasing the wrong thing.
Document the DVTDeveloperAccountManager log line that names the real
cause, the two commands that check what is actually configured, and the
build-unsigned-then-sign-manually stopgap.
* Reacquire Android audio focus when playback restarts
releaseAudioSession() abandons the AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
request and pauses the AudioTrack once capture and playback go idle, but
nothing took either back. The next playback turn therefore ran with no
focus held, so the assistant's response competed with whatever the user
was listening to instead of interrupting it.
Add acquireAudioSessionIfNeeded(), the mirror of the iOS
activateAudioSessionIfNeeded() already called from playPCMData, and use
it from both playPCMData() and resumePlayback(). A null audioFocusRequest
is what "we released" looks like, so it doubles as the guard against
re-requesting focus mid-turn.
Reported by Greptile on #2866.
* Make Android playback focus reacquisition non-fatal
The previous commit dropped PCM data when focus reacquisition did not
return AUDIOFOCUS_REQUEST_GRANTED. That is worse than playing unfocused:
playPCMData is fire-and-forget from JS, and playAudio() settles on a
duration timer rather than a native ack, so the runtime acknowledged and
discarded a chunk that was never queued.
It also broke delayed focus. The request is built with
setAcceptsDelayedFocusGain(true), so AUDIOFOCUS_REQUEST_DELAYED is a
normal outcome, but routing it through handleAudioFocusBlocked() abandoned
the pending request and the later grant could never arrive.
Request focus, then play regardless of the result, and leave the request
outstanding so a delayed grant still lands via the listener.
Reported by Greptile on #2866.
* Track scheduled buffers so iOS actually releases after playback
releaseAudioSession() guarded on !speechPlayer.isPlaying, but
AVAudioPlayerNode.isPlaying stays true from play() until an explicit
stop()/pause() — draining every scheduled buffer does not clear it. The
normal completion path is processQueue() -> releaseSessionIfIdle(), and
stopPlayback() is only reached from the explicit stop() API, so after the
first spoken response the guard blocked every release and the session
stayed active. The music-pausing bug returned in full for anyone using
voice responses.
Count buffers that are scheduled but not yet rendered, decrementing from
the .dataPlayedBack completion handler, and guard on that instead. The
handler runs on an AVAudioEngine-internal thread, so the count is behind a
lock. Both stop() paths reset it, since stop() discards what is scheduled
and a stale count would block every later release.
Found by Codex review on #2866.
* Stop re-taking Android audio focus on every foreground
ExpoTwoWayAudioLifeCycleListener calls resumeRecordingAndPlayer() from
onResume, and that requested AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE before
looking at isRecordingBeforePause. Foregrounding the app while nothing was
recording therefore took exclusive focus back and re-paused the user's
music — the same "every foreground kills my music" symptom this change set
exists to remove, just reached through the Android lifecycle instead of the
iOS category.
Return early unless capture or playback was actually live when we
backgrounded.
Found by Codex review on #2866.
* Add Korean UI localization
Refreshes the Korean translation from #1544 against the current client locale surface and adds cross-platform locale detection coverage.\n\nCo-authored-by: JinHyeok Jeong <150323322+Kesta-bos@users.noreply.github.com>
* fix(i18n): preserve reviewed Korean status labels
Restore the wording corrections contributed in the original Korean localization work and lock them with exact-copy assertions.
Co-authored-by: JinHyeok Jeong <150323322+Kesta-bos@users.noreply.github.com>
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Co-authored-by: JinHyeok Jeong <150323322+Kesta-bos@users.noreply.github.com>
The sidebar row narrowed to services only and its display-preferences item is
now labelled Services, while the panel and menu that list both types are still
Scripts. That is two words in the UI for things a reader will assume are one,
which is exactly what this file exists to settle.
Three systems were reporting the same states: a full and a muted status
tier in the theme, plus raw palette steps in StatusBadge and
getStatusDotColor. A PR icon was muted in the sidebar and full-strength
in the PR panel, and the Resolved badge used a theme-independent green
so it painted a dark block in light mode. The muted tier is deleted and
every status surface now reads the one token per signal.
Dots keep a band of their own. A dot is a few points of solid colour
with no shape to read and no label attached, and the running one pulses,
so at the status band's lightness it sat below the check icons and host
badge on its own row — backwards, since the dot is the row's state.
Chroma caps at 90% of gamut rather than 100% because at the edge green
reaches C=0.215 while blue manages 0.116 and the set stops reading as
one family; dark lightness caps at 0.72 because red desaturates to pink
above it.
Host badges needed a foreground variant of the identity table. That
table is tuned for a white letter on the colour as a fill; used as text
it cannot clear 4.5:1 on both themes at once, since light needs
luminance <= 0.183 and dark needs >= 0.212. The new set is generated on
the status family's exact lightness and chroma fraction, so a host name
and a CI check in the same meta row carry equal weight instead of the
brighter one winning the row.
Also: PR avatars had their own eight-hex palette and hash duplicating
identity-colors; workspace rows under a project group now indent to the
same rail as status-grouped rows; group headers are muted; and the
pulsing dot is faster and fades further.
Beta testers had to find prerelease assets on GitHub by hand. The
download page now carries a Stable/Beta switch, with stable staying the
default and the canonical URL.
The channel lives in the URL (?channel=beta) rather than client state so
maintainers can link someone straight to it. That link outlives the beta
it was shared for, so the switch only renders while the newest
prerelease leads stable on its core version, and a stale ?channel=beta
falls back to stable instead of serving a promoted version twice.
"What's new" deep-links the minor group anchor (/changelog#release-0.3),
not the exact entry: promotion rewrites the -beta.N entry in place, so
that anchor dies while the group survives — and the group is the
stable-to-beta diff a beta downloader came to read. A version missing
from the bundled changelog links plain /changelog rather than a dead
anchor, which covers the window between a tag going public and the site
redeploying.
Homebrew, the app stores, and the web app have no beta, so the Beta view
drops those rows instead of showing inert placeholders.
Beta tags now reach external testers without per-release dashboard work. Reserve distinct native build slots so successive betas and the promoted stable build can coexist under one app version.
Make provider catalogs compact and content-addressed so repeat launches can reuse the persisted snapshot instead of transferring it again. Remove eager global catalog loading and the stopped-agent defensive tail fetch, while keeping sequence-gap and reconnect recovery as the correctness paths. Add an opt-in production-ID Android profiling build for local startup traces.
Selecting an item parked its action in a ref and waited for the Modal's
onDismiss to flush it. MenuOverlay returns null as soon as the menu closes, so
the Modal unmounted and onDismiss never fired: every close-on-select action was
dropped. Creating a terminal from the compact workspace menu did nothing at all.
Wait on a timer instead. The grace period was always about giving UIKit time to
tear the surface down before a native presenter launches, and a callback fired
from inside a subtree being removed is racing its own removal. The sheet had no
flush at all, so it was broken the same way for a different reason.