18 Commits
Author SHA1 Message Date
Daniel Kim 56c9b5fd0d Generate release notes with linked fixes and optional highlights (OR-357) (#472)
* Add generated release notes and issue links

* Run release note check in CI

* Grant release job read access to linked issues

* Preserve release tags and keep issue lookup optional

* Fix version check release notes link
2026-09-29 13:33:07 -07:00
Myles AndersonandClaude Opus 5.5 c95be22a35 OR-326 Add the Linux desktop app as a self-updating AppImage (#445)
* Open the macOS app's dashboard in its own window

The OpenResearch.app now hosts the dashboard in a native window (tao + wry
WKWebView) instead of handing it to the user's browser. It adds a standard
menu bar, save panels for downloads, a native window.confirm panel, and a
Cmd+Q that flushes workspace state and shuts the server down cleanly.
Pop-ups open in the system browser (http/https/mailto only).

The app now prefers port 4792 so the window's localStorage survives
relaunches. The Dock-click tab-focus script, its Apple-events entitlement,
and the SSE client counter it relied on are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Open the dashboard in its own window on Windows

Adds the Windows desktop app. A small GUI-subsystem OpenResearch.exe
(windows/launcher) starts the orx.exe beside it as `orx app` in a hidden
console, so orx and the git, shell, and agent processes it runs share one
invisible console instead of each flashing a window.

`orx app` shares the macOS window code in src/commands/app.rs: WebView2
window, pop-ups to the system browser, save panel, page-load reveal, and
port 4792. On Windows, closing the window quits, a second launch focuses
the running window (named mutex + event), and the taskbar groups the
window with the Start menu shortcut. An update restart relaunches as the
app on the same port. Quit on both platforms now goes through
up::request_shutdown instead of a self-sent SIGTERM.

An Inno Setup script builds a per-user OpenResearch-Setup.exe with a Start
menu entry and a WebView2 bootstrap. CI builds it as an artifact, and
release-windows-app.yml attaches it to releases once WINDOWS_APP_ENABLED
is set. The icon is embedded via embed-resource.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix the Windows build and address review of the Windows app

- The focus listener captured its bare HANDLE (edition 2021 disjoint
  capture) instead of the Send wrapper, which failed to compile on Windows.
- An orx.exe away from the CLI installer's prefix, like the app's, is now
  Portable even when the installer's receipt exists, so it can update itself.
- Single instance creates its event before the mutex; the launcher hands its
  foreground rights to orx.exe.
- Focus requests and server readiness are ignored while quitting, and focus
  waits for the first load. The save dialog is owned by the window.
- Telemetry counts an app start only after the instance claim.
- The installer reports a failed WebView2 bootstrap and shows progress.
- Icon embedding now fails the build if no resource compiler is found.
- CI format-checks the launcher; the release job drops an unpinned action.
- Docs: maintainer notes for the release gate, two known gaps, and wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tighten the Windows app after a second review

- Keep the macOS save panel free-floating: only Windows gets the window as
  its owner, since a parent makes rfd show a sheet on macOS.
- Treat the WebView2 bootstrap as failed unless the runtime is then present.
- Move the UTF-16 helper out of the single-instance module, and bind the
  kernel object names before the mutex call that GetLastError follows.
- Docs and a dead_code reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Add the Linux desktop app as a self-updating AppImage

A `desktop` cargo feature builds the windowed app on Linux (glibc, WebKitGTK),
leaving the static musl CLI builds untouched. build.rs sets cfg(desktop_app)
for macOS, Windows, and Linux with the feature, replacing the macOS-or-Windows
gates.

The AppImage's AppRun starts `orx app`, sharing the Windows entry point:
the webview is built into the GTK window's box (X11 and Wayland), closing
quits, a Unix socket in XDG_RUNTIME_DIR keeps one instance and focuses it,
and the login shell's PATH is adopted as on macOS.

scripts/build-linux-appimage.sh bundles WebKitGTK with pinned linuxdeploy,
its GTK plugin, and appimagetool, copying WebKit's helper processes and
rewriting libwebkit2gtk's /usr paths to ././ so they resolve inside the
image. CI builds x86_64 and aarch64 AppImages on Ubuntu 22.04 and
smoke-tests each under Xvfb; release-linux-app.yml attaches them with
linux-app.json once LINUX_APP_ENABLED is set.

The AppImage updates itself (InstallChannel::AppImage, updates/linux_app.rs):
a sha256-checked download renamed over the file, production builds only,
and Restart execs the new AppImage on the same port.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix the Linux app's downloads, bundled WebKit, and host environment

From review of the Linux desktop app:

- Downloads froze the app: rfd's GTK backend runs its dialog on a thread
  of its own, which waits forever on the main context tao holds. The save
  dialog is now a gtk::FileChooserDialog run on the main thread.
- The bundled WebKit helpers found their libraries only beside themselves,
  so they loaded the host's WebKit or none. They now get an RPATH to the
  image's usr/lib, and linuxdeploy no longer makes stray copies of them.
- The GTK hook's variables reached every host program orx opens. AppRun
  now saves the session's values and orx hands them back to xdg-open, the
  folder picker, error dialogs, and agents.
- The smoke test now runs after WebKitGTK is removed from the runner and
  checks that each WebKit helper runs, and loads libwebkit2gtk, from the
  image; cleanup kills the app's whole process group.
- Startup failures now reach stderr and a zenity or kdialog dialog.
- The tools and the AppImage runtime are pinned by sha256, and only the
  release job that publishes can write to the release.
- Smaller fixes: the shell probe runs after the single-instance claim and
  falls back to /bin/sh; APPDIR is canonicalized and must not be empty or
  root; relative project paths resolve against home inside the image.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep the session's GTK settings across Linux app restarts and terminals

Restore the host GTK variables before an update relaunches the AppImage, so
the new AppRun doesn't save the old image's values as the session's, and in
the dashboard's terminals. Share one APPDIR containment check between the
update channel, relative project paths, and the folder picker, which now keeps
a terminal `orx up`'s working directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Relaunch the AppImage only from inside it, and tidy after review

Pick the relaunch target with the same APPDIR check as the update channel, so
an `orx app` started from the app's terminal restarts itself. Hand the shell
probe the session's GTK settings, harden the smoke test's helper checks and
cleanup, and bring the docs up to date.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Run the AppImage detection test only on Unix

Its mount paths aren't absolute on Windows, which has no AppImage anyway.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Import the AppImage test's helper inside the Unix-only test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show the app window even when the dashboard never finishes loading

On a UTM VM the Linux app ran with no window: it stays hidden until the page
finishes loading, and that never happened. Show it 15 seconds after the
server is up regardless, say so on stderr, and have the smoke test fail if
that fallback fires or no window appears.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Give the Linux app its name and icon in the dock, and keep GIO off host modules

GNOME shows an unmatched window as a gear named after its class, so name the
program OpenResearch and have each launch install a desktop entry and icon
pointing at the AppImage (TryExec hides it once the file is gone).

Bundle GLib's TLS module and set GIO_MODULE_DIR to the image's, so the bundled
GLib stops loading the host's modules, built against a newer one. A second
launch now shows the window even before the page loads, so a stuck instance
can't swallow it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tidy the Linux app after the fourth review

Write the desktop entry only from the AppImage's own orx (shared with the
update relaunch), keep a session's GIO_EXTRA_MODULES off the bundled GLib,
mark a Dock-reopened macOS window as shown so the load fallback can't reopen
it, close a race in the smoke test, and note the Fedora and openSUSE TLS gap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show a stalled app window, and close the app before the installer runs

If the dashboard never finishes loading, show the window 15 seconds after the
server is up and say so on stderr; a relaunch (or a Dock click on macOS) also
shows it before the page loads. The installer and uninstaller now check the
app's single-instance mutex and ask the user to close it, rather than
replacing files under a running app and skipping its shutdown path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep a replaced download's original until it lands, and don't offer a launch without WebView2

Move the file a download replaces aside and restore it if the download fails
or is cancelled (WebView2's flyout can cancel), rather than deleting it up
front. The installer no longer offers to start OpenResearch when the WebView2
Runtime is still missing, since the window could not open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 13:19:33 -07:00
Myles AndersonandClaude Opus 5.5 2c63f548c2 OR-326 Open the dashboard in its own window on Windows (#439)
* Open the macOS app's dashboard in its own window

The OpenResearch.app now hosts the dashboard in a native window (tao + wry
WKWebView) instead of handing it to the user's browser. It adds a standard
menu bar, save panels for downloads, a native window.confirm panel, and a
Cmd+Q that flushes workspace state and shuts the server down cleanly.
Pop-ups open in the system browser (http/https/mailto only).

The app now prefers port 4792 so the window's localStorage survives
relaunches. The Dock-click tab-focus script, its Apple-events entitlement,
and the SSE client counter it relied on are removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Open the dashboard in its own window on Windows

Adds the Windows desktop app. A small GUI-subsystem OpenResearch.exe
(windows/launcher) starts the orx.exe beside it as `orx app` in a hidden
console, so orx and the git, shell, and agent processes it runs share one
invisible console instead of each flashing a window.

`orx app` shares the macOS window code in src/commands/app.rs: WebView2
window, pop-ups to the system browser, save panel, page-load reveal, and
port 4792. On Windows, closing the window quits, a second launch focuses
the running window (named mutex + event), and the taskbar groups the
window with the Start menu shortcut. An update restart relaunches as the
app on the same port. Quit on both platforms now goes through
up::request_shutdown instead of a self-sent SIGTERM.

An Inno Setup script builds a per-user OpenResearch-Setup.exe with a Start
menu entry and a WebView2 bootstrap. CI builds it as an artifact, and
release-windows-app.yml attaches it to releases once WINDOWS_APP_ENABLED
is set. The icon is embedded via embed-resource.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Fix the Windows build and address review of the Windows app

- The focus listener captured its bare HANDLE (edition 2021 disjoint
  capture) instead of the Send wrapper, which failed to compile on Windows.
- An orx.exe away from the CLI installer's prefix, like the app's, is now
  Portable even when the installer's receipt exists, so it can update itself.
- Single instance creates its event before the mutex; the launcher hands its
  foreground rights to orx.exe.
- Focus requests and server readiness are ignored while quitting, and focus
  waits for the first load. The save dialog is owned by the window.
- Telemetry counts an app start only after the instance claim.
- The installer reports a failed WebView2 bootstrap and shows progress.
- Icon embedding now fails the build if no resource compiler is found.
- CI format-checks the launcher; the release job drops an unpinned action.
- Docs: maintainer notes for the release gate, two known gaps, and wording.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Tighten the Windows app after a second review

- Keep the macOS save panel free-floating: only Windows gets the window as
  its owner, since a parent makes rfd show a sheet on macOS.
- Treat the WebView2 bootstrap as failed unless the runtime is then present.
- Move the UTF-16 helper out of the single-instance module, and bind the
  kernel object names before the mutex call that GetLastError follows.
- Docs and a dead_code reason.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Show a stalled app window, and close the app before the installer runs

If the dashboard never finishes loading, show the window 15 seconds after the
server is up and say so on stderr; a relaunch (or a Dock click on macOS) also
shows it before the page loads. The installer and uninstaller now check the
app's single-instance mutex and ask the user to close it, rather than
replacing files under a running app and skipping its shutdown path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Keep a replaced download's original until it lands, and don't offer a launch without WebView2

Move the file a download replaces aside and restore it if the download fails
or is cancelled (WebView2's flyout can cancel), rather than deleting it up
front. The installer no longer offers to start OpenResearch when the WebView2
Runtime is still missing, since the window could not open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:25:33 -07:00
Myles AndersonandClaude Opus 5.5 b05312eb8c Even out the DMG installer window's vertical padding (#454)
Finder's window bounds include the 28pt title bar, so the 640x320 frame
left a 292pt content area: the background's bottom was cropped and the
labels sat closer to the bottom edge than the icons to the top. Size the
frame for a full 320pt content area, and park dot-folders off-window so
Finder builds that show hidden files don't shift the icons down.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:17:36 -07:00
Myles AndersonandClaude Opus 5.5 7159b1f134 Open the macOS app's dashboard in its own window (#438)
The OpenResearch.app now hosts the dashboard in a native window (tao + wry
WKWebView) instead of handing it to the user's browser. It adds a standard
menu bar, save panels for downloads, a native window.confirm panel, and a
Cmd+Q that flushes workspace state and shuts the server down cleanly.
Pop-ups open in the system browser (http/https/mailto only).

The app now prefers port 4792 so the window's localStorage survives
relaunches. The Dock-click tab-focus script, its Apple-events entitlement,
and the SSE client counter it relied on are removed.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 11:41:41 -07:00
Daniel Kim 609ad7e3ed Fix OpenCode 2.0.6+ health probe and WSL browser launch (#409)
OpenCode 2.0.6 removed /api/status in favor of /api/info, so orx probed
two dead routes until the 30s timeout and reported "Could not read
OpenCode configuration". The V2 check now negotiates /api/info ->
/api/status -> /api/health and requires a JSON body carrying `version`,
since the SPA fallback can answer unknown routes with HTML 200s.
/api/health answering still marks the legacy (<=2.0.3) route layout for
export/generate paths.

On WSL, xdg-open often has nothing to launch, so the dashboard URL never
reached the Windows browser. On WSL the URL now goes to the Windows host
through rundll32.exe url.dll,FileProtocolHandler (by PATH, then by
absolute System32 path for appendWindowsPath=false), then wslview, then
xdg-open. rundll32 is tried first because some wslview builds re-parse
the URL through cmd and truncate at '&', which breaks the login
callback's state parameter. Native Windows switches from cmd /C start to
the same argv-safe rundll32 call.

The compat fixture learns the same health negotiation, treats the 2.0.6+
synthetic idle message correctly when waiting for a reply, and looks for
export under /api/experimental first.

Refs #341
2026-09-22 10:32:10 -07:00
Daniel Kim 94ef49b37e Run SSH experiments in existing Docker containers (#407)
* feat: run SSH experiments in existing Docker containers

* Simplify SSH container settings and harden launch recovery
2026-09-21 18:55:53 -07:00
Daniel Kim 5fa74d1c3b Support OpenCode V2 alongside V1 with safe history migration (#348)
* Support OpenCode V2 alongside V1 with safe history migration

* Avoid guessing OpenCode V2 authentication method

* Remove OpenCode migration backups after validated upgrade
2026-09-15 16:49:19 -07:00
Daniel Kim cf4baa720f Move managed repositories from cache to persistent storage (OR-230) (#315)
* fix(storage): migrate managed repositories out of cache (OR-230)

* Repair native OpenCode directories during storage migration

* Keep migration checks read-only and verify repaired references

* Bump version to 0.2.1
2026-09-11 22:52:14 -07:00
Daniel Kim e791d30695 OR-251: Add native SSH remote workspaces (#275)
* OR-251: Open remote dashboards over SSH

* feat: add native SSH remote workspaces

* feat: persist remote OpenResearch agent hosts

* fix: harden persistent remote host lifecycle

* fix: close persistent remote session races

* Complete persistent remote workspace sessions
2026-09-04 10:34:56 -07:00
Daniel Kim 76c671c3b2 OR-215 Use GitHub CLI for publishing (#254)
* OR-215 use GitHub CLI for publishing

* Isolate dev slot caches

* Color GitHub CLI connection states
2026-08-26 17:41:55 -07:00
Myles AndersonandClaude Opus 5 f27c858c78 Simplify the DMG installer window to a plain drag-to-install (#228)
Drop the brand mark from the DMG background so the window is just the app
icon, an arrow, and the /Applications alias. With the mark gone the window
shrinks 640x400 -> 640x320 and the icon row recenters (86px above the icons,
86px below the labels).

The arrow now sits level with the icon centers (ICON_Y) and is centered on
the icons' artwork gap rather than their 128px cells, since the two icons
inset their art by different amounts.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 15:47:58 -07:00
Myles AndersonandClaude Opus 5 45eb162e19 Focus the existing dashboard tab on a Dock click (#212)
The macOS app's Dock-icon handler called `open <url>` on every click. Once the
dashboard SPA has navigated off `/` the browser has no exact-URL match, so the
click opened a new tab instead of returning to the dashboard already open.

The handler now tries, best first: the exact tab via a JXA script over the
scriptable browsers; the browser window itself, when a tab still holds its
`/api/events` stream (the only way to infer an open dashboard in Firefox, which
exposes no tab API); and finally the plain open it did before. A repeat click
within five seconds takes the plain open, so a raise that did not surface the
dashboard is always recoverable — the app's port is ephemeral, and the user has
no other route back to it.

JXA rather than AppleScript because it resolves each browser's terminology at
run time: an AppleScript `using terms from` block fails to compile on a Mac
without Chrome installed.

Signing now passes macos/entitlements.plist, since the hardened runtime denies
the Apple event outright without it — a failure that appears only in signed
builds, never in a local one.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 13:28:06 -07:00
Daniel Kim e9d322e547 Add lightweight project briefs and refresh onboarding (#200)
* feat: add lightweight project briefs and onboarding refresh

* fix: refine onboarding and project workspace interactions

* fix: harden demo onboarding state

* fix: satisfy current clippy lint

* chore: bump version to 0.1.105
2026-08-17 12:46:35 -07:00
Myles AndersonandClaude Opus 5 46d359ff5e OR-173 Make the macOS app find harnesses, and stop it fighting a CLI install (#195)
* fix(app): detect harnesses in the macOS app by adopting the shell PATH

Finder launches OpenResearch.app through launchd, so the bundle starts with
PATH=/usr/bin:/bin:/usr/sbin:/sbin and no shell rc ever sourced. Harness
detection resolves `claude`/`codex`/`opencode` off that PATH, so a DMG user
with a Homebrew, nvm, or npm-global install saw every harness missing --
including an already-signed-in Claude Code session -- while a terminal
`orx up` on the same machine detected them all.

App mode now probes the user's shell once at startup and installs the result
via `local::search_path`, which harness lookup and harness children consult
instead of the process PATH. The probe is `$SHELL -ilc`: interactive, because
zsh reads .zshrc only for interactive shells and that is where PATH edits
live -- a login-only probe missed the ~/.local/bin holding `claude`. It runs
an inner `sh -c` so fish reports a colon-separated PATH rather than its own
list-valued $PATH, fences the value between per-probe nonce markers so rc-file
chatter cannot forge one, and requires an absolute directory in the result.
Best-effort throughout: a 5s cap, and every outcome logged.

The five detection probes that spawned a harness binary with no env prep
(`bin_version`, `claude_accepts_ultracode`, `claude_list_models`,
`codex_model_list`, `run_models`) now go through `prepare_env` like
`probe_auth` already did. Without that, a node-shebang install resolves via
the hydrated PATH but its `--version` fails for want of `node`, and
`gate_oauth_version` turns the missing version into `Unknown` -- telling a
signed-in user to sign in.

Verified end to end: a bundle run under launchd's PATH with an isolated
ORX_DATA_DIR reports Claude Code agentReady, and finds a `codex` reachable
only through the hydrated PATH -- codex has no fallback drop-location, so
that resolution can only have come from the override.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): stop the bundle and a CLI install from conflicting

Three ways the DMG app and a curl-installed `orx` stepped on each other.

`orx delete` takes an exclusive lifecycle lock and refuses when another
OpenResearch process holds it -- "Close `orx up`, `orx serve`, and any active
runs before trying again." CLI `orx up` holds the matching read lock via
`dispatch`, but app mode returns from `main` before `dispatch` runs, so it held
nothing: `orx delete` from a CLI install would wipe the store out from under a
live app. App mode now takes the same read lock for the life of the process.

Agents shell out to `orx`, and `chat::prepare_env` prepends the running
executable's directory so they get THIS build. In the bundle that directory
held only `OpenResearch`, so `orx` fell through to whatever CLI the user had --
a different version, or nothing at all for a DMG-only user. The bundle now
ships `Contents/MacOS/orx` as a symlink to the executable. Verified the symlink
survives `codesign --options runtime` + `--verify --strict`.

That alias would otherwise open a second dashboard, since a bare `orx` reaches
app mode's no-arguments condition. `launched_as_app_bundle` now also requires
that the process was invoked under the bundle executable's own name, which
keeps the alias a plain CLI while leaving a direct run of the executable path
-- the documented way to watch the app's logs -- in GUI mode. macOS
`current_exe` reports the launch path symlink and all, so it is canonicalized
before the comparison.

Verified safe already, and left alone: the app binds an ephemeral port rather
than 4791; the store is WAL with a 5s busy timeout; and run supervisors hold a
per-run exclusive lock, so a second server recovering the same active run exits
instead of double-driving it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): address review of the PATH and coexistence work

Blocker: `launched_as_app_bundle` had grown real logic -- four distinct
outcomes turning on a subtle platform fact -- with no coverage, and being
macOS-gated it could not get any, since CI runs ubuntu only. The decision is
now the pure `is_bundle_exe_launch(exe, argv0)`, compiled everywhere and
table-tested for all four cases; only the process-state read stays gated. Same
seam `extract_path` uses.

The lifecycle lock moves ahead of the PATH probe. Taking it afterwards left the
store unprotected for however long the probe ran -- up to 5s -- which is exactly
when an `orx delete` racing app startup would land. A failed `read()` is now
logged too; it was the one outcome in this module that could pass silently, and
silently is how the app would then run unlocked.

`orx delete` blocks on a running app now, so its message says so instead of
naming three things the user isn't running.

Probe diagnostics split the timeout from the spawn failure and name the shell,
since "which shell, and did it hang or fail to start" is the whole question a
report needs to answer.

`bin_version` gets the `NO_COLOR` guard its siblings already carry: it feeds
`parse_version`, an escape-laden version line parses as `None`, and
`gate_oauth_version` turns that into `Unknown` -- reproducing the bug this
branch exists to fix, via the synced env the probe now inherits.

`find_opencode` skips empty PATH components, matching `find_on_path`.

Packaging asserts the `orx` symlink survived staging. Every step preserves it
today, but a dereferencing copy would fail silently -- the app builds, signs,
notarizes and runs, and agents just quietly fall back to the user's own CLI.

Docs: corrected the same overstatement in `search_path`'s module doc that
DISTRIBUTION.md already fixed, and recorded the known gap that the probe
imports only PATH, so `ORX_DATA_DIR`/`XDG_*` from a shell rc reach the CLI but
not a Finder-launched app.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): import the shell's data and config dirs, not just PATH

`ORX_DATA_DIR`, `XDG_DATA_HOME`, and `XDG_CONFIG_HOME` exported from a shell rc
reached the CLI but never a Finder-launched app, so the two resolved different
directories: the dashboard opened the default database while `orx` on the same
machine used the user's, and the lifecycle lock added last commit guarded a file
neither of them shared. Same root cause as the PATH bug, one layer down.

`search_path` generalizes to `shell_env`: an allowlist (`IMPORTED`) the startup
probe reads, NUL-separated so a directory can contain anything but NUL, behind
`var()` -- process environment when no override is installed, so nothing changes
for a terminal `orx`. `store::env_path` and `config::config_dir` are the only
two places that resolve these, and both now go through it. The allowlist stays
short on purpose: these are the variables whose divergence makes the app and the
CLI act like separate installs, while credentials already reach harness children
via `prepare_env`.

This moves the lifecycle lock back after the probe, undoing last commit's
reordering. The reason is now structural rather than incidental: the lock path
is derived from `config_dir()`, so taking it before the probe would lock the
default path while the CLI locks the user's -- protecting nothing. A correct
lock a few hundred milliseconds late beats an immediate lock on the wrong file.

Verified with the app's process environment stripped of both variables while the
probe shell exported them: `orx.db` lands in the shell's data dir and
`orx.lifecycle.lock` in the shell's config dir, and the HOME-derived defaults
are never created.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): carry the imported environment into orx child processes

Importing the shell's directories fixed the app's own resolution but not its
children's, which made things worse rather than better for anyone exporting
`ORX_DATA_DIR`. `spawn_detached_supervise` and the harness adapters spawn plain
CLI processes; those never probe, so they re-resolved to the default store while
the app read the user's. A run launched from the app would have been supervised
against a different database than the dashboard was showing — before this
branch both landed on the default and at least agreed.

`shell_env::export_to` hands the imported variables to a child. `prepare_env`
applies it, so an agent's `orx exp run` shares the dashboard's store, and
`spawn_detached_supervise` applies it plus PATH, which also reaches the run
payload it launches (`localbox` spawns `bash run.sh` with an inherited env, so
it was losing the user's PATH entirely — no `python`, no `uv`, no `conda`).

`opencode::spawn_agent` had hand-rolled a copy of `prepare_env`; it now calls
it rather than growing a third copy of the same fix.

Verified with the app's process environment stripped of both variables while the
probe shell exported them: a harness child is handed ORX_DATA_DIR and
XDG_CONFIG_HOME, and an `orx` run with that environment creates no default
store.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(cli): print a runnable command in the hints the bundle breaks

A DMG user has no `orx` anywhere on PATH, so every hint telling them to run
`orx exp wait …` names a command their shell cannot find. Agents are already
covered -- `prepare_env` puts the bundle's `orx` first on their PATH -- but a
human driving the bundle's CLI is not.

`invocation::orx()` resolves the spelling once: plain `orx` when it is on PATH,
which is every CLI install and every agent, otherwise this binary's own path,
shell-quoted when it needs it. Nothing changes for anyone who installed the CLI.

Applied to the three hints the report named, each of which had been copied per
backend: the post-launch "Follow it with …" line (nine copies), the missing
run-command error (eight), and the outdated-version warning. Both hint bodies
move into `invocation`, so the eight backends now share one line each -- a net
deletion, and one place to fix the next time the wording moves.

`warning_for` takes the spelling as an argument rather than reading process
state, keeping its tests deterministic.

Not covered: the long tail of `orx login`-style hints in error strings, which
still print a literal `orx`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): close the environment gaps review found

A dashboard-synced `ORX_DATA_DIR`/`XDG_*` silently beat the shell-imported one
for harness children: `prepare_env` exported the imported vars, then the synced
loop overwrote any whose key was absent from the *process* env -- which in app
mode is exactly these. The app read one store while claude and opencode wrote
another. The guard now consults `shell_env::var`, so the imported value counts
as present. codex escaped this only because it re-pins `ORX_DATA_DIR` after
`prepare_env`; claude and opencode have no such pin.

Local run payloads were never actually fixed. The previous commit claimed the
supervisor passed its environment to `bash run.sh`, but the supervisor does not
spawn it -- `localbox::run_job` does, in the launching process, from
`LocalJobSpec::env`. A run started from the app still executed the user's script
under launchd's PATH, with no python, uv, or conda. The env now carries the
imported PATH and directories, and the supervisor comment drops the wrong claim.

Three more readers of these same variables were still going to the process env,
and two of them made harness detection itself diverge between app and terminal:
`harness::xdg_config_home` (whose doc claims to mirror `config::config_dir`) and
`opencode_auth_path`, which would report opencode signed out in the app while
the CLI saw it signed in. `invocation::resolves_on_path` was the third -- it
decides whether a hint can say plain `orx`, and the reader pastes that into the
shell whose PATH we imported.

Also: `orx login` -- the first hint a fresh DMG user meets -- now resolves like
the rest; `export_to` iterates in `IMPORTED` order so the startup log is
diffable; and the doc drift is repaired, including the octal-escape invariant
that makes the nonce's leading underscore load-bearing, the reopened
`orx delete` window, and the scope note listing what still inherits the process
environment (`git`, `gh`, `kubectl`, `ssh`, `publish-branch`).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(app): allow dead_code for the launch check off macOS

`is_bundle_exe_launch` is deliberately un-gated so its tests run on CI's Linux
runner, but its only non-test caller is `#[cfg(target_os = "macos")]`, so the
Linux bin target sees it as dead. `mod local` carries a blanket
`#[allow(dead_code)]`, which is why `shell_env::parse_probe` -- same shape --
did not trip; `mod commands` does not.

Verified by rebuilding with every `target_os = "macos"` in `src/` swapped to
`"linux"`, which reproduces exactly what the runner cfgs out. Clean, and no
other item on this branch is dead off macOS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 17:27:10 -07:00
Myles AndersonandClaude Opus 4.8 8c17722d88 feat(macos): styled drag-to-Applications DMG installer window (#188)
Opening OpenResearch.dmg now shows OpenResearch.app and an /Applications
alias to drag it onto, over a branded background — instead of a bare app
in the DMG root.

- scripts/generate-dmg-background.mjs: pure-Node PNG background (no image
  deps, mirroring generate-icon.mjs) with the brand mark + a drag arrow,
  rendered at 1x and 2x and combined into a HiDPI background.tiff.
- scripts/package-macos-app.sh: stage app + Applications symlink +
  background, lay out the Finder window (size, icon positions, background)
  via AppleScript on a read-write image, then flatten to the compressed
  DMG. Styling is best-effort: if the Finder step fails (e.g. headless),
  a functional but unstyled DMG still ships, and the temp/mount cleanup
  runs regardless.
- release-macos-app.yml: bound the 10x-billed macOS job with a timeout.
- DISTRIBUTION.md: document the installer window and its constants.

Verified locally: styled DMG builds with the app + Applications alias +
branded background, 640x400 window; happy-path cleanup leaves no leaked
temp dirs or mounts.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 19:14:15 -07:00
Myles AndersonandClaude Opus 4.8 dccc37c4e8 feat: downloadable macOS app (OpenResearch.app) + signed release hosting (#180)
* feat: scaffold downloadable macOS app (OpenResearch.app)

Adds a native macOS `.app` bundle whose executable IS the `orx` binary.
Launched from the bundle (double-click, no args) it enters GUI "app mode":
an AppKit NSApplication + delegate run loop on the main thread — Dock icon
and "OpenResearch" name from the bundle's Info.plist + .icns, and a
Dock-icon click that reopens the dashboard in the browser — while the
`orx up` server runs on background tokio worker threads.

App mode is entered only when the executable lives in `.app/Contents/MacOS`
AND argv is empty, so the bundled binary is still usable as a CLI.

- src/commands/app.rs: bundle detection + AppKit delegate + background server
- macos/Info.plist: bundle metadata (name, icon, identifier, version)
- scripts/generate-icon.mjs: transparent-PNG rasterizer (from favicon.svg)
- scripts/build-macos-app.sh: builds release orx, generates .icns, assembles
  OpenResearch.app into dist/
- objc2/objc2-app-kit gated under cfg(target_os = "macos") so musl Linux is
  untouched

The bundle is unsigned; code-signing + notarization is a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat: sign, notarize, and host OpenResearch.app via GitHub Releases

Automates distribution of the macOS app as a signed, notarized DMG attached
to each GitHub Release:

- scripts/build-macos-app.sh: ORX_APP_UNIVERSAL=1 builds a universal
  (arm64 + x86_64) binary via lipo for distribution.
- scripts/package-macos-app.sh: codesigns (hardened runtime, inside-out),
  notarizes + staples the .app (so it launches offline once dragged out of
  the DMG), packages a DMG, then notarizes + staples the DMG. Env-gated:
  runs unsigned locally, fully signed in CI.
- .github/workflows/release-macos-app.yml: on the "Release" workflow
  completing (workflow_run — a GITHUB_TOKEN-created release: published event
  can't trigger workflows), a cheap ubuntu job gates on a real dispatched
  release + all signing secrets + the release existing, then a macOS job
  builds/signs/notarizes and uploads OpenResearch.dmg to that release.
- macos/DISTRIBUTION.md: the one-time Apple setup + the six repo secrets.

Inert until the signing secrets are configured, so it is safe to merge
before the Apple Developer account exists.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: gate macOS signing behind CODEOWNERS + a required-reviewer environment

Hardens the release-signing pipeline for a public repo:

- .github/CODEOWNERS: marks the release workflows and macOS signing scripts
  as owned so they can't change unreviewed (with branch protection's
  "Require review from Code Owners").
- release-macos-app.yml: the cert-using job now runs in the `release-signing`
  environment, so adding required reviewers to it pauses signing for human
  approval — the Developer ID cert is never used by an unreviewed change.
- DISTRIBUTION.md: documents creating the environment + branch protection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore: add @sox8502 to CODEOWNERS

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* security: environment-scoped signing secrets + SHA-pinned actions

Hardens the release-signing pipeline:

- The 6 signing secrets move from repo secrets to the `release-signing`
  environment, so only the reviewed, environment-gated macos-app job can read
  them — the cert secrets never exist in the cheap ubuntu gate.
- The gate now keys on a non-secret repo variable MACOS_SIGNING_ENABLED
  instead of reading the secrets to detect configuration.
- actions/checkout and actions/setup-node are pinned to commit SHAs so a moved
  tag can't inject code into the job that holds the Developer ID cert.
- DISTRIBUTION.md updated: create the environment (required reviewers +
  main-only deployment branches), add environment secrets, set the variable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs: trim DISTRIBUTION.md to repo-specific config

Drop the generic Apple-portal walkthrough (enrolment, cert creation,
export click-by-click) — Apple documents that. Keep only what's specific
to this repo: the environment/secrets/variable, the local build+sign
commands, and the download URL.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 15:25:33 -07:00
Daniel Kim 417828221b Add isolated dev slot helper (#166) 2026-08-07 17:12:29 -07:00