* fix(terminal): leave the alt screen before replaying a pushed host snapshot
A remote terminal running a full-screen agent (Claude Code) could show old
output (e.g. a setup script's pnpm log) interleaved with the agent's screen
after switching back to the tab. The host's pushed snapshot is a serialized
image that starts on the normal buffer and enters the alternate screen
itself, but the replay drain cleared with ESC[2J without leaving alt. The
image's history painted into the agent's screen, its own ?1049h was a no-op,
and the agent's diff paints landed on top of the stale cells.
The remote-runtime transport now marks snapshots as serialized images, and
the drain grounds them with the shared snapshot prologue (switching to the
normal buffer first). Raw byte replays (SSH relay ring buffers) keep the
in-place clear.
* fix(terminal): paint folded remote snapshots from the normal buffer everywhere
Review follow-ups:
- Rename the flag to carriesNormalBuffer: what the painters rely on is that
the image starts on the normal buffer and enters alt itself.
- Hidden-output restore had the same bug for remote requested snapshots
(history folded into data, alternateScreen set): the painter entered alt
first and painted the normal buffer into the TUI's screen. Requested remote
snapshots now carry the flag and take the normal-buffer start.
- Flag pushed snapshots replayed after a cancelled shutdown too.
- Pushed snapshots carry only the screen, so over a live TUI the drain keeps
the normal-buffer history it covers instead of wiping it.
- Read the pane's buffer after queued output parses.
- Tests compare whole buffers against a fresh terminal, use production image
shapes, and pin the raw-replay path with the same oracle.
* fix(terminal): let the replay drain own the recovery snapshot clear
The recovery prefix's own \x1b[3J ran after the drain's prologue and wiped the
history the drain keeps under a live TUI. Keep only the latch release, which
still makes an empty recovery snapshot non-empty so it is applied.
* fix(terminal): keep the recovery snapshot's own screen clear
Consumers that write recovery snapshots straight into xterm (no replay drain)
rely on the prefix clearing the stale screen. Restore \x1b[2J\x1b[H and drop
only \x1b[3J, which wiped history the image does not carry.
* test(terminal): drive the drain with the real recovery payload
The multiplexer prepends its own screen clear; replaying that exact payload
pins that the prefix cannot wipe the history a TUI covers.
* fix(terminal): keep TUI-covered history only when the grids match
Second review follow-ups:
- A pushed image carries only the host's screen; the pane's frozen history
continues it exactly only on the same grid. On another grid keeping it
duplicated or dropped lines, so the image replaces it there.
- Tests replay the pane's writes and grid changes into a real terminal and
compare whole buffers with the host, including a mismatched-grid case.
- The split branch shares the normal-buffer preamble; drop the now
single-use abort helper.
- Type serializeBuffer as RemoteRuntimeSnapshotImage so the flag is carried
by type, not by object pass-through.
- Register the grid and raw-replay cases in the reliability gate.
* fix(terminal): keep TUI-covered history only while the host is still on alt
The drain kept the pane's history whenever the pane was on the alt screen. If
the host's TUI exited while the tab was hidden, the shell wrote past that
history and the kept lines no longer continued the host's screen. Require the
host's own alternateScreen too; an absent flag proves nothing, so the image
replaces history as on main.
Also: one buildSnapshotReplayPreamble for every first replay write, the drain's
image and raw clears as separate branches (raw byte-identical to main, comment
restored), and a single paneAtSourceGrid check.
* fix(terminal): gate kept history on the host's shell-owner proof
The host sends alternateScreen only with terminalOwner 'shell', i.e. once it has
proven the TUI exited, so `alternateScreen === true` never held for a live TUI
and the drain wiped the history it should keep. Replace the history only once
the host proves the TUI exited; tests now use production snapshot shapes. Move
the relay-overlap comment into the raw-replay branch it describes.
* fix(terminal): keep TUI-covered history only on a proven shared grid
An image without its grid cannot prove the pane's history continues its screen,
so it now replaces history. Also update a stale recovery-prefix test comment.
* test(terminal): replay host-serialized snapshots through the real wire and drain
Hand-written replay meta hid a gate that production never satisfies. Drive the
host's own emulator, ownership mirror, serializer and recovery publisher through
the real wire, client parser, remote transport and pane drain, for a live TUI
and after the host proves it exited.
* fix(terminal): repaint only the alt frame over a live TUI, leaving history alone
Keeping the pane's history by clearing only the normal screen assumed a pushed
image carries no history, which the host does not guarantee: a 0-row push can
reuse a concurrent requested capture, and its history then landed twice in
scrollback. It also wiped history on a grid mismatch, where main kept it.
When pane and image are both on alt, the image's normal part adds nothing (the
normal buffers froze together), so paint only its alt payload after an alt-side
clear, split at the host's own boundary (splitAtAlternateScreenEntry, now shared
with the daemon). Any other image paints from the normal buffer. This drops
keepScrollback, the owner and grid gates, and the recovery-prefix change, so
history behaves exactly as on main.
* test(terminal): pin the parse wait and the cancelled-shutdown flag
Final-review follow-ups: a drain test where the TUI's ?1049h is still queued
when the image arrives, and a transport test replaying a push buffered during a
cancelled shutdown; each fails when its guard is removed. The requested-image
test now names the exited-TUI case it covers, requestSnapshot shares the
snapshot image type, and the clear comment no longer implies every clear drops
scrollback.
* Redesign the phone-control and phone-size terminal dialogs
Drop the eyebrow label and circled icon, shorten the copy so it no longer
restates the buttons, and give each state one primary action with a quieter
"all" action. Collapse moves out of the button row into a Minimize icon in
the corner. Behavior is unchanged.
* Point the phone settings copy at the renamed Restore button; drop dead ko overrides
The phone app and desktop update independently, so name only "Restore",
which matches both the old and new desktop banner labels.
Update tooltip and settings pane help text to accurately describe
Caffeinate's behavior: it prevents idle sleep while active, but
MacBook lid closing may still trigger sleep per device power policy.
The previous copy incorrectly suggested Orca could prevent lid-close
sleep.
* fix(sidebar): stop Manual sort from mirroring sortEpoch into state
In Manual mode the sort hook copied every sortEpoch bump into state from an
effect, adding a nested React update per bump. A burst of store bumps at
startup stacked those into 'Maximum update depth exceeded' (React #185).
Manual now reads the live epoch directly; debounced modes are unchanged.
* feat(sidebar): tell people when a drop switches sort to Manual
Reordering by drag still switches the sidebar to Manual so the drop sticks,
but it used to happen silently. Show a toast with a 'Back to <previous sort>'
action; it retires itself on any later sort change so it can't override a
newer choice. Drops while already in Manual stay silent.
* feat(store): settle sortEpoch in the store instead of in React state
Add settledSortEpoch plus a 3 s settle timer owned by a store listener
installed in the state creator. Every write path (slice actions, the web
session sync patch) goes through it, so the settled value stays correct
with no sidebar mounted. Manual, a sort-mode switch, and a bump that
changes the non-archived row count settle in the same notify; other bumps
restart the window. A reset that lands settled clears the timer, and the
disposer runs on HMR teardown.
* fix(sidebar): read the settled sort epoch instead of mirroring it into state
The sort hook no longer copies sortEpoch into React state from an effect in
any mode; it reads the store's settledSortEpoch directly. That pattern added
a nested update per bump and stacked into "Maximum update depth exceeded"
(React #185) under flushSync bursts. Tests cover Manual, add/remove, burst
reset, no-bump row changes, mode switch, and 80 flushSync bumps in Recent
while worktrees are added.
* cleaning up
* fix(store): settle bumps when rows update during pending window
Detect structural changes on worktreesByRepo updates in addition to sort
epoch changes. When a row arrives without its own bump during a pending
settlement window, the changed composition must still trigger settlement.
* Isolate code editor text and undo history by execution host
* Verify editor owner resolution and Windows model path isolation
* Respect native model line endings in content sync history coverage
* Preserve selection and scroll when editor ownership resolves
* Verify owner synchronization against a reachable editor change callback
Keep Cursor quota requests tied to the selected account by omitting ambient Electron session credentials. Preserve explicit account cookies and redirect handling.
Credit: Li-Sanze for the original credential-mode fix in #23626, carried through #24575; jjongsta and chengjiaxiao for the reports. Native HTTP/HTTPS cookie isolation, mutation controls and proxy behavior were verified before merging. This does not claim to resolve the separate initial-authentication failure in #23612.
* fix(native-chat): a new structured chat's model list comes from the host that runs it
agentSession.modelCatalog builds the structured-session host like agentSession.options,
so a host with no saved chats since it started answers instead of refusing. When the
host answers unknown because its first listing runs in the background, the picker
re-reads on a bounded schedule until that listing lands. Local terminal-backed chat
skips the structured catalog when a custom launch command is configured.
* fix(native-chat): wait on the host's first model listing instead of re-reading on a timer
A new structured chat's picker read the host catalog once; on an account the
host had never listed, the answer was "unknown" while a background listing ran,
and the client re-read on a 1-30 s schedule. Replace the schedule with the
host's own completion signal:
- The host answers a cold read with `listingInProgress: true` (new optional
field) once it has started or joined that listing. A read that passes the new
optional `waitForListing` param awaits the same joined listing and answers
with it, or a plain "unknown" if it failed. The at-rest options read never
waits. A host that predates the field never sends it, so the client never
sends the param to a host that would refuse it.
- The picker reads once per open and attach; after the host's report it sends
one waiting read, with a 90 s client timeout above the slowest listing.
While that read is out, the model pill keeps its label but cannot open or be
set (typed /model included). An answer, failure, timeout, hide, attach or
the provider's own list releases it.
- `agentSession.modelCatalog` builds the structured-session host only for a
read that names a session (a structured chat). Terminal-backed chat's
session-less read keeps the non-building gate, so a desktop that never runs
structured chat never opens the session journal.
* fix(native-chat): one waiting model-list read per chat, and no late menu open
The waiting catalog read was owned by one run of the picker's effect. Attach
(a new fence), hide/show or a send re-ran the effect: the cleanup released the
model picker onto the built-in list for a round trip, and the new run sent a
second waiting read while the first, which cannot be withdrawn, kept a remote
call slot until the listing ended.
The waiting read now belongs to the chat (runtime target + agent + session):
a small registry keeps one in flight per chat, every re-run or remount joins
it, and the entry is deleted when the read settles. The picker hold is derived
from that entry being in flight, so it lasts across attach and hide/show and
ends when the read settles, the provider reports its own list, or the pane
switches to another session. Answers still pass the stale and record checks.
A bare /model typed while the list loads no longer opens the model menu by
itself when the list lands: the menu stays keyed on the request, and only its
initial open is suppressed while pending, so the request is spent shut and the
end of the pending period never remounts it.
* fix(native-chat): release the model picker in the same commit as the host list
When the waiting catalog read settled in the chat that started it, the
registry dropped its entry and told subscribers first, and the host list was
applied a few microtasks later. React committed once with the picker enabled
on the built-in list, then again with the host's list.
Joiners now hand the registry their apply callback, and the registry runs
every joiner (with the answer, or nothing when the read failed or timed out)
before it deletes the entry and notifies. The release and the list land in one
commit. An effect cleanup leaves the wait instead of flagging itself stale.
* fix(runtime): queue model catalog reads in the long-wait lane
A model catalog read that waits on a host's first listing replies only when
that listing ends, yet it took one of the 8 foreground call slots for its
server. Enough chats opened during one cold listing would stall that server's
sends and interrupts until a wait settled.
agentSession.modelCatalog now joins worktree.rm in the long-wait lane: same
concurrency, counted apart from the foreground calls. The queue classifies by
method only, and a warm catalog read answers at once, so the whole method
moves.
* fix(mobile): say that workspace sort, grouping, and filters are shared
The Manual sort option was subtitled 'Server order', but it orders by the
desktop's drag ranks. Sort, grouping, and filters on the phone all write the
host's shared view settings, so changing them also changes every other
device on that host, which the screen never said. Relabel Manual as 'Desktop
drag order' and add 'Shared with other devices on this host' under the Sort
By, Group By, and Filter titles. The note avoids naming a desktop sidebar
because headless hosts have none.
* fix(mobile): prevent filter modal heading expansion
Add flexShrink: 1 to allow the heading container to shrink when
space is constrained. Update comment to clarify why workspace view
is shared across devices.
* update wording
* fix(opencode): keep scan budgets across queue waits and batches
Reuse the scan-owned lifetime proposed in #10708 by @AmethystLiang with the existing shared worker queue.
* test(opencode): check nonempty session fixtures and lint scoped controls
* Derive OpenCode scan deadline message from its budget
---------
Co-authored-by: Neil Parker <nwparker@MacBook-Pro-3.localdomain>
Co-authored-by: OpenCode issue campaign <codex@localhost>
* fix(native-chat): keep a message the host accepted then rejected in the desktop chat as not sent
Draw it in place from the host's history, so a crash that loses the outbox no
longer makes it vanish. A later copy of the same body supersedes it; the outbox
row wins while it holds the message; the phone is unchanged.
* test(native-chat): pin the same-id rule apart from the body match
* test(native-chat): type the rejected-in-place fixture body as a text block
* fix(native-chat): let the host's row own a message it recorded and then rejected
Once the host's journal records a send as rejected, the desktop outbox lets it
go, as it already does for delivered and Stop-withdrawn sends: the host's row
shows it as not sent, with the host's reason and no Retry. The outbox keeps
only sends the host refused before recording them, which keep their Retry.
A send whose own reply says it was rejected is drawn by its outbox entry, with
no Retry, until the journal carries the row; a copy left by an earlier session
is dropped when the chat opens.
- the transcript no longer hides a host row behind an outbox entry with the
same id or the same text; those rules and their cache are gone
- a rejected message the queue holds (a draft's hand-off, or a live card under
its id) is drawn as its card, not as a row
- a later copy of the same text hides a rejected row only when it was sent
once the rejection was known, so a deliberate repeat stays
- delivery notices read the same visibility rule as the transcript; a chat
whose only rejection a Stop withdrew no longer rebuilds them per batch
- the body fingerprint helper goes back to the host, its only user
* test(native-chat): keep one row when copies of a rejected message share an instant
* refactor(native-chat): let the host's notice replace the outbox's under the same id
* test(native-chat): pass the queued card ids in the tool-stream cost transcript
* fix(native-chat): keep the host's record as what lets a rejected message go
- the outbox no longer drops a host-rejected message when a chat opens; the
reconcile lets it go once the journal's submissions say it was rejected, and
that drop is written to storage, so nothing reads as still owed
- a message the host rejected while the chat watched waits for its journal row
with no Retry; one read back from storage with no row loaded keeps its Retry
under a new id, since the host may have lost it
- the delivery notices keep the same map and notice objects across a batch that
words every row the same, so a submission batch re-renders no row
- a rejected command such as /compact stays hidden: its own reply reports it
- the desktop transcript requires the queued card ids, with a controller-level
test that a card holding a rejected message keeps its row hidden
* fix(native-chat): draw a queued message where the host rejected it
A message accepted to hand over later and rejected before any handover now sits
at its rejection, as a handover places one: what the agent did while it waited
happened before it, and the newest history page holds it. One handed over, or
dispatched as it was recorded, keeps its place. An older host does not move it,
so it stays at its submission, still drawn.
A failed start now rejects the queued messages and writes its row in ONE
journal append, the messages first: no reader ever meets one without the
other, and the messages still draw above the row that says why.
* test(native-chat): pin that rows written together roll back together
* fix(native-chat): draw every rejected message where it was rejected
Not only a queued message: one handed over into a turn and then rejected, or
sent directly and rejected, also sits at its rejection, in no turn. A message
in doubt stays where it was, a plain bubble: it may have reached the agent.
* fix(native-chat): decide a rejected message's Retry from the host's stored fact
- a message the host recorded and then rejected has no Retry on any mount,
however that mount learned of it, and a Dismiss that clears it from storage;
a send refused before the host recorded it keeps its Retry
- the rule that keeps a rejected command such as /compact out of the
transcript moves into the one visibility function rows and notices share
- the outbox state docs say what lets a recorded message go: the client holding
its rejected submission, whose row the host places at the rejection
* fix(native-chat): write no start-failure row when a Stop withdrew every queued message first
* test(native-chat): pass the Dismiss action in the delivery-notice hook tests
* fix(native-chat): keep a rejected message's outbox copy until its row loads
An older host leaves a rejected message where it was sent, which may be older
than the loaded window: the chat then holds the rejected submission but not the
row that draws it. The outbox copy now stays until that row loads, marked as
the host recorded it (Dismiss, no Retry, in the host's words), and leaves once
the page holding the row is loaded. Derived from the loaded rows each time.
Tests that label their projection as the phone's now pass the phone's own
setting.
* test(native-chat): type the outbox hook props that carry loaded rows
* fix(native-chat): write nothing when a journal batch settles nothing in the outbox
The outbox re-reads the journal on every batch since it waits for a rejected
message's row to load. Its reconcile now returns each unchanged entry, and the
list, as themselves (a message left in doubt included), so a batch that changes
nothing writes nothing to storage. The reconcile moves to its own module.
A copy the host recorded and rejected owes no delivery, so it no longer keeps a
hidden pane reading the journal.
* test(native-chat): count storage writes on the outbox's own storage object
* fix(native-chat): let a recorded rejected message's outbox copy leave on its own, with no Dismiss
The outbox copy of a message the host recorded and then rejected draws it only
while the host's row is not loaded, and leaves on the batch or page that loads
that row. It owes no delivery and offers no control: sending it again is a new
message. The Dismiss that let the user clear it is gone, from the outbox, the
notices and the session controller.
* fix(native-chat): a resent send id gets its recorded answer, never an early refusal or a made-up record
The host now looks a resent send id up before preparing the session. A row
that settled refused answers with its refusal before the chat is opened. A
resend whose chat cannot be opened or made ready answers unknown instead of a
refusal. A /clear in flight refuses only ids the ledger does not hold.
A send row now records the journal epoch it was admitted into. An unsettled
row with nothing written in that same epoch runs for the first time; under a
later epoch the host answers unknown instead of reconstructing a submission
it never had. The host advertises agent-session.send-answers-proof.v1.
* test(native-chat): pass the ledger row to the thread-goal rerun check
* fix(native-chat): a send's answer commits with its write, and a resend is answered before any write
A send (and /compact) settles its ledger row `succeeded` in the same SQLite transaction as the
submission or queued draft that accepts it, so a row still `pending` proves nothing was written and
a resend runs it for the first time. The unknown-before-run mark and the per-row journal epoch go.
A resent id is answered from its row and the journal before preparation starts an agent and before
any write transaction: a recorded refusal with nothing opened; otherwise the conversation is opened
(no agent start for a send) and replayed, and a conversation that will not open answers unknown.
* fix(native-chat): a ledger refusal is answered first, and a /clear refuses only a send's first run
An id the ledger refuses (expired, conflict, invalid, capacity) is answered as admission would,
with no journal read, preparation or write, so a closed chat or a read-only store answers it too.
A re-read after the replay open that comes back refused returns that refusal.
Whether a /clear is in flight is read when a send arrives and applied in the send's preparation
for a first run only: an id the ledger holds by the send's turn, including one whose earlier
attempt was queued ahead of the clear, is answered from its record. MutationPlan makes
settlesWithWrite and settledOutcome exclusive; the capability text no longer promises a refused
id never sends.
* docs(native-chat): say what a replay's preparation does for every plan
* fix(codex): keep Orca-only MCP servers when refreshing the retained shared home
The refresh for panes that outlive an update treated the old shared
home's whole MCP root as owned by ~/.codex, so it deleted servers the
user had added from an Orca terminal, which existed only there. Read the
home's settings baseline instead, as the normal mirror does: drop only
servers the last mirror copied from ~/.codex. No baseline keeps the old
behaviour; an unreadable one skips the refresh. The baseline is not
advanced, keeping the refresh one-way.
STA-9109
* test(codex): type the MCP ownership baseline fixture
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* feat(codex): tell Windows users once what stays behind when Codex moves onto ~/.codex
When Windows' system-default Codex first runs on ~/.codex (launch prep or
the usage poll), main decides once whether Orca's managed home was ever
used and which MCP servers lived only there, and persists that in UI
state. The renderer shows one dismissible toast when a Codex terminal
exists, after the server-isolation notice rather than on top of it, and
clears the notice when shown.
The "kept only in the managed home" MCP rule is extracted into
isRuntimeOnlyMcpServer, which the config mirror merge now uses too, so
the notice names exactly the servers the mirror would have kept.
* fix(codex): stop counting Orca's own config.toml as use of the old Codex home
Orca's hook install writes that home's config.toml on every startup, so its
presence was true for nearly every Windows user with Codex. The home now
counts as used only with recorded sessions or an MCP server of its own.
Resolver tests keep one case per input source.
* refactor(codex): ask main for the shared-settings notice instead of persisting it
The persisted missing/object/null field, written from launch prep and the
usage poll, becomes a plain codexSharedSettingsNoticeSeen flag mirroring
codexTerminalServerIsolationNoticeSeen. When a Codex terminal first appears
and the flag is unset, the renderer asks codexConfigSync:sharedSettingsNotice
once; main answers read-only (Windows, system default on ~/.codex, managed
home path without mkdir) and maps any read error to null.
Runtime-home routing, launch and the test harness return to main's code.
The notice no longer waits for the server-isolation toast; they may stack.
The Codex-terminal watch moves to codex-terminal-presence.ts.
* refactor(codex): watch for the first Codex terminal in one place for both notices
The server-isolation notice now passes its due check to
whenCodexTerminalAppears instead of keeping its own copy of the presence
scan, input filter and subscription loop. Its behaviour and tests are
unchanged.
* docs(codex): trim isRuntimeOnlyMcpServer's comment to why it is shared
* refactor(codex): keep McpServerTomlOwnership private to its module
* test(codex): cover the shared-settings notice channel without type assertions
Handlers are looked up by channel now that two are registered, so the
status tests no longer depend on registration order.
* refactor(codex): show the Windows shared-settings notice without asking main
Every way of detecting who relied on Orca's old Codex folder had false
positives, so the renderer now shows one static toast on Windows the first
time a Codex terminal exists. This drops the main-process resolver, its IPC
channel, preload line, web stub and shared type, and the MCP-names variant
of the description.
* refactor(codex): restore the MCP server ownership helpers to main's shape
The static notice no longer reads MCP servers, so the shared
isRuntimeOnlyMcpServer extraction has no second caller.
* refactor(codex): let each notice decide when it is due, so the Codex watcher only watches
The isolation notice now selects its due predicate and starts the watcher only while due, so whenCodexTerminalAppears no longer takes an isDue or re-checks hydration and settings. The shared-settings notice uses isLocalWindowsDesktopClient, its test stubs the user agent instead of mocking pane-helpers, and the hydration safeguard it relies on is now tested on the UI slice itself.
* test(codex): drive the Codex notices through a reactive store, and drop a redundant hydration gate
The server-isolation notice now reads "is it due" through a store selector, but its test
mocked the store without re-rendering, so a due change after mount (persisted UI loading,
the setting turning off) was never exercised. The notice tests now share one harness backed
by a real zustand store, the shared watcher gets its own test, and both notices cover the
seen flag loading after mount.
persistedUIReady is dropped from isNoticeDue: the seen flag defaults to true and only
hydration clears it, in the same update that sets persistedUIReady. Both notices now gate
the same way.
* fix(codex): keep the shared-settings toast until dismissed, and shorten it
It is marked seen before it shows, so a 15s auto-close could lose it for good while the user
is typing in the Codex terminal that triggered it. Every other one-shot notice that marks
itself seen on show stays until dismissed; this now does too.
The text drops the sentence that repeated the title and keeps only what to expect and do.
---------
Co-authored-by: Orca Worker <orca-worker@localhost>
* Restore the owning Orca CLI path after shell profiles
* Use a literal marker for the Bash lookup regression
* Preserve plain panes and initialize zsh after prompt hook replacement
* Preserve user line-editor dispatchers during deferred startup
* fix: retain CLI startup when global Zsh replaces prompt hooks
* test: replay global Zsh hook replacement after host startup
* test: isolate controlled Zsh widgets from distro keyboard setup
* fix(shell): preserve user hooks during deferred zsh initialization
* Keep completed Zsh startup hooks retired when the wrapper is sourced again
---------
Co-authored-by: Codex <codex@openai.com>
Co-authored-by: Orca maintenance <orca-maintenance@users.noreply.github.com>
Co-authored-by: Orca campaign <orca-campaign@local.invalid>
* Speed up terminal test oracles without reducing replay coverage
* Call asynchronous parser through its checked test interface
* Preserve evidence document final newline for concurrent merges
* fix(agents): keep ~/.copilot/config.json owner-only when Orca trusts a folder
Marking a folder trusted for Copilot rewrote ~/.copilot/config.json through a
temp file created with the default umask mode (usually 0644), so an owner-only
file that can hold copilotTokens became readable by other local users. Since
the folder-trust change this write also runs on SSH hosts, where other users
exist. The rewrite now always writes the file owner-only (0600).
* test(agents): cover a fresh owner-only Copilot config.json under a permissive umask
---------
Co-authored-by: m4air <m4air@Mac.localdomain>
Keeps a fork issue’s details, metadata and edits bound to the repository the user opened, including same-number issues in fork and upstream. Repairs selected-assignee leakage and delayed failed edits repainting another issue.
Fixes#24378
Incorporates and cross-reviews contributor PR #24379, including its source-resolver correction and regression material. Covers the contributor PR’s Project-row identity and retained-dialog mutation findings. The final published head passes focused tests, hidden macOS rendering and current CI; the callback-timing bot thread has an evidence-based response.
Co-authored-by: Katsuma Takehisa <k.takehisa@nissogr.com>
Adds a user-assignable shortcut for the existing child-workspace chip action. It stays unassigned by default on macOS, Linux and Windows. Final-source rendered checks cover Settings recording/reset, guards, scroll preservation and restart.
Fixes#24163
Continues mmarabel’s original contribution in this PR. Issue #24163 has no sibling implementation PR. Existing bot findings are fixed, withdrawn or addressed in the PR review.
Co-authored-by: mmarabel <166927047+mmarabel@users.noreply.github.com>
* ci: run cross-version wire suites when agent sends or orchestration change
The selector skipped the cross-version wire job for #24901, which changed the
shared agent-send path, the structured send envelope builders, and orchestration
RPC code. Route the send payload/fingerprint builders, src/main/runtime/orchestration/,
and the orchestration RPC methods to the job, and pin each rule in a test.
* ci: run cross-version wire suites only for code they execute
The agent-session suites now build their send through the shared outbox
builder and check it against the release host's schema and fingerprint,
so the send builder and outbox are selected exactly. Load-only
orchestration rules are dropped; the dispatcher-path files every suite
request runs through are selected instead.
* ci: run the release's own send admission, cover queued sends and the RPC reply builder
* test(cross-version): a wrong send fingerprint must be refused, so an agreed one is not vacuous
* Use bounded OpenCode context for vault session continuation
OpenCode database and synthetic row paths are not text transcripts. Use the
vault preview or captured pane context, preserving actual transcript paths
containing a hash and supporting both OpenCode lanes and Windows paths.
Adapted the intent of #11859 and extended it to actual installed v2 vault rows.
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
* Read real OpenCode sessions in terminal-backed native Chat
Reuse the bounded AI Vault SQLite worker for v1 and v2 session pages and live updates. Keep terminal input as the real execution path and pace OpenCode Stop through its two-Escape interrupt.
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
* fix(opencode): publish approval cards for permission requests
* Send OpenCode native approval through its Enter selector
* Resolve mobile Chat readability for folder workspaces
* Bound OpenCode part batches and preserve v2 image attachments
* Prefer live migrated OpenCode sessions over legacy copies
* Consolidate mobile Chat eligibility test imports
* Consolidate OpenCode SQLite protocol type imports
* fix(native-chat): preserve OpenCode reasoning and patch parts
Separate genuine reasoning from answer blocks in both native SQLite schemas and retain recorded patches as completed patch tools. Keep each database row together at page boundaries so the existing raw-row cursors cannot drop half of a mixed row.
Adapted from @akhan157's OpenCode native history work in #13287 at bb661d10d716764fb472d824cd434678875b1947; retains the current bounded reader and account discovery instead of restoring the older capture and cursor implementation.
Verified against genuine private installed 2.0.16 and official 1.18.30 CLI ingestion.
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix(native-chat): keep split OpenCode rows intact on desktop and mobile
Preserve the native reader's bounded OpenCode row groups in paired reads and snapshot/replacement frames so a second presentation-count slice cannot drop reasoning while advancing the database cursor. Sort derived reasoning before its answer under the same provider timestamp while retaining journal order.
These two boundaries were reproduced with genuine installed 2.0.16 and official 1.18.30 sessions in a hidden desktop renderer and the current mobile view over an actual authenticated encrypted pairing.
Completes the semantic presentation from @akhan157's #13287 without importing its older clipping or cursor implementation.
Co-authored-by: Adnan Khan <adnank11427@gmail.com>
* fix(native-chat): keep reasoning and answers together in live windows
* Bound OpenCode transcript RPC pages and present omission notices
* Bound OpenCode transcript RPC pages and present omission notices
* Bound OpenCode transcript RPC pages and present omission notices
* Update native worker oversized-history notice contract
---------
Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: nwparker <nwparker@users.noreply.github.com>