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
7 changes: 7 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@
assets/inject/upstream/snow-skin/*.js text eol=lf
assets/inject/upstream/snow-skin/*.css text eol=lf

# Keep all byte-exact theme assets identical on Windows checkouts.
assets/inject/upstream/*/windows/*.js text eol=lf
assets/inject/upstream/*/windows/*.css text eol=lf
assets/inject/upstream/*/*.js text eol=lf
assets/inject/upstream/*/*.css text eol=lf
assets/inject/upstream/skin-packs/packs/*/theme.json text eol=lf

# Keep byte-exact macOS theme assets identical on every checkout platform.
assets/inject/upstream/*/macos/*.js text eol=lf
assets/inject/upstream/*/macos/*.css text eol=lf
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions apps/codex-plus-manager/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ serde.workspace = true
serde_json.workspace = true
tauri = { version = "2", features = ["protocol-asset", "custom-protocol", "tray-icon"] }
tauri-plugin-dialog = "2"
toml_edit.workspace = true

[build-dependencies]
tauri-build = { version = "2", features = [] }
Expand Down
53 changes: 52 additions & 1 deletion apps/codex-plus-manager/src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1627,6 +1627,7 @@ fn normalize_settings_before_save(mut settings: BackendSettings) -> BackendSetti
if !profile.use_common_config || profile.config_contents.trim().is_empty() {
continue;
}
let goals_override = relay_config_goals_value(&profile.config_contents);
match codex_plus_core::relay_config::strip_common_config_from_config(
&profile.config_contents,
&common_config,
Expand All @@ -1640,6 +1641,10 @@ fn normalize_settings_before_save(mut settings: BackendSettings) -> BackendSetti
strip_common_config_text_fallback(&profile.config_contents, &common_config);
}
}
if let Some(enabled) = goals_override {
profile.config_contents =
relay_config_set_goals_override(&profile.config_contents, enabled);
}
}
}
settings.provider_sync_saved_providers =
Expand All @@ -1653,6 +1658,30 @@ fn normalize_settings_before_save(mut settings: BackendSettings) -> BackendSetti
settings
}

fn relay_config_goals_value(config: &str) -> Option<bool> {
let doc = config.parse::<toml_edit::DocumentMut>().ok()?;
doc.get("features")?
.as_table_like()?
.get("goals")?
.as_bool()
}

fn relay_config_set_goals_override(config: &str, enabled: bool) -> String {
let Ok(mut doc) = config.parse::<toml_edit::DocumentMut>() else {
return config.to_string();
};
if !doc.as_table().contains_key("features")
|| doc
.get("features")
.and_then(toml_edit::Item::as_table_like)
.is_none()
{
doc["features"] = toml_edit::table();
}
doc["features"]["goals"] = toml_edit::value(enabled);
codex_plus_core::relay_config::normalize_config_text(&doc.to_string())
}

fn normalize_provider_sync_provider_list(values: Vec<String>) -> Vec<String> {
let mut seen = std::collections::HashSet::new();
let mut result = Vec::new();
Expand Down Expand Up @@ -5144,10 +5173,32 @@ enabled = true

assert!(config.contains("model = \"gpt-5\""));
assert!(!config.contains("model_reasoning_effort"));
assert!(!config.contains("[features]"));
// `goals` is an explicit per-profile override and must survive
// normalization even when it matches the common configuration.
assert!(config.contains("[features]"));
assert!(config.contains("goals = true"));
assert!(!config.contains("[plugins.\"superpowers@openai-curated\"]"));
}

#[test]
fn normalize_settings_before_save_preserves_explicit_false_goals_override() {
let settings = BackendSettings {
relay_common_config_contents: "[features]\ngoals = true\nfast_mode = true\n"
.to_string(),
relay_profiles: vec![RelayProfile {
use_common_config: true,
config_contents: "model = \"gpt-5\"\n[features]\ngoals = false\n".to_string(),
..RelayProfile::default()
}],
..BackendSettings::default()
};

let normalized = normalize_settings_before_save(settings);
let config = &normalized.relay_profiles[0].config_contents;
assert!(config.contains("goals = false"));
assert!(!config.contains("fast_mode = true"));
}

#[test]
fn normalize_settings_before_save_repairs_invalid_profile_common_duplication() {
let settings = BackendSettings {
Expand Down
117 changes: 55 additions & 62 deletions apps/codex-plus-manager/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea";
import { codexGoalsFeatureState, setCodexGoalsFeatureInConfig } from "./goals-config";
import { isGitHubRepositoryHomepage } from "./github-repository";
import {
mergeModelWindowRows,
Expand Down Expand Up @@ -5663,6 +5664,11 @@ function RelayProfileEditor({
}

const showApiFields = profile.relayMode !== "official" || profile.officialMixApiKey;
const goalsFeatureState = codexGoalsFeatureState(
profile.configContents,
form.relayCommonConfigContents,
profile.useCommonConfig,
);
const sub2apiBaseUrl = profile.upstreamBaseUrl.trim() || profile.baseUrl.trim();
const canFetchSub2ApiRate = profile.sub2apiEnabled && Boolean(sub2apiBaseUrl && profile.apiKey.trim());
const updateDraft = (patch: Partial<RelayProfile>) => {
Expand Down Expand Up @@ -5760,7 +5766,7 @@ function RelayProfileEditor({
<Field className="relay-field-goals" label={t("Codex 目标")}>
<label className="inline-check">
<input
checked={configHasCodexGoalsFeature(profile.configContents)}
checked={goalsFeatureState.enabled}
onChange={(event) =>
updateDraft({
configContents: setCodexGoalsFeatureInConfig(profile.configContents, event.currentTarget.checked),
Expand All @@ -5770,6 +5776,9 @@ function RelayProfileEditor({
/>
<span>{t("启用目标功能")}</span>
</label>
{goalsFeatureState.inherited ? (
<p className="field-hint">{t("当前继承公共配置;修改后将为该供应商保存独立设置。")}</p>
) : null}
</Field>
<div className="relay-advanced-toggle">
<Button
Expand Down Expand Up @@ -7422,76 +7431,60 @@ function filterContextEntriesBySelection(entries: CodexContextEntries, selection
};
}

function configHasCodexGoalsFeature(configContents: string): boolean {
let inFeatures = false;
for (const line of configContents.split(/\r?\n/)) {
const trimmed = line.trim();
if (/^\[features\]$/.test(trimmed)) {
inFeatures = true;
continue;
}
if (inFeatures && /^\[[^\]]+\]$/.test(trimmed)) {
inFeatures = false;
}
if (inFeatures && /^goals\s*=\s*true\b/.test(trimmed)) {
return true;
}
}
return false;
function effectiveRelayConfigPreview(profile: RelayProfile, settings: BackendSettings, contextProfile = profile): string {
const entries = contextEntriesForProfile(settings, contextProfile);
const isolatedConfig = stripContextEntriesFromConfig(profile.configContents, entries);
const configWithLimits = applyContextLimitPreview(isolatedConfig, profile);
const profileAndCommon = mergeFeaturesTableForPreview(configWithLimits, settings.relayCommonConfigContents || "");
return joinTomlSectionsRootFirst([profileAndCommon, selectedContextConfigToml(entries)]);
}

function setCodexGoalsFeatureInConfig(configContents: string, enabled: boolean): string {
const lines = configContents.split(/\r?\n/);
const next: string[] = [];
let inFeatures = false;
let sawFeatures = false;
let featuresHasGoals = false;
function mergeFeaturesTableForPreview(profileConfig: string, commonConfig: string): string {
const profile = splitFeaturesTable(profileConfig);
const common = splitFeaturesTable(commonConfig);
if (!profile.body && !common.body) return joinTomlSectionsRootFirst([profileConfig, commonConfig]);

const maybeInsertGoals = () => {
if (enabled && sawFeatures && !featuresHasGoals) {
next.push("goals = true");
featuresHasGoals = true;
}
};
const profileKeys = new Set(tomlAssignmentKeys(profile.body));
const commonBody = common.body
.split(/\r?\n/)
.filter((line) => {
const key = tomlAssignmentKey(line);
return !key || !profileKeys.has(key);
})
.join("\n");
const mergedFeatures = ["[features]", commonBody, profile.body]
.filter((part) => part.trim())
.join("\n");
return joinTomlSectionsRootFirst([
profile.without,
common.without,
mergedFeatures,
]);
}

for (const line of lines) {
const trimmed = line.trim();
if (/^\[features\]$/.test(trimmed)) {
if (inFeatures) maybeInsertGoals();
inFeatures = true;
sawFeatures = true;
featuresHasGoals = false;
next.push(line);
continue;
}
if (inFeatures && /^\[[^\]]+\]$/.test(trimmed)) {
maybeInsertGoals();
inFeatures = false;
}
if (inFeatures && /^goals\s*=/.test(trimmed)) {
if (enabled && !featuresHasGoals) {
next.push("goals = true");
featuresHasGoals = true;
}
continue;
function splitFeaturesTable(contents: string): { without: string; body: string } {
const lines = contents.trim().split(/\r?\n/);
const start = lines.findIndex((line) => line.trim() === "[features]");
if (start < 0) return { without: contents, body: "" };
let end = lines.length;
for (let index = start + 1; index < lines.length; index += 1) {
if (/^\s*\[[^\]]+\]\s*$/.test(lines[index])) {
end = index;
break;
}
next.push(line);
}

if (inFeatures) maybeInsertGoals();
if (enabled && !sawFeatures) {
const trimmed = ensureTrailingNewline(next.join("\n").trimEnd());
return joinTomlSections([trimmed, "[features]\ngoals = true"]);
}
return {
without: [...lines.slice(0, start), ...lines.slice(end)].join("\n"),
body: lines.slice(start + 1, end).join("\n"),
};
}

return ensureTrailingNewline(next.join("\n").trimEnd());
function tomlAssignmentKey(line: string): string | undefined {
return /^\s*([A-Za-z0-9_-]+)\s*=/.exec(line)?.[1];
}

function effectiveRelayConfigPreview(profile: RelayProfile, settings: BackendSettings, contextProfile = profile): string {
const entries = contextEntriesForProfile(settings, contextProfile);
const isolatedConfig = stripContextEntriesFromConfig(profile.configContents, entries);
const configWithLimits = applyContextLimitPreview(isolatedConfig, profile);
return joinTomlSectionsRootFirst([configWithLimits, settings.relayCommonConfigContents || "", selectedContextConfigToml(entries)]);
function tomlAssignmentKeys(contents: string): string[] {
return contents.split(/\r?\n/).map(tomlAssignmentKey).filter((key): key is string => Boolean(key));
}

function selectedContextConfigToml(entries: CodexContextEntries): string {
Expand Down
64 changes: 64 additions & 0 deletions apps/codex-plus-manager/src/goals-config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import assert from "node:assert/strict";
import test from "node:test";

import {
codexGoalsFeatureState,
codexGoalsFeatureValue,
setCodexGoalsFeatureInConfig,
} from "./goals-config.ts";

test("reads explicit goals values only from the features table", () => {
assert.equal(codexGoalsFeatureValue("[features]\ngoals = true\n"), true);
assert.equal(codexGoalsFeatureValue("[features]\ngoals = false\n"), false);
assert.equal(codexGoalsFeatureValue("[features.other]\ngoals = true\n"), undefined);
});

test("uses an explicit common goals value when the profile has no override", () => {
assert.deepEqual(
codexGoalsFeatureState("", "[features]\ngoals = true\n", true),
{ enabled: true, inherited: true },
);
assert.deepEqual(
codexGoalsFeatureState("", "[features]\ngoals = false\n", true),
{ enabled: false, inherited: true },
);
});

test("profile goals value overrides common config", () => {
assert.deepEqual(
codexGoalsFeatureState("[features]\ngoals = false\n", "[features]\ngoals = true\n", true),
{ enabled: false, inherited: false },
);
assert.deepEqual(
codexGoalsFeatureState("[features]\ngoals = true\n", "[features]\ngoals = false\n", true),
{ enabled: true, inherited: false },
);
});

test("uses the profile value when common config has no goals value", () => {
assert.deepEqual(
codexGoalsFeatureState("[features]\ngoals = true\n", "[features]\nfast_mode = true\n", true),
{ enabled: true, inherited: false },
);
});

test("ignores common goals when common config is disabled", () => {
assert.deepEqual(
codexGoalsFeatureState("", "[features]\ngoals = true\n", false),
{ enabled: false, inherited: false },
);
});

test("writes explicit true and false overrides without changing unrelated feature values", () => {
const enabled = setCodexGoalsFeatureInConfig("[features]\nfast_mode = true\n", true);
assert.equal(codexGoalsFeatureValue(enabled), true);
assert.match(enabled, /fast_mode = true/);

const disabled = setCodexGoalsFeatureInConfig(enabled, false);
assert.equal(disabled, "[features]\ngoals = false\nfast_mode = true\n");
assert.equal(codexGoalsFeatureValue(disabled), false);
});

test("creates a features table for an explicit false override", () => {
assert.equal(setCodexGoalsFeatureInConfig('model = "gpt-5"\n', false), 'model = "gpt-5"\n\n[features]\ngoals = false\n');
});
Loading
Loading