Commit Graph
12 Commits
Author SHA1 Message Date
Brennan Benson fd20d34362 Save the issue title on GitHub issue links made without one (#40853)
* fix(github): title GitHub issue links that were saved without one

A workspace linked to a GitHub issue from the CLI (`--issue N`), a phone, the paired web
client or a typed number kept only the number, so search could match its issue title only
while a client happened to hold that issue. The host that owns the workspace now reads each
newly linked untitled issue once, after the write returns, the way its card would, and saves
the title on the link. It keeps the current review selection and never resurrects a removed link.

* fix(github): bound and publish issue-title fills like other metadata writes

Skip writes that leave links untouched before normalizing, check a queued link is still wanted
before reading it, publish filled titles once per repo per 2 s window, refresh the resolved rows
remote clients read, never wake a stopped WSL distro for a background read, keep a selected
review's PR suppression, and keep a failing write observer from failing a saved write.

* fix(github): keep link-title fills off unrelated writes and out of per-title refreshes

Ignore linkedWorkItem in the unchanged-links check (it is rebuilt on every write and always
titled), publish filled titles at once on a quiet host and at most once per repo per 30 s
during a burst on a timer, and only lift a PR suppression when the selected PR changes, so
restating the current PR (as a title fill does) keeps a hidden branch PR hidden.

* perf(github): publish filled link titles as addressed workspace metadata

Instead of a whole-repo worktreesChanged refresh per burst window, the host publishes one
addressed workspaceMetadata set record {hostId, epoch, seq, id, row} for the workspace whose
link was titled: to its desktop window over IPC and to paired clients as a runtime client event.
Clients patch that one row (Cmd-J reads it) and drop replayed records; older clients ignore the
new event and see the title on their next listing.

* fix(github): agreed workspaceMetadata record shape, per-id seqs and late-listing refill

Records name their kind and domain, clients keep the last applied seq per workspace for each
producer epoch (dropped when the epoch changes), and a listing that read metadata before the
title write cannot drop the title: recorded titles refill rows that still hold the same untitled
link for 60 s, never restoring a removed link. The IPC channel is marked as interim transport.

* fix(github): title-only, host-addressed application of workspaceMetadata records

Clients fill the record's issue titles into the named row's still-untitled links and never take
its links over the row's current ones, so a record that arrives after an unlink cannot restore
the issue. A paired host's records reach the row of their own execution host (an SSH twin is a
different row), and go through the bridge's connection-generation check like other host events.

* test(github): type the metadata-record test helper as AppState

* fix(github): keep recorded titles until expiry, refill only changed repos, retire with the pairing

A record that beats the listing adding its link keeps its titles for the full window; the refill
runs only for repos whose listing changed, indexes that repo's rows once and skips rows already
checked; per-workspace seqs live in the expiring entries; a re-pair or reconnect of the producing
environment retires its titles.

* fix(github): read the listing through the workspace record selector

* fix(github): keep link titles until their producer changes; fall back to a listing on eviction

Recorded issue titles no longer expire on a timer (a slow listing could outlast it): they live until
the producer's epoch, pairing or connection changes, refilling only present untitled links. When
more than the cap are in flight the oldest is evicted and its repo is listed afresh, which carries
the saved title. The refill runs in updater form so it never merges a stale store snapshot.

* fix(github): sequenced listings replace the retained-title refill

Per the re-plan ruling: the store keeps one workspaceMetadata cursor per process; a title record takes
the next seq in the same turn as its write, and each listing records the cursor when it reads metadata.
Clients note each record's seq per producer and repo; a listing at or past it ends the obligation, an
older one keeps titles the rows already show and lists the repo afresh (a new invocation, never joining
the stale one), a record whose row or link isn't listed yet asks for a fresh listing, and a seq gap
lists every repo of that producer. The retained-title table, its watcher, TTL and cap are gone.

* test(github): stamp capture, gap, restart and recovery coverage for workspace metadata listings

Also retire an environment's earlier coverage bookkeeping when it is re-paired or reconnects.

* fix(space): keep issue-title coverage per repo and host until a newer listing lands

Coverage now keeps each projection's newest record seq after its covering listing settles, so an older listing that finishes later still keeps shown titles. Projections are keyed by repo and host, paired producers keep their stream cursor, retired pairings and unlisted projections are dropped, every listing served by this host carries its metadata stamp, and a record with any unlisted titled link asks for a listing.


* fix(space): re-list after any stale listing and drop retired metadata producers

A stale listing now always asks for one fresh listing, so a link it dropped comes back and a failed scan can no longer end the wait. Removed environments drop their producer state, the producer's repo list is built only on a gap or a first-seen projection, and recovery listings skip the lineage refresh.


* perf(space): filter current rows only for listings that keep titles


* fix(space): forget removed repos on a metadata gap; keep titles for another producer run's listing


* fix(palette): search a linked issue by its saved title before its card renders

The issue's search entry took its title only from the issue cache, which fills when the card renders, and it shadowed the saved link's titled entry under the same id. It now falls back to the link's saved title.


* perf(palette): read a row's links once per palette document


* fix(palette): give each saved link its own search unit

Saved-link evidence was meant to carry an `attachment:` id but the override set an unused field, so a link shared the `issue:N` unit id with the legacy issue field and with same-number links from other repos, and only the first (often untitled) survived. The id now applies to the unit and its fields, so every saved link's title is searchable. Replaces the cache-title fallback.


* fix(space): keep the title queue draining past a failed link; encode environment ids in coverage keys


---------
2026-11-10 16:38:44 -08:00
Neil 490a5c05b8 fix(hooks): run a created worktree's setup once its own content is approved (#37978)
* fix(hooks): run a created worktree's setup once its own content is approved

Adds worktrees:runApprovedSetup (IPC) and worktree.runApprovedSetup (RPC,
capability worktree.run-approved-setup.v1). The host re-hashes the worktree's
setup and default-tab commands, runs them only on a match, and otherwise
reports the new content. After a create mismatch the host opens only the
command-less default tabs; approving in the trust prompt starts setup and the
command tabs. Clients whose host lacks the capability keep setup skipped.

* fix(hooks): review a created branch's different setup from the workspace, not instead of it

The trust prompt now says the workspace was created and its branch's setup
differs; approving runs it for that workspace without replacing the primary
checkout's approval (which made the next create prompt twice). Declining shows
a toast whose Review setup action reopens the prompt. Repo-wide trust returns
the content's hash so the host can run it.

* fix(worktrees): import approved setup from its path after the RPC methods move
2026-11-06 15:37:32 -08:00
Brennan Benson 6d76f5a77b Keep a sleeping conversation resumable when the terminal limit is reached (#35401)
* Keep a sleeping conversation resumable when the terminal limit is reached

Resuming a slept pane while the host terminal limit was reached could drop
the Resume screen and fall back to a plain spawn. Route capacity refusals back
into the deferred sleep state, retry through the main-owned resume, and keep
the original session and provider until space frees up. A later Sleep or
cancellation still wins.

* Retain sleeping pane retries after transient owner failures

* Verify sleeping pane retry lookup bounds at scale

* Correct sleeping pane retry test fixture types

* Use transport attach contract in terminal test fixture

* Construct complete terminal transport test fixture
2026-10-29 12:17:37 -07:00
Neil 6eebdbbaf8 fix(wsl): keep a stopped distro's worktrees and re-list once it runs (#35733)
* fix(wsl): keep a stopped distro's worktrees and re-list once it runs

After a relaunch with a WSL distro stopped, the E9 gate refused the
worktree scan and the sidebar showed "Worktree scan failed" with no rows
until the user pressed Retry. The listing now answers with the stored
rows (non-authoritative, so nothing is pruned), marks the distro stopped
or unverifiable in a new optional field, and re-lists once a wake or a
settled running read shows the distro running. The re-scan is held once
per repo and dropped when the repo is removed. The sidebar shows a
neutral state instead of a failure.

* fix(wsl): make the stopped-distro Retry a user act that may start the distro

The indicator's click now runs the scan inside runAsWslUserAct for the
repo's distro (new worktrees:scanStartingWslDistro IPC), then re-lists,
so clicking it can start the distro instead of re-running the gated
background scan that never could.
2026-10-29 06:54:51 -07:00
Neil 82a3214049 fix(sleep): reopen a slept agent whose tab is gone in a fresh tab instead of stranding it (#33598)
A slept agent whose tab disappeared now resumes in a fresh tab (with --resume, through the existing duplicate/host guards) when its workspace opens, and main's stranded workspace-sleep record is then dropped (only if no tab still lists that pane), instead of the record sitting forever while the workspace opens a bare shell.

Fixes #22657
2026-10-25 11:00:28 -07:00
OrcaWinandOrcaWin d1aef1eee9 refactor(worktrees): remove unreachable legacy worktree list IPC (#32942)
* refactor(worktrees): remove unreachable legacy worktree list IPC

The desktop and web preloads both always provide `worktrees.listDetected`,
so the renderer's fallback to `worktrees.list` never ran, and nothing calls
`worktrees.listAll`. Remove both IPC handlers, their preload/web wrappers,
the renderer fallback, and helpers only they used. Tests that observed
behaviour through the removed handlers now go through `worktrees:listDetected`.

* test(worktrees): drop removed list channels from SSH listing matrix

Address review: the canonical SSH listing test built channel names dynamically
and still invoked worktrees:list/listAll. Also fold the detected-result reader
into the shared worktree test fixtures, refresh stale comments, and assert the
backfill no-op test listed authoritatively.

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-23 17:30:59 -07:00
Jinwoo HongandClaude 41661e3312 feat(sidebar): main runs workspace sleep; the window follows (P3b) (#31564)
* fix(sidebar): land flip-a's switch-on fixes on main, still dark (no flip)

The fixes found while the sidebar ran from main's feed, without turning
that on: ORCA_SIDEBAR_INPUTS_IS_MAIN stays false, and none of flip-a's
stop-gaps come along.

- Main stops counting a closed tab's terminal once it was asked to stop
  and no pane shows it; a failed or refused stop takes the request back,
  and a host out of contact stays unverifiable.
- Main fetches a paired server's rows with the transport's normal bound,
  so a server slower than 5 s still lists.
- A deferred unread raise flips the folder row it was decided for, and a
  read while the owner check is out cancels it.
- Phone: no worktree.ps flash before the feed; a host's last installed
  feed stands in while a fresh read is out; it highlights its own
  last-opened workspace and a fold never scrolls; projects file under
  their project group with the desktop's indent; the collapse spinner
  shows only after 500 ms; "Reopen to update" keeps the rows on screen.
- e2e helpers and journeys for the switch-on suite.

* fix(sidebar): keep an owed relay stop marked, clear a thrown stop, and prune pending unread raises

- A stop-and-wait that throws takes its stop request back, like one that fails.
- A final stop the legacy relay still owes stays marked, since the relay retries it.
- Pending unread raises are tracked only while one waits, so the map no longer grows.
- The paired-server fallback poll test gives its third fetch a reply and asserts live.

* feat(sidebar): main publishes cached snapshots and announces its own stores; detected, view and live-content domains (inert)

* fix(sidebar): keep links confirmed only by a new git run, no rescan on metadata writes, no phone wait on detected checkouts

* fix(sidebar): rescan the workspace rows when the Windows runtime default changes

* feat(sidebar): one lifecycle queue per workspace in main, with in-progress hints (switch-gated)

Create, delete, terminal.closeAll, terminal.sleep and worktree.sleep now run through one
first-in, first-out queue per host-qualified workspace: strict order, no merging, delete
cancels what has not started and waits for what has, an unconfirmed outcome pauses until
the host's own answer. Hints publish in a new inProgress feed domain (no reader yet); the
creations domain is its projection. A finished create or delete keeps its hint until a scan
started after it applies, in the same revision. Adds the paired-server metadata serializer
(saving domain, no caller yet). Replaces the creation registry.

* fix(sidebar): a create never queues behind the previous occupant's delete

* feat(sidebar): main runs sleep; the window follows (P3b)

* fix(sidebar): keep electron out of the headless server's sleep path

The desktop registers how sleep reaches paired servers; a headless serve has none.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): type-check the sleep e2e specs; drop the removed VM suspend stub

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(sidebar): resume a paused workspace by re-observing, never replaying

- A command issued while paused waits behind the pause; when the owner answers again main
  re-reads terminal liveness and the listing, then carries on. A forced delete runs past it.
- Commands keep their arrival order across a workspace lookup; deletes by name key by the
  resolved id, so they wait for that workspace's running sleep.
- A second delete with different options is refused as already in progress.
- Close and delete read lost contact as sleep does.
- A create always frees its own queue entry, even after a retry took over its id.
- Drops the unused opId, and the paired-server write serializer and its feed section,
  which land with their first caller.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(sidebar): main is the only writer of a sleep's wake records; sleep classifies only lost contact as unconfirmed

- The window no longer captures wake records for a sleep main runs; it takes main's records
  from main's answer, which its wake and tab-open resume then read.
- A workspace reopened during its sleep keeps its VM running (the window and a phone's
  activation tell main).
- Only lost contact pauses: a definite chat-stop failure or a server's still-live answer is a
  plain failure with today's toast.
- A paired server's chats are asked with the Sleep mark, and again without it when an older
  server refuses the field, instead of reading an unprobed status.
- A phone's worktree.sleep is answered on acceptance and its activation no longer waits on a
  queued wake, so neither can outlast the phone's request timeout. The web client passes
  markChatsAsleep and keeps the receipts.
- One desktop adapter, passed where the desktop builds the runtime, replaces the two module
  slots; a headless server gets none.

Co-Authored-By: Claude <noreply@anthropic.com>

* chore(sidebar): share worktree.sleep's params with the RPC catalog; lint fixes

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): test the paired-server sleep where main runs it; drop the window's copies

The window no longer sleeps a paired server's terminals, so its convergence and order tests for
that path move to main (remote-workspace-terminal-sleep.test.ts) or follow main's answer.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): retry the sleep spec's liveness read while terminals stop

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(sleep): main resumes every workspace-sleep wake; the window only attaches

- Main claims and starts each slept pane through its windowless resume path, with or
  without a window: the "window present" check is gone.
- A phone's open queues main's wake, which finishes only once the resumes have started,
  and always notes the reopen so a running sleep leaves the VM up.
- The window no longer relaunches a workspace sleep's records in fresh tabs; opening a
  slept pane asks main to resume it and the pane's spawn attaches to main's terminal.
- A window save cannot overwrite main's workspace-sleep records.
- A VM that would not suspend no longer reads as "kept open": the terminals slept, and
  the toast says the machine is still running.

* test(e2e): main resumes a slept pane once, in place, and keeps its record

* chore(i18n): add the VM-still-running sleep message

* fix(sleep): a phone's or the CLI's failed sleep shows the desktop toast again

* fix: restore main's final P2 test fixes a merge reverted

* fix(sleep): window saves never write or clear main's sleep records; headless wake report counts only real sleeps

* test: a same-id twin's grace follows the rows' change, not the removed catalog hook

* fix(sleep): drop the 1 s settle wait after an observed exit, and wake a slept workspace's mounted agents on phone open

A workspace sleep stopped each terminal through main's stop-and-verify, which polled for a
further second after the exit it had already seen. And a phone opening a slept workspace no
longer reached the window's mounted panes of agents that had finished before the sleep, so
they stayed asleep until the window opened their tab.

* test: give the workspace-sleep wake record its prompt field

* refactor: match slept-agent records to the workspace with worktreeIdsEqual

* chore(sleep): one import from sleep-record-pane-claim

* test(sleep): route the disconnected-sleep SSH spec, declare the desktop sleep adapter, and open the workspace after its sleep starts

* test(sleep): delete a workspace the spec creates, not the seeded one later specs share

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-22 19:39:07 -04:00
Brennan Benson 131986aa16 feat(worktree): show checkout progress while a worktree is created (#24093)
* feat(worktree): show checkout progress while a worktree is created

A local create spends most of its time in the checkout `git worktree add`
performs, and the card only said "Creating worktree…" for that whole time.
Git already writes its "Updating files: N%" meter into our stderr pipe, so
the add call reads it passively (same command line and environment) and the
create card shows "Checking out files… N%" with a thin bar. All six create
labels are translated so a create never switches language mid-flight.

Rebuilt on main from the version stacked on the create-gating branch. On
main a create can also claim a prepared checkout; that path never runs
`git worktree add`, so it keeps today's "Creating worktree…" label with no
bar, and a missed claim falls back to the plain add, which shows the bar.

* test(worktree): pin that finalizing a prepared checkout reports no checkout progress

Claiming a prepared checkout never runs `git worktree add`, so the create
card keeps its label with no bar; this pins that at the finalize step,
including the retarget `reset --hard`.

* test(worktree): keep only the checkout-progress tests that pin a contract

Drops unit tests that mirror the implementation; the real-git flow, the parser
captures, the cross-version Git contract and the create card test remain.

* test(worktree): pin the checkout meter's path from git to the create card

* test(worktree): merge the narrow-terminal capture's pipe chunks
2026-10-15 18:14:56 -07:00
Jinwoo HongandNeil 29aa2a4f0f Refresh Source Control only for the worktree whose git index changed (#26946)
* Refresh Source Control only for the worktree whose git index changed

Index and HEAD-log writes under <common>/worktrees/<entry>/ now carry the
checkout they belong to (resolved through the head-identity memo), and the
renderer skips the status nudge when that checkout is a known sibling of the
active one. Unattributable changes (shared config, bound upstream ref,
overflow, SSH watches, unknown admin dirs) still refresh as before.

* Pass the stable worktree-path getter as an effect dependency instead of a render-time ref

* Fan out sibling head moves to every checkout's Source Control

A commit to a shared branch (e.g. main) from a sibling worktree can move the
active checkout's local upstream, whose ref leaf is not polled. Its logs/HEAD
append now stays unattributed so ahead/behind refreshes immediately; index-only
churn keeps its per-worktree attribution.

---------

Co-authored-by: Neil <neil@stably.ai>
2026-10-14 04:40:22 +00:00
OrcaWinandm4air bd54fc7fa3 refactor(renderer): delete the direct-SSH authority and reconnect coordinator (#27587)
No ssh: git provider is registered in production, so the reconnect
coordinator's catalog and lineage reads always came back non-authoritative
and its worktree refresh never merged. Its only live effect was clearing an
unconverted target's stale PTY bindings on each connected push and on wake;
the state bridge now does that directly.

Deletes the coordinator, preparation, worktree refresh scheduler, host
hydration, rollout flag and their product telemetry event, the scoped
direct-SSH reconnect/hydration options, and the now caller-less
repos:listForExecutionHost and worktrees:listLineageForHost IPC handlers.

Co-authored-by: m4air <m4air@Mac.localdomain>
2026-10-11 14:41:17 -07:00
Neil c5a1c0ebc9 Add confirmed deletion of nested worktrees (#25668) 2026-10-05 12:42:21 -07:00
Neil d1abe28471 refactor(preload): split bridge API modules
(cherry picked from commit b77e31873b)
2026-09-01 00:57:54 -07:00