Skip to content

fix(terminal): one tab agent icon rule for desktop and mobile - #33963

Merged
AmoabaKelvin merged 16 commits into
mainfrom
mobile-terminal-tab-agent-parity
Oct 27, 2026
Merged

AmoabaKelvin merged 16 commits into
mainfrom
mobile-terminal-tab-agent-parity

Conversation

@AmoabaKelvin

@AmoabaKelvin AmoabaKelvin commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator
Files Added Deleted Net
Test 23 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​276 $\color{#cf222e}{\Huge{\mathbf{−}}}$​170 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​106
Prod 56 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​534 $\color{#cf222e}{\Huge{\mathbf{−}}}$​595 $\color{#cf222e}{\Huge{\mathbf{−}}}$​61

Stacked on #33959: review the commits after f48b6b1.

ELI5

The agent icon on a terminal tab is now picked by the same code on the phone and on desktop. Before, the phone used a simpler order, so after you quit Claude and started Codex in the same tab, the phone kept showing Claude. Desktop also had two drifted copies of its own rule; now there is one.

What Changed

  • One desktop rule. src/renderer/src/lib/tab-agent-from-signals.ts moved to src/shared/tab-agent-from-signals.ts. The real tab strip (use-tab-agent.ts) had its own exported copy of resolveTabAgentFromSignals and resolveLaunchedAgentExitEvidence that had drifted from the tested file. That copy is deleted, and the hook imports the shared rule. The icon mapping agentTypeToIconAgent moved to src/shared/agent-type-icon-agent.ts; agent-status.ts re-exports it.
  • One snapshot entry point. resolveTabAgentFromSignalSnapshot (previously the logic inside open-tab-occupant-agent.ts) calls the resolver with partial signals as a remote client. Open-tab search and the phone both use it. hasObservedTabAgentSignal is shared by the tab strip and the snapshot path.
  • Phone (terminal Fix update checker 406 error and add menu feedback #8, agent-sessions Add card gap and quick-create in worktree dialog #10). resolveMobileTerminalTabAgentId now calls the shared rule with what the phone receives: a hook whose state isn't done counts as live, a done hook as completed, plus the launch agent and the title.
    • Before: hook, then launch agent, then the host's Cursor identity, then title. Launch Claude, /exit, run codex: the phone kept Claude. Now the Codex title takes the pane back from the finished Claude hook, as on desktop.
    • Before: an OMP tab whose hook reports pi showed Pi. Now it shows OMP, the owner.
    • The host's cursor identity (Cursor imports Claude hooks) is fed in as the process signal only when no hook or launch agent owns the tab, the same place the old phone code ranked it.
  • Desktop tab-strip changes (the tested rule replacing the drifted copy):
    • process, title and finished-hook signals are matched against the launch agent when it owns the pane (OMP/Pi);
    • an OMP wrapper title on a Pi-owned pane (or the reverse) no longer counts as another agent taking the pane over;
    • Windows Git Bash prompt titles count as "no agent", so exit detection clears the launch agent there.
    • Sleeping-session identity is kept on shell/exit evidence, as the shipping copy did, so a hibernated agent keeps its icon.

Why

Mobile and desktop disagreed on the icon. Desktop's tested rule wasn't the one the tab strip ran. Moving the pure resolver to src/shared leaves one copy for the tab strip, open-tab search and the phone.

Linked Issue

Part of the mobile/desktop parity audit

Visual Proof

N/A: this adopts desktop's existing rule, resolveTabAgentFromSignals (now src/shared/tab-agent-from-signals.ts).

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Focused desktop tests (use-tab-agent*, open-tab occupant, decision table, hibernation shell-foreground) and mobile src/session. New phone tests cover the reclaim-after-exit and OMP/Pi cases, both failing on the old code, plus the cached-Cursor and unowned-Cursor cases. The decision-table counts match their values before #21714. No device run for this fleet.

AI Disclosure

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Mobile behaviors removed:

  • A hook agent name that isn't a known icon agent no longer passes through as the icon id.
  • A finished hook no longer always beats the title or the launch agent.
  • The host's cursor identity no longer ranks above the title when a hook or launch agent owns the tab.

Not done, and why:

  • The phone gets no process, sleeping-session or sibling-pane signals, so a tab whose only evidence is one of those shows no icon (or the launch agent) on the phone.
  • The phone receives the display title, so a renamed tab can't use the title signal. Fixing that needs a new wire field.
  • The fix(terminal): clear stale agent identity after shell exit #21714 fix (don't bring back a stale sleeping identity after /exit at a local shell) was only ever in the tested copy, not in the shipped tab strip. It conflicts with hibernation, which keeps the wake record on purpose, so the tab strip keeps the shipped behavior and that case stays open.

No wire changes; mixed phone/host versions are unaffected.

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)

@AmoabaKelvin
AmoabaKelvin force-pushed the mobile-terminal-tab-agent-parity branch from 2db1248 to 1c57427 Compare October 27, 2026 01:08
@AmoabaKelvin
AmoabaKelvin marked this pull request as ready for review October 27, 2026 01:08
@coderabbitai

coderabbitai Bot commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Walkthrough

The changes update terminal keyboard-mode tracking, mode-aware input encoding, program-status query handling, selection, viewport, and contrast behavior. They also centralize agent identity resolution and worker-terminal takeover reporting in shared modules. Mobile and renderer callers and tests are updated to use these shared behaviors.

Priority: Normal

Merge Risk: Low · up to 3413d

Open-tab search can show an agent icon after the local terminal has returned to the shell. Align its identity resolution with the tab strip; this is a bounded display inconsistency rather than a blocker.

Pre-merge checks: Passed 4 · Inconclusive 1

Failed Failed checks: 1 inconclusive

Check name Status Explanation Resolution
Docstring Coverage Inconclusive Docstring coverage is 26.19% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surv… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly identifies the primary change: using one tab-agent icon rule across desktop and mobile.
Description check Passed The description covers the required change, rationale, testing, visual proof, scope, compatibility notes, and checklist. It does not include an issue number in the Linked Issue section, but the rest o…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description covers the required change, rationale, testing, visual proof, scope, compatibility notes, and checklist. It does not include an issue number in the Linked Issue section, but the rest of the description is complete and relevant.

Full details: Docstring Coverage

Explanation

Docstring coverage is 26.19% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 42 functions across 50 files. (26 skipped: 26 over the file limit.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@AmoabaKelvin
AmoabaKelvin force-pushed the mobile-terminal-tab-agent-parity branch 2 times, most recently from d2ca9e4 to 37cb375 Compare October 27, 2026 02:20
@AmoabaKelvin
AmoabaKelvin force-pushed the mobile-terminal-tab-agent-parity branch from 37cb375 to f6f9161 Compare October 27, 2026 02:58
- ZWJ emoji width: activate the shared Orca unicode provider instead of plain Unicode 11.
- Contrast floor: share desktop's resolver and background parser; translucent
  backgrounds composite over the published light/dark surface.
- Word selection: use xterm's default word separators via a shared rule.
- Takeover reports: share the 30 s gate and single retry between desktop and mobile.
- Use the shared viewport minimums and default scrollback rows.
Moves the tab-strip identity resolver to src/shared and deletes the drifted copy in use-tab-agent.ts, so the desktop tab strip, open-tab search and the phone all run one rule.
…vidence

The kill that hibernates a pane can report the shell foreground while the wake record is kept on purpose; the tab strip never retired sleeping identity on exit evidence, so the shared resolver keeps that behavior.
@AmoabaKelvin
AmoabaKelvin force-pushed the mobile-terminal-tab-agent-parity branch from f6f9161 to 3413dd0 Compare October 27, 2026 03:24

@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
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 291e6b67-c5b9-409c-806d-bb618400cbeb
Commits

Reviewing files that changed from the base of the PR and between 3347d47 and 3413dd0.

Files selected for processing: 79
  • mobile/src/session/mobile-image-live-input-order.test.tsx
  • mobile/src/session/mobile-terminal-tab-agent.test.ts
  • mobile/src/session/mobile-terminal-tab-agent.ts
  • mobile/src/session/mobile-worker-takeover-send-sites.test.ts
  • mobile/src/session/terminal-field-submit-binding-send.test.tsx
  • mobile/src/session/use-mobile-session-tab-reconciliation.ts
  • mobile/src/session/use-mobile-session-terminal-input.ts
  • mobile/src/session/use-mobile-session-terminal-send-actions.ts
  • mobile/src/session/use-mobile-session-terminal-subscription.ts
  • mobile/src/session/use-mobile-terminal-paste.test.ts
  • mobile/src/session/use-mobile-terminal-paste.ts
  • mobile/src/terminal/document/document-scope.ts
  • mobile/src/terminal/document/document-terminal-double.test-support.ts
  • mobile/src/terminal/document/document-terminal-shape.ts
  • mobile/src/terminal/document/host-message-router.ts
  • mobile/src/terminal/document/host-seams.test.ts
  • mobile/src/terminal/document/mode-mirroring.ts
  • mobile/src/terminal/document/query-reply.ts
  • mobile/src/terminal/document/selection-range.test.ts
  • mobile/src/terminal/document/selection-range.ts
  • mobile/src/terminal/document/selection-state-and-eviction.ts
  • mobile/src/terminal/document/terminal-init.ts
  • mobile/src/terminal/document/terminal-theme.ts
  • mobile/src/terminal/mobile-terminal-query-reply.test.ts
  • mobile/src/terminal/mobile-terminal-query-reply.ts
  • mobile/src/terminal/terminal-accessory-keys.test.ts
  • mobile/src/terminal/terminal-accessory-keys.ts
  • mobile/src/terminal/terminal-grid-fit.ts
  • mobile/src/terminal/terminal-key-mode-encoding.test.ts
  • mobile/src/terminal/terminal-key-mode-encoding.ts
  • mobile/src/terminal/terminal-webview-contract.ts
  • mobile/src/terminal/terminal-webview-messages.ts
  • mobile/src/terminal/terminal-webview-notification-dispatch.ts
  • mobile/src/terminal/terminal-webview-theme.test.ts
  • mobile/src/terminal/use-terminal-webview-controller.ts
  • mobile/src/terminal/worker-terminal-takeover-report.test.ts
  • mobile/src/terminal/worker-terminal-takeover-report.ts
  • mobile/src/test-support/rpc-recording/adapters/session-terminal-gesture-mount-adapters.ts
  • mobile/src/test-support/rpc-recording/adapters/session-terminal-input-mount-adapters.ts
  • mobile/src/test-support/rpc-recording/adapters/terminal-mount-adapters.ts
  • src/main/runtime/rpc/methods/terminal/terminal-send-method.ts
  • src/main/runtime/rpc/terminal-send.test.ts
  • src/main/runtime/terminal/output/headless-terminal-state.ts
  • src/renderer/src/components/dashboard-popout/preview-terminal-options.ts
  • src/renderer/src/components/plain-ssh-terminal/PlainSshTerminalView.tsx
  • src/renderer/src/components/settings/TerminalAdvancedSection.tsx
  • src/renderer/src/components/settings/TerminalContrastSetting.tsx
  • src/renderer/src/components/settings/TerminalSettingsPreview.tsx
  • src/renderer/src/components/terminal-pane/pty-connection/apply-reattach-payload.ts
  • src/renderer/src/components/terminal-pane/pty-connection/hidden-output-seq-and-skip.ts
  • src/renderer/src/components/terminal-pane/pty-connection/reattach-payload-context.test.ts
  • src/renderer/src/components/terminal-pane/terminal-appearance.ts
  • src/renderer/src/components/terminal-pane/terminal-keyboard-runtime.ts
  • src/renderer/src/components/terminal-pane/terminal-shortcut-policy.ts
  • src/renderer/src/lib/agent-status.ts
  • src/renderer/src/lib/open-tab-occupant-agent.ts
  • src/renderer/src/lib/pane-manager/pane-terminal-options.ts
  • src/renderer/src/lib/terminal-theme.ts
  • src/renderer/src/lib/use-tab-agent-opencode-native-title.test.ts
  • src/renderer/src/lib/use-tab-agent-process-signals.test.ts
  • src/renderer/src/lib/use-tab-agent-sleeping-session.test.ts
  • src/renderer/src/lib/use-tab-agent.test.ts
  • src/renderer/src/lib/use-tab-agent.ts
  • src/renderer/src/lib/worker-terminal-takeover-report.test.ts
  • src/renderer/src/lib/worker-terminal-takeover-report.ts
  • src/renderer/src/runtime/sync-runtime-graph/mobile-terminal-theme.ts
  • src/shared/agent-type-icon-agent.ts
  • src/shared/tab-agent-from-signals-pi-identity.test.ts
  • src/shared/tab-agent-from-signals.ts
  • src/shared/tab-agent-identity-decision-table.test.ts
  • src/shared/terminal-contrast-correction.test.ts
  • src/shared/terminal-contrast-correction.ts
  • src/shared/terminal-kitty-keyboard-mode-tracker.ts
  • src/shared/terminal-modified-enter-encoding.ts
  • src/shared/terminal-title-contrast.ts
  • src/shared/terminal-unicode-provider.ts
  • src/shared/terminal-word-separator.ts
  • src/shared/worker-terminal-takeover-report-gate.test.ts
  • src/shared/worker-terminal-takeover-report-gate.ts
Files with no reviewable changes: 1
  • src/renderer/src/lib/worker-terminal-takeover-report.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 2 remain after this review.

Comment on lines +184 to +198
export function resolveTabAgentFromSignalSnapshot(
args: Omit<
Parameters<typeof resolveTabAgentFromSignals>[0],
'hasObservedAgentSignal' | 'isRemote' | 'processShellForeground'
>
): TerminalAgent | null {
return resolveTabAgentFromSignals({
...args,
hasObservedAgentSignal: hasObservedTabAgentSignal({
...args,
hasCompletedHook: Boolean(args.focusedCompletedHookAgent)
}),
isRemote: true
})
}

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.

Minor · Functional Correctness · Quick win

Supported by static analysis

Script executed:

#!/bin/bash
# Compare the old open-tab-occupant inputs with the snapshot call.
git log -n 3 --format=%h -- src/renderer/src/lib/open-tab-occupant-agent.ts
git diff HEAD~1 -- src/renderer/src/lib/open-tab-occupant-agent.ts | head -120
rg -nP --type=ts -C3 'shellForeground' src/renderer/src/store/slices/pane-foreground-agent.ts

Repository: stablyai/orca

Length of output: 2057


Script executed:

set -o pipefail
printf '%s\n' '--- shared resolver ---'
nl -ba src/shared/tab-agent-from-signals.ts | sed -n '1,240p'
printf '%s\n' '--- snapshot call sites ---'
rg -n -F --glob '*.ts' -e 'resolveTabAgentFromSignalSnapshot' -e 'resolveOpenTabOccupantAgent' src mobile
printf '%s\n' '--- open-tab caller ---'
nl -ba src/renderer/src/lib/open-tab-occupant-agent.ts | sed -n '1,130p'
printf '%s\n' '--- tab-strip resolver ---'
rg -n -F --glob '*.ts' -e 'resolveTabAgentFromSignals' -e 'processShellForeground' src/renderer/src/lib src/renderer/src/components | head -120

Repository: stablyai/orca

Length of output: 22183


Pass desktop shell evidence to the snapshot resolver.

resolveOpenTabOccupantAgent reads shellForeground but does not pass it to resolveTabAgentFromSignalSnapshot. That resolver forces isRemote: true, so idleIdentitySuppressed cannot clear a completed-hook identity for a local shell return. Open-tab search can therefore retain the completed-hook icon while useTabAgent clears it.

Make isRemote and processShellForeground optional snapshot inputs. Pass desktop values and keep the current mobile defaults.

Suggested fix
 export function resolveTabAgentFromSignalSnapshot(
   args: Omit<
     Parameters<typeof resolveTabAgentFromSignals>[0],
     'hasObservedAgentSignal' | 'isRemote' | 'processShellForeground'
-  >
+  > & {
+    isRemote?: boolean
+    processShellForeground?: boolean
+  }
 ): TerminalAgent | null {
   return resolveTabAgentFromSignals({
     ...args,
     hasObservedAgentSignal: hasObservedTabAgentSignal({
       ...args,
       hasCompletedHook: Boolean(args.focusedCompletedHookAgent)
     }),
-    isRemote: true
+    isRemote: args.isRemote ?? true,
+    processShellForeground: args.processShellForeground
   })
 }
     processAgent,
+    processShellForeground: Boolean(
+      focusedPaneKey && paneForegroundAgentByPaneKey?.[focusedPaneKey]?.shellForeground
+    ),
     sleepingSessionAgent,
-    launchAgent
+    launchAgent,
+    isRemote: false
   })
 }

@AmoabaKelvin
AmoabaKelvin merged commit 4c5af5d into main Oct 27, 2026
55 of 56 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant