niceeval 0.6.0 → 0.6.2

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.
Files changed (314) hide show
  1. package/dist/agents/types.d.ts +72 -6
  2. package/dist/context/types.d.ts +32 -12
  3. package/dist/i18n/en.d.ts +54 -0
  4. package/dist/i18n/zh-CN.d.ts +55 -1
  5. package/dist/o11y/types.d.ts +16 -2
  6. package/dist/report/aggregate.d.ts +5 -3
  7. package/dist/report/aggregate.js +32 -5
  8. package/dist/report/built-ins/experiment-comparison.d.ts +39 -0
  9. package/dist/report/built-ins/experiment-comparison.js +119 -0
  10. package/dist/report/built-ins/index.d.ts +2 -1
  11. package/dist/report/built-ins/index.js +2 -2
  12. package/dist/report/components.d.ts +10 -2
  13. package/dist/report/components.js +3 -3
  14. package/dist/report/compute.d.ts +11 -18
  15. package/dist/report/compute.js +68 -66
  16. package/dist/report/flag.d.ts +16 -1
  17. package/dist/report/flag.js +19 -1
  18. package/dist/report/format.d.ts +16 -14
  19. package/dist/report/format.js +28 -30
  20. package/dist/report/index.d.ts +5 -4
  21. package/dist/report/index.js +6 -5
  22. package/dist/report/locale.d.ts +23 -3
  23. package/dist/report/locale.js +47 -6
  24. package/dist/report/metrics.d.ts +13 -1
  25. package/dist/report/metrics.js +66 -15
  26. package/dist/report/primitives.d.ts +6 -0
  27. package/dist/report/react/AttemptList.d.ts +4 -4
  28. package/dist/report/react/AttemptList.js +8 -10
  29. package/dist/report/react/EvalList.d.ts +1 -1
  30. package/dist/report/react/EvalList.js +0 -0
  31. package/dist/report/react/ExperimentComparison.d.ts +8 -0
  32. package/dist/report/react/ExperimentComparison.js +11 -0
  33. package/dist/report/react/ExperimentList.d.ts +4 -2
  34. package/dist/report/react/ExperimentList.js +57 -7
  35. package/dist/report/react/MetricScatter.js +6 -16
  36. package/dist/report/react/chart-math.d.ts +23 -6
  37. package/dist/report/react/chart-math.js +71 -19
  38. package/dist/report/react/fixtures.d.ts +3 -3
  39. package/dist/report/react/fixtures.js +30 -18
  40. package/dist/report/react/format.d.ts +1 -1
  41. package/dist/report/react/format.js +1 -1
  42. package/dist/report/react/index.d.ts +1 -1
  43. package/dist/report/report.d.ts +5 -1
  44. package/dist/report/report.js +6 -2
  45. package/dist/report/text/faces.d.ts +1 -1
  46. package/dist/report/text/faces.js +100 -61
  47. package/dist/report/text/table.js +36 -5
  48. package/dist/report/types.d.ts +40 -34
  49. package/dist/results/types.d.ts +11 -0
  50. package/dist/runner/feedback/sink.d.ts +110 -0
  51. package/dist/runner/types.d.ts +513 -22
  52. package/dist/sandbox/docker.d.ts +23 -2
  53. package/dist/sandbox/e2b.d.ts +15 -1
  54. package/dist/sandbox/errors.d.ts +30 -3
  55. package/dist/sandbox/io-retry.d.ts +17 -0
  56. package/dist/sandbox/registry.d.ts +2 -0
  57. package/dist/sandbox/resolve.d.ts +18 -5
  58. package/dist/sandbox/retry.d.ts +11 -1
  59. package/dist/sandbox/types.d.ts +39 -5
  60. package/dist/sandbox/vercel.d.ts +7 -1
  61. package/dist/scoring/coverage.d.ts +30 -0
  62. package/dist/scoring/display.d.ts +21 -0
  63. package/dist/scoring/display.js +120 -0
  64. package/dist/scoring/types.d.ts +103 -20
  65. package/dist/shared/aggregate.d.ts +1 -0
  66. package/dist/shared/aggregate.js +3 -3
  67. package/dist/shared/types.d.ts +28 -0
  68. package/dist/tty-line.d.ts +0 -4
  69. package/dist/util.d.ts +23 -0
  70. package/docs-site/zh/concepts/adapter.mdx +24 -6
  71. package/docs-site/zh/concepts/assert.mdx +11 -10
  72. package/docs-site/zh/concepts/evals.mdx +7 -6
  73. package/docs-site/zh/concepts/experiment.mdx +1 -1
  74. package/docs-site/zh/concepts/overview.mdx +7 -7
  75. package/docs-site/zh/guides/agent-feedback-loop.mdx +35 -31
  76. package/docs-site/zh/guides/authoring.mdx +33 -0
  77. package/docs-site/zh/guides/ci-integration.mdx +23 -12
  78. package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
  79. package/docs-site/zh/guides/custom-reports.mdx +29 -34
  80. package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
  81. package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
  82. package/docs-site/zh/guides/debugging.mdx +210 -0
  83. package/docs-site/zh/guides/experiments.mdx +10 -3
  84. package/docs-site/zh/guides/fixtures.mdx +3 -1
  85. package/docs-site/zh/guides/official-adapters.mdx +27 -3
  86. package/docs-site/zh/guides/publish-report.mdx +30 -16
  87. package/docs-site/zh/guides/report-components.mdx +49 -37
  88. package/docs-site/zh/guides/reporters.mdx +2 -2
  89. package/docs-site/zh/guides/results-data.mdx +42 -8
  90. package/docs-site/zh/guides/runner.mdx +17 -7
  91. package/docs-site/zh/guides/sandbox-agent.mdx +57 -7
  92. package/docs-site/zh/guides/sandbox-providers.mdx +258 -10
  93. package/docs-site/zh/guides/scoring-guide.mdx +4 -4
  94. package/docs-site/zh/guides/viewing-results.mdx +85 -41
  95. package/docs-site/zh/guides/write-experiment.mdx +5 -3
  96. package/docs-site/zh/guides/write-send.mdx +19 -2
  97. package/docs-site/zh/index.mdx +1 -1
  98. package/docs-site/zh/reference/builtin-agents.mdx +27 -0
  99. package/docs-site/zh/reference/capabilities.mdx +2 -2
  100. package/docs-site/zh/reference/cli.mdx +35 -9
  101. package/docs-site/zh/reference/define-agent.mdx +60 -5
  102. package/docs-site/zh/reference/define-config.mdx +1 -1
  103. package/docs-site/zh/reference/define-eval.mdx +42 -9
  104. package/docs-site/zh/reference/events.mdx +2 -2
  105. package/docs-site/zh/reference/expect.mdx +36 -6
  106. package/package.json +5 -1
  107. package/src/agents/ai-sdk-otel.test.ts +1 -0
  108. package/src/agents/ai-sdk.test.ts +3 -0
  109. package/src/agents/ai-sdk.ts +3 -0
  110. package/src/agents/bub-install-spec.test.ts +34 -0
  111. package/src/agents/bub-install-spec.ts +32 -0
  112. package/src/agents/bub.ts +31 -32
  113. package/src/agents/claude-code.test.ts +130 -9
  114. package/src/agents/claude-code.ts +76 -4
  115. package/src/agents/codex.test.ts +189 -40
  116. package/src/agents/codex.ts +155 -14
  117. package/src/agents/coding-cli-versions.test.ts +15 -0
  118. package/src/agents/coding-cli-versions.ts +3 -0
  119. package/src/agents/index.ts +11 -0
  120. package/src/agents/langgraph.test.ts +204 -0
  121. package/src/agents/langgraph.ts +495 -0
  122. package/src/agents/marketplace.ts +85 -0
  123. package/src/agents/native-config.test.ts +179 -0
  124. package/src/agents/native-config.ts +267 -0
  125. package/src/agents/openai-compat.test.ts +1 -0
  126. package/src/agents/openclaw.test.ts +31 -0
  127. package/src/agents/openclaw.ts +171 -0
  128. package/src/agents/plugin-config.test.ts +1 -0
  129. package/src/agents/sdk-streams.test.ts +79 -0
  130. package/src/agents/sdk-streams.ts +55 -10
  131. package/src/agents/skills.test.ts +1 -0
  132. package/src/agents/streaming.test.ts +3 -9
  133. package/src/agents/types.ts +73 -6
  134. package/src/agents/ui-message-stream.test.ts +3 -0
  135. package/src/cli.ts +411 -108
  136. package/src/context/context.test.ts +51 -12
  137. package/src/context/context.ts +161 -29
  138. package/src/context/session.test.ts +1 -0
  139. package/src/context/session.ts +114 -6
  140. package/src/context/types.ts +30 -12
  141. package/src/define.test.ts +13 -8
  142. package/src/define.ts +25 -4
  143. package/src/expect/index.ts +53 -23
  144. package/src/i18n/en.ts +65 -4
  145. package/src/i18n/zh-CN.ts +66 -4
  146. package/src/o11y/cost.test.ts +1 -0
  147. package/src/o11y/execution-tree.test.ts +1 -20
  148. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  149. package/src/o11y/otlp/parse.test.ts +1 -0
  150. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  151. package/src/o11y/parsers/bub.test.ts +1 -0
  152. package/src/o11y/parsers/claude-code.test.ts +1 -34
  153. package/src/o11y/parsers/openclaw.test.ts +154 -0
  154. package/src/o11y/parsers/openclaw.ts +310 -0
  155. package/src/o11y/prices.json +746 -311
  156. package/src/o11y/tool-names.test.ts +1 -0
  157. package/src/o11y/types.ts +16 -2
  158. package/src/report/aggregate.ts +34 -5
  159. package/src/report/built-in-user-parity.test.tsx +127 -173
  160. package/src/report/built-ins/experiment-comparison.tsx +179 -0
  161. package/src/report/built-ins/index.ts +7 -2
  162. package/src/report/components.tsx +11 -3
  163. package/src/report/compute.ts +80 -74
  164. package/src/report/dual-render.test.tsx +222 -91
  165. package/src/report/flag.ts +30 -2
  166. package/src/report/format.ts +36 -27
  167. package/src/report/index.ts +23 -6
  168. package/src/report/locale.ts +49 -6
  169. package/src/report/metrics.ts +68 -15
  170. package/src/report/primitives.tsx +6 -0
  171. package/src/report/react/AttemptList.tsx +9 -36
  172. package/src/report/react/EvalList.tsx +0 -0
  173. package/src/report/react/ExperimentComparison.tsx +68 -0
  174. package/src/report/react/ExperimentList.tsx +173 -55
  175. package/src/report/react/MetricScatter.tsx +13 -25
  176. package/src/report/react/chart-math.test.ts +85 -0
  177. package/src/report/react/chart-math.ts +101 -22
  178. package/src/report/react/enhance.js +72 -1
  179. package/src/report/react/fixtures.ts +34 -21
  180. package/src/report/react/format.ts +1 -1
  181. package/src/report/react/index.tsx +0 -1
  182. package/src/report/react/render.test.tsx +30 -69
  183. package/src/report/react/styles.css +112 -14
  184. package/src/report/report.test.ts +308 -105
  185. package/src/report/report.ts +6 -2
  186. package/src/report/text/faces.ts +111 -67
  187. package/src/report/text/table.ts +42 -5
  188. package/src/report/types.ts +42 -34
  189. package/src/results/annotated-source.test.ts +62 -9
  190. package/src/results/annotated-source.ts +64 -6
  191. package/src/results/attempt-evidence.test.ts +9 -7
  192. package/src/results/attempt-evidence.ts +15 -8
  193. package/src/results/attempt-source.ts +6 -3
  194. package/src/results/copy.ts +145 -55
  195. package/src/results/host-equivalence.test.ts +11 -9
  196. package/src/results/index.ts +2 -0
  197. package/src/results/locator.test.ts +1 -22
  198. package/src/results/open.ts +7 -1
  199. package/src/results/publish.ts +149 -0
  200. package/src/results/results.test.ts +85 -51
  201. package/src/results/truncate.ts +90 -0
  202. package/src/results/types.ts +7 -0
  203. package/src/results/writer.ts +31 -13
  204. package/src/runner/attempt.test.ts +138 -7
  205. package/src/runner/attempt.ts +603 -104
  206. package/src/runner/discover.test.ts +47 -0
  207. package/src/runner/discover.ts +36 -2
  208. package/src/runner/eval-source.test.ts +1 -27
  209. package/src/runner/feedback/agent.test.ts +504 -0
  210. package/src/runner/feedback/agent.ts +409 -0
  211. package/src/runner/feedback/ci.test.ts +562 -0
  212. package/src/runner/feedback/ci.ts +401 -0
  213. package/src/runner/feedback/coordinator.test.ts +317 -0
  214. package/src/runner/feedback/coordinator.ts +397 -0
  215. package/src/runner/feedback/failure.ts +40 -0
  216. package/src/runner/feedback/human.test.ts +616 -0
  217. package/src/runner/feedback/human.ts +535 -0
  218. package/src/runner/feedback/index.ts +66 -0
  219. package/src/runner/feedback/io.ts +78 -0
  220. package/src/runner/feedback/profile.test.ts +50 -0
  221. package/src/runner/feedback/profile.ts +58 -0
  222. package/src/runner/feedback/reducer.test.ts +395 -0
  223. package/src/runner/feedback/reducer.ts +260 -0
  224. package/src/runner/feedback/renderer.ts +82 -0
  225. package/src/runner/feedback/sink.ts +203 -0
  226. package/src/runner/feedback/testing.ts +106 -0
  227. package/src/runner/ledger.test.ts +230 -0
  228. package/src/runner/ledger.ts +329 -0
  229. package/src/runner/report.test.ts +128 -3
  230. package/src/runner/report.ts +33 -9
  231. package/src/runner/reporters/artifacts.ts +8 -2
  232. package/src/runner/reporters/braintrust.test.ts +8 -7
  233. package/src/runner/reporters/braintrust.ts +9 -2
  234. package/src/runner/reporters/index.ts +2 -2
  235. package/src/runner/reporters/json.test.ts +162 -0
  236. package/src/runner/reporters/json.ts +35 -8
  237. package/src/runner/reporters/shared.ts +1 -5
  238. package/src/runner/run.test.ts +760 -3
  239. package/src/runner/run.ts +242 -36
  240. package/src/runner/sandbox-prep.ts +3 -42
  241. package/src/runner/timing.ts +158 -0
  242. package/src/runner/types.ts +518 -22
  243. package/src/sandbox/checkpoint.test.ts +55 -0
  244. package/src/sandbox/checkpoint.ts +29 -8
  245. package/src/sandbox/cli-commands.ts +407 -0
  246. package/src/sandbox/docker.ts +115 -16
  247. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  248. package/src/sandbox/e2b-agent-template.ts +94 -0
  249. package/src/sandbox/e2b.ts +74 -9
  250. package/src/sandbox/errors.ts +111 -4
  251. package/src/sandbox/index.ts +2 -0
  252. package/src/sandbox/io-retry.test.ts +58 -0
  253. package/src/sandbox/io-retry.ts +45 -0
  254. package/src/sandbox/keep-registry.test.ts +86 -0
  255. package/src/sandbox/keep-registry.ts +142 -0
  256. package/src/sandbox/keep.ts +178 -0
  257. package/src/sandbox/paths.test.ts +1 -0
  258. package/src/sandbox/paths.ts +19 -8
  259. package/src/sandbox/registry.ts +20 -3
  260. package/src/sandbox/resolve.ts +76 -11
  261. package/src/sandbox/retry.test.ts +70 -0
  262. package/src/sandbox/retry.ts +46 -4
  263. package/src/sandbox/types.ts +44 -6
  264. package/src/sandbox/vercel.ts +43 -20
  265. package/src/scoring/collector.ts +60 -17
  266. package/src/scoring/coverage.ts +95 -0
  267. package/src/scoring/diff.ts +81 -0
  268. package/src/scoring/display.test.ts +121 -0
  269. package/src/scoring/display.ts +133 -0
  270. package/src/scoring/evidence.test.ts +189 -0
  271. package/src/scoring/judge.test.ts +142 -0
  272. package/src/scoring/judge.ts +15 -18
  273. package/src/scoring/scoped.ts +217 -50
  274. package/src/scoring/types.ts +117 -20
  275. package/src/scoring/verdict.ts +16 -4
  276. package/src/shared/aggregate.ts +3 -2
  277. package/src/shared/types.ts +31 -0
  278. package/src/show/compose.ts +2 -2
  279. package/src/show/index.ts +29 -16
  280. package/src/show/render.ts +626 -308
  281. package/src/show/show.test.ts +251 -36
  282. package/src/tty-line.ts +8 -26
  283. package/src/util.test.ts +1 -0
  284. package/src/util.ts +41 -0
  285. package/src/view/app/components/AttemptModal.tsx +153 -2
  286. package/src/view/app/components/CodeView.tsx +32 -11
  287. package/src/view/app/components/CopyControls.tsx +2 -2
  288. package/src/view/app/i18n.ts +6 -0
  289. package/src/view/app/lib/attempt-route.test.ts +1 -0
  290. package/src/view/app/lib/verdict.ts +7 -9
  291. package/src/view/artifact-serving.test.ts +2 -1
  292. package/src/view/client-dist/app.css +1 -1
  293. package/src/view/client-dist/app.js +17 -17
  294. package/src/view/data.test.ts +2 -1
  295. package/src/view/data.ts +17 -7
  296. package/src/view/index.ts +12 -1
  297. package/src/view/server.ts +2 -0
  298. package/src/view/shared/types.ts +1 -1
  299. package/src/view/styles.css +3 -0
  300. package/src/view/view-report.test.ts +11 -10
  301. package/dist/o11y/execution-tree.d.ts +0 -103
  302. package/dist/o11y/otlp/select.d.ts +0 -22
  303. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
  304. package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
  305. package/dist/results/annotated-source.d.ts +0 -61
  306. package/dist/results/attempt-evidence.d.ts +0 -69
  307. package/dist/results/attempt-source.d.ts +0 -15
  308. package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
  309. package/src/runner/reporters/console.ts +0 -70
  310. package/src/runner/reporters/live.test.ts +0 -56
  311. package/src/runner/reporters/live.ts +0 -247
  312. package/src/runner/reporters/quiet.test.ts +0 -66
  313. package/src/runner/reporters/quiet.ts +0 -49
  314. package/src/runner/reporters/table.ts +0 -277
@@ -1,30 +1,54 @@
1
+ // cases: docs/engineering/unit-tests/adapters/cases.md
1
2
  // Codex native plugin 安装(installPlugins)的单测:单 plugin 的命令构造、同名 marketplace
2
- // 的去重、ref 钉定走 `--ref`(不像 claude-code 需要先 clone)、resolvedVersion 取不到时
3
- // 优雅省略(含 `codex plugin list --json` 的真实输出形状 `{ installed: [...], available: [...] }`,
4
- // 字段名 `pluginId`——实测 codex-cli 0.144.1,2026-07-13 native plugin e2e 复现过按裸数组 /
5
- // `{ plugins: [...] }` 猜形状的旧版本恒返回 undefined,见
6
- // memory/native-plugin-marketplace-name-not-caller-assignable.md)、marketplace/plugin 安装
7
- // 失败的报错。风格与 src/agents/skills.test.ts、src/agents/claude-code.test.ts 一致,不另起一套。
3
+ // 的去重、add 后的注册名回读校验、ref 钉定走 `--ref`(不像 claude-code 需要先 clone)、
4
+ // resolvedVersion 取不到时优雅省略(含 `codex plugin list --json` 的真实输出形状
5
+ // `{ installed: [...], available: [...] }`,字段名 `pluginId`——实测 codex-cli 0.144.1,
6
+ // 2026-07-13 native plugin e2e 复现过按裸数组 / `{ plugins: [...] }` 猜形状的旧版本恒返回
7
+ // undefined,见 memory/native-plugin-marketplace-name-not-caller-assignable.md)、
8
+ // marketplace/plugin 安装失败的报错;外加 configFile(原生配置文件)的 setup 流。
9
+ // 风格与 src/agents/skills.test.ts、src/agents/claude-code.test.ts 一致,不另起一套。
8
10
  // 定稿见 docs/feature/adapters/architecture/coding-agent-extensions.md。
11
+ // bug: memory/native-plugin-marketplace-name-not-caller-assignable.md
9
12
 
10
- import { describe, expect, it } from "vitest";
11
- import { installPlugins, type CodexPluginSpec } from "./codex.ts";
12
- import type { CommandResult, Sandbox, SandboxFile } from "../types.ts";
13
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
14
+ import { createHash } from "node:crypto";
15
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
16
+ import { tmpdir } from "node:os";
17
+ import { join } from "node:path";
18
+ import { codexAgent, installPlugins, type CodexPluginSpec } from "./codex.ts";
19
+ import { createAgentSession } from "../context/session.ts";
20
+ import type { AgentContext, AgentSetupManifest, CommandOptions, CommandResult, Sandbox, SandboxFile } from "../types.ts";
13
21
 
14
- /** 内存沙箱:runShell 记命令(可按命令包含的子串打脚本化输出) */
22
+ /** 内存沙箱:runShell 记命令(可按命令包含的子串打脚本化输出),uploadFile / writeFiles 记内容。 */
15
23
  class FakeSandbox implements Partial<Sandbox> {
16
24
  readonly workdir = "/workspace";
17
25
  readonly sandboxId = "fake";
18
26
  readonly otlpHost = null;
19
27
  readonly commands: string[] = [];
28
+ readonly uploads: { path: string; content: Buffer }[] = [];
29
+ readonly written: Record<string, string> = {};
20
30
  script: { match: string; result: (cmd: string) => Partial<CommandResult> }[] = [];
21
31
 
22
- async runShell(script: string): Promise<CommandResult> {
32
+ async runShell(script: string, opts: CommandOptions = {}): Promise<CommandResult> {
23
33
  this.commands.push(script);
24
34
  const hit = this.script.find((s) => script.includes(s.match));
25
- return { stdout: "", stderr: "", exitCode: 0, ...hit?.result(script) };
35
+ const result = { stdout: "", stderr: "", exitCode: 0, ...hit?.result(script) };
36
+ // 故意从 JSONL 中间切开,验证 adapter 自己负责跨 chunk 拼行,不依赖 provider 恰好
37
+ // 一行一回调的幸运时序。
38
+ if (result.stdout) {
39
+ const split = Math.max(1, Math.floor(result.stdout.length / 2));
40
+ await opts.onStdout?.(result.stdout.slice(0, split));
41
+ await opts.onStdout?.(result.stdout.slice(split));
42
+ }
43
+ if (result.stderr) await opts.onStderr?.(result.stderr);
44
+ return result;
45
+ }
46
+ async writeFiles(files: Record<string, string>): Promise<void> {
47
+ Object.assign(this.written, files);
48
+ }
49
+ async uploadFile(path: string, content: Buffer): Promise<void> {
50
+ this.uploads.push({ path, content });
26
51
  }
27
- async writeFiles(): Promise<void> {}
28
52
  async uploadFiles(_files: SandboxFile[]): Promise<void> {}
29
53
  async fileExists(): Promise<boolean> {
30
54
  return false;
@@ -36,7 +60,14 @@ class FakeSandbox implements Partial<Sandbox> {
36
60
 
37
61
  const sb = (s?: FakeSandbox["script"]): FakeSandbox => {
38
62
  const box = new FakeSandbox();
39
- if (s) box.script = s;
63
+ // add 后的注册名回读默认回到配置名(校验通过);mismatch 用例把自己的脚本放前面覆盖。
64
+ box.script = [
65
+ ...(s ?? []),
66
+ {
67
+ match: "codex plugin marketplace list --json",
68
+ result: () => ({ stdout: JSON.stringify({ marketplaces: [{ name: "acme" }] }) }),
69
+ },
70
+ ];
40
71
  return box;
41
72
  };
42
73
  const asSandbox = (box: FakeSandbox): Sandbox => box as unknown as Sandbox;
@@ -51,6 +82,7 @@ describe("codex installPlugins · 命令构造", () => {
51
82
 
52
83
  expect(box.commands).toEqual([
53
84
  "codex plugin marketplace add 'acme/codex-plugins'",
85
+ "codex plugin marketplace list --json",
54
86
  "codex plugin add 'repo-map@acme'",
55
87
  "codex plugin list --json --marketplace 'acme'",
56
88
  ]);
@@ -126,32 +158,6 @@ describe("codex installPlugins · 命令构造", () => {
126
158
  expect(out[0]?.resolvedVersion).toBe("2.1.0");
127
159
  });
128
160
 
129
- it("resolvedVersion:list 输出裸数组(非当前实测形状,但保留兼容)、按 id 命中 → manifest 记版本", async () => {
130
- const box = sb([
131
- {
132
- match: "codex plugin list --json",
133
- result: () => ({ stdout: JSON.stringify([{ id: "repo-map@acme", version: "2.0.0" }]) }),
134
- },
135
- ]);
136
- const out = await installPlugins(asSandbox(box), [
137
- { marketplace: { name: "acme", source: "acme/codex-plugins" }, name: "repo-map" },
138
- ]);
139
- expect(out[0]?.resolvedVersion).toBe("2.0.0");
140
- });
141
-
142
- it("resolvedVersion 取不到时优雅省略:list 输出旧的 `{ plugins: [...] }` 猜测形状(实测证伪,不是真实 CLI 输出)不再命中", async () => {
143
- const box = sb([
144
- {
145
- match: "codex plugin list --json",
146
- result: () => ({ stdout: JSON.stringify({ plugins: [{ name: "repo-map", version: "2.1.0" }] }) }),
147
- },
148
- ]);
149
- const out = await installPlugins(asSandbox(box), [
150
- { marketplace: { name: "acme", source: "acme/codex-plugins" }, name: "repo-map" },
151
- ]);
152
- expect(out[0]).not.toHaveProperty("resolvedVersion");
153
- });
154
-
155
161
  it("resolvedVersion 取不到时优雅省略(不阻断安装):list 命令失败 → manifest 里没有 resolvedVersion 键", async () => {
156
162
  const box = sb([{ match: "codex plugin list --json", result: () => ({ exitCode: 1 }) }]);
157
163
  const out = await installPlugins(asSandbox(box), [
@@ -190,3 +196,146 @@ describe("codex installPlugins · 失败语义", () => {
190
196
  ).rejects.toThrow(/repo-map/);
191
197
  });
192
198
  });
199
+
200
+ describe("codex installPlugins · marketplace 名回读校验", () => {
201
+ const plugins: CodexPluginSpec[] = [
202
+ { marketplace: { name: "acme", source: "acme/codex-plugins" }, name: "repo-map" },
203
+ ];
204
+
205
+ it("add 静默注册成别的名字(仓库 manifest 的真实 name)→ 立刻抛带两个名字的错误,不走到 plugin add", async () => {
206
+ const box = sb([
207
+ {
208
+ match: "codex plugin marketplace list --json",
209
+ result: () => ({ stdout: JSON.stringify({ marketplaces: [{ name: "duyet-claude-plugins" }] }) }),
210
+ },
211
+ ]);
212
+ const err = await installPlugins(asSandbox(box), plugins).catch((e: Error) => e);
213
+ expect(err).toBeInstanceOf(Error);
214
+ expect((err as Error).message).toContain("acme");
215
+ expect((err as Error).message).toContain("duyet-claude-plugins");
216
+ expect(box.commands.some((c) => c.startsWith("codex plugin add"))).toBe(false);
217
+ });
218
+
219
+ it("回读命令失败 / 输出解析不出 → 按回读失败抛错,不静默放行", async () => {
220
+ const failing = sb([
221
+ { match: "codex plugin marketplace list --json", result: () => ({ exitCode: 1, stderr: "boom" }) },
222
+ ]);
223
+ await expect(installPlugins(asSandbox(failing), plugins)).rejects.toThrow(/marketplace/);
224
+
225
+ const garbled = sb([{ match: "codex plugin marketplace list --json", result: () => ({ stdout: "not json" }) }]);
226
+ await expect(installPlugins(asSandbox(garbled), plugins)).rejects.toThrow(/acme/);
227
+ });
228
+ });
229
+
230
+ describe("codexAgent configFile · setup", () => {
231
+ let root: string;
232
+ let cwdBefore: string;
233
+
234
+ beforeEach(async () => {
235
+ root = await mkdtemp(join(tmpdir(), "niceeval-codex-config-"));
236
+ cwdBefore = process.cwd();
237
+ process.chdir(root); // configFile 相对项目根 = process.cwd()(docs 定稿口径)
238
+ });
239
+
240
+ afterEach(async () => {
241
+ process.chdir(cwdBefore);
242
+ await rm(root, { recursive: true, force: true });
243
+ });
244
+
245
+ const ctx = { model: "gpt-5", reasoningEffort: "high", flags: {} } as AgentContext;
246
+
247
+ it("用户 config.toml 原始字节夹在 Adapter 顶层键与 Adapter 表之间(TOML 没有回到根表的语法);manifest 记路径 + SHA-256,不落正文", async () => {
248
+ const body = '#:schema https://developers.openai.com/codex/config-schema.json\nweb_search = "disabled"\n';
249
+ await mkdir(join(root, "configs"), { recursive: true });
250
+ await writeFile(join(root, "configs/no-web.toml"), body);
251
+
252
+ const box = sb();
253
+ await codexAgent({ apiKey: "k", baseUrl: "https://s2a.example.com/v1", configFile: "configs/no-web.toml" }).setup!(
254
+ asSandbox(box),
255
+ ctx,
256
+ );
257
+
258
+ // 1) Adapter 顶层键先写(不带任何表头)
259
+ const topLevelIdx = box.commands.findIndex((c) => c.includes("cat > ~/.codex/config.toml"));
260
+ expect(topLevelIdx).toBeGreaterThan(-1);
261
+ const topLevel = box.commands[topLevelIdx]!;
262
+ expect(topLevel).toContain('model = "gpt-5"');
263
+ expect(topLevel).toContain('model_reasoning_effort = "high"');
264
+ expect(topLevel).not.toContain("[model_providers");
265
+
266
+ // 2) 用户文件原始字节经 uploadFile 追加(不走会改字节的 heredoc / 重新序列化)
267
+ expect(box.uploads).toHaveLength(1);
268
+ expect(box.uploads[0]!.content.toString("utf8")).toBe(body);
269
+ const appendIdx = box.commands.findIndex((c) => c.includes(`cat ${box.uploads[0]!.path} >> ~/.codex/config.toml`));
270
+ expect(appendIdx).toBeGreaterThan(topLevelIdx);
271
+
272
+ // 3) Adapter 的表([model_providers.s2a])最后追加
273
+ const tableIdx = box.commands.findIndex((c) => c.includes("[model_providers.s2a]"));
274
+ expect(tableIdx).toBeGreaterThan(appendIdx);
275
+ expect(box.commands[tableIdx]!).toContain('base_url = "https://s2a.example.com/v1"');
276
+
277
+ const manifest = JSON.parse(box.written["__niceeval__/agent-setup.json"]!) as AgentSetupManifest;
278
+ expect(manifest.nativeConfigFile).toEqual({
279
+ agent: "codex",
280
+ path: "configs/no-web.toml",
281
+ sha256: createHash("sha256").update(body).digest("hex"),
282
+ });
283
+ expect(box.written["__niceeval__/agent-setup.json"]).not.toContain("web_search");
284
+ });
285
+
286
+ it("保留键([mcp_servers.x] 表头)出现在文件里 → setup 报错点名冲突键,不写沙箱配置", async () => {
287
+ await writeFile(join(root, "bad.toml"), '[mcp_servers.browser]\ncommand = "npx"\n');
288
+ const box = sb();
289
+ await expect(
290
+ codexAgent({ apiKey: "k", configFile: "bad.toml" }).setup!(asSandbox(box), ctx),
291
+ ).rejects.toThrow(/mcp_servers/);
292
+ expect(box.uploads).toHaveLength(0);
293
+ expect(box.commands.some((c) => c.includes("config.toml"))).toBe(false);
294
+ });
295
+
296
+ it("没配 configFile 时布局与从前一致:一次 heredoc 写完顶层键 + provider 表,无 uploadFile", async () => {
297
+ const box = sb();
298
+ await codexAgent({ apiKey: "k", baseUrl: "https://s2a.example.com/v1" }).setup!(asSandbox(box), ctx);
299
+ const write = box.commands.find((c) => c.includes("cat > ~/.codex/config.toml"))!;
300
+ expect(write).toContain('model_reasoning_effort = "high"');
301
+ expect(write).toContain("[model_providers.s2a]");
302
+ expect(box.uploads).toHaveLength(0);
303
+ expect(box.written["__niceeval__/agent-setup.json"]).toBeUndefined();
304
+ });
305
+ });
306
+
307
+ describe("codexAgent · live step feedback", () => {
308
+ it("逐块消费 codex --json stdout,把 tool 与 assistant step 送进 progress,完整 transcript 仍正常解析", async () => {
309
+ const stdout = [
310
+ JSON.stringify({ type: "thread.started", thread_id: "thread-1" }),
311
+ JSON.stringify({ type: "item.started", item: { type: "command_execution", id: "cmd-1", command: "pnpm test" } }),
312
+ JSON.stringify({ type: "item.completed", item: { type: "command_execution", id: "cmd-1", command: "pnpm test", exit_code: 0 } }),
313
+ JSON.stringify({ type: "item.completed", item: { type: "agent_message", text: "All tests passed." } }),
314
+ ].join("\n") + "\n";
315
+ const box = sb([{ match: "codex exec", result: () => ({ stdout }) }]);
316
+ const progress: string[] = [];
317
+ const ctx: AgentContext = {
318
+ signal: new AbortController().signal,
319
+ flags: {},
320
+ sandbox: asSandbox(box),
321
+ session: createAgentSession(),
322
+ progress: (update) => progress.push(update.message),
323
+ diagnostic() {},
324
+ log() {},
325
+ };
326
+
327
+ const turn = await codexAgent({ apiKey: "test-key" }).send!({ text: "run the tests" }, ctx);
328
+
329
+ expect(progress).toEqual([
330
+ "tool: pnpm test",
331
+ "tool: pnpm test · completed",
332
+ "assistant: All tests passed.",
333
+ ]);
334
+ expect(turn.events).toMatchObject([
335
+ { type: "action.called", name: "command_execution", tool: "shell" },
336
+ { type: "action.result", callId: "cmd-1", status: "completed" },
337
+ { type: "message", role: "assistant", text: "All tests passed." },
338
+ ]);
339
+ expect(ctx.session.id).toBe("thread-1");
340
+ });
341
+ });
@@ -1,3 +1,4 @@
1
+ import { completeCoverage } from "../scoring/coverage.ts";
1
2
  import { defineSandboxAgent } from "../define.ts";
2
3
  import { requireEnv, getEnv } from "../util.ts";
3
4
  import { shared } from "./shared.ts";
@@ -8,8 +9,16 @@ import {
8
9
  skillDiscoveryInstruction,
9
10
  } from "./skills.ts";
10
11
  import { writeAgentSetupManifest } from "./manifest.ts";
12
+ import { verifyMarketplaceName } from "./marketplace.ts";
13
+ import {
14
+ appendNativeConfigFile,
15
+ assertTomlNativeConfig,
16
+ loadNativeConfigFile,
17
+ type LoadedNativeConfig,
18
+ } from "./native-config.ts";
11
19
  import { mapCodexSpans } from "../o11y/otlp/mappers/codex.ts";
12
20
  import { t } from "../i18n/index.ts";
21
+ import { DEFAULT_CODEX_CLI_VERSION } from "./coding-cli-versions.ts";
13
22
  import type { Agent, AgentSetupManifest, McpServer, Sandbox, SkillSpec } from "../types.ts";
14
23
 
15
24
  // ───────────────────────────────────────────────────────────────────────────
@@ -24,6 +33,19 @@ import type { Agent, AgentSetupManifest, McpServer, Sandbox, SkillSpec } from ".
24
33
  /** codex 的 skill 目录(`skills` 生态的「通用」目录);codex 不原生扫描它,靠下面的发现指引。 */
25
34
  const SKILL_DIR = ".agents/skills";
26
35
 
36
+ /**
37
+ * `configFile` 的保留键:model / provider 路由 / 推理努力归 experiment 与 Adapter 的生成层,
38
+ * MCP 表与 OTel 导出归 Adapter。清单定稿见 docs/feature/adapters/sdk/codex-cli/README.md。
39
+ */
40
+ const RESERVED_CONFIG_KEYS = [
41
+ "model",
42
+ "model_provider",
43
+ "model_providers",
44
+ "model_reasoning_effort",
45
+ "mcp_servers",
46
+ "otel",
47
+ ] as const;
48
+
27
49
  /**
28
50
  * Codex 的原生 Plugin —— **只属于 Codex**,不能传给 Claude Code(它有自己的
29
51
  * {@link import("./claude-code.ts").ClaudeCodePluginSpec})。字段当前相似不等于同一个类型:
@@ -60,6 +82,16 @@ export interface CodexConfig {
60
82
  skills?: SkillSpec[];
61
83
  /** Codex 原生 Plugin(先连 Marketplace,再从中装指定 Plugin)。 */
62
84
  plugins?: CodexPluginSpec[];
85
+ /**
86
+ * 一份完整的 Codex `config.toml`(官方 TOML 格式)在本地项目里的路径 —— 相对运行
87
+ * niceeval 的项目根(含 `niceeval.config.ts` 的目录)解析,不是 Sandbox 内路径;只接受
88
+ * 项目根内的相对路径,包含 `..` 的路径、绝对路径、`~` 路径和解析后逃出项目根的符号链接
89
+ * 都在 setup 阶段报错。原始字节原样并入沙箱里原本为空的用户级 `~/.codex/config.toml`
90
+ * (不继承宿主机配置、不解析后重写);保留键 `model`、`model_provider`、`model_providers`、
91
+ * `model_reasoning_effort`、`mcp_servers`、`otel` 出现在文件里 setup 报错。manifest 只记
92
+ * 项目相对路径与字节 SHA-256,不落正文。
93
+ */
94
+ configFile?: string;
63
95
  }
64
96
 
65
97
  export function codexAgent(config?: CodexConfig): Agent {
@@ -68,11 +100,23 @@ export function codexAgent(config?: CodexConfig): Agent {
68
100
 
69
101
  return defineSandboxAgent({
70
102
  name: "codex",
103
+ // 官方 adapter:transcript 经生命周期 fixture 验证,全通道 complete。
104
+ coverage: completeCoverage,
71
105
  spanMapper: mapCodexSpans,
72
106
 
73
107
  async setup(sb, ctx) {
74
108
  // 预制模板已把 codex 烘焙进镜像(PATH 上)就跳过安装;否则 npm 全局装。
75
- await sb.runShell("command -v codex >/dev/null 2>&1 || npm install -g @openai/codex");
109
+ await sb.runShell(
110
+ `command -v codex >/dev/null 2>&1 || npm install -g @openai/codex@${DEFAULT_CODEX_CLI_VERSION}`,
111
+ );
112
+
113
+ // 用户的原生配置文件:本地读原始字节 → 验 TOML 语法与保留键。字节 SHA-256 进
114
+ // manifest 与安装 checkpoint key(见 native-config.ts 的 nativeConfigCheckpointItem)。
115
+ let nativeConfig: LoadedNativeConfig | undefined;
116
+ if (config?.configFile !== undefined) {
117
+ nativeConfig = await loadNativeConfigFile({ agent: "codex", field: "configFile", path: config.configFile });
118
+ assertTomlNativeConfig(nativeConfig, { agent: "codex", field: "configFile", reservedKeys: RESERVED_CONFIG_KEYS });
119
+ }
76
120
 
77
121
  // model 归属:实验决定(ctx.model);省略时不写 model 行,交给 codex CLI 原生默认,
78
122
  // 不在 adapter 里硬编码一个会过期的模型名。
@@ -80,21 +124,34 @@ export function codexAgent(config?: CodexConfig): Agent {
80
124
  const effort = ctx.reasoningEffort ?? (ctx.flags.effort as string | undefined) ?? "medium";
81
125
  const base = getBaseUrl();
82
126
 
83
- if (base) {
127
+ const topLevel = base
128
+ ? modelLine + `model_provider = "s2a"\n` + `model_reasoning_effort = "${effort}"\n`
129
+ : `${modelLine}model_reasoning_effort = "${effort}"\n`;
130
+ const providerTable = base
131
+ ? `[model_providers.s2a]\n` +
132
+ `name = "s2a"\n` +
133
+ `base_url = "${base}"\n` +
134
+ `env_key = "CODEX_API_KEY"\n` +
135
+ `wire_api = "responses"\n`
136
+ : "";
137
+
138
+ if (!nativeConfig) {
84
139
  await shared.writeFile(
85
140
  sb,
86
141
  "~/.codex/config.toml",
87
- modelLine +
88
- `model_provider = "s2a"\n` +
89
- `model_reasoning_effort = "${effort}"\n\n` +
90
- `[model_providers.s2a]\n` +
91
- `name = "s2a"\n` +
92
- `base_url = "${base}"\n` +
93
- `env_key = "CODEX_API_KEY"\n` +
94
- `wire_api = "responses"\n`,
142
+ providerTable ? `${topLevel}\n${providerTable}` : topLevel,
95
143
  );
96
144
  } else {
97
- await shared.writeFile(sb, "~/.codex/config.toml", `${modelLine}model_reasoning_effort = "${effort}"\n`);
145
+ // codex 只读一份用户级 config.toml(没有 include / 第二配置层),Adapter 生成层与
146
+ // 用户文件只能同文件分段共存。TOML 没有「回到根表」的语法,顶层键必须先于任何表头,
147
+ // 所以布局固定为:Adapter 顶层键 → 用户文件原始字节(逐字节保留,自带表随意)→
148
+ // Adapter 的表([model_providers.*] 以及后续追加的 [mcp_servers.*]、[otel])。
149
+ // 保留键校验保证两层键不重叠,用户内容不被解析重写。
150
+ await shared.writeFile(sb, "~/.codex/config.toml", topLevel);
151
+ await appendNativeConfigFile(sb, nativeConfig, "~/.codex/config.toml");
152
+ if (providerTable) {
153
+ await sb.runShell(`cat >> ~/.codex/config.toml <<'NICEEVAL_PROVIDER_EOF'\n\n${providerTable}NICEEVAL_PROVIDER_EOF\n`);
154
+ }
98
155
  }
99
156
 
100
157
  if (config?.mcpServers?.length) {
@@ -134,7 +191,16 @@ export function codexAgent(config?: CodexConfig): Agent {
134
191
  ...(s.args?.length ? { args: [...s.args] } : {}),
135
192
  }));
136
193
  }
137
- if (manifest.skills.length || manifest.nativePlugins?.length || manifest.mcpServers?.length) {
194
+ if (nativeConfig) {
195
+ // 只记来源路径与字节哈希,不落正文(任意官方配置都可能带敏感字符串)。
196
+ manifest.nativeConfigFile = { agent: "codex", path: nativeConfig.path, sha256: nativeConfig.sha256 };
197
+ }
198
+ if (
199
+ manifest.skills.length ||
200
+ manifest.nativePlugins?.length ||
201
+ manifest.mcpServers?.length ||
202
+ manifest.nativeConfigFile
203
+ ) {
138
204
  await writeAgentSetupManifest(sb, manifest);
139
205
  }
140
206
  },
@@ -164,7 +230,15 @@ export function codexAgent(config?: CodexConfig): Agent {
164
230
  ? `codex exec resume ${ctx.session.id} ${flags} ${prompt}`
165
231
  : `codex exec ${flags} ${prompt}`;
166
232
 
167
- const res = await sb.runShell(cmd, { env: { CODEX_API_KEY: getApiKey() }, stream: true });
233
+ // `codex exec --json` 持续写出 ThreadEvent JSONL。把它压成短 step 送进 runner
234
+ // progress,human dashboard 因而能显示真正的 tool / reasoning / assistant 活动;完整
235
+ // transcript 仍在命令结束后一次解析并落入 events artifact,不能让 live 文案变成第二份结果。
236
+ const onStdout = codexProgressReporter(ctx);
237
+ const res = await sb.runShell(cmd, {
238
+ env: { CODEX_API_KEY: getApiKey() },
239
+ stream: true,
240
+ onStdout,
241
+ });
168
242
 
169
243
  const raw = shared.extractJsonlFromStdout(res.stdout);
170
244
  ctx.session.capture(shared.codexThreadId(res.stdout));
@@ -176,8 +250,67 @@ export function codexAgent(config?: CodexConfig): Agent {
176
250
  });
177
251
  }
178
252
 
253
+ /** 把 Codex JSONL 的高频原始帧收敛成 dashboard 当前行的一条短 detail。 */
254
+ function codexProgressReporter(ctx: import("../types.ts").AgentContext): (chunk: string) => void {
255
+ let remainder = "";
256
+ return (chunk) => {
257
+ remainder += chunk;
258
+ const lines = remainder.split("\n");
259
+ remainder = lines.pop() ?? "";
260
+ for (const line of lines) {
261
+ const detail = codexProgressDetail(line);
262
+ if (detail) ctx.progress({ message: detail });
263
+ }
264
+ };
265
+ }
266
+
267
+ function codexProgressDetail(line: string): string | undefined {
268
+ let event: Record<string, unknown>;
269
+ try {
270
+ const parsed: unknown = JSON.parse(line);
271
+ if (!parsed || typeof parsed !== "object") return undefined;
272
+ event = parsed as Record<string, unknown>;
273
+ } catch {
274
+ return undefined;
275
+ }
276
+
277
+ const item = event.item;
278
+ if (!item || typeof item !== "object") return undefined;
279
+ const data = item as Record<string, unknown>;
280
+ const type = typeof data.type === "string" ? data.type : "";
281
+ const completed = event.type === "item.completed";
282
+ const preview = (value: unknown, limit = 96): string | undefined => {
283
+ if (typeof value !== "string") return undefined;
284
+ const text = value.replace(/\s+/g, " ").trim();
285
+ if (!text) return undefined;
286
+ return text.length > limit ? `${text.slice(0, limit - 1)}…` : text;
287
+ };
288
+
289
+ if (type === "command_execution") {
290
+ const command = preview(data.command) ?? "shell";
291
+ return completed ? `tool: ${command} · completed` : `tool: ${command}`;
292
+ }
293
+ if (type === "mcp_tool_call") {
294
+ const tool = typeof data.tool === "string" ? data.tool : "MCP";
295
+ const server = typeof data.server === "string" ? `${data.server}.` : "";
296
+ return completed ? `tool: ${server}${tool} · completed` : `tool: ${server}${tool}`;
297
+ }
298
+ if (type === "web_search") return completed ? "tool: web search · completed" : "tool: web search";
299
+ if (type === "file_change") return completed ? "tool: file change · completed" : "tool: file change";
300
+ if (type === "reasoning") {
301
+ const text = preview(data.text ?? data.content);
302
+ return text ? `thinking: ${text}` : "thinking";
303
+ }
304
+ if (type === "agent_message") {
305
+ const text = preview(data.text ?? data.message);
306
+ return text ? `assistant: ${text}` : "assistant response";
307
+ }
308
+ return undefined;
309
+ }
310
+
179
311
  /**
180
- * 先按 `marketplace.name` 连 Marketplace(同名只连一次,`--ref` 钉版本),再装指定 Plugin。
312
+ * 先按 `marketplace.name` 连 Marketplace(同名只连一次,`--ref` 钉版本,add 后回读注册
313
+ * 列表校验名字真的注册上了),再装指定 Plugin。
181
314
  * 只按 codex 自己的 marketplace / plugin 协议走 —— 与 claude-code 的实现不共用命令、不共用类型。
182
315
  */
183
316
  export async function installPlugins(
@@ -205,6 +338,14 @@ export async function installPlugins(
205
338
  }),
206
339
  );
207
340
  }
341
+ // add 静默按目标仓库 manifest 的 name 注册,错名会拖到 plugin add 才炸;
342
+ // 回读注册列表立刻校验(契约与真机复现见 marketplace.ts 顶部说明)。
343
+ await verifyMarketplaceName(sb, {
344
+ agent: "codex",
345
+ listCommand: "codex plugin marketplace list --json",
346
+ marketplace,
347
+ knownNames: connected,
348
+ });
208
349
  connected.add(marketplace.name);
209
350
  }
210
351
 
@@ -0,0 +1,15 @@
1
+ // cases: docs/engineering/unit-tests/adapters/cases.md
2
+ import { readFile } from "node:fs/promises";
3
+ import { describe, expect, it } from "vitest";
4
+ import {
5
+ DEFAULT_CLAUDE_CODE_CLI_VERSION,
6
+ DEFAULT_CODEX_CLI_VERSION,
7
+ } from "./coding-cli-versions.ts";
8
+
9
+ describe("official coding-agent versions", () => {
10
+ it("keeps the non-TypeScript Docker recipe in sync", async () => {
11
+ const dockerfile = await readFile(new URL("../../sandbox/docker/Dockerfile", import.meta.url), "utf8");
12
+ expect(dockerfile).toContain(`ARG CODEX_VERSION=${DEFAULT_CODEX_CLI_VERSION}`);
13
+ expect(dockerfile).toContain(`ARG CLAUDE_CODE_VERSION=${DEFAULT_CLAUDE_CODE_CLI_VERSION}`);
14
+ });
15
+ });
@@ -0,0 +1,3 @@
1
+ /** Coding-agent CLI versions used by runtime fallback installs and official sandbox recipes. */
2
+ export const DEFAULT_CODEX_CLI_VERSION = "0.144.1";
3
+ export const DEFAULT_CLAUDE_CODE_CLI_VERSION = "2.1.207";
@@ -4,6 +4,11 @@ export { defineAgent, defineSandboxAgent } from "../define.ts";
4
4
  export { shared } from "./shared.ts";
5
5
  export type { Shared } from "./shared.ts";
6
6
 
7
+ // 证据覆盖声明:官方 SDK 适配器声明全通道 complete 用 completeCoverage;
8
+ // 手写映射按实际情况声明(见 docs/feature/adapters/architecture/evidence.md)。
9
+ export { completeCoverage } from "../scoring/coverage.ts";
10
+ export type { CoverageStatus, CoverageDeclaration, EvidenceCoverage } from "../types.ts";
11
+
7
12
  // span → canonical GenAI 归一(只服务瀑布图,不喂断言)。私有埋点写自己的 spanMapper 时用:
8
13
  // tagSpan 把判定写回 span(原属性只增不改),heuristicTag 是通用兜底判定;mapCodexSpans 是
9
14
  // 现成的参考实现(无侵入接 codex 后端时直接声明 `spanMapper: mapCodexSpans`)。
@@ -43,6 +48,10 @@ export type {
43
48
  CodexThreadStream,
44
49
  } from "./sdk-streams.ts";
45
50
 
51
+ // LangGraph 官方事件流转换器(不绑定 transport,不提供 langGraphAgent 工厂)。
52
+ export { fromLangGraphEvents } from "./langgraph.ts";
53
+ export type { LangGraphEventLike, LangGraphContentBlockLike, LangGraphStream } from "./langgraph.ts";
54
+
46
55
  // 通用「拼装方式」件:逐帧驱动循环、逐 token/参数增量累加器。见 docs-site/zh/guides/write-send.mdx——
47
56
  // 这些和任何具体协议无关,自己写 adapter 时优先拿这些拼,只有 transport(怎么发)与
48
57
  // 「帧类型 → 操作」这张映射表才是真正要手写的。会话续接与 HITL 停轮现场不再是可选件,
@@ -71,9 +80,11 @@ export { BUILTIN_AGENTS } from "./builtin.ts";
71
80
  export { claudeCodeAgent } from "./claude-code.ts";
72
81
  export { codexAgent } from "./codex.ts";
73
82
  export { bubAgent } from "./bub.ts";
83
+ export { openClawAgent } from "./openclaw.ts";
74
84
  export type { ClaudeCodeConfig, ClaudeCodePluginSpec } from "./claude-code.ts";
75
85
  export type { CodexConfig, CodexPluginSpec } from "./codex.ts";
76
86
  export type { BubConfig, PythonPluginSpec } from "./bub.ts";
87
+ export type { OpenClawConfig } from "./openclaw.ts";
77
88
 
78
89
  // 安装 manifest 的落点:adapter 写(shared.writeAgentSetup),运行器读并抬成 attempt artifact。
79
90
  export { AGENT_SETUP_MANIFEST_PATH } from "./manifest.ts";