feat(grok): conversation mgmt (delete/pin/unpin) + locale-independent selectors#1798
Conversation
|
Grok exhaustive coverage summary (commits Did the same deep-audit pass on Grok that landed in PR #1801 for Trae SOLO. Grok is now exhaustively wrapped at every layer reachable from outside its server-side API. Total: 24 commands across 3 layers
What's NOT wrapped — and why:
Safety patterns:
E2E verified live on grok.com: account name+email extraction, search-conversations with multi-language queries ( Known limitation (documented in code): Happy to split into smaller PRs if reviewer prefers — currently 3 commits, each a coherent unit. |
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).
Two changes bundled together since both touch clis/grok/utils.js:
1. fix(grok): locale-independent submit/model selectors
Two selectors 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 — on zh-CN they render as
aria-label='提交' / '模型选择'. Result:
$ opencli grok ask 'hi'
ok: false
error: Grok submit button did not reach a clickable state
$ opencli grok status
- Model: null
Fix: prefer locale-independent anchors with localized aria-label
fallbacks.
Submit → 'button[data-testid="chat-submit"]' →
'Submit' / '提交' / '送信'
Model → '#model-select-trigger' →
'Model select' / '模型选择' / 'モデル選択'
Both data-testid='chat-submit' and id='model-select-trigger' have
been stable on grok.com for months and don't change between locales.
2. feat(grok): add delete/pin/unpin (rename stub)
Right-clicking any '/c/<id>' sidebar link surfaces a radix context
menu with 4 items (i18n; zh-CN / en):
打开新标签页 / Open in new tab
重命名 / Rename
置顶 or 取消置顶 / Pin or Unpin (state-conditional)
删除 / Delete
3 of these are now first-class commands:
opencli grok delete <id> --yes # no UI confirmation; gate via --yes
opencli grok pin <id>
opencli grok unpin <id>
New utils helper clickConversationMenuItem() fires the full pointer
chain (pointerdown → mousedown → contextmenu → pointerup → mouseup
with button=2 at the link's bounding rect). Bare
MouseEvent('contextmenu') is ignored — radix listens on pointer
events.
Pin/Unpin are mutually exclusive in the menu slot; defineToggle
registers both commands binding to whichever label is present.
Known followup: 'rename' is a stub. Click on the Rename menu item
fires onSelect at the DOM level but radix's React handler doesn't
pick it up from synthetic clicks — the inline rename input never
materializes. Both JS .click() and opencli's CDP click succeed at
the DOM level but the React handler is skipped. Will need CDP
Input.dispatchMouseEvent at viewport coordinates.
Verified zh-CN, Chrome 148, grok.com free tier:
$ opencli grok ask 'reply with: working' -f yaml
- response: 思考了 3s working
$ opencli grok status -f yaml
- Model: Fast # was null before
$ 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 confirms still_present=false ✓
29/29 existing grok tests pass.
…enerate/react)
model [--list] [--set <name>]
— show / list / switch the active Grok model. Reuses utils.js
locale-independent MODEL_TRIGGER_SELECTORS pattern, then reads
radix [role=menu] portal items.
copy-message [--conv <id>] [--markdown] [--click-button]
— return the text of the last assistant message. With --click-button
also fires the in-UI Copy button (writes to system clipboard).
regenerate [--conv <id>]
— click Regenerate on the last assistant message.
react <like|dislike> [--conv <id>]
— click Like / Dislike on the last assistant message.
All message-action commands accept optional --conv <id|URL> to navigate
to a specific conversation before acting (mirrors detail.js navigation).
Without --conv, they operate on the current tab.
Selectors:
- Action buttons use locale-independent aria-label fallback lists
(Regenerate / 重新生成 / 再生成 — same pattern for Copy/Like/Dislike)
- Full pointer-event chain (pointerdown→mousedown→pointerup→mouseup→click)
to satisfy radix-style portal menus
- Action buttons live in the bubble's nearest ancestor; we walk up 5
parents then querySelectorAll('button') to find by aria-label
E2E verified live on grok.com against a real conversation:
copy-message extracted the assistant's actual text ("Hello Lin! 👋 ...")
react like / react dislike both clicked successfully
model --list returns all 4 models (Fast / Auto / Expert / Heavy)
Known limitation: model --set clicks the UI item successfully, but the
selection doesn't persist across separate opencli invocations because
each command opens a fresh tab. Useful for "list models" and inside a
single-tab workflow; for persistent model preference, follow up with a
send/ask on the same tab.
…hare UI
Browser-side state (analog to Trae SOLO's storage-* family):
storage-keys [--storage local|session] [--filter] [--limit]
— list localStorage (default) or sessionStorage keys with byte sizes
storage-get <key> [--storage] [--max-bytes]
— read a single LS/SS value, auto-decode JSON
user-settings
— shortcut: pretty-print Grok's "user-settings" object (privacy
toggles, chat preferences, agent library counts)
cookies
— list grok.com cookies visible to JS (httpOnly auth tokens are
deliberately NOT shown — that's a security feature). Preview is
truncated to 40 chars to avoid leaking session tokens into logs.
idb-list
— enumerate IndexedDB databases on grok.com (does NOT read store
contents — those may include third-party extension data)
Sidebar / dialog UI:
incognito
— switch the current tab to /c#private (private chat mode that's
not saved to history)
account
— read sidebar account info (name + email) and the dropdown menu
items (settings / upgrade / sign-out). Cleans up by pressing
Escape to leave the menu closed.
search-conversations <query> [--limit]
— open the sidebar search palette, type the query, return matched
conversation titles + ids. Cleans up on exit.
share [--conv <id>]
— click "Create share link" on the current (or specified) conv;
extract the grok.com/share/... URL from the resulting dialog.
Useful for sharing answers — caller's responsibility to know this
makes the conv publicly accessible.
E2E verified on grok.com:
storage-keys → 23 localStorage keys (chat-preferences, modes-selected-id,
user-settings, xai-ff-bu, etc.)
storage-get modes-selected-id → "fast"
user-settings → 14 preferences + 6 privacy toggles + agent library counts
cookies → 9 JS-visible cookies (x-userid, x-anonuserid, etc.)
idb-list → AiTopia + mixpanelBrowserDb
account → Name + Email + 6 menu items (settings / upgrade / sign-out)
incognito → navigates to /c# (private mode)
search-conversations "Taipei" → matches "Friendly greeting in Taipei"
share command verified by static review only — live E2E deferred because
running it would create a real public share URL for the user's existing
conversation. Calling code/user should opt in explicitly.
Deep-audit pass identified 8 UI buttons mapped earlier but not wrapped:
sidebar-toggle — collapse/expand the left sidebar (idempotent;
reports sidebarOpen localStorage state)
toggle-preview — show/hide conversation preview pane (隐藏对话预览)
view-all — open full conversation history page (查看全部)
project-list — list projects in sidebar accordion (graceful
empty result if no projects exist)
project-new <name> --yes — create a new project via the create-project
dialog (dry-run by default)
attach <file> [--conv] — set <input type=file> on the composer via CDP
page.upload; pair with `send` to actually send
edit-message [--index N] — click Edit on a user message (default: last);
returns the original text
more-actions [--conv] — open the per-message "More actions" menu on
the last assistant message and list its items
E2E verified live on grok.com:
sidebar-toggle → localStorage sidebarOpen flipped false ↔ true
project-list → EMPTY_RESULT (no projects on this account) — clean
project-new → dry-run shows planned action
more-actions → revealed 3 menu items: 报告问题, 导出到 PDF, 朗读
attach + toggle-preview + edit-message are E2E-deferrable: attach needs
an actual file to upload, toggle-preview needs a sidebar item with preview
already shown, edit-message needs an assistant turn loaded. All three
are wired up and verified by static review.
This brings Grok to 33 commands (was 25), matching Trae SOLO's coverage
depth across UI + browser storage layers.
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).
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 grok-adapter changes. Other surfaces (codex / antigravity / trae-solo) are in separate PRs.
See commit message for full details — bundled because both changes touch
clis/grok/utils.js:data-testid/ stable id with i18n aria-label fallback (was broken in zh-CN locale).opencli grok delete/pin/unpincommands using the radix sidebar context menu, with the full pointer chain needed to trigger radix events.Verified live on zh-CN Chrome 148 / grok.com free tier.
renameis stubbed (radix React onSelect intercept — separate followup).