Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@ tests/fixtures/oracle-0.17.0/promptComposer.js text eol=lf
tests/fixtures/oracle-0.17.1/thinkingTime.pristine.js text eol=lf
bin/oracle-compat/0.17.2/*.patch text eol=lf whitespace=-blank-at-eol
tests/fixtures/oracle-0.17.2/thinkingTime.pristine.js text eol=lf
bin/oracle-compat/0.17.3/*.patch text eol=lf whitespace=-blank-at-eol
tests/fixtures/oracle-0.17.3/assistantResponse.pristine.js text eol=lf
tests/fixtures/oracle-0.17.3/browserConfig.pristine.js text eol=lf
tests/fixtures/oracle-0.17.3/browserIndex.pristine.js text eol=lf
tests/fixtures/oracle-0.17.3/thinkingTime.pristine.js text eol=lf
2 changes: 1 addition & 1 deletion .github/workflows/upstream-drift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Summary
if: always()
run: cat upstream-drift.json >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@v7
if: always()
with:
name: upstream-drift
Expand Down
12 changes: 8 additions & 4 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ A Windows automation toolkit that delegates planning, research, review, code
changes, and testing to web ChatGPT while keeping local Codex work focused on
transport, recovery, identity, hashes, and the final deterministic gate.

The current release is `1.8.0`.
The current release is `1.8.1`.

It connects two upstream tools:

Expand Down Expand Up @@ -105,10 +105,14 @@ backpressure queue: four by default, configurable from 1 through 20 with
- An Oracle browser profile signed in to ChatGPT
- One manually registered DevSpace app in ChatGPT Developer Mode

The validated combination is Oracle `0.17.2` and DevSpace `1.0.7`. The installer
The validated combination is Oracle `0.17.3` and DevSpace `1.0.7`. The installer
applies Windows compatibility patches only when exact upstream file hashes
match. Oracle `0.16.1`, `0.17.0`, and `0.17.1` remain available only for exact
recovery of runs already persisted with those versions.
match. Oracle 0.17.3's upstream answer-placeholder bounding, manual-login
reattach cookie-sync opt-in, Japanese picker labels, and explicit headless
handling are preserved under the local hash-gated patches; live browser
validation is not yet performed. Oracle `0.16.1`, `0.17.0`, `0.17.1`, and
`0.17.2` remain available only for exact recovery of runs already persisted
with those versions.

## Install

Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Codex가 웹 ChatGPT에 계획·리서치·검토·코드 구현을 맡기고, 로컬 Codex는
제출·복구·해시·최종 테스트만 담당하도록 만드는 Windows용 자동화 도구입니다.

현재 릴리스는 `1.8.0`입니다.
현재 릴리스는 `1.8.1`입니다.

이 프로젝트는 다음 두 도구를 연결합니다.

Expand Down Expand Up @@ -101,10 +101,13 @@ SHA-256을 하위 프로세스 시작 전에 검사합니다. 작업 상태는
- 브라우저에서 ChatGPT에 로그인된 Oracle 프로필
- ChatGPT Developer Mode에 최초 한 번 수동 등록한 DevSpace 앱

현재 검증된 조합은 Oracle `0.17.2`와 DevSpace `1.0.7`입니다. 설치기는
현재 검증된 조합은 Oracle `0.17.3`와 DevSpace `1.0.7`입니다. 설치기는
정확한 파일 해시가 일치할 때만 Windows 호환 패치를 적용합니다. Oracle
`0.16.1`, `0.17.0`, `0.17.1`은 이미 저장된 해당 버전 실행의 정확한 복구에만
사용할 수 있습니다.
0.17.3의 상위 변경(답변 placeholder 제한, manual-login 재연결의 명시적
쿠키 동기화 opt-in, 일본어 picker 라벨, 명시적 headless 처리)은 로컬
hash-gated 패치 아래 그대로 보존하며 아직 라이브 브라우저 검증은 하지
않았습니다. Oracle `0.16.1`, `0.17.0`, `0.17.1`, `0.17.2`는 이미 저장된
해당 버전 실행의 정확한 복구에만 사용할 수 있습니다.

## 설치

Expand Down
11 changes: 6 additions & 5 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

This repository ships no copy of the Oracle package, DevSpace, Codex, browser
binaries, or account data. It ships narrow textual compatibility patches for
the active hash-verified Oracle 0.17.2 and DevSpace 1.0.7 npm distributions.
Oracle 0.16.1, 0.17.0, and 0.17.1 patch instructions remain only for exact
recovery of runs persisted with those versions.
the active hash-verified Oracle 0.17.3 and DevSpace 1.0.7 npm distributions.
Oracle 0.16.1, 0.17.0, 0.17.1, and 0.17.2 patch instructions remain only for
exact recovery of runs persisted with those versions.

- `hehee9/multi-gpt@4f5e130` is MIT-licensed. Its attribution and the recorded `server.mjs` hash must be preserved when its upstream-compatible integration is changed.
- `@steipete/oracle` is an external MIT-licensed browser automation package.
The active tested version is 0.17.2. Versions 0.16.1, 0.17.0, and 0.17.1 are
retained only to recover exact persisted runs. Its package source is not vendored.
The active tested version is 0.17.3. Versions 0.16.1, 0.17.0, 0.17.1, and
0.17.2 are retained only to recover exact persisted runs. Its package source
is not vendored.
Files under `bin/oracle-compat` are derivative patch instructions and retain the
following upstream MIT notice.
- `@waishnav/devspace` is an external MIT-licensed MCP workspace server. The
Expand Down
48 changes: 46 additions & 2 deletions bin/chatgpt_oracle_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
from pathlib import Path
from typing import Any, Sequence

SUPPORTED_VERSION = "0.17.2"
RECOVERABLE_VERSIONS = ("0.16.1", "0.17.0", "0.17.1", SUPPORTED_VERSION)
SUPPORTED_VERSION = "0.17.3"
RECOVERABLE_VERSIONS = ("0.16.1", "0.17.0", "0.17.1", "0.17.2", SUPPORTED_VERSION)
CREATE_NO_WINDOW = 0x08000000
PATCHES_0161 = {
"dist/src/cli/browserTabs.js": {
Expand Down Expand Up @@ -343,11 +343,55 @@
},
}

PATCHES_0173 = {
"dist/src/browser/chromeLifecycle.js": {
"patch": "chromeLifecycle.patch",
"pristine": "312b45c44d4cd69a3a057e7bd1584b58182b4b37bc88f6ce6c7d11e216267c81",
"patched": "61440e467d51031efb7bfc319aef05de7c9061585e5eec148d0e353938eb2093",
},
"dist/src/browser/recoverConversation.js": {
"patch": "recoverConversation.patch",
"pristine": "d7e39d21acf07e6d227e761944519e11cd8d93930629cc87555d7de75a42d1ca",
"patched": "cc2a036f6e2409ae7edceee1f381a5062cd6cc5cd1618af465a1b384081ed69e",
},
"dist/src/browser/profileCopy.js": {
"patch": "profileCopy.patch",
"pristine": "06c692861f8a4c1a8769f957b9c582426a13bf4972262c47c1f24a87b239064f",
"patched": "71459a25b7c46f57bae6f23a5498301f6f6a1d39addf0c1cd4eee1d99b03372c",
},
"dist/src/cli/browserConfig.js": {
"patch": "browserConfig.patch",
"pristine": "13b304a1b41cbc85257d9340a620bccd4d18bc52a36285ba46c2f72af84f0f84",
"patched": "a76f338e1afb3573c3436cd261ccbcefacd9c879c71a45e110cf7a3602a06d22",
},
"dist/src/browser/index.js": {
"patch": "browserIndex.patch",
"pristine": "421f15c6693799571d586d80b7fc35b10492a63acf78d901e21786bf6ec71a90",
"patched": "cb7b828902163bac941f5890f78edd136cf723e17e262c1347e2843df20c3e44",
},
"dist/src/browser/actions/assistantResponse.js": {
"patch": "assistantResponse.patch",
"pristine": "93d2465ed7dce43d8093a91bada7656bc9ba7ba3729d2fcc43229fa8aa6e36de",
"patched": "aff8f7cb4e926b0e56c4b02456f54983b14fffa9e01f595fed4fd44a338d41f4",
},
"dist/src/browser/actions/promptComposer.js": {
"patch": "promptComposer.patch",
"pristine": "db090a5fb6d13c4c88a68b5e474a53a19c3857295a64c3ba4a0eef1868d06000",
"patched": "3767d8a6702e42191e8195641ad2f0834882bed9cda1362a723c906249402d96",
},
"dist/src/browser/actions/thinkingTime.js": {
"patch": "thinkingTime.strict.patch",
"pristine": "6ff4420e81570f6c0a4e277bdd993caf66739c3f633a7cdb733ed645bec2acda",
"patched": "98724eaf24e27d6f75b3eb7795c49650aee0be6a9a3698e09882c6d3a06c3185",
},
}

VERSION_PATCHES = {
"0.16.1": PATCHES_0161,
"0.17.0": PATCHES_0170,
"0.17.1": PATCHES_0171,
"0.17.2": PATCHES_0172,
"0.17.3": PATCHES_0173,
}
# Active-version alias retained for existing callers and tests.
PATCHES = VERSION_PATCHES[SUPPORTED_VERSION]
Expand Down
2 changes: 1 addition & 1 deletion bin/chatgpt_oracle_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
REGULAR_THINKING_TIME = "extra-high"
DEVSPACE_APP_NAME = "DevSpace"
# Current ChatGPT exposes Pro as the maximum effort for GPT-5.6 Sol, not as a
# separate model row. Oracle 0.17.2 verifies that Pro effort independently
# separate model row. Oracle 0.17.3 verifies that Pro effort independently
# (Power 5 of 5 proof); `heavy` is only Oracle's compatibility token for it.
PRO_MODEL = "gpt-5.6-sol"
PRO_COMPOSER_PROMPT = "Read the attached prompt/instructions and all attached files, then complete the task."
Expand Down
74 changes: 60 additions & 14 deletions bin/chatgpt_oracle_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,22 @@
REGULAR_MODEL = "gpt-5.6"
REGULAR_MODEL_STRATEGY = "select"
REGULAR_THINKING_TIME = "extra-high"
ORACLE_ACTIVE_VERSION = "0.17.2"
ORACLE_RECOVERABLE_VERSIONS = ("0.16.1", "0.17.0", "0.17.1", ORACLE_ACTIVE_VERSION)
ORACLE_ACTIVE_VERSION = "0.17.3"
ORACLE_RECOVERABLE_VERSIONS = ("0.16.1", "0.17.0", "0.17.1", "0.17.2", ORACLE_ACTIVE_VERSION)
WAIT_CAPABLE_VERSIONS = {"0.17.0", "0.17.1", ORACLE_ACTIVE_VERSION}
ORACLE_UI_FAILURE_SETTLEMENT_VERSIONS = {"0.17.1", "0.17.2"}
ORACLE_UI_FAILURE_SETTLEMENT_VERSIONS = {"0.17.1", "0.17.2", ORACLE_ACTIVE_VERSION}
# Each strict pre-submit proof binds markers emitted by exactly two runtime
# generations: 0.17.2 (the previous active runtime, now exact-recovery-only)
# and the current active runtime. These are explicit per-marker sets on
# purpose — never an ORACLE_ACTIVE_VERSION alias, or promotion silently drops
# stored 0.17.2 runs from settlement. Older runtimes remain deliberately
# excluded because these marker-specific recovery proofs did not bind them
# before the 0.17.3 promotion.
ORACLE_APP_MENTION_ROUTE_UNCONFIRMED_PROOF_VERSIONS = {"0.17.2", ORACLE_ACTIVE_VERSION}
ORACLE_THINKING_TIME_STRICT_PROOF_VERSIONS = {"0.17.2", ORACLE_ACTIVE_VERSION}
ORACLE_MODEL_SWITCHER_PROOF_VERSIONS = {"0.17.2", ORACLE_ACTIVE_VERSION}
ORACLE_COPY_PROFILE_MANUAL_LOGIN_CONFLICT_PROOF_VERSIONS = {"0.17.2", ORACLE_ACTIVE_VERSION}
ORACLE_PROFILE_COPY_RSYNC_MISSING_PROOF_VERSIONS = {"0.17.2", ORACLE_ACTIVE_VERSION}
ORACLE_PACKAGE = "@steipete/oracle"
STATE_SCHEMA = "codex.chatgpt.oracle-run-state/v1"
STATUSES = {"prepared", "running", "complete", "failed", "attention_required", "abandoned"}
Expand Down Expand Up @@ -1073,6 +1085,9 @@ def _comprehensive_no_submission_evidence(state_path: Path) -> dict[str, Any] |
only proves that the run is eligible for an explicit user adjudication: no
output or conversation URL exists, Oracle reported an eligible composer
failure, and exact recovery has neither a live tab nor a saved URL.
The APP_MENTION_ROUTE_UNCONFIRMED branch binds markers emitted by the
0.17.2 runtime (exact-recovery-only) and the active runtime; the plain
prompt-timeout message is version-agnostic.
"""
state = load_state(state_path)
authority = str(state.get("session_authority") or "")
Expand Down Expand Up @@ -1111,7 +1126,8 @@ def _comprehensive_no_submission_evidence(state_path: Path) -> dict[str, Any] |
app_route_unconfirmed = (
str(state.get("transport") or "").casefold() == "devspace"
and str(state.get("app_name") or "").casefold() == "devspace"
and normalize_oracle_version(oracle.get("resolved_version")) == ORACLE_ACTIVE_VERSION
and normalize_oracle_version(oracle.get("resolved_version"))
in ORACLE_APP_MENTION_ROUTE_UNCONFIRMED_PROOF_VERSIONS
and {
f"ERROR: {ORACLE_APP_MENTION_ROUTE_UNCONFIRMED_MARKER}",
(
Expand Down Expand Up @@ -1471,7 +1487,12 @@ def _settlement_logs_have_conversation_url(state_path: Path) -> bool:


def _direct_app_route_no_submission_evidence(state_path: Path) -> dict[str, Any] | None:
"""Bind an exact direct pre-send app-route rejection to user adjudication."""
"""Bind an exact direct pre-send app-route rejection to user adjudication.

The APP_MENTION_ROUTE_UNCONFIRMED marker is bound only for the 0.17.2
runtime (exact-recovery-only) and the active runtime; older runtimes stay
fail-closed because their promptComposer patches were never bound here.
"""
state = load_state(state_path)
if (
str(state.get("session_authority") or "") not in {"submitted_unknown", "pre_submit"}
Expand Down Expand Up @@ -1513,7 +1534,8 @@ def _direct_app_route_no_submission_evidence(state_path: Path) -> dict[str, Any]
locator = str(oracle.get("session_locator") or oracle.get("slug") or "").strip()
stdout_lines = {line.strip() for line in stdout_text.splitlines()}
if (
normalize_oracle_version(oracle.get("resolved_version")) != ORACLE_ACTIVE_VERSION
normalize_oracle_version(oracle.get("resolved_version"))
not in ORACLE_APP_MENTION_ROUTE_UNCONFIRMED_PROOF_VERSIONS
or not locator
or f"Session: {locator}" not in stdout_text
or {
Expand Down Expand Up @@ -2172,11 +2194,13 @@ def proven_pre_submit_thinking_time_failure(state_path: Path) -> dict[str, Any]
"""Prove the final strict Power-slider selector refused before send.

Accepts only Oracle's exact selection-unverified/unknown-outcome
diagnostics from the active thinking-time patch, with the configured
diagnostics from the strict thinking-time patch, with the configured
Oracle effort mapped to its exact visible Power tier, and only while the
exact conversation URL and any durable output are absent. Anything else
keeps submitted-unknown ownership and therefore the project lock
fail-closed.
fail-closed. These markers are bound only for the 0.17.2 runtime
(exact-recovery-only) and the active runtime, which are the two
generations that shipped the strict patch.
"""
state = load_state(state_path)
if str(state.get("session_authority") or "") not in {"pre_submit", "submitted_unknown"}:
Expand Down Expand Up @@ -2220,7 +2244,11 @@ def proven_pre_submit_thinking_time_failure(state_path: Path) -> dict[str, Any]
return None
oracle = state.get("oracle") if isinstance(state.get("oracle"), dict) else {}
locator = str(oracle.get("session_locator") or oracle.get("slug") or "").strip()
if not locator or normalize_oracle_version(oracle.get("resolved_version")) != ORACLE_ACTIVE_VERSION:
if (
not locator
or normalize_oracle_version(oracle.get("resolved_version"))
not in ORACLE_THINKING_TIME_STRICT_PROOF_VERSIONS
):
return None
return {
"schema": "codex.chatgpt.oracle-pre-submit-ui-failure/v1",
Expand All @@ -2242,6 +2270,8 @@ def proven_pre_submit_model_switcher_failure(state_path: Path) -> dict[str, Any]
diagnostic, with both output and conversation evidence absent. A generic
browser error, a recorded conversation URL, or any durable output remains
submitted-unknown and therefore keeps the project lock fail-closed.
The diagnostic is bound only for the 0.17.2 runtime (exact-recovery-only)
and the active runtime, which both ship the model-switcher error path.
"""
state = load_state(state_path)
if str(state.get("session_authority") or "") not in {"pre_submit", "submitted_unknown"}:
Expand All @@ -2263,7 +2293,11 @@ def proven_pre_submit_model_switcher_failure(state_path: Path) -> dict[str, Any]
return None
oracle = state.get("oracle") if isinstance(state.get("oracle"), dict) else {}
locator = str(oracle.get("session_locator") or oracle.get("slug") or "").strip()
if not locator or normalize_oracle_version(oracle.get("resolved_version")) != ORACLE_ACTIVE_VERSION:
if (
not locator
or normalize_oracle_version(oracle.get("resolved_version"))
not in ORACLE_MODEL_SWITCHER_PROOF_VERSIONS
):
return None
return {
"schema": "codex.chatgpt.oracle-pre-submit-ui-failure/v1",
Expand All @@ -2278,7 +2312,12 @@ def proven_pre_submit_model_switcher_failure(state_path: Path) -> dict[str, Any]


def proven_pre_submit_copy_profile_manual_login_conflict(state_path: Path) -> dict[str, Any] | None:
"""Prove Oracle rejected mutually exclusive profile modes before browser launch."""
"""Prove Oracle rejected mutually exclusive profile modes before browser launch.

The conflict marker is bound only for the 0.17.2 runtime (exact-recovery-
only) and the active runtime, which both ship the browserIndex conflict
guard; older runtimes never emitted it.
"""
state = load_state(state_path)
if str(state.get("session_authority") or "") not in {"pre_submit", "submitted_unknown"}:
return None
Expand All @@ -2293,7 +2332,8 @@ def proven_pre_submit_copy_profile_manual_login_conflict(state_path: Path) -> di
stderr_record = _artifact_bytes(state, "stderr")
if (
not copy_profile
or str(oracle.get("resolved_version") or "").removeprefix("oracle ").strip() != ORACLE_ACTIVE_VERSION
or normalize_oracle_version(oracle.get("resolved_version"))
not in ORACLE_COPY_PROFILE_MANUAL_LOGIN_CONFLICT_PROOF_VERSIONS
or output_is_nonempty(output)
or stdout_record is None
or stderr_record is None
Expand All @@ -2319,7 +2359,12 @@ def proven_pre_submit_copy_profile_manual_login_conflict(state_path: Path) -> di


def proven_pre_submit_profile_copy_rsync_missing(state_path: Path) -> dict[str, Any] | None:
"""Prove profile copy failed before Chrome because Oracle invoked rsync."""
"""Prove profile copy failed before Chrome because Oracle invoked rsync.

The rsync-spawn marker is bound only for the 0.17.2 runtime (exact-
recovery-only) and the active runtime, which both ship the profileCopy
rsync spawn; older runtimes never emitted it.
"""
state = load_state(state_path)
if str(state.get("session_authority") or "") not in {"pre_submit", "submitted_unknown"}:
return None
Expand All @@ -2334,7 +2379,8 @@ def proven_pre_submit_profile_copy_rsync_missing(state_path: Path) -> dict[str,
stderr_record = _artifact_bytes(state, "stderr")
if (
not copy_profile
or str(oracle.get("resolved_version") or "").removeprefix("oracle ").strip() != ORACLE_ACTIVE_VERSION
or normalize_oracle_version(oracle.get("resolved_version"))
not in ORACLE_PROFILE_COPY_RSYNC_MISSING_PROOF_VERSIONS
or output_is_nonempty(output)
or stdout_record is None
or stderr_record is None
Expand Down
14 changes: 14 additions & 0 deletions bin/oracle-compat/0.17.3/assistantResponse.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
diff --git a/dist/src/browser/actions/assistantResponse.js b/dist/src/browser/actions/assistantResponse.js
index c0b9f0a..8967e9e 100644
--- a/dist/src/browser/actions/assistantResponse.js
+++ b/dist/src/browser/actions/assistantResponse.js
@@ -230,7 +230,8 @@ export async function waitForAssistantResponse(Runtime, timeoutMs, logger, minTu
throw error;
}
else if (source === "evaluation") {
- const recovered = await recoverAssistantResponse(Runtime, timeoutMs, logger, minTurnIndex, expectedConversationId);
+ const remainingMs = Math.max(0, timeoutMs - (Date.now() - start));
+ const recovered = await recoverAssistantResponse(Runtime, remainingMs, logger, minTurnIndex, expectedConversationId);
if (recovered) {
return recovered;
}
15 changes: 15 additions & 0 deletions bin/oracle-compat/0.17.3/browserConfig.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
diff --git a/dist/src/cli/browserConfig.js b/dist/src/cli/browserConfig.js
index 3b16fe5..173c898 100644
--- a/dist/src/cli/browserConfig.js
+++ b/dist/src/cli/browserConfig.js
@@ -73,7 +73,9 @@ export async function buildBrowserConfig(options) {
throw new Error("--copy-profile cannot be combined with --browser-keep-browser: the copied profile is a throwaway that is deleted after the run, so it must not be retained.");
}
if (options.copyProfile && options.browserManualLogin) {
- throw new Error("--copy-profile cannot be combined with --browser-manual-login: choose either a throwaway copied profile or the persistent manual-login profile.");
+ // Windows defaults to the persistent manual-login profile. An explicit
+ // copy-profile request is the isolation authority for this run.
+ options.browserManualLogin = false;
}
if (options.copyProfile && options.remoteChrome) {
throw new Error("--copy-profile cannot be combined with --remote-chrome: copied profiles require a locally launched Chrome instance.");
Loading