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.
- package/.manifest +2 -2
- package/CHANGELOG.md +59 -0
- package/dist/runtime/contracts/artifacts.d.ts.map +1 -1
- package/dist/runtime/contracts/artifacts.js +4 -1
- package/dist/runtime/contracts/artifacts.js.map +1 -1
- package/dist/runtime/dashboard/page.d.ts +1 -1
- package/dist/runtime/dashboard/page.d.ts.map +1 -1
- package/dist/runtime/dashboard/page.js +157 -47
- package/dist/runtime/dashboard/page.js.map +1 -1
- package/dist/runtime/dashboard/server.d.ts.map +1 -1
- package/dist/runtime/dashboard/server.js +56 -2
- package/dist/runtime/dashboard/server.js.map +1 -1
- package/dist/runtime/memory/store.d.ts +6 -0
- package/dist/runtime/memory/store.d.ts.map +1 -1
- package/dist/runtime/memory/store.js +14 -0
- package/dist/runtime/memory/store.js.map +1 -1
- package/dist/runtime/observability/tracer.d.ts +8 -1
- package/dist/runtime/observability/tracer.d.ts.map +1 -1
- package/dist/runtime/observability/tracer.js +14 -1
- package/dist/runtime/observability/tracer.js.map +1 -1
- package/dist/runtime/tests/dashboard.test.js +56 -0
- package/dist/runtime/tests/dashboard.test.js.map +1 -1
- package/dist/runtime/tests/memory.test.js +22 -0
- package/dist/runtime/tests/memory.test.js.map +1 -1
- package/dist/runtime/tests/tools.test.js +12 -0
- package/dist/runtime/tests/tools.test.js.map +1 -1
- package/dist/runtime/tests/tracer.test.js +18 -0
- package/dist/runtime/tests/tracer.test.js.map +1 -1
- package/dist/runtime/tests/unicode-hygiene.test.d.ts +2 -0
- package/dist/runtime/tests/unicode-hygiene.test.d.ts.map +1 -0
- package/dist/runtime/tests/unicode-hygiene.test.js +43 -0
- package/dist/runtime/tests/unicode-hygiene.test.js.map +1 -0
- package/dist/runtime/text/unicode-hygiene.d.ts +32 -0
- package/dist/runtime/text/unicode-hygiene.d.ts.map +1 -0
- package/dist/runtime/text/unicode-hygiene.js +66 -0
- package/dist/runtime/text/unicode-hygiene.js.map +1 -0
- package/dist/runtime/tools/registry.d.ts.map +1 -1
- package/dist/runtime/tools/registry.js +6 -1
- package/dist/runtime/tools/registry.js.map +1 -1
- package/package.json +1 -1
package/.manifest
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "izanagi-ai",
|
|
3
|
-
"version": "3.
|
|
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-
|
|
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;
|
|
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
|
-
|
|
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":"AAAA;;;;;;GAMG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"artifacts.js","sourceRoot":"","sources":["../../../src/runtime/contracts/artifacts.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,MAAM,aAAa,GAAG;IACpB,OAAO;IACP,QAAQ;IACR,MAAM;IACN,kBAAkB;IAClB,cAAc;IACd,kBAAkB;IAClB,cAAc;CACf,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAyC;IACpE,YAAY,EAAE;QACZ,IAAI,EAAE,cAAc;QACpB,QAAQ,EAAE,CAAC,OAAO,EAAE,YAAY,EAAE,YAAY,CAAC;QAC/C,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC,eAAe,EAAE,QAAQ,EAAE,MAAM,CAAC;KAC/C;IACD,YAAY,EAAE;QACZ,IAAI,EAAE,cAAc;QACpB,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC;QAC3C,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC,MAAM,EAAE,oBAAoB,CAAC;KAC1C;IACD,iBAAiB,EAAE;QACjB,IAAI,EAAE,iBAAiB;QACvB,QAAQ,EAAE,CAAC,OAAO,EAAE,WAAW,CAAC;QAChC,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE;YACd,IAAI,OAAO,CAAC,KAAK,QAAQ;gBAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;YAC3D,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,IAAI,CAAC,2CAA2C,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;YAC7G,IAAI,CAAC,oDAAoD,CAAC,IAAI,CAAC,CAAC,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;YACxH,OAAO,MAAM,CAAC;QAChB,CAAC;KACF;IACD,cAAc,EAAE;QACd,IAAI,EAAE,cAAc;QACpB,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC;QACnD,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC,MAAM,CAAC;KACpB;IACD,iBAAiB,EAAE;QACjB,IAAI,EAAE,iBAAiB;QACvB,QAAQ,EAAE,CAAC,UAAU,EAAE,iBAAiB,EAAE,aAAa,CAAC;QACxD,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC,MAAM,CAAC;KACpB;IACD,WAAW,EAAE;QACX,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,WAAW,CAAC;QAC9C,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC,MAAM,CAAC;KACpB;IACD,qBAAqB,EAAE;QACrB,IAAI,EAAE,qBAAqB;QAC3B,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;QAC5B,OAAO,EAAE,GAAG;QACZ,SAAS,EAAE,CAAC,MAAM,EAAE,oBAAoB,CAAC;KAC1C;IACD,UAAU,EAAE;QACV,IAAI,EAAE,YAAY;QAClB,QAAQ,EAAE,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC;QACzC,OAAO,EAAE,GAAG;KACb;IACD,kBAAkB,EAAE;QAClB,IAAI,EAAE,kBAAkB;QACxB,QAAQ,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC;QAChC,OAAO,EAAE,GAAG;KACb;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,CAAC,UAAU,EAAE,SAAS,CAAC;QACjC,OAAO,EAAE,GAAG;KACb;IACD,KAAK,EAAE;QACL,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC;QAC5B,OAAO,EAAE,EAAE;KACZ;IACD,GAAG,EAAE;QACH,IAAI,EAAE,KAAK;QACX,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,CAAC;KACX;CACF,CAAC;AAEF,mDAAmD;AACnD,SAAS,MAAM,CAAC,OAAgB;IAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAe;IACzC,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9E,CAAC;AAUD;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAkB,EAAE,OAAgB;IACnE,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC;IAC9D,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAE7B,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,CAAC,EAAE,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,2BAA2B,IAAI,CAAC,MAAM,kBAAkB,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;IACzF,CAAC;IAED,mDAAmD;IACnD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACpD,MAAM,GAAG,GAAG,OAAkC,CAAC;QAC/C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpC,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,SAAS,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;gBACzE,MAAM,CAAC,IAAI,CAAC,+BAA+B,KAAK,GAAG,CAAC,CAAC;YACvD,CAAC;QACH,CAAC;IACH,CAAC;SAAM,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtC,wDAAwD;QACxD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;gBACtD,MAAM,CAAC,IAAI,CAAC,yCAAyC,KAAK,kBAAkB,CAAC,CAAC;YAChF,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;QACvC,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,iCAAiC,CAAC,GAAG,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC1C,KAAK,MAAM,CAAC,IAAI,aAAa,EAAE,CAAC;YAC9B,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjB,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;gBAC9D,MAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,KAAK,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACpD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC;AAClF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,IAAkB,EAAE,IAAY,EAAE,OAAgB,EAAE,IAAa;IAC5F,4EAA4E;IAC5E,4EAA4E;IAC5E,MAAM,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAChD,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC5C,OAAO;QACL,IAAI;QACJ,IAAI;QACJ,IAAI;QACJ,IAAI,EAAE,IAAI,CAAC,MAAM;QACjB,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC;QACvB,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,MAAM,EAAE,MAAM,CAAC,MAAM;KACtB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,KAA0E;IAC7G,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,CAAC,KAAK,CAAC,IAAI;QAAE,MAAM,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC;IAC7D,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC;IAC5D,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAC/F,IAAI,CAAC,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;IAChI,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -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) => ({'&':'&','<':'<','>':'>'}[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) => ({'&':'&','<':'<','>':'>'}[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,
|
|
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 {
|
|
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:
|
|
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 {
|
|
19
|
-
header
|
|
20
|
-
header .
|
|
21
|
-
|
|
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:
|
|
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:
|
|
45
|
-
th { color:
|
|
46
|
-
|
|
47
|
-
.node-row
|
|
48
|
-
.node-row .
|
|
49
|
-
.
|
|
50
|
-
.kv
|
|
51
|
-
.
|
|
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 || '
|
|
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) => ({'&':'&',
|
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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(
|
|
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 (
|
|
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
|
|
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;
|
|
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"}
|