feat: add orca notify — agent-triggered pane notifications (desktop + mobile)#7956
feat: add orca notify — agent-triggered pane notifications (desktop + mobile)#7956saurabh wants to merge 8 commits into
orca notify — agent-triggered pane notifications (desktop + mobile)#7956Conversation
Register `orca://` as an OS custom protocol and add a `focus` route so a link such as `orca://focus?terminal=term_<id>` (or `?worktree=<selector>`) brings Orca to the foreground and reveals that terminal pane. This is the "click a link → jump straight to that agent's tab" capability for notifications and external tooling. Focus reuses the existing `runtime.focusTerminal` action — the same one `orca terminal focus` drives — rather than reinventing pane reveal. Intake covers every platform path: macOS `open-url` (warm + cold), Windows/Linux `second-instance` argv, and first-launch `process.argv`. Security: a deep link can be fired by any web page, so the route only focuses/reveals a local pane. It never executes commands, sends input, or mutates state, and unknown/exited targets degrade to a no-op after bringing the window forward. `orca://pair` stays paste-only — it is intentionally not an OS route, so a link can't apply runtime auth material. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add JSDoc to the exported deep-link and single-instance-lock functions (and the substantive parse/dispatch helpers) to clear CodeRabbit's docstring-coverage check. Also harden the cold-start path surfaced by field testing: a protocol launch can cold-start Orca, and the renderer graph can take tens of seconds to report `ready` on a fresh boot. The focus intent is already buffered by polling and the window is surfaced immediately (dispatch is fire-and-forget, so boot is never blocked), but the 15s readiness budget was shorter than a real cold boot and would silently drop the terminal reveal. Extend the budget to 60s so the queued focus lands once the graph is ready, and gracefully no-op afterward with the app already foregrounded. Add tests covering the buffered cold-start apply and the window-surfaced- before-graph-wait (non-blocking) ordering. No behavior change beyond the readiness budget; focus/reveal only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dispatcher Agents/orchestrators had no way to fire an Orca notification on demand: notifications were only auto-emitted by the renderer on agent-idle / terminal-bell, and the runtime RPC surface exposed only notifications.subscribe/unsubscribe (the outbound mobile push stream). An agent in a pane — which talks to the runtime RPC — could not dispatch one, forcing a shell-out to a third-party push service. Add a `notifications.dispatch` runtime RPC method carrying an arbitrary message + optional title, resolving the selected terminal to a worktreeId + paneKey with the exact resolution `terminal focus` uses (resolveActiveTerminal → showTerminal). The native desktop click handler already routes worktreeId+paneKey to the pane, so a tap lands on the exact split; the mobile push already fans out over the paired WebSocket. Reuse, don't duplicate: extract the native+mobile delivery core (performNotificationDelivery) from the renderer IPC handler and inject it into the runtime as a dispatcher (setNotificationDispatcher), so a dispatched notification takes the identical path as an auto-emitted one and honors the global enable toggle. The renderer-only gates (tray attention, focus suppression, burst dedupe) stay in the IPC handler. Security: the RPC validates inputs (zod) and only fires a notification — it never executes commands or injects text into a terminal. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`orca notify --message <text> [--title <text>] [--pane <handle>|--worktree <selector>] [--json]` fires a native Orca notification (desktop + paired mobile) whose tap deep-links to the named pane. Mirrors the terminal command handlers: resolves the pane selector the same way `terminal focus` does and calls the `notifications.dispatch` runtime RPC. Non-delivery is a soft failure surfaced via a non-zero exit code, matching `terminal wait`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mobile taps were worktree-granular: dispatchMobileNotification dropped paneKey and the navigation path stopped at /h/<host>/session/<worktreeId>. Thread paneKey through the mobile notification event and local notification data, append it to the session route as `?pane=<paneKey>`, and have the session screen resolve that pane key to a live handle (terminal.resolvePane) and focus it through the vetted switchTab path once its tab loads. Failure degrades to the pre-existing worktree-level view. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
notify targets a terminal/worktree, not a browser page, so exclude it from the browser-page flag set — same treatment as terminal/worktree/file. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughThis PR introduces an 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 92f72365-50b7-4423-a4e2-7468918c5c11
📒 Files selected for processing (25)
config/electron-builder.config.cjsmobile/app/h/[hostId]/session/[worktreeId].tsxmobile/src/notifications/mobile-notifications.tsmobile/src/notifications/notification-routing.test.tsmobile/src/notifications/notification-routing.tssrc/cli/args.tssrc/cli/dispatch.tssrc/cli/handlers/notify.tssrc/cli/help.tssrc/cli/specs/core.tssrc/main/deep-link/deep-link-dispatcher.test.tssrc/main/deep-link/deep-link-dispatcher.tssrc/main/deep-link/orca-deep-link.test.tssrc/main/deep-link/orca-deep-link.tssrc/main/index.tssrc/main/ipc/notification-options.tssrc/main/ipc/notifications.test.tssrc/main/ipc/notifications.tssrc/main/runtime/orca-runtime.tssrc/main/runtime/rpc/methods/notifications.test.tssrc/main/runtime/rpc/methods/notifications.tssrc/main/startup/single-instance-lock.test.tssrc/main/startup/single-instance-lock.tssrc/shared/runtime-types.tssrc/shared/types.ts
The pane-focus guard (routePaneFocusAppliedRef) and the resolved-handle cache (routePaneResolvedHandleRef) were one-shot and keyed to nothing. Expo reuses the mounted session-screen component across navigations, so a second notification tap that changes `?pane=` to a different split short-circuited at the guard — and even past it would re-focus the first tap's stale handle. Reset both refs whenever routePaneKey changes so each tap re-resolves and focuses its own pane; failure still degrades to the worktree-level view. Addresses CodeRabbit review on stablyai#7956. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Adds a native way for an agent/orchestrator to fire an Orca notification on demand — carrying an arbitrary message (e.g. an AI summary) and deep-linking the tap to a specific terminal pane on desktop and mobile.
Today notifications are only auto-emitted by the renderer on agent-idle / terminal-bell, and the runtime RPC surface exposes only
notifications.subscribe/unsubscribe(the outbound mobile push stream). An agent running in a pane — which talks to the runtime RPC — had no way to dispatch one, which is the single missing trigger that otherwise forces shelling out to a third-party push service.Important
Stacked on #7930 (
feat/focus-deep-link). This PR is branched off that PR's head, so the diff againstmainincludes #7930's commits. Only the commits fromfeat(notifications): add notifications.dispatch …onward belong to this PR. Merge #7930 first, then this rebases cleanly ontomain. (#7930 reusesruntime.focusTerminal; this PR reuses the same click→pane routing.)What's in it
A. Runtime RPC dispatch (
notifications.dispatch) —src/main/runtime/rpc/methods/notifications.ts. Resolves the selected terminal toworktreeId+paneKeyusing the exact resolutionterminal focususes (resolveActiveTerminal→showTerminal), then fires the notification. Reuses the delivery core rather than duplicating it: the native+mobile delivery (performNotificationDelivery) is extracted from the renderer IPC handler and injected into the runtime viasetNotificationDispatcher, so a dispatched notification takes the identical path as an auto-emitted one and honors the global enable toggle. The renderer-only gates (tray attention, focus suppression, burst dedupe) stay in the IPC handler.B. CLI verb (
orca notify) —src/cli/handlers/notify.ts, mirroring the terminal handlers.--pane <handle>(with--terminalalias) or--worktree <selector>picks the pane the tap focuses; omit both to target the active terminal in the current worktree. Non-delivery is a soft failure surfaced via non-zero exit code (parity withterminal wait).C. Pane-granular mobile tap — previously
dispatchMobileNotificationdroppedpaneKeyand mobile taps stopped at/h/<host>/session/<worktreeId>. NowpaneKeyis threaded through the mobile notification event + local notification data, appended to the session route as?pane=<paneKey>, and the session screen resolves it to a live handle (terminal.resolvePane) and focuses that split through the vettedswitchTabpath once its tab loads. Failure degrades to the pre-existing worktree-level view.The desktop click handler already routes
worktreeId+paneKeyto the exact pane (unchanged), so click-to-focus works the moment those fields are populated.Security
The RPC validates inputs with zod and only fires a notification — it never executes commands or injects text into a terminal.
message/titleare length-bounded before rendering. No new inbound-text surface.Testing
src/main/runtime/rpc/methods/notifications.test.ts(pane resolution, worktree fallback, bare notify with no terminal, explicit-target error, missing-message rejection).src/main/ipc/notifications.test.ts:dispatchsource builds the custom title/body + threadspaneKeyto mobile; the injected runtime dispatcher honors the global enable toggle.mobile/src/notifications/notification-routing.test.ts:paneKey→?pane=and local-data threading.pnpm typecheck(node/cli/web) ✅ ·mobiletsc --noEmit✅ ·oxlintclean on changed files ✅ ·check:max-lines-ratchet✅ (no new suppressions) · full CLI + RPC suites (899 tests) ✅.pnpm build:desktop✅; verifiedorca notify --helprenders from the built CLI.🤖 Generated with Claude Code
Screenshots
N/A - this is a CLI/runtime notification dispatch path with desktop/mobile tap routing; there is no new persistent visual UI state to capture.
AI Review Report
CodeRabbit reviewed the branch and raised one Major inline finding about the mobile pane-focus guard. Commit
7d40f69fixed it, Saurabh replied in-thread, and CodeRabbit confirmed the thread is fully addressed/resolved. This follow-up also adds JSDoc to the branch-specific exported notification helpers in response to the pre-merge docstring warning.Notes
This remains stacked on #7930. The mobile signing/build step is intentionally not part of this PR follow-up; existing source checks and targeted notification tests cover the code paths changed here.