minecodex 2.0.8 → 2.0.9
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 +18 -4
- package/apps/dashboard/app.js +59 -10
- package/apps/dashboard/assets/diagnostics.js +18 -0
- package/apps/dashboard/index.html +5 -0
- package/apps/dashboard/styles.css +5 -1
- package/assets/brand/README.md +2 -2
- package/features/hide-upsell-banner/src/hide-upsell-banner.mjs +2 -0
- package/features/images/src/adapters/codex/image-sources.mjs +8 -1
- package/features/images/src/adapters/codex/native-image-source.mjs +2 -2
- package/features/images/src/frontend/local-image-source.mjs +7 -0
- package/features/images/src/library-prompt-page.mjs +9 -0
- package/features/model-slider/src/adapters/codex/controller.mjs +14 -1
- package/features/model-slider/src/frontend/install.mjs +4 -1
- package/features/notes/src/frontend/entry.mjs +10 -0
- package/features/session-status/src/session-status.mjs +11 -0
- package/features/sidebar-focus/src/sidebar-focus.mjs +8 -0
- package/features/sites/src/frontend/entry.mjs +8 -0
- package/features/usage-meter/src/adapters/codex/renderer.mjs +2 -1
- package/features/usage-meter/src/usage-meter.mjs +8 -0
- package/package.json +1 -14
- package/packages/cli/src/commands.mjs +6 -1
- package/packages/cli/src/control-server.mjs +4 -1
- package/packages/cli/src/platform.mjs +10 -0
- package/packages/cli/src/runtime-manager.mjs +11 -3
- package/packages/codex-adapter/src/renderer/environment.mjs +6 -1
- package/packages/runtime-host/src/codex-injection.mjs +49 -16
- package/packages/runtime-host/src/codex-runtime.mjs +51 -0
- package/packages/runtime-host/src/feature-registry.mjs +2 -3
- package/packages/runtime-host/src/main.mjs +8 -4
- package/packages/runtime-host/src/renderer-self-check.mjs +56 -0
- package/packages/ui/src/components/composer-menu.mjs +7 -2
- package/packages/ui/src/native/theme.mjs +16 -1
- package/DESIGN.md +0 -192
- package/apps/dashboard/DESIGN.md +0 -67
- package/features/hide-upsell-banner/README.md +0 -30
- package/features/images/DESIGN.md +0 -151
- package/features/images/README.md +0 -37
- package/features/model-slider/DESIGN.md +0 -36
- package/features/model-slider/README.md +0 -45
- package/features/notes/DESIGN.md +0 -74
- package/features/notes/README.md +0 -33
- package/features/session-status/DESIGN.md +0 -45
- package/features/session-status/README.md +0 -43
- package/features/sidebar-focus/DESIGN.md +0 -17
- package/features/sites/DESIGN.md +0 -82
- package/features/sites/README.md +0 -31
- package/features/unread-inbox/DESIGN.md +0 -41
- package/features/unread-inbox/codex-feature.json +0 -23
- package/features/unread-inbox/src/adapters/codex/modules.mjs +0 -58
- package/features/unread-inbox/src/adapters/codex/renderer.mjs +0 -198
- package/features/unread-inbox/src/domain/notifications.mjs +0 -21
- package/features/unread-inbox/src/injection-source.mjs +0 -9
- package/features/unread-inbox/src/unread-inbox.mjs +0 -307
- package/features/usage-meter/DESIGN.md +0 -49
- package/features/usage-meter/README.md +0 -19
- package/packages/cli/native/README.md +0 -49
- package/packages/codex-adapter/README.md +0 -16
- package/packages/runtime-host/README.md +0 -213
- package/packages/ui/DESIGN.md +0 -38
|
@@ -8,6 +8,11 @@ export const RUNTIME_PLUGIN_STATES = Object.freeze([
|
|
|
8
8
|
"launching",
|
|
9
9
|
"ready",
|
|
10
10
|
"injected",
|
|
11
|
+
"checking",
|
|
12
|
+
"partial",
|
|
13
|
+
"degraded",
|
|
14
|
+
"unavailable",
|
|
15
|
+
"not-applicable",
|
|
11
16
|
"failed",
|
|
12
17
|
"stopped",
|
|
13
18
|
]);
|
|
@@ -48,7 +53,7 @@ export function normalizeRuntimeReady(ready = {}) {
|
|
|
48
53
|
: [];
|
|
49
54
|
const features = [...new Set([
|
|
50
55
|
...legacyFeatures,
|
|
51
|
-
...plugins.filter(({ state }) => ["configured", "launching", "ready", "injected"].includes(state)).map(({ featureId }) => featureId),
|
|
56
|
+
...plugins.filter(({ state }) => ["configured", "launching", "ready", "injected", "checking", "partial", "degraded", "unavailable", "not-applicable"].includes(state)).map(({ featureId }) => featureId),
|
|
52
57
|
])];
|
|
53
58
|
const renderer = ready.renderer && typeof ready.renderer === "object"
|
|
54
59
|
? {
|
|
@@ -78,12 +83,13 @@ function rendererUnavailableReady(ready) {
|
|
|
78
83
|
const normalized = normalizeRuntimeReady(ready);
|
|
79
84
|
return normalizeRuntimeReady({
|
|
80
85
|
...normalized,
|
|
81
|
-
plugins: normalized.plugins.map((plugin) => plugin.state
|
|
82
|
-
? { ...plugin, state: "
|
|
86
|
+
plugins: normalized.plugins.map((plugin) => ["injected","partial","degraded","not-applicable","unavailable","checking"].includes(plugin.state)
|
|
87
|
+
? { ...plugin, state: "checking", health: null, targets:[] }
|
|
83
88
|
: plugin),
|
|
84
89
|
renderer: {
|
|
85
90
|
...(normalized.renderer ?? {}),
|
|
86
91
|
discovered: false,
|
|
92
|
+
targets:[],
|
|
87
93
|
injectedTargets: 0,
|
|
88
94
|
active: false,
|
|
89
95
|
runtimeSessionId: normalized.runtimeSessionId,
|
|
@@ -170,6 +176,7 @@ export class RuntimeManager {
|
|
|
170
176
|
const config = await readConfig(this.paths.configPath);
|
|
171
177
|
const features = enabledFeatureIds(config);
|
|
172
178
|
if (typeof this.platform?.codex === "function") await this.platform.codex();
|
|
179
|
+
const codexBuild=await this.platform?.codexBuild?.();
|
|
173
180
|
await mkdir(this.paths.supportDir, { recursive: true, mode: 0o700 });
|
|
174
181
|
await unlink(this.paths.runtimeReadyPath).catch((error) => {
|
|
175
182
|
if (error.code !== "ENOENT") throw error;
|
|
@@ -179,6 +186,7 @@ export class RuntimeManager {
|
|
|
179
186
|
CODEX_FEATURES_ROOT: this.paths.featuresRoot,
|
|
180
187
|
CODEX_RUNTIME_PROFILE_DIR: this.paths.profileDir,
|
|
181
188
|
CODEX_RUNTIME_CDP_PORT: String(this.cdpPort),
|
|
189
|
+
MINECODEX_CODEX_BUILD: codexBuild ?? "",
|
|
182
190
|
CODEX_IMAGE_HOST_DATA_DIR: this.paths.imagesDataDir,
|
|
183
191
|
CODEX_NOTES_DATA_DIR: this.paths.notesDataDir,
|
|
184
192
|
MINECODEX_ENABLED_FEATURES: features.join(","),
|
|
@@ -65,7 +65,12 @@ export function createCodexEnvironment() {
|
|
|
65
65
|
const group = [...(scroll?.children ?? [])].find(child => child !== ownGroup && child.classList.contains('flex-col') && child.classList.contains('shrink-0'));
|
|
66
66
|
return node && template && scroll ? { rail:node, template, scroll, group } : null;
|
|
67
67
|
}
|
|
68
|
-
|
|
68
|
+
function route() {
|
|
69
|
+
const node = rail();
|
|
70
|
+
const scope = node && props(node, value => Boolean((value.router ?? value.value?.router)?.state?.location));
|
|
71
|
+
return (scope?.router ?? scope?.value?.router)?.state?.location?.pathname ?? null;
|
|
72
|
+
}
|
|
73
|
+
return { composer, composerSurface, rail, railSlot, route, currentThreadId:element => composer(element)?.threadId ?? null,
|
|
69
74
|
// 仅供 UI/插件 native Adapter 使用,交互层不读取 React props。
|
|
70
75
|
currentFiber, props, report, diagnostics:() => [...capabilities.values()].map(item => ({...item})) };
|
|
71
76
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRendererSelfCheck, tokenRequirements } from "./renderer-self-check.mjs";
|
|
1
2
|
import { readFileSync } from "node:fs";
|
|
2
3
|
import { createCodexEnvironment } from "../../codex-adapter/src/renderer/environment.mjs";
|
|
3
4
|
// 注入源生成:createInjectionSource 产出注入到 Codex Renderer 的自包含脚本字符串。
|
|
@@ -9,7 +10,7 @@ import { createUIInstallerSource } from "../../ui/src/install.mjs";
|
|
|
9
10
|
import { randomBytes } from "node:crypto";
|
|
10
11
|
import { CODEX_THEME_TOKENS, CODEX_THEME_TOKEN_ALIASES, MINECODEX_THEME_BINDINGS } from "../../ui/src/native/tokens.mjs";
|
|
11
12
|
|
|
12
|
-
export const RUNTIME_VERSION =
|
|
13
|
+
export const RUNTIME_VERSION = 109;
|
|
13
14
|
const PROJECT_ICON = Object.fromEntries([16, 20].map(size => [size,
|
|
14
15
|
readFileSync(new URL(`../../ui/assets/minecodex-menu-${size}.svg`, import.meta.url), "utf8"),
|
|
15
16
|
]));
|
|
@@ -127,14 +128,14 @@ export function createInjectionSource(features, {
|
|
|
127
128
|
bindingToken = "test-binding-token",
|
|
128
129
|
runtimeSessionId = randomBytes(16).toString("hex"),
|
|
129
130
|
} = {}) {
|
|
130
|
-
function install(config, installUI, installModelSliderFeature, createCodexEnvironment) {
|
|
131
|
+
function install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck) {
|
|
131
132
|
if (window.top !== window) return;
|
|
132
133
|
if (!document.documentElement) {
|
|
133
134
|
// The document bootstrap runs before the root element exists. Defer the
|
|
134
135
|
// whole install until the DOM is ready instead of crashing on a null
|
|
135
136
|
// documentElement (which previously left the runtime permanently absent
|
|
136
137
|
// after a renderer reload).
|
|
137
|
-
document.addEventListener("DOMContentLoaded", () => install(config, installUI, installModelSliderFeature, createCodexEnvironment), { once: true });
|
|
138
|
+
document.addEventListener("DOMContentLoaded", () => install(config, installUI, installModelSliderFeature, createCodexEnvironment, createRendererSelfCheck), { once: true });
|
|
138
139
|
return;
|
|
139
140
|
}
|
|
140
141
|
const currentRuntime = window.__codexPersonalRuntime;
|
|
@@ -211,6 +212,7 @@ export function createInjectionSource(features, {
|
|
|
211
212
|
}));
|
|
212
213
|
featureUI.accountMenuEntry?.({ label:"MineCodex", icon:config.projectIcon, externalIcon:config.externalIcon,
|
|
213
214
|
onOpen:() => requestPageHostAction("minecodex", "open-control-panel", {}) });
|
|
215
|
+
const selfCheck = createRendererSelfCheck(features, config.sessionId, featureUI.theme);
|
|
214
216
|
const featureById = new Map(features.map((feature) => [feature.id, feature]));
|
|
215
217
|
const sidebarFeatures = features.filter((feature) => feature.entry?.kind === "sidebar");
|
|
216
218
|
const toolbarFeatures = features.filter((feature) => feature.entry?.kind === "conversation-toolbar");
|
|
@@ -248,9 +250,19 @@ export function createInjectionSource(features, {
|
|
|
248
250
|
let nativeTabCapabilityPromise = null;
|
|
249
251
|
let nativeTabCapability = null;
|
|
250
252
|
let nativeTabCapabilityError = null;
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
253
|
+
let modelSlider = {ensure() {}, dispose() {}};
|
|
254
|
+
try {
|
|
255
|
+
modelSlider = installModelSliderFeature(lifetime.signal, {
|
|
256
|
+
feature: modelSelectorFeature, codex, ui: featureUI, createIcon: createMarkupIcon, requestRefresh: queueEnsure, locale,
|
|
257
|
+
});
|
|
258
|
+
if (modelSelectorFeature) {
|
|
259
|
+
selfCheck.register(modelSelectorFeature.id, () => modelSlider.selfCheck());
|
|
260
|
+
selfCheck.installed(modelSelectorFeature.id);
|
|
261
|
+
}
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if (modelSelectorFeature) selfCheck.failed(modelSelectorFeature.id);
|
|
264
|
+
console.warn("[codex-personal] model selector installation failed", error);
|
|
265
|
+
}
|
|
254
266
|
|
|
255
267
|
// ========================================================================
|
|
256
268
|
// page-script 特征安装 — 执行各 feature 声明的页面脚本并登记清理
|
|
@@ -271,6 +283,7 @@ export function createInjectionSource(features, {
|
|
|
271
283
|
const pageConfig = feature.pageScript.config ??= {};
|
|
272
284
|
pageConfig.ui = featureUI;
|
|
273
285
|
pageConfig.codex = codex;
|
|
286
|
+
pageConfig.registerSelfCheck = probe => selfCheck.register(feature.id, probe);
|
|
274
287
|
pageConfig.requestHostAction = (action, payload) => requestPageHostAction(feature.id, action, payload);
|
|
275
288
|
const cleanup = installer(
|
|
276
289
|
lifetime.signal,
|
|
@@ -280,7 +293,9 @@ export function createInjectionSource(features, {
|
|
|
280
293
|
pageConfig,
|
|
281
294
|
);
|
|
282
295
|
if (typeof cleanup === "function") pageScriptCleanups.push(cleanup);
|
|
296
|
+
selfCheck.installed(feature.id);
|
|
283
297
|
} catch (error) {
|
|
298
|
+
selfCheck.failed(feature.id);
|
|
284
299
|
console.warn(`[codex-personal] page-script feature failed: ${feature.id}`, error);
|
|
285
300
|
}
|
|
286
301
|
}
|
|
@@ -2369,6 +2384,7 @@ export function createInjectionSource(features, {
|
|
|
2369
2384
|
sessionId: config.sessionId,
|
|
2370
2385
|
updateHostBinding,
|
|
2371
2386
|
nativeCapabilities: () => codex.diagnostics(),
|
|
2387
|
+
selfCheck: () => ({...selfCheck.snapshot(),capabilities:codex.diagnostics()}),
|
|
2372
2388
|
ensure: queueEnsure,
|
|
2373
2389
|
show: showPageSurface,
|
|
2374
2390
|
hide: () => {
|
|
@@ -2422,6 +2438,7 @@ export function createInjectionSource(features, {
|
|
|
2422
2438
|
}
|
|
2423
2439
|
|
|
2424
2440
|
const runtimeFeatures = features.map((feature) => ({
|
|
2441
|
+
requiredTokens:tokenRequirements(`${feature.pageScript?.source ?? ""} ${Object.values(feature.pageScript?.resources ?? {}).filter(value => typeof value === "string").join(" ")} ${feature.modelSelector ? createModelSliderInstallerSource() : ""}`,MINECODEX_THEME_BINDINGS),
|
|
2425
2442
|
id: feature.id,
|
|
2426
2443
|
label: feature.label,
|
|
2427
2444
|
labels: feature.labels ?? null,
|
|
@@ -2457,7 +2474,7 @@ export function createInjectionSource(features, {
|
|
|
2457
2474
|
themeTokenAliases: CODEX_THEME_TOKEN_ALIASES,
|
|
2458
2475
|
themeBindings: MINECODEX_THEME_BINDINGS,
|
|
2459
2476
|
features: runtimeFeatures,
|
|
2460
|
-
})}, ${createUIInstallerSource()}, ${createModelSliderInstallerSource()}, ${createCodexEnvironment.toString()})`;
|
|
2477
|
+
})}, ${createUIInstallerSource()}, ${createModelSliderInstallerSource()}, ${createCodexEnvironment.toString()}, ${createRendererSelfCheck.toString()})`;
|
|
2461
2478
|
}
|
|
2462
2479
|
|
|
2463
2480
|
export function createRuntimeReadyContract({
|
|
@@ -2478,15 +2495,28 @@ export function createRuntimeReadyContract({
|
|
|
2478
2495
|
features: features.map((feature) => typeof feature === "string" ? feature : feature.id),
|
|
2479
2496
|
failures,
|
|
2480
2497
|
runtimeSessionId,
|
|
2481
|
-
plugins: plugins.map(
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
:
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2498
|
+
plugins: plugins.map(plugin => {
|
|
2499
|
+
const targets=(renderer.targets ?? []).map(target => {
|
|
2500
|
+
const result=target.sessionId === runtimeSessionId && active
|
|
2501
|
+
? target.plugins?.find(result => result.featureId === plugin.featureId) : null;
|
|
2502
|
+
return {targetId:target.targetId,checkedAt:target.checkedAt ?? null,
|
|
2503
|
+
...(result ?? {state:"unknown",reason:target.reason ?? "self-check-pending",installation:"pending",checks:[],tokens:[]})};
|
|
2504
|
+
});
|
|
2505
|
+
const states=targets.map(target=>target.state);
|
|
2506
|
+
// 未观测的窗口也参与汇总,不能凭一项成功宣称全部窗口可用。
|
|
2507
|
+
const missing=Math.max(0,discoveredTargets-targets.length);
|
|
2508
|
+
let state=plugin.state;
|
|
2509
|
+
const definition=features.find(feature=>typeof feature === "object" && feature.id === plugin.featureId);
|
|
2510
|
+
if (definition?.healthUrl && state === "configured") state="launching";
|
|
2511
|
+
else if (active && ["configured","ready","injected"].includes(state)) {
|
|
2512
|
+
if (states.includes("available")) state=missing || states.some(value=>!["available","not-applicable"].includes(value)) ? "partial" : "injected";
|
|
2513
|
+
else if (states.includes("degraded")) state="degraded";
|
|
2514
|
+
else if (states.some(value=>["failed","unavailable"].includes(value))) state="unavailable";
|
|
2515
|
+
else if (!missing && states.length && states.every(value=>value === "not-applicable")) state="not-applicable";
|
|
2516
|
+
else state="checking";
|
|
2517
|
+
} else if (state === "injected") state="checking";
|
|
2518
|
+
return {featureId:plugin.featureId,state,serviceState:plugin.state,owned:Boolean(plugin.owned),health:plugin.health ?? null,failure:plugin.failure ?? null,targets};
|
|
2519
|
+
}),
|
|
2490
2520
|
renderer: {
|
|
2491
2521
|
discovered,
|
|
2492
2522
|
discoveredTargets,
|
|
@@ -2495,6 +2525,9 @@ export function createRuntimeReadyContract({
|
|
|
2495
2525
|
healthy: active && targetFailures.length === 0 && injectedTargets === discoveredTargets,
|
|
2496
2526
|
targetFailures,
|
|
2497
2527
|
runtimeSessionId: renderer.runtimeSessionId ?? runtimeSessionId,
|
|
2528
|
+
targets:renderer.targets ?? [],
|
|
2529
|
+
codexBuild:renderer.codexBuild ?? null,
|
|
2530
|
+
buildSource:renderer.buildSource ?? null,
|
|
2498
2531
|
},
|
|
2499
2532
|
};
|
|
2500
2533
|
}
|
|
@@ -75,6 +75,7 @@ export class CodexRuntime {
|
|
|
75
75
|
surfaceLoadTimeoutMs = 5_000,
|
|
76
76
|
surfaceFramePollMs = 25,
|
|
77
77
|
featureInstanceId = null,
|
|
78
|
+
codexBuild = null,
|
|
78
79
|
runtimeSessionId = randomBytes(16).toString("hex"),
|
|
79
80
|
onStatusChange = null,
|
|
80
81
|
sitesService = null,
|
|
@@ -99,12 +100,14 @@ export class CodexRuntime {
|
|
|
99
100
|
this.surfaceLoadTimeoutMs = surfaceLoadTimeoutMs;
|
|
100
101
|
this.surfaceFramePollMs = surfaceFramePollMs;
|
|
101
102
|
this.featureInstanceId = featureInstanceId;
|
|
103
|
+
this.codexBuild = codexBuild;
|
|
102
104
|
this.runtimeSessionId = runtimeSessionId;
|
|
103
105
|
this.onStatusChange = onStatusChange;
|
|
104
106
|
this.onManagedCodexPidChange = onManagedCodexPidChange;
|
|
105
107
|
this.injectedTargets = new Set();
|
|
106
108
|
this.rendererTargetIds = new Set();
|
|
107
109
|
this.targetFailures = new Map();
|
|
110
|
+
this.targetChecks = new Map();
|
|
108
111
|
this.clients = new Map();
|
|
109
112
|
this.scriptIds = new Map();
|
|
110
113
|
this.bindingTokens = new Map();
|
|
@@ -171,6 +174,7 @@ export class CodexRuntime {
|
|
|
171
174
|
})).sort((left, right) => left.targetId.localeCompare(right.targetId));
|
|
172
175
|
const active = !this.stopping && this.rendererDiscovered && injectedTargets > 0;
|
|
173
176
|
return {
|
|
177
|
+
...(this.codexBuild ? {codexBuild:this.codexBuild,buildSource:"installation-at-start"} : {}),
|
|
174
178
|
discovered: this.rendererDiscovered,
|
|
175
179
|
discoveredTargets,
|
|
176
180
|
injectedTargets,
|
|
@@ -178,6 +182,7 @@ export class CodexRuntime {
|
|
|
178
182
|
healthy: active && targetFailures.length === 0 && injectedTargets === discoveredTargets,
|
|
179
183
|
targetFailures,
|
|
180
184
|
runtimeSessionId: this.runtimeSessionId,
|
|
185
|
+
targets:[...this.rendererTargetIds].map(targetId => ({targetId,...(this.targetChecks.get(targetId) ?? {sessionId:this.runtimeSessionId,checkedAt:null,plugins:[],tokens:[],reason:"self-check-pending"})})),
|
|
181
186
|
};
|
|
182
187
|
}
|
|
183
188
|
|
|
@@ -192,6 +197,7 @@ export class CodexRuntime {
|
|
|
192
197
|
&& previous.active === status.active
|
|
193
198
|
&& previous.healthy === status.healthy
|
|
194
199
|
&& JSON.stringify(previous.targetFailures) === JSON.stringify(status.targetFailures)
|
|
200
|
+
&& JSON.stringify(previous.targets) === JSON.stringify(status.targets)
|
|
195
201
|
&& previous.runtimeSessionId === status.runtimeSessionId
|
|
196
202
|
) return;
|
|
197
203
|
this.lastRendererStatus = status;
|
|
@@ -237,6 +243,7 @@ export class CodexRuntime {
|
|
|
237
243
|
this.injectedTargets.clear();
|
|
238
244
|
this.rendererTargetIds.clear();
|
|
239
245
|
this.targetFailures.clear();
|
|
246
|
+
this.targetChecks.clear();
|
|
240
247
|
this.rendererDiscovered = false;
|
|
241
248
|
this.browserClient?.socket.close();
|
|
242
249
|
this.browserClient = null;
|
|
@@ -695,6 +702,7 @@ export class CodexRuntime {
|
|
|
695
702
|
this.clients.delete(id);
|
|
696
703
|
this.scriptIds.delete(id);
|
|
697
704
|
this.bindingTokens.delete(id);
|
|
705
|
+
this.targetChecks.delete(id);
|
|
698
706
|
this.hostActionChains.delete(id);
|
|
699
707
|
this.injectedTargets.delete(id);
|
|
700
708
|
this.targetFailures.delete(id);
|
|
@@ -705,6 +713,18 @@ export class CodexRuntime {
|
|
|
705
713
|
if (removedTarget || rendererChanged) this.notifyStatusChange();
|
|
706
714
|
|
|
707
715
|
for (const target of targets) {
|
|
716
|
+
if (this.injectedTargets.has(target.id)) {
|
|
717
|
+
const present=await this.checkTarget(target.id);
|
|
718
|
+
if (present) continue;
|
|
719
|
+
const previousClient=this.clients.get(target.id),identifier=this.scriptIds.get(target.id);
|
|
720
|
+
if (identifier) await previousClient?.send("Page.removeScriptToEvaluateOnNewDocument",{identifier}).catch(()=>{});
|
|
721
|
+
previousClient?.socket.close();
|
|
722
|
+
this.clients.delete(target.id);
|
|
723
|
+
this.scriptIds.delete(target.id);
|
|
724
|
+
this.bindingTokens.delete(target.id);
|
|
725
|
+
this.injectedTargets.delete(target.id);
|
|
726
|
+
this.targetChecks.delete(target.id);
|
|
727
|
+
}
|
|
708
728
|
if (target.type !== "page" || target.url !== "app://-/index.html" || this.injectedTargets.has(target.id)) {
|
|
709
729
|
continue;
|
|
710
730
|
}
|
|
@@ -724,6 +744,12 @@ export class CodexRuntime {
|
|
|
724
744
|
await client.send("Runtime.addBinding", { name: HOST_BINDING_NAME });
|
|
725
745
|
const bindingToken = randomBytes(24).toString("hex");
|
|
726
746
|
this.bindingTokens.set(target.id, bindingToken);
|
|
747
|
+
client.on?.("Runtime.executionContextsCleared", () => {
|
|
748
|
+
if (this.clients.get(target.id) !== client) return;
|
|
749
|
+
// 新文档撤销启动检查,下一次接入周期重新检查一次。
|
|
750
|
+
this.targetChecks.delete(target.id);
|
|
751
|
+
this.notifyStatusChange();
|
|
752
|
+
});
|
|
727
753
|
client.on?.("Runtime.bindingCalled", (params) => {
|
|
728
754
|
let action;
|
|
729
755
|
try { action = JSON.parse(params.payload)?.action; } catch { return; }
|
|
@@ -756,6 +782,7 @@ export class CodexRuntime {
|
|
|
756
782
|
this.clients.set(target.id, client);
|
|
757
783
|
this.scriptIds.set(target.id, scriptIdentifier);
|
|
758
784
|
this.injectedTargets.add(target.id);
|
|
785
|
+
await this.checkTarget(target.id);
|
|
759
786
|
this.targetFailures.delete(target.id);
|
|
760
787
|
this.notifyStatusChange();
|
|
761
788
|
} catch (error) {
|
|
@@ -784,6 +811,28 @@ export class CodexRuntime {
|
|
|
784
811
|
this.notifyStatusChange();
|
|
785
812
|
}
|
|
786
813
|
|
|
814
|
+
async checkTarget(targetId) {
|
|
815
|
+
const client=this.clients.get(targetId);
|
|
816
|
+
if (!client || this.stopping) return false;
|
|
817
|
+
if (this.targetChecks.has(targetId)) return true;
|
|
818
|
+
try {
|
|
819
|
+
const response=await client.send("Runtime.evaluate", {
|
|
820
|
+
expression:`(() => { const runtime=window.__codexPersonalRuntime; return runtime?.version === ${RUNTIME_VERSION} && runtime.sessionId === ${JSON.stringify(this.runtimeSessionId)} ? runtime.selfCheck() : null; })()`,
|
|
821
|
+
returnByValue:true,
|
|
822
|
+
}, {timeoutMs:3_000});
|
|
823
|
+
if (this.clients.get(targetId) !== client || this.stopping) return false;
|
|
824
|
+
const snapshot=response.result?.value;
|
|
825
|
+
if (!snapshot) return false;
|
|
826
|
+
if (snapshot.sessionId !== this.runtimeSessionId || !Array.isArray(snapshot.plugins)) throw new Error("Invalid self-check snapshot");
|
|
827
|
+
this.targetChecks.set(targetId,snapshot);
|
|
828
|
+
return true;
|
|
829
|
+
} catch {
|
|
830
|
+
// 无法读到当前证据时立即撤销旧成功,不把临时 CDP 故障解释为插件不兼容。
|
|
831
|
+
this.targetChecks.set(targetId,{sessionId:this.runtimeSessionId,checkedAt:null,plugins:[],tokens:[],reason:"self-check-unavailable"});
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
787
836
|
async publishSessionStatus(onlyTargetId = null) {
|
|
788
837
|
for (const [targetId, client] of this.clients) {
|
|
789
838
|
if (onlyTargetId && targetId !== onlyTargetId) continue;
|
|
@@ -815,6 +864,7 @@ export class CodexRuntime {
|
|
|
815
864
|
if (this.clients.get(targetId) === client) {
|
|
816
865
|
client.socket.close();
|
|
817
866
|
this.injectedTargets.delete(targetId);
|
|
867
|
+
this.targetChecks.delete(targetId);
|
|
818
868
|
this.notifyStatusChange();
|
|
819
869
|
}
|
|
820
870
|
}
|
|
@@ -858,6 +908,7 @@ export class CodexRuntime {
|
|
|
858
908
|
this.injectedTargets.clear();
|
|
859
909
|
this.rendererTargetIds.clear();
|
|
860
910
|
this.targetFailures.clear();
|
|
911
|
+
this.targetChecks.clear();
|
|
861
912
|
this.rendererDiscovered = false;
|
|
862
913
|
this.notifyStatusChange();
|
|
863
914
|
this.monitorPromise = null;
|
|
@@ -834,10 +834,9 @@ export async function startFeatureProcesses(features, loggerOrOptions = console,
|
|
|
834
834
|
getFailures() {
|
|
835
835
|
return records.map(featureFailure).filter(Boolean);
|
|
836
836
|
},
|
|
837
|
-
getStatuses(
|
|
837
|
+
getStatuses() {
|
|
838
838
|
return records.map((record) => {
|
|
839
|
-
|
|
840
|
-
if (rendererActive && (state === "configured" || state === "ready")) state = "injected";
|
|
839
|
+
const state = record.state;
|
|
841
840
|
return {
|
|
842
841
|
featureId: record.featureId,
|
|
843
842
|
state,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import os from "node:os";
|
|
3
|
-
import { writeFile } from "node:fs/promises";
|
|
3
|
+
import { writeFile, rename } from "node:fs/promises";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { CodexRuntime, createRuntimeReadyContract } from "./codex-runtime.mjs";
|
|
@@ -43,16 +43,19 @@ const updateCodexPidFile = createManagedPidFileWriter(process.env.MINECODEX_CODE
|
|
|
43
43
|
function writeReadyFile() {
|
|
44
44
|
if (!process.env.MINECODEX_RUNTIME_READY_FILE) return Promise.resolve();
|
|
45
45
|
const filePath = process.env.MINECODEX_RUNTIME_READY_FILE;
|
|
46
|
-
readyWritePromise = readyWritePromise.catch(() => {}).then(() =>
|
|
46
|
+
readyWritePromise = readyWritePromise.catch(() => {}).then(async () => {
|
|
47
|
+
await writeFile(`${filePath}.pending`, `${JSON.stringify(
|
|
47
48
|
createRuntimeReadyContract({
|
|
48
49
|
pid: process.pid,
|
|
49
50
|
features,
|
|
50
|
-
plugins: featureProcesses.getStatuses(
|
|
51
|
+
plugins: featureProcesses.getStatuses(),
|
|
51
52
|
failures: featureProcesses.getFailures(),
|
|
52
53
|
renderer: runtime?.getRendererStatus(),
|
|
53
54
|
runtimeSessionId: runtime?.runtimeSessionId,
|
|
54
55
|
}),
|
|
55
|
-
|
|
56
|
+
)}\n`, { mode: 0o600 });
|
|
57
|
+
await rename(`${filePath}.pending`,filePath);
|
|
58
|
+
});
|
|
56
59
|
return readyWritePromise;
|
|
57
60
|
}
|
|
58
61
|
|
|
@@ -71,6 +74,7 @@ featureProcesses = await startFeatureProcesses(features, {
|
|
|
71
74
|
runtime = new CodexRuntime({
|
|
72
75
|
features,
|
|
73
76
|
featureInstanceId,
|
|
77
|
+
codexBuild:process.env.MINECODEX_CODEX_BUILD || null,
|
|
74
78
|
profileDir,
|
|
75
79
|
cdpPort,
|
|
76
80
|
onManagedCodexPidChange: updateCodexPidFile,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// 只管理安装证据;必要原生能力与适用场景由功能所属 Adapter 判定。
|
|
2
|
+
export function tokenRequirements(source, bindings) {
|
|
3
|
+
const nativeNames=new Map(Object.entries(bindings).map(([native,project])=>[project,native]));
|
|
4
|
+
const requirements=new Map();
|
|
5
|
+
for (const match of source.matchAll(/var\((--mcx-[a-z0-9-]+)\s*([,)])/g)) {
|
|
6
|
+
const name=nativeNames.get(match[1]) ?? match[1];
|
|
7
|
+
requirements.set(name,{name,required:match[2] === ")" || requirements.get(name)?.required === true,
|
|
8
|
+
...(nativeNames.has(match[1]) ? {} : {unmapped:true})});
|
|
9
|
+
}
|
|
10
|
+
return [...requirements.values()];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function createRendererSelfCheck(features, sessionId, theme) {
|
|
14
|
+
const records = new Map(features.map(feature => [feature.id, { featureId:feature.id, tokens:feature.requiredTokens ?? [], installation:"pending", probe:null }]));
|
|
15
|
+
function register(featureId, probe) {
|
|
16
|
+
if (typeof probe !== "function") throw new TypeError("Self-check must be a function");
|
|
17
|
+
records.get(featureId).probe = probe;
|
|
18
|
+
}
|
|
19
|
+
function installed(featureId) { records.get(featureId).installation = "installed"; }
|
|
20
|
+
function failed(featureId) { records.get(featureId).installation = "failed"; }
|
|
21
|
+
function snapshot() {
|
|
22
|
+
return {
|
|
23
|
+
sessionId,
|
|
24
|
+
checkedAt:Date.now(),
|
|
25
|
+
tokens:theme.diagnostics(),
|
|
26
|
+
plugins:[...records.values()].map(record => {
|
|
27
|
+
const base = { featureId:record.featureId, installation:record.installation };
|
|
28
|
+
if (record.installation === "failed") return {...base,state:"failed",reason:"installation-failed",checks:[]};
|
|
29
|
+
if (record.installation !== "installed" || !record.probe) return {...base,state:"unknown",reason:"self-check-pending",checks:[]};
|
|
30
|
+
try {
|
|
31
|
+
const result = record.probe();
|
|
32
|
+
const states = ["available","not-applicable","unavailable","unknown"];
|
|
33
|
+
if (!states.includes(result?.state)) throw new TypeError("Invalid self-check result");
|
|
34
|
+
const scopes = result.scopes ?? [];
|
|
35
|
+
const requirements=record.tokens.map(token=>typeof token === "string" ? {name:token,required:true} : token);
|
|
36
|
+
const tokens = scopes.filter(scope => scope.element?.isConnected).flatMap(scope =>
|
|
37
|
+
theme.checkTokens(scope.element,requirements.map(token=>token.name)).map(token => {
|
|
38
|
+
const requirement=requirements.find(item=>item.name === token.name);
|
|
39
|
+
return {...token,scope:scope.name,required:requirement.required,
|
|
40
|
+
...(requirement.unmapped ? {available:false,reason:"token-mapping-unavailable"} : {})};
|
|
41
|
+
}));
|
|
42
|
+
const missingTokens = tokens.some(token => token.required && (!token.available || (token.projectName && (!token.projectValue || token.scopeMatches === false))));
|
|
43
|
+
// 不导出异常文本、原生对象、凭据或用户内容。
|
|
44
|
+
return {...base,state:result.state === "available" && missingTokens ? "degraded" : result.state,
|
|
45
|
+
reason:result.state === "available" && missingTokens ? "design-token-unavailable" : result.reason ?? null,tokens,
|
|
46
|
+
checks:(result.checks ?? []).map(check => ({
|
|
47
|
+
name:check.name,available:check.available === true,reason:check.reason ?? null,
|
|
48
|
+
}))};
|
|
49
|
+
} catch {
|
|
50
|
+
return {...base,state:"unavailable",reason:"self-check-failed",checks:[]};
|
|
51
|
+
}
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {register,installed,failed,snapshot};
|
|
56
|
+
}
|
|
@@ -7,11 +7,16 @@ export function installComposerMenu(signal, ui) {
|
|
|
7
7
|
ui.surface(node, "composer-menu");
|
|
8
8
|
ui.layer(node, "floating");
|
|
9
9
|
const rect = body.getBoundingClientRect();
|
|
10
|
+
const root = anchor.closest("[data-codex-composer-root], [data-chatgpt-composer]");
|
|
11
|
+
// 首页 Add 向下展开;已有会话保留向上的输入框菜单。
|
|
12
|
+
const below = root?.getAttribute("data-composer-placement") === "home";
|
|
10
13
|
const zoom = Number.parseFloat(getComputedStyle(node).zoom) || 1;
|
|
11
14
|
node.style.left = `${rect.left / zoom}px`;
|
|
12
|
-
node.style.top = `${(rect.top - gap) / zoom}px`;
|
|
15
|
+
node.style.top = `${(below ? rect.bottom + gap : rect.top - gap) / zoom}px`;
|
|
16
|
+
node.style.transform = below ? "translateY(0)" : "translateY(-100%)";
|
|
13
17
|
node.style.width = `${rect.width / zoom}px`;
|
|
14
|
-
|
|
18
|
+
const availableHeight = below ? innerHeight - rect.bottom - gap - 16 : rect.top - 16;
|
|
19
|
+
node.style.maxHeight = `${Math.max(0, Math.min(maxHeight, availableHeight / zoom))}px`;
|
|
15
20
|
return node;
|
|
16
21
|
},
|
|
17
22
|
};
|
|
@@ -31,5 +31,20 @@ export function createNativeThemeAdapter(themeTokenNames,themeTokenAliases,theme
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function dispose() {for (const [name,value] of previousThemeBindings) { if(value) document.documentElement.style.setProperty(name,value); else document.documentElement.style.removeProperty(name); } }
|
|
34
|
-
|
|
34
|
+
function checkTokens(element, names) {
|
|
35
|
+
const style = getComputedStyle(element);
|
|
36
|
+
return names.map(name => {
|
|
37
|
+
const candidates = [name,...(themeTokenAliases[name] ?? [])].flatMap(candidate => candidate.startsWith("--color-")
|
|
38
|
+
? [candidate,candidate.replace("--color-","--app-color-")] : [candidate]);
|
|
39
|
+
const source = candidates.find(candidate => style.getPropertyValue(candidate).trim());
|
|
40
|
+
const value = source ? style.getPropertyValue(source).trim() : null;
|
|
41
|
+
const projectName = themeBindings[name];
|
|
42
|
+
const projectValue = projectName ? style.getPropertyValue(projectName).trim() || null : null;
|
|
43
|
+
return {name,source:source ?? null,value,available:Boolean(value),
|
|
44
|
+
projectName:projectName ?? null,projectValue,scopeMatches:!projectName || projectValue === value,
|
|
45
|
+
reason:!value ? "native-token-unavailable" : projectName && !projectValue ? "project-token-unavailable"
|
|
46
|
+
: projectName && projectValue !== value ? "scope-mapping-mismatch" : null};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return {read,dispose,checkTokens,diagnostics:() => checkTokens(document.documentElement,themeTokenNames)};
|
|
35
50
|
}
|