@tangle-network/agent-bench 0.1.0 → 0.3.5
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/CHANGELOG.md +17 -0
- package/HARNESS.md +302 -0
- package/README.md +26 -1
- package/fixtures/aec-bench.json +18 -0
- package/fixtures/agentbench-dbbench.json +22 -0
- package/fixtures/bfcl.json +45 -0
- package/fixtures/commit0.json +72 -0
- package/fixtures/crag.json +10 -0
- package/fixtures/dabstep.json +22 -0
- package/fixtures/enterpriseops-gym.json +103 -0
- package/fixtures/finresearchbench.json +21 -0
- package/fixtures/finsearchcomp.json +66 -0
- package/fixtures/frames.json +26 -0
- package/fixtures/hotpotqa.json +182 -0
- package/fixtures/nomiracl.json +26 -0
- package/fixtures/open-rag-bench.json +16 -0
- package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
- package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
- package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
- package/fixtures/pier-agent/no-model-task/task.toml +35 -0
- package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
- package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
- package/fixtures/programbench.json +17 -0
- package/fixtures/ragbench.json +21 -0
- package/fixtures/simpleqa.json +121 -0
- package/fixtures/t2-ragbench.json +13 -0
- package/fixtures/tau2-bench.json +16 -0
- package/fixtures/tau3-banking.json +16 -0
- package/fixtures/toollm.json +28 -0
- package/fixtures/webarena-verified.json +20 -0
- package/package.json +39 -15
- package/pier_agents/__init__.py +18 -0
- package/pier_agents/candidate_contract.py +755 -0
- package/pier_agents/process_boundary.py +321 -0
- package/pier_agents/tangle_candidate.py +907 -0
- package/pier_agents/workspace_boundary.py +368 -0
- package/scripts/appworld_driver.py +359 -0
- package/scripts/cadbench_prepare.py +22 -0
- package/scripts/cadgenbench_hard_parts.py +48 -0
- package/scripts/clbench_codebase_judge.py +73 -0
- package/scripts/commit0_judge.py +170 -0
- package/scripts/dabstep_judge.py +42 -0
- package/scripts/enterpriseops_gym_judge.py +281 -0
- package/scripts/programbench_judge.py +120 -0
- package/scripts/render-gate-chart.mjs +176 -0
- package/scripts/run-package-tests.mjs +56 -0
- package/scripts/terminate-pier-trial.mts +66 -0
- package/scripts/trata-hedge/README.md +56 -0
- package/scripts/trata-hedge/run.sh +60 -0
- package/scripts/trata-hedge/solve.py +83 -0
- package/scripts/verify-packed-consumer.mjs +224 -0
- package/scripts/verify-pier-agent.mts +715 -0
- package/scripts/verify-pier-pair.mts +74 -0
- package/scripts/verify-pier-recovery.mts +139 -0
- package/src/adapters.ts +26 -0
- package/src/benchmarks/_harness.test.mts +178 -0
- package/src/benchmarks/_harness.ts +239 -16
- package/src/benchmarks/agentbench.ts +163 -0
- package/src/benchmarks/appworld.test.mts +15 -9
- package/src/benchmarks/bfcl.ts +346 -0
- package/src/benchmarks/crag.ts +137 -0
- package/src/benchmarks/dabstep.test.mts +70 -0
- package/src/benchmarks/dabstep.ts +212 -0
- package/src/benchmarks/external-adapters.test.mts +150 -0
- package/src/benchmarks/finresearchbench.ts +269 -0
- package/src/benchmarks/humaneval.ts +20 -8
- package/src/benchmarks/nomiracl.ts +180 -0
- package/src/benchmarks/open-rag-bench.ts +153 -0
- package/src/benchmarks/rag-benchmarks.test.mts +138 -0
- package/src/benchmarks/rag-shared.ts +327 -0
- package/src/benchmarks/ragbench.ts +171 -0
- package/src/benchmarks/swe-bench.test.mts +61 -0
- package/src/benchmarks/swe-bench.ts +201 -19
- package/src/benchmarks/t2-ragbench.ts +166 -0
- package/src/benchmarks/tau-bench-shared.ts +214 -0
- package/src/benchmarks/tau2-bench.ts +30 -0
- package/src/benchmarks/tau3-banking.ts +29 -0
- package/src/benchmarks/terminal-bench.test.mts +33 -0
- package/src/benchmarks/terminal-bench.ts +23 -8
- package/src/benchmarks/toollm.ts +254 -0
- package/src/benchmarks/types.ts +42 -0
- package/src/benchmarks/webarena-verified.ts +200 -0
- package/src/commit0-prereqs.sh +0 -0
- package/src/coordination-mcp-container-reach.mts +181 -0
- package/src/decoder-live.mts +1 -1
- package/src/examples/README.md +103 -39
- package/src/examples/benchmark-matrix.mts +101 -0
- package/src/examples/lean-proof-gate.README.md +77 -0
- package/src/examples/lean-proof-gate.mts +162 -0
- package/src/examples/lean-verify.ts +95 -0
- package/src/examples/lean.Dockerfile +12 -0
- package/src/examples/math-demo.mts +9 -7
- package/src/examples/strategy-demo.mts +10 -12
- package/src/gate.ts +3 -2
- package/src/hev-eval.mts +69 -0
- package/src/hev-improve.mts +169 -0
- package/src/hev-structural.mts +688 -0
- package/src/index.ts +73 -0
- package/src/mbpp-structural.mts +662 -0
- package/src/pier-agent.test-fixtures.mts +19 -0
- package/src/pier-agent.test.mts +363 -0
- package/src/pier-agent.ts +657 -0
- package/src/pier-result-grader.mjs +30 -0
- package/src/pier-result-grader.test.mts +62 -0
- package/src/pier-result-grader.ts +108 -0
- package/src/pier-task-outcome.test.mts +117 -0
- package/src/pier-task-outcome.ts +240 -0
- package/src/pier-trial-controller.test.mts +412 -0
- package/src/pier-trial-controller.ts +858 -0
- package/src/pier-trial-supervisor.mjs +352 -0
- package/src/resolve-client.ts +25 -2
- package/src/run-benchmarks-cli.mts +72 -0
- package/src/run-benchmarks-report.ts +66 -0
- package/src/run-benchmarks.test.mts +231 -0
- package/src/run-benchmarks.ts +589 -0
- package/src/smoke-structural-rollout.mts +393 -0
- package/src/swe-bench-env.test.ts +207 -0
- package/src/swe-bench-env.ts +554 -0
- package/src/swe-jail.ts +293 -0
- package/src/swe-self-improve.mts +84 -0
- package/src/swe-structural-judge-policy.test.ts +117 -0
- package/src/swe-structural-judge-policy.ts +133 -0
- package/src/swe-structural-policy.test.ts +124 -0
- package/src/swe-structural-policy.ts +132 -0
- package/src/swe-structural-provenance.test.ts +93 -0
- package/src/swe-structural-provenance.ts +138 -0
- package/src/swe-structural.mts +1260 -0
- package/src/swe-temp.ts +14 -0
- package/src/tb-container-executor.mts +234 -0
- package/src/tb-container-executor.test.mts +99 -0
- package/src/tb-supervisor-sidecar.mts +222 -0
- package/src/trata-gepa.mts +1 -1
- package/steerers/eops-itsm-population.json +1 -0
- package/tb_agents/opencode_refine_agent.py +117 -0
- package/tb_agents/opencode_router_agent.py +406 -0
- package/tb_agents/opencode_supervisor_agent.py +239 -0
- package/tb_agents/script_agent.py +66 -0
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { createHash } from 'node:crypto'
|
|
3
|
+
import {
|
|
4
|
+
chmodSync,
|
|
5
|
+
cpSync,
|
|
6
|
+
lstatSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
mkdtempSync,
|
|
9
|
+
readFileSync,
|
|
10
|
+
readdirSync,
|
|
11
|
+
rmSync,
|
|
12
|
+
writeFileSync,
|
|
13
|
+
} from 'node:fs'
|
|
14
|
+
import { tmpdir } from 'node:os'
|
|
15
|
+
import path from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
|
|
18
|
+
import { canonicalJson, InMemoryTraceStore } from '@tangle-network/agent-eval'
|
|
19
|
+
import {
|
|
20
|
+
sealCandidateBenchmarkSuite,
|
|
21
|
+
sealCandidateBenchmarkTask,
|
|
22
|
+
} from '@tangle-network/agent-eval/contract'
|
|
23
|
+
import type {
|
|
24
|
+
AgentCandidateArtifactRef,
|
|
25
|
+
AgentCandidateBundle,
|
|
26
|
+
AgentCandidateWorkspaceSnapshotEvidence,
|
|
27
|
+
Sha256Digest,
|
|
28
|
+
} from '@tangle-network/agent-interface'
|
|
29
|
+
import {
|
|
30
|
+
createAgentCandidateWorkspacePort,
|
|
31
|
+
disposePreparedAgentCandidateExecution,
|
|
32
|
+
FileAgentCandidateExecutionClaimStore,
|
|
33
|
+
prepareAgentCandidateExecution,
|
|
34
|
+
type AgentCandidateExecutionPorts,
|
|
35
|
+
type AgentCandidateOutputArtifactPort,
|
|
36
|
+
type AgentCandidateTaskExecution,
|
|
37
|
+
type ResolvedAgentCandidateContainer,
|
|
38
|
+
sealAgentCandidateBundle,
|
|
39
|
+
verifyAgentCandidateBundle,
|
|
40
|
+
} from '@tangle-network/agent-runtime'
|
|
41
|
+
|
|
42
|
+
import { executePreparedPierCandidate } from '../src/pier-agent'
|
|
43
|
+
import { createPierResultGrader } from '../src/pier-result-grader'
|
|
44
|
+
import { FilePierCandidateTrialController } from '../src/pier-trial-controller'
|
|
45
|
+
|
|
46
|
+
const pinnedPierCommit = 'e69a20e4e0ac073ec71fde0274bab3d9f40bac87'
|
|
47
|
+
const pinnedPierVersion = '0.3.0'
|
|
48
|
+
const modelRequest = 'openai/gpt-5.4'
|
|
49
|
+
const fixtureImage = 'ghcr.io/tangle-network/devcontainers/universal:latest'
|
|
50
|
+
const prepareOnly = process.env.PIER_PREPARE_ONLY === '1'
|
|
51
|
+
const proofArm = process.env.PIER_PROOF_ARM
|
|
52
|
+
if (proofArm !== 'failure' && proofArm !== 'success') {
|
|
53
|
+
throw new Error('PIER_PROOF_ARM must be explicitly set to failure or success')
|
|
54
|
+
}
|
|
55
|
+
const expectedReward = proofArm === 'success' ? 1 : 0
|
|
56
|
+
const expectedPatchApplied = proofArm === 'success' ? 1 : 0
|
|
57
|
+
const benchDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
58
|
+
const pierRepo = path.resolve(process.env.PIER_REPO ?? path.join(benchDir, '..', '..', 'pier'))
|
|
59
|
+
const fixtureSource = path.join(benchDir, 'fixtures', 'pier-agent', 'no-model-task')
|
|
60
|
+
const scratch = mkdtempSync(path.join(tmpdir(), 'agent-bench-pier-runtime-'))
|
|
61
|
+
const taskDir = path.join(scratch, 'task')
|
|
62
|
+
const taskRoot = path.join(scratch, 'task-workspace')
|
|
63
|
+
const candidateRoot = path.join(scratch, 'candidate-workspace')
|
|
64
|
+
const profileRoot = path.join(scratch, 'profile-workspace')
|
|
65
|
+
const jobsDir = path.join(scratch, 'jobs')
|
|
66
|
+
|
|
67
|
+
function output(
|
|
68
|
+
command: string,
|
|
69
|
+
args: string[],
|
|
70
|
+
cwd = benchDir,
|
|
71
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
72
|
+
): string {
|
|
73
|
+
return execFileSync(command, args, {
|
|
74
|
+
cwd,
|
|
75
|
+
env,
|
|
76
|
+
encoding: 'utf8',
|
|
77
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
78
|
+
timeout: 10 * 60_000,
|
|
79
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
80
|
+
}).trim()
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function sha256(bytes: Uint8Array): Sha256Digest {
|
|
84
|
+
return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function canonicalBytes(value: unknown): Buffer {
|
|
88
|
+
return Buffer.from(canonicalJson(value), 'utf8')
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function sealCanonical<T extends object>(material: T): T & { digest: Sha256Digest } {
|
|
92
|
+
return { ...material, digest: sha256(canonicalBytes(material)) }
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function embedded(bytes: Uint8Array) {
|
|
96
|
+
return {
|
|
97
|
+
encoding: 'base64' as const,
|
|
98
|
+
content: Buffer.from(bytes).toString('base64'),
|
|
99
|
+
sha256: sha256(bytes),
|
|
100
|
+
byteLength: bytes.byteLength,
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function workspaceSnapshot(
|
|
105
|
+
root: string,
|
|
106
|
+
paths: readonly string[],
|
|
107
|
+
): AgentCandidateWorkspaceSnapshotEvidence {
|
|
108
|
+
const files = paths
|
|
109
|
+
.map((relative) => {
|
|
110
|
+
const absolute = path.join(root, relative)
|
|
111
|
+
const bytes = readFileSync(absolute)
|
|
112
|
+
const mode = lstatSync(absolute).mode & 0o777
|
|
113
|
+
if (mode !== 0o644 && mode !== 0o755) {
|
|
114
|
+
throw new Error(`unsupported fixture mode ${mode.toString(8)}: ${relative}`)
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
path: relative,
|
|
118
|
+
mode: mode as 0o644 | 0o755,
|
|
119
|
+
sha256: sha256(bytes),
|
|
120
|
+
byteLength: bytes.byteLength,
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
.sort((left, right) => left.path.localeCompare(right.path))
|
|
124
|
+
const material = {
|
|
125
|
+
kind: 'agent-candidate-workspace-manifest' as const,
|
|
126
|
+
files,
|
|
127
|
+
} satisfies AgentCandidateWorkspaceSnapshotEvidence['material']
|
|
128
|
+
const manifest = canonicalBytes(material)
|
|
129
|
+
return {
|
|
130
|
+
kind: 'agent-candidate-workspace-snapshot',
|
|
131
|
+
digest: sha256(manifest),
|
|
132
|
+
material,
|
|
133
|
+
manifest: embedded(manifest),
|
|
134
|
+
archive: embedded(Buffer.from(`pre-materialized:${sha256(manifest)}`, 'utf8')),
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function deterministicRepository(root: string, seed: string): { commit: string; tree: string } {
|
|
139
|
+
mkdirSync(path.join(root, 'src'), { recursive: true })
|
|
140
|
+
cpSync(seed, path.join(root, 'src', 'status.txt'))
|
|
141
|
+
chmodSync(path.join(root, 'src', 'status.txt'), 0o644)
|
|
142
|
+
output('git', ['init', '-b', 'main', root])
|
|
143
|
+
output('git', ['-C', root, 'config', 'user.email', 'fixture@tangle.tools'])
|
|
144
|
+
output('git', ['-C', root, 'config', 'user.name', 'Tangle Fixture'])
|
|
145
|
+
output('git', [
|
|
146
|
+
'-C',
|
|
147
|
+
root,
|
|
148
|
+
'remote',
|
|
149
|
+
'add',
|
|
150
|
+
'origin',
|
|
151
|
+
'git@github.com:tangle-network/agent-bench-pier-fixture.git',
|
|
152
|
+
])
|
|
153
|
+
output('git', ['-c', 'core.hooksPath=/dev/null', '-C', root, 'add', '-A'])
|
|
154
|
+
output(
|
|
155
|
+
'git',
|
|
156
|
+
['-c', 'core.hooksPath=/dev/null', '-C', root, 'commit', '-m', 'baseline'],
|
|
157
|
+
benchDir,
|
|
158
|
+
{
|
|
159
|
+
...process.env,
|
|
160
|
+
GIT_AUTHOR_DATE: '2000-01-01T00:00:00Z',
|
|
161
|
+
GIT_COMMITTER_DATE: '2000-01-01T00:00:00Z',
|
|
162
|
+
},
|
|
163
|
+
)
|
|
164
|
+
return {
|
|
165
|
+
commit: output('git', ['-C', root, 'rev-parse', 'HEAD']),
|
|
166
|
+
tree: output('git', ['-C', root, 'rev-parse', 'HEAD^{tree}']),
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function publicOciIdentity(
|
|
171
|
+
image: string,
|
|
172
|
+
): { indexDigest: Sha256Digest; manifestDigest: Sha256Digest } {
|
|
173
|
+
const material = JSON.parse(
|
|
174
|
+
output('docker', ['buildx', 'imagetools', 'inspect', image, '--format', '{{json .Manifest}}']),
|
|
175
|
+
) as {
|
|
176
|
+
digest?: string
|
|
177
|
+
manifests?: Array<{
|
|
178
|
+
digest?: string
|
|
179
|
+
platform?: { os?: string; architecture?: string }
|
|
180
|
+
}>
|
|
181
|
+
}
|
|
182
|
+
const topDigest = material.digest
|
|
183
|
+
if (!topDigest?.match(/^sha256:[a-f0-9]{64}$/)) {
|
|
184
|
+
throw new Error(`public image omitted a valid index digest: ${topDigest}`)
|
|
185
|
+
}
|
|
186
|
+
const selected = material.manifests?.find(
|
|
187
|
+
(entry) => entry.platform?.os === 'linux' && entry.platform.architecture === 'amd64',
|
|
188
|
+
)
|
|
189
|
+
const manifestDigest = selected?.digest
|
|
190
|
+
if (!manifestDigest) throw new Error('public image has no linux/amd64 manifest')
|
|
191
|
+
if (!manifestDigest.match(/^sha256:[a-f0-9]{64}$/)) {
|
|
192
|
+
throw new Error(`public image returned an invalid platform manifest: ${manifestDigest}`)
|
|
193
|
+
}
|
|
194
|
+
return {
|
|
195
|
+
indexDigest: topDigest as Sha256Digest,
|
|
196
|
+
manifestDigest: manifestDigest as Sha256Digest,
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function findTrialResult(root: string): { path: string; value: Record<string, any> } | undefined {
|
|
201
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
202
|
+
if (!entry.isDirectory()) continue
|
|
203
|
+
const child = path.join(root, entry.name)
|
|
204
|
+
const candidate = path.join(child, 'result.json')
|
|
205
|
+
try {
|
|
206
|
+
const value = JSON.parse(readFileSync(candidate, 'utf8')) as Record<string, any>
|
|
207
|
+
if (typeof value.trial_name === 'string') return { path: candidate, value }
|
|
208
|
+
} catch {}
|
|
209
|
+
const nested = findTrialResult(child)
|
|
210
|
+
if (nested) return nested
|
|
211
|
+
}
|
|
212
|
+
return undefined
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function assertTreeOmits(root: string, forbidden: string): void {
|
|
216
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
217
|
+
const absolute = path.join(root, entry.name)
|
|
218
|
+
if (entry.isDirectory()) {
|
|
219
|
+
assertTreeOmits(absolute, forbidden)
|
|
220
|
+
} else if (entry.isFile() && readFileSync(absolute).includes(Buffer.from(forbidden))) {
|
|
221
|
+
throw new Error(`protected value persisted in ${path.relative(root, absolute)}`)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function outputArtifactStore(graderBytes: Uint8Array): {
|
|
227
|
+
outputArtifacts: AgentCandidateOutputArtifactPort
|
|
228
|
+
graderArtifact: AgentCandidateArtifactRef
|
|
229
|
+
} {
|
|
230
|
+
const stored = new Map<string, Uint8Array>()
|
|
231
|
+
const put = (bytes: Uint8Array, purpose: string): AgentCandidateArtifactRef => {
|
|
232
|
+
const detached = Uint8Array.from(bytes)
|
|
233
|
+
const digest = sha256(detached)
|
|
234
|
+
const artifact: AgentCandidateArtifactRef = {
|
|
235
|
+
locator: {
|
|
236
|
+
kind: 's3',
|
|
237
|
+
bucket: 'agent-bench-pier-proof',
|
|
238
|
+
key: `${purpose}/${digest.slice('sha256:'.length)}`,
|
|
239
|
+
},
|
|
240
|
+
sha256: digest,
|
|
241
|
+
byteLength: detached.byteLength,
|
|
242
|
+
}
|
|
243
|
+
stored.set(digest, detached)
|
|
244
|
+
return artifact
|
|
245
|
+
}
|
|
246
|
+
const graderArtifact = put(graderBytes, 'graders')
|
|
247
|
+
return {
|
|
248
|
+
graderArtifact,
|
|
249
|
+
outputArtifacts: {
|
|
250
|
+
put: async ({ bytes, purpose, signal }) => {
|
|
251
|
+
signal?.throwIfAborted()
|
|
252
|
+
return put(bytes, purpose)
|
|
253
|
+
},
|
|
254
|
+
read: async (artifact) => {
|
|
255
|
+
const bytes = stored.get(artifact.sha256)
|
|
256
|
+
if (!bytes) throw new Error(`missing Pier proof artifact ${artifact.sha256}`)
|
|
257
|
+
return Uint8Array.from(bytes)
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
try {
|
|
264
|
+
if (!prepareOnly) {
|
|
265
|
+
const pierHead = output('git', ['rev-parse', 'HEAD'], pierRepo)
|
|
266
|
+
if (pierHead !== pinnedPierCommit) {
|
|
267
|
+
throw new Error(`Pier checkout mismatch: expected ${pinnedPierCommit}, got ${pierHead}`)
|
|
268
|
+
}
|
|
269
|
+
const pierStatus = output('git', ['status', '--porcelain'], pierRepo)
|
|
270
|
+
if (pierStatus !== '') throw new Error(`Pier checkout must be clean: ${pierStatus}`)
|
|
271
|
+
const pierVersion = output('uv', ['run', 'pier', '--version'], pierRepo)
|
|
272
|
+
if (pierVersion !== pinnedPierVersion) {
|
|
273
|
+
throw new Error(`Pier version mismatch: expected ${pinnedPierVersion}, got ${pierVersion}`)
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
cpSync(fixtureSource, taskDir, { recursive: true })
|
|
278
|
+
for (const relative of ['environment/seed/src/status.txt', 'tests/seed/src/status.txt']) {
|
|
279
|
+
chmodSync(path.join(taskDir, relative), 0o644)
|
|
280
|
+
}
|
|
281
|
+
for (const relative of ['pre_artifacts.sh', 'tests/test.sh']) {
|
|
282
|
+
chmodSync(path.join(taskDir, relative), 0o755)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const contextDigest = sha256(
|
|
286
|
+
Buffer.concat([
|
|
287
|
+
readFileSync(path.join(taskDir, 'environment', 'Dockerfile')),
|
|
288
|
+
readFileSync(path.join(taskDir, 'environment', 'seed', 'src', 'status.txt')),
|
|
289
|
+
]),
|
|
290
|
+
).slice(7, 23)
|
|
291
|
+
const identity = prepareOnly
|
|
292
|
+
? {
|
|
293
|
+
indexDigest: `sha256:${'1'.repeat(64)}` as Sha256Digest,
|
|
294
|
+
manifestDigest: `sha256:${'2'.repeat(64)}` as Sha256Digest,
|
|
295
|
+
}
|
|
296
|
+
: publicOciIdentity(fixtureImage)
|
|
297
|
+
const pinnedImage = `${fixtureImage}@${identity.indexDigest}`
|
|
298
|
+
if (!prepareOnly) output('docker', ['pull', '--platform', 'linux/amd64', pinnedImage])
|
|
299
|
+
const platform = prepareOnly
|
|
300
|
+
? 'linux/amd64'
|
|
301
|
+
: output('docker', [
|
|
302
|
+
'image',
|
|
303
|
+
'inspect',
|
|
304
|
+
'--format',
|
|
305
|
+
'{{.Os}}/{{.Architecture}}',
|
|
306
|
+
pinnedImage,
|
|
307
|
+
])
|
|
308
|
+
if (platform !== 'linux/amd64') throw new Error(`fixture image platform drifted: ${platform}`)
|
|
309
|
+
|
|
310
|
+
const configPath = path.join(taskDir, 'task.toml')
|
|
311
|
+
const config = readFileSync(configPath, 'utf8')
|
|
312
|
+
writeFileSync(
|
|
313
|
+
configPath,
|
|
314
|
+
config.replace(
|
|
315
|
+
'[environment]\n',
|
|
316
|
+
`[environment]\ndocker_image = "${pinnedImage}"\nos = "linux"\n`,
|
|
317
|
+
),
|
|
318
|
+
)
|
|
319
|
+
mkdirSync(candidateRoot)
|
|
320
|
+
mkdirSync(profileRoot)
|
|
321
|
+
const instruction = readFileSync(path.join(taskDir, 'instruction.md'), 'utf8')
|
|
322
|
+
const runner = `import pathlib, sys
|
|
323
|
+
task = pathlib.Path.cwd()
|
|
324
|
+
profile = task / 'AGENTS.md'
|
|
325
|
+
assert profile.is_file() and profile.read_text().strip()
|
|
326
|
+
assert sys.argv[-1] == ${JSON.stringify(instruction)}
|
|
327
|
+
${proofArm === 'success' ? "(task / 'src/status.txt').write_text('ready\\nowner=tangle\\n')" : ''}
|
|
328
|
+
`
|
|
329
|
+
writeFileSync(path.join(candidateRoot, 'runner.py'), runner)
|
|
330
|
+
chmodSync(path.join(candidateRoot, 'runner.py'), 0o755)
|
|
331
|
+
const repositoryState = deterministicRepository(
|
|
332
|
+
taskRoot,
|
|
333
|
+
path.join(taskDir, 'environment', 'seed', 'src', 'status.txt'),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
const taskWorkspace = workspaceSnapshot(taskRoot, ['src/status.txt'])
|
|
337
|
+
const candidateWorkspace = workspaceSnapshot(candidateRoot, ['runner.py'])
|
|
338
|
+
const bundleWithoutDigest = {
|
|
339
|
+
kind: 'agent-candidate-bundle' as const,
|
|
340
|
+
digestAlgorithm: 'rfc8785-sha256' as const,
|
|
341
|
+
profile: {
|
|
342
|
+
name: `pier-no-model-runtime-${proofArm}`,
|
|
343
|
+
prompt: {
|
|
344
|
+
instructions: [
|
|
345
|
+
'Edit only the task repository. Follow the exact user instruction and verify the result.',
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
model: { default: modelRequest, reasoningEffort: 'xhigh' as const },
|
|
349
|
+
harness: 'codex' as const,
|
|
350
|
+
resources: { failOnError: true as const },
|
|
351
|
+
},
|
|
352
|
+
code: {
|
|
353
|
+
kind: 'no-op' as const,
|
|
354
|
+
reason: 'proposer-no-change' as const,
|
|
355
|
+
repository: {
|
|
356
|
+
kind: 'github' as const,
|
|
357
|
+
owner: 'tangle-network',
|
|
358
|
+
repo: 'agent-bench-pier-fixture',
|
|
359
|
+
},
|
|
360
|
+
baseCommit: repositoryState.commit,
|
|
361
|
+
baseTree: repositoryState.tree,
|
|
362
|
+
},
|
|
363
|
+
execution: {
|
|
364
|
+
harness: 'codex' as const,
|
|
365
|
+
harnessVersion: 'agent-bench-pier/2.0.0',
|
|
366
|
+
launch: {
|
|
367
|
+
kind: 'candidate-entrypoint' as const,
|
|
368
|
+
entrypoint: 'runner.py',
|
|
369
|
+
interpreter: 'python3' as const,
|
|
370
|
+
},
|
|
371
|
+
instructionDelivery: { kind: 'argv-append' as const },
|
|
372
|
+
cwd: { workspace: 'task' as const, path: '.' },
|
|
373
|
+
environment: { kind: 'evaluator-task-container' as const },
|
|
374
|
+
workspace: candidateWorkspace,
|
|
375
|
+
isolation: {
|
|
376
|
+
network: 'disabled' as const,
|
|
377
|
+
remoteIntegrations: 'disabled' as const,
|
|
378
|
+
candidateSecrets: 'disabled' as const,
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
memory: { mode: 'disabled' as const },
|
|
382
|
+
}
|
|
383
|
+
const bundle: AgentCandidateBundle = sealAgentCandidateBundle(bundleWithoutDigest)
|
|
384
|
+
const container: ResolvedAgentCandidateContainer = {
|
|
385
|
+
source: 'evaluator-task-container',
|
|
386
|
+
image: fixtureImage,
|
|
387
|
+
indexDigest: identity.indexDigest,
|
|
388
|
+
manifestDigest: identity.manifestDigest,
|
|
389
|
+
platform: { os: 'linux', architecture: 'amd64' },
|
|
390
|
+
}
|
|
391
|
+
const graderBytes = readFileSync(new URL('../src/pier-result-grader.mjs', import.meta.url))
|
|
392
|
+
const { outputArtifacts, graderArtifact } = outputArtifactStore(graderBytes)
|
|
393
|
+
const grader = createPierResultGrader({
|
|
394
|
+
name: 'pier-official-result',
|
|
395
|
+
version: '1.0.0',
|
|
396
|
+
artifact: graderArtifact,
|
|
397
|
+
})
|
|
398
|
+
const executionId = `pier-no-model-${proofArm}-${contextDigest}`
|
|
399
|
+
const benchmarkTask = sealCandidateBenchmarkTask({
|
|
400
|
+
kind: 'agent-candidate-benchmark-task',
|
|
401
|
+
digestAlgorithm: 'rfc8785-sha256',
|
|
402
|
+
benchmark: {
|
|
403
|
+
name: 'pier-runtime-proof',
|
|
404
|
+
version: '1',
|
|
405
|
+
splitDigest: `sha256:${'b'.repeat(64)}`,
|
|
406
|
+
},
|
|
407
|
+
scenario: {
|
|
408
|
+
id: `agent-bench/pier-candidate-no-model-${proofArm}`,
|
|
409
|
+
kind: 'coding',
|
|
410
|
+
scenarioDigest: sha256(Buffer.from(`pier-runtime-proof:${proofArm}`, 'utf8')),
|
|
411
|
+
},
|
|
412
|
+
instruction,
|
|
413
|
+
repository: {
|
|
414
|
+
identity: 'github.com/tangle-network/agent-bench-pier-fixture',
|
|
415
|
+
rootIdentity: 'tangle-network/agent-bench-pier-fixture',
|
|
416
|
+
baseCommit: repositoryState.commit,
|
|
417
|
+
baseTree: repositoryState.tree,
|
|
418
|
+
},
|
|
419
|
+
outcome: { kind: 'workspace' },
|
|
420
|
+
attempt: { maxAttempts: 1, retryPolicy: 'none' },
|
|
421
|
+
model: {
|
|
422
|
+
requested: modelRequest,
|
|
423
|
+
provider: 'openai',
|
|
424
|
+
model: 'gpt-5.4',
|
|
425
|
+
snapshot: 'gpt-5.4-no-model-proof',
|
|
426
|
+
reasoningEffort: 'xhigh',
|
|
427
|
+
},
|
|
428
|
+
grader: {
|
|
429
|
+
name: grader.name,
|
|
430
|
+
version: grader.version,
|
|
431
|
+
format: 'tangle-grader',
|
|
432
|
+
artifact: grader.artifact,
|
|
433
|
+
},
|
|
434
|
+
workspace: taskWorkspace,
|
|
435
|
+
evaluatorTaskContainer: container,
|
|
436
|
+
limits: {
|
|
437
|
+
timeoutMs: 60_000,
|
|
438
|
+
maxSteps: 8,
|
|
439
|
+
maxModelCalls: 0,
|
|
440
|
+
maxInputTokens: 0,
|
|
441
|
+
maxOutputTokens: 0,
|
|
442
|
+
maxCostUsd: 0,
|
|
443
|
+
},
|
|
444
|
+
})
|
|
445
|
+
const benchmark = sealCandidateBenchmarkSuite({
|
|
446
|
+
tasks: [benchmarkTask],
|
|
447
|
+
reps: 1,
|
|
448
|
+
seeds: [42],
|
|
449
|
+
})
|
|
450
|
+
const task: AgentCandidateTaskExecution = {
|
|
451
|
+
executionId,
|
|
452
|
+
runCell: sealCanonical({
|
|
453
|
+
kind: 'agent-candidate-run-cell' as const,
|
|
454
|
+
experimentDigest: sha256(Buffer.from('pier-runtime-proof-experiment', 'utf8')),
|
|
455
|
+
arm: 'candidate' as const,
|
|
456
|
+
bundleDigest: bundle.digest,
|
|
457
|
+
suiteDigest: benchmark.suite.digest,
|
|
458
|
+
taskDigest: benchmarkTask.digest,
|
|
459
|
+
taskIndex: 0,
|
|
460
|
+
repetition: 0,
|
|
461
|
+
seed: 42,
|
|
462
|
+
attempt: 1,
|
|
463
|
+
}),
|
|
464
|
+
benchmarkSuite: benchmark.suite,
|
|
465
|
+
task: benchmarkTask,
|
|
466
|
+
executionRoots: { taskRoot: '/app', candidateRoot: '/opt/tangle-candidate' },
|
|
467
|
+
stagingRoots: { taskRoot, candidateRoot, profileRoot },
|
|
468
|
+
}
|
|
469
|
+
const workspaces = createAgentCandidateWorkspacePort()
|
|
470
|
+
const ports: AgentCandidateExecutionPorts = {
|
|
471
|
+
artifacts: {
|
|
472
|
+
read: async () => {
|
|
473
|
+
throw new Error('the proof uses only embedded artifacts')
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
repositories: { resolve: async () => taskRoot },
|
|
477
|
+
workspaces: {
|
|
478
|
+
materialize: async ({ role, snapshot, archive, destination }) => {
|
|
479
|
+
if (destination === taskRoot || destination === candidateRoot) return
|
|
480
|
+
await workspaces.materialize({
|
|
481
|
+
role,
|
|
482
|
+
snapshot,
|
|
483
|
+
archive,
|
|
484
|
+
destination,
|
|
485
|
+
})
|
|
486
|
+
},
|
|
487
|
+
},
|
|
488
|
+
containers: { resolve: async () => container },
|
|
489
|
+
models: {
|
|
490
|
+
resolve: async ({ requested, reasoningEffort }) => {
|
|
491
|
+
if (!reasoningEffort) throw new Error('resolved model requires reasoning effort')
|
|
492
|
+
return {
|
|
493
|
+
requested,
|
|
494
|
+
provider: 'openai',
|
|
495
|
+
model: 'gpt-5.4',
|
|
496
|
+
snapshot: 'gpt-5.4-no-model-proof',
|
|
497
|
+
reasoningEffort,
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
reserveGrant: async ({ limits, preparationId, expiresAtMs }) => ({
|
|
501
|
+
preparationId,
|
|
502
|
+
digest: `sha256:${'c'.repeat(64)}`,
|
|
503
|
+
expiresAtMs,
|
|
504
|
+
enforcedLimits: limits,
|
|
505
|
+
network: { mode: 'disabled' },
|
|
506
|
+
}),
|
|
507
|
+
activateGrant: async () => ({
|
|
508
|
+
env: { MODEL_GATEWAY_TOKEN: 'zero-model-proof' },
|
|
509
|
+
}),
|
|
510
|
+
settleGrant: async ({ preparationId }) => ({
|
|
511
|
+
preparationId,
|
|
512
|
+
grantDigest: `sha256:${'c'.repeat(64)}`,
|
|
513
|
+
closed: true,
|
|
514
|
+
calls: [],
|
|
515
|
+
}),
|
|
516
|
+
},
|
|
517
|
+
memory: {
|
|
518
|
+
reset: async () => {
|
|
519
|
+
throw new Error('disabled memory must not be reset')
|
|
520
|
+
},
|
|
521
|
+
activate: async () => {
|
|
522
|
+
throw new Error('disabled memory must not be activated')
|
|
523
|
+
},
|
|
524
|
+
close: async () => {
|
|
525
|
+
throw new Error('disabled memory must not be closed')
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const verified = await verifyAgentCandidateBundle(bundle, ports)
|
|
531
|
+
const prepared = await prepareAgentCandidateExecution(verified, task, ports)
|
|
532
|
+
if (prepareOnly) {
|
|
533
|
+
const disposal = await disposePreparedAgentCandidateExecution(prepared)
|
|
534
|
+
if (disposal.disposed !== true) throw new Error('prepared candidate was not disposed')
|
|
535
|
+
process.stdout.write(
|
|
536
|
+
`${JSON.stringify({
|
|
537
|
+
prepared: true,
|
|
538
|
+
disposed: true,
|
|
539
|
+
executionPlanDigest: prepared.executionPlan.value.digest,
|
|
540
|
+
graderDigest: benchmarkTask.grader.artifact.sha256,
|
|
541
|
+
})}\n`,
|
|
542
|
+
)
|
|
543
|
+
} else {
|
|
544
|
+
const traceStore = new InMemoryTraceStore()
|
|
545
|
+
const claimStore = new FileAgentCandidateExecutionClaimStore({
|
|
546
|
+
directory: path.join(scratch, 'claims'),
|
|
547
|
+
})
|
|
548
|
+
let acceptedRewards: { reward: number; patch_applied: number } | undefined
|
|
549
|
+
let acceptedTrialPath: string | undefined
|
|
550
|
+
const jobName = `tangle-runtime-candidate-no-model-${proofArm}`
|
|
551
|
+
const controller = new FilePierCandidateTrialController({
|
|
552
|
+
directory: path.join(scratch, 'trial-control'),
|
|
553
|
+
readResult: async ({ jobsDirectory, jobName }) => {
|
|
554
|
+
const trialResult = findTrialResult(path.join(jobsDirectory, jobName))
|
|
555
|
+
if (!trialResult) return undefined
|
|
556
|
+
return {
|
|
557
|
+
value: trialResult.value,
|
|
558
|
+
resultBytes: readFileSync(trialResult.path),
|
|
559
|
+
taskPatch: readFileSync(
|
|
560
|
+
path.join(path.dirname(trialResult.path), 'artifacts', 'model.patch'),
|
|
561
|
+
),
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
launch: (staged, { request }) => {
|
|
565
|
+
const evaluatorArgs = Object.keys(staged.evaluatorEnv).flatMap((name) => [
|
|
566
|
+
'--agent-env',
|
|
567
|
+
`${name}=\${${name}}`,
|
|
568
|
+
])
|
|
569
|
+
return {
|
|
570
|
+
command: 'uv',
|
|
571
|
+
args: [
|
|
572
|
+
'run',
|
|
573
|
+
'pier',
|
|
574
|
+
'run',
|
|
575
|
+
'--path',
|
|
576
|
+
taskDir,
|
|
577
|
+
...staged.agentArgs,
|
|
578
|
+
...staged.attemptArgs,
|
|
579
|
+
...evaluatorArgs,
|
|
580
|
+
'--env',
|
|
581
|
+
'docker',
|
|
582
|
+
'--job-name',
|
|
583
|
+
jobName,
|
|
584
|
+
'--jobs-dir',
|
|
585
|
+
jobsDir,
|
|
586
|
+
'--n-concurrent',
|
|
587
|
+
'1',
|
|
588
|
+
'--agent-timeout-multiplier',
|
|
589
|
+
'2',
|
|
590
|
+
'--quiet',
|
|
591
|
+
],
|
|
592
|
+
cwd: pierRepo,
|
|
593
|
+
env: { ...process.env, PYTHONPATH: benchDir, ...staged.evaluatorEnv },
|
|
594
|
+
jobsDirectory: jobsDir,
|
|
595
|
+
jobName,
|
|
596
|
+
readResult: async () => {
|
|
597
|
+
const trialResult = findTrialResult(jobsDir)
|
|
598
|
+
if (!trialResult) throw new Error(`Pier emitted no trial result under ${jobsDir}`)
|
|
599
|
+
const result = trialResult.value
|
|
600
|
+
if (result.exception_info !== null) {
|
|
601
|
+
throw new Error(
|
|
602
|
+
`Pier trial captured an exception: ${JSON.stringify(result.exception_info)}`,
|
|
603
|
+
)
|
|
604
|
+
}
|
|
605
|
+
const rewards = result.verifier_result?.rewards
|
|
606
|
+
if (
|
|
607
|
+
rewards?.reward !== expectedReward ||
|
|
608
|
+
rewards?.patch_applied !== expectedPatchApplied
|
|
609
|
+
) {
|
|
610
|
+
throw new Error(
|
|
611
|
+
`Pier ${proofArm} control returned unexpected rewards: ${JSON.stringify(rewards)}`,
|
|
612
|
+
)
|
|
613
|
+
}
|
|
614
|
+
const agentResult = result.agent_result
|
|
615
|
+
for (const name of ['n_input_tokens', 'n_cache_tokens', 'n_output_tokens', 'cost_usd']) {
|
|
616
|
+
if (agentResult?.[name] !== null) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`Pier must not author protected usage ${name}: ${agentResult?.[name]}`,
|
|
619
|
+
)
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const observedElapsedMs = agentResult?.metadata?.observedElapsedMs
|
|
623
|
+
if (!Number.isInteger(observedElapsedMs) || observedElapsedMs < 0) {
|
|
624
|
+
throw new Error(`Pier omitted protected elapsed time: ${observedElapsedMs}`)
|
|
625
|
+
}
|
|
626
|
+
const endedAt = Date.now()
|
|
627
|
+
await traceStore.appendRun({
|
|
628
|
+
runId: request.trace.runId,
|
|
629
|
+
scenarioId: benchmarkTask.scenario.id,
|
|
630
|
+
startedAt: endedAt - observedElapsedMs,
|
|
631
|
+
endedAt,
|
|
632
|
+
status: 'completed',
|
|
633
|
+
tags: { ...request.trace.tags },
|
|
634
|
+
})
|
|
635
|
+
acceptedRewards = rewards
|
|
636
|
+
acceptedTrialPath = path.relative(jobsDir, trialResult.path)
|
|
637
|
+
return {
|
|
638
|
+
value: result,
|
|
639
|
+
resultBytes: readFileSync(trialResult.path),
|
|
640
|
+
taskPatch: readFileSync(
|
|
641
|
+
path.join(path.dirname(trialResult.path), 'artifacts', 'model.patch'),
|
|
642
|
+
),
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
}
|
|
646
|
+
},
|
|
647
|
+
})
|
|
648
|
+
const finalized = await executePreparedPierCandidate({
|
|
649
|
+
prepared,
|
|
650
|
+
directory: path.join(scratch, 'sealed'),
|
|
651
|
+
pierVersion: pinnedPierVersion,
|
|
652
|
+
traceStore,
|
|
653
|
+
claimStore,
|
|
654
|
+
outputArtifacts,
|
|
655
|
+
grader,
|
|
656
|
+
controller,
|
|
657
|
+
})
|
|
658
|
+
if (!finalized.succeeded) {
|
|
659
|
+
throw new Error(`runtime rejected the protected Pier capture: ${finalized.reason}`)
|
|
660
|
+
}
|
|
661
|
+
if (!acceptedRewards || !acceptedTrialPath) {
|
|
662
|
+
throw new Error('atomic Pier executor returned without accepted verifier evidence')
|
|
663
|
+
}
|
|
664
|
+
if (
|
|
665
|
+
finalized.receipt.value.benchmarkResult.material.score !== expectedReward ||
|
|
666
|
+
finalized.receipt.value.benchmarkResult.material.passed !== (expectedReward === 1)
|
|
667
|
+
) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
`runtime receipt rejected the official Pier result: ${JSON.stringify(finalized.receipt.value.benchmarkResult.material)}`,
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
assertTreeOmits(scratch, 'zero-model-proof')
|
|
673
|
+
const usage = finalized.receipt.value.usage
|
|
674
|
+
if (
|
|
675
|
+
usage.modelCalls !== 0 ||
|
|
676
|
+
usage.inputTokens !== 0 ||
|
|
677
|
+
usage.outputTokens !== 0 ||
|
|
678
|
+
usage.costUsd !== 0 ||
|
|
679
|
+
finalized.receipt.value.trace.modelCallCount !== 0
|
|
680
|
+
) {
|
|
681
|
+
throw new Error(`runtime minted nonzero usage for a zero-model run: ${JSON.stringify(usage)}`)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
console.log(
|
|
685
|
+
JSON.stringify(
|
|
686
|
+
{
|
|
687
|
+
arm: proofArm,
|
|
688
|
+
reward: acceptedRewards.reward,
|
|
689
|
+
patchApplied: acceptedRewards.patch_applied,
|
|
690
|
+
modelCalls: usage.modelCalls,
|
|
691
|
+
inputTokens: usage.inputTokens,
|
|
692
|
+
outputTokens: usage.outputTokens,
|
|
693
|
+
costUsd: usage.costUsd,
|
|
694
|
+
pierContextUsage: null,
|
|
695
|
+
profileExcludedByVerifier: true,
|
|
696
|
+
container: {
|
|
697
|
+
image: fixtureImage,
|
|
698
|
+
indexDigest: identity.indexDigest,
|
|
699
|
+
manifestDigest: identity.manifestDigest,
|
|
700
|
+
platform,
|
|
701
|
+
},
|
|
702
|
+
executionPlanDigest: prepared.executionPlan.value.digest,
|
|
703
|
+
materializationReceiptDigest: prepared.materializationReceipt.digest,
|
|
704
|
+
runReceiptDigest: finalized.receipt.digest,
|
|
705
|
+
trial: acceptedTrialPath,
|
|
706
|
+
},
|
|
707
|
+
null,
|
|
708
|
+
2,
|
|
709
|
+
),
|
|
710
|
+
)
|
|
711
|
+
}
|
|
712
|
+
} finally {
|
|
713
|
+
if (process.env.KEEP_PIER_FIXTURE !== '1') rmSync(scratch, { recursive: true, force: true })
|
|
714
|
+
else console.error(`Pier runtime fixture retained at ${scratch}`)
|
|
715
|
+
}
|