diff --git a/AGENTS.md b/AGENTS.md index 87ba416..567c5f2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -216,7 +216,7 @@ agentops-showcase/ │ ├── validate-demo-external-css.sh # Lint docs/demo HTML/JS for external CSS only │ ├── validate-demo-ui.sh # CSS lint + demo unit tests (no cluster) │ ├── export-readme-architecture.sh # Playwright PNG for README (v5/live.html step 0) -│ ├── export-readme-architecture-gif.sh # Playwright frame capture + ffmpeg GIF (hops 0–6 default) +│ ├── export-readme-architecture-gif.sh # Playwright frame capture + ffmpeg GIF (hops 0–22 default) │ ├── demo-observability-proxy.py │ ├── demo-enable-guardrails.sh # Live demo Change 2: NeMo inference path │ ├── demo-disable-guardrails.sh # Reset to direct MaaS diff --git a/README.md b/README.md index 0d3ba9c..a6c0d40 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ This project demonstrates the **BYOA (Bring Your Own Agent)** approach from the ## Architecture -![AgentOps platform architecture](assets/overall-architecture.png) +![AgentOps platform architecture — full baseline flow](assets/overall-architecture.gif) -*Interactive map:* [v5/live.html](docs/demo/v5/live.html) step **Overall Demo** (`./scripts/demo-presenter-serve.sh`). Standalone reference: [`overall-demo-architecture.html`](docs/demo/overall-demo-architecture.html). Static PNG above: regenerate locally with `make export-architecture` after diagram edits. +*Interactive map:* [v5/live.html](https://davidseve.github.io/agentops-showcase/demo/v5/live.html#step-0) step **Overall Demo** (`./scripts/demo-presenter-serve.sh`). Standalone reference: [`overall-demo-architecture.html`](docs/demo/overall-demo-architecture.html). ## Platform Stack @@ -95,7 +95,7 @@ make deploy-all && make validate ├── Makefile # Wrapper → deploy/Makefile (make demo, deploy-all, …) ├── AGENTS.md # AI agent context (for Cursor/Claude) ├── README.md # This file -├── assets/ # README diagrams (overall-architecture.png — see make export-architecture) +├── assets/ # README diagrams (overall-architecture.gif/.png — see make export-architecture-gif) ├── config/ # OpenClaw template + OpenShell sandbox policies ├── docs/ # Guides, ADRs, demo narrative, ROADMAP ├── deploy/ # Helm charts + deploy/Makefile @@ -108,6 +108,7 @@ make deploy-all && make validate ## Documentation +- [CfP kit](docs/cfp/README.md) — reusable talk proposals and TLDR submission base - [NeMo Guardrails Installation](docs/nemo-guardrails-installation.md) - [Demo script (live, ~9–10 min)](docs/demo-script.md) — Cursor skills in [AGENTS.md](AGENTS.md) § Demo v1 - [Demo scenario logs runbook](docs/demo/demo-scenario-logs.md) — per-test log evidence and [Sandbox panel highlight rules](docs/demo/demo-scenario-logs.md#sandbox-panel-highlight-rules) diff --git a/assets/overall-architecture.gif b/assets/overall-architecture.gif index 60b4e10..2e496fa 100644 Binary files a/assets/overall-architecture.gif and b/assets/overall-architecture.gif differ diff --git a/assets/overall-architecture.png b/assets/overall-architecture.png index 9eeca09..9e6c5fd 100644 Binary files a/assets/overall-architecture.png and b/assets/overall-architecture.png differ diff --git a/assets/preview/README.md b/assets/preview/README.md deleted file mode 100644 index 02b9c81..0000000 --- a/assets/preview/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Architecture GIF previews (local, temporary) - -**Do not commit this folder.** It exists only to compare three animated variants of the v5 **Overall Demo** FlowStory diagram before picking one for the README (or dropping the GIF entirely). - -Source: `docs/demo/v5/live.html#step-0` (same canvas as `assets/overall-architecture.png`). - -## Variants in this folder - -| File | Hops (`jumpTo` index) | Narrative | Frames | FPS | ~Duration | -|------|------------------------|-----------|--------|-----|-----------| -| `overall-architecture-request-path.gif` | 0–6 | End user → OpenClaw → inference → NeMo → MaaS → LLM | 7 | 2 | ~3.5 s | -| `overall-architecture-request-response.gif` | 0–13 | Above + LLM response path + Landlock | 14 | 2 | ~7 s | -| `overall-architecture-full.gif` | 0–21 | Full baseline (+ egress + MLflow trace) | 22 | 3 | ~7.3 s | - -Open each GIF side by side in the IDE or a browser tab to compare pacing and scope. - -## Regenerate previews - -From repo root (requires `ffmpeg`, Node, Playwright Chromium): - -```bash -./scripts/export-architecture-gif-preview.sh -``` - -Single variant with custom output: - -```bash -cd tests -ARCH_GIF_LAST_INDEX=13 \ -ARCH_GIF_FPS=2 \ -ARCH_GIF_OUTPUT=../assets/preview/my-variant.gif \ -PLAYWRIGHT_BROWSERS_PATH=0 node export-architecture-gif.mjs -``` - -Useful env vars (see `tests/export-architecture-gif.mjs` header): - -| Variable | Default | Purpose | -|----------|---------|---------| -| `ARCH_GIF_FIRST_INDEX` | `0` | First hop index | -| `ARCH_GIF_LAST_INDEX` | `6` | Last hop index | -| `ARCH_GIF_FPS` | `2` | GIF playback rate | -| `ARCH_GIF_FRAME_DELAY_MS` | `700` | Wait after each `jumpTo` before screenshot | -| `ARCH_GIF_WIDTH` | `1200` | Output width (px) | -| `ARCH_GIF_INCLUDE_RESET` | `false` | Extra idle frame before hop 1 | -| `ARCH_GIF_OUTPUT` | `assets/overall-architecture.gif` | Output path | - -Official export (after a variant is chosen): - -```bash -make export-architecture-gif -# example: ARCH_GIF_LAST_INDEX=13 make export-architecture-gif -``` - -## When you pick a winner — cleanup checklist - -### A) Keep one GIF in the repo - -1. Copy the chosen file to the canonical path: - ```bash - cp assets/preview/overall-architecture-.gif assets/overall-architecture.gif - ``` -2. Set defaults in `tests/export-architecture-gif.mjs` (`ARCH_GIF_LAST_INDEX`, `ARCH_GIF_FPS`) to match the winner so `make export-architecture-gif` reproduces it. -3. **Delete temporary preview boilerplate:** - - `assets/preview/` (this entire directory, including this README and the three `*.gif` files) - - `scripts/export-architecture-gif-preview.sh` -4. Optionally wire `assets/overall-architecture.gif` into root `README.md` next to the PNG. -5. Commit only `assets/overall-architecture.gif` + any default/env doc tweaks — not `assets/preview/`. - -### B) Do not ship a GIF - -1. **Delete preview artifacts:** - - `assets/preview/` (entire directory) - - `scripts/export-architecture-gif-preview.sh` -2. **Delete GIF export tooling** (if no longer needed): - - `tests/export-architecture-gif.mjs` - - `scripts/export-readme-architecture-gif.sh` - - `assets/overall-architecture.gif` (if present) - - `make export-architecture-gif` target in root `Makefile` - - `export-architecture-gif` script in `tests/package.json` - - GIF section in `docs/demo/README.md` - - `export-readme-architecture-gif.sh` line in `AGENTS.md` -3. Keep `make export-architecture` (PNG) — that remains the documented static diagram. - -## Related (keep unless option B) - -| Path | Role | -|------|------| -| `tests/export-architecture-gif.mjs` | Playwright frame capture + ffmpeg assembly | -| `scripts/export-readme-architecture-gif.sh` | Wrapper for `make export-architecture-gif` | -| `tests/export-architecture-diagram.mjs` | Static PNG export (unchanged) | - ---- - -*Temporary local note — remove with `assets/preview/` once a decision is made.* diff --git a/assets/preview/overall-architecture-full.gif b/assets/preview/overall-architecture-full.gif deleted file mode 100644 index 357500a..0000000 Binary files a/assets/preview/overall-architecture-full.gif and /dev/null differ diff --git a/assets/preview/overall-architecture-request-path.gif b/assets/preview/overall-architecture-request-path.gif deleted file mode 100644 index 60b4e10..0000000 Binary files a/assets/preview/overall-architecture-request-path.gif and /dev/null differ diff --git a/assets/preview/overall-architecture-request-response.gif b/assets/preview/overall-architecture-request-response.gif deleted file mode 100644 index ed196d8..0000000 Binary files a/assets/preview/overall-architecture-request-response.gif and /dev/null differ diff --git a/docs/cfp/README.md b/docs/cfp/README.md new file mode 100644 index 0000000..a9979aa --- /dev/null +++ b/docs/cfp/README.md @@ -0,0 +1,56 @@ +# Call for Papers (CfP) kit + +Reusable content for conference and internal talk proposals about the AgentOps showcase. Start from a saved submission, swap event-specific fields, and pull snippets from shared blocks. + +## Workflow + +1. **Pick a base** — [`submissions/enterprise-agentops-rhoai.md`](submissions/enterprise-agentops-rhoai.md) is the canonical AgentOps + RHOAI proposal. +2. **Copy the template** — [`template.md`](template.md) for a new event or angle (hands-on lab, shorter lightning, Spanish abstract, etc.). +3. **Reuse blocks** — [`reusable-blocks.md`](reusable-blocks.md) holds key message, stack bullets, demo scenarios, links, and TDP mappings without duplicating full proposals. +4. **Align with live assets** — keep abstract/comments in sync with [`demo-script.md`](../demo-script.md), [`demo/README.md`](../demo/README.md), and repo [`README.md`](../../README.md). + +When a proposal is accepted or rejected, add a one-line status note at the top of the submission file (`Status: submitted | accepted | rejected | delivered`). + +## Directory + +``` +docs/cfp/ +├── README.md # This guide +├── template.md # Blank CfP form (Red Hat TLDR-style fields) +├── reusable-blocks.md # Copy-paste snippets +└── submissions/ + └── enterprise-agentops-rhoai.md # Base proposal (saved 2026-03) +``` + +## Related source material + +| Asset | Use in CfP | +|-------|------------| +| [README.md](../../README.md) | Elevator pitch, architecture GIF, platform stack table | +| [stack-decisions.md](../stack-decisions.md) | TDP / product alignment narrative | +| [AGENT-SANDBOX-AND-OPENSHELL.md](../AGENT-SANDBOX-AND-OPENSHELL.md) | Sandbox, Landlock, OpenShell deep dive | +| [demo-script.md](../demo-script.md) | Timed live demo flow (~9–10 min core; scale to 50 min with Q&A) | +| [demo-narrative-v1.md](../demo-narrative-v1.md) | Spanish presenter narrative | +| [demo/v5/live.html](../demo/v5/live.html) | Presenter companion URL for comments field | +| [ADR index](../adr/README.md) | Technical credibility (MLflow tracing, guardrails, pinning) | + +## Format cheat sheet (Red Hat TLDR) + +| Slot | Session | Break | Typical use | +|------|---------|-------|-------------| +| 10 min | 9 min | 1 min | Lightning (often ×3 in a block) | +| 30 min | 25 min | 5 min | Short demo or talk | +| 60 min | 50 min | 10 min | Full demo + discussion (**default for this project**) | +| 120 min | 110 min | 10 min | Hands-on lab | +| 240 min | 240 min | embedded | Workshop / hackathon | + +Virtual delivery: standard Google Meet or similar (noted in TLDR CfP forms). + +## Speakers (default) + +| Name | Email | +|------|-------| +| David Severia | dseveria@redhat.com | +| Carlos Cornejo | ccornejo@redhat.com | + +Update per event if the panel changes. diff --git a/docs/cfp/reusable-blocks.md b/docs/cfp/reusable-blocks.md new file mode 100644 index 0000000..05b3d0f --- /dev/null +++ b/docs/cfp/reusable-blocks.md @@ -0,0 +1,111 @@ +# Reusable CfP blocks + +Copy-paste snippets for new proposals. Combine with [`submissions/enterprise-agentops-rhoai.md`](submissions/enterprise-agentops-rhoai.md) or [`template.md`](template.md). + +## Key message + +``` +Your Agent. Our Platform. Production-Ready. +``` + +BYOA from the Red Hat AI Agentic Strategy 2026: the agent harness (OpenClaw) is interchangeable; the platform stack is not. + +## Titles + +**Primary (saved base):** + +``` +Enterprise AgentOps: Secure BYOA Agent Platform on Red Hat OpenShift AI +``` + +**Alternatives:** + +``` +Production AgentOps on RHOAI: Sandbox, Zero-Trust Credentials, and Full Traceability +``` + +``` +Bring Your Own Agent — Run It Safely on OpenShift AI +``` + +## One-line problem statement + +``` +Many enterprises want autonomous AI agents, but security teams block adoption due to credential leaks, uncontrolled OS execution, and missing audit trails. +``` + +## Platform pillars (bullet form) + +Use 3–4 in abstracts; full set in comments or long proposals. + +``` +- Sandbox containment: OpenClaw runs in an Agent Sandbox with Landlock file locks and OpenShell egress policies. +- Zero-trust credentials: All outbound traffic routes through OpenShell Gateway; inference.local injects API keys so agents never see raw secrets. +- AI guardrails: NeMo Guardrails (TrustyAI) filter requests/responses before MaaS inference. +- Full observability: MLflow captures agent reasoning, tool calls, and LLM hops for compliance tracing. +``` + +## Business value (closing line) + +``` +Business value: unlocks agentic AI adoption by solving security, credential isolation, and auditability in one platform stack. +``` + +## Live demo scenarios (A–D) + +For **Comments** or session outline: + +``` +Live interactive demo: Four scripted security scenarios — credential exfiltration attempt, filesystem access (/etc/shadow), selective egress opening, and NeMo Guardrails blocking prompt injection — with real-time sandbox blocking and MLflow trace visibility. +``` + +| Scenario | Story | Platform proof | +|----------|-------|----------------| +| A · Credentials | Agent tries to exfiltrate API keys | OpenShell gateway key vault; keys never in sandbox | +| B · Files | Access to `/etc/shadow` | Landlock file locks | +| C · Egress | Default deny → allow google.com | OpenShell egress policy change | +| D · Guardrails | Prompt injection | NeMo Guardrails (TrustyAI) before MaaS | + +## Multi-platform alignment (Comments) + +``` +Multi-platform alignment: Red Hat OpenShift, RHOAI 3.x (MLflow, TrustyAI, MaaS), Agent Sandbox Operator (OLM), OpenShell, and RHEL kernel security (Landlock). +``` + +## Links + +| Label | URL | +|-------|-----| +| Presenter companion (v5) | https://davidseve.github.io/agentops-showcase/demo/v5/live.html#step-0 | +| Repository | https://github.com/davidseve/agentops-showcase | +| Architecture (local) | `docs/demo/v5/live.html` step **Overall Demo** | + +## TDP mapping (Red Hat TLDR) + +| TDP | Rationale | +|-----|-----------| +| Virtualization | OpenShift / RHOAI on virtualized or cloud infrastructure | +| Container Management | Agent Sandbox Operator, OLM, sandbox pods, OpenShell on OCP | +| Automation | GitOps-ready Helm deploy, scripted demo lifecycle | +| AI Platform | RHOAI 3.x, MaaS, MLflow, TrustyAI / NeMo Guardrails | + +## Suggested form defaults + +| Field | Recommendation | +|-------|----------------| +| Format | Presentation/Demonstration | +| Duration | 60 min (50 + 10 break) — core demo ~10 min + architecture + Q&A | +| Categories | Product and Solutions Focus; Experience Reports | +| Audience | Specialist | +| Level | Intermediate | +| Capacity | 0 (unlimited virtual) | + +## Short abstract (~100 words) + +Trimmed from the base submission for strict word limits: + +``` +Security teams block agentic AI over credential leaks, uncontrolled execution, and missing audit trails. This session demos production AgentOps on Red Hat OpenShift AI 3.x for safe BYOA workloads: Agent Sandbox with Landlock and OpenShell egress; zero-trust credentials via the gateway and inference.local key injection; NeMo Guardrails before MaaS; MLflow traces for compliance. Live scenarios show blocked exfiltration, filesystem denial, controlled egress, and guardrail enforcement. Outcome: adopt autonomous agents without sacrificing isolation or observability. +``` + +(~95 words) diff --git a/docs/cfp/submissions/enterprise-agentops-rhoai.md b/docs/cfp/submissions/enterprise-agentops-rhoai.md new file mode 100644 index 0000000..71cecef --- /dev/null +++ b/docs/cfp/submissions/enterprise-agentops-rhoai.md @@ -0,0 +1,166 @@ +# Enterprise AgentOps on RHOAI — base CfP + +| Field | Value | +|-------|-------| +| Saved | 2026-03 | +| Status | base (reuse for future CfPs) | +| Event | _(fill when submitting)_ | + +Canonical reusable blocks: [`../reusable-blocks.md`](../reusable-blocks.md) + +--- + +## Title * + +``` +Enterprise AgentOps: Secure BYOA Agent Platform on Red Hat OpenShift AI +``` + +## Abstract * + +Keep this short — around 100 words. + +``` +Many enterprises want autonomous AI agents, but security teams block adoption due to credential leaks, uncontrolled OS execution, and missing audit trails. + +This session demos a production-grade AgentOps platform on Red Hat OpenShift AI (RHOAI) 3.x that enables safe Bring Your Own Agent (BYOA) workloads: + +- Sandbox containment: OpenClaw runs in an Agent Sandbox with Landlock file locks and OpenShell egress policies. +- Zero-trust credentials: All outbound traffic routes through OpenShell Gateway; inference.local injects API keys so agents never see raw secrets. +- AI guardrails: NeMo Guardrails (TrustyAI) filter requests/responses before MaaS inference. +- Full observability: MLflow captures agent reasoning, tool calls, and LLM hops for compliance tracing. + +Business value: unlocks agentic AI adoption by solving security, credential isolation, and auditability in one platform stack. +``` + +## Speakers emails * + +``` +dseveria@redhat.com, ccornejo@redhat.com +``` + +## Format * + +The TLDR will be virtual — running off standard Google Meet or similar technology. + +Defined styles and typical duration: + +- Presentation/Demonstration (25 or 50 mins) +- Topic Discussion (25 or 50 mins) +- Hands-on (110, 240 mins) + +**Selected:** Presentation/Demonstration + +## Duration (minutes) * + +Slot options (session + break): + +| Minutes | Session + break | +|---------|-----------------| +| 10 | 9 + 1 (lightning, groups of 3) | +| 30 | 25 + 5 | +| 60 | 50 + 10 | +| 120 | 110 + 10 (hands-on) | +| 240 | 240 embedded breaks (workshop) | + +**Selected:** 60 + +## Topic Categories * + +- Experience Reports — Lessons learned working with a customer, in the community or collaborating inside Red Hat. +- Product and Solutions Focus — Red Hat products and solutions portfolio +- Business, Leadership, Culture & Soft Skills +- Open Source Spotlight +- The Mix + +**Selected:** + +- [x] Experience Reports +- [x] Product and Solutions Focus + +## TDP * + +Red Hat Technology Decision Points alignment: + +| TDP | Selected | +|-----|----------| +| Server/Cloud Operating System | | +| Virtualization | ✓ | +| Container Management | ✓ | +| Application Platform | | +| Automation | ✓ | +| AI Platform | ✓ | +| NO TDP | | + +## Target audience * + +**Selected:** Specialist + +_(Options: Specialist · Generalist · Non-technical · All)_ + +## Level * + +**Selected:** Intermediate + +_(Options: Foundational · Intermediate · Advanced · Expert)_ + +## Capacity * + +``` +0 +``` + +Unlimited for virtual session. + +## Comments * + +``` +Multi-platform alignment: Red Hat OpenShift, RHOAI 3.x (MLflow, TrustyAI, MaaS), Agent Sandbox Operator (OLM), OpenShell, and RHEL kernel security (Landlock). + +Live interactive demo: Four scripted security scenarios — credential exfiltration attempt, filesystem access (/etc/shadow), selective egress opening, and NeMo Guardrails blocking prompt injection — with real-time sandbox blocking and MLflow trace visibility. + +Presenter companion UI: architecture map and observability panel for a guided walkthrough (not required for attendees) https://davidseve.github.io/agentops-showcase/demo/v5/live.html#step-0 + +Key message: Your Agent. Our Platform. Production-Ready. — BYOA from the Red Hat AI Agentic Strategy 2026; the agent harness (OpenClaw) is interchangeable; the platform stack is not. + +Repo: https://github.com/davidseve/agentops-showcase +``` + +--- + +## Copy-paste bundle (plain text) + +For forms that use a single text area: + +``` +Title: Enterprise AgentOps: Secure BYOA Agent Platform on Red Hat OpenShift AI + +Abstract: +Many enterprises want autonomous AI agents, but security teams block adoption due to credential leaks, uncontrolled OS execution, and missing audit trails. + +This session demos a production-grade AgentOps platform on Red Hat OpenShift AI (RHOAI) 3.x that enables safe Bring Your Own Agent (BYOA) workloads: + +- Sandbox containment: OpenClaw runs in an Agent Sandbox with Landlock file locks and OpenShell egress policies. +- Zero-trust credentials: All outbound traffic routes through OpenShell Gateway; inference.local injects API keys so agents never see raw secrets. +- AI guardrails: NeMo Guardrails (TrustyAI) filter requests/responses before MaaS inference. +- Full observability: MLflow captures agent reasoning, tool calls, and LLM hops for compliance tracing. + +Business value: unlocks agentic AI adoption by solving security, credential isolation, and auditability in one platform stack. + +Speakers: dseveria@redhat.com, ccornejo@redhat.com +Format: Presentation/Demonstration | Duration: 60 min | Virtual (Google Meet) +Categories: Experience Reports, Product and Solutions Focus +TDP: Virtualization, Container Management, Automation, AI Platform +Audience: Specialist | Level: Intermediate | Capacity: 0 + +Comments: +Multi-platform alignment: Red Hat OpenShift, RHOAI 3.x (MLflow, TrustyAI, MaaS), Agent Sandbox Operator (OLM), OpenShell, and RHEL kernel security (Landlock). + +Live interactive demo: Four scripted security scenarios — credential exfiltration attempt, filesystem access (/etc/shadow), selective egress opening, and NeMo Guardrails blocking prompt injection — with real-time sandbox blocking and MLflow trace visibility. + +Presenter companion UI: https://davidseve.github.io/agentops-showcase/demo/v5/live.html#step-0 + +Key message: Your Agent. Our Platform. Production-Ready. + +Repo: https://github.com/davidseve/agentops-showcase +``` diff --git a/docs/cfp/template.md b/docs/cfp/template.md new file mode 100644 index 0000000..5150499 --- /dev/null +++ b/docs/cfp/template.md @@ -0,0 +1,99 @@ +# CfP submission — [EVENT NAME] + +> Copy this file to `submissions/.md` and fill in. Delete sections your CfP form does not use. + +## Metadata + +| Field | Value | +|-------|-------| +| Event | | +| Submission deadline | | +| Status | draft | +| Based on | `enterprise-agentops-rhoai.md` | + +--- + +## Title * + +``` +[One line — see reusable-blocks.md § Titles] +``` + +## Abstract * (~100 words) + +``` +[Problem → demo → 3–4 bullets → business value] +``` + +## Speakers emails * + +``` +dseveria@redhat.com, ccornejo@redhat.com +``` + +## Format * + +- [ ] Presentation/Demonstration +- [ ] Topic Discussion +- [ ] Hands-on +- [ ] Other: + +## Duration (minutes) * + +- [ ] 10 +- [ ] 30 +- [ ] 60 +- [ ] 120 +- [ ] 240 + +## Topic Categories * + +- [ ] Experience Reports +- [ ] Product and Solutions Focus +- [ ] Business, Leadership, Culture & Soft Skills +- [ ] Open Source Spotlight +- [ ] The Mix + +## TDP (Technology Decision Points) + +- [ ] Server/Cloud Operating System +- [ ] Virtualization +- [ ] Container Management +- [ ] Application Platform +- [ ] Automation +- [ ] AI Platform +- [ ] NO TDP + +## Target audience * + +- [ ] Specialist +- [ ] Generalist +- [ ] Non-technical +- [ ] All + +## Level * + +- [ ] Foundational +- [ ] Intermediate +- [ ] Advanced +- [ ] Expert + +## Capacity * + +``` +0 +``` + +## Comments * + +``` +[Multi-platform alignment, live demo scenarios, presenter companion URL, key message, repo link] +``` + +--- + +## Internal notes (do not submit) + +- Angles to stress for this event: +- Sections to shorten: +- Hands-on prerequisites if lab format: diff --git a/docs/demo/README.md b/docs/demo/README.md index e79db4d..b808af5 100644 --- a/docs/demo/README.md +++ b/docs/demo/README.md @@ -180,11 +180,12 @@ make export-architecture Output: [`assets/overall-architecture.png`](../../assets/overall-architecture.png) (baseline flow with all hops revealed). Commit the PNG after regenerating — CI does not auto-export or validate it. -Animated GIF (request path hops 0–6 by default; requires `ffmpeg`): +Animated GIF (full baseline hops 0–22 by default; requires `ffmpeg`): ```bash make export-architecture-gif -# full baseline: ARCH_GIF_LAST_INDEX=21 make export-architecture-gif +# or: ./scripts/export-readme-architecture-gif.sh +# shorter path only: ARCH_GIF_LAST_INDEX=6 make export-architecture-gif ``` Output: [`assets/overall-architecture.gif`](../../assets/overall-architecture.gif). Same manual-only policy as the PNG. diff --git a/docs/demo/scenarios/overall-flows.js b/docs/demo/scenarios/overall-flows.js index e10e8b5..609b4a1 100644 --- a/docs/demo/scenarios/overall-flows.js +++ b/docs/demo/scenarios/overall-flows.js @@ -74,38 +74,28 @@ export const BASELINE_MUTATIONS = [ }, ], }, - { step: 6, label: `5 ${LAYER_NAMES.maas}`, actions: [{ id: "p-path", style: "highlight" }] }, - { step: 7, label: `6 ${LAYER_NAMES.llm}` }, - { - step: 8, - label: `6 ${LAYER_NAMES.llm} → ${LAYER_NAMES.maas} (read this file)`, - phase: "response", - replaceBody: [ - { - value: `Path: ${LAYER_NAMES.llm} → ${LAYER_NAMES.maas} → ${LAYER_NAMES.nemo} → ${LAYER_NAMES.gw} → ${LAYER_NAMES.ir} → ${LAYER_NAMES.openClaw} then ${LAYER_NAMES.landlock}`, - style: "add", - id: "p-path", - }, - ], - }, - { step: 9, label: `5 ${LAYER_NAMES.maas} → ${LAYER_NAMES.nemo}` }, + { step: 6, label: `5 ${LAYER_NAMES.nemo} → ${LAYER_NAMES.maas}` }, + { step: 7, label: `6 ${LAYER_NAMES.maas} → Guardrails LLM` }, + { step: 8, label: `7 ${LAYER_NAMES.maas}`, actions: [{ id: "p-path", style: "highlight" }] }, + { step: 9, label: `8 ${LAYER_NAMES.llm}` }, { step: 10, label: `4 ${LAYER_NAMES.nemo} → ${LAYER_NAMES.gw}` }, { step: 11, label: `3 ${LAYER_NAMES.gw} → ${LAYER_NAMES.ir}` }, { step: 12, label: `2 ${LAYER_NAMES.ir} → ${LAYER_NAMES.openClaw}` }, - { step: 13, label: `7 ${LAYER_NAMES.landlock} →`, actions: [{ id: "l-files", style: "highlight" }] }, - { step: 14, label: `7 ← ${LAYER_NAMES.landlock}` }, + { step: 13, label: `9 ${LAYER_NAMES.landlock} →`, actions: [{ id: "l-files", style: "highlight" }] }, + { step: 14, label: `9 ${LAYER_NAMES.landlock} → file` }, + { step: 15, label: `9 ← ${LAYER_NAMES.landlock}` }, { - step: 15, - label: `8 ${LAYER_NAMES.openClaw} → ${LAYER_NAMES.gw}`, + step: 16, + label: `10 ${LAYER_NAMES.openClaw} → ${LAYER_NAMES.gw}`, actions: [{ id: "l-net", style: "highlight" }, { id: "l-gw", style: "highlight" }], }, - { step: 16, label: `8 ${LAYER_NAMES.gw} → ${LAYER_NAMES.internet}` }, - { step: 17, label: `8 ${LAYER_NAMES.internet} → ${LAYER_NAMES.gw}` }, - { step: 18, label: `8 ${LAYER_NAMES.gw} → ${LAYER_NAMES.openClaw}` }, - { step: 19, label: `1 ${LAYER_NAMES.openClaw} → ${LAYER_NAMES.gw}` }, - { step: 20, label: `1 ${LAYER_NAMES.gw} → ${LAYER_NAMES.endUser}` }, - { step: 21, label: `9 ${LAYER_NAMES.openClaw} → ${LAYER_NAMES.gw}`, actions: [{ id: "l-mlflow", style: "highlight" }] }, - { step: 22, label: `9 ${LAYER_NAMES.gw} → ${LAYER_NAMES.mlflow}` }, + { step: 17, label: `10 ${LAYER_NAMES.gw} → ${LAYER_NAMES.internet}` }, + { step: 18, label: `10 ${LAYER_NAMES.internet} → ${LAYER_NAMES.gw}` }, + { step: 19, label: `10 ${LAYER_NAMES.gw} → ${LAYER_NAMES.openClaw}` }, + { step: 20, label: `1 ${LAYER_NAMES.openClaw} → ${LAYER_NAMES.gw}` }, + { step: 21, label: `1 ${LAYER_NAMES.gw} → ${LAYER_NAMES.endUser}` }, + { step: 22, label: `11 ${LAYER_NAMES.openClaw} → ${LAYER_NAMES.gw}`, actions: [{ id: "l-mlflow", style: "highlight" }] }, + { step: 23, label: `11 ${LAYER_NAMES.gw} → ${LAYER_NAMES.mlflow}` }, ]; export const BASELINE_STEPS = [ @@ -155,40 +145,40 @@ export const BASELINE_STEPS = [ ...ARROW.gwToNemo, }, { - text: `${LAYER_NAMES.nemo} forwards to ${LAYER_NAMES.maas}`, + text: `${LAYER_NAMES.nemo} sends to ${LAYER_NAMES.maas} for guardrail evaluation`, mode: "arrow", from: "nemo", to: "maas", - color: COLORS.maas, + color: COLORS.nemo, num: 5, ...ARROW.nemoToMaas, }, { - text: `${LAYER_NAMES.maas} calls the ${LAYER_NAMES.llm}`, + text: `${LAYER_NAMES.maas} calls the Guardrails LLM`, mode: "arrow", from: "maas", - to: "llm", - color: "#7ee787", + to: "guardrailsLlm", + color: COLORS.nemo, num: 6, - ...ARROW.maasToLlm, + ...ARROW.maasToGuardrailsLlm, }, { - text: `${LAYER_NAMES.llm} responds — it tells the agent to read a file`, + text: `${LAYER_NAMES.nemo} forwards to ${LAYER_NAMES.maas}`, mode: "arrow", - from: "llm", + from: "nemo", to: "maas", color: COLORS.maas, - num: 6, - ...ARROW.llmToMaas, + num: 7, + ...ARROW.nemoToMaasRight, }, { - text: `Response returns through ${LAYER_NAMES.nemo}`, + text: `${LAYER_NAMES.maas} calls the ${LAYER_NAMES.llm}`, mode: "arrow", from: "maas", - to: "nemo", - color: COLORS.maas, - num: 5, - ...ARROW.maasToNemo, + to: "llm", + color: "#7ee787", + num: 8, + ...ARROW.maasToLlmRight, }, { text: `Response returns through the ${LAYER_NAMES.gw}`, @@ -223,16 +213,25 @@ export const BASELINE_STEPS = [ from: "oc", to: "landlock", color: COLORS.landlock, - num: 7, + num: 9, ...ARROW.ocToLandlock, }, + { + text: `${LAYER_NAMES.landlock} denies file access`, + mode: "arrow", + from: "landlock", + to: "file", + color: COLORS.landlock, + num: 0, + ...ARROW.landlockToFile, + }, { text: `${LAYER_NAMES.landlock} returns to ${LAYER_NAMES.openClaw} — no secret files`, mode: "arrow", from: "landlock", to: "oc", color: COLORS.landlock, - num: 7, + num: 9, ...ARROW.landlockToOc, }, { @@ -241,7 +240,7 @@ export const BASELINE_STEPS = [ from: "oc", to: "gw", color: COLORS.risk, - num: 8, + num: 10, ...ARROW.ocToGw, }, { @@ -250,7 +249,7 @@ export const BASELINE_STEPS = [ from: "gw", to: "internet", color: COLORS.risk, - num: 8, + num: 10, ...ARROW.gwToInternet, }, { @@ -259,7 +258,7 @@ export const BASELINE_STEPS = [ from: "internet", to: "gw", color: COLORS.maas, - num: 8, + num: 10, ...ARROW.internetToGw, }, { @@ -268,7 +267,7 @@ export const BASELINE_STEPS = [ from: "gw", to: "oc", color: COLORS.maas, - num: 8, + num: 10, ...ARROW.gwToOcReturn, }, { @@ -295,7 +294,7 @@ export const BASELINE_STEPS = [ from: "oc", to: "gw", color: COLORS.trace, - num: 9, + num: 11, ...ARROW.ocToGwTrace, }, { @@ -304,7 +303,7 @@ export const BASELINE_STEPS = [ from: "gw", to: "mlflow", color: COLORS.trace, - num: 9, + num: 11, ...ARROW.gwToMlflow, }, ]; @@ -321,22 +320,23 @@ export const BASELINE_LEGEND_HOP_HIGHLIGHTS = { 4: [4, 5], 5: [5, 6], 6: [6, 7], - 7: [7], - 8: [7], - 9: [7, 6], + 7: [6, 7], + 8: [6, 7], + 9: [7], 10: [6, 5], 11: [5, 4], 12: [4, 2], 13: [2, 3], - 14: [3, 2], - 15: [1, 5, 2], - 16: [1, 5], + 14: [3], + 15: [3, 2], + 16: [1, 5, 2], 17: [1, 5], - 18: [5, 2], - 19: [2, 5], - 20: [0, 5], - 21: [8, 2, 5], - 22: [8, 5], + 18: [1, 5], + 19: [5, 2], + 20: [2, 5], + 21: [0, 5], + 22: [8, 2, 5], + 23: [8, 5], }; // --- Scenario A --- diff --git a/docs/demo/scenarios/scenario-layout.js b/docs/demo/scenarios/scenario-layout.js index ca9207f..8b9956b 100644 --- a/docs/demo/scenarios/scenario-layout.js +++ b/docs/demo/scenarios/scenario-layout.js @@ -392,12 +392,15 @@ export const ARROW = { }, gwToNemo: { fromBottom: true, toTop: true, fromXOff: 74, toXOff: -LANE_V }, nemoToMaas: { fromBottom: true, toTop: true, fromXOff: -LANE_V, toXOff: -LANE_V }, + nemoToMaasRight: { fromBottom: true, toTop: true, fromXOff: LANE_V, toXOff: LANE_V }, irToMaas: { fromBottom: true, toTop: true, fromXOff: -LANE_V, toXOff: -LANE_V }, irToNemo: { fromBottom: true, toTop: true, fromXOff: -LANE_V, toXOff: -LANE_V }, gwToMaas: { fromBottom: true, toTop: true, fromXOff: 74, toXOff: -LANE_V }, maasToLlm: { fromBottom: true, toTop: true, fromXOff: -LANE_V, toXOff: -LANE_V }, + maasToLlmRight: { fromBottom: true, toTop: true, fromXOff: LANE_V, toXOff: LANE_V }, llmToMaas: { fromTop: true, toBottom: true, fromXOff: LANE_V, toXOff: LANE_V }, maasToGuardrailsLlm: { fromBottom: true, toTop: true, fromXOff: -LANE_V, toXOff: LANE_V }, + nemoToGuardrailsLlm: { fromBottom: true, toTop: true, fromXOff: -LANE_V * 4, toXOff: 0 }, maasToNemo: { fromTop: true, toBottom: true, fromXOff: LANE_V, toXOff: LANE_V }, nemoToGw: { fromTop: true, toBottom: true, fromXOff: LANE_V, toXOff: 98 }, /** Band 9 — oc → gw trace (own column; separated from bands 1/8). */ diff --git a/docs/demo/scenarios/shared-scenario.js b/docs/demo/scenarios/shared-scenario.js index a07c1ec..d1b4c87 100644 --- a/docs/demo/scenarios/shared-scenario.js +++ b/docs/demo/scenarios/shared-scenario.js @@ -2,7 +2,7 @@ * Shared FlowStory bootstrap for per-scenario demo panels (A–D). */ -import { FlowStory } from "../shared/vendor/flowstory.min.js"; +import { FlowStory } from "../shared/vendor/flowstory.min.js?v=2"; import { setLogosEnabled } from "../shared/logo-renderer.js"; import { OVERALL_IN_DOC, syncCanvasWrapLegend } from "./overall-in-doc-resize.js"; diff --git a/docs/demo/shared/vendor/flowstory.min.js b/docs/demo/shared/vendor/flowstory.min.js index 7160f42..6238656 100644 --- a/docs/demo/shared/vendor/flowstory.min.js +++ b/docs/demo/shared/vendor/flowstory.min.js @@ -1,3 +1,3 @@ -var N=class{constructor(){this.nodes={},this.tooltips={},this.flows={},this.activeNodes=new Set,this.badges={},this.lines=[],this.glowing=new Set,this.fading={},this.dots=[],this.running=!1,this.paused=!1,this.stepIndex=0,this.playbackSpeed=1,this.loopMode=!1,this.activeFlow=null,this.currentStepDone=0,this.stepTimer=null,this.isDark=!0,this.headers=[],this.body=[],this.cycleState=[],this.phase="",this.stepLabel="",this.highlightKey=null,this.snapshots=[]}reset(){this.activeNodes.clear(),this.badges={},this.lines=[],this.glowing.clear(),this.fading={},this.dots=[],this.running=!1,this.paused=!1,this.stepIndex=0,this.currentStepDone=0,this.stepTimer&&(clearTimeout(this.stepTimer),this.stepTimer=null),this.headers=[],this.body=[],this.cycleState=[],this.phase="",this.stepLabel="",this.highlightKey=null,this.snapshots=[]}},F=class{constructor(t,e={}){this.canvas=t,this.ctx=t.getContext("2d"),this.logicalWidth=e.logicalWidth??1250,this.logicalHeight=e.logicalHeight??1050,this.panelWidth=e.panelWidth??380,this.W=0,this.H=0,this._ox=0,this._oy=0,this._sc=1,this._animFrame=null,this.state=new N,this.onDraw=null,this._animate=this._animate.bind(this),this._onResize=this._onResize.bind(this)}tx(t){return this._ox+t*this._sc}ty(t){return this._oy+t*this._sc}ts(t){return t*this._sc}resize(){this.W=this.canvas.width=innerWidth-this.panelWidth,this.H=this.canvas.height=innerHeight,this._sc=Math.min(this.W/this.logicalWidth,this.H/this.logicalHeight)*.98,this._ox=(this.W-this.logicalWidth*this._sc)/2,this._oy=(this.H-this.logicalHeight*this._sc)*.04,this.draw()}draw(){this.ctx.clearRect(0,0,this.W,this.H),typeof this.onDraw=="function"&&this.onDraw(this.ctx,this.state,this)}_animate(){this.draw(),this._animFrame=requestAnimationFrame(this._animate)}_onResize(){this.resize()}start(){addEventListener("resize",this._onResize),this.resize(),this._animFrame||(this._animFrame=requestAnimationFrame(this._animate))}stop(){removeEventListener("resize",this._onResize),this._animFrame&&(cancelAnimationFrame(this._animFrame),this._animFrame=null)}};var j=class{drawBox(t,e,s,o){if(!s||s.type==="boundary"||s.type==="container")return;let{tx:n,ty:r,ts:l,isDark:a,colors:h,activeNodes:f,badges:p,fading:d}=o,c=n(s.x),y=r(s.y),u=l(s.w),g=l(s.h),_=f&&f.has(e),m=p&&p[e]!=null;if(t.save(),s.type==="icon"){t.font=`${l(32)}px serif`,t.textAlign="center",t.fillText("\u{1F464}",c+u/2,y+l(26)),t.font=`bold ${l(16)}px system-ui`,t.fillStyle=h.txt,t.fillText(s.label,c+u/2,y+l(46)),t.restore();return}let T=1;if(d&&d[e]){let L=Date.now()-d[e];T=Math.min(L/500,1),L>500&&delete d[e]}(_||m)&&(t.shadowColor=s.color,t.shadowBlur=l(18)*T,t.globalAlpha=.3+.7*T),t.beginPath(),t.roundRect(c,y,u,g,l(8)),t.fillStyle=_||m?h.boxA:h.box,t.fill(),t.strokeStyle=_||m?s.color:h.bdr,t.lineWidth=l(_||m?2.5:1.5),s.type==="plugin"&&t.setLineDash([l(5),l(4)]),t.stroke(),t.setLineDash([]);let x=s.fs||16;t.fillStyle=h.txt,t.textAlign="center",t.textBaseline="middle",s.soon&&!s.sublabel?(t.font=`bold ${l(x)}px system-ui`,t.fillText(s.label,c+u/2,y+g/2-l(6)),t.font=`bold ${l(9)}px system-ui`,t.fillStyle="#f0883e",t.fillText("COMING SOON",c+u/2,y+g/2+l(8))):s.sublabel?(t.font=`bold ${l(x)}px system-ui`,t.fillText(s.label,c+u/2,y+g/2-l(8)),t.font=`${l(Math.max(x-3,11))}px system-ui`,t.fillStyle=h.dim,s.soon?(t.fillText(s.sublabel,c+u/2,y+g/2+l(6)),t.font=`bold ${l(8)}px system-ui`,t.fillStyle="#f0883e",t.fillText("COMING SOON",c+u/2,y+g/2+l(16))):t.fillText(s.sublabel,c+u/2,y+g/2+l(8))):(t.font=`bold ${l(x)}px system-ui`,t.fillText(s.label,c+u/2,y+g/2)),m&&(Array.isArray(p[e])?p[e]:[p[e]]).forEach((P,$)=>{let w=c+l(8)+$*l(22),v=y+l(5),S=l(9);t.shadowBlur=0,t.globalAlpha=1,t.beginPath(),t.arc(w+S,v+S,S,0,Math.PI*2),t.fillStyle=s.color,t.fill(),t.font=`bold ${l(10)}px system-ui`,t.fillStyle=a?"#0d1117":"#fff",t.textAlign="center",t.textBaseline="middle",t.fillText(String(P),w+S,v+S)}),t.restore()}drawContainer(t,e,s,o){if(!s||s.type!=="container")return;let{tx:n,ty:r,ts:l,isDark:a,glowing:h}=o,f=h&&h.has(e);t.save(),f&&(t.shadowColor=s.color,t.shadowBlur=l(22)),t.beginPath(),t.roundRect(n(s.x),r(s.y),l(s.w),l(s.h),l(10));let p=f?"cc":"55";t.strokeStyle=s.color+p,t.lineWidth=l(f?2:1.25),t.setLineDash([l(4),l(3)]),t.stroke(),t.setLineDash([]),t.font=`bold ${l(12)}px system-ui`,t.fillStyle=s.color+(a?"aa":"99"),t.textAlign="left",t.fillText(s.label,n(s.x+12),r(s.y+18)),t.restore()}drawBoundary(t,e,s,o){if(!s||s.type!=="boundary")return;let{tx:n,ty:r,ts:l,colors:a}=o;t.save(),t.beginPath(),t.roundRect(n(s.x),r(s.y),l(s.w),l(s.h),l(14)),t.strokeStyle=a.bdr,t.lineWidth=l(1.25),t.setLineDash([l(5),l(4)]),t.stroke(),t.setLineDash([]);let h=s.labelAlign||"right";t.font=`${l(13)}px system-ui`,s.labelColor?t.fillStyle=s.labelColor:t.fillStyle=a.dim,t.textAlign=h,h==="right"?t.fillText(s.label,n(s.x+s.w-15),r(s.y+18)):t.fillText(s.label,n(s.x+12),r(s.y+18)),t.restore()}drawSections(t,e,s){if(!e||e.length===0)return;let{tx:o,ty:n,ts:r,isDark:l}=s;t.save(),e.forEach(a=>{if(t.beginPath(),t.roundRect(o(a.x),n(a.y),r(a.w),r(a.h),r(8)),t.fillStyle=l?a.color+"0a":a.color+"08",t.fill(),t.strokeStyle=l?a.color+"30":a.color+"25",t.lineWidth=r(1),t.setLineDash([r(3),r(2.5)]),t.stroke(),t.setLineDash([]),a.label){t.font=`bold ${r(10)}px system-ui`,t.fillStyle=l?a.color+"88":a.color+"99",t.textAlign="left";let h=a.labelX!=null?a.labelX:a.x+12,f=a.labelY!=null?a.labelY:a.y+12;t.fillText(a.label,o(h),n(f))}}),t.restore()}drawStackDecoration(t,e,s,o,n){if(!s||s<1)return;let{tx:r,ty:l,ts:a,isDark:h}=n,f=e.color||"#d2a8ff",p=h?f+"18":f+"12",d=h?f+"44":f+"33",c=o&&o.dx||-8,y=o&&o.dy||-5;t.save();for(let u=s;u>=1;u--)t.beginPath(),t.roundRect(r(e.x+c*u),l(e.y+y*u),a(e.w),a(e.h),a(6)),t.fillStyle=p,t.strokeStyle=d,t.lineWidth=a(1),t.fill(),t.stroke();t.restore()}drawSubBoundary(t,e,s,o,n,r,l){let{tx:a,ty:h,ts:f}=l;t.save(),t.beginPath(),t.roundRect(a(e),h(s),f(o),f(n),f(8)),t.strokeStyle=r+"44",t.lineWidth=f(1),t.setLineDash([f(3),f(2.5)]),t.stroke(),t.setLineDash([]),t.restore()}renderAll(t,e,s){let o=Object.keys(e);for(let n of o){let r=e[n];r.type==="boundary"&&this.drawBoundary(t,n,r,s)}for(let n of o){let r=e[n];r.type==="container"&&this.drawContainer(t,n,r,s)}for(let n of o){let r=e[n];r.type==="container"&&r.sections&&this.drawSections(t,r.sections,s)}for(let n of o){let r=e[n];if(r.subBoundaries)for(let l of r.subBoundaries)this.drawSubBoundary(t,l.x,l.y,l.w,l.h,l.color,s)}for(let n of o){let r=e[n];r.stackCount&&r.type!=="boundary"&&r.type!=="container"&&this.drawStackDecoration(t,r,r.stackCount,r.stackOffset||{dx:-8,dy:-5},s)}for(let n of o){let r=e[n];r.type!=="boundary"&&r.type!=="container"&&this.drawBox(t,n,r,s)}}};function D(i,t,e,s,o){let n=t.x+t.w/2,r=t.y+t.h/2,l=e-n,a=s-r;if(Math.abs(l)<1&&Math.abs(a)<1)return{x:n,y:r};let h=t.w/2,f=t.h/2,p,d;return Math.abs(l)*f>Math.abs(a)*h?(p=n+(l>0?h:-h),d=r+a*(h/Math.abs(l))):(d=r+(a>0?f:-f),p=n+l*(f/Math.abs(a))),{x:p,y:d+(o||0)}}function B(i,t,e,s){let o=e.yOff||0;if(s)return e.fromLeft?{x:t.x,y:t.y+t.h/2+o}:e.fromRight?{x:t.x+t.w,y:t.y+t.h/2+o}:e.fromBottom?{x:t.x+t.w/2+(e.fromXOff||e.xOff||0),y:t.y+t.h+o}:e.fromTop?{x:t.x+t.w/2+(e.fromXOff||0),y:t.y+o}:e.waypoints?D(i,t,e.waypoints[0].x,e.waypoints[0].y,o):{x:t.x+t.w/2,y:t.y+t.h/2+o};if(e.toLeft)return{x:t.x,y:t.y+t.h/2+o};if(e.toTop)return{x:t.x+t.w/2+(e.toXOff||e.xOff||0),y:t.y+o};if(e.toRight)return{x:t.x+t.w,y:t.y+t.h/2+o};if(e.toBottom)return{x:t.x+t.w/2+(e.toXOff||0),y:t.y+t.h+o};if(e.waypoints){let n=e.waypoints;return D(i,t,n[n.length-1].x,n[n.length-1].y,o)}return{x:t.x+t.w/2,y:t.y+t.h/2+o}}function et(i,t,e,s,o,n,r,l){let{ctx:a,tx:h,ty:f,ts:p,isDark:d}=i;a.save(),a.globalAlpha=l||1,a.beginPath(),a.moveTo(h(t),f(e)),a.lineTo(h(s),f(o)),a.strokeStyle=n,a.lineWidth=p(2.5),a.stroke();let c=Math.atan2(o-e,s-t),y=p(10);if(a.beginPath(),a.moveTo(h(s),f(o)),a.lineTo(h(s)-y*Math.cos(c-.4),f(o)-y*Math.sin(c-.4)),a.lineTo(h(s)-y*Math.cos(c+.4),f(o)-y*Math.sin(c+.4)),a.closePath(),a.fillStyle=n,a.fill(),r!=null){let u=(t+s)/2,g=(e+o)/2,_=p(12);a.beginPath(),a.arc(h(u),f(g),_,0,Math.PI*2),a.fillStyle=n,a.fill(),a.font=`bold ${p(10)}px system-ui`,a.fillStyle=d?"#0d1117":"#fff",a.textAlign="center",a.textBaseline="middle",a.fillText(String(r),h(u),f(g))}a.restore()}function st(i,t){let{ctx:e,tx:s,ty:o,ts:n,nodes:r,isDark:l}=i;t.forEach(a=>{let h=r[a.from],f=r[a.to];if(!(!h||!f))if(a.waypoints){let p=B(i,h,a,!0),d=B(i,f,a,!1),c=[p,...a.waypoints,d];e.save(),e.globalAlpha=.8,e.beginPath(),e.moveTo(s(c[0].x),o(c[0].y));for(let m=1;mMath.abs(l)*a?(f=o+(r>0?a:-a),p=n+l*(a/Math.abs(r))):(p=n+(l>0?h:-h),f=o+r*(h/Math.abs(l))),{x:f,y:p+(s||0)}}function it(i,t,e){let s=t.yOff||0;if(e)return t.fromLeft?{x:i.x,y:i.y+i.h/2+s}:t.fromRight?{x:i.x+i.w,y:i.y+i.h/2+s}:t.fromBottom?{x:i.x+i.w/2+(t.fromXOff||t.xOff||0),y:i.y+i.h+s}:t.fromTop?{x:i.x+i.w/2+(t.fromXOff||0),y:i.y+s}:t.waypoints?C(i,t.waypoints[0].x,t.waypoints[0].y,s):{x:i.x+i.w/2,y:i.y+i.h/2+s};if(t.toLeft)return{x:i.x,y:i.y+i.h/2+s};if(t.toTop)return{x:i.x+i.w/2+(t.toXOff||t.xOff||0),y:i.y+s};if(t.toRight)return{x:i.x+i.w,y:i.y+i.h/2+s};if(t.toBottom)return{x:i.x+i.w/2+(t.toXOff||0),y:i.y+i.h+s};if(t.waypoints){let o=t.waypoints;return C(i,o[o.length-1].x,o[o.length-1].y,s)}return{x:i.x+i.w/2,y:i.y+i.h/2+s}}var R=class{constructor(t,e,s,o,n,r,l){this.color=s,this.speed=o||.012,this.t=0,this.cb=n,this.alive=!0,this.ctx=l;let a=l.nodes[t],h=l.nodes[e],f=r&&r.yOff||0,p,d,c=r||{};if(p=it(a,c,!0),d=it(h,c,!1),!c.fromLeft&&!c.fromRight&&!c.fromBottom&&!c.fromTop&&!c.waypoints){let u=h.x+h.w/2,g=h.y+h.h/2;p=C(a,u,g,f)}if(!c.toLeft&&!c.toRight&&!c.toBottom&&!c.toTop&&!c.waypoints){let u=a.x+a.w/2,g=a.y+a.h/2;d=C(h,u,g,f)}r&&r.waypoints?this.pts=[p,...r.waypoints,d]:this.pts=[p,d],this.segLens=[],this.totalLen=0;for(let u=1;u0?y/this.totalLen*(o/.02):o}update(){this.t+=this.speed,this.t>=1&&(this.t=1,this.alive=!1,this.cb&&this.cb());let t=this.t*this.totalLen,e=0;for(let o=0;o=t){let n=(t-e)/this.segLens[o];this.x=this.pts[o].x+(this.pts[o+1].x-this.pts[o].x)*n,this.y=this.pts[o].y+(this.pts[o+1].y-this.pts[o].y)*n,this.segIdx=o;return}e+=this.segLens[o]}let s=this.pts[this.pts.length-1];this.x=s.x,this.y=s.y,this.segIdx=this.segLens.length-1}posAt(t){let e=Math.max(0,Math.min(1,t))*this.totalLen,s=0;for(let n=0;n=e){let r=(e-s)/this.segLens[n];return{x:this.pts[n].x+(this.pts[n+1].x-this.pts[n].x)*r,y:this.pts[n].y+(this.pts[n+1].y-this.pts[n].y)*r}}s+=this.segLens[n]}let o=this.pts[this.pts.length-1];return{x:o.x,y:o.y}}draw(t){let{tx:e,ty:s,ts:o}=this.ctx;t.save(),t.beginPath(),t.moveTo(e(this.pts[0].x),s(this.pts[0].y));for(let h=0;h=1;h--){let f=this.t-h*.06;if(f<=0)continue;let p=this.posAt(f);t.globalAlpha=.15+(1-h/4)*.4,t.beginPath(),t.arc(e(p.x),s(p.y),o(2.5),0,Math.PI*2),t.fillStyle=this.color,t.fill()}t.globalAlpha=1;let n=Math.min(this.segIdx,this.pts.length-2),r=Math.atan2(this.pts[n+1].y-this.pts[n].y,this.pts[n+1].x-this.pts[n].x);t.save(),t.translate(e(this.x),s(this.y)),t.rotate(r);let l=o(13),a=o(7);t.beginPath(),t.moveTo(l/2,0),t.lineTo(l/6,-a/2),t.lineTo(-l/2,-a/2),t.lineTo(-l/2,a/2),t.lineTo(l/6,a/2),t.closePath(),t.fillStyle=this.color,t.shadowColor=this.color,t.shadowBlur=o(16),t.fill(),t.restore(),t.restore()}};var rt={bg:"#0d1117",box:"#161b22",boxA:"#1a2332",bdr:"#30363d",txt:"#c9d1d9",dim:"#8b949e",brt:"#fff"},ht={bg:"#fff",box:"#f6f8fa",boxA:"#ddf4ff",bdr:"#d0d7de",txt:"#24292f",dim:"#656d76",brt:"#000"},H=class{constructor(t=!0){this.isDark=t,this.onToggle=null}toggle(){this.isDark=!this.isDark,typeof this.onToggle=="function"&&this.onToggle(this.isDark)}colors(){return this.isDark?{...rt}:{...ht}}};function ft(i){return{fromLeft:i.fromLeft,toLeft:i.toLeft,fromRight:i.fromRight,toRight:i.toRight,fromBottom:i.fromBottom,fromTop:i.fromTop,toTop:i.toTop,toBottom:i.toBottom,yOff:i.yOff,xOff:i.xOff,fromXOff:i.fromXOff,toXOff:i.toXOff,waypoints:i.waypoints}}function ot(i){return{from:i.f,to:i.to,color:i.c,num:i.num,fromLeft:i.fromLeft,toLeft:i.toLeft,fromRight:i.fromRight,toRight:i.toRight,fromBottom:i.fromBottom,fromTop:i.fromTop,toTop:i.toTop,toBottom:i.toBottom,yOff:i.yOff,xOff:i.xOff,fromXOff:i.fromXOff,toXOff:i.toXOff,waypoints:i.waypoints}}var W=class{constructor(t){this._state=t.state,this._engine=t.engine,this._steps=t.stepsPanel??null,this._inspector=t.inspector??null,this._createDot=t.createDot,this._onFlowComplete=t.onFlowComplete??null,this._flowOrder=t.flowOrder??null,this._listeners=[]}get isActive(){return this._state.running||this._state.paused}get _flow(){return this._state.flows[this._state.activeFlow]??[]}get _order(){return this._flowOrder??Object.keys(this._state.flows)}onChange(t){return this._listeners.push(t),()=>{this._listeners=this._listeners.filter(e=>e!==t)}}_emit(t){for(let e of this._listeners)try{e(t,this)}catch{}}run(){let t=this._state;if(t.paused){this.resume();return}if(t.running){this.pause();return}this.reset(),t.running=!0,t.paused=!1,t.stepIndex=0,t.currentStepDone=0,this._inspector&&this._inspector.init(),this._emit("play"),this._ensureAnimating(),t.stepTimer=setTimeout(()=>this.execStep(),300)}pause(){let t=this._state;t.running&&(t.paused=!0,t.running=!1,t.stepTimer&&(clearTimeout(t.stepTimer),t.stepTimer=null),this._emit("pause"))}resume(){let t=this._state;t.paused&&(t.paused=!1,t.running=!0,this._emit("resume"),this._ensureAnimating(),t.stepTimer=setTimeout(()=>this.execStep(),300/t.playbackSpeed))}execStep(){let t=this._state,e=this._flow;if(!t.running||t.paused||t.stepIndex>=e.length){if(t.stepIndex>=e.length&&(t.running=!1,t.paused=!1,this._emit("reset"),this._onFlowComplete&&this._onFlowComplete(),t.loopMode)){let n=this._order,r=n.indexOf(t.activeFlow),l=n[(r+1)%n.length];t.stepTimer=setTimeout(()=>{this.setFlow(l),this.run()},3e3/t.playbackSpeed)}return}let s=t.stepIndex,o=e[s];if(this._steps){for(let n=0;n{t.lines.push(ot(o)),o.glow&&t.glowing.add(o.glow),t.activeNodes.add(o.to),this._steps&&this._steps.markDone(s),t.currentStepDone=s+1,this._saveSnapshot(s),t.stepIndex++,t.running&&!t.paused&&(t.stepTimer=setTimeout(()=>this.execStep(),300/t.playbackSpeed))},ft(o));t.dots.push(n)}else if(o.mode==="lightup"){if(t.fading[o.target]=Date.now(),t.activeNodes.add(o.target),o.errColor){let n=t.nodes[o.target];n&&(n._origColor=n._origColor||n.color,n.color=o.errColor)}if(t.badges[o.target]!=null){let n=Array.isArray(t.badges[o.target])?t.badges[o.target]:[t.badges[o.target]];n.push(o.badge),t.badges[o.target]=n}else t.badges[o.target]=o.badge;this._steps&&this._steps.markDone(s),t.currentStepDone=s+1,this._saveSnapshot(s),t.stepIndex++,t.running&&!t.paused&&(t.stepTimer=setTimeout(()=>this.execStep(),800/t.playbackSpeed))}}cycleSpeed(){let t=[.5,1,2],e=t.indexOf(this._state.playbackSpeed);return this._state.playbackSpeed=t[(e+1)%t.length],this._emit("speed"),this._state.playbackSpeed}toggleLoop(){let t=this._state;return t.loopMode=!t.loopMode,this._emit("loop"),t.loopMode&&!t.running&&!t.paused&&this.run(),t.loopMode}setFlow(t){if(!(t in this._state.flows))throw new Error(`PlaybackController: unknown flow "${t}"`);this._state.activeFlow=t,this.reset(),this._emit("flow")}reset(){let t=this._state,e=t.nodes;for(let s of Object.keys(e))e[s]._origColor&&(e[s].color=e[s]._origColor,delete e[s]._origColor);t.running=!1,t.paused=!1,t.stepIndex=0,t.stepTimer&&(clearTimeout(t.stepTimer),t.stepTimer=null),t.dots=[],t.activeNodes.clear(),t.badges={},t.lines=[],t.glowing.clear(),t.fading={},t.currentStepDone=0,t.snapshots=[],t.headers=[],t.body=[],t.cycleState=[],t.phase="",t.stepLabel="",this._steps&&this._steps.initSteps(this._flow),this._engine.draw(),this._emit("reset")}_saveSnapshot(t){let e=this._state;e.snapshots[t]={activeNodes:new Set(e.activeNodes),badges:JSON.parse(JSON.stringify(e.badges)),lines:JSON.parse(JSON.stringify(e.lines)),glowing:new Set(e.glowing)}}_restoreSnapshot(t){let e=this._state;if(t<0){e.activeNodes.clear(),e.badges={},e.lines=[],e.glowing.clear();return}let s=e.snapshots[t];s&&(e.activeNodes=new Set(s.activeNodes),e.badges=JSON.parse(JSON.stringify(s.badges)),e.lines=JSON.parse(JSON.stringify(s.lines)),e.glowing=new Set(s.glowing))}simulateUpTo(t){let e=this._state,s=this._flow;e.activeNodes.clear(),e.badges={},e.lines=[],e.glowing.clear();for(let o=0;o<=t;o++){let n=s[o];if(n.mode==="arrow")n.f&&e.activeNodes.add(n.f),e.lines.push(ot(n)),n.glow&&e.glowing.add(n.glow),e.activeNodes.add(n.to);else if(n.mode==="lightup"){if(e.activeNodes.add(n.target),n.errColor){let r=e.nodes[n.target];r&&(r._origColor=r._origColor||r.color,r.color=n.errColor)}if(e.badges[n.target]!=null){let r=Array.isArray(e.badges[n.target])?e.badges[n.target]:[e.badges[n.target]];r.push(n.badge),e.badges[n.target]=r}else e.badges[n.target]=n.badge}this._saveSnapshot(o)}}jumpToStep(t){let e=this._state;if(e.running&&(e.running=!1,e.stepTimer&&(clearTimeout(e.stepTimer),e.stepTimer=null)),e.snapshots[t]||this.simulateUpTo(t),this._restoreSnapshot(t),e.currentStepDone=t+1,e.stepIndex=t+1,this._steps){for(let o=0;o<=t;o++)this._steps.markDone(o);this._steps.markActive(t);let s=this._flow;for(let o=t+1;o/g,">")}var X=class{constructor(t,e,s=380){this._dom=t,this._cb=e,this._panelWidth=s,this._open=!1,this._wasRunning=!1,this._highlightKey=null,this._dom.overlay.addEventListener("click",o=>{o.target===this._dom.overlay&&this.close()})}get isOpen(){return this._open}get highlightKey(){return this._highlightKey}show(t,e,s,o){if(!s)return;let n=e,r=s,{tx:l,ty:a,ts:h,isDark:f,isRunning:p,isPaused:d,setRunning:c,setPaused:y,clearStepTimer:u,requestDraw:g}=this._cb;this._wasRunning=p(),this._highlightKey=t,p()&&!d()&&(y(!0),c(!1),u(),o&&o.updatePlayBtn&&o.updatePlayBtn());let _=this._dom.overlayCard,m=r.title||r.t,T=r.description||r.d,x=r.details||r.dt,L=r.links,P=r.logo;this._dom.overlayAccent.style.background=n.color||"#58a6ff",this._dom.overlayTitle.textContent=m,this._dom.overlayTitle.style.color=n.color||"#c9d1d9",this._dom.overlayDesc.textContent=T;let $=_.querySelector("img.overlay-logo");if($&&$.remove(),P){let k=document.createElement("img");k.className="overlay-logo",k.src=P,k.style.cssText="height:24px;margin-bottom:8px",this._dom.overlayTitle.parentNode.insertBefore(k,this._dom.overlayTitle)}let w="";x&&x.forEach(([k,U])=>{w+=`
${M(k)}${M(U)}
`}),L&&(w+='
',L.forEach(([k,U])=>{w+=`${M(k)} ↗`}),w+="
"),this._dom.overlayDetails.innerHTML=w;let v=l(n.x),S=a(n.y),A=h(n.w),lt=h(n.h),Z=innerWidth-this._panelWidth,E,tt;v+A+4400?E=v-440:E=Math.max(10,(Z-420)/2),tt=Math.max(10,Math.min(S-40,innerHeight-500)),_.style.left=E+"px",_.style.top=tt+"px",this._dom.overlay.style.display="block";let b=this._dom.highlightBox;b.style.left=v+"px",b.style.top=S+"px",b.style.width=A+"px",b.style.height=lt+"px",b.style.border=`3px solid ${n.color||"#58a6ff"}`,b.style.boxShadow=`0 0 30px ${n.color||"#58a6ff"}88, inset 0 0 20px ${n.color||"#58a6ff"}22`,b.style.background=f()?"#161b22":"#f6f8fa",b.style.display="flex",b.style.flexDirection="column",b.style.alignItems="center",b.style.justifyContent="center",b.style.fontFamily="system-ui",b.style.color=f()?"#c9d1d9":"#24292f",b.innerHTML=`
${M(n.label)}
`+(n.sublabel?`
${M(n.sublabel)}
`:""),b.style.transform="none",this._open=!0,g()}close(){this._dom.overlay.style.display="none";let t=this._dom.overlayCard.querySelector("img.overlay-logo");t&&t.remove(),this._dom.highlightBox.style.display="none",this._highlightKey=null,this._open=!1,this._cb.requestDraw(),this._wasRunning&&(this._cb.setPaused(!1),this._cb.setRunning(!0),this._cb.resumeAnimation())}};function pt(i){return i.replace(/&/g,"&").replace(//g,">")}var I=class{constructor(t){this._container=t,this._steps=[],this.onStepClick=null}init(t){this._steps=t,this._container.innerHTML="",t.forEach((e,s)=>{let o=document.createElement("div");o.className="fs-step pending",o.id="step-"+s,o.innerHTML=`${pt(e.t)}`,o.addEventListener("click",()=>{this.onStepClick&&this.onStepClick(s)}),this._container.appendChild(o)})}updateStep(t,e){let s=document.getElementById("step-"+t);s&&(s.className="fs-step "+e)}setActiveStep(t){this._steps.forEach((e,s)=>{s{this.updateStep(s,s<=t?"done":"pending")})}scrollToStep(t){let e=document.getElementById("step-"+t);e&&e.scrollIntoView({block:"nearest",behavior:"smooth"})}reset(){this._steps.forEach((t,e)=>{this.updateStep(e,"pending")})}get length(){return this._steps.length}};function z(i){return i.replace(/&/g,"&").replace(//g,">")}function O(i){return i.map(t=>({...t}))}var J=class{constructor(t,e){this._titleEl=t,this._contentEl=e,this.headers=[],this.body=[],this.cycleState=[],this.stepLabel="",this._phase="request"}init(t){this.headers=(t.headers||[]).map(e=>({v:e.v,s:e.s||"keep",id:e.id})),this.body=(t.body||[]).map(e=>({v:e.v,s:e.s||"keep",id:e.id})),this.cycleState=(t.cycleState||[]).map(e=>({v:e.v,s:e.s||"keep",id:e.id})),this.stepLabel=t.label||"",this.setPhase(t.phase||"request"),this.render()}setPhase(t){this._phase=t,t==="request"?(this._titleEl.textContent="Request",this._titleEl.style.color="#58a6ff"):t==="response"?(this._titleEl.textContent="Response",this._titleEl.style.color="#3fb950"):t==="error"&&(this._titleEl.textContent="Error Response",this._titleEl.style.color="#f85149")}applyMutation(t){if(t.resetStyles!==!1&&this.resetStyles(),t.phase&&this.setPhase(t.phase),t.label!==void 0&&(this.stepLabel=t.label),t.replaceHeaders&&(this.headers=t.replaceHeaders.map(e=>({v:e.v,s:e.s||"keep",id:e.id}))),t.replaceBody&&(this.body=t.replaceBody.map(e=>({v:e.v,s:e.s||"keep",id:e.id}))),t.replaceCycleState&&(this.cycleState=t.replaceCycleState.map(e=>({v:e.v,s:e.s||"keep",id:e.id}))),t.cycleState&&!t.replaceCycleState&&(this.cycleState=t.cycleState.map(e=>({v:e.v,s:e.s||"keep",id:e.id}))),t.clearCycleState&&(this.cycleState=[]),t.actions)for(let e of t.actions)e.action==="add"?this._addLine(e.target||"headers",{v:e.value,s:e.style||"add",id:e.id}):e.action==="remove"?this._removeLine(e.id):e.id&&e.style&&this._setStyle(e.id,e.style);this.render()}resetStyles(){let t=e=>{e.forEach(s=>{s.s!=="del"&&s.s!=="err"&&(s.s="keep")})};t(this.headers),t(this.body),t(this.cycleState)}_setStyle(t,e){let s=this._findLine(t);s&&(s.s=e)}_findLine(t){return this.headers.find(e=>e.id===t)||this.body.find(e=>e.id===t)||this.cycleState.find(e=>e.id===t)}_addLine(t,e){let s=this[t];s&&s.push(e)}_removeLine(t){for(let e of[this.headers,this.body,this.cycleState]){let s=e.findIndex(o=>o.id===t);if(s!==-1){e.splice(s,1);return}}}render(){let t="";this.stepLabel&&(t+=`
${z(this.stepLabel)}
`),t+='
Headers:
',this.headers.forEach(e=>{t+=`
${z(e.v)}
`}),t+='
Body:
',this.body.forEach(e=>{t+=`
${z(e.v)}
`}),this.cycleState.length>0&&(t+='
CycleState:
',this.cycleState.forEach(e=>{t+=`
${z(e.v)}
`})),this._contentEl.innerHTML=t}setLabel(t){this.stepLabel=t}snapshot(){return{headers:O(this.headers),body:O(this.body),cycleState:O(this.cycleState),stepLabel:this.stepLabel,phase:this._phase}}restore(t){this.headers=O(t.headers),this.body=O(t.body),this.cycleState=O(t.cycleState),this.stepLabel=t.stepLabel,this.setPhase(t.phase),this.render()}static highlight(t){return{actions:[{id:t,style:"highlight"}]}}static del(t){return{actions:[{id:t,style:"del"}]}}static addHeader(t,e){return{actions:[{action:"add",target:"headers",value:t,id:e}]}}static addBody(t,e){return{actions:[{action:"add",target:"body",value:t,id:e}]}}};function Q(i){let t=[];if(i==null||typeof i!="object"||Array.isArray(i))return{valid:!1,errors:["Diagram must be a non-null object"]};ct(i.meta,t);let e=dt(i.nodes,t);return yt(i.flows,e,t),i.tooltips!==void 0&>(i.tooltips,e,t),i.inspector!==void 0&&mt(i.inspector,t),i.canvas!==void 0&&_t(i.canvas,t),i.theme!==void 0&&bt(i.theme,t),t.length===0?{valid:!0}:{valid:!1,errors:t}}function ct(i,t){if(i==null||typeof i!="object"||Array.isArray(i)){t.push("meta: must be an object");return}(typeof i.title!="string"||i.title.length===0)&&t.push("meta.title: must be a non-empty string")}function dt(i,t){let e=new Set;if(i==null||typeof i!="object"||Array.isArray(i))return t.push("nodes: must be a non-empty object"),e;let s=Object.keys(i);if(s.length===0)return t.push("nodes: must contain at least one node"),e;for(let o of s){e.add(o);let n=i[o],r=`nodes.${o}`;if(n==null||typeof n!="object"||Array.isArray(n)){t.push(`${r}: must be an object`);continue}q(n,"x",r,t),q(n,"y",r,t),q(n,"w",r,t),q(n,"h",r,t),n.type!=="boundary"&&n.label!==void 0&&typeof n.label!="string"&&t.push(`${r}.label: must be a string`)}return e}function yt(i,t,e){if(i==null||typeof i!="object"||Array.isArray(i)){e.push("flows: must be a non-empty object");return}let s=Object.keys(i);if(s.length===0){e.push("flows: must contain at least one flow");return}for(let o of s){let n=i[o],r=`flows.${o}`;if(n==null||typeof n!="object"||Array.isArray(n)){e.push(`${r}: must be an object with label and steps`);continue}if((typeof n.label!="string"||n.label.length===0)&&e.push(`${r}.label: must be a non-empty string`),!Array.isArray(n.steps)){e.push(`${r}.steps: must be an array`);continue}for(let l=0;l0&&s.size>0&&(s.has(i)||o.push(`${e}.${t}: references unknown node "${i}"`))}var G={left:{from:"fromLeft",to:"toLeft"},right:{from:"fromRight",to:"toRight"},top:{from:"fromTop",to:"toTop"},bottom:{from:"fromBottom",to:"toBottom"}};async function nt(i){let t;if(typeof i=="string"){let s=await fetch(i);if(!s.ok)throw new Error(`Failed to fetch diagram from "${i}": ${s.status} ${s.statusText}`);try{t=await s.json()}catch(o){throw new Error(`Failed to parse diagram JSON from "${i}": ${o.message}`)}}else if(i!=null&&typeof i=="object"&&!Array.isArray(i))t=JSON.parse(JSON.stringify(i));else throw new Error("loadDiagram() expects a URL string or a diagram object");let e=Q(t);if(!e.valid)throw new Error(`Invalid diagram: +var N=class{constructor(){this.nodes={},this.tooltips={},this.flows={},this.activeNodes=new Set,this.badges={},this.lines=[],this.glowing=new Set,this.fading={},this.dots=[],this.running=!1,this.paused=!1,this.stepIndex=0,this.playbackSpeed=1,this.loopMode=!1,this.activeFlow=null,this.currentStepDone=0,this.stepTimer=null,this.isDark=!0,this.headers=[],this.body=[],this.cycleState=[],this.phase="",this.stepLabel="",this.highlightKey=null,this.snapshots=[]}reset(){this.activeNodes.clear(),this.badges={},this.lines=[],this.glowing.clear(),this.fading={},this.dots=[],this.running=!1,this.paused=!1,this.stepIndex=0,this.currentStepDone=0,this.stepTimer&&(clearTimeout(this.stepTimer),this.stepTimer=null),this.headers=[],this.body=[],this.cycleState=[],this.phase="",this.stepLabel="",this.highlightKey=null,this.snapshots=[]}},F=class{constructor(t,e={}){this.canvas=t,this.ctx=t.getContext("2d"),this.logicalWidth=e.logicalWidth??1250,this.logicalHeight=e.logicalHeight??1050,this.panelWidth=e.panelWidth??380,this.W=0,this.H=0,this._ox=0,this._oy=0,this._sc=1,this._animFrame=null,this.state=new N,this.onDraw=null,this._animate=this._animate.bind(this),this._onResize=this._onResize.bind(this)}tx(t){return this._ox+t*this._sc}ty(t){return this._oy+t*this._sc}ts(t){return t*this._sc}resize(){this.W=this.canvas.width=innerWidth-this.panelWidth,this.H=this.canvas.height=innerHeight,this._sc=Math.min(this.W/this.logicalWidth,this.H/this.logicalHeight)*.98,this._ox=(this.W-this.logicalWidth*this._sc)/2,this._oy=(this.H-this.logicalHeight*this._sc)*.04,this.draw()}draw(){this.ctx.clearRect(0,0,this.W,this.H),typeof this.onDraw=="function"&&this.onDraw(this.ctx,this.state,this)}_animate(){this.draw(),this._animFrame=requestAnimationFrame(this._animate)}_onResize(){this.resize()}start(){addEventListener("resize",this._onResize),this.resize(),this._animFrame||(this._animFrame=requestAnimationFrame(this._animate))}stop(){removeEventListener("resize",this._onResize),this._animFrame&&(cancelAnimationFrame(this._animFrame),this._animFrame=null)}};var j=class{drawBox(t,e,s,o){if(!s||s.type==="boundary"||s.type==="container")return;let{tx:n,ty:r,ts:l,isDark:a,colors:h,activeNodes:f,badges:p,fading:d}=o,c=n(s.x),y=r(s.y),u=l(s.w),g=l(s.h),_=f&&f.has(e),m=p&&p[e]!=null;if(t.save(),s.type==="icon"){t.font=`${l(32)}px serif`,t.textAlign="center",t.fillText("\u{1F464}",c+u/2,y+l(26)),t.font=`bold ${l(16)}px system-ui`,t.fillStyle=h.txt,t.fillText(s.label,c+u/2,y+l(46)),t.restore();return}let T=1;if(d&&d[e]){let L=Date.now()-d[e];T=Math.min(L/500,1),L>500&&delete d[e]}(_||m)&&(t.shadowColor=s.color,t.shadowBlur=l(18)*T,t.globalAlpha=.3+.7*T),t.beginPath(),t.roundRect(c,y,u,g,l(8)),t.fillStyle=_||m?h.boxA:h.box,t.fill(),t.strokeStyle=_||m?s.color:h.bdr,t.lineWidth=l(_||m?2.5:1.5),s.type==="plugin"&&t.setLineDash([l(5),l(4)]),t.stroke(),t.setLineDash([]);let x=s.fs||16;t.fillStyle=h.txt,t.textAlign="center",t.textBaseline="middle",s.soon&&!s.sublabel?(t.font=`bold ${l(x)}px system-ui`,t.fillText(s.label,c+u/2,y+g/2-l(6)),t.font=`bold ${l(9)}px system-ui`,t.fillStyle="#f0883e",t.fillText("COMING SOON",c+u/2,y+g/2+l(8))):s.sublabel?(t.font=`bold ${l(x)}px system-ui`,t.fillText(s.label,c+u/2,y+g/2-l(8)),t.font=`${l(Math.max(x-3,11))}px system-ui`,t.fillStyle=h.dim,s.soon?(t.fillText(s.sublabel,c+u/2,y+g/2+l(6)),t.font=`bold ${l(8)}px system-ui`,t.fillStyle="#f0883e",t.fillText("COMING SOON",c+u/2,y+g/2+l(16))):t.fillText(s.sublabel,c+u/2,y+g/2+l(8))):(t.font=`bold ${l(x)}px system-ui`,t.fillText(s.label,c+u/2,y+g/2)),m&&(Array.isArray(p[e])?p[e]:[p[e]]).forEach((P,$)=>{let w=c+l(8)+$*l(22),v=y+l(5),S=l(9);t.shadowBlur=0,t.globalAlpha=1,t.beginPath(),t.arc(w+S,v+S,S,0,Math.PI*2),t.fillStyle=s.color,t.fill(),t.font=`bold ${l(10)}px system-ui`,t.fillStyle=a?"#0d1117":"#fff",t.textAlign="center",t.textBaseline="middle",t.fillText(String(P),w+S,v+S)}),t.restore()}drawContainer(t,e,s,o){if(!s||s.type!=="container")return;let{tx:n,ty:r,ts:l,isDark:a,glowing:h}=o,f=h&&h.has(e);t.save(),f&&(t.shadowColor=s.color,t.shadowBlur=l(22)),t.beginPath(),t.roundRect(n(s.x),r(s.y),l(s.w),l(s.h),l(10));let p=f?"cc":"55";t.strokeStyle=s.color+p,t.lineWidth=l(f?2:1.25),t.setLineDash([l(4),l(3)]),t.stroke(),t.setLineDash([]),t.font=`bold ${l(12)}px system-ui`,t.fillStyle=s.color+(a?"aa":"99"),t.textAlign="left",t.fillText(s.label,n(s.x+12),r(s.y+18)),t.restore()}drawBoundary(t,e,s,o){if(!s||s.type!=="boundary")return;let{tx:n,ty:r,ts:l,colors:a}=o;t.save(),t.beginPath(),t.roundRect(n(s.x),r(s.y),l(s.w),l(s.h),l(14)),t.strokeStyle=a.bdr,t.lineWidth=l(1.25),t.setLineDash([l(5),l(4)]),t.stroke(),t.setLineDash([]);let h=s.labelAlign||"right";t.font=`${l(13)}px system-ui`,s.labelColor?t.fillStyle=s.labelColor:t.fillStyle=a.dim,t.textAlign=h,h==="right"?t.fillText(s.label,n(s.x+s.w-15),r(s.y+18)):t.fillText(s.label,n(s.x+12),r(s.y+18)),t.restore()}drawSections(t,e,s){if(!e||e.length===0)return;let{tx:o,ty:n,ts:r,isDark:l}=s;t.save(),e.forEach(a=>{if(t.beginPath(),t.roundRect(o(a.x),n(a.y),r(a.w),r(a.h),r(8)),t.fillStyle=l?a.color+"0a":a.color+"08",t.fill(),t.strokeStyle=l?a.color+"30":a.color+"25",t.lineWidth=r(1),t.setLineDash([r(3),r(2.5)]),t.stroke(),t.setLineDash([]),a.label){t.font=`bold ${r(10)}px system-ui`,t.fillStyle=l?a.color+"88":a.color+"99",t.textAlign="left";let h=a.labelX!=null?a.labelX:a.x+12,f=a.labelY!=null?a.labelY:a.y+12;t.fillText(a.label,o(h),n(f))}}),t.restore()}drawStackDecoration(t,e,s,o,n){if(!s||s<1)return;let{tx:r,ty:l,ts:a,isDark:h}=n,f=e.color||"#d2a8ff",p=h?f+"18":f+"12",d=h?f+"44":f+"33",c=o&&o.dx||-8,y=o&&o.dy||-5;t.save();for(let u=s;u>=1;u--)t.beginPath(),t.roundRect(r(e.x+c*u),l(e.y+y*u),a(e.w),a(e.h),a(6)),t.fillStyle=p,t.strokeStyle=d,t.lineWidth=a(1),t.fill(),t.stroke();t.restore()}drawSubBoundary(t,e,s,o,n,r,l){let{tx:a,ty:h,ts:f}=l;t.save(),t.beginPath(),t.roundRect(a(e),h(s),f(o),f(n),f(8)),t.strokeStyle=r+"44",t.lineWidth=f(1),t.setLineDash([f(3),f(2.5)]),t.stroke(),t.setLineDash([]),t.restore()}renderAll(t,e,s){let o=Object.keys(e);for(let n of o){let r=e[n];r.type==="boundary"&&this.drawBoundary(t,n,r,s)}for(let n of o){let r=e[n];r.type==="container"&&this.drawContainer(t,n,r,s)}for(let n of o){let r=e[n];r.type==="container"&&r.sections&&this.drawSections(t,r.sections,s)}for(let n of o){let r=e[n];if(r.subBoundaries)for(let l of r.subBoundaries)this.drawSubBoundary(t,l.x,l.y,l.w,l.h,l.color,s)}for(let n of o){let r=e[n];r.stackCount&&r.type!=="boundary"&&r.type!=="container"&&this.drawStackDecoration(t,r,r.stackCount,r.stackOffset||{dx:-8,dy:-5},s)}for(let n of o){let r=e[n];r.type!=="boundary"&&r.type!=="container"&&this.drawBox(t,n,r,s)}}};function D(i,t,e,s,o){let n=t.x+t.w/2,r=t.y+t.h/2,l=e-n,a=s-r;if(Math.abs(l)<1&&Math.abs(a)<1)return{x:n,y:r};let h=t.w/2,f=t.h/2,p,d;return Math.abs(l)*f>Math.abs(a)*h?(p=n+(l>0?h:-h),d=r+a*(h/Math.abs(l))):(d=r+(a>0?f:-f),p=n+l*(f/Math.abs(a))),{x:p,y:d+(o||0)}}function B(i,t,e,s){let o=e.yOff||0;if(s)return e.fromLeft?{x:t.x,y:t.y+t.h/2+o}:e.fromRight?{x:t.x+t.w,y:t.y+t.h/2+o}:e.fromBottom?{x:t.x+t.w/2+(e.fromXOff||e.xOff||0),y:t.y+t.h+o}:e.fromTop?{x:t.x+t.w/2+(e.fromXOff||0),y:t.y+o}:e.waypoints?D(i,t,e.waypoints[0].x,e.waypoints[0].y,o):{x:t.x+t.w/2,y:t.y+t.h/2+o};if(e.toLeft)return{x:t.x,y:t.y+t.h/2+o};if(e.toTop)return{x:t.x+t.w/2+(e.toXOff||e.xOff||0),y:t.y+o};if(e.toRight)return{x:t.x+t.w,y:t.y+t.h/2+o};if(e.toBottom)return{x:t.x+t.w/2+(e.toXOff||0),y:t.y+t.h+o};if(e.waypoints){let n=e.waypoints;return D(i,t,n[n.length-1].x,n[n.length-1].y,o)}return{x:t.x+t.w/2,y:t.y+t.h/2+o}}function et(i,t,e,s,o,n,r,l){let{ctx:a,tx:h,ty:f,ts:p,isDark:d}=i;a.save(),a.globalAlpha=l||1,a.beginPath(),a.moveTo(h(t),f(e)),a.lineTo(h(s),f(o)),a.strokeStyle=n,a.lineWidth=p(2.5),a.stroke();let c=Math.atan2(o-e,s-t),y=p(10);if(a.beginPath(),a.moveTo(h(s),f(o)),a.lineTo(h(s)-y*Math.cos(c-.4),f(o)-y*Math.sin(c-.4)),a.lineTo(h(s)-y*Math.cos(c+.4),f(o)-y*Math.sin(c+.4)),a.closePath(),a.fillStyle=n,a.fill(),r){let u=(t+s)/2,g=(e+o)/2,_=p(12);a.beginPath(),a.arc(h(u),f(g),_,0,Math.PI*2),a.fillStyle=n,a.fill(),a.font=`bold ${p(10)}px system-ui`,a.fillStyle=d?"#0d1117":"#fff",a.textAlign="center",a.textBaseline="middle",a.fillText(String(r),h(u),f(g))}a.restore()}function st(i,t){let{ctx:e,tx:s,ty:o,ts:n,nodes:r,isDark:l}=i;t.forEach(a=>{let h=r[a.from],f=r[a.to];if(!(!h||!f))if(a.waypoints){let p=B(i,h,a,!0),d=B(i,f,a,!1),c=[p,...a.waypoints,d];e.save(),e.globalAlpha=.8,e.beginPath(),e.moveTo(s(c[0].x),o(c[0].y));for(let m=1;mMath.abs(l)*a?(f=o+(r>0?a:-a),p=n+l*(a/Math.abs(r))):(p=n+(l>0?h:-h),f=o+r*(h/Math.abs(l))),{x:f,y:p+(s||0)}}function it(i,t,e){let s=t.yOff||0;if(e)return t.fromLeft?{x:i.x,y:i.y+i.h/2+s}:t.fromRight?{x:i.x+i.w,y:i.y+i.h/2+s}:t.fromBottom?{x:i.x+i.w/2+(t.fromXOff||t.xOff||0),y:i.y+i.h+s}:t.fromTop?{x:i.x+i.w/2+(t.fromXOff||0),y:i.y+s}:t.waypoints?C(i,t.waypoints[0].x,t.waypoints[0].y,s):{x:i.x+i.w/2,y:i.y+i.h/2+s};if(t.toLeft)return{x:i.x,y:i.y+i.h/2+s};if(t.toTop)return{x:i.x+i.w/2+(t.toXOff||t.xOff||0),y:i.y+s};if(t.toRight)return{x:i.x+i.w,y:i.y+i.h/2+s};if(t.toBottom)return{x:i.x+i.w/2+(t.toXOff||0),y:i.y+i.h+s};if(t.waypoints){let o=t.waypoints;return C(i,o[o.length-1].x,o[o.length-1].y,s)}return{x:i.x+i.w/2,y:i.y+i.h/2+s}}var R=class{constructor(t,e,s,o,n,r,l){this.color=s,this.speed=o||.012,this.t=0,this.cb=n,this.alive=!0,this.ctx=l;let a=l.nodes[t],h=l.nodes[e],f=r&&r.yOff||0,p,d,c=r||{};if(p=it(a,c,!0),d=it(h,c,!1),!c.fromLeft&&!c.fromRight&&!c.fromBottom&&!c.fromTop&&!c.waypoints){let u=h.x+h.w/2,g=h.y+h.h/2;p=C(a,u,g,f)}if(!c.toLeft&&!c.toRight&&!c.toBottom&&!c.toTop&&!c.waypoints){let u=a.x+a.w/2,g=a.y+a.h/2;d=C(h,u,g,f)}r&&r.waypoints?this.pts=[p,...r.waypoints,d]:this.pts=[p,d],this.segLens=[],this.totalLen=0;for(let u=1;u0?y/this.totalLen*(o/.02):o}update(){this.t+=this.speed,this.t>=1&&(this.t=1,this.alive=!1,this.cb&&this.cb());let t=this.t*this.totalLen,e=0;for(let o=0;o=t){let n=(t-e)/this.segLens[o];this.x=this.pts[o].x+(this.pts[o+1].x-this.pts[o].x)*n,this.y=this.pts[o].y+(this.pts[o+1].y-this.pts[o].y)*n,this.segIdx=o;return}e+=this.segLens[o]}let s=this.pts[this.pts.length-1];this.x=s.x,this.y=s.y,this.segIdx=this.segLens.length-1}posAt(t){let e=Math.max(0,Math.min(1,t))*this.totalLen,s=0;for(let n=0;n=e){let r=(e-s)/this.segLens[n];return{x:this.pts[n].x+(this.pts[n+1].x-this.pts[n].x)*r,y:this.pts[n].y+(this.pts[n+1].y-this.pts[n].y)*r}}s+=this.segLens[n]}let o=this.pts[this.pts.length-1];return{x:o.x,y:o.y}}draw(t){let{tx:e,ty:s,ts:o}=this.ctx;t.save(),t.beginPath(),t.moveTo(e(this.pts[0].x),s(this.pts[0].y));for(let h=0;h=1;h--){let f=this.t-h*.06;if(f<=0)continue;let p=this.posAt(f);t.globalAlpha=.15+(1-h/4)*.4,t.beginPath(),t.arc(e(p.x),s(p.y),o(2.5),0,Math.PI*2),t.fillStyle=this.color,t.fill()}t.globalAlpha=1;let n=Math.min(this.segIdx,this.pts.length-2),r=Math.atan2(this.pts[n+1].y-this.pts[n].y,this.pts[n+1].x-this.pts[n].x);t.save(),t.translate(e(this.x),s(this.y)),t.rotate(r);let l=o(13),a=o(7);t.beginPath(),t.moveTo(l/2,0),t.lineTo(l/6,-a/2),t.lineTo(-l/2,-a/2),t.lineTo(-l/2,a/2),t.lineTo(l/6,a/2),t.closePath(),t.fillStyle=this.color,t.shadowColor=this.color,t.shadowBlur=o(16),t.fill(),t.restore(),t.restore()}};var rt={bg:"#0d1117",box:"#161b22",boxA:"#1a2332",bdr:"#30363d",txt:"#c9d1d9",dim:"#8b949e",brt:"#fff"},ht={bg:"#fff",box:"#f6f8fa",boxA:"#ddf4ff",bdr:"#d0d7de",txt:"#24292f",dim:"#656d76",brt:"#000"},H=class{constructor(t=!0){this.isDark=t,this.onToggle=null}toggle(){this.isDark=!this.isDark,typeof this.onToggle=="function"&&this.onToggle(this.isDark)}colors(){return this.isDark?{...rt}:{...ht}}};function ft(i){return{fromLeft:i.fromLeft,toLeft:i.toLeft,fromRight:i.fromRight,toRight:i.toRight,fromBottom:i.fromBottom,fromTop:i.fromTop,toTop:i.toTop,toBottom:i.toBottom,yOff:i.yOff,xOff:i.xOff,fromXOff:i.fromXOff,toXOff:i.toXOff,waypoints:i.waypoints}}function ot(i){return{from:i.f,to:i.to,color:i.c,num:i.num,fromLeft:i.fromLeft,toLeft:i.toLeft,fromRight:i.fromRight,toRight:i.toRight,fromBottom:i.fromBottom,fromTop:i.fromTop,toTop:i.toTop,toBottom:i.toBottom,yOff:i.yOff,xOff:i.xOff,fromXOff:i.fromXOff,toXOff:i.toXOff,waypoints:i.waypoints}}var W=class{constructor(t){this._state=t.state,this._engine=t.engine,this._steps=t.stepsPanel??null,this._inspector=t.inspector??null,this._createDot=t.createDot,this._onFlowComplete=t.onFlowComplete??null,this._flowOrder=t.flowOrder??null,this._listeners=[]}get isActive(){return this._state.running||this._state.paused}get _flow(){return this._state.flows[this._state.activeFlow]??[]}get _order(){return this._flowOrder??Object.keys(this._state.flows)}onChange(t){return this._listeners.push(t),()=>{this._listeners=this._listeners.filter(e=>e!==t)}}_emit(t){for(let e of this._listeners)try{e(t,this)}catch{}}run(){let t=this._state;if(t.paused){this.resume();return}if(t.running){this.pause();return}this.reset(),t.running=!0,t.paused=!1,t.stepIndex=0,t.currentStepDone=0,this._inspector&&this._inspector.init(),this._emit("play"),this._ensureAnimating(),t.stepTimer=setTimeout(()=>this.execStep(),300)}pause(){let t=this._state;t.running&&(t.paused=!0,t.running=!1,t.stepTimer&&(clearTimeout(t.stepTimer),t.stepTimer=null),this._emit("pause"))}resume(){let t=this._state;t.paused&&(t.paused=!1,t.running=!0,this._emit("resume"),this._ensureAnimating(),t.stepTimer=setTimeout(()=>this.execStep(),300/t.playbackSpeed))}execStep(){let t=this._state,e=this._flow;if(!t.running||t.paused||t.stepIndex>=e.length){if(t.stepIndex>=e.length&&(t.running=!1,t.paused=!1,this._emit("reset"),this._onFlowComplete&&this._onFlowComplete(),t.loopMode)){let n=this._order,r=n.indexOf(t.activeFlow),l=n[(r+1)%n.length];t.stepTimer=setTimeout(()=>{this.setFlow(l),this.run()},3e3/t.playbackSpeed)}return}let s=t.stepIndex,o=e[s];if(this._steps){for(let n=0;n{t.lines.push(ot(o)),o.glow&&t.glowing.add(o.glow),t.activeNodes.add(o.to),this._steps&&this._steps.markDone(s),t.currentStepDone=s+1,this._saveSnapshot(s),t.stepIndex++,t.running&&!t.paused&&(t.stepTimer=setTimeout(()=>this.execStep(),300/t.playbackSpeed))},ft(o));t.dots.push(n)}else if(o.mode==="lightup"){if(t.fading[o.target]=Date.now(),t.activeNodes.add(o.target),o.errColor){let n=t.nodes[o.target];n&&(n._origColor=n._origColor||n.color,n.color=o.errColor)}if(t.badges[o.target]!=null){let n=Array.isArray(t.badges[o.target])?t.badges[o.target]:[t.badges[o.target]];n.push(o.badge),t.badges[o.target]=n}else t.badges[o.target]=o.badge;this._steps&&this._steps.markDone(s),t.currentStepDone=s+1,this._saveSnapshot(s),t.stepIndex++,t.running&&!t.paused&&(t.stepTimer=setTimeout(()=>this.execStep(),800/t.playbackSpeed))}}cycleSpeed(){let t=[.5,1,2],e=t.indexOf(this._state.playbackSpeed);return this._state.playbackSpeed=t[(e+1)%t.length],this._emit("speed"),this._state.playbackSpeed}toggleLoop(){let t=this._state;return t.loopMode=!t.loopMode,this._emit("loop"),t.loopMode&&!t.running&&!t.paused&&this.run(),t.loopMode}setFlow(t){if(!(t in this._state.flows))throw new Error(`PlaybackController: unknown flow "${t}"`);this._state.activeFlow=t,this.reset(),this._emit("flow")}reset(){let t=this._state,e=t.nodes;for(let s of Object.keys(e))e[s]._origColor&&(e[s].color=e[s]._origColor,delete e[s]._origColor);t.running=!1,t.paused=!1,t.stepIndex=0,t.stepTimer&&(clearTimeout(t.stepTimer),t.stepTimer=null),t.dots=[],t.activeNodes.clear(),t.badges={},t.lines=[],t.glowing.clear(),t.fading={},t.currentStepDone=0,t.snapshots=[],t.headers=[],t.body=[],t.cycleState=[],t.phase="",t.stepLabel="",this._steps&&this._steps.initSteps(this._flow),this._engine.draw(),this._emit("reset")}_saveSnapshot(t){let e=this._state;e.snapshots[t]={activeNodes:new Set(e.activeNodes),badges:JSON.parse(JSON.stringify(e.badges)),lines:JSON.parse(JSON.stringify(e.lines)),glowing:new Set(e.glowing)}}_restoreSnapshot(t){let e=this._state;if(t<0){e.activeNodes.clear(),e.badges={},e.lines=[],e.glowing.clear();return}let s=e.snapshots[t];s&&(e.activeNodes=new Set(s.activeNodes),e.badges=JSON.parse(JSON.stringify(s.badges)),e.lines=JSON.parse(JSON.stringify(s.lines)),e.glowing=new Set(s.glowing))}simulateUpTo(t){let e=this._state,s=this._flow;e.activeNodes.clear(),e.badges={},e.lines=[],e.glowing.clear();for(let o=0;o<=t;o++){let n=s[o];if(n.mode==="arrow")n.f&&e.activeNodes.add(n.f),e.lines.push(ot(n)),n.glow&&e.glowing.add(n.glow),e.activeNodes.add(n.to);else if(n.mode==="lightup"){if(e.activeNodes.add(n.target),n.errColor){let r=e.nodes[n.target];r&&(r._origColor=r._origColor||r.color,r.color=n.errColor)}if(e.badges[n.target]!=null){let r=Array.isArray(e.badges[n.target])?e.badges[n.target]:[e.badges[n.target]];r.push(n.badge),e.badges[n.target]=r}else e.badges[n.target]=n.badge}this._saveSnapshot(o)}}jumpToStep(t){let e=this._state;if(e.running&&(e.running=!1,e.stepTimer&&(clearTimeout(e.stepTimer),e.stepTimer=null)),e.snapshots[t]||this.simulateUpTo(t),this._restoreSnapshot(t),e.currentStepDone=t+1,e.stepIndex=t+1,this._steps){for(let o=0;o<=t;o++)this._steps.markDone(o);this._steps.markActive(t);let s=this._flow;for(let o=t+1;o/g,">")}var X=class{constructor(t,e,s=380){this._dom=t,this._cb=e,this._panelWidth=s,this._open=!1,this._wasRunning=!1,this._highlightKey=null,this._dom.overlay.addEventListener("click",o=>{o.target===this._dom.overlay&&this.close()})}get isOpen(){return this._open}get highlightKey(){return this._highlightKey}show(t,e,s,o){if(!s)return;let n=e,r=s,{tx:l,ty:a,ts:h,isDark:f,isRunning:p,isPaused:d,setRunning:c,setPaused:y,clearStepTimer:u,requestDraw:g}=this._cb;this._wasRunning=p(),this._highlightKey=t,p()&&!d()&&(y(!0),c(!1),u(),o&&o.updatePlayBtn&&o.updatePlayBtn());let _=this._dom.overlayCard,m=r.title||r.t,T=r.description||r.d,x=r.details||r.dt,L=r.links,P=r.logo;this._dom.overlayAccent.style.background=n.color||"#58a6ff",this._dom.overlayTitle.textContent=m,this._dom.overlayTitle.style.color=n.color||"#c9d1d9",this._dom.overlayDesc.textContent=T;let $=_.querySelector("img.overlay-logo");if($&&$.remove(),P){let k=document.createElement("img");k.className="overlay-logo",k.src=P,k.style.cssText="height:24px;margin-bottom:8px",this._dom.overlayTitle.parentNode.insertBefore(k,this._dom.overlayTitle)}let w="";x&&x.forEach(([k,U])=>{w+=`
${M(k)}${M(U)}
`}),L&&(w+='
',L.forEach(([k,U])=>{w+=`${M(k)} ↗`}),w+="
"),this._dom.overlayDetails.innerHTML=w;let v=l(n.x),S=a(n.y),A=h(n.w),lt=h(n.h),Z=innerWidth-this._panelWidth,E,tt;v+A+4400?E=v-440:E=Math.max(10,(Z-420)/2),tt=Math.max(10,Math.min(S-40,innerHeight-500)),_.style.left=E+"px",_.style.top=tt+"px",this._dom.overlay.style.display="block";let b=this._dom.highlightBox;b.style.left=v+"px",b.style.top=S+"px",b.style.width=A+"px",b.style.height=lt+"px",b.style.border=`3px solid ${n.color||"#58a6ff"}`,b.style.boxShadow=`0 0 30px ${n.color||"#58a6ff"}88, inset 0 0 20px ${n.color||"#58a6ff"}22`,b.style.background=f()?"#161b22":"#f6f8fa",b.style.display="flex",b.style.flexDirection="column",b.style.alignItems="center",b.style.justifyContent="center",b.style.fontFamily="system-ui",b.style.color=f()?"#c9d1d9":"#24292f",b.innerHTML=`
${M(n.label)}
`+(n.sublabel?`
${M(n.sublabel)}
`:""),b.style.transform="none",this._open=!0,g()}close(){this._dom.overlay.style.display="none";let t=this._dom.overlayCard.querySelector("img.overlay-logo");t&&t.remove(),this._dom.highlightBox.style.display="none",this._highlightKey=null,this._open=!1,this._cb.requestDraw(),this._wasRunning&&(this._cb.setPaused(!1),this._cb.setRunning(!0),this._cb.resumeAnimation())}};function pt(i){return i.replace(/&/g,"&").replace(//g,">")}var I=class{constructor(t){this._container=t,this._steps=[],this.onStepClick=null}init(t){this._steps=t,this._container.innerHTML="",t.forEach((e,s)=>{let o=document.createElement("div");o.className="fs-step pending",o.id="step-"+s,o.innerHTML=`${pt(e.t)}`,o.addEventListener("click",()=>{this.onStepClick&&this.onStepClick(s)}),this._container.appendChild(o)})}updateStep(t,e){let s=document.getElementById("step-"+t);s&&(s.className="fs-step "+e)}setActiveStep(t){this._steps.forEach((e,s)=>{s{this.updateStep(s,s<=t?"done":"pending")})}scrollToStep(t){let e=document.getElementById("step-"+t);e&&e.scrollIntoView({block:"nearest",behavior:"smooth"})}reset(){this._steps.forEach((t,e)=>{this.updateStep(e,"pending")})}get length(){return this._steps.length}};function z(i){return i.replace(/&/g,"&").replace(//g,">")}function O(i){return i.map(t=>({...t}))}var J=class{constructor(t,e){this._titleEl=t,this._contentEl=e,this.headers=[],this.body=[],this.cycleState=[],this.stepLabel="",this._phase="request"}init(t){this.headers=(t.headers||[]).map(e=>({v:e.v,s:e.s||"keep",id:e.id})),this.body=(t.body||[]).map(e=>({v:e.v,s:e.s||"keep",id:e.id})),this.cycleState=(t.cycleState||[]).map(e=>({v:e.v,s:e.s||"keep",id:e.id})),this.stepLabel=t.label||"",this.setPhase(t.phase||"request"),this.render()}setPhase(t){this._phase=t,t==="request"?(this._titleEl.textContent="Request",this._titleEl.style.color="#58a6ff"):t==="response"?(this._titleEl.textContent="Response",this._titleEl.style.color="#3fb950"):t==="error"&&(this._titleEl.textContent="Error Response",this._titleEl.style.color="#f85149")}applyMutation(t){if(t.resetStyles!==!1&&this.resetStyles(),t.phase&&this.setPhase(t.phase),t.label!==void 0&&(this.stepLabel=t.label),t.replaceHeaders&&(this.headers=t.replaceHeaders.map(e=>({v:e.v,s:e.s||"keep",id:e.id}))),t.replaceBody&&(this.body=t.replaceBody.map(e=>({v:e.v,s:e.s||"keep",id:e.id}))),t.replaceCycleState&&(this.cycleState=t.replaceCycleState.map(e=>({v:e.v,s:e.s||"keep",id:e.id}))),t.cycleState&&!t.replaceCycleState&&(this.cycleState=t.cycleState.map(e=>({v:e.v,s:e.s||"keep",id:e.id}))),t.clearCycleState&&(this.cycleState=[]),t.actions)for(let e of t.actions)e.action==="add"?this._addLine(e.target||"headers",{v:e.value,s:e.style||"add",id:e.id}):e.action==="remove"?this._removeLine(e.id):e.id&&e.style&&this._setStyle(e.id,e.style);this.render()}resetStyles(){let t=e=>{e.forEach(s=>{s.s!=="del"&&s.s!=="err"&&(s.s="keep")})};t(this.headers),t(this.body),t(this.cycleState)}_setStyle(t,e){let s=this._findLine(t);s&&(s.s=e)}_findLine(t){return this.headers.find(e=>e.id===t)||this.body.find(e=>e.id===t)||this.cycleState.find(e=>e.id===t)}_addLine(t,e){let s=this[t];s&&s.push(e)}_removeLine(t){for(let e of[this.headers,this.body,this.cycleState]){let s=e.findIndex(o=>o.id===t);if(s!==-1){e.splice(s,1);return}}}render(){let t="";this.stepLabel&&(t+=`
${z(this.stepLabel)}
`),t+='
Headers:
',this.headers.forEach(e=>{t+=`
${z(e.v)}
`}),t+='
Body:
',this.body.forEach(e=>{t+=`
${z(e.v)}
`}),this.cycleState.length>0&&(t+='
CycleState:
',this.cycleState.forEach(e=>{t+=`
${z(e.v)}
`})),this._contentEl.innerHTML=t}setLabel(t){this.stepLabel=t}snapshot(){return{headers:O(this.headers),body:O(this.body),cycleState:O(this.cycleState),stepLabel:this.stepLabel,phase:this._phase}}restore(t){this.headers=O(t.headers),this.body=O(t.body),this.cycleState=O(t.cycleState),this.stepLabel=t.stepLabel,this.setPhase(t.phase),this.render()}static highlight(t){return{actions:[{id:t,style:"highlight"}]}}static del(t){return{actions:[{id:t,style:"del"}]}}static addHeader(t,e){return{actions:[{action:"add",target:"headers",value:t,id:e}]}}static addBody(t,e){return{actions:[{action:"add",target:"body",value:t,id:e}]}}};function Q(i){let t=[];if(i==null||typeof i!="object"||Array.isArray(i))return{valid:!1,errors:["Diagram must be a non-null object"]};ct(i.meta,t);let e=dt(i.nodes,t);return yt(i.flows,e,t),i.tooltips!==void 0&>(i.tooltips,e,t),i.inspector!==void 0&&mt(i.inspector,t),i.canvas!==void 0&&_t(i.canvas,t),i.theme!==void 0&&bt(i.theme,t),t.length===0?{valid:!0}:{valid:!1,errors:t}}function ct(i,t){if(i==null||typeof i!="object"||Array.isArray(i)){t.push("meta: must be an object");return}(typeof i.title!="string"||i.title.length===0)&&t.push("meta.title: must be a non-empty string")}function dt(i,t){let e=new Set;if(i==null||typeof i!="object"||Array.isArray(i))return t.push("nodes: must be a non-empty object"),e;let s=Object.keys(i);if(s.length===0)return t.push("nodes: must contain at least one node"),e;for(let o of s){e.add(o);let n=i[o],r=`nodes.${o}`;if(n==null||typeof n!="object"||Array.isArray(n)){t.push(`${r}: must be an object`);continue}q(n,"x",r,t),q(n,"y",r,t),q(n,"w",r,t),q(n,"h",r,t),n.type!=="boundary"&&n.label!==void 0&&typeof n.label!="string"&&t.push(`${r}.label: must be a string`)}return e}function yt(i,t,e){if(i==null||typeof i!="object"||Array.isArray(i)){e.push("flows: must be a non-empty object");return}let s=Object.keys(i);if(s.length===0){e.push("flows: must contain at least one flow");return}for(let o of s){let n=i[o],r=`flows.${o}`;if(n==null||typeof n!="object"||Array.isArray(n)){e.push(`${r}: must be an object with label and steps`);continue}if((typeof n.label!="string"||n.label.length===0)&&e.push(`${r}.label: must be a non-empty string`),!Array.isArray(n.steps)){e.push(`${r}.steps: must be an array`);continue}for(let l=0;l0&&s.size>0&&(s.has(i)||o.push(`${e}.${t}: references unknown node "${i}"`))}var G={left:{from:"fromLeft",to:"toLeft"},right:{from:"fromRight",to:"toRight"},top:{from:"fromTop",to:"toTop"},bottom:{from:"fromBottom",to:"toBottom"}};async function nt(i){let t;if(typeof i=="string"){let s=await fetch(i);if(!s.ok)throw new Error(`Failed to fetch diagram from "${i}": ${s.status} ${s.statusText}`);try{t=await s.json()}catch(o){throw new Error(`Failed to parse diagram JSON from "${i}": ${o.message}`)}}else if(i!=null&&typeof i=="object"&&!Array.isArray(i))t=JSON.parse(JSON.stringify(i));else throw new Error("loadDiagram() expects a URL string or a diagram object");let e=Q(t);if(!e.valid)throw new Error(`Invalid diagram: `+e.errors.join(` `));return wt(t),vt(t.nodes),t.flows,t.tooltips===void 0&&(t.tooltips={}),t.inspector===void 0&&(t.inspector={}),St(t.flows),t}function wt(i){i.canvas||(i.canvas={}),i.canvas.width===void 0&&(i.canvas.width=1250),i.canvas.height===void 0&&(i.canvas.height=1050)}function vt(i){for(let t of Object.keys(i)){let e=i[t];e.type===void 0&&(e.type="box"),e.fontSize===void 0&&(e.fontSize=16),e.color===void 0&&(e.color="#58a6ff"),e.fs===void 0&&(e.fs=e.fontSize)}}function St(i){for(let t of Object.keys(i)){let s=i[t].steps;if(Array.isArray(s))for(let o of s)o.mode==="arrow"&&kt(o)}}function kt(i){let t=i.edge;if(!(!t||typeof t!="object")){if(t.fromSide&&G[t.fromSide]){let e=G[t.fromSide].from;i[e]=!0}if(t.toSide&&G[t.toSide]){let e=G[t.toSide].to;i[e]=!0}t.fromXOffset!==void 0&&(i.fromXOff=t.fromXOffset),t.toXOffset!==void 0&&(i.toXOff=t.toXOffset),t.yOffset!==void 0&&(i.yOff=t.yOffset),Array.isArray(t.waypoints)&&(i.waypoints=t.waypoints),delete i.edge}}var at=class{constructor(t,e={}){this._canvas=t,this._options=e,this._engine=new F(t,{panelWidth:e.panelWidth??380}),this._theme=new H(!0),this._renderer=new j,this._engine.onDraw=(s,o,n)=>{this._draw(s,o,n)},this._theme.onToggle=s=>{this._engine.state.isDark=s,typeof document<"u"&&document.body.classList.toggle("light",!s),this._engine.draw()},this._stepsPanel=null,this._inspector=null,this._overlay=null,this._playback=null,this._snapshot=null,this._diagram=null}async load(t){let e=await nt(t);this._diagram=e;let s=this._engine.state;if(s.nodes=e.nodes,s.tooltips=e.tooltips,s.isDark=this._theme.isDark,e.canvas&&(e.canvas.width&&(this._engine.logicalWidth=e.canvas.width),e.canvas.height&&(this._engine.logicalHeight=e.canvas.height)),e.theme){let h=e.theme==="dark";this._theme.isDark!==h&&this._theme.toggle()}s.flows={},this._flowLabels={};for(let[h,f]of Object.entries(e.flows))this._flowLabels[h]=f.label,s.flows[h]=(f.steps||[]).map(p=>{let d={...p};return d.from!==void 0&&d.f===void 0&&(d.f=d.from,delete d.from),d.text!==void 0&&d.t===void 0&&(d.t=d.text,delete d.text),d.color!==void 0&&d.c===void 0&&(d.c=d.color,delete d.color),d.num===void 0&&d.badge!==void 0&&(d.num=d.badge),d});let o=Object.keys(s.flows);s.activeFlow=o[0]||null;let n=this._options.stepsContainer||this._options.stepsElement;n&&(this._stepsPanel=new I(n),s.activeFlow&&this._stepsPanel.init(s.flows[s.activeFlow]));let r=this._options.inspectorTitle,l=this._options.inspectorContent;if(r&&l){this._inspector=new J(r,l);let h=e.inspector?.initialState;if(h){let f=p=>({v:p.value||p.v,s:p.style||p.s||"keep",id:p.id});this._inspector.init({headers:(h.headers||[]).map(f),body:(h.body||[]).map(f),cycleState:(h.cycleState||[]).map(f)}),h.phase&&this._inspector.setPhase(h.phase),this._inspector.render()}}(this._options.overlay||this._options.overlayElement)&&this._setupOverlay(),this._setupPlayback(),this._stepsPanel&&this._playback&&(this._stepsPanel.onStepClick=h=>{this._playback.jumpToStep(h)}),this._setupBranding(e),this._setupTitle(e),this._setupLegend(e),this._setupFlowSelector(),this._setupButtons(),this._setupCanvasClick(),this._engine.start()}play(t){if(this._playback){if(t&&t!==this._engine.state.activeFlow&&(this._playback.setFlow(t),this._stepsPanel)){let e=this._engine.state.flows[t];e&&this._stepsPanel.init(e)}this._playback.run()}}pause(){this._playback&&this._playback.pause()}resume(){this._playback&&this._playback.resume()}reset(){this._playback&&this._playback.reset()}jumpTo(t){this._playback&&this._playback.jumpToStep(t)}setSpeed(t){this._engine&&(this._engine.state.playbackSpeed=t)}toggleLoop(){if(this._playback)return this._playback.toggleLoop()}toggleTheme(){this._theme.toggle()}get state(){return this._engine.state}get flows(){return Object.keys(this._engine.state.flows)}get isPlaying(){return this._engine.state.running}get isPaused(){return this._engine.state.paused}_setupBranding(t){let e=this._options.brand;if(!e||!t.meta?.branding)return;let s=t.meta.branding;if(e.innerHTML="",s.logo){let o=document.createElement("img");o.src=s.logo,o.alt=s.title||"",e.appendChild(o)}if(s.title){let o=document.createElement("span");o.textContent=s.title,e.appendChild(o)}}_setupTitle(t){let e=this._options.title;if(!e)return;let s=t.meta?.title||"";e.innerHTML=s,this._titleEl=e,this._updateFlowLabel()}_updateFlowLabel(){if(!this._titleEl)return;let t=this._engine.state,e=this._flowLabels[t.activeFlow]||"",s=this._diagram?.meta?.title||"";this._titleEl.innerHTML=s+(e?` · ${e}`:"")}_setupLegend(t){let e=this._options.legend;if(!(!e||!t.legend)){e.innerHTML='
Legend
';for(let s of t.legend){let o=document.createElement("div");o.className="fs-legend-item";let n=document.createElement("div");n.className="fs-legend-dot",n.style.background=s.color,o.appendChild(n),o.appendChild(document.createTextNode(s.label)),e.appendChild(o)}}}_setupFlowSelector(){let t=this._options.flowSelect;if(!t)return;let e=this._engine.state,s=this._diagram.flowOrder||Object.keys(e.flows);t.innerHTML="";for(let o of s){let n=document.createElement("option");n.value=o,n.textContent=this._flowLabels[o]||o,t.appendChild(n)}t.value=e.activeFlow,t.addEventListener("change",()=>{let o=t.value;this.reset(),e.activeFlow=o,this._stepsPanel&&this._stepsPanel.init(e.flows[o]),this._updateFlowLabel(),this._engine.draw()})}_setupButtons(){let t=this._options,e=this;if(t.playBtn&&t.playBtn.addEventListener("click",()=>{e.play();let s=e._engine.state;t.playBtn.innerHTML=s.running&&!s.paused?"▮▮ Pause":"▶ Start"}),t.resetBtn&&t.resetBtn.addEventListener("click",()=>{e.reset(),t.playBtn&&(t.playBtn.innerHTML="▶ Start")}),t.speedBtn){let s=[1,2,.5],o=0;t.speedBtn.addEventListener("click",()=>{o=(o+1)%s.length,e._engine.state.playbackSpeed=s[o],t.speedBtn.textContent=s[o]+"x"})}t.loopBtn&&t.loopBtn.addEventListener("click",()=>{e._engine.state.loopMode=!e._engine.state.loopMode,t.loopBtn.textContent=e._engine.state.loopMode?"\u27F3 Loop ON":"\u27F3 Loop",t.loopBtn.style.borderColor=e._engine.state.loopMode?"#3fb950":"#79c0ff",t.loopBtn.style.color=e._engine.state.loopMode?"#3fb950":"#79c0ff"}),t.themeBtn&&t.themeBtn.addEventListener("click",()=>{e.toggleTheme(),t.themeBtn.innerHTML=e._theme.isDark?"☾":"☼"})}closeOverlay(){this._overlay&&this._overlay.close()}_draw(t,e,s){let o=this._theme.colors(),n=this._theme.isDark;t.fillStyle=o.bg,t.fillRect(0,0,s.W,s.H);let r={ctx:t,tx:l=>s.tx(l),ty:l=>s.ty(l),ts:l=>s.ts(l),isDark:n,colors:o,activeNodes:e.activeNodes,badges:e.badges,glowing:e.glowing,fading:e.fading,nodes:e.nodes};this._renderer.renderAll(t,e.nodes,r),e.lines.length>0&&st(r,e.lines);for(let l=e.dots.length-1;l>=0;l--){let a=e.dots[l];a.update(),a.draw(t),a.alive||e.dots.splice(l,1)}if(this._overlay&&this._overlay.isOpen&&this._overlay.highlightKey){let l=this._overlay.highlightKey,a=e.nodes[l];a&&(t.save(),t.globalAlpha=.15,t.fillStyle=a.color||"#58a6ff",t.fillRect(s.tx(a.x)-4,s.ty(a.y)-4,s.ts(a.w)+8,s.ts(a.h)+8),t.restore())}}_setupPlayback(){let t=this._engine,e=t.state;this._playback=new W({state:e,engine:t,stepsPanel:this._stepsPanel?{markActive:s=>this._stepsPanel.updateStep(s,"active"),markDone:s=>this._stepsPanel.updateStep(s,"done"),markPending:s=>this._stepsPanel.updateStep(s,"pending"),scrollTo:s=>this._stepsPanel.scrollToStep(s),initSteps:s=>this._stepsPanel.init(s)}:null,inspector:this._inspector&&this._diagram.inspector?{init:()=>{let s=this._diagram.inspector?.initialState;if(s){let o=n=>({v:n.value||n.v,s:n.style||n.s||"keep",id:n.id});this._inspector.init({headers:(s.headers||[]).map(o),body:(s.body||[]).map(o),cycleState:(s.cycleState||[]).map(o)}),s.phase&&this._inspector.setPhase(s.phase)}},step:s=>{let o=this._diagram.inspector?.mutations?.[this._engine.state.activeFlow];if(o){let n=o.find(r=>r.step===s);if(n){let r=a=>({v:a.value||a.v,s:a.style||a.s||"keep",id:a.id}),l={...n};l.actions&&(l.actions=l.actions.map(a=>({...a,value:a.value,style:a.style}))),l.replaceHeaders&&(l.replaceHeaders=l.replaceHeaders.map(r)),l.replaceBody&&(l.replaceBody=l.replaceBody.map(r)),l.cycleState&&(l.cycleState=l.cycleState.map(r)),l.replaceCycleState&&(l.replaceCycleState=l.replaceCycleState.map(r)),this._inspector.applyMutation(l)}}}}:null,createDot:(s,o,n,r,l,a)=>new R(s,o,n,r,l,a,{tx:h=>t.tx(h),ty:h=>t.ty(h),ts:h=>t.ts(h),nodes:e.nodes}),onFlowComplete:()=>{this._options.playBtn&&(this._options.playBtn.innerHTML="▶ Start"),this._updateFlowLabel(),this._options.flowSelect&&(this._options.flowSelect.value=this._engine.state.activeFlow),this._stepsPanel&&this._stepsPanel.init(this._engine.state.flows[this._engine.state.activeFlow])},flowOrder:this._diagram.flowOrder||Object.keys(e.flows)}),this._playback.onChange(s=>{s==="flow"&&(this._updateFlowLabel(),this._options.flowSelect&&(this._options.flowSelect.value=e.activeFlow),this._stepsPanel&&this._stepsPanel.init(e.flows[e.activeFlow])),(s==="play"||s==="resume")&&this._options.playBtn&&(this._options.playBtn.innerHTML="▮▮ Pause"),(s==="pause"||s==="reset")&&this._options.playBtn&&(this._options.playBtn.innerHTML="▶ Start")})}_setupOverlay(){let t=this._engine,e=t.state,s=this._options,o=s.overlay||s.overlayElement,n=s.overlayCard,r=s.overlayAccent,l=s.overlayTitle,a=s.overlayDesc,h=s.overlayDetails,f=s.highlightBox;!o||!n||(s.overlayClose&&s.overlayClose.addEventListener("click",()=>this.closeOverlay()),s.overlayResume&&s.overlayResume.addEventListener("click",()=>this.closeOverlay()),this._overlay=new X({overlay:o,overlayCard:n,overlayAccent:r,overlayTitle:l,overlayDesc:a,overlayDetails:h,highlightBox:f},{isRunning:()=>e.running,isPaused:()=>e.paused,setRunning:p=>{e.running=p},setPaused:p=>{e.paused=p},clearStepTimer:()=>{e.stepTimer&&(clearTimeout(e.stepTimer),e.stepTimer=null)},resumeAnimation:()=>{this._playback&&this._playback.resume()},requestDraw:()=>t.draw(),isDark:()=>this._theme.isDark,tx:p=>t.tx(p),ty:p=>t.ty(p),ts:p=>t.ts(p)},this._options.panelWidth??380))}_setupCanvasClick(){let t=this._engine,e=t.state;this._canvas.addEventListener("click",s=>{if(!this._overlay||this._overlay.isOpen)return;let o=this._canvas.getBoundingClientRect(),n=s.clientX-o.left,r=s.clientY-o.top,l=(n-t._ox)/t._sc,a=(r-t._oy)/t._sc;for(let[h,f]of Object.entries(e.nodes))if(!(f.type==="boundary"||f.type==="container")&&l>=f.x&&l<=f.x+f.w&&a>=f.y&&a<=f.y+f.h){let p=e.tooltips[h];p&&this._overlay.show(h,f,p);break}})}};export{N as DiagramState,R as Dot,F as Engine,at as FlowStory,J as InspectorPanel,X as OverlayManager,W as PlaybackController,j as Renderer,Y as SnapshotManager,I as StepsPanel,H as ThemeManager,et as drawArrowLine,st as drawLines,D as edgePt,nt as loadDiagram,B as resolveEdge,Q as validateDiagram}; diff --git a/docs/demo/v5/live.html b/docs/demo/v5/live.html index d725448..e0b935c 100644 --- a/docs/demo/v5/live.html +++ b/docs/demo/v5/live.html @@ -34,7 +34,7 @@

AgentOps — Live companion (v5)