minecodex 0.1.16 → 0.1.19
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.
package/package.json
CHANGED
|
@@ -613,6 +613,18 @@ export class MacPlatformAdapter {
|
|
|
613
613
|
return true;
|
|
614
614
|
}
|
|
615
615
|
|
|
616
|
+
async codexProcessStartedMsAgo(processInfo) {
|
|
617
|
+
if (!processInfo || !Number.isInteger(processInfo.pid) || processInfo.pid <= 1) return null;
|
|
618
|
+
const { stdout } = await this.execFile("/bin/ps", ["-axo", "pid=,lstart="]);
|
|
619
|
+
const line = String(stdout).split("\n").find((row) => {
|
|
620
|
+
const match = row.match(/\s*(\d+)\s+(.*)/);
|
|
621
|
+
return match?.[1] === String(processInfo.pid);
|
|
622
|
+
});
|
|
623
|
+
const startedAt = line ? Date.parse(line.replace(/^\s*\d+\s+/, "")) : Number.NaN;
|
|
624
|
+
if (!line || !Number.isFinite(startedAt)) return null;
|
|
625
|
+
return Math.max(0, Date.now() - startedAt);
|
|
626
|
+
}
|
|
627
|
+
|
|
616
628
|
async terminateNativeCodex(pid, expectedCommand = null) {
|
|
617
629
|
if (!Number.isInteger(pid) || pid <= 1) {
|
|
618
630
|
throw new Error("MineCodex requires an exact ChatGPT PID to terminate.");
|
|
@@ -120,7 +120,8 @@ export class RuntimeManager {
|
|
|
120
120
|
waitForReady: readyWaiter = waitForReady,
|
|
121
121
|
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
122
122
|
stopTimeoutMs = 5_000,
|
|
123
|
-
manualLaunchPollMs =
|
|
123
|
+
manualLaunchPollMs = 150,
|
|
124
|
+
manualTakeoverGraceMs = 60_000,
|
|
124
125
|
cdpPort = Number(process.env.CODEX_RUNTIME_CDP_PORT ?? 9231),
|
|
125
126
|
launchCoordinator = null,
|
|
126
127
|
}) {
|
|
@@ -132,6 +133,7 @@ export class RuntimeManager {
|
|
|
132
133
|
this.sleep = sleep;
|
|
133
134
|
this.stopTimeoutMs = stopTimeoutMs;
|
|
134
135
|
this.manualLaunchPollMs = manualLaunchPollMs;
|
|
136
|
+
this.manualTakeoverGraceMs = manualTakeoverGraceMs;
|
|
135
137
|
this.cdpPort = cdpPort;
|
|
136
138
|
this.child = null;
|
|
137
139
|
this.appliedFeatures = [];
|
|
@@ -327,6 +329,19 @@ export class RuntimeManager {
|
|
|
327
329
|
this.manualLaunchArmed = false;
|
|
328
330
|
}
|
|
329
331
|
|
|
332
|
+
async isManualTakeoverAllowed(processInfo) {
|
|
333
|
+
if (typeof this.platform?.codexProcessStartedMsAgo !== "function") return true;
|
|
334
|
+
let startedMsAgo;
|
|
335
|
+
try {
|
|
336
|
+
startedMsAgo = await this.platform.codexProcessStartedMsAgo(processInfo);
|
|
337
|
+
} catch (error) {
|
|
338
|
+
this.logger.warn?.("MineCodex could not read the ChatGPT start time", error.message);
|
|
339
|
+
return true;
|
|
340
|
+
}
|
|
341
|
+
if (!Number.isInteger(startedMsAgo) || startedMsAgo < 0) return true;
|
|
342
|
+
return startedMsAgo < this.manualTakeoverGraceMs;
|
|
343
|
+
}
|
|
344
|
+
|
|
330
345
|
async reconcileManualCodexLaunch() {
|
|
331
346
|
if (this.manualLaunchMonitorStopped || this.manualLaunchSuppressionDepth > 0) return false;
|
|
332
347
|
const snapshot = await this.nativeCodexSnapshot();
|
|
@@ -372,6 +387,14 @@ export class RuntimeManager {
|
|
|
372
387
|
return false;
|
|
373
388
|
}
|
|
374
389
|
try {
|
|
390
|
+
if (!(await this.isManualTakeoverAllowed(currentProcess))) {
|
|
391
|
+
this.enhancementFailure = {
|
|
392
|
+
code: "OFFICIAL_CHATGPT_IN_USE",
|
|
393
|
+
message: "MineCodex left the official ChatGPT instance running because it was already in use.",
|
|
394
|
+
phase: "launch",
|
|
395
|
+
};
|
|
396
|
+
return null;
|
|
397
|
+
}
|
|
375
398
|
const result = await this.launchCoordinator.ensureObservedLaunch(currentProcess, {
|
|
376
399
|
stopRuntime: () => this.stopInternal(),
|
|
377
400
|
startRuntime: () => this.startInternal(),
|
|
@@ -298,6 +298,8 @@ export function createInjectionSource(features, {
|
|
|
298
298
|
let modelSelectorTriggerObserver = null;
|
|
299
299
|
let modelSelectorObservedTrigger = null;
|
|
300
300
|
let modelSelectorNativeFastIcons = null;
|
|
301
|
+
// Fast 图标来源标记:bundle 静态提取 vs DOM 克隆,DOM 克隆优先以保持像素一致。
|
|
302
|
+
let modelSelectorNativeFastIconSources = null;
|
|
301
303
|
const modelSelectorStyle = document.createElement("style");
|
|
302
304
|
modelSelectorStyle.setAttribute("data-codex-model-slider-style", "");
|
|
303
305
|
modelSelectorStyle.textContent = `
|
|
@@ -376,6 +378,17 @@ export function createInjectionSource(features, {
|
|
|
376
378
|
if (modelSelectorFeature) {
|
|
377
379
|
document.documentElement.setAttribute("data-codex-model-slider-catalog-ready", "");
|
|
378
380
|
document.documentElement.append(modelSelectorStyle);
|
|
381
|
+
// 启动阶段就解析 Fast 图标:先读持久化的原生克隆,再从 app bundle 静态提取兜底,
|
|
382
|
+
// 避免首次会话必须等用户点开弹窗后 DOM 里才出现图标。
|
|
383
|
+
const persistedIcons = loadPersistedNativeFastIcons();
|
|
384
|
+
if (persistedIcons) {
|
|
385
|
+
modelSelectorNativeFastIcons = persistedIcons;
|
|
386
|
+
modelSelectorNativeFastIconSources = {};
|
|
387
|
+
for (const state of Object.keys(persistedIcons)) {
|
|
388
|
+
modelSelectorNativeFastIconSources[state] = "persisted";
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
void loadNativeFastIconFromBundle();
|
|
379
392
|
}
|
|
380
393
|
|
|
381
394
|
for (const feature of pageScriptFeatures) {
|
|
@@ -649,9 +662,99 @@ export function createInjectionSource(features, {
|
|
|
649
662
|
return null;
|
|
650
663
|
}
|
|
651
664
|
|
|
665
|
+
const modelSelectorFastIconStorageKey = "codex-model-slider:fast-icons:v1";
|
|
666
|
+
|
|
667
|
+
function persistNativeFastIcons() {
|
|
668
|
+
if (!modelSelectorNativeFastIcons) return;
|
|
669
|
+
try {
|
|
670
|
+
const payload = {};
|
|
671
|
+
for (const [state, template] of Object.entries(modelSelectorNativeFastIcons)) {
|
|
672
|
+
payload[state] = template.outerHTML;
|
|
673
|
+
}
|
|
674
|
+
localStorage.setItem(modelSelectorFastIconStorageKey, JSON.stringify(payload));
|
|
675
|
+
} catch {
|
|
676
|
+
// localStorage 不可用时静默降级,仅本次会话内生效。
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function loadPersistedNativeFastIcons() {
|
|
681
|
+
try {
|
|
682
|
+
const payload = JSON.parse(localStorage.getItem(modelSelectorFastIconStorageKey) ?? "null");
|
|
683
|
+
if (!payload || typeof payload !== "object") return null;
|
|
684
|
+
const icons = {};
|
|
685
|
+
for (const [state, markup] of Object.entries(payload)) {
|
|
686
|
+
if (typeof markup !== "string") continue;
|
|
687
|
+
const container = document.createElement("template");
|
|
688
|
+
container.innerHTML = markup;
|
|
689
|
+
const svg = container.content.querySelector("svg");
|
|
690
|
+
if (svg) icons[state] = svg;
|
|
691
|
+
}
|
|
692
|
+
return Object.keys(icons).length ? icons : null;
|
|
693
|
+
} catch {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function extractFastIconPath(source, prefix) {
|
|
699
|
+
const match = String(source).match(new RegExp(`d:\`(${prefix}[^\`]*)\``));
|
|
700
|
+
return match?.[1] ?? null;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async function loadNativeFastIconFromBundle() {
|
|
704
|
+
const sourceLink = document.querySelector('link[href*="/assets/app-initial-"][href$=".js"]');
|
|
705
|
+
if (!sourceLink?.href) return;
|
|
706
|
+
try {
|
|
707
|
+
const source = await fetch(sourceLink.href).then((response) => response.text());
|
|
708
|
+
if (!modelSelectorNativeFastIcons) {
|
|
709
|
+
modelSelectorNativeFastIcons = {};
|
|
710
|
+
modelSelectorNativeFastIconSources = {};
|
|
711
|
+
}
|
|
712
|
+
let gained = false;
|
|
713
|
+
const define = (state, prefix, definition) => {
|
|
714
|
+
if (modelSelectorNativeFastIcons[state]) return;
|
|
715
|
+
const path = extractFastIconPath(source, prefix);
|
|
716
|
+
if (!path) return;
|
|
717
|
+
const svg = createModelSelectorIcon({
|
|
718
|
+
viewBox: definition.viewBox,
|
|
719
|
+
markup: definition.transform
|
|
720
|
+
? `<g transform="${definition.transform}"><path d="${path}" fill="currentColor" /></g>`
|
|
721
|
+
: `<path d="${path}" fill="currentColor" />`,
|
|
722
|
+
}, { brand: true });
|
|
723
|
+
if (!svg) return;
|
|
724
|
+
modelSelectorNativeFastIcons[state] = svg;
|
|
725
|
+
modelSelectorNativeFastIconSources[state] = "bundle";
|
|
726
|
+
gained = true;
|
|
727
|
+
};
|
|
728
|
+
define("active", "M11\\.9125 21\\.4125", { viewBox: "0 0 24 24" });
|
|
729
|
+
define("inactive", "M7\\.38 16\\.2207", { viewBox: "0 0 20 20", transform: "translate(2.43 1.609)" });
|
|
730
|
+
if (gained) {
|
|
731
|
+
persistNativeFastIcons();
|
|
732
|
+
applyNativeFastIconsToShells();
|
|
733
|
+
queueEnsure();
|
|
734
|
+
}
|
|
735
|
+
} catch {
|
|
736
|
+
// 私有资源移动时保持原生控件不变,等待 DOM 克隆兜底。
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
function hasVisibleFastControl() {
|
|
741
|
+
return Boolean(
|
|
742
|
+
document.querySelector("[data-codex-model-slider-fast]")
|
|
743
|
+
|| document.querySelector('[class*="_FastModeToggle_"]')
|
|
744
|
+
|| document.querySelector('[class*="_ModelPickerTriggerFastIndicator_"] svg'),
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
|
|
652
748
|
function captureNativeFastIcon() {
|
|
749
|
+
// Fast 图标只在 Fast 控件渲染后才出现在 DOM(打开菜单或 GPT 模型);
|
|
750
|
+
// 启动阶段已有 bundle/持久化兜底,因此无可见 Fast 控件时不做全页扫描。
|
|
751
|
+
if (!hasVisibleFastControl()) {
|
|
752
|
+
return modelSelectorNativeFastIcons;
|
|
753
|
+
}
|
|
653
754
|
const capture = (state, pathPrefix) => {
|
|
654
|
-
|
|
755
|
+
const current = modelSelectorNativeFastIcons?.[state];
|
|
756
|
+
const currentSource = modelSelectorNativeFastIconSources?.[state];
|
|
757
|
+
if (current && currentSource === "dom") return;
|
|
655
758
|
const path = Array.from(document.querySelectorAll("svg path")).find(
|
|
656
759
|
(candidate) => candidate.getAttribute("d")?.startsWith(pathPrefix),
|
|
657
760
|
);
|
|
@@ -661,14 +764,33 @@ export function createInjectionSource(features, {
|
|
|
661
764
|
template.removeAttribute("id");
|
|
662
765
|
template.removeAttribute("width");
|
|
663
766
|
template.removeAttribute("height");
|
|
664
|
-
if (!modelSelectorNativeFastIcons)
|
|
767
|
+
if (!modelSelectorNativeFastIcons) {
|
|
768
|
+
modelSelectorNativeFastIcons = {};
|
|
769
|
+
modelSelectorNativeFastIconSources = {};
|
|
770
|
+
}
|
|
665
771
|
modelSelectorNativeFastIcons[state] = template;
|
|
772
|
+
modelSelectorNativeFastIconSources[state] = "dom";
|
|
666
773
|
};
|
|
667
774
|
capture("active", "M11.9125 21.4125");
|
|
668
775
|
capture("inactive", "M7.38 16.2207");
|
|
776
|
+
const ready = Boolean(
|
|
777
|
+
modelSelectorNativeFastIcons?.active && modelSelectorNativeFastIcons?.inactive,
|
|
778
|
+
);
|
|
779
|
+
if (ready) {
|
|
780
|
+
persistNativeFastIcons();
|
|
781
|
+
applyNativeFastIconsToShells();
|
|
782
|
+
}
|
|
669
783
|
return modelSelectorNativeFastIcons;
|
|
670
784
|
}
|
|
671
785
|
|
|
786
|
+
function applyNativeFastIconsToShells() {
|
|
787
|
+
document.querySelectorAll("[data-codex-model-slider-fast]").forEach((button) => {
|
|
788
|
+
const icon = createNativeFastIcon(button.getAttribute("aria-pressed") === "true");
|
|
789
|
+
const content = button.querySelector("[data-codex-model-slider-fast-content]");
|
|
790
|
+
if (icon && content) content.replaceChildren(icon);
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
|
|
672
794
|
function createNativeFastIcon(active) {
|
|
673
795
|
const template = captureNativeFastIcon()?.[active ? "active" : "inactive"];
|
|
674
796
|
if (!template) return null;
|
|
@@ -2992,42 +3114,6 @@ export function createInjectionSource(features, {
|
|
|
2992
3114
|
return null;
|
|
2993
3115
|
}
|
|
2994
3116
|
|
|
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
|
-
|
|
3031
3117
|
async function loadNativeTabCapability() {
|
|
3032
3118
|
if (nativeTabCapability) return nativeTabCapability;
|
|
3033
3119
|
if (nativeTabCapabilityPromise) return nativeTabCapabilityPromise;
|
|
@@ -3045,15 +3131,9 @@ export function createInjectionSource(features, {
|
|
|
3045
3131
|
&& value.panelId === "right"
|
|
3046
3132
|
));
|
|
3047
3133
|
if (!controller) throw new Error("Codex right-panel controller is unavailable");
|
|
3048
|
-
|
|
3049
|
-
if (
|
|
3050
|
-
!controller?.tabById$
|
|
3051
|
-
|| typeof jsx?.jsx !== "function"
|
|
3052
|
-
|| typeof jsx?.jsxs !== "function"
|
|
3053
|
-
) throw new Error("Codex native tab capability is incomplete");
|
|
3134
|
+
if (!controller?.tabById$) throw new Error("Codex native tab capability is incomplete");
|
|
3054
3135
|
nativeTabCapability = {
|
|
3055
3136
|
controller,
|
|
3056
|
-
jsx,
|
|
3057
3137
|
moduleAsset: moduleUrl.split("/").pop(),
|
|
3058
3138
|
};
|
|
3059
3139
|
nativeTabCapabilityError = null;
|
|
@@ -3068,10 +3148,20 @@ export function createInjectionSource(features, {
|
|
|
3068
3148
|
return capability;
|
|
3069
3149
|
}
|
|
3070
3150
|
|
|
3071
|
-
function
|
|
3151
|
+
function nativeReactElement(type, props) {
|
|
3152
|
+
return {
|
|
3153
|
+
$$typeof: Symbol.for("react.transitional.element"),
|
|
3154
|
+
type,
|
|
3155
|
+
key: null,
|
|
3156
|
+
ref: null,
|
|
3157
|
+
props: props ?? {},
|
|
3158
|
+
};
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
function nativeDetailIcon(feature, detail) {
|
|
3072
3162
|
const icon = detail.icon ?? feature.icon;
|
|
3073
3163
|
if (!icon?.markup) return undefined;
|
|
3074
|
-
return
|
|
3164
|
+
return nativeReactElement("svg", {
|
|
3075
3165
|
width: 16,
|
|
3076
3166
|
height: 16,
|
|
3077
3167
|
viewBox: icon.viewBox ?? "0 0 24 24",
|
|
@@ -3086,14 +3176,13 @@ export function createInjectionSource(features, {
|
|
|
3086
3176
|
});
|
|
3087
3177
|
}
|
|
3088
3178
|
|
|
3089
|
-
function nativeDetailComponent(feature, detail
|
|
3179
|
+
function nativeDetailComponent(feature, detail) {
|
|
3090
3180
|
const detailKey = `${feature.id}:${detail.id}`;
|
|
3091
3181
|
let Component = nativeDetailComponents.get(detailKey);
|
|
3092
3182
|
if (Component) return Component;
|
|
3093
|
-
const { jsx } = capability;
|
|
3094
3183
|
const frameName = surfaceFrameName(feature.id);
|
|
3095
3184
|
Component = function CodexPersonalDetailTab() {
|
|
3096
|
-
return
|
|
3185
|
+
return nativeReactElement("div", {
|
|
3097
3186
|
"data-codex-personal-native-detail": detailKey,
|
|
3098
3187
|
style: {
|
|
3099
3188
|
height: "100%",
|
|
@@ -3101,7 +3190,7 @@ export function createInjectionSource(features, {
|
|
|
3101
3190
|
overflow: "hidden",
|
|
3102
3191
|
background: "var(--color-token-main-surface-primary)",
|
|
3103
3192
|
},
|
|
3104
|
-
children:
|
|
3193
|
+
children: nativeReactElement("iframe", {
|
|
3105
3194
|
name: frameName,
|
|
3106
3195
|
src: "about:blank",
|
|
3107
3196
|
title: detail.label,
|
|
@@ -3152,7 +3241,7 @@ export function createInjectionSource(features, {
|
|
|
3152
3241
|
return null;
|
|
3153
3242
|
}
|
|
3154
3243
|
|
|
3155
|
-
const { controller
|
|
3244
|
+
const { controller } = capability;
|
|
3156
3245
|
const tabId = `${feature.id}-${detail.id}`;
|
|
3157
3246
|
const detailKey = `${feature.id}:${detail.id}`;
|
|
3158
3247
|
let session = Array.from(nativeOpenTabSessions).find((candidate) => (
|
|
@@ -3171,12 +3260,12 @@ export function createInjectionSource(features, {
|
|
|
3171
3260
|
}
|
|
3172
3261
|
|
|
3173
3262
|
try {
|
|
3174
|
-
controller.openTab(scope, nativeDetailComponent(feature, detail
|
|
3263
|
+
controller.openTab(scope, nativeDetailComponent(feature, detail), {
|
|
3175
3264
|
id: tabId,
|
|
3176
3265
|
kind: tabId,
|
|
3177
3266
|
title: detail.label,
|
|
3178
3267
|
tooltip: detail.label,
|
|
3179
|
-
icon: nativeDetailIcon(feature, detail
|
|
3268
|
+
icon: nativeDetailIcon(feature, detail),
|
|
3180
3269
|
isClosable: true,
|
|
3181
3270
|
props: {},
|
|
3182
3271
|
onClose: () => {
|