Skip to content

feat(rewards): integrate desktop rewards center and harden packaged desktop#789

Merged
lefarcen merged 59 commits intomainfrom
shared/rewards-center-integration
Apr 8, 2026
Merged

feat(rewards): integrate desktop rewards center and harden packaged desktop#789
lefarcen merged 59 commits intomainfrom
shared/rewards-center-integration

Conversation

@anthhub
Copy link
Copy Markdown

@anthhub anthhub commented Apr 3, 2026

What

Add the desktop-first rewards center across controller, web, and desktop surfaces, and fold in the packaged desktop/runtime hardening that this branch needed to make nightly builds and packaged reward flows work reliably.

Why

This branch brings rewards balances, task claiming, sharing assets, GitHub star verification, and budget fallback UX into the desktop flow. As the work matured, it also surfaced packaged runtime issues in CI and local DMG installs, so the PR now includes the fixes required to keep packaged desktop health checks, extracted sidecars, and signed OpenClaw payloads healthy.

How

  • Add controller rewards services/routes, desktop rewards state compilation, GitHub star verification, quota fallback, and the shared rewards schemas/sdk updates that power the UI.
  • Implement the web rewards experience, home/sidebar budget and balance surfaces, desktop cloud connect helpers, and the desktop test-balance dialog wired through host IPC.
  • Harden packaged desktop startup by asserting web-surface origin health instead of a specific pathname, refreshing extracted runtime sidecars on build identity changes, tearing down stale launchd/runtime processes before replacement, and signing packaged OpenClaw native payload binaries for notarization.

Affected areas

  • Desktop app (Electron shell)
  • Controller (backend / API)
  • Web dashboard (React UI)
  • OpenClaw runtime
  • Skills
  • Shared schemas / packages
  • Build / CI / Tooling

Checklist

  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes
  • pnpm generate-types run (if API routes/schemas changed)
  • No credentials or tokens in code or logs
  • No any types introduced (use unknown with narrowing)

Notes for reviewers

The branch is broader than the original rewards-center slice because it now includes the packaged desktop fixes that were required to keep this work green in nightly CI and in packaged runtime verification.

anthhub added 23 commits March 30, 2026 16:52
- Introduced new API endpoints for triggering automatic fallback to BYOK provider and restoring the default model to a managed (cloud) model.
- Updated OpenAPI schema to include new properties related to BYOK usage and fallback status.
- Implemented QuotaFallbackService to handle fallback logic and model restoration.
- Added BudgetWarningBanner component to notify users about credit status in the UI.
- Enhanced existing services and routes to integrate new quota management features.
- Updated the rewards link to use a flex layout for better alignment and spacing.
- Increased the size of the Coins icon for improved visibility.
- Adjusted the display of claimed and total credits to enhance readability.
- Refined the progress bar styling for a more polished appearance.
- Updated OpenAPI schema to include cloud balance properties.
- Implemented CloudRewardService for managing rewards status and claims.
- Enhanced NexuConfigStore to fetch and convert cloud rewards status.
- Added tests for CloudRewardService and updated existing tests for NexuConfigStore.
- Updated UI components to display cloud balance information.
- Localized new cloud balance strings in English and Chinese.
…ity and localization

- Introduced a status configuration for the BudgetWarningBanner to manage different states (warning and depleted) with corresponding styles and texts.
- Updated the component to utilize the new configuration for rendering, improving code readability and maintainability.
- Added new localization strings for budget warning and depleted states in English and Chinese.
- Implemented a debug panel for development purposes to simulate budget banner states.
- Added tests for the BudgetWarningBanner to ensure correct rendering based on status.
…rds status handling

- Added logic to automatically trigger a fallback to BYOK when the managed model's cloud balance is depleted.
- Integrated logging for fallback failures to improve error tracking.
- Created unit tests for the new fallback functionality to ensure reliability.
- Updated the BudgetWarningBanner and related UI components to reflect changes in rewards status and actions.
- Enhanced localization strings for clarity in user messaging regarding rewards and credits.
…rds status handling

- Added logic to automatically trigger a fallback to BYOK when the managed model's cloud balance is depleted.
- Integrated logging for fallback failures to improve error tracking.
- Created a new test suite for desktop rewards routes to validate fallback behavior and status retrieval.
- Updated existing UI components and localization strings to reflect changes in rewards status messaging.
- Remove 5 investigation markdown files accidentally committed to repo root
- Fix cloudConnecting state not reset in "Connection already in progress" path
- Extract handleCloudConnect + polling effects into shared useCloudConnect hook
- Deduplicate formatRewardAmount by exporting from home-rewards-teaser
- Introduced six new PNG images for reward sharing.
- Created a new module for managing reward share assets, including types and functions for selecting and downloading assets.
- Implemented tests to ensure the integrity of the reward share assets and their download functionality.
- Keep both rewards budget debug panel and seedance promo features
- Fix welcome.tsx merge: replace removed setCloudStatus with refetchDesktopCloudStatus
…ling

- Show skeleton placeholders instead of fallback tasks during loading
- Add autoFallbackTriggered flag to rewards status schema
- Display warning toast when BYOK auto-fallback is triggered
- Add refetchInterval: 60s to detect balance changes in background
…new headlines and actions

- Refactored the BudgetWarningBanner to use new headline keys for warning and depleted states.
- Updated the UI to include an upgrade action button and removed the previous earn credits button.
- Enhanced localization strings for better clarity in user messaging regarding budget status.
- Added a new RewardTaskIcon component for rendering task icons dynamically.
- Updated the rewards status hook to disable retries for fetching data.
- Added new localization strings for balance details in English and Chinese.
- Improved the WorkspaceLayout to display rewards balance information with a detailed popup.
- Refactored HomePage to manage session and channel data more effectively, ensuring proper rendering based on session history.
- Added tests to verify the rendering of rewards components and their behavior during loading states.
- Added a new module for handling virtual reward checks, including functions to manage task status and delay.
- Updated localization strings in English and Chinese for task checking and claiming processes.
- Refactored the RewardConfirmModal to utilize the new virtual check functionality, improving user feedback during reward processing.
- Introduced tests to validate the behavior of the virtual reward check and description key retrieval.
…t banner enhancements

- Introduced a new `ensureDesktopControllerReady` function to manage the readiness of the desktop controller, including polling and recovery logic.
- Refactored the `DesktopShell` and `useDesktopRuntimeConfig` to utilize the new controller readiness management.
- Enhanced the budget banner functionality in the `HomePage` to handle dismissal states more effectively, using session storage for tracking.
- Added a new `BudgetDepletedDialog` component to provide user feedback when budget is exhausted.
- Implemented tests for the controller readiness logic and budget banner dismissal behavior to ensure reliability.
- Added a new `GithubStarVerificationService` to manage GitHub star session preparation and verification.
- Enhanced the desktop rewards routes to include a new endpoint for preparing GitHub star verification sessions.
- Updated the claim reward logic to validate proof URLs and GitHub session IDs before processing claims.
- Introduced new localization strings for handling proof URL inputs and GitHub session errors.
- Added tests to ensure the correct behavior of the new verification and proof handling features.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: 4f75f2e1df

ℹ️ 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".

Comment thread apps/web/src/pages/models.tsx
Comment thread apps/desktop/src/lib/controller-ready.ts Outdated
anthhub and others added 6 commits April 3, 2026 13:32
* fix(desktop): make local startup work on Windows

* fix(desktop): sanitize persisted startup logs

* test(desktop): add Feishu websocket smoke harness

* fix(smoke): improve Feishu websocket diagnostics

* build(desktop): add Windows smoke distribution flow

* build(desktop): align Windows sidecar archives with zip flow

* build(desktop): align Windows packaging with NSIS defaults

* build(desktop): refine Windows distribution commands

* build(desktop): improve Windows installer UX

Switch the NSIS flow to an assisted installer so Windows install and uninstall are visible and data cleanup can be opted into without silent surprises. Restore executable resource editing and use tombstone-based app-data cleanup so shortcuts show the Nexu icon and uninstall stays fast without flashing a console window.

* fix(desktop): stabilize Windows dist and runtime install (#538)

* build(desktop): skip exe editing for unsigned Windows dist

* fix(openclaw-runtime): stabilize Windows postinstall

Keep the full install path available for debugging while making pruned installs skip Windows-native optional dependencies that break postinstall flows.

* fix(desktop): align dev health check with runtime state

* fix(openclaw-runtime): refresh cache on installer changes

* build(desktop): add windows CI coverage

* fix(desktop): avoid sidecar copy symlink loops on windows

* fix(desktop): clean runtime plugin self-links on windows

* build(desktop): scope windows CI to build validation

* fix(desktop): restore mac dist executable discovery

---------

Co-authored-by: mRcfps <1402401442@qq.com>

* build(desktop): stabilize Windows startup and shell polish

* fix(web): refine Windows sidebar toggle layout

* fix(desktop): harden Windows packaged runtime startup

* feat: streamline desktop local dev workflow (#640)

* feat: add local dev supervisor workflow

* feat: refine local dev workflow and desktop runtime scaffolding

* docs: add dev workflow faq

* fix: remove nested controller tsx watcher

* refactor: share ensure guards across dev process helpers

* chore: remove stale task notes

* refactor: centralize local dev path resolution

* refactor: move dev orchestration into scripts/dev

Keep @nexu/dev-utils focused on atomic helpers so service-level controller and web flows stay easier to reason about and recover. Add lightweight session tracing so leaked dev processes can be correlated and cleaned up without heavy self-healing.

* refactor: clarify scripts dev module boundaries

* feat: externalize dev runtime ownership

* feat: split local dev into explicit service controls

* refactor: remove legacy desktop dev launchers

* fix: run desktop local dev through the Vite supervisor

* fix: harden local dev stack flow

* chore: sync workspace lockfiles

* fix: restore desktop dev auth session

* chore: unify scripts dev logging

* chore: stabilize launchd encapsulation and windows desktop smoke flow (#651)

* chore: introduce shared desktop lifecycle contract

* chore: move desktop platform lifecycle behind adapters

* chore: centralize desktop platform compatibility

* chore: stage patched OpenClaw runtime for local dev

* chore: log staged OpenClaw runtime usage

* chore: speed up windows desktop build iteration

* chore: disable win executable editing for local builds

* chore: ignore local build cache

* fix(desktop): support windows openclaw sidecar archives

* chore: unify desktop dev launch under scripts/dev (#654)

* chore(ci): add windows packaging to desktop dist full

* chore(ci): opt desktop dist full into node 24 actions

* Revert "chore(ci): opt desktop dist full into node 24 actions"

This reverts commit b19336e.

* fix(desktop): harden windows packaging and installer flow

* chore: remove task handoff note

* chore: align desktop tests with platform runtime changes

* chore: fix linux desktop test platform mocks

* chore: stabilize dev check service supervision

* chore: fix packaged launchd path test platform setup

* fix(desktop): preserve explicit dev runtime env overrides

* fix(dev): preserve openclaw supervisor pid lock

* chore(ci): track scripts dev changes in desktop dev checks

* chore(ci): support manual desktop workflow dispatch

* fix(dev): treat active openclaw port as running

* fix(dev): align desktop runtime ports with CI contract

* fix(dev): wait for openclaw gateway before controller start

* chore(ci): capture dev logs and read desktop pid lock

* fix(dev): wait for openclaw health before reporting ready

* fix(controller): debounce sync on all skill dir events

* fix(dev): derive openclaw status from runtime health

* fix(dev): disable bonjour in desktop CI smoke

* fix(desktop): harden Windows packaging and cleanup flows

* fix(dev): stabilize Windows desktop dev lifecycle

* fix(dev): allow overriding desktop dev ports

* fix(dev): require openclaw readiness for running status

* fix(dev): expose builtin openclaw extensions in local controller

* fix(desktop): stabilize windows packaging workflows

* fix: restore windows dev UI and openclaw startup compatibility

* fix(desktop): clear stale startup errors after runtime recovery

* fix(desktop): use packaged openclaw sidecars in windows builds

* chore: refresh lockfile for desktop dependency changes

* fix: make nexu runtime model plugin reload state by content

---------

Co-authored-by: Marc Chan <mrc@powerformer.com>
Co-authored-by: mRcfps <1402401442@qq.com>
…lance tooltips; improve workspace layout with balance popup functionality
lefarcen added 2 commits April 7, 2026 17:17
The "view usage detail" button in the sidebar credits popup was navigating
to the local /workspace/rewards page instead of the actual usage detail
page on the connected cloud profile. Open the cloud profile's
/workspace/usage URL in the system browser via openExternalUrl, mirroring
how the cloud bill links work elsewhere.

Also correct resolveCloudUsageUrl's fallback host from nexu.net to nexu.io
(nexu.net is not the production domain).
…n' into shared/rewards-center-integration

# Conflicts:
#	apps/web/lib/api/types.gen.ts
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 7, 2026

Deploying nexu-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 9a35205
Status: ✅  Deploy successful!
Preview URL: https://b17bea8f.nexu-docs.pages.dev
Branch Preview URL: https://shared-rewards-center-integr.nexu-docs.pages.dev

View logs

lefarcen and others added 3 commits April 7, 2026 18:00
…-integration

# Conflicts:
#	apps/controller/tests/nexu-config-store.test.ts
#	apps/desktop/src/main.tsx
Add the analytics events the activation funnel needs from the desktop
client side, on top of the PR #778 PostHog migration:

- workspace_growth_rewards_click — fired when the sidebar "share nexu /
  earn extra credits" growth banner is clicked.
- workspace_click_usage_detail — fired when the credits balance popup
  "view usage detail" button is clicked.
- user_message_sent.state — controller analytics service now tags each
  user_message_sent with state="Success" or state="false". Failure is
  detected by openclaw:prompt-error transcript entries arriving before
  the assistant response; success is the default and gets confirmed
  when the assistant message lands.
- AnalyticsAuthSource type now also accepts "home" so the home page
  can be the source of an auth click. The desktop side of the
  signup_success / login_success plumbing (source pass-through to
  cloud) is still pending — that side has to be done together with the
  cloud auth-init schema and is intentionally left out of this commit.

The user_message_sent.credit_charged property the funnel spec also
mentions cannot be implemented client-side: there is no per-message
credit consumption pipeline yet. cloud's credit_usages table is empty
of llm-call entries, link writes link.usage_events (USD cost) but
nothing transforms USD into credit and writes credit_usages, and link
does not record channel either. That work belongs to the cloud team
under the existing credit consumption track and is documented in
specs/current/credit.md.
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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

ℹ️ 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".

Comment thread apps/web/src/hooks/use-cloud-connect.ts Outdated
Comment thread apps/controller/src/store/nexu-config-store.ts Outdated
lefarcen and others added 3 commits April 7, 2026 18:16
The cloud auth page (apps/web/src/pages/auth.tsx in nexu-io/cloud) is
already firing signup_success / login_success with the right method
field, but it sources `source` from a `?source=...` query string and
falls back to "welcome_page" otherwise. The desktop side never set
that query string, so every auth event from the desktop client showed
up as welcome_page regardless of where the user actually clicked.

This wires the desktop side end-to-end so the cloud event gets the
real source:

- packages/shared: cloudConnectBodySchema (new) and
  cloudProfileConnectBodySchema both accept an optional `source`
  string.
- controller (nexu-config-store / desktop-local-service / route):
  connect endpoints accept `source` in the request body and append it
  as `&source=…` to the cloud /auth browser URL.
- apps/web hook (use-cloud-connect): handleCloudConnect accepts an
  optional AnalyticsAuthSource parameter and forwards it.
- callers tagged with the page they fire from:
  - welcome.tsx → "welcome_page" (both initial connect and the
    retry-after-error path).
  - workspace-layout.tsx sidebar growth-card → derived from current
    route via existing isHomePage / isModelsPage flags
    (home / settings / fallback home).
  - rewards.tsx (task action and login CTA) → "home", since the
    rewards page is reached from home and the funnel spec only lists
    welcome_page / settings / home as valid sources.

Cloud-side counterpart (still pending, separate PR in nexu-io/cloud):
add "home" to the validSources allowlist in apps/web/src/pages/auth.tsx
so the postApiV1MeAuthSource backfill accepts the new value.
- rewards.tsx: guard i18n.language access with optional chaining to prevent
  SSR/test crash when i18n instance is not fully initialized
- models.tsx: treat 'Connection attempt already in progress' as pending and
  keep polling instead of dropping user into error state (Codex P2)
- controller-ready.ts: give final recovery attempt a bounded
  finalAttemptTimeoutMs (default 4x attemptTimeoutMs) so crash-looping
  controller eventually surfaces as false instead of leaking polling loops
  (Codex P2)
- tests: align reward-share-assets / home locale / controller-ready tests
  with consolidated mobile_share task and new docs URL format
lefarcen and others added 4 commits April 7, 2026 20:06
Verify packaged runtime unit health used to require that some embedded
webview have lastUrl.includes("/workspace"), but that asserts product
state ("the user has reached the workspace route") rather than runtime
health.

In fresh-state CI runs the renderer mounts /workspace, then the SPA's
AuthLayout (apps/web/src/layouts/auth-layout.tsx:14-16) and
WorkspaceLayout (apps/web/src/layouts/workspace-layout.tsx:346-349)
immediately Navigate('/') because there is no auth session and
SETUP_COMPLETE_KEY is unset. The desktop diagnostics reporter records
contents.getURL() at did-finish-load time
(apps/desktop/main/index.ts:1035-1056), so it captures the
post-redirect URL — webview ends up with lastUrl="http://127.0.0.1:50810/"
and the old check reported "workspace webview diagnostics are missing".

Local runs accidentally passed because the developer machine had a
persisted Local Storage with nexu_setup_complete=1 from earlier sessions,
which suppressed the redirect. CI is ephemeral and never had it.

The actual runtime health invariant is that an embedded webview
successfully loaded a page from the local web sidecar's origin — the
embed handshake worked, the renderer is alive, and there was no
fail-load. The path under that origin (/, /workspace, /welcome, ...)
is product state, not health.

- Add webOrigin to readinessUrls (parsed from the web URL).
- Replace getLatestWorkspaceWebview with getLatestWebSurfaceWebview that
  filters embedded entries by same-origin against webOrigin and ignores
  pathname entirely.
- Thread webOrigin through diagnosticsChecksPassed,
  collectDiagnosticsFailures, probesPassed, verifyRuntime.
- Failure message now reads "web surface webview diagnostics are
  missing (no embedded webview reported origin <origin>)".

Verified locally with two fixtures:
- {id:3, lastUrl:"http://127.0.0.1:50810/"}      → matches (used to miss)
- {id:3, lastUrl:"http://127.0.0.1:50810/workspace"} → still matches
…is starting (#893)

On relaunch, users saw a white card titled "Starting controller..." with
"Retry controller" and "Open control plane" buttons during the short
window where the local controller was booting but not yet ready. It
looked like the app had landed on an error page instead of the home
page, which is what issue #876 reported.

The only difference between the "polling" and "failed" states in the
old fallback UI was the heading text — both rendered the same card and
the same Retry button, so users could not tell "still starting" from
"actually broken".

Split the fallback into three branches:
- desktopWebUrl ready -> SurfaceFrame with the webview (unchanged)
- controller failed / recovering -> the original error card with Retry
- normal polling -> SurfaceFrame with src=null, which reuses the built-in
  NexuLoader overlay that SurfaceFrame already shows before webview
  did-finish-load

The polling loader and the post-mount webview loader are now the same
NexuLoader component with the same animation, so the transition from
"controller starting" to "home page" is visually seamless and never
exposes any text or buttons that could be mistaken for an error page.

Closes #876
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

cloudSessions: previousSession
? {
...restSessions,
[profile.name.trim()]: previousSession,
}

P2 Badge Reject cloud profile renames to an existing name

Renaming profile previousName to a name that already exists silently corrupts stored cloud sessions: this block always writes cloudSessions[profile.name.trim()] = previousSession, so the target profile's prior session is overwritten. Because the profile list is deduped by name during the same update, one profile definition is dropped while session data is merged onto the survivor, which can mismatch endpoint metadata and auth state. Add an explicit duplicate-name check (allowing unchanged names only) before applying the rename.

ℹ️ 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".

Comment thread apps/desktop/src/components/develop-set-balance-dialog.tsx Outdated
lefarcen added 5 commits April 7, 2026 21:30
The async openclaw sidecar materializer was named "Async" and wrapped
in async/await, but it called execFileSync("/usr/bin/tar", …) and
rmSync(…) internally. execFileSync blocks the Node.js event loop until
the child process exits, so for the ~14s tar extraction on first
install / post-update the main process could not respond to anything,
including IPC handshake from the renderer's preload. As a result the
renderer's loadFile took ~28s to fire did-finish-load (measured locally:
12:13:42.805 dispatched → 12:14:10.463 finished, while extraction ran
12:13:42 → 12:13:56), and the setup-animation video did not start
playing until extraction was complete — defeating the entire point of
showing the window with a white background "before loadFile, before
React, before anything" (apps/desktop/main/index.ts:945-955).

Switch the tar invocation to promisify(execFile), which still spawns a
real /usr/bin/tar child process but waits for it via libuv asynchronously
so the event loop stays responsive. Also replace the matching rmSync of
the previous extracted sidecar root with the async fs/promises rm to
keep the function uniformly non-blocking.

Sync materializer (createSyncTarSidecarMaterializer) is left untouched
because it is intentionally synchronous for early-bootstrap call sites.

Effect: renderer mounts and the setup-animation video starts playing
within seconds of window.show(), in parallel with extraction, instead
of after extraction finishes.
Avoid reusing stale extracted runtime sidecars when a rebuilt app keeps the same app version but changes CFBundleVersion, and keep the desktop rewards balance popup reachable while cloud balance data is still loading.
When packaged runtime identity changes, boot out launchd services and clear surviving Nexu processes before replacing the extracted runner and controller sidecar so reinstall flows cannot keep stale runtime code alive.
Sign all packaged OpenClaw native binary candidates directly during mac builds and invalidate stale archived sidecar payloads so notarization cannot fail on unsigned canvas/skia artifacts.
Recover stale desktop cloud connect sessions, validate reward group IDs and profile renames more defensively, and route desktop test balance operations through the host bridge instead of renderer-origin fetches.
@lefarcen lefarcen changed the title Shared/rewards center integration feat(rewards): integrate desktop rewards center and harden packaged desktop Apr 8, 2026
Build the expected file URL with Node path/url helpers so the desktop preload URL assertion passes on Windows as well as Unix runners.
@lefarcen lefarcen merged commit f005223 into main Apr 8, 2026
11 checks passed
@lefarcen lefarcen mentioned this pull request Apr 8, 2026
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.

4 participants