Repository navigation
Conversation
Neither provider publishes a percentage quota, so the status bar gains a money readout for them: - DeepSeek: account balance from /user/balance, which may be CNY or USD. The API exposes no usage or cost history, so the row is balance-only. - Fireworks.ai: rated spend for the current billing period, with the account ID discovered from the API key and an optional manual override. Adds an optional `credits` field to ProviderRateLimits with exact integer-nanos money arithmetic, keeps both API keys in the existing encrypted credential store rather than GlobalSettings, and teaches the window-shaped staleness predicates that credits are data so a configured provider is neither hidden nor blanked by a transient failure. `status: 'unavailable'` keeps meaning "not configured"; an exhausted balance stays visible via `credits.available` so the account can be topped up instead of vanishing. Fireworks spend-limit percentages are deliberately not wired: the `usageLimits` response shape is unverified. Extracts createSecureCredentialStore from the MiniMax API-key store and reuses it for the two new credentials.
The DeepSeek status-bar chip was showing a neutral lucide Waves glyph as a placeholder. Replace it with the real mark, taken verbatim from the inline SVG on deepseek.com (single `currentColor` path, 26.634x19.6). Inlined rather than shipped as a resources/ asset like MiniMax's: that mark is a fixed multi-colour gradient, whereas this one is a single path whose `currentColor` would resolve to the SVG's own colour inside an <img> and go invisible against one of the two themes. The icon keeps its native aspect ratio (13x10 at the default size) instead of being forced into a square box, which would squash the whale.
The Fireworks status-bar chip was showing a neutral lucide Flame glyph as a placeholder. Replace it with the real mark, taken verbatim from the SVG icon fireworks.ai serves as its favicon (three paths on a 32x32 grid, all #6720FF). Inlined like the Claude/Gemini marks rather than shipped as a resources/ asset: the fill is a single fixed brand colour, so it needs no separate file, and the icon grid already carries the mark's padding, so no viewBox cropping is required the way the DeepSeek whale needed. This removes the last lucide placeholder: icons.tsx no longer imports lucide-react at all.
…ighbours Their favicon grid carries ~25% vertical padding and the mark itself is 2:1, so at the same 13px width as the DeepSeek whale it rendered ~35% shorter and read as a smaller icon beside it. Crop the viewBox from the source's 32x32 to the mark's own bounds (y 8-23.8) and scale the width 1.25x, which lands the ink at 16x8 against the whale's 13x10.
Fireworks exposes credit only in its dashboard: there is no published REST
route for a balance, and `GET /v1/accounts/{id}/usageLimits` answers
`code 9, "usage limits are not enabled for account ..."` on a self-serve
account, so the row could only ever show spend.
Read the balance from the control-plane gateway instead — `gateway.Gateway/
GetBalance` over `gateway.fireworks.ai`, which is a different host from the
`api.fireworks.ai` inference API that serves the billing summary. The auth
metadata (`x-api-key`) and the message shapes were recovered from the
unstripped `firectl` binary, which is a thin client over that service and
exposes no subcommand for GetBalance.
Implemented with no new dependencies: a hand-rolled protobuf codec for the
three messages involved (`GetBalanceRequest`, `Balance`, `Money`) plus gRPC
framing over `node:http2`, rather than pulling in `@grpc/grpc-js` and
`@grpc/proto-loader` for one readout. The codec is unit-tested against the
wire shape a live gateway response was observed to carry.
The balance becomes the headline with the period's spend as its breakdown.
Because this is an internal API with no compatibility contract, the call is
best-effort: any failure — unreachable gateway, proxy, revoked key, schema
change — degrades to the previous spend-only readout instead of failing the
provider. Note the transport dials the host directly, so it deliberately does
not use the proxied default session that `net.fetch` callers do.
Plans without 5-hour and 7-day quota windows reported nothing useful: Orca
mapped their payloads to `{ session: null, weekly: null, status: 'ok' }`, which
the UI showed as "No usage data" — indistinguishable from a failed read.
Both providers do report a spending ceiling instead, so map it:
- Claude reads `spend` (with `extra_usage` as the fallback source) from the
usage endpoint, which previously discarded those keys entirely because
`OAuthUsageResponse` had no field for them.
- Codex reads `spend_control.individual_limit` from the backend usage payload,
which was never even requested: `supplementCodexSessionWindow` returned early
whenever the backend had no session window, so a windowless plan was skipped.
It also dropped the backend's `planType` on that path; the no-session branch
now keeps local windows (the backend can report a different bucket) but
adopts the allowance and plan.
Each ceiling's own percentage becomes the `monthly` window, so the status bar,
roster and detail panel render it with no renderer changes, and the amounts
ride a new `ProviderAllowance` readout: an amount against a limit in a unit the
provider picks — dollars for Claude, the provider's own `credit` unit for Codex,
which a currency-shaped type could not express. The percentage is always the
provider's reported figure, never derived from the amounts.
Only applies when an account reports no session *and* no weekly window, so a
normal subscription account with extra usage credits does not grow a monthly bar.
Note the backend reports `reset_at` in Unix seconds while Orca stores ms.
Copilot enterprise accounts report a monthly AI-credit budget rather than a
quota window, so this rides the allowance readout added for Claude and Codex:
the ceiling comes from `GET /enterprises/{ent}/settings/billing/budgets`, the
consumption from `/settings/billing/ai_credit/usage`, and the percentage fills
the `monthly` window.
The budget is denominated in dollars while usage is reported in credits, and
the usage payload carries the price that relates them, so the ceiling is
expressed in the unit the user recognises: 10000 dollars at the documented
0.01 price displays as 1,000,000 AI credits. With no price reported it falls
back to dollars rather than inventing a credit ceiling. GitHub documents these
budgets as monthly and partitions the usage report by calendar month, so the
reset is the next month boundary — inferable here in a way Claude's spend cap
was not.
Auth is a token plus the enterprise slug, stored in one encrypted file. The
`gh` CLI token Orca otherwise uses carries repo/read:org/project and cannot
read billing endpoints, so this is a separate credential. GitHub 401 and 403
map to stale-token and missing-scope so the existing error copy explains what
to fix.
The icon is the official Copilot mark — the robot with goggles, taken verbatim
from the brand icon GitHub ships for the product — not the octocat.
Also lands the identity plumbing: a copilot StatusBarItem, default-on
migration, visibility and roster wiring, an Accounts section, and the tests for
both the mapping and the credential flow.
The Copilot fetcher was the only authenticated GitHub caller in Orca not going through `gh`, which meant it carried its own token, hardcoded api.github.com, could not resolve a GitHub Enterprise Server host, and bypassed the gh rate-limit breaker. Route it through `gh api` like every other GitHub call, so the provider holds no token of its own: an explicit credential is only ever passed to gh as GH_TOKEN. That also lets the credential come from the sign-in the user already has. The enterprise slug is now resolved from gh rather than typed: `read:enterprise` exposes it through GraphQL `viewer.enterprises` and `manage_billing:enterprise` reads its billing data, with `admin:enterprise` implying both (verified against the live API — without `read:enterprise` the query fails with INSUFFICIENT_SCOPES rather than returning an empty list). A gh lacking those scopes is deliberately not treated as a credential source: the provider item is default-on, so doing otherwise would show a permanent error bar to every enterprise user who never asked for Copilot usage. The paste form stays as the explicit override and wins over the CLI. The probe is cached and refreshed out of band rather than awaited inside the fetch cycle, because `getState()` is synchronous and a subprocess on the refresh path would let gh latency — or a hung gh — stall every other provider. Settings → Accounts now says which source is in use and shows the literal `gh auth refresh -s read:enterprise -s manage_billing:enterprise` command when the scopes are the blocker.
Snapshot of 44 modified and 13 new files, captured byte-identically from the work machine and verified by patch-body comparison. Kept as one commit so the work is durably recorded; the concerns are split into their own branches for review. Committed with --no-verify so the snapshot matches the source tree exactly rather than being reformatted on the way in.
Upstream hardcodes stablyai/orca as the update feed, so a build of this fork offers to replace itself with an official release. Re-point the atom feed, the download base, both tag/asset regexes, the two latest/download fallbacks, and MAIN_RELEASE_REPO at nplez1/orca. The houry/daily/adhoc channel repos are left alone because those channels are unused here.
electron-updater verifies every installer it downloads against the publisherName in the installed app's app-update.yml and rejects its own channel's next build when it does not match. This fork ships unsigned, so drop the publisherName and disable update signature verification — the same thing upstream's dev channels do, for the same reason.
BRANCHES.md records what git cannot know — why each branch exists, what it depends on, and what is blocking it — and local/branch-status.mjs reports the live state so the doc cannot silently drift. Also records the release workflow in the fork-only inventory.
A local(...) commit belongs on nplez1/main only; on a PR branch the fork-only file would be reviewed upstream. commit-msg rather than pre-commit because the message is what marks a commit fork-only.
Records the step order that matters: validate the pipeline unsigned once before the first signed run, so a failure cannot be a signing bug or a pipeline bug at the same time. Also the five secrets, the per-machine install caveats, and the two-release minimum for proving an update.
orca.yaml gains an environmentRecipes entry and local/orca-vm/ the four lifecycle scripts, so a worktree can allocate its own machine on create and release it on delete. Inert until the provider verbs are wired: the scripts fail loudly rather than silently allocating nothing. Validated dry with orca vm recipe doctor (6/6, no fail or warn) and the create contract checked locally against stubbed verbs: exactly one JSON object on stdout, logs on stderr, no unknown keys.
A snapshot is only worth taking once the guest already has what Orca would otherwise install or compile on first connect. Checks node/npm/git, the Linux build toolchain node-gyp needs, a COMPLETE baked native-deps cache entry (.deps-complete plus both addons), and agent CLIs. Documents the two bake layers and why the shared native-deps cache, not the hash-keyed relay dir, is the artifact worth baking.
… absent one)
The first pipeline run died in packaging with '<repo> not a file': an unset secret still defines
CSC_LINK as an empty string, electron-builder reads a defined CSC_LINK as a certificate path, and
resolveCscLinkPath('', cwd) resolves to the repository root. Unset the CSC_*/APPLE_* variables on the
unsigned path, and record why in LOCAL-PATCHES.md so it is not re-broken.
…ish legs do not race The second run died with 422 'Published releases must have a valid tag': both artifact legs saw no release, both POSTed to create it, and the loser aborted the job even though the winner succeeded — leaving a PUBLISHED release with seven assets and no latest-mac.yml, i.e. downloads no installed build can update from. Create the release up front as a draft so both legs upload into a known tag, and flip it live at the end of the windows job once every platform's manifest is present. The manifest checks now address the tag explicitly, because gh release list hides drafts. Same shape upstream uses in daily-mac-build.yml, for the same reasons.
Renames every name an official install also owns: appId com.stablyai.orca -> com.nplez1.orca, productName 'Orca' -> 'Orca NP', the CLI command 'orca' -> 'orca-np', packaged userData -> appData/orca-np, ~/.orca -> ~/.orca-np, the NSIS Markdown ProgID, and the daemon-host root. Also pins userData explicitly instead of deriving it from package.json's name, and collapses the old orca/Orca case inconsistency, which diverges on a case-sensitive filesystem. The URL scheme stays orca://: it is interop surface (mobile pairing, inbound skill-share links), not plumbing, so a fork-only scheme would emit pairing links nothing else can open. Three couplings, each of which fails silently: - the macOS launcher shim hardcodes the in-bundle executable path, and the executable is named after productName; - the Windows launcher ships as bin/orca-np.exe + orca-np.cmd, with getBundledLauncherPath and the extraResources mapping moving as a unit; - the SSH relay shim is a DIFFERENT identity, deployed under a fixed plain `orca`, so remote launches resolve through getRelayCliCommandNameForPlatform instead of the locally installed name. That last one is why local and remote launch names now live in separate config fields: upstream could share one value only because both names were `orca`. Tests: the suite baseline was measured before touching anything (22 files already failing on nplez1/main), which made the identity fallout measurable: 42 files, all now green. See LOCAL-PATCHES.md § local(identity).
Follow-up to the identity rename, found by running the full suite after it landed rather than trusting the first green run: - the packaged Windows launcher ships as bin/orca-np.exe + orca-np.cmd, so the preflight's bundled launcher table and the launcher-asset guard test move with it; - the C# launcher normalizes ORCA_CLI_COMMAND against a whitelist, which silently relabelled the fork's CLI as `orca`; it now honours orca-np; - ORCA_CLI_COMMAND for managed WSL terminals was pinned to `orca-ide`, which no longer names the installed command; - the settings panel's CLI fallback name and its user-facing "Register `orca`" guidance; - claude-agent-teams-shim-env resolved the OLD packaged Windows launcher path on win32. Fixtures that mimic the packaged Windows layout were updated too, so a future reader is not misled by a path that no longer exists.
…s launcher The guard asserts the packaged Windows shim's extraResources mapping, which now ships as orca-np.cmd beside the native orca-np.exe.
The release body was a single placeholder sentence, which is a poor landing page for someone handed the build: no install steps per platform, no statement of what the fork is, and nothing about the first-run behaviour that otherwise reads as a bug (a fresh profile, and macOS asking for folder permissions again). The body now comes from .github/fork-release-notes.md, rendered per release with the version, the commit it was built from, the date, and a signing paragraph that follows the signing mode — signed and notarized, or the unsigned validation wording. Node does the render because one placeholder expands to a multi-line paragraph.
Brings in 131 upstream commits. Three of the 29 local commits needed resolution: two i18n locale files, and src/shared/agent-hook-listener/listener-state.ts, where upstream wrapped the state factory in a legacy-adapter layer — kept that scaffolding *and* our CopilotBackgroundWorkState type. Also here: - regenerated the derived runtime-required English catalog, which removed the stale fixture keys the local side of the i18n conflict carried (`pnpm run sync:localization-runtime-catalog`); - dropped an unused import in an upstream native-chat test that upstream's CI does not gate on but this repo's typecheck does; - recorded the sync in LOCAL-PATCHES.md, refreshed shas in BRANCHES.md, and corrected the note about the dev channels: the picker still offers hourly/daily/adhoc, which resolve to stablyai's repos. Verified: range-diff shows 26 of 29 patches byte-identical, all three typecheck projects clean, and 161 test files green across the conflict areas and the series' surfaces.
* feat(checks): make the Jenkins build tree readable at scale The Jenkins details from #39 read the build's stage list but listed every stage in the Checks pane, buried failures under it, kept an Output box that only repeated the build name, and stopped at a bare error when the account could not read the job. - Pane: list only the stages still running and the ones that failed, running first, capped at eight, with a "+N more stages" line — a pipeline can have hundreds. - Detail page: render the stages as a nested tree (stage -> its wfapi flow nodes) like Blue Ocean, with failed and running stages open by default. - Detail page: a Failures block under the run header names each failed stage and its error text, so "what broke" needs no scrolling. - Detail page: drop the Output box for a stage list; the build metadata is the run summary. - Settings: a Jenkins read failure the user can fix (no server, rejected token, no permission) now shows an "Open Jenkins settings" button that deep-links to the Jenkins card. Main reads each stage's own wfapi/describe (its _links.self.href, bounded to 40 requests at six concurrent, failed/running first under a four-second budget) for the steps and failure text, and carries the flow nodes on an optional PRCheckStep.children. The Jenkins node id is kept so repeated stage names (loops, parallel branches) cannot collide as React keys. * fix(lint): split files over the max-lines budget Five files exceed the oxlint max-lines budget on nplez1/main and have no suppression, so the whole-repo `pnpm exec oxlint` in the static-analysis job fails for every PR cut from it. They were pushed over by fork-only commits (`#34`, and three direct commits) that never ran PR CI. The repo's rule is to split rather than suppress, so this extracts cohesive pieces: - filesystem-watcher.test.ts: the local/WSL lifecycle tests move to filesystem-watcher-local-lifecycle.test.ts. - filesystem-watcher-local-subscription.ts: scheduleLocalWatcherTeardown moves to filesystem-watcher-local-teardown.ts. - ssh-target-cleanup.ts: the per-tab session helpers move to ssh-target-tab-sessions.ts. - ai-vault-session-refresh.ts: the session-ids key moves to ai-vault-session-ids.ts. - accounts-search.ts: the Grok and Cursor search entries move to accounts-search-extra-providers.ts and are re-exported. No behavior change; the affected suites pass.
…#41) * feat(tasks): cache the Jira board and remember its view state Returning to the Jira board always showed a loading screen, even right after leaving it, and the board forgot every choice made on it. Three causes, all in the board view's own state: - The overview and the sprint/backlog pages were refetched from scratch on every mount, because the Tasks view unmounts when you switch to a workspace. - The sprint/backlog tab, the Me/All filter, and the filter query lived only in component state, so nothing survived that unmount. - The provider filter chrome is hidden while the board is shown, which left the board with no filter box at all. Fixes: - Keep the last board snapshot in a module cache keyed by site/board (30 minute TTL, three boards) and seed the hooks from it synchronously, so a cached board paints on the first render. The refetch then runs silently behind it, reading back the pages that were already loaded so a paged board cannot mix fresh page one with stale later pages. Refresh still shows the spinner, and switching sprint drops the cached page rather than pairing the new sprint with the old one's issues. - Persist the board/list mode, sprint/backlog tab, Me/All filter, and filter text per device in local storage, mirroring the Linear issue-view preferences. They stay out of taskResumeState: that schema is strict behind a field-level catch, so a host that predates a key rejects the whole payload and drops the GitHub and Jira resume state with it. - Give the board its own filter box, narrowing the loaded issues client-side by key, summary, assignee, status, type, or label. - Open on the viewer's own work (Me) rather than the whole board filter. When the board's site has no resolvable viewer account, show everything instead of an empty board behind a disabled button. - Drop the Team filter and its Team field / Team value settings; the board filter is now Me/All. The Team field was the only consumer of the custom-field listing, so jira:listCustomFields, jira.listCustomFields, the preload and runtime clients, and the main-process query all go with it, and the generated RPC params catalog is regenerated. * feat(tasks): rank Jira priority by severity and wash blockers A Jira issue's priority was plain text in the same muted grey as every other field, so a blocker read exactly like a trivial task in a dense list and was easy to skim past on a board. - Give each priority a severity chip: a coloured bubble plus the tier name, so the hue is never the only signal. - Blocker and Critical share the red chip; Major is orange. Everything below (medium, normal, low, minor, lowest, trivial, and any custom scheme) stays muted, because a renamed priority scheme must not be mislabelled. - A blocker additionally washes its whole task surface — row or board card — and keeps that wash on hover and while selected, since a chip alone does not stand out enough on a board. The tiers are matched by name against the standard Jira vocabularies (blocker/highest, critical, major/high). Two new tokens, --severity-blocker and --severity-major, carry the ramp in both themes; Major no longer borrows --status-warning, which stays free for its own needs-attention role.
Settings -> AI Provider Accounts now has a master switch at the top of each provider section. Turning a provider off stops Orca both from polling its usage/quota and from reading its sign-in out of software installed on this computer (cursor-agent Keychain, ~/.codex/auth.json, gh CLI, the Grok, Gemini and OpenCode auth files, the macOS Claude Keychain), hides its status-bar meter, and drops any cached snapshot. Providers stay on by default; the switched-off set persists in GlobalSettings.disabledUsageProviders. The rate-limit service takes an isUsageProviderDisabled probe read from settings, so the gate covers the full cycle, the codex/claude/grok-only cycles, the inactive-account probes, the on-disk credential-presence flags and the codex reset-credit redemption. The pane's cursor/grok status endpoints and the startup gh probe are gated the same way, and RuntimeAccountController returns an empty Claude/Codex roster so a disabled provider's system-default identity is never read.
xterm carries 0x07 as an EXECUTABLE inside APC/DCS/PM/SOS, so a BEL that terminates one of those payloads never rings the bell. Tracking only OSC announced Pi's `ESC _ pi:c BEL` prompt repaint as a phantom "Attention requested" notification on every repaint. Deliberate divergence: xterm keeps those four states open until ST, but Pi terminates its APC with BEL and never sends ST, so xterm-strict semantics would pin the string open and swallow every later real bell.
Every fix here is a surface upstream added or rewrote after this fork's base, so no conflict marker showed it. `pnpm tc` and the touched suites are what found them: - The replayed `local(identity)` patch tore `getWindowsPowerShellLifecycleCommand` apart: the merge kept upstream's `const innerCommand =` opener and the base's closing paren, so the function had no `return`. `innerCommand` was unused; upstream's `return (...)` shape is restored with only the rename kept. - `use-worktree-activity-status`'s effect lost the comma after `hasRetainedFailed` (upstream inserted that field where the fork appends `setupRunning`). - Upstream's new hook providers (qoder, codebuddy, dsh) are a compile error in the fork's `DESCENDANT_PROVIDERS` Record, which enumerates `AgentHookSource` by design, so descendant support is decided together with each new provider. They report no children, like their peers. - The new dsh hook service hardcoded `.orca` for its remote script and managed-hooks path where claude/devin/droid/gemini/copilot/cursor use `HOME_DIRECTORY_NAME`, so an SSH install would have written into an official install's directory. - `providers.zcode !== undefined && ...` in the status bar's pending chain is not the predicate the other providers get: zcode is optional, so an absent slice must not read as pending and hold the setup CTA forever. - Upstream's new CLI-identity suites pin upstream's `orca`/`orca-dev`/`orca.exe`. The fork ships `orca-np`/`orca-np-dev`/`orca-np.exe`, and the login-shell harness stubs the app's launcher by that name — its bare-name arm models the fork's own command. - The ZCode fetcher joined the shared rate-limit harness, so the DeepSeek and Fireworks suites need its mock the way the Cursor suite already had it. - The Jenkins panel suite's `@/i18n/i18n` mock predates the localized catalog reading `i18n` off that module. - The derived English runtime catalog is regenerated (`pnpm run sync:localization-runtime-catalog`), which is what the two `--ours` resolutions taken during the replay left behind. - The mobile page's Phase C sweep is re-measured, not bumped: this head cuts the swept tree into 73 chunks against the 69 the table recorded, past the four the fence gives a refactor, so `MOBILE_WEB_APP_BUNDLE_SCRIPT_SWEEP` was re-derived by building `routes.slice(0, n)` for every n. Its worst marginal route now costs ten, which moves in the route count at which the derived asset ceiling passes the shell's 256 (32 to 30). The page still builds inside both budgets.
129 published commits replayed from 976844fac3: 107 byte-identical, 22 adapted, none dropped. Sixteen stopped on a conflict across 37 files — every one an additive union, a re-seat onto upstream's refactor, or an upstream deletion of a file the fork had patched. BRANCHES.md re-measures the base drift at 975 / 1069 commits behind.
The e2e workflow's two sides each named a report. Upstream's step sets `PLAYWRIGHT_JSON_OUTPUT_FILE: ci-shards/results.json` for its new failure summary; this fork's verdict step read `ci-shards/report.json`, which its own `PLAYWRIGHT_JSON_OUTPUT_NAME` wrote. Measured on Playwright 1.63.0, `JSON_OUTPUT_FILE` wins when both are set and `JSON_OUTPUT_NAME` is then ignored — so the union of the two sides left the fork's verdict reading a file nothing writes, and the fork's scheduled lane would have failed its known-failure ratchet on a missing report. One report now serves both readers: the step keeps upstream's env var and upstream's own `--reporter=list,json`, and the verdict reads `ci-shards/results.json`. That also drops the `if repository != stablyai/orca` shell guard, which existed only to add the fork's reporter argument — upstream's lane's command is now byte-identical to upstream's, and the report the verdict reads is the one the failure summary reads. The verdict contract suite pins the interaction instead of the strings: the env var's value, the reporter argument's presence, the absence of a second output name, and both readers naming the same file. The unit-shard step is unchanged: its report arguments stay inside the guard, because upstream added no reporter of its own there.
Workflow run 36563775496 published `v1.4.214-np.15` from c8fa8fe, signed and notarized, with both update manifests (latest-mac.yml, latest.yml) present.
…ng (#43) * fix(agent-status): keep restored spinner titles from reading as working Before: a cold restore handed checkpoint.json's lastTitle — written on checkpoint ticks, so it can capture a mid-turn spinner frame (real values on this machine: "⠴ π - orca", "⠧ π - Jenkins-Details", "⠧ π - Remove-Team") — into the PTY record and title tracker via seedTerminalRestoreTail -> applySeededAgentStatus. The (re)attach snapshot in getTerminalSideEffectSnapshot replayed that title to every surface, and because normalizeTerminalTitle canonicalizes braille frames but never strips them, the restored title classified as working through detectAgentStatusFromTitle. The sidebar worktree row, the worktree card, and the tab bar all flipped to working together and only self-corrected via the 30s cold-park delays and the 60s watchdog heal. After: the title tracker records provenance — a seeded title is restored state this process never observed on the wire, and the first live frame (including synthetic hook frames) confirms it. The replay snapshot stamps such facts restoredUnconfirmed (mirroring the agent-status hydration stamp), and the renderer fact handler — the single choke point every reader's titles flow through — strips working indicators from an unconfirmed fact that asserts working, feeding readers a title that can never claim working until a live frame re-asserts it. Both title fields are cleared so the completion coordinator cannot arm on the restored raw title and later mint a false task-complete. Permission claims ("π !") keep signalling, the OMP static marker is rewritten through its protocol table ("π :" -> "π >"), and observed titles — park/unpark and worktree-switch replays — pass through untouched. The treatment is host agnostic: local-daemon and SSH PTYs share the tracker and fact handler, and remote clients run the same handler. Route: clearing only the record-title fallback (where normalizedTitle is null) would not have fixed the bug. The restore seed populates the tracker, so the working title is served from the non-null tracker branch (a runtime probe returned "⠋ π - Jenkins-Details" with a tracker entry present). Since normalizedTitle being null does not identify a cold restore, the fix takes the provenance route: explicit restoredUnconfirmed state plus reader-side treatment, never a blanket clear on replay. Accepted, as agreed: an agent genuinely mid-turn across a relaunch keeps its indicator quiet until the next live frame re-asserts it — the same treatment restored hook rows already receive (restoredUnconfirmed state is never fresh). The same applies to the residual same-run reclaim whose seed-only title asserts working (e.g. an OMP static working marker that never repaints): quiet until its next title change. A stale working title on a PTY that keeps producing output is still healed by main's 3s stale-title timer exactly as before. The checkpoint wire format is unchanged; the fact channel only gains an optional field. * fix(lint): split the six files the sync left over the max-lines cap Before: `pnpm exec oxlint` fails with six max-lines errors, so static analysis is red on nplez1/main and on every branch cut from it. The upstream sync re-seated this fork's rate-limits, status-bar, sidebar and identity patches onto upstream files that had grown in the meantime, and the combined results crossed the 300/400-line caps. After: the linter is clean again. Per repo rules there is no max-lines disable and no cap bump anywhere — each over-cap file yields one cohesive unit to a domain-named sibling module, and the behavior is unchanged: - service-full-cycle-preparation.ts: the copilot stored-token-vs-gh credential selection moves to service-copilot-cycle-credentials.ts; the three repeated settled-result promise adapters move (as trackSettledProviderResult) onto service-sibling-provider-result.ts - service-full-cycle-application.ts: the nine settled-result to error-snapshot fallback blocks collapse into providerResultSnapshot in service-provider-result-snapshot.ts, shapes preserved exactly, including opencode-go's explicit monthly slot - tooltip.tsx: the rate-limit window unit (window derivation, usage urgency colors, per-window bar) moves to provider-rate-limit-window-sections.tsx - use-status-bar-controller.ts: the usage visibility projection moves to status-bar-usage-projection.ts - tui-agent-config.ts: the per-agent authoring table moves to tui-agent-config-table.ts - worktree-activation.ts: the sidebar reveal group moves to worktree-activation-sidebar-reveal.ts Pure refactor: no behavior changes, no test edits.
The floating workspace titlebar doubles as its move target, but once enough tabs fill the strip every press lands on a sortable tab — a no-drag target — so there is nowhere left to grab and the panel cannot be moved. Reserve a fixed-width handle at the far left of the titlebar, outside the tab strip, so it stays pressable no matter how many tabs are open. It relies on pointer events bubbling to the existing titlebar drag handlers, stays a plain div because `button` is a no-drag selector, and draws the same right border as the inter-tab dividers so the strip reads as one set of separators. Covered by unit tests for the handle's drag-target classification and a hidden renderer E2E that opens the panel with more tabs than fit, drags the handle, and asserts the panel moves.
Upstream's c85f487 moved the agent skills to internal delivery: it deleted .agents/skills/**, gitignored the directory, and now symlinks the private copies in via a $ORCA_INTERNAL_DEV_SETUP setup hook. That variable is set only by stablyai tooling and no-ops for this fork, so no worktree here ever gets those skills. Re-track the three that are useful outside the org, restored from c85f487^: electron (CDP/playwright-cli, plus this repo's ORCA_BACKGROUND_LAUNCH=1 launch rule), react-useeffect, and typescript. The auto-pr-merge / auto-review-fix / auto-submit trio is intentionally left out; it encodes stablyai's merge policy. Force-added with `git add -f` so .agents/skills/ stays ignored and the internal hook can still populate machine-local symlinks there. See LOCAL-PATCHES.md, "local(skills)".
* feat(dashboard): make the Agent Dashboard a first-class view The Agent Dashboard was a sidebar companion sheet (or a separate pop-out window) with its own toggle state, so it behaved nothing like the other sidebar entries: no back/forward history entry, and a selected state read from a boolean instead of the active view. Make it a top-level view like Tasks and Automations: switching to it sets activeView, records a back/forward history entry, and highlights the sidebar button via aria-current="page". Drop the pop-out window and the in-window drawer entirely. - add 'dashboard' to TopLevelView, UiViewHistory, the RPC view enum, and the persisted-view whitelist (startup hydration falls back to terminal when the experiment is off) - add previousViewBeforeDashboard plus open/closeAgentDashboardPage, mirroring the automations page (recordViewVisit + rewind on close) - register 'dashboard' in the shared back/forward history entries and replay - render a new AgentDashboardPage in the workspace content shell, reusing AgentKanbanBoard - sidebar entry selects on activeView === 'dashboard'; the shortcut toggles the view (still inert in Settings) - remove the dashboard pop-out BrowserWindow, its IPC, the preload dashboard API, the pop-out renderer entry, the snapshot bridge, and the drawer/host, updating main, preload, and renderer wiring * fix(lint): split the six files the sync left over the max-lines cap Before: `pnpm exec oxlint` fails with six max-lines errors, so static analysis is red on nplez1/main and on every branch cut from it. The upstream sync re-seated this fork's rate-limits, status-bar, sidebar and identity patches onto upstream files that had grown in the meantime, and the combined results crossed the 300/400-line caps. After: the linter is clean again. Per repo rules there is no max-lines disable and no cap bump anywhere — each over-cap file yields one cohesive unit to a domain-named sibling module, and the behavior is unchanged: - service-full-cycle-preparation.ts: the copilot stored-token-vs-gh credential selection moves to service-copilot-cycle-credentials.ts; the three repeated settled-result promise adapters move (as trackSettledProviderResult) onto service-sibling-provider-result.ts - service-full-cycle-application.ts: the nine settled-result to error-snapshot fallback blocks collapse into providerResultSnapshot in service-provider-result-snapshot.ts, shapes preserved exactly, including opencode-go's explicit monthly slot - tooltip.tsx: the rate-limit window unit (window derivation, usage urgency colors, per-window bar) moves to provider-rate-limit-window-sections.tsx - use-status-bar-controller.ts: the usage visibility projection moves to status-bar-usage-projection.ts - tui-agent-config.ts: the per-agent authoring table moves to tui-agent-config-table.ts - worktree-activation.ts: the sidebar reveal group moves to worktree-activation-sidebar-reveal.ts Pure refactor: no behavior changes, no test edits. * fix(test): drop the removed pop-out entry from renderer entry lists Removing src/renderer/src/popout.tsx left three stale references to it: - the renderer node-builtin boundary test walked it as an entry and threw ENOENT instead of building the graph - the react commit-cascade install-order test asserted the shim is its first import - config/knip.json listed it as an entrypoint Walk only the remaining renderer entries.
…bar (#45) * feat(issue-pane): show the workspace's linked issue in the right sidebar Add an Issue tab to the right-side panel that appears only while the active workspace is linked to a GitHub, Linear, or Jira issue. The pane renders the issue as compact vertical property rows — status, assignee, priority, labels — with an inline-editable title, a description, and an "Open in <provider>" button, so it fits the ~280px sidebar instead of reusing the Task view's wide controls. The assignee field is a typeahead: an empty query lists a short roster with the current user pinned first, and typing searches the wider directory for Jira or filters the roster for Linear and GitHub. Also extracts shared pieces so the Task view and the pane stay in sync: gh-edit-section-pills, jira-issue-title-labels-editor, and two Jira hydration hooks (useJiraIssueWorkspaceDetail, useJiraIssueComments, useJiraIssueSideData). Known limitation: adding 'issue' to STATIC_RIGHT_SIDEBAR_TABS means a newer paired client sending that tab to an older host has the small ui.set patch rejected, so the tab preference does not persist across that skew. Follows the existing static-tab precedent; a host-capability gate would close it fully. * feat(issue-pane): show and post comments from the Issue pane Add a compact comment thread and a composer to each provider pane, so an issue's conversation can be read and replied to without leaving the sidebar. - Shared IssueCommentThread: avatar, author, relative time, markdown body. - Jira reuses the Task view's JiraIssueCommentComposer, Linear reuses LinearIssueCommentFooter, GitHub reuses GHCommentComposer. - Each posts through the existing mutation path and appends optimistically, so a reply appears immediately and the Task view stays in sync. * fix(issue-pane): stop mutating the search ref during render React Doctor flags a render-time ref write in IssueAssigneeCombobox ("Ref mutated during render"): React may replay or discard render work, so the write can leak from UI that never commits. Move the latest-callback ref sync into an effect. Behavior is unchanged — useRef already seeds the value.
`reviewDecision` reports that a review is required; it does not say whether the requirement gates the person looking at the pull request. A ruleset bypass actor, or an administrator waiving classic branch protection, therefore saw "Approval required" with the merge refused, while GitHub's own merge box let them merge. Orca now asks GitHub the viewer-scoped question (`PullRequest.viewerCanMergeAsAdmin`), only for a pull request whose review gate is unmet, and offers the merge when GitHub says yes. Nothing bypasses silently: the surface must confirm, and the host only adds `gh pr merge --admin` when the flag was asked for *and* GitHub confirms the privilege. The bypass is withheld whenever a merge queue is required or its status is unknown, because `--admin` also skips the queue, and while GitHub reports the merge box blocked - which can mean required checks are failing, something the confirmation never covers. The rule lives in one shared module so the host and both presentations cannot drift.
Every fix here is a surface upstream added or rewrote after this fork's base, so no conflict marker showed it — `pnpm tc` is what found them: - `deleteBranchOfRemovedWorktree` survived the resolution as an inline body in `removeWorktree`, so `finishUnregisteredWorktreeRemoval` lost its definition. Restored as the helper upstream keeps, with the fork's remote-branch cleanup inside the per-repo serialized section. - The fork's `deleteRemoteBranch` never reached upstream's extracted `finishLocalWorktreeRemoval` or the runtime finish args, so the option was dropped between the IPC entry point and `git worktree remove`. Threaded through both, and recorded as `false` on the interrupted-removal path, whose persisted record carries no remote-branch intent — deleting a branch the user never asked for is worse than leaving it. - `remove-registered-local-worktree` sat over its max-lines budget once the option was threaded, so the orphaned-worktree recovery moved to `orphaned-local-worktree-cleanup.ts` rather than growing a suppression. - `AccountsPane` kept the `SecretAtRestProtection` import its states moved out of when the fork extracted the credential sections. - The derived English runtime catalog is regenerated (`pnpm run sync:localization-runtime-catalog`), which is what the two `--ours` resolutions taken during the replay left behind.
Upstream added a real Antigravity fetcher to the shared rate-limit harness in this sync's range, so `resetRateLimitProviderMocks` now resets it. The fork's DeepSeek and Fireworks suites mock every other provider the harness resets but not this one, so all 15 of their cases died on `vi.mocked(fetchAntigravityRateLimits).mockResolvedValue is not a function`. Same shape the ZCode fetcher arrived in last sync — the shared harness grows a provider and each suite's mock list has to follow. `claude/hook-service.test.ts` also still expected `~/.orca/agent-hooks` in one case upstream added while the fork ships `~/.orca-np`; every other expectation in that file had already been re-pointed.
Found by the full suite, all of them shapes where upstream's new test or new field met a fork surface no conflict marker connected: - **`tui-agent-config-table.ts` lost the fork's Claude trust presets.** `#34` extracted the table into a new module, so git saw a pure file add — but upstream's `21d4ae9448` added `preflightTrust` to `claude` and `claude-agent-teams` in the same range, and the extracted copy was taken from the fork's pre-sync table. The trust preflight was silently off for both launch modes; `agent-workspace-trust-spawn`'s preset census and four spawn/relay suites caught it. - **Two upstream tests hardcoded `~/.orca`** (`windows-hook-files` — a new file this sync — and `hook-process-capture`), while the fork's managed hooks write `~/.orca-np`. Both now derive from `HOME_DIRECTORY_NAME`, so the next rename cannot leave them behind. - **`FileExplorerFilesTreePane` read `nameFilterFiles` eagerly** where upstream short-circuits behind `hasNameFilter`, so the pane threw in upstream's partial fixture; and `hasError` used `!!treeError`, which reads an empty-string error as "no error" — upstream's own case says an empty message is a message. - **One upstream test mock** (`structured-agent-session-accepted-send-capability`) predates the fork's path-search capability module, which subscribes on import. - **`UNHIDDEN_SPAWNER_PIN` 60 → 59**, taken from the failing test's own message. Two upstream files moved off direct spawning in this range.
…nd Tasks close buttons The Agent Dashboard sat under an empty 36px titlebar and then spent two more rows on chrome of its own: a header carrying only a title, a count and a close button, and a toolbar carrying search and filters. The board's own columns therefore started 126px down the page. Tasks had a second, redundant exit as well — a left-anchored X sitting beside its source filters. Before: a blank band at the top of the dashboard, an X in the dashboard header, and an X on the left of the Tasks band. After: the dashboard's toolbar is its first row — search, filter and board settings — with the columns starting 81px higher, and both pages are left the way every other top-level view is. - the shell only suppressed its stacked titlebar for automations/artifacts, so the dashboard got a stripe holding nothing but the window-controls spacer; move that "page owns its top edge" decision into lib/page-owned-titlebar.ts and gate the strip on a derived stackedTitlebarVisible flag instead of an inline view comparison - delete AgentKanbanBoard's header row (title, total, close) and forward its trailing controls into AgentDashboardToolbar, which carries the same window-controls gutter the automations and artifacts headers reserve on their first row - delete the Tasks close button and its divider; Esc and the sidebar still close Tasks - add 'dashboard' to shouldShowWorktreeHistoryControls: the view already recorded a history entry, but hid the shared back/forward pair, so removing its X would have left it with no exit control of its own
`pnpm exec oxlint` fails on the fork's line with five max-lines errors — 301, 304, 305 (cap 300), 420 (cap 400) and 834 (cap 800) — so CI's Lint step fails and the verify job fails with it, which in turn skips the test and package jobs for every PR. None of the five is touched by the dashboard change on this branch; each is byte-identical to nplez1/main, and four of them were left over the cap by earlier fork PRs. Split each along a real seam. No max-lines disable and no cap change, per the repo rule, and nothing here changes behavior: - shared/agent-session-capability.ts: the agent-session capability IDs move out of protocol-version.ts and are re-exported from it, the pattern ./skill-install-capability already follows (304 -> 284) - ipc/worktrees/listing/list-repo-worktrees.ts: the scan pipeline that worktrees:listAll and worktrees:list carried verbatim in both handlers — around 55 duplicated lines — now lives in one helper that both call (305 -> 151) - native-chat/agent-session-wire/structured-agent-session-adapter-errors.ts: the adapter's refusal vocabulary and pre-spawn guard, re-exported so the claude, codex and runtime callers keep importing them from the adapter (301 -> 245) - right-sidebar/FileExplorerQuerySection.tsx: the explorer's query band — view switch, both cross-faded query rows, the truncation notice, the Contents filters (420 -> 365) - ipc/worktrees-local-setup-launch.test.ts: the four setup/startup-terminal tests move out of worktrees-local-create-flow.test.ts, which keeps its own copy of the shared mock prologue as every other suite in that folder does (834 -> 585; 20 tests before and after) Also regenerates en-runtime-required.json. The close-button strings this branch stopped referencing are still in en.json, and an English entry with no call site has to ship — a dynamic key resolves from the catalog, so the tool cannot tell it apart from a live one. `pnpm run sync:localization-runtime-catalog` adds those five entries. Verified: `pnpm lint` exits 0 (it previously stopped at oxlint), `pnpm tc` exits 0, and the suites around every split pass — src/shared 10,466 tests, src/main/ipc + src/main/claude + src/main/codex 8,934, right-sidebar 2,248. The native-chat folder keeps the 17 failures it has on nplez1/main (reproduced with this branch's files reverted).
…rectory
The Windows packaging job runs src/main/codex/hook-service-managed-install.test.ts, whose
three win32-only assertions build the expected managed command under a literal '.orca'
while the build installs into HOME_DIRECTORY_NAME ('.orca-np', the fork's rename). The
job's own failure prints both strings: 'The Expected' at .orca/agent-hooks/codex-hook.cmd
against 'Received' at .orca-np/agent-hooks/codex-hook.cmd, so the two failing tests are
comparing the same path two ways.
Derive every expectation here from HOME_DIRECTORY_NAME instead, as the production path
(agent-hooks/installer-utils.ts) already does, so the name cannot drift again. Two of
these four lines are on the win32 branch a macOS run skips, which is why this sat unseen:
the Windows job only started running once static analysis stopped failing ahead of it.
Tasks sat under the same empty 36px titlebar stripe the dashboard did: the shell draws one above every stacked-sidebar view, and Tasks renders nothing in it, so its source-icon row started 42px down the window with a blank band above it. Before: a blank band above the Tasks source row, whose center sat 58px down the page. After: that row is the page's top edge — its icon cluster's center lands 22px below the window top, the same first-row height the dashboard now uses — and everything below moves up with it, so the list card starts 36px higher. - add 'tasks' to lib/page-owned-titlebar.ts, the predicate the shell already consults for automations, artifacts and the dashboard - the source row is now the top edge, so it needs the window-controls gutter the automations/artifacts headers and the dashboard toolbar carry. It sits inside the frame's px-5, so .tasks-source-row-window-controls-inset pads by only the remainder past that inset and stays a no-op on macOS, where the var is 0px - Frame.tsx keeps pt-1.5; its comment claimed the cluster aligned with the sidebar's first row, which is no longer the intent now that Tasks owns the top edge With the sidebar collapsed nothing changes: the shell still draws its full-width titlebar (36px, drag region and window controls included) and the page starts below it.
9 tasks done
… launch (#50) The floating workspace launched its default agent from a boxed 24px outline button in its window controls (border, secondary fill, shadow), next to maximize and minimize, while its tab strip kept a plain "+". The main window's tab bar has one control for the same job: a 28px split button whose primary launches the default agent and whose chevron (or a hold) opens the create menu. Side by side, the same action looked like two different controls. Before: [tab][tab] + … boxed [agent][⤢][−]. After: [tab][tab] [agent][⌄] … [⤢][−], the same split control the main tab bar renders, with the window controls back to being window buttons. - TabBarNewTabButton now names where its agent comes from instead of a boolean that meant "hide it": `detected` (the configured default, only where the host detects it), `configured` (the settings default alone), and `none` (plain "+"). The floating panel asks for `configured`, because its synthetic worktree has no detected agents and its launch path resolves a genuinely missing agent itself. - FloatingTerminalWindowControls loses its agent button and the launch path behind it; the split control's launch is the shared `launchAgentFromNewTabEntry`, the same one the create menu's agent entries already used from the floating tab strip. That path reports `launchSource: 'tab_bar_quick_launch'` rather than the deleted button's `'shortcut'`, which is what it is now — a tab-bar quick launch. - The launch-caller fixture's floating profile follows the call site: same synthetic worktree id (no repo behind it), now through the tab bar's launch with the group it resolves to. - The three guarantees the deleted button's tests pinned (launch through the shared funnel rather than the tab store, focus the launched terminal on menu close, report a launch the planner refused) move to use-tab-bar-create-menu-controller.test.ts, the code that now owns them. The fourth, "the floating workspace stays off the structured route", is a shared resolver invariant that structured-native-chat-launch-route.test.ts already pins. - Also regenerates en-runtime-required.json: the two strings the deleted button referenced are still in en.json, and an English entry with no call site has to ship — a dynamic key resolves from the catalog, so the tool cannot tell it apart from a live one. Verified: `pnpm tc` clean, full `pnpm lint` clean (including the localization verifiers), changed-code gate clean, and the tab-bar, floating-terminal and launch-caller suites pass (89 files, 1001 tests). Rendered in the dev app with an agent configured: the strip shows the split control and the window controls show only maximize and minimize.
nplez1
pushed a commit
that referenced
this pull request
Oct 19, 2026
…iew off the deprecated owner resolver (stablyai#30562) * refactor(renderer): fold browser creation, file actions and doc-preview callers off the deprecated owner resolver The creation-action policy and browser creation (#3, #48) fold together, so the provider the policy offers is the one creation uses. Browser panes, page lifecycle and conversion, tab duplication, the explorer tree pane, doc-preview actions and editor path moves (#21, #43, #45-#47, #49, #50) read runtimeEnvironmentIdWithLegacyFallbackUnlessTwin. File-explorer writes on a folder (#51) take the owner from the rows alone, never from the focused server. Deprecated importers 16 -> 6. * refactor(renderer): fold file preview off the deprecated owner resolver; Open in Orca Browser refuses a twin A project two hosts publish reads as no server for the doc-preview plan, but the file:// path now refuses it with its own message instead of opening this machine's copy (#7). Deprecated importers 6 -> 5. * fix(renderer): a twin's file preview and open-externally refuse instead of opening this machine's copy Codex P2 on the F7 folds: a folder twin read as no server, so the preview plan built a local file:// tab and open-externally handed the path to this machine's OS. Both now check the owner for ambiguity first. Also fix two test fixtures that broke the renderer typecheck. * refactor(renderer): move the file:// open target out of file-preview.ts file-preview.ts crossed its line limit after main grew it; the file:// target, its type and its two refusal messages move to workspace-file-browser-open-target.ts, which they never depended on file-preview for. * test: mock the replacement resolver in the quick-action availability suite (Codex P2)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
The Agent Dashboard had a blank strip above everything, and its first row was mostly a title and an X to close it, so the actual board started 126px down the page. Tasks had the same blank strip plus an X of its own on the left, beside its filters. Both Xs are gone, the dashboard's search bar is its first row, and the Tasks source row is that page's first row, so each starts 36px higher than before — 81px for the dashboard, which also lost its separate title row.
What Changed
Before: opening the Agent Dashboard left an empty 36px band at the top (the app shell draws a titlebar strip there for the sidebar views), then a header row holding only "Agents", a total count, a settings gear and a close X, then a toolbar with the search box and filter. The kanban columns began 126px from the top edge. The Tasks page carried a left-anchored X ("Close tasks", tooltip "Close · Esc") before its source filters.
After: the dashboard's toolbar — search, ⌘K, Filter, board settings — is its first row, flush with the top of the window, and the columns start at 45px instead of 126px. Tasks' source row is now that page's top edge, its icon cluster centred 22px below the window top (the same first-row height the dashboard uses), so the list below it starts 36px higher. Neither page shows a close control any more; Tasks is left with Esc or the sidebar, and the dashboard is left with the sidebar or the back arrow.
Mechanism:
automationsandartifacts, so the dashboard and Tasks each got a stripe containing nothing but the window-controls spacer. That decision now lives inlib/page-owned-titlebar.tsand the shell gates the strip on a derivedstackedTitlebarVisibleflag fromuseAppChromeLayout, instead of an inline list of view names. Tasks joined the list once its own band was reclaimed, which is why the same change lands here rather than in a follow-up.AgentKanbanBoardlost its header row (title, count, close) and itsonCloseprop; itsheaderActionsbecametoolbarActions, forwarded intoAgentDashboardToolbarastrailingActions. That row now reserves the same Windows/Linux window-controls gutter the automations and artifacts headers reserve (paddingRight: max(0.75rem, var(--window-controls-width, 0px))).task-page/SourceBar.tsxlost the close button and the divider that separated it from the source icons. The Esc handler inuse-task-page-global-effects.tsis untouched.px-5, the new.tasks-source-row-window-controls-insetpads by only the remainder past that inset and stays a no-op on macOS.'dashboard'joinedshouldShowWorktreeHistoryControls. The view already recorded a back/forward history entry when it became a first-class view, but hid the shared arrow pair, so removing its X would have left the only full-page view with no exit control of its own.Why
A 36px empty band plus a header row that carried one word, a number and a close button is chrome spent on nothing on a surface whose whole job is showing as many agents as possible. The two changes follow the two patterns already in the repo rather than inventing a third: pages that draw their own top edge declare it (like
automations/artifacts), and a full-page view is left the way every other one is (sidebar, back control, or Esc) instead of by a bespoke X that exists on no other page.Alternatives considered and rejected: keeping the title row and only dropping the empty band (saves 36px of 126px, leaves the duplicated chrome the user flagged), and overlaying the header on the columns (would cover the column headers).
Linked Issue
N/A — fork-local change against the fork's own default branch.
Visual Proof
Agent Dashboard. Before — empty band above "Agents", header row with the X at top right, columns pushed down:
After — search/filter/settings are the first row, columns start 81px higher, no X (the ← back arrow is now in the titlebar):
Tasks. Before — X at the left of the source-filter band, under the same empty 36px stripe:
After — the source icons are the leftmost item and the row is the page's top edge:
Measured in the running app over CDP (sidebar open): the dashboard's columns started at
y=126and now start aty=45, with its toolbar row 45px tall aty=0; the Tasks source row started aty=42(band 0–36 plus the frame's 6px) and now starts aty=6, its 32px cluster centred at 22px. With the sidebar collapsed the behaviour is deliberately unchanged for both — the shell still draws its full-width titlebar aty 0–36(drag region plus window controls) and the page starts aty=36.Testing
pnpm tc(all TypeScript projects) passes.src/renderer/src/components/dashboard,dashboard-popout,app-shell, thelibtitlebar/page-titlebar helpers and the page-navigation slice — 60 files / 467 tests pass. Added: a board test asserting no title, no count and no close control with the board controls living in the toolbar row; apage-owned-titlebartest; an app-command-handlers test asserting the dashboard claims the back/forward chords and a non-history view does not; and the dashboard case in the history-controls test.pnpm run check:code-quality:changedreports 0 findings across the 13 changed files;oxfmt --checkclean on the same files.ORCA_BACKGROUND_LAUNCH=1):tests/e2e/agent-dashboard-sidebar-toggle.spec.tspasses, now leaving the dashboard through the shared Go back control and asserting the entry unselects.Gates that were red on the fork's base are fixed by the second commit (
fix(lint): split the five files that left the fork's static analysis red):pnpm lintnow exits 0 (was exiting 1). It stopped at oxlint on fivemax-lineserrors in files byte-identical tonplez1/main—right-sidebar/FileExplorer.tsx(420/400),native-chat/agent-session-wire/structured-agent-session-adapter.ts(301/300),ipc/worktrees/listing/register-worktree-catalog-handlers.ts(305/300),ipc/worktrees-local-create-flow.test.ts(834/800),shared/protocol-version.ts(304/300) — which failed CI's Lint step and, through the verify job, skipped the test and package jobs entirely. Each file is now split along a real seam to 365, 245, 151, 585 and 284 counted lines, with nomax-linesdisable and no cap change. The commit also regeneratesen-runtime-required.json, because the close-button strings this branch stopped referencing are still inen.jsonand an English entry with no call site has to ship (the tool cannot distinguish it from a dynamic key).pnpm test(100,467 tests) still has 63 failures in 10 files, none of them in a file this branch touches:src/main/runtime/agent-session-*,src/main/native-chat/agent-session-wire/*,src/main/runtime/rpc/methods/agent-launch-replay.test.ts,src/main/browser/browser-manager-*.test.tsandtests/e2e/relay-region-correction.unit.test.ts. Verified pre-existing: with this branch's modified files reverted toorigin/nplez1/main, three representative files still fail (3 failed | 30 failed | 35 passed), and the native-chat folder keeps its 17 failures with the adapter split stashed.pnpm buildwas not run locally (it would needpnpm install:releasefor both macOS architectures); left to CI.Two jobs that only began running once static analysis stopped failing ahead of them were red for
pre-existing reasons, and both are fixed here:
src/main/codex/hook-service-managed-install.test.ts: three win32-only assertions built the expected managed-hook command under a literal.orcawhile the build installs intoHOME_DIRECTORY_NAME(.orca-np). The job's own output prints the same path both ways. The expectations now derive from that constant, asagent-hooks/installer-utils.tsdoes.v1.4.211,v1.4.212) and the harness throws rather than skipping when a ref will not resolve; the fork's remote carried only its ownv1.4.214-np.*tags, sogit rev-parse v1.4.211^{commit}failed on CI. Both release tags are now mirrored into the fork (git push origin v1.4.211 v1.4.212), which is repo state rather than a commit, so the lane materializes those releases and runs its nine journeys.All 21 checks pass on
eb00d20c6a, and a full localpnpm testwith every commit here applied reports the same 10 failing files / 63 tests as the base, so the five splits add no failures.Platforms: macOS (dev). The window-controls gutter on the dashboard's first row is the same expression
AutomationsPageTopBar/ArtifactsPageuse, so Windows/Linux keep their controls clear of the settings gear; no keyboard bindings were added or moved.AI Disclosure
Implemented with an AI coding agent (pi), reviewed by the author.
Review
Agent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
en-runtime-required.jsonnow carries those five (it had carried none of them). Deleting them fromen.jsonand the five translated catalogs instead would be the leaner end state — the runtime-catalog tool cannot tell a dead key from a dynamic one, so it ships both — and that cleanup is worth its own PR if you want the boot bundle trimmed.-webkit-app-region: dragis not set on the dashboard's new first row — same as the automations and artifacts headers, which also start at the top edge. The window stays draggable through the sidebar's titlebar (and the full-width titlebar when the sidebar is collapsed).src/renderer/src/components/dashboard-popout/folder anddashboardPopout.*i18n namespace, as in feat(dashboard): make the Agent Dashboard a first-class view #44.fix(codex)commit touches a file no other branch owns, so it is safe here; if you would rather it live on its own branch, it is a two-line change to cherry-pick out.shared/protocol-version.tsis the thinnest of the five splits at 284 counted lines against a 300 cap, so the next capability that lands there may need the same treatment — the domain-module-plus-export *pattern the file already uses for./skill-install-capabilityis the move.Checklist
N/Awith reasonpnpm lintandpnpm typecheckpass locally.pnpm testis red only on the 63 pre-existing failures listed under Testing, all in files this branch does not touch;pnpm buildis left to CI.