Repository navigation
Conversation
Adds an optional pool of up to four fixed ChatGPT pages in one private Chrome profile (one action per conversation; maintenance drains every page; shared 429 back-off and Send spacing), with optional health fields describing capacity. The companion still defaults to one page. Also recovers orphaned Windows Chrome, normalizes renderer priority, waits for editor hydration instead of treating a missing editor as sign-out, journals owned unsent drafts, bounds read-only DOM observations and passive health polls, persists Retry-After across restarts, and adds optional Simplified and Bones presentation levels. Upstream's admission, pairing-window reservation, chat-mode, conversation-limit and transcript rate-limit handling are kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
devnoname120
left a comment
There was a problem hiding this comment.
Reviewed 1105978, including pooled admission/idempotency, shared navigation/Send budgets, page ownership, draft handling and packaging. Requesting changes for reproducible regressions.
The strongest blocker is the connector-selection caret regression: the existing native pairing test passes on main and fails on this head before Send. In an isolated control, restoring the caret to the composer end immediately after the new row click makes the same test pass; that temporary diagnostic change was reverted. A second native regression prematurely times out maintenance requests queued behind a healthy connector refresh. Please repair these before merging the reliability/pool changes.
Validation: bun test ./tests: 305 passed, 163 skipped, 2 failed (new package staging tests on macOS). Typecheck, build and all four boundary tests passed. The native chrome-surface and conversation-approval suites passed (44 tests); the pairing suite's first failure causes cascading failures, so I do not count the later cascade as independent defects. Separately reran the first pairing case: main passes, this head fails, the caret control passes. The account-settings suite passes all 7 tests on main; this head reproduces the same maintenance failure seen in CI. Current CI also reports two billing-plan assertion failures that must be reconciled.
Recommendation: not ready. The ownership/retained-cleanup work is substantial and has meaningful tests, but the common pairing path regresses and the native browser tests need to be green.
| const selected = await activeComposer(page, 30_000, signal); | ||
| // Click the verified app row directly. Enter in the composer can submit the | ||
| // mention as a message if React closes/rebuilds the menu between checks. | ||
| await exactRow.click({ timeout:CONNECTOR_ACTION_TIMEOUT_MS, signal }); |
There was a problem hiding this comment.
[P1] Restore the composer caret after clicking the connector row
Changing selection from keyboard confirmation to a row click leaves the editor's selection at the old query position. insertPlainTextIntoComposer preserves any collapsed selection already inside the editor, so the subsequent prompt can be inserted before the connector chip rather than at the intended end. On macOS the existing "pairing waits for enabled Send and binds to the new answer after history hydration" test now fails with external_interference before Send: the projected text is the exact prompt followed by an extra newline/space around the retained chip. The same test passes on main. As a control, moving the range to the composer end immediately after this click makes it pass on this head. Keep the click's safety benefit, but explicitly establish the insertion caret and cover this with the native pairing regression.
| queuedAbort=()=>reject(new BrowserCompanionError('Browser action was cancelled','cancelled')); | ||
| if(signal?.aborted){queuedAbort();return;} | ||
| signal?.addEventListener('abort',queuedAbort,{once:true}); | ||
| queuedTimer=setTimeout(()=>reject(new BrowserCompanionError('Browser companion is busy; a previous operation still owns the shared window')), |
There was a problem hiding this comment.
[P2] Do not expire queued maintenance before its valid predecessor finishes
The queue deadline is based on the waiting operation's timeoutMs, even when the current owner has a longer legitimate deadline. Connector refresh can hold its lease for 120 seconds, but an account-plan/reveal request queued behind it expires much earlier. The existing native account-settings scenario demonstrates this without a stuck operation: operationMs=1500, cleanupMs=100, hardStopMs=100 gives queued callers 1700 ms, while refreshDelay=2200 is valid and pendingRefresh completes normally. This head throws "a previous operation still owns the shared window"; the corresponding main test passes. Preserve bounded cancellation/abandoned-entry ordering while allowing the supported predecessor to settle, and rerun the queued plan/reveal regression.
| const root = fs.realpathSync(scratch); | ||
| const source = fs.realpathSync(unpacked); | ||
| const target = path.resolve(appRoot); | ||
| const within = candidate => { |
There was a problem hiding this comment.
[P2] Compare staging paths in a consistent real-path namespace
root and source are realpath-resolved, but target remains only path.resolve(appRoot). With a symlinked temporary directory, these name the same scratch tree using different prefixes, so within(target) rejects a valid destination. On macOS the new package-stage tests fail under the normal /var -> /private/var temporary-directory spelling: bun test ./tests reports two failures, including the basic successful-move case. Resolve the target's existing parent chain into the same namespace before the containment check while retaining the linked-parent rejection; the normal and escaped-parent tests should both pass on macOS as well as Windows.
Summary
Split out of #20, as requested. This PR covers the browser companion (TypeScript) plus the small native protocol change it needs. It is based on current
main(v2.3.29) and merged with upstream's recent companion work, so there are no conflicts.Changes
Parallel action pages (protocol)
browser-pool.tscan run up to four fixed ChatGPT pages in one private Chrome profile. Each conversation has at most one action at a time.actionCapacity,availableActionSlotsandactiveActionIds. A companion that leaves them out behaves exactly as before.GET /v1/health?ready=1waits for fresh proof that a session is idle. Ordinary health stays bounded and passive.companion.rs) validates the new fields.Windows Chrome
windows-chrome-recovery.tsrecovers an orphaned or stale Chrome left on the private profile.windows-priority.tsnormalizes renderer process priority.renderer-memory.tsbounds renderer memory on long sessions.shutdown.tsadds an orderly shutdown.process_is_aliveon Windows now waits on the process handle. Before, an exited process whose handle was still open counted as alive.Send reliability
conversation-readiness.ts: waits for ChatGPT's editor to hydrate after navigation (long chats can take a while). A missing editor is not treated as a sign-out.editor-recovery.ts: refreshes a stuck editor once before Send, if the opt-in recovery setting is enabled.draft-recovery.ts: journals our own unsent drafts, so a retry can reclaim them without discarding anything the user typed.stop-controls.ts: verifies the Stop and End-turn controls.submission-network.ts: optional network proof of a Send.approval-monitor.ts: watches for tool-approval cards.Retry-Aftersurvive a companion restart. Upstream's transcript-load rate-limit detection (conversation_load_rate_limited) is kept alongside them.Lighter, cheaper page observation
observation.tsbounds read-only DOM reads, andbrowser-window.tsadds a short-lived passive health cache. Together they stop polls from competing with an action for Chrome's main thread.minimal-page.ts,presentation*.ts: optional Simplified and Bones presentation levels (off by default).Merged with upstream
refresh-pairing,release-pairing), chat-mode compatibility, conversation-limit detection, model-picker refactor and stage diagnostics are all kept.Testing
bun test ./tests(Bun 1.4.0, Windows 11): 309 pass, 0 fail. The other 161 tests need a live Chrome and are skipped by design.tsc --noEmit: passes.cargo check --all-targetson this branch alone: passes.🤖 Generated with Claude Code