Skip to content

fix(opencode): bind same-directory sessions to the pane that submitted the prompt - #22847

Closed
werlang wants to merge 12 commits into
stablyai:mainfrom
werlang:fix/opencode-shared-server-pane-attribution
Closed

werlang wants to merge 12 commits into
stablyai:mainfrom
werlang:fix/opencode-shared-server-pane-attribution

Conversation

@werlang

@werlang werlang commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

ELI5

When two OpenCode agents run in panes that sit in the same folder, Orca could not tell which pane each agent belonged to. It gave up and blamed the first pane that had started OpenCode's shared background server. That pane showed a spinner while the other pane was doing the work, and the pane that actually owned the work showed grey.

This PR gives Orca two more pieces of evidence — which pane's OpenCode had just booted, and which pane you last typed into — and uses them, in that order, to work out the real owner.

What Changed

Before: correlateOpenCodeSessionOwners would only assign a session to a pane when exactly one pane in the session's directory had a live OpenCode client that could have created it. Two panes sharing a directory both qualify (split panes share a worktree; sibling folder workspaces share a folder by design), so neither won, the session stayed unbound, and every status post for it kept the frozen pane stamp from whichever pane started the shared server. The starter pane spun for someone else's task while the real pane never lit up.

After: two signals are computed, and they must agree before anything is bound. Launch recency — the pane whose OpenCode client booted within 20s of the session row appearing; Orca launches agents with --prompt, so the pane that created the session never writes a keystroke of its own. Input recency — the PTY registry records prompt activity per pane and the binder carries it into each round, so the pane written to immediately before the row appeared is the candidate; a pane's two most recent stamps are kept, because the creator usually keeps typing after submitting. Each signal must yield an unambiguous in-window leader (input must fall inside a 60-second window ending at creation and must not postdate it), and when the two name different panes the round abstains rather than choosing a side — a pane that just booted and a pane that was just written to explain the row equally well, and nothing separates a launched creator from an innocent pane opened nearby. Anything weaker falls through to today's unbound behavior: a wrong owner reproduces the reported bug rather than softening it.

A third signal covers prompts Orca itself dispatches: writeTerminalAgentPrompt records the prompt against the pane, since a prompt sent to a running agent is not a keystroke and the renderer path never sees it. The three are complementary — launch recency covers the --prompt launch, host stamping covers dispatches to a running agent, input recency covers typing — and none substitutes for another.

Mechanism touched: notePtyInput → BinderPaneSnapshot.lastInputAtMs, plus client start times the binder already collected → tieBreakByFreshLaunch → tieBreakByRecentInput. The existing argv, parent-lineage, and single-pane paths are unchanged and still take precedence.

Why

The status pipeline is only as good as its attribution: an OpenCode post carries no pane identity of its own (upstream sessions record a directory and a parentID, nothing else), so Orca infers the pane after the fact. When that inference gives up, the frozen stamp is wrong for every post of that session's life — which is exactly the reported pair of symptoms, one pane spinning for another's task and the real pane grey.

This extends rather than replaces #21577 (issue #21359), which introduced the registry, correlator and binder round this PR modifies. #21359 reported the identical symptom and its reporter called out the same root cause — "both panes here share one project directory, so even the directory cannot tell them apart" — but #21577 only resolves a tie when exactly one pane qualifies, and deliberately declines to guess otherwise. That limitation is asserted by its own test, stays unbound when both same-directory panes evidence a client, which expects []. This PR supplies evidence that breaks that specific tie without widening it into a guess.

Alternatives considered:

  • Rank the candidates (nearest client start, most recently created session). Rejected. Two clients that have both been open for days are indistinguishable from the process table, so ranking them would confidently attribute work to the wrong pane — the same bug, minus the chance of noticing it. The launch signal used instead is not a ranking: it matches only a client that booted within 20s of the row, which is an actual launch rather than a closeness contest, and it exists specifically because --prompt launches leave the creating pane no keystroke to find.
  • Launch opencode --standalone so every pane gets a private server and the stamp is correct by construction. Not free: --standalone opts out of the background service, and that single shared server is the v2 change over v1 — per [Bug]: opencode v2 shared background server freezes pane identity — every pane's status lands on whichever pane started the server #21359, "That worked when every pane ran its own opencode process. But opencode v2 runs a single shared background server for all panes." Reverting process topology to make a status dot truthful is the wrong side of the trade for the stable path. Orca already does exactly this for the opencode2 binary — tui-agent-config.ts passes --standalone under the comment "The private server inherits this pane's hook endpoint and identity" — so it is an existing workaround for this same defect, worth reviewing on its own merits rather than extending silently here. Tracked in [Tech debt]: opencode and opencode2 run the same OpenCode build in two different server topologies, split only for pane identity #22855.
  • Do nothing until the plugin stops needing the stamp. The binder already exists; extending its evidence is the smallest change that reaches the reported case.

Known gaps: only the desktop renderer and Orca's own prompt dispatch stamp prompt activity — mobile/web and CLI terminal send input reaches the PTY through RuntimeTerminalWriter without stamping, so those clients still produce sessions with no input evidence. A launch that takes over 20s to mint its session misses the launch window and falls through to input, where a sibling pane typed into during that minute could still win — narrowed from "every flag launch" to "very slow boots". And only well-formed terminal query replies are filtered from the input signal: full user-input provenance stops in the renderer (wasUserInput never reaches the transport), so an automatic write that is not a recognisable reply could still stamp a pane. Grey panes caused by the plugin failing to load are a separate defect (#22764, fix in flight as #22768).

Related issues and PRs

Lineage — this PR builds on

Filed alongside

Adjacent — deliberately NOT addressed here, different cause

Linked Issue

Fixes #22838

Residual case of #21359, closed as fixed by #21577 — this PR changes the correlator that PR added.

Visual Proof

N/A — no UI or interaction change. This alters which pane receives an already-existing status event; the icons and their states are unchanged. The behavior is observable only as correct attribution in a multi-pane OpenCode layout, and is covered by unit tests rather than screenshots.

Testing

  • pnpm tc — clean.
  • pnpm run check:code-quality:changed — 0 findings across all 10 changed files.
  • Targeted suites: agent-hook-listener*, src/main/opencode, src/main/agent-hooks, src/main/memory, src/main/ipc — pass.
  • Full pnpm test: 9332 files passed, 7 files failed with 20 tests failing. I checked out 7a4f080086 (the parent of this branch) and re-ran exactly those 7 files: the same 7 files and the same 20 tests fail there too, so none are caused by this change. They are:
    • src/main/rate-limits/codex-fetcher-pty-settle.test.ts (4) — derives a rate-limit session window from the current date, so it fails on a date-driven assertion; imports none of the touched modules.
    • src/main/browser/browser-manager-viewport-ownership.test.ts (12)
    • src/main/daemon/repro-13767-shell-ready-marker-lost-to-exec.test.ts (2)
    • config/scripts/skill-recipe-shell.test.mjs (1)
    • src/main/agent-hooks/wsl-hook-relay-live.integration.test.ts (1) — requires a WSL host.
    • tests/e2e/e2e-worker-env-isolation.unit.test.ts, tests/e2e/relay-region-correction.unit.test.ts — fail during file collection.

One failure this change did cause, caught and fixed before review: 55 test files mock memory/pty-registry through the shared factory in pty-ipc-mock-registry.ts, which did not know about the new notePtyInput export. Because writePtyInput wraps its body in try/catch, the missing function did not surface as an error — it silently degraded every write to return false, which showed up as 7 unrelated-looking src/main/ipc/pty-* failures. Adding the export to the shared factory cleared all of them.

New coverage:

  • agent-hook-listener-opencode-session-correlation.test.ts — tie broken by recency, in-window preference, only-typed-pane, exact tie stays unbound, stale input ignored, post-creation input ignored, and input not consulted when only one pane has client evidence.

  • opencode-session-binder.test.ts — input plumbed through a real round, and preserved across a pane remint whose live row has no history.

  • pty-registry.test.ts, write-input.test.ts — the new stamp, its unknown-id guard, teardown, and no stamp when no provider can serve the write. (A provider that refuses an already-dispatched write still stamps: the stamp records interaction with the pane, not byte delivery — see the declined finding in the review threads.)

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Cross-platform: the new signal is an epoch recorded on the renderer→main PTY write, which is the same path on macOS, Linux, and Windows. No path, shell, or shortcut handling changes.
  • SSH/remote: unchanged. The registry only holds local PTYs (its own documented scope), so remote panes keep today's behavior of staying unbound rather than being attributed on evidence Orca cannot observe.
  • Performance: one Map.get and one field assignment per keystroke, plus one integer comparison per ambiguous session per 60-second binder round.
  • Mobile: not affected; mobile PTYs are rejected earlier in the same write path.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…d the prompt

A shared OpenCode server freezes ORCA_PANE_KEY to whichever pane started it,
so stablyai#21577 rebinds each post to the session's real pane. The binder only
produced a binding when exactly one pane could have created the session, and
two panes sharing a directory — split panes in one worktree, or sibling
folder-workspace panes — always both qualify, so those sessions stayed
unbound forever. Every post for them kept the frozen stamp: the starter pane
spun for another pane's task while the pane that owned the task read grey.

Record the last interactive write against each pane's PTY and, when several
panes qualify, bind to the one whose PTY received input immediately before
the session row appeared. The window is 60s, the input must predate the row,
and the leader must be unambiguous; anything weaker still stays unbound,
because a wrong owner reproduces the reported bug rather than softening it.
Copilot AI lite review requested due to automatic review settings September 25, 2026 12:37

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Four unresolved moderate review findings affect input validity and session attribution.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 Medium severity

Open (1)
What changed in this PR

This PR improves OpenCode session-to-pane attribution when multiple panes share a directory.

Changes:

  • Tracks and propagates recent PTY input timestamps.
  • Uses input timing to resolve ambiguous session ownership.
  • Adds correlation, binder, registry, IPC, and mock coverage.

Four unresolved moderate findings remain regarding write validation, automatic query replies, pane remints, and multiple sessions created before one binder round.

File Summary
src/​shared/​agent-hook-listener/​opencode-session-correlation.ts Adds recent-input tie-breaking.
src/​shared/​agent-hook-listener-opencode-session-correlation.test.ts Tests correlation edge cases.
src/​main/​opencode/​opencode-session-binder.ts Carries input timestamps through binder rounds.
src/​main/​opencode/​opencode-session-binder.test.ts Tests timestamp propagation and remints.
src/​main/​memory/​pty-registry.ts Stores PTY input timestamps.
src/​main/​memory/​pty-registry.test.ts Tests timestamp lifecycle.
src/​main/​ipc/​pty/​ipc/​write-input.ts Records PTY input timestamps.
src/​main/​ipc/​pty/​ipc/​write-input.test.ts Tests stamping and refusal behavior.
src/​main/​ipc/​pty-ipc-mock-registry.ts Updates registry mocks.
src/​main/​agent-hooks/​server-opencode-binder.test.ts Updates binder fixtures.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/main/ipc/pty/ipc/write-input.ts Outdated
try {
const now = performance.now()
lastInputAtByPty.set(args.id, now)
notePtyInput(args.id, Date.now())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e9beb99. The stamp now lives inside writePtyProviderInputWithinLimit, which is only reached after isTerminalInputTooLargeWithDeferredMeasurement resolves false — so an oversized or never-dispatched payload no longer ages a pane. Both sites funnel through writePtyProviderInput, so both are covered by the one move.

Deliberately not gated on provider.write() returning false. The stamp records interaction with the pane, not byte delivery: a false there means the PTY is gone, and a torn-down PTY is dropped from listBinderPaneSnapshots outright so it cannot become a tie candidate at all — while a pane that just received a human prompt did interact with it, and not recording that loses a correct binding rather than avoiding a wrong one. Kept the timestamp where it is (pre-write) so it still provably precedes the session row, which tieBreakByRecentInput requires.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

The new tie-break can bind a same-directory session to a bystander pane when the pane that actually submitted the prompt left no PTY stamp — which is Orca's normal OpenCode launch path (--prompt on the spawn command). That contradicts the "remains unbound" known gap. Details inline.

Reviewed changes

  • Input-recency evidence — notePtyInput stamps every renderer→main interactive write on the PTY registry (write-input.ts:210,240), and lastInputAtMs is carried into binder pane snapshots.
  • Same-directory tie-break — tieBreakByRecentInput binds an ambiguous session to the unique pane whose last input falls in the 60s before creation; ties or absent evidence stay unbound.
  • Remint merge — the binder dedupe keeps the most recent input across duplicate pane rows so a re-registered PTY retains the replaced row's observation.
  • Test plumbing — the shared pty-registry mock factory gained notePtyInput, plus new coverage for correlation, binder plumbing, and the registry stamp.

ℹ️ Nitpicks

  • PtyRegistration.lastInputAtMs's comment (pty-registry.ts:29-35) says the stamp is "dropped when the PTY is re-registered under a new incarnation", but mostRecentInput (opencode-session-binder.ts:192-195) deliberately reintroduces a same-key row's stamp. Worth reconciling the comment with the intended behavior.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/shared/agent-hook-listener/opencode-session-correlation.ts Outdated
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

PTY input writes now record timestamps in the PTY registry, excluding recognized terminal query replies and writes that do not reach provider dispatch. Successful host-delivered prompts also record timestamps. The binder includes those timestamps in pane snapshots and preserves input history across deduplicated rows only when their directories match. When multiple panes qualify for an OpenCode session, correlation first uses qualifying client launch timing, then uses input within 60 seconds before session creation. Unresolved ties remain unbound.

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to 2fe58

A failed prompt submission can make another pane’s OpenCode session appear to belong to this pane. Move the activity stamp after successful submission before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 2fe58

Session attribution now depends on recent activity as well as process evidence. The decision is limited to eligible panes in the same directory, but activity that did not result in a completed write can still influence which pane owns a session.

Retained concerns

  • Low · architecture · inferred: An attempted or partially completed input can become lasting evidence for a session-owner decision. The renderer path stamps before its provider write, while the host prompt path does not retract its stamp if a later submit or verification fails. Where launch timing does not resolve two eligible panes, that evidence can select a pane whose activity did not create the session; later posts can then inherit the attribution.
Security review details

Security Blast Radius

  • inferred — The demonstrated effect is attribution among eligible local panes sharing a directory, followed by substitution of pane-related hook-post fields. The examined path does not establish cross-user, cross-service or credential access.

Trust Boundaries and Controls

  • observed — The IPC helper identifies events from the main window, and its write path checks the mobile driver and PTY provider before writing. The correlator additionally requires directory and client evidence before a timing signal can choose an owner.

Resilience and Maintainability Implications

  • observed — Recognized automatic terminal-query replies do not count as interactive input. A failed initial host prompt write does not stamp activity, although later failure does not retract a successful initial-write stamp.

Hardening Proposals

  • proposed — Consider making activity evidence conditional on write acceptance and defining when a failed, cancelled or superseded prompt ceases to count toward session ownership.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 25 functions across 13 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR meets the coding requirements in [#22838]. The correlator first uses a unique OpenCode client start in the 20-second pre-creation window, with its stated timestamp skew bound. If launch evidenc…
Out of Scope Changes check ✅ Passed The changes stay within [#22838]. PTY activity tracking, prompt activity tracking, binder history, directory matching, correlation logic, mocks, and tests directly support same-directory session owner…
Title check ✅ Passed The title clearly identifies the primary change: binding same-directory OpenCode sessions to the pane that submitted the prompt.
Description check ✅ Passed The description covers the required sections, linked issue, behavior change, rationale, testing, visual proof, scope, and compatibility considerations. It also documents known baseline test failures a…

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 36d52c4c-fca6-4b6e-9977-fc462f95c6c4

📥 Commits

Reviewing files that changed from the base of the PR and between c9d421f and 930548d.

📒 Files selected for processing (10)
  • src/main/agent-hooks/server-opencode-binder.test.ts
  • src/main/ipc/pty-ipc-mock-registry.ts
  • src/main/ipc/pty/ipc/write-input.test.ts
  • src/main/ipc/pty/ipc/write-input.ts
  • src/main/memory/pty-registry.test.ts
  • src/main/memory/pty-registry.ts
  • src/main/opencode/opencode-session-binder.test.ts
  • src/main/opencode/opencode-session-binder.ts
  • src/shared/agent-hook-listener-opencode-session-correlation.test.ts
  • src/shared/agent-hook-listener/opencode-session-correlation.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment thread src/main/ipc/pty/ipc/write-input.ts Outdated
Comment on lines 207 to 213
try {
const now = performance.now()
lastInputAtByPty.set(args.id, now)
notePtyInput(args.id, Date.now())
interactiveOutputCharsByPty.set(args.id, 0)
if (visibleRendererPtys.has(args.id)) {
clearHiddenRendererResizeOutput(args.id)

@coderabbitai coderabbitai Bot Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

rg -n 'writePtyProviderInput' src/main/ipc/pty
rg -n 'function writePtyProviderInput|const writePtyProviderInput|writePtyProviderInput\s*=' src/main

Repository: stablyai/orca

Length of output: 1264


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- write-input source ---'
sed -n '1,270p' src/main/ipc/pty/ipc/write-input.ts
printf '%s\n' '--- provider declarations and implementations ---'
rg -n -S --glob '*.ts' --glob '*.tsx' 'writePtyProviderInput|PtyProvider|provider\.write|writeInput|input\(.*data' src/main

Repository: stablyai/orca

Length of output: 41533


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- IPtyProvider contract ---'
sed -n '1,180p' src/main/providers/types.ts
printf '%s\n' '--- production write implementations ---'
rg -n -S --glob '*.ts' 'implements IPtyProvider|write\s*(=|:|\()|writeWithSettlement' src/main/providers src/main/daemon src/main/ipc/pty
printf '%s\n' '--- implementation slices ---'
for f in \
  src/main/providers/local-pty-provider.ts \
  src/main/providers/ssh-pty-provider.ts \
  src/main/daemon/daemon-pty-router.ts \
  src/main/daemon/degraded-daemon-pty-provider.ts; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    rg -n -A18 -B5 'write\s*(=|:|\()|writeWithSettlement' "$f"
  fi
done

Repository: stablyai/orca

Length of output: 41344


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- local write implementation ---'
rg -n -A28 -B8 'function writeLocalPty|const writeLocalPty|writeLocalPty\s*=' src/main/providers src/main
printf '%s\n' '--- SSH write implementation ---'
sed -n '1,140p' src/main/providers/ssh-pty-provider-rpc-operations.ts
rg -n -A35 -B8 'function writeToSshPty|const writeToSshPty|writeToSshPty\s*=' src/main
printf '%s\n' '--- provider contract write lines ---'
sed -n '136,158p' src/main/providers/pty-provider-contract.ts

Repository: stablyai/orca

Length of output: 16402


Record PTY activity only after the provider accepts the write.

Both handlers call notePtyInput before writePtyProviderInput. A local provider can return false when its PTY no longer exists, and an SSH provider can return false when its multiplexer is disposed. The helper ignores these return values and reports success. Oversized input also returns false after activity was recorded. This can create false PTY activity.

Propagate provider refusal and record activity only when the helper returns true.

Suggested fix
     if (first.done) {
-      provider.write(id, data)
-      return true
+      return provider.write(id, data) !== false
     }
     const second = chunks.next()
     if (second.done) {
-      provider.write(id, first.value)
-      return true
+      return provider.write(id, first.value) !== false
     }
@@
-        provider.write(id, chunk.value)
+        if (provider.write(id, chunk.value) === false) {
+          return false
+        }
@@
       return true
     } catch (error) {
       reportUnavailablePtyWrite(id, error)
       return false
     }
   }
+
+  const noteAcceptedPtyInput = (
+    id: string,
+    result: boolean | Promise<boolean>
+  ): boolean | Promise<boolean> => {
+    if (typeof result === 'boolean') {
+      if (result) notePtyInput(id, Date.now())
+      return result
+    }
+    return result.then((accepted) => {
+      if (accepted) notePtyInput(id, Date.now())
+      return accepted
+    })
+  }
@@
-      notePtyInput(args.id, Date.now())
       interactiveOutputCharsByPty.set(args.id, 0)
@@
-      return writePtyProviderInput(provider, args.id, args.data, admitted)
+      return noteAcceptedPtyInput(
+        args.id,
+        writePtyProviderInput(provider, args.id, args.data, admitted)
+      )
@@
-      notePtyInput(args.id, Date.now())
       interactiveOutputCharsByPty.set(args.id, 0)
@@
-      return writePtyProviderInput(provider, args.id, args.data, admitted)
+      return noteAcceptedPtyInput(
+        args.id,
+        writePtyProviderInput(provider, args.id, args.data, admitted)
+      )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e9beb99 for the case where nothing is attempted: the stamp moved into writePtyProviderInputWithinLimit, after the oversized-input check, so a rejected payload no longer counts as activity.

I did not adopt propagating provider.write() === false out of the chunk helpers. Those branches already return true unconditionally today, so changing them alters what writePtyInput reports to its IPC callers — a behaviour change in the write path that reaches well beyond this stamp, and it is not obviously safe to bundle here. As above, the stamp is intentionally a record of interaction with the pane rather than of confirmed byte delivery; a dead PTY exits the candidate set anyway when its registration is removed. Happy to take the propagation as its own change if you think the current contract is wrong.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
ast-grep outline src/main/memory/pty-registry.ts --items all --match 'notePtyInput|unregister|register'
ast-grep outline src/main/ipc/pty/ipc/write-input.ts --items all --match 'writePtyProviderInput|stampPtyInput'
ast-grep outline src/shared/agent-hook-listener/opencode-session-correlation.ts --items all --match 'correlate|candidate|tie'
rg -n -C3 'writeLocalPty\s*=|function writeLocalPty|writeToSshPty\s*=|function writeToSshPty|write\s*\(id:\s*string,\s*data:\s*string\)' src/main/providers
rg -n -C4 'notePtyInput|lastInputAtMs|unregisterPty|listRegisteredPtys' src/main/memory/pty-registry.ts src/main/opencode/opencode-session-binder.ts src/shared/agent-hook-listener/opencode-session-correlation.ts

Length of output: 13782


🏁 Script executed:

#!/bin/bash
set -e
ast-grep outline src/main/providers/local-pty-session-operations.ts --match writeLocalPty
ast-grep outline src/main/providers/ssh-pty-write.ts --match writeToSshPty
sed -n '20,70p' src/main/providers/local-pty-session-operations.ts
sed -n '20,48p' src/main/providers/ssh-pty-write.ts
sed -n '92,178p' src/main/ipc/pty/ipc/write-input.ts
sed -n '228,273p' src/shared/agent-hook-listener/opencode-session-correlation.ts

Length of output: 7750


@werlang, the provider code confirms your point. provider.write() can return false, but writePtyInput already reports success for that path. The updated stamp occurs after the oversized-input check and records a write attempt, not confirmed delivery. Changing the return behavior is outside this finding, so I’m withdrawing it.

You are interacting with an AI system.

Comment thread src/main/ipc/pty/ipc/write-input.ts Outdated
Comment thread src/main/opencode/opencode-session-binder.ts
Comment thread src/shared/agent-hook-listener/opencode-session-correlation.ts Outdated
…s on opencode2 running standalone

resolveOpenCodeSharedServerEnvelope rewrites only opencode and mimo-code.
opencode2 is correctly absent because Orca launches it with --standalone — a
private per-pane server whose env already names the right pane. Nothing said
that anywhere, so a reader could either "fix" the omission and rewrite stamps
that were never wrong, or drop the flag and leave opencode2 frozen onto the
server's starter pane with no rebinding.
…pane

Review of stablyai#22847 found four ways the new evidence could bind a session to the
wrong pane or claim more than it knew:

- Orca launches OpenCode with `--prompt`, so the creating pane writes no
  keystroke at all and input alone handed the session to whichever sibling
  pane had recently been typed into. Launch recency is now consulted first: a
  client that booted within 20s of the row appearing explains the session, a
  bystander's keystroke does not.
- lastInputAtMs holds only the newest write, so typing again after creation
  erased the pre-creation evidence and let a bystander become the unique
  in-window leader. A post-creation stamp now abstains rather than skips.
- A pane that moved directory inherited input recorded under its old root, so
  a session in the new one could win a tie on keystrokes from elsewhere.
- The stamp was taken before dispatch, so an oversized or never-dispatched
  payload aged a pane, and xterm's own CPR/DA1 replies counted as input. The
  stamp now sits at the dispatch point and skips terminal query replies.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 0834fb5c-78e0-4981-8b9a-200b21629181

📥 Commits

Reviewing files that changed from the base of the PR and between 930548d and e9beb99.

📒 Files selected for processing (7)
  • src/main/ipc/pty/ipc/write-input.test.ts
  • src/main/ipc/pty/ipc/write-input.ts
  • src/main/opencode/opencode-session-binder.test.ts
  • src/main/opencode/opencode-session-binder.ts
  • src/shared/agent-hook-listener-opencode-session-correlation.test.ts
  • src/shared/agent-hook-listener/opencode-session-correlation.ts
  • src/shared/agent-hook-listener/opencode-session-registry.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

Comment thread src/main/opencode/opencode-session-binder.ts Outdated

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — minor suggestions inline.

Reviewed changes

  • Launch-recency tie-break — added tieBreakByFreshLaunch and consult it before input, so an Orca --prompt launch (whose creating pane writes no keystroke) is identified by the client that booted within 20s of the session row.
  • Input abstain — tieBreakByRecentInput now returns null instead of skipping a pane whose last input postdates creation, since only the newest write is retained and skipping would hand the session to a bystander.
  • Stamp placement + reply filter — moved notePtyInput to the provider-dispatch point (after the oversized check) and skip isTerminalQueryReply(data), so oversized payloads and xterm's synthetic replies no longer count as input.
  • Directory-change guard — the binder's same-key carry-forward now merges a replaced row's stamp only when the directory is unchanged.
  • Tests — new coverage for the launch tie-break, the abstain path, the query-reply filter, and the directory guard; the 4 changed suites (45 tests) pass.
Technical details
# Fresh-launch tie-break discards a pane whose earlier client is a valid launch

## Affected sites
- `src/shared/agent-hook-listener/opencode-session-correlation.ts:199` — `newest` takes the max over the pane's clients that pass `clientCouldCreate`, which permits starts up to `createdAtMs + OPENCODE_CREATE_SKEW_MS` (2 min).
- `src/shared/agent-hook-listener/opencode-session-correlation.ts:208` — the pane is skipped when that max postdates `createdAtMs + 2_000`, so a later client hides an earlier in-window one.
- `src/main/opencode/opencode-session-binder.ts:150-161` — `toCorrelatedClients` does not dedupe by pane, so one pane can carry multiple client rows (e.g. an `opencode run` spawned inside the pane, which `isOpenCodeClientProcess` classifies as a client).

## Required outcome
- A pane with a client that booted within `OPENCODE_LAUNCH_TIEBREAK_WINDOW_MS` before creation should still qualify even when it also has a client started after the row.

## Suggested approach (optional)
- Apply the `client.startedAtMs > createdAtMs + 2_000` rejection inside the inner `for (const client of clients)` loop so `newest` only considers clients that could be the launch. A pane with only post-creation clients still ends at `NEGATIVE_INFINITY` and is skipped as today.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/shared/agent-hook-listener/opencode-session-correlation.ts Outdated
A prompt Orca dispatches to a running agent is written host-side through
writeTerminalAgentPrompt, never as a keystroke, so the pane that submitted it
recorded no prompt activity at all. In a shared directory that reproduces the
wrong-pane outcome review found for flag launches: the creator silent while a
sibling pane's typing wins the tie as unique in-window leader.

Stamp on the successful write, and broaden lastInputAtMs from "interactive
write" to "prompt activity" across the registry, binder and correlator docs —
the field now covers both a renderer keystroke and a prompt Orca delivered.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Host-side prompt stamping — writeTerminalAgentPrompt now calls notePtyInput(ptyId, Date.now()) after a successful PTY write, so a prompt Orca dispatches to a running agent registers as prompt activity instead of leaving the owning pane silent for the OpenCode tie-break.
  • Broader "prompt activity" terminology — the lastInputAtMs docs in pty-registry.ts, opencode-session-binder.ts, and the correlator JSDoc now say "prompt activity" (renderer keystroke or host-delivered prompt) rather than "interactive write".
  • Test coverage — records an Orca-delivered prompt as prompt activity on the pane registers a real pane and asserts the stamp lands after delivery; it fails if the stamp is removed.

Verified the five suites this PR touches (agent-prompt-submission-runtime, agent-hook-listener-opencode-session-correlation, opencode-session-binder, pty-registry, write-input) — 69 tests pass.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

… a respelled directory

Review of the tie-break fixes found two ways it could still misbind:

- tieBreakByFreshLaunch took the newest of a pane's qualifying clients, but
  clientCouldCreate admits starts up to CREATE_SKEW after the row. A client
  spawned inside the pane (an `opencode run`) therefore became the newest,
  tripped the post-creation rejection and dropped the whole pane — masking an
  earlier client that really was the launch, and letting a bystander win on
  input. The rejection now applies per client, so only post-creation clients
  are excluded from the max.
- The input-history merge compared directories as raw strings while the
  correlator normalizes them, so a remint that only respelled the root
  (/tmp/x vs /private/tmp/x) discarded valid input and handed the session to a
  pane with older activity — the opposite of the guard's intent. The binder
  now compares normalized directories via openCodeDirectoryMatches.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review at f8d25af: the fresh-launch masking fix, the normalized directory carry-forward, and their tests.

  • Fixed fresh-launch masking — tieBreakByFreshLaunch now applies the post-creation (createdAtMs + 2_000) rejection per client inside the loop, so an earlier pre-creation client on the same pane is no longer masked by a later one (e.g. a nested opencode run).
  • Normalized remint directory comparison — added openCodeDirectoryMatches (normalized equality: macOS /private alias, Windows case/backslash folding, dot segments) and used it for the binder's same-key carry-forward, so a remint that only respelled the root keeps its input history.
  • Tests — binder coverage for an alias-only remint and correlation coverage for the later-client case; the two changed suites (41 tests) pass.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Resolves conflicts against d443320/8fa5883217, which removed the
agent-session write gate from write-input.ts and dropped
clearHiddenRendererResizeOutput from its deps.

Resolution: took main's removals wholesale and re-laid the two additions this
branch depends on — the notePtyInput/isTerminalQueryReply imports and the
stampPtyInput call inside writePtyProviderInputWithinLimit (now reachable
directly, the gate and its admitted parameter being gone).
write-input.test.ts updated with them: it passed the removed dep and spied on
a gate this module no longer uses, so its refusal case now asserts the
equivalent path — no provider, no dispatch, no stamp.

--no-verify: the pre-commit hook runs oxfmt over every staged file, and a
merge stages main's own ~1200 files; formatting upstream's code inside this
merge commit would bury the resolution.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review at 465e297: a merge of main (01ed4b92) that resolved the only conflicts in this PR, in write-input.ts and write-input.test.ts.

  • Merged main into the branch — main's d443320af2 removed the agent-session write gate and the clearHiddenRendererResizeOutput dep from createPtyWriteInput. The resolution took those removals wholesale and re-laid this branch's additions — the notePtyInput/isTerminalQueryReply imports and the stampPtyInput call inside writePtyProviderInputWithinLimit, now reached directly since the gate and its admitted parameter are gone.
  • Refusal test re-pointed — the removed gate spy is replaced by a no-provider case (UNBACKED_PTY_ID) asserting the write returns false with no dispatch and no stamp, so the "only stamp once bytes are on their way" invariant still has a failing guard.
  • Verified — git diff 01ed4b92 2fe581f is exactly this PR's 716/12 across 13 files, with no reverted main changes or leftovers; the five touched suites (71 tests) pass and check:code-quality:changed reports 0 new findings.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 6457107e-6bae-434f-a864-a330c27da979

📥 Commits

Reviewing files that changed from the base of the PR and between 465e297 and 2fe581f.

📒 Files selected for processing (3)
  • src/main/ipc/pty/ipc/write-input.test.ts
  • src/main/ipc/pty/ipc/write-input.ts
  • src/main/runtime/orca-runtime-write-terminal-agent-prompt.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment thread src/main/runtime/orca-runtime-write-terminal-agent-prompt.ts Outdated
The host-side stamp fired after the paste write, but on the separate-submit
path nothing is submitted until the Enter that follows — and beforeWrite
failures, cancellation, and a permission state appearing all land between the
two. A pane whose prompt was pasted and then abandoned still claimed prompt
activity, so a session created elsewhere in the window could bind to a pane
that never submitted anything.

Stamp after the joined paste+submit frame when the prompt rides on one write,
and after the Enter write succeeds on the two-write path. Covered by a test
mirroring the existing "does not send Enter after a permission state appears"
scenario: the paste reaches the PTY, the Enter does not, and the pane must
stay unstamped.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the single commit since the prior pullfrog review at 2fe581f: the host-side prompt-activity stamp now lands only once the prompt is actually submitted.

  • Stamped after submission, not after the paste — writeTerminalAgentPrompt no longer stamps on the paste write; it stamps after the joined paste+submit frame, or after the separate Enter write succeeds. A prompt pasted and then abandoned (permission state, beforeWrite failure, cancellation) no longer credits a pane that never submitted anything.
  • Regression test — new case where the paste reaches the PTY but Enter never does (agent_prompt_blocked) asserts no \r write and no lastInputAtMs; confirmed it fails with the prior unconditional stamp restored (expected 1790369407027 to be undefined).

Verified the five touched suites (72 tests) pass and check:code-quality:changed reports 0 findings across all 13 changed files.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

…r submitted prompts

Two review findings on the tie-break:

- Launch recency was absolute: tieBreakByFreshLaunch never read lastInputAtMs,
  so a pane whose client merely booted inside the window beat a pane that had
  just been written to. Reachable whenever someone opens a same-directory pane
  and then submits a prompt in an established one — a wrong-pane bind, which is
  the symptom this work exists to remove. The two signals are symmetric and no
  evidence separates a launched creator from an innocent pane opened nearby, so
  disagreement now abstains rather than picking a side.

- The global veto added for a post-creation stamp fired whenever any pane was
  touched after the row, which is ordinary traffic, and left the session
  permanently unbound — the binder drops unbound sessions after ten minutes.
  The registry now keeps the stamp it replaces, so a pane that submitted and
  then kept typing still shows its submission. Abstention narrows to the case
  where both slots postdate the row and an earlier submission may genuinely
  have been evicted.

Also corrects the pty-registry module doc, which still claimed the write side
was two calls and that the PTY subsystem had no other reason to know about it.
@werlang

werlang commented Sep 26, 2026

Copy link
Copy Markdown
Contributor Author

Self-review round — follow-up is in 2831db153f.

I ran a two-axis review of this branch (standards + spec, independent agents) and it found two real defects in my own tie-break. Both are fixed, each with a test verified to fail without the fix.

1. Launch recency was absolute. tieBreakByFreshLaunch(...) ?? tieBreakByRecentInput(...) — and the launch probe never read lastInputAtMs. So a pane that had merely booted inside the window beat a pane that had just been written to. Reachable whenever someone opens a same-directory pane and then submits a prompt in an established one: the new pane takes the session. That is the wrong-pane symptom this work exists to remove, so I treated it as a blocker.

The two signals are symmetric — no evidence separates a launched creator from an innocent pane opened nearby — so disagreement now abstains rather than choosing a side. Practical effect: if a fresh client and a recent write name different panes, the session stays unbound. The launch signal now only decides when uncontradicted; input still decides the reported case (two established panes, prompt typed) exactly as before. I'd rather shrink the fix than ship a new way to light the wrong pane.

2. The veto added for a post-creation stamp was too broad. It fired whenever any candidate pane held a stamp after the row — ordinary typing — and because stamps only move forward it never cleared, while the binder drops unbound sessions after ten minutes. Those sessions lost attribution permanently. The registry now keeps the stamp it replaces, so a pane that submitted and then kept typing still shows its submission, and abstention narrows to the case where both stored slots postdate the row and an eviction is genuinely possible.

Verification: reverting the three source files while keeping the tests fails exactly the six tests that pin this behaviour — and nothing else.

Also corrected from the same review: the Testing claim about the refusal path (the test covers a missing provider; a refused already-dispatched write still stamps, which is the finding I declined in the threads), and a Known gap that was missing entirely — mobile/web and CLI terminal send input never stamps, so those clients still produce sessions with no input evidence.

Deliberately not addressed here: that mobile/CLI gap wants its own change at the RuntimeTerminalWriter chokepoint rather than a third stamping call site; stampPtyInput sits inside a catch that would swallow an unexpected error silently; and two nits — no pinning test for the oversized-payload case, and basis not distinguishing launch evidence from input evidence.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — one consistency gap inline.

Reviewed changes

Reviewed the delta since the prior pullfrog review at f7ae08d: the two-slot input history and the launch/input agreement requirement.

  • Two-slot input history — notePtyInput keeps the stamp it replaces in previousInputAtMs, so a pane that submitted a prompt and then kept typing still exposes its pre-creation submission.
  • Eviction-aware input tie-break — tieBreakByRecentInput consults both stamps, recovers a pre-creation submission from the older slot, skips a pane whose single stamp postdates creation, and abstains when both of a pane's stamps postdate it.
  • Signal agreement — correlateOpenCodeSessionOwners now computes a launch owner and an input owner and binds only when they agree (or one is absent), replacing the launch-first ?? fallback.
  • Remint carry — the binder's newestTwoStamps carries the two newest stamps across a same-directory remint, replacing mostRecentInput.
  • Tests — new coverage for the two-slot stamp, the remint carry of the replaced stamp, the recover/skip/abstain input paths, and the launch-vs-input disagreement; the five touched suites (77 tests) pass.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread src/shared/agent-hook-listener/opencode-session-correlation.ts Outdated
tieBreakByRecentInput returned a bare null for two different meanings — "no
in-window input leader" and "a candidate's history was evicted, so nobody may
claim this session" — and the caller read both as agreement before letting the
launch signal name a pane. A pane whose stamps were all evicted could therefore
be bypassed by another pane's fresh client, taking a session the abstain says
must stay unclaimed.

Both probes now return an explicit outcome (owner | none | abstain), and the
round stops on abstain before either signal is consulted.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review at 2831db1: the eviction abstain now has its own outcome, so it blocks a launch from claiming a session it cannot be ruled out of.

  • Discriminated tie-break outcome — both probes now return { kind: 'owner' } | { kind: 'none' } | { kind: 'abstain' } instead of overloading null, and correlateOpenCodeSessionOwners short-circuits on abstain before consulting either signal.
  • Regression test — does not credit a launch when another pane history was evicted (pane-a's fresh client, pane-b's two post-creation stamps) asserts the round stays unbound; it fails with the fix reverted.

The blanket abstain is deliberate and consistent with the "a wrong owner is the bug" contract: even when the launch owner is itself the evicted pane, a fresh client there could equally be an innocent pane opened nearby, so no pane is credited. The changed suite passes (32 tests).

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[Medium risk] Adds prompt-activity tracking to bind OpenCode sessions to the correct pane.

The PR does not appear safe to merge while either same-directory tie-breaker can assign a session to the wrong pane.

Findings

  1. P1 Unrelated typing can claim sessions ▶
  2. P1 Newer launch can claim session ▶

Summary

The PR adds PTY input and prompt-dispatch timestamps to help attribute same-directory OpenCode sessions, then uses recent launches and input to break otherwise ambiguous pane ties.

  • Existing argv, parent-lineage, and single-pane attribution paths remain.
  • The two previously reported wrong-pane attribution cases remain outstanding.

Reviews (2) · Last reviewed commit: "Merge remote-tracking branch 'upstream/m..."

Comment on lines +298 to +304
const activity = Math.max(...preCreation)
if (createdAtMs - activity > OPENCODE_INPUT_TIEBREAK_WINDOW_MS) {
continue
}
if (activity > bestAt) {
best = pane
bestAt = activity

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Unrelated typing can claim sessions When two panes in the same directory have long-running OpenCode clients, a prompt submitted in pane A can be followed by an unrelated keystroke in pane B before the session row appears. Renderer keystrokes are stamped without identifying prompt submissions, so this tie-break picks B’s newer stamp. With no fresh launch to contradict it, A’s session is bound to B and status appears on the wrong pane.

Comment on lines +247 to +255
if (newest > bestStart) {
best = pane
bestStart = newest
tied = false
} else if (newest === bestStart) {
tied = true
}
}
return best && !tied ? { kind: 'owner', paneKey: best.paneKey } : NO_OWNER

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Newer launch can claim session If pane A starts OpenCode with --prompt and a same-directory sibling opens OpenCode a few seconds later, both starts can fall within the 20-second window. This code picks the newer start rather than treating the two launches as ambiguous. Because the --prompt launch leaves no input stamp, the session can be bound to the innocent sibling, putting status on the wrong pane.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review at d970337: a merge of main into the branch and the follow-up adaptation commit that follows it.

  • Merged main into the branch — brought main's TerminalInputKind / PtyWritePayload.inputKind and the required BinderPaneSnapshot input fields. The resolution preserved the branch's notePtyInput calls and stampPtyInput untouched: write-input.ts still filters isTerminalQueryReply, and orca-runtime-write-terminal-agent-prompt.ts still stamps only on the joined paste+submit frame or after the separate submit Enter.
  • Adapted call sites and fixtures — edbbfd8 (28 lines across 3 test files) threads inputKind: 'driving' through the affected writePtyInput/sendTerminalAgentPrompt calls and fills the two new pane-snapshot fields with null, matching main's required shapes.

Verified: pnpm tc clean, the six touched suites (92 tests) pass, no conflict markers remain under src/, and check:code-quality:changed reports 0 findings across all 13 changed files.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@werlang

werlang commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

@oliver-mee — ready for review and merge. CI green on the current head (pullfrog ✅, Greptile ✅), which has latest main merged in (through #24244/#24222/#24074).

State of the work:

Verified after the latest merge: 92/92 tests across the touched suites, pnpm tc clean. MERGEABLE — just needs an approve + merge.

@werlang

werlang commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Same maintainer step needed here as on #24283: the native CI workflows are at action_required on this head (fork PRs need run approval before CI executes) — https://github.com/stablyai/orca/pull/22847/checks. That's also why the PR shows UNSTABLE despite pullfrog/Greptile green. Approve the runs → approve the PR → merge; my account is read-only so all three steps need a maintainer.

nwparker added a commit that referenced this pull request Oct 3, 2026
…24962)

* fix(opencode): bind legacy attach status to its TUI pane

Adapt the official v1 TUI API to the existing pane reporter and learn structural session identity only after execution-host admission. Preserve known creators, gate new reports to capable hosts, and register the legacy TUI entry without changing user settings or overlay targets.

Credit werlang for the same-directory investigation in #22838 and #22847; no source from that PR is copied.

(cherry picked from commit d9eeb028a23a66b78b08538a5ebf6052499ec24f)

* fix(opencode): fence legacy TUI viewers before creator attribution

Use the execution host's existing admission policy on the physical TUI
pane and launch token before rewriting to a session creator or mutating
the launch-token cache. Preserve live viewers, frozen server stamps and
OpenCode 2 behavior.

Add HTTP regressions for retired panes, closed tabs, replaced tokens and
relay retirement, plus current-launch, alias and compatibility controls.

Follow-up to #22838 and @werlang's original #22847; preserves the original
intentional creator attribution without copying that PR's source.

* fix(opencode): preserve current plugin exports in legacy TUI wrapper

Keep the current server, setup, id and other default export entries when
adding the legacy TUI entry. Cover their preservation and seed the ACL
fixtures with the separate installed TUI/config bytes.

This completes the current-main port of the legacy identity replacement
for issue #22838, carrying @werlang's original PR #22847 contribution.

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5029f9217f64970271a5b7f69d3f823

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddbc08fe1179f8bd964fff3dcb0e5d020
Original-topic-commit: 588117b5cf1dd57e2310c80068e3ddcc9f9044ec
Original-topic-commit: dedd4f8c862a961393977dc3c2547f049391b509
Original-topic-commit: 6645dae104ee5271139fcc0e9564d9223b74835d
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021c7ef1436ca606559e9d786580a84dc

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237db191c1e6280001c7e0563f3ca8846


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

* Skip store notifications when refreshed work items are unchanged (#24530)

An identical forced refresh previously returned an empty Zustand patch, creating a new root state and notifying every subscriber. It now returns the current state after renewing the existing freshness timestamp. Real row or metadata changes still publish once.

* Read project activity once while sorting the sidebar (#24549)

Reuse the existing pure recent rank once per actual entry tuple within each synchronous sort; discard the lookup after sorting.

* Skip impossible link and tag matches in docs search results (#24646)

* Skip impossible HTML matches when formatting docs search results

After the existing link-removal phase, skip the unchanged HTML regex only when the current string has no closing delimiter.

* Skip impossible link and tag matches in docs search results

Skip the five unchanged link regexes when their protected-code input lacks ]; skip the unchanged HTML regex when its post-link input lacks >.

* Decode complete transcript lines without copying their bytes (#24546)

Decode each single owned Buffer synchronously; preserve concatenation for multipart records and remove a redundant tail array copy.

* Clear unused speech worker timers after errors and exit (#24924)

Call the existing idle timer cleanup when private current-worker state is cleared; keep current-worker guards, transcript/error order, deliberate warmth and stop deadline unchanged.

* Reuse documentation search excerpts during result navigation (#24574)

Memoize the existing pure excerpt renderer by complete raw text for the current result array, retaining original rendering as a miss fallback.

* Append subagent handoffs without recopying their message list (#24672)

Append each message reference to its private call-local scope list; retain the final merge and existing ordering.

* Cancel plugin retry timers when their fetch is replaced (#24700)

Reuse the existing timer clear block immediately after the fetch generation advances; preserve live retries and all state publications.

* Avoid rebuilding visible file paths for single-row selection (#24743)

Skip the visible path array only for keyboard replacement selection; the existing replacement branch never reads that order.

* Reuse prepared reads while searching saved agent conversations (#24535)

Use schema-complete session columns to enable the existing bounded statement cache without caching result rows.

* Reuse discovered mobile chunks during static traversal (#24774)

Iterate the existing live visited Set in FIFO discovery order instead of maintaining a second shifted queue; both actual callers own untouched esbuild JSON metadata.

* Skip unused image-size calculations in mobile web browser requests (#24941)

Use the existing mobile density budget only for mobile view; pass the existing constant to the existing assembler for web/default mode, where that argument is discarded. No cache, policy, request or native path changes.

* Skip diff analysis after a result has been discarded (#24711)

Move the unchanged pure render-limit calculation after both existing generation and section-token rejection fences.

* Skip late browser grab toasts after their surface closes (#24727)

Reuse the existing mounted-owner ref at the toast presentation entry while preserving all admitted extraction/screenshot and clipboard completion.

* Classify native chat waiting messages once (#24771)

Use the existing native filter traversal to populate a fresh waiting array, keeping the original predicate, policy, projection and output ordering while classifying each actual private slot once.

* Skip discarded Kanban pruning for an empty selection (#24782)

Guard only the existing open pruning branch when both its private selected Set and nullable anchor are empty; retain all original pre-guard projections and nonempty/anchor-only/public-helper behavior.

* Index discovered test files once during shard selection validation (#24532)

Verified selection plans previously validated each selected filename with a scan of all discovered files. One per-call Set now handles membership checks. Exact source/discovery checks, nonempty selection, fallback to all tests, shard balancing and manifests remain intact.

* Clear the watchdog benchmark heartbeat when CPU sampling fails (#24802)

Move the existing initial CPU observation and histogram enable inside the existing try/finally so their failures clear the sampler's owned heartbeat, preserving successful operation order and original errors.

* Reuse the parsed notification when leasing a push delivery (#24645)

* Reuse the parsed notification when leasing a push delivery

Pass the notification already parsed for the dismissal check into the existing private delivery builder.

* Reuse the parsed notification when leasing a push delivery

Pass the notification already parsed for the dismissal check into the existing private delivery builder.

* Update README downloads badge

* Let retired-cache GC observations settle across the existing six-turn budget (#24967)

* Collect test-selection evidence when full unit tests fail (#24955)

* Collect advisory unit-selection evidence from failed full runs

* Trigger checks after retargeting the evidence fix to main

* Check each project repository once while filtering mobile cards (#24540)

Reuse exact raw source/slug matching decisions within one project filter call, preserving the matcher, membership, negative matches, output order and identity.

* Skip renderer callbacks after their request has ended (#24631)

Reuse the existing pending-request identity check before starting its deferred renderer callback.

* Decode single-piece saved-session tails without copying them (#24632)

Decode the existing owned Buffer directly when the EOF tail has one piece; preserve the existing concatenation for multiple pieces.

* Avoid irrelevant scroll-action searches in Mac snapshots (#24731)

Check the current pure action name before searching for vertical scroll actions in the same immutable array.

* Stop copying every retained browser page before attachment (#24553)

Find the first page/generation match directly in the existing insertion-ordered Map instead of copying all page references into an array.

* Reuse event byte sizes when relay watchers notify several clients (#24558)

Store the existing grouped numeric byte-size array on the already call-local watcher batch sizing object, for reuse by later chunking clients.

* Stop building unused import candidates in the test planner (#24611)

Replace the existing ordered extension map/find with a loop that forms each candidate only when its existing lookup is reached.

* docs(terminal): explain local macOS/Linux shell startup files

Document the actual login/interactive shell startup-file order and existing shell setup behavior.

Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(editor): recognize Ruby task and configuration files

Add Ruby task/configuration filenames to the existing generated language associations.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* Speed up large Markdown Find and render oversized tables (#24948)

* Speed up large Markdown Find and render oversized tables

* Poll table preview geometry outside hidden renderers

* Preserve Markdown navigation through refresh and tab restoration

* Confirm Markdown restoration when refreshed content is ready

* Trace table refresh positions and update Unicode search reference

* Recognize queued measurement scrolls before restoring Markdown anchors

* Rebuild Markdown Find ranges after renderer components change

* fix(source-control): generate clean OpenCode messages locally and over SSH (#24613)

* fix(source-control): generate clean OpenCode answers on local and SSH hosts

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: 64bb15e3f20799a21f108c849b0bdff83c692712
fix(source-control): generate clean OpenCode answers on local and SSH hosts

Use configured models and JSON answer/error events, preserve run-first arguments, and handle the precise v2 variant rejection. Hydrate SSH execution-host PATH through the existing bounded login environment resolver before direct spawning.

Credits: andy-murr (PR #5197 SSH environment intent) and coelho-doti (PR #13065 argument-order intent).

Original-commit: 1b60ec5d11132a5fed0695faea5de782a228a8e5
fix(source-control): retry inline OpenCode model and variant options

Original-commit: 98fdecc7a33ef1571dbf2854f309e4bfa1ec4300
Preserve OpenCode named errors without a data message

Restacked-from: 98fdecc7a33ef1571dbf2854f309e4bfa1ec4300
Restacked-onto: f7b1f9d8be14d12244957ee0b710a3482ec62383

* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)

* fix(ci): run mobile typechecks without concurrent dependency refresh

* test(ci): check effective Linux E2E package list

* test(ci): preserve the mobile production compiler barrier

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* test(terminal): restore the live fish fixture prerequisites (#24947)

A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.

Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.

* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: f707cde14ab8cb2670b4076916cdfb7e28c79983
fix(shortcuts): defer TUI editing chords in terminal-first mode

Original-commit: 0c6348e49e0d968e0170961d294a018adceaa9f3
docs(shortcuts): describe deferred preview terminal chords

Original-commit: be62c1b6c6b305cb8091161c1ccdd943a6e1d470
Align worktree history shortcut metadata with terminal conflict policy

Restacked-from: be62c1b6c6b305cb8091161c1ccdd943a6e1d470
Restacked-onto: f7b1f9d8be14d12244957ee0b710a3482ec62383

* Register supervised Qoder China and Qwen Code (#24616)

* Add Qoder session history and search with real CLI coverage

* Allow the real Qoder marker file to end with a newline

* Keep Qoder tool output out of history previews and search

* Keep Qoder search pages readable by older clients

* Verify persisted Qoder history after a real generated and resumed task

* Negotiate Qoder filters before searching an older execution host

* Combine search client imports for the CI plugin gate

* Keep the relay search oracle aligned with legacy agent filtering

* Register supervised Qoder China and Qwen lifecycle integration

* Cover Qoder China mobile assets and mixed-host resume gates

* Verify Qoder provider tags against the older released wire parser

* Verify China and Qwen keep independent Windows hook scripts

* Verify Qoder registrations against the installed older Windows release

* test(qoder): align search capability contracts and pin old-host fencing

* fix(qoder): rank exact picker identities and command aliases first

* test(qoder): preserve the regional CLI shared icon expectation

Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.

* fix(qoder): align China catalog entry with fallback order

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)

* Select lookup automatically for the measured hosted root-install profile

* Record hosted automatic-mode cold cache publication proof

* fix: bound remote generation setup and honor OpenCode option terminators

Count execution-host profile resolution inside the existing request deadline, cancel its waiter promptly, and pass only the remaining time to the child. Shared bounded profile probes keep their existing cache lifetime; the SSH transport margin is unchanged.

Read OpenCode output format from active final-argv options before -- so literal prompt arguments cannot select the JSON finalizer.

Fresh exact-source controls reproduce nine failures before; 139 related checks pass after, including primary/fallback delays, deadline boundaries, cancellation, parser metadata, and SSH lanes. Node typecheck and strict changed-file lint pass.

* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)

* feat(antigravity): bridge IDE history into new CLI conversations

* fix(antigravity): preserve fresh-launch model and environment for IDE references

* fix(antigravity): forward IDE history opt-in through desktop IPC

* fix(antigravity): rebuild remote IDE reference startup on its host

* fix(antigravity): register IDE continuation action labels

* fix(antigravity): confine IDE references and bound metadata reads

* fix(antigravity): localize IDE continuation badges

* Preserve scanner service cache assertions and refresh Antigravity opening metadata

* Preserve Antigravity opening joins and target folder runtime authority

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5029f9217f64970271a5b7f69d3f823

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddbc08fe1179f8bd964fff3dcb0e5d020
Original-topic-commit: 588117b5cf1dd57e2310c80068e3ddcc9f9044ec
Original-topic-commit: dedd4f8c862a961393977dc3c2547f049391b509
Original-topic-commit: 6645dae104ee5271139fcc0e9564d9223b74835d
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021c7ef1436ca606559e9d786580a84dc

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237db191c1e6280001c7e0563f3ca8846


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

* fix(opencode): enforce deadline through executable startup

Keep synchronous Windows PATH resolution and child startup within the existing request budget.

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(persistence): preserve projectGroupOrder on restart for flat folder-scan groups

Retain saved project ranks when the project remains in its folder-scan group.

Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(runtime): reclaim temp files orphaned by interrupted mobile store writes

Reuse the existing stale temporary-file cleanup policy when each mobile store opens.

Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(terminal): show actionable copy for missing folder workspace paths

Show the existing folder-path failure with concrete recovery instructions.

Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* docs: reference local orca.yaml and .worktreeinclude

Document the existing workspace configuration, include/copy rules and sharing behavior.

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

* fix(orchestration): allow model selection for OMP workers

Pass an explicitly requested model through the existing OMP worker launch catalog.

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

* fix(cli): preserve primitive success results in JSON output

Check for an object before inspecting screenshot fields so primitive success results remain printable.

Related: https://github.com/stablyai/orca/pull/14735

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>

* Show loaded file changes before deleting a workspace

Expose up to ten already loaded changed paths without altering deletion counts, hydration or authorization.

Related: https://github.com/stablyai/orca/pull/22778

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(keybindings): record and match Option+digit shortcuts on macOS

Use the physical digit key for explicitly assigned Mac Option+digit shortcuts while retaining modifier checks.

Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: Neil <neil@stably.ai>

* fix(editor): don't throw closing a stale active file

Recover stale active-file selection within the owning workspace before choosing a surviving editor.

Related: https://github.com/stablyai/orca/pull/24715

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Neil <neil@stably.ai>

* Fix Project Settings targeting for multiple local checkouts

Carry the selected checkout identity into the existing project Settings action.

Co-authored-by: fsmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>

* feat(documents): open CSV and TSV files from the OS

Extend existing OS document associations and delivery to CSV/TSV, preserving restoration and authorization.

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

* feat(cli): link GitHub and GitLab items on worktree create and set

Expose and validate the existing workspace link fields, preserving omitted values and provider identity checks.

Related: https://github.com/stablyai/orca/pull/22609

Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* feat(sidebar): include folder workspaces in keyboard navigation

Use the rendered sidebar row order and host identity when cycling through folder and Git workspaces.

Related: https://github.com/stablyai/orca/pull/10555

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>

* fix(build): turn off MSBuild file tracking for Windows native rebuilds

Default Windows native rebuilds to TrackFileAccess=false while preserving explicit caller preferences.

Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>

* Fix Ctrl+M in Linux and Windows terminals

Keep the Minimize menu item but disable its accelerator registration on Linux and Windows.

Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>

Related contribution: https://github.com/stablyai/orca/pull/24143

* fix(startup): read a nushell login PATH from $env.PATH

Use Nushell login command syntax and preserve the actual login PATH value.

Related: https://github.com/stablyai/orca/pull/22677

Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(cursor): run local hooks through sh for non-POSIX login shells

Keep POSIX hook syntax inside one quoted sh command so the login shell can invoke it safely.

Related: https://github.com/stablyai/orca/pull/22663

Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* Fix word wrap for both panes in side-by-side diffs

Forward wrapping to both diff panes through the existing editor option path and clean up listeners.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(monaco): highlight Svelte block closers inside markup

Return Svelte block closers to the existing markup tokenizer state.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(repos): keep active clone dialog open on outside clicks

Ignore accidental outside dismissal only while cloning; Escape, Close and Back still cancel.

Related: https://github.com/stablyai/orca/pull/24581, https://github.com/stablyai/orca/pull/23430

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>

* fix(editor): keep chat visible after closing Markdown tabs

Count remaining unified chat tabs before clearing workspace selection during editor close.

Related: https://github.com/stablyai/orca/pull/24273, https://github.com/stablyai/orca/pull/23760

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Honor typed starting numbers in chat ordered lists

Forward the existing parsed ordered-list start attribute to both chat Markdown renderers.

Related: https://github.com/stablyai/orca/pull/19765

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>

* Normalize base source once while checking changed-code diagnostics (#24557)

Reuse the exact existing moved-code matching algorithm with run-local lazy normalization of immutable base source blocks across diagnostics.

* Support real OpenCode sessions in native Chat (#24647)

* 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

* Update native chat settings contract for both OpenCode agents

* fix(native-chat): reconcile bounded OpenCode transcript reads

* fix(native-chat): dispatch OpenCode questions safely

* fix(native-chat): keep native discovery and transcript windows current

* feat(accounts): link standalone GLM Coding Plans (#24618)

* feat(accounts): link standalone GLM Coding Plans

Adapt the reviewed GLM accounts contribution to current main, retain Antigravity behavior, guard late credential results, expose storage protection, and redact quota errors.

Co-authored-by: Luchong <lu740528977@gmail.com>

* fix(accounts): retain GLM credential results during quota refresh

* fix(accounts): make GLM credential editing desktop-only

* fix(accounts): mirror the host GLM site in paired clients

* fix(accounts): report unknown GLM host details and split web settings tests

Apply the independently reviewed Accounts correction from697284a without the v2 adapter commits. Preserve the saved-key store and serialized write behavior.

* fix(zcode): ship required GLM account translation entries

* chore: record GLM reconciliation hook validation

* chore: validate installed GLM commit hooks

* test: complete GLM account fixtures and web API inventory

---------

Co-authored-by: Luchong <lu740528977@gmail.com>

* fix(native-chat): route transcript requests through shared SQLite worker

* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)

* fix(ci): run mobile typechecks without concurrent dependency refresh

* test(ci): check effective Linux E2E package list

* test(ci): preserve the mobile production compiler barrier

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* test(terminal): restore the live fish fixture prerequisites (#24947)

A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.

Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.

* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: f707cde14ab8cb2670b4076916cdfb7e28c79983
fix(shortcuts): defer TUI editing chords in terminal-first mode

Original-commit: 0c6348e49e0d968e0170961d294a018adceaa9f3
docs(shortcuts): describe deferred preview terminal chords

Original-commit: be62c1b6c6b305cb8091161c1ccdd943a6e1d470
Align worktree history shortcut metadata with terminal conflict policy

Restacked-from: be62c1b6c6b305cb8091161c1ccdd943a6e1d470
Restacked-onto: f7b1f9d8be14d12244957ee0b710a3482ec62383

* Register supervised Qoder China and Qwen Code (#24616)

* Add Qoder session history and search with real CLI coverage

* Allow the real Qoder marker file to end with a newline

* Keep Qoder tool output out of history previews and search

* Keep Qoder search pages readable by older clients

* Verify persisted Qoder history after a real generated and resumed task

* Negotiate Qoder filters before searching an older execution host

* Combine search client imports for the CI plugin gate

* Keep the relay search oracle aligned with legacy agent filtering

* Register supervised Qoder China and Qwen lifecycle integration

* Cover Qoder China mobile assets and mixed-host resume gates

* Verify Qoder provider tags against the older released wire parser

* Verify China and Qwen keep independent Windows hook scripts

* Verify Qoder registrations against the installed older Windows release

* test(qoder): align search capability contracts and pin old-host fencing

* fix(qoder): rank exact picker identities and command aliases first

* test(qoder): preserve the regional CLI shared icon expectation

Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.

* fix(qoder): align China catalog entry with fallback order

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)

* Select lookup automatically for the measured hosted root-install profile

* Record hosted automatic-mode cold cache publication proof

* fix(native-chat): keep OpenCode history usable at read limits

Continue past failed database probes while preserving discovery cancellation.
Verify rows displaced by a capped tail before deciding whether to replace history.
Represent oversized v1/v2 rows with the existing omission text and stable cursors.

* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)

* feat(antigravity): bridge IDE history into new CLI conversations

* fix(antigravity): preserve fresh-launch model and environment for IDE references

* fix(antigravity): forward IDE history opt-in through desktop IPC

* fix(antigravity): rebuild remote IDE reference startup on its host

* fix(antigravity): register IDE continuation action labels

* fix(antigravity): confine IDE references and bound metadata reads

* fix(antigravity): localize IDE continuation badges

* Preserve scanner service cache assertions and refresh Antigravity opening metadata

* Preserve Antigravity opening joins and target folder runtime authority

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5029f9217f64970271a5b7f69d3f823

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddbc08fe1179f8bd964fff3dcb0e5d020
Original-topic-commit: 588117b5cf1dd57e2310c80068e3ddcc9f9044ec
Original-topic-commit: dedd4f8c862a961393977dc3c2547f049391b509
Original-topic-commit: 6645dae104ee5271139fcc0e9564d9223b74835d
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021c7ef1436ca606559e9d786580a84dc

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237db191c1e6280001c7e0563f3ca8846


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

---------

Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* Drain removal fixture jobs before resetting and deleting their records (#24977)

* Reuse measured Electron preparation for current Terminal Perf refs (#24968)

* feat(jcode): add Jcode as a supported TUI agent with managed hooks

Ports PR #10521 onto current main: agent catalog, managed hook service,
agent-status listener, session resume, AI Vault parser, per-pane daemon
isolation, and Source Control AI support.

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

* feat(jcode): evidence-backed status pipeline (turn_start, live pre_tool, questions)

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): register vault fixture, dynamic model discovery, script refresher

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): keep finished-turn detail so completion notifications fire

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): show the prompt in agent rows instead of jcode's repainting title

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): pre-warm the per-pane daemon so a cold runtime dir cannot time out

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): stop the OSC color skip from crashing every pane connect

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* refactor(jcode): fold three reverse-scan copies into one, reuse the shared hook POST

The jcode journal reader, the Claude transcript reader and the Command Code
transcript reader each carried their own copy of the same reverse chunked line
scan; they now share one tested helper. jcode's managed hook script drops its
hand-rolled curl for buildPosixAgentHookPostCommand, which also gains it the
raw-JSON transport and the --noproxy guard the bespoke copy was missing.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): narrow dynamic reads with predicates, pin the Windows hook shape

CI's anti-slop audit rejects Reflect.get: parse dynamic input into a named type
instead. Adds Windows script-shape tests too, since Windows is the platform this
change could not be exercised on directly.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): measure the gate against the synchronous path, not the clock

The absolute 1s bound was the flake CI shard 3/8 hit: it is tight enough to catch
a synchronous gate on an idle laptop and too tight on a loaded runner. Measuring
the same POST both ways on the same machine makes the claim a ratio, which is what
the test is actually about. Mutation-checked: a synchronous gate reads 6120ms
against a 1517ms observer.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): drop the wall-clock gate assertion

The bound was machine-speed sensitive and flaked on CI shard 3/8 at 1525ms. The
structural assertions (detached gate branch, foreground observer branch) and the
no-hang stdin drain cover the same contract without a timer.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): address CodeRabbit review on #22539

- Windows posted no payload at all: the shared builder reads `payload@-` from
  stdin, which the gate has already drained and observer hooks never receive, so
  every Windows event was dropped. Write the env var to a temp file and pipe it.
- The daemon pre-warm never fired for daemon-host spawns, which is the default
  local path; it now runs there too, and from the final env so the daemon gets the
  hook port and token.
- A failed runtime-dir mkdir took down every local terminal, jcode or not.
- removeJcodeManagedHooks matched the raw line, so a user hook whose comment
  mentioned the managed script was deleted; matching on Windows never worked.
- A managed entry left by a copied home or a platform switch is now repointed
  instead of being reported as user-owned forever.
- The OSC colour skip only checked launchAgent, so a command- or telemetry-named
  jcode pane still leaked the reply into its composer.
- `['hooks']` and a commented scalar are recognised, instead of appending a
  second [hooks] table that makes jcode reject the whole config.
- A failed tool's error is marked as tool output rather than agent prose.
- The vault keeps a session's stored name, counts its tokens, and skips
  background_task and [Scheduled task] turns.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): read the journal once per turn, key prompts by byte offset

The jcode prompt reader ran a synchronous bounded file scan plus a JSON
parse on every hook event. jcode blocks on pre_tool, so a turn that ran
four tools charged the user eight scans of latency it did not need — the
prompt cannot change inside a turn.

- Cache the journal read per pane, refreshed on the turn boundary that
  can change it. The cache holds the whole evidence record, since
  hasExplicitUserPrompt needs the transcript-evidence flag and not just
  the text, and it joins the existing pane-scoped lifecycle (close,
  rename, reset) rather than living in a module singleton.
- Key a journal prompt by its absolute byte offset instead of its
  region-local line index. The backward scan windows the file from EOF,
  so appending shifted every boundary and reminted the key for a prompt
  that never moved; a repeated turn_end then slipped past the same-hash
  dedupe as a second done event with duplicate telemetry.
- Pin the platform in the daemon pre-warm tests. The pre-warm is a no-op
  off POSIX, so the dedupe and retry cases would have passed vacuously
  on a Windows runner.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): quote the managed hook path, drop tools from patch prompts

Three fixes, all on paths this PR could not exercise locally.

The managed hook command was stored as a bare path. jcode tokenizes that
string shell-style before exec'ing it directly (parse_hook_command,
crates/jcode-terminal-launch/src/lib.rs): unquoted whitespace splits, and
every unquoted backslash is consumed as an escape. So on Windows
`C:\Users\me\.orca\agent-hooks\jcode-hook.cmd` reached exec as
`C:Usersme.orcaagent-hooksjcode-hook.cmd` and no hook fired at all, and a
POSIX home with a space split into two arguments. Store the path
single-quoted (verbatim, backslashes included), falling back to double
quotes for a path containing a single quote. Existing bare entries are
already repointed by the stale-key path, and getStatus accepts both forms
so the repair is not reported as a user-owned hook. The quoting helper was
previously dead code that only tests called; the three production sites
now use it. isJcodeManagedCommand also normalizes separators, since a
`/`-only needle never matched a Windows entry.

Commit-message generation feeds a staged patch to `jcode run` as the
prompt — attacker-influenced text — while jcode's default profile exposes
shell, read, write, and MCP. Pass `--tool-profile none`, which resolves to
an empty allowed-tool set in jcode's config (base_allowed_tools), matching
the read-only posture claude (plan) and codex (read-only) already take.

docs/reference/jcode-hook-events.md was never actually in this PR: the
repo ignores docs/** and tracks reference docs by allow-list only, so the
captured-payload evidence four source comments point at was silently
dropped. Allow-list it.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): pin the tool-profile flag in the generation plan too

The argv assertion lives in two places; --tool-profile none only landed in
one, so the plan test still expected the unrestricted argv.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(text-generation): refuse an oversized argv prompt on Linux too

The pre-spawn size guard only ran on Windows. Linux caps a single argv
entry at MAX_ARG_STRLEN (32 pages, 128 KiB on a 4-KiB-page host) and
execve fails with E2BIG past it, so an agent that delivers the whole
prompt as one argument — jcode, and the other argv-delivery agents —
failed on a large staged diff with an error the user could not act on.

The cap is per-argument and in bytes, which is why it is not the Windows
line budget: 40k chars trips Windows and is nowhere near the Linux limit,
so folding them together would have refused prompts Linux runs fine.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): register in main's remote-installer guard, drop our duplicate

Rebasing onto 853 commits of main surfaced two things the earlier branch
had hidden.

main already owns a guard for the issue-#7253 bug class
(`remote-hook-service-registry-coverage.test.ts`). This branch had added a
second, near-identical one — a parallel implementation of a test that
already existed, which is what AGENTS.md's reuse rule is about. Deleted
ours and registered jcode in main's, which is the one that has kept pace
with every agent added since.

Also fixes a missing separator in the mobile icon map. `pnpm tc` does not
cover `mobile/`, so only the session-route closure suite caught it.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): decompose the four files jcode pushed over max-lines

Adding an agent tipped four modules past their line budget. AGENTS.md
forbids a `max-lines` disable or a per-file bump, so each is split on a
real seam rather than silenced:

- agent-catalog.tsx keeps `AgentIcon`, which 70+ files import, and the
  rows move out. The rows alone exceed the 300-line budget a `.ts` file
  gets, so they follow the primary/secondary split this repo already uses
  for commit-message agent specs.
- getAgentResumeArgv -> agent-resume-argv.ts, re-exported so the 18 call
  sites keep one import path.
- isDiscoverableSessionFile/pathSegments -> session-file-discovery.ts.
- remoteCodexSources -> remote-session-scanner-codex-sources.ts; Codex is
  the one remote agent with two CODEX_HOME roots.

Also:
- Records the readiness-census baseline jcode now needs. main added that
  gate while this branch was out; the fixture is the recorded 74-case
  matrix, not a hand-written one.
- Restores two entries a rebase resolution silently dropped from
  config/tsconfig.cli.json (gitlab/project-ref-parser,
  startup/shell-path-probe). Nothing to do with jcode; losing them was a
  conflict-resolution mistake on this branch.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* feat(native-chat): open structured chats on the paired Orca server that owns the workspace (#24205)

* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting

A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.

The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.

The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).

* fix(native-chat): the desktop declares structured chat support to paired hosts

The desktop renderer advertised agent-session.structured.v1 (and the Claude, turn-item and
background-task capabilities that go with it) to its own main process but not to a paired Orca
server. The server therefore refused every agentSession.* call from the desktop and stripped
structured chat tabs out of the tab list it published to it, so a structured chat running on a
paired server never appeared on the desktop, even though the renderer already mirrors a host's
agent-session tabs and drives each one against the server that owns its workspace.

The same renderer reads structured chats on either host, so the remote Electron list now carries
the same structured-session capabilities as the local one, and the capability test pins that
nothing is advertised only locally.

* feat(native-chat): open structured chats on the paired server that owns the workspace

With the structured-chat default on, an agent launched in a workspace that lives on a paired Orca
server always opened as a terminal (or the terminal-backed chat view). Three things kept it off
the structured path: the launch check refused every host but this machine, a remote workspace was
handed to the host-published terminal path before the structured route was even considered, and
the structured launch pipeline sent create and every follow-up call to this machine's runtime.

A workspace's owning runtime is fixed, so the pipeline now derives it from the workspace instead
of assuming this machine (structured-agent-session-owner.ts, the same derivation the chat pane
already uses to read a session). The launch intent carries that target; the pre-create support
check, create, the publication check and fence read, the launch prompt send, held option picks,
the "focus this chat" marker, the placeholder tab's host, and tab close/purge all use it.

The launch check now accepts a paired server and asks that server's own capabilities (read from
the status the client already cached for it) rather than this machine's. An SSH workspace stays
terminal-backed: no Orca runtime runs there. The host still answers createSupport before
anything is created, so an older server that refuses shows the failure in the chat tab.

A chat the user closed before its create landed is now also retired on the paired server when it
publishes, as the local sync already does. Orchestration workers placed on another runtime are
unchanged: federation creates terminal agents only.

* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals

Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.

* refactor(runtime): keep the Electron client capability list in its own module

protocol-version.ts is at its line budget; the list is what the desktop
advertises to paired hosts, not the host's own contract.

* fix(native-chat): the desktop declares it picks each launch mode itself

Paired hosts and the desktop's own main process then answer createSupport
by the workspace rather than by their own chat setting.

* fix(native-chat): pin each structured chat to the host it was launched on

- Route: a paired server opens a chat only when it advertises the
  client-chosen launch mode; an older server keeps its terminal. Its
  capabilities come from the store's host status, not the compatibility
  cache that is empty after boot or reconnect.
- A launch command override is this machine's: the route applies it only
  locally, and a host's createSupport refuses on its own override.
- The owning host is resolved once, from the same value the route used,
  and carried on the launch intent, its persisted record (legacy records
  load as local), the provisional tab and every mirrored chat tab. Close,
  purge, retry and reload read it instead of re-deriving it from a
  worktree id two hosts can share; an owner that cannot be named refuses.
- Cancellation tombstones record their host: only that host's
  authoritative inventory retires one, restored cleanup closes it there,
  and a paired host's tombstone expires after 30 days if it never answers.
- A paired server's frame settles launches it published, as the local
  inventory already does for this machine.

* fix(native-chat): a paired server that declines a chat opens its terminal instead

createSupport only reads, so both of its non-answers are settled before
anything is created:
- A paired server that answers it cannot run the chat (a WSL repo, a
  Claude account mismatch, its own launch command override) closes the
  chat tab and opens the terminal the route would have chosen, with a
  notice saying why. This machine's own decline stays a failed chat.
- A host that could not be asked closes the chat tab and leaves one
  failure toast, instead of a lingering "could not confirm" chat.

* test(native-chat): a provisional chat carries its launch's host and hands pre-create failures on

* chore(native-chat): justify the two type assertions this change's lines touch

* test(native-chat): state why each staged test fixture is cast

* fix(native-chat): chats that already exist keep showing whatever the chat setting says

The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.

* fix(native-chat): the browser client keeps its host terminal on paired servers

A browser client whose own preferences turn structured chat on took the
structured route for every paired-server workspace, but its handshake
never says it reads structured sessions, so the server refused the chat
and the user got a failed chat tab where a host terminal used to open.
The route for a paired host now also asks what this client advertises to
it: the desktop's list does, the browser client's does not. Its handshake
list is now a named constant the route reads, so the two cannot drift.

The chat setting's copy now says it runs on paired Orca servers too;
WSL and SSH hosts still use terminal chat.

* fix(native-chat): a retried launch a paired server declines opens its terminal too

A launch restored after a reload settles only through its Retry, so a
declining paired server left a failed chat there while a first launch got
the server's terminal and a notice. The chat's Retry now hands the same
pre-create failures to the same replacement, carrying the prompt the
launch had staged.

* refactor(native-chat): a paired host's cancelled-chat record ends on its 30-day TTL

The paired census re-read a host's whole inventory after every
authoritative frame to retire tombstones, and a tombstone restored after
a reload needed a second such frame, so in practice it retired nothing.
A tombstone guards a random session id and is inert once stale; the chat
is already closed on its host whenever a frame shows it. The census, its
trigger in the mirror layer and its cleanup are removed; the owner-scoped
tombstones, close-on-sight, the TTL and publication marking from frames
stay.

* fix(native-chat): a chat's pane and status read from the host recorded on its tab

The chat pane and its sidebar status still derived the host from the
workspace id, which two hosts can share; a paired chat in a non-active
same-id workspace was read from this machine. Both now read the owner
stamped on the tab, as close, purge and publication already do.

* fix(native-chat): "Resume in chat" follows the terminal resume's host rule

Agent Session History offered "Resume in chat" for a conversation
recorded on this machine into a paired server's workspace, where its
transcript does not exist. A chat now resumes a conversation only on the
host that recorded it, as the terminal resume does, and that host is the
one asked whether it can resume history.

* fix(native-chat): the chat setting says older paired servers keep terminal chat

* test(native-chat): pin that a host advertises the client-chosen launch mode

* fix(native-chat): mirror this machine's chats only where it holds them

Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.

The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.

* test(native-chat): route a paired-server launch over the capability lists both sides really advertise

* test(native-chat): record install listeners without a cast

* fix(native-chat): a paired server admits a chat before any of it exists here

The desktop opened a paired server's chat tab, launch record, queued
prompt and focus intent before asking the server, so a "no" needed a
replacement that undid and redid all of it, and every piece it missed
was a bug: the workspace deselected, the caller told "failed" while a
terminal ran its prompt, the caller's arguments and other queued prompts
lost, and a create whose reply was lost treated as never sent.

A paired launch now asks the server first and commits nothing until it
answers. Admitted opens the chat as before. Declined runs the caller's
own launch as the server's terminal, with the existing notice (a resume
fails instead, having no terminal equivalent). Unreachable opens nothing
and names the server in one toast. The new-tab launcher reports the
host's surface for paired workspaces, as it did before paired chats,
with the prompt delivery of whichever surface got the prompt. The
replacement and its error classes are gone, and the probe inside a
launch is back to its old meaning: a "no" is a failed chat with Retry,
and no answer leaves "Could not confirm" with Retry and the prompt kept,
here as on this machine.

* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built

Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.

* fix(native-chat): a fork's reveal never seeds a terminal beside the surface the launcher opens

Forking into a paired-server workspace revealed it as if nothing would open there, so the reveal
created a blank host terminal beside the forked chat (and beside a forked agent terminal on main).
The launcher always opens the fork's surface itself, so the reveal now says so for every surface,
as the fix-checks launch already does.

* fix(native-chat): a declined direct launch keeps the caller's CLI args; an unreachable resume toasts once

When a paired server declines a "Fix checks" chat in a new workspace, the terminal that opens
instead now carries the recipe's saved CLI arguments, launch platform and launch source, as the
terminal route did. "Resume in chat" to a server that cannot be reached showed the admission's
"Could not reach" toast and the vault's generic one; the admission marks its failure notified and
the vault adds nothing.

* fix(native-chat): a declined background create opens its terminal without switching workspaces

Since #23974 a worktree create the user moved away from must not pull them onto the new
workspace. When a paired server declined that create's chat, the fallback terminal opened as a new
agent tab, whose host create selects the workspace. The create now opens its own agent terminal the
way main's background branch does: in place from the request's startup plan (so its CLI args carry),
without selecting the workspace. A create the user is still watching keeps the new-tab fallback.

* test(native-chat): name the launch's host in main's new outbox fence test

Main's new staging-failure test calls settleStructuredAgentLaunchPrompt without the target this PR
made required; it is a local launch, as in the sibling tests.

* fix(native-chat): a paired server's new chat shows no model until the server reports the one it started

A chat on a paired server starts with the server's saved model and options, but the picker showed
this desktop's saved selection (or the catalog default) until the server reported a model, and a
pick made in that window was remembered on the server under that guessed model. A paired launch
now carries no desktop seed, and until the server reports its model the picker names no model and
takes no picks. Local chats are unchanged.

* test(native-chat): seed the paired repo without a cast

The repo literal already satisfies Repo, so the changed-lines cast gate has nothing to excuse.

* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with

A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.

* fix(native-chat): a paired server's new chat shows the selection the server will start it with

The paired server now names its saved model and options in the admission answer the desktop
already waits for. That seed goes into the launch intent and its persisted record, so the picker
shows the server's model at once, stays pickable like a local chat, and remembers picks on the
server under that model; a reload shows the same. The locked picker remains only for a server too
old to name a seed.

Also moves host admission and launch-outcome tracking into their own modules: the latest main
merge left structured-agent-session-launch.ts over the max-lines limit.

* test(native-chat): expect the launch intent's new seed argument in exact-call assertions

* refactor(protocol): move the Electron remote client capability list into its own module

Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.

* fix(native-chat): a paired chat with no saved server model is pickable; Retry shows the server's current seed

A server whose user never saved a chat model sends no seed, and the desktop showed a locked,
model-only picker for it, although that is the common case: no server that can admit a paired chat
predates the seed field. Such a chat now behaves like a local chat with no saved model: the CLI
default, pickable. The lock and its snapshot helper are gone.

Retry kept the first admission's seed while the create probe, which already runs on every attempt,
reported the server's current one and dropped it. The probe's seed now replaces a paired launch's
seed and the picker's, so a retried chat shows what its create will run.

* test(cross-version): stub the launch seed resolver createSupport now reads

* test(protocol): pin the desktop capability divergence against what a paired server receives

Every paired transport sends the shared remote base plus the Electron list, so the
divergence test now compares that union with the renderer's local list instead of
the declared Electron list. A capability added only to the shared base can no
longer slip past it. The two base-only capabilities it surfaced are recorded:
skills.install-result.v2 has no local caller; the authoritative-inventory label is
read by the local tabs sync but dropped by main, and is marked unsettled.

The turn-item and both background-task-stop capabilities were already sent through
the shared base, so the Electron list no longer repeats them. The wire set is
unchanged; this PR's real change on the wire is structured.v1, the Claude
structured capability and the client launch-mode capability.

* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off

* docs(native-chat): name the real exit for the released-phone createSupport rule

* fix(native-chat): the route reads the capabilities a paired host actually receives

The renderer decided whether a paired host would admit a chat from the desktop's Electron list, but
every desktop transport sends that list plus the shared remote base. They agreed only because the
route's checks happened to sit in both. The route input is now built with the same
remoteRuntimeClientCapabilities the transports use (the browser client already sends its list as is),
and a test pins each against the real handshake.

* test(cross-version): a released client still gets the host-setting createSupport answer; a launch-mode client gets supported plus the seed

* test(native-chat): let main's child-records test resolve each chat's owner

Main's new test mocks worktree-runtime-owner with only the runtime environment id, but the status
projection in this PR also resolves each structured chat's owner from the worktree. The mock keeps
the module's real exports and overrides only what the test pins.

* fix(deps): take #24204's lockfile that the merge reverted

* test(native-chat): let the Codex child-approval e2e unit test resolve each chat's owner

Its worktree-runtime-owner mock exported only the runtime environment id, but this PR's status
projection also resolves each structured chat's owner from the worktree. The mock now keeps the
module's real exports and overrides only that id, as structured-child-records-switch does.

* test(native-chat): move the close-race launch cases into their own file

Merging main added launch tests on both sides and took structured-agent-session-launch.test.ts past
the 800-line limit. The three cases where a tab close races a launch move to
structured-agent-session-launch-close-race.test.ts, with the same setup the other split launch
suites copy.

* feat(cli): add --unread/--read to orca worktree set

Pass validated read/unread flags through the existing workspace metadata update.

Contributor attribution correction: the earlier Ctrl+M squash message placed its related link after the co-author footer, so GitHub did not recognize all contributors. Preserve that credit here for https://github.com/stablyai/orca/pull/24100 and its related contribution https://github.com/stablyai/orca/pull/24143 .

Related CLI link validation retained from https://github.com/stablyai/orca/pull/20036 .

Co-authored-by: Raz Shlomo <1237333…
@nwparker

nwparker commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Superseded by merged PR #24962: #24962 (main commit 5e65871).

Credit @werlang for this PR and issue #22838. The replacement carries the full reviewed OpenCode ownership correction, takes each displayed session from the real TUI, and fences stale relay posts using the execution host’s successful PTY launch identity. It was validated with real OpenCode 1.18.30, two shared-server panes on macOS, and the different-repository Windows sequence from #24844. Genuine before/after proof and final CI results are in the merged PR. No source from this PR was copied.

nwparker added a commit that referenced this pull request Oct 5, 2026
…ts (#22682)

* Skip diff analysis after a result has been discarded (#24711)

Move the unchanged pure render-limit calculation after both existing generation and section-token rejection fences.

* Skip late browser grab toasts after their surface closes (#24727)

Reuse the existing mounted-owner ref at the toast presentation entry while preserving all admitted extraction/screenshot and clipboard completion.

* Classify native chat waiting messages once (#24771)

Use the existing native filter traversal to populate a fresh waiting array, keeping the original predicate, policy, projection and output ordering while classifying each actual private slot once.

* Skip discarded Kanban pruning for an empty selection (#24782)

Guard only the existing open pruning branch when both its private selected Set and nullable anchor are empty; retain all original pre-guard projections and nonempty/anchor-only/public-helper behavior.

* Index discovered test files once during shard selection validation (#24532)

Verified selection plans previously validated each selected filename with a scan of all discovered files. One per-call Set now handles membership checks. Exact source/discovery checks, nonempty selection, fallback to all tests, shard balancing and manifests remain intact.

* Clear the watchdog benchmark heartbeat when CPU sampling fails (#24802)

Move the existing initial CPU observation and histogram enable inside the existing try/finally so their failures clear the sampler's owned heartbeat, preserving successful operation order and original errors.

* Reuse the parsed notification when leasing a push delivery (#24645)

* Reuse the parsed notification when leasing a push delivery

Pass the notification already parsed for the dismissal check into the existing private delivery builder.

* Reuse the parsed notification when leasing a push delivery

Pass the notification already parsed for the dismissal check into the existing private delivery builder.

* Update README downloads badge

* Let retired-cache GC observations settle across the existing six-turn budget (#24967)

* Collect test-selection evidence when full unit tests fail (#24955)

* Collect advisory unit-selection evidence from failed full runs

* Trigger checks after retargeting the evidence fix to main

* Check each project repository once while filtering mobile cards (#24540)

Reuse exact raw source/slug matching decisions within one project filter call, preserving the matcher, membership, negative matches, output order and identity.

* Skip renderer callbacks after their request has ended (#24631)

Reuse the existing pending-request identity check before starting its deferred renderer callback.

* Decode single-piece saved-session tails without copying them (#24632)

Decode the existing owned Buffer directly when the EOF tail has one piece; preserve the existing concatenation for multiple pieces.

* Avoid irrelevant scroll-action searches in Mac snapshots (#24731)

Check the current pure action name before searching for vertical scroll actions in the same immutable array.

* Stop copying every retained browser page before attachment (#24553)

Find the first page/generation match directly in the existing insertion-ordered Map instead of copying all page references into an array.

* Reuse event byte sizes when relay watchers notify several clients (#24558)

Store the existing grouped numeric byte-size array on the already call-local watcher batch sizing object, for reuse by later chunking clients.

* Stop building unused import candidates in the test planner (#24611)

Replace the existing ordered extension map/find with a loop that forms each candidate only when its existing lookup is reached.

* docs(terminal): explain local macOS/Linux shell startup files

Document the actual login/interactive shell startup-file order and existing shell setup behavior.

Co-authored-by: brynnclaw <261708852+brynnclaw@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(editor): recognize Ruby task and configuration files

Add Ruby task/configuration filenames to the existing generated language associations.

Co-authored-by: ggbdpq <ggbdpq@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* Speed up large Markdown Find and render oversized tables (#24948)

* Speed up large Markdown Find and render oversized tables

* Poll table preview geometry outside hidden renderers

* Preserve Markdown navigation through refresh and tab restoration

* Confirm Markdown restoration when refreshed content is ready

* Trace table refresh positions and update Unicode search reference

* Recognize queued measurement scrolls before restoring Markdown anchors

* Rebuild Markdown Find ranges after renderer components change

* fix(source-control): generate clean OpenCode messages locally and over SSH (#24613)

* fix(source-control): generate clean OpenCode answers on local and SSH hosts

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: 64bb15e3f20799a21f108c849b0bdff83c692712
fix(source-control): generate clean OpenCode answers on local and SSH hosts

Use configured models and JSON answer/error events, preserve run-first arguments, and handle the precise v2 variant rejection. Hydrate SSH execution-host PATH through the existing bounded login environment resolver before direct spawning.

Credits: andy-murr (PR #5197 SSH environment intent) and coelho-doti (PR #13065 argument-order intent).

Original-commit: 1b60ec5d11132a5fed0695faea5de782a228a8e5
fix(source-control): retry inline OpenCode model and variant options

Original-commit: 98fdecc7a33ef1571dbf2854f309e4bfa1ec4300
Preserve OpenCode named errors without a data message

Restacked-from: 98fdecc7a33ef1571dbf2854f309e4bfa1ec4300
Restacked-onto: f7b1f9d8be14d12244957ee0b710a3482ec62383

* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)

* fix(ci): run mobile typechecks without concurrent dependency refresh

* test(ci): check effective Linux E2E package list

* test(ci): preserve the mobile production compiler barrier

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* test(terminal): restore the live fish fixture prerequisites (#24947)

A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.

Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.

* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: f707cde14ab8cb2670b4076916cdfb7e28c79983
fix(shortcuts): defer TUI editing chords in terminal-first mode

Original-commit: 0c6348e49e0d968e0170961d294a018adceaa9f3
docs(shortcuts): describe deferred preview terminal chords

Original-commit: be62c1b6c6b305cb8091161c1ccdd943a6e1d470
Align worktree history shortcut metadata with terminal conflict policy

Restacked-from: be62c1b6c6b305cb8091161c1ccdd943a6e1d470
Restacked-onto: f7b1f9d8be14d12244957ee0b710a3482ec62383

* Register supervised Qoder China and Qwen Code (#24616)

* Add Qoder session history and search with real CLI coverage

* Allow the real Qoder marker file to end with a newline

* Keep Qoder tool output out of history previews and search

* Keep Qoder search pages readable by older clients

* Verify persisted Qoder history after a real generated and resumed task

* Negotiate Qoder filters before searching an older execution host

* Combine search client imports for the CI plugin gate

* Keep the relay search oracle aligned with legacy agent filtering

* Register supervised Qoder China and Qwen lifecycle integration

* Cover Qoder China mobile assets and mixed-host resume gates

* Verify Qoder provider tags against the older released wire parser

* Verify China and Qwen keep independent Windows hook scripts

* Verify Qoder registrations against the installed older Windows release

* test(qoder): align search capability contracts and pin old-host fencing

* fix(qoder): rank exact picker identities and command aliases first

* test(qoder): preserve the regional CLI shared icon expectation

Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.

* fix(qoder): align China catalog entry with fallback order

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)

* Select lookup automatically for the measured hosted root-install profile

* Record hosted automatic-mode cold cache publication proof

* fix: bound remote generation setup and honor OpenCode option terminators

Count execution-host profile resolution inside the existing request deadline, cancel its waiter promptly, and pass only the remaining time to the child. Shared bounded profile probes keep their existing cache lifetime; the SSH transport margin is unchanged.

Read OpenCode output format from active final-argv options before -- so literal prompt arguments cannot select the JSON finalizer.

Fresh exact-source controls reproduce nine failures before; 139 related checks pass after, including primary/fallback delays, deadline boundaries, cancellation, parser metadata, and SSH lanes. Node typecheck and strict changed-file lint pass.

* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)

* feat(antigravity): bridge IDE history into new CLI conversations

* fix(antigravity): preserve fresh-launch model and environment for IDE references

* fix(antigravity): forward IDE history opt-in through desktop IPC

* fix(antigravity): rebuild remote IDE reference startup on its host

* fix(antigravity): register IDE continuation action labels

* fix(antigravity): confine IDE references and bound metadata reads

* fix(antigravity): localize IDE continuation badges

* Preserve scanner service cache assertions and refresh Antigravity opening metadata

* Preserve Antigravity opening joins and target folder runtime authority

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5029f9217f64970271a5b7f69d3f823

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddbc08fe1179f8bd964fff3dcb0e5d020
Original-topic-commit: 588117b5cf1dd57e2310c80068e3ddcc9f9044ec
Original-topic-commit: dedd4f8c862a961393977dc3c2547f049391b509
Original-topic-commit: 6645dae104ee5271139fcc0e9564d9223b74835d
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021c7ef1436ca606559e9d786580a84dc

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237db191c1e6280001c7e0563f3ca8846


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

* fix(opencode): enforce deadline through executable startup

Keep synchronous Windows PATH resolution and child startup within the existing request budget.

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(persistence): preserve projectGroupOrder on restart for flat folder-scan groups

Retain saved project ranks when the project remains in its folder-scan group.

Co-authored-by: lurunzi <lurunzi@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(runtime): reclaim temp files orphaned by interrupted mobile store writes

Reuse the existing stale temporary-file cleanup policy when each mobile store opens.

Co-authored-by: LDH1103 <ldh517525@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(terminal): show actionable copy for missing folder workspace paths

Show the existing folder-path failure with concrete recovery instructions.

Co-authored-by: Wayn_Liu <wayntingliu@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* docs: reference local orca.yaml and .worktreeinclude

Document the existing workspace configuration, include/copy rules and sharing behavior.

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

* fix(orchestration): allow model selection for OMP workers

Pass an explicitly requested model through the existing OMP worker launch catalog.

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

* fix(cli): preserve primitive success results in JSON output

Check for an object before inspecting screenshot fields so primitive success results remain printable.

Related: https://github.com/stablyai/orca/pull/14735

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: VXNCXNX <VXNCXNX@users.noreply.github.com>

* Show loaded file changes before deleting a workspace

Expose up to ten already loaded changed paths without altering deletion counts, hydration or authorization.

Related: https://github.com/stablyai/orca/pull/22778

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Michiel de Gooijer <mdgooijer@gmail.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* fix(keybindings): record and match Option+digit shortcuts on macOS

Use the physical digit key for explicitly assigned Mac Option+digit shortcuts while retaining modifier checks.

Co-authored-by: marcuslannister <marcus@lannister.cc>
Co-authored-by: Neil <neil@stably.ai>

* fix(editor): don't throw closing a stale active file

Recover stale active-file selection within the owning workspace before choosing a surviving editor.

Related: https://github.com/stablyai/orca/pull/24715

Co-authored-by: m4air <m4air@m4airs-Air.localdomain>
Co-authored-by: Neil <neil@stably.ai>

* Fix Project Settings targeting for multiple local checkouts

Carry the selected checkout identity into the existing project Settings action.

Co-authored-by: fsmeier <1506919+fsmeier@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>

* feat(documents): open CSV and TSV files from the OS

Extend existing OS document associations and delivery to CSV/TSV, preserving restoration and authorization.

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

* feat(cli): link GitHub and GitLab items on worktree create and set

Expose and validate the existing workspace link fields, preserving omitted values and provider identity checks.

Related: https://github.com/stablyai/orca/pull/22609

Co-authored-by: marco song <marco.song@mvlchain.io>
Co-authored-by: SongMarco <20613630+SongMarco@users.noreply.github.com>
Co-authored-by: Luca Critelli <lucacri@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* feat(sidebar): include folder workspaces in keyboard navigation

Use the rendered sidebar row order and host identity when cycling through folder and Git workspaces.

Related: https://github.com/stablyai/orca/pull/10555

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: JeongUk Park <jeongph.dev@gmail.com>

* fix(build): turn off MSBuild file tracking for Windows native rebuilds

Default Windows native rebuilds to TrackFileAccess=false while preserving explicit caller preferences.

Co-authored-by: B1nh M1nh <43268322+b1nhm1nh@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Neil <neil@stably.ai>

* Fix Ctrl+M in Linux and Windows terminals

Keep the Minimize menu item but disable its accelerator registration on Linux and Windows.

Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>

Related contribution: https://github.com/stablyai/orca/pull/24143

* fix(startup): read a nushell login PATH from $env.PATH

Use Nushell login command syntax and preserve the actual login PATH value.

Related: https://github.com/stablyai/orca/pull/22677

Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(cursor): run local hooks through sh for non-POSIX login shells

Keep POSIX hook syntax inside one quoted sh command so the login shell can invoke it safely.

Related: https://github.com/stablyai/orca/pull/22663

Co-authored-by: Kh05ifr4nD <meandSSH0219@gmail.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* Fix word wrap for both panes in side-by-side diffs

Forward wrapping to both diff panes through the existing editor option path and clean up listeners.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(monaco): highlight Svelte block closers inside markup

Return Svelte block closers to the existing markup tokenizer state.

Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Neil <neil@stably.ai>

* fix(repos): keep active clone dialog open on outside clicks

Ignore accidental outside dismissal only while cloning; Escape, Close and Back still cancel.

Related: https://github.com/stablyai/orca/pull/24581, https://github.com/stablyai/orca/pull/23430

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Nawapat Buakoet <nawapat.b@covest.finance>

* fix(editor): keep chat visible after closing Markdown tabs

Count remaining unified chat tabs before clearing workspace selection during editor close.

Related: https://github.com/stablyai/orca/pull/24273, https://github.com/stablyai/orca/pull/23760

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Wooseong Kim <innocarpe@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* Honor typed starting numbers in chat ordered lists

Forward the existing parsed ordered-list start attribute to both chat Markdown renderers.

Related: https://github.com/stablyai/orca/pull/19765

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Frederic Barthelemy <git@fbartho.com>

* Normalize base source once while checking changed-code diagnostics (#24557)

Reuse the exact existing moved-code matching algorithm with run-local lazy normalization of immutable base source blocks across diagnostics.

* Support real OpenCode sessions in native Chat (#24647)

* 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

* Update native chat settings contract for both OpenCode agents

* fix(native-chat): reconcile bounded OpenCode transcript reads

* fix(native-chat): dispatch OpenCode questions safely

* fix(native-chat): keep native discovery and transcript windows current

* feat(accounts): link standalone GLM Coding Plans (#24618)

* feat(accounts): link standalone GLM Coding Plans

Adapt the reviewed GLM accounts contribution to current main, retain Antigravity behavior, guard late credential results, expose storage protection, and redact quota errors.

Co-authored-by: Luchong <lu740528977@gmail.com>

* fix(accounts): retain GLM credential results during quota refresh

* fix(accounts): make GLM credential editing desktop-only

* fix(accounts): mirror the host GLM site in paired clients

* fix(accounts): report unknown GLM host details and split web settings tests

Apply the independently reviewed Accounts correction from697284a without the v2 adapter commits. Preserve the saved-key store and serialized write behavior.

* fix(zcode): ship required GLM account translation entries

* chore: record GLM reconciliation hook validation

* chore: validate installed GLM commit hooks

* test: complete GLM account fixtures and web API inventory

---------

Co-authored-by: Luchong <lu740528977@gmail.com>

* fix(native-chat): route transcript requests through shared SQLite worker

* fix(ci): prevent concurrent pnpm refresh during mobile typechecks (#24776)

* fix(ci): run mobile typechecks without concurrent dependency refresh

* test(ci): check effective Linux E2E package list

* test(ci): preserve the mobile production compiler barrier

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* test(terminal): restore the live fish fixture prerequisites (#24947)

A restored pane waits for the initial status replay before subscribing to
PTY output. This fixture never settled that replay, so fish printed its
mode-2031 arm before the renderer connected. Its PTY API also omitted the
reset-input listener required by the serializer, aborting attachment.

Settle and dispose the existing startup-snapshot registration and provide
the same reset-listener mock used by the other PTY tests. The real fish
child-stdin assertions and timeouts remain unchanged. No production change.

* fix(shortcuts): defer TUI editing chords in terminal-first mode (#24640)

Restack the original focused change onto current main, preserving every owned source and test blob and the merged CI contract and journal cleanup fixes.

Original-commit: f707cde14ab8cb2670b4076916cdfb7e28c79983
fix(shortcuts): defer TUI editing chords in terminal-first mode

Original-commit: 0c6348e49e0d968e0170961d294a018adceaa9f3
docs(shortcuts): describe deferred preview terminal chords

Original-commit: be62c1b6c6b305cb8091161c1ccdd943a6e1d470
Align worktree history shortcut metadata with terminal conflict policy

Restacked-from: be62c1b6c6b305cb8091161c1ccdd943a6e1d470
Restacked-onto: f7b1f9d8be14d12244957ee0b710a3482ec62383

* Register supervised Qoder China and Qwen Code (#24616)

* Add Qoder session history and search with real CLI coverage

* Allow the real Qoder marker file to end with a newline

* Keep Qoder tool output out of history previews and search

* Keep Qoder search pages readable by older clients

* Verify persisted Qoder history after a real generated and resumed task

* Negotiate Qoder filters before searching an older execution host

* Combine search client imports for the CI plugin gate

* Keep the relay search oracle aligned with legacy agent filtering

* Register supervised Qoder China and Qwen lifecycle integration

* Cover Qoder China mobile assets and mixed-host resume gates

* Verify Qoder provider tags against the older released wire parser

* Verify China and Qwen keep independent Windows hook scripts

* Verify Qoder registrations against the installed older Windows release

* test(qoder): align search capability contracts and pin old-host fencing

* fix(qoder): rank exact picker identities and command aliases first

* test(qoder): preserve the regional CLI shared icon expectation

Keep the full bundled-asset and no-remote-image checks, with an explicit
shared-logo basename for Qoder China. The map also works with older
catalog type unions.

* fix(qoder): align China catalog entry with fallback order

---------

Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>

* Use the measured pnpm lookup policy automatically in hosted root CI (#24951)

* Select lookup automatically for the measured hosted root-install profile

* Record hosted automatic-mode cold cache publication proof

* fix(native-chat): keep OpenCode history usable at read limits

Continue past failed database probes while preserving discovery cancellation.
Verify rows displaced by a capped tail before deciding whether to replace history.
Represent oversized v1/v2 rows with the existing omission text and stable cursors.

* Continue Antigravity IDE and 2.0 history in new CLI conversations (#24692)

* feat(antigravity): bridge IDE history into new CLI conversations

* fix(antigravity): preserve fresh-launch model and environment for IDE references

* fix(antigravity): forward IDE history opt-in through desktop IPC

* fix(antigravity): rebuild remote IDE reference startup on its host

* fix(antigravity): register IDE continuation action labels

* fix(antigravity): confine IDE references and bound metadata reads

* fix(antigravity): localize IDE continuation badges

* Preserve scanner service cache assertions and refresh Antigravity opening metadata

* Preserve Antigravity opening joins and target folder runtime authority

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5029f9217f64970271a5b7f69d3f823

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddbc08fe1179f8bd964fff3dcb0e5d020
Original-topic-commit: 588117b5cf1dd57e2310c80068e3ddcc9f9044ec
Original-topic-commit: dedd4f8c862a961393977dc3c2547f049391b509
Original-topic-commit: 6645dae104ee5271139fcc0e9564d9223b74835d
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021c7ef1436ca606559e9d786580a84dc

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237db191c1e6280001c7e0563f3ca8846


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

---------

Co-authored-by: mrcha033 <mrcha033@users.noreply.github.com>
Co-authored-by: xodmd45-ctrl <xodmd45-ctrl@users.noreply.github.com>
Co-authored-by: Luchong <lu740528977@gmail.com>
Co-authored-by: Orca Integration Recovery <orca-validation@invalid.example>
Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* Drain removal fixture jobs before resetting and deleting their records (#24977)

* Reuse measured Electron preparation for current Terminal Perf refs (#24968)

* feat(jcode): add Jcode as a supported TUI agent with managed hooks

Ports PR #10521 onto current main: agent catalog, managed hook service,
agent-status listener, session resume, AI Vault parser, per-pane daemon
isolation, and Source Control AI support.

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

* feat(jcode): evidence-backed status pipeline (turn_start, live pre_tool, questions)

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): register vault fixture, dynamic model discovery, script refresher

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): keep finished-turn detail so completion notifications fire

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): show the prompt in agent rows instead of jcode's repainting title

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): pre-warm the per-pane daemon so a cold runtime dir cannot time out

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): stop the OSC color skip from crashing every pane connect

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* refactor(jcode): fold three reverse-scan copies into one, reuse the shared hook POST

The jcode journal reader, the Claude transcript reader and the Command Code
transcript reader each carried their own copy of the same reverse chunked line
scan; they now share one tested helper. jcode's managed hook script drops its
hand-rolled curl for buildPosixAgentHookPostCommand, which also gains it the
raw-JSON transport and the --noproxy guard the bespoke copy was missing.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): narrow dynamic reads with predicates, pin the Windows hook shape

CI's anti-slop audit rejects Reflect.get: parse dynamic input into a named type
instead. Adds Windows script-shape tests too, since Windows is the platform this
change could not be exercised on directly.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): measure the gate against the synchronous path, not the clock

The absolute 1s bound was the flake CI shard 3/8 hit: it is tight enough to catch
a synchronous gate on an idle laptop and too tight on a loaded runner. Measuring
the same POST both ways on the same machine makes the claim a ratio, which is what
the test is actually about. Mutation-checked: a synchronous gate reads 6120ms
against a 1517ms observer.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): drop the wall-clock gate assertion

The bound was machine-speed sensitive and flaked on CI shard 3/8 at 1525ms. The
structural assertions (detached gate branch, foreground observer branch) and the
no-hang stdin drain cover the same contract without a timer.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): address CodeRabbit review on #22539

- Windows posted no payload at all: the shared builder reads `payload@-` from
  stdin, which the gate has already drained and observer hooks never receive, so
  every Windows event was dropped. Write the env var to a temp file and pipe it.
- The daemon pre-warm never fired for daemon-host spawns, which is the default
  local path; it now runs there too, and from the final env so the daemon gets the
  hook port and token.
- A failed runtime-dir mkdir took down every local terminal, jcode or not.
- removeJcodeManagedHooks matched the raw line, so a user hook whose comment
  mentioned the managed script was deleted; matching on Windows never worked.
- A managed entry left by a copied home or a platform switch is now repointed
  instead of being reported as user-owned forever.
- The OSC colour skip only checked launchAgent, so a command- or telemetry-named
  jcode pane still leaked the reply into its composer.
- `['hooks']` and a commented scalar are recognised, instead of appending a
  second [hooks] table that makes jcode reject the whole config.
- A failed tool's error is marked as tool output rather than agent prose.
- The vault keeps a session's stored name, counts its tokens, and skips
  background_task and [Scheduled task] turns.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): read the journal once per turn, key prompts by byte offset

The jcode prompt reader ran a synchronous bounded file scan plus a JSON
parse on every hook event. jcode blocks on pre_tool, so a turn that ran
four tools charged the user eight scans of latency it did not need — the
prompt cannot change inside a turn.

- Cache the journal read per pane, refreshed on the turn boundary that
  can change it. The cache holds the whole evidence record, since
  hasExplicitUserPrompt needs the transcript-evidence flag and not just
  the text, and it joins the existing pane-scoped lifecycle (close,
  rename, reset) rather than living in a module singleton.
- Key a journal prompt by its absolute byte offset instead of its
  region-local line index. The backward scan windows the file from EOF,
  so appending shifted every boundary and reminted the key for a prompt
  that never moved; a repeated turn_end then slipped past the same-hash
  dedupe as a second done event with duplicate telemetry.
- Pin the platform in the daemon pre-warm tests. The pre-warm is a no-op
  off POSIX, so the dedupe and retry cases would have passed vacuously
  on a Windows runner.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): quote the managed hook path, drop tools from patch prompts

Three fixes, all on paths this PR could not exercise locally.

The managed hook command was stored as a bare path. jcode tokenizes that
string shell-style before exec'ing it directly (parse_hook_command,
crates/jcode-terminal-launch/src/lib.rs): unquoted whitespace splits, and
every unquoted backslash is consumed as an escape. So on Windows
`C:\Users\me\.orca\agent-hooks\jcode-hook.cmd` reached exec as
`C:Usersme.orcaagent-hooksjcode-hook.cmd` and no hook fired at all, and a
POSIX home with a space split into two arguments. Store the path
single-quoted (verbatim, backslashes included), falling back to double
quotes for a path containing a single quote. Existing bare entries are
already repointed by the stale-key path, and getStatus accepts both forms
so the repair is not reported as a user-owned hook. The quoting helper was
previously dead code that only tests called; the three production sites
now use it. isJcodeManagedCommand also normalizes separators, since a
`/`-only needle never matched a Windows entry.

Commit-message generation feeds a staged patch to `jcode run` as the
prompt — attacker-influenced text — while jcode's default profile exposes
shell, read, write, and MCP. Pass `--tool-profile none`, which resolves to
an empty allowed-tool set in jcode's config (base_allowed_tools), matching
the read-only posture claude (plan) and codex (read-only) already take.

docs/reference/jcode-hook-events.md was never actually in this PR: the
repo ignores docs/** and tracks reference docs by allow-list only, so the
captured-payload evidence four source comments point at was silently
dropped. Allow-list it.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* test(jcode): pin the tool-profile flag in the generation plan too

The argv assertion lives in two places; --tool-profile none only landed in
one, so the plan test still expected the unrestricted argv.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(text-generation): refuse an oversized argv prompt on Linux too

The pre-spawn size guard only ran on Windows. Linux caps a single argv
entry at MAX_ARG_STRLEN (32 pages, 128 KiB on a 4-KiB-page host) and
execve fails with E2BIG past it, so an agent that delivers the whole
prompt as one argument — jcode, and the other argv-delivery agents —
failed on a large staged diff with an error the user could not act on.

The cap is per-argument and in bytes, which is why it is not the Windows
line budget: 40k chars trips Windows and is nowhere near the Linux limit,
so folding them together would have refused prompts Linux runs fine.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): register in main's remote-installer guard, drop our duplicate

Rebasing onto 853 commits of main surfaced two things the earlier branch
had hidden.

main already owns a guard for the issue-#7253 bug class
(`remote-hook-service-registry-coverage.test.ts`). This branch had added a
second, near-identical one — a parallel implementation of a test that
already existed, which is what AGENTS.md's reuse rule is about. Deleted
ours and registered jcode in main's, which is the one that has kept pace
with every agent added since.

Also fixes a missing separator in the mobile icon map. `pnpm tc` does not
cover `mobile/`, so only the session-route closure suite caught it.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* fix(jcode): decompose the four files jcode pushed over max-lines

Adding an agent tipped four modules past their line budget. AGENTS.md
forbids a `max-lines` disable or a per-file bump, so each is split on a
real seam rather than silenced:

- agent-catalog.tsx keeps `AgentIcon`, which 70+ files import, and the
  rows move out. The rows alone exceed the 300-line budget a `.ts` file
  gets, so they follow the primary/secondary split this repo already uses
  for commit-message agent specs.
- getAgentResumeArgv -> agent-resume-argv.ts, re-exported so the 18 call
  sites keep one import path.
- isDiscoverableSessionFile/pathSegments -> session-file-discovery.ts.
- remoteCodexSources -> remote-session-scanner-codex-sources.ts; Codex is
  the one remote agent with two CODEX_HOME roots.

Also:
- Records the readiness-census baseline jcode now needs. main added that
  gate while this branch was out; the fixture is the recorded 74-case
  matrix, not a hand-written one.
- Restores two entries a rebase resolution silently dropped from
  config/tsconfig.cli.json (gitlab/project-ref-parser,
  startup/shell-path-probe). Nothing to do with jcode; losing them was a
  conflict-resolution mistake on this branch.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>

* feat(native-chat): open structured chats on the paired Orca server that owns the workspace (#24205)

* fix(native-chat): a host admits structured sessions by client capability, not its own chat setting

A host's experimentalStructuredNativeChat decided whether any paired client could reach
agentSession.* at all, and whether session.tabs.* showed it structured tabs. That setting is the
host user's own launch preference: whether a new agent opens as a chat or a terminal is decided by
whoever launches it. Using it as admission control meant a client whose own preference was
"structured chat" was refused on a host whose preference was "terminal", and chats opened while
the setting was on were withheld from mobile once it was turned off.

The gate now asks one thing: did the client advertise agent-session.structured.v1 (in-process
callers negotiate nothing and are always admitted). Tab projection and restore follow the same
rule. With the setting no longer gating anything, the separate cleanup gate (close, cancel,
unsubscribe, release), which existed only so those kept working after the setting was switched
off, is identical to the main gate and is folded into it. The settings listener that republished
tabs when the setting changed is removed, since projection no longer depends on it.

The host setting still picks the default for launches that start on the host itself
(agent.launch from mobile, orchestration worker-start).

* fix(native-chat): the desktop declares structured chat support to paired hosts

The desktop renderer advertised agent-session.structured.v1 (and the Claude, turn-item and
background-task capabilities that go with it) to its own main process but not to a paired Orca
server. The server therefore refused every agentSession.* call from the desktop and stripped
structured chat tabs out of the tab list it published to it, so a structured chat running on a
paired server never appeared on the desktop, even though the renderer already mirrors a host's
agent-session tabs and drives each one against the server that owns its workspace.

The same renderer reads structured chats on either host, so the remote Electron list now carries
the same structured-session capabilities as the local one, and the capability test pins that
nothing is advertised only locally.

* feat(native-chat): open structured chats on the paired server that owns the workspace

With the structured-chat default on, an agent launched in a workspace that lives on a paired Orca
server always opened as a terminal (or the terminal-backed chat view). Three things kept it off
the structured path: the launch check refused every host but this machine, a remote workspace was
handed to the host-published terminal path before the structured route was even considered, and
the structured launch pipeline sent create and every follow-up call to this machine's runtime.

A workspace's owning runtime is fixed, so the pipeline now derives it from the workspace instead
of assuming this machine (structured-agent-session-owner.ts, the same derivation the chat pane
already uses to read a session). The launch intent carries that target; the pre-create support
check, create, the publication check and fence read, the launch prompt send, held option picks,
the "focus this chat" marker, the placeholder tab's host, and tab close/purge all use it.

The launch check now accepts a paired server and asks that server's own capabilities (read from
the status the client already cached for it) rather than this machine's. An SSH workspace stays
terminal-backed: no Orca runtime runs there. The host still answers createSupport before
anything is created, so an older server that refuses shows the failure in the chat tab.

A chat the user closed before its create landed is now also retired on the paired server when it
publishes, as the local sync already does. Orchestration workers placed on another runtime are
unchanged: federation creates terminal agents only.

* fix(native-chat): negotiate client-chosen launch mode so released phones and old servers keep terminals

Hosts advertise agent-session.structured.client-launch-mode.v1: they admit
structured sessions by client capability alone. A remote client that does
not advertise it (phones released before agent.launch) asks createSupport
to pick the launch mode, so the host keeps answering that with its own
setting, exactly as before. Cleanup methods keep their own named gate so a
future admission condition cannot make close or cancel refusable.

* refactor(runtime): keep the Electron client capability list in its own module

protocol-version.ts is at its line budget; the list is what the desktop
advertises to paired hosts, not the host's own contract.

* fix(native-chat): the desktop declares it picks each launch mode itself

Paired hosts and the desktop's own main process then answer createSupport
by the workspace rather than by their own chat setting.

* fix(native-chat): pin each structured chat to the host it was launched on

- Route: a paired server opens a chat only when it advertises the
  client-chosen launch mode; an older server keeps its terminal. Its
  capabilities come from the store's host status, not the compatibility
  cache that is empty after boot or reconnect.
- A launch command override is this machine's: the route applies it only
  locally, and a host's createSupport refuses on its own override.
- The owning host is resolved once, from the same value the route used,
  and carried on the launch intent, its persisted record (legacy records
  load as local), the provisional tab and every mirrored chat tab. Close,
  purge, retry and reload read it instead of re-deriving it from a
  worktree id two hosts can share; an owner that cannot be named refuses.
- Cancellation tombstones record their host: only that host's
  authoritative inventory retires one, restored cleanup closes it there,
  and a paired host's tombstone expires after 30 days if it never answers.
- A paired server's frame settles launches it published, as the local
  inventory already does for this machine.

* fix(native-chat): a paired server that declines a chat opens its terminal instead

createSupport only reads, so both of its non-answers are settled before
anything is created:
- A paired server that answers it cannot run the chat (a WSL repo, a
  Claude account mismatch, its own launch command override) closes the
  chat tab and opens the terminal the route would have chosen, with a
  notice saying why. This machine's own decline stays a failed chat.
- A host that could not be asked closes the chat tab and leaves one
  failure toast, instead of a lingering "could not confirm" chat.

* test(native-chat): a provisional chat carries its launch's host and hands pre-create failures on

* chore(native-chat): justify the two type assertions this change's lines touch

* test(native-chat): state why each staged test fixture is cast

* fix(native-chat): chats that already exist keep showing whatever the chat setting says

The structured chat setting decides only what new agents open as. With it
off, this machine's structured chats used to be hidden while the host,
which no longer reads the setting, still reported them to the workspace
activation gate, so a workspace holding only a chat opened empty. The
local chat mirror and its startup restore now run whatever the setting
says, the continue-after-restart offer follows the chats that exist, and
the setting's copy says it applies to new agents.

* fix(native-chat): the browser client keeps its host terminal on paired servers

A browser client whose own preferences turn structured chat on took the
structured route for every paired-server workspace, but its handshake
never says it reads structured sessions, so the server refused the chat
and the user got a failed chat tab where a host terminal used to open.
The route for a paired host now also asks what this client advertises to
it: the desktop's list does, the browser client's does not. Its handshake
list is now a named constant the route reads, so the two cannot drift.

The chat setting's copy now says it runs on paired Orca servers too;
WSL and SSH hosts still use terminal chat.

* fix(native-chat): a retried launch a paired server declines opens its terminal too

A launch restored after a reload settles only through its Retry, so a
declining paired server left a failed chat there while a first launch got
the server's terminal and a notice. The chat's Retry now hands the same
pre-create failures to the same replacement, carrying the prompt the
launch had staged.

* refactor(native-chat): a paired host's cancelled-chat record ends on its 30-day TTL

The paired census re-read a host's whole inventory after every
authoritative frame to retire tombstones, and a tombstone restored after
a reload needed a second such frame, so in practice it retired nothing.
A tombstone guards a random session id and is inert once stale; the chat
is already closed on its host whenever a frame shows it. The census, its
trigger in the mirror layer and its cleanup are removed; the owner-scoped
tombstones, close-on-sight, the TTL and publication marking from frames
stay.

* fix(native-chat): a chat's pane and status read from the host recorded on its tab

The chat pane and its sidebar status still derived the host from the
workspace id, which two hosts can share; a paired chat in a non-active
same-id workspace was read from this machine. Both now read the owner
stamped on the tab, as close, purge and publication already do.

* fix(native-chat): "Resume in chat" follows the terminal resume's host rule

Agent Session History offered "Resume in chat" for a conversation
recorded on this machine into a paired server's workspace, where its
transcript does not exist. A chat now resumes a conversation only on the
host that recorded it, as the terminal resume does, and that host is the
one asked whether it can resume history.

* fix(native-chat): the chat setting says older paired servers keep terminal chat

* test(native-chat): pin that a host advertises the client-chosen launch mode

* fix(native-chat): mirror this machine's chats only where it holds them

Round 1 ran the local chat mirror for everyone so existing chats show
whatever the setting says. That gave every desktop a permanent
session-tabs listener, which turns on the runtime's phone replication
paths, plus two full session-tab censuses at startup, and made the
browser client mirror its remote host a second time.

The runtime now says whether it holds structured chats: its structured
host is built only when saved chats were restored at startup or a client
created one here, and it announces the moment one is built. The mirror,
the startup restore and the continue-after-restart offer run only when
the setting launches chats or the host holds some, and never in the
browser client. A chat a paired client creates here with the setting off
still appears at once. The chat behaviour settings show wherever chats
exist, and the setting's copy says it picks what new agents open as. The
toggle-off teardown this made dead is removed.

* test(native-chat): route a paired-server launch over the capability lists both sides really advertise

* test(native-chat): record install listeners without a cast

* fix(native-chat): a paired server admits a chat before any of it exists here

The desktop opened a paired server's chat tab, launch record, queued
prompt and focus intent before asking the server, so a "no" needed a
replacement that undid and redid all of it, and every piece it missed
was a bug: the workspace deselected, the caller told "failed" while a
terminal ran its prompt, the caller's arguments and other queued prompts
lost, and a create whose reply was lost treated as never sent.

A paired launch now asks the server first and commits nothing until it
answers. Admitted opens the chat as before. Declined runs the caller's
own launch as the server's terminal, with the existing notice (a resume
fails instead, having no terminal equivalent). Unreachable opens nothing
and names the server in one toast. The new-tab launcher reports the
host's surface for paired workspaces, as it did before paired chats,
with the prompt delivery of whichever surface got the prompt. The
replacement and its error classes are gone, and the probe inside a
launch is back to its old meaning: a "no" is a failed chat with Retry,
and no answer leaves "Could not confirm" with Retry and the prompt kept,
here as on this machine.

* fix(native-chat): mirror this machine's chats only once it holds one, not once its host is built

Session history, resume preparation, terminal resume commands and replay-safe phone launches all
build the structured host for users who never had a chat, which turned on the chat mirror and the
structured-only settings rows until the next restart. The signal is now derived from the host's
records (or a records file still owed its import) and pushed when the first chat is restored or
created. A throwing listener no longer fails the install that fired it.

* fix(native-chat): a fork's reveal never seeds a terminal beside the surface the launcher opens

Forking into a paired-server workspace revealed it as if nothing would open there, so the reveal
created a blank host terminal beside the forked chat (and beside a forked agent terminal on main).
The launcher always opens the fork's surface itself, so the reveal now says so for every surface,
as the fix-checks launch already does.

* fix(native-chat): a declined direct launch keeps the caller's CLI args; an unreachable resume toasts once

When a paired server declines a "Fix checks" chat in a new workspace, the terminal that opens
instead now carries the recipe's saved CLI arguments, launch platform and launch source, as the
terminal route did. "Resume in chat" to a server that cannot be reached showed the admission's
"Could not reach" toast and the vault's generic one; the admission marks its failure notified and
the vault adds nothing.

* fix(native-chat): a declined background create opens its terminal without switching workspaces

Since #23974 a worktree create the user moved away from must not pull them onto the new
workspace. When a paired server declined that create's chat, the fallback terminal opened as a new
agent tab, whose host create selects the workspace. The create now opens its own agent terminal the
way main's background branch does: in place from the request's startup plan (so its CLI args carry),
without selecting the workspace. A create the user is still watching keeps the new-tab fallback.

* test(native-chat): name the launch's host in main's new outbox fence test

Main's new staging-failure test calls settleStructuredAgentLaunchPrompt without the target this PR
made required; it is a local launch, as in the sibling tests.

* fix(native-chat): a paired server's new chat shows no model until the server reports the one it started

A chat on a paired server starts with the server's saved model and options, but the picker showed
this desktop's saved selection (or the catalog default) until the server reported a model, and a
pick made in that window was remembered on the server under that guessed model. A paired launch
now carries no desktop seed, and until the server reports its model the picker names no model and
takes no picks. Local chats are unchanged.

* test(native-chat): seed the paired repo without a cast

The repo literal already satisfies Repo, so the changed-lines cast gate has nothing to excuse.

* feat(native-chat): createSupport reports the saved selection a new chat on this host starts with

A chat on a paired server starts with the server's saved model and options, which the desktop could
not read, so its picker showed a guess. createSupport's answer, which the desktop already waits for
before a paired launch, now also carries that seed as a new optional field (older clients ignore it).
Create and createSupport read it through one resolver so they cannot drift.

* fix(native-chat): a paired server's new chat shows the selection the server will start it with

The paired server now names its saved model and options in the admission answer the desktop
already waits for. That seed goes into the launch intent and its persisted record, so the picker
shows the server's model at once, stays pickable like a local chat, and remembers picks on the
server under that model; a reload shows the same. The locked picker remains only for a server too
old to name a seed.

Also moves host admission and launch-outcome tracking into their own modules: the latest main
merge left structured-agent-session-launch.ts over the max-lines limit.

* test(native-chat): expect the launch intent's new seed argument in exact-call assertions

* refactor(protocol): move the Electron remote client capability list into its own module

Merging main left protocol-version.ts one line over the max-lines limit on this branch. The list of
capabilities the desktop advertises to a paired host moves, unchanged, into
electron-remote-runtime-client-capabilities.ts, the module the next PR in the stack already uses
for it; importers point there.

* fix(native-chat): a paired chat with no saved server model is pickable; Retry shows the server's current seed

A server whose user never saved a chat model sends no seed, and the desktop showed a locked,
model-only picker for it, although that is the common case: no server that can admit a paired chat
predates the seed field. Such a chat now behaves like a local chat with no saved model: the CLI
default, pickable. The lock and its snapshot helper are gone.

Retry kept the first admission's seed while the create probe, which already runs on every attempt,
reported the server's current one and dropped it. The probe's seed now replaces a paired launch's
seed and the picker's, so a retried chat shows what its create will run.

* test(cross-version): stub the launch seed resolver createSupport now reads

* test(protocol): pin the desktop capability divergence against what a paired server receives

Every paired transport sends the shared remote base plus the Electron list, so the
divergence test now compares that union with the renderer's local list instead of
the declared Electron list. A capability added only to the shared base can no
longer slip past it. The two base-only capabilities it surfaced are recorded:
skills.install-result.v2 has no local caller; the authoritative-inventory label is
read by the local tabs sync but dropped by main, and is marked unsettled.

The turn-item and both background-task-stop capabilities were already sent through
the shared base, so the Electron list no longer repeats them. The wire set is
unchanged; this PR's real change on the wire is structured.v1, the Claude
structured capability and the client launch-mode capability.

* fix(native-chat): the desktop tells its own host it picks each launch mode, so retrying an existing chat works with the setting off

* docs(native-chat): name the real exit for the released-phone createSupport rule

* fix(native-chat): the route reads the capabilities a paired host actually receives

The renderer decided whether a paired host would admit a chat from the desktop's Electron list, but
every desktop transport sends that list plus the shared remote base. They agreed only because the
route's checks happened to sit in both. The route input is now built with the same
remoteRuntimeClientCapabilities the transports use (the browser client already sends its list as is),
and a test pins each against the real handshake.

* test(cross-version): a released client still gets the host-setting createSupport answer; a launch-mode client gets supported plus the seed

* test(native-chat): let main's child-records test resolve each chat's owner

Main's new test mocks worktree-runtime-owner with only the runtime environment id, but the status
projection in this PR also resolves each structured chat's owner from the worktree. The mock keeps
the module's real exports and overrides only what the test pins.

* fix(deps): take #24204's lockfile that the merge reverted

* test(native-chat): let the Codex child-approval e2e unit test resolve each chat's owner

Its worktree-runtime-owner mock exported only the runtime environment id, but this PR's status
projection also resolves each structured chat's owner from the worktree. The mock now keeps the
module's real exports and overrides only that id, as structured-child-records-switch does.

* test(native-chat): move the close-race launch cases into their own file

Merging main added launch tests on both sides and took structured-agent-session-launch.test.ts past
the 800-line limit. The three cases where a tab close races a launch move to
structured-agent-session-launch-close-race.test.ts, with the same setup the other split launch
suites copy.

* feat(cli): add --unread/--read to orca worktree set

Pass validated read/unread flags through the existing workspace metadata update.

Contributor attribution correction: the earlier Ctrl+M squash message placed its related link after the co-author footer, so GitHub did not recognize all contributors. Preserve that credit here for https://github.com/stablyai/orca/pull/24100 and its related contribution https://github.com/stablyai/orca/pull/24143 .

Related CLI link validation retained from https://github.com/stablyai/orca/pull/20036 .

Co-authored-by: Raz Shlomo <12373339+razshlomo@users.noreply.github.com>
Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
Co-authored-by: Ahmed Nagy <ahmednagy25t@gmail.com>

* feat(cli): configure external worktree visibility per repo

Pass show/hide/inherit to the existing repository visibility update.

Related: https://github.com/stablyai/orca/pull/23025

Preserves CLI link validation from https://github.com/stablyai/orca/pull/20036 and read controls from https://github.com/stablyai/orca/pull/22714 .

Co-authored-by: Neil <neil@stably.ai>
Co-authored-by: KAPUIST <thsxornjs12@gmail.com>

* perf(git): reuse queries and stop canceled catalog scans (#24923)

* perf(git): reuse queries and stop canceled catalog scans

* refactor(git): check relay filesystem error codes

* test(e2e): require exact Linux package tokens (#24995)

* Allow cold node-pty setup on Windows ARM CI (#24997)

Keep a ten-minute bound only for node-pty rebuilt on Windows ARM CI hosts; all other node-gyp calls retain five minutes. Cold setup in two completed ARM jobs left compilation less than a minute.

* fix(jcode): harden Windows hooks and negotiate remote history (#24998)

Redirect the managed Windows payload file into curl instead of starting
pipeline shells, register native Windows delivery coverage, and document
Jcode v0.89.0+ as the upstream launcher requirement for invisible hooks.

Negotiate Jcode history in both directions with mixed-version Orca hosts,
preserving supported search filters and old-client response compatibility.

Co-authored-by: czzczz <chanzrz_zbf@foxmail.com>
Co-authored-by: JianJia2018 <39438074+JianJia2018@users.noreply.github.com>

* fix(git): preserve SSH review context and worktree ownership (#24945)

* fix(git): preserve SSH arguments and guard background writes

* test(terminal): settle fish fixture startup readiness

* fix(git): preserve bare UNC SSH paths

* fix(git): unescape shell operators in Windows SSH paths

* test(runtime): settle removal writes before fixture cleanup

* test(git): skip optional OpenSSH probe when unavailable

* perf(git): skip equal-tip reads and bound relay discovery

* test(processes): ratchet the removed relay Git spawn

* test(shells): wait for initial zsh output before sending input

* Fix SSH review context and recover Git maintenance cleanup safely

* Keep relay Git compatibility fixtures outside shared client projects

* Fence superseded maintenance and preserve mixed-version session search

* test: model Git child termination and search catalogs

* test: retain catalog authority over history flags

* fix(opencode): keep shared-session TUI activity with its owning pane (#24962)

* fix(opencode): bind legacy attach status to its TUI pane

Adapt the official v1 TUI API to the existing pane reporter and learn structural session identity only after execution-host admission. Preserve known creators, gate new reports to capable hosts, and register the legacy TUI entry without changing user settings or overlay targets.

Credit werlang for the same-directory investigation in #22838 and #22847; no source from that PR is copied.

(cherry picked from commit d9eeb028a23a66b78b08538a5ebf6052499ec24f)

* fix(opencode): fence legacy TUI viewers before creator attribution

Use the execution host's existing admission policy on the physical TUI
pane and launch token before rewriting to a session creator or mutating
the launch-token cache. Preserve live viewers, frozen server stamps and
OpenCode 2 behavior.

Add HTTP regressions for retired panes, closed tabs, replaced tokens and
relay retirement, plus current-launch, alias and compatibility controls.

Follow-up to #22838 and @werlang's original #22847; preserves the original
intentional creator attribution without copying that PR's source.

* fix(opencode): preserve current plugin exports in legacy TUI wrapper

Keep the current server, setup, id and other default export entries when
adding the legacy TUI entry. Cover their preservation and seed the ACL
fixtures with the separate installed TUI/config bytes.

This completes the current-main port of the legacy identity replacement
for issue #22838, carrying @werlang's original PR #22847 contribution.

* fix(opencode): retry timed-out SSH plugin updates (#24666)

Preserve bounded retry behavior and the current-main status-envelope fields.

Original-PR: #24124
Reviewed-source: 103144f9c5029f9217f64970271a5b7f69d3f823

Co-authored-by: Justas Brazauskas <brazauskasjustas@gmail.com>

* fix(opencode): keep Go credentials private and resolve backend keys (#24615)

Preserve the complete credential storage, migration, IPC, Settings and rate-limit refresh change alongside standalone GLM plans, current-main database diagnostics and the reviewed unknown-backend environment correction. Keep native discovery cancellation third and selected environment fourth.

Original-topic-commit: 7903f1cddbc08fe1179f8bd964fff3dcb0e5d020
Original-topic-commit: 588117b5cf1dd57e2310c80068e3ddcc9f9044ec
Original-topic-commit: dedd4f8c862a961393977dc3c2547f049391b509
Original-topic-commit: 6645dae104ee5271139fcc0e9564d9223b74835d
Original-topic-commit: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-from: a25b80c02c1af7830b0e6a65e72d965b3ad98276
Restacked-onto: b032867021c7ef1436ca606559e9d786580a84dc

Co-authored-by: kespineira <kespineira@users.noreply.github.com>
Co-authored-by: kevimux <kevimux@users.noreply.github.com>
Reported-by: pullfrog
Reviewed-full-source: 849fe093073f4c1606bd65d79a0c725d955d0d1f
Native-helper-source: 80dbe23237db191c1e6280001c7e0563f3ca8846


Reviewed-full-current-source: 36acb57d44adb3d378c0289c8c15f7da0fda214c

* Skip store notifications when refreshed work items are unchanged (#24530)

An identical forced refresh previously returned an empty Zustand patch, creating a new root state and notifying every subscriber. It now returns the current state after renewing the existing freshness timestamp. Real row or metadata changes stil…
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.

[Bug]: OpenCode panes that share a directory attribute every session to the first pane that started the shared server

3 participants