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 CHANGED
@@ -19,9 +19,11 @@ mcx install
19
19
 
20
20
  `mcx install` 不会关闭或启动 Codex,也不会改变当前窗口。安装完成后,终端会提示
21
21
  MineCodex 将在下次重启 Codex 时启用,并询问是否现在重启;默认答案是 **No**。
22
- 只有用户明确确认,或之后主动运行 `mcx restart`,MineCodex 才会关闭并重新打开
23
- Codex。安装后,macOS 的“登录项与扩展”中会显示名为 **MineCodex** 的后台项目;
24
- 这个后台项目负责插件与本地控制台,不会在用户退出 Codex 后自动将它重新打开。
22
+ 安装过程只有在用户明确确认,或之后主动运行 `mcx restart` 时,才会关闭并重新打开
23
+ Codex。安装后,macOS 的“登录项与扩展”中会显示名为 **MineCodex** 的后台项目。
24
+ 这个后台项目负责插件与本地控制台:用户退出 Codex 后,它会保持停止;用户后来从
25
+ Dock 主动打开 Codex 时,这次新启动会被视为明确的启动意图,并切换为可注入的托管
26
+ 实例。由于普通 Dock 启动本身没有 CDP,首次切换时 Codex 可能会短暂重开一次。
25
27
 
26
28
  安装后三个插件默认开启。打开本地控制台:
27
29
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "images",
4
- "version": "0.1.5",
4
+ "version": "0.1.7",
5
5
  "label": {
6
6
  "en": "Images",
7
7
  "zh-CN": "图片"
@@ -303,9 +303,9 @@ function layoutMasonry() {
303
303
  const cards = [...grid.children];
304
304
  if (!cards.length) return;
305
305
 
306
- // Keep DOM writes and geometry reads in separate batches so the browser only
307
- // needs to resolve layout once, even for a full page of boards.
308
- for (const card of cards) card.style.gridRowEnd = "auto";
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)
@@ -617,6 +617,7 @@ h1,
617
617
 
618
618
  .thread-board-preview img {
619
619
  width: 100%;
620
+ height: auto;
620
621
  min-height: 76px;
621
622
  aspect-ratio: 16 / 6;
622
623
  display: block;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "model-slider",
4
- "version": "0.1.5",
4
+ "version": "0.1.7",
5
5
  "label": {
6
6
  "en": "Model Slider",
7
7
  "zh-CN": "模型滑条"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "id": "notes",
4
- "version": "0.1.5",
4
+ "version": "0.1.7",
5
5
  "label": {
6
6
  "en": "Notes",
7
7
  "zh-CN": "笔记"
@@ -1,4 +1,4 @@
1
- const params = new URLSearchParams(location.search);
1
+ const params = new URL(document.baseURI).searchParams;
2
2
  const SUPPORTED_LOCALES = new Set(["en", "zh-CN"]);
3
3
 
4
4
  function normalizeLocale(value) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minecodex",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Lightweight, local-first plugins for the Codex desktop app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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: process.env.CODEX_RUNTIME_CDP_PORT ?? "9231",
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(() => this.startInternal());
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" || !declaredSurfaceUrls(feature).has(value)) {
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.frame.src);
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);