Auto-generated workspace names walked the marine-creature list in order
and deduped only within the active repo (the default when workspaces are
nested), so the first auto-named worktree in every repo landed on
"Nautilus". That guaranteed identical branch names across repos, which
appear flat and indistinguishable in the sidebar.
The in-order walk also produced "Nautilus-2", "Nautilus-3", ... within a
single repo: the suggester ignores branches left behind by deleted
worktrees, so it kept re-proposing "Nautilus", and the create-time retry
loop suffixed it to dodge the lingering branch. Random selection now
yields a fresh creature each time instead of marching the same name.
- Dedup against worktrees in every repo, not just the active one
- Pick randomly from the unused pool instead of the first list entry
- Lowercase the result to match branch-name convention (fix/seahorse)
- Expand the corpus 260 -> 552 (more marine species plus public-domain
mythological sea creatures) so random picks rarely repeat
getSuggestedCreatureName drops the now-unused repoId/nestWorkspaces
params; the RNG is injectable for deterministic tests.
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix(sidebar): omit branch line when it duplicates the workspace name
Un-renamed worktrees default their display name to the branch, so the
gray branch sub-line repeated the title verbatim. Hide it when the two
match, and add a native title tooltip to the title so a clipped name is
still recoverable now that the branch line no longer serves as the
untruncated copy.
* refactor(sidebar): collapse worktree card to one line when it has no second-line content
The unread bell used to stack vertically under the status dot, forcing a
height floor that left a blank band beneath single-line titles. Move the
bell inline into the title row's right cluster (leading the PR/ports
indicators) so the left column is just the dot and the card height tracks
its content. Gate the branch/badge sub-line so it only renders when it
carries something, and lift PR/review/ports indicators onto the title row
so they don't strand on their own line once the branch is hidden. Replace
the spelled-out "primary" badge with a compact amber star.
* refactor(sidebar): make CacheTimer presentational; fix card test mocks
Pass the resolved cache start time and TTL into CacheTimer as props so the
card and the timer no longer double-subscribe to the same store slices —
the card already derives the start time to gate its metadata row. Collapse
the now single-child status-dot wrapper. Add the new
usePromptCacheCountdownStartedAt export to the ./CacheTimer mocks the card
tests rely on.
* fix(sidebar): keep compact card title recovery accessible
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* Add agent enablement controls
Implements the Enable/Disable Agents Dashboard behavior described in docs/enable-disable-agents-dashboard.md, including persisted agent enablement state, filtered launch surfaces, and settings UI affordances.
* Clarify agent availability controls
* Respect disabled agents across workspace and AI defaults
- Filter disabled TUI agents from mobile and quick workspace selection
- Avoid implicitly choosing disabled agents for commit/PR AI settings
- Broadcast settings changes to open windows for disabled-agent updates
* rm design doc
* fix: complete agent enablement propagation
* release: v0.0.1-rc.0 [rc-slot:2026-05-26-15]
* fix(sidebar): pin the correct repo header when scrolling between groups
The sticky-header swap was gated on scrollOffset >= header.start + 8px (the
inter-group spacer), so once the next repo's header row reached the top the
previous repo's opaque header stayed pinned over it — the wrong repo appeared
sticky while the next repo's worktrees scrolled underneath. Hand off the moment
the header row reaches the top; the pinned header still drops its spacer to sit
flush, so there is no position shift on swap.
Update the scroll-adjustment tests to assert activation at the row top and add
a regression case at scrollOffset === start.
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: sync worktree card title on terminal branch switch
When a worktree has no custom name, mergeWorktree derives its displayName
from the branch. A branch switch made in a terminal reaches the renderer
only via the git-status poll, which patched branch/head but never the
derived title — leaving the card title frozen on the old branch name while
the subtitle correctly updated.
updateWorktreeGitIdentity now re-derives displayName from the new branch
when the title was auto-derived (matches the old branch), and leaves
user-set custom names untouched.
* fix: reuse branch display name helper
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix: stop worktree jumping to top of sidebar on re-click
Returning to a worktree whose terminals had slept or disconnected
bounced it to the top of its repo group, as if there were activity.
The sidebar sorts by lastActivityAt, and click-driven activity is meant
to be suppressed via the pendingActivationSpawn tag. But setActiveWorktree
only applied that tag on a worktree's first activation. When the user
returned to a worktree with no live PTYs, the allDead branch bumped the
PTY generation, remounting the pane and fresh-spawning a PTY. That spawn
was untagged, so updateTabPtyId treated it as real activity: it stamped
lastActivityAt and bumped sortEpoch, floating the worktree to the top.
Tag the tabs whenever the allDead generation bump fires, not only on
first activation -- the respawn it triggers is always a click side-effect.
The flag is consumed on the next updateTabPtyId, so genuine later activity
still updates recency. This matches the intent already documented in the
terminals slice.
* fix activation spawn recency suppression
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
sendRequest handled the socket's 'error', 'data', and 'connect' events but
never 'close'. A clean peer close (FIN, no error) before a terminal frame —
e.g. the runtime crashing or closing the connection mid-request — left the
promise unsettled until the full timeout fired: 60s by default, up to 10min
once keepalive frames had refreshed the client-side timer.
Add a socket.once('close', ...) that rejects with runtime_unavailable when
the socket closes before a terminal frame settles the request. finish()
already guards against double-settle, so this no-ops on the normal
success/error paths that call socket.end().
* implement h1 and text toggle
* Review:
- Details parsing is now depth-aware, so nested <details> blocks are not partially consumed.
- Existing details blocks with raw HTML inside the summary/body now fall back to Orca’s raw HTML passthrough instead of
becoming editable toggle nodes and escaping tags.
- Simple supported details blocks still become editable rich toggle nodes.
- Added regression tests for raw HTML inside details and nested details.
* placeholder and fix enter behaviour
* review findings
* Review: align toggle commands with grouped slash menu
Co-authored-by: Orca <help@stably.ai>
* Review: finish toggle command rebase fixes
Co-authored-by: Orca <help@stably.ai>
* Review: preserve unsupported details HTML
Co-authored-by: Orca <help@stably.ai>
* Review: delete toggle body back into summary
Co-authored-by: Orca <help@stably.ai>
* Review: make empty toggle backspace two-step
Co-authored-by: Orca <help@stably.ai>
* Review: make empty toggle enter exit
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix: bound the eager PTY buffer and stop quadratic trimming
The eager reconnection buffer trimmed with Array.shift() (O(n) per chunk,
quadratic under many small pre-attach chunks) and let a single chunk
larger than the 512 KB cap bypass trimming entirely. Replace shift with a
head index (amortized O(1), occasional compaction) and clamp an oversized
chunk to its most-recent tail so the cap always holds.
Closes#2931
* fix: enforce eager PTY buffer byte cap
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* fix: prevent relay fs.rename from clobbering an existing destination
The remote file-explorer rename path (provider.rename -> relay
fs.rename) called fs.rename unconditionally, silently overwriting any
file/folder already at the destination. The local rename already
guards via assertFileExplorerRenameDestinationAvailable; apply the
same guard on the relay to restore local/remote parity (case-only
renames on case-insensitive filesystems still allowed).
Moved the collision helper from src/main to src/shared so both the
main process and the remotely-deployed relay share one implementation.
Closes#2926
* review: make SSH safe rename explicit
- keep relay fs.rename raw and add fs.renameNoClobber for user-facing renames
- route SSH file-explorer rename paths through renameNoClobber
- add relay/provider/runtime regression coverage and stale-relay fail-closed handling
- verified live SSH rename behavior on openclaw 2
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>