niceeval 0.5.3 → 0.6.0
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 +55 -0
- package/dist/agents/types.d.ts +317 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +178 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +618 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +32 -0
- package/dist/report/format.js +126 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +83 -0
- package/dist/report/locale.js +160 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +20 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +10 -0
- package/dist/report/react/ExperimentList.js +15 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +100 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +393 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +503 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +420 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/annotated-source.d.ts +61 -0
- package/dist/results/attempt-evidence.d.ts +69 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +9 -4
- package/docs-site/zh/concepts/assert.mdx +8 -8
- package/docs-site/zh/concepts/evals.mdx +5 -5
- package/docs-site/zh/concepts/experiment.mdx +3 -1
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +2 -2
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -0
- package/docs-site/zh/guides/fixtures.mdx +1 -1
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +4 -4
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +35 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +10 -5
- package/docs-site/zh/guides/sandbox-providers.mdx +102 -0
- package/docs-site/zh/guides/viewing-results.mdx +199 -79
- package/docs-site/zh/guides/write-experiment.mdx +23 -1
- package/docs-site/zh/guides/write-send.mdx +2 -2
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +24 -12
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +7 -1
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/package.json +10 -6
- package/src/agents/bub.ts +134 -43
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +11 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +62 -2
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/context.ts +3 -0
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +26 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +82 -0
- package/src/define.ts +72 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +36 -15
- package/src/i18n/zh-CN.ts +39 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +643 -0
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +223 -50
- package/src/report/compute.ts +358 -53
- package/src/report/dual-render.test.tsx +1125 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +50 -2
- package/src/report/index.ts +48 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +206 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +115 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +108 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +136 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +129 -0
- package/src/report/react/fixtures.ts +163 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +22 -6
- package/src/report/react/render.test.tsx +164 -31
- package/src/report/react/styles.css +405 -213
- package/src/report/report.test.ts +403 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +303 -95
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +196 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +97 -15
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +69 -28
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +125 -94
- package/src/show/render.ts +660 -160
- package/src/show/show.test.ts +472 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/docs-site/zh/guides/sandbox-backends.mdx +0 -75
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { quietLine } from "./quiet.ts";
|
|
3
|
+
import type { EvalResult } from "../../types.ts";
|
|
4
|
+
|
|
5
|
+
function baseResult(overrides: Partial<EvalResult> = {}): EvalResult {
|
|
6
|
+
return {
|
|
7
|
+
id: "algebra/quadratic",
|
|
8
|
+
agent: "codex",
|
|
9
|
+
verdict: "passed",
|
|
10
|
+
attempt: 0,
|
|
11
|
+
durationMs: 42_000,
|
|
12
|
+
assertions: [],
|
|
13
|
+
...overrides,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe("quietLine", () => {
|
|
18
|
+
it("passed / skipped 静默(返回 undefined)", () => {
|
|
19
|
+
expect(quietLine(baseResult())).toBeUndefined();
|
|
20
|
+
expect(quietLine(baseResult({ verdict: "skipped", skipReason: "no api key" }))).toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("errored 带 eval id、[who] 与截断后的 error", () => {
|
|
24
|
+
const line = quietLine(
|
|
25
|
+
baseResult({
|
|
26
|
+
verdict: "errored",
|
|
27
|
+
model: "gpt-5",
|
|
28
|
+
error: "sandbox create failed: e2b timeout after 3.9s",
|
|
29
|
+
}),
|
|
30
|
+
);
|
|
31
|
+
expect(line).toContain("algebra/quadratic");
|
|
32
|
+
expect(line).toContain("[codex/gpt-5]");
|
|
33
|
+
expect(line).toContain("errored");
|
|
34
|
+
expect(line).toContain("e2b timeout after 3.9s");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("[who] 与进度行同源:有 experimentId 时用其 basename", () => {
|
|
38
|
+
const line = quietLine(
|
|
39
|
+
baseResult({ verdict: "errored", experimentId: "compare/xxx--agents-md", error: "boom" }),
|
|
40
|
+
);
|
|
41
|
+
expect(line).toContain("[xxx--agents-md]");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("failed 无 error 时取首个失败断言(severity、阈值、detail)", () => {
|
|
45
|
+
const line = quietLine(
|
|
46
|
+
baseResult({
|
|
47
|
+
verdict: "failed",
|
|
48
|
+
assertions: [
|
|
49
|
+
{ name: "compiles", severity: "gate", score: 1, passed: true },
|
|
50
|
+
{ name: "closedQA", severity: "gate", score: 0.2, passed: false, threshold: 0.7, detail: "wrong city" },
|
|
51
|
+
],
|
|
52
|
+
}),
|
|
53
|
+
);
|
|
54
|
+
expect(line).toContain("failed");
|
|
55
|
+
expect(line).toContain("closedQA");
|
|
56
|
+
expect(line).toContain("0.20");
|
|
57
|
+
expect(line).toContain("wrong city");
|
|
58
|
+
expect(line).not.toContain("compiles");
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("超长 error 截断到 200 字符并加省略号", () => {
|
|
62
|
+
const line = quietLine(baseResult({ verdict: "errored", error: "x".repeat(500) }))!;
|
|
63
|
+
expect(line).toContain("…");
|
|
64
|
+
expect(line.length).toBeLessThan(300);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Quiet 报告器:--quiet 下的最小结果流。进度流照旧(attempt 无 onProgress 时直写 stderr),
|
|
2
|
+
// 这里只补「坏结果」:verdict 为 errored / failed 的结果各写一行 stderr,passed / skipped
|
|
3
|
+
// 静默 —— 保证 --quiet 下起沙箱失败这类执行错不会全程无声、只能事后读 summary.json。
|
|
4
|
+
// 结果流仍走统一的 Reporter 管线,不在 attempt 里散写。
|
|
5
|
+
|
|
6
|
+
import type { EvalResult, Reporter } from "../../types.ts";
|
|
7
|
+
import { t } from "../../i18n/index.ts";
|
|
8
|
+
import { runWho } from "../types.ts";
|
|
9
|
+
import { verdictSymbol } from "./shared.ts";
|
|
10
|
+
|
|
11
|
+
/** error / 断言 detail 的截断上限;比 Console 的 400 更紧,--quiet 只要能定位问题。 */
|
|
12
|
+
const DETAIL_MAX = 200;
|
|
13
|
+
|
|
14
|
+
/** 纯函数:一条结果 → 该写 stderr 的行;passed / skipped 返回 undefined(静默)。 */
|
|
15
|
+
export function quietLine(result: EvalResult): string | undefined {
|
|
16
|
+
if (result.verdict !== "errored" && result.verdict !== "failed") return undefined;
|
|
17
|
+
// [who] 与 attempt 进度行同源(runWho),两条流才能对上同一个运行配置。
|
|
18
|
+
const who = runWho({ agentName: result.agent, model: result.model, experimentId: result.experimentId });
|
|
19
|
+
const verdict = result.verdict === "errored" ? t("report.errored") : t("report.failed");
|
|
20
|
+
const detail =
|
|
21
|
+
result.error !== undefined
|
|
22
|
+
? `${t("report.error")}: ${truncate(result.error, DETAIL_MAX)}`
|
|
23
|
+
: firstFailedAssertion(result);
|
|
24
|
+
return ` ${verdictSymbol(result.verdict)} ${result.id} ${verdict} [${who}]${detail ? ` ${detail}` : ""}\n`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function firstFailedAssertion(result: EvalResult): string | undefined {
|
|
28
|
+
const a = result.assertions.find((x) => !x.passed);
|
|
29
|
+
if (!a) return undefined;
|
|
30
|
+
const sev = a.severity === "gate" ? t("report.gate") : t("report.soft");
|
|
31
|
+
const thr =
|
|
32
|
+
a.threshold !== undefined
|
|
33
|
+
? t("report.assertionThreshold", { score: a.score.toFixed(2), threshold: a.threshold })
|
|
34
|
+
: "";
|
|
35
|
+
return `${sev}: ${a.name}${thr}${a.detail ? ` — ${truncate(a.detail, DETAIL_MAX)}` : ""}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function truncate(s: string, n: number): string {
|
|
39
|
+
return s.length > n ? s.slice(0, n) + "…" : s;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function Quiet(): Reporter {
|
|
43
|
+
return {
|
|
44
|
+
onEvalComplete(result: EvalResult) {
|
|
45
|
+
const line = quietLine(result);
|
|
46
|
+
if (line !== undefined) process.stderr.write(line);
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
// reporter 共用的展示常量。符号表只此一份 —— console/live/table 各抄一份时,
|
|
2
2
|
// 改一个符号要同步三处且不报错。
|
|
3
3
|
|
|
4
|
-
import type {
|
|
4
|
+
import type { Verdict } from "../../types.ts";
|
|
5
5
|
|
|
6
|
-
export const
|
|
6
|
+
export const VERDICT_SYM: Record<Verdict, string> = {
|
|
7
7
|
passed: "✓",
|
|
8
8
|
failed: "✗",
|
|
9
9
|
errored: "!",
|
|
10
10
|
skipped: "○",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export function
|
|
14
|
-
return
|
|
13
|
+
export function verdictSymbol(verdict: string): string {
|
|
14
|
+
return VERDICT_SYM[verdict as Verdict] ?? "?";
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
/** live 表格里「还没抢到并发名额」的行:和转圈的 SPINNER、完成后的
|
|
17
|
+
/** live 表格里「还没抢到并发名额」的行:和转圈的 SPINNER、完成后的 VERDICT_SYM 三态区分开。 */
|
|
18
18
|
export const WAITING_SYM = "·";
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { EvalResult, RunSummary, Usage } from "../../types.ts";
|
|
2
2
|
import { t } from "../../i18n/index.ts";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { verdictSymbol } from "./shared.ts";
|
|
4
|
+
import { foldEvalVerdict, evalLevelStats as sharedEvalLevelStats } from "../../shared/verdict.ts";
|
|
5
5
|
// 展示口径(耗时/成本/百分比)与聚合小工具(标签/求和/排序)与 view 共用一份实现;
|
|
6
6
|
// console.ts 经这里 re-export formatDuration。
|
|
7
7
|
import { formatCost, formatDuration, formatPercent } from "../../shared/format.ts";
|
|
8
|
-
import {
|
|
8
|
+
import { VERDICT_ORDER, avg, displayExperimentName, fallbackExperimentLabel, sumMaybe, totalTokens } from "../../shared/aggregate.ts";
|
|
9
9
|
|
|
10
10
|
export { formatCost, formatDuration } from "../../shared/format.ts";
|
|
11
11
|
|
|
@@ -29,7 +29,7 @@ interface ExperimentRow {
|
|
|
29
29
|
|
|
30
30
|
interface EvalRow {
|
|
31
31
|
id: string;
|
|
32
|
-
|
|
32
|
+
verdict: EvalResult["verdict"];
|
|
33
33
|
reason: string;
|
|
34
34
|
durationMs: number;
|
|
35
35
|
tokens: number;
|
|
@@ -77,7 +77,7 @@ export function renderRunReport(summary: RunSummary): string {
|
|
|
77
77
|
t("report.table.runs"),
|
|
78
78
|
],
|
|
79
79
|
aggregateEvalRows(row.results).map((evalRow) => [
|
|
80
|
-
|
|
80
|
+
formatVerdict(evalRow.verdict),
|
|
81
81
|
evalRow.id,
|
|
82
82
|
evalRow.reason,
|
|
83
83
|
formatDuration(evalRow.durationMs),
|
|
@@ -114,14 +114,14 @@ export function formatTokens(n: number): string {
|
|
|
114
114
|
return String(n);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
export function
|
|
118
|
-
const sym =
|
|
119
|
-
switch (
|
|
117
|
+
export function formatVerdict(verdict: string): string {
|
|
118
|
+
const sym = verdictSymbol(verdict);
|
|
119
|
+
switch (verdict) {
|
|
120
120
|
case "passed": return `${sym} ${t("report.passed")}`;
|
|
121
121
|
case "failed": return `${sym} ${t("report.failed")}`;
|
|
122
122
|
case "errored": return `${sym} ${t("report.errored")}`;
|
|
123
123
|
case "skipped": return `${sym} ${t("report.skipped")}`;
|
|
124
|
-
default: return `${sym} ${
|
|
124
|
+
default: return `${sym} ${verdict}`;
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -162,26 +162,26 @@ function aggregateEvalRows(results: EvalResult[]): EvalRow[] {
|
|
|
162
162
|
for (const result of results) byEval.set(result.id, [...(byEval.get(result.id) ?? []), result]);
|
|
163
163
|
return [...byEval.entries()]
|
|
164
164
|
.map(([id, evalResults]) => {
|
|
165
|
-
const
|
|
165
|
+
const verdict = foldEvalVerdict(evalResults);
|
|
166
166
|
const representative =
|
|
167
|
-
evalResults.find((r) => r.
|
|
168
|
-
evalResults.find((r) => r.
|
|
167
|
+
evalResults.find((r) => r.verdict === verdict) ??
|
|
168
|
+
evalResults.find((r) => r.verdict !== "passed") ??
|
|
169
169
|
evalResults[0]!;
|
|
170
170
|
return {
|
|
171
171
|
id,
|
|
172
|
-
|
|
173
|
-
reason:
|
|
172
|
+
verdict,
|
|
173
|
+
reason: verdict === "passed" ? t("report.passed") : reasonFor(representative),
|
|
174
174
|
durationMs: avg(evalResults.map((r) => r.durationMs)),
|
|
175
175
|
tokens: totalTokens(evalResults.map((r) => r.usage)),
|
|
176
176
|
cost: sumMaybe(evalResults.map((r) => r.estimatedCostUSD)),
|
|
177
|
-
passedAttempts: evalResults.filter((r) => r.
|
|
177
|
+
passedAttempts: evalResults.filter((r) => r.verdict === "passed").length,
|
|
178
178
|
attempts: evalResults.length,
|
|
179
179
|
};
|
|
180
180
|
})
|
|
181
|
-
.sort((a, b) =>
|
|
181
|
+
.sort((a, b) => VERDICT_ORDER[a.verdict] - VERDICT_ORDER[b.verdict] || a.id.localeCompare(b.id));
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
// 折叠 / 计票口径与 view 同一份实现(src/shared/
|
|
184
|
+
// 折叠 / 计票口径与 view 同一份实现(src/shared/verdict.ts),CLI 表格和网页榜单永不打架。
|
|
185
185
|
function evalLevelStats(results: EvalResult[]) {
|
|
186
186
|
return sharedEvalLevelStats(results, (r) => r.id);
|
|
187
187
|
}
|
package/src/runner/run.ts
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
// reporter 编排 / 汇总在 report.ts,Sandbox 适配器在 remote-sandbox.ts。
|
|
4
4
|
|
|
5
5
|
import { readFile } from "node:fs/promises";
|
|
6
|
-
import { Effect, Cause,
|
|
6
|
+
import { Effect, Cause, Exit } from "effect";
|
|
7
7
|
import { probeJudge } from "../scoring/judge.ts";
|
|
8
8
|
import { t } from "../i18n/index.ts";
|
|
9
|
-
import { cacheKey,
|
|
9
|
+
import { cacheKey, planCarry } from "./fingerprint.ts";
|
|
10
10
|
import { OtelReceiverPool } from "../o11y/otlp/turn-otel.ts";
|
|
11
11
|
import { runAttemptEffect } from "./attempt.ts";
|
|
12
12
|
import { runReporter, emitReporterEvent, scopeReporter, summarize } from "./report.ts";
|
|
13
|
+
import { writeStderrLine } from "../tty-line.ts";
|
|
13
14
|
import type { Agent, EvalResult, JudgeConfig, Reporter, RunShape, RunSummary } from "../types.ts";
|
|
14
15
|
import type { AgentRun, Attempt, RunOptions } from "./types.ts";
|
|
15
16
|
|
|
@@ -54,42 +55,14 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
54
55
|
return p;
|
|
55
56
|
};
|
|
56
57
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
await Promise.all(jobs);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 跨实验结果复用:只有上次 passed 且 fingerprint 匹配的 (experimentId, evalId) 组合直接携入。
|
|
73
|
-
// 失败/错误/跳过/fingerprint 不匹配都会重跑。--force 跳过此逻辑。
|
|
74
|
-
const priorRunKeys = new Set<string>();
|
|
75
|
-
const carriedResults: EvalResult[] = [];
|
|
76
|
-
if (opts.priorResults?.length) {
|
|
77
|
-
for (const r of opts.priorResults) {
|
|
78
|
-
if (!r.experimentId) continue;
|
|
79
|
-
const key = `${r.experimentId}|${r.id}`;
|
|
80
|
-
if (r.outcome === "passed" && r.fingerprint !== undefined && r.fingerprint === plannedFingerprints.get(key)) {
|
|
81
|
-
priorRunKeys.add(key);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
for (const r of opts.priorResults) {
|
|
85
|
-
if (!r.experimentId || !priorRunKeys.has(`${r.experimentId}|${r.id}`)) continue;
|
|
86
|
-
// artifactsDir 是相对"本次 run 自己目录"的路径,新 summary 换了目录就会失效,必须清掉。
|
|
87
|
-
// artifactBase 不一样:它已经是 loadLatestResultsPerEval(经 withViewRefs)拼好的、
|
|
88
|
-
// 相对稳定的 .niceeval 根的路径,指向旧 run 的产物目录依然可解析,原样带过来
|
|
89
|
-
// ——不然 view 就再也找不到这条携带结果的源码/转录/trace 了。
|
|
90
|
-
carriedResults.push({ ...r, artifactsDir: undefined });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
58
|
+
// 跨实验结果复用:上次 passed 或 failed 且 fingerprint 匹配的 (experimentId, evalId) 组合
|
|
59
|
+
// 直接携入 —— 两者都是"跑完了、判定确定"的终态,没有理由重花一次 agent/sandbox 成本去
|
|
60
|
+
// 复现同一个已知结果。errored 是框架/环境层面的不确定失败(超时、沙箱挂了、judge 探测失败
|
|
61
|
+
// 等),判定本身不可信,必须重跑。跳过/fingerprint 不匹配同样重跑。--force 跳过此逻辑
|
|
62
|
+
// (cli.ts 在 --force 时不传 priorResults,也不算 carryPlan)。
|
|
63
|
+
// carryPlan 优先用调用方(cli.ts,为了 live 表格)已经算好的那份,不重算一遍。
|
|
64
|
+
const { plannedFingerprints, priorRunKeys, carriedResults } =
|
|
65
|
+
opts.carryPlan ?? (await planCarry(opts.evals, opts.agentRuns, opts.priorResults));
|
|
93
66
|
|
|
94
67
|
// 展开 attempts
|
|
95
68
|
// 外层按「round」(run index)迭代,内层按 eval 迭代:同一 key 的第 i+1 次 attempt 排在
|
|
@@ -207,17 +180,16 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
207
180
|
// 真的没过)才代表 agent 行为的样本,值得跑满 runs 去测通过率。earlyExit 开时两者都提前收尾。
|
|
208
181
|
const erroredKeys = new Set<string>();
|
|
209
182
|
|
|
210
|
-
// budget
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
183
|
+
// budget 护栏:只按「已完成 attempt 的实测花费」判断,不做预测性节流。之前的实现会按
|
|
184
|
+
// 「平均成本 × 在飞数」预扣,快到顶就让还没起飞的 attempt 排队等——这在探测阶段(还没有任何
|
|
185
|
+
// 成本样本时)等价于把同一 budgetKey 的并发摁到一个很小的数,且完全没有文档承诺过这个副作用
|
|
186
|
+
// (`docs-site/zh/guides/write-experiment.mdx` 对 `budget` 的描述只有一句「这一格配置的预算
|
|
187
|
+
// 上限」)。新语义:已完成 attempt 的花费加总一旦到顶,就不再放新 attempt 起飞(已经在飞的
|
|
188
|
+
// 照常跑完,不会被中途打断);到顶之前不做任何预测性限流,并发完全由 globalSem / runSem 决定。
|
|
189
|
+
// 代价是「已花 + 在飞未结算」的总花费可能短暂超出 budget——这是有意识的取舍:budget 是防止
|
|
190
|
+
// 无限烧钱的安全网,不是精确计费闸,不应该反过来限制吞吐。
|
|
217
191
|
interface BudgetState {
|
|
218
192
|
spent: number;
|
|
219
|
-
inflight: number;
|
|
220
|
-
costSamples: number;
|
|
221
193
|
completedNoCost: number;
|
|
222
194
|
unenforceableWarned: boolean;
|
|
223
195
|
}
|
|
@@ -225,7 +197,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
225
197
|
const budgetState = (key: string): BudgetState => {
|
|
226
198
|
let s = budgetStates.get(key);
|
|
227
199
|
if (!s) {
|
|
228
|
-
s = { spent: 0,
|
|
200
|
+
s = { spent: 0, completedNoCost: 0, unenforceableWarned: false };
|
|
229
201
|
budgetStates.set(key, s);
|
|
230
202
|
}
|
|
231
203
|
return s;
|
|
@@ -236,7 +208,7 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
236
208
|
// 并发 worker 交错写 → 用一个 permit=1 的信号量串起来(替代原先手搓的 reportQueue 链)。
|
|
237
209
|
const reportMutex = Effect.runSync(Effect.makeSemaphore(1));
|
|
238
210
|
// 沙箱启动单独限流:与 agent 并发(maxConcurrency)解耦,防高并发下 daemon/API 过载。
|
|
239
|
-
// 未显式指定时跟 maxConcurrency 走——各
|
|
211
|
+
// 未显式指定时跟 maxConcurrency 走——各 provider 的推荐值已在 cli 层写进 maxConcurrency 默认值。
|
|
240
212
|
const sandboxSem = Effect.runSync(Effect.makeSemaphore(opts.maxConcurrency));
|
|
241
213
|
|
|
242
214
|
// 两级并发闸:全局(opts.maxConcurrency)+ 实验级(AgentRun.maxConcurrency,可选)。
|
|
@@ -286,10 +258,15 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
286
258
|
Effect.forEach(
|
|
287
259
|
attempts,
|
|
288
260
|
(a) => {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
261
|
+
const budgetKey = a.run.experimentId ?? a.run.agent.name;
|
|
262
|
+
|
|
263
|
+
// preflight:「要不要开始跑」的许可判断(首过即停 + budget 上限检查),不持有
|
|
264
|
+
// globalSem——两类判断都是即时返回,不该占着全局并发槽位做无谓等待。runSem(实验自己的
|
|
265
|
+
// maxConcurrency)例外:它是实验私有资源,preflight 占着不影响别的实验,且和 mempal
|
|
266
|
+
// 那类「必须串行」的语义一致,所以仍然把 preflight 包在 runSem 里面(见下方)。
|
|
267
|
+
const preflight = Effect.gen(function* () {
|
|
268
|
+
// 首过即停:同 key 已通过,或已 errored(重跑只会重复同一个框架错误)且开了 earlyExit
|
|
269
|
+
// → 跳过未启动的 attempt。
|
|
293
270
|
if (a.run.earlyExit && (passedKeys.has(a.key) || erroredKeys.has(a.key))) {
|
|
294
271
|
yield* reportMutex.withPermits(1)(
|
|
295
272
|
Effect.promise(() =>
|
|
@@ -300,49 +277,32 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
300
277
|
}),
|
|
301
278
|
),
|
|
302
279
|
);
|
|
303
|
-
return;
|
|
280
|
+
return false;
|
|
304
281
|
}
|
|
305
282
|
|
|
306
283
|
const budget = a.run.budget;
|
|
307
|
-
const budgetKey = a.run.experimentId ?? a.run.agent.name;
|
|
308
284
|
if (budget !== undefined) {
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
),
|
|
321
|
-
);
|
|
322
|
-
}
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
if (s.costSamples === 0 && s.completedNoCost >= 3 && !s.unenforceableWarned) {
|
|
326
|
-
// 连续几次完成都拿不到成本:budget 对这个 agent 不可执行,说清楚再放行。
|
|
327
|
-
s.unenforceableWarned = true;
|
|
328
|
-
process.stderr.write(t("runner.budgetUnenforceable", { budgetKey }));
|
|
329
|
-
}
|
|
330
|
-
if (s.costSamples === 0 && s.unenforceableWarned) {
|
|
331
|
-
s.inflight += 1;
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
334
|
-
const avg = s.costSamples > 0 ? s.spent / s.costSamples : undefined;
|
|
335
|
-
const projected =
|
|
336
|
-
avg === undefined ? (s.inflight > 0 ? Number.POSITIVE_INFINITY : 0) : s.spent + s.inflight * avg;
|
|
337
|
-
if (projected < budget) {
|
|
338
|
-
s.inflight += 1;
|
|
339
|
-
break;
|
|
285
|
+
// 只看已完成 attempt 的实测花费(见上方 BudgetState 注释),到顶就跳过新 attempt,
|
|
286
|
+
// 没到顶就立即放行——不等待、不做预测性节流。
|
|
287
|
+
const s = budgetState(budgetKey);
|
|
288
|
+
if (s.spent >= budget) {
|
|
289
|
+
if (!budgetReported.has(budgetKey)) {
|
|
290
|
+
budgetReported.add(budgetKey);
|
|
291
|
+
yield* reportMutex.withPermits(1)(
|
|
292
|
+
Effect.promise(() =>
|
|
293
|
+
emitReporterEvent(reporters, { type: "run:budgetExceeded", budget, spent: s.spent }),
|
|
294
|
+
),
|
|
295
|
+
);
|
|
340
296
|
}
|
|
341
|
-
|
|
297
|
+
return false;
|
|
342
298
|
}
|
|
343
299
|
}
|
|
300
|
+
return true;
|
|
301
|
+
});
|
|
344
302
|
|
|
345
|
-
|
|
303
|
+
// body:preflight 放行之后才跑,只有这一段真正占用全局并发槽位(globalSem)。
|
|
304
|
+
const body = Effect.gen(function* () {
|
|
305
|
+
// 合并全局信号与本 eval 的首过即停信号:任一 abort → 本 attempt 的信号 abort。
|
|
346
306
|
const evalAc = evalAbortControllers.get(a.key);
|
|
347
307
|
const attemptSignal =
|
|
348
308
|
evalAc && opts.signal
|
|
@@ -361,34 +321,29 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
361
321
|
}),
|
|
362
322
|
),
|
|
363
323
|
);
|
|
364
|
-
const result = yield* runAttemptEffect(a, opts, sandboxSem, attemptSignal)
|
|
365
|
-
Effect.ensuring(
|
|
366
|
-
Effect.sync(() => {
|
|
367
|
-
if (a.run.budget !== undefined) {
|
|
368
|
-
const s = budgetState(budgetKey);
|
|
369
|
-
s.inflight = Math.max(0, s.inflight - 1);
|
|
370
|
-
}
|
|
371
|
-
}),
|
|
372
|
-
),
|
|
373
|
-
);
|
|
324
|
+
const result = yield* runAttemptEffect(a, opts, sandboxSem, attemptSignal);
|
|
374
325
|
if (a.run.budget !== undefined) {
|
|
375
326
|
const s = budgetState(budgetKey);
|
|
376
327
|
if (result.estimatedCostUSD !== undefined) {
|
|
377
328
|
s.spent += result.estimatedCostUSD;
|
|
378
|
-
s.costSamples += 1;
|
|
379
329
|
} else {
|
|
380
330
|
s.completedNoCost += 1;
|
|
331
|
+
if (s.spent === 0 && s.completedNoCost >= 3 && !s.unenforceableWarned) {
|
|
332
|
+
// 连续几次完成都拿不到成本:budget 对这个 agent 不可执行,说清楚一次。
|
|
333
|
+
s.unenforceableWarned = true;
|
|
334
|
+
writeStderrLine(t("runner.budgetUnenforceable", { budgetKey }));
|
|
335
|
+
}
|
|
381
336
|
}
|
|
382
337
|
}
|
|
383
338
|
|
|
384
|
-
if (result.
|
|
339
|
+
if (result.verdict === "passed") {
|
|
385
340
|
passedKeys.add(a.key);
|
|
386
341
|
evalAc?.abort(); // 让同 key 并发 attempt 尽早退出
|
|
387
342
|
} else if (a.run.earlyExit && (passedKeys.has(a.key) || erroredKeys.has(a.key))) {
|
|
388
343
|
// 并发情况:同 key 另一个 attempt 已通过/已 errored 后本 attempt 才完成
|
|
389
344
|
// (被 abort 后产出 errored),不计入结果。
|
|
390
345
|
return;
|
|
391
|
-
} else if (result.
|
|
346
|
+
} else if (result.verdict === "errored") {
|
|
392
347
|
erroredKeys.add(a.key);
|
|
393
348
|
evalAc?.abort(); // 框架层面的错误会确定性重复,让同 key 剩余 attempt 尽早退出
|
|
394
349
|
}
|
|
@@ -413,7 +368,11 @@ export async function runEvals(opts: RunOptions): Promise<RunSummary> {
|
|
|
413
368
|
Effect.promise(() => emitReporterEvent(reporters, { type: "eval:complete", result })),
|
|
414
369
|
);
|
|
415
370
|
});
|
|
416
|
-
const gated =
|
|
371
|
+
const gated = Effect.gen(function* () {
|
|
372
|
+
const proceed = yield* preflight;
|
|
373
|
+
if (!proceed) return;
|
|
374
|
+
yield* globalSem.withPermits(1)(body);
|
|
375
|
+
});
|
|
417
376
|
const runSem = runSems.get(a.run);
|
|
418
377
|
return runSem ? runSem.withPermits(1)(gated) : gated;
|
|
419
378
|
},
|