Compare commits

...
Author SHA1 Message Date
MervinPraison 88307cf5b0 infra: add merge gate, release gate, and pipeline automation
Port PraisonAI gate scripts with gate-config.js for PraisonAIUI paths,
single-package release preflight, pipeline label sync, Opus merge gate,
CI failure Claude trigger, pypi-auto patch releases, and review-chain hooks.
2026-07-08 09:36:56 +01:00
MervinPraison 140dd3a443 fix(version): update aiui to version 0.3.127 in project files
Updated the version in pyproject.toml and uv.lock to reflect the latest release.
2026-07-05 06:21:52 +01:00
MervinPraison 20b29caae3 chore(version): update aiui version to 0.3.126
Bumped the version in pyproject.toml and uv.lock to reflect the latest release.
2026-06-29 10:06:23 +01:00
MervinPraison d6cff26261 feat(version): update aiui version to 0.3.125
Incremented the version of the aiui project to 0.3.125 in both
pyproject.toml and uv.lock files to reflect the latest release.
2026-06-29 10:05:57 +01:00
Mervin Praison 7018271836 Merge pull request #171 from MervinPraison/claude/issue-170-20260628-120000
feat: world-class kanban board — dense cards, editable drawer, debounced live updates
2026-06-29 10:05:26 +01:00
Mervin Praison 61e5fd04ed Merge pull request #169 from MervinPraison/claude/issue-167-20260626-120000
fix: harden channel auto-start lifecycle and running state
2026-06-29 10:04:59 +01:00
Mervin Praison 80deeb451a Merge pull request #168 from MervinPraison/claude/issue-166-20260626-000000
fix: dashboard buildDashboard insertBefore NotFoundError (#166)
2026-06-29 10:04:46 +01:00
MervinPraisonandClaude 9726411010 feat: enrich kanban cards, editable drawer, debounced live updates (fixes #170)
Gap 1: card DTO now carries tenant, created_at, comment_count and child
progress; cardElement renders dense chrome (checkbox, status dot, id,
priority badge, tenant chip, assignee, age, comment count, progress pill).

Gap 2: new POST /api/kanban/tasks/{id}/comments route plus add_comment on
the store protocol; drawer is now editable (title/assignee/priority),
markdown-rendered (XSS-safe), shows a comment thread with Enter-to-submit
and run/event history rows.

Gap 3: SSE consumer refetches on a 300ms debounce, the open drawer
live-refreshes on events for its task, and destructive moves/delete prompt
for confirmation.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-28 04:23:35 +00:00
MervinPraisonandClaude 7cb20bca32 fix: harden channel auto-start lifecycle and running state (fixes #167)
Gateway _channel_tasks may be a dict or a list depending on SDK version;
calling .append() on a dict raised 'dict' object has no attribute 'append'
during auto-start. Register defensively for both shapes and never let a
gateway bookkeeping failure abort an already-started bot.

Also force running=False whenever start_error is set (in _sync_running_status
and the auto-start failure paths) so the API no longer reports running=true
alongside an error, and log full tracebacks via logger.exception.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-26 06:20:46 +00:00
MervinPraisonandClaude 11898f2c7d fix: append main before insertBefore in dashboard buildDashboard (fixes #166)
buildDashboard() called root.insertBefore(shellHeader, main) before main was
a child of root, throwing NotFoundError and aborting the dashboard plugin
init, which blocked Ctrl+K and the browser chat input. Append main to root
first so it is a valid reference node for the guarded header insert.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-26 06:19:37 +00:00
MervinPraisonandCursor 0b62e7dcec feat: add PyPI Release workflow for aiui package
Manual workflow_dispatch release matching PraisonAI: patch bump,
uv publish, PyPI wait, commit, tag, and GitHub release. Update
instruction.md with PraisonAIUI publish steps.

Co-authored-by: Cursor <[email protected]>
2026-06-25 07:48:12 +01:00
MervinPraisonandCursor fdef44b303 feat: add SDK/product value gate to Claude FINAL review prompts
Centralise PraisonAIUI scope and phase text in workflow env vars and
require Claude to evaluate genuine UI value, correct layering vs
praisonaiagents, and reject scope creep before approving PRs.

Co-authored-by: Cursor <[email protected]>
2026-06-25 07:47:13 +01:00
Mervin Praison 596bb2684c Merge pull request #164 from MervinPraison/claude/issue-162-20260625-0601
fix: sync __version__.py to 0.3.124 (fixes #162)
2026-06-25 07:46:27 +01:00
Mervin Praison 047b86b89f Merge pull request #165 from MervinPraison/claude/issue-163-20260625-0601
feat: parallelize legacy /health and add short-TTL cache
2026-06-25 07:46:10 +01:00
praisonai-triage-agent[bot]andpraisonai-triage-agent[bot] f60f81e086 feat: parallelize legacy /health and add short-TTL cache (fixes #163)
Run provider.health() concurrently with feature checks under a single
bounded gather instead of awaiting the provider first, and cache the deep
health payload for a short TTL so repeat probes stay fast.

Co-authored-by: praisonai-triage-agent[bot] <praisonai-triage-agent[bot]@users.noreply.github.com>
2026-06-25 06:04:59 +00:00
praisonai-triage-agent[bot]andMervin Praison 62d20465b5 fix: sync __version__.py to 0.3.124 (fixes #162)
Co-authored-by: Mervin Praison <[email protected]>
2026-06-25 06:02:41 +00:00
MervinPraison c2f8af6ad0 chore(release): bump version to 0.3.124
Updated version in pyproject.toml and uv.lock to reflect new release.
2026-06-24 23:50:34 +01:00
Mervin Praison 9bb312c8c4 Merge pull request #160 from MervinPraison/claude/issue-155-20260624-120000
feat: Ctrl+K session search in dashboard style (C-01, #155)
2026-06-24 23:50:10 +01:00
MervinPraison d07e41e69b fix(version): update aiui version to 0.3.123
Updated the version in pyproject.toml and uv.lock to reflect the new release.
2026-06-24 23:49:50 +01:00
MervinPraisonandClaude 043aa5c711 feat: port Ctrl+K session search to dashboard style (fixes #155)
Completes C-01 parity for the dashboard presentation plane. PR #145 shipped
SessionSearch in React layouts only, so the primary ops example
(15-dashboard-test, style="dashboard") never mounted React and Ctrl+K did
nothing. This adds a vanilla-JS command palette to dashboard.js (Ctrl+K /
Cmd+K toggle, debounced filter, arrow-key nav, Enter/click select, Esc close)
backed by GET /sessions, and wires chat.js to load the chosen session via an
aiui:session-select event.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-24 22:23:18 +00:00
MervinPraisonandCursor 4c5dadd255 fix: dedupe merge-conflict Claude triggers on main push bursts
Main pushes bypassed the 12h cooldown and concurrent merge runs each
posted @claude for every open DIRTY PR. Apply cooldown always, skip when
claude-conflict-pending label exists, cancel stale scans, and sync fork
handling plus FINAL review gate from PraisonAI.

Co-authored-by: Cursor <[email protected]>
2026-06-24 23:22:54 +01:00
Mervin Praison 86a6018c81 Merge pull request #156 from MervinPraison/claude/issue-153-20260624-0952
fix: sync __version__.py to 0.3.122 (fixes #153)
2026-06-24 23:18:29 +01:00
Mervin Praison 5f37843e3d Merge pull request #157 from MervinPraison/claude/issue-150-20260624-000000
fix(compiler): write all build output as UTF-8 (Windows cp1252)
2026-06-24 23:18:20 +01:00
Mervin Praison 4e2e14699c Merge pull request #158 from MervinPraison/claude/issue-151-20260624-000000
feat: implement /api/provider/health JSON route + guard /api/* catch-all
2026-06-24 23:18:06 +01:00
Mervin Praison 5e512d28da Merge pull request #159 from MervinPraison/claude/issue-152-20260624-0952
feat: parallelize legacy /health and migrate consumers to fast probes
2026-06-24 23:17:44 +01:00
Mervin Praison 420f3776d8 Merge pull request #161 from MervinPraison/claude/issue-154-20260624-0952
feat: MCP manager view + Ctrl+K command palette (B-08, B-02)
2026-06-24 23:17:08 +01:00
praisonai-triage-agent[bot]andMervin Praison 6afb282d76 feat: add MCP manager view and Ctrl+K command palette to dashboard (fixes #154)
Implements B-08 (MCP manager) and B-02 (command palette) from the OpenClaw
Control UI parity umbrella. MCP view lists/connects/disconnects servers via
the existing /api/mcp/* routes; command palette navigates pages via Ctrl+K.

Co-authored-by: Mervin Praison <[email protected]>
2026-06-24 09:58:56 +00:00
praisonai-triage-agent[bot]andMervin Praison 67137d4ba4 feat: parallelize legacy /health and migrate consumers to fast probes (fixes #152)
Legacy /health ran sequential per-feature health checks (~2.8s with 42
features). Parallelize with asyncio.gather + 0.5s per-feature timeout
matching /health/ready, and short-circuit ?deep=false to liveness.
Migrate CLI health-check/doctor status to /health/live and provider
status to /health/ready?deep=false. Update K8s/Docker probe docs to
/health/live.

Co-authored-by: Mervin Praison <[email protected]>
2026-06-24 09:57:46 +00:00
MervinPraisonandClaude faec8047b4 feat: implement /api/provider/health JSON route and guard /api/* catch-all (fixes #151)
Register a dedicated GET /api/provider/health handler before the SPA
catch-all so API clients receive JSON instead of dashboard HTML. Unmatched
/api/* paths now return a JSON 404 in both the static-mount fallback and the
path:path catch-all. Docs updated to match the implementation schema.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-24 09:57:43 +00:00
MervinPraisonandClaude 0d3baebaf3 fix(compiler): write all text output as UTF-8 (fixes #150)
Path.write_text/open default to the locale encoding (cp1252 on
Windows), causing UnicodeEncodeError when compiled HTML/JSON/CSS
contains non-ASCII content such as the U+2728 sparkle from docs.
Add explicit encoding="utf-8" to every text write in the build
and config pipeline.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-24 09:57:42 +00:00
praisonai-triage-agent[bot]andMervin Praison fe1bcd5e28 fix: sync __version__.py to 0.3.122 (fixes #153)
Co-authored-by: Mervin Praison <[email protected]>
2026-06-24 09:54:25 +00:00
MervinPraison ee62c7b908 feat(release): bump version to 0.3.122 and update dependencies
Updated the project version in `pyproject.toml` and `uv.lock`.
Changed dependency versions for `praisonai` and `praisonaiagents`
to their latest compatible versions, ensuring better stability and features.
2026-06-24 09:48:15 +01:00
Mervin Praison da9ff14884 Merge pull request #145 from MervinPraison/claude/issue-128-20260617-0955
feat: add session search palette with Ctrl+K shortcut
2026-06-24 09:47:14 +01:00
MervinPraison c55303684a chore: remove merge conflict CI test marker 2026-06-24 09:04:52 +01:00
MervinPraison d516d9e538 fix(ci): use GH_TOKEN PAT so @claude comment triggers claude.yml 2026-06-24 09:02:27 +01:00
MervinPraison a8d7e4598c fix(ci): grant pull-requests write for merge-conflict Claude comments 2026-06-24 09:00:40 +01:00
MervinPraison dcef9ff502 test: main-side merge conflict marker for CI automation test 2026-06-24 08:59:59 +01:00
MervinPraison fd038d054e ci: auto-trigger Claude on merge conflicts across PRs
Add merge-conflict-claude workflow and conflict rebase prompt with GraphQL merge_state detection in claude.yml.
2026-06-24 08:58:36 +01:00
Mervin Praison a2b941dd24 Merge pull request #146 from MervinPraison/claude/issue-132-20260623-0941
fix: Windows CLI UnicodeEncodeError for Rich output (fixes #132)
2026-06-23 12:33:09 +01:00
Mervin Praison 6d9d0727f2 Merge pull request #147 from MervinPraison/claude/issue-133-20260623-0941
fix: explicit UTF-8 encoding for text file reads on Windows (fixes #133)
2026-06-23 12:32:57 +01:00
Mervin Praison 1f887bd142 Merge pull request #148 from MervinPraison/claude/issue-134-20260623-0941
fix: doctor Gateway check uses /api/provider endpoint (fixes #134)
2026-06-23 12:32:50 +01:00
praisonai-triage-agent[bot]andMervin Praison 6facab2366 fix: use dynamic _icon() fallback for Windows-safe Rich output
Address Gemini reviewer feedback on the inconsistent hard-coded ASCII
replacements. Introduce _supports_unicode()/_icon() helpers that keep the
rich emoji UI on UTF-aware terminals (macOS/Linux/Windows Terminal) and
degrade to ASCII only on legacy code pages (cp1252/cp437), so output is
both crash-safe and free of UX regressions.

Co-authored-by: Mervin Praison <[email protected]>
2026-06-23 11:08:41 +00:00
praisonai-triage-agent[bot]andMervin Praison e9d5cff7db fix: doctor Gateway extractor reads flat /api/provider JSON (fixes #134)
The /api/provider endpoint flattens provider.health() into the top-level
response via info.update(health_data), so there is no nested "health"
object. The previous extractor read data.get("health") which was always
None, forcing status to "unknown" and the Gateway check to always "warn".
Read status/provider directly from the top-level dict.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-authored-by: Mervin Praison <[email protected]>
2026-06-23 11:06:15 +00:00
MervinPraison 193dfcdef5 chore(ci): pin Claude Actions model to claude-opus-4-8 2026-06-23 11:39:36 +01:00
Mervin Praison 56181c8532 Merge pull request #130 from MervinPraison/claude/issue-127-20260617-0955
feat: implement exec approval modal (B-01) for OpenClaw UI parity
2026-06-23 11:39:10 +01:00
Mervin Praison 69461ebef3 Merge pull request #131 from MervinPraison/claude/issue-126-20260617
feat: wire i18n config to frontend runtime
2026-06-23 11:39:05 +01:00
Mervin Praison 7cc2031477 Merge pull request #143 from MervinPraison/claude/issue-137-fix-popstate
feat: fix browser Back/Forward navigation in docs SPA
2026-06-23 11:39:00 +01:00
Mervin Praison 9624f0b8df Merge pull request #144 from MervinPraison/claude/issue-139-20260623
feat: standardize health status checking to accept both 'ok' and 'healthy'
2026-06-23 11:38:55 +01:00
Mervin Praison 81aa5bf1e4 Merge pull request #141 from MervinPraison/claude/issue-136-health-alias
feat: add 'health' alias for 'health-check' command
2026-06-23 11:38:49 +01:00
Mervin Praison ec5d3087ab Merge pull request #142 from MervinPraison/claude/issue-135-health-performance
feat: optimize health endpoints for performance (fixes #135)
2026-06-23 11:38:43 +01:00
Mervin Praison d0e02dfd6d Merge pull request #129 from MervinPraison/claude/issue-125-remove-deprecated-dashboard
feat: remove deprecated 12-agent-dashboard example (XSS vulnerability)
2026-06-23 11:38:38 +01:00
Mervin Praison 3ad900a293 Merge pull request #140 from MervinPraison/claude/issue-138-version-sync
feat: sync package version from 0.3.114 to 0.3.121 (fixes #138)
2026-06-23 11:38:29 +01:00
praisonai-triage-agent[bot]andMervin Praison 963735699c refactor: eliminate code duplication in App.tsx navigation logic
- Extract findNavItemByPath as a reusable helper function
- Remove duplicate findItem function definitions
- Use consistent updateSEO function in both handleItemClick and popstate handler
- Move updateSEO function definition before its usage to fix reference order

This improves code maintainability by reducing duplication and ensuring
consistent SEO update behavior across all navigation paths.

Co-authored-by: Mervin Praison <[email protected]>
2026-06-23 10:16:56 +00:00
praisonai-triage-agent[bot]andMervin Praison e6e4c5a3e8 fix: extend health status standardization to features commands and test_runner
- Updated features list and features status commands to use is_success_status utility
- Updated test_runner health check to accept both 'ok' and 'healthy' statuses
- Added comprehensive unit tests for features commands with both status values
- Ensures consistent health status handling across entire CLI

Co-authored-by: Mervin Praison <[email protected]>
2026-06-23 10:16:09 +00:00
MervinPraison 2b9e78ea50 feat: standardize health status checking to accept both 'ok' and 'healthy' (fixes #139)
- Add health_utils.py with is_success_status() helper function
- Update CLI health-check command to treat both 'ok' and 'healthy' as success
- Update doctor command health extractor to use shared helper
- Update provider status command to use shared helper
- Add comprehensive unit tests for both status strings
- Document health status values in API documentation

The system now consistently accepts both 'ok' and 'healthy' as success
statuses, eliminating false alarms in CLI and doctor commands while
maintaining compatibility with existing dashboard behavior.
2026-06-23 09:51:59 +00:00
MervinPraisonandClaude de6d655250 feat: fix browser Back/Forward navigation in docs SPA (fixes #137)
- Fix popstate event handler to properly update route state when browser Back/Forward buttons are used
- Improve title template handling to support single %s placeholder in SEO config
- Add comprehensive tests for frontend navigation functionality
- Rebuild and update frontend bundle with the fixes

The popstate handler now correctly:
- Finds and sets the matching nav item based on URL path
- Updates both selectedItem and activeItemPath state
- Updates the document title using SEO template
- Handles root path navigation

🤖 Generated with Claude Code

Co-Authored-By: Claude <[email protected]>
2026-06-23 09:51:08 +00:00
MervinPraisonandClaude d9e1f9497d feat: optimize health endpoints for performance (fixes #135)
- Add /health/live for fast liveness checks (<100ms)
- Add /health/ready with parallel feature health checks (timeout: 500ms)
- Implement caching for /api/health endpoint (30s TTL)
- Maintain backward compatibility with existing /health endpoint
- Add comprehensive unit tests for all health endpoints
- Document deployment configurations for K8s, Docker, AWS

Addresses performance issue where /health was taking ~5s due to
sequential feature health checks. New endpoints provide:
- Liveness: <500ms p95 (typically <100ms)
- Readiness: <1s with parallel execution
- Cached: <50ms after first request

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-23 09:50:00 +00:00
praisonai-triage-agent[bot]andMervin Praison 5c3b93f452 fix: add explicit UTF-8 encoding to all text file reads for Windows compatibility
- Add encoding="utf-8" to all Path.read_text() calls
- Add encoding="utf-8" to all open() calls for text files
- Fixes UnicodeDecodeError on Windows where default encoding is cp1252
- Affects CLI commands, compiler, config store, and various features

Co-authored-by: Mervin Praison <[email protected]>
2026-06-23 09:48:31 +00:00
MervinPraisonandClaude 1e81b32f1d feat: add 'health' alias for 'health-check' command (fixes #136)
- Register 'health' as an alias for 'health-check' to support both command names
- Update documentation to use canonical 'health-check' with notes about 'health' alias
- Add tests to verify both commands work correctly

This ensures backward compatibility for users who expect 'aiui health' to work
while maintaining 'health-check' as the canonical command name.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-23 09:47:46 +00:00
MervinPraisonandClaude 862f0c4e5e feat: sync package version from 0.3.114 to 0.3.121 (fixes #138)
- Updated __version__.py to match pyproject.toml (0.3.121)
- Added unit test to enforce version consistency
- Ensures CLI, imports, and package metadata report same version

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2026-06-23 09:46:36 +00:00
praisonai-triage-agent[bot]andMervin Praison 27298ebfb2 fix: Windows CLI crashes with UnicodeEncodeError (fixes #132)
- Add Windows UTF-8 reconfiguration at module load
- Replace Unicode symbols with ASCII fallbacks in CLI output
- Update status icons to use [PASS]/[WARN]/[FAIL] instead of emojis
- Replace Unicode arrows and borders with ASCII characters
- Add comprehensive unit tests for Windows encoding

Co-authored-by: Mervin Praison <[email protected]>
2026-06-23 09:46:30 +00:00
praisonai-triage-agent[bot]andMervin Praison 33391d6775 fix: update doctor Gateway check to use existing /api/provider endpoint (fixes #134)
The doctor Gateway Status check was calling /api/provider/health which doesn't exist,
causing it to always show HTTP 404. Updated to use the existing /api/provider endpoint
and extract health data from the nested health object.

Co-authored-by: Mervin Praison <[email protected]>
2026-06-23 09:45:23 +00:00
praisonai-triage-agent[bot]andMervin Praison e1bb77ff1f fix(security): add input validation and XSS protection to i18n
- Add regex escaping in LocaleProvider to prevent regex injection
- Add locale format validation (e.g., 'en', 'en-US')
- Add key format validation for translation keys
- Pre-compile regex patterns for better performance
- Add AbortController to handle race conditions
- Add loading state to context for better UX

Co-authored-by: Mervin Praison <[email protected]>
2026-06-17 10:29:41 +00:00
praisonai-triage-agent[bot]andMervin Praison 4fe5667e06 fix: address architecture review findings for approval modal
- Fix RunEvent type safety by using RUN_PAUSED instead of invalid 'approval_required' string
- Optimize imports with module-level caching to avoid repeated imports on every tool call
- Update frontend to handle approval events via extra_data type checking
- Fix linting issues (whitespace in blank lines)
- Maintain backward compatibility with existing event handling

Import time remains under 200ms limit (178.5ms tested)

Co-authored-by: Mervin Praison <[email protected]>
2026-06-17 10:24:23 +00:00
MervinPraisonandClaude d9aaec3c1b feat: wire i18n config to frontend runtime (fixes #126)
- Promote i18n from EXPERIMENTAL to IMPLEMENTED status in feature registry
- Create LocaleProvider React context for managing locale state
- Add LocaleSwitcher component in header when multiple locales configured
- Connect frontend to existing i18n API endpoints for translation strings
- Implement RTL support with automatic dir attribute switching
- Add comprehensive tests for i18n implementation status
- Update compiler to remove experimental comment for i18n

This completes the i18n wiring gap identified in issue #126, enabling full
internationalization support in the frontend runtime with locale switching,
RTL support, and translation loading from the backend API.

Co-Authored-By: Claude <[email protected]>
2026-06-17 10:06:35 +00:00
praisonai-triage-agent[bot]andMervin Praison 35a8030078 feat: implement exec approval modal for tool execution consent (fixes #127)
- Add approval modal system to dashboard with risk-based UI
- Integrate approval checking in provider for high-risk tools
- Connect SSE stream for real-time approval notifications
- Support approve/deny with optional 'always' policy
- Maintain import-time constraint under 200ms (186.8ms verified)

Co-authored-by: Mervin Praison <[email protected]>
2026-06-17 10:02:58 +00:00
MervinPraison 7ad2411cb5 feat: remove deprecated 12-agent-dashboard example (fixes #125)
Removes the deprecated examples/python/12-agent-dashboard/ directory
which contained XSS vulnerabilities via innerHTML usage. This example
was marked for removal in v0.5.0 due to unsafe rendering patterns.

- Deleted examples/python/12-agent-dashboard/ entirely
- Updated examples/README.md to redirect to secure alternatives (15-dashboard-test)
- Removed obsolete reference in 13-real-dashboard docstring

The example used innerHTML with API-derived data in 7+ locations,
creating stored XSS risks. Users should use examples 13 or 15 which
implement secure component-based rendering.
2026-06-17 09:59:49 +00:00
81 changed files with 7781 additions and 823 deletions

No files matched your search

+17 -1
View File
@@ -138,7 +138,23 @@ Final rule: conclude only when evidence shows missing = 0.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PUBLISH WORKFLOW (reference)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 1 — Publish praisonaiagents (Core SDK):
GitHub Actions (recommended):
GitHub → Actions → "PyPI Release" → Run workflow → bump: patch
Requires repo secrets: PYPI_TOKEN, GH_TOKEN
Requires GitHub Environment "pypi" with required reviewers (Settings → Environments)
Manual — Publish aiui (PraisonAIUI):
cd /Users/praison/PraisonAIUI
# Bump version in pyproject.toml and src/praisonaiui/__version__.py
rm -rf dist && uv lock && uv build && uv publish --token "$PYPI_TOKEN"
git add pyproject.toml uv.lock src/praisonaiui/__version__.py
git commit -m "Release vX.Y.Z" && git tag vX.Y.Z && git push && git push --tags
gh release create vX.Y.Z --title "PraisonAIUI vX.Y.Z" --notes "Release vX.Y.Z" --latest
PyPI package name: aiui
Verify: pip index versions aiui | head -1
PraisonAI stack (separate repo — publish SDK before wrapper):
cd /Users/praison/praisonai-package/src/praisonai-agents
praisonai publish pypi
# Uses uv internally: uv lock → uv build → uv publish
+153
View File
@@ -0,0 +1,153 @@
/**
* Idempotent CodeRabbit/Qodo kick for bot-opened PRs.
* @see .github/workflows/auto-pr-comment.yml, claude.yml, bot-pr-recovery.yml
*/
const KICK_AUTHORS = new Set(['MervinPraison', 'github-actions[bot]']);
const BOT_PR_AUTHORS = new Set(['praisonai-triage-agent[bot]', 'github-actions[bot]']);
async function listAllComments(github, owner, repo, issueNumber) {
if (typeof github.paginate === 'function') {
return github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
}
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
return data;
}
function kickAuthored(comment, marker) {
return (
KICK_AUTHORS.has(comment.user?.login) &&
(comment.body || '').includes(marker)
);
}
function coderabbitKickPosted(comments) {
return comments.some((c) => kickAuthored(c, '@coderabbitai review'));
}
function qodoKickPosted(comments) {
return comments.some((c) => kickAuthored(c, '/review'));
}
function chainKickPosted(comments) {
return coderabbitKickPosted(comments) && qodoKickPosted(comments);
}
function isBotOpenedPr(pr) {
if (BOT_PR_AUTHORS.has(pr.user?.login)) return true;
return pr.user?.type === 'Bot';
}
async function kickReviewChain(github, owner, repo, prNumber, core, preFetchedComments = null) {
const comments = preFetchedComments || await listAllComments(github, owner, repo, prNumber);
const needCoderabbit = !coderabbitKickPosted(comments);
const needQodo = !qodoKickPosted(comments);
if (!needCoderabbit && !needQodo) {
core?.info?.(`Review chain already kicked on PR #${prNumber}`);
return { kicked: false, reason: 'already_kicked' };
}
if (needCoderabbit) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: '@coderabbitai review',
});
}
if (needQodo) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: '/review',
});
}
core?.info?.(`Kicked review chain for PR #${prNumber} (coderabbit=${needCoderabbit}, qodo=${needQodo})`);
return { kicked: true, coderabbit: needCoderabbit, qodo: needQodo };
}
async function findOpenPrForIssue(github, owner, repo, issueNumber) {
const prefix = `claude/issue-${issueNumber}-`;
const { data: prs } = await github.rest.pulls.list({
owner,
repo,
state: 'open',
sort: 'created',
direction: 'desc',
per_page: 30,
});
return (
prs.find(
(p) => (p.head?.ref || '').startsWith(prefix) && isBotOpenedPr(p)
) || null
);
}
async function kickReviewChainForIssue(github, owner, repo, issueNumber, core) {
const pr = await findOpenPrForIssue(github, owner, repo, issueNumber);
if (!pr) {
core?.info?.(`No open PR for issue #${issueNumber}, skipping review kick`);
return { kicked: false, reason: 'no_pr' };
}
const result = await kickReviewChain(github, owner, repo, pr.number, core);
return { ...result, prNumber: pr.number };
}
async function recoverStalledBotPrs(github, owner, repo, options, core) {
const { prNumber = null, minAgeMs = 10 * 60 * 1000, maxRecover = 10 } = options || {};
let prs;
if (prNumber) {
const { data } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
prs = [data];
} else if (typeof github.paginate === 'function') {
prs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
} else {
const { data } = await github.rest.pulls.list({ owner, repo, state: 'open', per_page: 100 });
prs = data;
}
const cutoff = Date.now() - minAgeMs;
let recovered = 0;
for (const pr of prs) {
if (recovered >= maxRecover) break;
if (!isBotOpenedPr(pr)) continue;
if (!prNumber && new Date(pr.created_at).getTime() > cutoff) continue;
const comments = await listAllComments(github, owner, repo, pr.number);
if (chainKickPosted(comments)) continue;
await kickReviewChain(github, owner, repo, pr.number, core, comments);
recovered += 1;
}
core?.info?.(`Recovery complete (${recovered} PR(s) kicked)`);
return recovered;
}
module.exports = {
KICK_AUTHORS,
BOT_PR_AUTHORS,
listAllComments,
chainKickPosted,
coderabbitKickPosted,
qodoKickPosted,
isBotOpenedPr,
kickReviewChain,
findOpenPrForIssue,
kickReviewChainForIssue,
recoverStalledBotPrs,
};
@@ -0,0 +1,35 @@
#!/usr/bin/env node
/**
* Run: node .github/scripts/ci-failure-claude-selftest.js
*/
const ciFix = require('./ci-failure-claude.js');
const mergeGate = require('./merge-gate.js');
const config = require('./gate-config.js');
let failed = 0;
function assert(name, cond) {
if (!cond) {
console.error('FAIL:', name);
failed += 1;
} else {
console.log('ok:', name);
}
}
const LOG = `
python UNKNOWN STEP FAILED (0.0100s) tests/unit/test_example.py::test_foo - AssertionError: bar
python UNKNOWN STEP ##[error]Process completed with exit code 1.
`;
const parsed = ciFix.parsePytestFailures(LOG);
assert('parses pytest failure', parsed.length === 1);
assert('uses CI workflow list', config.ciFailureWorkflowRuns.includes('CI'));
const comment = ciFix.buildCiFixComment({
headSha: 'abc1234567890abcdef1234567890abcdef12',
failedChecks: [{ name: 'python', workflow: 'CI', html_url: 'https://example.com/job/1' }],
failureSummaries: [{ jobName: 'python', failures: parsed }],
});
assert('comment mentions product guardrails', comment.includes('Product guardrails'));
process.exit(failed ? 1 : 0);
+294
View File
@@ -0,0 +1,294 @@
/**
* Post @claude comments with CI failure details for internal PRs.
* @see .github/workflows/ci-failure-claude.yml
*/
const mergeGate = require('./merge-gate.js');
const config = require('./gate-config.js');
const CI_FIX_LABEL = 'claude-ci-fix-pending';
const COOLDOWN_MS = 12 * 60 * 60 * 1000;
const AUTO_ACTORS = mergeGate.AUTO_ACTORS;
const MAX_FAILURES = 15;
const MAX_JOBS_TO_FETCH = 5;
function shortSha(headSha) {
return (headSha || '').slice(0, 8);
}
function ciFixShaMarker(headSha) {
return `ci failed on head \`${shortSha(headSha)}`.toLowerCase();
}
function isCiFixComment(comment) {
const body = (comment.body || '').toLowerCase();
return body.includes('@claude') && body.includes('ci failed on head');
}
function hasCiFixCommentForSha(comments, headSha) {
const marker = shortSha(headSha).toLowerCase();
return comments.some((c) => {
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!isCiFixComment(c)) return false;
return (c.body || '').toLowerCase().includes(marker);
});
}
function hasRecentCiFixComment(comments, headSha) {
const cutoff = Date.now() - COOLDOWN_MS;
const marker = shortSha(headSha).toLowerCase();
return comments.some((c) => {
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!isCiFixComment(c)) return false;
const body = (c.body || '').toLowerCase();
if (body.includes(marker)) return true;
return new Date(c.created_at).getTime() > cutoff;
});
}
function shouldSkipCiFix({ comments, headSha, labels, hasFinal, claudeInProgress, failedChecks }) {
if (!failedChecks.length) return { skip: true, reason: 'no failed checks' };
if (labels.includes(CI_FIX_LABEL)) return { skip: true, reason: 'ci fix pending' };
if (!hasFinal) return { skip: true, reason: 'awaiting final claude review trigger' };
if (claudeInProgress) return { skip: true, reason: 'claude in progress' };
if (hasCiFixCommentForSha(comments, headSha)) {
return { skip: true, reason: 'already commented for this sha' };
}
if (hasRecentCiFixComment(comments, headSha)) {
return { skip: true, reason: 'recent ci fix comment cooldown' };
}
return { skip: false };
}
function parsePytestFailures(logText) {
if (!logText) return [];
const lines = logText.split('\n');
const failures = [];
const seen = new Set();
for (let i = lines.length - 1; i >= 0 && failures.length < MAX_FAILURES; i -= 1) {
const line = lines[i].trim();
if (!line) continue;
const pytestMatch = line.match(
/FAILED\s*(?:\([^)]+\))?\s+(tests\/[^\s]+(?:::[^\s-]+)*)\s*-\s*(.+)/i
);
if (pytestMatch) {
const testId = pytestMatch[1];
if (!seen.has(testId)) {
seen.add(testId);
failures.push({ testId, error: pytestMatch[2].trim() });
}
continue;
}
const errorMatch = line.match(/##\[error\](.+)/);
if (errorMatch && /tests\//.test(errorMatch[1])) {
const msg = errorMatch[1].trim();
if (!seen.has(msg)) {
seen.add(msg);
failures.push({ testId: msg, error: msg });
}
}
}
return failures;
}
async function fetchJobFailureSummary(github, owner, repo, jobId, jobName) {
try {
const response = await github.rest.actions.downloadJobLogsForWorkflowRun({
owner,
repo,
job_id: jobId,
});
let logText = typeof response.data === 'string' ? response.data : String(response.data || '');
if (logText.length > 100000) {
logText = logText.slice(-100000);
}
return { jobName, jobId, failures: parsePytestFailures(logText) };
} catch (err) {
return { jobName, jobId, failures: [], error: err.message };
}
}
function mapFailedChecks(runs) {
return runs.map((run) => ({
name: run.name,
id: run.id,
html_url: run.html_url || run.details_url,
workflow: run.app?.slug || run.check_suite?.app?.slug || run.name,
}));
}
function buildCiFixComment({ headSha, failedChecks, failureSummaries }) {
const parts = [
`@claude CI failed on HEAD \`${shortSha(headSha)}\`. Please fix the failures below and push to this branch.`,
'',
'## Failed checks',
];
for (const check of failedChecks) {
parts.push(`- **${check.workflow || check.name}** / \`${check.name}\` — ${check.html_url}`);
}
parts.push('', '## Failures (extracted)');
let idx = 0;
for (const summary of failureSummaries) {
for (const failure of summary.failures) {
idx += 1;
if (idx > MAX_FAILURES) break;
parts.push(`${idx}. \`${failure.testId}\` — \`${failure.error}\``);
parts.push(` - Job: \`${summary.jobName}\``);
}
if (idx >= MAX_FAILURES) break;
}
if (idx === 0) {
parts.push('_(Could not extract pytest details — see job logs above.)_');
}
const exampleTest = failureSummaries.find((s) => s.failures.length)?.failures[0]?.testId;
parts.push(
'',
'## Critical review first',
'Before changing code or tests, decide **which side is wrong**:',
'1. **Legitimate feature change** — the PR intent is correct but implementation or tests need updating. Preserve product guarantees; update tests only when behaviour intentionally changed and document why.',
'2. **Regression / bug in this PR** — the failure exposes a real breakage introduced here. Fix the implementation; **do not weaken, skip, or delete tests** just to go green.',
'3. **Pre-existing flake or unrelated failure** — say so explicitly; prefer fixing the root cause over masking it.',
'',
'**Product guardrails (AGENTS.md):**',
'- Do not disturb PraisonAIUI contracts or frontend/backend layering to accommodate scope creep.',
'- Tests must continue to guard backward compatibility and UX behaviour — passing CI by lowering test standards is not acceptable.',
'- If the feature does not genuinely add product value, recommend reverting or narrowing scope instead of patching around failures.',
'',
'## What to do',
'1. State your verdict: **legitimate fix**, **regression fix**, or **needs human review** — and why (1–3 sentences).',
'2. Fix root cause with **minimal changes**; never bloat the Agent class with extra params.',
exampleTest
? `3. Run failing tests locally, e.g. \`pytest ${exampleTest} -q\`, plus any related tests touched by the PR.`
: '3. Run failing tests locally with targeted pytest, plus any related tests touched by the PR.',
'4. Push to this branch and comment: files changed, review verdict, and why tests still protect product behaviour.',
);
return parts.join('\n');
}
async function maybeClearCiFixLabel(github, owner, repo, prNumber, labels, headSha, core) {
if (!labels.includes(CI_FIX_LABEL)) return false;
const ciGreen = await mergeGate.allChecksGreenOnSha(github, owner, repo, headSha, core);
if (!ciGreen) return false;
try {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: CI_FIX_LABEL,
});
core?.info?.(`Removed ${CI_FIX_LABEL} from PR #${prNumber}`);
} catch (err) {
if (err.status !== 404) throw err;
}
return true;
}
async function maybeTriggerCiFixClaude(github, owner, repo, prNumber, core, opts = {}) {
const baseRepo = `${owner}/${repo}`;
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
const pr = ctx.pr;
if (pr.state !== 'open') return { skipped: true, reason: 'not open' };
if (pr.draft) return { skipped: true, reason: 'draft' };
const headRepo = pr.head.repo?.full_name;
if (headRepo && headRepo !== baseRepo && !pr.maintainer_can_modify) {
return { skipped: true, reason: 'fork without maintainer edits' };
}
const headSha = pr.head.sha;
const labels = ctx.labels;
if (await maybeClearCiFixLabel(github, owner, repo, prNumber, labels, headSha, core)) {
return { skipped: true, reason: 'ci green, label cleared' };
}
const ciGreen = await mergeGate.allChecksGreenOnSha(github, owner, repo, headSha, core);
if (ciGreen) return { skipped: true, reason: 'ci green' };
const runs = await mergeGate.listChecksOnSha(github, owner, repo, headSha);
const failedRuns = mergeGate.listFailedChecksOnSha(runs);
if (failedRuns.length === 0) {
return { skipped: true, reason: 'no failed checks yet (may be pending)' };
}
const hasFinal = mergeGate.hasFinalClaudeReviewTrigger(ctx.comments);
const claudeInProgress = await mergeGate.hasInProgressClaudeAssistant(
github, owner, repo, prNumber
);
const skip = shouldSkipCiFix({
comments: ctx.comments,
headSha,
labels,
hasFinal,
claudeInProgress,
failedChecks: failedRuns,
});
if (skip.skip) return { skipped: true, reason: skip.reason };
const failedChecks = mapFailedChecks(failedRuns);
const failureSummaries = [];
for (const check of failedChecks.slice(0, MAX_JOBS_TO_FETCH)) {
failureSummaries.push(
await fetchJobFailureSummary(github, owner, repo, check.id, check.name)
);
}
const body = buildCiFixComment({ headSha, failedChecks, failureSummaries });
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [CI_FIX_LABEL],
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body,
});
core?.info?.(`Posted CI fix @claude on PR #${prNumber} (${shortSha(headSha)})`);
return { triggered: true, headSha: shortSha(headSha) };
}
async function processWorkflowRunFailure(github, owner, repo, workflowRun, core) {
if (workflowRun.conclusion !== 'failure') {
return { skipped: true, reason: `conclusion=${workflowRun.conclusion}` };
}
const allowed = config.ciFailureWorkflowRuns;
if (!allowed.includes(workflowRun.name)) {
return { skipped: true, reason: `workflow ${workflowRun.name}` };
}
const prNumber = await mergeGate.resolvePrNumberFromWorkflowRun(
github, owner, repo, workflowRun
);
if (!prNumber) return { skipped: true, reason: 'no linked PR' };
return maybeTriggerCiFixClaude(github, owner, repo, prNumber, core);
}
module.exports = {
CI_FIX_LABEL,
COOLDOWN_MS,
ciFixShaMarker,
isCiFixComment,
hasCiFixCommentForSha,
hasRecentCiFixComment,
shouldSkipCiFix,
parsePytestFailures,
fetchJobFailureSummary,
buildCiFixComment,
maybeClearCiFixLabel,
maybeTriggerCiFixClaude,
processWorkflowRunFailure,
};
+32
View File
@@ -0,0 +1,32 @@
/**
* PraisonAIUI gate configuration — imported by merge-gate, release-gate, ci-failure-claude.
*/
module.exports = {
repoFullName: 'MervinPraison/PraisonAIUI',
productPathPrefixes: ['src/praisonaiui/', 'src/frontend/', 'tests/'],
sensitivePathPatterns: [
/^\.github\/workflows\//,
/^pyproject\.toml$/,
/credentials\.json$/i,
],
requiredCheckPatterns: [/^ci$/i, /python/i, /test/i, /lint/i, /ruff/i],
ciWorkflowFile: 'ci.yml',
ciWorkflowName: 'CI',
mergeGateWorkflowRuns: ['CI', 'Claude Assistant'],
ciFailureWorkflowRuns: ['CI'],
pypiPackageName: 'aiui',
packagePaths: ['src/praisonaiui', 'src/frontend', 'pyproject.toml'],
finalClaudeScope:
'SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). '
+ 'Do NOT expand into praisonaiagents or the monorepo unless the PR explicitly requires it.',
finalClaudeProductValue:
'4. Product value: review whether the change genuinely adds PraisonAIUI UX value and correct '
+ 'layering (UI/frontend here; core agent logic belongs in praisonaiagents). Reject scope creep.',
agentPyChecks: false,
reviewBotLogins: [
'coderabbitai[bot]',
'qodo-code-review[bot]',
'greptile-apps[bot]',
],
};
+234
View File
@@ -0,0 +1,234 @@
#!/usr/bin/env node
/**
* Local self-test for merge-gate.js heuristics (no GitHub API).
* Run: node .github/scripts/merge-gate-selftest.js
*/
const mg = require('./merge-gate.js');
let failed = 0;
function assert(name, cond) {
if (!cond) {
console.error('FAIL:', name);
failed++;
} else {
console.log('ok:', name);
}
}
// Stale FINAL: push after FINAL, no @claude since head
const finals = [
{ user: { login: 'github-actions[bot]' }, body: '@claude FINAL architecture reviewer', created_at: '2026-06-12T08:00:00Z' },
];
assert('stale when head after final', mg.isStaleFinalAfterPush(finals, '2026-06-12T09:00:00Z'));
const withRecovery = [
...finals,
{ user: { login: 'github-actions[bot]' }, body: '@claude FINAL architecture reviewer', created_at: '2026-06-12T09:30:00Z' },
];
assert('not stale when @claude after head', !mg.isStaleFinalAfterPush(withRecovery, '2026-06-12T09:00:00Z'));
const withClaudeReply = [
...finals,
{
user: { login: 'praisonai-triage-agent[bot]' },
body: "**Claude finished @MervinPraison's task** —— [View job](https://github.com/)",
created_at: '2026-06-12T08:30:00Z',
},
];
assert('not stale when Claude replied after FINAL', !mg.isStaleFinalAfterPush(withClaudeReply, '2026-06-12T09:00:00Z'));
assert('claude final reply detected', mg.isClaudeFinalReplyComment(withClaudeReply[1]));
assert('cancelled detect-and-trigger does not block', mg.OPTIONAL_CANCELLED_CHECKS.has('detect-and-trigger'));
// Stale-FINAL recovery guards (PR #2560 push loop)
const nowMs = Date.now();
const iso = (ms) => new Date(ms).toISOString();
const firstFinal = {
user: { login: 'MervinPraison' },
body: '@claude You are the FINAL architecture reviewer.',
created_at: iso(nowMs - 20 * 60 * 1000),
};
const pushSoonAfter = mg.shouldSkipStaleFinalRecovery(
[firstFinal],
iso(nowMs - 16 * 60 * 1000)
);
assert('debounce push soon after FINAL', pushSoonAfter.skip && pushSoonAfter.reason.includes('soon after'));
const secondFinal = {
user: { login: 'MervinPraison' },
body: '@claude You are the FINAL architecture reviewer.',
created_at: iso(nowMs - 20 * 60 * 1000),
};
const capped = mg.shouldSkipStaleFinalRecovery(
[firstFinal, secondFinal],
iso(nowMs - 2 * 60 * 1000)
);
assert('hourly cap blocks third FINAL in window', capped.skip && capped.reason.includes('capped'));
const botPush = mg.shouldSkipStaleFinalRecovery(
[firstFinal],
iso(nowMs - 16 * 60 * 1000),
'praisonai-triage-agent[bot]'
);
assert('skip when automation pushed head', botPush.skip && botPush.reason.includes('automation'));
assert('claude automation login', mg.isClaudeAutomationLogin('praisonai-triage-agent[bot]'));
// Bot CHANGES_REQUESTED then APPROVE
const reviews = [
{ user: { login: 'coderabbit[bot]', type: 'Bot' }, state: 'CHANGES_REQUESTED', submitted_at: '2026-06-12T08:00:00Z' },
{ user: { login: 'coderabbit[bot]', type: 'Bot' }, state: 'APPROVED', submitted_at: '2026-06-12T09:00:00Z' },
];
assert('bot approve clears CR', !mg.hasAnyChangesRequested(reviews));
assert('human CR blocks', mg.hasAnyChangesRequested([
{ user: { login: 'MervinPraison', type: 'User' }, state: 'CHANGES_REQUESTED', submitted_at: '2026-06-12T09:00:00Z' },
]));
// Verdict after HEAD
const verdictComments = [
{ body: 'MERGE_GATE_VERDICT: APPROVE', created_at: '2026-06-12T08:00:00Z' },
];
assert('verdict before head rejected', mg.findMergeGateVerdict(verdictComments, null, '2026-06-12T09:00:00Z') === null);
assert('verdict after head accepted', mg.findMergeGateVerdict(
[{ body: 'MERGE_GATE_VERDICT: APPROVE', created_at: '2026-06-12T10:00:00Z' }],
null,
'2026-06-12T09:00:00Z'
) === 'APPROVE');
const fallbackApprove = [
{
body: 'MERGE_GATE_VERDICT: APPROVE\n\nAutomated fallback — Claude assess did not post a verdict comment.',
created_at: '2026-06-12T10:00:00Z',
},
];
assert(
'automated fallback APPROVE ignored when Opus required',
mg.findMergeGateVerdict(fallbackApprove, null, '2026-06-12T09:00:00Z', { excludeAutomatedFallback: true }) === null
);
assert(
'automated fallback APPROVE still visible without Opus-only filter',
mg.findMergeGateVerdict(fallbackApprove, null, '2026-06-12T09:00:00Z') === 'APPROVE'
);
const fallbackBlockNewFormat = [
{
body: 'MERGE_GATE_VERDICT: BLOCK\n\nAutomated fallback — Opus merge gate assessment did not complete.',
created_at: '2026-06-12T10:00:00Z',
},
];
assert(
'new-format fallback BLOCK ignored when Opus required',
mg.findMergeGateVerdict(fallbackBlockNewFormat, null, '2026-06-12T09:00:00Z', { excludeAutomatedFallback: true }) === null
);
assert(
'new-format fallback BLOCK detected by marker helper',
mg.isAutomatedFallbackVerdict(fallbackBlockNewFormat[0].body)
);
assert(
'findMergeGateVerdict null-safe with explicit null options',
mg.findMergeGateVerdict(fallbackApprove, null, '2026-06-12T09:00:00Z', null) === 'APPROVE'
);
const noise = [{ user: { login: 'MervinPraison' }, body: '**Merge gate scan** — wait for `@claude`', created_at: new Date().toISOString() }];
assert('diagnostic comment not a trigger', !mg.hasRecentClaudeTrigger(noise, 35));
// Cooldown skip requires an actual verdict on HEAD, not just a FINAL trigger comment
const recentFinalOnHead = [
{
user: { login: 'MervinPraison' },
body: '@claude You are the FINAL architecture reviewer.',
created_at: '2026-06-27T10:00:00Z',
},
];
assert(
'final trigger alone does not count as verdict on head',
mg.findMergeGateVerdict(recentFinalOnHead, null, '2026-06-27T09:55:00Z') === null
);
const recentVerdictOnHead = [
...recentFinalOnHead,
{
user: { login: 'github-actions[bot]' },
body: 'MERGE_GATE_VERDICT: APPROVE',
created_at: '2026-06-27T10:05:00Z',
},
];
assert(
'verdict on head skips cooldown gate',
mg.findMergeGateVerdict(recentVerdictOnHead, null, '2026-06-27T09:55:00Z') === 'APPROVE'
);
// Sensitive + secrets
assert('workflow path sensitive', mg.sensitivePathReasons([{ filename: '.github/workflows/foo.yml' }]).length === 1);
assert('ci-only label exempts workflows', mg.sensitivePathReasons(
[{ filename: '.github/workflows/foo.yml' }],
[mg.WORKFLOW_ONLY_LABEL]
).length === 0);
assert('ci-only label not exempt mixed changes', mg.sensitivePathReasons(
[{ filename: '.github/workflows/foo.yml' }, { filename: 'src/praisonaiui/foo.py' }],
[mg.WORKFLOW_ONLY_LABEL]
).length === 1);
assert('secret in patch', mg.secretScanReasons([{ filename: 'x.py', patch: '+key = "ghp_abcdefghijklmnopqrstuvwxyz1234567890"' }]).length === 1);
// Claude run scoping — other PR branches must not block
assert('other branch claude does not block', !mg.hasBlockingClaudeRunForPr(
[{ event: 'issue_comment', head_branch: 'other-branch' }],
'my-branch'
));
assert('same branch claude blocks', mg.hasBlockingClaudeRunForPr(
[{ event: 'issue_comment', head_branch: 'my-branch' }],
'my-branch'
));
assert('issues event never blocks', !mg.hasBlockingClaudeRunForPr(
[{ event: 'issues', head_branch: 'my-branch' }],
'my-branch'
));
// Conflict rebase clears after bot completion + FINAL on HEAD (within 12h cooldown)
const conflictNowMs = Date.now();
const conflictIso = (offsetMs) => new Date(conflictNowMs + offsetMs).toISOString();
const conflictTrigger = {
user: { login: 'MervinPraison' },
body: '@claude this PR has merge conflicts with main. Please rebase',
created_at: conflictIso(-30 * 60 * 1000),
};
const rebaseDone = {
user: { login: 'praisonai-triage-agent[bot]' },
body: 'Rebase complete — PR #2308 onto latest main',
created_at: conflictIso(-29 * 60 * 1000),
};
const finalAfterRebase = {
user: { login: 'MervinPraison' },
body: '@claude You are the FINAL architecture reviewer.',
created_at: conflictIso(-20 * 60 * 1000),
};
const headAfterRebase = conflictIso(-21 * 60 * 1000);
const rebaseComments = [conflictTrigger, rebaseDone, finalAfterRebase];
assert('conflict blocks before rebase done', mg.hasRecentConflictComment([conflictTrigger], headAfterRebase));
assert('conflict clears after rebase + FINAL on HEAD', !mg.hasRecentConflictComment(rebaseComments, headAfterRebase));
assert('conflict still blocks without FINAL on HEAD', mg.hasRecentConflictComment(
[conflictTrigger, rebaseDone],
headAfterRebase
));
// Tests heuristic
assert('product code without tests', mg.missingTestsReason([{ filename: 'src/praisonaiui/a/b.py', additions: 3 }]) !== null);
assert('product code with tests ok', mg.missingTestsReason([
{ filename: 'src/praisonaiui/a/b.py', additions: 3 },
{ filename: 'tests/test_x.py', additions: 10 },
]) === null);
// PR size
assert('large PR blocked', mg.prSizeReasons([{ additions: 900 }]).length > 0);
assert('internal PR link accepted', mg.isInternalPullRequestLink(
{ base: { repo: { full_name: 'MervinPraison/PraisonAIUI' } } },
'MervinPraison',
'PraisonAIUI'
));
assert('fork sync PR link rejected', !mg.isInternalPullRequestLink(
{ number: 21, base: { repo: { full_name: 'Milkmange/PraisonAIUI' } } },
'MervinPraison',
'PraisonAIUI'
));
process.exit(failed ? 1 : 0);
+900
View File
@@ -0,0 +1,900 @@
/**
* Shared merge-gate helpers for claude-merge-gate.yml
* @see .github/workflows/claude-merge-gate.yml
*/
const config = require('./gate-config.js');
const CLAUDE_TRIGGER_LOGINS = ['MervinPraison', 'github-actions[bot]'];
const AUTO_ACTORS = CLAUDE_TRIGGER_LOGINS;
const CONFLICT_COOLDOWN_MS = 12 * 60 * 60 * 1000;
const CLAUDE_ACTIVE_MS = 35 * 60 * 1000;
const STALE_FINAL_RECOVERY_WINDOW_MS = 60 * 60 * 1000;
const STALE_FINAL_MAX_PER_WINDOW = 2;
const PUSH_AFTER_FINAL_DEBOUNCE_MS = 15 * 60 * 1000;
const ALLOWED_MERGE_STATES = new Set(['CLEAN', 'UNSTABLE']);
const AGENT_PY_MAX_AUTO_LINES = 100;
const PR_MAX_AUTO_ADDITIONS = 800;
const PR_MAX_AUTO_FILES = 30;
const MANUAL_ONLY_LABELS = new Set(['security', 'breaking-change', 'needs-manual-review', 'release']);
const WORKFLOW_ONLY_LABEL = 'merge-gate-ci-only';
const CI_ONLY_PATH_PREFIXES = ['.github/workflows/', '.github/actions/', '.github/scripts/merge-gate'];
const SDK_PATH_PREFIXES = config.productPathPrefixes;
const SENSITIVE_PATH_PATTERNS = config.sensitivePathPatterns;
const REQUIRED_SDK_CHECK_PATTERNS = config.requiredCheckPatterns;
const SECRET_PATTERNS = [
/sk-[a-zA-Z0-9]{20,}/,
/AKIA[0-9A-Z]{16}/,
/ghp_[a-zA-Z0-9]{36,}/,
/github_pat_/,
/Bearer eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/,
/-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/,
];
const BLOCK_LABELS = new Set([
'claude-conflict-pending',
'claude-merge-gate-active',
'no-auto-merge',
'auto-merged-by-gate',
]);
const MERGE_READY_LABEL = 'pipeline/merge-ready';
/** Superseded concurrency runs; must not block merge when real tests passed. */
const OPTIONAL_CANCELLED_CHECKS = new Set(['detect-and-trigger']);
const BOT_REVIEWER_PATTERNS = [
'coderabbit',
'qodo',
'gemini',
'copilot',
'greptile',
];
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function isFinalClaudeTriggerComment(c) {
const body = (c.body || '').toLowerCase();
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!body.includes('@claude')) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
}
function isClaudeTriggerNoise(c) {
const body = c.body || '';
if (body.includes('Merge gate scan')) return true;
if (body.includes('MERGE_GATE_VERDICT')) return true;
if (body.includes('Merged by **Claude PR merge gate**')) return true;
return false;
}
function isClaudeFinalReplyComment(c) {
const login = (c.user?.login || '').toLowerCase();
if (!login.includes('praisonai-triage')) return false;
return (c.body || '').includes('Claude finished');
}
function hasRecentClaudeTrigger(comments, minutes = 35) {
const cutoff = Date.now() - minutes * 60 * 1000;
return comments.some((c) => {
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;
if (isClaudeTriggerNoise(c)) return false;
if (!(c.body || '').includes('@claude')) return false;
return new Date(c.created_at).getTime() > cutoff;
});
}
function isConflictRebaseTriggerComment(c) {
if (!AUTO_ACTORS.includes(c.user.login)) return false;
const body = (c.body || '').toLowerCase();
return body.includes('@claude') && body.includes('merge conflict');
}
function isConflictRebaseCompletionComment(c) {
const login = (c.user?.login || '').toLowerCase();
if (!login.includes('praisonai-triage') && !login.includes('github-actions')) return false;
const body = (c.body || '').toLowerCase();
return (
body.includes('rebase complete') ||
body.includes('rebase onto') ||
(body.includes('conflict') && body.includes('resolved'))
);
}
function conflictRebaseQuiescent(comments, headPushedAt) {
const conflictTriggers = comments.filter(isConflictRebaseTriggerComment);
if (conflictTriggers.length === 0) return true;
const latestConflict = conflictTriggers.reduce((a, b) =>
new Date(a.created_at) > new Date(b.created_at) ? a : b
);
const conflictTime = new Date(latestConflict.created_at).getTime();
const rebaseDone = comments.some(
(c) =>
new Date(c.created_at).getTime() > conflictTime && isConflictRebaseCompletionComment(c)
);
if (!rebaseDone) return false;
return finalClaudeCompletedOnSha(comments, headPushedAt);
}
function hasRecentConflictComment(comments, headPushedAt = null) {
const cutoff = Date.now() - CONFLICT_COOLDOWN_MS;
const hasRecentTrigger = comments.some((c) => {
if (!isConflictRebaseTriggerComment(c)) return false;
return new Date(c.created_at).getTime() > cutoff;
});
if (!hasRecentTrigger) return false;
if (headPushedAt && conflictRebaseQuiescent(comments, headPushedAt)) {
return false;
}
return true;
}
function isBotReviewer(login, userType) {
const lower = (login || '').toLowerCase();
if (userType === 'Bot') return true;
if (lower.endsWith('[bot]')) return true;
return ['coderabbit', 'qodo', 'gemini', 'copilot', 'greptile'].some((p) => lower.includes(p));
}
function latestReviewsByUser(reviews) {
const latestByUser = new Map();
for (const r of reviews) {
const login = r.user?.login;
if (!login) continue;
const prev = latestByUser.get(login);
if (!prev || new Date(r.submitted_at) > new Date(prev.submitted_at)) {
latestByUser.set(login, r);
}
}
return latestByUser;
}
function hasHumanChangesRequested(reviews) {
for (const [login, review] of latestReviewsByUser(reviews)) {
if (review.state !== 'CHANGES_REQUESTED') continue;
if (!isBotReviewer(login, review.user?.type)) return true;
}
return false;
}
function hasAnyChangesRequested(reviews) {
for (const [, review] of latestReviewsByUser(reviews)) {
if (review.state === 'CHANGES_REQUESTED') return true;
}
return false;
}
function hasFinalClaudeReviewTrigger(comments) {
return comments.some(isFinalClaudeTriggerComment);
}
function isStaleFinalAfterPush(comments, headPushedAt) {
if (!headPushedAt) return false;
const headTime = new Date(headPushedAt).getTime();
const finals = comments.filter(isFinalClaudeTriggerComment);
if (finals.length === 0) return true;
const latestFinal = finals.reduce((a, b) =>
new Date(a.created_at) > new Date(b.created_at) ? a : b
);
const finalTime = new Date(latestFinal.created_at).getTime();
if (headTime <= finalTime + 60000) return false;
const claudeRepliedAfterFinal = comments.some((c) => {
if (!isClaudeFinalReplyComment(c)) return false;
return new Date(c.created_at).getTime() >= finalTime - 60000;
});
if (claudeRepliedAfterFinal) return false;
const claudeSinceHead = comments.some((c) => {
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;
if (isClaudeTriggerNoise(c)) return false;
if (!(c.body || '').includes('@claude')) return false;
return new Date(c.created_at).getTime() >= headTime - 60000;
});
return !claudeSinceHead;
}
function needsStaleFinalRecovery(comments, headPushedAt) {
return (
hasFinalClaudeReviewTrigger(comments) &&
isStaleFinalAfterPush(comments, headPushedAt)
);
}
function shouldSkipFinalRecovery(comments, headPushedAt) {
const isStale = isStaleFinalAfterPush(comments, headPushedAt);
if (isStale) return false;
return hasRecentClaudeTrigger(comments, 35);
}
function countFinalTriggersSince(comments, sinceMs) {
return comments.filter(
(c) => isFinalClaudeTriggerComment(c) && new Date(c.created_at).getTime() > sinceMs
).length;
}
function isClaudeAutomationLogin(login) {
const lower = (login || '').toLowerCase();
return lower.includes('praisonai-triage') || lower === 'github-actions[bot]';
}
function isPushSoonAfterLatestFinal(comments, headPushedAt) {
if (!headPushedAt) return false;
const finals = comments.filter(isFinalClaudeTriggerComment);
if (finals.length === 0) return false;
const latestFinal = finals.reduce((a, b) =>
new Date(a.created_at) > new Date(b.created_at) ? a : b
);
const finalTime = new Date(latestFinal.created_at).getTime();
const headTime = new Date(headPushedAt).getTime();
if (headTime <= finalTime) return false;
return headTime - finalTime < PUSH_AFTER_FINAL_DEBOUNCE_MS;
}
/** Returns { skip: true, reason } when stale-FINAL recovery should not post. */
function shouldSkipStaleFinalRecovery(comments, headPushedAt, headPusherLogin = null) {
if (!isStaleFinalAfterPush(comments, headPushedAt)) {
return { skip: true, reason: 'not stale' };
}
if (headPusherLogin && isClaudeAutomationLogin(headPusherLogin)) {
return { skip: true, reason: 'head pushed by Claude automation' };
}
if (isPushSoonAfterLatestFinal(comments, headPushedAt)) {
return {
skip: true,
reason: 'head pushed soon after FINAL (wait for CI / batched fixes)',
};
}
const windowStart = Date.now() - STALE_FINAL_RECOVERY_WINDOW_MS;
const finalsInWindow = countFinalTriggersSince(comments, windowStart);
if (finalsInWindow >= STALE_FINAL_MAX_PER_WINDOW) {
return {
skip: true,
reason: `stale-FINAL capped (${finalsInWindow} FINAL triggers in last hour)`,
};
}
return { skip: false, reason: '' };
}
function finalClaudeCompletedOnSha(comments, headPushedAt) {
if (!hasFinalClaudeReviewTrigger(comments)) return false;
if (isStaleFinalAfterPush(comments, headPushedAt)) return false;
return true;
}
const FINAL_CLAUDE_REVIEW_BODY =
`@claude You are the FINAL architecture reviewer. If the branch is under ${config.repoFullName} (not a fork), you are able to make modifications to this branch and push directly. ${config.finalClaudeScope} Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions (lazy imports, import-time <200ms)\n${config.finalClaudeProductValue}\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items`;
async function getMergeState(github, owner, repo, prNumber) {
const query = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
mergeStateStatus
maintainerCanModify
isDraft
headRefOid
headRef { repository { nameWithOwner } }
}
}
}
`;
for (let attempt = 0; attempt < 3; attempt++) {
const result = await github.graphql(query, { owner, repo, number: prNumber });
const prGql = result.repository.pullRequest;
const status = (prGql?.mergeStateStatus || '').toUpperCase();
if (status && status !== 'UNKNOWN') {
return {
status,
isDraft: prGql.isDraft,
headRepo: prGql.headRef?.repository?.nameWithOwner,
headSha: prGql.headRefOid,
maintainerCanModify: prGql.maintainerCanModify === true,
};
}
if (attempt < 2) await sleep(10000);
}
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
return {
status: (pr.mergeable_state || '').toUpperCase(),
isDraft: pr.draft,
headRepo: pr.head.repo?.full_name,
headSha: pr.head.sha,
maintainerCanModify: pr.maintainer_can_modify === true,
};
}
async function listChecksOnSha(github, owner, repo, sha) {
const { data } = await github.rest.checks.listForRef({
owner,
repo,
ref: sha,
per_page: 100,
});
return (data.check_runs || []).filter((r) => r.head_sha === sha);
}
function listFailedChecksOnSha(runs) {
return (runs || []).filter((run) => {
if (run.status !== 'completed') return false;
return run.conclusion === 'failure';
});
}
async function allChecksGreenOnSha(github, owner, repo, sha, core) {
const runs = await listChecksOnSha(github, owner, repo, sha);
if (runs.length === 0) {
core?.info?.(`No check runs on ${sha.slice(0, 7)} — allowing (e.g. docs-only PR)`);
return true;
}
for (const run of runs) {
if (run.status !== 'completed') {
core?.info?.(`Check pending: ${run.name} (${run.status})`);
return false;
}
const ok =
['success', 'neutral', 'skipped'].includes(run.conclusion) ||
(run.conclusion === 'cancelled' && OPTIONAL_CANCELLED_CHECKS.has(run.name));
if (!ok) {
core?.info?.(`Check failed: ${run.name} (${run.conclusion})`);
return false;
}
}
return true;
}
function claudeRunBlocksPr(run, headRef) {
if (!run || run.event === 'issues') return false;
if (!headRef) return true;
return run.head_branch === headRef;
}
function hasBlockingClaudeRunForPr(runs, headRef) {
return (runs || []).some((r) => claudeRunBlocksPr(r, headRef));
}
async function hasInProgressClaudeAssistant(github, owner, repo, prNumber = null) {
try {
const { data } = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: 'claude.yml',
status: 'in_progress',
per_page: 20,
});
const runs = (data.workflow_runs || []).filter((r) => r.event !== 'issues');
if (runs.length === 0) return false;
if (prNumber == null) return runs.length > 0;
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
const headRef = pr.head.ref;
return hasBlockingClaudeRunForPr(runs, headRef);
} catch {
return false;
}
}
function isCiOnlyChange(files) {
if (!files.length) return false;
return files.every((f) => CI_ONLY_PATH_PREFIXES.some((p) => f.filename.startsWith(p)));
}
function isInternalPullRequestLink(link, owner, repo) {
if (link?.base?.repo?.full_name === `${owner}/${repo}`) return true;
const baseUrl = link?.base?.repo?.url || '';
return baseUrl.endsWith(`/repos/${owner}/${repo}`);
}
async function resolvePrNumberFromLinkedPullRequests(github, owner, repo, linked) {
const repoFull = `${owner}/${repo}`;
const internal = (linked || []).filter((l) => isInternalPullRequestLink(l, owner, repo));
for (const link of internal) {
if (!link.number) continue;
try {
const { data } = await github.rest.pulls.get({
owner,
repo,
pull_number: link.number,
});
if (data.state === 'open' && data.base?.repo?.full_name === repoFull) {
return data.number;
}
} catch (err) {
if (err.status !== 404) throw err;
}
}
return null;
}
async function resolvePrNumberFromHeadBranch(github, owner, repo, branch) {
if (!branch || branch === 'main' || branch === 'master') return null;
const { data } = await github.rest.pulls.list({
owner,
repo,
state: 'open',
head: `${owner}:${branch}`,
per_page: 1,
});
return data[0]?.number || null;
}
async function resolvePrNumberFromWorkflowRun(github, owner, repo, workflowRun) {
const fromLinked = await resolvePrNumberFromLinkedPullRequests(
github, owner, repo, workflowRun.pull_requests
);
if (fromLinked) return fromLinked;
const fromBranch = await resolvePrNumberFromHeadBranch(
github, owner, repo, workflowRun.head_branch
);
if (fromBranch) return fromBranch;
return null;
}
async function listPullFiles(github, owner, repo, prNumber) {
if (typeof github.paginate === 'function') {
return github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
}
const { data } = await github.rest.pulls.listFiles({
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
return data;
}
function countNewAgentParams(patch) {
if (!patch) return 0;
return patch
.split('\n')
.filter((l) => l.startsWith('+') && !l.startsWith('+++'))
.filter((l) => {
const t = l.slice(1).trim();
if (!t || t.startsWith('#') || t.startsWith('"""') || t.startsWith("'''")) return false;
if (/^(def|class|@|return\b|if\b|elif\b|else\b|for\b|while\b)/.test(t)) return false;
if (/^\w+\s*[:=]/.test(t) && !t.startsWith('self.')) return true;
return false;
}).length;
}
function getAgentPyChangeFromFiles(files) {
const agentFile = files.find((f) => f.filename.endsWith('praisonaiagents/agent/agent.py'));
if (!agentFile) {
return { touched: false, additions: 0, newParams: 0 };
}
return {
touched: true,
additions: agentFile.additions || 0,
newParams: countNewAgentParams(agentFile.patch || ''),
};
}
async function getAgentPyChange(github, owner, repo, prNumber) {
const files = await listPullFiles(github, owner, repo, prNumber);
return getAgentPyChangeFromFiles(files);
}
function touchesSdk(files) {
return files.some((f) => SDK_PATH_PREFIXES.some((p) => f.filename.startsWith(p)));
}
function hasManualOnlyLabel(labels) {
return labels.some((l) => MANUAL_ONLY_LABELS.has(l));
}
function sensitivePathReasons(files, labels = []) {
if (labels.includes(WORKFLOW_ONLY_LABEL) && isCiOnlyChange(files)) {
return [];
}
const reasons = [];
for (const f of files) {
if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(f.filename))) {
reasons.push(`sensitive path: ${f.filename}`);
break;
}
}
return reasons;
}
function prSizeReasons(files) {
const reasons = [];
const totalAdditions = files.reduce((sum, f) => sum + (f.additions || 0), 0);
if (totalAdditions > PR_MAX_AUTO_ADDITIONS) {
reasons.push(`PR +${totalAdditions} lines (>${PR_MAX_AUTO_ADDITIONS}) requires manual review`);
}
if (files.length > PR_MAX_AUTO_FILES) {
reasons.push(`${files.length} files changed (>${PR_MAX_AUTO_FILES}) requires manual review`);
}
return reasons;
}
function missingTestsReason(files) {
const productAdds = files.filter(
(f) =>
SDK_PATH_PREFIXES.some((p) => f.filename.startsWith(p)) &&
f.filename.startsWith('src/praisonaiui/') &&
f.filename.endsWith('.py') &&
!f.filename.endsWith('__init__.py') &&
(f.additions || 0) > 0
);
if (productAdds.length === 0) return null;
const hasTestChange = files.some(
(f) => /\/tests?\//.test(f.filename) || /test_.*\.py$/.test(f.filename) || /_test\.py$/.test(f.filename)
);
if (!hasTestChange) return 'Product code added without test file changes — requires manual review';
return null;
}
function secretScanReasons(files) {
for (const f of files) {
if (/\/tests?\//.test(f.filename) || /test_.*\.py$/.test(f.filename)) continue;
const patch = f.patch || '';
if (!patch) continue;
for (const pattern of SECRET_PATTERNS) {
if (pattern.test(patch)) {
return [`possible secret in diff (${f.filename}) — requires manual review`];
}
}
}
return [];
}
async function sdkTestChecksReason(github, owner, repo, sha, files, core) {
if (!touchesSdk(files)) return null;
const { data } = await github.rest.checks.listForRef({
owner,
repo,
ref: sha,
per_page: 100,
});
const runs = (data.check_runs || []).filter((r) => r.head_sha === sha);
if (runs.length === 0) {
return 'SDK code changed but no CI checks on HEAD';
}
const testRuns = runs.filter((r) => REQUIRED_SDK_CHECK_PATTERNS.some((p) => p.test(r.name || '')));
if (testRuns.length === 0) {
return 'SDK code changed but no test check runs on HEAD';
}
core?.info?.(`SDK test checks on HEAD: ${testRuns.map((r) => r.name).join(', ')}`);
return null;
}
function manualReviewReasonForAgentPy(agentChange) {
if (!agentChange.touched) return null;
if (agentChange.additions > AGENT_PY_MAX_AUTO_LINES) {
return `agent.py +${agentChange.additions} lines (>${AGENT_PY_MAX_AUTO_LINES}) requires manual review`;
}
if (agentChange.newParams > 0) {
return `agent.py adds ${agentChange.newParams} new Agent param(s) — requires manual review`;
}
return null;
}
async function listAllComments(github, owner, repo, issueNumber) {
if (typeof github.paginate === 'function') {
return github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
}
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
return data;
}
async function getHeadCommitDate(github, owner, repo, prNumber) {
try {
let commits;
if (typeof github.paginate === 'function') {
commits = await github.paginate(github.rest.pulls.listCommits, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
} else {
const { data } = await github.rest.pulls.listCommits({
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
commits = data;
}
const last = commits[commits.length - 1];
return last?.commit?.committer?.date || last?.commit?.author?.date || null;
} catch {
return null;
}
}
async function loadPrContext(github, owner, repo, prNumber) {
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: prNumber });
const comments = await listAllComments(github, owner, repo, prNumber);
const { data: reviews } = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
const mergeState = await getMergeState(github, owner, repo, prNumber);
const headSha = mergeState.headSha || pr.head.sha;
const headCommitDate = await getHeadCommitDate(github, owner, repo, prNumber);
const headPushedAt = headCommitDate || pr.updated_at;
return {
pr,
issue,
comments,
reviews,
mergeState,
headSha,
headPushedAt,
labels: issue.labels.map((l) => l.name),
baseRepo: `${owner}/${repo}`,
};
}
async function evaluatePipelineQuiescent(github, owner, repo, prNumber, core, options = {}) {
const {
forMergeStep = false,
skipGlobalClaudeRunCheck = false,
skipRecentClaudeCooldown = false,
} = options;
const ctx = await loadPrContext(github, owner, repo, prNumber);
const reasons = [];
if (ctx.pr.draft) reasons.push('draft');
if (ctx.pr.state !== 'open') reasons.push('not open');
if (ctx.labels.includes('auto-merged-by-gate')) reasons.push('already merged by gate');
if (ctx.labels.includes('no-auto-merge')) reasons.push('no-auto-merge label');
if (ctx.labels.includes('claude-conflict-pending')) reasons.push('claude-conflict-pending');
if (!forMergeStep && ctx.labels.includes('claude-merge-gate-active')) {
reasons.push('claude-merge-gate-active');
}
const { status, headRepo, maintainerCanModify } = ctx.mergeState;
if (!ALLOWED_MERGE_STATES.has(status)) reasons.push(`mergeState=${status}`);
if (headRepo && headRepo !== ctx.baseRepo) {
reasons.push('fork PR');
}
if (hasRecentConflictComment(ctx.comments, ctx.headPushedAt)) {
reasons.push('recent merge-conflict @claude');
}
if (!skipRecentClaudeCooldown && hasRecentClaudeTrigger(ctx.comments, 35)) {
const verdictOnHead = findMergeGateVerdict(ctx.comments, null, ctx.headPushedAt) !== null;
if (!verdictOnHead) reasons.push('recent @claude within 35min');
}
if (!skipGlobalClaudeRunCheck && (await hasInProgressClaudeAssistant(github, owner, repo, prNumber))) {
reasons.push('claude.yml in progress');
}
const checksOk = await allChecksGreenOnSha(github, owner, repo, ctx.headSha, core);
if (!checksOk) reasons.push('CI not green on HEAD');
if (!finalClaudeCompletedOnSha(ctx.comments, ctx.headPushedAt)) {
if (!hasFinalClaudeReviewTrigger(ctx.comments)) {
reasons.push('no FINAL Claude review trigger');
} else if (isStaleFinalAfterPush(ctx.comments, ctx.headPushedAt)) {
reasons.push('stale FINAL after new commits (needs @claude re-review)');
} else {
reasons.push('FINAL Claude not complete on HEAD');
}
}
if (hasAnyChangesRequested(ctx.reviews)) reasons.push('CHANGES_REQUESTED review');
if (hasManualOnlyLabel(ctx.labels)) {
const manualLabel = ctx.labels.find((l) => MANUAL_ONLY_LABELS.has(l));
reasons.push(`manual-only label: ${manualLabel}`);
}
const pullFiles = await listPullFiles(github, owner, repo, prNumber);
if (config.agentPyChecks) {
const agentChange = getAgentPyChangeFromFiles(pullFiles);
const agentManual = manualReviewReasonForAgentPy(agentChange);
if (agentManual) reasons.push(agentManual);
}
reasons.push(...sensitivePathReasons(pullFiles, ctx.labels));
reasons.push(...prSizeReasons(pullFiles));
reasons.push(...secretScanReasons(pullFiles));
const testsReason = missingTestsReason(pullFiles);
if (testsReason) reasons.push(testsReason);
const sdkCheckReason = await sdkTestChecksReason(github, owner, repo, ctx.headSha, pullFiles, core);
if (sdkCheckReason) reasons.push(sdkCheckReason);
if (ctx.pr.mergeable === false) reasons.push('not mergeable');
const ready = reasons.length === 0;
return {
ready,
reasons,
headSha: ctx.headSha,
prNumber,
};
}
async function listPrNumbersForMergeGateScan(github, owner, repo, core) {
let mergeReadyIssues;
if (typeof github.paginate === 'function') {
mergeReadyIssues = await github.paginate(github.rest.issues.listForRepo, {
owner,
repo,
state: 'open',
labels: MERGE_READY_LABEL,
per_page: 100,
});
} else {
({ data: mergeReadyIssues } = await github.rest.issues.listForRepo({
owner,
repo,
state: 'open',
labels: MERGE_READY_LABEL,
per_page: 100,
}));
}
const mergeReady = mergeReadyIssues
.filter((issue) => issue.pull_request)
.map((issue) => issue.number)
.sort((a, b) => a - b);
if (mergeReady.length > 0) {
core?.info?.(
`Merge gate scan: ${mergeReady.length} ${MERGE_READY_LABEL} PR(s) (skipping unlabelled open PRs)`
);
return mergeReady;
}
core?.info?.(`No ${MERGE_READY_LABEL} PRs — scanning oldest open PRs`);
const prs =
typeof github.paginate === 'function'
? await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
sort: 'created',
direction: 'asc',
})
: (await github.rest.pulls.list({
owner,
repo,
state: 'open',
per_page: 100,
sort: 'created',
direction: 'asc',
})).data;
return prs.map((pr) => pr.number);
}
async function selectMergeGateCandidates(github, owner, repo, prNumbers, maxCandidates, core) {
const readyList = [];
const skipped = [];
for (const num of prNumbers) {
if (readyList.length >= maxCandidates) {
core?.info?.(`Found ${maxCandidates} candidate(s) — skipping remaining PR scans`);
break;
}
const result = await evaluatePipelineQuiescent(github, owner, repo, num, core);
if (result.ready) {
readyList.push({ pr_number: num, head_sha: result.headSha });
} else {
skipped.push({ pr: num, reasons: result.reasons });
}
}
readyList.sort((a, b) => a.pr_number - b.pr_number);
return {
candidates: readyList.slice(0, maxCandidates),
skipped,
};
}
const AUTOMATED_FALLBACK_MARKER = 'Automated fallback —';
function isAutomatedFallbackVerdict(body) {
return (body || '').includes(AUTOMATED_FALLBACK_MARKER);
}
function findMergeGateVerdict(comments, minCreatedAt = null, headPushedAt = null, options = {}) {
const { excludeAutomatedFallback = false } = options || {};
const minTime = minCreatedAt ? new Date(minCreatedAt).getTime() : 0;
const headTime = headPushedAt ? new Date(headPushedAt).getTime() - 60000 : 0;
const gateComments = comments
.filter((c) => {
if (!(c.body || '').includes('MERGE_GATE_VERDICT:')) return false;
if (excludeAutomatedFallback && isAutomatedFallbackVerdict(c.body)) return false;
const created = new Date(c.created_at).getTime();
if (minTime && created < minTime) return false;
if (headTime && created < headTime) return false;
return true;
})
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
if (gateComments.length === 0) return null;
const body = gateComments[0].body || '';
if (body.includes('MERGE_GATE_VERDICT: APPROVE')) return 'APPROVE';
if (body.includes('MERGE_GATE_VERDICT: BLOCK')) return 'BLOCK';
return null;
}
module.exports = {
CLAUDE_TRIGGER_LOGINS,
AUTO_ACTORS,
isFinalClaudeTriggerComment,
isClaudeTriggerNoise,
isClaudeFinalReplyComment,
hasRecentClaudeTrigger,
hasRecentConflictComment,
isConflictRebaseTriggerComment,
isConflictRebaseCompletionComment,
conflictRebaseQuiescent,
hasHumanChangesRequested,
hasAnyChangesRequested,
hasFinalClaudeReviewTrigger,
finalClaudeCompletedOnSha,
getMergeState,
OPTIONAL_CANCELLED_CHECKS,
listChecksOnSha,
listFailedChecksOnSha,
allChecksGreenOnSha,
hasInProgressClaudeAssistant,
claudeRunBlocksPr,
hasBlockingClaudeRunForPr,
isCiOnlyChange,
isInternalPullRequestLink,
resolvePrNumberFromLinkedPullRequests,
resolvePrNumberFromHeadBranch,
resolvePrNumberFromWorkflowRun,
WORKFLOW_ONLY_LABEL,
getAgentPyChange,
getAgentPyChangeFromFiles,
manualReviewReasonForAgentPy,
countNewAgentParams,
listPullFiles,
touchesSdk,
hasManualOnlyLabel,
sensitivePathReasons,
prSizeReasons,
missingTestsReason,
secretScanReasons,
sdkTestChecksReason,
listAllComments,
getHeadCommitDate,
isStaleFinalAfterPush,
needsStaleFinalRecovery,
shouldSkipFinalRecovery,
shouldSkipStaleFinalRecovery,
isClaudeAutomationLogin,
isPushSoonAfterLatestFinal,
countFinalTriggersSince,
STALE_FINAL_RECOVERY_WINDOW_MS,
STALE_FINAL_MAX_PER_WINDOW,
FINAL_CLAUDE_REVIEW_BODY,
loadPrContext,
evaluatePipelineQuiescent,
MERGE_READY_LABEL,
listPrNumbersForMergeGateScan,
selectMergeGateCandidates,
isAutomatedFallbackVerdict,
AUTOMATED_FALLBACK_MARKER,
findMergeGateVerdict,
};
@@ -0,0 +1,82 @@
#!/usr/bin/env node
/**
* Run: node .github/scripts/pipeline-status-selftest.js
*/
const ps = require('./pipeline-status.js');
const mg = require('./merge-gate.js');
const config = require('./gate-config.js');
let failed = 0;
function assert(name, cond) {
if (!cond) {
console.error('FAIL:', name);
failed++;
} else {
console.log('ok:', name);
}
}
const kickComments = [
{ user: { login: 'MervinPraison' }, body: '@coderabbitai review' },
{ user: { login: 'MervinPraison' }, body: '/review' },
];
const finalComment = {
user: { login: 'MervinPraison' },
body: '@claude You are the FINAL architecture reviewer.',
created_at: '2026-06-26T10:00:00Z',
};
assert(
'reviews pending when not kicked',
ps.deriveStage([], { ready: false, reasons: ['no FINAL Claude review trigger'] })
=== 'pipeline/reviews-pending'
);
assert(
'final pending after kick',
ps.deriveStage(kickComments, { ready: false, reasons: ['no FINAL Claude review trigger'] })
=== 'pipeline/final-claude-pending'
);
assert(
'awaiting merge gate after bot review comment',
ps.deriveStage(
[
{ user: { login: 'coderabbitai[bot]' }, body: 'review summary' },
finalComment,
],
{ ready: false, reasons: ['recent @claude within 35min'] }
) === 'pipeline/awaiting-merge-gate'
);
assert('review bot logins configured', config.reviewBotLogins.includes('coderabbitai[bot]'));
assert(
'merge ready',
ps.deriveStage([...kickComments, finalComment], { ready: true, reasons: [] })
=== 'pipeline/merge-ready'
);
const blockers = ps.deriveBlockerLabels({
ready: false,
reasons: ['CI not green on HEAD', 'SDK code added without test file changes — requires manual review'],
});
assert('maps ci blocker', blockers.includes('pipeline/blocked:ci'));
assert('maps manual blocker', blockers.includes('pipeline/blocked:manual-review'));
assert(
'internal link matches upstream base',
mg.isInternalPullRequestLink(
{ base: { repo: { full_name: 'MervinPraison/PraisonAI' } } },
'MervinPraison',
'PraisonAI'
)
);
assert(
'fork sync link rejected',
!mg.isInternalPullRequestLink(
{ number: 21, base: { repo: { full_name: 'Milkmange/PraisonAI' } } },
'MervinPraison',
'PraisonAI'
)
);
process.exit(failed ? 1 : 0);
+264
View File
@@ -0,0 +1,264 @@
/**
* Sync pipeline stage/blocker labels on open PRs.
* @see .github/workflows/pipeline-status-sync.yml
*/
const mergeGate = require('./merge-gate.js');
const chain = require('./bot-pr-review-chain.js');
const ciFix = require('./ci-failure-claude.js');
const config = require('./gate-config.js');
const PIPELINE_PREFIX = 'pipeline/';
const STAGE_LABELS = [
'pipeline/reviews-pending',
'pipeline/final-claude-pending',
'pipeline/awaiting-merge-gate',
'pipeline/merge-ready',
'pipeline/merged',
];
const BLOCKER_LABELS = [
'pipeline/blocked:ci',
'pipeline/blocked:conflict',
'pipeline/blocked:manual-review',
'pipeline/blocked:cooldown',
'pipeline/blocked:stale-final',
'pipeline/blocked:no-final',
];
const ALL_PIPELINE_LABELS = [...STAGE_LABELS, ...BLOCKER_LABELS];
const LABEL_SPECS = [
{ name: 'pipeline/reviews-pending', color: 'fbca04', description: 'Waiting for CodeRabbit/Qodo/Copilot reviews' },
{ name: 'pipeline/final-claude-pending', color: 'd4c5f9', description: 'Reviews done; waiting for FINAL @claude' },
{ name: 'pipeline/awaiting-merge-gate', color: 'c5def5', description: 'FINAL done; waiting for merge gate / CI' },
{ name: 'pipeline/merge-ready', color: '0e8a16', description: 'Eligible for merge gate auto-merge' },
{ name: 'pipeline/merged', color: '6f42c1', description: 'Merged via pipeline' },
{ name: 'pipeline/blocked:ci', color: 'd93f0b', description: 'Blocked: CI not green on HEAD' },
{ name: 'pipeline/blocked:conflict', color: 'b60205', description: 'Blocked: merge conflict or rebase pending' },
{ name: 'pipeline/blocked:manual-review', color: 'e99695', description: 'Blocked: requires manual review' },
{ name: 'pipeline/blocked:cooldown', color: 'fef2c0', description: 'Blocked: post-push or @claude cooldown' },
{ name: 'pipeline/blocked:stale-final', color: 'f9d0c4', description: 'Blocked: FINAL stale after new commits' },
{ name: 'pipeline/blocked:no-final', color: 'ededed', description: 'Blocked: no FINAL @claude trigger yet' },
];
function reviewChainStarted(comments) {
if (chain.chainKickPosted(comments)) return true;
return comments.some((c) => config.reviewBotLogins.includes(c.user?.login));
}
function deriveStage(comments, evalResult) {
if (evalResult.ready) return 'pipeline/merge-ready';
if (!reviewChainStarted(comments)) return 'pipeline/reviews-pending';
if (!mergeGate.hasFinalClaudeReviewTrigger(comments)) return 'pipeline/final-claude-pending';
return 'pipeline/awaiting-merge-gate';
}
function reasonToBlockerLabel(reason) {
const r = (reason || '').toLowerCase();
if (r.includes('ci not green') || r.includes('sdk code changed but no ci')) return 'pipeline/blocked:ci';
if (
r.includes('conflict') ||
r.includes('mergestate=dirty') ||
r.includes('not mergeable')
) return 'pipeline/blocked:conflict';
if (
r.includes('manual') ||
r.includes('requires manual') ||
r.includes('sensitive path') ||
r.includes('no-auto-merge') ||
r.includes('manual-only label') ||
r.includes('without test') ||
r.includes('product code') ||
r.includes('files changed') ||
r.includes('possible secret')
) return 'pipeline/blocked:manual-review';
if (r.includes('recent @claude') || r.includes('post-push buffer')) return 'pipeline/blocked:cooldown';
if (r.includes('stale final')) return 'pipeline/blocked:stale-final';
if (r.includes('no final claude')) return 'pipeline/blocked:no-final';
if (r.includes('final claude not complete')) return 'pipeline/blocked:stale-final';
if (r.includes('claude.yml in progress') || r.includes('claude-merge-gate-active')) {
return null;
}
return null;
}
function deriveBlockerLabels(evalResult) {
if (evalResult.ready) return [];
const labels = new Set();
for (const reason of evalResult.reasons || []) {
const mapped = reasonToBlockerLabel(reason);
if (mapped && mapped.startsWith('pipeline/blocked:')) labels.add(mapped);
}
return [...labels];
}
function computePipelineLabels(comments, evalResult) {
const stage = deriveStage(comments, evalResult);
const blockers = stage === 'pipeline/merge-ready' ? [] : deriveBlockerLabels(evalResult);
return { stage, blockers, all: [stage, ...blockers] };
}
async function ensurePipelineLabels(github, owner, repo, core) {
let existing;
if (typeof github.paginate === 'function') {
existing = await github.paginate(github.rest.issues.listLabelsForRepo, {
owner,
repo,
per_page: 100,
});
} else {
({ data: existing } = await github.rest.issues.listLabelsForRepo({
owner,
repo,
per_page: 100,
}));
}
const names = new Set(existing.map((l) => l.name));
for (const spec of LABEL_SPECS) {
if (names.has(spec.name)) continue;
try {
await github.rest.issues.createLabel({
owner,
repo,
name: spec.name,
color: spec.color,
description: spec.description,
});
core?.info?.(`Created label ${spec.name}`);
} catch (err) {
if (err.status !== 422) throw err;
core?.info?.(`Label ${spec.name} already exists`);
}
}
}
async function syncPipelineLabels(github, owner, repo, prNumber, core) {
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
if (ctx.pr.state !== 'open') return { synced: false, reason: 'not_open' };
const evalResult = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core
);
const { stage, blockers, all } = computePipelineLabels(ctx.comments, evalResult);
const current = ctx.labels.filter((l) => l.startsWith(PIPELINE_PREFIX));
const desired = new Set(all);
const toRemove = current.filter((l) => !desired.has(l) && l !== 'pipeline/merged');
const toAdd = all.filter((l) => !current.includes(l));
for (const name of toRemove) {
try {
await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name });
} catch (err) {
if (err.status !== 404) throw err;
}
}
if (toAdd.length) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: toAdd,
});
}
core?.info?.(`PR #${prNumber}: stage=${stage} blockers=[${blockers.join(', ')}]`);
const ciNotGreen = (evalResult.reasons || []).some((r) =>
r.toLowerCase().includes('ci not green')
);
if (ciNotGreen || blockers.includes('pipeline/blocked:ci')) {
await ciFix.maybeTriggerCiFixClaude(github, owner, repo, prNumber, core);
} else {
await ciFix.maybeClearCiFixLabel(
github, owner, repo, prNumber, ctx.labels, ctx.headSha, core
);
}
return {
synced: true,
stage,
blockers,
ready: evalResult.ready,
reasons: evalResult.reasons,
labels: ctx.labels,
createdAt: new Date(ctx.pr.created_at).getTime(),
};
}
async function dispatchMergeGateForOldestReady(github, owner, repo, readyCandidates, core) {
if (!readyCandidates.length) return 0;
readyCandidates.sort((a, b) => a.createdAt - b.createdAt);
for (const cand of readyCandidates) {
if ((cand.labels || []).includes('claude-merge-gate-active')) {
core?.info?.(`Skip dispatch PR #${cand.prNumber}: merge gate already active`);
continue;
}
await github.rest.repos.createDispatchEvent({
owner,
repo,
event_type: 'claude-merge-gate',
client_payload: { pr_number: cand.prNumber },
});
core?.info?.(`Dispatched merge gate for ready PR #${cand.prNumber}`);
return cand.prNumber;
}
return 0;
}
async function syncOpenPullRequests(github, owner, repo, options, core) {
const { maxPrs = 20, dispatchMergeGate = true } = options || {};
await ensurePipelineLabels(github, owner, repo, core);
let prs;
if (maxPrs <= 100) {
({ data: prs } = await github.rest.pulls.list({
owner,
repo,
state: 'open',
per_page: 100,
}));
} else {
prs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
}
let synced = 0;
const readyCandidates = [];
for (const pr of prs) {
if (synced >= maxPrs) break;
if (pr.draft) continue;
if (pr.head?.repo?.full_name && pr.head.repo.full_name !== `${owner}/${repo}`) continue;
const result = await syncPipelineLabels(github, owner, repo, pr.number, core);
if (result.ready) {
readyCandidates.push({
prNumber: pr.number,
createdAt: result.createdAt || new Date(pr.created_at).getTime(),
labels: result.labels || [],
});
}
synced += 1;
}
let dispatched = 0;
if (dispatchMergeGate && readyCandidates.length) {
dispatched = await dispatchMergeGateForOldestReady(
github, owner, repo, readyCandidates, core
);
}
core?.info?.(`Pipeline label sync complete (${synced} PR(s), dispatched=${dispatched || 'none'})`);
return synced;
}
module.exports = {
STAGE_LABELS,
BLOCKER_LABELS,
ALL_PIPELINE_LABELS,
deriveStage,
deriveBlockerLabels,
computePipelineLabels,
ensurePipelineLabels,
syncPipelineLabels,
syncOpenPullRequests,
dispatchMergeGateForOldestReady,
};
+298
View File
@@ -0,0 +1,298 @@
/**
* PR review pipeline: CodeRabbit / Greptile (+ optional Qodo/Gemini) → Claude (FINAL).
* Copilot step optional — set REVIEW_CHAIN_SKIP_COPILOT=1 or skipCopilot: true to bypass.
* @see .github/workflows/auto-pr-comment.yml
*/
function isSkipCopilot(options = {}) {
if (options.skipCopilot === true) return true;
if (options.skipCopilot === false) return false;
return process.env.REVIEW_CHAIN_SKIP_COPILOT === '1';
}
const COPILOT_TRIGGER_LOGINS = new Set(['MervinPraison', 'github-actions[bot]']);
const CLAUDE_TRIGGER_LOGINS = new Set(['MervinPraison', 'github-actions[bot]']);
const REQUIRED_PRIOR = ['coderabbit', 'greptile'];
const OPTIONAL_PRIOR = ['qodo', 'gemini'];
const OPTIONAL_PRIOR_WAIT_MS = 20 * 60 * 1000;
function loginOf(item) {
return (item?.user?.login || '').toLowerCase();
}
function bodyOf(item) {
return item?.body || '';
}
function hasCoderabbitSummary(comments) {
return comments.some(
(c) =>
loginOf(c).includes('coderabbit') &&
bodyOf(c).toLowerCase().includes('summarize by coderabbit')
);
}
function hasGreptileReview(comments) {
return comments.some((c) => {
if (!loginOf(c).includes('greptile')) return false;
const body = bodyOf(c);
return body.includes('Greptile Summary') || body.includes('<h3>Greptile Summary</h3>') || body.length > 150;
});
}
function hasQodoReview(comments, reviews = []) {
if (comments.some((c) => loginOf(c).includes('qodo'))) return true;
return reviews.some((r) => loginOf(r).includes('qodo'));
}
function hasGeminiReview(comments, reviews = []) {
if (reviews.some((r) => loginOf(r).includes('gemini'))) return true;
return comments.some((c) => {
if (loginOf(c).includes('gemini')) return true;
const body = bodyOf(c);
return (
body.includes('Review completed by Gemini CLI') ||
body.includes('The fix is ready for review. Please test')
);
});
}
const PRIOR_CHECKS = {
coderabbit: (comments) => hasCoderabbitSummary(comments),
greptile: (comments) => hasGreptileReview(comments),
qodo: (comments, reviews) => hasQodoReview(comments, reviews),
gemini: (comments, reviews) => hasGeminiReview(comments, reviews),
};
function priorReviewerStatus(comments, reviews = []) {
const status = {};
for (const [id, check] of Object.entries(PRIOR_CHECKS)) {
status[id] = check(comments, reviews);
}
return status;
}
function priorReviewersReady(comments, reviews = [], options = {}) {
const waitMs = options.optionalWaitMs ?? OPTIONAL_PRIOR_WAIT_MS;
const prCreatedAt = options.prCreatedAt || null;
const status = priorReviewerStatus(comments, reviews);
const missingRequired = REQUIRED_PRIOR.filter((id) => !status[id]);
if (missingRequired.length) {
return { ready: false, reason: `waiting for ${missingRequired.join(', ')}`, status };
}
const missingOptional = OPTIONAL_PRIOR.filter((id) => !status[id]);
if (missingOptional.length && prCreatedAt) {
const age = Date.now() - new Date(prCreatedAt).getTime();
if (age < waitMs) {
return {
ready: false,
reason: `waiting for optional ${missingOptional.join(', ')} (${Math.round((waitMs - age) / 60000)}m left)`,
status,
};
}
}
return { ready: true, reason: '', status };
}
function copilotTriggerComment(comments) {
return comments.find(
(c) =>
bodyOf(c).includes('@copilot') &&
COPILOT_TRIGGER_LOGINS.has(c.user?.login) &&
!bodyOf(c).toLowerCase().includes('@claude')
);
}
function copilotTriggered(comments) {
return Boolean(copilotTriggerComment(comments));
}
function copilotReviewReady(comments, reviews = []) {
const trigger = copilotTriggerComment(comments);
if (!trigger) {
return { ready: false, reason: 'copilot not triggered yet' };
}
const triggerTime = new Date(trigger.created_at).getTime();
const copilotComment = comments.some((c) => {
if (c === trigger) return false;
if (new Date(c.created_at).getTime() < triggerTime) return false;
const login = loginOf(c);
return (
login.includes('copilot') ||
c.user?.login === 'Copilot' ||
c.user?.login === 'copilot-swe-agent'
);
});
const copilotReview = reviews.some((r) => {
if (new Date(r.submitted_at).getTime() < triggerTime) return false;
return loginOf(r).includes('copilot');
});
if (copilotComment || copilotReview) {
return { ready: true, reason: '' };
}
return { ready: false, reason: 'awaiting copilot review' };
}
function isFinalClaudeTriggerComment(c) {
const body = bodyOf(c).toLowerCase();
if (!CLAUDE_TRIGGER_LOGINS.has(c.user?.login)) return false;
if (!body.includes('@claude')) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
}
function claudeFinalAlreadyTriggered(comments) {
return comments.some(isFinalClaudeTriggerComment);
}
function claudeFinalReady(comments, reviews = [], options = {}) {
if (claudeFinalAlreadyTriggered(comments)) {
return { ready: false, reason: 'claude FINAL already triggered', already: true };
}
const prior = priorReviewersReady(comments, reviews, options);
if (!prior.ready) {
return { ready: false, reason: prior.reason };
}
if (isSkipCopilot(options)) {
return { ready: true, reason: 'copilot skipped', copilotSkipped: true };
}
const copilot = copilotReviewReady(comments, reviews);
if (copilot.ready) {
return { ready: true, reason: '' };
}
if (options.allowCopilotTimeout && copilotTriggered(comments)) {
return { ready: true, reason: 'copilot timeout fallback', copilotSkipped: true };
}
return { ready: false, reason: copilot.reason };
}
/** Post @copilot when prior reviewers are ready, then @claude FINAL when Copilot done (or timeout). */
async function advanceReviewChain(github, owner, repo, prNumber, finalBody, core, options = {}) {
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
const chainOpts = {
prCreatedAt: pr.created_at,
optionalWaitMs: options.optionalWaitMs ?? 0,
};
const copilot = options.skipCopilot
? { triggered: false, reason: 'skipped' }
: await maybeTriggerCopilot(github, owner, repo, prNumber, core, chainOpts);
const claude = await maybeTriggerClaudeFinal(
github, owner, repo, prNumber, finalBody, core,
{ ...options, ...chainOpts }
);
return { copilot, claude };
}
async function maybeTriggerClaudeFinal(github, owner, repo, prNumber, finalBody, core, options = {}) {
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
const { comments, reviews } = await listCommentsAndReviews(github, owner, repo, prNumber);
const gate = claudeFinalReady(comments, reviews, {
prCreatedAt: pr.created_at,
optionalWaitMs: options.optionalWaitMs,
allowCopilotTimeout: options.allowCopilotTimeout !== false,
});
if (!gate.ready) {
core?.info?.(`PR #${prNumber}: skip Claude FINAL — ${gate.reason}`);
return { posted: false, reason: gate.reason };
}
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: finalBody,
});
const note = gate.copilotSkipped ? ' (Copilot timeout fallback)' : '';
core?.info?.(`Posted Claude FINAL on PR #${prNumber}${note}`);
return { posted: true, reason: '' };
}
const COPILOT_REVIEW_BODY =
'@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, CodeRabbit, Gemini, and Greptile first — incorporate their findings.\n\nReview areas:\n1. **Bloat check**: Are changes minimal and focused? Any unnecessary code or scope creep?\n2. **Security**: Any hardcoded secrets, unsafe eval/exec, missing input validation?\n3. **Performance**: Any module-level heavy imports? Hot-path regressions?\n4. **Tests**: Are tests included? Do they cover the changes adequately?\n5. **Backward compat**: Any public API changes without deprecation?\n6. **Code quality**: DRY violations, naming conventions, error handling?\n7. **Address reviewer feedback**: If Qodo, CodeRabbit, Gemini, or Greptile flagged valid issues, include them in your review\n8. Suggest specific improvements with code examples where possible';
async function listCommentsAndReviews(github, owner, repo, prNumber) {
let comments;
if (typeof github.paginate === 'function') {
comments = await github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: prNumber,
per_page: 100,
});
} else {
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
per_page: 100,
});
comments = data;
}
const { data: reviews } = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
return { comments, reviews };
}
async function maybeTriggerCopilot(github, owner, repo, prNumber, core, options = {}) {
const { comments, reviews } = await listCommentsAndReviews(github, owner, repo, prNumber);
if (copilotTriggered(comments)) {
core?.info?.(`Copilot already triggered on PR #${prNumber}`);
return { triggered: false, reason: 'already_triggered' };
}
const prior = priorReviewersReady(comments, reviews, options);
if (!prior.ready) {
core?.info?.(`PR #${prNumber}: not ready for Copilot — ${prior.reason}`);
return { triggered: false, reason: prior.reason };
}
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: COPILOT_REVIEW_BODY,
});
core?.info?.(`Posted @copilot on PR #${prNumber} after prior reviewers`);
return { triggered: true };
}
async function pollCopilotResponse(github, owner, repo, prNumber, options = {}) {
const maxAttempts = options.maxAttempts ?? 20;
const delayMs = options.delayMs ?? 30000;
for (let i = 0; i < maxAttempts; i += 1) {
const { comments, reviews } = await listCommentsAndReviews(github, owner, repo, prNumber);
const copilot = copilotReviewReady(comments, reviews);
if (copilot.ready) {
return { ready: true, reason: '' };
}
if (i < maxAttempts - 1) {
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
return { ready: false, reason: 'copilot timeout' };
}
module.exports = {
isSkipCopilot,
REQUIRED_PRIOR,
OPTIONAL_PRIOR,
COPILOT_REVIEW_BODY,
priorReviewerStatus,
priorReviewersReady,
copilotTriggered,
copilotReviewReady,
claudeFinalReady,
claudeFinalAlreadyTriggered,
isFinalClaudeTriggerComment,
maybeTriggerCopilot,
maybeTriggerClaudeFinal,
advanceReviewChain,
pollCopilotResponse,
hasGeminiReview,
listCommentsAndReviews,
};
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env node
/**
* Run: node .github/scripts/release-gate-selftest.js
*/
const rg = require('./release-gate.js');
const config = require('./gate-config.js');
let failed = 0;
function assert(name, cond) {
if (!cond) {
console.error('FAIL:', name);
failed += 1;
} else {
console.log('ok:', name);
}
}
assert('package name is aiui', config.pypiPackageName === 'aiui');
assert('bumpPatch works', rg.bumpPatch('0.3.127') === '0.3.128');
try {
const versions = rg.readVersionsFromTree(process.cwd());
assert('reads current version', /^\d+\.\d+\.\d+$/.test(versions.current));
assert('computes target patch', versions.target === rg.bumpPatch(versions.current));
} catch (err) {
console.error('FAIL: readVersionsFromTree', err.message);
failed += 1;
}
assert('PACKAGE_PATHS includes pyproject', rg.PACKAGE_PATHS.includes('pyproject.toml'));
process.exit(failed ? 1 : 0);
+205
View File
@@ -0,0 +1,205 @@
/**
* Release gate preflight — path changes, CI SHA, dedupe, PyPI version checks (PraisonAIUI).
*/
const https = require('https');
const config = require('./gate-config.js');
const PACKAGE_PATHS = config.packagePaths;
const ACTIVE_RELEASE_STATUSES = new Set([
'queued', 'in_progress', 'waiting', 'pending', 'requested',
]);
function bumpPatch(version) {
const parts = version.split('.');
if (parts.length !== 3) throw new Error(`Invalid version: ${version}`);
return `${parts[0]}.${parts[1]}.${Number(parts[2]) + 1}`;
}
function readVersionsFromTree(root = '.') {
const fs = require('fs');
const path = require('path');
const toml = fs.readFileSync(path.join(root, 'pyproject.toml'), 'utf8');
const match = toml.match(/^version\s*=\s*"([^"]+)"/m);
if (!match) throw new Error('Could not read aiui version from pyproject.toml');
const current = match[1];
const target = bumpPatch(current);
return { current, target, packageName: config.pypiPackageName };
}
function pypiVersionExists(packageName, version) {
return new Promise((resolve) => {
const req = https.get(
`https://pypi.org/pypi/${packageName}/${version}/json`,
{ timeout: 15000 },
(res) => {
res.resume();
resolve(res.statusCode === 200);
}
);
req.on('error', () => resolve(false));
req.on('timeout', () => {
req.destroy();
resolve(false);
});
});
}
async function hasActiveReleaseRun(github, owner, repo) {
const runs = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: 'pypi-release.yml',
per_page: 20,
});
return runs.data.workflow_runs.some(
(r) => ACTIVE_RELEASE_STATUSES.has(r.status) && !r.conclusion
);
}
function utcDayStart(now = new Date()) {
return new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
}
async function hasSuccessfulReleaseToday(github, owner, repo, now = new Date()) {
const dayStart = utcDayStart(now);
const runs = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: 'pypi-release.yml',
status: 'completed',
per_page: 30,
});
return runs.data.workflow_runs.some(
(r) => r.conclusion === 'success' && new Date(r.created_at) >= dayStart
);
}
async function lastGreenCiSha(github, owner, repo) {
const runs = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: config.ciWorkflowFile,
branch: 'main',
status: 'completed',
per_page: 30,
});
const hit = runs.data.workflow_runs.find((r) => r.conclusion === 'success');
return hit ? hit.head_sha : '';
}
async function evaluateReleasePreflight(github, owner, repo, options, core) {
const {
headSha,
isCiTrigger = false,
bump = 'patch',
} = options;
const reasons = [];
const out = {
ready: false,
reasons,
headSha: headSha || '',
lastTag: '',
targetVersion: '',
packageName: config.pypiPackageName,
};
if (bump !== 'patch') {
reasons.push('only patch auto-release supported');
return out;
}
if (await hasActiveReleaseRun(github, owner, repo)) {
reasons.push('PyPI Release already in progress or awaiting approval');
return out;
}
const referenceTime = options.now instanceof Date ? options.now : new Date();
if (await hasSuccessfulReleaseToday(github, owner, repo, referenceTime)) {
const day = referenceTime.toISOString().slice(0, 10);
reasons.push(`already released today (UTC ${day}); max one patch release per day`);
return out;
}
let versions;
try {
versions = readVersionsFromTree();
} catch (err) {
reasons.push(err.message);
return out;
}
out.targetVersion = versions.target;
const onPypi = await pypiVersionExists(config.pypiPackageName, versions.target);
if (onPypi) {
reasons.push(`already published: ${config.pypiPackageName}==${versions.target}`);
return out;
}
const { execSync } = require('child_process');
let lastTag = '';
try {
lastTag = execSync('git describe --tags --match "v*" --abbrev=0', { encoding: 'utf8' }).trim();
} catch {
reasons.push('no v* tag found');
return out;
}
out.lastTag = lastTag;
let changed = '';
try {
changed = execSync(
`git diff --name-only ${lastTag} HEAD -- ${PACKAGE_PATHS.join(' ')}`,
{ encoding: 'utf8' }
).trim();
} catch {
reasons.push('git diff failed');
return out;
}
if (!changed) {
reasons.push(`no changes in ${PACKAGE_PATHS.join(' or ')} since ${lastTag}`);
return out;
}
if (isCiTrigger) {
let mainSha = '';
try {
mainSha = execSync('git rev-parse origin/main', { encoding: 'utf8' }).trim();
} catch {
reasons.push('could not resolve origin/main');
return out;
}
if (headSha !== mainSha) {
reasons.push(`superseded: green SHA ${headSha.slice(0, 7)} != main ${mainSha.slice(0, 7)}`);
return out;
}
out.headSha = headSha;
} else {
const evalSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
out.headSha = evalSha;
const greenSha = await lastGreenCiSha(github, owner, repo);
if (greenSha !== evalSha) {
reasons.push(`CI not green on HEAD (last green: ${greenSha ? greenSha.slice(0, 7) : 'none'})`);
return out;
}
}
out.ready = true;
out.reasons = ['ready'];
if (core) core.info(`Release preflight passed for ${out.headSha || headSha}`);
return out;
}
module.exports = {
bumpPatch,
readVersionsFromTree,
pypiVersionExists,
hasSuccessfulReleaseToday,
utcDayStart,
evaluateReleasePreflight,
lastGreenCiSha,
ACTIVE_RELEASE_STATUSES,
PACKAGE_PATHS,
};
+80 -11
View File
@@ -15,7 +15,7 @@ on:
pull_request_review:
types: [submitted]
pull_request:
types: [opened]
types: [opened, synchronize]
workflow_dispatch:
inputs:
pr_number:
@@ -34,6 +34,9 @@ env:
CLAUDE_TRIGGER_LOGINS: '["MervinPraison","github-actions[bot]"]'
# GH_TOKEN (user PAT) posts as MervinPraison for @copilot; fallback to github.token for @claude
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
CLAUDE_UI_SCOPE: 'SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). Do not expand into praisonaiagents or the monorepo unless the issue explicitly requires it.'
CLAUDE_FINAL_PHASES_COMPACT: "**Phase 1: Review per AGENTS.md** (lazy imports, backward compat, import-time <200ms; SDK/product value gate — genuine UI value, correct UI vs praisonaiagents layering, no scope creep)\n**Phase 2: FIX valid issues** found by prior reviewers — push to THIS branch\n**Phase 3: Final verdict** — approve PR if ready, else request changes"
CLAUDE_FINAL_PHASES_DETAILED: "**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions (lazy imports, import-time <200ms)\n4. SDK/product value: review whether the change genuinely adds value — stronger PraisonAIUI UX and correct layering (UI/frontend here; core agent logic belongs in praisonaiagents). Reject scope creep; request changes or recommend closing if value is unclear\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items"
jobs:
# ================================================================
@@ -200,12 +203,12 @@ jobs:
if (reviews.copilot.length) summaryParts.push('Copilot: ' + reviews.copilot.length + ' comments');
if (reviews.greptile.length) summaryParts.push('Greptile: ' + reviews.greptile.length + ' comments');
const reviewSummary = summaryParts.length ? summaryParts.join(' | ') : 'No bot reviews detected';
const claudeBody = '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). Read ALL prior reviewer comments. ' + reviewSummary + '.\n\n' +
const scope = process.env.CLAUDE_UI_SCOPE;
const phases = process.env.CLAUDE_FINAL_PHASES_COMPACT;
const claudeBody = '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL prior reviewer comments. ' + reviewSummary + '.\n\n' +
'Review Context: ' + comments.data.length + ' total comments, ' +
(reviews.coderabbit.length + reviews.qodo.length + reviews.gemini.length + reviews.copilot.length + reviews.greptile.length) + ' bot reviews.\n\n' +
'**Phase 1: Review per AGENTS.md** (lazy imports, backward compat, import-time <200ms)\n' +
'**Phase 2: FIX valid issues** found by prior reviewers — push to THIS branch\n' +
'**Phase 3: Final verdict** — approve PR if ready, else request changes';
phases;
await github.rest.issues.createComment({issue_number: pr, owner: context.repo.owner, repo: context.repo.repo, body: claudeBody});
console.log('Claude fallback triggered on PR #' + pr + ' with ' + reviewSummary);
@@ -226,6 +229,10 @@ jobs:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Wait for Copilot to respond (poll)
id: poll
uses: actions/github-script@v7
@@ -326,14 +333,30 @@ jobs:
github-token: ${{ env.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const scope = process.env.CLAUDE_UI_SCOPE;
const phases = process.env.CLAUDE_FINAL_PHASES_DETAILED;
await github.rest.issues.createComment({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, tests, docs). Do not expand scope into praisonaiagents or the monorepo unless the issue explicitly requires it. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n' + phases
});
console.log(`Claude triggered on PR #${pr}`);
- name: Sync pipeline status labels
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const pr = parseInt(process.env.PR_NUMBER, 10);
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, pr, core);
# ================================================================
# Claude after Qodo fallback path (same polling approach)
# ================================================================
@@ -430,11 +453,13 @@ jobs:
github-token: ${{ env.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const scope = process.env.CLAUDE_UI_SCOPE;
const phases = process.env.CLAUDE_FINAL_PHASES_DETAILED;
await github.rest.issues.createComment({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, tests, docs). Do not expand scope into praisonaiagents or the monorepo unless the issue explicitly requires it. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n' + phases
});
console.log(`Claude triggered on PR #${pr}`);
@@ -481,11 +506,13 @@ jobs:
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const scope = process.env.CLAUDE_UI_SCOPE;
const phases = process.env.CLAUDE_FINAL_PHASES_DETAILED;
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the Lead Engineer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on PraisonAIUI (src/praisonaiui, tests, docs). Do not expand scope into praisonaiagents or the monorepo unless the issue explicitly requires it. Read ALL analysis and reviews above carefully (Gemini, CodeRabbit, Qodo, Copilot, etc).\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n\n**Phase 2: FIX Valid Issues**\n4. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n5. Push all code fixes directly to THIS branch (do NOT create a new PR)\n6. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n7. If all issues are resolved, approve the PR / close the Issue\n8. If blocking issues remain, request changes / leave clear action items'
body: '@claude You are the Lead Engineer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + scope + ' Read ALL analysis and reviews above carefully (Gemini, CodeRabbit, Qodo, Copilot, etc).\n\n' + phases
});
# ================================================================
@@ -595,7 +622,7 @@ jobs:
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer on this bot-authored PR. Push fixes directly to THIS branch (do NOT open a new PR). SCOPE: PraisonAIUI only (src/praisonaiui, src/frontend, tests, docs). Read ALL CodeRabbit/Qodo comments above.\n\n**Phase 1:** Review per AGENTS.md (lazy imports, backward compat, import-time <200ms)\n**Phase 2:** FIX valid reviewer issues on this branch\n**Phase 3:** Approve PR if ready, else request changes with clear action items'
body: '@claude You are the FINAL architecture reviewer on this bot-authored PR. Push fixes directly to THIS branch (do NOT open a new PR). ' + process.env.CLAUDE_UI_SCOPE + ' Read ALL CodeRabbit/Qodo comments above.\n\n' + process.env.CLAUDE_FINAL_PHASES_COMPACT
});
console.log('Claude final review triggered on bot PR #' + pr);
@@ -635,6 +662,48 @@ jobs:
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer. Push fixes directly to THIS branch. SCOPE: PraisonAIUI (src/praisonaiui, src/frontend, tests, docs). Read all prior reviewer comments.\n\n**Phase 1:** Review per AGENTS.md\n**Phase 2:** FIX valid issues on this branch\n**Phase 3:** Approve PR if ready, else request changes'
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAIUI (not a fork), you are able to make modifications to this branch and push directly. ' + process.env.CLAUDE_UI_SCOPE + ' Read all prior reviewer comments.\n\n' + process.env.CLAUDE_FINAL_PHASES_COMPACT
});
console.log('Claude manually triggered on PR #' + pr);
console.log('Claude manually triggered on PR #' + pr);
# ================================================================
# BOT PRs: kick CodeRabbit + Qodo
# ================================================================
bot-pr-trigger-reviews:
if: |
github.event_name == 'pull_request' &&
github.event.action == 'opened' &&
(
github.event.pull_request.user.login == 'github-actions[bot]' ||
github.event.pull_request.user.login == 'praisonai-triage-agent[bot]' ||
github.event.pull_request.user.type == 'Bot'
)
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Trigger CodeRabbit and Qodo reviews
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const path = require('path');
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/bot-pr-review-chain.js'));
const pr = context.payload.pull_request.number;
await chain.kickReviewChain(github, context.repo.owner, context.repo.repo, pr, core);
- name: Sync pipeline status labels
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const pr = context.payload.pull_request.number;
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, pr, core);
+102
View File
@@ -0,0 +1,102 @@
name: Auto CI Failure → Claude
# Detects internal PRs with failing CI and posts @claude with extracted failure details.
on:
workflow_run:
workflows: [CI]
types: [completed]
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
pr_number:
description: 'Optional PR number to process (default: scan blocked PRs)'
required: false
type: string
schedule:
- cron: '15 */6 * * *'
concurrency:
group: ci-failure-scan-${{ github.repository }}-${{ github.event.pull_request.number || github.event.workflow_run.id || github.run_id }}
cancel-in-progress: true
env:
# PAT posts as MervinPraison so issue_comment triggers claude.yml (GITHUB_TOKEN cannot chain workflows)
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
permissions:
pull-requests: read
issues: write
actions: read
jobs:
detect-and-trigger:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Detect CI failures and trigger Claude
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const path = require('path');
const ciFix = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/ci-failure-claude.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const baseRepo = `${owner}/${repo}`;
async function processPR(prNumber) {
return ciFix.maybeTriggerCiFixClaude(github, owner, repo, prNumber, core);
}
let results = [];
if (context.eventName === 'workflow_run') {
const wr = context.payload.workflow_run;
const result = await ciFix.processWorkflowRunFailure(
github, owner, repo, wr, core
);
results.push({ event: 'workflow_run', workflow: wr.name, ...result });
} else if (context.eventName === 'pull_request') {
results.push({
pr: context.payload.pull_request.number,
...(await processPR(context.payload.pull_request.number)),
});
} else {
let prNumbers = [];
if (context.eventName === 'workflow_dispatch' && context.payload.inputs?.pr_number) {
prNumbers = [Number(context.payload.inputs.pr_number)];
} else {
const prs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
for (const pr of prs) {
if (pr.draft) continue;
const headRepo = pr.head?.repo?.full_name;
if (headRepo && headRepo !== baseRepo) continue;
const { data: issue } = await github.rest.issues.get({
owner,
repo,
issue_number: pr.number,
});
const labels = issue.labels.map((l) => l.name);
if (labels.includes('pipeline/blocked:ci') || labels.includes(ciFix.CI_FIX_LABEL)) {
prNumbers.push(pr.number);
}
}
}
for (const num of prNumbers) {
results.push({ pr: num, ...(await processPR(num)) });
}
}
core.summary.addRaw('```json\n' + JSON.stringify(results, null, 2) + '\n```');
await core.summary.write();
+1
View File
@@ -8,6 +8,7 @@ on:
jobs:
python:
name: python
runs-on: ubuntu-latest
strategy:
matrix:
+531
View File
@@ -0,0 +1,531 @@
name: Claude PR Merge Gate
# Read-only Claude assesses merge readiness; GH_TOKEN merges (no @claude, no gh pr review).
# Does not overlap with merge-conflict-claude.yml or auto-pr-comment @claude chains.
on:
schedule:
# Scan for merge-ready PRs every 10 minutes (assess runs only when candidates exist).
- cron: '*/10 * * * *'
workflow_dispatch:
inputs:
pr_number:
description: 'Optional PR number (scan all open internal PRs if empty)'
required: false
type: string
merge_method:
description: 'Merge method'
required: false
type: choice
default: merge
options:
- merge
- squash
- rebase
workflow_run:
workflows:
- Claude Assistant
- CI
types:
- completed
repository_dispatch:
types:
- claude-merge-gate
concurrency:
group: claude-merge-gate-${{ github.repository }}-${{ github.event.inputs.pr_number || github.event.client_payload.pr_number || github.event.workflow_run.id || 'scan' }}
cancel-in-progress: false
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
MAX_CANDIDATES: 3
DISPATCH_PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.client_payload.pr_number || '' }}
permissions:
contents: read
pull-requests: read
issues: read
actions: read
jobs:
auto-dispatch:
if: github.event_name == 'workflow_run'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
actions: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Dispatch merge gate when PR pipeline is ready
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const wr = context.payload.workflow_run;
const owner = context.repo.owner;
const repo = context.repo.repo;
if (wr.conclusion !== 'success') {
core.info(`Skip dispatch: ${wr.name} conclusion=${wr.conclusion}`);
return;
}
const allowed = ['Claude Assistant', 'CI'];
if (!allowed.includes(wr.name)) {
core.info(`Skip dispatch: workflow ${wr.name}`);
return;
}
const prNumber = await mergeGate.resolvePrNumberFromWorkflowRun(github, owner, repo, wr);
if (!prNumber) {
core.info('Skip dispatch: could not resolve PR from workflow run');
return;
}
const fromClaude = wr.name === 'Claude Assistant';
const check = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, {
skipGlobalClaudeRunCheck: fromClaude,
skipRecentClaudeCooldown: fromClaude,
}
);
if (!check.ready) {
core.info(`PR #${prNumber} not ready: ${check.reasons.join(', ')}`);
return;
}
await github.rest.repos.createDispatchEvent({
owner,
repo,
event_type: 'claude-merge-gate',
client_payload: { pr_number: prNumber },
});
core.info(`Dispatched merge gate for PR #${prNumber} after ${wr.name}`);
scan-candidates:
if: github.event_name != 'workflow_run'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: write
actions: read
id-token: write
outputs:
matrix: ${{ steps.scan.outputs.matrix }}
has_candidates: ${{ steps.scan.outputs.has_candidates }}
steps:
- uses: actions/checkout@v4
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Scan open PRs for merge gate eligibility
id: scan
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const maxCandidates = parseInt(process.env.MAX_CANDIDATES || '5', 10);
const inputPr = String(process.env.DISPATCH_PR_NUMBER || '').trim();
const candidates = [];
const skipped = [];
if (inputPr) {
const n = parseInt(inputPr, 10);
if (Number.isNaN(n)) {
throw new Error(`Invalid pr_number input: ${inputPr}`);
}
const result = await mergeGate.evaluatePipelineQuiescent(github, owner, repo, n, core);
if (result.ready) {
candidates.push({ pr_number: n, head_sha: result.headSha });
} else {
skipped.push({ pr: n, reasons: result.reasons });
}
} else {
const prNumbers = await mergeGate.listPrNumbersForMergeGateScan(
github, owner, repo, core
);
const selected = await mergeGate.selectMergeGateCandidates(
github, owner, repo, prNumbers, maxCandidates, core
);
candidates.push(...selected.candidates);
skipped.push(...selected.skipped);
}
core.info('Skipped: ' + JSON.stringify(skipped));
core.setOutput('matrix', JSON.stringify({ include: candidates }));
core.setOutput('has_candidates', candidates.length > 0 ? 'true' : 'false');
core.summary.addRaw('## Merge gate scan\n');
core.summary.addRaw(`Candidates: ${candidates.length}\n`);
core.summary.addRaw('```json\n' + JSON.stringify({ candidates, skipped }, null, 2) + '\n```');
await core.summary.write();
if (inputPr && skipped.length === 1 && skipped[0].pr === parseInt(inputPr, 10)) {
const reasons = skipped[0].reasons;
const body = [
'**Merge gate scan** — not eligible for auto-merge.',
'',
...reasons.map((r) => `- \`${r}\``),
'',
'**Actions:** wait for CI and the Claude review chain, or add label `needs-manual-review` and merge manually.',
'**Opt out:** label `no-auto-merge`.',
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: skipped[0].pr, body,
});
} else if (inputPr && candidates.length === 1) {
await github.rest.issues.createComment({
owner, repo, issue_number: candidates[0].pr_number,
body: '**Merge gate scan** — eligible for assessment. Claude merge gate will assess and may auto-merge if `MERGE_GATE_VERDICT: APPROVE`.',
});
}
await ps.syncOpenPullRequests(github, owner, repo, { maxPrs: 10 }, core);
claude-assess:
needs: scan-candidates
if: needs.scan-candidates.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.scan-candidates.outputs.matrix) }}
permissions:
contents: read
pull-requests: read
issues: write
actions: read
id-token: write
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Mark merge gate active
id: mark_active
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
core.setOutput('started_at', new Date().toISOString());
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ${{ matrix.pr_number }},
labels: ['claude-merge-gate-active'],
});
- name: Stash merge gate helpers
run: cp .github/scripts/merge-gate.js /tmp/merge-gate.js
- name: Fetch PR branch
env:
PR_NUMBER: ${{ matrix.pr_number }}
run: |
git fetch origin "pull/${PR_NUMBER}/head:pr-${PR_NUMBER}"
git checkout "pr-${PR_NUMBER}"
- name: Claude merge gate assessment (read-only)
id: assess
uses: ./.github/actions/claude-code-action
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
CLAUDE_CODE_SUBAGENT_MODEL: inherit
ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4-6
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
model: claude-opus-4-8
trigger_phrase: '@claude-merge-gate-internal'
direct_prompt: |
You are the MERGE GATE reviewer for PraisonAI PR #${{ matrix.pr_number }}.
READ-ONLY: do not modify code, commit, push, rebase, or create branches.
CRITICAL OUTPUT RULE:
Post exactly one PR comment via GitHub MCP containing either:
MERGE_GATE_VERDICT: APPROVE
or
MERGE_GATE_VERDICT: BLOCK
followed by a short rationale on the next lines.
NEVER include @claude in any comment. NEVER mention merge conflict resolution.
Review checklist:
1. Read AGENTS.md and the PR diff on the current branch.
2. Read all PR comments and reviews (FINAL architecture reviewer / Lead Engineer must have run).
3. Confirm SDK value gate: change strengthens praisonaiagents (no scope creep).
4. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
5. If agent.py adds >100 lines or new Agent __init__ params, respond MERGE_GATE_VERDICT: BLOCK (manual review required).
6. BLOCK if labels security/breaking-change/needs-manual-review/release, sensitive paths (.github/workflows, auth, pyproject.toml), secrets in diff, PR >800 lines or >30 files, or SDK changes without tests. Label `merge-gate-ci-only` exempts CI-only changes under `.github/workflows/`, `.github/actions/`, or merge-gate scripts.
7. Confirm no CHANGES_REQUESTED reviews and CI green on HEAD ${{ matrix.head_sha }}.
8. If ready to merge: MERGE_GATE_VERDICT: APPROVE. Otherwise: MERGE_GATE_VERDICT: BLOCK with reasons.
Do not approve or merge via GitHub UI/API — only post the verdict comment.
disallowed_tools: |
Bash
Edit
Write
Replace
allowed_tools: |
View
GlobTool
GrepTool
Read
timeout_minutes: 15
- name: Post fallback BLOCK if Opus did not comment
id: fallback_verdict
if: steps.assess.outcome != 'success'
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const mergeGate = require('/tmp/merge-gate.js');
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = ${{ matrix.pr_number }};
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
const minCreatedAt = new Date(Date.now() - 25 * 60 * 1000).toISOString();
const existing = mergeGate.findMergeGateVerdict(
ctx.comments, minCreatedAt, ctx.headPushedAt, { excludeAutomatedFallback: true }
);
if (existing) {
core.info(`Opus verdict already posted: ${existing}`);
return;
}
const check = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, { forMergeStep: true }
);
const lines = [
'MERGE_GATE_VERDICT: BLOCK',
'',
'Automated fallback — Opus merge gate assessment did not complete.',
];
if (check.ready) {
lines.push('Deterministic gates passed, but Opus MERGE_GATE_VERDICT is required before merge.');
} else {
lines.push('Blockers:', ...check.reasons.map((r) => `- ${r}`));
}
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber,
body: lines.join('\n'),
});
core.info('Posted fallback BLOCK — Opus verdict required');
- name: Require Opus MERGE_GATE_VERDICT
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const mergeGate = require('/tmp/merge-gate.js');
const comments = await mergeGate.listAllComments(
github, context.repo.owner, context.repo.repo, ${{ matrix.pr_number }}
);
const ctx = await mergeGate.loadPrContext(
github, context.repo.owner, context.repo.repo, ${{ matrix.pr_number }}
);
const minCreatedAt = new Date(Date.now() - 25 * 60 * 1000).toISOString();
const verdict = mergeGate.findMergeGateVerdict(
comments, minCreatedAt, ctx.headPushedAt, { excludeAutomatedFallback: true }
);
if (!verdict) {
core.setFailed(
'Opus merge gate assessment must post MERGE_GATE_VERDICT on this PR (automated fallback APPROVE is not accepted).'
);
return;
}
core.info(`Opus merge gate verdict: ${verdict}`);
core.setOutput('verdict', verdict);
- name: Remove merge gate active label
if: always()
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const prNumber = ${{ matrix.pr_number }};
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: 'claude-merge-gate-active',
});
core.info(`Removed claude-merge-gate-active from PR #${prNumber}`);
} catch (e) {
core.info(`Label claude-merge-gate-active already removed or missing on PR #${prNumber}`);
}
- name: Fail job if assessment failed
if: steps.assess.outcome == 'failure'
run: exit 1
merge-only:
needs: [scan-candidates, claude-assess]
if: |
always() &&
needs.scan-candidates.outputs.has_candidates == 'true' &&
needs.claude-assess.result == 'success'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.scan-candidates.outputs.matrix) }}
permissions:
contents: write
pull-requests: write
issues: write
actions: read
id-token: write
steps:
- uses: actions/checkout@v4
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Re-check gates and merge
uses: actions/github-script@v7
env:
MERGE_METHOD: ${{ github.event.inputs.merge_method || 'merge' }}
SCAN_HEAD_SHA: ${{ matrix.head_sha }}
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = ${{ matrix.pr_number }};
const scanHeadSha = process.env.SCAN_HEAD_SHA;
const removeLabels = async () => {
try {
await github.rest.issues.removeLabel({
owner, repo, issue_number: prNumber, name: 'claude-merge-gate-active',
});
} catch (e) {
core.info('Label claude-merge-gate-active already removed or missing');
}
};
try {
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
const comments = ctx.comments;
const verdictMin = new Date(Date.now() - 25 * 60 * 1000).toISOString();
const verdict = mergeGate.findMergeGateVerdict(
comments, verdictMin, ctx.headPushedAt, { excludeAutomatedFallback: true }
);
if (verdict !== 'APPROVE') {
core.info(`Skip merge: verdict=${verdict || 'NONE'}`);
await removeLabels();
return;
}
const check = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, { forMergeStep: true }
);
if (!check.ready) {
core.info(`Skip merge: ${check.reasons.join(', ')}`);
await removeLabels();
return;
}
if (check.headSha !== scanHeadSha) {
core.info(`Skip merge: HEAD moved ${scanHeadSha.slice(0, 7)} → ${check.headSha.slice(0, 7)}`);
await removeLabels();
return;
}
const method = process.env.MERGE_METHOD || 'merge';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let merged = false;
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await github.rest.pulls.merge({
owner, repo, pull_number: prNumber, merge_method: method,
});
merged = true;
break;
} catch (e) {
const msg = (e.message || '').toLowerCase();
if (attempt < 3 && msg.includes('base branch was modified')) {
core.info(`Merge attempt ${attempt} failed (base moved), retrying in 8s...`);
await sleep(8000);
const recheck = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, { forMergeStep: true }
);
if (!recheck.ready) {
core.info(`Skip merge retry: ${recheck.reasons.join(', ')}`);
await removeLabels();
return;
}
continue;
}
throw e;
}
}
if (!merged) {
core.info('Skip merge: exhausted retries');
await removeLabels();
return;
}
await github.rest.issues.addLabels({
owner, repo, issue_number: prNumber, labels: ['auto-merged-by-gate'],
});
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber,
body: [
'Merged by **Claude PR merge gate** (`claude-merge-gate.yml`).',
`Verdict: MERGE_GATE_VERDICT: APPROVE`,
`SHA: \`${check.headSha.slice(0, 7)}\``,
`Method: ${method}`,
].join('\n'),
});
core.info(`Merged PR #${prNumber}`);
} finally {
await removeLabels();
}
+25 -2
View File
@@ -268,6 +268,26 @@ jobs:
core.setOutput('pr_branch', pr.data.head.ref);
const headRepo = pr.data.head.repo?.full_name || context.repo.full_name;
core.setOutput('head_repo', headRepo);
let mergeState = '';
try {
const gql = await github.graphql(
`query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) { mergeStateStatus }
}
}`,
{ owner: context.repo.owner, repo: context.repo.repo, number: prNumber }
);
mergeState = (gql.repository.pullRequest.mergeStateStatus || '').toUpperCase();
} catch (e) {
core.warning(`GraphQL mergeStateStatus failed: ${e.message}`);
}
if (!mergeState) {
mergeState = (pr.data.mergeable_state || pr.data.merge_state_status || '').toUpperCase();
}
core.setOutput('merge_state', mergeState);
if (headRepo !== `${context.repo.owner}/${context.repo.repo}`) {
core.setOutput('is_fork', 'true');
core.setOutput('clone_url', pr.data.head.repo.clone_url);
@@ -279,6 +299,7 @@ jobs:
}
core.setOutput('is_pr_review', 'false');
core.setOutput('is_fork', 'false');
core.setOutput('merge_state', '');
- name: Checkout repository
uses: actions/checkout@v4
@@ -316,11 +337,13 @@ jobs:
allowed_bots: 'praisonai-triage-agent[bot]'
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ steps.github-token.outputs.token }}
model: opus
model: claude-opus-4-8
trigger_phrase: "@claude"
timeout_minutes: "30"
direct_prompt: |
${{ steps.check_fork.outputs.is_fork == 'true' && 'CRITICAL: FORK PR — comment only, do not push.\n\n' || 'PR on MervinPraison/PraisonAIUI — SAME REPO, NOT A FORK. You HAVE push permission to this branch. NEVER say this is a fork.\n\n' }}
${{ steps.check_fork.outputs.is_fork != 'true' && steps.check_fork.outputs.merge_state == 'DIRTY' && format('PRIORITY — RESOLVE MERGE CONFLICTS (branch: {0}, PR #{1}):\n- DO NOT create a new branch or PR\n- git fetch origin main && git rebase origin/main\n- Resolve conflicts: keep this PR intent, merge in main newer logic\n- git add -A && git rebase --continue (repeat until done)\n- git push --force-with-lease origin {0}\n- Run targeted pytest/ruff on changed paths\n- Comment files resolved and what you skipped\n- Only after conflicts are resolved, proceed with review phases below if still needed\n\n', steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.pr_number) || '' }}
${{ steps.check_fork.outputs.merge_state != 'DIRTY' && format('CONTEXT: PR #{0} on branch {1}. merge_state={2}.\n\n', steps.check_fork.outputs.pr_number, steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.merge_state) || '' }}
You are the Lead Engineer doing FINAL review on this open PR in MervinPraison/PraisonAIUI.
Read ALL reviewer comments (CodeRabbit, Gemini, Qodo, Copilot, human). Follow AGENTS.md.
SCOPE: src/praisonaiui, src/frontend, tests, docs only.
@@ -359,7 +382,7 @@ jobs:
allowed_bots: 'praisonai-triage-agent[bot]'
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ steps.github-token.outputs.token }}
model: opus
model: claude-opus-4-8
mode: ${{ github.event_name == 'workflow_dispatch' && 'agent' || 'tag' }}
trigger_phrase: "@claude"
label_trigger: "claude"
+183
View File
@@ -0,0 +1,183 @@
name: Auto Merge Conflict → Claude
# Detects internal PRs with mergeStateStatus=DIRTY and posts @claude to trigger claude.yml.
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
schedule:
- cron: '0 */6 * * *'
concurrency:
group: merge-conflict-scan-${{ github.repository }}
cancel-in-progress: true
env:
# PAT posts as MervinPraison so issue_comment triggers claude.yml (GITHUB_TOKEN cannot chain workflows)
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
permissions:
pull-requests: write
issues: write
jobs:
detect-and-trigger:
runs-on: ubuntu-latest
steps:
- name: Detect conflicting PRs and trigger Claude
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const baseRepo = `${owner}/${repo}`;
const COOLDOWN_MS = 12 * 60 * 60 * 1000;
const AUTO_ACTORS = ['github-actions[bot]', 'MervinPraison'];
const LABEL = 'claude-conflict-pending';
const COMMENT_BODY = [
'@claude this PR has merge conflicts with `main`.',
'Please rebase onto latest `main`, resolve conflicts (keep this PR\'s intent, merge in newer main logic),',
'run targeted tests, and force-push with `--force-with-lease`.',
'Comment which files you resolved.'
].join(' ');
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function getMergeState(prNumber) {
const query = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
mergeStateStatus
maintainerCanModify
isDraft
headRef { repository { nameWithOwner } }
}
}
}
`;
for (let attempt = 0; attempt < 3; attempt++) {
const result = await github.graphql(query, { owner, repo, number: prNumber });
const prGql = result.repository.pullRequest;
const status = (prGql?.mergeStateStatus || '').toUpperCase();
if (status && status !== 'UNKNOWN') {
return {
status,
isDraft: prGql.isDraft,
headRepo: prGql.headRef?.repository?.nameWithOwner,
maintainerCanModify: prGql.maintainerCanModify === true,
};
}
if (attempt < 2) await sleep(10000);
}
const pr = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
return {
status: (pr.data.mergeable_state || '').toUpperCase(),
isDraft: pr.data.draft,
headRepo: pr.data.head.repo?.full_name,
maintainerCanModify: pr.data.maintainer_can_modify === true,
};
}
function hasFinalClaudeReviewTrigger(comments) {
return comments.some((c) => {
const body = (c.body || '').toLowerCase();
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!body.includes('@claude')) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
});
}
function hasRecentAutoComment(comments) {
const cutoff = Date.now() - COOLDOWN_MS;
return comments.some((c) => {
if (!AUTO_ACTORS.includes(c.user.login)) return false;
const body = (c.body || '').toLowerCase();
if (!body.includes('@claude') || !body.includes('merge conflict')) return false;
return new Date(c.created_at).getTime() > cutoff;
});
}
async function processPR(prNumber) {
const { status, isDraft, headRepo, maintainerCanModify } = await getMergeState(prNumber);
core.info(`PR #${prNumber}: mergeState=${status}, head=${headRepo}, draft=${isDraft}, maintainerCanModify=${maintainerCanModify}`);
const { data: issue } = await github.rest.issues.get({
owner,
repo,
issue_number: prNumber,
});
const labels = issue.labels.map((l) => l.name);
if (status !== 'DIRTY' && labels.includes(LABEL)) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: LABEL,
});
core.info(`Removed ${LABEL} from PR #${prNumber}`);
}
if (status !== 'DIRTY') return { skipped: true, reason: 'not dirty' };
if (isDraft) return { skipped: true, reason: 'draft' };
if (headRepo && headRepo !== baseRepo && !maintainerCanModify) {
return { skipped: true, reason: 'fork without maintainer edits' };
}
if (labels.includes(LABEL)) {
return { skipped: true, reason: 'conflict resolution pending' };
}
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
per_page: 100,
});
if (hasRecentAutoComment(comments)) {
return { skipped: true, reason: 'recent auto comment' };
}
if (!hasFinalClaudeReviewTrigger(comments)) {
return { skipped: true, reason: 'awaiting final claude review trigger' };
}
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [LABEL],
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: COMMENT_BODY,
});
return { triggered: true };
}
let prNumbers = [];
if (context.eventName === 'pull_request') {
prNumbers = [context.payload.pull_request.number];
} else {
const prs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
prNumbers = prs.map((p) => p.number);
}
const results = [];
for (const num of prNumbers) {
results.push({ pr: num, ...(await processPR(num)) });
}
core.summary.addRaw('```json\n' + JSON.stringify(results, null, 2) + '\n```');
await core.summary.write();
+129
View File
@@ -0,0 +1,129 @@
name: Nightly Release Gate
# Preflight before PyPI Release (patch bump).
# Primary: CI success on main (workflow_run).
# Backstop: nightly cron at 00:00 UTC.
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
inputs:
dry_run:
description: 'Run preflight only; do not dispatch pypi-release.yml'
required: false
default: false
type: boolean
workflow_run:
workflows:
- CI
types:
- completed
branches:
- main
permissions:
contents: read
actions: write
concurrency:
group: nightly-release-gate
cancel-in-progress: false
jobs:
preflight:
if: |
github.event_name == 'workflow_dispatch' ||
github.event_name == 'schedule' ||
(
github.event_name == 'workflow_run' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main' &&
github.event.workflow_run.event == 'push'
)
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
steps:
- name: Resolve trigger context
id: ctx
run: |
if [ "${{ github.event_name }}" = "workflow_run" ]; then
echo "checkout_sha=${{ github.event.workflow_run.head_sha }}" >> "$GITHUB_OUTPUT"
echo "source=ci" >> "$GITHUB_OUTPUT"
echo "is_ci=true" >> "$GITHUB_OUTPUT"
else
echo "checkout_sha=main" >> "$GITHUB_OUTPUT"
echo "source=nightly" >> "$GITHUB_OUTPUT"
echo "is_ci=false" >> "$GITHUB_OUTPUT"
fi
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: ${{ steps.ctx.outputs.checkout_sha }}
fetch-depth: 0
- name: Fetch main and tags
run: git fetch --tags origin main
- name: Preflight — path changes, CI SHA, dedupe
id: preflight
uses: actions/github-script@v7
env:
HEAD_SHA: ${{ steps.ctx.outputs.checkout_sha }}
IS_CI: ${{ steps.ctx.outputs.is_ci }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const rg = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/release-gate.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const headSha = process.env.HEAD_SHA;
const isCi = process.env.IS_CI === 'true';
const result = await rg.evaluateReleasePreflight(
github, owner, repo,
{ headSha, isCiTrigger: isCi, bump: 'patch' },
core
);
core.setOutput('should_release', result.ready ? 'true' : 'false');
core.setOutput('reason', result.reasons.join('; '));
core.setOutput('head_sha', result.headSha || headSha);
core.setOutput('last_tag', result.lastTag || '');
core.setOutput('target_version', result.targetVersion || '');
core.summary.addRaw('## Release gate preflight\n');
core.summary.addRaw(`Ready: ${result.ready}\n`);
core.summary.addRaw(`Reason: ${result.reasons.join('; ')}\n`);
if (result.targetVersion) {
core.summary.addRaw(`Target: aiui==${result.targetVersion}\n`);
}
await core.summary.write();
if (!result.ready) {
core.info(`Skip release: ${result.reasons.join('; ')}`);
}
- name: Dry run summary
if: inputs.dry_run == true
run: |
echo "Dry run — preflight only, no pypi-release dispatch."
echo "should_release=${{ steps.preflight.outputs.should_release }}"
echo "reason=${{ steps.preflight.outputs.reason }}"
- name: Dispatch PyPI Release (patch)
if: steps.preflight.outputs.should_release == 'true' && inputs.dry_run != true
run: |
set -euo pipefail
gh workflow run pypi-release.yml \
--repo "${GITHUB_REPOSITORY}" \
--ref main \
-f bump=patch \
-f source=${{ steps.ctx.outputs.source }} \
-f trigger_sha=${{ steps.preflight.outputs.head_sha }} \
-f dry_run=false
echo "Dispatched pypi-release.yml (patch, source=${{ steps.ctx.outputs.source }})"
echo "Uses pypi-auto environment for ci/nightly patch releases."
@@ -0,0 +1,42 @@
name: Pipeline Status Sync
# Sync pipeline/* stage and blocker labels on open internal PRs.
on:
schedule:
- cron: '*/10 * * * *'
workflow_dispatch:
permissions:
pull-requests: read
issues: write
actions: write
id-token: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Sync pipeline labels on open PRs
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
await ps.syncOpenPullRequests(
github, context.repo.owner, context.repo.repo,
{ maxPrs: 30, dispatchMergeGate: true }, core
);
+232
View File
@@ -0,0 +1,232 @@
name: PyPI Release
# Requires GitHub Environment "pypi" (manual) or "pypi-auto" (ci/nightly patch).
# Secrets: PYPI_TOKEN (or PYPI_API_TOKEN), GH_TOKEN
on:
workflow_dispatch:
inputs:
bump:
description: 'Version bump type for aiui'
required: true
default: patch
type: choice
options:
- patch
- minor
- major
dry_run:
description: 'Preview versions and steps without publishing or pushing'
required: false
default: false
type: boolean
version:
description: 'Override version (empty = auto bump from pyproject.toml)'
required: false
default: ''
type: string
source:
description: 'Trigger source (audit trail)'
required: false
default: manual
type: string
trigger_sha:
description: 'SHA that triggered this release'
required: false
default: ''
type: string
permissions:
contents: write
concurrency:
group: pypi-release
cancel-in-progress: false
jobs:
release:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: >-
${{
(inputs.source == 'ci' || inputs.source == 'nightly') &&
inputs.bump == 'patch' &&
'pypi-auto' || 'pypi'
}}
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN || secrets.PYPI_API_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
PACKAGE: aiui
steps:
- name: Release audit
run: |
echo "Source: ${{ inputs.source }}"
echo "Trigger SHA: ${{ inputs.trigger_sha }}"
echo "Bump: ${{ inputs.bump }}"
echo "Environment: ${{ (inputs.source == 'ci' || inputs.source == 'nightly') && inputs.bump == 'patch' && 'pypi-auto' || 'pypi' }}"
- name: Checkout main
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: main
fetch-depth: 0
token: ${{ secrets.GH_TOKEN }}
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- name: Set up uv
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
- name: Configure git
run: |
git config user.name "MervinPraison"
git config user.email "[email protected]"
gh auth setup-git
- name: Compute release version
id: versions
env:
BUMP: ${{ inputs.bump }}
VERSION_OVERRIDE: ${{ inputs.version }}
run: |
python <<'PY'
import os
import re
import sys
from pathlib import Path
SEMVER = re.compile(r"^\d+\.\d+\.\d+$")
bump = os.environ["BUMP"]
override = os.environ.get("VERSION_OVERRIDE", "").strip()
def bump_version(version: str) -> str:
parts = version.split(".")
if len(parts) != 3:
print(f"Invalid version: {version}", file=sys.stderr)
sys.exit(1)
maj, min_, patch = (int(parts[0]), int(parts[1]), int(parts[2]))
if bump == "major":
return f"{maj + 1}.0.0"
if bump == "minor":
return f"{maj}.{min_ + 1}.0"
return f"{maj}.{min_}.{patch + 1}"
pyproject = Path("pyproject.toml")
content = pyproject.read_text()
match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
if not match:
print("Could not read version from pyproject.toml", file=sys.stderr)
sys.exit(1)
current = match.group(1)
if not SEMVER.match(current):
print(f"Invalid current version: {current}", file=sys.stderr)
sys.exit(1)
if override:
if not SEMVER.match(override):
print(f"Invalid version override: {override}", file=sys.stderr)
sys.exit(1)
new_version = override
else:
new_version = bump_version(current)
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
fh.write(f"current_version={current}\n")
fh.write(f"new_version={new_version}\n")
print(f"aiui: {current} -> {new_version}")
PY
- name: Dry run summary
if: inputs.dry_run
run: |
echo "Dry run — no publish, commit, or push."
echo "Package: ${{ env.PACKAGE }}"
echo "Version: ${{ steps.versions.outputs.current_version }} -> ${{ steps.versions.outputs.new_version }}"
echo "Would: bump pyproject.toml + __version__.py, uv publish, commit, tag, GitHub release"
- name: Bump version files
if: inputs.dry_run != true
env:
CURRENT: ${{ steps.versions.outputs.current_version }}
NEW_VERSION: ${{ steps.versions.outputs.new_version }}
run: |
set -euo pipefail
python <<'PY'
import os
import re
from pathlib import Path
current = os.environ["CURRENT"]
new = os.environ["NEW_VERSION"]
pyproject = Path("pyproject.toml")
content = pyproject.read_text()
pyproject.write_text(content.replace(f'version = "{current}"', f'version = "{new}"', 1))
version_py = Path("src/praisonaiui/__version__.py")
vcontent = version_py.read_text()
version_py.write_text(re.sub(
r'__version__ = "[^"]+"',
f'__version__ = "{new}"',
vcontent,
count=1,
))
PY
- name: Publish aiui to PyPI
if: inputs.dry_run != true
run: |
set -euo pipefail
rm -rf dist
uv lock
uv build
uv publish
- name: Wait for aiui on PyPI
if: inputs.dry_run != true
run: |
set -euo pipefail
VERSION="${{ steps.versions.outputs.new_version }}"
MAX_WAIT=600
INTERVAL=30
START=$(date +%s)
while true; do
if curl -fsSL "https://pypi.org/pypi/${PACKAGE}/${VERSION}/json" >/dev/null; then
echo "✅ ${PACKAGE}==${VERSION} is available on PyPI"
exit 0
fi
ELAPSED=$(( $(date +%s) - START ))
if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then
echo "❌ Timeout waiting for ${PACKAGE}==${VERSION} on PyPI"
exit 1
fi
echo "⏳ Waiting for ${PACKAGE}==${VERSION} ($(( MAX_WAIT - ELAPSED ))s remaining...)"
sleep "$INTERVAL"
done
- name: Commit, tag, and release
if: inputs.dry_run != true
env:
VERSION: ${{ steps.versions.outputs.new_version }}
run: |
set -euo pipefail
TAG="v${VERSION}"
git add pyproject.toml uv.lock src/praisonaiui/__version__.py
git diff --cached --quiet && { echo "No version files to commit"; exit 0; }
git commit -m "Release ${TAG}"
git tag -f "${TAG}"
git pull --rebase origin main
git tag -f "${TAG}"
git push origin main
git push --tags -f
gh release create "${TAG}" \
--title "PraisonAIUI ${TAG}" \
--notes "Release ${TAG}" \
--latest
- name: Verify release
if: inputs.dry_run != true
run: echo "✅ ${{ env.PACKAGE }}==${{ steps.versions.outputs.new_version }}"
@@ -25,7 +25,8 @@ jobs:
run: |
gh workflow run sync-secrets-to-aiui.yml \
--repo MervinPraison/PraisonAI \
--ref main
--ref main \
-f target_repo=MervinPraison/PraisonAIUI
echo "Triggered: https://github.com/MervinPraison/PraisonAI/actions/workflows/sync-secrets-to-aiui.yml"
echo ""
echo "If sync fails (401/403), refresh SECRETS_ADMIN_PAT in PraisonAI,"
+17 -1
View File
@@ -138,7 +138,23 @@ Final rule: conclude only when evidence shows missing = 0.
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PUBLISH WORKFLOW (reference)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Step 1 — Publish praisonaiagents (Core SDK):
GitHub Actions (recommended):
GitHub → Actions → "PyPI Release" → Run workflow → bump: patch
Requires repo secrets: PYPI_TOKEN, GH_TOKEN
Requires GitHub Environment "pypi" with required reviewers (Settings → Environments)
Manual — Publish aiui (PraisonAIUI):
cd /Users/praison/PraisonAIUI
# Bump version in pyproject.toml and src/praisonaiui/__version__.py
rm -rf dist && uv lock && uv build && uv publish --token "$PYPI_TOKEN"
git add pyproject.toml uv.lock src/praisonaiui/__version__.py
git commit -m "Release vX.Y.Z" && git tag vX.Y.Z && git push && git push --tags
gh release create vX.Y.Z --title "PraisonAIUI vX.Y.Z" --notes "Release vX.Y.Z" --latest
PyPI package name: aiui
Verify: pip index versions aiui | head -1
PraisonAI stack (separate repo — publish SDK before wrapper):
cd /Users/praison/praisonai-package/src/praisonai-agents
praisonai publish pypi
# Uses uv internally: uv lock → uv build → uv publish
+136
View File
@@ -0,0 +1,136 @@
# Health API Reference
The AIUI health API provides endpoints to monitor server and provider health status.
## Health Status Values
The following status values are used throughout the health API:
| Status | Meaning | Color Code |
|--------|---------|------------|
| `"ok"` | Service is functioning normally | Green |
| `"healthy"` | Service is functioning normally (legacy) | Green |
| `"degraded"` | Service is partially functional | Yellow |
| `"error"` | Service has errors | Red |
| `"unknown"` | Status cannot be determined | Yellow |
> **Note:** Both `"ok"` and `"healthy"` indicate successful operation. The system accepts either value for backward compatibility.
## Endpoints
### GET /health
Returns the overall server health status.
**Response:**
```json
{
"status": "ok",
"timestamp": "2026-06-23T10:00:00Z",
"integrated": true,
"sdk_gaps": []
}
```
### GET /api/provider
Returns information about the active AI provider.
**Response:**
```json
{
"name": "PraisonAIProvider",
"module": "praisonaiui.providers",
"status": "ok",
"agents": []
}
```
### GET /api/provider/health
Returns detailed JSON health information from the provider gateway. Always
responds with `Content-Type: application/json`. The base fields from the
provider's `health()` payload are merged with the normalised fields below.
**Response:**
```json
{
"status": "ok",
"provider": "PraisonAIProvider",
"type": "PraisonAIProvider",
"agents": 5,
"detail": "ok"
}
```
| Field | Type | Description |
|-------|------|-------------|
| `status` | string | One of the health status values above. |
| `type` | string | Provider type (from `provider` field, falls back to class name). |
| `agents` | integer | Count of agents reported by the provider. |
| `detail` | string | Human-readable detail; `"ok"` for healthy providers. |
> **Note:** Any unmatched `/api/*` path returns a JSON `404` (never the SPA
> HTML shell), so API clients can rely on parsing the response body.
## CLI Commands
### health-check
Check server health status:
```bash
aiui health-check --server http://127.0.0.1:8000
```
The command displays:
- Green panel for `"ok"` or `"healthy"` status
- Yellow panel for other statuses
- Red error message if server is unreachable
### doctor
Run comprehensive diagnostics:
```bash
aiui doctor --server http://127.0.0.1:8000
```
The doctor command checks:
1. Server Health
2. Provider Status
3. Gateway Status
4. Features Loaded
5. Config Store
6. Datastore
7. Channels
### provider status
Check provider health:
```bash
aiui provider status --server http://127.0.0.1:8000
```
## Python API
```python
from praisonaiui.health_utils import is_success_status
# Check if a status indicates success
if is_success_status("ok"):
print("Service is healthy")
# Available success statuses
from praisonaiui.health_utils import SUCCESS_STATUSES
# frozenset({'ok', 'healthy'})
```
## JavaScript/Dashboard
The dashboard automatically accepts both `"ok"` and `"healthy"` as success statuses:
```javascript
const ok = d && (d.status === 'ok' || d.status === 'healthy');
```
+39 -9
View File
@@ -49,7 +49,7 @@ services:
volumes:
- aiui-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8082/health"]
test: ["CMD", "curl", "-f", "http://localhost:8082/health/live"]
interval: 30s
timeout: 5s
retries: 3
@@ -81,7 +81,7 @@ curl -H "Authorization: Bearer <api-key>" http://localhost:8082/api/config
```
**Exempt paths** (always accessible without auth):
- `/health`, `/api/health` — health checks
- `/health`, `/health/live`, `/health/ready`, `/api/health` — health checks
- `/api/auth/login`, `/login` — login endpoints
- `/api/protocol`, `/api/protocol/negotiate` — protocol discovery
@@ -146,8 +146,36 @@ This affects:
### Health Endpoint
```bash
# Fast liveness probe (<500ms) - use for load balancers and K8s livenessProbe
curl http://localhost:8082/health/live
# Returns: {"status": "ok", "timestamp": "2024-..."}
# Readiness probe with parallel feature checks - use for K8s readinessProbe
curl http://localhost:8082/health/ready
# Deep diagnostics (parallel feature checks, deep by default)
curl http://localhost:8082/health
# Returns: {"status": "healthy", "timestamp": "2024-..."}
# Pass ?deep=false for an immediate liveness response
```
### Kubernetes Probes
Use the fast liveness endpoint for probes to avoid timeouts:
```yaml
livenessProbe:
httpGet:
path: /health/live
port: 8082
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 1
readinessProbe:
httpGet:
path: /health/ready
port: 8082
initialDelaySeconds: 5
periodSeconds: 10
```
### Docker HEALTHCHECK
@@ -156,17 +184,17 @@ The Dockerfile includes a built-in health check:
```dockerfile
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD curl -f http://localhost:${AIUI_PORT}/health || exit 1
CMD curl -f http://localhost:${AIUI_PORT}/health/live || exit 1
```
### CLI Health Commands
```bash
# Quick health check
aiui health --server http://localhost:8082
aiui health-check --server http://localhost:8082
# Detailed per-feature health
aiui health --server http://localhost:8082 --detailed
# Detailed per-feature health
aiui health-check --server http://localhost:8082 --detailed
# Full diagnostic (7 checks)
aiui doctor --server http://localhost:8082
@@ -199,7 +227,7 @@ curl -H "X-API-Key: <key>" http://localhost:8082/api/config
- **Internal networks**: Use `AUTH_ENFORCE=false` if behind a trusted reverse proxy
- **Public exposure**: Always set `AUTH_ENFORCE=true` and use HTTPS
- **Load balancers**: Health check path is `/health` (exempt from auth)
- **Load balancers**: Health check path is `/health/live` (fast, exempt from auth)
## Managed Hosting
@@ -209,7 +237,9 @@ Hosting platforms should expose these endpoints:
| Endpoint | Purpose |
|----------|---------|
| `/health` | Health check (returns `{"status": "healthy"}`) |
| `/health/live` | Fast liveness probe (returns `{"status": "ok"}`) |
| `/health/ready` | Readiness probe (parallel feature checks) |
| `/health` | Deep health (parallel feature checks; `?deep=false` for liveness) |
| `/api/protocol` | Protocol version and capabilities |
| `/api/protocol/negotiate` | Capability negotiation |
| `/api/features` | List of registered features |
+5 -2
View File
@@ -276,10 +276,13 @@ aiui features list # List all registered features
aiui features status # Feature health summary
```
### `aiui health`
### `aiui health-check`
Check server health. Can also be invoked as `aiui health` for convenience.
```bash
aiui health [--detailed] # Check server health
aiui health-check [--detailed] # Check server health
aiui health [--detailed] # Alias for health-check
```
### `aiui session-ext`
+1 -1
View File
@@ -101,6 +101,6 @@ If something isn't working:
```bash
aiui doctor # Check server health
aiui health --detailed # Detailed health check
aiui health-check --detailed # Detailed health check (alias: aiui health)
aiui test all # Run all integration tests
```
+209
View File
@@ -0,0 +1,209 @@
# Health Endpoints Guide
## Overview
PraisonAIUI provides multiple health endpoints optimized for different use cases:
- **`/health/live`** - Fast liveness check (<500ms)
- **`/health/ready`** - Readiness check with optional deep checks
- **`/health`** - Legacy endpoint (backward compatibility)
## Endpoints
### Liveness Check: `/health/live`
Fast endpoint to verify the server is running. Returns immediately with minimal processing.
**Use for:**
- Load balancer health probes
- Kubernetes liveness probes
- High-frequency monitoring
**Response time:** <500ms (typically <100ms)
**Example:**
```bash
curl http://localhost:8082/health/live
```
**Response:**
```json
{
"status": "ok",
"timestamp": "2024-01-20T10:30:00.000Z"
}
```
### Readiness Check: `/health/ready`
Verifies the server is ready to handle requests. Performs parallel health checks on all features with timeout protection.
**Use for:**
- Kubernetes readiness probes
- Pre-deployment validation
- Detailed diagnostics
**Query parameters:**
- `?deep=false` - Skip feature checks for faster response
**Response time:**
- With deep checks: <1s (parallel execution)
- Without deep checks: <200ms
**Example:**
```bash
# Full readiness check
curl http://localhost:8082/health/ready
# Basic readiness (no feature checks)
curl http://localhost:8082/health/ready?deep=false
```
**Response with deep checks:**
```json
{
"status": "ok",
"timestamp": "2024-01-20T10:30:00.000Z",
"provider": {
"name": "PraisonAIProvider",
"status": "ok"
},
"features": {
"agents": {"status": "ok", "feature": "agents"},
"chat": {"status": "ok", "feature": "chat"},
"memory": {"healthy": false, "detail": "timeout"}
}
}
```
### API Health: `/api/health`
Cached version of readiness check for dashboard polling. 30-second TTL to reduce load.
**Use for:**
- Dashboard health indicators
- Frequent client polling
- Status pages
**Response:** Same as `/health/ready` but cached for 30 seconds
## Deployment Configurations
### Kubernetes
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: praisonaiui
spec:
template:
spec:
containers:
- name: praisonaiui
image: praisonaiui:latest
livenessProbe:
httpGet:
path: /health/live
port: 8082
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2
readinessProbe:
httpGet:
path: /health/ready?deep=false
port: 8082
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
```
### AWS ALB/ELB
```terraform
resource "aws_lb_target_group" "praisonaiui" {
health_check {
enabled = true
path = "/health/live"
interval = 30
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 2
}
}
```
### Docker Compose
```yaml
services:
praisonaiui:
image: praisonaiui:latest
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8082/health/live"]
interval: 30s
timeout: 5s
retries: 3
start_period: 10s
```
### Nginx
```nginx
upstream praisonaiui {
server backend1:8082 max_fails=2 fail_timeout=5s;
server backend2:8082 max_fails=2 fail_timeout=5s;
# Health check configuration
check interval=5000 rise=2 fall=3 timeout=2000 type=http;
check_http_send "GET /health/live HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx;
}
```
## Migration Guide
If you're upgrading from a version using only `/health`:
1. **Update monitoring:** Change health checks to use `/health/live` for liveness
2. **Update readiness:** Use `/health/ready?deep=false` for readiness probes
3. **Update dashboards:** Point status indicators to `/api/health` (cached)
4. **Backward compatibility:** The `/health` endpoint remains available but may be slow
## Performance Characteristics
| Endpoint | Cold Start | Warm (p50) | Warm (p95) | Use Case |
|----------|------------|------------|------------|----------|
| `/health/live` | <100ms | <20ms | <50ms | Liveness probes |
| `/health/ready?deep=false` | <200ms | <50ms | <100ms | Basic readiness |
| `/health/ready` | <1s | <500ms | <800ms | Full diagnostics |
| `/api/health` (cached) | <1s* | <20ms | <50ms | Dashboard polling |
*First request only, then cached for 30s
## Troubleshooting
### Slow `/health` responses
If you're seeing slow responses on the legacy `/health` endpoint:
- Switch to `/health/live` for fast checks
- Use `/health/ready?deep=false` if you don't need feature health
- Enable caching with `/api/health` for frequent polling
### Feature timeouts
Features that timeout in `/health/ready` will show:
```json
{
"feature_name": {
"healthy": false,
"detail": "timeout"
}
}
```
This indicates the feature took longer than 500ms to respond. The server is still operational, but that specific feature may be experiencing issues.
### High latency under load
Under high load, use `/health/live` exclusively for monitoring. It has minimal overhead and won't impact performance.
+1 -1
View File
@@ -23,7 +23,7 @@ See also: [`docs/examples/basic.md`](../docs/examples/basic.md) and [`docs/examp
| 09 | [widget](python/09-widget/) | Copilot/sidebar mode | `aiui run app.py` |
| 10 | [feature-showcase](python/10-feature-showcase/) | All protocol features seeded, `create_app()` | `aiui run app.py` |
| 11 | [ui-integration](python/11-ui-integration/) | Gradio ASGI mount, Streamlit iframe, REST embedding | `aiui run app.py` |
| 12 | [agent-dashboard](python/12-agent-dashboard/) | OpenClaw-style rendered HTML admin panel | `python app.py` |
| 12 | ~~agent-dashboard~~ → Use [dashboard-test](python/15-dashboard-test/) | ~~Removed in v0.5.0 (XSS vulnerability)~~ | See example 15 |
| 13 | [real-dashboard](python/13-real-dashboard/) | Real PraisonAI Agent + native dashboard + live metrics | `aiui run app.py --style dashboard` |
| 14 | [all-features](python/14-all-features/) | Integration test suite for all 16 feature APIs | `python app.py` |
| 15 | [dashboard-test](python/15-dashboard-test/) | Protocol-first page registration, view registry | `python app.py` |
-508
View File
@@ -1,508 +0,0 @@
"""Agent Dashboard — OpenClaw/agent-ui style admin panel.
⚠️ DEPRECATED: This example uses unsafe innerHTML rendering and will be removed in v0.5.0.
For secure admin dashboards, use examples/15-dashboard-test/ or examples/13-real-dashboard/
which use the component-based rendering model.
What's New (vs full-dashboard/):
• Rendered HTML dashboard — not just JSON APIs
• Sidebar navigation with live page switching
• Status badges, toggle buttons, real-time data
• Glassmorphism dark theme
• All data from PraisonAIUI feature APIs
Run:
python app.py
# Dashboard at http://localhost:8082
"""
import os
import sys
import warnings
import uvicorn
from starlette.responses import HTMLResponse
from starlette.routing import Route
from praisonaiui.server import create_app
# Use shared seed data helper
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
from _shared.seed_data import seed_demo_data
# ── HTML Template ────────────────────────────────────────────────────
DASHBOARD_HTML = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Agent Dashboard — PraisonAIUI</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #0a0a12;
--bg-card: #12121f;
--bg-hover: #1a1a2e;
--border: #2a2a4a;
--text-primary: #e0e0e8;
--text-secondary: #94a3b8;
--accent-purple: #7c3aed;
--accent-cyan: #06b6d4;
--accent-green: #22c55e;
--accent-amber: #f59e0b;
--accent-red: #ef4444;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: 'Inter', -apple-system, sans-serif; background: var(--bg-primary);
color: var(--text-primary); display: flex; min-height: 100vh; }
/* Sidebar */
.sidebar { width: 240px; background: var(--bg-card); border-right: 1px solid var(--border);
display: flex; flex-direction: column; position: fixed; height: 100vh; z-index: 10; }
.sidebar-header { padding: 1.25rem; border-bottom: 1px solid var(--border); }
.sidebar-header h1 { font-size: 1.1rem; font-weight: 700;
background: linear-gradient(90deg, var(--accent-purple), var(--accent-cyan));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.sidebar-header p { font-size: 0.7rem; color: var(--text-secondary); margin-top: 2px; }
.nav-group { padding: 0.75rem 0; }
.nav-group-label { font-size: 0.65rem; color: var(--text-secondary); text-transform: uppercase;
letter-spacing: 0.1em; padding: 0 1rem; margin-bottom: 0.25rem; font-weight: 600; }
.nav-item { display: flex; align-items: center; gap: 0.6rem; padding: 0.5rem 1rem;
cursor: pointer; transition: all 0.2s; font-size: 0.85rem; color: var(--text-secondary);
border-left: 3px solid transparent; }
.nav-item:hover { background: var(--bg-hover); color: var(--text-primary); }
.nav-item.active { background: var(--bg-hover); color: var(--accent-purple);
border-left-color: var(--accent-purple); font-weight: 500; }
.nav-icon { font-size: 1rem; width: 1.5rem; text-align: center; }
.nav-badge { margin-left: auto; background: var(--accent-purple); color: white;
font-size: 0.65rem; padding: 1px 6px; border-radius: 10px; }
/* Main content */
.main { margin-left: 240px; flex: 1; padding: 1.5rem; }
.page-header { margin-bottom: 1.5rem; }
.page-header h2 { font-size: 1.5rem; font-weight: 600; }
.page-header p { color: var(--text-secondary); font-size: 0.9rem; margin-top: 0.25rem; }
/* Cards grid */
.stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem; margin-bottom: 1.5rem; }
.stat-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px;
padding: 1.25rem; transition: border-color 0.3s; }
.stat-card:hover { border-color: var(--accent-purple); }
.stat-label { font-size: 0.75rem; color: var(--text-secondary); text-transform: uppercase;
letter-spacing: 0.05em; }
.stat-value { font-size: 1.75rem; font-weight: 700; margin-top: 0.25rem;
background: linear-gradient(90deg, var(--accent-purple), var(--accent-cyan));
-webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.stat-sub { font-size: 0.75rem; color: var(--text-secondary); margin-top: 0.25rem; }
/* Table */
.data-table { width: 100%; background: var(--bg-card); border: 1px solid var(--border);
border-radius: 10px; overflow: hidden; }
.data-table th { text-align: left; padding: 0.75rem 1rem; font-size: 0.75rem;
text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-secondary);
background: var(--bg-hover); border-bottom: 1px solid var(--border); font-weight: 600; }
.data-table td { padding: 0.75rem 1rem; font-size: 0.85rem; border-bottom: 1px solid var(--border); }
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:hover td { background: rgba(124, 58, 237, 0.05); }
/* Badges */
.badge { padding: 2px 8px; border-radius: 4px; font-size: 0.7rem; font-weight: 600; }
.badge-online { background: rgba(34, 197, 94, 0.15); color: var(--accent-green); }
.badge-offline { background: rgba(239, 68, 68, 0.15); color: var(--accent-red); }
.badge-enabled { background: rgba(34, 197, 94, 0.15); color: var(--accent-green); }
.badge-disabled { background: rgba(148, 163, 184, 0.15); color: var(--text-secondary); }
.badge-running { background: rgba(6, 182, 212, 0.15); color: var(--accent-cyan); }
.badge-platform { background: rgba(124, 58, 237, 0.1); color: var(--accent-purple); }
/* Toggle switch */
.toggle { position: relative; width: 36px; height: 20px; cursor: pointer; }
.toggle input { display: none; }
.toggle-slider { position: absolute; inset: 0; background: #333; border-radius: 10px;
transition: 0.3s; }
.toggle-slider::before { content: ''; position: absolute; left: 2px; top: 2px; width: 16px;
height: 16px; background: white; border-radius: 50%; transition: 0.3s; }
.toggle input:checked + .toggle-slider { background: var(--accent-purple); }
.toggle input:checked + .toggle-slider::before { transform: translateX(16px); }
/* Section */
.section { margin-bottom: 1.5rem; }
.section-title { font-size: 0.85rem; color: var(--text-secondary); font-weight: 600;
text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.75rem;
display: flex; align-items: center; gap: 0.5rem; }
.section-count { background: var(--accent-purple); color: white; font-size: 0.65rem;
padding: 1px 6px; border-radius: 10px; }
/* Page display */
.page { display: none; }
.page.active { display: block; }
/* Animations */
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); } }
.page.active { animation: fadeIn 0.3s ease; }
/* Feature list */
.feature-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem; }
.feature-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px;
padding: 1.25rem; transition: all 0.3s; }
.feature-card:hover { border-color: var(--accent-purple); transform: translateY(-2px); }
.feature-name { font-weight: 600; margin-bottom: 0.25rem; }
.feature-desc { font-size: 0.8rem; color: var(--text-secondary); }
.feature-routes { margin-top: 0.75rem; font-size: 0.75rem; color: var(--text-secondary); }
.feature-route { font-family: monospace; font-size: 0.7rem; color: var(--accent-cyan);
display: block; margin-top: 2px; }
/* Empty state */
.empty { text-align: center; padding: 3rem; color: var(--text-secondary); }
.empty-icon { font-size: 3rem; margin-bottom: 1rem; }
</style>
</head>
<body>
<aside class="sidebar">
<div class="sidebar-header">
<h1>🤖 Agent Dashboard</h1>
<p>PraisonAIUI Admin</p>
</div>
<nav>
<div class="nav-group">
<div class="nav-group-label">Overview</div>
<div class="nav-item active" data-page="overview">
<span class="nav-icon">📊</span> Overview
</div>
<div class="nav-item" data-page="features">
<span class="nav-icon">⚡</span> Features
<span class="nav-badge" id="nav-feature-count">—</span>
</div>
</div>
<div class="nav-group">
<div class="nav-group-label">Messaging</div>
<div class="nav-item" data-page="channels">
<span class="nav-icon">📡</span> Channels
<span class="nav-badge" id="nav-channel-count">—</span>
</div>
</div>
<div class="nav-group">
<div class="nav-group-label">Infrastructure</div>
<div class="nav-item" data-page="nodes">
<span class="nav-icon">🖥️</span> Nodes
<span class="nav-badge" id="nav-node-count">—</span>
</div>
<div class="nav-item" data-page="instances">
<span class="nav-icon">📻</span> Instances
<span class="nav-badge" id="nav-instance-count">—</span>
</div>
</div>
<div class="nav-group">
<div class="nav-group-label">Automation</div>
<div class="nav-item" data-page="schedules">
<span class="nav-icon">⏰</span> Schedules
<span class="nav-badge" id="nav-schedule-count">—</span>
</div>
<div class="nav-item" data-page="skills">
<span class="nav-icon">🧩</span> Skills
<span class="nav-badge" id="nav-skill-count">—</span>
</div>
</div>
</nav>
</aside>
<main class="main">
<!-- OVERVIEW PAGE -->
<div class="page active" id="page-overview">
<div class="page-header">
<h2>Overview</h2>
<p>Real-time system status at a glance</p>
</div>
<div class="stats-grid" id="overview-stats"></div>
</div>
<!-- FEATURES PAGE -->
<div class="page" id="page-features">
<div class="page-header">
<h2>Features</h2>
<p>All registered protocol feature modules</p>
</div>
<div class="feature-grid" id="features-grid"></div>
</div>
<!-- CHANNELS PAGE -->
<div class="page" id="page-channels">
<div class="page-header">
<h2>Channels</h2>
<p>Multi-platform messaging channels</p>
</div>
<div class="section">
<div class="section-title">Active Channels <span class="section-count" id="channel-count">0</span></div>
<table class="data-table" id="channels-table">
<thead><tr>
<th>Name</th><th>Platform</th><th>Status</th><th>Enabled</th><th>Last Activity</th>
</tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- NODES PAGE -->
<div class="page" id="page-nodes">
<div class="page-header">
<h2>Nodes</h2>
<p>Execution nodes and agent bindings</p>
</div>
<div class="section">
<div class="section-title">Registered Nodes <span class="section-count" id="node-count">0</span></div>
<table class="data-table" id="nodes-table">
<thead><tr>
<th>Name</th><th>Host</th><th>Platform</th><th>Status</th><th>Agents</th><th>Policy</th>
</tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- INSTANCES PAGE -->
<div class="page" id="page-instances">
<div class="page-header">
<h2>Instances</h2>
<p>Connected clients and workers</p>
</div>
<div class="section">
<div class="section-title">Connected Instances <span class="section-count" id="instance-count">0</span></div>
<table class="data-table" id="instances-table">
<thead><tr>
<th>ID</th><th>Host</th><th>Platform</th><th>Mode</th><th>Roles</th><th>Last Seen</th>
</tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- SCHEDULES PAGE -->
<div class="page" id="page-schedules">
<div class="page-header">
<h2>Schedules</h2>
<p>Cron jobs and scheduled tasks</p>
</div>
<div class="section">
<div class="section-title">Scheduled Jobs <span class="section-count" id="schedule-count">0</span></div>
<table class="data-table" id="schedules-table">
<thead><tr>
<th>Name</th><th>Schedule</th><th>Agent</th><th>Enabled</th><th>Runs</th><th>Last Run</th>
</tr></thead>
<tbody></tbody>
</table>
</div>
</div>
<!-- SKILLS PAGE -->
<div class="page" id="page-skills">
<div class="page-header">
<h2>Skills</h2>
<p>Registered agent capabilities</p>
</div>
<div class="section">
<div class="section-title">Agent Skills <span class="section-count" id="skill-count">0</span></div>
<table class="data-table" id="skills-table">
<thead><tr>
<th>Name</th><th>ID</th><th>Enabled</th><th>Config</th>
</tr></thead>
<tbody></tbody>
</table>
</div>
</div>
</main>
<script>
// ── Navigation ──────────────────────────────────────────────────────
document.querySelectorAll('.nav-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelectorAll('.nav-item').forEach(i => i.classList.remove('active'));
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
item.classList.add('active');
document.getElementById('page-' + item.dataset.page).classList.add('active');
});
});
// ── Data loading ────────────────────────────────────────────────────
async function fetchJSON(url) {
const r = await fetch(url);
return r.json();
}
function timeAgo(ts) {
if (!ts) return '—';
const s = Math.floor(Date.now() / 1000 - ts);
if (s < 60) return s + 's ago';
if (s < 3600) return Math.floor(s / 60) + 'm ago';
if (s < 86400) return Math.floor(s / 3600) + 'h ago';
return Math.floor(s / 86400) + 'd ago';
}
async function loadOverview() {
const [features, channels, nodes, instances, schedules, skills] = await Promise.all([
fetchJSON('/api/features'),
fetchJSON('/api/channels'),
fetchJSON('/api/nodes'),
fetchJSON('/api/instances'),
fetchJSON('/api/schedules'),
fetchJSON('/api/skills'),
]);
// Nav badges
document.getElementById('nav-feature-count').textContent = features.count;
document.getElementById('nav-channel-count').textContent = channels.count;
document.getElementById('nav-node-count').textContent = nodes.count;
document.getElementById('nav-instance-count').textContent = instances.count;
document.getElementById('nav-schedule-count').textContent = schedules.count;
document.getElementById('nav-skill-count').textContent = skills.count;
// Overview stats
const statsEl = document.getElementById('overview-stats');
const runningChannels = channels.channels.filter(c => c.running).length;
const onlineNodes = nodes.nodes.filter(n => n.status === 'online').length;
statsEl.innerHTML = `
<div class="stat-card">
<div class="stat-label">Features</div>
<div class="stat-value">${features.count}</div>
<div class="stat-sub">Protocol modules active</div>
</div>
<div class="stat-card">
<div class="stat-label">Channels</div>
<div class="stat-value">${channels.count}</div>
<div class="stat-sub">${runningChannels} running</div>
</div>
<div class="stat-card">
<div class="stat-label">Nodes</div>
<div class="stat-value">${nodes.count}</div>
<div class="stat-sub">${onlineNodes} online</div>
</div>
<div class="stat-card">
<div class="stat-label">Instances</div>
<div class="stat-value">${instances.count}</div>
<div class="stat-sub">Connected clients & workers</div>
</div>
<div class="stat-card">
<div class="stat-label">Schedules</div>
<div class="stat-value">${schedules.count}</div>
<div class="stat-sub">Cron jobs registered</div>
</div>
<div class="stat-card">
<div class="stat-label">Skills</div>
<div class="stat-value">${skills.count}</div>
<div class="stat-sub">Agent capabilities</div>
</div>
`;
// Features grid
const fg = document.getElementById('features-grid');
fg.innerHTML = features.features.map(f => `
<div class="feature-card">
<div class="feature-name">${f.name}</div>
<div class="feature-desc">${f.description}</div>
<div class="feature-routes">${f.routes.length} routes
${f.routes.slice(0, 3).map(r => '<span class="feature-route">' + r + '</span>').join('')}
${f.routes.length > 3 ? '<span class="feature-route">... +' + (f.routes.length - 3) + ' more</span>' : ''}
</div>
</div>
`).join('');
// Channels table
document.getElementById('channel-count').textContent = channels.count;
document.querySelector('#channels-table tbody').innerHTML = channels.channels.map(c => `
<tr>
<td><strong>${c.name}</strong></td>
<td><span class="badge badge-platform">${c.platform}</span></td>
<td>${c.running ? '<span class="badge badge-running">● running</span>' : '<span class="badge badge-offline">● stopped</span>'}</td>
<td>${c.enabled ? '<span class="badge badge-enabled">enabled</span>' : '<span class="badge badge-disabled">disabled</span>'}</td>
<td>${timeAgo(c.last_activity)}</td>
</tr>
`).join('');
// Nodes table
document.getElementById('node-count').textContent = nodes.count;
document.querySelector('#nodes-table tbody').innerHTML = nodes.nodes.map(n => `
<tr>
<td><strong>${n.name}</strong></td>
<td><code>${n.host}</code></td>
<td>${n.platform}</td>
<td>${n.status === 'online' ? '<span class="badge badge-online">● online</span>' : '<span class="badge badge-offline">● offline</span>'}</td>
<td>${(n.agents || []).map(a => '<span class="badge badge-platform">' + a + '</span> ').join('')}</td>
<td><span class="badge">${n.approval_policy}</span></td>
</tr>
`).join('');
// Instances table
document.getElementById('instance-count').textContent = instances.count;
document.querySelector('#instances-table tbody').innerHTML = instances.instances.map(i => `
<tr>
<td><code>${i.id}</code></td>
<td>${i.host}</td>
<td>${i.platform}</td>
<td><span class="badge badge-${i.mode === 'worker' ? 'running' : 'enabled'}">${i.mode}</span></td>
<td>${(i.roles || []).join(', ')}</td>
<td>${timeAgo(i.last_seen)}</td>
</tr>
`).join('');
// Schedules table
document.getElementById('schedule-count').textContent = schedules.count;
document.querySelector('#schedules-table tbody').innerHTML = schedules.schedules.map(s => `
<tr>
<td><strong>${s.name}</strong></td>
<td><code>${s.schedule}</code></td>
<td>${s.agent_id || '—'}</td>
<td>${s.enabled ? '<span class="badge badge-enabled">enabled</span>' : '<span class="badge badge-disabled">disabled</span>'}</td>
<td>${s.run_count || 0}</td>
<td>${timeAgo(s.last_run)}</td>
</tr>
`).join('');
// Skills table
document.getElementById('skill-count').textContent = skills.count;
document.querySelector('#skills-table tbody').innerHTML = skills.skills.map(s => `
<tr>
<td><strong>${s.name}</strong></td>
<td><code>${s.id}</code></td>
<td>${s.enabled ? '<span class="badge badge-enabled">enabled</span>' : '<span class="badge badge-disabled">disabled</span>'}</td>
<td><code>${JSON.stringify(s.config || {})}</code></td>
</tr>
`).join('');
}
// Auto-refresh every 10 seconds
loadOverview();
setInterval(loadOverview, 10000);
</script>
</body>
</html>"""
# ── Entry point ──────────────────────────────────────────────────────
seed_demo_data()
if __name__ == "__main__":
warnings.warn(
"examples/12-agent-dashboard uses unsafe innerHTML rendering and will be removed in v0.5.0. "
"Use examples/15-dashboard-test/ or examples/13-real-dashboard/ for secure admin dashboards.",
DeprecationWarning,
stacklevel=2
)
app = create_app()
# Add the dashboard HTML landing page
async def dashboard_landing(request):
return HTMLResponse(DASHBOARD_HTML)
app.routes.insert(0, Route("/", dashboard_landing, methods=["GET"]))
print("✅ Agent Dashboard at http://localhost:8082")
print("⚠️ DEPRECATED: This example uses unsafe innerHTML rendering")
print(" For secure dashboards, use examples/15-dashboard-test/")
print(" JSON API: http://localhost:8082/api/features")
host = os.getenv("HOST", "127.0.0.1")
uvicorn.run(app, host=host, port=8082, log_level="info")
+1 -1
View File
@@ -1,6 +1,6 @@
"""Real Agent Dashboard — PraisonAI agents + PraisonAIUI dashboard.
Unlike 12-agent-dashboard (mock data), this runs REAL PraisonAI agents.
This example uses secure component-based rendering with REAL PraisonAI agents.
Uses PraisonAIUI's native dashboard with @reply, @page, register_agent()
— all backed by actual LLM calls.
+1 -1
View File
@@ -1,6 +1,6 @@
[project]
name = "aiui"
version = "0.3.121"
version = "0.3.127"
description = "YAML-driven website generator - CLI and compiler"
readme = "README.md"
license = {text = "MIT"}
+100 -73
View File
@@ -9,6 +9,7 @@ import { ZoneWidgets } from './Widgets'
import { Toc } from './Toc'
import { Footer } from './Footer'
import { ChatLayout, AgentUILayout, CopilotWidget, PlaygroundLayout } from './layouts'
import { LocaleProvider } from './i18n'
function SkipLink({ enabled }: { enabled?: boolean }) {
if (!enabled) return null
@@ -31,84 +32,32 @@ export default function App() {
const [selectedItem, setSelectedItem] = useState<NavItem | null>(null)
const [activeItemPath, setActiveItemPath] = useState<string>('')
useEffect(() => {
async function loadManifests() {
try {
const [configRes, navRes, routesRes] = await Promise.all([
fetch('/ui-config.json'),
fetch('/docs-nav.json'),
fetch('/route-manifest.json'),
])
if (!configRes.ok || !navRes.ok || !routesRes.ok) {
throw new Error('Failed to load manifests')
// Helper function to find nav item by path
const findNavItemByPath = (navData: DocsNav, path: string): NavItem | null => {
const findItem = (items: NavItem[]): NavItem | null => {
for (const item of items) {
if (item.path === path || item.path === path.replace(/^\//, '')) {
return item
}
const configData = await configRes.json()
const navData = await navRes.json()
setConfig(configData)
setNav(navData)
setRoutes(await routesRes.json())
// Apply theme from YAML config
const theme = configData.site?.theme
applyTheme(
theme?.preset || 'zinc',
theme?.darkMode !== false,
theme?.radius || 'md'
)
// Handle initial URL path for SPA routing
const currentPath = window.location.pathname
if (currentPath && currentPath !== '/') {
// Find matching nav item by path
const findItem = (items: NavItem[]): NavItem | null => {
for (const item of items) {
if (item.path === currentPath || item.path === currentPath.replace(/^\//, '')) {
return item
}
if (item.children) {
const found = findItem(item.children)
if (found) return found
}
}
return null
}
// Search all nav groups
if (navData.items) {
for (const group of navData.items) {
if (group.children) {
const found = findItem(group.children)
if (found) {
setSelectedItem(found)
setActiveItemPath(found.path || found.title)
break
}
}
}
}
if (item.children) {
const found = findItem(item.children)
if (found) return found
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setLoading(false)
}
return null
}
loadManifests()
// Handle browser back/forward navigation
const handlePopState = () => {
const path = window.location.pathname
if (path === '/') {
setSelectedItem(null)
setActiveItemPath('')
// Search all nav groups
if (navData.items) {
for (const group of navData.items) {
if (group.children) {
const found = findItem(group.children)
if (found) return found
}
}
}
window.addEventListener('popstate', handlePopState)
return () => window.removeEventListener('popstate', handlePopState)
}, [])
return null
}
// Update SEO meta tags dynamically
const updateSEO = (title: string, path: string, description?: string) => {
@@ -116,7 +65,12 @@ export default function App() {
const titleTemplate = config.seo?.titleTemplate || '%s | %s'
const siteName = config.site?.title || 'Documentation'
if (titleTemplate.includes('%s')) {
document.title = titleTemplate.replace('%s', title).replace('%s', siteName)
// Replace first %s with page title, second %s (if exists) with site name
let formattedTitle = titleTemplate.replace('%s', title)
if (formattedTitle.includes('%s')) {
formattedTitle = formattedTitle.replace('%s', siteName)
}
document.title = formattedTitle
} else {
document.title = `${title} | ${siteName}`
}
@@ -181,6 +135,75 @@ export default function App() {
}
}
useEffect(() => {
async function loadManifests() {
try {
const [configRes, navRes, routesRes] = await Promise.all([
fetch('/ui-config.json'),
fetch('/docs-nav.json'),
fetch('/route-manifest.json'),
])
if (!configRes.ok || !navRes.ok || !routesRes.ok) {
throw new Error('Failed to load manifests')
}
const configData = await configRes.json()
const navData = await navRes.json()
setConfig(configData)
setNav(navData)
setRoutes(await routesRes.json())
// Apply theme from YAML config
const theme = configData.site?.theme
applyTheme(
theme?.preset || 'zinc',
theme?.darkMode !== false,
theme?.radius || 'md'
)
// Handle initial URL path for SPA routing
const currentPath = window.location.pathname
if (currentPath && currentPath !== '/') {
const found = findNavItemByPath(navData, currentPath)
if (found) {
setSelectedItem(found)
setActiveItemPath(found.path || found.title)
}
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setLoading(false)
}
}
loadManifests()
}, [])
// Handle browser back/forward navigation in a separate effect
useEffect(() => {
const handlePopState = () => {
const path = window.location.pathname
const found = findNavItemByPath(nav, path)
if (found) {
setSelectedItem(found)
setActiveItemPath(found.path || found.title)
// Use updateSEO function for consistency
updateSEO(found.title, path)
} else if (path === '/') {
setSelectedItem(null)
setActiveItemPath('')
// Reset title to site title
document.title = config.site?.title || 'Documentation'
}
}
window.addEventListener('popstate', handlePopState)
return () => window.removeEventListener('popstate', handlePopState)
}, [nav, config])
const handleItemClick = (item: NavItem) => {
setSelectedItem(item)
setActiveItemPath(item.path || item.title)
@@ -372,5 +395,9 @@ export default function App() {
}
}
return renderByStyle()
return (
<LocaleProvider config={config.i18n}>
{renderByStyle()}
</LocaleProvider>
)
}
+2
View File
@@ -1,5 +1,6 @@
// Header component
import type { UIConfig } from './types'
import { LocaleSwitcher } from './i18n'
export function Header({ config }: { config: UIConfig }) {
// Resolve header component via template slot ref (e.g., header_main)
@@ -56,6 +57,7 @@ export function Header({ config }: { config: UIConfig }) {
</nav>
<div className="flex items-center gap-2">
<LocaleSwitcher />
{header?.cta && (
<a
href={header.cta.href}
+15
View File
@@ -320,6 +320,21 @@ function handleStoreEvent(
break
}
case 'approval_required':
case 'approval_pending':
case 'run_paused': {
// Check if this is an approval event via extra_data
const extra = event.extra_data as Record<string, unknown> | undefined
if (extra?.type === 'approval_required') {
// Emit custom event for dashboard to handle
const approvalData = extra
window.dispatchEvent(new CustomEvent('aiui:approval-required', {
detail: approvalData
}))
}
break
}
case 'end':
case 'done':
case 'run_cancelled':
+133
View File
@@ -0,0 +1,133 @@
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react'
import type { I18nConfig } from '../types'
interface LocaleContextValue {
locale: string
locales: string[]
rtlLocales: string[]
setLocale: (locale: string) => void
t: (key: string, variables?: Record<string, string>) => string
isRTL: boolean
strings: Record<string, string>
loading: boolean
}
const LocaleContext = createContext<LocaleContextValue | null>(null)
export function useLocale() {
const context = useContext(LocaleContext)
if (!context) {
throw new Error('useLocale must be used within LocaleProvider')
}
return context
}
interface LocaleProviderProps {
config?: I18nConfig
children: React.ReactNode
}
export function LocaleProvider({ config, children }: LocaleProviderProps) {
const [locale, setLocaleState] = useState(config?.defaultLocale || 'en')
const [strings, setStrings] = useState<Record<string, string>>({})
const [loading, setLoading] = useState(false)
const locales = config?.locales || ['en']
const rtlLocales = config?.rtlLocales || []
const isRTL = rtlLocales.includes(locale)
// Apply RTL to document
useEffect(() => {
if (isRTL) {
document.documentElement.dir = 'rtl'
} else {
document.documentElement.dir = 'ltr'
}
}, [isRTL])
// Load strings from API when locale changes
useEffect(() => {
const abortController = new AbortController()
async function loadStrings() {
if (!config || locales.length <= 1) return
setLoading(true)
try {
const response = await fetch(`/api/i18n/strings/${locale}`, {
signal: abortController.signal
})
if (response.ok) {
const data = await response.json()
setStrings(data.strings || {})
}
} catch (error) {
// Ignore abort errors
if (error instanceof Error && error.name === 'AbortError') return
console.error('Failed to load translations:', error)
} finally {
setLoading(false)
}
}
loadStrings()
// Cleanup: abort the request if component unmounts or locale changes
return () => abortController.abort()
}, [locale, config, locales.length])
const setLocale = useCallback(async (newLocale: string) => {
// Validate locale format (e.g., 'en', 'en-US', 'zh-CN')
if (!/^[a-z]{2}(-[A-Z]{2})?$/.test(newLocale)) {
console.error('Invalid locale format:', newLocale)
return
}
if (!locales.includes(newLocale)) return
setLocaleState(newLocale)
// Update server-side locale
try {
await fetch('/api/i18n/locale', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ locale: newLocale })
})
} catch (error) {
console.error('Failed to update server locale:', error)
}
}, [locales])
const t = useCallback((key: string, variables?: Record<string, string>) => {
let text = strings[key] || key
// Apply variable substitution with proper escaping
if (variables) {
Object.entries(variables).forEach(([k, v]) => {
// Escape special regex characters in the key to prevent injection
const escapedKey = k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
text = text.replace(new RegExp(`{${escapedKey}}`, 'g'), v)
})
}
return text
}, [strings])
const value: LocaleContextValue = {
locale,
locales,
rtlLocales,
setLocale,
t,
isRTL,
strings,
loading
}
return (
<LocaleContext.Provider value={value}>
{children}
</LocaleContext.Provider>
)
}
+25
View File
@@ -0,0 +1,25 @@
import { useLocale } from './LocaleProvider'
export function LocaleSwitcher() {
const { locale, locales, setLocale } = useLocale()
// Don't show switcher if only one locale
if (locales.length <= 1) {
return null
}
return (
<select
value={locale}
onChange={(e) => setLocale(e.target.value)}
className="px-3 py-1.5 rounded-md border border-border bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary/20"
aria-label="Language"
>
{locales.map((loc) => (
<option key={loc} value={loc}>
{loc.toUpperCase()}
</option>
))}
</select>
)
}
+2
View File
@@ -0,0 +1,2 @@
export { LocaleProvider, useLocale } from './LocaleProvider'
export { LocaleSwitcher } from './LocaleSwitcher'
+1 -1
View File
@@ -1,3 +1,3 @@
"""Version information."""
__version__ = "0.3.114"
__version__ = "0.3.124"
+122 -41
View File
@@ -1,6 +1,7 @@
"""CLI module - Typer-based command line interface."""
import asyncio
import sys
from pathlib import Path
from typing import Optional
@@ -10,6 +11,39 @@ from rich.panel import Panel
from praisonaiui.__version__ import __version__
# Configure UTF-8 on Windows to handle Unicode output
if sys.platform == "win32":
try:
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
except Exception:
pass
def _supports_unicode() -> bool:
"""Return True if stdout can encode non-ASCII (UTF-aware) output.
Legacy Windows consoles (cp1252/cp437) cannot encode emoji/box-drawing
characters and raise UnicodeEncodeError. When reconfigure() above succeeds
the encoding becomes utf-8 and rich symbols are kept; otherwise we fall
back to ASCII so output never crashes.
"""
try:
encoding = sys.stdout.encoding or ""
except Exception:
return False
return "utf" in encoding.lower()
def _icon(symbol: str, fallback: str) -> str:
"""Return ``symbol`` on UTF-aware terminals, else ``fallback`` (ASCII).
Preserves the rich emoji UI on macOS/Linux and modern Windows terminals
while degrading gracefully on legacy code pages instead of crashing.
"""
return symbol if _supports_unicode() else fallback
app = typer.Typer(
name="aiui",
help="PraisonAIUI - YAML-driven website generator",
@@ -123,7 +157,7 @@ routes:
template: "docs"
"""
config_path.write_text(minimal_config)
config_path.write_text(minimal_config, encoding="utf-8")
console.print(Panel(f"[green]✓[/green] Created {config_path}", title="Success"))
@@ -213,7 +247,7 @@ def validate(
raise typer.Exit(code=2)
try:
with open(config) as f:
with open(config, encoding="utf-8") as f:
data = yaml.safe_load(f)
cfg = Config.model_validate(data)
except Exception as e:
@@ -264,7 +298,7 @@ def build(
raise typer.Exit(code=2)
try:
with open(config) as f:
with open(config, encoding="utf-8") as f:
data = yaml.safe_load(f)
cfg = Config.model_validate(data)
except Exception as e:
@@ -341,7 +375,7 @@ def serve(
# Build first unless --no-build
if not no_build:
console.print("[yellow]⏳[/yellow] Building manifests...")
console.print(f"[yellow]{_icon('⏳', '...')}[/yellow] Building manifests...")
build(config=config, output=output, minify=False)
# Check if output directory exists
@@ -369,7 +403,9 @@ def serve(
raise typer.Exit(code=4)
if actual_port != port:
console.print(f"[yellow]⚠️[/yellow] Port {port} in use, using {actual_port}")
console.print(
f"[yellow]{_icon('⚠️', '[WARNING]')}[/yellow] Port {port} in use, using {actual_port}"
)
# Build Starlette app with security
from starlette.applications import Starlette
@@ -549,7 +585,7 @@ def dev(
)
tmp_cfg = None
if has_override and config_file.exists():
with open(config_file) as f:
with open(config_file, encoding="utf-8") as f:
config = yaml.safe_load(f.read())
if "site" not in config:
config["site"] = {}
@@ -562,7 +598,7 @@ def dev(
if dark_mode is not None:
config["site"]["theme"]["darkMode"] = dark_mode
tmp_cfg = example_path / ".aiui.tmp.yaml"
with open(tmp_cfg, "w") as f:
with open(tmp_cfg, "w", encoding="utf-8") as f:
yaml.dump(config, f, default_flow_style=False)
build_config = str(tmp_cfg)
@@ -945,7 +981,7 @@ def dev(
yaml_path = examples_dir / example_name / "aiui.template.yaml"
yaml_content = ""
if yaml_path.exists():
with open(yaml_path) as f:
with open(yaml_path, encoding="utf-8") as f:
yaml_content = f.read()
self.send_response(200)
self.send_header("Content-type", "application/json")
@@ -989,7 +1025,7 @@ def dev(
if example_name and example_name in examples:
yaml_path = examples_dir / example_name / "aiui.template.yaml"
# Write the new YAML
with open(yaml_path, "w") as f:
with open(yaml_path, "w", encoding="utf-8") as f:
f.write(yaml_content)
console.print(
f"[yellow]Rebuilding {example_name} with edited YAML...[/yellow]"
@@ -1348,10 +1384,10 @@ def run(
if is_yaml:
# Load YAML chat configuration
console.print(f"[yellow]⏳[/yellow] Loading {app_file}...")
console.print(f"[yellow]{_icon('⏳', '...')}[/yellow] Loading {app_file}...")
import yaml as _yaml
with open(app_file) as f:
with open(app_file, encoding="utf-8") as f:
chat_yaml = _yaml.safe_load(f) or {}
# Apply config from YAML (override CLI defaults only if present)
@@ -1386,7 +1422,7 @@ def run(
is_chat_mode = True
else:
# Load the user's app module FIRST to register callbacks
console.print(f"[yellow]⏳[/yellow] Loading {app_file}...")
console.print(f"[yellow]{_icon('⏳', '...')}[/yellow] Loading {app_file}...")
spec = importlib.util.spec_from_file_location("user_app", app_file)
if spec is None or spec.loader is None:
console.print(f"[red]Error:[/red] Could not load {app_file}")
@@ -1452,7 +1488,7 @@ def run(
# Build static files only if --config was explicitly provided
if config is not None and config.exists():
console.print("[yellow]⏳[/yellow] Building static files...")
console.print(f"[yellow]{_icon('⏳', '...')}[/yellow] Building static files...")
build(config=config, output=output, minify=False)
# If chat mode: set up chat frontend directly from templates
@@ -1484,12 +1520,14 @@ def run(
},
"style": style,
}
with open(ui_config_path, "w") as f:
with open(ui_config_path, "w", encoding="utf-8") as f:
_json.dump(ui_cfg, f, indent=2)
# Write minimal route-manifest and docs-nav so the SPA doesn't error
(output / "route-manifest.json").write_text('{"routes": []}')
(output / "docs-nav.json").write_text('{"items": []}')
(output / "route-manifest.json").write_text(
'{"routes": []}', encoding="utf-8"
)
(output / "docs-nav.json").write_text('{"items": []}', encoding="utf-8")
# Find available port
def find_available_port(start_port: int, max_attempts: int = 10) -> int:
@@ -1510,7 +1548,9 @@ def run(
raise typer.Exit(code=4)
if actual_port != port:
console.print(f"[yellow]⚠️[/yellow] Port {port} in use, using {actual_port}")
console.print(
f"[yellow]{_icon('⚠️', '[WARNING]')}[/yellow] Port {port} in use, using {actual_port}"
)
static_dir = output if output.exists() else None
@@ -1799,7 +1839,7 @@ def sessions_messages(
# ---------------------------------------------------------------------------
# Subcommand: health
# ---------------------------------------------------------------------------
@app.command()
@app.command("health-check")
def health_check(
server: str = typer.Option(
"http://127.0.0.1:8000",
@@ -1818,15 +1858,17 @@ def health_check(
import json as _json
from urllib.request import urlopen
from praisonaiui.health_utils import is_success_status
try:
with urlopen(f"{server}/health") as resp:
with urlopen(f"{server}/health/live") as resp:
data = _json.loads(resp.read())
status = data.get("status", "unknown")
ts = data.get("timestamp", "")
if status == "healthy":
if is_success_status(status):
console.print(
Panel.fit(
f"Server: [green]healthy[/green] ({ts})",
f"Server: [green]{status}[/green] ({ts})",
title="Health Check",
border_style="green",
)
@@ -1853,10 +1895,36 @@ def health_check(
health = feat.get("health", {})
healthy = health.get("healthy", True)
detail = health.get("detail", "ok")
icon = "[green]✅[/green]" if healthy else "[yellow]⚠️[/yellow]"
icon = (
f"[green]{_icon('✅', '[OK]')}[/green]"
if healthy
else f"[yellow]{_icon('⚠️', '[WARNING]')}[/yellow]"
)
console.print(f" {icon} {name}: {detail}")
except Exception as e:
console.print(f"[yellow]⚠️[/yellow] Could not fetch feature health: {e}")
console.print(
f"[yellow]{_icon('⚠️', '[WARNING]')}[/yellow] Could not fetch feature health: {e}"
)
# Register "health" as an alias for health-check (backward compatibility)
@app.command("health")
def health(
server: str = typer.Option(
"http://127.0.0.1:8000",
"--server",
"-s",
help="Server URL",
),
detailed: bool = typer.Option(
False,
"--detailed",
"-d",
help="Show per-feature health",
),
) -> None:
"""Check server health (alias for health-check)."""
health_check(server=server, detailed=detailed)
# ---------------------------------------------------------------------------
@@ -1883,13 +1951,15 @@ def provider_status(
import json as _json
from urllib.request import urlopen
from praisonaiui.health_utils import is_success_status
try:
with urlopen(f"{server}/api/provider") as resp:
data = _json.loads(resp.read())
console.print(f"[bold]Provider:[/bold] {data.get('name', 'unknown')}")
console.print(f"[bold]Module:[/bold] {data.get('module', 'unknown')}")
status = data.get("status", "unknown")
color = "green" if status == "ok" else "red"
color = "green" if is_success_status(status) else "red"
console.print(f"[bold]Status:[/bold] [{color}]{status}[/{color}]")
agents = data.get("agents", [])
if agents:
@@ -1920,7 +1990,7 @@ def provider_health(
from urllib.request import urlopen
try:
with urlopen(f"{server}/health") as resp:
with urlopen(f"{server}/health/ready?deep=false") as resp:
data = _json.loads(resp.read())
provider = data.get("provider", {})
console.print(f"[green]✓[/green] Provider: {provider.get('name', 'unknown')}")
@@ -2021,12 +2091,14 @@ app.add_typer(features_app, name="features")
@features_app.command("list")
def features_list(server: str = _SERVER_OPT) -> None:
"""List all registered protocol features."""
from praisonaiui.health_utils import is_success_status
try:
data = _api_get(server, "/api/features")
for f in data.get("features", []):
h = f.get("health", {})
status = h.get("status", "?")
color = "green" if status == "ok" else "red"
color = "green" if is_success_status(status) else "red"
console.print(f" [{color}]●[/{color}] {f['name']} — {f.get('description', '')}")
if f.get("routes"):
console.print(f" routes: {', '.join(f['routes'])}")
@@ -2038,15 +2110,17 @@ def features_list(server: str = _SERVER_OPT) -> None:
@features_app.command("status")
def features_status(server: str = _SERVER_OPT) -> None:
"""Show feature health summary."""
from praisonaiui.health_utils import is_success_status
try:
data = _api_get(server, "/api/features")
features = data.get("features", [])
ok = sum(1 for f in features if f.get("health", {}).get("status") == "ok")
ok = sum(1 for f in features if is_success_status(f.get("health", {}).get("status")))
console.print(f"Features: {ok}/{len(features)} healthy")
for f in features:
h = f.get("health", {})
status = h.get("status", "?")
color = "green" if status == "ok" else "red"
color = "green" if is_success_status(status) else "red"
console.print(f" [{color}]●[/{color}] {f['name']}: {status}")
except Exception as e:
console.print(f"[red]✗[/red] {e}")
@@ -3018,13 +3092,15 @@ def doctor(
return {"name": name, "status": "fail", "detail": str(e)}
# Check 1: Server Health
from praisonaiui.health_utils import is_success_status
def _health_extractor(data):
status = data.get("status", "unknown")
if status == "healthy":
if is_success_status(status):
return "pass", f"running on {server.split('://')[-1]}"
return "warn", f"status: {status}"
checks.append(_check("Server Health", "/health", _health_extractor))
checks.append(_check("Server Health", "/health/live", _health_extractor))
# Check 2: Provider Status
def _provider_extractor(data):
@@ -3035,11 +3111,14 @@ def doctor(
# Check 3: Gateway Status
def _gateway_extractor(data):
gw_type = data.get("type", "unknown")
agents = data.get("agents", 0)
return "pass", f"{gw_type} ({agents} agents)"
status = data.get("status", "unknown")
provider_type = data.get("provider", data.get("name", "unknown"))
agents_list = data.get("agents", [])
agent_count = len(agents_list) if isinstance(agents_list, list) else 0
check_status = "pass" if status in ("ok", "healthy") else "warn"
return check_status, f"{provider_type} ({agent_count} agents)"
checks.append(_check("Gateway Status", "/api/provider/health", _gateway_extractor))
checks.append(_check("Gateway Status", "/api/provider", _gateway_extractor))
# Check 4: Features Loaded
def _features_extractor(data):
@@ -3099,21 +3178,23 @@ def doctor(
console.print()
status_icons = {
"pass": "[green]✅[/green]",
"warn": "[yellow]⚠️[/yellow]",
"fail": "[red]❌[/red]",
"pass": f"[green]{_icon('✅', '[PASS]')}[/green]",
"warn": f"[yellow]{_icon('⚠️', '[WARN]')}[/yellow]",
"fail": f"[red]{_icon('❌', '[FAIL]')}[/red]",
}
arrow = _icon("▶", ">")
border = _icon("═", "=") * 43
for i, check in enumerate(checks, 1):
icon = status_icons.get(check["status"], "❓")
console.print(f"▶ {i}. {check['name']:20} {icon} {check['detail']}")
icon = status_icons.get(check["status"], _icon("❓", "[?]"))
console.print(f"{arrow} {i}. {check['name']:20} {icon} {check['detail']}")
console.print()
console.print("═" * 43)
console.print(border)
console.print(
f" SUMMARY: [green]{passed} passed[/green], [yellow]{warnings} warning{'s' if warnings != 1 else ''}[/yellow], [red]{failed} failed[/red]"
)
console.print("═" * 43)
console.print(border)
if failed > 0:
raise typer.Exit(code=1)
+12 -8
View File
@@ -333,7 +333,7 @@ class Compiler:
"ariaLabels": self.config.a11y.aria_labels,
}
# I18n configuration - emit for frontend consumption (experimental)
# I18n configuration - emit for frontend consumption
if self.config.i18n:
result["i18n"] = {
"defaultLocale": self.config.i18n.default_locale,
@@ -422,7 +422,9 @@ class Compiler:
def _write_json(self, path: Path, data: dict, indent: int | None) -> None:
"""Write JSON data to file."""
path.write_text(json.dumps(data, indent=indent, ensure_ascii=False))
path.write_text(
json.dumps(data, indent=indent, ensure_ascii=False), encoding="utf-8"
)
def _copy_viewer(self, output_dir: Path) -> None:
"""Copy frontend bundle to output directory."""
@@ -471,14 +473,14 @@ class Compiler:
"""Patch anti-flicker script in index.html to respect darkMode."""
theme = self.config.site.theme
dark_mode = theme.dark_mode if theme else True
html = html_path.read_text()
html = html_path.read_text(encoding="utf-8")
if not dark_mode:
# Remove the dark class addition for light-mode sites
html = html.replace(
"document.documentElement.classList.add('dark');",
"/* light mode — no dark class */",
)
html_path.write_text(html)
html_path.write_text(html, encoding="utf-8")
def _copy_plugins(self, output_dir: Path, frontend_dir: Path) -> None:
"""Copy frontend plugins and generate plugins.json config."""
@@ -519,7 +521,9 @@ class Compiler:
ordered.append(p)
plugins_config = {"plugins": ordered}
(plugins_dst / "plugins.json").write_text(json.dumps(plugins_config, indent=2))
(plugins_dst / "plugins.json").write_text(
json.dumps(plugins_config, indent=2), encoding="utf-8"
)
def _generate_route_pages(self, output_dir: Path, nav: dict) -> list[str]:
"""Generate per-route HTML files for SPA fallback and SEO.
@@ -538,7 +542,7 @@ class Compiler:
if not template_path.exists():
return []
template_html = template_path.read_text()
template_html = template_path.read_text(encoding="utf-8")
site_title = self.config.site.title
site_desc = self.config.site.description or f"Documentation built with {site_title}"
files: list[str] = []
@@ -593,7 +597,7 @@ class Compiler:
f'<div id="root"><noscript>{noscript_content}</noscript></div>',
)
page_file.write_text(html)
page_file.write_text(html, encoding="utf-8")
files.append(f"{relative}/index.html")
return files
@@ -677,7 +681,7 @@ class Compiler:
md_content = None
for candidate in md_candidates:
if candidate.exists():
md_content = candidate.read_text()
md_content = candidate.read_text(encoding="utf-8")
break
if not md_content:
+1 -1
View File
@@ -222,7 +222,7 @@ def update_component_exports(frontend_path: Optional[Path] = None) -> bool:
return False
# Read current exports
current_content = index_path.read_text()
current_content = index_path.read_text(encoding="utf-8")
# Check which components are missing from exports
missing_exports = []
+3 -3
View File
@@ -145,7 +145,7 @@ class YAMLConfigStore:
try:
import yaml
with open(self._path) as f:
with open(self._path, encoding="utf-8") as f:
loaded = yaml.safe_load(f) or {}
# Merge with defaults so new keys are always present
self._data = copy.deepcopy(_DEFAULT_CONFIG)
@@ -171,7 +171,7 @@ class YAMLConfigStore:
# Write to temp file in same directory, then atomic rename
fd, tmp_path = tempfile.mkstemp(dir=str(self._path.parent), suffix=".yaml.tmp")
try:
with os.fdopen(fd, "w") as f:
with os.fdopen(fd, "w", encoding="utf-8") as f:
yaml.dump(
self._data,
f,
@@ -200,7 +200,7 @@ class YAMLConfigStore:
return
try:
with open(json_path) as f:
with open(json_path, encoding="utf-8") as f:
data = json.load(f)
agents = data.get("agents", {})
if agents:
+20 -7
View File
@@ -224,13 +224,15 @@ class ChannelsFeature(BaseFeatureProtocol):
try:
error = await self._start_channel_bot(channel_id, entry)
if error:
entry["running"] = False
entry["start_error"] = error
logger.warning("Auto-start '%s' failed: %s", channel_id, error)
else:
logger.info("Auto-started channel '%s' (%s)", channel_id, entry.get("platform"))
except Exception as e:
entry["running"] = False
entry["start_error"] = str(e)
logger.warning("Auto-start '%s' exception: %s", channel_id, e)
logger.exception("Auto-start '%s' exception: %s", channel_id, e)
self._sync_running_status()
# ── Gateway integration ──────────────────────────────────────────
@@ -255,6 +257,10 @@ class ChannelsFeature(BaseFeatureProtocol):
all_bots.setdefault(ch_id, info.get("bot"))
for ch_id, ch in _channels.items():
if ch.get("start_error"):
ch["running"] = False
continue
bot = all_bots.get(ch_id)
info = _live_bots.get(ch_id, {})
task = info.get("task")
@@ -411,12 +417,19 @@ class ChannelsFeature(BaseFeatureProtocol):
# Also register in gateway if available
if gw is not None:
channel_bots = getattr(gw, "_channel_bots", None)
channel_tasks = getattr(gw, "_channel_tasks", None)
if channel_bots is not None:
channel_bots[channel_id] = bot
if channel_tasks is not None:
channel_tasks.append(task)
try:
channel_bots = getattr(gw, "_channel_bots", None)
if isinstance(channel_bots, dict):
channel_bots[channel_id] = bot
channel_tasks = getattr(gw, "_channel_tasks", None)
if isinstance(channel_tasks, dict):
channel_tasks[channel_id] = task
elif isinstance(channel_tasks, list):
channel_tasks.append(task)
except Exception as e:
logger.warning(
"Gateway task registration failed for '%s': %s", channel_id, e
)
entry["running"] = True
entry.pop("start_error", None)
+32 -5
View File
@@ -8,6 +8,7 @@ Architecture:
from __future__ import annotations
import logging
import re
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
@@ -19,6 +20,10 @@ from ._base import BaseFeatureProtocol
logger = logging.getLogger(__name__)
# Compile regex patterns once for performance
LOCALE_PATTERN = re.compile(r'^[a-z]{2}(-[A-Z]{2})?$')
KEY_PATTERN = re.compile(r'^[a-zA-Z0-9._-]+$')
# ── I18n Protocol ────────────────────────────────────────────────────
@@ -200,6 +205,11 @@ class I18nFeature(BaseFeatureProtocol):
async def _strings(self, request: Request) -> JSONResponse:
mgr = get_i18n_manager()
locale = request.path_params["locale"]
# Validate locale format to prevent injection
if not LOCALE_PATTERN.match(locale):
return JSONResponse({"error": f"Invalid locale format: '{locale}'"}, status_code=400)
strings = mgr.get_strings(locale)
if not strings:
return JSONResponse({"error": f"Locale '{locale}' not found"}, status_code=404)
@@ -208,10 +218,21 @@ class I18nFeature(BaseFeatureProtocol):
async def _translate(self, request: Request) -> JSONResponse:
mgr = get_i18n_manager()
body = await request.json()
text = mgr.t(
body.get("key", ""), locale=body.get("locale", ""), **body.get("variables", {})
)
return JSONResponse({"key": body.get("key", ""), "text": text})
# Validate locale if provided
locale = body.get("locale", "")
if locale:
if not LOCALE_PATTERN.match(locale):
return JSONResponse({"error": f"Invalid locale format: '{locale}'"}, status_code=400)
# Validate key format (alphanumeric with dots)
key = body.get("key", "")
if key:
if not KEY_PATTERN.match(key):
return JSONResponse({"error": f"Invalid key format: '{key}'"}, status_code=400)
text = mgr.t(key, locale=locale, **body.get("variables", {}))
return JSONResponse({"key": key, "text": text})
async def _get_locale(self, request: Request) -> JSONResponse:
mgr = get_i18n_manager()
@@ -220,7 +241,13 @@ class I18nFeature(BaseFeatureProtocol):
async def _set_locale(self, request: Request) -> JSONResponse:
mgr = get_i18n_manager()
body = await request.json()
mgr.set_locale(body.get("locale", "en"))
locale = body.get("locale", "en")
# Validate locale format to prevent injection
if not LOCALE_PATTERN.match(locale):
return JSONResponse({"error": f"Invalid locale format: '{locale}'"}, status_code=400)
mgr.set_locale(locale)
return JSONResponse({"locale": mgr.get_locale()})
+65 -2
View File
@@ -46,10 +46,33 @@ def _now_iso() -> str:
return datetime.now(timezone.utc).isoformat()
def _child_progress(task: Dict[str, Any]) -> Optional[Dict[str, int]]:
"""Derive done/total subtask progress from task.children or meta.
Accepts either a list of child dicts under "children" (each with a status)
or an explicit {"done": int, "total": int} under meta["progress"].
Returns None when there is nothing to report.
"""
meta = task.get("meta") or {}
explicit = meta.get("progress")
if isinstance(explicit, dict):
total = explicit.get("total")
done = explicit.get("done")
if isinstance(total, int) and total > 0 and isinstance(done, int):
return {"done": max(0, min(done, total)), "total": total}
children = task.get("children")
if isinstance(children, list) and children:
total = len(children)
done = sum(1 for c in children if isinstance(c, dict) and c.get("status") == "done")
return {"done": done, "total": total}
return None
def _task_card(task: Dict[str, Any]) -> Dict[str, Any]:
"""Normalise task dict to board card component."""
title = (task.get("title") or task.get("id") or "Task")[:120]
footer_parts = [p for p in (task.get("assignee"), task.get("priority")) if p]
comments = task.get("comments") or []
return {
"id": task.get("id"),
"title": title,
@@ -57,8 +80,12 @@ def _task_card(task: Dict[str, Any]) -> Dict[str, Any]:
"status": task.get("status"),
"assignee": task.get("assignee"),
"priority": task.get("priority"),
"tenant": task.get("tenant"),
"created_at": task.get("created_at"),
"comment_count": len(comments),
"progress": _child_progress(task),
"body": task.get("body"),
"comments": task.get("comments") or [],
"comments": comments,
"meta": task.get("meta") or {},
}
@@ -90,6 +117,9 @@ class KanbanStoreProtocol(ABC):
@abstractmethod
def move_task(self, task_id: str, status: str) -> Optional[Dict[str, Any]]: ...
@abstractmethod
def add_comment(self, task_id: str, comment: Dict[str, Any]) -> Optional[Dict[str, Any]]: ...
@abstractmethod
def bulk_update(self, task_ids: List[str], status: str) -> Dict[str, Any]: ...
@@ -174,6 +204,7 @@ class SimpleKanbanStore(KanbanStoreProtocol):
"priority": data.get("priority"),
"tenant": data.get("tenant"),
"board": data.get("board", "default"),
"children": data.get("children") or [],
"comments": [],
"meta": data.get("meta") or {},
"created_at": _now_iso(),
@@ -188,7 +219,7 @@ class SimpleKanbanStore(KanbanStoreProtocol):
task = self._tasks.get(task_id)
if not task:
return None
for key in ("title", "body", "assignee", "priority", "tenant", "meta"):
for key in ("title", "body", "assignee", "priority", "tenant", "meta", "children"):
if key in data:
task[key] = data[key]
if "status" in data and data["status"] in VALID_STATUSES:
@@ -209,6 +240,24 @@ class SimpleKanbanStore(KanbanStoreProtocol):
self._emit("task_moved", task, {"from_status": old, "to_status": status})
return dict(task)
def add_comment(self, task_id: str, comment: Dict[str, Any]) -> Optional[Dict[str, Any]]:
task = self._tasks.get(task_id)
if not task:
return None
text = str((comment or {}).get("text") or "").strip()
if not text:
return None
entry = {
"id": f"c_{uuid.uuid4().hex[:8]}",
"author": (comment or {}).get("author") or "human",
"text": text,
"created_at": _now_iso(),
}
task.setdefault("comments", []).append(entry)
task["updated_at"] = _now_iso()
self._emit("task_commented", task, {"comment_id": entry["id"]})
return dict(task)
def bulk_update(self, task_ids: List[str], status: str) -> Dict[str, Any]:
if status not in VALID_STATUSES:
return {"updated": 0, "errors": ["invalid status"]}
@@ -264,6 +313,9 @@ class InjectedKanbanStore(KanbanStoreProtocol):
def move_task(self, task_id: str, status: str) -> Optional[Dict[str, Any]]:
return self._call("move_task", task_id, status)
def add_comment(self, task_id: str, comment: Dict[str, Any]) -> Optional[Dict[str, Any]]:
return self._call("add_comment", task_id, comment)
def bulk_update(self, task_ids: List[str], status: str) -> Dict[str, Any]:
return self._call("bulk_update", task_ids, status)
@@ -331,6 +383,7 @@ class KanbanFeature(BaseFeatureProtocol):
Route("/api/kanban/tasks/{task_id}", self._patch, methods=["PATCH"]),
Route("/api/kanban/tasks/{task_id}", self._delete, methods=["DELETE"]),
Route("/api/kanban/tasks/{task_id}/move", self._move, methods=["POST"]),
Route("/api/kanban/tasks/{task_id}/comments", self._add_comment, methods=["POST"]),
Route("/api/kanban/events", self._events, methods=["GET"]),
Route("/api/kanban/health", self._health, methods=["GET"]),
Route("/api/kanban/boards", self._boards, methods=["GET"]),
@@ -380,6 +433,16 @@ class KanbanFeature(BaseFeatureProtocol):
return JSONResponse({"error": "not found or invalid status"}, status_code=404)
return JSONResponse(task)
async def _add_comment(self, request: Request) -> JSONResponse:
store = get_kanban_store()
body = await request.json()
if not isinstance(body, dict) or not str(body.get("text") or "").strip():
return JSONResponse({"error": "text required"}, status_code=400)
task = store.add_comment(request.path_params["task_id"], body)
if not task:
return JSONResponse({"error": "not found"}, status_code=404)
return JSONResponse(task)
async def _bulk(self, request: Request) -> JSONResponse:
store = get_kanban_store()
body = await request.json()
+1 -1
View File
@@ -413,7 +413,7 @@ class SDKMemoryManager(MemoryProtocol):
if json_file.name == "config.json":
continue
try:
data = json.loads(json_file.read_text())
data = json.loads(json_file.read_text(encoding="utf-8"))
if not isinstance(data, list):
continue
# Determine type from filename
+2 -2
View File
@@ -278,7 +278,7 @@ def _save_data() -> None:
"saved_at": time.time(),
}
_data_file.parent.mkdir(parents=True, exist_ok=True)
with open(_data_file, "w") as f:
with open(_data_file, "w", encoding="utf-8") as f:
json.dump(data, f)
except Exception:
pass # Silently fail
@@ -290,7 +290,7 @@ def _load_data() -> None:
if not _data_file or not _data_file.exists():
return
try:
with open(_data_file) as f:
with open(_data_file, encoding="utf-8") as f:
data = json.load(f)
_aggregates.update(data.get("aggregates", {}))
for record in data.get("records", []):
+16
View File
@@ -0,0 +1,16 @@
"""Health status utilities for consistent health checking across AIUI."""
from typing import Optional
SUCCESS_STATUSES = frozenset({"ok", "healthy"})
def is_success_status(status: Optional[str]) -> bool:
"""Check if a health status indicates success.
Args:
status: The health status string to check
Returns:
True if status is 'ok' or 'healthy', False otherwise
"""
return (status or "") in SUCCESS_STATUSES
+1 -1
View File
@@ -299,7 +299,7 @@ def create_gateway_from_yaml(
if not os.path.exists(config_path):
raise FileNotFoundError(f"Config not found: {config_path}")
with open(config_path) as f:
with open(config_path, encoding="utf-8") as f:
config = yaml.safe_load(f)
# Extract gateway settings
+100 -2
View File
@@ -144,6 +144,75 @@ def _stream_event_to_run_event(stream_event) -> Optional[RunEvent]:
return None
# ---------------------------------------------------------------------------
# Approval helpers
# ---------------------------------------------------------------------------
# Module-level cache for approval manager
_approval_manager_cache = None
_approval_manager_load_attempted = False
def _get_approval_manager_cached():
"""Lazy load and cache the approval manager."""
global _approval_manager_cache, _approval_manager_load_attempted
if not _approval_manager_load_attempted:
try:
from praisonaiui.features.approvals import get_approval_manager
_approval_manager_cache = get_approval_manager()
except ImportError:
_approval_manager_cache = None
_approval_manager_load_attempted = True
return _approval_manager_cache
# High-risk tools that should require approval
HIGH_RISK_TOOLS = {
"execute_command", "run_shell", "bash", "shell", "cmd",
"write_file", "delete_file", "modify_file",
"send_email", "make_api_call", "http_request",
"database_query", "sql_execute",
}
def _should_check_approval(tool_name: str) -> bool:
"""Check if a tool requires approval before execution."""
if not tool_name:
return False
# Check if tool is in high-risk list
tool_lower = tool_name.lower()
return any(risk in tool_lower for risk in HIGH_RISK_TOOLS)
def _get_tool_risk_level(tool_name: str) -> str:
"""Determine risk level of a tool."""
if not tool_name:
return "medium"
tool_lower = tool_name.lower()
if any(word in tool_lower for word in ["delete", "remove", "drop", "destroy"]):
return "critical"
if any(word in tool_lower for word in ["write", "modify", "execute", "send", "sql"]):
return "high"
if any(word in tool_lower for word in ["read", "get", "list", "search"]):
return "low"
return "medium"
def _request_tool_approval(tool_name: str, arguments: Any, agent_name: str, risk_level: str) -> Optional[str]:
"""Request approval for tool execution via the approval feature."""
try:
# Import is at module level via _get_approval_manager_cached
mgr = _get_approval_manager_cached()
if mgr is None:
return None
entry = mgr.request_approval({
"tool_name": tool_name,
"arguments": arguments,
"agent_name": agent_name,
"risk_level": risk_level,
"description": f"Tool {tool_name} requires approval for execution",
})
return entry.get("id")
except Exception as e:
logger.debug("Failed to request approval: %s", e)
return None
# ---------------------------------------------------------------------------
# Hook → RunEvent bridge
# ---------------------------------------------------------------------------
@@ -154,11 +223,40 @@ def _hook_event_to_run_events(hook_event_name: str, event_data) -> List[RunEvent
events = []
if hook_event_name == "before_tool":
tool_name = getattr(event_data, "tool_name", None)
args = getattr(event_data, "arguments", None)
# Check if this tool requires approval
should_check_approval = _should_check_approval(tool_name)
if should_check_approval:
# Create approval request
approval_id = _request_tool_approval(
tool_name=tool_name,
arguments=args,
agent_name=getattr(event_data, "agent_name", None),
risk_level=_get_tool_risk_level(tool_name),
)
if approval_id:
# Emit approval required event - using RUN_PAUSED as closest match
# The extra_data contains the approval info for frontend handling
events.append(
RunEvent(
type=RunEventType.RUN_PAUSED,
extra_data={
"type": "approval_required",
"id": approval_id,
"tool_name": tool_name,
"arguments": args,
"risk_level": _get_tool_risk_level(tool_name),
}
)
)
events.append(
RunEvent(
type=RunEventType.TOOL_CALL_STARTED,
name=getattr(event_data, "tool_name", None),
args=getattr(event_data, "arguments", None),
name=tool_name,
args=args,
)
)
elif hook_event_name == "after_tool":
+2 -2
View File
@@ -59,8 +59,8 @@ class ConfigFeatureRegistry:
"SEO configuration with title templates, OG tags, Twitter meta - basic implementation"),
"a11y": FeatureInfo("a11y", FeatureStatus.IMPLEMENTED, "0.5.0",
"Accessibility configuration - basic implementation for skip links and ARIA labels"),
"i18n": FeatureInfo("i18n", FeatureStatus.EXPERIMENTAL, None,
"Internationalization accepted in schema but content pipeline/routing not implemented"),
"i18n": FeatureInfo("i18n", FeatureStatus.IMPLEMENTED, "0.5.0",
"Internationalization configuration with multi-language support"),
}
def get_feature(self, name: str) -> FeatureInfo | None:
+250 -24
View File
@@ -825,8 +825,17 @@ async def _plugins_config(request: Request) -> JSONResponse:
return JSONResponse({"plugins": plugins})
async def _serve_index(request: Request) -> HTMLResponse:
"""Serve dynamically generated HTML based on active style."""
async def _serve_index(request: Request) -> Response:
"""Serve dynamically generated HTML based on active style.
Unmatched ``/api/*`` paths return a JSON 404 instead of the SPA shell so
API clients never receive HTML with a 200 status.
"""
if request.url.path.startswith("/api/"):
return JSONResponse(
{"error": "Not Found", "path": request.url.path},
status_code=404,
)
style = _effective_style # Single source of truth (set by create_app)
return HTMLResponse(_build_html(style))
@@ -938,37 +947,82 @@ def register_page_action(page_id: str):
return decorator
_DEEP_HEALTH_CACHE: Dict[str, Any] = {}
_DEEP_HEALTH_CACHE_TIME: float = 0.0
_DEEP_HEALTH_TTL: float = 5.0
async def health(request: Request) -> JSONResponse:
"""Health check endpoint."""
"""Health check endpoint - deep health with feature checks.
The provider check and every feature check run concurrently under a single
overall deadline so neither the provider nor a slow feature can serialise
the response. A short-TTL cache absorbs repeated probes. Pass ?deep=false
for an immediate liveness response. Use /health/live for the fastest
liveness probe or /health/ready for the readiness probe.
"""
import asyncio
import time
if request.query_params.get("deep", "true").lower() == "false":
return await health_live(request)
global _DEEP_HEALTH_CACHE, _DEEP_HEALTH_CACHE_TIME
now = time.monotonic()
if _DEEP_HEALTH_CACHE and (now - _DEEP_HEALTH_CACHE_TIME) < _DEEP_HEALTH_TTL:
cached = dict(_DEEP_HEALTH_CACHE)
cached["timestamp"] = datetime.utcnow().isoformat()
cached["cached"] = True
return JSONResponse(cached)
provider = get_provider()
provider_info = {"name": type(provider).__name__}
try:
provider_health = await provider.health()
provider_info.update(provider_health)
except Exception:
pass
async def _safe_provider() -> Dict[str, Any]:
try:
return await asyncio.wait_for(provider.health(), timeout=0.5)
except Exception:
return {}
sdk_gaps: list = []
integrated_flag: Optional[bool] = None
async def _safe_health(name: str, feat) -> tuple[str, Dict[str, Any]]:
try:
result = await asyncio.wait_for(feat.health(), timeout=0.5)
return (name, result)
except asyncio.TimeoutError:
return (name, {"healthy": False, "detail": "timeout"})
except Exception as e:
return (name, {"healthy": False, "detail": str(e)})
try:
from praisonaiui.backends import is_integrated_mode
from praisonaiui.features import get_features
integrated_flag = is_integrated_mode()
for name, feat in get_features().items():
try:
fh = await feat.health()
if fh.get("sdk_gap"):
sdk_gaps.append(
{
"feature": name,
"message": fh.get("sdk_gap_message", "SDK gap"),
}
)
except Exception:
continue
features = get_features()
feature_tasks = [_safe_health(name, feat) for name, feat in features.items()]
provider_result, *feature_results = await asyncio.gather(
_safe_provider(), *feature_tasks
)
provider_info.update(provider_result)
for name, fh in feature_results:
if fh.get("sdk_gap"):
sdk_gaps.append(
{
"feature": name,
"message": fh.get("sdk_gap_message", "SDK gap"),
}
)
except Exception:
pass
try:
provider_info.update(await _safe_provider())
except Exception:
pass
payload: Dict[str, Any] = {
"status": "ok",
@@ -979,9 +1033,126 @@ async def health(request: Request) -> JSONResponse:
payload["sdk_gaps"] = sdk_gaps
if integrated_flag is not None:
payload["integrated"] = integrated_flag
_DEEP_HEALTH_CACHE = payload
_DEEP_HEALTH_CACHE_TIME = time.monotonic()
return JSONResponse(payload)
async def health_live(request: Request) -> JSONResponse:
"""Fast liveness check - confirms server is running.
Returns immediately with minimal checks. Use for load balancer health probes.
Target: <500ms response time.
"""
return JSONResponse({
"status": "ok",
"timestamp": datetime.utcnow().isoformat(),
})
async def health_ready(request: Request) -> JSONResponse:
"""Readiness check with parallel feature health checks.
Performs health checks on all features in parallel with timeout.
Use ?deep=false for basic readiness without feature checks.
"""
import asyncio
# Check for deep query parameter
deep = request.query_params.get("deep", "true").lower() != "false"
provider = get_provider()
provider_info = {"name": type(provider).__name__}
try:
provider_health = await provider.health()
provider_info.update(provider_health)
except Exception:
pass
features_health = {}
sdk_gaps: list = []
integrated_flag: Optional[bool] = None
if deep:
try:
from praisonaiui.backends import is_integrated_mode
from praisonaiui.features import get_features
integrated_flag = is_integrated_mode()
async def _safe_health(name: str, feat) -> tuple[str, Dict[str, Any]]:
"""Wrapper to safely call feature health with timeout."""
try:
result = await asyncio.wait_for(feat.health(), timeout=0.5)
return (name, result)
except asyncio.TimeoutError:
return (name, {"healthy": False, "detail": "timeout"})
except Exception as e:
return (name, {"healthy": False, "detail": str(e)})
# Run all feature health checks in parallel
features = get_features()
health_tasks = [_safe_health(name, feat) for name, feat in features.items()]
results = await asyncio.gather(*health_tasks)
# Process results
for name, health_data in results:
features_health[name] = health_data
if health_data.get("sdk_gap"):
sdk_gaps.append({
"feature": name,
"message": health_data.get("sdk_gap_message", "SDK gap"),
})
except Exception:
pass
payload: Dict[str, Any] = {
"status": "ok",
"timestamp": datetime.utcnow().isoformat(),
"provider": provider_info,
}
if deep and features_health:
payload["features"] = features_health
if sdk_gaps:
payload["sdk_gaps"] = sdk_gaps
if integrated_flag is not None:
payload["integrated"] = integrated_flag
return JSONResponse(payload)
# Health cache for dashboard polling
_health_cache: Dict[str, Any] = {}
_health_cache_time: float = 0
async def health_cached(request: Request) -> JSONResponse:
"""Cached health endpoint for dashboard - 30 second TTL.
Returns cached deep health status to avoid repeated expensive checks.
"""
import json
import time
global _health_cache, _health_cache_time
current_time = time.time()
if _health_cache and (current_time - _health_cache_time) < 30:
return JSONResponse(_health_cache)
# Refresh cache by calling health_ready
response = await health_ready(request)
# Extract JSON content from response
_health_cache = json.loads(response.body.decode())
_health_cache_time = current_time
return JSONResponse(_health_cache)
async def list_agents(request: Request) -> JSONResponse:
"""List all registered agents (merges registry + provider).
@@ -1322,7 +1493,7 @@ async def api_config_handler(request: Request) -> JSONResponse:
config_data = body.get("config", body)
import yaml
with open(_config_path, "w") as f:
with open(_config_path, "w", encoding="utf-8") as f:
yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
_config_cache = config_data
return JSONResponse({"status": "saved", "config_path": str(_config_path)})
@@ -1409,6 +1580,34 @@ async def api_provider(request: Request) -> JSONResponse:
return JSONResponse(info)
async def api_provider_health(request: Request) -> JSONResponse:
"""Return JSON health information for the active provider gateway."""
provider = get_provider()
try:
health = await provider.health()
except Exception as exc:
health = {"status": "error", "detail": str(exc)}
agents: list = []
try:
listed = await provider.list_agents()
if isinstance(listed, list):
agents = listed
except Exception:
agents = []
status = health.get("status", "unknown")
detail = health.get("detail") or (
"ok" if status in ("ok", "healthy") else str(health)
)
body = {
**health,
"type": health.get("provider", type(provider).__name__),
"status": status,
"agents": len(agents),
"detail": detail,
}
return JSONResponse(body)
async def api_action_click(request: Request) -> JSONResponse:
"""Handle action button clicks and dispatch to registered callbacks.
@@ -2502,7 +2701,7 @@ def load_config_from_yaml(config_path: Path) -> Optional[dict]:
try:
import yaml
with open(config_path) as f:
with open(config_path, encoding="utf-8") as f:
return yaml.safe_load(f)
except Exception:
return None
@@ -2583,6 +2782,10 @@ class AuthEnforcementMiddleware:
EXEMPT_PATHS = {
"/health",
"/api/health",
"/health/live",
"/health/ready",
"/api/health/live",
"/api/health/ready",
"/api/auth/login",
"/login",
"/api/protocol",
@@ -2859,7 +3062,11 @@ def create_app(
routes = [
Route("/health", health, methods=["GET"]),
Route("/api/health", health, methods=["GET"]),
Route("/api/health", health_cached, methods=["GET"]),
Route("/health/live", health_live, methods=["GET"]),
Route("/health/ready", health_ready, methods=["GET"]),
Route("/api/health/live", health_live, methods=["GET"]),
Route("/api/health/ready", health_ready, methods=["GET"]),
Route("/login", login_handler, methods=["POST"]),
Route("/register", register_handler, methods=["POST"]),
Route("/logout", logout_handler, methods=["POST"]),
@@ -2900,6 +3107,7 @@ def create_app(
# Note: /api/usage is now provided by PraisonAIUsage feature
Route("/api/debug", api_debug, methods=["GET"]),
Route("/api/provider", api_provider, methods=["GET"]),
Route("/api/provider/health", api_provider_health, methods=["GET"]),
# Action callback API
Route("/api/actions/{action_id}/click", api_action_click, methods=["POST"]),
# Page registry protocol
@@ -3188,6 +3396,14 @@ def create_app(
"description": "Execution nodes & presence",
"order": 15,
},
{
"id": "mcp",
"title": "MCP",
"icon": "🔌",
"group": "Agent",
"description": "Model Context Protocol servers",
"order": 30,
},
{
"id": "config",
"title": "Config",
@@ -3282,6 +3498,7 @@ def create_app(
"sessions": "/sessions",
"cron": "/api/schedules",
"channels": "/api/channels",
"mcp": "/api/mcp/servers",
}
# Read page whitelist from config.yaml if set_pages() was not called
@@ -3373,6 +3590,15 @@ def create_app(
response_status = 404
if response_status == 404:
request_path = scope.get("path", "")
if request_path.startswith("/api/"):
# Never serve the SPA HTML shell for unmatched API paths.
response = JSONResponse(
{"error": "Not Found", "path": request_path},
status_code=404,
)
await response(scope, receive, send)
return
# SPA fallback — serve the generated index page
from starlette.responses import HTMLResponse
@@ -0,0 +1 @@
var b;(function(s){s.assertEqual=n=>{};function e(n){}s.assertIs=e;function t(n){throw new Error}s.assertNever=t,s.arrayToEnum=n=>{const i={};for(const a of n)i[a]=a;return i},s.getValidEnumValues=n=>{const i=s.objectKeys(n).filter(o=>typeof n[n[o]]!="number"),a={};for(const o of i)a[o]=n[o];return s.objectValues(a)},s.objectValues=n=>s.objectKeys(n).map(function(i){return n[i]}),s.objectKeys=typeof Object.keys=="function"?n=>Object.keys(n):n=>{const i=[];for(const a in n)Object.prototype.hasOwnProperty.call(n,a)&&i.push(a);return i},s.find=(n,i)=>{for(const a of n)if(i(a))return a},s.isInteger=typeof Number.isInteger=="function"?n=>Number.isInteger(n):n=>typeof n=="number"&&Number.isFinite(n)&&Math.floor(n)===n;function r(n,i=" | "){return n.map(a=>typeof a=="string"?`'${a}'`:a).join(i)}s.joinValues=r,s.jsonStringifyReplacer=(n,i)=>typeof i=="bigint"?i.toString():i})(b||(b={}));var Ie;(function(s){s.mergeShapes=(e,t)=>({...e,...t})})(Ie||(Ie={}));const f=b.arrayToEnum(["string","nan","number","integer","float","boolean","date","bigint","symbol","function","undefined","null","array","object","unknown","promise","void","never","map","set"]),I=s=>{switch(typeof s){case"undefined":return f.undefined;case"string":return f.string;case"number":return Number.isNaN(s)?f.nan:f.number;case"boolean":return f.boolean;case"function":return f.function;case"bigint":return f.bigint;case"symbol":return f.symbol;case"object":return Array.isArray(s)?f.array:s===null?f.null:s.then&&typeof s.then=="function"&&s.catch&&typeof s.catch=="function"?f.promise:typeof Map<"u"&&s instanceof Map?f.map:typeof Set<"u"&&s instanceof Set?f.set:typeof Date<"u"&&s instanceof Date?f.date:f.object;default:return f.unknown}},c=b.arrayToEnum(["invalid_type","invalid_literal","custom","invalid_union","invalid_union_discriminator","invalid_enum_value","unrecognized_keys","invalid_arguments","invalid_return_type","invalid_date","invalid_string","too_small","too_big","invalid_intersection_types","not_multiple_of","not_finite"]);class T extends Error{get errors(){return this.issues}constructor(e){super(),this.issues=[],this.addIssue=r=>{this.issues=[...this.issues,r]},this.addIssues=(r=[])=>{this.issues=[...this.issues,...r]};const t=new.target.prototype;Object.setPrototypeOf?Object.setPrototypeOf(this,t):this.__proto__=t,this.name="ZodError",this.issues=e}format(e){const t=e||function(i){return i.message},r={_errors:[]},n=i=>{for(const a of i.issues)if(a.code==="invalid_union")a.unionErrors.map(n);else if(a.code==="invalid_return_type")n(a.returnTypeError);else if(a.code==="invalid_arguments")n(a.argumentsError);else if(a.path.length===0)r._errors.push(t(a));else{let o=r,l=0;for(;l<a.path.length;){const d=a.path[l];l===a.path.length-1?(o[d]=o[d]||{_errors:[]},o[d]._errors.push(t(a))):o[d]=o[d]||{_errors:[]},o=o[d],l++}}};return n(this),r}static assert(e){if(!(e instanceof T))throw new Error(`Not a ZodError: ${e}`)}toString(){return this.message}get message(){return JSON.stringify(this.issues,b.jsonStringifyReplacer,2)}get isEmpty(){return this.issues.length===0}flatten(e=t=>t.message){const t={},r=[];for(const n of this.issues)if(n.path.length>0){const i=n.path[0];t[i]=t[i]||[],t[i].push(e(n))}else r.push(e(n));return{formErrors:r,fieldErrors:t}}get formErrors(){return this.flatten()}}T.create=s=>new T(s);const _e=(s,e)=>{let t;switch(s.code){case c.invalid_type:s.received===f.undefined?t="Required":t=`Expected ${s.expected}, received ${s.received}`;break;case c.invalid_literal:t=`Invalid literal value, expected ${JSON.stringify(s.expected,b.jsonStringifyReplacer)}`;break;case c.unrecognized_keys:t=`Unrecognized key(s) in object: ${b.joinValues(s.keys,", ")}`;break;case c.invalid_union:t="Invalid input";break;case c.invalid_union_discriminator:t=`Invalid discriminator value. Expected ${b.joinValues(s.options)}`;break;case c.invalid_enum_value:t=`Invalid enum value. Expected ${b.joinValues(s.options)}, received '${s.received}'`;break;case c.invalid_arguments:t="Invalid function arguments";break;case c.invalid_return_type:t="Invalid function return type";break;case c.invalid_date:t="Invalid date";break;case c.invalid_string:typeof s.validation=="object"?"includes"in s.validation?(t=`Invalid input: must include "${s.validation.includes}"`,typeof s.validation.position=="number"&&(t=`${t} at one or more positions greater than or equal to ${s.validation.position}`)):"startsWith"in s.validation?t=`Invalid input: must start with "${s.validation.startsWith}"`:"endsWith"in s.validation?t=`Invalid input: must end with "${s.validation.endsWith}"`:b.assertNever(s.validation):s.validation!=="regex"?t=`Invalid ${s.validation}`:t="Invalid";break;case c.too_small:s.type==="array"?t=`Array must contain ${s.exact?"exactly":s.inclusive?"at least":"more than"} ${s.minimum} element(s)`:s.type==="string"?t=`String must contain ${s.exact?"exactly":s.inclusive?"at least":"over"} ${s.minimum} character(s)`:s.type==="number"?t=`Number must be ${s.exact?"exactly equal to ":s.iLine truncated
@@ -1 +1 @@
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-400:oklch(75% .183 55.934);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-green-50:oklch(98.2% .018 155.826);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-emerald-400:oklch(76.5% .177 163.223);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-violet-500:oklch(60.6% .25 292.717);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-pink-400:oklch(71.8% .202 349.761);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-2xl:96rem;--container-xs:20rem;--container-sm:24rem;--container-md:28rem;--container-lg:32rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--text-lg:1.125rem;-Line truncated
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial;--tw-content:"";--tw-animation-delay:0s;--tw-animation-direction:normal;--tw-animation-duration:initial;--tw-animation-fill-mode:none;--tw-animation-iteration-count:1;--tw-enter-blur:0;--tw-enter-opacity:1;--tw-enter-rotate:0;--tw-enter-scale:1;--tw-enter-translate-x:0;--tw-enter-translate-y:0;--tw-exit-blur:0;--tw-exit-opacity:1;--tw-exit-rotate:0;--tw-exit-scale:1;--tw-exit-translate-x:0;--tw-exit-translate-y:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-100:oklch(93.6% .032 17.717);--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-500:oklch(63.7% .237 25.331);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-orange-50:oklch(98% .016 73.684);--color-orange-100:oklch(95.4% .038 75.164);--color-orange-200:oklch(90.1% .076 70.697);--color-orange-400:oklch(75% .183 55.934);--color-orange-600:oklch(64.6% .222 41.116);--color-orange-800:oklch(47% .157 37.304);--color-amber-400:oklch(82.8% .189 84.429);--color-amber-500:oklch(76.9% .188 70.08);--color-yellow-50:oklch(98.7% .026 102.212);--color-yellow-200:oklch(94.5% .129 101.54);--color-yellow-500:oklch(79.5% .184 86.047);--color-yellow-600:oklch(68.1% .162 75.834);--color-yellow-800:oklch(47.6% .114 61.907);--color-green-50:oklch(98.2% .018 155.826);--color-green-400:oklch(79.2% .209 151.711);--color-green-500:oklch(72.3% .219 149.579);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-green-800:oklch(44.8% .119 151.328);--color-emerald-400:oklch(76.5% .177 163.223);--color-cyan-400:oklch(78.9% .154 211.53);--color-blue-50:oklch(97% .014 254.604);--color-blue-200:oklch(88.2% .059 254.128);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-blue-800:oklch(42.4% .199 265.638);--color-blue-900:oklch(37.9% .146 265.522);--color-violet-500:oklch(60.6% .25 292.717);--color-purple-400:oklch(71.4% .203 305.504);--color-purple-500:oklch(62.7% .265 303.9);--color-pink-400:oklch(71.8% .202 349.761);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-100:oklch(96.7% .003 264.542);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--breakpoint-2xl:96rem;--contLine truncated
@@ -1,60 +1,61 @@
function M1(e,t){for(var r=0;r<t.length;r++){const a=t[r];if(typeof a!="string"&&!Array.isArray(a)){for(const o in a)if(o!=="default"&&!(o in e)){const s=Object.getOwnPropertyDescriptor(a,o);s&&Object.defineProperty(e,o,s.get?s:{enumerable:!0,get:()=>a[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))a(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const u of s.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&a(u)}).observe(document,{childList:!0,subtree:!0});function r(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function a(o){if(o.ep)return;o.ep=!0;const s=r(o);fetch(o.href,s)}})();function Zu(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var af={exports:{}},al={};var OT;function j1(){if(OT)return al;OT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,o,s){var u=null;if(s!==void 0&&(u=""+s),o.key!==void 0&&(u=""+o.key),"key"in o){s={};for(var d in o)d!=="key"&&(s[d]=o[d])}else s=o;return o=s.ref,{$$typeof:e,type:a,key:u,ref:o!==void 0?o:null,props:s}}return al.Fragment=t,al.jsx=r,al.jsxs=r,al}var IT;function U1(){return IT||(IT=1,af.exports=j1()),af.exports}var g=U1(),of={exports:{}},Ne={};var LT;function F1(){if(LT)return Ne;LT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),s=Symbol.for("react.consumer"),u=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),h=Symbol.for("react.lazy"),b=Symbol.for("react.activity"),v=Symbol.iterator;function E(j){return j===null||typeof j!="object"?null:(j=v&&j[v]||j["@@iterator"],typeof j=="function"?j:null)}var w={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},T=Object.assign,k={};function A(j,Q,I){this.props=j,this.context=Q,this.refs=k,this.updater=I||w}A.prototype.isReactComponent={},A.prototype.setState=function(j,Q){if(typeof j!="object"&&typeof j!="function"&&j!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,j,Q,"setState")},A.prototype.forceUpdate=function(j){this.updater.enqueueForceUpdate(this,j,"forceUpdate")};function _(){}_.prototype=A.prototype;function R(j,Q,I){this.props=j,this.context=Q,this.refs=k,this.updater=I||w}var L=R.prototype=new _;L.constructor=R,T(L,A.prototype),L.isPureReactComponent=!0;var F=Array.isArray;function D(){}var P={H:null,A:null,T:null,S:null},z=Object.prototype.hasOwnProperty;function W(j,Q,I){var ue=I.ref;return{$$typeof:e,type:j,key:Q,ref:ue!==void 0?ue:null,props:I}}function M(j,Q){return W(j.type,Q,j.props)}function H(j){return typeof j=="object"&&j!==null&&j.$$typeof===e}function X(j){var Q={"=":"=0",":":"=2"};return"$"+j.replace(/[=:]/g,function(I){return Q[I]})}var ee=/\/+/g;function te(j,Q){return typeof j=="object"&&j!==null&&j.key!=null?X(""+j.key):Q.toString(36)}function Z(j){switch(j.status){case"fulfilled":return j.value;case"rejected":throw j.reason;default:switch(typeof j.status=="string"?j.then(D,D):(j.status="pending",j.then(function(Q){j.status==="pending"&&(j.status="fulfilled",j.value=Q)},function(Q){j.status==="pending"&&(j.status="rejected",j.reason=Q)})),j.status){case"fulfilled":return j.value;case"rejected":throw j.reason}}throw j}function B(j,Q,I,ue,fe){var de=typeof j;(de==="undefined"||de==="boolean")&&(j=null);var le=!1;if(j===null)le=!0;else switch(de){case"bigint":case"string":case"number":le=!0;break;case"object":switch(j.$$typeof){case e:case t:le=!0;break;case h:return le=j._init,B(le(j._payload),Q,I,ue,fe)}}if(le)return fe=fe(j),le=ue===""?"."+te(j,0):ue,F(fe)?(I="",le!=null&&(I=le.replace(ee,"$&/")+"/"),B(fe,Q,I,"",function(Re){return Re})):fe!=null&&(H(fe)&&(fe=M(fe,I+(fe.key==null||j&&j.key===fe.key?"":(""+fe.key).replace(ee,"$&/")+"/")+le)),Q.push(fe)),1;le=0;var we=ue===""?".":ue+":";if(F(j))for(var me=0;me<j.length;me++)ue=j[me],de=we+te(ue,me),le+=B(ue,Q,I,de,fe);else if(me=E(j),typeof me=="function")for(j=me.call(j),me=0;!(ue=j.next()).done;)ue=ue.value,de=we+te(ue,me++),le+=B(ue,Q,I,de,fe);else if(de==="object"){if(typeof j.then=="function")return B(Z(j),Q,I,ue,fe);throw Q=String(j),Error("Objects are not valid as a React child (found: "+(Q==="[object Object]"?"object with keys {"+Object.keys(j).join(", ")+"}":Q)+"). If you meant to render a collection of children, use anLine truncated
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index2.js","assets/common-types.js","assets/index3.js"])))=>i.map(i=>d[i]);
function j1(e,t){for(var r=0;r<t.length;r++){const a=t[r];if(typeof a!="string"&&!Array.isArray(a)){for(const o in a)if(o!=="default"&&!(o in e)){const s=Object.getOwnPropertyDescriptor(a,o);s&&Object.defineProperty(e,o,s.get?s:{enumerable:!0,get:()=>a[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))a(o);new MutationObserver(o=>{for(const s of o)if(s.type==="childList")for(const u of s.addedNodes)u.tagName==="LINK"&&u.rel==="modulepreload"&&a(u)}).observe(document,{childList:!0,subtree:!0});function r(o){const s={};return o.integrity&&(s.integrity=o.integrity),o.referrerPolicy&&(s.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?s.credentials="include":o.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function a(o){if(o.ep)return;o.ep=!0;const s=r(o);fetch(o.href,s)}})();function Qu(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var of={exports:{}},al={};var LT;function U1(){if(LT)return al;LT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,o,s){var u=null;if(s!==void 0&&(u=""+s),o.key!==void 0&&(u=""+o.key),"key"in o){s={};for(var d in o)d!=="key"&&(s[d]=o[d])}else s=o;return o=s.ref,{$$typeof:e,type:a,key:u,ref:o!==void 0?o:null,props:s}}return al.Fragment=t,al.jsx=r,al.jsxs=r,al}var DT;function F1(){return DT||(DT=1,of.exports=U1()),of.exports}var g=F1(),lf={exports:{}},Ne={};var MT;function B1(){if(MT)return Ne;MT=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),o=Symbol.for("react.profiler"),s=Symbol.for("react.consumer"),u=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),p=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),h=Symbol.for("react.lazy"),b=Symbol.for("react.activity"),v=Symbol.iterator;function E(U){return U===null||typeof U!="object"?null:(U=v&&U[v]||U["@@iterator"],typeof U=="function"?U:null)}var w={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},T=Object.assign,k={};function A(U,J,I){this.props=U,this.context=J,this.refs=k,this.updater=I||w}A.prototype.isReactComponent={},A.prototype.setState=function(U,J){if(typeof U!="object"&&typeof U!="function"&&U!=null)throw Error("takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,U,J,"setState")},A.prototype.forceUpdate=function(U){this.updater.enqueueForceUpdate(this,U,"forceUpdate")};function _(){}_.prototype=A.prototype;function R(U,J,I){this.props=U,this.context=J,this.refs=k,this.updater=I||w}var L=R.prototype=new _;L.constructor=R,T(L,A.prototype),L.isPureReactComponent=!0;var j=Array.isArray;function D(){}var z={H:null,A:null,T:null,S:null},F=Object.prototype.hasOwnProperty;function V(U,J,I){var ue=I.ref;return{$$typeof:e,type:U,key:J,ref:ue!==void 0?ue:null,props:I}}function M(U,J){return V(U.type,J,U.props)}function H(U){return typeof U=="object"&&U!==null&&U.$$typeof===e}function K(U){var J={"=":"=0",":":"=2"};return"$"+U.replace(/[=:]/g,function(I){return J[I]})}var Z=/\/+/g;function te(U,J){return typeof U=="object"&&U!==null&&U.key!=null?K(""+U.key):J.toString(36)}function Q(U){switch(U.status){case"fulfilled":return U.value;case"rejected":throw U.reason;default:switch(typeof U.status=="string"?U.then(D,D):(U.status="pending",U.then(function(J){U.status==="pending"&&(U.status="fulfilled",U.value=J)},function(J){U.status==="pending"&&(U.status="rejected",U.reason=J)})),U.status){case"fulfilled":return U.value;case"rejected":throw U.reason}}throw U}function P(U,J,I,ue,fe){var de=typeof U;(de==="undefined"||de==="boolean")&&(U=null);var le=!1;if(U===null)le=!0;else switch(de){case"bigint":case"string":case"number":le=!0;break;case"object":switch(U.$$typeof){case e:case t:le=!0;break;case h:return le=U._init,P(le(U._payload),J,I,ue,fe)}}if(le)return fe=fe(U),le=ue===""?"."+te(U,0):ue,j(fe)?(I="",le!=null&&(I=le.replace(Z,"$&/")+"/"),P(fe,J,I,"",function(Re){return Re})):fe!=null&&(H(fe)&&(fe=M(fe,I+(fe.key==null||U&&U.key===fe.key?"":(""+fe.key).replace(Z,"$&/")+"/")+le)),J.push(fe)),1;le=0;var we=ue===""?".":ue+":";if(j(U))for(var me=0;me<U.length;me++)ue=U[me],de=we+te(ue,me),le+=P(ue,J,I,de,fe);else if(me=E(U),typeof me=="function")for(U=me.call(U),me=0;!(ue=U.next()).done;)ue=ue.value,de=we+te(ue,me++),le+=P(ue,J,I,de,fe);else if(de==="object"){if(typeof U.then=="function")return P(Q(U),J,I,ue,fe);throw J=String(U),Error("Objects are not valid as a React child (found: "+(J==="[object Object]"?"object with keys {"+Object.keys(U).join(", ")+"}":J)+"). If you meant to render a collection of children, use an arLine truncated
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
`+qe+n+dt}var qn=!1;function Yn(n,i){if(!n||qn)return"";qn=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var c={DetermineComponentFrameRoot:function(){try{if(i){var oe=function(){throw Error()};if(Object.defineProperty(oe.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(oe,[])}catch(J){var K=J}Reflect.construct(n,[],oe)}else{try{oe.call()}catch(J){K=J}n.call(oe.prototype)}}else{try{throw Error()}catch(J){K=J}(oe=n())&&typeof oe.catch=="function"&&oe.catch(function(){})}}catch(J){if(J&&K&&typeof J.stack=="string")return[J.stack,K.stack]}return[null,null]}};c.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var m=Object.getOwnPropertyDescriptor(c.DetermineComponentFrameRoot,"name");m&&m.configurable&&Object.defineProperty(c.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var y=c.DetermineComponentFrameRoot(),x=y[0],N=y[1];if(x&&N){var U=x.split(`
`),q=N.split(`
`);for(m=c=0;c<U.length&&!U[c].includes("DetermineComponentFrameRoot");)c++;for(;m<q.length&&!q[m].includes("DetermineComponentFrameRoot");)m++;if(c===U.length||m===q.length)for(c=U.length-1,m=q.length-1;1<=c&&0<=m&&U[c]!==q[m];)m--;for(;1<=c&&0<=m;c--,m--)if(U[c]!==q[m]){if(c!==1||m!==1)do if(c--,m--,0>m||U[c]!==q[m]){var re=`
`+U[c].replace(" at new "," at ");return n.displayName&&re.includes("<anonymous>")&&(re=re.replace("<anonymous>",n.displayName)),re}while(1<=c&&0<=m);break}}}finally{qn=!1,Error.prepareStackTrace=l}return(l=n?n.displayName||n.name:"")?st(l):""}function Cr(n,i){switch(n.tag){case 26:case 27:case 5:return st(n.type);case 16:return st("Lazy");case 13:return n.child!==i&&i!==null?st("Suspense Fallback"):st("Suspense");case 19:return st("SuspenseList");case 0:case 15:return Yn(n.type,!1);case 11:return Yn(n.type.render,!1);case 1:return Yn(n.type,!0);case 31:return st("Activity");default:return""}}function ql(n){try{var i="",l=null;do i+=Cr(n,l),l=n,n=n.return;while(n);return i}catch(c){return`
`+qe+n+dt}var qn=!1;function Yn(n,i){if(!n||qn)return"";qn=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var c={DetermineComponentFrameRoot:function(){try{if(i){var oe=function(){throw Error()};if(Object.defineProperty(oe.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(oe,[])}catch(ee){var X=ee}Reflect.construct(n,[],oe)}else{try{oe.call()}catch(ee){X=ee}n.call(oe.prototype)}}else{try{throw Error()}catch(ee){X=ee}(oe=n())&&typeof oe.catch=="function"&&oe.catch(function(){})}}catch(ee){if(ee&&X&&typeof ee.stack=="string")return[ee.stack,X.stack]}return[null,null]}};c.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var m=Object.getOwnPropertyDescriptor(c.DetermineComponentFrameRoot,"name");m&&m.configurable&&Object.defineProperty(c.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var y=c.DetermineComponentFrameRoot(),x=y[0],N=y[1];if(x&&N){var B=x.split(`
`),Y=N.split(`
`);for(m=c=0;c<B.length&&!B[c].includes("DetermineComponentFrameRoot");)c++;for(;m<Y.length&&!Y[m].includes("DetermineComponentFrameRoot");)m++;if(c===B.length||m===Y.length)for(c=B.length-1,m=Y.length-1;1<=c&&0<=m&&B[c]!==Y[m];)m--;for(;1<=c&&0<=m;c--,m--)if(B[c]!==Y[m]){if(c!==1||m!==1)do if(c--,m--,0>m||B[c]!==Y[m]){var re=`
`+B[c].replace(" at new "," at ");return n.displayName&&re.includes("<anonymous>")&&(re=re.replace("<anonymous>",n.displayName)),re}while(1<=c&&0<=m);break}}}finally{qn=!1,Error.prepareStackTrace=l}return(l=n?n.displayName||n.name:"")?st(l):""}function Cr(n,i){switch(n.tag){case 26:case 27:case 5:return st(n.type);case 16:return st("Lazy");case 13:return n.child!==i&&i!==null?st("Suspense Fallback"):st("Suspense");case 19:return st("SuspenseList");case 0:case 15:return Yn(n.type,!1);case 11:return Yn(n.type.render,!1);case 1:return Yn(n.type,!0);case 31:return st("Activity");default:return""}}function ql(n){try{var i="",l=null;do i+=Cr(n,l),l=n,n=n.return;while(n);return i}catch(c){return`
Error generating stack: `+c.message+`
`+c.stack}}var Ya=Object.prototype.hasOwnProperty,Wa=e.unstable_scheduleCallback,po=e.unstable_cancelCallback,Gc=e.unstable_shouldYield,Hc=e.unstable_requestPaint,zt=e.unstable_now,Vc=e.unstable_getCurrentPriorityLevel,ae=e.unstable_ImmediatePriority,pe=e.unstable_UserBlockingPriority,ke=e.unstable_NormalPriority,Me=e.unstable_LowPriority,We=e.unstable_IdlePriority,nn=e.log,or=e.unstable_setDisableYieldValue,Pt=null,vt=null;function Vt(n){if(typeof nn=="function"&&or(n),vt&&typeof vt.setStrictMode=="function")try{vt.setStrictMode(Pt,n)}catch{}}var tt=Math.clz32?Math.clz32:SO,Or=Math.log,In=Math.LN2;function SO(n){return n>>>=0,n===0?32:31-(Or(n)/In|0)|0}var Yl=256,Wl=262144,Kl=4194304;function ma(n){var i=n&42;if(i!==0)return i;switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return n&261888;case 262144:case 524288:case 1048576:case 2097152:return n&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return n&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return n}}function Xl(n,i,l){var c=n.pendingLanes;if(c===0)return 0;var m=0,y=n.suspendedLanes,x=n.pingedLanes;n=n.warmLanes;var N=c&134217727;return N!==0?(c=N&~y,c!==0?m=ma(c):(x&=N,x!==0?m=ma(x):l||(l=N&~n,l!==0&&(m=ma(l))))):(N=c&~y,N!==0?m=ma(N):x!==0?m=ma(x):l||(l=c&~n,l!==0&&(m=ma(l)))),m===0?0:i!==0&&i!==m&&(i&y)===0&&(y=m&-m,l=i&-i,y>=l||y===32&&(l&4194048)!==0)?i:m}function fo(n,i){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&i)===0}function xO(n,i){switch(n){case 1:case 2:case 4:case 8:case 64:return i+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return i+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Mv(){var n=Kl;return Kl<<=1,(Kl&62914560)===0&&(Kl=4194304),n}function qc(n){for(var i=[],l=0;31>l;l++)i.push(n);return i}function go(n,i){n.pendingLanes|=i,i!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function wO(n,i,l,c,m,y){var x=n.pendingLanes;n.pendingLanes=l,n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0,n.expiredLanes&=l,n.entangledLanes&=l,n.errorRecoveryDisabledLanes&=l,n.shellSuspendCounter=0;var N=n.entanglements,U=n.expirationTimes,q=n.hiddenUpdates;for(l=x&~l;0<l;){var re=31-tt(l),oe=1<<re;N[re]=0,U[re]=-1;var K=q[re];if(K!==null)for(q[re]=null,re=0;re<K.length;re++){var J=K[re];J!==null&&(J.lane&=-536870913)}l&=~oe}c!==0&&jv(n,c,0),y!==0&&m===0&&n.tag!==0&&(n.suspendedLanes|=y&~(x&~i))}function jv(n,i,l){n.pendingLanes|=i,n.suspendedLanes&=~i;var c=31-tt(i);n.entangledLanes|=i,n.entanglements[c]=n.entanglements[c]|1073741824|l&261930}function Uv(n,i){var l=n.entangledLanes|=i;for(n=n.entanglements;l;){var c=31-tt(l),m=1<<c;m&i|n[c]&i&&(n[c]|=i),l&=~m}}function Fv(n,i){var l=i&-i;return l=(l&42)!==0?1:Yc(l),(l&(n.suspendedLanes|i))!==0?0:l}function Yc(n){switch(n){case 2:n=1;break;case 8:n=4;break;case 32:n=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:n=128;break;case 268435456:n=134217728;break;default:n=0}return n}function Wc(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function Bv(){var n=Y.p;return n!==0?n:(n=window.event,n===void 0?32:TT(n.type))}function zv(n,i){var l=Y.p;try{return Y.p=n,i()}finally{Y.p=l}}var Ir=Math.random().toString(36).slice(2),kt="__reactFiber$"+Ir,qt="__reactProps$"+Ir,Ka="__reactContainer$"+Ir,Kc="__reactEvents$"+Ir,TO="__reactListeners$"+Ir,AO="__reactHandles$"+Ir,Pv="__reactResources$"+Ir,mo="__reactMarker$"+Ir;function Xc(n){delete n[kt],delete n[qt],delete n[Kc],delete n[TO],delete n[AO]}function Xa(n){var i=n[kt];if(i)return i;for(var l=n.parentNode;l;){if(i=l[Ka]||l[kt]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=sT(n);n!==null;){if(l=n[kt])return l;n=sT(n)}return i}n=l,l=n.parentNode}return null}function Za(n){if(n=n[kt]||n[Ka]){var i=n.tag;if(i===5||i===6||i===13||i===31||i===26||i===27||i===3)return n}return null}function ho(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n.stateNode;throw Error(a(33))}function Qa(n){var i=n[Pv];return i||(i=n[Pv]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function wt(n){n[mo]=!0}var $v=new Set,Gv={};function ha(n,i){Ja(n,i),Ja(n+"Capture",i)}function Ja(n,i){for(Gv[n]=i,n=0;n<i.length;n++)$v.add(i[n])}var kO=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\ Line truncated
`).replace(r1,"")}function Qw(n,i){return i=Zw(i),Zw(n)===i}function Je(n,i,l,c,m,y){switch(l){case"children":typeof c=="string"?i==="body"||i==="textarea"&&c===""||ti(n,c):(typeof c=="number"||typeof c=="bigint")&&i!=="body"&&ti(n,""+c);break;case"className":Ql(n,"class",c);break;case"tabIndex":Ql(n,"tabindex",c);break;case"dir":case"role":case"viewBox":case"width":case"height":Ql(n,l,c);break;case"style":Qv(n,c,y);break;case"data":if(i!=="object"){Ql(n,"data",c);break}case"src":case"href":if(c===""&&(i!=="a"||l!=="href")){n.removeAttribute(l);break}if(c==null||typeof c=="function"||typeof c=="symbol"||typeof c=="boolean"){n.removeAttribute(l);break}c=es(""+c),n.setAttribute(l,c);break;case"action":case"formAction":if(typeof c=="function"){n.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof y=="function"&&(l==="formAction"?(i!=="input"&&Je(n,i,"name",m.name,m,null),Je(n,i,"formEncType",m.formEncType,m,null),Je(n,i,"formMethod",m.formMethod,m,null),Je(n,i,"formTarget",m.formTarget,m,null)):(Je(n,i,"encType",m.encType,m,null),Je(n,i,"method",m.method,m,null),Je(n,i,"target",m.target,m,null)));if(c==null||typeof c=="symbol"||typeof c=="boolean"){n.removeAttribute(l);break}c=es(""+c),n.setAttribute(l,c);break;case"onClick":c!=null&&(n.onclick=sr);break;case"onScroll":c!=null&&Ue("scroll",n);break;case"onScrollEnd":c!=null&&Ue("scrollend",n);break;case"dangerouslySetInnerHTML":if(c!=null){if(typeof c!="object"||!("__html"in c))throw Error(a(61));if(l=c.__html,l!=null){if(m.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=c&&typeof c!="function"&&typeof c!="symbol";break;case"muted":n.muted=c&&typeof c!="function"&&typeof c!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(c==null||typeof c=="function"||typeof c=="boolean"||typeof c=="symbol"){n.removeAttribute("xlink:href");break}l=es(""+c),n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":c!=null&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,""+c):n.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":c&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":c===!0?n.setAttribute(l,""):c!==!1&&c!=null&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,c):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":c!=null&&typeof c!="function"&&typeof c!="symbol"&&!isNaN(c)&&1<=c?n.setAttribute(l,c):n.removeAttribute(l);break;case"rowSpan":case"start":c==null||typeof c=="function"||typeof c=="symbol"||isNaN(c)?n.removeAttribute(l):n.setAttribute(l,c);break;case"popover":Ue("beforetoggle",n),Ue("toggle",n),Zl(n,"popover",c);break;case"xlinkActuate":lr(n,"http://www.w3.org/1999/xlink","xlink:actuate",c);break;case"xlinkArcrole":lr(n,"http://www.w3.org/1999/xlink","xlink:arcrole",c);break;case"xlinkRole":lr(n,"http://www.w3.org/1999/xlink","xlink:role",c);break;case"xlinkShow":lr(n,"http://www.w3.org/1999/xlink","xlink:show",c);break;case"xlinkTitle":lr(n,"http://www.w3.org/1999/xlink","xlink:title",c);break;case"xlinkType":lr(n,"http://www.w3.org/1999/xlink","xlink:type",c);break;case"xmlBase":lr(n,"http://www.w3.org/XML/1998/namespace","xml:base",c);break;case"xmlLang":lr(n,"http://www.w3.org/XML/1998/namespace","xml:lang",c);break;case"xmlSpace":lr(n,"http://www.w3.org/XML/1998/namespace","xml:space",c);break;case"is":Zl(n,"is",c);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=OO.get(l)||l,Zl(n,l,c))}}function zp(n,i,l,c,m,y){switch(l){case"style":Qv(n,c,y);break;case"dangerouslySetInnerHTML":if(c!=null){if(typeof c!="object"||!("__html"in c))throw Error(a(61));if(l=c.__html,l!=null){if(m.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof c=="string"?ti(n,c):(typeof c=="number"||typeof c=="bigint")&&ti(n,""+c);break;case"onScroll":c!=null&&Ue("scroll",n);break;case"onScrollEnd":c!=null&&Ue("scrollend",n);break;case"onClick":c!=null&&(n.onclick=sr);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
`,f="/",h="*",b="",v="comment",E="declaration";function w(k,A){if(typeof k!="string")throw new TypeError("First argument must be a string");if(!k)return[];A=A||{};var _=1,R=1;function L(te){var Z=te.match(t);Z&&(_+=Z.length);var B=te.lastIndexOf(p);R=~B?te.length-B:R+te.length}function F(){var te={line:_,column:R};return function(Z){return Z.position=new D(te),W(),Z}}function D(te){this.start=te,this.end={line:_,column:R},this.source=A.source}D.prototype.content=k;function P(te){var Z=new Error(A.source+":"+_+":"+R+": "+te);if(Z.reason=te,Z.filename=A.source,Z.line=_,Z.column=R,Z.source=k,!A.silent)throw Z}function z(te){var Z=te.exec(k);if(Z){var B=Z[0];return L(B),k=k.slice(B.length),Z}}function W(){z(r)}function M(te){var Z;for(te=te||[];Z=H();)Z!==!1&&te.push(Z);return te}function H(){var te=F();if(!(f!=k.charAt(0)||h!=k.charAt(1))){for(var Z=2;b!=k.charAt(Z)&&(h!=k.charAt(Z)||f!=k.charAt(Z+1));)++Z;if(Z+=2,b===k.charAt(Z-1))return P("End of comment missing");var B=k.slice(2,Z-2);return R+=2,L(B),k=k.slice(Z),R+=2,te({type:v,comment:B})}}function X(){var te=F(),Z=z(a);if(Z){if(H(),!z(o))return P("property missing ':'");var B=z(s),Y=te({type:E,property:T(Z[0].replace(e,b)),value:B?T(B[0].replace(e,b)):b});return z(u),Y}}function ee(){var te=[];M(te);for(var Z;Z=X();)Z!==!1&&(te.push(Z),M(te));return te}return W(),ee()}function T(k){return k?k.replace(d,b):b}return ff=w,ff}var aA;function HD(){if(aA)return Li;aA=1;var e=Li&&Li.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(Li,"__esModule",{value:!0}),Li.default=r;const t=e(GD());function r(a,o){let s=null;if(!a||typeof a!="string")return s;const u=(0,t.default)(a),d=typeof o=="function";return u.forEach(p=>{if(p.type!=="declaration")return;const{property:f,value:h}=p;d?o(f,h,p):h&&(s=s||{},s[f]=h)}),s}return Li}var ll={},iA;function VD(){if(iA)return ll;iA=1,Object.defineProperty(ll,"__esModule",{value:!0}),ll.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,r=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,o=/^-(ms)-/,s=function(f){return!f||r.test(f)||e.test(f)},u=function(f,h){return h.toUpperCase()},d=function(f,h){return"".concat(h,"-")},p=function(f,h){return h===void 0&&(h={}),s(f)?f:(f=f.toLowerCase(),h.reactCompat?f=f.replace(o,d):f=f.replace(a,d),f.replace(t,u))};return ll.camelCase=p,ll}var sl,oA;function qD(){if(oA)return sl;oA=1;var e=sl&&sl.__importDefault||function(o){return o&&o.__esModule?o:{default:o}},t=e(HD()),r=VD();function a(o,s){var u={};return!o||typeof o!="string"||(0,t.default)(o,function(d,p){d&&p&&(u[(0,r.camelCase)(d,s)]=p)}),u}return a.default=a,sl=a,sl}var YD=qD();const WD=Zu(YD),D_=M_("end"),Dg=M_("start");function M_(e){return t;function t(r){const a=r&&r.position&&r.position[e]||{};if(typeof a.line=="number"&&a.line>0&&typeof a.column=="number"&&a.column>0)return{line:a.line,column:a.column,offset:typeof a.offset=="number"&&a.offset>-1?a.offset:void 0}}}function KD(e){const t=Dg(e),r=D_(e);if(t&&r)return{start:t,end:r}}function gl(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?lA(e.position):"start"in e||"end"in e?lA(e):"line"in e||"column"in e?Yf(e):""}function Yf(e){return sA(e&&e.line)+":"+sA(e&&e.column)}function lA(e){return Yf(e&&e.start)+"-"+Yf(e&&e.end)}function sA(e){return e&&typeof e=="number"?e:1}class Ut extends Error{constructor(t,r,a){super(),typeof r=="string"&&(a=r,r=void 0);let o="",s={},u=!1;if(r&&("line"in r&&"column"in r?s={place:r}:"start"in r&&"end"in r?s={place:r}:"type"in r?s={ancestors:[r],place:r.position}:s={...r}),typeof t=="string"?o=t:!s.cause&&t&&(u=!0,o=t.message,s.cause=t),!s.ruleId&&!s.source&&typeof a=="string"){const p=a.indexOf(":");p===-1?s.ruleId=a:(s.source=a.slice(0,p),s.ruleId=a.slice(p+1))}if(!s.place&&s.ancestors&&s.ancestors){const p=s.ancestors[s.ancestors.length-1];p&&(s.place=p.position)}const d=s.place&&"start"in s.place?s.place.start:s.place;this.ancestors=s.ancestors||void 0,this.cause=s.cause||void 0,this.column=d?d.column:void 0,this.fatal=void 0,this.file="",this.message=o,this.line=d?d.line:void 0,this.name=gl(s.place)||"1:1",this.place=s.place||void 0,this.reason=this.message,this.ruleId=s.ruleId||void 0,this.source=s.source||void 0,this.stack=u&&s.cause&&typeof s.cause.stack=="string"?s.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}Ut.prototype.file="";Ut.prototype.name="";Ut.prototype.reason="";Ut.prototype.message="";Ut.prototype.stack="";Ut.prototype.column=void 0;Ut.prototype.line=void 0;Ut.prototype.ancestors=void 0;Ut.prototype.cause=void 0;Ut.prototype.fatal=void 0;Ut.prototype.place=void 0;Ut.prototype.ruleId=void 0;Ut.prototype.source=void 0;const Mg={}.hasOwnProperty,XD=new Map,ZD=/[A-Z]/g,QD=new Set(["table","tbody","thead","tfoot","tr"]),JD=new Set(["td","th"]),j_="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function eM(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const r=t.filePath||void 0;let a;i Line truncated
`+c.stack}}var Wa=Object.prototype.hasOwnProperty,Ka=e.unstable_scheduleCallback,po=e.unstable_cancelCallback,Hc=e.unstable_shouldYield,Vc=e.unstable_requestPaint,zt=e.unstable_now,qc=e.unstable_getCurrentPriorityLevel,ae=e.unstable_ImmediatePriority,pe=e.unstable_UserBlockingPriority,ke=e.unstable_NormalPriority,Me=e.unstable_LowPriority,We=e.unstable_IdlePriority,nn=e.log,or=e.unstable_setDisableYieldValue,Pt=null,vt=null;function Vt(n){if(typeof nn=="function"&&or(n),vt&&typeof vt.setStrictMode=="function")try{vt.setStrictMode(Pt,n)}catch{}}var tt=Math.clz32?Math.clz32:xO,Or=Math.log,In=Math.LN2;function xO(n){return n>>>=0,n===0?32:31-(Or(n)/In|0)|0}var Yl=256,Wl=262144,Kl=4194304;function ma(n){var i=n&42;if(i!==0)return i;switch(n&-n){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return n&261888;case 262144:case 524288:case 1048576:case 2097152:return n&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return n&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return n}}function Xl(n,i,l){var c=n.pendingLanes;if(c===0)return 0;var m=0,y=n.suspendedLanes,x=n.pingedLanes;n=n.warmLanes;var N=c&134217727;return N!==0?(c=N&~y,c!==0?m=ma(c):(x&=N,x!==0?m=ma(x):l||(l=N&~n,l!==0&&(m=ma(l))))):(N=c&~y,N!==0?m=ma(N):x!==0?m=ma(x):l||(l=c&~n,l!==0&&(m=ma(l)))),m===0?0:i!==0&&i!==m&&(i&y)===0&&(y=m&-m,l=i&-i,y>=l||y===32&&(l&4194048)!==0)?i:m}function fo(n,i){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&i)===0}function wO(n,i){switch(n){case 1:case 2:case 4:case 8:case 64:return i+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return i+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Uv(){var n=Kl;return Kl<<=1,(Kl&62914560)===0&&(Kl=4194304),n}function Yc(n){for(var i=[],l=0;31>l;l++)i.push(n);return i}function go(n,i){n.pendingLanes|=i,i!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function TO(n,i,l,c,m,y){var x=n.pendingLanes;n.pendingLanes=l,n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0,n.expiredLanes&=l,n.entangledLanes&=l,n.errorRecoveryDisabledLanes&=l,n.shellSuspendCounter=0;var N=n.entanglements,B=n.expirationTimes,Y=n.hiddenUpdates;for(l=x&~l;0<l;){var re=31-tt(l),oe=1<<re;N[re]=0,B[re]=-1;var X=Y[re];if(X!==null)for(Y[re]=null,re=0;re<X.length;re++){var ee=X[re];ee!==null&&(ee.lane&=-536870913)}l&=~oe}c!==0&&Fv(n,c,0),y!==0&&m===0&&n.tag!==0&&(n.suspendedLanes|=y&~(x&~i))}function Fv(n,i,l){n.pendingLanes|=i,n.suspendedLanes&=~i;var c=31-tt(i);n.entangledLanes|=i,n.entanglements[c]=n.entanglements[c]|1073741824|l&261930}function Bv(n,i){var l=n.entangledLanes|=i;for(n=n.entanglements;l;){var c=31-tt(l),m=1<<c;m&i|n[c]&i&&(n[c]|=i),l&=~m}}function zv(n,i){var l=i&-i;return l=(l&42)!==0?1:Wc(l),(l&(n.suspendedLanes|i))!==0?0:l}function Wc(n){switch(n){case 2:n=1;break;case 8:n=4;break;case 32:n=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:n=128;break;case 268435456:n=134217728;break;default:n=0}return n}function Kc(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function Pv(){var n=W.p;return n!==0?n:(n=window.event,n===void 0?32:kT(n.type))}function $v(n,i){var l=W.p;try{return W.p=n,i()}finally{W.p=l}}var Ir=Math.random().toString(36).slice(2),kt="__reactFiber$"+Ir,qt="__reactProps$"+Ir,Xa="__reactContainer$"+Ir,Xc="__reactEvents$"+Ir,AO="__reactListeners$"+Ir,kO="__reactHandles$"+Ir,Gv="__reactResources$"+Ir,mo="__reactMarker$"+Ir;function Zc(n){delete n[kt],delete n[qt],delete n[Xc],delete n[AO],delete n[kO]}function Za(n){var i=n[kt];if(i)return i;for(var l=n.parentNode;l;){if(i=l[Xa]||l[kt]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=cT(n);n!==null;){if(l=n[kt])return l;n=cT(n)}return i}n=l,l=n.parentNode}return null}function Qa(n){if(n=n[kt]||n[Xa]){var i=n.tag;if(i===5||i===6||i===13||i===31||i===26||i===27||i===3)return n}return null}function ho(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n.stateNode;throw Error(a(33))}function Ja(n){var i=n[Gv];return i||(i=n[Gv]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function wt(n){n[mo]=!0}var Hv=new Set,Vv={};function ha(n,i){ei(n,i),ei(n+"Capture",i)}function ei(n,i){for(Vv[n]=i,n=0;n<i.length;n++)Hv.add(i[n])}var _O=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u20 Line truncated
`).replace(a1,"")}function eT(n,i){return i=Jw(i),Jw(n)===i}function Je(n,i,l,c,m,y){switch(l){case"children":typeof c=="string"?i==="body"||i==="textarea"&&c===""||ni(n,c):(typeof c=="number"||typeof c=="bigint")&&i!=="body"&&ni(n,""+c);break;case"className":Ql(n,"class",c);break;case"tabIndex":Ql(n,"tabindex",c);break;case"dir":case"role":case"viewBox":case"width":case"height":Ql(n,l,c);break;case"style":eS(n,c,y);break;case"data":if(i!=="object"){Ql(n,"data",c);break}case"src":case"href":if(c===""&&(i!=="a"||l!=="href")){n.removeAttribute(l);break}if(c==null||typeof c=="function"||typeof c=="symbol"||typeof c=="boolean"){n.removeAttribute(l);break}c=es(""+c),n.setAttribute(l,c);break;case"action":case"formAction":if(typeof c=="function"){n.setAttribute(l,"javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')");break}else typeof y=="function"&&(l==="formAction"?(i!=="input"&&Je(n,i,"name",m.name,m,null),Je(n,i,"formEncType",m.formEncType,m,null),Je(n,i,"formMethod",m.formMethod,m,null),Je(n,i,"formTarget",m.formTarget,m,null)):(Je(n,i,"encType",m.encType,m,null),Je(n,i,"method",m.method,m,null),Je(n,i,"target",m.target,m,null)));if(c==null||typeof c=="symbol"||typeof c=="boolean"){n.removeAttribute(l);break}c=es(""+c),n.setAttribute(l,c);break;case"onClick":c!=null&&(n.onclick=sr);break;case"onScroll":c!=null&&Ue("scroll",n);break;case"onScrollEnd":c!=null&&Ue("scrollend",n);break;case"dangerouslySetInnerHTML":if(c!=null){if(typeof c!="object"||!("__html"in c))throw Error(a(61));if(l=c.__html,l!=null){if(m.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=c&&typeof c!="function"&&typeof c!="symbol";break;case"muted":n.muted=c&&typeof c!="function"&&typeof c!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(c==null||typeof c=="function"||typeof c=="boolean"||typeof c=="symbol"){n.removeAttribute("xlink:href");break}l=es(""+c),n.setAttributeNS("http://www.w3.org/1999/xlink","xlink:href",l);break;case"contentEditable":case"spellCheck":case"draggable":case"value":case"autoReverse":case"externalResourcesRequired":case"focusable":case"preserveAlpha":c!=null&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,""+c):n.removeAttribute(l);break;case"inert":case"allowFullScreen":case"async":case"autoPlay":case"controls":case"default":case"defer":case"disabled":case"disablePictureInPicture":case"disableRemotePlayback":case"formNoValidate":case"hidden":case"loop":case"noModule":case"noValidate":case"open":case"playsInline":case"readOnly":case"required":case"reversed":case"scoped":case"seamless":case"itemScope":c&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":c===!0?n.setAttribute(l,""):c!==!1&&c!=null&&typeof c!="function"&&typeof c!="symbol"?n.setAttribute(l,c):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":c!=null&&typeof c!="function"&&typeof c!="symbol"&&!isNaN(c)&&1<=c?n.setAttribute(l,c):n.removeAttribute(l);break;case"rowSpan":case"start":c==null||typeof c=="function"||typeof c=="symbol"||isNaN(c)?n.removeAttribute(l):n.setAttribute(l,c);break;case"popover":Ue("beforetoggle",n),Ue("toggle",n),Zl(n,"popover",c);break;case"xlinkActuate":lr(n,"http://www.w3.org/1999/xlink","xlink:actuate",c);break;case"xlinkArcrole":lr(n,"http://www.w3.org/1999/xlink","xlink:arcrole",c);break;case"xlinkRole":lr(n,"http://www.w3.org/1999/xlink","xlink:role",c);break;case"xlinkShow":lr(n,"http://www.w3.org/1999/xlink","xlink:show",c);break;case"xlinkTitle":lr(n,"http://www.w3.org/1999/xlink","xlink:title",c);break;case"xlinkType":lr(n,"http://www.w3.org/1999/xlink","xlink:type",c);break;case"xmlBase":lr(n,"http://www.w3.org/XML/1998/namespace","xml:base",c);break;case"xmlLang":lr(n,"http://www.w3.org/XML/1998/namespace","xml:lang",c);break;case"xmlSpace":lr(n,"http://www.w3.org/XML/1998/namespace","xml:space",c);break;case"is":Zl(n,"is",c);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=IO.get(l)||l,Zl(n,l,c))}}function Pp(n,i,l,c,m,y){switch(l){case"style":eS(n,c,y);break;case"dangerouslySetInnerHTML":if(c!=null){if(typeof c!="object"||!("__html"in c))throw Error(a(61));if(l=c.__html,l!=null){if(m.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof c=="string"?ni(n,c):(typeof c=="number"||typeof c=="bigint")&&ni(n,""+c);break;case"onScroll":c!=null&&Ue("scroll",n);break;case"onScrollEnd":c!=null&&Ue("scrollend",n);break;case"onClick":c!=null&&(n.onclick=sr);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne Line truncated
`,f="/",h="*",b="",v="comment",E="declaration";function w(k,A){if(typeof k!="string")throw new TypeError("First argument must be a string");if(!k)return[];A=A||{};var _=1,R=1;function L(te){var Q=te.match(t);Q&&(_+=Q.length);var P=te.lastIndexOf(p);R=~P?te.length-P:R+te.length}function j(){var te={line:_,column:R};return function(Q){return Q.position=new D(te),V(),Q}}function D(te){this.start=te,this.end={line:_,column:R},this.source=A.source}D.prototype.content=k;function z(te){var Q=new Error(A.source+":"+_+":"+R+": "+te);if(Q.reason=te,Q.filename=A.source,Q.line=_,Q.column=R,Q.source=k,!A.silent)throw Q}function F(te){var Q=te.exec(k);if(Q){var P=Q[0];return L(P),k=k.slice(P.length),Q}}function V(){F(r)}function M(te){var Q;for(te=te||[];Q=H();)Q!==!1&&te.push(Q);return te}function H(){var te=j();if(!(f!=k.charAt(0)||h!=k.charAt(1))){for(var Q=2;b!=k.charAt(Q)&&(h!=k.charAt(Q)||f!=k.charAt(Q+1));)++Q;if(Q+=2,b===k.charAt(Q-1))return z("End of comment missing");var P=k.slice(2,Q-2);return R+=2,L(P),k=k.slice(Q),R+=2,te({type:v,comment:P})}}function K(){var te=j(),Q=F(a);if(Q){if(H(),!F(o))return z("property missing ':'");var P=F(s),W=te({type:E,property:T(Q[0].replace(e,b)),value:P?T(P[0].replace(e,b)):b});return F(u),W}}function Z(){var te=[];M(te);for(var Q;Q=K();)Q!==!1&&(te.push(Q),M(te));return te}return V(),Z()}function T(k){return k?k.replace(d,b):b}return mf=w,mf}var iA;function VD(){if(iA)return Di;iA=1;var e=Di&&Di.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(Di,"__esModule",{value:!0}),Di.default=r;const t=e(HD());function r(a,o){let s=null;if(!a||typeof a!="string")return s;const u=(0,t.default)(a),d=typeof o=="function";return u.forEach(p=>{if(p.type!=="declaration")return;const{property:f,value:h}=p;d?o(f,h,p):h&&(s=s||{},s[f]=h)}),s}return Di}var ll={},oA;function qD(){if(oA)return ll;oA=1,Object.defineProperty(ll,"__esModule",{value:!0}),ll.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,r=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,o=/^-(ms)-/,s=function(f){return!f||r.test(f)||e.test(f)},u=function(f,h){return h.toUpperCase()},d=function(f,h){return"".concat(h,"-")},p=function(f,h){return h===void 0&&(h={}),s(f)?f:(f=f.toLowerCase(),h.reactCompat?f=f.replace(o,d):f=f.replace(a,d),f.replace(t,u))};return ll.camelCase=p,ll}var sl,lA;function YD(){if(lA)return sl;lA=1;var e=sl&&sl.__importDefault||function(o){return o&&o.__esModule?o:{default:o}},t=e(VD()),r=qD();function a(o,s){var u={};return!o||typeof o!="string"||(0,t.default)(o,function(d,p){d&&p&&(u[(0,r.camelCase)(d,s)]=p)}),u}return a.default=a,sl=a,sl}var WD=YD();const KD=Qu(WD),M_=j_("end"),jg=j_("start");function j_(e){return t;function t(r){const a=r&&r.position&&r.position[e]||{};if(typeof a.line=="number"&&a.line>0&&typeof a.column=="number"&&a.column>0)return{line:a.line,column:a.column,offset:typeof a.offset=="number"&&a.offset>-1?a.offset:void 0}}}function XD(e){const t=jg(e),r=M_(e);if(t&&r)return{start:t,end:r}}function gl(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?sA(e.position):"start"in e||"end"in e?sA(e):"line"in e||"column"in e?Kf(e):""}function Kf(e){return uA(e&&e.line)+":"+uA(e&&e.column)}function sA(e){return Kf(e&&e.start)+"-"+Kf(e&&e.end)}function uA(e){return e&&typeof e=="number"?e:1}class Ut extends Error{constructor(t,r,a){super(),typeof r=="string"&&(a=r,r=void 0);let o="",s={},u=!1;if(r&&("line"in r&&"column"in r?s={place:r}:"start"in r&&"end"in r?s={place:r}:"type"in r?s={ancestors:[r],place:r.position}:s={...r}),typeof t=="string"?o=t:!s.cause&&t&&(u=!0,o=t.message,s.cause=t),!s.ruleId&&!s.source&&typeof a=="string"){const p=a.indexOf(":");p===-1?s.ruleId=a:(s.source=a.slice(0,p),s.ruleId=a.slice(p+1))}if(!s.place&&s.ancestors&&s.ancestors){const p=s.ancestors[s.ancestors.length-1];p&&(s.place=p.position)}const d=s.place&&"start"in s.place?s.place.start:s.place;this.ancestors=s.ancestors||void 0,this.cause=s.cause||void 0,this.column=d?d.column:void 0,this.fatal=void 0,this.file="",this.message=o,this.line=d?d.line:void 0,this.name=gl(s.place)||"1:1",this.place=s.place||void 0,this.reason=this.message,this.ruleId=s.ruleId||void 0,this.source=s.source||void 0,this.stack=u&&s.cause&&typeof s.cause.stack=="string"?s.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}Ut.prototype.file="";Ut.prototype.name="";Ut.prototype.reason="";Ut.prototype.message="";Ut.prototype.stack="";Ut.prototype.column=void 0;Ut.prototype.line=void 0;Ut.prototype.ancestors=void 0;Ut.prototype.cause=void 0;Ut.prototype.fatal=void 0;Ut.prototype.place=void 0;Ut.prototype.ruleId=void 0;Ut.prototype.source=void 0;const Ug={}.hasOwnProperty,ZD=new Map,QD=/[A-Z]/g,JD=new Set(["table","tbody","thead","tfoot","tr"]),eM=new Set(["td","th"]),U_="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function tM(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const r=t.filePath||void 0;let a;if( Line truncated
`;break}case-3:{u=`\r
`;break}case-2:{u=t?" ":" ";break}case-1:{if(!t&&o)continue;u=" ";break}default:u=String.fromCharCode(s)}o=s===-2,a.push(u)}return a.join("")}function sj(e){const a={constructs:P_([aj,...(e||{}).extensions||[]]),content:o(wM),defined:[],document:o(AM),flow:o(G2),lazy:{},string:o(q2),text:o(Y2)};return a;function o(s){return u;function u(d){return ij(a,s,d)}}}function uj(e){for(;!q_(e););return e}const yA=/[\0\t\n\r]/g;function cj(){let e=1,t="",r=!0,a;return o;function o(s,u,d){const p=[];let f,h,b,v,E;for(s=t+(typeof s=="string"?s.toString():new TextDecoder(u||void 0).decode(s)),b=0,t="",r&&(s.charCodeAt(0)===65279&&b++,r=void 0);b<s.length;){if(yA.lastIndex=b,f=yA.exec(s),v=f&&f.index!==void 0?f.index:s.length,E=s.charCodeAt(v),!f){t=s.slice(b);break}if(E===10&&b===v&&a)p.push(-3),a=void 0;else switch(a&&(p.push(-5),a=void 0),b<v&&(p.push(s.slice(b,v)),e+=v-b),E){case 0:{p.push(65533),e++;break}case 9:{for(h=Math.ceil(e/4)*4,p.push(-2);e++<h;)p.push(-1);break}case 10:{p.push(-4),e=1;break}default:a=!0,e=1}b=v+1}return d&&(a&&p.push(-5),t&&p.push(t),p.push(null)),p}}const dj=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function pj(e){return e.replace(dj,fj)}function fj(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const o=r.charCodeAt(1),s=o===120||o===88;return $_(r.slice(s?2:1),s?16:10)}return xl(r)||e}const Q_={}.hasOwnProperty;function gj(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),mj(r)(uj(sj(r).document().write(cj()(e,t,!0))))}function mj(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:s(Ya),autolinkProtocol:W,autolinkEmail:W,atxHeading:s(qn),blockQuote:s(Re),characterEscape:W,characterReference:W,codeFenced:s(Oe),codeFencedFenceInfo:u,codeFencedFenceMeta:u,codeIndented:s(Oe,u),codeText:s(qe,u),codeTextData:W,data:W,codeFlowValue:W,definition:s(dt),definitionDestinationString:u,definitionLabelString:u,definitionTitleString:u,emphasis:s(st),hardBreakEscape:s(Yn),hardBreakTrailing:s(Yn),htmlFlow:s(Cr,u),htmlFlowData:W,htmlText:s(Cr,u),htmlTextData:W,image:s(ql),label:u,link:s(Ya),listItem:s(po),listItemValue:v,listOrdered:s(Wa,b),listUnordered:s(Wa),paragraph:s(Gc),reference:I,referenceString:u,resourceDestinationString:u,resourceTitleString:u,setextHeading:s(qn),strong:s(Hc),thematicBreak:s(Vc)},exit:{atxHeading:p(),atxHeadingSequence:F,autolink:p(),autolinkEmail:me,autolinkProtocol:we,blockQuote:p(),characterEscapeValue:M,characterReferenceMarkerHexadecimal:fe,characterReferenceMarkerNumeric:fe,characterReferenceValue:de,characterReference:le,codeFenced:p(k),codeFencedFence:T,codeFencedFenceInfo:E,codeFencedFenceMeta:w,codeFlowValue:M,codeIndented:p(A),codeText:p(Z),codeTextData:M,data:M,definition:p(),definitionDestinationString:L,definitionLabelString:_,definitionTitleString:R,emphasis:p(),hardBreakEscape:p(X),hardBreakTrailing:p(X),htmlFlow:p(ee),htmlFlowData:M,htmlText:p(te),htmlTextData:M,image:p(Y),label:ce,labelText:ne,lineEnding:H,link:p(B),listItem:p(),listOrdered:p(),listUnordered:p(),paragraph:p(),referenceString:ue,resourceDestinationString:C,resourceTitleString:j,resource:Q,setextHeading:p(z),setextHeadingLineSequence:P,setextHeadingText:D,strong:p(),thematicBreak:p()}};J_(t,(e||{}).mdastExtensions||[]);const r={};return a;function a(ae){let pe={type:"root",children:[]};const ke={stack:[pe],tokenStack:[],config:t,enter:d,exit:f,buffer:u,resume:h,data:r},Me=[];let We=-1;for(;++We<ae.length;)if(ae[We][1].type==="listOrdered"||ae[We][1].type==="listUnordered")if(ae[We][0]==="enter")Me.push(We);else{const nn=Me.pop();We=o(ae,nn,We)}for(We=-1;++We<ae.length;){const nn=t[ae[We][0]];Q_.call(nn,ae[We][1].type)&&nn[ae[We][1].type].call(Object.assign({sliceSerialize:ae[We][2].sliceSerialize},ke),ae[We][1])}if(ke.tokenStack.length>0){const nn=ke.tokenStack[ke.tokenStack.length-1];(nn[1]||EA).call(ke,void 0,nn[0])}for(pe.position={start:aa(ae.length>0?ae[0][1].start:{line:1,column:1,offset:0}),end:aa(ae.length>0?ae[ae.length-2][1].end:{line:1,column:1,offset:0})},We=-1;++We<t.transforms.length;)pe=t.transforms[We](pe)||pe;return pe}function o(ae,pe,ke){let Me=pe-1,We=-1,nn=!1,or,Pt,vt,Vt;for(;++Me<=ke;){const tt=ae[Me];switch(tt[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{tt[0]==="enter"?We++:We--,Vt=void 0;break}case"lineEndingBlank":{tt[0]==="enter"&&(or&&!Vt&&!We&&!vt&&(vt=Me),Vt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Vt=void 0}if(!We&&tt[0]==="enter"&&tt[1].type==="listItemPrefix"||We===-1&&tt[0]==="exit"&&(tt[1].type==="listUnordered"||tt[1].type==="listOrdered")){if(or){let Or=Me;for(Pt=void 0;Or--;){const In=ae[Or];if(In[1].type==="lineEnding"||In[1].type==="lineEndingBlank"){if(In[0]==="exit")continue;Pt&&(ae[Pt][1].type="lineEndingBlank",nn=!0),In[1].type="lineEnding",Pt=Or}else if(!(In[1].type==="linePrefix"||In[1].type==="blockQuotePrefix"||In[1].type==="blockQuotePrefixWhitesLine truncated
`}]}function vj(e,t){const r=t.value?t.value+`
`:"",a={},o=t.lang?t.lang.split(/\s+/):[];o.length>0&&(a.className=["language-"+o[0]]);let s={type:"element",tagName:"code",properties:a,children:[{type:"text",value:r}]};return t.meta&&(s.data={meta:t.meta}),e.patch(t,s),s=e.applyData(t,s),s={type:"element",tagName:"pre",properties:{},children:[s]},e.patch(t,s),s}function Sj(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function xj(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function wj(e,t){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),o=Ji(a.toLowerCase()),s=e.footnoteOrder.indexOf(a);let u,d=e.footnoteCounts.get(a);d===void 0?(d=0,e.footnoteOrder.push(a),u=e.footnoteOrder.length):u=s+1,d+=1,e.footnoteCounts.set(a,d);const p={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+o,id:r+"fnref-"+o+(d>1?"-"+d:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(u)}]};e.patch(t,p);const f={type:"element",tagName:"sup",properties:{},children:[p]};return e.patch(t,f),e.applyData(t,f)}function Tj(e,t){const r={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Aj(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function eN(e,t){const r=t.referenceType;let a="]";if(r==="collapsed"?a+="[]":r==="full"&&(a+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+a}];const o=e.all(t),s=o[0];s&&s.type==="text"?s.value="["+s.value:o.unshift({type:"text",value:"["});const u=o[o.length-1];return u&&u.type==="text"?u.value+=a:o.push({type:"text",value:a}),o}function kj(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return eN(e,t);const o={src:Ji(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"img",properties:o,children:[]};return e.patch(t,s),e.applyData(t,s)}function _j(e,t){const r={src:Ji(t.url)};t.alt!==null&&t.alt!==void 0&&(r.alt=t.alt),t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"img",properties:r,children:[]};return e.patch(t,a),e.applyData(t,a)}function Nj(e,t){const r={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,r);const a={type:"element",tagName:"code",properties:{},children:[r]};return e.patch(t,a),e.applyData(t,a)}function Rj(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return eN(e,t);const o={href:Ji(a.url||"")};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"a",properties:o,children:e.all(t)};return e.patch(t,s),e.applyData(t,s)}function Cj(e,t){const r={href:Ji(t.url)};t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"a",properties:r,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function Oj(e,t,r){const a=e.all(t),o=r?Ij(r):tN(t),s={},u=[];if(typeof t.checked=="boolean"){const h=a[0];let b;h&&h.type==="element"&&h.tagName==="p"?b=h:(b={type:"element",tagName:"p",properties:{},children:[]},a.unshift(b)),b.children.length>0&&b.children.unshift({type:"text",value:" "}),b.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),s.className=["task-list-item"]}let d=-1;for(;++d<a.length;){const h=a[d];(o||d!==0||h.type!=="element"||h.tagName!=="p")&&u.push({type:"text",value:`
`;break}case-2:{u=t?" ":" ";break}case-1:{if(!t&&o)continue;u=" ";break}default:u=String.fromCharCode(s)}o=s===-2,a.push(u)}return a.join("")}function uj(e){const a={constructs:$_([ij,...(e||{}).extensions||[]]),content:o(TM),defined:[],document:o(kM),flow:o(H2),lazy:{},string:o(Y2),text:o(W2)};return a;function o(s){return u;function u(d){return oj(a,s,d)}}}function cj(e){for(;!Y_(e););return e}const EA=/[\0\t\n\r]/g;function dj(){let e=1,t="",r=!0,a;return o;function o(s,u,d){const p=[];let f,h,b,v,E;for(s=t+(typeof s=="string"?s.toString():new TextDecoder(u||void 0).decode(s)),b=0,t="",r&&(s.charCodeAt(0)===65279&&b++,r=void 0);b<s.length;){if(EA.lastIndex=b,f=EA.exec(s),v=f&&f.index!==void 0?f.index:s.length,E=s.charCodeAt(v),!f){t=s.slice(b);break}if(E===10&&b===v&&a)p.push(-3),a=void 0;else switch(a&&(p.push(-5),a=void 0),b<v&&(p.push(s.slice(b,v)),e+=v-b),E){case 0:{p.push(65533),e++;break}case 9:{for(h=Math.ceil(e/4)*4,p.push(-2);e++<h;)p.push(-1);break}case 10:{p.push(-4),e=1;break}default:a=!0,e=1}b=v+1}return d&&(a&&p.push(-5),t&&p.push(t),p.push(null)),p}}const pj=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function fj(e){return e.replace(pj,gj)}function gj(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const o=r.charCodeAt(1),s=o===120||o===88;return G_(r.slice(s?2:1),s?16:10)}return xl(r)||e}const J_={}.hasOwnProperty;function mj(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),hj(r)(cj(uj(r).document().write(dj()(e,t,!0))))}function hj(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:s(Wa),autolinkProtocol:V,autolinkEmail:V,atxHeading:s(qn),blockQuote:s(Re),characterEscape:V,characterReference:V,codeFenced:s(Oe),codeFencedFenceInfo:u,codeFencedFenceMeta:u,codeIndented:s(Oe,u),codeText:s(qe,u),codeTextData:V,data:V,codeFlowValue:V,definition:s(dt),definitionDestinationString:u,definitionLabelString:u,definitionTitleString:u,emphasis:s(st),hardBreakEscape:s(Yn),hardBreakTrailing:s(Yn),htmlFlow:s(Cr,u),htmlFlowData:V,htmlText:s(Cr,u),htmlTextData:V,image:s(ql),label:u,link:s(Wa),listItem:s(po),listItemValue:v,listOrdered:s(Ka,b),listUnordered:s(Ka),paragraph:s(Hc),reference:I,referenceString:u,resourceDestinationString:u,resourceTitleString:u,setextHeading:s(qn),strong:s(Vc),thematicBreak:s(qc)},exit:{atxHeading:p(),atxHeadingSequence:j,autolink:p(),autolinkEmail:me,autolinkProtocol:we,blockQuote:p(),characterEscapeValue:M,characterReferenceMarkerHexadecimal:fe,characterReferenceMarkerNumeric:fe,characterReferenceValue:de,characterReference:le,codeFenced:p(k),codeFencedFence:T,codeFencedFenceInfo:E,codeFencedFenceMeta:w,codeFlowValue:M,codeIndented:p(A),codeText:p(Q),codeTextData:M,data:M,definition:p(),definitionDestinationString:L,definitionLabelString:_,definitionTitleString:R,emphasis:p(),hardBreakEscape:p(K),hardBreakTrailing:p(K),htmlFlow:p(Z),htmlFlowData:M,htmlText:p(te),htmlTextData:M,image:p(W),label:ce,labelText:ne,lineEnding:H,link:p(P),listItem:p(),listOrdered:p(),listUnordered:p(),paragraph:p(),referenceString:ue,resourceDestinationString:C,resourceTitleString:U,resource:J,setextHeading:p(F),setextHeadingLineSequence:z,setextHeadingText:D,strong:p(),thematicBreak:p()}};eN(t,(e||{}).mdastExtensions||[]);const r={};return a;function a(ae){let pe={type:"root",children:[]};const ke={stack:[pe],tokenStack:[],config:t,enter:d,exit:f,buffer:u,resume:h,data:r},Me=[];let We=-1;for(;++We<ae.length;)if(ae[We][1].type==="listOrdered"||ae[We][1].type==="listUnordered")if(ae[We][0]==="enter")Me.push(We);else{const nn=Me.pop();We=o(ae,nn,We)}for(We=-1;++We<ae.length;){const nn=t[ae[We][0]];J_.call(nn,ae[We][1].type)&&nn[ae[We][1].type].call(Object.assign({sliceSerialize:ae[We][2].sliceSerialize},ke),ae[We][1])}if(ke.tokenStack.length>0){const nn=ke.tokenStack[ke.tokenStack.length-1];(nn[1]||vA).call(ke,void 0,nn[0])}for(pe.position={start:aa(ae.length>0?ae[0][1].start:{line:1,column:1,offset:0}),end:aa(ae.length>0?ae[ae.length-2][1].end:{line:1,column:1,offset:0})},We=-1;++We<t.transforms.length;)pe=t.transforms[We](pe)||pe;return pe}function o(ae,pe,ke){let Me=pe-1,We=-1,nn=!1,or,Pt,vt,Vt;for(;++Me<=ke;){const tt=ae[Me];switch(tt[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{tt[0]==="enter"?We++:We--,Vt=void 0;break}case"lineEndingBlank":{tt[0]==="enter"&&(or&&!Vt&&!We&&!vt&&(vt=Me),Vt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Vt=void 0}if(!We&&tt[0]==="enter"&&tt[1].type==="listItemPrefix"||We===-1&&tt[0]==="exit"&&(tt[1].type==="listUnordered"||tt[1].type==="listOrdered")){if(or){let Or=Me;for(Pt=void 0;Or--;){const In=ae[Or];if(In[1].type==="lineEnding"||In[1].type==="lineEndingBlank"){if(In[0]==="exit")continue;Pt&&(ae[Pt][1].type="lineEndingBlank",nn=!0),In[1].type="lineEnding",Pt=Or}else if(!(In[1].type==="linePrefix"||In[1].type==="blockQuotePrefix"||In[1].type==="blockQuotePrefixWhitespLine truncated
`}]}function Sj(e,t){const r=t.value?t.value+`
`:"",a={},o=t.lang?t.lang.split(/\s+/):[];o.length>0&&(a.className=["language-"+o[0]]);let s={type:"element",tagName:"code",properties:a,children:[{type:"text",value:r}]};return t.meta&&(s.data={meta:t.meta}),e.patch(t,s),s=e.applyData(t,s),s={type:"element",tagName:"pre",properties:{},children:[s]},e.patch(t,s),s}function xj(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function wj(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Tj(e,t){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),o=Ji(a.toLowerCase()),s=e.footnoteOrder.indexOf(a);let u,d=e.footnoteCounts.get(a);d===void 0?(d=0,e.footnoteOrder.push(a),u=e.footnoteOrder.length):u=s+1,d+=1,e.footnoteCounts.set(a,d);const p={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+o,id:r+"fnref-"+o+(d>1?"-"+d:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(u)}]};e.patch(t,p);const f={type:"element",tagName:"sup",properties:{},children:[p]};return e.patch(t,f),e.applyData(t,f)}function Aj(e,t){const r={type:"element",tagName:"h"+t.depth,properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function kj(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function tN(e,t){const r=t.referenceType;let a="]";if(r==="collapsed"?a+="[]":r==="full"&&(a+="["+(t.label||t.identifier)+"]"),t.type==="imageReference")return[{type:"text",value:"!["+t.alt+a}];const o=e.all(t),s=o[0];s&&s.type==="text"?s.value="["+s.value:o.unshift({type:"text",value:"["});const u=o[o.length-1];return u&&u.type==="text"?u.value+=a:o.push({type:"text",value:a}),o}function _j(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return tN(e,t);const o={src:Ji(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"img",properties:o,children:[]};return e.patch(t,s),e.applyData(t,s)}function Nj(e,t){const r={src:Ji(t.url)};t.alt!==null&&t.alt!==void 0&&(r.alt=t.alt),t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"img",properties:r,children:[]};return e.patch(t,a),e.applyData(t,a)}function Rj(e,t){const r={type:"text",value:t.value.replace(/\r?\n|\r/g," ")};e.patch(t,r);const a={type:"element",tagName:"code",properties:{},children:[r]};return e.patch(t,a),e.applyData(t,a)}function Cj(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return tN(e,t);const o={href:Ji(a.url||"")};a.title!==null&&a.title!==void 0&&(o.title=a.title);const s={type:"element",tagName:"a",properties:o,children:e.all(t)};return e.patch(t,s),e.applyData(t,s)}function Oj(e,t){const r={href:Ji(t.url)};t.title!==null&&t.title!==void 0&&(r.title=t.title);const a={type:"element",tagName:"a",properties:r,children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function Ij(e,t,r){const a=e.all(t),o=r?Lj(r):nN(t),s={},u=[];if(typeof t.checked=="boolean"){const h=a[0];let b;h&&h.type==="element"&&h.tagName==="p"?b=h:(b={type:"element",tagName:"p",properties:{},children:[]},a.unshift(b)),b.children.length>0&&b.children.unshift({type:"text",value:" "}),b.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),s.className=["task-list-item"]}let d=-1;for(;++d<a.length;){const h=a[d];(o||d!==0||h.type!=="element"||h.tagName!=="p")&&u.push({type:"text",value:`
`}),h.type==="element"&&h.tagName==="p"&&!o?u.push(...h.children):u.push(h)}const p=a[a.length-1];p&&(o||p.type!=="element"||p.tagName!=="p")&&u.push({type:"text",value:`
`});const f={type:"element",tagName:"li",properties:s,children:u};return e.patch(t,f),e.applyData(t,f)}function Ij(e){let t=!1;if(e.type==="list"){t=e.spread||!1;const r=e.children;let a=-1;for(;!t&&++a<r.length;)t=tN(r[a])}return t}function tN(e){const t=e.spread;return t??e.children.length>1}function Lj(e,t){const r={},a=e.all(t);let o=-1;for(typeof t.start=="number"&&t.start!==1&&(r.start=t.start);++o<a.length;){const u=a[o];if(u.type==="element"&&u.tagName==="li"&&u.properties&&Array.isArray(u.properties.className)&&u.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const s={type:"element",tagName:t.ordered?"ol":"ul",properties:r,children:e.wrap(a,!0)};return e.patch(t,s),e.applyData(t,s)}function Dj(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Mj(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function jj(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Uj(e,t){const r=e.all(t),a=r.shift(),o=[];if(a){const u={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],u),o.push(u)}if(r.length>0){const u={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},d=Dg(t.children[1]),p=D_(t.children[t.children.length-1]);d&&p&&(u.position={start:d,end:p}),o.push(u)}const s={type:"element",tagName:"table",properties:{},children:e.wrap(o,!0)};return e.patch(t,s),e.applyData(t,s)}function Fj(e,t,r){const a=r?r.children:void 0,s=(a?a.indexOf(t):1)===0?"th":"td",u=r&&r.type==="table"?r.align:void 0,d=u?u.length:t.children.length;let p=-1;const f=[];for(;++p<d;){const b=t.children[p],v={},E=u?u[p]:void 0;E&&(v.align=E);let w={type:"element",tagName:s,properties:v,children:[]};b&&(w.children=e.all(b),e.patch(b,w),w=e.applyData(b,w)),f.push(w)}const h={type:"element",tagName:"tr",properties:{},children:e.wrap(f,!0)};return e.patch(t,h),e.applyData(t,h)}function Bj(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const vA=9,SA=32;function zj(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),o=0;const s=[];for(;a;)s.push(xA(t.slice(o,a.index),o>0,!0),a[0]),o=a.index+a[0].length,a=r.exec(t);return s.push(xA(t.slice(o),o>0,!1)),s.join("")}function xA(e,t,r){let a=0,o=e.length;if(t){let s=e.codePointAt(a);for(;s===vA||s===SA;)a++,s=e.codePointAt(a)}if(r){let s=e.codePointAt(o-1);for(;s===vA||s===SA;)o--,s=e.codePointAt(o-1)}return o>a?e.slice(a,o):""}function Pj(e,t){const r={type:"text",value:zj(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function $j(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const Gj={blockquote:yj,break:Ej,code:vj,delete:Sj,emphasis:xj,footnoteReference:wj,heading:Tj,html:Aj,imageReference:kj,image:_j,inlineCode:Nj,linkReference:Rj,link:Cj,listItem:Oj,list:Lj,paragraph:Dj,root:Mj,strong:jj,table:Uj,tableCell:Bj,tableRow:Fj,text:Pj,thematicBreak:$j,toml:fu,yaml:fu,definition:fu,footnoteDefinition:fu};function fu(){}const nN=-1,ac=0,hl=1,Bu=2,zg=3,Pg=4,$g=5,Gg=6,rN=7,aN=8,wA=typeof self=="object"?self:globalThis,Hj=(e,t)=>{const r=(o,s)=>(e.set(s,o),o),a=o=>{if(e.has(o))return e.get(o);const[s,u]=t[o];switch(s){case ac:case nN:return r(u,o);case hl:{const d=r([],o);for(const p of u)d.push(a(p));return d}case Bu:{const d=r({},o);for(const[p,f]of u)d[a(p)]=a(f);return d}case zg:return r(new Date(u),o);case Pg:{const{source:d,flags:p}=u;return r(new RegExp(d,p),o)}case $g:{const d=r(new Map,o);for(const[p,f]of u)d.set(a(p),a(f));return d}case Gg:{const d=r(new Set,o);for(const p of u)d.add(a(p));return d}case rN:{const{name:d,message:p}=u;return r(new wA[d](p),o)}case aN:return r(BigInt(u),o);case"BigInt":return r(Object(BigInt(u)),o);case"ArrayBuffer":return r(new Uint8Array(u).buffer,u);case"DataView":{const{buffer:d}=new Uint8Array(u);return r(new DataView(d),u)}}return r(new wA[s](u),o)};return a},TA=e=>Hj(new Map,e)(0),Di="",{toString:Vj}={},{keys:qj}=Object,cl=e=>{const t=typeof e;if(t!=="object"||!e)return[ac,t];const r=Vj.call(e).slice(8,-1);switch(r){case"Array":return[hl,Di];case"Object":return[Bu,Di];case"Date":return[zg,Di];case"RegExp":return[Pg,Di];case"Map":return[$g,Di];case"Set":return[Gg,Di];case"DataView":return[hl,r]}return r.includes("Array")?[hl,r]:r.includes("Error")?[rN,r]:[Bu,r]},gu=([e,t])=>e===ac&&(t==="function"||t==="symbol"),Yj=(e,t,r,a)=>{const o=(u,d)=>{const p=a.push(u)-1;return r.set(d,p),p},s=u=>{if(r.has(u))return r.get(u);let[d,p]=cl(u);switch(d){case ac:{let h=u;switch(p){case"bigint":d=aN,h=u.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+p);h=null;break;case"undefined":return o([nN],u)}return o([d,h],u)}case hl:{if(p){let v=u;return p==="DataView"?v=new Uint8Array(u.buffer):p==="ArrayBuffer"&&(v=new Uint8Array( Line truncated
`});const f={type:"element",tagName:"li",properties:s,children:u};return e.patch(t,f),e.applyData(t,f)}function Lj(e){let t=!1;if(e.type==="list"){t=e.spread||!1;const r=e.children;let a=-1;for(;!t&&++a<r.length;)t=nN(r[a])}return t}function nN(e){const t=e.spread;return t??e.children.length>1}function Dj(e,t){const r={},a=e.all(t);let o=-1;for(typeof t.start=="number"&&t.start!==1&&(r.start=t.start);++o<a.length;){const u=a[o];if(u.type==="element"&&u.tagName==="li"&&u.properties&&Array.isArray(u.properties.className)&&u.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const s={type:"element",tagName:t.ordered?"ol":"ul",properties:r,children:e.wrap(a,!0)};return e.patch(t,s),e.applyData(t,s)}function Mj(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function jj(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function Uj(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function Fj(e,t){const r=e.all(t),a=r.shift(),o=[];if(a){const u={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],u),o.push(u)}if(r.length>0){const u={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},d=jg(t.children[1]),p=M_(t.children[t.children.length-1]);d&&p&&(u.position={start:d,end:p}),o.push(u)}const s={type:"element",tagName:"table",properties:{},children:e.wrap(o,!0)};return e.patch(t,s),e.applyData(t,s)}function Bj(e,t,r){const a=r?r.children:void 0,s=(a?a.indexOf(t):1)===0?"th":"td",u=r&&r.type==="table"?r.align:void 0,d=u?u.length:t.children.length;let p=-1;const f=[];for(;++p<d;){const b=t.children[p],v={},E=u?u[p]:void 0;E&&(v.align=E);let w={type:"element",tagName:s,properties:v,children:[]};b&&(w.children=e.all(b),e.patch(b,w),w=e.applyData(b,w)),f.push(w)}const h={type:"element",tagName:"tr",properties:{},children:e.wrap(f,!0)};return e.patch(t,h),e.applyData(t,h)}function zj(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const SA=9,xA=32;function Pj(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),o=0;const s=[];for(;a;)s.push(wA(t.slice(o,a.index),o>0,!0),a[0]),o=a.index+a[0].length,a=r.exec(t);return s.push(wA(t.slice(o),o>0,!1)),s.join("")}function wA(e,t,r){let a=0,o=e.length;if(t){let s=e.codePointAt(a);for(;s===SA||s===xA;)a++,s=e.codePointAt(a)}if(r){let s=e.codePointAt(o-1);for(;s===SA||s===xA;)o--,s=e.codePointAt(o-1)}return o>a?e.slice(a,o):""}function $j(e,t){const r={type:"text",value:Pj(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function Gj(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const Hj={blockquote:Ej,break:vj,code:Sj,delete:xj,emphasis:wj,footnoteReference:Tj,heading:Aj,html:kj,imageReference:_j,image:Nj,inlineCode:Rj,linkReference:Cj,link:Oj,listItem:Ij,list:Dj,paragraph:Mj,root:jj,strong:Uj,table:Fj,tableCell:zj,tableRow:Bj,text:$j,thematicBreak:Gj,toml:fu,yaml:fu,definition:fu,footnoteDefinition:fu};function fu(){}const rN=-1,ic=0,hl=1,zu=2,$g=3,Gg=4,Hg=5,Vg=6,aN=7,iN=8,TA=typeof self=="object"?self:globalThis,Vj=(e,t)=>{const r=(o,s)=>(e.set(s,o),o),a=o=>{if(e.has(o))return e.get(o);const[s,u]=t[o];switch(s){case ic:case rN:return r(u,o);case hl:{const d=r([],o);for(const p of u)d.push(a(p));return d}case zu:{const d=r({},o);for(const[p,f]of u)d[a(p)]=a(f);return d}case $g:return r(new Date(u),o);case Gg:{const{source:d,flags:p}=u;return r(new RegExp(d,p),o)}case Hg:{const d=r(new Map,o);for(const[p,f]of u)d.set(a(p),a(f));return d}case Vg:{const d=r(new Set,o);for(const p of u)d.add(a(p));return d}case aN:{const{name:d,message:p}=u;return r(new TA[d](p),o)}case iN:return r(BigInt(u),o);case"BigInt":return r(Object(BigInt(u)),o);case"ArrayBuffer":return r(new Uint8Array(u).buffer,u);case"DataView":{const{buffer:d}=new Uint8Array(u);return r(new DataView(d),u)}}return r(new TA[s](u),o)};return a},AA=e=>Vj(new Map,e)(0),Mi="",{toString:qj}={},{keys:Yj}=Object,cl=e=>{const t=typeof e;if(t!=="object"||!e)return[ic,t];const r=qj.call(e).slice(8,-1);switch(r){case"Array":return[hl,Mi];case"Object":return[zu,Mi];case"Date":return[$g,Mi];case"RegExp":return[Gg,Mi];case"Map":return[Hg,Mi];case"Set":return[Vg,Mi];case"DataView":return[hl,r]}return r.includes("Array")?[hl,r]:r.includes("Error")?[aN,r]:[zu,r]},gu=([e,t])=>e===ic&&(t==="function"||t==="symbol"),Wj=(e,t,r,a)=>{const o=(u,d)=>{const p=a.push(u)-1;return r.set(d,p),p},s=u=>{if(r.has(u))return r.get(u);let[d,p]=cl(u);switch(d){case ic:{let h=u;switch(p){case"bigint":d=iN,h=u.toString();break;case"function":case"symbol":if(e)throw new TypeError("unable to serialize "+p);h=null;break;case"undefined":return o([rN],u)}return o([d,h],u)}case hl:{if(p){let v=u;return p==="DataView"?v=new Uint8Array(u.buffer):p==="ArrayBuffer"&&(v=new Uint8Array( Line truncated
`},{type:"element",tagName:"ol",properties:{},children:e.wrap(d,!0)},{type:"text",value:`
`}]}}const ic=(function(e){if(e==null)return eU;if(typeof e=="function")return oc(e);if(typeof e=="object")return Array.isArray(e)?Zj(e):Qj(e);if(typeof e=="string")return Jj(e);throw new Error("Expected function, string, or object as test")});function Zj(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=ic(e[r]);return oc(a);function a(...o){let s=-1;for(;++s<t.length;)if(t[s].apply(this,o))return!0;return!1}}function Qj(e){const t=e;return oc(r);function r(a){const o=a;let s;for(s in e)if(o[s]!==t[s])return!1;return!0}}function Jj(e){return oc(t);function t(r){return r&&r.type===e}}function oc(e){return t;function t(r,a,o){return!!(tU(r)&&e.call(this,r,typeof a=="number"?a:void 0,o||void 0))}}function eU(){return!0}function tU(e){return e!==null&&typeof e=="object"&&"type"in e}const iN=[],nU=!0,Xf=!1,rU="skip";function oN(e,t,r,a){let o;typeof t=="function"&&typeof r!="function"?(a=r,r=t):o=t;const s=ic(o),u=a?-1:1;d(e,void 0,[])();function d(p,f,h){const b=p&&typeof p=="object"?p:{};if(typeof b.type=="string"){const E=typeof b.tagName=="string"?b.tagName:typeof b.name=="string"?b.name:void 0;Object.defineProperty(v,"name",{value:"node ("+(p.type+(E?"<"+E+">":""))+")"})}return v;function v(){let E=iN,w,T,k;if((!t||s(p,f,h[h.length-1]||void 0))&&(E=aU(r(p,h)),E[0]===Xf))return E;if("children"in p&&p.children){const A=p;if(A.children&&E[0]!==rU)for(T=(a?A.children.length:-1)+u,k=h.concat(A);T>-1&&T<A.children.length;){const _=A.children[T];if(w=d(_,T,k)(),w[0]===Xf)return w;T=typeof w[1]=="number"?w[1]:T+u}}return E}}}function aU(e){return Array.isArray(e)?e:typeof e=="number"?[nU,e]:e==null?iN:[e]}function Hg(e,t,r,a){let o,s,u;typeof t=="function"&&typeof r!="function"?(s=void 0,u=t,o=r):(s=t,u=r,o=a),oN(e,s,d,o);function d(p,f){const h=f[f.length-1],b=h?h.children.indexOf(p):void 0;return u(p,b,h)}}const Zf={}.hasOwnProperty,iU={};function oU(e,t){const r=t||iU,a=new Map,o=new Map,s=new Map,u={...Gj,...r.handlers},d={all:f,applyData:sU,definitionById:a,footnoteById:o,footnoteCounts:s,footnoteOrder:[],handlers:u,one:p,options:r,patch:lU,wrap:cU};return Hg(e,function(h){if(h.type==="definition"||h.type==="footnoteDefinition"){const b=h.type==="definition"?a:o,v=String(h.identifier).toUpperCase();b.has(v)||b.set(v,h)}}),d;function p(h,b){const v=h.type,E=d.handlers[v];if(Zf.call(d.handlers,v)&&E)return E(d,h,b);if(d.options.passThrough&&d.options.passThrough.includes(v)){if("children"in h){const{children:T,...k}=h,A=zu(k);return A.children=d.all(h),A}return zu(h)}return(d.options.unknownHandler||uU)(d,h,b)}function f(h){const b=[];if("children"in h){const v=h.children;let E=-1;for(;++E<v.length;){const w=d.one(v[E],h);if(w){if(E&&v[E-1].type==="break"&&(!Array.isArray(w)&&w.type==="text"&&(w.value=kA(w.value)),!Array.isArray(w)&&w.type==="element")){const T=w.children[0];T&&T.type==="text"&&(T.value=kA(T.value))}Array.isArray(w)?b.push(...w):b.push(w)}}}return b}}function lU(e,t){e.position&&(t.position=KD(e))}function sU(e,t){let r=t;if(e&&e.data){const a=e.data.hName,o=e.data.hChildren,s=e.data.hProperties;if(typeof a=="string")if(r.type==="element")r.tagName=a;else{const u="children"in r?r.children:[r];r={type:"element",tagName:a,properties:{},children:u}}r.type==="element"&&s&&Object.assign(r.properties,zu(s)),"children"in r&&r.children&&o!==null&&o!==void 0&&(r.children=o)}return r}function uU(e,t){const r=t.data||{},a="value"in t&&!(Zf.call(r,"hProperties")||Zf.call(r,"hChildren"))?{type:"text",value:t.value}:{type:"element",tagName:"div",properties:{},children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function cU(e,t){const r=[];let a=-1;for(t&&r.push({type:"text",value:`
`}]}}const oc=(function(e){if(e==null)return tU;if(typeof e=="function")return lc(e);if(typeof e=="object")return Array.isArray(e)?Qj(e):Jj(e);if(typeof e=="string")return eU(e);throw new Error("Expected function, string, or object as test")});function Qj(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=oc(e[r]);return lc(a);function a(...o){let s=-1;for(;++s<t.length;)if(t[s].apply(this,o))return!0;return!1}}function Jj(e){const t=e;return lc(r);function r(a){const o=a;let s;for(s in e)if(o[s]!==t[s])return!1;return!0}}function eU(e){return lc(t);function t(r){return r&&r.type===e}}function lc(e){return t;function t(r,a,o){return!!(nU(r)&&e.call(this,r,typeof a=="number"?a:void 0,o||void 0))}}function tU(){return!0}function nU(e){return e!==null&&typeof e=="object"&&"type"in e}const oN=[],rU=!0,Qf=!1,aU="skip";function lN(e,t,r,a){let o;typeof t=="function"&&typeof r!="function"?(a=r,r=t):o=t;const s=oc(o),u=a?-1:1;d(e,void 0,[])();function d(p,f,h){const b=p&&typeof p=="object"?p:{};if(typeof b.type=="string"){const E=typeof b.tagName=="string"?b.tagName:typeof b.name=="string"?b.name:void 0;Object.defineProperty(v,"name",{value:"node ("+(p.type+(E?"<"+E+">":""))+")"})}return v;function v(){let E=oN,w,T,k;if((!t||s(p,f,h[h.length-1]||void 0))&&(E=iU(r(p,h)),E[0]===Qf))return E;if("children"in p&&p.children){const A=p;if(A.children&&E[0]!==aU)for(T=(a?A.children.length:-1)+u,k=h.concat(A);T>-1&&T<A.children.length;){const _=A.children[T];if(w=d(_,T,k)(),w[0]===Qf)return w;T=typeof w[1]=="number"?w[1]:T+u}}return E}}}function iU(e){return Array.isArray(e)?e:typeof e=="number"?[rU,e]:e==null?oN:[e]}function qg(e,t,r,a){let o,s,u;typeof t=="function"&&typeof r!="function"?(s=void 0,u=t,o=r):(s=t,u=r,o=a),lN(e,s,d,o);function d(p,f){const h=f[f.length-1],b=h?h.children.indexOf(p):void 0;return u(p,b,h)}}const Jf={}.hasOwnProperty,oU={};function lU(e,t){const r=t||oU,a=new Map,o=new Map,s=new Map,u={...Hj,...r.handlers},d={all:f,applyData:uU,definitionById:a,footnoteById:o,footnoteCounts:s,footnoteOrder:[],handlers:u,one:p,options:r,patch:sU,wrap:dU};return qg(e,function(h){if(h.type==="definition"||h.type==="footnoteDefinition"){const b=h.type==="definition"?a:o,v=String(h.identifier).toUpperCase();b.has(v)||b.set(v,h)}}),d;function p(h,b){const v=h.type,E=d.handlers[v];if(Jf.call(d.handlers,v)&&E)return E(d,h,b);if(d.options.passThrough&&d.options.passThrough.includes(v)){if("children"in h){const{children:T,...k}=h,A=Pu(k);return A.children=d.all(h),A}return Pu(h)}return(d.options.unknownHandler||cU)(d,h,b)}function f(h){const b=[];if("children"in h){const v=h.children;let E=-1;for(;++E<v.length;){const w=d.one(v[E],h);if(w){if(E&&v[E-1].type==="break"&&(!Array.isArray(w)&&w.type==="text"&&(w.value=_A(w.value)),!Array.isArray(w)&&w.type==="element")){const T=w.children[0];T&&T.type==="text"&&(T.value=_A(T.value))}Array.isArray(w)?b.push(...w):b.push(w)}}}return b}}function sU(e,t){e.position&&(t.position=XD(e))}function uU(e,t){let r=t;if(e&&e.data){const a=e.data.hName,o=e.data.hChildren,s=e.data.hProperties;if(typeof a=="string")if(r.type==="element")r.tagName=a;else{const u="children"in r?r.children:[r];r={type:"element",tagName:a,properties:{},children:u}}r.type==="element"&&s&&Object.assign(r.properties,Pu(s)),"children"in r&&r.children&&o!==null&&o!==void 0&&(r.children=o)}return r}function cU(e,t){const r=t.data||{},a="value"in t&&!(Jf.call(r,"hProperties")||Jf.call(r,"hChildren"))?{type:"text",value:t.value}:{type:"element",tagName:"div",properties:{},children:e.all(t)};return e.patch(t,a),e.applyData(t,a)}function dU(e,t){const r=[];let a=-1;for(t&&r.push({type:"text",value:`
`});++a<e.length;)a&&r.push({type:"text",value:`
`}),r.push(e[a]);return t&&e.length>0&&r.push({type:"text",value:`
`}),r}function kA(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function _A(e,t){const r=oU(e,t),a=r.one(e,void 0),o=Xj(r),s=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return o&&s.children.push({type:"text",value:`
`},o),s}function dU(e,t){return e&&"run"in e?async function(r,a){const o=_A(r,{file:a,...t});await e.run(o,a)}:function(r,a){return _A(r,{file:a,...e||t})}}function NA(e){if(e)throw e}var bf,RA;function pU(){if(RA)return bf;RA=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(f){return typeof Array.isArray=="function"?Array.isArray(f):t.call(f)==="[object Array]"},s=function(f){if(!f||t.call(f)!=="[object Object]")return!1;var h=e.call(f,"constructor"),b=f.constructor&&f.constructor.prototype&&e.call(f.constructor.prototype,"isPrototypeOf");if(f.constructor&&!h&&!b)return!1;var v;for(v in f);return typeof v>"u"||e.call(f,v)},u=function(f,h){r&&h.name==="__proto__"?r(f,h.name,{enumerable:!0,configurable:!0,value:h.newValue,writable:!0}):f[h.name]=h.newValue},d=function(f,h){if(h==="__proto__")if(e.call(f,h)){if(a)return a(f,h).value}else return;return f[h]};return bf=function p(){var f,h,b,v,E,w,T=arguments[0],k=1,A=arguments.length,_=!1;for(typeof T=="boolean"&&(_=T,T=arguments[1]||{},k=2),(T==null||typeof T!="object"&&typeof T!="function")&&(T={});k<A;++k)if(f=arguments[k],f!=null)for(h in f)b=d(T,h),v=d(f,h),T!==v&&(_&&v&&(s(v)||(E=o(v)))?(E?(E=!1,w=b&&o(b)?b:[]):w=b&&s(b)?b:{},u(T,{name:h,newValue:p(_,w,v)})):typeof v<"u"&&u(T,{name:h,newValue:v}));return T},bf}var fU=pU();const yf=Zu(fU);function Qf(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function gU(){const e=[],t={run:r,use:a};return t;function r(...o){let s=-1;const u=o.pop();if(typeof u!="function")throw new TypeError("Expected function as last argument, not "+u);d(null,...o);function d(p,...f){const h=e[++s];let b=-1;if(p){u(p);return}for(;++b<o.length;)(f[b]===null||f[b]===void 0)&&(f[b]=o[b]);o=f,h?mU(h,d)(...f):u(null,...f)}}function a(o){if(typeof o!="function")throw new TypeError("Expected `middelware` to be a function, not "+o);return e.push(o),t}}function mU(e,t){let r;return a;function a(...u){const d=e.length>u.length;let p;d&&u.push(o);try{p=e.apply(this,u)}catch(f){const h=f;if(d&&r)throw h;return o(h)}d||(p&&p.then&&typeof p.then=="function"?p.then(s,o):p instanceof Error?o(p):s(p))}function o(u,...d){r||(r=!0,t(u,...d))}function s(u){o(null,u)}}const Qn={basename:hU,dirname:bU,extname:yU,join:EU,sep:"/"};function hU(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Ol(e);let r=0,a=-1,o=e.length,s;if(t===void 0||t.length===0||t.length>e.length){for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else a<0&&(s=!0,a=o+1);return a<0?"":e.slice(r,a)}if(t===e)return"";let u=-1,d=t.length-1;for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else u<0&&(s=!0,u=o+1),d>-1&&(e.codePointAt(o)===t.codePointAt(d--)?d<0&&(a=o):(d=-1,a=u));return r===a?a=u:a<0&&(a=e.length),e.slice(r,a)}function bU(e){if(Ol(e),e.length===0)return".";let t=-1,r=e.length,a;for(;--r;)if(e.codePointAt(r)===47){if(a){t=r;break}}else a||(a=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function yU(e){Ol(e);let t=e.length,r=-1,a=0,o=-1,s=0,u;for(;t--;){const d=e.codePointAt(t);if(d===47){if(u){a=t+1;break}continue}r<0&&(u=!0,r=t+1),d===46?o<0?o=t:s!==1&&(s=1):o>-1&&(s=-1)}return o<0||r<0||s===0||s===1&&o===r-1&&o===a+1?"":e.slice(o,r)}function EU(...e){let t=-1,r;for(;++t<e.length;)Ol(e[t]),e[t]&&(r=r===void 0?e[t]:r+"/"+e[t]);return r===void 0?".":vU(r)}function vU(e){Ol(e);const t=e.codePointAt(0)===47;let r=SU(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function SU(e,t){let r="",a=0,o=-1,s=0,u=-1,d,p;for(;++u<=e.length;){if(u<e.length)d=e.codePointAt(u);else{if(d===47)break;d=47}if(d===47){if(!(o===u-1||s===1))if(o!==u-1&&s===2){if(r.length<2||a!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(p=r.lastIndexOf("/"),p!==r.length-1){p<0?(r="",a=0):(r=r.slice(0,p),a=r.length-1-r.lastIndexOf("/")),o=u,s=0;continue}}else if(r.length>0){r="",a=0,o=u,s=0;continue}}t&&(r=r.length>0?r+"/..":"..",a=2)}else r.length>0?r+="/"+e.slice(o+1,u):r=e.slice(o+1,u),a=u-o-1;o=u,s=0}else d===46&&s>-1?s++:s=-1}return r}function Ol(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const xU={cwd:wU};function wU(){return"/"}function Jf(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function TU(e){if(typeof e=="string")e=new URL(e);else if(!Jf(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return AU(e)}function AU(e){if(e.hostname!==""){consLine truncated
`:" ")+s.indentLines(s.containerFlow(a,d.current()),t?dN:gF))),f(),p}}function gF(e,t,r){return t===0?e:dN(e,t,r)}function dN(e,t,r){return(r?"":" ")+e}const mF=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];pN.peek=vF;function hF(){return{canContainEols:["delete"],enter:{strikethrough:yF},exit:{strikethrough:EF}}}function bF(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:mF}],handlers:{delete:pN}}}function yF(e){this.enter({type:"delete",children:[]},e)}function EF(e){this.exit(e)}function pN(e,t,r,a){const o=r.createTracker(a),s=r.enter("strikethrough");let u=o.move("~~");return u+=r.containerPhrasing(e,{...o.current(),before:u,after:"~"}),u+=o.move("~~"),s(),u}function vF(){return"~"}function SF(e){return e.length}function xF(e,t){const r=t||{},a=(r.align||[]).concat(),o=r.stringLength||SF,s=[],u=[],d=[],p=[];let f=0,h=-1;for(;++h<e.length;){const T=[],k=[];let A=-1;for(e[h].length>f&&(f=e[h].length);++A<e[h].length;){const _=wF(e[h][A]);if(r.alignDelimiters!==!1){const R=o(_);k[A]=R,(p[A]===void 0||R>p[A])&&(p[A]=R)}T.push(_)}u[h]=T,d[h]=k}let b=-1;if(typeof a=="object"&&"length"in a)for(;++b<f;)s[b]=jA(a[b]);else{const T=jA(a);for(;++b<f;)s[b]=T}b=-1;const v=[],E=[];for(;++b<f;){const T=s[b];let k="",A="";T===99?(k=":",A=":"):T===108?k=":":T===114&&(A=":");let _=r.alignDelimiters===!1?1:Math.max(1,p[b]-k.length-A.length);const R=k+"-".repeat(_)+A;r.alignDelimiters!==!1&&(_=k.length+_+A.length,_>p[b]&&(p[b]=_),E[b]=_),v[b]=R}u.splice(1,0,v),d.splice(1,0,E),h=-1;const w=[];for(;++h<u.length;){const T=u[h],k=d[h];b=-1;const A=[];for(;++b<f;){const _=T[b]||"";let R="",L="";if(r.alignDelimiters!==!1){const F=p[b]-(k[b]||0),D=s[b];D===114?R=" ".repeat(F):D===99?F%2?(R=" ".repeat(F/2+.5),L=" ".repeat(F/2-.5)):(R=" ".repeat(F/2),L=R):L=" ".repeat(F)}r.delimiterStart!==!1&&!b&&A.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&_==="")&&(r.delimiterStart!==!1||b)&&A.push(" "),r.alignDelimiters!==!1&&A.push(R),A.push(_),r.alignDelimiters!==!1&&A.push(L),r.padding!==!1&&A.push(" "),(r.delimiterEnd!==!1||b!==f-1)&&A.push("|")}w.push(r.delimiterEnd===!1?A.join("").replace(/ +$/,""):A.join(""))}return w.join(`
`)}function wF(e){return e==null?"":String(e)}function jA(e){const t=typeof e=="string"?e.codePointAt(0):0;return t===67||t===99?99:t===76||t===108?108:t===82||t===114?114:0}function TF(e,t,r,a){const o=r.enter("blockquote"),s=r.createTracker(a);s.move("> "),s.shift(2);const u=r.indentLines(r.containerFlow(e,s.current()),AF);return o(),u}function AF(e,t,r){return">"+(r?"":" ")+e}function kF(e,t){return UA(e,t.inConstruct,!0)&&!UA(e,t.notInConstruct,!1)}function UA(e,t,r){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return r;let a=-1;for(;++a<t.length;)if(e.includes(t[a]))return!0;return!1}function FA(e,t,r,a){let o=-1;for(;++o<r.unsafe.length;)if(r.unsafe[o].character===`
`&&kF(r.stack,r.unsafe[o]))return/[ \t]/.test(a.before)?"":" ";return`\\
`}function _F(e,t){const r=String(e);let a=r.indexOf(t),o=a,s=0,u=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===o?++s>u&&(u=s):s=1,o=a+t.length,a=r.indexOf(t,o);return u}function NF(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function RF(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function CF(e,t,r,a){const o=RF(r),s=e.value||"",u=o==="`"?"GraveAccent":"Tilde";if(NF(e,r)){const b=r.enter("codeIndented"),v=r.indentLines(s,OF);return b(),v}const d=r.createTracker(a),p=o.repeat(Math.max(_F(s,o)+1,3)),f=r.enter("codeFenced");let h=d.move(p);if(e.lang){const b=r.enter(`codeFencedLang${u}`);h+=d.move(r.safe(e.lang,{before:h,after:" ",encode:["`"],...d.current()})),b()}if(e.lang&&e.meta){const b=r.enter(`codeFencedMeta${u}`);h+=d.move(" "),h+=d.move(r.safe(e.meta,{before:h,after:`
`}),r}function _A(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function NA(e,t){const r=lU(e,t),a=r.one(e,void 0),o=Zj(r),s=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return o&&s.children.push({type:"text",value:`
`},o),s}function pU(e,t){return e&&"run"in e?async function(r,a){const o=NA(r,{file:a,...t});await e.run(o,a)}:function(r,a){return NA(r,{file:a,...e||t})}}function RA(e){if(e)throw e}var Ef,CA;function fU(){if(CA)return Ef;CA=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=function(f){return typeof Array.isArray=="function"?Array.isArray(f):t.call(f)==="[object Array]"},s=function(f){if(!f||t.call(f)!=="[object Object]")return!1;var h=e.call(f,"constructor"),b=f.constructor&&f.constructor.prototype&&e.call(f.constructor.prototype,"isPrototypeOf");if(f.constructor&&!h&&!b)return!1;var v;for(v in f);return typeof v>"u"||e.call(f,v)},u=function(f,h){r&&h.name==="__proto__"?r(f,h.name,{enumerable:!0,configurable:!0,value:h.newValue,writable:!0}):f[h.name]=h.newValue},d=function(f,h){if(h==="__proto__")if(e.call(f,h)){if(a)return a(f,h).value}else return;return f[h]};return Ef=function p(){var f,h,b,v,E,w,T=arguments[0],k=1,A=arguments.length,_=!1;for(typeof T=="boolean"&&(_=T,T=arguments[1]||{},k=2),(T==null||typeof T!="object"&&typeof T!="function")&&(T={});k<A;++k)if(f=arguments[k],f!=null)for(h in f)b=d(T,h),v=d(f,h),T!==v&&(_&&v&&(s(v)||(E=o(v)))?(E?(E=!1,w=b&&o(b)?b:[]):w=b&&s(b)?b:{},u(T,{name:h,newValue:p(_,w,v)})):typeof v<"u"&&u(T,{name:h,newValue:v}));return T},Ef}var gU=fU();const vf=Qu(gU);function eg(e){if(typeof e!="object"||e===null)return!1;const t=Object.getPrototypeOf(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)}function mU(){const e=[],t={run:r,use:a};return t;function r(...o){let s=-1;const u=o.pop();if(typeof u!="function")throw new TypeError("Expected function as last argument, not "+u);d(null,...o);function d(p,...f){const h=e[++s];let b=-1;if(p){u(p);return}for(;++b<o.length;)(f[b]===null||f[b]===void 0)&&(f[b]=o[b]);o=f,h?hU(h,d)(...f):u(null,...f)}}function a(o){if(typeof o!="function")throw new TypeError("Expected `middelware` to be a function, not "+o);return e.push(o),t}}function hU(e,t){let r;return a;function a(...u){const d=e.length>u.length;let p;d&&u.push(o);try{p=e.apply(this,u)}catch(f){const h=f;if(d&&r)throw h;return o(h)}d||(p&&p.then&&typeof p.then=="function"?p.then(s,o):p instanceof Error?o(p):s(p))}function o(u,...d){r||(r=!0,t(u,...d))}function s(u){o(null,u)}}const Qn={basename:bU,dirname:yU,extname:EU,join:vU,sep:"/"};function bU(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Ol(e);let r=0,a=-1,o=e.length,s;if(t===void 0||t.length===0||t.length>e.length){for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else a<0&&(s=!0,a=o+1);return a<0?"":e.slice(r,a)}if(t===e)return"";let u=-1,d=t.length-1;for(;o--;)if(e.codePointAt(o)===47){if(s){r=o+1;break}}else u<0&&(s=!0,u=o+1),d>-1&&(e.codePointAt(o)===t.codePointAt(d--)?d<0&&(a=o):(d=-1,a=u));return r===a?a=u:a<0&&(a=e.length),e.slice(r,a)}function yU(e){if(Ol(e),e.length===0)return".";let t=-1,r=e.length,a;for(;--r;)if(e.codePointAt(r)===47){if(a){t=r;break}}else a||(a=!0);return t<0?e.codePointAt(0)===47?"/":".":t===1&&e.codePointAt(0)===47?"//":e.slice(0,t)}function EU(e){Ol(e);let t=e.length,r=-1,a=0,o=-1,s=0,u;for(;t--;){const d=e.codePointAt(t);if(d===47){if(u){a=t+1;break}continue}r<0&&(u=!0,r=t+1),d===46?o<0?o=t:s!==1&&(s=1):o>-1&&(s=-1)}return o<0||r<0||s===0||s===1&&o===r-1&&o===a+1?"":e.slice(o,r)}function vU(...e){let t=-1,r;for(;++t<e.length;)Ol(e[t]),e[t]&&(r=r===void 0?e[t]:r+"/"+e[t]);return r===void 0?".":SU(r)}function SU(e){Ol(e);const t=e.codePointAt(0)===47;let r=xU(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function xU(e,t){let r="",a=0,o=-1,s=0,u=-1,d,p;for(;++u<=e.length;){if(u<e.length)d=e.codePointAt(u);else{if(d===47)break;d=47}if(d===47){if(!(o===u-1||s===1))if(o!==u-1&&s===2){if(r.length<2||a!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(p=r.lastIndexOf("/"),p!==r.length-1){p<0?(r="",a=0):(r=r.slice(0,p),a=r.length-1-r.lastIndexOf("/")),o=u,s=0;continue}}else if(r.length>0){r="",a=0,o=u,s=0;continue}}t&&(r=r.length>0?r+"/..":"..",a=2)}else r.length>0?r+="/"+e.slice(o+1,u):r=e.slice(o+1,u),a=u-o-1;o=u,s=0}else d===46&&s>-1?s++:s=-1}return r}function Ol(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const wU={cwd:TU};function TU(){return"/"}function tg(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function AU(e){if(typeof e=="string")e=new URL(e);else if(!tg(e)){const t=new TypeError('The "path" argument must be of type string or an instance of URL. Received `'+e+"`");throw t.code="ERR_INVALID_ARG_TYPE",t}if(e.protocol!=="file:"){const t=new TypeError("The URL must be of scheme file");throw t.code="ERR_INVALID_URL_SCHEME",t}return kU(e)}function kU(e){if(e.hostname!==""){consLine truncated
`:" ")+s.indentLines(s.containerFlow(a,d.current()),t?pN:mF))),f(),p}}function mF(e,t,r){return t===0?e:pN(e,t,r)}function pN(e,t,r){return(r?"":" ")+e}const hF=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];fN.peek=SF;function bF(){return{canContainEols:["delete"],enter:{strikethrough:EF},exit:{strikethrough:vF}}}function yF(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:hF}],handlers:{delete:fN}}}function EF(e){this.enter({type:"delete",children:[]},e)}function vF(e){this.exit(e)}function fN(e,t,r,a){const o=r.createTracker(a),s=r.enter("strikethrough");let u=o.move("~~");return u+=r.containerPhrasing(e,{...o.current(),before:u,after:"~"}),u+=o.move("~~"),s(),u}function SF(){return"~"}function xF(e){return e.length}function wF(e,t){const r=t||{},a=(r.align||[]).concat(),o=r.stringLength||xF,s=[],u=[],d=[],p=[];let f=0,h=-1;for(;++h<e.length;){const T=[],k=[];let A=-1;for(e[h].length>f&&(f=e[h].length);++A<e[h].length;){const _=TF(e[h][A]);if(r.alignDelimiters!==!1){const R=o(_);k[A]=R,(p[A]===void 0||R>p[A])&&(p[A]=R)}T.push(_)}u[h]=T,d[h]=k}let b=-1;if(typeof a=="object"&&"length"in a)for(;++b<f;)s[b]=UA(a[b]);else{const T=UA(a);for(;++b<f;)s[b]=T}b=-1;const v=[],E=[];for(;++b<f;){const T=s[b];let k="",A="";T===99?(k=":",A=":"):T===108?k=":":T===114&&(A=":");let _=r.alignDelimiters===!1?1:Math.max(1,p[b]-k.length-A.length);const R=k+"-".repeat(_)+A;r.alignDelimiters!==!1&&(_=k.length+_+A.length,_>p[b]&&(p[b]=_),E[b]=_),v[b]=R}u.splice(1,0,v),d.splice(1,0,E),h=-1;const w=[];for(;++h<u.length;){const T=u[h],k=d[h];b=-1;const A=[];for(;++b<f;){const _=T[b]||"";let R="",L="";if(r.alignDelimiters!==!1){const j=p[b]-(k[b]||0),D=s[b];D===114?R=" ".repeat(j):D===99?j%2?(R=" ".repeat(j/2+.5),L=" ".repeat(j/2-.5)):(R=" ".repeat(j/2),L=R):L=" ".repeat(j)}r.delimiterStart!==!1&&!b&&A.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&_==="")&&(r.delimiterStart!==!1||b)&&A.push(" "),r.alignDelimiters!==!1&&A.push(R),A.push(_),r.alignDelimiters!==!1&&A.push(L),r.padding!==!1&&A.push(" "),(r.delimiterEnd!==!1||b!==f-1)&&A.push("|")}w.push(r.delimiterEnd===!1?A.join("").replace(/ +$/,""):A.join(""))}return w.join(`
`)}function TF(e){return e==null?"":String(e)}function UA(e){const t=typeof e=="string"?e.codePointAt(0):0;return t===67||t===99?99:t===76||t===108?108:t===82||t===114?114:0}function AF(e,t,r,a){const o=r.enter("blockquote"),s=r.createTracker(a);s.move("> "),s.shift(2);const u=r.indentLines(r.containerFlow(e,s.current()),kF);return o(),u}function kF(e,t,r){return">"+(r?"":" ")+e}function _F(e,t){return FA(e,t.inConstruct,!0)&&!FA(e,t.notInConstruct,!1)}function FA(e,t,r){if(typeof t=="string"&&(t=[t]),!t||t.length===0)return r;let a=-1;for(;++a<t.length;)if(e.includes(t[a]))return!0;return!1}function BA(e,t,r,a){let o=-1;for(;++o<r.unsafe.length;)if(r.unsafe[o].character===`
`&&_F(r.stack,r.unsafe[o]))return/[ \t]/.test(a.before)?"":" ";return`\\
`}function NF(e,t){const r=String(e);let a=r.indexOf(t),o=a,s=0,u=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===o?++s>u&&(u=s):s=1,o=a+t.length,a=r.indexOf(t,o);return u}function RF(e,t){return!!(t.options.fences===!1&&e.value&&!e.lang&&/[^ \r\n]/.test(e.value)&&!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(e.value))}function CF(e){const t=e.options.fence||"`";if(t!=="`"&&t!=="~")throw new Error("Cannot serialize code with `"+t+"` for `options.fence`, expected `` ` `` or `~`");return t}function OF(e,t,r,a){const o=CF(r),s=e.value||"",u=o==="`"?"GraveAccent":"Tilde";if(RF(e,r)){const b=r.enter("codeIndented"),v=r.indentLines(s,IF);return b(),v}const d=r.createTracker(a),p=o.repeat(Math.max(NF(s,o)+1,3)),f=r.enter("codeFenced");let h=d.move(p);if(e.lang){const b=r.enter(`codeFencedLang${u}`);h+=d.move(r.safe(e.lang,{before:h,after:" ",encode:["`"],...d.current()})),b()}if(e.lang&&e.meta){const b=r.enter(`codeFencedMeta${u}`);h+=d.move(" "),h+=d.move(r.safe(e.meta,{before:h,after:`
`,encode:["`"],...d.current()})),b()}return h+=d.move(`
`),s&&(h+=d.move(s+`
`)),h+=d.move(p),f(),h}function OF(e,t,r){return(r?"":" ")+e}function qg(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function IF(e,t,r,a){const o=qg(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("definition");let d=r.enter("label");const p=r.createTracker(a);let f=p.move("[");return f+=p.move(r.safe(r.associationId(e),{before:f,after:"]",...p.current()})),f+=p.move("]: "),d(),!e.url||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(d=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":`
`,...p.current()}))),d(),e.title&&(d=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),d()),u(),f}function LF(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function wl(e){return"&#x"+e.toString(16).toUpperCase()+";"}function Pu(e,t,r){const a=Wi(e),o=Wi(t);return a===void 0?o===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}fN.peek=DF;function fN(e,t,r,a){const o=LF(r),s=r.enter("emphasis"),u=r.createTracker(a),d=u.move(o);let p=u.move(r.containerPhrasing(e,{after:o,before:d,...u.current()}));const f=p.charCodeAt(0),h=Pu(a.before.charCodeAt(a.before.length-1),f,o);h.inside&&(p=wl(f)+p.slice(1));const b=p.charCodeAt(p.length-1),v=Pu(a.after.charCodeAt(0),b,o);v.inside&&(p=p.slice(0,-1)+wl(b));const E=u.move(o);return s(),r.attentionEncodeSurroundingInfo={after:v.outside,before:h.outside},d+p+E}function DF(e,t,r){return r.options.emphasis||"*"}function MF(e,t){let r=!1;return Hg(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,Xf}),!!((!e.depth||e.depth<3)&&Fg(e)&&(t.options.setext||r))}function jF(e,t,r,a){const o=Math.max(Math.min(6,e.depth||1),1),s=r.createTracker(a);if(MF(e,r)){const h=r.enter("headingSetext"),b=r.enter("phrasing"),v=r.containerPhrasing(e,{...s.current(),before:`
`)),h+=d.move(p),f(),h}function IF(e,t,r){return(r?"":" ")+e}function Wg(e){const t=e.options.quote||'"';if(t!=='"'&&t!=="'")throw new Error("Cannot serialize title with `"+t+"` for `options.quote`, expected `\"`, or `'`");return t}function LF(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("definition");let d=r.enter("label");const p=r.createTracker(a);let f=p.move("[");return f+=p.move(r.safe(r.associationId(e),{before:f,after:"]",...p.current()})),f+=p.move("]: "),d(),!e.url||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(d=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":`
`,...p.current()}))),d(),e.title&&(d=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),d()),u(),f}function DF(e){const t=e.options.emphasis||"*";if(t!=="*"&&t!=="_")throw new Error("Cannot serialize emphasis with `"+t+"` for `options.emphasis`, expected `*`, or `_`");return t}function wl(e){return"&#x"+e.toString(16).toUpperCase()+";"}function $u(e,t,r){const a=Wi(e),o=Wi(t);return a===void 0?o===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:o===void 0?{inside:!1,outside:!1}:o===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}gN.peek=MF;function gN(e,t,r,a){const o=DF(r),s=r.enter("emphasis"),u=r.createTracker(a),d=u.move(o);let p=u.move(r.containerPhrasing(e,{after:o,before:d,...u.current()}));const f=p.charCodeAt(0),h=$u(a.before.charCodeAt(a.before.length-1),f,o);h.inside&&(p=wl(f)+p.slice(1));const b=p.charCodeAt(p.length-1),v=$u(a.after.charCodeAt(0),b,o);v.inside&&(p=p.slice(0,-1)+wl(b));const E=u.move(o);return s(),r.attentionEncodeSurroundingInfo={after:v.outside,before:h.outside},d+p+E}function MF(e,t,r){return r.options.emphasis||"*"}function jF(e,t){let r=!1;return qg(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,Qf}),!!((!e.depth||e.depth<3)&&zg(e)&&(t.options.setext||r))}function UF(e,t,r,a){const o=Math.max(Math.min(6,e.depth||1),1),s=r.createTracker(a);if(jF(e,r)){const h=r.enter("headingSetext"),b=r.enter("phrasing"),v=r.containerPhrasing(e,{...s.current(),before:`
`,after:`
`});return b(),h(),v+`
`+(o===1?"=":"-").repeat(v.length-(Math.max(v.lastIndexOf("\r"),v.lastIndexOf(`
`))+1))}const u="#".repeat(o),d=r.enter("headingAtx"),p=r.enter("phrasing");s.move(u+" ");let f=r.containerPhrasing(e,{before:"# ",after:`
`,...s.current()});return/^[\t ]/.test(f)&&(f=wl(f.charCodeAt(0))+f.slice(1)),f=f?u+" "+f:u,r.options.closeAtx&&(f+=" "+u),p(),d(),f}gN.peek=UF;function gN(e){return e.value||""}function UF(){return"<"}mN.peek=FF;function mN(e,t,r,a){const o=qg(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("image");let d=r.enter("label");const p=r.createTracker(a);let f=p.move("![");return f+=p.move(r.safe(e.alt,{before:f,after:"]",...p.current()})),f+=p.move("]("),d(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(d=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":")",...p.current()}))),d(),e.title&&(d=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),d()),f+=p.move(")"),u(),f}function FF(){return"!"}hN.peek=BF;function hN(e,t,r,a){const o=e.referenceType,s=r.enter("imageReference");let u=r.enter("label");const d=r.createTracker(a);let p=d.move("![");const f=r.safe(e.alt,{before:p,after:"]",...d.current()});p+=d.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const b=r.safe(r.associationId(e),{before:p,after:"]",...d.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==b?p+=d.move(b+"]"):o==="shortcut"?p=p.slice(0,-1):p+=d.move("]"),p}function BF(){return"!"}bN.peek=zF;function bN(e,t,r){let a=e.value||"",o="`",s=-1;for(;new RegExp("(^|[^`])"+o+"([^`]|$)").test(a);)o+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++s<r.unsafe.length;){const u=r.unsafe[s],d=r.compilePattern(u);let p;if(u.atBreak)for(;p=d.exec(a);){let f=p.index;a.charCodeAt(f)===10&&a.charCodeAt(f-1)===13&&f--,a=a.slice(0,f)+" "+a.slice(p.index+1)}}return o+a+o}function zF(){return"`"}function yN(e,t){const r=Fg(e);return!!(!t.options.resourceLink&&e.url&&!e.title&&e.children&&e.children.length===1&&e.children[0].type==="text"&&(r===e.url||"mailto:"+r===e.url)&&/^[a-z][a-z+.-]+:/i.test(e.url)&&!/[\0- <>\u007F]/.test(e.url))}EN.peek=PF;function EN(e,t,r,a){const o=qg(r),s=o==='"'?"Quote":"Apostrophe",u=r.createTracker(a);let d,p;if(yN(e,r)){const h=r.stack;r.stack=[],d=r.enter("autolink");let b=u.move("<");return b+=u.move(r.containerPhrasing(e,{before:b,after:">",...u.current()})),b+=u.move(">"),d(),r.stack=h,b}d=r.enter("link"),p=r.enter("label");let f=u.move("[");return f+=u.move(r.containerPhrasing(e,{before:f,after:"](",...u.current()})),f+=u.move("]("),p(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(p=r.enter("destinationLiteral"),f+=u.move("<"),f+=u.move(r.safe(e.url,{before:f,after:">",...u.current()})),f+=u.move(">")):(p=r.enter("destinationRaw"),f+=u.move(r.safe(e.url,{before:f,after:e.title?" ":")",...u.current()}))),p(),e.title&&(p=r.enter(`title${s}`),f+=u.move(" "+o),f+=u.move(r.safe(e.title,{before:f,after:o,...u.current()})),f+=u.move(o),p()),f+=u.move(")"),d(),f}function PF(e,t,r){return yN(e,r)?"<":"["}vN.peek=$F;function vN(e,t,r,a){const o=e.referenceType,s=r.enter("linkReference");let u=r.enter("label");const d=r.createTracker(a);let p=d.move("[");const f=r.containerPhrasing(e,{before:p,after:"]",...d.current()});p+=d.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const b=r.safe(r.associationId(e),{before:p,after:"]",...d.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==b?p+=d.move(b+"]"):o==="shortcut"?p=p.slice(0,-1):p+=d.move("]"),p}function $F(){return"["}function Yg(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function GF(e){const t=Yg(e),r=e.options.bulletOther;if(!r)return t==="*"?"-":"*";if(r!=="*"&&r!=="+"&&r!=="-")throw new Error("Cannot serialize items with `"+r+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(r===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+r+"`) to be different");return r}function HF(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function SN(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function VF(e,t,r,a){const o=r.enter("list"),s=r.bulletCurrent;let u=e.ordered?HF(r):Yg(r);const d=e.ordered?u==="."?")":".":GF(r);let p=t&&r.bulletLastUsed?u===r.bulletLastUsed:!1;if(!e.ordered){const h=e.children?e.children[0]:void 0;if((u==="*"||u==="-")&&h&&(!h.children||!h.children[0])&&r.stack[r.stack.length-1]==="list"&&r.stack[r.stack.length-2]==="listItem"&&r.stack[r.stack.length-3]==="list"&&r.stack[r.stack.length-4]==="listItem"&&r.indexStack[r.indexStack.length-1]===0&&r.indexStack[r.indexStack.length-2]===0&&r.indexStack[r.indexStack.length-3]===0&&(p=!0),SN(r)===u&&h){let b=-1;for(;++b<e.chilLine truncated
`,...s.current()});return/^[\t ]/.test(f)&&(f=wl(f.charCodeAt(0))+f.slice(1)),f=f?u+" "+f:u,r.options.closeAtx&&(f+=" "+u),p(),d(),f}mN.peek=FF;function mN(e){return e.value||""}function FF(){return"<"}hN.peek=BF;function hN(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.enter("image");let d=r.enter("label");const p=r.createTracker(a);let f=p.move("![");return f+=p.move(r.safe(e.alt,{before:f,after:"]",...p.current()})),f+=p.move("]("),d(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=p.move("<"),f+=p.move(r.safe(e.url,{before:f,after:">",...p.current()})),f+=p.move(">")):(d=r.enter("destinationRaw"),f+=p.move(r.safe(e.url,{before:f,after:e.title?" ":")",...p.current()}))),d(),e.title&&(d=r.enter(`title${s}`),f+=p.move(" "+o),f+=p.move(r.safe(e.title,{before:f,after:o,...p.current()})),f+=p.move(o),d()),f+=p.move(")"),u(),f}function BF(){return"!"}bN.peek=zF;function bN(e,t,r,a){const o=e.referenceType,s=r.enter("imageReference");let u=r.enter("label");const d=r.createTracker(a);let p=d.move("![");const f=r.safe(e.alt,{before:p,after:"]",...d.current()});p+=d.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const b=r.safe(r.associationId(e),{before:p,after:"]",...d.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==b?p+=d.move(b+"]"):o==="shortcut"?p=p.slice(0,-1):p+=d.move("]"),p}function zF(){return"!"}yN.peek=PF;function yN(e,t,r){let a=e.value||"",o="`",s=-1;for(;new RegExp("(^|[^`])"+o+"([^`]|$)").test(a);)o+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++s<r.unsafe.length;){const u=r.unsafe[s],d=r.compilePattern(u);let p;if(u.atBreak)for(;p=d.exec(a);){let f=p.index;a.charCodeAt(f)===10&&a.charCodeAt(f-1)===13&&f--,a=a.slice(0,f)+" "+a.slice(p.index+1)}}return o+a+o}function PF(){return"`"}function EN(e,t){const r=zg(e);return!!(!t.options.resourceLink&&e.url&&!e.title&&e.children&&e.children.length===1&&e.children[0].type==="text"&&(r===e.url||"mailto:"+r===e.url)&&/^[a-z][a-z+.-]+:/i.test(e.url)&&!/[\0- <>\u007F]/.test(e.url))}vN.peek=$F;function vN(e,t,r,a){const o=Wg(r),s=o==='"'?"Quote":"Apostrophe",u=r.createTracker(a);let d,p;if(EN(e,r)){const h=r.stack;r.stack=[],d=r.enter("autolink");let b=u.move("<");return b+=u.move(r.containerPhrasing(e,{before:b,after:">",...u.current()})),b+=u.move(">"),d(),r.stack=h,b}d=r.enter("link"),p=r.enter("label");let f=u.move("[");return f+=u.move(r.containerPhrasing(e,{before:f,after:"](",...u.current()})),f+=u.move("]("),p(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(p=r.enter("destinationLiteral"),f+=u.move("<"),f+=u.move(r.safe(e.url,{before:f,after:">",...u.current()})),f+=u.move(">")):(p=r.enter("destinationRaw"),f+=u.move(r.safe(e.url,{before:f,after:e.title?" ":")",...u.current()}))),p(),e.title&&(p=r.enter(`title${s}`),f+=u.move(" "+o),f+=u.move(r.safe(e.title,{before:f,after:o,...u.current()})),f+=u.move(o),p()),f+=u.move(")"),d(),f}function $F(e,t,r){return EN(e,r)?"<":"["}SN.peek=GF;function SN(e,t,r,a){const o=e.referenceType,s=r.enter("linkReference");let u=r.enter("label");const d=r.createTracker(a);let p=d.move("[");const f=r.containerPhrasing(e,{before:p,after:"]",...d.current()});p+=d.move(f+"]["),u();const h=r.stack;r.stack=[],u=r.enter("reference");const b=r.safe(r.associationId(e),{before:p,after:"]",...d.current()});return u(),r.stack=h,s(),o==="full"||!f||f!==b?p+=d.move(b+"]"):o==="shortcut"?p=p.slice(0,-1):p+=d.move("]"),p}function GF(){return"["}function Kg(e){const t=e.options.bullet||"*";if(t!=="*"&&t!=="+"&&t!=="-")throw new Error("Cannot serialize items with `"+t+"` for `options.bullet`, expected `*`, `+`, or `-`");return t}function HF(e){const t=Kg(e),r=e.options.bulletOther;if(!r)return t==="*"?"-":"*";if(r!=="*"&&r!=="+"&&r!=="-")throw new Error("Cannot serialize items with `"+r+"` for `options.bulletOther`, expected `*`, `+`, or `-`");if(r===t)throw new Error("Expected `bullet` (`"+t+"`) and `bulletOther` (`"+r+"`) to be different");return r}function VF(e){const t=e.options.bulletOrdered||".";if(t!=="."&&t!==")")throw new Error("Cannot serialize items with `"+t+"` for `options.bulletOrdered`, expected `.` or `)`");return t}function xN(e){const t=e.options.rule||"*";if(t!=="*"&&t!=="-"&&t!=="_")throw new Error("Cannot serialize rules with `"+t+"` for `options.rule`, expected `*`, `-`, or `_`");return t}function qF(e,t,r,a){const o=r.enter("list"),s=r.bulletCurrent;let u=e.ordered?VF(r):Kg(r);const d=e.ordered?u==="."?")":".":HF(r);let p=t&&r.bulletLastUsed?u===r.bulletLastUsed:!1;if(!e.ordered){const h=e.children?e.children[0]:void 0;if((u==="*"||u==="-")&&h&&(!h.children||!h.children[0])&&r.stack[r.stack.length-1]==="list"&&r.stack[r.stack.length-2]==="listItem"&&r.stack[r.stack.length-3]==="list"&&r.stack[r.stack.length-4]==="listItem"&&r.indexStack[r.indexStack.length-1]===0&&r.indexStack[r.indexStack.length-2]===0&&r.indexStack[r.indexStack.length-3]===0&&(p=!0),xN(r)===u&&h){let b=-1;for(;++b<e.chilLine truncated
`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:v,table:u,tableCell:p,tableRow:d}};function u(E,w,T,k){return f(h(E,T,k),E.align)}function d(E,w,T,k){const A=b(E,T,k),_=f([A]);return _.slice(0,_.indexOf(`
`))}function p(E,w,T,k){const A=T.enter("tableCell"),_=T.enter("phrasing"),R=T.containerPhrasing(E,{...k,before:s,after:s});return _(),A(),R}function f(E,w){return xF(E,{align:w,alignDelimiters:a,padding:r,stringLength:o})}function h(E,w,T){const k=E.children;let A=-1;const _=[],R=w.enter("table");for(;++A<k.length;)_[A]=b(k[A],w,T);return R(),_}function b(E,w,T){const k=E.children;let A=-1;const _=[],R=w.enter("tableRow");for(;++A<k.length;)_[A]=p(k[A],E,w,T);return R(),_}function v(E,w,T){let k=wN.inlineCode(E,w,T);return T.stack.includes("tableCell")&&(k=k.replace(/\|/g,"\\$&")),k}}function uB(){return{exit:{taskListCheckValueChecked:zA,taskListCheckValueUnchecked:zA,paragraph:dB}}}function cB(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:pB}}}function zA(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function dB(e){const t=this.stack[this.stack.length-2];if(t&&t.type==="listItem"&&typeof t.checked=="boolean"){const r=this.stack[this.stack.length-1];r.type;const a=r.children[0];if(a&&a.type==="text"){const o=t.children;let s=-1,u;for(;++s<o.length;){const d=o[s];if(d.type==="paragraph"){u=d;break}}u===r&&(a.value=a.value.slice(1),a.value.length===0?r.children.shift():r.position&&a.position&&typeof a.position.start.offset=="number"&&(a.position.start.column++,a.position.start.offset++,r.position.start=Object.assign({},a.position.start)))}}this.exit(e)}function pB(e,t,r,a){const o=e.children[0],s=typeof e.checked=="boolean"&&o&&o.type==="paragraph",u="["+(e.checked?"x":" ")+"] ",d=r.createTracker(a);s&&d.move(u);let p=wN.listItem(e,t,r,{...a,...d.current()});return s&&(p=p.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,f)),p;function f(h){return h+u}}function fB(){return[VU(),pF(),hF(),nB(),uB()]}function gB(e){return{extensions:[qU(),fF(e),bF(),sB(e),cB()]}}const mB={tokenize:SB,partial:!0},TN={tokenize:xB,partial:!0},AN={tokenize:wB,partial:!0},kN={tokenize:TB,partial:!0},hB={tokenize:AB,partial:!0},_N={name:"wwwAutolink",tokenize:EB,previous:RN},NN={name:"protocolAutolink",tokenize:vB,previous:CN},Nr={name:"emailAutolink",tokenize:yB,previous:ON},rr={};function bB(){return{text:rr}}let Da=48;for(;Da<123;)rr[Da]=Nr,Da++,Da===58?Da=65:Da===91&&(Da=97);rr[43]=Nr;rr[45]=Nr;rr[46]=Nr;rr[95]=Nr;rr[72]=[Nr,NN];rr[104]=[Nr,NN];rr[87]=[Nr,_N];rr[119]=[Nr,_N];function yB(e,t,r){const a=this;let o,s;return u;function u(b){return!eg(b)||!ON.call(a,a.previous)||Wg(a.events)?r(b):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(b))}function d(b){return eg(b)?(e.consume(b),d):b===64?(e.consume(b),p):r(b)}function p(b){return b===46?e.check(hB,h,f)(b):b===45||b===95||Dt(b)?(s=!0,e.consume(b),p):h(b)}function f(b){return e.consume(b),o=!0,p}function h(b){return s&&o&&Ht(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(b)):r(b)}}function EB(e,t,r){const a=this;return o;function o(u){return u!==87&&u!==119||!RN.call(a,a.previous)||Wg(a.events)?r(u):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(mB,e.attempt(TN,e.attempt(AN,s),r),r)(u))}function s(u){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(u)}}function vB(e,t,r){const a=this;let o="",s=!1;return u;function u(b){return(b===72||b===104)&&CN.call(a,a.previous)&&!Wg(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),o+=String.fromCodePoint(b),e.consume(b),d):r(b)}function d(b){if(Ht(b)&&o.length<5)return o+=String.fromCodePoint(b),e.consume(b),d;if(b===58){const v=o.toLowerCase();if(v==="http"||v==="https")return e.consume(b),p}return r(b)}function p(b){return b===47?(e.consume(b),s?f:(s=!0,p)):r(b)}function f(b){return b===null||Fu(b)||rt(b)||Ua(b)||nc(b)?r(b):e.attempt(TN,e.attempt(AN,h),r)(b)}function h(b){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(b)}}function SB(e,t,r){let a=0;return o;function o(u){return(u===87||u===119)&&a<3?(a++,e.consume(u),o):u===46&&a===3?(e.consume(u),s):r(u)}function s(u){return u===null?r(u):t(u)}}function xB(e,t,r){let a,o,s;return u;function u(f){return f===46||f===95?e.check(kN,p,d)(f):f===null||rt(f)||Ua(f)||f!==45&&nc(f)?p(f):(s=!0,e.consume(f),u)}function d(f){return f===95?a=!0:(o=a,a=void 0),e.consume(f),u}function p(f){return o||a||!s?r(f):t(f)}}function wB(e,t){let r=0,a=0;return o;function o(u){return u===40?(r++,e.consume(u),o):u===41&&a<r?s(u):u===33||u===34||u===38||u===39||u===41||u===42||u===44||u===46||u===58||u===59||u===60||u===63||u===93||u===95||u===126?e.check(kN,t,s)(u):u===null||rt(u)||Ua(u)?t(u):(e.consume(u),o)}function s(u){return u===41&&a++,e.consume(u),o}}function TB(e,t,r){return a;function a(d){return d===33||d===34||d===39||d===41||d===42||d===44||d===46||d===58||d===59||d===63||d===95||d===126?(e.consume(d),a):d===38?(e.consume(d),s):d===93?(e.consume(d),o):d===60||d===null||rt(d)||Ua(d)?t(d):r(d)}function o(d){return d===null||d===40||d===91||rt(d)||Ua(d)?t(d):a(d)}function s(d){return Ht(d)?u(d):r(d)}function u(d){re Line truncated
`)===-1?g.jsx("code",{className:"bg-primary/10 text-primary px-1.5 py-0.5 rounded text-sm font-mono",children:h}):g.jsx("code",{className:"block font-mono",children:h}),pre:({children:f})=>g.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto my-4 font-mono whitespace-pre",children:f}),table:({children:f})=>g.jsx("div",{className:"overflow-auto my-4",children:g.jsx("table",{className:"w-full border-collapse text-sm",children:f})}),thead:({children:f})=>g.jsx("thead",{className:"bg-muted/50",children:f}),tr:({children:f})=>g.jsx("tr",{className:"border-b",children:f}),th:({children:f})=>g.jsx("th",{className:"px-4 py-2 text-left font-medium",children:f}),td:({children:f})=>g.jsx("td",{className:"px-4 py-2 text-muted-foreground",children:f}),hr:()=>g.jsx("hr",{className:"my-6 border-border"}),strong:({children:f})=>g.jsx("strong",{className:"font-semibold text-foreground",children:f}),em:({children:f})=>g.jsx("em",{children:f})};return r?g.jsx("main",{className:"flex-1 p-8 max-w-3xl",children:s?g.jsx("div",{className:"text-muted-foreground",children:"Loading content..."}):a?g.jsx("article",{className:"prose max-w-none",children:g.jsx(sN,{remarkPlugins:[IN],components:p,children:a})}):g.jsx("div",{className:"bg-muted/50 border rounded-lg p-6",children:g.jsxs("p",{className:"text-muted-foreground",children:["Content for ",g.jsx("strong",{className:"text-primary",children:r.title})," would be displayed here."]})})}):g.jsxs("main",{className:"flex-1 p-8 max-w-3xl",children:[g.jsx("h1",{className:"text-4xl font-bold tracking-tight mb-4",children:e.site?.title||"Documentation"}),g.jsx("p",{className:"text-muted-foreground text-lg mb-8",children:e.site?.description||"Welcome to the documentation."}),g.jsx(Pi,{className:"my-8"}),g.jsx("h2",{className:"text-2xl font-semibold mb-4 text-primary",children:"Theme Configuration"}),g.jsxs("div",{className:"bg-muted/50 border rounded-lg p-4 mb-6",children:[g.jsx("p",{className:"text-sm text-muted-foreground mb-2",children:g.jsx("strong",{children:"Current theme from YAML:"})}),g.jsx("pre",{className:"text-primary text-sm",children:`site:
`))}function p(E,w,T,k){const A=T.enter("tableCell"),_=T.enter("phrasing"),R=T.containerPhrasing(E,{...k,before:s,after:s});return _(),A(),R}function f(E,w){return wF(E,{align:w,alignDelimiters:a,padding:r,stringLength:o})}function h(E,w,T){const k=E.children;let A=-1;const _=[],R=w.enter("table");for(;++A<k.length;)_[A]=b(k[A],w,T);return R(),_}function b(E,w,T){const k=E.children;let A=-1;const _=[],R=w.enter("tableRow");for(;++A<k.length;)_[A]=p(k[A],E,w,T);return R(),_}function v(E,w,T){let k=TN.inlineCode(E,w,T);return T.stack.includes("tableCell")&&(k=k.replace(/\|/g,"\\$&")),k}}function cB(){return{exit:{taskListCheckValueChecked:PA,taskListCheckValueUnchecked:PA,paragraph:pB}}}function dB(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:fB}}}function PA(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function pB(e){const t=this.stack[this.stack.length-2];if(t&&t.type==="listItem"&&typeof t.checked=="boolean"){const r=this.stack[this.stack.length-1];r.type;const a=r.children[0];if(a&&a.type==="text"){const o=t.children;let s=-1,u;for(;++s<o.length;){const d=o[s];if(d.type==="paragraph"){u=d;break}}u===r&&(a.value=a.value.slice(1),a.value.length===0?r.children.shift():r.position&&a.position&&typeof a.position.start.offset=="number"&&(a.position.start.column++,a.position.start.offset++,r.position.start=Object.assign({},a.position.start)))}}this.exit(e)}function fB(e,t,r,a){const o=e.children[0],s=typeof e.checked=="boolean"&&o&&o.type==="paragraph",u="["+(e.checked?"x":" ")+"] ",d=r.createTracker(a);s&&d.move(u);let p=TN.listItem(e,t,r,{...a,...d.current()});return s&&(p=p.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,f)),p;function f(h){return h+u}}function gB(){return[qU(),fF(),bF(),rB(),cB()]}function mB(e){return{extensions:[YU(),gF(e),yF(),uB(e),dB()]}}const hB={tokenize:xB,partial:!0},AN={tokenize:wB,partial:!0},kN={tokenize:TB,partial:!0},_N={tokenize:AB,partial:!0},bB={tokenize:kB,partial:!0},NN={name:"wwwAutolink",tokenize:vB,previous:CN},RN={name:"protocolAutolink",tokenize:SB,previous:ON},Nr={name:"emailAutolink",tokenize:EB,previous:IN},rr={};function yB(){return{text:rr}}let Da=48;for(;Da<123;)rr[Da]=Nr,Da++,Da===58?Da=65:Da===91&&(Da=97);rr[43]=Nr;rr[45]=Nr;rr[46]=Nr;rr[95]=Nr;rr[72]=[Nr,RN];rr[104]=[Nr,RN];rr[87]=[Nr,NN];rr[119]=[Nr,NN];function EB(e,t,r){const a=this;let o,s;return u;function u(b){return!ng(b)||!IN.call(a,a.previous)||Xg(a.events)?r(b):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(b))}function d(b){return ng(b)?(e.consume(b),d):b===64?(e.consume(b),p):r(b)}function p(b){return b===46?e.check(bB,h,f)(b):b===45||b===95||Dt(b)?(s=!0,e.consume(b),p):h(b)}function f(b){return e.consume(b),o=!0,p}function h(b){return s&&o&&Ht(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(b)):r(b)}}function vB(e,t,r){const a=this;return o;function o(u){return u!==87&&u!==119||!CN.call(a,a.previous)||Xg(a.events)?r(u):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(hB,e.attempt(AN,e.attempt(kN,s),r),r)(u))}function s(u){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(u)}}function SB(e,t,r){const a=this;let o="",s=!1;return u;function u(b){return(b===72||b===104)&&ON.call(a,a.previous)&&!Xg(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),o+=String.fromCodePoint(b),e.consume(b),d):r(b)}function d(b){if(Ht(b)&&o.length<5)return o+=String.fromCodePoint(b),e.consume(b),d;if(b===58){const v=o.toLowerCase();if(v==="http"||v==="https")return e.consume(b),p}return r(b)}function p(b){return b===47?(e.consume(b),s?f:(s=!0,p)):r(b)}function f(b){return b===null||Bu(b)||rt(b)||Fa(b)||rc(b)?r(b):e.attempt(AN,e.attempt(kN,h),r)(b)}function h(b){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(b)}}function xB(e,t,r){let a=0;return o;function o(u){return(u===87||u===119)&&a<3?(a++,e.consume(u),o):u===46&&a===3?(e.consume(u),s):r(u)}function s(u){return u===null?r(u):t(u)}}function wB(e,t,r){let a,o,s;return u;function u(f){return f===46||f===95?e.check(_N,p,d)(f):f===null||rt(f)||Fa(f)||f!==45&&rc(f)?p(f):(s=!0,e.consume(f),u)}function d(f){return f===95?a=!0:(o=a,a=void 0),e.consume(f),u}function p(f){return o||a||!s?r(f):t(f)}}function TB(e,t){let r=0,a=0;return o;function o(u){return u===40?(r++,e.consume(u),o):u===41&&a<r?s(u):u===33||u===34||u===38||u===39||u===41||u===42||u===44||u===46||u===58||u===59||u===60||u===63||u===93||u===95||u===126?e.check(_N,t,s)(u):u===null||rt(u)||Fa(u)?t(u):(e.consume(u),o)}function s(u){return u===41&&a++,e.consume(u),o}}function AB(e,t,r){return a;function a(d){return d===33||d===34||d===39||d===41||d===42||d===44||d===46||d===58||d===59||d===63||d===95||d===126?(e.consume(d),a):d===38?(e.consume(d),s):d===93?(e.consume(d),o):d===60||d===null||rt(d)||Fa(d)?t(d):r(d)}function o(d){return d===null||d===40||d===91||rt(d)||Fa(d)?t(d):a(d)}function s(d){return Ht(d)?u(d):r(d)}function u(d){re Line truncated
`)===-1?g.jsx("code",{className:"bg-primary/10 text-primary px-1.5 py-0.5 rounded text-sm font-mono",children:h}):g.jsx("code",{className:"block font-mono",children:h}),pre:({children:f})=>g.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto my-4 font-mono whitespace-pre",children:f}),table:({children:f})=>g.jsx("div",{className:"overflow-auto my-4",children:g.jsx("table",{className:"w-full border-collapse text-sm",children:f})}),thead:({children:f})=>g.jsx("thead",{className:"bg-muted/50",children:f}),tr:({children:f})=>g.jsx("tr",{className:"border-b",children:f}),th:({children:f})=>g.jsx("th",{className:"px-4 py-2 text-left font-medium",children:f}),td:({children:f})=>g.jsx("td",{className:"px-4 py-2 text-muted-foreground",children:f}),hr:()=>g.jsx("hr",{className:"my-6 border-border"}),strong:({children:f})=>g.jsx("strong",{className:"font-semibold text-foreground",children:f}),em:({children:f})=>g.jsx("em",{children:f})};return r?g.jsx("main",{id:"main-content",className:"flex-1 p-8 max-w-3xl",children:s?g.jsx("div",{className:"text-muted-foreground",children:"Loading content..."}):a?g.jsx("article",{className:"prose max-w-none",children:g.jsx(uN,{remarkPlugins:[LN],components:p,children:a})}):g.jsx("div",{className:"bg-muted/50 border rounded-lg p-6",children:g.jsxs("p",{className:"text-muted-foreground",children:["Content for ",g.jsx("strong",{className:"text-primary",children:r.title})," would be displayed here."]})})}):g.jsxs("main",{className:"flex-1 p-8 max-w-3xl",children:[g.jsx("h1",{className:"text-4xl font-bold tracking-tight mb-4",children:e.site?.title||"Documentation"}),g.jsx("p",{className:"text-muted-foreground text-lg mb-8",children:e.site?.description||"Welcome to the documentation."}),g.jsx(Pi,{className:"my-8"}),g.jsx("h2",{className:"text-2xl font-semibold mb-4 text-primary",children:"Theme Configuration"}),g.jsxs("div",{className:"bg-muted/50 border rounded-lg p-4 mb-6",children:[g.jsx("p",{className:"text-sm text-muted-foreground mb-2",children:g.jsx("strong",{children:"Current theme from YAML:"})}),g.jsx("pre",{className:"text-primary text-sm",children:`site:
theme:
preset: "${d?.preset||"zinc"}"
radius: "${d?.radius||"md"}"
darkMode: ${d?.darkMode!==!1}`})]}),g.jsx("h3",{className:"text-lg font-medium mb-3",children:"Available Presets"}),g.jsx("div",{className:"flex flex-wrap gap-2 mb-6",children:Object.keys(q1).map(f=>g.jsx("span",{className:`px-3 py-1 rounded-full text-sm ${f===(d?.preset||"zinc")?"bg-primary text-primary-foreground":"bg-muted text-muted-foreground"}`,children:f},f))}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("strong",{children:"Click any item in the sidebar"})," to navigate."]}),g.jsx("h2",{className:"text-2xl font-semibold mt-8 mb-4",children:"Routes"}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("span",{className:"font-medium text-primary",children:t.routes?.length||0})," routes configured."]}),g.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto",children:JSON.stringify(t.routes?.slice(0,3),null,2)})]})}const $A=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,GA=s_,Kg=(e,t)=>r=>{var a;if(t?.variants==null)return GA(e,r?.class,r?.className);const{variants:o,defaultVariants:s}=t,u=Object.keys(o).map(f=>{const h=r?.[f],b=s?.[f];if(h===null)return null;const v=$A(h)||$A(b);return o[f][v]}),d=r&&Object.entries(r).reduce((f,h)=>{let[b,v]=h;return v===void 0||(f[b]=v),f},{}),p=t==null||(a=t.compoundVariants)===null||a===void 0?void 0:a.reduce((f,h)=>{let{class:b,className:v,...E}=h;return Object.entries(E).every(w=>{let[T,k]=w;return Array.isArray(k)?k.includes({...s,...d}[T]):{...s,...d}[T]===k})?[...f,b,v]:f},[]);return GA(e,u,p,r?.class,r?.className)},LN=Kg("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",xs:"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9","icon-xs":"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3","icon-sm":"size-8","icon-lg":"size-10"}},defaultVariants:{variant:"default",size:"default"}});function $i({className:e,variant:t="default",size:r="default",asChild:a=!1,...o}){const s=a?Ig:"button";return g.jsx(s,{"data-slot":"button","data-variant":t,"data-size":r,className:be(LN({variant:t,size:r,className:e})),...o})}function _n({className:e,...t}){return g.jsx("div",{"data-slot":"card",className:be("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",e),...t})}function Bn({className:e,...t}){return g.jsx("div",{"data-slot":"card-header",className:be("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",e),...t})}function zn({className:e,...t}){return g.jsx("div",{"data-slot":"card-title",className:be("leading-none font-semibold",e),...t})}function Xg({className:e,...t}){return g.jsx("div",{"data-slot":"card-description",className:be("text-muted-foreground text-sm",e),...t})}function Nn({className:e,...t}){return g.jsx("div",{"data-slot":"card-content",className:be("px-6",e),...t})}function WB({className:e,...t}){return g.jsx("div",{"data-slot":"card-footer",className:be("flex items-center px-6 [.border-t]:pt-6",e),...t})}function KB({props:e}){const t=e?.title||"Statistics",r=e?.value||"0",a=e?.change||"",o=e?.changeType||"positive";return g.jsxs(_n,{className:"mb-4",children:[g.jsxs(Bn,{className:"pb-2",children:[g.jsx(Xg,{children:t}),g.jsx(zn,{className:"text-2xl",children:r})]}),a&&g.jsx(Nn,{className:"pt-0",children:g.jsx("span",{className:o==="positive"?"text-green-500 text-sm":"text-red-500 text-sm",children:a})})]})}function XB({props:e}){const t=e?.title||"Quick Links",r=e?.links||[];return g.jsxs(_n,{className:"mb-4",children:[g.jsx(Bn,{className:"pb-2",children:g.jsx(zn,{className:"text-sm font-medium",children:t})}),g.jsx(Nn,{className:"space-y-1",children:r.map((a,o)=>g.jsx("a",{href:a.href,className:"block text-sm text-muted-foreground hover:text-foreground transition-colors",children:a.label},o))})]})}function ZB({ Line truncated
`);p=b.pop()||"";for(const v of b)if(v.startsWith("data: "))try{const E=JSON.parse(v.slice(6));yz(e,E,o)}catch{}}}catch(s){if(s.name!=="AbortError"){const u=ja.get(e);u&&An(e,{thinkingSteps:[...u.state.thinkingSteps,`Error: ${s.message}`]})}}finally{const s=ja.get(e);s&&(s.abortController=null),An(e,{isStreaming:!1}),o?.onEnd?.()}}function yz(e,t,r){const a=ja.get(e);if(!a)return;const o=a.toolCallMap;switch(t.type){case"session":t.session_id&&typeof t.session_id=="string"&&r?.onSessionId?.(t.session_id);break;case"token":case"run_content":case"team_run_content":{const u=t.token??t.content??"";u&&An(e,{currentResponse:a.state.currentResponse+u});break}case"message":case"run_completed":case"team_run_completed":{const u=t.content;u&&An(e,{currentResponse:u});break}case"thinking":case"reasoning_step":case"team_reasoning_step":{const u=t.step;u&&An(e,{thinkingSteps:[...a.state.thinkingSteps,u]});break}case"reasoning_started":case"team_reasoning_started":An(e,{thinkingSteps:[...a.state.thinkingSteps,"Starting reasoning..."]});break;case"tool_call":case"tool_call_started":case"team_tool_call_started":{const u={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"running",tool_call_id:t.tool_call_id,args:t.args};u.tool_call_id&&o.set(u.tool_call_id,u);const d=VA(a.state.toolCalls,u);An(e,{toolCalls:d});break}case"tool_call_completed":case"team_tool_call_completed":{const u=t.extra_data,d=t.a2ui??u?.a2ui,p=t.elements??u?.elements,f={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"done",tool_call_id:t.tool_call_id,args:t.args,formatted_result:t.formatted_result,result:t.result,surface_id:t.surface_id??u?.surface_id,a2ui:d?.length?{messages:d,surface_id:t.surface_id??u?.surface_id}:void 0,elements:p};f.tool_call_id&&o.set(f.tool_call_id,f);const h=VA(a.state.toolCalls,f),b=p?.length?[...a.state.pendingElements,...p]:a.state.pendingElements;An(e,{toolCalls:h,pendingElements:b});break}case"message_element":{const u=t.extra_data,d=t.element??u?.element;d&&An(e,{pendingElements:[...a.state.pendingElements,d]});break}case"error":case"run_error":case"team_run_error":{const u=t.error;u&&An(e,{thinkingSteps:[...a.state.thinkingSteps,`Error: ${u}`]});break}case"end":case"done":case"run_cancelled":case"team_run_cancelled":break;default:fz(e,t);break}}function VA(e,t){if(t.tool_call_id){const r=e.findIndex(a=>a.tool_call_id===t.tool_call_id);if(r>=0){const a=[...e];return a[r]={...a[r],...t},a}}return[...e,t]}function jN(e={}){const{externalSessionId:t}=e,[r,a]=S.useState(()=>yu(t));S.useEffect(()=>{if(!t){a(yu(null));return}return a(yu(t)),gz(t,()=>{a(yu(t))})},[t]);const o=S.useCallback((u,d)=>{const p=t??`new-${Date.now()}`;hz(p,u,d,{onSessionId:f=>{e.onSessionId?.(f)},onEnd:e.onEnd})},[t,e.onSessionId,e.onEnd]),s=S.useCallback(()=>{t&&mz(t)},[t]);return{isStreaming:r.isStreaming,currentResponse:r.currentResponse,toolCalls:r.toolCalls,thinkingSteps:r.thinkingSteps,pendingElements:r.pendingElements,sendMessage:o,cancel:s}}function Ez(e,t){if(e==null)return{};var r={};for(var a in e)if({}.hasOwnProperty.call(e,a)){if(t.indexOf(a)!==-1)continue;r[a]=e[a]}return r}function vz(e,t){if(e==null)return{};var r,a,o=Ez(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a<s.length;a++)r=s[a],t.indexOf(r)===-1&&{}.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function rg(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,a=Array(t);r<t;r++)a[r]=e[r];return a}function Sz(e){if(Array.isArray(e))return rg(e)}function xz(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function wz(e,t){if(e){if(typeof e=="string")return rg(e,t);var r={}.toString.call(e).slice(8,-1);return r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set"?Array.from(e):r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?rg(e,t):void 0}}function Tz(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function ag(e){return Sz(e)||xz(e)||wz(e)||Tz()}function Tl(e){"@babel/helpers - typeof";return Tl=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Tl(e)}function Az(e,t){if(Tl(e)!="object"||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var a=r.call(e,t);if(Tl(a)!="object")return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function kz(e){var t=Az(e,"string");return Tl(t)=="symbol"?t:t+""}function UN(e,t,r){return(t=kz(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function ig(){return ig=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)({}).hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e},ig.apply(null,arguments)}function qA(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),r.push.apply(r,a)}return r}function Gi(e){for(var t=1;t<arguments.length;t++){var r=arguments[t]!=null?arguments[t]:{};t%2?qA(Object(r),!0).forEach(function(a){UN(e,a,r[a])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):qA(Object(r)).forEach(function(a){Object.defineProperty(e,a,Object.getOwnPropertyDescriptor(r,a))})}return e}function _z(e){var t=e.length;if(t===0||t===1)return e;if(t===2)return[e[0],e[1],"".concat(e[0],".").concat(e[1]),"".concat(e[1],".").concat(e[0])];if(t===3)return[e[0],e[1],e[2],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0])];if(t>=4)return[e[0],e[1],e[2],e[3],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[2],".").concat(e[3]),"".concat(e[3],".").concat(e[0]),"".concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[0]),"".concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[3],".").concat(e[ Line truncated
`))})}function jz(e){var t=e.codeString,r=e.codeStyle,a=e.containerStyle,o=a===void 0?{float:"left",paddingRight:"10px"}:a,s=e.numberStyle,u=s===void 0?{}:s,d=e.startingLineNumber;return Ke.createElement("code",{style:Object.assign({},r,o)},Mz({lines:t.replace(/\n$/,"").split(`
`),style:u,startingLineNumber:d}))}function Uz(e){return"".concat(e.toString().length,".25em")}function BN(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function zN(e,t,r){var a={display:"inline-block",minWidth:Uz(r),paddingRight:"1em",textAlign:"right",userSelect:"none"},o=typeof e=="function"?e(t):e,s=oa(oa({},a),o);return s}function Nu(e){var t=e.children,r=e.lineNumber,a=e.lineNumberStyle,o=e.largestLineNumber,s=e.showInlineLineNumbers,u=e.lineProps,d=u===void 0?{}:u,p=e.className,f=p===void 0?[]:p,h=e.showLineNumbers,b=e.wrapLongLines,v=e.wrapLines,E=v===void 0?!1:v,w=E?oa({},typeof d=="function"?d(r):d):{};if(w.className=w.className?[].concat(ag(w.className.trim().split(/\s+/)),ag(f)):f,r&&s){var T=zN(a,r,o);t.unshift(BN(r,T))}return b&h&&(w.style=oa({display:"flex"},w.style)),{type:"element",tagName:"span",properties:w,children:t}}function PN(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[],r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];e.length===void 0&&(e=[e]);for(var a=0;a<e.length;a++){var o=e[a];if(o.type==="text")r.push(Nu({children:[o],className:ag(new Set(t))}));else if(o.children){var s,u=t.concat(((s=o.properties)===null||s===void 0?void 0:s.className)||[]);PN(o.children,u).forEach(function(d){return r.push(d)})}}return r}function Fz(e,t,r,a,o,s,u,d,p){var f,h=PN(e.value),b=[],v=-1,E=0;function w(F,D){var P=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return Nu({children:F,lineNumber:D,lineNumberStyle:d,largestLineNumber:u,showInlineLineNumbers:o,lineProps:r,className:P,showLineNumbers:a,wrapLongLines:p,wrapLines:t})}function T(F,D){if(a&&D&&o){var P=zN(d,D,u);F.unshift(BN(D,P))}return F}function k(F,D){var P=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return t||P.length>0?w(F,D,P):T(F,D)}for(var A=function(){var D=h[E],P=D.children[0].value,z=Dz(P);if(z){var W=P.split(`
`);W.forEach(function(M,H){var X=a&&b.length+s,ee={type:"text",value:"".concat(M,`
`)};if(H===0){var te=h.slice(v+1,E).concat(Nu({children:[ee],className:D.properties.className})),Z=k(te,X);b.push(Z)}else if(H===W.length-1){var B=h[E+1]&&h[E+1].children&&h[E+1].children[0],Y={type:"text",value:"".concat(M)};if(B){var ne=Nu({children:[Y],className:D.properties.className});h.splice(E+1,0,ne)}else{var ce=[Y],C=k(ce,X,D.properties.className);b.push(C)}}else{var j=[ee],Q=k(j,X,D.properties.className);b.push(Q)}}),v=E}E++};E<h.length;)A();if(v!==h.length-1){var _=h.slice(v+1,h.length);if(_&&_.length){var R=a&&b.length+s,L=k(_,R);b.push(L)}}return t?b:(f=[]).concat.apply(f,b)}function Bz(e){var t=e.rows,r=e.stylesheet,a=e.useInlineStyles;return t.map(function(o,s){return FN({node:o,stylesheet:r,useInlineStyles:a,key:"code-segment-".concat(s)})})}function $N(e){return e&&typeof e.highlightAuto<"u"}function zz(e){var t=e.astGenerator,r=e.language,a=e.code,o=e.defaultCodeValue;if($N(t)){var s=Oz(t,r);return r==="text"?{value:o,language:"text"}:s?t.highlight(r,a):t.highlightAuto(a)}try{return r&&r!=="text"?{value:t.highlight(a,r)}:{value:o}}catch{return{value:o}}}function Pz(e,t){return function(a){var o,s,u=a.language,d=a.children,p=a.style,f=p===void 0?t:p,h=a.customStyle,b=h===void 0?{}:h,v=a.codeTagProps,E=v===void 0?{className:u?"language-".concat(u):void 0,style:oa(oa({},f['code[class*="language-"]']),f['code[class*="language-'.concat(u,'"]')])}:v,w=a.useInlineStyles,T=w===void 0?!0:w,k=a.showLineNumbers,A=k===void 0?!1:k,_=a.showInlineLineNumbers,R=_===void 0?!0:_,L=a.startingLineNumber,F=L===void 0?1:L,D=a.lineNumberContainerStyle,P=a.lineNumberStyle,z=P===void 0?{}:P,W=a.wrapLines,M=a.wrapLongLines,H=M===void 0?!1:M,X=a.lineProps,ee=X===void 0?{}:X,te=a.renderer,Z=a.PreTag,B=Z===void 0?"pre":Z,Y=a.CodeTag,ne=Y===void 0?"code":Y,ce=a.code,C=ce===void 0?(Array.isArray(d)?d[0]:d)||"":ce,j=a.astGenerator,Q=vz(a,Iz);j=j||e;var I=A?Ke.createElement(jz,{containerStyle:D,codeStyle:E.style||{},numberStyle:z,startingLineNumber:F,codeString:C}):null,ue=f.hljs||f['pre[class*="language-"]']||{backgroundColor:"#fff"},fe=$N(j)?"hljs":"prismjs",de=T?Object.assign({},Q,{style:Object.assign({},ue,b)}):Object.assign({},Q,{className:Q.className?"".concat(fe," ").concat(Q.className):fe,style:Object.assign({},b)});if(H?E.style=oa({whiteSpace:"pre-wrap"},E.style):E.style=oa({whiteSpace:"pre"},E.style),!j)return Ke.createElement(B,de,I,Ke.createElement(ne,E,C));(W===void 0&&te||H)&&(W=!0),te=te||Bz;var le=[{type:"text",value:C}],we=zz({astGenerator:j,language:u,code:C,defaultCodeValue:le});we.language===null&&(we.value=le);var me=(o=(s=C.match(/\n/g))===null||s===void 0?void 0:s.length)!==null&&o!==void 0?o:0,Re=F+me,Oe=Fz(we,W,ee,A,R,F,Re,z,H);return Ke.createElement(B,de,Ke.createElement(ne,E,!R&&I,te({rows:Oe,stylesheet:f,useInlineStyles:T})))}}const $z="modulepreload",Gz=function(e){return"/"+e},KA={},XA=function(t,r,a){let o=Promise.resolve();if(r&&r.length>0){let f=function(h){return Promise.all(h.map(b=>Promise.resolve(b).then(v=>({status:"fulfilled",value:v}),v=>({status:"rejected",reason:v}))))};var u=f;document.getElementsByTagName("link");const d=document.querySelector("meta[property=csp-nonce]"),p=d?.nonce||d?.getAttribute("nonce");o=f(r.map(h=>{if(h=Gz(h),h in KA)return;KA[h]=!0;const b=h.endsWith(".css"),v=b?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${h}"]${v}`))return;const E=document.createElement("link");if(E.rel=b?"stylesheet":$z,b||(E.as="script"),E.crossOrigin="",E.href=h,p&&E.setAttribute("nonce",p),document.head.appendChild(E),b)return new Promise((w,T)=>{E.addEventListener("load",w),E.addEventListener("error",()=>T(new Error(`Unable to preload CSS for ${h}`)))})}))}function s(d){const p=new Event("vite:preloadError",{cancelable:!0});if(p.payload=d,window.dispatchEvent(p),!p.defaultPrevented)throw d}return o.then(d=>{for(const p of d||[])p.status==="rejected"&&s(p.reason);return t().catch(s)})},Hz=["abap","abnf","actionscript","ada","agda","al","antlr4","apacheconf","apex","apl","applescript","aql","arduino","arff","armasm","arturo","asciidoc","asm6502","asmatmel","aspnet","autohotkey","autoit","avisynth","avro-idl","awk","bash","basic","batch","bbcode","bbj","bicep","birb","bison","bnf","bqn","brainfuck","brightscript","bro","bsl","c","cfscript","chaiscript","cil","cilkc","cilkcpp","clike","clojure","cmake","cobol","coffeescript","concurnas","cooklang","coq","cpp","crystal","csharp","cshtml","csp","css-extras","css","csv","cue","cypher","d","dart","dataweave","dax","dhall","diff","django","dns-zone-file","docker","dot","ebnf","editorconfig","eiffel","ejs","elixir","elm","erb","erlang","etlua","excel-formula","factor","false","firestore-security-rules","flow","fortran","fsharp","ftl","gap","gcode","gdscript","gedcom","gettext","gherkin","git","glsl","gml","gn","go-module","go","gradle","graphql","groovy","haml","handlebars","haskell","haxe","hcl","hlsl","hoon","hpkp","hsts","http","ichigojam","icon","icu-message-format","idris","iecst","ignore","inform7","ini","io","j","j Line truncated
darkMode: ${d?.darkMode!==!1}`})]}),g.jsx("h3",{className:"text-lg font-medium mb-3",children:"Available Presets"}),g.jsx("div",{className:"flex flex-wrap gap-2 mb-6",children:Object.keys(Y1).map(f=>g.jsx("span",{className:`px-3 py-1 rounded-full text-sm ${f===(d?.preset||"zinc")?"bg-primary text-primary-foreground":"bg-muted text-muted-foreground"}`,children:f},f))}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("strong",{children:"Click any item in the sidebar"})," to navigate."]}),g.jsx("h2",{className:"text-2xl font-semibold mt-8 mb-4",children:"Routes"}),g.jsxs("p",{className:"text-muted-foreground mb-4",children:[g.jsx("span",{className:"font-medium text-primary",children:t.routes?.length||0})," routes configured."]}),g.jsx("pre",{className:"bg-muted p-4 rounded-lg text-sm overflow-auto",children:JSON.stringify(t.routes?.slice(0,3),null,2)})]})}const GA=e=>typeof e=="boolean"?`${e}`:e===0?"0":e,HA=u_,Zg=(e,t)=>r=>{var a;if(t?.variants==null)return HA(e,r?.class,r?.className);const{variants:o,defaultVariants:s}=t,u=Object.keys(o).map(f=>{const h=r?.[f],b=s?.[f];if(h===null)return null;const v=GA(h)||GA(b);return o[f][v]}),d=r&&Object.entries(r).reduce((f,h)=>{let[b,v]=h;return v===void 0||(f[b]=v),f},{}),p=t==null||(a=t.compoundVariants)===null||a===void 0?void 0:a.reduce((f,h)=>{let{class:b,className:v,...E}=h;return Object.entries(E).every(w=>{let[T,k]=w;return Array.isArray(k)?k.includes({...s,...d}[T]):{...s,...d}[T]===k})?[...f,b,v]:f},[]);return HA(e,u,p,r?.class,r?.className)},DN=Zg("inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",{variants:{variant:{default:"bg-primary text-primary-foreground hover:bg-primary/90",destructive:"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",outline:"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",secondary:"bg-secondary text-secondary-foreground hover:bg-secondary/80",ghost:"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",link:"text-primary underline-offset-4 hover:underline"},size:{default:"h-9 px-4 py-2 has-[>svg]:px-3",xs:"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",sm:"h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",lg:"h-10 rounded-md px-6 has-[>svg]:px-4",icon:"size-9","icon-xs":"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3","icon-sm":"size-8","icon-lg":"size-10"}},defaultVariants:{variant:"default",size:"default"}});function $i({className:e,variant:t="default",size:r="default",asChild:a=!1,...o}){const s=a?Dg:"button";return g.jsx(s,{"data-slot":"button","data-variant":t,"data-size":r,className:be(DN({variant:t,size:r,className:e})),...o})}function _n({className:e,...t}){return g.jsx("div",{"data-slot":"card",className:be("bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",e),...t})}function Bn({className:e,...t}){return g.jsx("div",{"data-slot":"card-header",className:be("@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",e),...t})}function zn({className:e,...t}){return g.jsx("div",{"data-slot":"card-title",className:be("leading-none font-semibold",e),...t})}function Qg({className:e,...t}){return g.jsx("div",{"data-slot":"card-description",className:be("text-muted-foreground text-sm",e),...t})}function Nn({className:e,...t}){return g.jsx("div",{"data-slot":"card-content",className:be("px-6",e),...t})}function KB({className:e,...t}){return g.jsx("div",{"data-slot":"card-footer",className:be("flex items-center px-6 [.border-t]:pt-6",e),...t})}function XB({props:e}){const t=e?.title||"Statistics",r=e?.value||"0",a=e?.change||"",o=e?.changeType||"positive";return g.jsxs(_n,{className:"mb-4",children:[g.jsxs(Bn,{className:"pb-2",children:[g.jsx(Qg,{children:t}),g.jsx(zn,{className:"text-2xl",children:r})]}),a&&g.jsx(Nn,{className:"pt-0",children:g.jsx("span",{className:o==="positive"?"text-green-500 text-sm":"text-red-500 text-sm",children:a})})]})}function ZB({props:e}){const t=e?.title||"Quick Links",r=e?.links||[];return g.jsxs(_n,{className:"mb-4",children:[g.jsx(Bn,{className:"pb-2",children:g.jsx(zn,{className:"text-sm font-medium",children:t})}),g.jsx(Nn,{className:"space-y-1",children:r.map((a,o)=>g.jsx("a",{href:a.href,className:"block text-sm text-muted-foreground hover:text-foreground transition-colors",children:a.label},o))})]})}function QB({ Line truncated
`);p=b.pop()||"";for(const v of b)if(v.startsWith("data: "))try{const E=JSON.parse(v.slice(6));Ez(e,E,o)}catch{}}}catch(s){if(s.name!=="AbortError"){const u=Ua.get(e);u&&An(e,{thinkingSteps:[...u.state.thinkingSteps,`Error: ${s.message}`]})}}finally{const s=Ua.get(e);s&&(s.abortController=null),An(e,{isStreaming:!1}),o?.onEnd?.()}}function Ez(e,t,r){const a=Ua.get(e);if(!a)return;const o=a.toolCallMap;switch(t.type){case"session":t.session_id&&typeof t.session_id=="string"&&r?.onSessionId?.(t.session_id);break;case"token":case"run_content":case"team_run_content":{const u=t.token??t.content??"";u&&An(e,{currentResponse:a.state.currentResponse+u});break}case"message":case"run_completed":case"team_run_completed":{const u=t.content;u&&An(e,{currentResponse:u});break}case"thinking":case"reasoning_step":case"team_reasoning_step":{const u=t.step;u&&An(e,{thinkingSteps:[...a.state.thinkingSteps,u]});break}case"reasoning_started":case"team_reasoning_started":An(e,{thinkingSteps:[...a.state.thinkingSteps,"Starting reasoning..."]});break;case"tool_call":case"tool_call_started":case"team_tool_call_started":{const u={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"running",tool_call_id:t.tool_call_id,args:t.args};u.tool_call_id&&o.set(u.tool_call_id,u);const d=qA(a.state.toolCalls,u);An(e,{toolCalls:d});break}case"tool_call_completed":case"team_tool_call_completed":{const u=t.extra_data,d=t.a2ui??u?.a2ui,p=t.elements??u?.elements,f={name:t.name,description:t.description,icon:t.icon,step_number:t.step_number,status:"done",tool_call_id:t.tool_call_id,args:t.args,formatted_result:t.formatted_result,result:t.result,surface_id:t.surface_id??u?.surface_id,a2ui:d?.length?{messages:d,surface_id:t.surface_id??u?.surface_id}:void 0,elements:p};f.tool_call_id&&o.set(f.tool_call_id,f);const h=qA(a.state.toolCalls,f),b=p?.length?[...a.state.pendingElements,...p]:a.state.pendingElements;An(e,{toolCalls:h,pendingElements:b});break}case"message_element":{const u=t.extra_data,d=t.element??u?.element;d&&An(e,{pendingElements:[...a.state.pendingElements,d]});break}case"error":case"run_error":case"team_run_error":{const u=t.error;u&&An(e,{thinkingSteps:[...a.state.thinkingSteps,`Error: ${u}`]});break}case"end":case"done":case"run_cancelled":case"team_run_cancelled":break;default:gz(e,t);break}}function qA(e,t){if(t.tool_call_id){const r=e.findIndex(a=>a.tool_call_id===t.tool_call_id);if(r>=0){const a=[...e];return a[r]={...a[r],...t},a}}return[...e,t]}function UN(e={}){const{externalSessionId:t}=e,[r,a]=S.useState(()=>yu(t));S.useEffect(()=>{if(!t){a(yu(null));return}return a(yu(t)),mz(t,()=>{a(yu(t))})},[t]);const o=S.useCallback((u,d)=>{const p=t??`new-${Date.now()}`;bz(p,u,d,{onSessionId:f=>{e.onSessionId?.(f)},onEnd:e.onEnd})},[t,e.onSessionId,e.onEnd]),s=S.useCallback(()=>{t&&hz(t)},[t]);return{isStreaming:r.isStreaming,currentResponse:r.currentResponse,toolCalls:r.toolCalls,thinkingSteps:r.thinkingSteps,pendingElements:r.pendingElements,sendMessage:o,cancel:s}}function vz(e,t){if(e==null)return{};var r={};for(var a in e)if({}.hasOwnProperty.call(e,a)){if(t.indexOf(a)!==-1)continue;r[a]=e[a]}return r}function Sz(e,t){if(e==null)return{};var r,a,o=vz(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(a=0;a<s.length;a++)r=s[a],t.indexOf(r)===-1&&{}.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}function ig(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,a=Array(t);r<t;r++)a[r]=e[r];return a}function xz(e){if(Array.isArray(e))return ig(e)}function wz(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function Tz(e,t){if(e){if(typeof e=="string")return ig(e,t);var r={}.toString.call(e).slice(8,-1);return r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set"?Array.from(e):r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r)?ig(e,t):void 0}}function Az(){throw new TypeError(`Invalid attempt to spread non-iterable instance.
In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function og(e){return xz(e)||wz(e)||Tz(e)||Az()}function Tl(e){"@babel/helpers - typeof";return Tl=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Tl(e)}function kz(e,t){if(Tl(e)!="object"||!e)return e;var r=e[Symbol.toPrimitive];if(r!==void 0){var a=r.call(e,t);if(Tl(a)!="object")return a;throw new TypeError("@@toPrimitive must return a primitive value.")}return(t==="string"?String:Number)(e)}function _z(e){var t=kz(e,"string");return Tl(t)=="symbol"?t:t+""}function FN(e,t,r){return(t=_z(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function lg(){return lg=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var a in r)({}).hasOwnProperty.call(r,a)&&(e[a]=r[a])}return e},lg.apply(null,arguments)}function YA(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);t&&(a=a.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),r.push.apply(r,a)}return r}function Gi(e){for(var t=1;t<arguments.length;t++){var r=arguments[t]!=null?arguments[t]:{};t%2?YA(Object(r),!0).forEach(function(a){FN(e,a,r[a])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):YA(Object(r)).forEach(function(a){Object.defineProperty(e,a,Object.getOwnPropertyDescriptor(r,a))})}return e}function Nz(e){var t=e.length;if(t===0||t===1)return e;if(t===2)return[e[0],e[1],"".concat(e[0],".").concat(e[1]),"".concat(e[1],".").concat(e[0])];if(t===3)return[e[0],e[1],e[2],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0])];if(t>=4)return[e[0],e[1],e[2],e[3],"".concat(e[0],".").concat(e[1]),"".concat(e[0],".").concat(e[2]),"".concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[0]),"".concat(e[1],".").concat(e[2]),"".concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[1]),"".concat(e[2],".").concat(e[3]),"".concat(e[3],".").concat(e[0]),"".concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[0]),"".concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[2],".").concat(e[0],".").concat(e[1]),"".concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[0]),"".concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[1]),"".concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[3],".").concat(e[1],".").concat(e[0]),"".concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[0],".").concat(e[1],".").concat(e[2],".").concat(e[3]),"".concat(e[0],".").concat(e[1],".").concat(e[3],".").concat(e[2]),"".concat(e[0],".").concat(e[2],".").concat(e[1],".").concat(e[3]),"".concat(e[0],".").concat(e[2],".").concat(e[3],".").concat(e[1]),"".concat(e[0],".").concat(e[3],".").concat(e[1],".").concat(e[2]),"".concat(e[0],".").concat(e[3],".").concat(e[2],".").concat(e[1]),"".concat(e[1],".").concat(e[0],".").concat(e[2],".").concat(e[3]),"".concat(e[1],".").concat(e[0],".").concat(e[3],".").concat(e[2]),"".concat(e[1],".").concat(e[2],".").concat(e[0],".").concat(e[3]),"".concat(e[1],".").concat(e[2],".").concat(e[3],".").concat(e[0]),"".concat(e[1],".").concat(e[3],".").concat(e[0],".").concat(e[2]),"".concat(e[1],".").concat(e[3],".").concat(e[2],".").concat(e[0]),"".concat(e[2],".").concat(e[0],".").concat(e[1],".").concat(e[3]),"".concat(e[2],".").concat(e[0],".").concat(e[3],".").concat(e[1]),"".concat(e[2],".").concat(e[1],".").concat(e[0],".").concat(e[3]),"".concat(e[2],".").concat(e[1],".").concat(e[3],".").concat(e[ Line truncated
`))})}function Uz(e){var t=e.codeString,r=e.codeStyle,a=e.containerStyle,o=a===void 0?{float:"left",paddingRight:"10px"}:a,s=e.numberStyle,u=s===void 0?{}:s,d=e.startingLineNumber;return Ke.createElement("code",{style:Object.assign({},r,o)},jz({lines:t.replace(/\n$/,"").split(`
`),style:u,startingLineNumber:d}))}function Fz(e){return"".concat(e.toString().length,".25em")}function zN(e,t){return{type:"element",tagName:"span",properties:{key:"line-number--".concat(e),className:["comment","linenumber","react-syntax-highlighter-line-number"],style:t},children:[{type:"text",value:e}]}}function PN(e,t,r){var a={display:"inline-block",minWidth:Fz(r),paddingRight:"1em",textAlign:"right",userSelect:"none"},o=typeof e=="function"?e(t):e,s=oa(oa({},a),o);return s}function Ru(e){var t=e.children,r=e.lineNumber,a=e.lineNumberStyle,o=e.largestLineNumber,s=e.showInlineLineNumbers,u=e.lineProps,d=u===void 0?{}:u,p=e.className,f=p===void 0?[]:p,h=e.showLineNumbers,b=e.wrapLongLines,v=e.wrapLines,E=v===void 0?!1:v,w=E?oa({},typeof d=="function"?d(r):d):{};if(w.className=w.className?[].concat(og(w.className.trim().split(/\s+/)),og(f)):f,r&&s){var T=PN(a,r,o);t.unshift(zN(r,T))}return b&h&&(w.style=oa({display:"flex"},w.style)),{type:"element",tagName:"span",properties:w,children:t}}function $N(e){var t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:[],r=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];e.length===void 0&&(e=[e]);for(var a=0;a<e.length;a++){var o=e[a];if(o.type==="text")r.push(Ru({children:[o],className:og(new Set(t))}));else if(o.children){var s,u=t.concat(((s=o.properties)===null||s===void 0?void 0:s.className)||[]);$N(o.children,u).forEach(function(d){return r.push(d)})}}return r}function Bz(e,t,r,a,o,s,u,d,p){var f,h=$N(e.value),b=[],v=-1,E=0;function w(j,D){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return Ru({children:j,lineNumber:D,lineNumberStyle:d,largestLineNumber:u,showInlineLineNumbers:o,lineProps:r,className:z,showLineNumbers:a,wrapLongLines:p,wrapLines:t})}function T(j,D){if(a&&D&&o){var z=PN(d,D,u);j.unshift(zN(D,z))}return j}function k(j,D){var z=arguments.length>2&&arguments[2]!==void 0?arguments[2]:[];return t||z.length>0?w(j,D,z):T(j,D)}for(var A=function(){var D=h[E],z=D.children[0].value,F=Mz(z);if(F){var V=z.split(`
`);V.forEach(function(M,H){var K=a&&b.length+s,Z={type:"text",value:"".concat(M,`
`)};if(H===0){var te=h.slice(v+1,E).concat(Ru({children:[Z],className:D.properties.className})),Q=k(te,K);b.push(Q)}else if(H===V.length-1){var P=h[E+1]&&h[E+1].children&&h[E+1].children[0],W={type:"text",value:"".concat(M)};if(P){var ne=Ru({children:[W],className:D.properties.className});h.splice(E+1,0,ne)}else{var ce=[W],C=k(ce,K,D.properties.className);b.push(C)}}else{var U=[Z],J=k(U,K,D.properties.className);b.push(J)}}),v=E}E++};E<h.length;)A();if(v!==h.length-1){var _=h.slice(v+1,h.length);if(_&&_.length){var R=a&&b.length+s,L=k(_,R);b.push(L)}}return t?b:(f=[]).concat.apply(f,b)}function zz(e){var t=e.rows,r=e.stylesheet,a=e.useInlineStyles;return t.map(function(o,s){return BN({node:o,stylesheet:r,useInlineStyles:a,key:"code-segment-".concat(s)})})}function GN(e){return e&&typeof e.highlightAuto<"u"}function Pz(e){var t=e.astGenerator,r=e.language,a=e.code,o=e.defaultCodeValue;if(GN(t)){var s=Iz(t,r);return r==="text"?{value:o,language:"text"}:s?t.highlight(r,a):t.highlightAuto(a)}try{return r&&r!=="text"?{value:t.highlight(a,r)}:{value:o}}catch{return{value:o}}}function $z(e,t){return function(a){var o,s,u=a.language,d=a.children,p=a.style,f=p===void 0?t:p,h=a.customStyle,b=h===void 0?{}:h,v=a.codeTagProps,E=v===void 0?{className:u?"language-".concat(u):void 0,style:oa(oa({},f['code[class*="language-"]']),f['code[class*="language-'.concat(u,'"]')])}:v,w=a.useInlineStyles,T=w===void 0?!0:w,k=a.showLineNumbers,A=k===void 0?!1:k,_=a.showInlineLineNumbers,R=_===void 0?!0:_,L=a.startingLineNumber,j=L===void 0?1:L,D=a.lineNumberContainerStyle,z=a.lineNumberStyle,F=z===void 0?{}:z,V=a.wrapLines,M=a.wrapLongLines,H=M===void 0?!1:M,K=a.lineProps,Z=K===void 0?{}:K,te=a.renderer,Q=a.PreTag,P=Q===void 0?"pre":Q,W=a.CodeTag,ne=W===void 0?"code":W,ce=a.code,C=ce===void 0?(Array.isArray(d)?d[0]:d)||"":ce,U=a.astGenerator,J=Sz(a,Lz);U=U||e;var I=A?Ke.createElement(Uz,{containerStyle:D,codeStyle:E.style||{},numberStyle:F,startingLineNumber:j,codeString:C}):null,ue=f.hljs||f['pre[class*="language-"]']||{backgroundColor:"#fff"},fe=GN(U)?"hljs":"prismjs",de=T?Object.assign({},J,{style:Object.assign({},ue,b)}):Object.assign({},J,{className:J.className?"".concat(fe," ").concat(J.className):fe,style:Object.assign({},b)});if(H?E.style=oa({whiteSpace:"pre-wrap"},E.style):E.style=oa({whiteSpace:"pre"},E.style),!U)return Ke.createElement(P,de,I,Ke.createElement(ne,E,C));(V===void 0&&te||H)&&(V=!0),te=te||zz;var le=[{type:"text",value:C}],we=Pz({astGenerator:U,language:u,code:C,defaultCodeValue:le});we.language===null&&(we.value=le);var me=(o=(s=C.match(/\n/g))===null||s===void 0?void 0:s.length)!==null&&o!==void 0?o:0,Re=j+me,Oe=Bz(we,V,Z,A,R,j,Re,F,H);return Ke.createElement(P,de,Ke.createElement(ne,E,!R&&I,te({rows:Oe,stylesheet:f,useInlineStyles:T})))}}const Gz="modulepreload",Hz=function(e){return"/"+e},XA={},ZA=function(t,r,a){let o=Promise.resolve();if(r&&r.length>0){let p=function(f){return Promise.all(f.map(h=>Promise.resolve(h).then(b=>({status:"fulfilled",value:b}),b=>({status:"rejected",reason:b}))))};document.getElementsByTagName("link");const u=document.querySelector("meta[property=csp-nonce]"),d=u?.nonce||u?.getAttribute("nonce");o=p(r.map(f=>{if(f=Hz(f),f in XA)return;XA[f]=!0;const h=f.endsWith(".css"),b=h?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${f}"]${b}`))return;const v=document.createElement("link");if(v.rel=h?"stylesheet":Gz,h||(v.as="script"),v.crossOrigin="",v.href=f,d&&v.setAttribute("nonce",d),document.head.appendChild(v),h)return new Promise((E,w)=>{v.addEventListener("load",E),v.addEventListener("error",()=>w(new Error(`Unable to preload CSS for ${f}`)))})}))}function s(u){const d=new Event("vite:preloadError",{cancelable:!0});if(d.payload=u,window.dispatchEvent(d),!d.defaultPrevented)throw u}return o.then(u=>{for(const d of u||[])d.status==="rejected"&&s(d.reason);return t().catch(s)})},Vz=["abap","abnf","actionscript","ada","agda","al","antlr4","apacheconf","apex","apl","applescript","aql","arduino","arff","armasm","arturo","asciidoc","asm6502","asmatmel","aspnet","autohotkey","autoit","avisynth","avro-idl","awk","bash","basic","batch","bbcode","bbj","bicep","birb","bison","bnf","bqn","brainfuck","brightscript","bro","bsl","c","cfscript","chaiscript","cil","cilkc","cilkcpp","clike","clojure","cmake","cobol","coffeescript","concurnas","cooklang","coq","cpp","crystal","csharp","cshtml","csp","css-extras","css","csv","cue","cypher","d","dart","dataweave","dax","dhall","diff","django","dns-zone-file","docker","dot","ebnf","editorconfig","eiffel","ejs","elixir","elm","erb","erlang","etlua","excel-formula","factor","false","firestore-security-rules","flow","fortran","fsharp","ftl","gap","gcode","gdscript","gedcom","gettext","gherkin","git","glsl","gml","gn","go-module","go","gradle","graphql","groovy","haml","handlebars","haskell","haxe","hcl","hlsl","hoon","hpkp","hsts","http","ichigojam","icon","icu-message-format","idris","iecst","ignore","inform7","ini","io","j","java","javado Line truncated
?|
|(?![\\s\\S])))+`,"m"),alias:s,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(a)[0]}}}}),Object.defineProperty(t.languages.diff,"PREFIXES",{value:r})})(e)}Bt.displayName="markup-templating";Bt.aliases=[];function Bt(e){e.register(Ot),(function(t){function r(a,o){return"___"+a.toUpperCase()+o+"___"}Object.defineProperties(t.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,o,s,u){if(a.language===o){var d=a.tokenStack=[];a.code=a.code.replace(s,function(p){if(typeof u=="function"&&!u(p))return p;for(var f=d.length,h;a.code.indexOf(h=r(o,f))!==-1;)++f;return d[f]=p,h}),a.grammar=t.languages.markup}}},tokenizePlaceholders:{value:function(a,o){if(a.language!==o||!a.tokenStack)return;a.grammar=t.languages[o];var s=0,u=Object.keys(a.tokenStack);function d(p){for(var f=0;f<p.length&&!(s>=u.length);f++){var h=p[f];if(typeof h=="string"||h.content&&typeof h.content=="string"){var b=u[s],v=a.tokenStack[b],E=typeof h=="string"?h:h.content,w=r(o,b),T=E.indexOf(w);if(T>-1){++s;var k=E.substring(0,T),A=new t.Token(o,t.tokenize(v,a.grammar),"language-"+o,v),_=E.substring(T+w.length),R=[];k&&R.push.apply(R,d([k])),R.push(A),_&&R.push.apply(R,d([_])),typeof h=="string"?p.splice.apply(p,[f,1].concat(R)):h.content=R}}else h.content&&d(h.content)}return p}d(a.tokens)}}})})(e)}lh.displayName="django";lh.aliases=["jinja2"];function lh(e){e.register(Bt),(function(t){t.languages.django={comment:/^\{#[\s\S]*?#\}$/,tag:{pattern:/(^\{%[+-]?\s*)\w+/,lookbehind:!0,alias:"keyword"},delimiter:{pattern:/^\{[{%][+-]?|[+-]?[}%]\}$/,alias:"punctuation"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},filter:{pattern:/(\|)\w+/,lookbehind:!0,alias:"function"},test:{pattern:/(\bis\s+(?:not\s+)?)(?!not\b)\w+/,lookbehind:!0,alias:"function"},function:/\b[a-z_]\w+(?=\s*\()/i,keyword:/\b(?:and|as|by|else|for|if|import|in|is|loop|not|or|recursive|with|without)\b/,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,number:/\b\d+(?:\.\d+)?\b/,boolean:/[Ff]alse|[Nn]one|[Tt]rue/,variable:/\b\w+\b/,punctuation:/[{}[\](),.:;]/};var r=/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}|\{#[\s\S]*?#\}/g,a=t.languages["markup-templating"];t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"django",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"django")}),t.languages.jinja2=t.languages.django,t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"jinja2",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"jinja2")})})(e)}sh.displayName="dns-zone-file";sh.aliases=["dns-zone"];function sh(e){e.languages["dns-zone-file"]={comment:/;.*/,string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},variable:[{pattern:/(^\$ORIGIN[ \t]+)\S+/m,lookbehind:!0},{pattern:/(^|\s)@(?=\s|$)/,lookbehind:!0}],keyword:/^\$(?:INCLUDE|ORIGIN|TTL)(?=\s|$)/m,class:{pattern:/(^|\s)(?:CH|CS|HS|IN)(?=\s|$)/,lookbehind:!0,alias:"keyword"},type:{pattern:/(^|\s)(?:A|A6|AAAA|AFSDB|APL|ATMA|CAA|CDNSKEY|CDS|CERT|CNAME|DHCID|DLV|DNAME|DNSKEY|DS|EID|GID|GPOS|HINFO|HIP|IPSECKEY|ISDN|KEY|KX|LOC|MAILA|MAILB|MB|MD|MF|MG|MINFO|MR|MX|NAPTR|NB|NBSTAT|NIMLOC|NINFO|NS|NSAP|NSAP-PTR|NSEC|NSEC3|NSEC3PARAM|NULL|NXT|OPENPGPKEY|PTR|PX|RKEY|RP|RRSIG|RT|SIG|SINK|SMIMEA|SOA|SPF|SRV|SSHFP|TA|TKEY|TLSA|TSIG|TXT|UID|UINFO|UNSPEC|URI|WKS|X25)(?=\s|$)/,lookbehind:!0,alias:"keyword"},punctuation:/[()]/},e.languages["dns-zone"]=e.languages["dns-zone-file"]}uh.displayName="docker";uh.aliases=["dockerfile"];function uh(e){(function(t){var r=/\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source,a=/(?:[ \t]+(?![ \t])(?:<SP_BS>)?|<SP_BS>)/.source.replace(/<SP_BS>/g,function(){return r}),o=/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source,s=/--[\w-]+=(?:<STR>|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(/<STR>/g,function(){return o}),u={pattern:RegExp(o),greedy:!0},d={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function p(f,h){return f=f.replace(/<OPT>/g,function(){return s}).replace(/<SP>/g,function(){return a}),RegExp(f,h)}t.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:p(/(^(?:ONBUILD<SP>)?\w+<SP>)<OPT>(?:<SP><OPT>)*/.source,"i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[u,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:p(/(^(?:ONBUILD<SP>)?HEALTHCHECK<SP>(?:<OPT><SP>)*)(?:CMD|NONE)\b/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^(?:ONBUILD<SP>)?FROM<SP>(?:<OPT><SP>)*(?!--)[^ \t\\]+<SP>)AS/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^ONBUILD<SP>)\w+/.source,"i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:d,string:u,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,oper Line truncated
]))(?:`+(/[^\s\\]/.source+'|[ ]+(?:(?![ "])|'+a+")|"+/\\[\r\n]+(?:[#;].*[\r\n]+)*(?![#;])/.source)+")*"),lookbehind:!0,greedy:!0,alias:"attr-value",inside:{comment:r,quoted:{pattern:RegExp(/(^|\s)/.source+a),lookbehind:!0,greedy:!0},punctuation:/\\$/m,boolean:{pattern:/^(?:false|no|off|on|true|yes)$/,greedy:!0}}},punctuation:/=/}})(e)}Ul.displayName="t4-templating";Ul.aliases=[];function Ul(e){(function(t){function r(o,s,u){return{pattern:RegExp("<#"+o+"[\\s\\S]*?#>"),alias:"block",inside:{delimiter:{pattern:RegExp("^<#"+o+"|#>$"),alias:"important"},content:{pattern:/[\s\S]+/,inside:s,alias:u}}}}function a(o){var s=t.languages[o],u="language-"+o;return{block:{pattern:/<#[\s\S]+?#>/,inside:{directive:r("@",{"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/,inside:{punctuation:/^=|^["']|["']$/}},keyword:/\b\w+(?=\s)/,"attr-name":/\b\w+/}),expression:r("=",s,u),"class-feature":r("\\+",s,u),standard:r("",s,u)}}}}t.languages["t4-templating"]=Object.defineProperty({},"createT4",{value:a})})(e)}xE.displayName="t4-cs";xE.aliases=["t4"];function xE(e){e.register(to),e.register(Ul),e.languages.t4=e.languages["t4-cs"]=e.languages["t4-templating"].createT4("csharp")}hc.displayName="vbnet";hc.aliases=[];function hc(e){e.register(cc),e.languages.vbnet=e.languages.extend("basic",{comment:[{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},{pattern:/(^|[^\\:])'.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(^|[^"])"(?:""|[^"])*"(?!")/,lookbehind:!0,greedy:!0},keyword:/(?:\b(?:ADDHANDLER|ADDRESSOF|ALIAS|AND|ANDALSO|AS|BEEP|BLOAD|BOOLEAN|BSAVE|BYREF|BYTE|BYVAL|CALL(?: ABSOLUTE)?|CASE|CATCH|CBOOL|CBYTE|CCHAR|CDATE|CDBL|CDEC|CHAIN|CHAR|CHDIR|CINT|CLASS|CLEAR|CLNG|CLOSE|CLS|COBJ|COM|COMMON|CONST|CONTINUE|CSBYTE|CSHORT|CSNG|CSTR|CTYPE|CUINT|CULNG|CUSHORT|DATA|DATE|DECIMAL|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DEFAULT|DELEGATE|DIM|DIRECTCAST|DO|DOUBLE|ELSE|ELSEIF|END|ENUM|ENVIRON|ERASE|ERROR|EVENT|EXIT|FALSE|FIELD|FILES|FINALLY|FOR(?: EACH)?|FRIEND|FUNCTION|GET|GETTYPE|GETXMLNAMESPACE|GLOBAL|GOSUB|GOTO|HANDLES|IF|IMPLEMENTS|IMPORTS|IN|INHERITS|INPUT|INTEGER|INTERFACE|IOCTL|IS|ISNOT|KEY|KILL|LET|LIB|LIKE|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|ME|MKDIR|MOD|MODULE|MUSTINHERIT|MUSTOVERRIDE|MYBASE|MYCLASS|NAME|NAMESPACE|NARROWING|NEW|NEXT|NOT|NOTHING|NOTINHERITABLE|NOTOVERRIDABLE|OBJECT|OF|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPERATOR|OPTION(?: BASE)?|OPTIONAL|OR|ORELSE|OUT|OVERLOADS|OVERRIDABLE|OVERRIDES|PARAMARRAY|PARTIAL|POKE|PRIVATE|PROPERTY|PROTECTED|PUBLIC|PUT|RAISEEVENT|READ|READONLY|REDIM|REM|REMOVEHANDLER|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SBYTE|SELECT(?: CASE)?|SET|SHADOWS|SHARED|SHELL|SHORT|SINGLE|SLEEP|STATIC|STEP|STOP|STRING|STRUCTURE|SUB|SWAP|SYNCLOCK|SYSTEM|THEN|THROW|TIMER|TO|TROFF|TRON|TRUE|TRY|TRYCAST|TYPE|TYPEOF|UINTEGER|ULONG|UNLOCK|UNTIL|USHORT|USING|VIEW PRINT|WAIT|WEND|WHEN|WHILE|WIDENING|WITH|WITHEVENTS|WRITE|WRITEONLY|XOR)|\B(?:#CONST|#ELSE|#ELSEIF|#END|#IF))(?:\$|\b)/i,punctuation:/[,;:(){}]/})}wE.displayName="t4-vb";wE.aliases=[];function wE(e){e.register(Ul),e.register(hc),e.languages["t4-vb"]=e.languages["t4-templating"].createT4("vbnet")}TE.displayName="tap";TE.aliases=[];function TE(e){e.register(uc),e.languages.tap={fail:/not ok[^#{\n\r]*/,pass:/ok[^#{\n\r]*/,pragma:/pragma [+-][a-z]+/,bailout:/bail out!.*/i,version:/TAP version \d+/i,plan:/\b\d+\.\.\d+(?: +#.*)?/,subtest:{pattern:/# Subtest(?:: .*)?/,greedy:!0},punctuation:/[{}]/,directive:/#.*/,yamlish:{pattern:/(^[ \t]*)---[\s\S]*?[\r\n][ \t]*\.\.\.$/m,lookbehind:!0,inside:e.languages.yaml,alias:"language-yaml"}}}AE.displayName="tcl";AE.aliases=[];function AE(e){e.languages.tcl={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},string:{pattern:/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"/,greedy:!0},variable:[{pattern:/(\$)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/,lookbehind:!0},{pattern:/(\$)\{[^}]+\}/,lookbehind:!0},{pattern:/(^[\t ]*set[ \t]+)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/m,lookbehind:!0}],function:{pattern:/(^[\t ]*proc[ \t]+)\S+/m,lookbehind:!0},builtin:[{pattern:/(^[\t ]*)(?:break|class|continue|error|eval|exit|for|foreach|if|proc|return|switch|while)\b/m,lookbehind:!0},/\b(?:else|elseif)\b/],scope:{pattern:/(^[\t ]*)(?:global|upvar|variable)\b/m,lookbehind:!0,alias:"constant"},keyword:{pattern:/(^[\t ]*|\[)(?:Safe_Base|Tcl|after|append|apply|array|auto_(?:execok|import|load|mkindex|qualify|reset)|automkindex_old|bgerror|binary|catch|cd|chan|clock|close|concat|dde|dict|encoding|eof|exec|expr|fblocked|fconfigure|fcopy|file(?:event|name)?|flush|gets|glob|history|http|incr|info|interp|join|lappend|lassign|lindex|linsert|list|llength|load|lrange|lrepeat|lreplace|lreverse|lsearch|lset|lsort|math(?:func|op)|memory|msgcat|namespace|open|package|parray|pid|pkg_mkIndex|platform|puts|pwd|re_syntax|read|refchan|regexp|registry|regsub|rename|scan|seek|set|socket|source|split|string|subst|tcl(?:_endOfWord|_findLibrary|startOf(?:Next|Previous)Word|test|vars|wordBreak(?:After|Before))|tell|time|tm|trace|unknown|unload|unset|update|uplevel|vwait Line truncated
|(?![\\s\\S])))+`,"m"),alias:s,inside:{line:{pattern:/(.)(?=[\s\S]).*(?:\r\n?|\n)?/,lookbehind:!0},prefix:{pattern:/[\s\S]/,alias:/\w+/.exec(a)[0]}}}}),Object.defineProperty(t.languages.diff,"PREFIXES",{value:r})})(e)}Bt.displayName="markup-templating";Bt.aliases=[];function Bt(e){e.register(Ot),(function(t){function r(a,o){return"___"+a.toUpperCase()+o+"___"}Object.defineProperties(t.languages["markup-templating"]={},{buildPlaceholders:{value:function(a,o,s,u){if(a.language===o){var d=a.tokenStack=[];a.code=a.code.replace(s,function(p){if(typeof u=="function"&&!u(p))return p;for(var f=d.length,h;a.code.indexOf(h=r(o,f))!==-1;)++f;return d[f]=p,h}),a.grammar=t.languages.markup}}},tokenizePlaceholders:{value:function(a,o){if(a.language!==o||!a.tokenStack)return;a.grammar=t.languages[o];var s=0,u=Object.keys(a.tokenStack);function d(p){for(var f=0;f<p.length&&!(s>=u.length);f++){var h=p[f];if(typeof h=="string"||h.content&&typeof h.content=="string"){var b=u[s],v=a.tokenStack[b],E=typeof h=="string"?h:h.content,w=r(o,b),T=E.indexOf(w);if(T>-1){++s;var k=E.substring(0,T),A=new t.Token(o,t.tokenize(v,a.grammar),"language-"+o,v),_=E.substring(T+w.length),R=[];k&&R.push.apply(R,d([k])),R.push(A),_&&R.push.apply(R,d([_])),typeof h=="string"?p.splice.apply(p,[f,1].concat(R)):h.content=R}}else h.content&&d(h.content)}return p}d(a.tokens)}}})})(e)}uh.displayName="django";uh.aliases=["jinja2"];function uh(e){e.register(Bt),(function(t){t.languages.django={comment:/^\{#[\s\S]*?#\}$/,tag:{pattern:/(^\{%[+-]?\s*)\w+/,lookbehind:!0,alias:"keyword"},delimiter:{pattern:/^\{[{%][+-]?|[+-]?[}%]\}$/,alias:"punctuation"},string:{pattern:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},filter:{pattern:/(\|)\w+/,lookbehind:!0,alias:"function"},test:{pattern:/(\bis\s+(?:not\s+)?)(?!not\b)\w+/,lookbehind:!0,alias:"function"},function:/\b[a-z_]\w+(?=\s*\()/i,keyword:/\b(?:and|as|by|else|for|if|import|in|is|loop|not|or|recursive|with|without)\b/,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,number:/\b\d+(?:\.\d+)?\b/,boolean:/[Ff]alse|[Nn]one|[Tt]rue/,variable:/\b\w+\b/,punctuation:/[{}[\](),.:;]/};var r=/\{\{[\s\S]*?\}\}|\{%[\s\S]*?%\}|\{#[\s\S]*?#\}/g,a=t.languages["markup-templating"];t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"django",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"django")}),t.languages.jinja2=t.languages.django,t.hooks.add("before-tokenize",function(o){a.buildPlaceholders(o,"jinja2",r)}),t.hooks.add("after-tokenize",function(o){a.tokenizePlaceholders(o,"jinja2")})})(e)}ch.displayName="dns-zone-file";ch.aliases=["dns-zone"];function ch(e){e.languages["dns-zone-file"]={comment:/;.*/,string:{pattern:/"(?:\\.|[^"\\\r\n])*"/,greedy:!0},variable:[{pattern:/(^\$ORIGIN[ \t]+)\S+/m,lookbehind:!0},{pattern:/(^|\s)@(?=\s|$)/,lookbehind:!0}],keyword:/^\$(?:INCLUDE|ORIGIN|TTL)(?=\s|$)/m,class:{pattern:/(^|\s)(?:CH|CS|HS|IN)(?=\s|$)/,lookbehind:!0,alias:"keyword"},type:{pattern:/(^|\s)(?:A|A6|AAAA|AFSDB|APL|ATMA|CAA|CDNSKEY|CDS|CERT|CNAME|DHCID|DLV|DNAME|DNSKEY|DS|EID|GID|GPOS|HINFO|HIP|IPSECKEY|ISDN|KEY|KX|LOC|MAILA|MAILB|MB|MD|MF|MG|MINFO|MR|MX|NAPTR|NB|NBSTAT|NIMLOC|NINFO|NS|NSAP|NSAP-PTR|NSEC|NSEC3|NSEC3PARAM|NULL|NXT|OPENPGPKEY|PTR|PX|RKEY|RP|RRSIG|RT|SIG|SINK|SMIMEA|SOA|SPF|SRV|SSHFP|TA|TKEY|TLSA|TSIG|TXT|UID|UINFO|UNSPEC|URI|WKS|X25)(?=\s|$)/,lookbehind:!0,alias:"keyword"},punctuation:/[()]/},e.languages["dns-zone"]=e.languages["dns-zone-file"]}dh.displayName="docker";dh.aliases=["dockerfile"];function dh(e){(function(t){var r=/\\[\r\n](?:\s|\\[\r\n]|#.*(?!.))*(?![\s#]|\\[\r\n])/.source,a=/(?:[ \t]+(?![ \t])(?:<SP_BS>)?|<SP_BS>)/.source.replace(/<SP_BS>/g,function(){return r}),o=/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"|'(?:[^'\\\r\n]|\\(?:\r\n|[\s\S]))*'/.source,s=/--[\w-]+=(?:<STR>|(?!["'])(?:[^\s\\]|\\.)+)/.source.replace(/<STR>/g,function(){return o}),u={pattern:RegExp(o),greedy:!0},d={pattern:/(^[ \t]*)#.*/m,lookbehind:!0,greedy:!0};function p(f,h){return f=f.replace(/<OPT>/g,function(){return s}).replace(/<SP>/g,function(){return a}),RegExp(f,h)}t.languages.docker={instruction:{pattern:/(^[ \t]*)(?:ADD|ARG|CMD|COPY|ENTRYPOINT|ENV|EXPOSE|FROM|HEALTHCHECK|LABEL|MAINTAINER|ONBUILD|RUN|SHELL|STOPSIGNAL|USER|VOLUME|WORKDIR)(?=\s)(?:\\.|[^\r\n\\])*(?:\\$(?:\s|#.*$)*(?![\s#])(?:\\.|[^\r\n\\])*)*/im,lookbehind:!0,greedy:!0,inside:{options:{pattern:p(/(^(?:ONBUILD<SP>)?\w+<SP>)<OPT>(?:<SP><OPT>)*/.source,"i"),lookbehind:!0,greedy:!0,inside:{property:{pattern:/(^|\s)--[\w-]+/,lookbehind:!0},string:[u,{pattern:/(=)(?!["'])(?:[^\s\\]|\\.)+/,lookbehind:!0}],operator:/\\$/m,punctuation:/=/}},keyword:[{pattern:p(/(^(?:ONBUILD<SP>)?HEALTHCHECK<SP>(?:<OPT><SP>)*)(?:CMD|NONE)\b/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^(?:ONBUILD<SP>)?FROM<SP>(?:<OPT><SP>)*(?!--)[^ \t\\]+<SP>)AS/.source,"i"),lookbehind:!0,greedy:!0},{pattern:p(/(^ONBUILD<SP>)\w+/.source,"i"),lookbehind:!0,greedy:!0},{pattern:/^\w+/,greedy:!0}],comment:d,string:u,variable:/\$(?:\w+|\{[^{}"'\\]*\})/,oper Line truncated
]))(?:`+(/[^\s\\]/.source+'|[ ]+(?:(?![ "])|'+a+")|"+/\\[\r\n]+(?:[#;].*[\r\n]+)*(?![#;])/.source)+")*"),lookbehind:!0,greedy:!0,alias:"attr-value",inside:{comment:r,quoted:{pattern:RegExp(/(^|\s)/.source+a),lookbehind:!0,greedy:!0},punctuation:/\\$/m,boolean:{pattern:/^(?:false|no|off|on|true|yes)$/,greedy:!0}}},punctuation:/=/}})(e)}Ul.displayName="t4-templating";Ul.aliases=[];function Ul(e){(function(t){function r(o,s,u){return{pattern:RegExp("<#"+o+"[\\s\\S]*?#>"),alias:"block",inside:{delimiter:{pattern:RegExp("^<#"+o+"|#>$"),alias:"important"},content:{pattern:/[\s\S]+/,inside:s,alias:u}}}}function a(o){var s=t.languages[o],u="language-"+o;return{block:{pattern:/<#[\s\S]+?#>/,inside:{directive:r("@",{"attr-value":{pattern:/=(?:("|')(?:\\[\s\S]|(?!\1)[^\\])*\1|[^\s'">=]+)/,inside:{punctuation:/^=|^["']|["']$/}},keyword:/\b\w+(?=\s)/,"attr-name":/\b\w+/}),expression:r("=",s,u),"class-feature":r("\\+",s,u),standard:r("",s,u)}}}}t.languages["t4-templating"]=Object.defineProperty({},"createT4",{value:a})})(e)}TE.displayName="t4-cs";TE.aliases=["t4"];function TE(e){e.register(to),e.register(Ul),e.languages.t4=e.languages["t4-cs"]=e.languages["t4-templating"].createT4("csharp")}bc.displayName="vbnet";bc.aliases=[];function bc(e){e.register(dc),e.languages.vbnet=e.languages.extend("basic",{comment:[{pattern:/(?:!|REM\b).+/i,inside:{keyword:/^REM/i}},{pattern:/(^|[^\\:])'.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(^|[^"])"(?:""|[^"])*"(?!")/,lookbehind:!0,greedy:!0},keyword:/(?:\b(?:ADDHANDLER|ADDRESSOF|ALIAS|AND|ANDALSO|AS|BEEP|BLOAD|BOOLEAN|BSAVE|BYREF|BYTE|BYVAL|CALL(?: ABSOLUTE)?|CASE|CATCH|CBOOL|CBYTE|CCHAR|CDATE|CDBL|CDEC|CHAIN|CHAR|CHDIR|CINT|CLASS|CLEAR|CLNG|CLOSE|CLS|COBJ|COM|COMMON|CONST|CONTINUE|CSBYTE|CSHORT|CSNG|CSTR|CTYPE|CUINT|CULNG|CUSHORT|DATA|DATE|DECIMAL|DECLARE|DEF(?: FN| SEG|DBL|INT|LNG|SNG|STR)|DEFAULT|DELEGATE|DIM|DIRECTCAST|DO|DOUBLE|ELSE|ELSEIF|END|ENUM|ENVIRON|ERASE|ERROR|EVENT|EXIT|FALSE|FIELD|FILES|FINALLY|FOR(?: EACH)?|FRIEND|FUNCTION|GET|GETTYPE|GETXMLNAMESPACE|GLOBAL|GOSUB|GOTO|HANDLES|IF|IMPLEMENTS|IMPORTS|IN|INHERITS|INPUT|INTEGER|INTERFACE|IOCTL|IS|ISNOT|KEY|KILL|LET|LIB|LIKE|LINE INPUT|LOCATE|LOCK|LONG|LOOP|LSET|ME|MKDIR|MOD|MODULE|MUSTINHERIT|MUSTOVERRIDE|MYBASE|MYCLASS|NAME|NAMESPACE|NARROWING|NEW|NEXT|NOT|NOTHING|NOTINHERITABLE|NOTOVERRIDABLE|OBJECT|OF|OFF|ON(?: COM| ERROR| KEY| TIMER)?|OPEN|OPERATOR|OPTION(?: BASE)?|OPTIONAL|OR|ORELSE|OUT|OVERLOADS|OVERRIDABLE|OVERRIDES|PARAMARRAY|PARTIAL|POKE|PRIVATE|PROPERTY|PROTECTED|PUBLIC|PUT|RAISEEVENT|READ|READONLY|REDIM|REM|REMOVEHANDLER|RESTORE|RESUME|RETURN|RMDIR|RSET|RUN|SBYTE|SELECT(?: CASE)?|SET|SHADOWS|SHARED|SHELL|SHORT|SINGLE|SLEEP|STATIC|STEP|STOP|STRING|STRUCTURE|SUB|SWAP|SYNCLOCK|SYSTEM|THEN|THROW|TIMER|TO|TROFF|TRON|TRUE|TRY|TRYCAST|TYPE|TYPEOF|UINTEGER|ULONG|UNLOCK|UNTIL|USHORT|USING|VIEW PRINT|WAIT|WEND|WHEN|WHILE|WIDENING|WITH|WITHEVENTS|WRITE|WRITEONLY|XOR)|\B(?:#CONST|#ELSE|#ELSEIF|#END|#IF))(?:\$|\b)/i,punctuation:/[,;:(){}]/})}AE.displayName="t4-vb";AE.aliases=[];function AE(e){e.register(Ul),e.register(bc),e.languages["t4-vb"]=e.languages["t4-templating"].createT4("vbnet")}kE.displayName="tap";kE.aliases=[];function kE(e){e.register(cc),e.languages.tap={fail:/not ok[^#{\n\r]*/,pass:/ok[^#{\n\r]*/,pragma:/pragma [+-][a-z]+/,bailout:/bail out!.*/i,version:/TAP version \d+/i,plan:/\b\d+\.\.\d+(?: +#.*)?/,subtest:{pattern:/# Subtest(?:: .*)?/,greedy:!0},punctuation:/[{}]/,directive:/#.*/,yamlish:{pattern:/(^[ \t]*)---[\s\S]*?[\r\n][ \t]*\.\.\.$/m,lookbehind:!0,inside:e.languages.yaml,alias:"language-yaml"}}}_E.displayName="tcl";_E.aliases=[];function _E(e){e.languages.tcl={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},string:{pattern:/"(?:[^"\\\r\n]|\\(?:\r\n|[\s\S]))*"/,greedy:!0},variable:[{pattern:/(\$)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/,lookbehind:!0},{pattern:/(\$)\{[^}]+\}/,lookbehind:!0},{pattern:/(^[\t ]*set[ \t]+)(?:::)?(?:[a-zA-Z0-9]+::)*\w+/m,lookbehind:!0}],function:{pattern:/(^[\t ]*proc[ \t]+)\S+/m,lookbehind:!0},builtin:[{pattern:/(^[\t ]*)(?:break|class|continue|error|eval|exit|for|foreach|if|proc|return|switch|while)\b/m,lookbehind:!0},/\b(?:else|elseif)\b/],scope:{pattern:/(^[\t ]*)(?:global|upvar|variable)\b/m,lookbehind:!0,alias:"constant"},keyword:{pattern:/(^[\t ]*|\[)(?:Safe_Base|Tcl|after|append|apply|array|auto_(?:execok|import|load|mkindex|qualify|reset)|automkindex_old|bgerror|binary|catch|cd|chan|clock|close|concat|dde|dict|encoding|eof|exec|expr|fblocked|fconfigure|fcopy|file(?:event|name)?|flush|gets|glob|history|http|incr|info|interp|join|lappend|lassign|lindex|linsert|list|llength|load|lrange|lrepeat|lreplace|lreverse|lsearch|lset|lsort|math(?:func|op)|memory|msgcat|namespace|open|package|parray|pid|pkg_mkIndex|platform|puts|pwd|re_syntax|read|refchan|regexp|registry|regsub|rename|scan|seek|set|socket|source|split|string|subst|tcl(?:_endOfWord|_findLibrary|startOf(?:Next|Previous)Word|test|vars|wordBreak(?:After|Before))|tell|time|tm|trace|unknown|unload|unset|update|uplevel|vwait Line truncated
- a positive number
- less than the value passed to \`max\` (or ${uv} if no \`max\` prop is set)
- less than the value passed to \`max\` (or ${dv} if no \`max\` prop is set)
- \`null\` or \`undefined\` if the progress is indeterminate.
Defaulting to \`null\`.`}var h3=zR,b3=$R;function y3({className:e,value:t,...r}){return g.jsx(h3,{"data-slot":"progress",className:be("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",e),...r,children:g.jsx(b3,{"data-slot":"progress-indicator",className:"bg-primary h-full w-full flex-1 transition-all",style:{transform:`translateX(-${100-(t||0)}%)`}})})}function Bl(e){const t=S.useRef({value:e,previous:e});return S.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}function zl(e){const[t,r]=S.useState(void 0);return gt(()=>{if(e){r({width:e.offsetWidth,height:e.offsetHeight});const a=new ResizeObserver(o=>{if(!Array.isArray(o)||!o.length)return;const s=o[0];let u,d;if("borderBoxSize"in s){const p=s.borderBoxSize,f=Array.isArray(p)?p[0]:p;u=f.inlineSize,d=f.blockSize}else u=e.offsetWidth,d=e.offsetHeight;r({width:u,height:d})});return a.observe(e,{box:"border-box"}),()=>a.unobserve(e)}else r(void 0)},[e]),t}var Tc="Switch",[E3]=jt(Tc),[v3,S3]=E3(Tc),HR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,name:a,checked:o,defaultChecked:s,required:u,disabled:d,value:p="on",onCheckedChange:f,form:h,...b}=e,[v,E]=S.useState(null),w=_e(t,R=>E(R)),T=S.useRef(!1),k=v?h||!!v.closest("form"):!0,[A,_]=en({prop:o,defaultProp:s??!1,onChange:f,caller:Tc});return g.jsxs(v3,{scope:r,checked:A,disabled:d,children:[g.jsx(Se.button,{type:"button",role:"switch","aria-checked":A,"aria-required":u,"data-state":WR(A),"data-disabled":d?"":void 0,disabled:d,value:p,...b,ref:w,onClick:ge(e.onClick,R=>{_(L=>!L),k&&(T.current=R.isPropagationStopped(),T.current||R.stopPropagation())})}),k&&g.jsx(YR,{control:v,bubbles:!T.current,name:a,value:p,checked:A,required:u,disabled:d,form:h,style:{transform:"translateX(-100%)"}})]})});HR.displayName=Tc;var VR="SwitchThumb",qR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,...a}=e,o=S3(VR,r);return g.jsx(Se.span,{"data-state":WR(o.checked),"data-disabled":o.disabled?"":void 0,...a,ref:t})});qR.displayName=VR;var x3="SwitchBubbleInput",YR=S.forwardRef(({__scopeSwitch:e,control:t,checked:r,bubbles:a=!0,...o},s)=>{const u=S.useRef(null),d=_e(u,s),p=Bl(r),f=zl(t);return S.useEffect(()=>{const h=u.current;if(!h)return;const b=window.HTMLInputElement.prototype,E=Object.getOwnPropertyDescriptor(b,"checked").set;if(p!==r&&E){const w=new Event("click",{bubbles:a});E.call(h,r),h.dispatchEvent(w)}},[p,r,a]),g.jsx("input",{type:"checkbox","aria-hidden":!0,defaultChecked:r,...o,tabIndex:-1,ref:d,style:{...o.style,...f,position:"absolute",pointerEvents:"none",opacity:0,margin:0}})});YR.displayName=x3;function WR(e){return e?"checked":"unchecked"}var w3=HR,T3=qR;function A3({className:e,size:t="default",...r}){return g.jsx(w3,{"data-slot":"switch","data-size":t,className:be("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",e),...r,children:g.jsx(T3,{"data-slot":"switch-thumb",className:be("bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")})})}var Ac="Checkbox",[k3]=jt(Ac),[_3,cv]=k3(Ac);function N3(e){const{__scopeCheckbox:t,checked:r,children:a,defaultChecked:o,disabled:s,form:u,name:d,onCheckedChange:p,required:f,value:h="on",internal_do_not_use_render:b}=e,[v,E]=en({prop:r,defaultProp:o??!1,onChange:p,caller:Ac}),[w,T]=S.useState(null),[k,A]=S.useState(null),_=S.useRef(!1),R=w?!!u||!!w.closest("form"):!0,L={checked:v,disabled:s,setChecked:E,control:w,setControl:T,name:d,form:u,value:h,hasConsumerStoppedPropagationRef:_,required:f,defaultChecked:la(o)?!1:o,isFormControl:R,bubbleInput:k,setBubbleInput:A};return g.jsx(_3,{scope:t,...L,children:R3(b)?b(L):a})}var KR="CheckboxTrigger",XR=S.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:r,...a},o)=>{const{control:s,value:u,disabled:d,checked:p,required:f,setControl:h,setChecked:b,hasConsumerStoppedPropagationRef:v,isFormControl:E,bubbleInput:w}=cv(KR,e),T=_e(o,h),k=S.useRef(p);return S.useEffect(()=>{const A=s?.form;if(A){const _=()=>b(k.current);return A.addEventListener("reset",_),()=>A.removeEventListener("reset",_)}},[s,b]),g.jsx(Se.button,{type:"button",role:"checkbox","aria-checked":la(p)?"mixed":p,"aria-required":f,"data-state":nC(p),"data-disabled":d?"":void 0,disabled:d,value:u,...a,ref:T,onKeyDown:ge(t,A=>{A.key==="Enter"&&A.preventDefault()}),onClick:ge(r,A=>{b(_=>la(_)?!0:!_),w&&E&&(v.current=A.isPropagatiLine truncated
.`.concat(CG,` {
Defaulting to \`null\`.`}var b3=PR,y3=GR;function E3({className:e,value:t,...r}){return g.jsx(b3,{"data-slot":"progress",className:be("bg-primary/20 relative h-2 w-full overflow-hidden rounded-full",e),...r,children:g.jsx(y3,{"data-slot":"progress-indicator",className:"bg-primary h-full w-full flex-1 transition-all",style:{transform:`translateX(-${100-(t||0)}%)`}})})}function Bl(e){const t=S.useRef({value:e,previous:e});return S.useMemo(()=>(t.current.value!==e&&(t.current.previous=t.current.value,t.current.value=e),t.current.previous),[e])}function zl(e){const[t,r]=S.useState(void 0);return gt(()=>{if(e){r({width:e.offsetWidth,height:e.offsetHeight});const a=new ResizeObserver(o=>{if(!Array.isArray(o)||!o.length)return;const s=o[0];let u,d;if("borderBoxSize"in s){const p=s.borderBoxSize,f=Array.isArray(p)?p[0]:p;u=f.inlineSize,d=f.blockSize}else u=e.offsetWidth,d=e.offsetHeight;r({width:u,height:d})});return a.observe(e,{box:"border-box"}),()=>a.unobserve(e)}else r(void 0)},[e]),t}var Ac="Switch",[v3]=jt(Ac),[S3,x3]=v3(Ac),VR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,name:a,checked:o,defaultChecked:s,required:u,disabled:d,value:p="on",onCheckedChange:f,form:h,...b}=e,[v,E]=S.useState(null),w=_e(t,R=>E(R)),T=S.useRef(!1),k=v?h||!!v.closest("form"):!0,[A,_]=en({prop:o,defaultProp:s??!1,onChange:f,caller:Ac});return g.jsxs(S3,{scope:r,checked:A,disabled:d,children:[g.jsx(Se.button,{type:"button",role:"switch","aria-checked":A,"aria-required":u,"data-state":KR(A),"data-disabled":d?"":void 0,disabled:d,value:p,...b,ref:w,onClick:ge(e.onClick,R=>{_(L=>!L),k&&(T.current=R.isPropagationStopped(),T.current||R.stopPropagation())})}),k&&g.jsx(WR,{control:v,bubbles:!T.current,name:a,value:p,checked:A,required:u,disabled:d,form:h,style:{transform:"translateX(-100%)"}})]})});VR.displayName=Ac;var qR="SwitchThumb",YR=S.forwardRef((e,t)=>{const{__scopeSwitch:r,...a}=e,o=x3(qR,r);return g.jsx(Se.span,{"data-state":KR(o.checked),"data-disabled":o.disabled?"":void 0,...a,ref:t})});YR.displayName=qR;var w3="SwitchBubbleInput",WR=S.forwardRef(({__scopeSwitch:e,control:t,checked:r,bubbles:a=!0,...o},s)=>{const u=S.useRef(null),d=_e(u,s),p=Bl(r),f=zl(t);return S.useEffect(()=>{const h=u.current;if(!h)return;const b=window.HTMLInputElement.prototype,E=Object.getOwnPropertyDescriptor(b,"checked").set;if(p!==r&&E){const w=new Event("click",{bubbles:a});E.call(h,r),h.dispatchEvent(w)}},[p,r,a]),g.jsx("input",{type:"checkbox","aria-hidden":!0,defaultChecked:r,...o,tabIndex:-1,ref:d,style:{...o.style,...f,position:"absolute",pointerEvents:"none",opacity:0,margin:0}})});WR.displayName=w3;function KR(e){return e?"checked":"unchecked"}var T3=VR,A3=YR;function k3({className:e,size:t="default",...r}){return g.jsx(T3,{"data-slot":"switch","data-size":t,className:be("peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 group/switch inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-[1.15rem] data-[size=default]:w-8 data-[size=sm]:h-3.5 data-[size=sm]:w-6",e),...r,children:g.jsx(A3,{"data-slot":"switch-thumb",className:be("bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block rounded-full ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0")})})}var kc="Checkbox",[_3]=jt(kc),[N3,pv]=_3(kc);function R3(e){const{__scopeCheckbox:t,checked:r,children:a,defaultChecked:o,disabled:s,form:u,name:d,onCheckedChange:p,required:f,value:h="on",internal_do_not_use_render:b}=e,[v,E]=en({prop:r,defaultProp:o??!1,onChange:p,caller:kc}),[w,T]=S.useState(null),[k,A]=S.useState(null),_=S.useRef(!1),R=w?!!u||!!w.closest("form"):!0,L={checked:v,disabled:s,setChecked:E,control:w,setControl:T,name:d,form:u,value:h,hasConsumerStoppedPropagationRef:_,required:f,defaultChecked:la(o)?!1:o,isFormControl:R,bubbleInput:k,setBubbleInput:A};return g.jsx(N3,{scope:t,...L,children:C3(b)?b(L):a})}var XR="CheckboxTrigger",ZR=S.forwardRef(({__scopeCheckbox:e,onKeyDown:t,onClick:r,...a},o)=>{const{control:s,value:u,disabled:d,checked:p,required:f,setControl:h,setChecked:b,hasConsumerStoppedPropagationRef:v,isFormControl:E,bubbleInput:w}=pv(XR,e),T=_e(o,h),k=S.useRef(p);return S.useEffect(()=>{const A=s?.form;if(A){const _=()=>b(k.current);return A.addEventListener("reset",_),()=>A.removeEventListener("reset",_)}},[s,b]),g.jsx(Se.button,{type:"button",role:"checkbox","aria-checked":la(p)?"mixed":p,"aria-required":f,"data-state":rC(p),"data-disabled":d?"":void 0,disabled:d,value:u,...a,ref:T,onKeyDown:ge(t,A=>{A.key==="Enter"&&A.preventDefault()}),onClick:ge(r,A=>{b(_=>la(_)?!0:!_),w&&E&&(v.current=A.isPropagatiLine truncated
.`.concat(OG,` {
overflow: hidden `).concat(a,`;
padding-right: `).concat(d,"px ").concat(a,`;
}
@@ -71,37 +72,37 @@ Defaulting to \`null\`.`}var h3=zR,b3=$R;function y3({className:e,value:t,...r})
`),r==="padding"&&"padding-right: ".concat(d,"px ").concat(a,";")].filter(Boolean).join(""),`
}
.`).concat(Ou,` {
.`).concat(Iu,` {
right: `).concat(d,"px ").concat(a,`;
}
.`).concat(Iu,` {
.`).concat(Lu,` {
margin-right: `).concat(d,"px ").concat(a,`;
}
.`).concat(Ou," .").concat(Ou,` {
.`).concat(Iu," .").concat(Iu,` {
right: 0 `).concat(a,`;
}
.`).concat(Iu," .").concat(Iu,` {
.`).concat(Lu," .").concat(Lu,` {
margin-right: 0 `).concat(a,`;
}
body[`).concat(qi,`] {
`).concat(OG,": ").concat(d,`px;
`).concat(IG,": ").concat(d,`px;
}
`)},Lk=function(){var e=parseInt(document.body.getAttribute(qi)||"0",10);return isFinite(e)?e:0},XG=function(){S.useEffect(function(){return document.body.setAttribute(qi,(Lk()+1).toString()),function(){var e=Lk()-1;e<=0?document.body.removeAttribute(qi):document.body.setAttribute(qi,e.toString())}},[])},ZG=function(e){var t=e.noRelative,r=e.noImportant,a=e.gapMode,o=a===void 0?"margin":a;XG();var s=S.useMemo(function(){return YG(o)},[o]);return S.createElement(WG,{styles:KG(s,!t,o,r?"":"!important")})},mg=!1;if(typeof window<"u")try{var Tu=Object.defineProperty({},"passive",{get:function(){return mg=!0,!0}});window.addEventListener("test",Tu,Tu),window.removeEventListener("test",Tu,Tu)}catch{mg=!1}var ji=mg?{passive:!1}:!1,QG=function(e){return e.tagName==="TEXTAREA"},qC=function(e,t){if(!(e instanceof Element))return!1;var r=window.getComputedStyle(e);return r[t]!=="hidden"&&!(r.overflowY===r.overflowX&&!QG(e)&&r[t]==="visible")},JG=function(e){return qC(e,"overflowY")},eH=function(e){return qC(e,"overflowX")},Dk=function(e,t){var r=t.ownerDocument,a=t;do{typeof ShadowRoot<"u"&&a instanceof ShadowRoot&&(a=a.host);var o=YC(e,a);if(o){var s=WC(e,a),u=s[1],d=s[2];if(u>d)return!0}a=a.parentNode}while(a&&a!==r.body);return!1},tH=function(e){var t=e.scrollTop,r=e.scrollHeight,a=e.clientHeight;return[t,r,a]},nH=function(e){var t=e.scrollLeft,r=e.scrollWidth,a=e.clientWidth;return[t,r,a]},YC=function(e,t){return e==="v"?JG(t):eH(t)},WC=function(e,t){return e==="v"?tH(t):nH(t)},rH=function(e,t){return e==="h"&&t==="rtl"?-1:1},aH=function(e,t,r,a,o){var s=rH(e,window.getComputedStyle(t).direction),u=s*a,d=r.target,p=t.contains(d),f=!1,h=u>0,b=0,v=0;do{if(!d)break;var E=WC(e,d),w=E[0],T=E[1],k=E[2],A=T-k-s*w;(w||A)&&YC(e,d)&&(b+=A,v+=w);var _=d.parentNode;d=_&&_.nodeType===Node.DOCUMENT_FRAGMENT_NODE?_.host:_}while(!p&&d!==document.body||p&&(t.contains(d)||t===d));return(h&&Math.abs(b)<1||!h&&Math.abs(v)<1)&&(f=!0),f},Au=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},Mk=function(e){return[e.deltaX,e.deltaY]},jk=function(e){return e&&"current"in e?e.current:e},iH=function(e,t){return e[0]===t[0]&&e[1]===t[1]},oH=function(e){return`
`)},Dk=function(){var e=parseInt(document.body.getAttribute(qi)||"0",10);return isFinite(e)?e:0},ZG=function(){S.useEffect(function(){return document.body.setAttribute(qi,(Dk()+1).toString()),function(){var e=Dk()-1;e<=0?document.body.removeAttribute(qi):document.body.setAttribute(qi,e.toString())}},[])},QG=function(e){var t=e.noRelative,r=e.noImportant,a=e.gapMode,o=a===void 0?"margin":a;ZG();var s=S.useMemo(function(){return WG(o)},[o]);return S.createElement(KG,{styles:XG(s,!t,o,r?"":"!important")})},bg=!1;if(typeof window<"u")try{var Tu=Object.defineProperty({},"passive",{get:function(){return bg=!0,!0}});window.addEventListener("test",Tu,Tu),window.removeEventListener("test",Tu,Tu)}catch{bg=!1}var Ui=bg?{passive:!1}:!1,JG=function(e){return e.tagName==="TEXTAREA"},YC=function(e,t){if(!(e instanceof Element))return!1;var r=window.getComputedStyle(e);return r[t]!=="hidden"&&!(r.overflowY===r.overflowX&&!JG(e)&&r[t]==="visible")},eH=function(e){return YC(e,"overflowY")},tH=function(e){return YC(e,"overflowX")},Mk=function(e,t){var r=t.ownerDocument,a=t;do{typeof ShadowRoot<"u"&&a instanceof ShadowRoot&&(a=a.host);var o=WC(e,a);if(o){var s=KC(e,a),u=s[1],d=s[2];if(u>d)return!0}a=a.parentNode}while(a&&a!==r.body);return!1},nH=function(e){var t=e.scrollTop,r=e.scrollHeight,a=e.clientHeight;return[t,r,a]},rH=function(e){var t=e.scrollLeft,r=e.scrollWidth,a=e.clientWidth;return[t,r,a]},WC=function(e,t){return e==="v"?eH(t):tH(t)},KC=function(e,t){return e==="v"?nH(t):rH(t)},aH=function(e,t){return e==="h"&&t==="rtl"?-1:1},iH=function(e,t,r,a,o){var s=aH(e,window.getComputedStyle(t).direction),u=s*a,d=r.target,p=t.contains(d),f=!1,h=u>0,b=0,v=0;do{if(!d)break;var E=KC(e,d),w=E[0],T=E[1],k=E[2],A=T-k-s*w;(w||A)&&WC(e,d)&&(b+=A,v+=w);var _=d.parentNode;d=_&&_.nodeType===Node.DOCUMENT_FRAGMENT_NODE?_.host:_}while(!p&&d!==document.body||p&&(t.contains(d)||t===d));return(h&&Math.abs(b)<1||!h&&Math.abs(v)<1)&&(f=!0),f},Au=function(e){return"changedTouches"in e?[e.changedTouches[0].clientX,e.changedTouches[0].clientY]:[0,0]},jk=function(e){return[e.deltaX,e.deltaY]},Uk=function(e){return e&&"current"in e?e.current:e},oH=function(e,t){return e[0]===t[0]&&e[1]===t[1]},lH=function(e){return`
.block-interactivity-`.concat(e,` {pointer-events: none;}
.allow-interactivity-`).concat(e,` {pointer-events: all;}
`)},lH=0,Ui=[];function sH(e){var t=S.useRef([]),r=S.useRef([0,0]),a=S.useRef(),o=S.useState(lH++)[0],s=S.useState(VC)[0],u=S.useRef(e);S.useEffect(function(){u.current=e},[e]),S.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var T=RG([e.lockRef.current],(e.shards||[]).map(jk),!0).filter(Boolean);return T.forEach(function(k){return k.classList.add("allow-interactivity-".concat(o))}),function(){document.body.classList.remove("block-interactivity-".concat(o)),T.forEach(function(k){return k.classList.remove("allow-interactivity-".concat(o))})}}},[e.inert,e.lockRef.current,e.shards]);var d=S.useCallback(function(T,k){if("touches"in T&&T.touches.length===2||T.type==="wheel"&&T.ctrlKey)return!u.current.allowPinchZoom;var A=Au(T),_=r.current,R="deltaX"in T?T.deltaX:_[0]-A[0],L="deltaY"in T?T.deltaY:_[1]-A[1],F,D=T.target,P=Math.abs(R)>Math.abs(L)?"h":"v";if("touches"in T&&P==="h"&&D.type==="range")return!1;var z=window.getSelection(),W=z&&z.anchorNode,M=W?W===D||W.contains(D):!1;if(M)return!1;var H=Dk(P,D);if(!H)return!0;if(H?F=P:(F=P==="v"?"h":"v",H=Dk(P,D)),!H)return!1;if(!a.current&&"changedTouches"in T&&(R||L)&&(a.current=F),!F)return!0;var X=a.current||F;return aH(X,k,T,X==="h"?R:L)},[]),p=S.useCallback(function(T){var k=T;if(!(!Ui.length||Ui[Ui.length-1]!==s)){var A="deltaY"in k?Mk(k):Au(k),_=t.current.filter(function(F){return F.name===k.type&&(F.target===k.target||k.target===F.shadowParent)&&iH(F.delta,A)})[0];if(_&&_.should){k.cancelable&&k.preventDefault();return}if(!_){var R=(u.current.shards||[]).map(jk).filter(Boolean).filter(function(F){return F.contains(k.target)}),L=R.length>0?d(k,R[0]):!u.current.noIsolation;L&&k.cancelable&&k.preventDefault()}}},[]),f=S.useCallback(function(T,k,A,_){var R={name:T,delta:k,target:A,should:_,shadowParent:uH(A)};t.current.push(R),setTimeout(function(){t.current=t.current.filter(function(L){return L!==R})},1)},[]),h=S.useCallback(function(T){r.current=Au(T),a.current=void 0},[]),b=S.useCallback(function(T){f(T.type,Mk(T),T.target,d(T,e.lockRef.current))},[]),v=S.useCallback(function(T){f(T.type,Au(T),T.target,d(T,e.lockRef.current))},[]);S.useEffect(function(){return Ui.push(s),e.setCallbacks({onScrollCapture:b,onWheelCapture:b,onTouchMoveCapture:v}),document.addEventListener("wheel",p,ji),document.addEventListener("touchmove",p,ji),document.addEventListener("touchstart",h,ji),function(){Ui=Ui.filter(function(T){return T!==s}),document.removeEventListener("wheel",p,ji),document.removeEventListener("touchmove",p,ji),document.removeEventListener("touchstart",h,ji)}},[]);var E=e.removeScrollBar,w=e.inert;return S.createElement(S.Fragment,null,w?S.createElement(s,{styles:oH(o)}):null,E?S.createElement(ZG,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function uH(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const cH=FG(HC,sH);var Dc=S.forwardRef(function(e,t){return S.createElement(Lc,Jn({},e,{ref:t,sideCar:cH}))});Dc.classNames=Lc.classNames;var dH=[" ","Enter","ArrowUp","ArrowDown"],pH=[" ","Enter"],Ba="Select",[Mc,jc,fH]=bc(Ba),[uo]=jt(Ba,[fH,so]),Uc=so(),[gH,pa]=uo(Ba),[mH,hH]=uo(Ba),KC=e=>{const{__scopeSelect:t,children:r,open:a,defaultOpen:o,onOpenChange:s,value:u,defaultValue:d,onValueChange:p,dir:f,name:h,autoComplete:b,disabled:v,required:E,form:w}=e,T=Uc(t),[k,A]=S.useState(null),[_,R]=S.useState(null),[L,F]=S.useState(!1),D=Ga(f),[P,z]=en({prop:a,defaultProp:o??!1,onChange:s,caller:Ba}),[W,M]=en({prop:u,defaultProp:d,onChange:p,caller:Ba}),H=S.useRef(null),X=k?w||!!k.closest("form"):!0,[ee,te]=S.useState(new Set),Z=Array.from(ee).map(B=>B.props.value).join(";");return g.jsx(Sv,{...T,children:g.jsxs(gH,{required:E,scope:t,trigger:k,onTriggerChange:A,valueNode:_,onValueNodeChange:R,valueNodeHasChildren:L,onValueNodeHasChildrenChange:F,contentId:Rn(),value:W,onValueChange:M,open:P,onOpenChange:z,dir:D,triggerPointerDownPosRef:H,disabled:v,children:[g.jsx(Mc.Provider,{scope:t,children:g.jsx(mH,{scope:e.__scopeSelect,onNativeOptionAdd:S.useCallback(B=>{te(Y=>new Set(Y).add(B))},[]),onNativeOptionRemove:S.useCallback(B=>{te(Y=>{const ne=new Set(Y);return ne.delete(B),ne})},[]),children:r})}),X?g.jsxs(b0,{"aria-hidden":!0,required:E,tabIndex:-1,name:h,autoComplete:b,value:W,onChange:B=>M(B.target.value),disabled:v,form:w,children:[W===void 0?g.jsx("option",{value:""}):null,Array.from(ee)]},Z):null]})})};KC.displayName=Ba;var XC="SelectTrigger",ZC=S.forwardRef((e,t)=>{const{__scopeSelect:r,disabled:a=!1,...o}=e,s=Uc(r),u=pa(XC,r),d=u.disabled||a,p=_e(t,u.onTriggerChange),f=jc(r),h=S.useRef("touch"),[b,v,E]=E0(T=>{const k=f().filter(R=>!R.disabled),A=k.find(R=>R.value===u.value),_=v0(k,T,A);_!==void 0&&u.onValueChange(_.value)}),w=T=>{d||(u.onOpenChange(!0),E()),T&&(u.triggerPointerDownPosRef.current={x:Math.round(T.pageX),y:Math.round(T.pageY)})};return g.jsx(Ic,{asChild:!0,...s,children:g.jsx(Se.button,{type:"button",role:"combobox","aria-controls":u.contentId,"aria Line truncated
`)},sH=0,Fi=[];function uH(e){var t=S.useRef([]),r=S.useRef([0,0]),a=S.useRef(),o=S.useState(sH++)[0],s=S.useState(qC)[0],u=S.useRef(e);S.useEffect(function(){u.current=e},[e]),S.useEffect(function(){if(e.inert){document.body.classList.add("block-interactivity-".concat(o));var T=CG([e.lockRef.current],(e.shards||[]).map(Uk),!0).filter(Boolean);return T.forEach(function(k){return k.classList.add("allow-interactivity-".concat(o))}),function(){document.body.classList.remove("block-interactivity-".concat(o)),T.forEach(function(k){return k.classList.remove("allow-interactivity-".concat(o))})}}},[e.inert,e.lockRef.current,e.shards]);var d=S.useCallback(function(T,k){if("touches"in T&&T.touches.length===2||T.type==="wheel"&&T.ctrlKey)return!u.current.allowPinchZoom;var A=Au(T),_=r.current,R="deltaX"in T?T.deltaX:_[0]-A[0],L="deltaY"in T?T.deltaY:_[1]-A[1],j,D=T.target,z=Math.abs(R)>Math.abs(L)?"h":"v";if("touches"in T&&z==="h"&&D.type==="range")return!1;var F=window.getSelection(),V=F&&F.anchorNode,M=V?V===D||V.contains(D):!1;if(M)return!1;var H=Mk(z,D);if(!H)return!0;if(H?j=z:(j=z==="v"?"h":"v",H=Mk(z,D)),!H)return!1;if(!a.current&&"changedTouches"in T&&(R||L)&&(a.current=j),!j)return!0;var K=a.current||j;return iH(K,k,T,K==="h"?R:L)},[]),p=S.useCallback(function(T){var k=T;if(!(!Fi.length||Fi[Fi.length-1]!==s)){var A="deltaY"in k?jk(k):Au(k),_=t.current.filter(function(j){return j.name===k.type&&(j.target===k.target||k.target===j.shadowParent)&&oH(j.delta,A)})[0];if(_&&_.should){k.cancelable&&k.preventDefault();return}if(!_){var R=(u.current.shards||[]).map(Uk).filter(Boolean).filter(function(j){return j.contains(k.target)}),L=R.length>0?d(k,R[0]):!u.current.noIsolation;L&&k.cancelable&&k.preventDefault()}}},[]),f=S.useCallback(function(T,k,A,_){var R={name:T,delta:k,target:A,should:_,shadowParent:cH(A)};t.current.push(R),setTimeout(function(){t.current=t.current.filter(function(L){return L!==R})},1)},[]),h=S.useCallback(function(T){r.current=Au(T),a.current=void 0},[]),b=S.useCallback(function(T){f(T.type,jk(T),T.target,d(T,e.lockRef.current))},[]),v=S.useCallback(function(T){f(T.type,Au(T),T.target,d(T,e.lockRef.current))},[]);S.useEffect(function(){return Fi.push(s),e.setCallbacks({onScrollCapture:b,onWheelCapture:b,onTouchMoveCapture:v}),document.addEventListener("wheel",p,Ui),document.addEventListener("touchmove",p,Ui),document.addEventListener("touchstart",h,Ui),function(){Fi=Fi.filter(function(T){return T!==s}),document.removeEventListener("wheel",p,Ui),document.removeEventListener("touchmove",p,Ui),document.removeEventListener("touchstart",h,Ui)}},[]);var E=e.removeScrollBar,w=e.inert;return S.createElement(S.Fragment,null,w?S.createElement(s,{styles:lH(o)}):null,E?S.createElement(QG,{noRelative:e.noRelative,gapMode:e.gapMode}):null)}function cH(e){for(var t=null;e!==null;)e instanceof ShadowRoot&&(t=e.host,e=e.host),e=e.parentNode;return t}const dH=BG(VC,uH);var Mc=S.forwardRef(function(e,t){return S.createElement(Dc,Jn({},e,{ref:t,sideCar:dH}))});Mc.classNames=Dc.classNames;var pH=[" ","Enter","ArrowUp","ArrowDown"],fH=[" ","Enter"],za="Select",[jc,Uc,gH]=yc(za),[uo]=jt(za,[gH,so]),Fc=so(),[mH,pa]=uo(za),[hH,bH]=uo(za),XC=e=>{const{__scopeSelect:t,children:r,open:a,defaultOpen:o,onOpenChange:s,value:u,defaultValue:d,onValueChange:p,dir:f,name:h,autoComplete:b,disabled:v,required:E,form:w}=e,T=Fc(t),[k,A]=S.useState(null),[_,R]=S.useState(null),[L,j]=S.useState(!1),D=Ha(f),[z,F]=en({prop:a,defaultProp:o??!1,onChange:s,caller:za}),[V,M]=en({prop:u,defaultProp:d,onChange:p,caller:za}),H=S.useRef(null),K=k?w||!!k.closest("form"):!0,[Z,te]=S.useState(new Set),Q=Array.from(Z).map(P=>P.props.value).join(";");return g.jsx(wv,{...T,children:g.jsxs(mH,{required:E,scope:t,trigger:k,onTriggerChange:A,valueNode:_,onValueNodeChange:R,valueNodeHasChildren:L,onValueNodeHasChildrenChange:j,contentId:Rn(),value:V,onValueChange:M,open:z,onOpenChange:F,dir:D,triggerPointerDownPosRef:H,disabled:v,children:[g.jsx(jc.Provider,{scope:t,children:g.jsx(hH,{scope:e.__scopeSelect,onNativeOptionAdd:S.useCallback(P=>{te(W=>new Set(W).add(P))},[]),onNativeOptionRemove:S.useCallback(P=>{te(W=>{const ne=new Set(W);return ne.delete(P),ne})},[]),children:r})}),K?g.jsxs(y0,{"aria-hidden":!0,required:E,tabIndex:-1,name:h,autoComplete:b,value:V,onChange:P=>M(P.target.value),disabled:v,form:w,children:[V===void 0?g.jsx("option",{value:""}):null,Array.from(Z)]},Q):null]})})};XC.displayName=za;var ZC="SelectTrigger",QC=S.forwardRef((e,t)=>{const{__scopeSelect:r,disabled:a=!1,...o}=e,s=Fc(r),u=pa(ZC,r),d=u.disabled||a,p=_e(t,u.onTriggerChange),f=Uc(r),h=S.useRef("touch"),[b,v,E]=v0(T=>{const k=f().filter(R=>!R.disabled),A=k.find(R=>R.value===u.value),_=S0(k,T,A);_!==void 0&&u.onValueChange(_.value)}),w=T=>{d||(u.onOpenChange(!0),E()),T&&(u.triggerPointerDownPosRef.current={x:Math.round(T.pageX),y:Math.round(T.pageY)})};return g.jsx(Lc,{asChild:!0,...s,children:g.jsx(Se.button,{type:"button",role:"combobox","aria-controls":u.contentId,"aria-ex Line truncated
If you want to hide the \`${t.titleName}\`, you can wrap it with our VisuallyHidden component.
For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return S.useEffect(()=>{e&&(document.getElementById(e)||console.error(r))},[r,e]),null},I5="DialogDescriptionWarning",L5=({contentRef:e,descriptionId:t})=>{const a=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${X0(I5).contentName}}.`;return S.useEffect(()=>{const o=e.current?.getAttribute("aria-describedby");t&&o&&(document.getElementById(t)||console.warn(a))},[a,e,t]),null},D5=j0,M5=F0,j5=z0,U5=P0,F5=$0,B5=H0,z5=q0,P5=W0;function $5({...e}){return g.jsx(D5,{"data-slot":"dialog",...e})}function G5({...e}){return g.jsx(M5,{"data-slot":"dialog-trigger",...e})}function H5({...e}){return g.jsx(j5,{"data-slot":"dialog-portal",...e})}function V5({className:e,...t}){return g.jsx(U5,{"data-slot":"dialog-overlay",className:be("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",e),...t})}function q5({className:e,children:t,showCloseButton:r=!0,...a}){return g.jsxs(H5,{"data-slot":"dialog-portal",children:[g.jsx(V5,{}),g.jsxs(F5,{"data-slot":"dialog-content",className:be("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",e),...a,children:[t,r&&g.jsxs(P5,{"data-slot":"dialog-close",className:"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",children:[g.jsx(B$,{}),g.jsx("span",{className:"sr-only",children:"Close"})]})]})]})}function Y5({className:e,...t}){return g.jsx("div",{"data-slot":"dialog-header",className:be("flex flex-col gap-2 text-center sm:text-left",e),...t})}function W5({className:e,...t}){return g.jsx(B5,{"data-slot":"dialog-title",className:be("text-lg leading-none font-semibold",e),...t})}function K5({className:e,...t}){return g.jsx(z5,{"data-slot":"dialog-description",className:be("text-muted-foreground text-sm",e),...t})}var X5=Symbol("radix.slottable");function Z5(e){const t=({children:r})=>g.jsx(g.Fragment,{children:r});return t.displayName=`${e}.Slottable`,t.__radixId=X5,t}var[zc]=jt("Tooltip",[so]),Pc=so(),Z0="TooltipProvider",Q5=700,Tg="tooltip.open",[J5,Cv]=zc(Z0),Q0=e=>{const{__scopeTooltip:t,delayDuration:r=Q5,skipDelayDuration:a=300,disableHoverableContent:o=!1,children:s}=e,u=S.useRef(!0),d=S.useRef(!1),p=S.useRef(0);return S.useEffect(()=>{const f=p.current;return()=>window.clearTimeout(f)},[]),g.jsx(J5,{scope:t,isOpenDelayedRef:u,delayDuration:r,onOpen:S.useCallback(()=>{window.clearTimeout(p.current),u.current=!1},[]),onClose:S.useCallback(()=>{window.clearTimeout(p.current),p.current=window.setTimeout(()=>u.current=!0,a)},[a]),isPointerInTransitRef:d,onPointerInTransitChange:S.useCallback(f=>{d.current=f},[]),disableHoverableContent:o,children:s})};Q0.displayName=Z0;var kl="Tooltip",[e6,Hl]=zc(kl),J0=e=>{const{__scopeTooltip:t,children:r,open:a,defaultOpen:o,onOpenChange:s,disableHoverableContent:u,delayDuration:d}=e,p=Cv(kl,e.__scopeTooltip),f=Pc(t),[h,b]=S.useState(null),v=Rn(),E=S.useRef(0),w=u??p.disableHoverableContent,T=d??p.delayDuration,k=S.useRef(!1),[A,_]=en({prop:a,defaultProp:o??!1,onChange:P=>{P?(p.onOpen(),document.dispatchEvent(new CustomEvent(Tg))):p.onClose(),s?.(P)},caller:kl}),R=S.useMemo(()=>A?k.current?"delayed-open":"instant-open":"closed",[A]),L=S.useCallback(()=>{window.clearTimeout(E.current),E.current=0,k.current=!1,_(!0)},[_]),F=S.useCallback(()=>{window.clearTimeout(E.current),E.current=0,_(!1)},[_]),D=S.useCallback(()=>{window.clearTimeout(E.current),E.current=window.setTimeout(()=>{k.current=!0,_(!0),E.current=0},T)},[T,_]);return S.useEffect(()=>()=>{E.current&&(window.clearTimeout(E.current),E.current=0)},[]),g.jsx(Sv,{...f,children:g.jsx(e6,{scope:t,contentId:v,open:A,stateAttribute:R,trigger:h,onTriggerChange:b,onTriggerEnter:S.useCallback(()=>{p.isOpenDelayedRef.current?D():L()},[p.isOpenDelayedRef,D,L]),onTriggerLeave:S.useCallback(()=>{w?F():(window.clearTimeout(E.current),E.current=0)},[F,w]),onOpen:L,onClose:F,disableHoverableContent:w,children:r})})};J0.displayName=kl;var Ag="TooltipTrigger",eO=S.forwardRef((e,t)=>{const{__scopeTooltip:r,...a}=e,o=Hl(Ag,r),s=Cv(Ag,r),u=Pc(r),d=S.useRef(null),p=_e(t,d,o.onTriggerChange),f=S.useRef(!1),h=S.useRef(!1),b=S.useCallback(()=>f.current=!1,[]);return S.useEffect(()=>()=>document.removeEventListener("pointerup",b),[b]),g.jsx(Ic,{asChild:!0,...u,children:g.jsx(Se.button, Line truncated
For more information, see https://radix-ui.com/primitives/docs/components/${t.docsSlug}`;return S.useEffect(()=>{e&&(document.getElementById(e)||console.error(r))},[r,e]),null},L5="DialogDescriptionWarning",D5=({contentRef:e,descriptionId:t})=>{const a=`Warning: Missing \`Description\` or \`aria-describedby={undefined}\` for {${Z0(L5).contentName}}.`;return S.useEffect(()=>{const o=e.current?.getAttribute("aria-describedby");t&&o&&(document.getElementById(t)||console.warn(a))},[a,e,t]),null},M5=U0,j5=B0,U5=P0,F5=$0,B5=G0,z5=V0,P5=Y0,$5=K0;function G5({...e}){return g.jsx(M5,{"data-slot":"dialog",...e})}function H5({...e}){return g.jsx(j5,{"data-slot":"dialog-trigger",...e})}function V5({...e}){return g.jsx(U5,{"data-slot":"dialog-portal",...e})}function q5({className:e,...t}){return g.jsx(F5,{"data-slot":"dialog-overlay",className:be("data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",e),...t})}function Y5({className:e,children:t,showCloseButton:r=!0,...a}){return g.jsxs(V5,{"data-slot":"dialog-portal",children:[g.jsx(q5,{}),g.jsxs(B5,{"data-slot":"dialog-content",className:be("bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border p-6 shadow-lg duration-200 outline-none sm:max-w-lg",e),...a,children:[t,r&&g.jsxs($5,{"data-slot":"dialog-close",className:"ring-offset-background focus:ring-ring data-[state=open]:bg-accent data-[state=open]:text-muted-foreground absolute top-4 right-4 rounded-xs opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",children:[g.jsx(z$,{}),g.jsx("span",{className:"sr-only",children:"Close"})]})]})]})}function W5({className:e,...t}){return g.jsx("div",{"data-slot":"dialog-header",className:be("flex flex-col gap-2 text-center sm:text-left",e),...t})}function K5({className:e,...t}){return g.jsx(z5,{"data-slot":"dialog-title",className:be("text-lg leading-none font-semibold",e),...t})}function X5({className:e,...t}){return g.jsx(P5,{"data-slot":"dialog-description",className:be("text-muted-foreground text-sm",e),...t})}var Z5=Symbol("radix.slottable");function Q5(e){const t=({children:r})=>g.jsx(g.Fragment,{children:r});return t.displayName=`${e}.Slottable`,t.__radixId=Z5,t}var[Pc]=jt("Tooltip",[so]),$c=so(),Q0="TooltipProvider",J5=700,kg="tooltip.open",[e6,Iv]=Pc(Q0),J0=e=>{const{__scopeTooltip:t,delayDuration:r=J5,skipDelayDuration:a=300,disableHoverableContent:o=!1,children:s}=e,u=S.useRef(!0),d=S.useRef(!1),p=S.useRef(0);return S.useEffect(()=>{const f=p.current;return()=>window.clearTimeout(f)},[]),g.jsx(e6,{scope:t,isOpenDelayedRef:u,delayDuration:r,onOpen:S.useCallback(()=>{window.clearTimeout(p.current),u.current=!1},[]),onClose:S.useCallback(()=>{window.clearTimeout(p.current),p.current=window.setTimeout(()=>u.current=!0,a)},[a]),isPointerInTransitRef:d,onPointerInTransitChange:S.useCallback(f=>{d.current=f},[]),disableHoverableContent:o,children:s})};J0.displayName=Q0;var kl="Tooltip",[t6,Hl]=Pc(kl),eO=e=>{const{__scopeTooltip:t,children:r,open:a,defaultOpen:o,onOpenChange:s,disableHoverableContent:u,delayDuration:d}=e,p=Iv(kl,e.__scopeTooltip),f=$c(t),[h,b]=S.useState(null),v=Rn(),E=S.useRef(0),w=u??p.disableHoverableContent,T=d??p.delayDuration,k=S.useRef(!1),[A,_]=en({prop:a,defaultProp:o??!1,onChange:z=>{z?(p.onOpen(),document.dispatchEvent(new CustomEvent(kg))):p.onClose(),s?.(z)},caller:kl}),R=S.useMemo(()=>A?k.current?"delayed-open":"instant-open":"closed",[A]),L=S.useCallback(()=>{window.clearTimeout(E.current),E.current=0,k.current=!1,_(!0)},[_]),j=S.useCallback(()=>{window.clearTimeout(E.current),E.current=0,_(!1)},[_]),D=S.useCallback(()=>{window.clearTimeout(E.current),E.current=window.setTimeout(()=>{k.current=!0,_(!0),E.current=0},T)},[T,_]);return S.useEffect(()=>()=>{E.current&&(window.clearTimeout(E.current),E.current=0)},[]),g.jsx(wv,{...f,children:g.jsx(t6,{scope:t,contentId:v,open:A,stateAttribute:R,trigger:h,onTriggerChange:b,onTriggerEnter:S.useCallback(()=>{p.isOpenDelayedRef.current?D():L()},[p.isOpenDelayedRef,D,L]),onTriggerLeave:S.useCallback(()=>{w?j():(window.clearTimeout(E.current),E.current=0)},[j,w]),onOpen:L,onClose:j,disableHoverableContent:w,children:r})})};eO.displayName=kl;var _g="TooltipTrigger",tO=S.forwardRef((e,t)=>{const{__scopeTooltip:r,...a}=e,o=Hl(_g,r),s=Iv(_g,r),u=$c(r),d=S.useRef(null),p=_e(t,d,o.onTriggerChange),f=S.useRef(!1),h=S.useRef(!1),b=S.useCallback(()=>f.current=!1,[]);return S.useEffect(()=>()=>document.removeEventListener("pointerup",b),[b]),g.jsx(Lc,{asChild:!0,...u,children:g.jsx(Se.button, Line truncated
`);for(;t.length&&!t[0].trim();)t.shift();for(;t.length&&!t[t.length-1].trim();)t.pop();if(!t.length)return"";const r=Math.min(...t.filter(a=>a.trim().length>0).map(a=>a.match(/^ */)?.[0].length??0));return r===0?t.join(`
`):t.map(a=>a.slice(r)).join(`
`)}function xV({language:e,children:t}){const[r,a]=S.useState(!1),o=S.useCallback(async()=>{await navigator.clipboard.writeText(t),a(!0),setTimeout(()=>a(!1),2e3)},[t]);return g.jsxs("div",{className:"relative group/code my-3 rounded-lg overflow-hidden",children:[g.jsxs("div",{className:"flex items-center justify-between px-4 py-1.5 bg-[#1e1e2e] text-xs text-gray-400",children:[g.jsx("span",{children:e||"code"}),g.jsx("button",{onClick:o,className:"flex items-center gap-1 hover:text-white transition-colors",children:r?"✓ Copied":"Copy"})]}),g.jsx(WN,{style:bP,language:e||"text",PreTag:"div",customStyle:{margin:0,borderRadius:0,fontSize:"13px"},children:t})]})}function wV(e){const t=e.split(`
`)}function wV({language:e,children:t}){const[r,a]=S.useState(!1),o=S.useCallback(async()=>{await navigator.clipboard.writeText(t),a(!0),setTimeout(()=>a(!1),2e3)},[t]);return g.jsxs("div",{className:"relative group/code my-3 rounded-lg overflow-hidden",children:[g.jsxs("div",{className:"flex items-center justify-between px-4 py-1.5 bg-[#1e1e2e] text-xs text-gray-400",children:[g.jsx("span",{children:e||"code"}),g.jsx("button",{onClick:o,className:"flex items-center gap-1 hover:text-white transition-colors",children:r?"✓ Copied":"Copy"})]}),g.jsx(KN,{style:yP,language:e||"text",PreTag:"div",customStyle:{margin:0,borderRadius:0,fontSize:"13px"},children:t})]})}function TV(e){const t=e.split(`
`);let r=!1,a=!1,o="",s=0;for(let u=0;u<t.length;u++){const d=t[u];if(/^[ \t]*```/.test(d)){r=!r;continue}if(r)continue;const p=d.match(/^([ \t]*)(\d+)\.[ \t]/);if(p){const f=p[1];!a||f!==o?(a=!0,o=f,s=1):s++,t[u]=d.replace(/^([ \t]*)\d+\./,`${f}${s}.`)}else d.trim()===""&&a||a&&/^[ \t]+\S/.test(d)||a&&(a=!1,s=0)}return t.join(`
`)}function EO({content:e}){const t=wV(e);return g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-pre:p-0 prose-pre:bg-transparent",children:g.jsx(sN,{remarkPlugins:[IN],components:{ul({children:r,ordered:a,start:o}){if(a){const s=o!=null?Number(o):1;return g.jsx("ol",{start:s,style:{listStyleType:"decimal",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})}return g.jsx("ul",{style:{listStyleType:"disc",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})},li({children:r}){return g.jsx("li",{style:{display:"list-item",marginBottom:"0.25rem"},children:r})},code({className:r,children:a,...o}){const s=/language-(\w+)/.exec(r||""),u=SV(String(a));return!s&&!u.includes(`
`)?g.jsx("code",{className:"px-1.5 py-0.5 rounded bg-muted text-sm font-mono",...o,children:a}):g.jsx(xV,{language:s?.[1],children:u})},a({href:r,children:a}){return g.jsx("a",{href:r,target:"_blank",rel:"noopener noreferrer",className:"text-primary hover:underline",children:a})},table({children:r}){return g.jsx("div",{className:"overflow-x-auto my-3",children:g.jsx("table",{className:"min-w-full text-sm",children:r})})}},children:t})})}function TV({message:e,sessionId:t}){return e.role==="user"?g.jsx("div",{className:"flex justify-end",children:g.jsxs("div",{className:"flex gap-3 max-w-[80%]",children:[g.jsx("div",{className:"rounded-2xl px-4 py-2.5 bg-primary text-primary-foreground",children:g.jsx("p",{className:"text-sm whitespace-pre-wrap",children:e.content})}),g.jsx(Xu,{role:"user"})]})}):g.jsxs("div",{className:"flex gap-3 group",children:[g.jsx(Xu,{role:"assistant"}),g.jsxs("div",{className:"flex-1 min-w-0",children:[e.toolCalls&&e.toolCalls.length>0&&g.jsx("div",{className:"mb-2 space-y-1",children:e.toolCalls.map((a,o)=>g.jsx(yO,{toolCall:a},o))}),g.jsx(EO,{content:e.content}),e.elements&&e.elements.length>0&&g.jsx("div",{className:"mt-3 space-y-3",children:e.elements.map((a,o)=>g.jsx(Dv,{element:a,className:"w-full"},o))}),e.images&&e.images.length>0&&g.jsx("div",{className:"mt-2 flex flex-wrap gap-2",children:e.images.map((a,o)=>g.jsx("img",{src:a,alt:"",className:"max-w-full rounded-md max-h-64 object-contain"},o))}),e.actions&&e.actions.length>0&&g.jsx(gV,{actions:e.actions,messageId:e.id,sessionId:t}),g.jsxs("div",{className:"mt-1 flex items-center gap-1 relative",children:[g.jsx(EV,{text:e.content}),g.jsx(vV,{messageId:e.id})]})]})]})}function AV({onSend:e,onCancel:t,isStreaming:r=!1,placeholder:a="Type a message...",enableFileUpload:o=!1}){const[s,u]=S.useState(""),[d,p]=S.useState([]),f=S.useRef(null),h=S.useRef(null),b=S.useCallback(()=>{const A=s.trim();(A||d.length>0)&&!r&&(e(A,d.length>0?d:void 0),u(""),p([]),f.current&&(f.current.style.height="auto"))},[s,d,r,e]),v=S.useCallback(A=>{const _=Array.from(A.target.files||[]);p(R=>[...R,..._]),h.current&&(h.current.value="")},[]),E=S.useCallback(()=>{h.current?.click()},[]),w=S.useCallback(A=>{p(_=>_.filter((R,L)=>L!==A))},[]),T=S.useCallback(A=>{A.key==="Enter"&&!A.shiftKey&&(A.preventDefault(),b())},[b]),k=S.useCallback(()=>{const A=f.current;A&&(A.style.height="auto",A.style.height=`${Math.min(A.scrollHeight,200)}px`)},[]);return g.jsx("div",{className:"p-4 pb-6",children:g.jsxs("div",{className:"max-w-3xl mx-auto",children:[g.jsxs("div",{className:"rounded-2xl border bg-background/80 backdrop-blur-sm shadow-lg",children:[d.length>0&&g.jsx("div",{className:"flex flex-wrap gap-1 px-4 pt-3",children:d.map((A,_)=>g.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-muted rounded-lg text-xs",children:[g.jsx("span",{className:"truncate max-w-[100px]",children:A.name}),g.jsx("button",{type:"button",onClick:()=>w(_),className:"hover:text-destructive",children:"×"})]},`${A.name}-${_}`))}),g.jsxs("div",{className:"flex items-end gap-2 p-2",children:[o&&g.jsxs(g.Fragment,{children:[g.jsx("input",{ref:h,type:"file",multiple:!0,onChange:v,className:"hidden",accept:"image/*,audio/*,video/*,.pdf,.doc,.docx,.txt,.csv,.json"}),g.jsx("button",{type:"button",onClick:E,className:"p-2 rounded-xl hover:bg-accent text-muted-foreground transition-colors",title:"Attach file",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"})})})]}),g.jsx("textarea",{ref:f,value:s,onChange:A=>u(A.target.value),onKeyDown:T,onInput:k,placeholder:a,disabled:r,rows:1,className:"flex-1 resize-none bg-transparent px-3 py-2.5 text-sm focus:outline-none disabled:opacity-50 placeholder:text-muted-foreground/60",style:{maxHeight:"200px"}}),r?g.jsx("button",{type:"button",onClick:t,className:"p-2 rounded-xl bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors",title:"Stop",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"currentColor",children:g.jsx("rect",{x:"6",y:"6",width:"12",height:"12",rx:"2"})})}):g.jsx("button",{type:"button",onClick:b,disabled:!s.trim(),className:"p-2 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 disabled:cursor-not-allowed transition-colors",title:"Send",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"M5 12h14M12 5l7 7-7 7"})})})]})]}),g.jsx("p",{className:"text-[10px] text-center text-muted-foreground/50 mt-2",children:"Press Enter to send, Shift+Enter for new line" Line truncated
`),b=S.useCallback(async()=>{!r.trim()||s||p(r)},[r,s,p]),v=S.useCallback(()=>{a("")},[]);return g.jsxs("div",{className:"min-h-screen bg-background text-foreground flex flex-col",children:[g.jsxs("header",{className:"border-b px-6 py-4 flex items-center justify-between",children:[g.jsx("h1",{className:"text-xl font-semibold",children:t||"Playground"}),g.jsx("div",{className:"flex items-center gap-2",children:g.jsx("button",{onClick:v,className:"px-3 py-1.5 text-sm rounded-md border hover:bg-accent",children:"Clear"})})]}),g.jsxs("div",{className:"flex-1 flex",children:[g.jsxs("div",{className:"flex-1 flex flex-col border-r",children:[g.jsx("div",{className:"px-4 py-2 border-b bg-muted/30",children:g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Input"})}),g.jsx("div",{className:"flex-1 p-4",children:g.jsx("textarea",{value:r,onChange:E=>a(E.target.value),placeholder:"Enter your prompt here...",className:"w-full h-full resize-none bg-transparent border rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-primary"})}),g.jsx("div",{className:"px-4 py-3 border-t flex justify-end gap-2",children:s?g.jsx("button",{onClick:f,className:"px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90",children:"Stop"}):g.jsx("button",{onClick:b,disabled:!r.trim(),className:"px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",children:"Submit"})})]}),g.jsxs("div",{className:"flex-1 flex flex-col",children:[g.jsxs("div",{className:"px-4 py-2 border-b bg-muted/30 flex items-center justify-between",children:[g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Output"}),s&&g.jsx("span",{className:"text-xs text-muted-foreground animate-pulse",children:"Processing..."})]}),g.jsx("div",{className:"flex-1 p-4 overflow-auto",children:h?g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap",children:h}):g.jsx("div",{className:"h-full flex items-center justify-center text-muted-foreground text-sm",children:"Output will appear here"})})]})]}),e?.features&&g.jsxs("div",{className:"border-t px-6 py-3 bg-muted/20 flex items-center gap-4 text-sm text-muted-foreground",children:[e.features.streaming&&g.jsx("span",{children:"✓ Streaming"}),e.features.reasoning&&g.jsx("span",{children:"✓ Reasoning"}),e.features.tools&&g.jsx("span",{children:"✓ Tools"})]})]})}function MV(){const[e,t]=S.useState({}),[r,a]=S.useState({}),[o,s]=S.useState({}),[u,d]=S.useState(!0),[p,f]=S.useState(null),[h,b]=S.useState(null),[v,E]=S.useState("");S.useEffect(()=>{async function L(){try{const[D,P,z]=await Promise.all([fetch("/ui-config.json"),fetch("/docs-nav.json"),fetch("/route-manifest.json")]);if(!D.ok||!P.ok||!z.ok)throw new Error("Failed to load manifests");const W=await D.json(),M=await P.json();t(W),a(M),s(await z.json());const H=W.site?.theme;W1(H?.preset||"zinc",H?.darkMode!==!1,H?.radius||"md");const X=window.location.pathname;if(X&&X!=="/"){const ee=te=>{for(const Z of te){if(Z.path===X||Z.path===X.replace(/^\//,""))return Z;if(Z.children){const B=ee(Z.children);if(B)return B}}return null};if(M.items){for(const te of M.items)if(te.children){const Z=ee(te.children);if(Z){b(Z),E(Z.path||Z.title);break}}}}}catch(D){f(D instanceof Error?D.message:"Unknown error")}finally{d(!1)}}L();const F=()=>{window.location.pathname==="/"&&(b(null),E(""))};return window.addEventListener("popstate",F),()=>window.removeEventListener("popstate",F)},[]);const w=(L,F,D)=>{document.title=`${L} | ${e.site?.title||"Documentation"}`;let P=document.querySelector('link[rel="canonical"]');P||(P=document.createElement("link"),P.rel="canonical",document.head.appendChild(P)),P.href=window.location.origin+F;let z=document.querySelector('meta[name="description"]');z||(z=document.createElement("meta"),z.name="description",document.head.appendChild(z)),z.content=`${L} - ${e.site?.description||""}`;let W=document.querySelector('meta[property="og:title"]');W||(W=document.createElement("meta"),W.setAttribute("property","og:title"),document.head.appendChild(W)),W.content=L;let M=document.querySelector('meta[property="og:url"]');M||(M=document.createElement("meta"),M.setAttribute("property","og:url"),document.head.appendChild(M)),M.content=window.location.origin+F},T=L=>{b(L),E(L.path||L.title);const F=L.path||`/${L.title.toLowerCase().replace(/\s+/g,"-")}`;window.history.pushState({path:F},L.title,F),w(L.title,F)};if(u)return g.jsx("div",{className:"dark flex items-center justify-center min-h-screen bg-background",children:g.jsx("span",{className:"text-muted-foreground",children:"Loading..."})});if(p)return g.jsxs("div",{className:"dark flex flex-col items-center justify-center min-h-screen gap-4 bg-background",children:[g.jsx("h2",{className:"text-xl font-semibold text-destructive",children:"Failed to load"}),g.jsx("p",{className:"text-muted-foreground",children:p})]});const k=e.templates? Line truncated
`)}function vO({content:e}){const t=TV(e);return g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none prose-p:leading-relaxed prose-pre:p-0 prose-pre:bg-transparent",children:g.jsx(uN,{remarkPlugins:[LN],components:{ul({children:r,ordered:a,start:o}){if(a){const s=o!=null?Number(o):1;return g.jsx("ol",{start:s,style:{listStyleType:"decimal",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})}return g.jsx("ul",{style:{listStyleType:"disc",paddingLeft:"1.5rem",margin:"0.5rem 0"},children:r})},li({children:r}){return g.jsx("li",{style:{display:"list-item",marginBottom:"0.25rem"},children:r})},code({className:r,children:a,...o}){const s=/language-(\w+)/.exec(r||""),u=xV(String(a));return!s&&!u.includes(`
`)?g.jsx("code",{className:"px-1.5 py-0.5 rounded bg-muted text-sm font-mono",...o,children:a}):g.jsx(wV,{language:s?.[1],children:u})},a({href:r,children:a}){return g.jsx("a",{href:r,target:"_blank",rel:"noopener noreferrer",className:"text-primary hover:underline",children:a})},table({children:r}){return g.jsx("div",{className:"overflow-x-auto my-3",children:g.jsx("table",{className:"min-w-full text-sm",children:r})})}},children:t})})}function AV({message:e,sessionId:t}){return e.role==="user"?g.jsx("div",{className:"flex justify-end",children:g.jsxs("div",{className:"flex gap-3 max-w-[80%]",children:[g.jsx("div",{className:"rounded-2xl px-4 py-2.5 bg-primary text-primary-foreground",children:g.jsx("p",{className:"text-sm whitespace-pre-wrap",children:e.content})}),g.jsx(Zu,{role:"user"})]})}):g.jsxs("div",{className:"flex gap-3 group",children:[g.jsx(Zu,{role:"assistant"}),g.jsxs("div",{className:"flex-1 min-w-0",children:[e.toolCalls&&e.toolCalls.length>0&&g.jsx("div",{className:"mb-2 space-y-1",children:e.toolCalls.map((a,o)=>g.jsx(EO,{toolCall:a},o))}),g.jsx(vO,{content:e.content}),e.elements&&e.elements.length>0&&g.jsx("div",{className:"mt-3 space-y-3",children:e.elements.map((a,o)=>g.jsx(jv,{element:a,className:"w-full"},o))}),e.images&&e.images.length>0&&g.jsx("div",{className:"mt-2 flex flex-wrap gap-2",children:e.images.map((a,o)=>g.jsx("img",{src:a,alt:"",className:"max-w-full rounded-md max-h-64 object-contain"},o))}),e.actions&&e.actions.length>0&&g.jsx(mV,{actions:e.actions,messageId:e.id,sessionId:t}),g.jsxs("div",{className:"mt-1 flex items-center gap-1 relative",children:[g.jsx(vV,{text:e.content}),g.jsx(SV,{messageId:e.id})]})]})]})}function kV({onSend:e,onCancel:t,isStreaming:r=!1,placeholder:a="Type a message...",enableFileUpload:o=!1}){const[s,u]=S.useState(""),[d,p]=S.useState([]),f=S.useRef(null),h=S.useRef(null),b=S.useCallback(()=>{const A=s.trim();(A||d.length>0)&&!r&&(e(A,d.length>0?d:void 0),u(""),p([]),f.current&&(f.current.style.height="auto"))},[s,d,r,e]),v=S.useCallback(A=>{const _=Array.from(A.target.files||[]);p(R=>[...R,..._]),h.current&&(h.current.value="")},[]),E=S.useCallback(()=>{h.current?.click()},[]),w=S.useCallback(A=>{p(_=>_.filter((R,L)=>L!==A))},[]),T=S.useCallback(A=>{A.key==="Enter"&&!A.shiftKey&&(A.preventDefault(),b())},[b]),k=S.useCallback(()=>{const A=f.current;A&&(A.style.height="auto",A.style.height=`${Math.min(A.scrollHeight,200)}px`)},[]);return g.jsx("div",{className:"p-4 pb-6",children:g.jsxs("div",{className:"max-w-3xl mx-auto",children:[g.jsxs("div",{className:"rounded-2xl border bg-background/80 backdrop-blur-sm shadow-lg",children:[d.length>0&&g.jsx("div",{className:"flex flex-wrap gap-1 px-4 pt-3",children:d.map((A,_)=>g.jsxs("span",{className:"inline-flex items-center gap-1 px-2 py-1 bg-muted rounded-lg text-xs",children:[g.jsx("span",{className:"truncate max-w-[100px]",children:A.name}),g.jsx("button",{type:"button",onClick:()=>w(_),className:"hover:text-destructive",children:"×"})]},`${A.name}-${_}`))}),g.jsxs("div",{className:"flex items-end gap-2 p-2",children:[o&&g.jsxs(g.Fragment,{children:[g.jsx("input",{ref:h,type:"file",multiple:!0,onChange:v,className:"hidden",accept:"image/*,audio/*,video/*,.pdf,.doc,.docx,.txt,.csv,.json"}),g.jsx("button",{type:"button",onClick:E,className:"p-2 rounded-xl hover:bg-accent text-muted-foreground transition-colors",title:"Attach file",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"m21.44 11.05-9.19 9.19a6 6 0 0 1-8.49-8.49l8.57-8.57A4 4 0 1 1 18 8.84l-8.59 8.57a2 2 0 0 1-2.83-2.83l8.49-8.48"})})})]}),g.jsx("textarea",{ref:f,value:s,onChange:A=>u(A.target.value),onKeyDown:T,onInput:k,placeholder:a,disabled:r,rows:1,className:"flex-1 resize-none bg-transparent px-3 py-2.5 text-sm focus:outline-none disabled:opacity-50 placeholder:text-muted-foreground/60",style:{maxHeight:"200px"}}),r?g.jsx("button",{type:"button",onClick:t,className:"p-2 rounded-xl bg-destructive text-destructive-foreground hover:bg-destructive/90 transition-colors",title:"Stop",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"currentColor",children:g.jsx("rect",{x:"6",y:"6",width:"12",height:"12",rx:"2"})})}):g.jsx("button",{type:"button",onClick:b,disabled:!s.trim(),className:"p-2 rounded-xl bg-primary text-primary-foreground hover:bg-primary/90 disabled:opacity-30 disabled:cursor-not-allowed transition-colors",title:"Send",children:g.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:g.jsx("path",{d:"M5 12h14M12 5l7 7-7 7"})})})]})]}),g.jsx("p",{className:"text-[10px] text-center text-muted-foreground/50 mt-2",children:"Press Enter to send, Shift+Enter for new line" Line truncated
`),b=S.useCallback(async()=>{!r.trim()||s||p(r)},[r,s,p]),v=S.useCallback(()=>{a("")},[]);return g.jsxs("div",{className:"min-h-screen bg-background text-foreground flex flex-col",children:[g.jsxs("header",{className:"border-b px-6 py-4 flex items-center justify-between",children:[g.jsx("h1",{className:"text-xl font-semibold",children:t||"Playground"}),g.jsx("div",{className:"flex items-center gap-2",children:g.jsx("button",{onClick:v,className:"px-3 py-1.5 text-sm rounded-md border hover:bg-accent",children:"Clear"})})]}),g.jsxs("div",{className:"flex-1 flex",children:[g.jsxs("div",{className:"flex-1 flex flex-col border-r",children:[g.jsx("div",{className:"px-4 py-2 border-b bg-muted/30",children:g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Input"})}),g.jsx("div",{className:"flex-1 p-4",children:g.jsx("textarea",{value:r,onChange:E=>a(E.target.value),placeholder:"Enter your prompt here...",className:"w-full h-full resize-none bg-transparent border rounded-md p-3 focus:outline-none focus:ring-2 focus:ring-primary"})}),g.jsx("div",{className:"px-4 py-3 border-t flex justify-end gap-2",children:s?g.jsx("button",{onClick:f,className:"px-4 py-2 bg-destructive text-destructive-foreground rounded-md hover:bg-destructive/90",children:"Stop"}):g.jsx("button",{onClick:b,disabled:!r.trim(),className:"px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",children:"Submit"})})]}),g.jsxs("div",{className:"flex-1 flex flex-col",children:[g.jsxs("div",{className:"px-4 py-2 border-b bg-muted/30 flex items-center justify-between",children:[g.jsx("span",{className:"text-sm font-medium text-muted-foreground",children:"Output"}),s&&g.jsx("span",{className:"text-xs text-muted-foreground animate-pulse",children:"Processing..."})]}),g.jsx("div",{className:"flex-1 p-4 overflow-auto",children:h?g.jsx("div",{className:"prose prose-sm dark:prose-invert max-w-none whitespace-pre-wrap",children:h}):g.jsx("div",{className:"h-full flex items-center justify-center text-muted-foreground text-sm",children:"Output will appear here"})})]})]}),e?.features&&g.jsxs("div",{className:"border-t px-6 py-3 bg-muted/20 flex items-center gap-4 text-sm text-muted-foreground",children:[e.features.streaming&&g.jsx("span",{children:"✓ Streaming"}),e.features.reasoning&&g.jsx("span",{children:"✓ Reasoning"}),e.features.tools&&g.jsx("span",{children:"✓ Tools"})]})]})}function _u({enabled:e}){return e?g.jsx("a",{href:"#main-content",className:"sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 z-50 bg-primary text-primary-foreground px-4 py-2 rounded-md text-sm font-medium",children:"Skip to main content"}):null}function jV(){const[e,t]=S.useState({}),[r,a]=S.useState({}),[o,s]=S.useState({}),[u,d]=S.useState(!0),[p,f]=S.useState(null),[h,b]=S.useState(null),[v,E]=S.useState("");S.useEffect(()=>{async function L(){try{const[j,D,z]=await Promise.all([fetch("/ui-config.json"),fetch("/docs-nav.json"),fetch("/route-manifest.json")]);if(!j.ok||!D.ok||!z.ok)throw new Error("Failed to load manifests");const F=await j.json(),V=await D.json();t(F),a(V),s(await z.json());const M=F.site?.theme;K1(M?.preset||"zinc",M?.darkMode!==!1,M?.radius||"md");const H=window.location.pathname;if(H&&H!=="/"){const K=Z=>{for(const te of Z){if(te.path===H||te.path===H.replace(/^\//,""))return te;if(te.children){const Q=K(te.children);if(Q)return Q}}return null};if(V.items){for(const Z of V.items)if(Z.children){const te=K(Z.children);if(te){b(te),E(te.path||te.title);break}}}}}catch(j){f(j instanceof Error?j.message:"Unknown error")}finally{d(!1)}}L()},[]),S.useEffect(()=>{const L=()=>{const j=window.location.pathname,D=F=>{for(const V of F){if(V.path===j||V.path===j.replace(/^\//,""))return V;if(V.children){const M=D(V.children);if(M)return M}}return null};let z=null;if(r.items){for(const F of r.items)if(F.children&&(z=D(F.children),z))break}if(z){b(z),E(z.path||z.title);const F=e.site?.title||"Documentation",V=e.seo?.titleTemplate||"%s | %s";if(V.includes("%s")){let M=V.replace("%s",z.title);M.includes("%s")&&(M=M.replace("%s",F)),document.title=M}else document.title=`${z.title} | ${F}`}else j==="/"&&(b(null),E(""),document.title=e.site?.title||"Documentation")};return window.addEventListener("popstate",L),()=>window.removeEventListener("popstate",L)},[r,e]);const w=(L,j,D)=>{const z=e.seo?.titleTemplate||"%s | %s",F=e.site?.title||"Documentation";if(z.includes("%s")){let Z=z.replace("%s",L);Z.includes("%s")&&(Z=Z.replace("%s",F)),document.title=Z}else document.title=`${L} | ${F}`;let V=document.querySelector('link[rel="canonical"]');V||(V=document.createElement("link"),V.rel="canonical",document.head.appendChild(V)),V.href=window.location.origin+j;let M=document.querySelector('meta[name="description"]');M||(M=document.createElement("meta"),M.name="description",document.head.appendChild(M)),M.content=`${L} - ${e.site?.description||""}`;let H=document.querySele Line truncated
@@ -0,0 +1,5 @@
import{y as Q,A as _,j as X,n as E,a as w,o as f,r as q,b as O,s as m,l as S,u as $,c as N,d as k,e as Y,f as K}from"./common-types.js";import{g as mt,h as ht,i as gt,k as yt,C as bt,m as vt,p as St,q as At,t as _t,v as wt,w as Mt,D as It,x as Zt,z as kt,B as Dt,E as Pt,F as jt,G as Ct,H as Ot,I as $t,J as Nt,K as Et,L as Tt,M as xt,N as Ut}from"./common-types.js";class v{constructor(){this.listeners=new Set}subscribe(e){return this.listeners.add(e),{unsubscribe:()=>this.listeners.delete(e)}}async emit(e){for(const t of this.listeners)try{await t(e)}catch(a){console.error("EventEmitter error:",a)}}dispose(){this.listeners.clear()}}function D(n){return/^\d+$/.test(n)}class ee{constructor(e={}){this.data={},this.signals=new Map,this.subscriptions=new Set,this.data=e}getSignal(e){const t=this.normalizePath(e);return this.signals.has(t)||this.signals.set(t,Q(this.get(t))),this.signals.get(t)}set(e,t){if(e==null)throw new _("Path cannot be null or undefined.");if(e==="/"||e==="")return this.data=t,this.notifyAllSignals(),this;const a=this.parsePath(e),s=a.pop();this.data||(this.data={});let r=this.data;for(let i=0;i<a.length;i++){const o=a[i];if(Array.isArray(r)&&!D(o))throw new _(`Cannot use non-numeric segment '${o}' on an array in path '${e}'.`,e);if(r[o]!==void 0&&r[o]!==null&&typeof r[o]!="object")throw new _(`Cannot set path '${e}': segment '${o}' is a primitive value.`,e);if(r[o]===void 0||r[o]===null){const c=i<a.length-1?a[i+1]:s;r[o]=D(c)?[]:{}}r=r[o]}if(Array.isArray(r)&&!D(s))throw new _(`Cannot use non-numeric segment '${s}' on an array in path '${e}'.`,e);return t===void 0?Array.isArray(r)?r[parseInt(s,10)]=void 0:delete r[s]:r[s]=t,this.notifySignals(e),this}get(e){if(e==null)throw new _("Path cannot be null or undefined.");if(e==="/"||e==="")return this.data;const t=this.parsePath(e);let a=this.data;for(const s of t){if(a==null)return;a=a[s]}return a}subscribe(e,t){const a=this.getSignal(e);let s=!0,r=a.peek();const i=X(()=>{const o=a.value;r=o,s||t(o)});return s=!1,this.subscriptions.add(i),{get value(){return r},unsubscribe:()=>{i(),this.subscriptions.delete(i)}}}dispose(){for(const e of this.subscriptions)e();this.subscriptions.clear(),this.signals.clear()}normalizePath(e){return e.length>1&&e.endsWith("/")?e.slice(0,-1):e||"/"}parsePath(e){return e.split("/").filter(t=>t.length>0)}notifySignals(e){const t=this.normalizePath(e);E(()=>{this.updateSignal(t);let a=t;for(;a!=="/"&&a!=="";)a=a.substring(0,a.lastIndexOf("/"))||"/",this.updateSignal(a);for(const s of this.signals.keys())this.isDescendant(s,t)&&this.updateSignal(s)})}updateSignal(e){const t=this.signals.get(e);if(t){const a=this.get(e);Array.isArray(a)?t.value=[...a]:typeof a=="object"&&a!==null?t.value={...a}:t.value=a}}notifyAllSignals(){E(()=>{for(const e of this.signals.keys())this.updateSignal(e)})}isDescendant(e,t){return t==="/"||t===""?e!=="/":e.startsWith(t+"/")}}class te{constructor(){this.components=new Map,this._onCreated=new v,this._onDeleted=new v,this.onCreated=this._onCreated,this.onDeleted=this._onDeleted}get(e){return this.components.get(e)}get entries(){return this.components.entries()}addComponent(e){if(this.components.has(e.id))throw new w(`Component with id '${e.id}' already exists.`);this.components.set(e.id,e),this._onCreated.emit(e)}removeComponent(e){const t=this.components.get(e);t&&(this.components.delete(e),t.dispose(),this._onDeleted.emit(e))}dispose(){for(const e of this.components.values())e.dispose();this.components.clear(),this._onCreated.dispose(),this._onDeleted.dispose()}}const V=f({name:m().describe("The name of the action, taken from the component's action.event.name property."),surfaceId:m().describe("The id of the surface where the event originated."),sourceComponentId:m().describe("The id of the component that triggered the event."),timestamp:m().datetime().describe("An ISO 8601 timestamp of when the event occurred."),context:q(O()).describe("A JSON object containing the key-value pairs from the component's action.event.context, after resolving all data bindings.")}).strict(),ne=f({code:S("VALIDATION_FAILED"),surfaceId:m().describe("The id of the surface where the error occurred."),path:m().describe("The JSON pointer to the field that failed validation (e.g. '/components/0/text')."),message:m().describe("A short one or two sentence description of why validation failed.")}).strict(),ae=f({code:m().refine(n=>n!=="VALIDATION_FAILED"),message:m().describe("A short one or two sentence description of why the error occurred."),surfaceId:m().describe("The id of the surface where the error occurred.")}).passthrough(),se=$([ne,ae]),re=f({version:S("v0.9")}).and($([f({action:V}),f({error:se})])),ot=f({version:S("v0.9"),surfaces:q(f({}).passthrough()).describe("A map of surface IDs to their current data models.")}).strict(),ie=N(re).describe("A list of client messages."),ct=f({messages:ie}).strict().describe("An object wrapping a list of client messages.");class oe{constructor(e,t,a={},s=!1){this.id=e,thLine truncated
]))`;continue}else if(a[c]==="$"){s+=`($|(?=[\r
]))`;continue}}if(t.s&&a[c]==="."){s+=i?`${a[c]}\r
`:`[${a[c]}\r
]`;continue}s+=a[c],a[c]==="\\"?r=!0:i&&a[c]==="]"?i=!1:!i&&a[c]==="["&&(i=!0)}try{new RegExp(s)}catch{return console.warn(`Could not convert regex pattern at ${e.currentPath.join("/")} to a flag-independent form! Falling back to the flag-ignorant source`),n.source}return s}function H(n,e){if(e.target==="openAi"&&console.warn("Warning: OpenAI may not support records in schemas! Try an array of key-value pairs instead."),e.target==="openApi3"&&n.keyType?._def.typeName===u.ZodEnum)return{type:"object",required:n.keyType._def.values,properties:n.keyType._def.values.reduce((a,s)=>({...a,[s]:l(n.valueType._def,{...e,currentPath:[...e.currentPath,"properties",s]})??g(e)}),{}),additionalProperties:e.rejectedAdditionalProperties};const t={type:"object",additionalProperties:l(n.valueType._def,{...e,currentPath:[...e.currentPath,"additionalProperties"]})??e.allowedAdditionalProperties};if(e.target==="openApi3")return t;if(n.keyType?._def.typeName===u.ZodString&&n.keyType._def.checks?.length){const{type:a,...s}=G(n.keyType._def,e);return{...t,propertyNames:s}}else{if(n.keyType?._def.typeName===u.ZodEnum)return{...t,propertyNames:{enum:n.keyType._def.values}};if(n.keyType?._def.typeName===u.ZodBranded&&n.keyType._def.type._def.typeName===u.ZodString&&n.keyType._def.type._def.checks?.length){const{type:a,...s}=J(n.keyType._def,e);return{...t,propertyNames:s}}}return t}function Ie(n,e){if(e.mapStrategy==="record")return H(n,e);const t=l(n.keyType._def,{...e,currentPath:[...e.currentPath,"items","items","0"]})||g(e),a=l(n.valueType._def,{...e,currentPath:[...e.currentPath,"items","items","1"]})||g(e);return{type:"array",maxItems:125,items:{type:"array",items:[t,a],minItems:2,maxItems:2}}}function Ze(n){const e=n.values,a=Object.keys(n.values).filter(r=>typeof e[e[r]]!="number").map(r=>e[r]),s=Array.from(new Set(a.map(r=>typeof r)));return{type:s.length===1?s[0]==="string"?"string":"number":["string","number"],enum:a}}function ke(n){return n.target==="openAi"?void 0:{not:g({...n,currentPath:[...n.currentPath,"not"]})}}function De(n){return n.target==="openApi3"?{enum:["null"],nullable:!0}:{type:"null"}}const Z={ZodString:"string",ZodNumber:"number",ZodBigInt:"integer",ZodBoolean:"boolean",ZodNull:"null"};function Pe(n,e){if(e.target==="openApi3")return R(n,e);const t=n.options instanceof Map?Array.from(n.options.values()):n.options;if(t.every(a=>a._def.typeName in Z&&(!a._def.checks||!a._def.checks.length))){const a=t.reduce((s,r)=>{const i=Z[r._def.typeName];return i&&!s.includes(i)?[...s,i]:s},[]);return{type:a.length>1?a:a[0]}}else if(t.every(a=>a._def.typeName==="ZodLiteral"&&!a.description)){const a=t.reduce((s,r)=>{const i=typeof r._def.value;switch(i){case"string":case"number":case"boolean":return[...s,i];case"bigint":return[...s,"integer"];case"object":if(r._def.value===null)return[...s,"null"];default:return s}},[]);if(a.length===t.length){const s=a.filter((r,i,o)=>o.indexOf(r)===i);return{type:s.length>1?s:s[0],enum:t.reduce((r,i)=>r.includes(i._def.value)?r:[...r,i._def.value],[])}}}else if(t.every(a=>a._def.typeName==="ZodEnum"))return{type:"string",enum:t.reduce((a,s)=>[...a,...s._def.values.filter(r=>!a.includes(r))],[])};return R(n,e)}const R=(n,e)=>{const t=(n.options instanceof Map?Array.from(n.options.values()):n.options).map((a,s)=>l(a._def,{...e,currentPath:[...e.currentPath,"anyOf",`${s}`]})).filter(a=>!!a&&(!e.strictUnions||typeof a=="object"&&Object.keys(a).length>0));return t.length?{anyOf:t}:void 0};function je(n,e){if(["ZodString","ZodNumber","ZodBigInt","ZodBoolean","ZodNull"].includes(n.innerType._def.typeName)&&(!n.innerType._def.checks||!n.innerType._def.checks.length))return e.target==="openApi3"?{type:Z[n.innerType._def.typeName],nullable:!0}:{type:[Z[n.innerType._def.typeName],"null"]};if(e.target==="openApi3"){const a=l(n.innerType._def,{...e,currentPath:[...e.currentPath]});return a&&"$ref"in a?{allOf:[a],nullable:!0}:a&&{...a,nullable:!0}}const t=l(n.innerType._def,{...e,currentPath:[...e.currentPath,"anyOf","0"]});return t&&{anyOf:[t,{type:"null"}]}}function Ce(n,e){const t={type:"number"};if(!n.checks)return t;for(const a of n.checks)switch(a.kind){case"int":t.type="integer",B(t,"type",a.message,e);break;case"min":e.target==="jsonSchema7"?a.inclusive?d(t,"minimum",a.value,a.message,e):d(t,"exclusiveMinimum",a.value,a.message,e):(a.inclusive||(t.exclusiveMinimum=!0),d(t,"minimum",a.value,a.message,e));break;case"max":e.target==="jsonSchema7"?a.inclusive?d(t,"maximum",a.value,a.message,e):d(t,"exclusiveMaximum",a.value,a.message,e):(a.inclusive||(t.exclusiveMaximum=!0),d(t,"maximum",a.value,a.message,e));break;case"multipleOf":d(t,"multipleOf",a.value,a.message,e);break}return t}function Oe(n,e){const t=e.target==="openAi",a={type:"object",properties:{}},s=[],r=n.shape();for(const o in r){let c=r[o];if(c===void 0||c._def===void 0)continue;let p=Ne(c);p&&t&&(c._def.typeName==="ZodOptional"&&(c=c._def.innerType),c.isNullable()||(c=c.nullable()),p=!1);const A=l(c._def,{...e,currentPath:[.. Line truncated
@@ -0,0 +1,67 @@
import{j as s,r as y,R as W}from"./index.js";import{h as E,o as u,O as g,P as m,b as D,c as X,s as v,e as q,L as b,K as ke,M as Se,Q as V,i as Te,R as T,F as S,u as Z,q as te,w as $,p as F,k as Ce,z as De,E as Me,B as Oe,C as Ae,v as Pe,I as ie}from"./common-types.js";const U=class U{parse(t,n=0){if(n>U.MAX_DEPTH)throw new E("Max recursion depth reached in parse");if(!t||!t.includes("${"))return[t];const a=[],r=new oe(t);for(;!r.isAtEnd();)if(r.matches("${")){r.advance(2);const i=this.extractInterpolationContent(r),o=this.parseExpression(i,n+1);o!==null&&a.push(o)}else if(r.peek()==="\\"&&r.peek(1)==="$"&&r.peek(2)==="{")r.advance(),a.push("${"),r.advance(2);else{const i=r.pos;for(;!r.isAtEnd()&&!(r.matches("${")||r.peek()==="\\"&&r.peek(1)==="$"&&r.peek(2)==="{");)r.advance();a.push(r.input.substring(i,r.pos))}return a.filter(i=>i!==null&&i!=="")}extractInterpolationContent(t){const n=t.pos;let a=1;for(;!t.isAtEnd()&&a>0;){const r=t.advance();if(r==="{")a++;else if(r==="}")a--;else if(r==="'"||r==='"'){const i=r;for(;!t.isAtEnd();){const o=t.advance();if(o==="\\")t.advance();else if(o===i)break}}}if(a>0)throw new E("Unclosed interpolation: missing '}'");return t.input.substring(n,t.pos-1)}parseExpression(t,n=0){if(t=t.trim(),!t)return"";const a=new oe(t),r=this.parseExpressionInternal(a,n);if(!a.isAtEnd())throw new E(`Unexpected characters at end of expression: '${a.input.substring(a.pos)}'`);return r}parseExpressionInternal(t,n){if(t.skipWhitespace(),t.isAtEnd())return"";if(t.matches("${")){t.advance(2);const r=this.extractInterpolationContent(t);return this.parseExpression(r,n+1)}if(t.matchesString("'")||t.matchesString('"'))return this.parseStringLiteral(t);if(this.isDigit(t.peek()))return this.parseNumberLiteral(t);if(t.matchesKeyword("true"))return!0;if(t.matchesKeyword("false"))return!1;if(t.matchesKeyword("null"))return"";const a=this.scanPathOrIdentifier(t);return t.skipWhitespace(),t.peek()==="("?this.parseFunctionCall(a,t,n):a?{path:a}:""}scanPathOrIdentifier(t){const n=t.pos;for(;!t.isAtEnd();){const a=t.peek();if(this.isAlnum(a)||a==="/"||a==="."||a==="_"||a==="-")t.advance();else break}return t.input.substring(n,t.pos)}parseFunctionCall(t,n,a){n.match("("),n.skipWhitespace();const r={};for(;!n.isAtEnd()&&n.peek()!==")";){const i=this.scanIdentifier(n);if(n.skipWhitespace(),!n.match(":"))throw new E(`Expected ':' after argument name '${i}' in function '${t}'`);n.skipWhitespace(),r[i]=this.parseExpressionInternal(n,a),n.skipWhitespace(),n.peek()===","&&(n.advance(),n.skipWhitespace())}if(!n.match(")"))throw new E(`Expected ')' after function arguments for '${t}'`);return{call:t,args:r,returnType:"any"}}scanIdentifier(t){const n=t.pos;for(;!t.isAtEnd()&&(this.isAlnum(t.peek())||t.peek()==="_");)t.advance();return t.input.substring(n,t.pos)}parseStringLiteral(t){const n=t.advance();let a="";for(;!t.isAtEnd();){const r=t.advance();if(r==="\\"){const i=t.advance();i==="n"?a+=`
`:i==="t"?a+=" ":i==="r"?a+="\r":a+=i}else{if(r===n)break;a+=r}}return a}parseNumberLiteral(t){const n=t.pos;for(;!t.isAtEnd()&&(this.isDigit(t.peek())||t.peek()===".");)t.advance();return Number(t.input.substring(n,t.pos))}isAlnum(t){return t>="a"&&t<="z"||t>="A"&&t<="Z"||t>="0"&&t<="9"}isDigit(t){return t>="0"&&t<="9"}};U.MAX_DEPTH=10;let ee=U;class oe{constructor(t){this.input=t,this.pos=0}isAtEnd(){return this.pos>=this.input.length}peek(t=0){return this.pos+t>=this.input.length?"\0":this.input[this.pos+t]}advance(t=1){const n=this.input.substring(this.pos,this.pos+t);return this.pos+=t,n}match(t){return this.peek()===t?(this.advance(),!0):!1}matches(t){return!!this.input.startsWith(t,this.pos)}matchesString(t){return this.peek()===t}matchesKeyword(t){if(this.input.startsWith(t,this.pos)){const n=this.peek(t.length);if(!/[a-zA-Z0-9_]/.test(n))return this.advance(t.length),!0}return!1}skipWhitespace(){for(;!this.isAtEnd()&&/\s/.test(this.peek());)this.advance()}}const be=6048e5,Ie=864e5,se=Symbol.for("constructDateFrom");function P(e,t){return typeof e=="function"?e(t):e&&typeof e=="object"&&se in e?e[se](t):e instanceof Date?new e.constructor(t):new Date(t)}function C(e,t){return P(t||e,e)}let je={};function G(){return je}function L(e,t){const n=G(),a=t?.weekStartsOn??t?.locale?.options?.weekStartsOn??n.weekStartsOn??n.locale?.options?.weekStartsOn??0,r=C(e,t?.in),i=r.getDay(),o=(i<a?7:0)+i-a;return r.setDate(r.getDate()-o),r.setHours(0,0,0,0),r}function H(e,t){return L(e,{...t,weekStartsOn:1})}function ge(e,t){const n=C(e,t?.in),a=n.getFullYear(),r=P(n,0);r.setFullYear(a+1,0,4),r.setHours(0,0,0,0);const i=H(r),o=P(n,0);o.setFullYear(a,0,4),o.setHours(0,0,0,0);const c=H(o);return n.getTime()>=i.getTime()?a+1:n.getTime()>=c.getTime()?a:a-1}function ce(e){const t=C(e),n=new Date(Date.UTC(t.getFullYear(),t.getMonth(),t.getDate(),t.getHours(),t.getMinutes(),t.getSeconds(),t.getMilliseconds()));return n.setUTCFullYear(t.getFullYear()),+e-+n}function Ne(e,...t){const n=P.bind(null,t.find(a=>typeof a=="object"));return t.map(n)}function ue(e,t){const n=C(e,t?.in);return n.setHours(0,0,0,0),n}function Ee(e,t,n){const[a,r]=Ne(n?.in,e,t),i=ue(a),o=ue(r),c=+i-ce(i),h=+o-ce(o);return Math.round((c-h)/Ie)}function We(e,t){const n=ge(e,t),a=P(e,0);return a.setFullYear(n,0,4),a.setHours(0,0,0,0),H(a)}function Fe(e){return e instanceof Date||typeof e=="object"&&Object.prototype.toString.call(e)==="[object Date]"}function ze(e){return!(!Fe(e)&&typeof e!="number"||isNaN(+C(e)))}function Re(e,t){const n=C(e,t?.in);return n.setFullYear(n.getFullYear(),0,1),n.setHours(0,0,0,0),n}const Ye={lessThanXSeconds:{one:"less than a second",other:"less than {{count}} seconds"},xSeconds:{one:"1 second",other:"{{count}} seconds"},halfAMinute:"half a minute",lessThanXMinutes:{one:"less than a minute",other:"less than {{count}} minutes"},xMinutes:{one:"1 minute",other:"{{count}} minutes"},aboutXHours:{one:"about 1 hour",other:"about {{count}} hours"},xHours:{one:"1 hour",other:"{{count}} hours"},xDays:{one:"1 day",other:"{{count}} days"},aboutXWeeks:{one:"about 1 week",other:"about {{count}} weeks"},xWeeks:{one:"1 week",other:"{{count}} weeks"},aboutXMonths:{one:"about 1 month",other:"about {{count}} months"},xMonths:{one:"1 month",other:"{{count}} months"},aboutXYears:{one:"about 1 year",other:"about {{count}} years"},xYears:{one:"1 year",other:"{{count}} years"},overXYears:{one:"over 1 year",other:"over {{count}} years"},almostXYears:{one:"almost 1 year",other:"almost {{count}} years"}},qe=(e,t,n)=>{let a;const r=Ye[e];return typeof r=="string"?a=r:t===1?a=r.one:a=r.other.replace("{{count}}",t.toString()),n?.addSuffix?n.comparison&&n.comparison>0?"in "+a:a+" ago":a};function J(e){return(t={})=>{const n=t.width?String(t.width):e.defaultWidth;return e.formats[n]||e.formats[e.defaultWidth]}}const $e={full:"EEEE, MMMM do, y",long:"MMMM do, y",medium:"MMM d, y",short:"MM/dd/yyyy"},Le={full:"h:mm:ss a zzzz",long:"h:mm:ss a z",medium:"h:mm:ss a",short:"h:mm a"},_e={full:"{{date}} 'at' {{time}}",long:"{{date}} 'at' {{time}}",medium:"{{date}}, {{time}}",short:"{{date}}, {{time}}"},Be={date:J({formats:$e,defaultWidth:"full"}),time:J({formats:Le,defaultWidth:"full"}),dateTime:J({formats:_e,defaultWidth:"full"})},Ve={lastWeek:"'last' eeee 'at' p",yesterday:"'yesterday at' p",today:"'today at' p",tomorrow:"'tomorrow at' p",nextWeek:"eeee 'at' p",other:"P"},He=(e,t,n,a)=>Ve[e];function z(e){return(t,n)=>{const a=n?.context?String(n.context):"standalone";let r;if(a==="formatting"&&e.formattingValues){const o=e.defaultFormattingWidth||e.defaultWidth,c=n?.width?String(n.width):o;r=e.formattingValues[c]||e.formattingValues[o]}else{const o=e.defaultWidth,c=n?.width?String(n.width):e.defaultWidth;r=e.values[c]||e.values[o]}const i=e.argumentCallback?e.argumentCallback(t):t;return r[i]}}const Ue={narrow:["B","A"],abbreviated:["BC","AD"],wide:["Before Christ","Anno Domini"]},Xe={narrow:["1","2","3","4"],abbreviated:["Q1","Q2","Q3","Q4"],wide:["1st quarter" Line truncated
:where(:root) {
color-scheme: light dark;
}
:where(.a2ui-dark) {
color-scheme: dark;
}
:where(.a2ui-light) {
color-scheme: light;
}
:where(:root), :where(.a2ui-dark), :where(.a2ui-light) {
--a2ui-color-background: light-dark(#eee, #111);
--a2ui-color-on-background: light-dark(#333, #eee);
--a2ui-color-surface: light-dark(
color-mix(in oklab, var(--a2ui-color-background) 85%, white),
color-mix(in oklab, var(--a2ui-color-background) 95%, white)
);
--a2ui-color-on-surface: light-dark(#333, #eee);
--a2ui-color-primary: #17e;
--a2ui-color-primary-light: ${N("light",{colorVar:"--a2ui-color-primary"})};
--a2ui-color-primary-dark: ${N("dark",{colorVar:"--a2ui-color-primary"})};
--a2ui-color-primary-hover: ${N("hover",{darkVar:"--a2ui-color-primary-dark",lightVar:"--a2ui-color-primary-light"})};
--a2ui-color-on-primary: #fff;
--a2ui-color-secondary: light-dark(#ddd, #333);
--a2ui-color-secondary-light: ${N("light",{colorVar:"--a2ui-color-secondary"})};
--a2ui-color-secondary-dark: ${N("dark",{colorVar:"--a2ui-color-secondary",percentage:95})};
--a2ui-color-secondary-hover: ${N("hover",{darkVar:"--a2ui-color-secondary-dark",lightVar:"--a2ui-color-secondary-light"})};
--a2ui-color-on-secondary: light-dark(#333, #eee);
--a2ui-border-radius: 0.25rem;
--a2ui-color-border: light-dark(#ccc, #444);
--a2ui-border-width: 1px;
--a2ui-border: 1px solid var(--a2ui-color-border, #ccc);
--a2ui-font-family-title: inherit;
--a2ui-font-family-monospace: monospace;
--a2ui-color-input: light-dark(#fff, #2a2a2a);
--a2ui-color-on-input: light-dark(#333, #eee);
--a2ui-grid-base: 0.5rem;
--a2ui-spacing-xs: calc(var(--a2ui-spacing-s) / 2);
--a2ui-spacing-s: calc(var(--a2ui-spacing-m) / 2);
--a2ui-spacing-m: var(--a2ui-grid-base);
--a2ui-spacing-l: calc(var(--a2ui-spacing-m) * 2);
--a2ui-spacing-xl: calc(var(--a2ui-spacing-l) * 2);
--a2ui-font-size: 1rem;
--a2ui-font-scale: 1.2;
--a2ui-font-size-xs: calc(var(--a2ui-font-size-s) / var(--a2ui-font-scale));
--a2ui-font-size-s: calc(var(--a2ui-font-size-m) / var(--a2ui-font-scale));
--a2ui-font-size-m: var(--a2ui-font-size);
--a2ui-font-size-l: calc(var(--a2ui-font-size-m) * var(--a2ui-font-scale));
--a2ui-font-size-xl: calc(var(--a2ui-font-size-l) * var(--a2ui-font-scale));
--a2ui-font-size-2xl: calc(var(--a2ui-font-size-xl) * var(--a2ui-font-scale));
--a2ui-line-height-headings: 1.2;
--a2ui-line-height-body: 1.5;
}
`;let B;function na(){return B||(B=new CSSStyleSheet,B.replaceSync(ta)),B}function aa(){if(typeof document>"u")return;const e=na();document.adoptedStyleSheets.includes(e)||(document.adoptedStyleSheets=[...document.adoptedStyleSheets,e])}function N(e,t){switch(e){case"light":{const n=t;return`color-mix(in oklab, var(${n.colorVar}) ${n.percentage??85}%, ${n.mixColor??"white"})`}case"dark":{const n=t;return`color-mix(in oklab, var(${n.colorVar}) ${n.percentage??85}%, ${n.mixColor??"black"})`}case"hover":{const n=t;return`light-dark(var(${n.darkVar}), var(${n.lightVar}))`}}}var xe=y.memo(({surface:e,id:t,basePath:n,compImpl:a,componentModel:r})=>{const i=a.render,o=y.useMemo(()=>new Pe(e,t,n),[e,t,n,r]),c=y.useCallback((h,l)=>{const d=l||o.dataContext.path;return s.jsx(ne,{surface:e,id:h,basePath:d},`${h}-${d}`)},[e,o.dataContext.path]);return s.jsx(i,{context:o,buildChild:c})});xe.displayName="ResolvedChild";var ne=y.memo(({surface:e,id:t,basePath:n})=>{const a=y.useMemo(()=>{let o=0;return{subscribe:c=>{const h=e.componentsModel.onCreated.subscribe(d=>{d.id===t&&(o++,c())}),l=e.componentsModel.onDeleted.subscribe(d=>{d===t&&(o++,c())});return()=>{h.unsubscribe(),l.unsubscribe()}},getSnapshot:()=>{const c=e.componentsModel.get(t);return c?`${c.type}-${o}`:`missing-${o}`}}},[e,t]);y.useSyncExternalStore(a.subscribe,a.getSnapshot);const r=e.componentsModel.get(t);if(!r)return s.jsxs("div",{style:{color:"gray",padding:"4px"},children:["[Loading ",t,"...]"]});const i=e.catalog.components.get(r.type);return i?s.jsx(xe,{surface:e,id:t,basePath:n,componentModel:r,compImpl:i}):s.jsxs("div",{style:{color:"red"},children:["Unknown component: ",r.type]})});ne.displayName="DeferredChild";var ja=({surface:e})=>s.jsx(ne,{surface:e,id:"root",basePath:"/"});function w(e,t){const n=y.memo(t,(r,i)=>!(r.props!==i.props||r.context.componentModel.id!==i.context.componentModel.id||r.context.dataContext.path!==i.context.dataContext.path)),a=({context:r,buildChild:i})=>{const o=y.useRef(null);o.current?o.current.context!==r&&(o.current.dispose(),o.current=new ie(r,e.schema)):o.current=new ie(r,e.schema);const c=o.current,h=y.useCallback(f=>{const M=c.subscribe(f);return()=>M.unsubscribe()},[c]),l=y.useCallback(()=>c.snapshot,[c]),d=y.useSyncExternalStore(h,l);return y.useEffect(()=>()=>c.dispose(),[c]),s.jsx(n,{props:d||{},buildChild:i,context:r})};return{name:e.name,schema:e.schema,render:a}}function Na(e,t){return{name:e.name,schema:e.schema,render:t}}var k=()=>{y.useEffect(()=>{typeof document<"u"&&document.adoptedStyleSheets&&aa()},[])},we=e=>{switch(e){case"center":return"center";case"end":return"flex-end";case"spaceAround":return"space-around";case"spaceBetween":return"space-between";case"spaceEvenly":return"space-evenly";case"start":return"flex-start";case"stretch":return"stretch";default:return"flex-start"}},ae=e=>{switch(e){case"start":return"flex-start";case"center":return"center";case"end":return"flex-end";case"stretch":return"stretch";default:return"stretch"}},Q=()=>({boxSizing:"border-box"}),ra=()=>({boxSizing:"border-box"}),_=e=>typeof e!="number"?{}:{flex:`${e}`,minWidth:0,minHeight:0},ia=y.createContext(void 0),oa=()=>y.useContext(ia),fe=!1;function sa(e,t){const n=oa(),[a,r]=y.useState(null),i=JSON.stringify(t);return y.useEffect(()=>{if(!n){fe||(console.warn("[useMarkdown]",`can't render markdown because no markdown renderer is configured.
`,"Use `@a2ui/markdown-it`, or your own markdown renderer."),fe=!0),r(null);return}let o=!0;const c=i?JSON.parse(i):void 0;return n(e,c).then(h=>{o&&r(h)}).catch(h=>{console.error("[useMarkdown] Render failed:",h)}),()=>{o=!1}},[e,n,i]),a}var pe={},ca=(e,t)=>{switch(t){case"h1":return`# ${e}`;case"h2":return`## ${e}`;case"h3":return`### ${e}`;case"h4":return`#### ${e}`;case"h5":return`##### ${e}`;case"caption":return`*${e}*`;default:return e}},ua=w(Fn,({props:e})=>{k();const t=typeof e.text=="string"?e.text:String(e.text??""),n=ca(t,e.variant),a=sa(n),r={...Q(),..._(e.weight)},i=e.variant==="caption",o=[pe.a2uiText,i?pe.a2uiCaption:e.variant||"body"];a===null&&o.push("no-markdown-renderer");const c=a!==null?{dangerouslySetInnerHTML:{__html:a}}:{children:n};return i?s.jsx("span",{className:o.join(" "),style:r,...c}):s.jsx("div",{className:o.join(" "),style:r,...c})}),la=w(zn,({props:e})=>{k();const t=a=>a==="scaleDown"?"scale-down":a||"fill",n={...Q(),..._(e.weight),objectFit:t(e.fit),display:"block",borderRadius:"var(--a2ui-image-border-radius, 0)"};return e.variant==="icon"?(n.width="var(--a2ui-image-icon-size, 24px)",n.height="var(--a2ui-image-icon-size, 24px)"):e.variant==="avatar"?(n.width="var(--a2ui-image-avatar-size, 40px)",n.height="var(--a2ui-image-avatar-size, 40px)",n.borderRadius="50%"):e.variant==="smallFeature"?n.maxWidth="var(--a2ui-image-small-feature-size, 100px)":e.variant==="largeFeature"?n.maxHeight="var(--a2ui-image-large-feature-size, 400px)":e.variant==="header"&&(n.height="var(--a2ui-image-header-size, 200px)",n.objectFit="cover"),s.jsx("img",{src:e.url,alt:e.description||"",style:n})}),da={play:"play_arrow",rewind:"fast_rewind",favoriteOff:"favorite_border",starOff:"star_border"};function ha(e){return da[e]??e.replace(/[A-Z]/g,t=>"_"+t.toLowerCase())}var ma=w(Yn,({props:e})=>{k();const t=typeof e.name=="object"&&e.name!==null&&"svgPath"in e.name,n={...Q(),display:"inline-flex",alignItems:"center",justifyContent:"center",fontSize:"var(--a2ui-icon-size, var(--a2ui-font-size-xl, 24px))",color:"var(--a2ui-icon-color, inherit)",lineHeight:1};if(t){const i=e.name.svgPath;return s.jsx("svg",{className:"a2ui-icon svg",viewBox:"0 0 24 24",style:{...n,fill:"currentColor",width:"var(--a2ui-icon-size, 24px)",height:"var(--a2ui-icon-size, 24px)"},children:s.jsx("path",{d:i})})}const a=typeof e.name=="string"?ha(e.name):"",r={...n,fontFamily:'var(--a2ui-icon-font-family, "Material Symbols Outlined", sans-serif)',fontVariationSettings:'var(--a2ui-icon-font-variation-settings, "FILL" 1)',fontWeight:"normal",fontStyle:"normal",letterSpacing:"normal",textTransform:"none"};return s.jsx("span",{className:"material-symbols-outlined",style:r,children:a})}),fa=w(qn,({props:e})=>{k();const t={...Q(),width:"100%",height:"auto",borderRadius:"var(--a2ui-video-border-radius, 0)"};return s.jsx("video",{src:e.url,controls:!0,style:t})}),pa=w($n,({props:e})=>{k();const t={display:"flex",flexDirection:"column",gap:"var(--a2ui-spacing-xs, 0.25rem)",background:"var(--a2ui-audioplayer-background, transparent)",borderRadius:"var(--a2ui-audioplayer-border-radius, 0)",padding:"var(--a2ui-audioplayer-padding, 0)"};return s.jsxs("div",{style:t,children:[e.description&&s.jsx("span",{style:{fontSize:"var(--a2ui-font-size-xs, 0.75rem)",color:"var(--a2ui-text-caption-color, light-dark(#666, #aaa))"},children:e.description}),s.jsx("audio",{src:e.url,controls:!0})]})}),re=({childList:e,buildChild:t})=>Array.isArray(e)?s.jsx(s.Fragment,{children:e.map((n,a)=>{if(n&&typeof n=="object"&&"id"in n){const r=n;return s.jsx(W.Fragment,{children:t(r.id,r.basePath)},`${r.id}-${a}`)}return typeof n=="string"?s.jsx(W.Fragment,{children:t(n)},`${n}-${a}`):null})}):null,ba=w(Ln,({props:e,buildChild:t,context:n})=>(k(),s.jsx("div",{style:{..._(e.weight),display:"flex",flexDirection:"row",justifyContent:we(e.justify),alignItems:ae(e.align),gap:"var(--a2ui-row-gap, var(--a2ui-spacing-m))"},children:s.jsx(re,{childList:e.children,buildChild:t,context:n})}))),ga=w(_n,({props:e,buildChild:t,context:n})=>(k(),s.jsx("div",{style:{..._(e.weight),display:"flex",flexDirection:"column",justifyContent:we(e.justify),alignItems:ae(e.align),gap:"var(--a2ui-column-gap, var(--a2ui-spacing-m))"},children:s.jsx(re,{childList:e.children,buildChild:t,context:n})}))),ya=w(Bn,({props:e,buildChild:t,context:n})=>{k();const a=e.direction==="horizontal",r={display:"flex",flexDirection:a?"row":"column",alignItems:ae(e.align),overflowX:a?"auto":"hidden",overflowY:a?"hidden":"auto",gap:"var(--a2ui-list-gap, var(--a2ui-spacing-s))",padding:"var(--a2ui-list-padding, 0)"};return s.jsx("div",{style:r,children:s.jsx(re,{childList:e.children,buildChild:t,context:n})})}),va=w(Vn,({props:e,buildChild:t})=>{k();const n={...ra(),..._(e.weight),display:"block",border:"var(--a2ui-card-border, var(--a2ui-border))",borderRadius:"var(--a2ui-card-border-radius, var(--a2ui-border-radius, 8px))",padding:"var(--a2ui-card-padding, var(--a2ui-spacing-m, 16px))",background:"var(--a2ui-card-b Line truncated
@@ -35,6 +35,60 @@ function el(tag, attrs, children) {
return node;
}
function relativeTime(value) {
if (!value) return '';
const then = typeof value === 'number' ? value * 1000 : Date.parse(value);
if (!then || Number.isNaN(then)) return '';
const secs = Math.max(0, Math.floor((Date.now() - then) / 1000));
if (secs < 60) return `${secs}s ago`;
const mins = Math.floor(secs / 60);
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}h ago`;
const days = Math.floor(hrs / 24);
return `${days}d ago`;
}
function escapeHtml(text) {
return String(text == null ? '' : text)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
function renderMarkdown(text) {
const safe = escapeHtml(text);
const lines = safe.split(/\r?\n/);
const html = [];
let inCode = false;
let listOpen = false;
const closeList = () => { if (listOpen) { html.push('</ul>'); listOpen = false; } };
lines.forEach((raw) => {
if (/^```/.test(raw.trim())) {
if (inCode) { html.push('</code></pre>'); inCode = false; }
else { closeList(); html.push('<pre><code>'); inCode = true; }
return;
}
if (inCode) { html.push(raw + '\n'); return; }
let line = raw;
line = line.replace(/`([^`]+)`/g, '<code>$1</code>');
line = line.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
line = line.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>');
const heading = line.match(/^(#{1,3})\s+(.*)$/);
if (heading) { closeList(); html.push(`<h${heading[1].length}>${heading[2]}</h${heading[1].length}>`); return; }
const bullet = line.match(/^\s*[-*]\s+(.*)$/);
if (bullet) { if (!listOpen) { html.push('<ul>'); listOpen = true; } html.push(`<li>${bullet[1]}</li>`); return; }
closeList();
if (line.trim() === '') html.push('<br/>');
else html.push(`<p>${line}</p>`);
});
closeList();
if (inCode) html.push('</code></pre>');
return html.join('');
}
function cardElement(card, { selectable, selected, onSelect, onOpen }) {
const wrap = el('div', {
className: `aiui-board-card${selected ? ' aiui-board-card-selected' : ''}`,
@@ -42,18 +96,48 @@ function cardElement(card, { selectable, selected, onSelect, onOpen }) {
'data-task-id': card.id || '',
'data-status': card.status || '',
});
const inner = el('div', { className: 'db-card' });
if (card.title) inner.appendChild(el('div', { className: 'db-card-title', text: card.title }));
if (card.value != null) inner.appendChild(el('div', { className: 'db-card-value', text: String(card.value) }));
const footer = card.footer || card.assignee || '';
if (footer) inner.appendChild(el('div', { className: 'db-card-footer', text: footer }));
const inner = el('div', { className: 'db-card aiui-board-card-dense' });
const head = el('div', { className: 'aiui-board-card-head' });
if (selectable && onSelect) {
const box = el('input', {
type: 'checkbox',
className: 'aiui-board-card-check',
'aria-label': 'Select task',
});
box.checked = !!selected;
box.addEventListener('click', (e) => { e.stopPropagation(); onSelect(card.id, true); });
head.appendChild(box);
}
if (card.id) head.appendChild(el('span', { className: 'aiui-board-card-id', text: card.id }));
const colour = STATUS_COLOURS[card.status];
if (card.status) {
const dot = el('span', { className: 'aiui-board-status-dot', text: '●', title: card.status });
if (colour) dot.style.color = colour;
head.appendChild(dot);
}
if (card.priority) {
inner.appendChild(el('span', {
className: 'aiui-board-badge',
text: String(card.priority),
style: 'font-size:10px;margin-left:6px;opacity:0.8',
head.appendChild(el('span', { className: 'aiui-board-badge aiui-board-priority', text: String(card.priority) }));
}
inner.appendChild(head);
if (card.title) inner.appendChild(el('div', { className: 'db-card-title aiui-board-card-name', text: card.title }));
if (card.value != null) inner.appendChild(el('div', { className: 'db-card-value', text: String(card.value) }));
const foot = el('div', { className: 'aiui-board-card-foot' });
if (card.tenant) foot.appendChild(el('span', { className: 'aiui-board-chip aiui-board-tenant', text: String(card.tenant) }));
if (card.assignee) foot.appendChild(el('span', { className: 'aiui-board-chip', text: String(card.assignee) }));
const age = relativeTime(card.created_at);
if (age) foot.appendChild(el('span', { className: 'aiui-board-card-age', text: age }));
if (card.comment_count) foot.appendChild(el('span', { className: 'aiui-board-card-count', text: `\uD83D\uDCAC ${card.comment_count}` }));
if (card.progress && card.progress.total) {
foot.appendChild(el('span', {
className: 'aiui-board-progress',
text: `${card.progress.done}/${card.progress.total}`,
}));
}
if (foot.childNodes.length) inner.appendChild(foot);
wrap.appendChild(inner);
wrap.addEventListener('click', (e) => {
if (e.shiftKey && onSelect) onSelect(card.id, true);
@@ -148,7 +232,9 @@ function renderBoardDOM(container, data, opts) {
e.preventDefault();
trash.classList.remove('aiui-board-drop-target');
const taskId = e.dataTransfer.getData('text/plain');
if (taskId && opts.onDelete) await opts.onDelete(taskId);
if (!taskId || !opts.onDelete) return;
if (typeof confirm === 'function' && !confirm('Permanently delete this task?')) return;
await opts.onDelete(taskId);
});
wrap.appendChild(trash);
}
@@ -156,46 +242,170 @@ function renderBoardDOM(container, data, opts) {
return wrap;
}
function openTaskDrawer(card, { apiBase, onClose, onRefresh }) {
function historyRows(card) {
const meta = card.meta || {};
const rows = [];
const events = meta.events || meta.history;
if (Array.isArray(events)) {
events.forEach((ev) => {
if (typeof ev === 'string') rows.push({ text: ev });
else if (ev && typeof ev === 'object') {
const label = ev.type || ev.event || ev.status || 'event';
const when = relativeTime(ev.ts || ev.created_at || ev.time);
rows.push({ text: ev.text || ev.message || label, when });
}
});
}
return rows;
}
function openTaskDrawer(card, { apiBase, onClose, onRefresh, fetchTask }) {
const overlay = el('div', { className: 'aiui-board-drawer-overlay' });
const drawer = el('div', { className: 'aiui-board-drawer' });
const close = () => overlay.remove();
drawer.appendChild(el('button', {
className: 'aiui-board-drawer-close',
text: '×',
onclick: close,
}));
drawer.appendChild(el('h3', { text: card.title || 'Task' }));
if (card.body) drawer.appendChild(el('pre', { className: 'aiui-board-drawer-body', text: card.body }));
else drawer.appendChild(el('p', { className: 'aiui-board-drawer-meta', text: `ID: ${card.id || '—'}` }));
const meta = el('div', { className: 'aiui-board-drawer-meta' });
meta.textContent = [card.status, card.assignee, card.priority].filter(Boolean).join(' · ');
drawer.appendChild(meta);
let current = card;
let closed = false;
const close = () => { closed = true; overlay.remove(); if (onClose) onClose(); };
if (apiBase && card.id) {
const actions = el('div', { className: 'aiui-board-drawer-actions' });
['blocked', 'ready', 'done'].forEach((st) => {
actions.appendChild(el('button', {
className: 'db-btn db-btn-sm',
text: st,
onclick: async () => {
await fetch(`${apiBase}/tasks/${card.id}/move`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: st }),
});
if (onRefresh) await onRefresh();
close();
if (onClose) onClose();
},
}));
const editable = !!(apiBase && card.id);
async function patch(data) {
if (!editable) return;
await fetch(`${apiBase}/tasks/${current.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
drawer.appendChild(actions);
if (onRefresh) await onRefresh();
await reload();
}
async function reload() {
if (closed) return;
if (fetchTask && current.id) {
try {
const fresh = await fetchTask(current.id);
if (fresh) current = { ...current, ...fresh };
} catch (_) { /* keep stale */ }
}
render();
}
function field(label, value, onSave) {
const row = el('div', { className: 'aiui-board-drawer-field' });
row.appendChild(el('label', { className: 'aiui-board-drawer-label', text: label }));
const input = el('input', { className: 'aiui-board-drawer-input', value: value == null ? '' : String(value) });
if (!editable) input.disabled = true;
input.addEventListener('change', () => onSave(input.value));
input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); onSave(input.value); } });
row.appendChild(input);
return row;
}
function render() {
drawer.innerHTML = '';
drawer.appendChild(el('button', { className: 'aiui-board-drawer-close', text: '×', onclick: close }));
drawer.appendChild(field('Title', current.title, (v) => patch({ title: v })));
if (current.body != null) {
const bodyEl = el('div', { className: 'aiui-board-drawer-body aiui-board-markdown' });
bodyEl.innerHTML = renderMarkdown(current.body);
drawer.appendChild(bodyEl);
}
const grid = el('div', { className: 'aiui-board-drawer-grid' });
grid.appendChild(field('Assignee', current.assignee, (v) => patch({ assignee: v })));
grid.appendChild(field('Priority', current.priority, (v) => patch({ priority: v })));
drawer.appendChild(grid);
const meta = el('div', { className: 'aiui-board-drawer-meta' });
meta.textContent = [current.status, current.tenant, relativeTime(current.created_at)].filter(Boolean).join(' · ');
drawer.appendChild(meta);
const hist = historyRows(current);
if (hist.length) {
drawer.appendChild(el('h4', { className: 'aiui-board-drawer-h', text: 'History' }));
const histWrap = el('div', { className: 'aiui-board-history' });
hist.forEach((r) => {
const row = el('div', { className: 'aiui-board-history-row' });
row.appendChild(el('span', { className: 'aiui-board-history-text', text: r.text }));
if (r.when) row.appendChild(el('span', { className: 'aiui-board-history-when', text: r.when }));
histWrap.appendChild(row);
});
drawer.appendChild(histWrap);
}
drawer.appendChild(el('h4', { className: 'aiui-board-drawer-h', text: 'Comments' }));
const thread = el('div', { className: 'aiui-board-comments' });
(current.comments || []).forEach((c) => {
const item = el('div', { className: 'aiui-board-comment' });
const head = el('div', { className: 'aiui-board-comment-head' });
head.appendChild(el('span', { className: 'aiui-board-comment-author', text: c.author || 'human' }));
const when = relativeTime(c.created_at);
if (when) head.appendChild(el('span', { className: 'aiui-board-comment-when', text: when }));
item.appendChild(head);
const text = el('div', { className: 'aiui-board-comment-text aiui-board-markdown' });
text.innerHTML = renderMarkdown(typeof c === 'string' ? c : (c.text || ''));
item.appendChild(text);
thread.appendChild(item);
});
drawer.appendChild(thread);
if (editable) {
const box = el('textarea', {
className: 'aiui-board-comment-input',
placeholder: 'Add a comment… (Enter to submit, Shift+Enter for newline)',
rows: '2',
});
const submit = async () => {
const value = box.value.trim();
if (!value) return;
box.value = '';
await fetch(`${apiBase}/tasks/${current.id}/comments`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: value }),
});
if (onRefresh) await onRefresh();
await reload();
};
box.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); submit(); }
});
drawer.appendChild(box);
const actions = el('div', { className: 'aiui-board-drawer-actions' });
['blocked', 'ready', 'done'].forEach((st) => {
actions.appendChild(el('button', {
className: 'db-btn db-btn-sm',
text: st,
onclick: async () => {
if ((st === 'done' || st === 'blocked') && !confirm(`Move task to "${st}"?`)) return;
await fetch(`${apiBase}/tasks/${current.id}/move`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: st }),
});
if (onRefresh) await onRefresh();
close();
},
}));
});
drawer.appendChild(actions);
}
}
render();
overlay.appendChild(drawer);
overlay.addEventListener('click', (e) => { if (e.target === overlay) close(); });
document.body.appendChild(overlay);
return {
id: card.id,
isOpen: () => !closed,
refresh: reload,
close,
};
}
/**
@@ -205,8 +415,11 @@ export function createInteractiveBoard(root, opts = {}) {
let destroyed = false;
let pollTimer = null;
let eventSource = null;
let debounceTimer = null;
let openDrawer = null;
const selected = new Set();
let lastData = null;
const debounceMs = opts.debounceMs != null ? opts.debounceMs : 300;
const toolbar = el('div', { className: 'aiui-board-toolbar' });
root.appendChild(toolbar);
@@ -231,6 +444,14 @@ export function createInteractiveBoard(root, opts = {}) {
if (!res.ok) throw new Error(await res.text());
}
const drawerApiBase = (opts.drawerApiBase || apiBase).replace(/\/$/, '');
async function fetchTask(taskId) {
const res = await fetch(`${drawerApiBase}/tasks/${taskId}`);
if (!res.ok) return null;
return res.json();
}
async function defaultBulk(status) {
if (!selected.size) return;
const res = await fetch(`${apiBase}/tasks/bulk`, {
@@ -303,7 +524,15 @@ export function createInteractiveBoard(root, opts = {}) {
showTrash: opts.showTrash !== false,
selected,
onSelect: opts.interactive !== false ? onSelect : null,
onOpen: (card) => openTaskDrawer(card, { apiBase: opts.drawerApiBase || apiBase, onRefresh: refresh }),
onOpen: (card) => {
if (openDrawer && openDrawer.isOpen()) openDrawer.close();
openDrawer = openTaskDrawer(card, {
apiBase: drawerApiBase,
onRefresh: refresh,
fetchTask,
onClose: () => { openDrawer = null; },
});
},
onMove: async (taskId, status) => {
await (opts.onMove || defaultMove)(taskId, status);
await refresh();
@@ -319,12 +548,28 @@ export function createInteractiveBoard(root, opts = {}) {
}
}
function debouncedRefresh() {
if (!debounceMs) { refresh(); return; }
if (debounceTimer) clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => { debounceTimer = null; refresh(); }, debounceMs);
}
function handleEvent(raw) {
let ev = null;
try { ev = raw ? JSON.parse(raw) : null; } catch (_) { ev = null; }
if (openDrawer && openDrawer.isOpen() && ev && ev.task_id && ev.task_id === openDrawer.id) {
openDrawer.refresh();
}
debouncedRefresh();
}
function connectEvents() {
if (!opts.eventsUrl || typeof EventSource === 'undefined') return;
const url = opts.eventsUrl + (opts.eventsUrl.includes('?') ? '&' : '?') + `board=${encodeURIComponent(opts.currentBoard || 'default')}`;
const activeBoard = opts.boardState?.id || opts.currentBoard || 'default';
const url = opts.eventsUrl + (opts.eventsUrl.includes('?') ? '&' : '?') + `board=${encodeURIComponent(activeBoard)}`;
try {
eventSource = new EventSource(url);
eventSource.onmessage = () => refresh();
eventSource.onmessage = (e) => handleEvent(e && e.data);
} catch (_) { /* polling fallback */ }
}
@@ -337,7 +582,9 @@ export function createInteractiveBoard(root, opts = {}) {
destroy() {
destroyed = true;
if (pollTimer) clearInterval(pollTimer);
if (debounceTimer) clearTimeout(debounceTimer);
if (eventSource) eventSource.close();
if (openDrawer && openDrawer.isOpen()) openDrawer.close();
root.innerHTML = '';
},
};
@@ -348,4 +595,4 @@ export function createBoard(root, opts = {}) {
return createInteractiveBoard(root, { ...opts, interactive: false, showTrash: false });
}
export { renderBoardDOM, el };
export { renderBoardDOM, el, renderMarkdown, relativeTime, escapeHtml };
@@ -9,6 +9,123 @@
// Record load time for overview stats
window.__aiuiLoadTime = window.__aiuiLoadTime || Date.now();
// ── Approval Modal System ────────────────────────────────────────────
// Handles approval_required events from SSE stream
let approvalModalShown = false;
let pendingApprovals = [];
let approvalSSESource = null;
function showApprovalModal(approval) {
if (approvalModalShown) {
pendingApprovals.push(approval);
return;
}
approvalModalShown = true;
const overlay = document.createElement('div');
overlay.className = 'db-modal-overlay';
overlay.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center;animation:fadeIn 0.2s';
const modal = document.createElement('div');
modal.className = 'db-approval-modal';
modal.style.cssText = 'background:var(--db-card-bg);border:1px solid var(--db-border);border-radius:12px;padding:24px;max-width:500px;width:90%;max-height:80vh;overflow-y:auto;animation:slideIn 0.2s;backdrop-filter:blur(10px)';
const riskIcons = {low: '✅', medium: '⚠️', high: '🟠', critical: '🔴'};
const icon = riskIcons[approval.risk_level] || '⚠️';
modal.innerHTML = `
<h2 style="margin:0 0 16px;font-size:20px;font-weight:600;color:var(--db-text)">Tool Execution Approval Required</h2>
<div style="padding:16px;background:rgba(255,255,255,0.03);border-radius:8px;margin-bottom:20px">
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
<span style="font-size:24px">${icon}</span>
<div>
<div style="font-weight:500;font-size:16px">${approval.tool_name || 'Unknown Tool'}</div>
<div style="font-size:13px;color:var(--db-text-dim);margin-top:4px">Risk Level: ${approval.risk_level || 'medium'}</div>
</div>
</div>
${approval.agent_name ? `<div style="font-size:13px;color:var(--db-text-dim);margin-top:8px">Agent: ${approval.agent_name}</div>` : ''}
${approval.description ? `<div style="font-size:13px;margin-top:8px">${approval.description}</div>` : ''}
${approval.arguments ? `<details style="margin-top:12px"><summary style="cursor:pointer;font-size:13px;color:var(--db-text-dim)">Arguments</summary><pre style="font-size:11px;margin:8px 0 0;padding:8px;background:rgba(0,0,0,0.2);border-radius:4px;overflow-x:auto">${JSON.stringify(approval.arguments, null, 2)}</pre></details>` : ''}
</div>
<div style="margin-bottom:16px">
<label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px">
<input type="checkbox" id="approval-always" style="cursor:pointer">
<span>Always ${approval.risk_level === 'critical' ? 'deny' : 'allow'} this tool</span>
</label>
</div>
<div style="display:flex;gap:12px;justify-content:flex-end">
<button id="approval-deny" style="padding:8px 20px;background:rgba(239,68,68,0.15);color:#ef4444;border:1px solid rgba(239,68,68,0.3);border-radius:6px;cursor:pointer;font-size:14px;font-weight:500">✗ Deny</button>
<button id="approval-approve" style="padding:8px 20px;background:rgba(34,197,94,0.15);color:#22c55e;border:1px solid rgba(34,197,94,0.3);border-radius:6px;cursor:pointer;font-size:14px;font-weight:500">✓ Approve</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
async function resolveApproval(approved) {
const always = document.getElementById('approval-always').checked;
const endpoint = approved ? `/api/approvals/${approval.id}/approve` : `/api/approvals/${approval.id}/deny`;
try {
await fetch(endpoint, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({always, reason: approved ? 'Approved via UI' : 'Denied via UI'})
});
} catch (e) {
console.error('Failed to resolve approval:', e);
}
overlay.style.animation = 'fadeOut 0.2s';
setTimeout(() => {
overlay.remove();
approvalModalShown = false;
if (pendingApprovals.length > 0) {
showApprovalModal(pendingApprovals.shift());
}
}, 200);
}
document.getElementById('approval-approve').addEventListener('click', () => resolveApproval(true));
document.getElementById('approval-deny').addEventListener('click', () => resolveApproval(false));
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
resolveApproval(false);
}
});
}
function initApprovalStream() {
if (approvalSSESource) return;
try {
approvalSSESource = new EventSource('/api/approvals/stream');
approvalSSESource.onmessage = (event) => {
try {
const data = JSON.parse(event.data);
if (data.event === 'new' && data.data) {
showApprovalModal(data.data);
}
} catch (e) {
console.error('Failed to parse approval SSE:', e);
}
};
approvalSSESource.onerror = () => {
console.warn('Approval SSE connection error, retrying...');
if (approvalSSESource) {
approvalSSESource.close();
approvalSSESource = null;
}
setTimeout(initApprovalStream, 5000);
};
} catch (e) {
console.error('Failed to init approval stream:', e);
}
}
// ── Extensible Component Registry ────────────────────────────────────
// Maps component type names to custom render functions.
// Custom renderers take priority over the built-in switch in renderComponent().
@@ -57,6 +174,7 @@ const BUILTIN_VIEWS = {
'jobs-board': '/plugins/views/jobs-board.js',
auth: '/plugins/views/auth.js',
api: '/plugins/views/api.js',
mcp: '/plugins/views/mcp.js',
};
const SLOT_REGISTRY = {};
@@ -290,6 +408,37 @@ const DASHBOARD_STYLE = `
.aiui-board-drawer-body { white-space: pre-wrap; font-size: 13px; color: var(--db-text-dim); }
.aiui-board-drawer-meta { font-size: 12px; color: var(--db-text-dim); margin: 8px 0; }
.aiui-board-drawer-actions { display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap; }
.aiui-board-card-dense { padding: 10px 12px; }
.aiui-board-card-head { display: flex; align-items: center; gap: 6px; margin-bottom: 4px; }
.aiui-board-card-check { margin: 0; cursor: pointer; }
.aiui-board-card-id { font-family: var(--db-mono, monospace); font-size: 10px; color: var(--db-text-dim); }
.aiui-board-status-dot { font-size: 10px; line-height: 1; }
.aiui-board-priority { margin-left: auto; font-size: 10px; opacity: 0.85; }
.aiui-board-card-name { font-size: 13px; text-transform: none; letter-spacing: 0; color: var(--db-text); margin: 0 0 6px; font-weight: 500; }
.aiui-board-card-foot { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 11px; color: var(--db-text-dim); }
.aiui-board-chip { padding: 1px 6px; border-radius: 999px; background: rgba(255,255,255,0.06); font-size: 10px; }
.aiui-board-tenant { background: var(--db-accent-glow); color: var(--db-accent); }
.aiui-board-card-age { opacity: 0.8; }
.aiui-board-card-count { opacity: 0.9; }
.aiui-board-progress { margin-left: auto; padding: 1px 6px; border-radius: 999px; border: 1px solid var(--db-border); font-size: 10px; }
.aiui-board-drawer-field { margin: 8px 0; }
.aiui-board-drawer-label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--db-text-dim); margin-bottom: 4px; }
.aiui-board-drawer-input { width: 100%; padding: 6px 8px; background: var(--db-card-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); color: var(--db-text); font-size: 13px; }
.aiui-board-drawer-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.aiui-board-drawer-h { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--db-text-dim); margin: 16px 0 8px; }
.aiui-board-markdown { font-size: 13px; line-height: 1.5; color: var(--db-text-dim); }
.aiui-board-markdown p { margin: 0 0 6px; }
.aiui-board-markdown pre { background: var(--db-card-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); padding: 8px; overflow-x: auto; }
.aiui-board-markdown code { font-family: var(--db-mono, monospace); font-size: 12px; }
.aiui-board-history { display: flex; flex-direction: column; gap: 4px; }
.aiui-board-history-row { display: flex; justify-content: space-between; gap: 8px; font-size: 12px; color: var(--db-text-dim); }
.aiui-board-history-when { opacity: 0.7; white-space: nowrap; }
.aiui-board-comments { display: flex; flex-direction: column; gap: 10px; }
.aiui-board-comment { border-left: 2px solid var(--db-border); padding-left: 10px; }
.aiui-board-comment-head { display: flex; gap: 8px; align-items: baseline; }
.aiui-board-comment-author { font-size: 12px; font-weight: 600; color: var(--db-text); }
.aiui-board-comment-when { font-size: 11px; color: var(--db-text-dim); }
.aiui-board-comment-input { width: 100%; margin-top: 10px; padding: 8px; background: var(--db-card-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); color: var(--db-text); font-size: 13px; resize: vertical; }
.aiui-board-page-header { margin-bottom: 8px; }
.db-slot-host { min-height: 0; }
.db-sidebar-footer { margin-top: auto; padding: 10px 14px; border-top: 1px solid var(--db-border); font-size: 11px; color: var(--db-text-dim); }
@@ -576,6 +725,19 @@ const DASHBOARD_STYLE = `
.db-dialog-title { font-size: 18px; font-weight: 600; margin: 0 0 4px; }
.db-dialog-desc { font-size: 13px; color: var(--db-text-dim); margin: 0 0 16px; }
/* ── Session Search Palette (Ctrl+K) ─────────────────── */
.db-session-search-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); z-index: 300; display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; backdrop-filter: blur(4px); }
.db-session-search { background: var(--db-sidebar-bg); border: 1px solid var(--db-border); border-radius: 12px; width: 560px; max-width: 90vw; max-height: 60vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 12px 40px rgba(0,0,0,0.4); }
.db-session-search-input { width: 100%; padding: 16px 18px; font-size: 15px; color: var(--db-text); background: transparent; border: none; border-bottom: 1px solid var(--db-border); outline: none; box-sizing: border-box; }
.db-session-search-list { overflow-y: auto; padding: 6px; }
.db-session-search-empty { padding: 24px; text-align: center; font-size: 13px; color: var(--db-text-dim); }
.db-session-search-group { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--db-text-dim); padding: 10px 12px 4px; }
.db-session-search-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border-radius: 8px; cursor: pointer; }
.db-session-search-item:hover, .db-session-search-item.active { background: var(--db-hover); }
.db-session-search-item-title { font-size: 14px; font-weight: 500; color: var(--db-text); }
.db-session-search-item-meta { font-size: 12px; color: var(--db-text-dim); }
.db-session-search-item-current { font-size: 11px; padding: 2px 8px; border-radius: 6px; background: var(--db-accent); color: #fff; }
/* ── Caption ─────────────────────────────────────────── */
.db-caption { font-size: 12px; color: var(--db-text-dim); margin-bottom: 8px; }
@@ -625,6 +787,25 @@ const DASHBOARD_STYLE = `
.db-popover { position: relative; display: inline-block; }
.db-popover-content { display: none; position: absolute; top: calc(100% + 8px); left: 0; min-width: 200px; padding: 16px; background: var(--db-sidebar-bg); border: 1px solid var(--db-border); border-radius: var(--db-radius); z-index: 100; box-shadow: 0 4px 20px rgba(0,0,0,0.3); }
.db-popover.open .db-popover-content { display: block; }
/* ── Approval Modal Animations ──────────────────────── */
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } }
@keyframes slideIn { from { transform: translateY(-20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
/* ── Command Palette (Ctrl+K) ────────────────────────── */
.db-cmdk-overlay { position: fixed; inset: 0; z-index: 10002; display: flex; align-items: flex-start; justify-content: center; padding-top: 12vh; background: rgba(0,0,0,0.5); backdrop-filter: blur(4px); animation: fadeIn 0.15s ease; }
.db-cmdk-box { width: 560px; max-width: 92vw; background: var(--db-sidebar-bg); border: 1px solid var(--db-border); border-radius: 12px; box-shadow: 0 16px 60px rgba(0,0,0,0.55); overflow: hidden; animation: slideIn 0.15s ease; }
.db-cmdk-input { width: 100%; box-sizing: border-box; padding: 16px 20px; font-size: 15px; color: var(--db-text); background: transparent; border: none; border-bottom: 1px solid var(--db-border); outline: none; }
.db-cmdk-input::placeholder { color: var(--db-text-dim); }
.db-cmdk-list { max-height: 50vh; overflow-y: auto; padding: 6px; }
.db-cmdk-item { display: flex; align-items: center; gap: 12px; padding: 10px 14px; border-radius: 8px; cursor: pointer; font-size: 14px; color: var(--db-text); }
.db-cmdk-item.active { background: var(--db-accent); color: #fff; }
.db-cmdk-item.active .db-cmdk-group { color: rgba(255,255,255,0.8); }
.db-cmdk-icon { width: 20px; text-align: center; flex-shrink: 0; }
.db-cmdk-title { flex: 1; }
.db-cmdk-group { font-size: 11px; color: var(--db-text-dim); text-transform: uppercase; letter-spacing: 0.04em; }
.db-cmdk-empty { padding: 24px; text-align: center; font-size: 13px; color: var(--db-text-dim); }
`;
let activePageId = null;
@@ -787,6 +968,284 @@ function setupMobileNav(root, sidebar) {
}
}
let _paletteEl = null;
let _paletteActive = -1;
let _paletteResults = [];
function openCommandPalette() {
if (_paletteEl) { closeCommandPalette(); return; }
const overlay = document.createElement('div');
overlay.className = 'db-cmdk-overlay';
overlay.innerHTML = `
<div class="db-cmdk-box" role="dialog" aria-label="Command palette">
<input class="db-cmdk-input" type="text" placeholder="Search pages and actions…" autocomplete="off" spellcheck="false">
<div class="db-cmdk-list"></div>
</div>`;
document.body.appendChild(overlay);
_paletteEl = overlay;
const input = overlay.querySelector('.db-cmdk-input');
const listEl = overlay.querySelector('.db-cmdk-list');
const sessionAction = { id: '__sessions', title: 'Search sessions…', icon: '⌕', group: 'Actions', action: 'sessions' };
const renderResults = (q) => {
const query = q.trim().toLowerCase();
const candidates = pagesData
.filter((p) => p.id !== activePageId || query)
.concat([sessionAction]);
const items = candidates
.map((p) => ({ page: p, score: paletteScore(p, query) }))
.filter((x) => query === '' || x.score > 0)
.sort((a, b) => b.score - a.score || (a.page.order || 0) - (b.page.order || 0))
.slice(0, 50)
.map((x) => x.page);
_paletteResults = items;
_paletteActive = items.length ? 0 : -1;
listEl.innerHTML = items.length
? items
.map(
(p, i) =>
`<div class="db-cmdk-item${i === 0 ? ' active' : ''}" data-idx="${i}" data-id="${p.id}"><span class="db-cmdk-icon">${p.icon || '•'}</span><span class="db-cmdk-title">${p.title}</span>${p.group ? `<span class="db-cmdk-group">${p.group}</span>` : ''}</div>`
)
.join('')
: '<div class="db-cmdk-empty">No matches</div>';
listEl.querySelectorAll('.db-cmdk-item').forEach((el) => {
el.addEventListener('click', () => runPaletteItem(parseInt(el.dataset.idx, 10)));
el.addEventListener('mousemove', () => setPaletteActive(parseInt(el.dataset.idx, 10)));
});
};
input.addEventListener('input', () => renderResults(input.value));
input.addEventListener('keydown', (e) => {
if (e.key === 'ArrowDown') { e.preventDefault(); setPaletteActive(_paletteActive + 1); }
else if (e.key === 'ArrowUp') { e.preventDefault(); setPaletteActive(_paletteActive - 1); }
else if (e.key === 'Enter') { e.preventDefault(); runPaletteItem(_paletteActive); }
else if (e.key === 'Escape') { e.preventDefault(); closeCommandPalette(); }
});
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeCommandPalette(); });
renderResults('');
input.focus();
}
function paletteScore(page, query) {
if (!query) return 1;
const hay = `${page.title} ${page.id} ${page.group || ''} ${page.description || ''}`.toLowerCase();
if (hay.includes(query)) return 100 - hay.indexOf(query);
let qi = 0;
for (let i = 0; i < hay.length && qi < query.length; i++) {
if (hay[i] === query[qi]) qi++;
}
return qi === query.length ? 1 : 0;
}
function setPaletteActive(idx) {
if (!_paletteEl || !_paletteResults.length) return;
const n = _paletteResults.length;
_paletteActive = ((idx % n) + n) % n;
const items = _paletteEl.querySelectorAll('.db-cmdk-item');
items.forEach((el, i) => el.classList.toggle('active', i === _paletteActive));
const activeEl = items[_paletteActive];
if (activeEl) activeEl.scrollIntoView({ block: 'nearest' });
}
function runPaletteItem(idx) {
const page = _paletteResults[idx];
if (!page) return;
closeCommandPalette();
if (page.action === 'sessions') {
openSessionSearch();
return;
}
selectPage(page.id);
}
function closeCommandPalette() {
if (_paletteEl) { _paletteEl.remove(); _paletteEl = null; }
_paletteActive = -1;
_paletteResults = [];
}
function initCommandPalette() {
document.addEventListener('keydown', (e) => {
if ((e.ctrlKey || e.metaKey) && (e.key === 'k' || e.key === 'K')) {
e.preventDefault();
openCommandPalette();
}
});
}
// ── Session Search Palette ──────────────────────────────────────────
// Vanilla-JS port of the React SessionSearch / useSessionSearch behavior so
// that the dashboard style reaches C-01 parity. The palette lists sessions
// from GET /sessions with live filtering; Enter or click navigates to the
// chat page and loads the chosen session. It is reachable from the Ctrl+K
// command palette ("Search sessions…") and via window.aiui.openSessionSearch.
let sessionSearchOpen = false;
let sessionSearchOverlay = null;
let sessionSearchInitialized = false;
function getCurrentSessionId() {
try {
const params = new URLSearchParams(location.search);
return params.get('session') || null;
} catch (e) {
return null;
}
}
function selectSession(sessionId) {
closeSessionSearch();
// Navigate to the chat page first so its view mounts and registers the
// aiui:session-select listener, then dispatch on the next tick.
const navigate = (typeof selectPage === 'function')
? Promise.resolve(selectPage('chat'))
: Promise.resolve();
navigate.finally(() => {
window.dispatchEvent(new CustomEvent('aiui:session-select', { detail: { sessionId } }));
});
}
function renderSessionSearchList(listEl, sessions, query, currentSessionId) {
const q = (query || '').trim().toLowerCase();
const filtered = q
? sessions.filter((s) => (`${s.title || ''} ${s.id || ''}`).toLowerCase().includes(q))
: sessions;
listEl.innerHTML = '';
if (!filtered.length) {
const empty = document.createElement('div');
empty.className = 'db-session-search-empty';
empty.textContent = sessions.length ? 'No sessions matching your search.' : 'No sessions found. Start a chat to create one.';
listEl.appendChild(empty);
return;
}
filtered.forEach((session, i) => {
const item = document.createElement('div');
item.className = 'db-session-search-item' + (i === 0 ? ' active' : '');
item.dataset.sessionId = session.id || '';
const info = document.createElement('div');
const title = document.createElement('div');
title.className = 'db-session-search-item-title';
title.textContent = session.title || 'Untitled Session';
info.appendChild(title);
const meta = document.createElement('div');
meta.className = 'db-session-search-item-meta';
const count = session.message_count !== undefined ? `${session.message_count} messages` : '';
meta.textContent = count;
info.appendChild(meta);
item.appendChild(info);
if (currentSessionId && currentSessionId === session.id) {
const badge = document.createElement('span');
badge.className = 'db-session-search-item-current';
badge.textContent = 'Current';
item.appendChild(badge);
}
item.addEventListener('click', () => selectSession(session.id));
item.addEventListener('mouseenter', () => {
listEl.querySelectorAll('.db-session-search-item').forEach((el) => el.classList.remove('active'));
item.classList.add('active');
});
listEl.appendChild(item);
});
}
async function openSessionSearch() {
if (sessionSearchOpen) return;
sessionSearchOpen = true;
const overlay = document.createElement('div');
overlay.className = 'db-session-search-overlay';
const palette = document.createElement('div');
palette.className = 'db-session-search';
palette.setAttribute('role', 'dialog');
palette.setAttribute('aria-label', 'Search sessions');
const input = document.createElement('input');
input.className = 'db-session-search-input';
input.type = 'text';
input.placeholder = 'Search sessions...';
input.setAttribute('aria-label', 'Search sessions');
const list = document.createElement('div');
list.className = 'db-session-search-list';
list.innerHTML = '<div class="db-session-search-empty">Loading sessions...</div>';
palette.appendChild(input);
palette.appendChild(list);
overlay.appendChild(palette);
document.body.appendChild(overlay);
sessionSearchOverlay = overlay;
const currentSessionId = getCurrentSessionId();
let sessions = [];
let debounce = null;
input.addEventListener('input', () => {
if (debounce) clearTimeout(debounce);
debounce = setTimeout(() => {
renderSessionSearchList(list, sessions, input.value, currentSessionId);
}, 150);
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
const active = list.querySelector('.db-session-search-item.active') || list.querySelector('.db-session-search-item');
if (active && active.dataset.sessionId) selectSession(active.dataset.sessionId);
} else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
e.preventDefault();
const items = Array.from(list.querySelectorAll('.db-session-search-item'));
if (!items.length) return;
let idx = items.findIndex((el) => el.classList.contains('active'));
items.forEach((el) => el.classList.remove('active'));
idx = e.key === 'ArrowDown' ? Math.min(items.length - 1, idx + 1) : Math.max(0, idx - 1);
items[idx].classList.add('active');
items[idx].scrollIntoView({ block: 'nearest' });
}
});
overlay.addEventListener('click', (e) => { if (e.target === overlay) closeSessionSearch(); });
try {
const res = await fetch('/sessions');
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
sessions = data.sessions || [];
} catch (err) {
console.error('[AIUI] Failed to fetch sessions:', err);
sessions = [];
}
renderSessionSearchList(list, sessions, '', currentSessionId);
input.focus();
}
function closeSessionSearch() {
sessionSearchOpen = false;
if (sessionSearchOverlay) {
sessionSearchOverlay.remove();
sessionSearchOverlay = null;
}
}
function initSessionSearch() {
if (sessionSearchInitialized) return;
sessionSearchInitialized = true;
// The Ctrl+K binding is owned by the command palette (initCommandPalette),
// which exposes a "Search sessions…" entry that calls openSessionSearch.
// Here we only handle Escape so the session palette can be dismissed.
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && sessionSearchOpen) {
closeSessionSearch();
}
});
}
window.aiui = window.aiui || {};
window.aiui.openSessionSearch = openSessionSearch;
async function init() {
// Inject styles
const style = document.createElement('style');
@@ -803,6 +1262,22 @@ async function init() {
}
await buildDashboard();
// Initialize Ctrl+K / Cmd+K command palette
initCommandPalette();
// Initialize approval stream for modal notifications
initApprovalStream();
// Initialize Ctrl+K session search palette
initSessionSearch();
// Listen for approval events from chat stream
window.addEventListener('aiui:approval-required', (e) => {
if (e.detail) {
showApprovalModal(e.detail);
}
});
}
async function buildDashboard() {
@@ -858,11 +1333,11 @@ async function buildDashboard() {
root.appendChild(sidebar);
setupMobileNav(root, sidebar);
}
root.appendChild(main);
const shellHeader = document.createElement('div');
shellHeader.id = 'db-shell-header-slot';
renderSlot('shell:header', shellHeader);
if (shellHeader.children.length) root.insertBefore(shellHeader, main);
root.appendChild(main);
// Resolve initial page from URL path or default to first page
const pathId = location.pathname.replace(/^\//, '') || '';
@@ -19,6 +19,7 @@ let currentRunId = null;
let agents = [];
let messageQueue = [];
let focusMode = false;
let sessionSelectHandler = null;
let containerRef = null;
let currentDeltaEl = null;
let currentDeltaBodyEl = null;
@@ -784,10 +785,21 @@ export async function render(container) {
await loadSessions();
connectWebSocket();
// Load a session chosen from the dashboard Ctrl+K search palette
sessionSelectHandler = (e) => {
const id = e.detail && e.detail.sessionId;
if (id) loadSession(id);
};
window.addEventListener('aiui:session-select', sessionSelectHandler);
}
export function cleanup() {
if (ws) { try { ws.close(); } catch(e) {} ws = null; }
if (sessionSelectHandler) {
window.removeEventListener('aiui:session-select', sessionSelectHandler);
sessionSelectHandler = null;
}
currentSessionId = null;
currentAgentName = null;
isStreaming = false;
@@ -0,0 +1,214 @@
/**
* MCP Manager View — list, connect, and disconnect Model Context Protocol servers.
* API: GET /api/mcp/servers, POST /api/mcp/connect, POST /api/mcp/disconnect/{name}
*/
import { showToast, showConfirm } from '../toast.js';
const STATUS_STYLE = {
connected: 'background:rgba(34,197,94,0.15);color:#22c55e',
connecting: 'background:rgba(234,179,8,0.15);color:#eab308',
error: 'background:rgba(239,68,68,0.15);color:#ef4444',
disconnected: 'background:rgba(113,113,122,0.2);color:var(--db-text-dim)',
};
function esc(s) {
const d = document.createElement('div');
d.textContent = s == null ? '' : String(s);
return d.innerHTML;
}
export async function render(container) {
container.innerHTML = '<div class="db-loading"><div class="db-spinner"></div></div>';
let servers = [];
let loadError = null;
try {
const r = await fetch('/api/mcp/servers');
const d = await r.json();
servers = d.servers || [];
if (d.error) loadError = d.error;
} catch (e) {
loadError = e.message;
}
const connected = servers.filter((s) => s.status === 'connected').length;
const totalTools = servers.reduce((n, s) => n + ((s.tools && s.tools.length) || 0), 0);
container.innerHTML = `
<div class="db-columns" style="grid-template-columns:repeat(3,1fr);margin-bottom:24px">
<div class="db-card"><div class="db-card-title">Servers</div><div class="db-card-value">${servers.length}</div><div class="db-card-footer">${connected} connected</div></div>
<div class="db-card"><div class="db-card-title">Tools</div><div class="db-card-value">${totalTools}</div></div>
<div class="db-card"><div class="db-card-title">Health</div><div class="db-card-value">${servers.length ? Math.round((connected / servers.length) * 100) : 0}%</div></div>
</div>
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">
<h3 style="margin:0;font-size:15px;font-weight:600">MCP Servers</h3>
<button id="mcp-add" class="db-btn db-btn-primary">+ Connect Server</button>
</div>
${loadError ? `<div class="db-alert db-alert-warning"><span>⚠️</span><span>${esc(loadError)}</span></div>` : ''}
<div id="mcp-list"></div>
<div id="mcp-form-host"></div>
`;
const list = container.querySelector('#mcp-list');
servers.forEach((s) => {
const card = document.createElement('div');
card.className = 'db-card';
card.style.cssText = 'margin-bottom:10px;padding:16px 20px';
const isConnected = s.status === 'connected';
const statusStyle = STATUS_STYLE[s.status] || STATUS_STYLE.disconnected;
const tools = s.tools || [];
card.innerHTML = `
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px">
<div style="display:flex;align-items:center;gap:10px;flex:1;min-width:0">
<span style="font-size:20px">🔌</span>
<div style="min-width:0">
<div style="font-weight:500;font-size:14px">${esc(s.name)}</div>
<div style="font-size:12px;color:var(--db-text-dim)">${esc(s.transport || '')} · ${tools.length} tool${tools.length !== 1 ? 's' : ''}</div>
</div>
</div>
<div style="display:flex;align-items:center;gap:10px">
<span style="font-size:11px;padding:3px 10px;border-radius:12px;${statusStyle}">${esc(s.status || 'unknown')}</span>
${isConnected ? `<button class="mcp-disconnect db-btn db-btn-destructive" data-name="${esc(s.name)}" style="padding:4px 12px;font-size:11px">Disconnect</button>` : ''}
</div>
</div>
${s.last_error ? `<div style="font-size:12px;color:#ef4444;margin-top:8px">${esc(s.last_error)}</div>` : ''}
${tools.length ? `<details style="margin-top:10px"><summary style="cursor:pointer;font-size:12px;color:var(--db-text-dim)">Tools (${tools.length})</summary><div style="margin-top:8px;display:flex;flex-direction:column;gap:6px">${tools
.map(
(t) =>
`<div style="font-size:12px"><code style="color:var(--db-text)">${esc(t.name)}</code>${t.description ? ` <span style="color:var(--db-text-dim)">— ${esc(t.description)}</span>` : ''}</div>`
)
.join('')}</div></details>` : ''}
`;
list.appendChild(card);
});
if (servers.length === 0 && !loadError) {
list.innerHTML = '<div class="db-viewer"><pre>No MCP servers connected. Click "Connect Server" to add one.</pre></div>';
}
container.querySelectorAll('.mcp-disconnect').forEach((b) =>
b.addEventListener('click', async () => {
const name = b.dataset.name;
if (!(await showConfirm('Disconnect MCP Server', `Disconnect "${name}"?`))) return;
try {
const r = await fetch(`/api/mcp/disconnect/${encodeURIComponent(name)}`, { method: 'POST' });
const d = await r.json();
if (r.ok && d.success) {
showToast(`Disconnected from ${name}`, 'success');
render(container);
} else {
showToast(d.error || 'Disconnect failed', 'error');
}
} catch (e) {
showToast(e.message, 'error');
}
})
);
const addBtn = container.querySelector('#mcp-add');
const formHost = container.querySelector('#mcp-form-host');
addBtn.addEventListener('click', () => openConnectForm(formHost, container));
}
function openConnectForm(host, container) {
host.innerHTML = `
<div class="db-card" style="margin-top:16px;padding:20px">
<h3 style="margin:0 0 16px;font-size:14px;font-weight:600">Connect MCP Server</h3>
<div class="db-form-group">
<label class="db-form-label">Name</label>
<input id="mcp-f-name" class="db-form-input" placeholder="my-server">
</div>
<div class="db-form-group">
<label class="db-form-label">Transport</label>
<select id="mcp-f-transport" class="db-form-select">
<option value="stdio">stdio (command)</option>
<option value="sse">SSE / HTTP (url)</option>
</select>
</div>
<div id="mcp-f-stdio">
<div class="db-form-group">
<label class="db-form-label">Command</label>
<input id="mcp-f-command" class="db-form-input" placeholder="npx">
</div>
<div class="db-form-group">
<label class="db-form-label">Arguments (space-separated)</label>
<input id="mcp-f-args" class="db-form-input" placeholder="-y @modelcontextprotocol/server-filesystem /tmp">
</div>
</div>
<div id="mcp-f-url" style="display:none">
<div class="db-form-group">
<label class="db-form-label">URL</label>
<input id="mcp-f-urlval" class="db-form-input" placeholder="https://example.com/sse">
</div>
</div>
<div class="db-button-group" style="margin:0">
<button id="mcp-f-submit" class="db-btn db-btn-primary">Connect</button>
<button id="mcp-f-cancel" class="db-btn">Cancel</button>
</div>
</div>
`;
const transportSel = host.querySelector('#mcp-f-transport');
const stdioBlock = host.querySelector('#mcp-f-stdio');
const urlBlock = host.querySelector('#mcp-f-url');
transportSel.addEventListener('change', () => {
const isStdio = transportSel.value === 'stdio';
stdioBlock.style.display = isStdio ? '' : 'none';
urlBlock.style.display = isStdio ? 'none' : '';
});
host.querySelector('#mcp-f-cancel').addEventListener('click', () => {
host.innerHTML = '';
});
host.querySelector('#mcp-f-submit').addEventListener('click', async () => {
const name = host.querySelector('#mcp-f-name').value.trim();
if (!name) {
showToast('Name is required', 'error');
return;
}
const body = { name };
if (transportSel.value === 'stdio') {
const command = host.querySelector('#mcp-f-command').value.trim();
if (!command) {
showToast('Command is required for stdio', 'error');
return;
}
body.command = command;
const argsRaw = host.querySelector('#mcp-f-args').value.trim();
body.args = argsRaw ? argsRaw.split(/\s+/) : [];
} else {
const url = host.querySelector('#mcp-f-urlval').value.trim();
if (!url) {
showToast('URL is required', 'error');
return;
}
body.url = url;
}
const submitBtn = host.querySelector('#mcp-f-submit');
submitBtn.disabled = true;
submitBtn.textContent = 'Connecting…';
try {
const r = await fetch('/api/mcp/connect', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const d = await r.json();
if (r.ok && d.server) {
showToast(`Connected to ${d.server.name}`, 'success');
host.innerHTML = '';
render(container);
} else {
showToast(d.error || d.hint || 'Connection failed', 'error');
submitBtn.disabled = false;
submitBtn.textContent = 'Connect';
}
} catch (e) {
showToast(e.message, 'error');
submitBtn.disabled = false;
submitBtn.textContent = 'Connect';
}
});
}
+5 -3
View File
@@ -311,7 +311,7 @@ def test_sessions(server: str = _SERVER_OPT) -> None:
r.check(f"Session file exists: {session_file.name}", session_file.exists())
if session_file.exists():
data = json.loads(session_file.read_text())
data = json.loads(session_file.read_text(encoding="utf-8"))
file_msgs = data.get("messages", [])
r.check(
"File contains messages",
@@ -354,9 +354,11 @@ def test_endpoints(server: str = _SERVER_OPT) -> None:
# ── Health ───────────────────────────────────────────────────────
console.print("\n[dim]Health[/dim]")
from praisonaiui.health_utils import is_success_status
try:
data = _get(server, "/health")
r.check("GET /health", data.get("status") == "ok")
r.check("GET /health", is_success_status(data.get("status")))
except Exception as e:
r.check("GET /health", False, str(e))
@@ -798,7 +800,7 @@ def test_features(server: str = _SERVER_OPT) -> None:
# Try loading from .env
for env_path in [Path.cwd() / ".env", Path.home() / ".env"]:
if env_path.exists():
for line in env_path.read_text().splitlines():
for line in env_path.read_text(encoding="utf-8").splitlines():
if line.startswith("TELEGRAM_BOT_TOKEN="):
tg_token = line.split("=", 1)[1].strip().strip('"').strip("'")
break
+3 -3
View File
@@ -246,7 +246,7 @@ def _read_disk_cache() -> Optional[dict]:
age = time.time() - path.stat().st_mtime
if age > _DISK_CACHE_TTL:
return None
return json.loads(path.read_text())
return json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return None
@@ -256,7 +256,7 @@ def _write_disk_cache(data: dict) -> None:
try:
path = _disk_cache_path()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(data))
path.write_text(json.dumps(data), encoding="utf-8")
except OSError:
pass # Non-critical — build still works
@@ -357,7 +357,7 @@ def inject_theme_css(
# Write to theme.css
theme_file = output_dir / "assets" / "theme.css"
theme_file.parent.mkdir(parents=True, exist_ok=True)
theme_file.write_text(css)
theme_file.write_text(css, encoding="utf-8")
def get_available_themes() -> list[str]:
+13
View File
@@ -734,6 +734,19 @@ class TestPagesAPI:
if ev:
assert ev["group"] == "Control"
def test_mcp_page_registered(self, client):
r = client.get("/api/pages")
pages = r.json().get("pages", r.json())
mcp = next((p for p in pages if p["id"] == "mcp"), None)
assert mcp is not None, "MCP page should be registered"
assert mcp["group"] == "Agent"
assert mcp["api_endpoint"] == "/api/mcp/servers"
def test_mcp_servers_endpoint(self, client):
r = client.get("/api/mcp/servers")
assert r.status_code == 200
assert "servers" in r.json()
# ===========================================================================
# Persistence across restart
+95 -1
View File
@@ -5,7 +5,12 @@ from __future__ import annotations
from starlette.testclient import TestClient
from praisonaiui.backends import clear_backends, set_backend
from praisonaiui.features.kanban import get_kanban_store, reset_kanban_store
from praisonaiui.features.kanban import (
_child_progress,
_task_card,
get_kanban_store,
reset_kanban_store,
)
from praisonaiui.server import create_app, reset_state
@@ -81,6 +86,9 @@ def test_kanban_uses_injected_store():
def move_task(self, task_id, status):
return None
def add_comment(self, task_id, comment):
return None
def bulk_update(self, task_ids, status):
return {"updated": 0}
@@ -105,6 +113,92 @@ def test_kanban_uses_injected_store():
reset_kanban_store()
def test_task_card_enriched_dto():
card = _task_card(
{
"id": "t1",
"title": "Refactor auth",
"status": "running",
"assignee": "coder",
"priority": "P2",
"tenant": "acme",
"created_at": "2024-01-01T00:00:00+00:00",
"comments": [{"text": "a"}, {"text": "b"}],
}
)
assert card["tenant"] == "acme"
assert card["created_at"] == "2024-01-01T00:00:00+00:00"
assert card["comment_count"] == 2
assert card["progress"] is None
def test_child_progress_from_children_and_meta():
children = {"children": [{"status": "done"}, {"status": "ready"}, {"status": "done"}]}
assert _child_progress(children) == {"done": 2, "total": 3}
explicit = {"meta": {"progress": {"done": 1, "total": 4}}}
assert _child_progress(explicit) == {"done": 1, "total": 4}
assert _child_progress({}) is None
assert _child_progress({"meta": {"progress": {"done": 0, "total": 0}}}) is None
def test_card_in_board_carries_progress_and_counts():
clear_backends()
client = _client()
store = get_kanban_store()
store.create_task(
{
"id": "px",
"title": "Parent",
"status": "todo",
"tenant": "acme",
"children": [{"status": "done"}, {"status": "todo"}],
}
)
board = client.get("/api/kanban/board").json()
todo_cards = next(c for c in board["columns"] if c["id"] == "todo")["cards"]
card = next(c for c in todo_cards if c["id"] == "px")
assert card["tenant"] == "acme"
assert card["progress"] == {"done": 1, "total": 2}
assert card["comment_count"] == 0
clear_backends()
def test_kanban_add_comment():
clear_backends()
client = _client()
create = client.post("/api/kanban/tasks", json={"title": "Has comments", "status": "todo"})
task_id = create.json()["id"]
resp = client.post(f"/api/kanban/tasks/{task_id}/comments", json={"text": "please unblock"})
assert resp.status_code == 200
task = resp.json()
assert len(task["comments"]) == 1
assert task["comments"][0]["text"] == "please unblock"
assert task["comments"][0]["author"] == "human"
board = client.get("/api/kanban/board").json()
todo_cards = next(c for c in board["columns"] if c["id"] == "todo")["cards"]
card = next(c for c in todo_cards if c["id"] == task_id)
assert card["comment_count"] == 1
clear_backends()
def test_kanban_add_comment_validation():
clear_backends()
client = _client()
create = client.post("/api/kanban/tasks", json={"title": "T", "status": "todo"})
task_id = create.json()["id"]
empty = client.post(f"/api/kanban/tasks/{task_id}/comments", json={"text": " "})
assert empty.status_code == 400
missing = client.post("/api/kanban/tasks/nope/comments", json={"text": "hi"})
assert missing.status_code == 404
clear_backends()
def test_jobs_board_endpoint():
clear_backends()
client = _client()
+166
View File
@@ -0,0 +1,166 @@
"""Tests for channel auto-start lifecycle and running-status correctness.
Regression coverage for issue #167: auto-start raised
``'dict' object has no attribute 'append'`` when the gateway exposed
``_channel_tasks`` as a dict, and reported ``running: true`` despite a
``start_error`` being set.
"""
from __future__ import annotations
from unittest.mock import patch
import pytest
from praisonaiui.features import channels as channels_mod
from praisonaiui.features.channels import ChannelsFeature
class _FakeBot:
def __init__(self) -> None:
self.is_running = False
self.started = False
async def start(self) -> None:
self.started = True
class _GatewayDictTasks:
def __init__(self) -> None:
self._channel_bots: dict = {}
self._channel_tasks: dict = {}
self._agents: dict = {}
class _GatewayListTasks:
def __init__(self) -> None:
self._channel_bots: dict = {}
self._channel_tasks: list = []
self._agents: dict = {}
@pytest.fixture(autouse=True)
def _isolate_channel_state():
saved_channels = dict(channels_mod._channels)
saved_bots = dict(channels_mod._live_bots)
saved_flag = ChannelsFeature._auto_started
channels_mod._channels.clear()
channels_mod._live_bots.clear()
ChannelsFeature._auto_started = False
yield
channels_mod._channels.clear()
channels_mod._channels.update(saved_channels)
channels_mod._live_bots.clear()
channels_mod._live_bots.update(saved_bots)
ChannelsFeature._auto_started = saved_flag
@pytest.mark.asyncio
async def test_start_channel_bot_dict_channel_tasks_no_attribute_error():
feature = ChannelsFeature()
gw = _GatewayDictTasks()
bot = _FakeBot()
entry = {"id": "ch1", "platform": "discord", "config": {"bot_token": "x" * 30}}
with patch.object(feature, "_get_gateway", return_value=gw), patch.object(
feature, "_create_bot_direct", return_value=bot
), patch.object(feature, "_attach_chat_bridge"):
error = await feature._start_channel_bot("ch1", entry)
assert error is None
assert "ch1" in gw._channel_tasks
assert gw._channel_bots["ch1"] is bot
assert entry["running"] is True
assert "start_error" not in entry
@pytest.mark.asyncio
async def test_start_channel_bot_list_channel_tasks_no_regression():
feature = ChannelsFeature()
gw = _GatewayListTasks()
bot = _FakeBot()
entry = {"id": "ch2", "platform": "discord", "config": {"bot_token": "x" * 30}}
with patch.object(feature, "_get_gateway", return_value=gw), patch.object(
feature, "_create_bot_direct", return_value=bot
), patch.object(feature, "_attach_chat_bridge"):
error = await feature._start_channel_bot("ch2", entry)
assert error is None
assert len(gw._channel_tasks) == 1
assert gw._channel_bots["ch2"] is bot
assert entry["running"] is True
def test_sync_running_status_start_error_forces_not_running():
feature = ChannelsFeature()
channels_mod._channels["chx"] = {
"id": "chx",
"platform": "discord",
"running": True,
"start_error": "boom",
}
with patch.object(feature, "_get_gateway", return_value=None):
feature._sync_running_status()
assert channels_mod._channels["chx"]["running"] is False
@pytest.mark.asyncio
async def test_auto_start_runs_once_per_process():
feature = ChannelsFeature()
channels_mod._channels["ch3"] = {
"id": "ch3",
"platform": "discord",
"enabled": True,
"config": {"bot_token": "x" * 30},
}
with patch.object(
feature, "_start_channel_bot", return_value=None
) as mock_start:
await feature._auto_start_enabled_channels()
await feature._auto_start_enabled_channels()
assert mock_start.call_count == 1
@pytest.mark.asyncio
async def test_auto_start_skips_disabled_channels():
feature = ChannelsFeature()
channels_mod._channels["ch4"] = {
"id": "ch4",
"platform": "discord",
"enabled": False,
"config": {"bot_token": "x" * 30},
}
with patch.object(
feature, "_start_channel_bot", return_value=None
) as mock_start:
await feature._auto_start_enabled_channels()
assert mock_start.call_count == 0
@pytest.mark.asyncio
async def test_auto_start_failure_sets_not_running_and_error():
feature = ChannelsFeature()
entry = {
"id": "ch5",
"platform": "discord",
"enabled": True,
"running": True,
"config": {"bot_token": "x" * 30},
}
channels_mod._channels["ch5"] = entry
async def _boom(channel_id, e):
raise AttributeError("'dict' object has no attribute 'append'")
with patch.object(feature, "_start_channel_bot", side_effect=_boom):
await feature._auto_start_enabled_channels()
assert entry["running"] is False
assert "start_error" in entry
+121
View File
@@ -0,0 +1,121 @@
"""Unit tests for CLI features commands."""
import json
from unittest.mock import MagicMock, patch
import pytest
from typer.testing import CliRunner
from praisonaiui.cli import app
runner = CliRunner()
class TestFeaturesCommands:
"""Test the features CLI commands."""
@pytest.mark.parametrize("status", ["ok", "healthy"])
def test_features_list_shows_green_for_success_statuses(self, status):
"""Test features list command shows green indicator for both ok and healthy."""
features_data = {
"features": [
{
"name": "Feature1",
"description": "Test feature 1",
"health": {"status": status},
"routes": ["/api/feature1", "/api/feature1/status"],
},
{
"name": "Feature2",
"description": "Test feature 2",
"health": {"status": status},
}
]
}
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(features_data).encode("utf-8")
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.return_value.__enter__.return_value = mock_response
result = runner.invoke(app, ["features", "list"])
assert result.exit_code == 0
assert "Feature1" in result.stdout
assert "Feature2" in result.stdout
assert "Test feature 1" in result.stdout
assert "/api/feature1" in result.stdout
@pytest.mark.parametrize("status", ["error", "degraded", "unknown"])
def test_features_list_shows_red_for_non_success_statuses(self, status):
"""Test features list command shows red indicator for non-success statuses."""
features_data = {
"features": [
{
"name": "FailingFeature",
"description": "Feature with error",
"health": {"status": status},
}
]
}
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(features_data).encode("utf-8")
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.return_value.__enter__.return_value = mock_response
result = runner.invoke(app, ["features", "list"])
assert result.exit_code == 0
assert "FailingFeature" in result.stdout
assert "Feature with error" in result.stdout
@pytest.mark.parametrize("status", ["ok", "healthy"])
def test_features_status_counts_success_statuses_correctly(self, status):
"""Test features status command counts both ok and healthy as success."""
features_data = {
"features": [
{"name": "Feature1", "health": {"status": status}},
{"name": "Feature2", "health": {"status": status}},
{"name": "Feature3", "health": {"status": "error"}},
]
}
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(features_data).encode("utf-8")
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.return_value.__enter__.return_value = mock_response
result = runner.invoke(app, ["features", "status"])
assert result.exit_code == 0
assert "Features: 2/3 healthy" in result.stdout
assert "Feature1" in result.stdout
assert "Feature2" in result.stdout
assert "Feature3" in result.stdout
def test_features_status_mixed_success_statuses(self):
"""Test features status handles mix of ok and healthy statuses."""
features_data = {
"features": [
{"name": "Feature1", "health": {"status": "ok"}},
{"name": "Feature2", "health": {"status": "healthy"}},
{"name": "Feature3", "health": {"status": "degraded"}},
{"name": "Feature4", "health": {"status": "error"}},
]
}
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(features_data).encode("utf-8")
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.return_value.__enter__.return_value = mock_response
result = runner.invoke(app, ["features", "status"])
assert result.exit_code == 0
# Both ok and healthy should be counted as healthy
assert "Features: 2/4 healthy" in result.stdout
+168
View File
@@ -0,0 +1,168 @@
"""Unit tests for CLI health-check command."""
import json
from unittest.mock import MagicMock, patch
import pytest
from typer.testing import CliRunner
from praisonaiui.cli import app
runner = CliRunner()
class TestHealthCheckCommand:
"""Test the health-check CLI command."""
@pytest.mark.parametrize("status", ["ok", "healthy"])
def test_health_check_shows_green_for_success_statuses(self, status):
"""Test health-check command shows green panel for both ok and healthy."""
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(
{"status": status, "timestamp": "2026-06-23T10:00:00Z"}
).encode("utf-8")
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.return_value.__enter__.return_value = mock_response
result = runner.invoke(app, ["health-check"])
assert result.exit_code == 0
assert "Health Check" in result.stdout
# Check that status is displayed (Rich strips color codes in test output)
assert status in result.stdout
# The test would need more complex setup to verify colors in Rich output
@pytest.mark.parametrize("status", ["degraded", "error", "unknown"])
def test_health_check_shows_yellow_for_non_success_statuses(self, status):
"""Test health-check command shows yellow panel for non-success statuses."""
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(
{"status": status, "timestamp": "2026-06-23T10:00:00Z"}
).encode("utf-8")
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.return_value.__enter__.return_value = mock_response
result = runner.invoke(app, ["health-check"])
assert result.exit_code == 0
assert "Health Check" in result.stdout
# Check that status is displayed (Rich strips color codes in test output)
assert status in result.stdout
def test_health_check_handles_server_unreachable(self):
"""Test health-check command handles unreachable server."""
with patch("urllib.request.urlopen", side_effect=Exception("Connection refused")):
result = runner.invoke(app, ["health-check"])
assert result.exit_code == 1
assert "Server unreachable" in result.stdout
assert "✗" in result.stdout # Rich strips color codes
class TestDoctorCommand:
"""Test the doctor CLI command."""
@pytest.mark.parametrize("status", ["ok", "healthy"])
def test_doctor_health_extractor_accepts_success_statuses(self, status):
"""Test doctor command's health extractor accepts both ok and healthy."""
def mock_api_get(server, path):
if path == "/health/live":
return {"status": status}
elif path == "/api/provider":
return {"name": "TestProvider"}
elif path == "/api/provider/health":
return {"type": "gateway", "agents": 2}
elif path == "/api/features":
return {"features": []}
elif path == "/api/config":
return {"config": {}}
elif path == "/sessions":
return []
elif path == "/api/channels":
return {"channels": []}
return {}
with patch("praisonaiui.cli._api_get", side_effect=mock_api_get):
result = runner.invoke(app, ["doctor", "--json"])
assert result.exit_code == 0
output = json.loads(result.stdout)
# Find the Server Health check
health_check = next(
(c for c in output["checks"] if c["name"] == "Server Health"), None
)
assert health_check is not None
assert health_check["status"] == "pass"
assert "running on" in health_check["detail"]
@pytest.mark.parametrize("status", ["degraded", "error"])
def test_doctor_health_extractor_warns_for_non_success_statuses(self, status):
"""Test doctor command's health extractor warns for non-success statuses."""
def mock_api_get(server, path):
if path == "/health/live":
return {"status": status}
return {}
with patch("praisonaiui.cli._api_get", side_effect=mock_api_get):
result = runner.invoke(app, ["doctor", "--json"])
# Since we're not providing all required endpoints, exit code might be 1
# But we can still check the output
output = json.loads(result.stdout)
# Find the Server Health check
health_check = next(
(c for c in output["checks"] if c["name"] == "Server Health"), None
)
assert health_check is not None
assert health_check["status"] == "warn"
assert f"status: {status}" in health_check["detail"]
class TestProviderStatusCommand:
"""Test the provider status CLI command."""
@pytest.mark.parametrize("status", ["ok", "healthy"])
def test_provider_status_shows_green_for_success_statuses(self, status):
"""Test provider status command shows green for both ok and healthy."""
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(
{
"name": "TestProvider",
"module": "test.provider",
"status": status,
"agents": []
}
).encode("utf-8")
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.return_value.__enter__.return_value = mock_response
result = runner.invoke(app, ["provider", "status"])
assert result.exit_code == 0
assert status in result.stdout # Rich strips color codes in test output
@pytest.mark.parametrize("status", ["error", "degraded"])
def test_provider_status_shows_red_for_non_success_statuses(self, status):
"""Test provider status command shows red for non-success statuses."""
mock_response = MagicMock()
mock_response.read.return_value = json.dumps(
{
"name": "TestProvider",
"module": "test.provider",
"status": status,
"agents": []
}
).encode("utf-8")
with patch("urllib.request.urlopen") as mock_urlopen:
mock_urlopen.return_value.__enter__.return_value = mock_response
result = runner.invoke(app, ["provider", "status"])
assert result.exit_code == 0
assert status in result.stdout # Rich strips color codes in test output
+53
View File
@@ -599,3 +599,56 @@ class TestCompositionResolver:
assert len(left_sidebar_widgets) == 1
assert left_sidebar_widgets[0]["props"]["searchable"] is True
assert "collapsible" not in left_sidebar_widgets[0]["props"]
class TestCompilerUtf8Writes:
"""Regression tests for issue #150 — UTF-8 on write (Windows cp1252)."""
SPARKLE = "\u2728"
def test_doc_page_written_as_utf8(self, tmp_path: Path):
docs_dir = tmp_path / "docs"
docs_dir.mkdir()
(docs_dir / "index.md").write_text(
f"# Welcome\n\n## {self.SPARKLE} Features\n\nHello!", encoding="utf-8"
)
config = Config(
site=SiteConfig(title="Unicode Site"),
content=ContentConfig(docs=ContentSourceConfig(dir=str(docs_dir))),
templates={"docs": TemplateConfig(layout="Default", slots={})},
routes=[RouteConfig(match="/docs/**", template="docs")],
)
compiler = Compiler(config, base_path=tmp_path)
result = compiler.compile(tmp_path / "output")
assert result.success is True
page = tmp_path / "output" / "docs" / "index" / "index.html"
assert page.exists()
raw = page.read_bytes()
assert self.SPARKLE in raw.decode("utf-8")
assert b"\xe2\x9c\xa8" in raw
def test_write_json_uses_utf8(self, minimal_config: Config, tmp_path: Path):
compiler = Compiler(minimal_config, base_path=tmp_path)
target = tmp_path / "out.json"
compiler._write_json(target, {"sparkle": self.SPARKLE}, indent=2)
raw = target.read_bytes()
assert self.SPARKLE in raw.decode("utf-8")
assert b"\xe2\x9c\xa8" in raw
def test_plugins_json_uses_utf8(self, tmp_path: Path):
frontend_dir = tmp_path / "frontend"
plugins_dst = frontend_dir / "plugins"
plugins_dst.mkdir(parents=True)
config = Config(
site=SiteConfig(title="Plugin Site"),
templates={"docs": TemplateConfig(layout="Default", slots={})},
routes=[RouteConfig(match="/docs/**", template="docs")],
)
compiler = Compiler(config, base_path=tmp_path)
compiler._copy_plugins(tmp_path / "output", frontend_dir)
plugins_file = tmp_path / "output" / "plugins" / "plugins.json"
assert plugins_file.exists()
assert plugins_file.read_bytes().decode("utf-8")
+50
View File
@@ -0,0 +1,50 @@
"""Regression tests for #166 — buildDashboard() insertBefore NotFoundError.
buildDashboard() called `root.insertBefore(shellHeader, main)` before `main`
was a child of `root`, violating the DOM contract and throwing
NotFoundError. That aborted the dashboard plugin init, blocking Ctrl+K and
the browser chat input. These source-parsing tests assert `main` is appended
to `root` before any insertBefore references it, mirroring
`test_session_search_dashboard.py`.
"""
from __future__ import annotations
import re
from pathlib import Path
FRONTEND = (
Path(__file__).resolve().parents[2]
/ "src" / "praisonaiui" / "templates" / "frontend" / "plugins"
)
DASHBOARD_JS = FRONTEND / "dashboard.js"
class TestDashboardShellOrder:
def _build_body(self) -> str:
src = DASHBOARD_JS.read_text()
m = re.search(
r"function buildDashboard\([^)]*\)\s*\{(.*?)^}",
src, re.DOTALL | re.MULTILINE,
)
assert m, "buildDashboard not found"
return m.group(1)
def test_main_appended_before_insert_before(self):
body = self._build_body()
append_idx = body.find("root.appendChild(main)")
insert_idx = body.find("root.insertBefore(shellHeader, main)")
assert append_idx != -1, "root.appendChild(main) not found"
assert insert_idx != -1, "root.insertBefore(shellHeader, main) not found"
assert append_idx < insert_idx, (
"main must be appended to root before insertBefore(shellHeader, main)"
)
def test_shell_header_insert_guarded_by_children(self):
body = self._build_body()
m = re.search(
r"if \(shellHeader\.children\.length\)\s*"
r"root\.insertBefore\(shellHeader, main\)",
body,
)
assert m, "shellHeader insert must be guarded by children.length"
+46
View File
@@ -0,0 +1,46 @@
"""Test frontend navigation functionality."""
from pathlib import Path
class TestFrontendNavigation:
"""Tests for browser history navigation in the frontend."""
def test_frontend_bundle_exists(self):
"""Test that the built frontend bundle exists in templates."""
bundle_path = Path("src/praisonaiui/templates/frontend/assets/index.js")
assert bundle_path.exists(), "Frontend bundle should exist in templates"
def test_index_html_includes_assets(self):
"""Test that index.html references the correct assets."""
index_path = Path("src/praisonaiui/templates/frontend/index.html")
assert index_path.exists(), "index.html should exist"
content = index_path.read_text()
assert "/assets/index.js" in content, "Should reference index.js"
assert "/assets/index.css" in content, "Should reference index.css"
def test_popstate_handler_in_bundle(self):
"""Test that popstate handler is present in the bundle."""
bundle_path = Path("src/praisonaiui/templates/frontend/assets/index.js")
if bundle_path.exists():
# Check if the bundle contains popstate handling code
# Note: The bundle is minified, so we check for the event name
content = bundle_path.read_text()
assert "popstate" in content, "Bundle should contain popstate event handler"
def test_title_template_handling(self):
"""Test that title template handling is robust."""
# This tests the logic, not the actual implementation
# as the JS is bundled and minified
test_cases = [
("%s | Site", "Page", "Page | Site"),
("%s", "Page", "Page"),
("%s | %s", "Page", "Page | Site"), # Second %s would be replaced with site name
]
for template, page_title, expected_start in test_cases:
# Simulate the title replacement logic
result = template.replace("%s", page_title, 1)
assert result.startswith(expected_start.split("|")[0].strip())
+100
View File
@@ -0,0 +1,100 @@
"""Test that both health-check and health commands work."""
import json
from unittest.mock import MagicMock, patch
from typer.testing import CliRunner
from praisonaiui.cli import app
runner = CliRunner()
def test_health_check_command_exists():
"""Test that health-check command is registered."""
result = runner.invoke(app, ["health-check", "--help"])
assert result.exit_code == 0
assert "Check server health" in result.stdout
def test_health_alias_exists():
"""Test that health alias is registered."""
result = runner.invoke(app, ["health", "--help"])
assert result.exit_code == 0
assert "Check server health" in result.stdout
@patch("urllib.request.urlopen")
def test_health_check_command_works(mock_urlopen):
"""Test that health-check command works correctly."""
mock_response = MagicMock()
mock_response.read.return_value = json.dumps({
"status": "healthy",
"timestamp": "2026-06-23T12:00:00"
}).encode()
mock_response.__enter__.return_value = mock_response
mock_response.__exit__.return_value = None
mock_urlopen.return_value = mock_response
result = runner.invoke(app, ["health-check", "--server", "http://127.0.0.1:8000"])
assert result.exit_code == 0
assert "healthy" in result.stdout.lower()
@patch("urllib.request.urlopen")
def test_health_alias_works(mock_urlopen):
"""Test that health alias works correctly."""
mock_response = MagicMock()
mock_response.read.return_value = json.dumps({
"status": "healthy",
"timestamp": "2026-06-23T12:00:00"
}).encode()
mock_response.__enter__.return_value = mock_response
mock_response.__exit__.return_value = None
mock_urlopen.return_value = mock_response
result = runner.invoke(app, ["health", "--server", "http://127.0.0.1:8000"])
assert result.exit_code == 0
assert "healthy" in result.stdout.lower()
@patch("urllib.request.urlopen")
@patch("praisonaiui.cli._api_get")
def test_health_detailed_works(mock_api_get, mock_urlopen):
"""Test that both commands work with --detailed flag."""
# Mock health check response
mock_response = MagicMock()
mock_response.read.return_value = json.dumps({
"status": "healthy",
"timestamp": "2026-06-23T12:00:00"
}).encode()
mock_response.__enter__.return_value = mock_response
mock_response.__exit__.return_value = None
mock_urlopen.return_value = mock_response
# Mock features response
mock_api_get.return_value = {
"features": [
{"name": "test_feature", "health": {"healthy": True, "detail": "ok"}}
]
}
# Test health-check with --detailed
result1 = runner.invoke(app, ["health-check", "--server", "http://127.0.0.1:8000", "--detailed"])
assert result1.exit_code == 0
assert "healthy" in result1.stdout.lower()
assert "test_feature" in result1.stdout
# Test health alias with --detailed
result2 = runner.invoke(app, ["health", "--server", "http://127.0.0.1:8000", "--detailed"])
assert result2.exit_code == 0
assert "healthy" in result2.stdout.lower()
assert "test_feature" in result2.stdout
def test_help_shows_both_commands():
"""Test that main help shows both health commands."""
result = runner.invoke(app, ["--help"])
assert result.exit_code == 0
# At least one health-related command should be shown
assert "health" in result.stdout.lower()
+36
View File
@@ -0,0 +1,36 @@
"""Unit tests for health status utilities."""
import pytest
from praisonaiui.health_utils import SUCCESS_STATUSES, is_success_status
class TestHealthUtils:
"""Test health status utility functions."""
def test_success_statuses_constant(self):
"""Test SUCCESS_STATUSES contains expected values."""
assert "ok" in SUCCESS_STATUSES
assert "healthy" in SUCCESS_STATUSES
assert len(SUCCESS_STATUSES) == 2
@pytest.mark.parametrize("status", ["ok", "healthy"])
def test_is_success_status_with_valid_statuses(self, status):
"""Test is_success_status returns True for valid success statuses."""
assert is_success_status(status) is True
@pytest.mark.parametrize("status", ["error", "degraded", "unknown", "failing", ""])
def test_is_success_status_with_invalid_statuses(self, status):
"""Test is_success_status returns False for invalid statuses."""
assert is_success_status(status) is False
def test_is_success_status_with_none(self):
"""Test is_success_status handles None input gracefully."""
assert is_success_status(None) is False
def test_is_success_status_case_sensitive(self):
"""Test is_success_status is case sensitive."""
assert is_success_status("OK") is False
assert is_success_status("HEALTHY") is False
assert is_success_status("Ok") is False
assert is_success_status("Healthy") is False
+27
View File
@@ -1,12 +1,14 @@
"""Tests for i18n and accessibility configuration."""
from praisonaiui.schema.features import get_feature_registry
from praisonaiui.schema.models import (
A11yConfig,
Config,
I18nConfig,
SiteConfig,
)
from praisonaiui.schema.validators import validate_config
class TestI18nConfig:
@@ -112,3 +114,28 @@ class TestConfigWithI18nA11y:
assert config.i18n.locales == ["en", "ja"]
assert config.a11y.reduce_motion is True
def test_i18n_implemented_status(self):
"""Test that i18n is marked as IMPLEMENTED in feature registry."""
registry = get_feature_registry()
assert registry.is_implemented("i18n") is True
assert registry.is_experimental("i18n") is False
def test_strict_validation_passes_with_i18n(self):
"""Test that strict validation passes with valid i18n config."""
config = Config(
site=SiteConfig(title="Test Site"),
i18n=I18nConfig(
defaultLocale="en",
locales=["en", "es", "fr"],
rtlLocales=["ar"],
fallbackLocale="en"
),
)
result = validate_config(config, strict=True)
# Should not have experimental error (code 4001) for i18n
assert not any(e.code == 4001 and 'i18n' in e.message for e in result.errors)
# Validation should pass with no errors related to i18n being experimental
assert result.valid or len([e for e in result.errors if 'i18n' in e.message and 'experimental' in e.message]) == 0
+135
View File
@@ -0,0 +1,135 @@
"""Tests for i18n feature implementation."""
import pytest
from praisonaiui.features.i18n import I18nFeature, JSONLocaleManager, get_i18n_manager
class TestI18nFeature:
"""Tests for I18nFeature class."""
def test_feature_metadata(self):
"""Test feature metadata properties."""
feature = I18nFeature()
assert feature.name == "i18n"
assert "Internationalization" in feature.description
def test_feature_routes(self):
"""Test that i18n feature exposes correct routes."""
feature = I18nFeature()
routes = feature.routes()
# Check expected routes exist
route_paths = [route.path for route in routes]
assert "/api/i18n/locales" in route_paths
assert "/api/i18n/strings/{locale}" in route_paths
assert "/api/i18n/translate" in route_paths
assert "/api/i18n/locale" in route_paths
@pytest.mark.asyncio
async def test_feature_health(self):
"""Test health endpoint."""
feature = I18nFeature()
health = await feature.health()
assert health["status"] == "ok"
assert health["feature"] == "i18n"
assert "provider" in health
class TestJSONLocaleManager:
"""Tests for JSONLocaleManager."""
def test_default_initialization(self):
"""Test default initialization."""
manager = JSONLocaleManager()
assert manager.get_locale() == "en"
assert len(manager.list_locales()) > 0
def test_translation_lookup(self):
"""Test translation key lookup."""
manager = JSONLocaleManager()
# Test English translation
text = manager.t("app.welcome", locale="en")
assert "Welcome" in text
# Test Spanish translation
text = manager.t("app.welcome", locale="es")
assert "Bienvenido" in text
# Test French translation
text = manager.t("app.welcome", locale="fr")
assert "Bienvenue" in text
def test_translation_with_variables(self):
"""Test translation with variable substitution."""
manager = JSONLocaleManager()
manager._strings["en"]["greeting"] = "Hello {name}!"
text = manager.t("greeting", locale="en", name="World")
assert text == "Hello World!"
def test_unknown_key_returns_key(self):
"""Test that unknown keys return the key itself."""
manager = JSONLocaleManager()
text = manager.t("unknown.key", locale="en")
assert text == "unknown.key"
def test_locale_switching(self):
"""Test switching default locale."""
manager = JSONLocaleManager()
assert manager.get_locale() == "en"
manager.set_locale("es")
assert manager.get_locale() == "es"
# Test that default locale is used when none specified
text = manager.t("app.welcome")
assert "Bienvenido" in text
def test_get_strings(self):
"""Test getting all strings for a locale."""
manager = JSONLocaleManager()
en_strings = manager.get_strings("en")
assert "app.title" in en_strings
assert "chat.placeholder" in en_strings
es_strings = manager.get_strings("es")
assert "app.title" in es_strings
assert es_strings["chat.placeholder"] == "Escribe tu mensaje..."
def test_list_locales(self):
"""Test listing available locales."""
manager = JSONLocaleManager()
locales = manager.list_locales()
# Check structure
assert isinstance(locales, list)
assert len(locales) >= 3 # At least en, es, fr
# Check format
en_locale = next((loc for loc in locales if loc["code"] == "en"), None)
assert en_locale is not None
assert en_locale["name"] == "English"
assert en_locale["native"] == "English"
class TestI18nIntegration:
"""Integration tests for i18n system."""
def test_singleton_manager(self):
"""Test that get_i18n_manager returns singleton."""
mgr1 = get_i18n_manager()
mgr2 = get_i18n_manager()
assert mgr1 is mgr2
def test_manager_health(self):
"""Test manager health check."""
manager = get_i18n_manager()
health = manager.health()
assert health["status"] == "ok"
assert "provider" in health
assert "default_locale" in health
+192
View File
@@ -20,6 +20,10 @@ def client():
# Clear state before each test
_agents.clear()
_callbacks.clear()
import praisonaiui.server as _srv
_srv._DEEP_HEALTH_CACHE = {}
_srv._DEEP_HEALTH_CACHE_TIME = 0.0
set_datastore(MemoryDataStore())
app = create_app()
return TestClient(app)
@@ -36,6 +40,194 @@ class TestHealthEndpoint:
assert data["status"] == "ok"
assert "timestamp" in data
def test_health_live_is_fast(self, client):
"""Test liveness endpoint returns quickly."""
import time
start = time.time()
response = client.get("/health/live")
duration = time.time() - start
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert "timestamp" in data
# Should respond in under 100ms (generous for tests)
assert duration < 0.1
def test_health_ready_with_deep_check(self, client):
"""Test readiness endpoint with deep feature checks."""
response = client.get("/health/ready")
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert "timestamp" in data
assert "provider" in data
def test_health_ready_without_deep_check(self, client):
"""Test readiness endpoint with deep=false skips feature checks."""
response = client.get("/health/ready?deep=false")
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert "timestamp" in data
# Features should not be present when deep=false
assert "features" not in data
def test_api_health_is_cached(self, client):
"""Test /api/health endpoint uses caching."""
import time
# First request
response1 = client.get("/api/health")
assert response1.status_code == 200
data1 = response1.json()
# Second request should be cached
time.sleep(0.1)
response2 = client.get("/api/health")
assert response2.status_code == 200
data2 = response2.json()
# Timestamps should be identical (cached)
assert data1["timestamp"] == data2["timestamp"]
def test_health_endpoints_accessible(self, client):
"""Test all health endpoints are accessible."""
endpoints = [
"/health",
"/health/live",
"/health/ready",
"/api/health",
"/api/health/live",
"/api/health/ready",
]
for endpoint in endpoints:
response = client.get(endpoint)
assert response.status_code == 200
def test_health_live_under_500ms(self, client):
"""Liveness probe must respond well under 500ms."""
import time
start = time.perf_counter()
response = client.get("/health/live")
duration = time.perf_counter() - start
assert response.status_code == 200
assert response.json()["status"] == "ok"
assert duration < 0.5
def test_health_deep_false_is_liveness(self, client):
"""Legacy /health?deep=false returns an immediate liveness response."""
response = client.get("/health?deep=false")
assert response.status_code == 200
data = response.json()
assert data["status"] == "ok"
assert "timestamp" in data
assert "provider" not in data
def test_health_slow_feature_does_not_block(self, client):
"""A slow feature must not block the legacy /health response."""
import asyncio
import time
from praisonaiui.features import _features
class _SlowFeature:
async def health(self):
await asyncio.sleep(5)
return {"healthy": True}
_features["__slow_test__"] = _SlowFeature()
try:
start = time.perf_counter()
response = client.get("/health")
duration = time.perf_counter() - start
assert response.status_code == 200
assert duration < 2.0
finally:
_features.pop("__slow_test__", None)
def test_health_many_features_under_budget(self, client):
"""Deep /health with many slow features stays bounded (parallel)."""
import asyncio
import time
from praisonaiui.features import _features
class _SlowFeature:
async def health(self):
await asyncio.sleep(0.4)
return {"healthy": True}
for i in range(40):
_features[f"__perf_{i}__"] = _SlowFeature()
try:
start = time.perf_counter()
response = client.get("/health")
duration = time.perf_counter() - start
assert response.status_code == 200
assert duration < 1.5
finally:
for i in range(40):
_features.pop(f"__perf_{i}__", None)
def test_health_deep_is_cached(self, client):
"""Repeat /health probes hit the short-TTL cache and stay fast."""
import time
first = client.get("/health")
assert first.status_code == 200
start = time.perf_counter()
second = client.get("/health")
duration = time.perf_counter() - start
assert second.status_code == 200
assert second.json().get("cached") is True
assert duration < 0.1
class TestProviderHealthEndpoint:
"""Tests for the provider health JSON route (#151)."""
def test_api_provider_health_is_json(self, client):
"""/api/provider/health returns JSON, not the SPA HTML shell."""
response = client.get("/api/provider/health")
assert response.status_code == 200
assert "application/json" in response.headers["content-type"]
body = response.json()
assert "status" in body
assert "type" in body
assert "agents" in body
assert "detail" in body
def test_api_provider_health_accept_json(self, client):
"""Accept: application/json still yields JSON body."""
response = client.get(
"/api/provider/health", headers={"Accept": "application/json"}
)
assert response.status_code == 200
assert "application/json" in response.headers["content-type"]
assert "status" in response.json()
def test_unknown_api_path_not_html(self, client):
"""Unmatched /api/* paths return JSON 404, never SPA HTML 200."""
response = client.get("/api/this-route-does-not-exist")
assert response.status_code == 404
assert "text/html" not in response.headers.get("content-type", "")
assert "application/json" in response.headers["content-type"]
def test_provider_health_route_before_catchall(self, client):
"""The dedicated route is registered before the SPA catch-all."""
paths = [getattr(r, "path", None) for r in client.app.routes]
assert "/api/provider/health" in paths
catchall = next(
(i for i, p in enumerate(paths) if p == "/{path:path}"), None
)
if catchall is not None:
assert paths.index("/api/provider/health") < catchall
class TestAgentsEndpoint:
"""Tests for agents endpoint."""
+113
View File
@@ -0,0 +1,113 @@
"""Regression tests for C-01 — Ctrl+K session search in the dashboard style.
Issue #155: PR #145 shipped SessionSearch in React layouts only. The dashboard
style (`dashboard.js`) skips React entirely, so Ctrl+K never reached the primary
ops example. These tests assert the vanilla-JS palette is wired into
`dashboard.js` (and its session-select handler into the chat view), mirroring the
source-parsing approach used by `test_form_input_names.py`.
"""
from __future__ import annotations
import re
from pathlib import Path
FRONTEND = (
Path(__file__).resolve().parents[2]
/ "src" / "praisonaiui" / "templates" / "frontend" / "plugins"
)
DASHBOARD_JS = FRONTEND / "dashboard.js"
CHAT_JS = FRONTEND / "views" / "chat.js"
class TestDashboardSessionSearch:
def _src(self) -> str:
return DASHBOARD_JS.read_text()
def test_ctrl_k_keydown_handler_present(self):
src = self._src()
assert "initSessionSearch" in src
m = re.search(
r"function initSessionSearch\(\)\s*\{(.*?)^}",
src, re.DOTALL | re.MULTILINE,
)
assert m, "initSessionSearch not found"
body = m.group(1)
assert "keydown" in body
assert "ctrlKey" in body and "metaKey" in body
assert "'k'" in body or '"k"' in body
assert "preventDefault" in body
def test_palette_fetches_sessions_endpoint(self):
src = self._src()
m = re.search(
r"async function openSessionSearch\(\)\s*\{(.*?)^}",
src, re.DOTALL | re.MULTILINE,
)
assert m, "openSessionSearch not found"
assert "/sessions" in m.group(1)
def test_select_navigates_via_selectPage(self):
src = self._src()
m = re.search(
r"function selectSession\(sessionId\)\s*\{(.*?)^}",
src, re.DOTALL | re.MULTILINE,
)
assert m, "selectSession not found"
body = m.group(1)
assert "selectPage" in body
assert "aiui:session-select" in body
def test_init_invokes_session_search(self):
src = self._src()
init_idx = src.find("async function init()")
assert init_idx != -1, "init() not found"
invoke_idx = src.find("initSessionSearch()", init_idx)
approval_idx = src.find("initApprovalStream()", init_idx)
build_idx = src.find("await buildDashboard()", init_idx)
assert invoke_idx != -1, "initSessionSearch() not invoked in init()"
assert build_idx < invoke_idx, "session search must init after dashboard build"
assert abs(invoke_idx - approval_idx) < 400, (
"initSessionSearch() should be wired alongside initApprovalStream()"
)
def test_public_api_exposes_open_session_search(self):
src = self._src()
assert "window.aiui.openSessionSearch" in src
def test_escape_closes_palette(self):
src = self._src()
assert "closeSessionSearch" in src
m = re.search(
r"function initSessionSearch\(\)\s*\{(.*?)^}",
src, re.DOTALL | re.MULTILINE,
)
assert "Escape" in m.group(1)
class TestChatViewSessionSelectListener:
def _src(self) -> str:
return CHAT_JS.read_text()
def test_chat_listens_for_session_select(self):
src = self._src()
assert "aiui:session-select" in src
assert "addEventListener('aiui:session-select'" in src
def test_chat_loads_selected_session(self):
src = self._src()
m = re.search(
r"sessionSelectHandler\s*=\s*\(e\)\s*=>\s*\{(.*?)\};",
src, re.DOTALL,
)
assert m, "sessionSelectHandler not found"
assert "loadSession" in m.group(1)
def test_chat_cleanup_removes_listener(self):
src = self._src()
m = re.search(
r"export function cleanup\(\)\s*\{(.*?)\n}",
src, re.DOTALL,
)
assert m, "cleanup() not found"
assert "removeEventListener('aiui:session-select'" in m.group(1)
+54
View File
@@ -0,0 +1,54 @@
"""Tests for version consistency between pyproject.toml and __version__.py"""
import sys
from pathlib import Path
def test_version_consistency():
"""Test that pyproject.toml version matches __version__.py"""
# Python 3.11+ has tomllib in stdlib, older versions need tomli
if sys.version_info >= (3, 11):
import tomllib
else:
try:
import tomli as tomllib
except ImportError:
# For Python < 3.11 without tomli, use a simple regex approach
import re
# Read pyproject.toml
project_root = Path(__file__).parent.parent.parent
pyproject_path = project_root / "pyproject.toml"
with open(pyproject_path, "r") as f:
content = f.read()
# Extract version from pyproject.toml
match = re.search(r'^version\s*=\s*"([^"]+)"', content, re.MULTILINE)
if not match:
raise ValueError("Could not find version in pyproject.toml")
pyproject_version = match.group(1)
# Import and check __version__
from praisonaiui import __version__
assert __version__ == pyproject_version, (
f"Version mismatch: pyproject.toml has {pyproject_version} "
f"but __version__.py has {__version__}"
)
return
# Standard approach with tomllib/tomli
project_root = Path(__file__).parent.parent.parent
pyproject_path = project_root / "pyproject.toml"
with open(pyproject_path, "rb") as f:
pyproject = tomllib.load(f)
pyproject_version = pyproject["project"]["version"]
from praisonaiui import __version__
assert __version__ == pyproject_version, (
f"Version mismatch: pyproject.toml has {pyproject_version} "
f"but __version__.py has {__version__}"
)
+86
View File
@@ -0,0 +1,86 @@
"""Tests for Windows encoding compatibility in CLI output.
These verify the dynamic ``_icon()`` fallback: rich Unicode symbols are kept on
UTF-aware terminals (macOS/Linux/Windows Terminal) and degrade to ASCII only on
legacy Windows code pages (cp1252/cp437) so output never raises
``UnicodeEncodeError``.
"""
import sys
from unittest import mock
from praisonaiui import cli
from praisonaiui.cli import _icon, _supports_unicode
class TestSupportsUnicode:
"""Detection of UTF-aware terminals."""
def test_utf8_encoding_is_supported(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "utf-8"
with mock.patch("sys.stdout", mock_stdout):
assert _supports_unicode() is True
def test_cp1252_encoding_is_not_supported(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "cp1252"
with mock.patch("sys.stdout", mock_stdout):
assert _supports_unicode() is False
def test_missing_encoding_falls_back_to_ascii(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = None
with mock.patch("sys.stdout", mock_stdout):
assert _supports_unicode() is False
class TestIconHelper:
"""The _icon() helper picks symbol vs fallback based on terminal."""
def test_returns_symbol_on_utf8(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "utf-8"
with mock.patch("sys.stdout", mock_stdout):
assert _icon("✅", "[OK]") == "✅"
def test_returns_fallback_on_cp1252(self):
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "cp1252"
with mock.patch("sys.stdout", mock_stdout):
assert _icon("✅", "[OK]") == "[OK]"
def test_fallback_is_pure_ascii(self):
"""Every fallback must be encodable on a legacy code page."""
mock_stdout = mock.MagicMock()
mock_stdout.encoding = "cp1252"
with mock.patch("sys.stdout", mock_stdout):
for symbol, fallback in [
("✅", "[PASS]"),
("⚠️", "[WARN]"),
("❌", "[FAIL]"),
("⏳", "..."),
("▶", ">"),
("═", "="),
]:
result = _icon(symbol, fallback)
result.encode("cp1252") # must not raise
class TestUtf8Reconfigure:
"""The module reconfigures stdio to UTF-8 on Windows at import time."""
def test_utf8_reconfigure_on_windows(self):
import importlib
with mock.patch("sys.platform", "win32"):
mock_reconfigure = mock.MagicMock()
with mock.patch.object(sys.stdout, "reconfigure", mock_reconfigure, create=True):
with mock.patch.object(sys.stderr, "reconfigure", mock_reconfigure, create=True):
importlib.reload(cli)
assert mock_reconfigure.call_count >= 1
mock_reconfigure.assert_called_with(encoding="utf-8")
# Reload once more without the Windows patch so other tests see the
# real module state.
importlib.reload(cli)
Generated
+13 -9
View File
@@ -207,7 +207,7 @@ wheels = [
[[package]]
name = "aiui"
version = "0.3.121"
version = "0.3.127"
source = { editable = "." }
dependencies = [
{ name = "pydantic" },
@@ -367,9 +367,9 @@ requires-dist = [
{ name = "mem0ai", marker = "extra == 'knowledge'", specifier = ">=0.1.0" },
{ name = "mem0ai", extras = ["graph"], marker = "extra == 'graph'", specifier = ">=0.1.0" },
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0" },
{ name = "praisonai", extras = ["bot", "api", "os"], marker = "extra == 'praisonai'", specifier = ">=2.0.0" },
{ name = "praisonai", extras = ["bot", "api", "os"], marker = "extra == 'praisonai'", specifier = ">=4.6.0" },
{ name = "praisonaiagents", extras = ["a2ui"], marker = "extra == 'a2ui'", specifier = ">=1.6.38" },
{ name = "praisonaiagents", extras = ["knowledge", "search", "crawl"], marker = "extra == 'praisonai'", specifier = ">=1.5.56" },
{ name = "praisonaiagents", extras = ["knowledge", "search", "crawl"], marker = "extra == 'praisonai'", specifier = ">=1.6.46" },
{ name = "praisonaiagents", extras = ["llm"], marker = "extra == 'dev'", specifier = ">=1.5.56" },
{ name = "psutil", marker = "extra == 'praisonai'", specifier = ">=5.9.0" },
{ name = "pydantic", specifier = ">=2.0" },
@@ -4961,13 +4961,14 @@ wheels = [
[[package]]
name = "praisonai"
version = "4.5.33"
version = "4.6.71"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "litellm" },
{ name = "markdown" },
{ name = "mcp" },
{ name = "praisonaiagents" },
{ name = "pydantic" },
{ name = "pyparsing" },
{ name = "python-dotenv" },
{ name = "pyyaml" },
@@ -4975,9 +4976,9 @@ dependencies = [
{ name = "textual" },
{ name = "typer" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/cd/afe5d205376ab485d218d30e6a882efd4cc02a8340f15d397138c60712ee/praisonai-4.5.33.tar.gz", hash = "sha256:ae32eacb9a0247088a6f6fc43be6460eeaa8cc8c07a2aad46fcd01fc44bfa777", size = 1686757, upload-time = "2026-03-10T06:07:47.771Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a3/a6/609ab1bdeef2575a32e8042d8014c739201561573db946db4d703be91511/praisonai-4.6.71.tar.gz", hash = "sha256:eddea7915f4d70f0932e968c5b26fb4f3cb17cb83972d4a583418559dae61f09", size = 2129838, upload-time = "2026-06-22T23:54:32.224Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/e4/1130314a1f9ddaa90612d931d23fca80feca95a2b18d74d76859fa660b24/praisonai-4.5.33-py3-none-any.whl", hash = "sha256:3bc4ac16af293609126c5b7e3788cde95927a98bedd6e2c8528eb3328253f38a", size = 1979940, upload-time = "2026-03-10T06:07:45.016Z" },
{ url = "https://files.pythonhosted.org/packages/a8/53/ece82f6b7c01f0b0fe9bf34442ae3fa2ab87c909f36babb5534b4984c80b/praisonai-4.6.71-py3-none-any.whl", hash = "sha256:fc60aa5a9b87430ec7be2f5a638bc1330b26bbb19275ff4f6ec20f4a49088fc3", size = 2559663, upload-time = "2026-06-22T23:54:29.49Z" },
]
[package.optional-dependencies]
@@ -4989,8 +4990,11 @@ api = [
]
bot = [
{ name = "aiohttp" },
{ name = "aiui" },
{ name = "brotlicffi" },
{ name = "ddgs" },
{ name = "discord-py" },
{ name = "psutil" },
{ name = "python-telegram-bot" },
{ name = "slack-bolt" },
{ name = "slack-sdk" },
@@ -5004,7 +5008,7 @@ os = [
[[package]]
name = "praisonaiagents"
version = "1.6.43"
version = "1.6.71"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "aiohttp" },
@@ -5014,9 +5018,9 @@ dependencies = [
{ name = "pyyaml" },
{ name = "rich" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a7/73/d37d1478c8471869b1a7aa7af0d1b70cdff26de39fc4abebd5529f1c49f7/praisonaiagents-1.6.43.tar.gz", hash = "sha256:6ec949d38b650969a91f7494828f21bfcad0da36a3ec433eb46e8fa072dda42e", size = 1436634, upload-time = "2026-05-20T11:09:16.645Z" }
sdist = { url = "https://files.pythonhosted.org/packages/be/9b/4e1431d7b2125dab034192984d5f419e96bd412d314871450855d52e5bab/praisonaiagents-1.6.71.tar.gz", hash = "sha256:ea048bc8dc00bea911be050e881c7bbaf167ac2d4805b4c3013958ba8ee63e59", size = 1589730, upload-time = "2026-06-22T23:52:30.753Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/97/768038d2d3a2f791de7e6cf0df2783c590b9262ba3ec895308236715b033/praisonaiagents-1.6.43-py3-none-any.whl", hash = "sha256:27028366b0bde0267abdbfe4140d4d5a4eed7e5c9bdb544243973dba8d5cb57b", size = 1511490, upload-time = "2026-05-20T11:09:14.467Z" },
{ url = "https://files.pythonhosted.org/packages/b8/cf/b177db73453470093a421f8cdb07e959d546ebfc147842cfdd5937827e0e/praisonaiagents-1.6.71-py3-none-any.whl", hash = "sha256:50878751aa9b27960aeeda80190ed291c6bd2fa8ab9cf398cda3f55908860eae", size = 1679696, upload-time = "2026-06-22T23:52:28.673Z" },
]
[package.optional-dependencies]