Skip to content

Preserve full automation reports and sync the desktop release prerequisite - #37

Merged
karlorz merged 191 commits into
fork-mainfrom
fix/daily-sleep-report-capture
Oct 19, 2026
Merged

karlorz merged 191 commits into
fork-mainfrom
fix/daily-sleep-report-capture

Conversation

@karlorz

@karlorz karlorz commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

ELI5

Long Daily Sleep reports were saved as an 8,000-character preview while claiming they were complete. Automation history now preserves the bounded final report for the exact provider session and prompt, and reports truncation truthfully when only a preview is available.

What Changed

The execution host reuses Grok's completed-turn reader to carry at most 256 Ki characters separately from normal status. Capture verifies the provider session, prompt, terminal ownership and text prefix before saving the report. Normal status keeps its 8,000-character cap; SSH drops oversized optional prose before status, and replay cannot revive it. Completed runs read “Session completed.”

This branch also reconciles the upstream source required by the fork release policy, through 90e99c48f43. It preserves the fork's model settings, bound controls, pet voice, relay password and trusted-machine behavior. CI now uses hosted Ubuntu on this fork where upstream uses Blacksmith, with all required jobs and aggregate failure handling retained. Integration corrections cover draft admission, cancellation keys, native-cache preparation, runtime test routing and stale mocks/assertions. E2E artifact upload now finishes before later action startup rewrites the runner event payload, preventing a background-upload JSON parse race.

The real WSL lane exposed a recovered-terminal close bug after host restart. Authenticated live daemon inventory now restores missing incarnation stop fences even before local adapter attachment, while preserving current-provider, host, live-session and no-overwrite checks. Follow-up integration fixes remove a duplicate automation persistence write, let unfinished quit records resume on reveal, notify the layout owner before returning retained completion evidence, and use the palette's ranked activity timestamp for its displayed age. Completed hibernation output still waits for an explicit wake.

Why

The reviewed 9,379-character report lost its final 1,379 characters. Separating final prose from normal status preserves the preview budget and uses established turn attribution. Session completion remains separate from the maintenance task's outcome. Existing identity fences, byte ceilings and quality limits are preserved.

Linked Issue

Owner-requested Daily Sleep repair. Work record: projects/llm-wiki/work/2026-10-19-daily-sleep-pipeline-code-repair/plan.md. No upstream issue or PR.

Visual Proof

N/A — no layout or styling changes. “Done” becomes “Session completed”; report fidelity and truncation are asserted against stored output. The failed E2E artifact confirms the current accessible New tab action is “Start a chat with Claude.”

Testing

  • Combined source at upstream merge 91f1709 (before the final cancellation-fixture-only correction): all typechecks pass (17,136 Node files, 15,056 renderer, 3,065 CLI, 31 auxiliary).
  • New merge coverage: 297 tests pass across 24 changed-module files; 87 terminal-close/recovery tests pass across eight files. Workflow/aggregate/release contracts: 166 tests pass across 11 files. Upload ordering requires synchronous completion and retains failure propagation. Cancellation/concurrency/cache/draft-admission coverage: 118 tests pass across four files; the explicit cancellation fixture includes all three fork image workflow groups.
  • Exact combined-head changed-code quality passes with zero findings across 2,999 files and all seven scans, against the actual fork-main PR base. Full local lint and all repository ratchets/catalog checks pass, including the new unproven PTY retirement ratchet; five inherited documentation warnings remain.
  • Previous combined head e42b811: 2,350 tests pass across 50 files, with three platform-specific skips. Complete real WSL workflow 39253587792 passed on that commit; forced-update close reports ptyKilled=true and immediate census liveSessions=0, inProcessSessions=0. This is historical evidence; the final head needs its own acceptance result.
  • Exact final head c201947b8a0: real WSL update acceptance 39257442065 passed. Downloaded receipts confirm forced-update close ptyKilled=true, immediate zero live/in-process census, both holders adopted live after 60 seconds and later released exited, plus deferred/forced/silent/reboot updates.
  • Previous broad coverage includes 436 PTY/controller/close tests, 936 launch/quoting/byte-budget tests (one platform skip), and all 232 cross-version wire tests across 46 files.
  • The failed full runs 39248173749 and 39253587931 exposed the integration corrections, artifact-upload race and stale cancellation fixture. They do not authorize merge. Exact final head c201947b8a083e39c993a9f5378aa9918de2af4d passed full PR Checks 39257442392: 42 successful jobs, nine skipped, no failures. Required verify aggregate job 118088523513 passed; workflow path and head independently verified.
  • Local background E2E build succeeded, but the structured-chat test correctly skipped because a system-wide Claude would shadow its isolated stub. All three Linux general E2E shards passed in the final full workflow, including the corrected selector.

AI Disclosure

Implemented with OpenAI Codex under the repository owner's instructions.

Review

Reviewed reuse, simplification, efficiency and boundary placement. Removed redundant persistence logic and kept wake policy in the existing eligibility module. No file-size suppression, timeout relaxation, byte-budget increase or incarnation-guard bypass.

Agent skill upstream boundary

  • Not applicable: no upstream skill-installer material copied.

Notes

Optional report data remains compatible with older peers. Transcript reads belong to the execution host. No wiki-content repair, historical journal cleanup or scheduler-outcome inference is included. Final head c201947b8a0 contains the upload race fix and cancellation fixture correction. Prior-head full run 39253587931 finished with 33 successful jobs, two underlying failures and the failed aggregate; both failures are corrected and locally tested. Release, installed-app long-report acceptance and work-item closure remain pending.

Checklist

  • User-visible behavior, mechanism and tradeoffs explained
  • Reviewed correctness, security, performance, cross-platform and SSH behavior
  • Automated tests added or updated
  • Full PR checks passed for the exact final head

AmoabaKelvin and others added 30 commits October 18, 2026 18:42
…ing Recent (dark) (stablyai#30397)

* fix(cleanup): count visits as last activity once terminals stop stamping Recent (dark)

With ORCA_SIDEBAR_INPUTS=main, terminal start and exit no longer write
lastActivityAt, so a workspace used only through plain shells could look idle
to workspace cleanup and old in the jump palette. Both now read the later of
the Recent stamp and the last visit. Switch off: unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(cleanup): stamp the workspace you leave; space table reads visits too (dark)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(cleanup): every reveal stamps the workspace you leave, host-aware (dark)

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…i#29903)

* fix(terminal): a file dropped on a pane makes that pane active

Dropping a file on an inactive pane's title strip pasted the path but left the
other pane selected, because the drop only focused the terminal. Activate the
pane instead (which also focuses it). Tracked in STA-9893.

* test(e2e): give the title-strip drop the source owner real drags carry

* test(e2e): preserve pane selection when checking title-strip drops

---------

Co-authored-by: Orca Worker <orca-worker@localhost>
… or saved order (dark) (stablyai#30401)

* refactor(sidebar): the stamp-sorted window list runs no settle, latch or saved order (dark)

With ORCA_SIDEBAR_INPUTS=main the window's sort hook is a pure read of the
rows' stamps: no settled-epoch store listener, no Smart latch, no saved Smart
order writes, no class telemetry over the old attention map. Switch off: the
existing hook, unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): stamp sort hook reads no live sources; stronger switch-on tests

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): clear the order-save mock between tests

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(sidebar): stamp sort reads a shared empty-sources constant, not a renderer layout literal

The layout-writer ratchet counts layout field keys written in the renderer;
the empty sources object now lives beside SmartSortSources in shared code.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…s own (dark) (stablyai#30404)

* refactor(sidebar): the stamp-sorted window list runs no settle, latch or saved order (dark)

With ORCA_SIDEBAR_INPUTS=main the window's sort hook is a pure read of the
rows' stamps: no settled-epoch store listener, no Smart latch, no saved Smart
order writes, no class telemetry over the old attention map. Switch off: the
existing hook, unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): stamp sort hook reads no live sources; stronger switch-on tests

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): clear the order-save mock between tests

Co-Authored-By: Claude <noreply@anthropic.com>

* wip: main keeps sleeping retention only

* fix(sidebar): stamp sort reads a shared empty-sources constant, not a renderer layout literal

The layout-writer ratchet counts layout field keys written in the renderer;
the empty sources object now lives beside SmartSortSources in shared code.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(sidebar): main keeps sleeping retention only; no order of its own (dark)

Clients sort by the owning host's stamps, so main's temporal owner loses its
settle window, Smart latch, create grace and catalog-listing classifier, and
the shadow check stops comparing order. Retention, its clock and its shadow
comparison stay.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(sidebar): every retention listing shares one sequence; publish after the first

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): the first listing bumps the retention revision

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…line skills (stablyai#30359)

* refactor(native-chat): move the slash picker builder and upload chunking to shared

Mobile builds the same skill and command list as desktop.

* feat(mobile): redesign the chat composer with file attachments and inline skills

* fix(mobile): the composer field owns its input style

The page's font-size census cannot follow a style passed in as a prop.
…main has no pane for closes in the window (dark, layout-owner build only) (stablyai#30391)

* fix(layout): a workspace only main held shows main's tab on the window's first visit (dark, layout-owner build only)

* fix(layout): a live terminal no pane shows goes through main, which names the tab it holds; a pane close main has no pane for closes here (dark, layout-owner build only)

* test: no type assertions in the CLI-first tests

* refactor: the switch-off build keeps today's live-terminal mint as it was

* review: frame sync under the line limit; the first-visit test names exactly main's tabs

* fix(layout): adopt takes the recorded pane only while it is unbound

* fix(layout): a refused live-terminal adoption is declined, not counted as shown
…ld orphans are reclaimed (stablyai#30148)

* fix(macos): the TCC prompt watcher's log stream dies with Orca

log stream only writes when its predicate matches (about never), so it never
gets SIGPIPE when Orca dies without running stop() (crash, SIGKILL, e2e
teardown, force-quit dev builds) and lived on under launchd. One dev machine had
126 orphans holding 566 MB and ~40% CPU. A /bin/sh wrapper now holds a
never-written stdin pipe from Orca and kills log stream on EOF.

* fix(macos): reclaim TCC log streams that older builds orphaned

The wrapper stops new leaks, but users upgrading still have every log stream an
earlier crash or force-quit left under launchd. Ten seconds after launch, once
per process, Orca terminates launchd-parented processes running exactly the
watcher's old command line. A live watcher's log stream is parented by its
wrapper, so it never matches.

* test(macos): document the fake log stream cast

* fix(macos): arm log stream wrapper trap before fork; verify orphan executable

* fix(macos): re-read orphan identity before SIGTERM; disable inherited errexit in wrapper
…30425)

* feat(sidebar): Smart Done means finished and unread (dark)

Owner decision Q1: with ORCA_SIDEBAR_INPUTS=main, a Done row holds its tier only
while the workspace unread flag is set, so opening the workspace or sending the
next prompt ends it. Reuses the existing flag; no new state or timer.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): reading a workspace drops its Done row in the live list

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
* Settle Codex review turns from their session completion

* Preserve interruption relay metadata when settling Codex reviews

* Keep the Codex turn id across status writers and settle through the existing host verdict path

The turn id that matches a rollout turn end was dropped by every lead writer
that did not pass it, so a failed turn after a child update never settled.
Drop the synthetic Stop label: the unlabelled poll update already carries the
host's main-agent verdict, including a failure, to current desktops. A remote
desktop now takes the host's settled verdict for a late hook, and a rollout
settle retires hook-only children like Stop does.

* Retire Codex children through the poll and start a fresh turn on any non-compaction session start

A rollout-settled turn deleted the child roster, so the local reconcile
re-seeded the child from the last row as running and the pane stayed Working.
Clear it instead, only from the poll (a child hook settling the root must not
drop that live child), and let a remote desktop clear its roster when the host
reports a done turn with no children. A resumed or forked session no longer
inherits the previous turn id. Also drops accidental pnpm-lock churn.

* Replay a Codex rollout turn end as the root Stop it stands in for

Codex fires no Stop for internal subagents such as /review, so the rollout's
task_complete was settling the main agent through a second path that every
other status writer (local and remote reconcile, child hooks, polls) had to
learn about. The transcript poll now replays that end as an ordinary root Stop
through hook normalization, and a root hook for an already ended turn is
classified as that Stop. A failed end carries its failure outcome and is
published unlabelled like a cancellation, since desktops derive a labelled
Stop's outcome as success. The separate settle path, the remote host-verdict
adoption and the child-hook rollout reads are gone.

* Keep an ended Codex turn retired across compaction, and keep WSL polls alive

A compaction SessionStart queued before the next prompt carried the previous,
already ended turn id, so the poll replayed its Stop: a Done flash and a
second "finished" notification. A compaction now keeps the turn only while
it is still running and its rollout end is unrecorded. The WSL guest
rescheduled its transcript poll with the update instead of the stored row,
so the poll stopped after its first update and a later turn end never
replayed there. A Stop also clears the child roster instead of deleting it,
so a reconcile cannot re-seed retired children from the previous row.

* Restate an ended Codex turn's hook in provider dispatch

Keeps the shared hook normalizer within its size limit after main's merge;
the Codex dispatch returns the event it normalized, which the envelope uses
for the published name and prompt classification.

* Type the rollout record helper in the Codex turn-end test

* Accept Codex's turn_complete name and keep only replayed Stops labelled

The rollout reader also records turn_complete, the name the existing
interruption reader already accepts. A poll row that is not a replay no
longer restates the last hook's event name, so only a replayed Stop can
carry the Stop label.

* Drop a delayed Codex hook for an ended turn the lead has moved past

A late root hook for an older finished turn was restated as Stop and marked
the newer running turn Done; passing it through would instead rewind the
lead's turn id so the poll replays the old end.
…he same list (dark) (stablyai#30426)

* test(sidebar): the desktop window and a feed reader draw the same list (dark)

* fix(sidebar): a feed reader lists a paired server's folders and nested workspaces as the window does (dark)

* fix(sidebar): the paired-server shadow compares the lineage the server's check keeps (dark)

* fix(sidebar): paired-server rows carry the unread flag Smart Done reads (dark)
…outs (stablyai#29587)

* fix(shortcuts): Cmd+C no longer pastes on remapped Latin keyboard layouts

On macOS the main-process paste intercept matched `code === 'KeyV'` OR
`key === 'v'`, so on Turkish F (where the C letter sits on the physical
KeyV) Cmd+C was routed to ui:appMenuPaste. Trust the produced key when it
is a Latin letter or digit and only fall back to the physical code for
non-Latin layouts (Cyrillic etc.), matching the shared keybinding matcher.

Fixes stablyai#29574

Claude-Session: https://claude.ai/code/session_01TYbS9P1pnW4WRN7bGpuNaW

* test(shortcuts): cover Dvorak, Korean and IME composition for the macOS paste intercept

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* refactor(shortcuts): match the macOS paste intercept by produced key token, in its own module

The produced key token wins on every Latin layout, punctuation included (BEPO puts . on KeyV); non-Latin and IME keys fall back to the physical key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: İzzetEmre <emreizzet@hotmail.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…s last active (dark) (stablyai#30431)

* feat(cleanup): the owning host records last terminal use for cleanup's last active (dark)

Owner decision Q2: with ORCA_SIDEBAR_INPUTS=main, the host that owns a
workspace writes lastTerminalUseAt when a terminal takes input (window, phone
or CLI, through the one input funnel) or a phone attaches, at most once a
minute per workspace. Output never counts. Cleanup's last active reads it
next to the Recent stamp, so a workspace used only from the phone never looks
idle. Never a sort key.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(cleanup): count only typed input, skip paired-server terminals, share record routing

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ory (stablyai#29150)

* fix(native-chat): identify the owner when resume create is refused

* test(native-chat): consolidate transcript refusal cases

* test(native-chat): keep ownership coverage at the launch boundary

* test(native-chat): update adoption fixture for early ownership admission

* test(native-chat): align resume admission assertions with host ownership

* test(native-chat): keep RPC coverage focused on ownership proof

* test(native-chat): assert blank request presence before optional source

* fix(native-chat): admit the actual CLI source during chat creation

* fix(native-chat): satisfy source admission lint checks

* test(native-chat): provide the waiting hook prompt

* test(native-chat): isolate source admission fixtures from host homes

* test(native-chat): canonicalize adoption fixture directories

* test(native-chat): preserve early ownership admission in source stack

* Resume CLI conversations directly in native chat

* fix(native-chat): preserve resume refusal behavior and honest transcript feedback

* fix(native-chat): retain import read evidence in typed result

* Use the header test fixture record type

* Consolidate launch owner imports and correct feedback assertion

* Keep existing resume translation in runtime catalog

* fix(native-chat): preserve admitted source and prepared execution home

* fix(chat): retain source evidence for dispatched adoption

* fix(chat): keep source capture within module limits

* Keep deferred chat publication in the shared launch owner

* test(chat): allow host evidence in create routing

* Satisfy launch publication checks and scope slot cleanup

* Preserve canonical tab membership on projected reorders

* Use recorded launch ownership for surface recovery

* Reserve deferred chat order through existing group actions

* Pass the active layout owner into launch cancellation

* fix(native-chat): admit an unsaved transcript under a linked account folder

A missing transcript stayed unresolved while its account root resolved through
symlinks, so containment failed and a not-yet-saved conversation showed a load
error instead of the unsaved notice.

* refactor(native-chat): move the newest-item import window into its own module

Keeps journal-legacy-import.ts under the line limit after the source
identity observer landed on top of the streaming import.

* fix(native-chat): restore the refusal error import lost in the main merge

* test(native-chat): pass the layout owner in the layout-owner cancel test

main's new test predates launch cancellation taking the active layout owner.

* fix(native-chat): admit Pi transcripts against the root Session History walks

A PI_CODING_AGENT_DIR that already ends in sessions, or any custom folder,
made admission expect <dir>/sessions while the lookup searched <dir>, so a
found conversation was refused.

* refactor(native-chat): admit and look up CLI transcripts in one store per account

Each agent's import described where an account keeps its conversations twice,
once for the path check and once for the lookup, and the two could disagree
(the linked-folder and Pi custom-root refusals). The import now names one
conversation store per account; the create path resolves it once and both the
hook-path check and the fallback lookup judge that same store. OpenCode's
store is its exact database.

* fix(native-chat): keep a History resume that joins a terminal resume alive when the terminal closes

A History click that joins a pending terminal-tab resume now takes the
launch over: the terminal hand-off runs at join time and the create
resumes by conversation id, so closing the terminal before create no
longer abandons the shared launch.

* refactor(native-chat): move the launch source hand-off into its own module

* fix: pick the chat that adopts a header resume the way History does
… list print (stablyai#30438)

* Accept host aliases and infer the host for a single-setup project

`orca worktree create --project scalp-it` failed with "Project is not set
up on the selected host" even though the project has exactly one ready
setup, and `--host trader-local` was rejected as an invalid value even
though `orca host list` prints that name. Three separate causes:

- `--project` was matched against the stored `projectId` only, which is a
  provider-scoped string (`github:owner/repo`). The display name shown by
  `orca project list` never matched, so the lookup found nothing and the
  failure was reported as a missing host.
- With no `--host`, the host fallback existed but was unreachable, because
  the project lookup above had already filtered to zero candidates.
- `parseExecutionHostId` accepts only `local`, `ssh:<id>` and
  `runtime:<id>`, so a bare alias could not parse at all. `ssh:<label>`
  already resolved labels; only the unprefixed spelling was missing.

Now:

- `--project` takes the project id or the display name, resolving through
  `project.list` and falling back to the setup's own display name for
  hosts that predate that method. Two projects sharing a name are refused
  rather than guessed.
- `--host` is only required when the project is set up on more than one
  host. A single setup is used directly; an ambiguous one fails with every
  host and the flag to select it.
- `--host <alias>` resolves against both axes: paired servers by name
  before routing, SSH targets by label once a client exists. An alias that
  names a machine on both is refused with the prefixed flag for each.

Errors now echo the flag the caller typed instead of an `ssh:` spelling
they did not use, and carry the hosts to retry with in `nextSteps`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refuse a host alias that collides with duplicated SSH labels

assertAliasNamesOneHost asked findSshTargetByName whether the alias also
named an SSH target, but that function returns undefined for a label two
targets share — deliberately, so an ambiguous label never resolves. Read
as a boolean it says "no SSH target by that name", so an alias naming one
paired server and two same-labelled SSH targets looked conflict-free and
resolved to the server.

The opposite direction was already covered: a unique SSH target against
two servers sharing a name fails through assertEnvironmentNameUnambiguous.
Fall back to ambiguousSshTargets so both axes refuse, and list every
colliding target in nextSteps rather than only the first.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Only fall back to setup display names on a server without project.list

findProjectsByName caught every error from project.list and returned an
empty list, so a network failure, a permission error, or a genuine server
fault was indistinguishable from "no project by that name" — and the
caller was then told `Project is not set up on the selected host` for a
project that exists and is set up.

Narrow the catch to method_not_found, the only code that means the weaker
fallback is warranted, matching callProjectHostSetup and listSshTargets in
the same layer. The function now returns null for that version gap and an
empty array for a real miss, so the two cases stay distinguishable at the
call site.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refuse a legacy display-name match that spans two projects

The pre-project.list fallback matches ProjectHostSetup.displayName, which
is the repo's folder name rather than the project's name, so two projects
holding repos with the same folder name both match. selectSetupOnHost
would then have taken the first candidate — targeting a project the caller
never named.

Reject when the matched setups span more than one projectId and name each
one, the same rule the project.list path already applies to duplicate
display names. A single project with several setups is unaffected; the
host selection below still decides between them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Spell the --host placeholder as <host> in the project setup usage lines

The worktree create usage was updated to <host> when aliases became
valid, but the four project setup-* lines kept <host-id> in both the
command specs and the root help text they mirror. Those commands accept
an alias too, so the narrower placeholder hid it from anyone reading help.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Document host names and the optional --host in the CLI docs

The published CLI reference described `--host` as taking only `local`,
`ssh:<target-id>` or `runtime:<environment-id>`, and the automations page
showed `--host <hostId>` as required alongside `--project`. Both now
understate what the commands accept, which is how the original report
started: `orca host list` prints a name, the docs never said you could
type it, so the generated id looked mandatory.

Spell out that `--host` takes the bare name from `host list` on either
axis, that a name resolves only when unique and is otherwise refused with
the prefixed flag for each candidate, and that `--host` is unnecessary
when the project has a single setup. Also note `--project` accepts the
display name from `orca project list`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Name the runtime host id as the paired-server tie-breaker in the docs

The host-selection page told readers to fall back to ids when a name is
ambiguous but only ever showed `--environment <server-name>` for paired
servers — and that selector is exactly the one that cannot separate two
servers sharing a name. Spell out the pair the resolver actually emits in
its nextSteps: `--host ssh:<target-id>` and `--host runtime:<environment-id>`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Spell the automations create usage as --project <id-or-name> [--host <host>]

`automations create` resolves --project and --host through the same
alias-capable resolver as `worktree create`, and its own notes already say
--project takes a display name and --host takes a bare host name. The usage
line still read `--project <id> [--host <id>]`, the last spot left on the old
spelling (flagged by CodeRabbit after the main merge).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give the fake runtime client cast in the host alias tests its SAFETY note

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cli): accept every name host list and project list print, everywhere --project/--host is taken

Extends the alias work to the commands it did not reach and the names it
missed:

- `project setups --project <display name>` filtered by projectId only.
- `project setup-existing-folder|setup-clone|setup-create --project <name>`
  now send the project id that name resolves to (pass-through for ids and
  for servers without project.list).
- `--host "this machine"` (the local row's name) resolves to local, and is
  refused if an SSH target or paired server shares it.
- One pair of wrapping quotes is ignored, since cmd.exe passes single
  quotes through literally.
- With --environment/--pairing-code, a bare --host name is a machine the
  routed server reaches, not a different locally paired server.
- An unknown project is reported as a project miss, not a host miss.

Co-authored-by: Younghwan Chae <chyohw97@gmail.com>

* fix(cli): keep exact project ids over names, and refuse an unverifiable host name

An exact --project id whose only setup is not ready no longer falls through
to a project whose display name equals that id. A bare --host name that
matches a paired server now routes there only once this machine's SSH
targets were actually read, and under --environment/--pairing-code a bare
name always means a machine the routed server reaches.

Co-authored-by: Younghwan Chae <chyohw97@gmail.com>

---------

Co-authored-by: younghwan <chyohw97@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ayout.apply (stablyai#30430)

Every renderer sender of layout.apply (sendLayoutApply, recordLayoutApply, the activation
terminal create and the live terminal adoption) returns before the call for a worktree or
folder workspace a paired server owns, and a source census pins that no other renderer file
sends layout.apply.
…ring the last one sticks (stablyai#30433)

A client's pane rename already lands in the host's saved layout (titlesByLeafId),
but terminal.list/show never read it, and clearing a remote tab's last pane title
sent no titles at all, which every host reads as 'unchanged'.

- terminal summaries carry an optional paneTitle from the host's saved layout;
  the CLI list prints it ahead of the terminal title. Older hosts omit it.
- clearing the last pane title of a remote tab pushes titlesByLeafId: {}, which
  old and new hosts both persist as cleared.

Supersedes stablyai#30333, which renamed the whole tab via terminal.rename.

Co-authored-by: wangshifeng <shifeng.wang@magic-shield.com>
…i#30379)

* fix(drops): name extensionless dropped images by their bytes

A file dropped onto a remote workspace with no extension (some drag sources
hand images over as `noname`) now gets the extension its PNG/JPEG/GIF/WebP
signature declares, so the agent sees `noname.png`. Repo icon detection
reuses the same signature check.

Fixes stablyai#30378

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(drops): sniff extensionless images only for .orca/drops and chat attachments

Explorer imports into a user-picked remote folder keep the name, matching
local imports. Chat attachments on a paired server now use the staged name
so they get the extension too.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>
* Open file links beside their source tab's group

Clicking a file link in a terminal or agent chat now opens the file in an editor next to that tab, not in whichever group happens to have focus. A new "Open in this group" row pins the file to the source group when that group has neighbors.

- Capture the source tab at click time, so focus changes during path resolution don't redirect the file.
- Rank candidate groups by adjacency: a neighbor already showing the file, then a neighbor showing an editor, then any neighbor, then the source group.
- "Open in this group" never falls back to a browser, system app, or other group. If the group is gone or the target is not an editor, it shows a toast and does nothing.
- Line reveals target the exact view that was opened, and are dropped if that view closes before the editor applies them.

* rm scratch files

* Preserve empty split panes during tab reconciliation

Reconciliation removed every empty group, so an empty split pane the user had made beside a tab was collapsed on the next pass. A file link opened from that tab then landed in the source group instead of the empty pane. Reconciliation now removes only groups that stale-tab cleanup emptied, and keeps panes that were already empty. Tests cover terminal and agent-session sources in both worktree and folder workspaces.

* Preserve runtime owner for local file links
…k, layout-owner build only) (stablyai#30439)

* fix(layout): a CLI split whose pane main closes before it mounts answers at once (dark, layout-owner build only)

* test(layout): the closed-before-mount split fails because of the close
…ght seams from their defining modules (stablyai#30440)

The three ipc modules re-exported symbols owned elsewhere. Importers and test mocks now name the
defining module, and the re-export lines are gone (no barrels).
…ting would stamp (stablyai#30445)

A tab restored before its worktree catalog loads gets a placeholder row. SSH placeholders had no
host, and a server's nested-SSH placeholder carried only runtime:E, so an ssh: host selection found
no matching owner and routed the terminal as a direct dial from this desktop. Stamp the SSH target,
and the server proxying it, from the repo row; leave a repo id that lives on several hosts unstamped.
…s the whole page and clicks land under the cursor (stablyai#30437)

* fix(browser): a remote viewer larger than the host's browser pane sees the whole page and clicks land under the cursor

Fixes stablyai#30341

* test: note why the screencast double is cast

* fix(browser): drop guest frames until the fitted size is applied; forget the cached CSS size when the frame size changes

* fix(browser): keep a press's release and markup inside the letterboxed remote frame

* fix(browser): resume live frames once fitted metrics land; remeasure the page after its frame size changes
…its own host's copy (stablyai#30447)

applyCreatedWorktree merged a create into any same-id row, so a local create could inherit a
server twin's stamp (or wipe it). Lineage refresh stamped a server's reply with the focused host and
replaced every same-id row, overwriting the local twin. Both now match id and host, and the lineage
reply is stamped with the transport it came from.
karlorz and others added 5 commits October 19, 2026 19:53
… refuse a reserve roll target by name (stablyai#30707)

* fix(relay-ops): let same-cap rolls run while another cell is reserve; refuse a reserve roll target by name

The director refuses cell-status for a step-5 reserve cell with a 404
{error: cell_in_reserve_mode}. The same-cap headroom check read it for
every general cell, so any roll failed while c7 was reserve. A reserve
cell that is not being rolled now counts as no free slots (its seats are
not in the database) and is listed with reserve: true. A reserve roll
target, in the headroom check, the same-cap job's first cell-status read
and the candidate tool, now fails with the design's procedure: flip it to
admitMode=db first, roll, then flip it back. Any other 404 still fails.

* fix(relay-ops): pin the headroom gate's imports as evidence code; name both cells in a two-cell reserve refusal
…d runtime-terminal-inspection (D2, D3) (stablyai#30684)

* refactor: de-barrel remote-runtime-client and runtime-terminal-stream (D2)

importers name the defining modules; the two vi.mock factories that named a re-export move to
the defining module (pty-id spy) or drop the dead key (RemoteRuntimeClientError)

* refactor(renderer): de-barrel runtime-terminal-inspection (D3)

importers name runtime-terminal-input-recording, runtime-terminal-verified-input and
shared/remote-runtime-pty-id; 22 vi.mock factories move the re-exported keys to those modules
(pty-id keeps its other exports via importOriginal)

* test: drop the duplicated verified-input mock (Opus P3)
…store (stablyai#30697)

* plugins: extract the verified artifact-pack registry from language packs

Pure refactor. Verify, bounded read, parse and per-plugin error handling
move into PluginArtifactPackRegistry; language packs become one instance,
so later data families reuse it instead of copying the file.

* plugins: reconcile content families from one list

Pure refactor. Reconcile and error reporting walk one family list in the
same order as before, so a new family is one entry.

* plugins: one line per family for declared artifacts and contribution defaults

Pure refactor. Declared artifact paths are a list of per-family rows, and
the contributes default comes from the schema (prefault) instead of a
second copy of the field list; the existing defaults test pins the result.

* plugins: extract the renderer plugin content list store

Pure refactor. The fetch generation, startup join window and content-change
subscription move into createPluginContentListStore; language packs become
one instance, and store fields are renamed to entries/fetchEntries.
@karlorz karlorz changed the title Preserve full automation reports with truthful truncation Preserve full automation reports and sync the desktop release prerequisite Oct 19, 2026
nwparker and others added 21 commits October 19, 2026 04:20
…uns out of space (stablyai#30679)

* fix(ssh): say the host's disk is full when a managed-server install write fails for lack of space

OpenSSH's sftp-server reports ENOSPC and EDQUOT as the generic SSH_FX_FAILURE, so a deploy onto a
full home failed with the bare detail "Failure" under "Try connecting again". After a failed
install write, probe the host's free space where the write was going; when it cannot hold the
write, fail with an error that names the filesystem, how much is free and how much is needed.
Lost or aborted transfers and Windows hosts keep their original error.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(ssh): diagnose only SFTP's generic failure on an exhausted filesystem

Codex review: a partial upload may spend the room the rest needed, so compare against an empty
filesystem, not the bundle size, and keep explicit refusals such as permission denied.

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor(ssh): reuse formatKb and size an upload the way uploadDirectory walks it

Opus review: the shared formatKb already exists, and following symlinks inside the tree could
recurse; uploadDirectory follows the root and skips links below it.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(ssh): measure the free-space probe in the host-op byte budget

---------

Co-authored-by: Claude <noreply@anthropic.com>
…stablyai#30710)

* fix(security): put every trust dialog on a host-owned token surface

Plugin themes write CSS tokens on the document root, and dialogs portal to
body, so any trust surface without plugin-security-chrome inherits them; a
theme could hide what is being approved. The orca.yaml hook trust dialog,
notebook run confirmation, plugin install, remove and rollback dialogs were
missing it. DialogContent gains surface="security", which applies the
host-owned tokens, and a scrollable prop for the plugin dialogs' existing
viewport-height scrolling; every trust dialog uses the surface and a test
lists them. The style guide says when to use each.

* fix(security): keep inline trust decisions on the host-owned surface

The agent command approval card (Allow/Deny) and the browser certificate
override sit outside a dialog, so plugin theme tokens still reached them.
A SecuritySurface primitive puts its child on the same host-owned token set
as DialogContent surface="security"; both use it, and the security surface
test now lists inline trust surfaces as well as dialogs.
…ablyai#30693)

The Git runner sends a command into WSL when the working directory is a
\wsl.localhost UNC path, even if no WSL distro is set in the options.
literalPathspec only looked at the options, so Windows-style backslashes
in file paths reached Git inside WSL unconverted and stage, unstage and
discard failed to match the file.

literalPathspec now also checks the worktree path, and every caller
passes it.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
…stablyai#30659)

GitHub orgs using an SSH certificate authority get an SCP remote whose user is
org-<id> rather than git. The three GitHub remote parsers only matched a
literal git@ user, so those repos were classified not-github and lost PR,
issue and check support.

Co-authored-by: Yi-111-a <yi-111-a@users.noreply.github.com>
…er pages everywhere, headless sleep stops terminals (stablyai#30715)

* fix(runtime): one owned-session teardown policy; removal closes host browser pages everywhere, headless sleep stops terminals

A declared table says what each entry point (worktree, folder and project removal,
sleep, disconnect) does to each owned kind (PTY, native chat, host browser page,
launch record), so a new kind is a compile error until decided. Removal over desktop
IPC and repo.rm now close the host's browser pages and launch records like the
runtime removal; worktree.sleep with no renderer sleeps the host's terminals instead
of doing nothing. Sleep->chat is declared pending stablyai#28445; disconnect stops nothing.

* fix(runtime): project removal finds headless pages and failed launches without a client page; stub the new runtime method

* fix(runtime): headless sleep rejects an unproven stop; read headless page owners through the bridge, not Electron
… remote contradicts (stablyai#30717)

* fix(projects): refuse to set a folder up under a project its own GitHub remote contradicts

Setting an existing folder up for a project stamped the project's identity
as the folder's upstream even when the folder's own remote named another
GitHub repository, so the folder was silently relabelled into the wrong
project (fabricated upstream, wrong projectId on its setup and workspaces).

Fixes stablyai#30663

* test: type the folder-remote spec store without casts

* fix(projects): judge a registered folder by its current remotes; ignore host aliases

* test: cover the desktop re-read of an already-registered folder
…the desktop (dark switch) (stablyai#30721)

* fix(sidebar): key folder workspace rows by host in the shared builder so the phone matches the desktop

Co-Authored-By: Claude <noreply@anthropic.com>

* test(sidebar): pin the SSH folder row key encoding

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
…all an older version" (stablyai#30724)

* fix(ssh): name a noexec home instead of calling the host unsupported

* test(ssh): measure the stage exec probe in the host-op byte budget

* fix(ssh): the connect error names a noexec home too

* chore(i18n): add the noexec status line to the English catalog

* fix(ssh): only the shell's exec refusal reads as noexec; other probe failures reach the self-test

* fix(ssh): a stage the probe can't write skips the probe, so a full disk still reads as full

* fix(ssh): read the probe's exit 126 as noexec in any locale
… of nothing (stablyai#30725)

* fix(runtime): say why a new terminal on a host failed, with a retry

* fix(runtime): never offer a retry or say refused when the host's reply was lost

* fix(runtime): only a host error reply counts as refused; the wake flow keeps its own toast

* refactor(runtime): one 'couldn't confirm' notice carrying the reason, no failure classification

* fix(runtime): one create notice per workspace, so retried seeding can't stack toasts
… unproven ones (stablyai#30722)

* refactor(pty): every PTY retirement names its exit proof; ratchet the unproven ones

* refactor(pty): count raw ownership drops, route an abandoned relay stop to unverifiable, honest proof labels
@karlorz
karlorz merged commit 2c435ab into fork-main Oct 19, 2026
108 of 109 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.