Skip to content

Sync upstream 21e26de45 (conflicts) - #4

Open
WrittenByAI wants to merge 353 commits into
mainfrom
sync/upstream-21e26de45
Open

WrittenByAI wants to merge 353 commits into
mainfrom
sync/upstream-21e26de45

Conversation

@WrittenByAI

Copy link
Copy Markdown
Owner

Upstream 21e26de45 merged into the fork with conflicts.

Unresolved markers remain in: src/preload/index.ts, src/renderer/src/i18n/locales/en.json, src/renderer/src/i18n/locales/es.json, src/renderer/src/i18n/locales/ja.json, src/renderer/src/i18n/locales/ko.json, src/renderer/src/i18n/locales/zh.json

Fork files upstream also touched: package.json, pnpm-lock.yaml, src/main/github/client.ts, src/main/ipc/github.test.ts, src/main/ipc/github.ts, src/main/runtime/orca-runtime.ts, src/main/runtime/rpc/methods/github.test.ts, src/main/runtime/rpc/methods/github.ts, src/main/runtime/runtime-rpc.ts, src/preload/api-types.ts, src/preload/index.ts, src/renderer/src/assets/rich-markdown-editor.css, src/renderer/src/components/editor/EditorContent.tsx, src/renderer/src/components/editor/EditorPanelShell.tsx, src/renderer/src/components/right-sidebar/SourceControl.tsx, src/renderer/src/i18n/locales/en.json, src/renderer/src/i18n/locales/es.json, src/renderer/src/i18n/locales/ja.json, src/renderer/src/i18n/locales/ko.json, src/renderer/src/i18n/locales/zh.json, src/renderer/src/lib/language-detect.ts, src/renderer/src/store/slices/diffComments.test.ts, src/renderer/src/store/slices/diffComments.ts, src/renderer/src/web/web-preload-api.test.ts, src/renderer/src/web/web-preload-api.ts, src/shared/types.ts


Upstream sync conflict report — 21e26de45

  • Fork HEAD: 0e6048b34 (branch sync/upstream-21e26de45)
  • Upstream MERGE_HEAD: 21e26de45 ("Update README downloads badge")
  • Merge base: 792e11372 (fork base v1.4.142-rc.1)
  • Incoming upstream commits: 352
  • Conflicted files: 6 — all resolved, no markers remain.

Summary

Every conflict in this merge was purely additive on both sides: upstream
appended new entries next to spots where the fork had already appended its own,
and Git could not pick an order. No upstream logic was dropped, and no fork
behaviour was weakened. Resolution in all six files was "keep both".


src/preload/index.ts

Upstream changed: added GitHubOwnerRepo to the import type { … } from '../shared/types' block. The type is upstream's repo-scoping identity, used
across ~18 prRepo?: GitHubOwnerRepo | null parameters that arrived with
fix(github): keep work-item searches repo-scoped (#9668) and
fix(github): load PR diffs for Enterprise remotes (#8932).

Fork changed: added GitHubUploadIssueImageResult to the same import block.
This is the fork's canvas→GitHub-issue path — the return type of
gh:uploadIssueImage, which backs src/main/github/issue-images.ts (layer 3 of
the fork's tldraw feature per CLAUDE.md).

Why they collided: both sides inserted a single line at the same position,
between GitHubCreateIssueResult and GitHubWorkItem. A textbook adjacent-line
add/add conflict — no semantic overlap whatsoever.

What I did: kept both imports, ordered alphabetically
(GitHubOwnerRepo, GitHubUploadIssueImageResult, GitHubWorkItem) to match the
local sorting of that run of names. Verified both symbols are (a) exported from
src/shared/types.ts (lines 1824 and 1876) and (b) actually referenced in the
merged file body — GitHubUploadIssueImageResult at line 1264, GitHubOwnerRepo
at 17 call sites — so neither import is now unused.

Human decision needed: none.


src/renderer/src/i18n/locales/{en,es,ja,ko,zh}.json

All five locale files had the same two conflicts in the same two places, with
identical structure. They are described once here; the resolution was applied
uniformly to all five.

Conflict 1 — under auto.components.right.sidebar.SourceControl

Upstream changed: added key 97e7124eac — "Could not refresh Source Control.
Try again." (from fix(source-control): keep huge change sets responsive (#9477); used at SourceControl.tsx:7551). Upstream shipped this one properly
translated
in all four non-English locales.

Fork changed: added keys 5c951f3e68 and 897d7c622d, both "Canvas" — the
fork's Source Control labels for .tldr entries (used at SourceControl.tsx:7146
and :7303).

Why they collided: both sides appended to the tail of the same JSON object,
and the fork's last key had to gain a trailing comma to accept a successor.

What I did: kept all three keys, fork's first then upstream's, with the comma
fixed up. Upstream's translated strings were carried through verbatim per locale
(e.g. ja "Source Control を更新できませんでした。もう一度お試しください。").

Conflict 2 — under auto.components.editor

Upstream changed: added the nested block
editor.save.failure.notice.8c59ce5075 — "Failed to save the file. Please try
again." (from fix(editor): surface file-save failures instead of swallowing them (#9657); used at editor-file-save-attempt.ts:14).

Fork changed: added four sibling blocks, the entire i18n surface of the fork's
tldraw feature — TldrawIssueDialog (14 keys), TldrawSelectionOverlay (4 keys),
TldrawViewer (1 key), and tldraw.issue.image.a1b46be31d.

Why they collided: this was the messier of the two. Both sides appended a
block at the end of auto.components.editor, so both laid claim to the same
shared closing braces (} / } / } / },) as their own terminator. Git
therefore split the hunk mid-structure: the fork's side ends at an unclosed
tldraw.issue.image, upstream's ends at an unclosed editor.save.failure, and
the four common trailing lines below the marker close whichever side wins. A
naive "take both halves" concatenation would have produced invalid JSON.

What I did: rebuilt the region so each block closes its own nesting —
tldraw.issue.image gets its own } / } / },, then upstream's editor block
follows as a sibling and consumes the shared tail. Indentation was normalised to
the surrounding 8/10/12/14-space convention.

Verification

  • No conflict markers remain anywhere under src/ (ripgrep, 0 hits).
  • All five locale files parse as valid JSON (jq).
  • All eight contested keys/blocks are present in all five locales
    (jq probe of SourceControl.{5c951f3e68,897d7c622d,97e7124eac} and
    editor.{TldrawIssueDialog,TldrawSelectionOverlay,TldrawViewer,tldraw.issue.image.a1b46be31d,editor.save.failure.notice.8c59ce5075})
    — 5/5 true on every probe.

Human decision needed: none for correctness. Two cosmetic notes:

  1. The fork's tldraw strings remain English in es/ja/ko/zh. That is
    pre-existing fork state, unchanged by this merge — not a regression introduced
    here. If the fork ever wants them localised, that is separate work.
  2. Upstream's own new key 8c59ce5075 is also English in all four non-English
    locales
    . I confirmed this is upstream's shipped state, not merge damage:
    git show origin/main:…/ja.json returns the English string. So the merged
    files match upstream byte-for-byte on that key; do not "fix" it in the fork or
    it will conflict again next sync.

Overall

No fork intent was lost, and no upstream change was dropped. Nothing in this
merge touched the fork's three tldraw layers structurally — the collisions were
entirely about where new names and keys sit in a list. The merge is ready for the
normal build/test gate; nothing here needs a human judgement call.

Verdict: clean.

AmethystLiang and others added 30 commits July 16, 2026 18:08
Co-authored-by: Orca <help@stably.ai>
* Add mobile image-diff previews via shared data-URI builder

- Extracts a `buildImageDataUri` helper (src/shared/image-data-uri.ts) shared by
  the desktop ImageViewer and mobile, so both trim whitespace-wrapped base64 and
  skip non-previewable mimes (e.g. application/pdf) the same way.
- Adds mobile-diff-image-preview.ts to render binary git.diff results (add/modify/
  delete) as images instead of falling back to "Binary preview unavailable".
- Extracts resolveMobileFileTabDoc to consolidate the session file-tab loading
  logic (diff/image/html/text) out of the route file for testability.

* Fix stale binary image fallback for empty modified diffs and relay reads

- mobileDiffImageDataUri now distinguishes a true deletion (modified
  side absent) from a modify whose binary bytes arrived empty
  (relay/size-cap cases), returning null instead of the stale
  pre-change image
- readWorkingDiffFile passes the file path to bufferToBlob so relay
  working-tree reads can detect previewable image extensions instead
  of always reporting empty binary content
- add mobile-file-tab-doc.test.ts covering diff/image/binary/text
  resolution paths

* Regenerate skill bundle manifest for 1.4.144-rc.2

Co-authored-by: Orca <help@stably.ai>

* fix(review): trim comments to AGENTS.md's one/two-line why-only rule

Comments in mobile-diff-image-preview.ts and mobile-file-tab-doc.ts ran
3-6 lines and narrated mechanism instead of stating only the non-obvious
reason, per AGENTS.md's "Code Comments: Document the Why, Briefly" rule.

Co-authored-by: Orca <help@stably.ai>

* Distinguish read failures from true deletions in binary diff results

- Working-tree stat/readFile errors and relay reads previously collapsed
  onto the same empty-content signal as a genuine deletion, letting
  previewers fall back to stale original bytes on a failed read.
- Add modifiedDeleted/missing flags through status.ts, git-handler-ops,
  and git-working-file-read so only proven deletions trigger the
  original-bytes fallback; failed reads now return null.
- Tighten buildImageDataUri to accept only image/* mimes instead of
  special-casing application/pdf.

* fix(relay): expect missing:false on index blob maxBuffer overflow

readBlobAtIndex now returns a missing flag so staged deletions are
distinct from size-capped binary reads; update the overflow test.

* Allow opening deleted files to show pre-delete text or image diffs

Deleted files can now be opened to view their pre-delete content via
git.diff (including images via modifiedDeleted). Only unresolved conflicts
remain unopenable. Centralizes the canOpen rule in canOpenMobileGitStatusEntry()
to keep opener guards consistent across the mobile source control UI.

---------

Co-authored-by: Orca <help@stably.ai>
)

* fix(naming): keep the built-in branch-name prompt general

The shipped auto-rename prompt no longer hard-codes style rules (word
count, kebab-case, no prefixes). Users can already override naming via
Source Control AI instructions and the branch-name command template; a
prescriptive default fought those overrides. Git-safe sanitization still
runs after generation.

* fix(naming): preserve branch prompt overrides

* chore(skills): refresh rc.2 manifest version
…ar (stablyai#9086)

* fix(settings): show "Up to date" for current skills in sidebar

Orchestration and Computer Use showed a green "Installed" badge in the
settings sidebar even when freshness tracking knows the copy is current.
Add an "up-to-date" nav status so a present, no-update skill reads
"Up to date" (pairing with the existing "Update available" state), while
Voice — which tracks model readiness, not skill freshness — keeps
"Installed".

* fix(settings): require freshness proof for current badge

Only label a skill Up to date when every discovered placement is current. Keep Installed for unloaded, blocked, inaccessible, or unrecognized inventories, and share the classifier with the existing freshness pill. Regenerate the current skill manifest so its app version matches the package release and the required verification gate passes.
* ci(release): regenerate skill manifest on version bump

The release-cut "Bump package.json and tag" step bumped package.json but
never regenerated resources/skills/current-manifest.json, so its appVersion
stayed at the prior release. That drift shipped in v1.4.144-rc.1, rc.2, and
rc.3 (all carried an rc.1 manifest) and turns verify:skill-bundle-manifest
red on every branch after a cut, since that check runs in `pnpm lint` and
the PR `verify` job.

Regenerate the manifest right after `npm version` and stage resources/skills
into the release commit so the bundled manifest always matches the shipped
version. The generator is dependency-free (node builtins + git), so it runs
without a pnpm install, and the step's fetch-depth:0 checkout supplies the
tag history it reads.

* test(release): guard skill manifest regeneration

* test(release): require full history for skill manifest
…se (stablyai#9114)

* fix(terminal): tear down runtime-owned headless PTYs on whole-tab close

stablyai#8958 placed the closeTerminalTab renderer relay ahead of the authoritative
headless teardown in closeMobileSessionTab. Production always wires
closeTerminalTab, so the teardown became dead code for whole-parent closes
whenever a window is attached. The relay resolves close targets only from
renderer state (resolveTerminalCloseTarget), which never contains a
runtime-owned headless tab, so it acks success without killing the PTY —
leaking SSH-durable/serve PTYs and letting syncMobileSessionTabs republish
the "closed" tab (the exact case the removed comment warned about).

Run the isRuntimeOwnedHeadlessMobileTab teardown before the relay so headless
tabs are killed and pruned authoritatively even with a renderer attached;
normal adopted tabs still take the durable closeTerminalTab path. Add a
regression test that wires both notifiers (prior tests covered only the
no-notifier and in-graph cases).

* fix(terminal): preserve adopted tab close guards

* fix(runtime): preserve headless close durability on notifier failure
…tablyai#9104)

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
stablyai#9096)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: MarkXian <mark-xian@foxmail.com>
…tablyai#9097)

Co-authored-by: 조재중 <126754298+m-a-king@users.noreply.github.com>
…tablyai#9100)

Fixes stablyai#8832.

HTTP hit-testing no longer consumes path hard-wrap candidates that poisoned mid-row URLs with next-line labels. Uses soft-wrap, framed hard-wrap HTTP, and dedicated edge-wrap reconstruction instead.

Supersedes stablyai#8852 (edge-wrap approach credit: @xianjianlf2).
…tablyai#9107)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: davkim1030 <davkim1030@gmail.com>
…blyai#9101)

* fix(sidebar): preserve grouped projects under sleep filter

* test(sidebar): harden stablyai#8865 project-group sleep-filter coverage

Add adversarial cases for explicit project filters, multi-member groups,
ungrouped neighbors, and buildRows membership/count preservation.

Co-authored-by: bbingz <zzb@gxsmjx.com>

---------

Co-authored-by: bbingz <zzb@gxsmjx.com>
* fix(agents): recognize OpenCode native OC | tab titles

OpenCode's native OSC titles use `OC | <task>` without an `opencode`
token, so title classifiers left tabs as Claude/unknown. Map the native
marker (optional mux prefix) to OpenCode identity in both title
classifiers, exclude it from isClaudeAgent, and cover lookalikes plus
stale Claude launch reclaim.

Builds on and supersedes stablyai#8590 (credit @gatsby74). Fixes stablyai#8478.

* fix(agents): drop renderer import from stablyai#8478 shared repro

tsconfig.node includes src/shared tests; importing agent-status pulled
renderer modules outside the node project and failed typecheck. Assert
opencode identity via shared title classifiers only (OpenCode TUI sets
"OpenCode" and `OC | ${title}`).

* fix(runtime): fill browser cert failure map in mobile snapshot fixtures

Main's stablyai#9104 reads browserCertificateFailuresByPageId in buildMobileBrowserTab
but left partial AppState test helpers without the field, breaking PR Checks
merge commits. Default the map in fixtures and use optional chaining so partial
state cannot throw.
…tablyai#9119)

The current manifest stamped package.json's version into itself (9 lines),
so every RC/stable version bump made the committed artifact stale on every
open branch: lint failed until authors committed content-free regeneration
diffs, which also dragged the resources/skills-filtered update-roundtrip
matrix onto unrelated PRs. Cutting a release tag whose skills tree changed
had the same effect through release-mapping.json.

- current-manifest.json is now schema 2 and content-only; the generator no
  longer reads package.json. Registry and mapping stay schema 1 so the
  append-only released-history guard keeps its schema gate.
- The running build's version enters at the IPC boundary
  (skills:freshnessInventory passes app.getVersion()) and threads through
  the inventory to placement observation; current-revision placements are
  labeled with it while historical revisions keep resolving through the
  release mapping. The artifact loader and its cache stay content-only.
- verify tolerates a committed release mapping that is a byte-exact prefix
  of the derived one when every missing trailing row's revisions equal the
  current manifest (a just-cut tag over unchanged-since bytes); such rows
  are provably redundant until the next real regeneration adds them.

Artifacts now change only when skills/ content changes.
…e (Mod+Alt+N) (stablyai#8250)

* feat(editor): bindable shortcut to add a markdown review note

Adds editor.addReviewNote (default Mod+Alt+N) to the shared keybinding
registry and wires it into all three markdown surfaces: the rich editor
key handler invokes the annotation popover opener, the Monaco editor
installs a keydown listener that opens the composer for the tracked
selection target, and the preview maps the DOM selection to its
annotation block. openAnnotationPopover now prefers the live selection
target over synced state so the shortcut works even before the sync
render lands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia

* fix(editor): cover list items and Monaco path for add-review-note shortcut

Tag the preview's list-item annotation blocks with data-annotation-block-key
so the shortcut resolves selections inside li blocks (review feedback), and
extend the e2e spec to drive the Monaco source-editor wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia

* docs(e2e): explain store-driven view-mode switch in add-review-note spec

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XYtipbTz8N4woN1sxTK1ia

* refactor(editor): extract add-review-note + selection-flush modules to satisfy max-lines after rebase

* refactor(editor): spread key-handler params and extract TOC hook to satisfy max-lines

* test(editor): move add-review-note installer test into its own describe

* fix(editor): pass add-review-note chord through when Monaco cannot act; cover preview surface e2e

* fix(editor): unify add-review-note chord consumption — consume only when a composer opens

* fix(editor): gate list-item annotation block key on composer availability

* fix(editor): require live selection for keyboard add-review-note

* chore: retrigger CI against current main (merge ref built during transient main breakage at 6e91ca6)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
…tablyai#9134)

stablyai#9085 made resolveNativeChatSessionOptionDefaults fall back to the catalog
default model (sonnet) and effort (high) whenever the user had not explicitly
picked one, injecting `--model sonnet --effort high` (and codex `-m`/`-c`
equivalents) onto every agent spawn: composer, worktree activation,
source-control agents, onboarding, work-item launch, floating terminal, and
new-tab launch. That silently overrode each user's own CLI-configured default
model.

Return undefined (spawn the agent exactly as its own CLI would) unless the user
has explicitly selected a model, and carry only explicitly stored option
values. An explicit model selection still applies that model and its catalog
option defaults via resolveAgentSessionOptionLaunch.
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com>
- 分页按钮文字修正:Next→下一页, Next page→前往下一页, Previous page→前往上一页
- 翻译 4 处 Jira 连接对话框未翻译描述文案
- 翻译 2 处 Jira 集成卡片描述(远程存储/本地存储)
- 翻译 2 处文件删除确认混合场景文案
- 翻译 Grok 统一计费账户月度已用额度说明

Closes stablyai#8988

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
… imported pets (stablyai#8730)

Imported .codex-pet bundles played every animation at a flat 8 fps (~9x too
fast). Mirror Codex's exact per-frame duration tables, render uneven holds as
step-end keyframes, upgrade legacy-persisted pets at render, and add the Codex
mascot's pointer interactions (hover, grab-and-hold on frame 0, horizontal-drag
running). Hardened over several adversarial review rounds: honor explicit fps,
start each row/pet from frame 0, scope drag to its pointer, and guard untrusted
persisted data.

Fixes stablyai#8729

Co-authored-by: nasagong <zinho2000@gachon.ac.kr>
…tions (stablyai#9140)

Follow-up polish to stablyai#8730: key @Keyframes on the resolved row/frames (so a same-row fallback like hover on a jumping-less pet doesn't restart idle), mirror the importer's 60s per-frame cap at render, and trim comments to the 1-2 line convention.
* fix(shortcuts): preserve native input-source switching

* fix(shortcuts): harden native input-source switch companions

Build on stablyai#8305 (@gatsby74): normalize Space key identities so Chromium
keypress without code still cancels companions, clear stale pending on
blur/next keydown, and block insertText beforeinput while a native-only
chord is live. Add stablyai#8299 regression coverage.

Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>

* fix(shortcuts): harden native-only event tracking

---------

Co-authored-by: gatsby74 <166927047+gatsby74@users.noreply.github.com>
… cannot crash the app (stablyai#9142)

Co-authored-by: Orca <help@stably.ai>
)

* Add setting for pinned worktree group display

Co-authored-by: Orca <help@stably.ai>

* Fix pinned worktree host metadata

Co-authored-by: Orca <help@stably.ai>

* Polish pinned worktree setting copy

Co-authored-by: Orca <help@stably.ai>

* Clarify pinned worktree setting copy

Co-authored-by: Orca <help@stably.ai>

* Use original lists in pinned setting copy

Co-authored-by: Orca <help@stably.ai>

* Fix pinned sidebar order and render churn

* Fix pinned host and inbox placement

* Fix worktree host ownership consistency

---------

Co-authored-by: Orca <help@stably.ai>
…ablyai#9084)

* feat(native-chat): add mobile default-view toggle for native chat

Adds a per-device "Native chat" setting on mobile (Settings → Native chat)
that controls whether supported agent sessions open in the native chat view
or the raw terminal. Default stays terminal; flipping it on makes eligible
sessions render as native chat, mirroring the desktop default-view control.

- New orca:defaultSessionView preference (loadDefaultSessionView/save).
- Per-tab chat set refactored into a tri-state override map so a session can
  be pinned to terminal or chat regardless of the default; the legacy array
  format migrates to chat overrides on load.
- useMobileSessionViewMode resolves each tab as override ?? default and
  reloads the default on focus so a Settings change applies without remount.
- Toggle/action-sheet builders take an isTabChatView predicate.

* chore(skills): refresh skill bundle manifest for v1.4.144-rc.2

The v1.4.144-rc.2 release bump left resources/skills/current-manifest.json
pointing at rc.1, so verify:skill-bundle-manifest fails on any branch that
reaches it. Regenerated (appVersion only; no skill content changed) to
unblock CI. Unrelated to the native-chat toggle in this PR.

* fix(native-chat): harden mobile view preferences

* fix(native-chat): harden default view persistence

* fix(native-chat): serialize mobile view persistence

* fix(native-chat): reconcile failed mobile view saves

* fix(native-chat): fail closed on unreadable view overrides

* fix(native-chat): honor fail-closed view races
…ring startup (stablyai#9166)

* fix(runtime): avoid false no-agent send rejection during agent startup

* fix(renderer): let guarded note sends resolve agent status races
stablyai#9074)

* fix(agent-status): clear answered Claude question waits at answer time

An answered AskUserQuestion left the amber "waiting" indicator on sidebar
rows and tabs until the agent's next tool hook or turn end — unbounded
linger while the model thinks or streams after the answer (measured 17s
for a 1000-word reply, 44s for 3000 words).

Root cause is an event-shape change: newer Claude reports the
AskUserQuestion wait as PermissionRequest (not the PreToolUse shape stablyai#7852
special-cased), so the wait inherited real-permission stickiness and
shouldKeepClaudePermissionVisible swallowed the answer-time
PostToolUse(AskUserQuestion) working event — the identity match can never
succeed because the question's PermissionRequest carries no inheritable
tool_use_id. That silently undid stablyai#8311 for questions.

Two scoped changes, both keyed on the tool name rather than the hook
event name:

- Sticky permission hold now exempts AskUserQuestion waits, so the real
  answer-time hook (when Claude sends one) clears the wait as stablyai#8311
  intended.
- New guarded inference for the hook Claude may never send: the submit
  keystroke (Enter or digit quick-select) into a pane whose fresh status
  is a waiting AskUserQuestion synthesizes the post-answer state, exactly
  mirroring the existing interrupt inference (renderer baseline capture,
  main-process re-validation, listener lead-state sync so child-driven
  refreshes cannot resurrect the dismissed question).

Real permission waits (other tools) keep their sticky semantics; batched
input and pastes never match the submit classifier.

Verified live against a real claude CLI: waiting -> working within ~50ms
of both Enter and digit answers, question card dropped, unanswered
questions still hold amber, permission stickiness covered by tests.

* fix(agent-status): guard question answer inference

Keep multi-question, multi-select, and free-text selector interactions waiting until the full prompt is submitted. Wire native-chat answers into the same guarded inference only after every paced runtime write succeeds, with cancellation and delivery-failure coverage.

* chore(skills): refresh manifest for rc.2

* fix(agent-status): verify native chat answer delivery

* fix(agent-status): await verified question delivery

* fix(agent-status): pin native-chat answer baseline before delivery

The native-chat question-answered inference read the live pane status at
settle time (after the paced send + remote acceptance, which can span
seconds on SSH). If a replacement AskUserQuestion became current in that
window, the settle callback minted a fresh baseline from the new question
and the server cleared *its* wait — dismissing a question the user never
answered.

Capture the answered question's baseline before delivery and have the
inference getter return it, so the server re-validates against the pinned
baseline and rejects a changed status — the same capture-then-revalidate
contract the terminal keystroke path already uses. Also hoist the
shouldStepNativeChatAskAnswer predicate to a single evaluation.

Regression test swaps the live status between sendAnswer and settle and
asserts the answered question's baseline is used (fails against the prior
live-read getter).
nwparker and others added 30 commits July 20, 2026 23:59
…lescer (stablyai#9421)

* perf(mobile): route graph-sync snapshot fanout through the notify coalescer

syncWindowGraph no longer unconditionally re-serializes+fans every worktree's mobile snapshot to every client. Serve-hydrate fast path skips the build-then-drop loop when no serve-owned pty exists (desktop case); changed-worktree gating (strict superset of snapshot inputs) routes only real changes through the existing coalescer. No-op syncs 720->0 emits; single-worktree churn 720->120 (~83% fewer bytes to phone). Real changes still propagate. 11-round GPT-5.6-Sol xHIGH review converged 2-consecutive-clean; independently verified tsc+830 tests.

Co-authored-by: Orca <help@stably.ai>

* fix(mobile): bump snapshotVersion on preserved prune frames + carry split-group layout

Pre-merge adversarial review found two stale-phone regressions: (1) preserved-headless snapshots did not bump snapshotVersion, so a pruned-but-serve-preserved worktree change was dropped by clients' same-epoch freshness gate (orca-runtime.ts:22316); (2) serve-only no-op hydrate dropped the phone's split-group layout (orca-runtime.ts:3731). Both fixed + mutation-verified regression tests. 3 typechecks + suites green.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
…stablyai#9422)

* perf(runtime): gate PTY path-candidate extraction on mobile-connected

The 3-regex path extractor ran on every PTY chunk of every session for a mobile-only consumer. Now gated behind a sticky mobile-connected flag; first onReady synchronously backfills candidates from the retained 64KB window per original chunk boundary (lazy safety-net fallback). ~28.5us/chunk (~99.8%) saved on the desktop-only hot path; mobile parity preserved (Tier-1 raw-window read unchanged + backfill before any file RPC). 5-round review converged 2-consecutive-clean; independently verified tsc+902 tests.

Co-authored-by: Orca <help@stably.ai>

* fix(runtime): guard path-candidate activation on the remote runtime proxy

MobileSocketWiring.onReady called this.runtime.activateRecentPtyPathCandidateTracking() unconditionally, throwing on the E2EE/remote proxy (which lacks the method) and breaking the remote runtime connection (CI verify failure). Guarded with optional chaining (runtime-rpc.ts:900) — activation is a local-host concern. Mutation-verified E2EE-proxy regression test. Integration test + 3 typechecks green.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
…9685)

* fix(editor): fail safe when rich-markdown reconcile throws

commitRichMarkdownSerialization called reconcileSerializedMarkdown with no
guard. Its three callers all assumed the only failure was editor teardown —
which commit already handles internally (didSerialize:false) — so their empty
`catch {}` (debounced onUpdate, flush) and missing catch (Cmd+S shortcut)
actually only ever swallowed, or crashed on, a genuine reconcile throw.

When reconcile threw, the Cmd+S path crashed uncaught, and the debounced/flush
paths dropped the draft update silently — leaving editorDrafts stale and
stalling auto-save with no UI signal (the auto-save half of stablyai#9158/STA-2027,
diagnosed by @klay7w).

Make the shared choke point degrade to canonical `edited` on any reconcile
throw — the same fallback reconcile already uses internally — so no path can
crash or silently lose content; worst case is a canonical-style save (stablyai#6080).
The two remaining catch blocks now log instead of swallowing so an unexpected
serialize failure can't be invisible.

The multi-byte crash that was the known trigger is already fixed (stablyai#9642); this
hardens the shared path against any future reconcile failure.

* fix(editor): preserve source EOL on reconcile fallback
…r and feed live usage from session statuslines (stablyai#9617)

* fix(rate-limits): unstick Claude "Limited" usage and feed live usage from session statuslines

The OAuth usage endpoint's 429 Retry-After (~50 min) was ignored, so the
30s-15min automated retry lanes kept landing inside the throttle window and
the status bar stayed on a bare "Limited" indefinitely while Claude itself
worked fine.

- Respect Retry-After on 429: carry it through usageMetadata.retryAtMs and
  gate automated refetches (activation lane, poll cycles) until it expires;
  user-directed refreshes still bypass.
- Keep the last-known usage snapshot visible through rate-limited windows
  (24h) instead of dropping it after the generic 30-minute stale threshold.
- Add a managed Claude statusLine command that forwards each session's
  rate_limits (Claude Code >=2.1.80) to a new /statusline/claude loopback
  route, feeding live usage windows with zero usage-endpoint calls; OAuth
  polling pauses while the live feed is fresh. User-owned statusLine
  settings are never overwritten.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(rate-limits): keep last-known window when a statusline post carries only one

Statusline payloads may report five_hour and seven_day independently; a
partial post must not wipe the other bar to null. Also document the
seconds-vs-ms epoch heuristic.

Addresses CodeRabbit review on stablyai#9617.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(rate-limits): unstick Claude usage with live statusline feed

The OAuth polling endpoint is rate-limited; Claude's status often shows
"Limited" until the next poll cycle, even when quota remains. Live posts
from the statusline command update usage within 100ms, eliminating false
"Limited" displays during active sessions.

Manages install lifecycle via marker to respect user deletions. Handles
Windows payload buffering and guards before curl spawn. Protects against
live-post/OAuth-fetch races and cross-attribution during account switches.
Gracefully tolerates schema drift in statusline parsing.

* test(rate-limits): assert stale outgoing post doesn't affect incoming

Capture usedPercent before ingesting and assert it remains unchanged,
rather than checking for a specific value. This is more precise and less
brittle when testing session switch isolation.

---------

Co-authored-by: Dzmitry Bachko <dbachko@users.noreply.github.com>
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
…9700) (stablyai#9711)

Pin the large-QR grid track to a shared size token so long under-QR copy
(relay-degraded notice) cannot max-content the auto column and collapse
the pairing step to one glyph per line. Constrain the QR stack for wrap
and lock the layout contract in tests.
…lyai#9709)

The card column-move uses the View Transitions API, whose snapshot
pseudo-elements render in the browser top layer — above any z-index,
including the terminal dialog's z-50 Radix portal. So a card morphing
columns briefly flickered over the open terminal.

Skip the view transition while the terminal dialog is open; the card
just settles into its new column under the dialog (which covers the
board anyway) instead of animating on top.
* Fix Linear skill-install modal CTA hierarchy

Remove the redundant 'Not now' button from the Linear agent-skill
install modal and make Install the filled primary action, matching the
other setup surfaces (filled primary + muted dismiss).

'Not now' was doing exactly what the dialog's × already does (session
snooze via onOpenChange), but styled as an outline button in the
footer-right CTA slot it read as the primary action over Install —
which itself was only an outline button buried in the panel body. Users
reflexively clicked the dismiss.

- Drop 'Not now' + its unused onSnoozeForSession prop / i18n key (all locales)
- Add opt-in installVariant to AgentSkillSetupPanel (defaults 'outline';
  settings surfaces unchanged) and pass 'default' from the Linear modal
- Update tests to drive the session snooze via the dialog ×

* Keep 'Not now' as the safe dismiss; demote 'Don't show again' to a muted link

This modal auto-opens unsolicited (first activation of a Linear-linked
worktree with the skill missing), so the reflexive dismiss must be
non-destructive. Removing 'Not now' left the permanent 'Don't show again'
as the only labeled footer button — the reflexive target — which would
nuke a useful prompt forever.

Restore 'Not now' (session snooze) as a quiet ghost — the easy, safe
dismiss, matching Neil's original ask ('not now is fine, just not the
CTA'). Demote 'Don't show again' to a muted text link so permanent
suppression takes intent. Install stays the filled primary.

Restores onSnoozeForSession wiring + notNow i18n key (all locales);
tests exercise the Not now snooze path again.

* Remove 'Not now' from Linear skill-install modal; × handles session dismiss

Per product call: the modal is typically reached by clicking 'Set up' on
the reminder toast (an opt-in), and the dialog's × already dismisses for
the session. Drop the redundant 'Not now' button. Footer keeps only the
quiet muted 'Don't show again' (permanent opt-out); Install stays the
filled primary.

Removes onSnoozeForSession wiring + notNow i18n key (all locales); tests
exercise the × (Close) session-snooze path.

* Move permanent opt-out to an EyeOff icon next to the × (matches SetupGuideModal)

Replaces the footer 'Don't show again' link with a quiet EyeOff icon
button beside the dialog ×, matching the house pattern for hiding a
setup/teaching surface (SetupGuideModal, StatusBarUsageEmptyCta,
SetupGuideSidebarEntry). The footer is gone entirely, so Install is the
only labeled action; the two 'make it go away' controls (× = for now,
EyeOff = never) sit together and neither is a tempting text button.

- Rendered last in the branch so initial dialog focus lands on Install,
  not the hide button (Enter-on-open must not permanently dismiss).
- Wrapped in a local TooltipProvider so it works outside the app's global
  provider (isolated tests/mounts).
- Reuses the existing dontShowAgain i18n key for aria-label + tooltip.
- Tests locate the control by aria-label now that it has no text.

* Align EyeOff hide icon with the × (top-3 not top-3.5)

The icon-xs button is 24px tall vs the bare 16px × close, so its centered
icon sat 2px low. top-3 lines the two icon centers up (measured pixel-exact
in the running app).
…ands (stablyai#9706)

* fix(source-control): keep commit generation entry point visible

* fix(source-control): translate and clarify generate button visibility

Translate Chinese-language comments to English and improve clarity
around the showGenerate logic: config errors surface in the generation
dialog, and the Create PR flow owns generation state to prevent stacking
spinners.

---------

Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
…yai#9693)

* feat(status-bar): add tooltips explaining Detailed vs Compact usage modes

The Detailed/Compact density picker in the usage popover named both modes
but didn't explain what they change. Add hover tooltips via a new optional
`tooltip` field on SettingsSegmentedControl's options (no behavior change
for existing segmented controls).

* reword usage tooltips + add zh/ko/ja/es translations

Detailed: drop 'in the status bar'. Compact: 'Condensed usage: only the
tightest window'. Translate both keys into all locales via the repo's
translate+repair policy.

* test(status-bar): wrap UsageRosterPanel renders in TooltipProvider

The new segment tooltips use Radix Tooltip, which requires an ancestor
TooltipProvider (present in the real app at App root). Wrap the test
renders to match, fixing 3 failures.

* i18n(usage-tooltips): fix zh/ja mistranslations of 'full usage'

The zh/ja 'Detailed' tooltips read as an imperative ('make full use of
bars/labels/percentages') instead of the intended noun ('full usage
shown with ...'). Correct to the usage-status noun, align ja Compact to
使用状況 (not 使用法/how-to), and drop a stray trailing period in es.

* chore(skills): regenerate skill bundle manifest for release-tag drift

New release tags cut since the manifest was last committed appended
orca-cli snapshots (releaseRevision 33→35). Append-only regen — released
history is preserved; only unblocks the verify:skill-bundle-manifest gate.

* i18n(usage-tooltips): polish zh Compact tooltip wording

The zh Compact tooltip read like an instruction ('only use the tightest
window'). Reword to a descriptive summary parallel to Detailed. Keep the
'tightest = highest-usage window' meaning (getTightestUsageSection picks
max usedPercent) rather than CodeRabbit's '最短/shortest', which would
misdescribe it as the shortest-duration window.
stablyai#9764)

* feat(settings): link Linear capabilities pane to Integrations settings

The Agent capabilities -> Linear pane installs the orca-linear skill but
gave no path to where the connection itself lives. Add a link to the
Integrations settings pane, where connected Linear workspaces and API
keys are managed.

* refactor(settings): move Integrations link to bottom of Linear pane

* refactor(settings): drop icon from Linear Integrations link
* feat(linear): add complete issue relations

* fix(linear): harden relation reads and writes

* fix(linear): classify ambiguous relation writes
* feat(linear): add MCP-style save issue

* fix(linear): harden save issue parity

* fix(linear): close save issue contract gaps

* docs(linear): bundle project discovery with save issue
…radius (stablyai#9763)

* fix(codex): count per-account homes in usage and state removal blast radius

- usage scanner now includes codex-accounts/*/home/sessions so multi-account
  usage is no longer silently undercounted (audit F2)
- account-removal dialog copy now states that session history and MCP logins
  are permanently deleted with the managed home (audit F1 mitigation)

* fix(codex): harden account usage discovery
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ablyai#9779)

* fix(settings): stop mislabeling a WSL default shell as PowerShell

The Terminal settings shell toggle coerced a stored `wsl.exe` default to
`powershell.exe` for display, so a WSL default (set in onboarding) showed
"PowerShell" selected and wrongly surfaced the PowerShell-version options.

Drop the coercion and surface WSL as a disabled, already-selected segment
when it's the active default, so the control reflects the real shell. WSL
stays non-selectable here because choosing it needs a companion distro that
only the onboarding step configures; wiring a full WSL picker into Settings
is left for a deliberate design pass against the per-project runtime model.

* test(settings): cover persisted WSL shell display
* fix(web): preserve paired runtime ownership

* fix(web): validate runtime port scan payloads

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…dow (stablyai#9765)

* fix(dashboard-popout): wire terminal copy/paste in the popped-out window

The Edit menu's Paste is a custom item that routes Cmd/Ctrl+V to the
focused window as ui:appMenuPaste, and only the main window's React root
listens for it — the popout dropped it, so paste silently did nothing.
The copy chord similarly resolves in the main window's before-input-event
handler, which the popout window never registers; the menu's role:'copy'
no-ops on xterm's empty hidden textarea.

AgentTerminalPreview now subscribes to onAppMenuPaste (guarded on focus
inside the preview) and pastes via terminal.paste(), which xterm flags as
user input so the existing preview->PTY routing and main-process input
limits apply. A custom key handler honors the terminal.copySelection and
terminal.paste keybindings, skipping plain Mod+V since the menu
accelerator owns that chord (handling it twice would paste double). The
popout bootstrap fetches keybinding overrides so custom bindings apply.

* fix(dashboard-popout): harden terminal clipboard routing

* fix(dashboard-popout): authorize terminal clipboard text

* test(dashboard-popout): harden terminal paste coverage

* fix(terminal): normalize streamed paste newlines

* fix(dashboard-popout): forward macOS IME native-text commits in the preview terminal (stablyai#9771)

* fix(dashboard-popout): forward macOS IME native-text commits in the preview terminal

The preview terminal enables xterm's kitty keyboard protocol (via
buildDefaultTerminalOptions), whose encoder can encode and cancel a
printable keydown before Chromium commits the real IME/native text —
silently dropping macOS input-source commits and synthetic Unicode
injection. Main-window panes guard this with the IME native-text
forwarder; the pop-out preview never installed it.

Install the composition tracker + forwarder (macOS-only, mirroring
TerminalPane) and claim native-text key events at the top of the
preview's custom key handler so the committed glyph reaches the PTY via
terminal.input() and the existing user-input routing.

* fix(dashboard-popout): prewarm IME input source

* fix(skills): preserve released history across new tags (stablyai#9778)

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

* fix(skills): record latest Linear release history (stablyai#9777)

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>

---------

Co-authored-by: OrcaWin <alpha-eng@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…ehind it (stablyai#9769)

* fix(dashboard-popout): zoom the pop-out window, not the main window behind it

The View menu's zoom handlers always sent terminal:zoom to the main
window, so zooming while the dashboard pop-out was focused zoomed the
window behind it. The pop-out also never applied the persisted
uiZoomLevel, so it always rendered at 100% in a zoomed app.

The pop-out now applies uiZoomLevel on dom-ready and follows app-zoom
changes while open (main-window zoom, settings control, mobile ui.set).
Menu zoom routes to the pop-out when it is the focused window, stepping
its own webContents zoom, and a narrow before-input-event handler
resolves the zoom.in/out/reset chords (honoring keybinding overrides)
since the pop-out has no renderer-side shortcut plumbing. The step/clamp
constants move to shared/ui-zoom-level.ts so main and renderer share one
definition.

* fix(dashboard-popout): isolate window zoom

* fix(dashboard-popout): deny unused permissions

* docs(dashboard-popout): clarify wheel zoom path
* feat(codex): surface nested subagents

* fix(codex): retire child rows on root stop

* fix(codex): preserve nested agent state on restart

* fix(codex): preserve nested agent identity

* fix(codex): preserve subagents across relay restarts

* chore(skills): refresh generated manifests

* fix(codex): keep inferred interrupts terminal

* chore(skills): record latest release snapshots
)

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Resolved by fork-sync agent; see report.
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.