minecodex 0.1.5 → 0.1.7
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/images/web/app.js +3 -3
- package/features/images/web/styles.css +1 -0
- package/features/model-slider/codex-feature.json +1 -1
- package/features/notes/codex-feature.json +1 -1
- package/features/notes/web/app.js +1 -1
- package/package.json +1 -1
- package/packages/cli/src/runtime-manager.mjs +175 -3
- package/packages/runtime-host/src/codex-runtime.mjs +15 -2
package/README.md
CHANGED
|
@@ -19,9 +19,11 @@ mcx install
|
|
|
19
19
|
|
|
20
20
|
`mcx install` 不会关闭或启动 Codex,也不会改变当前窗口。安装完成后,终端会提示
|
|
21
21
|
MineCodex 将在下次重启 Codex 时启用,并询问是否现在重启;默认答案是 **No**。
|
|
22
|
-
|
|
23
|
-
Codex。安装后,macOS 的“登录项与扩展”中会显示名为 **MineCodex**
|
|
24
|
-
|
|
22
|
+
安装过程只有在用户明确确认,或之后主动运行 `mcx restart` 时,才会关闭并重新打开
|
|
23
|
+
Codex。安装后,macOS 的“登录项与扩展”中会显示名为 **MineCodex** 的后台项目。
|
|
24
|
+
这个后台项目负责插件与本地控制台:用户退出 Codex 后,它会保持停止;用户后来从
|
|
25
|
+
Dock 主动打开 Codex 时,这次新启动会被视为明确的启动意图,并切换为可注入的托管
|
|
26
|
+
实例。由于普通 Dock 启动本身没有 CDP,首次切换时 Codex 可能会短暂重开一次。
|
|
25
27
|
|
|
26
28
|
安装后三个插件默认开启。打开本地控制台:
|
|
27
29
|
|
|
@@ -303,9 +303,9 @@ function layoutMasonry() {
|
|
|
303
303
|
const cards = [...grid.children];
|
|
304
304
|
if (!cards.length) return;
|
|
305
305
|
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
306
|
+
// Existing spans do not affect the intrinsic card boxes because the grid
|
|
307
|
+
// aligns items to the start. Clearing them here would collapse the 1px row
|
|
308
|
+
// grid and clamp the user's scroll position before the new spans are applied.
|
|
309
309
|
const blockGap = Number.parseFloat(getComputedStyle(cards[0]).marginBottom);
|
|
310
310
|
const spans = cards.map((card) => (
|
|
311
311
|
Math.ceil((card.getBoundingClientRect().height + blockGap) / rowHeight)
|
package/package.json
CHANGED
|
@@ -19,6 +19,19 @@ function hasExited(child) {
|
|
|
19
19
|
return Boolean(child && (child.exitCode != null || child.signalCode != null));
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
function hasExactCommandArgument(command, argument) {
|
|
23
|
+
const value = String(command);
|
|
24
|
+
let offset = value.indexOf(argument);
|
|
25
|
+
while (offset >= 0) {
|
|
26
|
+
const before = offset === 0 || /\s/.test(value[offset - 1]);
|
|
27
|
+
const end = offset + argument.length;
|
|
28
|
+
const after = end === value.length || /\s/.test(value[end]);
|
|
29
|
+
if (before && after) return true;
|
|
30
|
+
offset = value.indexOf(argument, offset + 1);
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
function normalizedFailure(failure) {
|
|
23
36
|
if (!failure || typeof failure !== "object") return failure ?? null;
|
|
24
37
|
return {
|
|
@@ -119,6 +132,8 @@ export class RuntimeManager {
|
|
|
119
132
|
waitForReady: readyWaiter = waitForReady,
|
|
120
133
|
sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
121
134
|
stopTimeoutMs = 5_000,
|
|
135
|
+
manualLaunchPollMs = 1_000,
|
|
136
|
+
cdpPort = Number(process.env.CODEX_RUNTIME_CDP_PORT ?? 9231),
|
|
122
137
|
}) {
|
|
123
138
|
this.paths = paths;
|
|
124
139
|
this.platform = platform;
|
|
@@ -127,6 +142,8 @@ export class RuntimeManager {
|
|
|
127
142
|
this.waitForReady = readyWaiter;
|
|
128
143
|
this.sleep = sleep;
|
|
129
144
|
this.stopTimeoutMs = stopTimeoutMs;
|
|
145
|
+
this.manualLaunchPollMs = manualLaunchPollMs;
|
|
146
|
+
this.cdpPort = cdpPort;
|
|
130
147
|
this.child = null;
|
|
131
148
|
this.appliedFeatures = [];
|
|
132
149
|
this.plugins = [];
|
|
@@ -134,6 +151,12 @@ export class RuntimeManager {
|
|
|
134
151
|
this.stopping = false;
|
|
135
152
|
this.lifecycleQueue = Promise.resolve();
|
|
136
153
|
this.restartInFlight = null;
|
|
154
|
+
this.manualLaunchMonitorPromise = null;
|
|
155
|
+
this.manualLaunchMonitorStopped = true;
|
|
156
|
+
this.manualLaunchTimer = null;
|
|
157
|
+
this.manualLaunchWake = null;
|
|
158
|
+
this.manualLaunchBaselinePids = new Set();
|
|
159
|
+
this.manualLaunchArmed = false;
|
|
137
160
|
}
|
|
138
161
|
|
|
139
162
|
enqueue(operation) {
|
|
@@ -163,7 +186,7 @@ export class RuntimeManager {
|
|
|
163
186
|
...process.env,
|
|
164
187
|
CODEX_FEATURES_ROOT: this.paths.featuresRoot,
|
|
165
188
|
CODEX_RUNTIME_PROFILE_DIR: this.paths.profileDir,
|
|
166
|
-
CODEX_RUNTIME_CDP_PORT:
|
|
189
|
+
CODEX_RUNTIME_CDP_PORT: String(this.cdpPort),
|
|
167
190
|
CODEX_IMAGE_HOST_DATA_DIR: this.paths.imagesDataDir,
|
|
168
191
|
CODEX_NOTES_DATA_DIR: this.paths.notesDataDir,
|
|
169
192
|
MINECODEX_ENABLED_FEATURES: features.join(","),
|
|
@@ -228,7 +251,155 @@ export class RuntimeManager {
|
|
|
228
251
|
}
|
|
229
252
|
|
|
230
253
|
start() {
|
|
231
|
-
return this.enqueue(() =>
|
|
254
|
+
return this.enqueue(async () => {
|
|
255
|
+
const status = await this.startInternal();
|
|
256
|
+
await this.startManualLaunchMonitor();
|
|
257
|
+
return status;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async nativeCodexSnapshot() {
|
|
262
|
+
if (typeof this.platform?.snapshotNativeCodexState !== "function") return null;
|
|
263
|
+
const snapshot = await this.platform.snapshotNativeCodexState();
|
|
264
|
+
const cdpArgument = `--remote-debugging-port=${this.cdpPort}`;
|
|
265
|
+
const processes = (Array.isArray(snapshot?.processes) ? snapshot.processes : [])
|
|
266
|
+
.filter(({ command }) => !hasExactCommandArgument(command, cdpArgument));
|
|
267
|
+
return {
|
|
268
|
+
count: processes.length,
|
|
269
|
+
processes,
|
|
270
|
+
pids: new Set(processes.flatMap(({ pid }) => Number.isInteger(pid) ? [pid] : [])),
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async startManualLaunchMonitor() {
|
|
275
|
+
if (this.manualLaunchMonitorPromise || typeof this.platform?.snapshotNativeCodexState !== "function") return;
|
|
276
|
+
const snapshot = await this.nativeCodexSnapshot();
|
|
277
|
+
this.manualLaunchBaselinePids = new Set(snapshot?.pids ?? []);
|
|
278
|
+
this.manualLaunchArmed = this.manualLaunchBaselinePids.size === 0;
|
|
279
|
+
this.manualLaunchMonitorStopped = false;
|
|
280
|
+
const monitor = this.monitorManualCodexLaunches().catch((error) => {
|
|
281
|
+
if (!this.manualLaunchMonitorStopped) {
|
|
282
|
+
this.logger.warn?.("MineCodex manual Codex launch monitor failed", error.message);
|
|
283
|
+
}
|
|
284
|
+
}).finally(() => {
|
|
285
|
+
if (this.manualLaunchMonitorPromise === monitor) this.manualLaunchMonitorPromise = null;
|
|
286
|
+
});
|
|
287
|
+
this.manualLaunchMonitorPromise = monitor;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
waitForManualLaunchPoll() {
|
|
291
|
+
if (this.manualLaunchMonitorStopped) return Promise.resolve();
|
|
292
|
+
return new Promise((resolve) => {
|
|
293
|
+
const finish = () => {
|
|
294
|
+
if (this.manualLaunchTimer) clearTimeout(this.manualLaunchTimer);
|
|
295
|
+
this.manualLaunchTimer = null;
|
|
296
|
+
this.manualLaunchWake = null;
|
|
297
|
+
resolve();
|
|
298
|
+
};
|
|
299
|
+
this.manualLaunchWake = finish;
|
|
300
|
+
this.manualLaunchTimer = setTimeout(finish, this.manualLaunchPollMs);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async monitorManualCodexLaunches() {
|
|
305
|
+
while (!this.manualLaunchMonitorStopped) {
|
|
306
|
+
await this.waitForManualLaunchPoll();
|
|
307
|
+
if (this.manualLaunchMonitorStopped) break;
|
|
308
|
+
try {
|
|
309
|
+
await this.reconcileManualCodexLaunch();
|
|
310
|
+
} catch (error) {
|
|
311
|
+
this.logger.warn?.("MineCodex could not adopt the Codex launch", error.message);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async stopManualLaunchMonitor() {
|
|
317
|
+
this.manualLaunchMonitorStopped = true;
|
|
318
|
+
this.manualLaunchWake?.();
|
|
319
|
+
await this.manualLaunchMonitorPromise;
|
|
320
|
+
this.manualLaunchMonitorPromise = null;
|
|
321
|
+
this.manualLaunchBaselinePids.clear();
|
|
322
|
+
this.manualLaunchArmed = false;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async reconcileManualCodexLaunch() {
|
|
326
|
+
if (this.manualLaunchMonitorStopped) return false;
|
|
327
|
+
const snapshot = await this.nativeCodexSnapshot();
|
|
328
|
+
if (!snapshot) return false;
|
|
329
|
+
|
|
330
|
+
if (this.manualLaunchBaselinePids.size > 0) {
|
|
331
|
+
const baselineStillRunning = [...this.manualLaunchBaselinePids]
|
|
332
|
+
.some((pid) => snapshot.pids.has(pid));
|
|
333
|
+
if (baselineStillRunning) return false;
|
|
334
|
+
this.manualLaunchBaselinePids.clear();
|
|
335
|
+
if (snapshot.count === 0) {
|
|
336
|
+
this.manualLaunchArmed = true;
|
|
337
|
+
return false;
|
|
338
|
+
}
|
|
339
|
+
this.manualLaunchArmed = true;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (snapshot.count === 0) {
|
|
343
|
+
this.manualLaunchArmed = true;
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
if (!this.manualLaunchArmed) return false;
|
|
347
|
+
|
|
348
|
+
const observedPids = new Set(snapshot.pids);
|
|
349
|
+
this.manualLaunchArmed = false;
|
|
350
|
+
this.manualLaunchBaselinePids = observedPids;
|
|
351
|
+
return this.enqueue(async () => {
|
|
352
|
+
if (this.manualLaunchMonitorStopped) return false;
|
|
353
|
+
const latest = await this.nativeCodexSnapshot();
|
|
354
|
+
const observedProcessStillRunning = latest
|
|
355
|
+
&& [...observedPids].some((pid) => latest.pids.has(pid));
|
|
356
|
+
if (!observedProcessStillRunning) {
|
|
357
|
+
if (!latest?.count) {
|
|
358
|
+
this.manualLaunchBaselinePids.clear();
|
|
359
|
+
this.manualLaunchArmed = true;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await this.stopInternal();
|
|
365
|
+
let terminated;
|
|
366
|
+
try {
|
|
367
|
+
terminated = await this.platform.terminateNativeCodex();
|
|
368
|
+
} catch (error) {
|
|
369
|
+
await this.restoreIdleRuntimeAfterAdoptionFailure(error);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (!(terminated > 0)) {
|
|
374
|
+
await this.startInternal();
|
|
375
|
+
this.manualLaunchBaselinePids.clear();
|
|
376
|
+
this.manualLaunchArmed = true;
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
try {
|
|
381
|
+
const status = await this.startInternal({ launchCodex: true });
|
|
382
|
+
this.manualLaunchBaselinePids.clear();
|
|
383
|
+
this.manualLaunchArmed = true;
|
|
384
|
+
return status;
|
|
385
|
+
} catch (error) {
|
|
386
|
+
this.manualLaunchBaselinePids.clear();
|
|
387
|
+
this.manualLaunchArmed = true;
|
|
388
|
+
await this.restoreIdleRuntimeAfterAdoptionFailure(error);
|
|
389
|
+
throw error;
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async restoreIdleRuntimeAfterAdoptionFailure(failure) {
|
|
395
|
+
try {
|
|
396
|
+
await this.startInternal();
|
|
397
|
+
} catch (recoveryError) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`${failure.message}; idle RuntimeHost recovery failed: ${recoveryError.message}`,
|
|
400
|
+
{ cause: failure },
|
|
401
|
+
);
|
|
402
|
+
}
|
|
232
403
|
}
|
|
233
404
|
|
|
234
405
|
async stopInternal() {
|
|
@@ -274,7 +445,8 @@ export class RuntimeManager {
|
|
|
274
445
|
}
|
|
275
446
|
}
|
|
276
447
|
|
|
277
|
-
stop() {
|
|
448
|
+
async stop() {
|
|
449
|
+
await this.stopManualLaunchMonitor();
|
|
278
450
|
return this.enqueue(() => this.stopInternal());
|
|
279
451
|
}
|
|
280
452
|
|
|
@@ -51,12 +51,25 @@ function declaredSurfaceUrls(feature) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function requireDeclaredLoopbackSurface(feature, value) {
|
|
54
|
-
if (typeof value !== "string"
|
|
54
|
+
if (typeof value !== "string") {
|
|
55
55
|
throw Object.assign(new Error("Surface URL is not declared by this feature"), {
|
|
56
56
|
code: "SURFACE_URL_NOT_ALLOWED",
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
const url = new URL(value);
|
|
60
|
+
const matchesDeclaredDocument = Array.from(declaredSurfaceUrls(feature), (declared) => new URL(declared))
|
|
61
|
+
.some((declared) => (
|
|
62
|
+
declared.origin === url.origin
|
|
63
|
+
&& declared.username === url.username
|
|
64
|
+
&& declared.password === url.password
|
|
65
|
+
&& declared.pathname === url.pathname
|
|
66
|
+
&& declared.hash === url.hash
|
|
67
|
+
));
|
|
68
|
+
if (!matchesDeclaredDocument) {
|
|
69
|
+
throw Object.assign(new Error("Surface URL is not declared by this feature"), {
|
|
70
|
+
code: "SURFACE_URL_NOT_ALLOWED",
|
|
71
|
+
});
|
|
72
|
+
}
|
|
60
73
|
if (url.protocol !== "http:" || !LOOPBACK_SURFACE_HOSTS.has(url.hostname)) {
|
|
61
74
|
throw Object.assign(new Error("Surface URL must use an exact loopback HTTP origin"), {
|
|
62
75
|
code: "SURFACE_URL_NOT_ALLOWED",
|
|
@@ -2541,7 +2554,7 @@ export function createInjectionSource(features, {
|
|
|
2541
2554
|
|
|
2542
2555
|
closePromptPreview({ immediate: true });
|
|
2543
2556
|
closeModalSurface();
|
|
2544
|
-
const editorUrl = new URL(sourceRecord.
|
|
2557
|
+
const editorUrl = new URL(sourceRecord.surfaceUrl);
|
|
2545
2558
|
editorUrl.searchParams.set("surface", "editor");
|
|
2546
2559
|
editorUrl.searchParams.set("kind", kind);
|
|
2547
2560
|
if (typeof payload.id === "string" && payload.id) editorUrl.searchParams.set("id", payload.id);
|