minecodex 0.1.2 → 0.1.3
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/README.md +5 -3
- package/features/images/codex-feature.json +1 -1
- package/features/model-slider/codex-feature.json +1 -1
- package/features/notes/codex-feature.json +1 -1
- package/package.json +1 -1
- package/packages/cli/src/commands.mjs +23 -27
- package/packages/cli/src/runtime-manager.mjs +4 -2
- package/packages/runtime-host/src/codex-runtime.mjs +23 -86
- package/packages/runtime-host/src/main.mjs +2 -1
package/README.md
CHANGED
|
@@ -17,9 +17,11 @@ npm install -g minecodex
|
|
|
17
17
|
mcx install
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
`mcx install`
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
`mcx install` 不会关闭或启动 Codex,也不会改变当前窗口。安装完成后,终端会提示
|
|
21
|
+
MineCodex 将在下次重启 Codex 时启用,并询问是否现在重启;默认答案是 **No**。
|
|
22
|
+
只有用户明确确认,或之后主动运行 `mcx restart`,MineCodex 才会关闭并重新打开
|
|
23
|
+
Codex。安装后,macOS 的“登录项与扩展”中会显示名为 **MineCodex** 的后台项目;
|
|
24
|
+
这个后台项目负责插件与本地控制台,不会在用户退出 Codex 后自动将它重新打开。
|
|
23
25
|
|
|
24
26
|
安装后三个插件默认开启。打开本地控制台:
|
|
25
27
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { access, readFile, rm } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
3
4
|
import { assertNodeSupported, MacPlatformAdapter } from "./platform.mjs";
|
|
4
5
|
import { ensureConfig, ensureControlToken, readConfig } from "./config.mjs";
|
|
5
6
|
import { createControlServer } from "./control-server.mjs";
|
|
@@ -98,37 +99,32 @@ async function restoreService(platform, snapshot, { cliPath, paths }) {
|
|
|
98
99
|
return platform.restoreServiceState(snapshot, { cliPath, paths });
|
|
99
100
|
}
|
|
100
101
|
|
|
101
|
-
async function nativeSnapshot(platform) {
|
|
102
|
-
if (platform.snapshotNativeCodexState) return platform.snapshotNativeCodexState();
|
|
103
|
-
return { count: 0 };
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
async function restoreNative(platform, snapshot) {
|
|
107
|
-
if (!snapshot?.count) return;
|
|
108
|
-
if (platform.restoreNativeCodexState) {
|
|
109
|
-
await platform.restoreNativeCodexState(snapshot);
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
await platform.openNativeCodex?.();
|
|
113
|
-
}
|
|
114
|
-
|
|
115
102
|
function appendRollbackError(error, rollbackError) {
|
|
116
103
|
if (!rollbackError) return error;
|
|
117
104
|
return new Error(`${error.message} Automatic rollback also failed: ${rollbackError.message}`, { cause: error });
|
|
118
105
|
}
|
|
119
106
|
|
|
120
|
-
async function
|
|
107
|
+
export async function confirmRestartNow({ input = process.stdin, terminalOutput = process.stdout } = {}) {
|
|
108
|
+
if (!input.isTTY || !terminalOutput.isTTY) return false;
|
|
109
|
+
const prompt = createInterface({ input, output: terminalOutput });
|
|
110
|
+
try {
|
|
111
|
+
const answer = await prompt.question("Restart Codex now? [y/N] ");
|
|
112
|
+
return /^(y|yes)$/i.test(answer.trim());
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
} finally {
|
|
116
|
+
prompt.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function installCommand({ paths, platform, cliPath, output, serviceProbe, fetchImpl, confirmRestart, nodeVersion = process.versions.node }) {
|
|
121
121
|
assertNodeSupported(nodeVersion);
|
|
122
122
|
platform.assertInstallSupported();
|
|
123
123
|
await platform.assertCodexInstalled();
|
|
124
124
|
const previousService = await serviceSnapshot(platform, paths);
|
|
125
|
-
const previousNative = await nativeSnapshot(platform);
|
|
126
125
|
await ensureConfig(paths.configPath);
|
|
127
126
|
await ensureControlToken(paths.tokenPath);
|
|
128
|
-
let nativeCount = 0;
|
|
129
127
|
try {
|
|
130
|
-
nativeCount = await platform.terminateNativeCodex();
|
|
131
|
-
if (nativeCount) output(`Restarting ${nativeCount} running Codex instance${nativeCount === 1 ? "" : "s"} with MineCodex enabled…`);
|
|
132
128
|
await platform.installService({ cliPath, paths, start: true });
|
|
133
129
|
const waitForServiceImpl = serviceProbe?.waitForService ?? waitForService;
|
|
134
130
|
const waitForManagedReadyImpl = serviceProbe?.waitForManagedReady ?? waitForManagedReady;
|
|
@@ -143,15 +139,14 @@ async function installCommand({ paths, platform, cliPath, output, serviceProbe,
|
|
|
143
139
|
} catch (failure) {
|
|
144
140
|
rollbackError = failure;
|
|
145
141
|
}
|
|
146
|
-
try {
|
|
147
|
-
await restoreNative(platform, previousNative?.count ? previousNative : { count: nativeCount });
|
|
148
|
-
} catch (failure) {
|
|
149
|
-
rollbackError = rollbackError ?? failure;
|
|
150
|
-
}
|
|
151
142
|
throw appendRollbackError(error, rollbackError);
|
|
152
143
|
}
|
|
153
|
-
output("MineCodex installed.
|
|
154
|
-
|
|
144
|
+
output("MineCodex installed. It will be enabled the next time Codex restarts.");
|
|
145
|
+
if (await confirmRestart()) {
|
|
146
|
+
await restartCommand({ paths, output, fetchImpl });
|
|
147
|
+
} else {
|
|
148
|
+
output("Run `mcx restart` when you are ready.");
|
|
149
|
+
}
|
|
155
150
|
}
|
|
156
151
|
|
|
157
152
|
async function openCommand({ paths, platform, serviceProbe, fetchImpl }) {
|
|
@@ -377,12 +372,13 @@ export async function runCli(argv, {
|
|
|
377
372
|
npm = createNpmAdapter(),
|
|
378
373
|
serviceProbe,
|
|
379
374
|
fetchImpl = fetch,
|
|
375
|
+
confirmRestart = confirmRestartNow,
|
|
380
376
|
nodeVersion = process.versions.node,
|
|
381
377
|
} = {}) {
|
|
382
378
|
const [command = "--help", ...args] = argv;
|
|
383
379
|
if (["--help", "-h", "help"].includes(command)) return printHelp(output);
|
|
384
380
|
if (["--version", "-v"].includes(command)) return output(packageMetadata.version);
|
|
385
|
-
if (command === "install") return installCommand({ paths, platform, cliPath, output, serviceProbe, fetchImpl, nodeVersion });
|
|
381
|
+
if (command === "install") return installCommand({ paths, platform, cliPath, output, serviceProbe, fetchImpl, confirmRestart, nodeVersion });
|
|
386
382
|
if (command === "open" || command === "gui") return openCommand({ paths, platform, serviceProbe, fetchImpl });
|
|
387
383
|
if (command === "status") return statusCommand({ paths, platform, jsonOutput: args.includes("--json"), output, fetchImpl });
|
|
388
384
|
if (command === "restart") return restartCommand({ paths, output, fetchImpl });
|
|
@@ -142,7 +142,7 @@ export class RuntimeManager {
|
|
|
142
142
|
return next;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
async startInternal() {
|
|
145
|
+
async startInternal({ launchCodex = false } = {}) {
|
|
146
146
|
if (this.child && !hasExited(this.child)) return this.status();
|
|
147
147
|
this.clearChildState();
|
|
148
148
|
this.stopping = false;
|
|
@@ -169,6 +169,7 @@ export class RuntimeManager {
|
|
|
169
169
|
MINECODEX_ENABLED_FEATURES: features.join(","),
|
|
170
170
|
MINECODEX_CODEX_PID_FILE: this.paths.codexPidPath,
|
|
171
171
|
MINECODEX_RUNTIME_READY_FILE: this.paths.runtimeReadyPath,
|
|
172
|
+
MINECODEX_LAUNCH_CODEX: launchCodex ? "1" : "0",
|
|
172
173
|
...(codexExecutable ? { MINECODEX_CODEX_EXECUTABLE: codexExecutable } : {}),
|
|
173
174
|
},
|
|
174
175
|
stdio: "inherit",
|
|
@@ -307,7 +308,8 @@ export class RuntimeManager {
|
|
|
307
308
|
const operation = this.enqueue(async () => {
|
|
308
309
|
await this.stopInternal();
|
|
309
310
|
await this.platform.terminateOwnedCodex(this.paths);
|
|
310
|
-
|
|
311
|
+
await this.platform.terminateNativeCodex?.();
|
|
312
|
+
return this.startInternal({ launchCodex: true });
|
|
311
313
|
});
|
|
312
314
|
this.restartInFlight = operation;
|
|
313
315
|
const shared = this.restartInFlight.finally(() => {
|
|
@@ -3738,8 +3738,6 @@ export class CodexRuntime {
|
|
|
3738
3738
|
sleep = (timeoutMs) => new Promise((resolve) => setTimeout(resolve, timeoutMs)),
|
|
3739
3739
|
availabilityTimeoutMs = 20_000,
|
|
3740
3740
|
availabilityPollMs = 500,
|
|
3741
|
-
relaunchBaseDelayMs = 250,
|
|
3742
|
-
relaunchMaxDelayMs = 2_000,
|
|
3743
3741
|
monitorIntervalMs = 2_000,
|
|
3744
3742
|
runtimeSessionId = randomBytes(16).toString("hex"),
|
|
3745
3743
|
onStatusChange = null,
|
|
@@ -3756,8 +3754,6 @@ export class CodexRuntime {
|
|
|
3756
3754
|
this.sleep = sleep;
|
|
3757
3755
|
this.availabilityTimeoutMs = availabilityTimeoutMs;
|
|
3758
3756
|
this.availabilityPollMs = availabilityPollMs;
|
|
3759
|
-
this.relaunchBaseDelayMs = relaunchBaseDelayMs;
|
|
3760
|
-
this.relaunchMaxDelayMs = relaunchMaxDelayMs;
|
|
3761
3757
|
this.monitorIntervalMs = monitorIntervalMs;
|
|
3762
3758
|
this.runtimeSessionId = runtimeSessionId;
|
|
3763
3759
|
this.onStatusChange = onStatusChange;
|
|
@@ -3774,7 +3770,6 @@ export class CodexRuntime {
|
|
|
3774
3770
|
this.targetRefreshQueued = false;
|
|
3775
3771
|
this.appPid = null;
|
|
3776
3772
|
this.managedCodexChild = null;
|
|
3777
|
-
this.recoveryPromise = null;
|
|
3778
3773
|
this.monitorTimer = null;
|
|
3779
3774
|
this.monitorWake = null;
|
|
3780
3775
|
this.stopPromise = null;
|
|
@@ -3782,20 +3777,35 @@ export class CodexRuntime {
|
|
|
3782
3777
|
this.pidChangeQueue = null;
|
|
3783
3778
|
this.rendererDiscovered = false;
|
|
3784
3779
|
this.lastRendererStatus = null;
|
|
3785
|
-
this.codexLifecycleManaged = false;
|
|
3786
3780
|
}
|
|
3787
3781
|
|
|
3788
|
-
async start() {
|
|
3782
|
+
async start({ launchCodex = false } = {}) {
|
|
3789
3783
|
this.stopping = false;
|
|
3790
3784
|
await mkdir(this.profileDir, { recursive: true });
|
|
3791
|
-
|
|
3792
|
-
if (!
|
|
3793
|
-
this.launchManagedCodex();
|
|
3794
|
-
|
|
3785
|
+
const available = await this.isAvailable();
|
|
3786
|
+
if (!available && launchCodex) {
|
|
3787
|
+
const child = this.launchManagedCodex();
|
|
3788
|
+
try {
|
|
3789
|
+
await this.waitUntilAvailable();
|
|
3790
|
+
} catch (error) {
|
|
3791
|
+
if (this.managedCodexChild === child && !processChildHasExited(child)) {
|
|
3792
|
+
child.kill?.("SIGTERM");
|
|
3793
|
+
const exited = await waitForProcessChildExit(child, 500);
|
|
3794
|
+
if (!exited && !processChildHasExited(child)) {
|
|
3795
|
+
child.kill?.("SIGKILL");
|
|
3796
|
+
await waitForProcessChildExit(child, 500);
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
throw error;
|
|
3800
|
+
}
|
|
3795
3801
|
}
|
|
3796
3802
|
|
|
3797
|
-
|
|
3798
|
-
|
|
3803
|
+
if (available || launchCodex) {
|
|
3804
|
+
await this.startTargetDiscovery();
|
|
3805
|
+
await this.refresh();
|
|
3806
|
+
} else {
|
|
3807
|
+
this.notifyStatusChange();
|
|
3808
|
+
}
|
|
3799
3809
|
await this.flushManagedCodexPidChange();
|
|
3800
3810
|
this.monitorPromise = this.monitor();
|
|
3801
3811
|
}
|
|
@@ -3869,14 +3879,10 @@ export class CodexRuntime {
|
|
|
3869
3879
|
|
|
3870
3880
|
markCodexUnavailable(error) {
|
|
3871
3881
|
this.clearRendererState();
|
|
3872
|
-
if (this.codexLifecycleManaged && !this.stopping) {
|
|
3873
|
-
this.scheduleCodexRecovery();
|
|
3874
|
-
}
|
|
3875
3882
|
return error;
|
|
3876
3883
|
}
|
|
3877
3884
|
|
|
3878
3885
|
launchManagedCodex() {
|
|
3879
|
-
this.codexLifecycleManaged = true;
|
|
3880
3886
|
const child = this.spawnProcess(
|
|
3881
3887
|
this.appPath,
|
|
3882
3888
|
[
|
|
@@ -3901,7 +3907,6 @@ export class CodexRuntime {
|
|
|
3901
3907
|
this.notifyManagedCodexPidChange(null);
|
|
3902
3908
|
if (this.stopping) return;
|
|
3903
3909
|
this.clearRendererState();
|
|
3904
|
-
this.scheduleCodexRecovery();
|
|
3905
3910
|
};
|
|
3906
3911
|
child.once?.("exit", onExit);
|
|
3907
3912
|
child.once?.("error", (error) => {
|
|
@@ -3912,73 +3917,6 @@ export class CodexRuntime {
|
|
|
3912
3917
|
return child;
|
|
3913
3918
|
}
|
|
3914
3919
|
|
|
3915
|
-
scheduleCodexRecovery() {
|
|
3916
|
-
if (this.stopping || this.recoveryPromise) return this.recoveryPromise;
|
|
3917
|
-
const recovery = this.recoverManagedCodex();
|
|
3918
|
-
let trackedRecovery;
|
|
3919
|
-
trackedRecovery = recovery.finally(() => {
|
|
3920
|
-
if (this.recoveryPromise === trackedRecovery) this.recoveryPromise = null;
|
|
3921
|
-
});
|
|
3922
|
-
this.recoveryPromise = trackedRecovery;
|
|
3923
|
-
return trackedRecovery;
|
|
3924
|
-
}
|
|
3925
|
-
|
|
3926
|
-
async recoverManagedCodex() {
|
|
3927
|
-
let attempt = 0;
|
|
3928
|
-
while (!this.stopping) {
|
|
3929
|
-
if (this.managedCodexChild && !processChildHasExited(this.managedCodexChild)) {
|
|
3930
|
-
await this.sleep(Math.max(1, Math.min(this.relaunchMaxDelayMs, this.relaunchBaseDelayMs || 1)));
|
|
3931
|
-
continue;
|
|
3932
|
-
}
|
|
3933
|
-
if (await this.isAvailable()) {
|
|
3934
|
-
try {
|
|
3935
|
-
await this.startTargetDiscovery();
|
|
3936
|
-
await this.refresh();
|
|
3937
|
-
await this.flushManagedCodexPidChange();
|
|
3938
|
-
return;
|
|
3939
|
-
} catch (error) {
|
|
3940
|
-
if (this.stopping) return;
|
|
3941
|
-
if (await this.isAvailable()) {
|
|
3942
|
-
this.logger.warn?.("Managed Codex CDP is still available; retrying discovery", error.message);
|
|
3943
|
-
await this.sleep(Math.max(1, Math.min(this.relaunchMaxDelayMs, this.relaunchBaseDelayMs || 1)));
|
|
3944
|
-
continue;
|
|
3945
|
-
}
|
|
3946
|
-
}
|
|
3947
|
-
}
|
|
3948
|
-
const delay = Math.min(
|
|
3949
|
-
this.relaunchBaseDelayMs * (2 ** attempt),
|
|
3950
|
-
this.relaunchMaxDelayMs,
|
|
3951
|
-
);
|
|
3952
|
-
if (delay > 0) await this.sleep(delay);
|
|
3953
|
-
if (this.stopping) return;
|
|
3954
|
-
let child;
|
|
3955
|
-
try {
|
|
3956
|
-
child = this.launchManagedCodex();
|
|
3957
|
-
await this.waitUntilAvailable();
|
|
3958
|
-
await this.startTargetDiscovery();
|
|
3959
|
-
await this.refresh();
|
|
3960
|
-
await this.flushManagedCodexPidChange();
|
|
3961
|
-
return;
|
|
3962
|
-
} catch (error) {
|
|
3963
|
-
this.logger.warn?.("Managed Codex recovery retry failed", error.message);
|
|
3964
|
-
if (
|
|
3965
|
-
child
|
|
3966
|
-
&& this.managedCodexChild === child
|
|
3967
|
-
&& child.exitCode === null
|
|
3968
|
-
&& child.signalCode === null
|
|
3969
|
-
) {
|
|
3970
|
-
child.kill?.("SIGTERM");
|
|
3971
|
-
const exited = await waitForProcessChildExit(child, 500);
|
|
3972
|
-
if (!exited && !processChildHasExited(child)) {
|
|
3973
|
-
child.kill?.("SIGKILL");
|
|
3974
|
-
await waitForProcessChildExit(child, 500);
|
|
3975
|
-
}
|
|
3976
|
-
}
|
|
3977
|
-
attempt += 1;
|
|
3978
|
-
}
|
|
3979
|
-
}
|
|
3980
|
-
}
|
|
3981
|
-
|
|
3982
3920
|
queueTargetRefresh() {
|
|
3983
3921
|
if (this.targetRefreshQueued || this.stopping) return;
|
|
3984
3922
|
this.targetRefreshQueued = true;
|
|
@@ -4303,7 +4241,6 @@ export class CodexRuntime {
|
|
|
4303
4241
|
this.stopping = true;
|
|
4304
4242
|
this.targetRefreshQueued = false;
|
|
4305
4243
|
this.monitorWake?.();
|
|
4306
|
-
await this.recoveryPromise?.catch(() => {});
|
|
4307
4244
|
await this.monitorPromise?.catch(() => {});
|
|
4308
4245
|
await Promise.all(Array.from(this.clients, async ([targetId, client]) => {
|
|
4309
4246
|
const identifier = this.scriptIds.get(targetId);
|
|
@@ -17,6 +17,7 @@ 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";
|
|
20
21
|
|
|
21
22
|
const discoveredFeatures = await discoverFeatures(featuresRoot);
|
|
22
23
|
if (!discoveredFeatures.length) throw new Error(`No codex-feature.json files found under ${featuresRoot}`);
|
|
@@ -70,7 +71,7 @@ runtime = new CodexRuntime({
|
|
|
70
71
|
});
|
|
71
72
|
|
|
72
73
|
try {
|
|
73
|
-
await runtime.start();
|
|
74
|
+
await runtime.start({ launchCodex });
|
|
74
75
|
runtimeReady = true;
|
|
75
76
|
await writeReadyFile();
|
|
76
77
|
} catch (error) {
|