mirror of
https://github.com/alphaXiv/OpenResearch.git
synced 2026-11-11 09:59:34 +08:00
* refactor: unify claude/codex/opencode/cursor behind a Harness trait + registry
Collapse the three parallel per-harness surfaces (detection in
harness.rs, chat run_turn in chat/*, skill install in install_skills.rs)
into one Harness trait with per-harness impls under src/local/harness/.
- Detection, chat-turn, and skill-install are now optional capabilities
on one trait; each consumer iterates a single registry() and filters
by capability (supports_chat / skill_target).
- Removes the stringly-typed dispatch match and the triple-duplicated
harness id lists (HARNESS_IDS, detect assembly, install Agent enum).
- Cursor becomes a first-class install-only harness impl.
- Adding a harness is one new file + one registry() line.
No behavior change: detection order, wire output, and install paths are
preserved.
* feat(chat): per-session permission mode + reasoning level toggles
Add two composer controls, wired end to end and driven by the Harness
trait so Codex/OpenCode can adopt them by implementing one method.
- New harness/options.rs: PermissionMode (auto/plan/default/bypass) and
ReasoningLevel (low/medium/high) as harness-agnostic wire ids, plus
HarnessOptions the harness advertises via Harness::options().
- Claude Code maps mode -> --permission-mode (auto=acceptEdits, etc.,
replacing the hardcoded bypassPermissions) and reasoning -> a thinking
directive prepended to the prompt. Defaults: Auto + High.
- Store: chat_sessions gains permission_mode/reasoning_level columns
(best-effort ALTER migration) + setters; sticky like model.
- Chat: send_message takes a TurnOverrides struct; TurnCtx carries the
effective mode/level; overrides persist per-session.
- API: create/send accept permissionMode/reasoningLevel; session_json
and /api/harnesses (options) expose them.
- UI: composer redesign — permission mode bottom-left, model + reasoning
bottom-right (matches Claude Code desktop). OptionPicker renders only
the axes a harness advertises, so Codex/OpenCode show nothing yet.
Verified end to end: create+send round-trips the values, they persist
across reload, per-turn overrides are sticky, and the spawned claude
process receives the mapped --permission-mode.
* fix(chat): match Claude Code's real mode + effort values
Correct the toggles to Claude Code's actual CLI vocabulary (verified
against claude 2.1.145 --help), addressing two errors in the first cut:
Permission modes — 5, not 4. `auto` is a distinct --permission-mode value,
not an alias for acceptEdits. Wire ids now equal the CLI values so no
per-harness remap is needed:
Ask permissions -> default
Accept edits -> acceptEdits
Plan mode -> plan
Auto mode (default)-> auto
Bypass permissions -> bypassPermissions
Reasoning — use Claude's own --effort tiers (low/medium/high/xhigh/max,
default high), passed straight through as --effort, replacing both the
generic low/medium/high list and the prompt-injected 'think hard' hack.
Reasoning vocabulary is now per-harness (ReasoningLevel enum removed):
Claude owns its tiers; Codex will own reasoning_effort. Permission mode
stays a shared enum since the concept maps across harnesses.
Composer menu redesigned to mirror the Claude Code mode menu: a header,
the default row pinned on top with a '· Default' note + separator, and
1-based number hints.
Verified: /api/harnesses serves the 5 modes + 5 effort tiers with the
right labels/defaults, and a plan+xhigh session spawns
`claude --print --permission-mode plan --effort xhigh`.
* feat(chat): plan approval + permission/question prompt cards
Surface Claude Code's interactive moments in the UI and let the user act
on them, using the CLI's resume model (headless --print can't answer
inline, so a turn ends on the prompt and the answer resumes the session).
Shared, harness-agnostic wire type WirePart{kind:"prompt"} carries three
prompt kinds — plan / permission / question — so Codex's approvals map
onto the same cards later:
- ExitPlanMode -> plan card (renders the plan markdown) with Approve &
auto-accept / Approve & ask-each-step / Keep planning. Approve resumes
the session in the chosen permission mode; keep-planning stays in plan.
- AskUserQuestion -> question card with the options; picking resumes with
the choice as the reply. No longer disallowed.
- permission_denials -> permission card (Allow / Deny). Allow resumes
with a more permissive mode.
New POST /api/chat/sessions/{id}/respond + ChatHost::respond marks the
prompt resolved in the transcript and resumes via send_message with a
synthesized follow-up + mode override. Prompt parts render read-only once
resolved.
Verified end to end against claude 2.1.145: plan approve resumes in
acceptEdits, a question surfaces its options and answering resumes with
the pick, denials surface as permission cards.
* feat(ui): compact, coalesced tool-call rendering (Claude-desktop style)
Replace the bulky per-tool bordered boxes with minimal gray one-liners
that blend in, like the Claude desktop app:
- Consecutive tool parts coalesce into one ToolGroup. Collapsed it shows
a single muted line — the tool's verb phrase for one call, "Used N
tools" for several. Click to expand all rows; a running tool
auto-expands.
- toolLine() renders human summaries from the tool + input: "Ran <cmd>",
"Read <file>", "Edited <file>", "Searched <pattern>", etc., instead of
the raw tool name + full JSON.
- Each row expands to show the command + output inline in subtle boxes;
file tools get an "open" link to the viewer.
- Status dot: amber (running) / red (error) / muted (done). Chevron
rotates on expand.
Verified by rendering the real components' markup against the app's
actual styles.css and screenshotting — matches the reference.
* style(ui): darker, larger, roomier assistant transcript text
Assistant prose was rendering in muted gray (--subtext), small and
tight. Match the desired look:
- .md text color --subtext -> --text (near-black); tool-call lines stay
gray (they're styled separately).
- Body size 13px -> 14px with line-height 1.62 on .msg-assistant / .md.
- Paragraph spacing 6px -> 10px.
Also improves report readability in the file viewer (shares .md).
Verified by screenshotting the real .md markup against styles.css.
* feat(ui): render markdown tables + syntax-highlighted code blocks
Chat markdown was rendering tables as raw pipe-text and code blocks as
plain unhighlighted <pre>. Fix both:
- Add remark-gfm so GFM tables (and their column alignment) render.
Restyle .md tables: header background, full border + radius, roomier
cells, row hover, right-align honored from the `---:` syntax.
- Fenced code blocks now highlight via refractor (same tokenizer +
.token theme the file viewer uses) through a custom code renderer, and
get a hover copy button. Inline code stays a plain chip.
Verified by rendering real markdown through the Md component and
screenshotting — matches the reference.
* fix(review): address blockers from multi-agent review
Correctness:
- respond(): 'Keep planning' (plan reject) was dead — the early-return
fired for every non-question reject, so keep-planning silently stopped
the session. Now only 'permission' denials / bare rejects close the
card; plan-reject and question answers resume.
- respond() now broadcasts the mutated assistant message (chat.message
with resolved:true) so prompt cards render read-only on all clients
immediately, instead of staying clickable and re-firing a duplicate
resume after the turn ends.
- send_message: atomic turn-slot reservation (TurnGuard) closes the
check-then-insert race where two rapid /respond or /message calls could
both spawn a turn against the same session and orphan an abort handle.
turns map now holds Option<AbortHandle> (None = reserved-not-spawned).
- claude.rs result handler: drop the guard that swallowed
genuine failures whenever any denial was present. A plan/permission
pause reports subtype:success/is_error:false (verified), so drive the
error path off is_error alone.
- respond() rejects an empty question answer before marking resolved.
Quality:
- Extract shared refractor highlighting into ui/src/syntaxHighlight.tsx;
Md.tsx and FileViewer.tsx both use it (was duplicated).
- Soften over-promising doc comments (WirePrompt/PermissionMode/options)
to mark the interactive-prompt vocabulary Claude-shaped-until-Codex,
and note synthesize_resume encodes Claude's resume-by-new-message model.
Verified end to end: keep-planning resumes, resolved card broadcasts over
SSE, unknown prompt id -> 400. fmt/clippy/37 tests/tsc green.
* fix(review r2): close interrupt-during-setup turn-slot race
Round-2 review found a real orphaned-turn bug: TurnGuard reserves the
slot under the lock, but send_message's prologue then runs lock-free
across many awaits before upgrading None->Some(handle). An interrupt in
that window removed the reservation and emitted idle, yet send_message
still unconditionally re-inserted the handle and spawned a live turn no
one could abort — busy desynced (UI idle, server busy) and the next
send/respond was wrongly rejected.
Fix: upgrade None->Some under the lock with an atomic re-check. If the
reservation is gone (interrupt won), abort the freshly-spawned task and
don't re-insert — honoring the interrupt. Verified with a send+interrupt
race stress test: slot never wedges, a fresh send stays accepted, no
panics.
Also from round 2 (cleanups): collapse respond's redundant match arms to
a single 'denied permission closes the card' condition, and document why
TurnGuard::drop's try_lock can't leak (the lock is always free when an
armed guard drops).
* fix(review r3): defuse TurnGuard in both upgrade branches
Round-3 review found a residual narrow race in the r2 fix: the abort
(interrupt-wins) branch left the guard armed, so its synchronous Drop
still ran after the locked block. A same-session send that claimed a
fresh reservation in the gap before Drop could have it clobbered by the
stale guard — silently dropping that second turn (a lost turn, not a
busy-desync, but still wrong).
Defuse the guard in BOTH branches while still holding the lock, so its
Drop can never run post-upgrade. Corrected the misleading comment.
Both round-3 reviewers confirmed mergeable; this closes the last
refinement. Re-ran the send+interrupt race stress test: not wedged, no
panics. fmt/clippy/37 tests/tsc green.
31 lines
702 B
JSON
31 lines
702 B
JSON
{
|
|
"name": "orx-ui",
|
|
"private": true,
|
|
"version": "0.1.0",
|
|
"type": "module",
|
|
"scripts": {
|
|
"dev": "vite",
|
|
"build": "tsc --noEmit && vite build",
|
|
"preview": "vite preview"
|
|
},
|
|
"dependencies": {
|
|
"@xterm/addon-fit": "^0.10.0",
|
|
"@xterm/xterm": "^5.5.0",
|
|
"@xyflow/react": "^12.6.0",
|
|
"lucide-react": "^1.23.0",
|
|
"react": "^19.1.0",
|
|
"react-diff-view": "^3.3.3",
|
|
"react-dom": "^19.1.0",
|
|
"react-markdown": "^10.1.0",
|
|
"refractor": "^5.0.0",
|
|
"remark-gfm": "^4.0.1"
|
|
},
|
|
"devDependencies": {
|
|
"@types/react": "^19.1.0",
|
|
"@types/react-dom": "^19.1.0",
|
|
"@vitejs/plugin-react": "^4.5.0",
|
|
"typescript": "^5.8.3",
|
|
"vite": "^6.3.5"
|
|
}
|
|
}
|