Skip to content

feat: ChatGPT Desktop CDP + app-server list/read with host filters - #116

Merged
ScriptedAlchemy merged 27 commits into
mainfrom
cursor/chatgpt-desktop-cdp-scaffold-827b
Sep 29, 2026
Merged

ScriptedAlchemy merged 27 commits into
mainfrom
cursor/chatgpt-desktop-cdp-scaffold-827b

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Sep 29, 2026 •

Copy link
Copy Markdown
Owner

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 (#114 npm verify retry + #96 @rstest/core 0.12.2) so CI can run.

Desktop

  • List / search / read: app-server primary; list merges remote-control summaries from ~/.codex/.codex-global-state.json.
  • Send / new-thread / wait: CDP-only.
  • Separate filters: host and modelProvider — any string, not enums. Discover allowed values via chatgpt_desktop_list_hosts / gbot chatgpt-desktop hosts.
  • New machines or connections appear with no code change.

No hardcoding (Zack hard requirement)

  • Zod + inputJsonSchema for host / modelProvider are freeform string (no static JSON-schema enum of hosts or providers).
  • Descriptions say: see chatgpt_desktop_list_hosts.
  • Docs CLI examples use <hostId> / <providerId> placeholders, not machine names as defaults.
  • Test injects a brand-new host-supercomputer-<uuid> into the global-state fixture and asserts it shows up in listDiscoveredHosts / listHosts and can be filtered by hostId and friendly name with no code change.

Bugs fixed

Ripwire impact/grep/--situ audit across src/core/chatgpt-desktop/* and listCodexThreads. Each item has a regression test.

  1. Temp vs real thread ids — After a new-thread send, wait for [data-response-annotation-conversation] and return that durable id as threadId. Never return local:client-new-thread:* as threadId (may still appear as temporaryThreadId). Same durable resolution on wait-for-reply.
  2. Timeline scroll direction — Timeline is column-reverse (scrollTop 0 = newest). Full history loads via Input.dispatchMouseEvent mouseWheel with negative FULL_READ_WHEEL_DELTA_Y; never by writing scrollTop.
  3. Loading task… waits — waitForLoadingTaskGone polls with a hard timeout (default 90s) and throws when the banner never clears.
  4. Duplicate sidebar rows — dedupeThreadsById merges local:<id> with bare <id>, preferring selected/pinned rows.
  5. Target selection — Attach uses Target.getTargets + exact app://-/index.html (pickMainWindowTarget); ignores overlay/webview URLs and /json/list order.
  6. Loopback-only CDP — assertLoopbackHostname / forceLoopbackWebSocketUrl; non-loopback hosts rejected.
  7. Cold-list timing — thread/list uses useStateDbOnly: true + modelProviders: [], then falls back to a full scan when the state DB returns an empty first page; pages via nextCursor.

Codex path fixes (same PR)

  • Strip Desktop local: on every codex threadId acceptor.
  • Remote-control “thread not loaded” → REMOTE_THREAD_NOT_LOADED with host on send/conversation open.
  • readCodexThread: thread/read metadata + thread/turns/list (never deprecated includeTurns).
  • GROK_BOT_CODEX_THREADS allowlist compares normalized ids; outcomeFromError forwards hostId/hint/code.

Discovery sources (list_hosts)

Field Path
hostsSource remote-thread-summaries-v3+local (+ optional app-server:<remote-env-method>)
modelProvidersSource Prefer app-server:<list-method>; else thread/list-distinct

Docs

docs/chatgpt-desktop-cdp.md.

Open in Web Open in Cursor 

@changeset-bot

changeset-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 94b283c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
grok-bot-cli Minor

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

@cursor cursor Bot changed the title feat: ChatGPT Desktop CDP adapter + MCP tools (scaffold) feat: ChatGPT Desktop CDP adapter + app-server deep-read Sep 29, 2026
@cursor cursor Bot changed the title feat: ChatGPT Desktop CDP adapter + app-server deep-read feat: ChatGPT Desktop app-server list/read + modelProviders fix Sep 29, 2026
@cursor cursor Bot changed the title feat: ChatGPT Desktop app-server list/read + modelProviders fix feat: ChatGPT Desktop list merges remote-control threads Sep 29, 2026
@cursor cursor Bot changed the title feat: ChatGPT Desktop list merges remote-control threads feat: ChatGPT Desktop CDP + app-server list/read with host filters Sep 29, 2026
cursoragent and others added 16 commits September 29, 2026 01:57
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>
Rebased onto main (#114 verify retry + #96 @rstest/core 0.12.2) and
rebuilt agent-bundle artifacts so the committed snapshot matches source.

Co-authored-by: Zack Jackson <ScriptedAlchemy@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/chatgpt-desktop-cdp-scaffold-827b branch from 42e43cb to 323e8e1 Compare September 29, 2026 01:59
cursoragent and others added 8 commits September 29, 2026 02:02
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>
@ScriptedAlchemy
ScriptedAlchemy marked this pull request as ready for review September 29, 2026 05:38
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T05:42:54.192428Z dc38123 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/core/chatgpt-desktop/facade.ts Outdated
Comment on lines +408 to +414
const result = finalizeThreadList(filtered, {
limit,
host,
modelProvider: options.modelProvider?.trim() || undefined,
project: options.project,
groupBy: options.groupBy,
remotes: [],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +652 to +658
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/core/chatgpt-desktop/facade.ts Outdated
Comment on lines +229 to +230
const appList: ListThreadsResult = { backend: 'app-server', limit: appRows.length, threads: appRows };
const base = cdpList ? mergeThreadLists(appList, cdpList) : appList;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@ScriptedAlchemy
ScriptedAlchemy merged commit f581d51 into main Sep 29, 2026
2 checks passed
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.

2 participants