@tangle-network/agent-bench 0.1.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.
Files changed (111) hide show
  1. package/README.md +15 -0
  2. package/package.json +35 -0
  3. package/src/adapters.ts +60 -0
  4. package/src/aec-gate.mts +217 -0
  5. package/src/atom-humaneval.mts +197 -0
  6. package/src/atom-mcp-e2e.mts +223 -0
  7. package/src/benchmarks/_harness.ts +206 -0
  8. package/src/benchmarks/aec-bench.test.mts +53 -0
  9. package/src/benchmarks/aec-bench.ts +319 -0
  10. package/src/benchmarks/appworld.test.mts +45 -0
  11. package/src/benchmarks/appworld.ts +426 -0
  12. package/src/benchmarks/cad-design.ts +429 -0
  13. package/src/benchmarks/cadbench.ts +135 -0
  14. package/src/benchmarks/cadgenbench.ts +121 -0
  15. package/src/benchmarks/commit0.test.mts +71 -0
  16. package/src/benchmarks/commit0.ts +266 -0
  17. package/src/benchmarks/enterpriseops-gym.test.mts +77 -0
  18. package/src/benchmarks/enterpriseops-gym.ts +343 -0
  19. package/src/benchmarks/finsearchcomp.ts +371 -0
  20. package/src/benchmarks/frames.ts +520 -0
  21. package/src/benchmarks/hotpotqa.ts +320 -0
  22. package/src/benchmarks/humaneval.ts +251 -0
  23. package/src/benchmarks/mind2web.ts +311 -0
  24. package/src/benchmarks/programbench.test.mts +60 -0
  25. package/src/benchmarks/programbench.ts +210 -0
  26. package/src/benchmarks/simpleqa.ts +390 -0
  27. package/src/benchmarks/swe-bench.ts +152 -0
  28. package/src/benchmarks/terminal-bench.ts +182 -0
  29. package/src/benchmarks/trata-hedge.ts +496 -0
  30. package/src/benchmarks/types.ts +58 -0
  31. package/src/browser/adapters/bad-design-audit.ts +153 -0
  32. package/src/browser/adapters/bad-design-audit.verify.ts +85 -0
  33. package/src/browser/adapters/bad.ts +165 -0
  34. package/src/browser/agent-adapter.ts +145 -0
  35. package/src/browser/process-adapter.ts +146 -0
  36. package/src/browser/run-to-spans.ts +105 -0
  37. package/src/browser/run-to-spans.verify.ts +89 -0
  38. package/src/browser/ui-reviewer.ts +200 -0
  39. package/src/browser/ui-reviewer.verify.ts +94 -0
  40. package/src/browser/verify.ts +96 -0
  41. package/src/clbench-codebase-gate.mts +314 -0
  42. package/src/clbench-context-gate.mts +305 -0
  43. package/src/cloud-loop.mts +138 -0
  44. package/src/coding-skills/minimal-diff.md +9 -0
  45. package/src/coding-skills/read-before-edit.md +9 -0
  46. package/src/coding-skills/reproduce-first.md +10 -0
  47. package/src/coding-skills/run-tests-after-edit.md +9 -0
  48. package/src/coding-skills/trace-the-failure.md +9 -0
  49. package/src/commit0-env-run.mts +59 -0
  50. package/src/commit0-env.ts +173 -0
  51. package/src/commit0-gate.mts +529 -0
  52. package/src/commit0-prereqs.sh +48 -0
  53. package/src/corpus-replay.mts +300 -0
  54. package/src/corpus-report.mts +504 -0
  55. package/src/corpus.test.mts +273 -0
  56. package/src/corpus.ts +325 -0
  57. package/src/decoder-live.mts +133 -0
  58. package/src/directives.ts +84 -0
  59. package/src/diverse-gate.mjs +112 -0
  60. package/src/egress-probe.mts +26 -0
  61. package/src/eops-skills/address-every-subtask.md +5 -0
  62. package/src/eops-skills/exact-tools-and-args.md +5 -0
  63. package/src/eops-skills/full-sequence-to-goal.md +5 -0
  64. package/src/eops-skills/ground-every-value.md +5 -0
  65. package/src/eops-skills/honor-the-policies.md +5 -0
  66. package/src/examples/README.md +58 -0
  67. package/src/examples/math-demo.mts +110 -0
  68. package/src/examples/strategy-demo.mts +119 -0
  69. package/src/fleet.mts +121 -0
  70. package/src/gate-cli.mts +101 -0
  71. package/src/gate.test.mts +129 -0
  72. package/src/gate.ts +460 -0
  73. package/src/generate-eval/certify.ts +178 -0
  74. package/src/generate-eval/schema.ts +78 -0
  75. package/src/humaneval-gate.mts +204 -0
  76. package/src/humaneval-repair-gate.mts +143 -0
  77. package/src/index.ts +19 -0
  78. package/src/mcp-mount-probe.mts +126 -0
  79. package/src/profile-coordinates.ts +134 -0
  80. package/src/profiles.ts +128 -0
  81. package/src/refine-loop.test.mts +106 -0
  82. package/src/refine-loop.ts +106 -0
  83. package/src/research-gate.mts +132 -0
  84. package/src/research-shot.ts +134 -0
  85. package/src/resolve-client.ts +58 -0
  86. package/src/router-executor.ts +51 -0
  87. package/src/run-pool.ts +48 -0
  88. package/src/runtime-hook-recorder.ts +137 -0
  89. package/src/sandbox-run.ts +125 -0
  90. package/src/search-bench/bridge.ts +124 -0
  91. package/src/search-bench/export.mts +0 -0
  92. package/src/search-bench/parametric-check.mts +63 -0
  93. package/src/search-bench/profiles.ts +98 -0
  94. package/src/search-bench/run.mts +287 -0
  95. package/src/search-bench/tasks-fresh.ts +688 -0
  96. package/src/search-bench/tasks.ts +129 -0
  97. package/src/search-tool.ts +95 -0
  98. package/src/selector.test.mts +189 -0
  99. package/src/selector.ts +366 -0
  100. package/src/skill-sandbox-smoke.mts +100 -0
  101. package/src/stats.mts +90 -0
  102. package/src/terminal-compare.ts +519 -0
  103. package/src/trajectory-assemble.mjs +130 -0
  104. package/src/trata-gate.mts +243 -0
  105. package/src/trata-gepa.mts +434 -0
  106. package/src/worker-blender.ts +230 -0
  107. package/src/worker-browser.ts +102 -0
  108. package/src/worker-build123d.ts +143 -0
  109. package/src/worker-cad.ts +451 -0
  110. package/src/worker.ts +136 -0
  111. package/src/workspace-loop.mts +133 -0
@@ -0,0 +1,319 @@
1
+ /**
2
+ * AEC-Bench adapter (TheodoreGalanos/aec-bench, MIT) — closed-form
3
+ * Architecture/Engineering/Construction calculation tasks. Worker artifact = a
4
+ * markdown solution ending in a fenced ```json block with the required numeric
5
+ * fields. Judge = the task's OWN `tests/verify.py`, run with python3: it
6
+ * recomputes ground truth from the embedded engineering formulas, extracts the
7
+ * last JSON block from the artifact, scores each field by math.isclose within a
8
+ * per-field rel_tol, and writes {"reward": mean} + per-field details.json.
9
+ * GRADED / partial-credit, FULLY DETERMINISTIC — no LLM judge.
10
+ *
11
+ * Distinct from the multimodal nomic-ai/aec-bench; this is the deterministic
12
+ * calculation platform. The runnable-instance verify.py only needs python3 (no
13
+ * Docker, no Harbor) for the pure-calc disciplines, so the local gate runs at
14
+ * conc<=2 without a container backend.
15
+ *
16
+ * Requires for a live run: network to raw.githubusercontent.com /
17
+ * api.github.com (the in-repo tasks tree) + a python3 interpreter (the bench
18
+ * venv) to run verify.py. For offline/CI, set AEC_FIXTURES=1 to load the
19
+ * committed fixtures (bench/fixtures/aec-bench.json) — never a silent fallback.
20
+ */
21
+
22
+ import { readFile } from 'node:fs/promises'
23
+ import { join } from 'node:path'
24
+ import { benchRoot, preflightVenvImports, readJsonReport, runStagedJudge, stageFile, venvPython } from './_harness'
25
+ import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
26
+
27
+ const FIXTURES = join(benchRoot, 'fixtures', 'aec-bench.json')
28
+
29
+ const REPO = 'TheodoreGalanos/aec-bench'
30
+ const RAW = `https://raw.githubusercontent.com/${REPO}/main`
31
+ const TREE = `https://api.github.com/repos/${REPO}/git/trees/main?recursive=1`
32
+
33
+ /** Matches every runnable-instance task id at ANY depth under tasks/. */
34
+ const verifyPathPattern = /^tasks\/(.+)\/tests\/verify\.py$/
35
+
36
+ /** Default cap on tasks enumerated before a `limit` is applied. */
37
+ const DEFAULT_LIMIT = 10
38
+
39
+ interface AecRecord {
40
+ /** Canonical task id `<discipline>/<task>`, e.g. 'electrical/catenary-sag'. */
41
+ id: string
42
+ discipline: string
43
+ /** instruction.md — the self-contained prompt (table + required outputs + JSON schema). */
44
+ instruction: string
45
+ /** task.toml — metadata/difficulty/timeouts (carried for trace context). */
46
+ task_toml: string
47
+ /** tests/verify.py — the deterministic verifier (recomputes GT, scores fields). */
48
+ verify_py: string
49
+ /** tests/fixtures/golden_pass.md — the oracle artifact that scores reward 1.0,
50
+ * when the task ships one. Null when the task only ships a non-md ground truth
51
+ * (e.g. tests/ground_truth.json); the judge never needs it, only goldArtifact does. */
52
+ golden_pass_md: string | null
53
+ }
54
+
55
+ interface AecMeta {
56
+ taskId: string
57
+ discipline: string
58
+ taskToml: string
59
+ verifyPy: string
60
+ goldenPassMd: string | null
61
+ }
62
+
63
+ function recordToTask(rec: AecRecord): BenchTask {
64
+ const meta: AecMeta = {
65
+ taskId: rec.id,
66
+ discipline: rec.discipline,
67
+ taskToml: rec.task_toml,
68
+ verifyPy: rec.verify_py,
69
+ goldenPassMd: rec.golden_pass_md,
70
+ }
71
+ return {
72
+ id: rec.id,
73
+ split: rec.discipline,
74
+ // instruction.md is fully self-contained and already specifies the exact JSON
75
+ // output schema + the "write to /workspace/output.md" contract the verifier
76
+ // keys off — we pass it through verbatim so the verify.py extractor matches.
77
+ prompt: rec.instruction,
78
+ metadata: meta as unknown as Record<string, unknown>,
79
+ }
80
+ }
81
+
82
+ function readMeta(task: BenchTask): AecMeta {
83
+ const md = task.metadata
84
+ // Gold is optional (goldenPassMd may be null) — only the verifier + id are required.
85
+ if (!md || typeof md.verifyPy !== 'string' || typeof md.taskId !== 'string') {
86
+ throw new Error(`aec-bench task ${task.id} missing verifier metadata — loadTasks did not populate it`)
87
+ }
88
+ return md as unknown as AecMeta
89
+ }
90
+
91
+ async function fetchText(url: string): Promise<string> {
92
+ const res = await fetch(url)
93
+ if (!res.ok) throw new Error(`aec-bench fetch ${res.status}: ${url}`)
94
+ return res.text()
95
+ }
96
+
97
+ /** Like fetchText but returns null on a 404 (absent optional file); throws on any other non-OK. */
98
+ async function fetchTextOrNull(url: string): Promise<string | null> {
99
+ const res = await fetch(url)
100
+ if (res.status === 404) return null
101
+ if (!res.ok) throw new Error(`aec-bench fetch ${res.status}: ${url}`)
102
+ return res.text()
103
+ }
104
+
105
+ interface GitTree {
106
+ tree: Array<{ path: string; type: string }>
107
+ }
108
+
109
+ /**
110
+ * One recursive git-tree call enumerates EVERY runnable-instance id at any depth:
111
+ * a task is runnable iff it ships tests/verify.py. The captured group is the id
112
+ * `tasks/<id>/tests/verify.py` → `<id>` (e.g. 'electrical/pf-droop', or a deeper
113
+ * '<discipline>/<family>/<task>'). Throws loud on a non-OK tree response.
114
+ */
115
+ async function listAllInstances(): Promise<string[]> {
116
+ const res = await fetch(TREE)
117
+ if (!res.ok) throw new Error(`aec-bench tree ${res.status}: ${TREE}`)
118
+ const { tree } = (await res.json()) as GitTree
119
+ const ids: string[] = []
120
+ for (const entry of tree) {
121
+ const m = verifyPathPattern.exec(entry.path)
122
+ if (m?.[1]) ids.push(m[1])
123
+ }
124
+ return ids
125
+ }
126
+
127
+ /**
128
+ * Fetch one task's instruction.md + task.toml + tests/verify.py + golden_pass.md.
129
+ * Returns null when the dir is a SEED (no runnable verify.py) so enumeration can
130
+ * skip it without faking a task. Gold is OPTIONAL — a task that ships a non-md
131
+ * ground truth (e.g. tests/ground_truth.json) yields golden_pass_md=null; the
132
+ * deterministic judge needs only verify.py.
133
+ */
134
+ async function fetchInstance(id: string): Promise<AecRecord | null> {
135
+ const base = `${RAW}/tasks/${id}`
136
+ const verify = await fetch(`${base}/tests/verify.py`)
137
+ if (verify.status === 404) return null
138
+ if (!verify.ok) throw new Error(`aec-bench fetch ${verify.status}: ${id}/tests/verify.py`)
139
+ const [instruction, task_toml, golden_pass_md] = await Promise.all([
140
+ fetchText(`${base}/instruction.md`),
141
+ fetchText(`${base}/task.toml`),
142
+ fetchTextOrNull(`${base}/tests/fixtures/golden_pass.md`),
143
+ ])
144
+ return {
145
+ id,
146
+ discipline: id.split('/')[0] ?? '',
147
+ instruction,
148
+ task_toml,
149
+ verify_py: await verify.text(),
150
+ golden_pass_md,
151
+ }
152
+ }
153
+
154
+ function selectFixtures(records: AecRecord[], opts: LoadOptions): BenchTask[] {
155
+ let tasks = records.map(recordToTask)
156
+ if (opts.split) tasks = tasks.filter((t) => t.split === opts.split)
157
+ if (opts.ids) {
158
+ const want = new Set(opts.ids)
159
+ tasks = tasks.filter((t) => want.has(t.id))
160
+ } else if (opts.limit !== undefined) {
161
+ tasks = tasks.slice(0, opts.limit)
162
+ }
163
+ return tasks
164
+ }
165
+
166
+ async function loadFixtures(opts: LoadOptions): Promise<BenchTask[]> {
167
+ const records = JSON.parse(await readFile(FIXTURES, 'utf8')) as AecRecord[]
168
+ console.warn(
169
+ `[aec-bench] AEC_FIXTURES=1 — loading ${records.length} committed fixtures from ${FIXTURES} (no GitHub fetch)`,
170
+ )
171
+ return selectFixtures(records, opts)
172
+ }
173
+
174
+ /** Enumerate live tasks: explicit ids (each required — throws on a bad id), or a
175
+ * capped slice of the recursive tree (optionally filtered to one split). Per-task
176
+ * resilient: a single fetch failure warns + SKIPS that task, never aborting the
177
+ * batch. Skips seed dirs (no verify.py) — never fabricates. */
178
+ async function loadLive(opts: LoadOptions): Promise<BenchTask[]> {
179
+ if (opts.ids) {
180
+ const records: AecRecord[] = []
181
+ for (const id of opts.ids) {
182
+ const rec = await fetchInstance(id)
183
+ if (!rec) throw new Error(`aec-bench: ${id} has no tests/verify.py (seed-only or wrong id)`)
184
+ records.push(rec)
185
+ }
186
+ return records.map(recordToTask)
187
+ }
188
+ const limit = opts.limit ?? DEFAULT_LIMIT
189
+ let ids = await listAllInstances()
190
+ if (opts.split) ids = ids.filter((id) => id.startsWith(`${opts.split}/`))
191
+ const records: AecRecord[] = []
192
+ for (const id of ids) {
193
+ if (records.length >= limit) break
194
+ try {
195
+ const rec = await fetchInstance(id)
196
+ if (rec) records.push(rec)
197
+ } catch (err) {
198
+ // One bad task must NEVER abort the batch — warn and skip it.
199
+ console.warn(`[aec-bench] skipping ${id}: ${err instanceof Error ? err.message : String(err)}`)
200
+ }
201
+ }
202
+ if (records.length === 0) {
203
+ throw new Error(
204
+ `aec-bench loadTasks found no runnable instances for ${JSON.stringify(opts)} ` +
205
+ `(no tests/verify.py matched the requested split). Set AEC_FIXTURES=1 to run offline.`,
206
+ )
207
+ }
208
+ return records.map(recordToTask)
209
+ }
210
+
211
+ /**
212
+ * Run the task's own verify.py with python3 over the artifact via the shared
213
+ * staged-judge spine (mkdtemp → stage → spawn → parseReport → cleanup). verify.py
214
+ * writes {"reward": mean} to --output and per-field details.json as a sibling. We
215
+ * read both: reward → graded score; details → sub-scores for the trace-analyst.
216
+ * Fail loud if the verifier never wrote a numeric reward (a crashed verifier is
217
+ * NOT a silent 0 — verify.py's own except-trap writes reward 0.0, so an absent /
218
+ * non-numeric reward.json is a real bug, surfaced by readJsonReport).
219
+ */
220
+ async function runVerifier(meta: AecMeta, artifact: string): Promise<BenchScore> {
221
+ return runStagedJudge({
222
+ tmpPrefix: 'aecbench-',
223
+ timeoutMs: 120_000,
224
+ async stage(dir) {
225
+ await Promise.all([
226
+ stageFile(join(dir, 'output.md'), artifact),
227
+ stageFile(join(dir, 'verify.py'), meta.verifyPy),
228
+ ])
229
+ },
230
+ bin: venvPython,
231
+ argv: (dir) => [join(dir, 'verify.py'), '--input', join(dir, 'output.md'), '--output', join(dir, 'reward.json')],
232
+ async parseReport(dir) {
233
+ const report = await readJsonReport<{ reward?: number }>(join(dir, 'reward.json'))
234
+ const reward = report.reward
235
+ if (typeof reward !== 'number' || !Number.isFinite(reward)) {
236
+ throw new Error(`aec-bench verify.py wrote no numeric reward for ${meta.taskId}: ${JSON.stringify(report)}`)
237
+ }
238
+ const details = await readFile(join(dir, 'details.json'), 'utf8').then(
239
+ (s) => JSON.parse(s) as Record<string, number>,
240
+ () => ({}),
241
+ )
242
+ const score = Math.max(0, Math.min(1, reward))
243
+ return {
244
+ // resolved = full credit (all fields within tolerance), matching aec-bench's perfect_rate.
245
+ resolved: score >= 1,
246
+ score,
247
+ detail: JSON.stringify({ taskId: meta.taskId, discipline: meta.discipline, reward, fields: details }),
248
+ }
249
+ },
250
+ })
251
+ }
252
+
253
+ export function createAecBenchAdapter(): BenchmarkAdapter {
254
+ const fixturesMode = process.env.AEC_FIXTURES === '1'
255
+
256
+ return {
257
+ name: 'aec-bench',
258
+
259
+ async preflight() {
260
+ // The verifier is python3 over the stdlib (math/json/re) — no pip install.
261
+ // Reuse the shared import-probe so the bench venv interpreter is proven to
262
+ // exist + run before any judge spawns verify.py.
263
+ await preflightVenvImports({
264
+ modules: ['math', 'json', 're'],
265
+ requireDocker: false,
266
+ fix: 'Fix: python3 -m venv bench/.venv (verify.py only needs the stdlib — no pip install).',
267
+ })
268
+ if (fixturesMode) {
269
+ await readFile(FIXTURES, 'utf8').catch((err) => {
270
+ throw new Error(`AEC_FIXTURES=1 but ${FIXTURES} unreadable: ${err instanceof Error ? err.message : err}`)
271
+ })
272
+ return
273
+ }
274
+ const res = await fetch(`${RAW}/README.md`, { method: 'HEAD' }).catch((err) => {
275
+ throw new Error(
276
+ `aec-bench preflight failed reaching ${RAW}: ${err instanceof Error ? err.message : err}\n` +
277
+ `Fix: ensure network access to raw.githubusercontent.com, or set AEC_FIXTURES=1 to run offline.`,
278
+ )
279
+ })
280
+ if (!res.ok) {
281
+ throw new Error(
282
+ `aec-bench preflight: ${REPO} README HEAD ${res.status}. Set AEC_FIXTURES=1 to run against committed fixtures.`,
283
+ )
284
+ }
285
+ },
286
+
287
+ async loadTasks(opts: LoadOptions = {}) {
288
+ if (fixturesMode) return loadFixtures(opts)
289
+ try {
290
+ return await loadLive(opts)
291
+ } catch (err) {
292
+ // A reachability failure falls back to fixtures with an explicit warn; a
293
+ // wrong-id / seed-only error (the loader's own throw) propagates.
294
+ if (err instanceof Error && /fetch \d|ENOTFOUND|getaddrinfo|network/i.test(err.message)) {
295
+ console.warn(
296
+ `[aec-bench] live fetch failed (${err.message.slice(0, 160)}); falling back to committed fixtures at ${FIXTURES}`,
297
+ )
298
+ return loadFixtures(opts)
299
+ }
300
+ throw err
301
+ }
302
+ },
303
+
304
+ async goldArtifact(task: BenchTask) {
305
+ // Gold = the task's own golden_pass.md (scores reward 1.0 through the SAME
306
+ // verify.py the real artifact takes), proving the judge end-to-end. Tasks
307
+ // without a golden_pass.md (non-md ground truth) have no oracle artifact.
308
+ const meta = readMeta(task)
309
+ return meta.goldenPassMd ?? undefined
310
+ },
311
+
312
+ async judge(task: BenchTask, artifact: string): Promise<BenchScore> {
313
+ const meta = readMeta(task)
314
+ // verify.py fail-closes an empty/unparseable artifact to reward 0.0 itself,
315
+ // so we pass it straight through (no pre-judging here).
316
+ return runVerifier(meta, artifact)
317
+ },
318
+ }
319
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Offline AppWorld adapter test. AppWorld has NO committed fixture (task data
3
+ * exists only after `appworld download data`; fabricating a task would be a fake),
4
+ * so loadTasks/judge both require the live engine. This exercises the only offline
5
+ * surface (the solution OutputAdapter, goldArtifact) and asserts preflight + the
6
+ * engine-backed loadTasks FAIL LOUD with the documented install steps — never a
7
+ * fabricated task or score. Run: npx tsx --test src/benchmarks/appworld.test.mts
8
+ */
9
+ import assert from 'node:assert/strict'
10
+ import { test } from 'node:test'
11
+ import { appworldSolutionOutput, createAppWorldAdapter } from './appworld'
12
+
13
+ type Events = Parameters<typeof appworldSolutionOutput.parse>[0]
14
+ const stream = (text: string): Events => [{ data: { finalText: text } }] as unknown as Events
15
+
16
+ test('solution OutputAdapter: last fenced ```python wins; fence-less falls back to trimmed text', () => {
17
+ const fenced = appworldSolutionOutput.parse(stream('plan\n```python\napis.supervisor.complete_task()\n```\n'))
18
+ assert.equal(fenced, 'apis.supervisor.complete_task()')
19
+ const last = appworldSolutionOutput.parse(stream('```python\nFIRST\n```\nmid\n```py\nSECOND\n```'))
20
+ assert.equal(last, 'SECOND')
21
+ const raw = appworldSolutionOutput.parse(stream(' bare code '))
22
+ assert.equal(raw, 'bare code')
23
+ })
24
+
25
+ test('goldArtifact is undefined — reference solution ships only inside the engine bundle, not portable', async () => {
26
+ const a = createAppWorldAdapter()
27
+ assert.equal(await a.goldArtifact({ id: 't', prompt: '', metadata: { taskId: 't', split: 'dev' } }), undefined)
28
+ })
29
+
30
+ test('preflight FAILS LOUD with the install + download-data fix when the engine is absent', async () => {
31
+ const a = createAppWorldAdapter()
32
+ await assert.rejects(a.preflight(), (e: Error) => {
33
+ assert.match(e.message, /pip install appworld/)
34
+ assert.match(e.message, /appworld download data/)
35
+ return true
36
+ })
37
+ })
38
+
39
+ test('loadTasks FAILS LOUD (engine enumeration) rather than fabricating tasks offline', async () => {
40
+ const a = createAppWorldAdapter()
41
+ await assert.rejects(a.loadTasks({ limit: 1 }), (e: Error) => {
42
+ assert.match(e.message, /appworld driver failed|appworld import failed/)
43
+ return true
44
+ })
45
+ })