minecodex 0.1.16 → 0.1.18

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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 = 1_000,
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(),
@@ -2992,42 +2992,6 @@ export function createInjectionSource(features, {
2992
2992
  return null;
2993
2993
  }
2994
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
-
3031
2995
  async function loadNativeTabCapability() {
3032
2996
  if (nativeTabCapability) return nativeTabCapability;
3033
2997
  if (nativeTabCapabilityPromise) return nativeTabCapabilityPromise;
@@ -3045,15 +3009,9 @@ export function createInjectionSource(features, {
3045
3009
  && value.panelId === "right"
3046
3010
  ));
3047
3011
  if (!controller) throw new Error("Codex right-panel controller is unavailable");
3048
- const jsx = nativeJsxRuntime(appModule);
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");
3012
+ if (!controller?.tabById$) throw new Error("Codex native tab capability is incomplete");
3054
3013
  nativeTabCapability = {
3055
3014
  controller,
3056
- jsx,
3057
3015
  moduleAsset: moduleUrl.split("/").pop(),
3058
3016
  };
3059
3017
  nativeTabCapabilityError = null;
@@ -3068,10 +3026,20 @@ export function createInjectionSource(features, {
3068
3026
  return capability;
3069
3027
  }
3070
3028
 
3071
- function nativeDetailIcon(feature, detail, jsx) {
3029
+ function nativeReactElement(type, props) {
3030
+ return {
3031
+ $$typeof: Symbol.for("react.transitional.element"),
3032
+ type,
3033
+ key: null,
3034
+ ref: null,
3035
+ props: props ?? {},
3036
+ };
3037
+ }
3038
+
3039
+ function nativeDetailIcon(feature, detail) {
3072
3040
  const icon = detail.icon ?? feature.icon;
3073
3041
  if (!icon?.markup) return undefined;
3074
- return jsx.jsx("svg", {
3042
+ return nativeReactElement("svg", {
3075
3043
  width: 16,
3076
3044
  height: 16,
3077
3045
  viewBox: icon.viewBox ?? "0 0 24 24",
@@ -3086,14 +3054,13 @@ export function createInjectionSource(features, {
3086
3054
  });
3087
3055
  }
3088
3056
 
3089
- function nativeDetailComponent(feature, detail, capability) {
3057
+ function nativeDetailComponent(feature, detail) {
3090
3058
  const detailKey = `${feature.id}:${detail.id}`;
3091
3059
  let Component = nativeDetailComponents.get(detailKey);
3092
3060
  if (Component) return Component;
3093
- const { jsx } = capability;
3094
3061
  const frameName = surfaceFrameName(feature.id);
3095
3062
  Component = function CodexPersonalDetailTab() {
3096
- return jsx.jsx("div", {
3063
+ return nativeReactElement("div", {
3097
3064
  "data-codex-personal-native-detail": detailKey,
3098
3065
  style: {
3099
3066
  height: "100%",
@@ -3101,7 +3068,7 @@ export function createInjectionSource(features, {
3101
3068
  overflow: "hidden",
3102
3069
  background: "var(--color-token-main-surface-primary)",
3103
3070
  },
3104
- children: jsx.jsx("iframe", {
3071
+ children: nativeReactElement("iframe", {
3105
3072
  name: frameName,
3106
3073
  src: "about:blank",
3107
3074
  title: detail.label,
@@ -3152,7 +3119,7 @@ export function createInjectionSource(features, {
3152
3119
  return null;
3153
3120
  }
3154
3121
 
3155
- const { controller, jsx } = capability;
3122
+ const { controller } = capability;
3156
3123
  const tabId = `${feature.id}-${detail.id}`;
3157
3124
  const detailKey = `${feature.id}:${detail.id}`;
3158
3125
  let session = Array.from(nativeOpenTabSessions).find((candidate) => (
@@ -3171,12 +3138,12 @@ export function createInjectionSource(features, {
3171
3138
  }
3172
3139
 
3173
3140
  try {
3174
- controller.openTab(scope, nativeDetailComponent(feature, detail, capability), {
3141
+ controller.openTab(scope, nativeDetailComponent(feature, detail), {
3175
3142
  id: tabId,
3176
3143
  kind: tabId,
3177
3144
  title: detail.label,
3178
3145
  tooltip: detail.label,
3179
- icon: nativeDetailIcon(feature, detail, jsx),
3146
+ icon: nativeDetailIcon(feature, detail),
3180
3147
  isClosable: true,
3181
3148
  props: {},
3182
3149
  onClose: () => {