@emseepea/testing 0.23.2 → 0.23.4
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 +22 -4
- package/package.json +1 -1
- package/semantic/provider.mjs +56 -2
package/README.md
CHANGED
|
@@ -109,12 +109,30 @@ EMSEEPEA_CODEX_MODEL=gpt-5.5 emseepea-test --provider codex-local eval
|
|
|
109
109
|
|
|
110
110
|
`codex-local` uses the existing Codex CLI sign-in without copying it.
|
|
111
111
|
`codex-ci` requires `OPENAI_API_KEY` from the CI secret store. Both providers
|
|
112
|
-
run `codex exec --json`, resume the actual
|
|
113
|
-
user configuration, and pre-approve only
|
|
114
|
-
server.
|
|
112
|
+
support Codex CLI 0.162.0 or later, run `codex exec --json`, resume the actual
|
|
113
|
+
Codex session for follow-ups, ignore user configuration, and pre-approve only
|
|
114
|
+
the discovered tools from the test server.
|
|
115
115
|
|
|
116
116
|
An isolated command policy denies shell launches and spawned shell environments
|
|
117
|
-
inherit no provider credentials.
|
|
117
|
+
inherit no provider credentials. The runner also disables Codex account apps,
|
|
118
|
+
plugins, remote plugins, multi-agent execution, web search, shell tools, and
|
|
119
|
+
unified execution for each one-shot and resumed native invocation.
|
|
120
|
+
|
|
121
|
+
The invocation configuration sets the default account-app state to disabled,
|
|
122
|
+
omits account-app instructions, and disables Codex app, plugin, and tool
|
|
123
|
+
suggestion features.
|
|
124
|
+
|
|
125
|
+
Native-client events are authoritative for the tools that were actually used:
|
|
126
|
+
a turn is accepted only when every application MCP call belongs to the
|
|
127
|
+
configured test server and its expected tools.
|
|
128
|
+
|
|
129
|
+
Codex may make its two built-in resource-discovery calls through the CLI's
|
|
130
|
+
`codex` server. The runner accepts them only when their native results prove the
|
|
131
|
+
resource and template inventories are empty, and excludes them from application
|
|
132
|
+
assertions and raw evidence.
|
|
133
|
+
|
|
134
|
+
Any non-empty, unrecognised, account-app, plugin, or other foreign call is
|
|
135
|
+
rejected without storing raw account metadata.
|
|
118
136
|
|
|
119
137
|
Failed Codex runs retain only a fixed failure category: authentication,
|
|
120
138
|
configuration, host storage, permission or sandbox, provider, transport, or
|
package/package.json
CHANGED
package/semantic/provider.mjs
CHANGED
|
@@ -6,6 +6,11 @@ import { scriptedElicitations } from "./elicitation.mjs";
|
|
|
6
6
|
|
|
7
7
|
const claudeModel = "claude-sonnet-4-6";
|
|
8
8
|
const mcpServerName = "emseepea_eval";
|
|
9
|
+
const minimumCodexCliVersion = "0.162.0";
|
|
10
|
+
const codexEmptyDiscoveryTools = new Map([
|
|
11
|
+
["list_mcp_resources", "resources"],
|
|
12
|
+
["list_mcp_resource_templates", "resourceTemplates"],
|
|
13
|
+
]);
|
|
9
14
|
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
10
15
|
const providerErrorMessages = new Map([
|
|
11
16
|
["error_during_execution", "Model command failed during execution"],
|
|
@@ -39,6 +44,9 @@ export async function modelVersion(provider = "claude-local") {
|
|
|
39
44
|
const result = await runProcess(command, ["--version"], { env: modelEnvironment({}) });
|
|
40
45
|
const version = result.stdout.trim().match(/(?:codex-cli\s+)?(\d+\.\d+\.\d+)\b/)?.[1];
|
|
41
46
|
if (result.code !== 0 || !version) throw new Error(`Could not verify ${codex ? "Codex" : "Claude"} CLI version`);
|
|
47
|
+
if (codex && compareVersions(version, minimumCodexCliVersion) < 0) {
|
|
48
|
+
throw new Error(`Codex CLI ${version} is unsupported; use ${minimumCodexCliVersion} or later`);
|
|
49
|
+
}
|
|
42
50
|
return version;
|
|
43
51
|
}
|
|
44
52
|
|
|
@@ -53,11 +61,16 @@ export function providerSettings(provider) {
|
|
|
53
61
|
return provider.startsWith("codex-")
|
|
54
62
|
? {
|
|
55
63
|
approvalPolicy: "never",
|
|
64
|
+
appInstructions: false,
|
|
65
|
+
apps: false,
|
|
56
66
|
loginShell: false,
|
|
57
67
|
multiAgent: false,
|
|
68
|
+
plugins: false,
|
|
69
|
+
remotePlugin: false,
|
|
58
70
|
sandbox: "read-only",
|
|
59
71
|
shell: "forbidden",
|
|
60
72
|
shellEnvironment: "empty",
|
|
73
|
+
toolSuggest: false,
|
|
61
74
|
userConfig: "ignored",
|
|
62
75
|
webSearch: false,
|
|
63
76
|
}
|
|
@@ -453,7 +466,13 @@ export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, proc
|
|
|
453
466
|
const forbidden = completed.filter((item) => !["agent_message", "reasoning", "mcp_tool_call"].includes(item?.type));
|
|
454
467
|
if (forbidden.length) throw forbiddenItemError(forbidden);
|
|
455
468
|
const toolItems = completed.filter(({ type }) => type === "mcp_tool_call");
|
|
456
|
-
const
|
|
469
|
+
const discoveryItems = toolItems.filter(({ server, tool }) =>
|
|
470
|
+
server === "codex" && codexEmptyDiscoveryTools.has(tool));
|
|
471
|
+
if (discoveryItems.some((item) => !emptyCodexDiscovery(item))) {
|
|
472
|
+
throw forbiddenItemError(discoveryItems.filter((item) => !emptyCodexDiscovery(item)));
|
|
473
|
+
}
|
|
474
|
+
const applicationToolItems = toolItems.filter((item) => !discoveryItems.includes(item));
|
|
475
|
+
const calls = applicationToolItems.map((item) => {
|
|
457
476
|
if (item.server !== mcpServerName || !advertised.has(item.tool)
|
|
458
477
|
|| !item.arguments || typeof item.arguments !== "object" || Array.isArray(item.arguments)) {
|
|
459
478
|
throw forbiddenItemError([item]);
|
|
@@ -467,7 +486,7 @@ export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, proc
|
|
|
467
486
|
const messages = completed.filter(({ type }) => type === "agent_message");
|
|
468
487
|
const answer = messages.at(-1)?.text;
|
|
469
488
|
if (typeof answer !== "string") throw new Error("Model command returned no answer");
|
|
470
|
-
const toolResults =
|
|
489
|
+
const toolResults = applicationToolItems.map(({ result }) => ({ content: result.content ?? result, isError: false }));
|
|
471
490
|
return {
|
|
472
491
|
answer,
|
|
473
492
|
calls,
|
|
@@ -487,6 +506,25 @@ export function parseCodexEvents(stdout, advertisedTools, expectedThreadId, proc
|
|
|
487
506
|
};
|
|
488
507
|
}
|
|
489
508
|
|
|
509
|
+
function emptyCodexDiscovery(item) {
|
|
510
|
+
if (item.status !== "completed" || !item.arguments || typeof item.arguments !== "object"
|
|
511
|
+
|| Array.isArray(item.arguments) || Object.keys(item.arguments).length !== 0
|
|
512
|
+
|| !item.result || typeof item.result !== "object" || Array.isArray(item.result)
|
|
513
|
+
|| item.result.structured_content !== null || Object.keys(item.result).length !== 2
|
|
514
|
+
|| !Array.isArray(item.result.content) || item.result.content.length !== 1) return false;
|
|
515
|
+
const [content] = item.result.content;
|
|
516
|
+
if (!content || content.type !== "text" || typeof content.text !== "string"
|
|
517
|
+
|| Object.keys(content).length !== 2) return false;
|
|
518
|
+
try {
|
|
519
|
+
const parsed = JSON.parse(content.text);
|
|
520
|
+
const key = codexEmptyDiscoveryTools.get(item.tool);
|
|
521
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
522
|
+
&& Object.keys(parsed).length === 1 && Array.isArray(parsed[key]) && parsed[key].length === 0;
|
|
523
|
+
} catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
490
528
|
function startCodexConversation(provider, directory, url, tools, authToken, context, signal, serverSecrets) {
|
|
491
529
|
let threadId;
|
|
492
530
|
let closed = false;
|
|
@@ -545,8 +583,14 @@ function codexInvocation(provider, prompt, directory, conversation) {
|
|
|
545
583
|
"-c", 'web_search="disabled"',
|
|
546
584
|
"-c", 'shell_environment_policy.inherit="none"',
|
|
547
585
|
"-c", "allow_login_shell=false",
|
|
586
|
+
"-c", "apps._default.enabled=false",
|
|
587
|
+
"-c", "include_apps_instructions=false",
|
|
588
|
+
"-c", "features.apps=false",
|
|
548
589
|
"-c", "features.multi_agent=false",
|
|
590
|
+
"-c", "features.plugins=false",
|
|
591
|
+
"-c", "features.remote_plugin=false",
|
|
549
592
|
"-c", "features.shell_tool=false",
|
|
593
|
+
"-c", "features.tool_suggest=false",
|
|
550
594
|
"-c", "features.unified_exec=false",
|
|
551
595
|
];
|
|
552
596
|
if (conversation?.context) args.push("-c", `developer_instructions=${JSON.stringify(conversation.context)}`);
|
|
@@ -633,6 +677,16 @@ function classifyCodexFailure(stderr) {
|
|
|
633
677
|
return "unknown";
|
|
634
678
|
}
|
|
635
679
|
|
|
680
|
+
function compareVersions(left, right) {
|
|
681
|
+
const a = left.split(".").map(Number);
|
|
682
|
+
const b = right.split(".").map(Number);
|
|
683
|
+
for (let index = 0; index < Math.max(a.length, b.length); index += 1) {
|
|
684
|
+
const difference = (a[index] ?? 0) - (b[index] ?? 0);
|
|
685
|
+
if (difference !== 0) return difference;
|
|
686
|
+
}
|
|
687
|
+
return 0;
|
|
688
|
+
}
|
|
689
|
+
|
|
636
690
|
function codexEvidenceError(message, category) {
|
|
637
691
|
return Object.assign(new Error(message), {
|
|
638
692
|
providerFailureCategory: codexFailureCategories.has(category) ? category : "unknown",
|