* 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>
* fix: reject short SSH stream chunks instead of zero-filling reads
The stream reader validated chunk COUNT but not chunk byte length, so a
short chunk (e.g. a 1-byte final chunk for a 2-chunk file) passed the
count check and resolved with the pre-allocated buffer's tail left
zero-filled — silent corruption of remote file reads. Validate each
chunk's exact length, and add a last-line bytesReceived === totalSize
invariant guard before resolving.
Closes#2930
* review: harden SSH stream byte validation
- fill relay protocol chunks across short fs.read returns before emitting streamChunk
- cover short final and non-final client chunks
- add relay pump coverage for short reads before EOF
- verified lint, typecheck, targeted stream tests, relay round-trip, and Electron boot smoke
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
- Detect existing non-archived workspaces linked to GitHub PRs and open them instead of starting duplicates.
- Show the attached workspace label in PR lists and detail pages, with a menu action to start a new workspace when needed.
* fix: reject file writes with missing content
files.write / files.writeBase64 coerced a missing or non-string
`content` to '' and wrote it, silently truncating the target file.
Require a real string instead; an explicit '' (empty file) is still
allowed. Applies to writeBase64Chunk via its base schema.
Closes#2925
* review: add files.writeBase64Chunk missing-content test
Code review noted the chunk variant's missing-content rejection was only
covered transitively via schema inheritance. Add an explicit test.
* review: expand file write content validation tests
- cover null and non-string write content for text, base64, and chunk writes
- keep explicit empty base64 writes valid
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>