mirror of
https://github.com/alphaXiv/OpenResearch.git
synced 2026-11-11 09:59:34 +08:00
2dd67bc2cab3e1f60bc6932d5ac516de27773665
137
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2dd67bc2ca |
UI design-consistency pass: token scales, sentence case, sans-first, rounded everywhere (v0.1.68) (#103)
* ui: add type/weight/radius token scales, drop dead CSS rules * ui: drop all uppercase transforms and tracking, sentence-case status strings * ui: reserve mono for code, paths, ids and terminal output * ui: primary content uses --text; grays reserved for true secondary text * ui: normalize font weights onto the --fw-* scale * ui: normalize radii and type sizes onto token scales, round callouts and inputs * ui: pin badges to sans in mono contexts, un-mono instance durations; rebuild dist * ui: sentence-case remaining status strings, fix stale ModelPicker comment * ui: promote composer reasoning pill to --text * ui: shared Wordmark component — logo mark + all-black OpenResearch text * ui: match home wordmark weight to the landing page (800 -> 700) * chore: bump version to 0.1.68v0.1.68 |
||
|
|
0a42fb8b28 |
Onboarding tour: spotlight walkthrough of the workspace (#102)
* Onboarding tour: spotlight walkthrough of the workspace Seven-step tour (welcome, composer, model picker, Files, Compute, experiments panel, new session) shown once after first-run setup and replayable from a help button in the chat header. Ported from the openresearch.sh onboarding system: data-onboarding attribute targeting, ResizeObserver/MutationObserver bounds tracking, box-shadow spotlight dim, viewport-clamped card with pointer arrow. Completion persists to localStorage (orx:tour-done). Escape is captured so it can't interrupt a running agent turn. * Tour polish: exact-primary ring, data-tip tooltip on replay button The ring's 45%-alpha glow plus a 1.5px border read as a lighter red than the theme primary; a crisp 2px border with no glow matches it exactly. The header help button now uses the app's data-tip tooltip instead of a native title. |
||
|
|
726ce566b4 |
Local-mode skill variants, slimmer playbook, no-merge/rebase rule, first-run setup questions (v0.1.67) (#101)
* Local-mode bodies for compute + experiment-tree skills; slim and extend the playbook - agent-skills/orx-compute/SKILL.local.md and orx-experiment-tree/SKILL.local.md: local-mode variants (same public names) that drop managed-SKU compute, cache-dir clone flow, and locally-unavailable commands (orx experiments, exp cmd, artifacts, query, chart); orx-compute also dedupes the per-backend shared-contract boilerplate into one section. - src/local/agent_skills.rs: serve the local bodies in SkillSet::Local via _LOCAL/_CLOUD consts sharing one description each. - SYSTEM_PROMPT.md: fold 'Where runs execute' into cardinal rule 5, drop content now covered by skills (baseline para, lit para, report layout, supervise note); add cardinal rule 6 (never merge/rebase a branch with a completed non-failed run; child + merge commit; never rebase) and a 'Learn how the user runs their code' section (ask about env/deps before the first launch on a fresh project; two env failures -> stop and ask). The no-merge/rebase rule is echoed in orx-git's rules list. * chore: bump version to 0.1.67v0.1.67 |
||
|
|
aba90b6298 |
Files tab redesign: split explorer with inline preview (#100)
Replace the pill-header list + full-page report swap with a master-detail split: a resizable file tree on the left (folders open by default, report folders titled by their report, dir path demoted to a copyable footer) and a persistent preview pane that renders everything inline — reports and markdown as GFM docs with a view-source toggle, images and PDFs directly, other text as highlighted code via the new shared CodeView (also adopted by FileViewer), with an Open-raw fallback for binary/oversize files. Icon-only controls get a CSS data-tip tooltip primitive; empty states for no-files and nothing-selected; FilesTab keyed by project and PreviewPane keyed by path so state can't leak across switches. |
||
|
|
a72c4cdc2b |
feat(codex): native plan mode as a selectable permission mode (v0.1.66) (#98)
* feat(codex): native plan mode as a selectable permission mode
Codex ≥ 0.144 supports plan mode as an app-server collaboration mode.
Advertise Plan + Auto + Bypass for codex and wire the full flow:
- initialize sends capabilities.experimentalApi (unlocks collaborationMode)
- conditional per-turn collaborationMode mask: plan on plan turns; default
on a non-plan turn only when the thread may be sticky-planned (DB
prev_permission_mode threaded via TurnCtx + in-memory last-sent tracking);
otherwise no mask (a mask always injects a template)
- render streamed plan items (item/plan/delta + completed plan item) and
synthesize an end-turn plan card / PlanStrip
- requestUserInput -> question card, answerable inline or via composer;
per-kind server-request settle shapes (approval/userInput/other)
- resume_from_prompt: plan (send-message), permission/question (inline reply)
- store: keep 'plan' off the codex retire-list; reuse claude.rs
synthesize_resume / should_synthesize_plan via harness re-export
- UI: PlanStrip agentLabel; ChatPanel pendingQuestion gate includes codex
Legacy exec path (< 0.144) keeps Plan->read-only, with an updated agent note.
* fix(chat): keep prompt-resume scaffolding out of the transcript
Plan/permission card resumes send synthesized text the user never typed
("Implement the plan.", "The user approved that action. Continue.").
Persisting it as a user message misattributes it in the UI. Split the
transcript text from the harness text in send_message (the slash-skills
precedent): plan/permission resumes now record only the user's own note
— nothing at all when there isn't one; the resolved card tells the rest.
Question resumes still record the answer, which the user did choose.
* chore: bump version to 0.1.66
v0.1.66
|
||
|
|
9be46cd4dd |
Telemetry: delineate human vs automated installs; kill consent phantom persons (#99)
* Delineate human vs automated installs in telemetry; kill consent phantoms - Every event now carries an install_kind base property: "human" by default, or the machine context set via ORX_TELEMETRY_CONTEXT / the new `orx telemetry context <value>` (persisted in settings.json), so first-party automation (cloud agent boxes) can be excluded from human usage metrics centrally via PostHog's internal-users filter. - cli_telemetry_consent no longer mints a fresh UUID person per event: agrees use the persisted install id (joinable with the active-install population), declines collapse into one fixed sentinel id. * Reattach record_consent docs after helper extraction; doc nits |
||
|
|
087fde79af |
Contain file serving to the files dir even through symlinks (#97)
* Contain file serving to the files dir even through symlinks is_safe_rel_path only blocks lexical escapes (..); a symlink inside the files dir pointing outside it was followed by read_file, read_report_markdown, and the report-title scan, so anything reachable on disk could be served to whatever can reach the local API. Resolve every served path with canonicalize() and require it to stay under the canonicalized files dir. Internal symlinks keep working and now also appear in listings (DirEntry::metadata never follows links, so they were silently dropped before); escaping ones are skipped from the listing and fingerprint, and refused by the serve endpoints. Delete resolves parent segments the same way but removes a final-component symlink literally, never its target. * Review nits: shared followed_metadata helper, drop redundant pre-check Extract the symlink-aware metadata lookup shared by collect_tree and hash_dir, let resolve_contained handle read_report_markdown's path validation, and document the containment guarantee (and the accepted check-vs-use gap) at module level. |
||
|
|
66c8159511 |
Match the web app's favicon and retitle the tab to OpenResearch CLI (#96)
Ports favicon.svg from openresearch.sh so the embedded UI's browser tab shows the same brand mark, and retitles the tab "OpenResearch CLI" to distinguish it from the hosted web app. favicon.svg joins index.html in the no-cache branch since it's served under a fixed (unhashed) name. |
||
|
|
67190285ab | chore: support automated reproductions (#95) | ||
|
|
2c72297604 | chore: add marimo reproduction notebooks (#94) v0.1.65 | ||
|
|
99f358c5fe |
chore: neutral reproduction assessments (#93)
* chore: neutral reproduction assessments * chore: bump version to 0.1.64 * chore: plan reproductions to compute budgetv0.1.64 |
||
|
|
87df17ce0f |
chore: Report md heading styles (#91)
* Report pane: restore Markdown heading hierarchy in report-md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * Bump version to 0.1.63 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3010c8cea6 |
Guide reproduce-paper toward visual reports (#92)
* Guide reproduce-paper toward visual reports * Encourage evidence-rich report figures |
||
|
|
1526d78a14 |
ControlPlane trait: plane-agnostic dual-mode commands (v0.1.63) (#90)
* ControlPlane trait: hoist dual-mode command bodies behind two planes Phase 3 (final) of the control-plane separation. Adds src/plane.rs — a #[async_trait(?Send)] ControlPlane trait with 14 verbs derived from the six dual-mode commands' paired arms — with ServerPlane (wraps client.rs, connects via require_credentials on the server arm only, with a sync ServerPlaceholder so pre-login guards keep firing before the login check) and LocalPlane (owns the Store; ?Send because rusqlite is !Sync — planes are driven inline under block_on and never spawned). The six commands collapse to resolve-once-then-verb; their local/server bodies move verbatim into the plane impls. Printing stays in the impl where the two planes differ (status blocks, launch recaps) and in the command where shared (runs table, logs footer). Domain type plane::Run (+RunLog/RunOrigin) merges the two run_failure_detail twins — output.rs no longer depends on client::Run. CLI-facing only: local::model (the up-API wire), the SQLite schema, and the client.rs DTOs are untouched, verified by byte-identical golden snapshots of the affected commands on a seeded isolated store. The plane lives outside src/local/ because local/mod.rs's invariant (nothing there calls client.rs) would otherwise break. up.rs and supervise.rs stay off the trait as the deliberate cloud-mirror bridges. * Bump version to 0.1.63v0.1.63 |
||
|
|
a0b6f3ea5f |
Persistent cross-session agent memory (user + project memory files, {memory} playbook token) (#89)
* Persistent cross-session agent memory: user + project memory files inlined via {memory} playbook token
* Mention files/ + memory/ in datadir module doc
|
||
|
|
8d07767b2d |
Control-plane resolver: centralize local-vs-server dispatch (phases 1+2) (#88)
* Centralize local-vs-server control-plane dispatch in a resolver Commands used to hand-roll the "is this id a local project/run or a server one?" decision as a copy-pasted get_local_project / local_run fork across 14 files. Add local::resolve — ProjectRef/resolve_project and RunRef/resolve_run (which reuses local::local_run, since store membership alone is not enough: server HF runs also live in the runs table) — and route every dispatch site through it. Reject-guard sites use the is_local() guard form; dual-mode sites carry the fetched project through the Local arm, so no second store lookup. up.rs and supervise.rs keep their direct store calls deliberately: those are lookups and the cloud-mirror bridge, not ownership dispatch. Store::open_at(dir) lets the resolver's tests run against a throwaway temp-dir store without mutating the process-global ORX_DATA_DIR, which the localbox lifecycle test owns under the parallel test runner. Phase 1 of the control-plane separation plan; a later phase abstracts the per-arm bodies behind the resolver. * Phase 2: total-match dispatch + ExperimentRef for dual-mode commands Convert the six dual-mode commands (exp, runs, logs, project, report, create-experiment) from local-early-return guards to a uniform total match over the resolver, with server bodies extracted into named run_server-style fns — the mechanical hoist target for the later ControlPlane trait. Adds ExperimentRef/resolve_experiment (keyed on local_experiments) and routes exp.rs's five experiment-scoped dispatch sites plus both wait branches through it. Bodies are unchanged; credentials stay inside the server arms so local mode keeps working logged-out. Verified end-to-end against an isolated data dir + config (local arms serve from the store; server arms stop at the login check without touching the api). |
||
|
|
f633b80f9c |
Native modular agent skills: per-session worktree install + orx skill modules (v0.1.62) (#87)
* Native modular agent skills: per-session worktree install + orx skill modules (v0.1.62)
- Factor the 66KB SKILL.md into 10 embedded modules under agent-skills/
- Write the Local set as native SKILL.md skills into each orx up session
worktree (harness-specific dir), fresh every turn beside the playbook
- Slim the playbook: compute/k8s/report detail moves to skills, generated
skills index added; cardinal rules and command surface stay inline
- orx skill <name> serves bundled modules (API fallback unchanged);
no-arg prints the slim root + module index
- orx install-skills --full writes the Full set globally (cloud box);
Codex shim migrates to ~/.agents/skills (native, model-discoverable),
legacy prompt kept
* agent-skills: store modules as literal SKILL.md files, installed verbatim
Each module is now a complete, GitHub-readable skill at
agent-skills/<name>/SKILL.md (frontmatter included); local-mode body
variants live as SKILL.local.md siblings. The render() step is gone —
files are embedded and installed byte-for-byte, and the frontmatter
drift test now validates the files against the code's name/description.
* Revert incidental ui/dist rebuild (no UI source changes in this branch)
* Drop leftover rebuilt dist assets — ui/dist now matches origin/main exactly
* agent-skills: trigger-rich descriptions with explicit liberal 'Use when' cues
Descriptions are the model's trigger surface; err toward false-positive
loads. Bound raised 150 -> 400 chars (Codex ambient budget is ~8k across
the set).
* Playbook: dedupe against skills + extract to literal SYSTEM_PROMPT.md
The system prompt is now a GitHub-readable template at repo root; playbook_md
strips the leading doc comment and substitutes {tokens} (project facts,
compute default, skills index). Content duplicated by the installed skills is
trimmed to pointers: the five per-backend exp-run table rows collapse to one,
the loop keeps only its skeleton, and the sizing/analysis sections fold into
one-liners. The prompt now explicitly tells the agent to load the relevant
skill before acting in its area (long-horizon staleness guard). Placeholder
test extended to all ten tokens + comment-stripping.
* orx-compute skill: add missing slurm + openresearch backend sections
The playbook trim removed the slurm/openresearch table rows, but orx-compute's
body (extracted from the old SKILL.md) never had sections for them — the
detail existed only in the playbook. Content moved from the pre-PR playbook
rows in the same per-backend section style. Also restore the shell-tool
time-limit rationale on the wait loop step in SYSTEM_PROMPT.md.
v0.1.62
|
||
|
|
012672958d |
Plan-mode chat: AskUserQuestion double-card/hang fix + desktop-parity plan/question cards (v0.1.61) (#84)
* Fix harness prompt bugs: AskUserQuestion double-card + hang, resolved-card rendering
- plan_auto_policy auto-allows AskUserQuestion: the bridge no longer fronts
the question card with a redundant permission card whose held request also
deadlocked the end-turn resume (answer rejected as busy until interrupt).
- respond() resolves a card only after its answer is delivered, so a rejected
send leaves the card actionable instead of resolved-with-the-answer-dropped.
- Resolved cards mirror Claude Code: permission cards vanish, plan/question
cards collapse to compact inline expandable rows with an answer echo
(answers/approved/note stamped on resolve, carried through turn flushes).
* Review round 1 fixes: doc split, echo-preserving adopt, neutral outcome, read-only class rename
* Review round 2 fixes: PendingGuard doc, best-effort resolve folded into helper, neutral question echo
* Post-review nits: doc wording, dedupe question text in collapsed body, word-break on collapsed rows
* Plan-mode AskUserQuestion truly blocks: bridge holds the call as the question card
Auto-allowing AskUserQuestion was wrong: headless the tool returns no
answer, so the model guessed and kept planning instead of waiting. The
bridge now surfaces the QUESTION itself as a held mid-turn card (like
ExitPlanMode's plan card); the answer settles the held call as a denial
whose message is the answer, so the model continues the same turn with
the user's actual choice. The turn-status line says 'Waiting for your
input…' instead of 'Working…' while a card holds the turn open.
* Post-review nits: deny-message hardening, question cards don't suppress plan-synth, empty-options fallback, held-card status via nativeId, comment accuracy
* Polish: no tool row for bridge-held calls (deny-with-answer painted them red), plan pane gets padding + plain background
The bridged AskUserQuestion/ExitPlanMode tool_use now renders nothing —
the held card is the representation, and the denial that carries the
user's answer back made the CLI report the tool as errored, painting a
successful Q&A as a red error row. The right-pane plan tab had no CSS
at all: flush text on the panel tint; it's a markdown document, so it
gets a padded, plain reading surface.
* PlanStrip: Claude-desktop shape — title line, real buttons in a row below
The strip's buttons used .btn-primary/.btn-ghost, which are only styled
inside .prompt-actions — in the strip they rendered as bare text crammed
next to the title. The actions row now reuses .prompt-actions (same
buttons as every card) and sits on its own line under the title.
* PlanStrip buttons: monochrome — black text/borders, no brand red
* PlanStrip split button: single internal divider (caret drops its own left border)
* PlanStrip split button: flat inner corners (radius reset must out-rank .prompt-actions shorthand)
* Composer: attention bell instead of Stop while a held card awaits the user
A blocked-on-input turn is busy, so the composer showed the Stop X — a
loading/cancel affordance while the agent is actually waiting on an
answer. When the session is busy AND a held card (nativeId) is
unresolved, show a pulsing bell that scrolls to the pending card; Esc
still interrupts. The transcript status line shares the same
awaitingInput signal.
* Composer: two states only — Send by default, Stop X only while actively streaming
Drop the bell experiment. A turn held on an unanswered card shows the
idle Send button; the card and the status line are the waiting
affordance. Esc still interrupts a held turn.
* Composer answers the pending card (Claude-desktop): typed text = plan revision feedback / custom question answer
- send() routes plain typed text to the newest unresolved plan/question
card (respond with note) instead of racing the held turn with a new
message the busy guard would drop; placeholder says so. Questions
route only on Claude sessions (opencode rejects note-only replies).
- Rail busy-dot stops pulsing when the turn is held waiting on the user
(steady dot; same awaitingInput signal as the status line).
- Note-only question answers echo the note in the collapsed row.
* PlanStrip: Claude-desktop parity actions + popovers above the strip
Reject / Revise… / Accept / Accept-and-auto-mode (+ bypass in the caret),
Open plan as a title-row link. Reject is a true rejection: the note-less
plan denial now tells the model to stop and wait (was 'keep refining',
which made it improvise revisions/questions); collapsed rows echo
'Rejected' vs 'Revision requested' by note presence. Accept resumes
under accept-edits (guarded), Accept-and-auto under Auto — the primary
is the solid dark button, not brand red. The strip moved inside
.composer so the mode/model picker popovers (z 50 in that stacking
context) layer above it — as a sibling, .composer's z-index:4 capped
them below the strip.
* PlanStrip: single Accept-and-auto-mode primary, drop the plan excerpt
Matches the desktop reference exactly: [Reject] [Revise…] on the left,
one primary button on the right whose caret menu holds the two guarded
tiers (Accept = accept-edits, Accept and bypass all). The separate
outline Accept button and the plan-title excerpt next to the header
are gone — plan.excerpt/plan prop was fully unused after removal.
* PlanStrip: self-contained Revise mode (Claude-desktop parity)
Swaps the strip into its own textarea + [Back] [Revise ↵] footer
instead of routing through the main composer — matches the desktop
reference exactly. Enter submits, Shift+Enter newlines, Esc cancels
back to the action row. The main-composer typed-text routing
(pendingPrompt) is now question-only; plans have their own dedicated
UI and no longer need it.
* PlanStrip: drop the plain accept-edits Accept option
We have no partial-approval story (edits-only, everything else still
gated) — the caret now holds only Accept and bypass all.
* Plan revision: visible feedback + never silently dead-end
- Submitting a revision now swaps the strip to a 'Revising the plan…'
placeholder until the revised card arrives (or the turn ends) —
previously the strip snapped back to an identical-looking action row
and the submit read as a no-op.
- An empty revise note maps to a generic 'Please revise the plan.'
instead of riding the note-less wire shape, which the backend reads
as a hard rejection ('stop and wait') — the '(optional)' field was
accidentally a Reject.
- respond() reconciles messages AND busy state from the server after
every answer: a stale card (e.g. resolved by the bridge timeout while
the tab's SSE was dropped) previously stayed actionable forever and
swallowed every answer.
* PlanStrip: drop the 'Revising the plan…' placeholder
The transcript's Working… spinner already communicates it. The strip
still hides while a submitted revision is in flight (the outgoing card
must not look actionable) and the revised card swaps in on arrival.
* Review round 1 fixes: zombie-card recovery, honest composer button, reject-loop fix
Blockers:
- A held card orphaned by a process restart (PendingGuard never ran)
permanently captured typed input: the composer routed every message to
it and errors were swallowed. Now the backend collapses the zombie on
the first answer attempt, and the composer only routes typed text to a
question card that is actually answerable (live turn for native cards).
- The Send/Stop swap keyed on awaitingInput while send() could only
service question cards — a held plan/permission card showed a lying
Send (silent no-op) with no Stop control. Now keyed on pendingQuestion.
Also: note-less Reject on an end-turn/synthesized plan card resolves
with no resume (resuming just to say 'stop' re-synthesized a card — an
un-dismissable strip); the bridge plan-deny arm reuses synthesize_resume
verbatim; respond() reconciles busy for its own session only and reports
failure so the question route can restore a consumed draft; revising
clear gated to its session; un-docked plan card fallback matches the
strip's semantics; Rejected gets its own outcome class; note-only
answers echo once; split-button CSS consolidated; dead CSS/union
removed; test pins the reject wording.
* Post-clean-round nits: keep optimistic busy on failed reconcile fetch, sweep zombie held cards at claude turn entry
- respond()'s busy reconcile no longer clears busy when the sessions
fetch fails — during a Handled resume nothing re-asserts busy
mid-stream, so a transient fetch failure would hide Working…/Stop for
the rest of the turn.
- claude run_turn sweeps unresolved native_id cards at turn entry
(resolve_stale_prompts gains a native_only mode; codex keeps sweeping
everything). A held card can never outlive its process, so one left
unresolved is a crash artifact — previously it could capture the
composer's typed-text routing once the new turn made the session busy.
* Bump version to 0.1.61
v0.1.61
|
||
|
|
7450249cec |
Compute tab redesign: unified target list + default compute target (#86)
* Settings: persist default compute target (backend + flavor) with pure resolver * Launch paths: resolve the default compute target; widen HTTP run to all 7 backends * API: compute-targets summary, default-target POST, local hardware + openresearch status endpoints * Agent guidance: reveal the default compute target in the playbook, SKILL.md, and skill templates * Compute tab: unified target accordion with This-machine + OpenResearch rows and default picker * cargo fmt * Polish: summary casing, a11y-clean row controls, instances subtitle * Review round 1: coherent no-flavor default prompt, 500 on settings io errors, UI dedup + summary refresh, doc fixes * Clean-round nits: refetch race guard, aligned token-source labels, flavor-required const, doc accuracy * Quiet the agent-disclosure footnote: muted text instead of amber callout * Info icon on the agent-disclosure footnote |
||
|
|
866ccd4ec0 |
Session code browser + GitHub branch links (#85)
* Session code browser + GitHub branch links
Backend: GET /api/projects/{id}/code-tree?sessionId= lists the chat
session's worktree via git ls-files (branch name included); factor the
worktree/clone root resolution shared with project_file into
resolve_checkout_root. Add current_branch + list_worktree_files git helpers.
UI: per-session Code right-panel tab (CodeTab) with a collapsible file
tree that opens files in the existing FileViewer; a Code button in the
chat header; GitHub tree/<branch> links (per-segment URL-encoded) on
experiment cards (TreeView), the Changes header (DetailDrawer), and the
Code tab header. Rebuild committed ui/dist.
* Code tab: browse experiment branches, not just the live worktree
Sessions usually sit detached on the baseline tip, so the worktree-only
view showed baseline code instead of the experiment's. The tab now has a
source select: live session worktree or any experiment branch's committed
tree (default: the branch of the latest run's experiment).
Backend: code-tree and file endpoints take an optional ref= param —
resolve_branch_commit (local ref, then origin; option-injection guarded)
+ list_tree_files (ls-tree) / file_at (git show); root:"branch" in
responses; 404 when the branch doesn't exist. Live mode unchanged.
UI: getCodeTree/getProjectFile take {sessionId?, ref?}; FileViewDef
carries the ref through FileViewer (branch badge, ref-aware not-found
copy); polling only in live mode; GitHub link follows the selected ref.
Rebuild committed ui/dist.
* Code tab review round 2: lift tab state, stream branch-file reads
- CodeTab source + expansion state move onto the App-side tab def
(CodeTabDef.sel/selPicked/toggled): the component unmounts whenever a
file tab fronts it, and remount must not rederive the default against
newer runs or lose expansion. Default computed once in openCodeTab.
- file_at_capped: cat-file -e existence check (exit code, no stderr
parsing) + streamed cat-file blob read capped at limit+1 bytes — a
committed multi-GB blob no longer means a full-blob allocation, and
content is byte-exact (no trim). Used by project_file's ref arm;
file_at kept for its small-file callers.
- resolve_branch_commit rejects rev-suffix expressions (@{, ^, ~, :,
whitespace); doc comments cross-reference branch_head_sha's opposite
local/remote preference.
- CodeTab load lifecycle: request-id guard covers stale sources,
superseded polls and post-unmount responses; manual Refresh surfaces
errors (inline note when a tree is showing); a seeded default branch
that doesn't exist yet falls back to live once, user picks keep the
error. split_nul helper dedups the NUL-split decode. Copy/comment
alignment. Rebuild committed ui/dist.
* Code tab post-review polish
- file_at_capped: reap the child before propagating read errors (no
zombies); after a complete read, a failing cat-file exit (tree path
via crafted request, vanished object) is an error, not a fake empty
file. Kill only on read error/truncation — after EOF git exits on its
own, and killing then could race a bogus signal-death status.
- list_worktree_files drops dir entries ('nested/' from a nested git
repo under --others) server-side.
- openCodeTab's updater self-guards against duplicate defs.
- Seeded default branch selection falls back to live on any fetch
failure (best-effort), not just 'branch not found'; user picks keep
errors. Doc/copy alignment; drop unreferenced tree row classNames.
Rebuild committed ui/dist.
* Tree cards + file viewer UX: Code shortcut, labels, line numbers
- Cards: labeled Changes/Logs/Code actions (Terminal renamed), GitHub-mark
icon link at the card edge; width 240->264 so the row fits; no phantom
selection outline or pointer cursor (selection state was write-only and
is removed; RF nodes stay selectable so pointer-events keep the buttons
alive, visuals suppressed in CSS)
- Card Code button opens the project-level Code tab pinned to that
experiment's branch (CodeTabDef.sessionId nullable); chat-header Code
button removed along with the session-tab opener it fed
- Changes/Code headers: BRANCH/COMMIT/SOURCE micro-labels; shared GitHubMark
(lucide has no brand icons) used by pill, cards, and code-tab link
- File viewer: sticky line-number gutter (1:1 with unwrapped rows) and a
slimmer header
- ui/dist rebuilt
* Code tab: collapse the dead session path
The chat-header opener was removed deliberately (
|
||
|
|
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
v0.1.60
|
||
|
|
81a255be74 |
Plan mode: MCP permission bridge — desktop-style mid-turn approvals (#82)
* 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.
* 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.
|
||
|
|
352e574860 | readme update | ||
|
|
436994224c | README | ||
|
|
7d1aed6f7d |
Plan mode: synthesized plan card fallback + accept-edits option (#81)
* 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)
|
||
|
|
203f63b900 |
Configurable data directory: choose, change, and move with progress (#80)
Let users pick where orx stores everything (orx.db + WAL, run-logs/, artifacts/, chat-attachments/, agent-*.log) instead of assuming ~/.local/share/openresearch. The default is unchanged; the location is now choosable from the product. Resolution order in store::data_dir(): $ORX_DATA_DIR (forced override) → persisted user choice → $XDG_DATA_HOME → default. An explicit UI choice beats the ambient XDG base; the env var stays authoritative for sandboxes/tests. Empty env vars are treated as unset. - Persisted path lives in config_dir()/settings.json, OWNED by telemetry's locked+atomic mutate_settings (in-process mutex + cross-process flock + temp -and-rename + corrupt-file refusal). data_dir is a field on telemetry::Settings so a write can't clobber installId/telemetryDisabled and vice-versa. - Move engine (src/local/datadir.rs): validate → WAL checkpoint → same-fs rename (atomic, consumes old) or cross-fs copy-with-progress (verify, then KEEP the old dir, reported as oldPathLeft — never deletes data a stray writer might still be using) → swap the persisted path. Free-space pre-check via statvfs; EXDEV falls back to copy. - Endpoints (up.rs): GET/POST /api/settings/data-dir, /validate, /move. Move streams datadir.move.progress/done/error over the existing SSE broadcast and returns 202. A move-in-progress flag (claimed via compare_exchange) makes every store-mutating handler back off with 409 for the duration. - After a move, harness children are restarted so any that pinned the old $ORX_DATA_DIR (Codex) respawn on the new path. UI: - Settings → Storage card: current path + source, validate, move with a new byte-level progress bar, env-forced read-only state, in-flight 409 handling. - Onboarding gains a "Storage" step (default pre-filled, skippable). - Files view links "Change storage location →" to Settings → Storage. - Chat send surfaces the move-in-progress 409 and restores the draft instead of dropping it. human_bytes shared via store::human_bytes; fmtBytes shared via api.ts. |
||
|
|
1b1a9453df |
Plan gate: read-only git verbs + pipelines into pure consumers + 2>&1 (#79)
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. |
||
|
|
55a3ac73d7 |
Plan gate: allow batched read-only orx commands (; and && sequences) (#78)
The plan-mode PreToolUse hook only auto-allowed a *single* orx read command; any compound line was hard-rejected (contained ';'/'&&'/'|'/...). The planning agent routinely batches read-only lookups into one Bash call punctuated by echo separators (orx exp desc A; echo ====; orx exp desc B), so those calls fell through to plan mode's gate and failed under headless --print. Split the line on ';' and '&&' and allow it only if *every* segment is independently a read-only orx command or harmless glue (echo/true/:). One write, non-orx, or unclassifiable segment gates the whole line, preserving the allowlist-only security property: a read-only prefix can never smuggle a write through as a later segment. Pipes, redirection, backticks, $(...), background &, and newlines remain rejected outright. |
||
|
|
106eceb864 |
orx up --remote: run remote orx under POSIX sh -c on any login shell + PATH/CARGO_HOME (v0.1.59) (#77)
* up --remote: find orx via ~/.cargo/bin on non-interactive PATH; bump 0.1.59
The remote preflight and launch run orx over a non-interactive ssh shell,
which skips ~/.bashrc/~/.profile — so on a minimal image (RunPod's Ubuntu)
~/.cargo/bin isn't on PATH and 'command -v orx' fails even though 'which orx'
works when logged in. Wrap both remote commands in a PATH prelude that
prepends ~/.cargo/bin and ~/.local/bin (the two common installer targets),
with $HOME left for the remote shell to expand so it works for root or the
runpod user. The 'not installed' error now names those paths and the
install command.
Tests: remote_shell prepends both dirs and keeps $HOME literal; remote_up_cmd
still carries the port behind the prelude.
* up --remote: run remote orx under POSIX sh -c; cover CARGO_HOME
Harden the non-interactive-PATH fix so `orx up --remote` works on any box,
not just RunPod:
- Force POSIX sh. ssh runs the preflight and launch under the remote user's
*login* shell, which may be csh/tcsh/fish — none understand `export VAR=val`,
so the previous `export PATH=...; cmd` was a syntax error there and got
misreported as "unreachable"/"not installed". Wrap the POSIX body in
`sh -c <sh_quote(body)>` so /bin/sh interprets it whatever the login shell is
(mirrors how the job backends run their payload through an explicit shell).
- Cover a relocated CARGO_HOME. The installer honors $CARGO_HOME
(install-path = CARGO_HOME); search ${CARGO_HOME:-$HOME/.cargo}/bin instead
of a hardcoded ~/.cargo/bin so a box that relocates cargo still resolves orx.
- Split the preflight outcome: ORX_MISSING -> clear "not installed"; neither
marker (ssh exited 0 but no answer) -> a distinct message that echoes what we
saw, instead of lumping it into "not installed".
Verified end-to-end that the generated command resolves orx under /bin/sh,
bash, and a tcsh login shell, with orx only in ~/.cargo/bin, with a relocated
CARGO_HOME, and reports ORX_MISSING when genuinely absent.
v0.1.59
|
||
|
|
af8eed1724 |
orx up --remote: custom SSH port support + readable, honest banner (#74)
* orx up remote banner: make the SSH commands the readable part The remote-access banner dimmed the very things you need to read and copy: the whole `ssh -N -L …` command was faint gray, and the `<ssh-alias>` placeholder in both commands was dimmed too — so the prose asides and the commands were nearly the same weight, and the one line you'd paste was the hardest to see. Invert the emphasis. Both commands are now bold with a `$` prompt so they read as "type this", the `<ssh-alias>` fill-in-the-blank is cyan (legible, obviously a placeholder) instead of dimmed, and only the prose (intro, the alias note, the trailing `# then open …` comment) is dim. Reworded the intro/option headings so the two paths scan as 1) and 2). Plain-text output (pipes/CI/NO_COLOR) is unchanged in spirit and still carries no escape codes. * orx up remote banner: adapt to boxes with no ~/.ssh/config alias The banner assumed a `~/.ssh/config` alias existed and told the user to type `<ssh-alias>`. On a fresh RunPod / openresearch dev node you connect with a raw `root@<ip> -p <port>` and have no alias at all, so the primary instruction was a dead end. Make the connection target adaptive: - Anchor on `<user@host>` — the one thing every reader has, alias or not — with a concrete example, and say plainly no config alias is needed. - When `SSH_CONNECTION` gives a usable *public* server IP, splice it straight into both commands so they're nearly copy-paste. - Custom SSH ports can't be recovered here (`SSH_CONNECTION` carries sshd's listen port, not the client-dialed one — e.g. RunPod maps :38455 onto sshd :22), and `orx up --remote` has no `-p` flag. So the `[-p PORT]` slot goes only on the manual `ssh` line (which accepts it), and a caveat tells the reader to use option 2 on a non-default port. Tests cover both branches (placeholder vs inlined public IP) and assert the port slot never leaks onto the `orx up --remote` line. * up --remote: security-aware host-key policy + IPv6/port hardening Addresses multi-agent review of the custom-port support: - HostKeyPolicy enum (jobs/ssh.rs) centralizes host-key handling behind SshTarget::host_port, so the '-p'/'-o' opt vector and its rationale live in one place. openresearch_ssh_target now uses it (Ephemeral), replacing its inline vec — behavior byte-identical (guarded by an exact-vector test). - Don't silently disable host-key verification for a user-typed HOSTNAME with a port: a name may be one the user has pinned. Only a raw IP (the freshly-provisioned-box signal) gets accept-new TOFU against the real known_hosts; a hostname gets '-p' only (UserConfig). Neither uses /dev/null — that stays reserved for provider-managed boxes. - Fix raw-IPv6 mis-parse: '2001:db8::5' was split into host + '-p 5'. And fix 'user@[::1]:2222' silently dropping its port (the bracket check now looks past the user@ prefix). Reject port 0. - Be honest in the banner/help/errors: 'orx up --remote' reconstructs only user@host + port, so a custom key / jump host must come from ~/.ssh/config; the reachability error now hints at ssh-keygen -R for a reused-IP key clash. Tests cover the policy dispatch, IPv6 (raw/bracketed/user-prefixed), port 0, >u16, host_is_ip_literal forms, and the exact Ephemeral opt vector. * chore: bump version to 0.1.58 Ships the orx up --remote custom-port support + readable/honest SSH banner.v0.1.58 |
||
|
|
2c50f99a21 | report skill (#76) | ||
|
|
9cfc2e9cfa |
Claude plan mode: read-only orx allowed via PreToolUse hook (#75)
Re-enable plan mode for the Claude Code chat harness (headless `--print --permission-mode plan`). Plan mode gates `Bash(orx …)`, which broke the read-only `orx` inspection the agent plans from. A PreToolUse hook (`orx plan-gate`) returns an allow decision for read-only `orx` verbs, which overrides plan mode's Bash gate while file edits and launches stay blocked until the user approves the plan. - New hidden `orx plan-gate` command: the hook body (reads Claude's hook JSON on stdin, emits allow for read-only orx invocations). - Allowlist-only classifier in local/harness/plan_gate.rs; unknown verbs gate. `exp desc`/`cmd` are reads only without --set/--stdin; `exp wait` and whole-verb reads (runs/logs/query/…) allowed; launches gated. - Claude harness advertises Plan and writes the hook --settings file only in plan mode (reuses jobs::ssh::sh_quote). - Drift-guard test + pointer comment keep the allowlist synced with the Command enum in main.rs. Codex stays out (headless collaboration_mode=plan is a no-op). OpenCode already has a native read-only plan agent. Verified live against claude 2.1.197: read-only orx executes under plan mode with the hook, is denied without it, and orx exp run stays denied. |
||
|
|
08d30a8e61 |
Add anonymous, opt-out usage telemetry (PostHog) (#70)
* Add anonymous, opt-out usage telemetry (PostHog)
`orx` shipped with no telemetry, so there was no way to measure installs,
DAU/WAU, retention, or command usage. This adds anonymous, opt-out analytics
sent to a dedicated PostHog project.
What's collected: a per-invocation `cli_command` event and explicit
`experiment_started` events (create + run, with a coarse compute target),
carrying only a random per-install UUID, CLI version, os/arch, and CI flag.
Never any code, prompts, file contents, ids, names, paths, or tokens.
Design:
- New `src/telemetry.rs`: settings (install id + opt-out) at
`config_dir()/settings.json`, opt-out policy, event send, and a
fire-and-forget session modeled on `updates::UpdateWarning`.
- Opt-out: `DO_NOT_TRACK`, `ORX_TELEMETRY=off`, `CI`, a `--no-telemetry`
global flag, and a persistent `orx telemetry on|off|status` subcommand.
A one-time disclosure prints after the first `orx login` (shown even when
disabled, so it's never permanently suppressed).
- Never blocks or crashes the CLI: events are spawned and flushed once at
exit within a bounded window, so success output is never delayed; all
errors are swallowed.
- Cross-process safe: settings writes use an `fd_lock` advisory lock plus
atomic temp+rename, so a concurrent write can't revert a persisted opt-out.
- musl-safe: reuses the rustls `reqwest` client; no new TLS/C dependency.
Events land at PostHog `/i/v0/e/` with a public write-only `phc_` key and a
client-side ISO-8601 timestamp. `$process_person_profile:false` keeps events
anonymous. Unit tests cover opt-out precedence, id stability, payload shape
(PII-free), timestamp math, corrupt-file fail-safe, and the non-blocking
flush path.
* Prefix all CLI events with `cli_` to separate from website analytics
This PostHog project is shared with the website/cloud-agent analytics, so CLI
events must be distinguishable by event name alone — relying only on the
`source: "cli"` property is fragile (one forgotten dashboard filter leaks CLI
events into website metrics).
`build_payload` now stamps a `cli_` prefix onto every event name centrally, so
every current and future CLI event is `cli_*` by construction. Wire names:
- `cli_command` (was `cli_command`)
- `cli_experiment_started` (was `experiment_started`)
Added `every_event_name_is_cli_prefixed` to lock the guarantee.
* Telemetry: trim opt-out surface to --no-telemetry + `orx telemetry off`
Drop the `DO_NOT_TRACK`, `ORX_TELEMETRY`, and `CI` environment-variable
opt-out/disable checks. They were redundant with the two mechanisms we keep:
the `--no-telemetry` per-run flag and the persistent `orx telemetry off`.
The `CI` auto-disable in particular guarded a scenario that's mostly
hypothetical for an interactive research CLI, and it only ever fired for
hosted CI providers that set `CI` (missing Jenkins, agent boxes, and ad-hoc
scripts — the automation `orx` actually runs under). Every event still carries
a `ci` property, so automated runs can be filtered at query time in analytics
without being dropped up front.
Updates the opt-out policy, first-login notice text, `--no-telemetry` help,
README, and tests accordingly.
* orx up: add usage-analytics step to onboarding
Adds a third onboarding step ("Usage analytics") to the `orx up` dashboard
first-run walkthrough, after the coding-agents and git/GitHub steps. It shows
what is and isn't collected and lets the user toggle anonymous analytics On/Off
before creating their first project. On by default (matches the CLI's opt-out
model); the choice persists via the same `telemetry_disabled` setting as
`orx telemetry off`.
- Backend: `GET/POST /api/settings/telemetry` in up.rs, reusing
telemetry::disabled_reason / set_persisted_disabled (mirrors the git-settings
handler pattern). Returns `{ enabled, reason }`.
- UI: getTelemetry/setTelemetry in api.ts; Onboarding.tsx grows to a 3-step
flow with a TelemetryCard (On/Off segmented control).
Verified end-to-end in orx up: toggling Off in the wizard persists
`telemetryDisabled: true` to settings.json; toggling On clears it.
* Telemetry: drop the CLI login stderr disclosure
The one-time `orx login` stderr notice (show_notice_once) is removed now that
the disclosure lives in the `orx up` onboarding step, which is the right place
for it. Also removes the now-unused `notice_shown` settings field and updates
the module doc + README to point at the onboarding step instead of a login
notice.
* Telemetry: record consent decisions (cli_telemetry_consent)
Adds one event, `cli_telemetry_consent { agreed: bool }`, that records whether
the user opts into or out of analytics — so we can count agrees vs rejects.
Unlike every other telemetry event, this one is UNCONDITIONAL: it bypasses the
opt-out check and the persisted install id, and uses a fresh anonymous
distinct_id per call. If it honored the opt-out, every rejection would be
invisible and the ratio would be meaningless.
Fired once when the user leaves the analytics choice:
- `orx up` onboarding: on "Create your first project" (records the final On/Off
state, so users who accept the default On are counted too) via a dedicated
POST /api/settings/telemetry/consent endpoint.
- CLI: on `orx telemetry on` / `orx telemetry off`.
Tests: consent payload shape + name prefix, and that record_consent sends even
when persisted-off without generating an install id.
* Bump version to 0.1.57
Ships the anonymous opt-out telemetry subsystem, the orx up onboarding
usage-analytics step, and the cli_telemetry_consent event. Merging this PR
cuts release v0.1.57.
v0.1.57
|
||
|
|
843e5ed14b |
orx up: remote access over SSH (#73)
* orx up: remote access over SSH Running `orx up` while SSH'd into a remote box was broken: the server binds 127.0.0.1 (no auth, by design), so its loopback URL is unreachable from the laptop and there's no browser on the box to open. Add two complementary paths, keeping the server on loopback (never 0.0.0.0). Part A — guidance from the remote side (src/remote.rs): When `orx up` runs inside an SSH session (detected via SSH_CONNECTION/ SSH_TTY/SSH_CLIENT), skip the futile browser-open and print exact forwarding instructions — the `orx up --remote` one-liner plus a manual `ssh -L` fallback, both framed as "run on your laptop, not this session." A public SSH_CONNECTION server IP is offered as a best-effort hint; private/loopback/link-local/CGNAT addresses are filtered out. Part B — automatic forwarding from the laptop (src/commands/up_remote.rs): `orx up --remote <host>` owns the SSH client, so it can forward the port itself: check the host is reachable and has `orx`, start `orx up` there, bring up an `ssh -L 127.0.0.1:port:localhost:port` tunnel (ExitOnForwardFailure so a busy local port fails loudly instead of attaching to the wrong server), wait for the forwarded /api/health, open the browser, and hold the tunnel until Ctrl-C. Also: `orx up` now shuts down on SIGTERM/SIGHUP as well as SIGINT, so the remote server started over the tunnel exits when the tunnel closes instead of leaking. The remote dashboard is unauthenticated on the remote's loopback — noted in `--remote` help, since co-tenants on that host can reach it. * remote: clarify <ssh-alias> placeholder is the alias, not the IP The old <the-host-you-ssh'd-into> placeholder was ambiguous — a user could paste the box's IP, which usually won't work (the ~/.ssh/config alias carries the key/port/user the connection needs). Rename to <ssh-alias>, state plainly it's the name given to ssh (not the IP), and reframe the IP hint as a fallback that may need the usual -i/-p options. * remote: make the SSH forwarding guidance concise and scannable The startup message was a wall of equal-weight text. Tighten it and add light terminal styling: bold the header + the one command that matters (orx up --remote), dim the manual fallback and asides so the eye lands on the primary path. Fold the 'then open localhost' step onto the ssh -L line as an inline comment. Styling mirrors the updates.rs bold() idiom and is stripped on pipes/CI/NO_COLOR. Refactored into a testable render_instructions(port, ansi) core. |
||
|
|
eaace241d6 |
jobs: default PYTHONUNBUFFERED=1 so streamed logs never stall (#71)
Every job backend surfaces logs by tailing a pipe or a redirected `log`
file. When the job's Python block-buffers stdout (the default when stdout
isn't a TTY), those logs appear frozen until the buffer fills or the
process exits — the classic "frozen then a wall of text" symptom.
Default PYTHONUNBUFFERED=1 into the job environment across all five
backends so a job's prints stream live, while letting an explicit
author value win:
- modal: the sandbox env, plus the local launcher subprocess whose stdout
orx reads line-by-line (separate layer, same buffering problem)
- kubernetes: the container env in the Job manifest
- ssh / localbox: the exported env in the generated run.sh
- slurm: the sbatch payload's exports (not the login-node clone script)
Shared via jobs::default_unbuffered for the HashMap-env backends;
kubernetes open-codes the equivalent because its env is a JSON
[{name,value}] array. Inert for non-Python jobs and inherited by child
processes (unlike the -u flag).
|
||
|
|
36b7058f98 |
Chat: Escape stops the streaming turn (all harnesses) (#69)
Press Escape to interrupt an in-flight chat turn and drop focus back into the composer, mirroring the Claude Code desktop app. Works for every harness — a chat-view-scoped, busy-only document keydown listener calls the existing stop() -> interruptChat, which the backend routes to the active session's harness (Claude, Codex, OpenCode, ...). Also hardens the shared usePopover() hook to own Escape while open (capture phase + stopPropagation) — a pre-existing gap the new listener surfaced: without it, closing a composer picker with Escape would also interrupt the turn. The slash menu keeps its bubble-phase preventDefault path, gated by the listener's defaultPrevented check. Rebuilt ui/dist (tracked; debug reads from disk, release embeds it). |
||
|
|
bd9f438501 |
Chat: rename sessions from the Recents kebab menu (#68)
Add a "Rename" item to the per-row three-dot menu in the chat Recents
list (alongside Archive/Delete). Selecting it turns the row title into an
inline input — Enter/blur saves, Esc cancels — with an optimistic update
that reconciles with the server's chat.session broadcast.
Backend: extend the existing PATCH /api/chat/sessions/{id} handler to
accept a `title` field (trimmed, empty rejected) and add ChatHost::set_title,
mirroring set_archived (re-read after write + SSE emit).
Also guard harness auto-titling: TurnCtx::set_title (opencode's
session.updated path) now sets the title only when the session has none yet,
via an atomic conditional UPDATE, so it can't clobber a title the user set
via Rename.
Incidental: make the vite dev proxy target overridable with ORX_BACKEND
(defaults to the standard 4791) for testing against a non-default backend.
|
||
|
|
2c08a60fed |
Add OpenResearch compute as a backend: --backend openresearch (#67)
* Add OpenResearch compute as a backend: --backend openresearch
An ephemeral org-billed platform box per run, symmetric with the other
external backends. Submit validates flags, resolves the org, preflights the
user's registered SSH keys, pushes the branch, provisions via POST /sandboxes
(402 -> top-up message), records the run as starting, and detaches the
supervisor. The supervisor polls GET /sandboxes/{id} until the box is online,
records the SSH endpoint on the descriptor (restart-safe), launches the
clone-and-run payload over ssh wrapped in a wall-clock timeout guard, runs
the shared ssh watch loop, and deletes the box on every exit path — logs are
drained off the box before teardown, and a failed teardown appends an
'orx instance delete' hint to the run instead of changing its status.
Supporting changes:
- jobs/ssh.rs: SshTarget {dest, extra_opts} so the transport reaches
explicit user@host:port endpoints (openresearch) as well as config
aliases (ssh/slurm); ControlPath hash folds in the opts.
- supervise.rs: run_ssh's two-half loop extracted into watch_ssh_job,
shared by ssh and openresearch.
- Descriptor: sshHost/sshPort/sshUser + timeoutSecs optional fields
(fixed field list — unknown keys do not survive a round-trip).
- store: set_backend_json (narrow setter; upsert would clobber
status/markdown and race the cancel flag).
- client: get_sandbox / delete_sandbox / list_sandboxes / list_ssh_keys.
- orx instance list/delete: the manual cleanup path for a leaked box.
- UI: OpenResearch badge in BackendLogos; startRun type widened.
Requires the platform's companion PR (GET /sandboxes/{id} + PAT auth on
sandbox list/delete and ssh-keys list); against an older API the ssh-key
preflight degrades to a warning and provisioning-wait fails the run with
the API error after teardown retries.
* Bump version to 0.1.56 — releases --backend openresearch on merge
v0.1.56
|
||
|
|
18102cf0a9 | message default (#66) v0.1.55 | ||
|
|
a70206655b |
chore: prompt improvements (#65)
* improvements * skill * cargo * prompt improvementsv0.1.54 |
||
|
|
b1cab32b48 |
chore: local runs (#64)
* open runs * open runs * civ0.1.53 |
||
|
|
f1df93d4a3 |
Codex Auto: sandbox escalations become permission cards (PR2 re-land, v0.1.52) (#63)
* Codex auto mode: grant the orx data dir as a sandbox writable root workspace-write denies writes outside the workspace, and the orx SQLite store lives under ~/.local/share (WAL + read-write open), so every store-touching orx command died with 'unable to open database file' under Auto mode. Add store::data_dir() to the turn's writable_roots alongside the shared git dir. * Review fixes: pin ORX_DATA_DIR to the granted root, ensure_ naming - Pin ORX_DATA_DIR on the codex child (after prepare_env) so the store the sandboxed orx resolves is the exact path granted as a writable root — synced env or a relative override could otherwise diverge. - Rename orx_data_dir -> ensure_orx_data_dir (codebase ensure_ prefix for create-if-missing helpers); note the grant is dir-wide. - Dedent the '-c replaces user roots' note: it covers both roots. * Review round 2: match-expression pin, stale options() doc, asymmetry note * Codex chat: migrate to app-server protocol with exec fallback One long-lived 'codex app-server' child per session (CodexHost, the AgentHost pattern), JSON-RPC over stdio. Threads persist as the session's native id and resume across orx up restarts; the playbook rides developerInstructions; the sandbox policy travels per turn as a first-class sandboxPolicy param (writable roots + network) instead of -c config overrides that clobbered user config. approvalPolicy stays 'never' on every mode — this is a pure transport swap; the follow-up flips Auto to on-request and surfaces approval requests as permission cards (validated live against codex-cli 0.144: sandbox escalations DO arrive as item/commandExecution/requestApproval, upstream #21982 does not reproduce). codex < 0.144 keeps the legacy exec path for one release (ORX_CODEX_EXEC=1 forces it); detect() notes the upgrade. * Review round 1 fixes: turn-id filter, drop race, bounded interrupt Majors: - Filter turn events by the turn/start response's turn id so an aborted predecessor's tail (fatally, its turn/completed) can't leak into a successor turn's transcript. - TurnRoute::drop only detaches its own channel (same_channel guard) — an aborted task's late drop can no longer clobber a successor's registration. - interrupt_active_turn bounded to 5s so a wedged child can't hold the user-facing interrupt/delete paths hostage; reader EOF now also kills a half-dead child instead of leaving a zombie in the registry. - thread/resume falls back to a fresh thread only on a server-reported error (try_request); transport failures propagate so a resumable thread is never discarded over a timeout. - delete_project now kills codex children too (was opencode-only). Nits: options()/sandbox_policy_json doc rewrites, tool_part/tool_status/ part_exists/find_codex_required/respond_decline helpers, HashSet for unanswered ids, REQUEST_TIMEOUT const, outputDelta creates its part, turn/completed(inProgress) guard, double-error dedup, truthy ORX_CODEX_EXEC, softer agent_note, import cleanups. * Review round 2 fixes: abort-proof child bring-up, shared-helpers section Major: spawn + register + handshake now run in a detached task, so an aborted turn task (interrupt/delete during the first message) can no longer orphan an unregistered app-server child; a no-displace insert keeps a raced successor's live client intact, and handshake failure kills + deregisters only its own entry. Nits: shared helpers moved out of the legacy section under their own banner; set_active_turn armed from the turn/start response (deletes the turn/started special case and closes the early-interrupt no-op window); parse_codex_version takes the first parseable token; resume-rejection logged; HANDSHAKE_TIMEOUT const; TurnEnd/apply_notification docs; has_error_part test; fold-test guard tightened; comment accuracy. * Round 3 polish: doc word order, eprintln prefix, mid-handshake reuse note * fmt * Codex Auto: on-request approvals surfaced as permission cards Auto now runs approvalPolicy on-request: the writable roots keep routine orx traffic prompt-free, and anything escalating past the sandbox arrives as item/*/requestApproval, surfaced as a permission card (native_id = JSON-RPC request id) and answered inline via resume_from_prompt -> {"decision": accept|decline}. Invariant: no unresolved codex card outlives its turn — every turn exit sweeps open cards (resolve + decline with codex); interrupt paths cancel pending approvals natively before turn/interrupt; serverRequest/resolved reconciles codex-side settlement (its ~5min approval deadline). Stale-turn requests are declined, never surfaced; Bypass auto-accepts; unknown request types get a JSON-RPC error instead of blocking codex. A 10-minute no-event watchdog (suspended while a card is pending) interrupts wedged turns. * PR2 review round 1 fixes: stale-card defense, Bypass reply schema, card content - Consent integrity: codex request ids restart per child, so a stale unresolved card could deliver a click to a different live request. Turn-entry resolve_stale_prompts sweep (store-side, codex-only — end-turn harnesses keep their cross-turn cards) + turn-scoped part ids (appr-{turn}-{id}) close it from both ends. - Bypass now auto-accepts only card-able approval methods; unknown request types get the JSON-RPC error reply in every mode (a {decision} reply to item/tool/requestUserInput would fail to parse). - Permission cards show the request's human-readable reason (fileChange cards carry nothing else); UI dist rebuilt. - serverRequest/resolved also clears the id from the client's pending set (stale-answer guard stays truthful after codex self-settles). - Reconciliation computes the part id directly (approval_part_id) instead of a reverse scan; resolve_card returns (); sweep declines unconditionally (respond's pending set is the single arbiter); watchdog bumped to 30min with an honest false-positive note; resume_from_prompt gains a kind guard. * PR2 round 2 polish: schema-aware fallback replies, immediate card flush Non-approval request methods now get the JSON-RPC error reply in every fallback path (read_loop unrouted, stale-turn arm) via a shared is_approval_request; codex-settled cards flush immediately instead of waiting for the next event; message_json reuse; Handled doc drift; watchdog comment dedup. * Bump version to 0.1.52v0.1.52 |
||
|
|
34fd606402 |
Codex chat: migrate to app-server protocol (PR1 — transport swap) (#61)
* Codex auto mode: grant the orx data dir as a sandbox writable root workspace-write denies writes outside the workspace, and the orx SQLite store lives under ~/.local/share (WAL + read-write open), so every store-touching orx command died with 'unable to open database file' under Auto mode. Add store::data_dir() to the turn's writable_roots alongside the shared git dir. * Review fixes: pin ORX_DATA_DIR to the granted root, ensure_ naming - Pin ORX_DATA_DIR on the codex child (after prepare_env) so the store the sandboxed orx resolves is the exact path granted as a writable root — synced env or a relative override could otherwise diverge. - Rename orx_data_dir -> ensure_orx_data_dir (codebase ensure_ prefix for create-if-missing helpers); note the grant is dir-wide. - Dedent the '-c replaces user roots' note: it covers both roots. * Review round 2: match-expression pin, stale options() doc, asymmetry note * Codex chat: migrate to app-server protocol with exec fallback One long-lived 'codex app-server' child per session (CodexHost, the AgentHost pattern), JSON-RPC over stdio. Threads persist as the session's native id and resume across orx up restarts; the playbook rides developerInstructions; the sandbox policy travels per turn as a first-class sandboxPolicy param (writable roots + network) instead of -c config overrides that clobbered user config. approvalPolicy stays 'never' on every mode — this is a pure transport swap; the follow-up flips Auto to on-request and surfaces approval requests as permission cards (validated live against codex-cli 0.144: sandbox escalations DO arrive as item/commandExecution/requestApproval, upstream #21982 does not reproduce). codex < 0.144 keeps the legacy exec path for one release (ORX_CODEX_EXEC=1 forces it); detect() notes the upgrade. * Review round 1 fixes: turn-id filter, drop race, bounded interrupt Majors: - Filter turn events by the turn/start response's turn id so an aborted predecessor's tail (fatally, its turn/completed) can't leak into a successor turn's transcript. - TurnRoute::drop only detaches its own channel (same_channel guard) — an aborted task's late drop can no longer clobber a successor's registration. - interrupt_active_turn bounded to 5s so a wedged child can't hold the user-facing interrupt/delete paths hostage; reader EOF now also kills a half-dead child instead of leaving a zombie in the registry. - thread/resume falls back to a fresh thread only on a server-reported error (try_request); transport failures propagate so a resumable thread is never discarded over a timeout. - delete_project now kills codex children too (was opencode-only). Nits: options()/sandbox_policy_json doc rewrites, tool_part/tool_status/ part_exists/find_codex_required/respond_decline helpers, HashSet for unanswered ids, REQUEST_TIMEOUT const, outputDelta creates its part, turn/completed(inProgress) guard, double-error dedup, truthy ORX_CODEX_EXEC, softer agent_note, import cleanups. * Review round 2 fixes: abort-proof child bring-up, shared-helpers section Major: spawn + register + handshake now run in a detached task, so an aborted turn task (interrupt/delete during the first message) can no longer orphan an unregistered app-server child; a no-displace insert keeps a raced successor's live client intact, and handshake failure kills + deregisters only its own entry. Nits: shared helpers moved out of the legacy section under their own banner; set_active_turn armed from the turn/start response (deletes the turn/started special case and closes the early-interrupt no-op window); parse_codex_version takes the first parseable token; resume-rejection logged; HANDSHAKE_TIMEOUT const; TurnEnd/apply_notification docs; has_error_part test; fold-test guard tightened; comment accuracy. * Round 3 polish: doc word order, eprintln prefix, mid-handshake reuse note * fmt |
||
|
|
433c91c479 |
Codex auto mode: grant the orx data dir as a sandbox writable root (#60)
* Codex auto mode: grant the orx data dir as a sandbox writable root workspace-write denies writes outside the workspace, and the orx SQLite store lives under ~/.local/share (WAL + read-write open), so every store-touching orx command died with 'unable to open database file' under Auto mode. Add store::data_dir() to the turn's writable_roots alongside the shared git dir. * Review fixes: pin ORX_DATA_DIR to the granted root, ensure_ naming - Pin ORX_DATA_DIR on the codex child (after prepare_env) so the store the sandboxed orx resolves is the exact path granted as a writable root — synced env or a relative override could otherwise diverge. - Rename orx_data_dir -> ensure_orx_data_dir (codebase ensure_ prefix for create-if-missing helpers); note the grant is dir-wide. - Dedent the '-c replaces user roots' note: it covers both roots. * Review round 2: match-expression pin, stale options() doc, asymmetry note |
||
|
|
6636e8e8cc |
Files tab redesign: minimalist tree + proper report rendering (v0.1.51) (#58)
* Files tab redesign: minimalist tree, centered column, GFM report tables * Bump version to 0.1.51 * Review round 1 fixes: GFM link/table edge cases, shared code blocks, class rename sweep * Rebuild ui/distv0.1.51 |
||
|
|
9799d21d9f | fix: append ellipsis to truncated chat session titles (#59) | ||
|
|
a0e67cd210 |
File viewer: open files from the chat session's worktree (#57)
Clicking a file chip in chat failed with "path must be repo-relative": agents work in per-session worktrees, and the viewer only knew how to strip the hub-clone prefix — and only served files from the hub clone, where agent-created files don't exist. - App.tsx parseFilePath maps agent-reported paths (relative, repoPath- prefixed, worktrees/ and repos/ cache layouts) to a repo-relative path plus the session id; relative paths inherit the click context's session (chat's active session, or the viewed file's). - project_file gains a sessionId query param: the session is validated against the store (must belong to the project), its worktree is served when present, and a missing worktree degrades to the hub clone. The response's root field says which checkout answered so the viewer can flag the fallback. - File tabs are keyed by (path, sessionId) so the same path from different sessions opens distinct tabs. |
||
|
|
10eaa5a25e |
Recents: hover kebab menu (archive/delete) + Active/Archived/All filter (#56)
* Recents: hover kebab menu (archive/delete) + Active/Archived/All filter
Claude-desktop-style session management in the chat rail:
- three-dot menu on session-row hover with Archive/Unarchive and Delete
(confirm-guarded; delete already existed server-side)
- filter button beside the Recents label: Active (default) / Archived / All
- new archived column on chat_sessions + PATCH /api/chat/sessions/{id};
archive keeps updated_at so sessions keep their recency slot
* Review round 1: delete broadcast, keyboard access, rollback + polish
- delete_session now emits chat.session.deleted; dashboards remove the row
(and its cached transcript/busy state) instead of keeping ghosts
- set_archived re-reads after the write so the broadcast can't clobber a
concurrent title change or resurrect a deleted session; sending into an
archived session unarchives it (activity unarchives)
- kebab reveal uses :focus-within (row-only :focus-visible dropped focus to
body mid-Tab); row keydown ignores events from descendant buttons
- archive rollback restores the pre-request snapshot, not the negation;
removeSession alerts on failure and uses functional setActiveId
- Escape closes all popovers; dead .rail-filter-btn.active rule removed;
danger color uses --accent-red; copy/labels tidied
* Review round 2 polish: delete tombstones, keyboard menu dismiss, error surfacing
- deletedIds tombstone set ignores chat.session upserts racing a delete
(a naturally-finishing turn could resurrect a ghost row)
- row Enter/Space mirrors the click branch: dismiss an open menu instead of
switching sessions underneath it
- deleteChatSession goes through the shared json helper so the alert shows
the real server error; alert copy names the session
- one filter reset in send() covers new sessions and unarchive-on-activity
- SESSION_FILTERS carries railLabel (single table); comment placement fix
|
||
|
|
7844718961 | skills (#55) | ||
|
|
55692ef239 |
Codex harness: inject GitHub token so sandboxed git/gh can authenticate (v0.1.50) (#53)
* Codex harness: inject GitHub token so sandboxed git/gh can authenticate Codex's workspace-write Seatbelt sandbox blocks the macOS keyring, so gh inside the runner reports its stored token invalid and git's credential helper can't authenticate — private-repo fetch/push fail even with the network access granted in #51. Resolve the token in the parent process (GITHUB_TOKEN env, synced env, gh auth token) and pass it to the codex child as GH_TOKEN/GITHUB_TOKEN, which gh and its credential helper prefer over the keyring. * Bump version to 0.1.50v0.1.50 |