Skip to content

feat(codex): pin/unpin/archive/rename + model selector fix#1799

Merged
jackwener merged 6 commits into
jackwener:mainfrom
LeoLin990405:feat/codex-mgmt
May 31, 2026
Merged

feat(codex): pin/unpin/archive/rename + model selector fix#1799
jackwener merged 6 commits into
jackwener:mainfrom
LeoLin990405:feat/codex-mgmt

Conversation

@LeoLin990405
Copy link
Copy Markdown
Contributor

Split from #1797. This PR contains all codex-adapter changes.

See commit message for full details. Summary:

New commands:

  • opencli codex pin / unpin / archive / rename — all targeting threads via the existing --project / --index / --thread-id selectors.
  • All four use the Chat actions header dropdown (button[aria-label="Chat actions"]) instead of per-row hover buttons, because Codex's per-row buttons are React-lazily-mounted and require document.visibilityState === 'visible' (broken when window is minimized).

Fix:

  • opencli codex model — old impl always returned "Unknown or Not Found" (selector pattern didn't match Codex's current UI). New impl finds the model toolbar button via composer-walk + text-pattern match, and supports --list to enumerate options.

Verified live: pin/unpin/archive all work end-to-end; model read/list/switch all work (verified "Extra High → Medium → Extra High" cycle). Rename is partial (commit-on-Enter intermittent — known followup).

14/14 grok tests pass.

@LeoLin990405
Copy link
Copy Markdown
Contributor Author

Codex App deep-audit summary (commits 9fa3f890 + 75656fca)

Live-snapshot of Codex Desktop revealed 161 visible interactive elements / 105 unique labels. After the deep audit pass this PR now ships 39 commands across 5 layers.

Layer Commands
Conversation mgmt ask send read new history projects pin unpin archive rename export screenshot dump status
Models model (list / read / switch — mixes versions + reasoning levels)
Diffs / files extract-diff add-files
Per-message actions react (good/bad) copy-message fork edit-message undo scroll-bottom
Global UI settings search filter-chats sidebar-toggle account nav (back/forward) toggle-panel chat-actions-menu project-new start-chat-in
Menu items (revealed by chat-actions-menu + filter-chats) side-chat add-automation open-in-new-window archive-all organize-sidebar sort-by

Safety patterns:

  • fork / undo / archive-all / project-new require --yes (default dry-run)
  • All other write commands are idempotent or low-risk

Known issue: search selector fails when sidebar Search button has the "⌘G" unicode suffix — chat-actions-menu / filter-chats / sort-by cover the menu-enumeration use-case more reliably.

E2E verified: account, chat-actions-menu (5 items: Open side chat / Fork / Add automation / Open in new window), filter-chats (Archive all / Organize sidebar / Sort by), sort-by (Created, Updated), copy-message, all dry-runs clean.

LeoLin990405 added a commit to LeoLin990405/OpenCLI that referenced this pull request May 31, 2026
Antigravity is VSCode-derived, so it has BOTH a renderer-side storage
layer (LS/SS/cookies/IDB via CDP) AND the full VSCode FS state.vscdb
on disk. This commit wraps both.

Renderer side (4 — same shape as PR jackwener#1798 Grok / PR jackwener#1799 Codex):
  storage-keys [--storage local|session] [--filter] [--limit]
  storage-get <key> [--storage] [--max-bytes]
  cookies
  idb-list

FS side (4 — VSCode-style state.vscdb + workspace enumeration):
  state-keys [--filter] [--workspace] [--limit]
        — list keys in globalStorage state.vscdb. With --workspace
          <id>, query a per-workspace state.vscdb. Works while
          Antigravity is closed.
  state-get <key> [--workspace] [--max-bytes]
        — read one value (auto-decode JSON).
  recent-paths
        — pretty-print history.recentlyOpenedPathsList (the File >
          Open Recent list).
  workspaces-list
        — enumerate workspaceStorage/<uuid>/ + resolve workspace.json
          to the actual folder path.

Settings (1):
  settings-read
        — parse and pretty-print user settings.json (handles JSONC:
          line comments + block comments + trailing commas).

E2E verified against Leo's Antigravity install:
  state-keys --filter antigravity → 5+ antigravity.notification.* keys
                                     (out of 202 total in state.vscdb)
  recent-paths → 5+ folders/files (xiaotie, 竞品代码, Mini-Agent, …)
  workspaces-list → 7 workspaces with folder paths + mtimes
  settings-read → colorTheme=Solarized Light, http.proxy=127.0.0.1:7897,
                  agCockpit.displayMode=quickpick, tfa.system.autoAccept=true
  state-get history.recentlyOpenedPathsList → full JSON returned
  renderer storage-keys → EMPTY_RESULT (correct: Antigravity uses
                          state.vscdb instead of browser LS)

Brings Antigravity adapter to 31 commands.
LeoLin990405 added a commit to LeoLin990405/OpenCLI that referenced this pull request May 31, 2026
Cross-surface naming harmonization (matches PR jackwener#1798 Grok / jackwener#1799 Codex /
jackwener#1800 Antigravity):
  storage-* → renderer-side localStorage / sessionStorage (via CDP)
  state-*   → on-disk VSCode state.vscdb (via sqlite3)

Changes:
  - Renamed storage-keys → state-keys + storage-get → state-get
    (these query state.vscdb, kept their behavior 100% identical)
  - Added renderer-storage.js: storage-keys / storage-get / cookies / idb-list
    (CDP-based, query the Electron renderer's LS/SS/cookies/IDB)
  - Added settings.js: settings-read (parse User/settings.json, JSONC-aware)

E2E verified against Leo's Trae SOLO:
  state-keys --filter "AI.agent"  → 3 AI.agent.* keys (renamed, same behavior)
  settings-read                   → 1 key: AI.toolcall.v2.command.allowList

Known limitation: renderer storage-keys / storage-get / cookies / idb-list
return SecurityError on Trae SOLO because Trae's renderer runs under
vscode-file:// scheme with strict sandboxing — opencli's eval context
hits an isolated world without access to the main page's
localStorage/cookies/IndexedDB. Direct CDP probes (bypassing opencli's
session manager) CAN see the data (22 LS keys + @byted/ve-rtc IDB),
but the standard adapter pathway is blocked. Documented as a structural
limitation; the commands are wired correctly and will work on any Trae
build that relaxes the sandbox.

Brings Trae SOLO to 44 commands (38 + 1 settings-read + 4 renderer + 1
state-* alias gap).
@LeoLin990405
Copy link
Copy Markdown
Contributor Author

Update — 4 renderer-side storage commands added (commit `90e2f4ca`)

Mirrors the same shape now landed on PR #1798 (Grok) and PR #1800 (Antigravity):

  • `storage-keys [--storage local|session] [--filter] [--limit]`
  • `storage-get [--storage] [--max-bytes]`
  • `cookies` — list JS-visible cookies on app:// origin
  • `idb-list` — list IndexedDB databases on the renderer

E2E verified: `storage-keys` returns 5 statsig analytics keys (~400KB feature-flag cache). `cookies` / `idb-list` correctly EmptyResult since Codex uses Electron session cookies + server-side auth + no IDB.

Brings Codex App to 43 commands.

jackwener pushed a commit to LeoLin990405/OpenCLI that referenced this pull request May 31, 2026
Cross-surface naming harmonization (matches PR jackwener#1798 Grok / jackwener#1799 Codex /
jackwener#1800 Antigravity):
  storage-* → renderer-side localStorage / sessionStorage (via CDP)
  state-*   → on-disk VSCode state.vscdb (via sqlite3)

Changes:
  - Renamed storage-keys → state-keys + storage-get → state-get
    (these query state.vscdb, kept their behavior 100% identical)
  - Added renderer-storage.js: storage-keys / storage-get / cookies / idb-list
    (CDP-based, query the Electron renderer's LS/SS/cookies/IDB)
  - Added settings.js: settings-read (parse User/settings.json, JSONC-aware)

E2E verified against Leo's Trae SOLO:
  state-keys --filter "AI.agent"  → 3 AI.agent.* keys (renamed, same behavior)
  settings-read                   → 1 key: AI.toolcall.v2.command.allowList

Known limitation: renderer storage-keys / storage-get / cookies / idb-list
return SecurityError on Trae SOLO because Trae's renderer runs under
vscode-file:// scheme with strict sandboxing — opencli's eval context
hits an isolated world without access to the main page's
localStorage/cookies/IndexedDB. Direct CDP probes (bypassing opencli's
session manager) CAN see the data (22 LS keys + @byted/ve-rtc IDB),
but the standard adapter pathway is blocked. Documented as a structural
limitation; the commands are wired correctly and will work on any Trae
build that relaxes the sandbox.

Brings Trae SOLO to 44 commands (38 + 1 settings-read + 4 renderer + 1
state-* alias gap).
LeoLin990405 and others added 5 commits May 31, 2026 22:56
5 changes to the Codex Desktop adapter:

1. feat: 4 new conversation management commands
     opencli codex pin     [--project P --index N | --thread-id ID]
     opencli codex unpin   ...
     opencli codex archive ...   (--yes required; Codex's term for delete)
     opencli codex rename  '<new title>' ...   (partial — see note below)

   All four resolve the target via the existing openCodexConversation
   helper (--project / --conversation / --index / --thread-id) and then
   trigger the action via the chat header 'Chat actions' dropdown
   (button[aria-label='Chat actions']).

   Why the header menu and not per-row sidebar buttons?
   The per-row Pin chat / Archive chat buttons are React-lazily-mounted
   — they only render while the row is hovered AND
   document.visibilityState === 'visible'. When the user has the Codex
   window minimized, programmatic mouseenter is not enough to surface
   them. The Chat actions header menu mounts items on click, independent
   of window visibility.

   Implementation patterns (shared in _actions.js):
   - Full pointer-event chain on radix menu trigger
     (pointerdown → mousedown → pointerup → mouseup → click)
   - Defer the menu-item click to Promise.resolve().then(...) so the
     eval reply returns BEFORE the action triggers a sidebar re-render
     that would otherwise eat the reply and surface as a 30s
     Runtime.evaluate timeout

   Known partial: 'rename' returns status:'renamed' and the rename
   contenteditable does receive the new title, but commit-on-Enter
   sometimes doesn't persist on the next selection probe. Pin/unpin/
   archive are fully verified end-to-end.

2. fix: model command actually reads + switches the current model

   Old impl looked for [aria-label*='Model'] / .model-selector /
   [class*='ModelPicker'] — none of these exist in current Codex
   Desktop. Result was always 'Unknown or Not Found'.

   New impl walks up from the composer contenteditable and finds the
   toolbar button whose visible text matches a model/reasoning pattern
   (/5\.\d|Extra High|Medium|Low|...|GPT-/). Clicking opens a menu
   shared with Chat actions — we filter out Pin/Rename/Archive/etc.
   so the user gets just the model + reasoning rows.

   Adds --list to enumerate available options.

Verified live (Codex Desktop on macOS, app://-/index.html):

  $ opencli codex pin --project leo --index 2
    - status: pinned
  $ opencli codex archive --project leo --index 2 --yes
    - status: archived
  $ opencli codex projects --project leo  → row gone ✓

  $ opencli codex model
    - Status: Active, Model: 5.5 Extra High
  $ opencli codex model --list
    Low / Medium / High / Extra High / GPT-5.5 / Speed
  $ opencli codex model Medium
    - Status: switched, Model: Medium  (verified by re-read)

14/14 existing codex tests pass.
Live-snapshot of Codex Desktop revealed 161 visible interactive elements
across 105 unique labels. Beyond the existing 16 commands, identified
and wrapped 23 high-value buttons:

Per-message actions (chat-actions.js):
  react <good|bad>           — Good response / Bad response on last reply
  copy-message [--click-button] — extract last assistant message text
  fork --yes                 — Fork from this point (new branch)
  edit-message               — Edit message on last user turn
  undo --yes                 — undo last agent action
  scroll-bottom              — Scroll to bottom (or fallback scroller scroll)

Global UI (ui-actions.js):
  settings                   — click Settings (Cmd+,)
  search <query>             — Search ⌘G + type + read results
  filter-chats               — open Filter sidebar chats + list items
  sidebar-toggle             — Hide / Show sidebar
  account                    — read account dropdown items
  nav <back|forward>         — in-app history nav
  toggle-panel <which>       — summary / bottom / side panel
  add-files <file>           — composer file upload via CDP page.upload
  chat-actions-menu          — enumerate Chat actions menu items
  project-new <name> --yes   — Add new project + name + submit
  start-chat-in <project>    — Start new chat in <project>

Menu items revealed by chat-actions-menu + filter-chats (menu-items.js):
  side-chat                  — Open side chat
  add-automation             — Add automation…
  open-in-new-window         — Open in new window
  archive-all --yes          — DANGER: archive ALL chats
  organize-sidebar           — Open Organize sidebar dialog
  sort-by                    — List Sort by submenu options

E2E verified live against Codex Desktop:
  account             → email + 12 sidebar items
  chat-actions-menu   → 5 items including Open side chat / Fork / Add automation
  filter-chats        → 3 items (Archive all chats, Organize sidebar, Sort by)
  sort-by             → 2 items (Created, Updated)
  copy-message        → real user/assistant text extracted
  all --yes-gated commands print clean dry-run preview

Known: `search <query>` selector is brittle (Search button has unicode
hotkey suffix "⌘G" that complicates aria-label matching). Defer fix —
chat-actions-menu / filter-chats / sort-by enumerate menus reliably.

Brings Codex App adapter to 39 commands.
…use per-char spans

Two root causes after live-probe:

1. Codex's Search button has NO aria-label or data-testid. Its only
   identifier is innerText 'Search\n⌘G'. Switched the open-search step
   from CSS attribute selector to a JS text filter (innerText starts
   with 'Search', length < 30).

2. Search results render as [role='option'] but each character is in
   its own <span> (for fuzzy-match highlighting), making innerText
   return empty. textContent works. Title is the first '.truncate'
   descendant; the rest of the option text concatenates project name
   + hotkey + description which we deliberately drop.

Verified live:
  codex search 'opencli'  →  8 matched conv titles
  codex search '毕业'      →  1 Chinese match + 'Loading chats…' (race)
  storage-keys [--storage local|session] [--filter] [--limit]
                — list LS/SS keys with byte sizes
  storage-get <key> [--storage] [--max-bytes]
                — read one value, auto-decode JSON
  cookies       — list JS-visible cookies on app:// origin
  idb-list      — list IndexedDB databases on the renderer

E2E verified against Codex Desktop (CDP 9238):
  storage-keys → 5 keys (statsig feature-flag cache, ~400KB)
  sessionStorage / cookies / idb-list → all return gracefully empty
    (Codex uses Electron session cookies + server-side auth)

Brings Codex App adapter to 43 commands.
@jackwener jackwener merged commit d149302 into jackwener:main May 31, 2026
jackwener pushed a commit to LeoLin990405/OpenCLI that referenced this pull request May 31, 2026
Cross-surface naming harmonization (matches PR jackwener#1798 Grok / jackwener#1799 Codex /
jackwener#1800 Antigravity):
  storage-* → renderer-side localStorage / sessionStorage (via CDP)
  state-*   → on-disk VSCode state.vscdb (via sqlite3)

Changes:
  - Renamed storage-keys → state-keys + storage-get → state-get
    (these query state.vscdb, kept their behavior 100% identical)
  - Added renderer-storage.js: storage-keys / storage-get / cookies / idb-list
    (CDP-based, query the Electron renderer's LS/SS/cookies/IDB)
  - Added settings.js: settings-read (parse User/settings.json, JSONC-aware)

E2E verified against Leo's Trae SOLO:
  state-keys --filter "AI.agent"  → 3 AI.agent.* keys (renamed, same behavior)
  settings-read                   → 1 key: AI.toolcall.v2.command.allowList

Known limitation: renderer storage-keys / storage-get / cookies / idb-list
return SecurityError on Trae SOLO because Trae's renderer runs under
vscode-file:// scheme with strict sandboxing — opencli's eval context
hits an isolated world without access to the main page's
localStorage/cookies/IndexedDB. Direct CDP probes (bypassing opencli's
session manager) CAN see the data (22 LS keys + @byted/ve-rtc IDB),
but the standard adapter pathway is blocked. Documented as a structural
limitation; the commands are wired correctly and will work on any Trae
build that relaxes the sandbox.

Brings Trae SOLO to 44 commands (38 + 1 settings-read + 4 renderer + 1
state-* alias gap).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants