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
2 changes: 1 addition & 1 deletion apps/codex-plus-manager/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3522,7 +3522,7 @@ function EnhanceScreen({
<FeatureToggle title={t("切换对话保留位置")} detail={t("切换 thread 时恢复上一次浏览位置。")} checked={form.codexAppThreadScrollRestore} disabled={!masterEnabled} onChange={(value) => setEnhanceFlag("codexAppThreadScrollRestore", value)} />
</FeatureGroup>
<FeatureGroup title="Stepwise" detail={t("基于当前对话生成下一步建议,使用独立 API 配置。")}>
<FeatureToggle title="Stepwise" detail={t("在 Codex 页面显示可拖动的后续建议浮层;建议由单独配置的 Stepwise API 生成。")} checked={form.codexAppStepwiseEnabled} disabled={!masterEnabled} onChange={(value) => setEnhanceFlag("codexAppStepwiseEnabled", value)} />
<FeatureToggle title="Stepwise" detail={t("在 Codex 页面显示可拖动的后续建议浮层;建议由单独配置的 Stepwise API 生成。启停后需重启 Codex++ 生效。")} checked={form.codexAppStepwiseEnabled} disabled={!masterEnabled} onChange={(value) => setEnhanceFlag("codexAppStepwiseEnabled", value)} />
<FeatureToggle title={t("Stepwise 直接发送")} detail={t("点击建议后自动发送;关闭时只填入输入框。")} checked={form.codexAppStepwiseDirectSend} disabled={!masterEnabled || !form.codexAppStepwiseEnabled} onChange={(value) => setEnhanceFlag("codexAppStepwiseDirectSend", value)} />
</FeatureGroup>
<FeatureGroup title={t("界面与启动")} detail={t("控制语言、启动速度和 Codex 原生界面调整。")}>
Expand Down
4 changes: 2 additions & 2 deletions apps/codex-plus-manager/src/i18n-en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,8 +374,8 @@ export const EN_PLAIN: Record<string, string> = {
"图片覆盖层": "Image overlay",
"在会话列表悬停显示删除按钮,并支持撤销。": "Show a delete button on hover in the session list, with undo support.",
"在会话列表显示导出按钮,导出带时间戳的 Markdown。": "Show an export button in the session list to export timestamped Markdown.",
"在 Codex 页面显示可拖动的后续建议浮层;建议由单独配置的 Stepwise API 生成。":
"Shows a draggable follow-up suggestion overlay in the Codex page. Suggestions are generated by a separate Stepwise API.",
"在 Codex 页面显示可拖动的后续建议浮层;建议由单独配置的 Stepwise API 生成。启停后需重启 Codex++ 生效。":
"Shows a draggable follow-up suggestion overlay in the Codex page. Suggestions are generated by a separate Stepwise API. Restart Codex++ after changing this setting.",
"在侧边栏会话标题前显示短 ID 和 UUIDv7 创建时间,方便定位历史会话。":
"Show a short ID and UUIDv7 creation time before session titles in the sidebar to help locate past sessions.",
"基础设置": "Basic settings",
Expand Down
2 changes: 1 addition & 1 deletion assets/inject/renderer-inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -3540,7 +3540,7 @@
<button type="button" class="codex-plus-toggle" data-codex-plus-setting="petRealMouseLook"><span></span></button>
</div>` : ""}
<div class="codex-plus-row">
<div><div class="codex-plus-row-title">Stepwise</div><div class="codex-plus-row-description">在当前 Codex 页面显示可拖动的下一步建议浮层,可在设置页配置模型和直接发送。</div></div>
<div><div class="codex-plus-row-title">Stepwise</div><div class="codex-plus-row-description">在当前 Codex 页面显示可拖动的下一步建议浮层,可在设置页配置模型和直接发送。启停后需重启 Codex++ 生效。</div></div>
<button type="button" class="codex-plus-toggle" data-codex-plus-setting="stepwise"><span></span></button>
</div>
<div class="codex-plus-row" data-codex-service-tier-controls="true">
Expand Down
23 changes: 20 additions & 3 deletions assets/inject/stepwise-inject.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
return !state.destroyed && window[API_KEY]?.instanceId === INSTANCE_ID;
}

function stepwiseEnabled() {
return state.settings?.enabled === true;
}

function normalizeText(value) {
return String(value || "")
.replace(/\u00a0/g, " ")
Expand Down Expand Up @@ -914,7 +918,7 @@
}

function installFloat() {
if (!isCurrentInstance()) return;
if (!isCurrentInstance() || !stepwiseEnabled()) return;
document.querySelectorAll?.(`[${ROOT_ATTR}="true"]`).forEach((node) => {
if (node !== state.root) node.remove();
});
Expand Down Expand Up @@ -1757,6 +1761,7 @@
}

function requestBridgeStepwise(key, userText, assistantText) {
if (!stepwiseEnabled()) return;
if (!key || state.bridgePendingHash === key || state.bridgeCache.has(key)) return;

state.bridgePendingHash = key;
Expand Down Expand Up @@ -2029,6 +2034,10 @@

function scan() {
if (!isCurrentInstance()) return;
if (!stepwiseEnabled()) {
stopRuntime();
return;
}
state.timer = 0;
state.scans += 1;
installStyle();
Expand Down Expand Up @@ -2106,12 +2115,16 @@

function scheduleScan(delay = SCAN_DELAY_MS) {
if (!isCurrentInstance()) return;
if (!stepwiseEnabled()) {
stopRuntime();
return;
}
if (state.timer) window.clearTimeout(state.timer);
state.timer = window.setTimeout(scan, delay);
}

function installObserver() {
if (!isCurrentInstance()) return false;
if (!isCurrentInstance() || !stepwiseEnabled()) return false;
const root = document.body || document.documentElement;
if (!root) return false;

Expand Down Expand Up @@ -2147,13 +2160,17 @@
}

function activateRuntime() {
if (!stepwiseEnabled()) {
stopRuntime();
return;
}
installStyle();
installFloat();
if (!state.observer && !installObserver()) {
document.addEventListener(
"DOMContentLoaded",
() => {
if (!isCurrentInstance()) return;
if (!isCurrentInstance() || !stepwiseEnabled()) return;
installObserver();
installFloat();
void ensureSettings();
Expand Down
7 changes: 6 additions & 1 deletion crates/codex-plus-core/src/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,11 @@ pub fn injection_script_with_settings(helper_port: u16, settings: &BackendSettin
let paste_fix = paste_fix_enabled_config(settings);
let force_chinese_locale = force_chinese_locale_config(settings);
let fast_startup = fast_startup_config(settings);
let stepwise_runtime = if settings.codex_app_stepwise_enabled {
stepwise_script()
} else {
""
};
format!(
"window.__CODEX_SESSION_DELETE_HELPER__ = {};\nwindow.__CODEX_PLUS_SPONSOR_IMAGES__ = {};\nwindow.__CODEX_PLUS_VERSION__ = {};\nwindow.__CODEX_PLUS_BUILD__ = {};\nwindow.__CODEX_PLUS_IMAGE_OVERLAY__ = {};\nwindow.__CODEX_PLUS_PLUGIN_MARKETPLACES__ = {};\nwindow.__CODEX_PLUS_EXTERNAL_DREAM_SKIN_RUNTIME__ = true;\nwindow.__CODEX_PLUS_DREAM_SKIN_PLATFORM__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_REVISION__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_ART__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_ART_SIGNATURE__ = {};\nwindow.__CODEX_PLUS_DREAM_SKIN_THEME__ = {};\nwindow.__CODEX_PLUS_PASTE_FIX__ = {};\nwindow.__CODEX_PLUS_FORCE_CHINESE_LOCALE__ = {};\nwindow.__CODEX_PLUS_FAST_STARTUP__ = {};\n{}\n{}\n{}",
serde_json::to_string(&helper_url).expect("helper URL should serialize"),
Expand All @@ -360,7 +365,7 @@ pub fn injection_script_with_settings(helper_port: u16, settings: &BackendSettin
.expect("force Chinese locale config should serialize"),
serde_json::to_string(&fast_startup).expect("fast startup config should serialize"),
renderer_script(),
stepwise_script(),
stepwise_runtime,
dream_skin_target_runtime,
)
}
Expand Down
38 changes: 37 additions & 1 deletion crates/codex-plus-core/tests/cdp_bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use codex_plus_core::cdp::{
is_quick_chat_page_target, list_targets, pick_injectable_codex_page_target, pick_page_target,
validate_cdp_websocket_url,
};
use codex_plus_core::settings::BackendSettings;

use futures_util::{SinkExt, StreamExt};
use serde_json::json;
Expand Down Expand Up @@ -67,6 +68,25 @@ fn injection_script_prefixes_helper_url_and_sponsor_images() {
assert!(script.contains("data-codex-plus-discord"));
}

#[test]
fn injection_script_omits_stepwise_runtime_when_disabled() {
let script = assets::injection_script_with_settings(57321, &BackendSettings::default());

assert!(!script.contains("const API_KEY = \"__codexStepwisePanel\";"));
assert!(script.contains("data-codex-plus-setting=\"stepwise\""));
}

#[test]
fn injection_script_includes_stepwise_runtime_when_enabled() {
let settings = BackendSettings {
codex_app_stepwise_enabled: true,
..Default::default()
};
let script = assets::injection_script_with_settings(57321, &settings);

assert!(script.contains("const API_KEY = \"__codexStepwisePanel\";"));
}

#[test]
fn pet_real_mouse_settings_are_gated_to_windows_in_injected_ui() {
let script = assets::injection_script(57321);
Expand Down Expand Up @@ -699,7 +719,11 @@ fn injection_script_menu_exposes_marketplace_plugin_switch_only() {

#[test]
fn injection_script_menu_exposes_stepwise_switch_and_syncs_panel() {
let script = assets::injection_script(57321);
let settings = BackendSettings {
codex_app_stepwise_enabled: true,
..Default::default()
};
let script = assets::injection_script_with_settings(57321, &settings);

assert!(script.contains("stepwise: false"));
assert!(script.contains("stepwise: \"codexAppStepwiseEnabled\""));
Expand All @@ -712,6 +736,18 @@ fn injection_script_menu_exposes_stepwise_switch_and_syncs_panel() {
assert!(script.contains("activateRuntime();"));
}

#[test]
fn stepwise_runtime_stops_work_when_disabled() {
let script = assets::stepwise_script().replace("\r\n", "\n");

assert!(script.contains("function stepwiseEnabled()"));
assert!(script.contains("if (!stepwiseEnabled()) {"));
assert!(script.contains("stopRuntime();"));
assert!(script.contains(
"function requestBridgeStepwise(key, userText, assistantText) {\n if (!stepwiseEnabled()) return;"
));
}

#[test]
fn stepwise_direct_send_targets_main_chat_composer() {
let script = assets::stepwise_script();
Expand Down
37 changes: 37 additions & 0 deletions docs/plans/2026-07-17-stepwise-disabled-injection-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Stepwise Disabled Injection Design

## Goal

Prevent Codex++ from injecting the Stepwise runtime when Stepwise is disabled, and
ensure a runtime that was already active cannot recreate its floating UI after the
setting is turned off.

## Current Behavior

`injection_script_with_settings` always appends `stepwise_script()`. The injected
runtime loads settings later, so a disabled setting blocks the backend request but
does not prevent the Stepwise code from reaching the Codex page. Existing delayed
scan and DOM-ready paths can create UI after the feature is disabled.

## Design

The launcher already passes `BackendSettings` to `injection_script_with_settings`.
Use `codex_app_stepwise_enabled` there to append the Stepwise script only when it
is enabled. The renderer menu uses optional chaining for the Stepwise panel, so it
continues to render and persist the toggle when the runtime is absent.

The Stepwise runtime will additionally guard scan scheduling and DOM-ready work
with `state.settings?.enabled === true`. Its disabled path will call
`stopRuntime()`, which removes the root, style, observer, and timer.

Changing the setting changes the new-document bundle, so the UI copy will state
that restarting Codex++ is required for the change to take effect.

## Validation

- A disabled settings bundle must not contain a Stepwise runtime marker.
- An enabled settings bundle must contain the Stepwise runtime and the renderer
toggle integration.
- The runtime source must show enabled guards before scan, scheduling, and
DOM-ready activation paths.
- Run the targeted core bridge tests and Rust formatting checks.