feat(codex): pin/unpin/archive/rename + model selector fix#1799
Conversation
|
Codex App deep-audit summary (commits 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.
Safety patterns:
Known issue: E2E verified: |
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.
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).
|
Update — 4 renderer-side storage commands added (commit `90e2f4ca`) Mirrors the same shape now landed on PR #1798 (Grok) and PR #1800 (Antigravity):
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. |
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).
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.
90e2f4c to
bbbd714
Compare
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).
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-idselectors.button[aria-label="Chat actions"]) instead of per-row hover buttons, because Codex's per-row buttons are React-lazily-mounted and requiredocument.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--listto 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.