Skip to content

Releases: spacex-3/cts-plugin

Codex Turn State Probe v0.6.3

Choose a tag to compare

@github-actions github-actions released this 22 Sep 21:22

The admission filter is gone. Any state the upstream returns is now cached and injected, and routing cookies no longer wait for a ticket.

The measurement behind this release

0.6.0–0.6.2 accepted a ticket only when its Fernet block count appeared in accepted_blocks (default [10, 12] ≈ 292/332). A 312 (11 blocks) was read as the upstream refusing the ticket we injected, and the cache entry was dropped on that very response. Three observations retired that premise:

  • The shape changes on its own. The same gateway, the same routing cookie and the same two-minute window returned 292 on one request and 312 on the next. Two gateways flipped in opposite directions within the same minute.
  • The shape is not carryable. Injecting a captured 292 back never returned that 292 — 3 pairs out of 3 changed the ticket, and the control group without any state behaved identically. Even a captured 312 came back as a different 312.
  • Length tracks whether that turn produced reasoning content. 312 responses carry a reasoning output item with an encrypted reasoning block (gAAAAA…) and 13–24 reasoning tokens; 292 responses carry none. It never tracked which gateway served the request, which is what a routing metric would have to do.

Changed in v0.6.3

  • No admission filter. accepted_blocks and target_state_length are gone, along with the "turn state rejected (length …, blocks …)" error path. That error fired on every probe while the upstream was handing back 312 everywhere, which is why probing showed Operation failed; inspect local plugin logs for details and the ticket pool stayed empty. Length and block count are still reported on the status page (chip 收票门槛: 不限) as diagnostics, never as gates.
  • Cookies no longer wait for a ticket. The interceptor used to short-circuit when the cache was empty, so a request between two successful probes carried neither the state nor the cookies that had already been captured. Now the Cookie header is built and injected independently; the injection log records the source as cookie-only so the two paths stay distinguishable.
  • invalidate_on_reject removed. It only ever fired on a shape mismatch, so it retired working tickets — and in today's upstream mode it would have invalidated on every single request. Tickets now live until ttl_seconds, and the cookie pool ages out on cookie_ttl_seconds. The combo-lifetime table keeps reporting TTL-driven expiries.
  • Retries are failure-driven. attempts_per_route and max_probe_attempts still apply, but they retry on a failed attempt (no state, non-2xx, transport error) rather than on an unexpected length. The first state that comes back wins.

Unchanged

Everything from 0.6.1 and 0.6.2 still applies: cold probes by default, the 探测凭据 chip, cookies_sent on every probe log line, the Cookie 请求头 column in the injection table, and the requirement that each Codex auth file declares "headers": { "Cookie": "$Cookie" } before the upstream receives any cookie at all — the status page banners the accounts that still lack it.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

CPA hot-reloads plugins when they are installed from the store; check the log for plugin hot reloaded and the new version number. Restart CPA only if the reload did not happen.

Codex Turn State Probe v0.6.2

Choose a tag to compare

@github-actions github-actions released this 22 Sep 07:00

Probes now go out cold by default. A probe is how a fresh ticket is minted, and it must not recycle a routing cookie captured somewhere else.

Changed in v0.6.2

  • probe_send_cookies now defaults to false. The cookie jar is keyed by account while probes rotate egresses, so a probe that carried a cookie captured on exit A while connecting from exit B asked the upstream for a ticket for a route the connection was not on — the shape that answers 312. A cold probe cannot contradict itself, and its response still hands back the fresh ticket and its cookie pair, which is captured as before whenever harvest_cookies is on. Set probe_send_cookies: true only to make probes mirror production traffic exactly.

Added in v0.6.2

  • Status page chip 探测凭据: 冷启动(不带 Cookie) or 携带 Cookie(热启动) — the first thing to look at when every proxied probe answers 312.
  • Every probe log line records cookies_sent (captured ticket, rejected ticket, direct and proxied failures), so the log states whether that attempt actually carried cookies instead of leaving it to inference. grep codex-turn-state <CPA>/logs/main.log is enough.

Unchanged

Everything from 0.6.1 still applies: cookies are captured and injected with the state, the injection table shows the Cookie 请求头 column, and the upstream only receives cookies once each Codex auth file declares "headers": { "Cookie": "$Cookie" } — the status page banners the accounts that still lack it.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

CPA hot-reloads plugins when they are installed from the store; check the log for plugin hot reloaded and the new version number. Restart CPA only if the reload did not happen.

Codex Turn State Probe v0.6.1

Choose a tag to compare

@github-actions github-actions released this 22 Sep 03:50

v0.6.0 shipped cookie injection; this release makes it visible and catches the one thing that silently cancels it.

Fixed in v0.6.1

  • Cookie injection silently did nothing — now diagnosed. CPA's Codex executor does not forward client headers upstream; it copies a fixed whitelist (x-codex-turn-state, x-codex-turn-metadata, session_id, User-Agent, Originator, ...) and Cookie is not on it. A cookie header written by the plugin was therefore dropped before the request left CPA, which is why 已注入 climbed while nothing changed. The status page now checks every Codex auth file for the one declaration that opens the path and names the accounts that lack it:

    { "access_token": "...", "headers": { "Cookie": "$Cookie" } }

    Without it the page shows an amber banner with the affected accounts, and each account card reads Cookie 转发: 未开启(注入的 Cookie 到不了上游).

Changed in v0.6.1

  • The injection table has a dedicated Cookie 请求头 column next to State 请求头, showing the cookies actually attached to that request (__cflb=***; __oailb=***) plus their age. Values stay masked.
  • The request-header JSON records the injected cookie. Previously Cookie only appeared when the inbound client request already carried one, so an injected cookie was invisible; the log now always records "Cookie": "[redacted]" when the plugin attached it.
  • Cookie 转发 is reported per account in the JSON status view as well (cookie_forward: on | missing | custom | unknown).

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

Restart CPA after updating so the new library is loaded.

Codex Turn State Probe v0.6.0

Choose a tag to compare

@github-actions github-actions released this 22 Sep 03:02

A turn state is no longer enough on its own: the routing cookies now decide how long a ticket holds.

Changed in v0.6.0

  • Default TTL is now five minutes, down from one hour. A measured 292 kept working for nine consecutive answers up to 191.7 seconds and the upstream started answering 312 at about 267 seconds, so an hour-long cache mostly meant injecting a dead ticket. ttl_seconds: 3600 restores the old value.
  • Direct probing is on by default. The direct egress is what produced accepted tickets in practice; set direct_probe: false to keep probes on the proxy pool only.
  • Probe scheduling defaults to state_aware with a 2-minute interval and a 180-second renewal lead, instead of fixed with 30-minute intervals. With a five-minute ticket, a 30-minute interval leaves long stretches of requests going out bare.
  • Probes, activation and status wording now reflect that a ticket lives minutes, not an hour.

Added in v0.6.0

  • Routing cookie support. The plugin captures __cflb and __oailb from upstream responses (production traffic and probes) and injects them together with the state. Only those two names are read, stored or forwarded; every other cookie is ignored. inject_cookies, harvest_cookies, probe_send_cookies, cookie_ttl_seconds control the behaviour, and the cookies are account-level, so one live pair is reused by every model of that account.
  • invalidate_on_reject (default on). When a request that carried our injected state comes back with a state the plugin rejects — a 312, for example — the cached entry is dropped immediately and a reprobe is queued, instead of injecting the dead ticket until the TTL expires. Bare requests cannot trigger this, so their 312s never retire a good ticket. This is the upstream telling us the ticket is dead for free, and it is more accurate than any fixed TTL.
  • Measured combo lifetime. The plugin now records how long each (ticket + cookie) pair actually survived, why it ended (upstream refusal attributed to the ticket or to the older cookie, or simply the TTL), and reports the average/minimum/last lifetime per account+model, so ttl_seconds can be tuned from data rather than guessed.
  • Injection accounting for cookies. The status page counts 带Cookie注入 next to 已注入 / 裸发 / 回票相同 / 换票, and lists the live cookie names, their age and provenance per account. Cookie values are never rendered; the JSON view exposes names, lengths and ages instead.
  • state_refresh_seconds expresses the renewal point as "ticket older than N seconds" when probe_lead_seconds is not the right knob.
  • cookie_ttl_seconds caps how long a routing cookie is kept; a shorter upstream Max-Age/Expires always wins.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

Then merge config.example.yaml into the existing CPA configuration. The new defaults apply as soon as the plugin reloads; set the old values explicitly to keep the previous behaviour.

Codex Turn State Probe v0.5.4

Choose a tag to compare

@github-actions github-actions released this 20 Sep 15:30

Keep the admission filter conservative: 312-character states stay out unless you opt in.

Changed in v0.5.4

  • accepted_blocks goes back to [10, 12] (≈292 / 332) as the default. v0.5.2 widened it to include 11 (≈312) on the strength of a third-party report that the gpt-5.6 / gpt-6 era returns 312; that claim contradicts both field observation (direct exits return 292, proxied exits return 312) and another implementation that targets these same models at 292. Since a 312 ticket could displace a known-good 292 one, the filter now rejects 312 by default and the operator opts in with accepted_blocks: [10, 11, 12].
  • The status page chip now renders the accepted shapes as 10≈292 / 12≈332 instead of bare block numbers, and the rejection message names the shape it saw, so the choice is visible rather than implicit.

Fixed in v0.5.3

  • Repeated proxy entries are no longer deduplicated. A rotating residential pool returns a new exit IP per connection, so listing the same endpoint three times means three egress slots; the plugin used to collapse them into one. Listing it once and raising attempts_per_route behaves the same way.
  • README.md, README_CN.md, and config.example.yaml now show all three accepted shapes for the proxy fields (a multi-line JSON array, a YAML list, and one entry per line in the legacy proxy string) instead of only the JSON-array form.

Fixed in v0.5.2

  • Probing no longer requires a proxy pool: with direct_probe: true and no proxies, the round now runs direct-only. Before this, an empty pool skipped probing entirely with at least one proxy is required for probing.
  • One unparsable proxy line no longer aborts the whole round. Valid entries keep working, skipped entries are logged with their line number, and the round is only skipped when nothing is usable.
  • Pasting a JSON array (the shape the config editor uses for proxies) into the legacy proxy string field now works instead of failing with proxy must be [host]:port:user:password.
  • Proxy entries without credentials are accepted: host:port and host:port:user are valid next to host:port:user:password, and passwords may contain colons.
  • accepted_blocks now defaults to [10, 11, 12]. 11 blocks ≈ 312 characters is the current full-strength shape for the gpt-5.6 / gpt-6 era, so the previous [10, 12] default rejected every probe against those models. Set [10, 12] to restore the stricter behaviour.

Added in v0.5.2

  • The status page explains plugin-raised failures instead of the generic "Operation failed; inspect local plugin logs for details": missing/unusable proxy configuration, unreachable egress, upstream HTTP status, and rejected state shapes (with the received length and block count) each get their own message.
  • Every failed probe attempt is written to the log with route, proxy_index, attempt, and the error, with proxy credentials redacted, so a failing egress can be identified without guessing.
  • The status page shows the accepted block counts as its own chip (also accepted_blocks in the JSON view).

Fixed in v0.5.1

  • attempts_per_route is now declared in the plugin registration metadata, so it appears in the CPA plugin configuration editor instead of only in config.yaml.
  • The status page shows the per-route attempt budget as its own chip.
  • The direct_probe description now matches the actual behaviour: an accepted direct state is cached and ends the round rather than only being logged.

Added in v0.5.0

  • The status page and JSON now count, per account+model, requests that carried a cached ticket (injections), requests that passed every gate but left with no state (bare_requests), and whether a harvested response handed back the ticket already held (ticket_echoes) or a different one (ticket_changes). A non-zero bare_requests is the visible signal that injection is silently failing open, and the echo/change split shows whether the upstream returns the ticket it was given or reissues one per turn.
  • The counters are persisted in runtime.json next to cached states, probe logs, and injection records, so a reload keeps the history.
  • attempts_per_route (default 1, maximum 10): retry the direct route or a single proxy before the probe moves on to the next egress, inside the max_probe_attempts budget. Raise it when one attempt per egress keeps returning a rejected state length. The default keeps the previous rotation.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

Then merge config.example.yaml into the existing CPA configuration.

Codex Turn State Probe v0.5.3

Choose a tag to compare

@github-actions github-actions released this 20 Sep 15:19

Keep repeated egresses in the proxy list, and document how the proxy fields are filled.

Fixed in v0.5.3

  • Repeated proxy entries are no longer deduplicated. A rotating residential pool returns a new exit IP per connection, so listing the same endpoint three times means three egress slots; the plugin used to collapse them into one. Listing it once and raising attempts_per_route behaves the same way.
  • README.md, README_CN.md, and config.example.yaml now show all three accepted shapes for the proxy fields (a multi-line JSON array, a YAML list, and one entry per line in the legacy proxy string) instead of only the JSON-array form.

Fixed in v0.5.2

  • Probing no longer requires a proxy pool: with direct_probe: true and no proxies, the round now runs direct-only. Before this, an empty pool skipped probing entirely with at least one proxy is required for probing.
  • One unparsable proxy line no longer aborts the whole round. Valid entries keep working, skipped entries are logged with their line number, and the round is only skipped when nothing is usable.
  • Pasting a JSON array (the shape the config editor uses for proxies) into the legacy proxy string field now works instead of failing with proxy must be [host]:port:user:password.
  • Proxy entries without credentials are accepted: host:port and host:port:user are valid next to host:port:user:password, and passwords may contain colons.
  • accepted_blocks now defaults to [10, 11, 12]. 11 blocks ≈ 312 characters is the current full-strength shape for the gpt-5.6 / gpt-6 era, so the previous [10, 12] default rejected every probe against those models. Set [10, 12] to restore the stricter behaviour.

Added in v0.5.2

  • The status page explains plugin-raised failures instead of the generic "Operation failed; inspect local plugin logs for details": missing/unusable proxy configuration, unreachable egress, upstream HTTP status, and rejected state shapes (with the received length and block count) each get their own message.
  • Every failed probe attempt is written to the log with route, proxy_index, attempt, and the error, with proxy credentials redacted, so a failing egress can be identified without guessing.
  • The status page shows the accepted block counts as its own chip (also accepted_blocks in the JSON view).

Fixed in v0.5.1

  • attempts_per_route is now declared in the plugin registration metadata, so it appears in the CPA plugin configuration editor instead of only in config.yaml.
  • The status page shows the per-route attempt budget as its own chip.
  • The direct_probe description now matches the actual behaviour: an accepted direct state is cached and ends the round rather than only being logged.

Added in v0.5.0

  • The status page and JSON now count, per account+model, requests that carried a cached ticket (injections), requests that passed every gate but left with no state (bare_requests), and whether a harvested response handed back the ticket already held (ticket_echoes) or a different one (ticket_changes). A non-zero bare_requests is the visible signal that injection is silently failing open, and the echo/change split shows whether the upstream returns the ticket it was given or reissues one per turn.
  • The counters are persisted in runtime.json next to cached states, probe logs, and injection records, so a reload keeps the history.
  • attempts_per_route (default 1, maximum 10): retry the direct route or a single proxy before the probe moves on to the next egress, inside the max_probe_attempts budget. Raise it when one attempt per egress keeps returning a rejected state length. The default keeps the previous rotation.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

Then merge config.example.yaml into the existing CPA configuration.

Codex Turn State Probe v0.5.2

Choose a tag to compare

@github-actions github-actions released this 20 Sep 14:59

Let an empty proxy pool mean "direct only", and stop hiding why a probe failed.

Fixed in v0.5.2

  • Probing no longer requires a proxy pool: with direct_probe: true and no proxies, the round now runs direct-only. Before this, an empty pool skipped probing entirely with at least one proxy is required for probing.
  • One unparsable proxy line no longer aborts the whole round. Valid entries keep working, skipped entries are logged with their line number, and the round is only skipped when nothing is usable.
  • Pasting a JSON array (the shape the config editor uses for proxies) into the legacy proxy string field now works instead of failing with proxy must be [host]:port:user:password.
  • Proxy entries without credentials are accepted: host:port and host:port:user are valid next to host:port:user:password, and passwords may contain colons.
  • accepted_blocks now defaults to [10, 11, 12]. 11 blocks ≈ 312 characters is the current full-strength shape for the gpt-5.6 / gpt-6 era, so the previous [10, 12] default rejected every probe against those models. Set [10, 12] to restore the stricter behaviour.

Added in v0.5.2

  • The status page explains plugin-raised failures instead of the generic "Operation failed; inspect local plugin logs for details": missing/unusable proxy configuration, unreachable egress, upstream HTTP status, and rejected state shapes (with the received length and block count) each get their own message.
  • Every failed probe attempt is written to the log with route, proxy_index, attempt, and the error, with proxy credentials redacted, so a failing egress can be identified without guessing.
  • The status page shows the accepted block counts as its own chip (also accepted_blocks in the JSON view).

Fixed in v0.5.1

  • attempts_per_route is now declared in the plugin registration metadata, so it appears in the CPA plugin configuration editor instead of only in config.yaml.
  • The status page shows the per-route attempt budget as its own chip.
  • The direct_probe description now matches the actual behaviour: an accepted direct state is cached and ends the round rather than only being logged.

Added in v0.5.0

  • The status page and JSON now count, per account+model, requests that carried a cached ticket (injections), requests that passed every gate but left with no state (bare_requests), and whether a harvested response handed back the ticket already held (ticket_echoes) or a different one (ticket_changes). A non-zero bare_requests is the visible signal that injection is silently failing open, and the echo/change split shows whether the upstream returns the ticket it was given or reissues one per turn.
  • The counters are persisted in runtime.json next to cached states, probe logs, and injection records, so a reload keeps the history.
  • attempts_per_route (default 1, maximum 10): retry the direct route or a single proxy before the probe moves on to the next egress, inside the max_probe_attempts budget. Raise it when one attempt per egress keeps returning a rejected state length. The default keeps the previous rotation.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

Then merge config.example.yaml into the existing CPA configuration.

Codex Turn State Probe v0.5.1

Choose a tag to compare

@github-actions github-actions released this 20 Sep 11:33

Report what turn-state injection actually does per account, and let one probe egress retry before the rotation moves on.

Fixed in v0.5.1

  • attempts_per_route is now declared in the plugin registration metadata, so it appears in the CPA plugin configuration editor instead of only in config.yaml.
  • The status page shows the per-route attempt budget as its own chip.
  • The direct_probe description now matches the actual behaviour: an accepted direct state is cached and ends the round rather than only being logged.

Added in v0.5.0

  • The status page and JSON now count, per account+model, requests that carried a cached ticket (injections), requests that passed every gate but left with no state (bare_requests), and whether a harvested response handed back the ticket already held (ticket_echoes) or a different one (ticket_changes). A non-zero bare_requests is the visible signal that injection is silently failing open, and the echo/change split shows whether the upstream returns the ticket it was given or reissues one per turn.
  • The counters are persisted in runtime.json next to cached states, probe logs, and injection records, so a reload keeps the history.
  • attempts_per_route (default 1, maximum 10): retry the direct route or a single proxy before the probe moves on to the next egress, inside the max_probe_attempts budget. Raise it when one attempt per egress keeps returning a rejected state length. The default keeps the previous rotation.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

Then merge config.example.yaml into the existing CPA configuration.

Codex Turn State Probe v0.5.0

Choose a tag to compare

@github-actions github-actions released this 20 Sep 11:13

Report what turn-state injection actually does per account, and let one probe egress retry before the rotation moves on.

Added in v0.5.0

  • The status page and JSON now count, per account+model, requests that carried a cached ticket (injections), requests that passed every gate but left with no state (bare_requests), and whether a harvested response handed back the ticket already held (ticket_echoes) or a different one (ticket_changes). A non-zero bare_requests is the visible signal that injection is silently failing open, and the echo/change split shows whether the upstream returns the ticket it was given or reissues one per turn.
  • The counters are persisted in runtime.json next to cached states, probe logs, and injection records, so a reload keeps the history.
  • attempts_per_route (default 1, maximum 10): retry the direct route or a single proxy before the probe moves on to the next egress, inside the max_probe_attempts budget. Raise it when one attempt per egress keeps returning a rejected state length. The default keeps the previous rotation.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

Then merge config.example.yaml into the existing CPA configuration.

Codex Turn State Probe v0.4.5

Choose a tag to compare

@github-actions github-actions released this 19 Sep 14:12

Fix Codex probe requests that were rejected with Unsupported parameter: max_output_tokens.

Fixed in v0.2.1

  • Stop sending max_output_tokens to the native Codex Responses endpoint.
  • Match CPA's Codex request normalization by including parallel_tool_calls and reasoning.encrypted_content.
  • Keep max_output_tokens as an ignored compatibility setting so existing configurations continue to load.
  • Continue to support provider-style SOCKS5 syntax, direct baselines, and per-attempt state logs from v0.2.0.

Install with CPA

Add this URL to plugins.store-sources, refresh the plugin store, and install Codex Turn State Probe:

https://raw.githubusercontent.com/spacex-3/cts-plugin/main/registry.json

Then merge config.example.yaml into the existing CPA configuration.