mesurer-solid 0.2.0-beta.12 → 0.2.0-beta.13
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 +7 -20
- package/dist/index.d.ts +3 -3
- package/dist/index.js +244 -219
- package/dist/inject-script.js +12 -6
- package/dist/inject.js +250 -225
- 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 +219 -206
- 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
package/codex/codex-connect.mjs
DELETED
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
5
|
-
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { parseArgs } from "node:util";
|
|
8
|
-
|
|
9
|
-
const DEFAULT_BRIDGE = "http://127.0.0.1:47365";
|
|
10
|
-
|
|
11
|
-
const BRIDGE_NAME = "mesurer-codex";
|
|
12
|
-
|
|
13
|
-
const BRIDGE_PROTOCOL_VERSION = 1;
|
|
14
|
-
|
|
15
|
-
const START_TIMEOUT_MS = 10_000;
|
|
16
|
-
|
|
17
|
-
const POLL_INTERVAL_MS = 100;
|
|
18
|
-
|
|
19
|
-
const usage = `Usage:
|
|
20
|
-
mesurer-codex-connect [options]
|
|
21
|
-
|
|
22
|
-
Ensures the Mesurer Codex bridge is running, then registers one Codex session
|
|
23
|
-
and makes that session the active Mesurer destination.
|
|
24
|
-
|
|
25
|
-
Options:
|
|
26
|
-
--thread <value> Codex session UUID or exact session name
|
|
27
|
-
--session-start Read a Codex SessionStart hook event from stdin
|
|
28
|
-
--bridge <url> Bridge URL (default: ${DEFAULT_BRIDGE})
|
|
29
|
-
--codex <path> Codex executable passed to a newly-started bridge
|
|
30
|
-
--once Start a new bridge with --once (primarily useful for tests)
|
|
31
|
-
--quiet Suppress success output
|
|
32
|
-
--help Show this help
|
|
33
|
-
|
|
34
|
-
Without --thread or --session-start, CODEX_THREAD_ID is used when available.
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
const { values } = parseArgs({
|
|
38
|
-
options: {
|
|
39
|
-
thread: { type: "string" },
|
|
40
|
-
"session-start": { type: "boolean", default: false },
|
|
41
|
-
bridge: { type: "string", default: DEFAULT_BRIDGE },
|
|
42
|
-
codex: { type: "string" },
|
|
43
|
-
once: { type: "boolean", default: false },
|
|
44
|
-
quiet: { type: "boolean", default: false },
|
|
45
|
-
help: { type: "boolean", default: false },
|
|
46
|
-
},
|
|
47
|
-
allowPositionals: false,
|
|
48
|
-
strict: true,
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
if (values.help) {
|
|
52
|
-
process.stdout.write(usage);
|
|
53
|
-
process.exit(0);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const fail = (message, code = 1) => {
|
|
57
|
-
process.stderr.write(`${message}\n`);
|
|
58
|
-
process.exit(code);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const readStdin = async () => {
|
|
62
|
-
process.stdin.setEncoding("utf8");
|
|
63
|
-
let input = "";
|
|
64
|
-
|
|
65
|
-
for await (const chunk of process.stdin) input += chunk;
|
|
66
|
-
|
|
67
|
-
return input;
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const explicitThread = values.thread?.trim() || null;
|
|
71
|
-
|
|
72
|
-
const fromSessionStart = values["session-start"];
|
|
73
|
-
|
|
74
|
-
if (explicitThread && fromSessionStart) {
|
|
75
|
-
fail("Use either --thread or --session-start, not both.", 2);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
let thread = explicitThread ?? process.env.CODEX_THREAD_ID?.trim() ?? null;
|
|
79
|
-
|
|
80
|
-
let cwd = process.cwd();
|
|
81
|
-
|
|
82
|
-
if (fromSessionStart) {
|
|
83
|
-
const input = await readStdin();
|
|
84
|
-
let event;
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
event = JSON.parse(input);
|
|
88
|
-
} catch {
|
|
89
|
-
fail("--session-start requires one Codex SessionStart JSON event on stdin.", 2);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (event?.hook_event_name !== "SessionStart") {
|
|
93
|
-
fail(`Expected hook_event_name SessionStart, got ${event?.hook_event_name ?? "<missing>"}.`, 2);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
thread = event?.session_id?.trim?.() || null;
|
|
97
|
-
cwd = event?.cwd?.trim?.() || cwd;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (!thread) {
|
|
101
|
-
fail(
|
|
102
|
-
fromSessionStart
|
|
103
|
-
? "Codex SessionStart input did not include a session_id."
|
|
104
|
-
: "mesurer-codex-connect requires --thread, CODEX_THREAD_ID, or --session-start.",
|
|
105
|
-
2,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const quiet = values.quiet || fromSessionStart;
|
|
110
|
-
|
|
111
|
-
const bridge = values.bridge?.trim() || DEFAULT_BRIDGE;
|
|
112
|
-
|
|
113
|
-
const bridgeUrl = new URL(bridge.endsWith("/") ? bridge : `${bridge}/`);
|
|
114
|
-
|
|
115
|
-
const isLoopbackHost = (hostname) =>
|
|
116
|
-
hostname === "127.0.0.1"
|
|
117
|
-
|| hostname === "localhost"
|
|
118
|
-
|| hostname === "::1"
|
|
119
|
-
|| hostname === "[::1]";
|
|
120
|
-
|
|
121
|
-
if (bridgeUrl.protocol !== "http:" || !isLoopbackHost(bridgeUrl.hostname)) {
|
|
122
|
-
fail(`Automatic bridge startup requires a loopback HTTP URL, got ${bridge}.`, 2);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (bridgeUrl.pathname !== "/" || bridgeUrl.search || bridgeUrl.hash) {
|
|
126
|
-
fail(`Automatic bridge startup requires a root bridge URL, got ${bridge}.`, 2);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const port = bridgeUrl.port ? Number(bridgeUrl.port) : 80;
|
|
130
|
-
|
|
131
|
-
if (!Number.isInteger(port) || port < 1 || port > 65_535) {
|
|
132
|
-
fail(`Invalid bridge port in ${bridge}.`, 2);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const bridgeScript = fileURLToPath(new URL("./codex-bridge.mjs", import.meta.url));
|
|
136
|
-
|
|
137
|
-
const expectedSourceHash = createHash("sha256")
|
|
138
|
-
.update(await readFile(bridgeScript))
|
|
139
|
-
.digest("hex");
|
|
140
|
-
|
|
141
|
-
const readPayload = async (response) => {
|
|
142
|
-
const text = await response.text();
|
|
143
|
-
|
|
144
|
-
if (!text) return {};
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
return JSON.parse(text);
|
|
148
|
-
} catch {
|
|
149
|
-
return { error: text };
|
|
150
|
-
}
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
const health = async () => {
|
|
154
|
-
let response;
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
response = await fetch(new URL("health", bridgeUrl));
|
|
158
|
-
} catch {
|
|
159
|
-
return null;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const payload = await readPayload(response);
|
|
163
|
-
|
|
164
|
-
if (!response.ok || payload.ok !== true) {
|
|
165
|
-
throw new Error(
|
|
166
|
-
payload.error || `Unexpected service at ${bridgeUrl.origin}: HTTP ${response.status}.`,
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return payload;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const waitForBridge = async () => {
|
|
174
|
-
const deadline = Date.now() + START_TIMEOUT_MS;
|
|
175
|
-
|
|
176
|
-
while (Date.now() < deadline) {
|
|
177
|
-
const payload = await health();
|
|
178
|
-
|
|
179
|
-
if (payload) return payload;
|
|
180
|
-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
throw new Error(`Mesurer Codex bridge did not become ready at ${bridgeUrl.origin}.`);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const isExactBridge = (payload) =>
|
|
187
|
-
payload?.bridge?.name === BRIDGE_NAME
|
|
188
|
-
&& payload.bridge.protocol === BRIDGE_PROTOCOL_VERSION
|
|
189
|
-
&& payload.bridge.sourceHash === expectedSourceHash;
|
|
190
|
-
|
|
191
|
-
const waitForBridgeToStop = async () => {
|
|
192
|
-
const deadline = Date.now() + START_TIMEOUT_MS;
|
|
193
|
-
let unavailableChecks = 0;
|
|
194
|
-
|
|
195
|
-
while (Date.now() < deadline) {
|
|
196
|
-
if (await health()) {
|
|
197
|
-
unavailableChecks = 0;
|
|
198
|
-
} else {
|
|
199
|
-
unavailableChecks += 1;
|
|
200
|
-
|
|
201
|
-
if (unavailableChecks >= 3) return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
throw new Error(`Stale Mesurer Codex bridge did not stop at ${bridgeUrl.origin}.`);
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
const replaceStaleBridge = async (payload) => {
|
|
211
|
-
if (payload?.bridge?.name !== BRIDGE_NAME || payload.bridge.canShutdown !== true) return false;
|
|
212
|
-
let response;
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
response = await fetch(new URL("shutdown", bridgeUrl), { method: "POST" });
|
|
216
|
-
} catch (cause) {
|
|
217
|
-
const error = cause instanceof Error ? cause.message : String(cause);
|
|
218
|
-
throw new Error(`Could not stop stale Mesurer Codex bridge at ${bridgeUrl.origin}: ${error}`);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const body = await readPayload(response);
|
|
222
|
-
|
|
223
|
-
if (!response.ok || body.ok === false) {
|
|
224
|
-
throw new Error(body.error || `Stale Mesurer Codex bridge returned HTTP ${response.status} while stopping.`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
await waitForBridgeToStop();
|
|
228
|
-
|
|
229
|
-
return true;
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
let current;
|
|
233
|
-
|
|
234
|
-
try {
|
|
235
|
-
current = await health();
|
|
236
|
-
} catch (cause) {
|
|
237
|
-
fail(cause instanceof Error ? cause.message : String(cause));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (current && !isExactBridge(current)) {
|
|
241
|
-
let replaced = false;
|
|
242
|
-
|
|
243
|
-
try {
|
|
244
|
-
replaced = await replaceStaleBridge(current);
|
|
245
|
-
} catch (cause) {
|
|
246
|
-
fail(cause instanceof Error ? cause.message : String(cause));
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (replaced) {
|
|
250
|
-
current = null;
|
|
251
|
-
} else if (!current.bridge && Array.isArray(current.threads)) {
|
|
252
|
-
fail(
|
|
253
|
-
`An older Mesurer Codex bridge is already running at ${bridgeUrl.origin}. Stop that legacy bridge once, then rerun mesurer-codex-connect. Refusing to reuse it because its source identity cannot be verified.`,
|
|
254
|
-
);
|
|
255
|
-
} else {
|
|
256
|
-
fail(
|
|
257
|
-
`An incompatible service is already running at ${bridgeUrl.origin}. Refusing to register this Codex session with an unverified bridge.`,
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (!current) {
|
|
263
|
-
const args = [bridgeScript, "--port", String(port), "--thread", thread, "--cwd", cwd];
|
|
264
|
-
|
|
265
|
-
if (values.codex?.trim()) args.push("--codex", values.codex.trim());
|
|
266
|
-
|
|
267
|
-
if (values.once) args.push("--once");
|
|
268
|
-
|
|
269
|
-
const child = spawn(process.execPath, args, {
|
|
270
|
-
detached: true,
|
|
271
|
-
env: process.env,
|
|
272
|
-
shell: false,
|
|
273
|
-
stdio: "ignore",
|
|
274
|
-
windowsHide: true,
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
child.unref();
|
|
278
|
-
|
|
279
|
-
try {
|
|
280
|
-
current = await waitForBridge();
|
|
281
|
-
} catch (cause) {
|
|
282
|
-
fail(cause instanceof Error ? cause.message : String(cause));
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
if (!isExactBridge(current)) {
|
|
286
|
-
fail(`Mesurer Codex bridge at ${bridgeUrl.origin} started with an unexpected source identity.`);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const registration = { thread, cwd };
|
|
291
|
-
|
|
292
|
-
const appToolsPipe = process.env.CODEX_APP_TOOLS_PIPE_PATH?.trim();
|
|
293
|
-
|
|
294
|
-
if (appToolsPipe) registration.appToolsPipe = appToolsPipe;
|
|
295
|
-
|
|
296
|
-
const codexHome = process.env.CODEX_HOME?.trim();
|
|
297
|
-
|
|
298
|
-
if (codexHome) registration.codexHome = codexHome;
|
|
299
|
-
|
|
300
|
-
let response;
|
|
301
|
-
|
|
302
|
-
try {
|
|
303
|
-
response = await fetch(new URL("threads/register", bridgeUrl), {
|
|
304
|
-
method: "POST",
|
|
305
|
-
headers: { "Content-Type": "application/json" },
|
|
306
|
-
body: JSON.stringify(registration),
|
|
307
|
-
});
|
|
308
|
-
} catch (cause) {
|
|
309
|
-
const error = cause instanceof Error ? cause.message : String(cause);
|
|
310
|
-
fail(`Could not register Codex thread with ${bridgeUrl.origin}: ${error}`);
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const payload = await readPayload(response);
|
|
314
|
-
|
|
315
|
-
if (!response.ok || payload.ok === false) {
|
|
316
|
-
fail(payload.error || `Mesurer Codex bridge returned HTTP ${response.status}.`);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (!quiet) {
|
|
320
|
-
console.log(`Mesurer Codex bridge ready: ${bridgeUrl.origin}`);
|
|
321
|
-
console.log(`Registered Codex thread: ${thread}`);
|
|
322
|
-
console.log(`BRIDGE_URL=${bridgeUrl.origin}`);
|
|
323
|
-
console.log(`BRIDGE_THREAD=${thread}`);
|
|
324
|
-
}
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { parseArgs } from "node:util";
|
|
4
|
-
|
|
5
|
-
const DEFAULT_BRIDGE = "http://127.0.0.1:47365";
|
|
6
|
-
|
|
7
|
-
const { values } = parseArgs({
|
|
8
|
-
options: {
|
|
9
|
-
bridge: { type: "string", default: DEFAULT_BRIDGE },
|
|
10
|
-
},
|
|
11
|
-
allowPositionals: false,
|
|
12
|
-
strict: true,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const readStdin = async () => {
|
|
16
|
-
process.stdin.setEncoding("utf8");
|
|
17
|
-
let input = "";
|
|
18
|
-
|
|
19
|
-
for await (const chunk of process.stdin) input += chunk;
|
|
20
|
-
|
|
21
|
-
return input;
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
try {
|
|
25
|
-
const input = await readStdin();
|
|
26
|
-
const event = JSON.parse(input);
|
|
27
|
-
const eventName = event?.hook_event_name;
|
|
28
|
-
|
|
29
|
-
if (!["UserPromptSubmit", "Stop", "Interrupt"].includes(eventName)) process.exit(0);
|
|
30
|
-
|
|
31
|
-
const sessionId = event?.session_id?.trim?.() || null;
|
|
32
|
-
const turnId = event?.turn_id?.trim?.() || null;
|
|
33
|
-
|
|
34
|
-
if (!sessionId || !turnId) process.exit(0);
|
|
35
|
-
|
|
36
|
-
const bridge = values.bridge?.trim() || DEFAULT_BRIDGE;
|
|
37
|
-
const base = bridge.endsWith("/") ? bridge : `${bridge}/`;
|
|
38
|
-
await fetch(new URL("lifecycle", base), {
|
|
39
|
-
method: "POST",
|
|
40
|
-
headers: { "Content-Type": "application/json" },
|
|
41
|
-
body: JSON.stringify({
|
|
42
|
-
event: eventName,
|
|
43
|
-
sessionId,
|
|
44
|
-
turnId,
|
|
45
|
-
prompt: eventName === "UserPromptSubmit" ? event?.prompt ?? "" : undefined,
|
|
46
|
-
stopHookActive: eventName === "Stop" ? event?.stop_hook_active === true : undefined,
|
|
47
|
-
}),
|
|
48
|
-
}).catch(() => undefined);
|
|
49
|
-
} catch {
|
|
50
|
-
// Lifecycle telemetry is best-effort and must never block or inject output into Codex.
|
|
51
|
-
}
|
package/scripts/codex-bridge.mjs
DELETED