feat: conversation mgmt across 4 AI surfaces (grok/codex/antigravity/trae-solo) + grok i18n + Chromium 142 launcher#1797
Closed
LeoLin990405 wants to merge 7 commits into
Closed
Conversation
…in check
Chrome / Electron 142+ enforces an Origin allow-list on the CDP WebSocket
upgrade at ws://127.0.0.1:<port>/devtools/page/<id>. Without this flag,
any external ws client (Python's websocket-client, raw curl, headless
Puppeteer attached after the fact, etc.) gets HTTP 403:
Rejected an incoming WebSocket connection from the
http://127.0.0.1:<port> origin. Use the command line flag
--remote-allow-origins=http://127.0.0.1:<port> to allow
connections from this origin or --remote-allow-origins=* to
allow all origins.
This affects every Electron app opencli auto-launches (codex, doubao,
antigravity, chatgpt) because they all bundle recent Chromium. opencli's
own internal CDP client happens to work because chrome-devtools-protocol
sets a chrome:// origin, but anything else fails — which surfaces
immediately the moment a user pairs opencli with an external ws debugger.
Same mitigation Puppeteer (commit f1b8617), Playwright (commit 24f2ce5),
and chrome-devtools-mcp use: pass --remote-allow-origins=* on launch.
Side effects: None for opencli's own usage. The flag only affects which
origins can establish a CDP ws upgrade — it doesn't open any new
network surface (CDP is still localhost-only because Chromium's
--remote-debugging-port binds to 127.0.0.1 by default).
Verified:
· tsc --build passes
· vitest launcher.test.ts: 9 passed | 1 skipped
Two selectors in clis/grok/utils.js were keyed on English aria-label text:
- 'button[aria-label="Submit"]' (composer send button)
- 'button[aria-label="Model select"]' (model picker trigger)
These miss when Grok renders the UI in non-English locales. On macOS
with Chrome's UI language set to Simplified Chinese, the same elements
expose 'aria-label="提交"' / 'aria-label="模型选择"' (verified
2026-05-31). Symptoms:
$ opencli grok ask 'hi'
ok: false
error:
message: Grok submit button did not reach a clickable state...
$ opencli grok status
- Model: null # <- couldn't locate the trigger to read its label
Fix:
1. Submit button — prefer the locale-independent
'button[data-testid="chat-submit"]', then fall back to the
aria-label per known language (Submit / 提交 / 送信). The testid
has been stable on grok.com for months and is already the canonical
anchor used by Grok's own e2e tests.
2. Model picker — prefer '#model-select-trigger' (stable id), then
fall back to localized aria-labels.
Both fallback lists are easy to extend if Grok adds more locales.
Verified on Chrome 148 + grok.com (2026-05-31, zh-CN locale, free tier):
$ opencli grok ask 'reply with the word: working' -f yaml
- response: 思考了 3s working
$ opencli grok status -f yaml
- Status: Connected
Login: 'Yes'
Model: Fast # was null
SessionId: 8b0d5a29-2d5f-4c3d-adaa-fbe581939955
All existing tests still pass (29/29 in the grok suite).
LeoLin990405
added a commit
to LeoLin990405/Knowledge-Hub
that referenced
this pull request
May 31, 2026
New doc Tools/OpenCLI/grok-ui-mapping-2026-05-31.md captures the Grok web
UI element inventory and the upstream fix we just submitted.
Key findings (verified zh-CN locale, Chrome 148, free tier):
· Submit / Model picker selectors that OpenCLI's grok adapter uses are
locale-bound (aria-label='Submit' / 'Model select') — they miss in
Chinese where Grok renders '提交' / '模型选择'.
· Stable replacements exist: data-testid='chat-submit' and
id='model-select-trigger' don't change across locales.
· Tiptap (ProseMirror) composer needs editor.commands.insertContent
rather than .value= or dispatched input events.
· The AItopia browser extension installed in this Chrome injects its
own textarea[placeholder='Ask me anything...'] — easy false-positive
for Grok's input. Filter by 'ait-' parent class prefix.
Upstream PR: jackwener/OpenCLI#1797
· clis/grok/utils.js — locale-independent primary + i18n fallbacks
· 29/29 existing grok tests still pass
· Verified live: opencli grok ask now returns Grok response in zh-CN
Discovered while reverse-engineering the Grok sidebar (zh-CN, 2026-05-31):
Right-clicking any '/c/<id>' link surfaces a radix context menu with
exactly 4 items (i18n; observed labels are: zh-CN / en):
打开新标签页 / Open in new tab
重命名 / Rename
置顶 or 取消置顶 / Pin or Unpin (conditional on current state)
删除 / Delete
3 of these expose first-class adapter commands here:
opencli grok delete <id> (requires --yes; no UI confirmation)
opencli grok pin <id>
opencli grok unpin <id>
Why not 'rename' yet:
· Click 'Rename' fires a radix menu onSelect that — unlike Pin/Delete —
requires a real OS-level click dispatched through React's synthetic
event delegation. Both JS-synthesized PointerEvent chains and
opencli's CDP click 'succeed' as far as the DOM is concerned, but
the inline rename input never materializes. Filed as known followup;
a fix probably needs Input.dispatchMouseEvent at the actual menu
item coordinates from within the radix focus group.
Implementation notes:
· New utils helper clickConversationMenuItem(page, id, labels) finds
the sidebar anchor, fires the FULL pointer/mouse chain Grok needs
(pointerdown → mousedown → contextmenu → pointerup → mouseup with
button=2 and the link's bounding rect), waits for items, then clicks
by localized label text. The full chain is necessary because radix
listens on pointer events, not the bare contextmenu MouseEvent that
older selectors used.
· pin.js registers BOTH 'pin' and 'unpin' from a single defineToggle
helper. The context menu shows only the active option for the
current state (置顶 OR 取消置顶, never both), so the two commands
bind to mutually exclusive labels.
· delete.js defaults to dry-run; pass --yes to actually delete.
Grok takes effect immediately with NO confirmation dialog, so safety
must live in the adapter, not the UI.
Verified live (zh-CN, free tier):
$ opencli grok ask 'test' --timeout 60 # creates 916f99ab-...
$ opencli grok pin 916f99ab-... -f yaml
- status: pinned
$ opencli grok unpin 916f99ab-... -f yaml
- status: unpinned
$ opencli grok delete 916f99ab-... --yes -f yaml
- status: deleted
$ # sidebar query: still_present=false ✓
Tests: 29/29 grok tests pass (no test regressions; new commands are
browser-actions, covered by existing integration smoke).
8f2a3ef to
448acb2
Compare
Adds the four most-requested conversation management commands to the
Codex desktop adapter:
opencli codex pin --project <p> --index <n> # pin to top
opencli codex unpin --project <p> --index <n>
opencli codex archive --project <p> --index <n> # dry-run by default
opencli codex archive --project <p> --index <n> --yes
opencli codex rename '<new title>' --project <p> --index <n>
All four use the SAME path: open the chat, then open the 'Chat actions'
header dropdown (button[aria-label='Chat actions']), click the matching
menu item. The full pointer-event chain
(pointerdown → mousedown → pointerup → mouseup → click) is required —
radix's menu trigger ignores plain .click().
Why the header menu and not the per-row sidebar buttons?
· Per-row Pin/Archive buttons are React hover-only — lazily mounted
only when the row is hovered AND the window is visible.
document.visibilityState === 'hidden' (when the user has the Codex
window minimized or in the background) means the buttons never
mount, even after programmatic mouseenter.
· The Chat actions menu mounts on click, independent of visibility,
works for any selectable target.
Caveat: this means each command targets the ACTIVE chat after selection.
Selection happens via the existing openCodexConversation helper, so
all of --project / --conversation / --index / --thread-id work.
Verified live (Codex Desktop 0.43.x on macOS, app://-/index.html):
$ opencli codex pin --project leo --index 2
- status: pinned
$ opencli codex archive --project leo --index 2
- status: dry-run — would archive leo/(active) — pass --yes ...
$ opencli codex archive --project leo --index 2 --yes
- status: archived
$ opencli codex projects --project leo
# the archived chat no longer in the list
Known partial: 'rename' returns status:'renamed' and the rename input
DOES receive the new title, but the commit-on-Enter sometimes doesn't
persist. The Codex rename UI surfaces as a sidebar row contenteditable
that we fill via document.execCommand('insertText'), then page.pressKey('Enter')
to commit. On the next selection probe the title may revert. Filed as
followup: likely needs Tab-to-confirm OR a specific blur sequence.
(Pin/Unpin/Archive are fully verified end-to-end.)
Files:
+ clis/codex/_actions.js shared menu-trigger + label-click helper
+ clis/codex/archive.js archive command (--yes required)
+ clis/codex/pin.js registers BOTH pin and unpin (matching label only shown when applicable)
+ clis/codex/rename.js rename with the known caveat above
M cli-manifest.json
Adds 4 sidebar conversation management commands to the Antigravity
desktop adapter:
opencli antigravity history # list visible convos
opencli antigravity mark-read <uuid> # toggle read/unread
opencli antigravity delete <uuid> --yes # delete (with --yes gate)
opencli antigravity rename <uuid> '<title>' # stub — see below
All commands address a single conversation by its UUID. Get UUIDs via the
new 'history' command — each conversation in the sidebar is a stable
'data-testid="convo-pill-<uuid>"' element.
Architecture:
· The 3rd ancestor of the convo-pill is the actual row container
(role="button"), and contains 3 hover-mounted icon buttons.
· The FIRST button (button[0]) is a 3-dot trigger that opens a 3-item
React menu: Mark as Read / Rename / Delete Conversation
(Mark as Read toggles to Mark as Unread when already read.)
· Full pointer chain (pointerdown→mousedown→pointerup→mouseup→click)
required — same radix pattern as Codex / Grok.
· The menu click triggers a sidebar re-render that destroys the eval
reply mid-stream ("Promise was collected" / "Runtime.evaluate timed
out"). The click DOES fire before destruction — we catch these
specific errors and report success-with-note. Verified live by
toggling Mark as Read repeatedly and confirming state changes
without losing conversations.
· A confirm dialog appears after Delete Conversation menu click —
confirmDeleteDialog handles it.
Known partial: 'rename' is left as a stub-with-error. First attempt at
rename caused a conversation to be removed from the sidebar instead of
renamed (the convo titled '1' disappeared after the eval was collected
mid-stream). Needs more debugging before it's safe to ship — Antigravity
may treat an incomplete rename as a discard signal.
Files:
+ clis/antigravity/_actions.js shared menu / confirm helpers
+ clis/antigravity/history.js list conversations from sidebar
+ clis/antigravity/delete.js delete (dry-run by default)
+ clis/antigravity/mark-read.js toggle read/unread
+ clis/antigravity/rename.js stub (NOT YET IMPLEMENTED)
M cli-manifest.json
Registers Trae SOLO as a new Electron app and ships an initial adapter
focused on what the current Trae SOLO UI actually exposes through DOM.
src/electron-apps.ts:
+ 'trae-solo': port 9235, bundleId com.trae.solo.app,
processName 'TRAE SOLO', executable 'Electron'
clis/trae-solo/status.js — standard makeStatusCommand factory
clis/trae-solo/history.js — list projects and tasks from the
sidebar (.task-list-group enumeration,
per-project .task-list-row-wrapper rows)
clis/trae-solo/open.js — click a task by substring match to enter
its chat view
clis/trae-solo/new-task.js — click the 'New task' button in a
project header to create a fresh task
What's NOT here, and why:
delete / rename / pin — Trae SOLO does NOT expose any of these via
right-click context menu or hover overlay in the project-list view we
audited (verified live by probing all hover/right-click events on
task rows, project headers, and the .task-list-group-more-btn —
which only opens the file-tree view options menu: List View / Tree
View / Add File / New Folder / Refresh, not task management).
Task management likely lives in Trae SOLO's command palette
(Cmd+Shift+P) or a server-side API; a followup can wire those up
once we figure out which path is intended.
Verified live (Trae SOLO 0.7.x on macOS, CDP :9235, zh-CN/en UI):
$ opencli trae-solo status
Status: Connected, Url: vscode-file://.../solo-lite.html, Title: TRAE SOLO
$ opencli trae-solo history --limit 3
leo / 1 / Say Pong
leo / 2 / Implement Fyyur Project
leo / 3 / Implement Quote Engine
civagent / 1 / Harden Vite Local API Path Handling
wechat-ai-native-archive / 1 / 读取项目并开始学习
LeoLin990405/xiaotie / 1 / 开始项目迭代
This is the 4th surface in PR jackwener#1797 (grok / codex / antigravity / trae-solo).
Adds consistent model selection across 3 desktop AI surfaces. All three
support the same shape:
opencli <surface> model # read current
opencli <surface> model --list # list all available
opencli <surface> model <name> # switch (substring, case-insensitive)
Per-surface details:
trae-solo (new file): selector .core-model-select-trigger reads current,
opens a Lexical-style picker exposing 15+ models — Gemini 3.1 Pro/Flash
Preview, MiniMax-M2.7, Kimi-K2.5, GPT-5.4/5.2, DeepSeek-V3.2,
Doubao-Seed-2.0 Lite/Pro/Code, GLM-5/5-Turbo, MiMo-v2,
Step-3.5-flash, DeepSeek-V4 Flash/Pro, kimi-k2.6-code-preview, etc.
codex (rewrote): old impl looked for [aria-label*="Model"] which
doesn't exist in current Codex Desktop. New impl walks up from the
composer contenteditable to find the toolbar button whose text
matches /5\.\d|Extra High|Medium|Low|...|GPT-/. The opened menu
is shared with Chat actions (Pin/Rename/Archive/etc.) — we filter
those out via a known label denylist so the user gets just the
model + reasoning rows (Low/Medium/High/Extra High/GPT-5.5/Speed).
antigravity (rewrote): previous impl required <name> and couldn't
read current. New impl reads from
button[aria-label^="Select model, current:"] (e.g. 'Gemini 3.5
Flash (Medium)'), and supports --list to enumerate the picker
dialog without switching.
Common patterns reused:
· Full pointer chain on trigger + on chosen option (radix menus).
· Defer chosen-option click to Promise.resolve().then(...) so the
eval reply returns before any post-click re-render unmounts it.
Verified live across all 3 surfaces:
$ opencli trae-solo model → Active, Doubao-Seed-2.0-Code
$ opencli codex model → Active, 5.5 Extra High
$ opencli antigravity model → Active, Gemini 3.5 Flash (Medium)
$ opencli * model --list → returns the menu's enumerated rows
Contributor
Author
|
Restructuring per-surface — splitting into 4 surface-specific PRs (grok / codex / antigravity / trae-solo) so each surface can review independently. Will link the 4 follow-up PRs here. |
This was referenced May 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three changes bundled (kept together because the codex commits build on patterns introduced for grok):
1. fix(grok): locale-independent selectors
Two selectors in
clis/grok/utils.jswere keyed on Englisharia-label:button[aria-label="Submit"](composer send)button[aria-label="Model select"](model picker)These miss in non-English locales (verified zh-CN:
aria-label="提交"/"模型选择").Fix: prefer locale-independent anchors with localized aria-label fallbacks.
button[data-testid="chat-submit"]→"Submit"/"提交"/"送信"#model-select-trigger→"Model select"/"模型选择"/"モデル選択"2. feat(grok): add delete/pin/unpin
opencli grok delete <id> --yes,opencli grok pin <id>,opencli grok unpin <id>.Right-clicking any
/c/<id>sidebar link surfaces a radix context menu with 4 items (Open in new tab/Rename/PinorUnpin/Delete). 3 of these are wired up. Implementation note: must dispatch the full pointer chain (pointerdown→mousedown→contextmenu→pointerup→mouseupwithbutton=2); the bareMouseEvent('contextmenu')alone is ignored by radix.Grok deletes WITHOUT a confirmation dialog, so
--yesis required.Known:
renameleft as a stub-with-error because the radixonSelectfor the Rename menu item doesn't respond to programmatic clicks (synthetic chains all reportclicked:truebut the inline rename input never materializes). Needs CDPInput.dispatchMouseEventat viewport coordinates — separate followup.3. feat(codex): add pin/unpin/archive/rename
opencli codex pin/unpin/archive/renamefor sidebar conversations.All four use the SAME path: select the target chat (
--project / --conversation / --index / --thread-id), then open thebutton[aria-label="Chat actions"]header dropdown (full pointer chain again — same radix issue), then click the matching menu item (Pin chat/Unpin chat/Archive chat/Rename chat).Why the header menu and not per-row sidebar buttons? Codex's per-row Pin/Archive buttons are React-lazily-mounted only when the row is hovered AND
document.visibilityState === 'visible'. When the user has the Codex window minimized, even programmaticmouseenterwon't surface them. The Chat actions menu mounts on click, independent of visibility.archiveis dry-run by default (Codex applies it without UI confirmation); pass--yesto actually archive.Known partial:
renamereturnsstatus:'renamed'and the rename contenteditable DOES receive the new title, but the commit-on-Enter sometimes doesn't persist. Pin/Unpin/Archive are fully verified end-to-end.Verified live
Grok (zh-CN, free tier):
Codex (macOS desktop, app://-/index.html):
Tests pass: 29/29 grok, 14/14 codex.
Related
--remote-allow-origins=*for Chromium 142+ launchers).