You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Ship OpenBot Desktop as a directly installable macOS application with Developer ID signing, Apple notarization, stapled tickets, and verified DMG/ZIP artifacts.
Migrate the applicable desktop behaviors and operational lessons from the private legacy trytilde/agent repository into OpenBot's Electron architecture: menu-bar/taskbar presence, status/menu state, local-runtime supervision, lifecycle notifications, configuration/versioning lessons, and release verification. Do not import Electrobun as a dependency or paste private implementation into the issue.
Current state
apps/desktop uses Electron and electron-builder and can package DMG/ZIP artifacts, but has no signing identity, hardened-runtime entitlements, notarization, release workflow, tray/menu integration, notifications, or clean-machine installation proof.
The packaged app currently contains the web renderer and same-origin proxy shell; a clean-machine release also needs a defined local-control-runtime/bootstrap story rather than assuming a development service already listens on localhost.
@openbot/cli currently runs TypeScript source through the development-only tsx loader and has no production bin artifact. It is not bundled into Desktop today.
The legacy repository uses a different desktop framework. Its transferable value is behavior, state-machine design, process-health supervision, signing order, version/release lessons, and MIT-licensed assets/source—not its framework-specific code.
Provenance and migration policy
Before copying any source or asset:
pin the inspected legacy commit and record each imported file/behavior in PROVENANCE.md;
confirm MIT copyright/license headers and preserve required notices;
distinguish verbatim imports, adapted imports, and behavior-only reimplementations;
record original and resulting SHA-256 values for verbatim/adapted assets;
do not publish unrelated private source, secrets, comments, internal endpoints, or historical credentials;
keep OpenBot-specific composition outside any preserved upstream directory.
Prefer behavior-level reimplementation using Electron APIs where the legacy code is tied to Electrobun or a different runtime. Notifications need a new Electron-native implementation if no reusable legacy implementation exists.
Architecture
1. Desktop shell responsibilities
Electron main owns privileged desktop behavior. The renderer remains browser-compatible with context isolation, sandboxing, no Node integration, and a narrow typed preload bridge.
Main-process modules should be cohesive and independently testable:
local-runtime-supervisor — start/attach/stop and readiness for the packaged local OpenBot control runtime;
desktop-state — pure reducer from health/user/system events to stable UI state;
tray — menu-bar/taskbar icon and menu projection;
notifications — bounded native notifications and safe deep links;
updates — future release update integration, kept out of the first installability slice unless explicitly selected.
Do not expose generic process, filesystem, shell, or credential methods to the renderer.
2. Clean-machine runtime
Choose and document one directly installable runtime model:
Bundle the local control-service artifact and start it from Electron main/utility process; or
make Desktop a client of a configured hosted OpenBot installation and make local mode an explicit optional component.
The default installer cannot silently depend on globally installed Node, pnpm, repository source, or a manually started control service. If local mode is included, package all required code/runtime, store mutable state under the platform application-data directory, bind only loopback by default, use deterministic port/conflict handling, and expose a readiness endpoint.
Supervision distinguishes “process exists” from “service ready,” uses bounded start/stop timeouts, reaps children on quit, and never logs inherited secrets or command environments.
3. Bundle the production OpenBot CLI
Ship the OpenBot CLI inside every Desktop application bundle, but keep it out of Desktop's development dependency graph and ordinary development startup path.
The CLI package owns a real production build:
add a stable executable entrypoint and bin contract for openbot;
compile/bundle the CLI and its runtime workspace dependencies from the tagged source into a deterministic production artifact;
remove all runtime dependence on tsx, TypeScript source, workspace resolution, pnpm, node_modules, or the repository checkout;
embed the release version, commit, target architecture, and artifact digest and expose them through openbot --version --json;
emit a manifest of bundled production modules/licenses and fail if test-only or development-only packages enter the artifact.
The preferred runtime is a bundled JavaScript CLI plus a private, version-pinned Node-compatible runtime already shipped with Desktop. If Electron's executable is reused under its documented Node mode, isolate that launcher path from normal app startup and prove it on every supported Electron upgrade. Otherwise, bundle a dedicated minimal Node runtime. Do not rely on a globally installed node, pnpm, or tsx.
Desktop packaging consumes the CLI as an explicit release artifact, not as a package dependency:
@openbot/desktop must not list @openbot/cli in dependencies or devDependencies.
pnpm --filter @openbot/desktop dev builds/runs the Electron shell without building, resolving, or executing the bundled CLI.
The release workflow builds the CLI first and passes its immutable output path/digest to electron-builder through extraResources or an equivalent provider-owned packaging input.
Electron main may invoke only explicitly modeled internal CLI operations through a narrow supervisor; it must not expose arbitrary CLI arguments, commands, environment variables, or stdout to the renderer.
The shell-facing CLI remains fully user-invokable. Add an explicit Install command-line tool… action that installs a thin launcher or link into a user-owned location such as ~/.local/bin; do not silently edit shell profiles, require sudo, or place files in system directories.
Moving or updating the app must leave the installed launcher recoverable. The launcher should locate the current application bundle deterministically or be refreshed atomically by the app.
The CLI is signed/notarized as part of the app bundle. Its bytes and manifest are final before nested signing begins, and the release manifest in #31 records the CLI digest separately. If the CLI is also published as a standalone artifact later, it must be built from the same tagged source and have the same semantic version, but it is not required for Desktop to run.
4. Tray/menu and dock behavior
Use Electron Tray, Menu, nativeImage, and app APIs:
ship a monochrome macOS template icon plus platform-appropriate Linux icon;
show status such as starting, ready, stopping, stopped, and error;
provide Open OpenBot, Start/Stop local runtime where applicable, Settings, diagnostic status, and Quit;
disable conflicting actions during transitions and recover from a timed-out transition based on observed health;
keep the app resident when the main window closes if menu-bar mode is enabled;
make Dock visibility and “launch at login” explicit owner preferences, not hidden side effects.
The status model is a pure reducer. Menu rendering and process actions subscribe to transitions rather than mutating state independently.
5. Native notifications
Use Electron Notification only for actionable lifecycle events: owner handoff/approval, completed background work, failed routines, disconnected local runtime, and important update status.
request/observe OS permission and degrade gracefully;
default to agent/work type plus a safe summary, not raw messages, tool output, secrets, URLs with tokens, or PII;
deduplicate/coalesce repeated events and respect per-category preferences plus quiet hours;
clicking a notification opens an allowlisted app route/session ID through the existing narrow bridge;
renderer content cannot construct arbitrary native notifications or external URLs.
6. Signing and notarization
Use a stable bundle identifier and Developer ID Application certificate. Release builds must enable hardened runtime, a secure timestamp, and the minimum entitlements actually required by Electron and packaged helpers.
Release order is strict:
Build renderer and packaged runtime from the tagged clean checkout.
Apply icons, Info.plist fields, protocol declarations, minimum macOS target, and entitlements before signing.
Sign every nested executable/framework/helper and the outer .app with electron-builder's supported signing path.
Verify signatures and entitlements.
Submit the final app archive to Apple's notary service with notarytool credentials.
Staple and validate the app ticket.
Build/sign the final DMG, notarize/staple it when required by the selected distribution path, then generate checksums/attestations.
No bytes inside the app may change after final signing/notarization. Avoid using codesign --deep as a substitute for correct nested signing. Release mode fails closed if real signing/notarization is unavailable; it must never publish an ad-hoc or unsigned artifact under a stable release name.
7. Signing credential custody
Store the Developer ID certificate/private key and App Store Connect notary credentials only in the protected desktop-release GitHub Environment from #31, with required reviewers and least-privilege access.
Import the certificate into an ephemeral keychain on the macOS runner and delete the keychain at job end.
Prefer App Store Connect API-key authentication for notarytool; never print key contents or certificate passwords.
PR and ordinary CI builds are unsigned and cannot access signing secrets.
Release artifacts and logs expose only certificate identity metadata, not credentials.
Document certificate/API-key rotation, revocation, and emergency release shutdown.
8. Verification on real macOS surfaces
Automate structural verification in the release job:
strict recursive code-signature verification;
Gatekeeper assessment of the app and distribution image;
stapler validation and retained Apple notarization log/status;
bundle ID, version, architecture, minimum OS, entitlements, icons, helper signatures, and absence of forbidden files;
mount DMG, copy to Applications-equivalent location, launch, wait for readiness, open/close window, exercise tray state, show a redacted test notification, and quit cleanly.
Run a quarantined-download installation E2E on clean supported macOS versions for arm64 and x64/translation as applicable. A package building successfully on GitHub is not sufficient evidence that Gatekeeper permits direct installation.
Legacy behaviors to carry forward
Health-driven ready/down state rather than treating a child PID as readiness.
Explicit starting/stopping states with bounded transition recovery.
Menu actions derived from one reducer state.
Graceful shutdown followed by a bounded forced termination fallback.
Stable product/bundle/version metadata before signing.
Notarization after every byte-changing packaging step.
Re-evaluate legacy entitlements and minimum-OS patches instead of copying them. Electron's Chromium/JIT needs and OpenBot's packaged runtime differ from Electrobun, and overly broad entitlements must not survive by inertia.
Delivery slices
Record the pinned legacy inventory, license, provenance, and selected behavior/source migrations.
Decide and implement the clean-machine local/hosted runtime model.
Add a deterministic production CLI build and bundle its immutable output into Desktop without adding a desktop package dependency.
Add the explicit per-user command-line-tool installer/repair flow and CLI manifest/version verification.
Add lifecycle reducer, health supervision, tray/menu, dock preferences, and safe notifications in Electron main.
Add icons, bundle metadata, minimum OS, entitlements, Developer ID signing, notarization, and stapling.
Add clean-machine Gatekeeper/install/launch/tray/notification/quit E2E coverage and operator runbooks.
Acceptance criteria
A release DMG downloaded on a clean supported Mac opens without bypassing Gatekeeper or using xattr, right-click overrides, or ad-hoc signing.
The app, nested helpers, and final distribution artifact pass signature, Gatekeeper, notarization, and stapler validation.
The installed app starts successfully without global Node/pnpm, repository source, or a manually started development service.
The installed app bundle contains a working openbot CLI whose version/commit matches the Desktop release and whose digest appears in the release manifest.
The bundled CLI starts and prints help/version without global Node, pnpm, tsx, TypeScript source, workspace packages, or external node_modules.
@openbot/desktop has no dependency or devDependency on @openbot/cli, and desktop development starts successfully without building or executing the production CLI artifact.
Installing the shell command is explicit, non-root, does not edit shell profiles silently, survives or repairs app moves/updates, and invokes the CLI inside the current signed app.
No development/test-only dependency is present in the bundled CLI manifest, and renderer code cannot execute arbitrary CLI commands or read raw CLI output.
Tray/menu state follows observed service health, transition actions are race-safe, and quitting reaps packaged processes.
Dock/menu-bar and launch-at-login behavior are explicit preferences and survive restart.
Native notifications are permission-aware, deduplicated, deep-link safely, and exclude raw sensitive content by default.
Release mode fails instead of publishing when signing/notarization credentials or verification are missing.
Both selected architectures and minimum supported macOS versions pass clean-machine installation and first-launch tests.
Every migrated legacy file/asset has provenance/license evidence, and no unrelated private source or secret is published.
Outcome
Ship OpenBot Desktop as a directly installable macOS application with Developer ID signing, Apple notarization, stapled tickets, and verified DMG/ZIP artifacts.
Migrate the applicable desktop behaviors and operational lessons from the private legacy
trytilde/agentrepository into OpenBot's Electron architecture: menu-bar/taskbar presence, status/menu state, local-runtime supervision, lifecycle notifications, configuration/versioning lessons, and release verification. Do not import Electrobun as a dependency or paste private implementation into the issue.Current state
apps/desktopuses Electron and electron-builder and can package DMG/ZIP artifacts, but has no signing identity, hardened-runtime entitlements, notarization, release workflow, tray/menu integration, notifications, or clean-machine installation proof.@openbot/clicurrently runs TypeScript source through the development-onlytsxloader and has no productionbinartifact. It is not bundled into Desktop today.Provenance and migration policy
Before copying any source or asset:
PROVENANCE.md;Prefer behavior-level reimplementation using Electron APIs where the legacy code is tied to Electrobun or a different runtime. Notifications need a new Electron-native implementation if no reusable legacy implementation exists.
Architecture
1. Desktop shell responsibilities
Electron main owns privileged desktop behavior. The renderer remains browser-compatible with context isolation, sandboxing, no Node integration, and a narrow typed preload bridge.
Main-process modules should be cohesive and independently testable:
application-lifecycle— single-instance lock, ready/activate/quit behavior, clean shutdown;local-runtime-supervisor— start/attach/stop and readiness for the packaged local OpenBot control runtime;desktop-state— pure reducer from health/user/system events to stable UI state;tray— menu-bar/taskbar icon and menu projection;notifications— bounded native notifications and safe deep links;updates— future release update integration, kept out of the first installability slice unless explicitly selected.Do not expose generic process, filesystem, shell, or credential methods to the renderer.
2. Clean-machine runtime
Choose and document one directly installable runtime model:
The default installer cannot silently depend on globally installed Node, pnpm, repository source, or a manually started control service. If local mode is included, package all required code/runtime, store mutable state under the platform application-data directory, bind only loopback by default, use deterministic port/conflict handling, and expose a readiness endpoint.
Supervision distinguishes “process exists” from “service ready,” uses bounded start/stop timeouts, reaps children on quit, and never logs inherited secrets or command environments.
3. Bundle the production OpenBot CLI
Ship the OpenBot CLI inside every Desktop application bundle, but keep it out of Desktop's development dependency graph and ordinary development startup path.
The CLI package owns a real production build:
bincontract foropenbot;tsx, TypeScript source, workspace resolution, pnpm,node_modules, or the repository checkout;openbot --version --json;The preferred runtime is a bundled JavaScript CLI plus a private, version-pinned Node-compatible runtime already shipped with Desktop. If Electron's executable is reused under its documented Node mode, isolate that launcher path from normal app startup and prove it on every supported Electron upgrade. Otherwise, bundle a dedicated minimal Node runtime. Do not rely on a globally installed
node,pnpm, ortsx.Desktop packaging consumes the CLI as an explicit release artifact, not as a package dependency:
@openbot/desktopmust not list@openbot/cliindependenciesordevDependencies.pnpm --filter @openbot/desktop devbuilds/runs the Electron shell without building, resolving, or executing the bundled CLI.extraResourcesor an equivalent provider-owned packaging input.~/.local/bin; do not silently edit shell profiles, requiresudo, or place files in system directories.The CLI is signed/notarized as part of the app bundle. Its bytes and manifest are final before nested signing begins, and the release manifest in #31 records the CLI digest separately. If the CLI is also published as a standalone artifact later, it must be built from the same tagged source and have the same semantic version, but it is not required for Desktop to run.
4. Tray/menu and dock behavior
Use Electron
Tray,Menu,nativeImage, andappAPIs:The status model is a pure reducer. Menu rendering and process actions subscribe to transitions rather than mutating state independently.
5. Native notifications
Use Electron
Notificationonly for actionable lifecycle events: owner handoff/approval, completed background work, failed routines, disconnected local runtime, and important update status.6. Signing and notarization
Use a stable bundle identifier and Developer ID Application certificate. Release builds must enable hardened runtime, a secure timestamp, and the minimum entitlements actually required by Electron and packaged helpers.
Release order is strict:
.appwith electron-builder's supported signing path.notarytoolcredentials.No bytes inside the app may change after final signing/notarization. Avoid using
codesign --deepas a substitute for correct nested signing. Release mode fails closed if real signing/notarization is unavailable; it must never publish an ad-hoc or unsigned artifact under a stable release name.7. Signing credential custody
Store the Developer ID certificate/private key and App Store Connect notary credentials only in the protected
desktop-releaseGitHub Environment from #31, with required reviewers and least-privilege access.notarytool; never print key contents or certificate passwords.8. Verification on real macOS surfaces
Automate structural verification in the release job:
Run a quarantined-download installation E2E on clean supported macOS versions for arm64 and x64/translation as applicable. A package building successfully on GitHub is not sufficient evidence that Gatekeeper permits direct installation.
Legacy behaviors to carry forward
Re-evaluate legacy entitlements and minimum-OS patches instead of copying them. Electron's Chromium/JIT needs and OpenBot's packaged runtime differ from Electrobun, and overly broad entitlements must not survive by inertia.
Delivery slices
Acceptance criteria
xattr, right-click overrides, or ad-hoc signing.openbotCLI whose version/commit matches the Desktop release and whose digest appears in the release manifest.tsx, TypeScript source, workspace packages, or externalnode_modules.@openbot/desktophas no dependency or devDependency on@openbot/cli, and desktop development starts successfully without building or executing the production CLI artifact.Dependencies
Open decisions