minecodex 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,4 @@
1
1
  import { randomBytes } from "node:crypto";
2
- import { spawn } from "node:child_process";
3
2
  import { mkdir, stat } from "node:fs/promises";
4
3
  import path from "node:path";
5
4
 
@@ -321,6 +320,9 @@ export function createInjectionSource(features, {
321
320
  [data-codex-model-slider-trigger-label] {
322
321
  display: inline; overflow: visible; text-overflow: clip; white-space: nowrap;
323
322
  }
323
+ [data-codex-model-slider-trigger] [class*="_ModelPickerTriggerEffortLabel_"] {
324
+ display: inline-flex; align-items: center; align-self: center;
325
+ }
324
326
  html[data-codex-model-slider-selecting-effort] [role="menu"][data-state="open"]:not(:has([data-reasoning-slider])) {
325
327
  visibility: hidden !important; opacity: 0 !important; animation: none !important; pointer-events: none !important;
326
328
  }
@@ -374,7 +376,6 @@ export function createInjectionSource(features, {
374
376
  if (modelSelectorFeature) {
375
377
  document.documentElement.setAttribute("data-codex-model-slider-catalog-ready", "");
376
378
  document.documentElement.append(modelSelectorStyle);
377
- void loadNativeFastIcon();
378
379
  }
379
380
 
380
381
  for (const feature of pageScriptFeatures) {
@@ -617,32 +618,6 @@ export function createInjectionSource(features, {
617
618
  return icon;
618
619
  }
619
620
 
620
- async function loadNativeFastIcon() {
621
- const sourceLink = document.querySelector('link[href*="/assets/app-initial-"][href$=".js"]');
622
- if (!sourceLink?.href) return;
623
- try {
624
- const source = await fetch(sourceLink.href).then((response) => response.text());
625
- const active = source.match(/d:`(M11\.9125 21\.4125[^`]+)`,fill:`currentColor`/);
626
- const inactive = source.match(/d:`(M7\.38 16\.2207[^`]+)`,fill:`currentColor`/);
627
- if (!active?.[1] || !inactive?.[1]) return;
628
- modelSelectorNativeFastIcons = {
629
- active: { viewBox: "0 0 24 24", markup: `<path d="${active[1]}" fill="currentColor" />` },
630
- inactive: {
631
- viewBox: "0 0 20 20",
632
- markup: `<g transform="translate(2.43 1.609)"><path d="${inactive[1]}" fill="currentColor" /></g>`,
633
- },
634
- };
635
- document.querySelectorAll("[data-codex-model-slider-fast]").forEach((button) => {
636
- const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
637
- const content = button.querySelector("[data-codex-model-slider-fast-content]");
638
- if (icon && content) content.replaceChildren(icon);
639
- });
640
- queueEnsure();
641
- } catch {
642
- // Leave the native control untouched when this private Codex asset moves.
643
- }
644
- }
645
-
646
621
  function nativeCssModuleClass(tokenName, root = document) {
647
622
  const pattern = new RegExp(`^_${tokenName}_[A-Za-z0-9_-]+$`);
648
623
  const live = Array.from(root.querySelectorAll(`[class*="_${tokenName}_"]`))
@@ -674,11 +649,33 @@ export function createInjectionSource(features, {
674
649
  return null;
675
650
  }
676
651
 
652
+ function captureNativeFastIcon() {
653
+ const capture = (state, pathPrefix) => {
654
+ if (modelSelectorNativeFastIcons?.[state]) return;
655
+ const path = Array.from(document.querySelectorAll("svg path")).find(
656
+ (candidate) => candidate.getAttribute("d")?.startsWith(pathPrefix),
657
+ );
658
+ const svg = path?.closest("svg");
659
+ if (!svg) return;
660
+ const template = svg.cloneNode(true);
661
+ template.removeAttribute("id");
662
+ template.removeAttribute("width");
663
+ template.removeAttribute("height");
664
+ if (!modelSelectorNativeFastIcons) modelSelectorNativeFastIcons = {};
665
+ modelSelectorNativeFastIcons[state] = template;
666
+ };
667
+ capture("active", "M11.9125 21.4125");
668
+ capture("inactive", "M7.38 16.2207");
669
+ return modelSelectorNativeFastIcons;
670
+ }
671
+
677
672
  function createNativeFastIcon(active) {
678
- const definition = modelSelectorNativeFastIcons?.[active ? "active" : "inactive"];
679
- const icon = createModelSelectorIcon(definition, { brand: true });
673
+ const template = captureNativeFastIcon()?.[active ? "active" : "inactive"];
674
+ if (!template) return null;
675
+ const icon = template.cloneNode(true);
676
+ icon.setAttribute("aria-hidden", "true");
680
677
  const iconClass = nativeCssModuleClass("FastModeIcon");
681
- if (icon && iconClass) icon.classList.add(iconClass);
678
+ if (iconClass && !icon.classList.contains(iconClass)) icon.classList.add(iconClass);
682
679
  return icon;
683
680
  }
684
681
 
@@ -852,7 +849,20 @@ export function createInjectionSource(features, {
852
849
  const aliases = (selector.models ?? []).filter((model) => (
853
850
  comparableModelLabel(model.slug) === target || comparableModelLabel(model.displayName) === target
854
851
  ));
855
- return aliases.length === 1 ? aliases[0] : null;
852
+ return resolveModelAlias(aliases, identity);
853
+ }
854
+
855
+ function resolveModelAlias(aliases, identity) {
856
+ if (aliases.length === 1) return aliases[0];
857
+ if (aliases.length < 2 || identity.provider) return null;
858
+ const native = aliases.find((model) => !model.slug.includes("/"));
859
+ const thirdParty = aliases.find((model) => model.slug.includes("/"));
860
+ if (!native || !thirdParty) return null;
861
+ if (!modelIdentity(native.slug).backend.toLowerCase().startsWith("gpt")) return null;
862
+ const nativeLabel = formatIdentifier(
863
+ modelIdentity(String(native.displayName ?? native.slug)).backend,
864
+ ).toLowerCase();
865
+ return formatIdentifier(identity.backend).toLowerCase() === nativeLabel ? native : thirdParty;
856
866
  }
857
867
 
858
868
  function loadModelFavorites(selector) {
@@ -1128,6 +1138,29 @@ export function createInjectionSource(features, {
1128
1138
  }, 260);
1129
1139
  }
1130
1140
 
1141
+ function nativeGptSortRank(rawValue) {
1142
+ const known = new Map([
1143
+ ["gpt-5.6-sol", 1], ["gpt-5.6-terra", 2], ["gpt-5.6-luna", 3],
1144
+ ["gpt-5.5", 4], ["gpt-5.4", 5], ["gpt-5.4-mini", 6], ["gpt-5.3-codex-spark", 7],
1145
+ ]);
1146
+ const raw = String(rawValue ?? "").trim();
1147
+ const knownRank = known.get(raw);
1148
+ if (knownRank != null) return { official: true, rank: knownRank };
1149
+ const identity = modelIdentity(raw);
1150
+ return identity.provider ? null : { official: true, rank: 100 };
1151
+ }
1152
+
1153
+ function compareEnhancedModelItems(a, b, favorites) {
1154
+ const favA = Number(favorites.has(a.dataset.codexModelSliderRaw));
1155
+ const favB = Number(favorites.has(b.dataset.codexModelSliderRaw));
1156
+ if (favA !== favB) return favB - favA;
1157
+ const rankA = nativeGptSortRank(a.dataset.codexModelSliderRaw);
1158
+ const rankB = nativeGptSortRank(b.dataset.codexModelSliderRaw);
1159
+ if (Boolean(rankA) !== Boolean(rankB)) return rankA ? -1 : 1;
1160
+ if (rankA && rankB) return rankA.rank - rankB.rank;
1161
+ return 0;
1162
+ }
1163
+
1131
1164
  function renderEnhancedModelMenu(menu, selector) {
1132
1165
  const items = modelMenuItems(menu).filter((item) => item.dataset.codexModelSliderRaw);
1133
1166
  if (!items.length) return;
@@ -1135,7 +1168,7 @@ export function createInjectionSource(features, {
1135
1168
  items.forEach((item) => { item.style.display = ""; });
1136
1169
  const starred = items.filter((item) => favorites.has(item.dataset.codexModelSliderRaw));
1137
1170
  const ordinary = items.filter((item) => !favorites.has(item.dataset.codexModelSliderRaw));
1138
- const desired = [...starred, ...ordinary];
1171
+ const desired = items.slice().sort((a, b) => compareEnhancedModelItems(a, b, favorites));
1139
1172
  const parent = items[0].parentElement;
1140
1173
  if (!parent || !desired.every((item) => item.parentElement === parent)) return;
1141
1174
  const current = Array.from(parent.children).filter((child) => child.hasAttribute?.("data-codex-model-slider-item"));
@@ -1148,7 +1181,10 @@ export function createInjectionSource(features, {
1148
1181
  divider.setAttribute("data-codex-model-slider-divider", "");
1149
1182
  divider.setAttribute("role", "separator");
1150
1183
  }
1151
- if (divider.nextElementSibling !== ordinary[0]) parent.insertBefore(divider, ordinary[0]);
1184
+ const firstOrdinary = desired.find((item) => !favorites.has(item.dataset.codexModelSliderRaw));
1185
+ if (firstOrdinary && divider.nextElementSibling !== firstOrdinary) {
1186
+ parent.insertBefore(divider, firstOrdinary);
1187
+ }
1152
1188
  } else {
1153
1189
  divider?.remove();
1154
1190
  }
@@ -1204,6 +1240,7 @@ export function createInjectionSource(features, {
1204
1240
  }
1205
1241
 
1206
1242
  function enhanceModelItem(item, menu, selector, index) {
1243
+ if (item.querySelector("[data-codex-model-slider-row]")) return;
1207
1244
  const observedRaw = modelItemRawValue(item);
1208
1245
  if (!observedRaw) return;
1209
1246
  if (!modelSelectorOriginalItems.has(item)) modelSelectorOriginalItems.set(item, item.innerHTML);
@@ -1215,7 +1252,6 @@ export function createInjectionSource(features, {
1215
1252
  item.dataset.codexModelSliderItem = "";
1216
1253
  item.dataset.codexModelSliderOrder = String(index);
1217
1254
  item.dataset.codexModelSliderIdentitySource = catalogModel ? "catalog" : nativeDescriptor ? "native" : "visible";
1218
- if (item.querySelector("[data-codex-model-slider-row]")) return;
1219
1255
 
1220
1256
  const identity = modelIdentity(raw);
1221
1257
  const display = modelDisplayLabel(identity, selector);
@@ -1808,8 +1844,14 @@ export function createInjectionSource(features, {
1808
1844
  const model = modelDefinitionFor(identity, selector);
1809
1845
  const efforts = model?.supportedReasoningLevels ?? [];
1810
1846
  if (!model || efforts.length < 2) return false;
1811
- const selectedEffort = document.querySelector("[data-codex-intelligence-trigger]")
1812
- ?.getAttribute("data-selected-reasoning-effort") ?? model.defaultReasoningLevel ?? efforts[0].effort;
1847
+ // 控制器推导的任务内实际 effort 优先,避免子任务/主任务切换后触发器属性滞后;
1848
+ // 触发器属性仅在控制器无法解析时作为实时回退,静态目录默认值最后兜底。
1849
+ const nativeModel = nativeCatalogModelFor(identity);
1850
+ const selectedEffort = nativeModel?.defaultReasoningLevel
1851
+ ?? document.querySelector("[data-codex-intelligence-trigger]")
1852
+ ?.getAttribute("data-selected-reasoning-effort")
1853
+ ?? model.defaultReasoningLevel
1854
+ ?? efforts[0].effort;
1813
1855
  const selectedServiceTier = nativeComposerModelController()?.selectedServiceTier;
1814
1856
  const serviceTierKey = selectedServiceTier?.id ?? selectedServiceTier ?? "standard";
1815
1857
  const key = `${identity.raw}:${selectedEffort}:${serviceTierKey}:${efforts.map((level) => level.effort).join(",")}`;
@@ -1818,7 +1860,6 @@ export function createInjectionSource(features, {
1818
1860
  parentMenu.firstElementChild
1819
1861
  ?.querySelector("[data-model-picker-power-slider] [class*='_TickRail_'] > span"),
1820
1862
  );
1821
- const nativeModel = nativeCatalogModelFor(identity);
1822
1863
  const templateWaitCount = Number(parentMenu.getAttribute("data-codex-model-slider-template-wait") ?? 0);
1823
1864
  if (nativeModel?.slug.toLowerCase().startsWith("gpt-") && !nativeTemplateReady && templateWaitCount < 3) {
1824
1865
  parentMenu.setAttribute("data-codex-model-slider-template-wait", String(templateWaitCount + 1));
@@ -1914,6 +1955,7 @@ export function createInjectionSource(features, {
1914
1955
  function ensureModelSelector() {
1915
1956
  const selector = modelSelectorFeature?.modelSelector;
1916
1957
  if (!selector) return;
1958
+ captureNativeFastIcon();
1917
1959
  pruneModelSubmenuPositionObservers();
1918
1960
  if (!modelSelectorStyle.isConnected) document.head?.append(modelSelectorStyle);
1919
1961
  enhanceComposerModelTrigger(selector);
@@ -2950,6 +2992,42 @@ export function createInjectionSource(features, {
2950
2992
  return null;
2951
2993
  }
2952
2994
 
2995
+ function nativeJsxRuntime(appModule) {
2996
+ const candidates = Object.values(appModule).filter((value) => {
2997
+ if (typeof value !== "function" || value.length !== 0) return false;
2998
+ let source;
2999
+ try {
3000
+ source = Function.prototype.toString.call(value);
3001
+ } catch {
3002
+ return false;
3003
+ }
3004
+ if (source.length > 1200) return false;
3005
+ if (
3006
+ source.includes("useState")
3007
+ || source.includes("memo_cache_sentinel")
3008
+ || source.includes("(0,")
3009
+ || source.startsWith("class ")
3010
+ ) return false;
3011
+ return source.includes("jsxs") && source.includes("Fragment");
3012
+ });
3013
+ for (const candidate of candidates) {
3014
+ let runtime;
3015
+ try {
3016
+ runtime = candidate();
3017
+ } catch {
3018
+ continue;
3019
+ }
3020
+ if (
3021
+ runtime
3022
+ && typeof runtime === "object"
3023
+ && typeof runtime.jsx === "function"
3024
+ && typeof runtime.jsxs === "function"
3025
+ && "Fragment" in runtime
3026
+ ) return runtime;
3027
+ }
3028
+ return null;
3029
+ }
3030
+
2953
3031
  async function loadNativeTabCapability() {
2954
3032
  if (nativeTabCapability) return nativeTabCapability;
2955
3033
  if (nativeTabCapabilityPromise) return nativeTabCapabilityPromise;
@@ -2958,28 +3036,24 @@ export function createInjectionSource(features, {
2958
3036
  const moduleUrl = await waitForNativeTabModuleUrl();
2959
3037
  if (!moduleUrl) throw new Error("Codex app-initial modulepreload was not found");
2960
3038
  const appModule = await import(moduleUrl);
2961
- if (typeof appModule.Gtt !== "function") {
2962
- throw new Error("Codex right-panel controller factory is unavailable");
2963
- }
2964
- if (typeof appModule.hkt !== "function" || typeof appModule.Skt !== "function") {
2965
- throw new Error("Codex React runtime factories are unavailable");
2966
- }
2967
- const controller = appModule.Gtt("right");
2968
- const jsx = appModule.hkt();
2969
- const React = appModule.Skt();
3039
+ const controller = Object.values(appModule).find((value) => (
3040
+ value
3041
+ && typeof value === "object"
3042
+ && typeof value.openTab === "function"
3043
+ && typeof value.closeTab === "function"
3044
+ && typeof value.activateTab === "function"
3045
+ && value.panelId === "right"
3046
+ ));
3047
+ if (!controller) throw new Error("Codex right-panel controller is unavailable");
3048
+ const jsx = nativeJsxRuntime(appModule);
2970
3049
  if (
2971
- typeof controller?.openTab !== "function"
2972
- || typeof controller?.activateTab !== "function"
2973
- || typeof controller?.closeTab !== "function"
2974
- || !controller?.tabById$
3050
+ !controller?.tabById$
2975
3051
  || typeof jsx?.jsx !== "function"
2976
- || typeof React?.useLayoutEffect !== "function"
2977
- || typeof React?.useRef !== "function"
3052
+ || typeof jsx?.jsxs !== "function"
2978
3053
  ) throw new Error("Codex native tab capability is incomplete");
2979
3054
  nativeTabCapability = {
2980
3055
  controller,
2981
3056
  jsx,
2982
- React,
2983
3057
  moduleAsset: moduleUrl.split("/").pop(),
2984
3058
  };
2985
3059
  nativeTabCapabilityError = null;
@@ -3016,38 +3090,10 @@ export function createInjectionSource(features, {
3016
3090
  const detailKey = `${feature.id}:${detail.id}`;
3017
3091
  let Component = nativeDetailComponents.get(detailKey);
3018
3092
  if (Component) return Component;
3019
- const { jsx, React } = capability;
3093
+ const { jsx } = capability;
3094
+ const frameName = surfaceFrameName(feature.id);
3020
3095
  Component = function CodexPersonalDetailTab() {
3021
- const elementRef = React.useRef(null);
3022
- const frameRef = React.useRef(null);
3023
- const recordKeyRef = React.useRef(null);
3024
- const frameNameRef = React.useRef(null);
3025
- frameNameRef.current ??= surfaceFrameName(feature.id);
3026
- React.useLayoutEffect(() => {
3027
- const element = elementRef.current;
3028
- const frame = frameRef.current;
3029
- if (!element || !frame) return undefined;
3030
- const instanceId = crypto.randomUUID?.()
3031
- ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
3032
- const recordKey = surfaceKey(feature.id, "native-detail", `${detail.id}:${instanceId}`);
3033
- recordKeyRef.current = recordKey;
3034
- registerSurface(recordKey, feature, "detail", detail.surfaceUrl, element, frame, detail.id);
3035
- let keys = nativeDetailSurfaceKeys.get(detailKey);
3036
- if (!keys) {
3037
- keys = new Set();
3038
- nativeDetailSurfaceKeys.set(detailKey, keys);
3039
- }
3040
- keys.add(recordKey);
3041
- queueTheme();
3042
- return () => {
3043
- keys.delete(recordKey);
3044
- if (keys.size === 0) nativeDetailSurfaceKeys.delete(detailKey);
3045
- removeSurfaceRecord(recordKey);
3046
- recordKeyRef.current = null;
3047
- };
3048
- }, []);
3049
3096
  return jsx.jsx("div", {
3050
- ref: elementRef,
3051
3097
  "data-codex-personal-native-detail": detailKey,
3052
3098
  style: {
3053
3099
  height: "100%",
@@ -3056,8 +3102,7 @@ export function createInjectionSource(features, {
3056
3102
  background: "var(--color-token-main-surface-primary)",
3057
3103
  },
3058
3104
  children: jsx.jsx("iframe", {
3059
- ref: frameRef,
3060
- name: frameNameRef.current,
3105
+ name: frameName,
3061
3106
  src: "about:blank",
3062
3107
  title: detail.label,
3063
3108
  allow: "clipboard-write",
@@ -3085,17 +3130,17 @@ export function createInjectionSource(features, {
3085
3130
  return reloaded;
3086
3131
  }
3087
3132
 
3088
- async function waitForNativeDetailTabMount(scope, controller, tabId, detailKey) {
3133
+ async function waitForNativeDetailTabElement(detailKey) {
3089
3134
  const deadline = performance.now() + 2_000;
3090
3135
  do {
3091
- let registered = false;
3092
- try {
3093
- registered = scope.get(controller.tabById$, tabId) != null;
3094
- } catch {}
3095
- if (registered && nativeDetailSurfaceKeys.has(detailKey)) return true;
3136
+ const element = document.querySelector(
3137
+ `[data-codex-personal-native-detail="${detailKey}"]`,
3138
+ );
3139
+ const frame = element?.querySelector("iframe");
3140
+ if (element && frame) return { element, frame };
3096
3141
  await new Promise((resolve) => setTimeout(resolve, 25));
3097
3142
  } while (performance.now() < deadline);
3098
- return false;
3143
+ return null;
3099
3144
  }
3100
3145
 
3101
3146
  async function openNativeDetailTab(feature, detail) {
@@ -3134,7 +3179,14 @@ export function createInjectionSource(features, {
3134
3179
  icon: nativeDetailIcon(feature, detail, jsx),
3135
3180
  isClosable: true,
3136
3181
  props: {},
3137
- onClose: () => nativeOpenTabSessions.delete(session),
3182
+ onClose: () => {
3183
+ nativeOpenTabSessions.delete(session);
3184
+ const closedKeys = nativeDetailSurfaceKeys.get(detailKey);
3185
+ if (closedKeys) {
3186
+ for (const key of closedKeys) removeSurfaceRecord(key);
3187
+ nativeDetailSurfaceKeys.delete(detailKey);
3188
+ }
3189
+ },
3138
3190
  });
3139
3191
  } catch (error) {
3140
3192
  if (isNewSession) nativeOpenTabSessions.delete(session);
@@ -3142,15 +3194,29 @@ export function createInjectionSource(features, {
3142
3194
  return null;
3143
3195
  }
3144
3196
 
3145
- if (!await waitForNativeDetailTabMount(scope, controller, tabId, detailKey)) {
3146
- if (isNewSession) nativeOpenTabSessions.delete(session);
3147
- if (!focusedExisting) {
3148
- try {
3149
- controller.closeTab(scope, tabId);
3150
- } catch {}
3197
+ if (!nativeDetailSurfaceKeys.has(detailKey)) {
3198
+ const mounted = await waitForNativeDetailTabElement(detailKey);
3199
+ if (!mounted) {
3200
+ if (isNewSession) nativeOpenTabSessions.delete(session);
3201
+ if (!focusedExisting) {
3202
+ try {
3203
+ controller.closeTab(scope, tabId);
3204
+ } catch {}
3205
+ }
3206
+ nativeTabCapabilityError = new Error("Codex native right-panel Tab did not mount");
3207
+ return null;
3151
3208
  }
3152
- nativeTabCapabilityError = new Error("Codex native right-panel Tab did not mount");
3153
- return null;
3209
+ const instanceId = crypto.randomUUID?.()
3210
+ ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
3211
+ const recordKey = surfaceKey(feature.id, "native-detail", `${detail.id}:${instanceId}`);
3212
+ registerSurface(recordKey, feature, "detail", detail.surfaceUrl, mounted.element, mounted.frame, detail.id);
3213
+ let keys = nativeDetailSurfaceKeys.get(detailKey);
3214
+ if (!keys) {
3215
+ keys = new Set();
3216
+ nativeDetailSurfaceKeys.set(detailKey, keys);
3217
+ }
3218
+ keys.add(recordKey);
3219
+ queueTheme();
3154
3220
  }
3155
3221
 
3156
3222
  nativeTabCapabilityError = null;
@@ -3168,6 +3234,9 @@ export function createInjectionSource(features, {
3168
3234
  }
3169
3235
 
3170
3236
  function closeNativeDetailTabs() {
3237
+ for (const keys of Array.from(nativeDetailSurfaceKeys.values())) {
3238
+ for (const key of keys) removeSurfaceRecord(key);
3239
+ }
3171
3240
  for (const session of Array.from(nativeOpenTabSessions)) {
3172
3241
  try {
3173
3242
  session.controller.closeTab(session.scope, session.tabId);
@@ -3884,7 +3953,7 @@ export class CodexRuntime {
3884
3953
  cdpPort = 9231,
3885
3954
  logger = console,
3886
3955
  fetchImpl = globalThis.fetch,
3887
- spawnProcess = spawn,
3956
+ launchApplication = null,
3888
3957
  connectClient = connect,
3889
3958
  sleep = (timeoutMs) => new Promise((resolve) => setTimeout(resolve, timeoutMs)),
3890
3959
  availabilityTimeoutMs = 20_000,
@@ -3903,7 +3972,7 @@ export class CodexRuntime {
3903
3972
  this.cdpPort = cdpPort;
3904
3973
  this.logger = logger;
3905
3974
  this.fetchImpl = fetchImpl;
3906
- this.spawnProcess = spawnProcess;
3975
+ this.launchApplication = launchApplication;
3907
3976
  this.connectClient = connectClient;
3908
3977
  this.sleep = sleep;
3909
3978
  this.availabilityTimeoutMs = availabilityTimeoutMs;
@@ -4040,17 +4109,19 @@ export class CodexRuntime {
4040
4109
  }
4041
4110
 
4042
4111
  launchManagedCodex() {
4043
- const child = this.spawnProcess(
4044
- this.appPath,
4045
- [
4112
+ if (typeof this.launchApplication !== "function") {
4113
+ throw new Error("CodexRuntime requires an explicit application launcher for isolated tests.");
4114
+ }
4115
+ const child = this.launchApplication({
4116
+ appPath: this.appPath,
4117
+ args: [
4046
4118
  `--user-data-dir=${this.profileDir}`,
4047
4119
  "--remote-debugging-address=127.0.0.1",
4048
4120
  `--remote-debugging-port=${this.cdpPort}`,
4049
4121
  `--remote-allow-origins=http://127.0.0.1:${this.cdpPort}`,
4050
4122
  "--no-first-run",
4051
4123
  ],
4052
- { detached: true, stdio: "ignore" },
4053
- );
4124
+ });
4054
4125
  this.managedCodexChild = child;
4055
4126
  this.appPid = child.pid ?? null;
4056
4127
  this.notifyManagedCodexPidChange(this.appPid);
@@ -17,7 +17,6 @@ const featuresRoot = process.env.CODEX_FEATURES_ROOT ?? path.dirname(runtimeRoot
17
17
  const legacyDataDir = process.env.CODEX_IMAGE_HOST_DATA_DIR ?? path.join(os.homedir(), ".codex-image-host");
18
18
  const profileDir = process.env.CODEX_RUNTIME_PROFILE_DIR ?? path.join(legacyDataDir, "codex-profile");
19
19
  const cdpPort = Number(process.env.CODEX_RUNTIME_CDP_PORT ?? 9231);
20
- const launchCodex = process.env.MINECODEX_LAUNCH_CODEX === "1";
21
20
 
22
21
  const discoveredFeatures = await discoverFeatures(featuresRoot);
23
22
  if (!discoveredFeatures.length) throw new Error(`No codex-feature.json files found under ${featuresRoot}`);
@@ -66,13 +65,12 @@ runtime = new CodexRuntime({
66
65
  featureInstanceId,
67
66
  profileDir,
68
67
  cdpPort,
69
- appPath: process.env.MINECODEX_CODEX_EXECUTABLE || undefined,
70
68
  onManagedCodexPidChange: updateCodexPidFile,
71
69
  onStatusChange: requestReadyFileRefresh,
72
70
  });
73
71
 
74
72
  try {
75
- await runtime.start({ launchCodex });
73
+ await runtime.start();
76
74
  runtimeReady = true;
77
75
  await writeReadyFile();
78
76
  } catch (error) {