Files
OpenResearch/ui
Daniel Kim 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.52
2026-07-13 15:27:04 -07:00
..
2026-07-06 20:05:41 -07:00
2026-07-08 18:23:16 -07:00
2026-07-06 20:05:41 -07:00
2026-07-06 20:05:41 -07:00
2026-07-06 20:05:41 -07:00