feat: Micro levers for Codex/Claude/Cursor ACP (lean idle)#89
Conversation
Give every Integrations tile a clear next-step checklist, clarify auto-discovered “Detected” labels, and fill Settings whitespace with a subtle mouse-reactive mesh that never blocks clicks.
Wire attach-on-demand Codex app-server and Claude PermissionRequest bridges, bus-bound Cursor ACP sessions, fail-closed owner-0 routing, and dormant MCP by default — plus UI aliveness and Agent Key LEDs — without new always-on poll loops.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 49 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughAdds Cursor lifecycle observation and ACP control, Claude permission hooks, Codex proxy control, daemon routing and MCP gating changes, plus expanded Settings integration guidance, mesh visuals, and status updates. ChangesIntegration control and setup experience
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Settings
participant MicrobridgeDaemon
participant CursorACP
participant ClaudeHook
Settings->>MicrobridgeDaemon: enable integration and request action
MicrobridgeDaemon->>CursorACP: dispatch ACP session action
CursorACP->>MicrobridgeDaemon: return session or action result
ClaudeHook->>MicrobridgeDaemon: submit pending approval lifecycle
MicrobridgeDaemon->>ClaudeHook: write approval decision
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Comment |
There was a problem hiding this comment.
Pull request overview
Adds “Micro” control levers (approve/reject/interrupt/new session where supported) across Codex, Claude, and Cursor by introducing attach-on-demand / hook-driven control planes while keeping the daemon idle footprint lean, plus UI updates to reflect “connected but lifecycle-only” honestly and guide setup.
Changes:
- Add new internal control-plane adapters: Codex app-server proxy attach, Claude PermissionRequest decision-file bridge, and Cursor Agent (ACP).
- Update daemon/session routing and adapter state/diagnostics (Cursor lifecycle shown as Connected; owner-0 actions fail-closed).
- Expand UI integration tiles: structured guidance, Ready·idle semantics for journal-backed hosts, status/LED pulse + mesh background.
Reviewed changes
Copilot reviewed 34 out of 35 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| docs/protocol.md | Updates Cursor adapter status example/diagnostic text. |
| docs/project-tracking.md | Adds Linear links and defers Zed/Cmux until lever-complete. |
| docs/lean-overhead.md | Documents the “lean overhead” rules and local baseline checks. |
| crates/microbridged/src/state.rs | Adjusts adapter state/diagnostics, adds cursor_acp adapter entry, fail-closed owner-0 action routing, and new tests. |
| crates/microbridged/src/mcp.rs | Makes embedded MCP server opt-in via env; updates MCP owner id. |
| crates/microbridged/src/main.rs | Wires new internal adapters and adds Cursor transcript adapter event stream. |
| crates/microbridged/src/lib.rs | Exposes new control-plane modules (claude_control/codex_control/cursor_acp). |
| crates/microbridged/src/cursor_acp.rs | Implements Cursor CLI agent acp control plane for Microbridge-owned sessions. |
| crates/microbridged/src/codex_control.rs | Implements attach-on-demand Codex app-server proxy control plane. |
| crates/microbridged/src/claude_control.rs | Implements Claude decision-file control plane + hook presence detection. |
| crates/microbridged/src/auto_discover.rs | Corrects OpenCode auto-discover capabilities (lifecycle+interrupt only). |
| crates/mb-adapters/src/lib.rs | Exports new Cursor transcript adapter. |
| crates/mb-adapters/src/cursor.rs | Adds Cursor agent-transcript watcher producing lossy lifecycle sessions. |
| apps/microbridge-ui/src/surfaces/surfaces.test.tsx | Updates Settings surface expectations for new Connected/Ready·idle semantics. |
| apps/microbridge-ui/src/surfaces/Settings.tsx | Integrations UI: adds mesh background, cursor_acp tile, guidance, and improved Agent Key LEDs. |
| apps/microbridge-ui/src/lib/integrationSetup.ts | Adds structured per-integration guidance model (steps + primary CTA). |
| apps/microbridge-ui/src/lib/integrationSetup.test.ts | Adds tests for integrationGuidance behaviors (Cursor, ACP, idle hosts, etc.). |
| apps/microbridge-ui/src/lib/integrationIcons.ts | Adds icon mapping for cursor_acp. |
| apps/microbridge-ui/src/lib/hosts.ts | Refines integrationView: Ready·idle for journal hosts; “Detected” label; lifecycle-only “Connected · lifecycle”. |
| apps/microbridge-ui/src/lib/hosts.test.ts | Updates/adds tests for Ready·idle, lifecycle-only Connected, and Detected labeling. |
| apps/microbridge-ui/src/lib/bus.ts | Adds cursor_acp to the demo snapshot adapter list. |
| apps/microbridge-ui/src/index.css | Adds status pulse animation and reduced-motion handling. |
| apps/microbridge-ui/src/components/MeshBackground.tsx | Adds subtle mouse-reactive settings mesh canvas. |
| apps/microbridge-ui/src/components/IntegrationCard.tsx | Adds pulsing status dot; detail panel can render structured guidance. |
| apps/microbridge-ui/src/components/DeviceTwin.tsx | Extends LED “breathe” animation to Working state. |
| apps/microbridge-ui/src/components/DeviceEcho.tsx | Extends LED “breathe” animation to Working state. |
| apps/microbridge-ui/src-tauri/src/lib.rs | Adds Claude hook installation/merge logic and sync wiring. |
| apps/microbridge-ui/src-tauri/Cargo.lock | Bumps microbridge-ui version to 0.3.7. |
| AGENTS.md | Adds agent guidance notes (preferences + workspace facts). |
| adapters/cursor/README.md | Documents Cursor lifecycle vs ACP boundary and new “Connected lifecycle” semantics. |
| adapters/cursor/hooks/hooks.json | Adds Cursor hook for sessionStart lifecycle event. |
| adapters/cursor/hooks/event.mjs | Maps session_start to a lifecycle state. |
| adapters/cursor-acp/README.md | Documents Cursor Agent (ACP) integration and action→method mapping. |
| adapters/claude/README.md | Documents Claude hook setup and privacy stance. |
| adapters/claude/hooks/microbridge-permission.mjs | Adds Claude PermissionRequest/PreToolUse hook bridge implementation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Require MICROBRIDGE_MCP_TOKEN with Host checks, drop wildcard CORS, read full Content-Length bodies, preserve user Claude PermissionRequest hooks, and fix cfg-gated unused import on non-macOS.
Cards sit in a CSS grid, not a list — avoid orphaned li elements.
Bundle Claude hooks like Cursor/OpenCode, respect disabled chatgpt/synara/claude_desktop, send jsonrpc 2.0 on Codex proxy, and stop spawning sleep in the permission wait loop.
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (3)
adapters/claude/hooks/microbridge-permission.mjs (1)
88-121: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winBusy-wait spawns a subprocess every ~50ms instead of using an async sleep.
waitDecisioncallsexecFileSync("sleep", ["0.05"], ...)on every polling iteration, which forks a new OS process each time. For the 10-minute permission timeout (line 183) that's up to ~12k process spawns while the prompt is open — needless overhead for what should be a simple delay. Since the script already uses top-levelawait(line 156), this can be replaced with a native async sleep with no subprocess cost.♻️ Proposed fix: replace subprocess sleep with async setTimeout
-function waitDecision(id, timeoutMs) { +async function waitDecision(id, timeoutMs) { ensureDir(PENDING); const specific = path.join(PENDING, `${id}.decision`); const latest = path.join(PENDING, "latest.decision"); const interruptFlag = path.join(PENDING, "interrupt", `${id}.flag`); const start = Date.now(); while (Date.now() - start < timeoutMs) { for (const file of [specific, latest]) { try { if (fs.existsSync(file)) { const decision = fs.readFileSync(file, "utf8").trim(); fs.unlinkSync(file); return decision; } } catch { /* ignore */ } } try { if (fs.existsSync(interruptFlag)) { fs.unlinkSync(interruptFlag); return "deny_interrupt"; } } catch { /* ignore */ } - try { - execFileSync("sleep", ["0.05"], { stdio: "ignore" }); - } catch { - /* ignore */ - } + await new Promise((resolve) => setTimeout(resolve, 50)); } return null; }Then update the call site:
const decision = await waitDecision(id, 10 * 60 * 1000);🤖 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 `@adapters/claude/hooks/microbridge-permission.mjs` around lines 88 - 121, Update waitDecision to be asynchronous and replace the execFileSync("sleep", ...) polling delay with a native setTimeout-based await. Update its call site to await waitDecision(id, 10 * 60 * 1000), preserving the existing decision, interrupt, and timeout behavior.apps/microbridge-ui/src/surfaces/Settings.tsx (1)
698-707: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHelp text still says "Idle" though the tile now reads "Ready · idle".
The Synara/desktop-apps paragraph reads "Idle means waiting for sessions, not a broken setup," but the corresponding label was changed to "Ready · idle" (see
hosts.tslines 138-144). Consider updating the copy to match the actual displayed term for consistency.🤖 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/microbridge-ui/src/surfaces/Settings.tsx` around lines 698 - 707, Update the Synara and desktop-apps help text in the Settings component to refer to “Ready · idle” instead of “Idle,” matching the label rendered by the corresponding host tile while preserving the existing explanation about waiting for sessions.apps/microbridge-ui/src/components/MeshBackground.tsx (1)
41-156: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCanvas mesh animation doesn't respect
prefers-reduced-motion.The rAF loop runs continuously (throttled only by
document.hidden), while this same PR adds aprefers-reduced-motion: reducemedia query inindex.cssfor the other new pulse/breathe/drain animations. For consistency, consider skipping the animated redraw (or drawing a single static frame) when the user has reduced motion enabled.♻️ Suggested guard
resize(); const observer = new ResizeObserver(resize); observer.observe(parent); + + const reduceMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ).matches;const draw = () => { - if (document.hidden) { + if (document.hidden || reduceMotion) { rafRef.current = requestAnimationFrame(draw); return; }🤖 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/microbridge-ui/src/components/MeshBackground.tsx` around lines 41 - 156, Update the MeshBackground useEffect animation flow to honor the prefers-reduced-motion media setting: detect whether the user prefers reduced motion, draw one static canvas frame without starting the continuous requestAnimationFrame loop when enabled, and retain the existing animated loop otherwise. Ensure resize and cleanup behavior remain intact.
🤖 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/microbridge-ui/src-tauri/src/lib.rs`:
- Around line 528-542: Update install_claude_hooks to load
microbridge-permission.mjs from a packaged Tauri resource available at runtime,
rather than relying on source_candidates and the hardcoded developer checkout
path. Add the hook script to the Tauri bundle resources configuration and
preserve the existing destination-copy behavior.
In `@apps/microbridge-ui/src/components/IntegrationCard.tsx`:
- Around line 48-50: Update the status-dot class condition in IntegrationCard to
apply mb-status-pulse for yellow only when the label exactly represents the
transitional connecting state, rather than using an includes("connect")
substring check that also matches "Not connected". Preserve the existing green
and busy pulsing behavior.
In `@apps/microbridge-ui/src/surfaces/Settings.tsx`:
- Around line 836-871: Update the enable CTA button in the Settings component’s
guidance.primaryAction === "enable" branch to use a fixed actionable label such
as “Install” or “Enable” instead of guidance.title. Keep guidance.title
unchanged for the surrounding panel heading and preserve the existing enable
operation behavior.
In `@crates/microbridged/src/codex_control.rs`:
- Around line 116-169: The Approve and Reject branches in dispatch_action
incorrectly originate requestApproval calls instead of replying to the pending
server request. Update the proxy flow and poll_and_respond_approval to remain
attached to the app-server, capture the pending request ID, and send a JSON-RPC
response containing that id and result.decision set to accept or decline,
preserving the existing error reporting when no live approval request is
available.
In `@crates/microbridged/src/state.rs`:
- Around line 691-701: The session reconciliation path in
reconcile_observed_sessions currently re-upserts cached sessions with owner 0,
losing existing control-plane ownership. Preserve and reapply each session’s
original owner when rebuilding observed sessions, while retaining owner 0 for
genuinely lifecycle-only sessions so Approve, Reject, and Interrupt continue to
work when a control plane is attached.
In `@docs/protocol.md`:
- Line 167: Update the host-state mapping in hosts.ts so Cursor lifecycle-only
sessions with state "connected" and lifecycle_observation capability display
"Connected · lifecycle", while preserving the existing "limited" behavior. Then
update the Cursor fixtures and related tests to cover and expect this
lifecycle-only connected status.
---
Nitpick comments:
In `@adapters/claude/hooks/microbridge-permission.mjs`:
- Around line 88-121: Update waitDecision to be asynchronous and replace the
execFileSync("sleep", ...) polling delay with a native setTimeout-based await.
Update its call site to await waitDecision(id, 10 * 60 * 1000), preserving the
existing decision, interrupt, and timeout behavior.
In `@apps/microbridge-ui/src/components/MeshBackground.tsx`:
- Around line 41-156: Update the MeshBackground useEffect animation flow to
honor the prefers-reduced-motion media setting: detect whether the user prefers
reduced motion, draw one static canvas frame without starting the continuous
requestAnimationFrame loop when enabled, and retain the existing animated loop
otherwise. Ensure resize and cleanup behavior remain intact.
In `@apps/microbridge-ui/src/surfaces/Settings.tsx`:
- Around line 698-707: Update the Synara and desktop-apps help text in the
Settings component to refer to “Ready · idle” instead of “Idle,” matching the
label rendered by the corresponding host tile while preserving the existing
explanation about waiting for sessions.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6411b2df-b775-4408-953c-6e2c3c7041b1
⛔ Files ignored due to path filters (1)
apps/microbridge-ui/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (34)
AGENTS.mdadapters/claude/README.mdadapters/claude/hooks/microbridge-permission.mjsadapters/cursor-acp/README.mdadapters/cursor/README.mdadapters/cursor/hooks/event.mjsadapters/cursor/hooks/hooks.jsonapps/microbridge-ui/src-tauri/src/lib.rsapps/microbridge-ui/src/components/DeviceEcho.tsxapps/microbridge-ui/src/components/DeviceTwin.tsxapps/microbridge-ui/src/components/IntegrationCard.tsxapps/microbridge-ui/src/components/MeshBackground.tsxapps/microbridge-ui/src/index.cssapps/microbridge-ui/src/lib/bus.tsapps/microbridge-ui/src/lib/hosts.test.tsapps/microbridge-ui/src/lib/hosts.tsapps/microbridge-ui/src/lib/integrationIcons.tsapps/microbridge-ui/src/lib/integrationSetup.test.tsapps/microbridge-ui/src/lib/integrationSetup.tsapps/microbridge-ui/src/surfaces/Settings.tsxapps/microbridge-ui/src/surfaces/surfaces.test.tsxcrates/mb-adapters/src/cursor.rscrates/mb-adapters/src/lib.rscrates/microbridged/src/auto_discover.rscrates/microbridged/src/claude_control.rscrates/microbridged/src/codex_control.rscrates/microbridged/src/cursor_acp.rscrates/microbridged/src/lib.rscrates/microbridged/src/main.rscrates/microbridged/src/mcp.rscrates/microbridged/src/state.rsdocs/lean-overhead.mddocs/project-tracking.mddocs/protocol.md
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@crates/microbridged/src/mcp.rs`:
- Around line 89-122: Harden read_http_request by enforcing a maximum
Content-Length/body size before entering the body-reading loop, returning None
for oversized requests, and applying a per-read timeout to every stream.read
call used for headers and body. Preserve complete request-body reads within the
limit, but abort stalled or incomplete connections so unauthenticated clients
cannot cause unbounded memory growth or hold tasks indefinitely.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 542b55c3-436e-48d7-bcf2-dfed538a413e
📒 Files selected for processing (9)
.gitignoreadapters/claude/hooks/microbridge-permission.mjsapps/microbridge-ui/src-tauri/src/lib.rsapps/microbridge-ui/src-tauri/tauri.conf.jsonapps/microbridge-ui/src/components/IntegrationCard.tsxcrates/microbridged/src/claude_control.rscrates/microbridged/src/codex_control.rscrates/microbridged/src/mcp.rscrates/microbridged/src/state.rs
🚧 Files skipped from review as they are similar to previous changes (6)
- apps/microbridge-ui/src/components/IntegrationCard.tsx
- apps/microbridge-ui/src-tauri/src/lib.rs
- crates/microbridged/src/claude_control.rs
- adapters/claude/hooks/microbridge-permission.mjs
- crates/microbridged/src/codex_control.rs
- crates/microbridged/src/state.rs
Keep observed-session owners across reconcile, reply to Codex server approval request IDs, bound MCP request reads, and tighten UI CTA/lifecycle/pulse labels.
What
Real Micro control for Codex (app-server attach-on-demand), Claude (PermissionRequest decision-file bridge), and Cursor ACP (bus-bound sessions), plus Cursor≈Claude parity hooks/watcher, fail-closed owner-0 routing, dormant MCP by default, and UI aliveness (Ready·idle, status pulse, Agent Key LEDs). Includes the prior integrations setup guidance + settings mesh commit.
Why
Micro should drive real Interrupt / Approve / Reject levers without false capability chips or new always-on poll loops. Zed and Cmux stay deferred until the existing twelve hosts are lever-complete.
How was this tested?
cargo test -p microbridged --lib(47 passed)cargo clippy -p microbridged -- -D warningscargo fmtnpm test -- --runinapps/microbridge-ui(28 passed)cargo checkinapps/microbridge-ui/src-tauriChecklist
cargo fmt/clippy -D warnings/cargo testpass locallymb-protocolanddocs/protocol.md(docs/protocol.md only; no wire protocol change)docs/lean-overhead.md; MCP dormant unlessMICROBRIDGE_MCP=1; no new poll loopsNotes
Summary by CodeRabbit
New Features
Bug Fixes
Documentation