windows2t2 ShadowPenguin commited on
Commit
1866d85
·
0 Parent(s):

Duplicate from markov-ai/autocad-bench

Browse files

Co-authored-by: Dev Mandal <ShadowPenguin@users.noreply.huggingface.co>

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +17 -0
  2. .gitattributes +37 -0
  3. .gitignore +30 -0
  4. .python-version +1 -0
  5. CONTRIBUTING.md +89 -0
  6. README.md +182 -0
  7. apps/dashboard/README.md +17 -0
  8. apps/dashboard/backfill-trace-summaries.mjs +85 -0
  9. apps/dashboard/cloud-metadata.mjs +123 -0
  10. apps/dashboard/index.html +14 -0
  11. apps/dashboard/package-lock.json +1910 -0
  12. apps/dashboard/package.json +26 -0
  13. apps/dashboard/server.d.mts +1 -0
  14. apps/dashboard/server.mjs +372 -0
  15. apps/dashboard/src/App.vue +722 -0
  16. apps/dashboard/src/main.ts +5 -0
  17. apps/dashboard/src/styles.css +451 -0
  18. apps/dashboard/src/types.ts +223 -0
  19. apps/dashboard/tests/cloud-metadata.test.mjs +117 -0
  20. apps/dashboard/tests/trace-api.test.mjs +872 -0
  21. apps/dashboard/trace-api.d.mts +71 -0
  22. apps/dashboard/trace-api.mjs +1080 -0
  23. apps/dashboard/tsconfig.app.json +17 -0
  24. apps/dashboard/tsconfig.json +7 -0
  25. apps/dashboard/tsconfig.node.json +15 -0
  26. apps/dashboard/vite.config.ts +22 -0
  27. apps/results-dashboard/.env.example +6 -0
  28. apps/results-dashboard/README.md +17 -0
  29. apps/results-dashboard/index.html +14 -0
  30. apps/results-dashboard/package-lock.json +1910 -0
  31. apps/results-dashboard/package.json +25 -0
  32. apps/results-dashboard/results-api.mjs +714 -0
  33. apps/results-dashboard/server.mjs +71 -0
  34. apps/results-dashboard/src/App.vue +831 -0
  35. apps/results-dashboard/src/components/ReliableImage.vue +91 -0
  36. apps/results-dashboard/src/env.d.ts +7 -0
  37. apps/results-dashboard/src/main.ts +5 -0
  38. apps/results-dashboard/src/styles.css +400 -0
  39. apps/results-dashboard/src/types.ts +164 -0
  40. apps/results-dashboard/tests/results-api.test.mjs +131 -0
  41. apps/results-dashboard/tsconfig.app.json +22 -0
  42. apps/results-dashboard/tsconfig.json +7 -0
  43. apps/results-dashboard/tsconfig.node.json +17 -0
  44. apps/results-dashboard/vite.config.ts +15 -0
  45. artifacts/gold-cache/README.md +31 -0
  46. artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-001/metadata.json +0 -0
  47. artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-001/render.png +3 -0
  48. artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-002/metadata.json +0 -0
  49. artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-002/render.png +3 -0
  50. artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-003/metadata.json +0 -0
.env.example ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Model providers
2
+ OPENAI_API_KEY=
3
+ ANTHROPIC_API_KEY=
4
+ OPENROUTER_API_KEY=
5
+ FIREWORKS_API_KEY=
6
+
7
+ # AWS control plane
8
+ # AWS_PROFILE=my-profile
9
+ AWS_REGION=us-east-1
10
+
11
+ # Optional when invoking an installed wheel outside the repository root
12
+ AUTOCAD_BENCH_ROOT=
13
+
14
+ # Optional dashboard cloud artifact retrieval
15
+ # AUTOCAD_BENCH_S3_BUCKET=your-autocad-bench-bucket
16
+ AUTOCAD_BENCH_S3_RUNS_PREFIX=controller/runs
17
+ AUTOCAD_BENCH_CLOUD_ARTIFACTS=off
.gitattributes ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ *.png filter=lfs diff=lfs merge=lfs -text
37
+ *.dwg filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .venv/
2
+ node_modules/
3
+ .env
4
+ .env.*
5
+ !.env.example
6
+ .private/
7
+ .pytest_cache/
8
+ __pycache__/
9
+ *.py[cod]
10
+ .DS_Store
11
+ dist/
12
+ runs/
13
+ artifacts/gold-cache/*/
14
+ artifacts/gold-cache/summary.json
15
+ !artifacts/gold-cache/autocad-2019-r23.0.46-v4/
16
+ !artifacts/gold-cache/autocad-2019-r23.0.46-v4/**
17
+ apps/dashboard/dist/
18
+ apps/dashboard/.tsbuild/
19
+ apps/dashboard/*.tsbuildinfo
20
+ apps/dashboard/*.d.ts
21
+ apps/dashboard/vite.config.js
22
+ apps/dashboard/src/*.js
23
+ apps/results-dashboard/.tsbuild/
24
+ apps/results-dashboard/*.tsbuildinfo
25
+ apps/results-dashboard/*.d.ts
26
+ apps/results-dashboard/vite.config.js
27
+ apps/results-dashboard/src/**/*.js
28
+ apps/results-dashboard/src/**/*.d.ts
29
+ !apps/results-dashboard/src/env.d.ts
30
+ archive/source/*.zip
.python-version ADDED
@@ -0,0 +1 @@
 
 
1
+ 3.12.12
CONTRIBUTING.md ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contributing
2
+
3
+ AutoCAD Bench separates task data, model execution, trusted evaluation, and infrastructure. Keep changes inside the narrowest applicable boundary and preserve the scorer/model trust split.
4
+
5
+ ## Development setup
6
+
7
+ ```bash
8
+ uv sync --dev
9
+ cd apps/dashboard && npm ci
10
+ ```
11
+
12
+ ## Common changes
13
+
14
+ ### Add or update a task
15
+
16
+ 1. Add the reference PNG and gold DWG under `tasks/corpus/2d/` or `tasks/corpus/3d/`.
17
+ 2. Add or update the canonical row in `tasks/manifest.jsonl`.
18
+ 3. Update `tasks/gold-audit.jsonl` with verified file metadata.
19
+ 4. Rebuild the gold cache with the pinned trusted evaluator.
20
+ 5. Run corpus, audit, cache, and test validation.
21
+
22
+ Never expose a gold path through `PublicTaskSpec`, `AutoCADTaskData`, a model prompt, or a model trace.
23
+
24
+ ### Change the harness
25
+
26
+ Model-provider logic and the agent loop belong under `src/autocad_bench/harness/`. Computer actions must remain typed and fully validated before any broker call. Preserve the 7,000-action rollout budget and the distinction between audit-only intermediate frames and model-visible observations.
27
+
28
+ ### Change evaluation
29
+
30
+ Trusted evaluation belongs under `src/autocad_bench/evaluation/`. A render-contract change requires a new evaluator version and gold cache; never reinterpret an old version in place. Windows evaluator changes also require a compiled fresh-clone gate before an AMI becomes active.
31
+
32
+ ### Change infrastructure
33
+
34
+ Backend selection, registration, and lifecycle boundaries belong under
35
+ `src/autocad_bench/infrastructure/`. Keep the batch controller
36
+ backend-neutral. New external backends should implement the public plugin
37
+ contract and register an `autocad_bench.infrastructure` entry point; see
38
+ `docs/infrastructure-plugins.md`. Backends that retain reconnectable workers
39
+ should also implement the optional recovery protocol; do not put
40
+ provider-specific worker inspection back into `orchestration/resume.py`.
41
+
42
+ The built-in AWS lifecycle and broker-client code remains under
43
+ `src/autocad_bench/sandbox/`. Windows runtime source belongs under
44
+ `sandbox/windows/`; cloud-controller assets belong under
45
+ `infra/cloud-controller/`. Do not launch instances, deploy infrastructure, or
46
+ replace the active AMI without explicit approval.
47
+
48
+ ## Validation
49
+
50
+ Run the complete offline gate before handing off a change:
51
+
52
+ ```bash
53
+ uv run autocad-bench validate
54
+ uv run autocad-bench validate-audit
55
+ uv run pytest
56
+
57
+ cd apps/dashboard
58
+ npm test
59
+ npm run build
60
+ ```
61
+
62
+ Before publishing, build the package and corpus into one new release directory,
63
+ then verify that complete directory:
64
+
65
+ ```bash
66
+ uv run autocad-bench-release build \
67
+ --output dist/releases/0.1.0
68
+ uv run autocad-bench-release verify \
69
+ --root dist/releases/0.1.0
70
+ ```
71
+
72
+ The builder uses the allowlist-based bundle exporter and pinned PEP 517
73
+ dependencies. It must never be expanded to include configs, environment files,
74
+ controller state, or run outputs. See `docs/releases.md` for the complete
75
+ manual publishing and reproducibility workflow.
76
+
77
+ When a current evaluator cache is available locally:
78
+
79
+ ```bash
80
+ uv run autocad-bench verify-gold-cache \
81
+ --evaluator-version autocad-2019-r23.0.46-v4 \
82
+ --split all
83
+ ```
84
+
85
+ Live provider or AWS checks are separate from the offline suite and require explicit credentials.
86
+
87
+ ## Documentation
88
+
89
+ Update the relevant contract whenever behavior changes. Release evidence is append-only: add a new release record instead of editing historical results to describe a newer runtime.
README.md ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ tags:
3
+ - benchmark
4
+ - computer-use
5
+ - autocad
6
+ - cad
7
+ - multimodal
8
+ ---
9
+
10
+ # AutoCAD Bench
11
+
12
+ AutoCAD Bench is a computer-use benchmark for evaluating whether multimodal agents can reproduce dimensioned 2D drawings and reconstruct dimensionally accurate 3D models in desktop AutoCAD.
13
+
14
+ Each rollout receives a task-specific instruction and reference image, operates an isolated AutoCAD session using mouse and keyboard actions, and is evaluated from the final DWG. Gold drawings and evaluator credentials never enter the model-visible session.
15
+
16
+ ## How it works
17
+
18
+ ```text
19
+ tasks/manifest.jsonl + reference PNG
20
+ │
21
+ ▼
22
+ model harness + agent loop
23
+ │
24
+ ▼
25
+ isolated Windows/AutoCAD sandbox
26
+ │
27
+ ▼
28
+ final attempt.dwg
29
+ │
30
+ ▼
31
+ trusted inspection + deterministic score + vision judgment
32
+ │
33
+ ▼
34
+ runs/<benchmark>/ + dashboard
35
+ ```
36
+
37
+ The benchmark contains exactly 50 tasks, numbered `task-001` through
38
+ `task-050` with no gaps: 21 2D drawings and 29 3D drawings. A 2D task is
39
+ completed in Model Space. A 3D task requires true geometry in Model Space and
40
+ the complete reference sheet on `Layout1`; evaluator v4 renders and compares
41
+ that required layout.
42
+
43
+ ## Getting started
44
+
45
+ Requirements:
46
+
47
+ - Python 3.12
48
+ - [uv](https://docs.astral.sh/uv/)
49
+ - Node.js 20+ for the dashboard
50
+ - Infrastructure-backend and model-provider credentials only for live runs
51
+
52
+ Install the Python environment and validate the corpus:
53
+
54
+ ```bash
55
+ # The development group includes the built-in AWS backend dependencies.
56
+ uv sync --dev
57
+ uv run autocad-bench validate
58
+ uv run autocad-bench validate-audit
59
+ uv run autocad-bench list --split all
60
+ ```
61
+
62
+ Published installations that use the built-in backend should install
63
+ `autocad-bench[aws]`. Custom backends can depend on the base package and their
64
+ own infrastructure plugin without installing the AWS SDK.
65
+
66
+ The wheel contains code, while scorer-side benchmark data can come from either
67
+ a checkout or a checksum-bound bundle:
68
+
69
+ ```bash
70
+ autocad-bench bundle-validate --root /path/to/autocad-bench-corpus-v1
71
+ export AUTOCAD_BENCH_ROOT=/path/to/autocad-bench-corpus-v1
72
+ ```
73
+
74
+ See [Portable benchmark bundles](docs/benchmark-bundles.md) for the data
75
+ format and [Manual releases](docs/releases.md) for reproducible package and
76
+ corpus publishing.
77
+
78
+ Run the offline tests:
79
+
80
+ ```bash
81
+ uv run pytest
82
+ cd apps/dashboard
83
+ npm ci
84
+ npm test
85
+ npm run build
86
+ ```
87
+
88
+ ## Run a benchmark
89
+
90
+ Copy the AWS example and replace every infrastructure, bucket, and model
91
+ placeholder with resources you own:
92
+
93
+ ```bash
94
+ cp configs/aws.example.json configs/my-benchmark.json
95
+
96
+ uv run autocad-bench-batch \
97
+ --config configs/my-benchmark.json \
98
+ --skip-direct-model-check
99
+ ```
100
+
101
+ Execute only after provider credentials are exported and preflight passes:
102
+
103
+ ```bash
104
+ export OPENAI_API_KEY=...
105
+
106
+ uv run autocad-bench-batch \
107
+ --config configs/my-benchmark.json \
108
+ --output-dir runs/benchmarks/<run-id> \
109
+ --execute
110
+ ```
111
+
112
+ Every rollout is evaluated automatically after its final DWG is retrieved and before its sandbox is cleaned up. Evaluation progress is written atomically for the dashboard.
113
+
114
+ ## Inspect results
115
+
116
+ ```bash
117
+ cd apps/dashboard
118
+ npm ci
119
+ npm run build
120
+ npm run preview
121
+ ```
122
+
123
+ Open [http://127.0.0.1:4173](http://127.0.0.1:4173). The viewer reads local `runs/` data and can lazily retrieve missing trace artifacts from the encrypted benchmark bucket when configured.
124
+
125
+ ## Repository layout
126
+
127
+ ```text
128
+ autoCAD-bench/
129
+ ├── tasks/ # Scorer-side manifest, audit inventory, and source corpus
130
+ ├── src/autocad_bench/
131
+ │ ├── tasks/ # Task loading, validation, and public task models
132
+ │ ├── harness/ # Agent loop, model calls, and single-run execution
133
+ │ ├── evaluation/ # Trusted DWG evaluation and scoring
134
+ │ ├── infrastructure/ # Backend contract, registry, and built-in AWS plugin
135
+ │ ├── sandbox/ # Broker client and AWS/AutoCAD lifecycle
136
+ │ ├── orchestration/ # Parallel batches, resume, and recovery
137
+ │ └── common/ # Shared utilities
138
+ ├── sandbox/windows/ # Windows broker, evaluator, and AMI bootstrap source
139
+ ├── infra/cloud-controller/ # Persistent controller deployment assets
140
+ ├── configs/ # Bring-your-own infrastructure examples
141
+ ├── apps/dashboard/ # Local benchmark registry and trace viewer
142
+ ├── artifacts/gold-cache/ # Versioned trusted evaluator outputs
143
+ ├── docs/ # Contracts, architecture, operations, and releases
144
+ ├── tests/ # Offline regression suite
145
+ └── runs/ # Generated benchmark results; ignored by git
146
+ ```
147
+
148
+ ## Active runtime contract
149
+
150
+ - Broker: `windows-autocad-2019-v10`
151
+ - Evaluator: `autocad-2019-r23.0.46-v4`
152
+ - Per-task action budget: 5,000
153
+ - Action batch size: bounded only by the remaining rollout budget
154
+
155
+ Worker images are operator-owned and must satisfy the pinned broker contract.
156
+ Broker v11 adds controller-owned DWG save but remains a separate candidate
157
+ runtime. The complete evaluator-v4 cache contains and verifies all 50 source
158
+ DWGs.
159
+
160
+ ## Documentation
161
+
162
+ | Guide | Purpose |
163
+ |---|---|
164
+ | [Architecture](docs/architecture.md) | Component boundaries and data flow |
165
+ | [Benchmark contract](docs/benchmark-contract.md) | Model-visible and scorer-side task contract |
166
+ | [Sandbox contract](docs/deterministic-sandbox-contract.md) | AWS lifecycle, admission, and cleanup invariants |
167
+ | [Evaluator contract](docs/autocad-evaluator-contract.md) | DWG inspection and `Layout1` behavior |
168
+ | [Scoring](docs/final-dwg-scorer.md) | Deterministic and vision scoring |
169
+ | [Parallel rollouts](docs/parallel-rollouts.md) | Batch preflight and execution |
170
+ | [Infrastructure plugins](docs/infrastructure-plugins.md) | Bring-your-own worker backend contract |
171
+ | [Benchmark bundles](docs/benchmark-bundles.md) | Portable task and evaluator-data releases |
172
+ | [Manual releases](docs/releases.md) | Reproducible local assembly and verification |
173
+ | [Cloud controller](docs/cloud-controller.md) | Cloud-owned execution and recovery |
174
+ | [Contributing](CONTRIBUTING.md) | Development and validation workflow |
175
+
176
+ ## Security boundary
177
+
178
+ `tasks/manifest.jsonl` and the corpus are scorer-side inputs and contain paths
179
+ to gold DWGs that are private from the model session. Model-visible task
180
+ serialization excludes gold paths and DWG names. A rollout receives only its
181
+ reference image and task statement; trusted evaluator exchange uses
182
+ request-scoped presigned S3 objects after model execution.
apps/dashboard/README.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Benchmark dashboard
2
+
3
+ The Vue dashboard is a read-only registry and trace viewer for every local benchmark under `runs/`. It displays normalized rollout outcomes, live evaluation stages, task evidence, model turns, action receipts, usage, cost, and timing.
4
+
5
+ ```bash
6
+ npm ci
7
+ npm test
8
+ npm run build
9
+ npm run preview
10
+ ```
11
+
12
+ The production viewer binds to `127.0.0.1:4173`. Set
13
+ `AUTOCAD_BENCH_RUNS_ROOT` to inspect another run tree. Optional cloud artifact
14
+ retrieval is disabled unless `AUTOCAD_BENCH_S3_BUCKET` is set. It uses
15
+ `AUTOCAD_BENCH_S3_RUNS_PREFIX`, optional `AWS_PROFILE`, and optional
16
+ `AWS_REGION`; without a named profile, the AWS SDK uses its standard credential
17
+ chain.
apps/dashboard/backfill-trace-summaries.mjs ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import readline from 'node:readline'
4
+ import { once } from 'node:events'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ import { _test } from './trace-api.mjs'
8
+
9
+ const dashboardRoot = path.dirname(fileURLToPath(import.meta.url))
10
+ const targetRoot = path.resolve(
11
+ process.argv[2] || path.join(dashboardRoot, '..', '..', 'runs'),
12
+ )
13
+
14
+ function discover(directory, output = []) {
15
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
16
+ const candidate = path.join(directory, entry.name)
17
+ if (entry.isDirectory()) discover(candidate, output)
18
+ else if (entry.isFile() && entry.name === 'model_turns.jsonl') output.push(candidate)
19
+ }
20
+ return output
21
+ }
22
+
23
+ async function writeSummary(sourcePath) {
24
+ const destination = path.join(path.dirname(sourcePath), 'model_turn_summaries.jsonl')
25
+ const sourceBefore = fs.statSync(sourcePath)
26
+ try {
27
+ const existing = fs.statSync(destination)
28
+ if (existing.mtimeMs >= sourceBefore.mtimeMs && existing.size > 0) {
29
+ return { sourcePath, destination, status: 'current', turns: null }
30
+ }
31
+ } catch {
32
+ // A missing index is expected on legacy traces.
33
+ }
34
+
35
+ const temporary = `${destination}.tmp`
36
+ const input = fs.createReadStream(sourcePath, { encoding: 'utf8' })
37
+ const lines = readline.createInterface({ input, crlfDelay: Infinity })
38
+ const output = fs.createWriteStream(temporary, { encoding: 'utf8' })
39
+ let turns = 0
40
+ try {
41
+ for await (const line of lines) {
42
+ if (!line.trim()) continue
43
+ let turn
44
+ try {
45
+ turn = JSON.parse(line.replace(/^\uFEFF/, ''))
46
+ } catch {
47
+ continue
48
+ }
49
+ if (!output.write(`${JSON.stringify(_test.compactTurn(turn))}\n`)) {
50
+ await once(output, 'drain')
51
+ }
52
+ turns += 1
53
+ }
54
+ output.end()
55
+ await once(output, 'finish')
56
+ const sourceAfter = fs.statSync(sourcePath)
57
+ if (
58
+ sourceAfter.size !== sourceBefore.size
59
+ || sourceAfter.mtimeMs !== sourceBefore.mtimeMs
60
+ ) {
61
+ fs.rmSync(temporary, { force: true })
62
+ return { sourcePath, destination, status: 'changed_during_index', turns }
63
+ }
64
+ fs.renameSync(temporary, destination)
65
+ return { sourcePath, destination, status: 'indexed', turns }
66
+ } catch (error) {
67
+ output.destroy()
68
+ fs.rmSync(temporary, { force: true })
69
+ throw error
70
+ }
71
+ }
72
+
73
+ const sources = fs.statSync(targetRoot).isFile()
74
+ ? [targetRoot]
75
+ : discover(targetRoot)
76
+
77
+ let indexed = 0
78
+ let current = 0
79
+ for (const source of sources) {
80
+ const result = await writeSummary(source)
81
+ if (result.status === 'indexed') indexed += 1
82
+ if (result.status === 'current') current += 1
83
+ process.stdout.write(`${JSON.stringify(result)}\n`)
84
+ }
85
+ process.stdout.write(`${JSON.stringify({ indexed, current, total: sources.length })}\n`)
apps/dashboard/cloud-metadata.mjs ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ const BENCHMARK_ROLLOUT_METADATA = [
5
+ 'run-state.json',
6
+ 'result.json',
7
+ 'evaluation/status.json',
8
+ 'evaluation/score.json',
9
+ 'evaluation/vision-judge/score.json',
10
+ ]
11
+
12
+ const TRACE_METADATA = [
13
+ 'run-state.json',
14
+ 'result.json',
15
+ 'model_turn_summaries.jsonl',
16
+ 'input_receipts.jsonl',
17
+ 'evaluation/status.json',
18
+ 'evaluation/score.json',
19
+ 'evaluation/vision-judge/score.json',
20
+ ]
21
+
22
+ export function benchmarkMetadataPaths(state) {
23
+ const rolloutIds = state?.rollouts && typeof state.rollouts === 'object'
24
+ ? Object.keys(state.rollouts)
25
+ : []
26
+ return [
27
+ 'batch-state.json',
28
+ ...rolloutIds.flatMap((rolloutId) => BENCHMARK_ROLLOUT_METADATA.map(
29
+ (relativePath) => `rollouts/${rolloutId}/${relativePath}`,
30
+ )),
31
+ ]
32
+ }
33
+
34
+ export function traceMetadataPaths() {
35
+ return [...TRACE_METADATA]
36
+ }
37
+
38
+ function readJson(filePath) {
39
+ try {
40
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
41
+ } catch {
42
+ return null
43
+ }
44
+ }
45
+
46
+ export function metadataUpdatedAt(filePath) {
47
+ const value = readJson(filePath)
48
+ const timestamp = Date.parse(value?.updated_at || '')
49
+ return Number.isFinite(timestamp) ? timestamp : null
50
+ }
51
+
52
+ export function evaluatorVersionRank(value) {
53
+ const match = String(value || '').match(/-v(\d+)$/i)
54
+ return match ? Number(match[1]) : null
55
+ }
56
+
57
+ function evaluatorVersionForMetadata(filePath) {
58
+ const value = readJson(filePath)
59
+ const direct = value?.evaluator_version || value?.evaluation?.evaluator_version
60
+ if (direct) return direct
61
+
62
+ const marker = `${path.sep}evaluation${path.sep}`
63
+ const markerIndex = filePath.lastIndexOf(marker)
64
+ if (markerIndex < 0) return null
65
+ const evaluationRoot = filePath.slice(0, markerIndex + marker.length - 1)
66
+ for (const name of ['status.json', 'metadata.json']) {
67
+ const sibling = readJson(path.join(evaluationRoot, name))
68
+ if (sibling?.evaluator_version) return sibling.evaluator_version
69
+ }
70
+ return null
71
+ }
72
+
73
+ function rescoreJobForMetadata(filePath) {
74
+ const value = readJson(filePath)
75
+ const direct = value?.rescore_job_id || value?.evaluation?.rescore_job_id
76
+ if (direct) return direct
77
+
78
+ const marker = `${path.sep}evaluation${path.sep}`
79
+ const markerIndex = filePath.lastIndexOf(marker)
80
+ if (markerIndex < 0) return null
81
+ const rolloutRoot = filePath.slice(0, markerIndex)
82
+ return readJson(path.join(rolloutRoot, 'result.json'))?.evaluation
83
+ ?.rescore_job_id || null
84
+ }
85
+
86
+ export function shouldPromoteMetadata(source, destination) {
87
+ if (!fs.existsSync(destination)) return true
88
+ if (path.extname(source) === '.jsonl') {
89
+ // Live JSONL traces are append-only. Never let an older S3 snapshot
90
+ // truncate a controller-local trace when the dashboard runs in-cloud.
91
+ return fs.statSync(source).size >= fs.statSync(destination).size
92
+ }
93
+
94
+ const sourceRank = evaluatorVersionRank(evaluatorVersionForMetadata(source))
95
+ const destinationRank = evaluatorVersionRank(
96
+ evaluatorVersionForMetadata(destination),
97
+ )
98
+ if (sourceRank != null && destinationRank != null) {
99
+ if (sourceRank < destinationRank) return false
100
+ if (sourceRank > destinationRank) return true
101
+ }
102
+
103
+
104
+ // A completed local rescore is an immutable, job-bound snapshot. The shared
105
+ // mirror can temporarily contain an earlier attempt from the same rescore
106
+ // job, and those files often have no useful top-level updated_at timestamp.
107
+ // Keep the local terminal snapshot when evaluator versions are equal.
108
+ const destinationRescoreJob = rescoreJobForMetadata(destination)
109
+ if (destinationRescoreJob) {
110
+ const sourceRescoreJob = rescoreJobForMetadata(source)
111
+ if (sourceRescoreJob !== destinationRescoreJob) return false
112
+ if (sourceRank == null || destinationRank == null || sourceRank === destinationRank) {
113
+ return false
114
+ }
115
+ }
116
+
117
+ const sourceUpdatedAt = metadataUpdatedAt(source)
118
+ const destinationUpdatedAt = metadataUpdatedAt(destination)
119
+ if (sourceUpdatedAt != null && destinationUpdatedAt != null) {
120
+ return sourceUpdatedAt >= destinationUpdatedAt
121
+ }
122
+ return true
123
+ }
apps/dashboard/index.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="dark" />
7
+ <meta name="description" content="Inspect AutoCAD computer-use rollouts and full benchmark suites." />
8
+ <title>AutoCAD Evaluation Console</title>
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ <script type="module" src="/src/main.ts"></script>
13
+ </body>
14
+ </html>
apps/dashboard/package-lock.json ADDED
@@ -0,0 +1,1910 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "autocad-bench-trace-dashboard",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "autocad-bench-trace-dashboard",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "@aws-sdk/client-s3": "3.1090.0",
12
+ "@aws-sdk/credential-providers": "3.1090.0",
13
+ "@aws-sdk/s3-request-presigner": "3.1090.0",
14
+ "vue": "3.5.40"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "20.19.43",
18
+ "@vitejs/plugin-vue": "5.2.4",
19
+ "typescript": "5.9.3",
20
+ "vite": "6.4.3",
21
+ "vue-tsc": "3.3.7"
22
+ }
23
+ },
24
+ "node_modules/@aws-sdk/checksums": {
25
+ "version": "3.1000.18",
26
+ "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.18.tgz",
27
+ "integrity": "sha512-IImkbEyXdV6/uaF5r6Wkk+8718mQw1ll83j0a4a30R3JM/rHVFdWAiT4jtJpFjJiIwM/oJ6SxIxr0z2TaQUGqw==",
28
+ "license": "Apache-2.0",
29
+ "dependencies": {
30
+ "@aws-sdk/core": "^3.975.3",
31
+ "@aws-sdk/types": "^3.974.2",
32
+ "@smithy/core": "^3.29.4",
33
+ "@smithy/types": "^4.16.1",
34
+ "tslib": "^2.6.2"
35
+ },
36
+ "engines": {
37
+ "node": ">=20.0.0"
38
+ }
39
+ },
40
+ "node_modules/@aws-sdk/client-s3": {
41
+ "version": "3.1090.0",
42
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1090.0.tgz",
43
+ "integrity": "sha512-R6GX9cd1jljwzZ8xFmgAI/hHCuX1MobIKBdsymv7WL9SENvO9Vgz9KOR6avTnu0Ao+w1LmxnTe+jqmZXEn7Q/Q==",
44
+ "license": "Apache-2.0",
45
+ "dependencies": {
46
+ "@aws-sdk/checksums": "^3.1000.18",
47
+ "@aws-sdk/core": "^3.975.3",
48
+ "@aws-sdk/credential-provider-node": "^3.972.70",
49
+ "@aws-sdk/middleware-sdk-s3": "^3.972.64",
50
+ "@aws-sdk/signature-v4-multi-region": "^3.996.41",
51
+ "@aws-sdk/types": "^3.974.2",
52
+ "@smithy/core": "^3.29.4",
53
+ "@smithy/fetch-http-handler": "^5.6.6",
54
+ "@smithy/node-http-handler": "^4.9.6",
55
+ "@smithy/types": "^4.16.1",
56
+ "tslib": "^2.6.2"
57
+ },
58
+ "engines": {
59
+ "node": ">=20.0.0"
60
+ }
61
+ },
62
+ "node_modules/@aws-sdk/core": {
63
+ "version": "3.975.3",
64
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.3.tgz",
65
+ "integrity": "sha512-7ur3kCKuvPLqlsZ2XlvnNBVQ7KkpSu6Y6dOTwSPHLrFpTEfZM8isLBJc4cgv96WB7GifeVM436mpycwxBd2vEA==",
66
+ "license": "Apache-2.0",
67
+ "dependencies": {
68
+ "@aws-sdk/types": "^3.974.2",
69
+ "@aws-sdk/xml-builder": "^3.972.36",
70
+ "@aws/lambda-invoke-store": "^0.3.0",
71
+ "@smithy/core": "^3.29.4",
72
+ "@smithy/signature-v4": "^5.6.5",
73
+ "@smithy/types": "^4.16.1",
74
+ "bowser": "^2.11.0",
75
+ "tslib": "^2.6.2"
76
+ },
77
+ "engines": {
78
+ "node": ">=20.0.0"
79
+ }
80
+ },
81
+ "node_modules/@aws-sdk/credential-provider-cognito-identity": {
82
+ "version": "3.972.58",
83
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.58.tgz",
84
+ "integrity": "sha512-s5uoABv5eOzuH/S+XngHjHSrY8mK0UTBUFs8pm1ynBNuxXmYp176zarDyxN9lUS3Rry0wjzNvJUV09QROaO98g==",
85
+ "license": "Apache-2.0",
86
+ "dependencies": {
87
+ "@aws-sdk/nested-clients": "^3.997.33",
88
+ "@aws-sdk/types": "^3.974.2",
89
+ "@smithy/core": "^3.29.4",
90
+ "@smithy/types": "^4.16.1",
91
+ "tslib": "^2.6.2"
92
+ },
93
+ "engines": {
94
+ "node": ">=20.0.0"
95
+ }
96
+ },
97
+ "node_modules/@aws-sdk/credential-provider-env": {
98
+ "version": "3.972.59",
99
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.59.tgz",
100
+ "integrity": "sha512-Ny5e4Mfh3QPmiAc0AiUe+cbTXDlxkU3Rc+EpWOfyWeWEy6yp7Fa1KmfNeCc+1a8by9zQ9gtohmiQUkMPScF3ng==",
101
+ "license": "Apache-2.0",
102
+ "dependencies": {
103
+ "@aws-sdk/core": "^3.975.3",
104
+ "@aws-sdk/types": "^3.974.2",
105
+ "@smithy/core": "^3.29.4",
106
+ "@smithy/types": "^4.16.1",
107
+ "tslib": "^2.6.2"
108
+ },
109
+ "engines": {
110
+ "node": ">=20.0.0"
111
+ }
112
+ },
113
+ "node_modules/@aws-sdk/credential-provider-http": {
114
+ "version": "3.972.61",
115
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.61.tgz",
116
+ "integrity": "sha512-8jAjgStl5Ytq4+HF3X/9f+EmRinaRbGRRtQGktlPfBRVx73H+R1y48vIeXerQtYGFaUqkEp3fT6jP854rVO2yQ==",
117
+ "license": "Apache-2.0",
118
+ "dependencies": {
119
+ "@aws-sdk/core": "^3.975.3",
120
+ "@aws-sdk/types": "^3.974.2",
121
+ "@smithy/core": "^3.29.4",
122
+ "@smithy/fetch-http-handler": "^5.6.6",
123
+ "@smithy/node-http-handler": "^4.9.6",
124
+ "@smithy/types": "^4.16.1",
125
+ "tslib": "^2.6.2"
126
+ },
127
+ "engines": {
128
+ "node": ">=20.0.0"
129
+ }
130
+ },
131
+ "node_modules/@aws-sdk/credential-provider-ini": {
132
+ "version": "3.973.4",
133
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.4.tgz",
134
+ "integrity": "sha512-e6ZvVsj90aRALf1kHP+J4iqC1496ZpVgqI/+u0LJ5HL7q7ATauGy4gdDvRCP13L1pN/fMiZLah162PGIYkbUVQ==",
135
+ "license": "Apache-2.0",
136
+ "dependencies": {
137
+ "@aws-sdk/core": "^3.975.3",
138
+ "@aws-sdk/credential-provider-env": "^3.972.59",
139
+ "@aws-sdk/credential-provider-http": "^3.972.61",
140
+ "@aws-sdk/credential-provider-login": "^3.972.66",
141
+ "@aws-sdk/credential-provider-process": "^3.972.59",
142
+ "@aws-sdk/credential-provider-sso": "^3.973.3",
143
+ "@aws-sdk/credential-provider-web-identity": "^3.972.65",
144
+ "@aws-sdk/nested-clients": "^3.997.33",
145
+ "@aws-sdk/types": "^3.974.2",
146
+ "@smithy/core": "^3.29.4",
147
+ "@smithy/credential-provider-imds": "^4.4.9",
148
+ "@smithy/types": "^4.16.1",
149
+ "tslib": "^2.6.2"
150
+ },
151
+ "engines": {
152
+ "node": ">=20.0.0"
153
+ }
154
+ },
155
+ "node_modules/@aws-sdk/credential-provider-login": {
156
+ "version": "3.972.66",
157
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.66.tgz",
158
+ "integrity": "sha512-g2fsqm87r/nKthLZ0VkkDBElkGg0PvSa8d97HQ6EilMbJTZ6hxa8FxkSZyJfgPfFdZn0TTmkOffQmTSUcAHIng==",
159
+ "license": "Apache-2.0",
160
+ "dependencies": {
161
+ "@aws-sdk/core": "^3.975.3",
162
+ "@aws-sdk/nested-clients": "^3.997.33",
163
+ "@aws-sdk/types": "^3.974.2",
164
+ "@smithy/core": "^3.29.4",
165
+ "@smithy/types": "^4.16.1",
166
+ "tslib": "^2.6.2"
167
+ },
168
+ "engines": {
169
+ "node": ">=20.0.0"
170
+ }
171
+ },
172
+ "node_modules/@aws-sdk/credential-provider-node": {
173
+ "version": "3.972.70",
174
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.70.tgz",
175
+ "integrity": "sha512-3xzvkGdykBunxqh8WudmUpSyLWvIhfI6aBQo1b5rb3mDO5mNLadK+0hiI0qBQBMVynJbfLO+Ajy9dztMwy9O8w==",
176
+ "license": "Apache-2.0",
177
+ "dependencies": {
178
+ "@aws-sdk/credential-provider-env": "^3.972.59",
179
+ "@aws-sdk/credential-provider-http": "^3.972.61",
180
+ "@aws-sdk/credential-provider-ini": "^3.973.4",
181
+ "@aws-sdk/credential-provider-process": "^3.972.59",
182
+ "@aws-sdk/credential-provider-sso": "^3.973.3",
183
+ "@aws-sdk/credential-provider-web-identity": "^3.972.65",
184
+ "@aws-sdk/types": "^3.974.2",
185
+ "@smithy/core": "^3.29.4",
186
+ "@smithy/credential-provider-imds": "^4.4.9",
187
+ "@smithy/types": "^4.16.1",
188
+ "tslib": "^2.6.2"
189
+ },
190
+ "engines": {
191
+ "node": ">=20.0.0"
192
+ }
193
+ },
194
+ "node_modules/@aws-sdk/credential-provider-process": {
195
+ "version": "3.972.59",
196
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.59.tgz",
197
+ "integrity": "sha512-DlZF2/MhLlatDdlrIy3CUCpfdbLrKx+3SMjVo+WyHnPpwzkc/M3vwAHw4OVJf7DMvO+4vfRqSCMc/E9I1auN0g==",
198
+ "license": "Apache-2.0",
199
+ "dependencies": {
200
+ "@aws-sdk/core": "^3.975.3",
201
+ "@aws-sdk/types": "^3.974.2",
202
+ "@smithy/core": "^3.29.4",
203
+ "@smithy/types": "^4.16.1",
204
+ "tslib": "^2.6.2"
205
+ },
206
+ "engines": {
207
+ "node": ">=20.0.0"
208
+ }
209
+ },
210
+ "node_modules/@aws-sdk/credential-provider-sso": {
211
+ "version": "3.973.3",
212
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.3.tgz",
213
+ "integrity": "sha512-hmdDHoy2G5Es2e8IgelNMYUuSQI6uCIAKZMJ2u2PdKDhxvbk1uWD/g4+R7R5c/tJfKEB1+KjjWiaoCr/S+ZTiQ==",
214
+ "license": "Apache-2.0",
215
+ "dependencies": {
216
+ "@aws-sdk/core": "^3.975.3",
217
+ "@aws-sdk/nested-clients": "^3.997.33",
218
+ "@aws-sdk/token-providers": "3.1088.0",
219
+ "@aws-sdk/types": "^3.974.2",
220
+ "@smithy/core": "^3.29.4",
221
+ "@smithy/types": "^4.16.1",
222
+ "tslib": "^2.6.2"
223
+ },
224
+ "engines": {
225
+ "node": ">=20.0.0"
226
+ }
227
+ },
228
+ "node_modules/@aws-sdk/credential-provider-web-identity": {
229
+ "version": "3.972.65",
230
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.65.tgz",
231
+ "integrity": "sha512-gHQb/Kt0chjk/JQDa/GJDqmAvEuVn8n7z10wK2h0LFM9TUDRkohgOO4aEF+s2sBLM0br7Cl5W6P7phgjrrJvLQ==",
232
+ "license": "Apache-2.0",
233
+ "dependencies": {
234
+ "@aws-sdk/core": "^3.975.3",
235
+ "@aws-sdk/nested-clients": "^3.997.33",
236
+ "@aws-sdk/types": "^3.974.2",
237
+ "@smithy/core": "^3.29.4",
238
+ "@smithy/types": "^4.16.1",
239
+ "tslib": "^2.6.2"
240
+ },
241
+ "engines": {
242
+ "node": ">=20.0.0"
243
+ }
244
+ },
245
+ "node_modules/@aws-sdk/credential-providers": {
246
+ "version": "3.1090.0",
247
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1090.0.tgz",
248
+ "integrity": "sha512-HEfLv6RdiFu8+HO5CUQjjQ/KCWWAHcQCtSPenaF26lYQSjjzKjgvjwAMGBV9/VMrO8V0faIRyoFWhF548UjTOg==",
249
+ "license": "Apache-2.0",
250
+ "dependencies": {
251
+ "@aws-sdk/core": "^3.975.3",
252
+ "@aws-sdk/credential-provider-cognito-identity": "^3.972.58",
253
+ "@aws-sdk/credential-provider-env": "^3.972.59",
254
+ "@aws-sdk/credential-provider-http": "^3.972.61",
255
+ "@aws-sdk/credential-provider-ini": "^3.973.4",
256
+ "@aws-sdk/credential-provider-login": "^3.972.66",
257
+ "@aws-sdk/credential-provider-node": "^3.972.70",
258
+ "@aws-sdk/credential-provider-process": "^3.972.59",
259
+ "@aws-sdk/credential-provider-sso": "^3.973.3",
260
+ "@aws-sdk/credential-provider-web-identity": "^3.972.65",
261
+ "@aws-sdk/nested-clients": "^3.997.33",
262
+ "@aws-sdk/types": "^3.974.2",
263
+ "@smithy/core": "^3.29.4",
264
+ "@smithy/credential-provider-imds": "^4.4.9",
265
+ "@smithy/types": "^4.16.1",
266
+ "tslib": "^2.6.2"
267
+ },
268
+ "engines": {
269
+ "node": ">=20.0.0"
270
+ }
271
+ },
272
+ "node_modules/@aws-sdk/middleware-sdk-s3": {
273
+ "version": "3.972.64",
274
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.64.tgz",
275
+ "integrity": "sha512-RBi43anhDBUv+HCfxCOXwGOE7GmT4n7ChV04Mwr22RhXTNcamW/iWnJlOotDPCZSrJ4dEvhZSiWWQMwLX+ZhFA==",
276
+ "license": "Apache-2.0",
277
+ "dependencies": {
278
+ "@aws-sdk/core": "^3.975.3",
279
+ "@aws-sdk/signature-v4-multi-region": "^3.996.41",
280
+ "@aws-sdk/types": "^3.974.2",
281
+ "@smithy/core": "^3.29.4",
282
+ "@smithy/types": "^4.16.1",
283
+ "tslib": "^2.6.2"
284
+ },
285
+ "engines": {
286
+ "node": ">=20.0.0"
287
+ }
288
+ },
289
+ "node_modules/@aws-sdk/nested-clients": {
290
+ "version": "3.997.33",
291
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.33.tgz",
292
+ "integrity": "sha512-dVZOroI/r3/ENvqNGgjMPul+jjlz9GddfVusgTXlVjfZj5isibOxecLkGQbRPp8XOuX+RAfjXLFgPkD1JS5xrw==",
293
+ "license": "Apache-2.0",
294
+ "dependencies": {
295
+ "@aws-sdk/core": "^3.975.3",
296
+ "@aws-sdk/signature-v4-multi-region": "^3.996.41",
297
+ "@aws-sdk/types": "^3.974.2",
298
+ "@smithy/core": "^3.29.4",
299
+ "@smithy/fetch-http-handler": "^5.6.6",
300
+ "@smithy/node-http-handler": "^4.9.6",
301
+ "@smithy/types": "^4.16.1",
302
+ "tslib": "^2.6.2"
303
+ },
304
+ "engines": {
305
+ "node": ">=20.0.0"
306
+ }
307
+ },
308
+ "node_modules/@aws-sdk/s3-request-presigner": {
309
+ "version": "3.1090.0",
310
+ "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1090.0.tgz",
311
+ "integrity": "sha512-DaYXp8GMptWJUDbvpoB51xZztOw6tcbpXjYAZMXTu17E0aR+5g8FWBU3t3t0KEhhrUiKyfLpVi6uNQxNSXfbhg==",
312
+ "license": "Apache-2.0",
313
+ "dependencies": {
314
+ "@aws-sdk/core": "^3.975.3",
315
+ "@aws-sdk/signature-v4-multi-region": "^3.996.41",
316
+ "@aws-sdk/types": "^3.974.2",
317
+ "@smithy/core": "^3.29.4",
318
+ "@smithy/types": "^4.16.1",
319
+ "tslib": "^2.6.2"
320
+ },
321
+ "engines": {
322
+ "node": ">=20.0.0"
323
+ }
324
+ },
325
+ "node_modules/@aws-sdk/signature-v4-multi-region": {
326
+ "version": "3.996.41",
327
+ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.41.tgz",
328
+ "integrity": "sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==",
329
+ "license": "Apache-2.0",
330
+ "dependencies": {
331
+ "@aws-sdk/types": "^3.974.2",
332
+ "@smithy/signature-v4": "^5.6.5",
333
+ "@smithy/types": "^4.16.1",
334
+ "tslib": "^2.6.2"
335
+ },
336
+ "engines": {
337
+ "node": ">=20.0.0"
338
+ }
339
+ },
340
+ "node_modules/@aws-sdk/token-providers": {
341
+ "version": "3.1088.0",
342
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1088.0.tgz",
343
+ "integrity": "sha512-4ObatWt2qpJg5FBk4LOOKrTQYzaqeewAtdO3r9ZO8lH9YqLtpTzLyIdy0mJ+nVdfYOnqISkKNfmzP22bNDhwyw==",
344
+ "license": "Apache-2.0",
345
+ "dependencies": {
346
+ "@aws-sdk/core": "^3.975.3",
347
+ "@aws-sdk/nested-clients": "^3.997.33",
348
+ "@aws-sdk/types": "^3.974.2",
349
+ "@smithy/core": "^3.29.4",
350
+ "@smithy/types": "^4.16.1",
351
+ "tslib": "^2.6.2"
352
+ },
353
+ "engines": {
354
+ "node": ">=20.0.0"
355
+ }
356
+ },
357
+ "node_modules/@aws-sdk/types": {
358
+ "version": "3.974.2",
359
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz",
360
+ "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==",
361
+ "license": "Apache-2.0",
362
+ "dependencies": {
363
+ "@smithy/types": "^4.16.1",
364
+ "tslib": "^2.6.2"
365
+ },
366
+ "engines": {
367
+ "node": ">=20.0.0"
368
+ }
369
+ },
370
+ "node_modules/@aws-sdk/xml-builder": {
371
+ "version": "3.972.36",
372
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.36.tgz",
373
+ "integrity": "sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==",
374
+ "license": "Apache-2.0",
375
+ "dependencies": {
376
+ "@smithy/types": "^4.16.1",
377
+ "tslib": "^2.6.2"
378
+ },
379
+ "engines": {
380
+ "node": ">=20.0.0"
381
+ }
382
+ },
383
+ "node_modules/@aws/lambda-invoke-store": {
384
+ "version": "0.3.0",
385
+ "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz",
386
+ "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==",
387
+ "license": "Apache-2.0",
388
+ "engines": {
389
+ "node": ">=18.0.0"
390
+ }
391
+ },
392
+ "node_modules/@babel/helper-string-parser": {
393
+ "version": "7.29.7",
394
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
395
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
396
+ "license": "MIT",
397
+ "engines": {
398
+ "node": ">=6.9.0"
399
+ }
400
+ },
401
+ "node_modules/@babel/helper-validator-identifier": {
402
+ "version": "7.29.7",
403
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
404
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
405
+ "license": "MIT",
406
+ "engines": {
407
+ "node": ">=6.9.0"
408
+ }
409
+ },
410
+ "node_modules/@babel/parser": {
411
+ "version": "7.29.7",
412
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
413
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
414
+ "license": "MIT",
415
+ "dependencies": {
416
+ "@babel/types": "^7.29.7"
417
+ },
418
+ "bin": {
419
+ "parser": "bin/babel-parser.js"
420
+ },
421
+ "engines": {
422
+ "node": ">=6.0.0"
423
+ }
424
+ },
425
+ "node_modules/@babel/types": {
426
+ "version": "7.29.7",
427
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
428
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
429
+ "license": "MIT",
430
+ "dependencies": {
431
+ "@babel/helper-string-parser": "^7.29.7",
432
+ "@babel/helper-validator-identifier": "^7.29.7"
433
+ },
434
+ "engines": {
435
+ "node": ">=6.9.0"
436
+ }
437
+ },
438
+ "node_modules/@esbuild/aix-ppc64": {
439
+ "version": "0.25.12",
440
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
441
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
442
+ "cpu": [
443
+ "ppc64"
444
+ ],
445
+ "dev": true,
446
+ "license": "MIT",
447
+ "optional": true,
448
+ "os": [
449
+ "aix"
450
+ ],
451
+ "engines": {
452
+ "node": ">=18"
453
+ }
454
+ },
455
+ "node_modules/@esbuild/android-arm": {
456
+ "version": "0.25.12",
457
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
458
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
459
+ "cpu": [
460
+ "arm"
461
+ ],
462
+ "dev": true,
463
+ "license": "MIT",
464
+ "optional": true,
465
+ "os": [
466
+ "android"
467
+ ],
468
+ "engines": {
469
+ "node": ">=18"
470
+ }
471
+ },
472
+ "node_modules/@esbuild/android-arm64": {
473
+ "version": "0.25.12",
474
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
475
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
476
+ "cpu": [
477
+ "arm64"
478
+ ],
479
+ "dev": true,
480
+ "license": "MIT",
481
+ "optional": true,
482
+ "os": [
483
+ "android"
484
+ ],
485
+ "engines": {
486
+ "node": ">=18"
487
+ }
488
+ },
489
+ "node_modules/@esbuild/android-x64": {
490
+ "version": "0.25.12",
491
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
492
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
493
+ "cpu": [
494
+ "x64"
495
+ ],
496
+ "dev": true,
497
+ "license": "MIT",
498
+ "optional": true,
499
+ "os": [
500
+ "android"
501
+ ],
502
+ "engines": {
503
+ "node": ">=18"
504
+ }
505
+ },
506
+ "node_modules/@esbuild/darwin-arm64": {
507
+ "version": "0.25.12",
508
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
509
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
510
+ "cpu": [
511
+ "arm64"
512
+ ],
513
+ "dev": true,
514
+ "license": "MIT",
515
+ "optional": true,
516
+ "os": [
517
+ "darwin"
518
+ ],
519
+ "engines": {
520
+ "node": ">=18"
521
+ }
522
+ },
523
+ "node_modules/@esbuild/darwin-x64": {
524
+ "version": "0.25.12",
525
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
526
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
527
+ "cpu": [
528
+ "x64"
529
+ ],
530
+ "dev": true,
531
+ "license": "MIT",
532
+ "optional": true,
533
+ "os": [
534
+ "darwin"
535
+ ],
536
+ "engines": {
537
+ "node": ">=18"
538
+ }
539
+ },
540
+ "node_modules/@esbuild/freebsd-arm64": {
541
+ "version": "0.25.12",
542
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
543
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
544
+ "cpu": [
545
+ "arm64"
546
+ ],
547
+ "dev": true,
548
+ "license": "MIT",
549
+ "optional": true,
550
+ "os": [
551
+ "freebsd"
552
+ ],
553
+ "engines": {
554
+ "node": ">=18"
555
+ }
556
+ },
557
+ "node_modules/@esbuild/freebsd-x64": {
558
+ "version": "0.25.12",
559
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
560
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
561
+ "cpu": [
562
+ "x64"
563
+ ],
564
+ "dev": true,
565
+ "license": "MIT",
566
+ "optional": true,
567
+ "os": [
568
+ "freebsd"
569
+ ],
570
+ "engines": {
571
+ "node": ">=18"
572
+ }
573
+ },
574
+ "node_modules/@esbuild/linux-arm": {
575
+ "version": "0.25.12",
576
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
577
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
578
+ "cpu": [
579
+ "arm"
580
+ ],
581
+ "dev": true,
582
+ "license": "MIT",
583
+ "optional": true,
584
+ "os": [
585
+ "linux"
586
+ ],
587
+ "engines": {
588
+ "node": ">=18"
589
+ }
590
+ },
591
+ "node_modules/@esbuild/linux-arm64": {
592
+ "version": "0.25.12",
593
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
594
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
595
+ "cpu": [
596
+ "arm64"
597
+ ],
598
+ "dev": true,
599
+ "license": "MIT",
600
+ "optional": true,
601
+ "os": [
602
+ "linux"
603
+ ],
604
+ "engines": {
605
+ "node": ">=18"
606
+ }
607
+ },
608
+ "node_modules/@esbuild/linux-ia32": {
609
+ "version": "0.25.12",
610
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
611
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
612
+ "cpu": [
613
+ "ia32"
614
+ ],
615
+ "dev": true,
616
+ "license": "MIT",
617
+ "optional": true,
618
+ "os": [
619
+ "linux"
620
+ ],
621
+ "engines": {
622
+ "node": ">=18"
623
+ }
624
+ },
625
+ "node_modules/@esbuild/linux-loong64": {
626
+ "version": "0.25.12",
627
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
628
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
629
+ "cpu": [
630
+ "loong64"
631
+ ],
632
+ "dev": true,
633
+ "license": "MIT",
634
+ "optional": true,
635
+ "os": [
636
+ "linux"
637
+ ],
638
+ "engines": {
639
+ "node": ">=18"
640
+ }
641
+ },
642
+ "node_modules/@esbuild/linux-mips64el": {
643
+ "version": "0.25.12",
644
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
645
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
646
+ "cpu": [
647
+ "mips64el"
648
+ ],
649
+ "dev": true,
650
+ "license": "MIT",
651
+ "optional": true,
652
+ "os": [
653
+ "linux"
654
+ ],
655
+ "engines": {
656
+ "node": ">=18"
657
+ }
658
+ },
659
+ "node_modules/@esbuild/linux-ppc64": {
660
+ "version": "0.25.12",
661
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
662
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
663
+ "cpu": [
664
+ "ppc64"
665
+ ],
666
+ "dev": true,
667
+ "license": "MIT",
668
+ "optional": true,
669
+ "os": [
670
+ "linux"
671
+ ],
672
+ "engines": {
673
+ "node": ">=18"
674
+ }
675
+ },
676
+ "node_modules/@esbuild/linux-riscv64": {
677
+ "version": "0.25.12",
678
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
679
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
680
+ "cpu": [
681
+ "riscv64"
682
+ ],
683
+ "dev": true,
684
+ "license": "MIT",
685
+ "optional": true,
686
+ "os": [
687
+ "linux"
688
+ ],
689
+ "engines": {
690
+ "node": ">=18"
691
+ }
692
+ },
693
+ "node_modules/@esbuild/linux-s390x": {
694
+ "version": "0.25.12",
695
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
696
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
697
+ "cpu": [
698
+ "s390x"
699
+ ],
700
+ "dev": true,
701
+ "license": "MIT",
702
+ "optional": true,
703
+ "os": [
704
+ "linux"
705
+ ],
706
+ "engines": {
707
+ "node": ">=18"
708
+ }
709
+ },
710
+ "node_modules/@esbuild/linux-x64": {
711
+ "version": "0.25.12",
712
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
713
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
714
+ "cpu": [
715
+ "x64"
716
+ ],
717
+ "dev": true,
718
+ "license": "MIT",
719
+ "optional": true,
720
+ "os": [
721
+ "linux"
722
+ ],
723
+ "engines": {
724
+ "node": ">=18"
725
+ }
726
+ },
727
+ "node_modules/@esbuild/netbsd-arm64": {
728
+ "version": "0.25.12",
729
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
730
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
731
+ "cpu": [
732
+ "arm64"
733
+ ],
734
+ "dev": true,
735
+ "license": "MIT",
736
+ "optional": true,
737
+ "os": [
738
+ "netbsd"
739
+ ],
740
+ "engines": {
741
+ "node": ">=18"
742
+ }
743
+ },
744
+ "node_modules/@esbuild/netbsd-x64": {
745
+ "version": "0.25.12",
746
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
747
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
748
+ "cpu": [
749
+ "x64"
750
+ ],
751
+ "dev": true,
752
+ "license": "MIT",
753
+ "optional": true,
754
+ "os": [
755
+ "netbsd"
756
+ ],
757
+ "engines": {
758
+ "node": ">=18"
759
+ }
760
+ },
761
+ "node_modules/@esbuild/openbsd-arm64": {
762
+ "version": "0.25.12",
763
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
764
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
765
+ "cpu": [
766
+ "arm64"
767
+ ],
768
+ "dev": true,
769
+ "license": "MIT",
770
+ "optional": true,
771
+ "os": [
772
+ "openbsd"
773
+ ],
774
+ "engines": {
775
+ "node": ">=18"
776
+ }
777
+ },
778
+ "node_modules/@esbuild/openbsd-x64": {
779
+ "version": "0.25.12",
780
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
781
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
782
+ "cpu": [
783
+ "x64"
784
+ ],
785
+ "dev": true,
786
+ "license": "MIT",
787
+ "optional": true,
788
+ "os": [
789
+ "openbsd"
790
+ ],
791
+ "engines": {
792
+ "node": ">=18"
793
+ }
794
+ },
795
+ "node_modules/@esbuild/openharmony-arm64": {
796
+ "version": "0.25.12",
797
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
798
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
799
+ "cpu": [
800
+ "arm64"
801
+ ],
802
+ "dev": true,
803
+ "license": "MIT",
804
+ "optional": true,
805
+ "os": [
806
+ "openharmony"
807
+ ],
808
+ "engines": {
809
+ "node": ">=18"
810
+ }
811
+ },
812
+ "node_modules/@esbuild/sunos-x64": {
813
+ "version": "0.25.12",
814
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
815
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
816
+ "cpu": [
817
+ "x64"
818
+ ],
819
+ "dev": true,
820
+ "license": "MIT",
821
+ "optional": true,
822
+ "os": [
823
+ "sunos"
824
+ ],
825
+ "engines": {
826
+ "node": ">=18"
827
+ }
828
+ },
829
+ "node_modules/@esbuild/win32-arm64": {
830
+ "version": "0.25.12",
831
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
832
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
833
+ "cpu": [
834
+ "arm64"
835
+ ],
836
+ "dev": true,
837
+ "license": "MIT",
838
+ "optional": true,
839
+ "os": [
840
+ "win32"
841
+ ],
842
+ "engines": {
843
+ "node": ">=18"
844
+ }
845
+ },
846
+ "node_modules/@esbuild/win32-ia32": {
847
+ "version": "0.25.12",
848
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
849
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
850
+ "cpu": [
851
+ "ia32"
852
+ ],
853
+ "dev": true,
854
+ "license": "MIT",
855
+ "optional": true,
856
+ "os": [
857
+ "win32"
858
+ ],
859
+ "engines": {
860
+ "node": ">=18"
861
+ }
862
+ },
863
+ "node_modules/@esbuild/win32-x64": {
864
+ "version": "0.25.12",
865
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
866
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
867
+ "cpu": [
868
+ "x64"
869
+ ],
870
+ "dev": true,
871
+ "license": "MIT",
872
+ "optional": true,
873
+ "os": [
874
+ "win32"
875
+ ],
876
+ "engines": {
877
+ "node": ">=18"
878
+ }
879
+ },
880
+ "node_modules/@jridgewell/sourcemap-codec": {
881
+ "version": "1.5.5",
882
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
883
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
884
+ "license": "MIT"
885
+ },
886
+ "node_modules/@rollup/rollup-android-arm-eabi": {
887
+ "version": "4.62.2",
888
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
889
+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
890
+ "cpu": [
891
+ "arm"
892
+ ],
893
+ "dev": true,
894
+ "license": "MIT",
895
+ "optional": true,
896
+ "os": [
897
+ "android"
898
+ ]
899
+ },
900
+ "node_modules/@rollup/rollup-android-arm64": {
901
+ "version": "4.62.2",
902
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
903
+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
904
+ "cpu": [
905
+ "arm64"
906
+ ],
907
+ "dev": true,
908
+ "license": "MIT",
909
+ "optional": true,
910
+ "os": [
911
+ "android"
912
+ ]
913
+ },
914
+ "node_modules/@rollup/rollup-darwin-arm64": {
915
+ "version": "4.62.2",
916
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
917
+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
918
+ "cpu": [
919
+ "arm64"
920
+ ],
921
+ "dev": true,
922
+ "license": "MIT",
923
+ "optional": true,
924
+ "os": [
925
+ "darwin"
926
+ ]
927
+ },
928
+ "node_modules/@rollup/rollup-darwin-x64": {
929
+ "version": "4.62.2",
930
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
931
+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
932
+ "cpu": [
933
+ "x64"
934
+ ],
935
+ "dev": true,
936
+ "license": "MIT",
937
+ "optional": true,
938
+ "os": [
939
+ "darwin"
940
+ ]
941
+ },
942
+ "node_modules/@rollup/rollup-freebsd-arm64": {
943
+ "version": "4.62.2",
944
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
945
+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
946
+ "cpu": [
947
+ "arm64"
948
+ ],
949
+ "dev": true,
950
+ "license": "MIT",
951
+ "optional": true,
952
+ "os": [
953
+ "freebsd"
954
+ ]
955
+ },
956
+ "node_modules/@rollup/rollup-freebsd-x64": {
957
+ "version": "4.62.2",
958
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
959
+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
960
+ "cpu": [
961
+ "x64"
962
+ ],
963
+ "dev": true,
964
+ "license": "MIT",
965
+ "optional": true,
966
+ "os": [
967
+ "freebsd"
968
+ ]
969
+ },
970
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
971
+ "version": "4.62.2",
972
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
973
+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
974
+ "cpu": [
975
+ "arm"
976
+ ],
977
+ "dev": true,
978
+ "license": "MIT",
979
+ "optional": true,
980
+ "os": [
981
+ "linux"
982
+ ]
983
+ },
984
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
985
+ "version": "4.62.2",
986
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
987
+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
988
+ "cpu": [
989
+ "arm"
990
+ ],
991
+ "dev": true,
992
+ "license": "MIT",
993
+ "optional": true,
994
+ "os": [
995
+ "linux"
996
+ ]
997
+ },
998
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
999
+ "version": "4.62.2",
1000
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
1001
+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
1002
+ "cpu": [
1003
+ "arm64"
1004
+ ],
1005
+ "dev": true,
1006
+ "license": "MIT",
1007
+ "optional": true,
1008
+ "os": [
1009
+ "linux"
1010
+ ]
1011
+ },
1012
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
1013
+ "version": "4.62.2",
1014
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
1015
+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
1016
+ "cpu": [
1017
+ "arm64"
1018
+ ],
1019
+ "dev": true,
1020
+ "license": "MIT",
1021
+ "optional": true,
1022
+ "os": [
1023
+ "linux"
1024
+ ]
1025
+ },
1026
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
1027
+ "version": "4.62.2",
1028
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
1029
+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
1030
+ "cpu": [
1031
+ "loong64"
1032
+ ],
1033
+ "dev": true,
1034
+ "license": "MIT",
1035
+ "optional": true,
1036
+ "os": [
1037
+ "linux"
1038
+ ]
1039
+ },
1040
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
1041
+ "version": "4.62.2",
1042
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
1043
+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
1044
+ "cpu": [
1045
+ "loong64"
1046
+ ],
1047
+ "dev": true,
1048
+ "license": "MIT",
1049
+ "optional": true,
1050
+ "os": [
1051
+ "linux"
1052
+ ]
1053
+ },
1054
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
1055
+ "version": "4.62.2",
1056
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
1057
+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
1058
+ "cpu": [
1059
+ "ppc64"
1060
+ ],
1061
+ "dev": true,
1062
+ "license": "MIT",
1063
+ "optional": true,
1064
+ "os": [
1065
+ "linux"
1066
+ ]
1067
+ },
1068
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
1069
+ "version": "4.62.2",
1070
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
1071
+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
1072
+ "cpu": [
1073
+ "ppc64"
1074
+ ],
1075
+ "dev": true,
1076
+ "license": "MIT",
1077
+ "optional": true,
1078
+ "os": [
1079
+ "linux"
1080
+ ]
1081
+ },
1082
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
1083
+ "version": "4.62.2",
1084
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
1085
+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
1086
+ "cpu": [
1087
+ "riscv64"
1088
+ ],
1089
+ "dev": true,
1090
+ "license": "MIT",
1091
+ "optional": true,
1092
+ "os": [
1093
+ "linux"
1094
+ ]
1095
+ },
1096
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
1097
+ "version": "4.62.2",
1098
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
1099
+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
1100
+ "cpu": [
1101
+ "riscv64"
1102
+ ],
1103
+ "dev": true,
1104
+ "license": "MIT",
1105
+ "optional": true,
1106
+ "os": [
1107
+ "linux"
1108
+ ]
1109
+ },
1110
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
1111
+ "version": "4.62.2",
1112
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
1113
+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
1114
+ "cpu": [
1115
+ "s390x"
1116
+ ],
1117
+ "dev": true,
1118
+ "license": "MIT",
1119
+ "optional": true,
1120
+ "os": [
1121
+ "linux"
1122
+ ]
1123
+ },
1124
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
1125
+ "version": "4.62.2",
1126
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
1127
+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
1128
+ "cpu": [
1129
+ "x64"
1130
+ ],
1131
+ "dev": true,
1132
+ "license": "MIT",
1133
+ "optional": true,
1134
+ "os": [
1135
+ "linux"
1136
+ ]
1137
+ },
1138
+ "node_modules/@rollup/rollup-linux-x64-musl": {
1139
+ "version": "4.62.2",
1140
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
1141
+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
1142
+ "cpu": [
1143
+ "x64"
1144
+ ],
1145
+ "dev": true,
1146
+ "license": "MIT",
1147
+ "optional": true,
1148
+ "os": [
1149
+ "linux"
1150
+ ]
1151
+ },
1152
+ "node_modules/@rollup/rollup-openbsd-x64": {
1153
+ "version": "4.62.2",
1154
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
1155
+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
1156
+ "cpu": [
1157
+ "x64"
1158
+ ],
1159
+ "dev": true,
1160
+ "license": "MIT",
1161
+ "optional": true,
1162
+ "os": [
1163
+ "openbsd"
1164
+ ]
1165
+ },
1166
+ "node_modules/@rollup/rollup-openharmony-arm64": {
1167
+ "version": "4.62.2",
1168
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
1169
+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
1170
+ "cpu": [
1171
+ "arm64"
1172
+ ],
1173
+ "dev": true,
1174
+ "license": "MIT",
1175
+ "optional": true,
1176
+ "os": [
1177
+ "openharmony"
1178
+ ]
1179
+ },
1180
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
1181
+ "version": "4.62.2",
1182
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
1183
+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
1184
+ "cpu": [
1185
+ "arm64"
1186
+ ],
1187
+ "dev": true,
1188
+ "license": "MIT",
1189
+ "optional": true,
1190
+ "os": [
1191
+ "win32"
1192
+ ]
1193
+ },
1194
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
1195
+ "version": "4.62.2",
1196
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
1197
+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
1198
+ "cpu": [
1199
+ "ia32"
1200
+ ],
1201
+ "dev": true,
1202
+ "license": "MIT",
1203
+ "optional": true,
1204
+ "os": [
1205
+ "win32"
1206
+ ]
1207
+ },
1208
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
1209
+ "version": "4.62.2",
1210
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
1211
+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
1212
+ "cpu": [
1213
+ "x64"
1214
+ ],
1215
+ "dev": true,
1216
+ "license": "MIT",
1217
+ "optional": true,
1218
+ "os": [
1219
+ "win32"
1220
+ ]
1221
+ },
1222
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
1223
+ "version": "4.62.2",
1224
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
1225
+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
1226
+ "cpu": [
1227
+ "x64"
1228
+ ],
1229
+ "dev": true,
1230
+ "license": "MIT",
1231
+ "optional": true,
1232
+ "os": [
1233
+ "win32"
1234
+ ]
1235
+ },
1236
+ "node_modules/@smithy/core": {
1237
+ "version": "3.29.5",
1238
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.5.tgz",
1239
+ "integrity": "sha512-i0dk2t5B+CwV/dcJdUHILYkOQF5lof8f44dFCfDWToGCxjT9YQ+CgHqTAvJxzc3+zqQwm2QtVoJ5IqiNar/CnQ==",
1240
+ "license": "Apache-2.0",
1241
+ "dependencies": {
1242
+ "@smithy/types": "^4.16.1",
1243
+ "tslib": "^2.6.2"
1244
+ },
1245
+ "engines": {
1246
+ "node": ">=18.0.0"
1247
+ }
1248
+ },
1249
+ "node_modules/@smithy/credential-provider-imds": {
1250
+ "version": "4.4.10",
1251
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.10.tgz",
1252
+ "integrity": "sha512-MJenAe4OKRZUo1LdYYFDCsSHxaHvInIU/z52GsheO9vl1/VSySVCr0zkyKD6TFiGkSUaWGxvKZ/70OvgUZR5HQ==",
1253
+ "license": "Apache-2.0",
1254
+ "dependencies": {
1255
+ "@smithy/core": "^3.29.5",
1256
+ "@smithy/types": "^4.16.1",
1257
+ "tslib": "^2.6.2"
1258
+ },
1259
+ "engines": {
1260
+ "node": ">=18.0.0"
1261
+ }
1262
+ },
1263
+ "node_modules/@smithy/fetch-http-handler": {
1264
+ "version": "5.6.7",
1265
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.7.tgz",
1266
+ "integrity": "sha512-3zpg8yqqyXzoK2TsRDdkqVOj2RDBFfLXwCczOZ5c7TWB4eiaebfSCsbMjDPYB3PJ9ihV62QaeadZ+wLadZtNGA==",
1267
+ "license": "Apache-2.0",
1268
+ "dependencies": {
1269
+ "@smithy/core": "^3.29.5",
1270
+ "@smithy/types": "^4.16.1",
1271
+ "tslib": "^2.6.2"
1272
+ },
1273
+ "engines": {
1274
+ "node": ">=18.0.0"
1275
+ }
1276
+ },
1277
+ "node_modules/@smithy/node-http-handler": {
1278
+ "version": "4.9.7",
1279
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.7.tgz",
1280
+ "integrity": "sha512-wCU8HCLjAtAVqxxe0j2xff9LcEPw3yjBbg5IdQDIYFnxnPxbxcSLc7rgex7kqm9L/WYOnJEgaWQlfDkZleozMA==",
1281
+ "license": "Apache-2.0",
1282
+ "dependencies": {
1283
+ "@smithy/core": "^3.29.5",
1284
+ "@smithy/types": "^4.16.1",
1285
+ "tslib": "^2.6.2"
1286
+ },
1287
+ "engines": {
1288
+ "node": ">=18.0.0"
1289
+ }
1290
+ },
1291
+ "node_modules/@smithy/signature-v4": {
1292
+ "version": "5.6.6",
1293
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.6.tgz",
1294
+ "integrity": "sha512-efP6DN3UTFrzIsGO42/xcabv8jU7+9nwEdphFUH7yL0k010ERyAWaO41KFQIDLcFZLZ8xzIQr4wplFxNzslSGQ==",
1295
+ "license": "Apache-2.0",
1296
+ "dependencies": {
1297
+ "@smithy/core": "^3.29.5",
1298
+ "@smithy/types": "^4.16.1",
1299
+ "tslib": "^2.6.2"
1300
+ },
1301
+ "engines": {
1302
+ "node": ">=18.0.0"
1303
+ }
1304
+ },
1305
+ "node_modules/@smithy/types": {
1306
+ "version": "4.16.1",
1307
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz",
1308
+ "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==",
1309
+ "license": "Apache-2.0",
1310
+ "dependencies": {
1311
+ "tslib": "^2.6.2"
1312
+ },
1313
+ "engines": {
1314
+ "node": ">=18.0.0"
1315
+ }
1316
+ },
1317
+ "node_modules/@types/estree": {
1318
+ "version": "1.0.9",
1319
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
1320
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
1321
+ "dev": true,
1322
+ "license": "MIT"
1323
+ },
1324
+ "node_modules/@types/node": {
1325
+ "version": "20.19.43",
1326
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
1327
+ "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
1328
+ "dev": true,
1329
+ "license": "MIT",
1330
+ "dependencies": {
1331
+ "undici-types": "~6.21.0"
1332
+ }
1333
+ },
1334
+ "node_modules/@vitejs/plugin-vue": {
1335
+ "version": "5.2.4",
1336
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
1337
+ "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
1338
+ "dev": true,
1339
+ "license": "MIT",
1340
+ "engines": {
1341
+ "node": "^18.0.0 || >=20.0.0"
1342
+ },
1343
+ "peerDependencies": {
1344
+ "vite": "^5.0.0 || ^6.0.0",
1345
+ "vue": "^3.2.25"
1346
+ }
1347
+ },
1348
+ "node_modules/@volar/language-core": {
1349
+ "version": "2.4.28",
1350
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
1351
+ "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
1352
+ "dev": true,
1353
+ "license": "MIT",
1354
+ "dependencies": {
1355
+ "@volar/source-map": "2.4.28"
1356
+ }
1357
+ },
1358
+ "node_modules/@volar/source-map": {
1359
+ "version": "2.4.28",
1360
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
1361
+ "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
1362
+ "dev": true,
1363
+ "license": "MIT"
1364
+ },
1365
+ "node_modules/@volar/typescript": {
1366
+ "version": "2.4.28",
1367
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
1368
+ "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
1369
+ "dev": true,
1370
+ "license": "MIT",
1371
+ "dependencies": {
1372
+ "@volar/language-core": "2.4.28",
1373
+ "path-browserify": "^1.0.1",
1374
+ "vscode-uri": "^3.0.8"
1375
+ }
1376
+ },
1377
+ "node_modules/@vue/compiler-core": {
1378
+ "version": "3.5.40",
1379
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz",
1380
+ "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==",
1381
+ "license": "MIT",
1382
+ "dependencies": {
1383
+ "@babel/parser": "^7.29.7",
1384
+ "@vue/shared": "3.5.40",
1385
+ "entities": "^7.0.1",
1386
+ "estree-walker": "^2.0.2",
1387
+ "source-map-js": "^1.2.1"
1388
+ }
1389
+ },
1390
+ "node_modules/@vue/compiler-dom": {
1391
+ "version": "3.5.40",
1392
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz",
1393
+ "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==",
1394
+ "license": "MIT",
1395
+ "dependencies": {
1396
+ "@vue/compiler-core": "3.5.40",
1397
+ "@vue/shared": "3.5.40"
1398
+ }
1399
+ },
1400
+ "node_modules/@vue/compiler-sfc": {
1401
+ "version": "3.5.40",
1402
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz",
1403
+ "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==",
1404
+ "license": "MIT",
1405
+ "dependencies": {
1406
+ "@babel/parser": "^7.29.7",
1407
+ "@vue/compiler-core": "3.5.40",
1408
+ "@vue/compiler-dom": "3.5.40",
1409
+ "@vue/compiler-ssr": "3.5.40",
1410
+ "@vue/shared": "3.5.40",
1411
+ "estree-walker": "^2.0.2",
1412
+ "magic-string": "^0.30.21",
1413
+ "postcss": "^8.5.19",
1414
+ "source-map-js": "^1.2.1"
1415
+ }
1416
+ },
1417
+ "node_modules/@vue/compiler-ssr": {
1418
+ "version": "3.5.40",
1419
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz",
1420
+ "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==",
1421
+ "license": "MIT",
1422
+ "dependencies": {
1423
+ "@vue/compiler-dom": "3.5.40",
1424
+ "@vue/shared": "3.5.40"
1425
+ }
1426
+ },
1427
+ "node_modules/@vue/language-core": {
1428
+ "version": "3.3.7",
1429
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.7.tgz",
1430
+ "integrity": "sha512-LzmkKinXAMMoh8Jfi/jMUSDUjuPdv8mynH5WJGKfXyZtDw3hQ6GBaoI6Bcnl/Xqlu32q/0Z6i/trp4VXykzyLw==",
1431
+ "dev": true,
1432
+ "license": "MIT",
1433
+ "dependencies": {
1434
+ "@volar/language-core": "2.4.28",
1435
+ "@vue/compiler-dom": "^3.5.0",
1436
+ "@vue/shared": "^3.5.0",
1437
+ "alien-signals": "^3.2.1",
1438
+ "muggle-string": "^0.4.1",
1439
+ "path-browserify": "^1.0.1",
1440
+ "picomatch": "^4.0.4"
1441
+ }
1442
+ },
1443
+ "node_modules/@vue/reactivity": {
1444
+ "version": "3.5.40",
1445
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz",
1446
+ "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==",
1447
+ "license": "MIT",
1448
+ "dependencies": {
1449
+ "@vue/shared": "3.5.40"
1450
+ }
1451
+ },
1452
+ "node_modules/@vue/runtime-core": {
1453
+ "version": "3.5.40",
1454
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz",
1455
+ "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==",
1456
+ "license": "MIT",
1457
+ "dependencies": {
1458
+ "@vue/reactivity": "3.5.40",
1459
+ "@vue/shared": "3.5.40"
1460
+ }
1461
+ },
1462
+ "node_modules/@vue/runtime-dom": {
1463
+ "version": "3.5.40",
1464
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz",
1465
+ "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==",
1466
+ "license": "MIT",
1467
+ "dependencies": {
1468
+ "@vue/reactivity": "3.5.40",
1469
+ "@vue/runtime-core": "3.5.40",
1470
+ "@vue/shared": "3.5.40",
1471
+ "csstype": "^3.2.3"
1472
+ }
1473
+ },
1474
+ "node_modules/@vue/server-renderer": {
1475
+ "version": "3.5.40",
1476
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz",
1477
+ "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==",
1478
+ "license": "MIT",
1479
+ "dependencies": {
1480
+ "@vue/compiler-ssr": "3.5.40",
1481
+ "@vue/runtime-dom": "3.5.40",
1482
+ "@vue/shared": "3.5.40"
1483
+ }
1484
+ },
1485
+ "node_modules/@vue/shared": {
1486
+ "version": "3.5.40",
1487
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz",
1488
+ "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==",
1489
+ "license": "MIT"
1490
+ },
1491
+ "node_modules/alien-signals": {
1492
+ "version": "3.2.1",
1493
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz",
1494
+ "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==",
1495
+ "dev": true,
1496
+ "license": "MIT"
1497
+ },
1498
+ "node_modules/bowser": {
1499
+ "version": "2.14.1",
1500
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
1501
+ "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
1502
+ "license": "MIT"
1503
+ },
1504
+ "node_modules/csstype": {
1505
+ "version": "3.2.3",
1506
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1507
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1508
+ "license": "MIT"
1509
+ },
1510
+ "node_modules/entities": {
1511
+ "version": "7.0.1",
1512
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
1513
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
1514
+ "license": "BSD-2-Clause",
1515
+ "engines": {
1516
+ "node": ">=0.12"
1517
+ },
1518
+ "funding": {
1519
+ "url": "https://github.com/fb55/entities?sponsor=1"
1520
+ }
1521
+ },
1522
+ "node_modules/esbuild": {
1523
+ "version": "0.25.12",
1524
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
1525
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
1526
+ "dev": true,
1527
+ "hasInstallScript": true,
1528
+ "license": "MIT",
1529
+ "bin": {
1530
+ "esbuild": "bin/esbuild"
1531
+ },
1532
+ "engines": {
1533
+ "node": ">=18"
1534
+ },
1535
+ "optionalDependencies": {
1536
+ "@esbuild/aix-ppc64": "0.25.12",
1537
+ "@esbuild/android-arm": "0.25.12",
1538
+ "@esbuild/android-arm64": "0.25.12",
1539
+ "@esbuild/android-x64": "0.25.12",
1540
+ "@esbuild/darwin-arm64": "0.25.12",
1541
+ "@esbuild/darwin-x64": "0.25.12",
1542
+ "@esbuild/freebsd-arm64": "0.25.12",
1543
+ "@esbuild/freebsd-x64": "0.25.12",
1544
+ "@esbuild/linux-arm": "0.25.12",
1545
+ "@esbuild/linux-arm64": "0.25.12",
1546
+ "@esbuild/linux-ia32": "0.25.12",
1547
+ "@esbuild/linux-loong64": "0.25.12",
1548
+ "@esbuild/linux-mips64el": "0.25.12",
1549
+ "@esbuild/linux-ppc64": "0.25.12",
1550
+ "@esbuild/linux-riscv64": "0.25.12",
1551
+ "@esbuild/linux-s390x": "0.25.12",
1552
+ "@esbuild/linux-x64": "0.25.12",
1553
+ "@esbuild/netbsd-arm64": "0.25.12",
1554
+ "@esbuild/netbsd-x64": "0.25.12",
1555
+ "@esbuild/openbsd-arm64": "0.25.12",
1556
+ "@esbuild/openbsd-x64": "0.25.12",
1557
+ "@esbuild/openharmony-arm64": "0.25.12",
1558
+ "@esbuild/sunos-x64": "0.25.12",
1559
+ "@esbuild/win32-arm64": "0.25.12",
1560
+ "@esbuild/win32-ia32": "0.25.12",
1561
+ "@esbuild/win32-x64": "0.25.12"
1562
+ }
1563
+ },
1564
+ "node_modules/estree-walker": {
1565
+ "version": "2.0.2",
1566
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
1567
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
1568
+ "license": "MIT"
1569
+ },
1570
+ "node_modules/fdir": {
1571
+ "version": "6.5.0",
1572
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
1573
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1574
+ "dev": true,
1575
+ "license": "MIT",
1576
+ "engines": {
1577
+ "node": ">=12.0.0"
1578
+ },
1579
+ "peerDependencies": {
1580
+ "picomatch": "^3 || ^4"
1581
+ },
1582
+ "peerDependenciesMeta": {
1583
+ "picomatch": {
1584
+ "optional": true
1585
+ }
1586
+ }
1587
+ },
1588
+ "node_modules/fsevents": {
1589
+ "version": "2.3.3",
1590
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1591
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1592
+ "dev": true,
1593
+ "hasInstallScript": true,
1594
+ "license": "MIT",
1595
+ "optional": true,
1596
+ "os": [
1597
+ "darwin"
1598
+ ],
1599
+ "engines": {
1600
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1601
+ }
1602
+ },
1603
+ "node_modules/magic-string": {
1604
+ "version": "0.30.21",
1605
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
1606
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
1607
+ "license": "MIT",
1608
+ "dependencies": {
1609
+ "@jridgewell/sourcemap-codec": "^1.5.5"
1610
+ }
1611
+ },
1612
+ "node_modules/muggle-string": {
1613
+ "version": "0.4.1",
1614
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
1615
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
1616
+ "dev": true,
1617
+ "license": "MIT"
1618
+ },
1619
+ "node_modules/nanoid": {
1620
+ "version": "3.3.16",
1621
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
1622
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
1623
+ "funding": [
1624
+ {
1625
+ "type": "github",
1626
+ "url": "https://github.com/sponsors/ai"
1627
+ }
1628
+ ],
1629
+ "license": "MIT",
1630
+ "bin": {
1631
+ "nanoid": "bin/nanoid.cjs"
1632
+ },
1633
+ "engines": {
1634
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1635
+ }
1636
+ },
1637
+ "node_modules/path-browserify": {
1638
+ "version": "1.0.1",
1639
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
1640
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
1641
+ "dev": true,
1642
+ "license": "MIT"
1643
+ },
1644
+ "node_modules/picocolors": {
1645
+ "version": "1.1.1",
1646
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1647
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1648
+ "license": "ISC"
1649
+ },
1650
+ "node_modules/picomatch": {
1651
+ "version": "4.0.5",
1652
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
1653
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
1654
+ "dev": true,
1655
+ "license": "MIT",
1656
+ "engines": {
1657
+ "node": ">=12"
1658
+ },
1659
+ "funding": {
1660
+ "url": "https://github.com/sponsors/jonschlinkert"
1661
+ }
1662
+ },
1663
+ "node_modules/postcss": {
1664
+ "version": "8.5.19",
1665
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz",
1666
+ "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==",
1667
+ "funding": [
1668
+ {
1669
+ "type": "opencollective",
1670
+ "url": "https://opencollective.com/postcss/"
1671
+ },
1672
+ {
1673
+ "type": "tidelift",
1674
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1675
+ },
1676
+ {
1677
+ "type": "github",
1678
+ "url": "https://github.com/sponsors/ai"
1679
+ }
1680
+ ],
1681
+ "license": "MIT",
1682
+ "dependencies": {
1683
+ "nanoid": "^3.3.12",
1684
+ "picocolors": "^1.1.1",
1685
+ "source-map-js": "^1.2.1"
1686
+ },
1687
+ "engines": {
1688
+ "node": "^10 || ^12 || >=14"
1689
+ }
1690
+ },
1691
+ "node_modules/rollup": {
1692
+ "version": "4.62.2",
1693
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
1694
+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
1695
+ "dev": true,
1696
+ "license": "MIT",
1697
+ "dependencies": {
1698
+ "@types/estree": "1.0.9"
1699
+ },
1700
+ "bin": {
1701
+ "rollup": "dist/bin/rollup"
1702
+ },
1703
+ "engines": {
1704
+ "node": ">=18.0.0",
1705
+ "npm": ">=8.0.0"
1706
+ },
1707
+ "optionalDependencies": {
1708
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
1709
+ "@rollup/rollup-android-arm64": "4.62.2",
1710
+ "@rollup/rollup-darwin-arm64": "4.62.2",
1711
+ "@rollup/rollup-darwin-x64": "4.62.2",
1712
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
1713
+ "@rollup/rollup-freebsd-x64": "4.62.2",
1714
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
1715
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
1716
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
1717
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
1718
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
1719
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
1720
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
1721
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
1722
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
1723
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
1724
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
1725
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
1726
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
1727
+ "@rollup/rollup-openbsd-x64": "4.62.2",
1728
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
1729
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
1730
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
1731
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
1732
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
1733
+ "fsevents": "~2.3.2"
1734
+ }
1735
+ },
1736
+ "node_modules/source-map-js": {
1737
+ "version": "1.2.1",
1738
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1739
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1740
+ "license": "BSD-3-Clause",
1741
+ "engines": {
1742
+ "node": ">=0.10.0"
1743
+ }
1744
+ },
1745
+ "node_modules/tinyglobby": {
1746
+ "version": "0.2.17",
1747
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
1748
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
1749
+ "dev": true,
1750
+ "license": "MIT",
1751
+ "dependencies": {
1752
+ "fdir": "^6.5.0",
1753
+ "picomatch": "^4.0.4"
1754
+ },
1755
+ "engines": {
1756
+ "node": ">=12.0.0"
1757
+ },
1758
+ "funding": {
1759
+ "url": "https://github.com/sponsors/SuperchupuDev"
1760
+ }
1761
+ },
1762
+ "node_modules/tslib": {
1763
+ "version": "2.8.1",
1764
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
1765
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
1766
+ "license": "0BSD"
1767
+ },
1768
+ "node_modules/typescript": {
1769
+ "version": "5.9.3",
1770
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1771
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1772
+ "devOptional": true,
1773
+ "license": "Apache-2.0",
1774
+ "bin": {
1775
+ "tsc": "bin/tsc",
1776
+ "tsserver": "bin/tsserver"
1777
+ },
1778
+ "engines": {
1779
+ "node": ">=14.17"
1780
+ }
1781
+ },
1782
+ "node_modules/undici-types": {
1783
+ "version": "6.21.0",
1784
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1785
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1786
+ "dev": true,
1787
+ "license": "MIT"
1788
+ },
1789
+ "node_modules/vite": {
1790
+ "version": "6.4.3",
1791
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
1792
+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
1793
+ "dev": true,
1794
+ "license": "MIT",
1795
+ "dependencies": {
1796
+ "esbuild": "^0.25.0",
1797
+ "fdir": "^6.4.4",
1798
+ "picomatch": "^4.0.2",
1799
+ "postcss": "^8.5.3",
1800
+ "rollup": "^4.34.9",
1801
+ "tinyglobby": "^0.2.13"
1802
+ },
1803
+ "bin": {
1804
+ "vite": "bin/vite.js"
1805
+ },
1806
+ "engines": {
1807
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
1808
+ },
1809
+ "funding": {
1810
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1811
+ },
1812
+ "optionalDependencies": {
1813
+ "fsevents": "~2.3.3"
1814
+ },
1815
+ "peerDependencies": {
1816
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
1817
+ "jiti": ">=1.21.0",
1818
+ "less": "*",
1819
+ "lightningcss": "^1.21.0",
1820
+ "sass": "*",
1821
+ "sass-embedded": "*",
1822
+ "stylus": "*",
1823
+ "sugarss": "*",
1824
+ "terser": "^5.16.0",
1825
+ "tsx": "^4.8.1",
1826
+ "yaml": "^2.4.2"
1827
+ },
1828
+ "peerDependenciesMeta": {
1829
+ "@types/node": {
1830
+ "optional": true
1831
+ },
1832
+ "jiti": {
1833
+ "optional": true
1834
+ },
1835
+ "less": {
1836
+ "optional": true
1837
+ },
1838
+ "lightningcss": {
1839
+ "optional": true
1840
+ },
1841
+ "sass": {
1842
+ "optional": true
1843
+ },
1844
+ "sass-embedded": {
1845
+ "optional": true
1846
+ },
1847
+ "stylus": {
1848
+ "optional": true
1849
+ },
1850
+ "sugarss": {
1851
+ "optional": true
1852
+ },
1853
+ "terser": {
1854
+ "optional": true
1855
+ },
1856
+ "tsx": {
1857
+ "optional": true
1858
+ },
1859
+ "yaml": {
1860
+ "optional": true
1861
+ }
1862
+ }
1863
+ },
1864
+ "node_modules/vscode-uri": {
1865
+ "version": "3.1.0",
1866
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
1867
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
1868
+ "dev": true,
1869
+ "license": "MIT"
1870
+ },
1871
+ "node_modules/vue": {
1872
+ "version": "3.5.40",
1873
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz",
1874
+ "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==",
1875
+ "license": "MIT",
1876
+ "dependencies": {
1877
+ "@vue/compiler-dom": "3.5.40",
1878
+ "@vue/compiler-sfc": "3.5.40",
1879
+ "@vue/runtime-dom": "3.5.40",
1880
+ "@vue/server-renderer": "3.5.40",
1881
+ "@vue/shared": "3.5.40"
1882
+ },
1883
+ "peerDependencies": {
1884
+ "typescript": "*"
1885
+ },
1886
+ "peerDependenciesMeta": {
1887
+ "typescript": {
1888
+ "optional": true
1889
+ }
1890
+ }
1891
+ },
1892
+ "node_modules/vue-tsc": {
1893
+ "version": "3.3.7",
1894
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.7.tgz",
1895
+ "integrity": "sha512-+C+rgD49wAQ5bUTl2sp5a8Bzg4YoldMNXM+g7CFe604MYcQ8PrZPMQhIjJSzKXtPBCa+C5ayMipqjbA7splekQ==",
1896
+ "dev": true,
1897
+ "license": "MIT",
1898
+ "dependencies": {
1899
+ "@volar/typescript": "2.4.28",
1900
+ "@vue/language-core": "3.3.7"
1901
+ },
1902
+ "bin": {
1903
+ "vue-tsc": "bin/vue-tsc.js"
1904
+ },
1905
+ "peerDependencies": {
1906
+ "typescript": ">=5.0.0"
1907
+ }
1908
+ }
1909
+ }
1910
+ }
apps/dashboard/package.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "autocad-bench-trace-dashboard",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --host 127.0.0.1",
8
+ "build": "vue-tsc -b && vite build",
9
+ "index-traces": "node backfill-trace-summaries.mjs",
10
+ "test": "node --test tests/*.test.mjs",
11
+ "preview": "node server.mjs"
12
+ },
13
+ "dependencies": {
14
+ "@aws-sdk/client-s3": "3.1090.0",
15
+ "@aws-sdk/credential-providers": "3.1090.0",
16
+ "@aws-sdk/s3-request-presigner": "3.1090.0",
17
+ "vue": "3.5.40"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "20.19.43",
21
+ "@vitejs/plugin-vue": "5.2.4",
22
+ "typescript": "5.9.3",
23
+ "vite": "6.4.3",
24
+ "vue-tsc": "3.3.7"
25
+ }
26
+ }
apps/dashboard/server.d.mts ADDED
@@ -0,0 +1 @@
 
 
1
+ export {};
apps/dashboard/server.mjs ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'node:fs'
2
+ import http from 'node:http'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import { execFile } from 'node:child_process'
6
+ import { createHash } from 'node:crypto'
7
+ import { fileURLToPath } from 'node:url'
8
+ import { promisify } from 'node:util'
9
+ import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3'
10
+ import { fromIni } from '@aws-sdk/credential-providers'
11
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
12
+ import { createTraceApi } from './trace-api.mjs'
13
+ import {
14
+ benchmarkMetadataPaths,
15
+ shouldPromoteMetadata,
16
+ traceMetadataPaths,
17
+ } from './cloud-metadata.mjs'
18
+
19
+ const root = path.dirname(fileURLToPath(import.meta.url))
20
+ const dist = path.join(root, 'dist')
21
+ const execFileAsync = promisify(execFile)
22
+ const cloudDownloads = new Map()
23
+ const cloudBucket = process.env.AUTOCAD_BENCH_S3_BUCKET || ''
24
+ const cloudPrefix = (process.env.AUTOCAD_BENCH_S3_RUNS_PREFIX || 'controller/runs')
25
+ .replace(/^\/+|\/+$/g, '')
26
+ const awsProfile = process.env.AWS_PROFILE || ''
27
+ const awsRegion = process.env.AWS_REGION || ''
28
+ const cloudArtifactsEnabled = Boolean(cloudBucket)
29
+ && process.env.AUTOCAD_BENCH_CLOUD_ARTIFACTS !== 'off'
30
+ const signedArtifactLifetimeSeconds = 300
31
+ const s3 = new S3Client({
32
+ ...(awsRegion ? { region: awsRegion } : {}),
33
+ ...(awsProfile ? { credentials: fromIni({ profile: awsProfile }) } : {}),
34
+ })
35
+ const cloudMetadataDownloads = new Map()
36
+ const cloudMetadataFreshAt = new Map()
37
+ const cloudArtifactAttempts = Math.max(
38
+ 1,
39
+ Math.min(5, Number(process.env.AUTOCAD_BENCH_CLOUD_ARTIFACT_ATTEMPTS || 3)),
40
+ )
41
+ const cloudArtifactTimeoutMs = Math.max(
42
+ 5_000,
43
+ Math.min(60_000, Number(process.env.AUTOCAD_BENCH_CLOUD_ARTIFACT_TIMEOUT_MS || 15_000)),
44
+ )
45
+ const cloudMetadataCache = path.join(
46
+ os.tmpdir(),
47
+ `autocad-bench-dashboard-${createHash('sha256')
48
+ .update(`${cloudBucket}/${cloudPrefix}`)
49
+ .digest('hex')
50
+ .slice(0, 16)}`,
51
+ )
52
+
53
+ const cloudMetadataConcurrency = Math.max(
54
+ 1,
55
+ Math.min(64, Number(process.env.AUTOCAD_BENCH_CLOUD_METADATA_CONCURRENCY || 24)),
56
+ )
57
+
58
+ function promoteCloudMetadata(shadowDirectory, destinationDirectory) {
59
+ if (!fs.existsSync(shadowDirectory)) return
60
+ const visit = (directory) => {
61
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
62
+ const source = path.join(directory, entry.name)
63
+ if (entry.isDirectory()) {
64
+ visit(source)
65
+ continue
66
+ }
67
+ if (!entry.isFile()) continue
68
+ const relativePath = path.relative(shadowDirectory, source)
69
+ const destination = path.resolve(destinationDirectory, relativePath)
70
+ if (!destination.startsWith(path.resolve(destinationDirectory) + path.sep)) continue
71
+ if (!shouldPromoteMetadata(source, destination)) continue
72
+ fs.mkdirSync(path.dirname(destination), { recursive: true })
73
+ const temporary = `${destination}.cloud-update-${process.pid}-${Date.now()}`
74
+ try {
75
+ fs.copyFileSync(source, temporary)
76
+ fs.renameSync(temporary, destination)
77
+ } finally {
78
+ try {
79
+ if (fs.existsSync(temporary)) fs.unlinkSync(temporary)
80
+ } catch {
81
+ // A failed cleanup cannot invalidate a successful atomic promotion.
82
+ }
83
+ }
84
+ }
85
+ }
86
+ visit(shadowDirectory)
87
+ }
88
+
89
+ function wait(milliseconds) {
90
+ return new Promise((resolve) => setTimeout(resolve, milliseconds))
91
+ }
92
+
93
+ async function cloudMetadataLoader({
94
+ id,
95
+ destinationDirectory,
96
+ relativePaths,
97
+ maxAgeMs = 0,
98
+ }) {
99
+ if (!cloudArtifactsEnabled) return
100
+ const safeRelativePaths = [...new Set(relativePaths)].filter((relativePath) => {
101
+ if (!relativePath || relativePath.includes('\\')) return false
102
+ return !relativePath.split('/').some((segment) => !segment || segment === '.' || segment === '..')
103
+ })
104
+ if (!safeRelativePaths.length) return
105
+ const downloadKey = `${id}\0${safeRelativePaths.join('\0')}`
106
+ const refreshedAt = cloudMetadataFreshAt.get(downloadKey) || 0
107
+ if (maxAgeMs > 0 && Date.now() - refreshedAt < maxAgeMs) return
108
+ if (cloudMetadataDownloads.has(downloadKey)) {
109
+ return cloudMetadataDownloads.get(downloadKey)
110
+ }
111
+ const download = (async () => {
112
+ const pathNamespace = createHash('sha256')
113
+ .update(safeRelativePaths.join('\0'))
114
+ .digest('hex')
115
+ .slice(0, 12)
116
+ const shadowDirectory = path.resolve(cloudMetadataCache, pathNamespace, id)
117
+ if (!shadowDirectory.startsWith(path.resolve(cloudMetadataCache) + path.sep)) {
118
+ throw new Error('Unsafe cloud metadata cache path')
119
+ }
120
+ if (
121
+ maxAgeMs > 0
122
+ && fs.existsSync(shadowDirectory)
123
+ && Date.now() - fs.statSync(shadowDirectory).mtimeMs < maxAgeMs
124
+ ) {
125
+ cloudMetadataFreshAt.set(downloadKey, Date.now())
126
+ return
127
+ }
128
+ fs.mkdirSync(shadowDirectory, { recursive: true })
129
+ let nextIndex = 0
130
+ const errors = []
131
+ const workers = Array.from({
132
+ length: Math.min(cloudMetadataConcurrency, safeRelativePaths.length),
133
+ }, async () => {
134
+ while (nextIndex < safeRelativePaths.length) {
135
+ const relativePath = safeRelativePaths[nextIndex]
136
+ nextIndex += 1
137
+ const destination = path.resolve(shadowDirectory, relativePath)
138
+ if (!destination.startsWith(path.resolve(shadowDirectory) + path.sep)) continue
139
+ const key = [cloudPrefix, id, relativePath]
140
+ .join('/')
141
+ .replaceAll('\\', '/')
142
+ .replace(/\/{2,}/g, '/')
143
+ try {
144
+ const object = await s3.send(new GetObjectCommand({
145
+ Bucket: cloudBucket,
146
+ Key: key,
147
+ }))
148
+ if (!object.Body) continue
149
+ const bytes = await object.Body.transformToByteArray()
150
+ fs.mkdirSync(path.dirname(destination), { recursive: true })
151
+ const temporary = `${destination}.cloud-download-${process.pid}-${Date.now()}`
152
+ try {
153
+ fs.writeFileSync(temporary, bytes)
154
+ fs.renameSync(temporary, destination)
155
+ } finally {
156
+ try {
157
+ if (fs.existsSync(temporary)) fs.unlinkSync(temporary)
158
+ } catch {
159
+ // A failed temporary-file cleanup cannot invalidate the cache.
160
+ }
161
+ }
162
+ } catch (error) {
163
+ const statusCode = error?.$metadata?.httpStatusCode
164
+ if (statusCode === 404 || ['NoSuchKey', 'NotFound'].includes(error?.name)) continue
165
+ errors.push(error)
166
+ }
167
+ }
168
+ })
169
+ await Promise.all(workers)
170
+ promoteCloudMetadata(shadowDirectory, destinationDirectory)
171
+ if (errors.length) throw errors[0]
172
+ const refreshed = new Date()
173
+ fs.utimesSync(shadowDirectory, refreshed, refreshed)
174
+ cloudMetadataFreshAt.set(downloadKey, refreshed.valueOf())
175
+ })()
176
+ cloudMetadataDownloads.set(downloadKey, download)
177
+ try {
178
+ await download
179
+ } finally {
180
+ cloudMetadataDownloads.delete(downloadKey)
181
+ }
182
+ }
183
+
184
+ async function cloudBenchmarkLoader({ benchmarkId, benchmarkDirectory }) {
185
+ let state = null
186
+ try {
187
+ state = JSON.parse(fs.readFileSync(
188
+ path.join(benchmarkDirectory, 'batch-state.json'),
189
+ 'utf8',
190
+ ))
191
+ } catch {
192
+ // A new cloud-only benchmark still needs a blocking first bootstrap.
193
+ }
194
+ if (!state) {
195
+ // Fetch the one authoritative suite index directly. Returning as soon as
196
+ // this file is present makes a cold benchmark visible immediately; the
197
+ // rollout metadata is hydrated concurrently below.
198
+ await cloudMetadataLoader({
199
+ id: benchmarkId,
200
+ destinationDirectory: benchmarkDirectory,
201
+ relativePaths: ['batch-state.json'],
202
+ })
203
+ try {
204
+ state = JSON.parse(fs.readFileSync(
205
+ path.join(benchmarkDirectory, 'batch-state.json'),
206
+ 'utf8',
207
+ ))
208
+ } catch {
209
+ return
210
+ }
211
+ }
212
+ const terminal = !['starting', 'running'].includes(String(state?.state || ''))
213
+ const refresh = cloudMetadataLoader({
214
+ id: benchmarkId,
215
+ destinationDirectory: benchmarkDirectory,
216
+ relativePaths: benchmarkMetadataPaths(state),
217
+ maxAgeMs: state ? (terminal ? 5 * 60_000 : 10_000) : 0,
218
+ })
219
+ // Benchmark detail is always local-first. The direct metadata GETs continue
220
+ // in the background and the next 15-second UI refresh promotes the snapshot.
221
+ void refresh.catch(() => {})
222
+ }
223
+
224
+ async function cloudTraceLoader({ runId, runDirectory }) {
225
+ let state = null
226
+ try {
227
+ state = JSON.parse(fs.readFileSync(path.join(runDirectory, 'run-state.json'), 'utf8'))
228
+ } catch {
229
+ // A not-yet-mirrored rollout should always attempt its first refresh.
230
+ }
231
+ const terminal = [
232
+ 'completed', 'recovered', 'budget_exhausted', 'model_failed',
233
+ 'infrastructure_failed', 'artifact_missing', 'interrupted',
234
+ ].includes(String(state?.state || state?.execution_status || ''))
235
+ const refresh = cloudMetadataLoader({
236
+ id: runId,
237
+ destinationDirectory: runDirectory,
238
+ relativePaths: traceMetadataPaths(),
239
+ maxAgeMs: state ? (terminal ? 5 * 60_000 : 5_000) : 0,
240
+ })
241
+ if (state) {
242
+ void refresh.catch(() => {})
243
+ return
244
+ }
245
+ await refresh
246
+ }
247
+
248
+ async function cloudArtifactLoader({
249
+ runId,
250
+ relativePath,
251
+ artifact,
252
+ expectedSha256 = null,
253
+ force = false,
254
+ }) {
255
+ if (!cloudArtifactsEnabled) return
256
+ if (!force && fs.existsSync(artifact) && fs.statSync(artifact).isFile()) return
257
+ if (cloudDownloads.has(artifact)) return cloudDownloads.get(artifact)
258
+ const download = (async () => {
259
+ fs.mkdirSync(path.dirname(artifact), { recursive: true })
260
+ let lastError = null
261
+ const key = [cloudPrefix, runId, relativePath]
262
+ .join('/')
263
+ .replaceAll('\\', '/')
264
+ .replace(/\/{2,}/g, '/')
265
+ for (let attempt = 1; attempt <= cloudArtifactAttempts; attempt += 1) {
266
+ const temporary = `${artifact}.download-${process.pid}-${Date.now()}-${attempt}`
267
+ try {
268
+ const awsArguments = [
269
+ 's3', 'cp', `s3://${cloudBucket}/${key}`, temporary,
270
+ '--only-show-errors',
271
+ ]
272
+ if (awsProfile) awsArguments.push('--profile', awsProfile)
273
+ if (awsRegion) awsArguments.push('--region', awsRegion)
274
+ await execFileAsync('aws', awsArguments, {
275
+ timeout: cloudArtifactTimeoutMs,
276
+ maxBuffer: 1024 * 1024,
277
+ })
278
+ if (expectedSha256) {
279
+ const actualSha256 = createHash('sha256')
280
+ .update(fs.readFileSync(temporary))
281
+ .digest('hex')
282
+ if (actualSha256 !== expectedSha256) {
283
+ throw new Error(
284
+ `Cloud artifact checksum mismatch for ${relativePath}: expected ${expectedSha256}, got ${actualSha256}`,
285
+ )
286
+ }
287
+ }
288
+ fs.renameSync(temporary, artifact)
289
+ return
290
+ } catch (error) {
291
+ lastError = error
292
+ } finally {
293
+ try {
294
+ if (fs.existsSync(temporary)) fs.unlinkSync(temporary)
295
+ } catch {
296
+ // A failed cleanup cannot change the artifact response.
297
+ }
298
+ }
299
+ if (attempt < cloudArtifactAttempts) {
300
+ await wait(150 * attempt * attempt)
301
+ }
302
+ }
303
+ throw lastError || new Error(`Cloud artifact download failed for ${relativePath}`)
304
+ })()
305
+ cloudDownloads.set(artifact, download)
306
+ try {
307
+ await download
308
+ } finally {
309
+ cloudDownloads.delete(artifact)
310
+ }
311
+ }
312
+
313
+ async function cloudArtifactUrl({ runId, relativePath }) {
314
+ if (!cloudArtifactsEnabled) return null
315
+ const key = [cloudPrefix, runId, relativePath]
316
+ .join('/')
317
+ .replaceAll('\\', '/')
318
+ .replace(/\/{2,}/g, '/')
319
+ const extension = path.extname(relativePath).toLowerCase()
320
+ const responseContentType = {
321
+ '.png': 'image/png',
322
+ '.json': 'application/json',
323
+ '.jsonl': 'application/x-ndjson',
324
+ '.dwg': 'application/acad',
325
+ }[extension] || 'application/octet-stream'
326
+ return getSignedUrl(
327
+ s3,
328
+ new GetObjectCommand({
329
+ Bucket: cloudBucket,
330
+ Key: key,
331
+ ResponseCacheControl: 'private, max-age=31536000, immutable',
332
+ ResponseContentType: responseContentType,
333
+ }),
334
+ { expiresIn: signedArtifactLifetimeSeconds },
335
+ )
336
+ }
337
+
338
+ const api = createTraceApi({
339
+ cloudArtifactLoader,
340
+ cloudArtifactUrl,
341
+ cloudBenchmarkLoader,
342
+ cloudTraceLoader,
343
+ })
344
+ const host = process.env.HOST || '127.0.0.1'
345
+ const port = Number(process.env.PORT || 4173)
346
+
347
+ const mime = {
348
+ '.css': 'text/css; charset=utf-8',
349
+ '.html': 'text/html; charset=utf-8',
350
+ '.js': 'text/javascript; charset=utf-8',
351
+ '.json': 'application/json; charset=utf-8',
352
+ '.png': 'image/png',
353
+ '.svg': 'image/svg+xml',
354
+ '.woff2': 'font/woff2',
355
+ }
356
+
357
+ http
358
+ .createServer(async (request, response) => {
359
+ if (await api(request, response)) return
360
+ const pathname = decodeURIComponent(new URL(request.url || '/', `http://${host}`).pathname)
361
+ const requested = path.resolve(dist, pathname.replace(/^\/+/, ''))
362
+ const safe = requested.startsWith(dist + path.sep) ? requested : null
363
+ const filePath = safe && fs.existsSync(safe) && fs.statSync(safe).isFile()
364
+ ? safe
365
+ : path.join(dist, 'index.html')
366
+ response.statusCode = 200
367
+ response.setHeader('Content-Type', mime[path.extname(filePath)] || 'application/octet-stream')
368
+ fs.createReadStream(filePath).pipe(response)
369
+ })
370
+ .listen(port, host, () => {
371
+ process.stdout.write(`AutoCAD Trace Console: http://${host}:${port}\n`)
372
+ })
apps/dashboard/src/App.vue ADDED
@@ -0,0 +1,722 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup lang="ts">
2
+ import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
3
+ import type {
4
+ BenchmarkDetail,
5
+ BenchmarkSummary,
6
+ BenchmarkTask,
7
+ EvaluationScoreSummary,
8
+ FrameArtifact,
9
+ InputReceipt,
10
+ ModelTurn,
11
+ TraceDetail,
12
+ } from './types'
13
+
14
+ type ExpandedImage = { src: string; label: string }
15
+ type BenchmarkStatusFilter = 'all' | 'live' | 'completed' | 'failed'
16
+ type TaskTypeFilter = 'all' | '2d' | '3d'
17
+
18
+ const benchmarks = ref<BenchmarkSummary[]>([])
19
+ const selectedBenchmarkId = ref('')
20
+ const benchmark = ref<BenchmarkDetail | null>(null)
21
+ const selectedTaskId = ref('')
22
+ const trace = ref<TraceDetail | null>(null)
23
+ const selectedTurn = ref(0)
24
+
25
+ const benchmarkQuery = ref('')
26
+ const benchmarkStatus = ref<BenchmarkStatusFilter>('all')
27
+ const benchmarkModel = ref('all')
28
+ const taskQuery = ref('')
29
+ const taskType = ref<TaskTypeFilter>('all')
30
+ const taskLevel = ref('all')
31
+ const taskState = ref('all')
32
+
33
+ const loading = ref(true)
34
+ const detailLoading = ref(false)
35
+ const traceLoading = ref(false)
36
+ const error = ref('')
37
+ const lastRefresh = ref<Date | null>(null)
38
+ const expandedImage = ref<ExpandedImage | null>(null)
39
+ let refreshTimer: number | undefined
40
+ let refreshInFlight = false
41
+ let benchmarkLoadVersion = 0
42
+ let traceLoadVersion = 0
43
+ let routeSyncVersion = 0
44
+ const imageRetryTimers = new Set<number>()
45
+ const IMAGE_RETRY_LIMIT = 4
46
+ const API_TIMEOUT_MS = 20_000
47
+
48
+ async function fetchJson<T>(url: string): Promise<T> {
49
+ const controller = new AbortController()
50
+ const timeout = window.setTimeout(() => controller.abort(), API_TIMEOUT_MS)
51
+ try {
52
+ const response = await fetch(url, {
53
+ cache: 'no-store',
54
+ signal: controller.signal,
55
+ })
56
+ if (!response.ok) throw new Error(`Request failed (HTTP ${response.status})`)
57
+ return await response.json() as T
58
+ } catch (reason) {
59
+ if (reason instanceof DOMException && reason.name === 'AbortError') {
60
+ throw new Error('The dashboard request timed out. Retry to refresh the local cloud cache.')
61
+ }
62
+ throw reason
63
+ } finally {
64
+ window.clearTimeout(timeout)
65
+ }
66
+ }
67
+
68
+ const activeStatuses = new Set(['starting', 'resetting', 'handoff_ready', 'running', 'finalizing'])
69
+
70
+ const modelOptions = computed(() =>
71
+ [...new Set(benchmarks.value.flatMap((item) => item.models || [item.modelId]))].sort(),
72
+ )
73
+
74
+ const globalSummary = computed(() => {
75
+ const totalTasks = benchmarks.value.reduce((sum, item) => sum + item.aggregate.totalTasks, 0)
76
+ const scoredTasks = benchmarks.value.reduce((sum, item) => sum + item.aggregate.scoredTasks, 0)
77
+ const weightedScore = benchmarks.value.reduce(
78
+ (sum, item) => sum + (item.aggregate.meanScore || 0) * item.aggregate.scoredTasks,
79
+ 0,
80
+ )
81
+ const visionScoredTasks = benchmarks.value.reduce(
82
+ (sum, item) => sum + item.aggregate.visionScoredTasks,
83
+ 0,
84
+ )
85
+ const visionWeightedScore = benchmarks.value.reduce(
86
+ (sum, item) => sum
87
+ + (item.aggregate.visionMeanScore || 0) * item.aggregate.visionScoredTasks,
88
+ 0,
89
+ )
90
+ const deterministicScoredTasks = benchmarks.value.reduce(
91
+ (sum, item) => sum + item.aggregate.deterministicScoredTasks,
92
+ 0,
93
+ )
94
+ const deterministicWeightedScore = benchmarks.value.reduce(
95
+ (sum, item) => sum
96
+ + (item.aggregate.deterministicMeanScore || 0)
97
+ * item.aggregate.deterministicScoredTasks,
98
+ 0,
99
+ )
100
+ return {
101
+ suites: benchmarks.value.length,
102
+ live: benchmarks.value.filter((item) => ['starting', 'running'].includes(item.status)).length,
103
+ totalTasks,
104
+ scoredTasks,
105
+ meanScore: scoredTasks ? weightedScore / scoredTasks : null,
106
+ visionScoredTasks,
107
+ visionMeanScore: visionScoredTasks ? visionWeightedScore / visionScoredTasks : null,
108
+ deterministicScoredTasks,
109
+ deterministicMeanScore: deterministicScoredTasks
110
+ ? deterministicWeightedScore / deterministicScoredTasks
111
+ : null,
112
+ models: new Set(benchmarks.value.flatMap((item) => item.models || [item.modelId])).size,
113
+ }
114
+ })
115
+
116
+ const filteredBenchmarks = computed(() => {
117
+ const query = benchmarkQuery.value.trim().toLowerCase()
118
+ return benchmarks.value.filter((item) => {
119
+ const statusMatches = benchmarkStatus.value === 'all'
120
+ || (benchmarkStatus.value === 'live' && ['starting', 'running'].includes(item.status))
121
+ || (benchmarkStatus.value === 'completed' && ['completed', 'completed_with_issues'].includes(item.status))
122
+ || (benchmarkStatus.value === 'failed' && ['failed', 'interrupted', 'error'].includes(item.status))
123
+ const modelMatches = benchmarkModel.value === 'all'
124
+ || (item.models || [item.modelId]).includes(benchmarkModel.value)
125
+ const queryMatches = !query || [
126
+ item.name,
127
+ item.batchId,
128
+ item.modelId,
129
+ item.provider,
130
+ ...(item.models || []),
131
+ ].some((value) => String(value || '').toLowerCase().includes(query))
132
+ return statusMatches && modelMatches && queryMatches
133
+ })
134
+ })
135
+
136
+ const selectedTask = computed(() =>
137
+ benchmark.value?.tasks.find((task) => task.taskId === selectedTaskId.value) || null,
138
+ )
139
+
140
+ const taskLevelOptions = computed(() =>
141
+ [...new Set(benchmark.value?.tasks.map((task) => task.level) || [])].sort(),
142
+ )
143
+
144
+ const taskStateOptions = computed(() =>
145
+ [...new Set(benchmark.value?.tasks.map((task) => task.status) || [])].sort(),
146
+ )
147
+
148
+ const filteredTasks = computed(() => {
149
+ const query = taskQuery.value.trim().toLowerCase()
150
+ return (benchmark.value?.tasks || []).filter((task) => {
151
+ const queryMatches = !query || [
152
+ task.taskId,
153
+ task.displayName,
154
+ task.modelId,
155
+ task.provider,
156
+ ].some((value) => value.toLowerCase().includes(query))
157
+ return queryMatches
158
+ && (taskType.value === 'all' || task.drawingType === taskType.value)
159
+ && (taskLevel.value === 'all' || task.level === taskLevel.value)
160
+ && (taskState.value === 'all' || task.status === taskState.value)
161
+ })
162
+ })
163
+
164
+ const currentTurn = computed<ModelTurn | null>(() =>
165
+ trace.value?.turns[selectedTurn.value] || null,
166
+ )
167
+
168
+ const currentReceipts = computed<InputReceipt[]>(() => {
169
+ const toolUseId = currentTurn.value?.tool_use_id
170
+ const receipts = trace.value?.receipts || []
171
+ if (toolUseId && receipts.some((receipt) => receipt.tool_use_id)) {
172
+ return receipts.filter((receipt) => receipt.tool_use_id === toolUseId)
173
+ }
174
+ const legacy = receipts.find((receipt) => receipt.action_index === selectedTurn.value + 1)
175
+ return legacy ? [legacy] : []
176
+ })
177
+
178
+ const currentActions = computed<Record<string, unknown>[]>(() => {
179
+ const payload = currentTurn.value?.action
180
+ if (Array.isArray(payload?.actions)) {
181
+ return payload.actions.filter(
182
+ (action): action is Record<string, unknown> => Boolean(action) && typeof action === 'object',
183
+ )
184
+ }
185
+ return payload ? [payload] : []
186
+ })
187
+
188
+ function frameAt(sequence: number): FrameArtifact | null {
189
+ const name = `frame_${String(sequence).padStart(4, '0')}.png`
190
+ return trace.value?.frames.find((frame) => frame.name === name) || null
191
+ }
192
+
193
+ const latestFrame = computed(() => trace.value?.frames.at(-1) || null)
194
+
195
+ const turnInputFrame = computed(() => {
196
+ const match = JSON.stringify(currentTurn.value?.model_input || {}).match(/frames\/frame_(\d+)\.png/)
197
+ if (match) return frameAt(Number(match[1]))
198
+ const actionIndex = currentReceipts.value[0]?.action_index
199
+ return actionIndex != null ? frameAt(actionIndex - 1) : null
200
+ })
201
+
202
+ const turnOutputFrame = computed(() => {
203
+ const receipt = currentReceipts.value.at(-1)
204
+ if (receipt) return frameAt(receipt.action_index)
205
+ const nextTurn = trace.value?.turns[selectedTurn.value + 1]
206
+ const match = JSON.stringify(nextTurn?.model_input || {}).match(/frames\/frame_(\d+)\.png/)
207
+ return match ? frameAt(Number(match[1])) : latestFrame.value
208
+ })
209
+
210
+ function formatDate(value: string | null): string {
211
+ if (!value) return 'Unknown date'
212
+ const date = new Date(value)
213
+ return Number.isNaN(date.valueOf())
214
+ ? value
215
+ : new Intl.DateTimeFormat(undefined, {
216
+ month: 'short',
217
+ day: 'numeric',
218
+ year: 'numeric',
219
+ hour: 'numeric',
220
+ minute: '2-digit',
221
+ }).format(date)
222
+ }
223
+
224
+ function formatDuration(seconds: number | null): string {
225
+ if (seconds == null) return '—'
226
+ if (seconds < 60) return `${Math.round(seconds)}s`
227
+ const hours = Math.floor(seconds / 3600)
228
+ const minutes = Math.floor((seconds % 3600) / 60)
229
+ return hours ? `${hours}h ${minutes}m` : `${minutes}m ${Math.round(seconds % 60)}s`
230
+ }
231
+
232
+ function formatCost(value: number | null): string {
233
+ if (value == null) return '—'
234
+ return new Intl.NumberFormat(undefined, {
235
+ style: 'currency',
236
+ currency: 'USD',
237
+ minimumFractionDigits: value < 1 ? 3 : 2,
238
+ maximumFractionDigits: value < 1 ? 3 : 2,
239
+ }).format(value)
240
+ }
241
+
242
+ function formatNumber(value: number): string {
243
+ return new Intl.NumberFormat().format(value)
244
+ }
245
+
246
+ function formatScore(score: number | null): string {
247
+ return score == null ? '—' : (score * 100).toFixed(1)
248
+ }
249
+
250
+ function readable(value: string): string {
251
+ return value.replaceAll('_', ' ')
252
+ }
253
+
254
+ function benchmarkProgress(item: BenchmarkSummary): number {
255
+ if (!item.aggregate.totalTasks) return 0
256
+ const settled = item.aggregate.settledTasks
257
+ return Math.max(0, Math.min(100, settled / item.aggregate.totalTasks * 100))
258
+ }
259
+
260
+ function stateTone(value: string): string {
261
+ if (['target_acquired', 'scored', 'completed', 'recovered'].includes(value)) return 'positive'
262
+ if (['mostly_acquired', 'awaiting_score', 'partial', 'budget_exhausted', 'completed_with_issues'].includes(value)) return 'warning'
263
+ if (['not_acquired', 'candidate_invalid', 'evaluation_failed', 'evaluation_interrupted', 'run_failed', 'model_failed', 'infrastructure_failed', 'artifact_missing', 'failed', 'error', 'interrupted', 'no_artifact'].includes(value)) return 'negative'
264
+ if (['running', 'evaluating', 'evaluation_retrying', 'starting', 'resetting', 'handoff_ready', 'finalizing'].includes(value)) return 'live'
265
+ return 'neutral'
266
+ }
267
+
268
+ function taskOutcome(task: BenchmarkTask): string {
269
+ return task.verdict ? readable(task.verdict) : readable(task.evaluationState)
270
+ }
271
+
272
+ function scoreSourceLabel(task: BenchmarkTask): string {
273
+ if (task.scoreSource === 'vision_judge') return 'Vision judge'
274
+ if (task.scoreSource === 'deterministic') return 'Deterministic'
275
+ if (task.evaluationState === 'evaluating') return readable(task.evaluationStage || 'evaluating')
276
+ if (task.evaluationState === 'evaluation_failed') return 'Automatic evaluation failed'
277
+ if (task.evaluationState === 'evaluation_interrupted') return 'Automatic evaluation interrupted'
278
+ if (task.evaluationState === 'evaluation_retrying') return 'Vision judge retry queued'
279
+ if (task.evaluationState === 'candidate_invalid') return 'Invalid saved DWG'
280
+ return 'Not scored'
281
+ }
282
+
283
+ function judgeResultLabel(score: EvaluationScoreSummary | null): string {
284
+ if (!score) return 'Not available'
285
+ if (score.valid === false) return readable(score.failureReason || 'invalid candidate')
286
+ if (score.verdict) return readable(score.verdict)
287
+ return score.score != null ? 'Valid diagnostic' : 'Not scored'
288
+ }
289
+
290
+ function judgeResultTone(score: EvaluationScoreSummary | null): string {
291
+ if (!score) return 'neutral'
292
+ if (score.valid === false) return 'negative'
293
+ return stateTone(score.verdict || (score.score != null ? 'scored' : 'neutral'))
294
+ }
295
+
296
+ function resetTaskFilters() {
297
+ taskQuery.value = ''
298
+ taskType.value = 'all'
299
+ taskLevel.value = 'all'
300
+ taskState.value = 'all'
301
+ }
302
+
303
+ function updateHash() {
304
+ const params = new URLSearchParams()
305
+ if (selectedBenchmarkId.value) params.set('benchmark', selectedBenchmarkId.value)
306
+ if (selectedTaskId.value) params.set('task', selectedTaskId.value)
307
+ window.history.replaceState(null, '', params.size ? `#${params.toString()}` : window.location.pathname)
308
+ }
309
+
310
+ async function loadBenchmarks(silent = false) {
311
+ if (!silent) loading.value = true
312
+ try {
313
+ const payload = await fetchJson<{ benchmarks: BenchmarkSummary[] }>('/api/benchmarks')
314
+ benchmarks.value = payload.benchmarks
315
+ lastRefresh.value = new Date()
316
+ error.value = ''
317
+ } catch (reason) {
318
+ error.value = reason instanceof Error ? reason.message : String(reason)
319
+ } finally {
320
+ loading.value = false
321
+ }
322
+ }
323
+
324
+ async function loadBenchmark(id: string, silent = false) {
325
+ const requestVersion = ++benchmarkLoadVersion
326
+ if (!id) {
327
+ benchmark.value = null
328
+ return
329
+ }
330
+ if (!silent) detailLoading.value = true
331
+ try {
332
+ const payload = await fetchJson<BenchmarkDetail>(
333
+ `/api/benchmarks/${encodeURIComponent(id)}`,
334
+ )
335
+ if (requestVersion !== benchmarkLoadVersion || selectedBenchmarkId.value !== id) return
336
+ benchmark.value = payload
337
+ error.value = ''
338
+ } catch (reason) {
339
+ if (requestVersion !== benchmarkLoadVersion || selectedBenchmarkId.value !== id) return
340
+ error.value = reason instanceof Error ? reason.message : String(reason)
341
+ } finally {
342
+ if (requestVersion === benchmarkLoadVersion) detailLoading.value = false
343
+ }
344
+ }
345
+
346
+ async function loadTrace(runId: string, silent = false) {
347
+ const requestVersion = ++traceLoadVersion
348
+ if (!runId) {
349
+ trace.value = null
350
+ return
351
+ }
352
+ if (!silent) traceLoading.value = true
353
+ try {
354
+ const payload = await fetchJson<TraceDetail>(`/api/runs/${encodeURIComponent(runId)}`)
355
+ if (requestVersion !== traceLoadVersion || selectedTask.value?.runId !== runId) return
356
+ trace.value = payload
357
+ selectedTurn.value = Math.min(selectedTurn.value, Math.max(0, trace.value.turns.length - 1))
358
+ error.value = ''
359
+ } catch (reason) {
360
+ if (requestVersion !== traceLoadVersion || selectedTask.value?.runId !== runId) return
361
+ trace.value = null
362
+ error.value = reason instanceof Error ? reason.message : String(reason)
363
+ } finally {
364
+ if (requestVersion === traceLoadVersion) traceLoading.value = false
365
+ }
366
+ }
367
+
368
+ async function syncRouteFromHash(force = false) {
369
+ const requestVersion = ++routeSyncVersion
370
+ const hash = new URLSearchParams(window.location.hash.replace(/^#/, ''))
371
+ const nextBenchmarkId = hash.get('benchmark') || ''
372
+ const nextTaskId = hash.get('task') || ''
373
+ const benchmarkChanged = nextBenchmarkId !== selectedBenchmarkId.value
374
+ const taskChanged = nextTaskId !== selectedTaskId.value
375
+
376
+ if (!force && !benchmarkChanged && !taskChanged) return
377
+
378
+ selectedBenchmarkId.value = nextBenchmarkId
379
+ selectedTaskId.value = nextTaskId
380
+ selectedTurn.value = 0
381
+ trace.value = null
382
+ if (benchmarkChanged) {
383
+ benchmark.value = null
384
+ resetTaskFilters()
385
+ }
386
+
387
+ if (!nextBenchmarkId) return
388
+ if (benchmarkChanged || force || !benchmark.value) await loadBenchmark(nextBenchmarkId)
389
+ if (requestVersion !== routeSyncVersion) return
390
+
391
+ const task = selectedTask.value
392
+ if (task && task.status !== 'queued') await loadTrace(task.runId)
393
+ }
394
+
395
+ function onHashChange() {
396
+ void syncRouteFromHash()
397
+ }
398
+
399
+ async function openBenchmark(id: string) {
400
+ selectedBenchmarkId.value = id
401
+ selectedTaskId.value = ''
402
+ benchmark.value = null
403
+ trace.value = null
404
+ resetTaskFilters()
405
+ updateHash()
406
+ await loadBenchmark(id)
407
+ window.scrollTo({ top: 0, behavior: 'smooth' })
408
+ }
409
+
410
+ function closeBenchmark() {
411
+ selectedBenchmarkId.value = ''
412
+ selectedTaskId.value = ''
413
+ benchmark.value = null
414
+ trace.value = null
415
+ updateHash()
416
+ requestAnimationFrame(() => window.scrollTo({ top: 0, behavior: 'smooth' }))
417
+ }
418
+
419
+ async function openTask(task: BenchmarkTask) {
420
+ if (task.status === 'queued') return
421
+ selectedTaskId.value = task.taskId
422
+ selectedTurn.value = 0
423
+ trace.value = null
424
+ updateHash()
425
+ await loadTrace(task.runId)
426
+ requestAnimationFrame(() => document.querySelector('#task-inspector')?.scrollIntoView({
427
+ behavior: 'smooth',
428
+ block: 'start',
429
+ }))
430
+ }
431
+
432
+ function closeTask() {
433
+ selectedTaskId.value = ''
434
+ trace.value = null
435
+ updateHash()
436
+ }
437
+
438
+ function chooseTurn(index: number) {
439
+ selectedTurn.value = Math.max(0, Math.min(index, Math.max(0, (trace.value?.turns.length || 1) - 1)))
440
+ }
441
+
442
+ function openImage(src: string | null | undefined, label: string) {
443
+ if (src) expandedImage.value = { src, label }
444
+ }
445
+
446
+ function artifactImageLoaded(event: Event) {
447
+ const image = event.currentTarget as HTMLImageElement
448
+ image.dataset.retryAttempts = '0'
449
+ image.dataset.loadState = 'loaded'
450
+ }
451
+
452
+ function retryArtifactImage(event: Event) {
453
+ const image = event.currentTarget as HTMLImageElement
454
+ const source = image.dataset.source || image.getAttribute('src') || ''
455
+ const attempts = Number(image.dataset.retryAttempts || 0)
456
+ if (!source || attempts >= IMAGE_RETRY_LIMIT) {
457
+ image.dataset.loadState = 'failed'
458
+ return
459
+ }
460
+ const nextAttempt = attempts + 1
461
+ image.dataset.retryAttempts = String(nextAttempt)
462
+ image.dataset.loadState = 'retrying'
463
+ const retryDelay = Math.min(2_000, 200 * 2 ** (nextAttempt - 1))
464
+ const timer = window.setTimeout(() => {
465
+ imageRetryTimers.delete(timer)
466
+ const retryUrl = new URL(source, window.location.origin)
467
+ retryUrl.searchParams.set('_image_retry', `${nextAttempt}-${Date.now()}`)
468
+ image.src = retryUrl.toString()
469
+ }, retryDelay)
470
+ imageRetryTimers.add(timer)
471
+ }
472
+
473
+ async function refresh() {
474
+ if (refreshInFlight || document.hidden) return
475
+ refreshInFlight = true
476
+ try {
477
+ await loadBenchmarks(true)
478
+ if (selectedBenchmarkId.value) await loadBenchmark(selectedBenchmarkId.value, true)
479
+ const task = selectedTask.value
480
+ if (task && (
481
+ activeStatuses.has(task.status) || task.evaluationState === 'evaluating'
482
+ )) {
483
+ await loadTrace(task.runId, true)
484
+ }
485
+ } finally {
486
+ refreshInFlight = false
487
+ }
488
+ }
489
+
490
+ function onKeydown(event: KeyboardEvent) {
491
+ if (event.key === 'Escape') {
492
+ if (expandedImage.value) expandedImage.value = null
493
+ else if (selectedTaskId.value) closeTask()
494
+ return
495
+ }
496
+ if (expandedImage.value) return
497
+ if (event.target instanceof HTMLInputElement || event.target instanceof HTMLSelectElement) return
498
+ if (event.key === 'ArrowLeft') chooseTurn(selectedTurn.value - 1)
499
+ if (event.key === 'ArrowRight') chooseTurn(selectedTurn.value + 1)
500
+ }
501
+
502
+ onMounted(async () => {
503
+ window.addEventListener('hashchange', onHashChange)
504
+ window.addEventListener('keydown', onKeydown)
505
+ await Promise.all([
506
+ loadBenchmarks(),
507
+ syncRouteFromHash(true),
508
+ ])
509
+ refreshTimer = window.setInterval(refresh, 15_000)
510
+ })
511
+
512
+ onBeforeUnmount(() => {
513
+ if (refreshTimer) window.clearInterval(refreshTimer)
514
+ for (const timer of imageRetryTimers) window.clearTimeout(timer)
515
+ imageRetryTimers.clear()
516
+ window.removeEventListener('hashchange', onHashChange)
517
+ window.removeEventListener('keydown', onKeydown)
518
+ })
519
+ </script>
520
+
521
+ <template>
522
+ <div class="app">
523
+ <header class="app-header">
524
+ <button class="brand" type="button" @click="closeBenchmark">
525
+ <span class="brand-cube" aria-hidden="true"><i></i><i></i><i></i></span>
526
+ <span>
527
+ <strong>AutoCAD Bench</strong>
528
+ <small>Model evaluation registry</small>
529
+ </span>
530
+ </button>
531
+ <div class="header-actions">
532
+ <span v-if="globalSummary.live" class="live-indicator"><i></i>{{ globalSummary.live }} live</span>
533
+ <span class="refresh-time">Updated {{ lastRefresh ? formatDate(lastRefresh.toISOString()) : '—' }}</span>
534
+ <button class="icon-button" type="button" title="Refresh benchmark data" @click="refresh">↻</button>
535
+ </div>
536
+ </header>
537
+
538
+ <main class="page">
539
+ <div v-if="error" class="error-banner">{{ error }}</div>
540
+
541
+ <template v-if="!selectedBenchmarkId">
542
+ <section class="page-heading">
543
+ <div>
544
+ <p class="kicker">BENCHMARK HISTORY</p>
545
+ <h1>Every AutoCAD evaluation,<br><span>in one place.</span></h1>
546
+ <p class="heading-copy">
547
+ Follow live suites, compare model outcomes, and inspect any task without switching between benchmark and rollout views.
548
+ </p>
549
+ </div>
550
+ <div class="corpus-badge">
551
+ <span>Current corpus</span>
552
+ <strong>65 tasks</strong>
553
+ <small>34 2D · 31 3D</small>
554
+ </div>
555
+ </section>
556
+
557
+ <section class="overview-grid" aria-label="Benchmark totals">
558
+ <article><span>Benchmark suites</span><strong>{{ globalSummary.suites }}</strong><small>{{ globalSummary.live ? `${globalSummary.live} currently live` : 'No active suites' }}</small></article>
559
+ <article><span>Task attempts</span><strong>{{ formatNumber(globalSummary.totalTasks) }}</strong><small>Across every recorded suite</small></article>
560
+ <article><span>Scored results</span><strong>{{ formatNumber(globalSummary.scoredTasks) }}</strong><small>{{ globalSummary.totalTasks ? Math.round(globalSummary.scoredTasks / globalSummary.totalTasks * 100) : 0 }}% evaluation coverage</small></article>
561
+ <article class="score-overview vision-score"><span>Vision mean</span><strong>{{ formatScore(globalSummary.visionMeanScore) }}<small v-if="globalSummary.visionMeanScore != null">/100</small></strong><small>{{ globalSummary.visionScoredTasks }} vision judgments</small></article>
562
+ <article class="score-overview deterministic-score"><span>Deterministic mean</span><strong>{{ formatScore(globalSummary.deterministicMeanScore) }}<small v-if="globalSummary.deterministicMeanScore != null">/100</small></strong><small>{{ globalSummary.deterministicScoredTasks }} diagnostics</small></article>
563
+ <article><span>Models evaluated</span><strong>{{ globalSummary.models }}</strong><small>Unique provider model IDs</small></article>
564
+ </section>
565
+
566
+ <section class="registry-panel">
567
+ <div class="registry-heading">
568
+ <div><p class="kicker">ALL RUNS</p><h2>Benchmark registry</h2></div>
569
+ <span>{{ filteredBenchmarks.length }} of {{ benchmarks.length }} suites</span>
570
+ </div>
571
+ <div class="filter-bar">
572
+ <label class="search-field"><span>Search</span><input v-model="benchmarkQuery" type="search" placeholder="Name, model, provider, batch ID…" /></label>
573
+ <label><span>Status</span><select v-model="benchmarkStatus"><option value="all">All statuses</option><option value="live">Live</option><option value="completed">Completed</option><option value="failed">Failed / interrupted</option></select></label>
574
+ <label><span>Model</span><select v-model="benchmarkModel"><option value="all">All models</option><option v-for="model in modelOptions" :key="model" :value="model">{{ model }}</option></select></label>
575
+ </div>
576
+ <div v-if="loading" class="empty-panel">Loading benchmark history…</div>
577
+ <div v-else-if="!filteredBenchmarks.length" class="empty-panel"><strong>No matching benchmarks</strong><span>Clear the filters to see the complete history.</span></div>
578
+ <div v-else class="benchmark-list">
579
+ <button v-for="item in filteredBenchmarks" :key="item.id" type="button" class="benchmark-row" @click="openBenchmark(item.id)">
580
+ <span class="row-status" :class="stateTone(item.status)"><i></i>{{ readable(item.status) }}</span>
581
+ <span class="row-identity"><strong>{{ item.name }}</strong><small>{{ item.modelId }} · {{ item.provider }} · {{ formatDate(item.startedAt || item.updatedAt) }}</small></span>
582
+ <span class="row-progress"><span><strong>{{ item.aggregate.settledTasks }}</strong>/{{ item.aggregate.totalTasks }} settled</span><i><b :style="{ width: `${benchmarkProgress(item)}%` }"></b></i></span>
583
+ <span class="row-result row-vision-score"><strong>{{ formatScore(item.aggregate.visionMeanScore) }}</strong><small>Vision · {{ item.aggregate.visionScoredTasks }} judged</small></span>
584
+ <span class="row-result row-deterministic-score"><strong>{{ formatScore(item.aggregate.deterministicMeanScore) }}</strong><small>Deterministic · {{ item.aggregate.deterministicScoredTasks }}</small></span>
585
+ <span class="row-coverage"><strong>{{ item.aggregate.evaluationStates?.evaluating || item.aggregate.evaluationStates?.awaiting_score || 0 }}</strong><small>{{ item.aggregate.evaluationStates?.evaluating ? 'evaluating now' : 'awaiting score' }}</small></span>
586
+ <span class="row-cost"><strong>{{ formatCost(item.aggregate.usage.apiCost) }}</strong><small>model spend</small></span>
587
+ <span class="row-arrow">→</span>
588
+ </button>
589
+ </div>
590
+ </section>
591
+ </template>
592
+
593
+ <template v-else>
594
+ <button class="back-button" type="button" @click="closeBenchmark">← All benchmarks</button>
595
+ <section v-if="detailLoading && !benchmark" class="empty-panel">Loading benchmark…</section>
596
+ <section v-else-if="!benchmark" class="empty-panel"><strong>Benchmark unavailable</strong><span>{{ error || 'The benchmark metadata could not be loaded.' }}</span><button type="button" @click="loadBenchmark(selectedBenchmarkId)">Retry</button></section>
597
+ <template v-else-if="benchmark">
598
+ <section class="detail-heading">
599
+ <div><div class="detail-meta"><span class="status-chip" :class="stateTone(benchmark.status)"><i></i>{{ readable(benchmark.status) }}</span><span>{{ formatDate(benchmark.startedAt) }}</span><span v-if="benchmark.batchId">{{ benchmark.batchId }}</span></div><h1>{{ benchmark.name }}</h1><p>{{ benchmark.modelId }} · {{ benchmark.provider }} · up to {{ benchmark.maxConcurrency || '—' }} concurrent desktops</p></div>
600
+ <div class="detail-score-pair">
601
+ <div class="vision-score"><span>Vision mean · official</span><strong>{{ formatScore(benchmark.aggregate.visionMeanScore) }}</strong><small>{{ benchmark.aggregate.visionScoredTasks }}/{{ benchmark.aggregate.totalTasks }} judged</small></div>
602
+ <div class="deterministic-score"><span>Deterministic mean</span><strong>{{ formatScore(benchmark.aggregate.deterministicMeanScore) }}</strong><small>{{ benchmark.aggregate.deterministicScoredTasks }}/{{ benchmark.aggregate.totalTasks }} diagnostics</small></div>
603
+ </div>
604
+ </section>
605
+
606
+ <section class="detail-metrics">
607
+ <article><span>Suite progress</span><strong>{{ benchmark.aggregate.settledTasks }}/{{ benchmark.aggregate.totalTasks }}</strong><small>{{ benchmark.aggregate.activeTasks }} active · {{ benchmark.aggregate.queuedTasks }} queued</small></article>
608
+ <article><span>Artifacts</span><strong>{{ benchmark.aggregate.artifactTasks }}</strong><small>{{ benchmark.aggregate.evaluationStates.evaluating || 0 }} evaluating · {{ benchmark.aggregate.evaluationStates.awaiting_score || 0 }} waiting</small></article>
609
+ <article><span>Acquired targets</span><strong>{{ benchmark.aggregate.verdicts.target_acquired || 0 }}</strong><small>{{ benchmark.aggregate.verdicts.mostly_acquired || 0 }} mostly acquired</small></article>
610
+ <article><span>Execution issues</span><strong>{{ benchmark.aggregate.failedTasks + benchmark.aggregate.infrastructureFailedTasks + benchmark.aggregate.artifactMissingTasks }}</strong><small>{{ benchmark.aggregate.failedTasks }} model · {{ benchmark.aggregate.infrastructureFailedTasks }} infrastructure · {{ benchmark.aggregate.artifactMissingTasks }} artifact</small></article>
611
+ <article><span>Actions</span><strong>{{ formatNumber(benchmark.aggregate.acceptedActions) }}</strong><small>{{ benchmark.aggregate.invalidActions }} rejected</small></article>
612
+ <article><span>Model spend</span><strong>{{ formatCost(benchmark.aggregate.usage.apiCost) }}</strong><small>{{ formatNumber(benchmark.aggregate.usage.totalTokens) }} tokens</small></article>
613
+ </section>
614
+
615
+ <section class="coverage-strip" aria-label="Evaluation result states">
616
+ <div><span>Execution outcomes</span><strong>{{ benchmark.aggregate.settledTasks }} settled</strong></div>
617
+ <span v-for="(count, state) in benchmark.aggregate.executionStates" :key="`execution-${state}`" class="coverage-chip" :class="stateTone(String(state))">{{ count }} {{ readable(String(state)) }}</span>
618
+ </section>
619
+
620
+ <section class="coverage-strip" aria-label="Evaluation result states">
621
+ <div><span>Evaluation coverage</span><strong>{{ benchmark.aggregate.scoredTasks }} scored</strong></div>
622
+ <span class="coverage-chip positive">{{ benchmark.aggregate.visionScoredTasks }} vision judged</span>
623
+ <span class="coverage-chip neutral">{{ benchmark.aggregate.deterministicScoredTasks }} deterministic</span>
624
+ <span v-for="(count, state) in benchmark.aggregate.evaluationStates" :key="state" class="coverage-chip" :class="stateTone(String(state))">{{ count }} {{ readable(String(state)) }}</span>
625
+ <span class="corpus-split">{{ benchmark.aggregate.drawingTypes['2d']?.total || 0 }} 2D · {{ benchmark.aggregate.drawingTypes['3d']?.total || 0 }} 3D</span>
626
+ </section>
627
+
628
+ <section class="task-panel">
629
+ <div class="registry-heading"><div><p class="kicker">TASK RESULTS</p><h2>All attempts</h2></div><span>{{ filteredTasks.length }} of {{ benchmark.tasks.length }} tasks</span></div>
630
+ <div class="filter-bar task-filters">
631
+ <label class="search-field"><span>Find task</span><input v-model="taskQuery" type="search" placeholder="Task ID or model…" /></label>
632
+ <label><span>Type</span><select v-model="taskType"><option value="all">2D + 3D</option><option value="2d">2D only</option><option value="3d">3D only</option></select></label>
633
+ <label><span>Level</span><select v-model="taskLevel"><option value="all">All levels</option><option v-for="level in taskLevelOptions" :key="level" :value="level">{{ readable(level) }}</option></select></label>
634
+ <label><span>Result</span><select v-model="taskState"><option value="all">All results</option><option v-for="state in taskStateOptions" :key="state" :value="state">{{ readable(state) }}</option></select></label>
635
+ </div>
636
+
637
+ <div class="task-table-head"><span>Task</span><span>Run state</span><span>Outcome</span><span>Vision score</span><span>Deterministic score</span><span>Official components</span><span>Actions</span><span>Time</span><span>Cost</span><span></span></div>
638
+ <div class="task-list">
639
+ <button v-for="task in filteredTasks" :key="task.rolloutId" type="button" class="task-row" :class="{ selected: task.taskId === selectedTaskId, disabled: task.status === 'queued' }" :disabled="task.status === 'queued'" @click="openTask(task)">
640
+ <span class="task-identity"><strong>{{ task.taskId }}</strong><small><b>{{ task.drawingType.toUpperCase() }}</b> {{ readable(task.level) }} · {{ task.split }}</small></span>
641
+ <span><em class="state-dot" :class="stateTone(task.status)"></em>{{ readable(task.status) }}</span>
642
+ <span><strong class="outcome" :class="stateTone(task.verdict || task.evaluationState)">{{ taskOutcome(task) }}</strong><small>{{ scoreSourceLabel(task) }}</small></span>
643
+ <span class="numeric-score vision-score">{{ formatScore(task.visionScore?.score ?? null) }}</span>
644
+ <span class="numeric-score deterministic-score">{{ formatScore(task.deterministicScore?.score ?? null) }}</span>
645
+ <span class="component-mini"><small v-if="task.geometrySimilarity != null">G {{ formatScore(task.geometrySimilarity) }}</small><small v-if="task.dimensionSimilarity != null">D {{ formatScore(task.dimensionSimilarity) }}</small><small v-if="task.geometrySimilarity == null">—</small></span>
646
+ <span>{{ formatNumber(task.acceptedActions) }}</span><span>{{ formatDuration(task.rolloutElapsedSeconds ?? task.elapsedSeconds) }}</span><span>{{ formatCost(task.usage.apiCost) }}</span><span class="row-arrow">→</span>
647
+ </button>
648
+ </div>
649
+ </section>
650
+
651
+ <section v-if="selectedTask" id="task-inspector" class="inspector-panel">
652
+ <header class="inspector-heading">
653
+ <div><p class="kicker">TASK INSPECTOR</p><h2>{{ selectedTask.taskId }}</h2><p>{{ selectedTask.modelId }} · {{ selectedTask.provider }} · {{ selectedTask.drawingType.toUpperCase() }} {{ selectedTask.level }}</p></div>
654
+ <div class="inspector-actions"><a v-if="trace?.artifacts.dwg" class="button secondary" :href="trace.artifacts.dwg">Download DWG</a><button class="icon-button" type="button" title="Close task inspector" @click="closeTask">×</button></div>
655
+ </header>
656
+
657
+ <div class="judge-results">
658
+ <section class="judge-result vision-result">
659
+ <header><div><span>Vision judge</span><strong>Official result</strong></div><div class="judge-total"><strong>{{ formatScore(selectedTask.visionScore?.score ?? null) }}</strong><small>/100</small></div></header>
660
+ <div v-if="selectedTask.visionScore" class="judge-result-body">
661
+ <p :class="judgeResultTone(selectedTask.visionScore)">{{ judgeResultLabel(selectedTask.visionScore) }}</p>
662
+ <div class="judge-components">
663
+ <div><span>Geometry</span><strong>{{ formatScore(selectedTask.visionScore.geometry) }}</strong></div>
664
+ <div><span>Dimensions</span><strong>{{ formatScore(selectedTask.visionScore.dimensions) }}</strong></div>
665
+ <div><span>Annotations</span><strong>{{ formatScore(selectedTask.visionScore.annotations) }}</strong></div>
666
+ <div><span>Presentation</span><strong>{{ formatScore(selectedTask.visionScore.presentation) }}</strong></div>
667
+ </div>
668
+ <small>{{ selectedTask.visionScore.confidence != null ? `${Math.round(selectedTask.visionScore.confidence * 100)}% confidence` : 'No confidence value' }}<template v-if="selectedTask.visionScore.needsHumanReview"> · human review flagged</template></small>
669
+ </div>
670
+ <div v-else class="judge-unavailable">No completed vision judgment</div>
671
+ </section>
672
+
673
+ <section class="judge-result deterministic-result">
674
+ <header><div><span>Deterministic scorer</span><strong>Diagnostic result</strong></div><div class="judge-total"><strong>{{ formatScore(selectedTask.deterministicScore?.score ?? null) }}</strong><small>/100</small></div></header>
675
+ <div v-if="selectedTask.deterministicScore" class="judge-result-body">
676
+ <p :class="judgeResultTone(selectedTask.deterministicScore)">{{ judgeResultLabel(selectedTask.deterministicScore) }}</p>
677
+ <div class="judge-components">
678
+ <div><span>Geometry</span><strong>{{ formatScore(selectedTask.deterministicScore.geometry) }}</strong></div>
679
+ <div><span>Dimensions</span><strong>{{ formatScore(selectedTask.deterministicScore.dimensions) }}</strong></div>
680
+ <div><span>Annotations</span><strong>{{ formatScore(selectedTask.deterministicScore.annotations) }}</strong></div>
681
+ <div><span>Visual</span><strong>{{ formatScore(selectedTask.deterministicScore.presentation) }}</strong></div>
682
+ </div>
683
+ <small>{{ selectedTask.deterministicScore.method || 'Unknown deterministic method' }}</small>
684
+ </div>
685
+ <div v-else class="judge-unavailable">No deterministic diagnostic</div>
686
+ </section>
687
+ </div>
688
+
689
+ <div v-if="traceLoading" class="empty-panel">Loading task artifacts…</div>
690
+ <template v-else-if="trace">
691
+ <div class="artifact-grid">
692
+ <figure><figcaption><span>Reference target</span><small>Task authority</small></figcaption><button type="button" @click="openImage(trace.referenceUrl, 'Reference target')"><img :src="trace.referenceUrl" :data-source="trace.referenceUrl" alt="Reference drawing" loading="eager" decoding="async" fetchpriority="high" @load="artifactImageLoaded" @error="retryArtifactImage" /></button></figure>
693
+ <figure><figcaption><span>Saved candidate</span><small>{{ trace.artifacts.candidateRender ? 'Trusted evaluator render' : 'Final desktop frame' }}</small></figcaption><button v-if="trace.artifacts.candidateRender" type="button" @click="openImage(trace.artifacts.candidateRender, 'Saved candidate render')"><img :src="trace.artifacts.candidateRender" :data-source="trace.artifacts.candidateRender" alt="Candidate DWG render" loading="eager" decoding="async" fetchpriority="high" @load="artifactImageLoaded" @error="retryArtifactImage" /></button><button v-else-if="latestFrame" type="button" @click="openImage(latestFrame.url, 'Final desktop frame')"><img :src="latestFrame.url" :data-source="latestFrame.url" alt="Final desktop frame" loading="eager" decoding="async" fetchpriority="high" @load="artifactImageLoaded" @error="retryArtifactImage" /></button><div v-else class="artifact-missing">No candidate render or frame yet</div></figure>
694
+ <figure v-if="trace.artifacts.diffRender"><figcaption><span>Deterministic diff</span><small>Diagnostic only</small></figcaption><button type="button" @click="openImage(trace.artifacts.diffRender, 'Deterministic diff')"><img :src="trace.artifacts.diffRender" :data-source="trace.artifacts.diffRender" alt="Candidate and gold diff" loading="eager" decoding="async" @load="artifactImageLoaded" @error="retryArtifactImage" /></button></figure>
695
+ </div>
696
+
697
+ <section v-if="trace.turns.length" class="trace-section">
698
+ <header><div><p class="kicker">MODEL TRACE</p><h3>Turn {{ selectedTurn + 1 }} of {{ trace.turns.length }}</h3></div><div class="turn-controls"><button type="button" :disabled="selectedTurn === 0" @click="chooseTurn(selectedTurn - 1)">←</button><select v-model.number="selectedTurn"><option v-for="(turn, index) in trace.turns" :key="turn.tool_use_id || index" :value="index">Turn {{ index + 1 }} · {{ Array.isArray(turn.action?.actions) ? turn.action.actions.length : 1 }} action(s)</option></select><button type="button" :disabled="selectedTurn >= trace.turns.length - 1" @click="chooseTurn(selectedTurn + 1)">→</button></div></header>
699
+ <div class="turn-frame-grid">
700
+ <figure><figcaption><span>Frame shown to model</span><small v-if="turnInputFrame">{{ turnInputFrame.name }}</small></figcaption><button v-if="turnInputFrame" :key="turnInputFrame.url" type="button" @click="openImage(turnInputFrame.url, 'Frame shown to model')"><img :src="turnInputFrame.url" :data-source="turnInputFrame.url" alt="Desktop frame shown to model" loading="eager" decoding="async" @load="artifactImageLoaded" @error="retryArtifactImage" /></button><div v-else class="artifact-missing">Frame unavailable</div></figure>
701
+ <figure><figcaption><span>Final frame after batch</span><small v-if="turnOutputFrame">{{ turnOutputFrame.name }}</small></figcaption><button v-if="turnOutputFrame" :key="turnOutputFrame.url" type="button" @click="openImage(turnOutputFrame.url, 'Final frame after batch')"><img :src="turnOutputFrame.url" :data-source="turnOutputFrame.url" alt="Final desktop frame after action batch" loading="eager" decoding="async" @load="artifactImageLoaded" @error="retryArtifactImage" /></button><div v-else class="artifact-missing">Frame unavailable</div></figure>
702
+ </div>
703
+ <div class="action-list"><article v-for="(action, index) in currentActions" :key="index"><span>{{ index + 1 }}</span><div><strong>{{ readable(String(action.action || 'unknown')) }}</strong><p>{{ String(action.intent || 'No intent recorded') }}</p></div><code>{{ Object.fromEntries(Object.entries(action).filter(([key]) => !['action', 'intent'].includes(key))) }}</code></article></div>
704
+ <details class="raw-details"><summary>Raw turn, both scores, and controller receipts</summary><div><pre>{{ JSON.stringify(currentTurn, null, 2) }}</pre><pre>{{ JSON.stringify(currentReceipts, null, 2) }}</pre><pre>{{ JSON.stringify(trace.scores, null, 2) }}</pre></div></details>
705
+ </section>
706
+ <div v-else class="empty-panel">
707
+ {{ trace.traceNotice || 'No model turns were recorded for this task.' }}
708
+ </div>
709
+ </template>
710
+ <div v-else class="empty-panel"><strong>No trace directory is available</strong><span>The batch row exists, but its rollout never produced inspectable artifacts.</span></div>
711
+ </section>
712
+ </template>
713
+ </template>
714
+ </main>
715
+
716
+ <Transition name="lightbox">
717
+ <div v-if="expandedImage" class="lightbox" role="dialog" aria-modal="true" @click.self="expandedImage = null">
718
+ <div><header><strong>{{ expandedImage.label }}</strong><button type="button" @click="expandedImage = null">×</button></header><img :src="expandedImage.src" :data-source="expandedImage.src" :alt="expandedImage.label" loading="eager" decoding="async" @load="artifactImageLoaded" @error="retryArtifactImage" /><small>Press Escape to close</small></div>
719
+ </div>
720
+ </Transition>
721
+ </div>
722
+ </template>
apps/dashboard/src/main.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import { createApp } from 'vue'
2
+ import App from './App.vue'
3
+ import './styles.css'
4
+
5
+ createApp(App).mount('#app')
apps/dashboard/src/styles.css ADDED
@@ -0,0 +1,451 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700;800&display=swap');
2
+
3
+ :root {
4
+ color-scheme: dark;
5
+ font-family: 'Manrope', system-ui, sans-serif;
6
+ --bg: #080b0f;
7
+ --panel: #10151b;
8
+ --panel-2: #151b22;
9
+ --panel-3: #0c1116;
10
+ --line: #252d36;
11
+ --line-soft: #1c232b;
12
+ --text: #f5f7f8;
13
+ --muted: #8996a3;
14
+ --muted-2: #b1bbc4;
15
+ --lime: #c9f66b;
16
+ --cyan: #6dd7e8;
17
+ --amber: #f2bc66;
18
+ --red: #ff7676;
19
+ --green: #72dfa5;
20
+ background: var(--bg);
21
+ color: var(--text);
22
+ font-synthesis: none;
23
+ text-rendering: optimizeLegibility;
24
+ }
25
+ * { box-sizing: border-box; }
26
+ html { min-width: 320px; background: var(--bg); }
27
+ body { min-width: 320px; min-height: 100vh; margin: 0; background:
28
+ radial-gradient(circle at 20% -10%, rgba(109, 215, 232, .09), transparent 28rem),
29
+ radial-gradient(circle at 85% 10%, rgba(201, 246, 107, .055), transparent 24rem),
30
+ var(--bg); }
31
+ button, input, select { font: inherit; }
32
+ button, a { -webkit-tap-highlight-color: transparent; }
33
+ button { color: inherit; }
34
+ a { color: inherit; }
35
+ #app, .app { min-height: 100vh; }
36
+
37
+ .app-header {
38
+ position: sticky;
39
+ top: 0;
40
+ z-index: 20;
41
+ height: 72px;
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: space-between;
45
+ padding: 0 max(24px, calc((100vw - 1500px) / 2));
46
+ border-bottom: 1px solid rgba(71, 82, 94, .55);
47
+ background: rgba(8, 11, 15, .88);
48
+ backdrop-filter: blur(18px);
49
+ }
50
+ .brand {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 13px;
54
+ padding: 0;
55
+ border: 0;
56
+ background: transparent;
57
+ cursor: pointer;
58
+ text-align: left;
59
+ }
60
+ .brand > span:last-child { display: grid; gap: 2px; }
61
+ .brand strong { font-size: 14px; font-weight: 800; letter-spacing: -.01em; }
62
+ .brand small { color: var(--muted); font-size: 10px; }
63
+ .brand-cube { position: relative; width: 27px; height: 27px; display: block; transform: rotate(30deg); }
64
+ .brand-cube i { position: absolute; display: block; border: 1.5px solid var(--lime); background: rgba(201, 246, 107, .055); }
65
+ .brand-cube i:nth-child(1) { inset: 4px 9px 4px 1px; }
66
+ .brand-cube i:nth-child(2) { inset: 4px 1px 4px 17px; }
67
+ .brand-cube i:nth-child(3) { inset: 1px 5px 16px; }
68
+ .header-actions { display: flex; align-items: center; gap: 16px; }
69
+ .refresh-time { color: var(--muted); font-size: 10px; }
70
+ .live-indicator, .status-chip, .row-status {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ gap: 7px;
74
+ color: var(--muted-2);
75
+ font-size: 10px;
76
+ font-weight: 700;
77
+ text-transform: capitalize;
78
+ }
79
+ .live-indicator i, .status-chip i, .row-status i, .state-dot {
80
+ width: 7px;
81
+ height: 7px;
82
+ display: inline-block;
83
+ flex: none;
84
+ border-radius: 50%;
85
+ background: #6d7883;
86
+ }
87
+ .live-indicator i, .live i, .state-dot.live { background: var(--cyan); box-shadow: 0 0 0 4px rgba(109, 215, 232, .1); }
88
+ .icon-button {
89
+ width: 34px;
90
+ height: 34px;
91
+ display: grid;
92
+ place-items: center;
93
+ padding: 0;
94
+ border: 1px solid var(--line);
95
+ border-radius: 9px;
96
+ background: var(--panel);
97
+ cursor: pointer;
98
+ font-size: 18px;
99
+ }
100
+ .icon-button:hover { border-color: #46535f; background: var(--panel-2); }
101
+
102
+ .page { width: min(1500px, calc(100% - 48px)); margin: 0 auto; padding: 46px 0 80px; }
103
+ .error-banner { margin-bottom: 20px; padding: 13px 15px; border: 1px solid rgba(255, 118, 118, .35); border-radius: 10px; background: rgba(255, 118, 118, .08); color: #ffaaaa; font-size: 12px; }
104
+
105
+ .page-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 40px; margin: 18px 0 40px; }
106
+ .kicker { margin: 0 0 9px; color: var(--lime); font: 500 9px 'DM Mono', monospace; letter-spacing: .16em; }
107
+ .page-heading h1, .detail-heading h1 {
108
+ margin: 0;
109
+ font-size: clamp(38px, 5vw, 68px);
110
+ font-weight: 600;
111
+ letter-spacing: -.055em;
112
+ line-height: 1.01;
113
+ }
114
+ .page-heading h1 span { color: #7e8b96; }
115
+ .heading-copy { max-width: 650px; margin: 20px 0 0; color: var(--muted-2); font-size: 14px; line-height: 1.65; }
116
+ .corpus-badge {
117
+ min-width: 210px;
118
+ padding: 21px 23px;
119
+ border: 1px solid rgba(201, 246, 107, .28);
120
+ border-radius: 14px;
121
+ background: linear-gradient(145deg, rgba(201, 246, 107, .09), rgba(201, 246, 107, .015));
122
+ }
123
+ .corpus-badge span, .corpus-badge small { display: block; color: var(--muted); font-size: 10px; }
124
+ .corpus-badge strong { display: block; margin: 7px 0 3px; font-size: 27px; letter-spacing: -.04em; }
125
+
126
+ .overview-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); margin-bottom: 24px; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: var(--panel); }
127
+ .overview-grid article, .detail-metrics article, .task-score-grid article {
128
+ min-width: 0;
129
+ padding: 19px 20px;
130
+ border-right: 1px solid var(--line);
131
+ }
132
+ .overview-grid article:last-child, .detail-metrics article:last-child, .task-score-grid article:last-child { border-right: 0; }
133
+ .overview-grid span, .detail-metrics span, .task-score-grid span, .detail-score span, .detail-score-pair span {
134
+ display: block;
135
+ margin-bottom: 8px;
136
+ color: var(--muted);
137
+ font-size: 9px;
138
+ font-weight: 700;
139
+ letter-spacing: .07em;
140
+ text-transform: uppercase;
141
+ }
142
+ .overview-grid strong, .detail-metrics strong, .task-score-grid strong { display: block; font-size: 25px; font-weight: 650; letter-spacing: -.035em; }
143
+ .overview-grid small, .detail-metrics small, .task-score-grid small, .detail-score small, .detail-score-pair small { display: block; margin-top: 6px; overflow: hidden; color: #778491; font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
144
+ .score-overview { background: linear-gradient(145deg, rgba(201, 246, 107, .07), transparent); }
145
+ .score-overview strong { color: var(--lime); }
146
+ .score-overview strong small { display: inline; margin-left: 3px; color: #788462; font-size: 11px; }
147
+ .score-overview.deterministic-score { background: linear-gradient(145deg, rgba(109, 215, 232, .07), transparent); }
148
+ .score-overview.deterministic-score strong { color: var(--cyan); }
149
+
150
+ .registry-panel, .task-panel, .inspector-panel {
151
+ border: 1px solid var(--line);
152
+ border-radius: 14px;
153
+ background: rgba(16, 21, 27, .9);
154
+ box-shadow: 0 22px 60px rgba(0, 0, 0, .18);
155
+ overflow: hidden;
156
+ }
157
+ .registry-heading { min-height: 76px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 16px 20px; border-bottom: 1px solid var(--line); }
158
+ .registry-heading .kicker { margin-bottom: 5px; }
159
+ .registry-heading h2 { margin: 0; font-size: 18px; letter-spacing: -.02em; }
160
+ .registry-heading > span { color: var(--muted); font-size: 10px; }
161
+
162
+ .filter-bar { display: grid; grid-template-columns: minmax(260px, 1fr) 190px 230px; gap: 10px; padding: 13px; border-bottom: 1px solid var(--line); background: var(--panel-3); }
163
+ .filter-bar label { min-width: 0; display: grid; gap: 5px; }
164
+ .filter-bar label > span { padding-left: 2px; color: #6f7b86; font-size: 8px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
165
+ .filter-bar input, .filter-bar select, .turn-controls select {
166
+ width: 100%;
167
+ height: 38px;
168
+ padding: 0 12px;
169
+ border: 1px solid var(--line);
170
+ border-radius: 8px;
171
+ outline: none;
172
+ background: #11171d;
173
+ color: var(--text);
174
+ font-size: 11px;
175
+ }
176
+ .filter-bar input:focus, .filter-bar select:focus, .turn-controls select:focus { border-color: var(--cyan); box-shadow: 0 0 0 3px rgba(109, 215, 232, .08); }
177
+
178
+ .benchmark-list { display: grid; }
179
+ .benchmark-row {
180
+ width: 100%;
181
+ min-height: 87px;
182
+ display: grid;
183
+ grid-template-columns: 92px minmax(280px, 2.2fr) minmax(150px, .9fr) 92px 112px 96px 92px 24px;
184
+ align-items: center;
185
+ gap: 18px;
186
+ padding: 14px 20px;
187
+ border: 0;
188
+ border-bottom: 1px solid var(--line-soft);
189
+ background: transparent;
190
+ cursor: pointer;
191
+ text-align: left;
192
+ transition: background .14s, transform .14s;
193
+ }
194
+ .benchmark-row:last-child { border-bottom: 0; }
195
+ .benchmark-row:hover { background: rgba(109, 215, 232, .035); }
196
+ .benchmark-row:focus-visible { outline: 2px solid var(--cyan); outline-offset: -2px; }
197
+ .row-status.positive i, .status-chip.positive i, .state-dot.positive { background: var(--green); }
198
+ .row-status.negative i, .status-chip.negative i, .state-dot.negative { background: var(--red); }
199
+ .row-status.warning i, .status-chip.warning i, .state-dot.warning { background: var(--amber); }
200
+ .row-identity { min-width: 0; }
201
+ .row-identity strong { display: block; overflow: hidden; font-size: 12px; text-overflow: ellipsis; white-space: nowrap; }
202
+ .row-identity small, .row-result small, .row-coverage small, .row-cost small { display: block; margin-top: 6px; overflow: hidden; color: var(--muted); font-size: 9px; text-overflow: ellipsis; white-space: nowrap; }
203
+ .row-progress > span { display: block; margin-bottom: 8px; color: var(--muted-2); font-size: 9px; }
204
+ .row-progress > span strong { color: var(--text); }
205
+ .row-progress > i { height: 4px; display: block; overflow: hidden; border-radius: 9px; background: #252d35; }
206
+ .row-progress > i b { height: 100%; display: block; border-radius: inherit; background: linear-gradient(90deg, var(--cyan), var(--lime)); }
207
+ .row-result strong, .row-coverage strong, .row-cost strong { display: block; font-size: 14px; font-weight: 650; }
208
+ .row-result strong { color: var(--lime); }
209
+ .row-result strong { font: 650 17px 'DM Mono', monospace; }
210
+ .row-deterministic-score strong { color: var(--cyan); }
211
+ .row-arrow { color: #596671; font-size: 18px; transition: transform .14s, color .14s; }
212
+ .benchmark-row:hover .row-arrow, .task-row:hover .row-arrow { color: var(--cyan); transform: translateX(3px); }
213
+
214
+ .empty-panel { min-height: 150px; display: grid; place-content: center; gap: 7px; padding: 30px; color: var(--muted); font-size: 12px; text-align: center; }
215
+ .empty-panel strong { color: var(--text); font-size: 14px; }
216
+ .back-button { margin-bottom: 22px; padding: 7px 0; border: 0; background: transparent; color: var(--muted-2); cursor: pointer; font-size: 11px; }
217
+ .back-button:hover { color: var(--lime); }
218
+
219
+ .detail-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 40px; margin-bottom: 30px; }
220
+ .detail-heading h1 { max-width: 950px; margin-top: 14px; font-size: clamp(32px, 4vw, 55px); }
221
+ .detail-heading p { margin: 14px 0 0; color: var(--muted); font-size: 12px; }
222
+ .detail-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 14px; color: var(--muted); font: 9px 'DM Mono', monospace; }
223
+ .status-chip { padding: 6px 9px; border: 1px solid var(--line); border-radius: 20px; background: var(--panel); }
224
+ .detail-score { min-width: 190px; padding: 18px 20px; border-left: 1px solid var(--line); }
225
+ .detail-score strong { display: block; color: var(--lime); font-size: 45px; font-weight: 600; letter-spacing: -.06em; line-height: 1; }
226
+ .detail-score-pair { min-width: 370px; display: grid; grid-template-columns: 1fr 1fr; overflow: hidden; border: 1px solid var(--line); border-radius: 12px; background: var(--panel); }
227
+ .detail-score-pair > div { min-width: 0; padding: 17px 18px; }
228
+ .detail-score-pair > div + div { border-left: 1px solid var(--line); }
229
+ .detail-score-pair strong { display: block; color: var(--lime); font-size: 35px; font-weight: 600; letter-spacing: -.06em; line-height: 1; }
230
+ .detail-score-pair .deterministic-score strong { color: var(--cyan); }
231
+
232
+ .detail-metrics { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); margin-bottom: 12px; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: var(--panel); }
233
+ .detail-metrics article { padding: 17px 16px; }
234
+ .detail-metrics strong { font-size: 20px; }
235
+ .coverage-strip { min-height: 60px; display: flex; align-items: center; gap: 8px; margin-bottom: 24px; padding: 10px 13px; border: 1px solid var(--line); border-radius: 12px; background: var(--panel-3); }
236
+ .coverage-strip > div { min-width: 145px; padding: 0 8px; }
237
+ .coverage-strip > div span { display: block; color: var(--muted); font-size: 8px; text-transform: uppercase; }
238
+ .coverage-strip > div strong { display: block; margin-top: 3px; font-size: 12px; }
239
+ .coverage-chip { padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; color: var(--muted-2); font-size: 9px; text-transform: capitalize; }
240
+ .coverage-chip.positive { border-color: rgba(114, 223, 165, .26); color: var(--green); }
241
+ .coverage-chip.warning { border-color: rgba(242, 188, 102, .28); color: var(--amber); }
242
+ .coverage-chip.negative { border-color: rgba(255, 118, 118, .26); color: var(--red); }
243
+ .coverage-chip.live { border-color: rgba(109, 215, 232, .26); color: var(--cyan); }
244
+ .corpus-split { margin-left: auto; padding: 0 8px; color: var(--muted); font: 9px 'DM Mono', monospace; }
245
+
246
+ .task-panel { margin-bottom: 24px; }
247
+ .task-filters { grid-template-columns: minmax(230px, 1fr) 150px 170px 190px; }
248
+ .task-table-head, .task-row {
249
+ display: grid;
250
+ grid-template-columns: minmax(160px, 1.3fr) 100px 140px 76px 96px 105px 62px 72px 72px 20px;
251
+ align-items: center;
252
+ gap: 14px;
253
+ }
254
+ .task-table-head { min-height: 38px; padding: 0 17px; border-bottom: 1px solid var(--line); background: #0d1217; color: #697681; font-size: 8px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
255
+ .task-row {
256
+ width: 100%;
257
+ min-height: 67px;
258
+ padding: 10px 17px;
259
+ border: 0;
260
+ border-bottom: 1px solid var(--line-soft);
261
+ background: transparent;
262
+ cursor: pointer;
263
+ color: var(--muted-2);
264
+ font-size: 10px;
265
+ text-align: left;
266
+ }
267
+ .task-row:last-child { border-bottom: 0; }
268
+ .task-row:hover, .task-row.selected { background: rgba(109, 215, 232, .04); }
269
+ .task-row.selected { box-shadow: inset 3px 0 var(--cyan); }
270
+ .task-row.disabled { cursor: default; opacity: .52; }
271
+ .task-row > span { min-width: 0; }
272
+ .task-identity strong, .task-row > span > strong { display: block; color: var(--text); font-size: 10px; }
273
+ .task-identity small, .task-row > span > small { display: block; margin-top: 4px; color: var(--muted); font-size: 8px; text-transform: capitalize; }
274
+ .task-identity small b { margin-right: 4px; color: var(--cyan); }
275
+ .state-dot { margin-right: 7px; vertical-align: 1px; }
276
+ .outcome { color: var(--muted-2) !important; text-transform: capitalize; }
277
+ .outcome.positive, .task-score-grid strong.positive { color: var(--green) !important; }
278
+ .outcome.warning, .task-score-grid strong.warning { color: var(--amber) !important; }
279
+ .outcome.negative, .task-score-grid strong.negative { color: var(--red) !important; }
280
+ .outcome.live, .task-score-grid strong.live { color: var(--cyan) !important; }
281
+ .numeric-score { color: var(--lime); font: 650 17px 'DM Mono', monospace; letter-spacing: -.03em; }
282
+ .numeric-score.deterministic-score { color: var(--cyan); }
283
+ .component-mini { display: flex; gap: 6px; }
284
+ .component-mini small { margin: 0 !important; font-family: 'DM Mono', monospace; }
285
+
286
+ .inspector-panel { scroll-margin-top: 88px; }
287
+ .inspector-heading { min-height: 90px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 17px 20px; border-bottom: 1px solid var(--line); }
288
+ .inspector-heading .kicker { margin-bottom: 5px; }
289
+ .inspector-heading h2 { margin: 0; font-size: 22px; }
290
+ .inspector-heading p { margin: 6px 0 0; color: var(--muted); font-size: 10px; text-transform: capitalize; }
291
+ .inspector-actions { display: flex; align-items: center; gap: 8px; }
292
+ .button { min-height: 34px; display: inline-flex; align-items: center; justify-content: center; padding: 0 13px; border: 1px solid var(--line); border-radius: 8px; text-decoration: none; font-size: 10px; font-weight: 700; }
293
+ .button:hover { border-color: var(--cyan); }
294
+ .task-score-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); border-bottom: 1px solid var(--line); background: var(--panel-3); }
295
+ .task-score-grid strong { font-size: 18px; text-transform: capitalize; }
296
+ .judge-results { display: grid; grid-template-columns: 1fr 1fr; border-bottom: 1px solid var(--line); background: var(--panel-3); }
297
+ .judge-result { min-width: 0; }
298
+ .judge-result + .judge-result { border-left: 1px solid var(--line); }
299
+ .judge-result > header { min-height: 78px; display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 15px 19px; border-bottom: 1px solid var(--line-soft); }
300
+ .judge-result > header span { display: block; margin-bottom: 6px; color: var(--muted); font-size: 8px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
301
+ .judge-result > header > div:first-child strong { font-size: 13px; }
302
+ .judge-total { display: flex; align-items: baseline; gap: 4px; }
303
+ .judge-total strong { color: var(--lime); font-size: 32px; letter-spacing: -.05em; }
304
+ .deterministic-result .judge-total strong { color: var(--cyan); }
305
+ .judge-total small { color: var(--muted); font-size: 9px; }
306
+ .judge-result-body { padding: 14px 19px 16px; }
307
+ .judge-result-body > p { margin: 0 0 12px; color: var(--muted-2); font-size: 10px; font-weight: 700; text-transform: capitalize; }
308
+ .judge-result-body > p.positive { color: var(--green); }
309
+ .judge-result-body > p.warning { color: var(--amber); }
310
+ .judge-result-body > p.negative { color: var(--red); }
311
+ .judge-result-body > small { display: block; margin-top: 12px; color: var(--muted); font: 9px 'DM Mono', monospace; }
312
+ .judge-components { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); overflow: hidden; border: 1px solid var(--line-soft); border-radius: 8px; background: #0b1015; }
313
+ .judge-components > div { min-width: 0; padding: 10px; border-right: 1px solid var(--line-soft); }
314
+ .judge-components > div:last-child { border-right: 0; }
315
+ .judge-components span { display: block; overflow: hidden; color: var(--muted); font-size: 7px; font-weight: 700; letter-spacing: .05em; text-overflow: ellipsis; text-transform: uppercase; white-space: nowrap; }
316
+ .judge-components strong { display: block; margin-top: 5px; font: 650 14px 'DM Mono', monospace; }
317
+ .judge-unavailable { min-height: 92px; display: grid; place-items: center; padding: 20px; color: var(--muted); font-size: 10px; }
318
+
319
+ .artifact-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; border-bottom: 1px solid var(--line); background: var(--line); }
320
+ .artifact-grid figure, .turn-frame-grid figure { min-width: 0; margin: 0; background: #090d11; }
321
+ .artifact-grid figcaption { height: 43px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; border-bottom: 1px solid var(--line-soft); font-size: 9px; }
322
+ .artifact-grid figcaption small { color: var(--muted); }
323
+ .artifact-grid figure > button, .turn-frame-grid figure > button { position: relative; width: 100%; height: 310px; display: block; overflow: hidden; padding: 0; border: 0; background: #050709; cursor: zoom-in; }
324
+ .artifact-grid img, .turn-frame-grid img { width: 100%; height: 100%; display: block; object-fit: contain; transition: opacity .18s; }
325
+ .artifact-grid img[data-load-state="retrying"], .turn-frame-grid img[data-load-state="retrying"], .lightbox img[data-load-state="retrying"] { opacity: .3; }
326
+ .artifact-grid img[data-load-state="failed"], .turn-frame-grid img[data-load-state="failed"], .lightbox img[data-load-state="failed"] { opacity: .08; }
327
+ .artifact-grid button:has(img[data-load-state="retrying"])::after, .turn-frame-grid button:has(img[data-load-state="retrying"])::after,
328
+ .artifact-grid button:has(img[data-load-state="failed"])::after, .turn-frame-grid button:has(img[data-load-state="failed"])::after {
329
+ position: absolute;
330
+ inset: 0;
331
+ display: grid;
332
+ place-items: center;
333
+ color: var(--muted-2);
334
+ font: 500 10px 'DM Mono', monospace;
335
+ pointer-events: none;
336
+ }
337
+ .artifact-grid button:has(img[data-load-state="retrying"])::after, .turn-frame-grid button:has(img[data-load-state="retrying"])::after { content: 'Retrying image…'; }
338
+ .artifact-grid button:has(img[data-load-state="failed"])::after, .turn-frame-grid button:has(img[data-load-state="failed"])::after { content: 'Image unavailable · refresh to retry'; color: var(--red); }
339
+ .artifact-missing { height: 310px; display: grid; place-items: center; color: #56616c; font-size: 10px; }
340
+
341
+ .trace-section { background: #0d1217; }
342
+ .trace-section > header { min-height: 72px; display: flex; align-items: center; justify-content: space-between; padding: 13px 17px; border-bottom: 1px solid var(--line); }
343
+ .trace-section > header .kicker { margin-bottom: 4px; }
344
+ .trace-section h3 { margin: 0; font-size: 15px; }
345
+ .turn-controls { display: flex; align-items: center; gap: 6px; }
346
+ .turn-controls button { width: 35px; height: 35px; border: 1px solid var(--line); border-radius: 8px; background: var(--panel); cursor: pointer; }
347
+ .turn-controls button:disabled { opacity: .3; cursor: default; }
348
+ .turn-controls select { width: 180px; height: 35px; }
349
+ .turn-frame-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; border-bottom: 1px solid var(--line); background: var(--line); }
350
+ .turn-frame-grid figcaption { height: 37px; display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 0 12px; color: var(--muted-2); font-size: 9px; }
351
+ .turn-frame-grid figcaption small { overflow: hidden; color: var(--muted); font-family: var(--mono); text-overflow: ellipsis; white-space: nowrap; }
352
+ .turn-frame-grid figure > button, .turn-frame-grid .artifact-missing { height: 360px; }
353
+
354
+ .action-list { display: grid; padding: 12px; }
355
+ .action-list article { display: grid; grid-template-columns: 24px minmax(180px, 1fr) minmax(160px, .7fr); align-items: center; gap: 12px; padding: 11px 10px; border-bottom: 1px solid var(--line-soft); }
356
+ .action-list article:last-child { border-bottom: 0; }
357
+ .action-list article > span { width: 22px; height: 22px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: 6px; color: var(--muted); font: 9px 'DM Mono', monospace; }
358
+ .action-list strong { display: block; font-size: 10px; text-transform: capitalize; }
359
+ .action-list p { margin: 4px 0 0; color: var(--muted-2); font-size: 10px; }
360
+ .action-list code { overflow: hidden; color: #74818c; font: 9px 'DM Mono', monospace; text-overflow: ellipsis; white-space: nowrap; }
361
+ .raw-details { border-top: 1px solid var(--line); }
362
+ .raw-details summary { padding: 13px 17px; color: var(--muted-2); cursor: pointer; font-size: 10px; }
363
+ .raw-details > div { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; padding: 0 13px 13px; }
364
+ pre { max-height: 380px; margin: 0; padding: 12px; overflow: auto; border: 1px solid var(--line); border-radius: 8px; background: #070a0d; color: #aebac4; font: 9px/1.55 'DM Mono', monospace; white-space: pre-wrap; word-break: break-word; }
365
+
366
+ .lightbox { position: fixed; inset: 0; z-index: 100; display: grid; place-items: center; padding: 24px; background: rgba(2, 4, 6, .94); backdrop-filter: blur(15px); }
367
+ .lightbox > div { width: min(94vw, 1700px); height: min(92vh, 1100px); display: grid; grid-template-rows: 54px minmax(0, 1fr) 34px; border: 1px solid #394550; border-radius: 12px; overflow: hidden; background: #080c0f; box-shadow: 0 30px 100px rgba(0, 0, 0, .7); }
368
+ .lightbox header { display: flex; align-items: center; justify-content: space-between; padding: 0 15px; border-bottom: 1px solid var(--line); }
369
+ .lightbox header strong { font-size: 11px; }
370
+ .lightbox header button { border: 0; background: transparent; cursor: pointer; font-size: 24px; }
371
+ .lightbox img { width: 100%; height: 100%; min-height: 0; display: block; object-fit: contain; background: #030506; }
372
+ .lightbox small { display: grid; place-items: center; border-top: 1px solid var(--line); color: var(--muted); font-size: 9px; }
373
+ .lightbox-enter-active, .lightbox-leave-active { transition: opacity .16s; }
374
+ .lightbox-enter-from, .lightbox-leave-to { opacity: 0; }
375
+
376
+ @media (max-width: 1200px) {
377
+ .overview-grid { grid-template-columns: repeat(3, 1fr); }
378
+ .overview-grid article:nth-child(3) { border-right: 0; }
379
+ .overview-grid article:nth-child(n+4) { border-top: 1px solid var(--line); }
380
+ .benchmark-row { grid-template-columns: 85px minmax(250px, 2fr) minmax(135px, 1fr) 82px 98px 80px 20px; }
381
+ .row-coverage { display: none; }
382
+ .detail-metrics { grid-template-columns: repeat(3, 1fr); }
383
+ .detail-metrics article:nth-child(3) { border-right: 0; }
384
+ .detail-metrics article:nth-child(n+4) { border-top: 1px solid var(--line); }
385
+ .task-table-head, .task-row { grid-template-columns: minmax(150px, 1.3fr) 90px 130px 68px 80px 85px 60px 70px 18px; }
386
+ .task-table-head > span:nth-child(8), .task-row > span:nth-child(8) { display: none; }
387
+ .task-score-grid { grid-template-columns: repeat(3, 1fr); }
388
+ .task-score-grid article:nth-child(3) { border-right: 0; }
389
+ .task-score-grid article:nth-child(n+4) { border-top: 1px solid var(--line); }
390
+ }
391
+
392
+ @media (max-width: 860px) {
393
+ .app-header { padding: 0 16px; }
394
+ .refresh-time { display: none; }
395
+ .page { width: min(100% - 28px, 720px); padding-top: 28px; }
396
+ .page-heading, .detail-heading { align-items: flex-start; flex-direction: column; }
397
+ .corpus-badge { width: 100%; }
398
+ .overview-grid { grid-template-columns: 1fr 1fr; }
399
+ .overview-grid article { border-top: 1px solid var(--line); }
400
+ .overview-grid article:nth-child(odd) { border-right: 1px solid var(--line); }
401
+ .overview-grid article:nth-child(even) { border-right: 0; }
402
+ .overview-grid article:first-child, .overview-grid article:nth-child(2) { border-top: 0; }
403
+ .filter-bar, .task-filters { grid-template-columns: 1fr 1fr; }
404
+ .search-field { grid-column: 1 / -1; }
405
+ .benchmark-row { grid-template-columns: 82px minmax(0, 1fr) 62px 72px 20px; gap: 12px; }
406
+ .row-progress, .row-coverage, .row-cost { display: none; }
407
+ .detail-score { width: 100%; border-top: 1px solid var(--line); border-left: 0; }
408
+ .detail-score-pair { width: 100%; min-width: 0; }
409
+ .detail-metrics { grid-template-columns: 1fr 1fr; }
410
+ .detail-metrics article { border-top: 1px solid var(--line); }
411
+ .detail-metrics article:nth-child(odd) { border-right: 1px solid var(--line); }
412
+ .detail-metrics article:nth-child(even) { border-right: 0; }
413
+ .detail-metrics article:first-child, .detail-metrics article:nth-child(2) { border-top: 0; }
414
+ .coverage-strip { flex-wrap: wrap; }
415
+ .corpus-split { width: 100%; margin: 5px 0 0; }
416
+ .task-table-head { display: none; }
417
+ .task-row { grid-template-columns: minmax(130px, 1fr) 100px 58px 68px 18px; gap: 10px; padding: 12px 14px; }
418
+ .task-row > span:nth-child(2), .task-row > span:nth-child(6), .task-row > span:nth-child(7), .task-row > span:nth-child(8), .task-row > span:nth-child(9) { display: none; }
419
+ .artifact-grid, .turn-frame-grid { grid-template-columns: 1fr; }
420
+ .judge-results { grid-template-columns: 1fr; }
421
+ .judge-result + .judge-result { border-top: 1px solid var(--line); border-left: 0; }
422
+ .artifact-grid figure > button, .artifact-missing { height: 280px; }
423
+ .turn-frame-grid figure > button, .turn-frame-grid .artifact-missing { height: 280px; }
424
+ .raw-details > div { grid-template-columns: 1fr; }
425
+ }
426
+
427
+ @media (max-width: 540px) {
428
+ .brand small, .live-indicator { display: none; }
429
+ .page-heading h1 { font-size: 38px; }
430
+ .overview-grid { grid-template-columns: 1fr; }
431
+ .overview-grid article { border-right: 0 !important; border-top: 1px solid var(--line) !important; }
432
+ .overview-grid article:first-child { border-top: 0 !important; }
433
+ .filter-bar, .task-filters { grid-template-columns: 1fr; }
434
+ .search-field { grid-column: auto; }
435
+ .benchmark-row { grid-template-columns: 68px minmax(0, 1fr) 52px 60px 18px; gap: 8px; }
436
+ .row-result strong { font-size: 14px; }
437
+ .row-result small { font-size: 7px; }
438
+ .detail-metrics, .task-score-grid { grid-template-columns: 1fr 1fr; }
439
+ .detail-score-pair { grid-template-columns: 1fr; }
440
+ .detail-score-pair > div + div { border-top: 1px solid var(--line); border-left: 0; }
441
+ .judge-components { grid-template-columns: 1fr 1fr; }
442
+ .judge-components > div:nth-child(2) { border-right: 0; }
443
+ .judge-components > div:nth-child(n+3) { border-top: 1px solid var(--line-soft); }
444
+ .detail-metrics article, .task-score-grid article { border-top: 1px solid var(--line); }
445
+ .detail-metrics article:nth-child(odd), .task-score-grid article:nth-child(odd) { border-right: 1px solid var(--line); }
446
+ .detail-metrics article:nth-child(even), .task-score-grid article:nth-child(even) { border-right: 0; }
447
+ .detail-metrics article:first-child, .detail-metrics article:nth-child(2), .task-score-grid article:first-child, .task-score-grid article:nth-child(2) { border-top: 0; }
448
+ .inspector-heading { align-items: flex-start; }
449
+ .action-list article { grid-template-columns: 24px 1fr; }
450
+ .action-list code { grid-column: 2; }
451
+ }
apps/dashboard/src/types.ts ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface EvaluationScoreSummary {
2
+ source: string | null
3
+ score: number | null
4
+ valid: boolean | null
5
+ failureReason: string | null
6
+ method: string | null
7
+ verdict: string | null
8
+ targetAcquired: boolean | null
9
+ confidence: number | null
10
+ needsHumanReview: boolean
11
+ geometry: number | null
12
+ dimensions: number | null
13
+ annotations: number | null
14
+ presentation: number | null
15
+ }
16
+
17
+ export interface RunSummary {
18
+ id: string
19
+ taskId: string
20
+ attemptId: string | null
21
+ modelId: string
22
+ provider: string
23
+ reasoningEffort: string | null
24
+ status: string
25
+ stopReason: string | null
26
+ failure: string | null
27
+ acceptedActions: number
28
+ invalidActions: number
29
+ screenshots: number
30
+ elapsedSeconds: number | null
31
+ artifactBytes: number
32
+ score: number | null
33
+ valid: boolean | null
34
+ scoreFailure: string | null
35
+ scoreMethod: string | null
36
+ scoreSource: string | null
37
+ verdict: string | null
38
+ targetAcquired: boolean | null
39
+ judgeConfidence: number | null
40
+ needsHumanReview: boolean
41
+ visionScore: EvaluationScoreSummary | null
42
+ deterministicScore: EvaluationScoreSummary | null
43
+ evaluationState: string
44
+ evaluationStage: string | null
45
+ evaluationError: string | null
46
+ updatedAt: string
47
+ }
48
+
49
+ export interface FrameArtifact {
50
+ name: string
51
+ sha256: string | null
52
+ url: string
53
+ }
54
+
55
+ export interface ModelTurn {
56
+ turn: number
57
+ tool_use_id?: string
58
+ action: Record<string, unknown>
59
+ stop_reason: string
60
+ usage: Record<string, number>
61
+ latency_ms: number
62
+ text: string[]
63
+ discarded_tool_uses: number
64
+ model_input?: Record<string, unknown> | null
65
+ }
66
+
67
+ export interface InputReceipt {
68
+ action_index: number
69
+ request_id?: string | null
70
+ idempotent_replay?: boolean
71
+ receipt: Record<string, unknown> | null
72
+ action?: Record<string, unknown> | null
73
+ tool_use_id?: string | null
74
+ batch_index?: number
75
+ batch_size?: number
76
+ batch_start_action_index?: number
77
+ }
78
+
79
+ export interface TraceDetail {
80
+ summary: RunSummary
81
+ state: Record<string, unknown> | null
82
+ result: Record<string, unknown> | null
83
+ score: Record<string, unknown> | null
84
+ scores: {
85
+ vision: Record<string, unknown> | null
86
+ deterministic: Record<string, unknown> | null
87
+ }
88
+ turns: ModelTurn[]
89
+ receipts: InputReceipt[]
90
+ traceIndexed: boolean
91
+ traceNotice: string | null
92
+ frames: FrameArtifact[]
93
+ referenceUrl: string
94
+ artifacts: {
95
+ dwg: string | null
96
+ candidateRender: string | null
97
+ diffRender: string | null
98
+ }
99
+ }
100
+
101
+ export interface BenchmarkUsage {
102
+ inputTokens: number
103
+ cachedInputTokens: number
104
+ outputTokens: number
105
+ reasoningOutputTokens: number
106
+ totalTokens: number
107
+ modelTurns: number
108
+ apiCost: number | null
109
+ }
110
+
111
+ export interface BenchmarkLevelSummary {
112
+ total: number
113
+ completed: number
114
+ scored: number
115
+ meanScore: number | null
116
+ cost: number
117
+ }
118
+
119
+ export interface BenchmarkDrawingTypeSummary {
120
+ total: number
121
+ scored: number
122
+ meanScore: number | null
123
+ }
124
+
125
+ export interface BenchmarkAggregate {
126
+ totalTasks: number
127
+ queuedTasks: number
128
+ activeTasks: number
129
+ settledTasks: number
130
+ completedTasks: number
131
+ recoveredTasks: number
132
+ budgetExhaustedTasks: number
133
+ failedTasks: number
134
+ infrastructureFailedTasks: number
135
+ artifactMissingTasks: number
136
+ artifactTasks: number
137
+ scoredTasks: number
138
+ meanScore: number | null
139
+ visionScoredTasks: number
140
+ visionMeanScore: number | null
141
+ deterministicScoredTasks: number
142
+ deterministicMeanScore: number | null
143
+ acceptedActions: number
144
+ invalidActions: number
145
+ aggregateElapsedSeconds: number
146
+ usage: BenchmarkUsage
147
+ levels: Record<string, BenchmarkLevelSummary>
148
+ drawingTypes: Record<string, BenchmarkDrawingTypeSummary>
149
+ evaluationStates: Record<string, number>
150
+ executionStates: Record<string, number>
151
+ verdicts: Record<string, number>
152
+ }
153
+
154
+ export interface BenchmarkSummary {
155
+ id: string
156
+ batchId: string | null
157
+ name: string
158
+ status: string
159
+ modelId: string
160
+ provider: string
161
+ models: string[]
162
+ providers: string[]
163
+ startedAt: string | null
164
+ updatedAt: string | null
165
+ maxConcurrency: number | null
166
+ aggregate: BenchmarkAggregate
167
+ }
168
+
169
+ export interface BenchmarkTask {
170
+ rolloutId: string
171
+ runId: string
172
+ taskId: string
173
+ level: string
174
+ drawingType: string
175
+ split: string
176
+ displayName: string
177
+ modelId: string
178
+ provider: string
179
+ status: string
180
+ stopReason: string | null
181
+ failure: string | null
182
+ acceptedActions: number
183
+ invalidActions: number
184
+ elapsedSeconds: number | null
185
+ setupElapsedSeconds: number | null
186
+ rolloutElapsedSeconds: number | null
187
+ artifactBytes: number
188
+ score: number | null
189
+ valid: boolean | null
190
+ scoreFailure: string | null
191
+ scoreMethod: string | null
192
+ scoreSource: string | null
193
+ verdict: string | null
194
+ targetAcquired: boolean | null
195
+ judgeConfidence: number | null
196
+ needsHumanReview: boolean
197
+ visionScore: EvaluationScoreSummary | null
198
+ deterministicScore: EvaluationScoreSummary | null
199
+ geometrySimilarity: number | null
200
+ dimensionSimilarity: number | null
201
+ annotationSimilarity: number | null
202
+ visualSimilarity: number | null
203
+ evaluationState: string
204
+ evaluationStage: string | null
205
+ evaluationError: string | null
206
+ startedAt: string | null
207
+ finishedAt: string | null
208
+ updatedAt: string | null
209
+ usage: BenchmarkUsage
210
+ }
211
+
212
+ export interface BenchmarkDetail extends BenchmarkSummary {
213
+ finishedAt: string | null
214
+ pricing: {
215
+ currency?: string
216
+ input_per_million: number
217
+ cached_input_per_million: number
218
+ output_per_million: number
219
+ source_url?: string | null
220
+ effective_date?: string | null
221
+ } | null
222
+ tasks: BenchmarkTask[]
223
+ }
apps/dashboard/tests/cloud-metadata.test.mjs ADDED
@@ -0,0 +1,117 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import assert from 'node:assert/strict'
2
+ import fs from 'node:fs'
3
+ import os from 'node:os'
4
+ import path from 'node:path'
5
+ import test from 'node:test'
6
+
7
+ import {
8
+ benchmarkMetadataPaths,
9
+ shouldPromoteMetadata,
10
+ traceMetadataPaths,
11
+ } from '../cloud-metadata.mjs'
12
+
13
+ function writeJson(filePath, value) {
14
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
15
+ fs.writeFileSync(filePath, JSON.stringify(value))
16
+ }
17
+
18
+ test('plans direct benchmark metadata reads without wildcard prefix scans', () => {
19
+ const paths = benchmarkMetadataPaths({
20
+ rollouts: {
21
+ 'task-001': {},
22
+ 'task-022': {},
23
+ },
24
+ })
25
+
26
+ assert.equal(paths[0], 'batch-state.json')
27
+ assert.equal(paths.length, 11)
28
+ assert(paths.includes('rollouts/task-001/run-state.json'))
29
+ assert(paths.includes('rollouts/task-022/evaluation/vision-judge/score.json'))
30
+ assert.equal(paths.some((value) => value.includes('*')), false)
31
+ })
32
+
33
+ test('plans the compact trace metadata set without frame downloads', () => {
34
+ const paths = traceMetadataPaths()
35
+
36
+ assert(paths.includes('model_turn_summaries.jsonl'))
37
+ assert(paths.includes('input_receipts.jsonl'))
38
+ assert.equal(paths.some((value) => value.startsWith('frames/')), false)
39
+ })
40
+
41
+ test('shared V3 result cannot overwrite a local V4 rescore at the same timestamp', () => {
42
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dashboard-version-'))
43
+ const source = path.join(root, 'source', 'result.json')
44
+ const destination = path.join(root, 'destination', 'result.json')
45
+ writeJson(source, {
46
+ updated_at: '2026-07-19T00:00:00Z',
47
+ evaluation: { evaluator_version: 'autocad-2019-r23.0.46-v3' },
48
+ })
49
+ writeJson(destination, {
50
+ updated_at: '2026-07-19T00:00:00Z',
51
+ evaluation: { evaluator_version: 'autocad-2019-r23.0.46-v4' },
52
+ })
53
+
54
+ assert.equal(shouldPromoteMetadata(source, destination), false)
55
+ })
56
+
57
+ test('nested vision artifact inherits the evaluator version from status', () => {
58
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dashboard-vision-'))
59
+ const source = path.join(root, 'source', 'evaluation', 'vision-judge', 'score.json')
60
+ const destination = path.join(
61
+ root,
62
+ 'destination',
63
+ 'evaluation',
64
+ 'vision-judge',
65
+ 'score.json',
66
+ )
67
+ writeJson(source, { overall_score: 0.2 })
68
+ writeJson(path.join(root, 'source', 'evaluation', 'status.json'), {
69
+ evaluator_version: 'autocad-2019-r23.0.46-v3',
70
+ })
71
+ writeJson(destination, { overall_score: 0.8 })
72
+ writeJson(path.join(root, 'destination', 'evaluation', 'status.json'), {
73
+ evaluator_version: 'autocad-2019-r23.0.46-v4',
74
+ })
75
+
76
+ assert.equal(shouldPromoteMetadata(source, destination), false)
77
+ })
78
+
79
+ test('a higher evaluator version is promoted even with an older timestamp', () => {
80
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dashboard-upgrade-'))
81
+ const source = path.join(root, 'source', 'evaluation', 'status.json')
82
+ const destination = path.join(root, 'destination', 'evaluation', 'status.json')
83
+ writeJson(source, {
84
+ updated_at: '2026-07-18T00:00:00Z',
85
+ evaluator_version: 'autocad-2019-r23.0.46-v4',
86
+ })
87
+ writeJson(destination, {
88
+ updated_at: '2026-07-19T00:00:00Z',
89
+ evaluator_version: 'autocad-2019-r23.0.46-v3',
90
+ })
91
+
92
+ assert.equal(shouldPromoteMetadata(source, destination), true)
93
+ })
94
+
95
+ test('an earlier snapshot from the same rescore job cannot replace the local result', () => {
96
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dashboard-rescore-'))
97
+ const source = path.join(root, 'source', 'result.json')
98
+ const destination = path.join(root, 'destination', 'result.json')
99
+ writeJson(source, {
100
+ evaluation: {
101
+ evaluator_version: 'autocad-2019-r23.0.46-v4',
102
+ rescore_job_id: 'rescore-v4-test',
103
+ status: 'completed',
104
+ vision_score: 0.4,
105
+ },
106
+ })
107
+ writeJson(destination, {
108
+ evaluation: {
109
+ evaluator_version: 'autocad-2019-r23.0.46-v4',
110
+ rescore_job_id: 'rescore-v4-test',
111
+ status: 'completed',
112
+ vision_score: 0.8,
113
+ },
114
+ })
115
+
116
+ assert.equal(shouldPromoteMetadata(source, destination), false)
117
+ })
apps/dashboard/tests/trace-api.test.mjs ADDED
@@ -0,0 +1,872 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import assert from 'node:assert/strict'
2
+ import { createHash } from 'node:crypto'
3
+ import fs from 'node:fs'
4
+ import os from 'node:os'
5
+ import path from 'node:path'
6
+ import { once } from 'node:events'
7
+ import { Writable } from 'node:stream'
8
+ import test from 'node:test'
9
+
10
+ import { _test, createTraceApi } from '../trace-api.mjs'
11
+
12
+ class TestResponse extends Writable {
13
+ constructor() {
14
+ super()
15
+ this.headers = {}
16
+ this.body = []
17
+ this.statusCode = 0
18
+ }
19
+
20
+ setHeader(name, value) {
21
+ this.headers[name] = value
22
+ }
23
+
24
+ _write(chunk, _encoding, callback) {
25
+ this.body.push(Buffer.from(chunk))
26
+ callback()
27
+ }
28
+ }
29
+
30
+ test('reads complete JSONL rows while a writer is appending a partial row', () => {
31
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-jsonl-'))
32
+ const file = path.join(root, 'events.json')
33
+ fs.writeFileSync(file, '{"actionIndex":1}\n{"actionIndex":2')
34
+ assert.deepEqual(_test.readJsonl(file), [{ actionIndex: 1 }])
35
+ })
36
+
37
+ test('rejects run and artifact path traversal', () => {
38
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-safe-'))
39
+ const run = path.join(root, 'run-001')
40
+ fs.mkdirSync(run)
41
+ assert.equal(_test.safeRunDirectory(root, '../outside'), null)
42
+ assert.equal(_test.safeRunDirectory(root, 'run-001'), run)
43
+ assert.equal(_test.safeRunDirectory(root, 'batch/rollouts/run-001'), path.join(root, 'batch', 'rollouts', 'run-001'))
44
+ assert.equal(_test.safeRunDirectory(root, 'batch//run-001'), null)
45
+ assert.equal(_test.safeArtifact(run, '../../secret'), null)
46
+ assert.equal(_test.safeArtifact(run, 'frames/frame_0000.png'), path.join(run, 'frames', 'frame_0000.png'))
47
+ })
48
+
49
+ test('recursively indexes batch rollout directories', () => {
50
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-batch-'))
51
+ const rollout = path.join(root, 'batch-001', 'rollouts', 'model-a')
52
+ fs.mkdirSync(rollout, { recursive: true })
53
+ fs.writeFileSync(path.join(rollout, 'run-state.json'), JSON.stringify({
54
+ task_id: 'task-001',
55
+ model_id: 'model-a',
56
+ provider: 'openai',
57
+ state: 'running',
58
+ updated_at: '2026-07-16T00:00:00Z',
59
+ }))
60
+
61
+ const runs = _test.listRuns(root)
62
+
63
+ assert.equal(runs.length, 1)
64
+ assert.equal(runs[0].id, 'batch-001/rollouts/model-a')
65
+ assert.equal(runs[0].status, 'running')
66
+ })
67
+
68
+ test('indexes an unsuccessful wall-time rollout as budget exhausted', () => {
69
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-run-'))
70
+ const run = path.join(root, 'gpt-run')
71
+ fs.mkdirSync(run)
72
+ fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
73
+ task_id: 'task-001',
74
+ model_id: 'gpt-5.6-sol',
75
+ provider: 'openai',
76
+ reasoning_effort: 'xhigh',
77
+ completed: false,
78
+ stop_reason: 'wall_timeout',
79
+ accepted_actions: 40,
80
+ invalid_actions: 0,
81
+ screenshots: 41,
82
+ artifact_bytes: 0,
83
+ }))
84
+ const summary = _test.runSummary(root, 'gpt-run')
85
+ assert.equal(summary.status, 'budget_exhausted')
86
+ assert.equal(summary.stopReason, 'wall_timeout')
87
+ assert.equal(summary.acceptedActions, 40)
88
+ assert.equal(summary.evaluationState, 'no_artifact')
89
+ })
90
+
91
+ test('prefers the versioned vision judgment over the legacy deterministic score', () => {
92
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-vision-score-'))
93
+ const run = path.join(root, 'vision-run')
94
+ fs.mkdirSync(path.join(run, 'evaluation', 'vision-judge'), { recursive: true })
95
+ fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
96
+ task_id: 'task-001',
97
+ model_id: 'gpt-5.6-sol',
98
+ provider: 'openai',
99
+ state: 'completed',
100
+ }))
101
+ fs.writeFileSync(path.join(run, 'evaluation', 'score.json'), JSON.stringify({
102
+ score: 0.52,
103
+ valid: true,
104
+ scoring_method: 'vector-semantic-v2',
105
+ }))
106
+ fs.writeFileSync(
107
+ path.join(run, 'evaluation', 'vision-judge', 'score.json'),
108
+ JSON.stringify({
109
+ prompt_version: 'autocad-vision-judge-v1',
110
+ overall_score: 0.93,
111
+ target_acquired: true,
112
+ confidence: 0.91,
113
+ needs_human_review: false,
114
+ component_scores: {
115
+ geometry: 0.98,
116
+ dimensions: 0.96,
117
+ annotations: 0.75,
118
+ presentation: 0.70,
119
+ },
120
+ }),
121
+ )
122
+
123
+ const summary = _test.runSummary(root, 'vision-run')
124
+ const detail = _test.traceDetail(root, 'vision-run')
125
+
126
+ assert.equal(summary.score, 0.93)
127
+ assert.equal(summary.scoreMethod, 'vision:autocad-vision-judge-v1')
128
+ assert.equal(summary.scoreSource, 'vision_judge')
129
+ assert.equal(summary.visionScore.score, 0.93)
130
+ assert.equal(summary.visionScore.geometry, 0.98)
131
+ assert.equal(summary.deterministicScore.score, 0.52)
132
+ assert.equal(summary.deterministicScore.method, 'vector-semantic-v2')
133
+ assert.equal(summary.targetAcquired, true)
134
+ assert.equal(summary.evaluationState, 'scored')
135
+ assert.equal(detail.score.geometry_similarity, 0.98)
136
+ assert.equal(detail.score.score_source, 'vision_judge')
137
+ assert.equal(detail.scores.vision.overall_score, 0.93)
138
+ assert.equal(detail.scores.deterministic.score, 0.52)
139
+ })
140
+
141
+ test('shows automatic evaluation progress immediately after model completion', () => {
142
+ const heartbeat = new Date().toISOString()
143
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-evaluating-'))
144
+ const run = path.join(root, 'evaluating-run')
145
+ fs.mkdirSync(path.join(run, 'evaluation'), { recursive: true })
146
+ fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
147
+ task_id: 'task-001',
148
+ model_id: 'gpt-test',
149
+ provider: 'openai',
150
+ completed: true,
151
+ artifact_bytes: 1234,
152
+ }))
153
+ fs.writeFileSync(path.join(run, 'evaluation', 'status.json'), JSON.stringify({
154
+ stage: 'vision_judging',
155
+ updated_at: heartbeat,
156
+ }))
157
+
158
+ const summary = _test.runSummary(root, 'evaluating-run')
159
+
160
+ assert.equal(summary.evaluationState, 'evaluating')
161
+ assert.equal(summary.evaluationStage, 'vision_judging')
162
+ assert.equal(summary.updatedAt, heartbeat)
163
+ })
164
+
165
+ test('reports independent vision and deterministic benchmark means', () => {
166
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-dual-score-'))
167
+ const benchmark = path.join(root, 'benchmarks', 'dual-score')
168
+ const run = path.join(benchmark, 'rollouts', 'task-001')
169
+ fs.mkdirSync(path.join(run, 'evaluation', 'vision-judge'), { recursive: true })
170
+ fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
171
+ state: 'completed',
172
+ rollouts: {
173
+ 'task-001': {
174
+ task_id: 'task-001', model_id: 'model', provider: 'openai', state: 'completed',
175
+ },
176
+ },
177
+ }))
178
+ fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
179
+ task_id: 'task-001', model_id: 'model', provider: 'openai',
180
+ completed: true, artifact_bytes: 100,
181
+ }))
182
+ fs.writeFileSync(path.join(run, 'evaluation', 'score.json'), JSON.stringify({
183
+ score: 0.4,
184
+ valid: true,
185
+ scoring_method: 'vector-semantic-v2',
186
+ geometry_similarity: 0.5,
187
+ }))
188
+ fs.writeFileSync(
189
+ path.join(run, 'evaluation', 'vision-judge', 'score.json'),
190
+ JSON.stringify({
191
+ overall_score: 0.8,
192
+ verdict: 'mostly_acquired',
193
+ component_scores: { geometry: 0.9, dimensions: 0.7, annotations: 0.6, presentation: 0.5 },
194
+ }),
195
+ )
196
+
197
+ const detail = _test.benchmarkDetail(root, 'benchmarks/dual-score')
198
+
199
+ assert.equal(detail.aggregate.meanScore, 0.8)
200
+ assert.equal(detail.aggregate.visionMeanScore, 0.8)
201
+ assert.equal(detail.aggregate.visionScoredTasks, 1)
202
+ assert.equal(detail.aggregate.deterministicMeanScore, 0.4)
203
+ assert.equal(detail.aggregate.deterministicScoredTasks, 1)
204
+ assert.equal(detail.tasks[0].visionScore.score, 0.8)
205
+ assert.equal(detail.tasks[0].deterministicScore.score, 0.4)
206
+ })
207
+
208
+ test('does not show a stale evaluator heartbeat as live forever', () => {
209
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-stale-evaluation-'))
210
+ const run = path.join(root, 'stale-evaluation')
211
+ fs.mkdirSync(path.join(run, 'evaluation'), { recursive: true })
212
+ fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
213
+ task_id: 'task-001',
214
+ completed: true,
215
+ stop_reason: 'done',
216
+ artifact_bytes: 100,
217
+ }))
218
+ fs.writeFileSync(path.join(run, 'evaluation', 'status.json'), JSON.stringify({
219
+ stage: 'vision_judging',
220
+ updated_at: '2020-01-01T00:00:00Z',
221
+ }))
222
+
223
+ assert.equal(_test.runSummary(root, 'stale-evaluation').evaluationState, 'evaluation_interrupted')
224
+ })
225
+
226
+ test('labels a batch process without a pilot trace as startup, not handoff', () => {
227
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-startup-'))
228
+ const benchmark = path.join(root, 'benchmark-starting')
229
+ fs.mkdirSync(benchmark, { recursive: true })
230
+ fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
231
+ batch_id: 'batch-starting',
232
+ state: 'running',
233
+ rollouts: {
234
+ 'task-002': {
235
+ task_id: 'task-002',
236
+ model_id: 'model-test',
237
+ provider: 'openrouter',
238
+ state: 'running',
239
+ },
240
+ },
241
+ }))
242
+
243
+ const detail = _test.benchmarkDetail(root, 'benchmark-starting')
244
+
245
+ assert.equal(detail.tasks[0].status, 'starting')
246
+ assert.equal(detail.tasks[0].evaluationState, 'running')
247
+ })
248
+
249
+ test('retains batch grouping receipts and intermediate audit frames', () => {
250
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-batch-turn-'))
251
+ const run = path.join(root, 'batched-run')
252
+ const frames = path.join(run, 'frames')
253
+ fs.mkdirSync(frames, { recursive: true })
254
+ fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
255
+ task_id: 'task-001',
256
+ model_id: 'gpt-test',
257
+ provider: 'openai',
258
+ state: 'running',
259
+ }))
260
+ fs.writeFileSync(path.join(run, 'model_turns.jsonl'), `${JSON.stringify({
261
+ turn: 1,
262
+ tool_use_id: 'call-batch',
263
+ action: {
264
+ actions: [
265
+ { action: 'type', text: 'LINE', intent: 'Type LINE.' },
266
+ { action: 'key', keys: ['ENTER'], intent: 'Submit LINE.' },
267
+ ],
268
+ },
269
+ })}\n`)
270
+ fs.writeFileSync(path.join(run, 'input_receipts.jsonl'), [
271
+ { action_index: 1, tool_use_id: 'call-batch', batch_index: 1, batch_size: 2 },
272
+ { action_index: 2, tool_use_id: 'call-batch', batch_index: 2, batch_size: 2 },
273
+ ].map(JSON.stringify).join('\n') + '\n')
274
+ for (const name of ['frame_0000.png', 'frame_0001.png', 'frame_0002.png']) {
275
+ fs.writeFileSync(path.join(frames, name), 'png')
276
+ }
277
+
278
+ const detail = _test.traceDetail(root, 'batched-run')
279
+
280
+ assert.equal(detail.turns[0].action.actions.length, 2)
281
+ assert.deepEqual(detail.receipts.map((receipt) => receipt.batch_index), [1, 2])
282
+ assert.deepEqual(detail.frames.map((frame) => frame.name), [
283
+ 'frame_0000.png',
284
+ 'frame_0001.png',
285
+ 'frame_0002.png',
286
+ ])
287
+ })
288
+
289
+ test('indexes cloud-backed frames referenced by compact resumed traces', () => {
290
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-frames-'))
291
+ const run = path.join(root, 'cloud-frame-run')
292
+ const frames = path.join(run, 'frames')
293
+ fs.mkdirSync(frames, { recursive: true })
294
+ fs.writeFileSync(path.join(frames, 'frame_0000.png'), 'png')
295
+ fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
296
+ task_id: 'task-001',
297
+ model_id: 'gpt-test',
298
+ provider: 'openai',
299
+ state: 'completed',
300
+ accepted_actions: 12,
301
+ }))
302
+ fs.writeFileSync(path.join(run, 'model_turn_summaries.jsonl'), `${JSON.stringify({
303
+ turn: 2,
304
+ model_input: {
305
+ artifacts: [{ artifact_path: 'frames/frame_0010.png', sequence: 10 }],
306
+ },
307
+ })}\n`)
308
+
309
+ const detail = _test.traceDetail(root, 'cloud-frame-run')
310
+
311
+ assert.deepEqual(detail.frames.map((frame) => frame.name), [
312
+ 'frame_0000.png',
313
+ 'frame_0010.png',
314
+ 'frame_0012.png',
315
+ ])
316
+ })
317
+
318
+ test('advertises and lazily hydrates cloud reference and trusted render images', async () => {
319
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-images-'))
320
+ const runId = 'benchmarks/sol/rollouts/task-022'
321
+ const run = path.join(root, runId)
322
+ fs.mkdirSync(path.join(run, 'evaluation', 'vision-judge'), { recursive: true })
323
+ fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
324
+ task_id: 'cloud-only-task',
325
+ model_id: 'gpt-test',
326
+ provider: 'openai',
327
+ state: 'completed',
328
+ }))
329
+ fs.writeFileSync(
330
+ path.join(run, 'evaluation', 'vision-judge', 'score.json'),
331
+ JSON.stringify({
332
+ prompt_version: 'autocad-vision-judge-v3',
333
+ overall_score: 0.9,
334
+ component_scores: { geometry: 0.9 },
335
+ }),
336
+ )
337
+
338
+ const detail = _test.traceDetail(root, runId)
339
+ assert.equal(
340
+ detail.referenceUrl,
341
+ `/api/runs/${encodeURIComponent(runId)}/file/reference.png`,
342
+ )
343
+ assert.equal(
344
+ detail.artifacts.candidateRender,
345
+ `/api/runs/${encodeURIComponent(runId)}/file/evaluation/render.png`,
346
+ )
347
+
348
+ const hydrated = []
349
+ const api = createTraceApi({
350
+ runsRoot: root,
351
+ cloudArtifactLoader: async (request) => {
352
+ hydrated.push(request.relativePath)
353
+ fs.mkdirSync(path.dirname(request.artifact), { recursive: true })
354
+ fs.writeFileSync(request.artifact, `cloud:${request.relativePath}`)
355
+ },
356
+ })
357
+ for (const relativePath of ['reference.png', 'evaluation/render.png']) {
358
+ const response = new TestResponse()
359
+ const finished = once(response, 'finish')
360
+ await api({
361
+ method: 'GET',
362
+ url: `/api/runs/${encodeURIComponent(runId)}/file/${relativePath}`,
363
+ }, response)
364
+ await finished
365
+ assert.equal(response.statusCode, 200)
366
+ assert.equal(response.headers['Content-Type'], 'image/png')
367
+ assert.equal(response.headers['Cache-Control'], 'private, max-age=31536000, immutable')
368
+ assert.equal(Buffer.concat(response.body).toString(), `cloud:${relativePath}`)
369
+ }
370
+ assert.deepEqual(hydrated, ['reference.png', 'evaluation/render.png'])
371
+ })
372
+
373
+ test('hydrates a missing frame from the cloud artifact loader on demand', async () => {
374
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-fetch-'))
375
+ const run = path.join(root, 'batch', 'rollouts', 'task-001')
376
+ fs.mkdirSync(run, { recursive: true })
377
+ let hydrated = null
378
+ const api = createTraceApi({
379
+ runsRoot: root,
380
+ cloudArtifactLoader: async (request) => {
381
+ hydrated = request
382
+ fs.mkdirSync(path.dirname(request.artifact), { recursive: true })
383
+ fs.writeFileSync(request.artifact, 'cloud-png')
384
+ },
385
+ })
386
+ const response = new TestResponse()
387
+ const finished = once(response, 'finish')
388
+
389
+ const handled = await api({
390
+ method: 'GET',
391
+ url: '/api/runs/batch%2Frollouts%2Ftask-001/file/frames/frame_0042.png',
392
+ }, response)
393
+ await finished
394
+
395
+ assert.equal(handled, true)
396
+ assert.equal(response.statusCode, 200)
397
+ assert.equal(response.headers['Content-Type'], 'image/png')
398
+ assert.equal(Buffer.concat(response.body).toString(), 'cloud-png')
399
+ assert.equal(hydrated.runId, 'batch/rollouts/task-001')
400
+ assert.equal(hydrated.relativePath, 'frames/frame_0042.png')
401
+ })
402
+
403
+ test('redirects a cold cloud image directly to a short-lived private URL', async () => {
404
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-redirect-'))
405
+ const runId = 'batch/rollouts/task-001'
406
+ fs.mkdirSync(path.join(root, runId), { recursive: true })
407
+ let signed = null
408
+ const api = createTraceApi({
409
+ runsRoot: root,
410
+ cloudArtifactUrl: async (request) => {
411
+ signed = request
412
+ return 'https://private-origin.example/signed-frame'
413
+ },
414
+ cloudArtifactLoader: async () => {
415
+ assert.fail('the proxy loader must not run after a successful signed redirect')
416
+ },
417
+ })
418
+ const response = new TestResponse()
419
+ const finished = once(response, 'finish')
420
+
421
+ await api({
422
+ method: 'GET',
423
+ url: `/api/runs/${encodeURIComponent(runId)}/file/frames/frame_0042.png`,
424
+ }, response)
425
+ await finished
426
+
427
+ assert.equal(response.statusCode, 307)
428
+ assert.equal(response.headers.Location, 'https://private-origin.example/signed-frame')
429
+ assert.equal(response.headers['Cache-Control'], 'private, max-age=240')
430
+ assert.deepEqual(signed, {
431
+ runId,
432
+ relativePath: 'frames/frame_0042.png',
433
+ })
434
+ })
435
+
436
+ test('bootstraps a missing live rollout trace from cloud metadata', async () => {
437
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-cloud-index-'))
438
+ const runId = 'benchmarks/live-suite/rollouts/task-004'
439
+ const run = path.join(root, runId)
440
+ let hydration = null
441
+ const api = createTraceApi({
442
+ runsRoot: root,
443
+ cloudTraceLoader: async (request) => {
444
+ hydration = request
445
+ fs.mkdirSync(request.runDirectory, { recursive: true })
446
+ fs.writeFileSync(path.join(request.runDirectory, 'run-state.json'), JSON.stringify({
447
+ task_id: 'task-004',
448
+ model_id: 'claude-fable-5',
449
+ provider: 'anthropic',
450
+ state: 'running',
451
+ accepted_actions: 42,
452
+ }))
453
+ fs.writeFileSync(path.join(request.runDirectory, 'model_turn_summaries.jsonl'), `${JSON.stringify({
454
+ turn: 1,
455
+ tool_use_id: 'tool-1',
456
+ action: { actions: [{ action: 'click', x: 1, y: 2 }] },
457
+ model_input: {
458
+ artifacts: [{ artifact_path: 'frames/frame_0041.png' }],
459
+ },
460
+ })}\n`)
461
+ },
462
+ })
463
+ const response = new TestResponse()
464
+ const finished = once(response, 'finish')
465
+
466
+ await api({
467
+ method: 'GET',
468
+ url: `/api/runs/${encodeURIComponent(runId)}`,
469
+ }, response)
470
+ await finished
471
+
472
+ assert.equal(response.statusCode, 200)
473
+ assert.equal(hydration.runId, runId)
474
+ assert.equal(hydration.runDirectory, run)
475
+ const detail = JSON.parse(Buffer.concat(response.body).toString())
476
+ assert.equal(detail.summary.acceptedActions, 42)
477
+ assert.equal(detail.turns.length, 1)
478
+ assert.deepEqual(detail.frames.map((frame) => frame.name), [
479
+ 'frame_0041.png',
480
+ 'frame_0042.png',
481
+ ])
482
+ })
483
+
484
+ test('refreshes stale live benchmark metadata before returning its detail', async () => {
485
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-benchmark-cloud-refresh-'))
486
+ const benchmarkId = 'benchmarks/live-suite'
487
+ const benchmark = path.join(root, benchmarkId)
488
+ const run = path.join(benchmark, 'rollouts', 'task-004')
489
+ fs.mkdirSync(run, { recursive: true })
490
+ fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
491
+ state: 'running',
492
+ updated_at: '2026-07-18T00:00:00Z',
493
+ rollouts: {
494
+ 'task-004': {
495
+ task_id: 'task-004',
496
+ model_id: 'claude-fable-5',
497
+ provider: 'anthropic',
498
+ state: 'running',
499
+ },
500
+ },
501
+ }))
502
+ fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
503
+ task_id: 'task-004',
504
+ model_id: 'claude-fable-5',
505
+ provider: 'anthropic',
506
+ state: 'running',
507
+ accepted_actions: 1,
508
+ }))
509
+ let refreshCount = 0
510
+ const api = createTraceApi({
511
+ runsRoot: root,
512
+ cloudBenchmarkLoader: async ({ benchmarkDirectory }) => {
513
+ refreshCount += 1
514
+ fs.writeFileSync(path.join(
515
+ benchmarkDirectory, 'rollouts', 'task-004', 'run-state.json',
516
+ ), JSON.stringify({
517
+ task_id: 'task-004',
518
+ model_id: 'claude-fable-5',
519
+ provider: 'anthropic',
520
+ state: 'running',
521
+ accepted_actions: 87,
522
+ turn_count: 21,
523
+ }))
524
+ },
525
+ })
526
+ const response = new TestResponse()
527
+ const finished = once(response, 'finish')
528
+
529
+ await api({
530
+ method: 'GET',
531
+ url: `/api/benchmarks/${encodeURIComponent(benchmarkId)}`,
532
+ }, response)
533
+ await finished
534
+
535
+ assert.equal(response.statusCode, 200)
536
+ assert.equal(refreshCount, 1)
537
+ const detail = JSON.parse(Buffer.concat(response.body).toString())
538
+ assert.equal(detail.tasks[0].acceptedActions, 87)
539
+ assert.equal(detail.aggregate.acceptedActions, 87)
540
+ assert.equal(detail.aggregate.usage.modelTurns, 21)
541
+ })
542
+
543
+ test('content-addresses model frames and replaces a stale local frame', async () => {
544
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-stale-frame-'))
545
+ const runId = 'batch/rollouts/task-001'
546
+ const run = path.join(root, runId)
547
+ const frames = path.join(run, 'frames')
548
+ fs.mkdirSync(frames, { recursive: true })
549
+ const freshFrame = Buffer.from('fresh-cloud-frame')
550
+ const expectedSha256 = createHash('sha256').update(freshFrame).digest('hex')
551
+ fs.writeFileSync(path.join(frames, 'frame_0042.png'), 'stale-local-frame')
552
+ fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
553
+ task_id: 'task-001',
554
+ model_id: 'gpt-test',
555
+ provider: 'openai',
556
+ state: 'completed',
557
+ }))
558
+ fs.writeFileSync(path.join(run, 'model_turn_summaries.jsonl'), `${JSON.stringify({
559
+ turn: 1,
560
+ model_input: {
561
+ artifacts: [{
562
+ artifact_path: 'frames/frame_0042.png',
563
+ sha256: expectedSha256,
564
+ sequence: 42,
565
+ }],
566
+ },
567
+ })}\n`)
568
+
569
+ const detail = _test.traceDetail(root, runId)
570
+ assert.equal(detail.frames[0].sha256, expectedSha256)
571
+ assert.equal(
572
+ detail.frames[0].url,
573
+ `/api/runs/batch%2Frollouts%2Ftask-001/file/frames/frame_0042.png?sha256=${expectedSha256}`,
574
+ )
575
+
576
+ let hydration = null
577
+ const api = createTraceApi({
578
+ runsRoot: root,
579
+ cloudArtifactLoader: async (request) => {
580
+ hydration = request
581
+ fs.writeFileSync(request.artifact, freshFrame)
582
+ },
583
+ })
584
+ const response = new TestResponse()
585
+ const finished = once(response, 'finish')
586
+ await api({
587
+ method: 'GET',
588
+ url: `${detail.frames[0].url}`,
589
+ }, response)
590
+ await finished
591
+
592
+ assert.equal(response.statusCode, 200)
593
+ assert.equal(Buffer.concat(response.body).toString(), freshFrame.toString())
594
+ assert.equal(response.headers['Cache-Control'], 'private, max-age=31536000, immutable')
595
+ assert.equal(hydration.force, true)
596
+ assert.equal(hydration.expectedSha256, expectedSha256)
597
+ })
598
+
599
+ test('never serves a stale frame when cloud refresh does not match its checksum', async () => {
600
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-reject-stale-frame-'))
601
+ const runId = 'batch/rollouts/task-001'
602
+ const run = path.join(root, runId)
603
+ const frames = path.join(run, 'frames')
604
+ fs.mkdirSync(frames, { recursive: true })
605
+ const expectedSha256 = createHash('sha256').update('expected-frame').digest('hex')
606
+ fs.writeFileSync(path.join(frames, 'frame_0001.png'), 'stale-frame')
607
+
608
+ const api = createTraceApi({
609
+ runsRoot: root,
610
+ cloudArtifactLoader: async () => {},
611
+ })
612
+ const response = new TestResponse()
613
+ const finished = once(response, 'finish')
614
+ await api({
615
+ method: 'GET',
616
+ url: `/api/runs/${encodeURIComponent(runId)}/file/frames/frame_0001.png?sha256=${expectedSha256}`,
617
+ }, response)
618
+ await finished
619
+
620
+ assert.equal(response.statusCode, 404)
621
+ assert.match(Buffer.concat(response.body).toString(), /artifact_not_found/)
622
+ })
623
+
624
+ test('uses compact turn summaries without returning repeated request history', () => {
625
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-compact-'))
626
+ const run = path.join(root, 'compact-run')
627
+ fs.mkdirSync(run)
628
+ fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
629
+ task_id: 'task-001',
630
+ model_id: 'claude-test',
631
+ provider: 'anthropic',
632
+ state: 'completed',
633
+ turn_count: 1,
634
+ }))
635
+ fs.writeFileSync(path.join(run, 'model_turns.jsonl'), 'x'.repeat(9 * 1024 * 1024))
636
+ fs.writeFileSync(path.join(run, 'model_turn_summaries.jsonl'), `${JSON.stringify({
637
+ turn: 1,
638
+ tool_use_id: 'call-compact',
639
+ action: { action: 'done' },
640
+ model_input: {
641
+ model: 'claude-test',
642
+ artifacts: [{ artifact_path: 'frames/frame_0000.png' }],
643
+ },
644
+ })}\n`)
645
+
646
+ const detail = _test.traceDetail(root, 'compact-run')
647
+
648
+ assert.equal(detail.traceIndexed, true)
649
+ assert.equal(detail.turns.length, 1)
650
+ assert.equal(detail.turns[0].tool_use_id, 'call-compact')
651
+ assert.equal(JSON.stringify(detail).includes('repeated request history'), false)
652
+ assert.equal('events' in detail, false)
653
+ assert.equal('frameEvents' in detail, false)
654
+ })
655
+
656
+ test('does not synchronously parse a large unindexed trace for benchmark usage', () => {
657
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-large-'))
658
+ const run = path.join(root, 'large-run')
659
+ fs.mkdirSync(run)
660
+ const trace = path.join(run, 'model_turns.jsonl')
661
+ fs.writeFileSync(trace, '')
662
+ fs.truncateSync(trace, 9 * 1024 * 1024)
663
+
664
+ const usage = _test.runUsage(run, { input_per_million: 5 }, {
665
+ turn_count: 42,
666
+ usage: { input_tokens: 1200, output_tokens: 30, total_tokens: 1230 },
667
+ })
668
+
669
+ assert.equal(usage.modelTurns, 42)
670
+ assert.equal(usage.inputTokens, 1200)
671
+ assert.equal(usage.totalTokens, 1230)
672
+ assert.equal(usage.apiCost, null)
673
+ })
674
+
675
+ test('uses the live aggregate instead of parsing a large compact usage index', () => {
676
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-summary-large-'))
677
+ const run = path.join(root, 'large-summary-run')
678
+ fs.mkdirSync(run)
679
+ const summary = path.join(run, 'model_turn_summaries.jsonl')
680
+ fs.writeFileSync(summary, '')
681
+ fs.truncateSync(summary, 600 * 1024)
682
+
683
+ const usage = _test.runUsage(run, { input_per_million: 5 }, {
684
+ turn_count: 77,
685
+ usage: { input_tokens: 9000, output_tokens: 80, total_tokens: 9080 },
686
+ })
687
+
688
+ assert.equal(usage.modelTurns, 77)
689
+ assert.equal(usage.inputTokens, 9000)
690
+ assert.equal(usage.totalTokens, 9080)
691
+ assert.equal(usage.apiCost, null)
692
+ })
693
+
694
+ test('reconciles a stale running batch row with a completed rollout trace', () => {
695
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-trace-stale-batch-'))
696
+ const benchmark = path.join(root, 'stale-batch')
697
+ const run = path.join(benchmark, 'rollouts', 'task-001')
698
+ fs.mkdirSync(run, { recursive: true })
699
+ fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
700
+ state: 'running',
701
+ updated_at: '2020-01-01T00:00:00Z',
702
+ rollouts: {
703
+ 'task-001': {
704
+ task_id: 'task-001',
705
+ model_id: 'claude-test',
706
+ provider: 'anthropic',
707
+ state: 'running',
708
+ },
709
+ 'task-002': {
710
+ task_id: 'task-002',
711
+ model_id: 'claude-test',
712
+ provider: 'anthropic',
713
+ state: 'queued',
714
+ },
715
+ },
716
+ }))
717
+ fs.writeFileSync(path.join(run, 'run-state.json'), JSON.stringify({
718
+ task_id: 'task-001',
719
+ model_id: 'claude-test',
720
+ provider: 'anthropic',
721
+ state: 'completed',
722
+ turn_count: 1,
723
+ }))
724
+
725
+ const detail = _test.benchmarkDetail(root, 'stale-batch')
726
+
727
+ assert.equal(detail.tasks[0].status, 'completed')
728
+ assert.equal(detail.aggregate.completedTasks, 1)
729
+ assert.equal(detail.aggregate.activeTasks, 0)
730
+ assert.equal(detail.status, 'interrupted')
731
+ })
732
+
733
+ test('calculates cached and long-context token costs per model turn', () => {
734
+ const pricing = {
735
+ input_per_million: 5,
736
+ cached_input_per_million: 0.5,
737
+ output_per_million: 30,
738
+ long_context_threshold_tokens: 272000,
739
+ long_context_input_multiplier: 2,
740
+ long_context_output_multiplier: 1.5,
741
+ }
742
+ const usage = _test.usageSummary([
743
+ { usage: {
744
+ input_tokens: 1000,
745
+ cached_input_tokens: 400,
746
+ output_tokens: 100,
747
+ reasoning_output_tokens: 60,
748
+ total_tokens: 1100,
749
+ } },
750
+ { usage: { input_tokens: 300000, output_tokens: 1000, total_tokens: 301000 } },
751
+ ], pricing)
752
+
753
+ assert.equal(usage.inputTokens, 301000)
754
+ assert.equal(usage.cachedInputTokens, 400)
755
+ assert.equal(usage.outputTokens, 1100)
756
+ assert.equal(usage.reasoningOutputTokens, 60)
757
+ assert.ok(Math.abs(usage.apiCost - 3.0512) < 1e-12)
758
+ })
759
+
760
+ test('aggregates a live multi-task benchmark from batch state and rollout traces', () => {
761
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-benchmark-'))
762
+ const benchmark = path.join(root, 'benchmarks', 'full-suite')
763
+ const completed = path.join(benchmark, 'rollouts', 'task-001')
764
+ fs.mkdirSync(completed, { recursive: true })
765
+ fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
766
+ schema_version: 2,
767
+ batch_id: 'batch-full',
768
+ benchmark_name: 'Full suite',
769
+ state: 'running',
770
+ started_at: '2026-07-16T00:00:00Z',
771
+ updated_at: '2026-07-16T00:10:00Z',
772
+ max_concurrency: 10,
773
+ pricing: {
774
+ input_per_million: 5,
775
+ cached_input_per_million: 0.5,
776
+ output_per_million: 30,
777
+ },
778
+ rollouts: {
779
+ 'task-001': {
780
+ task_id: 'task-001',
781
+ model_id: 'gpt-5.6-sol',
782
+ provider: 'openai',
783
+ state: 'completed',
784
+ },
785
+ 'task-022': {
786
+ task_id: 'task-022',
787
+ model_id: 'gpt-5.6-sol',
788
+ provider: 'openai',
789
+ state: 'queued',
790
+ },
791
+ },
792
+ }))
793
+ fs.writeFileSync(path.join(completed, 'result.json'), JSON.stringify({
794
+ task_id: 'task-001',
795
+ model_id: 'gpt-5.6-sol',
796
+ provider: 'openai',
797
+ completed: true,
798
+ accepted_actions: 20,
799
+ artifact_bytes: 1234,
800
+ }))
801
+ fs.writeFileSync(path.join(completed, 'model_turns.jsonl'), `${JSON.stringify({
802
+ usage: { input_tokens: 1000, cached_input_tokens: 400, output_tokens: 100 },
803
+ })}\n`)
804
+
805
+ const detail = _test.benchmarkDetail(root, 'benchmarks/full-suite')
806
+
807
+ assert.equal(detail.name, 'Full suite')
808
+ assert.equal(detail.aggregate.totalTasks, 2)
809
+ assert.equal(detail.aggregate.completedTasks, 1)
810
+ assert.equal(detail.aggregate.queuedTasks, 1)
811
+ assert.equal(detail.aggregate.artifactTasks, 1)
812
+ assert.equal(detail.aggregate.usage.inputTokens, 1000)
813
+ assert.ok(Math.abs(detail.aggregate.usage.apiCost - 0.0062) < 1e-12)
814
+ assert.deepEqual(detail.tasks.map((task) => task.level), ['basic', 'basic'])
815
+ assert.deepEqual(detail.tasks.map((task) => task.drawingType), ['2d', '3d'])
816
+ assert.deepEqual(detail.aggregate.drawingTypes, {
817
+ '2d': { total: 1, scored: 0, meanScore: null },
818
+ '3d': { total: 1, scored: 0, meanScore: null },
819
+ })
820
+ assert.deepEqual(detail.aggregate.evaluationStates, {
821
+ awaiting_score: 1,
822
+ queued: 1,
823
+ })
824
+ assert.equal(_test.listBenchmarks(root)[0].id, 'benchmarks/full-suite')
825
+ })
826
+
827
+ test('keeps execution, recovery, budget, infrastructure, and evaluation states separate', () => {
828
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'autocad-outcomes-'))
829
+ const benchmark = path.join(root, 'benchmarks', 'outcomes')
830
+ const cases = {
831
+ completed: { completed: true, stop_reason: 'done', artifact_bytes: 10 },
832
+ recovered: { completed: false, stop_reason: 'recovered_finalized_session', artifact_bytes: 10 },
833
+ budget: { completed: false, stop_reason: 'max_actions', artifact_bytes: 10 },
834
+ infra: { completed: false, stop_reason: 'rollout_error', failure: 'ConnectError', artifact_bytes: 10 },
835
+ model: { completed: false, stop_reason: 'invalid_action_limit', failure: 'invalid', artifact_bytes: 10 },
836
+ }
837
+ fs.mkdirSync(benchmark, { recursive: true })
838
+ fs.writeFileSync(path.join(benchmark, 'batch-state.json'), JSON.stringify({
839
+ state: 'running',
840
+ updated_at: '2020-01-01T00:00:00Z',
841
+ rollouts: Object.fromEntries(Object.keys(cases).map((task) => [task, {
842
+ task_id: 'task-001', model_id: 'model', provider: 'openai', state: 'failed',
843
+ failure: 'stale wrapper failure',
844
+ }])),
845
+ }))
846
+ for (const [name, result] of Object.entries(cases)) {
847
+ const run = path.join(benchmark, 'rollouts', name)
848
+ fs.mkdirSync(path.join(run, 'evaluation'), { recursive: true })
849
+ fs.writeFileSync(path.join(run, 'result.json'), JSON.stringify({
850
+ task_id: 'task-001', model_id: 'model', provider: 'openai', ...result,
851
+ }))
852
+ }
853
+ fs.writeFileSync(
854
+ path.join(benchmark, 'rollouts', 'completed', 'evaluation', 'status.json'),
855
+ JSON.stringify({ stage: 'failed', error: 'VisionJudgeError: ReadTimeout' }),
856
+ )
857
+
858
+ const detail = _test.benchmarkDetail(root, 'benchmarks/outcomes')
859
+
860
+ assert.equal(detail.status, 'completed_with_issues')
861
+ assert.equal(detail.aggregate.settledTasks, 5)
862
+ assert.equal(detail.aggregate.completedTasks, 1)
863
+ assert.equal(detail.aggregate.recoveredTasks, 1)
864
+ assert.equal(detail.aggregate.budgetExhaustedTasks, 1)
865
+ assert.equal(detail.aggregate.infrastructureFailedTasks, 1)
866
+ assert.equal(detail.aggregate.failedTasks, 1)
867
+ assert.equal(detail.tasks.find((task) => task.rolloutId === 'completed').status, 'completed')
868
+ assert.equal(detail.tasks.find((task) => task.rolloutId === 'completed').failure, null)
869
+ assert.equal(detail.tasks.find((task) => task.rolloutId === 'budget').failure, null)
870
+ assert.equal(detail.tasks.find((task) => task.rolloutId === 'infra').failure, 'ConnectError')
871
+ assert.equal(detail.tasks.find((task) => task.rolloutId === 'completed').evaluationState, 'evaluation_failed')
872
+ })
apps/dashboard/trace-api.d.mts ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export function createTraceApi(options?: {}): (request: any, response: any) => Promise<boolean>;
2
+ export namespace _test {
3
+ export { readJsonl };
4
+ export { safeRunDirectory };
5
+ export { safeArtifact };
6
+ export { runSummary };
7
+ export { traceDetail };
8
+ }
9
+ declare function readJsonl(filePath: any): any[];
10
+ declare function safeRunDirectory(runsRoot: any, runId: any): string;
11
+ declare function safeArtifact(runDirectory: any, relativePath: any): string;
12
+ declare function runSummary(runsRoot: any, runId: any): {
13
+ id: any;
14
+ taskId: any;
15
+ attemptId: any;
16
+ modelId: any;
17
+ provider: any;
18
+ reasoningEffort: any;
19
+ status: any;
20
+ stopReason: any;
21
+ failure: any;
22
+ acceptedActions: any;
23
+ invalidActions: any;
24
+ screenshots: any;
25
+ elapsedSeconds: any;
26
+ artifactBytes: any;
27
+ score: any;
28
+ valid: any;
29
+ scoreFailure: any;
30
+ updatedAt: any;
31
+ };
32
+ declare function traceDetail(runsRoot: any, runId: any): {
33
+ summary: {
34
+ id: any;
35
+ taskId: any;
36
+ attemptId: any;
37
+ modelId: any;
38
+ provider: any;
39
+ reasoningEffort: any;
40
+ status: any;
41
+ stopReason: any;
42
+ failure: any;
43
+ acceptedActions: any;
44
+ invalidActions: any;
45
+ screenshots: any;
46
+ elapsedSeconds: any;
47
+ artifactBytes: any;
48
+ score: any;
49
+ valid: any;
50
+ scoreFailure: any;
51
+ updatedAt: any;
52
+ };
53
+ state: any;
54
+ result: any;
55
+ score: any;
56
+ turns: any[];
57
+ receipts: any[];
58
+ events: any[];
59
+ frameEvents: any[];
60
+ frames: {
61
+ name: string;
62
+ url: string;
63
+ }[];
64
+ referenceUrl: string;
65
+ artifacts: {
66
+ dwg: string;
67
+ candidateRender: string;
68
+ diffRender: string;
69
+ };
70
+ };
71
+ export {};
apps/dashboard/trace-api.mjs ADDED
@@ -0,0 +1,1080 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'node:fs'
2
+ import { createHash } from 'node:crypto'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const DASHBOARD_ROOT = path.dirname(fileURLToPath(import.meta.url))
7
+ const CHECKOUT_ROOT = path.resolve(DASHBOARD_ROOT, '..', '..')
8
+ const BENCHMARK_DATA_ROOT = path.resolve(
9
+ process.env.AUTOCAD_BENCH_ROOT || CHECKOUT_ROOT,
10
+ )
11
+ const DEFAULT_RUNS_ROOT = path.join(CHECKOUT_ROOT, 'runs')
12
+ const MANIFEST_PATH = path.join(BENCHMARK_DATA_ROOT, 'tasks', 'manifest.jsonl')
13
+ const TRACE_SUMMARY_NAME = 'model_turn_summaries.jsonl'
14
+ const MAX_INLINE_LEGACY_TRACE_BYTES = 8 * 1024 * 1024
15
+ const MAX_INLINE_USAGE_SUMMARY_BYTES = 512 * 1024
16
+ const usageCache = new Map()
17
+
18
+ function readJson(filePath) {
19
+ try {
20
+ return JSON.parse(fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, ''))
21
+ } catch {
22
+ return null
23
+ }
24
+ }
25
+
26
+ function readJsonl(filePath) {
27
+ try {
28
+ return fs
29
+ .readFileSync(filePath, 'utf8')
30
+ .split(/\r?\n/)
31
+ .filter((line) => line.trim())
32
+ .flatMap((line) => {
33
+ try {
34
+ return [JSON.parse(line.replace(/^\uFEFF/, ''))]
35
+ } catch {
36
+ return []
37
+ }
38
+ })
39
+ } catch {
40
+ return []
41
+ }
42
+ }
43
+
44
+ function modelInputArtifacts(value, output = []) {
45
+ if (Array.isArray(value)) {
46
+ for (const item of value) modelInputArtifacts(item, output)
47
+ return output
48
+ }
49
+ if (!value || typeof value !== 'object') return output
50
+ if (typeof value.artifact_path === 'string') {
51
+ output.push(Object.fromEntries(Object.entries(value).filter(([key]) => [
52
+ 'type', 'artifact_path', 'sha256', 'bytes', 'width', 'height', 'sequence',
53
+ ].includes(key))))
54
+ }
55
+ for (const item of Object.values(value)) modelInputArtifacts(item, output)
56
+ return output
57
+ }
58
+
59
+ function compactTurn(turn) {
60
+ if (!turn || typeof turn !== 'object') return turn
61
+ const compact = Object.fromEntries(
62
+ Object.entries(turn).filter(([key]) => key !== 'model_input'),
63
+ )
64
+ if (turn.model_input && typeof turn.model_input === 'object') {
65
+ compact.model_input = {
66
+ model: turn.model_input.model || null,
67
+ artifacts: modelInputArtifacts(turn.model_input),
68
+ }
69
+ }
70
+ return compact
71
+ }
72
+
73
+ function traceSummaryPath(runDirectory) {
74
+ return path.join(runDirectory, TRACE_SUMMARY_NAME)
75
+ }
76
+
77
+ function traceTurns(runDirectory) {
78
+ const summaryPath = traceSummaryPath(runDirectory)
79
+ if (fs.existsSync(summaryPath)) return readJsonl(summaryPath)
80
+ const legacyPath = path.join(runDirectory, 'model_turns.jsonl')
81
+ try {
82
+ if (fs.statSync(legacyPath).size <= MAX_INLINE_LEGACY_TRACE_BYTES) {
83
+ return readJsonl(legacyPath).map(compactTurn)
84
+ }
85
+ } catch {
86
+ // Missing or actively-created traces simply have no turns yet.
87
+ }
88
+ return []
89
+ }
90
+
91
+ function manifestByTask() {
92
+ const entries = readJsonl(MANIFEST_PATH)
93
+ return new Map(entries.map((entry) => [entry.task_id, entry]))
94
+ }
95
+
96
+ function safeRunDirectory(runsRoot, runId) {
97
+ if (!runId || runId.includes('\\')) {
98
+ return null
99
+ }
100
+ const segments = runId.split('/')
101
+ if (segments.some((segment) => !segment || segment === '.' || segment === '..')) return null
102
+ const candidate = path.resolve(runsRoot, runId)
103
+ return candidate.startsWith(path.resolve(runsRoot) + path.sep) ? candidate : null
104
+ }
105
+
106
+ function safeArtifact(runDirectory, relativePath) {
107
+ const candidate = path.resolve(runDirectory, relativePath)
108
+ return candidate.startsWith(path.resolve(runDirectory) + path.sep) ? candidate : null
109
+ }
110
+
111
+ function normalizeSha256(value) {
112
+ const sha256 = String(value || '').toLowerCase()
113
+ return /^[a-f0-9]{64}$/.test(sha256) ? sha256 : null
114
+ }
115
+
116
+ function fileSha256(filePath) {
117
+ try {
118
+ if (!fs.statSync(filePath).isFile()) return null
119
+ return createHash('sha256').update(fs.readFileSync(filePath)).digest('hex')
120
+ } catch {
121
+ return null
122
+ }
123
+ }
124
+
125
+ function contentType(filePath) {
126
+ const extension = path.extname(filePath).toLowerCase()
127
+ return {
128
+ '.png': 'image/png',
129
+ '.json': 'application/json; charset=utf-8',
130
+ '.jsonl': 'application/x-ndjson; charset=utf-8',
131
+ '.dwg': 'application/acad',
132
+ }[extension] || 'application/octet-stream'
133
+ }
134
+
135
+ function sendJson(response, status, value) {
136
+ response.statusCode = status
137
+ response.setHeader('Content-Type', 'application/json; charset=utf-8')
138
+ response.setHeader('Cache-Control', 'no-store')
139
+ response.end(JSON.stringify(value))
140
+ }
141
+
142
+ function sendFile(response, filePath, { immutable = false } = {}) {
143
+ if (!filePath || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
144
+ sendJson(response, 404, { error: 'artifact_not_found' })
145
+ return
146
+ }
147
+ response.statusCode = 200
148
+ response.setHeader('Content-Type', contentType(filePath))
149
+ response.setHeader(
150
+ 'Cache-Control',
151
+ immutable ? 'private, max-age=31536000, immutable' : 'no-store',
152
+ )
153
+ fs.createReadStream(filePath).pipe(response)
154
+ }
155
+
156
+ function sendPrivateRedirect(response, location) {
157
+ response.statusCode = 307
158
+ response.setHeader('Location', location)
159
+ // Keep the stable local URL cached only inside this browser and comfortably
160
+ // below the five-minute lifetime of the signed origin URL.
161
+ response.setHeader('Cache-Control', 'private, max-age=240')
162
+ response.end()
163
+ }
164
+
165
+ function readRunScores(runDirectory) {
166
+ const visionArtifact = readJson(path.join(
167
+ runDirectory, 'evaluation', 'vision-judge', 'score.json',
168
+ ))
169
+ const vision = visionArtifact
170
+ ? {
171
+ ...visionArtifact,
172
+ score: visionArtifact.overall_score ?? null,
173
+ valid: true,
174
+ failure_reason: null,
175
+ scoring_method: `vision:${visionArtifact.prompt_version || 'unknown'}`,
176
+ geometry_similarity: visionArtifact.component_scores?.geometry ?? null,
177
+ dimension_similarity: visionArtifact.component_scores?.dimensions ?? null,
178
+ annotation_similarity: visionArtifact.component_scores?.annotations ?? null,
179
+ visual_similarity: visionArtifact.component_scores?.presentation ?? null,
180
+ score_source: 'vision_judge',
181
+ }
182
+ : null
183
+ const deterministicArtifact = readJson(path.join(
184
+ runDirectory, 'evaluation', 'score.json',
185
+ ))
186
+ const deterministic = deterministicArtifact
187
+ ? { ...deterministicArtifact, score_source: 'deterministic' }
188
+ : null
189
+ return {
190
+ vision,
191
+ deterministic,
192
+ displayed: vision || deterministic,
193
+ }
194
+ }
195
+
196
+ function readRunScore(runDirectory) {
197
+ return readRunScores(runDirectory).displayed
198
+ }
199
+
200
+ function scoreSummary(score) {
201
+ if (!score) return null
202
+ return {
203
+ source: score.score_source ?? null,
204
+ score: score.score ?? null,
205
+ valid: score.valid ?? null,
206
+ failureReason: score.failure_reason ?? null,
207
+ method: score.scoring_method ?? null,
208
+ verdict: score.verdict ?? null,
209
+ targetAcquired: score.target_acquired ?? null,
210
+ confidence: score.confidence ?? null,
211
+ needsHumanReview: score.needs_human_review ?? false,
212
+ geometry: score.geometry_similarity ?? null,
213
+ dimensions: score.dimension_similarity ?? null,
214
+ annotations: score.annotation_similarity ?? null,
215
+ presentation: score.visual_similarity ?? null,
216
+ }
217
+ }
218
+
219
+ const ACTIVE_STATES = new Set([
220
+ 'starting', 'resetting', 'handoff_ready', 'running', 'finalizing',
221
+ ])
222
+ const TERMINAL_EXECUTION_STATES = new Set([
223
+ 'completed', 'recovered', 'budget_exhausted', 'model_failed',
224
+ 'infrastructure_failed', 'artifact_missing', 'interrupted',
225
+ ])
226
+ const ACTIVE_EVALUATION_STAGES = new Set([
227
+ 'inspecting', 'deterministic_scoring', 'vision_judging',
228
+ ])
229
+
230
+ function executionState(result, state = null) {
231
+ if (TERMINAL_EXECUTION_STATES.has(result?.execution_status)) {
232
+ return result.execution_status
233
+ }
234
+ const stopReason = String(result?.stop_reason || '').toLowerCase()
235
+ const failure = String(result?.failure || '').toLowerCase()
236
+ const artifactBytes = Number(result?.artifact_bytes || 0)
237
+ if (result?.completed === true || stopReason === 'done') {
238
+ return artifactBytes > 0 ? 'completed' : 'artifact_missing'
239
+ }
240
+ if (stopReason === 'recovered_finalized_session' && artifactBytes > 0) return 'recovered'
241
+ if (['max_actions', 'wall_timeout'].includes(stopReason)) return 'budget_exhausted'
242
+ if (['operator_stopped', 'interrupted'].includes(stopReason)) return 'interrupted'
243
+ if (stopReason === 'rollout_error' || [
244
+ 'brokererror', 'connecterror', 'connection attempts failed',
245
+ 'nodename nor servname', 'transport unavailable', 'ssm',
246
+ ].some((marker) => failure.includes(marker))) return 'infrastructure_failed'
247
+ if (result && artifactBytes <= 0 && !failure) return 'artifact_missing'
248
+ if (result) return 'model_failed'
249
+ if (ACTIVE_STATES.has(state?.state)) return state.state
250
+ if (TERMINAL_EXECUTION_STATES.has(state?.state)) return state.state
251
+ return state?.state || 'running'
252
+ }
253
+
254
+ function evaluationState({ status, artifactBytes, score, evaluationStatus = null }) {
255
+ if (status === 'queued') return 'queued'
256
+ if (ACTIVE_STATES.has(status)) return 'running'
257
+ if (ACTIVE_EVALUATION_STAGES.has(evaluationStatus?.stage)) {
258
+ const updatedAt = Date.parse(evaluationStatus?.updated_at || '')
259
+ if (Number.isFinite(updatedAt) && Date.now() - updatedAt > 30 * 60 * 1000) {
260
+ return 'evaluation_interrupted'
261
+ }
262
+ return 'evaluating'
263
+ }
264
+ if (evaluationStatus?.stage === 'retry_pending') return 'evaluation_retrying'
265
+ if (evaluationStatus?.stage === 'failed') return 'evaluation_failed'
266
+ if (score?.valid === false || score?.failure_reason) return 'candidate_invalid'
267
+ if (Number.isFinite(score?.score)) return 'scored'
268
+ if (artifactBytes > 0) return 'awaiting_score'
269
+ if (['model_failed', 'infrastructure_failed', 'artifact_missing', 'interrupted'].includes(status)) return 'run_failed'
270
+ return 'no_artifact'
271
+ }
272
+
273
+ function runSummary(runsRoot, runId) {
274
+ const runDirectory = safeRunDirectory(runsRoot, runId)
275
+ if (!runDirectory) return null
276
+ const result = readJson(path.join(runDirectory, 'result.json'))
277
+ const state = readJson(path.join(runDirectory, 'run-state.json'))
278
+ const evaluationStatus = readJson(path.join(
279
+ runDirectory, 'evaluation', 'status.json',
280
+ ))
281
+ const scores = readRunScores(runDirectory)
282
+ const score = scores.displayed
283
+ const stats = fs.statSync(runDirectory)
284
+ const status = executionState(result, state)
285
+ const artifactBytes = result?.artifact_bytes ?? 0
286
+ const executionCanCarryFailure = [
287
+ 'model_failed', 'infrastructure_failed', 'artifact_missing', 'interrupted',
288
+ ].includes(status)
289
+ return {
290
+ id: runId,
291
+ taskId: state?.task_id || result?.task_id || 'unknown',
292
+ attemptId: state?.attempt_id || result?.attempt_id || null,
293
+ modelId: state?.model_id || result?.model_id || 'unknown',
294
+ provider: state?.provider || result?.provider || (String(result?.model_id || '').includes('nova') ? 'bedrock' : 'unknown'),
295
+ reasoningEffort: state?.reasoning_effort || result?.reasoning_effort || null,
296
+ status,
297
+ stopReason: result?.stop_reason || state?.stop_reason || null,
298
+ failure: executionCanCarryFailure
299
+ ? (result?.failure || state?.failure || null)
300
+ : (result?.failure || null),
301
+ acceptedActions: state?.accepted_actions ?? result?.accepted_actions ?? readJsonl(path.join(runDirectory, 'input_receipts.jsonl')).length,
302
+ invalidActions: state?.invalid_actions ?? result?.invalid_actions ?? 0,
303
+ screenshots: state?.screenshots ?? result?.screenshots ?? 0,
304
+ elapsedSeconds: result?.elapsed_s ?? null,
305
+ artifactBytes,
306
+ score: score?.score ?? null,
307
+ valid: score?.valid ?? null,
308
+ scoreFailure: score?.failure_reason ?? null,
309
+ scoreMethod: score?.scoring_method ?? null,
310
+ scoreSource: score?.score_source ?? null,
311
+ verdict: score?.verdict ?? null,
312
+ targetAcquired: score?.target_acquired ?? null,
313
+ judgeConfidence: score?.confidence ?? null,
314
+ needsHumanReview: score?.needs_human_review ?? false,
315
+ visionScore: scoreSummary(scores.vision),
316
+ deterministicScore: scoreSummary(scores.deterministic),
317
+ evaluationState: evaluationState({
318
+ status, artifactBytes, score, evaluationStatus,
319
+ }),
320
+ evaluationStage: evaluationStatus?.stage || null,
321
+ evaluationError: evaluationStatus?.error || null,
322
+ updatedAt: evaluationStatus?.updated_at || state?.updated_at || stats.mtime.toISOString(),
323
+ }
324
+ }
325
+
326
+ function listRuns(runsRoot) {
327
+ if (!fs.existsSync(runsRoot)) return []
328
+ const runIds = []
329
+ const visit = (directory, relativeDirectory, depth) => {
330
+ if (depth > 4) return
331
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
332
+ if (!entry.isDirectory()) continue
333
+ const relativePath = relativeDirectory
334
+ ? `${relativeDirectory}/${entry.name}`
335
+ : entry.name
336
+ const child = path.join(directory, entry.name)
337
+ const isRun = ['run-state.json', 'result.json', 'model_turns.jsonl']
338
+ .some((name) => fs.existsSync(path.join(child, name)))
339
+ if (isRun) runIds.push(relativePath)
340
+ else visit(child, relativePath, depth + 1)
341
+ }
342
+ }
343
+ visit(runsRoot, '', 1)
344
+ return runIds
345
+ .map((runId) => runSummary(runsRoot, runId))
346
+ .filter(Boolean)
347
+ .sort((left, right) => String(right.updatedAt).localeCompare(String(left.updatedAt)))
348
+ }
349
+
350
+ function usageSummary(turns, pricing = null) {
351
+ const totals = {
352
+ inputTokens: 0,
353
+ cachedInputTokens: 0,
354
+ outputTokens: 0,
355
+ reasoningOutputTokens: 0,
356
+ totalTokens: 0,
357
+ modelTurns: turns.length,
358
+ apiCost: pricing ? 0 : null,
359
+ }
360
+ for (const turn of turns) {
361
+ const usage = turn?.usage || {}
362
+ const input = Number(usage.input_tokens ?? usage.inputTokens ?? 0) || 0
363
+ const cached = Math.min(
364
+ input,
365
+ Number(usage.cached_input_tokens ?? usage.cachedInputTokens ?? 0) || 0,
366
+ )
367
+ const output = Number(usage.output_tokens ?? usage.outputTokens ?? 0) || 0
368
+ const reasoning = Number(
369
+ usage.reasoning_output_tokens ?? usage.reasoningOutputTokens ?? 0,
370
+ ) || 0
371
+ totals.inputTokens += input
372
+ totals.cachedInputTokens += cached
373
+ totals.outputTokens += output
374
+ totals.reasoningOutputTokens += reasoning
375
+ totals.totalTokens += Number(usage.total_tokens ?? usage.totalTokens ?? input + output) || 0
376
+ if (pricing) {
377
+ const isLong = pricing.long_context_threshold_tokens != null
378
+ && input > Number(pricing.long_context_threshold_tokens)
379
+ const inputMultiplier = isLong
380
+ ? Number(pricing.long_context_input_multiplier ?? 1)
381
+ : 1
382
+ const outputMultiplier = isLong
383
+ ? Number(pricing.long_context_output_multiplier ?? 1)
384
+ : 1
385
+ const uncached = Math.max(0, input - cached)
386
+ totals.apiCost += inputMultiplier * (
387
+ uncached * Number(pricing.input_per_million ?? 0)
388
+ + cached * Number(pricing.cached_input_per_million ?? 0)
389
+ ) / 1_000_000
390
+ totals.apiCost += outputMultiplier
391
+ * output * Number(pricing.output_per_million ?? 0) / 1_000_000
392
+ }
393
+ }
394
+ return totals
395
+ }
396
+
397
+ function cachedUsageSummary(filePath, pricing) {
398
+ let stats
399
+ try {
400
+ stats = fs.statSync(filePath)
401
+ } catch {
402
+ return null
403
+ }
404
+ const cacheKey = JSON.stringify([
405
+ stats.size, stats.mtimeMs, pricing || null,
406
+ ])
407
+ const cached = usageCache.get(filePath)
408
+ if (cached?.key === cacheKey) return cached.value
409
+ const value = usageSummary(readJsonl(filePath), pricing)
410
+ usageCache.set(filePath, { key: cacheKey, value })
411
+ if (usageCache.size > 64) usageCache.delete(usageCache.keys().next().value)
412
+ return value
413
+ }
414
+
415
+ function runUsage(
416
+ runDirectory,
417
+ pricing,
418
+ runState = null,
419
+ result = null,
420
+ { preferAggregate = false } = {},
421
+ ) {
422
+ const summaryPath = traceSummaryPath(runDirectory)
423
+ if (!preferAggregate) {
424
+ try {
425
+ if (fs.statSync(summaryPath).size <= MAX_INLINE_USAGE_SUMMARY_BYTES) {
426
+ const compact = cachedUsageSummary(summaryPath, pricing)
427
+ if (compact) return compact
428
+ }
429
+ } catch {
430
+ // Fall through to the legacy trace or live aggregate.
431
+ }
432
+ }
433
+
434
+ const legacyPath = path.join(runDirectory, 'model_turns.jsonl')
435
+ if (!preferAggregate) {
436
+ try {
437
+ if (fs.statSync(legacyPath).size <= MAX_INLINE_LEGACY_TRACE_BYTES) {
438
+ return cachedUsageSummary(legacyPath, pricing)
439
+ }
440
+ } catch {
441
+ // Fall through to the live aggregate written by newer pilots.
442
+ }
443
+ }
444
+
445
+ const raw = runState?.usage || result?.usage
446
+ const modelTurns = Number(runState?.turn_count ?? 0) || 0
447
+ if (!raw || typeof raw !== 'object') {
448
+ return {
449
+ inputTokens: 0,
450
+ cachedInputTokens: 0,
451
+ outputTokens: 0,
452
+ reasoningOutputTokens: 0,
453
+ totalTokens: 0,
454
+ modelTurns,
455
+ apiCost: modelTurns ? null : (pricing ? 0 : null),
456
+ }
457
+ }
458
+ const inputTokens = Number(raw.input_tokens ?? raw.inputTokens ?? 0) || 0
459
+ const outputTokens = Number(raw.output_tokens ?? raw.outputTokens ?? 0) || 0
460
+ return {
461
+ inputTokens,
462
+ cachedInputTokens: Number(
463
+ raw.cached_input_tokens ?? raw.cache_read_input_tokens
464
+ ?? raw.cachedInputTokens ?? 0,
465
+ ) || 0,
466
+ outputTokens,
467
+ reasoningOutputTokens: Number(
468
+ raw.reasoning_output_tokens ?? raw.reasoningOutputTokens ?? 0,
469
+ ) || 0,
470
+ totalTokens: Number(
471
+ raw.total_tokens ?? raw.totalTokens ?? inputTokens + outputTokens,
472
+ ) || 0,
473
+ modelTurns,
474
+ // Exact cost can depend on per-request long-context thresholds. The compact
475
+ // per-turn sidecar is authoritative whenever pricing is configured.
476
+ apiCost: null,
477
+ }
478
+ }
479
+
480
+ function listBenchmarkIds(runsRoot) {
481
+ if (!fs.existsSync(runsRoot)) return []
482
+ const ids = []
483
+ const visit = (directory, relativeDirectory, depth) => {
484
+ if (depth > 4) return
485
+ if (fs.existsSync(path.join(directory, 'batch-state.json'))) {
486
+ ids.push(relativeDirectory)
487
+ return
488
+ }
489
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
490
+ if (!entry.isDirectory()) continue
491
+ const relativePath = relativeDirectory
492
+ ? `${relativeDirectory}/${entry.name}`
493
+ : entry.name
494
+ visit(path.join(directory, entry.name), relativePath, depth + 1)
495
+ }
496
+ }
497
+ visit(runsRoot, '', 0)
498
+ return ids.filter(Boolean)
499
+ }
500
+
501
+ function benchmarkDetail(
502
+ runsRoot,
503
+ benchmarkId,
504
+ { preferAggregateUsage = false } = {},
505
+ ) {
506
+ const directory = safeRunDirectory(runsRoot, benchmarkId)
507
+ if (!directory || !fs.existsSync(directory)) return null
508
+ const state = readJson(path.join(directory, 'batch-state.json'))
509
+ if (!state || !state.rollouts || typeof state.rollouts !== 'object') return null
510
+ const manifest = manifestByTask()
511
+ const pricing = state.pricing || null
512
+ const tasks = Object.entries(state.rollouts).map(([rolloutId, row]) => {
513
+ const runId = `${benchmarkId}/rollouts/${rolloutId}`
514
+ const runDirectory = path.join(directory, 'rollouts', rolloutId)
515
+ const hasRun = fs.existsSync(runDirectory)
516
+ const summary = hasRun ? runSummary(runsRoot, runId) : null
517
+ const runState = hasRun ? readJson(path.join(runDirectory, 'run-state.json')) : null
518
+ const result = hasRun ? readJson(path.join(runDirectory, 'result.json')) : null
519
+ const evaluationStatus = hasRun ? readJson(path.join(
520
+ runDirectory, 'evaluation', 'status.json',
521
+ )) : null
522
+ const scores = hasRun
523
+ ? readRunScores(runDirectory)
524
+ : { vision: null, deterministic: null, displayed: null }
525
+ const score = scores.displayed
526
+ const usage = hasRun
527
+ ? runUsage(runDirectory, pricing, runState, result, {
528
+ preferAggregate: preferAggregateUsage,
529
+ })
530
+ : usageSummary([], pricing)
531
+ const taskId = row.task_id || summary?.taskId || state.task_id || 'unknown'
532
+ const task = manifest.get(taskId)
533
+ // Batch state covers the complete sandbox lifecycle, including automatic
534
+ // evaluation. The pilot's local result can say completed while its wrapper
535
+ // is still judging or has failed evaluation.
536
+ const rowStatus = row.state || 'queued'
537
+ const summaryIsTerminal = TERMINAL_EXECUTION_STATES.has(summary?.status)
538
+ const status = !hasRun && rowStatus === 'running'
539
+ ? 'starting'
540
+ : summaryIsTerminal
541
+ ? summary.status
542
+ : summary?.status || rowStatus || 'queued'
543
+ const artifactBytes = summary?.artifactBytes ?? result?.artifact_bytes ?? 0
544
+ const executionCanCarryFailure = [
545
+ 'model_failed', 'infrastructure_failed', 'artifact_missing', 'interrupted',
546
+ ].includes(status)
547
+ return {
548
+ rolloutId,
549
+ runId,
550
+ taskId,
551
+ level: task?.level || 'unknown',
552
+ drawingType: task?.drawing_type || '2d',
553
+ split: task?.split || 'unknown',
554
+ displayName: row.display_name || rolloutId,
555
+ modelId: row.model_id || summary?.modelId || 'unknown',
556
+ provider: row.provider || summary?.provider || 'unknown',
557
+ status,
558
+ stopReason: summary?.stopReason || result?.stop_reason || null,
559
+ failure: executionCanCarryFailure
560
+ ? (summary?.failure || result?.failure || row.failure || null)
561
+ : (result?.failure || null),
562
+ acceptedActions: summary?.acceptedActions ?? result?.accepted_actions ?? 0,
563
+ invalidActions: summary?.invalidActions ?? result?.invalid_actions ?? 0,
564
+ elapsedSeconds: summary?.elapsedSeconds ?? result?.elapsed_s ?? null,
565
+ setupElapsedSeconds: result?.setup_elapsed_s ?? null,
566
+ rolloutElapsedSeconds: result?.rollout_elapsed_s ?? null,
567
+ artifactBytes,
568
+ score: score?.score ?? null,
569
+ valid: score?.valid ?? null,
570
+ scoreFailure: score?.failure_reason ?? null,
571
+ scoreMethod: score?.scoring_method ?? null,
572
+ scoreSource: score?.score_source ?? null,
573
+ verdict: score?.verdict ?? null,
574
+ targetAcquired: score?.target_acquired ?? null,
575
+ judgeConfidence: score?.confidence ?? null,
576
+ needsHumanReview: score?.needs_human_review ?? false,
577
+ visionScore: scoreSummary(scores.vision),
578
+ deterministicScore: scoreSummary(scores.deterministic),
579
+ geometrySimilarity: score?.geometry_similarity ?? null,
580
+ dimensionSimilarity: score?.dimension_similarity ?? null,
581
+ annotationSimilarity: score?.annotation_similarity ?? null,
582
+ visualSimilarity: score?.visual_similarity ?? null,
583
+ evaluationState: summary?.evaluationState || evaluationState({
584
+ status, artifactBytes, score, evaluationStatus,
585
+ }),
586
+ evaluationStage: summary?.evaluationStage || evaluationStatus?.stage || null,
587
+ evaluationError: summary?.evaluationError || evaluationStatus?.error || null,
588
+ startedAt: row.started_at || null,
589
+ finishedAt: row.finished_at || null,
590
+ updatedAt: summary?.updatedAt || row.finished_at || row.started_at || state.updated_at,
591
+ usage,
592
+ }
593
+ })
594
+ const numericScores = tasks
595
+ .map((task) => task.score)
596
+ .filter((score) => Number.isFinite(score))
597
+ const visionScores = tasks
598
+ .map((task) => task.visionScore?.score)
599
+ .filter((score) => Number.isFinite(score))
600
+ const deterministicScores = tasks
601
+ .map((task) => task.deterministicScore?.score)
602
+ .filter((score) => Number.isFinite(score))
603
+ const levels = {}
604
+ const drawingTypes = {}
605
+ const evaluationStates = {}
606
+ const executionStates = {}
607
+ const verdicts = {}
608
+ for (const task of tasks) {
609
+ const level = task.level
610
+ levels[level] ||= { total: 0, completed: 0, scored: 0, scoreSum: 0, cost: 0 }
611
+ levels[level].total += 1
612
+ if (task.status === 'completed') levels[level].completed += 1
613
+ if (Number.isFinite(task.score)) {
614
+ levels[level].scored += 1
615
+ levels[level].scoreSum += task.score
616
+ }
617
+ levels[level].cost += task.usage.apiCost || 0
618
+ const drawingType = task.drawingType
619
+ drawingTypes[drawingType] ||= { total: 0, scored: 0, scoreSum: 0 }
620
+ drawingTypes[drawingType].total += 1
621
+ if (Number.isFinite(task.score)) {
622
+ drawingTypes[drawingType].scored += 1
623
+ drawingTypes[drawingType].scoreSum += task.score
624
+ }
625
+ evaluationStates[task.evaluationState] = (
626
+ evaluationStates[task.evaluationState] || 0
627
+ ) + 1
628
+ executionStates[task.status] = (executionStates[task.status] || 0) + 1
629
+ if (task.verdict) verdicts[task.verdict] = (verdicts[task.verdict] || 0) + 1
630
+ }
631
+ for (const level of Object.values(levels)) {
632
+ level.meanScore = level.scored ? level.scoreSum / level.scored : null
633
+ delete level.scoreSum
634
+ }
635
+ for (const drawingType of Object.values(drawingTypes)) {
636
+ drawingType.meanScore = drawingType.scored
637
+ ? drawingType.scoreSum / drawingType.scored
638
+ : null
639
+ delete drawingType.scoreSum
640
+ }
641
+ const aggregateUsage = tasks.reduce((aggregate, task) => {
642
+ for (const key of [
643
+ 'inputTokens', 'cachedInputTokens', 'outputTokens',
644
+ 'reasoningOutputTokens', 'totalTokens', 'modelTurns',
645
+ ]) aggregate[key] += task.usage[key]
646
+ if (aggregate.apiCost != null) {
647
+ if (task.usage.apiCost == null && task.usage.modelTurns > 0) {
648
+ aggregate.apiCost = null
649
+ } else {
650
+ aggregate.apiCost += task.usage.apiCost || 0
651
+ }
652
+ }
653
+ return aggregate
654
+ }, {
655
+ inputTokens: 0,
656
+ cachedInputTokens: 0,
657
+ outputTokens: 0,
658
+ reasoningOutputTokens: 0,
659
+ totalTokens: 0,
660
+ modelTurns: 0,
661
+ apiCost: pricing ? 0 : null,
662
+ })
663
+ const aggregate = {
664
+ totalTasks: tasks.length,
665
+ queuedTasks: tasks.filter((task) => task.status === 'queued').length,
666
+ activeTasks: tasks.filter((task) => ACTIVE_STATES.has(task.status)).length,
667
+ settledTasks: tasks.filter((task) => TERMINAL_EXECUTION_STATES.has(task.status)).length,
668
+ completedTasks: tasks.filter((task) => task.status === 'completed').length,
669
+ recoveredTasks: tasks.filter((task) => task.status === 'recovered').length,
670
+ budgetExhaustedTasks: tasks.filter((task) => task.status === 'budget_exhausted').length,
671
+ failedTasks: tasks.filter((task) => task.status === 'model_failed').length,
672
+ infrastructureFailedTasks: tasks.filter((task) => task.status === 'infrastructure_failed').length,
673
+ artifactMissingTasks: tasks.filter((task) => task.status === 'artifact_missing').length,
674
+ artifactTasks: tasks.filter((task) => task.artifactBytes > 0).length,
675
+ scoredTasks: numericScores.length,
676
+ meanScore: numericScores.length
677
+ ? numericScores.reduce((sum, score) => sum + score, 0) / numericScores.length
678
+ : null,
679
+ visionScoredTasks: visionScores.length,
680
+ visionMeanScore: visionScores.length
681
+ ? visionScores.reduce((sum, score) => sum + score, 0) / visionScores.length
682
+ : null,
683
+ deterministicScoredTasks: deterministicScores.length,
684
+ deterministicMeanScore: deterministicScores.length
685
+ ? deterministicScores.reduce((sum, score) => sum + score, 0)
686
+ / deterministicScores.length
687
+ : null,
688
+ acceptedActions: tasks.reduce((sum, task) => sum + task.acceptedActions, 0),
689
+ invalidActions: tasks.reduce((sum, task) => sum + task.invalidActions, 0),
690
+ aggregateElapsedSeconds: tasks.reduce(
691
+ (sum, task) => sum + (task.elapsedSeconds || 0), 0,
692
+ ),
693
+ usage: aggregateUsage,
694
+ levels,
695
+ drawingTypes,
696
+ evaluationStates,
697
+ executionStates,
698
+ verdicts,
699
+ }
700
+ const models = [...new Set(tasks.map((task) => task.modelId))].sort()
701
+ const providers = [...new Set(tasks.map((task) => task.provider))].sort()
702
+ const reportedStatus = state.state || 'running'
703
+ const updatedAtMs = Date.parse(state.updated_at || '')
704
+ const controllerIsStale = ['starting', 'running'].includes(reportedStatus)
705
+ && aggregate.activeTasks === 0
706
+ && aggregate.queuedTasks > 0
707
+ && !aggregate.evaluationStates.evaluating
708
+ && Number.isFinite(updatedAtMs)
709
+ && Date.now() - updatedAtMs > 5 * 60 * 1000
710
+ const hasExecutionIssues = aggregate.failedTasks > 0
711
+ || aggregate.infrastructureFailedTasks > 0
712
+ || aggregate.artifactMissingTasks > 0
713
+ || aggregate.evaluationStates.evaluation_failed > 0
714
+ || aggregate.evaluationStates.evaluation_interrupted > 0
715
+ const derivedStatus = aggregate.activeTasks > 0
716
+ ? 'running'
717
+ : aggregate.queuedTasks > 0
718
+ ? (controllerIsStale ? 'interrupted' : reportedStatus)
719
+ : aggregate.settledTasks === aggregate.totalTasks
720
+ ? (hasExecutionIssues ? 'completed_with_issues' : 'completed')
721
+ : reportedStatus
722
+ return {
723
+ id: benchmarkId,
724
+ batchId: state.batch_id || null,
725
+ name: state.benchmark_name || state.batch_id || benchmarkId,
726
+ status: derivedStatus,
727
+ modelId: models.length === 1 ? models[0] : `${models.length} models`,
728
+ provider: providers.length === 1 ? providers[0] : `${providers.length} providers`,
729
+ models,
730
+ providers,
731
+ startedAt: state.started_at || null,
732
+ finishedAt: state.finished_at || null,
733
+ updatedAt: state.updated_at || null,
734
+ maxConcurrency: state.max_concurrency || state.preflight?.quota?.requested_instances || null,
735
+ pricing,
736
+ aggregate,
737
+ tasks,
738
+ }
739
+ }
740
+
741
+ function listBenchmarks(runsRoot) {
742
+ return listBenchmarkIds(runsRoot)
743
+ // Registry refreshes must never synchronously parse every model-turn file
744
+ // across every historical run. Live run-state aggregates are sufficient
745
+ // for registry totals; exact per-turn cost remains available in traces.
746
+ .map((id) => benchmarkDetail(runsRoot, id, { preferAggregateUsage: true }))
747
+ .filter(Boolean)
748
+ .map((benchmark) => ({
749
+ id: benchmark.id,
750
+ batchId: benchmark.batchId,
751
+ name: benchmark.name,
752
+ status: benchmark.status,
753
+ modelId: benchmark.modelId,
754
+ provider: benchmark.provider,
755
+ models: benchmark.models,
756
+ providers: benchmark.providers,
757
+ startedAt: benchmark.startedAt,
758
+ updatedAt: benchmark.updatedAt,
759
+ maxConcurrency: benchmark.maxConcurrency,
760
+ aggregate: benchmark.aggregate,
761
+ }))
762
+ .sort((left, right) => String(right.updatedAt).localeCompare(String(left.updatedAt)))
763
+ }
764
+
765
+ function traceDetail(runsRoot, runId) {
766
+ const runDirectory = safeRunDirectory(runsRoot, runId)
767
+ if (!runDirectory || !fs.existsSync(runDirectory)) return null
768
+ const summary = runSummary(runsRoot, runId)
769
+ const frameDirectory = path.join(runDirectory, 'frames')
770
+ const localFrames = fs.existsSync(frameDirectory)
771
+ ? fs.readdirSync(frameDirectory).filter((name) => /^frame_\d+\.png$/.test(name)).sort()
772
+ : []
773
+ const turns = traceTurns(runDirectory)
774
+ const state = readJson(path.join(runDirectory, 'run-state.json'))
775
+ const frameNames = new Set(localFrames)
776
+ const frameSha256 = new Map()
777
+ let referenceSha256 = null
778
+ for (const turn of turns) {
779
+ for (const artifact of modelInputArtifacts(turn?.model_input)) {
780
+ if (String(artifact.artifact_path || '') === 'reference.png') {
781
+ referenceSha256 ||= normalizeSha256(artifact.sha256)
782
+ }
783
+ const match = String(artifact.artifact_path || '').match(/^frames\/(frame_\d+\.png)$/)
784
+ if (match) {
785
+ frameNames.add(match[1])
786
+ const sha256 = normalizeSha256(artifact.sha256)
787
+ if (sha256) frameSha256.set(match[1], sha256)
788
+ }
789
+ }
790
+ }
791
+ const acceptedActions = Number(state?.accepted_actions)
792
+ if (Number.isInteger(acceptedActions) && acceptedActions >= 0) {
793
+ frameNames.add(`frame_${String(acceptedActions).padStart(4, '0')}.png`)
794
+ }
795
+ const frames = [...frameNames].sort()
796
+ const fullTracePath = path.join(runDirectory, 'model_turns.jsonl')
797
+ const scores = readRunScores(runDirectory)
798
+ const score = scores.displayed
799
+ const hasTrustedRender = Boolean(
800
+ fs.existsSync(path.join(runDirectory, 'evaluation', 'render.png'))
801
+ || fs.existsSync(path.join(runDirectory, 'evaluation', 'candidate-render.png'))
802
+ || score?.score_source === 'vision_judge',
803
+ )
804
+ const traceIndexed = fs.existsSync(traceSummaryPath(runDirectory))
805
+ || !fs.existsSync(fullTracePath)
806
+ || fs.statSync(fullTracePath).size <= MAX_INLINE_LEGACY_TRACE_BYTES
807
+ return {
808
+ summary,
809
+ state,
810
+ result: readJson(path.join(runDirectory, 'result.json')),
811
+ score,
812
+ scores: {
813
+ vision: scores.vision,
814
+ deterministic: scores.deterministic,
815
+ },
816
+ turns,
817
+ receipts: readJsonl(path.join(runDirectory, 'input_receipts.jsonl')),
818
+ traceIndexed,
819
+ traceNotice: traceIndexed
820
+ ? null
821
+ : 'This legacy trace needs a compact viewer index before its turns can be displayed.',
822
+ frames: frames.map((name) => {
823
+ const sha256 = frameSha256.get(name) || null
824
+ const version = sha256 ? `?sha256=${encodeURIComponent(sha256)}` : ''
825
+ return {
826
+ name,
827
+ sha256,
828
+ url: `/api/runs/${encodeURIComponent(runId)}/file/frames/${encodeURIComponent(name)}${version}`,
829
+ }
830
+ }),
831
+ // A rollout always publishes its exact model-visible reference to cloud
832
+ // storage. Keep the URL run-scoped so lightweight dashboard deployments do
833
+ // not need a second copy of the private task corpus. The file route falls
834
+ // back to the local manifest image for local-only development runs.
835
+ referenceUrl: `/api/runs/${encodeURIComponent(runId)}/file/reference.png${
836
+ referenceSha256 ? `?sha256=${encodeURIComponent(referenceSha256)}` : ''
837
+ }`,
838
+ artifacts: {
839
+ dwg: fs.existsSync(path.join(runDirectory, 'attempt.dwg'))
840
+ ? `/api/runs/${encodeURIComponent(runId)}/file/attempt.dwg`
841
+ : null,
842
+ candidateRender: hasTrustedRender
843
+ ? `/api/runs/${encodeURIComponent(runId)}/file/evaluation/render.png`
844
+ : null,
845
+ diffRender: fs.existsSync(path.join(runDirectory, 'evaluation', 'diff.png'))
846
+ ? `/api/runs/${encodeURIComponent(runId)}/file/evaluation/diff.png`
847
+ : null,
848
+ },
849
+ }
850
+ }
851
+
852
+ export function createTraceApi(options = {}) {
853
+ const runsRoot = path.resolve(options.runsRoot || process.env.AUTOCAD_BENCH_RUNS_ROOT || DEFAULT_RUNS_ROOT)
854
+ const manifest = manifestByTask()
855
+ async function refreshCloudMetadata(loader, request) {
856
+ if (typeof loader !== 'function') return
857
+ try {
858
+ await loader(request)
859
+ } catch {
860
+ // Cloud mirroring is best effort. A transient AWS/auth failure must not
861
+ // hide an already-synchronized local trace or take down the registry.
862
+ }
863
+ }
864
+ return async function handleTraceApi(request, response) {
865
+ const url = new URL(request.url || '/', 'http://127.0.0.1')
866
+ if (!url.pathname.startsWith('/api/')) return false
867
+ if (request.method !== 'GET') {
868
+ sendJson(response, 405, { error: 'method_not_allowed' })
869
+ return true
870
+ }
871
+ if (url.pathname === '/api/health') {
872
+ sendJson(response, 200, { status: 'ok', runsRoot })
873
+ return true
874
+ }
875
+ if (url.pathname === '/api/runs') {
876
+ sendJson(response, 200, { runs: listRuns(runsRoot) })
877
+ return true
878
+ }
879
+ if (url.pathname === '/api/benchmarks') {
880
+ sendJson(response, 200, { benchmarks: listBenchmarks(runsRoot) })
881
+ return true
882
+ }
883
+ const benchmarkMatch = url.pathname.match(/^\/api\/benchmarks\/([^/]+)$/)
884
+ if (benchmarkMatch) {
885
+ const benchmarkId = decodeURIComponent(benchmarkMatch[1])
886
+ const benchmarkDirectory = safeRunDirectory(runsRoot, benchmarkId)
887
+ if (benchmarkDirectory) {
888
+ await refreshCloudMetadata(options.cloudBenchmarkLoader, {
889
+ benchmarkId,
890
+ benchmarkDirectory,
891
+ })
892
+ }
893
+ const detail = benchmarkDetail(runsRoot, benchmarkId)
894
+ sendJson(response, detail ? 200 : 404, detail || { error: 'benchmark_not_found' })
895
+ return true
896
+ }
897
+ const runFileMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/file\/(.+)$/)
898
+ if (runFileMatch) {
899
+ const runId = decodeURIComponent(runFileMatch[1])
900
+ const relativePath = decodeURIComponent(runFileMatch[2])
901
+ const runDirectory = safeRunDirectory(runsRoot, runId)
902
+ const artifact = runDirectory
903
+ ? safeArtifact(runDirectory, relativePath)
904
+ : null
905
+ const requestedSha256 = normalizeSha256(url.searchParams.get('sha256'))
906
+ const artifactExists = Boolean(
907
+ artifact && fs.existsSync(artifact) && fs.statSync(artifact).isFile(),
908
+ )
909
+ const artifactMatches = artifactExists && (
910
+ !requestedSha256 || fileSha256(artifact) === requestedSha256
911
+ )
912
+ if (artifactMatches) {
913
+ sendFile(response, artifact, {
914
+ immutable: Boolean(requestedSha256) || relativePath.toLowerCase().endsWith('.png'),
915
+ })
916
+ return true
917
+ }
918
+ if (relativePath === 'reference.png' && runDirectory) {
919
+ const taskId = readJson(path.join(runDirectory, 'run-state.json'))?.task_id
920
+ || readJson(path.join(runDirectory, 'result.json'))?.task_id
921
+ const entry = manifest.get(taskId)
922
+ const manifestImage = entry ? path.resolve(BENCHMARK_DATA_ROOT, entry.image_path) : null
923
+ if (
924
+ manifestImage
925
+ && manifestImage.startsWith(BENCHMARK_DATA_ROOT + path.sep)
926
+ && fs.existsSync(manifestImage)
927
+ && fs.statSync(manifestImage).isFile()
928
+ && (!requestedSha256 || fileSha256(manifestImage) === requestedSha256)
929
+ ) {
930
+ sendFile(response, manifestImage, { immutable: true })
931
+ return true
932
+ }
933
+ }
934
+ if (relativePath === 'evaluation/render.png' && runDirectory) {
935
+ const legacyRender = safeArtifact(runDirectory, 'evaluation/candidate-render.png')
936
+ if (legacyRender && fs.existsSync(legacyRender) && fs.statSync(legacyRender).isFile()) {
937
+ sendFile(response, legacyRender, { immutable: true })
938
+ return true
939
+ }
940
+ }
941
+ if (
942
+ artifact
943
+ && !artifactExists
944
+ && typeof options.cloudArtifactUrl === 'function'
945
+ ) {
946
+ try {
947
+ const location = await options.cloudArtifactUrl({ runId, relativePath })
948
+ if (location) {
949
+ sendPrivateRedirect(response, location)
950
+ return true
951
+ }
952
+ } catch {
953
+ // Fall through to the local caching proxy if signing is unavailable.
954
+ }
955
+ }
956
+ if (
957
+ artifact
958
+ && !artifactMatches
959
+ && typeof options.cloudArtifactLoader === 'function'
960
+ ) {
961
+ try {
962
+ await options.cloudArtifactLoader({
963
+ runId,
964
+ relativePath,
965
+ artifact,
966
+ expectedSha256: requestedSha256,
967
+ force: artifactExists,
968
+ })
969
+ } catch {
970
+ // The normal 404 below is the stable contract when the cloud mirror
971
+ // also lacks an artifact or AWS is temporarily unavailable.
972
+ }
973
+ }
974
+ const refreshedMatches = Boolean(
975
+ artifact
976
+ && fs.existsSync(artifact)
977
+ && fs.statSync(artifact).isFile()
978
+ && (!requestedSha256 || fileSha256(artifact) === requestedSha256),
979
+ )
980
+ let servedArtifact = refreshedMatches ? artifact : null
981
+ if (!servedArtifact && relativePath === 'evaluation/render.png' && runDirectory) {
982
+ const fallbackRelativePath = 'evaluation/candidate-render.png'
983
+ const fallbackArtifact = safeArtifact(runDirectory, fallbackRelativePath)
984
+ if (
985
+ fallbackArtifact
986
+ && !fs.existsSync(fallbackArtifact)
987
+ && typeof options.cloudArtifactLoader === 'function'
988
+ ) {
989
+ try {
990
+ await options.cloudArtifactLoader({
991
+ runId,
992
+ relativePath: fallbackRelativePath,
993
+ artifact: fallbackArtifact,
994
+ })
995
+ } catch {
996
+ // Older evaluator runs may not have either render filename.
997
+ }
998
+ }
999
+ if (
1000
+ fallbackArtifact
1001
+ && fs.existsSync(fallbackArtifact)
1002
+ && fs.statSync(fallbackArtifact).isFile()
1003
+ ) {
1004
+ servedArtifact = fallbackArtifact
1005
+ }
1006
+ }
1007
+ if (!servedArtifact && relativePath === 'reference.png') {
1008
+ const taskId = runDirectory
1009
+ ? readJson(path.join(runDirectory, 'run-state.json'))?.task_id
1010
+ || readJson(path.join(runDirectory, 'result.json'))?.task_id
1011
+ : null
1012
+ const entry = manifest.get(taskId)
1013
+ const manifestImage = entry ? path.resolve(BENCHMARK_DATA_ROOT, entry.image_path) : null
1014
+ if (
1015
+ manifestImage
1016
+ && manifestImage.startsWith(BENCHMARK_DATA_ROOT + path.sep)
1017
+ && fs.existsSync(manifestImage)
1018
+ && fs.statSync(manifestImage).isFile()
1019
+ ) {
1020
+ servedArtifact = manifestImage
1021
+ }
1022
+ }
1023
+ sendFile(response, servedArtifact, {
1024
+ // Run-scoped PNGs are append-only/final artifacts. Browser caching
1025
+ // makes reopening a rollout instant and avoids repeated S3 downloads.
1026
+ immutable: Boolean(requestedSha256) || relativePath.toLowerCase().endsWith('.png'),
1027
+ })
1028
+ return true
1029
+ }
1030
+ const runMatch = url.pathname.match(/^\/api\/runs\/([^/]+)$/)
1031
+ if (runMatch) {
1032
+ const runId = decodeURIComponent(runMatch[1])
1033
+ const runDirectory = safeRunDirectory(runsRoot, runId)
1034
+ if (runDirectory) {
1035
+ // Live cloud-controller runs may be present in batch-state before their
1036
+ // rollout directory has ever been mirrored locally. Bootstrap and
1037
+ // refresh the small trace index here; frame PNGs remain lazy-loaded by
1038
+ // the /file route below.
1039
+ await refreshCloudMetadata(options.cloudTraceLoader, {
1040
+ runId,
1041
+ runDirectory,
1042
+ })
1043
+ }
1044
+ const detail = traceDetail(runsRoot, runId)
1045
+ sendJson(response, detail ? 200 : 404, detail || { error: 'run_not_found' })
1046
+ return true
1047
+ }
1048
+ const referenceMatch = url.pathname.match(/^\/api\/reference\/([^/]+)$/)
1049
+ if (referenceMatch) {
1050
+ const taskId = decodeURIComponent(referenceMatch[1])
1051
+ const entry = manifest.get(taskId)
1052
+ const imagePath = entry ? path.resolve(BENCHMARK_DATA_ROOT, entry.image_path) : null
1053
+ const safeImage = imagePath && imagePath.startsWith(BENCHMARK_DATA_ROOT + path.sep) ? imagePath : null
1054
+ sendFile(response, safeImage)
1055
+ return true
1056
+ }
1057
+ sendJson(response, 404, { error: 'not_found' })
1058
+ return true
1059
+ }
1060
+ }
1061
+
1062
+ export const _test = {
1063
+ readJsonl,
1064
+ safeRunDirectory,
1065
+ safeArtifact,
1066
+ runSummary,
1067
+ traceDetail,
1068
+ traceTurns,
1069
+ listRuns,
1070
+ usageSummary,
1071
+ runUsage,
1072
+ compactTurn,
1073
+ normalizeSha256,
1074
+ fileSha256,
1075
+ benchmarkDetail,
1076
+ listBenchmarks,
1077
+ executionState,
1078
+ readRunScores,
1079
+ scoreSummary,
1080
+ }
apps/dashboard/tsconfig.app.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "strict": true,
8
+ "jsx": "preserve",
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "esModuleInterop": true,
12
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
13
+ "types": ["vite/client"],
14
+ "skipLibCheck": true
15
+ },
16
+ "include": ["src/**/*.ts", "src/**/*.vue"]
17
+ }
apps/dashboard/tsconfig.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
apps/dashboard/tsconfig.node.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "target": "ES2022",
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "allowJs": true,
8
+ "checkJs": false,
9
+ "outDir": ".tsbuild/node",
10
+ "rootDir": ".",
11
+ "types": ["node"],
12
+ "skipLibCheck": true
13
+ },
14
+ "include": ["vite.config.ts", "trace-api.mjs", "server.mjs"]
15
+ }
apps/dashboard/vite.config.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig, type Plugin } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+ import { createTraceApi } from './trace-api.mjs'
4
+
5
+ function traceApiPlugin(): Plugin {
6
+ const handle = createTraceApi()
7
+ return {
8
+ name: 'autocad-trace-api',
9
+ configureServer(server) {
10
+ server.middlewares.use(async (request, response, next) => {
11
+ if (await handle(request, response)) return
12
+ next()
13
+ })
14
+ },
15
+ }
16
+ }
17
+
18
+ export default defineConfig({
19
+ plugins: [vue(), traceApiPlugin()],
20
+ server: { port: 5173 },
21
+ build: { target: 'es2022' },
22
+ })
apps/results-dashboard/.env.example ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ AUTOCAD_RESULTS_BUCKET=your-autocad-results-bucket
2
+ # AWS_PROFILE=my-profile
3
+ AWS_REGION=us-east-1
4
+ HOST=127.0.0.1
5
+ PORT=4273
6
+ AUTOCAD_RESULTS_SIGNED_URL_TTL_SECONDS=3600
apps/results-dashboard/README.md ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AutoCAD benchmark results dashboard
2
+
3
+ Read-only dashboard for the finalized, model-level aggregates in the AutoCAD benchmark results bucket. It discovers aggregate manifests, compares models and categories, and exposes each canonical rollout's trace, receipts, renders, DWG, and full screenshot sequence.
4
+
5
+ Media is served with short-lived S3 URLs and immutable browser cache headers. The dashboard server only signs requests; image bytes travel directly from S3 to the browser.
6
+
7
+ ```bash
8
+ npm ci
9
+ npm test
10
+ npm run build
11
+ npm run preview
12
+ ```
13
+
14
+ The local viewer listens on `http://127.0.0.1:4273`.
15
+ `AUTOCAD_RESULTS_BUCKET` is required and must name a bucket you own.
16
+ `AWS_PROFILE` and `AWS_REGION` are optional; the AWS SDK uses its standard
17
+ credential and region chains when they are omitted. See `.env.example`.
apps/results-dashboard/index.html ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="dark" />
7
+ <meta name="theme-color" content="#0a0d0f" />
8
+ <title>AutoCAD Bench Results</title>
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ <script type="module" src="/src/main.ts"></script>
13
+ </body>
14
+ </html>
apps/results-dashboard/package-lock.json ADDED
@@ -0,0 +1,1910 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "autocad-bench-results-dashboard",
3
+ "version": "0.1.0",
4
+ "lockfileVersion": 3,
5
+ "requires": true,
6
+ "packages": {
7
+ "": {
8
+ "name": "autocad-bench-results-dashboard",
9
+ "version": "0.1.0",
10
+ "dependencies": {
11
+ "@aws-sdk/client-s3": "3.1090.0",
12
+ "@aws-sdk/credential-providers": "3.1090.0",
13
+ "@aws-sdk/s3-request-presigner": "3.1090.0",
14
+ "vue": "3.5.40"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "20.19.43",
18
+ "@vitejs/plugin-vue": "5.2.4",
19
+ "typescript": "5.9.3",
20
+ "vite": "6.4.3",
21
+ "vue-tsc": "3.3.7"
22
+ }
23
+ },
24
+ "node_modules/@aws-sdk/checksums": {
25
+ "version": "3.1000.19",
26
+ "resolved": "https://registry.npmjs.org/@aws-sdk/checksums/-/checksums-3.1000.19.tgz",
27
+ "integrity": "sha512-Hc4N100RdkuWshKBnhPzmpdftfi9mCLz+OHFELHM1QIgMH4QRUUWyWgfiebta/YX2Bd62wTcm3EqAP8TeXv0gA==",
28
+ "license": "Apache-2.0",
29
+ "dependencies": {
30
+ "@aws-sdk/core": "^3.976.0",
31
+ "@aws-sdk/types": "^3.974.2",
32
+ "@smithy/core": "^3.29.4",
33
+ "@smithy/types": "^4.16.1",
34
+ "tslib": "^2.6.2"
35
+ },
36
+ "engines": {
37
+ "node": ">=20.0.0"
38
+ }
39
+ },
40
+ "node_modules/@aws-sdk/client-s3": {
41
+ "version": "3.1090.0",
42
+ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.1090.0.tgz",
43
+ "integrity": "sha512-R6GX9cd1jljwzZ8xFmgAI/hHCuX1MobIKBdsymv7WL9SENvO9Vgz9KOR6avTnu0Ao+w1LmxnTe+jqmZXEn7Q/Q==",
44
+ "license": "Apache-2.0",
45
+ "dependencies": {
46
+ "@aws-sdk/checksums": "^3.1000.18",
47
+ "@aws-sdk/core": "^3.975.3",
48
+ "@aws-sdk/credential-provider-node": "^3.972.70",
49
+ "@aws-sdk/middleware-sdk-s3": "^3.972.64",
50
+ "@aws-sdk/signature-v4-multi-region": "^3.996.41",
51
+ "@aws-sdk/types": "^3.974.2",
52
+ "@smithy/core": "^3.29.4",
53
+ "@smithy/fetch-http-handler": "^5.6.6",
54
+ "@smithy/node-http-handler": "^4.9.6",
55
+ "@smithy/types": "^4.16.1",
56
+ "tslib": "^2.6.2"
57
+ },
58
+ "engines": {
59
+ "node": ">=20.0.0"
60
+ }
61
+ },
62
+ "node_modules/@aws-sdk/core": {
63
+ "version": "3.976.0",
64
+ "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.976.0.tgz",
65
+ "integrity": "sha512-0cjRaEdlVoOrsNb9pP5q1Syyc8pXw5xSj2Np2ryReRTr9FppIIRVSdZK4lbnfmc2Hvgux/xBOUU6baB7z8//uA==",
66
+ "license": "Apache-2.0",
67
+ "dependencies": {
68
+ "@aws-sdk/types": "^3.974.2",
69
+ "@aws-sdk/xml-builder": "^3.972.36",
70
+ "@aws/lambda-invoke-store": "^0.3.0",
71
+ "@smithy/core": "^3.29.4",
72
+ "@smithy/signature-v4": "^5.6.5",
73
+ "@smithy/types": "^4.16.1",
74
+ "bowser": "^2.11.0",
75
+ "tslib": "^2.6.2"
76
+ },
77
+ "engines": {
78
+ "node": ">=20.0.0"
79
+ }
80
+ },
81
+ "node_modules/@aws-sdk/credential-provider-cognito-identity": {
82
+ "version": "3.972.59",
83
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.972.59.tgz",
84
+ "integrity": "sha512-iWPfye2ZOCmAHKmN1EwAyeHZdZxZymctAnEOD+7jzwqc5gZlK1lwG1lzGVtpH0+d/NnyrK670ycqBNKD4zUGZA==",
85
+ "license": "Apache-2.0",
86
+ "dependencies": {
87
+ "@aws-sdk/nested-clients": "^3.997.34",
88
+ "@aws-sdk/types": "^3.974.2",
89
+ "@smithy/core": "^3.29.4",
90
+ "@smithy/types": "^4.16.1",
91
+ "tslib": "^2.6.2"
92
+ },
93
+ "engines": {
94
+ "node": ">=20.0.0"
95
+ }
96
+ },
97
+ "node_modules/@aws-sdk/credential-provider-env": {
98
+ "version": "3.972.60",
99
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.60.tgz",
100
+ "integrity": "sha512-BAkxdoe7tpDDqCghGpuOeHQRbm/2znVvOQm0AvpQbA2tbfMN46doN4zx65fv85ImP3KADwc2zQPmbrlI9MPfMg==",
101
+ "license": "Apache-2.0",
102
+ "dependencies": {
103
+ "@aws-sdk/core": "^3.976.0",
104
+ "@aws-sdk/types": "^3.974.2",
105
+ "@smithy/core": "^3.29.4",
106
+ "@smithy/types": "^4.16.1",
107
+ "tslib": "^2.6.2"
108
+ },
109
+ "engines": {
110
+ "node": ">=20.0.0"
111
+ }
112
+ },
113
+ "node_modules/@aws-sdk/credential-provider-http": {
114
+ "version": "3.972.62",
115
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.62.tgz",
116
+ "integrity": "sha512-g/0fGqKTb9xpKdd9AtpmV5Eo3DFKbnkpA2+w0peISSlu7NfAoWOuYBFxsu+yWBtxU89ka55ezoZBCbFaS8pjYQ==",
117
+ "license": "Apache-2.0",
118
+ "dependencies": {
119
+ "@aws-sdk/core": "^3.976.0",
120
+ "@aws-sdk/types": "^3.974.2",
121
+ "@smithy/core": "^3.29.4",
122
+ "@smithy/fetch-http-handler": "^5.6.6",
123
+ "@smithy/node-http-handler": "^4.9.6",
124
+ "@smithy/types": "^4.16.1",
125
+ "tslib": "^2.6.2"
126
+ },
127
+ "engines": {
128
+ "node": ">=20.0.0"
129
+ }
130
+ },
131
+ "node_modules/@aws-sdk/credential-provider-ini": {
132
+ "version": "3.973.5",
133
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.5.tgz",
134
+ "integrity": "sha512-ylubazcRfq2TVus/qXucSXeC42Qdjp5HQxTu68K/BsdMiZlcSLD1zkpoCgApXZX1Y6YJhtGGs7ZHhO/GuIgBlw==",
135
+ "license": "Apache-2.0",
136
+ "dependencies": {
137
+ "@aws-sdk/core": "^3.976.0",
138
+ "@aws-sdk/credential-provider-env": "^3.972.60",
139
+ "@aws-sdk/credential-provider-http": "^3.972.62",
140
+ "@aws-sdk/credential-provider-login": "^3.972.67",
141
+ "@aws-sdk/credential-provider-process": "^3.972.60",
142
+ "@aws-sdk/credential-provider-sso": "^3.973.4",
143
+ "@aws-sdk/credential-provider-web-identity": "^3.972.66",
144
+ "@aws-sdk/nested-clients": "^3.997.34",
145
+ "@aws-sdk/types": "^3.974.2",
146
+ "@smithy/core": "^3.29.4",
147
+ "@smithy/credential-provider-imds": "^4.4.9",
148
+ "@smithy/types": "^4.16.1",
149
+ "tslib": "^2.6.2"
150
+ },
151
+ "engines": {
152
+ "node": ">=20.0.0"
153
+ }
154
+ },
155
+ "node_modules/@aws-sdk/credential-provider-login": {
156
+ "version": "3.972.67",
157
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.67.tgz",
158
+ "integrity": "sha512-CCygIKJ9YbI3n84OClSaSppkgKKHVj2TGT33c6FRORZrYNZQ1POmD+ip0FLYokiJAK7sSdc3YVkOsBm90oxWMQ==",
159
+ "license": "Apache-2.0",
160
+ "dependencies": {
161
+ "@aws-sdk/core": "^3.976.0",
162
+ "@aws-sdk/nested-clients": "^3.997.34",
163
+ "@aws-sdk/types": "^3.974.2",
164
+ "@smithy/core": "^3.29.4",
165
+ "@smithy/types": "^4.16.1",
166
+ "tslib": "^2.6.2"
167
+ },
168
+ "engines": {
169
+ "node": ">=20.0.0"
170
+ }
171
+ },
172
+ "node_modules/@aws-sdk/credential-provider-node": {
173
+ "version": "3.972.71",
174
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.71.tgz",
175
+ "integrity": "sha512-HIg7Q2osBzajQwL+1Vkyh2E7Gim3eTNb9RHIsOxDGjW0eZg4oEKtRs5sioCnc73ilhaOm4gX2lHVF8J7+nt2rg==",
176
+ "license": "Apache-2.0",
177
+ "dependencies": {
178
+ "@aws-sdk/credential-provider-env": "^3.972.60",
179
+ "@aws-sdk/credential-provider-http": "^3.972.62",
180
+ "@aws-sdk/credential-provider-ini": "^3.973.5",
181
+ "@aws-sdk/credential-provider-process": "^3.972.60",
182
+ "@aws-sdk/credential-provider-sso": "^3.973.4",
183
+ "@aws-sdk/credential-provider-web-identity": "^3.972.66",
184
+ "@aws-sdk/types": "^3.974.2",
185
+ "@smithy/core": "^3.29.4",
186
+ "@smithy/credential-provider-imds": "^4.4.9",
187
+ "@smithy/types": "^4.16.1",
188
+ "tslib": "^2.6.2"
189
+ },
190
+ "engines": {
191
+ "node": ">=20.0.0"
192
+ }
193
+ },
194
+ "node_modules/@aws-sdk/credential-provider-process": {
195
+ "version": "3.972.60",
196
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.60.tgz",
197
+ "integrity": "sha512-YIo3f99hM43QdYG8hDzwGemnR/pU95b0kramqSJUTleCqaB7+HwKf7YZFHqvOgTqZTPx/mRmNIqoDRr3U0Z3Tw==",
198
+ "license": "Apache-2.0",
199
+ "dependencies": {
200
+ "@aws-sdk/core": "^3.976.0",
201
+ "@aws-sdk/types": "^3.974.2",
202
+ "@smithy/core": "^3.29.4",
203
+ "@smithy/types": "^4.16.1",
204
+ "tslib": "^2.6.2"
205
+ },
206
+ "engines": {
207
+ "node": ">=20.0.0"
208
+ }
209
+ },
210
+ "node_modules/@aws-sdk/credential-provider-sso": {
211
+ "version": "3.973.4",
212
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.4.tgz",
213
+ "integrity": "sha512-BPdmL8sSBOCv4ngZ+3LHxyc3CNqDCEK37CHioCk7zGrTMY5sUtkH8q+o6qA80nn6w3/fyBPGNE7OIRlmoOxRQA==",
214
+ "license": "Apache-2.0",
215
+ "dependencies": {
216
+ "@aws-sdk/core": "^3.976.0",
217
+ "@aws-sdk/nested-clients": "^3.997.34",
218
+ "@aws-sdk/token-providers": "3.1092.0",
219
+ "@aws-sdk/types": "^3.974.2",
220
+ "@smithy/core": "^3.29.4",
221
+ "@smithy/types": "^4.16.1",
222
+ "tslib": "^2.6.2"
223
+ },
224
+ "engines": {
225
+ "node": ">=20.0.0"
226
+ }
227
+ },
228
+ "node_modules/@aws-sdk/credential-provider-web-identity": {
229
+ "version": "3.972.66",
230
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.66.tgz",
231
+ "integrity": "sha512-kSAziJboOmZmsR9/MTbiNjowl2BPes1bQuJpne4qAZ62ubi8fjfr/aupJSQje6udBoYxXTQbsL0e0kby2la3ng==",
232
+ "license": "Apache-2.0",
233
+ "dependencies": {
234
+ "@aws-sdk/core": "^3.976.0",
235
+ "@aws-sdk/nested-clients": "^3.997.34",
236
+ "@aws-sdk/types": "^3.974.2",
237
+ "@smithy/core": "^3.29.4",
238
+ "@smithy/types": "^4.16.1",
239
+ "tslib": "^2.6.2"
240
+ },
241
+ "engines": {
242
+ "node": ">=20.0.0"
243
+ }
244
+ },
245
+ "node_modules/@aws-sdk/credential-providers": {
246
+ "version": "3.1090.0",
247
+ "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.1090.0.tgz",
248
+ "integrity": "sha512-HEfLv6RdiFu8+HO5CUQjjQ/KCWWAHcQCtSPenaF26lYQSjjzKjgvjwAMGBV9/VMrO8V0faIRyoFWhF548UjTOg==",
249
+ "license": "Apache-2.0",
250
+ "dependencies": {
251
+ "@aws-sdk/core": "^3.975.3",
252
+ "@aws-sdk/credential-provider-cognito-identity": "^3.972.58",
253
+ "@aws-sdk/credential-provider-env": "^3.972.59",
254
+ "@aws-sdk/credential-provider-http": "^3.972.61",
255
+ "@aws-sdk/credential-provider-ini": "^3.973.4",
256
+ "@aws-sdk/credential-provider-login": "^3.972.66",
257
+ "@aws-sdk/credential-provider-node": "^3.972.70",
258
+ "@aws-sdk/credential-provider-process": "^3.972.59",
259
+ "@aws-sdk/credential-provider-sso": "^3.973.3",
260
+ "@aws-sdk/credential-provider-web-identity": "^3.972.65",
261
+ "@aws-sdk/nested-clients": "^3.997.33",
262
+ "@aws-sdk/types": "^3.974.2",
263
+ "@smithy/core": "^3.29.4",
264
+ "@smithy/credential-provider-imds": "^4.4.9",
265
+ "@smithy/types": "^4.16.1",
266
+ "tslib": "^2.6.2"
267
+ },
268
+ "engines": {
269
+ "node": ">=20.0.0"
270
+ }
271
+ },
272
+ "node_modules/@aws-sdk/middleware-sdk-s3": {
273
+ "version": "3.972.65",
274
+ "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.65.tgz",
275
+ "integrity": "sha512-udwNhRfDTfCB98mAHjjgsnKQlxygB4e0X+Obne/XjJpvVsF0YCQC8ZErd/8Z6IPoLQjtiKHzwqEDbZiLrJEnOg==",
276
+ "license": "Apache-2.0",
277
+ "dependencies": {
278
+ "@aws-sdk/core": "^3.976.0",
279
+ "@aws-sdk/signature-v4-multi-region": "^3.996.41",
280
+ "@aws-sdk/types": "^3.974.2",
281
+ "@smithy/core": "^3.29.4",
282
+ "@smithy/types": "^4.16.1",
283
+ "tslib": "^2.6.2"
284
+ },
285
+ "engines": {
286
+ "node": ">=20.0.0"
287
+ }
288
+ },
289
+ "node_modules/@aws-sdk/nested-clients": {
290
+ "version": "3.997.34",
291
+ "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.34.tgz",
292
+ "integrity": "sha512-Y9REVrSwmLM+Qy6sZJ7ofMC2S3Hr3tPP/4CzL5U1olPP7OGoF+6+Px0E49cVQBtSxJtyeLJMf0UaBErfeSahAA==",
293
+ "license": "Apache-2.0",
294
+ "dependencies": {
295
+ "@aws-sdk/core": "^3.976.0",
296
+ "@aws-sdk/signature-v4-multi-region": "^3.996.41",
297
+ "@aws-sdk/types": "^3.974.2",
298
+ "@smithy/core": "^3.29.4",
299
+ "@smithy/fetch-http-handler": "^5.6.6",
300
+ "@smithy/node-http-handler": "^4.9.6",
301
+ "@smithy/types": "^4.16.1",
302
+ "tslib": "^2.6.2"
303
+ },
304
+ "engines": {
305
+ "node": ">=20.0.0"
306
+ }
307
+ },
308
+ "node_modules/@aws-sdk/s3-request-presigner": {
309
+ "version": "3.1090.0",
310
+ "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.1090.0.tgz",
311
+ "integrity": "sha512-DaYXp8GMptWJUDbvpoB51xZztOw6tcbpXjYAZMXTu17E0aR+5g8FWBU3t3t0KEhhrUiKyfLpVi6uNQxNSXfbhg==",
312
+ "license": "Apache-2.0",
313
+ "dependencies": {
314
+ "@aws-sdk/core": "^3.975.3",
315
+ "@aws-sdk/signature-v4-multi-region": "^3.996.41",
316
+ "@aws-sdk/types": "^3.974.2",
317
+ "@smithy/core": "^3.29.4",
318
+ "@smithy/types": "^4.16.1",
319
+ "tslib": "^2.6.2"
320
+ },
321
+ "engines": {
322
+ "node": ">=20.0.0"
323
+ }
324
+ },
325
+ "node_modules/@aws-sdk/signature-v4-multi-region": {
326
+ "version": "3.996.41",
327
+ "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.41.tgz",
328
+ "integrity": "sha512-QMUytg+FQMGouc8gHS00KoYih3+N6cqmVI/pQGOIo7Nr7OpQaiXjSYOuL+vsPZ1tymY4LAQ8MYcHJmws5LRxng==",
329
+ "license": "Apache-2.0",
330
+ "dependencies": {
331
+ "@aws-sdk/types": "^3.974.2",
332
+ "@smithy/signature-v4": "^5.6.5",
333
+ "@smithy/types": "^4.16.1",
334
+ "tslib": "^2.6.2"
335
+ },
336
+ "engines": {
337
+ "node": ">=20.0.0"
338
+ }
339
+ },
340
+ "node_modules/@aws-sdk/token-providers": {
341
+ "version": "3.1092.0",
342
+ "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1092.0.tgz",
343
+ "integrity": "sha512-hBYUAr6iBLNFcsiWTgtBb0stdSw39VOUq4Sp4A5caCNf66BAZplWN4FleKrVpJx5li2YgdnK2DqoFSMWC642FQ==",
344
+ "license": "Apache-2.0",
345
+ "dependencies": {
346
+ "@aws-sdk/core": "^3.976.0",
347
+ "@aws-sdk/nested-clients": "^3.997.34",
348
+ "@aws-sdk/types": "^3.974.2",
349
+ "@smithy/core": "^3.29.4",
350
+ "@smithy/types": "^4.16.1",
351
+ "tslib": "^2.6.2"
352
+ },
353
+ "engines": {
354
+ "node": ">=20.0.0"
355
+ }
356
+ },
357
+ "node_modules/@aws-sdk/types": {
358
+ "version": "3.974.2",
359
+ "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.2.tgz",
360
+ "integrity": "sha512-3W6IUtSxFbH6X7Wb7DzGCV5QiFQsd0g8bOfntpmDxQlzBoKWUMBu/JPQR0DwkE+Hpnxd6db1tXbOwdeHddG6cA==",
361
+ "license": "Apache-2.0",
362
+ "dependencies": {
363
+ "@smithy/types": "^4.16.1",
364
+ "tslib": "^2.6.2"
365
+ },
366
+ "engines": {
367
+ "node": ">=20.0.0"
368
+ }
369
+ },
370
+ "node_modules/@aws-sdk/xml-builder": {
371
+ "version": "3.972.36",
372
+ "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.36.tgz",
373
+ "integrity": "sha512-RdGmS1GLrtaTOLE1ElSluMldNrpk9Emq6uYs8SS8iHlu5xTAmM9rRkM91o48+rIRryBtyO9t+uLYCoMG6jVMVA==",
374
+ "license": "Apache-2.0",
375
+ "dependencies": {
376
+ "@smithy/types": "^4.16.1",
377
+ "tslib": "^2.6.2"
378
+ },
379
+ "engines": {
380
+ "node": ">=20.0.0"
381
+ }
382
+ },
383
+ "node_modules/@aws/lambda-invoke-store": {
384
+ "version": "0.3.0",
385
+ "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz",
386
+ "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==",
387
+ "license": "Apache-2.0",
388
+ "engines": {
389
+ "node": ">=18.0.0"
390
+ }
391
+ },
392
+ "node_modules/@babel/helper-string-parser": {
393
+ "version": "7.29.7",
394
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
395
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
396
+ "license": "MIT",
397
+ "engines": {
398
+ "node": ">=6.9.0"
399
+ }
400
+ },
401
+ "node_modules/@babel/helper-validator-identifier": {
402
+ "version": "7.29.7",
403
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
404
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
405
+ "license": "MIT",
406
+ "engines": {
407
+ "node": ">=6.9.0"
408
+ }
409
+ },
410
+ "node_modules/@babel/parser": {
411
+ "version": "7.29.7",
412
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
413
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
414
+ "license": "MIT",
415
+ "dependencies": {
416
+ "@babel/types": "^7.29.7"
417
+ },
418
+ "bin": {
419
+ "parser": "bin/babel-parser.js"
420
+ },
421
+ "engines": {
422
+ "node": ">=6.0.0"
423
+ }
424
+ },
425
+ "node_modules/@babel/types": {
426
+ "version": "7.29.7",
427
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
428
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
429
+ "license": "MIT",
430
+ "dependencies": {
431
+ "@babel/helper-string-parser": "^7.29.7",
432
+ "@babel/helper-validator-identifier": "^7.29.7"
433
+ },
434
+ "engines": {
435
+ "node": ">=6.9.0"
436
+ }
437
+ },
438
+ "node_modules/@esbuild/aix-ppc64": {
439
+ "version": "0.25.12",
440
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz",
441
+ "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==",
442
+ "cpu": [
443
+ "ppc64"
444
+ ],
445
+ "dev": true,
446
+ "license": "MIT",
447
+ "optional": true,
448
+ "os": [
449
+ "aix"
450
+ ],
451
+ "engines": {
452
+ "node": ">=18"
453
+ }
454
+ },
455
+ "node_modules/@esbuild/android-arm": {
456
+ "version": "0.25.12",
457
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz",
458
+ "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==",
459
+ "cpu": [
460
+ "arm"
461
+ ],
462
+ "dev": true,
463
+ "license": "MIT",
464
+ "optional": true,
465
+ "os": [
466
+ "android"
467
+ ],
468
+ "engines": {
469
+ "node": ">=18"
470
+ }
471
+ },
472
+ "node_modules/@esbuild/android-arm64": {
473
+ "version": "0.25.12",
474
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz",
475
+ "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==",
476
+ "cpu": [
477
+ "arm64"
478
+ ],
479
+ "dev": true,
480
+ "license": "MIT",
481
+ "optional": true,
482
+ "os": [
483
+ "android"
484
+ ],
485
+ "engines": {
486
+ "node": ">=18"
487
+ }
488
+ },
489
+ "node_modules/@esbuild/android-x64": {
490
+ "version": "0.25.12",
491
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz",
492
+ "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==",
493
+ "cpu": [
494
+ "x64"
495
+ ],
496
+ "dev": true,
497
+ "license": "MIT",
498
+ "optional": true,
499
+ "os": [
500
+ "android"
501
+ ],
502
+ "engines": {
503
+ "node": ">=18"
504
+ }
505
+ },
506
+ "node_modules/@esbuild/darwin-arm64": {
507
+ "version": "0.25.12",
508
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz",
509
+ "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==",
510
+ "cpu": [
511
+ "arm64"
512
+ ],
513
+ "dev": true,
514
+ "license": "MIT",
515
+ "optional": true,
516
+ "os": [
517
+ "darwin"
518
+ ],
519
+ "engines": {
520
+ "node": ">=18"
521
+ }
522
+ },
523
+ "node_modules/@esbuild/darwin-x64": {
524
+ "version": "0.25.12",
525
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz",
526
+ "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==",
527
+ "cpu": [
528
+ "x64"
529
+ ],
530
+ "dev": true,
531
+ "license": "MIT",
532
+ "optional": true,
533
+ "os": [
534
+ "darwin"
535
+ ],
536
+ "engines": {
537
+ "node": ">=18"
538
+ }
539
+ },
540
+ "node_modules/@esbuild/freebsd-arm64": {
541
+ "version": "0.25.12",
542
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz",
543
+ "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==",
544
+ "cpu": [
545
+ "arm64"
546
+ ],
547
+ "dev": true,
548
+ "license": "MIT",
549
+ "optional": true,
550
+ "os": [
551
+ "freebsd"
552
+ ],
553
+ "engines": {
554
+ "node": ">=18"
555
+ }
556
+ },
557
+ "node_modules/@esbuild/freebsd-x64": {
558
+ "version": "0.25.12",
559
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz",
560
+ "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==",
561
+ "cpu": [
562
+ "x64"
563
+ ],
564
+ "dev": true,
565
+ "license": "MIT",
566
+ "optional": true,
567
+ "os": [
568
+ "freebsd"
569
+ ],
570
+ "engines": {
571
+ "node": ">=18"
572
+ }
573
+ },
574
+ "node_modules/@esbuild/linux-arm": {
575
+ "version": "0.25.12",
576
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz",
577
+ "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==",
578
+ "cpu": [
579
+ "arm"
580
+ ],
581
+ "dev": true,
582
+ "license": "MIT",
583
+ "optional": true,
584
+ "os": [
585
+ "linux"
586
+ ],
587
+ "engines": {
588
+ "node": ">=18"
589
+ }
590
+ },
591
+ "node_modules/@esbuild/linux-arm64": {
592
+ "version": "0.25.12",
593
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz",
594
+ "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==",
595
+ "cpu": [
596
+ "arm64"
597
+ ],
598
+ "dev": true,
599
+ "license": "MIT",
600
+ "optional": true,
601
+ "os": [
602
+ "linux"
603
+ ],
604
+ "engines": {
605
+ "node": ">=18"
606
+ }
607
+ },
608
+ "node_modules/@esbuild/linux-ia32": {
609
+ "version": "0.25.12",
610
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz",
611
+ "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==",
612
+ "cpu": [
613
+ "ia32"
614
+ ],
615
+ "dev": true,
616
+ "license": "MIT",
617
+ "optional": true,
618
+ "os": [
619
+ "linux"
620
+ ],
621
+ "engines": {
622
+ "node": ">=18"
623
+ }
624
+ },
625
+ "node_modules/@esbuild/linux-loong64": {
626
+ "version": "0.25.12",
627
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz",
628
+ "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==",
629
+ "cpu": [
630
+ "loong64"
631
+ ],
632
+ "dev": true,
633
+ "license": "MIT",
634
+ "optional": true,
635
+ "os": [
636
+ "linux"
637
+ ],
638
+ "engines": {
639
+ "node": ">=18"
640
+ }
641
+ },
642
+ "node_modules/@esbuild/linux-mips64el": {
643
+ "version": "0.25.12",
644
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz",
645
+ "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==",
646
+ "cpu": [
647
+ "mips64el"
648
+ ],
649
+ "dev": true,
650
+ "license": "MIT",
651
+ "optional": true,
652
+ "os": [
653
+ "linux"
654
+ ],
655
+ "engines": {
656
+ "node": ">=18"
657
+ }
658
+ },
659
+ "node_modules/@esbuild/linux-ppc64": {
660
+ "version": "0.25.12",
661
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz",
662
+ "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==",
663
+ "cpu": [
664
+ "ppc64"
665
+ ],
666
+ "dev": true,
667
+ "license": "MIT",
668
+ "optional": true,
669
+ "os": [
670
+ "linux"
671
+ ],
672
+ "engines": {
673
+ "node": ">=18"
674
+ }
675
+ },
676
+ "node_modules/@esbuild/linux-riscv64": {
677
+ "version": "0.25.12",
678
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz",
679
+ "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==",
680
+ "cpu": [
681
+ "riscv64"
682
+ ],
683
+ "dev": true,
684
+ "license": "MIT",
685
+ "optional": true,
686
+ "os": [
687
+ "linux"
688
+ ],
689
+ "engines": {
690
+ "node": ">=18"
691
+ }
692
+ },
693
+ "node_modules/@esbuild/linux-s390x": {
694
+ "version": "0.25.12",
695
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz",
696
+ "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==",
697
+ "cpu": [
698
+ "s390x"
699
+ ],
700
+ "dev": true,
701
+ "license": "MIT",
702
+ "optional": true,
703
+ "os": [
704
+ "linux"
705
+ ],
706
+ "engines": {
707
+ "node": ">=18"
708
+ }
709
+ },
710
+ "node_modules/@esbuild/linux-x64": {
711
+ "version": "0.25.12",
712
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz",
713
+ "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==",
714
+ "cpu": [
715
+ "x64"
716
+ ],
717
+ "dev": true,
718
+ "license": "MIT",
719
+ "optional": true,
720
+ "os": [
721
+ "linux"
722
+ ],
723
+ "engines": {
724
+ "node": ">=18"
725
+ }
726
+ },
727
+ "node_modules/@esbuild/netbsd-arm64": {
728
+ "version": "0.25.12",
729
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz",
730
+ "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==",
731
+ "cpu": [
732
+ "arm64"
733
+ ],
734
+ "dev": true,
735
+ "license": "MIT",
736
+ "optional": true,
737
+ "os": [
738
+ "netbsd"
739
+ ],
740
+ "engines": {
741
+ "node": ">=18"
742
+ }
743
+ },
744
+ "node_modules/@esbuild/netbsd-x64": {
745
+ "version": "0.25.12",
746
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz",
747
+ "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==",
748
+ "cpu": [
749
+ "x64"
750
+ ],
751
+ "dev": true,
752
+ "license": "MIT",
753
+ "optional": true,
754
+ "os": [
755
+ "netbsd"
756
+ ],
757
+ "engines": {
758
+ "node": ">=18"
759
+ }
760
+ },
761
+ "node_modules/@esbuild/openbsd-arm64": {
762
+ "version": "0.25.12",
763
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz",
764
+ "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==",
765
+ "cpu": [
766
+ "arm64"
767
+ ],
768
+ "dev": true,
769
+ "license": "MIT",
770
+ "optional": true,
771
+ "os": [
772
+ "openbsd"
773
+ ],
774
+ "engines": {
775
+ "node": ">=18"
776
+ }
777
+ },
778
+ "node_modules/@esbuild/openbsd-x64": {
779
+ "version": "0.25.12",
780
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz",
781
+ "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==",
782
+ "cpu": [
783
+ "x64"
784
+ ],
785
+ "dev": true,
786
+ "license": "MIT",
787
+ "optional": true,
788
+ "os": [
789
+ "openbsd"
790
+ ],
791
+ "engines": {
792
+ "node": ">=18"
793
+ }
794
+ },
795
+ "node_modules/@esbuild/openharmony-arm64": {
796
+ "version": "0.25.12",
797
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz",
798
+ "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==",
799
+ "cpu": [
800
+ "arm64"
801
+ ],
802
+ "dev": true,
803
+ "license": "MIT",
804
+ "optional": true,
805
+ "os": [
806
+ "openharmony"
807
+ ],
808
+ "engines": {
809
+ "node": ">=18"
810
+ }
811
+ },
812
+ "node_modules/@esbuild/sunos-x64": {
813
+ "version": "0.25.12",
814
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz",
815
+ "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==",
816
+ "cpu": [
817
+ "x64"
818
+ ],
819
+ "dev": true,
820
+ "license": "MIT",
821
+ "optional": true,
822
+ "os": [
823
+ "sunos"
824
+ ],
825
+ "engines": {
826
+ "node": ">=18"
827
+ }
828
+ },
829
+ "node_modules/@esbuild/win32-arm64": {
830
+ "version": "0.25.12",
831
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz",
832
+ "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==",
833
+ "cpu": [
834
+ "arm64"
835
+ ],
836
+ "dev": true,
837
+ "license": "MIT",
838
+ "optional": true,
839
+ "os": [
840
+ "win32"
841
+ ],
842
+ "engines": {
843
+ "node": ">=18"
844
+ }
845
+ },
846
+ "node_modules/@esbuild/win32-ia32": {
847
+ "version": "0.25.12",
848
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz",
849
+ "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==",
850
+ "cpu": [
851
+ "ia32"
852
+ ],
853
+ "dev": true,
854
+ "license": "MIT",
855
+ "optional": true,
856
+ "os": [
857
+ "win32"
858
+ ],
859
+ "engines": {
860
+ "node": ">=18"
861
+ }
862
+ },
863
+ "node_modules/@esbuild/win32-x64": {
864
+ "version": "0.25.12",
865
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz",
866
+ "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==",
867
+ "cpu": [
868
+ "x64"
869
+ ],
870
+ "dev": true,
871
+ "license": "MIT",
872
+ "optional": true,
873
+ "os": [
874
+ "win32"
875
+ ],
876
+ "engines": {
877
+ "node": ">=18"
878
+ }
879
+ },
880
+ "node_modules/@jridgewell/sourcemap-codec": {
881
+ "version": "1.5.5",
882
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
883
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
884
+ "license": "MIT"
885
+ },
886
+ "node_modules/@rollup/rollup-android-arm-eabi": {
887
+ "version": "4.62.2",
888
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
889
+ "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
890
+ "cpu": [
891
+ "arm"
892
+ ],
893
+ "dev": true,
894
+ "license": "MIT",
895
+ "optional": true,
896
+ "os": [
897
+ "android"
898
+ ]
899
+ },
900
+ "node_modules/@rollup/rollup-android-arm64": {
901
+ "version": "4.62.2",
902
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
903
+ "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
904
+ "cpu": [
905
+ "arm64"
906
+ ],
907
+ "dev": true,
908
+ "license": "MIT",
909
+ "optional": true,
910
+ "os": [
911
+ "android"
912
+ ]
913
+ },
914
+ "node_modules/@rollup/rollup-darwin-arm64": {
915
+ "version": "4.62.2",
916
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
917
+ "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
918
+ "cpu": [
919
+ "arm64"
920
+ ],
921
+ "dev": true,
922
+ "license": "MIT",
923
+ "optional": true,
924
+ "os": [
925
+ "darwin"
926
+ ]
927
+ },
928
+ "node_modules/@rollup/rollup-darwin-x64": {
929
+ "version": "4.62.2",
930
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
931
+ "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
932
+ "cpu": [
933
+ "x64"
934
+ ],
935
+ "dev": true,
936
+ "license": "MIT",
937
+ "optional": true,
938
+ "os": [
939
+ "darwin"
940
+ ]
941
+ },
942
+ "node_modules/@rollup/rollup-freebsd-arm64": {
943
+ "version": "4.62.2",
944
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
945
+ "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
946
+ "cpu": [
947
+ "arm64"
948
+ ],
949
+ "dev": true,
950
+ "license": "MIT",
951
+ "optional": true,
952
+ "os": [
953
+ "freebsd"
954
+ ]
955
+ },
956
+ "node_modules/@rollup/rollup-freebsd-x64": {
957
+ "version": "4.62.2",
958
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
959
+ "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
960
+ "cpu": [
961
+ "x64"
962
+ ],
963
+ "dev": true,
964
+ "license": "MIT",
965
+ "optional": true,
966
+ "os": [
967
+ "freebsd"
968
+ ]
969
+ },
970
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
971
+ "version": "4.62.2",
972
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
973
+ "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
974
+ "cpu": [
975
+ "arm"
976
+ ],
977
+ "dev": true,
978
+ "license": "MIT",
979
+ "optional": true,
980
+ "os": [
981
+ "linux"
982
+ ]
983
+ },
984
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
985
+ "version": "4.62.2",
986
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
987
+ "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
988
+ "cpu": [
989
+ "arm"
990
+ ],
991
+ "dev": true,
992
+ "license": "MIT",
993
+ "optional": true,
994
+ "os": [
995
+ "linux"
996
+ ]
997
+ },
998
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
999
+ "version": "4.62.2",
1000
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
1001
+ "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
1002
+ "cpu": [
1003
+ "arm64"
1004
+ ],
1005
+ "dev": true,
1006
+ "license": "MIT",
1007
+ "optional": true,
1008
+ "os": [
1009
+ "linux"
1010
+ ]
1011
+ },
1012
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
1013
+ "version": "4.62.2",
1014
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
1015
+ "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
1016
+ "cpu": [
1017
+ "arm64"
1018
+ ],
1019
+ "dev": true,
1020
+ "license": "MIT",
1021
+ "optional": true,
1022
+ "os": [
1023
+ "linux"
1024
+ ]
1025
+ },
1026
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
1027
+ "version": "4.62.2",
1028
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
1029
+ "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
1030
+ "cpu": [
1031
+ "loong64"
1032
+ ],
1033
+ "dev": true,
1034
+ "license": "MIT",
1035
+ "optional": true,
1036
+ "os": [
1037
+ "linux"
1038
+ ]
1039
+ },
1040
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
1041
+ "version": "4.62.2",
1042
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
1043
+ "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
1044
+ "cpu": [
1045
+ "loong64"
1046
+ ],
1047
+ "dev": true,
1048
+ "license": "MIT",
1049
+ "optional": true,
1050
+ "os": [
1051
+ "linux"
1052
+ ]
1053
+ },
1054
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
1055
+ "version": "4.62.2",
1056
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
1057
+ "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
1058
+ "cpu": [
1059
+ "ppc64"
1060
+ ],
1061
+ "dev": true,
1062
+ "license": "MIT",
1063
+ "optional": true,
1064
+ "os": [
1065
+ "linux"
1066
+ ]
1067
+ },
1068
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
1069
+ "version": "4.62.2",
1070
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
1071
+ "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
1072
+ "cpu": [
1073
+ "ppc64"
1074
+ ],
1075
+ "dev": true,
1076
+ "license": "MIT",
1077
+ "optional": true,
1078
+ "os": [
1079
+ "linux"
1080
+ ]
1081
+ },
1082
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
1083
+ "version": "4.62.2",
1084
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
1085
+ "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
1086
+ "cpu": [
1087
+ "riscv64"
1088
+ ],
1089
+ "dev": true,
1090
+ "license": "MIT",
1091
+ "optional": true,
1092
+ "os": [
1093
+ "linux"
1094
+ ]
1095
+ },
1096
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
1097
+ "version": "4.62.2",
1098
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
1099
+ "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
1100
+ "cpu": [
1101
+ "riscv64"
1102
+ ],
1103
+ "dev": true,
1104
+ "license": "MIT",
1105
+ "optional": true,
1106
+ "os": [
1107
+ "linux"
1108
+ ]
1109
+ },
1110
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
1111
+ "version": "4.62.2",
1112
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
1113
+ "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
1114
+ "cpu": [
1115
+ "s390x"
1116
+ ],
1117
+ "dev": true,
1118
+ "license": "MIT",
1119
+ "optional": true,
1120
+ "os": [
1121
+ "linux"
1122
+ ]
1123
+ },
1124
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
1125
+ "version": "4.62.2",
1126
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
1127
+ "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
1128
+ "cpu": [
1129
+ "x64"
1130
+ ],
1131
+ "dev": true,
1132
+ "license": "MIT",
1133
+ "optional": true,
1134
+ "os": [
1135
+ "linux"
1136
+ ]
1137
+ },
1138
+ "node_modules/@rollup/rollup-linux-x64-musl": {
1139
+ "version": "4.62.2",
1140
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
1141
+ "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
1142
+ "cpu": [
1143
+ "x64"
1144
+ ],
1145
+ "dev": true,
1146
+ "license": "MIT",
1147
+ "optional": true,
1148
+ "os": [
1149
+ "linux"
1150
+ ]
1151
+ },
1152
+ "node_modules/@rollup/rollup-openbsd-x64": {
1153
+ "version": "4.62.2",
1154
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
1155
+ "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
1156
+ "cpu": [
1157
+ "x64"
1158
+ ],
1159
+ "dev": true,
1160
+ "license": "MIT",
1161
+ "optional": true,
1162
+ "os": [
1163
+ "openbsd"
1164
+ ]
1165
+ },
1166
+ "node_modules/@rollup/rollup-openharmony-arm64": {
1167
+ "version": "4.62.2",
1168
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
1169
+ "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
1170
+ "cpu": [
1171
+ "arm64"
1172
+ ],
1173
+ "dev": true,
1174
+ "license": "MIT",
1175
+ "optional": true,
1176
+ "os": [
1177
+ "openharmony"
1178
+ ]
1179
+ },
1180
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
1181
+ "version": "4.62.2",
1182
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
1183
+ "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
1184
+ "cpu": [
1185
+ "arm64"
1186
+ ],
1187
+ "dev": true,
1188
+ "license": "MIT",
1189
+ "optional": true,
1190
+ "os": [
1191
+ "win32"
1192
+ ]
1193
+ },
1194
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
1195
+ "version": "4.62.2",
1196
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
1197
+ "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
1198
+ "cpu": [
1199
+ "ia32"
1200
+ ],
1201
+ "dev": true,
1202
+ "license": "MIT",
1203
+ "optional": true,
1204
+ "os": [
1205
+ "win32"
1206
+ ]
1207
+ },
1208
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
1209
+ "version": "4.62.2",
1210
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
1211
+ "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
1212
+ "cpu": [
1213
+ "x64"
1214
+ ],
1215
+ "dev": true,
1216
+ "license": "MIT",
1217
+ "optional": true,
1218
+ "os": [
1219
+ "win32"
1220
+ ]
1221
+ },
1222
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
1223
+ "version": "4.62.2",
1224
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
1225
+ "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
1226
+ "cpu": [
1227
+ "x64"
1228
+ ],
1229
+ "dev": true,
1230
+ "license": "MIT",
1231
+ "optional": true,
1232
+ "os": [
1233
+ "win32"
1234
+ ]
1235
+ },
1236
+ "node_modules/@smithy/core": {
1237
+ "version": "3.29.7",
1238
+ "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.7.tgz",
1239
+ "integrity": "sha512-BiEE2bnnGoPKdlGe3L+gOYORDHFGPuYVRLP7iUow/Sflm0B4hC4XY3FC1MRuc7ltzpW2xNnXopKi34TTkULlKQ==",
1240
+ "license": "Apache-2.0",
1241
+ "dependencies": {
1242
+ "@smithy/types": "^4.16.1",
1243
+ "tslib": "^2.6.2"
1244
+ },
1245
+ "engines": {
1246
+ "node": ">=18.0.0"
1247
+ }
1248
+ },
1249
+ "node_modules/@smithy/credential-provider-imds": {
1250
+ "version": "4.4.12",
1251
+ "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.12.tgz",
1252
+ "integrity": "sha512-ZZPDbl/aRp77aycuoMlo3BTayT4CE2a3uoqETYZU5ySnVbhpl5IJiY7dCZedn+ZusyDLqVv44IvKBiXd2/nK0Q==",
1253
+ "license": "Apache-2.0",
1254
+ "dependencies": {
1255
+ "@smithy/core": "^3.29.7",
1256
+ "@smithy/types": "^4.16.1",
1257
+ "tslib": "^2.6.2"
1258
+ },
1259
+ "engines": {
1260
+ "node": ">=18.0.0"
1261
+ }
1262
+ },
1263
+ "node_modules/@smithy/fetch-http-handler": {
1264
+ "version": "5.6.9",
1265
+ "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.9.tgz",
1266
+ "integrity": "sha512-EJktha5m5MXCwzdXrlWyqb9UCNHNFKlg+PmTpRsdX3dncJPTiqYleM9OKj2mLgdVJHR01d2tU4alG+z2NdH5rQ==",
1267
+ "license": "Apache-2.0",
1268
+ "dependencies": {
1269
+ "@smithy/core": "^3.29.7",
1270
+ "@smithy/types": "^4.16.1",
1271
+ "tslib": "^2.6.2"
1272
+ },
1273
+ "engines": {
1274
+ "node": ">=18.0.0"
1275
+ }
1276
+ },
1277
+ "node_modules/@smithy/node-http-handler": {
1278
+ "version": "4.9.9",
1279
+ "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.9.tgz",
1280
+ "integrity": "sha512-xVBZ3hptB99iNO9XyWqEhC7KD9bP9UPXhuy3h5Y2ItCfBv160D9IIC/Fmmp3EbnWwit4C+KVqlSE+E29Nk/pPg==",
1281
+ "license": "Apache-2.0",
1282
+ "dependencies": {
1283
+ "@smithy/core": "^3.29.7",
1284
+ "@smithy/types": "^4.16.1",
1285
+ "tslib": "^2.6.2"
1286
+ },
1287
+ "engines": {
1288
+ "node": ">=18.0.0"
1289
+ }
1290
+ },
1291
+ "node_modules/@smithy/signature-v4": {
1292
+ "version": "5.6.8",
1293
+ "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.8.tgz",
1294
+ "integrity": "sha512-iGBm6hIwD2MGvVRSgrjVWa4FXtXDq3akxu0DCpnkmBo0xtEHZ/siMRt7ycfZAefYr2UdywUgmGtoRLaq5u56pg==",
1295
+ "license": "Apache-2.0",
1296
+ "dependencies": {
1297
+ "@smithy/core": "^3.29.7",
1298
+ "@smithy/types": "^4.16.1",
1299
+ "tslib": "^2.6.2"
1300
+ },
1301
+ "engines": {
1302
+ "node": ">=18.0.0"
1303
+ }
1304
+ },
1305
+ "node_modules/@smithy/types": {
1306
+ "version": "4.16.1",
1307
+ "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz",
1308
+ "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==",
1309
+ "license": "Apache-2.0",
1310
+ "dependencies": {
1311
+ "tslib": "^2.6.2"
1312
+ },
1313
+ "engines": {
1314
+ "node": ">=18.0.0"
1315
+ }
1316
+ },
1317
+ "node_modules/@types/estree": {
1318
+ "version": "1.0.9",
1319
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
1320
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
1321
+ "dev": true,
1322
+ "license": "MIT"
1323
+ },
1324
+ "node_modules/@types/node": {
1325
+ "version": "20.19.43",
1326
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
1327
+ "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
1328
+ "dev": true,
1329
+ "license": "MIT",
1330
+ "dependencies": {
1331
+ "undici-types": "~6.21.0"
1332
+ }
1333
+ },
1334
+ "node_modules/@vitejs/plugin-vue": {
1335
+ "version": "5.2.4",
1336
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
1337
+ "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
1338
+ "dev": true,
1339
+ "license": "MIT",
1340
+ "engines": {
1341
+ "node": "^18.0.0 || >=20.0.0"
1342
+ },
1343
+ "peerDependencies": {
1344
+ "vite": "^5.0.0 || ^6.0.0",
1345
+ "vue": "^3.2.25"
1346
+ }
1347
+ },
1348
+ "node_modules/@volar/language-core": {
1349
+ "version": "2.4.28",
1350
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz",
1351
+ "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==",
1352
+ "dev": true,
1353
+ "license": "MIT",
1354
+ "dependencies": {
1355
+ "@volar/source-map": "2.4.28"
1356
+ }
1357
+ },
1358
+ "node_modules/@volar/source-map": {
1359
+ "version": "2.4.28",
1360
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz",
1361
+ "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==",
1362
+ "dev": true,
1363
+ "license": "MIT"
1364
+ },
1365
+ "node_modules/@volar/typescript": {
1366
+ "version": "2.4.28",
1367
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz",
1368
+ "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==",
1369
+ "dev": true,
1370
+ "license": "MIT",
1371
+ "dependencies": {
1372
+ "@volar/language-core": "2.4.28",
1373
+ "path-browserify": "^1.0.1",
1374
+ "vscode-uri": "^3.0.8"
1375
+ }
1376
+ },
1377
+ "node_modules/@vue/compiler-core": {
1378
+ "version": "3.5.40",
1379
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz",
1380
+ "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==",
1381
+ "license": "MIT",
1382
+ "dependencies": {
1383
+ "@babel/parser": "^7.29.7",
1384
+ "@vue/shared": "3.5.40",
1385
+ "entities": "^7.0.1",
1386
+ "estree-walker": "^2.0.2",
1387
+ "source-map-js": "^1.2.1"
1388
+ }
1389
+ },
1390
+ "node_modules/@vue/compiler-dom": {
1391
+ "version": "3.5.40",
1392
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz",
1393
+ "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==",
1394
+ "license": "MIT",
1395
+ "dependencies": {
1396
+ "@vue/compiler-core": "3.5.40",
1397
+ "@vue/shared": "3.5.40"
1398
+ }
1399
+ },
1400
+ "node_modules/@vue/compiler-sfc": {
1401
+ "version": "3.5.40",
1402
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz",
1403
+ "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==",
1404
+ "license": "MIT",
1405
+ "dependencies": {
1406
+ "@babel/parser": "^7.29.7",
1407
+ "@vue/compiler-core": "3.5.40",
1408
+ "@vue/compiler-dom": "3.5.40",
1409
+ "@vue/compiler-ssr": "3.5.40",
1410
+ "@vue/shared": "3.5.40",
1411
+ "estree-walker": "^2.0.2",
1412
+ "magic-string": "^0.30.21",
1413
+ "postcss": "^8.5.19",
1414
+ "source-map-js": "^1.2.1"
1415
+ }
1416
+ },
1417
+ "node_modules/@vue/compiler-ssr": {
1418
+ "version": "3.5.40",
1419
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz",
1420
+ "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==",
1421
+ "license": "MIT",
1422
+ "dependencies": {
1423
+ "@vue/compiler-dom": "3.5.40",
1424
+ "@vue/shared": "3.5.40"
1425
+ }
1426
+ },
1427
+ "node_modules/@vue/language-core": {
1428
+ "version": "3.3.7",
1429
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.7.tgz",
1430
+ "integrity": "sha512-LzmkKinXAMMoh8Jfi/jMUSDUjuPdv8mynH5WJGKfXyZtDw3hQ6GBaoI6Bcnl/Xqlu32q/0Z6i/trp4VXykzyLw==",
1431
+ "dev": true,
1432
+ "license": "MIT",
1433
+ "dependencies": {
1434
+ "@volar/language-core": "2.4.28",
1435
+ "@vue/compiler-dom": "^3.5.0",
1436
+ "@vue/shared": "^3.5.0",
1437
+ "alien-signals": "^3.2.1",
1438
+ "muggle-string": "^0.4.1",
1439
+ "path-browserify": "^1.0.1",
1440
+ "picomatch": "^4.0.4"
1441
+ }
1442
+ },
1443
+ "node_modules/@vue/reactivity": {
1444
+ "version": "3.5.40",
1445
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz",
1446
+ "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==",
1447
+ "license": "MIT",
1448
+ "dependencies": {
1449
+ "@vue/shared": "3.5.40"
1450
+ }
1451
+ },
1452
+ "node_modules/@vue/runtime-core": {
1453
+ "version": "3.5.40",
1454
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz",
1455
+ "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==",
1456
+ "license": "MIT",
1457
+ "dependencies": {
1458
+ "@vue/reactivity": "3.5.40",
1459
+ "@vue/shared": "3.5.40"
1460
+ }
1461
+ },
1462
+ "node_modules/@vue/runtime-dom": {
1463
+ "version": "3.5.40",
1464
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz",
1465
+ "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==",
1466
+ "license": "MIT",
1467
+ "dependencies": {
1468
+ "@vue/reactivity": "3.5.40",
1469
+ "@vue/runtime-core": "3.5.40",
1470
+ "@vue/shared": "3.5.40",
1471
+ "csstype": "^3.2.3"
1472
+ }
1473
+ },
1474
+ "node_modules/@vue/server-renderer": {
1475
+ "version": "3.5.40",
1476
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz",
1477
+ "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==",
1478
+ "license": "MIT",
1479
+ "dependencies": {
1480
+ "@vue/compiler-ssr": "3.5.40",
1481
+ "@vue/runtime-dom": "3.5.40",
1482
+ "@vue/shared": "3.5.40"
1483
+ }
1484
+ },
1485
+ "node_modules/@vue/shared": {
1486
+ "version": "3.5.40",
1487
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz",
1488
+ "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==",
1489
+ "license": "MIT"
1490
+ },
1491
+ "node_modules/alien-signals": {
1492
+ "version": "3.2.1",
1493
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz",
1494
+ "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==",
1495
+ "dev": true,
1496
+ "license": "MIT"
1497
+ },
1498
+ "node_modules/bowser": {
1499
+ "version": "2.14.1",
1500
+ "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz",
1501
+ "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==",
1502
+ "license": "MIT"
1503
+ },
1504
+ "node_modules/csstype": {
1505
+ "version": "3.2.3",
1506
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
1507
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
1508
+ "license": "MIT"
1509
+ },
1510
+ "node_modules/entities": {
1511
+ "version": "7.0.1",
1512
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
1513
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
1514
+ "license": "BSD-2-Clause",
1515
+ "engines": {
1516
+ "node": ">=0.12"
1517
+ },
1518
+ "funding": {
1519
+ "url": "https://github.com/fb55/entities?sponsor=1"
1520
+ }
1521
+ },
1522
+ "node_modules/esbuild": {
1523
+ "version": "0.25.12",
1524
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz",
1525
+ "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==",
1526
+ "dev": true,
1527
+ "hasInstallScript": true,
1528
+ "license": "MIT",
1529
+ "bin": {
1530
+ "esbuild": "bin/esbuild"
1531
+ },
1532
+ "engines": {
1533
+ "node": ">=18"
1534
+ },
1535
+ "optionalDependencies": {
1536
+ "@esbuild/aix-ppc64": "0.25.12",
1537
+ "@esbuild/android-arm": "0.25.12",
1538
+ "@esbuild/android-arm64": "0.25.12",
1539
+ "@esbuild/android-x64": "0.25.12",
1540
+ "@esbuild/darwin-arm64": "0.25.12",
1541
+ "@esbuild/darwin-x64": "0.25.12",
1542
+ "@esbuild/freebsd-arm64": "0.25.12",
1543
+ "@esbuild/freebsd-x64": "0.25.12",
1544
+ "@esbuild/linux-arm": "0.25.12",
1545
+ "@esbuild/linux-arm64": "0.25.12",
1546
+ "@esbuild/linux-ia32": "0.25.12",
1547
+ "@esbuild/linux-loong64": "0.25.12",
1548
+ "@esbuild/linux-mips64el": "0.25.12",
1549
+ "@esbuild/linux-ppc64": "0.25.12",
1550
+ "@esbuild/linux-riscv64": "0.25.12",
1551
+ "@esbuild/linux-s390x": "0.25.12",
1552
+ "@esbuild/linux-x64": "0.25.12",
1553
+ "@esbuild/netbsd-arm64": "0.25.12",
1554
+ "@esbuild/netbsd-x64": "0.25.12",
1555
+ "@esbuild/openbsd-arm64": "0.25.12",
1556
+ "@esbuild/openbsd-x64": "0.25.12",
1557
+ "@esbuild/openharmony-arm64": "0.25.12",
1558
+ "@esbuild/sunos-x64": "0.25.12",
1559
+ "@esbuild/win32-arm64": "0.25.12",
1560
+ "@esbuild/win32-ia32": "0.25.12",
1561
+ "@esbuild/win32-x64": "0.25.12"
1562
+ }
1563
+ },
1564
+ "node_modules/estree-walker": {
1565
+ "version": "2.0.2",
1566
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
1567
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
1568
+ "license": "MIT"
1569
+ },
1570
+ "node_modules/fdir": {
1571
+ "version": "6.5.0",
1572
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
1573
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
1574
+ "dev": true,
1575
+ "license": "MIT",
1576
+ "engines": {
1577
+ "node": ">=12.0.0"
1578
+ },
1579
+ "peerDependencies": {
1580
+ "picomatch": "^3 || ^4"
1581
+ },
1582
+ "peerDependenciesMeta": {
1583
+ "picomatch": {
1584
+ "optional": true
1585
+ }
1586
+ }
1587
+ },
1588
+ "node_modules/fsevents": {
1589
+ "version": "2.3.3",
1590
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
1591
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
1592
+ "dev": true,
1593
+ "hasInstallScript": true,
1594
+ "license": "MIT",
1595
+ "optional": true,
1596
+ "os": [
1597
+ "darwin"
1598
+ ],
1599
+ "engines": {
1600
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
1601
+ }
1602
+ },
1603
+ "node_modules/magic-string": {
1604
+ "version": "0.30.21",
1605
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
1606
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
1607
+ "license": "MIT",
1608
+ "dependencies": {
1609
+ "@jridgewell/sourcemap-codec": "^1.5.5"
1610
+ }
1611
+ },
1612
+ "node_modules/muggle-string": {
1613
+ "version": "0.4.1",
1614
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
1615
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
1616
+ "dev": true,
1617
+ "license": "MIT"
1618
+ },
1619
+ "node_modules/nanoid": {
1620
+ "version": "3.3.16",
1621
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
1622
+ "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
1623
+ "funding": [
1624
+ {
1625
+ "type": "github",
1626
+ "url": "https://github.com/sponsors/ai"
1627
+ }
1628
+ ],
1629
+ "license": "MIT",
1630
+ "bin": {
1631
+ "nanoid": "bin/nanoid.cjs"
1632
+ },
1633
+ "engines": {
1634
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
1635
+ }
1636
+ },
1637
+ "node_modules/path-browserify": {
1638
+ "version": "1.0.1",
1639
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
1640
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
1641
+ "dev": true,
1642
+ "license": "MIT"
1643
+ },
1644
+ "node_modules/picocolors": {
1645
+ "version": "1.1.1",
1646
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
1647
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
1648
+ "license": "ISC"
1649
+ },
1650
+ "node_modules/picomatch": {
1651
+ "version": "4.0.5",
1652
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
1653
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
1654
+ "dev": true,
1655
+ "license": "MIT",
1656
+ "engines": {
1657
+ "node": ">=12"
1658
+ },
1659
+ "funding": {
1660
+ "url": "https://github.com/sponsors/jonschlinkert"
1661
+ }
1662
+ },
1663
+ "node_modules/postcss": {
1664
+ "version": "8.5.22",
1665
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.22.tgz",
1666
+ "integrity": "sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==",
1667
+ "funding": [
1668
+ {
1669
+ "type": "opencollective",
1670
+ "url": "https://opencollective.com/postcss/"
1671
+ },
1672
+ {
1673
+ "type": "tidelift",
1674
+ "url": "https://tidelift.com/funding/github/npm/postcss"
1675
+ },
1676
+ {
1677
+ "type": "github",
1678
+ "url": "https://github.com/sponsors/ai"
1679
+ }
1680
+ ],
1681
+ "license": "MIT",
1682
+ "dependencies": {
1683
+ "nanoid": "^3.3.16",
1684
+ "picocolors": "^1.1.1",
1685
+ "source-map-js": "^1.2.1"
1686
+ },
1687
+ "engines": {
1688
+ "node": "^10 || ^12 || >=14"
1689
+ }
1690
+ },
1691
+ "node_modules/rollup": {
1692
+ "version": "4.62.2",
1693
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
1694
+ "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
1695
+ "dev": true,
1696
+ "license": "MIT",
1697
+ "dependencies": {
1698
+ "@types/estree": "1.0.9"
1699
+ },
1700
+ "bin": {
1701
+ "rollup": "dist/bin/rollup"
1702
+ },
1703
+ "engines": {
1704
+ "node": ">=18.0.0",
1705
+ "npm": ">=8.0.0"
1706
+ },
1707
+ "optionalDependencies": {
1708
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
1709
+ "@rollup/rollup-android-arm64": "4.62.2",
1710
+ "@rollup/rollup-darwin-arm64": "4.62.2",
1711
+ "@rollup/rollup-darwin-x64": "4.62.2",
1712
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
1713
+ "@rollup/rollup-freebsd-x64": "4.62.2",
1714
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
1715
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
1716
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
1717
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
1718
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
1719
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
1720
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
1721
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
1722
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
1723
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
1724
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
1725
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
1726
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
1727
+ "@rollup/rollup-openbsd-x64": "4.62.2",
1728
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
1729
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
1730
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
1731
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
1732
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
1733
+ "fsevents": "~2.3.2"
1734
+ }
1735
+ },
1736
+ "node_modules/source-map-js": {
1737
+ "version": "1.2.1",
1738
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
1739
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
1740
+ "license": "BSD-3-Clause",
1741
+ "engines": {
1742
+ "node": ">=0.10.0"
1743
+ }
1744
+ },
1745
+ "node_modules/tinyglobby": {
1746
+ "version": "0.2.17",
1747
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
1748
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
1749
+ "dev": true,
1750
+ "license": "MIT",
1751
+ "dependencies": {
1752
+ "fdir": "^6.5.0",
1753
+ "picomatch": "^4.0.4"
1754
+ },
1755
+ "engines": {
1756
+ "node": ">=12.0.0"
1757
+ },
1758
+ "funding": {
1759
+ "url": "https://github.com/sponsors/SuperchupuDev"
1760
+ }
1761
+ },
1762
+ "node_modules/tslib": {
1763
+ "version": "2.8.1",
1764
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
1765
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
1766
+ "license": "0BSD"
1767
+ },
1768
+ "node_modules/typescript": {
1769
+ "version": "5.9.3",
1770
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
1771
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
1772
+ "devOptional": true,
1773
+ "license": "Apache-2.0",
1774
+ "bin": {
1775
+ "tsc": "bin/tsc",
1776
+ "tsserver": "bin/tsserver"
1777
+ },
1778
+ "engines": {
1779
+ "node": ">=14.17"
1780
+ }
1781
+ },
1782
+ "node_modules/undici-types": {
1783
+ "version": "6.21.0",
1784
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
1785
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
1786
+ "dev": true,
1787
+ "license": "MIT"
1788
+ },
1789
+ "node_modules/vite": {
1790
+ "version": "6.4.3",
1791
+ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz",
1792
+ "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==",
1793
+ "dev": true,
1794
+ "license": "MIT",
1795
+ "dependencies": {
1796
+ "esbuild": "^0.25.0",
1797
+ "fdir": "^6.4.4",
1798
+ "picomatch": "^4.0.2",
1799
+ "postcss": "^8.5.3",
1800
+ "rollup": "^4.34.9",
1801
+ "tinyglobby": "^0.2.13"
1802
+ },
1803
+ "bin": {
1804
+ "vite": "bin/vite.js"
1805
+ },
1806
+ "engines": {
1807
+ "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
1808
+ },
1809
+ "funding": {
1810
+ "url": "https://github.com/vitejs/vite?sponsor=1"
1811
+ },
1812
+ "optionalDependencies": {
1813
+ "fsevents": "~2.3.3"
1814
+ },
1815
+ "peerDependencies": {
1816
+ "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
1817
+ "jiti": ">=1.21.0",
1818
+ "less": "*",
1819
+ "lightningcss": "^1.21.0",
1820
+ "sass": "*",
1821
+ "sass-embedded": "*",
1822
+ "stylus": "*",
1823
+ "sugarss": "*",
1824
+ "terser": "^5.16.0",
1825
+ "tsx": "^4.8.1",
1826
+ "yaml": "^2.4.2"
1827
+ },
1828
+ "peerDependenciesMeta": {
1829
+ "@types/node": {
1830
+ "optional": true
1831
+ },
1832
+ "jiti": {
1833
+ "optional": true
1834
+ },
1835
+ "less": {
1836
+ "optional": true
1837
+ },
1838
+ "lightningcss": {
1839
+ "optional": true
1840
+ },
1841
+ "sass": {
1842
+ "optional": true
1843
+ },
1844
+ "sass-embedded": {
1845
+ "optional": true
1846
+ },
1847
+ "stylus": {
1848
+ "optional": true
1849
+ },
1850
+ "sugarss": {
1851
+ "optional": true
1852
+ },
1853
+ "terser": {
1854
+ "optional": true
1855
+ },
1856
+ "tsx": {
1857
+ "optional": true
1858
+ },
1859
+ "yaml": {
1860
+ "optional": true
1861
+ }
1862
+ }
1863
+ },
1864
+ "node_modules/vscode-uri": {
1865
+ "version": "3.1.0",
1866
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
1867
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
1868
+ "dev": true,
1869
+ "license": "MIT"
1870
+ },
1871
+ "node_modules/vue": {
1872
+ "version": "3.5.40",
1873
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz",
1874
+ "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==",
1875
+ "license": "MIT",
1876
+ "dependencies": {
1877
+ "@vue/compiler-dom": "3.5.40",
1878
+ "@vue/compiler-sfc": "3.5.40",
1879
+ "@vue/runtime-dom": "3.5.40",
1880
+ "@vue/server-renderer": "3.5.40",
1881
+ "@vue/shared": "3.5.40"
1882
+ },
1883
+ "peerDependencies": {
1884
+ "typescript": "*"
1885
+ },
1886
+ "peerDependenciesMeta": {
1887
+ "typescript": {
1888
+ "optional": true
1889
+ }
1890
+ }
1891
+ },
1892
+ "node_modules/vue-tsc": {
1893
+ "version": "3.3.7",
1894
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.7.tgz",
1895
+ "integrity": "sha512-+C+rgD49wAQ5bUTl2sp5a8Bzg4YoldMNXM+g7CFe604MYcQ8PrZPMQhIjJSzKXtPBCa+C5ayMipqjbA7splekQ==",
1896
+ "dev": true,
1897
+ "license": "MIT",
1898
+ "dependencies": {
1899
+ "@volar/typescript": "2.4.28",
1900
+ "@vue/language-core": "3.3.7"
1901
+ },
1902
+ "bin": {
1903
+ "vue-tsc": "bin/vue-tsc.js"
1904
+ },
1905
+ "peerDependencies": {
1906
+ "typescript": ">=5.0.0"
1907
+ }
1908
+ }
1909
+ }
1910
+ }
apps/results-dashboard/package.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "autocad-bench-results-dashboard",
3
+ "private": true,
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --host 127.0.0.1 --port 4273",
8
+ "build": "vue-tsc -b && vite build",
9
+ "test": "node --test tests/*.test.mjs",
10
+ "preview": "node server.mjs"
11
+ },
12
+ "dependencies": {
13
+ "@aws-sdk/client-s3": "3.1090.0",
14
+ "@aws-sdk/credential-providers": "3.1090.0",
15
+ "@aws-sdk/s3-request-presigner": "3.1090.0",
16
+ "vue": "3.5.40"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "20.19.43",
20
+ "@vitejs/plugin-vue": "5.2.4",
21
+ "typescript": "5.9.3",
22
+ "vite": "6.4.3",
23
+ "vue-tsc": "3.3.7"
24
+ }
25
+ }
apps/results-dashboard/results-api.mjs ADDED
@@ -0,0 +1,714 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ GetObjectCommand,
3
+ ListObjectsV2Command,
4
+ } from '@aws-sdk/client-s3'
5
+
6
+ const MANIFEST_SUFFIX = 'aggregate/manifest.json'
7
+ const DEFAULT_CACHE_TTL_MS = 5 * 60_000
8
+ const DEFAULT_TRACE_CACHE_ENTRIES = 12
9
+
10
+ function asNumber(value) {
11
+ if (value == null || value === '') return null
12
+ const parsed = Number(value)
13
+ return Number.isFinite(parsed) ? parsed : null
14
+ }
15
+
16
+ function mean(values) {
17
+ const usable = values.filter((value) => Number.isFinite(value))
18
+ return usable.length ? usable.reduce((sum, value) => sum + value, 0) / usable.length : null
19
+ }
20
+
21
+ function median(values) {
22
+ const usable = values.filter((value) => Number.isFinite(value)).sort((a, b) => a - b)
23
+ if (!usable.length) return null
24
+ const middle = Math.floor(usable.length / 2)
25
+ return usable.length % 2 ? usable[middle] : (usable[middle - 1] + usable[middle]) / 2
26
+ }
27
+
28
+ function slugify(value) {
29
+ return String(value || 'model')
30
+ .toLowerCase()
31
+ .replace(/[^a-z0-9]+/g, '-')
32
+ .replace(/^-+|-+$/g, '')
33
+ }
34
+
35
+ const CANONICAL_TASK_RANGES = [
36
+ { first: 1, last: 3, drawingType: '2d', level: 'basic' },
37
+ { first: 4, last: 9, drawingType: '2d', level: 'intermediate' },
38
+ { first: 10, last: 15, drawingType: '2d', level: 'advanced' },
39
+ { first: 16, last: 21, drawingType: '2d', level: 'project' },
40
+ { first: 22, last: 28, drawingType: '3d', level: 'basic' },
41
+ { first: 29, last: 34, drawingType: '3d', level: 'intermediate' },
42
+ { first: 35, last: 42, drawingType: '3d', level: 'advanced' },
43
+ { first: 43, last: 50, drawingType: '3d', level: 'project' },
44
+ ]
45
+
46
+ function taskDimensions(taskId) {
47
+ const canonical = String(taskId).match(/^task-(\d{3})$/)
48
+ if (canonical) {
49
+ const number = Number(canonical[1])
50
+ const range = CANONICAL_TASK_RANGES.find(
51
+ (candidate) => number >= candidate.first && number <= candidate.last,
52
+ )
53
+ if (range) return { drawingType: range.drawingType, level: range.level }
54
+ }
55
+
56
+ // Preserve readability for imported result manifests created before the
57
+ // benchmark switched to opaque, globally consecutive IDs.
58
+ const drawingType = taskId.startsWith('3d-') ? '3d' : '2d'
59
+ const withoutDimension = taskId.replace(/^3d-/, '')
60
+ const level = withoutDimension.split('-')[0] || 'other'
61
+ return { drawingType, level }
62
+ }
63
+
64
+ function scoreStats(values) {
65
+ const usable = values.filter((value) => Number.isFinite(value))
66
+ return {
67
+ count: usable.length,
68
+ mean: mean(usable),
69
+ median: median(usable),
70
+ min: usable.length ? Math.min(...usable) : null,
71
+ max: usable.length ? Math.max(...usable) : null,
72
+ }
73
+ }
74
+
75
+ function histogram(values, buckets = 10) {
76
+ const counts = Array.from({ length: buckets }, () => 0)
77
+ for (const value of values) {
78
+ if (!Number.isFinite(value)) continue
79
+ const index = Math.max(0, Math.min(buckets - 1, Math.floor(value * buckets)))
80
+ counts[index] += 1
81
+ }
82
+ return counts
83
+ }
84
+
85
+ export function normalizeManifest(manifest, { manifestKey = MANIFEST_SUFFIX } = {}) {
86
+ const modelPrefix = manifestKey.slice(0, -MANIFEST_SUFFIX.length)
87
+ const modelId = String(manifest.model_id || modelPrefix.replace(/\/$/, '') || 'unknown')
88
+ const slug = modelPrefix ? slugify(modelPrefix.replace(/\/$/, '')) : slugify(modelId)
89
+ const tasks = (Array.isArray(manifest.tasks) ? manifest.tasks : []).map((task, index) => {
90
+ const taskId = String(task.task_id || `task-${index + 1}`)
91
+ const derivedDimensions = taskDimensions(taskId)
92
+ const drawingType = String(task.drawing_type || derivedDimensions.drawingType).toLowerCase() === '3d'
93
+ ? '3d'
94
+ : '2d'
95
+ const level = String(task.level || derivedDimensions.level)
96
+ const executionStatus = String(
97
+ task.execution_status || task.execution_state || task.controller_state || 'unknown',
98
+ )
99
+ const scoreIsVision = !task.score_source || task.score_source === 'vision_judge'
100
+ return {
101
+ index,
102
+ taskId,
103
+ drawingType,
104
+ level,
105
+ category: `${drawingType}-${level}`,
106
+ included: task.included !== false,
107
+ executionStatus,
108
+ evaluationStatus: String(task.evaluation_status || task.evaluation_stage || 'unknown'),
109
+ selectedAttempt: String(task.selected_attempt || 'selected'),
110
+ selectedRunId: String(task.selected_run_id || ''),
111
+ destinationPrefix: String(task.destination_prefix || ''),
112
+ sourcePrefix: String(task.source_prefix || ''),
113
+ objectCount: asNumber(task.object_count) || 0,
114
+ bytes: asNumber(task.bytes) || 0,
115
+ deterministicScore: asNumber(task.deterministic_score),
116
+ deterministicValid: task.deterministic_valid !== false,
117
+ visionScore: asNumber(task.vision_score ?? (scoreIsVision ? task.score : null)),
118
+ visionVerdict: String(task.vision_verdict || task.outcome || 'unavailable'),
119
+ evaluatorVersion: String(task.evaluator_version || ''),
120
+ controllerClean: typeof task.controller_clean === 'boolean'
121
+ ? task.controller_clean
122
+ : executionStatus === 'completed',
123
+ exclusionReason: task.exclusion_reason ? String(task.exclusion_reason) : null,
124
+ }
125
+ })
126
+ const included = tasks.filter((task) => task.included)
127
+ const categoryMap = new Map()
128
+ for (const task of included) {
129
+ if (!categoryMap.has(task.category)) categoryMap.set(task.category, [])
130
+ categoryMap.get(task.category).push(task)
131
+ }
132
+ const categories = [...categoryMap.entries()]
133
+ .map(([category, members]) => ({
134
+ category,
135
+ drawingType: members[0].drawingType,
136
+ level: members[0].level,
137
+ tasks: members.length,
138
+ deterministic: scoreStats(members.map((task) => task.deterministicScore)),
139
+ vision: scoreStats(members.map((task) => task.visionScore)),
140
+ }))
141
+ .sort((a, b) => a.category.localeCompare(b.category))
142
+ const deterministicValues = included.map((task) => task.deterministicScore)
143
+ const visionValues = included.map((task) => task.visionScore)
144
+ return {
145
+ slug,
146
+ manifestKey,
147
+ modelPrefix,
148
+ modelId,
149
+ aggregateId: String(manifest.aggregate_id || ''),
150
+ createdAt: manifest.created_at ? String(manifest.created_at) : null,
151
+ sourceBucket: String(manifest.source_bucket || ''),
152
+ destinationBucket: String(manifest.destination_bucket || ''),
153
+ evaluatorVersion: String(included.find((task) => task.evaluatorVersion)?.evaluatorVersion || ''),
154
+ tasks,
155
+ categories,
156
+ totals: {
157
+ expected: asNumber(manifest.summary?.expected_tasks) ?? tasks.length,
158
+ included: included.length,
159
+ excluded: tasks.length - included.length,
160
+ clean: included.filter((task) => task.controllerClean).length,
161
+ budgetExhausted: included.filter((task) => task.executionStatus === 'budget_exhausted').length,
162
+ bytes: included.reduce((sum, task) => sum + task.bytes, 0),
163
+ objects: included.reduce((sum, task) => sum + task.objectCount, 0),
164
+ },
165
+ scores: {
166
+ deterministic: scoreStats(deterministicValues),
167
+ vision: scoreStats(visionValues),
168
+ deterministicHistogram: histogram(deterministicValues),
169
+ visionHistogram: histogram(visionValues),
170
+ },
171
+ selectionPolicy: manifest.selection_policy || null,
172
+ sourceRuns: Array.isArray(manifest.source_runs) ? manifest.source_runs : [],
173
+ }
174
+ }
175
+
176
+ function parseJsonLines(text) {
177
+ const rows = []
178
+ for (const line of String(text || '').split(/\r?\n/)) {
179
+ if (!line.trim()) continue
180
+ try {
181
+ rows.push(JSON.parse(line))
182
+ } catch {
183
+ // A partially written final line must not hide the rest of the trace.
184
+ }
185
+ }
186
+ return rows
187
+ }
188
+
189
+ function imageSequence(value) {
190
+ const match = String(value || '').match(/frames\/frame_(\d+)\.png/)
191
+ return match ? Number(match[1]) : null
192
+ }
193
+
194
+ function compactReceipt(receipt) {
195
+ return {
196
+ actionIndex: asNumber(receipt?.action_index),
197
+ batchIndex: asNumber(receipt?.batch_index),
198
+ batchSize: asNumber(receipt?.batch_size),
199
+ toolUseId: receipt?.tool_use_id || null,
200
+ action: receipt?.action || null,
201
+ receipt: receipt?.receipt || null,
202
+ requestId: receipt?.request_id || null,
203
+ idempotentReplay: Boolean(receipt?.idempotent_replay),
204
+ }
205
+ }
206
+
207
+ async function mapConcurrent(items, concurrency, mapper) {
208
+ const results = new Array(items.length)
209
+ let next = 0
210
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, async () => {
211
+ while (next < items.length) {
212
+ const index = next
213
+ next += 1
214
+ results[index] = await mapper(items[index], index)
215
+ }
216
+ }))
217
+ return results
218
+ }
219
+
220
+ function notFound(error) {
221
+ return error?.name === 'NoSuchKey'
222
+ || error?.name === 'NotFound'
223
+ || error?.$metadata?.httpStatusCode === 404
224
+ }
225
+
226
+ export class ResultsRepository {
227
+ constructor({
228
+ s3,
229
+ bucket,
230
+ signUrl,
231
+ cacheTtlMs = DEFAULT_CACHE_TTL_MS,
232
+ traceCacheEntries = DEFAULT_TRACE_CACHE_ENTRIES,
233
+ signedUrlTtlSeconds = 3600,
234
+ }) {
235
+ this.s3 = s3
236
+ this.bucket = bucket
237
+ this.signUrl = signUrl
238
+ this.cacheTtlMs = cacheTtlMs
239
+ this.traceCacheEntries = traceCacheEntries
240
+ this.signedUrlTtlSeconds = signedUrlTtlSeconds
241
+ this.cache = new Map()
242
+ this.traceCache = new Map()
243
+ }
244
+
245
+ async cached(key, loader, ttlMs = this.cacheTtlMs) {
246
+ const current = this.cache.get(key)
247
+ if (current && current.expiresAt > Date.now()) return current.value
248
+ const promise = Promise.resolve().then(loader)
249
+ this.cache.set(key, { expiresAt: Date.now() + ttlMs, value: promise })
250
+ try {
251
+ return await promise
252
+ } catch (error) {
253
+ this.cache.delete(key)
254
+ throw error
255
+ }
256
+ }
257
+
258
+ async text(key, { optional = false, ttlMs = this.cacheTtlMs } = {}) {
259
+ return this.cached(`object:${key}`, async () => {
260
+ try {
261
+ const response = await this.s3.send(new GetObjectCommand({
262
+ Bucket: this.bucket,
263
+ Key: key,
264
+ }))
265
+ if (!response.Body) return ''
266
+ if (typeof response.Body.transformToString === 'function') {
267
+ return response.Body.transformToString()
268
+ }
269
+ return String(response.Body)
270
+ } catch (error) {
271
+ if (optional && notFound(error)) return null
272
+ throw error
273
+ }
274
+ }, ttlMs)
275
+ }
276
+
277
+ async json(key, options = {}) {
278
+ const value = await this.text(key, options)
279
+ if (value == null || value === '') return null
280
+ return JSON.parse(value)
281
+ }
282
+
283
+ async discoverModels({ force = false } = {}) {
284
+ if (force) this.cache.delete('models')
285
+ return this.cached('models', async () => {
286
+ const root = await this.s3.send(new ListObjectsV2Command({
287
+ Bucket: this.bucket,
288
+ Delimiter: '/',
289
+ MaxKeys: 1000,
290
+ }))
291
+ const prefixes = (root.CommonPrefixes || [])
292
+ .map((entry) => String(entry.Prefix || ''))
293
+ .filter((prefix) => prefix && !['aggregate/', 'artifacts/', 'batch/'].includes(prefix))
294
+ const candidates = [MANIFEST_SUFFIX, ...prefixes.map((prefix) => `${prefix}${MANIFEST_SUFFIX}`)]
295
+ const manifests = await Promise.all(candidates.map(async (manifestKey) => {
296
+ try {
297
+ const manifest = await this.json(manifestKey)
298
+ return manifest ? normalizeManifest(manifest, { manifestKey }) : null
299
+ } catch (error) {
300
+ if (notFound(error)) return null
301
+ throw error
302
+ }
303
+ }))
304
+ return manifests.filter(Boolean).sort((a, b) => a.modelId.localeCompare(b.modelId))
305
+ }, 60_000)
306
+ }
307
+
308
+ async model(slug) {
309
+ const models = await this.discoverModels()
310
+ return models.find((model) => model.slug === slug) || null
311
+ }
312
+
313
+ async task(slug, taskId) {
314
+ const model = await this.model(slug)
315
+ if (!model) return { model: null, task: null }
316
+ return { model, task: model.tasks.find((candidate) => candidate.taskId === taskId) || null }
317
+ }
318
+
319
+ taskKey(task, relativePath) {
320
+ const clean = String(relativePath || '').replace(/^\/+/, '')
321
+ if (!clean || clean.includes('\\') || clean.split('/').some((part) => part === '..' || part === '.')) {
322
+ throw new Error('Unsafe task artifact path')
323
+ }
324
+ if (!task.destinationPrefix) throw new Error('Task has no finalized artifact prefix')
325
+ return `${task.destinationPrefix}${clean}`
326
+ }
327
+
328
+ async taskJson(task, relativePath, { optional = true } = {}) {
329
+ return this.json(this.taskKey(task, relativePath), { optional })
330
+ }
331
+
332
+ async modelMetrics(slug) {
333
+ const model = await this.model(slug)
334
+ if (!model) return null
335
+ const included = model.tasks.filter((task) => task.included && task.destinationPrefix)
336
+ const taskMetrics = await mapConcurrent(included, 12, async (task) => {
337
+ const [result, state, evaluationScore, evaluationStatus] = await Promise.all([
338
+ this.taskJson(task, 'result.json'),
339
+ this.taskJson(task, 'run-state.json'),
340
+ this.taskJson(task, 'evaluation/score.json'),
341
+ this.taskJson(task, 'evaluation/status.json'),
342
+ ])
343
+ const usage = result?.usage || state?.usage || {}
344
+ const inputTokens = asNumber(usage.input_tokens)
345
+ const cachedInputTokens = asNumber(usage.cached_input_tokens ?? usage.cache_read_input_tokens)
346
+ const cacheCreationTokens = asNumber(usage.cache_creation_input_tokens)
347
+ const outputTokens = asNumber(usage.output_tokens)
348
+ const reportedTotalTokens = asNumber(usage.total_tokens)
349
+ return {
350
+ taskId: task.taskId,
351
+ drawingType: task.drawingType,
352
+ level: task.level,
353
+ acceptedActions: asNumber(result?.accepted_actions ?? state?.accepted_actions),
354
+ elapsedSeconds: asNumber(result?.elapsed_s),
355
+ rolloutSeconds: asNumber(result?.rollout_elapsed_s),
356
+ turns: asNumber(state?.turn_count),
357
+ screenshots: asNumber(result?.screenshots ?? state?.screenshots),
358
+ inputTokens,
359
+ cachedInputTokens,
360
+ cacheCreationTokens,
361
+ outputTokens,
362
+ reasoningTokens: asNumber(usage.reasoning_output_tokens),
363
+ totalTokens: reportedTotalTokens
364
+ ?? [inputTokens, cachedInputTokens, cacheCreationTokens, outputTokens]
365
+ .reduce((total, value) => total + (value || 0), 0),
366
+ artifactBytes: asNumber(result?.artifact_bytes),
367
+ stopReason: String(result?.stop_reason || state?.stop_reason || ''),
368
+ deterministicScore: task.deterministicScore ?? asNumber(evaluationScore?.score),
369
+ visionScore: task.visionScore
370
+ ?? asNumber(evaluationStatus?.vision_score ?? result?.evaluation?.vision_score),
371
+ visionVerdict: task.visionVerdict !== 'unavailable'
372
+ ? task.visionVerdict
373
+ : String(evaluationStatus?.vision_verdict || result?.evaluation?.vision_verdict || 'unavailable'),
374
+ }
375
+ })
376
+ const sum = (key) => taskMetrics.reduce((total, task) => total + (task[key] || 0), 0)
377
+ const categoryMap = new Map()
378
+ for (const task of taskMetrics) {
379
+ const category = `${task.drawingType}-${task.level}`
380
+ if (!categoryMap.has(category)) categoryMap.set(category, [])
381
+ categoryMap.get(category).push(task)
382
+ }
383
+ const deterministicValues = taskMetrics.map((task) => task.deterministicScore)
384
+ const visionValues = taskMetrics.map((task) => task.visionScore)
385
+ return {
386
+ tasks: taskMetrics,
387
+ totals: {
388
+ acceptedActions: sum('acceptedActions'),
389
+ elapsedSeconds: sum('elapsedSeconds'),
390
+ turns: sum('turns'),
391
+ screenshots: sum('screenshots'),
392
+ inputTokens: sum('inputTokens'),
393
+ cachedInputTokens: sum('cachedInputTokens'),
394
+ cacheCreationTokens: sum('cacheCreationTokens'),
395
+ outputTokens: sum('outputTokens'),
396
+ reasoningTokens: sum('reasoningTokens'),
397
+ totalTokens: sum('totalTokens'),
398
+ },
399
+ medians: {
400
+ acceptedActions: median(taskMetrics.map((task) => task.acceptedActions)),
401
+ elapsedSeconds: median(taskMetrics.map((task) => task.elapsedSeconds)),
402
+ turns: median(taskMetrics.map((task) => task.turns)),
403
+ },
404
+ scores: {
405
+ deterministic: scoreStats(deterministicValues),
406
+ vision: scoreStats(visionValues),
407
+ deterministicHistogram: histogram(deterministicValues),
408
+ visionHistogram: histogram(visionValues),
409
+ },
410
+ categories: [...categoryMap.entries()].map(([category, members]) => ({
411
+ category,
412
+ drawingType: members[0].drawingType,
413
+ level: members[0].level,
414
+ tasks: members.length,
415
+ deterministic: scoreStats(members.map((task) => task.deterministicScore)),
416
+ vision: scoreStats(members.map((task) => task.visionScore)),
417
+ })).sort((a, b) => a.category.localeCompare(b.category)),
418
+ }
419
+ }
420
+
421
+ async taskDetail(slug, taskId) {
422
+ const { model, task } = await this.task(slug, taskId)
423
+ if (!model || !task) return null
424
+ const [result, runState, evaluationScore, evaluationStatus, visionScore] = await Promise.all([
425
+ this.taskJson(task, 'result.json'),
426
+ this.taskJson(task, 'run-state.json'),
427
+ this.taskJson(task, 'evaluation/score.json'),
428
+ this.taskJson(task, 'evaluation/status.json'),
429
+ this.taskJson(task, 'evaluation/vision-judge/score.json'),
430
+ ])
431
+ return {
432
+ model: {
433
+ slug: model.slug,
434
+ modelId: model.modelId,
435
+ aggregateId: model.aggregateId,
436
+ },
437
+ task,
438
+ result,
439
+ runState,
440
+ evaluationScore,
441
+ evaluationStatus,
442
+ visionScore,
443
+ frameCount: asNumber(result?.screenshots ?? runState?.screenshots) || 0,
444
+ turnCount: asNumber(runState?.turn_count) || 0,
445
+ media: {
446
+ reference: 'reference.png',
447
+ final: 'evaluation/render.png',
448
+ diff: 'evaluation/diff.png',
449
+ dwg: 'attempt.dwg',
450
+ },
451
+ downloads: [
452
+ ['Final DWG', 'attempt.dwg'],
453
+ ['Result', 'result.json'],
454
+ ['Run state', 'run-state.json'],
455
+ ['Model turns', 'model_turns.jsonl'],
456
+ ['Turn summaries', 'model_turn_summaries.jsonl'],
457
+ ['Action receipts', 'input_receipts.jsonl'],
458
+ ['Events', 'events.json'],
459
+ ['Frame events', 'frame_events.json'],
460
+ ['Evaluator score', 'evaluation/score.json'],
461
+ ['Evaluator metadata', 'evaluation/metadata.json'],
462
+ ].map(([label, path]) => ({ label, path })),
463
+ }
464
+ }
465
+
466
+ async traceBundle(task) {
467
+ const key = task.destinationPrefix
468
+ if (this.traceCache.has(key)) {
469
+ const cached = this.traceCache.get(key)
470
+ this.traceCache.delete(key)
471
+ this.traceCache.set(key, cached)
472
+ return cached
473
+ }
474
+ const bundle = Promise.all([
475
+ this.text(this.taskKey(task, 'model_turn_summaries.jsonl'), { optional: true }),
476
+ this.text(this.taskKey(task, 'input_receipts.jsonl'), { optional: true }),
477
+ ]).then(async ([turnText, receiptText]) => {
478
+ const completeTurnText = turnText || await this.text(
479
+ this.taskKey(task, 'model_turns.jsonl'),
480
+ { optional: true },
481
+ )
482
+ return {
483
+ turns: parseJsonLines(completeTurnText),
484
+ receipts: parseJsonLines(receiptText),
485
+ }
486
+ })
487
+ this.traceCache.set(key, bundle)
488
+ while (this.traceCache.size > this.traceCacheEntries) {
489
+ this.traceCache.delete(this.traceCache.keys().next().value)
490
+ }
491
+ try {
492
+ return await bundle
493
+ } catch (error) {
494
+ this.traceCache.delete(key)
495
+ throw error
496
+ }
497
+ }
498
+
499
+ async traceIndex(slug, taskId) {
500
+ const { model, task } = await this.task(slug, taskId)
501
+ if (!model || !task) return null
502
+ const bundle = await this.traceBundle(task)
503
+ const receiptsByTool = new Map()
504
+ for (const receipt of bundle.receipts) {
505
+ const key = String(receipt.tool_use_id || '')
506
+ if (!receiptsByTool.has(key)) receiptsByTool.set(key, [])
507
+ receiptsByTool.get(key).push(receipt)
508
+ }
509
+ const turns = bundle.turns.map((turn, index) => {
510
+ const toolUseId = String(turn.tool_use_id || '')
511
+ const receipts = receiptsByTool.get(toolUseId) || []
512
+ const actionIndices = receipts.map((receipt) => asNumber(receipt.action_index)).filter(Number.isFinite)
513
+ const artifacts = Array.isArray(turn.model_input?.artifacts) ? turn.model_input.artifacts : []
514
+ const inputArtifact = artifacts.find((artifact) => imageSequence(artifact?.artifact_path) != null)
515
+ const declaredActions = Array.isArray(turn.action?.actions)
516
+ ? turn.action.actions
517
+ : (turn.action ? [turn.action] : [])
518
+ return {
519
+ index,
520
+ turn: asNumber(turn.turn) ?? index + 1,
521
+ toolUseId,
522
+ latencyMs: asNumber(turn.latency_ms),
523
+ stopReason: String(turn.stop_reason || ''),
524
+ requestAttempts: asNumber(turn.request_attempts),
525
+ validationRetries: asNumber(turn.response_validation_retries),
526
+ discardedToolUses: asNumber(turn.discarded_tool_uses),
527
+ usage: turn.usage || null,
528
+ text: turn.text || null,
529
+ actions: declaredActions,
530
+ actionCount: receipts.length || declaredActions.length,
531
+ inputFrameSequence: imageSequence(inputArtifact?.artifact_path),
532
+ outputFrameSequence: actionIndices.length ? Math.max(...actionIndices) : null,
533
+ firstActionIndex: actionIndices.length ? Math.min(...actionIndices) : null,
534
+ lastActionIndex: actionIndices.length ? Math.max(...actionIndices) : null,
535
+ }
536
+ })
537
+ return { taskId, turns, receiptCount: bundle.receipts.length }
538
+ }
539
+
540
+ async turnDetail(slug, taskId, turnNumber) {
541
+ const { task } = await this.task(slug, taskId)
542
+ if (!task) return null
543
+ const bundle = await this.traceBundle(task)
544
+ const index = Math.max(0, Number(turnNumber) - 1)
545
+ const turn = bundle.turns.find((candidate) => Number(candidate.turn) === Number(turnNumber))
546
+ || bundle.turns[index]
547
+ if (!turn) return null
548
+ const toolUseId = String(turn.tool_use_id || '')
549
+ const receipts = bundle.receipts
550
+ .filter((receipt) => String(receipt.tool_use_id || '') === toolUseId)
551
+ .map(compactReceipt)
552
+ return { turn, receipts }
553
+ }
554
+
555
+ async framePage(slug, taskId, { offset = 0, limit = 120 } = {}) {
556
+ const detail = await this.taskDetail(slug, taskId)
557
+ if (!detail) return null
558
+ const safeOffset = Math.max(0, Math.floor(Number(offset) || 0))
559
+ const safeLimit = Math.max(1, Math.min(240, Math.floor(Number(limit) || 120)))
560
+ const end = Math.min(detail.frameCount, safeOffset + safeLimit)
561
+ return {
562
+ offset: safeOffset,
563
+ limit: safeLimit,
564
+ total: detail.frameCount,
565
+ hasMore: end < detail.frameCount,
566
+ frames: Array.from({ length: Math.max(0, end - safeOffset) }, (_, index) => {
567
+ const sequence = safeOffset + index
568
+ return {
569
+ sequence,
570
+ name: `frame_${String(sequence).padStart(4, '0')}.png`,
571
+ path: `frames/frame_${String(sequence).padStart(4, '0')}.png`,
572
+ }
573
+ }),
574
+ }
575
+ }
576
+
577
+ async mediaUrl(slug, taskId, relativePath, { download = false } = {}) {
578
+ const { task } = await this.task(slug, taskId)
579
+ if (!task) return null
580
+ const key = this.taskKey(task, relativePath)
581
+ const extension = relativePath.split('.').at(-1)?.toLowerCase()
582
+ const contentType = {
583
+ png: 'image/png',
584
+ json: 'application/json',
585
+ jsonl: 'application/x-ndjson',
586
+ dwg: 'application/acad',
587
+ }[extension] || 'application/octet-stream'
588
+ const command = new GetObjectCommand({
589
+ Bucket: this.bucket,
590
+ Key: key,
591
+ ResponseContentType: contentType,
592
+ ResponseCacheControl: relativePath.endsWith('.png')
593
+ ? 'private, max-age=31536000, immutable'
594
+ : 'private, max-age=3600',
595
+ ...(download ? {
596
+ ResponseContentDisposition: `attachment; filename="${relativePath.split('/').at(-1)}"`,
597
+ } : {}),
598
+ })
599
+ const cacheLifetime = Math.max(
600
+ 60_000,
601
+ Math.min(10 * 60_000, (this.signedUrlTtlSeconds - 60) * 1000),
602
+ )
603
+ return this.cached(
604
+ `signed:${key}:${download ? 'download' : 'inline'}`,
605
+ () => this.signUrl(command, { expiresIn: this.signedUrlTtlSeconds }),
606
+ cacheLifetime,
607
+ )
608
+ }
609
+ }
610
+
611
+ function sendJson(response, status, body, cacheControl = 'private, no-store') {
612
+ const payload = JSON.stringify(body)
613
+ response.statusCode = status
614
+ response.setHeader('Content-Type', 'application/json; charset=utf-8')
615
+ response.setHeader('Content-Length', Buffer.byteLength(payload))
616
+ response.setHeader('Cache-Control', cacheControl)
617
+ response.end(payload)
618
+ }
619
+
620
+ function decode(value) {
621
+ try {
622
+ return decodeURIComponent(value)
623
+ } catch {
624
+ return ''
625
+ }
626
+ }
627
+
628
+ export function createResultsApi({ repository }) {
629
+ return async (request, response) => {
630
+ const url = new URL(request.url || '/', 'http://results.local')
631
+ if (!url.pathname.startsWith('/api/results')) return false
632
+ try {
633
+ if (url.pathname === '/api/results/health') {
634
+ sendJson(response, 200, { ok: true, bucket: repository.bucket })
635
+ return true
636
+ }
637
+ if (url.pathname === '/api/results/models') {
638
+ const models = await repository.discoverModels({ force: url.searchParams.get('refresh') === '1' })
639
+ sendJson(response, 200, { models }, 'private, max-age=30')
640
+ return true
641
+ }
642
+ const modelMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)$/)
643
+ if (modelMatch) {
644
+ const model = await repository.model(decode(modelMatch[1]))
645
+ sendJson(response, model ? 200 : 404, model || { error: 'model_not_found' })
646
+ return true
647
+ }
648
+ const metricsMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/metrics$/)
649
+ if (metricsMatch) {
650
+ const metrics = await repository.modelMetrics(decode(metricsMatch[1]))
651
+ sendJson(response, metrics ? 200 : 404, metrics || { error: 'model_not_found' })
652
+ return true
653
+ }
654
+ const taskMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)$/)
655
+ if (taskMatch) {
656
+ const detail = await repository.taskDetail(decode(taskMatch[1]), decode(taskMatch[2]))
657
+ sendJson(response, detail ? 200 : 404, detail || { error: 'task_not_found' })
658
+ return true
659
+ }
660
+ const traceMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)\/trace$/)
661
+ if (traceMatch) {
662
+ const trace = await repository.traceIndex(decode(traceMatch[1]), decode(traceMatch[2]))
663
+ sendJson(response, trace ? 200 : 404, trace || { error: 'trace_not_found' })
664
+ return true
665
+ }
666
+ const turnMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)\/turns\/(\d+)$/)
667
+ if (turnMatch) {
668
+ const turn = await repository.turnDetail(
669
+ decode(turnMatch[1]),
670
+ decode(turnMatch[2]),
671
+ Number(turnMatch[3]),
672
+ )
673
+ sendJson(response, turn ? 200 : 404, turn || { error: 'turn_not_found' })
674
+ return true
675
+ }
676
+ const framesMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)\/frames$/)
677
+ if (framesMatch) {
678
+ const frames = await repository.framePage(decode(framesMatch[1]), decode(framesMatch[2]), {
679
+ offset: url.searchParams.get('offset'),
680
+ limit: url.searchParams.get('limit'),
681
+ })
682
+ sendJson(response, frames ? 200 : 404, frames || { error: 'task_not_found' }, 'private, max-age=300')
683
+ return true
684
+ }
685
+ const mediaMatch = url.pathname.match(/^\/api\/results\/models\/([^/]+)\/tasks\/([^/]+)\/media$/)
686
+ if (mediaMatch) {
687
+ const relativePath = url.searchParams.get('path') || ''
688
+ const signedUrl = await repository.mediaUrl(
689
+ decode(mediaMatch[1]),
690
+ decode(mediaMatch[2]),
691
+ relativePath,
692
+ { download: url.searchParams.get('download') === '1' },
693
+ )
694
+ if (!signedUrl) {
695
+ sendJson(response, 404, { error: 'task_not_found' })
696
+ return true
697
+ }
698
+ response.statusCode = 307
699
+ response.setHeader('Location', signedUrl)
700
+ response.setHeader('Cache-Control', 'private, max-age=300, stale-while-revalidate=60')
701
+ response.end()
702
+ return true
703
+ }
704
+ sendJson(response, 404, { error: 'route_not_found' })
705
+ return true
706
+ } catch (error) {
707
+ sendJson(response, 500, {
708
+ error: 'results_api_failed',
709
+ message: error instanceof Error ? error.message : String(error),
710
+ })
711
+ return true
712
+ }
713
+ }
714
+ }
apps/results-dashboard/server.mjs ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'node:fs'
2
+ import http from 'node:http'
3
+ import path from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { S3Client } from '@aws-sdk/client-s3'
6
+ import { fromIni } from '@aws-sdk/credential-providers'
7
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
8
+ import { createResultsApi, ResultsRepository } from './results-api.mjs'
9
+
10
+ const root = path.dirname(fileURLToPath(import.meta.url))
11
+ const dist = path.join(root, 'dist')
12
+ const host = process.env.HOST || '127.0.0.1'
13
+ const port = Number(process.env.PORT || 4273)
14
+ const region = process.env.AWS_REGION || ''
15
+ const profile = process.env.AWS_PROFILE || ''
16
+ const bucket = process.env.AUTOCAD_RESULTS_BUCKET || ''
17
+ if (!bucket) {
18
+ throw new Error('AUTOCAD_RESULTS_BUCKET is required')
19
+ }
20
+ const signedUrlTtlSeconds = Math.max(
21
+ 300,
22
+ Math.min(604800, Number(process.env.AUTOCAD_RESULTS_SIGNED_URL_TTL_SECONDS || 3600)),
23
+ )
24
+
25
+ const s3 = new S3Client({
26
+ ...(region ? { region } : {}),
27
+ ...(profile ? { credentials: fromIni({ profile }) } : {}),
28
+ maxAttempts: 5,
29
+ })
30
+ const repository = new ResultsRepository({
31
+ s3,
32
+ bucket,
33
+ signedUrlTtlSeconds,
34
+ signUrl: (command, options) => getSignedUrl(s3, command, options),
35
+ })
36
+ const api = createResultsApi({ repository })
37
+
38
+ const mime = {
39
+ '.css': 'text/css; charset=utf-8',
40
+ '.html': 'text/html; charset=utf-8',
41
+ '.js': 'text/javascript; charset=utf-8',
42
+ '.json': 'application/json; charset=utf-8',
43
+ '.map': 'application/json; charset=utf-8',
44
+ '.png': 'image/png',
45
+ '.woff2': 'font/woff2',
46
+ }
47
+
48
+ http.createServer(async (request, response) => {
49
+ if (await api(request, response)) return
50
+ const pathname = decodeURIComponent(new URL(request.url || '/', `http://${host}`).pathname)
51
+ const requested = path.resolve(dist, pathname.replace(/^\/+/, ''))
52
+ const safe = requested.startsWith(`${dist}${path.sep}`) ? requested : null
53
+ const file = safe && fs.existsSync(safe) && fs.statSync(safe).isFile()
54
+ ? safe
55
+ : path.join(dist, 'index.html')
56
+ if (!fs.existsSync(file)) {
57
+ response.statusCode = 503
58
+ response.setHeader('Content-Type', 'text/plain; charset=utf-8')
59
+ response.end('Dashboard build not found. Run npm run build first.')
60
+ return
61
+ }
62
+ response.statusCode = 200
63
+ response.setHeader('Content-Type', mime[path.extname(file)] || 'application/octet-stream')
64
+ response.setHeader(
65
+ 'Cache-Control',
66
+ path.basename(file) === 'index.html' ? 'no-cache' : 'public, max-age=31536000, immutable',
67
+ )
68
+ fs.createReadStream(file).pipe(response)
69
+ }).listen(port, host, () => {
70
+ process.stdout.write(`AutoCAD Results: http://${host}:${port}\n`)
71
+ })
apps/results-dashboard/src/App.vue ADDED
@@ -0,0 +1,831 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup lang="ts">
2
+ import { computed, nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
3
+ import ReliableImage from './components/ReliableImage.vue'
4
+ import type {
5
+ Frame,
6
+ FramePage,
7
+ ModelMetrics,
8
+ ModelResult,
9
+ ResultTask,
10
+ TaskDetail,
11
+ TraceIndex,
12
+ TraceTurn,
13
+ TurnDetail,
14
+ } from './types'
15
+
16
+ type View = 'overview' | 'tasks' | 'trace' | 'frames'
17
+ type ExpandedImage = { src: string; label: string }
18
+
19
+ const models = ref<ModelResult[]>([])
20
+ const selectedModelSlug = ref('')
21
+ const selectedTaskId = ref('')
22
+ const view = ref<View>('overview')
23
+ const metrics = ref<ModelMetrics | null>(null)
24
+ const taskDetail = ref<TaskDetail | null>(null)
25
+ const trace = ref<TraceIndex | null>(null)
26
+ const selectedTurnIndex = ref(0)
27
+ const turnDetail = ref<TurnDetail | null>(null)
28
+ const frames = ref<Frame[]>([])
29
+ const frameTotal = ref(0)
30
+ const frameHasMore = ref(false)
31
+
32
+ const loadingModels = ref(true)
33
+ const loadingMetrics = ref(false)
34
+ const loadingTask = ref(false)
35
+ const loadingTurn = ref(false)
36
+ const loadingFrames = ref(false)
37
+ const error = ref('')
38
+ const expandedImage = ref<ExpandedImage | null>(null)
39
+ const taskSearch = ref('')
40
+ const taskDimension = ref('all')
41
+ const taskLevel = ref('all')
42
+ const taskStatus = ref('all')
43
+ const taskSort = ref('task')
44
+
45
+ let taskLoadVersion = 0
46
+ let turnLoadVersion = 0
47
+
48
+ async function fetchJson<T>(url: string, timeoutMs = 30_000): Promise<T> {
49
+ let lastError: unknown
50
+ for (let attempt = 0; attempt < 3; attempt += 1) {
51
+ const controller = new AbortController()
52
+ const timeout = window.setTimeout(() => controller.abort(), timeoutMs)
53
+ try {
54
+ const response = await fetch(url, { cache: 'no-store', signal: controller.signal })
55
+ if (!response.ok) throw new Error(`Request failed (HTTP ${response.status})`)
56
+ return await response.json() as T
57
+ } catch (reason) {
58
+ lastError = reason
59
+ if (attempt < 2) await new Promise((resolve) => window.setTimeout(resolve, 250 * 2 ** attempt))
60
+ } finally {
61
+ window.clearTimeout(timeout)
62
+ }
63
+ }
64
+ throw lastError instanceof Error ? lastError : new Error(String(lastError))
65
+ }
66
+
67
+ const selectedModel = computed(() =>
68
+ models.value.find((model) => model.slug === selectedModelSlug.value) || null,
69
+ )
70
+
71
+ const selectedTask = computed(() =>
72
+ selectedModel.value?.tasks.find((task) => task.taskId === selectedTaskId.value) || null,
73
+ )
74
+
75
+ const selectedTurn = computed<TraceTurn | null>(() =>
76
+ trace.value?.turns[selectedTurnIndex.value] || null,
77
+ )
78
+
79
+ const metricByTask = computed(() => new Map(
80
+ (metrics.value?.tasks || []).map((metric) => [metric.taskId, metric]),
81
+ ))
82
+
83
+ const activeScores = computed(() => metrics.value?.scores || selectedModel.value?.scores || null)
84
+ const activeCategories = computed(() => metrics.value?.categories || selectedModel.value?.categories || [])
85
+
86
+ const selectedVisionScore = computed(() => {
87
+ if (!selectedTask.value) return null
88
+ return metricByTask.value.get(selectedTask.value.taskId)?.visionScore
89
+ ?? selectedTask.value.visionScore
90
+ ?? numberField(taskDetail.value?.evaluationStatus || null, 'vision_score')
91
+ })
92
+
93
+ const selectedDeterministicScore = computed(() => {
94
+ if (!selectedTask.value) return null
95
+ return metricByTask.value.get(selectedTask.value.taskId)?.deterministicScore
96
+ ?? selectedTask.value.deterministicScore
97
+ ?? numberField(taskDetail.value?.evaluationScore || null, 'score')
98
+ })
99
+
100
+ const levels = computed(() =>
101
+ [...new Set(selectedModel.value?.tasks.map((task) => task.level) || [])].sort(),
102
+ )
103
+
104
+ const statuses = computed(() =>
105
+ [...new Set(selectedModel.value?.tasks.map((task) => task.executionStatus) || [])].sort(),
106
+ )
107
+
108
+ const filteredTasks = computed(() => {
109
+ const query = taskSearch.value.trim().toLowerCase()
110
+ const result = (selectedModel.value?.tasks || []).filter((task) => (
111
+ (!query || task.taskId.toLowerCase().includes(query))
112
+ && (taskDimension.value === 'all' || task.drawingType === taskDimension.value)
113
+ && (taskLevel.value === 'all' || task.level === taskLevel.value)
114
+ && (taskStatus.value === 'all' || task.executionStatus === taskStatus.value)
115
+ ))
116
+ return [...result].sort((a, b) => {
117
+ if (taskSort.value === 'vision') return (taskVisionScore(b) ?? -1) - (taskVisionScore(a) ?? -1)
118
+ if (taskSort.value === 'deterministic') {
119
+ return (taskDeterministicScore(b) ?? -1) - (taskDeterministicScore(a) ?? -1)
120
+ }
121
+ if (taskSort.value === 'actions') {
122
+ return (metricByTask.value.get(b.taskId)?.acceptedActions ?? -1)
123
+ - (metricByTask.value.get(a.taskId)?.acceptedActions ?? -1)
124
+ }
125
+ return a.taskId.localeCompare(b.taskId, undefined, { numeric: true })
126
+ })
127
+ })
128
+
129
+ const suiteTotals = computed(() => ({
130
+ models: models.value.length,
131
+ tasks: models.value.reduce((sum, model) => sum + model.totals.included, 0),
132
+ objects: models.value.reduce((sum, model) => sum + model.totals.objects, 0),
133
+ bytes: models.value.reduce((sum, model) => sum + model.totals.bytes, 0),
134
+ }))
135
+
136
+ const scoreBreakdown = computed(() => {
137
+ const score = taskDetail.value?.evaluationScore || {}
138
+ return [
139
+ ['Linework', numberField(score, 'linework_similarity')],
140
+ ['Geometry', numberField(score, 'geometry_similarity')],
141
+ ['Extents', numberField(score, 'geometry_extent_similarity') ?? numberField(score, 'extent_similarity')],
142
+ ['Dimensions', numberField(score, 'dimension_similarity')],
143
+ ['Annotations', numberField(score, 'annotation_similarity')],
144
+ ] as Array<[string, number | null]>
145
+ })
146
+
147
+ function numberField(value: Record<string, unknown> | null, key: string): number | null {
148
+ const number = Number(value?.[key])
149
+ return Number.isFinite(number) ? number : null
150
+ }
151
+
152
+ function readRoute() {
153
+ const params = new URLSearchParams(window.location.hash.replace(/^#/, ''))
154
+ return {
155
+ model: params.get('model') || '',
156
+ task: params.get('task') || '',
157
+ view: (params.get('view') || 'overview') as View,
158
+ turn: Math.max(1, Number(params.get('turn') || 1)),
159
+ }
160
+ }
161
+
162
+ function writeRoute() {
163
+ const params = new URLSearchParams()
164
+ if (selectedModelSlug.value) params.set('model', selectedModelSlug.value)
165
+ if (selectedTaskId.value) params.set('task', selectedTaskId.value)
166
+ params.set('view', view.value)
167
+ if (view.value === 'trace' && selectedTurn.value) params.set('turn', String(selectedTurn.value.turn))
168
+ window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}#${params}`)
169
+ }
170
+
171
+ async function loadModels() {
172
+ loadingModels.value = true
173
+ error.value = ''
174
+ try {
175
+ const response = await fetchJson<{ models: ModelResult[] }>('/api/results/models')
176
+ models.value = response.models
177
+ const route = readRoute()
178
+ selectedModelSlug.value = models.value.some((model) => model.slug === route.model)
179
+ ? route.model
180
+ : (models.value[0]?.slug || '')
181
+ view.value = ['overview', 'tasks', 'trace', 'frames'].includes(route.view)
182
+ ? route.view
183
+ : 'overview'
184
+ await loadMetrics()
185
+ if (route.task && selectedModel.value?.tasks.some((task) => task.taskId === route.task)) {
186
+ selectedTaskId.value = route.task
187
+ await loadTask(route.turn)
188
+ if (view.value === 'frames') await loadFrames(true)
189
+ } else if (view.value === 'trace' || view.value === 'frames') {
190
+ view.value = 'tasks'
191
+ }
192
+ writeRoute()
193
+ } catch (reason) {
194
+ error.value = reason instanceof Error ? reason.message : String(reason)
195
+ } finally {
196
+ loadingModels.value = false
197
+ }
198
+ }
199
+
200
+ async function refreshModels() {
201
+ loadingModels.value = true
202
+ try {
203
+ const response = await fetchJson<{ models: ModelResult[] }>('/api/results/models?refresh=1')
204
+ models.value = response.models
205
+ if (!selectedModel.value) selectedModelSlug.value = models.value[0]?.slug || ''
206
+ await loadMetrics()
207
+ } catch (reason) {
208
+ error.value = reason instanceof Error ? reason.message : String(reason)
209
+ } finally {
210
+ loadingModels.value = false
211
+ }
212
+ }
213
+
214
+ async function loadMetrics() {
215
+ metrics.value = null
216
+ if (!selectedModelSlug.value) return
217
+ loadingMetrics.value = true
218
+ try {
219
+ metrics.value = await fetchJson<ModelMetrics>(
220
+ `/api/results/models/${encodeURIComponent(selectedModelSlug.value)}/metrics`,
221
+ 60_000,
222
+ )
223
+ } catch {
224
+ // The manifest scores remain fully usable when optional usage enrichment fails.
225
+ } finally {
226
+ loadingMetrics.value = false
227
+ }
228
+ }
229
+
230
+ async function selectModel(slug: string) {
231
+ if (slug === selectedModelSlug.value) return
232
+ selectedModelSlug.value = slug
233
+ selectedTaskId.value = ''
234
+ taskDetail.value = null
235
+ trace.value = null
236
+ frames.value = []
237
+ view.value = 'overview'
238
+ writeRoute()
239
+ await loadMetrics()
240
+ }
241
+
242
+ async function openTask(taskId: string) {
243
+ selectedTaskId.value = taskId
244
+ view.value = 'trace'
245
+ writeRoute()
246
+ await loadTask(1)
247
+ }
248
+
249
+ async function loadTask(preferredTurn = 1) {
250
+ const version = ++taskLoadVersion
251
+ loadingTask.value = true
252
+ taskDetail.value = null
253
+ trace.value = null
254
+ turnDetail.value = null
255
+ frames.value = []
256
+ error.value = ''
257
+ const base = `/api/results/models/${encodeURIComponent(selectedModelSlug.value)}`
258
+ + `/tasks/${encodeURIComponent(selectedTaskId.value)}`
259
+ try {
260
+ const [detail, traceIndex] = await Promise.all([
261
+ fetchJson<TaskDetail>(base),
262
+ fetchJson<TraceIndex>(`${base}/trace`, 60_000),
263
+ ])
264
+ if (version !== taskLoadVersion) return
265
+ taskDetail.value = detail
266
+ trace.value = traceIndex
267
+ const preferredIndex = Math.max(0, traceIndex.turns.findIndex((turn) => turn.turn === preferredTurn))
268
+ await selectTurn(preferredIndex)
269
+ } catch (reason) {
270
+ if (version === taskLoadVersion) {
271
+ error.value = reason instanceof Error ? reason.message : String(reason)
272
+ }
273
+ } finally {
274
+ if (version === taskLoadVersion) loadingTask.value = false
275
+ }
276
+ }
277
+
278
+ async function selectTurn(index: number) {
279
+ if (!trace.value?.turns.length) return
280
+ const safe = Math.max(0, Math.min(trace.value.turns.length - 1, index))
281
+ selectedTurnIndex.value = safe
282
+ turnDetail.value = null
283
+ const turn = trace.value.turns[safe]
284
+ const version = ++turnLoadVersion
285
+ loadingTurn.value = true
286
+ writeRoute()
287
+ prefetchTurnFrames(turn)
288
+ try {
289
+ const detail = await fetchJson<TurnDetail>(
290
+ `/api/results/models/${encodeURIComponent(selectedModelSlug.value)}`
291
+ + `/tasks/${encodeURIComponent(selectedTaskId.value)}/turns/${turn.turn}`,
292
+ )
293
+ if (version === turnLoadVersion) turnDetail.value = detail
294
+ } catch {
295
+ // Turn actions remain visible from the compact trace index.
296
+ } finally {
297
+ if (version === turnLoadVersion) loadingTurn.value = false
298
+ }
299
+ }
300
+
301
+ async function showView(nextView: View) {
302
+ view.value = nextView
303
+ writeRoute()
304
+ if (nextView === 'frames' && selectedTaskId.value && !frames.value.length) {
305
+ await loadFrames(true)
306
+ }
307
+ }
308
+
309
+ async function loadFrames(reset = false) {
310
+ if (!selectedTaskId.value || loadingFrames.value) return
311
+ if (reset) frames.value = []
312
+ loadingFrames.value = true
313
+ try {
314
+ const offset = frames.value.length
315
+ const page = await fetchJson<FramePage>(
316
+ `/api/results/models/${encodeURIComponent(selectedModelSlug.value)}`
317
+ + `/tasks/${encodeURIComponent(selectedTaskId.value)}/frames?offset=${offset}&limit=120`,
318
+ )
319
+ frames.value.push(...page.frames)
320
+ frameTotal.value = page.total
321
+ frameHasMore.value = page.hasMore
322
+ } catch (reason) {
323
+ error.value = reason instanceof Error ? reason.message : String(reason)
324
+ } finally {
325
+ loadingFrames.value = false
326
+ }
327
+ }
328
+
329
+ function mediaUrl(path: string, download = false): string {
330
+ if (!selectedModelSlug.value || !selectedTaskId.value) return ''
331
+ const base = `/api/results/models/${encodeURIComponent(selectedModelSlug.value)}`
332
+ + `/tasks/${encodeURIComponent(selectedTaskId.value)}/media`
333
+ return `${base}?path=${encodeURIComponent(path)}${download ? '&download=1' : ''}`
334
+ }
335
+
336
+ function frameUrl(sequence: number | null): string {
337
+ if (sequence == null) return ''
338
+ return mediaUrl(`frames/frame_${String(sequence).padStart(4, '0')}.png`)
339
+ }
340
+
341
+ function prefetchTurnFrames(turn: TraceTurn) {
342
+ const sequences = new Set([
343
+ turn.inputFrameSequence,
344
+ turn.outputFrameSequence,
345
+ turn.outputFrameSequence == null ? null : turn.outputFrameSequence + 1,
346
+ ])
347
+ for (const sequence of sequences) {
348
+ if (sequence == null || sequence < 0) continue
349
+ const image = new Image()
350
+ image.decoding = 'async'
351
+ image.src = frameUrl(sequence)
352
+ }
353
+ }
354
+
355
+ function formatScore(value: number | null | undefined): string {
356
+ return value == null ? '—' : `${(value * 100).toFixed(1)}`
357
+ }
358
+
359
+ function formatNumber(value: number | null | undefined): string {
360
+ return value == null ? '—' : new Intl.NumberFormat().format(Math.round(value))
361
+ }
362
+
363
+ function formatBytes(value: number | null | undefined): string {
364
+ if (value == null) return '—'
365
+ const units = ['B', 'KB', 'MB', 'GB', 'TB']
366
+ let size = value
367
+ let unit = 0
368
+ while (size >= 1000 && unit < units.length - 1) {
369
+ size /= 1000
370
+ unit += 1
371
+ }
372
+ return `${size.toFixed(unit > 1 ? 1 : 0)} ${units[unit]}`
373
+ }
374
+
375
+ function formatDuration(seconds: number | null | undefined): string {
376
+ if (seconds == null) return '—'
377
+ const hours = Math.floor(seconds / 3600)
378
+ const minutes = Math.floor((seconds % 3600) / 60)
379
+ return hours ? `${hours}h ${minutes}m` : `${minutes}m`
380
+ }
381
+
382
+ function formatLatency(milliseconds: number | null | undefined): string {
383
+ if (milliseconds == null) return '—'
384
+ return milliseconds > 1000 ? `${(milliseconds / 1000).toFixed(1)}s` : `${Math.round(milliseconds)}ms`
385
+ }
386
+
387
+ function formatDate(value: string | null): string {
388
+ if (!value) return 'Unknown date'
389
+ const date = new Date(value)
390
+ return Number.isNaN(date.valueOf())
391
+ ? value
392
+ : new Intl.DateTimeFormat(undefined, { month: 'short', day: 'numeric', year: 'numeric' }).format(date)
393
+ }
394
+
395
+ function readable(value: string): string {
396
+ return value.replaceAll('_', ' ')
397
+ }
398
+
399
+ function pretty(value: unknown): string {
400
+ return JSON.stringify(value, null, 2)
401
+ }
402
+
403
+ function scoreWidth(value: number | null | undefined): string {
404
+ return `${Math.max(0, Math.min(100, (value || 0) * 100))}%`
405
+ }
406
+
407
+ function histogramHeight(count: number, values: number[]): string {
408
+ const maximum = Math.max(...values, 1)
409
+ return `${Math.max(3, count / maximum * 100)}%`
410
+ }
411
+
412
+ function taskVisionScore(task: ResultTask): number | null {
413
+ return metricByTask.value.get(task.taskId)?.visionScore ?? task.visionScore
414
+ }
415
+
416
+ function taskDeterministicScore(task: ResultTask): number | null {
417
+ return metricByTask.value.get(task.taskId)?.deterministicScore ?? task.deterministicScore
418
+ }
419
+
420
+ function taskDeterministicVerdict(task: ResultTask): string {
421
+ const score = taskDeterministicScore(task)
422
+ if (score == null) return 'unavailable'
423
+ if (score >= 0.9) return 'target_acquired'
424
+ if (score >= 0.75) return 'mostly_acquired'
425
+ if (score >= 0.4) return 'partial'
426
+ return 'not_acquired'
427
+ }
428
+
429
+ function verdictTone(value: string): string {
430
+ if (value === 'target_acquired') return 'positive'
431
+ if (value === 'mostly_acquired') return 'good'
432
+ if (value === 'partial') return 'warning'
433
+ if (value === 'not_acquired') return 'negative'
434
+ return 'neutral'
435
+ }
436
+
437
+ function statusTone(task: ResultTask): string {
438
+ if (!task.included) return 'neutral'
439
+ if (task.executionStatus === 'completed') return 'positive'
440
+ if (task.executionStatus === 'budget_exhausted') return 'warning'
441
+ return 'negative'
442
+ }
443
+
444
+ function openExpanded(payload: ExpandedImage) {
445
+ expandedImage.value = payload
446
+ }
447
+
448
+ function closeExpanded() {
449
+ expandedImage.value = null
450
+ }
451
+
452
+ function onKeydown(event: KeyboardEvent) {
453
+ if (event.key === 'Escape' && expandedImage.value) {
454
+ closeExpanded()
455
+ return
456
+ }
457
+ if (view.value !== 'trace' || expandedImage.value) return
458
+ const target = event.target as HTMLElement | null
459
+ if (target?.matches('input, select, textarea')) return
460
+ if (event.key === 'ArrowLeft') void selectTurn(selectedTurnIndex.value - 1)
461
+ if (event.key === 'ArrowRight') void selectTurn(selectedTurnIndex.value + 1)
462
+ }
463
+
464
+ onMounted(async () => {
465
+ window.addEventListener('keydown', onKeydown)
466
+ await nextTick()
467
+ await loadModels()
468
+ })
469
+
470
+ onBeforeUnmount(() => window.removeEventListener('keydown', onKeydown))
471
+ </script>
472
+
473
+ <template>
474
+ <div class="app-shell">
475
+ <header class="topbar">
476
+ <button class="brand" type="button" @click="showView('overview')">
477
+ <span class="brand-mark">A</span>
478
+ <span>
479
+ <strong>AutoCAD Bench</strong>
480
+ <small>Final results registry</small>
481
+ </span>
482
+ </button>
483
+ <div class="topbar-meta">
484
+ <span class="health"><i /> Direct S3 media</span>
485
+ <span>{{ suiteTotals.models }} models</span>
486
+ <span>{{ formatNumber(suiteTotals.tasks) }} finalized tasks</span>
487
+ <button class="icon-button" type="button" :disabled="loadingModels" @click="refreshModels">
488
+ {{ loadingModels ? 'Refreshing…' : 'Refresh' }}
489
+ </button>
490
+ </div>
491
+ </header>
492
+
493
+ <div v-if="loadingModels && !models.length" class="loading-screen">
494
+ <div class="loading-orbit"><span /></div>
495
+ <p>Reading finalized benchmark manifests…</p>
496
+ </div>
497
+
498
+ <main v-else class="workspace">
499
+ <aside class="model-rail">
500
+ <div class="rail-heading">
501
+ <span>Models</span>
502
+ <b>{{ models.length }}</b>
503
+ </div>
504
+ <button
505
+ v-for="model in models"
506
+ :key="model.slug"
507
+ type="button"
508
+ class="model-item"
509
+ :class="{ active: model.slug === selectedModelSlug }"
510
+ @click="selectModel(model.slug)"
511
+ >
512
+ <span class="model-avatar">{{ model.modelId.slice(0, 1).toUpperCase() }}</span>
513
+ <span class="model-copy">
514
+ <strong>{{ model.modelId }}</strong>
515
+ <small>{{ model.totals.included }}/{{ model.totals.expected }} tasks · {{ formatScore(model.scores.vision.mean) }} vision audit</small>
516
+ </span>
517
+ <span class="model-score">{{ formatScore(model.scores.deterministic.mean) }}</span>
518
+ </button>
519
+ <div class="rail-footnote">
520
+ <span>{{ formatNumber(suiteTotals.objects) }} objects</span>
521
+ <span>{{ formatBytes(suiteTotals.bytes) }}</span>
522
+ <small>Canonical artifacts only</small>
523
+ </div>
524
+ </aside>
525
+
526
+ <section v-if="selectedModel" class="content">
527
+ <div class="content-heading">
528
+ <div>
529
+ <div class="eyebrow">Final aggregate · {{ formatDate(selectedModel.createdAt) }}</div>
530
+ <h1>{{ selectedModel.modelId }}</h1>
531
+ <p>{{ selectedModel.aggregateId }}</p>
532
+ </div>
533
+ <div class="heading-actions">
534
+ <span class="registry-badge"><i /> Verified registry</span>
535
+ <button type="button" class="secondary-button" @click="showView('tasks')">Browse tasks</button>
536
+ </div>
537
+ </div>
538
+
539
+ <nav class="section-tabs" aria-label="Result sections">
540
+ <button type="button" :class="{ active: view === 'overview' }" @click="showView('overview')">Overview</button>
541
+ <button type="button" :class="{ active: view === 'tasks' }" @click="showView('tasks')">Tasks <span>{{ selectedModel.totals.included }}</span></button>
542
+ <button v-if="selectedTaskId" type="button" :class="{ active: view === 'trace' }" @click="showView('trace')">Trace</button>
543
+ <button v-if="selectedTaskId" type="button" :class="{ active: view === 'frames' }" @click="showView('frames')">All frames <span>{{ formatNumber(taskDetail?.frameCount) }}</span></button>
544
+ </nav>
545
+
546
+ <div v-if="error" class="error-banner">
547
+ <span>{{ error }}</span>
548
+ <button type="button" @click="error = ''">Dismiss</button>
549
+ </div>
550
+
551
+ <template v-if="view === 'overview'">
552
+ <section class="stat-grid">
553
+ <article class="stat-card hero-stat">
554
+ <span>Deterministic score</span>
555
+ <strong>{{ loadingMetrics ? '…' : formatScore(activeScores?.deterministic.mean) }}</strong>
556
+ <small>median {{ formatScore(activeScores?.deterministic.median) }} · {{ activeScores?.deterministic.count || 0 }} scored</small>
557
+ <div class="score-track"><i :style="{ width: scoreWidth(activeScores?.deterministic.mean) }" /></div>
558
+ </article>
559
+ <article class="stat-card">
560
+ <span>Vision audit</span>
561
+ <strong>{{ loadingMetrics ? '…' : formatScore(activeScores?.vision.mean) }}</strong>
562
+ <small>median {{ formatScore(activeScores?.vision.median) }}</small>
563
+ </article>
564
+ <article class="stat-card">
565
+ <span>Finalized</span>
566
+ <strong>{{ selectedModel.totals.included }}<em>/{{ selectedModel.totals.expected }}</em></strong>
567
+ <small>{{ selectedModel.totals.clean }} clean · {{ selectedModel.totals.budgetExhausted }} budget exhausted</small>
568
+ </article>
569
+ <article class="stat-card">
570
+ <span>Median actions</span>
571
+ <strong>{{ loadingMetrics ? '…' : formatNumber(metrics?.medians.acceptedActions) }}</strong>
572
+ <small>{{ loadingMetrics ? 'Loading trace totals' : `${formatNumber(metrics?.totals.screenshots)} screenshots total` }}</small>
573
+ </article>
574
+ <article class="stat-card">
575
+ <span>Runtime</span>
576
+ <strong>{{ loadingMetrics ? '…' : formatDuration(metrics?.totals.elapsedSeconds) }}</strong>
577
+ <small>{{ formatBytes(selectedModel.totals.bytes) }} retained</small>
578
+ </article>
579
+ </section>
580
+
581
+ <div class="overview-grid">
582
+ <section class="panel category-panel">
583
+ <div class="panel-heading">
584
+ <div>
585
+ <span class="eyebrow">Performance map</span>
586
+ <h2>Score by category</h2>
587
+ </div>
588
+ <div class="legend"><i class="deterministic" /> Deterministic <i class="vision" /> Vision audit</div>
589
+ </div>
590
+ <div class="category-list">
591
+ <div v-for="category in activeCategories" :key="category.category" class="category-row">
592
+ <div class="category-name">
593
+ <strong>{{ category.drawingType.toUpperCase() }} {{ category.level }}</strong>
594
+ <small>{{ category.tasks }} tasks</small>
595
+ </div>
596
+ <div class="dual-bars">
597
+ <div><i class="deterministic" :style="{ width: scoreWidth(category.deterministic.mean) }" /></div>
598
+ <div><i class="vision" :style="{ width: scoreWidth(category.vision.mean) }" /></div>
599
+ </div>
600
+ <div class="category-values">
601
+ <strong>{{ formatScore(category.deterministic.mean) }}</strong>
602
+ <span>{{ formatScore(category.vision.mean) }}</span>
603
+ </div>
604
+ </div>
605
+ </div>
606
+ </section>
607
+
608
+ <section class="panel distribution-panel">
609
+ <div class="panel-heading">
610
+ <div>
611
+ <span class="eyebrow">Deterministic evaluator</span>
612
+ <h2>Score distribution</h2>
613
+ </div>
614
+ </div>
615
+ <div class="histogram">
616
+ <div v-for="(count, index) in activeScores?.deterministicHistogram || []" :key="index" class="histogram-column">
617
+ <span>{{ count || '' }}</span>
618
+ <i :style="{ height: histogramHeight(count, activeScores?.deterministicHistogram || []) }" />
619
+ <small>{{ index * 10 }}</small>
620
+ </div>
621
+ </div>
622
+ <div class="distribution-summary">
623
+ <div><span>Lowest</span><strong>{{ formatScore(activeScores?.deterministic.min) }}</strong></div>
624
+ <div><span>Median</span><strong>{{ formatScore(activeScores?.deterministic.median) }}</strong></div>
625
+ <div><span>Highest</span><strong>{{ formatScore(activeScores?.deterministic.max) }}</strong></div>
626
+ </div>
627
+ </section>
628
+ </div>
629
+
630
+ <section class="panel comparison-panel">
631
+ <div class="panel-heading">
632
+ <div>
633
+ <span class="eyebrow">Registry comparison</span>
634
+ <h2>All models</h2>
635
+ </div>
636
+ <span class="panel-note">Scores are shown independently; they are not blended.</span>
637
+ </div>
638
+ <div class="comparison-table table-scroll">
639
+ <table>
640
+ <thead><tr><th>Model</th><th>Finalized</th><th>Deterministic mean</th><th>Vision audit</th><th>Deterministic range</th><th>Artifacts</th></tr></thead>
641
+ <tbody>
642
+ <tr v-for="model in models" :key="model.slug" :class="{ selected: model.slug === selectedModelSlug }" @click="selectModel(model.slug)">
643
+ <td><strong>{{ model.modelId }}</strong><small>{{ model.aggregateId }}</small></td>
644
+ <td>{{ model.totals.included }}/{{ model.totals.expected }}</td>
645
+ <td><b>{{ formatScore(model.scores.deterministic.mean) }}</b></td>
646
+ <td>{{ formatScore(model.scores.vision.mean) }}</td>
647
+ <td>{{ formatScore(model.scores.deterministic.min) }}–{{ formatScore(model.scores.deterministic.max) }}</td>
648
+ <td>{{ formatBytes(model.totals.bytes) }}</td>
649
+ </tr>
650
+ </tbody>
651
+ </table>
652
+ </div>
653
+ </section>
654
+ </template>
655
+
656
+ <template v-else-if="view === 'tasks'">
657
+ <section class="task-toolbar">
658
+ <label class="search-field">
659
+ <span>Search</span>
660
+ <input v-model="taskSearch" type="search" placeholder="Find a task…" />
661
+ </label>
662
+ <label><span>Drawing</span><select v-model="taskDimension"><option value="all">All</option><option value="2d">2D</option><option value="3d">3D</option></select></label>
663
+ <label><span>Level</span><select v-model="taskLevel"><option value="all">All levels</option><option v-for="level in levels" :key="level" :value="level">{{ level }}</option></select></label>
664
+ <label><span>Status</span><select v-model="taskStatus"><option value="all">All statuses</option><option v-for="status in statuses" :key="status" :value="status">{{ readable(status) }}</option></select></label>
665
+ <label><span>Sort</span><select v-model="taskSort"><option value="task">Task ID</option><option value="deterministic">Deterministic score</option><option value="vision">Vision audit</option><option value="actions">Actions</option></select></label>
666
+ <div class="result-count">{{ filteredTasks.length }} results</div>
667
+ </section>
668
+ <section class="panel task-table-panel">
669
+ <div class="table-scroll">
670
+ <table class="task-table">
671
+ <thead><tr><th>Task</th><th>Type</th><th>Status</th><th>Deterministic</th><th>Vision audit</th><th>Verdict</th><th>Actions</th><th>Frames</th><th /></tr></thead>
672
+ <tbody>
673
+ <tr v-for="task in filteredTasks" :key="task.taskId" tabindex="0" @click="openTask(task.taskId)" @keydown.enter="openTask(task.taskId)">
674
+ <td><strong>{{ task.taskId }}</strong><small>{{ task.selectedAttempt }}</small></td>
675
+ <td><span class="type-chip">{{ task.drawingType.toUpperCase() }}</span> {{ task.level }}</td>
676
+ <td><span class="status-pill" :class="statusTone(task)"><i />{{ task.included ? readable(task.executionStatus) : 'excluded' }}</span></td>
677
+ <td><strong>{{ formatScore(taskDeterministicScore(task)) }}</strong></td>
678
+ <td>{{ formatScore(taskVisionScore(task)) }}</td>
679
+ <td><span class="verdict" :class="verdictTone(taskDeterministicVerdict(task))">{{ readable(taskDeterministicVerdict(task)) }}</span></td>
680
+ <td>{{ loadingMetrics ? '…' : formatNumber(metricByTask.get(task.taskId)?.acceptedActions) }}</td>
681
+ <td>{{ loadingMetrics ? '…' : formatNumber(metricByTask.get(task.taskId)?.screenshots) }}</td>
682
+ <td><button type="button" class="row-arrow" aria-label="Open task">→</button></td>
683
+ </tr>
684
+ </tbody>
685
+ </table>
686
+ </div>
687
+ </section>
688
+ </template>
689
+
690
+ <template v-else-if="view === 'trace'">
691
+ <div v-if="loadingTask" class="task-loading"><div class="loading-orbit"><span /></div><p>Loading canonical trace…</p></div>
692
+ <template v-else-if="taskDetail && selectedTask">
693
+ <section class="task-titlebar">
694
+ <button type="button" class="back-button" @click="showView('tasks')">← Tasks</button>
695
+ <div><span class="eyebrow">{{ selectedTask.drawingType.toUpperCase() }} · {{ selectedTask.level }}</span><h2>{{ selectedTask.taskId }}</h2></div>
696
+ <div class="task-title-scores">
697
+ <span>Deterministic <strong>{{ formatScore(selectedDeterministicScore) }}</strong></span>
698
+ <span>Vision audit <strong>{{ formatScore(selectedVisionScore) }}</strong></span>
699
+ <span class="status-pill" :class="statusTone(selectedTask)"><i />{{ readable(selectedTask.executionStatus) }}</span>
700
+ </div>
701
+ </section>
702
+
703
+ <section class="artifact-strip">
704
+ <ReliableImage :src="mediaUrl(taskDetail.media.reference)" alt="Reference drawing" label="Reference" eager @expand="openExpanded" />
705
+ <ReliableImage :src="mediaUrl(taskDetail.media.final)" alt="Final model drawing" label="Model output" eager @expand="openExpanded" />
706
+ <ReliableImage :src="mediaUrl(taskDetail.media.diff)" alt="Evaluation difference" label="Difference" eager @expand="openExpanded" />
707
+ <div class="rubric-card">
708
+ <div class="rubric-score"><span>Evaluator</span><strong>{{ formatScore(selectedDeterministicScore) }}</strong></div>
709
+ <div v-for="([label, score]) in scoreBreakdown" :key="label" class="rubric-row">
710
+ <span>{{ label }}</span><div><i :style="{ width: scoreWidth(score) }" /></div><b>{{ formatScore(score) }}</b>
711
+ </div>
712
+ </div>
713
+ </section>
714
+
715
+ <section class="trace-workbench">
716
+ <aside class="turn-list">
717
+ <div class="turn-list-heading">
718
+ <div><span>Model trace</span><strong>{{ trace?.turns.length || 0 }} turns</strong></div>
719
+ <small>{{ formatNumber(trace?.receiptCount) }} actions</small>
720
+ </div>
721
+ <button
722
+ v-for="(turn, index) in trace?.turns || []"
723
+ :key="turn.toolUseId || index"
724
+ type="button"
725
+ class="turn-item"
726
+ :class="{ active: index === selectedTurnIndex }"
727
+ @click="selectTurn(index)"
728
+ >
729
+ <span class="turn-number">{{ String(turn.turn).padStart(2, '0') }}</span>
730
+ <span class="turn-summary"><strong>{{ turn.actionCount }} action{{ turn.actionCount === 1 ? '' : 's' }}</strong><small>frames {{ turn.inputFrameSequence ?? '—' }} → {{ turn.outputFrameSequence ?? '—' }}</small></span>
731
+ <span class="turn-latency">{{ formatLatency(turn.latencyMs) }}</span>
732
+ </button>
733
+ </aside>
734
+
735
+ <div v-if="selectedTurn" class="turn-stage">
736
+ <div class="turn-stage-heading">
737
+ <div><span class="eyebrow">Turn {{ selectedTurn.turn }}</span><h3>{{ selectedTurn.actionCount }} recorded actions</h3></div>
738
+ <div class="turn-controls">
739
+ <button type="button" :disabled="selectedTurnIndex === 0" @click="selectTurn(selectedTurnIndex - 1)">←</button>
740
+ <span>{{ selectedTurnIndex + 1 }} / {{ trace?.turns.length }}</span>
741
+ <button type="button" :disabled="selectedTurnIndex + 1 >= (trace?.turns.length || 0)" @click="selectTurn(selectedTurnIndex + 1)">→</button>
742
+ </div>
743
+ </div>
744
+ <div class="turn-images">
745
+ <ReliableImage v-if="selectedTurn.inputFrameSequence != null" :src="frameUrl(selectedTurn.inputFrameSequence)" :alt="`Turn ${selectedTurn.turn} input frame`" :label="`Input · frame ${selectedTurn.inputFrameSequence}`" eager @expand="openExpanded" />
746
+ <div v-else class="empty-frame">No input frame recorded</div>
747
+ <ReliableImage v-if="selectedTurn.outputFrameSequence != null" :src="frameUrl(selectedTurn.outputFrameSequence)" :alt="`Turn ${selectedTurn.turn} output frame`" :label="`Output · frame ${selectedTurn.outputFrameSequence}`" eager @expand="openExpanded" />
748
+ <div v-else class="empty-frame">No output frame recorded</div>
749
+ </div>
750
+ <div class="turn-facts">
751
+ <span>Latency <strong>{{ formatLatency(selectedTurn.latencyMs) }}</strong></span>
752
+ <span>Input tokens <strong>{{ formatNumber(selectedTurn.usage?.input_tokens) }}</strong></span>
753
+ <span>Output tokens <strong>{{ formatNumber(selectedTurn.usage?.output_tokens) }}</strong></span>
754
+ <span>Attempts <strong>{{ selectedTurn.requestAttempts ?? '—' }}</strong></span>
755
+ <span>Validation retries <strong>{{ selectedTurn.validationRetries ?? '—' }}</strong></span>
756
+ </div>
757
+ <div class="trace-detail-grid">
758
+ <section class="trace-card">
759
+ <div class="trace-card-heading"><h4>Model response</h4><span>{{ selectedTurn.stopReason }}</span></div>
760
+ <pre>{{ pretty(selectedTurn.text) }}</pre>
761
+ </section>
762
+ <section class="trace-card">
763
+ <div class="trace-card-heading"><h4>Requested actions</h4><span>{{ selectedTurn.actions.length }}</span></div>
764
+ <div v-if="selectedTurn.actions.length" class="action-list">
765
+ <div v-for="(action, index) in selectedTurn.actions" :key="index" class="action-item"><span>{{ index + 1 }}</span><pre>{{ pretty(action) }}</pre></div>
766
+ </div>
767
+ <div v-else class="empty-copy">No valid action payload in this turn.</div>
768
+ </section>
769
+ </div>
770
+ <section class="trace-card receipts-card">
771
+ <div class="trace-card-heading"><h4>Execution receipts</h4><span>{{ loadingTurn ? 'Loading…' : `${turnDetail?.receipts.length || 0} receipts` }}</span></div>
772
+ <div v-if="turnDetail?.receipts.length" class="receipt-list">
773
+ <details v-for="receipt in turnDetail.receipts" :key="receipt.actionIndex || 0">
774
+ <summary><span>#{{ receipt.actionIndex }}</span><strong>{{ String(receipt.action?.action || 'action') }}</strong><small>{{ receipt.receipt?.ok === false ? 'failed' : 'accepted' }}</small></summary>
775
+ <pre>{{ pretty(receipt) }}</pre>
776
+ </details>
777
+ </div>
778
+ <div v-else-if="!loadingTurn" class="empty-copy">No execution receipts matched this model turn.</div>
779
+ </section>
780
+ <details v-if="turnDetail?.turn" class="raw-turn">
781
+ <summary>Raw model-turn record</summary>
782
+ <pre>{{ pretty(turnDetail.turn) }}</pre>
783
+ </details>
784
+ </div>
785
+ <div v-else class="empty-trace">No model turns were recorded for this canonical task.</div>
786
+ </section>
787
+
788
+ <section class="panel downloads-panel">
789
+ <div class="panel-heading"><div><span class="eyebrow">Raw evidence</span><h2>Artifacts & trace files</h2></div><button type="button" class="secondary-button" @click="showView('frames')">Open all {{ formatNumber(taskDetail.frameCount) }} frames</button></div>
790
+ <div class="download-grid">
791
+ <a v-for="download in taskDetail.downloads" :key="download.path" :href="mediaUrl(download.path, true)"><span>{{ download.label }}</span><small>{{ download.path }}</small><b>↓</b></a>
792
+ </div>
793
+ </section>
794
+ </template>
795
+ </template>
796
+
797
+ <template v-else-if="view === 'frames'">
798
+ <section class="task-titlebar">
799
+ <button type="button" class="back-button" @click="showView('trace')">← Trace</button>
800
+ <div><span class="eyebrow">Complete screenshot sequence</span><h2>{{ selectedTaskId }}</h2></div>
801
+ <div class="frame-progress"><strong>{{ formatNumber(frames.length) }}</strong><span>of {{ formatNumber(frameTotal || taskDetail?.frameCount) }} loaded</span></div>
802
+ </section>
803
+ <div class="frame-gallery-note"><span><i /> Images load only as they approach the viewport</span><span>Direct, signed S3 delivery · automatic retry · immutable browser cache</span></div>
804
+ <section class="frame-gallery">
805
+ <button v-for="frame in frames" :key="frame.sequence" type="button" class="frame-tile" @click="openExpanded({ src: mediaUrl(frame.path), label: `${selectedTaskId} · frame ${frame.sequence}` })">
806
+ <ReliableImage :src="mediaUrl(frame.path)" :alt="`${selectedTaskId} frame ${frame.sequence}`" :label="`Frame ${frame.sequence}`" :expandable="false" />
807
+ </button>
808
+ </section>
809
+ <div class="load-more">
810
+ <button v-if="frameHasMore" type="button" class="primary-button" :disabled="loadingFrames" @click="loadFrames(false)">{{ loadingFrames ? 'Loading…' : `Load next ${Math.min(120, frameTotal - frames.length)} frames` }}</button>
811
+ <span v-else-if="frames.length">All {{ formatNumber(frameTotal) }} frames loaded</span>
812
+ <span v-else-if="loadingFrames">Loading frames…</span>
813
+ </div>
814
+ </template>
815
+ </section>
816
+
817
+ <section v-else class="empty-registry">
818
+ <h1>No result manifests found</h1>
819
+ <p>The configured bucket does not contain an aggregate manifest.</p>
820
+ </section>
821
+ </main>
822
+
823
+ <div v-if="expandedImage" class="lightbox" role="dialog" aria-modal="true" :aria-label="expandedImage.label" @click.self="closeExpanded">
824
+ <button type="button" class="lightbox-close" aria-label="Close image" @click="closeExpanded">×</button>
825
+ <div class="lightbox-content">
826
+ <img :src="expandedImage.src" :alt="expandedImage.label" />
827
+ <span>{{ expandedImage.label }}</span>
828
+ </div>
829
+ </div>
830
+ </div>
831
+ </template>
apps/results-dashboard/src/components/ReliableImage.vue ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script setup lang="ts">
2
+ import { computed, onBeforeUnmount, ref, watch } from 'vue'
3
+
4
+ const props = withDefaults(defineProps<{
5
+ src: string
6
+ alt: string
7
+ label?: string
8
+ eager?: boolean
9
+ expandable?: boolean
10
+ }>(), {
11
+ label: '',
12
+ eager: false,
13
+ expandable: true,
14
+ })
15
+
16
+ const emit = defineEmits<{
17
+ expand: [payload: { src: string; label: string }]
18
+ }>()
19
+
20
+ const attempt = ref(0)
21
+ const loaded = ref(false)
22
+ const failed = ref(false)
23
+ let retryTimer: number | undefined
24
+
25
+ const resolvedSrc = computed(() => {
26
+ const separator = props.src.includes('?') ? '&' : '?'
27
+ return attempt.value ? `${props.src}${separator}retry=${attempt.value}` : props.src
28
+ })
29
+
30
+ function reset() {
31
+ if (retryTimer) window.clearTimeout(retryTimer)
32
+ attempt.value = 0
33
+ loaded.value = false
34
+ failed.value = false
35
+ }
36
+
37
+ function onLoad() {
38
+ loaded.value = true
39
+ failed.value = false
40
+ }
41
+
42
+ function onError() {
43
+ loaded.value = false
44
+ if (attempt.value >= 4) {
45
+ failed.value = true
46
+ return
47
+ }
48
+ const delay = 250 * 2 ** attempt.value
49
+ retryTimer = window.setTimeout(() => {
50
+ attempt.value += 1
51
+ }, delay)
52
+ }
53
+
54
+ function retry() {
55
+ attempt.value += 1
56
+ failed.value = false
57
+ }
58
+
59
+ function expand() {
60
+ if (loaded.value && props.expandable) {
61
+ emit('expand', { src: resolvedSrc.value, label: props.label || props.alt })
62
+ }
63
+ }
64
+
65
+ watch(() => props.src, reset)
66
+ onBeforeUnmount(() => {
67
+ if (retryTimer) window.clearTimeout(retryTimer)
68
+ })
69
+ </script>
70
+
71
+ <template>
72
+ <div class="reliable-image" :class="{ 'is-loaded': loaded, 'is-failed': failed }">
73
+ <div v-if="!loaded && !failed" class="image-skeleton" aria-hidden="true">
74
+ <span />
75
+ </div>
76
+ <img
77
+ :src="resolvedSrc"
78
+ :alt="alt"
79
+ :loading="eager ? 'eager' : 'lazy'"
80
+ :fetchpriority="eager ? 'high' : 'auto'"
81
+ decoding="async"
82
+ @load="onLoad"
83
+ @error="onError"
84
+ @click="expand"
85
+ />
86
+ <button v-if="failed" type="button" class="image-retry" @click="retry">
87
+ Image unavailable · retry
88
+ </button>
89
+ <div v-if="label" class="image-label">{{ label }}</div>
90
+ </div>
91
+ </template>
apps/results-dashboard/src/env.d.ts ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue'
5
+ const component: DefineComponent<object, object, unknown>
6
+ export default component
7
+ }
apps/results-dashboard/src/main.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import { createApp } from 'vue'
2
+ import App from './App.vue'
3
+ import './styles.css'
4
+
5
+ createApp(App).mount('#app')
apps/results-dashboard/src/styles.css ADDED
@@ -0,0 +1,400 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ :root {
2
+ font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
3
+ color: #e8edef;
4
+ background: #090c0e;
5
+ font-synthesis: none;
6
+ text-rendering: optimizeLegibility;
7
+ --bg: #090c0e;
8
+ --surface: #101518;
9
+ --surface-2: #151b1f;
10
+ --surface-3: #1b2328;
11
+ --line: #263037;
12
+ --line-soft: #1c252a;
13
+ --text: #e8edef;
14
+ --muted: #8b999f;
15
+ --quiet: #5d6b72;
16
+ --mint: #8fffc9;
17
+ --mint-strong: #43d891;
18
+ --cyan: #70d4ed;
19
+ --amber: #f0ba69;
20
+ --red: #ff8a80;
21
+ --radius: 14px;
22
+ }
23
+
24
+ * { box-sizing: border-box; }
25
+
26
+ html { min-width: 320px; background: var(--bg); }
27
+ body { margin: 0; min-width: 320px; min-height: 100vh; background: var(--bg); }
28
+ button, input, select { font: inherit; }
29
+ button, a { -webkit-tap-highlight-color: transparent; }
30
+ button { color: inherit; }
31
+
32
+ .app-shell { min-height: 100vh; }
33
+
34
+ .topbar {
35
+ position: sticky;
36
+ z-index: 50;
37
+ top: 0;
38
+ display: flex;
39
+ height: 68px;
40
+ align-items: center;
41
+ justify-content: space-between;
42
+ padding: 0 24px;
43
+ border-bottom: 1px solid var(--line-soft);
44
+ background: rgba(9, 12, 14, .92);
45
+ backdrop-filter: blur(18px);
46
+ }
47
+
48
+ .brand {
49
+ display: flex;
50
+ align-items: center;
51
+ gap: 11px;
52
+ padding: 0;
53
+ border: 0;
54
+ background: none;
55
+ text-align: left;
56
+ cursor: pointer;
57
+ }
58
+
59
+ .brand-mark {
60
+ display: grid;
61
+ width: 34px;
62
+ height: 34px;
63
+ place-items: center;
64
+ border: 1px solid #487660;
65
+ border-radius: 9px;
66
+ color: var(--mint);
67
+ background: linear-gradient(145deg, #183426, #101b17);
68
+ font-weight: 800;
69
+ letter-spacing: -.04em;
70
+ }
71
+
72
+ .brand strong, .brand small { display: block; }
73
+ .brand strong { font-size: 14px; letter-spacing: -.01em; }
74
+ .brand small { margin-top: 2px; color: var(--muted); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; }
75
+
76
+ .topbar-meta { display: flex; align-items: center; gap: 22px; color: var(--muted); font-size: 12px; }
77
+ .health, .registry-badge { display: inline-flex; align-items: center; gap: 7px; }
78
+ .health i, .registry-badge i, .frame-gallery-note i {
79
+ display: block;
80
+ width: 7px;
81
+ height: 7px;
82
+ border-radius: 50%;
83
+ background: var(--mint-strong);
84
+ box-shadow: 0 0 0 4px rgba(67, 216, 145, .1);
85
+ }
86
+
87
+ .icon-button, .secondary-button, .primary-button, .back-button {
88
+ border: 1px solid var(--line);
89
+ border-radius: 8px;
90
+ background: var(--surface-2);
91
+ cursor: pointer;
92
+ }
93
+ .icon-button { padding: 7px 11px; color: var(--text); font-size: 11px; }
94
+ .secondary-button { padding: 9px 13px; color: var(--text); font-size: 12px; font-weight: 650; }
95
+ .primary-button { padding: 11px 18px; border-color: #4e8f70; color: #07110c; background: var(--mint); font-weight: 750; }
96
+ .icon-button:hover, .secondary-button:hover, .back-button:hover { border-color: #41515a; background: var(--surface-3); }
97
+ button:disabled { opacity: .42; cursor: not-allowed; }
98
+
99
+ .workspace { display: grid; min-height: calc(100vh - 68px); grid-template-columns: 256px minmax(0, 1fr); }
100
+
101
+ .model-rail {
102
+ position: sticky;
103
+ top: 68px;
104
+ display: flex;
105
+ height: calc(100vh - 68px);
106
+ flex-direction: column;
107
+ padding: 22px 14px;
108
+ border-right: 1px solid var(--line-soft);
109
+ background: #0b0f11;
110
+ }
111
+
112
+ .rail-heading { display: flex; align-items: center; justify-content: space-between; padding: 0 9px 12px; color: var(--muted); font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; }
113
+ .rail-heading b { display: grid; min-width: 21px; height: 21px; place-items: center; border-radius: 6px; background: var(--surface-2); color: var(--text); font-size: 10px; }
114
+
115
+ .model-item {
116
+ display: grid;
117
+ width: 100%;
118
+ grid-template-columns: 34px minmax(0, 1fr) auto;
119
+ align-items: center;
120
+ gap: 10px;
121
+ padding: 11px 9px;
122
+ border: 1px solid transparent;
123
+ border-radius: 10px;
124
+ color: var(--muted);
125
+ background: transparent;
126
+ text-align: left;
127
+ cursor: pointer;
128
+ }
129
+ .model-item + .model-item { margin-top: 3px; }
130
+ .model-item:hover { background: rgba(255, 255, 255, .025); }
131
+ .model-item.active { border-color: #284337; color: var(--text); background: linear-gradient(100deg, rgba(65, 180, 119, .12), rgba(65, 180, 119, .025)); }
132
+ .model-avatar { display: grid; width: 34px; height: 34px; place-items: center; border: 1px solid var(--line); border-radius: 9px; color: var(--muted); background: var(--surface); font-size: 12px; font-weight: 750; }
133
+ .model-item.active .model-avatar { border-color: #3c6a54; color: var(--mint); background: #12241b; }
134
+ .model-copy { min-width: 0; }
135
+ .model-copy strong, .model-copy small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
136
+ .model-copy strong { font-size: 12px; font-weight: 650; }
137
+ .model-copy small { margin-top: 4px; color: var(--quiet); font-size: 9px; }
138
+ .model-score { color: var(--text); font-size: 12px; font-variant-numeric: tabular-nums; }
139
+ .rail-footnote { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: auto; padding: 14px 10px 0; border-top: 1px solid var(--line-soft); color: var(--muted); font-size: 10px; }
140
+ .rail-footnote small { grid-column: 1 / -1; color: var(--quiet); }
141
+
142
+ .content { min-width: 0; padding: 34px clamp(24px, 4vw, 62px) 80px; }
143
+ .content-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 24px; }
144
+ .eyebrow { color: var(--mint-strong); font-size: 10px; font-weight: 750; letter-spacing: .12em; text-transform: uppercase; }
145
+ .content-heading h1 { margin: 9px 0 5px; font-size: clamp(28px, 3vw, 42px); font-weight: 680; letter-spacing: -.045em; }
146
+ .content-heading p { margin: 0; color: var(--muted); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
147
+ .heading-actions { display: flex; align-items: center; gap: 12px; }
148
+ .registry-badge { padding: 8px 10px; border: 1px solid #264333; border-radius: 8px; color: #a7ddbe; background: rgba(38, 86, 57, .14); font-size: 10px; font-weight: 650; text-transform: uppercase; letter-spacing: .06em; }
149
+
150
+ .section-tabs { display: flex; gap: 24px; margin: 27px 0 25px; border-bottom: 1px solid var(--line-soft); }
151
+ .section-tabs button { position: relative; padding: 0 1px 12px; border: 0; color: var(--muted); background: none; font-size: 12px; font-weight: 650; cursor: pointer; }
152
+ .section-tabs button span { margin-left: 4px; color: var(--quiet); }
153
+ .section-tabs button.active { color: var(--text); }
154
+ .section-tabs button.active::after { position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; border-radius: 2px; background: var(--mint-strong); content: ""; }
155
+
156
+ .error-banner { display: flex; align-items: center; justify-content: space-between; margin: 0 0 18px; padding: 10px 12px; border: 1px solid #5d3533; border-radius: 9px; color: #ffb2ab; background: #211311; font-size: 12px; }
157
+ .error-banner button { border: 0; color: inherit; background: none; cursor: pointer; }
158
+
159
+ .stat-grid { display: grid; grid-template-columns: 1.4fr repeat(4, minmax(0, 1fr)); gap: 10px; }
160
+ .stat-card { min-width: 0; padding: 18px; border: 1px solid var(--line-soft); border-radius: var(--radius); background: linear-gradient(145deg, #12181b, #0f1417); }
161
+ .stat-card > span { display: block; color: var(--muted); font-size: 10px; font-weight: 650; letter-spacing: .05em; text-transform: uppercase; }
162
+ .stat-card > strong { display: block; margin: 16px 0 8px; font-size: 29px; font-weight: 600; line-height: 1; letter-spacing: -.04em; font-variant-numeric: tabular-nums; }
163
+ .stat-card > strong em { color: var(--quiet); font-size: 15px; font-style: normal; font-weight: 500; }
164
+ .stat-card > small { display: block; overflow: hidden; color: var(--quiet); font-size: 10px; text-overflow: ellipsis; white-space: nowrap; }
165
+ .hero-stat { border-color: #284538; background: radial-gradient(circle at 15% 0, rgba(85, 223, 151, .12), transparent 55%), #111916; }
166
+ .hero-stat > strong { color: var(--mint); }
167
+ .score-track { height: 3px; margin-top: 13px; overflow: hidden; border-radius: 10px; background: #24302a; }
168
+ .score-track i { display: block; height: 100%; border-radius: inherit; background: var(--mint-strong); }
169
+
170
+ .overview-grid { display: grid; grid-template-columns: minmax(0, 1.55fr) minmax(300px, .8fr); gap: 12px; margin-top: 12px; }
171
+ .panel { border: 1px solid var(--line-soft); border-radius: var(--radius); background: var(--surface); }
172
+ .panel-heading { display: flex; align-items: center; justify-content: space-between; gap: 20px; padding: 20px 21px 17px; border-bottom: 1px solid var(--line-soft); }
173
+ .panel-heading h2 { margin: 5px 0 0; font-size: 15px; font-weight: 650; letter-spacing: -.015em; }
174
+ .panel-note { color: var(--quiet); font-size: 10px; }
175
+ .legend { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 9px; }
176
+ .legend i { width: 7px; height: 7px; border-radius: 2px; }
177
+ .legend i.deterministic { margin-left: 7px; }
178
+ .vision { background: var(--mint-strong) !important; }
179
+ .deterministic { background: var(--cyan) !important; }
180
+
181
+ .category-list { padding: 9px 20px 16px; }
182
+ .category-row { display: grid; grid-template-columns: 112px minmax(80px, 1fr) 76px; align-items: center; gap: 14px; padding: 10px 0; }
183
+ .category-name strong, .category-name small { display: block; }
184
+ .category-name strong { font-size: 10px; font-weight: 680; }
185
+ .category-name small { margin-top: 3px; color: var(--quiet); font-size: 9px; }
186
+ .dual-bars { display: grid; gap: 4px; }
187
+ .dual-bars div { height: 5px; overflow: hidden; border-radius: 4px; background: #20292e; }
188
+ .dual-bars i { display: block; height: 100%; border-radius: inherit; }
189
+ .category-values { display: flex; justify-content: flex-end; gap: 10px; font-size: 10px; font-variant-numeric: tabular-nums; }
190
+ .category-values strong { color: var(--mint); }
191
+ .category-values span { color: var(--cyan); }
192
+
193
+ .histogram { display: flex; height: 160px; align-items: flex-end; gap: 6px; padding: 26px 20px 20px; }
194
+ .histogram-column { display: grid; height: 100%; flex: 1; grid-template-rows: 18px minmax(4px, 1fr) 14px; align-items: end; text-align: center; }
195
+ .histogram-column span { align-self: start; color: var(--muted); font-size: 8px; }
196
+ .histogram-column i { display: block; min-height: 3px; border-radius: 4px 4px 1px 1px; background: linear-gradient(#6be5a6, #2e9b67); }
197
+ .histogram-column small { margin-top: 4px; color: var(--quiet); font-size: 8px; }
198
+ .distribution-summary { display: grid; grid-template-columns: repeat(3, 1fr); border-top: 1px solid var(--line-soft); }
199
+ .distribution-summary div { padding: 14px 16px; }
200
+ .distribution-summary div + div { border-left: 1px solid var(--line-soft); }
201
+ .distribution-summary span, .distribution-summary strong { display: block; }
202
+ .distribution-summary span { color: var(--quiet); font-size: 8px; text-transform: uppercase; }
203
+ .distribution-summary strong { margin-top: 6px; font-size: 14px; font-weight: 600; }
204
+
205
+ .comparison-panel { margin-top: 12px; }
206
+ .table-scroll { overflow-x: auto; }
207
+ table { width: 100%; border-collapse: collapse; }
208
+ th { padding: 11px 18px; color: var(--quiet); font-size: 8px; font-weight: 700; letter-spacing: .07em; text-align: left; text-transform: uppercase; white-space: nowrap; }
209
+ td { padding: 14px 18px; border-top: 1px solid var(--line-soft); color: var(--muted); font-size: 11px; white-space: nowrap; }
210
+ td strong, td small { display: block; }
211
+ td strong { color: var(--text); font-weight: 620; }
212
+ td small { max-width: 360px; margin-top: 4px; overflow: hidden; color: var(--quiet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 8px; text-overflow: ellipsis; }
213
+ .comparison-table tbody tr { cursor: pointer; }
214
+ .comparison-table tbody tr:hover, .comparison-table tbody tr.selected { background: rgba(86, 208, 143, .035); }
215
+
216
+ .task-toolbar { display: grid; grid-template-columns: minmax(220px, 1.4fr) repeat(4, minmax(110px, .55fr)) auto; align-items: end; gap: 9px; margin-bottom: 12px; }
217
+ .task-toolbar label { display: grid; gap: 6px; }
218
+ .task-toolbar label > span { color: var(--quiet); font-size: 8px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
219
+ .task-toolbar input, .task-toolbar select { width: 100%; height: 35px; padding: 0 10px; border: 1px solid var(--line); border-radius: 8px; outline: none; color: var(--text); background: var(--surface); font-size: 11px; }
220
+ .task-toolbar input:focus, .task-toolbar select:focus { border-color: #47725e; box-shadow: 0 0 0 3px rgba(72, 176, 123, .08); }
221
+ .result-count { padding: 0 4px 10px; color: var(--muted); font-size: 10px; white-space: nowrap; }
222
+ .task-table-panel { overflow: hidden; }
223
+ .task-table tbody tr { cursor: pointer; outline: none; }
224
+ .task-table tbody tr:hover, .task-table tbody tr:focus { background: #141b1e; }
225
+ .type-chip { display: inline-grid; min-width: 25px; height: 20px; margin-right: 6px; place-items: center; border: 1px solid var(--line); border-radius: 5px; color: var(--text); background: var(--surface-2); font-size: 8px; font-weight: 750; }
226
+ .status-pill { display: inline-flex; align-items: center; gap: 6px; padding: 4px 7px; border: 1px solid var(--line); border-radius: 20px; font-size: 8px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; }
227
+ .status-pill i { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
228
+ .status-pill.positive { border-color: #2b543e; color: #75dba2; background: #102219; }
229
+ .status-pill.warning { border-color: #5e4828; color: #f0ba69; background: #211a10; }
230
+ .status-pill.negative { border-color: #5b302d; color: #ff8a80; background: #221210; }
231
+ .status-pill.neutral { color: var(--muted); }
232
+ .verdict { color: var(--muted); font-size: 9px; }
233
+ .verdict.positive { color: var(--mint); }
234
+ .verdict.good { color: #92d8e8; }
235
+ .verdict.warning { color: var(--amber); }
236
+ .verdict.negative { color: var(--red); }
237
+ .row-arrow { border: 0; color: var(--quiet); background: none; cursor: pointer; }
238
+
239
+ .task-titlebar { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 16px; margin-bottom: 15px; }
240
+ .back-button { padding: 8px 10px; color: var(--muted); font-size: 10px; }
241
+ .task-titlebar h2 { margin: 4px 0 0; font-size: 20px; letter-spacing: -.025em; }
242
+ .task-title-scores { display: flex; align-items: center; gap: 16px; }
243
+ .task-title-scores > span:not(.status-pill) { color: var(--muted); font-size: 9px; }
244
+ .task-title-scores > span strong { margin-left: 4px; color: var(--text); font-size: 13px; }
245
+
246
+ .artifact-strip { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(240px, .78fr); gap: 9px; margin-bottom: 12px; }
247
+ .artifact-strip .reliable-image { min-height: 180px; }
248
+ .rubric-card { padding: 15px; border: 1px solid var(--line-soft); border-radius: var(--radius); background: var(--surface); }
249
+ .rubric-score { display: flex; align-items: center; justify-content: space-between; margin-bottom: 15px; }
250
+ .rubric-score span { color: var(--muted); font-size: 9px; text-transform: uppercase; }
251
+ .rubric-score strong { color: var(--mint); font-size: 24px; font-weight: 600; }
252
+ .rubric-row { display: grid; grid-template-columns: 67px 1fr 31px; align-items: center; gap: 7px; margin-top: 9px; color: var(--muted); font-size: 8px; }
253
+ .rubric-row > div { height: 3px; overflow: hidden; border-radius: 4px; background: #263038; }
254
+ .rubric-row i { display: block; height: 100%; background: var(--mint-strong); }
255
+ .rubric-row b { color: var(--text); text-align: right; font-weight: 550; }
256
+
257
+ .reliable-image { position: relative; overflow: hidden; min-height: 110px; border: 1px solid var(--line-soft); border-radius: 11px; background: #080b0d; }
258
+ .reliable-image img { display: block; width: 100%; height: 100%; min-height: inherit; object-fit: contain; opacity: 0; transition: opacity .18s ease; cursor: zoom-in; }
259
+ .reliable-image.is-loaded img { opacity: 1; }
260
+ .image-label { position: absolute; right: 8px; bottom: 8px; padding: 5px 7px; border: 1px solid rgba(255,255,255,.1); border-radius: 6px; color: #dfe8e4; background: rgba(8, 12, 13, .78); backdrop-filter: blur(8px); font-size: 8px; font-weight: 650; text-transform: uppercase; letter-spacing: .06em; }
261
+ .image-skeleton { position: absolute; inset: 0; overflow: hidden; background: #0d1214; }
262
+ .image-skeleton span { position: absolute; inset: 0; transform: translateX(-100%); background: linear-gradient(90deg, transparent, rgba(255,255,255,.045), transparent); animation: shimmer 1.5s infinite; }
263
+ .image-retry { position: absolute; inset: 0; width: 100%; border: 0; color: var(--red); background: #151011; font-size: 10px; cursor: pointer; }
264
+ @keyframes shimmer { to { transform: translateX(100%); } }
265
+
266
+ .trace-workbench { display: grid; min-height: 670px; grid-template-columns: 226px minmax(0, 1fr); overflow: hidden; border: 1px solid var(--line-soft); border-radius: var(--radius); background: var(--surface); }
267
+ .turn-list { max-height: 760px; overflow-y: auto; border-right: 1px solid var(--line-soft); background: #0d1214; }
268
+ .turn-list-heading { position: sticky; z-index: 2; top: 0; display: flex; align-items: flex-end; justify-content: space-between; padding: 15px 13px; border-bottom: 1px solid var(--line-soft); background: rgba(13, 18, 20, .96); backdrop-filter: blur(12px); }
269
+ .turn-list-heading span, .turn-list-heading strong { display: block; }
270
+ .turn-list-heading span { color: var(--quiet); font-size: 8px; text-transform: uppercase; }
271
+ .turn-list-heading strong { margin-top: 4px; font-size: 11px; }
272
+ .turn-list-heading small { color: var(--quiet); font-size: 8px; }
273
+ .turn-item { display: grid; width: 100%; grid-template-columns: 27px 1fr auto; align-items: center; gap: 8px; padding: 10px 11px; border: 0; border-bottom: 1px solid #171e21; color: var(--muted); background: transparent; text-align: left; cursor: pointer; }
274
+ .turn-item:hover { background: rgba(255,255,255,.02); }
275
+ .turn-item.active { color: var(--text); background: linear-gradient(90deg, rgba(61, 186, 121, .15), transparent); box-shadow: inset 2px 0 var(--mint-strong); }
276
+ .turn-number { color: var(--quiet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9px; }
277
+ .turn-summary strong, .turn-summary small { display: block; }
278
+ .turn-summary strong { font-size: 9px; font-weight: 650; }
279
+ .turn-summary small { margin-top: 3px; color: var(--quiet); font-size: 8px; }
280
+ .turn-latency { color: var(--quiet); font-size: 8px; }
281
+
282
+ .turn-stage { min-width: 0; padding: 18px; }
283
+ .turn-stage-heading { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
284
+ .turn-stage-heading h3 { margin: 5px 0 0; font-size: 15px; }
285
+ .turn-controls { display: flex; align-items: center; gap: 8px; color: var(--muted); font-size: 9px; }
286
+ .turn-controls button { display: grid; width: 27px; height: 27px; place-items: center; border: 1px solid var(--line); border-radius: 7px; background: var(--surface-2); cursor: pointer; }
287
+ .turn-images { display: grid; grid-template-columns: 1fr 1fr; gap: 9px; }
288
+ .turn-images .reliable-image, .empty-frame { aspect-ratio: 16 / 9; }
289
+ .empty-frame { display: grid; place-items: center; border: 1px dashed var(--line); border-radius: 11px; color: var(--quiet); font-size: 10px; }
290
+ .turn-facts { display: flex; flex-wrap: wrap; gap: 19px; margin: 11px 0; padding: 9px 11px; border: 1px solid var(--line-soft); border-radius: 8px; background: #0e1315; color: var(--quiet); font-size: 8px; }
291
+ .turn-facts strong { margin-left: 4px; color: var(--text); font-weight: 550; }
292
+ .trace-detail-grid { display: grid; grid-template-columns: .85fr 1.15fr; gap: 9px; }
293
+ .trace-card { min-width: 0; overflow: hidden; border: 1px solid var(--line-soft); border-radius: 10px; background: #0d1214; }
294
+ .trace-card-heading { display: flex; align-items: center; justify-content: space-between; padding: 10px 12px; border-bottom: 1px solid var(--line-soft); }
295
+ .trace-card-heading h4 { margin: 0; font-size: 10px; font-weight: 650; }
296
+ .trace-card-heading span { color: var(--quiet); font-size: 8px; }
297
+ .trace-card > pre, .action-item pre, details pre { max-height: 270px; margin: 0; overflow: auto; padding: 12px; color: #b7c3c7; font: 9px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; word-break: break-word; }
298
+ .action-list { max-height: 295px; overflow-y: auto; }
299
+ .action-item { display: grid; grid-template-columns: 27px 1fr; }
300
+ .action-item + .action-item { border-top: 1px solid var(--line-soft); }
301
+ .action-item > span { padding: 13px 0 0 10px; color: var(--mint-strong); font: 8px ui-monospace, SFMono-Regular, Menlo, monospace; }
302
+ .action-item pre { padding-left: 5px; }
303
+ .receipts-card { margin-top: 9px; }
304
+ .receipt-list { max-height: 310px; overflow-y: auto; }
305
+ .receipt-list details + details { border-top: 1px solid var(--line-soft); }
306
+ .receipt-list summary { display: grid; grid-template-columns: 48px 1fr auto; gap: 9px; padding: 10px 12px; color: var(--muted); font-size: 9px; cursor: pointer; list-style: none; }
307
+ .receipt-list summary::-webkit-details-marker { display: none; }
308
+ .receipt-list summary span { color: var(--quiet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
309
+ .receipt-list summary strong { color: var(--text); }
310
+ .receipt-list summary small { color: var(--mint-strong); }
311
+ .empty-copy, .empty-trace { padding: 25px; color: var(--quiet); font-size: 10px; text-align: center; }
312
+ .empty-trace { display: grid; place-items: center; }
313
+ .raw-turn { margin-top: 9px; overflow: hidden; border: 1px solid var(--line-soft); border-radius: 9px; background: #0d1214; }
314
+ .raw-turn summary { padding: 10px 12px; color: var(--muted); font-size: 9px; cursor: pointer; }
315
+ .raw-turn pre { max-height: 420px; margin: 0; overflow: auto; padding: 12px; border-top: 1px solid var(--line-soft); color: #b7c3c7; font: 9px/1.55 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; word-break: break-word; }
316
+
317
+ .downloads-panel { margin-top: 12px; }
318
+ .download-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1px; background: var(--line-soft); }
319
+ .download-grid a { display: grid; min-width: 0; grid-template-columns: 1fr auto; padding: 13px 14px; color: var(--text); background: var(--surface); text-decoration: none; }
320
+ .download-grid a:hover { background: var(--surface-2); }
321
+ .download-grid span, .download-grid small { display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
322
+ .download-grid span { font-size: 9px; font-weight: 650; }
323
+ .download-grid small { grid-column: 1; margin-top: 4px; color: var(--quiet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 7px; }
324
+ .download-grid b { grid-row: 1 / 3; grid-column: 2; align-self: center; color: var(--mint-strong); font-size: 14px; }
325
+
326
+ .frame-progress { text-align: right; }
327
+ .frame-progress strong, .frame-progress span { display: block; }
328
+ .frame-progress strong { font-size: 18px; }
329
+ .frame-progress span { margin-top: 3px; color: var(--quiet); font-size: 9px; }
330
+ .frame-gallery-note { display: flex; justify-content: space-between; margin-bottom: 12px; padding: 9px 11px; border: 1px solid var(--line-soft); border-radius: 8px; color: var(--quiet); background: var(--surface); font-size: 9px; }
331
+ .frame-gallery-note span { display: flex; align-items: center; gap: 8px; }
332
+ .frame-gallery { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
333
+ .frame-tile { padding: 0; overflow: hidden; border: 0; border-radius: 11px; background: none; cursor: zoom-in; }
334
+ .frame-tile .reliable-image { aspect-ratio: 16 / 9; pointer-events: none; }
335
+ .frame-tile:hover .reliable-image { border-color: #48725f; box-shadow: 0 0 0 2px rgba(67, 216, 145, .08); }
336
+ .load-more { display: flex; justify-content: center; padding: 24px; color: var(--muted); font-size: 10px; }
337
+
338
+ .lightbox { position: fixed; z-index: 100; inset: 0; display: grid; place-items: center; padding: 44px; background: rgba(2, 4, 5, .94); backdrop-filter: blur(12px); }
339
+ .lightbox-close { position: absolute; top: 18px; right: 23px; width: 36px; height: 36px; border: 1px solid #344048; border-radius: 50%; color: var(--text); background: #101518; font-size: 23px; cursor: pointer; }
340
+ .lightbox-content { display: grid; max-width: min(96vw, 1700px); max-height: 92vh; gap: 9px; justify-items: center; }
341
+ .lightbox-content img { max-width: 100%; max-height: calc(92vh - 30px); border: 1px solid #29343a; border-radius: 9px; background: #000; object-fit: contain; }
342
+ .lightbox-content span { color: var(--muted); font-size: 10px; }
343
+
344
+ .loading-screen, .task-loading, .empty-registry { display: grid; min-height: 60vh; place-content: center; justify-items: center; color: var(--muted); font-size: 11px; }
345
+ .task-loading { min-height: 420px; }
346
+ .loading-orbit { position: relative; width: 34px; height: 34px; border: 1px solid #2a3932; border-radius: 50%; }
347
+ .loading-orbit span { position: absolute; top: -2px; left: 15px; width: 4px; height: 4px; border-radius: 50%; background: var(--mint); animation: orbit 1s linear infinite; transform-origin: 2px 19px; }
348
+ @keyframes orbit { to { transform: rotate(360deg); } }
349
+
350
+ @media (max-width: 1180px) {
351
+ .stat-grid { grid-template-columns: repeat(3, 1fr); }
352
+ .hero-stat { grid-column: span 1; }
353
+ .overview-grid { grid-template-columns: 1fr; }
354
+ .artifact-strip { grid-template-columns: repeat(3, 1fr); }
355
+ .rubric-card { grid-column: 1 / -1; }
356
+ .download-grid { grid-template-columns: repeat(3, 1fr); }
357
+ .task-toolbar { grid-template-columns: repeat(3, 1fr); }
358
+ .search-field { grid-column: span 2; }
359
+ .frame-gallery { grid-template-columns: repeat(3, 1fr); }
360
+ }
361
+
362
+ @media (max-width: 850px) {
363
+ .topbar-meta > span:not(.health) { display: none; }
364
+ .workspace { grid-template-columns: 1fr; }
365
+ .model-rail { position: static; display: flex; width: 100%; height: auto; flex-direction: row; overflow-x: auto; padding: 10px 14px; border-right: 0; border-bottom: 1px solid var(--line-soft); }
366
+ .rail-heading, .rail-footnote { display: none; }
367
+ .model-item { min-width: 205px; }
368
+ .content { padding: 25px 16px 60px; }
369
+ .content-heading { align-items: flex-start; }
370
+ .registry-badge { display: none; }
371
+ .stat-grid { grid-template-columns: repeat(2, 1fr); }
372
+ .trace-workbench { grid-template-columns: 1fr; }
373
+ .turn-list { display: flex; max-height: none; overflow-x: auto; border-right: 0; border-bottom: 1px solid var(--line-soft); }
374
+ .turn-list-heading { min-width: 130px; }
375
+ .turn-item { min-width: 150px; border-right: 1px solid var(--line-soft); border-bottom: 0; }
376
+ .turn-images, .trace-detail-grid { grid-template-columns: 1fr; }
377
+ .download-grid { grid-template-columns: repeat(2, 1fr); }
378
+ .frame-gallery { grid-template-columns: repeat(2, 1fr); }
379
+ .task-title-scores { display: none; }
380
+ }
381
+
382
+ @media (max-width: 560px) {
383
+ .topbar { padding: 0 14px; }
384
+ .health { display: none; }
385
+ .content-heading h1 { font-size: 28px; }
386
+ .heading-actions .secondary-button { display: none; }
387
+ .section-tabs { gap: 15px; overflow-x: auto; }
388
+ .stat-grid { grid-template-columns: 1fr; }
389
+ .category-row { grid-template-columns: 92px 1fr 60px; }
390
+ .task-toolbar { grid-template-columns: 1fr 1fr; }
391
+ .search-field { grid-column: 1 / -1; }
392
+ .result-count { display: none; }
393
+ .artifact-strip { grid-template-columns: 1fr; }
394
+ .rubric-card { grid-column: auto; }
395
+ .turn-stage { padding: 12px; }
396
+ .download-grid { grid-template-columns: 1fr; }
397
+ .frame-gallery { grid-template-columns: 1fr; }
398
+ .frame-gallery-note { display: grid; gap: 6px; }
399
+ .lightbox { padding: 48px 8px 20px; }
400
+ }
apps/results-dashboard/src/types.ts ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export type ScoreStats = {
2
+ count: number
3
+ mean: number | null
4
+ median: number | null
5
+ min: number | null
6
+ max: number | null
7
+ }
8
+
9
+ export type ResultTask = {
10
+ index: number
11
+ taskId: string
12
+ drawingType: '2d' | '3d'
13
+ level: string
14
+ category: string
15
+ included: boolean
16
+ executionStatus: string
17
+ evaluationStatus: string
18
+ selectedAttempt: string
19
+ selectedRunId: string
20
+ destinationPrefix: string
21
+ sourcePrefix: string
22
+ objectCount: number
23
+ bytes: number
24
+ deterministicScore: number | null
25
+ deterministicValid: boolean
26
+ visionScore: number | null
27
+ visionVerdict: string
28
+ evaluatorVersion: string
29
+ controllerClean: boolean
30
+ exclusionReason: string | null
31
+ }
32
+
33
+ export type CategorySummary = {
34
+ category: string
35
+ drawingType: '2d' | '3d'
36
+ level: string
37
+ tasks: number
38
+ deterministic: ScoreStats
39
+ vision: ScoreStats
40
+ }
41
+
42
+ export type ModelResult = {
43
+ slug: string
44
+ manifestKey: string
45
+ modelPrefix: string
46
+ modelId: string
47
+ aggregateId: string
48
+ createdAt: string | null
49
+ sourceBucket: string
50
+ destinationBucket: string
51
+ evaluatorVersion: string
52
+ tasks: ResultTask[]
53
+ categories: CategorySummary[]
54
+ totals: {
55
+ expected: number
56
+ included: number
57
+ excluded: number
58
+ clean: number
59
+ budgetExhausted: number
60
+ bytes: number
61
+ objects: number
62
+ }
63
+ scores: {
64
+ deterministic: ScoreStats
65
+ vision: ScoreStats
66
+ deterministicHistogram: number[]
67
+ visionHistogram: number[]
68
+ }
69
+ selectionPolicy: Record<string, unknown> | null
70
+ sourceRuns: Record<string, unknown>[]
71
+ }
72
+
73
+ export type TaskMetric = {
74
+ taskId: string
75
+ drawingType: '2d' | '3d'
76
+ level: string
77
+ acceptedActions: number | null
78
+ elapsedSeconds: number | null
79
+ rolloutSeconds: number | null
80
+ turns: number | null
81
+ screenshots: number | null
82
+ inputTokens: number | null
83
+ cachedInputTokens: number | null
84
+ cacheCreationTokens: number | null
85
+ outputTokens: number | null
86
+ reasoningTokens: number | null
87
+ totalTokens: number | null
88
+ artifactBytes: number | null
89
+ stopReason: string
90
+ deterministicScore: number | null
91
+ visionScore: number | null
92
+ visionVerdict: string
93
+ }
94
+
95
+ export type ModelMetrics = {
96
+ tasks: TaskMetric[]
97
+ totals: Record<string, number>
98
+ medians: Record<string, number | null>
99
+ scores: ModelResult['scores']
100
+ categories: CategorySummary[]
101
+ }
102
+
103
+ export type TaskDetail = {
104
+ model: { slug: string; modelId: string; aggregateId: string }
105
+ task: ResultTask
106
+ result: Record<string, unknown> | null
107
+ runState: Record<string, unknown> | null
108
+ evaluationScore: Record<string, unknown> | null
109
+ evaluationStatus: Record<string, unknown> | null
110
+ visionScore: Record<string, unknown> | null
111
+ frameCount: number
112
+ turnCount: number
113
+ media: Record<'reference' | 'final' | 'diff' | 'dwg', string>
114
+ downloads: Array<{ label: string; path: string }>
115
+ }
116
+
117
+ export type TraceTurn = {
118
+ index: number
119
+ turn: number
120
+ toolUseId: string
121
+ latencyMs: number | null
122
+ stopReason: string
123
+ requestAttempts: number | null
124
+ validationRetries: number | null
125
+ discardedToolUses: number | null
126
+ usage: Record<string, number> | null
127
+ text: unknown
128
+ actions: Record<string, unknown>[]
129
+ actionCount: number
130
+ inputFrameSequence: number | null
131
+ outputFrameSequence: number | null
132
+ firstActionIndex: number | null
133
+ lastActionIndex: number | null
134
+ }
135
+
136
+ export type TraceIndex = {
137
+ taskId: string
138
+ turns: TraceTurn[]
139
+ receiptCount: number
140
+ }
141
+
142
+ export type TurnDetail = {
143
+ turn: Record<string, unknown>
144
+ receipts: Array<{
145
+ actionIndex: number | null
146
+ batchIndex: number | null
147
+ batchSize: number | null
148
+ toolUseId: string | null
149
+ action: Record<string, unknown> | null
150
+ receipt: Record<string, unknown> | null
151
+ requestId: string | null
152
+ idempotentReplay: boolean
153
+ }>
154
+ }
155
+
156
+ export type Frame = { sequence: number; name: string; path: string }
157
+
158
+ export type FramePage = {
159
+ offset: number
160
+ limit: number
161
+ total: number
162
+ hasMore: boolean
163
+ frames: Frame[]
164
+ }
apps/results-dashboard/tests/results-api.test.mjs ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import assert from 'node:assert/strict'
2
+ import test from 'node:test'
3
+ import { normalizeManifest, ResultsRepository } from '../results-api.mjs'
4
+
5
+ const manifest = {
6
+ aggregate_id: 'aggregate-1',
7
+ model_id: 'gpt-5.6-terra',
8
+ created_at: '2026-07-22T00:00:00Z',
9
+ summary: { expected_tasks: 2 },
10
+ tasks: [
11
+ {
12
+ task_id: 'task-001',
13
+ included: true,
14
+ execution_status: 'completed',
15
+ destination_prefix: 'terra/artifacts//controller/run/task-001/',
16
+ object_count: 20,
17
+ bytes: 100,
18
+ deterministic_score: 0.4,
19
+ vision_score: 0.8,
20
+ vision_verdict: 'mostly_acquired',
21
+ },
22
+ {
23
+ task_id: 'task-049',
24
+ included: true,
25
+ execution_status: 'budget_exhausted',
26
+ controller_clean: false,
27
+ destination_prefix: 'terra/artifacts//controller/run/task-049/',
28
+ object_count: 30,
29
+ bytes: 200,
30
+ deterministic_score: 0.2,
31
+ vision_score: 0.6,
32
+ vision_verdict: 'partial',
33
+ },
34
+ ],
35
+ }
36
+
37
+ test('normalizes model and category statistics without blending evaluator scores', () => {
38
+ const result = normalizeManifest(manifest, { manifestKey: 'terra/aggregate/manifest.json' })
39
+ assert.equal(result.slug, 'terra')
40
+ assert.equal(result.totals.included, 2)
41
+ assert.equal(result.totals.budgetExhausted, 1)
42
+ assert.equal(result.totals.objects, 50)
43
+ assert.equal(result.scores.vision.mean, 0.7)
44
+ assert.equal(result.scores.deterministic.mean, 0.30000000000000004)
45
+ assert.deepEqual(result.categories.map((category) => category.category), ['2d-basic', '3d-project'])
46
+ })
47
+
48
+ test('normalizes the original aggregate manifest score and status field names', () => {
49
+ const result = normalizeManifest({
50
+ model_id: 'claude-fable-5',
51
+ summary: { expected_tasks: 1 },
52
+ tasks: [{
53
+ task_id: 'task-022',
54
+ included: true,
55
+ execution_state: 'budget_exhausted',
56
+ drawing_type: '3d',
57
+ level: 'basic',
58
+ score: 0.55,
59
+ outcome: 'partial',
60
+ score_source: 'vision_judge',
61
+ destination_prefix: 'fable/artifacts//run/task-022/',
62
+ }],
63
+ }, { manifestKey: 'fable/aggregate/manifest.json' })
64
+ assert.equal(result.tasks[0].executionStatus, 'budget_exhausted')
65
+ assert.equal(result.tasks[0].visionScore, 0.55)
66
+ assert.equal(result.tasks[0].visionVerdict, 'partial')
67
+ assert.equal(result.tasks[0].controllerClean, false)
68
+ assert.equal(result.scores.deterministic.count, 0)
69
+ })
70
+
71
+ test('preserves the aggregate bucket double slash in signed artifact keys', async () => {
72
+ let signedCommand = null
73
+ const repository = new ResultsRepository({
74
+ bucket: 'results',
75
+ s3: {
76
+ async send(command) {
77
+ if (command.constructor.name === 'ListObjectsV2Command') {
78
+ return { CommonPrefixes: [{ Prefix: 'terra/' }] }
79
+ }
80
+ const key = command.input.Key
81
+ if (key === 'terra/aggregate/manifest.json') {
82
+ return { Body: { transformToString: async () => JSON.stringify(manifest) } }
83
+ }
84
+ const error = new Error('missing')
85
+ error.name = 'NoSuchKey'
86
+ throw error
87
+ },
88
+ },
89
+ signUrl: async (command) => {
90
+ signedCommand = command
91
+ return 'https://signed.example/frame'
92
+ },
93
+ })
94
+ const url = await repository.mediaUrl('terra', 'task-001', 'frames/frame_0001.png')
95
+ assert.equal(url, 'https://signed.example/frame')
96
+ assert.equal(
97
+ signedCommand.input.Key,
98
+ 'terra/artifacts//controller/run/task-001/frames/frame_0001.png',
99
+ )
100
+ })
101
+
102
+ test('derives a deterministic frame page from the finalized screenshot count', async () => {
103
+ const objects = new Map([
104
+ ['terra/aggregate/manifest.json', manifest],
105
+ ['terra/artifacts//controller/run/task-001/result.json', { screenshots: 4 }],
106
+ ['terra/artifacts//controller/run/task-001/run-state.json', { turn_count: 2 }],
107
+ ])
108
+ const repository = new ResultsRepository({
109
+ bucket: 'results',
110
+ signUrl: async () => 'signed',
111
+ s3: {
112
+ async send(command) {
113
+ if (command.constructor.name === 'ListObjectsV2Command') {
114
+ return { CommonPrefixes: [{ Prefix: 'terra/' }] }
115
+ }
116
+ const value = objects.get(command.input.Key)
117
+ if (value) return { Body: { transformToString: async () => JSON.stringify(value) } }
118
+ const error = new Error('missing')
119
+ error.name = 'NoSuchKey'
120
+ throw error
121
+ },
122
+ },
123
+ })
124
+ const page = await repository.framePage('terra', 'task-001', { offset: 1, limit: 2 })
125
+ assert.equal(page.total, 4)
126
+ assert.equal(page.hasMore, true)
127
+ assert.deepEqual(page.frames.map((frame) => frame.path), [
128
+ 'frames/frame_0001.png',
129
+ 'frames/frame_0002.png',
130
+ ])
131
+ })
apps/results-dashboard/tsconfig.app.json ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
5
+ "target": "ES2022",
6
+ "useDefineForClassFields": true,
7
+ "module": "ESNext",
8
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
9
+ "skipLibCheck": true,
10
+ "moduleResolution": "Bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "noEmit": true,
13
+ "resolveJsonModule": true,
14
+ "isolatedModules": true,
15
+ "esModuleInterop": true,
16
+ "jsx": "preserve",
17
+ "strict": true,
18
+ "noUnusedLocals": true,
19
+ "noUnusedParameters": true
20
+ },
21
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
22
+ }
apps/results-dashboard/tsconfig.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "files": [],
3
+ "references": [
4
+ { "path": "./tsconfig.app.json" },
5
+ { "path": "./tsconfig.node.json" }
6
+ ]
7
+ }
apps/results-dashboard/tsconfig.node.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
5
+ "target": "ES2023",
6
+ "lib": ["ES2023"],
7
+ "module": "ESNext",
8
+ "skipLibCheck": true,
9
+ "moduleResolution": "Bundler",
10
+ "allowImportingTsExtensions": true,
11
+ "verbatimModuleSyntax": true,
12
+ "moduleDetection": "force",
13
+ "noEmit": true,
14
+ "strict": true
15
+ },
16
+ "include": ["vite.config.ts"]
17
+ }
apps/results-dashboard/vite.config.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ server: {
7
+ proxy: {
8
+ '/api': 'http://127.0.0.1:4274',
9
+ },
10
+ },
11
+ build: {
12
+ target: 'es2022',
13
+ sourcemap: true,
14
+ },
15
+ })
artifacts/gold-cache/README.md ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Gold cache
2
+
3
+ This directory holds trusted, evaluator-versioned render and metadata artifacts:
4
+
5
+ ```text
6
+ artifacts/gold-cache/<evaluator-version>/<task-id>/
7
+ ├── metadata.json
8
+ └── render.png
9
+ ```
10
+
11
+ Production entries are generated only by the pinned Windows/AutoCAD evaluator from the corresponding private gold DWG. Reference PNGs are never used to construct cache entries.
12
+
13
+ Historical evaluator-version directories are generated artifacts and ignored by
14
+ git. The public distribution includes the active
15
+ `autocad-2019-r23.0.46-v4` cache so a checkout or exported bundle is
16
+ evaluation-ready without using somebody else's infrastructure. It verifies all
17
+ 50 task source checksums and pins 3D rendering to `Layout1`.
18
+
19
+ Portable bundle export checksum-binds the selected cache alongside the source
20
+ corpus:
21
+
22
+ ```bash
23
+ uv run autocad-bench bundle-export \
24
+ --output dist/autocad-bench-corpus-v1
25
+ ```
26
+
27
+ ```bash
28
+ uv run autocad-bench verify-gold-cache \
29
+ --evaluator-version autocad-2019-r23.0.46-v4 \
30
+ --split all
31
+ ```
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-001/metadata.json ADDED
The diff for this file is too large to render. See raw diff
 
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-001/render.png ADDED

Git LFS Details

  • SHA256: 4fbee3c187c428e99c695bd640bc310a766b46ffad571ce37f3d2725bba738df
  • Pointer size: 130 Bytes
  • Size of remote file: 20.8 kB
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-002/metadata.json ADDED
The diff for this file is too large to render. See raw diff
 
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-002/render.png ADDED

Git LFS Details

  • SHA256: 7c3e96ef4ac998755a5331b4e8b490960e8f35e8634e5d48441035fb7fc29186
  • Pointer size: 130 Bytes
  • Size of remote file: 15.9 kB
artifacts/gold-cache/autocad-2019-r23.0.46-v4/task-003/metadata.json ADDED
The diff for this file is too large to render. See raw diff