Skip to content

ade code: per-provider Chat/CLI choice + TUI/remote/sync hardening pass#696

Merged
arul28 merged 18 commits into
mainfrom
ade/purpose-lane-run-workflow-audit-2d0dddd3
Jul 3, 2026
Merged

ade code: per-provider Chat/CLI choice + TUI/remote/sync hardening pass#696
arul28 merged 18 commits into
mainfrom
ade/purpose-lane-run-workflow-audit-2d0dddd3

Conversation

@arul28

@arul28 arul28 commented Jul 3, 2026

Copy link
Copy Markdown
Owner

ADE Code hardening + per-provider Chat/CLI interface choice

Large hardening and feature pass on ade code (TUI), plus remote/sync reliability and cross-surface interface persistence. 13 commits, audited → implemented → dual-reviewed → quality/test-gated.

Feature: Chat vs CLI per provider (desktop/iOS parity)

  • New Interface: Chat | CLI row in the TUI new-chat/model setup panes. Chat creates an SDK chat via chat.createSession (all providers — Claude SDK chat creation from the TUI is new); CLI starts a tracked provider CLI terminal via start_cli_session (claude/codex/cursor/droid/opencode).
  • Provider-neutral terminal rendering + Ctrl+T control (<PROVIDER> CONTROL), non-Claude tracked CLIs now listed/resumable everywhere incl. ade code remote.
  • Interface-aware Cursor model gating (sdk vs cli availability), interface choice persists per project on TUI, desktop (already did), and iOS (Work screen + hub composer).

Reliability (remote/sync)

  • TUI RPC: per-request timeouts, linear framing, buffer caps + parse-failure teardown, string-id tolerance.
  • ade code remote: route re-selection on reconnect, bounded remote stderr surfaced in errors, child/socket cleanup, Unix-socket (0700) local bridge, version/capability warnings.
  • Brain/daemon: spawn lock (no double-brain), event-buffer gap signaling + byte budgets with consumer recovery, sync-host backpressure heartbeats, bounded required sends, catalog chunk delivery all-or-disconnect, per-peer message serialization (control frames exempt), async artifact reads with size cap.

Performance

  • Single-pass transcript row derivation (was 3-4× per render), deduped/gated full-event scans, React.memo + stabilized props on hot children, 200ms debounced background refresh (foreground no longer stutters when other lanes stream), markdown LRU, terminal grid read tightening.
  • Reconciled stale local ink 5.2.1 → locked 7.1.0 (incremental renderer; local dev now matches CI/packaged).

Bugs + parity + UX

  • Dropped-events-on-session-switch merge fix, CJK/emoji display-width selection/cursor math, form double-submit guard, async state persistence (no event-loop-blocking lock), bracketed paste (composer + control-mode), Ctrl+C cancel-before-copy, Droid agi mode, /login retry prefill, Claude hook-error compaction, /new lane Linear+template fields, lane setup retry, durable draft-launch status, closed-session browsing (closed (N) toggle + resume), /secrets read/copy pane, digit-picker highlight-then-Enter semantics (free text can start with a digit), UI polish sweep.
  • app.tsx −654 lines via pure-module extractions (modelState, closedCliSessions, modelPickerController, remoteBridge, providerMetadata).

Verification

  • /quality: 3-track review (correctness/security + maintainability + UI cleanliness) + independent re-review; all applied fixes verified, extractions byte-compared.
  • /test: suite pruned/consolidated (TUI 50→45 test files), parity passes for docs/CLI/TUI/iOS. Full ade-cli suite 72 files / 1437 tests green; build + dist verifier green; desktop typecheck + targeted tests green (Node 22); iOS build-for-testing green.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added terminal list/ls enhancements and terminal resume (reattach) for provider CLI terminal sessions, including new “ended”/“resume” table columns.
    • Added /secrets command and expanded model picker + new lane setup with template/Linear issue inputs and interface-aware behavior.
    • Improved TUI drawer support for closed CLI sessions and enhanced bracketed-paste input handling.
  • Bug Fixes
    • Improved runtime event replay with explicit gap/oldest-cursor metadata and more reliable buffering.
    • Hardened sync/remote/local streaming under backpressure, reconnects, and timeouts.
  • Documentation
    • Updated CLI help and session-response documentation to match the new behaviors.

Greptile Summary

This PR adds per-provider Chat/CLI selection across ADE Code and hardens remote and sync runtime paths. The main changes are:

  • Chat or CLI interface choice for new sessions in the TUI and iOS surfaces.
  • Tracked provider CLI terminal launch, listing, resume, and remote session support.
  • Remote JSON-RPC bridge timeouts, bounded framing, stderr tails, route re-selection, and cleanup.
  • Sync event gap metadata, catalog chunking, backpressure handling, and per-peer message serialization.
  • TUI model picker, closed-session browsing, bracketed paste, display-width handling, and rendering performance updates.

Confidence Score: 5/5

This PR appears safe to merge from the reviewed changes.

No accepted bug findings remain. The reviewed high-risk paths include remote bridge framing and teardown, JSON-RPC timeouts, sync catalog/backpressure handling, TUI interface-aware launch logic, and iOS Chat/CLI persistence.

No files require special attention.

T-Rex T-Rex Logs

What T-Rex did

  • Captured terminal CLI sessions before and after running the harness, recording commands, exit statuses, stdout/stderr, and the harness rpcSeen state showing no terminal.list or pty.resumeSession RPCs were observed on the socket.
  • Captured interface state changes: before lacked an interface Chat footer and treated both Cursor models as available for both interface modes; after shows interface Chat with focused detail Chat · CLI, with Chat disabling the CLI-only Cursor model and the CLI disabling the SDK-only Cursor model, and per-project draft kind reloading as cli/chat.
  • Ran the TUI RPC hardening tests; the before-run timed out with EXIT_CODE 124 due to no response, while the after-run completed with EXIT_CODE 0 and verbose scenario logs plus structured responses for all four scenarios.
  • Observed byte-budget replay evolution: before shows gap: true, oldestCursor: 4, replayed ids [4,5,6], and bounded replay bytes 490; after an oversize skipped event the head reports oldestCursor: 8, showing a new recovery boundary.
  • Checked iOS interface persistence; the before run shows missing commands with exit code 127, and the after run similarly shows 127 for missing commands, with environment discovery indicating the iOS project exists but xcodebuild and swift are absent on the Linux host.

View all artifacts

T-Rex Ran code and verified through T-Rex

Important Files Changed

Filename Overview
apps/ade-cli/src/services/sync/syncHostService.ts Adds sync backpressure, catalog chunking, per-peer serialization, and recovery guards.
apps/ade-cli/src/tuiClient/remoteBridge.ts Introduces remote stdio bridge, bounded framing, route re-selection, stderr capture, and cleanup.
apps/ade-cli/src/tuiClient/jsonRpcClient.ts Hardens JSON-RPC parsing with request timeouts, content-length support, buffer caps, and string/number id normalization.
apps/ade-cli/src/tuiClient/app.tsx Integrates Chat/CLI interface selection, closed CLI browsing/resume, and TUI performance refactors.
apps/ade-cli/src/tuiClient/modelState.ts Extracts provider/interface-aware model state and Cursor CLI gating logic with launch-time validation.
apps/ios/ADE/Views/Work/WorkNewChatScreen.swift Adds per-project Chat/CLI selection and CLI launch flow for the iOS work new-chat screen.
apps/ios/ADE/Views/Hub/HubComposerDrawer.swift Extends the hub composer with per-project Chat/CLI persistence and cross-project CLI/chat launching.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
  participant User
  participant UI as TUI / iOS Composer
  participant Runtime as ADE Runtime RPC
  participant Chat as SDK Chat Service
  participant Pty as Provider CLI Terminal
  participant Remote as Remote Bridge / Sync Host

  User->>UI: Select provider, model, and interface
  alt "Interface = Chat"
    UI->>Runtime: chat.createSession / chat.send
    Runtime->>Chat: Create SDK-backed session
    Chat-->>Runtime: Session and streamed events
  else "Interface = CLI"
    UI->>Runtime: work.startCliSession / pty.resumeSession
    Runtime->>Pty: Launch tracked provider CLI
    Pty-->>Runtime: Terminal session and transcript events
  end
  Runtime-->>UI: Runtime events and terminal snapshots
  opt Remote or sync client
    UI->>Remote: JSON-RPC or sync request
    Remote->>Runtime: Forward request with guards
    Runtime-->>Remote: Response, events, or catalog chunks
    Remote-->>UI: Framed response or bounded disconnect
  end
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
  participant User
  participant UI as TUI / iOS Composer
  participant Runtime as ADE Runtime RPC
  participant Chat as SDK Chat Service
  participant Pty as Provider CLI Terminal
  participant Remote as Remote Bridge / Sync Host

  User->>UI: Select provider, model, and interface
  alt "Interface = Chat"
    UI->>Runtime: chat.createSession / chat.send
    Runtime->>Chat: Create SDK-backed session
    Chat-->>Runtime: Session and streamed events
  else "Interface = CLI"
    UI->>Runtime: work.startCliSession / pty.resumeSession
    Runtime->>Pty: Launch tracked provider CLI
    Pty-->>Runtime: Terminal session and transcript events
  end
  Runtime-->>UI: Runtime events and terminal snapshots
  opt Remote or sync client
    UI->>Remote: JSON-RPC or sync request
    Remote->>Runtime: Forward request with guards
    Runtime-->>Remote: Response, events, or catalog chunks
    Remote-->>UI: Framed response or bounded disconnect
  end
Loading

Comments Outside Diff (1)

  1. apps/ade-cli/src/tuiClient/adeApi.ts, line 529 (link)

    P1 Preserve explicit fast off
    fastMode: false is currently omitted, so a user who disables the fast toggle on a supported chat model does not send that choice to chat.createSession; the runtime can then apply its default instead. This contradicts the iOS path’s explicit true/false behavior and makes the TUI unable to honor an explicit OFF selection.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/ade-cli/src/tuiClient/adeApi.ts
    Line: 529
    
    Comment:
    **Preserve explicit fast off**
    `fastMode: false` is currently omitted, so a user who disables the fast toggle on a supported chat model does not send that choice to `chat.createSession`; the runtime can then apply its default instead. This contradicts the iOS path’s explicit true/false behavior and makes the TUI unable to honor an explicit OFF selection.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Reviews (6): Last reviewed commit: "ship: iteration 4 — provider-aware termi..." | Re-trigger Greptile

arul28 and others added 13 commits July 3, 2026 01:59
Add an Interface: Chat | CLI row to the ADE Code (Ink TUI) new-chat and
/model setup panes, matching the desktop/iOS switcher. Chat creates an
SDK chat via chat.createSession (all providers, including Claude — a new
path); CLI starts a tracked provider CLI terminal via start_cli_session
(claude/codex/cursor/droid/opencode). Defaults to Chat; editable on a
draft, read-only once a session exists.

- Generalize the Claude-only terminal paths: startClaudeTerminalSession
  -> provider-generic startCliTerminalSession; listTerminalSessions and
  remoteLauncher.isTerminalSessionLaunchable now surface every tracked
  CLI provider (not just Claude); terminalSessionToChatSummary, the
  Ctrl+T control gates, TerminalPane status ("<PROVIDER> CONTROL"),
  FooterControls label, and grid control hint are provider-neutral.
- Submit-path branching: focused terminal -> pty send/resume (Claude
  keeps its double-enter, other providers use pty.sendToSession); draft
  Interface=CLI -> tracked CLI terminal; otherwise chat.createSession.
- Interface-aware Cursor model gating in the model picker (Chat disables
  CLI-only Cursor models and vice versa).
- Keep Claude-only chrome: closed-transcript stripping, naming hint, and
  /model + /effort writing into a running Claude terminal.
- Tests: provider-generic start payloads (5 providers), trackedCli
  provider resolution, listTerminalSessions/isTerminalSessionLaunchable
  inclusivity, interface-row state machine + defaulting, Cursor gating.
- Docs: ADE Code README chat-setup + terminal-control sections.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes remote/sync audit findings #1-#4, #6-#20, plus perf M6, bug-sweep #13, and perf L9.

Skips #5 per disposition because TUI app/component files are owned by concurrent perf work. Implements #6 as the requested partial version/capability validation only.
…round refresh

Findings:
- H1: derive selectable transcript rows once per render and build copy text lazily.
- H2: reuse pending steers, gate full Chat Info scans, and collapse cheap latest scans.
- H3/M8: memoize hot TUI children and stabilize high-churn props without adding hover throttles.
- H4: debounce/coalesce background session refreshes with an in-flight guard.
- M5: LRU-cache assistant markdown parses by message text.
- M7: tighten terminal grid reads and avoid trailing blank cell/row rendering.
- L10: reconcile local Ink install to locked 7.1.0 and validate ADE CLI.
The in-project New Chat screen and the all-projects hub composer both
defaulted the Chat/CLI switcher to .chat on every open, so the choice was
forgotten across app restarts and project switches. Add a shared per-project
store (WorkNewSessionModePreferences, keyed by project id in the app-group
UserDefaults) that mirrors desktop's per-project WorkProjectViewState.draftKind.

- Seed sessionMode from the store in each composer's init (not onAppear) so the
  sessionMode onChange never fires to reset runtimeMode to the provider default.
- Persist only on an explicit Chat/CLI switcher tap via a new onUserSelect
  callback; programmatic model-availability fallbacks never write the store.
- A stored CLI choice is honored only when the restored model can run in CLI;
  otherwise the session opens on chat without discarding the preference.

Desktop already persists this per project (WorkProjectViewState.draftKind in
localStorage ade.workViewState.v1, preserved across new-draft/close-tab/lane-
refresh/project-switch); no desktop change needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The hub composer seeded sessionMode once in init and never reloaded it when
the destination project changed, so switching from a project saved as Chat to
one saved as CLI (or vice versa) left submit branching on the stale mode.

Centralize the availability-aware fallback in a pure
WorkNewSessionModePreferences.resolvedMode(stored:modelId:provider:) (reused by
both composers' init) and add HubComposerDrawer.reloadSessionMode(forProjectId:)
called wherever pickedProjectId changes — the projectRow tap and the
reconcileDestination fallback. Reload is read-only; explicit switcher taps
remain the only writes, and a stored CLI choice still drops to chat for the
session when the current model can't run in CLI without discarding the pref.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-review finding: exit 130/143 and runtimeState 'killed' are the daemon's
user-initiated close classification (ptyService.statusFromExit), so the
closed-session drawer was marking most intentionally closed CLI sessions
with the failed glyph. Only terminalStatus === 'failed' or a genuine
non-{0,130,143} exit code renders failed now.

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

mintlify Bot commented Jul 3, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
ade-ac1c6011 🟢 Ready View Preview Jul 3, 2026, 4:44 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview Jul 3, 2026 6:17pm

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR propagates runtime replay-gap metadata through buffering, RPC, IPC, and preload layers; hardens sync-host catalog delivery and backpressure handling; expands ADE CLI/TUI session, rendering, and model-selection flows; and adds per-project Chat/CLI session-mode persistence on iOS.

Changes

Runtime gap/replay metadata

Layer / File(s) Summary
Event buffer gap metadata
apps/ade-cli/src/eventBuffer.ts, apps/ade-cli/src/bootstrap.test.ts
EventBufferDrainResult adds gap and oldestCursor; replay draining now tracks byte-retained events and gap cases.
RPC gap propagation
apps/ade-cli/src/adeRpcServer.ts, apps/ade-cli/src/multiProjectRpcServer.ts
stream_events and runtimeEvents.subscribe now return gap and oldestCursor.
CLI connection gap handling and spawn lock
apps/ade-cli/src/tuiClient/connection.ts, apps/ade-cli/src/tuiClient/types.ts, apps/ade-cli/src/tuiClient/__tests__/connection.test.ts
Adds onGap wiring, socket-spawn locking, and string-id JSON-RPC correlation with tests.
Desktop gap propagation
apps/desktop/src/main/services/ipc/runtimeBridge.ts, apps/desktop/src/main/services/localRuntime/localRuntimeConnectionPool.ts, apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts, apps/desktop/src/shared/types/remoteRuntime.ts, apps/desktop/src/preload/preload.ts, apps/desktop/src/preload/preload.test.ts
Gap metadata is preserved across desktop runtime bridges and preload binding-change handling.
Docs
apps/ade-cli/README.md, apps/ade-cli/src/cli.ts
Runtime gap semantics and terminal list/resume help text are documented.
Estimated code review effort: 5 (Critical) ~120 minutes

Sync host reliability and catalog delivery

Layer / File(s) Summary
Catalog chunking
apps/ade-cli/src/services/sync/syncHostService.ts, apps/ade-cli/src/services/sync/brainProjectActionsSyncHandler.ts, apps/ade-cli/src/services/sync/syncHostService.test.ts
Project catalogs are split into byte-bounded envelopes and sent through the sync host and project actions handler.
Backpressure and send budgets
apps/ade-cli/src/services/sync/syncHostService.ts, apps/ade-cli/src/services/sync/syncHostService.test.ts
Required sends and queued peer processing enforce buffered-byte limits and backpressure timeouts.
Heartbeat and artifact handling
apps/ade-cli/src/services/sync/syncHostService.ts
Heartbeat handling is centralized and artifact reads are async and size-limited.
Parked buffer byte budget
apps/ade-cli/src/services/sync/sharedSyncListener.ts, apps/ade-cli/src/services/sync/syncHostService.test.ts
Parked peer buffering is limited by frame count and total bytes.

ADE CLI and TUI client

Layer / File(s) Summary
Shared provider metadata
apps/ade-cli/src/tuiClient/providerMetadata.ts
Provider labels, normalization, and refresh-provider selection are centralized.
Multi-provider terminal tracking and remote bridge
apps/ade-cli/src/tuiClient/adeApi.ts, apps/ade-cli/src/tuiClient/remoteLauncher.ts, apps/ade-cli/src/tuiClient/remoteBridge.ts, related tests
Claude-only terminal handling is generalized to tracked CLI providers, and remote bridge/RPC plumbing moves into a dedicated module.
JSON-RPC framing and timeouts
apps/ade-cli/src/tuiClient/jsonRpcClient.ts, apps/ade-cli/src/tuiClient/__tests__/connection.test.ts
Requests now time out, parse with a byte queue, and reject malformed frames.
Socket liveness and terminal resume
apps/ade-cli/src/cli.ts
Socket startup checks liveness before unlinking and adds terminal resume/list behavior.
Display-cell width and chat selection
apps/ade-cli/src/tuiClient/displayWidth.ts, apps/ade-cli/src/tuiClient/components/ChatView.tsx, tests
Chat selection and wrapping use display-cell widths instead of codepoint counts.
Closed CLI session drawer UI
apps/ade-cli/src/tuiClient/closedCliSessions.ts, drawerLayout.ts, drawerSelection.ts, components/Drawer.tsx, relativeTime.ts, tests
Closed CLI sessions can be derived, rendered, and hit-tested in the drawer.
Model state and interface-mode-aware picker
apps/ade-cli/src/tuiClient/modelState.ts, modelPickerController.ts, modelPickerLayout.ts, modelPickerGeometry.ts, components/ModelPicker/*, components/RightPane.tsx, types.ts, tests
Interface mode affects Cursor availability, setup rows, and right-pane model-picker layout.
TerminalPane and FooterControls provider labeling
apps/ade-cli/src/tuiClient/components/TerminalPane.tsx, FooterControls.tsx, Header.tsx, ApprovalPrompt.tsx, MultiChatGrid.tsx, tests
Terminal chrome now uses dynamic provider labels and Claude-specific cleanup toggles.
Pending digit selection and hook aggregation
apps/ade-cli/src/tuiClient/pendingInput.ts, aggregate.ts, tests
Digit shortcuts for pending questions and hook-error aggregation are added.
Bracketed paste input handling
apps/ade-cli/src/tuiClient/bracketedPaste.ts, apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
Terminal bracketed paste markers are parsed, normalized, and forwarded.
Async TUI state persistence
apps/ade-cli/src/tuiClient/state.ts, tests
Draft kind state is persisted with async lock-queued writes.
Markdown cache, commands, new lane form, and keybindings
apps/ade-cli/src/tuiClient/format.ts, commands.ts, newLaneForm.ts, keybindings/index.ts, tests
Markdown parsing is cached; /secrets, Linear issue, template, and editor-command parsing support is added.

iOS session mode preferences

Layer / File(s) Summary
WorkNewSessionModePreferences and screen wiring
apps/ios/ADE/Views/Work/WorkNewChatScreen.swift, HubComposerDrawer.swift, WorkRootScreen.swift, WorkPreviews.swift, apps/ios/ADETests/ADETests.swift
Per-project Chat/CLI mode is persisted and reloaded for the composer and hub flows, with tests covering the preference behavior.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • arul28/ADE#291: Both PRs modify apps/ade-cli/src/tuiClient/aggregate.ts and related aggregation behavior.
  • arul28/ADE#353: Both PRs extend runtime stream pagination metadata around eventEpoch/replay handling.
  • arul28/ADE#579: Both PRs touch sync-host project catalog and handshake plumbing.

Suggested labels: desktop, ios

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main changes: per-provider Chat/CLI choice plus a broader TUI/remote/sync hardening pass.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ade/purpose-lane-run-workflow-audit-2d0dddd3

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.

@arul28 arul28 changed the title Ade Code Chat Cli Chooser ade code: per-provider Chat/CLI choice + TUI/remote/sync hardening pass Jul 3, 2026
@arul28

arul28 commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@copilot review but do not make fixes

…ening branch

Conflicts resolved:
- apps/ios/ADE/Views/Hub/HubComposerDrawer.swift: re-wove per-project Chat/CLI
  interface persistence into #692's HubInlineComposer restructure (init seed
  capture + onUserSelect on the relocated switcher inside isExpanded).
- docs/features/sync-and-multi-device/ios-companion.md: union of both maps.
- brainProjectActionsSyncHandler.ts: pass crossProjectChatEnabled=false on the
  degraded fallback hello payload (new required field from #692; no foreign-chat
  provider on that path).

Validated: ade-cli typecheck + sync/cli tests green; iOS build-for-testing green.

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

arul28 commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review

Comment thread apps/ade-cli/src/tuiClient/remoteBridge.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d03bd97152

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

try {
const spawned = spawnDaemon(machineSocketPath);
const spawned = await withSocketSpawnLock(machineSocketPath, async () => {
if (fs.existsSync(machineSocketPath)) return false;

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 Badge Unlink stale socket files before retrying daemon startup

When the default machine socket path exists but no daemon is listening on it (for example after a crash leaves the Unix socket file behind), the first tryDaemon(1) fails and reaches this catch path, but the spawn lock returns false solely because the stale path still exists. The following tryDaemon(25) just retries the same dead socket and never launches a replacement daemon; before this change the catch path spawned unconditionally. Please remove/probe the stale socket before this guard, or allow spawnDaemon to run after a failed connect.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/ios/ADE/Views/Hub/HubComposerDrawer.swift (1)

450-456: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Avoid normalizing sessionMode unconditionally on project switches reloadSessionMode still feeds .onChange(of: sessionMode), and normalizeSelection(for:) always ends by resetting runtimeMode to the provider default. Switching to a project with a different stored Chat/CLI mode can therefore overwrite a valid manual runtime choice even when the current model/provider is already allowed. Gate the fallback the same way onAppearSetup does, or split the availability fallback from the default-reset path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ios/ADE/Views/Hub/HubComposerDrawer.swift` around lines 450 - 456, Avoid
normalizing sessionMode unconditionally when switching projects in
HubComposerDrawer. Update reloadSessionMode(forProjectId:) and
normalizeSelection(for:) so that changing pickedProjectId/selectedLaneId only
restores the project’s stored mode when it is still valid for the current
provider/model, and does not always force runtimeMode back to the provider
default. Match the gating already used by onAppearSetup, or separate the
availability-fallback logic from the default-reset path so a valid manual
Chat/CLI selection is preserved.
apps/ade-cli/src/tuiClient/connection.ts (1)

942-959: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Embedded mode still delivers gapped replay events; attached mode discards them.

In the attached path (lines 700-704), a detected gap clears pending and routes only through onGap, skipping the (potentially incomplete) replayed events. Here in the embedded path, subscriptionArgs.onGap?.(gap) fires but the code still iterates replay.events and forwards them to callback unconditionally. A consumer switching between --embedded and daemon-attached modes would see different behavior on the exact same gap scenario — one recovers cleanly via onGap-triggered resync, the other silently receives a discontinuous replay through the normal path.

As per coding guidelines, "For ADE CLI changes, verify both headless mode and the desktop socket-backed ADE RPC path" — please align these two code paths (either both forward pre-gap data or both suppress it), and add embedded-mode test coverage mirroring the existing attached-mode gap test.

🛠️ Proposed fix (align with attached-mode behavior)
       const replay = subscriptionArgs.replay !== false && typeof eventBuffer.drain === "function"
         ? eventBuffer.drain(subscriptionArgs.cursor ?? 0, subscriptionArgs.limit ?? 100)
         : { events: [] };
       const gap = runtimeEventGapMetadata(replay);
-      if (gap) subscriptionArgs.onGap?.(gap);
-      for (const event of replay.events) {
+      if (gap) {
+        subscriptionArgs.onGap?.(gap);
+      } else {
+        for (const event of replay.events) {
           if (shouldForward(event)) callback(event);
         }
+      }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/connection.ts` around lines 942 - 959, The
embedded subscription path in subscribeRuntimeEvents forwards replay.events even
after runtimeEventGapMetadata detects a gap, which makes it behave differently
from the attached-mode gap handling. Update subscribeRuntimeEvents so that the
replayed events are suppressed or otherwise handled consistently with the
attached path when onGap is triggered, using the existing shouldForward/callback
flow. Add embedded-mode coverage for the gap scenario to match the attached-mode
test and verify both paths behave the same when a replay gap is detected.

Source: Coding guidelines

apps/ade-cli/src/tuiClient/adeApi.ts (1)

79-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Misleading error when template lookup itself fails vs. template genuinely missing.

listTemplates errors are swallowed to [] at line 85. If an explicit templateId is passed and the RPC call to list templates fails, the code throws Setup template "X" was not found. even though the actual cause was an RPC failure, not template absence — this could confuse callers/users debugging setup failures.

🩹 Proposed fix: distinguish RPC failure from missing template
-  const [templates, defaultTemplateId] = await Promise.all([
-    connection.action<LaneTemplate[]>("lane", "listTemplates").catch(() => []),
-    connection.action<string | null>("lane", "getDefaultTemplate").catch(() => null),
-  ]);
+  let templatesFetchFailed = false;
+  const [templates, defaultTemplateId] = await Promise.all([
+    connection.action<LaneTemplate[]>("lane", "listTemplates").catch(() => {
+      templatesFetchFailed = true;
+      return [];
+    }),
+    connection.action<string | null>("lane", "getDefaultTemplate").catch(() => null),
+  ]);
   const explicitTemplateId = typeof options.templateId === "string" ? options.templateId.trim() : "";
   const trimmedTemplateId = explicitTemplateId || (typeof defaultTemplateId === "string" ? defaultTemplateId.trim() : "");
   const templateId = trimmedTemplateId
     ? templates.find((template) => template.id === trimmedTemplateId)?.id ?? null
     : null;
   if (explicitTemplateId && !templateId) {
-    throw new Error(`Setup template "${explicitTemplateId}" was not found.`);
+    throw new Error(
+      templatesFetchFailed
+        ? `Could not verify setup template "${explicitTemplateId}": failed to list templates.`
+        : `Setup template "${explicitTemplateId}" was not found.`,
+    );
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/adeApi.ts` around lines 79 - 100,
runDefaultLaneSetup is masking failures from connection.action("lane",
"listTemplates") by falling back to an empty array, which makes explicit
template lookups throw a misleading “not found” error. Update the logic in
runDefaultLaneSetup to distinguish RPC failure from a genuine missing template:
capture errors from listTemplates separately, only report “Setup template … was
not found” when templates were successfully loaded and the ID is absent, and
otherwise surface the underlying RPC error before continuing to applyTemplate or
initEnv.
🧹 Nitpick comments (10)
apps/ade-cli/src/services/sync/sharedSyncListener.ts (2)

179-194: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a regression test for the initial snapshot-overflow path.

The provided test coverage (syncHostService.test.ts) exercises the onMessage overflow path with a 600KB frame while parked, but this initial-buffering loop in park() (executed when a snapshot already carries oversized bufferedMessages from handoff) has a separate/duplicated overflow branch that doesn't appear directly covered.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/services/sync/sharedSyncListener.ts` around lines 179 - 194,
Add a regression test for the initial snapshot overflow path in
sharedSyncListener’s park logic, since the existing syncHostService.test.ts
coverage only hits the parked onMessage overflow case. Exercise the branch that
iterates over snapshot.bufferedMessages in park(), provide an oversized
preloaded bufferedMessages snapshot, and assert the websocket is closed with the
handoff buffer exceeded code/message when PARKED_MESSAGE_BUFFER_LIMIT or
PARKED_MESSAGE_BUFFER_BYTES is exceeded.

179-223: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate overflow-check logic between initial buffering and onMessage.

The count/byte threshold check (bufferedMessages.length >= PARKED_MESSAGE_BUFFER_LIMIT || bufferedBytes(+entry.bufferedBytes) + messageBytes > PARKED_MESSAGE_BUFFER_BYTES) and the close(4002, ...) handling are copy-pasted between the initial loop (Lines 183-191) and the onMessage handler (Lines 203-220). If one path's threshold or close reason is updated later, it's easy to miss the other, silently reintroducing an unbounded buffer path.

Consider extracting a single helper, e.g. tryBufferMessage(bufferedMessages, bufferedBytesRef, message), used by both call sites.

♻️ Sketch of a shared helper
+function canBufferMore(currentCount: number, currentBytes: number, nextBytes: number): boolean {
+  return (
+    currentCount < PARKED_MESSAGE_BUFFER_LIMIT &&
+    currentBytes + nextBytes <= PARKED_MESSAGE_BUFFER_BYTES
+  );
+}

Then both the initial loop and onMessage call canBufferMore(...) instead of repeating the inline condition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/services/sync/sharedSyncListener.ts` around lines 179 - 223,
The overflow check and close(4002, ...) handling are duplicated in the initial
snapshot buffering loop and the onMessage handler, so extract that logic into a
shared helper used by both paths. Centralize the buffer-limit/byte-limit check
around PARKED_MESSAGE_BUFFER_LIMIT, PARKED_MESSAGE_BUFFER_BYTES, and
entry.bufferedBytes so the behavior stays identical in one place. Keep the
existing unpark and ws.close("Sync host handoff buffer exceeded") flow, but have
both the snapshot replay loop and onMessage call the same helper to avoid drift.
apps/desktop/src/shared/chatSubagents.ts (2)

310-363: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider splitting per-event-type handling into helpers.

The function mixes terminal-turn tracking, teammate.idle, task.completed, and subagent.* handling in one body, contributing to the flagged medium complexity. Extracting each branch into a small named helper (e.g. applyTeammateEvent, applySubagentEvent) would ease future maintenance as more event types/providers are added.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/shared/chatSubagents.ts` around lines 310 - 363, The
complexity in subagentActivitySummaryFromEvents comes from handling terminal
turn tracking, teammate events, and subagent events all in one loop body.
Refactor the branching logic into small named helpers such as applyTeammateEvent
and applySubagentEvent (and a helper for terminal turn detection if helpful),
then call them from subagentActivitySummaryFromEvents so each event type is
handled in an isolated place while preserving the current snapshot and
terminalTurnIds behavior.

365-379: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove dead snapshots.set write-back.

snapshots.set(id, { ...snapshot, status }) at line 376 has no effect on the returned value: totalCount uses snapshots.size and runningCount is already accumulated from the local status variable. The map is discarded right after the loop, so this mutation is inert and reads like leftover logic (perhaps copied from a sibling function that does return snapshots).

♻️ Proposed cleanup
   let runningCount = 0;
-  for (const [id, snapshot] of snapshots) {
+  for (const snapshot of snapshots.values()) {
     let status = snapshot.status;
     if (
       snapshot.kind === "subagent"
       && status === "running"
       && snapshot.background !== true
       && snapshot.turnId
       && terminalTurnIds.has(snapshot.turnId)
     ) {
       status = "stopped";
-      snapshots.set(id, { ...snapshot, status });
     }
     if (status === "running") runningCount += 1;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/shared/chatSubagents.ts` around lines 365 - 379, Remove the
dead write-back inside the running-count loop in chatSubagents logic: in the
snapshot iteration where status is switched from running to stopped for subagent
terminal turns, delete the snapshots.set(id, { ...snapshot, status }) mutation
because it does not affect the returned totals or any later result. Keep the
local status adjustment in the loop that updates runningCount, and verify the
relevant loop in chatSubagents is only using the local status variable rather
than mutating the snapshots map.
apps/ade-cli/src/tuiClient/format.ts (1)

387-464: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Streaming deltas will pollute the markdown cache with one-off entries.

aggregate.ts calls parseAssistantMarkdown(mergedText) on every streamed delta merge, and mergedText is a new/longer string on each call until the stream finishes. Each of these intermediate strings becomes its own cache entry that will (almost) never be looked up again, since it's superseded by the next delta's text. For a handful of concurrently streaming long messages, this can churn through the entire 500-entry/50MB budget with write-once garbage, evicting genuinely reusable entries for already-rendered messages and defeating the purpose of the cache for the case it seems intended to help (avoiding re-parse cost on scroll/re-render).

Consider only caching "settled" text (e.g., gate caching on a completion signal from the caller, or key/cap based on whether the text is still actively growing) rather than caching every intermediate streaming state.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/format.ts` around lines 387 - 464, The markdown
cache in parseAssistantMarkdown is storing every intermediate streamed text
snapshot, which creates one-off entries and churns the cache. Update
parseAssistantMarkdown/rememberMarkdownParse so only settled text is cached, or
add a caller-provided flag from aggregate.ts to skip caching while mergedText is
still growing. Keep the reusable cache behavior for finalized messages by
distinguishing streaming deltas from completed content.
apps/desktop/src/main/services/localRuntime/localRuntimeConnectionPool.ts (1)

1098-1151: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Same gap/oldestCursor normalization duplicated with remoteConnectionPool.ts.

streamEventsForRoot (Lines 1130-1143) and normalizeRuntimeEventsSubscribeResult (Lines 1847-1862) here are structurally identical to streamEventsWithEntry and normalizeRuntimeEventsSubscribeResult in apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts (Lines 693-716 and 1157-1175). This is a good candidate to consolidate into a shared normalization utility used by both local and remote pools.

Also applies to: 1836-1863

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/localRuntime/localRuntimeConnectionPool.ts`
around lines 1098 - 1151, The event-stream result normalization in
streamEventsForRoot is duplicated in the remote pool and should be shared
instead of copied. Extract the common gap/oldestCursor/eventEpoch/nextCursor
normalization into a reusable helper and have both streamEventsForRoot and the
corresponding remoteRuntime normalization path call it, using the existing
streamEventsForRoot and normalizeRuntimeEventsSubscribeResult symbols to locate
the duplicated logic.
apps/desktop/src/main/services/ipc/runtimeBridge.ts (1)

1014-1021: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated gap/oldestCursor normalization pattern.

The ...(subscriptionInit?.gap === true ? { gap: true } : {}) / oldestCursor conditional-spread block is repeated here (twice) and again almost verbatim in localRuntimeConnectionPool.ts and remoteConnectionPool.ts. Consider extracting a small shared helper (e.g. pickGapFields(init): Pick<RemoteRuntimeStreamEventsResult, "gap" | "oldestCursor">) to keep the normalization contract in one place.

♻️ Example shared helper
function pickGapFields(
  source: Pick<RemoteRuntimeStreamEventsResult, "gap" | "oldestCursor"> | null,
): Pick<RemoteRuntimeStreamEventsResult, "gap" | "oldestCursor"> {
  return {
    ...(source?.gap === true ? { gap: true } : {}),
    ...(typeof source?.oldestCursor === "number" ? { oldestCursor: source.oldestCursor } : {}),
  };
}

Also applies to: 1075-1082

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/desktop/src/main/services/ipc/runtimeBridge.ts` around lines 1014 -
1021, The `subscriptionInit` normalization for `gap` and `oldestCursor` is
duplicated in `runtimeBridge.ts` and the same pattern is repeated in the
connection pool code, so extract a small shared helper such as `pickGapFields`
and use it from the relevant return-building paths. Update the `runtimeBridge`
logic that constructs the result object to call the shared helper instead of
inlining the conditional spreads, and reuse that helper in
`localRuntimeConnectionPool` and `remoteConnectionPool` so the normalization
contract lives in one place.
apps/ade-cli/src/tuiClient/connection.ts (1)

852-858: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Redundant conditional — both branches return the same call.

if (spawned) return await tryDaemon(25); return await tryDaemon(25); always resolves to tryDaemon(25) regardless of spawned. Compare to the analogous first-usage site (lines 836-842), which differentiates attempts via tryDaemon(spawned ? 25 : 1). Either this should collapse to a single unconditional call, or the differentiation was intended here too and was dropped by mistake — worth double-checking intent.

♻️ Proposed simplification (if no differentiation intended)
         const spawned = await withSocketSpawnLock(machineSocketPath, async () => {
           if (fs.existsSync(machineSocketPath)) return false;
           return spawnDaemon(machineSocketPath);
         });
-        if (spawned) return await tryDaemon(25);
-        return await tryDaemon(25);
+        return await tryDaemon(25);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/connection.ts` around lines 852 - 858, The spawned
check in connection.ts is redundant because both branches call the same
tryDaemon(25) path. Update the logic around withSocketSpawnLock and spawnDaemon
so it either collapses to a single unconditional tryDaemon(25) call, or, if the
first-attempt behavior was meant to match the earlier tryDaemon(spawned ? 25 :
1) pattern, restore that intended differentiation using the spawned flag.
apps/ade-cli/src/tuiClient/jsonRpcClient.ts (1)

260-268: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Isolate notification handlers so a throwing listener can't crash the socket.

handleData calls handleResponse in an unguarded loop; an exception from a notification handler propagates up into the socket.on("data", …) callback and surfaces as an uncaught exception (process crash). Close handlers are already wrapped in try/catch (Lines 154-158) — mirror that here for consistency and resilience.

🛡️ Proposed guard
       if (typeof response.method === "string") {
         for (const handler of this.notificationHandlers.get(response.method) ?? []) {
-          handler(response.params);
+          try {
+            handler(response.params);
+          } catch {
+            // A notification listener throwing must not tear down frame parsing.
+          }
         }
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/jsonRpcClient.ts` around lines 260 - 268,
Notification handlers in handleResponse can throw and escape the socket data
path, so wrap each handler invocation in try/catch inside
JsonRpcClient.handleResponse, mirroring the existing close-handler guard used
elsewhere in the class. Keep the logic around response.id and
notificationHandlers.get(response.method) the same, but isolate each
handler(response.params) call so one bad listener cannot crash the socket or
terminate the process.
apps/ade-cli/src/tuiClient/__tests__/Drawer.test.tsx (1)

119-155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding coverage for untracked/chat-backed toolTypes.

None of the current cases pass a toolType like "shell" or "codex-chat" through deriveClosedCliSessions, so the misclassification issue flagged in closedCliSessions.ts (provider fallback to "claude") isn't caught by this suite.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/__tests__/Drawer.test.tsx` around lines 119 - 155,
The Drawer test suite currently only covers terminal-backed closed sessions
without exercising toolType-based classification, so it misses the provider
fallback bug in deriveClosedCliSessions. Add coverage in Drawer.test.tsx for
sessions passed through deriveClosedCliSessions with toolType values like
"shell" and "codex-chat" to verify closedCliSessionStatusKind and
closedCliRightPaneRow still classify them correctly. Use the existing terminal()
helper and the deriveClosedCliSessions/closedCliSessionStatusKind symbols to
locate the relevant assertions, and include a case that would fail if the
provider defaults to "claude".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/ade-cli/src/eventBuffer.ts`:
- Around line 58-82: Track oversized event rejections in the EventBuffer gap
metadata by updating the logic in drainMetadata and push. Right now, oversized
events are skipped without affecting the gap calculation, so record the last
skipped BufferedEvent id when bytes exceeds maxEventBytes and include that
cursor in the gap check alongside retained events. Use the existing
drainMetadata and push helpers in eventBuffer.ts to keep oldestCursor and gap
accurate for replay consumers.

In `@apps/ade-cli/src/services/sync/syncHostService.ts`:
- Around line 1848-1869: Add a per-message timeout to the queued non-control
envelope handling in syncHostService’s ws.on("message") flow so a hung
handleMessage call cannot block peer.messageQueue forever. Update the
peer.messageQueue chain around handleMessage(peer, envelope) to race the handler
against a watchdog/abort, and treat timeout as a failed message. Keep the
existing parse and control-envelope handling intact, and make sure the timeout
path still logs through args.logger.warn with peer metadata.

In `@apps/ade-cli/src/tuiClient/components/TerminalPane.tsx`:
- Around line 384-391: The scan limit in TerminalPane’s row extraction is using
fallbackText.length, which counts UTF-16 code units instead of terminal columns
and can truncate wide-character lines. Update the scan loop in the TerminalPane
logic to iterate across the full terminal.cols range, and keep relying on
lastContentColumn plus the existing cells.length trim to remove trailing blanks.

In `@apps/ade-cli/src/tuiClient/connection.ts`:
- Around line 529-572: The lock handling in withSocketSpawnLock currently treats
any lock file older than 30s as stale based only on mtime, which can let an
active owner be replaced and then have its cleanup remove another process’s
lock. Update the lock ownership logic to write and verify an owner identity in
the lock file (for example process id plus timestamp) and only reclaim or unlink
the lock when the recorded owner matches the current holder; keep the timeout
behavior, but make the stale-lock check and finally cleanup in
withSocketSpawnLock and its lockPath/fd flow ownership-aware.

In `@apps/ade-cli/src/tuiClient/displayWidth.ts`:
- Around line 80-92: splitByDisplayCells is duplicating wide grapheme clusters
when a boundary falls inside a multi-cell character because it builds
before/selected/after with three independent sliceByDisplayCells calls. Fix
splitByDisplayCells to partition the string in a single pass over display
clusters so each cluster is assigned to exactly one segment, instead of letting
the same cluster satisfy overlap in multiple slices. Keep the behavior
consistent with sliceByDisplayCells and ensure the cursor/selection path used by
displayCellForCodeUnitIndex, app.tsx, and ChatView’s splitTextByColumns no
longer renders wide CJK/emoji characters twice.

In `@apps/ade-cli/src/tuiClient/modelState.ts`:
- Around line 362-374: Add explicit handling for the OpenCode-backed providers
in the permission helpers so Ollama and LM Studio do not fall through to Cursor
behavior. Update permissionSummary, permissionOptionsDetail, and
applyProviderPermissionMode to treat "ollama" and "lmstudio" the same way as
"opencode" by reusing the OpenCode permission mode mapping rather than
cursorModeLabel/cursorModeIdsForState or returning a no-op patch. Use the
existing provider mapping patterns from runtimeProviderForUiProvider and
fallbackModelStatePatch to keep the branches consistent across this file.

In `@apps/ade-cli/src/tuiClient/state.ts`:
- Around line 34-54: The final state write is fire-and-forget, so the shutdown
path can exit before the last save completes. Update the cleanup flow that calls
saveAdeCodeState/saveAdeCodeProjectState to await the async write (or add an
explicit flush before process exit), using saveAdeCodeStateAsync and the related
shutdown/heartbeat exit handling so the last state update is persisted.

In `@apps/desktop/src/preload/preload.ts`:
- Around line 1889-1893: The gap-notification path is triggering the same
project-binding change callback even when it’s for the current binding, which
makes renderer consumers like AppShell reapply project state unnecessarily.
Update the `batch.gap` handling in `preload.ts` so
`notifyProjectBindingChangedCallbacks(binding)` is only used for the intended
binding-change flow, and avoid treating same-binding gap events as a full
refresh; use `binding.key` in the callback path to keep the logic scoped. Also
remove `oldestCursor` from this desktop bridge path if it is not consumed
anywhere in the preload-to-renderer flow.

---

Outside diff comments:
In `@apps/ade-cli/src/tuiClient/adeApi.ts`:
- Around line 79-100: runDefaultLaneSetup is masking failures from
connection.action("lane", "listTemplates") by falling back to an empty array,
which makes explicit template lookups throw a misleading “not found” error.
Update the logic in runDefaultLaneSetup to distinguish RPC failure from a
genuine missing template: capture errors from listTemplates separately, only
report “Setup template … was not found” when templates were successfully loaded
and the ID is absent, and otherwise surface the underlying RPC error before
continuing to applyTemplate or initEnv.

In `@apps/ade-cli/src/tuiClient/connection.ts`:
- Around line 942-959: The embedded subscription path in subscribeRuntimeEvents
forwards replay.events even after runtimeEventGapMetadata detects a gap, which
makes it behave differently from the attached-mode gap handling. Update
subscribeRuntimeEvents so that the replayed events are suppressed or otherwise
handled consistently with the attached path when onGap is triggered, using the
existing shouldForward/callback flow. Add embedded-mode coverage for the gap
scenario to match the attached-mode test and verify both paths behave the same
when a replay gap is detected.

In `@apps/ios/ADE/Views/Hub/HubComposerDrawer.swift`:
- Around line 450-456: Avoid normalizing sessionMode unconditionally when
switching projects in HubComposerDrawer. Update reloadSessionMode(forProjectId:)
and normalizeSelection(for:) so that changing pickedProjectId/selectedLaneId
only restores the project’s stored mode when it is still valid for the current
provider/model, and does not always force runtimeMode back to the provider
default. Match the gating already used by onAppearSetup, or separate the
availability-fallback logic from the default-reset path so a valid manual
Chat/CLI selection is preserved.

---

Nitpick comments:
In `@apps/ade-cli/src/services/sync/sharedSyncListener.ts`:
- Around line 179-194: Add a regression test for the initial snapshot overflow
path in sharedSyncListener’s park logic, since the existing
syncHostService.test.ts coverage only hits the parked onMessage overflow case.
Exercise the branch that iterates over snapshot.bufferedMessages in park(),
provide an oversized preloaded bufferedMessages snapshot, and assert the
websocket is closed with the handoff buffer exceeded code/message when
PARKED_MESSAGE_BUFFER_LIMIT or PARKED_MESSAGE_BUFFER_BYTES is exceeded.
- Around line 179-223: The overflow check and close(4002, ...) handling are
duplicated in the initial snapshot buffering loop and the onMessage handler, so
extract that logic into a shared helper used by both paths. Centralize the
buffer-limit/byte-limit check around PARKED_MESSAGE_BUFFER_LIMIT,
PARKED_MESSAGE_BUFFER_BYTES, and entry.bufferedBytes so the behavior stays
identical in one place. Keep the existing unpark and ws.close("Sync host handoff
buffer exceeded") flow, but have both the snapshot replay loop and onMessage
call the same helper to avoid drift.

In `@apps/ade-cli/src/tuiClient/__tests__/Drawer.test.tsx`:
- Around line 119-155: The Drawer test suite currently only covers
terminal-backed closed sessions without exercising toolType-based
classification, so it misses the provider fallback bug in
deriveClosedCliSessions. Add coverage in Drawer.test.tsx for sessions passed
through deriveClosedCliSessions with toolType values like "shell" and
"codex-chat" to verify closedCliSessionStatusKind and closedCliRightPaneRow
still classify them correctly. Use the existing terminal() helper and the
deriveClosedCliSessions/closedCliSessionStatusKind symbols to locate the
relevant assertions, and include a case that would fail if the provider defaults
to "claude".

In `@apps/ade-cli/src/tuiClient/connection.ts`:
- Around line 852-858: The spawned check in connection.ts is redundant because
both branches call the same tryDaemon(25) path. Update the logic around
withSocketSpawnLock and spawnDaemon so it either collapses to a single
unconditional tryDaemon(25) call, or, if the first-attempt behavior was meant to
match the earlier tryDaemon(spawned ? 25 : 1) pattern, restore that intended
differentiation using the spawned flag.

In `@apps/ade-cli/src/tuiClient/format.ts`:
- Around line 387-464: The markdown cache in parseAssistantMarkdown is storing
every intermediate streamed text snapshot, which creates one-off entries and
churns the cache. Update parseAssistantMarkdown/rememberMarkdownParse so only
settled text is cached, or add a caller-provided flag from aggregate.ts to skip
caching while mergedText is still growing. Keep the reusable cache behavior for
finalized messages by distinguishing streaming deltas from completed content.

In `@apps/ade-cli/src/tuiClient/jsonRpcClient.ts`:
- Around line 260-268: Notification handlers in handleResponse can throw and
escape the socket data path, so wrap each handler invocation in try/catch inside
JsonRpcClient.handleResponse, mirroring the existing close-handler guard used
elsewhere in the class. Keep the logic around response.id and
notificationHandlers.get(response.method) the same, but isolate each
handler(response.params) call so one bad listener cannot crash the socket or
terminate the process.

In `@apps/desktop/src/main/services/ipc/runtimeBridge.ts`:
- Around line 1014-1021: The `subscriptionInit` normalization for `gap` and
`oldestCursor` is duplicated in `runtimeBridge.ts` and the same pattern is
repeated in the connection pool code, so extract a small shared helper such as
`pickGapFields` and use it from the relevant return-building paths. Update the
`runtimeBridge` logic that constructs the result object to call the shared
helper instead of inlining the conditional spreads, and reuse that helper in
`localRuntimeConnectionPool` and `remoteConnectionPool` so the normalization
contract lives in one place.

In `@apps/desktop/src/main/services/localRuntime/localRuntimeConnectionPool.ts`:
- Around line 1098-1151: The event-stream result normalization in
streamEventsForRoot is duplicated in the remote pool and should be shared
instead of copied. Extract the common gap/oldestCursor/eventEpoch/nextCursor
normalization into a reusable helper and have both streamEventsForRoot and the
corresponding remoteRuntime normalization path call it, using the existing
streamEventsForRoot and normalizeRuntimeEventsSubscribeResult symbols to locate
the duplicated logic.

In `@apps/desktop/src/shared/chatSubagents.ts`:
- Around line 310-363: The complexity in subagentActivitySummaryFromEvents comes
from handling terminal turn tracking, teammate events, and subagent events all
in one loop body. Refactor the branching logic into small named helpers such as
applyTeammateEvent and applySubagentEvent (and a helper for terminal turn
detection if helpful), then call them from subagentActivitySummaryFromEvents so
each event type is handled in an isolated place while preserving the current
snapshot and terminalTurnIds behavior.
- Around line 365-379: Remove the dead write-back inside the running-count loop
in chatSubagents logic: in the snapshot iteration where status is switched from
running to stopped for subagent terminal turns, delete the snapshots.set(id, {
...snapshot, status }) mutation because it does not affect the returned totals
or any later result. Keep the local status adjustment in the loop that updates
runningCount, and verify the relevant loop in chatSubagents is only using the
local status variable rather than mutating the snapshots map.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 14642fb5-305d-4150-9bb3-608d5fbd7dbd

📥 Commits

Reviewing files that changed from the base of the PR and between 607c382 and d0d87d0.

⛔ Files ignored due to path filters (7)
  • apps/ade-cli/package-lock.json is excluded by !**/package-lock.json, !**/package-lock.json
  • docs/ARCHITECTURE.md is excluded by !docs/**
  • docs/features/ade-code/README.md is excluded by !docs/**
  • docs/features/remote-runtime/README.md is excluded by !docs/**
  • docs/features/remote-runtime/internal-architecture.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/ios-companion.md is excluded by !docs/**
📒 Files selected for processing (78)
  • apps/ade-cli/README.md
  • apps/ade-cli/package.json
  • apps/ade-cli/src/adeRpcServer.ts
  • apps/ade-cli/src/bootstrap.test.ts
  • apps/ade-cli/src/cli.ts
  • apps/ade-cli/src/eventBuffer.ts
  • apps/ade-cli/src/multiProjectRpcServer.ts
  • apps/ade-cli/src/services/sync/brainProjectActionsSyncHandler.ts
  • apps/ade-cli/src/services/sync/sharedSyncListener.ts
  • apps/ade-cli/src/services/sync/syncHostService.test.ts
  • apps/ade-cli/src/services/sync/syncHostService.ts
  • apps/ade-cli/src/tuiClient/__tests__/ApprovalPrompt.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/ChatView.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/Drawer.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/RightPane.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/TerminalPane.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/adeApi.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/aggregate.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/appPolling.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/commands.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/connection.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/drawerLayout.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/format.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/keybindings.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/newLaneForm.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/pendingInput.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/planMode.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/remoteLauncher.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/state.test.ts
  • apps/ade-cli/src/tuiClient/adeApi.ts
  • apps/ade-cli/src/tuiClient/aggregate.ts
  • apps/ade-cli/src/tuiClient/app.tsx
  • apps/ade-cli/src/tuiClient/bracketedPaste.ts
  • apps/ade-cli/src/tuiClient/closedCliSessions.ts
  • apps/ade-cli/src/tuiClient/commands.ts
  • apps/ade-cli/src/tuiClient/components/ApprovalPrompt.tsx
  • apps/ade-cli/src/tuiClient/components/ChatView.tsx
  • apps/ade-cli/src/tuiClient/components/Drawer.tsx
  • apps/ade-cli/src/tuiClient/components/FooterControls.tsx
  • apps/ade-cli/src/tuiClient/components/Header.tsx
  • apps/ade-cli/src/tuiClient/components/ModelPicker/ModelPickerPane.tsx
  • apps/ade-cli/src/tuiClient/components/ModelPicker/modelPickerGeometry.ts
  • apps/ade-cli/src/tuiClient/components/ModelPicker/modelPickerLayout.test.ts
  • apps/ade-cli/src/tuiClient/components/ModelPicker/modelPickerLayout.ts
  • apps/ade-cli/src/tuiClient/components/RightPane.tsx
  • apps/ade-cli/src/tuiClient/components/TerminalPane.tsx
  • apps/ade-cli/src/tuiClient/connection.ts
  • apps/ade-cli/src/tuiClient/displayWidth.ts
  • apps/ade-cli/src/tuiClient/drawerLayout.ts
  • apps/ade-cli/src/tuiClient/drawerSelection.ts
  • apps/ade-cli/src/tuiClient/format.ts
  • apps/ade-cli/src/tuiClient/jsonRpcClient.ts
  • apps/ade-cli/src/tuiClient/keybindings/index.ts
  • apps/ade-cli/src/tuiClient/modelPickerController.ts
  • apps/ade-cli/src/tuiClient/modelState.ts
  • apps/ade-cli/src/tuiClient/newLaneForm.ts
  • apps/ade-cli/src/tuiClient/pendingInput.ts
  • apps/ade-cli/src/tuiClient/project.ts
  • apps/ade-cli/src/tuiClient/providerMetadata.ts
  • apps/ade-cli/src/tuiClient/relativeTime.ts
  • apps/ade-cli/src/tuiClient/remoteBridge.ts
  • apps/ade-cli/src/tuiClient/remoteLauncher.ts
  • apps/ade-cli/src/tuiClient/state.ts
  • apps/ade-cli/src/tuiClient/types.ts
  • apps/ade-cli/tsup.config.ts
  • apps/desktop/src/main/services/ipc/runtimeBridge.ts
  • apps/desktop/src/main/services/localRuntime/localRuntimeConnectionPool.ts
  • apps/desktop/src/main/services/remoteRuntime/remoteConnectionPool.ts
  • apps/desktop/src/preload/preload.test.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/shared/chatSubagents.ts
  • apps/desktop/src/shared/types/remoteRuntime.ts
  • apps/ios/ADE/Views/Hub/HubComposerDrawer.swift
  • apps/ios/ADE/Views/Work/WorkNewChatScreen.swift
  • apps/ios/ADE/Views/Work/WorkPreviews.swift
  • apps/ios/ADE/Views/Work/WorkRootScreen.swift
  • apps/ios/ADETests/ADETests.swift

Comment thread apps/ade-cli/src/eventBuffer.ts
Comment thread apps/ade-cli/src/services/sync/syncHostService.ts
Comment thread apps/ade-cli/src/tuiClient/components/TerminalPane.tsx Outdated
Comment thread apps/ade-cli/src/tuiClient/connection.ts
Comment thread apps/ade-cli/src/tuiClient/displayWidth.ts
Comment thread apps/ade-cli/src/tuiClient/modelState.ts
Comment thread apps/ade-cli/src/tuiClient/state.ts
Comment thread apps/desktop/src/preload/preload.ts
…, coderabbit x8)

- remoteBridge + cli.ts transports: normalize JSON-RPC response ids (class sweep)
- connection.ts: unlink stale socket before daemon spawn retry; owner-aware spawn-lock cleanup
- eventBuffer: oversized skipped events mark replay gaps
- syncHostService: queued-message watchdog timeout + warning metadata
- TerminalPane: full-column wide-glyph scan
- displayWidth: splitByDisplayCells single-pass grapheme partition (+ new test file)
- modelState: ollama/lmstudio use OpenCode permission behavior (not Cursor)
- state.ts: final state write flushed + awaited on signal exit
- preload: gap polling no longer fires same-binding project refresh

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

arul28 commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d80f5d981

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

title,
terminalId,
cliLabel,
claudeChrome = true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Default non-Claude grid terminals to neutral cleanup

When a non-Claude CLI terminal is shown as a split/grid tile, the existing MultiChatGrid caller does not pass the new claudeChrome prop, so this default still runs compactClosedTerminalTranscript for closed Codex/Cursor/Droid/OpenCode transcripts. That cleaner removes Claude-specific prompt/footer/session lines, which can hide legitimate output from those providers in grid tiles; default to neutral cleanup or have the grid pass the derived terminal provider like the main pane does.

Useful? React with 👍 / 👎.

export function truncateDisplayEnd(value: string, maxCells: number): string {
if (terminalDisplayWidth(value) <= maxCells) return value;
if (maxCells <= 1) return sliceByDisplayCells(value, 0, Math.max(0, maxCells));
return `${sliceByDisplayCells(value, 0, maxCells - 1)}…`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Keep truncated CJK text within the requested cell width

For labels beginning with a double-width grapheme, this path can return a string wider than maxCells: for example maxCells = 2 calls sliceByDisplayCells(..., 0, 1), which includes the whole 2-cell character, then appends a 1-cell ellipsis. Any chat footer/drawer cell using truncateDisplayEnd can overflow its allocated width for CJK or emoji text; reserve space in terminal cells before appending the ellipsis.

Useful? React with 👍 / 👎.

…ate overflow)

- MultiChatGrid passes claudeChrome by derived terminal provider so non-Claude
  grid tiles keep neutral closed-transcript cleanup
- truncateDisplayEnd uses a cluster-boundary-safe prefix so a leading wide
  grapheme can no longer overflow the allocated cell width (+ regression tests)

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

arul28 commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d442d2d627

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

}

export function splitEditorCommand(value: string): string[] {
return value.trim().split(/\s+/).filter(Boolean);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3 Badge Preserve quoted EDITOR commands

When VISUAL/EDITOR contains shell quoting, such as a macOS app path with spaces or emacsclient -a "", this whitespace split turns the quoted segment into multiple argv entries and openKeybindingsFile() then spawns a non-existent command while swallowing the error. The previous shell-based path handled these editor commands, so users with quoted editor settings can no longer open the keybindings file; please parse quoted command strings or otherwise preserve quoted editor argv while still appending the target file safely.

Useful? React with 👍 / 👎.

splitEditorCommand now tokenizes with quote/escape support so editors like
'emacsclient -a ""' or app paths with spaces keep working after the
shell:false hardening; quoted empty strings survive as argv entries.

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

arul28 commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9a50966c93

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

const toolType = session.toolType ?? "";
if (CHAT_BACKED_TERMINAL_TOOL_TYPES.has(toolType)) return false;
return isClaudeTerminalSession(session);
return trackedCliTerminalProvider(session) !== null;

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 Badge Preserve provider state for non-Claude CLI sessions

When this broader terminal filter lets Codex/Cursor/Droid/OpenCode CLI sessions into the TUI, selecting one still hits the existing refresh path that treats every nextTerminalSession as Claude and applies fallbackModelStatePatch("claude") in app.tsx. In practice a newly started non-Claude CLI session refreshes with the wrong model/footer provider, so subsequent model changes or new CLI launches target Claude instead of the active provider. Hydrate the model state from terminalSessionProvider(nextTerminalSession) (or keep non-Claude terminals out until that path is provider-aware).

Useful? React with 👍 / 👎.

function permissionOptionsDetail(modelState: AdeCodeModelState): string {
if (modelState.provider === "codex") return CODEX_PRESETS.join(" · ");
if (modelState.provider === "claude") return "default · plan · auto · bypass";
if (runtimeProviderForUiProvider(modelState.provider) === "opencode") return OPENCODE_PERMISSION_OPTIONS.join(" · ");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Cycle OpenCode-backed permissions for local providers

For Ollama and LM Studio this row now advertises the OpenCode permission choices because runtimeProviderForUiProvider(...) === "opencode", but cyclePermission only updates opencodePermissionMode when modelState.provider === "opencode"; those local providers fall through to the Cursor-mode branch instead. When users choose Ollama/LM Studio and cycle Permissions, the visible OpenCode mode does not change and launches keep the old permission mode, so the advertised plan/full-auto/config-toml choices are unreachable.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
apps/ade-cli/src/tuiClient/connection.ts (1)

956-961: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Redundant branch: both paths call tryDaemon(25).

The if (spawned) guard has no effect since both arms return tryDaemon(25). Compare with the initial-spawn path at Lines 944-945 which selects the attempt count via spawned ? 25 : 1. Confirm this parity is intentional (i.e. we always want 25 attempts here even when no spawn occurred) rather than a copy from the other block that dropped the 1-attempt case.

♻️ Collapse to a single return if 25 is intended
-        if (spawned) return await tryDaemon(25);
-        return await tryDaemon(25);
+        return await tryDaemon(spawned ? 25 : 1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/ade-cli/src/tuiClient/connection.ts` around lines 956 - 961, The
`withSocketSpawnLock` result handling in `connection.ts` has a redundant `if
(spawned)` branch because both paths return `tryDaemon(25)`. Update this block
to either collapse it into a single return if 25 attempts is always intended, or
restore the intended parity with the earlier spawn logic by varying the attempt
count based on `spawned`. Use the `spawned`, `tryDaemon`, and
`withSocketSpawnLock` symbols to locate and align this flow with the
initial-spawn path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/ade-cli/src/tuiClient/connection.ts`:
- Around line 956-961: The `withSocketSpawnLock` result handling in
`connection.ts` has a redundant `if (spawned)` branch because both paths return
`tryDaemon(25)`. Update this block to either collapse it into a single return if
25 attempts is always intended, or restore the intended parity with the earlier
spawn logic by varying the attempt count based on `spawned`. Use the `spawned`,
`tryDaemon`, and `withSocketSpawnLock` symbols to locate and align this flow
with the initial-spawn path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 32e06327-504d-4cf3-88f8-292097acb6fd

📥 Commits

Reviewing files that changed from the base of the PR and between d0d87d0 and 9a50966.

⛔ Files ignored due to path filters (3)
  • docs/ARCHITECTURE.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/ios-companion.md is excluded by !docs/**
📒 Files selected for processing (25)
  • apps/ade-cli/src/bootstrap.test.ts
  • apps/ade-cli/src/cli.ts
  • apps/ade-cli/src/eventBuffer.ts
  • apps/ade-cli/src/services/sync/brainProjectActionsSyncHandler.ts
  • apps/ade-cli/src/services/sync/syncHostService.test.ts
  • apps/ade-cli/src/services/sync/syncHostService.ts
  • apps/ade-cli/src/tuiClient/__tests__/TerminalPane.test.tsx
  • apps/ade-cli/src/tuiClient/__tests__/appInput.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/connection.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/displayWidth.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/keybindings.test.ts
  • apps/ade-cli/src/tuiClient/__tests__/state.test.ts
  • apps/ade-cli/src/tuiClient/app.tsx
  • apps/ade-cli/src/tuiClient/components/MultiChatGrid.tsx
  • apps/ade-cli/src/tuiClient/components/TerminalPane.tsx
  • apps/ade-cli/src/tuiClient/connection.ts
  • apps/ade-cli/src/tuiClient/displayWidth.ts
  • apps/ade-cli/src/tuiClient/keybindings/index.ts
  • apps/ade-cli/src/tuiClient/modelState.ts
  • apps/ade-cli/src/tuiClient/remoteBridge.ts
  • apps/ade-cli/src/tuiClient/state.ts
  • apps/desktop/src/preload/preload.test.ts
  • apps/desktop/src/preload/preload.ts
  • apps/ios/ADE/Views/Hub/HubComposerDrawer.swift
  • apps/ios/ADETests/ADETests.swift
💤 Files with no reviewable changes (1)
  • apps/desktop/src/preload/preload.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • apps/ade-cli/src/tuiClient/tests/TerminalPane.test.tsx
  • apps/ade-cli/src/tuiClient/tests/keybindings.test.ts
  • apps/ade-cli/src/eventBuffer.ts
  • apps/ade-cli/src/tuiClient/tests/state.test.ts
  • apps/ios/ADETests/ADETests.swift
  • apps/ade-cli/src/tuiClient/displayWidth.ts
  • apps/ios/ADE/Views/Hub/HubComposerDrawer.swift
  • apps/ade-cli/src/services/sync/syncHostService.test.ts
  • apps/ade-cli/src/services/sync/brainProjectActionsSyncHandler.ts
  • apps/ade-cli/src/tuiClient/state.ts
  • apps/ade-cli/src/tuiClient/modelState.ts
  • apps/ade-cli/src/services/sync/syncHostService.ts

…permission cycle (codex P1/P2)

- selecting a non-Claude tracked CLI session now hydrates model state from
  terminalSessionProvider instead of forcing claude (footer/model targets the
  right provider for subsequent changes and launches)
- cyclePermission + updateChatModel payload route ollama/lmstudio through the
  OpenCode permission branch via runtimeProviderForUiProvider (class sweep)

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

arul28 commented Jul 3, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot 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.

💡 Codex Review

: { ...payload, projects: [] };

P2 Badge Request the oversized catalog instead of sending empty projects

When the hello_ok envelope exceeds the catalog cap, this fallback sends an explicit empty projects array. The iOS hello handler treats any present projects field as the authoritative catalog and only sends project_catalog_request when that field is absent, so a host with enough projects to hit this cap causes a newly connected phone to apply an empty remote catalog after handshake even though the later request path can chunk the catalog. Please omit projects for the oversized case or immediately send the chunked catalog after hello.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@arul28 arul28 merged commit d426159 into main Jul 3, 2026
29 checks passed
@arul28 arul28 deleted the ade/purpose-lane-run-workflow-audit-2d0dddd3 branch July 3, 2026 18:30
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