mesurer-solid 0.2.1-beta.0 → 0.2.1-beta.2
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/AGENT_INTEGRATION.md +3 -3
- package/README.md +7 -6
- package/dist/index.d.ts +16 -14
- package/dist/index.js +3950 -3770
- package/dist/inject-script.js +14 -14
- package/dist/inject.js +2652 -2472
- package/dist/plugin-catalog.d.ts +0 -7
- package/dist/plugins/codex/index.d.ts +1 -1
- package/dist/plugins.js +1342 -1233
- package/package.json +6 -1
- package/plugins/codex/bridge.d.ts +34 -0
- package/plugins/codex/bridge.mjs +158 -0
- package/plugins/codex/preload.cjs +25 -0
- package/plugins/codex/preload.d.ts +22 -0
- package/plugins/codex/preload.mjs +18 -0
- package/skills/mesurer-ui/SKILL.md +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mesurer-solid",
|
|
3
|
-
"version": "0.2.1-beta.
|
|
3
|
+
"version": "0.2.1-beta.2",
|
|
4
4
|
"description": "Framework-agnostic UI measurement and inspection tools for browser apps and coding agents.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -32,6 +32,11 @@
|
|
|
32
32
|
"./plugins/codex/bridge": {
|
|
33
33
|
"types": "./plugins/codex/bridge.d.ts",
|
|
34
34
|
"default": "./plugins/codex/bridge.mjs"
|
|
35
|
+
},
|
|
36
|
+
"./plugins/codex/preload": {
|
|
37
|
+
"types": "./plugins/codex/preload.d.ts",
|
|
38
|
+
"import": "./plugins/codex/preload.mjs",
|
|
39
|
+
"require": "./plugins/codex/preload.cjs"
|
|
35
40
|
}
|
|
36
41
|
},
|
|
37
42
|
"files": [
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export type CodexBridgeAction =
|
|
2
|
+
| "activate"
|
|
3
|
+
| "deactivate"
|
|
2
4
|
| "runtime"
|
|
3
5
|
| "health"
|
|
4
6
|
| "threads"
|
|
@@ -9,6 +11,7 @@ export type CodexBridgeAction =
|
|
|
9
11
|
|
|
10
12
|
export type CodexBridgeRequest = {
|
|
11
13
|
action: CodexBridgeAction;
|
|
14
|
+
leaseId?: string;
|
|
12
15
|
thread?: string;
|
|
13
16
|
limit?: number;
|
|
14
17
|
message?: string;
|
|
@@ -21,6 +24,8 @@ export type CodexBridgeOptions = {
|
|
|
21
24
|
codex?: string;
|
|
22
25
|
/** Codex state directory. Defaults to CODEX_HOME, then ~/.codex. */
|
|
23
26
|
codexHome?: string;
|
|
27
|
+
/** Native host client identity. Electron hosts should derive this from the invoking WebContents. */
|
|
28
|
+
clientId?: string;
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
export type CodexBridgeRuntime = {
|
|
@@ -39,6 +44,8 @@ export type CodexBridgeThread = {
|
|
|
39
44
|
|
|
40
45
|
export type CodexBridgeResponse = {
|
|
41
46
|
ok: true;
|
|
47
|
+
leaseId?: string;
|
|
48
|
+
released?: boolean;
|
|
42
49
|
runtime?: CodexBridgeRuntime;
|
|
43
50
|
thread?: string | null;
|
|
44
51
|
threads?: string[];
|
|
@@ -61,3 +68,30 @@ export function codexBridge(
|
|
|
61
68
|
request: CodexBridgeRequest,
|
|
62
69
|
options?: CodexBridgeOptions,
|
|
63
70
|
): Promise<CodexBridgeResponse>;
|
|
71
|
+
|
|
72
|
+
export type MesurerCodexHostEvent = {
|
|
73
|
+
sender: {
|
|
74
|
+
id: number;
|
|
75
|
+
on?(event: "did-navigate" | "render-process-gone" | "destroyed", listener: () => void): void;
|
|
76
|
+
removeListener?(event: "did-navigate" | "render-process-gone" | "destroyed", listener: () => void): void;
|
|
77
|
+
};
|
|
78
|
+
senderFrame?: { parent?: object | null } | null;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export type MesurerCodexHostOptions = Omit<CodexBridgeOptions, "clientId"> & {
|
|
82
|
+
ipcMain: {
|
|
83
|
+
handle(channel: string, listener: (event: MesurerCodexHostEvent, request: CodexBridgeRequest) => CodexBridgeResponse | Promise<CodexBridgeResponse>): void;
|
|
84
|
+
removeHandler(channel: string): void;
|
|
85
|
+
};
|
|
86
|
+
channel?: string;
|
|
87
|
+
validateSender(event: MesurerCodexHostEvent): boolean | Promise<boolean>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export type MesurerCodexHost = {
|
|
91
|
+
channel: string;
|
|
92
|
+
dispose(): void;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const MESURER_CODEX_BRIDGE_CHANNEL: "mesurer:codex-bridge";
|
|
96
|
+
|
|
97
|
+
export function installMesurerCodexHost(options: MesurerCodexHostOptions): MesurerCodexHost;
|
package/plugins/codex/bridge.mjs
CHANGED
|
@@ -29,6 +29,8 @@ const MAX_DELIVERIES = 100;
|
|
|
29
29
|
|
|
30
30
|
const deliveries = new Map();
|
|
31
31
|
|
|
32
|
+
const leases = new Map();
|
|
33
|
+
|
|
32
34
|
let deliveryStateLoadPromise = null;
|
|
33
35
|
|
|
34
36
|
let loadedDeliveryStatePath = null;
|
|
@@ -51,6 +53,37 @@ const normalizeTitle = (value) => {
|
|
|
51
53
|
|
|
52
54
|
const normalizeTimestamp = (value) => Number.isFinite(value) ? Number(value) : null;
|
|
53
55
|
|
|
56
|
+
const bridgeClientId = (options) => normalizeString(options.clientId) ?? "direct";
|
|
57
|
+
|
|
58
|
+
const requireLease = (request, options) => {
|
|
59
|
+
const leaseId = normalizeString(request.leaseId);
|
|
60
|
+
|
|
61
|
+
if (!leaseId) throw new Error("Codex Bridge request requires an active lease.");
|
|
62
|
+
|
|
63
|
+
const lease = leases.get(leaseId);
|
|
64
|
+
|
|
65
|
+
if (!lease || lease.clientId !== bridgeClientId(options)) {
|
|
66
|
+
throw new Error("Codex Bridge lease is not active for this host client.");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return lease;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const releaseCodexBridgeClient = (clientId) => {
|
|
73
|
+
const normalized = normalizeString(clientId);
|
|
74
|
+
|
|
75
|
+
if (!normalized) return 0;
|
|
76
|
+
let released = 0;
|
|
77
|
+
|
|
78
|
+
for (const [leaseId, lease] of leases) {
|
|
79
|
+
if (lease.clientId !== normalized) continue;
|
|
80
|
+
leases.delete(leaseId);
|
|
81
|
+
released += 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return released;
|
|
85
|
+
};
|
|
86
|
+
|
|
54
87
|
const shortThread = (thread) => thread.length > 16
|
|
55
88
|
? `${thread.slice(0, 8)}…${thread.slice(-4)}`
|
|
56
89
|
: thread;
|
|
@@ -1233,6 +1266,125 @@ const restoreDelivery = async (request, options, desktop = null) => {
|
|
|
1233
1266
|
};
|
|
1234
1267
|
};
|
|
1235
1268
|
|
|
1269
|
+
const activateBridge = async (request, options) => {
|
|
1270
|
+
const desktop = await desktopTransport(options);
|
|
1271
|
+
|
|
1272
|
+
await ensureDeliveriesLoaded(options);
|
|
1273
|
+
|
|
1274
|
+
const ready = desktop
|
|
1275
|
+
? desktopHealth(desktop)
|
|
1276
|
+
: await health(normalizeString(request.thread), options);
|
|
1277
|
+
|
|
1278
|
+
const leaseId = randomUUID();
|
|
1279
|
+
|
|
1280
|
+
leases.set(leaseId, {
|
|
1281
|
+
clientId: bridgeClientId(options),
|
|
1282
|
+
createdAt: Date.now(),
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
return {
|
|
1286
|
+
...ready,
|
|
1287
|
+
leaseId,
|
|
1288
|
+
};
|
|
1289
|
+
};
|
|
1290
|
+
|
|
1291
|
+
const deactivateBridge = (request, options) => {
|
|
1292
|
+
requireLease(request, options);
|
|
1293
|
+
const leaseId = normalizeString(request.leaseId);
|
|
1294
|
+
|
|
1295
|
+
leases.delete(leaseId);
|
|
1296
|
+
|
|
1297
|
+
return {
|
|
1298
|
+
ok: true,
|
|
1299
|
+
leaseId,
|
|
1300
|
+
released: true,
|
|
1301
|
+
};
|
|
1302
|
+
};
|
|
1303
|
+
|
|
1304
|
+
export const MESURER_CODEX_BRIDGE_CHANNEL = "mesurer:codex-bridge";
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* Install Mesurer's Codex IPC handler in an Electron main process.
|
|
1308
|
+
*
|
|
1309
|
+
* The adapter binds native leases to the invoking WebContents, rejects subframe
|
|
1310
|
+
* callers, supports an application-owned sender validator, and releases every
|
|
1311
|
+
* lease when that renderer navigates, exits, or is destroyed.
|
|
1312
|
+
*/
|
|
1313
|
+
export function installMesurerCodexHost(options = {}) {
|
|
1314
|
+
const ipcMain = options.ipcMain;
|
|
1315
|
+
|
|
1316
|
+
if (!ipcMain?.handle || !ipcMain?.removeHandler) {
|
|
1317
|
+
throw new Error("installMesurerCodexHost requires Electron ipcMain.");
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
if (!(options.validateSender instanceof Function)) {
|
|
1321
|
+
throw new Error("installMesurerCodexHost requires validateSender(event).");
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
const channel = normalizeString(options.channel) ?? MESURER_CODEX_BRIDGE_CHANNEL;
|
|
1325
|
+
const clients = new Map();
|
|
1326
|
+
|
|
1327
|
+
const releaseSender = (clientId) => {
|
|
1328
|
+
const record = clients.get(clientId);
|
|
1329
|
+
|
|
1330
|
+
if (!record) return;
|
|
1331
|
+
clients.delete(clientId);
|
|
1332
|
+
releaseCodexBridgeClient(clientId);
|
|
1333
|
+
|
|
1334
|
+
record.sender.removeListener?.("did-navigate", record.release);
|
|
1335
|
+
record.sender.removeListener?.("render-process-gone", record.release);
|
|
1336
|
+
record.sender.removeListener?.("destroyed", record.release);
|
|
1337
|
+
};
|
|
1338
|
+
|
|
1339
|
+
const bindSender = (sender) => {
|
|
1340
|
+
const clientId = String(sender.id);
|
|
1341
|
+
|
|
1342
|
+
if (clients.has(clientId)) return clientId;
|
|
1343
|
+
|
|
1344
|
+
const release = () => releaseSender(clientId);
|
|
1345
|
+
|
|
1346
|
+
clients.set(clientId, { sender, release });
|
|
1347
|
+
sender.on?.("did-navigate", release);
|
|
1348
|
+
sender.on?.("render-process-gone", release);
|
|
1349
|
+
sender.on?.("destroyed", release);
|
|
1350
|
+
|
|
1351
|
+
return clientId;
|
|
1352
|
+
};
|
|
1353
|
+
|
|
1354
|
+
ipcMain.handle(channel, async (event, request) => {
|
|
1355
|
+
if (event?.senderFrame?.parent) {
|
|
1356
|
+
throw new Error("Mesurer Codex host requests are only accepted from the main frame.");
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
if (!await options.validateSender(event)) {
|
|
1360
|
+
throw new Error("Mesurer Codex host rejected the invoking renderer.");
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
const sender = event?.sender;
|
|
1364
|
+
|
|
1365
|
+
if (!sender || !Number.isFinite(sender.id)) {
|
|
1366
|
+
throw new Error("Mesurer Codex host could not identify the invoking renderer.");
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const clientId = bindSender(sender);
|
|
1370
|
+
|
|
1371
|
+
return codexBridge(request, {
|
|
1372
|
+
codex: options.codex,
|
|
1373
|
+
codexHome: options.codexHome,
|
|
1374
|
+
clientId,
|
|
1375
|
+
});
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
return {
|
|
1379
|
+
channel,
|
|
1380
|
+
dispose() {
|
|
1381
|
+
ipcMain.removeHandler(channel);
|
|
1382
|
+
|
|
1383
|
+
for (const clientId of clients.keys()) releaseSender(clientId);
|
|
1384
|
+
},
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1236
1388
|
/**
|
|
1237
1389
|
* Native Codex transport for Mesurer's Codex plugin.
|
|
1238
1390
|
*
|
|
@@ -1256,6 +1408,12 @@ export async function codexBridge(request, options = {}) {
|
|
|
1256
1408
|
};
|
|
1257
1409
|
}
|
|
1258
1410
|
|
|
1411
|
+
if (action === "activate") return activateBridge(request, options);
|
|
1412
|
+
|
|
1413
|
+
if (action === "deactivate") return deactivateBridge(request, options);
|
|
1414
|
+
|
|
1415
|
+
requireLease(request, options);
|
|
1416
|
+
|
|
1259
1417
|
const desktop = await desktopTransport(options);
|
|
1260
1418
|
|
|
1261
1419
|
await ensureDeliveriesLoaded(options);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const MESURER_CODEX_BRIDGE_CHANNEL = "mesurer:codex-bridge";
|
|
4
|
+
|
|
5
|
+
const createMesurerCodexPreloadBridge = (
|
|
6
|
+
ipcRenderer,
|
|
7
|
+
options = {},
|
|
8
|
+
) => {
|
|
9
|
+
if (!ipcRenderer?.invoke) {
|
|
10
|
+
throw new Error("createMesurerCodexPreloadBridge requires Electron ipcRenderer.");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const configuredChannel = options.channel?.constructor === String
|
|
14
|
+
? options.channel.trim()
|
|
15
|
+
: "";
|
|
16
|
+
|
|
17
|
+
const channel = configuredChannel || MESURER_CODEX_BRIDGE_CHANNEL;
|
|
18
|
+
|
|
19
|
+
return (request) => ipcRenderer.invoke(channel, request);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
MESURER_CODEX_BRIDGE_CHANNEL,
|
|
24
|
+
createMesurerCodexPreloadBridge,
|
|
25
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CodexBridgeRequest,
|
|
3
|
+
CodexBridgeResponse,
|
|
4
|
+
} from "./bridge";
|
|
5
|
+
|
|
6
|
+
export type MesurerCodexPreloadOptions = {
|
|
7
|
+
channel?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type MesurerCodexIpcRenderer = {
|
|
11
|
+
invoke(
|
|
12
|
+
channel: string,
|
|
13
|
+
request: CodexBridgeRequest,
|
|
14
|
+
): Promise<CodexBridgeResponse>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const MESURER_CODEX_BRIDGE_CHANNEL: "mesurer:codex-bridge";
|
|
18
|
+
|
|
19
|
+
export function createMesurerCodexPreloadBridge(
|
|
20
|
+
ipcRenderer: MesurerCodexIpcRenderer,
|
|
21
|
+
options?: MesurerCodexPreloadOptions,
|
|
22
|
+
): (request: CodexBridgeRequest) => Promise<CodexBridgeResponse>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const MESURER_CODEX_BRIDGE_CHANNEL = "mesurer:codex-bridge";
|
|
2
|
+
|
|
3
|
+
export function createMesurerCodexPreloadBridge(
|
|
4
|
+
ipcRenderer,
|
|
5
|
+
options = {},
|
|
6
|
+
) {
|
|
7
|
+
if (!ipcRenderer?.invoke) {
|
|
8
|
+
throw new Error("createMesurerCodexPreloadBridge requires Electron ipcRenderer.");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const configuredChannel = options.channel?.constructor === String
|
|
12
|
+
? options.channel.trim()
|
|
13
|
+
: "";
|
|
14
|
+
|
|
15
|
+
const channel = configuredChannel || MESURER_CODEX_BRIDGE_CHANNEL;
|
|
16
|
+
|
|
17
|
+
return (request) => ipcRenderer.invoke(channel, request);
|
|
18
|
+
}
|
|
@@ -9,7 +9,7 @@ Mesurer is shared visual state between the person reviewing a page and the codin
|
|
|
9
9
|
|
|
10
10
|
The normal coding-agent workflow requires no Mesurer MCP server, chat-delivery daemon, or Send-to-agent callback. Use the browser/evaluation channel the browser controller already provides and read `window.__MESURER__` directly. The first-party `codex()` plugin is a separate human-initiated convenience for routing Context feedback through the application's native Codex host capability.
|
|
11
11
|
|
|
12
|
-
Do not add a second Mesurer process for Codex delivery. Native applications expose the narrow `window.__MESURER_HOST__.codexBridge(request)` capability. Shared sessions use Codex's local app-server. When the native host inherited both `CODEX_THREAD_ID` and `CODEX_APP_TOOLS_PIPE_PATH` from Codex Desktop, Mesurer may queue only to that exact thread and wake it through `codex://threads/<id>`. Treat the app-tools pipe as an ownership signal only
|
|
12
|
+
Do not add a second Mesurer process for Codex delivery. Native applications expose the narrow `window.__MESURER_HOST__.codexBridge(request)` capability. The enabled plugin owns a renderer-scoped native lease; disabling it releases that lease without stopping Codex's shared daemon. Shared sessions use Codex's local app-server. When the native host inherited both `CODEX_THREAD_ID` and `CODEX_APP_TOOLS_PIPE_PATH` from Codex Desktop, Mesurer may queue only to that exact thread and wake it through `codex://threads/<id>`. Treat the app-tools pipe as an ownership signal only. Never connect to, proxy, or invoke it. A Desktop-bundled Codex executable must never start the shared daemon.
|
|
13
13
|
|
|
14
14
|
A meaningful Mesurer step must return evidence the agent actually uses.
|
|
15
15
|
|
|
@@ -133,7 +133,7 @@ If review is still numerically wrong, continue editing. If target status is stal
|
|
|
133
133
|
|
|
134
134
|
The human-facing inspection tool is **Typography**. The internal compatibility id remains `text-inspector`; do not automate normal application work by guessing toolbar labels.
|
|
135
135
|
|
|
136
|
-
Direct editing starts by double-click or double-tap while Edit is active. Typography in Select is inspection-only. Edit also owns the existing Arrange
|
|
136
|
+
Direct editing starts by double-click or double-tap while Edit is active. Typography in Select is inspection-only. Edit also owns movement intent stored under the existing Arrange compatibility names.
|
|
137
137
|
|
|
138
138
|
The editor exposes direct B/I/U, Font, Size, Weight, rendered-page colors, custom color, and a separate Text/H1/H2/H3 semantic preset popup. Missing heading levels are not invented.
|
|
139
139
|
|