* Honor configured shells during worktree setup
* Align setup launch paths with selected Windows shells
* Carry setup shell selection through deferred launches
* Prove Windows setup shell routing at its real adapters
* Ground remote PowerShell proof in the real writer
* Preserve Git Bash across deferred setup launches
* Harden Windows setup runner shell selection
- Resolve remote PowerShell binary without local pwsh probe: for SSH/remote
Windows worktrees, isPwshAvailable() reflects only the LOCAL host, so an
'auto' implementation could route the remote runner to a pwsh.exe the remote
lacks. Add resolveSetupRunnerShell(..., { probeLocalPwsh: false }) so remote
auto keeps the always-present powershell.exe; explicit pwsh.exe still honored.
- Preserve native exit codes in the PowerShell runner by checking
$LASTEXITCODE before $?, so a failing native command surfaces its real code
instead of a generic exit 1; $? still catches cmdlet soft-failures.
- Write the PowerShell runner with a UTF-8 BOM so Windows PowerShell 5.1 (the
new default powershell.exe) reads it as UTF-8 instead of ANSI, preventing
non-ASCII setup-script corruption.
- Add unit tests for the remote-probe behavior.
* Restore setup-shell scope narrowing over the rebase
The force-pushed rebase dropped five review-fix commits that were already
on this branch; this reapplies their combined effect on top of the new
base and the hardening commit:
- Keep SSH setup shell selection remote-owned (no local terminalWindowsShell
or pwsh routing for remote hosts; supersedes the probeLocalPwsh guard)
- Preserve cmd setup compatibility outside POSIX shells (no .ps1 runner
family, so the BOM/exit-code hardening is no longer applicable)
- Route WSL setup runners from the project runtime
- Avoid blocking PowerShell probes during setup creation
- Correct SSH and WSL background setup fixtures
* Satisfy the changed-code gates for the setup-shell runner
- createWorktreeRunnerScript took 7 positional parameters, tripping the
changed-code max-params gate; move it to a single options object.
- hooks-runner.test.ts deep-equals the createSetupRunnerScript result, so
assert the cmd shell now returned for native Windows worktrees.
* Carry the setup launch shell through observed and issue runners
- buildObservedSetupCommand takes the runner's launch shell so WSL-routed
Windows-drive setup replays use /mnt/c instead of Git Bash /c
- resolveSetupRunnerShell gates the posix runner on the same Git Bash
resolution the PTY uses, so a missing or non-MSYS bash keeps the cmd runner
- issue-command runners carry their launch shell, and the renderer passes it
when building the queued command
- treat a bare `bash` shell setting as POSIX like `bash.exe`
Co-authored-by: Orca <help@stably.ai>
* fix(worktrees): close counsel P1 gaps for Windows setup shells
Route windowless/headless creates through the shell-aware setup runner when a
PTY controller is available, existence-check explicit Git Bash paths before
committing to .sh runners, thread the resolved shell into issue-command
runners, and document the intentional Git Bash interpreter flip with a narrow
scope table.
* Convert setup env to MSYS form and harden the bare cmd runner launch
C3: a Git Bash setup runner now receives ORCA_*/CONDUCTOR_*/GHOSTX_* path
values in /c/... form, matching the runner path and the shell's own HOME/PWD.
C5: extension-less `bash` resolves to Git Bash everywhere, matching how
resolveWindowsShellStartupFamily already classifies it.
C7: runner paths carrying characters that cannot be quoted on a cmd command
line launch through a delayed-expansion PowerShell shim instead, and the batch
runner disables inherited delayed expansion so `!` in setup lines survives.
Co-authored-by: Orca <help@stably.ai>
* docs: note MSYS ORCA_* paths and bare bash Git Bash resolution
Keep the setup-shell release note aligned with C3 env conversion and C5 bare
bash resolution so the published claim matches runtime behavior.
* revert: drop windows-setup-shell doc allowlist and AGENTS link
Keep the counsel P1/P2 product fixes without expanding the docs allowlist
or AGENTS.md guidance surface.
* fix(plugins): contain Parcel unsubscribe rejections under Vitest
Dev plugin watchers fire-and-forget unsubscribe, and in-process Parcel
can reject when temp watch roots are already deleted. Catch those
rejections so they cannot fail the suite as unhandled errors.
* fix(plugins): keep in-process unsubscribe rejection surface
Swallowing Parcel unsubscribe errors broke mocked unsubscribe tests
that return non-Promises and expect rejections. Contain failures only
in PluginDevWatcher fire-and-forget paths.
---------
Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* Allow clearing all agents from AI Vault session history filter
Add "Select all" / "Clear" buttons so users can quickly isolate one agent without unchecking each box individually. Previously, at least one agent had to remain enabled; now users can filter to zero agents and re-enable selectively.
* Address PR #12128 review feedback
- Make Select all / Clear real DropdownMenuItems so Radix roving focus reaches them by keyboard.
- Rename the zero-agent empty state to a neutral "No agents selected" now that zero agents is a valid filter.
- Use 모두 해제 for the Korean Clear label instead of 지우기 (erase).
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* feat(updater): add an adhoc release channel for branch builds
Hourly covers main. This covers everything that is not main yet: a
dispatchable macOS build of an unlanded branch, published to
stablyai/orca-adhoc, so the team can run an experimental feature for a
few days instead of reasoning about it from a diff.
Adhoc sits at the bottom of the version order — 'adhoc' < 'hourly' <
'rc' < stable — so no routine check can walk anyone onto somebody's
branch; only an explicit pinned jump reaches one. It gets its own repo
rather than sharing orca-hourly's, because a branch build must not
appear in the list a developer riding main is looking at.
Signed and notarized exactly like hourly, for the same reason: macOS
anchors a notarized app's TCC grants on identifier + team, so an
unnotarized build reads as a new client and silently loses file access
under Documents/Desktop/Downloads.
Tags stamp to the second rather than the minute. Hourly runs under a
concurrency group and cannot overlap itself; adhoc builds are dispatched
on demand, so two people cutting from different branches inside one
minute is ordinary — and a minute-resolution tag would collide and fail
the second build after its whole pack-and-notarize run.
Channel-specific behaviour now derives from one DEDICATED_REPO_CHANNELS
list: repo mapping, macOS-only support, and UpdateSource. The RPC schema
that validates releaseChannelOverride was a hand-copied enum missing the
new channel, which would have rejected the override on its way to the
main process; it reads the predicate now.
* fix(updater): merge the duplicated shared/types import
Co-authored-by: Orca <help@stably.ai>
* fix(ci): default the adhoc build ref to the dispatch branch
The Actions UI puts its own "Use workflow from" branch picker directly
above the ref field, and picking a branch there is what most people read
as "build this". Making the field optional means the obvious action is
also the correct one; naming a branch explicitly still wins, so main's
copy of the workflow runs rather than a stale one on an old branch.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
mobile/src/constants/marine-creatures.ts was a hand-maintained copy of
src/shared/marine-creatures.ts, identical except for a comment header. The
copy existed because Metro only watched mobile/ and could not resolve
repo-root modules; mobile/metro.config.js:11 added src/shared to
watchFolders five weeks later, and ~195 mobile files already import from
src/shared. The renderer collapsed its copy to a re-export at the same time;
mobile was the leftover.
Point the one consumer at the shared corpus and delete the mirror, the
bespoke regex-scraping parity test that policed it, and the now-stale
max-lines baseline entry.
No behavior change: same exported symbol, byte-identical name list.
* chore(dead-code): drop 2k lines of unreachable exports and orphan modules
Ran knip across every build entry (main, preload, renderer, popout, web,
cli, relay, workers, forked sidecars, config scripts) and removed what no
entry graph can reach.
- 11 orphan modules nothing imported, plus one test that only covered them
- 159 unused exports/types, with their now-dead helpers, imports and tests
Each candidate was verified against dynamic references before deletion.
42 knip hits were false positives and are kept: shared modules consumed by
the mobile/ workspace, the src/shared/plugins/** public API, vendored
shadcn primitives, and relay wire-protocol constants held for compatibility.
Adds knip.json + `pnpm audit:dead-code` so this stays measurable.
Verified: pnpm typecheck, pnpm lint, and 2081 tests across the 73 affected
test files all pass.
* chore(dead-code): move knip config under config/
Root-level additions are blocked by the root directory guard.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
macOS anchors a notarized Developer ID app's TCC grants on identifier +
team, which is cdhash-independent and so survives an in-place update.
Without a notarization ticket there is no such stable identity, so every
hourly reads as a different client: the grant row stays but stops
matching, and file access under Documents/Desktop/Downloads fails with
EPERM and no re-prompt. `tccutil reset` fixes it until the next build —
and orca-hourly has shipped as many as 14 builds in a day.
Skipping notarization was chosen because Squirrel.Mac validates the
replacement bundle's signature, not its notarization. That is true, but
it is the wrong requirement; the in-place swap was never the problem.
Budgets grow to absorb the notary round trip (publish 2x45, job 150), and
the App token is re-minted after the build so its one-hour life starts at
the first call that uses it rather than during `pnpm install`.
Remove one-off incident docs and committed test-results noise, move
dev/repro/bench tools under tests/tools, and relocate i18next config
into config/ so the GitHub root scrolls to the description faster.
* feat(updater): name hourly releases by version, build number, time, and sha
Hourly releases were titled with their raw tag
(`v1.4.163-hourly.202607312054`), which reads as one opaque digit run and
does not say which commit it came from.
Title them `1.4.163 • 01 • 07-31 13:54 • e698241` instead, and show that
same string in the in-app build picker by having the picker render the
release's stored name rather than deriving its own label. Composing it in
one place means the two surfaces cannot drift.
The build number is monotonic across the channel. It is read as the
highest number already in use rather than as a count of releases: the
prune step trims to 72, so a count would roll backwards after three days
and reissue numbers. Drafts count toward it — unlike in the freshness
check, which asks whether a commit shipped, this asks whether a number is
free, and a stranded draft still holds one.
Times are Pacific while the tag's stamp stays UTC. The stamp is a sort
key and a local one would repeat an hour at every DST fall-back, making
two distinct builds compare equal; the title is only ever read.
* fix(updater): fail the hourly build when the release name is missing
The workflow checks out `ref: main`, but a workflow_dispatch runs the
workflow file from whatever branch was dispatched. A branch that edits
this step while main still carries the old script produces an empty name
and an untitled release — silent, and only visible once someone opens the
releases page. Verified by hitting exactly that on run 30665586904.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* docs(i18n): adopt gettext PO as the canonical translation source
Records the revised translation-source architecture decision: gettext PO
replaces the original constrained XLIFF 2.0 profile after tooling,
contributor-workflow, and repository-evidence review. Documents the
read-only compiler / post-merge reconciler split, the four-state entry
taxonomy, message ID and placeholder policy, mobile and plugin-language-pack
scope, and the field-by-field PR B acceptance gate. Links the decision from
the localization audit and corrects its allowlist description.
* docs(i18n): add mobile dual-projection and bridge-ID constraints
From PR 11446 owner review: mobile needs two deterministic projections
(i18next JSON + pre-JS native metadata with a documented native fallback
rule and the zh/zh-Hans locale-ID mapping); the hash-ID grandfather clause
is date-scoped to the desktop catalog so freshly minted bridge IDs must be
renamed in a dedicated adjacent change; OS permission prompts and native
app metadata join the human-review list; imported bridge provenance must
be classified in PR C.
* fix(release): restore the SignPath composite action when cutting from an older ref
Co-authored-by: Orca <help@stably.ai>
* fix(startup): record a durable diagnostic before the bootstrap fatal-exit guard exits
Co-authored-by: Orca <help@stably.ai>
* fix(settings): make agent-skill Re-check rescan skill freshness
Co-authored-by: Orca <help@stably.ai>
* fix(startup): keep the bootstrap fatal diagnostic when the log override is unwritable
Create the parent directory an overridden ORCA_BOOTSTRAP_FATAL_LOG names and fall
back to the default location when that path still cannot be opened, so a missing
parent no longer costs the only account of the failure. Also pins the Re-check
freshness rescan to the completed install scan rather than the click.
Co-authored-by: Orca <help@stably.ai>
* refactor(settings): move the post-recheck surface sync out of the panel
Co-authored-by: Orca <help@stably.ai>
* fix(startup): retain diagnostics without node fs
* fix(skills): keep freshness scoped to the local runtime
* fix(settings): register freshness status translations
* fix(settings): scope and sequence skill freshness refreshes
* fix(settings): refresh freshness across runtime transitions
---------
Co-authored-by: Orca <help@stably.ai>
* 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>