* ci: fail when a checked-in test file has no runner
Two node:test files under .github/actions lost their runner when #31792
trimmed cloud pretest, and the two monaco .bench.ts budgets were never run
by any CI job. Wire both back in and add a static check that every
test-like file is collected by a declared runner or imported by a
collected entry (the src .spec.* suite-module convention).
* ci: ignore type-only imports and quote the lease glob for cmd.exe
* ci: parse type-only imports, cover UNIT_EXCLUDE, route cloud runner edits
Type-only imports are now dropped with the TypeScript parser, so a type
alias before a runtime re-export no longer hides it. UNIT_EXCLUDE files
must be named by a pr.yml or unit-tests.yml step. A cloud-only diff that
adds a test or edits a runner config now runs the static job. The monaco
benchmarks stay manual (pnpm test:perf:benchmarks) until a CI runner
proves their wall-clock budgets.
* ci: require UNIT_INCLUDE names in dedicated jobs and erase typeof import()
* ci: resolve each runtime import to the one file Vite loads
The unit dependency graph keeps every plausible candidate so it can
invalidate conservatively, which cannot prove a suite runs: an import of
./case.spec counted both case.spec.ts and case.spec.tsx. Resolve with
Vite's extension order instead.
* ci: take runtime imports from transpiled output
Imports used only as types are erased without a type modifier, so
parsing for type-only syntax still followed them. Derive edges from
ts.transpileModule output, which erases what Vitest's transform erases,
and only transpile files that can reach an uncollected candidate.
* ci: fix the agent-status benchmark config and route all cloud code
The agent-status benchmark config inherited the unit projects, whose
includes override its own, so it ran the whole unit suite and never the
benchmark. Clear projects as the performance config does. Route every
cloud code file to the static job, since content-detected tests can have
any name.
* ci: move routine checks and unsigned builds to Blacksmith
* ci: retain GitHub Mac images for isolated SSH fixtures
* ci: keep shared terminal perf preparation on Blacksmith
* test: allow encrypted slow-link fixture to finish on ARM CI
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* ci: run relay integration on the Blacksmith x86 pool
* ci: use the validated Blacksmith pool for Linux packaging
* test: allow shared Linux download caches across validated runner pools
* Use incremental tsrs for local typechecks
* Update pinned Bun to 1.4.4
* fix(deps): bump dompurify to 3.4.17 and docs next to 16.4.1 for security advisories
dompurify 3.4.17 patches GHSA-cg2g-xh64-v4q7, GHSA-mrv2-3pqr-j5jr and
GHSA-c6gr-qvv4-5v3f in the root and mobile lockfiles. next 16.4.1 patches
GHSA-2464-fhr4-w9xw, GHSA-673q-mjm6-r5xr and GHSA-p3m5-59mp-jv5r in the
docs site.
* Prepare mobile bundle for smoke benchmarks
* Give native compiler benchmarks a larger ARM runner
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* Add Orca's own bundled-skill planner and installer (not wired in yet)
One planner decides what installing Orca's bundled skills does to each
target folder; execute re-plans under per-path locks and runs only a plan
whose digest still matches; a shared summary derives badge, wording and
exit code. A copy counts as Orca's untouched one only when its SKILL.md
bytes match a version some build shipped (resources/skills/released-digests.json,
generated from git history and checked add-only).
Nothing calls this yet; the npx path is unchanged.
* Report left-alone folders as warnings and keep backups in the home
A leave is no longer a failure: the outcome line says what happened plus
"N left alone: <reason>", and the exit code is 1 only for a failed or
changed op. Backups for every scope go to <home>/.agents/.orca-skill-backups
on the executing host, so a project Replace never writes into the repo;
moving there falls back to copy, verify, then remove across filesystems.
* Update Orca's skills with Orca's own installer instead of npx skills
The app's Update skills dialog, badge, nudge and status pills now read the
bundled-skill plan and summary, and Update executes the reviewed plan's
digest. `orca skills install|update` runs the same planner in-process with
--dry-run, --force, --json and per-summary exit codes. Removes both npx
spawn sites, the freshness inventory modules and the npx round-trip CI.
* Update only where the skill already is; one provider-root rule for app and CLI
installedOnly now drops every location that holds nothing (keeping the shared slot
only when an existing agent link points at it), so Update never adds a skill for
another agent. The app and CLI resolve the Claude skills folder through the same
rule in localBundledSkillInput; a test asserts both plan identically.
* Compact Update skills dialog: one row per skill, locations named by agent
summarizeBundledSkillPlan now returns each skill's locations, labelled by
agent (or "Shared folder") with a ~-collapsed path; the dialog shows one row
per skill that needs something, collapses up-to-date skills into one line, and
lists every location behind "Show all locations". The result reads "Updated N
skills · M left alone". Under installedOnly a Replace with no shared copy
replaces in place instead of adding one.
* fix(skills): keep location paths readable by putting the state under the path
* fix(skills): hold the last plan in state, not a ref mutated during render
Split the update dialog's rows, headline and footer into components, move the Update label
next to the other plan copy, and use z.strictObject for the request schemas.
* Escape non-ASCII characters in bundled-skill regex and wording
The orcad bundle rejects a literal non-ASCII character in a regex.
* fix(cli): escape non-ASCII apostrophes in the skills install help text
* test(orcad): check the bundled CLI plans a skill install without npx
Drop the deleted roundtrip workflow's group from the closed-PR cancellation fixture.
* Make bundled-skill test fixtures link folders the way the product does on Windows
Directory links in fixtures are junctions on Windows, which need no
Developer Mode; the one file-link fixture is skipped there.
* Resolve legacy skill agent keys canonically
* Reject stale skill replacements when auxiliary content changes
* Keep ordinary bundled skill classification shallow
* Leave unreadable skill replacements alone without aborting plans
* Pass force-all through shallow planning and describe unreadable copies
* Clarify skill update statuses and folder details
* Keep selected skill replacements in place with one status
* Report completed skill replacements with their backup confirmation
* Clarify available skill updates and skipped copies
* Keep skill update results in preview order
* Show each skill skip reason once
* Count skipped skills instead of skipped folders
* Make installer input and link tests work on Windows
* Bound bundled skill classification reads and parse names linearly
* Keep workspace skill roots inside the resolved workspace
* Compare workspace skill roots with native path semantics
* Restrict bundled skill backups to their owner on POSIX
* Keep recovered skills needing attention until they are updated
* Count completed skill updates in the status tooltip
* Sync the runtime localization catalog
* fix(L273-core): tabs this desktop created in a paired server's frame-drawn workspace survive the server's frames (switch-on)
A tab the server's layout never lists (a client-hosted browser tab today; any kind recorded the
same way) used to vanish on the server's next frame rebuild. This desktop now records such tabs by
server and workspace, in its localStorage only (never a session field nor sent), and the server's
frame builds keep them as drawn, inside the server's own groups:
- A held tab drawn in a group the server lacks moves to the nearest server group; it shows there
only if the user was on it, and the user's group follows it. The group tree stays the server's.
- The record forgets an id once the server's frame lists it, on close, and on worktree removal,
and prunes ids the restored session no longer has before the first frame after a reload.
- A held tab's drag moves it here only (no session.tabs.move); it offers no new split, and a
server tab sharing its group counts only the server's tabs for split no-ops.
- Client-hosted staging records its tab (both ids) in layout-owner builds; restage takes the
worktree from its caller instead of scanning the store.
* refactor(L273-core): the desktop-created record is inert with the switch off; clearer names
- With the switch off the record is never read, kept or written (a single gate in the record).
- The frame-drawn check is isDesktopCreatedTabInFrameDrawnServerWorkspace in
frame-drawn-desktop-created-tabs.ts: "held" stays the frame hold's word.
- The held-tab placement drops its unread activeGroupId; the group follow derives it.
* wip(L270): a host advertising agentSession.create.tab-placement.v1 opens a created chat's tab where the creating client asked, in its own group and tab ids
* test(L270): a switch-off windowless host saves a chat opened with no client place at the end of the remembered active group, as before (test only)
* wip(L270): a desktop's chat in a frame-drawn server workspace stays put: the frames hold its provisional tab until the server lists it, and the create names its group and the server tab before it (layout-owner build only)
* test(L270): a switch-on serve saves a client's chat after the tab it named; the server-layout read moves to one e2e helper (test only)
* refactor(L270): the create's tab place keeps its own name, and the layout rule's comment names the client's place first
* refactor(L270): the create's tab fields live in their own schema file and the windowless save passes its chat through, so no file passes its line limit; the placement test types its mocks without assertions
* refactor(L270): the chat's place names server tabs through H5c's route, which also skips tabs the server's frames do not hold
* fix(L270): a chat opened while a reconnected server stream awaits its snapshot still names its group; the layout-owner e2e list runs the placement spec
* fix(L270): only a create in a frame-drawn server workspace awaits its tab place, so every other create keeps its timing (the eager History joiner after an early terminal close opens again)
* fix(native-chat): read a frame-drawn chat tab's place without a tick, so an eager History joiner's create is still sent
* test(native-chat): type the create's params so an async placement cannot compile; name the probe that fills the cache
* test: type the placement mock's original through a type import
* wip(h5c-1): server workspace layout intents go to the server's main; positions per stream
* wip(h5c-1): e2e without a cast
* fix(h5c-1): a routed move never moves here; routed callers read the server's stream; a restarted main starts its positions over
* fix(h5c-1): layout-apply-send back under its line limit
* test(h5c-1): a routed answer reaches the handler with the server's stream key
* Editor tabs main-first: the window sends each editor action to main and draws editor tabs only from main's frames (dark, layout-owner build only)
With ORCA_LAYOUT_OWNER_IS_MAIN on, editor open (preview replace, open to the side), reopen at
its position, close, close-all, promote, the resolved conflict placeholder, a restored-editor
move and a disk rename are layout commands the window sends before it draws anything. Window
content (open file records, drafts, buffers, the autosave gate) is written at once for an open,
and rekeyed in the same store update as the frame for a move or rename. Main publishes each
non-edit tab's view, keeps views in memory across applies and partition changes, stamps the
tab's time, folds same-file duplicates, and republishes a windowless host's session.tabs after
its own file open. The switch-off path is unchanged.
* fix(terminal): a CLI or phone close through main keeps a pinned tab, as the window-era close did
* feat(terminal): main applies phone closes, renames, pane titles, sleeps and agent-launch tabs itself, with or without a window (layout-owner build)
* fix(terminal): a tab close through main also ends the tab's dormant SSH relay session; agent-launch tabs main writes keep the window's focus rule, prompt and outcome (layout-owner build)
* feat(terminal): main starts a new tab's first terminal under the window's ids, and the pane attaches to it (layout-owner build)
* test(terminal): tests that counted the window's create calls or publications see main's start of a new tab
* fix(terminal): an account-switch restart binds only into a pane main's layout holds, with no window tree (layout-owner build, L197)
* test(terminal): the new-tab start tests hold no type assertions; the start's ledger row is L252
* fix(terminal): a new tab's pane attaches to any terminal main binds it to, and a failed start keeps the tab's startup
* test(terminal): a Claude account-switch restart's tests draw the pane main holds
* test(d3a-2): the buffer view index follows a rename main answers
* fix(terminal): a launch pane main takes back still tells the window why, which drops its hold and prompt; a rename through main answers the name it stored (layout-owner build)
* fix(terminal): a new tab another owner's terminal won attaches to it, and a failed start's startup never runs twice
* fix(terminal): main's pane close reads its stop result as a type only, so no runtime module loads before its base class
* fix(terminal): a phone's close through main drops the closed rows from what phones list at once, not at the window's next publish (layout-owner build)
* chore(terminal): mark the phone-close row drop interim (ledger L260)
* test(worktree-create): a new local tab's startup counts as run when main starts it (layout-owner build)
* test(worktree-create): record main's starts without a type assertion
* fix(terminal): a tab closed before main declines its start gets no startup back (layout-owner build)
* feat(h5d): hand desktop-only editor tabs to a layout-intents server before its frames draw the workspace
A paired desktop dropped an editor tab it opened in a server workspace (with its open file and
unsaved draft) on the server's next records frame once that workspace became frame-drawn, and
editor opens made after gating hit the same loss.
- openEditorTab is now on SERVER_ROUTED_LAYOUT_COMMANDS: the proposed tab id and group are kept,
the file id becomes the server's own, and the record loses the desktop's runtimeEnvironmentId.
- Before a workspace gates, server-layout-frames hands its desktop-only editor tabs to the
server's main (sendToServerMain), recomputes them on each answer, and gates only when all
answered ok or tab_exists. Any other refusal or a failed call keeps it on the mirror for that
connection; the next one retries. No timer, no hold store.
* fix(h5d): only an applied open counts as handed over; tab_exists keeps the tab on the mirror
* fix(terminal): the window's new tab starts in main when its own window asks over IPC, which labels itself a client (layout-owner build)
* feat(h5c-2): a paired desktop's writes to a server workspace its frames draw land in that server's main (layout-owner build)
- Routes setTabProps, setChatPane and non-terminal renameTab to the server's main as layout.apply;
closes, starts, sleeps and terminal names keep the RPCs that main now applies (session.tabs.close,
terminal.close, terminal.split, session.tabs.createTerminal, terminal.rename). A window-style
closeTabs is never routed: main takes it as already stopped and stops nothing.
- A close the server refuses (a pinned tab) redraws the workspace from the server's frames, so the
window's optimistic drop never outlives the answer; same for a refused pane close.
- A frame-drawn workspace keeps none of the mirror's ledgers (L240): no tab-id mappings, close or
focus intents; they stay only for servers without layout.intents.v1 until the Phase 2 cutoff.
Its pane-geometry push is skipped (it goes as intents); pane renames still push.
- e2e: paired-desktop-server-layout-writes covers close (layout, session.tabs, terminal stop),
rename, split and new tab on a windowless switch-on serve.
* fix(h5c-2): an undelivered close of a frame-drawn tab is held for replay; a launch-agent clear stays off the server route
* refactor(h5c-2): the undelivered-close test moves to its own module, keeping the lifecycle file under its line cap
* fix(h5d): a tab closed while its hand-over was in flight is closed on the server before the workspace gates
* fix(h5d): an editor tab closed in a workspace a server's frames draw closes on that server by session.tabs.close; a tab kept on the mirror closes here only
* fix(h5d): a handed-over tab closed here before the gate is closed on the server first; a server-opened tab's close in a frame-drawn workspace is covered
* chore(h5d): a closed stream also closes the hand-over its hand-back started
* refactor(h5d): move the frame stream's routing inputs out of server-layout-frames to keep it under the line cap
Main's #31869 grew server-layout-frames past 300 lines once merged with H5d; the two helpers move unchanged.
* feat(native-chat): a Pi chat shows the turns made outside it
When a Pi chat's agent opens its conversation, Orca asks Pi for the entries
saved after the point the chat last accounted for (get_entries since that
entry id) and adds the terminal's turns once, in Pi's order, under the
"Continued outside this chat." divider. That point is saved on the
conversation handle after each chat turn and at each open. When the chat's
own unsaved work and the terminal's share a suffix that can't be split
(Orca died mid-turn), that suffix is skipped rather than duplicated, and
later terminal turns show normally. A Pi reply over the 8 MiB line limit now
fails only the optional read that asked for it instead of ending the chat.
* refactor(native-chat): share the catch-up open runner across agents
The Codex catch-up's agent-neutral parts move to one module: the open-time
envelope (its own deadline and cancellation, never gating the open), the
bounded staged batch, the single settlement commit that re-checks the
journal before writing, and the "Continued outside this chat." divider.
Codex keeps only how it reads its saved turns and what the chat already
holds. No behavior change; the other agents' catch-up builds on this
instead of copying it.
* refactor(pi): catch up through the shared catch-up runner instead of a copy
* chore: remove unrelated pnpm executable metadata
* refactor(native-chat): the catch-up runner reads the chat's view only when the agent asks
* refactor(pi): read the chat's view through the runner's view()
* chore: remove regenerated pnpm executable metadata
* fix(native-chat): the catch-up deadline also bounds waiting for its commit to be written
A journal write barrier that never settled after the import was admitted held
the provider's open past the catch-up deadline. The wait now ends with the
deadline or the open's cancellation, like every other step.
* test(pi): check Pi's session entry ids and get_entries since against the pinned real Pi
* test: type the history catch-up sink fixture without assertions
* fix(native-chat): bound and revoke history preparation
* test: the runner's journal fake answers the cursor Pi's view reads
* feat(native-chat): validate expected saved items during history catch-up
* fix(pi): preserve outside history and optional read recovery
* test(pi): fail RPC smoke promptly when the child stops
* wip(h5c-1): server workspace layout intents go to the server's main; positions per stream
* wip(h5c-1): e2e without a cast
* fix(h5c-1): a routed move never moves here; routed callers read the server's stream; a restarted main starts its positions over
* fix(h5c-1): layout-apply-send back under its line limit
* test(h5c-1): a routed answer reaches the handler with the server's stream key
* fix(terminal): a CLI or phone close through main keeps a pinned tab, as the window-era close did
* feat(terminal): main applies phone closes, renames, pane titles, sleeps and agent-launch tabs itself, with or without a window (layout-owner build)
* fix(terminal): a tab close through main also ends the tab's dormant SSH relay session; agent-launch tabs main writes keep the window's focus rule, prompt and outcome (layout-owner build)
* feat(terminal): main starts a new tab's first terminal under the window's ids, and the pane attaches to it (layout-owner build)
* test(terminal): tests that counted the window's create calls or publications see main's start of a new tab
* fix(terminal): an account-switch restart binds only into a pane main's layout holds, with no window tree (layout-owner build, L197)
* test(terminal): the new-tab start tests hold no type assertions; the start's ledger row is L252
* fix(terminal): a new tab's pane attaches to any terminal main binds it to, and a failed start keeps the tab's startup
* test(terminal): a Claude account-switch restart's tests draw the pane main holds
* fix(terminal): a launch pane main takes back still tells the window why, which drops its hold and prompt; a rename through main answers the name it stored (layout-owner build)
* fix(terminal): a new tab another owner's terminal won attaches to it, and a failed start's startup never runs twice
* fix(terminal): main's pane close reads its stop result as a type only, so no runtime module loads before its base class
* fix(terminal): a phone's close through main drops the closed rows from what phones list at once, not at the window's next publish (layout-owner build)
* chore(terminal): mark the phone-close row drop interim (ledger L260)
* test(worktree-create): a new local tab's startup counts as run when main starts it (layout-owner build)
* test(worktree-create): record main's starts without a type assertion
* fix(terminal): a tab closed before main declines its start gets no startup back (layout-owner build)
* fix(terminal): the window's new tab starts in main when its own window asks over IPC, which labels itself a client (layout-owner build)
* feat(h5c-2): a paired desktop's writes to a server workspace its frames draw land in that server's main (layout-owner build)
- Routes setTabProps, setChatPane and non-terminal renameTab to the server's main as layout.apply;
closes, starts, sleeps and terminal names keep the RPCs that main now applies (session.tabs.close,
terminal.close, terminal.split, session.tabs.createTerminal, terminal.rename). A window-style
closeTabs is never routed: main takes it as already stopped and stops nothing.
- A close the server refuses (a pinned tab) redraws the workspace from the server's frames, so the
window's optimistic drop never outlives the answer; same for a refused pane close.
- A frame-drawn workspace keeps none of the mirror's ledgers (L240): no tab-id mappings, close or
focus intents; they stay only for servers without layout.intents.v1 until the Phase 2 cutoff.
Its pane-geometry push is skipped (it goes as intents); pane renames still push.
- e2e: paired-desktop-server-layout-writes covers close (layout, session.tabs, terminal stop),
rename, split and new tab on a windowless switch-on serve.
* fix(h5c-2): an undelivered close of a frame-drawn tab is held for replay; a launch-agent clear stays off the server route
* refactor(h5c-2): the undelivered-close test moves to its own module, keeping the lifecycle file under its line cap
* fix(h5c): a routed tab move anchors on the server's tabs only, and never sends an anchor the server does not hold (layout-owner build)
* fix(h5c): every routed tab move is placed among the server's tabs at the routing point, skipping desktop-only tabs (layout-owner build)
* fix(ci): recover verified pnpm setup from registry metadata outages
* fix(ci): prepare native pnpm shims after fallback verification
* fix(ci): provide Node for pnpm fallback in bare containers
* chore(cloud): update relay runtimes and security scanning
* docs(cloud): record Go binary advisory reachability
* chore(cloud): rerun security checks after resolving main conflict
* ci: propose downloads badge updates through pull requests
* fix(i18n): align Portuguese CLI version message with current catalog
* test: provide locale in queued editor translation mock
* ci: install fish 4 from its GitHub release when the Launchpad PPA is down
* TEMP: skip the fish PPA to prove the GitHub release fallback (revert)
* Revert "TEMP: skip the fish PPA to prove the GitHub release fallback (revert)"
This reverts commit 58ba0c5074.
* ci: install pinned fish directly from its verified release
* ci: isolate standalone fish from runner resources
* Prepare template assembly during qualification and cache Intel downloads
* Limit early template qualification polling to fifteen seconds
* Verify Windows retains its download cache policy
* Drop Intel store restoration after measuring its overhead
* Parallelize adhoc Mac architecture packaging
* Include parallel Mac jobs in release timing reports
* Cover parallel Mac publication and failure gates
* Explicitly scope archive targets to each Mac architecture
* ci: one list for Windows boundary tests and one step for ratchets
The Windows boundary step's 74 test paths were hand-listed in pr.yml and
again in WINDOWS_PACKAGE_TESTS, and the copies had drifted: six files ran
in the step but could not trigger the job. The step now prints
WINDOWS_BOUNDARY_TESTS, which WINDOWS_PACKAGE_TESTS spreads.
The ten per-ratchet preflight steps and the eight lint entries become one
run-ratchets.mjs that runs every check-*-ratchet(s).mjs it finds.
* fix(ci): preserve existing local lint ratchet scope
* fix(runtime): a phone reads and saves Markdown tabs on a headless server
With no window to relay through, markdown.readTab and markdown.saveTab answered
renderer_unavailable. Main now reads the tab's file (or its saved draft) from disk and
saves with a version compare plus the disk write precondition, in the shapes the
window's editor relay returns. With a window attached the relay is unchanged.
* Headless Markdown save: keep the draft's disk baseline, honour read-only tabs, re-find the record to clear
* Use the disk baseline only for a draft
* ci: run the phone Markdown spec's orcad case in the orca serve mode-switch job
* Headless Markdown: answer from disk only; a window's saved draft stays its view state
Clearing a saved draft wrote workspace layout from main, which no layout command carries.
Main now never shows or edits a draft; the desktop's restore check reports a disk change.
* Phone Markdown: one path through main and the disk, window or not
A paired server's workspace is read and saved on that server with files.read /
files.writeIfUnchanged, so it answers from its own disk and keeps the precondition.
The window relay (ui:mobileMarkdownRequest and the renderer bridge) is removed.
* e2e: a phone saves a Markdown tab the desktop holds unsaved edits in
The phone reads the disk with the tab marked dirty; its save lands on disk, and the
desktop keeps its draft and shows the changed-on-disk banner.
* Group the Markdown disk host's imports
* Drop an unneeded store cast from the dirty-readers test
* Phone Markdown: refuse a save on a server too old to check the disk; cover the untitled tab
A server before #30605 has no files.writeIfUnchanged, so the save is refused with the
existing update-required message rather than written unchecked.
* Phone Markdown: refuse a file an SSH host holds outside the workspace; pin the untitled answer
* Phone Markdown: untitled tabs stay read-only; older servers save like the desktop editor; a closed-source preview still reads
- The desktop lists an untitled document's tab with isUntitled (optional, additive); main answers
it read-only with unsupported_untitled and refuses its save before touching the disk.
- A server without the files.write.disk-precondition capability gets the desktop editor's guarded
save: compare from here, then a plain files.write, or a conflict. Every call of a save is pinned
to the pairing it probed.
- A preview whose source tab was closed has no saved record; it reads its own file, read-only.
* e2e: read the untitled tab's answer without a cast
* Phone Markdown: pin a save's pairing before its first read
A save captures the server's pairing before it reads, and every call of the save, the read
included, is pinned to it, so a re-pair between the read and the write is refused.
* Test: with no window, a phone saves an existing untitled.md and never creates a deleted one
---------
Co-authored-by: E2E Test <e2e@test.local>
* A paired desktop shows a layout-owner server's held panes on the existing banner; Reopen asks the server (switch-on builds only)
The server's frames carry status 'held' on a pane a newer build's terminal service may still
run. The desktop now lists those panes on its existing newer-build banner, with the same wording
and states. Reopen calls the server's new `layout.reopenHeldPanes` under the server's ids: the
server stops its newer service, as the desktop's own Reopen does, and starts each pane still
held. The desktop starts nothing; the server's next frames clear the status and bind the pane.
A refusal or failure shows the banner's failed state.
Served and advertised (`layout.reopenHeldPanes.v1`) only by a layout-owner build, to paired
desktops (not phones). A server without the capability, or that publishes no status, shows no
banner, as today.
* test(layout): held server pane stays Reopening until the reattach frame, stays failed after a refusal, and shows nothing without the reopen capability
* fix(layout): a held pane's Reopen acts only on its own host and on main's published status (switch-on builds only)
- Reopen needs the clicked pane's key; a pane that already left the banner's store does nothing,
so a server banner clicked as its frame drops the pane never stops this machine's newer service.
- Local versus server is decided by the pane's host, not by whether it has a release.
- The banner portal for a pane with no terminal handle exists only in a layout-owner build.
- The server reopens only panes its published layout shows held, the status the desktop saw.
* test(layout): Reopen ignores a pane rebound to a new terminal whose held entry is still stored
* test(layout): the dropped-pane Reopen test checks for the reopen method, since the view report shares the RPC client
* fix(deps): root and mobile resolve one zod, and CI fails if they drift
Mobile type-checks src/shared zod schemas. With mobile on zod 4.6.5 and the
root on 4.6.7, TypeScript treated them as unrelated packages, so wrapped
schemas inferred {} (tests/e2e/helpers/phone-feed-list.ts:154) whenever a
job installed mobile deps.
* fix(deps): guard every package mobile's tsconfig remaps, not only zod
* test(ci): pin the shared-dependency version check to the static phase
* perf(ci): accelerate adhoc release builds and runner scheduling
* Overlap adhoc release preparation and reuse qualified build inputs
* Bind qualified template reuse to its embedded CLI source commit
* Use the available CPUs for native compilation in fast dev builds
* Verify qualified template cache provenance and daily invalidation
* Avoid incremental MSBuild tracking for fresh fast-release builds
* Right-size Intel macOS and Windows ARM release qualification
* Route Intel qualification through the standard larger macOS label
* Overlap Windows preparation and initialize the server native compiler once
* Test server compiler invocation through its process-runner interface
* Align adhoc signing contract with its larger packaging runner
* Recognize the native cache fingerprint as batch-file metadata
* fix(process-host): let a slow Windows taskkill finish instead of orphaning the grandchild
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(process-host): abandon taskkill shortly after the root exits so it cannot walk a reissued pid
Co-Authored-By: Claude <noreply@anthropic.com>
* test(process-host): log the taskkill outcome and caller settle time under load
Co-Authored-By: Claude <noreply@anthropic.com>
* ci: register the late-taskkill Windows test in the package-windows path list
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(process-host): release the git admission slot on root close again; cap the background taskkill at 30 s
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* feat(layout-owner): a CLI or phone file on a layout-owner server opens in the group a paired desktop shows
Each paired desktop reports, through a new layout.reportView (served only by layout.intents.v1
servers, never to phones), the group it shows in each workspace the server's frames draw. The
server keeps reports per connection beside the window's, opens files in the latest one's group,
and drops a connection's reports when it closes, falling back to the saved view.
* test(e2e): a CLI file open on a switch-on server lands in the group a paired desktop shows
* fix(layout-owner): layout.reportView refuses a caller with no connection, as sidebarInputs.reportView does
* feat(layout-owner): a desktop's re-sent view after a reconnect never outranks another desktop's live report
layout.reportView takes an optional refresh flag. The desktop's first report of each workspace
after a stream snapshot carries it; the server ranks such entries below every live report, while
they still replace the connection's own earlier entries and beat the saved view.
* feat(layout-owner): a paired desktop re-sends its view, as a refresh, when its control connection to the server comes up on a new socket
The server drops a connection's view reports when it closes, and the layout stream can stay up
across that, so the desktop watches the shared control diagnostics for a new socket.
* fix(layout-owner): a desktop's refresh keeps its own live rank; stop reporting to a server without layout.reportView
A same-connection resubscribe re-sends its view as a refresh, which demoted the connection's
own live report below an older desktop's. Ranks are now an explicit live/refresh tier. A pre-PR
switch-on server answers method_not_found; the desktop then stops sending for that stream.
* fix(layout-owner): a group the server's own frames move is re-sent as a refresh, never a live report
The snapshot resend read the store before the sync applied the snapshot, so the flush's group
change reached the store subscription as the user's. Writes made by the frame sync now report as
refreshes, and the snapshot resend runs after the flush.
* test(layout-owner): a change frame's group move goes out as a refresh; the frame-write mark survives nested writes