* OR-350: Release idle agent children sooner on low-RAM machines
Each resident claude / codex app-server child holds a few hundred MB. On
machines with <= 8 GiB of RAM, release an idle child after 2 minutes and
keep at most one idle child per harness warm (LRU, 30s floor). Codex now
gets the same idle reaper Claude already had.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* OR-350: Unblock reaping after cancelled requests; release exited children
A Codex request cancelled by an outer timeout left its id in `pending`,
so the reaper treated the child as busy forever; a drop guard now removes
it on every exit. Both reapers now release a child whose process has
exited (an npm wrapper can leave its native app-server running) instead
of letting it hold the warm slot.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Use line-tables-only debuginfo for dev builds
Full debuginfo made each worktree's target/ grow to several GB. File:line
backtraces are kept; only debugger variable inspection is lost.
Co-Authored-By: Claude <noreply@anthropic.com>
* Drop debuginfo for third-party crates in dev builds
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* OR-349: Retry rate-limited literature requests with a clear wait message
alphaXiv, OpenAlex, bioRxiv and arXiv requests failed on the first 429,
so parallel agent calls to `orx paper`/`orx discover` gave up mid-analysis.
PubMed's existing retry becomes a shared `public_get` used by every keyless
literature request: up to 3 attempts honoring Retry-After (1s/2s backoff
without it), failing fast with "retry in Ns" when the host asks for longer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Honor HTTP-date Retry-After and randomize retry jitter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Require public reproduction inputs in agent feedback
* Clarify exact errors and conditional identifiers in feedback
* Keep feedback filing compatible with agent permission checks
* Make restricted public inputs reconstructible in feedback
* Generalize feedback detail guidance around sensitivity
* Bump OpenResearch CLI to 0.2.13
* 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>
* 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>
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>
* Trust the OS root store alongside the bundled webpki roots
reqwest and tokio-tungstenite were built with rustls' compiled-in Mozilla
root list only. On a network where a proxy terminates and re-signs TLS
(Zscaler, Netskope, Palo Alto), the re-signing CA is installed in the OS
trust store but invisible to rustls, so every outbound HTTPS request
failed at connect:
invalid peer certificate: UnknownIssuer
That takes out `discover`, `paper`, update checks, telemetry, and the
Overleaf websocket — `orx` has no working network path on such a network,
while `curl` to the same URL succeeds.
Enable `rustls-tls-native-roots` in addition to `rustls-tls-webpki-roots`
on both dependencies. reqwest merges both sets into one root store, so
this is additive: the bundled roots still apply where there is no OS
store (scratch containers), and the OS store is consulted on top. reqwest
already skips unparseable native certificates, which matters because
system bundles routinely carry a few.
No API or configuration change; corporate users need no flag or env var.
`SSL_CERT_FILE` / `SSL_CERT_DIR` are also honored now, since rustls reads
them when loading native roots.
The musl static-link rationale in the removed comment still holds — this
does not reintroduce OpenSSL.
* Clarify direct websocket trust scope
---------
Co-authored-by: Daniel Kim <sox8502@gmail.com>
* Let agents report OpenResearch feedback with orx feedback (OR-308)
Adds the orx feedback subcommand, which posts a bug, feature request, or
frustration report to the openresearch.sh /feedback endpoint (token only when
logged in, no-op when telemetry is opted out), and an orx-feedback agent skill
that sets a high bar for filing, requires redacting research details, and keeps
the report silent. feedback is allow-listed in the Claude plan gate.
* Gate orx feedback like analytics: no reports from development builds
orx feedback now uses telemetry's effective_disabled_reason, so development
builds, ORX_TELEMETRY_ENV test runs, and opted-out users send nothing.
* chore: bump version to 0.2.10
`orx discover pubmed` searches PubMed through NCBI E-utilities (esearch for
Best Match PMIDs, efetch XML for records), and `orx paper` reads a PMID,
`pmid:` id, or PubMed URL. PubMed joins the Settings/composer source toggles,
chat activity rendering, and the orx-lit-review skill alongside alphaXiv,
OpenAlex, and bioRxiv.
- Date windows map to `datetype=pdat` with both bounds filled; recency and
historical rerank a wider relevance pool like OpenAlex. No citation counts,
so `popular` keeps relevance order.
- Book records (StatPearls, GeneReviews) are parsed alongside journal articles.
- 429s are retried per NCBI's Retry-After with jitter, since keyless
E-utilities allows 3 req/s and agents issue parallel calls.
- Adds roxmltree because efetch serves abstracts only as XML.
- The PubMed logo is a placeholder mark.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Fix first-install onboarding stall via two-phase harness detection
The welcome page gated Continue on GET /api/harnesses, which ran a full
synchronous detection sweep: sequential --version probes per binary
candidate, auth probes, model-catalog calls, and OpenCode V2 server
startup — ~3s on Linux, ~6s on macOS, and 16-25s on Windows where each
subprocess costs 1.5-3s. Startup preflight ran the same sweep again and
discarded it, and the auth monitor cleared the cache on every generation
bump so even warm calls re-detected. OR-305: ~15% of users churned here.
Serve a spawn-free snapshot instead: filesystem-existence install checks
plus file/env auth evidence, marked catalogPending and clamped to
agentReady=false so no consumer treats provisional data as verified. A
single-flight background fill runs the full sweep, swaps the cache, and
emits harness.catalog; the UI polls at 1 Hz while pending so a missed
SSE event can't strand the provisional payload. refresh=1 still runs the
full sweep inline, outside the cache lock.
Also: preflight shares the cache path (no duplicate sweep), candidate
--version probes run in parallel, demo repo install prewarms in the
background off the onboarding click path, and Claude credential presence
(not expiry) indicates refreshable login so the auth overlay no longer
flaps the cache. Pending harnesses render "checking" in Onboarding,
Settings, ModelPicker, ChatPanel, and LocalModelSetup rather than a
false "unavailable"/sign-in prompt.
Measured on fresh VMs: cold harnesses 16-25s -> ~350ms on Windows,
~6.1s -> 0ms on macOS, ~3.2s -> ~1ms on Linux; warm ~0-4ms; onboarding
complete ~4.5s -> ~720ms on Windows. Catalog fill (~2-13s) is off-path.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Cut detection spawns, commit fills progressively, instrument timing
Second OR-305 pass on the Windows first-install profile. CreateProcess
blocks the runtime worker while AV scans each CLI, so detection spawns
now run on the blocking pool; spawn count drops from ~10 to ~4 via
claude's PE version resource + Credential Manager sign-out check, codex
version from the install path, opencode's install manifest, cursor's
bundled node, a merged claude auth/ultracode probe, and canonicalized
path dedupe. The catalog fill commits each harness entry as its probes
land instead of batching behind the slowest, so the first ready agent
unblocks onboarding at ~3.2s rather than the ~8s all-clear. Demo prewarm
git children run at idle priority and escalate if the user clicks in.
Every timed probe records into a bounded buffer that the fill drains and
reports as harness_detect / harness_detect_probe analytics events on a
shared fillId — per-probe and per-pass latency with harness/OS metadata,
so detection hangs are visible in production. Delivery is batched into
one payload per fill and stays nonblocking and opt-out-aware.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Scope probe timings per fill, hold spawn permits across process creation
Review found the shared probe-timing buffer misattributed concurrent
detects to the in-flight fill, semaphore acquisition spent probe
deadlines, a cancelled spawn released its lane while CreateProcess was
still running, and progressive claude entries were overlaid back to
Unknown until fill end. Probe timings are now scoped to a task-local
fill id (spawned probes re-enter it explicitly), the permit travels into
the blocking spawn and returns with a kill_on_drop child, every
deadline acquires its lane before the clock starts, and each committed
claude entry adopts its own auth verdict. first_ready_ms records only
published, overlay-stable readiness.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Scope fill timings to a per-fill sink, bound OpenCode probes, fix JSONC
Review follow-ups: probe timings now collect into a per-fill Arc<Mutex>
sink carried by the task-local instead of a process-global buffer — no
fill ids on records, no drain partitioning, no stale-residue clearing.
OpenCode's full pass falls back to `debug config --pure` when a config
source can't strict-parse (`.jsonc`, comments), so local providers no
longer silently vanish, and `run_models` puts process creation inside
its deadline so a wedged CreateProcess can't hold the fill. The final
first_ready_ms fallback only fires after cache ownership is confirmed,
and ready/installed counts come from the reconciled payload.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Address review majors: telemetry off the lock, timing on cancelled probes
- capture_detect runs via spawn_blocking so settings+outbox IO never sits
on the lock-held /api/harnesses path or a runtime worker
- probe timings record through a drop guard holding the sink, so aborted
speculative probes still report; the spec-miss rerun is timed too
- codex: an explicitly too-old version vetoes steering even when the
model/list handshake answered — catalog liveness isn't turn/steer proof
- opencode manifest pin: reject binary/manifest mtime skew in either
direction, not just a newer binary
- opencode config: snapshot_config returns an "unresolved" flag covering
.jsonc, comments, OPENCODE_CONFIG_CONTENT, and OPENCODE_CONFIG_DIR, so
the full pass knows when to pay for `debug config --pure`
- credman: ERROR_NOT_FOUND means signed-out; other enumeration failures
err toward the live probe
- pe_version reads VS_FIXEDFILEINFO by name instead of u32 offsets
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Await aborted speculative probes before a pass can drain timings
abort() only schedules teardown — the probe's timing guard lands in the
fill's sink when the task is dropped, which could trail the fill's drain
and lose the row. Awaiting the handle makes teardown deterministic.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* chore: bump version to 0.2.8
---------
Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Fix OpenCode config read when the output exceeds the pipe buffer
`opencode debug config --pure` writes its whole document in a single
unchecked `write(2)`. On Linux a pipe holds 64 KiB, so that write stops
at exactly that many bytes while the child still exits 0 — and the piped
read in `run_models` returned the truncated JSON as a successful result,
which surfaced as "Could not read OpenCode configuration" whenever the
resolved config was larger (issue #307).
Hand the child a temp file instead and read it after exit. The file is
created with `create_new` so a path pre-planted in a shared temp dir
cannot redirect the output, and the child is `kill_on_drop` so one that
outlives the timeout cannot keep writing to a file being removed.
`Command::output` cannot be used here: it forces stdout back to a pipe.
Two tests cover the boundary — the child sees a regular file on fd 1, and
70 KB of output arrives whole.
* Protect OpenCode capture files and isolate regression tests
* Release orx 0.2.0
---------
Co-authored-by: Daniel Kim <sox8502@gmail.com>
* Make orx build and resolve its tools on Windows
Block A of the Windows beta: the fixes that need no Windows machine to
write, so the branch is ready to clone the moment one is set up.
- Extend a bare binary name with PATHEXT when searching PATH. Every
harness is looked up by bare name, so on Windows the picker reported
Claude Code, Codex and OpenCode all absent and the dashboard could do
nothing.
- Compose a child's PATH with `;` rather than a hardcoded `:`.
- Find bash through `git` rather than PATH. The `bash.exe` usually on
PATH is the WSL launcher in System32, which sees a different
filesystem than the run directory it would be handed.
- Give git an empty path it understands. `/dev/null` reads as a missing
relative path on Windows, silently re-enabling repository hooks.
- Check a local run's liveness with a zero-timeout wait; there is no
`ps` to shell out to.
- Link through native_store::create_symlink, which already has both
branches, instead of calling the unix symlink directly.
- Add a windows-latest CI job so none of this regresses.
The Windows branches here have not been compiled yet — that is the next
step, on the machine.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Allow CI to be dispatched against a branch
* Compile the remaining unix-only code paths out on Windows
Round one of the Windows compile, from the first CI run: 25 diagnostics,
seven root causes.
- Give remote_host the `#[cfg(not(unix))]` twins its uid/mode helpers were
missing, matching what `directory_writable` already does. Windows keeps
only the symlink and directory checks in `ensure_private_dir`, which is
a weaker guarantee than the unix path makes.
- Gate the control channel itself. It is a Unix domain socket, so
`--remote-host` is now refused up front on Windows rather than starting
a host nothing could attach to. Local `orx up` is untouched.
- Annotate `signal`, whose type came only from the cfg(unix) block.
- Gate the EXDEV rename fallback, the inode and mode assertions, and the
shell-hook tests, all of which assert on things Windows does not have.
- Silence the parameters that are only read on unix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Gate the remote-host command surface off on Windows
Round two, from the cascade the unresolved socket import had been
suppressing: every caller of the six control-channel functions.
The module splits cleanly in two. `orx up` uses its types and its
dashboard locking — HostDescriptor, DashboardLock, canonical_data_dir —
and none of that needs a socket. The `orx remote-host` command surface is
socket-backed all the way down, so it is gated as a unit and the command
now refuses on Windows instead of failing somewhere further in.
Also: the two imports only the gated half reached, and `signal`, which
loses its one assignment on Windows and with it the need for `mut`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Keep the compiled-out remote-host half rather than gate it item by item
Round three was 24 dead-code and unused-import lints and no real compile
errors — the tail of removing a feature's entry points while its
machinery stays behind.
One scoped allow on the module says that plainly. Gating each constant,
helper and request type would be bulkier than the code it guards, would
have to be unpicked the moment the control channel gets a Windows
transport, and would say nothing the module comment does not.
updates.rs is the same shape at smaller scale: relaunch_target and
relaunch_args keep their tests on every platform and lose only their
caller in the binary.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Stop handing git a verbatim path, and let test cleanup fail
The Windows suite ran for the first time: 662 passed, 46 failed. Two
causes account for 27 of them.
`repository_git_dir` canonicalizes, and Windows answers canonicalize with
a `\\?\C:\…` verbatim path. Git rejects those outright — "not a git
repository" — so importing any existing repository failed. This is a real
bug, not a test artifact; the prefix now comes off for drive paths, which
are the only ones with a plain form.
The other 21 were `remove_dir_all(root).unwrap()` in test teardown, which
Windows refuses while any handle into the tree is still open. The
codebase already prefers a tolerant cleanup in 146 other places; these 86
now match. All of them are inside `#[cfg(test)]`.
Also: the probe fixtures asserted on POSIX PATHs, which are not absolute
on Windows and so were rejected by the parser under test. They are
parameterized rather than gated — the parsing they cover is not
platform-specific, only the fixture was.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Check out LF on every platform
Git for Windows defaults to core.autocrlf=true, and SYSTEM_PROMPT.md and
the agent-skills SKILL.md files are include_str!'d at build time. A
Windows build therefore embedded CRLF and then failed to parse it:
playbook_md() splits the template on `-->\n\n` to drop its leading HTML
comment, that split stopped matching, and `unwrap_or` kept the comment —
so every agent session would have been handed OpenResearch's internal
notes as the opening of its system prompt.
Two tests caught it, one of them by scanning the rendered playbook for
unresolved `{token}` placeholders rather than a fixed list. That is the
only reason this surfaced as a test failure and not as a shipped binary.
Nothing renormalizes: no tracked text file has CRLF in the index today,
so this only governs what lands in a working tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Fix what Windows found: exec bits, remote paths, cancellation
Round two of the Windows suite: 691 passed, 17 failed. Four of those were
real bugs rather than test artifacts.
Demo onboarding rejected the demo it had just built. NTFS has no
executable bit, so runcpu.sh committed as 100644, the tree hash moved,
and the commit ids drifted off the BASELINE_SHA/EXPERIMENT_SHA the demo
validates itself against. The mode now reaches the tree through the
index, where it does not depend on the filesystem.
Remote paths were validated with local rules. `storage_root` asked
`Path::is_absolute` about `/home/me/.cargo/bin/orx`, which is false under
Windows, so a Windows client rejected every legitimate remote path — and
the PathBuf it built would have reached a Linux shell separated by
backslashes. Remote paths are POSIX whatever the client runs, so they are
handled as strings now.
`manage_local_file` took `reports/result.md` from the dashboard and
answered `reports\summary.md`, which the next request would have carried
straight back. Relative paths cross the API `/`-separated.
Cancelling a local run did nothing: there is no process group to TERM.
`taskkill /T` takes the tree instead, which is what TERMing the group was
for.
The rest were fixtures asserting on POSIX shapes, a filename using a
character Windows reserves, and two LaTeX tests that need a symlink and a
shebang.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Let the index own the demo's exec bit
Setting the mode through `update-index` fixed the commit ids but left the
index at 100755 against a working tree NTFS always reports as 0644. With
`core.filemode true` git believed the filesystem, read runcpu.sh as
modified, and refused to check main back out. Windows trusts the index
instead, which is where the mode came from.
The two telemetry failures are not diagnosed. HTTP delivery itself works
on Windows — `sender_posts_the_first_party_endpoint` asserts an
Acknowledged post and passes — so what fails is the removal that follows
it, and reading has not told me why. Both assertions now report what a
second removal says, which separates a blocked delete from a delete that
succeeded while the name lingered.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Keep the demo's origin path native, and judge cancel by liveness
Three down to two.
`join("demo-repos/nanochat.git")` embeds a separator the platform did not
choose, so on Windows the bare origin came out as
`...\data\demo-repos/nanochat.git` — mixed, and textually unequal to what
git echoes back for the remote it was given. Filesystem calls did not
care; comparing the recorded URL to git's answer did. Split into two
joins, in product code as well as the tests that caught it.
`taskkill /T` returns non-zero when any descendant has already exited on
its own, which says nothing about whether the run stopped. Cancellation
now reads the leader's liveness, which is the question actually being
asked.
The telemetry pair is one step from diagnosed. The retry in the previous
round answered Ok(()), so the file was there and freely deletable and the
removal was never reached — delivery must have come back Retryable. The
assertion now names the outcome, which separates a request that never
landed from a response classified unexpectedly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Read the request before answering it in the telemetry mock servers
The last two Windows failures, and they were the tests' fault rather than
delivery's.
Two of the three mock servers accepted a connection and wrote their
response without ever reading the request. Closing a socket that still
holds unread data aborts the connection on Windows instead of closing it
gracefully, so the client saw a reset rather than the reply it had
already been sent, and classified a 400 as Retryable — which left the
outbox file the assertion expected to be gone. The third server always
read, which is why it passed and misled the search toward the removal.
The read loop it already had is now shared by all three.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Canonicalize into paths a child process can actually use
An audit of the `orx up` paths no test exercises. Every bug this branch
has fixed so far was in code a test happened to touch; the dashboard's
own request handling was the largest area where that was not true.
Windows `canonicalize` answers with a verbatim `\\?\C:\…` path, and
`CreateProcessW` will not take one as a working directory. The dashboard
canonicalizes a session's checkout root and then runs git in it — twelve
call sites behind `resolve_checkout_root`, covering the code tree, file
reads and writes, and diffs — so none of that could have worked. The same
root reaches `run_shell_command` as the cwd for a `!` command, and Codex
receives canonicalized paths as its sandbox writable roots.
Consistency matters as much as the spelling: containment checks compare a
canonicalized child against a canonicalized root, and a mix of the two
forms denies access to paths that are genuinely inside. So every
canonicalization in the crate now goes through one helper rather than the
handful that happened to be reachable from a failing test — that is what
makes the property hold rather than the individual fixes.
The earlier one-off in `repository_git_dir`, which the import failure
found, is folded into it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Look for the harness binaries under the names they actually have
Second audit pass, over the spawn path.
`find_claude` and `find_opencode` fall back to their installers' drop
locations when PATH does not answer, and both spell them as bare names —
`~/.local/bin/claude`, `~/.opencode/bin/opencode` — which match no file
on Windows. `~/.local/bin` is exactly where Claude Code's own Windows
installer puts claude.exe, so the fallback was dead precisely where it
was needed. Both now look for the same names PATH does.
The rest of the spawn path holds up: config directories travel as
environment variables, where a raw path is correct; Codex's config.toml
is written through serde_json, whose backslash escape is the one TOML
wants; and the binaries themselves come from find_on_path, which learned
about PATHEXT earlier on this branch.
One gap left deliberately: the PATH guard writes zsh and bash startup
hooks and needs ZDOTDIR or HOME to place them, neither of which Windows
sets, so it returns early and the guard is simply absent there. It fails
closed — a session gets no hook rather than a broken one — and building a
Windows equivalent is worth more than a beta needs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Address review round one
Six blockers, all real.
`orx_bin_dir` dropped any directory whose name contains a colon, which on
Windows is every absolute path, so agents were never handed the running
orx on PATH — and the separator constant added earlier sat in a branch
that never ran. It filters on that constant now.
A dashboard shell command that hit its timeout killed nothing on Windows
and then waited on the child with no timeout, hanging the turn forever.
The non-unix arm terminates the tree, the shape latex already used.
BIBINPUTS/BSTINPUTS joined with a colon, so bibtex on Windows lost both
the paper's own directory and the system defaults.
`restore_local_repository` never pinned core.autocrlf, so a restored demo
clone was written CRLF against LF blobs and then failed the cleanliness
check that reads it with global config disabled — telling the user their
demo "is not the OpenResearch nanochat demo".
The remove_dir_all sweep had converted four setup removals along with the
teardown, letting tests pass while testing nothing.
The Windows uid/mode stubs were unreachable: their whole chain is rooted
in cfg(unix) entry points. Gated instead, stubs deleted, and with them a
comment claiming a weaker-but-working private runtime directory that does
not exist.
Also from review: prefix stripping now matches the parsed prefix, so a
non-UTF-8 name cannot keep `\\?\` while its root loses it and fail its own
containment check; UNC verbatim paths are decoded rather than passed
through. PATHEXT set-but-empty no longer reports every tool missing. The
bash fallback no longer resolves back to the WSL launcher it rejects.
pdfPath goes through the same API path rule as every other relative path.
The telemetry signature widened for a test message is reverted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Address review round two
Two blockers, both introduced by round one's own fixes.
Deleting the Windows uid/mode stubs was wrong: `open_lock` is ungated and
calls all three, and `DashboardLock::acquire` reaches it on every `orx
up`. That is a Windows compile error on the one platform this branch
exists for, and macOS cannot see it. The ownership and mode checks are
gated inside `open_lock` now, which keeps the file-type check everywhere
and states plainly what Windows does not enforce.
Teaching `orx_bin_dir` about the platform separator also handed
`ORX_BIN_DIR` to PATH_GUARD, a POSIX snippet that joins `PATH` on colons.
Under Git Bash a drive letter split it in two and the guard re-prepended
on every startup. The stamp is skipped off unix; `prepare_env` still
fronts the directory, which was the point.
Also: `cancel_job`'s cfg'd `return` is the function's last statement once
stripped, which the Windows clippy job would have rejected; the restore
path pins `core.eol` alongside autocrlf so a global `core.eol=crlf`
cannot reproduce the same dirty checkout; the verbatim UNC head is built
from `OsString` like the tail it is joined to; and the null-device
comment no longer claims git falls back to the repository's own hooks,
which it does not.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Polish from review round three
No blockers in either round-three review. Taking the nits: `open_lock`
fstats once instead of twice; the Windows branch of the `ORX_BIN_DIR`
stamp is a `#[cfg]` rather than a runtime-false `cfg!`, so it stops
resolving the executable only to discard it; two doc comments that
described one platform now describe both; and the ownership comment names
the case it does not cover — a lock under a shared `ORX_DATA_DIR` has
only its ACL on Windows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Keep the windows job out of the release gate
release.yml calls ci.yml and requires it to pass, so the windows job
added here would have held up a macOS or Linux release over a platform
those releases ship nothing for.
`plan` is passed only by release.yml, so skipping on it draws the line
where it belongs: Windows still gates pull requests, which is what keeps
it from rotting, and never gates a release.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Upload a release orx.exe, not a debug one
The artifact is what a tester is handed, and a debug build is slow enough
to colour their impression of the dashboard rather than the code's.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Turn SSH multiplexing off explicitly on Windows
Win32-OpenSSH implements no connection multiplexing: it accepts
ControlMaster and ControlPath and then never creates a master. Omitting
them is not enough, because the user's own ssh_config can still set a
ControlPath, and one it honours fails the connection outright with
"getsockname failed: Not a socket". Passing ControlMaster=no and
ControlPath=none is what overrides that.
The socket machinery goes with it — the control directory, its path hash
and its preparation are unix's, and `master_is_running` can only answer
false where no master is ever created. Nothing changes on unix, where the
shared master still covers later status, log and job commands.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Address review
Two tests asserted the unix option set unconditionally and would have
failed on Windows — the very platform this change is for, and one CI
never builds. Both derive the expectation from the platform now.
`PathBuf`'s only uses became unix-gated, so its import would have failed
the Windows clippy job under -D warnings.
git shells out to ssh with its own command line, which never carried the
override, so a user's ssh_config could still break git-over-SSH on
Windows for exactly the reason this change exists. Routed through one
helper.
The rest: a doc comment my new test had come between and its rightful
test; four comments still promising multiplexing unconditionally, the
`interactive_args` one worst because it states the invariant that is
false on Windows; the new test pinned to the exact vector the way its
neighbour already is; and BatchMode coverage split out, since it holds on
every platform and was about to be gated away with the ControlPath half.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Address review round two
No blockers in either round-two review. Taking the nits.
Deriving the option count from `multiplexing_opts` had quietly removed
the only assertion pinning the unix vector — ControlPersist could have
been deleted and nothing in the tree would have failed. Pinned alongside
the Windows one.
The rest: a test whose name promised reuse on a platform that reuses
nothing; `git_ssh_command` says which command it builds; two more doc
comments of the class round one fixed, in the two other files that carry
them; and the Windows sentence dropped from `ssh_opts`, where it was the
third statement of the same fact within 150 lines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Point git's config paths at an empty file, not the null device
Onboarding died on Windows at the demo's first `git init`:
fatal: unable to access 'NUL': Invalid argument
`NUL` is right for `diff --no-index`, which recognizes it before touching
the path, and wrong everywhere a path is actually read. Git skips a
config file that reports ENOENT and fatals on any other error, and
Windows cannot `access()` its null device by name at all.
A real empty file behaves the same on every platform, and this file
already had that idiom in `clone_public_repository`. Shared now, and
pointing `hooksPath` at it still disables hooks, since git finds no hook
inside a file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Give the spawned bash its own toolchain on PATH
A local run died at `run.sh: line 6: mkdir: command not found`. bash was
running; it just had no coreutils. Git for Windows keeps those in
`usr\bin` and puts only `cmd` on the Windows PATH, so a bash started from
a Windows process inherits a PATH with no Unix tools at all.
Fronted for the two bash spawns only — a local run's launcher and the
composer's `!` command. Doing it for every child would shadow Windows'
own `find` and `sort` with the MSYS ones, which is a worse bug than the
one being fixed.
prepare_env's PATH is now a function the bash spawn can extend, so the
running orx still comes first and the toolchain sits ahead of both.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Fold the toolchain into the PATH run.sh exports
The previous commit set the toolchain PATH on the spawn, which run.sh
then overwrote on its second line: localrun exports the shell's PATH into
the script so a user's own command finds python and uv, and that export
wins over anything the process was started with.
Caught by the agent inside the dashboard, which read the generated run.sh
rather than the launcher and saw the hardcoded Windows PATH for what it
was.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Let the demo probe run under Git Bash
Two things differ there, and the probe hit both. uv ships no shell
installer for Windows, so the setup uses its PowerShell one; and uv lays
the venv out as `Scripts/` rather than `bin/`, so both activations are
tried — either platform can have either layout depending on how the venv
was made.
These are Rust constants rather than files in the demo repository, so the
commit ids the demo validates itself against do not move. `runs/runcpu.sh`
carries the same POSIX assumption and is committed, so making the
project's own run command portable is a separate change that regenerates
those ids.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Stop writing a Windows PATH into a bash script
The run script exported the process PATH so a user's command could find
python and uv. On Windows that value is semicolon-separated, and bash
splits PATH on `:` — so it collapsed at the first drive colon and nothing
resolved, `mkdir` included. Adding the Git toolchain to it, as the last
two commits did, only added more entries to the same unusable string.
Git Bash converts the PATH it inherits into POSIX form for itself, so the
fix is to leave it alone: the launcher sets the process PATH, and the
script no longer overwrites it.
The `cd` on the next line had the same shape — bash reads a quoted
`C:\…` literally — so the run directory is forward-slashed now.
Found by the agent running inside the dashboard, which read the generated
run.sh, spotted the separator mismatch, and verified a candidate PATH
against both splitters before proposing anything.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Hand the shell paths it can read, not Windows ones
`tar: Cannot connect to C: resolve failed` — GNU tar reads a colon before
the first slash as a `host:path` remote spec, so the snapshot archive's
Windows path made it try to resolve `C:` as a hostname. Forward slashes
would not have helped; the colon is what triggers it.
One conversion now covers both places a local path reaches the shell: the
archive tar unpacks, and the `cd` the launcher writes. `/c/Users/…` has
no colon at all. The HF backend passes a Linux container path and is
deliberately left alone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Give runs a UTF-8 stdout
A run died mid-training on `UnicodeEncodeError`: CPython encodes stdout in
the console's codepage, cp1252 on a default Windows install, and the
script printed a banner outside Latin-1. Nothing to do with the
experiment — it had already trained a tokenizer by then.
`PYTHONIOENCODING=utf-8` joins `PYTHONUNBUFFERED` as a default the author
can override, through the same helper every backend already calls, and
open-coded once more for kubernetes, whose env is a JSON array.
Set on every platform rather than only Windows: a Linux container with
`LANG=C` has the same ASCII default and would fail the same way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Let git write paths past Windows' 260-character cap
Creating a session worktree for a deep repository failed partway through
with "Filename too long". Windows refuses paths over 260 characters
unless a program opts into the wide API, and a session worktree spends
about a hundred of them on `worktrees/<project>/chat_<session>` before
the repository's own paths begin.
`core.longpaths=true` on the invocations that write a working tree: the
shared helper every worktree and checkout goes through, the authenticated
fetch, and the anonymous clone. Read-only plumbing does not need it.
This was graded Minor in the original survey. It is a blocker for any
repository with a deep tree, which is most of them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Link the agent's config without asking Windows for a privilege
Windows grants symlinks only to an elevated process or one in Developer
Mode, so preparing the isolated agent home failed onboarding outright with
os error 1314. Fall back to the links that need no privilege: a junction
for a directory, which reads back exactly like a symlink, and a hard link
for a file, which does not — so the reconcile now recognizes one instead
of filing a conflict against it on every launch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Stop asking Windows' ssh to multiplex
Windows' OpenSSH cannot create the AF_UNIX socket ControlMaster needs, and
rather than decline the option it kills the connection outright with
"getsockname failed: Not a socket" — so every ssh call orx made from Windows
failed, whatever the destination. Drop the Control* options there and pay a
handshake per call.
Same option vector, same platform: `UserKnownHostsFile=/dev/null` is a name
Windows' ssh takes literally, creating a `\dev\null` on the current drive.
It now gets a scratch file of orx's own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Count the Windows ssh opts the Windows way
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Revert the duplicate multiplexing fix
PR #302 already turns Windows' multiplexing off, and does it by setting
ControlMaster=no explicitly rather than by omission — which is what
overrides a ControlPath in the user's own ssh_config — and covers git's
ssh besides. Take that one instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Give Windows a real file for the host keys it must forget
`UserKnownHostsFile=/dev/null` is a name Windows' ssh takes literally,
creating a `\dev\null` on whichever drive is current. Point it at a
scratch file of orx's own instead; StrictHostKeyChecking=no is what makes
a recycled provider key acceptable, so the behaviour is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Ship a Windows binary and a way to install it
The demo's own run command reached for a POSIX uv installer and
`.venv/bin/activate`, neither of which exists on Windows, so the nanochat
demo could not run there; both now branch at runtime. That changes the
experiment tree, hence the new EXPERIMENT_SHA.
Releases built no Windows artifact at all, which is what the CI job's
non-blocking rationale rested on. Add the target and a PowerShell installer
so a beta tester has something to install.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Tell Windows testers what they need before they need it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Let Windows hold up a release now that a release ships it
The job skipped release runs because releases carried no Windows artifact.
They do now, so a regression on main would ship broken rather than fail
loudly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Say what signing will and will not fix
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Say Git for Windows is missing at startup, not at the first run
Without it there is no usable bash, so experiments and the demo die on
their first command with a spawn error naming a path the user has never
heard of. The agents check next to it already warns this way.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Make a second click reach the dashboard, not die on its port
Double-clicking orx.exe runs `orx up`. Doing it while orx is already
running failed the bind, printed an error into a console Explorer then
closed, and looked to the user like nothing happened at all. Open the
running dashboard instead, and hold the console open for whatever else
a startup failure has to say.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Make a failed start say so where a console cannot
The console Explorer opens for a double-clicked orx.exe dies with the
process, so both ways this program exits with something to say — a
returned error and a panic — reached a window that was already gone. Put
the message in a dialog instead, and only when no terminal is attached to
read the printed one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Start the dashboard when orx.exe is double-clicked
A bare `orx` prints usage and exits, which is right at a prompt and useless
from Explorer: the console it printed into closes with the process, so the
gesture looked like nothing happening. Owning the console is what tells the
two apart, and it now starts `up` — the same trade the macOS .app makes for
the same gesture.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Keep demo installs seeded before the Windows change working
Moving EXPERIMENT_SHA left every existing mac and linux install pointing
at a commit its repository does not contain. Each agent turn resolves the
session worktree from that commit, so all three demo chats would have
failed on their next message after upgrading. An install now starts its
sessions from whichever known experiment its demo branch descends from.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Address review round four
- Release verify job reads the Windows .zip and orx.exe; without it no
release would have published for any platform.
- "Already running" reuse only for a double-click, and only of a
dashboard speaking this build's protocol. On mac and linux a second
`orx up` fails on the port again, as it did before this branch.
- An old demo install whose cache was wiped but origin kept restores its
own experiment rather than building a sibling it cannot push.
- Same-file probe errors fall through to the old reconcile; the demo probe
only puts ~/.local/bin on PATH after installing uv; the panic hook is
Windows-only.
- Docs name the assets cargo-dist actually publishes.
- Comments cut to the one-or-two-line house rule; three doc comments moved
back onto the functions they describe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Address review round five
- A double-click keeps the flags it was given (`--no-telemetry` from a
shortcut no longer turns telemetry back on).
- A kept demo origin is validated before a clone is restored from it, so a
foreign or moved origin is named as the problem instead of the cache.
- The ssh opt-vector test reads the Windows known-hosts path once; it
follows XDG_CONFIG_HOME, which telemetry tests mutate in parallel.
- Every comment this branch adds is now one or two lines.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* Address review round six
- Remote storage paths accept an interior `.` again. `Path::components`
skipped it on main, so `ORX_DATA_DIR=./orx` on a remote box — probed as
`/home/u/./orx/orx.db` — broke `orx up --remote` on this branch.
- A foreign kept demo origin is rejected before anything is restored from
it, now with a test; `install_repository` keeps main's shape, since the
restore helper creates its own parent.
- Comments trimmed last round get back the why they lost, and the
PYTHONIOENCODING one no longer claims the console codepage: redirected
output uses the ANSI one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* Support local OpenCode models in onboarding
* Add local endpoint setup to OpenCode settings and model picker
* Handle recovered OpenCode context compaction without false failures
* Bump version to 0.1.122 for local model release
* fix: rebuild ui/dist for the telemetry UI changes in #29766d2cd8 changed ui/src without regenerating the committed bundle, which
release builds embed verbatim, so a release cut from that state would ship
the Rust half of the telemetry feature and none of the UI half.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* chore: bump version to 0.1.121
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat: live Overleaf sync over the editor channel
A linked paper now follows Overleaf as people type, and sends saves
straight back, instead of syncing over the git bridge every 30s. The
bridge snapshots on demand and rate-limits polling, so it can never be
immediate; the editor's own socket.io channel can.
- `local::overleaf_live` speaks the channel (sharejs text OT, UTF-16
positions, one in-flight op) for the paper's `.tex`/`.bib`/class files.
Each doc keeps the confirmed server text as a three-way base, persisted
per paper folder and seeded only from the git sync's agreed hashes, so a
reconnect can tell a local edit from a remote one. Where both sides
moved the same span the file is held for the git sync's conflict
prompt rather than guessed at.
- The git sync stays for figures, conflicts and fallback, pausing the
channel while it runs and re-seeding it afterwards. It also runs on a
slow timer while live, so figures move without being asked.
- The session cookie can be imported from a signed-in browser
(Chrome/Edge/Brave/Arc/Vivaldi/Chromium/Firefox, macOS) instead of
pasted. Only the Overleaf cookie is read, and the Keychain is asked for
a browser's key only when that browser actually holds one.
- Saves carry a hash of the bytes the editor loaded, so a save cannot
overwrite a collaborator's edit that landed first.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix: address review of the live Overleaf sync
- A channel stuck reconnecting no longer suppresses the git sync for ever;
only one that is actually up carries the text.
- Whether a sync has agreed a base is held per paper, not derived from the
channel, so the effect's own teardown can no longer swallow a retry.
- A remote edit keeps the file's CRLF line endings instead of rewriting
every line as LF.
- Reads refuse a symlink, as writes already did, so a link out of the paper
cannot have its target pushed into the Overleaf document.
- The git sync releases the channel through a guard, so a folder cannot be
left paused if the sync unwinds.
- A cookie refusal says so on the status, rather than the dashboard reading
the prose of an error message.
- Firefox profiles that cannot be read are skipped like Chromium's, and the
WAL sidecars are copied so a cookie set moments ago is visible.
- The sync button only claims text is live when it is, the paste and import
buttons no longer share a label, and the live line is a status region.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The GitHub repository was renamed from openresearch-cli to OpenResearch, so
build.rs rejected ORX_OFFICIAL_RELEASE_BUILD=1 and the v0.1.119 release failed.
Update the guard and the release/download URLs to the new name.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* feat: add Tinker hybrid compute backend
* fix: use official Tinker logo
* fix: handle zombie process groups during cancellation
* fix: use portable process table arguments
* fix: delimit process group signal targets
* chore: bump version to 0.1.115
* refactor: use standard cancellation for Tinker
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>
* Open any absolute-path file in the viewer (OR-183)
The right-side file viewer could only open files inside the project's git
checkout or artifacts dir. An agent-referenced absolute path outside both
(e.g. /Users/me/.ssh/config) routed to the repo /file endpoint, which rejects
absolute paths with "path must be repo-relative", so it failed to load.
Add loopback-only endpoints GET /api/files/abs (JSON, capped ~512 KB,
root:"abs") and /api/files/abs/raw (streamed, for media/download), mirroring
project_file / project_raw_file. In the UI, parseFilePath tags an unrecognized
absolute path source:"abs" and FileViewer loads it read-only.
Also fixes two adjacent issues surfaced in review:
- A /private symlink divergence (/private/tmp vs /tmp) between an inlined path
and the stored repo/artifacts dir now reconciles, so such a checkout file
stays editable instead of silently downgrading to read-only abs.
- A relative markdown link inside an abs file now anchors to that file's
directory instead of resolving against the project clone.
Bumps 0.1.107 -> 0.1.108 (ships the rebuilt ui/dist).
* Route ~-prefixed paths to the abs file reader (OR-183)
A file the agent inlined as ~/.ssh/config didn't open: ~ doesn't start with
/, so parseFilePath classified it as a repo-relative path and sent it to the
checkout endpoint, which reported "not found in the session's worktree" — the
file was never read off disk.
Route ~ / ~/… paths to source:"abs" in the UI, and expand a leading ~ to the
home dir in the abs endpoint's path validation (the shell never expands it for
us). The display string stays as typed; only the resolved path is expanded.
Puts the CLI and macOS app self-update (#201) into users' hands. It merged
after v0.1.106 was cut, so no released build contains it yet.
For the macOS app this is the changeover release: 0.1.106 and earlier have no
updater compiled in, so nothing can fetch an update for them. Everyone on the
app today needs one manual install of this build; from then on it self-updates.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>