Skip to content

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
jackwener:mainfrom
LeoLin990405:fix/grok-i18n-selectors
Closed

feat: conversation mgmt across 4 AI surfaces (grok/codex/antigravity/trae-solo) + grok i18n + Chromium 142 launcher#1797
LeoLin990405 wants to merge 7 commits into
jackwener:mainfrom
LeoLin990405:fix/grok-i18n-selectors

Conversation

@LeoLin990405
Copy link
Copy Markdown
Contributor

@LeoLin990405 LeoLin990405 commented May 31, 2026

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.js were keyed on English aria-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.

  • Submit: button[data-testid="chat-submit"]"Submit" / "提交" / "送信"
  • Model: #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 / Pin or Unpin / Delete). 3 of these are wired up. Implementation note: must dispatch the full pointer chain (pointerdownmousedowncontextmenupointerupmouseup with button=2); the bare MouseEvent('contextmenu') alone is ignored by radix.

Grok deletes WITHOUT a confirmation dialog, so --yes is required.

Known: rename left as a stub-with-error because the radix onSelect for the Rename menu item doesn't respond to programmatic clicks (synthetic chains all report clicked:true but the inline rename input never materializes). Needs CDP Input.dispatchMouseEvent at viewport coordinates — separate followup.

3. feat(codex): add pin/unpin/archive/rename

opencli codex pin/unpin/archive/rename for sidebar conversations.

All four use the SAME path: select the target chat (--project / --conversation / --index / --thread-id), then open the button[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 programmatic mouseenter won't surface them. The Chat actions menu mounts on click, independent of visibility.

archive is dry-run by default (Codex applies it without UI confirmation); pass --yes to actually archive.

Known partial: rename returns status:'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):

$ opencli grok ask 'reply with: working'
- response: 思考了 3s working

$ opencli grok status
- Model: Fast    # was null before

$ opencli grok pin/unpin/delete --yes <uuid>
- status: pinned / unpinned / deleted    # sidebar still_present=false after delete

Codex (macOS desktop, app://-/index.html):

$ opencli codex pin --project leo --index 2
- status: pinned

$ opencli codex archive --project leo --index 2 --yes
- status: archived

Tests pass: 29/29 grok, 14/14 codex.

Related

…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).
@LeoLin990405 LeoLin990405 changed the title fix(grok): make submit/model selectors locale-independent fix(grok): locale-independent selectors + feat: delete/pin/unpin commands May 31, 2026
@LeoLin990405 LeoLin990405 force-pushed the fix/grok-i18n-selectors branch from 8f2a3ef to 448acb2 Compare May 31, 2026 01:47
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
@LeoLin990405 LeoLin990405 changed the title fix(grok): locale-independent selectors + feat: delete/pin/unpin commands fix(grok)+feat(codex): i18n selectors, grok delete/pin/unpin, codex pin/unpin/archive/rename May 31, 2026
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
@LeoLin990405 LeoLin990405 changed the title fix(grok)+feat(codex): i18n selectors, grok delete/pin/unpin, codex pin/unpin/archive/rename feat: conversation management for grok/codex/antigravity + grok i18n + Chromium 142 launcher fix May 31, 2026
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).
@LeoLin990405 LeoLin990405 changed the title feat: conversation management for grok/codex/antigravity + grok i18n + Chromium 142 launcher fix feat: conversation mgmt across 4 AI surfaces (grok/codex/antigravity/trae-solo) + grok i18n + Chromium 142 launcher May 31, 2026
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
@LeoLin990405
Copy link
Copy Markdown
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.

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.

1 participant