Daniel Kim 874e84080c Plan UI: docked approval strip + right-pane plan tab (#83)
* Plan gate: read-only git verbs + pipelines into pure consumers + 2>&1

Planning agents read experiment code that lives only on git branches
(git show <ref>:<file>, git ls-tree) and habitually pipe output through
head/grep with a stderr-merge — all of which the gate rejected outright
via its whole-command metachar blacklist. Rework classification to be
per-pipeline-stage:

- segments (;, &&) split into pipe stages; stage 0 must be a read-only
  producer (orx read, git read, glue, or pure consumer), later stages
  pure consumers (head/tail/grep/wc/cat/sort/uniq/cut/tr/nl/column —
  tee/xargs/sed/awk deliberately excluded)
- standalone 2>&1 tokens dropped; any other token carrying > or & (or
  backticks/$(/</newlines anywhere) still gates the whole line
- read-only git allowlist: whole-verb reads (status/log/show/diff/grep/
  ls-tree/...) with --output and -O/--open-files-in-pager escape-hatch
  guards; conditional forms for branch/tag (list-query only), stash
  (list/show), remote (bare/show/get-url), worktree (list), reflog;
  global flags limited to -P/--no-pager/-C (rejecting -c config
  injection); git config excluded wholesale
- command_is_readonly exported for reuse by the upcoming MCP permission
  bridge auto-policy

Fail-safe allowlist property preserved: unknown programs, verbs, or
unparseable forms gate the entire command.

* Plan mode: synthesize a plan card when ExitPlanMode never fires + accept-edits option

Observed failure: a plan-mode session ended with the plan as plain chat
text and no ExitPlanMode call — and since only a plan-card answer
switches the resume mode, the user's chat 'yes' resumed still in plan
mode, stranding the session read-only. Root cause of the missing call:
the shared playbook still said 'Never call an interactive
question/elicitation tool — it will hang', guidance that predates the
prompt-card work in #37/#40.

- run_turn tracks (plan_mode, saw_prompt, turn_errored, last_text) and
  synthesizes a WirePrompt{kind:plan, synthesized:true} from the final
  text when a plan-mode turn ends card-less and error-free; the pure
  condition fn is unit-tested
- WirePrompt/ChatPrompt gain ; the UI softens the header to
  'Plan mode — ready to proceed?' for synthesized cards
- playbook: interactive tools surface as cards now; in plan mode, always
  present the finished plan via ExitPlanMode
- plan card gains 'Approve & accept edits' (resumeMode accept-edits —
  synthesize_resume already honors it)

* chore: drop stray npm lockfile (ui uses pnpm)

* Plan mode: MCP permission bridge — desktop-style mid-turn approvals

Headless plan mode had no approval loop: gray-area tools hard-denied
with nobody to ask, WebFetch was denied (verified on claude 2.1.197),
and — worst — ExitPlanMode SELF-APPROVED headless ("User has approved
exiting plan mode", nobody asked), letting the model exit plan mode and
edit files without the user's say.

The fix is a permission bridge on Claude Code's (hidden but functional)
--permission-prompt-tool flag:

  claude ── --mcp-config ──▶ orx mcp-gate (stdio MCP server, hidden cmd)
                                 │ HTTP long-poll, 127.0.0.1:<up port>
                                 ▼
  orx up: POST /api/internal/permissions ─ oneshot ─▶ card (SSE)
          user answers /respond ─▶ ResumeAction::Handled ─▶ claude unblocks

- src/commands/mcp_gate.rs: newline JSON-RPC stdio server (initialize /
  tools/list / tools/call), concurrent calls, single stdout writer; any
  transport failure answers deny — never hangs claude
- ChatHost: request_permission (token gate -> tier-1 auto-policy ->
  card + held oneshot, 55-min timeout under MCP_TOOL_TIMEOUT=60min),
  settle_permission, per-session drain on turn end/interrupt, drop-guard
  so an abandoned request always resolves its card
- auto-policy (pure fn, tested): read-only Bash allow (shared
  plan_gate classifier), WebFetch/WebSearch allow, file edits DENY —
  with a permission tool configured the CLI delegates plan mode's edit
  block to it (verified: an allow creates files mid-plan), so that deny
  IS the safety
- plan_gate hook now also matches ExitPlanMode and answers "ask",
  routing plan approval to the bridge card instead of self-approval;
  without a bridge, ask denies headless — still better than self-approve
- claude.rs: write_mcp_config + flags wired per plan-mode turn
  (best-effort; older CLIs silently ignore the flag and degrade to
  today's gating), ExitPlanMode tool_use renders as a plain tool row
  while the bridge card is authoritative, bridge cards count as
  "saw a prompt" for the synthesized-card fallback
- resume_from_prompt: native (mid-turn) branch — permission allow/deny
  and plan keep-planning settle the held request in place (Handled);
  plan approve interrupts the paused plan turn and resumes via the
  proven --resume + mode-flip machinery (respond()'s SendMessage arm
  now interrupts first for native cards only)

Empirical grounding (claude 2.1.197 spikes): denials don't fail headless
runs (permission_denials + subtype success); WebFetch denied natively in
plan mode; hook allow bypasses the permission tool (no card spam);
bridge tool timeout = tool error, turn continues; hook "ask" on
ExitPlanMode routes it through the bridge and an allow resumes the same
turn.

* Plan UI: docked approval strip + right-pane plan tab

The plan card scrolls away with the transcript and renders the whole
plan inline. Desktop-style flow instead:

- PlanStrip (new): docked between the thread and the composer while a
  plan awaits a decision — "View plan" / "Keep planning" / "Approve &
  run" with a split-menu for accept-edits / bypass. Keep-planning
  consumes the composer draft as the refinement note, so typing
  feedback then clicking the button is the natural gesture. Solid card
  floated over the composer's gradient fade; disappears on resolve via
  the SSE re-emit.
- Plan tab: "View plan" opens the markdown as a right-pane ClosableTab
  (PlanViewDef in the rightTab union, one tab per prompt id, no fetch —
  the text is already client-side); file links inside the plan resolve
  against the plan's session worktree.
- The inline plan card goes compact while a strip host exists: clamped
  to ~9.5em with a fade, "View full plan" link, no buttons (the strip
  owns the actions; single source of truth). Without a strip host the
  card keeps its full button row.

* Store: stop normalizing claude-code plan sessions to auto

The retired-modes migration runs on every Store::open and still listed
'plan' for claude-code — written when plan mode was dropped (pre-#75).
Re-adding plan to options() without updating it meant picking Plan
silently degraded to Auto on the next request; the composer masked it by
re-sending the mode with every message, but any turn without an override
(API callers, resumes) ran in auto. Caught live in the plan-mode E2E.

* chore: bump version to 0.1.60
2026-07-16 11:17:35 -07:00
2026-06-07 00:35:02 -07:00
2026-07-06 20:05:41 -07:00
2026-07-16 11:09:35 -07:00
2026-07-13 23:52:03 -07:00

OpenResearch CLI (orx)

Run autoresearch on your machine.

orx up starts a local dashboard where you give each research direction its own agent. Agents work in parallel in isolated git worktrees, propose and edit experiments, launch runs on GPU compute, and analyze the results — using your existing tools, accounts, and compute.

Quick start

curl -LsSf https://openresearch.sh/install.sh | sh
orx up

The dashboard opens at http://127.0.0.1:4791. Give the agent a goal — for example, ask it to reproduce a paper:

/reproduce-paper <paper URL or title> on <compute>

or turn one into an interactive marimo notebook:

/paper-to-marimo <paper URL or title> on <compute>

The dashboard

orx up runs a single local process on 127.0.0.1 — an embedded web UI plus a JSON/SSE API over a local SQLite store. From there you get:

  • Agent chat — a research assistant with full project context, backed by your locally installed harness: Claude Code, Codex, or OpenCode (pick the harness and model in the UI). Ask it to analyze runs, dig into results, edit code, and spin up new experiments.
  • The experiment tree — every experiment is a git branch: a runnable snapshot of your code. The root is your baseline; children are variants measured against it, so lineage stays explicit.
  • Runs — launch on your compute (Modal, Hugging Face Jobs, Kubernetes, Slurm, any SSH box, your own machine) or OpenResearch managed GPUs, and watch live logs, statuses, git diffs, files, and linked W&B runs stream in.
  • Autoresearch — describe a goal and let the agent run autonomously toward it: proposing, launching, and analyzing experiments.

Everything binds to loopback only; nothing on the dashboard's paths leaves your machine except the compute and paper-search calls you initiate.

On a remote machine

Develop from your laptop while the dashboard runs next to your GPUs:

orx up --remote user@host        # or an ~/.ssh/config alias; append :PORT for a custom SSH port

This starts orx up on the remote box over SSH, tunnels the port back, and opens your browser locally. Note the remote server is unauthenticated on that host's loopback, so other users on the same box can reach it.

Commands

Run orx --help (or orx <command> --help) for full usage. The highlights:

Area Commands
Dashboard up
Auth login, logout
Projects projects, explore, project, create-project, env
Experiments experiments, create-experiment, exp status/cmd/run/cancel
Runs & evidence runs, logs, search-logs, artifacts, artifact, wandb, query, chart, report
Compute compute, instance create
Literature lit, paper (alphaXiv full-text search — no login required)
Agent integration install-skills, skill
Maintenance version, update, telemetry

orx install-skills drops the OpenResearch skill into your local coding agents (Claude Code, Codex, OpenCode, Cursor) so they can drive orx themselves — orx login offers this too.

Installing

The install script above fetches the latest prebuilt release (macOS and Linux, x86_64 and arm64) and is the same as:

curl -LsSf https://github.com/alphaXiv/openresearch-cli/releases/latest/download/openresearch-cli-installer.sh | sh

orx update keeps script-installed binaries current; interactive terminals also get a once-a-day background check with a one-line stderr notice (silence it with ORX_NO_UPDATE_CHECK=1).

From source

Requires Rust (stable) via rustup. The prebuilt dashboard UI is committed at ui/dist, so a plain build works:

cargo build --release          # binary at target/release/orx
cargo install --path .         # or install onto your PATH (~/.cargo/bin)

To hack on the dashboard UI itself (Vite + React, embedded into the binary at build time):

cd ui && pnpm install && pnpm build

Run the tests with cargo test.

Configuration

  • API URL — defaults to production (https://api.openresearch.sh); override with --api-url or OPENRESEARCH_API_URL.
  • Credentials — orx login opens your browser, mints a personal access token, and stores it at ${XDG_CONFIG_HOME:-~/.config}/openresearch/credentials.json (mode 0600). Sent as Authorization: Bearer … on every request.

Usage analytics

orx sends anonymous usage analytics to help prioritize features. It's opt-out, and the orx up onboarding surfaces the choice on first run.

  • Collected: command name, a random per-install UUID, CLI version, OS/arch, a CI flag, and coarse event labels (e.g. "a run launched on modal").
  • Never collected: code, prompts, file contents or paths, project or experiment ids/names, repo names, tokens, emails — nothing identifying. The install UUID is not tied to your account.
orx telemetry off        # persistent, per-machine
orx telemetry status     # current state + the anonymous install id
orx <cmd> --no-telemetry # per-run

Events are fire-and-forget on a background task and never block a command.

S
Description
GitHub Trending: alphaXiv/OpenResearch
Readme MIT
935 MiB
Languages
Rust 70.9%
TypeScript 20.9%
JavaScript 6.4%
Python 0.8%
HTML 0.3%
Other 0.6%