* fix(wsl): name an explicit Windows cwd for wsl.exe spawns
Removing the worktree Orca was launched from broke every wsl.exe spawn for
the rest of the session. The WSL command builders passed `cwd: undefined`
meaning "the directory is inside the command" -- but CreateProcessW reads
NULL as "inherit the parent's", and the parent's was a \\wsl.localhost path
Linux had just deleted.
Fixes#16463
* fix(wsl): name the spawn directory at the six remaining wsl.exe sites
The first commit fixed the WSL command builders. Six spawn sites were left
inheriting the process cwd, which is the same deletable `\\wsl.localhost`
worktree: `wsl-availability` (both probes), the WSL filesystem watcher, the
agent-hook relay launch, the UNC delete, and the local worktree filesystem.
`wsl-availability` is the one that matters most, and it turns the bug into a
latching false negative. `isRetryableWslProbeFailure` returns false for ENOENT,
so a spawn that failed only because the inherited cwd was gone is cached as
"WSL is not installed" on the 10-minute definitive TTL with exponential
backoff up to 30 minutes. Git keeps working and Orca reports WSL unavailable --
worse than the bug being fixed.
ENOENT stays non-retryable. It is answer-shaped for the reason it is meant to
be -- wsl.exe is not on PATH -- and naming the directory is what removes the
one cause that was not. Making it retryable would instead re-probe every
non-WSL Windows machine on the short window, and would leave the false ENOENT
in place for the other five sites, which have no cache to correct.
Three of these are also on the `runWslProcess` W3 migration allowlist; this is
the interim until they move, and matches what #17837 does inside the runner.
On Windows, seven production call sites reach listWslDistrosAsync and on a cold
cache each spawned its own `wsl.exe --list --quiet` (5s timeout each): the
wsl:listDistros IPC behind the renderer capability read, the host.wsl.listDistros
RPC, the skill-install IPC, CLI registration reconciliation, the hook relay deps,
the kimi runtime home, plus relay preflight in the relay process. Concurrent
callers in one process now share one spawn.
Joining happens ahead of the negative cache, which also fixes a stranding bug: a
synchronous listWslDistros() landing an empty result mid-probe arms the 15s retry
window, and later async callers read that [] even though the pending probe is
about to see a distro that just finished provisioning. The non-empty-cache
short-circuit sits ahead of the join so a list already found synchronously is
still returned without waiting; that is main's existing behaviour preserved, not
a new fast path.
The shared promise cannot reject -- `catch` sits ahead of the stored promise, so
joiners get the same fail-safe [] the old per-caller catch returned -- and the
slot is cleared on settle, by the owning probe only.
wsl-directory-probe-command.ts is a verbatim move of the guest directory-probe
marker protocol and its parser out of wsl.ts, for oxlint max-lines headroom:
inlining it back makes wsl.ts 306 effective lines against a cap of 300. It takes
WslUncPathInfo from ../shared/wsl-paths -- the actual type of every value passed
at both call sites -- so it does not import from wsl.ts. _resetWslCachesForTests
and _setWslCachesForTests now share one resetWslDistroListState() instead of
repeating the same six assignments.
Per-platform delta:
- WSL on Windows: fewer wsl.exe spawns under startup fan-out, and a distro
provisioned while a probe is pending is no longer hidden for the retry window.
- Native Windows without WSL: no behavioural change. The empty/failure retry
windows, their backoff and the cache sequence guard are unchanged; N concurrent
callers now cost one failed spawn instead of N.
- macOS, Linux, folder workspaces: no change. Both new early returns are
unreachable off win32.
- SSH remote: no change for macOS/Linux hosts; a remote Windows host gets the
Windows behaviour in its own process. No wire change -- host.wsl.listDistros
keeps its string[] shape and its [] failure value.
- Relay: same single-flight inside the relay process. It stays per-process; the
relay and main process still probe independently, as before.
Costs: a never-settling execFileUtf8 now pins the shared slot for the process
lifetime rather than only its own callers -- transient-to-permanent, not identical
exposure. And a joiner inherits the first probe's failure instead of making an
independent attempt.
* fix(worktrees): preserve user workspace names across branch changes
* test(worktrees): cover pinned rename metadata
* fix(workspaces): address display-name review edge cases
* fix(workspaces): keep automatic names fresh across refreshes
* fix(workspaces): preserve legacy CLI labels
* fix(workspaces): preserve display-name provenance across hosts
* fix(workspaces): honor legacy display-name provenance
* fix(workspaces): fence display-name refresh races
* fix(workspaces): accept peer renames from provenance-less hosts
The old-host preserve fence kept a pinned local label on every refresh,
which also suppressed a legitimate rename another client persisted
through the same host until app restart. Narrow it to labels the host
re-derived itself (branch short name, or path basename when detached);
any other changed label in a mode-less response is explicit meta a peer
wrote there. Stale prior-label responses stay covered by the downstream
staleness fence, in-flight writes by the pending fence.
* refactor(workspaces): unify display-name pin derivation
Three call sites (renderer optimistic update, local IPC updateMeta
handler, remote worktree.set handler) each restated the same formula;
a future edit to one would silently skew provenance between paths.
* fix(wsl): scan sessions only in running distros
* test(ai-vault): pin WSL discovery platform
* fix(wsl): suspend transcript watchers for stopped distros
* test(wsl): pin transcript scan gate platform
* fix(wsl): settle stopped transcript loading
* fix(wsl): add last-known-good fallback and backoff to running-distro discovery
listRunningWslDistrosAsync failed closed on any probe error (timeout, ENOENT,
wsl.exe hiccup), indistinguishable from "no distros running". A 2s poll
(wsl-transcript-running-observer.ts) calls it indefinitely while any WSL
transcript tab is open, so a persistently broken wsl.exe silently made every
WSL session vanish app-wide with no way to tell "discovery broken" from
"distro stopped", and re-spawned wsl.exe every 2s forever.
Extract a dedicated cache/backoff module (wsl-running-distro-cache.ts,
mirroring the sibling machinery already in wsl.ts for the full distro list)
so a probe failure falls back to the last-known-good running-distro list and
backs off further probes, while a genuine empty result (no distros running)
stays authoritative. Add a consumer-level test simulating a sustained wsl.exe
outage across a live transcript-watcher polling session, asserting the
observer keeps reporting "running" and that real wsl.exe spawns stay bounded.
* fix(build): list the new WSL cache module in the web typecheck project
config/tsconfig.tc.web.json enumerates its files explicitly, so a new
module imported by wsl.ts fails the full typecheck with TS6307 until it
is listed. pnpm tc:node passes without it, which is how this got missed.
src/main/wsl.ts(13,8): error TS6307: File 'src/main/wsl-running-distro-cache.ts'
is not listed within the file list of project 'config/tsconfig.tc.web.json'.
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
`src/preload/api-types.ts` was 3,752 raw lines (3,533 counted, 11.8x the
300-line budget) behind an `eslint-disable max-lines`. Almost all of it was a
single `PreloadApi` object type whose ~83 namespace properties were declared
inline, so any IPC surface change meant editing one 2,600-line type.
Give each namespace a named type in its own module under `src/preload/api/`
(`pty-api.ts`, `filesystem-api.ts`, `github-pull-request-api.ts`, ...) and
recompose `PreloadApi` from those names. `api-types.ts` keeps the `declare
global` Window augmentation and re-exports every moved name, so all 52 import
sites are untouched.
Two shapes needed care to stay type-identical rather than merely compatible:
- Three keys (`gh`, `git`, `ui`) are composed from two modules each. A plain
intersection is NOT identical to the original flat object literal, so those
use a `Merged<T>` mapped type; a negative control confirmed that dropping it
fails the parity assertion.
- Keys whose module groups several namespaces use indexed access
(`fs: FilesystemApi['fs']`) to preserve exact identity and source order.
`config/tsconfig.web.json` and `tsconfig.tc.web.json` enumerate files by path,
so they need `src/preload/api/**/*` alongside the existing `api-types.ts` seed
or the web projects fail TS6307.
Verified by exact type identity, not assignability: 41 assertions of the form
`Equals<Now.X, Before.X>` against a frozen pre-split snapshot, covering every
exported name, plus a per-key pass over all 83 `PreloadApi` keys. All three
projects typecheck clean with those assertions active.
Verification note: these tsconfigs are `composite: true`, and `tsc --noEmit`
will reuse a stale `.tsbuildinfo` and report clean for a state that genuinely
fails. Every result above was produced after deleting the buildinfo, including
a negative control confirming the gate still fails on deliberate drift.
Drops the `max-lines` bypass and its baseline entry (ratchet 346 -> 345).
* perf(windows): stop the capability poll respawning blocking wsl.exe probes
#11295 added a 30s renderer interval to `useWindowsTerminalCapabilities` whose
early-return only fires when WSL is available with at least one distro, so on the
common Windows host (no WSL) it re-ran a full capability read forever. Each read
IPCs four probes whose main-process handlers were synchronous `execFileSync` calls
to wsl.exe/pwsh.exe, blocking the Electron main event loop for up to 5s a time.
The un-latching intent is kept: a host that answers "no WSL" is still re-checked,
now on an exponential backoff (30s, +60s, +120s) that parks once the answer stops
moving, re-arms on window focus, is shared by all consumers of an owner key, and
stops entirely when the last consumer unmounts. The wsl/pwsh IPC handlers now use
async twins that share the existing caches and back off identically.
* fix(windows): classify async wsl/pwsh probe failures with the execFile error shape
The async twins feed `execFile` callback errors into classifiers written for
`execFileSync`: a non-zero exit lands on `error.code` as a number rather than
`error.status`, and a timeout is a SIGTERM kill rather than ETIMEDOUT.
So a Windows host without WSL (wsl.exe ships in System32, so it exits non-zero
instead of ENOENT) was cached as retryable, shrinking the shared window from
10min to 45s and making the still-sync callers re-pay their blocking spawn ~13x
more often; and a pwsh cold start past 5s cached "pwsh missing" for 30s,
demoting the user's PowerShell 7 preference — the exact case the ETIMEDOUT
branch exists to prevent.
Also drops a literal NUL byte from the new re-probe module's signature
separator, which made the file binary to git, and seeds `lastProbeAt` at
registration so focus churn right after mount cannot defer the first re-probe
indefinitely.
Co-authored-by: Orca <help@stably.ai>
* perf(windows): route relay host-capability probes through the async wsl/pwsh twins
A paired web/mobile client resolves `useWindowsTerminalCapabilities` to a local
target (TabBar's `isWebClient` gate, and `useSettingsNavigationMetadata` forces
`{kind:'local'}`), so the new re-probe arms there too. But `window.api.wsl/pwsh`
on a web client is not the ipc/app.ts channel — it is `host.wsl.*`/`host.pwsh.*`
over the runtime RPC, which still ran the sync probes and blocked the desktop
main event loop on `execFileSync('wsl.exe' | 'pwsh.exe')` for up to 5s per call.
Switch those handlers and the relay preflight capability probe to the async
twins added here; they share the same caches, dedupe and backoff, so remote
callers see no behavior change.
* fix(windows): harden async capability reprobes
* fix(windows): dedupe PowerShell shell probes
---------
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(serve): recognize CLI-form serve args on the Electron process
When the binary is launched as `… serve --port …` without the CLI rewrite
that injects `--serve`, normalize argv so isServeMode, headless GPU flags,
and serve option parsing all engage.
Preserves existing `--serve*` flag behavior for the CLI-spawned path.
Fixes#12677
* fix(serve): treat only CLI subcommand position as serve
Parse bare `serve` as the first positional token after flags/values so an
option value named `serve` cannot enable headless mode.
Addresses CodeRabbit on #12818.
* fix(serve): keep CLI redirects ahead of the serve argv rewrite
Rewriting argv before maybeRedirectAppImageCliLaunch replaced the `serve`
positional with `--serve`, so the redirect's command-name lookup saw a port
number and bailed — dropping AppImage serve launches out of the CLI path.
Also translate `--port=6768` (the CLI accepts it, getServeOptions only reads
the next token) and the mixed `--serve --port` form, so a security-shaped flag
like `--no-pairing` can no longer read as accepted while pairing stays on.
Map lookups replace `in` on object literals, which turned a stray `serve
toString` positional into a function spliced onto argv.
* fix(serve): close the CLI-form serve gaps found in review
second-instance: shouldActivateDesktopForSecondInstance matched only `--serve`,
so a duplicate `<binary> serve --port …` — the ExecStart shape documented in
docs/reference/headless-linux-server.md — promoted the live headless server to a
desktop window, un-fixing #11935 on exactly the launch shape this PR legitimizes.
findServeSubcommandIndex consumed a flag's value unconditionally while the
rewrite consumed it only when the next token was not flag-shaped. The two could
disagree and swallow the `serve` token, leaving `--serve` uninjected: #12677
again in a new shape (`--port --port serve`, `--port -- serve`). Both scans now
share one definition of value consumption.
`<binary> serve --help` / `serve help` bound a network-exposed runtime server
with pairing on and printed nothing; the AppImage redirect already routes those
three tokens to the CLI, so refuse them here too.
`--no-pairing=false` translated to `--serve-no-pairing` with the value dropped,
disabling pairing for an operator who asked for the opposite. The CLI reads its
serve booleans as `flags.get(name) === true`, so a boolean is now translated only
in its bare form and the `=` form rides through as the CLI treats it.
Tests: spec-derived parity between src/cli/specs/serve.ts and the rewrite,
covering both ends of the contract (serveOrcaApp and getServeOptions); a
source-text lock on the index.ts redirect/rewrite ordering, which reverted
silently green before; an exhaustive self-consistency property test; and the
real GUI launch argv shapes that must never enter serve mode.
---------
Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
STA-1716 reported that a packaged `orca serve` could become the single-instance owner after the desktop app exits, leaving Dock/Finder unable to restore a window — and that forcing a reopen made the headless process hydrate a renderer that interrupted and DUPLICATED live agent sessions.
Verification against main found every criterion already fixed (#8646 for desktop promotion and the fail-closed CLI, #12212 for duplicate serve activation, #12574 + #9729 for the resume/ownership guards). The genuine gap was criterion 6: the ticket's own automated regression never existed. An existing reliability gate asserted PTY identity survives promotion, but nothing asserted what the incident was actually about — how many agents the promoted renderer resumes.
This adds that coverage: a unit/service-level journey that drives the real single-instance lock, activation gate, settle and focus paths, then runs the real resume logic against a store seeded as a renderer freshly mounted inside the serve process, asserting zero duplicate resumes.
`settleServeDesktopActivation` moved from `index.ts` into its own module with identical semantics, so the test drives the real decision rather than re-implementing it — the earlier repro had to mirror that logic locally, which is the "test passes without running the scenario" failure mode.
Proven to be a real oracle: breaking each guard individually turns it red, and reverting the pre-#12574 pane form reproduces the incident exactly (two duplicate `codex resume` tabs).
* fix(checks): stop skipped and manual checks reporting as failures
Route every check-classification surface through one shared helper so
desktop renderer, desktop main and mobile agree on the same verdict.
- GitLab `manual` jobs and pipelines are neutral again, not action_required/failure
- `skipped` counts as passed everywhere, including mobile
- a neutral check no longer demotes a summary that has passing checks
* fix(checks): move the check-classification parity test into the renderer project
The parity table lived in src/shared but imported a renderer module, and both
config/tsconfig.node.json and config/tsconfig.cli.json are composite projects
that include src/shared without that renderer path, so `pnpm typecheck` failed
with TS6307 on two of its three projects. Only the web project spans both trees.
Co-authored-by: Orca <help@stably.ai>
* fix(checks): stop the Tasks-grid pill contradicting its own verdict
The checks pill's label, tone and icon all read one ProviderCheckSummary, but
getChecksLabel short-circuited on the raw `neutral` counter while the tone and
icon key off `state`. After the classification fix a PR with 19 success + 1
neutral renders an emerald CheckCircle2 pill that reads "1 unresolved", and
mobile's own label (which keys off `state`) reads "19/20 passed" for the same
summary.
Move the label into src/shared/provider-check-summary.ts so desktop and mobile
cannot fork it again, and key it off `state`.
Also covers deriveWorkItemCheckSummary, the desktop-main producer of the summary
that reaches the Tasks grid and the relay-paired mobile client. It was rewritten
here with no test at all; the parity table stands in derivePRCheckStatusFromRollup,
which is a different normalizer. The new main-process test drives getWorkItem with
a real statusCheckRollup fixture, pinning the StatusContext `state` fallback that
would otherwise be deletable with the whole suite still green.
Co-authored-by: Orca <help@stably.ai>
* fix(gitlab): route the pipeline job-array rollup through the shared check classifier
The array path in derivePipelineStatus kept its own copy of the rollup rules, so
manual-only read green and one unrecognized job status demoted a passing pipeline
to neutral — both disagreeing with every other check surface.
Also retry the packaged-CLI smoke temp cleanup on Windows: the copied Orca.exe can
still be locked by AV/indexers after every assertion passed, failing the package job.
Co-authored-by: Orca <help@stably.ai>
* fix(gitlab): stop the skipped pipeline string diverging from the Checks tab
- classifyPipelineString now counts a skipped pipeline as passing, matching
the per-check classifier; canceled stays neutral and is pinned as an
explicit, sign-off-pending divergence.
- Pin the production string path (head_pipeline.status) in the parity table
and note that the job-array branch has no production caller yet.
- Count skipped checks in the Checks panel's passing header so it agrees
with the checks pill.
- Correct the packaged-CLI smoke retry comment: the EBUSY is the smoke's own
just-exited Electron process, not AV/indexers.
Co-authored-by: Orca <help@stably.ai>
* fix(checks): finish cross-surface check parity and back out the skipped MR-card flip
Review follow-ups on the check-classification PR.
- PullRequestPage and GitHubItemDialog kept private copies of getCheckCounts /
getChecksSummaryLabel that still counted only `success` as passing, so a
2-success/3-skipped PR read "2 passing · 3 skipped" there and "5 passing" in
the sidebar. Both copies move to pr-check-counts.ts, which routes the passing
bucket through classifyCheckOutcome; action_required keeps its own amber
bucket. The summary icon now keys off passing count, so an all-neutral PR
stops painting a green tick above "0 of N checks passing".
- The sidebar checks header and triage strip still called
`{status: completed, conclusion: null}` pending, contradicting the grey
"Unresolved checks" pill. Both now read summarizeProviderChecks and render an
unresolved chip/strip instead of an amber spinner that can never resolve.
- classifyPipelineString('skipped') is reverted to neutral. That flip painted
MR cards green for pipelines that never ran, on the only GitLab path with
production callers, and contradicted the same function's deferral of
`canceled`. Both tone changes stay deferred, pinned by one test.
- classifyPipelineString('manual') resolves to pending rather than neutral: a
blocked pipeline is outstanding, and neutral let the worktree card fall
through to its emerald `open` default while GitLab still refuses the merge.
- TaskPage's checks pill helpers move to task-page-checks-pill.ts so the
"1 unresolved on a green pill" fix is actually pinned by a test.
- smoke-packaged-cli no longer lets an EBUSY cleanup replace the real failure.
* fix(checks): stop completed unknown checks from spinning
---------
Co-authored-by: Orca <help@stably.ai>
* fix(windows): make the GPU fallback actually remove the GPU child, and stop WSL latching absent
Three Windows crash/regression fixes from shipped 1.4.156/1.4.158/1.4.159 crash reports.
GPU fallback (cluster D, 14 reports, exit 0x80000003 STATUS_BREAKPOINT):
the software-rendering fallback called disableHardwareAcceleration() plus
--disable-gpu, neither of which removes the GPU child process — Chromium still
spawns it to host Viz and merely drops the backend to software GL. Measured on
Windows 11 / Electron 43.1.0: gpuProcessCount stays 1. So a GPU process being
killed by a bad driver or an injected DLL kept dying after the fallback engaged,
on every launch, for the life of that build (the marker is sticky per version).
The crash tails show exactly this: gpu_fallback_applied followed by another GPU
crash 1.3s later. --in-process-gpu is the only switch that drops the child count
to 0; --disable-software-rasterizer is deliberately excluded because it also
kills SwiftShader, which would drop every terminal to the DOM renderer.
WSL distro list: a successful-but-empty `wsl --list --quiet` was cached for the
process lifetime. `wsl --install` reports zero distros while one is still
provisioning, so an early probe latched "no WSL" until restart — WSL appeared
during setup and then vanished from the terminal picker. Empty results now
re-probe on an exponential window (15s doubling to a 5min cap) while staying
readable, so a missing distro is still visible to isKnownMissingDistro.
WSL availability: isWslAvailable() latched false on any failure via a bare catch,
so one slow wsl.exe activation disabled WSL for the whole session. Failures are
now classified — a numeric exit status or ENOENT is answer-shaped and holds for
10min, anything else (timeout, spawn failure) retries after 45s — and both back
off per consecutive failure, mirroring isPwshAvailable.
Windows-only: every changed path is behind an existing process.platform check,
so macOS and Linux behaviour is unchanged.
* fix(windows): drop a stale WSL availability failure once a distro list succeeds
The distro-list and availability caches expire independently, and
getWslRepairReason checks availability first. So a definitive availability
failure (numeric exit status or ENOENT) held for 10-30min would keep reporting
`wsl-unavailable` even after `wsl --list --quiet` successfully returned a
distro — i.e. over a WSL that demonstrably just answered. That is the same
latch class this branch fixes, surviving in the gap between the two caches.
A non-empty distro list proves wsl.exe ran, so drop the negative availability
cache and let the next call re-probe. Scoped to non-empty lists only: those are
cached for the process lifetime, so this cannot re-spawn the blocking 5s probe
more than once. An empty list keeps its failure cache, since it re-probes on a
15s-to-5min schedule and would otherwise pay the blocking probe far too often.
* fix(windows): harden GPU safe mode and WSL recovery
* fix(wsl): make capability refresh cleanup explicit
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* feat: rename worktree folder to match branch on first work
When the first agent message auto-renames a freshly created creature branch to a
short, work-derived name, also align the on-disk worktree folder and the sidebar
display name with it. Re-key every worktree-scoped slice of state — renderer
store maps plus the persisted main-process state — through the resulting id
change so the live worktree survives the rename instead of being treated as a
deletion (its tabs, terminals, browser panes, and git status all follow).
The rename is best-effort and local-only: a skip or failure (remote runtime,
Windows lock, destination taken) leaves the folder as-is and never undoes the
branch/display rename that already landed.
Squashed from the original PR #4743 commits, rebased onto upstream/main to drop
accumulated merge commits and i18n formatting churn so the branch carries only
the feature diff:
- Rename worktree folder to match branch on first work
- migrate renamed worktree session ids
- keep the live worktree alive through a folder rename
- address PR review feedback
* Address PR review feedback (#4743)
- orca-runtime: emit in-process worktreesChanged client event on folder rename, mirroring notifyBranchRenamed so onClientEvent listeners aren't left stale
- worktrees: re-key rightSidebarExplorerViewByWorktree and activeWorkspaceKey through a worktree-identity rename (both were worktree-scoped but missed by buildWorktreeRenameState)
- branch-name-from-work: treat prefix-only model output as an empty slug so the caller skips the rename instead of producing a doubled prefix
- worktree-folder-rename-target: document why posix.dirname is safe (Windows filtered out earlier)
- tests for each of the above
* Extract branch rename test helpers to a separate harness file
Move git responders, mock builders, and test event fixtures out of
first-work-branch-rename.test.ts into a new test harness file. This
reduces file length and removes the max-lines ESLint disable directive,
complying with project style guidelines.
* Wrap entire OnboardingFlow in TooltipProvider
Enable the use of tooltips anywhere within the onboarding flow, rather
than restricting them to the step indicators.
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Extend worktree creation, remote-conflict detection, and the Checks panel UI to support linked PRs from Bitbucket, Azure DevOps, and Gitea alongside GitHub and GitLab.
* Extract shared metadata lookup helpers to map provider-specific review identifiers.
* Update remote-conflict validation to check hosted reviews on target providers during worktree creation.
* Propagate provider-specific PR states through the RPC layers and into the Checks and Source Control UI panels.
* fix(tabs): repair mixed tab shortcut switching
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(typecheck): unblock tc:web project checks
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
* fix(tabs): split group/fallback id matching into separate branches
Why: matching both `tabId` and `id` in one findIndex predicate mixed two
identifier domains and risked a pathological collision between a tab's
backing entity id and another tab's unified id. Keep the group-path
(strict tabId match) and the fallback-path (backing-id match) in
separate branches.
Also clarify the comment on the dual `setActiveFile` + `activateTab`
write so future readers know why both calls are needed for split-group
disambiguation.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* chore: clean up repo root for faster README visibility
- Delete unused images (debug_orca.png, orca_3d.jpg, screenshot.png)
- Delete stale design docs from docs/
- Move tsconfig sub-configs, electron-builder config, and vitest config to config/
- Move file-drag.gif to docs/assets/ and design doc to docs/
- Update all path references in package.json, tsconfig.json, and moved configs
* fix: remove stale worktree dialog callback dependency