Skip to content
Draft
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
784 changes: 588 additions & 196 deletions assets/inject/renderer-inject.js

Large diffs are not rendered by default.

Binary file not shown.
37 changes: 30 additions & 7 deletions bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func (r *launcherRuntime) handleBridgeRequest(path string, payload json.RawMessa
r.logRendererDiagnostic(payload)
result = map[string]any{"status": "ok", "message": "日志已记录"}
case "/user-scripts/list":
result = userScriptInventoryValue()
result = userScriptInventoryValue(payloadMap["runtime_status"])
case "/user-scripts/set-enabled":
config := loadUserScriptConfig()
config.Enabled = boolFromAny(payloadMap["enabled"])
Expand Down Expand Up @@ -143,8 +143,8 @@ func (r *launcherRuntime) bridgeSettingsValue(settings backendSettings) map[stri
"relayProfilesEnabled": settings.RelayProfilesEnabled,
"ccsLinkEnabled": settings.CCSLinkEnabled,
"enhancementsEnabled": settings.Enhancements,
"codexAppPluginEntryUnlock": settings.CodexAppPluginEntryUnlock,
"codexAppForcePluginInstall": settings.CodexAppForcePluginInstall,
"codexAppPluginMarketplaceUnlock": settings.CodexAppPluginMarketplaceUnlock,
"codexAppPluginAutoExpand": settings.CodexAppPluginAutoExpand,
"codexAppModelWhitelistUnlock": settings.CodexAppModelWhitelistUnlock,
"codexAppSessionDelete": settings.CodexAppSessionDelete,
"codexAppMarkdownExport": settings.CodexAppMarkdownExport,
Expand All @@ -168,6 +168,7 @@ func (r *launcherRuntime) bridgeSettingsValue(settings backendSettings) map[stri
"codexAppImageOverlayEnabled": settings.CodexAppImageOverlayEnabled,
"codexAppImageOverlayPath": settings.CodexAppImageOverlayPath,
"codexAppImageOverlayOpacity": settings.CodexAppImageOverlayOpacity,
"codexAppImageOverlayFitMode": settings.CodexAppImageOverlayFitMode,
"codexGoalsEnabled": settings.CodexGoalsEnabled,
"mobileControlEnabled": settings.MobileControlEnabled,
"mobileControlRelayUrl": settings.MobileControlRelayURL,
Expand Down Expand Up @@ -206,8 +207,8 @@ func (r *launcherRuntime) setBridgeSettings(payload map[string]any) map[string]a
applyBool("relayProfilesEnabled", &settings.RelayProfilesEnabled)
applyBool("ccsLinkEnabled", &settings.CCSLinkEnabled)
applyBool("enhancementsEnabled", &settings.Enhancements)
applyBool("codexAppPluginEntryUnlock", &settings.CodexAppPluginEntryUnlock)
applyBool("codexAppForcePluginInstall", &settings.CodexAppForcePluginInstall)
applyBool("codexAppPluginMarketplaceUnlock", &settings.CodexAppPluginMarketplaceUnlock)
applyBool("codexAppPluginAutoExpand", &settings.CodexAppPluginAutoExpand)
applyBool("codexAppModelWhitelistUnlock", &settings.CodexAppModelWhitelistUnlock)
applyBool("codexAppSessionDelete", &settings.CodexAppSessionDelete)
applyBool("codexAppMarkdownExport", &settings.CodexAppMarkdownExport)
Expand Down Expand Up @@ -239,6 +240,9 @@ func (r *launcherRuntime) setBridgeSettings(payload map[string]any) map[string]a
if _, ok := payload["codexAppImageOverlayOpacity"]; ok {
settings.CodexAppImageOverlayOpacity = intArg(payload, "codexAppImageOverlayOpacity", settings.CodexAppImageOverlayOpacity)
}
if _, ok := payload["codexAppImageOverlayFitMode"]; ok {
settings.CodexAppImageOverlayFitMode = normalizeImageOverlayFitMode(stringFromAny(payload["codexAppImageOverlayFitMode"]))
}
if _, ok := payload["mobileControlRelayUrl"]; ok {
settings.MobileControlRelayURL = strings.TrimSpace(stringFromAny(payload["mobileControlRelayUrl"]))
}
Expand Down Expand Up @@ -287,7 +291,24 @@ func enabledUserScriptBundle() string {
if err != nil {
continue
}
parts = append(parts, fmt.Sprintf("\n;(() => {\n%s\n})();\n", string(data)))
keyJSON, _ := json.Marshal(item.Key)
nameJSON, _ := json.Marshal(item.Name)
sourceJSON, _ := json.Marshal(item.Source)
parts = append(parts, fmt.Sprintf(`
;(() => {
window.__codexPlusUserScripts = window.__codexPlusUserScripts || { scripts: {} };
const key = %s;
window.__codexPlusUserScripts.scripts[key] = { key, name: %s, source: %s, status: "loading", error: "", loadedAt: new Date().toISOString() };
try {
%s
window.__codexPlusUserScripts.scripts[key].status = "loaded";
window.__codexPlusUserScripts.scripts[key].loadedAt = new Date().toISOString();
} catch (error) {
window.__codexPlusUserScripts.scripts[key].status = "failed";
window.__codexPlusUserScripts.scripts[key].error = String(error && (error.stack || error.message) || error);
}
})();
`, keyJSON, nameJSON, sourceJSON, string(data)))
}
return strings.Join(parts, "\n")
}
Expand Down Expand Up @@ -561,10 +582,11 @@ func injectionScript(helperPort uint16, settings backendSettings) string {
versionJSON, _ := json.Marshal(version)
buildJSON, _ := json.Marshal("go-20260711-1")
imageOverlayJSON, _ := json.Marshal(imageOverlayConfig(helperPort, settings))
pluginMarketplacesJSON, _ := json.Marshal(localPluginMarketplaces(codexHomeDir()))
fastStartupJSON, _ := json.Marshal(map[string]any{"enabled": settings.CodexAppFastStartup, "statsigTimeoutMs": 800})
chineseLocaleJSON, _ := json.Marshal(map[string]any{"enabled": settings.CodexAppForceChineseLocale, "locale": "zh-CN"})
nativeMenuLocalizationJSON, _ := json.Marshal(map[string]any{"enabled": settings.CodexAppNativeMenuLocalization, "locale": "zh-CN"})
return fmt.Sprintf("window.__CODEX_SESSION_DELETE_HELPER__ = %s;\nwindow.__CODEX_PLUS_VERSION__ = %s;\nwindow.__CODEX_PLUS_BUILD__ = %s;\nwindow.__CODEX_PLUS_IMAGE_OVERLAY__ = %s;\nwindow.__CODEX_PLUS_FAST_STARTUP__ = %s;\nwindow.__CODEX_PLUS_FORCE_CHINESE_LOCALE__ = %s;\nwindow.__CODEX_PLUS_NATIVE_MENU_LOCALIZATION__ = %s;\n%s", helperJSON, versionJSON, buildJSON, imageOverlayJSON, fastStartupJSON, chineseLocaleJSON, nativeMenuLocalizationJSON, rendererInjectScript)
return fmt.Sprintf("window.__CODEX_SESSION_DELETE_HELPER__ = %s;\nwindow.__CODEX_PLUS_VERSION__ = %s;\nwindow.__CODEX_PLUS_BUILD__ = %s;\nwindow.__CODEX_PLUS_IMAGE_OVERLAY__ = %s;\nwindow.__CODEX_PLUS_PLUGIN_MARKETPLACES__ = %s;\nwindow.__CODEX_PLUS_FAST_STARTUP__ = %s;\nwindow.__CODEX_PLUS_FORCE_CHINESE_LOCALE__ = %s;\nwindow.__CODEX_PLUS_NATIVE_MENU_LOCALIZATION__ = %s;\n%s", helperJSON, versionJSON, buildJSON, imageOverlayJSON, pluginMarketplacesJSON, fastStartupJSON, chineseLocaleJSON, nativeMenuLocalizationJSON, rendererInjectScript)
}

func imageOverlayConfig(helperPort uint16, settings backendSettings) map[string]any {
Expand Down Expand Up @@ -597,6 +619,7 @@ func imageOverlayConfig(helperPort uint16, settings backendSettings) map[string]
return map[string]any{
"enabled": enabled,
"opacity": float64(opacity) / 100.0,
"fitMode": normalizeImageOverlayFitMode(settings.CodexAppImageOverlayFitMode),
"dataUrl": dataURL,
"imageUrl": imageURL,
}
Expand Down
36 changes: 32 additions & 4 deletions bridge_parity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,18 +185,21 @@ func TestBridgeSettingsIncludesRuntimeCodexAppVersion(t *testing.T) {
}
}

func TestInjectionScriptDoesNotInjectLocalPluginMarketplaces(t *testing.T) {
func TestInjectionScriptInjectsLocalPluginMarketplacesWithoutAds(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
manifest := filepath.Join(home, ".codex", ".tmp", "plugins", ".agents", "plugins", "marketplace.json")
writeTestFile(t, manifest, `{"name":"openai-curated","plugins":[{"name":"writer"}]}`)
script := injectionScript(57321, defaultSettings())

for _, forbidden := range []string{"window.__CODEX_PLUS_PLUGIN_MARKETPLACES__", `"openai-curated"`, `"writer"`} {
if strings.Contains(script, forbidden) {
t.Fatalf("injection should rely on Codex's native marketplace instead of injecting local payload %q", forbidden)
for _, expected := range []string{"window.__CODEX_PLUS_PLUGIN_MARKETPLACES__", `"writer"`, `"openai-curated"`} {
if !strings.Contains(script, expected) {
t.Fatalf("injection should include normalized local marketplace payload %q", expected)
}
}
if strings.Contains(script, "/ads") {
t.Fatal("local marketplace payload must not reintroduce ads")
}
}

func TestInjectionScriptIncludesV1224RuntimeConfig(t *testing.T) {
Expand Down Expand Up @@ -228,6 +231,7 @@ func TestImageOverlayConfigAndInjectionScript(t *testing.T) {
settings.CodexAppImageOverlayEnabled = true
settings.CodexAppImageOverlayPath = imagePath
settings.CodexAppImageOverlayOpacity = 42
settings.CodexAppImageOverlayFitMode = "tile"

config := imageOverlayConfig(57321, settings)

Expand All @@ -240,6 +244,9 @@ func TestImageOverlayConfigAndInjectionScript(t *testing.T) {
if got := stringFromAny(config["imageUrl"]); got != "http://127.0.0.1:57321/overlay/image" {
t.Fatalf("overlay image URL mismatch: %q", got)
}
if got := stringFromAny(config["fitMode"]); got != "tile" {
t.Fatalf("overlay fit mode mismatch: %q", got)
}
script := injectionScript(57321, settings)
for _, expected := range []string{"__CODEX_PLUS_IMAGE_OVERLAY__", "codex-plus-image-overlay", "image_overlay_installed"} {
if !strings.Contains(script, expected) {
Expand All @@ -248,6 +255,27 @@ func TestImageOverlayConfigAndInjectionScript(t *testing.T) {
}
}

func TestBridgeSettingsPersistLatestEnhancementFields(t *testing.T) {
t.Setenv("HOME", t.TempDir())
runtime := &launcherRuntime{}
result := runtime.handleBridgeRequest("/settings/set", json.RawMessage(`{
"codexAppPluginMarketplaceUnlock": false,
"codexAppPluginAutoExpand": false,
"codexAppFastStartup": true,
"codexAppImageOverlayFitMode": "stretch"
}`))
if stringFromAny(result["status"]) != "ok" {
t.Fatalf("settings set failed: %#v", result)
}
loaded := loadSettings()
if loaded.CodexAppPluginMarketplaceUnlock || loaded.CodexAppPluginAutoExpand {
t.Fatalf("explicitly disabled plugin settings were not preserved: %#v", loaded)
}
if !loaded.CodexAppFastStartup || loaded.CodexAppImageOverlayFitMode != "stretch" {
t.Fatalf("latest enhancement fields were not persisted: %#v", loaded)
}
}

func TestResilientLoopbackGuardHoldsLockAndListener(t *testing.T) {
port := freeLoopbackPort(t)
guard, err := acquireResilientLoopbackPortGuardAt(port, t.TempDir())
Expand Down
87 changes: 87 additions & 0 deletions codex_sqlite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

import (
"os"
"path/filepath"
"sort"
"strings"
)

func codexSQLiteHome(home string) string {
if value := strings.TrimSpace(os.Getenv("CODEX_SQLITE_HOME")); value != "" {
return filepath.Clean(os.ExpandEnv(value))
}
return filepath.Clean(home)
}

func codexSessionDBPaths(home string) []string {
sqliteHome := codexSQLiteHome(home)
paths := make([]string, 0)
seen := map[string]bool{}
add := func(path string) {
path = filepath.Clean(path)
if path == "" || seen[path] || !fileExists(path) {
return
}
seen[path] = true
paths = append(paths, path)
}
sqliteDir := filepath.Join(sqliteHome, "sqlite")
if entries, err := os.ReadDir(sqliteDir); err == nil {
for _, entry := range entries {
if entry.IsDir() {
continue
}
lower := strings.ToLower(entry.Name())
if strings.HasSuffix(lower, ".db") || strings.HasSuffix(lower, ".sqlite") {
add(filepath.Join(sqliteDir, entry.Name()))
}
}
}
sort.Strings(paths)
add(filepath.Join(sqliteHome, "state_5.sqlite"))
return paths
}

func codexPreferredSessionDBPath(home string) string {
paths := codexSessionDBPaths(home)
for _, path := range paths {
if sqlitePathHasTable(path, "threads") {
return path
}
}
return filepath.Join(codexSQLiteHome(home), "state_5.sqlite")
}

func sqlitePathHasTable(path, table string) bool {
if !fileExists(path) {
return false
}
db, err := openSQLite(path)
if err != nil {
return false
}
defer db.Close()
columns, err := sqliteTableColumns(db, table)
return err == nil && len(columns) > 0
}

func codexLogsDBPath(home string) string {
return filepath.Join(codexSQLiteHome(home), "logs_2.sqlite")
}

func pathWithin(root, candidate string) bool {
rootAbs, err := filepath.Abs(root)
if err != nil {
return false
}
candidateAbs, err := filepath.Abs(candidate)
if err != nil {
return false
}
relative, err := filepath.Rel(rootAbs, candidateAbs)
if err != nil {
return false
}
return relative == "." || relative != ".." && !strings.HasPrefix(relative, ".."+string(os.PathSeparator))
}
Loading