Skip to content

feat: add orca notify — agent-triggered pane notifications (desktop + mobile)#7956

Open
saurabh wants to merge 8 commits into
stablyai:mainfrom
saurabh:feat/notify-dispatch
Open

feat: add orca notify — agent-triggered pane notifications (desktop + mobile)#7956
saurabh wants to merge 8 commits into
stablyai:mainfrom
saurabh:feat/notify-dispatch

Conversation

@saurabh

@saurabh saurabh commented Jul 9, 2026

Copy link
Copy Markdown

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.

orca notify --message "Tests passed — ready for review"
orca notify --pane term_abc123 --title "Blocked" --message "Needs your input on the migration"
orca notify --worktree active --message "Deploy finished"

Important

Stacked on #7930 (feat/focus-deep-link). This PR is branched off that PR's head, so the diff against main includes #7930's commits. Only the commits from feat(notifications): add notifications.dispatch … onward belong to this PR. Merge #7930 first, then this rebases cleanly onto main. (#7930 reuses runtime.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 to worktreeId + paneKey using the exact resolution terminal focus uses (resolveActiveTerminalshowTerminal), 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 via 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.

B. CLI verb (orca notify)src/cli/handlers/notify.ts, mirroring the terminal handlers. --pane <handle> (with --terminal alias) 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 with terminal wait).

C. Pane-granular mobile tap — previously dispatchMobileNotification dropped paneKey and mobile taps stopped at /h/<host>/session/<worktreeId>. Now paneKey is 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 vetted switchTab path once its tab loads. Failure degrades to the pre-existing worktree-level view.

The desktop click handler already routes worktreeId+paneKey to 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/title are length-bounded before rendering. No new inbound-text surface.

Testing

  • New RPC unit tests: src/main/runtime/rpc/methods/notifications.test.ts (pane resolution, worktree fallback, bare notify with no terminal, explicit-target error, missing-message rejection).
  • Extended src/main/ipc/notifications.test.ts: dispatch source builds the custom title/body + threads paneKey to mobile; the injected runtime dispatcher honors the global enable toggle.
  • Extended mobile/src/notifications/notification-routing.test.ts: paneKey?pane= and local-data threading.
  • pnpm typecheck (node/cli/web) ✅ · mobile tsc --noEmit ✅ · oxlint clean on changed files ✅ · check:max-lines-ratchet ✅ (no new suppressions) · full CLI + RPC suites (899 tests) ✅.
  • pnpm build:desktop ✅; verified orca notify --help renders 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 7d40f69 fixed 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.

saurabh and others added 6 commits July 9, 2026 16:46
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>
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8a7ef8cd-4df2-4222-bcc8-ec8450fc9ec9

📥 Commits

Reviewing files that changed from the base of the PR and between 7d40f69 and 64d2197.

📒 Files selected for processing (5)
  • mobile/src/notifications/notification-routing.ts
  • src/cli/handlers/notify.ts
  • src/main/ipc/notification-options.ts
  • src/main/ipc/notifications.ts
  • src/main/runtime/rpc/methods/notifications.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/cli/handlers/notify.ts
  • src/main/ipc/notification-options.ts
  • src/main/runtime/rpc/methods/notifications.ts
  • src/main/ipc/notifications.ts
  • mobile/src/notifications/notification-routing.ts

📝 Walkthrough

Walkthrough

This PR introduces an orca:// deep-link protocol with dispatcher wiring in Electron startup, single-instance handling, and macOS open-url events. It also adds a new dispatch notification source that carries optional pane and worktree targeting through shared types, the runtime, RPC, IPC delivery, a new orca notify CLI command, mobile notification routing, and session pane focusing.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main change: adding the orca notify command for agent-triggered pane notifications.
Description check ✅ Passed All required sections are present and substantive, with clear summary, testing, AI review, security, and notes.
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.

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 92f72365-50b7-4423-a4e2-7468918c5c11

📥 Commits

Reviewing files that changed from the base of the PR and between 8adfef4 and 16970ff.

📒 Files selected for processing (25)
  • config/electron-builder.config.cjs
  • mobile/app/h/[hostId]/session/[worktreeId].tsx
  • mobile/src/notifications/mobile-notifications.ts
  • mobile/src/notifications/notification-routing.test.ts
  • mobile/src/notifications/notification-routing.ts
  • src/cli/args.ts
  • src/cli/dispatch.ts
  • src/cli/handlers/notify.ts
  • src/cli/help.ts
  • src/cli/specs/core.ts
  • src/main/deep-link/deep-link-dispatcher.test.ts
  • src/main/deep-link/deep-link-dispatcher.ts
  • src/main/deep-link/orca-deep-link.test.ts
  • src/main/deep-link/orca-deep-link.ts
  • src/main/index.ts
  • src/main/ipc/notification-options.ts
  • src/main/ipc/notifications.test.ts
  • src/main/ipc/notifications.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/rpc/methods/notifications.test.ts
  • src/main/runtime/rpc/methods/notifications.ts
  • src/main/startup/single-instance-lock.test.ts
  • src/main/startup/single-instance-lock.ts
  • src/shared/runtime-types.ts
  • src/shared/types.ts

Comment thread mobile/app/h/[hostId]/session/[worktreeId].tsx
saurabh and others added 2 commits July 10, 2026 00:51
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>
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.

2 participants