Detect ChatGPT's chat wall on the composer surface, roll over before Send, and rotate long convergences #67

Merged
xicv merged 31 commits from feat/exhaustion-and-rotation into main 2026-09-23 10:34:01 +00:00
xicv commented 2026-09-23 07:41:25 +00:00 (Migrated from github.com)

Summary

Stacked on #66. ChatGPT shows its conversation-length wall and usage limits on the composer surface, not in the reply, so Ego Chat could not see them. This PR reads that surface, stops before Send when a chat is already full, rolls a convergence over on a wall seen before Send, and rotates long convergences before they reach the wall.

Background, from ChatGPT's own client code (read-only, 2026-09-23): the 2026 wall sentence, "You've reached the maximum length for this conversation, but you can keep talking by starting a new chat.", is server-provided and arrives through the composer banner system. "Continue in a new chat" belongs to ChatGPT's silent context truncation, which starts far below the wall, so it is only advisory.

  • Composer-surface observer (src/composer-surface.mjs).

    • Reads only div#thread-bottom. It skips the editor, the disclaimer, message content and hidden nodes, and has no page-wide fallback.
    • Its evidence is bounded: up to 4 banner labels of 160 characters, up to 8 call-to-action labels of 80, and whether the composer accepts input.
    • Classification, in order:
      1. exhausted: a label exactly matching a wall sentence;
      2. quota: a usage-limit sentence;
      3. closed: an unusable composer next to "New chat" or "Start new chat";
      4. advisory continuationOffered: "Continue in a new chat" with a usable composer.
    • A page error classifies nothing.
  • Before Send. Two reads one second apart that agree stop the exchange before the policy menu and before anything is composed. The stop codes are chatgpt_conversation_exhausted_before_send, chatgpt_conversation_closed_before_send and chatgpt_quota_limited_before_send. They are human-only and exported in PRE_SEND_PROVEN_STOP_CODES. The first read is kept on the Send record.

  • After Send. Capture attributes a composer state to the turn only when all of these hold:

    • the prompt is the last user message;
    • no reply ended with the terminal marker;
    • nothing is generating;
    • the Send's own pre-Send read had not already seen that state;
    • a second read agrees.

    The result is providerTerminal with source composer_surface, including a new kind conversation_closed that maps to chatgpt_conversation_closed. Banner labels also feed the pending capture's statusLabels and the Jev terminal judgment.

  • Rollover on a pre-Send wall (src/conversation-continuation.mjs). A checkpoint variant without a confirmed Send hands the review, which was never delivered, to the successor unchanged. A closed chat or a usage limit pauses the convergence and never rolls over. Manual continuation is unchanged.

  • Size and proactive rotation.

    • Bindings count egoChatExchanges and egoChatBytes inside the existing head-commit write, with no extra revision.
    • Bindings and exchange results expose conversationSize with rotationRecommended, set at 120 or more messages or after a continuation offer.
    • New convergences rotate to a successor between cycles, before the review Send, at rotation.maxMessages: 120 by default, 40 to 1000, or "off". They never rotate under manual continuation.
    • Convergences started before this change never rotate. Existing workflows are not silently upgraded.
    • ego-chat-main, the long-lived main chat, defaults to rotation "off", and its rotationRecommended stays false. An explicit rotation input still applies there. The MCP schema leaves an omitted rotation to the broker, because only the broker knows this per-binding default.
  • An MCP instruction explains the _before_send codes. For a usage limit it says to wait for the reset and retry on the same binding.

Browser contract 30, runtime generation 2026-09-23.7.

Verification

  • npm test: 1188 tests, 1187 pass, 1 skipped.
  • ESLint is clean on every changed file.
  • Receipt suite: 16/16.
  • cargo test: 33 + 1.
  • Negative tests cover:
    • the safety banner: classified closed, not exhausted;
    • a usage limit: classified quota;
    • the disclaimer alone;
    • a user message quoting the wall;
    • the continue offer alone;
    • a missing #thread-bottom.

Not yet done

Not merged, released or installed.

The wall's real DOM has never been observed. These can only be confirmed live:

  • the banner markup;
  • whether its text sits in one element;
  • the exact usage-limit wording;
  • whether the calls to action are buttons or links;
  • what the editor shows at the wall.

A wall that renders differently falls through to the previous behaviour, a normal pending capture.

Known gap: a convergence paused on a closed chat or a usage limit has no resume path. The only way out is to cancel it.

## Summary Stacked on #66. ChatGPT shows its conversation-length wall and usage limits on the composer surface, not in the reply, so Ego Chat could not see them. This PR reads that surface, stops before Send when a chat is already full, rolls a convergence over on a wall seen before Send, and rotates long convergences before they reach the wall. Background, from ChatGPT's own client code (read-only, 2026-09-23): the 2026 wall sentence, "You've reached the maximum length for this conversation, but you can keep talking by starting a new chat.", is server-provided and arrives through the composer banner system. "Continue in a new chat" belongs to ChatGPT's silent context truncation, which starts far below the wall, so it is only advisory. - **Composer-surface observer** (`src/composer-surface.mjs`). - Reads only `div#thread-bottom`. It skips the editor, the disclaimer, message content and hidden nodes, and has no page-wide fallback. - Its evidence is bounded: up to 4 banner labels of 160 characters, up to 8 call-to-action labels of 80, and whether the composer accepts input. - Classification, in order: 1. **exhausted:** a label exactly matching a wall sentence; 2. **quota:** a usage-limit sentence; 3. **closed:** an unusable composer next to "New chat" or "Start new chat"; 4. **advisory `continuationOffered`:** "Continue in a new chat" with a usable composer. - A page error classifies nothing. - **Before Send.** Two reads one second apart that agree stop the exchange before the policy menu and before anything is composed. The stop codes are `chatgpt_conversation_exhausted_before_send`, `chatgpt_conversation_closed_before_send` and `chatgpt_quota_limited_before_send`. They are human-only and exported in `PRE_SEND_PROVEN_STOP_CODES`. The first read is kept on the Send record. - **After Send.** Capture attributes a composer state to the turn only when all of these hold: - the prompt is the last user message; - no reply ended with the terminal marker; - nothing is generating; - the Send's own pre-Send read had not already seen that state; - a second read agrees. The result is `providerTerminal` with source `composer_surface`, including a new kind `conversation_closed` that maps to `chatgpt_conversation_closed`. Banner labels also feed the pending capture's `statusLabels` and the Jev terminal judgment. - **Rollover on a pre-Send wall** (`src/conversation-continuation.mjs`). A checkpoint variant without a confirmed Send hands the review, which was never delivered, to the successor unchanged. A closed chat or a usage limit pauses the convergence and never rolls over. Manual continuation is unchanged. - **Size and proactive rotation.** - Bindings count `egoChatExchanges` and `egoChatBytes` inside the existing head-commit write, with no extra revision. - Bindings and exchange results expose `conversationSize` with `rotationRecommended`, set at 120 or more messages or after a continuation offer. - New convergences rotate to a successor between cycles, before the review Send, at `rotation.maxMessages`: 120 by default, 40 to 1000, or `"off"`. They never rotate under manual continuation. - Convergences started before this change never rotate. Existing workflows are not silently upgraded. - `ego-chat-main`, the long-lived main chat, defaults to rotation `"off"`, and its `rotationRecommended` stays false. An explicit `rotation` input still applies there. The MCP schema leaves an omitted `rotation` to the broker, because only the broker knows this per-binding default. - An MCP instruction explains the `_before_send` codes. For a usage limit it says to wait for the reset and retry on the same binding. Browser contract 30, runtime generation 2026-09-23.7. ## Verification - `npm test`: 1188 tests, 1187 pass, 1 skipped. - ESLint is clean on every changed file. - Receipt suite: 16/16. - `cargo test`: 33 + 1. - Negative tests cover: - the safety banner: classified closed, not exhausted; - a usage limit: classified quota; - the disclaimer alone; - a user message quoting the wall; - the continue offer alone; - a missing `#thread-bottom`. ## Not yet done Not merged, released or installed. The wall's real DOM has never been observed. These can only be confirmed live: - the banner markup; - whether its text sits in one element; - the exact usage-limit wording; - whether the calls to action are buttons or links; - what the editor shows at the wall. A wall that renders differently falls through to the previous behaviour, a normal pending capture. Known gap: a convergence paused on a closed chat or a usage limit has no resume path. The only way out is to cancel it.
Sign in to join this conversation.
No description provided.