izanagi-ai 3.3.0 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.manifest +2 -2
  2. package/CHANGELOG.md +59 -0
  3. package/dist/runtime/contracts/artifacts.d.ts.map +1 -1
  4. package/dist/runtime/contracts/artifacts.js +4 -1
  5. package/dist/runtime/contracts/artifacts.js.map +1 -1
  6. package/dist/runtime/dashboard/page.d.ts +1 -1
  7. package/dist/runtime/dashboard/page.d.ts.map +1 -1
  8. package/dist/runtime/dashboard/page.js +157 -47
  9. package/dist/runtime/dashboard/page.js.map +1 -1
  10. package/dist/runtime/dashboard/server.d.ts.map +1 -1
  11. package/dist/runtime/dashboard/server.js +56 -2
  12. package/dist/runtime/dashboard/server.js.map +1 -1
  13. package/dist/runtime/memory/store.d.ts +6 -0
  14. package/dist/runtime/memory/store.d.ts.map +1 -1
  15. package/dist/runtime/memory/store.js +14 -0
  16. package/dist/runtime/memory/store.js.map +1 -1
  17. package/dist/runtime/observability/tracer.d.ts +8 -1
  18. package/dist/runtime/observability/tracer.d.ts.map +1 -1
  19. package/dist/runtime/observability/tracer.js +14 -1
  20. package/dist/runtime/observability/tracer.js.map +1 -1
  21. package/dist/runtime/tests/dashboard.test.js +56 -0
  22. package/dist/runtime/tests/dashboard.test.js.map +1 -1
  23. package/dist/runtime/tests/memory.test.js +22 -0
  24. package/dist/runtime/tests/memory.test.js.map +1 -1
  25. package/dist/runtime/tests/tools.test.js +12 -0
  26. package/dist/runtime/tests/tools.test.js.map +1 -1
  27. package/dist/runtime/tests/tracer.test.js +18 -0
  28. package/dist/runtime/tests/tracer.test.js.map +1 -1
  29. package/dist/runtime/tests/unicode-hygiene.test.d.ts +2 -0
  30. package/dist/runtime/tests/unicode-hygiene.test.d.ts.map +1 -0
  31. package/dist/runtime/tests/unicode-hygiene.test.js +43 -0
  32. package/dist/runtime/tests/unicode-hygiene.test.js.map +1 -0
  33. package/dist/runtime/text/unicode-hygiene.d.ts +32 -0
  34. package/dist/runtime/text/unicode-hygiene.d.ts.map +1 -0
  35. package/dist/runtime/text/unicode-hygiene.js +66 -0
  36. package/dist/runtime/text/unicode-hygiene.js.map +1 -0
  37. package/dist/runtime/tools/registry.d.ts.map +1 -1
  38. package/dist/runtime/tools/registry.js +6 -1
  39. package/dist/runtime/tools/registry.js.map +1 -1
  40. package/package.json +1 -1
package/.manifest CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "izanagi-ai",
3
- "version": "3.3.0",
3
+ "version": "3.5.0",
4
4
  "description": "Izanagi AI - Modular Skill-Oriented AI Prompt & Agent Framework for Autonomous Software Engineering",
5
5
  "author": "Pedro Henrique Sanches Leal",
6
6
  "license": "MIT",
7
7
  "homepage": "https://github.com/pedrohenriquesanchesleal4-debug/izanagi-ai#readme",
8
- "generatedAt": "2026-08-17T01:54:31.620Z",
8
+ "generatedAt": "2026-08-17T02:55:24.878Z",
9
9
  "agents": [
10
10
  {
11
11
  "id": "adversarial-critic",
package/CHANGELOG.md CHANGED
@@ -4,6 +4,65 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## [3.4.0] — 2026-08-17
8
+
9
+ ### Fixed (CRITICAL)
10
+ - **The 3 commits after the 3.3.0 release (crash-safe MemoryStore/Tracer persistence, dashboard live SSE updates, dashboard visual polish) were pushed to GitHub but never published to npm** — `izanagi-ai@3.3.0` on the registry was missing all of it. Caught because the version number hadn't changed, so nothing signaled a re-publish was needed. This release ships everything below.
11
+
12
+ ### Added
13
+ - **Crash-safe persistence**: `MemoryStore` now calls `save()` from every mutator (`recordAgentRun`/`recordSkillRun`/`recordModelRun`/`recordFailure`/`invalidateFailure`/`archiveFailure`/`addLearning`) instead of relying on one explicit `.save()` at the end of a run. `Tracer.flush()` persists a partial `RunTrace` snapshot after every span closes. Closing the CLI mid-run (Ctrl+C, crash, closed terminal) no longer loses that run's progress.
14
+ - **`izanagi dashboard` live updates**: new `/api/events` Server-Sent Events endpoint backed by `fs.watch` on the state directories (the dashboard and `izanagi run` are separate processes, so this is the channel that actually works across them — the in-memory Event System isn't reachable from a different process). The page now has a "● live" indicator and refreshes the visible run/benchmark list automatically.
15
+ - **Dashboard Execution Graph** now renders `trace.graph.parallelBatches` as real side-by-side lanes instead of a flat span list (falls back to spans for older traces without a graph). Runs without an `evaluation` field render a "running" badge instead of breaking. Memory panel gained a Models table (`recordModelRun` stats existed but were never surfaced anywhere).
16
+ - **Dashboard visual polish**: refined palette, pill badges, animated live-dot, and a real stats bar (total runs, pass rate, avg score, in-progress count, runs needing healing) computed client-side from the same data already fetched — no invented numbers.
17
+
18
+ ---
19
+
20
+ ## [3.3.0] — 2026-08-16
21
+
22
+ ### Added
23
+ Izanagi Evolution — audited the runtime against a 7-phase roadmap (Foundation →
24
+ Reliability → Observability → Model Runtime → Memory → Arena → Platform)
25
+ before writing any code. Found ~70% already implemented (ArtifactRegistry,
26
+ EvaluationEngine, Healer, ModelRouter, MemoryStore, BenchmarkRunner all
27
+ pre-existed); closed the real, specific gaps only — no reimplementation, no
28
+ invented metrics/benchmarks/results, one commit per phase:
29
+
30
+ - **Foundation**: `FailureCategory` (12-value failure-origin taxonomy,
31
+ `runtime/recovery/healing.ts`), `ArtifactRef` provenance (`id`/`producer`/
32
+ `createdAt`/`status`, populated from the existing `ArtifactRegistry`).
33
+ - **Reliability**: `ArtifactRegistry.detectRegression()` — a healed retry that
34
+ scores worse than the version it replaced now fails the node instead of
35
+ silently "succeeding" (roadmap's `HEALING_REJECTED` concept). Verified the
36
+ heal→retry→evaluate verification loop already existed end-to-end.
37
+ - **Observability**: real-time Event System (`runtime/observability/events.ts`)
38
+ — `EventBus` emitting `run.*`/`node.*`/`evaluation.*`/`diagnosis.*`/
39
+ `healing.*`/`verification.*`/`quality_gate.*`; new `OrchestratorOptions.onEvent`
40
+ hook (the dashboard below consumes the underlying data, not this hook yet).
41
+ - **Model Runtime**: `OllamaAdapter`, `LMStudioAdapter`, `OpenRouterAdapter`,
42
+ `CustomOpenAICompatibleAdapter` (`runtime/llm/client.ts`) — local providers
43
+ require explicit opt-in (`IZANAGI_OLLAMA_ENABLED=1` etc.) so the existing
44
+ zero-config headless mode can't silently break.
45
+ - **Memory**: failure-pattern lifecycle — `MemoryStore.invalidateFailure()` /
46
+ `archiveFailure()`, new `izanagi memory invalidate|archive <pattern>`.
47
+ - **Arena**: `BenchmarkRunner.runBaselines()`/`compareBaselines()` (N producers
48
+ head-to-head, genuine ties return no forced winner), `izanagi benchmark
49
+ report <id>`, `izanagi arena` as a literal alias for the existing benchmark
50
+ CLI (roadmap's naming, zero reimplementation).
51
+ - **Platform (foundation only, not the full phase)**: `izanagi dashboard
52
+ [--port N]` — a local `node:http` server (zero new dependencies) reading
53
+ straight from `TraceStore`/`ArtifactRegistry`/`MemoryStore`/benchmark
54
+ reports, serving a single embedded-HTML page (Run Explorer, Arena panel,
55
+ Memory panel). Deliberately not built: hosted multi-user platform, auth,
56
+ Skill/Agent Registry UI, plugin extensibility — scoped down to match how
57
+ the rest of the framework already works (local-first, single-user) after
58
+ asking the user directly instead of guessing.
59
+
60
+ 250/250 tests passing (57 new across the 7 phases), `izanagi doctor` PASSED
61
+ after every phase, no breaking changes to any existing CLI command or public
62
+ API.
63
+
64
+ ---
65
+
7
66
  ## [3.2.0] — 2026-08-16
8
67
 
9
68
  ### Added
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../../src/runtime/contracts/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAY7E;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,YAAY,EAAE,cAAc,CA2EjE,CAAC;AAYF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB,CA6CvF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAY3G;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAOzH"}
1
+ {"version":3,"file":"artifacts.d.ts","sourceRoot":"","sources":["../../../src/runtime/contracts/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAa7E;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,YAAY,EAAE,cAAc,CA2EjE,CAAC;AAYF,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,GAAG,gBAAgB,CA6CvF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,WAAW,CAc3G;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAA;CAAE,GAAG,MAAM,EAAE,CAOzH"}
@@ -6,6 +6,7 @@
6
6
  * devolve issues estruturadas.
7
7
  */
8
8
  import crypto from 'crypto';
9
+ import { sanitizeText } from '../text/unicode-hygiene.js';
9
10
  const STUB_PATTERNS = [
10
11
  /TODO/i,
11
12
  /FIXME/i,
@@ -162,7 +163,9 @@ export function validateArtifact(kind, content) {
162
163
  * Cria um ArtifactRef validado a partir de conteúdo.
163
164
  */
164
165
  export function makeArtifact(kind, name, content, path) {
165
- const text = toText(content);
166
+ // Unicode Hygiene (sempre ativa): hash/validação refletem o mesmo texto que
167
+ // de fato seria gravado em disco via fs.write, não o texto bruto do modelo.
168
+ const text = sanitizeText(toText(content)).text;
166
169
  const report = validateArtifact(kind, text);
167
170
  return {
168
171
  kind,
@@ -1 +1 @@
1
- {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../../src/runtime/contracts/artifacts.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../../src/runtime/contracts/artifacts.ts"],"names":[],"mappings":"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"}
@@ -2,5 +2,5 @@
2
2
  * HTML/CSS/JS do Dashboard, embutido como string — sem build step, sem
3
3
  * dependência nova. Vanilla JS puro (fetch + DOM), tema técnico escuro.
4
4
  */
5
- export declare const DASHBOARD_HTML = "<!doctype html>\n<html lang=\"pt-br\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Izanagi Dashboard</title>\n<style>\n :root { color-scheme: dark; }\n * { box-sizing: border-box; }\n body {\n margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n background: #0a0a0c; color: #d4d4d8; font-size: 13px; line-height: 1.5;\n }\n header { padding: 14px 20px; border-bottom: 1px solid #27272a; display: flex; align-items: center; gap: 16px; }\n header h1 { font-size: 14px; font-weight: 600; margin: 0; color: #e4e4e7; letter-spacing: 0.02em; }\n header .tag { color: #71717a; font-size: 11px; }\n nav { display: flex; gap: 2px; padding: 0 20px; border-bottom: 1px solid #27272a; }\n nav button {\n background: none; border: none; color: #71717a; padding: 10px 14px; cursor: pointer;\n font-family: inherit; font-size: 12px; border-bottom: 2px solid transparent;\n }\n nav button.active { color: #e4e4e7; border-bottom-color: #52525b; }\n main { display: grid; grid-template-columns: 340px 1fr; height: calc(100vh - 90px); }\n .list { overflow-y: auto; border-right: 1px solid #27272a; }\n .list-item { padding: 10px 16px; border-bottom: 1px solid #18181b; cursor: pointer; }\n .list-item:hover { background: #18181b; }\n .list-item.active { background: #1f1f23; border-left: 2px solid #52525b; }\n .list-item .id { color: #e4e4e7; font-weight: 600; font-size: 12px; }\n .list-item .meta { color: #71717a; font-size: 11px; margin-top: 2px; }\n .badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; }\n .badge.pass, .badge.ok { background: #14532d; color: #86efac; }\n .badge.fail, .badge.error { background: #450a0a; color: #fca5a5; }\n .badge.blocked, .badge.pending { background: #451a03; color: #fdba74; }\n .badge.unknown { background: #27272a; color: #a1a1aa; }\n .detail { padding: 20px; overflow-y: auto; }\n .empty { color: #52525b; padding: 40px; text-align: center; }\n section { margin-bottom: 24px; }\n section h2 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #71717a; margin: 0 0 8px; }\n table { width: 100%; border-collapse: collapse; }\n td, th { padding: 4px 8px; text-align: left; border-bottom: 1px solid #18181b; font-size: 12px; }\n th { color: #71717a; font-weight: 500; }\n .node-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px solid #18181b; }\n .node-row .name { flex: 1; color: #d4d4d8; }\n .node-row .dur { color: #52525b; font-size: 11px; }\n .kv { display: flex; gap: 6px; color: #a1a1aa; font-size: 11px; margin-bottom: 4px; }\n .kv b { color: #71717a; font-weight: 500; min-width: 90px; }\n .score { font-weight: 700; }\n</style>\n</head>\n<body>\n<header>\n <h1>IZANAGI</h1>\n <span class=\"tag\">dashboard local \u2014 foundation (Fase 7)</span>\n</header>\n<nav>\n <button data-tab=\"runs\" class=\"active\">Runs</button>\n <button data-tab=\"arena\">Arena</button>\n <button data-tab=\"memory\">Memory</button>\n</nav>\n<main>\n <div class=\"list\" id=\"list\"></div>\n <div class=\"detail\" id=\"detail\"><div class=\"empty\">Selecione um item \u00E0 esquerda.</div></div>\n</main>\n<script>\nlet currentTab = 'runs';\nlet items = [];\n\nfunction badge(status) {\n const s = (status || 'unknown').toLowerCase();\n return '<span class=\"badge ' + s + '\">' + s + '</span>';\n}\nfunction fmtMs(ms) { return ms == null ? '-' : (ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's'); }\nfunction esc(s) { return String(s ?? '').replace(/[&<>]/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }\n\nasync function loadTab(tab) {\n currentTab = tab;\n document.querySelectorAll('nav button').forEach((b) => b.classList.toggle('active', b.dataset.tab === tab));\n document.getElementById('detail').innerHTML = '<div class=\"empty\">Selecione um item \u00E0 esquerda.</div>';\n const list = document.getElementById('list');\n list.innerHTML = '<div class=\"empty\">carregando\u2026</div>';\n\n if (tab === 'runs') {\n items = await (await fetch('/api/runs')).json();\n list.innerHTML = items.map((r, i) =>\n '<div class=\"list-item\" data-i=\"' + i + '\">' +\n '<div class=\"id\">' + esc(r.runId) + '</div>' +\n '<div class=\"meta\">' + badge(r.evaluation && r.evaluation.verdict) + ' ' + esc(r.task || '').slice(0, 40) + ' \u00B7 ' + fmtMs(r.durationMs) + '</div>' +\n '</div>'\n ).join('') || '<div class=\"empty\">nenhum run ainda \u2014 rode \"izanagi run\"</div>';\n list.querySelectorAll('.list-item').forEach((el) => el.addEventListener('click', () => selectRun(items[+el.dataset.i].runId, el)));\n }\n\n if (tab === 'arena') {\n items = await (await fetch('/api/benchmarks')).json();\n list.innerHTML = items.map((r, i) =>\n '<div class=\"list-item\" data-i=\"' + i + '\">' +\n '<div class=\"id\">' + esc(r.id) + '</div>' +\n '<div class=\"meta\">' + esc(r.suite) + ' \u00B7 <span class=\"score\">' + r.summary.avgScore + '</span> \u00B7 ' + r.summary.passed + '/' + r.summary.total + '</div>' +\n '</div>'\n ).join('') || '<div class=\"empty\">nenhum benchmark rodado ainda \u2014 \"izanagi arena run\"</div>';\n list.querySelectorAll('.list-item').forEach((el) => el.addEventListener('click', () => selectBenchmark(items[+el.dataset.i], el)));\n }\n\n if (tab === 'memory') {\n const m = await (await fetch('/api/memory')).json();\n list.innerHTML = '<div class=\"list-item active\"><div class=\"id\">resumo</div><div class=\"meta\">agentes, skills, failures, learnings</div></div>';\n renderMemory(m);\n }\n}\n\nasync function selectRun(runId, el) {\n document.querySelectorAll('.list-item').forEach((x) => x.classList.remove('active'));\n el.classList.add('active');\n const data = await (await fetch('/api/runs/' + runId)).json();\n renderRun(data.trace, data.artifacts);\n}\n\nfunction renderRun(trace, artifacts) {\n const ev = trace.evaluation;\n let html = '';\n html += '<section><h2>Run</h2>';\n html += '<div class=\"kv\"><b>id</b>' + esc(trace.runId) + '</div>';\n html += '<div class=\"kv\"><b>task</b>' + esc(trace.task) + '</div>';\n html += '<div class=\"kv\"><b>status</b>' + badge(ev && ev.verdict) + '</div>';\n html += '<div class=\"kv\"><b>dura\u00E7\u00E3o</b>' + fmtMs(trace.durationMs) + '</div>';\n html += '<div class=\"kv\"><b>modelo</b>' + esc(trace.model || '-') + '</div>';\n html += '<div class=\"kv\"><b>agentes</b>' + esc((trace.agents || []).join(', ')) + '</div>';\n html += '<div class=\"kv\"><b>skills</b>' + esc((trace.skills || []).join(', ')) + '</div>';\n html += '<div class=\"kv\"><b>tokens</b>' + (trace.tokens ? trace.tokens.total : '-') + '</div>';\n html += '</section>';\n\n if (ev) {\n html += '<section><h2>Evaluation</h2>';\n html += '<div class=\"kv\"><b>score</b><span class=\"score\">' + ev.score + '</span></div>';\n html += '<div class=\"kv\"><b>verdict</b>' + badge(ev.verdict) + '</div>';\n if (ev.metrics) {\n html += '<table>' + Object.entries(ev.metrics).map(([k, v]) => '<tr><td>' + esc(k) + '</td><td>' + v + '</td></tr>').join('') + '</table>';\n }\n html += '</section>';\n }\n\n html += '<section><h2>Execution Graph (spans)</h2>';\n html += (trace.spans || []).map((s) =>\n '<div class=\"node-row\">' + badge(s.status) + '<span class=\"name\">' + esc(s.name) + ' <span style=\"color:#52525b\">[' + esc(s.type) + ']</span></span><span class=\"dur\">' + fmtMs(s.durationMs) + '</span></div>'\n ).join('') || '<div class=\"empty\">sem spans</div>';\n html += '</section>';\n\n if (trace.healing && trace.healing.length) {\n html += '<section><h2>Healing</h2>';\n html += trace.healing.map((h) =>\n '<div class=\"kv\"><b>' + esc(h.kind) + '</b>' + esc(h.category || h.failureKind) + ' \u2014 ' + esc((h.message || '').slice(0, 100)) + '</div>'\n ).join('');\n html += '</section>';\n }\n\n html += '<section><h2>Artifacts</h2>';\n html += '<table><tr><th>nome</th><th>vers\u00E3o</th><th>v\u00E1lido</th><th>produtor</th></tr>' +\n (artifacts || []).map((a) => '<tr><td>' + esc(a.name) + '</td><td>' + a.version + '</td><td>' + (a.valid ? '\u2714' : '\u2716') + '</td><td>' + esc((a.producer && (a.producer.agent || a.producer.skill)) || '-') + '</td></tr>').join('') +\n '</table></section>';\n\n document.getElementById('detail').innerHTML = html;\n}\n\nfunction selectBenchmark(report, el) {\n document.querySelectorAll('.list-item').forEach((x) => x.classList.remove('active'));\n el.classList.add('active');\n let html = '<section><h2>Benchmark Report</h2>';\n html += '<div class=\"kv\"><b>id</b>' + esc(report.id) + '</div>';\n html += '<div class=\"kv\"><b>suite</b>' + esc(report.suite) + '</div>';\n html += '<div class=\"kv\"><b>framework</b>v' + esc(report.frameworkVersion) + '</div>';\n html += '<div class=\"kv\"><b>score m\u00E9dio</b><span class=\"score\">' + report.summary.avgScore + '</span></div>';\n html += '<div class=\"kv\"><b>passou</b>' + report.summary.passed + '/' + report.summary.total + '</div>';\n html += '</section><section><h2>Por dom\u00EDnio</h2><table>' +\n Object.entries(report.byDomain || {}).map(([d, s]) => '<tr><td>' + esc(d) + '</td><td>' + s + '</td></tr>').join('') +\n '</table></section><section><h2>Casos</h2><table><tr><th>caso</th><th>dom\u00EDnio</th><th>status</th><th>score</th></tr>' +\n report.results.map((r) => '<tr><td>' + esc(r.caseId) + '</td><td>' + esc(r.domain) + '</td><td>' + badge(r.passed ? 'pass' : 'fail') + '</td><td>' + r.score + '</td></tr>').join('') +\n '</table></section>';\n document.getElementById('detail').innerHTML = html;\n}\n\nfunction renderMemory(m) {\n let html = '<section><h2>Agentes</h2><table><tr><th>agente</th><th>runs</th><th>score m\u00E9dio</th></tr>' +\n Object.entries(m.agents || {}).map(([id, s]) => '<tr><td>' + esc(id) + '</td><td>' + s.runs + '</td><td>' + s.avgScore.toFixed(2) + '</td></tr>').join('') +\n '</table></section>';\n html += '<section><h2>Skills</h2><table><tr><th>skill</th><th>uses</th></tr>' +\n Object.entries(m.skills || {}).map(([id, s]) => '<tr><td>' + esc(id) + '</td><td>' + s.uses + '</td></tr>').join('') +\n '</table></section>';\n html += '<section><h2>Failure patterns ativos</h2><table><tr><th>pattern</th><th>ocorr\u00EAncias</th><th>confian\u00E7a</th></tr>' +\n (m.failures || []).map((f) => '<tr><td>' + esc(f.pattern) + '</td><td>' + f.occurrences + '</td><td>' + f.confidence + '</td></tr>').join('') +\n '</table></section>';\n html += '<section><h2>Learnings</h2>' +\n (m.learnings || []).map((l) => '<div class=\"kv\">' + esc(l.text) + '</div>').join('') +\n '</section>';\n document.getElementById('detail').innerHTML = html;\n}\n\ndocument.querySelectorAll('nav button').forEach((b) => b.addEventListener('click', () => loadTab(b.dataset.tab)));\nloadTab('runs');\n</script>\n</body>\n</html>\n";
5
+ export declare const DASHBOARD_HTML = "<!doctype html>\n<html lang=\"pt-br\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Izanagi Dashboard</title>\n<style>\n :root {\n color-scheme: dark;\n --bg: #08080a; --bg-2: #0e0e11; --bg-3: #151519; --border: #212127; --border-soft: #1a1a1f;\n --text: #e4e4e7; --text-dim: #9a9aa2; --text-faint: #55555f;\n --accent: #8b7cf6; --accent-dim: #4c3fa8;\n --ok: #34d399; --ok-bg: #0d2b21; --bad: #f87171; --bad-bg: #2d1113; --warn: #fbbf24; --warn-bg: #2b210a;\n }\n * { box-sizing: border-box; }\n body {\n margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n background: var(--bg); color: var(--text); font-size: 13px; line-height: 1.6; -webkit-font-smoothing: antialiased;\n }\n header {\n padding: 16px 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 14px;\n background: linear-gradient(180deg, var(--bg-2), var(--bg));\n }\n header .mark {\n width: 22px; height: 22px; border-radius: 6px; flex-shrink: 0;\n background: linear-gradient(135deg, var(--accent), var(--accent-dim));\n display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 12px; color: #fff;\n }\n header h1 { font-size: 14px; font-weight: 700; margin: 0; color: var(--text); letter-spacing: 0.04em; }\n header .tag { color: var(--text-faint); font-size: 11px; }\n header .spacer { flex: 1; }\n .stats { display: flex; gap: 22px; padding: 10px 24px; border-bottom: 1px solid var(--border); background: var(--bg-2); }\n .stat { display: flex; flex-direction: column; gap: 2px; }\n .stat .n { font-size: 17px; font-weight: 700; color: var(--text); }\n .stat .n.ok { color: var(--ok); }\n .stat .n.bad { color: var(--bad); }\n .stat .l { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-faint); }\n nav { display: flex; gap: 4px; padding: 0 24px; border-bottom: 1px solid var(--border); background: var(--bg); }\n nav button {\n background: none; border: none; color: var(--text-dim); padding: 11px 14px; cursor: pointer;\n font-family: inherit; font-size: 12px; font-weight: 500; border-bottom: 2px solid transparent; transition: color .15s;\n }\n nav button:hover { color: var(--text); }\n nav button.active { color: var(--text); border-bottom-color: var(--accent); }\n main { display: grid; grid-template-columns: 320px 1fr; height: calc(100vh - 140px); }\n .list { overflow-y: auto; border-right: 1px solid var(--border); background: var(--bg-2); }\n .list-item { padding: 11px 16px; border-bottom: 1px solid var(--border-soft); cursor: pointer; transition: background .12s; }\n .list-item:hover { background: var(--bg-3); }\n .list-item.active { background: var(--bg-3); border-left: 2px solid var(--accent); padding-left: 14px; }\n .list-item .id { color: var(--text); font-weight: 600; font-size: 12px; }\n .list-item .meta { color: var(--text-dim); font-size: 11px; margin-top: 3px; }\n .badge {\n display: inline-block; padding: 2px 7px; border-radius: 20px; font-size: 9.5px; font-weight: 700;\n text-transform: uppercase; letter-spacing: 0.03em;\n }\n .badge.pass, .badge.pass_with_warnings, .badge.ok { background: var(--ok-bg); color: var(--ok); }\n .badge.fail, .badge.error, .badge.blocked { background: var(--bad-bg); color: var(--bad); }\n .badge.running, .badge.pending { background: var(--warn-bg); color: var(--warn); }\n .badge.unknown { background: var(--bg-3); color: var(--text-dim); }\n #live { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-faint); }\n #live .dot { width: 6px; height: 6px; border-radius: 50%; background: #3f3f46; transition: background .2s; }\n #live.on .dot { background: var(--ok); box-shadow: 0 0 6px var(--ok); animation: pulse 2s ease-in-out infinite; }\n #live.on { color: var(--ok); }\n @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }\n .batch { display: flex; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }\n .batch-node { border: 1px solid var(--border); border-radius: 6px; padding: 8px 12px; font-size: 11px; flex: 1; min-width: 150px; background: var(--bg-2); }\n .batch-label { color: var(--text-faint); font-size: 10px; width: 18px; padding-top: 9px; }\n .detail { padding: 24px; overflow-y: auto; }\n .empty { color: var(--text-faint); padding: 60px 20px; text-align: center; }\n section { margin-bottom: 26px; }\n section h2 {\n font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent);\n margin: 0 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border-soft);\n }\n table { width: 100%; border-collapse: collapse; }\n td, th { padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border-soft); font-size: 12px; }\n th { color: var(--text-faint); font-weight: 500; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; }\n tr:hover td { background: var(--bg-2); }\n .node-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border-soft); }\n .node-row .name { flex: 1; color: var(--text); }\n .node-row .dur { color: var(--text-faint); font-size: 11px; }\n .kv { display: flex; gap: 8px; color: var(--text-dim); font-size: 11.5px; margin-bottom: 5px; }\n .kv b { color: var(--text-faint); font-weight: 500; min-width: 90px; }\n .score { font-weight: 700; color: var(--text); }\n</style>\n</head>\n<body>\n<header>\n <span class=\"mark\">\u30A4</span>\n <h1>IZANAGI</h1>\n <span class=\"tag\">dashboard local \u2014 foundation (Fase 7)</span>\n <span class=\"spacer\"></span>\n <span id=\"live\"><span class=\"dot\"></span><span id=\"live-label\">conectando\u2026</span></span>\n</header>\n<div class=\"stats\" id=\"stats\"></div>\n<nav>\n <button data-tab=\"runs\" class=\"active\">Runs</button>\n <button data-tab=\"arena\">Arena</button>\n <button data-tab=\"memory\">Memory</button>\n</nav>\n<main>\n <div class=\"list\" id=\"list\"></div>\n <div class=\"detail\" id=\"detail\"><div class=\"empty\">Selecione um item \u00E0 esquerda.</div></div>\n</main>\n<script>\nlet currentTab = 'runs';\nlet items = [];\nlet selectedRunId = null;\n\nfunction badge(status) {\n const s = (status || 'running').toLowerCase();\n return '<span class=\"badge ' + s + '\">' + s + '</span>';\n}\nfunction fmtMs(ms) { return ms == null ? '-' : (ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's'); }\nfunction esc(s) { return String(s ?? '').replace(/[&<>]/g, (c) => ({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }\n\nasync function loadTab(tab) {\n currentTab = tab;\n selectedRunId = null;\n document.querySelectorAll('nav button').forEach((b) => b.classList.toggle('active', b.dataset.tab === tab));\n document.getElementById('detail').innerHTML = '<div class=\"empty\">Selecione um item \u00E0 esquerda.</div>';\n await refreshList();\n}\n\nfunction renderStats(runs) {\n const stats = document.getElementById('stats');\n const total = runs.length;\n const finished = runs.filter((r) => r.evaluation);\n const passed = finished.filter((r) => r.evaluation.verdict === 'PASS' || r.evaluation.verdict === 'PASS_WITH_WARNINGS').length;\n const running = total - finished.length;\n const avgScore = finished.length ? (finished.reduce((a, r) => a + (r.evaluation.score || 0), 0) / finished.length).toFixed(2) : '-';\n const healingRuns = runs.filter((r) => (r.healing || []).length > 0).length;\n stats.innerHTML =\n stat(total, 'runs') +\n stat(finished.length ? Math.round((passed / finished.length) * 100) + '%' : '-', 'pass rate', passed >= finished.length / 2 ? 'ok' : 'bad') +\n stat(avgScore, 'score m\u00E9dio') +\n stat(running, 'em andamento', running > 0 ? 'ok' : '') +\n stat(healingRuns, 'com healing');\n}\nfunction stat(n, label, cls) {\n return '<div class=\"stat\"><span class=\"n' + (cls ? ' ' + cls : '') + '\">' + n + '</span><span class=\"l\">' + label + '</span></div>';\n}\n\nasync function refreshList() {\n const list = document.getElementById('list');\n\n if (currentTab === 'runs') {\n items = await (await fetch('/api/runs')).json();\n list.innerHTML = items.map((r, i) =>\n '<div class=\"list-item' + (r.runId === selectedRunId ? ' active' : '') + '\" data-i=\"' + i + '\">' +\n '<div class=\"id\">' + esc(r.runId) + '</div>' +\n '<div class=\"meta\">' + badge(r.evaluation && r.evaluation.verdict) + ' ' + esc(r.task || '').slice(0, 40) + ' \u00B7 ' + fmtMs(r.durationMs) + '</div>' +\n '</div>'\n ).join('') || '<div class=\"empty\">nenhum run ainda \u2014 rode \"izanagi run\"</div>';\n list.querySelectorAll('.list-item').forEach((el) => el.addEventListener('click', () => selectRun(items[+el.dataset.i].runId, el)));\n renderStats(items);\n }\n\n if (currentTab === 'arena') {\n document.getElementById('stats').innerHTML = '';\n items = await (await fetch('/api/benchmarks')).json();\n list.innerHTML = items.map((r, i) =>\n '<div class=\"list-item\" data-i=\"' + i + '\">' +\n '<div class=\"id\">' + esc(r.id) + '</div>' +\n '<div class=\"meta\">' + esc(r.suite) + ' \u00B7 <span class=\"score\">' + r.summary.avgScore + '</span> \u00B7 ' + r.summary.passed + '/' + r.summary.total + '</div>' +\n '</div>'\n ).join('') || '<div class=\"empty\">nenhum benchmark rodado ainda \u2014 \"izanagi arena run\"</div>';\n list.querySelectorAll('.list-item').forEach((el) => el.addEventListener('click', () => selectBenchmark(items[+el.dataset.i], el)));\n }\n\n if (currentTab === 'memory') {\n document.getElementById('stats').innerHTML = '';\n const m = await (await fetch('/api/memory')).json();\n list.innerHTML = '<div class=\"list-item active\"><div class=\"id\">resumo</div><div class=\"meta\">agentes, skills, modelos, failures, learnings</div></div>';\n renderMemory(m);\n }\n}\n\nasync function selectRun(runId, el) {\n selectedRunId = runId;\n document.querySelectorAll('.list-item').forEach((x) => x.classList.remove('active'));\n el.classList.add('active');\n const data = await (await fetch('/api/runs/' + runId)).json();\n renderRun(data.trace, data.artifacts);\n}\n\nfunction renderExecutionGraph(trace) {\n const graph = trace.graph;\n if (!graph || !graph.parallelBatches || !graph.parallelBatches.length) {\n return (trace.spans || []).map((s) =>\n '<div class=\"node-row\">' + badge(s.status) + '<span class=\"name\">' + esc(s.name) + ' <span style=\"color:#52525b\">[' + esc(s.type) + ']</span></span><span class=\"dur\">' + fmtMs(s.durationMs) + '</span></div>'\n ).join('') || '<div class=\"empty\">sem spans</div>';\n }\n const byId = Object.fromEntries((graph.nodes || []).map((n) => [n.id, n]));\n return graph.parallelBatches.map((batch, i) =>\n '<div class=\"batch\"><span class=\"batch-label\">' + (i + 1) + '</span>' +\n batch.map((id) => {\n const n = byId[id] || { id, status: 'unknown' };\n return '<div class=\"batch-node\">' + badge(n.status) + ' <b>' + esc(n.id) + '</b><br><span style=\"color:#52525b\">' + esc(n.agent || (n.skills || []).join(',') || n.kind) + ' \u00B7 ' + fmtMs(n.durationMs) + '</span></div>';\n }).join('') +\n '</div>'\n ).join('') + '<div class=\"kv\" style=\"margin-top:6px\"><b>legenda</b>cada linha \u00E9 um lote paralelo \u2014 n\u00F3s lado a lado rodaram ao mesmo tempo</div>';\n}\n\nfunction renderRun(trace, artifacts) {\n const ev = trace.evaluation;\n const running = !ev;\n let html = '';\n html += '<section><h2>Run</h2>';\n html += '<div class=\"kv\"><b>id</b>' + esc(trace.runId) + '</div>';\n html += '<div class=\"kv\"><b>task</b>' + esc(trace.task) + '</div>';\n html += '<div class=\"kv\"><b>status</b>' + (running ? badge('running') : badge(ev.verdict)) + '</div>';\n html += '<div class=\"kv\"><b>dura\u00E7\u00E3o</b>' + fmtMs(trace.durationMs) + (running ? ' (em andamento)' : '') + '</div>';\n html += '<div class=\"kv\"><b>modelo</b>' + esc(trace.model || '-') + '</div>';\n html += '<div class=\"kv\"><b>agentes</b>' + esc((trace.agents || []).join(', ')) + '</div>';\n html += '<div class=\"kv\"><b>skills</b>' + esc((trace.skills || []).join(', ')) + '</div>';\n html += '<div class=\"kv\"><b>tokens</b>' + (trace.tokens ? trace.tokens.total : '-') + '</div>';\n html += '</section>';\n\n if (ev) {\n html += '<section><h2>Evaluation</h2>';\n html += '<div class=\"kv\"><b>score</b><span class=\"score\">' + ev.score + '</span></div>';\n html += '<div class=\"kv\"><b>verdict</b>' + badge(ev.verdict) + '</div>';\n if (ev.metrics) {\n html += '<table>' + Object.entries(ev.metrics).map(([k, v]) => '<tr><td>' + esc(k) + '</td><td>' + v + '</td></tr>').join('') + '</table>';\n }\n html += '</section>';\n }\n\n html += '<section><h2>Execution Graph</h2>' + renderExecutionGraph(trace) + '</section>';\n\n if (trace.healing && trace.healing.length) {\n html += '<section><h2>Healing</h2>';\n html += trace.healing.map((h) =>\n '<div class=\"kv\"><b>' + esc(h.kind) + '</b>' + esc(h.category || h.failureKind) + ' \u2014 ' + esc((h.message || '').slice(0, 100)) + '</div>'\n ).join('');\n html += '</section>';\n }\n\n html += '<section><h2>Artifacts</h2>';\n html += '<table><tr><th>nome</th><th>vers\u00E3o</th><th>v\u00E1lido</th><th>produtor</th></tr>' +\n (artifacts || []).map((a) => '<tr><td>' + esc(a.name) + '</td><td>' + a.version + '</td><td>' + (a.valid ? '\u2714' : '\u2716') + '</td><td>' + esc((a.producer && (a.producer.agent || a.producer.skill)) || '-') + '</td></tr>').join('') +\n '</table></section>';\n\n document.getElementById('detail').innerHTML = html;\n}\n\nfunction selectBenchmark(report, el) {\n document.querySelectorAll('.list-item').forEach((x) => x.classList.remove('active'));\n el.classList.add('active');\n let html = '<section><h2>Benchmark Report</h2>';\n html += '<div class=\"kv\"><b>id</b>' + esc(report.id) + '</div>';\n html += '<div class=\"kv\"><b>suite</b>' + esc(report.suite) + '</div>';\n html += '<div class=\"kv\"><b>framework</b>v' + esc(report.frameworkVersion) + '</div>';\n html += '<div class=\"kv\"><b>score m\u00E9dio</b><span class=\"score\">' + report.summary.avgScore + '</span></div>';\n html += '<div class=\"kv\"><b>passou</b>' + report.summary.passed + '/' + report.summary.total + '</div>';\n html += '</section><section><h2>Por dom\u00EDnio</h2><table>' +\n Object.entries(report.byDomain || {}).map(([d, s]) => '<tr><td>' + esc(d) + '</td><td>' + s + '</td></tr>').join('') +\n '</table></section><section><h2>Casos</h2><table><tr><th>caso</th><th>dom\u00EDnio</th><th>status</th><th>score</th></tr>' +\n report.results.map((r) => '<tr><td>' + esc(r.caseId) + '</td><td>' + esc(r.domain) + '</td><td>' + badge(r.passed ? 'pass' : 'fail') + '</td><td>' + r.score + '</td></tr>').join('') +\n '</table></section>';\n document.getElementById('detail').innerHTML = html;\n}\n\nfunction renderMemory(m) {\n let html = '<section><h2>Agentes</h2><table><tr><th>agente</th><th>runs</th><th>score m\u00E9dio</th></tr>' +\n Object.entries(m.agents || {}).map(([id, s]) => '<tr><td>' + esc(id) + '</td><td>' + s.runs + '</td><td>' + s.avgScore.toFixed(2) + '</td></tr>').join('') +\n '</table></section>';\n html += '<section><h2>Skills</h2><table><tr><th>skill</th><th>uses</th></tr>' +\n Object.entries(m.skills || {}).map(([id, s]) => '<tr><td>' + esc(id) + '</td><td>' + s.uses + '</td></tr>').join('') +\n '</table></section>';\n html += '<section><h2>Modelos</h2><table><tr><th>modelo</th><th>runs</th><th>taxa de sucesso</th><th>tokens m\u00E9dios</th></tr>' +\n Object.entries(m.models || {}).map(([id, s]) => '<tr><td>' + esc(id) + '</td><td>' + s.runs + '</td><td>' + Math.round((s.successes / Math.max(1, s.runs)) * 100) + '%</td><td>' + Math.round(s.avgTokens) + '</td></tr>').join('') +\n '</table></section>';\n html += '<section><h2>Failure patterns ativos</h2><table><tr><th>pattern</th><th>ocorr\u00EAncias</th><th>confian\u00E7a</th></tr>' +\n (m.failures || []).map((f) => '<tr><td>' + esc(f.pattern) + '</td><td>' + f.occurrences + '</td><td>' + f.confidence + '</td></tr>').join('') +\n '</table></section>';\n html += '<section><h2>Learnings</h2>' +\n (m.learnings || []).map((l) => '<div class=\"kv\">' + esc(l.text) + '</div>').join('') +\n '</section>';\n document.getElementById('detail').innerHTML = html;\n}\n\nfunction connectLive() {\n const live = document.getElementById('live');\n const label = document.getElementById('live-label');\n const source = new EventSource('/api/events');\n source.onopen = () => { live.classList.add('on'); label.textContent = 'live'; };\n source.onerror = () => { live.classList.remove('on'); label.textContent = 'reconectando\u2026'; };\n source.onmessage = async (evt) => {\n let payload;\n try { payload = JSON.parse(evt.data); } catch { return; }\n if (payload.kind === 'runs' && (currentTab === 'runs')) {\n await refreshList();\n if (selectedRunId) {\n const data = await (await fetch('/api/runs/' + selectedRunId)).json();\n renderRun(data.trace, data.artifacts);\n }\n }\n if (payload.kind === 'benchmarks' && currentTab === 'arena') {\n await refreshList();\n }\n };\n}\n\ndocument.querySelectorAll('nav button').forEach((b) => b.addEventListener('click', () => loadTab(b.dataset.tab)));\nloadTab('runs');\nconnectLive();\n</script>\n</body>\n</html>\n";
6
6
  //# sourceMappingURL=page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/runtime/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,cAAc,qyVAyM1B,CAAC"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/runtime/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,cAAc,iriBAuT1B,CAAC"}
@@ -9,53 +9,94 @@ export const DASHBOARD_HTML = `<!doctype html>
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1" />
10
10
  <title>Izanagi Dashboard</title>
11
11
  <style>
12
- :root { color-scheme: dark; }
12
+ :root {
13
+ color-scheme: dark;
14
+ --bg: #08080a; --bg-2: #0e0e11; --bg-3: #151519; --border: #212127; --border-soft: #1a1a1f;
15
+ --text: #e4e4e7; --text-dim: #9a9aa2; --text-faint: #55555f;
16
+ --accent: #8b7cf6; --accent-dim: #4c3fa8;
17
+ --ok: #34d399; --ok-bg: #0d2b21; --bad: #f87171; --bad-bg: #2d1113; --warn: #fbbf24; --warn-bg: #2b210a;
18
+ }
13
19
  * { box-sizing: border-box; }
14
20
  body {
15
21
  margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
16
- background: #0a0a0c; color: #d4d4d8; font-size: 13px; line-height: 1.5;
22
+ background: var(--bg); color: var(--text); font-size: 13px; line-height: 1.6; -webkit-font-smoothing: antialiased;
23
+ }
24
+ header {
25
+ padding: 16px 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 14px;
26
+ background: linear-gradient(180deg, var(--bg-2), var(--bg));
27
+ }
28
+ header .mark {
29
+ width: 22px; height: 22px; border-radius: 6px; flex-shrink: 0;
30
+ background: linear-gradient(135deg, var(--accent), var(--accent-dim));
31
+ display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 12px; color: #fff;
17
32
  }
18
- header { padding: 14px 20px; border-bottom: 1px solid #27272a; display: flex; align-items: center; gap: 16px; }
19
- header h1 { font-size: 14px; font-weight: 600; margin: 0; color: #e4e4e7; letter-spacing: 0.02em; }
20
- header .tag { color: #71717a; font-size: 11px; }
21
- nav { display: flex; gap: 2px; padding: 0 20px; border-bottom: 1px solid #27272a; }
33
+ header h1 { font-size: 14px; font-weight: 700; margin: 0; color: var(--text); letter-spacing: 0.04em; }
34
+ header .tag { color: var(--text-faint); font-size: 11px; }
35
+ header .spacer { flex: 1; }
36
+ .stats { display: flex; gap: 22px; padding: 10px 24px; border-bottom: 1px solid var(--border); background: var(--bg-2); }
37
+ .stat { display: flex; flex-direction: column; gap: 2px; }
38
+ .stat .n { font-size: 17px; font-weight: 700; color: var(--text); }
39
+ .stat .n.ok { color: var(--ok); }
40
+ .stat .n.bad { color: var(--bad); }
41
+ .stat .l { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-faint); }
42
+ nav { display: flex; gap: 4px; padding: 0 24px; border-bottom: 1px solid var(--border); background: var(--bg); }
22
43
  nav button {
23
- background: none; border: none; color: #71717a; padding: 10px 14px; cursor: pointer;
24
- font-family: inherit; font-size: 12px; border-bottom: 2px solid transparent;
44
+ background: none; border: none; color: var(--text-dim); padding: 11px 14px; cursor: pointer;
45
+ font-family: inherit; font-size: 12px; font-weight: 500; border-bottom: 2px solid transparent; transition: color .15s;
46
+ }
47
+ nav button:hover { color: var(--text); }
48
+ nav button.active { color: var(--text); border-bottom-color: var(--accent); }
49
+ main { display: grid; grid-template-columns: 320px 1fr; height: calc(100vh - 140px); }
50
+ .list { overflow-y: auto; border-right: 1px solid var(--border); background: var(--bg-2); }
51
+ .list-item { padding: 11px 16px; border-bottom: 1px solid var(--border-soft); cursor: pointer; transition: background .12s; }
52
+ .list-item:hover { background: var(--bg-3); }
53
+ .list-item.active { background: var(--bg-3); border-left: 2px solid var(--accent); padding-left: 14px; }
54
+ .list-item .id { color: var(--text); font-weight: 600; font-size: 12px; }
55
+ .list-item .meta { color: var(--text-dim); font-size: 11px; margin-top: 3px; }
56
+ .badge {
57
+ display: inline-block; padding: 2px 7px; border-radius: 20px; font-size: 9.5px; font-weight: 700;
58
+ text-transform: uppercase; letter-spacing: 0.03em;
59
+ }
60
+ .badge.pass, .badge.pass_with_warnings, .badge.ok { background: var(--ok-bg); color: var(--ok); }
61
+ .badge.fail, .badge.error, .badge.blocked { background: var(--bad-bg); color: var(--bad); }
62
+ .badge.running, .badge.pending { background: var(--warn-bg); color: var(--warn); }
63
+ .badge.unknown { background: var(--bg-3); color: var(--text-dim); }
64
+ #live { display: inline-flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-faint); }
65
+ #live .dot { width: 6px; height: 6px; border-radius: 50%; background: #3f3f46; transition: background .2s; }
66
+ #live.on .dot { background: var(--ok); box-shadow: 0 0 6px var(--ok); animation: pulse 2s ease-in-out infinite; }
67
+ #live.on { color: var(--ok); }
68
+ @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.45; } }
69
+ .batch { display: flex; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
70
+ .batch-node { border: 1px solid var(--border); border-radius: 6px; padding: 8px 12px; font-size: 11px; flex: 1; min-width: 150px; background: var(--bg-2); }
71
+ .batch-label { color: var(--text-faint); font-size: 10px; width: 18px; padding-top: 9px; }
72
+ .detail { padding: 24px; overflow-y: auto; }
73
+ .empty { color: var(--text-faint); padding: 60px 20px; text-align: center; }
74
+ section { margin-bottom: 26px; }
75
+ section h2 {
76
+ font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--accent);
77
+ margin: 0 0 10px; padding-bottom: 6px; border-bottom: 1px solid var(--border-soft);
25
78
  }
26
- nav button.active { color: #e4e4e7; border-bottom-color: #52525b; }
27
- main { display: grid; grid-template-columns: 340px 1fr; height: calc(100vh - 90px); }
28
- .list { overflow-y: auto; border-right: 1px solid #27272a; }
29
- .list-item { padding: 10px 16px; border-bottom: 1px solid #18181b; cursor: pointer; }
30
- .list-item:hover { background: #18181b; }
31
- .list-item.active { background: #1f1f23; border-left: 2px solid #52525b; }
32
- .list-item .id { color: #e4e4e7; font-weight: 600; font-size: 12px; }
33
- .list-item .meta { color: #71717a; font-size: 11px; margin-top: 2px; }
34
- .badge { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 600; }
35
- .badge.pass, .badge.ok { background: #14532d; color: #86efac; }
36
- .badge.fail, .badge.error { background: #450a0a; color: #fca5a5; }
37
- .badge.blocked, .badge.pending { background: #451a03; color: #fdba74; }
38
- .badge.unknown { background: #27272a; color: #a1a1aa; }
39
- .detail { padding: 20px; overflow-y: auto; }
40
- .empty { color: #52525b; padding: 40px; text-align: center; }
41
- section { margin-bottom: 24px; }
42
- section h2 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #71717a; margin: 0 0 8px; }
43
79
  table { width: 100%; border-collapse: collapse; }
44
- td, th { padding: 4px 8px; text-align: left; border-bottom: 1px solid #18181b; font-size: 12px; }
45
- th { color: #71717a; font-weight: 500; }
46
- .node-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; border-bottom: 1px solid #18181b; }
47
- .node-row .name { flex: 1; color: #d4d4d8; }
48
- .node-row .dur { color: #52525b; font-size: 11px; }
49
- .kv { display: flex; gap: 6px; color: #a1a1aa; font-size: 11px; margin-bottom: 4px; }
50
- .kv b { color: #71717a; font-weight: 500; min-width: 90px; }
51
- .score { font-weight: 700; }
80
+ td, th { padding: 6px 8px; text-align: left; border-bottom: 1px solid var(--border-soft); font-size: 12px; }
81
+ th { color: var(--text-faint); font-weight: 500; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.03em; }
82
+ tr:hover td { background: var(--bg-2); }
83
+ .node-row { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border-soft); }
84
+ .node-row .name { flex: 1; color: var(--text); }
85
+ .node-row .dur { color: var(--text-faint); font-size: 11px; }
86
+ .kv { display: flex; gap: 8px; color: var(--text-dim); font-size: 11.5px; margin-bottom: 5px; }
87
+ .kv b { color: var(--text-faint); font-weight: 500; min-width: 90px; }
88
+ .score { font-weight: 700; color: var(--text); }
52
89
  </style>
53
90
  </head>
54
91
  <body>
55
92
  <header>
93
+ <span class="mark">イ</span>
56
94
  <h1>IZANAGI</h1>
57
95
  <span class="tag">dashboard local — foundation (Fase 7)</span>
96
+ <span class="spacer"></span>
97
+ <span id="live"><span class="dot"></span><span id="live-label">conectando…</span></span>
58
98
  </header>
99
+ <div class="stats" id="stats"></div>
59
100
  <nav>
60
101
  <button data-tab="runs" class="active">Runs</button>
61
102
  <button data-tab="arena">Arena</button>
@@ -68,9 +109,10 @@ export const DASHBOARD_HTML = `<!doctype html>
68
109
  <script>
69
110
  let currentTab = 'runs';
70
111
  let items = [];
112
+ let selectedRunId = null;
71
113
 
72
114
  function badge(status) {
73
- const s = (status || 'unknown').toLowerCase();
115
+ const s = (status || 'running').toLowerCase();
74
116
  return '<span class="badge ' + s + '">' + s + '</span>';
75
117
  }
76
118
  function fmtMs(ms) { return ms == null ? '-' : (ms < 1000 ? ms + 'ms' : (ms / 1000).toFixed(1) + 's'); }
@@ -78,23 +120,48 @@ function esc(s) { return String(s ?? '').replace(/[&<>]/g, (c) => ({'&':'&amp;',
78
120
 
79
121
  async function loadTab(tab) {
80
122
  currentTab = tab;
123
+ selectedRunId = null;
81
124
  document.querySelectorAll('nav button').forEach((b) => b.classList.toggle('active', b.dataset.tab === tab));
82
125
  document.getElementById('detail').innerHTML = '<div class="empty">Selecione um item à esquerda.</div>';
126
+ await refreshList();
127
+ }
128
+
129
+ function renderStats(runs) {
130
+ const stats = document.getElementById('stats');
131
+ const total = runs.length;
132
+ const finished = runs.filter((r) => r.evaluation);
133
+ const passed = finished.filter((r) => r.evaluation.verdict === 'PASS' || r.evaluation.verdict === 'PASS_WITH_WARNINGS').length;
134
+ const running = total - finished.length;
135
+ const avgScore = finished.length ? (finished.reduce((a, r) => a + (r.evaluation.score || 0), 0) / finished.length).toFixed(2) : '-';
136
+ const healingRuns = runs.filter((r) => (r.healing || []).length > 0).length;
137
+ stats.innerHTML =
138
+ stat(total, 'runs') +
139
+ stat(finished.length ? Math.round((passed / finished.length) * 100) + '%' : '-', 'pass rate', passed >= finished.length / 2 ? 'ok' : 'bad') +
140
+ stat(avgScore, 'score médio') +
141
+ stat(running, 'em andamento', running > 0 ? 'ok' : '') +
142
+ stat(healingRuns, 'com healing');
143
+ }
144
+ function stat(n, label, cls) {
145
+ return '<div class="stat"><span class="n' + (cls ? ' ' + cls : '') + '">' + n + '</span><span class="l">' + label + '</span></div>';
146
+ }
147
+
148
+ async function refreshList() {
83
149
  const list = document.getElementById('list');
84
- list.innerHTML = '<div class="empty">carregando…</div>';
85
150
 
86
- if (tab === 'runs') {
151
+ if (currentTab === 'runs') {
87
152
  items = await (await fetch('/api/runs')).json();
88
153
  list.innerHTML = items.map((r, i) =>
89
- '<div class="list-item" data-i="' + i + '">' +
154
+ '<div class="list-item' + (r.runId === selectedRunId ? ' active' : '') + '" data-i="' + i + '">' +
90
155
  '<div class="id">' + esc(r.runId) + '</div>' +
91
156
  '<div class="meta">' + badge(r.evaluation && r.evaluation.verdict) + ' ' + esc(r.task || '').slice(0, 40) + ' · ' + fmtMs(r.durationMs) + '</div>' +
92
157
  '</div>'
93
158
  ).join('') || '<div class="empty">nenhum run ainda — rode "izanagi run"</div>';
94
159
  list.querySelectorAll('.list-item').forEach((el) => el.addEventListener('click', () => selectRun(items[+el.dataset.i].runId, el)));
160
+ renderStats(items);
95
161
  }
96
162
 
97
- if (tab === 'arena') {
163
+ if (currentTab === 'arena') {
164
+ document.getElementById('stats').innerHTML = '';
98
165
  items = await (await fetch('/api/benchmarks')).json();
99
166
  list.innerHTML = items.map((r, i) =>
100
167
  '<div class="list-item" data-i="' + i + '">' +
@@ -105,28 +172,49 @@ async function loadTab(tab) {
105
172
  list.querySelectorAll('.list-item').forEach((el) => el.addEventListener('click', () => selectBenchmark(items[+el.dataset.i], el)));
106
173
  }
107
174
 
108
- if (tab === 'memory') {
175
+ if (currentTab === 'memory') {
176
+ document.getElementById('stats').innerHTML = '';
109
177
  const m = await (await fetch('/api/memory')).json();
110
- list.innerHTML = '<div class="list-item active"><div class="id">resumo</div><div class="meta">agentes, skills, failures, learnings</div></div>';
178
+ list.innerHTML = '<div class="list-item active"><div class="id">resumo</div><div class="meta">agentes, skills, modelos, failures, learnings</div></div>';
111
179
  renderMemory(m);
112
180
  }
113
181
  }
114
182
 
115
183
  async function selectRun(runId, el) {
184
+ selectedRunId = runId;
116
185
  document.querySelectorAll('.list-item').forEach((x) => x.classList.remove('active'));
117
186
  el.classList.add('active');
118
187
  const data = await (await fetch('/api/runs/' + runId)).json();
119
188
  renderRun(data.trace, data.artifacts);
120
189
  }
121
190
 
191
+ function renderExecutionGraph(trace) {
192
+ const graph = trace.graph;
193
+ if (!graph || !graph.parallelBatches || !graph.parallelBatches.length) {
194
+ return (trace.spans || []).map((s) =>
195
+ '<div class="node-row">' + badge(s.status) + '<span class="name">' + esc(s.name) + ' <span style="color:#52525b">[' + esc(s.type) + ']</span></span><span class="dur">' + fmtMs(s.durationMs) + '</span></div>'
196
+ ).join('') || '<div class="empty">sem spans</div>';
197
+ }
198
+ const byId = Object.fromEntries((graph.nodes || []).map((n) => [n.id, n]));
199
+ return graph.parallelBatches.map((batch, i) =>
200
+ '<div class="batch"><span class="batch-label">' + (i + 1) + '</span>' +
201
+ batch.map((id) => {
202
+ const n = byId[id] || { id, status: 'unknown' };
203
+ return '<div class="batch-node">' + badge(n.status) + ' <b>' + esc(n.id) + '</b><br><span style="color:#52525b">' + esc(n.agent || (n.skills || []).join(',') || n.kind) + ' · ' + fmtMs(n.durationMs) + '</span></div>';
204
+ }).join('') +
205
+ '</div>'
206
+ ).join('') + '<div class="kv" style="margin-top:6px"><b>legenda</b>cada linha é um lote paralelo — nós lado a lado rodaram ao mesmo tempo</div>';
207
+ }
208
+
122
209
  function renderRun(trace, artifacts) {
123
210
  const ev = trace.evaluation;
211
+ const running = !ev;
124
212
  let html = '';
125
213
  html += '<section><h2>Run</h2>';
126
214
  html += '<div class="kv"><b>id</b>' + esc(trace.runId) + '</div>';
127
215
  html += '<div class="kv"><b>task</b>' + esc(trace.task) + '</div>';
128
- html += '<div class="kv"><b>status</b>' + badge(ev && ev.verdict) + '</div>';
129
- html += '<div class="kv"><b>duração</b>' + fmtMs(trace.durationMs) + '</div>';
216
+ html += '<div class="kv"><b>status</b>' + (running ? badge('running') : badge(ev.verdict)) + '</div>';
217
+ html += '<div class="kv"><b>duração</b>' + fmtMs(trace.durationMs) + (running ? ' (em andamento)' : '') + '</div>';
130
218
  html += '<div class="kv"><b>modelo</b>' + esc(trace.model || '-') + '</div>';
131
219
  html += '<div class="kv"><b>agentes</b>' + esc((trace.agents || []).join(', ')) + '</div>';
132
220
  html += '<div class="kv"><b>skills</b>' + esc((trace.skills || []).join(', ')) + '</div>';
@@ -143,11 +231,7 @@ function renderRun(trace, artifacts) {
143
231
  html += '</section>';
144
232
  }
145
233
 
146
- html += '<section><h2>Execution Graph (spans)</h2>';
147
- html += (trace.spans || []).map((s) =>
148
- '<div class="node-row">' + badge(s.status) + '<span class="name">' + esc(s.name) + ' <span style="color:#52525b">[' + esc(s.type) + ']</span></span><span class="dur">' + fmtMs(s.durationMs) + '</span></div>'
149
- ).join('') || '<div class="empty">sem spans</div>';
150
- html += '</section>';
234
+ html += '<section><h2>Execution Graph</h2>' + renderExecutionGraph(trace) + '</section>';
151
235
 
152
236
  if (trace.healing && trace.healing.length) {
153
237
  html += '<section><h2>Healing</h2>';
@@ -189,6 +273,9 @@ function renderMemory(m) {
189
273
  html += '<section><h2>Skills</h2><table><tr><th>skill</th><th>uses</th></tr>' +
190
274
  Object.entries(m.skills || {}).map(([id, s]) => '<tr><td>' + esc(id) + '</td><td>' + s.uses + '</td></tr>').join('') +
191
275
  '</table></section>';
276
+ html += '<section><h2>Modelos</h2><table><tr><th>modelo</th><th>runs</th><th>taxa de sucesso</th><th>tokens médios</th></tr>' +
277
+ Object.entries(m.models || {}).map(([id, s]) => '<tr><td>' + esc(id) + '</td><td>' + s.runs + '</td><td>' + Math.round((s.successes / Math.max(1, s.runs)) * 100) + '%</td><td>' + Math.round(s.avgTokens) + '</td></tr>').join('') +
278
+ '</table></section>';
192
279
  html += '<section><h2>Failure patterns ativos</h2><table><tr><th>pattern</th><th>ocorrências</th><th>confiança</th></tr>' +
193
280
  (m.failures || []).map((f) => '<tr><td>' + esc(f.pattern) + '</td><td>' + f.occurrences + '</td><td>' + f.confidence + '</td></tr>').join('') +
194
281
  '</table></section>';
@@ -198,8 +285,31 @@ function renderMemory(m) {
198
285
  document.getElementById('detail').innerHTML = html;
199
286
  }
200
287
 
288
+ function connectLive() {
289
+ const live = document.getElementById('live');
290
+ const label = document.getElementById('live-label');
291
+ const source = new EventSource('/api/events');
292
+ source.onopen = () => { live.classList.add('on'); label.textContent = 'live'; };
293
+ source.onerror = () => { live.classList.remove('on'); label.textContent = 'reconectando…'; };
294
+ source.onmessage = async (evt) => {
295
+ let payload;
296
+ try { payload = JSON.parse(evt.data); } catch { return; }
297
+ if (payload.kind === 'runs' && (currentTab === 'runs')) {
298
+ await refreshList();
299
+ if (selectedRunId) {
300
+ const data = await (await fetch('/api/runs/' + selectedRunId)).json();
301
+ renderRun(data.trace, data.artifacts);
302
+ }
303
+ }
304
+ if (payload.kind === 'benchmarks' && currentTab === 'arena') {
305
+ await refreshList();
306
+ }
307
+ };
308
+ }
309
+
201
310
  document.querySelectorAll('nav button').forEach((b) => b.addEventListener('click', () => loadTab(b.dataset.tab)));
202
311
  loadTab('runs');
312
+ connectLive();
203
313
  </script>
204
314
  </body>
205
315
  </html>
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/runtime/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyM7B,CAAC"}
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/runtime/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuT7B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/runtime/dashboard/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,IAAI,MAAM,MAAM,CAAC;AAOxB,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAQD,mHAAmH;AACnH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAC,MAAM,CA2D/E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/runtime/dashboard/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,IAAI,MAAM,MAAM,CAAC;AAOxB,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAoCD,mHAAmH;AACnH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,sBAAsB,GAAG,IAAI,CAAC,MAAM,CAsF/E"}