mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
4dcd6af5dfa8f7bdc656d9ee18011d34f3497f21
1511
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
245e5d83fd |
Reduce tab preview tooltip delay to 500 ms (#33447)
* Reduce tab preview tooltip delay to 500 ms * Reduce tab hover card test delay expectations |
||
|
|
e1ee51dec4 |
fix(ssh): saved port forwards come back after a restart that reconnects through the host's Orca server (#32484) (#33388)
* test(e2e): a saved SSH forward on a managed host comes back after a restart (#32484) * test(e2e): a restart that reaches the host only through its server's tunnel restores saved forwards (#32484) * fix(ssh): restore saved port forwards whenever a host's server is up, not only after an SSH connect (#32484) A host whose managed server the app reaches at startup through its tunnel publishes its served status from the tunnel path, which never ran the forward restore. The served status itself now triggers the restore, from any publisher, and a host kept on the legacy relay bridge restores too. * test(ssh): type the restore spy's target argument * fix(ssh): user forward edits keep unrestored saved forwards; restore once the tunnel is up Add, edit and remove now merge with saved forwards that aren't live, so a restart that restored nothing no longer lets one re-added forward erase the rest. A call that brings up the managed server's tunnel restores too, so a failed update check or redeploy can't skip it, and a restore runs once per transport instead of on every status note. * fix(ssh): a forward that failed to bind is retried on the same transport * fix(ssh): restore again when a restored forward closed; a reused local port replaces its old mapping * fix(ssh): user forward edits and restores share one per-host queue A restore that ran mid-edit could reopen the mapping the edit was replacing and save both. Every add, edit, remove and restore for a host now runs in order through the same queue. * fix(ssh): pace forward restores and back off a forward that keeps failing Each runtime call triggers a restore over the managed tunnel, and with system SSH every attempt is a new login. Restores on the same transport now run at most once per 30 s, a forward that keeps failing waits longer each time, and teardown forgets both. A tunnel with no status yet counts only outside a connect; a remove re-checks its forward inside the queue; drop a circular listener test and the plain restart e2e case. * fix(ssh): a start a teardown cancelled records no backoff * fix(ssh): a reconnected transport retries failed forwards at once |
||
|
|
e13e5e8b7d |
fix(orchestration): submit mail pointers to Cursor CLI instead of leaving them in the composer (#33347)
Orchestration mailbox pointers now get their delayed Enter for Cursor too (the Cursor no-Enter skip from #3156 left mail delivered but never acted on). Verified with real cursor-agent. Fixes #19760. Supersedes #19761. Co-authored-by: Maple <54388301+zleo-ai@users.noreply.github.com> |
||
|
|
a6d4a4fea5 |
fix(windows): respawn a ConPTY pane that started without keyboard input (#33423)
* fix(windows): respawn a ConPTY pane that started without an input reader Bundled ConPTY occasionally starts a pane whose console host never reads its input pipe: the shell prints its banner and prompt, but every keystroke and the typed launch command are dropped. Such a pane is identifiable from its first output, because a healthy one always queries DA1 before relaying anything else. The daemon now waits for that first output, respawns once when it is missing, and replays the buffered startup output so no byte is lost. The table stress probe now waits for node-pty's own readiness rather than typed input. * fix(windows): bound the ConPTY startup check and never re-run a command - Respawn only when no command rode in the shell's argv; otherwise keep the pane and say on screen that it cannot take input. - Accept the pane unverified after 5s of silence instead of stalling the spawn. - Distrust the check after two misses with no pane ever announcing, so a changed ConPTY build cannot respawn every pane. - Kill a discarded pane through its job before closing the pseudoconsole. - Report a cancel during the wait as an attach cancel, not a spawn failure. - Assert on real Windows ConPTY that a fresh pane announces its input reader. * fix(windows): respawn only Orca's own shell argv and close an unready pane now - Treat user shell args like an argv startup command: never respawn, show the notice instead, so a command such as pwsh -Command never runs twice. - Close a discarded pane's pseudoconsole directly; node-pty defers kill() until first output, which a silent pane never produces. Kill the shell by pid when no job owns it. - Reconcile a master node-pty retired during the startup wait. - Drop the test-only respawn counter; the count rides in the daemon log. * fix(windows): tear a discarded unready pane down through node-pty's agent node-pty defers kill() until first output, so closing only the native pseudoconsole left the input pipe and output worker of a silent discarded pane alive until daemon exit. The agent's kill closes all three. * fix(windows): discard startup panes by handle only; account for the mock prelude A pid-addressed kill could hit a recycled pid when the shell exited during startup; the job and the agent's native kill both act on handles. Lifecycle assertions now expect the replayed DA1 query when the suite runs on Windows. * test(e2e): give the mocked Windows pane its ConPTY prelude |
||
|
|
fef00e4461 |
feat(dev): give each worktree its own pnpm dev profile and ports (#33416)
Linked worktrees now get a per-worktree userData profile under
orca-dev-wt/<hash>, a deterministic collision-checked renderer port
(15200+) and CDP port (9333+), so parallel `pnpm dev` runs no longer hit
the single-instance lock. The main checkout keeps the shared orca-dev
profile; --shared-profile / --worktree-profile / ORCA_DEV_PROFILE override.
The runner prints `ORCA_DEV_INSTANCE {json}` on stdout and mirrors it to
out/dev-instance.json so agents can attach over CDP. orca-dev.mjs and the
terminal wrappers follow the profile the worktree's dev app used.
Adds profile seeding (pnpm dev:seed-profile, pnpm dev --seed-profile,
pnpm dev:fresh --seed-from): an allowlisted slim copy of projects,
workspaces, settings, SSH targets, and the account session, written as
orca-data.json for Orca's own import; the source is only read.
Storage reclaim removes per-worktree profiles once their worktree is gone
and no app, runtime, or daemon still uses them. dev-fresh-profile.sh is
replaced by a cross-platform dev-fresh-profile.mjs.
|
||
|
|
8e9f86b119 |
feat(managed-server): Update and Update anyway for a managed SSH server, keeping its running terminals (#33397)
* feat(managed-server): offer Update and Update anyway on a managed SSH server's row A managed SSH server only updated on connect, and every connect deferred while a terminal ran there. Its row now offers Update when this desktop bundles a newer build than the Orca that runs the host. When running terminals defer it, Update anyway restarts the server past them; the terminal daemon carries them across the restart. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(managed-server): Update anyway passes only counted live terminals and keeps Update's version admission Update anyway now sends the incumbent build it confirmed over; under the host fence it is refused if another desktop activated a build since, keeps the newer-host downgrade refusal, and never passes an uncounted terminal census. The offer also covers hosts activated before the Orca version was recorded, for the build this desktop already deferred there. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(managed-server): Update anyway also confirms a menu Update the row does not advertise Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(managed-server): the row shows one confirmation at a time Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * ci(e2e): route managed-server update sources to the Update anyway Docker spec Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com> |
||
|
|
95df4c74ed | refactor: group Parcel watcher process ownership (#33401) | ||
|
|
4fa91acb4f |
fix(repos): drop a paired server's old workspace rows when it relists them under new project ids (#33396)
* fix(repos): drop a repo's worktree rows when its host's catalog no longer lists it A paired server that relists its checkouts under new repo ids (a project copied into another profile, then that profile switched to) left the desktop showing the old rows beside the new ones, the old under Unknown. Every catalog fetch now drops the rows of the repo registrations it removed, scoped to the dropped host. Refs STA-12076 * fix(repos): purge a dropped repo's workspaces like a vanished checkout Clears selection, tabs and per-workspace state of the dropped rows through the same bulk purge an authoritative scan uses, flushing delayed editor text first; the three catalog fetch paths share one finalize step. * fix(repos): drop rows only, never a registration added after the request Review: purging tabs and selection could race repo add/remove and lose state. Keep the change to rows, and fence it to registrations the store held when the catalog request went out. * test(e2e): drop the fixed settle wait from the repo id change repro |
||
|
|
fdcf887bf0 |
chore(lint): fix suppressed lint violations instead of ignoring them (#33294)
* chore(lint): remove disable directives that suppress nothing
Each was reported unused by the oxlint config that owns its rule (root,
casting, react-doctor, anti-slop, native-plugins), including 17 file-level
react-doctor blanket disables whose rule no longer fires. An unused
react-hooks disable was masking a react/purity finding in
useAgentBucketCounts (compiler rules skip functions with hook
suppressions); suppress that one explicitly where the code explains it.
* test(tab-bar): use typed tab/file fixtures instead of @ts-expect-error partials
* chore(lint): fix misc suppressed lint violations instead of disabling
- consistent-type-imports: the "vi.importActual requires inline import()"
rationale was wrong; use typed vi.mock(import(...), importOriginal) or a
type-only namespace import.
- no-control-regex: source-control commit/push failure summaries, shell PATH
hydration, and ipynb ANSI parsing reuse the shared ansi-escape-sequences
stripper and control-character pattern instead of private copies.
- no-explicit-any / no-useless-spread / no-this-alias: typed `this` in
bridge test mocks, drop blanket file-level disables, drop a this-alias.
* refactor(main): replace avoidable type assertions with checked narrowing
17 production cast suppressions in main/wsl-guest removed: untrusted IPC
and RPC payloads (Jira issue update, WSL guest plugin install, handshake
type) now narrow through isRecord/type guards and build typed results;
redundant casts dropped; hand-rolled object checks and errno casts use the
existing isRecord / hasErrorCode helpers.
* refactor(renderer,mobile): replace avoidable type assertions with checked types
13 production cast suppressions removed: RPC failures read through the
`ok` discriminant instead of casting to RpcFailure; react-native-web host
refs narrowed with instanceof HTMLElement; redundant reply/diff casts
become checked annotations; the TUI agent param uses z.custom(isTuiAgent)
like its sibling schemas; the agent-status slice cast that hid missing
actions is gone so the creator's return type checks the whole slice.
* chore(lint): drop dead tiptap wrapper; scope anti-slop mocking rule to test support
- safe-react-node-view-renderer patched handleSelectionUpdate, which
@tiptap/react 3.31.3 no longer has, so it always returned early. Use the
stock ReactNodeViewRenderer.
- The anti-slop test override only matched *.test.{ts,tsx}, forcing
file-level no-module-mocking disables in test harnesses and fixtures.
Match *-test-harness, *-test-fixture(s), .test-fixture and *.test.mjs,
then drop those disables and the now-redundant per-file exemptions.
* test: replace repeated cast suppressions with typed fixtures (main/shared/e2e)
Net -119 cast directives. Typed fixtures replace recurring casts:
unusedSshConnection/unusedOrcadHostTransport placeholders, a typed
readPersistedState harness read (exposed a test treating optional
httpProxyUrl as always set), real Response objects in fetch spies, an
updater main-window fixture, a CDP webContents adapter, one shared
releaseModuleExport for cross-version tests, and requireOverlayDir in
place of `as string` on possibly-undefined overlay paths.
* fix(runtime): validate loaded device rows and host scope instead of casting
Two cast suppressions claimed checks that did not happen:
- device-registry-file-read: "normalizeLoadedDeviceRow re-validates every
field" was false; deviceId, token, name and timestamps were never
checked. Rows without an identity are now dropped and the file is set
aside through the existing corrupt-file path (registry reads as
doubtful, so no revocation verdicts); a non-string token is stripped
so the row stays listed but cannot authenticate.
- client-ui: "only optionality differs" was false; any string reached
persisted workspaceHostScope. Narrow it with isWorkspaceHostScope and
ignore unknown scopes (wire schema unchanged for mixed versions). The
remaining cast now states what it actually covers.
* test(runtime,ipc): drop redundant cast suppressions; typed runtime store fixture
-98 cast directives across runtime/ipc tests: 61 casts that typecheck
without the assertion; a typed vi.fn for scoped invalidation; a
partialRuntimeStore fixture in place of `store as never` for
OrcaRuntimeService; typed worker terminal/authority builders in the
incarnation-fallback suite (the old stub lacked runtimeId, ptyId,
worktreeId and launchTokenHash) plus removal of stacked duplicate
directives there.
* chore(lint): typed vi.mock imports and explicit listener snapshot in shared-bucket tests
Remaining files from the misc-rule pass that overlapped other buckets:
codex-accounts mocks use the typed vi.mock(import(...)) form instead of
a consistent-type-imports disable; the daemon late-exit fixture copies
its listener set with Array.from instead of a no-useless-spread disable.
* refactor(github,mobile): narrow routing settings type; drop remaining avoidable casts
- work-item-routing cast a one-field object to AppState['settings'] under
a claim that readers only consume activeRuntimeEnvironmentId. Return the
existing RuntimeFocusSettings pick instead and narrow the cache-key
chain's parameters, so the compiler enforces that claim.
- mobile file preview reads refusals through the RpcResponse `ok`
discriminant rather than a cast.
- terminal document host-notify accepts `unknown` errors, removing the
message-bridge cast.
- native chat readability drops a redundant repo-list cast.
* test(renderer): replace repeated cast suppressions with typed fixtures
-140 cast directives (and ~150 unsuppressed casts of the same shapes):
- mockPanePtyArgs owns the connectPanePty fixture casts, so a wrong
argument order or fixture is now a type error; drop binding casts for
members PanePtyBinding already declares.
- Seed AppState tab rows with the typed makeTerminalTab; this exposed a
split direction 'row' that is not a TerminalPaneSplitDirection.
- Partial GlobalSettings use getDefaultSettings overrides; Jira-only
stores use createTestStore; runtime environment rows/statuses come from
a typed paired-runtime-environment fixture.
* chore(lint): drop no-module-mocking disable now covered by the test-fixture glob
* test(e2e): fix all 221 grandfathered e2e type errors; baseline to zero
The e2e typecheck baseline grandfathered 221 errors in 119 files. About a
third were tests drifted from the current API, several of which would
fail or mislead at runtime:
- terminal-shortcuts called a nonexistent xterm buffer method;
- the daemon-lifecycle fake lacked terminateOwnedTree, hidden by importing
its type from a module that does not export it;
- arguments in the wrong position (listProcesses(hostId, opts), a dropped
pty.write inputKind, an extra toWebTerminalSurfaceTabId argument);
- removed enum values ('drawer', 'idle', 'editor',
'session-insecure-origin') and a launch_source the telemetry schema
rejects;
- missing required fields (turnScope, childWorkOpen, providerIdentity,
reviewLookupOutcome, runtimeServiceAccount behind an `as RelayConfig`);
- dead reads (tab.customTitle, bucket.targets, changedPixels).
Also lifts variables that narrowed to `never` after poll-callback
assignment, and adds a typed xterm render-dimensions accessor. Includes
the cross-version suites' shared releaseModuleExport adoption.
* chore(typecheck): gate the now-clean e2e typecheck in local lint
The e2e baseline is empty, so the ratchet now rejects any tests/ type
error. CI already runs it via run-ratchets; add it to pnpm lint's
explicit ratchet list and replace the stale 'not yet clean' note.
* test(mobile): typecheck every mobile test; baseline 105 files -> 0
mobile/tests-typecheck-baseline.txt grandfathered 105 test files (531
errors) whose types CI never checked. Contract changes had landed without
these tests following, and type pins in them silently checked nothing.
- Root cause: salvagedOptional (src/shared/zod-salvage.ts) cast away
zod's optional marker, so every reply type built on it claimed
salvaged fields were always present. Return a real ZodOptional;
runtime is unchanged (the outer optional short-circuits undefined).
- Production now takes the port it uses: RpcOperationSender instead of
RpcClient for send-only callers, a narrow fetch signature, and only the
request fields push-receive reads.
- findAllByHostType/findByHostType replace `'Text' as never` lookups;
stubRpcClient gives tests a typed RpcClient.
- Real test bugs: settings-host-client-lifecycle "relay" hosts were
direct hosts (relayHostId no longer exists); two toEqualTypeOf pins
could never hold; a recovery hook ran with an undefined scope key;
a create-PR test cast to a never-imported type; "no URL" assertions
passed on null; plus stale required props, args and enum values.
* test(e2e): parse workerStart result instead of casting it
* test(runtime): typed partial runtime stores with production-shaped fixture data
Route every partial store passed to OrcaRuntimeService through
partialRuntimeStore with typed row builders, removing 60 cast directives
that hid fixture data production never produces:
- paired-client-navigation-test-harness keyed setWorktreeMeta writes by
`[object Object]` (it took an id where the store takes a target), so
reads never saw them;
- invalid worktree lineage origin/capture and incomplete workspace lineage;
- getWorkspaceSession returning null where the store returns the default
session; getGitHubCache returning undefined;
- orcad migration states missing migrationId/manifest/receipt, with a
toEqual pinned to that incomplete shape;
- partial repos, metadata, folders, project groups and notification rows.
* refactor(renderer): resolve suppressed React hook lint instead of disabling it
109 of 187 react-hooks/react-doctor suppressions fixed:
- ~95 exhaustive-deps disables only hid stable refs and setters passed
through controller objects; list them (identity never changes, so
nothing re-runs), plus a few real stale closures (tabId/worktreeId).
- Effects keyed on one value that read the latest props now use
useEffectEvent (WebConnect, skill freshness, Bitbucket dialog seeding,
Landing preflight, automation catalog, structured-session recovery,
file explorer).
- The conditional layout-owner hook is selected once at module load from
the build constant instead of a rules-of-hooks disable that also hid
the whole controller from compiler and react-doctor rules.
- A hook disable was masking a react/purity Date.now() in the palette's
recent-tabs memo; sample it when the snapshot is captured.
- Three identical debounced-search effects share
useTaskSearchDebouncedApply; layout snapshot merge helpers move out to
stay under max-lines.
Remaining suppressions are mount-once resources, invalidation epochs,
deliberately keyed fields, and test harnesses that mock the dispatcher.
* chore(ratchet): exempt test harnesses and fixtures from the focus-read ratchet
The ratchet skipped *.test-fixture.ts but scanned *-test-harness,
*-test-fixture(s) and *.test-support files. Those seed settings with
getDefaultSettings (which the ratchet discovers as a focus reader) and
never route host work, so 17 baseline entries were false positives, and
replacing a test's `settings: {...} as never` with typed defaults tripped
it. Match the repo's test-support names and prune (which also drops one
stale entry), and rename the frame-ordering harness to *-test-harness like
its siblings. A blanket "-harness" match would also exempt
e2e-terminal-pty-harness, which production imports, so the names stay
explicit.
* test: merge duplicate shared/constants imports
* revert(file-explorer): restore original tree load effects pending SSH e2e investigation
* test(runtime): drop RuntimeStore cast now that the fixture is typed
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
|
||
|
|
db8fc6d0cc | refactor: group agent-session conversation and message identity (#33324) | ||
|
|
874a251cb4 | test(e2e): click a terminal link only after the terminal refits to its pane (#33328) | ||
|
|
870038cfc6 | Test title-only fallback for unavailable AI Vault search (#33234) | ||
|
|
537f561d58 |
Revert "fix(terminal): a file link opens in the workspace it was clicked in (#32320)" (#33304)
* Revert "fix(terminal): a file link opens in the workspace it was clicked in (#32320)"
This reverts commit
|
||
|
|
b5f9e06164 |
fix(editor): highlight Zig source and diffs (#32705)
* fix(editor): add bounded Zig syntax highlighting
Recognize .zig files and load the existing pinned Shiki Zig grammar through
the lazy TextMate provider. Map its scopes to stock Monaco theme categories
while preserving the shared tokenizer state and per-line bounds. Keep .zon
and formatting outside this lexical-only change.
Reuse the optional scope-mapping hook reviewed in Haskell PR #25411 and
its refreshed candidate
|
||
|
|
ba43f8539c |
fix(runtime): a cold-restored terminal keeps its agent session without showing the old Done (STA-12043) (#33302)
* fix(runtime): a pane's new process does not wear its previous run's Done (STA-12043) * fix(runtime): keep the superseded turn's segment and date process start at spawn request * fix(runtime): take the identity row's segment from the turn clients were shown |
||
|
|
0c27fd5f91 |
fix(ssh): move a managed tunnel off an unbindable saved local port (#33271)
* fix(ssh): move a managed tunnel off an unbindable saved local port When the saved local port of a managed SSH server's tunnel can no longer be bound (EADDRINUSE from another listener, or EACCES from a Windows excluded port range), pick a free port, save it with the pairing and the deployment link together, and build the tunnel again. The pairing, server identity, remote port and projects are unchanged. A move interrupted between its sidecar and store writes reads the moved port from the stored endpoint, so the deployment link is never dropped. Fixes #33182 Co-authored-by: SahilZ0810 <sahil@zamp.ai> * test(e2e): drop squatter connections so teardown never waits on them * fix(ssh): move the tunnel port before pairing repair and retire old transports Move outside buildWithPairingRepair so a refusal on the moved port is repaired against the revision the move saved, and retire transports and status owners still dialing the old port, as a repair does. * fix(ssh): refresh maintenance pairings on the current tunnel port Update, rollback and restart reread the deployment's local port when they save the new pairing, since the census before them may have moved it. * fix(ssh): let concurrent tunnel builds follow a port another caller moved --------- Co-authored-by: SahilZ0810 <sahil@zamp.ai> |
||
|
|
2679464ead |
fix: let capable paired hosts plan composer terminal agent launches (#32192)
A workspace created from the composer on a paired host launched its terminal agent with the desktop's command, arguments and environment. Hosts that negotiate agent.launch.v2 now get startupAgent (and startupPrompt) and build the launch through their own planner and settings. Older hosts, failed probes, hosts that predate the agent's name, linked-item drafts and VM recipes keep the desktop-built command. When the host planned the launch, the window never runs the desktop plan or its own agent recovery, and says it couldn't confirm the agent started if the host reported no agent terminal. |
||
|
|
8733ec4033 | refactor(persistence): colocate test fixtures and harness (#33233) | ||
|
|
015fae84cc | Keep search highlights visible in long session excerpts (#33220) | ||
|
|
da180863a9 |
Fix Resource Manager tab sizing and sort PTYs by workspace activity (#33226)
* Keep PTY resource tab mounted and sort workspaces by activity * Fix Resource Manager tab sizing and verify PTY list scaling |
||
|
|
4948524c84 |
fix(editor): enable Lean syntax highlighting (#31930)
* fix(editor): enable Lean syntax highlighting * fix(editor): enable Lean syntax highlighting Adapt #31930 onto current main and retain its bundled Lean grammar repair. Register the lazy TextMate provider and cover detection, proof syntax, nested comments, and bounded tokenization. * test(editor): verify Lean highlighting in the full app --------- Co-authored-by: Neil <neil@stably.ai> |
||
|
|
28258c0ba9 |
fix(terminal): keep host-retired panes out of every layout a paired client writes (#33083)
Exiting a paired host tab's original pane moved the host's active leaf to the surviving split. The client's reconciliation effect followed it with setActivePane, and that persisted every still-mounted pane, writing the retired leaf (whose remote PTY had not ended yet) back into the layout. The leaf was then no longer retired, so its pane stayed forever. Any other persist in that window (insertions, clicks, title edits, divider drags) could do the same. persistLayoutSnapshot now drops the leaves the host retired, using the retireLeavesFromTerminalLayout main already uses, moved to shared. The retired set it reads is the one the reconciliation effect tracks. |
||
|
|
7190b835f1 |
test(e2e): stop restarted serve hosts with SIGTERM so the PTY daemon survives (#33067)
On macOS, orca serve vetoes an unmarked app.quit() (#15537), so Playwright's close never finished and its fallback force-killed the whole process tree, including the detached PTY daemon's terminals. The restart spec's fixture process got SIGTERM and the spec failed on every macOS run. Restart now stops serve the way a supervisor does (SIGTERM, a marked quit), and the restart spec no longer shows or focuses its client window. |
||
|
|
4b8c59c400 |
fix(terminal): a split right after a paired-server tab opens starts on the server (#33078)
* fix(terminal): a split of a paired-server pane still attaching starts on the server A pane of a server tab whose own attach is in flight has no transport id, so its split fell back to a local split. Only the server starts terminals in its tabs, so the new pane stayed blank. Split from the terminal the server already published for the pane, or wait for it. * fix(terminal): tell the user when a held split never starts, and keep focus where they went A split held for the server's source terminal now gives up when the pane, its tab or the pairing goes, toasts when the server never publishes it, and lands without focus if the user moved on. * fix(terminal): a held split runs only on the server the gesture targeted Check the workspace still belongs to that server before reading the source binding, and accept only a binding that names it, so a hydration swapping both cannot redirect the split. * refactor(terminal): route an unattached server pane's split straight to the server Drop the held split. The split uses the terminal the server published for the source pane, and when there is none yet it says so at once instead of queueing a gesture that needs its own fences. * fix(terminal): a server-tab pane never splits locally Refuse every split the server cannot take for a pane of its tab, and say whether the host is disconnected or the source terminal has not started yet. |
||
|
|
395aa755b4 |
fix(keybindings): report conflicts with shortcuts the main process consumes first (#10780)
* fix(keybindings): report conflicts between terminal shortcuts and chords that fire in terminals Actions that reach a focused terminal now share a conflict bucket, and the Shortcuts pane judges the bindings as written in the file instead of the map the loader already stripped. * fix(keybindings): flag only the ignored binding and say it was ignored The other claimant keeps its own default and never stopped working, so a warning on its row blamed an untouched action. Adds the e2e regression that seeds a conflicting file inside the isolated E2E home. * test(keybindings): capture the shortcuts pane at each stage of the conflict report * test(keybindings): assert the untouched action carries no warning Visibility alone would still pass if a regression warned on both rows. * fix(keybindings): narrow the ignored-binding guard and tighten its negative test Review follow-ups: - Only warn when the file asked for a binding and the loader took it away. The guard now also requires the action to be absent from the active map, so an override that survived is never labelled "was ignored". - The negative case used view.tasks, which has no default binding, so the conflict assertion passed without exercising the terminal bucket at all. It now uses worktree.quickOpen and asserts the definition exists, is not terminal-reachable, and has a non-empty default list. * fix(keybindings): compare main-process shortcuts against every scope - List the actions resolveWindowShortcutAction consumes in main and put them in every scope's conflict bucket, so a binding elsewhere on the same chord is reported instead of silently losing it. - Browser bindings are compared only against the actions a focused guest forwards ahead of browser chords. - Dictation stays out, since main lets its chord through while voice is off (the default). - The two digit ranges may still share a modifier. - Drop the allowInTerminal terminal-bucket rule. - Explain overrides the loader drops on a later pass. - Pin the list to the resolver and the guest order with tests. * fix: report main-intercepted shortcuts in Settings without dropping saved overrides Main-process interception now only feeds the Settings conflict report. The loader, write path and web normalization keep main's scope-only check, so existing keybindings.json overrides load exactly as before. --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
6059a5f7fa |
Native chat: a message is never marked not delivered after a crash, and reopening a chat stops reading the transcript (#30580)
* Native chat: never mark a message not delivered after a crash; reopening a chat stops reading the transcript After a crash or restart, attach read a window of the agent's saved log and marked any unconfirmed message missing from it as rejected "not delivered". Absence from a log Orca does not own is not proof: the window's anchor moves every turn (STA-7416) and its text filters drop tagged prompts (STA-7453), so messages the agent received were marked undelivered and retyping them duplicated the question. The read also ran on every attach (STA-7434). Delete the inference: the provider history window and its adapter hooks (Claude window and branch proof, OpenCode stored-messages reader), the submission matcher, and the attach step. A send the crash boundary leaves unconfirmed stays an ordinary sent message; the interrupted turn still ends through the existing settlement. Explicit live refusals are unchanged. A queued card whose hand-off a restart left unconfirmed no longer counts as able to return to the queue, so it stops keeping an idle chat loaded. * test(native-chat): update restart fixture calls after history removal * Hide rewind targets without a saved native boundary * Run rewind boundary regression across host and renderer * Keep incoming main package-manager lock unchanged * Test recovered chat consumer outcomes without delivery inference * Assign the complete cursor in recovered phone regression fixture * Supply presentation capture in rewind boundary fixture |
||
|
|
c8db507932 |
fix(ui): dismiss notifications without closing modal editors (#32065)
* fix(ui): allow toast dismissal over modal panels * test(ui): retain toast stacking and credential boundaries * Preserve modal state when dismissing stacked toasts * fix(ui): limit modal toast gate to dialog and sheet overlays Radix menus and selects also set body pointer-events:none; gating on that blocked toast clicks from closing an open dropdown. --------- Co-authored-by: Neil <neil@stably.ai> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> |
||
|
|
dd69e7dd80 |
fix(renderer): restore focus after floating tab rename (#31510)
* fix(renderer): restore focus after floating tab rename * fix: limit one-shot chat composer focus to tab rename return Other onlyIfFocusUnclaimed callers (e.g. floating terminal) keep main's replayable composer focus request. --------- Co-authored-by: MimeLyc <11363886+MimeLyc@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
f04b831a61 |
Record Stop immediately and settle subagents from exit proof (#28051)
* Observe Claude readiness through the host status * Keep optional readback behind host readiness * Recheck launch permission after host readiness * Preserve the awaited child during permission revalidation * Await Codex protocol open under host ownership * Await ACP protocol open in the host * Derive ACP opening from its protocol session facts * Exercise unanswered ACP options with an explicit silent route * Await Pi open and fail signed-out construction * Read Pi catalog facts from awaited opening * Keep the Pi fixture exit observer * Record failed owned opening before the first message * Abort a matching provider open when its child exits * Cover provider exit during an unanswered host open * Cover explicit failure facts on a host-stopped child * Capture message choices per accepted send and preserve picker state * Apply accepted Claude message choices at prompt handover * Apply captured message choices at provider dispatch * Bind native chat choices to accepted message dispatch * Reset Codex turn choices for each captured message * Resolve Codex message defaults from native facts * Align message choice and Pi default fixtures with provider dispatch * Align option fixtures with saved message choices * Match Codex message choices to the active turn origin * fix: let pending commands reach handover refusal * Abort pre-prompt message choice writes with host-owned wait * Verify message choices through provider input boundaries * Keep legacy mobile test hosts explicit about message choices * Align Stop close causes and request-note fixtures * test: align ACP Stop note fixtures with settled behavior * Verify close intent reaches the native adapter * Record the Stop request before waiting for interruption * Keep Stop cleanup independent of request-note persistence * Keep startup waits cancellable and preserve operation order * Derive Codex startup exits from host phase * Open Codex protocol in background-task test fixtures * Keep ACP startup ownership until handshake completes * Run ACP opening catalog fixtures with Node SQLite * Verify saved ACP picks without live option writes * Keep ACP catalog bookkeeping from failing an options read * Restore signed-out restart and fail Pi startup faults * Run Pi opening catalog fixtures with Node SQLite * Preserve message choices across cold clients and kept cards * Remove the unused provider option restore ledger * Clarify launch choices and legacy option hydration * Remove remaining restore-ledger forwarding and assertions * Remove unused ACP and Pi restore bookkeeping * Report late Codex exits and keep card Cancel notes scoped * Remove the Pi close wrapper left by unused cause plumbing * Update Stop tests for the simplified close contract and Node journals * Update remaining close and interrupt assertions after cause removal * Keep live choices and delivery order while agents start * Drop retired startup revision from Codex opening * Keep live-option cancellation until message choices and clarify catalog reads * Hold ACP option replies in live-write cancellation regressions * Remove retired Pi startup revision and match saved-pick behavior * Check running choices and retained rewind messages after delivery integration * Use observed root exit in Codex close reports * Apply startup picks and let parked messages release operations [skip ci] * Remove unused Codex opening report field [skip ci] * Keep ACP restoration facts local to the provider [skip ci] * Apply Pi startup picks after readiness and remove unused open facts [skip ci] * Cover goal changes during the held Codex message wait [skip ci] * Save a waiting startup pick after Stop ends its opening [skip ci] * Save choices when startup ends or a replacement is still opening * Check saved choices separately from Claude startup reports * Release scripted Codex startup before awaiting queued picks * Check the saved picker selection in per-message choice fixtures * Exercise current choices and eager client capabilities in runtime fixtures * Keep accepted Stop requests in runtime settlement assertions * Reuse runtime fixture reads and type the fake connection directly * Type the Codex child-proof fixture connection directly * Align startup fixtures and preserve observed Claude exits * Honor queued send authorization in Claude runtime fixtures * Open Codex protocol sessions before Stop fixture dispatch * Wait for the opened Codex child before runtime notifications * Keep repeated permission picks usable and align Codex fixtures * Validate pending picks against the live option state * Order startup picks before later messages and retain flow regressions * Keep reviewed lifecycle contracts while pruning duplicate tests * Save picks after a proven exit without requiring a child * Type the startup reply fixture against the Claude control contract * Keep Codex startup flow contracts and remove overlapping adapter tests * Restore the committed dependency lockfile after test tooling * Keep startup recovery flows and remove duplicate Pi handshake cases * Cover both Pi startup choices in the delivery flow * Match the Pi prompt entry recorded by the scripted wire * Honor the before-input callback in the startup reply fixture * Keep message choice flows and remove overlapping selector tests * Retain Stop flows and remove repeated reducer and adapter cases * Keep Stop note writes independent of full session context * Keep startup option menus independent and show the saved choice * Exercise startup picks across real menus and the runtime * Match startup picker fixture to saved message choices * Keep saved effort visible when picking a model during startup * Include the running-turn scope in repeated Stop fixture * Use the current attach contract in import fixtures * Keep the Codex journal translator within its line limit * Match ACP startup fixtures to saved picks and late Stop behavior * Check startup effort after saved picker choices are acknowledged * Complete Stop target types and keep the effort regression concise * Use requested picker values in the effort regression acknowledgement * Exercise Stop receipt replay across a resumed provider child * Require owned provider spawn and one host-awaited open (from #28055) * Restore the live checks the startup cleanup's deleted tests pinned One assertion each, in the surviving suites: a stale-generation exit is ignored, a failed create lets its sink go, a readback before open is saved, an idle stop that fails is retried, a no-pick Pi start teaches its default, an older client's pre-spawn failure and packed answer, an undrivable chat stays readable, the resting slash menu stays on its own host, the main agent reads apart from subagents, restore publishes status, naming runs off the host's status feed, and a throwing logger never reaches the operation. Also corrects the reliability gate note that claimed the send-idempotency suite replaced the refusal-retry tests. * Read a rewind in doubt outside the chat's serialize and apply it in the commit The started settle and the attach's ready-child path read the provider's history first, then apply what it proves in a short commit that re-checks the rewind it read. * Accept messages while the agent starts: run the start outside the chat's serialize The chat's serialize was both where a send was admitted and where its agent started, so every slow step of a start (closing-child join, lease reconcile, recovery probes, resolvers, history read, spawn, sink drain, provider dispatch) held every later send. A start now registers in one short step before its first wait: its abort controller (which Stop, close and quit already reach outside the lock), its startup clock (the 10-minute ceiling now counts from here) and quit's drain. Every wait after runs outside the lock, raced with that signal, and every write is a commit that first re-checks the start is still the session's. A start that loses the check is late: its spawn is closed through the post-acquisition failure path and nothing is handed to it. - The delivery loop's prepare returns a start handle; the loop awaits the start outside, then re-prepares. Handover writes the pending row and begins the dispatch abort handle in one commit, calls the provider outside after re-checking the child, and resolves in a second commit. - Ready operations claim a start instead of running one in their step. - A client attach runs the same steps under one serialize, as before. - An exit reported between spawn and publish is held for the publish commit, which ends the child it indexes. - An unproven close still fails only what was queued when its start was asked for. * Test that a send is accepted at every wait an agent start makes One whole-flow test holds the start at each wait in turn (closing-child join, reconcile probe, recovery re-probe, resolver, spawn, sink drain, open, started recovery, pending row, provider call): a second send is accepted at once, a Stop is recorded without waiting and nothing reaches the provider, and what was sent goes to the published child once, in order. Also quit before spawn, concurrent sends, a lease left recovering, and an idle sweep racing a send. * Bound each startup step on its own and drop the startup silence rule A start no longer ends after 60 s without output: output is not protocol progress. Each protocol step now has its own bound where its handshake lives, and one 10-minute ceiling from registration stays as the outer bound. - Codex: initialize 15 s and thread start/resume on the connection's 30 s default again, instead of the startup ceiling. - ACP: initialize 60 s, authenticate 120 s, session new/load/resume/fork 300 s; turns stay unbounded and option writes keep their caller's bound. - Claude: the initialize answer has a 60 s bound; past it the open fails with the ordinary startup-failure words and the host ends the child. - The onOutput plumbing that only fed the silence rule is removed. * Count Pi ready once it reports its state Pi's start no longer waits for its model listing and slash commands. The open returns after get_state and the unknown-provider check; the listing then arrives as an options report (with the catalog listing the start resolved) and the commands as a later publish. An empty model list no longer fails the start as signed out: a signed-out Pi runs the unknown provider, which still fails it, and a prompt it cannot run is refused with the sign-in words by the existing prompt path. get_state keeps the connection's 30 s request bound instead of the startup ceiling. * Test that model and effort picked while a chat is created ride its first message A slow host's create reply arrives after the user picked a model and effort. The picker's view layers the launch's held picks over its seed and the host's saved selection, and every send carries what the picker shows, so the first message carries the picks whether the host's status names its selection or not, and while the last pick is still on its way to the host. * Start a phone's new empty chat on its first message The phone's new chat is an agent.launch with no prompt, which always created the chat with its agent started, while desktop chats start on the first message. The phone now advertises the create-without-start capability, and a launch with no prompt from a caller advertising it creates the chat at rest, keyed in the ledger by its create intent as a client's create is. Launches with a prompt, the CLI (which advertises nothing), orchestration workers and ensure still start at once; an older host ignores the capability and stays eager. * Take main's lockfile; the branch changes no dependencies * Restore main's exact text in files the merge only reformatted The pre-commit formatter rewrote 37 of main's files staged by the merge, though this branch never changed them, pushing two past the line limit. Committed without the hook because the hook would reformat main's text again; CI lints the result. * Fit the startup tests to the owned-spawn, host-awaited-open contract Test doubles gain the mandatory open and an acquisition generation, the removed started event gives way to the rig's proveStart, and two literal types are tightened for CI's typecheck. A Stop during a held start writes no host note on this branch: the taken-back send draws its own stopped-before-start row, which the startup-attempt test now asserts. * Fit the remaining CI checks to the startup limits and current releases - Run agent-launch-at-rest.test.ts in the SQLite Node runtime project. - Pin the send-options cross-version case to v1.4.228, the first published release with agent-session.send-options.v1; v1.4.226 was never pushed. - Load the baseline's isStructuredTab from src/shared (moved in v1.4.228), falling back to the renderer path for older baselines. - End the never-proven start with a short startup ceiling: the silence limit it relied on is gone, and the ceiling counts from registration. * Read the option stamp from the chat's one host-execution selection * fix: preserve startup authority and accepted agent choices * Retain dispatch authority and reconcile accepted options * fix(native-chat): preserve accepted choices and cancellation authority * Restore established client selection flow * Preserve provider output observation and typed startup fixtures * fix: preserve initial Claude launch choices and effort readback * test: complete Claude catalog session fixture * Preserve incoming main files after merge formatting * Preserve compact handover through startup merge * Type instant startup fixtures before their open is supplied * Keep browser sign-in under its existing expiry owner * Finish the main merge: retired-model display, start rewind cut, new tests A model the Claude CLI retired is reported again, so the picker shows what the child runs and the next send moves the chat off it. The start reads a rewind in doubt against the handle it answered with, the record its commit then applies to. Main's tests follow the start's open: the admission fixture holds a real turn, and a sign-in close during setup fails the open. * Type the start's rewind record as the store returns it * Type the Sleep test's held open as the provider open it replaces * Sleep counts a chat stopped once the start it aborted leaves it at rest A start runs outside the chat's lane, so Sleep's step can run before the start it aborted has settled; it then re-reads the chat once that start is done, instead of reporting it unverified. * Keep Sleep's start-abort bookkeeping beside the rest of Sleep's stop * Keep the listed background Stop lifetime cases The two #29905 cases lived in the turns test this branch removed. They pin that a listed Stop never ends a helper lifetime it did not capture: a same-id restart during acceptance, or a refused helper that appears mid Stop-all, which must not close the agent. * Reconcile test registries and queued provider work accessor * Update CI scope fixtures after removed connection test * Bound native chat Stop protocol steps and omit empty startup feedback * Supply current session fence in empty-start Stop test * Align goal cancellation test with accepted-message feedback * test(native-chat): cover distinct repeated Stop through Codex adapter * fix(native-chat): save dispatch proof before Stop closes the child * ci: retrigger checks for Stop dispatch repair * Keep accepted Stop effects through dispatch settlement * Capture earlier Stop evidence before writing request feedback * Preserve named Stop runtime effects across turn advancement * Combine projection imports in named Stop regression |
||
|
|
82534d0183 | refactor: colocate remote runtime passthrough socket (#33009) | ||
|
|
de37a4cbe8 |
fix(workspace-board): close the board when a right-sidebar tab is clicked (#30063)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com> Co-authored-by: Neil <neil@stably.ai> |
||
|
|
b6d1f63e01 |
fix(editor): resume media previews from their last playback position (#32480)
* fix(editor): resume media previews from their last playback position Switching tabs unmounts the editor surface, so the media element was recreated and every video or audio preview restarted from zero. Record the playback time on teardown and seek back once metadata loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(editor): resume media previews from their last playback position Switching tabs unmounts the editor surface, so the media element was recreated and every video or audio preview restarted from zero. Record the playback time on teardown and seek back once metadata loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(editor): verify media remount playback and cleanup --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
8a63b4344a |
fix(terminal): focus the neighboring pane when a split pane closes (#32238)
* fix(terminal): focus the neighboring pane when a split pane closes * test(terminal): cover focus after closing a split pane in the app * fix(terminal): focus the neighboring pane when a split pane closes * test(terminal): cover focus after closing a split pane in the app * test(terminal): keep pane-close parity exclusions evidence based --------- Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
60c58ea7e2 |
fix(runtime): collapse mirrored Markdown aliases with shared backing identity (#31984)
* fix(runtime): collapse mirrored editor tab variants that differ only by environment A snapshot that already lists one file under several per-environment host tab ids (editor:<worktree>:<env>:<path> or the bare path) fanned out into one mirrored tab per variant. Keep one minted variant per (worktree, mode, path) when building mirrored editor tabs; tabs with opaque ids (deliberate splits) are untouched. Protects receivers from older peers that still republish mirrored files and from snapshots that are already polluted. * fix(runtime): match encoded editor tab ids and keep the navigation target when collapsing variants Host tab ids come from buildOwnedEditorFileId, which URL-encodes the worktree, environment and path, so the raw-path predicate never matched a real variant. Each candidate is now checked by rebuilding it with buildOwnedEditorFileId, and the tab a pending focus/follow intent targets wins its bucket ahead of an active one. Tests build their ids the same way. * fix(runtime): preserve editor identity while collapsing aliases * test(runtime): keep headless mirror regression across project boundaries --------- Co-authored-by: Neil <neil@stably.ai> |
||
|
|
8ef0e99a7c |
fix(ui): use focusable tooltip menu triggers (#9595)
* fix(ui): use focusable tooltip menu triggers * test(tooltips): give each menu surface its own remount check Keep all hover, focus, description, keyboard, pointer and two-remount assertions while avoiding a shared 120-second budget across three surfaces. The failed CI trace passed five full exercises before global teardown. --------- Co-authored-by: Yuris Auzins <zuz666@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com> |
||
|
|
359fec1a09 |
fix(terminal): a file link opens in the workspace it was clicked in (#32320)
* fix(terminal): a file link opens in the workspace it was clicked in A terminal or chat link to a file another workspace on the same host holds used to switch to that workspace. It now opens as a plain read-only file in the workspace it was clicked in (local and WSL read on this computer; paired and managed servers read through the owning workspace's scope), with a toast naming the owner and an Open there action, and a Read-only badge in the editor header. HTML on server hosts, server workspaces the client does not know, directories, shift-click download and direct-SSH workspaces keep the switch. The view is watched with its owner through one derived scope (lib/open-file-owner-scope.ts); phones skip plain file tabs they cannot read by workspace-relative path. Fixes #32158 * fix(terminal): a fresh id per other-workspace hint, and owner lookups cached per route inputs * fix(terminal): only this computer's files open in place; SSH and server links keep opening in their workspace Servers read files only by workspace-relative path, so a read-only view of another workspace's server file needed owner rerouting in every read path. Paired and managed servers keep today's switch; the loader moves any restored read-only server tab of another workspace's file there as before. |
||
|
|
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> |
||
|
|
c84817e8f9 |
fix(native-chat): preserve accepted command outcomes across agent startup (#31605)
* fix(native-chat): a goal set while the agent rests clears the composer Setting a goal starts the agent when none is running, which moves the session's runtime fence before the host answers. The composer dropped that successful answer as stale, so the objective and Goal chip stayed. Keep the reply for goal changes as for the other writes that may start the agent. * fix(native-chat): settle each command independently of runtime changes * fix(native-chat): scope rewind timeout feedback to its issuing pane * test: use checked native chat command fixtures * test: complete native chat presentation fixtures * fix(native-chat): admit Stop before capability discovery * test(native-chat): consolidate overlapping startup coverage * test(native-chat): preserve IME composition on goal acceptance * fix(native-chat): capture each action before async preparation * fix(native-chat): preserve shared Stop failure reporting * test(native-chat): cover already-sent queue notices after disposal |
||
|
|
d49ad60ead |
feat(chat): add terminal-style pane header actions (#28968)
* Transfer keyboard focus when a sibling chat pane opens * fix(native-chat): hand focus to newly opened chat composer * fix(native-chat): keep tab bar controller within line limit * fix(chat): split into a fresh conversation beside the current chat * test(chat): declare the split fixture store without an assertion * fix(chat): route the split to the source conversation host * test(chat): keep the split regression focused on the launch contract * test(chat): give the command palette split fixture its conversation agent * test(chat): seed the legacy command fixture without expanding its asserted shape * test(chat): isolate pane identity commands from the split launch boundary * Prevent chat splits from starting a terminal on host refusal * Cancel structured launches when their destination pane closes * Model live destination groups in agent launch test fixture * Capture split chat focus intent at the user action * feat(chat): add the terminal pane hover action cluster * fix(chat): keep pane controls above the conversation surface * refactor(chat): share the tab resume launch hook and format pane controls * style(chat): document reuse of the terminal header button styles * refactor(ui): share the pane title button variants between terminals and chats * fix(chat): keep header actions scoped to the displayed chat * fix(chat): abandon Resume when its destination pane closes * test(chat): declare destination fixtures without type assertions * fix(chat): keep Resume visible when its source pane closes * fix(chat): reuse tab menu Resume placement unchanged * test: fix Resume regression harness setup * fix(chat): hand input to the split pane during admission * fix(chat): Split opens a fresh chat beside the current one Split Right/Down on a native chat used to move the chat into a new pane. It now opens an empty pane beside the chat and launches a new chat of the same agent on the same host into it through the New Chat launch, so the new composer takes focus at once. A launch that never opens a tab closes the empty pane. Drops the earlier admission and focus-transfer changes that main now covers. * fix(chat): Split palette search words describe a new chat, not a move * fix: remove misleading chat move shortcut hints * fix(chat): a new chat stays in its pane until its host lists it A host tab list built before a chat's create landed can arrive after the create reply. Once the reply settled the launch, nothing protected the new tab, so that older list dropped it: Split's new pane collapsed and focus moved to another chat, and the next list re-added the chat elsewhere. Keep a small in-memory record for a chat this client created that no host list has shown yet. While it exists, a list that omits the chat keeps its tab. The record ends when an accepted list from that host includes the chat, when the tab closes, on workspace teardown, or 30 seconds after the reply, when the worktree is re-read from its host (including an unchanged version) so a chat the host really closed is dropped then. The launch lifecycle, status, persistence and settlement are unchanged. * test(chat): type the faked create replies without an assertion * Keep new chat protection tied to accepted host lists * Update paired chat close regression to current suppression API * fix(chat): Portuguese Split palette words describe a new chat * test(chat): use the destination group option in split fixture * Avoid History projection in native chat header and currentness checks * fix(native-chat): keep Find below pane controls * fix(ui): keep noninteractive tooltip layers pointer-transparent * Mount native chat actions only for shown panes * Use checked property access in header instrumentation * Read Continue eligibility records through workspace selectors * Cover native header Find clearance and tooltip click passage --------- Co-authored-by: Orca Worker <orca-worker@localhost> |
||
|
|
291e2e807e | feat(browser): open links beside their pane, focus link tabs, honor window.close() (#31844) | ||
|
|
aeb039938f |
refactor(jira): colocate shared contracts and capabilities (#32927)
* refactor(jira): colocate shared contracts and capabilities * test(jira): validate title width against panel budget |
||
|
|
6ed9382c05 |
Allow SSH migration recovery without stopping preserved relay terminals (#32914)
* fix(ssh): preserve running relay panes during migration recovery * fix(ssh): wire pane eligibility through existing relay seams |
||
|
|
4c7991509f | feat(browser): say why a page failed to load and reload it when it can (#31847) | ||
|
|
13ab5239f5 |
fix(terminal): never present a half-built synchronized-output frame on reveal or recovery (#32281)
Settled reveal and every other atlas recovery forced the current xterm buffer onto the screen even while a TUI held DEC 2026 synchronized output. Codex erases rows inside each held redraw, so a reveal mid-redraw showed a blank pane for a frame (#32198). Viewport presents now have one mode: they keep the last coherent canvas and let xterm present when the TUI closes the frame, bounded by xterm's 1s synchronized-output safety timeout. Structural replay keeps its retained frame up until an open synchronized frame closes, bounded by its existing hold cap. Co-authored-by: YEONGUK LEE <contact@abruption.dev> |
||
|
|
551108aea8 | test: finish fixture work before unit-test cleanup (#32285) | ||
|
|
f9e883a4bf |
Show activity status with worktree-style counts (#26443)
* Show prioritized agent counts on the activity bell * Move activity badge into the corner and shrink its count * Use a fixed side column for activity status and counts * Fix activity count spacing and status alignment * Use worktree-style superscript counts for activity * Keep activity count positions and update only number styling * Keep idle layout fixtures out of active agent status updates |
||
|
|
47484b13d6 |
fix(native-chat): keep queued messages in the chat's own record, so a refused one can't be skipped (#30418)
* fix(native-chat): a refused queued message still blocks the queue when its card write fails Every reader of a queued card goes through one derived view of the journal's dispatch facts, so a card whose message was refused reads as returned even when its card-table update and heal both failed. One decision and one writer store the derived state for every action. * refactor(native-chat): keep each queued message in the chat's journal, not a second table A queued message lived in its own SQL table, and a hook that may be skipped copied each refusal from the journal into it. When that write failed, a refused card vanished: the cards behind it sent, and its Send and Delete did nothing. Each queued message is now a `queue` row in the chat's journal beside the submission that hands it off and the dispatch row that answers it. The reducer folds the card from those rows, so a refusal returns the card and holds the cards behind it with nothing written, and replay reads the same. Every action writes its own rows in one transaction. Cards an earlier build stored are read through the same classification until they move: each action moves its own card, and a bulk import runs once per open. A new epoch restates every card still owed an answer in the transaction that deletes the old rows. The settlement hook and its savepoint, the open-time heal, the drain's heal and the derived-card view are gone. The published card list is unchanged. * test(native-chat): adapt the queue tests to cards kept in the journal Cards are written through the journal, so tests queue them with their body alone and hand them off under their real fingerprint. Tests of the removed settlement hook, heal and SQL store are deleted; the text-update spec now covers the edit decision alone, and a test of an earlier build's stored holds seeds the legacy table and reads the imported cards. Rolling over an epoch now keeps a card whose hand-off was never handed over, so that test checks the retried id never sends rather than that nothing sends. * test(native-chat): a refused card holds the queue through rewind, a new epoch and a restart One whole-flow test through the host: the refusal writes no card row, a rewind that retires the refused hand-off and a rolled-over epoch keep the card returned, a restart and Resume send nothing behind it, its own Send refused again still holds the queue, and its Delete releases the next card in order. * test(native-chat): an earlier build's queued cards move into the journal once, each read the same One mixed-import test through the host. The first open's import fails and moves nothing, yet every card reads as it will; Delete and Send each move only their own card. The next open moves the rest in one transaction, writing a refusal only where its hand-off is gone, nothing for answered cards, and leaving an unreadable row on disk. A later command turn spends a moved command card, a third open writes nothing, an old Delete replays from its receipt, and a chat with only answered legacy rows clears them in a delete-only transaction. * test(native-chat): adapt typechecked tests to cards kept in the journal The row-kind table lists a queue row, the clear and legacy-import tests match the current signatures, and the downgrade test keeps only the leftover-send case: a queued card is now a newer-version journal row an older build keeps read-only, and users don't downgrade. * refactor(native-chat): drop the journal row writer's unused transaction hook No caller passes one since the queue's savepoint bookkeeping left. * test(native-chat): downgrade probes write rows at the version older builds read The highest version this build reads is now 4, the queue row's. A newer build's row in these probes is written at version 3 so the older build meets its kind, not a newer version, and the Stop-event probe hands the older build the rows as stored, since a read raises each row's version. * test(native-chat): unit tests follow the queue row's version 4 The legacy-import test opens real SQLite, so it runs in the Node runtime project. The newest version this build reads is now 4, and a read raises a row's version to it, so the turn-lifecycle test no longer pins the in-memory version (the written one stays pinned by journalRowSchemaVersion). * fix(native-chat): one batch echoing two refused queued messages spends both The per-row delivery check returned only the first waiting card an echo proved delivered, so when one settlement carried echoes for two cards the second stayed waiting and was sent again. It now returns every card the row proves delivered, and the fold withdraws each; replay folds the same. * fix(native-chat): a rewind moves an earlier build's cards into the journal first While the open's import had failed, an earlier build's card that read as refused through its hand-off lost that reading when an in-place rewind retired the hand-off: it disappeared and the card behind it sent. The rewind now adopts every legacy card ahead of its tombstones and deletes their rows in its own transaction, as an epoch replacement already does, so the fold latches each card's reading before its hand-off is gone. * fix(native-chat): keep an earlier build's card whose hand-off is still unconfirmed The import dropped a legacy card handed off as a send whose delivery was unknown, though the fold keeps such a card linked and a later refusal returns it. The import now keeps every card whose hand-off a later row can still answer, through the same check the reducer folds dispatch rows by. * fix(native-chat): one delivery echo withdraws at most one queued card One provider echo proves one delivery, yet it withdrew every waiting card with the same text whose hand-off was refused, so a second identical card was lost. The fold now pairs each echo with one card: on its own row the earliest-positioned match no other echo of that row took, and for a card that waits again the earliest applied echo no card has taken. Paired echoes live in the fold's state, so live and replay pair the same; readers of cards not yet imported work on a copy. * fix(native-chat): a queued message handed to the agent is sent again only on proof it never got it Pressing Stop after Orca handed a queued or steered message to the agent, before the agent confirmed it, put the card back in the queue, and Resume or the next send sent it again. A check matching the agent's echo of the text was the only guard against a double send, and it kept trading a resend for a lost card. Whether the card waited again was decided by the rejection's category, not by proof the agent never got the message. A refused hand-off now waits again only where Orca proves the agent never received it: the host still held it, or whoever settled it recorded that. A failure still returns the card and holds the cards behind it. Anything else may have reached the agent, so the card is spent and its message reads as sent, derived on every fold from the stored rejection: with the turn it was answered into, as that turn's unconfirmed message where it was handed over; withdrawn before any turn opened, as a person's own send a Stop took back. The reopen mark ignores a recovered doubt, and the restart reconciler no longer judges a queued hand-off unsent from what history lacks. The echo text matching and its pairing are deleted. A close or quit while the agent starts settles sends with the unrun hold. The host hands nothing to a child before its start answers (structured-agent-session-delivery-loop.ts: a handover needs phase ready), so such a send went to an earlier child and may have run: it now reads as sent instead of waiting again. * fix(native-chat): Claude's report that it removed a queued message unrun lets the card wait again Claude says when it removed a send without running it: the interrupt receipt's cancelled list, or a command_lifecycle cancelled, discarded or refused frame for a command that never started. Every one settles through rejectClaudeDispatchWaiters, which now marks the settlement unrun. For a queued card's hand-off the host records that as proof the agent never got it, so the card waits again under the queue's pause. A direct send keeps the plain refusal: there the proof would owe it as stopped history. * fix(native-chat): an earlier build's card stopped after hand-over follows the same rule on upgrade Earlier builds put a card back to waiting after any refusal without a verdict and cleared its link, so a card the agent may already have would be sent again after the upgrade. The card's own hand-offs in the journal decide now: unchanged and last sent as one that may have reached the agent, it is spent on that hand-off and its message reads as sent; edited or sent again since, it is held for its own Send under a host-only marker that publishes as a plain hold; with no hand-off in doubt, including one that never left Orca, it waits as before. The import writes a card spent on a hand-off in doubt, so its message shows. * fix(native-chat): read a queued row's hand-off before the lookup that uses it The lookup's callback lost the narrowing of the row's change, which the mobile test typecheck rejects. * test(native-chat): a card steered into a turn a Stop ends stays sent in the Stop tests Three tests pinned the old rule, that a card handed into a running turn comes back to waiting when a Stop ends it. Under the new rule nothing proves the agent never got it, so the card is spent: with the turn named, its message reads as that turn's unconfirmed one; without, as a send the Stop took back. Neither is ever sent again. * fix(native-chat): a queued message stopped before Orca handed it to the agent waits again A Stop that aborts a hand-off after its row is written but before the agent call now records the withdrawal as never reaching the agent, for queued hand-offs only, so plan D's rule returns the card to waiting under the Stop's pause instead of spending it. Direct sends keep the plain withdrawal (the flag would owe them as stopped history). The agent's own refusal while aborted is kept rather than overwritten, and an abort thrown during the call stays in doubt. * fix(native-chat): a queued message stopped while the agent prepares it waits again The abort signal reaches only an adapter's preparation (its options, the previous turn opening), never its write, so the signal's own reason thrown back is the adapter saying nothing was written. The handover now reads that, for queued hand-offs only, as never reaching the agent, so the card waits again under the Stop's pause. Claude no longer wraps that abort as an option refusal, which had returned a stopped card as a refused one blocking the queue. A real refusal keeps its cause and its barrier; any other throw stays in doubt and is never resent. * test(native-chat): the usage-limit Continue backlog case writes its card through the journal queue Main's #30396 wrote its backlog card with the stored fingerprint and host-instance stamp the journal queue derives instead; the card the Continue must yield to is the same waiting card. * test(claude): the real-CLI queued Stop expects the unrun mark Claude's cancelled-unrun receipt carries * fix(native-chat): retain spent legacy messages when import fails |
||
|
|
a3512bcd47 | perf(renderer): scope runtime connection and parking subscriptions (#32199) | ||
|
|
ab1563e6f3 |
fix(cli): negotiate agent launches with paired runtimes (#32029)
* fix(cli): negotiate agent launches with paired runtimes * refactor(cli): share paired client capability list across request paths Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
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> |