mesurer-solid 0.2.0-beta.8 → 0.2.0
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 +8 -13
- package/README.md +13 -21
- package/dist/arrange.d.ts +2 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2193 -1828
- package/dist/inject-script.js +23 -14
- package/dist/inject.js +2188 -1823
- package/dist/plugin-catalog.d.ts +5 -1
- package/dist/{codex-plugin.d.ts → plugins/codex/index.d.ts} +17 -19
- package/dist/plugins.d.ts +3 -3
- package/dist/plugins.js +1445 -1090
- package/package.json +8 -9
- package/plugins/codex/bridge.d.ts +63 -0
- package/plugins/codex/bridge.mjs +1304 -0
- package/plugins/codex/desktop.mjs +155 -0
- package/scripts/install-skill.mjs +1 -10
- package/skills/mesurer-ui/SKILL.md +12 -29
- package/codex/codex-bridge.mjs +0 -2037
- package/codex/codex-connect.mjs +0 -324
- package/codex/codex-lifecycle.mjs +0 -51
- package/scripts/codex-bridge.mjs +0 -2
- package/scripts/codex-connect.mjs +0 -2
- package/scripts/codex-lifecycle.mjs +0 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
const DESKTOP_COMMAND_TIMEOUT_MS = 10_000;
|
|
4
|
+
|
|
5
|
+
const normalizeString = (value) => {
|
|
6
|
+
if (value?.constructor !== String) return null;
|
|
7
|
+
|
|
8
|
+
const text = value.trim();
|
|
9
|
+
|
|
10
|
+
return text || null;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const desktopSessionFromEnvironment = (env = process.env) => {
|
|
14
|
+
const thread = normalizeString(env.CODEX_THREAD_ID);
|
|
15
|
+
const appToolsPipe = normalizeString(env.CODEX_APP_TOOLS_PIPE_PATH);
|
|
16
|
+
|
|
17
|
+
if (!thread || !appToolsPipe) return null;
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
thread,
|
|
21
|
+
appToolsPipe,
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const run = (
|
|
26
|
+
command,
|
|
27
|
+
args,
|
|
28
|
+
env,
|
|
29
|
+
timeoutMs,
|
|
30
|
+
label,
|
|
31
|
+
) => new Promise((resolve, reject) => {
|
|
32
|
+
const child = spawn(command, args, {
|
|
33
|
+
env,
|
|
34
|
+
shell: false,
|
|
35
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
36
|
+
windowsHide: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
let stdout = "";
|
|
40
|
+
let stderr = "";
|
|
41
|
+
let settled = false;
|
|
42
|
+
|
|
43
|
+
const finish = (error, output) => {
|
|
44
|
+
if (settled) return;
|
|
45
|
+
settled = true;
|
|
46
|
+
clearTimeout(timeout);
|
|
47
|
+
|
|
48
|
+
if (error) reject(error);
|
|
49
|
+
else resolve(output);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const timeout = setTimeout(() => {
|
|
53
|
+
if (child.exitCode === null) child.kill("SIGTERM");
|
|
54
|
+
|
|
55
|
+
finish(new Error(`${label} timed out after ${timeoutMs}ms.`));
|
|
56
|
+
}, timeoutMs);
|
|
57
|
+
|
|
58
|
+
child.stdout.on("data", (chunk) => { stdout += chunk.toString(); });
|
|
59
|
+
child.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
|
|
60
|
+
child.on("error", (error) => finish(error));
|
|
61
|
+
child.on("close", (code, signal) => {
|
|
62
|
+
if (code === 0) {
|
|
63
|
+
finish(null, stdout.trim());
|
|
64
|
+
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const detail = stderr.trim()
|
|
69
|
+
|| stdout.trim()
|
|
70
|
+
|| `exit code ${code ?? "unknown"}${signal ? ` (${signal})` : ""}`;
|
|
71
|
+
|
|
72
|
+
finish(new Error(`${label} failed: ${detail}`));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const queuedSubmissionId = (output, thread) => {
|
|
77
|
+
const escapedThread = thread.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
78
|
+
|
|
79
|
+
const exact = output.match(new RegExp(
|
|
80
|
+
`Queued message\\s+(\\S+)\\s+for thread\\s+${escapedThread}\\.?`,
|
|
81
|
+
));
|
|
82
|
+
|
|
83
|
+
if (exact?.[1]) return exact[1];
|
|
84
|
+
|
|
85
|
+
const generic = output.match(/Queued message\s+(\S+)\s+for thread\s+\S+\.?/);
|
|
86
|
+
|
|
87
|
+
return generic?.[1] ?? null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const queueDesktopThread = async ({
|
|
91
|
+
command,
|
|
92
|
+
thread,
|
|
93
|
+
message,
|
|
94
|
+
env = process.env,
|
|
95
|
+
}) => {
|
|
96
|
+
const output = await run(
|
|
97
|
+
command,
|
|
98
|
+
["queue", "--thread", thread, "--message", message],
|
|
99
|
+
env,
|
|
100
|
+
DESKTOP_COMMAND_TIMEOUT_MS,
|
|
101
|
+
"Codex Desktop queue",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const submissionId = queuedSubmissionId(output, thread);
|
|
105
|
+
|
|
106
|
+
if (!submissionId) {
|
|
107
|
+
throw new Error("Codex queued the Desktop message without a recognizable submission id.");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
output,
|
|
112
|
+
queuedSubmissionId: submissionId,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const openDesktopThread = async ({
|
|
117
|
+
thread,
|
|
118
|
+
env = process.env,
|
|
119
|
+
platform = process.platform,
|
|
120
|
+
}) => {
|
|
121
|
+
const url = `codex://threads/${thread}`;
|
|
122
|
+
const override = normalizeString(env.MESURER_CODEX_DESKTOP_OPEN_BIN);
|
|
123
|
+
|
|
124
|
+
let command;
|
|
125
|
+
let args;
|
|
126
|
+
|
|
127
|
+
if (override) {
|
|
128
|
+
command = override;
|
|
129
|
+
args = [url];
|
|
130
|
+
} else if (platform === "darwin") {
|
|
131
|
+
command = "/usr/bin/open";
|
|
132
|
+
args = [url];
|
|
133
|
+
} else if (platform === "win32") {
|
|
134
|
+
command = "powershell.exe";
|
|
135
|
+
const quotedUrl = url.replaceAll("'", "''");
|
|
136
|
+
|
|
137
|
+
args = [
|
|
138
|
+
"-NoProfile",
|
|
139
|
+
"-Command",
|
|
140
|
+
`Start-Process -FilePath '${quotedUrl}'`,
|
|
141
|
+
];
|
|
142
|
+
} else {
|
|
143
|
+
throw new Error(`Codex Desktop thread wake is unsupported on platform ${platform}.`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await run(
|
|
147
|
+
command,
|
|
148
|
+
args,
|
|
149
|
+
env,
|
|
150
|
+
DESKTOP_COMMAND_TIMEOUT_MS,
|
|
151
|
+
"Codex Desktop thread wake",
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
return url;
|
|
155
|
+
};
|
|
@@ -14,9 +14,6 @@ if (command !== "install") {
|
|
|
14
14
|
} else {
|
|
15
15
|
const source = new URL("../skills/mesurer-ui/", import.meta.url);
|
|
16
16
|
const injector = new URL("../dist/inject-script.js", import.meta.url);
|
|
17
|
-
const codexBridge = new URL("../codex/codex-bridge.mjs", import.meta.url);
|
|
18
|
-
const codexConnect = new URL("../codex/codex-connect.mjs", import.meta.url);
|
|
19
|
-
const codexLifecycle = new URL("../codex/codex-lifecycle.mjs", import.meta.url);
|
|
20
17
|
const destination = resolve(process.cwd(), ".agents/skills/mesurer-ui");
|
|
21
18
|
const assets = resolve(destination, "assets");
|
|
22
19
|
|
|
@@ -26,18 +23,12 @@ if (command !== "install") {
|
|
|
26
23
|
} else if (!existsSync(injector)) {
|
|
27
24
|
console.error("Mesurer inject-script asset is missing. Reinstall mesurer-solid and retry.");
|
|
28
25
|
process.exitCode = 1;
|
|
29
|
-
} else if (!existsSync(codexBridge) || !existsSync(codexConnect) || !existsSync(codexLifecycle)) {
|
|
30
|
-
console.error("Mesurer Codex companion assets are missing. Reinstall mesurer-solid and retry.");
|
|
31
|
-
process.exitCode = 1;
|
|
32
26
|
} else {
|
|
33
27
|
if (force) rmSync(destination, { recursive: true, force: true });
|
|
34
28
|
mkdirSync(resolve(destination, ".."), { recursive: true });
|
|
35
29
|
cpSync(source, destination, { recursive: true });
|
|
36
30
|
mkdirSync(assets, { recursive: true });
|
|
37
31
|
cpSync(injector, resolve(assets, "inject-script.js"));
|
|
38
|
-
|
|
39
|
-
cpSync(codexConnect, resolve(assets, "codex-connect.mjs"));
|
|
40
|
-
cpSync(codexLifecycle, resolve(assets, "codex-lifecycle.mjs"));
|
|
41
|
-
console.log(`Installed Mesurer Agent Skill and assets at ${destination}`);
|
|
32
|
+
console.log(`Installed Mesurer Agent Skill and injector at ${destination}`);
|
|
42
33
|
}
|
|
43
34
|
}
|
|
@@ -7,9 +7,9 @@ description: Use Mesurer for frontend UI implementation, review, debugging, layo
|
|
|
7
7
|
|
|
8
8
|
Mesurer is shared visual state between the person reviewing a page and the coding agent editing it. The rendered page is the integration boundary.
|
|
9
9
|
|
|
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
|
|
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
|
-
|
|
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; never connect to, proxy, or invoke it. A Desktop-bundled Codex executable must never be used to bootstrap the shared daemon.
|
|
13
13
|
|
|
14
14
|
A meaningful Mesurer step must return evidence the agent actually uses.
|
|
15
15
|
|
|
@@ -290,39 +290,22 @@ The optional Screenshot plugin is a separate human camera workflow. Native host
|
|
|
290
290
|
|
|
291
291
|
## Optional Codex thread handoff
|
|
292
292
|
|
|
293
|
-
Use this only when the user asked for Mesurer-to-Codex delivery or the live Mesurer instance
|
|
293
|
+
Use this only when the user asked for Mesurer-to-Codex delivery or the live Mesurer instance has `mesurer.codex` enabled. Normal agent work still uses `window.__MESURER__` and browser verification.
|
|
294
294
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
```js
|
|
298
|
-
const codexEnabled = Boolean(
|
|
299
|
-
window.__MESURER_INSTANCE__?.pluginHost?.has("mesurer.codex")
|
|
300
|
-
)
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
If delivery was requested or `codexEnabled` is true, start or reuse the packaged companion from the Codex shell or tool environment:
|
|
304
|
-
|
|
305
|
-
```bash
|
|
306
|
-
node .agents/skills/mesurer-ui/assets/codex-connect.mjs
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Inside the Mesurer Solid repository, use:
|
|
310
|
-
|
|
311
|
-
```bash
|
|
312
|
-
bun run mesurer-codex-connect
|
|
313
|
-
```
|
|
295
|
+
Codex delivery uses the native host's in-process Codex Bridge. There is no Mesurer Codex server, bridge port, marketplace plugin, SessionStart hook, or thread-registration command.
|
|
314
296
|
|
|
315
297
|
Keep these delivery rules:
|
|
316
298
|
|
|
317
299
|
- Mesurer uses Queue, never `turn/steer`.
|
|
318
|
-
- Codex's
|
|
319
|
-
-
|
|
320
|
-
-
|
|
321
|
-
-
|
|
322
|
-
- Each page keeps its
|
|
300
|
+
- Codex's shared app-server and durable queue are the message source of truth.
|
|
301
|
+
- Only threads reported as loaded by Codex are sendable.
|
|
302
|
+
- The host-side bridge uses `thread/queue/add` directly. Do not shell through `codex queue` or start a parallel app-server.
|
|
303
|
+
- Mesurer's persistence file tracks correlation and lifecycle; it is not another message queue.
|
|
304
|
+
- Each page keeps its explicitly chosen thread in per-tab state. If routing is ambiguous, require a human choice.
|
|
323
305
|
- Queue submission is single-flight. Do not bypass duplicate suppression with a second request.
|
|
324
|
-
- A completed matched turn may remove only the annotation ids included in that delivery. Interrupted, failed, or
|
|
325
|
-
- Mesurer does not create Codex threads. Create or open the thread in Codex
|
|
306
|
+
- A completed matched turn may remove only the annotation ids included in that delivery. Interrupted, failed, ambiguous, or unreadable work keeps them.
|
|
307
|
+
- Mesurer does not create Codex threads. Create or open the thread in Codex first, then use it once Codex reports it as loaded.
|
|
308
|
+
- Do not stop Codex's shared app-server daemon when Mesurer closes.
|
|
326
309
|
|
|
327
310
|
The typed `codex:v1` service supports `health()`, `listThreads()`, `useThread(thread)`, `delivery(deliveryId)`, and `queue({ thread })`. `send()` is retained only as a compatibility alias. Generic automation uses `codex.queue`; `codex.send` remains its compatibility alias.
|
|
328
311
|
|