feat: ChatGPT Desktop CDP + app-server list/read with host filters - #116
Conversation
🦋 Changeset detectedLatest commit: 94b283c The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Add a ChatGptDesktopAdapter with a local-only CDP implementation, DOM selectors in one module, macOS relaunch helper, chatgpt_desktop_* MCP tools, gbot chatgpt-desktop CLI commands, and app-server fallback that reports backend. Includes rstest coverage with a fake adapter and a minor changeset. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Include the compiled grok-bot MCP server and gbot CLI routes for the ChatGPT Desktop CDP scaffold so packed installs expose the new tools. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Tighten cdp-dom selectors against the macOS Desktop probe: exact main window URL filtering, _MainContentSurface thread root, named experimental composer constants, and focus → Input.insertText → submit send order. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Prove chatgpt_desktop_* prefer CDP, reuse codex-bridge when CDP is unreachable, and tag results with backend cdp|app-server. NotImplemented DOM gaps do not trigger fallback. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Update cdp-dom and the adapter to the verified Desktop behaviors: project new-chat button, composer focus+insertText+Enter, Stop/final-assistant reply detection, conversation-id annotation, Loading task wait, and column-reverse mouseWheel full history reads with limit/full. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Desktop local:<conversationId> maps to bare app-server thread ids. Visible reads stay on CDP; full/deep history uses thread/read (wheel crawl last-resort). List prefers app-server and merges CDP sidebar fields. Send, new-thread-in-project, and wait-for-reply stay CDP-only. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Strip local: before app-server RPCs. Read via thread/read metadata plus thread/turns/list (itemsView full); never resume for reads. List and codex list-threads pass modelProviders:[]. Surface richer thread fields, remote-thread typed errors, and search. CDP keeps send/wait/selected. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Parse ~/.codex/.codex-global-state.json remote-thread-summaries-v3 defensively and merge into chatgpt_desktop_list_threads with location, hostId, hostName, host filter, and groupBy=host. read_thread returns a typed remote error with a host app-server hint. Fixture-backed tests. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Keep host (all|local|id/name) and modelProvider→modelProviders as independent list/search filters. Add chatgpt_desktop_list_hosts / gbot chatgpt-desktop hosts with thread counts, friendly names, and modelProviders discovered via app-server list method or distinct thread/list values (hostsSource / modelProvidersSource reported). Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
When listRemoteThreads is injected (or remotes arrive without a matching CODEX_HOME), still surface those hostIds with accurate thread counts. Rebuild artifacts for chatgpt_desktop_list_hosts. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
- list-threads: modelProviders:[], nextCursor paging, useStateDbOnly with full-scan fallback when the state DB returns nothing - normalize Desktop local: thread ids on every codex threadId acceptor - map remote-control "thread not loaded" to REMOTE_THREAD_NOT_LOADED with hostId/hostName on codex send and conversation open - readCodexThread uses thread/read metadata + thread/turns/list (never deprecated includeTurns) - outcomeFromError forwards hostId/hint/code; allowlist matches normalized ids Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Resolve new-thread send to data-response-annotation-conversation (never return local:client-new-thread:* as threadId). Harden column-reverse history wheels, Loading task timeouts, local:/bare sidebar dedupe+open, exact MAIN_WINDOW_URL attach, and keep loopback + useStateDbOnly cold-list guards covered by tests. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Zack hard requirement: no hardcoded hosts, machines, connections, or providers in schemas, enums, docs defaults, or tests beyond fixtures. host and modelProvider accept any string; descriptions point to chatgpt_desktop_list_hosts. Add a test that injects an unknown hostId into the global-state fixture and proves list_hosts + filtering work with no code change. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
42e43cb to
323e8e1
Compare
Package CI failed because tools.test.ts still expected the pre-list_hosts tool set after rebase. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
The package CI snapshot check requires the committed manifest sourceInputs hash to match tests/route-unit/tools.test.ts. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Rebuild agent-bundle evidence/manifest so sourceInputs match the Desktop CDP isolation/reply-tracking commit that just landed. Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc38123669
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const result = finalizeThreadList(filtered, { | ||
| limit, | ||
| host, | ||
| modelProvider: options.modelProvider?.trim() || undefined, | ||
| project: options.project, | ||
| groupBy: options.groupBy, | ||
| remotes: [], |
There was a problem hiding this comment.
Preserve app-server matches displaced by remote search rows
When the first app-server search page is full and at least one matching remote summary is merged, finalizeThreadList sorts remote rows first and this second limiting pass drops one or more app-server rows from an already-consumed page. The returned nextCursor starts after those dropped rows, and subsequent pages disable remote merging, so a complete cursor walk can never return the missing matches. Search needs a stable merged snapshot or a cursor that retains displaced rows.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 94b283c: searchThreads now builds one stable merged snapshot (all app-server search pages + CDP + remote rows, deduped and sorted) and pages it with a fingerprinted offset cursor, so a full cursor walk returns every match. Regression: walks every app-server search match when a remote row displaces a full first page.
| if (/thread not loaded|not loaded|no rollout found|thread not found/i.test(message)) { | ||
| const remote = findRemoteThread(threadId); | ||
| if (remote) { | ||
| return new RemoteThreadNotLoadedError(threadId, remote.hostId, remote.hostName); | ||
| } | ||
| const hostId = findRemoteThreadHostId(threadId); | ||
| return new RemoteThreadNotLoadedError(threadId, hostId, null); |
There was a problem hiding this comment.
Allow CDP fallback for ordinary missing local threads
For a local ID absent from remote state, ordinary app-server errors such as no rollout found or thread not found reach this unconditional return and become RemoteThreadNotLoadedError with a null host. The facade treats that type as authoritative and skips its CDP fallback, so a thread visible in Desktop but missing from daemon state cannot be read, while genuinely unknown IDs are falsely described as remote-owned. Only construct this error when remote ownership is actually found.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 94b283c: RemoteThreadNotLoadedError is now only built when remote ownership is actually found (remote thread or a non-null hostId); otherwise the original error propagates so the facade falls back to CDP. Regression: falls back to CDP when a local thread is absent from app-server and remote state.
| const appList: ListThreadsResult = { backend: 'app-server', limit: appRows.length, threads: appRows }; | ||
| const base = cdpList ? mergeThreadLists(appList, cdpList) : appList; |
There was a problem hiding this comment.
Report only backends that successfully contributed
When the app-server listing throws but CDP or remote summaries still provide rows, execution continues with a warning, yet this unconditional empty appList causes the result backend to be reported as app-server+cdp, app-server+remote-state, or both. Callers therefore cannot use backend to determine whether the authoritative app-server inventory actually contributed; track app-server success separately and build provenance only from successful sources.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 94b283c: the list/search snapshots and listHosts track app-server success separately and report backend only from sources that contributed (e.g. cdp+remote-state when the app-server listing fails). Regression: reports only CDP and remote state when app-server inventory fails.
Summary
ChatGPT Desktop adapter over loopback CDP + Codex app-server (0.158.0), plus Codex path bugfixes and a Ripwire production-risk audit.
Rebased onto latest
main(#114npm verify retry +#96@rstest/core0.12.2) so CI can run.Desktop
~/.codex/.codex-global-state.json.hostandmodelProvider— any string, not enums. Discover allowed values viachatgpt_desktop_list_hosts/gbot chatgpt-desktop hosts.No hardcoding (Zack hard requirement)
inputJsonSchemaforhost/modelProviderare freeformstring(no static JSON-schema enum of hosts or providers).chatgpt_desktop_list_hosts.<hostId>/<providerId>placeholders, not machine names as defaults.host-supercomputer-<uuid>into the global-state fixture and asserts it shows up inlistDiscoveredHosts/listHostsand can be filtered by hostId and friendly name with no code change.Bugs fixed
Ripwire impact/grep/
--situaudit acrosssrc/core/chatgpt-desktop/*andlistCodexThreads. Each item has a regression test.[data-response-annotation-conversation]and return that durable id asthreadId. Never returnlocal:client-new-thread:*asthreadId(may still appear astemporaryThreadId). Same durable resolution on wait-for-reply.scrollTop0 = newest). Full history loads viaInput.dispatchMouseEventmouseWheel with negativeFULL_READ_WHEEL_DELTA_Y; never by writingscrollTop.Loading task…waits —waitForLoadingTaskGonepolls with a hard timeout (default 90s) and throws when the banner never clears.dedupeThreadsByIdmergeslocal:<id>with bare<id>, preferring selected/pinned rows.Target.getTargets+ exactapp://-/index.html(pickMainWindowTarget); ignores overlay/webview URLs and/json/listorder.assertLoopbackHostname/forceLoopbackWebSocketUrl; non-loopback hosts rejected.thread/listusesuseStateDbOnly: true+modelProviders: [], then falls back to a full scan when the state DB returns an empty first page; pages vianextCursor.Codex path fixes (same PR)
local:on every codex threadId acceptor.REMOTE_THREAD_NOT_LOADEDwith host on send/conversation open.readCodexThread:thread/readmetadata +thread/turns/list(never deprecatedincludeTurns).GROK_BOT_CODEX_THREADSallowlist compares normalized ids;outcomeFromErrorforwardshostId/hint/code.Discovery sources (
list_hosts)hostsSourceremote-thread-summaries-v3+local(+ optionalapp-server:<remote-env-method>)modelProvidersSourceapp-server:<list-method>; elsethread/list-distinctDocs
docs/chatgpt-desktop-cdp.md.