Skip to content

Desktop: attach to the supervised daemon instead of a private sidecar#1005

Merged
RhysSullivan merged 1 commit into
mainfrom
daemon/desktop-attach
Jun 14, 2026
Merged

Desktop: attach to the supervised daemon instead of a private sidecar#1005
RhysSullivan merged 1 commit into
mainfrom
daemon/desktop-attach

Conversation

@RhysSullivan

@RhysSullivan RhysSullivan commented Jun 13, 2026

Copy link
Copy Markdown
Owner

Builds on the executor service stack to make the desktop app a thin client of the OS-supervised gateway, so quitting the app leaves MCP serving.

Behavior

  • On packaged macOS launch the app attaches to a running supervised daemon. If none is registered, a one-time consent prompt offers to install one — a launchd LaunchAgent pointing at the bundled executor-sidecar binary running in supervised mode. Every other case (dev, macOS < 13 path, install declined/failed, Linux/Windows) falls back to the existing managed sidecar unchanged.
  • Quitting the app, restarting the window, installing an update, or resetting state no longer stops a supervised daemon. The previous "another server owns the data dir → fatal dialog" path becomes a clean attach.
  • The sidecar entry, when run supervised (no Electron parent), self-writes the discovery manifest and reads its password from service.key.
  • A crash monitor shows a brief reconnecting overlay while launchd restarts the daemon; regenerating the password reinstalls the service and re-points the window at the refreshed credential. A service-control IPC pair (status / set-enabled) backs a Settings toggle.

No second binary is bundled — the supervised daemon reuses the sidecar that already ships. The launchd plist generator mirrors the canonical copy in the CLI (apps/cli/src/service.ts).

Verification & coverage

The launchd mechanics are exercised live via the CLI path in the parent PR. The desktop-specific flow (attach, consent install, quit-leaves-serving) needs a code-signed packaged build to verify end-to-end — that part is reviewed as source here and should be smoke-tested on a signed build before release. Typecheck and lint pass.

Stack

  1. Add busy_timeout to the local libSQL connection #1003
  2. Add executor service to supervise the local gateway daemon #1004
  3. Desktop: attach to the supervised daemon instead of a private sidecar #1005 👈 current

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 13, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
executor-marketing 50c18a9 Commit Preview URL

Branch Preview URL
Jun 14 2026, 07:07 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 13, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
executor-cloud 50c18a9 Jun 14 2026, 07:08 AM

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Cloudflare preview

Torn down — the PR is closed.

@pkg-pr-new

pkg-pr-new Bot commented Jun 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@executor-js/cli

npm i https://pkg.pr.new/@executor-js/cli@1005

@executor-js/config

npm i https://pkg.pr.new/@executor-js/config@1005

@executor-js/execution

npm i https://pkg.pr.new/@executor-js/execution@1005

@executor-js/sdk

npm i https://pkg.pr.new/@executor-js/sdk@1005

@executor-js/codemode-core

npm i https://pkg.pr.new/@executor-js/codemode-core@1005

@executor-js/runtime-quickjs

npm i https://pkg.pr.new/@executor-js/runtime-quickjs@1005

@executor-js/plugin-file-secrets

npm i https://pkg.pr.new/@executor-js/plugin-file-secrets@1005

@executor-js/plugin-graphql

npm i https://pkg.pr.new/@executor-js/plugin-graphql@1005

@executor-js/plugin-keychain

npm i https://pkg.pr.new/@executor-js/plugin-keychain@1005

@executor-js/plugin-mcp

npm i https://pkg.pr.new/@executor-js/plugin-mcp@1005

@executor-js/plugin-onepassword

npm i https://pkg.pr.new/@executor-js/plugin-onepassword@1005

@executor-js/plugin-openapi

npm i https://pkg.pr.new/@executor-js/plugin-openapi@1005

executor

npm i https://pkg.pr.new/executor@1005

commit: 3203153

@RhysSullivan RhysSullivan force-pushed the daemon/desktop-attach branch from 19316c1 to 3e1a78b Compare June 13, 2026 21:10
@greptile-apps

greptile-apps Bot commented Jun 13, 2026

Copy link
Copy Markdown

Greptile Summary

This PR replaces the desktop app's owned sidecar with a thin-client pattern: on packaged macOS, the app attaches to an OS-supervised launchd daemon (reusing the bundled executor-sidecar binary), so the MCP gateway keeps running after the app quits. A one-time consent prompt installs the LaunchAgent on first run; every other platform or decline falls back to the existing managed-spawn path unchanged.

  • service.ts (new): writes a launchd plist, bootstraps/boots-out the agent, and exposes installSupervisedService / uninstallSupervisedService / restartSupervisedService.
  • index.ts: adds ensureSupervisedConnection at boot, a crash monitor (armSupervisedMonitor), and IPC handlers for service:status / service:set-enabled; stopConnection now no-ops for supervised connections so quit/update/reset never kill the daemon.
  • sidecar.ts: extends SidecarConnection with a supervisedDaemon flag and adds attachToSupervisedDaemon which reads server.json and probes the endpoint.
  • server.ts: in supervised mode, self-writes the discovery manifest on start and cleans it up on SIGTERM.

Confidence Score: 3/5

The core supervised-attach and install flows are structurally sound, but the token-rotation path for a supervised daemon can permanently install a stale credential into the Electron session if the daemon restart takes longer than 15 seconds.

The rotate-token handler falls back to the old SidecarConnection with the pre-rotation authToken when waitForSupervisedAttach times out. installBearerAuthHeader then embeds this stale token in the Electron session. Because a quick restart means supervisedDaemonDown is never set to true, the crash monitor never triggers a reload, so the bad token persists until the user manually restarts.

apps/desktop/src/main/index.ts — specifically the executor:server:rotate-token IPC handler's supervised-daemon branch.

Important Files Changed

Filename Overview
apps/desktop/src/main/index.ts Adds supervised-daemon attach/install/monitor flow at boot; introduces a stale-token bug in the rotate-token handler when waitForSupervisedAttach times out.
apps/desktop/src/main/service.ts New file implementing launchd install/uninstall/restart; plist write correctly uses 0o600; error handling is mostly sound with the noted orphaned-plist issue tracked separately.
apps/desktop/src/main/sidecar.ts Adds supervisedDaemon flag to SidecarConnection and implements attachToSupervisedDaemon; correctly guards child-process-only code paths.
apps/desktop/src/sidecar/server.ts Adds supervised-mode manifest write/cleanup; manifest file lacks explicit 0o600 mode (tracked in previous thread), rest of the supervised boot path is straightforward.

Sequence Diagram

sequenceDiagram
    participant App as Desktop App
    participant ens as ensureSupervisedConnection
    participant launchd as launchd / OS
    participant daemon as executor-sidecar (supervised)
    participant fs as server.json

    App->>ens: boot (app.isPackaged)
    ens->>fs: attachToSupervisedDaemon read server.json
    alt manifest exists and reachable
        fs-->>ens: "SidecarConnection supervisedDaemon=true"
        ens-->>App: attach OK armSupervisedMonitor
    else plist registered not running
        ens->>launchd: launchctl kickstart -k
        launchd->>daemon: start
        daemon->>fs: writeSupervisedManifest port newToken
        ens->>fs: waitForSupervisedAttach polls
        fs-->>ens: SidecarConnection
        ens-->>App: attach OK armSupervisedMonitor
    else first run
        App->>App: confirmEnableBackgroundService dialog
        App->>launchd: launchctl bootstrap installSupervisedService
        launchd->>daemon: start
        daemon->>fs: writeSupervisedManifest
        ens->>fs: waitForSupervisedAttach polls
        fs-->>ens: SidecarConnection
        ens-->>App: attach OK armSupervisedMonitor
    else not supported or declined
        ens-->>App: null fallback managed-spawn
    end

    note over App,daemon: On quit supervisedDaemon=true connection=null no stop
    note over App,daemon: rotate-token restartSupervisedService + waitForSupervisedAttach 15s
Loading

Reviews (6): Last reviewed commit: "Desktop: attach to the supervised daemon..." | Re-trigger Greptile

Comment thread apps/desktop/src/main/index.ts
Comment thread apps/desktop/src/main/index.ts
Comment thread apps/desktop/src/sidecar/server.ts Outdated
Comment on lines +108 to +114
const writeSupervisedManifest = (port: number) => {
const connection = normalizeExecutorServerConnection({
origin: `http://${hostname}:${port}`,
displayName: "Supervised daemon",
...(authPassword
? { auth: { kind: "basic" as const, username: "executor", password: authPassword } }
: {}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Hardcoded "executor" username instead of the shared constant

writeSupervisedManifest writes username: "executor" directly. The rest of the codebase (managed sidecar manifest, attachToSupervisedDaemon) uses SERVER_SETTINGS_USERNAME from ../shared/server-settings. If the username ever changes, this site will drift. Since server.ts already imports from @executor-js/sdk/shared, the constant should be accessible or re-exported there.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@RhysSullivan RhysSullivan force-pushed the daemon/desktop-attach branch from 3e1a78b to cf387fd Compare June 13, 2026 21:38
Comment thread apps/desktop/src/main/index.ts
@RhysSullivan RhysSullivan force-pushed the daemon/desktop-attach branch from cf387fd to 04595d7 Compare June 13, 2026 23:31
@RhysSullivan RhysSullivan force-pushed the daemon/desktop-attach branch from 04595d7 to 3203153 Compare June 14, 2026 06:47
Comment thread apps/desktop/src/main/service.ts
Comment thread apps/desktop/src/sidecar/server.ts
The Electron app becomes a thin client of the OS-supervised gateway:

- On packaged macOS launch it attaches to a running supervised daemon, or (with
  a one-time consent prompt) registers one by pointing a launchd LaunchAgent at
  the bundled sidecar binary in supervised mode. Falls back to the existing
  managed sidecar everywhere else.
- Quitting the app, restarting the window, updating, or resetting state no
  longer stops a supervised daemon -- it keeps serving MCP. The old 'another
  server owns the data dir -> fatal dialog' path becomes a clean attach.
- The sidecar entry, when supervised, self-writes the discovery manifest and
  reads its password from service.key.
- A crash monitor shows a reconnecting overlay while launchd restarts the
  daemon; regenerating the password reinstalls + re-points the window.
@RhysSullivan RhysSullivan force-pushed the daemon/desktop-attach branch from 3203153 to 50c18a9 Compare June 14, 2026 07:05
@RhysSullivan RhysSullivan changed the base branch from daemon/cli-service to main June 14, 2026 07:05
@RhysSullivan RhysSullivan merged commit 9564174 into main Jun 14, 2026
1 check was pending
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