impel-cli 0.20.42 → 0.20.44
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 +10 -0
- package/package.json +1 -1
- package/scripts/profile-native-codex.mjs +604 -57
- package/src/agents.js +217 -21
- package/src/cli.js +1 -0
- package/src/commands/agents.js +1 -0
- package/src/commands/launch.js +31 -7
- package/src/managedProfileVersion.js +1 -1
- package/src/nativeAgentTransport.js +9 -2
- package/src/selfInvocation.js +20 -1
|
@@ -7,14 +7,22 @@ import os from "node:os";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
|
|
10
|
+
import {
|
|
11
|
+
MANAGED_AGENT_MCP_SERVER,
|
|
12
|
+
NATIVE_AGENT_ANSWER_TOOL,
|
|
13
|
+
nativeToolNamespace,
|
|
14
|
+
resolveManagedCodexAgentProfile,
|
|
15
|
+
} from "../src/agents.js";
|
|
10
16
|
import { nativeSpawnInvocation } from "../src/nativeProcess.js";
|
|
11
|
-
import { summarizeRawAttempt } from "./analyze-native-codex.mjs";
|
|
17
|
+
import { parseStrictJsonLines, summarizeRawAttempt } from "./analyze-native-codex.mjs";
|
|
12
18
|
|
|
13
19
|
const PROFILE_SCHEMA = "impel.native-codex-profile.v1";
|
|
14
20
|
const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000;
|
|
15
21
|
const DEFAULT_GRACE_MS = 5_000;
|
|
16
22
|
const MAX_CAPTURE_BYTES = 256 * 1024 * 1024;
|
|
23
|
+
const MAX_CONFIG_BYTES = 1024 * 1024;
|
|
17
24
|
const TENANT_PREFLIGHT_RETRY_DELAYS_MS = [250, 750, 1_500];
|
|
25
|
+
const ISOLATED_PROFILE_PREFIX = "impel-native-codex-benchmark-";
|
|
18
26
|
|
|
19
27
|
function privateDirectory(directory) {
|
|
20
28
|
if (fs.existsSync(directory) && fs.lstatSync(directory).isSymbolicLink()) {
|
|
@@ -29,6 +37,16 @@ function privateWrite(filePath, contents) {
|
|
|
29
37
|
try { fs.chmodSync(filePath, 0o600); } catch { /* Best effort on Windows. */ }
|
|
30
38
|
}
|
|
31
39
|
|
|
40
|
+
function privateCopy(source, destination) {
|
|
41
|
+
const stat = fs.lstatSync(source);
|
|
42
|
+
if (!stat.isFile() || stat.size > MAX_CONFIG_BYTES) {
|
|
43
|
+
throw new Error("the selected Impel config must be a bounded regular file");
|
|
44
|
+
}
|
|
45
|
+
privateDirectory(path.dirname(destination));
|
|
46
|
+
fs.copyFileSync(source, destination, fs.constants.COPYFILE_EXCL);
|
|
47
|
+
try { fs.chmodSync(destination, 0o600); } catch { /* Best effort on Windows. */ }
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
function openPrivate(filePath) {
|
|
33
51
|
const descriptor = fs.openSync(filePath, "wx", 0o600);
|
|
34
52
|
try { fs.chmodSync(filePath, 0o600); } catch { /* Best effort on Windows. */ }
|
|
@@ -233,6 +251,350 @@ export async function assertExpectedTenant({ expectedTenant, impelBinary = "impe
|
|
|
233
251
|
return actual;
|
|
234
252
|
}
|
|
235
253
|
|
|
254
|
+
function selectedConfigPath(environment = process.env) {
|
|
255
|
+
const home = environment.HOME || environment.USERPROFILE || os.homedir();
|
|
256
|
+
const root = environment.IMPEL_CONFIG_DIR || path.join(home, ".config", "impel");
|
|
257
|
+
return path.join(root, "config.json");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isolatedBenchmarkPaths(root, tenantId) {
|
|
261
|
+
const home = path.join(root, "home");
|
|
262
|
+
const configRoot = path.join(root, "config");
|
|
263
|
+
return {
|
|
264
|
+
root,
|
|
265
|
+
home,
|
|
266
|
+
configRoot,
|
|
267
|
+
codexHome: path.join(configRoot, "cli", "tenants", tenantId, "codex"),
|
|
268
|
+
nativeCodexHome: path.join(root, "native-codex"),
|
|
269
|
+
appHome: path.join(root, "apps"),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function isolatedBenchmarkEnvironment(options, paths, telemetryPath, baseEnvironment) {
|
|
274
|
+
return {
|
|
275
|
+
...baseEnvironment,
|
|
276
|
+
HOME: paths.home,
|
|
277
|
+
USERPROFILE: paths.home,
|
|
278
|
+
XDG_CONFIG_HOME: path.join(paths.home, ".config"),
|
|
279
|
+
IMPEL_CONFIG_DIR: paths.configRoot,
|
|
280
|
+
IMPEL_APP_HOME: paths.appHome,
|
|
281
|
+
// `impel codex` runs against the tenant's generated profile directory. An
|
|
282
|
+
// explicit CODEX_HOME overrides that derivation, so it must point at the
|
|
283
|
+
// isolated tenant profile; pointing it anywhere else starts Codex with no
|
|
284
|
+
// MCP servers, and the cohort silently measures model prose instead of
|
|
285
|
+
// agent latency.
|
|
286
|
+
CODEX_HOME: paths.codexHome,
|
|
287
|
+
IMPEL_SKIP_UPDATE_CHECK: "1",
|
|
288
|
+
IMPEL_NATIVE_AGENT_TELEMETRY_PATH: telemetryPath,
|
|
289
|
+
IMPEL_NATIVE_HOST: options.mode === "direct"
|
|
290
|
+
? "codex-direct-profile"
|
|
291
|
+
: "codex-compatible-agent",
|
|
292
|
+
IMPEL_NATIVE_HOST_BUILD: options.hostBuild,
|
|
293
|
+
IMPEL_NATIVE_BENCHMARK: "1",
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function profileEnvironmentEntry(contents, name, value) {
|
|
298
|
+
return contents.includes(`${JSON.stringify(name)} = ${JSON.stringify(value)}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function regularFileContents(filePath, missingMessage) {
|
|
302
|
+
try {
|
|
303
|
+
const stat = fs.lstatSync(filePath);
|
|
304
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error();
|
|
305
|
+
return fs.readFileSync(filePath, "utf8");
|
|
306
|
+
} catch {
|
|
307
|
+
throw new Error(missingMessage);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function tomlSection(contents, sectionName) {
|
|
312
|
+
const header = `[${sectionName}]`;
|
|
313
|
+
const lines = contents.split(/\r?\n/u);
|
|
314
|
+
const matches = lines.flatMap((line, index) => line.trim() === header ? [index] : []);
|
|
315
|
+
if (matches.length !== 1) return null;
|
|
316
|
+
const start = matches[0] + 1;
|
|
317
|
+
const relativeEnd = lines.slice(start).findIndex((line) => /^\s*\[/u.test(line));
|
|
318
|
+
return lines.slice(start, relativeEnd === -1 ? lines.length : start + relativeEnd).join("\n");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function tomlAssignment(section, name) {
|
|
322
|
+
if (section === null) return null;
|
|
323
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
324
|
+
const matches = [...section.matchAll(new RegExp(`^\\s*${escaped}\\s*=\\s*(.+?)\\s*$`, "gmu"))];
|
|
325
|
+
return matches.length === 1 ? matches[0][1] : null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function tomlStringList(value) {
|
|
329
|
+
if (typeof value !== "string" || !/^\s*\[.*\]\s*$/su.test(value)) return null;
|
|
330
|
+
try {
|
|
331
|
+
const parsed = JSON.parse(value);
|
|
332
|
+
return Array.isArray(parsed) && parsed.every((entry) => typeof entry === "string")
|
|
333
|
+
? parsed
|
|
334
|
+
: null;
|
|
335
|
+
} catch {
|
|
336
|
+
return null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function assertSelectedProfileContract(contents, environment, policyFingerprint) {
|
|
341
|
+
const mcpSectionName = `mcp_servers.${MANAGED_AGENT_MCP_SERVER}`;
|
|
342
|
+
const mcpSection = tomlSection(contents, mcpSectionName);
|
|
343
|
+
if (mcpSection === null) {
|
|
344
|
+
throw new Error(`the isolated managed Codex agent profile has no [${mcpSectionName}] block`);
|
|
345
|
+
}
|
|
346
|
+
if (!tomlAssignment(mcpSection, "command") || !tomlAssignment(mcpSection, "args")) {
|
|
347
|
+
throw new Error("the isolated managed Codex agent profile has an incomplete MCP command binding");
|
|
348
|
+
}
|
|
349
|
+
if (tomlAssignment(mcpSection, "tool_timeout_sec") !== "120") {
|
|
350
|
+
throw new Error("the isolated managed Codex agent profile has no reviewed MCP tool timeout");
|
|
351
|
+
}
|
|
352
|
+
const enabledTools = tomlStringList(tomlAssignment(mcpSection, "enabled_tools"));
|
|
353
|
+
if (!enabledTools?.includes(NATIVE_AGENT_ANSWER_TOOL)) {
|
|
354
|
+
throw new Error(
|
|
355
|
+
`the isolated managed Codex agent profile does not enable ${NATIVE_AGENT_ANSWER_TOOL}`,
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
const mcpEnvironment = tomlAssignment(mcpSection, "env");
|
|
359
|
+
for (const name of [
|
|
360
|
+
"IMPEL_NATIVE_AGENT_TELEMETRY_PATH",
|
|
361
|
+
"IMPEL_NATIVE_HOST",
|
|
362
|
+
"IMPEL_NATIVE_HOST_BUILD",
|
|
363
|
+
"IMPEL_NATIVE_BENCHMARK",
|
|
364
|
+
]) {
|
|
365
|
+
if (!profileEnvironmentEntry(mcpEnvironment || "", name, environment[name])) {
|
|
366
|
+
throw new Error(`the isolated managed Codex agent MCP env did not bake ${name}`);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const mcpArgs = tomlAssignment(mcpSection, "args") || "";
|
|
370
|
+
if (!mcpArgs.includes(JSON.stringify("--policy-fingerprint"))
|
|
371
|
+
|| !mcpArgs.includes(JSON.stringify(policyFingerprint))) {
|
|
372
|
+
throw new Error("the isolated managed Codex agent profile lost its policy fingerprint binding");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const codeModeSection = tomlSection(contents, "features.code_mode");
|
|
376
|
+
if (tomlAssignment(codeModeSection, "enabled") !== "true") {
|
|
377
|
+
throw new Error("the isolated managed Codex agent profile does not enable reviewed code mode");
|
|
378
|
+
}
|
|
379
|
+
const namespaces = tomlStringList(
|
|
380
|
+
tomlAssignment(codeModeSection, "direct_only_tool_namespaces"),
|
|
381
|
+
);
|
|
382
|
+
if (!namespaces?.includes(nativeToolNamespace())) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`the isolated managed Codex agent profile does not bind ${nativeToolNamespace()} as a direct-only namespace`,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
export function verifyIsolatedBenchmarkProfile({
|
|
390
|
+
codexHome,
|
|
391
|
+
expectedTenant,
|
|
392
|
+
agent,
|
|
393
|
+
environment,
|
|
394
|
+
}) {
|
|
395
|
+
const manifestPath = path.join(codexHome, "agents", "impel-managed", ".manifest.json");
|
|
396
|
+
let manifest;
|
|
397
|
+
try {
|
|
398
|
+
const stat = fs.lstatSync(manifestPath);
|
|
399
|
+
if (!stat.isFile() || stat.isSymbolicLink()) throw new Error();
|
|
400
|
+
manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
401
|
+
} catch {
|
|
402
|
+
throw new Error("the isolated managed Codex agent manifest was not generated");
|
|
403
|
+
}
|
|
404
|
+
if (manifest?.client !== "codex"
|
|
405
|
+
|| manifest.tenantId !== expectedTenant
|
|
406
|
+
|| manifest.directProfiles !== true
|
|
407
|
+
|| manifest.directCodeMode !== true
|
|
408
|
+
|| manifest.nativeBenchmark !== true
|
|
409
|
+
|| !Array.isArray(manifest.files)
|
|
410
|
+
|| !Array.isArray(manifest.profiles)
|
|
411
|
+
|| !manifest.contentDigests
|
|
412
|
+
|| typeof manifest.contentDigests !== "object"
|
|
413
|
+
|| Array.isArray(manifest.contentDigests)
|
|
414
|
+
|| !manifest.nativeAgentTelemetryEnvironment
|
|
415
|
+
|| typeof manifest.nativeAgentTelemetryEnvironment !== "object"
|
|
416
|
+
|| Array.isArray(manifest.nativeAgentTelemetryEnvironment)) {
|
|
417
|
+
throw new Error("the isolated managed Codex agent manifest is missing its benchmark telemetry contract");
|
|
418
|
+
}
|
|
419
|
+
for (const name of [
|
|
420
|
+
"IMPEL_NATIVE_AGENT_TELEMETRY_PATH",
|
|
421
|
+
"IMPEL_NATIVE_HOST",
|
|
422
|
+
"IMPEL_NATIVE_HOST_BUILD",
|
|
423
|
+
]) {
|
|
424
|
+
if (manifest.nativeAgentTelemetryEnvironment[name] !== environment[name]) {
|
|
425
|
+
throw new Error(`the isolated managed Codex agent manifest did not retain ${name}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const matches = (Array.isArray(manifest.agents) ? manifest.agents : []).filter((record) =>
|
|
429
|
+
record
|
|
430
|
+
&& !record.retired
|
|
431
|
+
&& [record.agentId, record.title, record.profileName].includes(agent)
|
|
432
|
+
);
|
|
433
|
+
if (matches.length !== 1) {
|
|
434
|
+
throw new Error(`the isolated managed Codex agent ${JSON.stringify(agent)} was not generated exactly once`);
|
|
435
|
+
}
|
|
436
|
+
const [record] = matches;
|
|
437
|
+
if (!/^[a-f0-9]{64}$/u.test(record.policyFingerprint || "")) {
|
|
438
|
+
throw new Error("the isolated managed Codex agent manifest has no policy fingerprint");
|
|
439
|
+
}
|
|
440
|
+
let resolved;
|
|
441
|
+
try {
|
|
442
|
+
resolved = resolveManagedCodexAgentProfile(codexHome, expectedTenant, agent);
|
|
443
|
+
} catch (error) {
|
|
444
|
+
throw new Error(`the isolated managed Codex agent profile failed its managed integrity check: ${error.message}`);
|
|
445
|
+
}
|
|
446
|
+
const profileFileName = resolved.profileFileName;
|
|
447
|
+
if (typeof profileFileName !== "string"
|
|
448
|
+
|| path.basename(profileFileName) !== profileFileName
|
|
449
|
+
|| !profileFileName.endsWith(".config.toml")) {
|
|
450
|
+
throw new Error("the isolated managed Codex agent profile mapping is invalid");
|
|
451
|
+
}
|
|
452
|
+
const profilePath = path.join(codexHome, profileFileName);
|
|
453
|
+
const contents = regularFileContents(
|
|
454
|
+
profilePath,
|
|
455
|
+
"the isolated managed Codex agent profile was not written as a regular file",
|
|
456
|
+
);
|
|
457
|
+
const definitionFileName = `${resolved.profileName}.toml`;
|
|
458
|
+
if (!manifest.files.includes(definitionFileName)) {
|
|
459
|
+
throw new Error("the isolated managed Codex agent definition is absent from its manifest");
|
|
460
|
+
}
|
|
461
|
+
const definitionPath = path.join(codexHome, "agents", definitionFileName);
|
|
462
|
+
const definitionContents = regularFileContents(
|
|
463
|
+
definitionPath,
|
|
464
|
+
"the isolated managed Codex agent definition was not written as a regular file",
|
|
465
|
+
);
|
|
466
|
+
if (manifest.contentDigests?.[`agents/${definitionFileName}`]
|
|
467
|
+
!== crypto.createHash("sha256").update(fs.readFileSync(definitionPath)).digest("hex")) {
|
|
468
|
+
throw new Error("the isolated managed Codex agent definition failed its managed integrity check");
|
|
469
|
+
}
|
|
470
|
+
assertSelectedProfileContract(contents, environment, record.policyFingerprint);
|
|
471
|
+
try {
|
|
472
|
+
assertSelectedProfileContract(definitionContents, environment, record.policyFingerprint);
|
|
473
|
+
} catch (error) {
|
|
474
|
+
throw new Error(error.message.replace("agent profile", "agent definition"));
|
|
475
|
+
}
|
|
476
|
+
return { manifestPath, profilePath, profileFileName, definitionPath };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function generateIsolatedBenchmarkProfile(options, isolation) {
|
|
480
|
+
// `managedSkillProfiles` only emits the "Impel isolated Codex" target when a
|
|
481
|
+
// tenant is selected AND its profile directory already exists, so a fresh
|
|
482
|
+
// isolated root would otherwise generate the native profile only — leaving
|
|
483
|
+
// Codex with no impel_agent MCP server and the cohort measuring model prose.
|
|
484
|
+
const select = impelInvocation(options.impelBinary, [
|
|
485
|
+
"tenant",
|
|
486
|
+
"use",
|
|
487
|
+
options.expectedTenant,
|
|
488
|
+
], isolation.environment);
|
|
489
|
+
const selected = await runCaptured(select.command, select.args, {
|
|
490
|
+
environment: isolation.environment,
|
|
491
|
+
});
|
|
492
|
+
if (selected.code !== 0) {
|
|
493
|
+
throw new Error(
|
|
494
|
+
`\`impel tenant use ${options.expectedTenant}\` exited with status ${selected.code ?? "unknown"}`,
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
fs.mkdirSync(isolation.codexHome, { recursive: true, mode: 0o700 });
|
|
498
|
+
|
|
499
|
+
const invocation = impelInvocation(options.impelBinary, [
|
|
500
|
+
"agents",
|
|
501
|
+
"sync",
|
|
502
|
+
"codex",
|
|
503
|
+
"--skip-apps",
|
|
504
|
+
], isolation.environment);
|
|
505
|
+
const result = await runCaptured(invocation.command, invocation.args, {
|
|
506
|
+
environment: isolation.environment,
|
|
507
|
+
});
|
|
508
|
+
if (result.code !== 0) {
|
|
509
|
+
throw new Error(`\`impel agents sync codex\` exited with status ${result.code ?? "unknown"}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Run a cohort with the CLI's existing IMPEL_CONFIG_DIR override pointed at a
|
|
515
|
+
* private temporary root. Only config.json is copied in for authentication;
|
|
516
|
+
* no managed profile from the operator's config root is read or written.
|
|
517
|
+
*/
|
|
518
|
+
export async function withIsolatedBenchmarkProfile(options, sessionDir, callback, {
|
|
519
|
+
baseEnvironment = process.env,
|
|
520
|
+
operatorConfigPath = selectedConfigPath(baseEnvironment),
|
|
521
|
+
prepareProfile = generateIsolatedBenchmarkProfile,
|
|
522
|
+
assertTenant = assertExpectedTenant,
|
|
523
|
+
stderr = process.stderr,
|
|
524
|
+
temporaryParent = os.tmpdir(),
|
|
525
|
+
} = {}) {
|
|
526
|
+
const root = fs.mkdtempSync(path.join(temporaryParent, ISOLATED_PROFILE_PREFIX));
|
|
527
|
+
const paths = isolatedBenchmarkPaths(root, options.expectedTenant);
|
|
528
|
+
const telemetryPath = path.join(sessionDir, "telemetry.jsonl");
|
|
529
|
+
const environment = isolatedBenchmarkEnvironment(
|
|
530
|
+
options,
|
|
531
|
+
paths,
|
|
532
|
+
telemetryPath,
|
|
533
|
+
baseEnvironment,
|
|
534
|
+
);
|
|
535
|
+
let removed = false;
|
|
536
|
+
const removeRoot = () => {
|
|
537
|
+
if (removed) return;
|
|
538
|
+
removed = true;
|
|
539
|
+
fs.rmSync(root, { recursive: true, force: true });
|
|
540
|
+
};
|
|
541
|
+
// Synchronous exit cleanup also covers Ctrl-C/termination paths where the
|
|
542
|
+
// event loop cannot continue far enough to unwind the async callback.
|
|
543
|
+
process.once("exit", removeRoot);
|
|
544
|
+
let prepared = false;
|
|
545
|
+
try {
|
|
546
|
+
privateDirectory(paths.home);
|
|
547
|
+
privateDirectory(paths.configRoot);
|
|
548
|
+
stderr.write(
|
|
549
|
+
`Using isolated benchmark profile ${paths.codexHome}; cohort telemetry: ${telemetryPath}\n`,
|
|
550
|
+
);
|
|
551
|
+
try {
|
|
552
|
+
privateCopy(operatorConfigPath, path.join(paths.configRoot, "config.json"));
|
|
553
|
+
// Explicit sync discovers initialized isolated CLI roots. Creating only
|
|
554
|
+
// this empty destination lets the production sync command generate the
|
|
555
|
+
// complete agent catalog without launching Codex or touching a live
|
|
556
|
+
// profile.
|
|
557
|
+
privateDirectory(paths.codexHome);
|
|
558
|
+
await assertTenant({
|
|
559
|
+
expectedTenant: options.expectedTenant,
|
|
560
|
+
impelBinary: options.impelBinary,
|
|
561
|
+
environment,
|
|
562
|
+
});
|
|
563
|
+
await prepareProfile(options, {
|
|
564
|
+
...paths,
|
|
565
|
+
telemetryPath,
|
|
566
|
+
environment,
|
|
567
|
+
});
|
|
568
|
+
const verified = verifyIsolatedBenchmarkProfile({
|
|
569
|
+
codexHome: paths.codexHome,
|
|
570
|
+
expectedTenant: options.expectedTenant,
|
|
571
|
+
agent: options.agent,
|
|
572
|
+
environment,
|
|
573
|
+
});
|
|
574
|
+
// Profile preparation must not contribute any events to the measured
|
|
575
|
+
// cohort. The generated profile keeps pointing at this same path.
|
|
576
|
+
fs.rmSync(telemetryPath, { force: true });
|
|
577
|
+
prepared = true;
|
|
578
|
+
return await callback({
|
|
579
|
+
...paths,
|
|
580
|
+
...verified,
|
|
581
|
+
telemetryPath,
|
|
582
|
+
environment,
|
|
583
|
+
});
|
|
584
|
+
} catch (error) {
|
|
585
|
+
if (prepared) throw error;
|
|
586
|
+
throw new Error(
|
|
587
|
+
`could not generate the isolated benchmark profile for tenant ${JSON.stringify(options.expectedTenant)}: `
|
|
588
|
+
+ `${error?.message || error}. Verify Impel auth, network access, the selected tenant, and `
|
|
589
|
+
+ "that `impel agents sync codex` can fetch this tenant's agent catalog.",
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
} finally {
|
|
593
|
+
process.off("exit", removeRoot);
|
|
594
|
+
removeRoot();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
236
598
|
function listRollouts(root, startedAtMs, endedAtMs) {
|
|
237
599
|
if (!fs.existsSync(root)) return [];
|
|
238
600
|
const results = [];
|
|
@@ -439,35 +801,141 @@ export function safeAttemptRecord({ options, index, processResult, summary, json
|
|
|
439
801
|
};
|
|
440
802
|
}
|
|
441
803
|
|
|
804
|
+
export function profiledCodexAttemptOptions(options, telemetryPath, environment = process.env) {
|
|
805
|
+
return {
|
|
806
|
+
codexArgs: [
|
|
807
|
+
"codex",
|
|
808
|
+
"--benchmark",
|
|
809
|
+
...(options.mode === "direct" ? ["--agent", options.agent] : []),
|
|
810
|
+
"exec",
|
|
811
|
+
"--json",
|
|
812
|
+
...options.extraArgs,
|
|
813
|
+
"-",
|
|
814
|
+
],
|
|
815
|
+
environment: {
|
|
816
|
+
...environment,
|
|
817
|
+
IMPEL_NATIVE_AGENT_TELEMETRY_PATH: telemetryPath,
|
|
818
|
+
IMPEL_NATIVE_HOST: options.mode === "direct" ? "codex-direct-profile" : "codex-compatible-agent",
|
|
819
|
+
IMPEL_NATIVE_HOST_BUILD: options.hostBuild,
|
|
820
|
+
IMPEL_NATIVE_BENCHMARK: "1",
|
|
821
|
+
},
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function attemptTelemetryWindow(attempt, index) {
|
|
826
|
+
const startedAtMs = attempt?.processResult?.startedAtMs ?? Date.parse(attempt?.startedAt || "");
|
|
827
|
+
const endedAtMs = attempt?.processResult?.endedAtMs ?? Date.parse(attempt?.endedAt || "");
|
|
828
|
+
if (!Number.isFinite(startedAtMs) || !Number.isFinite(endedAtMs) || endedAtMs < startedAtMs) {
|
|
829
|
+
throw new Error(`attempt ${index + 1} has an invalid telemetry window`);
|
|
830
|
+
}
|
|
831
|
+
return { index, startedAtMs, endedAtMs };
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function eventTimestampMs(event) {
|
|
835
|
+
const value = Date.parse(event?.timestamp || "");
|
|
836
|
+
return Number.isFinite(value) ? value : null;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
function windowOwner(windows, timestampMs) {
|
|
840
|
+
if (timestampMs === null) return null;
|
|
841
|
+
const candidates = windows.filter(({ startedAtMs, endedAtMs }) =>
|
|
842
|
+
timestampMs >= startedAtMs && timestampMs <= endedAtMs
|
|
843
|
+
);
|
|
844
|
+
if (candidates.length === 0) return null;
|
|
845
|
+
// Concurrent attempts can overlap. Prefer the attempt whose process start is
|
|
846
|
+
// closest to the event, then use input order as a stable tie breaker.
|
|
847
|
+
return candidates.sort((left, right) =>
|
|
848
|
+
(timestampMs - left.startedAtMs) - (timestampMs - right.startedAtMs)
|
|
849
|
+
|| left.index - right.index
|
|
850
|
+
)[0].index;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Attribute a shared cohort telemetry stream without duplicating events.
|
|
855
|
+
* A correlation's first timestamp anchors the whole correlation to one
|
|
856
|
+
* attempt; records without a correlation ID fall back to attempt windows.
|
|
857
|
+
*/
|
|
858
|
+
export function splitCohortTelemetryEvents(attempts, telemetryText) {
|
|
859
|
+
const windows = attempts.map(attemptTelemetryWindow);
|
|
860
|
+
const split = attempts.map(() => []);
|
|
861
|
+
const events = telemetryText.trim()
|
|
862
|
+
? parseStrictJsonLines(telemetryText, "native-agent cohort telemetry")
|
|
863
|
+
: [];
|
|
864
|
+
const correlationOwner = new Map();
|
|
865
|
+
for (const event of events) {
|
|
866
|
+
if (typeof event.correlationId !== "string" || !event.correlationId) continue;
|
|
867
|
+
if (!correlationOwner.has(event.correlationId)) {
|
|
868
|
+
correlationOwner.set(
|
|
869
|
+
event.correlationId,
|
|
870
|
+
windowOwner(windows, eventTimestampMs(event)),
|
|
871
|
+
);
|
|
872
|
+
} else if (correlationOwner.get(event.correlationId) === null) {
|
|
873
|
+
const owner = windowOwner(windows, eventTimestampMs(event));
|
|
874
|
+
if (owner !== null) correlationOwner.set(event.correlationId, owner);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
for (const event of events) {
|
|
878
|
+
const owner = typeof event.correlationId === "string" && event.correlationId
|
|
879
|
+
? correlationOwner.get(event.correlationId) ?? null
|
|
880
|
+
: windowOwner(windows, eventTimestampMs(event));
|
|
881
|
+
if (owner !== null) split[owner].push(event);
|
|
882
|
+
}
|
|
883
|
+
return split;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
export function finalizeProfileAttempts(attempts, telemetryText) {
|
|
887
|
+
const telemetryByAttempt = splitCohortTelemetryEvents(attempts, telemetryText);
|
|
888
|
+
return attempts.map((attempt, attemptIndex) => {
|
|
889
|
+
if (attempt?.profileAttempt !== true) return attempt;
|
|
890
|
+
const attributedTelemetryText = telemetryByAttempt[attemptIndex]
|
|
891
|
+
.map((event) => JSON.stringify(event))
|
|
892
|
+
.join("\n");
|
|
893
|
+
let summary = null;
|
|
894
|
+
let parseError = null;
|
|
895
|
+
try {
|
|
896
|
+
summary = summarizeRawAttempt({
|
|
897
|
+
stdoutText: attempt.stdoutText,
|
|
898
|
+
telemetryText: attributedTelemetryText,
|
|
899
|
+
rolloutTexts: attempt.rolloutTexts,
|
|
900
|
+
});
|
|
901
|
+
} catch (error) {
|
|
902
|
+
parseError = error;
|
|
903
|
+
}
|
|
904
|
+
const record = safeAttemptRecord({
|
|
905
|
+
options: attempt.options,
|
|
906
|
+
index: attempt.index,
|
|
907
|
+
processResult: attempt.processResult,
|
|
908
|
+
summary,
|
|
909
|
+
jsonValid: parseError === null,
|
|
910
|
+
parseError,
|
|
911
|
+
});
|
|
912
|
+
privateWrite(path.join(attempt.attemptDir, "record.json"), `${JSON.stringify(record, null, 2)}\n`);
|
|
913
|
+
return record;
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
|
|
442
917
|
async function profileAttempt(options, sessionDir, index) {
|
|
918
|
+
const environment = options.environment || process.env;
|
|
443
919
|
await assertExpectedTenant({
|
|
444
920
|
expectedTenant: options.expectedTenant,
|
|
445
921
|
impelBinary: options.impelBinary,
|
|
446
|
-
environment
|
|
922
|
+
environment,
|
|
447
923
|
});
|
|
448
924
|
const attemptName = `${options.cohort}-${String(index + 1 + options.attemptOffset).padStart(3, "0")}`;
|
|
449
925
|
const attemptDir = path.join(sessionDir, attemptName);
|
|
450
926
|
privateDirectory(attemptDir);
|
|
451
927
|
const stdoutPath = path.join(attemptDir, "stdout.jsonl");
|
|
452
928
|
const stderrPath = path.join(attemptDir, "stderr.log");
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
"-",
|
|
461
|
-
];
|
|
462
|
-
const invocation = impelInvocation(options.impelBinary, codexArgs, process.env);
|
|
929
|
+
const attemptOptions = profiledCodexAttemptOptions(
|
|
930
|
+
options,
|
|
931
|
+
options.telemetryPath,
|
|
932
|
+
environment,
|
|
933
|
+
);
|
|
934
|
+
const { codexArgs } = attemptOptions;
|
|
935
|
+
const invocation = impelInvocation(options.impelBinary, codexArgs, attemptOptions.environment);
|
|
463
936
|
const processResult = await runProfiledProcess({
|
|
464
937
|
...invocation,
|
|
465
|
-
environment:
|
|
466
|
-
...process.env,
|
|
467
|
-
IMPEL_NATIVE_AGENT_TELEMETRY_PATH: telemetryPath,
|
|
468
|
-
IMPEL_NATIVE_HOST: options.mode === "direct" ? "codex-direct-profile" : "codex-compatible-agent",
|
|
469
|
-
IMPEL_NATIVE_HOST_BUILD: options.hostBuild,
|
|
470
|
-
},
|
|
938
|
+
environment: attemptOptions.environment,
|
|
471
939
|
input: options.prompt,
|
|
472
940
|
stdoutPath,
|
|
473
941
|
stderrPath,
|
|
@@ -481,27 +949,15 @@ async function profileAttempt(options, sessionDir, index) {
|
|
|
481
949
|
stdoutText,
|
|
482
950
|
destination: path.join(attemptDir, "rollouts"),
|
|
483
951
|
});
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
try {
|
|
487
|
-
summary = summarizeRawAttempt({
|
|
488
|
-
stdoutText,
|
|
489
|
-
telemetryText: fs.existsSync(telemetryPath) ? fs.readFileSync(telemetryPath, "utf8") : "",
|
|
490
|
-
rolloutTexts: rolloutPaths.map((rolloutPath) => fs.readFileSync(rolloutPath, "utf8")),
|
|
491
|
-
});
|
|
492
|
-
} catch (error) {
|
|
493
|
-
parseError = error;
|
|
494
|
-
}
|
|
495
|
-
const record = safeAttemptRecord({
|
|
952
|
+
return {
|
|
953
|
+
profileAttempt: true,
|
|
496
954
|
options,
|
|
497
955
|
index,
|
|
956
|
+
attemptDir,
|
|
498
957
|
processResult,
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
});
|
|
503
|
-
privateWrite(path.join(attemptDir, "record.json"), `${JSON.stringify(record, null, 2)}\n`);
|
|
504
|
-
return record;
|
|
958
|
+
stdoutText,
|
|
959
|
+
rolloutTexts: rolloutPaths.map((rolloutPath) => fs.readFileSync(rolloutPath, "utf8")),
|
|
960
|
+
};
|
|
505
961
|
}
|
|
506
962
|
|
|
507
963
|
function profilerFailureClass(error) {
|
|
@@ -514,16 +970,20 @@ function profilerFailureClass(error) {
|
|
|
514
970
|
return "attempt-infrastructure";
|
|
515
971
|
}
|
|
516
972
|
|
|
517
|
-
export async function runWorkers(options, sessionDir, profileAttemptImpl = profileAttempt
|
|
973
|
+
export async function runWorkers(options, sessionDir, profileAttemptImpl = profileAttempt, {
|
|
974
|
+
startIndex = 0,
|
|
975
|
+
endIndex = options.attempts,
|
|
976
|
+
concurrency = options.concurrency,
|
|
977
|
+
} = {}) {
|
|
518
978
|
const results = new Array(options.attempts);
|
|
519
|
-
let next =
|
|
979
|
+
let next = startIndex;
|
|
520
980
|
let abort = null;
|
|
521
981
|
const worker = async () => {
|
|
522
982
|
for (;;) {
|
|
523
983
|
if (abort) return;
|
|
524
984
|
const index = next;
|
|
525
985
|
next += 1;
|
|
526
|
-
if (index >=
|
|
986
|
+
if (index >= endIndex) return;
|
|
527
987
|
try {
|
|
528
988
|
results[index] = await profileAttemptImpl(options, sessionDir, index);
|
|
529
989
|
} catch (error) {
|
|
@@ -534,10 +994,98 @@ export async function runWorkers(options, sessionDir, profileAttemptImpl = profi
|
|
|
534
994
|
}
|
|
535
995
|
}
|
|
536
996
|
};
|
|
537
|
-
|
|
997
|
+
const workerCount = Math.min(concurrency, Math.max(0, endIndex - startIndex));
|
|
998
|
+
await Promise.all(Array.from({ length: workerCount }, () => worker()));
|
|
538
999
|
return { attempts: results.filter(Boolean), abort };
|
|
539
1000
|
}
|
|
540
1001
|
|
|
1002
|
+
const MISSING_MANAGED_AGENT_MESSAGE =
|
|
1003
|
+
"the isolated profile did not expose the managed agent tool; the cohort would measure model prose, not agent latency";
|
|
1004
|
+
|
|
1005
|
+
export function firstAttemptIntegrityAbort(record) {
|
|
1006
|
+
const answerCalls = record?.toolCounts?.[NATIVE_AGENT_ANSWER_TOOL] || 0;
|
|
1007
|
+
const terminalResults = record?.terminalResults || 0;
|
|
1008
|
+
if (answerCalls > 0 && terminalResults > 0) return null;
|
|
1009
|
+
return {
|
|
1010
|
+
failureClass: "managed-agent-integrity",
|
|
1011
|
+
attemptId: record?.attemptId || null,
|
|
1012
|
+
message: `${MISSING_MANAGED_AGENT_MESSAGE} (first attempt: ${answerCalls} ${NATIVE_AGENT_ANSWER_TOOL} call${answerCalls === 1 ? "" : "s"}, ${terminalResults} terminal result${terminalResults === 1 ? "" : "s"}); aborting remaining attempts`,
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
function readTelemetryText(telemetryPath) {
|
|
1017
|
+
return fs.existsSync(telemetryPath) ? fs.readFileSync(telemetryPath, "utf8") : "";
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* The first attempt is deliberately run alone. Until its rollout proves that
|
|
1022
|
+
* the fixed answer tool reached a terminal result, starting concurrent work
|
|
1023
|
+
* would risk collecting a whole cohort of plausible-looking model prose.
|
|
1024
|
+
*/
|
|
1025
|
+
export async function runGuardedCohort(options, sessionDir, {
|
|
1026
|
+
profileAttemptImpl = profileAttempt,
|
|
1027
|
+
readTelemetry = readTelemetryText,
|
|
1028
|
+
} = {}) {
|
|
1029
|
+
const first = await runWorkers(options, sessionDir, profileAttemptImpl, {
|
|
1030
|
+
startIndex: 0,
|
|
1031
|
+
endIndex: 1,
|
|
1032
|
+
concurrency: 1,
|
|
1033
|
+
});
|
|
1034
|
+
const firstRecords = finalizeProfileAttempts(
|
|
1035
|
+
first.attempts,
|
|
1036
|
+
readTelemetry(options.telemetryPath),
|
|
1037
|
+
);
|
|
1038
|
+
if (first.abort) return { attempts: firstRecords, abort: first.abort };
|
|
1039
|
+
const integrityAbort = firstAttemptIntegrityAbort(firstRecords[0]);
|
|
1040
|
+
if (integrityAbort) return { attempts: firstRecords, abort: integrityAbort };
|
|
1041
|
+
if (options.attempts === 1) return { attempts: firstRecords, abort: null };
|
|
1042
|
+
|
|
1043
|
+
const remaining = await runWorkers(options, sessionDir, profileAttemptImpl, {
|
|
1044
|
+
startIndex: 1,
|
|
1045
|
+
endIndex: options.attempts,
|
|
1046
|
+
concurrency: options.concurrency,
|
|
1047
|
+
});
|
|
1048
|
+
const remainingRecords = finalizeProfileAttempts(
|
|
1049
|
+
remaining.attempts,
|
|
1050
|
+
readTelemetry(options.telemetryPath),
|
|
1051
|
+
);
|
|
1052
|
+
return {
|
|
1053
|
+
attempts: [...firstRecords, ...remainingRecords],
|
|
1054
|
+
abort: remaining.abort,
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
export function buildProfileAggregate({ sessionId, generatedAt, options, result }) {
|
|
1059
|
+
const telemetryCaptured = result.attempts.some(
|
|
1060
|
+
(attempt) => (attempt.telemetryEventCount || 0) > 0,
|
|
1061
|
+
);
|
|
1062
|
+
return {
|
|
1063
|
+
schema: PROFILE_SCHEMA,
|
|
1064
|
+
sessionId,
|
|
1065
|
+
generatedAt,
|
|
1066
|
+
tenantId: options.expectedTenant,
|
|
1067
|
+
sloClass: options.sloClass,
|
|
1068
|
+
mode: options.mode,
|
|
1069
|
+
cohort: options.cohort,
|
|
1070
|
+
cliVersion: options.hostBuild,
|
|
1071
|
+
promptSha256: options.promptSha256,
|
|
1072
|
+
complete: result.abort === null && result.attempts.length === options.attempts,
|
|
1073
|
+
telemetryCaptured,
|
|
1074
|
+
expectedAttempts: options.attempts,
|
|
1075
|
+
abort: result.abort,
|
|
1076
|
+
attempts: result.attempts,
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
export function telemetryCaptureWarning(aggregate) {
|
|
1081
|
+
const terminalResults = aggregate.attempts.reduce(
|
|
1082
|
+
(total, attempt) => total + (attempt.terminalResults || 0),
|
|
1083
|
+
0,
|
|
1084
|
+
);
|
|
1085
|
+
if (!aggregate.complete || aggregate.telemetryCaptured || terminalResults === 0) return null;
|
|
1086
|
+
return "WARNING: the cohort completed with managed-agent terminal results but captured zero telemetry events; aggregate telemetryCaptured=false";
|
|
1087
|
+
}
|
|
1088
|
+
|
|
541
1089
|
async function restoreTenant(originalTenant, options) {
|
|
542
1090
|
let actual;
|
|
543
1091
|
try { actual = await currentTenant(options.impelBinary, process.env); } catch { return false; }
|
|
@@ -572,8 +1120,6 @@ async function main(argv) {
|
|
|
572
1120
|
throw new Error("could not establish the Impel CLI build under test");
|
|
573
1121
|
}
|
|
574
1122
|
options.hostBuild = version.stdout.trim();
|
|
575
|
-
const configRoot = process.env.IMPEL_CONFIG_DIR || path.join(os.homedir(), ".config", "impel");
|
|
576
|
-
options.rolloutRoot = path.join(configRoot, "cli", "tenants", options.expectedTenant, "codex", "sessions");
|
|
577
1123
|
const outputRoot = path.resolve(options.outputDir);
|
|
578
1124
|
privateDirectory(outputRoot);
|
|
579
1125
|
const sessionId = `codex-${new Date().toISOString().replace(/[:.]/gu, "-")}-${crypto.randomBytes(4).toString("hex")}`;
|
|
@@ -581,28 +1127,29 @@ async function main(argv) {
|
|
|
581
1127
|
privateDirectory(sessionDir);
|
|
582
1128
|
let restored = false;
|
|
583
1129
|
try {
|
|
584
|
-
const result = await
|
|
585
|
-
|
|
586
|
-
|
|
1130
|
+
const result = await withIsolatedBenchmarkProfile(options, sessionDir, async (isolation) => {
|
|
1131
|
+
options.environment = isolation.environment;
|
|
1132
|
+
options.telemetryPath = isolation.telemetryPath;
|
|
1133
|
+
options.rolloutRoot = path.join(isolation.codexHome, "sessions");
|
|
1134
|
+
return runGuardedCohort(options, sessionDir);
|
|
1135
|
+
});
|
|
1136
|
+
const aggregate = buildProfileAggregate({
|
|
587
1137
|
sessionId,
|
|
588
1138
|
generatedAt: new Date().toISOString(),
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
cohort: options.cohort,
|
|
593
|
-
cliVersion: options.hostBuild,
|
|
594
|
-
promptSha256: options.promptSha256,
|
|
595
|
-
complete: result.abort === null && result.attempts.length === options.attempts,
|
|
596
|
-
expectedAttempts: options.attempts,
|
|
597
|
-
abort: result.abort,
|
|
598
|
-
attempts: result.attempts,
|
|
599
|
-
};
|
|
1139
|
+
options,
|
|
1140
|
+
result,
|
|
1141
|
+
});
|
|
600
1142
|
const aggregatePath = path.join(sessionDir, "aggregate.json");
|
|
601
1143
|
privateWrite(aggregatePath, `${JSON.stringify(aggregate, null, 2)}\n`);
|
|
602
1144
|
process.stderr.write(`Private Codex profile written under ${sessionDir}\n`);
|
|
1145
|
+
const telemetryWarning = telemetryCaptureWarning(aggregate);
|
|
1146
|
+
if (telemetryWarning) process.stderr.write(`${telemetryWarning}\n`);
|
|
603
1147
|
process.stdout.write(`${JSON.stringify(aggregate)}\n`);
|
|
604
1148
|
if (!aggregate.complete) {
|
|
605
|
-
|
|
1149
|
+
const detail = aggregate.abort?.message ? `: ${aggregate.abort.message}` : "";
|
|
1150
|
+
throw new Error(
|
|
1151
|
+
`cohort aborted (${aggregate.abort?.failureClass || "incomplete"})${detail}; partial aggregate retained`,
|
|
1152
|
+
);
|
|
606
1153
|
}
|
|
607
1154
|
} finally {
|
|
608
1155
|
restored = await restoreTenant(originalTenant, options);
|