Skip to content

feat(desktop): native mobile push integration (2/3) - #19935

Merged
Jinwoo-H merged 3 commits into
mainfrom
feat/mobile-push-desktop
Sep 11, 2026
Merged

Jinwoo-H merged 3 commits into
mainfrom
feat/mobile-push-desktop

Conversation

@Jinwoo-H

@Jinwoo-H Jinwoo-H commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 44 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3073 $\color{#cf222e}{\Huge{\mathbf{−}}}$​36 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3037
Prod 65 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2613 $\color{#cf222e}{\Huge{\mathbf{−}}}$​317 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​2296

ELI5

Connects the desktop runtime to Orca's native push gateway. A paired phone can register for notifications, and the desktop forwards eligible events and dismissals while preserving desktop notification settings and the three-minute away policy.

This is PR 2 of the cloud → desktop → mobile split. Cloud landed in #19912; mobile adoption follows separately.

What Changed

  • Gateway host proof/session, device registration, delivery filtering and seven-day mobile-use lease.
  • Durable unregister cleanup, serialized per device; failed local unpair/removal preserves a still-live gateway registration. Explicit re-registration clears old cleanup first.
  • Electron and headless runtime startup/shutdown, notification RPC capability and authenticated mobile-only registration.
  • Desktop presence and auto-acknowledgement: leaving a pane selected while away does not silently clear attention. Focused web clients retain browser behavior; irrelevant store writes do not trigger idle queries.
  • Legacy socket subscriptions/reconnect retain filtering and cooldown defaults. New optional fields support push and dismissal reconciliation.

Why

The cloud layer needs an authenticated desktop sender and lifecycle owner. Extracts the reviewed desktop implementation from #19204 onto current main, preserving newer mainline changes. No mobile app changes or cloud infrastructure changes are included.

Linked Issue

Split from #19204; follows merged #19912.

Visual Proof

No new controls or layouts. Hidden Electron smoke verified the branch identity, rendered app shell, and real desktop-away IPC. Focus/idle acknowledgement behavior is exercised by deterministic renderer tests; a native-focus before/after recording was not performed on the user's active display. Full device acceptance belongs to the mobile layer.

Testing

  • Automated tests added/updated.
  • 302 changed-file tests passed. Additional remote-runtime/Relay proof and RPC boundary tests passed.
  • Failed-unpair regression fails with the fix removed and passes with it; recovery after later removal and explicit re-registration are covered.
  • Node/web/CLI typechecks passed; changed-code quality found zero new findings; reliability manifest passed.
  • Electron main/preload development builds and hidden renderer smoke passed on macOS using an isolated profile.
  • Legacy live/replay filter tests, real runtime connection tests and headed/headless startup contract tests exercise compatibility boundaries. No live paired-host, SSH outage, Windows/Linux native-focus or physical-phone test in this extraction pass; these tests do not establish provider delivery.
  • Hosted CI passed: eight test shards, static analysis/typechecks, cross-version wire, macOS/Windows packaging, changed-spec E2E and SSH Docker watcher. No deployment or production database mutation.

Review

Review registration/outbox ownership first, then event policy and presence, then RPC/startup wiring. The shared host challenge envelope reuses the existing Relay proof primitive. The original combined branch remains preserved; mobile source stays for PR 3.

Agent skill upstream boundary

  • Not applicable; no upstream skill-installer implementation copied.

Checklist

  • Scope is desktop/runtime integration only.
  • Self-reviewed source equivalence and fresh-main conflicts.
  • Cross-platform, folder-workspace and independent client/server versions considered.
  • Local focused tests, typechecks and quality checks passed.
  • Hosted CI for latest review fixes completed (prior head passed).

Diff size

100 files; +5,457/-349. Tests/fixtures: +3,136/-32; implementation/docs/config: +2,321/-317. Counts include comments and blank lines.

Review fixes

973197ae7df preserves the legacy socket eligibility decision at event recording, so reconnect cannot resurrect a cooldown-suppressed alert even after earlier buffer entries are evicted. Removes per-subscription cooldown recalculation. Also protects unreadable dismissal history, rejects malformed persisted boolean preferences, gives floating panes precedence on colliding tab IDs, reports shutdown as retryable, and fills five locale translations.

159 focused notification tests passed, all root typechecks and changed-code quality passed. New HEAD hosted CI pending. Existing-client regression runs the real controller and subscription/replay RPC handlers. No new deployment or physical-device test.

@Jinwoo-H
Jinwoo-H marked this pull request as ready for review September 10, 2026 22:10

@pullfrog pullfrog 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.

ℹ️ No blocking issues — two minor rough edges noted below.

Reviewed changes

  • Push gateway client + session — challenge/proof handshake, session caching/negative-refusal backoff, redirect-free and deadline-bounded fetch, register/delete/send with result-vs-throw semantics.
  • Delivery + registration policy — DesktopPushService with serialized per-device registration, durable unregister outbox (PushUnregisterOutbox), capped retry backoff, seven-day mobile lease with explicit-renewal-only, and PushDispatcher fan-out with per-phone sound/burst filtering and dead-registration pruning.
  • Shared proof envelope — host-challenge-envelope.ts extracted from relay-host-proof.ts (relay refactored onto it), plus the push-specific transcript validation and cross-workspace proof vector.
  • RPC surface — notifications.registerPush / notifications.unregisterPush (mobile-authenticated), includeDesktopSuppressed / deliveredPushes reconciliation on the notification stream, and the notifications.remote-push.v1 capability flag.
  • Presence & auto-ack — desktop-away-state three-minute boundary, renderer auto-ack gated on native idle presence, and the completion-tracking refactor that moves desktop-notification gating into main so mobile delivery stays independent.

ℹ️ Nitpicks

  • src/shared/mobile-push-contract.ts, src/main/runtime/push/push-host-proof.ts, and src/main/runtime/push/push-gateway-client.ts all cite docs/reference/mobile-push-contract.md, but that file doesn't exist in the repo (the cloud half lives under cloud/packages/push-contract/). Either land the doc here or repoint the comments.
  • The new notifications.agentStatus.* strings were added only to en.json, while buildAgentTaskCompleteNotificationOptions runs through main-process translateMain, which never reads en.json (English comes from the fallback argument) and lazy-loads only es/fr/ja/ko/zh. Non-English desktops will show needs input / working / stopped / finished in English until those five locale files receive the keys.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@coderabbitai

coderabbitai Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 5933ea04-ac28-4d2d-b66c-2edec916ade1

📥 Commits

Reviewing files that changed from the base of the PR and between 973197a and 5265c79.

📒 Files selected for processing (6)
  • src/renderer/src/hooks/useAutoAckViewedAgent.test.ts
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/src/i18n/locales/fr.json

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

Adds end-to-end mobile push support for desktop and headless runtimes. The change adds authenticated gateway sessions, push registration persistence, durable unregister cleanup, delivery filtering, dismissal reconciliation, desktop-away detection, startup wiring, RPC methods, localized notification states, and renderer auto-ack presence checks. It also adds unit and integration coverage, documentation, reliability-gate metadata, and shared URL and protocol contracts.

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to 5265c

The changed localization and auto-acknowledgement behavior are consistent, but a narrow shutdown race may delay push-registration cleanup after shutdown. This is bounded lifecycle risk suitable for owner follow-up.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 59 files. (5 skipped:… 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 clearly identifies the main change: native mobile push integration in the desktop runtime. It is concise and specific.
Description check ✅ Passed The description is mostly complete. It includes the ELI5 summary, changes, rationale, linked issues, visual-proof explanation, testing results, review guidance, scope boundary, and checklist. The AI D…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 15.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 64 functions across 59 files. (5 skipped: 5 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

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: 3

🧹 Nitpick comments (2)
src/main/runtime/mobile-notification-dismissal-store.ts (1)

57-72: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Keep dismissedThrough monotonic in the dismiss branch.

The dismiss branch removes the entry for the same notificationId and notificationEpoch, then pushes a replacement with dismissedThrough: event.notificationSeq. The prior watermark is lost. If a dismiss record arrives with a lower notificationSeq than one already recorded, reconcile() stops reporting the higher sequence as dismissed and the alert reappears.

The notification branch already preserves the prior value at line 53. Use the maximum here for the same invariant.

♻️ Proposed fix to preserve the highest dismissed sequence
     } else {
+      const priorDismissedThrough = kept.find(same)?.dismissedThrough ?? -1
       next = kept
         .filter((entry) => !same(entry))
         .map((entry) =>
           entry.notificationId === event.notificationId
             ? { ...entry, dismissedThrough: entry.notificationSeq, expiresAt: now + RETENTION_MS }
             : entry
         )
       next.push({
         notificationId: event.notificationId,
         notificationEpoch: event.notificationEpoch,
         notificationSeq: event.notificationSeq,
-        dismissedThrough: event.notificationSeq,
+        dismissedThrough: Math.max(priorDismissedThrough, event.notificationSeq),
         expiresAt: now + RETENTION_MS
       })
     }
src/main/runtime/push/desktop-push-service.ts (1)

135-137: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Return a retryable reason when the service is stopped.

After the outbox await, this.stopped can be true while the device scope remains 'mobile'. The combined condition then returns not_mobile, which misclassifies shutdown and can stop client retries. Return gateway_unreachable for the stopped case.

♻️ Proposed split of the two conditions
-    if (this.registry.getDevice(input.deviceId)?.scope !== 'mobile' || this.stopped) {
+    if (this.registry.getDevice(input.deviceId)?.scope !== 'mobile') {
       return { registered: false, reason: 'not_mobile' }
     }
+    if (this.stopped) {
+      return { registered: false, reason: 'gateway_unreachable' }
+    }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 92ff2f1d-86cf-492f-987c-b3463f64a015

📥 Commits

Reviewing files that changed from the base of the PR and between eb2f2d5 and 67306cb.

📒 Files selected for processing (100)
  • config/reliability-gates.jsonc
  • docs/reference/headless-linux-server.md
  • src/main/global-fetch-call-site-audit.test.ts
  • src/main/host/electron-runtime-desktop-surface.ts
  • src/main/ipc/notification-burst-cooldown.ts
  • src/main/ipc/notification-options.ts
  • src/main/ipc/notifications-message-formatting.test.ts
  • src/main/ipc/notifications-mobile-fanout.test.ts
  • src/main/ipc/notifications.ts
  • src/main/notifications/desktop-away-state.test.ts
  • src/main/notifications/desktop-away-state.ts
  • src/main/orca-profiles/profile-cloud-auth-config.ts
  • src/main/orcad/orcad-entry.ts
  • src/main/orcad/orcad-push-startup.test.ts
  • src/main/runtime/device-registry.ts
  • src/main/runtime/host-challenge-envelope.ts
  • src/main/runtime/mobile-notification-dismissal-store.test.ts
  • src/main/runtime/mobile-notification-dismissal-store.ts
  • src/main/runtime/push/desktop-push-service-unreadable-outbox.test.ts
  • src/main/runtime/push/desktop-push-service.test.ts
  • src/main/runtime/push/desktop-push-service.ts
  • src/main/runtime/push/push-agent-state.test.ts
  • src/main/runtime/push/push-cleanup-auth-expiry.test.ts
  • src/main/runtime/push/push-delivery-policy.test.ts
  • src/main/runtime/push/push-device-registration-persistence.test.ts
  • src/main/runtime/push/push-dispatcher.test-fixture.ts
  • src/main/runtime/push/push-dispatcher.test.ts
  • src/main/runtime/push/push-dispatcher.ts
  • src/main/runtime/push/push-gateway-client.test.ts
  • src/main/runtime/push/push-gateway-client.ts
  • src/main/runtime/push/push-gateway-origin.ts
  • src/main/runtime/push/push-gateway-response.ts
  • src/main/runtime/push/push-gateway-session.test.ts
  • src/main/runtime/push/push-gateway-session.ts
  • src/main/runtime/push/push-host-challenge-fixtures.ts
  • src/main/runtime/push/push-host-proof-vector.test.ts
  • src/main/runtime/push/push-host-proof.test.ts
  • src/main/runtime/push/push-host-proof.ts
  • src/main/runtime/push/push-outcome-counters.test.ts
  • src/main/runtime/push/push-outcome-counters.ts
  • src/main/runtime/push/push-policy-pipeline.integration.test.ts
  • src/main/runtime/push/push-preferences.test.ts
  • src/main/runtime/push/push-register-throttle.ts
  • src/main/runtime/push/push-registration-races.test.ts
  • src/main/runtime/push/push-registration-rpc.test.ts
  • src/main/runtime/push/push-unpair-persistence.test.ts
  • src/main/runtime/push/push-unregister-outbox.test.ts
  • src/main/runtime/push/push-unregister-outbox.ts
  • src/main/runtime/relay/relay-host-proof.ts
  • src/main/runtime/remote-runtime-close-intent.integration.test.ts
  • src/main/runtime/remote-runtime-request-connection.integration.test.ts
  • src/main/runtime/rpc/methods/notification-preferences.test.ts
  • src/main/runtime/rpc/methods/notification-stream-policy.ts
  • src/main/runtime/rpc/methods/notifications.ts
  • src/main/runtime/runtime-desktop-surface.ts
  • src/main/runtime/runtime-mobile-notification-controller.ts
  • src/main/runtime/runtime-rpc-mobile-method-allowlist-fixtures.ts
  • src/main/runtime/runtime-rpc-mobile-terminal-streaming.test.ts
  • src/main/runtime/runtime-rpc-request-authorization.test.ts
  • src/main/runtime/runtime-rpc-websocket-long-poll-caps.test.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-mobile-method-allowlist.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-pairing.ts
  • src/main/runtime/runtime-rpc/runtime-rpc-state.ts
  • src/main/runtime/runtime-service-command-surface.ts
  • src/main/startup/main-process-push-startup.ts
  • src/main/startup/main-process-quit.ts
  • src/main/startup/main-process-relay-status.ts
  • src/main/startup/main-process-runtime-launch.ts
  • src/main/startup/main-process-state.ts
  • src/preload/api/notifications-bridge.ts
  • src/preload/api/os-permission-api.ts
  • src/renderer/src/components/terminal-pane/agent-task-complete-policy.ts
  • src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.test.ts
  • src/renderer/src/components/terminal-pane/parked-terminal-byte-watcher.ts
  • src/renderer/src/components/terminal-pane/pty-connection-notification-settings-gating.test.ts
  • src/renderer/src/components/terminal-pane/pty-connection-types.ts
  • src/renderer/src/components/terminal-pane/pty-connection/agent-task-complete-notify.ts
  • src/renderer/src/components/terminal-pane/terminal-pane-lifecycle-types.ts
  • src/renderer/src/components/terminal-pane/use-notification-dispatch.test.ts
  • src/renderer/src/components/terminal-pane/use-notification-dispatch.ts
  • src/renderer/src/hooks/agent-auto-ack-presence.ts
  • src/renderer/src/hooks/agent-auto-ack-targets.ts
  • src/renderer/src/hooks/agent-hook-completion-fresh-working.test.ts
  • src/renderer/src/hooks/agent-hook-completion-notifications.test.ts
  • src/renderer/src/hooks/agent-hook-completion-notifications.ts
  • src/renderer/src/hooks/agent-hook-completion-store-sync.test.ts
  • src/renderer/src/hooks/agent-hook-completion-store-sync.ts
  • src/renderer/src/hooks/ipc-events-agent-status-store-test-fixtures.ts
  • src/renderer/src/hooks/ipc-events-agent-status-window-test-fixtures.ts
  • src/renderer/src/hooks/useAutoAckViewedAgent.away.test.ts
  • src/renderer/src/hooks/useAutoAckViewedAgent.clock-skew.test.ts
  • src/renderer/src/hooks/useAutoAckViewedAgent.floating-panel.test.ts
  • src/renderer/src/hooks/useAutoAckViewedAgent.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/web/preload-api/web-notifications-api.ts
  • src/shared/cloud-service-url.test.ts
  • src/shared/cloud-service-url.ts
  • src/shared/mobile-push-contract.ts
  • src/shared/notification-burst-cooldown.ts
  • src/shared/protocol-version.ts
💤 Files with no reviewable changes (2)
  • src/renderer/src/components/terminal-pane/pty-connection-types.ts
  • src/renderer/src/components/terminal-pane/terminal-pane-lifecycle-types.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 3 remain after this review.

Comment thread src/shared/mobile-push-contract.ts Resolved
@greptile-apps

greptile-apps Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

Too many files changed for review (109 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Froze the legacy socket cooldown at dispatch time — RuntimeMobileNotificationController.dispatch now computes legacySocketAllowed once before recording and createNotificationStreamFilter became a pure predicate over it, so a reconnect or replay-buffer eviction can no longer resurrect a cooldown-suppressed alert.
  • Split DesktopPushService.register failure reasons — a stopped service now returns the retryable gateway_unreachable instead of miscategorizing shutdown as not_mobile.
  • Protected unreadable dismissal history — MobileNotificationDismissalStore latches an unreadable flag on EIO/EPERM/EACCES/EBUSY/EMFILE/ENFILE and skips writes so a still-intact file is never clobbered, while parse-garbage still self-heals.
  • Gave the floating pane precedence — resolveAutoAckTabTargets acks the floating workspace on colliding tab IDs, since it renders on top.
  • Rejected malformed persisted registration filters — non-boolean onlyWhenDesktopAway/sound now invalidate the row instead of being silently stripped.
  • Filled locale and doc gaps — added notifications.agentStatus.* to es/fr/ja/ko/zh and repointed the contract comments from docs/reference/mobile-push-contract.md to cloud/packages/push-contract/src.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@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: Advanced

Run ID: 754ea4f0-c74d-4414-9420-8087e01b25b6

📥 Commits

Reviewing files that changed from the base of the PR and between 67306cb and 973197a.

📒 Files selected for processing (19)
  • src/main/runtime/mobile-notification-dismissal-read-failure.test.ts
  • src/main/runtime/mobile-notification-dismissal-store.ts
  • src/main/runtime/push/desktop-push-service.ts
  • src/main/runtime/push/push-gateway-client.ts
  • src/main/runtime/push/push-host-proof.ts
  • src/main/runtime/push/push-registration-races.test.ts
  • src/main/runtime/rpc/methods/notification-preferences.test.ts
  • src/main/runtime/rpc/methods/notification-reconnect-cooldown.test.ts
  • src/main/runtime/rpc/methods/notification-stream-policy.ts
  • src/main/runtime/runtime-mobile-notification-controller.ts
  • src/renderer/src/hooks/agent-auto-ack-targets.ts
  • src/renderer/src/hooks/useAutoAckViewedAgent.floating-panel.test.ts
  • src/renderer/src/i18n/locales/es.json
  • src/renderer/src/i18n/locales/fr.json
  • src/renderer/src/i18n/locales/ja.json
  • src/renderer/src/i18n/locales/ko.json
  • src/renderer/src/i18n/locales/zh.json
  • src/shared/mobile-push-contract.test.ts
  • src/shared/mobile-push-contract.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/renderer/src/hooks/useAutoAckViewedAgent.floating-panel.test.ts
  • src/main/runtime/mobile-notification-dismissal-store.ts
  • src/renderer/src/hooks/agent-auto-ack-targets.ts
  • src/main/runtime/push/push-host-proof.ts
  • src/main/runtime/push/push-gateway-client.ts
  • src/shared/mobile-push-contract.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment on lines +138 to +140
if (this.stopped) {
return { registered: false, reason: 'gateway_unreachable' }
}

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🤖 get_repo_knowledge executed:

get_repo_knowledge stablyai/orca /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings

Length of output: 10674


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- outline ---'
ast-grep outline src/main/runtime/push/desktop-push-service.ts
printf '%s\n' '--- target implementation ---'
sed -n '90,175p' src/main/runtime/push/desktop-push-service.ts
printf '%s\n' '--- relevant symbols and callers ---'
rg -n -C 3 'registerAfterCleanup|flushUnregisterOutbox|scheduleRetry|setMobilePushRegistrar|deleteQueued|stop\(\)' src/main/runtime/push/desktop-push-service.ts src/main/runtime

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- outbox drain and deleteQueued ---'
sed -n '180,285p' src/main/runtime/push/desktop-push-service.ts
printf '%s\n' '--- imports and client binding ---'
sed -n '1,45p' src/main/runtime/push/desktop-push-service.ts
printf '%s\n' '--- registrar binding definitions ---'
rg -n -C 5 'setPushRegistrar|registerPushDevice|MobilePushRegisterInput|MobilePushRegisterResult' src/main/runtime --glob '*.ts' --glob '!*.integration.test.ts'

Repository: stablyai/orca

Length of output: 18127


Move the stopped guard before the pending-delete cleanup loop.

registerAfterCleanup checks this.stopped only after the loop. deleteQueued calls this.client.deleteDevice(...) for each pending item. If an in-flight register() reaches this method after stop(), it can send a gateway delete before returning. Check this.stopped before the loop.

🔧 Proposed fix
     if (this.outbox.isUnreadable()) {
       return { registered: false, reason: 'registration_storage_failed' }
     }
+    if (this.stopped) {
+      return { registered: false, reason: 'gateway_unreachable' }
+    }
     // A stable gateway ID must not inherit a delete from an earlier registration.
     for (const item of this.outbox.pending().filter((entry) => entry.deviceId === input.deviceId)) {
       if (!(await this.deleteQueued(item.reqId, item.registrationId))) {
         this.scheduleFlushRetry()
         return { registered: false, reason: 'gateway_unreachable' }
       }
     }
     if (this.registry.getDevice(input.deviceId)?.scope !== 'mobile') {
       return { registered: false, reason: 'not_mobile' }
     }
-    if (this.stopped) {
-      return { registered: false, reason: 'gateway_unreachable' }
-    }

@pullfrog pullfrog 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.

✅ No new issues found.

Reviewed changes

  • Corrected the resolveAutoAckTabTargets assertions — the tests now expect the floating target first, and the colliding-tab-id case returns FLOATING_TERMINAL_WORKTREE_ID, matching the implementation in agent-auto-ack-targets.ts (floating pane pushed first, main tab skipped on collision). The corrected file passes 24/24.
  • Moved notifications.agentStatus.* to the top-level notifications namespace — the follow-up previously nested these keys under a deeper object (beside a search sub-object), where translateMain('notifications.agentStatus.*') could not resolve them and silently fell back to English. They now sit at the top level consistent with en.json.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

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