Skip to content
Open
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 README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ ChatGPT のツール呼び出しを現在の Codex タスクへ接続します

## 運用

**設定 → Pro 圧縮モデル** では、自動 Pro タスクの要約だけに Extra High、GPT-5.6 Pro、
GPT-5.5 Pro を選べます。通常のターンは元のモデルを使用し、既定値はメインタスクに従います。
モデルの提供状況とアカウント上限は引き続き適用されます。
詳しくは[圧縮モデルの選択](docs/compaction-model-selection.md)を参照してください。

安全なローカル診断には **アクティビティ**、エンドツーエンドのヘルスチェックには
**設定 → 診断を実行**を使用します。設定から、保持中のブラウザーターンのキャンセルや、
アンインストール前の Codex 統合削除も行えます。すべてのブラウザーチェックポイントでスクリーンショットが必要な場合にのみ、
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ that option clicks **Allow once**, never a permanent grant.

Use **Activity** for safe local diagnostics and **Settings → Run doctor** for end-to-end health.
Settings can also cancel a retained browser turn or remove the Codex integration before uninstall.
**Settings → Pro compaction model** optionally uses Extra High, GPT-5.6 Pro, or GPT-5.5 Pro
for automatic Pro summaries while leaving ordinary task turns unchanged. The default follows the
main task. Model availability and account limits still apply; see
[compaction model selection](docs/compaction-model-selection.md).
Set `CODEX_CHATGPT_WEB_BROWSER_DIAGNOSTICS=1` only when every browser checkpoint needs a screenshot.

New installs use **Compatibility V1** for cross-backend subagents. **Native** preserves Codex's own
Expand Down
4 changes: 4 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ bun run app

## 日常操作

**设置 → Pro 任务压缩模型** 可为自动 Pro 任务的摘要单独选择 Extra High、GPT-5.6 Pro 或
GPT-5.5 Pro,普通任务回合保持原模型。默认跟随主任务,仍受模型可用性和账户额度限制;详见
[压缩模型选择](docs/compaction-model-selection.md)。

使用 **活动** 页面查看安全的本地诊断,并通过 **设置 → 运行诊断** 执行端到端健康检查。设置页还可
取消保留的浏览器任务,或在卸载前移除 Codex 集成。仅在需要为每个浏览器检查点保存截图时设置
`CODEX_CHATGPT_WEB_BROWSER_DIAGNOSTICS=1`。
Expand Down
4 changes: 2 additions & 2 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ every later tool action in the same turn continues to present the current turn c
ChatGPT status rows become reasoning summaries, while stable prose between rows becomes native
Codex commentary.

Automatic Pro compaction can optionally use a separately selected summary model. The bridge
keeps source ownership and transaction identity tied to the original request, and passes an
execution-only policy through the helper protocol. Explicit policies require live pre-send
effort/family verification and prohibit Pro staging. The default and handoff timeouts are unchanged;
see [compaction model selection](compaction-model-selection.md).

## Installation and service lifecycle

Each native desktop package contains Electron, a platform-matched pinned Bun executable, the
Expand Down
91 changes: 91 additions & 0 deletions docs/compaction-model-selection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Optional model for Pro compaction

**Settings → Pro compaction model** changes only the summary generated when an
automatic ChatGPT Web Pro task compacts its history. Ordinary task turns keep
their original model.

| Choice | Summary execution |
| --- | --- |
| Follow main task (default) | Existing behavior; no override |
| GPT-5.6 Extra High | GPT-5.6 at Extra High effort |
| GPT-5.6 Pro | GPT-5.6 at Pro effort |
| GPT-5.5 Pro | GPT-5.5 at Pro effort |

The first request for a logical compaction pins the choice. Reconnects join that
same run even if the setting changes meanwhile. A new choice applies to the next
compaction; it does not interrupt active work, restart the service, or alter the
Codex model catalogue. Zero Risk, Luna, non-Pro tasks, and accounts without the
required controls keep their existing behavior.

## Behavior and limits

Extra High sends the summary at Extra High rather than Pro effort. The two fixed
Pro choices send the summary with the selected Pro family. ChatGPT still controls
which families and efforts are available, how usage is counted, and when limits
reset. This setting does not bypass those controls.

A different summary model can change checkpoint quality and latency. It does not
repair an expired session, browser disconnect, invalid handoff, or unavailable
model.

## Execution boundaries

1. Codex still decides when to compact. This setting does not change context or
compaction thresholds.
2. Source lookup, transaction identity, cancellation, deduplication, and
retirement remain tied to the original Pro request. Only the summary execution
receives the selected family and effort.
3. The retained-source handoff remains the primary path. If the retained source is
unavailable, the existing read-only fresh-chat fallback uses the same pinned
summary choice. Browser-only summaries follow the same rule and do not gain
ordinary Codex tools.
4. Multipart preparation for an explicit override stays below Pro effort. If one
indivisible record needs Pro's larger message envelope, the request fails before
sending instead of spending an extra Pro message during staging.
5. Immediately before submission, the bridge verifies the live family, effort,
and numeric picker position. Missing or contradictory state fails explicitly;
no other model is substituted.
6. Existing checkpoint validation, browser cleanup, and the five-minute structured
handoff liveness budget are unchanged. An accepted prompt is not retried.

## Configuration and compatibility

The optional core field is `compactionModel`, with values `extra-high`,
`5.6-pro`, or `5.5-pro`. Omitting it means follow-main. The launcher writes the
field through its authenticated control channel, and setup or application updates
preserve the rest of the existing configuration.

The runtime and browser helper must both advertise `compaction-execution`. An old
helper cannot ignore an explicit choice: the request fails before submission.
Unknown settings and inconsistent execution payloads are rejected.

The production service reads the preference for each new eligible compaction. The
isolated DEV named-chat CLI snapshots configuration at startup, so reopen that CLI
after changing the DEV preference. The launcher itself does not need to restart.

## Picker-state regression checks

The family list belongs to the composer-owned menu. Its collapsed advanced view can
remain attached and geometrically visible while `inert`, so visibility alone does
not prove that a model row is clickable. The selector expands a collapsed model
trigger before a normal actionability-checked click. It reuses a family only when
its unique exact model row is explicitly checked; a rendered 5.6 label under
Latest is not proof that 5.6 is pinned.

After changing the control, checked state and spoken family/effort each receive a
bounded, read-only settling window of up to one second. This does not retry an
accepted prompt or extend compaction's handoff budget. Immediate pre-send
verification stays strict; unavailable or contradictory state still blocks sending.

`tests/pro-model-picker-dom.test.ts` runs the actual selector against an offline
HTML fixture in a fresh headless Chromium context, including inert rows and an
unrelated duplicate model outside the owned menu. Network requests are blocked;
it neither opens a logged-in profile nor submits a ChatGPT prompt. The tests are
skipped unless an existing Chromium executable is explicitly supplied:

```bash
CHATGPT_PICKER_TEST_CHROME=/absolute/path/to/chrome bun test tests/pro-model-picker-dom.test.ts
```

These offline checks are not a substitute for an authenticated, installed
integration test on each model.
2 changes: 1 addition & 1 deletion launcher/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions launcher/electron/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,13 @@ function validateBrowserInteractionMode(value) {
return value;
}

function validateCompactionModel(value) {
if (value !== null && value !== "extra-high" && value !== "5.6-pro" && value !== "5.5-pro") {
throw new Error("Compaction model must be follow, extra-high, 5.6-pro, or 5.5-pro");
}
return value;
}

function validateBounds(value) {
if (!value || typeof value !== "object") throw new Error("Browser bounds are required");
for (const key of ["x", "y", "width", "height"]) {
Expand All @@ -430,6 +437,7 @@ function registerIpc({ logger, stateStore }) {
userData: launcherUserData,
},
state: stateStore.read(),
compactionModel: runtimeHost.compactionModel(),
browser: browserHost?.snapshot() ?? null,
connectorName: runtimeHost.browserConnectorName(),
connectorNames: {
Expand Down Expand Up @@ -819,6 +827,10 @@ function registerIpc({ logger, stateStore }) {
if (!IS_DEV_PROFILE && result.configured) startCatalogVerificationMonitor({ logger, stateStore });
return { state, credentialsRequired: false, targetMode: mode };
});
handle("launcher:compaction-model", async (_event, rawModel) => {
// Config-only: active turns and an already-started compaction keep their pinned model.
return runtimeHost.setCompactionModel(validateCompactionModel(rawModel));
});
handle("launcher:set-preference", (_event, key, value) => {
const ordinary = key === "keepRunningOnClose" || key === "showBrowserDuringTurns";
if (!ordinary) throw new Error("Unknown preference");
Expand Down
1 change: 1 addition & 0 deletions launcher/electron/preload.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ contextBridge.exposeInMainWorld("codexWebLauncher", {
setAutostart: (enabled) => ipcRenderer.invoke("launcher:autostart", enabled),
setBiggerContext: (enabled) => ipcRenderer.invoke("launcher:bigger-context", enabled),
setZeroRiskPro: (enabled) => ipcRenderer.invoke("launcher:zero-risk-pro", enabled),
setCompactionModel: (model) => ipcRenderer.invoke("launcher:compaction-model", model),
setBrowserInteractionMode: (mode) => ipcRenderer.invoke("launcher:browser-interaction-mode", mode),
setPreference: (key, value) => ipcRenderer.invoke("launcher:set-preference", key, value),
setSidebarState: (state) => ipcRenderer.invoke("launcher:sidebar-state", state),
Expand Down
6 changes: 6 additions & 0 deletions launcher/electron/runtime-supervisor.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,12 @@ function validateConfig(config, descriptorPath, platform = process.platform, lau
&& typeof config.experimentalBiggerContext !== "boolean") {
throw new Error("Runtime configuration has an invalid experimentalBiggerContext");
}
if (config.compactionModel !== undefined
&& config.compactionModel !== "extra-high"
&& config.compactionModel !== "5.6-pro"
&& config.compactionModel !== "5.5-pro") {
throw new Error("Runtime configuration has an invalid compactionModel");
}
if (config.stallTimeoutSec !== undefined
&& (!Number.isFinite(config.stallTimeoutSec) || config.stallTimeoutSec <= 0)) {
throw new Error("Runtime configuration has an invalid stallTimeoutSec");
Expand Down
37 changes: 37 additions & 0 deletions launcher/electron/runtime.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,43 @@ class RuntimeHost {
return { ...result, mode, enabled: enabled === true };
}

compactionModel() {
return this.runtimeConfigSnapshot().config?.compactionModel ?? null;
}

async setCompactionModel(value) {
if (value !== null && value !== "extra-high" && value !== "5.6-pro" && value !== "5.5-pro") {
throw new Error("Compaction model must be follow, extra-high, 5.6-pro, or 5.5-pro");
}
const current = this.runtimeConfigSnapshot();
if (!current.configured) {
throw new Error("Install the Codex integration before changing the compaction model");
}
if ((current.config?.compactionModel ?? null) === value) return { compactionModel: value };
const args = [
...(this.launcherProfile === "development" ? ["dev"] : []),
"config",
"compaction-model",
value ?? "follow",
"--launcher-control",
];
await this.run("compaction-model", args, {
...(this.launcherProfile === "development" ? {
embedded: true,
environment: this.devSetupEnvironment(),
} : {}),
env: this.launcherControlEnvironment(),
message: "Saving the Pro compaction model",
successMessage: "Pro compaction model saved for the next compaction",
timeoutMs: CORE_SETUP_TIMEOUT_MS,
});
const saved = this.compactionModel();
if (saved !== value) {
throw new Error("Runtime configuration did not persist the requested compaction model");
}
return { compactionModel: saved };
}

async setZeroRiskPro(enabled) {
const current = this.runtimeConfigSnapshot();
if (!current.configured) {
Expand Down
58 changes: 58 additions & 0 deletions launcher/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Icon, type IconName } from "./icons";
import type {
BrowserInteractionMode,
BrowserState,
CompactionModel,
DoctorReport,
Language,
LauncherSnapshot,
Expand Down Expand Up @@ -97,6 +98,10 @@ export function App() {
: current);
}, []);

const updateCompactionModel = useCallback((compactionModel: CompactionModel) => {
setSnapshot((current) => current ? { ...current, compactionModel } : current);
}, []);

if (!api) return <FatalMessage message="Launcher IPC is unavailable." />;
if (!snapshot) return <LaunchLoading />;

Expand Down Expand Up @@ -130,6 +135,7 @@ export function App() {
operation={operation}
setError={setError}
snapshot={snapshot}
updateCompactionModel={updateCompactionModel}
updateState={updateState}
/>
)}
Expand Down Expand Up @@ -330,6 +336,7 @@ function LauncherShell({
operation,
setError,
snapshot,
updateCompactionModel,
updateState,
}: {
browser: BrowserState | null;
Expand All @@ -339,6 +346,7 @@ function LauncherShell({
operation: OperationState | null;
setError: (error: string | null) => void;
snapshot: LauncherSnapshot;
updateCompactionModel: (value: CompactionModel) => void;
updateState: (state: LauncherState) => void;
}) {
const interactionSetupComplete = snapshot.state.coreSetupComplete === true
Expand Down Expand Up @@ -706,6 +714,7 @@ function LauncherShell({
language={language}
setError={setError}
snapshot={snapshot}
updateCompactionModel={updateCompactionModel}
updateState={updateState}
/>
) : null}
Expand Down Expand Up @@ -1582,6 +1591,7 @@ function SettingsSurface({
language,
setError,
snapshot,
updateCompactionModel,
updateState,
}: {
configureInteractionMode: (mode: BrowserInteractionMode) => void;
Expand All @@ -1590,6 +1600,7 @@ function SettingsSurface({
language: Language;
setError: (error: string | null) => void;
snapshot: LauncherSnapshot;
updateCompactionModel: (value: CompactionModel) => void;
updateState: (state: LauncherState) => void;
}) {
const [doctor, setDoctor] = useState<DoctorReport | null>(null);
Expand Down Expand Up @@ -1650,6 +1661,18 @@ function SettingsSurface({
setBusy(false);
}
};
const setCompactionModel = async (value: CompactionModel) => {
setBusy(true);
setError(null);
try {
const result = await api!.setCompactionModel(value);
updateCompactionModel(result.compactionModel);
} catch (cause) {
setError(messageOf(cause));
} finally {
setBusy(false);
}
};
const uninstallIntegration = async () => {
setBusy(true);
setError(null);
Expand Down Expand Up @@ -1684,6 +1707,14 @@ function SettingsSurface({
mode={snapshot.state.browserInteractionMode}
onChange={(mode) => void setInteractionMode(mode)}
/>
<SettingRow body={devProfile ? copy.devCompactionModelBody : copy.compactionModelBody} label={copy.compactionModel}>
<CompactionModelMenu
copy={copy}
disabled={busy || snapshot.state.coreSetupComplete !== true || snapshot.state.browserInteractionMode === "manual"}
onChange={(value) => void setCompactionModel(value)}
value={snapshot.compactionModel}
/>
</SettingRow>
<SettingRow body={devProfile ? copy.devKeepRunningBody : copy.keepRunningOnCloseBody} label={copy.keepRunningOnClose}>
<Switch
checked={snapshot.state.keepRunningOnClose}
Expand Down Expand Up @@ -2352,6 +2383,33 @@ function LanguageMenu({ copy, language, onChange }: { copy: Copy; language: Lang
);
}

function CompactionModelMenu({
copy,
disabled,
onChange,
value,
}: {
copy: Copy;
disabled: boolean;
onChange: (value: CompactionModel) => void;
value: CompactionModel;
}) {
return (
<select
aria-label={copy.compactionModel}
className="settings-select"
disabled={disabled}
onChange={(event) => onChange(event.target.value === "" ? null : event.target.value as CompactionModel)}
value={value ?? ""}
>
<option value="">{copy.compactionModelFollow}</option>
<option value="extra-high">{copy.compactionModelExtraHigh}</option>
<option value="5.6-pro">{copy.compactionModel56Pro}</option>
<option value="5.5-pro">{copy.compactionModel55Pro}</option>
</select>
);
}

function StateDot({ state }: { state: "idle" | "ready" | "busy" | "error" }) {
return <i aria-hidden="true" className={`state-dot is-${state}`} />;
}
Expand Down
Loading
Loading