niceeval 0.8.1 → 0.9.1
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/INDEX.md +77 -45
- package/dist/agents/types.d.ts +28 -6
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/zh-CN.d.ts +3 -1
- package/dist/report/built-in/index.d.ts +3 -2
- package/dist/report/built-in/index.js +7 -8
- package/dist/report/built-in/standard.d.ts +1 -0
- package/dist/report/built-in/standard.js +30 -0
- package/dist/report/components.d.ts +69 -2
- package/dist/report/components.js +152 -3
- package/dist/report/compute.d.ts +28 -1
- package/dist/report/compute.js +123 -0
- package/dist/report/index.d.ts +4 -4
- package/dist/report/index.js +3 -2
- package/dist/report/locale.d.ts +39 -1
- package/dist/report/locale.js +69 -0
- package/dist/report/react/AttemptList.d.ts +3 -1
- package/dist/report/react/AttemptList.js +3 -3
- package/dist/report/react/CopyFixPrompt.d.ts +12 -0
- package/dist/report/react/CopyFixPrompt.js +12 -0
- package/dist/report/react/HeroCard.d.ts +13 -0
- package/dist/report/react/HeroCard.js +35 -0
- package/dist/report/react/PoweredBy.d.ts +5 -0
- package/dist/report/react/PoweredBy.js +7 -0
- package/dist/report/react/ScopeWarnings.d.ts +12 -0
- package/dist/report/react/ScopeWarnings.js +18 -0
- package/dist/report/react/TraceWaterfall.d.ts +14 -0
- package/dist/report/react/TraceWaterfall.js +22 -0
- package/dist/report/react/index.d.ts +6 -1
- package/dist/report/react/index.js +6 -0
- package/dist/report/report.d.ts +22 -6
- package/dist/report/report.js +68 -55
- package/dist/report/scope-warnings.d.ts +28 -0
- package/dist/report/scope-warnings.js +101 -0
- package/dist/report/text/faces.d.ts +19 -1
- package/dist/report/text/faces.js +61 -0
- package/dist/report/tree.d.ts +1 -1
- package/dist/report/tree.js +7 -2
- package/dist/report/types.d.ts +45 -0
- package/dist/report/web.d.ts +5 -4
- package/dist/report/web.js +7 -22
- package/dist/results/select.d.ts +17 -4
- package/dist/results/select.js +76 -11
- package/dist/results/types.d.ts +26 -0
- package/dist/runner/fingerprint.d.ts +3 -3
- package/dist/runner/sandbox-selection.d.ts +12 -0
- package/dist/runner/types.d.ts +18 -6
- package/dist/sandbox/types.d.ts +12 -0
- package/dist/shared/aggregate.d.ts +1 -1
- package/dist/shared/aggregate.js +1 -1
- package/docs-site/zh/explanation/evals.mdx +2 -1
- package/docs-site/zh/explanation/experiment.mdx +2 -0
- package/docs-site/zh/how-to/custom-reports.mdx +23 -7
- package/docs-site/zh/how-to/experiments.mdx +2 -2
- package/docs-site/zh/how-to/publish-report.mdx +11 -4
- package/docs-site/zh/how-to/viewing-results.mdx +17 -8
- package/docs-site/zh/how-to/write-experiment.mdx +40 -1
- package/docs-site/zh/reference/builtin-agents.mdx +40 -3
- package/docs-site/zh/reference/cli.mdx +3 -3
- package/docs-site/zh/reference/define-eval.mdx +8 -0
- package/docs-site/zh/reference/official-adapters.mdx +10 -5
- package/docs-site/zh/troubleshooting/debugging.mdx +3 -3
- package/package.json +2 -1
- package/src/agents/bub.ts +13 -1
- package/src/agents/claude-code.test.ts +43 -1
- package/src/agents/claude-code.ts +32 -14
- package/src/agents/codex.test.ts +168 -1
- package/src/agents/codex.ts +51 -15
- package/src/agents/mcp.ts +31 -0
- package/src/agents/post-setup.ts +33 -0
- package/src/agents/types.ts +28 -7
- package/src/cli.ts +15 -14
- package/src/define.ts +3 -0
- package/src/i18n/en.ts +8 -5
- package/src/i18n/zh-CN.ts +8 -4
- package/src/index.ts +1 -0
- package/src/report/built-in/index.tsx +8 -7
- package/src/report/built-in/standard.tsx +59 -0
- package/src/report/components.tsx +218 -2
- package/src/report/compute.ts +138 -1
- package/src/report/dual-render.test.tsx +141 -14
- package/src/report/index.ts +20 -0
- package/src/report/locale.ts +83 -1
- package/src/report/react/AttemptList.tsx +13 -1
- package/src/report/react/CopyFixPrompt.tsx +37 -0
- package/src/report/react/HeroCard.tsx +59 -0
- package/src/report/react/PoweredBy.tsx +20 -0
- package/src/report/react/ScopeWarnings.tsx +74 -0
- package/src/report/react/TraceWaterfall.tsx +78 -0
- package/src/report/react/enhance.js +14 -0
- package/src/report/react/index.tsx +11 -0
- package/src/report/react/styles.css +193 -7
- package/src/report/report.ts +99 -64
- package/src/report/scope-warnings.ts +155 -0
- package/src/report/site-components.test.tsx +526 -0
- package/src/report/text/faces.ts +66 -0
- package/src/report/tree.ts +10 -3
- package/src/report/types.ts +51 -0
- package/src/report/web.ts +7 -40
- package/src/results/host-equivalence.test.ts +6 -2
- package/src/results/open.ts +5 -4
- package/src/results/results.test.ts +78 -1
- package/src/results/select.ts +80 -12
- package/src/results/types.ts +27 -0
- package/src/runner/attempt.ts +10 -9
- package/src/runner/discover.test.ts +9 -1
- package/src/runner/discover.ts +3 -3
- package/src/runner/fingerprint.ts +9 -4
- package/src/runner/ledger.test.ts +30 -1
- package/src/runner/ledger.ts +26 -4
- package/src/runner/run.ts +5 -1
- package/src/runner/sandbox-selection.test.ts +131 -0
- package/src/runner/sandbox-selection.ts +110 -0
- package/src/runner/types.ts +19 -2
- package/src/sandbox/types.ts +6 -0
- package/src/shared/aggregate.ts +1 -1
- package/src/show/index.ts +19 -12
- package/src/show/render.ts +12 -12
- package/src/show/report-host.test.ts +34 -17
- package/src/show/report-host.ts +6 -5
- package/src/show/show.test.ts +141 -4
- package/src/view/app/App.test.tsx +79 -17
- package/src/view/app/App.tsx +25 -74
- package/src/view/app/components/CopyControls.tsx +4 -42
- package/src/view/app/i18n.ts +5 -227
- package/src/view/app/lib/rows.ts +3 -21
- package/src/view/app/shared.ts +1 -3
- package/src/view/app/types.ts +2 -2
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.ts +21 -10
- package/src/view/index.ts +2 -12
- package/src/view/server.ts +4 -4
- package/src/view/shared/types.ts +11 -6
- package/src/view/styles.css +9 -252
- package/src/view/view-report.test.ts +99 -33
- package/src/view/app/components/LazyArtifact.tsx +0 -51
- package/src/view/app/components/SkippedRunsBanner.tsx +0 -140
- package/src/view/app/pages/AttemptsPage.tsx +0 -80
- package/src/view/app/pages/TracesPage.tsx +0 -35
package/src/agents/codex.test.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { tmpdir } from "node:os";
|
|
|
17
17
|
import { join } from "node:path";
|
|
18
18
|
import { codexAgent, installPlugins, type CodexPluginSpec } from "./codex.ts";
|
|
19
19
|
import { createAgentSession } from "../context/session.ts";
|
|
20
|
-
import type { AgentContext, AgentSetupManifest, CommandOptions, CommandResult, Sandbox, SandboxFile } from "../types.ts";
|
|
20
|
+
import type { AgentContext, AgentSetupManifest, CommandOptions, CommandResult, McpServer, Sandbox, SandboxFile } from "../types.ts";
|
|
21
21
|
|
|
22
22
|
/** 内存沙箱:runShell 记命令(可按命令包含的子串打脚本化输出),uploadFile / writeFiles 记内容。 */
|
|
23
23
|
class FakeSandbox implements Partial<Sandbox> {
|
|
@@ -91,6 +91,32 @@ describe("codex installPlugins · 命令构造", () => {
|
|
|
91
91
|
]);
|
|
92
92
|
});
|
|
93
93
|
|
|
94
|
+
it("marketplace.sparse 逐项生成 --sparse <path>(缺省不含由上面的精确命令断言覆盖);manifest 不记录 sparse", async () => {
|
|
95
|
+
const box = sb();
|
|
96
|
+
const out = await installPlugins(asSandbox(box), [
|
|
97
|
+
{
|
|
98
|
+
marketplace: {
|
|
99
|
+
name: "acme",
|
|
100
|
+
source: "acme/codex-plugins",
|
|
101
|
+
sparse: [".agents", "plugins/repo-map"],
|
|
102
|
+
},
|
|
103
|
+
name: "repo-map",
|
|
104
|
+
},
|
|
105
|
+
]);
|
|
106
|
+
expect(box.commands[0]).toBe(
|
|
107
|
+
"codex plugin marketplace add 'acme/codex-plugins' --sparse '.agents' --sparse 'plugins/repo-map'",
|
|
108
|
+
);
|
|
109
|
+
expect(out[0]!.marketplace).toEqual({ name: "acme", source: "acme/codex-plugins" });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("marketplace.sparse 空数组与缺省等价:add 命令不含 --sparse", async () => {
|
|
113
|
+
const box = sb();
|
|
114
|
+
await installPlugins(asSandbox(box), [
|
|
115
|
+
{ marketplace: { name: "acme", source: "acme/codex-plugins", sparse: [] }, name: "repo-map" },
|
|
116
|
+
]);
|
|
117
|
+
expect(box.commands[0]).toBe("codex plugin marketplace add 'acme/codex-plugins'");
|
|
118
|
+
});
|
|
119
|
+
|
|
94
120
|
it("同名 marketplace 只连一次:两个 plugin 共用一个 marketplace.name → 只有一条 marketplace add,两条 plugin add", async () => {
|
|
95
121
|
const box = sb();
|
|
96
122
|
const plugins: CodexPluginSpec[] = [
|
|
@@ -339,3 +365,144 @@ describe("codexAgent · live step feedback", () => {
|
|
|
339
365
|
expect(ctx.session.id).toBe("thread-1");
|
|
340
366
|
});
|
|
341
367
|
});
|
|
368
|
+
|
|
369
|
+
describe("codexAgent · exec 信任姿态", () => {
|
|
370
|
+
// bug: memory/codex-hook-trust-headless-silent-skip.md
|
|
371
|
+
it("首轮与 resume 的 exec 命令都含 --dangerously-bypass-hook-trust(headless 下未授信 hook 被 codex 静默跳过,零报错)", async () => {
|
|
372
|
+
const stdout = JSON.stringify({ type: "thread.started", thread_id: "thread-1" }) + "\n";
|
|
373
|
+
const box = sb([{ match: "codex exec", result: () => ({ stdout }) }]);
|
|
374
|
+
const ctx: AgentContext = {
|
|
375
|
+
signal: new AbortController().signal,
|
|
376
|
+
flags: {},
|
|
377
|
+
sandbox: asSandbox(box),
|
|
378
|
+
session: createAgentSession(),
|
|
379
|
+
progress() {},
|
|
380
|
+
diagnostic() {},
|
|
381
|
+
log() {},
|
|
382
|
+
};
|
|
383
|
+
const agent = codexAgent({ apiKey: "test-key" });
|
|
384
|
+
|
|
385
|
+
await agent.send!({ text: "first" }, ctx);
|
|
386
|
+
await agent.send!({ text: "second" }, ctx);
|
|
387
|
+
|
|
388
|
+
const execs = box.commands.filter((c) => c.startsWith("codex exec"));
|
|
389
|
+
expect(execs).toHaveLength(2);
|
|
390
|
+
expect(execs[0]).not.toContain("codex exec resume");
|
|
391
|
+
expect(execs[1]).toContain("codex exec resume thread-1");
|
|
392
|
+
for (const cmd of execs) expect(cmd).toContain("--dangerously-bypass-hook-trust");
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("codexAgent mcpServers · 形态落位", () => {
|
|
397
|
+
const ctx = { flags: {} } as AgentContext;
|
|
398
|
+
|
|
399
|
+
it("HTTP 形态:url 行 + [mcp_servers.<name>.http_headers] 子表;manifest 只记 name/url,headers 值不落盘", async () => {
|
|
400
|
+
const box = sb();
|
|
401
|
+
await codexAgent({
|
|
402
|
+
apiKey: "k",
|
|
403
|
+
mcpServers: [
|
|
404
|
+
{ name: "team-memory", url: "https://mem.example.com/mcp/", headers: { Authorization: "Bearer sekret" } },
|
|
405
|
+
],
|
|
406
|
+
}).setup!(asSandbox(box), ctx);
|
|
407
|
+
|
|
408
|
+
const mcp = box.commands.find((c) => c.includes("[mcp_servers.team-memory]"))!;
|
|
409
|
+
expect(mcp).toContain('url = "https://mem.example.com/mcp/"');
|
|
410
|
+
expect(mcp).toContain("[mcp_servers.team-memory.http_headers]");
|
|
411
|
+
expect(mcp).toContain('"Authorization" = "Bearer sekret"');
|
|
412
|
+
expect(mcp).not.toContain("command =");
|
|
413
|
+
|
|
414
|
+
const manifestRaw = box.written["__niceeval__/agent-setup.json"]!;
|
|
415
|
+
const manifest = JSON.parse(manifestRaw) as AgentSetupManifest;
|
|
416
|
+
expect(manifest.mcpServers).toEqual([{ name: "team-memory", url: "https://mem.example.com/mcp/" }]);
|
|
417
|
+
expect(manifestRaw).not.toContain("sekret");
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
it("边界:HTTP 形态无 headers → 不写空 http_headers 子表", async () => {
|
|
421
|
+
const box = sb();
|
|
422
|
+
await codexAgent({
|
|
423
|
+
apiKey: "k",
|
|
424
|
+
mcpServers: [{ name: "team-memory", url: "https://mem.example.com/mcp/" }],
|
|
425
|
+
}).setup!(asSandbox(box), ctx);
|
|
426
|
+
|
|
427
|
+
const mcp = box.commands.find((c) => c.includes("[mcp_servers.team-memory]"))!;
|
|
428
|
+
expect(mcp).toContain('url = "https://mem.example.com/mcp/"');
|
|
429
|
+
expect(mcp).not.toContain("http_headers");
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it("反例:同一 server 同时给出 command 与 url → setup 报错点名该 server,不写 MCP 块", async () => {
|
|
433
|
+
const box = sb();
|
|
434
|
+
// 形状判别不设 kind 标签,双字段的错误配置在类型上可能混得进来 —— 运行期兜底点名报错。
|
|
435
|
+
const dup = { name: "dup-server", command: "npx", url: "https://x.example.com/mcp" } as McpServer;
|
|
436
|
+
await expect(codexAgent({ apiKey: "k", mcpServers: [dup] }).setup!(asSandbox(box), ctx)).rejects.toThrow(
|
|
437
|
+
/dup-server/,
|
|
438
|
+
);
|
|
439
|
+
expect(box.commands.some((c) => c.includes("[mcp_servers."))).toBe(false);
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
describe("codexAgent postSetup · 安装后钩子", () => {
|
|
444
|
+
const mkCtx = (): AgentContext =>
|
|
445
|
+
({
|
|
446
|
+
flags: {},
|
|
447
|
+
experimentId: "exp-1",
|
|
448
|
+
signal: new AbortController().signal,
|
|
449
|
+
progress: () => {},
|
|
450
|
+
diagnostic: () => {},
|
|
451
|
+
}) as unknown as AgentContext;
|
|
452
|
+
|
|
453
|
+
it("钩子在 Adapter 安装与 manifest 之后按数组顺序执行,拿到 SandboxHook 窄上下文", async () => {
|
|
454
|
+
const box = sb();
|
|
455
|
+
const seen: string[] = [];
|
|
456
|
+
let manifestPresentAtHook = false;
|
|
457
|
+
await codexAgent({
|
|
458
|
+
apiKey: "k",
|
|
459
|
+
mcpServers: [{ name: "browser", command: "npx" }],
|
|
460
|
+
postSetup: [
|
|
461
|
+
async (sandbox, hookCtx) => {
|
|
462
|
+
manifestPresentAtHook = box.written["__niceeval__/agent-setup.json"] !== undefined;
|
|
463
|
+
seen.push(`a:${hookCtx.experimentId}`);
|
|
464
|
+
await sandbox.runShell("post-hook-a");
|
|
465
|
+
},
|
|
466
|
+
async (sandbox) => {
|
|
467
|
+
seen.push("b");
|
|
468
|
+
await sandbox.runShell("post-hook-b");
|
|
469
|
+
},
|
|
470
|
+
],
|
|
471
|
+
}).setup!(asSandbox(box), mkCtx());
|
|
472
|
+
|
|
473
|
+
expect(seen).toEqual(["a:exp-1", "b"]);
|
|
474
|
+
expect(manifestPresentAtHook).toBe(true);
|
|
475
|
+
const configIdx = box.commands.findIndex((c) => c.includes("config.toml"));
|
|
476
|
+
const aIdx = box.commands.indexOf("post-hook-a");
|
|
477
|
+
const bIdx = box.commands.indexOf("post-hook-b");
|
|
478
|
+
expect(aIdx).toBeGreaterThan(configIdx);
|
|
479
|
+
expect(bIdx).toBeGreaterThan(aIdx);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("钩子返回的 cleanup 合成一个闭包交还 runner,按 LIFO 执行", async () => {
|
|
483
|
+
const box = sb();
|
|
484
|
+
const order: string[] = [];
|
|
485
|
+
const cleanup = await codexAgent({
|
|
486
|
+
apiKey: "k",
|
|
487
|
+
postSetup: [() => () => void order.push("a"), () => () => void order.push("b")],
|
|
488
|
+
}).setup!(asSandbox(box), mkCtx());
|
|
489
|
+
|
|
490
|
+
expect(typeof cleanup).toBe("function");
|
|
491
|
+
await (cleanup as () => Promise<void> | void)();
|
|
492
|
+
expect(order).toEqual(["b", "a"]);
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
it("反例:钩子抛错从 setup 传播(attempt errored 通道)", async () => {
|
|
496
|
+
const box = sb();
|
|
497
|
+
await expect(
|
|
498
|
+
codexAgent({
|
|
499
|
+
apiKey: "k",
|
|
500
|
+
postSetup: [
|
|
501
|
+
() => {
|
|
502
|
+
throw new Error("hook boom");
|
|
503
|
+
},
|
|
504
|
+
],
|
|
505
|
+
}).setup!(asSandbox(box), mkCtx()),
|
|
506
|
+
).rejects.toThrow("hook boom");
|
|
507
|
+
});
|
|
508
|
+
});
|
package/src/agents/codex.ts
CHANGED
|
@@ -19,7 +19,9 @@ import {
|
|
|
19
19
|
import { mapCodexSpans } from "../o11y/otlp/mappers/codex.ts";
|
|
20
20
|
import { t } from "../i18n/index.ts";
|
|
21
21
|
import { DEFAULT_CODEX_CLI_VERSION } from "./coding-cli-versions.ts";
|
|
22
|
-
import
|
|
22
|
+
import { assertMcpServers, isHttpMcp, mcpManifestEntries } from "./mcp.ts";
|
|
23
|
+
import { runPostSetupHooks } from "./post-setup.ts";
|
|
24
|
+
import type { Agent, AgentSetupManifest, McpServer, Sandbox, SandboxHook, SkillSpec } from "../types.ts";
|
|
23
25
|
|
|
24
26
|
// ───────────────────────────────────────────────────────────────────────────
|
|
25
27
|
// OpenAI Codex CLI 的 agent adapter(沙箱型)。
|
|
@@ -59,6 +61,12 @@ export interface CodexPluginSpec {
|
|
|
59
61
|
source: string;
|
|
60
62
|
/** 固定 Marketplace 的 Tag、Commit 或 Branch(→ `codex plugin marketplace add --ref`)。 */
|
|
61
63
|
ref?: string;
|
|
64
|
+
/**
|
|
65
|
+
* sparse 拉取的路径列表,每项生成一个 `codex plugin marketplace add --sparse <path>`
|
|
66
|
+
* (codex 的 `--sparse` 必须带路径参数、可重复):大仓库只取插件所需路径,省略或空数组即全量 clone。
|
|
67
|
+
* 只影响拉取速度,不影响装出来的内容;manifest 不记录它。
|
|
68
|
+
*/
|
|
69
|
+
sparse?: string[];
|
|
62
70
|
};
|
|
63
71
|
/** Marketplace 中的 Plugin 名。 */
|
|
64
72
|
name: string;
|
|
@@ -71,7 +79,8 @@ export interface CodexConfig {
|
|
|
71
79
|
baseUrl?: string;
|
|
72
80
|
/**
|
|
73
81
|
* 额外 MCP server(每个沙箱 setup 时追加进 ~/.codex/config.toml)。
|
|
74
|
-
*
|
|
82
|
+
* stdio 形态(command/args/env)写 [mcp_servers.<name>] 的 command 行;
|
|
83
|
+
* Streamable HTTP 形态(url/headers)写 url 行,headers 进 [mcp_servers.<name>.http_headers] 子表。
|
|
75
84
|
*/
|
|
76
85
|
mcpServers?: McpServer[];
|
|
77
86
|
/**
|
|
@@ -92,6 +101,14 @@ export interface CodexConfig {
|
|
|
92
101
|
* 项目相对路径与字节 SHA-256,不落正文。
|
|
93
102
|
*/
|
|
94
103
|
configFile?: string;
|
|
104
|
+
/**
|
|
105
|
+
* 安装后按数组顺序运行的用户钩子(复用 SandboxHook 的窄上下文):在写主配置、挂 MCP、
|
|
106
|
+
* 装 Skills / Plugin、写 manifest 全部完成后执行,适合跑插件自带的 setup 脚本这类
|
|
107
|
+
* 「安装产物就位后才能跑」的过程动作。钩子返回的 cleanup 按 LIFO 与 teardown 一起收尾;
|
|
108
|
+
* 抛错按基础设施错误计(attempt errored)。
|
|
109
|
+
* 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。
|
|
110
|
+
*/
|
|
111
|
+
postSetup?: SandboxHook[];
|
|
95
112
|
}
|
|
96
113
|
|
|
97
114
|
export function codexAgent(config?: CodexConfig): Agent {
|
|
@@ -155,15 +172,25 @@ export function codexAgent(config?: CodexConfig): Agent {
|
|
|
155
172
|
}
|
|
156
173
|
|
|
157
174
|
if (config?.mcpServers?.length) {
|
|
175
|
+
assertMcpServers(config.mcpServers);
|
|
158
176
|
const mcpToml = config.mcpServers
|
|
159
177
|
.map((s) => {
|
|
160
178
|
// 注意是复数 mcp_servers:单数 [mcp_server.x] 会被 codex 静默忽略,
|
|
161
179
|
// MCP 压根挂不上(实测 codex-cli 0.142.x,`codex mcp list` 可核对)。
|
|
162
|
-
const lines: string[] = [`[mcp_servers.${s.name}]
|
|
163
|
-
if (
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
180
|
+
const lines: string[] = [`[mcp_servers.${s.name}]`];
|
|
181
|
+
if (isHttpMcp(s)) {
|
|
182
|
+
lines.push(`url = "${s.url}"`);
|
|
183
|
+
if (s.headers && Object.keys(s.headers).length) {
|
|
184
|
+
lines.push(`[mcp_servers.${s.name}.http_headers]`);
|
|
185
|
+
for (const [k, v] of Object.entries(s.headers)) lines.push(`"${k}" = "${v}"`);
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
lines.push(`command = "${s.command}"`);
|
|
189
|
+
if (s.args?.length) lines.push(`args = [${s.args.map((a) => `"${a}"`).join(", ")}]`);
|
|
190
|
+
if (s.env && Object.keys(s.env).length) {
|
|
191
|
+
lines.push(`[mcp_servers.${s.name}.env]`);
|
|
192
|
+
for (const [k, v] of Object.entries(s.env)) lines.push(`${k} = "${v}"`);
|
|
193
|
+
}
|
|
167
194
|
}
|
|
168
195
|
return lines.join("\n");
|
|
169
196
|
})
|
|
@@ -184,12 +211,8 @@ export function codexAgent(config?: CodexConfig): Agent {
|
|
|
184
211
|
manifest.nativePlugins = await installPlugins(sb, config.plugins);
|
|
185
212
|
}
|
|
186
213
|
if (config?.mcpServers?.length) {
|
|
187
|
-
// manifest 只记「挂了哪个 server
|
|
188
|
-
manifest.mcpServers = config.mcpServers
|
|
189
|
-
name: s.name,
|
|
190
|
-
command: s.command,
|
|
191
|
-
...(s.args?.length ? { args: [...s.args] } : {}),
|
|
192
|
-
}));
|
|
214
|
+
// manifest 只记「挂了哪个 server、怎么连」;env / headers 里可能有 token,不落盘。
|
|
215
|
+
manifest.mcpServers = mcpManifestEntries(config.mcpServers);
|
|
193
216
|
}
|
|
194
217
|
if (nativeConfig) {
|
|
195
218
|
// 只记来源路径与字节哈希,不落正文(任意官方配置都可能带敏感字符串)。
|
|
@@ -203,6 +226,10 @@ export function codexAgent(config?: CodexConfig): Agent {
|
|
|
203
226
|
) {
|
|
204
227
|
await writeAgentSetupManifest(sb, manifest);
|
|
205
228
|
}
|
|
229
|
+
|
|
230
|
+
// 安装后钩子(postSetup):排在 manifest 之后——manifest 审计 Adapter 自身的安装事实,
|
|
231
|
+
// 钩子失败不该丢掉这份证据。返回的合成 cleanup 交给 runner,与 teardown 一起 LIFO 收尾。
|
|
232
|
+
return await runPostSetupHooks(sb, ctx, config?.postSetup);
|
|
206
233
|
},
|
|
207
234
|
|
|
208
235
|
tracing: {
|
|
@@ -223,7 +250,12 @@ export function codexAgent(config?: CodexConfig): Agent {
|
|
|
223
250
|
|
|
224
251
|
async send(input, ctx) {
|
|
225
252
|
const sb = ctx.sandbox;
|
|
226
|
-
|
|
253
|
+
// hook trust bypass 是 runtime-only(config.toml 设不了):headless 下 codex 对非 managed
|
|
254
|
+
// 来源 hook 的交互式授信永远无人应答,不带它插件装的 hook 会被静默跳过、零报错
|
|
255
|
+
// (见 memory/codex-hook-trust-headless-silent-skip.md)。沙箱内 hook 来源全部由实验配置
|
|
256
|
+
// 声明,与 approvals/sandbox bypass 同一信任层级。
|
|
257
|
+
const flags =
|
|
258
|
+
"--json --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check --dangerously-bypass-hook-trust";
|
|
227
259
|
const prompt = shared.shellQuote(input.text);
|
|
228
260
|
const resuming = ctx.session.id;
|
|
229
261
|
const cmd = resuming
|
|
@@ -324,8 +356,12 @@ export async function installPlugins(
|
|
|
324
356
|
const { marketplace } = plugin;
|
|
325
357
|
if (!connected.has(marketplace.name)) {
|
|
326
358
|
const refFlag = marketplace.ref ? ` --ref ${shared.shellQuote(marketplace.ref)}` : "";
|
|
359
|
+
// --sparse 只影响拉取速度,不影响装出来的内容;manifest 不记录它。
|
|
360
|
+
const sparseFlags = (marketplace.sparse ?? [])
|
|
361
|
+
.map((path) => ` --sparse ${shared.shellQuote(path)}`)
|
|
362
|
+
.join("");
|
|
327
363
|
const add = await sb.runShell(
|
|
328
|
-
`codex plugin marketplace add ${shared.shellQuote(marketplace.source)}${refFlag}`,
|
|
364
|
+
`codex plugin marketplace add ${shared.shellQuote(marketplace.source)}${refFlag}${sparseFlags}`,
|
|
329
365
|
);
|
|
330
366
|
if (add.exitCode !== 0) {
|
|
331
367
|
throw new Error(
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// MCP server 的形态判别与 manifest 映射 —— Claude Code / Codex 两个 adapter 共用。
|
|
2
|
+
// 契约见 docs/feature/adapters/architecture/coding-agent-extensions.md「类型边界」:
|
|
3
|
+
// stdio(command)与 Streamable HTTP(url)按形状判别;双字段是配置错误,setup 点名报错。
|
|
4
|
+
|
|
5
|
+
import { t } from "../i18n/index.ts";
|
|
6
|
+
import type { AgentSetupManifest, McpHttpServer, McpServer } from "./types.ts";
|
|
7
|
+
|
|
8
|
+
/** 形态判别:有 url 的是 HTTP。调用前先过 {@link assertMcpServers}(双字段在那里报错)。 */
|
|
9
|
+
export function isHttpMcp(server: McpServer): server is McpHttpServer {
|
|
10
|
+
return "url" in server && typeof server.url === "string";
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** 双字段(command + url)的配置错误在写任何沙箱配置前抛出,点名 server。 */
|
|
14
|
+
export function assertMcpServers(servers: readonly McpServer[]): void {
|
|
15
|
+
for (const server of servers) {
|
|
16
|
+
if ("command" in server && "url" in server) {
|
|
17
|
+
throw new Error(t("mcp.ambiguousTransport", { name: server.name }));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** manifest 条目:只记非 secret 字段(stdio 不含 env,HTTP 不含 headers)。 */
|
|
23
|
+
export function mcpManifestEntries(
|
|
24
|
+
servers: readonly McpServer[],
|
|
25
|
+
): NonNullable<AgentSetupManifest["mcpServers"]> {
|
|
26
|
+
return servers.map((s) =>
|
|
27
|
+
isHttpMcp(s)
|
|
28
|
+
? { name: s.name, url: s.url }
|
|
29
|
+
: { name: s.name, command: s.command, ...(s.args?.length ? { args: [...s.args] } : {}) },
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// factory 的 postSetup 钩子执行器 —— Claude Code / Codex / Bub 共用。
|
|
2
|
+
// 契约见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」:
|
|
3
|
+
// 在 adapter 全部安装步骤(含 manifest)之后按数组顺序执行;复用 SandboxHook 的窄上下文;
|
|
4
|
+
// 返回的 cleanup 合成一个 LIFO 闭包,由 runner 与 agent teardown 一起收尾;钩子抛错
|
|
5
|
+
// 直接传播(attempt errored)。
|
|
6
|
+
|
|
7
|
+
import type { Cleanup } from "../shared/types.ts";
|
|
8
|
+
import type { Sandbox, SandboxHook, SandboxHookContext } from "../sandbox/types.ts";
|
|
9
|
+
import type { AgentContext } from "./types.ts";
|
|
10
|
+
|
|
11
|
+
export async function runPostSetupHooks(
|
|
12
|
+
sb: Sandbox,
|
|
13
|
+
ctx: AgentContext,
|
|
14
|
+
hooks: readonly SandboxHook[] | undefined,
|
|
15
|
+
): Promise<Cleanup | void> {
|
|
16
|
+
if (!hooks?.length) return;
|
|
17
|
+
// 窄上下文与沙箱钩子同款:不把 session / model / telemetry 借给过程钩子。
|
|
18
|
+
const hookCtx: SandboxHookContext = {
|
|
19
|
+
experimentId: ctx.experimentId,
|
|
20
|
+
signal: ctx.signal,
|
|
21
|
+
progress: (update) => ctx.progress(update),
|
|
22
|
+
diagnostic: (input) => ctx.diagnostic(input),
|
|
23
|
+
};
|
|
24
|
+
const cleanups: Cleanup[] = [];
|
|
25
|
+
for (const hook of hooks) {
|
|
26
|
+
const cleanup = await hook(sb, hookCtx);
|
|
27
|
+
if (typeof cleanup === "function") cleanups.push(cleanup);
|
|
28
|
+
}
|
|
29
|
+
if (!cleanups.length) return;
|
|
30
|
+
return async () => {
|
|
31
|
+
for (const cleanup of [...cleanups].reverse()) await cleanup();
|
|
32
|
+
};
|
|
33
|
+
}
|
package/src/agents/types.ts
CHANGED
|
@@ -7,21 +7,42 @@ import type { StreamEvent, TraceSpan, Usage } from "../o11y/types.ts";
|
|
|
7
7
|
import type { Sandbox } from "../sandbox/types.ts";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* 见 docs/feature/adapters/architecture/coding-agent-extensions.md「类型边界」。
|
|
10
|
+
* 本地 stdio 形态的 MCP server:沙箱内起子进程,按 stdio 说 MCP 协议。
|
|
11
|
+
* 与 {@link McpHttpServer} 按形状判别(有 `command` 的是 stdio,有 `url` 的是 HTTP)。
|
|
13
12
|
*/
|
|
14
|
-
export interface
|
|
13
|
+
export interface McpStdioServer {
|
|
15
14
|
/** 服务器唯一名(config key)。 */
|
|
16
15
|
name: string;
|
|
17
16
|
/** 启动命令(如 "npx"、"node"、"uvx")。 */
|
|
18
17
|
command: string;
|
|
19
18
|
/** 传给命令的参数。 */
|
|
20
19
|
args?: string[];
|
|
21
|
-
/**
|
|
20
|
+
/** 注入服务器进程的环境变量(可能含 secret,不进 manifest)。 */
|
|
22
21
|
env?: Record<string, string>;
|
|
23
22
|
}
|
|
24
23
|
|
|
24
|
+
/**
|
|
25
|
+
* 远程 Streamable HTTP 形态的 MCP server:沙箱直接连一个 HTTP 端点。
|
|
26
|
+
* `url` 必须沙箱内可达——宿主机上的服务先经隧道(cloudflared / tailscale 等)暴露。
|
|
27
|
+
*/
|
|
28
|
+
export interface McpHttpServer {
|
|
29
|
+
/** 服务器唯一名(config key)。 */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Streamable HTTP 端点(如 https://mem.example.com/mcp/)。 */
|
|
32
|
+
url: string;
|
|
33
|
+
/** 逐字写进每个请求的 HTTP 头(常用于 Authorization;可能含 secret,不进 manifest)。 */
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* MCP server 描述符 —— 支持 MCP 的 adapter(Claude Code / Codex)共用的工具服务单元,
|
|
39
|
+
* 不是 native plugin 的一种。stdio 与 Streamable HTTP 两种形态按形状判别,不设 kind 标签
|
|
40
|
+
* (两种形态各有唯一必填判别字段);同时给出 `command` 与 `url` 属配置错误,setup 报错点名。
|
|
41
|
+
* 在 agent factory config 里声明,setup 阶段写进各自的配置文件。
|
|
42
|
+
* 见 docs/feature/adapters/architecture/coding-agent-extensions.md「类型边界」。
|
|
43
|
+
*/
|
|
44
|
+
export type McpServer = McpStdioServer | McpHttpServer;
|
|
45
|
+
|
|
25
46
|
/**
|
|
26
47
|
* Skill 的来源描述 —— Claude Code / Codex / Bub 共用的**数据类型**:只统一「从哪里取得
|
|
27
48
|
* 哪份 Skill」,安装位置、发现机制、要不要额外写 project instruction 由各 Adapter 决定。
|
|
@@ -67,8 +88,8 @@ export interface AgentSetupManifest {
|
|
|
67
88
|
/** 安装后 CLI 报告的版本;取不到时省略。 */
|
|
68
89
|
resolvedVersion?: string;
|
|
69
90
|
}>;
|
|
70
|
-
/** 挂上的 MCP server(不含 env
|
|
71
|
-
mcpServers?: Array<{ name: string; command: string; args?: string[] }>;
|
|
91
|
+
/** 挂上的 MCP server(只记非 secret 字段:stdio 不含 env,HTTP 不含 headers)。 */
|
|
92
|
+
mcpServers?: Array<{ name: string; command: string; args?: string[] } | { name: string; url: string }>;
|
|
72
93
|
/**
|
|
73
94
|
* 官方原生配置文件(Claude Code `settings.json` / Codex `config.toml`):只记 Agent 名、
|
|
74
95
|
* 项目相对来源路径与原始字节的 SHA-256,不落正文 —— 任意官方配置都可能携带敏感字符串,
|
package/src/cli.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { planCarry } from "./runner/fingerprint.ts";
|
|
|
17
17
|
import { failureDetailFromResult } from "./runner/feedback/failure.ts";
|
|
18
18
|
import { stopAllSandboxes, liveSandboxCount } from "./sandbox/registry.ts";
|
|
19
19
|
import { evalLevelStats } from "./shared/verdict.ts";
|
|
20
|
-
import {
|
|
20
|
+
import { prepareRunSandboxes, resolvedSandboxRecommendedConcurrency } from "./runner/sandbox-selection.ts";
|
|
21
21
|
import { Json, JUnit } from "./runner/reporters/json.ts";
|
|
22
22
|
import { Artifacts as ArtifactsReporter } from "./runner/reporters/artifacts.ts";
|
|
23
23
|
import {
|
|
@@ -170,15 +170,15 @@ const FLAG_OPTIONS = {
|
|
|
170
170
|
diff: { type: "boolean" },
|
|
171
171
|
/** `show` 命令专用:执行时间轴——对匹配的每个 experiment × eval 分节,逐 attempt 列时间 / verdict / 摘要 / 耗时 / 成本 / locator;与 `--report` 互斥。 */
|
|
172
172
|
history: { type: "boolean" },
|
|
173
|
-
/** `show` / `view` 命令专用:按路径段前缀收窄 experiment
|
|
174
|
-
|
|
173
|
+
/** `show` / `view` 命令专用:按路径段前缀收窄 experiment(与 `niceeval exp` 位置参数同一套匹配);组名会选中组内全部配置。`view --out` 时同一收窄决定出站内容。 */
|
|
174
|
+
exp: { type: "string" },
|
|
175
175
|
/** `show` / `view` / `sandbox enter|list|stop` 共用:结果根目录(`.niceeval` 之外的另一个根,如 `copySnapshots` 产出的发布根)。 */
|
|
176
176
|
results: { type: "string" },
|
|
177
177
|
/** `view` 命令专用:只打开这一份快照文件(`snapshot.json`);文件不可读时命令失败(扫描模式只跳过)。 */
|
|
178
178
|
snapshot: { type: "string" },
|
|
179
179
|
/** `show` / `view` 命令专用:用文件默认导出的 `defineReport(...)` 替换两者共用的默认报告。 */
|
|
180
180
|
report: { type: "string" },
|
|
181
|
-
/** `show` / `view`
|
|
181
|
+
/** `show` / `view` 命令专用:选择报告的初始页;`show` 渲染该页并在尾部附其余页索引,`view` 以它作初始路由。未命中的页 id 按用法错误退出并列出可用页 id。 */
|
|
182
182
|
page: { type: "string" },
|
|
183
183
|
/** 只打印本次会匹配到的 eval × 运行配置,不实际执行(按下面 `--output` 选中的 profile 给出预览)。 */
|
|
184
184
|
dry: { type: "boolean" },
|
|
@@ -308,7 +308,7 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
|
|
|
308
308
|
sandboxPath: values.path as string | undefined,
|
|
309
309
|
leaveRunning: values["leave-running"] === true,
|
|
310
310
|
history: values.history === true,
|
|
311
|
-
experiment: values.
|
|
311
|
+
experiment: values.exp as string | undefined,
|
|
312
312
|
results: values.results as string | undefined,
|
|
313
313
|
snapshot: values.snapshot as string | undefined,
|
|
314
314
|
report: values.report as string | undefined,
|
|
@@ -320,7 +320,7 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
|
|
|
320
320
|
/**
|
|
321
321
|
* exp 只接受两类输入:位置参数选「跑哪些 eval」+ 调度/输出/机器出口 flag 选「对着哪个 agent、
|
|
322
322
|
* 怎么跑」。show / view 专属的证据切面(`--source`/`--execution`/`--diff`)、时间轴(`--history`)、
|
|
323
|
-
* Scope 收窄(`--
|
|
323
|
+
* Scope 收窄(`--exp`/`--results`)、报告装载(`--report`/`--page`)、查看器
|
|
324
324
|
* (`--snapshot`/`--out`/`--port`/`--open`)不能被 exp 静默忽略(见 docs/feature/experiments/
|
|
325
325
|
* cli.md「用法错误」)。返回第一个被误用的 flag 及其归属命令(用于报错),没有误用返回 undefined。
|
|
326
326
|
*/
|
|
@@ -333,7 +333,7 @@ function firstViewerOnlyFlag(flags: Flags): { flag: string; command: string } |
|
|
|
333
333
|
if (flags.timing !== undefined) return { flag: "--timing", command: SHOW };
|
|
334
334
|
if (flags.diff || flags.diffPath !== undefined) return { flag: "--diff", command: SHOW };
|
|
335
335
|
if (flags.history) return { flag: "--history", command: SHOW };
|
|
336
|
-
if (flags.experiment !== undefined) return { flag: "--
|
|
336
|
+
if (flags.experiment !== undefined) return { flag: "--exp", command: BOTH };
|
|
337
337
|
if (flags.results !== undefined) return { flag: "--results", command: BOTH };
|
|
338
338
|
if (flags.report !== undefined) return { flag: "--report", command: BOTH };
|
|
339
339
|
if (flags.page !== undefined) return { flag: "--page", command: BOTH };
|
|
@@ -468,7 +468,7 @@ function evalsFilterFromExperiment(
|
|
|
468
468
|
): (id: string) => boolean {
|
|
469
469
|
const patternFilter = makeFilter(patterns);
|
|
470
470
|
let expFilter: (id: string) => boolean = () => true;
|
|
471
|
-
if (Array.isArray(evals)) expFilter = (
|
|
471
|
+
if (Array.isArray(evals)) expFilter = makeFilter(evals);
|
|
472
472
|
else if (typeof evals === "function") expFilter = evals;
|
|
473
473
|
return (id) => expFilter(id) && patternFilter(id);
|
|
474
474
|
}
|
|
@@ -566,11 +566,10 @@ async function main(): Promise<void> {
|
|
|
566
566
|
}
|
|
567
567
|
|
|
568
568
|
if (command === "view") {
|
|
569
|
-
// 位置参数只有一种含义:eval id 前缀(
|
|
569
|
+
// 位置参数只有一种含义:eval id 前缀(收窄有效根)。结果根经 --results 递入,
|
|
570
570
|
// 单开一份快照经 --snapshot 递入;--report 整槽替换报告槽(与 show --report 吃同一个文件),
|
|
571
571
|
// --page 定初始页。文件与目录都不进位置参数(docs/feature/reports/view.md「打开与收窄」)。
|
|
572
|
-
// --out
|
|
573
|
-
// + filter 的下一步),经 exitOnViewUserError 按用法错误退出。
|
|
572
|
+
// --out 接受同一收窄:出站内容即收窄后的有效根(docs/feature/reports/view.md「静态导出」)。
|
|
574
573
|
let viewInput: { input?: string; patterns: string[] };
|
|
575
574
|
try {
|
|
576
575
|
viewInput = resolveViewInput(cwd, positionals, {
|
|
@@ -771,6 +770,9 @@ async function main(): Promise<void> {
|
|
|
771
770
|
process.exit(1);
|
|
772
771
|
}
|
|
773
772
|
|
|
773
|
+
// environments 查表在 dry-run 与真实运行共用的规划边界完成;缺表项在任何沙箱/agent 花费前穷举失败。
|
|
774
|
+
prepareRunSandboxes(evals, agentRuns, config.sandbox);
|
|
775
|
+
|
|
774
776
|
if (flags.dry) {
|
|
775
777
|
// --dry 只按所选 profile 打印计划,不运行、不落盘 —— 三种 profile 各自的展示逻辑
|
|
776
778
|
// 都在 runner/feedback/{human,agent,ci}.ts 里,这里只负责拼数据、选函数、写流、退出。
|
|
@@ -826,7 +828,7 @@ async function main(): Promise<void> {
|
|
|
826
828
|
// envelope 展示的"携入"就会和 run.ts 真实调度的"携入"对不上(见 memory 的
|
|
827
829
|
// live-carry-row-shows-waiting-forever)。
|
|
828
830
|
const priorResults = flags.force ? undefined : await loadLatestResultsPerEval(join(cwd, ".niceeval"));
|
|
829
|
-
const carryPlan = priorResults?.length ? await planCarry(evals, agentRuns, priorResults) : undefined;
|
|
831
|
+
const carryPlan = priorResults?.length ? await planCarry(evals, agentRuns, priorResults, config.sandbox) : undefined;
|
|
830
832
|
const reusedFailures = (carryPlan?.carriedResults ?? [])
|
|
831
833
|
.map(failureDetailFromResult)
|
|
832
834
|
.filter((failure) => failure !== undefined);
|
|
@@ -834,8 +836,7 @@ async function main(): Promise<void> {
|
|
|
834
836
|
// 无全局默认:并发上限由 sandbox provider 的推荐值决定(多个 agentRun 各有 sandbox 时取
|
|
835
837
|
// 最小值,最保守的 provider 决定上限)。同一个值既进 RunFeedbackPlan.shape,也传给 runEvals——
|
|
836
838
|
// 两处必须是同一个数字,dashboard 展示的并发上限不能和真实调度的并发上限对不上。
|
|
837
|
-
const
|
|
838
|
-
const sandboxDefaultConcurrency = sandboxRecs.length > 0 ? Math.min(...sandboxRecs) : 10;
|
|
839
|
+
const sandboxDefaultConcurrency = resolvedSandboxRecommendedConcurrency(evals, agentRuns, config.sandbox);
|
|
839
840
|
const maxConcurrency =
|
|
840
841
|
flags.maxConcurrency ??
|
|
841
842
|
envNumber("NICEEVAL_MAX_CONCURRENCY") ??
|
package/src/define.ts
CHANGED
|
@@ -55,6 +55,9 @@ export function defineEval(def: EvalDef): EvalDef {
|
|
|
55
55
|
if (typeof def.test !== "function") {
|
|
56
56
|
throw new Error(t("define.evalTestRequired"));
|
|
57
57
|
}
|
|
58
|
+
if (def.environment !== undefined && def.environment.trim().length === 0) {
|
|
59
|
+
throw new Error(t("define.evalEnvironmentEmpty"));
|
|
60
|
+
}
|
|
58
61
|
return def;
|
|
59
62
|
}
|
|
60
63
|
|
package/src/i18n/en.ts
CHANGED
|
@@ -24,6 +24,8 @@ export const en = {
|
|
|
24
24
|
"skill.repoAmbiguous": "Repo skill {{source}} contains multiple skills; select which ones to enable with `skills: [...]`. Available: {{available}}.",
|
|
25
25
|
"skill.repoUnknownSkill": "Repo skill {{source}} (ref: {{ref}}) has no skill named \"{{skill}}\". Available: {{available}}.",
|
|
26
26
|
"skill.copyFailed": "Could not install skill \"{{name}}\" into {{dest}}:\n{{tail}}",
|
|
27
|
+
"mcp.ambiguousTransport":
|
|
28
|
+
"MCP server \"{{name}}\" specifies both \"command\" and \"url\" — pick one transport: \"command\" for a local stdio server, \"url\" for a remote Streamable HTTP endpoint.",
|
|
27
29
|
"plugin.marketplaceFailed": "Could not connect {{agent}} marketplace \"{{name}}\" (source: {{source}}, ref: {{ref}}):\n{{tail}}",
|
|
28
30
|
"plugin.marketplaceVerifyFailed": "Could not read back the registered marketplace list after adding {{agent}} marketplace \"{{name}}\" ({{command}}):\n{{tail}}",
|
|
29
31
|
"plugin.marketplaceNameMismatch":
|
|
@@ -82,15 +84,15 @@ export const en = {
|
|
|
82
84
|
" --timing unified timing tree for the attempt (phases + hooks/commands/turns + per-turn OTel)\n" +
|
|
83
85
|
" --diff[=file] sandbox workspace file-change summary; =file expands one file\n" +
|
|
84
86
|
" --history per experiment × eval execution timeline (mutually exclusive with --report)\n" +
|
|
85
|
-
" --results <dir> pin a results root --
|
|
86
|
-
" --report <file> custom report --page <id> pick
|
|
87
|
-
" reports
|
|
87
|
+
" --results <dir> pin a results root --exp <id> one experiment\n" +
|
|
88
|
+
" --report <file> custom report --page <id> pick the initial page (multi-page\n" +
|
|
89
|
+
" reports render it, then list the rest as a page index with copyable commands)\n" +
|
|
88
90
|
" niceeval list list discovered evals\n" +
|
|
89
91
|
" niceeval view [eval-id-prefix…] [--out dir] [--port n] [--no-open]\n" +
|
|
90
92
|
" report pages + evidence rooms; --report <file> swaps in your report\n" +
|
|
91
93
|
" (same file as show); --page <id> picks the initial page;\n" +
|
|
92
94
|
" --results <dir> pins a results root; --snapshot <file> opens exactly\n" +
|
|
93
|
-
" one snapshot; --
|
|
95
|
+
" one snapshot; --exp <id> one experiment\n" +
|
|
94
96
|
" --out <dir> exports a static site: index.html plus the viewer\n" +
|
|
95
97
|
" artifacts, ready for any static host\n" +
|
|
96
98
|
" niceeval sandbox list|enter|history|diff|stop inspect & destroy sandboxes kept by --keep-sandbox\n" +
|
|
@@ -107,7 +109,7 @@ export const en = {
|
|
|
107
109
|
"cli.show.noResults": "No results found under {{root}}. Run `niceeval exp` first, then `niceeval show`.\n",
|
|
108
110
|
"cli.show.runDirMissing": "Results directory not found: {{dir}}\n",
|
|
109
111
|
"cli.show.noEvalMatch": "No results matched: {{patterns}}. Evals with results: {{evals}}\n",
|
|
110
|
-
"cli.show.noExperimentMatch": "No experiment matched --
|
|
112
|
+
"cli.show.noExperimentMatch": "No experiment matched --exp {{arg}}. Experiments with results: {{experiments}}\n",
|
|
111
113
|
"cli.show.historyReportConflict":
|
|
112
114
|
"`--history` and `--report` are mutually exclusive: both take over the main output. --history is the host's per-attempt execution timeline; for snapshot-level trends, compose exp.snapshots inside your report file instead.\n",
|
|
113
115
|
"cli.show.evidenceNeedsEval":
|
|
@@ -151,6 +153,7 @@ export const en = {
|
|
|
151
153
|
"context.turnFailedDefault": "This send returned failed (turn status = failed)",
|
|
152
154
|
"define.agentNameRequired": "defineAgent requires name.",
|
|
153
155
|
"define.evalIdRejected": "defineEval does not accept id; ids are derived from file paths.",
|
|
156
|
+
"define.evalEnvironmentEmpty": "defineEval environment must be a non-empty profile id when provided.",
|
|
154
157
|
"define.evalTestRequired": "defineEval requires an async test(t) function.",
|
|
155
158
|
"define.experimentAgentRequired": "defineExperiment requires agent.",
|
|
156
159
|
"define.experimentFlagNotJson": "experiment.flags.{{key}} is not JSON-serializable (functions / undefined / cycles / bigint are not allowed); flags are persisted verbatim into result snapshots and must be plain JSON.",
|