Skip to content

test(e2e): repair full-suite specs that missed intentional product changes (fork notes) - #1

Closed
SimonSQLML wants to merge 333 commits into
mainfrom
fix/e2e-main-stale-specs
Closed

SimonSQLML wants to merge 333 commits into
mainfrom
fix/e2e-main-stale-specs

Conversation

@SimonSQLML

@SimonSQLML SimonSQLML commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner
Files Added Deleted Net
Test 8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​49 $\color{#cf222e}{\Huge{\mathbf{−}}}$​25 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​24
Prod 0 0 0 0

포크 전용 작업 기록입니다. upstream(stablyai/orca)에 올리지 않았습니다. 다음 세션에서 리뷰 후 upstream PR 여부를 결정합니다.

업데이트 (2026-10-17)

  • 최신 upstream main(cf6a5446a6) 위로 rebase했습니다.
  • upstream이 직접 고친 2건은 이 PR에서 뺐습니다.
    • 전용 lane spec이 일반 shard에 섞이던 CI 버그: AmethystLiang이 #28161로 머지(10/16 19:55)했습니다. 아래 "1." 항목은 이제 해당 없습니다.
    • mobile-subscribe-idle-hidden-pane-fit: upstream에서 수정돼 실패 목록에서 빠졌습니다.
  • 이 PR에 남은 것은 spec 8개 파일 수정입니다(제품 코드 무변경).
  • 최신 main에서 다시 검증: 8개 파일 17 passed (WSL, CI와 같은 방식).
  • 10/17 main 전체 E2E(run 39060220800, shard 12개 실패)에서도 이 8개는 여전히 실패 중입니다. 수정이 아직 유효하다는 뜻입니다.
  • 같은 실행에서 새로 보이는 실패(원인 미조사): tasks-page, native-chat-zcode-transcript, github-created-issue-start-prefill, layout-compat-old-phone-on-new-server.
  • 아래 원문은 10/16 기준 기록입니다.

배경

  • 2026-10-16, 우리 PR fix(runtime): resolve session tab workspace path aliases stablyai/orca#29543(nwparker가 #27264를 이어받아 머지)과 후속 #29592가 main에 들어간 뒤 "main에서 에러가 난다"는 의심으로 조사를 시작했습니다.
  • 결론: 우리 PR 때문이 아닙니다. main의 전체 E2E(14 shard)는 최소 10/14부터 매 실행마다 같은 약 30개 테스트가 실패하고 있었습니다.
  • PR CI는 그 PR이 건드린 spec만 돌리고("changed e2e specs" job), 전체 suite는 관리자가 수동(workflow_dispatch)으로 가끔 돌립니다. 머지 조건이 아니라서 PR은 계속 초록불로 머지됩니다.
  • 10/16 12:00 초록 실행(run 38897522599)은 shard가 전부 skip된 changed-spec 실행이었습니다. 전체 통과가 아닙니다.

근거가 된 main E2E 실행

run 시각 (UTC) sha 결과
38906248356 10/16 13:13 068cdda 실패 34개
38888262404 10/16 10:35 1855ba4 실패 38개 (거의 같은 목록)
38789811595 10/15 18:56 fd21909 같은 패턴
38620260575 10/14 19:02 33b8558 같은 패턴

이 PR이 고치는 것

1. CI 버그: 전용 lane spec이 일반 shard에 섞임

  • config/scripts/ci-e2e-shard-plan.mjs가 Playwright 전체 discovery를 그대로 shard에 나눕니다.
  • 그래서 workspace-layout-oracle-headless.spec.ts가 orcad 빌드가 없는 일반 shard에서 돌고, CI=1이면 skip이 금지돼 9개가 매번 실패합니다.
  • 수정: ci-e2e-job-selection.mjs의 isDedicatedE2eSpec로 전용 spec(orcad, managed SSH ssh-orcad-*, native IME, Windows 전용)을 shard 계획에서 제외합니다. changed-spec job이 이미 쓰던 기준과 같습니다.
  • discovery 경로는 testDir 기준(foo.spec.ts)이고 전용 목록은 repo 기준(tests/e2e/foo.spec.ts)이라 앞에 tests/e2e/를 붙여 비교합니다.
  • 단위 테스트 추가: ci-e2e-shard-selection.test.mjs.

2. 제품의 의도된 변경을 못 따라간 spec (테스트만 수정, 제품 코드 무변경)

spec 원인 관련 커밋
linear-url-workspace-entry, paired-cmd-j-host-qualified-tabs, workspace-emoji-picker, worktree-jump-palette-filter Cmd+J placeholder에 "tasks" 추가 stablyai#28617
add-project-error-toast-stacking 토스터 기본 z-index 40 → 47 (floating workspace 위) stablyai#28068
profile-state-save-delay 저장 지연 경고에 닫기 버튼이 생김 (버튼 0개 → 1개) stablyai#28900
workspace-emoji-picker 다이얼로그 제목 "Edit Worktree Details" → "Workspace details"
os-opened-documents (csv, tsv) 편집 가능한 CSV는 table이 아닌 ARIA grid로 렌더링됨. data-testid="csv-grid"로 role 무관하게 찾도록 변경
ai-vault-panel-search disabled/no-service 응답은 그 호스트를 title-only 모드로 고정함 (stablyai#22453). 옛 에러 문구 대신 title-only 문구를 기대하고, 이유마다 패널을 remount하도록 재작성
mobile-subscribe-idle-hidden-pane-fit remote runtime 요청 API가 pairing offer 대신 paired-connection handle을 받음. pairedRuntimeConnectionFromOffer로 감쌈 stablyai#27533

검증

  • WSL Ubuntu 24.04 + xvfb + openbox, CI와 같은 방식(build:electron-vite:parallel --mode e2e, CI=1 SKIP_BUILD=1).
  • 수정 전: 대상 spec에서 CI와 같은 실패가 재현됐습니다 (22개 spec 기준 26 failed / 15 passed).
  • 수정 후: 위 수정 spec 9개 파일 전부 통과 (18 passed).
  • shard plan 단위 테스트 2개 통과.
  • 실제 CI에서는 아직 돌려보지 않았습니다.

아직 안 고친 것 (다음 세션 할 일)

로컬에서 모두 재현됩니다. 문구 변경이 아니라 런타임 디버깅이 필요합니다.

실제 제품 버그 가능성 높음

  • markdown-find-adversarial (2개): 찾기 입력창에 포커스한 상태에서 에디터를 Shift-클릭하면 선택이 직전 클릭 위치가 아니라 문서 맨 처음부터 잡힙니다. 프로브 결과 DOM selection이 "Adversarial Find interactions\nNeedle first match..."로 시작했습니다. 의심 지점: src/renderer/src/components/editor/rich-markdown-search-focus.ts의 Shift 분기 view.focus(). Replace 입력 포커스 복귀 실패도 같은 계열로 보입니다.
  • markdown-review-selection-performance: 클릭 후 Shift+ArrowRight로 선택해도 "Add review note" 버튼이 안 뜹니다. 버튼은 RichMarkdownAnnotationOverlay.tsx에 남아 있고, target 계산(rich-markdown-review-annotations.ts의 getRichMarkdownAnnotationTarget)을 확인해야 합니다.
  • tab-strip-tab-render-isolation: 백그라운드 탭 제목 하나를 바꾸면 커밋당 탭이 1개가 아니라 2개 다시 렌더링됩니다. 성능 회귀 가능성이 있습니다.

원인 미확인

  • terminal-attention: 포커스된 탭에 BEL을 보내도 unread가 안 됩니다.
  • terminal-pane-title-strip-placement: 제목 strip에 드롭해도 해당 pane이 활성화되지 않습니다.
  • terminal-split-pane-paste-ownership: 내부 파일 드롭이 대상 PTY에 쓰이지 않습니다.
  • landing-preflight-runtime-routing: 클라이언트 PATH를 빈 폴더로 바꿔도 preflight.check().git.installed가 true입니다. 로컬 probe(src/main/ipc/preflight-command-exec.ts)가 PATH 밖 설치 경로도 찾기 때문으로 추정되지만, /usr/bin은 그 목록에 없어 확인이 필요합니다.
  • github-project-board: 드래그 후 빈 컬럼에 카드가 나타나지 않습니다.
  • jira-panel-layout: 폭별로 제목 너비가 200px 초과여야 하는데 190px입니다. fix(jira): keep issue titles readable in narrow panels stablyai/orca#28583/feat(jira): choose planning columns in the issue list stablyai/orca#28615 이후 컬럼 추가 영향으로 보입니다. 어느 폭에서 실패하는지 아직 모릅니다.
  • native-chat-ask-user-question-card: Submit 버튼이 나타나지 않습니다.
  • wsl-host-fake-transport: 가끔 실패합니다.

별도 이슈

우리 PR(stablyai#29543, stablyai#29592) 재검토 결과

  • 심각한 결함 없음. 관련 테스트 387개 통과 (--config config/vitest.config.ts 필요).
  • 위 E2E 실패와 무관합니다. 실패 목록에 session-tabs 관련 spec이 없고, 머지 전(10/13~15)에도 같은 실패가 있었습니다.
  • 낮은 심각도 엣지 케이스:
    1. 스트림 철자가 타이밍에 따라 달라짐 (session-tabs.ts 168, 196-200행 부근): 호스트 publish 전에 alias로 구독하면 프레임이 alias 철자로, publish 후에 구독하면 호스트 철자로 옵니다. 데스크톱 렌더러는 frame의 worktree로 탭을 보관하므로(apply-snapshot.ts), alias를 쥔 클라이언트가 늦게 구독하면 탭이 여전히 비어 보일 수 있습니다. 데스크톱은 보통 worktree 목록에서 호스트 철자를 받으므로 노출은 작습니다.
    2. alias 스트림이 alias에 고착될 수 있음: alias가 나중에 정확히 일치하는 ID가 되면(예: 그 철자로 PTY 등록) 이후 호스트 철자 업데이트가 조용히 버려집니다.
    3. resolver 비용: alias 조회마다 snapshot, 저장 세션, PTY를 전부 훑습니다. PTY가 많을 때만 의미가 있습니다.
  • 이전부터 있던 버그: session-tabs.ts 241행 부근. subscriptionId 없이 unsubscribe하면 <key>: 접두사로 지워서 folder workspace 인스턴스나 :이 들어간 POSIX 경로의 형제 스트림까지 끊을 수 있습니다.

로컬 재현 환경

  • WSL Ubuntu 디스크를 G:\wsl\Ubuntu로 옮겼습니다. 원래 C:에 있던 디스크가 빌드로 커지면서 C:가 꽉 차서 옮긴 것입니다.
  • 클론 위치는 WSL ~/orca, sha dbc1e18b이고 이 브랜치 변경이 적용돼 있습니다. Node 24는 nvm으로 설치했습니다.
  • 실행 예:
    . ~/.nvm/nvm.sh && cd ~/orca
    CI=1 SKIP_BUILD=1 ORCA_E2E_WEB_CLIENT=1 ORCA_BACKGROUND_LAUNCH=1 \
      xvfb-run --auto-servernum bash .github/scripts/e2e-with-window-manager.sh \
      pnpm exec playwright test --config tests/playwright.config.ts \
      --project=electron-headless --workers=3 --reporter=list <spec들>
    
  • 제품 코드를 고치면 VITE_EXPOSE_STORE=true pnpm run build:electron-vite:parallel --mode e2e로 다시 빌드해야 합니다.

🤖 Generated with Claude Code

@SimonSQLML
SimonSQLML force-pushed the fix/e2e-main-stale-specs branch from 276ebfd to 1895eb1 Compare October 17, 2026 20:36
@SimonSQLML SimonSQLML changed the title test(e2e): repair stale full-suite specs; keep dedicated specs out of shards (fork notes) test(e2e): repair full-suite specs that missed intentional product changes (fork notes) Oct 17, 2026
@SimonSQLML
SimonSQLML force-pushed the fix/e2e-main-stale-specs branch from 1895eb1 to 33fe472 Compare October 20, 2026 20:46
nwparker and others added 27 commits October 26, 2026 15:24
…one (E9) (stablyai#34063)

`wsl --shutdown` can end the watch and holder with code 0. Two places only reacted
to a failed exit:
- the settle window that keeps a poll from reconnecting into a stopping distro;
- the liveness run's 500 ms settle and running-list re-read before the verdict.
On a clean exit the run read a possibly stale list that still showed the distro,
and readIdentity then exec'd wsl.exe into it, booting it again. Both now use one
"any watch or holder exit" predicate.

Co-authored-by: Claude <noreply@anthropic.com>
…er boots (E9 follow-up) (stablyai#34093)

* fix(wsl-host): no-boot connects stay quiet, and liveness recovery never boots (E9 follow-up)

Two lows from the stablyai#34050 review:
- A no-boot connect published "Connecting" before its running-list read, and a quiet
  refusal then left the row on "Connecting". It no longer publishes the pending state.
- Liveness recovery reconnects only after confirming the distro runs, but that read
  could go stale before the reconnect spawned anything. Recovery now passes the same
  running check to the connection manager, which re-reads it right before spawning.

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

* fix(wsl-host): a no-boot recovery connect still publishes its own pending status

Only the default "Connecting" is skipped for a no-boot connect; an explicit status
(liveness recovery's "retrying now") is still published.

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

---------

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

Same Vitest registration race as the daemon-init fix: concurrent first imports
can load dependencies before pending vi.mock/vi.doMock calls are registered.
…d start after a DEBUG trap change (stablyai#29165)

On bash <= 5.0 the PROMPT_COMMAND is a single scalar string. After the
user changed the DEBUG trap at the prompt (`trap - DEBUG`, `trap ':' DEBUG`),
Orca's preexec never saw the status-capture command, so the legacy wrapper
flag stayed unset and the dispatch tail emitted OSC 133;C before the next
prompt was drawn.

Set __orca_in_legacy_prompt_wrapper directly in the PROMPT_COMMAND string,
and re-arm the Orca DEBUG trap at the top level of that string: bash <= 4.3
(including macOS /bin/bash 3.2) restores the caller's DEBUG trap when a
function returns, so the re-arm inside __orca_finish_legacy_prompt_dispatch
was undone. Tests force the legacy dispatch on any bash and also run with
/bin/bash on macOS.

Fixes stablyai#29164

Co-authored-by: Neil <neil@stably.ai>
)

* perf(bench): separate historical startup loading evidence

* fix(bench): retry owned root identity before teardown

* fix(bench): format survivor identities explicitly

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ore, never replays the launch (stablyai#34054)

* fix(runtime): a restored host pane resumes its agent from the hook store, never replays the launch

When a WSL host's daemon restarted, the host re-created each pane with its
original launch command (a fresh Codex conversation), or a plain shell for an
agent typed into one: orcad has no resume record, because only layout-owner
builds record hook status. A pane with no record now resumes the conversation
its own hook rows name (local rows only, this workspace's), through the same
pane-owned resume path a recorded session takes.

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

* test(runtime): hook-store resume vetoes: other worktree, two sessions, shared or live elsewhere, structured host

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

* fix(runtime): a vetoed hook-store resume launches the tab's agent fresh, as before

A record read from the hook store is no promise to keep: when it is vetoed
(two sessions, shared or live in another pane, no resume command) the tab's
agent now launches fresh as main did, not a plain shell. Its two-session veto
counts only the rows it was read from (local, this workspace), so a relayed
SSH row no longer vetoes a local resume.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ed file names (stablyai#32987)

Co-authored-by: Kelvin Amoaba <97001695+AmoabaKelvin@users.noreply.github.com>
…em default (stablyai#20842)

The Codex switcher only rendered usage bars on inactive account rows from
the per-account cache, so the active row (and System default, which never
has a managed account id) showed nothing. Render the live Codex usage on
the active row when this desktop owns the accounts; a remote owner's usage
belongs to that host's target and stays off desktop-selected rows.

Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
…s first adapter (stablyai#34088)

Moves routing, machine choice, held starts, the post queue, prompt cards and owner controls out of the Slack files into a provider-neutral connector core, with Slack as the first adapter behind one adapter type. Conversation records and delivery ids are keyed by source, so Slack and Discord ids cannot collide. Behaviour-neutral for Slack apart from a slack: delivery-id prefix and a 403 (not 404) from the reply route when Slack is off. Staging's connector database is reset after deploy.
* perf(native-chat): stop hidden chats re-rendering on every tab change

Skill discovery now subscribes to tab and workspace state only while the
slash/skill picker is open, and the per-chat image owner subscription
reads only its own workspace's tabs. Previously every mounted chat
re-rendered and rescanned the tab maps on any tab change anywhere.

* Resolve native chat image context from its selected owner

* Cover selected chat image owners through preview consumers

* Use typed reads in chat subscription performance fixture

* Key chat subscription probes by fixture chat identity

* Type dense chat subscription measurement results

* Name the read-only floating chat resolver view

* Exercise document markdown in selected-owner preview test

* Check unavailable chat image through its preview dialog
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
… and cold-restores (stablyai#34095)

* fix(terminal): a WSL bridge pane whose distro went away keeps its tab and cold-restores

wsl --terminate (or a shutdown or crash) exits every live bridge pane with an
ordinary code, so Orca closed those tabs as if the shell had exited, and the
next launch opened new sessions instead of restoring the old ones. A proven
exit of a WSL bridge session now checks the shared, coalesced
wsl --list --running (never boots, bounded); if the distro is confirmed gone
it is host loss: code -1, history left unended, tab kept, exactly as after a
daemon crash. A running distro or an unanswered check leaves the exit as is,
so  still closes the tab. Native and SSH sessions are untouched.

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

* fix(terminal): trust only a running-list probe that started after the exit; close a kept tab's history

Review of the WSL bridge distro-loss check:
- A probe already in flight from before the exit can predate the distro's
  boot, so it could call a user's own quick exit host loss. Only a probe that
  started after the exit answers; one fresh probe is chained after an older
  in-flight one and shared by the whole wave.
- The outer bound drops to 3 s: every bridge exit waits on it.
- Closing a kept tab later hit SessionNotFound before removeSession, leaking
  its history; an explicit close now treats that as already stopped.

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

* fix(terminal): only a session this adapter lost to its distro may close as already gone

Swallowing SessionNotFound for any id could remove the history of a session a
legacy daemon (sharing the history dir) still runs. The adapter now records
the sessions it turned into host loss, clears them on respawn or close, and
swallows SessionNotFound on an explicit close only for those.

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

---------

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

did-start-loading fires for subframe loads too, so a page that reloads an
iframe on each poll invalidated the annotation document every cycle: the
pending card closed, grab mode was cancelled and saved-note markers were
retired. Main-frame did-start-navigation already invalidates for every
document replacement (including same-URL reloads) and same-document
navigation, so drop the did-start-loading handler and move the overlay
viewport reset next to that invalidation.

Fixes stablyai#23988

Co-authored-by: Neil <neil@stably.ai>
… it creates (stablyai#33859)

* fix(terminal): keep a brand-new remote pane's typing for the terminal it creates

* fix(terminal): new-pane typing goes only to a genuinely fresh create, never an adopted or restored terminal

* fix(terminal): decide a new pane looked blank at connect; at bind re-check only what the create can't cause

* test(terminal): replayed history from the real restore path drops a new remote pane's typing

* fix(terminal): type the replayed-history marks by xterm Terminal
stablyai#34117)

The served-relay blackbox test closed the phone and data sockets from the client and sent the resume confirm before the relay had processed the close, so on a busy runner the relay correctly answered device-resume-confirmed and the test failed (about 1.8% under load). The test now closes only the phone and waits for the relay to close the data leg with 4408, which happens in the same step that marks the connection inactive. Test-only.
…stablyai#34111)

* fix(orcad): find the app's bundled agent-browser driver for orca serve

Desktop orcad templates omit the driver for size, so orcad materialized from an
installed app reported driver_missing with ORCA_BROWSER_EXECUTABLE set. orca serve
now hands orcad the app's resources copy via ORCA_AGENT_BROWSER_DRIVER; the
slot's own driver still wins for standalone artifacts.

Fixes stablyai#33846

* test(serve): expect the app's agent-browser driver handed to orcad

* fix(serve): run a profile copy of the driver on Windows so app updates stay replaceable

* fix(serve): never lend the installed Windows driver when staging fails

* fix(serve): keep another launch's in-progress driver copy

* refactor(serve): share claimed snapshot publication for the Windows driver copy
…yai#34074)

* refactor(mobile): group native chat options and queued editing

* refactor(mobile): use a concrete type import in the recording adapter
… connector reply, local only) (stablyai#33855)

A connector-started agent is told it can answer in its thread with orca connector reply --text; the desktop posts the reply through its Orca Cloud connection, naming the conversation and turn, so the machine never holds a Slack token. Works for agents on this machine; elsewhere the command answers that replies aren't available from this host yet, and the turn's final message is still posted when the turn ends. Behind the experimentalConnectors setting.
…-exit, not a user close (stablyai#34083)

The parked watcher passes only hostCloseReason 'pty-exit' (so the pinned
guard still applies), and the local branch then let closeTab default the
reason to 'user': a process exit wrote a user tombstone and retired the
session. The local close now carries the pty-exit reason.

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

Adds Discord as a second connector adapter on the shared core: signed interactions for answer buttons, one gateway worker (same image, single leader by database lock, commits on the lock's connection, checkpoints for resume), thread posts through the shared post queue, owner-only control, and teammate context only from linked accounts. Inert until the Discord settings are configured. A narrow gitleaks allowlist entry covers connector Discord env var names in config errors.
* Source control actions start chats and terminals on their saved model

A "start an agent" action's model lived only in free-text CLI arguments, which a native chat
never reads, so AI Fix opened a chat on the default model. Each action now saves a structured
model and effort that both routes apply: chat create options, or the agent's own flags in a
terminal (winning over the same flag in CLI arguments). Desktop, dialog, fix-checks and mobile
launches carry it; Settings rows get Model/Effort selects and the CLI arguments field says it is
terminal-only.

Tracked in STA-11102.

* Name the model in the repository override note

* Type the dialog's model hand-off; keep Agent in English in Japanese copy

* fix: preserve action chat model choices through launch

* Preserve action selection ownership across chat launches

* Keep terminal host contract fixtures aligned with preflight

* fix: wait for action Settings agent inventory

* fix: consolidate direct agent routing imports

* fix: use standard action option labels

* fix: align action launch contracts with shared types

* fix: sync action option localization metadata

* test: provide action Settings inventory in mounted fixtures

* Keep action options row height stable while loading

* Let each Source Control action choose its chat permission mode

An action's saved chat choice may now carry a permission mode, with or without a model;
"Same as new chats" saves nothing. A model is replaced only when the action names one,
so a permission-only choice keeps the chat's remembered model and options.

* fix(native-chat): leave older hosts their own permission mode and keep action options readable

- An action's saved permission mode is sent only to hosts that advertise
  agentSession.create.options-mode.v1. Older hosts get no mode, in create
  options or a later setOption, so the chat starts on that host's new-chat
  default, as a phone launch to such a host already does. Hosts from
  1.4.225 to 1.4.227 refused a mode their tables lacked (OpenCode, Grok,
  OMP), leaving the action unable to start.
- Settings action rows give Model and Permissions full rows in the options
  column so saved values are readable; the loading placeholder reserves the
  same rows.
- Remove two unused locale keys; fix two stale comments.
- Tests: the older-host create carries no permission mode; a permission
  mode picked alone keeps the remembered model on an acquired create.

* fix(settings): reserve one options row while loading an action with nothing saved

Terminal and permissions-only rows no longer shrink when loading ends; a chat
agent with a model list may grow by its Model row once known.

* fix(native-chat): keep the create options mode a checked literal type

* fix(settings): share verified action option fields

* fix(settings): keep action selectors on standard typography

* Keep the launch files under their length limits after the main merge

The structured route imports its result types where they are defined, and the two older-host
action-launch tests share one setup helper.

* One action settings section for chat and terminal launches

A Source Control action's Model, model options and Permissions now apply whether it opens a
chat or a terminal; terminals receive them as the agent's own flags, and a repository's own
CLI arguments keep the flags it sets. Extra CLI arguments stay terminal-only.

* fix(source-control-ai): one terminal projection for saved action choices

Ask in a terminal now states its permission instead of leaving it to the
user's own config: Claude and Grok pass --permission-mode default, Codex
passes -a on-request -s read-only -c approvals_reviewer="user" (the
interactive CLI has no untrusted policy, so a read-only sandbox is what
asks before edits). Agents whose CLI has no flag for a mode show the
existing "Applies to chats only" hint.

The strict launch-preferences projection now requires the launch agent at
every caller, composes model ids through the resolver's own step, carries
a mode only where the agent's terminal has a flag for it, and the local
string wire keeps booleans as 'true'/'false', so Cursor Thinking/Fast reach
the terminal and an OpenCode permission-only choice is not refused.

The argument parser knows flag arity, so removing a boolean permission
flag keeps other options' values, following positionals and '--'.
Pre-admission and executor share one terminal admission check on the
options the terminal will receive. Customize with Save launches the recipe
as it will resolve once saved; legacy partial repository overrides are
edited as the complete recipe they resolve to. Empty Extra CLI arguments
start collapsed.

* fix(source-control-ai): an edited repository action saves complete

Every Settings edit of a repository action (agent, model, permission,
prompt or arguments) now starts from the complete recipe the action
resolves to, whether its stored override is owned or a legacy partial.
The resolved agent selection is kept (never an agent only detected to run
a default), an explicit null args clear stays null, and empty args stay
empty instead of being dropped. Clearing the last preference therefore
means the action agent's own defaults rather than re-inheriting Global.

The action text draft is lossless: unset or cleared prompt and args stay
so until the user types, and the inputs show them as empty, so saving a
prompt no longer writes empty args that suppress the agent's default
flags. Untouched sibling actions are not rewritten by the edit.

* fix(settings): style the Extra CLI arguments toggle through Button's own variant and size

CI's changed-code quality check refuses restyle classes on Button and
CollapsibleContent. Use the ghost variant at size xs with a margin, and
put the content spacing on a plain inner wrapper.

* fix: keep saved action and restart launch recipes consistent

* fix: describe inherited arguments and chat-only permissions accurately

* test: check repository action settings fixture type

* test: map saved action options into terminal compiler inputs

* fix: retain saved action controls during agent detection

* fix(i18n): retire obsolete Portuguese action labels

* test: align folder launch fallback mock with outcome export

* fix: carry resolved connector launch intent and mode together

* test: pin shared action launch branches to recorded agent output

* test: preserve saved source control choices through detection and migration

* test: align launch fixtures with integrated result contract

* test: retain recipe and restart contracts after main integration

* Unify Source Control action permission settings

* Keep native Ask copy with its owning change

* Preserve action drafts and old-host permission intent

* Consume rejected Source Control action saves

* Follow the moved Settings persistence fixture

* Keep permission integration contracts in the cross-version test project

* Include the action permission inheritance label in English catalogs

* Align launch contracts with shared permission recipes

* Keep Source Control action controls within their Settings columns

* Use primitive variants for compact action settings controls

* Show action arguments only for terminal defaults and widen recipe rows

* Apply terminal-only action arguments visibility to repository overrides

* Align action permission control with model controls

* Use shared settings label for action permissions

* Hide action model controls until argument migration ships
… comes due (E9 B) (stablyai#33839)

* feat(wsl-host): start a WSL host's distro when one of its automations comes due

E9 connect policy, part 2. A WSL host's automations run inside its distro, so a
distro that is off (a reboot, an idle release) missed them. While a host is connected
the desktop now reads its enabled automations' next run (on connect and every 15 min)
and keeps the soonest one per host in userData. When that time comes with the host off,
it boots the distro once, and orcad's startup catch-up fires the run within its missed-
run grace. A run due while the computer was off fires once Orca starts. Nothing wakes
past the grace window, and a connected host is left to its own scheduler.

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

* test(wsl-host): a due automation boots a parked host once, never with WSL hosts off

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

* fix(wsl-host): keep the following run, and forget changed automations (E9 review)

Review of stablyai#33839:
- A run handled while connected dropped the host's next due time. The wake now keeps
  each host's enabled schedules and computes the following run locally whatever
  happens to this one (wake failed, past grace, handled by the host), and re-reads the
  real one from a connected host.
- A deleted, disabled or rescheduled automation kept waking the distro at its old
  time. A successful automation.create/update/delete from this desktop now re-reads the
  host's automations, as do the 15-minute re-read and every connect. Nothing boots a
  distro to check: on wake, a distro already running is just reconnected.
- wakeForAutomation/isConnected live here now (moved out of stablyai#33838); isConnected reads
  the host's reachability, never booting.

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

* refactor(wsl-host): say isConnected answers for this process; keep the service in bounds

isConnected reports whether the host answers through this process; false is not
"stopped" (a fresh process has no bridge yet, and the wake just connects). The
service's quiet record read moves beside the other record helpers so the file stays
within its line limit after the E9 additions.

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

* refactor(wsl-host): give the connector contract its own file; one wake path for focus and automations

After rebasing onto stablyai#34063, stablyai#34093 and stablyai#33110 the WSL host service was over its line
limit. The connector contract (what the service drives) moves to
wsl-host-connector-contract.ts, re-exported from the service, and the focus entry and
the automation wake share one private wake step: lift the park, then reconnect unless
the host answers, read with stablyai#33110's focusReachability so neither pays a WSL round trip
when this process has no bridge yet. An automation wake no longer reconnects a host
that already answers.

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

* test(wsl-host): an automation wake with no bridge skips the reachability probe

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
…e test (stablyai#34115)

Freezes the connector's shipped table definitions: later schema changes must be additive statements. Adds a baseline fixture and an upgrade test (baseline, then current schema, compared with a fresh database on SQLite and Postgres), plus the statement-shape check relay already uses. No runtime change.
erishforG and others added 27 commits October 27, 2026 05:52
…yai#14765)

Codex paginated rollouts persist an item_completed ContextCompaction item
with no content, and legacy rollouts persist an event_msg context_compacted.
The Codex decoder dropped both, so the native Chat timeline never showed
that context was compacted. Emit a system row with the shared compaction
copy and presentation so it renders as the compaction divider.

Fixes stablyai#14188

Co-authored-by: Neil <neil@stably.ai>
…oml (stablyai#33999)

Jcode accepts a string or an array of strings per hook event, but Orca's
[hooks] parser only understood single-line strings, so any array made
status and install report error and no hook was written.

Parse string arrays, append Orca's command as the last element (after the
user's pre_tool gates), repair a stale Orca element in place, collapse
duplicate managed elements on reinstall, and remove only Orca's element on
uninstall. Edits are offset splices on the original text, so user entries,
comments and layout survive; malformed or non-string arrays still fail
closed.

Fixes stablyai#33998

Co-authored-by: Neil <neil@stably.ai>
… revert them (stablyai#34414)

QuickCommandsPane, useAddRepoCloneFlow, UntitledFileRenameDialog and the
workspace board column resize hook guarded a render-phase setState with a
ref written in the same render. When a sibling suspends, React discards
that render and its queued updates but keeps the ref write, so the retry
skipped the adjustment (no Add dialog, empty clone destination, stale
save-as name, stale column width). Track each guard in state instead, and
sync the column-resize handler refs in a layout effect.

The other two sites from the issue (WorktreeMetaDialog prevIsOpenRef and
use-native-chat-draft lastScopeKey) no longer exist on main: the dialog
remounts a keyed draft and the chat draft reads via useSyncExternalStore.

Fixes stablyai#25527

Co-authored-by: Matheus Breguêz <matbrgz@gmail.com>
…i#33190)

An unexpected ACP agent exit reported only the whole-run stderr tail, so a
startup warning read as the cause, and the observed exit code/signal was
dropped. Add the exit code or signal to the ended reason and Details, file
the stderr tail as a log diagnostic like the Claude and Codex lanes, and
read the exit status from ManagedProviderProcess.exitStatus through a
shared formatter that Claude reuses.

Fixes stablyai#33035

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
…rbatim (stablyai#14037)

Without --enter, --text reaches the PTY unmodified, so integrators can send
Escape or Ctrl+U directly. With --enter and without --interrupt, prompts to a
recognized running agent are bracketed-pasted and embedded Escape is made inert.

Fixes stablyai#14032

Co-authored-by: Neil <neil@stably.ai>
…ment while scrolling (stablyai#34267)

Scroll-spy pages rewrite the URL fragment with history.replaceState as
you scroll. The webview navigation handlers and setBrowserPageUrl treated
those same-document updates as a new document, retiring saved-note
badges and cancelling an open comment card. Fragment-only in-place
updates now keep the geometry; document navigations and in-place route
changes (path or query) still retire it, and the store no longer guesses
document boundaries from URL changes.
…tablyai#11359)

* fix(windows): probe fnm's real default alias dir for Node fallback

On Windows, fnm stores the default alias under FNM_DIR, else
%APPDATA%\fnm (legacy ~\.fnm), and node.exe sits directly in
aliases\default with no bin subdir. Probe those dirs and append them to
PATH so fnm's default only backfills a missing Node instead of shadowing
an inherited one.

Fixes stablyai#11356

Co-authored-by: niceSprite <16523864+iFwu@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>

* test: keep onTestFinished callbacks returning void

---------

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Claude <noreply@anthropic.com>
…u rows (stablyai#34047)

* fix(mobile): build the Source Control action sheet from desktop's menu rows

Adds Force Push and desktop's order, enable rules and hints.

* fix(mobile): rebase from desktop's effective base and keep the Force Push warning in the action sheet

* fix(mobile): keep the rebase row's base when the branch compare fails

* fix(mobile): keep upstream compares working when the default base lookup fails

* fix(mobile): keep the rebase target apart from the branch compare so failed or unsupported compares keep it

* fix(mobile): leave force pushes to desktop when the linked review has its own push target

* refactor(mobile): rebase onto the shown base and share the force-push block with the primary action
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…b page probe (stablyai#34427)

The title and icon-link regexes rescanned the rest of the body from every
unclosed opener, so a 192 KB page of repeated '<link ' or '<title>' blocked
the main or runtime event loop for seconds. Scan tags forward only.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…unt it will start on (stablyai#34429)

* fix(native-chat): list a resting Claude chat's / commands from the account it will start on

A resting chat's next start follows the selected Claude account, but its / menu was read from the account saved when the chat was created. Read it from the same account a new chat would be created on, and rescan when the selection changes.

* refactor(native-chat): resolve the at-rest account in the shared catalog so its key matches the scan

* test(native-chat): build the second at-rest chat record from a lease fixture
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…nd session option picks (stablyai#33977)

* fix(mobile): resolve terminal chat agents in desktop's order

Mobile now uses the shared detected-then-launch precedence and transcript-readability check; the phone's remembered agent only fills in after them.

* fix(mobile): hide Codex foreground commands from the background-task strip

* refactor(native-chat): share diff-card gutter, basename and task count

* fix(mobile): show clipped tool output sizes in KB/MB like desktop

* refactor(native-chat): share the tool-run header rules with mobile

* fix(native-chat): share tool-line detail rules; skip unsafe-only search hits

* fix(mobile): copy message text with desktop's rule

Copy now leaves out roster and background-task fallback twins and wordless provider frames.

* refactor(native-chat): memoize shared tool-line view and output sizes; share result preview

* fix(native-chat): read a lone result's preview from its narrowed body

* chore(i18n): ship chat tool and stop copy in the runtime English catalog

* refactor(native-chat): move the pending-send echo rule to shared

Mobile reuses it instead of its own matcher.

* fix(mobile): match chat echoes after their send boundary, sink unmatched ones

Uses desktop's rule: rows before the send no longer retire an echo, a send that
never lands no longer lets later glued sends retire, and an unmatched echo sits
below later turns.

* fix(mobile): keep glued photo captions and folded boundaries in echo matching

* fix(mobile): give image preview echoes the send time the occurrence rule reads

* fix(mobile): match echoes on image-normalized rows and cap open sends at eight

Same input and bound as desktop; each open send rescans the transcript.

* test(mobile): drop a pending-order case the tail test already covers

* refactor(mobile): read the send boundary once and skip echo normalization when idle

* test(native-chat): type the occurrence case's pending send

* test(mobile): drop the pending-echo wall-clock entry from the flaky-test baseline

* refactor(native-chat): share session option labels with mobile

Mobile's options pill now shortens effort levels (Med, XHigh, Min) and names
choices and options the way desktop does, from one English table that desktop
translates.

* refactor(native-chat): run mobile terminal chat options on the desktop surface

The terminal-chat option surface, its appliers, the option cache and the
dispatch types move to src/shared, and the phone builds its pickers from them.
Grok and every other agent with a catalog now get model and effort pickers on
the phone, picks are saved as the host's launch defaults, the OMP live switch
gate and discovery mapping come from one copy, and authoritative discovery
retires persisted models it no longer lists. The option stamp moves to shared.

* refactor(mobile): merge the session option type imports

* fix(mobile): keep terminal chat option picks on their own chat

The phone keeps one option surface per chat, so a pick that lands after leaving
and returning shows there, and a picker command only switches the chat it came
from. Discovered models merge with the seed catalog as on desktop, so Grok keeps
its effort picker. A Claude model switch the phone cannot confirm is never
saved as a launch default.

* fix(mobile): start terminal chat options over when a tab changes agent

A tab that runs another agent drops its kept option surfaces, report markers
and cached record, so a fresh session shows its own model. A pick that lands
after the phone moved to another chat is not saved as a launch default.

* fix(mobile): fence retired terminal chat option surfaces

A write that lands after its tab started another agent no longer publishes the
old agent's record, and a changed OMP switch capability rebuilds the chat's
surface from the cache instead of reviving an older one.

* refactor(native-chat): simplify the shared session option wiring

Desktop translation pieces use literal English defaults so the localization gate can read them, the selected-choice lookup is shared once, and the phone keeps one option surface per tab with the agent change detected from the cached record.

* refactor(mobile): return no option surface for an agent without a catalog

* test(native-chat): expect the shared Effort label in the startup picker test

* test(native-chat): keep only the label cases main still checks after the rebase

* refactor(native-chat): import the session option snapshot rules once in the PTY option surface

* refactor(native-chat): share structured option pick bookkeeping with mobile

Desktop and the phone now track picks per option through one shared book: a
repeat of an option on its way waits, other options go together, a lost reply
is not a pick done, and a stale reply only commits its own option. The phone
also re-reads options each turn and after the provider starts, and shows picks
at once while the host is still starting the provider.

* refactor(native-chat): share the option pick blocking rule

* fix(native-chat): read in-flight option picks from the shared book for launch send options

* fix(native-chat): keep the structured options hook within its line budget and pass the phone turn in the e2e harness
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…#34440)

* refactor(pty): colocate IPC test fixtures with PTY owners

* test: exclude PTY test constants from credential source census
…34443)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…op and the chat composer (stablyai#34000)

* refactor(mobile): share the queued-card projection with desktop

Image-only queued drafts showed a blank card on the phone.

* fix(mobile): honor the host's Queue follow-ups setting

With it off, a mid-turn send from the phone still queued while desktop sent it into the running turn.

* fix(mobile): keep a queued-card edit when the card leaves the queue

The phone closed the editor and dropped the typing; it now goes to the chat box as on desktop.

* fix(mobile): hold /clear and /compact for background work and sends

The phone fired the command where desktop holds it, showed start failures twice, and left the agent unnamed in failures.

* fix(mobile): hand a queued edit back to the chat it came from

* fix(mobile): scope a command's send hold to its own chat

* fix(mobile): read a published null queue as empty

* refactor(mobile): share the returned queued-card caption rule

* test(mobile): drop queued-card tests the shared rules already own

* refactor(native-chat): share the needs-attention rule and drop leftover wrappers

* fix(mobile): type the structured sends mock, drop the moved queued-cards path and ship queued edit copy at boot

* fix(native-chat): drop the queued card's unused image-label import after the rebase

* fix(mobile): order the phone's queue reorder preview by position, as the shared cards sort

* test(mobile): keep the queued-messages test within its line budget

* fix(mobile): settle terminal chat working state and turn timing with desktop's rules

Move desktop's live-status merge, Stop suppression, hook-state resolver and
terminal turn timing to src/shared/native-chat. Mobile reads the transcript
lifecycle the host already sends, so a provider turn end settles Working the
same way desktop does. Terminal chats now get the turn bar, timer and folding,
and the streaming preview uses the shared deriveNativeChatStreamingText.

* fix(mobile): send desktop's terminal Stop keys, a second Escape only for OpenCode

Desktop sends one Escape on Stop. It adds a paced second Escape only for
OpenCode, which confirms an interrupt that way. Claude Code and Codex read
Esc-Esc at an idle prompt as rewind. The rule moves to
native-chat-agent-support, and the phone now paces the second Escape at
desktop's answer step instead of 80ms.

* fix(mobile): word Orca-stop and usage-limit notices like desktop

Move desktop's Orca-stop sentences to src/shared with an English table that
desktop translates. The phone names the Orca host it pairs with, and when
that host is unnamed it says "the remote server". Usage-limit rows rebuild
desktop's sentence from the failure fact. The usageLimitLineText fallback,
which only the phone used, is deleted.

* test(mobile): pass the overlay's host name in the prompt controller harness

* test(mobile): re-record Stop goldens at desktop's step, split status-notice tests, pin the turn clock and ship notice copy at boot

* fix(mobile): offer structured Stop while a send is out and read queue-sends-next as working

Move desktop's structured Stop rule, its Stop-joining helpers and the
queue-sends-next rule to src/shared. Both sides now use them. The phone
counts its in-flight sends, so a conversation-stop host offers Stop before
the turn opens. Between queued turns the phone reads as working, and
commands that can't wait in line hold like they do on desktop. Untargeted
Stops are no longer joined on legacy hosts.

* fix(mobile): offer terminal Stop again when its Escape went nowhere

Stop hides Working on the phone like it does on desktop. The phone's Escape
can fail outright, though, while desktop writes to a local PTY. When no Escape
could have landed, the phone now drops that suppression. The streaming preview
also treats a pending echo as the transcript's tail, as desktop does, so an
earlier reply can't hide a new preview.

* fix(mobile): hold structured Stop while a send it covers is in flight

On desktop, Stop's held and Stopping state depends on whether the chat can
stop, and that includes a send still in flight. The phone now does the same.
Once Stop is pressed before the turn opens, the button holds and repeated
presses send no more untargeted cancels.

* fix(mobile): end the terminal Stop hold when a new message is sent

Desktop clears Stop's working suppression on an optimistic send, so a new turn
that keeps the same hook epoch (Claude with working subagents) still offers
Stop. The phone now does the same when a new pending echo appears.

* refactor(mobile): trim chat turn wiring after the desktop rule move

Drop the controller's unread nativeChatAgentWorking field. Re-render only when
a chat starts or stops having a send out. Keep the structured turn indicator
stable across stream frames. Type the Stop test table instead of casting.

* refactor(mobile): track structured sends in flight once for Stop and command holds

* test(mobile): re-record Stop goldens now that Stop settles with its Escape delivery

* fix(mobile): escape terminal image paths like desktop drops

* fix(mobile): keep launch-draft prefill over older user turns

* fix(mobile): rank @ file matches with desktop's fuzzy Quick Open search

* fix(mobile): use desktop's composer triggers for mid-prompt / and $ picks

Picked @ files are now quoted when the path has spaces.

* fix(mobile): send picked skill paths on structured chats

* fix(mobile): list the host's disk-scanned skills in the / and $ menus

The phone names the workspace and the host resolves its folder, refusing SSH
workspaces as desktop does; hosts without skills.discover-workspace.v1 list none.

* fix(mobile): forget a picked skill once its token is deleted

* fix(mobile): drop a picked skill as soon as its token is deleted, and retry failed skill scans

* fix(mobile): treat a picked leading skill as chat and stop failed skill scans from refiring

* test(mobile): drop the menu ranking case the shared picker tests own

* refactor(mobile): keep skill picks on the draft record and reuse shared send rules

A rejected send now keeps its picks and leading-skill origin on both send paths.

* fix(mobile): replace a re-picked skill's file and restore picks with a rejected send

* fix(mobile): read the chat agent for the skill catalog from the active resolution

* style(native-chat): format the composer autocomplete files

* fix(native-chat): import the picker's apply and send-classify rules from the shared composer module

* refactor(mobile): import the composer's draft-store hooks once

* style(mobile): format the notice row's style import
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…34449)

* refactor: group Claude structured session lifecycle owners

* refactor: consolidate retargeted Claude state type imports
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Align still-stale e2e expectations with shipped product behavior:
- the Cmd+J placeholder names workspaces and tasks
- the worktree meta dialog is titled "Workspace details"
- OS-opened CSV/TSV files are editable and render as an ARIA grid
- the delayed-save notice carries the app toaster's close button

Fixes stablyai#30114

Co-authored-by: Neil <neil@stably.ai>
@nwparker
nwparker force-pushed the fix/e2e-main-stale-specs branch from 33fe472 to 0fba66c Compare October 27, 2026 16:28
Since stablyai#33270, Create worktree reads Linear status from the repo's host
(linear:status IPC) instead of the store, so the store-only fixture left
Linear disconnected in the composer and the pasted URL was never looked up.
Stub the linear:status handler with the same connected status.
@SimonSQLML SimonSQLML closed this Oct 27, 2026
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.