diff --git a/assets/inject/renderer-inject.js b/assets/inject/renderer-inject.js index f4d4c70d1..d576d87fe 100644 --- a/assets/inject/renderer-inject.js +++ b/assets/inject/renderer-inject.js @@ -5804,6 +5804,8 @@ contextRevision: 0, draftContext: null, contextPromise: null, + generatorUnavailableRevision: -1, + generatorUnavailableSkippedEvents: {}, lastGenericBeginAt: 0, homeRouteRevision: -1, }; @@ -5845,6 +5847,17 @@ && codexProjectlessMainWindowState.intent === "generic"; } + function reportCodexProjectlessGeneratorUnavailable(event, detail) { + const revision = codexProjectlessMainWindowState.revision; + const skippedEvents = codexProjectlessMainWindowState.generatorUnavailableSkippedEvents || {}; + if (skippedEvents[event] === revision) return; + codexProjectlessMainWindowState.generatorUnavailableSkippedEvents = { + ...skippedEvents, + [event]: revision, + }; + sendCodexPlusDiagnostic(event, detail); + } + function codexProjectlessContextValid(context) { return !!context && typeof context === "object" @@ -5878,6 +5891,7 @@ async function prepareCodexProjectlessDraftContext(prompt = "") { if (!codexProjectlessMainWindowShouldEnforce()) return null; const revision = codexProjectlessMainWindowState.revision; + if (codexProjectlessMainWindowState.generatorUnavailableRevision === revision) return null; if (codexProjectlessMainWindowState.contextRevision === revision && codexProjectlessContextValid(codexProjectlessMainWindowState.draftContext)) { return codexProjectlessMainWindowState.draftContext; @@ -5887,7 +5901,11 @@ return await codexProjectlessMainWindowState.contextPromise; } const contextPromise = Promise.resolve().then(async () => { - const module = await loadCodexAppModule("projectless-thread-"); + const module = await loadOptionalCodexAppModule("projectless-thread-"); + if (!module) { + codexProjectlessMainWindowState.generatorUnavailableRevision = revision; + return null; + } if (typeof module.n !== "function") throw new Error("Codex projectless-thread 生成器不可用"); const options = String(prompt || "").trim() ? { prompt: String(prompt).trim() } : {}; const context = await module.n(["~"], options); @@ -5989,6 +6007,13 @@ || !codexProjectlessMainWindowShouldEnforce()) { return dispatch(originalMessage); } + if (!context) { + reportCodexProjectlessGeneratorUnavailable("projectless_thread_start_override_skipped", { + type: String(type || ""), + reason: "projectless-generator-unavailable", + }); + return dispatch(originalMessage); + } const message = applyCodexProjectlessRequestOverride(originalMessage, context); sendCodexPlusDiagnostic("projectless_thread_start_overridden", { type: String(type || ""), @@ -6731,12 +6756,19 @@ const context = await prepareCodexProjectlessDraftContext(codexProjectlessPromptFromValue(params)); if (revision === codexProjectlessMainWindowState.revision && codexProjectlessMainWindowShouldEnforce()) { - nextParams = applyCodexProjectlessAppServerRequestOverride(method, params, context); - sendCodexPlusDiagnostic("projectless_app_server_start_overridden", { - method: String(method || ""), - workspaceRootCount: context.workspaceRoots.length, - hasOutputDirectory: !!context.projectlessOutputDirectory, - }); + if (!context) { + reportCodexProjectlessGeneratorUnavailable("projectless_app_server_start_override_skipped", { + method: String(method || ""), + reason: "projectless-generator-unavailable", + }); + } else { + nextParams = applyCodexProjectlessAppServerRequestOverride(method, params, context); + sendCodexPlusDiagnostic("projectless_app_server_start_overridden", { + method: String(method || ""), + workspaceRootCount: context.workspaceRoots.length, + hasOutputDirectory: !!context.projectlessOutputDirectory, + }); + } } } catch (error) { sendCodexPlusDiagnostic("projectless_app_server_start_override_failed", { diff --git a/crates/codex-plus-core/tests/cdp_bridge.rs b/crates/codex-plus-core/tests/cdp_bridge.rs index d988f23f5..b86ca30a7 100644 --- a/crates/codex-plus-core/tests/cdp_bridge.rs +++ b/crates/codex-plus-core/tests/cdp_bridge.rs @@ -1759,9 +1759,11 @@ fn injection_script_applies_projectless_main_window_contract() { assert!(script.contains("installCodexProjectlessNewTaskButtons")); assert!(script.contains("codexProjectlessMainWindowVersion = \"5\"")); assert!(script.contains("generic-new-task-button")); - assert!(script.contains("loadCodexAppModule(\"projectless-thread-\")")); + assert!(script.contains("loadOptionalCodexAppModule(\"projectless-thread-\")")); assert!(script.contains("projectless_thread_start_overridden")); + assert!(script.contains("projectless_thread_start_override_skipped")); assert!(script.contains("projectless_app_server_start_overridden")); + assert!(script.contains("projectless_app_server_start_override_skipped")); assert!(script.contains("projectless_main_window_home_route_cleared")); assert!(script.contains("dispatcher.dispatchHostMessage")); assert!(script.contains("[\"use-host-config-\", \"app-server-manager-signals-\"]")); @@ -1793,6 +1795,10 @@ fn injection_script_applies_projectless_main_window_contract() { assert_eq!(cases["dispatchedType"], "start-conversation"); assert_eq!(cases["dispatchedWorkspaceKind"], "projectless"); assert_eq!(cases["dispatchedCwd"], "C:/generated/work"); + assert_eq!(cases["fallbackDispatchResult"], "sent"); + assert_eq!(cases["fallbackDispatchedCount"], 1); + assert_eq!(cases["fallbackDispatchedWorkspaceKind"], "project"); + assert_eq!(cases["fallbackDispatchedCwd"], "C:/recent-project"); assert_eq!(cases["appServerRequestNeedsOverride"], true); assert_eq!(cases["appServerPatchedWorkspaceKind"], "projectless"); assert_eq!(cases["appServerPatchedCwd"], "C:/generated/work"); @@ -1803,6 +1809,9 @@ fn injection_script_applies_projectless_main_window_contract() { assert_eq!(cases["appServerSentMethod"], "start-conversation"); assert_eq!(cases["appServerSentWorkspaceKind"], "projectless"); assert_eq!(cases["appServerSentCwd"], "C:/generated/work"); + assert_eq!(cases["fallbackAppServerSentCount"], 2); + assert_eq!(cases["fallbackAppServerWorkspaceKind"], "project"); + assert_eq!(cases["fallbackAppServerCwd"], "C:/recent-project"); assert_eq!(cases["explicitProjectWins"], false); assert_eq!(cases["explicitProjectRequestIsUntouched"], false); assert_eq!(cases["disabledIsNoop"], false); @@ -1930,6 +1939,17 @@ const appServerClient = {{ }}; api.patchAppServerClient(appServerClient); await appServerClient.sendRequest("start-conversation", appServerProjectRequest); +const appServerSentCountBeforeFallback = appServerSent.length; +api.setDraftContext(null); +const fallbackDispatched = []; +const fallbackDispatcher = {{ + __codexServiceTierOriginalDispatchMessage(type, payload) {{ + fallbackDispatched.push({{ type, payload }}); + return "sent"; + }}, +}}; +const fallbackDispatchResult = await api.dispatchMessage(fallbackDispatcher, "start-conversation", projectRequest); +await appServerClient.sendRequest("start-conversation", appServerProjectRequest); api.setIntent("project", "test"); const explicitProjectWins = api.shouldEnforce(); const explicitProjectRequestIsUntouched = api.requestNeedsOverride(projectRequest); @@ -1951,16 +1971,23 @@ process.stdout.write(JSON.stringify({{ dispatchedType: dispatched[0]?.type, dispatchedWorkspaceKind: dispatched[0]?.payload?.workspaceKind, dispatchedCwd: dispatched[0]?.payload?.cwd, + fallbackDispatchResult, + fallbackDispatchedCount: fallbackDispatched.length, + fallbackDispatchedWorkspaceKind: fallbackDispatched[0]?.payload?.workspaceKind, + fallbackDispatchedCwd: fallbackDispatched[0]?.payload?.cwd, appServerRequestNeedsOverride, appServerPatchedWorkspaceKind: appServerPatchedRequest.workspaceKind, appServerPatchedCwd: appServerPatchedRequest.cwd, appServerPatchedHasProjectAssignment: Object.hasOwn(appServerPatchedRequest, "projectAssignment"), nestedAppServerWorkspaceKind: nestedAppServerPatchedRequest.params.workspaceKind, nestedAppServerCwd: nestedAppServerPatchedRequest.params.cwd, - appServerSentCount: appServerSent.length, + appServerSentCount: appServerSentCountBeforeFallback, appServerSentMethod: appServerSent[0]?.method, appServerSentWorkspaceKind: appServerSent[0]?.params?.workspaceKind, appServerSentCwd: appServerSent[0]?.params?.cwd, + fallbackAppServerSentCount: appServerSent.length, + fallbackAppServerWorkspaceKind: appServerSent[1]?.params?.workspaceKind, + fallbackAppServerCwd: appServerSent[1]?.params?.cwd, explicitProjectWins, explicitProjectRequestIsUntouched, disabledIsNoop, }})); process.exit(0);