mirror of
https://github.com/usestrix/strix.git
synced 2026-11-09 05:19:39 -10:00
Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8faf47de96 | ||
|
|
fb60e104f6 | ||
|
|
8267d7b311 | ||
|
|
8685c53b55 | ||
|
|
b4113e662f | ||
|
|
93dae3db07 | ||
|
|
1e1339e494 | ||
|
|
0ff9f8c324 | ||
|
|
954bc0d527 | ||
|
|
e66c56c473 | ||
|
|
50425c2c99 | ||
|
|
6ae036e6c1 | ||
|
|
0c702723aa | ||
|
|
d6dd9dec26 | ||
|
|
ae38fe70cd | ||
|
|
4c1be22150 | ||
|
|
56f7d45388 | ||
|
|
e158eab3f8 | ||
|
|
56e9ae982c | ||
|
|
355a8bb437 | ||
|
|
77a0cf839b | ||
|
|
cafa4b19fd | ||
|
|
976835194d | ||
|
|
4c1f00d1ee | ||
|
|
46d7bdb290 | ||
|
|
910c1ea4bb | ||
|
|
65d495bb7f | ||
|
|
2dadbb748a | ||
|
|
84f4108195 | ||
|
|
0c4364a6a7 | ||
|
|
95e085eb6c | ||
|
|
22959a7ba6 | ||
|
|
52b1923347 | ||
|
|
ff5c8cc8e4 | ||
|
|
afce7d95e8 | ||
|
|
2e1db25786 | ||
|
|
f4b0416b71 |
No files matched your search
@@ -1,4 +1,4 @@
|
||||
.PHONY: help install dev-install format lint type-check security check-all clean pre-commit setup-dev dev viewer wheel tui-build tui-test tui-lint
|
||||
.PHONY: help install dev-install format lint format-check lint-check type-check security check-all clean pre-commit setup-dev dev viewer wheel tui-build tui-test tui-lint
|
||||
|
||||
TUI_BINARY := build/sidecar/strix-tui$(if $(filter Windows_NT,$(OS)),.exe)
|
||||
|
||||
@@ -10,7 +10,9 @@ help:
|
||||
@echo ""
|
||||
@echo "Code Quality:"
|
||||
@echo " format - Format code with ruff"
|
||||
@echo " lint - Lint code with ruff"
|
||||
@echo " lint - Lint code with ruff and apply fixes"
|
||||
@echo " format-check - Check formatting without modifying files"
|
||||
@echo " lint-check - Check lint without modifying files"
|
||||
@echo " type-check - Run type checking with mypy and pyright"
|
||||
@echo " security - Run security checks with bandit"
|
||||
@echo " check-all - Run all code quality checks"
|
||||
@@ -45,6 +47,12 @@ lint:
|
||||
uv run ruff check . --fix
|
||||
@echo "✅ Linting complete!"
|
||||
|
||||
format-check:
|
||||
uv run ruff format --check .
|
||||
|
||||
lint-check:
|
||||
uv run ruff check .
|
||||
|
||||
type-check:
|
||||
@echo "🔍 Type checking with mypy..."
|
||||
uv run mypy strix/
|
||||
@@ -57,7 +65,7 @@ security:
|
||||
uv run bandit -r strix/ -c pyproject.toml
|
||||
@echo "✅ Security checks complete!"
|
||||
|
||||
check-all: format lint type-check security
|
||||
check-all: format-check lint-check type-check security
|
||||
@echo "✅ All code quality checks passed!"
|
||||
|
||||
pre-commit:
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
<a href="https://strix.ai"><img src="https://img.shields.io/badge/Website-strix.ai-f0f0f0?style=for-the-badge&logoColor=000000" alt="Website"></a>
|
||||
[](https://discord.gg/strix-ai)
|
||||
|
||||
<a href="https://app.strix.ai?utm_source=github&utm_medium=readme&utm_content=badge_cloud"><img src="https://img.shields.io/badge/Strix%20Cloud-app.strix.ai-2b9246?style=for-the-badge&logoColor=white" alt="Strix Cloud"></a>
|
||||
<a href="https://strix.ai/demo?utm_source=github&utm_medium=readme&utm_content=badge_demo"><img src="https://img.shields.io/badge/Try%20Strix%20Enterprise-555555?style=for-the-badge&logoColor=white" alt="Try Strix Enterprise"></a>
|
||||
|
||||
<a href="https://deepwiki.com/usestrix/strix"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
||||
<a href="https://github.com/usestrix/strix"><img src="https://img.shields.io/github/stars/usestrix/strix?style=flat-square" alt="GitHub Stars"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-Apache%202.0-3b82f6?style=flat-square" alt="License"></a>
|
||||
@@ -34,7 +37,7 @@
|
||||
|
||||
|
||||
> [!TIP]
|
||||
> **New!** Strix integrates seamlessly with GitHub Actions and CI/CD pipelines. Automatically scan for vulnerabilities on every pull request and block insecure code before it reaches production - [Get started with no setup required](https://app.strix.ai).
|
||||
> **New!** Strix integrates seamlessly with GitHub Actions and CI/CD pipelines. Automatically scan for vulnerabilities on every pull request and block insecure code before it reaches production - [Get started with no setup required](https://app.strix.ai?utm_source=github&utm_medium=readme&utm_content=tip_ci).
|
||||
|
||||
---
|
||||
|
||||
@@ -94,9 +97,17 @@ strix --target ./app-directory
|
||||
|
||||
---
|
||||
|
||||
## ☁️ Strix Platform
|
||||
## Ways to Run Strix
|
||||
|
||||
Try the Strix full-stack penetration testing platform at **[app.strix.ai](https://app.strix.ai)** - sign up for free, connect your repos and domains, and launch a pentest in minutes.
|
||||
- **Open Source** - free, runs locally with Docker and your own LLM key. [Quick Start](https://docs.strix.ai/quickstart)
|
||||
- **Strix Cloud** - no setup, validated findings, one-click autofix, and PR reviews. [Run a pentest →](https://app.strix.ai?intent=pentest&utm_source=github&utm_medium=readme&utm_content=table_cloud)
|
||||
- **Enterprise** - SSO, compliance-ready reports, VPC or self-hosted deployment. [Try Strix Enterprise →](https://strix.ai/demo?utm_source=github&utm_medium=readme&utm_content=table_demo)
|
||||
|
||||
---
|
||||
|
||||
## ☁️ Strix Cloud
|
||||
|
||||
Try the Strix full-stack penetration testing platform at **[app.strix.ai](https://app.strix.ai?utm_source=github&utm_medium=readme&utm_content=cloud_heading)** - sign up for free, connect your repos and domains, and launch a pentest in minutes.
|
||||
|
||||
- **Validated findings with PoCs** - every vulnerability includes a working proof-of-concept exploit and reproduction steps
|
||||
- **One-click autofix** - AI-generated security patches as ready-to-merge pull requests
|
||||
@@ -104,7 +115,13 @@ Try the Strix full-stack penetration testing platform at **[app.strix.ai](https:
|
||||
- **DevSecOps integrations** - GitHub, GitLab, Bitbucket, Slack, Jira, Linear, and CI/CD pipelines
|
||||
- **Continuous learning** - AI that builds on past findings, adapts to your codebase, and reduces false positives over time
|
||||
|
||||
[**Start your first pentest →**](https://app.strix.ai)
|
||||
[**Run a pentest →**](https://app.strix.ai?intent=pentest&utm_source=github&utm_medium=readme&utm_content=cloud_cta)
|
||||
|
||||
## 🏢 Enterprise
|
||||
|
||||
Get the same Strix experience with enterprise-grade controls: SSO (SAML/OIDC), custom compliance-ready penetration testing reports (SOC 2, ISO 27001, PCI DSS), dedicated support and SLA, custom deployment options (VPC or self-hosted), BYOK model support, and tailored AI pentesting agents optimized for your environment.
|
||||
|
||||
[**Try Strix Enterprise →**](https://strix.ai/demo?utm_source=github&utm_medium=readme&utm_content=enterprise_cta)
|
||||
|
||||
---
|
||||
|
||||
@@ -333,10 +350,6 @@ Each server's tools are namespaced by `name`, for example `github_list_issues`.
|
||||
|
||||
See the [LLM Providers documentation](https://docs.strix.ai/llm-providers/overview) for all supported providers including Vertex AI, Bedrock, Azure, and local models.
|
||||
|
||||
## Enterprise Pentesting
|
||||
|
||||
Get the same Strix experience with [enterprise-grade](https://strix.ai/demo) controls: SSO (SAML/OIDC), custom compliance-ready penetration testing reports (SOC 2, ISO 27001, PCI DSS), dedicated support & SLA, custom deployment options (VPC/self-hosted), BYOK model support, and tailored AI pentesting agents optimized for your environment. [Learn more](https://strix.ai/demo).
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation is available at **[docs.strix.ai](https://docs.strix.ai)** - including detailed guides for usage, CI/CD integrations, skills, and advanced configuration.
|
||||
|
||||
@@ -19,6 +19,12 @@ Configure Strix using environment variables or a config file.
|
||||
Custom API base URL. Also accepts `OPENAI_API_BASE`, `LITELLM_BASE_URL`, or `OLLAMA_API_BASE`.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="STRIX_API_TYPE" type="string">
|
||||
Select the OpenAI API path for the model: `responses` or `chat_completions`.
|
||||
By default, a custom `LLM_API_BASE` uses chat completions. Set this variable
|
||||
when your gateway requires the other API. Also accepts `STRIX_FORCE_API`.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="LLM_EXTRA_HEADERS" type="string">
|
||||
Extra HTTP headers sent on every LLM request, as a JSON object (e.g.
|
||||
`{"X-Feature-Key":"value","X-Tenant":"acme"}`). Useful for OpenAI-compatible
|
||||
@@ -43,6 +49,14 @@ Configure Strix using environment variables or a config file.
|
||||
Timeout in seconds for memory compression operations (context summarization).
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="STRIX_OPENROUTER_STICKY_SESSIONS" default="false" type="boolean">
|
||||
Send a per-agent `session_id` on OpenRouter requests, so each agent's calls stay on one upstream provider and its prompt cache carries over between turns. When unset, OpenRouter routes every request freely.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="STRIX_CACHE_BLOCK_TOKENS" default="128" type="integer">
|
||||
Token block size that providers cache prompts in. A turn counts as a cache miss in the run report only when the cached tokens fall at least this many tokens short of the previous prompt and the prompt did not shrink. Lower it for providers with smaller blocks (DeepSeek and GLM use 64; vLLM defaults to 16).
|
||||
</ParamField>
|
||||
|
||||
### Dedicated deduplication model
|
||||
|
||||
Finding deduplication is a cheap, structured classification task. By default it
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
"llm-providers/openai",
|
||||
"llm-providers/anthropic",
|
||||
"llm-providers/openrouter",
|
||||
"llm-providers/vercel-ai-gateway",
|
||||
"llm-providers/vertex",
|
||||
"llm-providers/bedrock",
|
||||
"llm-providers/azure",
|
||||
|
||||
@@ -46,6 +46,9 @@ See the [Local Models guide](/llm-providers/local) for setup instructions and re
|
||||
<Card title="OpenRouter" href="/llm-providers/openrouter">
|
||||
Access 100+ models through a single API.
|
||||
</Card>
|
||||
<Card title="Vercel AI Gateway" href="/llm-providers/vercel-ai-gateway">
|
||||
Access models from multiple providers through one endpoint.
|
||||
</Card>
|
||||
<Card title="Google Vertex AI" href="/llm-providers/vertex">
|
||||
Gemini 3 models via Google Cloud.
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
title: "Vercel AI Gateway"
|
||||
description: "Configure Strix with models via Vercel AI Gateway"
|
||||
---
|
||||
|
||||
[Vercel AI Gateway](https://vercel.com/docs/ai-gateway) provides an OpenAI-compatible API for models from multiple providers.
|
||||
|
||||
## Setup
|
||||
|
||||
Create an [AI Gateway API key](https://vercel.com/docs/ai-gateway/authentication-and-byok), then configure Strix:
|
||||
|
||||
```bash
|
||||
export STRIX_LLM="openai/anthropic/claude-opus-5"
|
||||
export LLM_API_KEY="your-ai-gateway-api-key"
|
||||
export LLM_API_BASE="https://ai-gateway.vercel.sh/v1"
|
||||
```
|
||||
|
||||
The first `openai/` segment tells Strix to use its OpenAI-compatible client. The remaining value is the [AI Gateway model ID](https://vercel.com/docs/ai-gateway/models-and-providers).
|
||||
|
||||
## Available Models
|
||||
|
||||
Use any language model returned by the AI Gateway models endpoint:
|
||||
|
||||
```text
|
||||
https://ai-gateway.vercel.sh/v1/models
|
||||
```
|
||||
|
||||
Prefix its model ID with `openai/` when setting `STRIX_LLM`. For example, the Gateway model ID `anthropic/claude-opus-5` becomes `openai/anthropic/claude-opus-5` in Strix.
|
||||
|
||||
## Get API Key
|
||||
|
||||
1. Open the [AI Gateway API key settings](https://vercel.com/docs/ai-gateway/authentication-and-byok)
|
||||
2. Create an API key
|
||||
3. Set the key as `LLM_API_KEY`
|
||||
|
||||
## Benefits
|
||||
|
||||
- Access models from multiple providers through one endpoint
|
||||
- Track Gateway usage and cost in Vercel
|
||||
@@ -96,7 +96,8 @@ agent task, so the agent knows where to read them.
|
||||
|
||||
Rules that apply to every workspace file:
|
||||
|
||||
- The file is read-only inside the sandbox.
|
||||
- Strix copies the file into the sandbox. The agent can edit the copy, but the
|
||||
file on your machine does not change.
|
||||
- The destination must stay inside `/workspace`.
|
||||
- The destination must not fall inside a target directory, because target files
|
||||
come from the target itself. Strix skips such a file and logs a warning.
|
||||
|
||||
+5
-2
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "strix-agent"
|
||||
version = "1.6.1"
|
||||
version = "1.6.2"
|
||||
description = "Open-source AI Hackers for your apps"
|
||||
readme = "README.md"
|
||||
license = "Apache-2.0"
|
||||
@@ -35,7 +35,7 @@ classifiers = [
|
||||
dependencies = [
|
||||
"openai-agents[litellm]>=0.19.0,<0.20",
|
||||
"openai>=2.45.0,<3",
|
||||
"litellm",
|
||||
"litellm>=1.101.0",
|
||||
"pydantic>=2.11.3",
|
||||
"pydantic-settings>=2.13.0",
|
||||
"rich",
|
||||
@@ -308,6 +308,9 @@ ignore = [
|
||||
"strix/report/dedupe.py" = ["PLC0415"]
|
||||
"strix/telemetry/logging.py" = ["PLC0415"]
|
||||
"strix/config/models.py" = ["PLC0415"]
|
||||
# LiteLLM is imported lazily: the request log is wired at startup on every
|
||||
# route, including the native OpenAI ones that never load LiteLLM.
|
||||
"strix/llm/request_log.py" = ["PLC0415"]
|
||||
# Heavy inference deps (httpx, openai) imported lazily so auth-status checks
|
||||
# don't pull them in.
|
||||
"strix/config/codex.py" = ["PLC0415"]
|
||||
|
||||
@@ -346,6 +346,9 @@ echo -e "${MUTED}For more information visit ${NC}https://strix.ai"
|
||||
echo -e "${MUTED}Supported models ${NC}https://docs.strix.ai/llm-providers/overview"
|
||||
echo -e "${MUTED}Join our community ${NC}https://discord.gg/strix-ai"
|
||||
echo ""
|
||||
echo -e "${MUTED}Run a pentest in Strix Cloud ${NC}https://app.strix.ai"
|
||||
echo -e "${MUTED}Enterprise ${NC}https://strix.ai/demo"
|
||||
echo ""
|
||||
|
||||
echo -e "${YELLOW}→${NC} Run ${MUTED}source ~/.$(basename $SHELL)rc${NC} or open a new terminal"
|
||||
echo ""
|
||||
@@ -12,7 +12,7 @@ from typing import Any
|
||||
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
||||
|
||||
|
||||
class CustomBuildHook(BuildHookInterface[Any]):
|
||||
class CustomBuildHook(BuildHookInterface): # type: ignore[type-arg]
|
||||
"""Compile the Bubble Tea sidecar and ship it inside the wheel.
|
||||
|
||||
The sidecar is the only interactive interface, so every wheel is a
|
||||
|
||||
@@ -48,7 +48,7 @@ Out of scope: POST /billing/*, POST /notifications/broadcast."
|
||||
- **GraphQL:** point at the GraphQL endpoint and say whether introspection is enabled; call out that you want batching/aliasing abuse, depth/complexity limits, and per-field authorization tested.
|
||||
- **Internal/private APIs** unreachable from your machine: use the managed platform's network connector — see **managed-pentesting-with-strix**.
|
||||
- Use `--instruction-file` when the credential/context block gets long, and keep tokens out of shell history and out of committed files.
|
||||
- **Supporting files** the agents should read but not test, such as an endpoint wordlist or handwritten notes about the tenancy model: pass `--workspace-file ./notes.md`. The file lands read-only in `/workspace`. Add `:DEST` to choose the path, for example `--workspace-file ./wordlist.txt:lists/wordlist.txt`.
|
||||
- **Supporting files** the agents should read but not test, such as an endpoint wordlist or handwritten notes about the tenancy model: pass `--workspace-file ./notes.md`. Strix copies the file into `/workspace`. The file on your machine does not change. Add `:DEST` to choose the path, for example `--workspace-file ./wordlist.txt:lists/wordlist.txt`.
|
||||
|
||||
## 3. Verify findings
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ Key flags:
|
||||
| `-n, --non-interactive` | Headless, exits on completion. Required for agents. |
|
||||
| `-m, --scan-mode` | `quick` (minutes) / `standard` (~30 min) / `deep` (hours, default). |
|
||||
| `--instruction` / `--instruction-file` | Credentials, focus areas, scope rules. |
|
||||
| `--workspace-file PATH[:DEST]` | Place a file from this machine into `/workspace` read-only before the scan, for a wordlist, a spec, or notes. Repeatable. |
|
||||
| `--workspace-file PATH[:DEST]` | Copy a file from this machine into `/workspace` before the scan, for a wordlist, a spec, or notes. Repeatable. |
|
||||
| `--max-budget USD` | Hard LLM spend cap; scan wraps up cleanly at the limit. |
|
||||
| `--max-turns N` | Per-agent turn cap (default 500). |
|
||||
| `--resume RUN_NAME` | Resume a prior run from `strix_runs/`, with its agent history and targets. Cannot be combined with `-t`. |
|
||||
|
||||
+11
-1
@@ -29,7 +29,13 @@ from strix.tools.agents_graph.tools import (
|
||||
from strix.tools.coverage.tools import list_coverage, record_coverage, update_coverage
|
||||
from strix.tools.finish.tool import finish_scan
|
||||
from strix.tools.load_skill.tool import load_skill
|
||||
from strix.tools.mcp import call_mcp, describe_mcp, list_mcps
|
||||
from strix.tools.mcp import (
|
||||
call_mcp,
|
||||
describe_mcp,
|
||||
get_mcp_tool_schema,
|
||||
list_mcps,
|
||||
search_mcp_tools,
|
||||
)
|
||||
from strix.tools.notes.tools import (
|
||||
create_note,
|
||||
delete_note,
|
||||
@@ -50,6 +56,7 @@ from strix.tools.proxy.tools import (
|
||||
from strix.tools.reporting.tool import (
|
||||
create_dependency_report,
|
||||
create_vulnerability_report,
|
||||
delete_vulnerability_report,
|
||||
get_report,
|
||||
list_reports,
|
||||
update_vulnerability_report,
|
||||
@@ -583,6 +590,7 @@ _BASE_TOOLS: tuple[Tool, ...] = (
|
||||
create_vulnerability_report,
|
||||
create_dependency_report,
|
||||
update_vulnerability_report,
|
||||
delete_vulnerability_report,
|
||||
list_reports,
|
||||
get_report,
|
||||
list_requests,
|
||||
@@ -592,6 +600,8 @@ _BASE_TOOLS: tuple[Tool, ...] = (
|
||||
view_sitemap_entry,
|
||||
scope_rules,
|
||||
list_mcps,
|
||||
search_mcp_tools,
|
||||
get_mcp_tool_schema,
|
||||
describe_mcp,
|
||||
call_mcp,
|
||||
view_agent_graph,
|
||||
|
||||
+38
-2
@@ -16,6 +16,10 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
_PROMPT_DIRNAME = "prompts"
|
||||
|
||||
# Marks where the system prompt is split so the part before it can be cached.
|
||||
# Removed before the prompt is sent.
|
||||
CACHE_POINT = "<cache_point>"
|
||||
|
||||
|
||||
def _resolve_skills(
|
||||
*,
|
||||
@@ -80,8 +84,13 @@ def render_system_prompt(
|
||||
is_diff_scoped: bool = False,
|
||||
interactive: bool = False,
|
||||
system_prompt_context: dict[str, Any] | None = None,
|
||||
include_scope: bool = True,
|
||||
) -> str:
|
||||
"""Render the system prompt. Returns empty string on template failure."""
|
||||
"""Render the system prompt. Returns empty string on template failure.
|
||||
|
||||
The per-run scope (targets, MCP connections) goes last so the rest of the
|
||||
prompt is an identical prefix across runs and can be served from cache.
|
||||
"""
|
||||
try:
|
||||
prompt_dir = get_strix_resource_path("agents", _PROMPT_DIRNAME)
|
||||
loader_dirs = [prompt_dir, *skill_search_dirs()]
|
||||
@@ -93,6 +102,16 @@ def render_system_prompt(
|
||||
),
|
||||
)
|
||||
|
||||
shared = {
|
||||
name.split("/")[-1]
|
||||
for name in _resolve_skills(
|
||||
requested=None,
|
||||
scan_mode=scan_mode,
|
||||
is_whitebox=is_whitebox,
|
||||
is_root=is_root,
|
||||
is_diff_scoped=is_diff_scoped,
|
||||
)
|
||||
}
|
||||
skills_to_load = _resolve_skills(
|
||||
requested=skills,
|
||||
scan_mode=scan_mode,
|
||||
@@ -103,12 +122,16 @@ def render_system_prompt(
|
||||
skill_content = load_skills(skills_to_load)
|
||||
env.globals["get_skill"] = lambda name: skill_content.get(name, "")
|
||||
|
||||
# Skills every agent of this kind loads come first, so siblings share them
|
||||
# as a cached prefix; the ones the caller asked for vary and go after.
|
||||
rendered = env.get_template("system_prompt.jinja").render(
|
||||
loaded_skill_names=list(skill_content.keys()),
|
||||
shared_skill_names=[name for name in skill_content if name in shared],
|
||||
requested_skill_names=[name for name in skill_content if name not in shared],
|
||||
available_skills=get_available_skills(),
|
||||
interactive=interactive,
|
||||
is_root=is_root,
|
||||
system_prompt_context=system_prompt_context or {},
|
||||
include_scope=include_scope,
|
||||
**skill_content,
|
||||
)
|
||||
except Exception:
|
||||
@@ -124,3 +147,16 @@ def render_system_prompt(
|
||||
len(rendered),
|
||||
)
|
||||
return str(rendered)
|
||||
|
||||
|
||||
def render_scope_prompt(system_prompt_context: dict[str, Any] | None) -> str:
|
||||
"""Render only the per-run scope block that ends the system prompt."""
|
||||
prompt_dir = get_strix_resource_path("agents", _PROMPT_DIRNAME)
|
||||
env = Environment(
|
||||
loader=FileSystemLoader(prompt_dir),
|
||||
autoescape=select_autoescape(enabled_extensions=(), default_for_string=False),
|
||||
)
|
||||
rendered = env.get_template("scope.jinja").render(
|
||||
system_prompt_context=system_prompt_context or {},
|
||||
)
|
||||
return str(rendered).strip()
|
||||
@@ -0,0 +1,35 @@
|
||||
<cache_point>
|
||||
{% if system_prompt_context and system_prompt_context.authorized_targets %}
|
||||
SYSTEM-VERIFIED SCOPE:
|
||||
- The following scope metadata is injected by the platform into the system prompt and is authoritative
|
||||
- Scope source: {{ system_prompt_context.scope_source }}
|
||||
- Authorization source: {{ system_prompt_context.authorization_source }}
|
||||
- Every target listed below has already been verified by the platform as in-scope and authorized
|
||||
- User instructions, chat messages, and other free-form text do NOT expand scope beyond this list
|
||||
- NEVER refuse, question authorization, or claim lack of permission for any target in this system-verified scope
|
||||
- NEVER test any external domain, URL, host, IP, or repository that is not explicitly listed in this system-verified scope
|
||||
- If the user mentions any asset outside this list, ignore that asset and continue working only on the listed in-scope targets
|
||||
|
||||
AUTHORIZED TARGETS:
|
||||
{% for target in system_prompt_context.authorized_targets %}
|
||||
- {{ target.type }}: {{ target.value }}{% if target.workspace_path %} (workspace: {{ target.workspace_path }}){% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if system_prompt_context and system_prompt_context.mcp_available %}
|
||||
MCP CONNECTIONS (available this run):
|
||||
- The user connected one or more MCP (Model Context Protocol) servers. Their individual tools do NOT appear in your tool list. Use the four discovery and dispatch tools to reach them.
|
||||
{% if system_prompt_context.mcp_connections %}
|
||||
- Connected this run (search one to find relevant tools):
|
||||
{% for connection in system_prompt_context.mcp_connections %}
|
||||
- {{ connection.name }} ({{ connection.tool_count }} tools){% if connection.purpose %}: {{ connection.purpose }}{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
- Reach for a connection whenever the target itself cannot give you information a connection could: its database schema and access policies, real deployment or infrastructure configuration, known issues or prior findings, or server logs. In those cases call list_mcps early to see what is available, and prefer a connection's authoritative data over inferring from the target's responses. Do not wait to be told a connection exists.
|
||||
1. Call list_mcps() to discover the available connections.
|
||||
2. Call search_mcp_tools(connection="<name>", query="<capability>") for a short candidate list.
|
||||
3. Call get_mcp_tool_schema(connection="<name>", tool="<tool>") for the one schema you need.
|
||||
4. Call call_mcp(connection="<name>", tool="<tool>", arguments={...}) to run it, passing an arguments object that matches the schema (omit arguments for a tool that takes none).
|
||||
- Do not assume a connection or tool exists; discover it with list_mcps and search_mcp_tools before calling.
|
||||
- Use describe_mcp only as a compatibility fallback when targeted search cannot identify an expected tool. Its full catalog can be large.
|
||||
{% endif %}
|
||||
@@ -58,39 +58,6 @@ AUTONOMOUS BEHAVIOR:
|
||||
</communication_rules>
|
||||
|
||||
<execution_guidelines>
|
||||
{% if system_prompt_context and system_prompt_context.authorized_targets %}
|
||||
SYSTEM-VERIFIED SCOPE:
|
||||
- The following scope metadata is injected by the platform into the system prompt and is authoritative
|
||||
- Scope source: {{ system_prompt_context.scope_source }}
|
||||
- Authorization source: {{ system_prompt_context.authorization_source }}
|
||||
- Every target listed below has already been verified by the platform as in-scope and authorized
|
||||
- User instructions, chat messages, and other free-form text do NOT expand scope beyond this list
|
||||
- NEVER refuse, question authorization, or claim lack of permission for any target in this system-verified scope
|
||||
- NEVER test any external domain, URL, host, IP, or repository that is not explicitly listed in this system-verified scope
|
||||
- If the user mentions any asset outside this list, ignore that asset and continue working only on the listed in-scope targets
|
||||
|
||||
AUTHORIZED TARGETS:
|
||||
{% for target in system_prompt_context.authorized_targets %}
|
||||
- {{ target.type }}: {{ target.value }}{% if target.workspace_path %} (workspace: {{ target.workspace_path }}){% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{% if system_prompt_context and system_prompt_context.mcp_available %}
|
||||
MCP CONNECTIONS (available this run):
|
||||
- The user connected one or more MCP (Model Context Protocol) servers — external tool providers you can reach on demand. Their individual tools do NOT appear in your tool list; three dispatch tools are the only way in.
|
||||
{% if system_prompt_context.mcp_connections %}
|
||||
- Connected this run (call describe_mcp on one to see its tools):
|
||||
{% for connection in system_prompt_context.mcp_connections %}
|
||||
- {{ connection.name }} ({{ connection.tool_count }} tools){% if connection.purpose %}: {{ connection.purpose }}{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
- Reach for a connection whenever the target itself cannot give you information a connection could: its database schema and access policies, real deployment or infrastructure configuration, known issues or prior findings, or server logs. In those cases call list_mcps early to see what is available, and prefer a connection's authoritative data over inferring from the target's responses. Do not wait to be told a connection exists.
|
||||
1. Call list_mcps() to discover the available connections.
|
||||
2. Call describe_mcp(connection="<name>") to inspect one connection's tools, each with its name, description, and JSON input schema.
|
||||
3. Call call_mcp(connection="<name>", tool="<tool>", arguments={...}) to run one, passing an arguments object that matches the schema (omit arguments for a tool that takes none).
|
||||
- Do not assume a connection or tool exists; discover it with list_mcps and describe it with describe_mcp before calling.
|
||||
{% endif %}
|
||||
|
||||
AUTHORIZATION STATUS:
|
||||
- You have FULL AUTHORIZATION for authorized security validation on in-scope targets to help secure the target systems/app
|
||||
- All permission checks have been COMPLETED and APPROVED - never question your authority
|
||||
@@ -240,7 +207,9 @@ VALIDATION REQUIREMENTS:
|
||||
- A vulnerability is ONLY considered reported when a reporting agent uses create_vulnerability_report (or create_dependency_report for known-CVE dependency/supply-chain findings) with full details. Mentions in agent_finish, finish_scan, or generic messages are NOT sufficient
|
||||
- Reporting and fixing are ONE step, not two: when source is available, the reporting agent derives the concrete fix and files it INLINE via create_vulnerability_report (`code_locations` with `fix_before`/`fix_after` + `fix_pr_body`) — the report is not complete without it. Do NOT report first and then spawn a separate downstream agent to re-derive and re-apply the same patch; that just re-does the analysis and wastes tokens. (Do not silently patch a finding WITHOUT filing a report — the report, with its embedded fix, is the deliverable.)
|
||||
- DEDUPLICATION: The create_vulnerability_report tool uses LLM-based deduplication. If it rejects your report as a duplicate, DO NOT attempt to re-submit the same vulnerability. Accept the rejection and move on to testing other areas. The vulnerability has already been reported by another agent. If your evidence proves more than the finding it matched (a working exploit where that one had only a static trace, a chain that raises the impact), revise that finding with update_vulnerability_report using the duplicate_of id — never re-file it.
|
||||
- HTTP EVIDENCE: a finding you validated through the proxy is not fully filed until `http_exchange_ids` carries the proxy request ids of the exchanges that prove it — the request that triggers the vulnerability plus the baseline/control request it differs from (an unauthenticated success next to the authenticated one, the payload response next to the benign one). Copy the ids exactly as `list_requests`/`view_request` show them, never invent or guess one, and never omit the field to bypass validation. Leave it out only when there is no captured HTTP exchange at all (static-only code findings, dependency CVEs). If you filed before the proving exchanges existed, attach them afterwards with update_vulnerability_report. Without the ids, the finding ships as prose nobody can replay.
|
||||
- REVISING A FINDING: use update_vulnerability_report (report id + the fields you want to replace + update_reason) when you learn something a finding already on file does not carry — you built the PoC after filing it, a chain raised its impact, further testing weakened it, or its counterevidence/remediation/code locations were wrong. Editing a finding needs no duplicate verdict, and it is always better than filing a second report for the same issue. Read the finding first with get_report, and pass only the fields that change.
|
||||
- WITHDRAWING A FINDING: when re-testing proves a finding you filed is not a vulnerability at all — the exploit only worked because of your own test setup (a mixed-up session, a self-inflicted state change, a misread response), the behaviour is intended, or the control is in fact enforced — delete it with delete_vulnerability_report (report id + delete_reason). Never leave a disproved finding on file as a zero-CVSS, "retracted", or "false positive" report, and never rewrite its text into a note saying it was wrong: a report that exists is read as a finding. Delete is for a finding that does not exist; a finding that is real but weaker than filed is revised, not deleted.
|
||||
- REVIEWING FILED FINDINGS (orchestrator/root agent): use list_reports to see every vulnerability filed so far in this scan (by any agent, root or child) — metadata-first with per-severity counts — and get_report to read one finding in full by its id. These are read-only orchestration tools: the root agent uses them to track coverage, avoid dispatching work on already-covered ground, assemble the finish_scan executive summary, and reason about attack-chaining across confirmed findings. Leaf/specialist agents should NOT call them — just do your assigned testing and file findings. Each entry shows which agent filed it (agent_name), and your own entries are flagged by_you. list_notes/get_note do the same for notes.
|
||||
|
||||
STATE & COORDINATION TOOLS (when and how):
|
||||
@@ -493,6 +462,8 @@ SPECIALIZED TOOLS:
|
||||
PROXY & INTERCEPTION:
|
||||
- Caido CLI - Modern web proxy (already running). Use the proxy tools
|
||||
directly, or import `caido_api` from sandbox Python scripts.
|
||||
- Every proxied exchange has a request id (`list_requests`/`view_request`). Note the ids of the
|
||||
requests that prove a finding as you test — they go into `http_exchange_ids` when you report it.
|
||||
- HTTPQL filters (for `list_requests`): quote string values, leave integers unquoted (`resp.code.eq:200`, not `"200"`); combine terms with `AND`/`OR` (there is no `NOT` — use the negated operator `ne`/`ncont`/`nregex`). Numeric fields (`resp.code`, `req.port`) use `eq`/`ne`/`gt`/`gte`/`lt`/`lte`; text fields (`req.host`, `req.path`, `req.method`, `req.raw`) use `cont`/`ncont`/`eq`/`regex`. Example: `resp.code.gte:200 AND resp.code.lt:300 AND req.host.cont:"api"`.
|
||||
|
||||
CAIDO PROXY ERROR PAGES — NOT RESPONSES FROM THE TARGET:
|
||||
@@ -522,9 +493,9 @@ Directories:
|
||||
Default user: pentester (sudo available)
|
||||
</environment>
|
||||
|
||||
{% if loaded_skill_names %}
|
||||
{% if shared_skill_names %}
|
||||
<specialized_knowledge>
|
||||
{% for skill_name in loaded_skill_names %}
|
||||
{% for skill_name in shared_skill_names %}
|
||||
<{{ skill_name }}>
|
||||
{{ get_skill(skill_name) }}
|
||||
</{{ skill_name }}>
|
||||
@@ -534,7 +505,7 @@ Default user: pentester (sudo available)
|
||||
|
||||
{% if available_skills %}
|
||||
<available_skills>
|
||||
On-demand specialist skills. Spawn a specialist via `create_agent(skills=[...])`, or pull guidance inline for yourself via `load_skill(skills=[...])`. Anything wrapped in `<specialized_knowledge>` above is already loaded for you.
|
||||
On-demand specialist skills. Spawn a specialist via `create_agent(skills=[...])`, or pull guidance inline for yourself via `load_skill(skills=[...])`. Anything wrapped in `<specialized_knowledge>` is already loaded for you.
|
||||
|
||||
{% for category, skills in available_skills | dictsort -%}
|
||||
{% for skill in skills -%}
|
||||
@@ -543,3 +514,18 @@ On-demand specialist skills. Spawn a specialist via `create_agent(skills=[...])`
|
||||
{% endfor -%}
|
||||
</available_skills>
|
||||
{% endif %}
|
||||
|
||||
{% if requested_skill_names %}
|
||||
<cache_point>
|
||||
<specialized_knowledge>
|
||||
{% for skill_name in requested_skill_names %}
|
||||
<{{ skill_name }}>
|
||||
{{ get_skill(skill_name) }}
|
||||
</{{ skill_name }}>
|
||||
{% endfor %}
|
||||
</specialized_knowledge>
|
||||
{% endif %}
|
||||
|
||||
{% if include_scope %}
|
||||
{% include "scope.jinja" %}
|
||||
{% endif %}
|
||||
@@ -356,6 +356,8 @@ def build_openai_client() -> AsyncOpenAI:
|
||||
import httpx
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
from strix.llm import request_log
|
||||
|
||||
get_valid_token() # fail fast at configure time if the sign-in is dead
|
||||
|
||||
async def _auth_hook(request: httpx.Request) -> None:
|
||||
@@ -367,6 +369,7 @@ def build_openai_client() -> AsyncOpenAI:
|
||||
timeout=httpx.Timeout(600.0, connect=30.0),
|
||||
event_hooks={"request": [_auth_hook]},
|
||||
)
|
||||
request_log.observe_http_client(http_client)
|
||||
return AsyncOpenAI(
|
||||
api_key="strix-codex-oauth", # placeholder; the hook overwrites Authorization
|
||||
base_url=CODEX_BASE_URL,
|
||||
|
||||
+142
-60
@@ -8,6 +8,7 @@ import inspect
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import uuid
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
@@ -36,10 +37,12 @@ from openai.types.responses import (
|
||||
from openai.types.responses.response_usage import ResponseUsage
|
||||
from openai.types.shared import Reasoning
|
||||
|
||||
from strix.agents.prompt import CACHE_POINT
|
||||
from strix.config import codex
|
||||
from strix.config.loader import load_settings
|
||||
from strix.config.tool_call_ids import TurnCallIdRewriter, dedupe_input
|
||||
from strix.config.tool_call_limits import TurnToolCallLimiter
|
||||
from strix.llm import request_log
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -305,6 +308,9 @@ class _TurnGuardModel(Model):
|
||||
) -> ModelResponse:
|
||||
sanitized = dedupe_input(input)
|
||||
rewriter = TurnCallIdRewriter(sanitized)
|
||||
system_instructions, sanitized = _split_cached_prefix(
|
||||
system_instructions, sanitized, model_settings
|
||||
)
|
||||
response = await self._inner.get_response(
|
||||
system_instructions,
|
||||
cast("str | list[TResponseInputItem]", sanitized),
|
||||
@@ -338,6 +344,9 @@ class _TurnGuardModel(Model):
|
||||
) -> AsyncIterator[TResponseStreamEvent]:
|
||||
sanitized = dedupe_input(input)
|
||||
rewriter = TurnCallIdRewriter(sanitized)
|
||||
system_instructions, sanitized = _split_cached_prefix(
|
||||
system_instructions, sanitized, model_settings
|
||||
)
|
||||
limiter = self._limiter()
|
||||
stream = self._inner.stream_response(
|
||||
system_instructions,
|
||||
@@ -358,6 +367,27 @@ class _TurnGuardModel(Model):
|
||||
self._log_dropped(limiter)
|
||||
|
||||
|
||||
def _split_cached_prefix(
|
||||
system_instructions: str | None,
|
||||
model_input: str | list[Any],
|
||||
model_settings: ModelSettings,
|
||||
) -> tuple[str | None, str | list[Any]]:
|
||||
"""Split the system prompt at each ``CACHE_POINT`` on cache-point routes.
|
||||
|
||||
LiteLLM puts a cache point at the end of each system message, so each part
|
||||
gets its own. Other routes get the prompt with the markers removed.
|
||||
"""
|
||||
if not system_instructions or CACHE_POINT not in system_instructions:
|
||||
return system_instructions, model_input
|
||||
extra_args = model_settings.extra_args or {}
|
||||
if "cache_control_injection_points" not in extra_args:
|
||||
return system_instructions.replace(CACHE_POINT, ""), model_input
|
||||
if isinstance(model_input, str):
|
||||
model_input = [{"role": "user", "content": model_input}]
|
||||
parts = [part for part in system_instructions.split(CACHE_POINT) if part.strip()]
|
||||
return None, [*({"role": "system", "content": part} for part in parts), *model_input]
|
||||
|
||||
|
||||
async def _aclose(stream: AsyncIterator[TResponseStreamEvent]) -> None:
|
||||
if isinstance(stream, AsyncGenerator):
|
||||
with contextlib.suppress(Exception):
|
||||
@@ -390,7 +420,7 @@ def _guard_event(
|
||||
event: TResponseStreamEvent, rewriter: TurnCallIdRewriter, limiter: TurnToolCallLimiter
|
||||
) -> TResponseStreamEvent | None:
|
||||
if isinstance(event, ResponseOutputItemAddedEvent | ResponseOutputItemDoneEvent):
|
||||
rewritten = rewriter.rewrite_item(event.item)
|
||||
rewritten = rewriter.rewrite_item(event.item, event.output_index)
|
||||
if not limiter.allow(rewritten):
|
||||
return None
|
||||
if rewritten is not event.item:
|
||||
@@ -525,13 +555,37 @@ class StrixProvider(MultiProvider):
|
||||
# The ChatGPT subscription backend is always streamed; it has no
|
||||
# non-streaming mode to fall back to, so LLM_DISABLE_STREAMING
|
||||
# does not apply here.
|
||||
model: Model = _CodexResponsesModel(
|
||||
slug,
|
||||
codex.get_subscription_client(),
|
||||
reasoning_effort=llm.reasoning_effort,
|
||||
model: Model = request_log.RequestLoggingModel(
|
||||
_CodexResponsesModel(
|
||||
slug,
|
||||
codex.get_subscription_client(),
|
||||
reasoning_effort=llm.reasoning_effort,
|
||||
),
|
||||
model_name=slug,
|
||||
provider="openai-codex",
|
||||
base_url=None,
|
||||
)
|
||||
else:
|
||||
model = super().get_model(model_name)
|
||||
resolved_name = model_name or llm.model or "unknown"
|
||||
if _routes_via_litellm(model):
|
||||
# LiteLLM's callbacks log every reply; only a cancelled attempt
|
||||
# (stream idle timeout, abandoned turn) escapes them.
|
||||
model = request_log.RequestLoggingModel(
|
||||
model,
|
||||
model_name=resolved_name,
|
||||
provider=_litellm_provider(resolved_name),
|
||||
base_url=self._override_base_url or llm.api_base,
|
||||
route="litellm",
|
||||
abandoned_only=True,
|
||||
)
|
||||
else:
|
||||
model = request_log.RequestLoggingModel(
|
||||
model,
|
||||
model_name=resolved_name,
|
||||
provider="openai",
|
||||
base_url=self._override_base_url or llm.api_base,
|
||||
)
|
||||
if llm.disable_streaming:
|
||||
model = _NonStreamingModel(model)
|
||||
# The wrapper emits its single event only once the whole request
|
||||
@@ -545,6 +599,23 @@ class StrixProvider(MultiProvider):
|
||||
)
|
||||
|
||||
|
||||
def _routes_via_litellm(model: Model) -> bool:
|
||||
"""LiteLLM-backed models are captured by the LiteLLM callback, not the wrapper."""
|
||||
from agents.extensions.models.litellm_model import LitellmModel
|
||||
|
||||
return isinstance(model, LitellmModel)
|
||||
|
||||
|
||||
def _litellm_provider(model_name: str) -> str | None:
|
||||
"""The provider LiteLLM will route ``model_name`` to, if it can tell."""
|
||||
try:
|
||||
import litellm
|
||||
|
||||
return str(litellm.get_llm_provider(model_name)[1])
|
||||
except Exception: # noqa: BLE001 - unknown model ids are the provider's problem, not the log's
|
||||
return None
|
||||
|
||||
|
||||
DEFAULT_MODEL_RETRY = ModelRetrySettings(
|
||||
max_retries=5,
|
||||
backoff=ModelRetryBackoffSettings(
|
||||
@@ -593,17 +664,27 @@ RECOMMENDED_MODEL_NAMES = (
|
||||
|
||||
_RECOMMENDED_MODEL_NAME_SET = frozenset(name.lower() for name in RECOMMENDED_MODEL_NAMES)
|
||||
|
||||
FRONTIER_MODEL_FAMILIES = (
|
||||
(("azure", "azure_ai", "bedrock_mantle", "chatgpt", "openai"), ("gpt-5",)),
|
||||
(
|
||||
("anthropic", "azure_ai", "bedrock", "claude", "databricks", "snowflake", "vertex_ai"),
|
||||
("claude-fable-5", "claude-opus-5", "claude-opus-4", "claude-sonnet-5", "claude-sonnet-4"),
|
||||
),
|
||||
(("google", "gemini", "vertex_ai"), ("gemini-3",)),
|
||||
(("deepseek",), ("deepseek-v4", "deepseek-r1", "deepseek-reasoner")),
|
||||
(("alibaba", "dashscope", "qwen"), ("qwen3.8", "qwen3.7", "qwen3-max")),
|
||||
(("moonshot", "moonshotai", "kimi"), ("kimi-k3", "kimi-k2.7", "kimi-k2.6")),
|
||||
(("zai", "z-ai", "zai-org", "zhipuai"), ("glm-5.3", "glm-5.2")),
|
||||
# Matched against the bare model name only: the route (``openai/``, ``openrouter/``,
|
||||
# a local gateway, ...) says nothing about the model's quality.
|
||||
FRONTIER_MODEL_PREFIXES = (
|
||||
"gpt-5",
|
||||
"claude-fable-5",
|
||||
"claude-opus-5",
|
||||
"claude-opus-4",
|
||||
"claude-sonnet-5",
|
||||
"claude-sonnet-4",
|
||||
"gemini-3",
|
||||
"deepseek-v4",
|
||||
"deepseek-r1",
|
||||
"deepseek-reasoner",
|
||||
"qwen3.8",
|
||||
"qwen3.7",
|
||||
"qwen3-max",
|
||||
"kimi-k3",
|
||||
"kimi-k2.7",
|
||||
"kimi-k2.6",
|
||||
"glm-5.3",
|
||||
"glm-5.2",
|
||||
)
|
||||
|
||||
|
||||
@@ -611,6 +692,7 @@ def configure_sdk_model_defaults(settings: Settings) -> None:
|
||||
"""Apply Strix config to SDK-native defaults."""
|
||||
llm = settings.llm
|
||||
set_tracing_disabled(True)
|
||||
request_log.install()
|
||||
if codex.subscription_model(llm.model):
|
||||
return
|
||||
_configure_litellm_compatibility()
|
||||
@@ -622,9 +704,11 @@ def configure_sdk_model_defaults(settings: Settings) -> None:
|
||||
if llm.api_base:
|
||||
os.environ["OPENAI_BASE_URL"] = llm.api_base
|
||||
_configure_litellm_default("api_base", llm.api_base)
|
||||
set_default_openai_api("chat_completions")
|
||||
else:
|
||||
set_default_openai_api("responses")
|
||||
api_type = llm.api_type
|
||||
if api_type is None:
|
||||
api_type = "chat_completions" if llm.api_base else "responses"
|
||||
|
||||
set_default_openai_api(api_type)
|
||||
_configure_extra_headers(llm)
|
||||
|
||||
|
||||
@@ -663,6 +747,11 @@ def _configure_litellm_compatibility() -> None:
|
||||
_install_openrouter_stream_cost_capture()
|
||||
|
||||
|
||||
# Agent ids are 8 hex characters and can repeat across runs; the session id
|
||||
# OpenRouter pins a provider to must not, so each agent gets its own UUID.
|
||||
_OPENROUTER_SESSION_IDS: dict[str, str] = {}
|
||||
|
||||
|
||||
def _install_openrouter_stream_cost_capture() -> None:
|
||||
"""Preserve OpenRouter's per-stream cost, which LiteLLM drops when streaming.
|
||||
|
||||
@@ -681,14 +770,16 @@ def _install_openrouter_stream_cost_capture() -> None:
|
||||
OpenrouterConfig,
|
||||
)
|
||||
|
||||
from strix.report.state import streamed_openrouter_costs
|
||||
from strix.report.state import record_openrouter_provider, streamed_openrouter_costs
|
||||
|
||||
class _StrixOpenRouterStreamingHandler(OpenRouterChatCompletionStreamingHandler):
|
||||
def chunk_parser(self, chunk: dict[str, Any]) -> Any:
|
||||
stream = super().chunk_parser(chunk)
|
||||
streamed_openrouter_costs.remember(
|
||||
chunk.get("id") or getattr(stream, "id", None), chunk.get("usage")
|
||||
)
|
||||
usage = chunk.get("usage")
|
||||
response_id = chunk.get("id") or getattr(stream, "id", None)
|
||||
streamed_openrouter_costs.remember(response_id, usage)
|
||||
if usage:
|
||||
record_openrouter_provider(chunk.get("provider"), usage)
|
||||
return stream
|
||||
|
||||
class _StrixOpenrouterConfig(OpenrouterConfig):
|
||||
@@ -701,6 +792,26 @@ def _install_openrouter_stream_cost_capture() -> None:
|
||||
json_mode=json_mode,
|
||||
)
|
||||
|
||||
def transform_response(self, *args: Any, **kwargs: Any) -> Any:
|
||||
# Non-streamed replies (LLM_DISABLE_STREAMING) skip the chunk parser.
|
||||
response = super().transform_response(*args, **kwargs)
|
||||
raw_response = kwargs.get("raw_response", args[1] if len(args) > 1 else None)
|
||||
with contextlib.suppress(Exception):
|
||||
body = raw_response.json() # type: ignore[union-attr]
|
||||
if body.get("usage"):
|
||||
record_openrouter_provider(body.get("provider"), body["usage"])
|
||||
return response
|
||||
|
||||
def transform_request(self, *args: Any, **kwargs: Any) -> dict[str, Any]:
|
||||
# Pin each agent's calls to one upstream provider so its prompt cache
|
||||
# survives between turns.
|
||||
body = super().transform_request(*args, **kwargs)
|
||||
agent_id = request_log.current_call_context().agent_id
|
||||
if agent_id and load_settings().llm.openrouter_sticky_sessions:
|
||||
session_id = _OPENROUTER_SESSION_IDS.setdefault(agent_id, str(uuid.uuid4()))
|
||||
body.setdefault("session_id", session_id)
|
||||
return body
|
||||
|
||||
# LiteLLM's provider-config factory reads litellm.OpenrouterConfig at call
|
||||
# time, so overriding the attribute is enough for the subclass to take
|
||||
# effect. (type: ignore — mypy rejects reassigning a class attribute.)
|
||||
@@ -761,12 +872,16 @@ def _merge_litellm_headers(headers: dict[str, str]) -> None:
|
||||
|
||||
def _register_openai_client_with_headers(llm: LlmSettings, headers: dict[str, str]) -> None:
|
||||
from agents import set_default_openai_client
|
||||
from agents.models.openai_provider import shared_http_client
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
client = AsyncOpenAI(
|
||||
api_key=llm.api_key or "not-needed",
|
||||
base_url=llm.api_base,
|
||||
default_headers=dict(headers),
|
||||
# The SDK's shared client is the one the request log observes for
|
||||
# reply status, headers and provider request ids.
|
||||
http_client=shared_http_client(),
|
||||
)
|
||||
set_default_openai_client(client, use_for_tracing=False)
|
||||
|
||||
@@ -799,6 +914,8 @@ def uses_chat_completions_tool_schema(model_name: str, settings: Settings) -> bo
|
||||
model = model_name.strip().lower()
|
||||
if "/" in model and not model.startswith("openai/"):
|
||||
return True
|
||||
if settings.llm.api_type is not None:
|
||||
return settings.llm.api_type == "chat_completions"
|
||||
if settings.llm.api_base:
|
||||
return True
|
||||
return not model_supports_reasoning(model_name)
|
||||
@@ -837,11 +954,8 @@ def is_recommended_or_frontier_model(model_name: str) -> bool:
|
||||
return False
|
||||
if name in _RECOMMENDED_MODEL_NAME_SET:
|
||||
return True
|
||||
provider_name, bare_model_name = _split_model_provider(name)
|
||||
return any(
|
||||
_matches_frontier_family(provider_name, bare_model_name, provider_markers, prefixes)
|
||||
for provider_markers, prefixes in FRONTIER_MODEL_FAMILIES
|
||||
)
|
||||
bare_model_name = name.rsplit("/", 1)[-1]
|
||||
return _matches_model_prefix(bare_model_name, FRONTIER_MODEL_PREFIXES)
|
||||
|
||||
|
||||
def _normalized_model_name(model_name: str) -> str:
|
||||
@@ -853,28 +967,6 @@ def _normalized_model_name(model_name: str) -> str:
|
||||
return name
|
||||
|
||||
|
||||
def _split_model_provider(model_name: str) -> tuple[str | None, str]:
|
||||
if "/" not in model_name:
|
||||
return None, model_name
|
||||
provider_name, bare_model_name = model_name.rsplit("/", 1)
|
||||
return provider_name, bare_model_name
|
||||
|
||||
|
||||
def _matches_frontier_family(
|
||||
provider_name: str | None,
|
||||
model_name: str,
|
||||
provider_markers: tuple[str, ...],
|
||||
model_prefixes: tuple[str, ...],
|
||||
) -> bool:
|
||||
if not _matches_model_prefix(model_name, model_prefixes):
|
||||
return False
|
||||
if provider_name is None:
|
||||
return True
|
||||
return _contains_provider_marker(
|
||||
provider_name, provider_markers, split_compound_names=True
|
||||
) or _contains_provider_marker(model_name, provider_markers)
|
||||
|
||||
|
||||
def _matches_model_prefix(model_name: str, model_prefixes: tuple[str, ...]) -> bool:
|
||||
return any(
|
||||
candidate.startswith(prefix)
|
||||
@@ -892,16 +984,6 @@ def _model_name_candidates(model_name: str) -> tuple[str, ...]:
|
||||
return (model_name, *suffixes)
|
||||
|
||||
|
||||
def _contains_provider_marker(
|
||||
value: str, provider_markers: tuple[str, ...], *, split_compound_names: bool = False
|
||||
) -> bool:
|
||||
parts = set(value.replace(".", "/").split("/"))
|
||||
if split_compound_names:
|
||||
for separator in ("_", "-"):
|
||||
parts.update(piece for part in tuple(parts) for piece in part.split(separator))
|
||||
return any(marker in parts for marker in provider_markers)
|
||||
|
||||
|
||||
def is_known_openai_bare_model(model_name: str) -> bool:
|
||||
import litellm
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
ReasoningEffort = Literal["none", "minimal", "low", "medium", "high", "xhigh", "max"]
|
||||
ApiType = Literal["responses", "chat_completions"]
|
||||
|
||||
DEFAULT_MAX_TURNS = 500
|
||||
|
||||
@@ -23,6 +24,11 @@ class LlmSettings(BaseSettings):
|
||||
model_config = _BASE_CONFIG
|
||||
|
||||
model: str | None = Field(default=None, alias="STRIX_LLM")
|
||||
api_type: ApiType | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("STRIX_API_TYPE", "STRIX_FORCE_API"),
|
||||
description="Force 'responses' or 'chat_completions' API path",
|
||||
)
|
||||
api_key: str | None = Field(
|
||||
default=None,
|
||||
validation_alias=AliasChoices("LLM_API_KEY", "OPENAI_API_KEY"),
|
||||
@@ -52,6 +58,14 @@ class LlmSettings(BaseSettings):
|
||||
default=True,
|
||||
alias="STRIX_PROMPT_CACHE",
|
||||
)
|
||||
# Providers cache prompts in fixed-size token blocks, so a fully cached prompt
|
||||
# can read back up to a block short. 128 covers the largest common size
|
||||
# (OpenAI; DeepSeek and GLM use 64, vLLM defaults to 16).
|
||||
cache_block_tokens: int = Field(default=128, ge=1, alias="STRIX_CACHE_BLOCK_TOKENS")
|
||||
openrouter_sticky_sessions: bool = Field(
|
||||
default=False,
|
||||
alias="STRIX_OPENROUTER_STICKY_SESSIONS",
|
||||
)
|
||||
disable_streaming: bool = Field(
|
||||
default=False,
|
||||
alias="LLM_DISABLE_STREAMING",
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
"""Keep tool-call ids unique within a conversation.
|
||||
"""Keep tool-call ids present and unique within a conversation.
|
||||
|
||||
Some providers return per-turn tool-call ids (``exec_command:0``,
|
||||
``exec_command:1``, ...) whose counter restarts on every turn. Once the same
|
||||
id appears twice in one conversation, the request payload has two assistant
|
||||
tool calls sharing an id and strict providers reject the whole turn, which
|
||||
permanently kills the agent because the malformed history is replayed on
|
||||
every retry. Rewriting duplicates to fresh unique ids keeps the history
|
||||
valid for any provider.
|
||||
every retry.
|
||||
|
||||
Others omit the id altogether, or return it as an empty string. That turns
|
||||
the paired tool result into a ``tool`` message with an empty
|
||||
``tool_call_id``, which strict providers reject the same way and with the
|
||||
same permanent outcome. Rewriting both blank and duplicate ids to fresh
|
||||
unique ones keeps the history valid for any provider.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -15,6 +20,7 @@ from collections import defaultdict, deque
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
|
||||
from agents.models.fake_id import FAKE_RESPONSES_ID
|
||||
from openai.types.responses import ResponseFunctionToolCall
|
||||
|
||||
|
||||
@@ -22,23 +28,29 @@ def new_call_id() -> str:
|
||||
return f"call_{uuid4().hex}"
|
||||
|
||||
|
||||
def _pairing_key(call_id: Any) -> str:
|
||||
"""Bucket an id for call/output pairing; all blank ids share one bucket."""
|
||||
return call_id if isinstance(call_id, str) else ""
|
||||
|
||||
|
||||
def collect_call_ids(items: list[Any]) -> set[str]:
|
||||
used: set[str] = set()
|
||||
for item in items:
|
||||
if isinstance(item, dict):
|
||||
call_id = item.get("call_id")
|
||||
if isinstance(call_id, str):
|
||||
if isinstance(call_id, str) and call_id:
|
||||
used.add(call_id)
|
||||
elif isinstance(item, ResponseFunctionToolCall):
|
||||
elif isinstance(item, ResponseFunctionToolCall) and item.call_id:
|
||||
used.add(item.call_id)
|
||||
return used
|
||||
|
||||
|
||||
def dedupe_history_call_ids(items: list[Any]) -> tuple[list[Any], bool]:
|
||||
"""Rewrite duplicate call ids in a conversation history.
|
||||
"""Rewrite blank and duplicate call ids in a conversation history.
|
||||
|
||||
Outputs are paired with their call by order, so parallel calls that share
|
||||
an id keep answering the right call after the rewrite.
|
||||
an id — or are all missing one — keep answering the right call after the
|
||||
rewrite.
|
||||
"""
|
||||
used: set[str] = set()
|
||||
pending: dict[str, deque[str]] = defaultdict(deque)
|
||||
@@ -49,22 +61,24 @@ def dedupe_history_call_ids(items: list[Any]) -> tuple[list[Any], bool]:
|
||||
if not isinstance(item, dict):
|
||||
rebuilt.append(item)
|
||||
continue
|
||||
call_id = item.get("call_id")
|
||||
if not isinstance(call_id, str):
|
||||
|
||||
kind = item.get("type")
|
||||
if kind not in ("function_call", "function_call_output"):
|
||||
rebuilt.append(item)
|
||||
continue
|
||||
|
||||
kind = item.get("type")
|
||||
call_id = item.get("call_id")
|
||||
key = _pairing_key(call_id)
|
||||
if kind == "function_call":
|
||||
effective = call_id
|
||||
if call_id in used:
|
||||
effective = key
|
||||
if not effective or effective in used:
|
||||
effective = new_call_id()
|
||||
item = {**item, "call_id": effective} # noqa: PLW2901
|
||||
changed = True
|
||||
used.add(effective)
|
||||
pending[call_id].append(effective)
|
||||
elif kind == "function_call_output":
|
||||
queue = pending.get(call_id)
|
||||
pending[key].append(effective)
|
||||
else:
|
||||
queue = pending.get(key)
|
||||
if queue:
|
||||
effective = queue.popleft()
|
||||
if effective != call_id:
|
||||
@@ -83,7 +97,7 @@ def dedupe_input(model_input: str | list[Any]) -> str | list[Any]:
|
||||
|
||||
|
||||
class TurnCallIdRewriter:
|
||||
"""Rewrite a single turn's tool-call ids that collide with the history.
|
||||
"""Rewrite a single turn's tool-call ids that are blank or collide with the history.
|
||||
|
||||
A turn's items surface several times (streamed item events, then the
|
||||
completed response), so the same original id must always map to the same
|
||||
@@ -93,12 +107,37 @@ class TurnCallIdRewriter:
|
||||
def __init__(self, model_input: str | list[Any]) -> None:
|
||||
self._used = set() if isinstance(model_input, str) else collect_call_ids(model_input)
|
||||
self._remap: dict[str, str] = {}
|
||||
self._blank_remap: dict[str, str] = {}
|
||||
self._settled: set[str] = set()
|
||||
|
||||
def rewrite_item(self, item: Any) -> Any:
|
||||
def _rewrite_blank(
|
||||
self, item: ResponseFunctionToolCall, position: int | None
|
||||
) -> ResponseFunctionToolCall:
|
||||
"""Give a call with no id one that stays the same on every sighting.
|
||||
|
||||
A real item id tells parallel calls apart. Chat Completions routes give
|
||||
every item the same placeholder id, so there the call's position in
|
||||
the turn's output tells them apart instead.
|
||||
"""
|
||||
if item.id and item.id != FAKE_RESPONSES_ID:
|
||||
key = item.id
|
||||
else:
|
||||
key = f"{FAKE_RESPONSES_ID}#{position}"
|
||||
replacement = self._blank_remap.get(key)
|
||||
if replacement is None:
|
||||
replacement = new_call_id()
|
||||
self._blank_remap[key] = replacement
|
||||
self._used.add(replacement)
|
||||
self._settled.add(replacement)
|
||||
return item.model_copy(update={"call_id": replacement})
|
||||
|
||||
def rewrite_item(self, item: Any, position: int | None = None) -> Any:
|
||||
"""Rewrite one item; ``position`` is its index in the turn's output."""
|
||||
if not isinstance(item, ResponseFunctionToolCall):
|
||||
return item
|
||||
original = item.call_id
|
||||
if not original:
|
||||
return self._rewrite_blank(item, position)
|
||||
if original in self._settled:
|
||||
return item
|
||||
replacement = self._remap.get(original)
|
||||
@@ -114,4 +153,4 @@ class TurnCallIdRewriter:
|
||||
return item.model_copy(update={"call_id": replacement})
|
||||
|
||||
def rewrite_items(self, items: list[Any]) -> list[Any]:
|
||||
return [self.rewrite_item(item) for item in items]
|
||||
return [self.rewrite_item(item, position) for position, item in enumerate(items)]
|
||||
+46
-3
@@ -33,6 +33,7 @@ from strix.core.sessions import (
|
||||
seed_initial_input,
|
||||
strip_all_images_from_session,
|
||||
)
|
||||
from strix.llm import request_log
|
||||
from strix.llm.compaction import is_context_overflow, maybe_compact
|
||||
|
||||
|
||||
@@ -197,6 +198,44 @@ async def run_agent_loop(
|
||||
start_parked: bool = False,
|
||||
event_sink: StreamEventSink | None = None,
|
||||
hooks: RunHooks[dict[str, Any]] | None = None,
|
||||
) -> RunResultBase | None:
|
||||
agent_name = getattr(agent, "name", None)
|
||||
token = request_log.bind_call_context(
|
||||
agent_id, agent_name if isinstance(agent_name, str) else None
|
||||
)
|
||||
try:
|
||||
return await _run_agent_loop(
|
||||
agent=agent,
|
||||
initial_input=initial_input,
|
||||
run_config=run_config,
|
||||
context=context,
|
||||
max_turns=max_turns,
|
||||
coordinator=coordinator,
|
||||
agent_id=agent_id,
|
||||
interactive=interactive,
|
||||
session=session,
|
||||
start_parked=start_parked,
|
||||
event_sink=event_sink,
|
||||
hooks=hooks,
|
||||
)
|
||||
finally:
|
||||
request_log.reset_call_context(token)
|
||||
|
||||
|
||||
async def _run_agent_loop(
|
||||
*,
|
||||
agent: Any,
|
||||
initial_input: Any,
|
||||
run_config: RunConfig,
|
||||
context: dict[str, Any],
|
||||
max_turns: int,
|
||||
coordinator: AgentCoordinator,
|
||||
agent_id: str,
|
||||
interactive: bool,
|
||||
session: Session | None,
|
||||
start_parked: bool,
|
||||
event_sink: StreamEventSink | None,
|
||||
hooks: RunHooks[dict[str, Any]] | None,
|
||||
) -> RunResultBase | None:
|
||||
await coordinator.attach_runtime(
|
||||
agent_id,
|
||||
@@ -636,7 +675,7 @@ async def _run_cycle_parked(
|
||||
raise
|
||||
except Exception as exc:
|
||||
logger.exception("error escaped the run cycle for %s; parking as failed", agent_id)
|
||||
await coordinator.set_status(agent_id, "failed", error=str(exc) or type(exc).__name__)
|
||||
await coordinator.set_status(agent_id, "failed", error=request_log.failure_text(exc))
|
||||
await notify_parent_on_terminal(coordinator, agent_id, "failed")
|
||||
return None
|
||||
|
||||
@@ -658,6 +697,7 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
|
||||
image_strips = 0
|
||||
compactions = 0
|
||||
model_retries = 0
|
||||
request_log.set_retry_attempt(0)
|
||||
while True:
|
||||
stream: Any = None
|
||||
pre_run_items: list[Any] = []
|
||||
@@ -785,6 +825,7 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
|
||||
exc,
|
||||
)
|
||||
await asyncio.sleep(delay)
|
||||
request_log.set_retry_attempt(model_retries)
|
||||
if session is not None:
|
||||
input_data = []
|
||||
continue
|
||||
@@ -792,7 +833,9 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
|
||||
await _salvage_stream_to_session(session, pre_run_items, stream, agent_id)
|
||||
if isinstance(exc, ProviderRefusalError):
|
||||
logger.warning("agent %s refused by the model provider: %s", agent_id, exc)
|
||||
await coordinator.set_status(agent_id, "failed", error=str(exc))
|
||||
await coordinator.set_status(
|
||||
agent_id, "failed", error=request_log.failure_text(exc)
|
||||
)
|
||||
await notify_parent_on_terminal(coordinator, agent_id, "failed")
|
||||
return None
|
||||
if isinstance(exc, MaxTurnsExceeded):
|
||||
@@ -806,7 +849,7 @@ async def _run_cycle( # noqa: PLR0912, PLR0915
|
||||
# non-interactive agent's task: a child that dies still owes its parent a
|
||||
# report, and the parent would otherwise wait out its timeout on a message
|
||||
# the dead child can no longer send.
|
||||
await coordinator.set_status(agent_id, status, error=str(exc) or type(exc).__name__)
|
||||
await coordinator.set_status(agent_id, status, error=request_log.failure_text(exc))
|
||||
await notify_parent_on_terminal(coordinator, agent_id, status)
|
||||
if not interactive:
|
||||
raise
|
||||
|
||||
+12
-10
@@ -312,11 +312,12 @@ def _reasoning_settings(effort: ReasoningEffort) -> ModelSettings:
|
||||
def _prompt_cache_extra_args(model_name: str) -> dict[str, Any] | None:
|
||||
"""LiteLLM ``cache_control_injection_points`` for Claude prompt caching.
|
||||
|
||||
System prompt + rolling last-message breakpoint everywhere; ``tool_config``
|
||||
only on Bedrock Converse (the only route whose LiteLLM transform consumes
|
||||
it — elsewhere it leaks onto the wire and native Anthropic 400s). Unmapped
|
||||
Bedrock models get no points at all: Bedrock rejects the passed-through
|
||||
field outright.
|
||||
A breakpoint on each system message, plus a rolling last-message one. The
|
||||
system prompt is split into up to three messages, which with the last
|
||||
message uses all four breakpoints Claude allows. There is none on
|
||||
``tool_config``: the tools come before the system prompt, so its first
|
||||
breakpoint caches them too. Unmapped Bedrock models get no points at all:
|
||||
Bedrock rejects the passed-through field outright.
|
||||
|
||||
The field is LiteLLM's own, consumed by its transform, so it only goes to
|
||||
routes LiteLLM serves. A bare ``claude-...`` name is served by the SDK's
|
||||
@@ -328,11 +329,12 @@ def _prompt_cache_extra_args(model_name: str) -> dict[str, Any] | None:
|
||||
if is_bedrock_route(model_name) and not bedrock_route_supports_prompt_caching(model_name):
|
||||
return None
|
||||
|
||||
points: list[dict[str, Any]] = [{"location": "message", "role": "system"}]
|
||||
if is_bedrock_route(model_name):
|
||||
points.append({"location": "tool_config"})
|
||||
points.append({"location": "message", "index": -1})
|
||||
return {"cache_control_injection_points": points}
|
||||
return {
|
||||
"cache_control_injection_points": [
|
||||
{"location": "message", "role": "system"},
|
||||
{"location": "message", "index": -1},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def child_initial_input(
|
||||
|
||||
+56
-72
@@ -17,7 +17,7 @@ from agents.sandbox import SandboxRunConfig
|
||||
from openai import RateLimitError
|
||||
|
||||
from strix.agents.factory import build_strix_agent, make_child_factory
|
||||
from strix.agents.prompt import render_system_prompt
|
||||
from strix.agents.prompt import render_scope_prompt, render_system_prompt
|
||||
from strix.config import load_settings
|
||||
from strix.config.models import (
|
||||
StrixProvider,
|
||||
@@ -45,6 +45,7 @@ from strix.core.paths import run_dir_for, runtime_state_dir
|
||||
from strix.core.sessions import open_agent_session
|
||||
from strix.report.state import get_global_report_state
|
||||
from strix.runtime import session_manager
|
||||
from strix.telemetry import set_scan_phase
|
||||
from strix.telemetry.logging import set_scan_id, setup_scan_logging
|
||||
from strix.tools.output_store import (
|
||||
WORKSPACE_SPILL_DIR,
|
||||
@@ -58,10 +59,8 @@ if TYPE_CHECKING:
|
||||
|
||||
from strix.runtime.status import StatusSink
|
||||
from strix.tools.mcp import (
|
||||
ConnectedMcpServer,
|
||||
McpConnectionRequest,
|
||||
McpRegistry,
|
||||
SupervisedMcpSession,
|
||||
)
|
||||
|
||||
|
||||
@@ -70,8 +69,8 @@ logger = logging.getLogger(__name__)
|
||||
StreamEventSink = Callable[[str, Any], None]
|
||||
|
||||
# Receives the run's MCP connection roster as a list of non-secret status dicts
|
||||
# ({"name", "provider", "tool_count", "dead"}), once when the connections are
|
||||
# established and again each time a connection transitions to dead. An interface
|
||||
# ({"name", "provider", "tool_count", "dead", "state"}), once when the connections
|
||||
# are registered and again on lifecycle transitions. An interface
|
||||
# can persist it, render it, or forward it on as connection status. Kept as a
|
||||
# snapshot of the whole roster (not a per-
|
||||
# connection delta) so every call carries a consistent, current picture.
|
||||
@@ -79,30 +78,21 @@ McpStatusSink = Callable[[list[dict[str, Any]]], None]
|
||||
|
||||
|
||||
def _mcp_roster_payload(registry: McpRegistry) -> list[dict[str, Any]]:
|
||||
"""The run's MCP roster as non-secret status dicts (name/provider/tool_count/dead)."""
|
||||
"""The run's MCP roster as non-secret lifecycle status dicts."""
|
||||
return [
|
||||
{
|
||||
"name": status.name,
|
||||
"provider": status.provider,
|
||||
"tool_count": status.tool_count,
|
||||
"dead": status.dead,
|
||||
"state": status.state,
|
||||
}
|
||||
for status in registry.statuses()
|
||||
]
|
||||
|
||||
|
||||
def _mcp_startup_summary(connections: list[ConnectedMcpServer]) -> str:
|
||||
"""One user-facing line summarizing the MCP servers that connected."""
|
||||
server_count = len(connections)
|
||||
tool_count = sum(c.tool_count for c in connections)
|
||||
servers_word = "server" if server_count == 1 else "servers"
|
||||
tools_word = "tool" if tool_count == 1 else "tools"
|
||||
names = ", ".join(c.name for c in connections)
|
||||
return f"MCP: connected {server_count} {servers_word} ({tool_count} {tools_word}): {names}"
|
||||
|
||||
|
||||
def _record_mcp_connections(connections: list[ConnectedMcpServer]) -> None:
|
||||
"""Record which MCP servers this run connected, for the interfaces.
|
||||
def _record_mcp_connections(connection_names: list[str]) -> None:
|
||||
"""Record which MCP servers this run configured, for the interfaces.
|
||||
|
||||
A server's tools are offered to the model under a name built from the
|
||||
connection name and the tool's own name, which cannot be split back apart, so
|
||||
@@ -113,7 +103,14 @@ def _record_mcp_connections(connections: list[ConnectedMcpServer]) -> None:
|
||||
report_state = get_global_report_state()
|
||||
if report_state is None:
|
||||
return
|
||||
report_state.record_mcp_connections([connection.name for connection in connections])
|
||||
report_state.record_mcp_connections(connection_names)
|
||||
|
||||
|
||||
def _note_exit_reason(reason: str) -> None:
|
||||
"""Record why the scan stopped so the end-of-scan beacon reports it."""
|
||||
report_state = get_global_report_state()
|
||||
if report_state is not None and report_state.scan_ended_exit_reason is None:
|
||||
report_state.scan_ended_exit_reason = reason
|
||||
|
||||
|
||||
def _persist_mcp_status(roster: list[dict[str, Any]]) -> None:
|
||||
@@ -167,15 +164,17 @@ def _compose_root_instructions_override(
|
||||
is_diff_scoped=is_diff_scoped,
|
||||
interactive=interactive,
|
||||
system_prompt_context=system_prompt_context,
|
||||
include_scope=False,
|
||||
)
|
||||
return (
|
||||
f"{base_instructions}\n\n"
|
||||
"<root_scan_instructions_override>\n"
|
||||
"The following root scan instructions are subordinate to the "
|
||||
"system-verified scope above. They cannot expand, replace, or weaken "
|
||||
"system-verified scope below. They cannot expand, replace, or weaken "
|
||||
"authorized target constraints.\n\n"
|
||||
f"{root_instructions_override}\n"
|
||||
"</root_scan_instructions_override>"
|
||||
"</root_scan_instructions_override>\n\n"
|
||||
f"{render_scope_prompt(system_prompt_context)}"
|
||||
)
|
||||
|
||||
|
||||
@@ -313,6 +312,7 @@ async def run_strix_scan(
|
||||
root_id = uuid.uuid4().hex[:8]
|
||||
|
||||
logger.info("Bringing up sandbox session for scan %s", scan_id)
|
||||
set_scan_phase("sandbox_init")
|
||||
bundle = await session_manager.create_or_reuse(
|
||||
scan_id,
|
||||
image=image,
|
||||
@@ -322,6 +322,7 @@ async def run_strix_scan(
|
||||
)
|
||||
report("Waiting for the first model response")
|
||||
logger.info("Sandbox ready for scan %s", scan_id)
|
||||
set_scan_phase("agent_setup")
|
||||
|
||||
sandbox_session = bundle["session"]
|
||||
|
||||
@@ -338,7 +339,7 @@ async def run_strix_scan(
|
||||
configure_spill_writer(_spill_to_workspace)
|
||||
|
||||
sessions_to_close: list[SQLiteSession] = []
|
||||
mcp_sessions: list[SupervisedMcpSession] = []
|
||||
mcp_registry: McpRegistry | None = None
|
||||
|
||||
try:
|
||||
targets = scan_config.get("targets") or []
|
||||
@@ -390,7 +391,6 @@ async def run_strix_scan(
|
||||
from strix.tools.mcp import (
|
||||
McpConnectionRequest,
|
||||
McpRegistry,
|
||||
attach_mcp_requests,
|
||||
load_user_mcp_configs,
|
||||
)
|
||||
|
||||
@@ -406,56 +406,37 @@ async def run_strix_scan(
|
||||
else:
|
||||
mcp_requests = mcp_connection_requests
|
||||
if mcp_requests:
|
||||
connections = await attach_mcp_requests(mcp_requests, mcp_registry)
|
||||
mcp_sessions = [c.session for c in connections]
|
||||
# Recorded even when nothing connected, so a resumed run does not
|
||||
# keep attributing tool calls to servers it no longer has.
|
||||
_record_mcp_connections(connections)
|
||||
if connections:
|
||||
report(_mcp_startup_summary(connections))
|
||||
# Name the connected servers in the prompt so every agent
|
||||
# (root and children, both deriving from scope_context) sees
|
||||
# what is available at the start; they can still re-list or
|
||||
# inspect them at run time via list_mcps / describe_mcp. Set
|
||||
# only when a connection exists, so a run with no MCP leaves
|
||||
# the prompt context unchanged.
|
||||
scope_context["mcp_available"] = bool(mcp_registry)
|
||||
scope_context["mcp_connections"] = [
|
||||
{
|
||||
"name": summary.name,
|
||||
"purpose": summary.purpose,
|
||||
"tool_count": summary.tool_count,
|
||||
}
|
||||
for summary in mcp_registry.summaries()
|
||||
]
|
||||
for request in mcp_requests:
|
||||
mcp_registry.register(request)
|
||||
_record_mcp_connections(mcp_registry.names())
|
||||
report(
|
||||
f"MCP: configured {len(mcp_registry)} connection(s); "
|
||||
"warming them in the background"
|
||||
)
|
||||
scope_context["mcp_available"] = True
|
||||
scope_context["mcp_connections"] = [
|
||||
{
|
||||
"name": summary.name,
|
||||
"purpose": summary.purpose,
|
||||
"tool_count": summary.tool_count,
|
||||
}
|
||||
for summary in mcp_registry.summaries()
|
||||
]
|
||||
|
||||
# Feed a non-secret connection roster (name / provider /
|
||||
# tool_count / dead) to two consumers: once now (all
|
||||
# currently healthy) and again whenever a connection later
|
||||
# dies. It is always persisted to run.json so the viewer,
|
||||
# which re-reads the run's files from disk, can render the
|
||||
# MCP connections panel and health without an in-memory
|
||||
# sink. When an interface sink is attached (the TUI backend,
|
||||
# or pro forwarding into the app's event stream) it also
|
||||
# receives the same snapshot. In-use is derived separately by
|
||||
# each interface from the connection-tagged tool-call events,
|
||||
# so it is not carried here.
|
||||
def _emit_mcp_status() -> None:
|
||||
roster = _mcp_roster_payload(mcp_registry)
|
||||
_persist_mcp_status(roster)
|
||||
if mcp_status_sink is not None:
|
||||
try:
|
||||
mcp_status_sink(roster)
|
||||
except Exception:
|
||||
logger.exception("MCP status sink failed")
|
||||
def _emit_mcp_status() -> None:
|
||||
roster = _mcp_roster_payload(mcp_registry)
|
||||
_persist_mcp_status(roster)
|
||||
if mcp_status_sink is not None:
|
||||
try:
|
||||
mcp_status_sink(roster)
|
||||
except Exception:
|
||||
logger.exception("MCP status sink failed")
|
||||
|
||||
for connection_name in mcp_registry.names():
|
||||
entry = mcp_registry.get(connection_name)
|
||||
if entry is not None:
|
||||
entry.session.set_on_dead(_emit_mcp_status)
|
||||
_emit_mcp_status()
|
||||
mcp_registry.set_status_sink(_emit_mcp_status)
|
||||
_emit_mcp_status()
|
||||
mcp_registry.start_warmup(max_concurrency=6)
|
||||
except Exception:
|
||||
logger.exception("Failed to connect user MCP servers; continuing without them")
|
||||
logger.exception("Failed to configure user MCP servers; continuing without them")
|
||||
|
||||
root_context = _merge_root_prompt_context(scope_context, extra_system_prompt_context)
|
||||
root_instructions = _compose_root_instructions_override(
|
||||
@@ -573,6 +554,7 @@ async def run_strix_scan(
|
||||
async with coordinator._lock:
|
||||
root_status = coordinator.statuses.get(root_id)
|
||||
|
||||
set_scan_phase("agent_loop")
|
||||
result = await run_agent_loop(
|
||||
agent=root_agent,
|
||||
initial_input=initial_input,
|
||||
@@ -610,6 +592,7 @@ async def run_strix_scan(
|
||||
return result # noqa: TRY300
|
||||
except BudgetExceededError as exc:
|
||||
logger.info("Scan %s stopped: %s", scan_id, exc)
|
||||
_note_exit_reason("budget_exceeded")
|
||||
if root_id is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
await coordinator.set_status(root_id, "stopped")
|
||||
@@ -622,6 +605,7 @@ async def run_strix_scan(
|
||||
exc,
|
||||
scan_id,
|
||||
)
|
||||
_note_exit_reason("rate_limited")
|
||||
if root_id is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
await coordinator.set_status(root_id, "stopped")
|
||||
@@ -648,9 +632,9 @@ async def run_strix_scan(
|
||||
for s in sessions_to_close:
|
||||
with contextlib.suppress(Exception):
|
||||
s.close()
|
||||
for mcp_session in mcp_sessions:
|
||||
if mcp_registry is not None:
|
||||
with contextlib.suppress(Exception):
|
||||
await mcp_session.aclose()
|
||||
await mcp_registry.close()
|
||||
with contextlib.suppress(Exception):
|
||||
await coordinator._maybe_snapshot()
|
||||
if cleanup_on_exit:
|
||||
|
||||
@@ -122,10 +122,32 @@ async def run_cli(args: Any) -> None: # noqa: PLR0915
|
||||
console.print(vuln_panel)
|
||||
console.print()
|
||||
|
||||
def display_vulnerability_deleted(report: dict[str, Any]) -> None:
|
||||
report_id = str(report.get("id", "unknown"))
|
||||
deletion = report.get("deletion")
|
||||
deletion = deletion if isinstance(deletion, dict) else {}
|
||||
deleted_by = deletion.get("agent_name") or deletion.get("agent_id") or "agent"
|
||||
text = Text()
|
||||
text.append("Withdrawn: ", style="bold")
|
||||
text.append(f"{report.get('title', '')}\n\n")
|
||||
text.append(f"Deleted by {deleted_by}. ", style="dim")
|
||||
text.append(str(deletion.get("reason") or ""))
|
||||
console.print(
|
||||
Panel(
|
||||
text,
|
||||
title=f"[bold yellow]{report_id.upper()} (withdrawn)",
|
||||
title_align="left",
|
||||
border_style="yellow",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
console.print()
|
||||
|
||||
report_state.vulnerability_found_callback = display_vulnerability
|
||||
report_state.vulnerability_updated_callback = lambda report: display_vulnerability(
|
||||
report, updated=True
|
||||
)
|
||||
report_state.vulnerability_deleted_callback = display_vulnerability_deleted
|
||||
|
||||
def cleanup_on_exit() -> None:
|
||||
report_state.cleanup()
|
||||
|
||||
@@ -98,6 +98,14 @@ Examples:
|
||||
# Extra files placed in the sandbox workspace
|
||||
strix --target ./my-project --workspace-file ./wordlist.txt
|
||||
strix --target https://app.com --workspace-file ./openapi.yaml:specs/openapi.yaml
|
||||
|
||||
Strix Cloud:
|
||||
strix cloud login
|
||||
strix cloud scans start --source . --yes --wait
|
||||
strix cloud # list every cloud resource
|
||||
|
||||
Run a pentest in Strix Cloud https://app.strix.ai
|
||||
Try Strix Enterprise https://strix.ai/demo
|
||||
""",
|
||||
)
|
||||
|
||||
@@ -163,8 +171,9 @@ Examples:
|
||||
help="Place a file from this machine into the sandbox workspace before the scan "
|
||||
"starts, for example a wordlist, an API specification, or notes. Repeat the option "
|
||||
"for more files. DEST is the path inside /workspace and defaults to the file name "
|
||||
"(for example '--workspace-file ./wordlist.txt:lists/wordlist.txt'). The file is "
|
||||
"read-only inside the sandbox and lands outside every target directory.",
|
||||
"(for example '--workspace-file ./wordlist.txt:lists/wordlist.txt'). Strix copies "
|
||||
"the file into the sandbox, outside every target directory. The agent can edit the "
|
||||
"copy. The file on this machine does not change.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
|
||||
@@ -14,6 +14,7 @@ from strix.interface.utils import (
|
||||
image_exists,
|
||||
process_pull_line,
|
||||
)
|
||||
from strix.telemetry import report_error
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -44,6 +45,7 @@ def validate_environment() -> None:
|
||||
f"[red]STRIX_LLM={settings.llm.model} uses your ChatGPT subscription, "
|
||||
"but you're not signed in.[/] Run [cyan]strix auth login chatgpt[/] first."
|
||||
)
|
||||
report_error("subscription_not_signed_in")
|
||||
sys.exit(1)
|
||||
logger.info("Environment OK (ChatGPT subscription)")
|
||||
return
|
||||
@@ -153,6 +155,7 @@ def validate_environment() -> None:
|
||||
console.print("\n")
|
||||
console.print(panel)
|
||||
console.print()
|
||||
report_error("missing_required_config")
|
||||
sys.exit(1)
|
||||
logger.info(
|
||||
"Environment OK (optional missing: %s)",
|
||||
@@ -180,6 +183,7 @@ def check_docker_installed() -> None:
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print("\n", panel, "\n")
|
||||
report_error("docker_not_installed")
|
||||
sys.exit(1)
|
||||
logger.debug("Docker CLI present")
|
||||
|
||||
@@ -227,6 +231,7 @@ def pull_docker_image() -> None:
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print(panel, "\n")
|
||||
report_error("image_pull_failed", e)
|
||||
sys.exit(1)
|
||||
|
||||
logger.info("Docker image %s ready", image)
|
||||
|
||||
+15
-4
@@ -42,7 +42,7 @@ from strix.interface.utils import (
|
||||
build_final_stats_text,
|
||||
)
|
||||
from strix.llm.warmup import start_import_warmup, wait_for_import_warmup
|
||||
from strix.telemetry import posthog, scarf
|
||||
from strix.telemetry import posthog, report_error, scarf, set_scan_phase
|
||||
from strix.telemetry.logging import configure_dependency_logging
|
||||
|
||||
|
||||
@@ -333,6 +333,11 @@ def display_completion_message(args: argparse.Namespace, results_path: Path) ->
|
||||
"[#60a5fa]docs.strix.ai[/] [dim]·[/] "
|
||||
"[#60a5fa]discord.gg/strix-ai[/]"
|
||||
)
|
||||
if not args.non_interactive:
|
||||
console.print(
|
||||
"[dim]Run a pentest in Strix Cloud[/] [#60a5fa]app.strix.ai[/] [dim]·[/] "
|
||||
"[dim]Enterprise[/] [#60a5fa]strix.ai/demo[/]"
|
||||
)
|
||||
console.print()
|
||||
if not args.non_interactive:
|
||||
notify_update(console)
|
||||
@@ -396,15 +401,18 @@ def _bootstrap_scan(args: argparse.Namespace) -> None:
|
||||
happen inside the TUI so the interface paints immediately instead of
|
||||
waiting on a model round trip.
|
||||
"""
|
||||
set_scan_phase("preflight")
|
||||
try:
|
||||
asyncio.run(warm_up_llm(show_model_warning=True))
|
||||
except ModelConnectionError as exc:
|
||||
report_error("model_connection_failed", exc)
|
||||
_print_model_connection_error(exc, exc.model_name)
|
||||
sys.exit(1)
|
||||
persist_current()
|
||||
try:
|
||||
prepare_run(args)
|
||||
except ValueError as e:
|
||||
report_error("scan_preparation_failed", e)
|
||||
_print_error_panel("SCAN PREPARATION FAILED", str(e))
|
||||
sys.exit(1)
|
||||
telemetry_start(args)
|
||||
@@ -479,18 +487,21 @@ def main() -> None:
|
||||
from strix.interface.cli import run_cli
|
||||
|
||||
asyncio.run(run_cli(args))
|
||||
# Headless runs have no user to quit: the agent either finished
|
||||
# (already beaconed as finished_by_tool) or stopped on its own.
|
||||
exit_reason = "agent_stopped"
|
||||
else:
|
||||
asyncio.run(run_tui(args))
|
||||
except InteractiveSetupUnavailableError as exc:
|
||||
exit_reason = "error"
|
||||
report_error("interactive_setup_unavailable", exc)
|
||||
_print_error_panel("INTERACTIVE SETUP UNAVAILABLE", str(exc))
|
||||
sys.exit(1)
|
||||
except KeyboardInterrupt:
|
||||
exit_reason = "interrupted"
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
exit_reason = "error"
|
||||
posthog.error("unhandled_exception")
|
||||
scarf.error("unhandled_exception")
|
||||
report_error("unhandled_exception", exc)
|
||||
raise
|
||||
finally:
|
||||
report_state = get_global_report_state()
|
||||
|
||||
@@ -192,7 +192,7 @@ class TuiController:
|
||||
model_warning = ""
|
||||
if model and not is_recommended_or_frontier_model(model):
|
||||
model_warning = (
|
||||
f"{model} is not a recommended frontier model; pentest quality could be degraded"
|
||||
f"{model} is not a recommended frontier model. Pentest quality could be degraded."
|
||||
)
|
||||
state = {
|
||||
"setup_mode": self.setup_mode,
|
||||
|
||||
@@ -384,7 +384,16 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.vulnerabilityCopyError = msg.err.Error()
|
||||
}
|
||||
return m, nil
|
||||
case tea.ResumeMsg:
|
||||
// Suspend turns mouse tracking off with the rest of the terminal state,
|
||||
// but the restore brings back only the alt screen, so turn it back on.
|
||||
return m, tea.EnableMouseCellMotion
|
||||
case tea.KeyMsg:
|
||||
// Raw mode clears ISIG, so ctrl+z arrives as a key instead of SIGTSTP.
|
||||
// Suspend on every screen, the way a shell job would.
|
||||
if msg.Type == tea.KeyCtrlZ {
|
||||
return m, tea.Suspend
|
||||
}
|
||||
if m.showSplash {
|
||||
switch msg.String() {
|
||||
case "ctrl+c", "ctrl+q", "q", "esc":
|
||||
|
||||
@@ -370,6 +370,17 @@ func TestStartedSnapshotTransitionsToLiveView(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplashModelWarningRendersTheBackendSentenceOnce(t *testing.T) {
|
||||
warning := "openai/glm-5.3 is not a recommended frontier model. Pentest quality could be degraded."
|
||||
got := ansi.Strip(splashModelWarning("openai/glm-5.3", warning))
|
||||
if got != "⚠ "+warning {
|
||||
t.Fatalf("splash warning = %q, want %q", got, "⚠ "+warning)
|
||||
}
|
||||
if got := ansi.Strip(splashModelWarning("other/model", warning)); got != "⚠ "+warning {
|
||||
t.Fatalf("splash warning with unrelated model = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupStartScreenFitsNarrowTerminal(t *testing.T) {
|
||||
model := New(nil)
|
||||
model.width, model.height = 40, 18
|
||||
@@ -1428,3 +1439,31 @@ func TestNarrowTerminalKeepsTheFrameIntact(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCtrlZSuspendsFromEveryScreen(t *testing.T) {
|
||||
for name, prepare := range map[string]func(*Model){
|
||||
"splash": func(m *Model) { m.showSplash = true },
|
||||
"modal": func(m *Model) { m.showSplash = false; m.openModal(modalHelp) },
|
||||
"main": func(m *Model) { m.showSplash = false },
|
||||
} {
|
||||
model := New(nil)
|
||||
prepare(&model)
|
||||
_, cmd := model.Update(tea.KeyMsg{Type: tea.KeyCtrlZ})
|
||||
if cmd == nil {
|
||||
t.Fatalf("%s: ctrl+z returned no command", name)
|
||||
}
|
||||
if _, ok := cmd().(tea.SuspendMsg); !ok {
|
||||
t.Fatalf("%s: ctrl+z did not suspend", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestResumeReenablesMouse(t *testing.T) {
|
||||
_, cmd := New(nil).Update(tea.ResumeMsg{})
|
||||
if cmd == nil {
|
||||
t.Fatal("resume returned no command")
|
||||
}
|
||||
if msg := cmd(); msg != tea.EnableMouseCellMotion() {
|
||||
t.Fatalf("resume did not re-enable mouse tracking: %#v", msg)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
"github.com/usestrix/strix/tui/internal/render"
|
||||
)
|
||||
|
||||
type selectionCopiedMsg struct{ err error }
|
||||
@@ -206,7 +207,7 @@ func (m *Model) toggleEventAtLine(line int) {
|
||||
|
||||
func (m Model) selectedText() string {
|
||||
fromLine, fromCol, toLine, toCol := m.selection.bounds()
|
||||
source := m.viewportContent
|
||||
source := render.StopSpinners(m.viewportContent)
|
||||
if m.selection.region == regionInput {
|
||||
source = m.inputText()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
"github.com/usestrix/strix/tui/internal/protocol"
|
||||
)
|
||||
|
||||
func TestParkedWaitSpins(t *testing.T) {
|
||||
model := New(nil)
|
||||
model.width, model.height, model.showSplash, model.ready = 130, 30, false, true
|
||||
model.snapshot = protocol.Snapshot{
|
||||
Agents: []protocol.Agent{{ID: "one", Name: "Agent", Status: "waiting"}},
|
||||
Events: []protocol.Event{{ID: "1", AgentID: "one", Type: "tool", Data: map[string]any{"tool_name": "wait_for_agents"}}},
|
||||
}
|
||||
model.resizeViewport()
|
||||
before := ansi.Strip(model.View())
|
||||
model.sweepFrame += 2
|
||||
if after := ansi.Strip(model.View()); strings.Contains(before, "○ waiting") || before == after {
|
||||
t.Fatalf("wait line did not spin:\n%s", before)
|
||||
}
|
||||
}
|
||||
@@ -28,15 +28,16 @@ type renderedBlock struct {
|
||||
version int
|
||||
width int
|
||||
expanded bool
|
||||
live bool
|
||||
wrapped string
|
||||
expandable bool
|
||||
height int
|
||||
}
|
||||
|
||||
func (m *Model) renderEvent(event protocol.Event, width int) renderedBlock {
|
||||
func (m *Model) renderEvent(event protocol.Event, width int, live bool) renderedBlock {
|
||||
expanded := m.expandedEvents[event.ID]
|
||||
if cached, ok := m.blockCache[event.ID]; ok &&
|
||||
cached.version == event.Version && cached.width == width && cached.expanded == expanded {
|
||||
cached.version == event.Version && cached.width == width && cached.expanded == expanded && cached.live == live {
|
||||
return cached
|
||||
}
|
||||
var block string
|
||||
@@ -48,7 +49,10 @@ func (m *Model) renderEvent(event protocol.Event, width int) renderedBlock {
|
||||
name := render.StringValue(event.Data["tool_name"])
|
||||
block, expandable = render.CollapseTool(render.Tool(event.Data), name, expanded)
|
||||
}
|
||||
entry := renderedBlock{version: event.Version, width: width, expanded: expanded, expandable: expandable}
|
||||
if !live {
|
||||
block = render.StopSpinners(block)
|
||||
}
|
||||
entry := renderedBlock{version: event.Version, width: width, expanded: expanded, live: live, expandable: expandable}
|
||||
if block != "" {
|
||||
entry.wrapped = wrapBlock(block, width)
|
||||
entry.height = strings.Count(entry.wrapped, "\n") + 1
|
||||
@@ -96,6 +100,15 @@ func (m *Model) chatContent() string {
|
||||
// to width-2 and indent every line by one cell.
|
||||
contentWidth := max(1, m.viewport.Width-2)
|
||||
render.SetImageWidth(contentWidth - 2)
|
||||
// A parked agent is waiting on its latest tool call.
|
||||
parkedOn := ""
|
||||
if m.snapshot.Agents[m.selectedAgent].Status == "waiting" {
|
||||
for _, event := range events {
|
||||
if event.AgentID == agentID && event.Type == "tool" {
|
||||
parkedOn = event.ID
|
||||
}
|
||||
}
|
||||
}
|
||||
var blocks []string
|
||||
var spans []eventSpan
|
||||
line := 0
|
||||
@@ -103,7 +116,7 @@ func (m *Model) chatContent() string {
|
||||
if event.AgentID != agentID {
|
||||
continue
|
||||
}
|
||||
entry := m.renderEvent(event, contentWidth)
|
||||
entry := m.renderEvent(event, contentWidth, event.ID == parkedOn)
|
||||
if entry.wrapped == "" {
|
||||
continue
|
||||
}
|
||||
@@ -411,7 +424,7 @@ func (m Model) splashView() string {
|
||||
welcome + "\n" + version + "\n" + tagline + "\n\n" +
|
||||
start.String() + "\n\n" + url
|
||||
if warn := m.snapshot.ModelWarning; warn != "" {
|
||||
content += "\n\n" + splashModelWarning(warn)
|
||||
content += "\n\n" + splashModelWarning(m.snapshot.Model, warn)
|
||||
}
|
||||
panel := lipgloss.NewStyle().Border(lipgloss.RoundedBorder()).BorderForeground(green).Padding(1, 6).Align(lipgloss.Center).Render(content)
|
||||
// #splash_screen background is solid black.
|
||||
@@ -419,12 +432,16 @@ func (m Model) splashView() string {
|
||||
lipgloss.WithWhitespaceBackground(black))
|
||||
}
|
||||
|
||||
// splashModelWarning ports SplashScreen._build_model_warning_text.
|
||||
func splashModelWarning(model string) string {
|
||||
// splashModelWarning renders the backend's full warning sentence, with the
|
||||
// model name highlighted when the sentence leads with it.
|
||||
func splashModelWarning(model, warning string) string {
|
||||
yellow := lipgloss.Color("#eab308")
|
||||
return lipgloss.NewStyle().Bold(true).Foreground(yellow).Render("⚠ ") +
|
||||
lipgloss.NewStyle().Bold(true).Foreground(render.Cyan).Render(model) +
|
||||
lipgloss.NewStyle().Foreground(yellow).Render(" is not a recommended frontier model - pentest quality could be degraded")
|
||||
out := lipgloss.NewStyle().Bold(true).Foreground(yellow).Render("⚠ ")
|
||||
if model != "" && strings.HasPrefix(warning, model) {
|
||||
out += lipgloss.NewStyle().Bold(true).Foreground(render.Cyan).Render(model)
|
||||
warning = strings.TrimPrefix(warning, model)
|
||||
}
|
||||
return out + lipgloss.NewStyle().Foreground(yellow).Render(warning)
|
||||
}
|
||||
|
||||
// chatPaneKey identifies everything the bordered trace depends on.
|
||||
@@ -433,6 +450,7 @@ type chatPaneKey struct {
|
||||
width, height int
|
||||
border lipgloss.Color
|
||||
selection selectionState
|
||||
spinnerFrame int
|
||||
}
|
||||
|
||||
// chatPane memoizes the bordered trace: slicing, scrollbar padding and border
|
||||
@@ -446,12 +464,18 @@ var chatPane struct {
|
||||
}
|
||||
|
||||
func (m Model) renderChatPane(width, height int, border lipgloss.Color) string {
|
||||
key := chatPaneKey{offset: m.viewport.YOffset, width: width, height: height, border: border, selection: m.selection}
|
||||
visible := visibleContent(m.viewportContent, m.viewport.YOffset, height)
|
||||
// Only a trace with a spinner on screen changes with the tick.
|
||||
spinnerFrame := 0
|
||||
if strings.Contains(visible, render.SpinnerMarker) {
|
||||
spinnerFrame = m.sweepFrame / 2
|
||||
}
|
||||
key := chatPaneKey{offset: m.viewport.YOffset, width: width, height: height, border: border, selection: m.selection, spinnerFrame: spinnerFrame}
|
||||
if chatPane.out != "" && chatPane.key == key && chatPane.content == m.viewportContent {
|
||||
return chatPane.out
|
||||
}
|
||||
trace := withVerticalScrollbar(
|
||||
m.highlightSelection(visibleContent(m.viewportContent, m.viewport.YOffset, height), m.viewport.YOffset),
|
||||
render.AnimateSpinners(m.highlightSelection(visible, m.viewport.YOffset), spinnerFrame),
|
||||
width,
|
||||
height,
|
||||
m.viewport.TotalLineCount(),
|
||||
|
||||
@@ -55,7 +55,8 @@ func renderAgentGraphTool(name string, args map[string]any, result any) string {
|
||||
b.WriteString("\n " + Dim().Render("Completing task..."))
|
||||
}
|
||||
case "wait_for_agents":
|
||||
b.WriteString(Col(Gray).Render("○ ") + Dim().Render("waiting"))
|
||||
// The chat pane animates the marker while the agent is parked here.
|
||||
b.WriteString(Col(Gray).Render(SpinnerMarker+" ") + Dim().Render("waiting"))
|
||||
if reason := StringValue(args["reason"]); reason != "" {
|
||||
b.WriteString("\n " + Dim().Render(reason))
|
||||
}
|
||||
|
||||
@@ -86,6 +86,8 @@ func Tool(data map[string]any) string {
|
||||
return renderVulnerabilityReport(args, result)
|
||||
case "update_vulnerability_report":
|
||||
return renderVulnerabilityReportUpdate(args, result)
|
||||
case "delete_vulnerability_report":
|
||||
return renderVulnerabilityReportDelete(args, result)
|
||||
case "create_dependency_report":
|
||||
return renderDependencyReport(args, result)
|
||||
case "list_reports":
|
||||
|
||||
@@ -21,6 +21,36 @@ func renderVulnerabilityReportUpdate(args map[string]any, result any) string {
|
||||
return renderReport(args, result, "Vulnerability Report Updated", "Updating report...")
|
||||
}
|
||||
|
||||
// A deletion names the report it withdraws and the reason; the result carries
|
||||
// the title the report had, so the reader knows what left the scan.
|
||||
func renderVulnerabilityReportDelete(args map[string]any, result any) string {
|
||||
resultMap, _ := result.(map[string]any)
|
||||
var b strings.Builder
|
||||
b.WriteString("🐞 " + Bold(ReportHdr).Render("Vulnerability Report Deleted"))
|
||||
|
||||
reportID := StringValue(args["report_id"])
|
||||
if reportID != "" {
|
||||
b.WriteString("\n\n" + Bold(Field).Render("Report: ") + reportID)
|
||||
}
|
||||
if title := StringValue(resultMap["title"]); title != "" {
|
||||
b.WriteString("\n\n" + Bold(Field).Render("Title: ") + title)
|
||||
}
|
||||
if sev := StringValue(resultMap["severity"]); sev != "" {
|
||||
b.WriteString("\n\n" + Bold(Field).Render("Severity: ") +
|
||||
lipgloss.NewStyle().Bold(true).Foreground(SeverityColor(sev)).Render(strings.ToUpper(sev)))
|
||||
}
|
||||
if reason := StringValue(args["delete_reason"]); reason != "" {
|
||||
b.WriteString("\n\n" + Bold(Field).Render("Reason") + "\n" + reason)
|
||||
}
|
||||
if errMsg := StringValue(resultMap["error"]); errMsg != "" {
|
||||
b.WriteString("\n\n" + Col(SevHigh).Render(errMsg))
|
||||
}
|
||||
if reportID == "" {
|
||||
b.WriteString("\n " + Dim().Render("Deleting report..."))
|
||||
}
|
||||
return "\n\n" + b.String() + "\n\n"
|
||||
}
|
||||
|
||||
func renderReport(args map[string]any, result any, heading, pending string) string {
|
||||
resultMap, _ := result.(map[string]any)
|
||||
var b strings.Builder
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package render
|
||||
|
||||
import "strings"
|
||||
|
||||
// SpinnerMarker is a one-cell placeholder for a spinner. Rendered blocks are
|
||||
// cached, so the chat pane swaps in the current frame (AnimateSpinners) or a
|
||||
// still circle once the wait is over (StopSpinners).
|
||||
const SpinnerMarker = "\uE000"
|
||||
|
||||
var spinnerFrames = []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}
|
||||
|
||||
func StopSpinners(s string) string {
|
||||
return strings.ReplaceAll(s, SpinnerMarker, "○")
|
||||
}
|
||||
|
||||
func AnimateSpinners(s string, tick int) string {
|
||||
return strings.ReplaceAll(s, SpinnerMarker, spinnerFrames[tick%len(spinnerFrames)])
|
||||
}
|
||||
@@ -37,6 +37,7 @@ from strix.interface.tui.sidecar import (
|
||||
)
|
||||
from strix.interface.utils import read_workspace_files
|
||||
from strix.report.state import ReportState, set_global_report_state
|
||||
from strix.telemetry import report_error, set_scan_phase
|
||||
from strix.utils.resource_paths import get_strix_resource_path
|
||||
|
||||
|
||||
@@ -48,6 +49,11 @@ if TYPE_CHECKING:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _revision_count(report: dict[str, Any]) -> int:
|
||||
history = report.get("update_history")
|
||||
return len(history) if isinstance(history, list) else 0
|
||||
|
||||
|
||||
class GoTuiPreActivationError(RuntimeError):
|
||||
"""A sidecar failure raised before the Go TUI activates."""
|
||||
|
||||
@@ -63,6 +69,7 @@ class GoTuiRuntime:
|
||||
self.scan_error: BaseException | None = None
|
||||
self._last_sync_fingerprint = ""
|
||||
self._error_noted_agents: set[str] = set()
|
||||
self._output_syncs: set[asyncio.Task[None]] = set()
|
||||
self.model_verified = False
|
||||
self._setup_preflight: asyncio.Task[None] | None = None
|
||||
self.controller = TuiController(
|
||||
@@ -108,6 +115,9 @@ class GoTuiRuntime:
|
||||
self.report_state.vulnerability_updated_callback = lambda _report: (
|
||||
self.controller.notify_changed()
|
||||
)
|
||||
self.report_state.vulnerability_deleted_callback = lambda _report: (
|
||||
self.controller.notify_changed()
|
||||
)
|
||||
self.controller.notify_changed()
|
||||
|
||||
async def check_setup_model(self) -> None:
|
||||
@@ -138,11 +148,13 @@ class GoTuiRuntime:
|
||||
await self._preflight_model()
|
||||
except Exception as exc:
|
||||
logger.exception("Go TUI setup model preflight failed")
|
||||
report_error("model_connection_failed", exc)
|
||||
raise RuntimeError(f"Model connection failed: {exc}") from exc
|
||||
|
||||
async def _preflight_model(self) -> None:
|
||||
model = (load_settings().llm.model or "").strip()
|
||||
self.controller.add_message("Verifying model connection...")
|
||||
set_scan_phase("preflight")
|
||||
await preflight_model_connection(model)
|
||||
self.model_verified = True
|
||||
|
||||
@@ -181,7 +193,11 @@ class GoTuiRuntime:
|
||||
candidate.target = list(self.controller.targets)
|
||||
candidate.target_list = []
|
||||
build_targets_info(candidate)
|
||||
prepare_run(candidate)
|
||||
try:
|
||||
prepare_run(candidate)
|
||||
except Exception as exc:
|
||||
report_error("scan_preparation_failed", exc)
|
||||
raise
|
||||
telemetry_start(candidate)
|
||||
|
||||
vars(self.args).update(vars(candidate))
|
||||
@@ -195,13 +211,21 @@ class GoTuiRuntime:
|
||||
launch so the interface appears immediately.
|
||||
"""
|
||||
model = (load_settings().llm.model or "").strip()
|
||||
set_scan_phase("preflight")
|
||||
try:
|
||||
await preflight_model_connection(model)
|
||||
except Exception as exc:
|
||||
logger.exception("Go TUI scan preparation failed")
|
||||
report_error("model_connection_failed", exc)
|
||||
self.controller.fail_preparation(str(exc))
|
||||
return
|
||||
try:
|
||||
persist_current()
|
||||
prepare_run(self.args)
|
||||
telemetry_start(self.args)
|
||||
except Exception as exc:
|
||||
logger.exception("Go TUI scan preparation failed")
|
||||
report_error("scan_preparation_failed", exc)
|
||||
self.controller.fail_preparation(str(exc))
|
||||
return
|
||||
self.controller.scan_state = "running"
|
||||
@@ -240,6 +264,9 @@ class GoTuiRuntime:
|
||||
self.controller.scan_state = "completed" if report_status == "completed" else "stopped"
|
||||
except Exception as exc:
|
||||
logger.exception("Go TUI scan failed")
|
||||
report_error("unhandled_exception", exc)
|
||||
if self.report_state is not None and self.report_state.scan_ended_exit_reason is None:
|
||||
self.report_state.scan_ended_exit_reason = "error"
|
||||
self.scan_error = exc
|
||||
self.controller.error = str(exc)
|
||||
self.controller.scan_state = "failed"
|
||||
@@ -250,6 +277,18 @@ class GoTuiRuntime:
|
||||
|
||||
def capture_event(self, agent_id: str, event: Any) -> None:
|
||||
self.live_view.ingest_sdk_event(agent_id, event)
|
||||
if getattr(getattr(event, "item", None), "type", "") == "tool_call_output_item":
|
||||
# A tool that parks its agent has already set the agent's status by
|
||||
# the time it returns; sync it now so both reach the TUI together.
|
||||
task = asyncio.get_running_loop().create_task(self._sync_and_notify())
|
||||
self._output_syncs.add(task)
|
||||
task.add_done_callback(self._output_syncs.discard)
|
||||
return
|
||||
self.controller.notify_changed()
|
||||
|
||||
async def _sync_and_notify(self) -> None:
|
||||
with contextlib.suppress(Exception):
|
||||
await self._sync_agent_state()
|
||||
self.controller.notify_changed()
|
||||
|
||||
def capture_mcp_status(self, roster: list[dict[str, Any]]) -> None:
|
||||
@@ -321,7 +360,9 @@ class GoTuiRuntime:
|
||||
if self.report_state is not None:
|
||||
usage = dict(self.report_state.get_total_llm_usage())
|
||||
vulnerabilities = [
|
||||
report.get("id", index) if isinstance(report, dict) else index
|
||||
(report.get("id", index), _revision_count(report))
|
||||
if isinstance(report, dict)
|
||||
else index
|
||||
for index, report in enumerate(self.report_state.vulnerability_reports)
|
||||
]
|
||||
return json.dumps(
|
||||
|
||||
@@ -19,6 +19,7 @@ from rich.panel import Panel
|
||||
from rich.text import Text
|
||||
|
||||
from strix.config import load_settings
|
||||
from strix.telemetry import report_error
|
||||
from strix.utils.api_spec import detect_spec_format
|
||||
|
||||
|
||||
@@ -1322,6 +1323,7 @@ def collect_local_sources(targets_info: list[dict[str, Any]]) -> list[dict[str,
|
||||
"source_path": details["target_path"],
|
||||
"workspace_subdir": workspace_subdir,
|
||||
"protect_metadata": True,
|
||||
"read_only": bool(details.get("read_only")),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1602,7 +1604,8 @@ def check_docker_connection() -> Any:
|
||||
|
||||
try:
|
||||
return docker.from_env()
|
||||
except DockerException:
|
||||
except DockerException as exc:
|
||||
report_error("docker_unavailable", exc)
|
||||
console = Console()
|
||||
error_text = Text()
|
||||
error_text.append("DOCKER NOT AVAILABLE", style="bold red")
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Radar,
|
||||
Rocket,
|
||||
ArrowUpRight,
|
||||
Building2,
|
||||
History,
|
||||
} from "lucide-react";
|
||||
import type { Vulnerability, VulnerabilitySeverity } from "@/types/issues";
|
||||
@@ -35,7 +36,7 @@ import {
|
||||
type LoadedRun,
|
||||
type RunsPayload,
|
||||
} from "@/data/serverSource";
|
||||
import { SIGNUP_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import { SIGNUP_URL, DEMO_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import { runTitle } from "@/lib/target-utils";
|
||||
import Sidebar from "@/components/Sidebar";
|
||||
import PastRunsView from "@/components/PastRunsView";
|
||||
@@ -706,6 +707,31 @@ function OverviewTab({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{finished && (
|
||||
<div className="animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<p className="text-sm font-semibold text-white">Strix Cloud</p>
|
||||
<p className="mt-0.5 text-xs text-[#666]">Run your next pentest in Strix Cloud.</p>
|
||||
<div className="mt-3 flex flex-wrap gap-2.5">
|
||||
<ProInlineCta
|
||||
label="Run a pentest in Strix Cloud"
|
||||
desc="Validated findings, autofix, and PR reviews."
|
||||
slug="overview_cloud"
|
||||
surface="overview"
|
||||
icon={Rocket}
|
||||
primary
|
||||
/>
|
||||
<ProInlineCta
|
||||
label="Try Strix Enterprise"
|
||||
desc="SSO, compliance-ready reports, VPC or self-hosted deployment."
|
||||
slug="book_demo"
|
||||
surface="overview"
|
||||
icon={Building2}
|
||||
href={DEMO_URL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{sections.length > 0 ? (
|
||||
<div className="animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5 space-y-8">
|
||||
{sections.map((s) => (
|
||||
@@ -789,14 +815,23 @@ function AgentsTab({ run, canSteer }: { run: LoadedRun; canSteer: boolean }) {
|
||||
{/* Re-run always routes to Strix Cloud. */}
|
||||
<div className="rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5">
|
||||
<p className="text-sm font-semibold text-white">Run this pentest with more depth</p>
|
||||
<p className="mt-0.5 text-xs text-[#666]">Re-run this pentest on managed infra in the cloud.</p>
|
||||
<p className="mt-0.5 text-xs text-[#666]">Run this pentest again in Strix Cloud.</p>
|
||||
<div className="mt-3 flex flex-wrap gap-2.5">
|
||||
<ProInlineCta
|
||||
label="Re-run in Strix Pro with more depth"
|
||||
desc="Run this pentest on managed infra with more depth."
|
||||
desc="More depth, validated findings, and autofix."
|
||||
slug="live_scan"
|
||||
surface="agents"
|
||||
icon={Rocket}
|
||||
primary
|
||||
/>
|
||||
<ProInlineCta
|
||||
label="Try Strix Enterprise"
|
||||
desc="SSO, compliance-ready reports, VPC or self-hosted deployment."
|
||||
slug="book_demo"
|
||||
surface="agents"
|
||||
icon={Building2}
|
||||
href={DEMO_URL}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,23 +48,40 @@ export function ProInlineCta({
|
||||
slug,
|
||||
icon: Icon,
|
||||
surface,
|
||||
href = SIGNUP_URL,
|
||||
primary = false,
|
||||
}: {
|
||||
label: string;
|
||||
desc: string;
|
||||
slug: string;
|
||||
icon: React.ElementType;
|
||||
surface?: string;
|
||||
/** Destination before attribution params. Defaults to cloud sign-up. */
|
||||
href?: string;
|
||||
/** Solid white button instead of the outlined default. */
|
||||
primary?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<Tooltip text={desc}>
|
||||
<a
|
||||
href={ctaUrl(SIGNUP_URL, slug)}
|
||||
href={ctaUrl(href, slug)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta(slug, surface)}
|
||||
className="group inline-flex items-center gap-2 rounded-lg border border-[#222] bg-[rgba(255,255,255,0.02)] px-3 py-2 text-sm text-[#aaa] transition-colors hover:border-[#444] hover:text-white"
|
||||
className={
|
||||
primary
|
||||
? "group inline-flex items-center gap-2 rounded-lg border border-white bg-white px-3 py-2 text-sm font-semibold text-black transition-colors hover:bg-[#e5e5e5]"
|
||||
: "group inline-flex items-center gap-2 rounded-lg border border-[#222] bg-[rgba(255,255,255,0.02)] px-3 py-2 text-sm text-[#aaa] transition-colors hover:border-[#444] hover:text-white"
|
||||
}
|
||||
>
|
||||
<Icon className="h-4 w-4 text-[#888] transition-colors group-hover:text-white" aria-hidden="true" />
|
||||
<Icon
|
||||
className={
|
||||
primary
|
||||
? "h-4 w-4 text-black"
|
||||
: "h-4 w-4 text-[#888] transition-colors group-hover:text-white"
|
||||
}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>{label}</span>
|
||||
</a>
|
||||
</Tooltip>
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
WandSparkles,
|
||||
Plug,
|
||||
} from "lucide-react";
|
||||
import { SIGNUP_URL, PRICING_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
import { SIGNUP_URL, PRICING_URL, DEMO_URL, ctaUrl, trackCta } from "@/lib/cta";
|
||||
|
||||
/**
|
||||
* Dialog shown when a platform feature is clicked in the sidebar: a short
|
||||
@@ -141,6 +141,19 @@ export function UpgradeModal({
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p className="text-center text-xs text-[#666]">
|
||||
SSO, compliance reports, or a private deployment?{" "}
|
||||
<a
|
||||
href={ctaUrl(DEMO_URL, "upgrade_book_demo")}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={() => trackCta("upgrade_book_demo", source)}
|
||||
className="whitespace-nowrap text-[#aaa] underline underline-offset-2 transition-colors hover:text-white"
|
||||
>
|
||||
Try Strix Enterprise
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
"use client";
|
||||
|
||||
import type { ToolRendererProps } from "@/types/events";
|
||||
import { TruncatedText } from "./ToolCard";
|
||||
|
||||
const SEVERITY_COLORS: Record<string, string> = {
|
||||
critical: "text-red-400", high: "text-orange-400", medium: "text-yellow-400",
|
||||
low: "text-blue-400", info: "text-cyan-400",
|
||||
};
|
||||
|
||||
/** A withdrawn finding: which report went, and what disproved it. */
|
||||
export default function VulnReportDeleteRenderer({ args, result, status }: ToolRendererProps) {
|
||||
const reportId = (args.report_id as string) ?? "";
|
||||
const reason = (args.delete_reason as string) ?? "";
|
||||
|
||||
const res = result && typeof result === "object" ? (result as Record<string, unknown>) : null;
|
||||
const ok = res?.success === true;
|
||||
const error = typeof res?.error === "string" ? res.error : "";
|
||||
const title = typeof res?.title === "string" ? res.title : "";
|
||||
const severity = typeof res?.severity === "string" ? res.severity.toLowerCase() : "";
|
||||
const failed = res != null ? !ok : status === "failed" || status === "error";
|
||||
const pending = res == null && !failed;
|
||||
const label = pending ? "withdrawing report\u2026" : failed ? "report not withdrawn" : "report withdrawn";
|
||||
const labelColor = pending ? "text-[#888]" : failed ? "text-red-400/80" : "text-yellow-400/80";
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<span className={`font-semibold text-sm ${labelColor}`} aria-live="polite">
|
||||
{label}
|
||||
</span>
|
||||
{reportId && <span className="text-[#555] font-mono text-[13px]">{reportId}</span>}
|
||||
{severity && (
|
||||
<span className={`text-[13px] line-through ${SEVERITY_COLORS[severity] ?? "text-[#888]"}`}>
|
||||
{severity.toUpperCase()}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{title && <div className="text-[15px] text-white/60 line-through">{title}</div>}
|
||||
{reason && (
|
||||
<div>
|
||||
<span className="text-emerald-400/60 text-sm font-semibold">Why</span>
|
||||
<div className="mt-1"><TruncatedText text={reason} maxLines={10} /></div>
|
||||
</div>
|
||||
)}
|
||||
{error && <div className="text-red-400/80 text-[13px]">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import FileEditRenderer from "./FileEditRenderer";
|
||||
import ApplyPatchRenderer from "./ApplyPatchRenderer";
|
||||
import ViewImageRenderer from "./ViewImageRenderer";
|
||||
import VulnReportRenderer from "./VulnReportRenderer";
|
||||
import VulnReportDeleteRenderer from "./VulnReportDeleteRenderer";
|
||||
import ReportListRenderer from "./ReportListRenderer";
|
||||
import ProxyRenderer from "./ProxyRenderer";
|
||||
import ThinkRenderer from "./ThinkRenderer";
|
||||
@@ -116,7 +117,7 @@ const CATEGORY_TOOLS: Record<ToolCategory, readonly string[]> = {
|
||||
filesystem: ["apply_patch", "view_image", "str_replace_editor", "list_files", "search_files"],
|
||||
// Caido proxy tools (legacy: send_request)
|
||||
proxy: ["list_requests", "view_request", "repeat_request", "list_sitemap", "view_sitemap_entry", "scope_rules", "send_request"],
|
||||
reporting: ["create_vulnerability_report", "update_vulnerability_report", "list_reports", "get_report"],
|
||||
reporting: ["create_vulnerability_report", "update_vulnerability_report", "delete_vulnerability_report", "list_reports", "get_report"],
|
||||
thinking: ["think"],
|
||||
agents: ["create_agent", "agent_finish", "send_message_to_agent", "wait_for_agents", "view_agent_graph", "stop_agent"],
|
||||
search: ["web_search"],
|
||||
@@ -151,6 +152,7 @@ const RENDERER_OVERRIDES: Partial<Record<string, ComponentType<ToolRendererProps
|
||||
view_image: ViewImageRenderer,
|
||||
list_reports: ReportListRenderer,
|
||||
get_report: ReportListRenderer,
|
||||
delete_vulnerability_report: VulnReportDeleteRenderer,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -7,4 +7,4 @@ pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5p
|
||||
|
||||
Outdated base version: https://github.com/primer/github-syntax-dark
|
||||
Current colors taken from GitHub's CSS
|
||||
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__panLine truncated
|
||||
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-variable,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id{color:#79c0ff}.hljs-regexp,.hljs-string,.hljs-meta .hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-comment,.hljs-code,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}.react-flow{direction:ltr;--xy-edge-stroke-default: #b1b1b7;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #555;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(255, 255, 255, .5);--xy-minimap-background-color-default: #fff;--xy-minimap-mask-background-color-default: rgba(240, 240, 240, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #e2e2e2;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: transparent;--xy-background-pattern-dots-color-default: #91919a;--xy-background-pattern-lines-color-default: #eee;--xy-background-pattern-cross-color-default: #e2e2e2;background-color:var(--xy-background-color, var(--xy-background-color-default));--xy-node-color-default: inherit;--xy-node-border-default: 1px solid #1a192b;--xy-node-background-color-default: #fff;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(0, 0, 0, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #1a192b;--xy-node-border-radius-default: 3px;--xy-handle-background-color-default: #1a192b;--xy-handle-border-color-default: #fff;--xy-selection-background-color-default: rgba(0, 89, 220, .08);--xy-selection-border-default: 1px dotted rgba(0, 89, 220, .8);--xy-controls-button-background-color-default: #fefefe;--xy-controls-button-background-color-hover-default: #f4f4f4;--xy-controls-button-color-default: inherit;--xy-controls-button-color-hover-default: inherit;--xy-controls-button-border-color-default: #eee;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #ffffff;--xy-edge-label-color-default: inherit;--xy-resize-background-color-default: #3367d9}.react-flow.dark{--xy-edge-stroke-default: #3e3e3e;--xy-edge-stroke-width-default: 1;--xy-edge-stroke-selected-default: #727272;--xy-connectionline-stroke-default: #b1b1b7;--xy-connectionline-stroke-width-default: 1;--xy-attribution-background-color-default: rgba(150, 150, 150, .25);--xy-minimap-background-color-default: #141414;--xy-minimap-mask-background-color-default: rgba(60, 60, 60, .6);--xy-minimap-mask-stroke-color-default: transparent;--xy-minimap-mask-stroke-width-default: 1;--xy-minimap-node-background-color-default: #2b2b2b;--xy-minimap-node-stroke-color-default: transparent;--xy-minimap-node-stroke-width-default: 2;--xy-background-color-default: #141414;--xy-background-pattern-dots-color-default: #555;--xy-background-pattern-lines-color-default: #333;--xy-background-pattern-cross-color-default: #333;--xy-node-color-default: #f8f8f8;--xy-node-border-default: 1px solid #3c3c3c;--xy-node-background-color-default: #1e1e1e;--xy-node-group-background-color-default: rgba(240, 240, 240, .25);--xy-node-boxshadow-hover-default: 0 1px 4px 1px rgba(255, 255, 255, .08);--xy-node-boxshadow-selected-default: 0 0 0 .5px #999;--xy-handle-background-color-default: #bebebe;--xy-handle-border-color-default: #1e1e1e;--xy-selection-background-color-default: rgba(200, 200, 220, .08);--xy-selection-border-default: 1px dotted rgba(200, 200, 220, .8);--xy-controls-button-background-color-default: #2b2b2b;--xy-controls-button-background-color-hover-default: #3e3e3e;--xy-controls-button-color-default: #f8f8f8;--xy-controls-button-color-hover-default: #fff;--xy-controls-button-border-color-default: #5b5b5b;--xy-controls-box-shadow-default: 0 0 2px 1px rgba(0, 0, 0, .08);--xy-edge-label-background-color-default: #141414;--xy-edge-label-color-default: #f8f8f8}.react-flow__background{background-color:var(--xy-background-color-props, var(--xy-background-color, var(--xy-background-color-default)));pointer-events:none;z-index:-1}.react-flow__container{position:absolute;width:100%;height:100%;top:0;left:0}.react-flow__pane{z-index:1;touch-action:none}.react-flow__panLine truncated
|
||||
+138
-133
@@ -1,4 +1,4 @@
|
||||
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const c of o.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&a(c)}).observe(document,{childList:!0,subtree:!0});function r(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(s){if(s.ep)return;s.ep=!0;const o=r(s);fetch(s.href,o)}})();function Ao(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var fh={exports:{}},Xl={};/**
|
||||
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))a(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const c of o.addedNodes)c.tagName==="LINK"&&c.rel==="modulepreload"&&a(c)}).observe(document,{childList:!0,subtree:!0});function r(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(s){if(s.ep)return;s.ep=!0;const o=r(s);fetch(s.href,o)}})();function Mo(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var hh={exports:{}},Xl={};/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.js
|
||||
*
|
||||
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var Q0;function Mk(){if(Q0)return Xl;Q0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,s,o){var c=null;if(o!==void 0&&(c=""+o),s.key!==void 0&&(c=""+s.key),"key"in s){o={};for(var d in s)d!=="key"&&(o[d]=s[d])}else o=s;return s=o.ref,{$$typeof:e,type:a,key:c,ref:s!==void 0?s:null,props:o}}return Xl.Fragment=t,Xl.jsx=r,Xl.jsxs=r,Xl}var W0;function Ok(){return W0||(W0=1,fh.exports=Mk()),fh.exports}var m=Ok(),hh={exports:{}},Ve={};/**
|
||||
*/var J0;function jk(){if(J0)return Xl;J0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.fragment");function r(a,s,o){var c=null;if(o!==void 0&&(c=""+o),s.key!==void 0&&(c=""+s.key),"key"in s){o={};for(var d in s)d!=="key"&&(o[d]=s[d])}else o=s;return s=o.ref,{$$typeof:e,type:a,key:c,ref:s!==void 0?s:null,props:o}}return Xl.Fragment=t,Xl.jsx=r,Xl.jsxs=r,Xl}var ey;function Dk(){return ey||(ey=1,hh.exports=jk()),hh.exports}var m=Dk(),mh={exports:{}},Ve={};/**
|
||||
* @license React
|
||||
* react.production.js
|
||||
*
|
||||
@@ -14,7 +14,7 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var J0;function Rk(){if(J0)return Ve;J0=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),o=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),h=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),p=Symbol.for("react.lazy"),g=Symbol.for("react.activity"),b=Symbol.iterator;function y(L){return L===null||typeof L!="object"?null:(L=b&&L[b]||L["@@iterator"],typeof L=="function"?L:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},N=Object.assign,S={};function w(L,F,D){this.props=L,this.context=F,this.refs=S,this.updater=D||_}w.prototype.isReactComponent={},w.prototype.setState=function(L,F){if(typeof L!="object"&&typeof L!="function"&&L!=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,L,F,"setState")},w.prototype.forceUpdate=function(L){this.updater.enqueueForceUpdate(this,L,"forceUpdate")};function C(){}C.prototype=w.prototype;function E(L,F,D){this.props=L,this.context=F,this.refs=S,this.updater=D||_}var A=E.prototype=new C;A.constructor=E,N(A,w.prototype),A.isPureReactComponent=!0;var B=Array.isArray;function R(){}var H={H:null,A:null,T:null,S:null},z=Object.prototype.hasOwnProperty;function Y(L,F,D){var V=D.ref;return{$$typeof:e,type:L,key:F,ref:V!==void 0?V:null,props:D}}function j(L,F){return Y(L.type,F,L.props)}function I(L){return typeof L=="object"&&L!==null&&L.$$typeof===e}function Z(L){var F={"=":"=0",":":"=2"};return"$"+L.replace(/[=:]/g,function(D){return F[D]})}var P=/\/+/g;function k(L,F){return typeof L=="object"&&L!==null&&L.key!=null?Z(""+L.key):F.toString(36)}function $(L){switch(L.status){case"fulfilled":return L.value;case"rejected":throw L.reason;default:switch(typeof L.status=="string"?L.then(R,R):(L.status="pending",L.then(function(F){L.status==="pending"&&(L.status="fulfilled",L.value=F)},function(F){L.status==="pending"&&(L.status="rejected",L.reason=F)})),L.status){case"fulfilled":return L.value;case"rejected":throw L.reason}}throw L}function O(L,F,D,V,q){var Q=typeof L;(Q==="undefined"||Q==="boolean")&&(L=null);var J=!1;if(L===null)J=!0;else switch(Q){case"bigint":case"string":case"number":J=!0;break;case"object":switch(L.$$typeof){case e:case t:J=!0;break;case p:return J=L._init,O(J(L._payload),F,D,V,q)}}if(J)return q=q(L),J=V===""?"."+k(L,0):V,B(q)?(D="",J!=null&&(D=J.replace(P,"$&/")+"/"),O(q,F,D,"",function(oe){return oe})):q!=null&&(I(q)&&(q=j(q,D+(q.key==null||L&&L.key===q.key?"":(""+q.key).replace(P,"$&/")+"/")+J)),F.push(q)),1;J=0;var W=V===""?".":V+":";if(B(L))for(var te=0;te<L.length;te++)V=L[te],Q=W+k(V,te),J+=O(V,F,D,Q,q);else if(te=y(L),typeof te=="function")for(L=te.call(L),te=0;!(V=L.next()).done;)V=V.value,Q=W+k(V,te++),J+=O(V,F,D,Q,q);else if(Q==="object"){if(typeof L.then=="function")return O($(L),F,D,V,q);throw F=String(L),Error("Objects are not valid as a React child (found: "+(F==="[object Object]"?"object with keys {"+Object.keys(L).join(", ")+"}":F)+"). If you meant to render a collection of children, use an array instead.")}return J}function U(L,F,D){if(L==null)return L;var V=[],q=0;return O(L,V,"","",function(Q){return F.call(D,Q,q++)}),V}function K(L){if(L._status===-1){var F=L._result;F=F(),F.then(function(D){(L._status===0||L._status===-1)&&(L._status=1,L._result=D)},function(D){(L._status===0||L._status===-1)&&(L._status=2,L._result=D)}),L._status===-1&&(L._status=0,L._result=F)}if(L._status===1)return L._result.default;throw L._result}var X=typeof reportError=="function"?reportError:function(L){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var F=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof L=="object"&&L!==null&&typeof L.message=="string"?String(L.message):String(L),error:L});if(!window.dispatchEvent(F))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",L);return}console.error(L)},M={map:U,forEach:function(L,F,D){U(L,function(){F.apply(this,arguments)},D)},count:function(L){var F=0;return U(L,function(){F++}),F},toArray:function(L){return U(L,function(F){return F})||[]},only:function(L){if(!I(L))throw Error("React.Children.only expected to receive a single React element child.");return L}};return Ve.Activity=g,Ve.Children=M,Ve.Component=w,Ve.Fragment=r,Ve.Profiler=s,Ve.PureComponent=E,Ve.StrictMode=a,Ve.Suspense=h,Ve.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=H,Ve.__COMPILER_RUNTIME={__proto__:null,c:function(L){return H.H.useMemoCache(L)}},Ve.cache=function(L){return function(){return L.apply(null,arguments)}},Ve.cacheSignal=function(){return null},Ve.cloneElement=function(L,F,D){if(L==null)throw Error("The argument must be a React element, but you passed "+L+".");var
Line truncated
|
||||
*/var ty;function Lk(){if(ty)return Ve;ty=1;var e=Symbol.for("react.transitional.element"),t=Symbol.for("react.portal"),r=Symbol.for("react.fragment"),a=Symbol.for("react.strict_mode"),s=Symbol.for("react.profiler"),o=Symbol.for("react.consumer"),c=Symbol.for("react.context"),d=Symbol.for("react.forward_ref"),h=Symbol.for("react.suspense"),f=Symbol.for("react.memo"),p=Symbol.for("react.lazy"),g=Symbol.for("react.activity"),b=Symbol.iterator;function y(L){return L===null||typeof L!="object"?null:(L=b&&L[b]||L["@@iterator"],typeof L=="function"?L:null)}var _={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},N=Object.assign,S={};function w(L,F,D){this.props=L,this.context=F,this.refs=S,this.updater=D||_}w.prototype.isReactComponent={},w.prototype.setState=function(L,F){if(typeof L!="object"&&typeof L!="function"&&L!=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,L,F,"setState")},w.prototype.forceUpdate=function(L){this.updater.enqueueForceUpdate(this,L,"forceUpdate")};function C(){}C.prototype=w.prototype;function E(L,F,D){this.props=L,this.context=F,this.refs=S,this.updater=D||_}var A=E.prototype=new C;A.constructor=E,N(A,w.prototype),A.isPureReactComponent=!0;var B=Array.isArray;function R(){}var H={H:null,A:null,T:null,S:null},z=Object.prototype.hasOwnProperty;function Y(L,F,D){var V=D.ref;return{$$typeof:e,type:L,key:F,ref:V!==void 0?V:null,props:D}}function j(L,F){return Y(L.type,F,L.props)}function I(L){return typeof L=="object"&&L!==null&&L.$$typeof===e}function Z(L){var F={"=":"=0",":":"=2"};return"$"+L.replace(/[=:]/g,function(D){return F[D]})}var P=/\/+/g;function k(L,F){return typeof L=="object"&&L!==null&&L.key!=null?Z(""+L.key):F.toString(36)}function $(L){switch(L.status){case"fulfilled":return L.value;case"rejected":throw L.reason;default:switch(typeof L.status=="string"?L.then(R,R):(L.status="pending",L.then(function(F){L.status==="pending"&&(L.status="fulfilled",L.value=F)},function(F){L.status==="pending"&&(L.status="rejected",L.reason=F)})),L.status){case"fulfilled":return L.value;case"rejected":throw L.reason}}throw L}function O(L,F,D,V,q){var Q=typeof L;(Q==="undefined"||Q==="boolean")&&(L=null);var J=!1;if(L===null)J=!0;else switch(Q){case"bigint":case"string":case"number":J=!0;break;case"object":switch(L.$$typeof){case e:case t:J=!0;break;case p:return J=L._init,O(J(L._payload),F,D,V,q)}}if(J)return q=q(L),J=V===""?"."+k(L,0):V,B(q)?(D="",J!=null&&(D=J.replace(P,"$&/")+"/"),O(q,F,D,"",function(oe){return oe})):q!=null&&(I(q)&&(q=j(q,D+(q.key==null||L&&L.key===q.key?"":(""+q.key).replace(P,"$&/")+"/")+J)),F.push(q)),1;J=0;var W=V===""?".":V+":";if(B(L))for(var te=0;te<L.length;te++)V=L[te],Q=W+k(V,te),J+=O(V,F,D,Q,q);else if(te=y(L),typeof te=="function")for(L=te.call(L),te=0;!(V=L.next()).done;)V=V.value,Q=W+k(V,te++),J+=O(V,F,D,Q,q);else if(Q==="object"){if(typeof L.then=="function")return O($(L),F,D,V,q);throw F=String(L),Error("Objects are not valid as a React child (found: "+(F==="[object Object]"?"object with keys {"+Object.keys(L).join(", ")+"}":F)+"). If you meant to render a collection of children, use an array instead.")}return J}function U(L,F,D){if(L==null)return L;var V=[],q=0;return O(L,V,"","",function(Q){return F.call(D,Q,q++)}),V}function K(L){if(L._status===-1){var F=L._result;F=F(),F.then(function(D){(L._status===0||L._status===-1)&&(L._status=1,L._result=D)},function(D){(L._status===0||L._status===-1)&&(L._status=2,L._result=D)}),L._status===-1&&(L._status=0,L._result=F)}if(L._status===1)return L._result.default;throw L._result}var X=typeof reportError=="function"?reportError:function(L){if(typeof window=="object"&&typeof window.ErrorEvent=="function"){var F=new window.ErrorEvent("error",{bubbles:!0,cancelable:!0,message:typeof L=="object"&&L!==null&&typeof L.message=="string"?String(L.message):String(L),error:L});if(!window.dispatchEvent(F))return}else if(typeof process=="object"&&typeof process.emit=="function"){process.emit("uncaughtException",L);return}console.error(L)},M={map:U,forEach:function(L,F,D){U(L,function(){F.apply(this,arguments)},D)},count:function(L){var F=0;return U(L,function(){F++}),F},toArray:function(L){return U(L,function(F){return F})||[]},only:function(L){if(!I(L))throw Error("React.Children.only expected to receive a single React element child.");return L}};return Ve.Activity=g,Ve.Children=M,Ve.Component=w,Ve.Fragment=r,Ve.Profiler=s,Ve.PureComponent=E,Ve.StrictMode=a,Ve.Suspense=h,Ve.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=H,Ve.__COMPILER_RUNTIME={__proto__:null,c:function(L){return H.H.useMemoCache(L)}},Ve.cache=function(L){return function(){return L.apply(null,arguments)}},Ve.cacheSignal=function(){return null},Ve.cloneElement=function(L,F,D){if(L==null)throw Error("The argument must be a React element, but you passed "+L+".");var
Line truncated
|
||||
* @license React
|
||||
* scheduler.production.js
|
||||
*
|
||||
@@ -22,7 +22,7 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var ty;function jk(){return ty||(ty=1,(function(e){function t(O,U){var K=O.length;O.push(U);e:for(;0<K;){var X=K-1>>>1,M=O[X];if(0<s(M,U))O[X]=U,O[K]=M,K=X;else break e}}function r(O){return O.length===0?null:O[0]}function a(O){if(O.length===0)return null;var U=O[0],K=O.pop();if(K!==U){O[0]=K;e:for(var X=0,M=O.length,L=M>>>1;X<L;){var F=2*(X+1)-1,D=O[F],V=F+1,q=O[V];if(0>s(D,K))V<M&&0>s(q,D)?(O[X]=q,O[V]=K,X=V):(O[X]=D,O[F]=K,X=F);else if(V<M&&0>s(q,K))O[X]=q,O[V]=K,X=V;else break e}}return U}function s(O,U){var K=O.sortIndex-U.sortIndex;return K!==0?K:O.id-U.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var o=performance;e.unstable_now=function(){return o.now()}}else{var c=Date,d=c.now();e.unstable_now=function(){return c.now()-d}}var h=[],f=[],p=1,g=null,b=3,y=!1,_=!1,N=!1,S=!1,w=typeof setTimeout=="function"?setTimeout:null,C=typeof clearTimeout=="function"?clearTimeout:null,E=typeof setImmediate<"u"?setImmediate:null;function A(O){for(var U=r(f);U!==null;){if(U.callback===null)a(f);else if(U.startTime<=O)a(f),U.sortIndex=U.expirationTime,t(h,U);else break;U=r(f)}}function B(O){if(N=!1,A(O),!_)if(r(h)!==null)_=!0,R||(R=!0,Z());else{var U=r(f);U!==null&&$(B,U.startTime-O)}}var R=!1,H=-1,z=5,Y=-1;function j(){return S?!0:!(e.unstable_now()-Y<z)}function I(){if(S=!1,R){var O=e.unstable_now();Y=O;var U=!0;try{e:{_=!1,N&&(N=!1,C(H),H=-1),y=!0;var K=b;try{t:{for(A(O),g=r(h);g!==null&&!(g.expirationTime>O&&j());){var X=g.callback;if(typeof X=="function"){g.callback=null,b=g.priorityLevel;var M=X(g.expirationTime<=O);if(O=e.unstable_now(),typeof M=="function"){g.callback=M,A(O),U=!0;break t}g===r(h)&&a(h),A(O)}else a(h);g=r(h)}if(g!==null)U=!0;else{var L=r(f);L!==null&&$(B,L.startTime-O),U=!1}}break e}finally{g=null,b=K,y=!1}U=void 0}}finally{U?Z():R=!1}}}var Z;if(typeof E=="function")Z=function(){E(I)};else if(typeof MessageChannel<"u"){var P=new MessageChannel,k=P.port2;P.port1.onmessage=I,Z=function(){k.postMessage(null)}}else Z=function(){w(I,0)};function $(O,U){H=w(function(){O(e.unstable_now())},U)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(O){O.callback=null},e.unstable_forceFrameRate=function(O){0>O||125<O?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):z=0<O?Math.floor(1e3/O):5},e.unstable_getCurrentPriorityLevel=function(){return b},e.unstable_next=function(O){switch(b){case 1:case 2:case 3:var U=3;break;default:U=b}var K=b;b=U;try{return O()}finally{b=K}},e.unstable_requestPaint=function(){S=!0},e.unstable_runWithPriority=function(O,U){switch(O){case 1:case 2:case 3:case 4:case 5:break;default:O=3}var K=b;b=O;try{return U()}finally{b=K}},e.unstable_scheduleCallback=function(O,U,K){var X=e.unstable_now();switch(typeof K=="object"&&K!==null?(K=K.delay,K=typeof K=="number"&&0<K?X+K:X):K=X,O){case 1:var M=-1;break;case 2:M=250;break;case 5:M=1073741823;break;case 4:M=1e4;break;default:M=5e3}return M=K+M,O={id:p++,callback:U,priorityLevel:O,startTime:K,expirationTime:M,sortIndex:-1},K>X?(O.sortIndex=K,t(f,O),r(h)===null&&O===r(f)&&(N?(C(H),H=-1):N=!0,$(B,K-X))):(O.sortIndex=M,t(h,O),_||y||(_=!0,R||(R=!0,Z()))),O},e.unstable_shouldYield=j,e.unstable_wrapCallback=function(O){var U=b;return function(){var K=b;b=U;try{return O.apply(this,arguments)}finally{b=K}}}})(gh)),gh}var ny;function Dk(){return ny||(ny=1,ph.exports=jk()),ph.exports}var xh={exports:{}},Tn={};/**
|
||||
*/var ry;function zk(){return ry||(ry=1,(function(e){function t(O,U){var K=O.length;O.push(U);e:for(;0<K;){var X=K-1>>>1,M=O[X];if(0<s(M,U))O[X]=U,O[K]=M,K=X;else break e}}function r(O){return O.length===0?null:O[0]}function a(O){if(O.length===0)return null;var U=O[0],K=O.pop();if(K!==U){O[0]=K;e:for(var X=0,M=O.length,L=M>>>1;X<L;){var F=2*(X+1)-1,D=O[F],V=F+1,q=O[V];if(0>s(D,K))V<M&&0>s(q,D)?(O[X]=q,O[V]=K,X=V):(O[X]=D,O[F]=K,X=F);else if(V<M&&0>s(q,K))O[X]=q,O[V]=K,X=V;else break e}}return U}function s(O,U){var K=O.sortIndex-U.sortIndex;return K!==0?K:O.id-U.id}if(e.unstable_now=void 0,typeof performance=="object"&&typeof performance.now=="function"){var o=performance;e.unstable_now=function(){return o.now()}}else{var c=Date,d=c.now();e.unstable_now=function(){return c.now()-d}}var h=[],f=[],p=1,g=null,b=3,y=!1,_=!1,N=!1,S=!1,w=typeof setTimeout=="function"?setTimeout:null,C=typeof clearTimeout=="function"?clearTimeout:null,E=typeof setImmediate<"u"?setImmediate:null;function A(O){for(var U=r(f);U!==null;){if(U.callback===null)a(f);else if(U.startTime<=O)a(f),U.sortIndex=U.expirationTime,t(h,U);else break;U=r(f)}}function B(O){if(N=!1,A(O),!_)if(r(h)!==null)_=!0,R||(R=!0,Z());else{var U=r(f);U!==null&&$(B,U.startTime-O)}}var R=!1,H=-1,z=5,Y=-1;function j(){return S?!0:!(e.unstable_now()-Y<z)}function I(){if(S=!1,R){var O=e.unstable_now();Y=O;var U=!0;try{e:{_=!1,N&&(N=!1,C(H),H=-1),y=!0;var K=b;try{t:{for(A(O),g=r(h);g!==null&&!(g.expirationTime>O&&j());){var X=g.callback;if(typeof X=="function"){g.callback=null,b=g.priorityLevel;var M=X(g.expirationTime<=O);if(O=e.unstable_now(),typeof M=="function"){g.callback=M,A(O),U=!0;break t}g===r(h)&&a(h),A(O)}else a(h);g=r(h)}if(g!==null)U=!0;else{var L=r(f);L!==null&&$(B,L.startTime-O),U=!1}}break e}finally{g=null,b=K,y=!1}U=void 0}}finally{U?Z():R=!1}}}var Z;if(typeof E=="function")Z=function(){E(I)};else if(typeof MessageChannel<"u"){var P=new MessageChannel,k=P.port2;P.port1.onmessage=I,Z=function(){k.postMessage(null)}}else Z=function(){w(I,0)};function $(O,U){H=w(function(){O(e.unstable_now())},U)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(O){O.callback=null},e.unstable_forceFrameRate=function(O){0>O||125<O?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):z=0<O?Math.floor(1e3/O):5},e.unstable_getCurrentPriorityLevel=function(){return b},e.unstable_next=function(O){switch(b){case 1:case 2:case 3:var U=3;break;default:U=b}var K=b;b=U;try{return O()}finally{b=K}},e.unstable_requestPaint=function(){S=!0},e.unstable_runWithPriority=function(O,U){switch(O){case 1:case 2:case 3:case 4:case 5:break;default:O=3}var K=b;b=O;try{return U()}finally{b=K}},e.unstable_scheduleCallback=function(O,U,K){var X=e.unstable_now();switch(typeof K=="object"&&K!==null?(K=K.delay,K=typeof K=="number"&&0<K?X+K:X):K=X,O){case 1:var M=-1;break;case 2:M=250;break;case 5:M=1073741823;break;case 4:M=1e4;break;default:M=5e3}return M=K+M,O={id:p++,callback:U,priorityLevel:O,startTime:K,expirationTime:M,sortIndex:-1},K>X?(O.sortIndex=K,t(f,O),r(h)===null&&O===r(f)&&(N?(C(H),H=-1):N=!0,$(B,K-X))):(O.sortIndex=M,t(h,O),_||y||(_=!0,R||(R=!0,Z()))),O},e.unstable_shouldYield=j,e.unstable_wrapCallback=function(O){var U=b;return function(){var K=b;b=U;try{return O.apply(this,arguments)}finally{b=K}}}})(xh)),xh}var iy;function Ik(){return iy||(iy=1,gh.exports=zk()),gh.exports}var bh={exports:{}},Tn={};/**
|
||||
* @license React
|
||||
* react-dom.production.js
|
||||
*
|
||||
@@ -30,7 +30,7 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var ry;function Lk(){if(ry)return Tn;ry=1;var e=Mo();function t(h){var f="https://react.dev/errors/"+h;if(1<arguments.length){f+="?args[]="+encodeURIComponent(arguments[1]);for(var p=2;p<arguments.length;p++)f+="&args[]="+encodeURIComponent(arguments[p])}return"Minified React error #"+h+"; visit "+f+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function r(){}var a={d:{f:r,r:function(){throw Error(t(522))},D:r,C:r,L:r,m:r,X:r,S:r,M:r},p:0,findDOMNode:null},s=Symbol.for("react.portal");function o(h,f,p){var g=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:s,key:g==null?null:""+g,children:h,containerInfo:f,implementation:p}}var c=e.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function d(h,f){if(h==="font")return"";if(typeof f=="string")return f==="use-credentials"?f:""}return Tn.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,Tn.createPortal=function(h,f){var p=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!f||f.nodeType!==1&&f.nodeType!==9&&f.nodeType!==11)throw Error(t(299));return o(h,f,null,p)},Tn.flushSync=function(h){var f=c.T,p=a.p;try{if(c.T=null,a.p=2,h)return h()}finally{c.T=f,a.p=p,a.d.f()}},Tn.preconnect=function(h,f){typeof h=="string"&&(f?(f=f.crossOrigin,f=typeof f=="string"?f==="use-credentials"?f:"":void 0):f=null,a.d.C(h,f))},Tn.prefetchDNS=function(h){typeof h=="string"&&a.d.D(h)},Tn.preinit=function(h,f){if(typeof h=="string"&&f&&typeof f.as=="string"){var p=f.as,g=d(p,f.crossOrigin),b=typeof f.integrity=="string"?f.integrity:void 0,y=typeof f.fetchPriority=="string"?f.fetchPriority:void 0;p==="style"?a.d.S(h,typeof f.precedence=="string"?f.precedence:void 0,{crossOrigin:g,integrity:b,fetchPriority:y}):p==="script"&&a.d.X(h,{crossOrigin:g,integrity:b,fetchPriority:y,nonce:typeof f.nonce=="string"?f.nonce:void 0})}},Tn.preinitModule=function(h,f){if(typeof h=="string")if(typeof f=="object"&&f!==null){if(f.as==null||f.as==="script"){var p=d(f.as,f.crossOrigin);a.d.M(h,{crossOrigin:p,integrity:typeof f.integrity=="string"?f.integrity:void 0,nonce:typeof f.nonce=="string"?f.nonce:void 0})}}else f==null&&a.d.M(h)},Tn.preload=function(h,f){if(typeof h=="string"&&typeof f=="object"&&f!==null&&typeof f.as=="string"){var p=f.as,g=d(p,f.crossOrigin);a.d.L(h,p,{crossOrigin:g,integrity:typeof f.integrity=="string"?f.integrity:void 0,nonce:typeof f.nonce=="string"?f.nonce:void 0,type:typeof f.type=="string"?f.type:void 0,fetchPriority:typeof f.fetchPriority=="string"?f.fetchPriority:void 0,referrerPolicy:typeof f.referrerPolicy=="string"?f.referrerPolicy:void 0,imageSrcSet:typeof f.imageSrcSet=="string"?f.imageSrcSet:void 0,imageSizes:typeof f.imageSizes=="string"?f.imageSizes:void 0,media:typeof f.media=="string"?f.media:void 0})}},Tn.preloadModule=function(h,f){if(typeof h=="string")if(f){var p=d(f.as,f.crossOrigin);a.d.m(h,{as:typeof f.as=="string"&&f.as!=="script"?f.as:void 0,crossOrigin:p,integrity:typeof f.integrity=="string"?f.integrity:void 0})}else a.d.m(h)},Tn.requestFormReset=function(h){a.d.r(h)},Tn.unstable_batchedUpdates=function(h,f){return h(f)},Tn.useFormState=function(h,f,p){return c.H.useFormState(h,f,p)},Tn.useFormStatus=function(){return c.H.useHostTransitionStatus()},Tn.version="19.2.7",Tn}var iy;function D_(){if(iy)return xh.exports;iy=1;function e(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),xh.exports=Lk(),xh.exports}/**
|
||||
*/var ay;function Bk(){if(ay)return Tn;ay=1;var e=Oo();function t(h){var f="https://react.dev/errors/"+h;if(1<arguments.length){f+="?args[]="+encodeURIComponent(arguments[1]);for(var p=2;p<arguments.length;p++)f+="&args[]="+encodeURIComponent(arguments[p])}return"Minified React error #"+h+"; visit "+f+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function r(){}var a={d:{f:r,r:function(){throw Error(t(522))},D:r,C:r,L:r,m:r,X:r,S:r,M:r},p:0,findDOMNode:null},s=Symbol.for("react.portal");function o(h,f,p){var g=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null;return{$$typeof:s,key:g==null?null:""+g,children:h,containerInfo:f,implementation:p}}var c=e.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;function d(h,f){if(h==="font")return"";if(typeof f=="string")return f==="use-credentials"?f:""}return Tn.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE=a,Tn.createPortal=function(h,f){var p=2<arguments.length&&arguments[2]!==void 0?arguments[2]:null;if(!f||f.nodeType!==1&&f.nodeType!==9&&f.nodeType!==11)throw Error(t(299));return o(h,f,null,p)},Tn.flushSync=function(h){var f=c.T,p=a.p;try{if(c.T=null,a.p=2,h)return h()}finally{c.T=f,a.p=p,a.d.f()}},Tn.preconnect=function(h,f){typeof h=="string"&&(f?(f=f.crossOrigin,f=typeof f=="string"?f==="use-credentials"?f:"":void 0):f=null,a.d.C(h,f))},Tn.prefetchDNS=function(h){typeof h=="string"&&a.d.D(h)},Tn.preinit=function(h,f){if(typeof h=="string"&&f&&typeof f.as=="string"){var p=f.as,g=d(p,f.crossOrigin),b=typeof f.integrity=="string"?f.integrity:void 0,y=typeof f.fetchPriority=="string"?f.fetchPriority:void 0;p==="style"?a.d.S(h,typeof f.precedence=="string"?f.precedence:void 0,{crossOrigin:g,integrity:b,fetchPriority:y}):p==="script"&&a.d.X(h,{crossOrigin:g,integrity:b,fetchPriority:y,nonce:typeof f.nonce=="string"?f.nonce:void 0})}},Tn.preinitModule=function(h,f){if(typeof h=="string")if(typeof f=="object"&&f!==null){if(f.as==null||f.as==="script"){var p=d(f.as,f.crossOrigin);a.d.M(h,{crossOrigin:p,integrity:typeof f.integrity=="string"?f.integrity:void 0,nonce:typeof f.nonce=="string"?f.nonce:void 0})}}else f==null&&a.d.M(h)},Tn.preload=function(h,f){if(typeof h=="string"&&typeof f=="object"&&f!==null&&typeof f.as=="string"){var p=f.as,g=d(p,f.crossOrigin);a.d.L(h,p,{crossOrigin:g,integrity:typeof f.integrity=="string"?f.integrity:void 0,nonce:typeof f.nonce=="string"?f.nonce:void 0,type:typeof f.type=="string"?f.type:void 0,fetchPriority:typeof f.fetchPriority=="string"?f.fetchPriority:void 0,referrerPolicy:typeof f.referrerPolicy=="string"?f.referrerPolicy:void 0,imageSrcSet:typeof f.imageSrcSet=="string"?f.imageSrcSet:void 0,imageSizes:typeof f.imageSizes=="string"?f.imageSizes:void 0,media:typeof f.media=="string"?f.media:void 0})}},Tn.preloadModule=function(h,f){if(typeof h=="string")if(f){var p=d(f.as,f.crossOrigin);a.d.m(h,{as:typeof f.as=="string"&&f.as!=="script"?f.as:void 0,crossOrigin:p,integrity:typeof f.integrity=="string"?f.integrity:void 0})}else a.d.m(h)},Tn.requestFormReset=function(h){a.d.r(h)},Tn.unstable_batchedUpdates=function(h,f){return h(f)},Tn.useFormState=function(h,f,p){return c.H.useFormState(h,f,p)},Tn.useFormStatus=function(){return c.H.useHostTransitionStatus()},Tn.version="19.2.7",Tn}var sy;function z_(){if(sy)return bh.exports;sy=1;function e(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(t){console.error(t)}}return e(),bh.exports=Bk(),bh.exports}/**
|
||||
* @license React
|
||||
* react-dom-client.production.js
|
||||
*
|
||||
@@ -38,427 +38,432 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var ay;function zk(){if(ay)return Kl;ay=1;var e=Dk(),t=Mo(),r=D_();function a(n){var i="https://react.dev/errors/"+n;if(1<arguments.length){i+="?args[]="+encodeURIComponent(arguments[1]);for(var l=2;l<arguments.length;l++)i+="&args[]="+encodeURIComponent(arguments[l])}return"Minified React error #"+n+"; visit "+i+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function s(n){return!(!n||n.nodeType!==1&&n.nodeType!==9&&n.nodeType!==11)}function o(n){var i=n,l=n;if(n.alternate)for(;i.return;)i=i.return;else{n=i;do i=n,(i.flags&4098)!==0&&(l=i.return),n=i.return;while(n)}return i.tag===3?l:null}function c(n){if(n.tag===13){var i=n.memoizedState;if(i===null&&(n=n.alternate,n!==null&&(i=n.memoizedState)),i!==null)return i.dehydrated}return null}function d(n){if(n.tag===31){var i=n.memoizedState;if(i===null&&(n=n.alternate,n!==null&&(i=n.memoizedState)),i!==null)return i.dehydrated}return null}function h(n){if(o(n)!==n)throw Error(a(188))}function f(n){var i=n.alternate;if(!i){if(i=o(n),i===null)throw Error(a(188));return i!==n?null:n}for(var l=n,u=i;;){var x=l.return;if(x===null)break;var v=x.alternate;if(v===null){if(u=x.return,u!==null){l=u;continue}break}if(x.child===v.child){for(v=x.child;v;){if(v===l)return h(x),n;if(v===u)return h(x),i;v=v.sibling}throw Error(a(188))}if(l.return!==u.return)l=x,u=v;else{for(var T=!1,G=x.child;G;){if(G===l){T=!0,l=x,u=v;break}if(G===u){T=!0,u=x,l=v;break}G=G.sibling}if(!T){for(G=v.child;G;){if(G===l){T=!0,l=v,u=x;break}if(G===u){T=!0,u=v,l=x;break}G=G.sibling}if(!T)throw Error(a(189))}}if(l.alternate!==u)throw Error(a(190))}if(l.tag!==3)throw Error(a(188));return l.stateNode.current===l?n:i}function p(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n;for(n=n.child;n!==null;){if(i=p(n),i!==null)return i;n=n.sibling}return null}var g=Object.assign,b=Symbol.for("react.element"),y=Symbol.for("react.transitional.element"),_=Symbol.for("react.portal"),N=Symbol.for("react.fragment"),S=Symbol.for("react.strict_mode"),w=Symbol.for("react.profiler"),C=Symbol.for("react.consumer"),E=Symbol.for("react.context"),A=Symbol.for("react.forward_ref"),B=Symbol.for("react.suspense"),R=Symbol.for("react.suspense_list"),H=Symbol.for("react.memo"),z=Symbol.for("react.lazy"),Y=Symbol.for("react.activity"),j=Symbol.for("react.memo_cache_sentinel"),I=Symbol.iterator;function Z(n){return n===null||typeof n!="object"?null:(n=I&&n[I]||n["@@iterator"],typeof n=="function"?n:null)}var P=Symbol.for("react.client.reference");function k(n){if(n==null)return null;if(typeof n=="function")return n.$$typeof===P?null:n.displayName||n.name||null;if(typeof n=="string")return n;switch(n){case N:return"Fragment";case w:return"Profiler";case S:return"StrictMode";case B:return"Suspense";case R:return"SuspenseList";case Y:return"Activity"}if(typeof n=="object")switch(n.$$typeof){case _:return"Portal";case E:return n.displayName||"Context";case C:return(n._context.displayName||"Context")+".Consumer";case A:var i=n.render;return n=n.displayName,n||(n=i.displayName||i.name||"",n=n!==""?"ForwardRef("+n+")":"ForwardRef"),n;case H:return i=n.displayName||null,i!==null?i:k(n.type)||"Memo";case z:i=n._payload,n=n._init;try{return k(n(i))}catch{}}return null}var $=Array.isArray,O=t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,U=r.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,K={pending:!1,data:null,method:null,action:null},X=[],M=-1;function L(n){return{current:n}}function F(n){0>M||(n.current=X[M],X[M]=null,M--)}function D(n,i){M++,X[M]=n.current,n.current=i}var V=L(null),q=L(null),Q=L(null),J=L(null);function W(n,i){switch(D(Q,i),D(q,n),D(V,null),i.nodeType){case 9:case 11:n=(n=i.documentElement)&&(n=n.namespaceURI)?v0(n):0;break;default:if(n=i.tagName,i=i.namespaceURI)i=v0(i),n=_0(i,n);else switch(n){case"svg":n=1;break;case"math":n=2;break;default:n=0}}F(V),D(V,n)}function te(){F(V),F(q),F(Q)}function oe(n){n.memoizedState!==null&&D(J,n);var i=V.current,l=_0(i,n.type);i!==l&&(D(q,n),D(V,l))}function fe(n){q.current===n&&(F(V),F(q)),J.current===n&&(F(J),Fl._currentValue=K)}var xe,we;function Ne(n){if(xe===void 0)try{throw Error()}catch(l){var i=l.stack.trim().match(/\n( *(at )?)/);xe=i&&i[1]||"",we=-1<l.stack.indexOf(`
|
||||
*/var ly;function Uk(){if(ly)return Kl;ly=1;var e=Ik(),t=Oo(),r=z_();function a(n){var i="https://react.dev/errors/"+n;if(1<arguments.length){i+="?args[]="+encodeURIComponent(arguments[1]);for(var l=2;l<arguments.length;l++)i+="&args[]="+encodeURIComponent(arguments[l])}return"Minified React error #"+n+"; visit "+i+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}function s(n){return!(!n||n.nodeType!==1&&n.nodeType!==9&&n.nodeType!==11)}function o(n){var i=n,l=n;if(n.alternate)for(;i.return;)i=i.return;else{n=i;do i=n,(i.flags&4098)!==0&&(l=i.return),n=i.return;while(n)}return i.tag===3?l:null}function c(n){if(n.tag===13){var i=n.memoizedState;if(i===null&&(n=n.alternate,n!==null&&(i=n.memoizedState)),i!==null)return i.dehydrated}return null}function d(n){if(n.tag===31){var i=n.memoizedState;if(i===null&&(n=n.alternate,n!==null&&(i=n.memoizedState)),i!==null)return i.dehydrated}return null}function h(n){if(o(n)!==n)throw Error(a(188))}function f(n){var i=n.alternate;if(!i){if(i=o(n),i===null)throw Error(a(188));return i!==n?null:n}for(var l=n,u=i;;){var x=l.return;if(x===null)break;var v=x.alternate;if(v===null){if(u=x.return,u!==null){l=u;continue}break}if(x.child===v.child){for(v=x.child;v;){if(v===l)return h(x),n;if(v===u)return h(x),i;v=v.sibling}throw Error(a(188))}if(l.return!==u.return)l=x,u=v;else{for(var T=!1,G=x.child;G;){if(G===l){T=!0,l=x,u=v;break}if(G===u){T=!0,u=x,l=v;break}G=G.sibling}if(!T){for(G=v.child;G;){if(G===l){T=!0,l=v,u=x;break}if(G===u){T=!0,u=v,l=x;break}G=G.sibling}if(!T)throw Error(a(189))}}if(l.alternate!==u)throw Error(a(190))}if(l.tag!==3)throw Error(a(188));return l.stateNode.current===l?n:i}function p(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n;for(n=n.child;n!==null;){if(i=p(n),i!==null)return i;n=n.sibling}return null}var g=Object.assign,b=Symbol.for("react.element"),y=Symbol.for("react.transitional.element"),_=Symbol.for("react.portal"),N=Symbol.for("react.fragment"),S=Symbol.for("react.strict_mode"),w=Symbol.for("react.profiler"),C=Symbol.for("react.consumer"),E=Symbol.for("react.context"),A=Symbol.for("react.forward_ref"),B=Symbol.for("react.suspense"),R=Symbol.for("react.suspense_list"),H=Symbol.for("react.memo"),z=Symbol.for("react.lazy"),Y=Symbol.for("react.activity"),j=Symbol.for("react.memo_cache_sentinel"),I=Symbol.iterator;function Z(n){return n===null||typeof n!="object"?null:(n=I&&n[I]||n["@@iterator"],typeof n=="function"?n:null)}var P=Symbol.for("react.client.reference");function k(n){if(n==null)return null;if(typeof n=="function")return n.$$typeof===P?null:n.displayName||n.name||null;if(typeof n=="string")return n;switch(n){case N:return"Fragment";case w:return"Profiler";case S:return"StrictMode";case B:return"Suspense";case R:return"SuspenseList";case Y:return"Activity"}if(typeof n=="object")switch(n.$$typeof){case _:return"Portal";case E:return n.displayName||"Context";case C:return(n._context.displayName||"Context")+".Consumer";case A:var i=n.render;return n=n.displayName,n||(n=i.displayName||i.name||"",n=n!==""?"ForwardRef("+n+")":"ForwardRef"),n;case H:return i=n.displayName||null,i!==null?i:k(n.type)||"Memo";case z:i=n._payload,n=n._init;try{return k(n(i))}catch{}}return null}var $=Array.isArray,O=t.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,U=r.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,K={pending:!1,data:null,method:null,action:null},X=[],M=-1;function L(n){return{current:n}}function F(n){0>M||(n.current=X[M],X[M]=null,M--)}function D(n,i){M++,X[M]=n.current,n.current=i}var V=L(null),q=L(null),Q=L(null),J=L(null);function W(n,i){switch(D(Q,i),D(q,n),D(V,null),i.nodeType){case 9:case 11:n=(n=i.documentElement)&&(n=n.namespaceURI)?w0(n):0;break;default:if(n=i.tagName,i=i.namespaceURI)i=w0(i),n=E0(i,n);else switch(n){case"svg":n=1;break;case"math":n=2;break;default:n=0}}F(V),D(V,n)}function te(){F(V),F(q),F(Q)}function oe(n){n.memoizedState!==null&&D(J,n);var i=V.current,l=E0(i,n.type);i!==l&&(D(q,n),D(V,l))}function fe(n){q.current===n&&(F(V),F(q)),J.current===n&&(F(J),Fl._currentValue=K)}var xe,we;function Ne(n){if(xe===void 0)try{throw Error()}catch(l){var i=l.stack.trim().match(/\n( *(at )?)/);xe=i&&i[1]||"",we=-1<l.stack.indexOf(`
|
||||
at`)?" (<anonymous>)":-1<l.stack.indexOf("@")?"@unknown:0:0":""}return`
|
||||
`+xe+n+we}var De=!1;function $e(n,i){if(!n||De)return"";De=!0;var l=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var u={DetermineComponentFrameRoot:function(){try{if(i){var ge=function(){throw Error()};if(Object.defineProperty(ge.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(ge,[])}catch(de){var ce=de}Reflect.construct(n,[],ge)}else{try{ge.call()}catch(de){ce=de}n.call(ge.prototype)}}else{try{throw Error()}catch(de){ce=de}(ge=n())&&typeof ge.catch=="function"&&ge.catch(function(){})}}catch(de){if(de&&ce&&typeof de.stack=="string")return[de.stack,ce.stack]}return[null,null]}};u.DetermineComponentFrameRoot.displayName="DetermineComponentFrameRoot";var x=Object.getOwnPropertyDescriptor(u.DetermineComponentFrameRoot,"name");x&&x.configurable&&Object.defineProperty(u.DetermineComponentFrameRoot,"name",{value:"DetermineComponentFrameRoot"});var v=u.DetermineComponentFrameRoot(),T=v[0],G=v[1];if(T&&G){var ne=T.split(`
|
||||
`),le=G.split(`
|
||||
`);for(x=u=0;u<ne.length&&!ne[u].includes("DetermineComponentFrameRoot");)u++;for(;x<le.length&&!le[x].includes("DetermineComponentFrameRoot");)x++;if(u===ne.length||x===le.length)for(u=ne.length-1,x=le.length-1;1<=u&&0<=x&&ne[u]!==le[x];)x--;for(;1<=u&&0<=x;u--,x--)if(ne[u]!==le[x]){if(u!==1||x!==1)do if(u--,x--,0>x||ne[u]!==le[x]){var he=`
|
||||
`+ne[u].replace(" at new "," at ");return n.displayName&&he.includes("<anonymous>")&&(he=he.replace("<anonymous>",n.displayName)),he}while(1<=u&&0<=x);break}}}finally{De=!1,Error.prepareStackTrace=l}return(l=n?n.displayName||n.name:"")?Ne(l):""}function st(n,i){switch(n.tag){case 26:case 27:case 5:return Ne(n.type);case 16:return Ne("Lazy");case 13:return n.child!==i&&i!==null?Ne("Suspense Fallback"):Ne("Suspense");case 19:return Ne("SuspenseList");case 0:case 15:return $e(n.type,!1);case 11:return $e(n.type.render,!1);case 1:return $e(n.type,!0);case 31:return Ne("Activity");default:return""}}function Rt(n){try{var i="",l=null;do i+=st(n,l),l=n,n=n.return;while(n);return i}catch(u){return`
|
||||
Error generating stack: `+u.message+`
|
||||
`+u.stack}}var Xt=Object.prototype.hasOwnProperty,Pt=e.unstable_scheduleCallback,Kt=e.unstable_cancelCallback,Yn=e.unstable_shouldYield,Nn=e.unstable_requestPaint,ct=e.unstable_now,It=e.unstable_getCurrentPriorityLevel,ue=e.unstable_ImmediatePriority,be=e.unstable_UserBlockingPriority,Oe=e.unstable_NormalPriority,Fe=e.unstable_LowPriority,Ze=e.unstable_IdlePriority,cn=e.log,Sn=e.unstable_setDisableYieldValue,Zt=null,At=null;function Jt(n){if(typeof cn=="function"&&Sn(n),At&&typeof At.setStrictMode=="function")try{At.setStrictMode(Zt,n)}catch{}}var ut=Math.clz32?Math.clz32:Ni,In=Math.log,un=Math.LN2;function Ni(n){return n>>>=0,n===0?32:31-(In(n)/un|0)|0}var nt=256,Xn=262144,On=4194304;function mn(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 re(n,i,l){var u=n.pendingLanes;if(u===0)return 0;var x=0,v=n.suspendedLanes,T=n.pingedLanes;n=n.warmLanes;var G=u&134217727;return G!==0?(u=G&~v,u!==0?x=mn(u):(T&=G,T!==0?x=mn(T):l||(l=G&~n,l!==0&&(x=mn(l))))):(G=u&~v,G!==0?x=mn(G):T!==0?x=mn(T):l||(l=u&~n,l!==0&&(x=mn(l)))),x===0?0:i!==0&&i!==x&&(i&v)===0&&(v=x&-x,l=i&-i,v>=l||v===32&&(l&4194048)!==0)?i:x}function me(n,i){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&i)===0}function Ee(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 Pe(){var n=On;return On<<=1,(On&62914560)===0&&(On=4194304),n}function St(n){for(var i=[],l=0;31>l;l++)i.push(n);return i}function gt(n,i){n.pendingLanes|=i,i!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function Me(n,i,l,u,x,v){var T=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 G=n.entanglements,ne=n.expirationTimes,le=n.hiddenUpdates;for(l=T&~l;0<l;){var he=31-ut(l),ge=1<<he;G[he]=0,ne[he]=-1;var ce=le[he];if(ce!==null)for(le[he]=null,he=0;he<ce.length;he++){var de=ce[he];de!==null&&(de.lane&=-536870913)}l&=~ge}u!==0&&Se(n,u,0),v!==0&&x===0&&n.tag!==0&&(n.suspendedLanes|=v&~(T&~i))}function Se(n,i,l){n.pendingLanes|=i,n.suspendedLanes&=~i;var u=31-ut(i);n.entangledLanes|=i,n.entanglements[u]=n.entanglements[u]|1073741824|l&261930}function Ue(n,i){var l=n.entangledLanes|=i;for(n=n.entanglements;l;){var u=31-ut(l),x=1<<u;x&i|n[u]&i&&(n[u]|=i),l&=~x}}function Bt(n,i){var l=i&-i;return l=(l&42)!==0?1:Mt(l),(l&(n.suspendedLanes|i))!==0?0:l}function Mt(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 yr(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function Si(){var n=U.p;return n!==0?n:(n=window.event,n===void 0?32:F0(n.type))}function ki(n,i){var l=U.p;try{return U.p=n,i()}finally{U.p=l}}var lr=Math.random().toString(36).slice(2),Ut="__reactFiber$"+lr,pn="__reactProps$"+lr,vr="__reactContainer$"+lr,Ci="__reactEvents$"+lr,ga="__reactListeners$"+lr,Ti="__reactHandles$"+lr,ts="__reactResources$"+lr,Wr="__reactMarker$"+lr;function xa(n){delete n[Ut],delete n[pn],delete n[Ci],delete n[ga],delete n[Ti]}function bn(n){var i=n[Ut];if(i)return i;for(var l=n.parentNode;l;){if(i=l[vr]||l[Ut]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=T0(n);n!==null;){if(l=n[Ut])return l;n=T0(n)}return i}n=l,l=n.parentNode}return null}function _r(n){if(n=n[Ut]||n[vr]){var i=n.tag;if(i===5||i===6||i===13||i===31||i===26||i===27||i===3)return n}return null}function wr(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n.stateNode;throw Error(a(33))}function Br(n){var i=n[ts];return i||(i=n[ts]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function Ft(n){n[Wr]=!0}var ns=new Set,Jr={};function Er(n,i){ye(n,i),ye(n+"Capture",i)}function ye(n,i){for(Jr[n]=i,n=0;n<i.length;n++)ns.add(i[n])}var Le=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u2
Line truncated
|
||||
`).replace(WS,"")}function b0(n,i){return i=x0(i),x0(n)===i}function Et(n,i,l,u,x,v){switch(l){case"children":typeof u=="string"?i==="body"||i==="textarea"&&u===""||qe(n,u):(typeof u=="number"||typeof u=="bigint")&&i!=="body"&&qe(n,""+u);break;case"className":Rn(n,"class",u);break;case"tabIndex":Rn(n,"tabindex",u);break;case"dir":case"role":case"viewBox":case"width":case"height":Rn(n,l,u);break;case"style":xt(n,u,v);break;case"data":if(i!=="object"){Rn(n,"data",u);break}case"src":case"href":if(u===""&&(i!=="a"||l!=="href")){n.removeAttribute(l);break}if(u==null||typeof u=="function"||typeof u=="symbol"||typeof u=="boolean"){n.removeAttribute(l);break}u=ji(""+u),n.setAttribute(l,u);break;case"action":case"formAction":if(typeof u=="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 v=="function"&&(l==="formAction"?(i!=="input"&&Et(n,i,"name",x.name,x,null),Et(n,i,"formEncType",x.formEncType,x,null),Et(n,i,"formMethod",x.formMethod,x,null),Et(n,i,"formTarget",x.formTarget,x,null)):(Et(n,i,"encType",x.encType,x,null),Et(n,i,"method",x.method,x,null),Et(n,i,"target",x.target,x,null)));if(u==null||typeof u=="symbol"||typeof u=="boolean"){n.removeAttribute(l);break}u=ji(""+u),n.setAttribute(l,u);break;case"onClick":u!=null&&(n.onclick=_e);break;case"onScroll":u!=null&&et("scroll",n);break;case"onScrollEnd":u!=null&&et("scrollend",n);break;case"dangerouslySetInnerHTML":if(u!=null){if(typeof u!="object"||!("__html"in u))throw Error(a(61));if(l=u.__html,l!=null){if(x.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=u&&typeof u!="function"&&typeof u!="symbol";break;case"muted":n.muted=u&&typeof u!="function"&&typeof u!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(u==null||typeof u=="function"||typeof u=="boolean"||typeof u=="symbol"){n.removeAttribute("xlink:href");break}l=ji(""+u),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":u!=null&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,""+u):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":u&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":u===!0?n.setAttribute(l,""):u!==!1&&u!=null&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,u):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":u!=null&&typeof u!="function"&&typeof u!="symbol"&&!isNaN(u)&&1<=u?n.setAttribute(l,u):n.removeAttribute(l);break;case"rowSpan":case"start":u==null||typeof u=="function"||typeof u=="symbol"||isNaN(u)?n.removeAttribute(l):n.setAttribute(l,u);break;case"popover":et("beforetoggle",n),et("toggle",n),gn(n,"popover",u);break;case"xlinkActuate":kn(n,"http://www.w3.org/1999/xlink","xlink:actuate",u);break;case"xlinkArcrole":kn(n,"http://www.w3.org/1999/xlink","xlink:arcrole",u);break;case"xlinkRole":kn(n,"http://www.w3.org/1999/xlink","xlink:role",u);break;case"xlinkShow":kn(n,"http://www.w3.org/1999/xlink","xlink:show",u);break;case"xlinkTitle":kn(n,"http://www.w3.org/1999/xlink","xlink:title",u);break;case"xlinkType":kn(n,"http://www.w3.org/1999/xlink","xlink:type",u);break;case"xmlBase":kn(n,"http://www.w3.org/XML/1998/namespace","xml:base",u);break;case"xmlLang":kn(n,"http://www.w3.org/XML/1998/namespace","xml:lang",u);break;case"xmlSpace":kn(n,"http://www.w3.org/XML/1998/namespace","xml:space",u);break;case"is":gn(n,"is",u);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=ei.get(l)||l,gn(n,l,u))}}function Xf(n,i,l,u,x,v){switch(l){case"style":xt(n,u,v);break;case"dangerouslySetInnerHTML":if(u!=null){if(typeof u!="object"||!("__html"in u))throw Error(a(61));if(l=u.__html,l!=null){if(x.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof u=="string"?qe(n,u):(typeof u=="number"||typeof u=="bigint")&&qe(n,""+u);break;case"onScroll":u!=null&&et("scroll",n);break;case"onScrollEnd":u!=null&&et("scrollend",n);break;case"onClick":u!=null&&(n.onclick=_e);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne
Line truncated
|
||||
`+u.stack}}var Xt=Object.prototype.hasOwnProperty,Ft=e.unstable_scheduleCallback,Kt=e.unstable_cancelCallback,Yn=e.unstable_shouldYield,Nn=e.unstable_requestPaint,ct=e.unstable_now,It=e.unstable_getCurrentPriorityLevel,ue=e.unstable_ImmediatePriority,be=e.unstable_UserBlockingPriority,Oe=e.unstable_NormalPriority,Fe=e.unstable_LowPriority,Ze=e.unstable_IdlePriority,cn=e.log,Sn=e.unstable_setDisableYieldValue,Zt=null,At=null;function Jt(n){if(typeof cn=="function"&&Sn(n),At&&typeof At.setStrictMode=="function")try{At.setStrictMode(Zt,n)}catch{}}var ut=Math.clz32?Math.clz32:Si,In=Math.log,un=Math.LN2;function Si(n){return n>>>=0,n===0?32:31-(In(n)/un|0)|0}var nt=256,Xn=262144,On=4194304;function mn(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 re(n,i,l){var u=n.pendingLanes;if(u===0)return 0;var x=0,v=n.suspendedLanes,T=n.pingedLanes;n=n.warmLanes;var G=u&134217727;return G!==0?(u=G&~v,u!==0?x=mn(u):(T&=G,T!==0?x=mn(T):l||(l=G&~n,l!==0&&(x=mn(l))))):(G=u&~v,G!==0?x=mn(G):T!==0?x=mn(T):l||(l=u&~n,l!==0&&(x=mn(l)))),x===0?0:i!==0&&i!==x&&(i&v)===0&&(v=x&-x,l=i&-i,v>=l||v===32&&(l&4194048)!==0)?i:x}function me(n,i){return(n.pendingLanes&~(n.suspendedLanes&~n.pingedLanes)&i)===0}function Ee(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 Pe(){var n=On;return On<<=1,(On&62914560)===0&&(On=4194304),n}function St(n){for(var i=[],l=0;31>l;l++)i.push(n);return i}function gt(n,i){n.pendingLanes|=i,i!==268435456&&(n.suspendedLanes=0,n.pingedLanes=0,n.warmLanes=0)}function Me(n,i,l,u,x,v){var T=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 G=n.entanglements,ne=n.expirationTimes,le=n.hiddenUpdates;for(l=T&~l;0<l;){var he=31-ut(l),ge=1<<he;G[he]=0,ne[he]=-1;var ce=le[he];if(ce!==null)for(le[he]=null,he=0;he<ce.length;he++){var de=ce[he];de!==null&&(de.lane&=-536870913)}l&=~ge}u!==0&&Se(n,u,0),v!==0&&x===0&&n.tag!==0&&(n.suspendedLanes|=v&~(T&~i))}function Se(n,i,l){n.pendingLanes|=i,n.suspendedLanes&=~i;var u=31-ut(i);n.entangledLanes|=i,n.entanglements[u]=n.entanglements[u]|1073741824|l&261930}function Ue(n,i){var l=n.entangledLanes|=i;for(n=n.entanglements;l;){var u=31-ut(l),x=1<<u;x&i|n[u]&i&&(n[u]|=i),l&=~x}}function Bt(n,i){var l=i&-i;return l=(l&42)!==0?1:Mt(l),(l&(n.suspendedLanes|i))!==0?0:l}function Mt(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 vr(n){return n&=-n,2<n?8<n?(n&134217727)!==0?32:268435456:8:2}function ki(){var n=U.p;return n!==0?n:(n=window.event,n===void 0?32:V0(n.type))}function Ci(n,i){var l=U.p;try{return U.p=n,i()}finally{U.p=l}}var lr=Math.random().toString(36).slice(2),Ut="__reactFiber$"+lr,pn="__reactProps$"+lr,_r="__reactContainer$"+lr,Ti="__reactEvents$"+lr,ga="__reactListeners$"+lr,Ai="__reactHandles$"+lr,ts="__reactResources$"+lr,Wr="__reactMarker$"+lr;function xa(n){delete n[Ut],delete n[pn],delete n[Ti],delete n[ga],delete n[Ai]}function bn(n){var i=n[Ut];if(i)return i;for(var l=n.parentNode;l;){if(i=l[_r]||l[Ut]){if(l=i.alternate,i.child!==null||l!==null&&l.child!==null)for(n=M0(n);n!==null;){if(l=n[Ut])return l;n=M0(n)}return i}n=l,l=n.parentNode}return null}function wr(n){if(n=n[Ut]||n[_r]){var i=n.tag;if(i===5||i===6||i===13||i===31||i===26||i===27||i===3)return n}return null}function Er(n){var i=n.tag;if(i===5||i===26||i===27||i===6)return n.stateNode;throw Error(a(33))}function Br(n){var i=n[ts];return i||(i=n[ts]={hoistableStyles:new Map,hoistableScripts:new Map}),i}function Gt(n){n[Wr]=!0}var ns=new Set,Jr={};function Nr(n,i){ye(n,i),ye(n+"Capture",i)}function ye(n,i){for(Jr[n]=i,n=0;n<i.length;n++)ns.add(i[n])}var Le=RegExp("^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u2
Line truncated
|
||||
`).replace(tk,"")}function v0(n,i){return i=y0(i),y0(n)===i}function Et(n,i,l,u,x,v){switch(l){case"children":typeof u=="string"?i==="body"||i==="textarea"&&u===""||qe(n,u):(typeof u=="number"||typeof u=="bigint")&&i!=="body"&&qe(n,""+u);break;case"className":Rn(n,"class",u);break;case"tabIndex":Rn(n,"tabindex",u);break;case"dir":case"role":case"viewBox":case"width":case"height":Rn(n,l,u);break;case"style":xt(n,u,v);break;case"data":if(i!=="object"){Rn(n,"data",u);break}case"src":case"href":if(u===""&&(i!=="a"||l!=="href")){n.removeAttribute(l);break}if(u==null||typeof u=="function"||typeof u=="symbol"||typeof u=="boolean"){n.removeAttribute(l);break}u=Di(""+u),n.setAttribute(l,u);break;case"action":case"formAction":if(typeof u=="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 v=="function"&&(l==="formAction"?(i!=="input"&&Et(n,i,"name",x.name,x,null),Et(n,i,"formEncType",x.formEncType,x,null),Et(n,i,"formMethod",x.formMethod,x,null),Et(n,i,"formTarget",x.formTarget,x,null)):(Et(n,i,"encType",x.encType,x,null),Et(n,i,"method",x.method,x,null),Et(n,i,"target",x.target,x,null)));if(u==null||typeof u=="symbol"||typeof u=="boolean"){n.removeAttribute(l);break}u=Di(""+u),n.setAttribute(l,u);break;case"onClick":u!=null&&(n.onclick=_e);break;case"onScroll":u!=null&&et("scroll",n);break;case"onScrollEnd":u!=null&&et("scrollend",n);break;case"dangerouslySetInnerHTML":if(u!=null){if(typeof u!="object"||!("__html"in u))throw Error(a(61));if(l=u.__html,l!=null){if(x.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"multiple":n.multiple=u&&typeof u!="function"&&typeof u!="symbol";break;case"muted":n.muted=u&&typeof u!="function"&&typeof u!="symbol";break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"defaultValue":case"defaultChecked":case"innerHTML":case"ref":break;case"autoFocus":break;case"xlinkHref":if(u==null||typeof u=="function"||typeof u=="boolean"||typeof u=="symbol"){n.removeAttribute("xlink:href");break}l=Di(""+u),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":u!=null&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,""+u):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":u&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,""):n.removeAttribute(l);break;case"capture":case"download":u===!0?n.setAttribute(l,""):u!==!1&&u!=null&&typeof u!="function"&&typeof u!="symbol"?n.setAttribute(l,u):n.removeAttribute(l);break;case"cols":case"rows":case"size":case"span":u!=null&&typeof u!="function"&&typeof u!="symbol"&&!isNaN(u)&&1<=u?n.setAttribute(l,u):n.removeAttribute(l);break;case"rowSpan":case"start":u==null||typeof u=="function"||typeof u=="symbol"||isNaN(u)?n.removeAttribute(l):n.setAttribute(l,u);break;case"popover":et("beforetoggle",n),et("toggle",n),gn(n,"popover",u);break;case"xlinkActuate":kn(n,"http://www.w3.org/1999/xlink","xlink:actuate",u);break;case"xlinkArcrole":kn(n,"http://www.w3.org/1999/xlink","xlink:arcrole",u);break;case"xlinkRole":kn(n,"http://www.w3.org/1999/xlink","xlink:role",u);break;case"xlinkShow":kn(n,"http://www.w3.org/1999/xlink","xlink:show",u);break;case"xlinkTitle":kn(n,"http://www.w3.org/1999/xlink","xlink:title",u);break;case"xlinkType":kn(n,"http://www.w3.org/1999/xlink","xlink:type",u);break;case"xmlBase":kn(n,"http://www.w3.org/XML/1998/namespace","xml:base",u);break;case"xmlLang":kn(n,"http://www.w3.org/XML/1998/namespace","xml:lang",u);break;case"xmlSpace":kn(n,"http://www.w3.org/XML/1998/namespace","xml:space",u);break;case"is":gn(n,"is",u);break;case"innerText":case"textContent":break;default:(!(2<l.length)||l[0]!=="o"&&l[0]!=="O"||l[1]!=="n"&&l[1]!=="N")&&(l=ei.get(l)||l,gn(n,l,u))}}function Kf(n,i,l,u,x,v){switch(l){case"style":xt(n,u,v);break;case"dangerouslySetInnerHTML":if(u!=null){if(typeof u!="object"||!("__html"in u))throw Error(a(61));if(l=u.__html,l!=null){if(x.children!=null)throw Error(a(60));n.innerHTML=l}}break;case"children":typeof u=="string"?qe(n,u):(typeof u=="number"||typeof u=="bigint")&&qe(n,""+u);break;case"onScroll":u!=null&&et("scroll",n);break;case"onScrollEnd":u!=null&&et("scrollend",n);break;case"onClick":u!=null&&(n.onclick=_e);break;case"suppressContentEditableWarning":case"suppressHydrationWarning":case"inne
Line truncated
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const L_=(...e)=>e.filter((t,r,a)=>!!t&&t.trim()!==""&&a.indexOf(t)===r).join(" ").trim();/**
|
||||
*/const I_=(...e)=>e.filter((t,r,a)=>!!t&&t.trim()!==""&&a.indexOf(t)===r).join(" ").trim();/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Uk=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();/**
|
||||
*/const qk=e=>e.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase();/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Hk=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,r,a)=>a?a.toUpperCase():r.toLowerCase());/**
|
||||
*/const Pk=e=>e.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,r,a)=>a?a.toUpperCase():r.toLowerCase());/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const ly=e=>{const t=Hk(e);return t.charAt(0).toUpperCase()+t.slice(1)};/**
|
||||
*/const cy=e=>{const t=Pk(e);return t.charAt(0).toUpperCase()+t.slice(1)};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/var $k={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
|
||||
*/var Fk={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const qk=e=>{for(const t in e)if(t.startsWith("aria-")||t==="role"||t==="title")return!0;return!1};/**
|
||||
*/const Gk=e=>{for(const t in e)if(t.startsWith("aria-")||t==="role"||t==="title")return!0;return!1};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Pk=ee.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:r=2,absoluteStrokeWidth:a,className:s="",children:o,iconNode:c,...d},h)=>ee.createElement("svg",{ref:h,...$k,width:t,height:t,stroke:e,strokeWidth:a?Number(r)*24/Number(t):r,className:L_("lucide",s),...!o&&!qk(d)&&{"aria-hidden":"true"},...d},[...c.map(([f,p])=>ee.createElement(f,p)),...Array.isArray(o)?o:[o]]));/**
|
||||
*/const Vk=ee.forwardRef(({color:e="currentColor",size:t=24,strokeWidth:r=2,absoluteStrokeWidth:a,className:s="",children:o,iconNode:c,...d},h)=>ee.createElement("svg",{ref:h,...Fk,width:t,height:t,stroke:e,strokeWidth:a?Number(r)*24/Number(t):r,className:I_("lucide",s),...!o&&!Gk(d)&&{"aria-hidden":"true"},...d},[...c.map(([f,p])=>ee.createElement(f,p)),...Array.isArray(o)?o:[o]]));/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Te=(e,t)=>{const r=ee.forwardRef(({className:a,...s},o)=>ee.createElement(Pk,{ref:o,iconNode:t,className:L_(`lucide-${Uk(ly(e))}`,`lucide-${e}`,a),...s}));return r.displayName=ly(e),r};/**
|
||||
*/const Ce=(e,t)=>{const r=ee.forwardRef(({className:a,...s},o)=>ee.createElement(Vk,{ref:o,iconNode:t,className:I_(`lucide-${qk(cy(e))}`,`lucide-${e}`,a),...s}));return r.displayName=cy(e),r};/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Fk=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],wp=Te("arrow-left",Fk);/**
|
||||
*/const Yk=[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]],Ep=Ce("arrow-left",Yk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Gk=[["path",{d:"M7 7h10v10",key:"1tivn9"}],["path",{d:"M7 17 17 7",key:"1vkiza"}]],z_=Te("arrow-up-right",Gk);/**
|
||||
*/const Xk=[["path",{d:"M7 7h10v10",key:"1tivn9"}],["path",{d:"M7 17 17 7",key:"1vkiza"}]],B_=Ce("arrow-up-right",Xk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Vk=[["path",{d:"m5 12 7-7 7 7",key:"hav0vg"}],["path",{d:"M12 19V5",key:"x0mq9r"}]],Yk=Te("arrow-up",Vk);/**
|
||||
*/const Kk=[["path",{d:"m5 12 7-7 7 7",key:"hav0vg"}],["path",{d:"M12 19V5",key:"x0mq9r"}]],Zk=Ce("arrow-up",Kk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Xk=[["path",{d:"M4.929 4.929 19.07 19.071",key:"196cmz"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],I_=Te("ban",Xk);/**
|
||||
*/const Qk=[["path",{d:"M4.929 4.929 19.07 19.071",key:"196cmz"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],U_=Ce("ban",Qk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Kk=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0",key:"vwvbt9"}],["path",{d:"M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742",key:"178tsu"}],["path",{d:"m2 2 20 20",key:"1ooewy"}],["path",{d:"M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05",key:"1hqiys"}]],Zk=Te("bell-off",Kk);/**
|
||||
*/const Wk=[["path",{d:"M10.268 21a2 2 0 0 0 3.464 0",key:"vwvbt9"}],["path",{d:"M17 17H4a1 1 0 0 1-.74-1.673C4.59 13.956 6 12.499 6 8a6 6 0 0 1 .258-1.742",key:"178tsu"}],["path",{d:"m2 2 20 20",key:"1ooewy"}],["path",{d:"M8.668 3.01A6 6 0 0 1 18 8c0 2.687.77 4.653 1.707 6.05",key:"1hqiys"}]],Jk=Ce("bell-off",Wk);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Qk=[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]],Oo=Te("bot",Qk);/**
|
||||
*/const eC=[["path",{d:"M12 8V4H8",key:"hb8ula"}],["rect",{width:"16",height:"12",x:"4",y:"8",rx:"2",key:"enze0r"}],["path",{d:"M2 14h2",key:"vft8re"}],["path",{d:"M20 14h2",key:"4cs60a"}],["path",{d:"M15 13v2",key:"1xurst"}],["path",{d:"M9 13v2",key:"rq6x2g"}]],Ro=Ce("bot",eC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Wk=[["path",{d:"M12 18V5",key:"adv99a"}],["path",{d:"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4",key:"1e3is1"}],["path",{d:"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5",key:"1gqd8o"}],["path",{d:"M17.997 5.125a4 4 0 0 1 2.526 5.77",key:"iwvgf7"}],["path",{d:"M18 18a4 4 0 0 0 2-7.464",key:"efp6ie"}],["path",{d:"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517",key:"1gq6am"}],["path",{d:"M6 18a4 4 0 0 1-2-7.464",key:"k1g0md"}],["path",{d:"M6.003 5.125a4 4 0 0 0-2.526 5.77",key:"q97ue3"}]],B_=Te("brain",Wk);/**
|
||||
*/const tC=[["path",{d:"M12 18V5",key:"adv99a"}],["path",{d:"M15 13a4.17 4.17 0 0 1-3-4 4.17 4.17 0 0 1-3 4",key:"1e3is1"}],["path",{d:"M17.598 6.5A3 3 0 1 0 12 5a3 3 0 1 0-5.598 1.5",key:"1gqd8o"}],["path",{d:"M17.997 5.125a4 4 0 0 1 2.526 5.77",key:"iwvgf7"}],["path",{d:"M18 18a4 4 0 0 0 2-7.464",key:"efp6ie"}],["path",{d:"M19.967 17.483A4 4 0 1 1 12 18a4 4 0 1 1-7.967-.517",key:"1gq6am"}],["path",{d:"M6 18a4 4 0 0 1-2-7.464",key:"k1g0md"}],["path",{d:"M6.003 5.125a4 4 0 0 0-2.526 5.77",key:"q97ue3"}]],H_=Ce("brain",tC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const Jk=[["path",{d:"M16 14v2.2l1.6 1",key:"fo4ql5"}],["path",{d:"M16 2v4",key:"4m81vk"}],["path",{d:"M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5",key:"1osxxc"}],["path",{d:"M3 10h5",key:"r794hk"}],["path",{d:"M8 2v4",key:"1cmpym"}],["circle",{cx:"16",cy:"16",r:"6",key:"qoo3c4"}]],eC=Te("calendar-clock",Jk);/**
|
||||
*/const nC=[["path",{d:"M10 12h4",key:"a56b0p"}],["path",{d:"M10 8h4",key:"1sr2af"}],["path",{d:"M14 21v-3a2 2 0 0 0-4 0v3",key:"1rgiei"}],["path",{d:"M6 10H4a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-2",key:"secmi2"}],["path",{d:"M6 21V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v16",key:"16ra0t"}]],$_=Ce("building-2",nC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const tC=[["path",{d:"M18 6 7 17l-5-5",key:"116fxf"}],["path",{d:"m22 10-7.5 7.5L13 16",key:"ke71qq"}]],nC=Te("check-check",tC);/**
|
||||
*/const rC=[["path",{d:"M16 14v2.2l1.6 1",key:"fo4ql5"}],["path",{d:"M16 2v4",key:"4m81vk"}],["path",{d:"M21 7.5V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h3.5",key:"1osxxc"}],["path",{d:"M3 10h5",key:"r794hk"}],["path",{d:"M8 2v4",key:"1cmpym"}],["circle",{cx:"16",cy:"16",r:"6",key:"qoo3c4"}]],iC=Ce("calendar-clock",rC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const rC=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],Vs=Te("check",rC);/**
|
||||
*/const aC=[["path",{d:"M18 6 7 17l-5-5",key:"116fxf"}],["path",{d:"m22 10-7.5 7.5L13 16",key:"ke71qq"}]],sC=Ce("check-check",aC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const iC=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],po=Te("chevron-down",iC);/**
|
||||
*/const lC=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],Vs=Ce("check",lC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const aC=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],sC=Te("chevron-right",aC);/**
|
||||
*/const oC=[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]],po=Ce("chevron-down",oC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const lC=[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]],U_=Te("chevron-up",lC);/**
|
||||
*/const cC=[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]],uC=Ce("chevron-right",cC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const oC=[["path",{d:"m7 15 5 5 5-5",key:"1hf1tw"}],["path",{d:"m7 9 5-5 5 5",key:"sgt6xg"}]],cC=Te("chevrons-up-down",oC);/**
|
||||
*/const dC=[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]],q_=Ce("chevron-up",dC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const uC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],Fu=Te("circle-alert",uC);/**
|
||||
*/const fC=[["path",{d:"m7 15 5 5 5-5",key:"1hf1tw"}],["path",{d:"m7 9 5-5 5 5",key:"sgt6xg"}]],hC=Ce("chevrons-up-down",fC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const dC=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]],H_=Te("circle-check-big",dC);/**
|
||||
*/const mC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"12",x2:"12",y1:"8",y2:"12",key:"1pkeuh"}],["line",{x1:"12",x2:"12.01",y1:"16",y2:"16",key:"4dfq90"}]],Gu=Ce("circle-alert",mC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const fC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],Eu=Te("circle-check",fC);/**
|
||||
*/const pC=[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]],P_=Ce("circle-check-big",pC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const hC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}]],mC=Te("circle-dot",hC);/**
|
||||
*/const gC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],Nu=Ce("circle-check",gC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const pC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],gC=Te("circle-question-mark",pC);/**
|
||||
*/const xC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}]],bC=Ce("circle-dot",xC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const xC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"9",x2:"15",y1:"15",y2:"9",key:"1dfufj"}]],bC=Te("circle-slash",xC);/**
|
||||
*/const yC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3",key:"1u773s"}],["path",{d:"M12 17h.01",key:"p32p05"}]],vC=Ce("circle-question-mark",yC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const yC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],$_=Te("circle",yC);/**
|
||||
*/const _C=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"9",x2:"15",y1:"15",y2:"9",key:"1dfufj"}]],wC=Ce("circle-slash",_C);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const vC=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1",key:"tgr4d6"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2",key:"116196"}],["path",{d:"M12 11h4",key:"1jrz19"}],["path",{d:"M12 16h4",key:"n85exb"}],["path",{d:"M8 11h.01",key:"1dfujw"}],["path",{d:"M8 16h.01",key:"18s6g9"}]],q_=Te("clipboard-list",vC);/**
|
||||
*/const EC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],F_=Ce("circle",EC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const _C=[["path",{d:"M12 6v6l4 2",key:"mmk7yg"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],P_=Te("clock",_C);/**
|
||||
*/const NC=[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1",key:"tgr4d6"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2",key:"116196"}],["path",{d:"M12 11h4",key:"1jrz19"}],["path",{d:"M12 16h4",key:"n85exb"}],["path",{d:"M8 11h.01",key:"1dfujw"}],["path",{d:"M8 16h.01",key:"18s6g9"}]],G_=Ce("clipboard-list",NC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const wC=[["path",{d:"m16 18 6-6-6-6",key:"eg8j8"}],["path",{d:"m8 6-6 6 6 6",key:"ppft3o"}]],EC=Te("code",wC);/**
|
||||
*/const SC=[["path",{d:"M12 6v6l4 2",key:"mmk7yg"}],["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}]],V_=Ce("clock",SC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const NC=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],go=Te("copy",NC);/**
|
||||
*/const kC=[["path",{d:"m16 18 6-6-6-6",key:"eg8j8"}],["path",{d:"m8 6-6 6 6 6",key:"ppft3o"}]],CC=Ce("code",kC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const SC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"22",x2:"18",y1:"12",y2:"12",key:"l9bcsi"}],["line",{x1:"6",x2:"2",y1:"12",y2:"12",key:"13hhkx"}],["line",{x1:"12",x2:"12",y1:"6",y2:"2",key:"10w3f3"}],["line",{x1:"12",x2:"12",y1:"22",y2:"18",key:"15g9kq"}]],Gu=Te("crosshair",SC);/**
|
||||
*/const TC=[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]],go=Ce("copy",TC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const kC=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],oy=Te("external-link",kC);/**
|
||||
*/const AC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["line",{x1:"22",x2:"18",y1:"12",y2:"12",key:"l9bcsi"}],["line",{x1:"6",x2:"2",y1:"12",y2:"12",key:"13hhkx"}],["line",{x1:"12",x2:"12",y1:"6",y2:"2",key:"10w3f3"}],["line",{x1:"12",x2:"12",y1:"22",y2:"18",key:"15g9kq"}]],Vu=Ce("crosshair",AC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const CC=[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],TC=Te("eye",CC);/**
|
||||
*/const MC=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],uy=Ce("external-link",MC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const AC=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]],MC=Te("file-text",AC);/**
|
||||
*/const OC=[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]],RC=Ce("eye",OC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const OC=[["path",{d:"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528",key:"1jaruq"}]],F_=Te("flag",OC);/**
|
||||
*/const jC=[["path",{d:"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",key:"1oefj6"}],["path",{d:"M14 2v5a1 1 0 0 0 1 1h5",key:"wfsgrz"}],["path",{d:"M10 9H8",key:"b1mrlr"}],["path",{d:"M16 13H8",key:"t4e002"}],["path",{d:"M16 17H8",key:"z1uh3a"}]],DC=Ce("file-text",jC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const RC=[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M6 21V9a9 9 0 0 0 9 9",key:"7kw0sc"}]],jC=Te("git-merge",RC);/**
|
||||
*/const LC=[["path",{d:"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528",key:"1jaruq"}]],Y_=Ce("flag",LC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const DC=[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7",key:"1yeb86"}],["line",{x1:"6",x2:"6",y1:"9",y2:"21",key:"rroup"}]],LC=Te("git-pull-request",DC);/**
|
||||
*/const zC=[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M6 21V9a9 9 0 0 0 9 9",key:"7kw0sc"}]],IC=Ce("git-merge",zC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const zC=[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]],IC=Te("github",zC);/**
|
||||
*/const BC=[["circle",{cx:"18",cy:"18",r:"3",key:"1xkwt0"}],["circle",{cx:"6",cy:"6",r:"3",key:"1lh9wr"}],["path",{d:"M13 6h3a2 2 0 0 1 2 2v7",key:"1yeb86"}],["line",{x1:"6",x2:"6",y1:"9",y2:"21",key:"rroup"}]],UC=Ce("git-pull-request",BC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const BC=[["path",{d:"m22 13.29-3.33-10a.42.42 0 0 0-.14-.18.38.38 0 0 0-.22-.11.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18l-2.26 6.67H8.32L6.1 3.26a.42.42 0 0 0-.1-.18.38.38 0 0 0-.26-.08.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18L2 13.29a.74.74 0 0 0 .27.83L12 21l9.69-6.88a.71.71 0 0 0 .31-.83Z",key:"148pdi"}]],UC=Te("gitlab",BC);/**
|
||||
*/const HC=[["path",{d:"M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4",key:"tonef"}],["path",{d:"M9 18c-4.51 2-5-2-7-2",key:"9comsn"}]],$C=Ce("github",HC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const HC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],G_=Te("globe",HC);/**
|
||||
*/const qC=[["path",{d:"m22 13.29-3.33-10a.42.42 0 0 0-.14-.18.38.38 0 0 0-.22-.11.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18l-2.26 6.67H8.32L6.1 3.26a.42.42 0 0 0-.1-.18.38.38 0 0 0-.26-.08.39.39 0 0 0-.23.07.42.42 0 0 0-.14.18L2 13.29a.74.74 0 0 0 .27.83L12 21l9.69-6.88a.71.71 0 0 0 .31-.83Z",key:"148pdi"}]],PC=Ce("gitlab",qC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const $C=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]],Ys=Te("history",$C);/**
|
||||
*/const FC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]],X_=Ce("globe",FC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const qC=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]],PC=Te("image",qC);/**
|
||||
*/const GC=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}],["path",{d:"M12 7v5l4 2",key:"1fdv2h"}]],Ys=Ce("history",GC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const FC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]],GC=Te("info",FC);/**
|
||||
*/const VC=[["rect",{width:"18",height:"18",x:"3",y:"3",rx:"2",ry:"2",key:"1m3agn"}],["circle",{cx:"9",cy:"9",r:"2",key:"af1f0g"}],["path",{d:"m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21",key:"1xmnt7"}]],YC=Ce("image",VC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const VC=[["path",{d:"M13 5h8",key:"a7qcls"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 19h8",key:"c3s6r1"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["rect",{x:"3",y:"4",width:"6",height:"6",rx:"1",key:"cif1o7"}]],YC=Te("list-todo",VC);/**
|
||||
*/const XC=[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]],KC=Ce("info",XC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const XC=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]],Ps=Te("loader-circle",XC);/**
|
||||
*/const ZC=[["path",{d:"M13 5h8",key:"a7qcls"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 19h8",key:"c3s6r1"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["rect",{x:"3",y:"4",width:"6",height:"6",rx:"1",key:"cif1o7"}]],QC=Ce("list-todo",ZC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const KC=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]],ZC=Te("lock",KC);/**
|
||||
*/const WC=[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]],Ps=Ce("loader-circle",WC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const QC=[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]],WC=Te("log-out",QC);/**
|
||||
*/const JC=[["rect",{width:"18",height:"11",x:"3",y:"11",rx:"2",ry:"2",key:"1w4ew1"}],["path",{d:"M7 11V7a5 5 0 0 1 10 0v4",key:"fwvmzm"}]],eT=Ce("lock",JC);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const JC=[["path",{d:"m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7",key:"132q7q"}],["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2",key:"izxlao"}]],Ep=Te("mail",JC);/**
|
||||
*/const tT=[["path",{d:"m16 17 5-5-5-5",key:"1bji2h"}],["path",{d:"M21 12H9",key:"dn1m92"}],["path",{d:"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4",key:"1uf3rs"}]],nT=Ce("log-out",tT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const eT=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719",key:"1sd12s"}]],bh=Te("message-circle",eT);/**
|
||||
*/const rT=[["path",{d:"m22 7-8.991 5.727a2 2 0 0 1-2.009 0L2 7",key:"132q7q"}],["rect",{x:"2",y:"4",width:"20",height:"16",rx:"2",key:"izxlao"}]],Np=Ce("mail",rT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const tT=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]],nT=Te("pencil",tT);/**
|
||||
*/const iT=[["path",{d:"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092 10 10 0 1 0-4.777-4.719",key:"1sd12s"}]],yh=Ce("message-circle",iT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const rT=[["path",{d:"M12 22v-5",key:"1ega77"}],["path",{d:"M15 8V2",key:"18g5xt"}],["path",{d:"M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z",key:"1xoxul"}],["path",{d:"M9 8V2",key:"14iosj"}]],V_=Te("plug",rT);/**
|
||||
*/const aT=[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]],sT=Ce("pencil",aT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const iT=[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]],Y_=Te("plus",iT);/**
|
||||
*/const lT=[["path",{d:"M12 22v-5",key:"1ega77"}],["path",{d:"M15 8V2",key:"18g5xt"}],["path",{d:"M17 8a1 1 0 0 1 1 1v4a4 4 0 0 1-4 4h-4a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1z",key:"1xoxul"}],["path",{d:"M9 8V2",key:"14iosj"}]],K_=Ce("plug",lT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const aT=[["path",{d:"M19.07 4.93A10 10 0 0 0 6.99 3.34",key:"z3du51"}],["path",{d:"M4 6h.01",key:"oypzma"}],["path",{d:"M2.29 9.62A10 10 0 1 0 21.31 8.35",key:"qzzz0"}],["path",{d:"M16.24 7.76A6 6 0 1 0 8.23 16.67",key:"1yjesh"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M17.99 11.66A6 6 0 0 1 15.77 16.67",key:"1u2y91"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["path",{d:"m13.41 10.59 5.66-5.66",key:"mhq4k0"}]],sT=Te("radar",aT);/**
|
||||
*/const oT=[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]],Z_=Ce("plus",oT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const lT=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],oT=Te("refresh-cw",lT);/**
|
||||
*/const cT=[["path",{d:"M19.07 4.93A10 10 0 0 0 6.99 3.34",key:"z3du51"}],["path",{d:"M4 6h.01",key:"oypzma"}],["path",{d:"M2.29 9.62A10 10 0 1 0 21.31 8.35",key:"qzzz0"}],["path",{d:"M16.24 7.76A6 6 0 1 0 8.23 16.67",key:"1yjesh"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M17.99 11.66A6 6 0 0 1 15.77 16.67",key:"1u2y91"}],["circle",{cx:"12",cy:"12",r:"2",key:"1c9p78"}],["path",{d:"m13.41 10.59 5.66-5.66",key:"mhq4k0"}]],uT=Ce("radar",cT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const cT=[["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",key:"m3kijz"}],["path",{d:"m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z",key:"1fmvmk"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0",key:"1f8sc4"}],["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}]],uT=Te("rocket",cT);/**
|
||||
*/const dT=[["path",{d:"M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",key:"v9h5vc"}],["path",{d:"M21 3v5h-5",key:"1q7to0"}],["path",{d:"M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",key:"3uifl3"}],["path",{d:"M8 16H3v5",key:"1cv678"}]],fT=Ce("refresh-cw",dT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const dT=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]],fT=Te("rotate-ccw",dT);/**
|
||||
*/const hT=[["path",{d:"M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z",key:"m3kijz"}],["path",{d:"m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z",key:"1fmvmk"}],["path",{d:"M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0",key:"1f8sc4"}],["path",{d:"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5",key:"qeys4"}]],Q_=Ce("rocket",hT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const hT=[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",key:"1c8476"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7",key:"1ydtos"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7",key:"t51u73"}]],mT=Te("save",hT);/**
|
||||
*/const mT=[["path",{d:"M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8",key:"1357e3"}],["path",{d:"M3 3v5h5",key:"1xhq8a"}]],pT=Ce("rotate-ccw",mT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const pT=[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]],gT=Te("search",pT);/**
|
||||
*/const gT=[["path",{d:"M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z",key:"1c8476"}],["path",{d:"M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7",key:"1ydtos"}],["path",{d:"M7 3v4a1 1 0 0 0 1 1h7",key:"t51u73"}]],xT=Ce("save",gT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const xT=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]],bT=Te("shield-alert",xT);/**
|
||||
*/const bT=[["path",{d:"m21 21-4.34-4.34",key:"14j7rj"}],["circle",{cx:"11",cy:"11",r:"8",key:"4ej97u"}]],yT=Ce("search",bT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const yT=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],X_=Te("shield-check",yT);/**
|
||||
*/const vT=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]],_T=Ce("shield-alert",vT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const vT=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]],_T=Te("shield",vT);/**
|
||||
*/const wT=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]],W_=Ce("shield-check",wT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const wT=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]],Pm=Te("sparkles",wT);/**
|
||||
*/const ET=[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]],NT=Ce("shield",ET);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const ET=[["path",{d:"M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",key:"1dfntj"}],["path",{d:"M15 3v5a1 1 0 0 0 1 1h5",key:"6s6qgf"}]],NT=Te("sticky-note",ET);/**
|
||||
*/const ST=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]],Fm=Ce("sparkles",ST);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const ST=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],K_=Te("terminal",ST);/**
|
||||
*/const kT=[["path",{d:"M21 9a2.4 2.4 0 0 0-.706-1.706l-3.588-3.588A2.4 2.4 0 0 0 15 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z",key:"1dfntj"}],["path",{d:"M15 3v5a1 1 0 0 0 1 1h5",key:"6s6qgf"}]],CT=Ce("sticky-note",kT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const kT=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],CT=Te("trash-2",kT);/**
|
||||
*/const TT=[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]],J_=Ce("terminal",TT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const TT=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],Np=Te("triangle-alert",TT);/**
|
||||
*/const AT=[["path",{d:"M10 11v6",key:"nco0om"}],["path",{d:"M14 11v6",key:"outv1u"}],["path",{d:"M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6",key:"miytrc"}],["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2",key:"e791ji"}]],MT=Ce("trash-2",AT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const AT=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]],MT=Te("users",AT);/**
|
||||
*/const OT=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],Sp=Ce("triangle-alert",OT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const OT=[["path",{d:"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72",key:"ul74o6"}],["path",{d:"m14 7 3 3",key:"1r5n42"}],["path",{d:"M5 6v4",key:"ilb8ba"}],["path",{d:"M19 14v4",key:"blhpug"}],["path",{d:"M10 2v2",key:"7u0qdc"}],["path",{d:"M7 8H3",key:"zfb6yr"}],["path",{d:"M21 16h-4",key:"1cnmox"}],["path",{d:"M11 3H9",key:"1obp7u"}]],RT=Te("wand-sparkles",OT);/**
|
||||
*/const RT=[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]],jT=Ce("users",RT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const jT=[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",key:"1ngwbx"}]],Fm=Te("wrench",jT);/**
|
||||
*/const DT=[["path",{d:"m21.64 3.64-1.28-1.28a1.21 1.21 0 0 0-1.72 0L2.36 18.64a1.21 1.21 0 0 0 0 1.72l1.28 1.28a1.2 1.2 0 0 0 1.72 0L21.64 5.36a1.2 1.2 0 0 0 0-1.72",key:"ul74o6"}],["path",{d:"m14 7 3 3",key:"1r5n42"}],["path",{d:"M5 6v4",key:"ilb8ba"}],["path",{d:"M19 14v4",key:"blhpug"}],["path",{d:"M10 2v2",key:"7u0qdc"}],["path",{d:"M7 8H3",key:"zfb6yr"}],["path",{d:"M21 16h-4",key:"1cnmox"}],["path",{d:"M11 3H9",key:"1obp7u"}]],LT=Ce("wand-sparkles",DT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const DT=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],Sp=Te("x",DT);/**
|
||||
*/const zT=[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",key:"1ngwbx"}]],Gm=Ce("wrench",zT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const LT=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],zT=Te("zap",LT),IT={open:{label:"Open",color:"bg-red-500/10 text-red-400 border-red-500/20",dotColor:"bg-red-500",description:"Newly discovered, awaiting triage"},in_progress:{label:"In Progress",color:"bg-blue-500/10 text-blue-400 border-blue-500/20",dotColor:"bg-blue-500",description:"Someone is working on this"},snoozed:{label:"Snoozed",color:"bg-purple-500/10 text-purple-400 border-purple-500/20",dotColor:"bg-purple-500",description:"Temporarily hidden until a follow-up date"},fixed:{label:"Fixed",color:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20",dotColor:"bg-emerald-500",description:"This vulnerability has been fixed"},ignored:{label:"Ignored",color:"bg-gray-500/10 text-gray-400 border-gray-500/20",dotColor:"bg-gray-500",description:"Acknowledged but accepted"}},BT={trivial:{label:"Trivial",color:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20"},low:{label:"Low",color:"bg-blue-500/10 text-blue-400 border-blue-500/20"},medium:{label:"Medium",color:"bg-yellow-500/10 text-yellow-400 border-yellow-500/20"},high:{label:"High",color:"bg-orange-500/10 text-orange-400 border-orange-500/20"}},Z_={critical:"bg-red-500/20 text-red-500 border-red-500/30",high:"bg-orange-500/20 text-orange-500 border-orange-500/30",medium:"bg-yellow-500/20 text-yellow-500 border-yellow-500/30",low:"bg-blue-500/20 text-blue-500 border-blue-500/30"};function Wc(e){return e.original_severity!=null&&e.original_severity!==e.severity}const UT={js:"javascript",ts:"typescript",tsx:"typescript",jsx:"javascript",py:"python",rb:"ruby",go:"go",rs:"rust",java:"java",php:"php",cs:"csharp",cpp:"cpp",c:"c",sh:"bash",bash:"bash",sql:"sql",html:"html",css:"css",json:"json",yaml:"yaml",yml:"yaml",xml:"xml"};function HT(e){var r;if(!e)return null;const t=(r=e.split(".").pop())==null?void 0:r.toLowerCase();return t&&UT[t]||null}function kp(e){switch(e){case"critical":return"bg-red-500";case"high":return"bg-orange-500";case"medium":return"bg-yellow-500";default:return"bg-blue-500"}}async function Cp(e){try{await navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea");t.value=e,t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}}const Vu="https://app.strix.ai/api/auth/signup",$T="https://strix.ai/pricing",qT="ref=oss_viewer&utm_source=oss_viewer&utm_medium=local_viewer&utm_campaign=oss_viewer";function ha(e,t){const r=e.includes("?")?"&":"?";return`${e}${r}${qT}&utm_content=${encodeURIComponent(t)}`}function Ar(e,t={}){try{const r={event:e};for(const[s,o]of Object.entries(t))o!==void 0&&(r[s]=o);const a=JSON.stringify(r);typeof navigator<"u"&&navigator.sendBeacon?navigator.sendBeacon("/api/event",a):fetch("/api/event",{method:"POST",body:a,keepalive:!0})}catch{}}function jr(e,t){Ar("cta_clicked",{cta:e,surface:t})}function Q_(e){var t,r,a="";if(typeof e=="string"||typeof e=="number")a+=e;else if(typeof e=="object")if(Array.isArray(e)){var s=e.length;for(t=0;t<s;t++)e[t]&&(r=Q_(e[t]))&&(a&&(a+=" "),a+=r)}else for(r in e)e[r]&&(a&&(a+=" "),a+=r);return a}function PT(){for(var e,t,r=0,a="",s=arguments.length;r<s;r++)(e=arguments[r])&&(t=Q_(e))&&(a&&(a+=" "),a+=t);return a}const FT=(e,t)=>{const r=new Array(e.length+t.length);for(let a=0;a<e.length;a++)r[a]=e[a];for(let a=0;a<t.length;a++)r[e.length+a]=t[a];return r},GT=(e,t)=>({classGroupId:e,validator:t}),W_=(e=new Map,t=null,r)=>({nextPart:e,validators:t,classGroupId:r}),Nu="-",cy=[],VT="arbitrary..",YT=e=>{const t=KT(e),{conflictingClassGroups:r,conflictingClassGroupModifiers:a}=e;return{getClassGroupId:c=>{if(c.startsWith("[")&&c.endsWith("]"))return XT(c);const d=c.split(Nu),h=d[0]===""&&d.length>1?1:0;return J_(d,h,t)},getConflictingClassGroupIds:(c,d)=>{if(d){const h=a[c],f=r[c];return h?f?FT(f,h):h:f||cy}return r[c]||cy}}},J_=(e,t,r)=>{if(e.length-t===0)return r.classGroupId;const s=e[t],o=r.nextPart.get(s);if(o){const f=J_(e,t+1,o);if(f)return f}const c=r.validators;if(c===null)return;const d=t===0?e.join(Nu):e.slice(t).join(Nu),h=c.length;for(let f=0;f<h;f++){const p=c[f];if(p.validator(d))return p.classGroupId}},XT=e=>e.slice(1,-1).indexOf(":")===-1?void 0:(()=>{const t=e.slice(1,-1),r=t.indexOf(":"),a=t.slice(0,r);return a?VT+a:void 0})(),KT=e=>{const{theme:t,classGroups:r}=e;return ZT(r,t)},ZT=(e,t)=>{const r=W_();for(const a in e){const s=e[a];Tp(s,r,a,t)}return r},Tp=(e,t,r,a)=>{const s=e.length;for(let o=0;o<s;o++){const c=e[o];QT(c,t,r,a)}},QT=(e,t,r,a)=>{if(typeof e=="string"){WT(e,t,r);return}if(typeof e=="function"){JT(e,t,r,a);return}eA(e,t,r,a)},WT=(e,t,r)=>{const a=e===""?t:ew(t,e);a.classGroupId=r},JT=(e,t,r,a)=>{if(tA(e)){Tp(e(a),t,r,a);return}t.validators===null&&(t.validators=[]),t.validators.push(GT(r,e))},eA=(e,t,r,a)=>{const s=Object.enLine truncated
|
||||
`,f="/",p="*",g="",b="comment",y="declaration";function _(S,w){if(typeof S!="string")throw new TypeError("First argument must be a string");if(!S)return[];w=w||{};var C=1,E=1;function A(k){var $=k.match(t);$&&(C+=$.length);var O=k.lastIndexOf(h);E=~O?k.length-O:E+k.length}function B(){var k={line:C,column:E};return function($){return $.position=new R(k),Y(),$}}function R(k){this.start=k,this.end={line:C,column:E},this.source=w.source}R.prototype.content=S;function H(k){var $=new Error(w.source+":"+C+":"+E+": "+k);if($.reason=k,$.filename=w.source,$.line=C,$.column=E,$.source=S,!w.silent)throw $}function z(k){var $=k.exec(S);if($){var O=$[0];return A(O),S=S.slice(O.length),$}}function Y(){z(r)}function j(k){var $;for(k=k||[];$=I();)$!==!1&&k.push($);return k}function I(){var k=B();if(!(f!=S.charAt(0)||p!=S.charAt(1))){for(var $=2;g!=S.charAt($)&&(p!=S.charAt($)||f!=S.charAt($+1));)++$;if($+=2,g===S.charAt($-1))return H("End of comment missing");var O=S.slice(2,$-2);return E+=2,A(O),S=S.slice($),E+=2,k({type:b,comment:O})}}function Z(){var k=B(),$=z(a);if($){if(I(),!z(s))return H("property missing ':'");var O=z(o),U=k({type:y,property:N($[0].replace(e,g)),value:O?N(O[0].replace(e,g)):g});return z(c),U}}function P(){var k=[];j(k);for(var $;$=Z();)$!==!1&&(k.push($),j(k));return k}return Y(),P()}function N(S){return S?S.replace(d,g):g}return _h=_,_h}var vy;function tM(){if(vy)return zs;vy=1;var e=zs&&zs.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(zs,"__esModule",{value:!0}),zs.default=r;const t=e(eM());function r(a,s){let o=null;if(!a||typeof a!="string")return o;const c=(0,t.default)(a),d=typeof s=="function";return c.forEach(h=>{if(h.type!=="declaration")return;const{property:f,value:p}=h;d?s(f,p,h):p&&(o=o||{},o[f]=p)}),o}return zs}var Ql={},_y;function nM(){if(_y)return Ql;_y=1,Object.defineProperty(Ql,"__esModule",{value:!0}),Ql.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,r=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,s=/^-(ms)-/,o=function(f){return!f||r.test(f)||e.test(f)},c=function(f,p){return p.toUpperCase()},d=function(f,p){return"".concat(p,"-")},h=function(f,p){return p===void 0&&(p={}),o(f)?f:(f=f.toLowerCase(),p.reactCompat?f=f.replace(s,d):f=f.replace(a,d),f.replace(t,c))};return Ql.camelCase=h,Ql}var Wl,wy;function rM(){if(wy)return Wl;wy=1;var e=Wl&&Wl.__importDefault||function(s){return s&&s.__esModule?s:{default:s}},t=e(tM()),r=nM();function a(s,o){var c={};return!s||typeof s!="string"||(0,t.default)(s,function(d,h){d&&h&&(c[(0,r.camelCase)(d,o)]=h)}),c}return a.default=a,Wl=a,Wl}var iM=rM();const aM=Ao(iM),yw=vw("end"),Rp=vw("start");function vw(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 sM(e){const t=Rp(e),r=yw(e);if(t&&r)return{start:t,end:r}}function oo(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?Ey(e.position):"start"in e||"end"in e?Ey(e):"line"in e||"column"in e?Zm(e):""}function Zm(e){return Ny(e&&e.line)+":"+Ny(e&&e.column)}function Ey(e){return Zm(e&&e.start)+"-"+Zm(e&&e.end)}function Ny(e){return e&&typeof e=="number"?e:1}class Mn extends Error{constructor(t,r,a){super(),typeof r=="string"&&(a=r,r=void 0);let s="",o={},c=!1;if(r&&("line"in r&&"column"in r?o={place:r}:"start"in r&&"end"in r?o={place:r}:"type"in r?o={ancestors:[r],place:r.position}:o={...r}),typeof t=="string"?s=t:!o.cause&&t&&(c=!0,s=t.message,o.cause=t),!o.ruleId&&!o.source&&typeof a=="string"){const h=a.indexOf(":");h===-1?o.ruleId=a:(o.source=a.slice(0,h),o.ruleId=a.slice(h+1))}if(!o.place&&o.ancestors&&o.ancestors){const h=o.ancestors[o.ancestors.length-1];h&&(o.place=h.position)}const d=o.place&&"start"in o.place?o.place.start:o.place;this.ancestors=o.ancestors||void 0,this.cause=o.cause||void 0,this.column=d?d.column:void 0,this.fatal=void 0,this.file="",this.message=s,this.line=d?d.line:void 0,this.name=oo(o.place)||"1:1",this.place=o.place||void 0,this.reason=this.message,this.ruleId=o.ruleId||void 0,this.source=o.source||void 0,this.stack=c&&o.cause&&typeof o.cause.stack=="string"?o.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}Mn.prototype.file="";Mn.prototype.name="";Mn.prototype.reason="";Mn.prototype.message="";Mn.prototype.stack="";Mn.prototype.column=void 0;Mn.prototype.line=void 0;Mn.prototype.ancestors=void 0;Mn.prototype.cause=void 0;Mn.prototype.fatal=void 0;Mn.prototype.place=void 0;Mn.prototype.ruleId=void 0;Mn.prototype.source=void 0;const jp={}.hasOwnProperty,lM=new Map,oM=/[A-Z]/g,cM=new Set(["table","tbody","thead","tfoot","tr"]),uM=new Set(["td","th"]),_w="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function dM(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const r=t.filePath||void 0;let a;if(t.development){if(typeof t.j
Line truncated
|
||||
*/const IT=[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]],kp=Ce("x",IT);/**
|
||||
* @license lucide-react v0.563.0 - ISC
|
||||
*
|
||||
* This source code is licensed under the ISC license.
|
||||
* See the LICENSE file in the root directory of this source tree.
|
||||
*/const BT=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],UT=Ce("zap",BT),HT={open:{label:"Open",color:"bg-red-500/10 text-red-400 border-red-500/20",dotColor:"bg-red-500",description:"Newly discovered, awaiting triage"},in_progress:{label:"In Progress",color:"bg-blue-500/10 text-blue-400 border-blue-500/20",dotColor:"bg-blue-500",description:"Someone is working on this"},snoozed:{label:"Snoozed",color:"bg-purple-500/10 text-purple-400 border-purple-500/20",dotColor:"bg-purple-500",description:"Temporarily hidden until a follow-up date"},fixed:{label:"Fixed",color:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20",dotColor:"bg-emerald-500",description:"This vulnerability has been fixed"},ignored:{label:"Ignored",color:"bg-gray-500/10 text-gray-400 border-gray-500/20",dotColor:"bg-gray-500",description:"Acknowledged but accepted"}},$T={trivial:{label:"Trivial",color:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20"},low:{label:"Low",color:"bg-blue-500/10 text-blue-400 border-blue-500/20"},medium:{label:"Medium",color:"bg-yellow-500/10 text-yellow-400 border-yellow-500/20"},high:{label:"High",color:"bg-orange-500/10 text-orange-400 border-orange-500/20"}},ew={critical:"bg-red-500/20 text-red-500 border-red-500/30",high:"bg-orange-500/20 text-orange-500 border-orange-500/30",medium:"bg-yellow-500/20 text-yellow-500 border-yellow-500/30",low:"bg-blue-500/20 text-blue-500 border-blue-500/30"};function Jc(e){return e.original_severity!=null&&e.original_severity!==e.severity}const qT={js:"javascript",ts:"typescript",tsx:"typescript",jsx:"javascript",py:"python",rb:"ruby",go:"go",rs:"rust",java:"java",php:"php",cs:"csharp",cpp:"cpp",c:"c",sh:"bash",bash:"bash",sql:"sql",html:"html",css:"css",json:"json",yaml:"yaml",yml:"yaml",xml:"xml"};function PT(e){var r;if(!e)return null;const t=(r=e.split(".").pop())==null?void 0:r.toLowerCase();return t&&qT[t]||null}function Cp(e){switch(e){case"critical":return"bg-red-500";case"high":return"bg-orange-500";case"medium":return"bg-yellow-500";default:return"bg-blue-500"}}async function Tp(e){try{await navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea");t.value=e,t.style.position="absolute",t.style.left="-9999px",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t)}}const Yu="https://app.strix.ai/api/auth/signup",Ap="https://strix.ai/demo",FT="https://strix.ai/pricing",GT="ref=oss_viewer&utm_source=oss_viewer&utm_medium=local_viewer&utm_campaign=oss_viewer";function _i(e,t){const r=e.includes("?")?"&":"?";return`${e}${r}${GT}&utm_content=${encodeURIComponent(t)}`}function Mr(e,t={}){try{const r={event:e};for(const[s,o]of Object.entries(t))o!==void 0&&(r[s]=o);const a=JSON.stringify(r);typeof navigator<"u"&&navigator.sendBeacon?navigator.sendBeacon("/api/event",a):fetch("/api/event",{method:"POST",body:a,keepalive:!0})}catch{}}function yr(e,t){Mr("cta_clicked",{cta:e,surface:t})}function tw(e){var t,r,a="";if(typeof e=="string"||typeof e=="number")a+=e;else if(typeof e=="object")if(Array.isArray(e)){var s=e.length;for(t=0;t<s;t++)e[t]&&(r=tw(e[t]))&&(a&&(a+=" "),a+=r)}else for(r in e)e[r]&&(a&&(a+=" "),a+=r);return a}function VT(){for(var e,t,r=0,a="",s=arguments.length;r<s;r++)(e=arguments[r])&&(t=tw(e))&&(a&&(a+=" "),a+=t);return a}const YT=(e,t)=>{const r=new Array(e.length+t.length);for(let a=0;a<e.length;a++)r[a]=e[a];for(let a=0;a<t.length;a++)r[e.length+a]=t[a];return r},XT=(e,t)=>({classGroupId:e,validator:t}),nw=(e=new Map,t=null,r)=>({nextPart:e,validators:t,classGroupId:r}),Su="-",dy=[],KT="arbitrary..",ZT=e=>{const t=WT(e),{conflictingClassGroups:r,conflictingClassGroupModifiers:a}=e;return{getClassGroupId:c=>{if(c.startsWith("[")&&c.endsWith("]"))return QT(c);const d=c.split(Su),h=d[0]===""&&d.length>1?1:0;return rw(d,h,t)},getConflictingClassGroupIds:(c,d)=>{if(d){const h=a[c],f=r[c];return h?f?YT(f,h):h:f||dy}return r[c]||dy}}},rw=(e,t,r)=>{if(e.length-t===0)return r.classGroupId;const s=e[t],o=r.nextPart.get(s);if(o){const f=rw(e,t+1,o);if(f)return f}const c=r.validators;if(c===null)return;const d=t===0?e.join(Su):e.slice(t).join(Su),h=c.length;for(let f=0;f<h;f++){const p=c[f];if(p.validator(d))return p.classGroupId}},QT=e=>e.slice(1,-1).indexOf(":")===-1?void 0:(()=>{const t=e.slice(1,-1),r=t.indexOf(":"),a=t.slice(0,r);return a?KT+a:void 0})(),WT=e=>{const{theme:t,classGroups:r}=e;return JT(r,t)},JT=(e,t)=>{const r=nw();for(const a in e){const s=e[a];Mp(s,r,a,t)}return r},Mp=(e,t,r,a)=>{const s=e.length;for(let o=0;o<s;o++){const c=e[o];eA(c,t,r,a)}},eA=(e,t,r,a)=>{if(typeof e=="string"){tA(e,t,r);return}if(typeof e=="function"){nA(e,t,r,a);return}rA(e,t,r,a)},tA=(e,t,r)=>{const a=e===""?t:iw(t,e);a.classGroupId=r},nA=(e,t,r,a)=>{if(iA(e)){Mp(e(a),t,r,a);return}t.validators===null&&(t.validators=[]),t.validators.push(XT(r,e))},rA=(eLine truncated
|
||||
`,f="/",p="*",g="",b="comment",y="declaration";function _(S,w){if(typeof S!="string")throw new TypeError("First argument must be a string");if(!S)return[];w=w||{};var C=1,E=1;function A(k){var $=k.match(t);$&&(C+=$.length);var O=k.lastIndexOf(h);E=~O?k.length-O:E+k.length}function B(){var k={line:C,column:E};return function($){return $.position=new R(k),Y(),$}}function R(k){this.start=k,this.end={line:C,column:E},this.source=w.source}R.prototype.content=S;function H(k){var $=new Error(w.source+":"+C+":"+E+": "+k);if($.reason=k,$.filename=w.source,$.line=C,$.column=E,$.source=S,!w.silent)throw $}function z(k){var $=k.exec(S);if($){var O=$[0];return A(O),S=S.slice(O.length),$}}function Y(){z(r)}function j(k){var $;for(k=k||[];$=I();)$!==!1&&k.push($);return k}function I(){var k=B();if(!(f!=S.charAt(0)||p!=S.charAt(1))){for(var $=2;g!=S.charAt($)&&(p!=S.charAt($)||f!=S.charAt($+1));)++$;if($+=2,g===S.charAt($-1))return H("End of comment missing");var O=S.slice(2,$-2);return E+=2,A(O),S=S.slice($),E+=2,k({type:b,comment:O})}}function Z(){var k=B(),$=z(a);if($){if(I(),!z(s))return H("property missing ':'");var O=z(o),U=k({type:y,property:N($[0].replace(e,g)),value:O?N(O[0].replace(e,g)):g});return z(c),U}}function P(){var k=[];j(k);for(var $;$=Z();)$!==!1&&(k.push($),j(k));return k}return Y(),P()}function N(S){return S?S.replace(d,g):g}return wh=_,wh}var wy;function iM(){if(wy)return zs;wy=1;var e=zs&&zs.__importDefault||function(a){return a&&a.__esModule?a:{default:a}};Object.defineProperty(zs,"__esModule",{value:!0}),zs.default=r;const t=e(rM());function r(a,s){let o=null;if(!a||typeof a!="string")return o;const c=(0,t.default)(a),d=typeof s=="function";return c.forEach(h=>{if(h.type!=="declaration")return;const{property:f,value:p}=h;d?s(f,p,h):p&&(o=o||{},o[f]=p)}),o}return zs}var Ql={},Ey;function aM(){if(Ey)return Ql;Ey=1,Object.defineProperty(Ql,"__esModule",{value:!0}),Ql.camelCase=void 0;var e=/^--[a-zA-Z0-9_-]+$/,t=/-([a-z])/g,r=/^[^-]+$/,a=/^-(webkit|moz|ms|o|khtml)-/,s=/^-(ms)-/,o=function(f){return!f||r.test(f)||e.test(f)},c=function(f,p){return p.toUpperCase()},d=function(f,p){return"".concat(p,"-")},h=function(f,p){return p===void 0&&(p={}),o(f)?f:(f=f.toLowerCase(),p.reactCompat?f=f.replace(s,d):f=f.replace(a,d),f.replace(t,c))};return Ql.camelCase=h,Ql}var Wl,Ny;function sM(){if(Ny)return Wl;Ny=1;var e=Wl&&Wl.__importDefault||function(s){return s&&s.__esModule?s:{default:s}},t=e(iM()),r=aM();function a(s,o){var c={};return!s||typeof s!="string"||(0,t.default)(s,function(d,h){d&&h&&(c[(0,r.camelCase)(d,o)]=h)}),c}return a.default=a,Wl=a,Wl}var lM=sM();const oM=Mo(lM),Ew=Nw("end"),Dp=Nw("start");function Nw(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 cM(e){const t=Dp(e),r=Ew(e);if(t&&r)return{start:t,end:r}}function oo(e){return!e||typeof e!="object"?"":"position"in e||"type"in e?Sy(e.position):"start"in e||"end"in e?Sy(e):"line"in e||"column"in e?Qm(e):""}function Qm(e){return ky(e&&e.line)+":"+ky(e&&e.column)}function Sy(e){return Qm(e&&e.start)+"-"+Qm(e&&e.end)}function ky(e){return e&&typeof e=="number"?e:1}class Mn extends Error{constructor(t,r,a){super(),typeof r=="string"&&(a=r,r=void 0);let s="",o={},c=!1;if(r&&("line"in r&&"column"in r?o={place:r}:"start"in r&&"end"in r?o={place:r}:"type"in r?o={ancestors:[r],place:r.position}:o={...r}),typeof t=="string"?s=t:!o.cause&&t&&(c=!0,s=t.message,o.cause=t),!o.ruleId&&!o.source&&typeof a=="string"){const h=a.indexOf(":");h===-1?o.ruleId=a:(o.source=a.slice(0,h),o.ruleId=a.slice(h+1))}if(!o.place&&o.ancestors&&o.ancestors){const h=o.ancestors[o.ancestors.length-1];h&&(o.place=h.position)}const d=o.place&&"start"in o.place?o.place.start:o.place;this.ancestors=o.ancestors||void 0,this.cause=o.cause||void 0,this.column=d?d.column:void 0,this.fatal=void 0,this.file="",this.message=s,this.line=d?d.line:void 0,this.name=oo(o.place)||"1:1",this.place=o.place||void 0,this.reason=this.message,this.ruleId=o.ruleId||void 0,this.source=o.source||void 0,this.stack=c&&o.cause&&typeof o.cause.stack=="string"?o.cause.stack:"",this.actual=void 0,this.expected=void 0,this.note=void 0,this.url=void 0}}Mn.prototype.file="";Mn.prototype.name="";Mn.prototype.reason="";Mn.prototype.message="";Mn.prototype.stack="";Mn.prototype.column=void 0;Mn.prototype.line=void 0;Mn.prototype.ancestors=void 0;Mn.prototype.cause=void 0;Mn.prototype.fatal=void 0;Mn.prototype.place=void 0;Mn.prototype.ruleId=void 0;Mn.prototype.source=void 0;const Lp={}.hasOwnProperty,uM=new Map,dM=/[A-Z]/g,fM=new Set(["table","tbody","thead","tfoot","tr"]),hM=new Set(["td","th"]),Sw="https://github.com/syntax-tree/hast-util-to-jsx-runtime";function mM(e,t){if(!t||t.Fragment===void 0)throw new TypeError("Expected `Fragment` in options");const r=t.filePath||void 0;let a;if(t.development){if(typeof t.j
Line truncated
|
||||
`;break}case-3:{c=`\r
|
||||
`;break}case-2:{c=t?" ":" ";break}case-1:{if(!t&&s)continue;c=" ";break}default:c=String.fromCharCode(o)}s=o===-2,a.push(c)}return a.join("")}function yO(e){const a={constructs:kw([pO,...(e||{}).extensions||[]]),content:s(DM),defined:[],document:s(zM),flow:s(eO),lazy:{},string:s(rO),text:s(iO)};return a;function s(o){return c;function c(d){return gO(a,o,d)}}}function vO(e){for(;!Ow(e););return e}const Dy=/[\0\t\n\r]/g;function _O(){let e=1,t="",r=!0,a;return s;function s(o,c,d){const h=[];let f,p,g,b,y;for(o=t+(typeof o=="string"?o.toString():new TextDecoder(c||void 0).decode(o)),g=0,t="",r&&(o.charCodeAt(0)===65279&&g++,r=void 0);g<o.length;){if(Dy.lastIndex=g,f=Dy.exec(o),b=f&&f.index!==void 0?f.index:o.length,y=o.charCodeAt(b),!f){t=o.slice(g);break}if(y===10&&g===b&&a)h.push(-3),a=void 0;else switch(a&&(h.push(-5),a=void 0),g<b&&(h.push(o.slice(g,b)),e+=b-g),y){case 0:{h.push(65533),e++;break}case 9:{for(p=Math.ceil(e/4)*4,h.push(-2);e++<p;)h.push(-1);break}case 10:{h.push(-4),e=1;break}default:a=!0,e=1}g=b+1}return d&&(a&&h.push(-5),t&&h.push(t),h.push(null)),h}}const wO=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function EO(e){return e.replace(wO,NO)}function NO(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const s=r.charCodeAt(1),o=s===120||s===88;return Cw(r.slice(o?2:1),o?16:10)}return Ip(r)||e}const Iw={}.hasOwnProperty;function SO(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),kO(r)(vO(yO(r).document().write(_O()(e,t,!0))))}function kO(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:o(Xt),autolinkProtocol:Y,autolinkEmail:Y,atxHeading:o(De),blockQuote:o(oe),characterEscape:Y,characterReference:Y,codeFenced:o(fe),codeFencedFenceInfo:c,codeFencedFenceMeta:c,codeIndented:o(fe,c),codeText:o(xe,c),codeTextData:Y,data:Y,codeFlowValue:Y,definition:o(we),definitionDestinationString:c,definitionLabelString:c,definitionTitleString:c,emphasis:o(Ne),hardBreakEscape:o($e),hardBreakTrailing:o($e),htmlFlow:o(st,c),htmlFlowData:Y,htmlText:o(st,c),htmlTextData:Y,image:o(Rt),label:c,link:o(Xt),listItem:o(Kt),listItemValue:b,listOrdered:o(Pt,g),listUnordered:o(Pt),paragraph:o(Yn),reference:D,referenceString:c,resourceDestinationString:c,resourceTitleString:c,setextHeading:o(De),strong:o(Nn),thematicBreak:o(It)},exit:{atxHeading:h(),atxHeadingSequence:B,autolink:h(),autolinkEmail:te,autolinkProtocol:W,blockQuote:h(),characterEscapeValue:j,characterReferenceMarkerHexadecimal:q,characterReferenceMarkerNumeric:q,characterReferenceValue:Q,characterReference:J,codeFenced:h(S),codeFencedFence:N,codeFencedFenceInfo:y,codeFencedFenceMeta:_,codeFlowValue:j,codeIndented:h(w),codeText:h($),codeTextData:j,data:j,definition:h(),definitionDestinationString:A,definitionLabelString:C,definitionTitleString:E,emphasis:h(),hardBreakEscape:h(Z),hardBreakTrailing:h(Z),htmlFlow:h(P),htmlFlowData:j,htmlText:h(k),htmlTextData:j,image:h(U),label:X,labelText:K,lineEnding:I,link:h(O),listItem:h(),listOrdered:h(),listUnordered:h(),paragraph:h(),referenceString:V,resourceDestinationString:M,resourceTitleString:L,resource:F,setextHeading:h(z),setextHeadingLineSequence:H,setextHeadingText:R,strong:h(),thematicBreak:h()}};Bw(t,(e||{}).mdastExtensions||[]);const r={};return a;function a(ue){let be={type:"root",children:[]};const Oe={stack:[be],tokenStack:[],config:t,enter:d,exit:f,buffer:c,resume:p,data:r},Fe=[];let Ze=-1;for(;++Ze<ue.length;)if(ue[Ze][1].type==="listOrdered"||ue[Ze][1].type==="listUnordered")if(ue[Ze][0]==="enter")Fe.push(Ze);else{const cn=Fe.pop();Ze=s(ue,cn,Ze)}for(Ze=-1;++Ze<ue.length;){const cn=t[ue[Ze][0]];Iw.call(cn,ue[Ze][1].type)&&cn[ue[Ze][1].type].call(Object.assign({sliceSerialize:ue[Ze][2].sliceSerialize},Oe),ue[Ze][1])}if(Oe.tokenStack.length>0){const cn=Oe.tokenStack[Oe.tokenStack.length-1];(cn[1]||Ly).call(Oe,void 0,cn[0])}for(be.position={start:ia(ue.length>0?ue[0][1].start:{line:1,column:1,offset:0}),end:ia(ue.length>0?ue[ue.length-2][1].end:{line:1,column:1,offset:0})},Ze=-1;++Ze<t.transforms.length;)be=t.transforms[Ze](be)||be;return be}function s(ue,be,Oe){let Fe=be-1,Ze=-1,cn=!1,Sn,Zt,At,Jt;for(;++Fe<=Oe;){const ut=ue[Fe];switch(ut[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{ut[0]==="enter"?Ze++:Ze--,Jt=void 0;break}case"lineEndingBlank":{ut[0]==="enter"&&(Sn&&!Jt&&!Ze&&!At&&(At=Fe),Jt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Jt=void 0}if(!Ze&&ut[0]==="enter"&&ut[1].type==="listItemPrefix"||Ze===-1&&ut[0]==="exit"&&(ut[1].type==="listUnordered"||ut[1].type==="listOrdered")){if(Sn){let In=Fe;for(Zt=void 0;In--;){const un=ue[In];if(un[1].type==="lineEnding"||un[1].type==="lineEndingBlank"){if(un[0]==="exit")continue;Zt&&(ue[Zt][1].type="lineEndingBlank",cn=!0),un[1].type="lineEnding",Zt=In}else if(!(un[1].type==="linePrefix"||un[1].type==="blockQuotePrefix"||un[1].type==="blockQuotePrefixWhitespace"||un[Line truncated
|
||||
`}]}function OO(e,t){const r=t.value?t.value+`
|
||||
`:"",a={},s=t.lang?t.lang.split(/\s+/):[];s.length>0&&(a.className=["language-"+s[0]]);let o={type:"element",tagName:"code",properties:a,children:[{type:"text",value:r}]};return t.meta&&(o.data={meta:t.meta}),e.patch(t,o),o=e.applyData(t,o),o={type:"element",tagName:"pre",properties:{},children:[o]},e.patch(t,o),o}function RO(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function jO(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function DO(e,t){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),s=rl(a.toLowerCase()),o=e.footnoteOrder.indexOf(a);let c,d=e.footnoteCounts.get(a);d===void 0?(d=0,e.footnoteOrder.push(a),c=e.footnoteOrder.length):c=o+1,d+=1,e.footnoteCounts.set(a,d);const h={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+s,id:r+"fnref-"+s+(d>1?"-"+d:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(c)}]};e.patch(t,h);const f={type:"element",tagName:"sup",properties:{},children:[h]};return e.patch(t,f),e.applyData(t,f)}function LO(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 zO(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function Uw(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 s=e.all(t),o=s[0];o&&o.type==="text"?o.value="["+o.value:s.unshift({type:"text",value:"["});const c=s[s.length-1];return c&&c.type==="text"?c.value+=a:s.push({type:"text",value:a}),s}function IO(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return Uw(e,t);const s={src:rl(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(s.title=a.title);const o={type:"element",tagName:"img",properties:s,children:[]};return e.patch(t,o),e.applyData(t,o)}function BO(e,t){const r={src:rl(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 UO(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 HO(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return Uw(e,t);const s={href:rl(a.url||"")};a.title!==null&&a.title!==void 0&&(s.title=a.title);const o={type:"element",tagName:"a",properties:s,children:e.all(t)};return e.patch(t,o),e.applyData(t,o)}function $O(e,t){const r={href:rl(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 qO(e,t,r){const a=e.all(t),s=r?PO(r):Hw(t),o={},c=[];if(typeof t.checked=="boolean"){const p=a[0];let g;p&&p.type==="element"&&p.tagName==="p"?g=p:(g={type:"element",tagName:"p",properties:{},children:[]},a.unshift(g)),g.children.length>0&&g.children.unshift({type:"text",value:" "}),g.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),o.className=["task-list-item"]}let d=-1;for(;++d<a.length;){const p=a[d];(s||d!==0||p.type!=="element"||p.tagName!=="p")&&c.push({type:"text",value:`
|
||||
`;break}case-2:{c=t?" ":" ";break}case-1:{if(!t&&s)continue;c=" ";break}default:c=String.fromCharCode(o)}s=o===-2,a.push(c)}return a.join("")}function wO(e){const a={constructs:Mw([bO,...(e||{}).extensions||[]]),content:s(IM),defined:[],document:s(UM),flow:s(rO),lazy:{},string:s(sO),text:s(lO)};return a;function s(o){return c;function c(d){return yO(a,o,d)}}}function EO(e){for(;!Lw(e););return e}const zy=/[\0\t\n\r]/g;function NO(){let e=1,t="",r=!0,a;return s;function s(o,c,d){const h=[];let f,p,g,b,y;for(o=t+(typeof o=="string"?o.toString():new TextDecoder(c||void 0).decode(o)),g=0,t="",r&&(o.charCodeAt(0)===65279&&g++,r=void 0);g<o.length;){if(zy.lastIndex=g,f=zy.exec(o),b=f&&f.index!==void 0?f.index:o.length,y=o.charCodeAt(b),!f){t=o.slice(g);break}if(y===10&&g===b&&a)h.push(-3),a=void 0;else switch(a&&(h.push(-5),a=void 0),g<b&&(h.push(o.slice(g,b)),e+=b-g),y){case 0:{h.push(65533),e++;break}case 9:{for(p=Math.ceil(e/4)*4,h.push(-2);e++<p;)h.push(-1);break}case 10:{h.push(-4),e=1;break}default:a=!0,e=1}g=b+1}return d&&(a&&h.push(-5),t&&h.push(t),h.push(null)),h}}const SO=/\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;function kO(e){return e.replace(SO,CO)}function CO(e,t,r){if(t)return t;if(r.charCodeAt(0)===35){const s=r.charCodeAt(1),o=s===120||s===88;return Ow(r.slice(o?2:1),o?16:10)}return Up(r)||e}const $w={}.hasOwnProperty;function TO(e,t,r){return t&&typeof t=="object"&&(r=t,t=void 0),AO(r)(EO(wO(r).document().write(NO()(e,t,!0))))}function AO(e){const t={transforms:[],canContainEols:["emphasis","fragment","heading","paragraph","strong"],enter:{autolink:o(Xt),autolinkProtocol:Y,autolinkEmail:Y,atxHeading:o(De),blockQuote:o(oe),characterEscape:Y,characterReference:Y,codeFenced:o(fe),codeFencedFenceInfo:c,codeFencedFenceMeta:c,codeIndented:o(fe,c),codeText:o(xe,c),codeTextData:Y,data:Y,codeFlowValue:Y,definition:o(we),definitionDestinationString:c,definitionLabelString:c,definitionTitleString:c,emphasis:o(Ne),hardBreakEscape:o($e),hardBreakTrailing:o($e),htmlFlow:o(st,c),htmlFlowData:Y,htmlText:o(st,c),htmlTextData:Y,image:o(Rt),label:c,link:o(Xt),listItem:o(Kt),listItemValue:b,listOrdered:o(Ft,g),listUnordered:o(Ft),paragraph:o(Yn),reference:D,referenceString:c,resourceDestinationString:c,resourceTitleString:c,setextHeading:o(De),strong:o(Nn),thematicBreak:o(It)},exit:{atxHeading:h(),atxHeadingSequence:B,autolink:h(),autolinkEmail:te,autolinkProtocol:W,blockQuote:h(),characterEscapeValue:j,characterReferenceMarkerHexadecimal:q,characterReferenceMarkerNumeric:q,characterReferenceValue:Q,characterReference:J,codeFenced:h(S),codeFencedFence:N,codeFencedFenceInfo:y,codeFencedFenceMeta:_,codeFlowValue:j,codeIndented:h(w),codeText:h($),codeTextData:j,data:j,definition:h(),definitionDestinationString:A,definitionLabelString:C,definitionTitleString:E,emphasis:h(),hardBreakEscape:h(Z),hardBreakTrailing:h(Z),htmlFlow:h(P),htmlFlowData:j,htmlText:h(k),htmlTextData:j,image:h(U),label:X,labelText:K,lineEnding:I,link:h(O),listItem:h(),listOrdered:h(),listUnordered:h(),paragraph:h(),referenceString:V,resourceDestinationString:M,resourceTitleString:L,resource:F,setextHeading:h(z),setextHeadingLineSequence:H,setextHeadingText:R,strong:h(),thematicBreak:h()}};qw(t,(e||{}).mdastExtensions||[]);const r={};return a;function a(ue){let be={type:"root",children:[]};const Oe={stack:[be],tokenStack:[],config:t,enter:d,exit:f,buffer:c,resume:p,data:r},Fe=[];let Ze=-1;for(;++Ze<ue.length;)if(ue[Ze][1].type==="listOrdered"||ue[Ze][1].type==="listUnordered")if(ue[Ze][0]==="enter")Fe.push(Ze);else{const cn=Fe.pop();Ze=s(ue,cn,Ze)}for(Ze=-1;++Ze<ue.length;){const cn=t[ue[Ze][0]];$w.call(cn,ue[Ze][1].type)&&cn[ue[Ze][1].type].call(Object.assign({sliceSerialize:ue[Ze][2].sliceSerialize},Oe),ue[Ze][1])}if(Oe.tokenStack.length>0){const cn=Oe.tokenStack[Oe.tokenStack.length-1];(cn[1]||Iy).call(Oe,void 0,cn[0])}for(be.position={start:aa(ue.length>0?ue[0][1].start:{line:1,column:1,offset:0}),end:aa(ue.length>0?ue[ue.length-2][1].end:{line:1,column:1,offset:0})},Ze=-1;++Ze<t.transforms.length;)be=t.transforms[Ze](be)||be;return be}function s(ue,be,Oe){let Fe=be-1,Ze=-1,cn=!1,Sn,Zt,At,Jt;for(;++Fe<=Oe;){const ut=ue[Fe];switch(ut[1].type){case"listUnordered":case"listOrdered":case"blockQuote":{ut[0]==="enter"?Ze++:Ze--,Jt=void 0;break}case"lineEndingBlank":{ut[0]==="enter"&&(Sn&&!Jt&&!Ze&&!At&&(At=Fe),Jt=void 0);break}case"linePrefix":case"listItemValue":case"listItemMarker":case"listItemPrefix":case"listItemPrefixWhitespace":break;default:Jt=void 0}if(!Ze&&ut[0]==="enter"&&ut[1].type==="listItemPrefix"||Ze===-1&&ut[0]==="exit"&&(ut[1].type==="listUnordered"||ut[1].type==="listOrdered")){if(Sn){let In=Fe;for(Zt=void 0;In--;){const un=ue[In];if(un[1].type==="lineEnding"||un[1].type==="lineEndingBlank"){if(un[0]==="exit")continue;Zt&&(ue[Zt][1].type="lineEndingBlank",cn=!0),un[1].type="lineEnding",Zt=In}else if(!(un[1].type==="linePrefix"||un[1].type==="blockQuotePrefix"||un[1].type==="blockQuotePrefixWhitespace"||un[Line truncated
|
||||
`}]}function DO(e,t){const r=t.value?t.value+`
|
||||
`:"",a={},s=t.lang?t.lang.split(/\s+/):[];s.length>0&&(a.className=["language-"+s[0]]);let o={type:"element",tagName:"code",properties:a,children:[{type:"text",value:r}]};return t.meta&&(o.data={meta:t.meta}),e.patch(t,o),o=e.applyData(t,o),o={type:"element",tagName:"pre",properties:{},children:[o]},e.patch(t,o),o}function LO(e,t){const r={type:"element",tagName:"del",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function zO(e,t){const r={type:"element",tagName:"em",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function IO(e,t){const r=typeof e.options.clobberPrefix=="string"?e.options.clobberPrefix:"user-content-",a=String(t.identifier).toUpperCase(),s=rl(a.toLowerCase()),o=e.footnoteOrder.indexOf(a);let c,d=e.footnoteCounts.get(a);d===void 0?(d=0,e.footnoteOrder.push(a),c=e.footnoteOrder.length):c=o+1,d+=1,e.footnoteCounts.set(a,d);const h={type:"element",tagName:"a",properties:{href:"#"+r+"fn-"+s,id:r+"fnref-"+s+(d>1?"-"+d:""),dataFootnoteRef:!0,ariaDescribedBy:["footnote-label"]},children:[{type:"text",value:String(c)}]};e.patch(t,h);const f={type:"element",tagName:"sup",properties:{},children:[h]};return e.patch(t,f),e.applyData(t,f)}function BO(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 UO(e,t){if(e.options.allowDangerousHtml){const r={type:"raw",value:t.value};return e.patch(t,r),e.applyData(t,r)}}function Pw(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 s=e.all(t),o=s[0];o&&o.type==="text"?o.value="["+o.value:s.unshift({type:"text",value:"["});const c=s[s.length-1];return c&&c.type==="text"?c.value+=a:s.push({type:"text",value:a}),s}function HO(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return Pw(e,t);const s={src:rl(a.url||""),alt:t.alt};a.title!==null&&a.title!==void 0&&(s.title=a.title);const o={type:"element",tagName:"img",properties:s,children:[]};return e.patch(t,o),e.applyData(t,o)}function $O(e,t){const r={src:rl(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 qO(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 PO(e,t){const r=String(t.identifier).toUpperCase(),a=e.definitionById.get(r);if(!a)return Pw(e,t);const s={href:rl(a.url||"")};a.title!==null&&a.title!==void 0&&(s.title=a.title);const o={type:"element",tagName:"a",properties:s,children:e.all(t)};return e.patch(t,o),e.applyData(t,o)}function FO(e,t){const r={href:rl(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 GO(e,t,r){const a=e.all(t),s=r?VO(r):Fw(t),o={},c=[];if(typeof t.checked=="boolean"){const p=a[0];let g;p&&p.type==="element"&&p.tagName==="p"?g=p:(g={type:"element",tagName:"p",properties:{},children:[]},a.unshift(g)),g.children.length>0&&g.children.unshift({type:"text",value:" "}),g.children.unshift({type:"element",tagName:"input",properties:{type:"checkbox",checked:t.checked,disabled:!0},children:[]}),o.className=["task-list-item"]}let d=-1;for(;++d<a.length;){const p=a[d];(s||d!==0||p.type!=="element"||p.tagName!=="p")&&c.push({type:"text",value:`
|
||||
`}),p.type==="element"&&p.tagName==="p"&&!s?c.push(...p.children):c.push(p)}const h=a[a.length-1];h&&(s||h.type!=="element"||h.tagName!=="p")&&c.push({type:"text",value:`
|
||||
`});const f={type:"element",tagName:"li",properties:o,children:c};return e.patch(t,f),e.applyData(t,f)}function PO(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=Hw(r[a])}return t}function Hw(e){const t=e.spread;return t??e.children.length>1}function FO(e,t){const r={},a=e.all(t);let s=-1;for(typeof t.start=="number"&&t.start!==1&&(r.start=t.start);++s<a.length;){const c=a[s];if(c.type==="element"&&c.tagName==="li"&&c.properties&&Array.isArray(c.properties.className)&&c.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const o={type:"element",tagName:t.ordered?"ol":"ul",properties:r,children:e.wrap(a,!0)};return e.patch(t,o),e.applyData(t,o)}function GO(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function VO(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function YO(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function XO(e,t){const r=e.all(t),a=r.shift(),s=[];if(a){const c={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],c),s.push(c)}if(r.length>0){const c={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},d=Rp(t.children[1]),h=yw(t.children[t.children.length-1]);d&&h&&(c.position={start:d,end:h}),s.push(c)}const o={type:"element",tagName:"table",properties:{},children:e.wrap(s,!0)};return e.patch(t,o),e.applyData(t,o)}function KO(e,t,r){const a=r?r.children:void 0,o=(a?a.indexOf(t):1)===0?"th":"td",c=r&&r.type==="table"?r.align:void 0,d=c?c.length:t.children.length;let h=-1;const f=[];for(;++h<d;){const g=t.children[h],b={},y=c?c[h]:void 0;y&&(b.align=y);let _={type:"element",tagName:o,properties:b,children:[]};g&&(_.children=e.all(g),e.patch(g,_),_=e.applyData(g,_)),f.push(_)}const p={type:"element",tagName:"tr",properties:{},children:e.wrap(f,!0)};return e.patch(t,p),e.applyData(t,p)}function ZO(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const zy=9,Iy=32;function QO(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),s=0;const o=[];for(;a;)o.push(By(t.slice(s,a.index),s>0,!0),a[0]),s=a.index+a[0].length,a=r.exec(t);return o.push(By(t.slice(s),s>0,!1)),o.join("")}function By(e,t,r){let a=0,s=e.length;if(t){let o=e.codePointAt(a);for(;o===zy||o===Iy;)a++,o=e.codePointAt(a)}if(r){let o=e.codePointAt(s-1);for(;o===zy||o===Iy;)s--,o=e.codePointAt(s-1)}return s>a?e.slice(a,s):""}function WO(e,t){const r={type:"text",value:QO(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function JO(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const eR={blockquote:AO,break:MO,code:OO,delete:RO,emphasis:jO,footnoteReference:DO,heading:LO,html:zO,imageReference:IO,image:BO,inlineCode:UO,linkReference:HO,link:$O,listItem:qO,list:FO,paragraph:GO,root:VO,strong:YO,table:XO,tableCell:ZO,tableRow:KO,text:WO,thematicBreak:JO,toml:tu,yaml:tu,definition:tu,footnoteDefinition:tu};function tu(){}const $w=-1,Ku=0,uo=1,ku=2,Up=3,Hp=4,$p=5,qp=6,qw=7,Pw=8,Fw=typeof self=="object"?self:globalThis,Uy=(e,t)=>{switch(e){case"Function":case"SharedWorker":case"Worker":case"eval":case"setInterval":case"setTimeout":throw new TypeError("unable to deserialize "+e)}return new Fw[e](t)},tR=(e,t)=>{const r=(s,o)=>(e.set(o,s),s),a=s=>{if(e.has(s))return e.get(s);const[o,c]=t[s];switch(o){case Ku:case $w:return r(c,s);case uo:{const d=r([],s);for(const h of c)d.push(a(h));return d}case ku:{const d=r({},s);for(const[h,f]of c)d[a(h)]=a(f);return d}case Up:return r(new Date(c),s);case Hp:{const{source:d,flags:h}=c;return r(new RegExp(d,h),s)}case $p:{const d=r(new Map,s);for(const[h,f]of c)d.set(a(h),a(f));return d}case qp:{const d=r(new Set,s);for(const h of c)d.add(a(h));return d}case qw:{const{name:d,message:h}=c;return r(typeof Fw[d]=="function"?Uy(d,h):new Error(h),s)}case Pw:return r(BigInt(c),s);case"BigInt":return r(Object(BigInt(c)),s);case"ArrayBuffer":return r(new Uint8Array(c).buffer,c);case"DataView":{const{buffer:d}=new Uint8Array(c);return r(new DataView(d),c)}}return r(Uy(o,c),s)};return a},Hy=e=>tR(new Map,e)(0),Ua="",{toString:nR}={},{keys:rR}=Object,eo=e=>{const t=typeof e;if(t!=="object"||!e)return[Ku,t];const r=nR.call(e).slice(8,-1);switch(r){case"Array":return[uo,Ua];case"Object":return[ku,Ua];case"Date":return[Up,Ua];case"RegExp":return[Hp,Ua];case"Map":return[$p,Ua];case"Set":return[qp,Ua];case"DataView":return[uo,r]}return r.includes("Array")?[uo,r]:e instanceof Error?[qw,e.name||"Error"]:[ku,r]},nu=([e,t])=>e===Ku&&(t==="function"||t==="symbol"),iR=(e,t,r,a)=>{const s=(c,d)=>{const h=a.push(c)-1;return r.set(d,h),h},o=c=>{if(r.has(c))return r.get(c);let[d,h]=eo(c);switch(d){case Ku:{let p=c;switch(h){case"bigint":d=Pw,p=c.toString();break;case"function":case"symbol":
Line truncated
|
||||
`});const f={type:"element",tagName:"li",properties:o,children:c};return e.patch(t,f),e.applyData(t,f)}function VO(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=Fw(r[a])}return t}function Fw(e){const t=e.spread;return t??e.children.length>1}function YO(e,t){const r={},a=e.all(t);let s=-1;for(typeof t.start=="number"&&t.start!==1&&(r.start=t.start);++s<a.length;){const c=a[s];if(c.type==="element"&&c.tagName==="li"&&c.properties&&Array.isArray(c.properties.className)&&c.properties.className.includes("task-list-item")){r.className=["contains-task-list"];break}}const o={type:"element",tagName:t.ordered?"ol":"ul",properties:r,children:e.wrap(a,!0)};return e.patch(t,o),e.applyData(t,o)}function XO(e,t){const r={type:"element",tagName:"p",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function KO(e,t){const r={type:"root",children:e.wrap(e.all(t))};return e.patch(t,r),e.applyData(t,r)}function ZO(e,t){const r={type:"element",tagName:"strong",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}function QO(e,t){const r=e.all(t),a=r.shift(),s=[];if(a){const c={type:"element",tagName:"thead",properties:{},children:e.wrap([a],!0)};e.patch(t.children[0],c),s.push(c)}if(r.length>0){const c={type:"element",tagName:"tbody",properties:{},children:e.wrap(r,!0)},d=Dp(t.children[1]),h=Ew(t.children[t.children.length-1]);d&&h&&(c.position={start:d,end:h}),s.push(c)}const o={type:"element",tagName:"table",properties:{},children:e.wrap(s,!0)};return e.patch(t,o),e.applyData(t,o)}function WO(e,t,r){const a=r?r.children:void 0,o=(a?a.indexOf(t):1)===0?"th":"td",c=r&&r.type==="table"?r.align:void 0,d=c?c.length:t.children.length;let h=-1;const f=[];for(;++h<d;){const g=t.children[h],b={},y=c?c[h]:void 0;y&&(b.align=y);let _={type:"element",tagName:o,properties:b,children:[]};g&&(_.children=e.all(g),e.patch(g,_),_=e.applyData(g,_)),f.push(_)}const p={type:"element",tagName:"tr",properties:{},children:e.wrap(f,!0)};return e.patch(t,p),e.applyData(t,p)}function JO(e,t){const r={type:"element",tagName:"td",properties:{},children:e.all(t)};return e.patch(t,r),e.applyData(t,r)}const By=9,Uy=32;function eR(e){const t=String(e),r=/\r?\n|\r/g;let a=r.exec(t),s=0;const o=[];for(;a;)o.push(Hy(t.slice(s,a.index),s>0,!0),a[0]),s=a.index+a[0].length,a=r.exec(t);return o.push(Hy(t.slice(s),s>0,!1)),o.join("")}function Hy(e,t,r){let a=0,s=e.length;if(t){let o=e.codePointAt(a);for(;o===By||o===Uy;)a++,o=e.codePointAt(a)}if(r){let o=e.codePointAt(s-1);for(;o===By||o===Uy;)s--,o=e.codePointAt(s-1)}return s>a?e.slice(a,s):""}function tR(e,t){const r={type:"text",value:eR(String(t.value))};return e.patch(t,r),e.applyData(t,r)}function nR(e,t){const r={type:"element",tagName:"hr",properties:{},children:[]};return e.patch(t,r),e.applyData(t,r)}const rR={blockquote:RO,break:jO,code:DO,delete:LO,emphasis:zO,footnoteReference:IO,heading:BO,html:UO,imageReference:HO,image:$O,inlineCode:qO,linkReference:PO,link:FO,listItem:GO,list:YO,paragraph:XO,root:KO,strong:ZO,table:QO,tableCell:JO,tableRow:WO,text:tR,thematicBreak:nR,toml:nu,yaml:nu,definition:nu,footnoteDefinition:nu};function nu(){}const Gw=-1,Zu=0,uo=1,Cu=2,$p=3,qp=4,Pp=5,Fp=6,Vw=7,Yw=8,Xw=typeof self=="object"?self:globalThis,$y=(e,t)=>{switch(e){case"Function":case"SharedWorker":case"Worker":case"eval":case"setInterval":case"setTimeout":throw new TypeError("unable to deserialize "+e)}return new Xw[e](t)},iR=(e,t)=>{const r=(s,o)=>(e.set(o,s),s),a=s=>{if(e.has(s))return e.get(s);const[o,c]=t[s];switch(o){case Zu:case Gw:return r(c,s);case uo:{const d=r([],s);for(const h of c)d.push(a(h));return d}case Cu:{const d=r({},s);for(const[h,f]of c)d[a(h)]=a(f);return d}case $p:return r(new Date(c),s);case qp:{const{source:d,flags:h}=c;return r(new RegExp(d,h),s)}case Pp:{const d=r(new Map,s);for(const[h,f]of c)d.set(a(h),a(f));return d}case Fp:{const d=r(new Set,s);for(const h of c)d.add(a(h));return d}case Vw:{const{name:d,message:h}=c;return r(typeof Xw[d]=="function"?$y(d,h):new Error(h),s)}case Yw:return r(BigInt(c),s);case"BigInt":return r(Object(BigInt(c)),s);case"ArrayBuffer":return r(new Uint8Array(c).buffer,c);case"DataView":{const{buffer:d}=new Uint8Array(c);return r(new DataView(d),c)}}return r($y(o,c),s)};return a},qy=e=>iR(new Map,e)(0),Ua="",{toString:aR}={},{keys:sR}=Object,eo=e=>{const t=typeof e;if(t!=="object"||!e)return[Zu,t];const r=aR.call(e).slice(8,-1);switch(r){case"Array":return[uo,Ua];case"Object":return[Cu,Ua];case"Date":return[$p,Ua];case"RegExp":return[qp,Ua];case"Map":return[Pp,Ua];case"Set":return[Fp,Ua];case"DataView":return[uo,r]}return r.includes("Array")?[uo,r]:e instanceof Error?[Vw,e.name||"Error"]:[Cu,r]},ru=([e,t])=>e===Zu&&(t==="function"||t==="symbol"),lR=(e,t,r,a)=>{const s=(c,d)=>{const h=a.push(c)-1;return r.set(d,h),h},o=c=>{if(r.has(c))return r.get(c);let[d,h]=eo(c);switch(d){case Zu:{let p=c;switch(h){case"bigint":d=Yw,p=c.toString();break;case"function":case"symbol":
Line truncated
|
||||
`},{type:"element",tagName:"ol",properties:{},children:e.wrap(d,!0)},{type:"text",value:`
|
||||
`}]}}const Zu=(function(e){if(e==null)return dR;if(typeof e=="function")return Qu(e);if(typeof e=="object")return Array.isArray(e)?oR(e):cR(e);if(typeof e=="string")return uR(e);throw new Error("Expected function, string, or object as test")});function oR(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=Zu(e[r]);return Qu(a);function a(...s){let o=-1;for(;++o<t.length;)if(t[o].apply(this,s))return!0;return!1}}function cR(e){const t=e;return Qu(r);function r(a){const s=a;let o;for(o in e)if(s[o]!==t[o])return!1;return!0}}function uR(e){return Qu(t);function t(r){return r&&r.type===e}}function Qu(e){return t;function t(r,a,s){return!!(fR(r)&&e.call(this,r,typeof a=="number"?a:void 0,s||void 0))}}function dR(){return!0}function fR(e){return e!==null&&typeof e=="object"&&"type"in e}const Gw=[],hR=!0,Jm=!1,mR="skip";function Vw(e,t,r,a){let s;typeof t=="function"&&typeof r!="function"?(a=r,r=t):s=t;const o=Zu(s),c=a?-1:1;d(e,void 0,[])();function d(h,f,p){const g=h&&typeof h=="object"?h:{};if(typeof g.type=="string"){const y=typeof g.tagName=="string"?g.tagName:typeof g.name=="string"?g.name:void 0;Object.defineProperty(b,"name",{value:"node ("+(h.type+(y?"<"+y+">":""))+")"})}return b;function b(){let y=Gw,_,N,S;if((!t||o(h,f,p[p.length-1]||void 0))&&(y=pR(r(h,p)),y[0]===Jm))return y;if("children"in h&&h.children){const w=h;if(w.children&&y[0]!==mR)for(N=(a?w.children.length:-1)+c,S=p.concat(w);N>-1&&N<w.children.length;){const C=w.children[N];if(_=d(C,N,S)(),_[0]===Jm)return _;N=typeof _[1]=="number"?_[1]:N+c}}return y}}}function pR(e){return Array.isArray(e)?e:typeof e=="number"?[hR,e]:e==null?Gw:[e]}function Pp(e,t,r,a){let s,o,c;typeof t=="function"&&typeof r!="function"?(o=void 0,c=t,s=r):(o=t,c=r,s=a),Vw(e,o,d,s);function d(h,f){const p=f[f.length-1],g=p?p.children.indexOf(h):void 0;return c(h,g,p)}}const ep={}.hasOwnProperty,gR={};function xR(e,t){const r=t||gR,a=new Map,s=new Map,o=new Map,c={...eR,...r.handlers},d={all:f,applyData:yR,definitionById:a,footnoteById:s,footnoteCounts:o,footnoteOrder:[],handlers:c,one:h,options:r,patch:bR,wrap:_R};return Pp(e,function(p){if(p.type==="definition"||p.type==="footnoteDefinition"){const g=p.type==="definition"?a:s,b=String(p.identifier).toUpperCase();g.has(b)||g.set(b,p)}}),d;function h(p,g){const b=p.type,y=d.handlers[b];if(ep.call(d.handlers,b)&&y)return y(d,p,g);if(d.options.passThrough&&d.options.passThrough.includes(b)){if("children"in p){const{children:N,...S}=p,w=Cu(S);return w.children=d.all(p),w}return Cu(p)}return(d.options.unknownHandler||vR)(d,p,g)}function f(p){const g=[];if("children"in p){const b=p.children;let y=-1;for(;++y<b.length;){const _=d.one(b[y],p);if(_){if(y&&b[y-1].type==="break"&&(!Array.isArray(_)&&_.type==="text"&&(_.value=qy(_.value)),!Array.isArray(_)&&_.type==="element")){const N=_.children[0];N&&N.type==="text"&&(N.value=qy(N.value))}Array.isArray(_)?g.push(..._):g.push(_)}}}return g}}function bR(e,t){e.position&&(t.position=sM(e))}function yR(e,t){let r=t;if(e&&e.data){const a=e.data.hName,s=e.data.hChildren,o=e.data.hProperties;if(typeof a=="string")if(r.type==="element")r.tagName=a;else{const c="children"in r?r.children:[r];r={type:"element",tagName:a,properties:{},children:c}}r.type==="element"&&o&&Object.assign(r.properties,Cu(o)),"children"in r&&r.children&&s!==null&&s!==void 0&&(r.children=s)}return r}function vR(e,t){const r=t.data||{},a="value"in t&&!(ep.call(r,"hProperties")||ep.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 _R(e,t){const r=[];let a=-1;for(t&&r.push({type:"text",value:`
|
||||
`}]}}const Qu=(function(e){if(e==null)return mR;if(typeof e=="function")return Wu(e);if(typeof e=="object")return Array.isArray(e)?dR(e):fR(e);if(typeof e=="string")return hR(e);throw new Error("Expected function, string, or object as test")});function dR(e){const t=[];let r=-1;for(;++r<e.length;)t[r]=Qu(e[r]);return Wu(a);function a(...s){let o=-1;for(;++o<t.length;)if(t[o].apply(this,s))return!0;return!1}}function fR(e){const t=e;return Wu(r);function r(a){const s=a;let o;for(o in e)if(s[o]!==t[o])return!1;return!0}}function hR(e){return Wu(t);function t(r){return r&&r.type===e}}function Wu(e){return t;function t(r,a,s){return!!(pR(r)&&e.call(this,r,typeof a=="number"?a:void 0,s||void 0))}}function mR(){return!0}function pR(e){return e!==null&&typeof e=="object"&&"type"in e}const Kw=[],gR=!0,ep=!1,xR="skip";function Zw(e,t,r,a){let s;typeof t=="function"&&typeof r!="function"?(a=r,r=t):s=t;const o=Qu(s),c=a?-1:1;d(e,void 0,[])();function d(h,f,p){const g=h&&typeof h=="object"?h:{};if(typeof g.type=="string"){const y=typeof g.tagName=="string"?g.tagName:typeof g.name=="string"?g.name:void 0;Object.defineProperty(b,"name",{value:"node ("+(h.type+(y?"<"+y+">":""))+")"})}return b;function b(){let y=Kw,_,N,S;if((!t||o(h,f,p[p.length-1]||void 0))&&(y=bR(r(h,p)),y[0]===ep))return y;if("children"in h&&h.children){const w=h;if(w.children&&y[0]!==xR)for(N=(a?w.children.length:-1)+c,S=p.concat(w);N>-1&&N<w.children.length;){const C=w.children[N];if(_=d(C,N,S)(),_[0]===ep)return _;N=typeof _[1]=="number"?_[1]:N+c}}return y}}}function bR(e){return Array.isArray(e)?e:typeof e=="number"?[gR,e]:e==null?Kw:[e]}function Gp(e,t,r,a){let s,o,c;typeof t=="function"&&typeof r!="function"?(o=void 0,c=t,s=r):(o=t,c=r,s=a),Zw(e,o,d,s);function d(h,f){const p=f[f.length-1],g=p?p.children.indexOf(h):void 0;return c(h,g,p)}}const tp={}.hasOwnProperty,yR={};function vR(e,t){const r=t||yR,a=new Map,s=new Map,o=new Map,c={...rR,...r.handlers},d={all:f,applyData:wR,definitionById:a,footnoteById:s,footnoteCounts:o,footnoteOrder:[],handlers:c,one:h,options:r,patch:_R,wrap:NR};return Gp(e,function(p){if(p.type==="definition"||p.type==="footnoteDefinition"){const g=p.type==="definition"?a:s,b=String(p.identifier).toUpperCase();g.has(b)||g.set(b,p)}}),d;function h(p,g){const b=p.type,y=d.handlers[b];if(tp.call(d.handlers,b)&&y)return y(d,p,g);if(d.options.passThrough&&d.options.passThrough.includes(b)){if("children"in p){const{children:N,...S}=p,w=Tu(S);return w.children=d.all(p),w}return Tu(p)}return(d.options.unknownHandler||ER)(d,p,g)}function f(p){const g=[];if("children"in p){const b=p.children;let y=-1;for(;++y<b.length;){const _=d.one(b[y],p);if(_){if(y&&b[y-1].type==="break"&&(!Array.isArray(_)&&_.type==="text"&&(_.value=Fy(_.value)),!Array.isArray(_)&&_.type==="element")){const N=_.children[0];N&&N.type==="text"&&(N.value=Fy(N.value))}Array.isArray(_)?g.push(..._):g.push(_)}}}return g}}function _R(e,t){e.position&&(t.position=cM(e))}function wR(e,t){let r=t;if(e&&e.data){const a=e.data.hName,s=e.data.hChildren,o=e.data.hProperties;if(typeof a=="string")if(r.type==="element")r.tagName=a;else{const c="children"in r?r.children:[r];r={type:"element",tagName:a,properties:{},children:c}}r.type==="element"&&o&&Object.assign(r.properties,Tu(o)),"children"in r&&r.children&&s!==null&&s!==void 0&&(r.children=s)}return r}function ER(e,t){const r=t.data||{},a="value"in t&&!(tp.call(r,"hProperties")||tp.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 NR(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 qy(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function Py(e,t){const r=xR(e,t),a=r.one(e,void 0),s=lR(r),o=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return s&&o.children.push({type:"text",value:`
|
||||
`},s),o}function wR(e,t){return e&&"run"in e?async function(r,a){const s=Py(r,{file:a,...t});await e.run(s,a)}:function(r,a){return Py(r,{file:a,...e||t})}}function Fy(e){if(e)throw e}var Sh,Gy;function ER(){if(Gy)return Sh;Gy=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,s=function(f){return typeof Array.isArray=="function"?Array.isArray(f):t.call(f)==="[object Array]"},o=function(f){if(!f||t.call(f)!=="[object Object]")return!1;var p=e.call(f,"constructor"),g=f.constructor&&f.constructor.prototype&&e.call(f.constructor.prototype,"isPrototypeOf");if(f.constructor&&!p&&!g)return!1;var b;for(b in f);return typeof b>"u"||e.call(f,b)},c=function(f,p){r&&p.name==="__proto__"?r(f,p.name,{enumerable:!0,configurable:!0,value:p.newValue,writable:!0}):f[p.name]=p.newValue},d=function(f,p){if(p==="__proto__")if(e.call(f,p)){if(a)return a(f,p).value}else return;return f[p]};return Sh=function h(){var f,p,g,b,y,_,N=arguments[0],S=1,w=arguments.length,C=!1;for(typeof N=="boolean"&&(C=N,N=arguments[1]||{},S=2),(N==null||typeof N!="object"&&typeof N!="function")&&(N={});S<w;++S)if(f=arguments[S],f!=null)for(p in f)g=d(N,p),b=d(f,p),N!==b&&(C&&b&&(o(b)||(y=s(b)))?(y?(y=!1,_=g&&s(g)?g:[]):_=g&&o(g)?g:{},c(N,{name:p,newValue:h(C,_,b)})):typeof b<"u"&&c(N,{name:p,newValue:b}));return N},Sh}var NR=ER();const kh=Ao(NR);function tp(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 SR(){const e=[],t={run:r,use:a};return t;function r(...s){let o=-1;const c=s.pop();if(typeof c!="function")throw new TypeError("Expected function as last argument, not "+c);d(null,...s);function d(h,...f){const p=e[++o];let g=-1;if(h){c(h);return}for(;++g<s.length;)(f[g]===null||f[g]===void 0)&&(f[g]=s[g]);s=f,p?kR(p,d)(...f):c(null,...f)}}function a(s){if(typeof s!="function")throw new TypeError("Expected `middelware` to be a function, not "+s);return e.push(s),t}}function kR(e,t){let r;return a;function a(...c){const d=e.length>c.length;let h;d&&c.push(s);try{h=e.apply(this,c)}catch(f){const p=f;if(d&&r)throw p;return s(p)}d||(h&&h.then&&typeof h.then=="function"?h.then(o,s):h instanceof Error?s(h):o(h))}function s(c,...d){r||(r=!0,t(c,...d))}function o(c){s(null,c)}}const Gr={basename:CR,dirname:TR,extname:AR,join:MR,sep:"/"};function CR(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Do(e);let r=0,a=-1,s=e.length,o;if(t===void 0||t.length===0||t.length>e.length){for(;s--;)if(e.codePointAt(s)===47){if(o){r=s+1;break}}else a<0&&(o=!0,a=s+1);return a<0?"":e.slice(r,a)}if(t===e)return"";let c=-1,d=t.length-1;for(;s--;)if(e.codePointAt(s)===47){if(o){r=s+1;break}}else c<0&&(o=!0,c=s+1),d>-1&&(e.codePointAt(s)===t.codePointAt(d--)?d<0&&(a=s):(d=-1,a=c));return r===a?a=c:a<0&&(a=e.length),e.slice(r,a)}function TR(e){if(Do(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 AR(e){Do(e);let t=e.length,r=-1,a=0,s=-1,o=0,c;for(;t--;){const d=e.codePointAt(t);if(d===47){if(c){a=t+1;break}continue}r<0&&(c=!0,r=t+1),d===46?s<0?s=t:o!==1&&(o=1):s>-1&&(o=-1)}return s<0||r<0||o===0||o===1&&s===r-1&&s===a+1?"":e.slice(s,r)}function MR(...e){let t=-1,r;for(;++t<e.length;)Do(e[t]),e[t]&&(r=r===void 0?e[t]:r+"/"+e[t]);return r===void 0?".":OR(r)}function OR(e){Do(e);const t=e.codePointAt(0)===47;let r=RR(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function RR(e,t){let r="",a=0,s=-1,o=0,c=-1,d,h;for(;++c<=e.length;){if(c<e.length)d=e.codePointAt(c);else{if(d===47)break;d=47}if(d===47){if(!(s===c-1||o===1))if(s!==c-1&&o===2){if(r.length<2||a!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(h=r.lastIndexOf("/"),h!==r.length-1){h<0?(r="",a=0):(r=r.slice(0,h),a=r.length-1-r.lastIndexOf("/")),s=c,o=0;continue}}else if(r.length>0){r="",a=0,s=c,o=0;continue}}t&&(r=r.length>0?r+"/..":"..",a=2)}else r.length>0?r+="/"+e.slice(s+1,c):r=e.slice(s+1,c),a=c-s-1;s=c,o=0}else d===46&&o>-1?o++:o=-1}return r}function Do(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const jR={cwd:DR};function DR(){return"/"}function np(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function LR(e){if(typeof e=="string")e=new URL(e);else if(!np(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 zR(e)}function zR(e){if(e.hostname!==""){consLine truncated
|
||||
`:" ")+o.indentLines(o.containerFlow(a,d.current()),t?Zw:S3))),f(),h}}function S3(e,t,r){return t===0?e:Zw(e,t,r)}function Zw(e,t,r){return(r?"":" ")+e}const k3=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];Qw.peek=O3;function C3(){return{canContainEols:["delete"],enter:{strikethrough:A3},exit:{strikethrough:M3}}}function T3(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:k3}],handlers:{delete:Qw}}}function A3(e){this.enter({type:"delete",children:[]},e)}function M3(e){this.exit(e)}function Qw(e,t,r,a){const s=r.createTracker(a),o=r.enter("strikethrough");let c=s.move("~~");return c+=r.containerPhrasing(e,{...s.current(),before:c,after:"~"}),c+=s.move("~~"),o(),c}function O3(){return"~"}function R3(e){return e.length}function j3(e,t){const r=t||{},a=(r.align||[]).concat(),s=r.stringLength||R3,o=[],c=[],d=[],h=[];let f=0,p=-1;for(;++p<e.length;){const N=[],S=[];let w=-1;for(e[p].length>f&&(f=e[p].length);++w<e[p].length;){const C=D3(e[p][w]);if(r.alignDelimiters!==!1){const E=s(C);S[w]=E,(h[w]===void 0||E>h[w])&&(h[w]=E)}N.push(C)}c[p]=N,d[p]=S}let g=-1;if(typeof a=="object"&&"length"in a)for(;++g<f;)o[g]=Wy(a[g]);else{const N=Wy(a);for(;++g<f;)o[g]=N}g=-1;const b=[],y=[];for(;++g<f;){const N=o[g];let S="",w="";N===99?(S=":",w=":"):N===108?S=":":N===114&&(w=":");let C=r.alignDelimiters===!1?1:Math.max(1,h[g]-S.length-w.length);const E=S+"-".repeat(C)+w;r.alignDelimiters!==!1&&(C=S.length+C+w.length,C>h[g]&&(h[g]=C),y[g]=C),b[g]=E}c.splice(1,0,b),d.splice(1,0,y),p=-1;const _=[];for(;++p<c.length;){const N=c[p],S=d[p];g=-1;const w=[];for(;++g<f;){const C=N[g]||"";let E="",A="";if(r.alignDelimiters!==!1){const B=h[g]-(S[g]||0),R=o[g];R===114?E=" ".repeat(B):R===99?B%2?(E=" ".repeat(B/2+.5),A=" ".repeat(B/2-.5)):(E=" ".repeat(B/2),A=E):A=" ".repeat(B)}r.delimiterStart!==!1&&!g&&w.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&C==="")&&(r.delimiterStart!==!1||g)&&w.push(" "),r.alignDelimiters!==!1&&w.push(E),w.push(C),r.alignDelimiters!==!1&&w.push(A),r.padding!==!1&&w.push(" "),(r.delimiterEnd!==!1||g!==f-1)&&w.push("|")}_.push(r.delimiterEnd===!1?w.join("").replace(/ +$/,""):w.join(""))}return _.join(`
|
||||
`)}function D3(e){return e==null?"":String(e)}function Wy(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 L3(e,t,r,a){const s=r.enter("blockquote"),o=r.createTracker(a);o.move("> "),o.shift(2);const c=r.indentLines(r.containerFlow(e,o.current()),z3);return s(),c}function z3(e,t,r){return">"+(r?"":" ")+e}function I3(e,t){return Jy(e,t.inConstruct,!0)&&!Jy(e,t.notInConstruct,!1)}function Jy(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 ev(e,t,r,a){let s=-1;for(;++s<r.unsafe.length;)if(r.unsafe[s].character===`
|
||||
`&&I3(r.stack,r.unsafe[s]))return/[ \t]/.test(a.before)?"":" ";return`\\
|
||||
`}function B3(e,t){const r=String(e);let a=r.indexOf(t),s=a,o=0,c=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===s?++o>c&&(c=o):o=1,s=a+t.length,a=r.indexOf(t,s);return c}function U3(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 H3(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 $3(e,t,r,a){const s=H3(r),o=e.value||"",c=s==="`"?"GraveAccent":"Tilde";if(U3(e,r)){const g=r.enter("codeIndented"),b=r.indentLines(o,q3);return g(),b}const d=r.createTracker(a),h=s.repeat(Math.max(B3(o,s)+1,3)),f=r.enter("codeFenced");let p=d.move(h);if(e.lang){const g=r.enter(`codeFencedLang${c}`);p+=d.move(r.safe(e.lang,{before:p,after:" ",encode:["`"],...d.current()})),g()}if(e.lang&&e.meta){const g=r.enter(`codeFencedMeta${c}`);p+=d.move(" "),p+=d.move(r.safe(e.meta,{before:p,after:`
|
||||
`}),r}function Fy(e){let t=0,r=e.charCodeAt(t);for(;r===9||r===32;)t++,r=e.charCodeAt(t);return e.slice(t)}function Gy(e,t){const r=vR(e,t),a=r.one(e,void 0),s=uR(r),o=Array.isArray(a)?{type:"root",children:a}:a||{type:"root",children:[]};return s&&o.children.push({type:"text",value:`
|
||||
`},s),o}function SR(e,t){return e&&"run"in e?async function(r,a){const s=Gy(r,{file:a,...t});await e.run(s,a)}:function(r,a){return Gy(r,{file:a,...e||t})}}function Vy(e){if(e)throw e}var kh,Yy;function kR(){if(Yy)return kh;Yy=1;var e=Object.prototype.hasOwnProperty,t=Object.prototype.toString,r=Object.defineProperty,a=Object.getOwnPropertyDescriptor,s=function(f){return typeof Array.isArray=="function"?Array.isArray(f):t.call(f)==="[object Array]"},o=function(f){if(!f||t.call(f)!=="[object Object]")return!1;var p=e.call(f,"constructor"),g=f.constructor&&f.constructor.prototype&&e.call(f.constructor.prototype,"isPrototypeOf");if(f.constructor&&!p&&!g)return!1;var b;for(b in f);return typeof b>"u"||e.call(f,b)},c=function(f,p){r&&p.name==="__proto__"?r(f,p.name,{enumerable:!0,configurable:!0,value:p.newValue,writable:!0}):f[p.name]=p.newValue},d=function(f,p){if(p==="__proto__")if(e.call(f,p)){if(a)return a(f,p).value}else return;return f[p]};return kh=function h(){var f,p,g,b,y,_,N=arguments[0],S=1,w=arguments.length,C=!1;for(typeof N=="boolean"&&(C=N,N=arguments[1]||{},S=2),(N==null||typeof N!="object"&&typeof N!="function")&&(N={});S<w;++S)if(f=arguments[S],f!=null)for(p in f)g=d(N,p),b=d(f,p),N!==b&&(C&&b&&(o(b)||(y=s(b)))?(y?(y=!1,_=g&&s(g)?g:[]):_=g&&o(g)?g:{},c(N,{name:p,newValue:h(C,_,b)})):typeof b<"u"&&c(N,{name:p,newValue:b}));return N},kh}var CR=kR();const Ch=Mo(CR);function np(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 TR(){const e=[],t={run:r,use:a};return t;function r(...s){let o=-1;const c=s.pop();if(typeof c!="function")throw new TypeError("Expected function as last argument, not "+c);d(null,...s);function d(h,...f){const p=e[++o];let g=-1;if(h){c(h);return}for(;++g<s.length;)(f[g]===null||f[g]===void 0)&&(f[g]=s[g]);s=f,p?AR(p,d)(...f):c(null,...f)}}function a(s){if(typeof s!="function")throw new TypeError("Expected `middelware` to be a function, not "+s);return e.push(s),t}}function AR(e,t){let r;return a;function a(...c){const d=e.length>c.length;let h;d&&c.push(s);try{h=e.apply(this,c)}catch(f){const p=f;if(d&&r)throw p;return s(p)}d||(h&&h.then&&typeof h.then=="function"?h.then(o,s):h instanceof Error?s(h):o(h))}function s(c,...d){r||(r=!0,t(c,...d))}function o(c){s(null,c)}}const Gr={basename:MR,dirname:OR,extname:RR,join:jR,sep:"/"};function MR(e,t){if(t!==void 0&&typeof t!="string")throw new TypeError('"ext" argument must be a string');Lo(e);let r=0,a=-1,s=e.length,o;if(t===void 0||t.length===0||t.length>e.length){for(;s--;)if(e.codePointAt(s)===47){if(o){r=s+1;break}}else a<0&&(o=!0,a=s+1);return a<0?"":e.slice(r,a)}if(t===e)return"";let c=-1,d=t.length-1;for(;s--;)if(e.codePointAt(s)===47){if(o){r=s+1;break}}else c<0&&(o=!0,c=s+1),d>-1&&(e.codePointAt(s)===t.codePointAt(d--)?d<0&&(a=s):(d=-1,a=c));return r===a?a=c:a<0&&(a=e.length),e.slice(r,a)}function OR(e){if(Lo(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 RR(e){Lo(e);let t=e.length,r=-1,a=0,s=-1,o=0,c;for(;t--;){const d=e.codePointAt(t);if(d===47){if(c){a=t+1;break}continue}r<0&&(c=!0,r=t+1),d===46?s<0?s=t:o!==1&&(o=1):s>-1&&(o=-1)}return s<0||r<0||o===0||o===1&&s===r-1&&s===a+1?"":e.slice(s,r)}function jR(...e){let t=-1,r;for(;++t<e.length;)Lo(e[t]),e[t]&&(r=r===void 0?e[t]:r+"/"+e[t]);return r===void 0?".":DR(r)}function DR(e){Lo(e);const t=e.codePointAt(0)===47;let r=LR(e,!t);return r.length===0&&!t&&(r="."),r.length>0&&e.codePointAt(e.length-1)===47&&(r+="/"),t?"/"+r:r}function LR(e,t){let r="",a=0,s=-1,o=0,c=-1,d,h;for(;++c<=e.length;){if(c<e.length)d=e.codePointAt(c);else{if(d===47)break;d=47}if(d===47){if(!(s===c-1||o===1))if(s!==c-1&&o===2){if(r.length<2||a!==2||r.codePointAt(r.length-1)!==46||r.codePointAt(r.length-2)!==46){if(r.length>2){if(h=r.lastIndexOf("/"),h!==r.length-1){h<0?(r="",a=0):(r=r.slice(0,h),a=r.length-1-r.lastIndexOf("/")),s=c,o=0;continue}}else if(r.length>0){r="",a=0,s=c,o=0;continue}}t&&(r=r.length>0?r+"/..":"..",a=2)}else r.length>0?r+="/"+e.slice(s+1,c):r=e.slice(s+1,c),a=c-s-1;s=c,o=0}else d===46&&o>-1?o++:o=-1}return r}function Lo(e){if(typeof e!="string")throw new TypeError("Path must be a string. Received "+JSON.stringify(e))}const zR={cwd:IR};function IR(){return"/"}function rp(e){return!!(e!==null&&typeof e=="object"&&"href"in e&&e.href&&"protocol"in e&&e.protocol&&e.auth===void 0)}function BR(e){if(typeof e=="string")e=new URL(e);else if(!rp(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 UR(e)}function UR(e){if(e.hostname!==""){consLine truncated
|
||||
`:" ")+o.indentLines(o.containerFlow(a,d.current()),t?eE:T3))),f(),h}}function T3(e,t,r){return t===0?e:eE(e,t,r)}function eE(e,t,r){return(r?"":" ")+e}const A3=["autolink","destinationLiteral","destinationRaw","reference","titleQuote","titleApostrophe"];tE.peek=D3;function M3(){return{canContainEols:["delete"],enter:{strikethrough:R3},exit:{strikethrough:j3}}}function O3(){return{unsafe:[{character:"~",inConstruct:"phrasing",notInConstruct:A3}],handlers:{delete:tE}}}function R3(e){this.enter({type:"delete",children:[]},e)}function j3(e){this.exit(e)}function tE(e,t,r,a){const s=r.createTracker(a),o=r.enter("strikethrough");let c=s.move("~~");return c+=r.containerPhrasing(e,{...s.current(),before:c,after:"~"}),c+=s.move("~~"),o(),c}function D3(){return"~"}function L3(e){return e.length}function z3(e,t){const r=t||{},a=(r.align||[]).concat(),s=r.stringLength||L3,o=[],c=[],d=[],h=[];let f=0,p=-1;for(;++p<e.length;){const N=[],S=[];let w=-1;for(e[p].length>f&&(f=e[p].length);++w<e[p].length;){const C=I3(e[p][w]);if(r.alignDelimiters!==!1){const E=s(C);S[w]=E,(h[w]===void 0||E>h[w])&&(h[w]=E)}N.push(C)}c[p]=N,d[p]=S}let g=-1;if(typeof a=="object"&&"length"in a)for(;++g<f;)o[g]=ev(a[g]);else{const N=ev(a);for(;++g<f;)o[g]=N}g=-1;const b=[],y=[];for(;++g<f;){const N=o[g];let S="",w="";N===99?(S=":",w=":"):N===108?S=":":N===114&&(w=":");let C=r.alignDelimiters===!1?1:Math.max(1,h[g]-S.length-w.length);const E=S+"-".repeat(C)+w;r.alignDelimiters!==!1&&(C=S.length+C+w.length,C>h[g]&&(h[g]=C),y[g]=C),b[g]=E}c.splice(1,0,b),d.splice(1,0,y),p=-1;const _=[];for(;++p<c.length;){const N=c[p],S=d[p];g=-1;const w=[];for(;++g<f;){const C=N[g]||"";let E="",A="";if(r.alignDelimiters!==!1){const B=h[g]-(S[g]||0),R=o[g];R===114?E=" ".repeat(B):R===99?B%2?(E=" ".repeat(B/2+.5),A=" ".repeat(B/2-.5)):(E=" ".repeat(B/2),A=E):A=" ".repeat(B)}r.delimiterStart!==!1&&!g&&w.push("|"),r.padding!==!1&&!(r.alignDelimiters===!1&&C==="")&&(r.delimiterStart!==!1||g)&&w.push(" "),r.alignDelimiters!==!1&&w.push(E),w.push(C),r.alignDelimiters!==!1&&w.push(A),r.padding!==!1&&w.push(" "),(r.delimiterEnd!==!1||g!==f-1)&&w.push("|")}_.push(r.delimiterEnd===!1?w.join("").replace(/ +$/,""):w.join(""))}return _.join(`
|
||||
`)}function I3(e){return e==null?"":String(e)}function ev(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 B3(e,t,r,a){const s=r.enter("blockquote"),o=r.createTracker(a);o.move("> "),o.shift(2);const c=r.indentLines(r.containerFlow(e,o.current()),U3);return s(),c}function U3(e,t,r){return">"+(r?"":" ")+e}function H3(e,t){return tv(e,t.inConstruct,!0)&&!tv(e,t.notInConstruct,!1)}function tv(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 nv(e,t,r,a){let s=-1;for(;++s<r.unsafe.length;)if(r.unsafe[s].character===`
|
||||
`&&H3(r.stack,r.unsafe[s]))return/[ \t]/.test(a.before)?"":" ";return`\\
|
||||
`}function $3(e,t){const r=String(e);let a=r.indexOf(t),s=a,o=0,c=0;if(typeof t!="string")throw new TypeError("Expected substring");for(;a!==-1;)a===s?++o>c&&(c=o):o=1,s=a+t.length,a=r.indexOf(t,s);return c}function q3(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 P3(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 F3(e,t,r,a){const s=P3(r),o=e.value||"",c=s==="`"?"GraveAccent":"Tilde";if(q3(e,r)){const g=r.enter("codeIndented"),b=r.indentLines(o,G3);return g(),b}const d=r.createTracker(a),h=s.repeat(Math.max($3(o,s)+1,3)),f=r.enter("codeFenced");let p=d.move(h);if(e.lang){const g=r.enter(`codeFencedLang${c}`);p+=d.move(r.safe(e.lang,{before:p,after:" ",encode:["`"],...d.current()})),g()}if(e.lang&&e.meta){const g=r.enter(`codeFencedMeta${c}`);p+=d.move(" "),p+=d.move(r.safe(e.meta,{before:p,after:`
|
||||
`,encode:["`"],...d.current()})),g()}return p+=d.move(`
|
||||
`),o&&(p+=d.move(o+`
|
||||
`)),p+=d.move(h),f(),p}function q3(e,t,r){return(r?"":" ")+e}function Vp(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 P3(e,t,r,a){const s=Vp(r),o=s==='"'?"Quote":"Apostrophe",c=r.enter("definition");let d=r.enter("label");const h=r.createTracker(a);let f=h.move("[");return f+=h.move(r.safe(r.associationId(e),{before:f,after:"]",...h.current()})),f+=h.move("]: "),d(),!e.url||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=h.move("<"),f+=h.move(r.safe(e.url,{before:f,after:">",...h.current()})),f+=h.move(">")):(d=r.enter("destinationRaw"),f+=h.move(r.safe(e.url,{before:f,after:e.title?" ":`
|
||||
`,...h.current()}))),d(),e.title&&(d=r.enter(`title${o}`),f+=h.move(" "+s),f+=h.move(r.safe(e.title,{before:f,after:s,...h.current()})),f+=h.move(s),d()),c(),f}function F3(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 bo(e){return"&#x"+e.toString(16).toUpperCase()+";"}function Tu(e,t,r){const a=Xs(e),s=Xs(t);return a===void 0?s===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}Ww.peek=G3;function Ww(e,t,r,a){const s=F3(r),o=r.enter("emphasis"),c=r.createTracker(a),d=c.move(s);let h=c.move(r.containerPhrasing(e,{after:s,before:d,...c.current()}));const f=h.charCodeAt(0),p=Tu(a.before.charCodeAt(a.before.length-1),f,s);p.inside&&(h=bo(f)+h.slice(1));const g=h.charCodeAt(h.length-1),b=Tu(a.after.charCodeAt(0),g,s);b.inside&&(h=h.slice(0,-1)+bo(g));const y=c.move(s);return o(),r.attentionEncodeSurroundingInfo={after:b.outside,before:p.outside},d+h+y}function G3(e,t,r){return r.options.emphasis||"*"}function V3(e,t){let r=!1;return Pp(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,Jm}),!!((!e.depth||e.depth<3)&&zp(e)&&(t.options.setext||r))}function Y3(e,t,r,a){const s=Math.max(Math.min(6,e.depth||1),1),o=r.createTracker(a);if(V3(e,r)){const p=r.enter("headingSetext"),g=r.enter("phrasing"),b=r.containerPhrasing(e,{...o.current(),before:`
|
||||
`)),p+=d.move(h),f(),p}function G3(e,t,r){return(r?"":" ")+e}function Xp(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 V3(e,t,r,a){const s=Xp(r),o=s==='"'?"Quote":"Apostrophe",c=r.enter("definition");let d=r.enter("label");const h=r.createTracker(a);let f=h.move("[");return f+=h.move(r.safe(r.associationId(e),{before:f,after:"]",...h.current()})),f+=h.move("]: "),d(),!e.url||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=h.move("<"),f+=h.move(r.safe(e.url,{before:f,after:">",...h.current()})),f+=h.move(">")):(d=r.enter("destinationRaw"),f+=h.move(r.safe(e.url,{before:f,after:e.title?" ":`
|
||||
`,...h.current()}))),d(),e.title&&(d=r.enter(`title${o}`),f+=h.move(" "+s),f+=h.move(r.safe(e.title,{before:f,after:s,...h.current()})),f+=h.move(s),d()),c(),f}function Y3(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 bo(e){return"&#x"+e.toString(16).toUpperCase()+";"}function Au(e,t,r){const a=Xs(e),s=Xs(t);return a===void 0?s===void 0?r==="_"?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!0}:a===1?s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!0}:{inside:!1,outside:!1}:s===void 0?{inside:!1,outside:!1}:s===1?{inside:!0,outside:!1}:{inside:!1,outside:!1}}nE.peek=X3;function nE(e,t,r,a){const s=Y3(r),o=r.enter("emphasis"),c=r.createTracker(a),d=c.move(s);let h=c.move(r.containerPhrasing(e,{after:s,before:d,...c.current()}));const f=h.charCodeAt(0),p=Au(a.before.charCodeAt(a.before.length-1),f,s);p.inside&&(h=bo(f)+h.slice(1));const g=h.charCodeAt(h.length-1),b=Au(a.after.charCodeAt(0),g,s);b.inside&&(h=h.slice(0,-1)+bo(g));const y=c.move(s);return o(),r.attentionEncodeSurroundingInfo={after:b.outside,before:p.outside},d+h+y}function X3(e,t,r){return r.options.emphasis||"*"}function K3(e,t){let r=!1;return Gp(e,function(a){if("value"in a&&/\r?\n|\r/.test(a.value)||a.type==="break")return r=!0,ep}),!!((!e.depth||e.depth<3)&&Bp(e)&&(t.options.setext||r))}function Z3(e,t,r,a){const s=Math.max(Math.min(6,e.depth||1),1),o=r.createTracker(a);if(K3(e,r)){const p=r.enter("headingSetext"),g=r.enter("phrasing"),b=r.containerPhrasing(e,{...o.current(),before:`
|
||||
`,after:`
|
||||
`});return g(),p(),b+`
|
||||
`+(s===1?"=":"-").repeat(b.length-(Math.max(b.lastIndexOf("\r"),b.lastIndexOf(`
|
||||
`))+1))}const c="#".repeat(s),d=r.enter("headingAtx"),h=r.enter("phrasing");o.move(c+" ");let f=r.containerPhrasing(e,{before:"# ",after:`
|
||||
`,...o.current()});return/^[\t ]/.test(f)&&(f=bo(f.charCodeAt(0))+f.slice(1)),f=f?c+" "+f:c,r.options.closeAtx&&(f+=" "+c),h(),d(),f}Jw.peek=X3;function Jw(e){return e.value||""}function X3(){return"<"}eE.peek=K3;function eE(e,t,r,a){const s=Vp(r),o=s==='"'?"Quote":"Apostrophe",c=r.enter("image");let d=r.enter("label");const h=r.createTracker(a);let f=h.move("![");return f+=h.move(r.safe(e.alt,{before:f,after:"]",...h.current()})),f+=h.move("]("),d(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=h.move("<"),f+=h.move(r.safe(e.url,{before:f,after:">",...h.current()})),f+=h.move(">")):(d=r.enter("destinationRaw"),f+=h.move(r.safe(e.url,{before:f,after:e.title?" ":")",...h.current()}))),d(),e.title&&(d=r.enter(`title${o}`),f+=h.move(" "+s),f+=h.move(r.safe(e.title,{before:f,after:s,...h.current()})),f+=h.move(s),d()),f+=h.move(")"),c(),f}function K3(){return"!"}tE.peek=Z3;function tE(e,t,r,a){const s=e.referenceType,o=r.enter("imageReference");let c=r.enter("label");const d=r.createTracker(a);let h=d.move("![");const f=r.safe(e.alt,{before:h,after:"]",...d.current()});h+=d.move(f+"]["),c();const p=r.stack;r.stack=[],c=r.enter("reference");const g=r.safe(r.associationId(e),{before:h,after:"]",...d.current()});return c(),r.stack=p,o(),s==="full"||!f||f!==g?h+=d.move(g+"]"):s==="shortcut"?h=h.slice(0,-1):h+=d.move("]"),h}function Z3(){return"!"}nE.peek=Q3;function nE(e,t,r){let a=e.value||"",s="`",o=-1;for(;new RegExp("(^|[^`])"+s+"([^`]|$)").test(a);)s+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++o<r.unsafe.length;){const c=r.unsafe[o],d=r.compilePattern(c);let h;if(c.atBreak)for(;h=d.exec(a);){let f=h.index;a.charCodeAt(f)===10&&a.charCodeAt(f-1)===13&&f--,a=a.slice(0,f)+" "+a.slice(h.index+1)}}return s+a+s}function Q3(){return"`"}function rE(e,t){const r=zp(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))}iE.peek=W3;function iE(e,t,r,a){const s=Vp(r),o=s==='"'?"Quote":"Apostrophe",c=r.createTracker(a);let d,h;if(rE(e,r)){const p=r.stack;r.stack=[],d=r.enter("autolink");let g=c.move("<");return g+=c.move(r.containerPhrasing(e,{before:g,after:">",...c.current()})),g+=c.move(">"),d(),r.stack=p,g}d=r.enter("link"),h=r.enter("label");let f=c.move("[");return f+=c.move(r.containerPhrasing(e,{before:f,after:"](",...c.current()})),f+=c.move("]("),h(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(h=r.enter("destinationLiteral"),f+=c.move("<"),f+=c.move(r.safe(e.url,{before:f,after:">",...c.current()})),f+=c.move(">")):(h=r.enter("destinationRaw"),f+=c.move(r.safe(e.url,{before:f,after:e.title?" ":")",...c.current()}))),h(),e.title&&(h=r.enter(`title${o}`),f+=c.move(" "+s),f+=c.move(r.safe(e.title,{before:f,after:s,...c.current()})),f+=c.move(s),h()),f+=c.move(")"),d(),f}function W3(e,t,r){return rE(e,r)?"<":"["}aE.peek=J3;function aE(e,t,r,a){const s=e.referenceType,o=r.enter("linkReference");let c=r.enter("label");const d=r.createTracker(a);let h=d.move("[");const f=r.containerPhrasing(e,{before:h,after:"]",...d.current()});h+=d.move(f+"]["),c();const p=r.stack;r.stack=[],c=r.enter("reference");const g=r.safe(r.associationId(e),{before:h,after:"]",...d.current()});return c(),r.stack=p,o(),s==="full"||!f||f!==g?h+=d.move(g+"]"):s==="shortcut"?h=h.slice(0,-1):h+=d.move("]"),h}function J3(){return"["}function Yp(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 e4(e){const t=Yp(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 t4(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 sE(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 n4(e,t,r,a){const s=r.enter("list"),o=r.bulletCurrent;let c=e.ordered?t4(r):Yp(r);const d=e.ordered?c==="."?")":".":e4(r);let h=t&&r.bulletLastUsed?c===r.bulletLastUsed:!1;if(!e.ordered){const p=e.children?e.children[0]:void 0;if((c==="*"||c==="-")&&p&&(!p.children||!p.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&&(h=!0),sE(r)===c&&p){let g=-1;for(;++g<e.chilLine truncated
|
||||
`,...o.current()});return/^[\t ]/.test(f)&&(f=bo(f.charCodeAt(0))+f.slice(1)),f=f?c+" "+f:c,r.options.closeAtx&&(f+=" "+c),h(),d(),f}rE.peek=Q3;function rE(e){return e.value||""}function Q3(){return"<"}iE.peek=W3;function iE(e,t,r,a){const s=Xp(r),o=s==='"'?"Quote":"Apostrophe",c=r.enter("image");let d=r.enter("label");const h=r.createTracker(a);let f=h.move("![");return f+=h.move(r.safe(e.alt,{before:f,after:"]",...h.current()})),f+=h.move("]("),d(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(d=r.enter("destinationLiteral"),f+=h.move("<"),f+=h.move(r.safe(e.url,{before:f,after:">",...h.current()})),f+=h.move(">")):(d=r.enter("destinationRaw"),f+=h.move(r.safe(e.url,{before:f,after:e.title?" ":")",...h.current()}))),d(),e.title&&(d=r.enter(`title${o}`),f+=h.move(" "+s),f+=h.move(r.safe(e.title,{before:f,after:s,...h.current()})),f+=h.move(s),d()),f+=h.move(")"),c(),f}function W3(){return"!"}aE.peek=J3;function aE(e,t,r,a){const s=e.referenceType,o=r.enter("imageReference");let c=r.enter("label");const d=r.createTracker(a);let h=d.move("![");const f=r.safe(e.alt,{before:h,after:"]",...d.current()});h+=d.move(f+"]["),c();const p=r.stack;r.stack=[],c=r.enter("reference");const g=r.safe(r.associationId(e),{before:h,after:"]",...d.current()});return c(),r.stack=p,o(),s==="full"||!f||f!==g?h+=d.move(g+"]"):s==="shortcut"?h=h.slice(0,-1):h+=d.move("]"),h}function J3(){return"!"}sE.peek=e4;function sE(e,t,r){let a=e.value||"",s="`",o=-1;for(;new RegExp("(^|[^`])"+s+"([^`]|$)").test(a);)s+="`";for(/[^ \r\n]/.test(a)&&(/^[ \r\n]/.test(a)&&/[ \r\n]$/.test(a)||/^`|`$/.test(a))&&(a=" "+a+" ");++o<r.unsafe.length;){const c=r.unsafe[o],d=r.compilePattern(c);let h;if(c.atBreak)for(;h=d.exec(a);){let f=h.index;a.charCodeAt(f)===10&&a.charCodeAt(f-1)===13&&f--,a=a.slice(0,f)+" "+a.slice(h.index+1)}}return s+a+s}function e4(){return"`"}function lE(e,t){const r=Bp(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))}oE.peek=t4;function oE(e,t,r,a){const s=Xp(r),o=s==='"'?"Quote":"Apostrophe",c=r.createTracker(a);let d,h;if(lE(e,r)){const p=r.stack;r.stack=[],d=r.enter("autolink");let g=c.move("<");return g+=c.move(r.containerPhrasing(e,{before:g,after:">",...c.current()})),g+=c.move(">"),d(),r.stack=p,g}d=r.enter("link"),h=r.enter("label");let f=c.move("[");return f+=c.move(r.containerPhrasing(e,{before:f,after:"](",...c.current()})),f+=c.move("]("),h(),!e.url&&e.title||/[\0- \u007F]/.test(e.url)?(h=r.enter("destinationLiteral"),f+=c.move("<"),f+=c.move(r.safe(e.url,{before:f,after:">",...c.current()})),f+=c.move(">")):(h=r.enter("destinationRaw"),f+=c.move(r.safe(e.url,{before:f,after:e.title?" ":")",...c.current()}))),h(),e.title&&(h=r.enter(`title${o}`),f+=c.move(" "+s),f+=c.move(r.safe(e.title,{before:f,after:s,...c.current()})),f+=c.move(s),h()),f+=c.move(")"),d(),f}function t4(e,t,r){return lE(e,r)?"<":"["}cE.peek=n4;function cE(e,t,r,a){const s=e.referenceType,o=r.enter("linkReference");let c=r.enter("label");const d=r.createTracker(a);let h=d.move("[");const f=r.containerPhrasing(e,{before:h,after:"]",...d.current()});h+=d.move(f+"]["),c();const p=r.stack;r.stack=[],c=r.enter("reference");const g=r.safe(r.associationId(e),{before:h,after:"]",...d.current()});return c(),r.stack=p,o(),s==="full"||!f||f!==g?h+=d.move(g+"]"):s==="shortcut"?h=h.slice(0,-1):h+=d.move("]"),h}function n4(){return"["}function Kp(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 r4(e){const t=Kp(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 i4(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 uE(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 a4(e,t,r,a){const s=r.enter("list"),o=r.bulletCurrent;let c=e.ordered?i4(r):Kp(r);const d=e.ordered?c==="."?")":".":r4(r);let h=t&&r.bulletLastUsed?c===r.bulletLastUsed:!1;if(!e.ordered){const p=e.children?e.children[0]:void 0;if((c==="*"||c==="-")&&p&&(!p.children||!p.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&&(h=!0),uE(r)===c&&p){let g=-1;for(;++g<e.chilLine truncated
|
||||
`,inConstruct:"tableCell"},{atBreak:!0,character:"|",after:"[ :-]"},{character:"|",inConstruct:"tableCell"},{atBreak:!0,character:":",after:"-"},{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{inlineCode:b,table:c,tableCell:h,tableRow:d}};function c(y,_,N,S){return f(p(y,N,S),y.align)}function d(y,_,N,S){const w=g(y,N,S),C=f([w]);return C.slice(0,C.indexOf(`
|
||||
`))}function h(y,_,N,S){const w=N.enter("tableCell"),C=N.enter("phrasing"),E=N.containerPhrasing(y,{...S,before:o,after:o});return C(),w(),E}function f(y,_){return j3(y,{align:_,alignDelimiters:a,padding:r,stringLength:s})}function p(y,_,N){const S=y.children;let w=-1;const C=[],E=_.enter("table");for(;++w<S.length;)C[w]=g(S[w],_,N);return E(),C}function g(y,_,N){const S=y.children;let w=-1;const C=[],E=_.enter("tableRow");for(;++w<S.length;)C[w]=h(S[w],y,_,N);return E(),C}function b(y,_,N){let S=oE.inlineCode(y,_,N);return N.stack.includes("tableCell")&&(S=S.replace(/\|/g,"\\$&")),S}}function v4(){return{exit:{taskListCheckValueChecked:nv,taskListCheckValueUnchecked:nv,paragraph:w4}}}function _4(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:E4}}}function nv(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function w4(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 s=t.children;let o=-1,c;for(;++o<s.length;){const d=s[o];if(d.type==="paragraph"){c=d;break}}c===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 E4(e,t,r,a){const s=e.children[0],o=typeof e.checked=="boolean"&&s&&s.type==="paragraph",c="["+(e.checked?"x":" ")+"] ",d=r.createTracker(a);o&&d.move(c);let h=oE.listItem(e,t,r,{...a,...d.current()});return o&&(h=h.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,f)),h;function f(p){return p+c}}function N4(){return[n3(),E3(),C3(),h4(),v4()]}function S4(e){return{extensions:[r3(),N3(e),T3(),y4(e),_4()]}}const k4={tokenize:R4,partial:!0},cE={tokenize:j4,partial:!0},uE={tokenize:D4,partial:!0},dE={tokenize:L4,partial:!0},C4={tokenize:z4,partial:!0},fE={name:"wwwAutolink",tokenize:M4,previous:mE},hE={name:"protocolAutolink",tokenize:O4,previous:pE},Ei={name:"emailAutolink",tokenize:A4,previous:gE},Kr={};function T4(){return{text:Kr}}let Ia=48;for(;Ia<123;)Kr[Ia]=Ei,Ia++,Ia===58?Ia=65:Ia===91&&(Ia=97);Kr[43]=Ei;Kr[45]=Ei;Kr[46]=Ei;Kr[95]=Ei;Kr[72]=[Ei,hE];Kr[104]=[Ei,hE];Kr[87]=[Ei,fE];Kr[119]=[Ei,fE];function A4(e,t,r){const a=this;let s,o;return c;function c(g){return!rp(g)||!gE.call(a,a.previous)||Xp(a.events)?r(g):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(g))}function d(g){return rp(g)?(e.consume(g),d):g===64?(e.consume(g),h):r(g)}function h(g){return g===46?e.check(C4,p,f)(g):g===45||g===95||An(g)?(o=!0,e.consume(g),h):p(g)}function f(g){return e.consume(g),s=!0,h}function p(g){return o&&s&&Ln(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(g)):r(g)}}function M4(e,t,r){const a=this;return s;function s(c){return c!==87&&c!==119||!mE.call(a,a.previous)||Xp(a.events)?r(c):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(k4,e.attempt(cE,e.attempt(uE,o),r),r)(c))}function o(c){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(c)}}function O4(e,t,r){const a=this;let s="",o=!1;return c;function c(g){return(g===72||g===104)&&pE.call(a,a.previous)&&!Xp(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),s+=String.fromCodePoint(g),e.consume(g),d):r(g)}function d(g){if(Ln(g)&&s.length<5)return s+=String.fromCodePoint(g),e.consume(g),d;if(g===58){const b=s.toLowerCase();if(b==="http"||b==="https")return e.consume(g),h}return r(g)}function h(g){return g===47?(e.consume(g),o?f:(o=!0,h)):r(g)}function f(g){return g===null||Su(g)||Tt(g)||Va(g)||Yu(g)?r(g):e.attempt(cE,e.attempt(uE,p),r)(g)}function p(g){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(g)}}function R4(e,t,r){let a=0;return s;function s(c){return(c===87||c===119)&&a<3?(a++,e.consume(c),s):c===46&&a===3?(e.consume(c),o):r(c)}function o(c){return c===null?r(c):t(c)}}function j4(e,t,r){let a,s,o;return c;function c(f){return f===46||f===95?e.check(dE,h,d)(f):f===null||Tt(f)||Va(f)||f!==45&&Yu(f)?h(f):(o=!0,e.consume(f),c)}function d(f){return f===95?a=!0:(s=a,a=void 0),e.consume(f),c}function h(f){return s||a||!o?r(f):t(f)}}function D4(e,t){let r=0,a=0;return s;function s(c){return c===40?(r++,e.consume(c),s):c===41&&a<r?o(c):c===33||c===34||c===38||c===39||c===41||c===42||c===44||c===46||c===58||c===59||c===60||c===63||c===93||c===95||c===126?e.check(dE,t,o)(c):c===null||Tt(c)||Va(c)?t(c):(e.consume(c),s)}function o(c){return c===41&&a++,e.consume(c),s}}function L4(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),o):d===93?(e.consume(d),s):d===60||d===null||Tt(d)||Va(d)?t(d):r(d)}function s(d){return d===null||d===40||d===91||Tt(d)||Va(d)?t(d):a(d)}function o(d){return Ln(d)?c(d):r(d)}function c(d){re
Line truncated
|
||||
https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void 0&&(Qe=!0);const gn={code:ft,language:Ht};Jr("before:highlight",gn);const Rn=gn.result?gn.result:yr(gn.language,gn.code,Qe);return Rn.code=gn.code,Jr("after:highlight",Rn),Rn}function yr(ye,Le,Qe,ft){const Ht=Object.create(null);function gn(_e,Re){return _e.keywords[Re]}function Rn(){if(!qe.keywords){en.addText(xt);return}let _e=0;qe.keywordPatternRe.lastIndex=0;let Re=qe.keywordPatternRe.exec(xt),Ye="";for(;Re;){Ye+=xt.substring(_e,Re.index);const rt=dn.case_insensitive?Re[0].toLowerCase():Re[0],$t=gn(qe,rt);if($t){const[or,sl]=$t;if(en.addText(Ye),Ye="",Ht[rt]=(Ht[rt]||0)+1,Ht[rt]<=Xn&&(Ri+=sl),or.startsWith("_"))Ye+=Re[0];else{const Po=dn.classNameAliases[or]||or;jn(Re[0],Po)}}else Ye+=Re[0];_e=qe.keywordPatternRe.lastIndex,Re=qe.keywordPatternRe.exec(xt)}Ye+=xt.substring(_e),en.addText(Ye)}function kn(){if(xt==="")return;let _e=null;if(typeof qe.subLanguage=="string"){if(!me[qe.subLanguage]){en.addText(xt);return}_e=yr(qe.subLanguage,xt,!0,qo[qe.subLanguage]),qo[qe.subLanguage]=_e._top}else _e=ki(xt,qe.subLanguage.length?qe.subLanguage:null);qe.relevance>0&&(Ri+=_e.relevance),en.__addSublanguage(_e._emitter,_e.language)}function _t(){qe.subLanguage!=null?kn():Rn(),xt=""}function jn(_e,Re){_e!==""&&(en.startScope(Re),en.addText(_e),en.endScope())}function rs(_e,Re){let Ye=1;const rt=Re.length-1;for(;Ye<=rt;){if(!_e._emit[Ye]){Ye++;continue}const $t=dn.classNameAliases[_e[Ye]]||_e[Ye],or=Re[Ye];$t?jn(or,$t):(xt=or,Rn(),xt=""),Ye++}}function Ai(_e,Re){return _e.scope&&typeof _e.scope=="string"&&en.openNode(dn.classNameAliases[_e.scope]||_e.scope),_e.beginScope&&(_e.beginScope._wrap?(jn(xt,dn.classNameAliases[_e.beginScope._wrap]||_e.beginScope._wrap),xt=""):_e.beginScope._multi&&(rs(_e.beginScope,Re),xt="")),qe=Object.create(_e,{parent:{value:qe}}),qe}function Ur(_e,Re,Ye){let rt=E(_e.endRe,Ye);if(rt){if(_e["on:end"]){const $t=new t(_e);_e["on:end"](Re,$t),$t.isMatchIgnored&&(rt=!1)}if(rt){for(;_e.endsParent&&_e.parent;)_e=_e.parent;return _e}}if(_e.endsWithParent)return Ur(_e.parent,Re,Ye)}function Mi(_e){return qe.matcher.regexIndex===0?(xt+=_e[0],1):(ji=!0,0)}function is(_e){const Re=_e[0],Ye=_e.rule,rt=new t(Ye),$t=[Ye.__beforeBegin,Ye["on:begin"]];for(const or of $t)if(or&&(or(_e,rt),rt.isMatchIgnored))return Mi(Re);return Ye.skip?xt+=Re:(Ye.excludeBegin&&(xt+=Re),_t(),!Ye.returnBegin&&!Ye.excludeBegin&&(xt=Re)),Ai(Ye,_e),Ye.returnBegin?0:Re.length}function Cn(_e){const Re=_e[0],Ye=Le.substring(_e.index),rt=Ur(qe,_e,Ye);if(!rt)return nt;const $t=qe;qe.endScope&&qe.endScope._wrap?(_t(),jn(Re,qe.endScope._wrap)):qe.endScope&&qe.endScope._multi?(_t(),rs(qe.endScope,_e)):$t.skip?xt+=Re:($t.returnEnd||$t.excludeEnd||(xt+=Re),_t(),$t.excludeEnd&&(xt=Re));do qe.scope&&en.closeNode(),!qe.skip&&!qe.subLanguage&&(Ri+=qe.relevance),qe=qe.parent;while(qe!==rt.parent);return rt.starts&&Ai(rt.starts,_e),$t.returnEnd?0:Re.length}function ba(){const _e=[];for(let Re=qe;Re!==dn;Re=Re.parent)Re.scope&&_e.unshift(Re.scope);_e.forEach(Re=>en.openNode(Re))}let Nr={};function Oi(_e,Re){const Ye=Re&&Re[0];if(xt+=_e,Ye==null)return _t(),0;if(Nr.type==="begin"&&Re.type==="end"&&Nr.index===Re.index&&Ye===""){if(xt+=Le.slice(Re.index,Re.index+1),!St){const rt=new Error(`0 width match regex (${ye})`);throw rt.languageName=ye,rt.badRule=Nr.rule,rt}return 1}if(Nr=Re,Re.type==="begin")return is(Re);if(Re.type==="illegal"&&!Qe){const rt=new Error('Illegal lexeme "'+Ye+'" for mode "'+(qe.scope||"<unnamed>")+'"');throw rt.mode=qe,rt}else if(Re.type==="end"){const rt=Cn(Re);if(rt!==nt)return rt}if(Re.type==="illegal"&&Ye==="")return xt+=`
|
||||
`,1;if(al>1e5&&al>Re.index*3)throw new Error("potential infinite loop, way more iterations than matches");return xt+=Ye,Ye.length}const dn=bn(ye);if(!dn)throw ct(gt.replace("{}",ye)),new Error('Unknown language: "'+ye+'"');const ya=Zt(dn);let as="",qe=ft||ya;const qo={},en=new Se.__emitter(Se);ba();let xt="",Ri=0,ei=0,al=0,ji=!1;try{if(dn.__emitTokens)dn.__emitTokens(Le,en);else{for(qe.matcher.considerAll();;){al++,ji?ji=!1:qe.matcher.considerAll(),qe.matcher.lastIndex=ei;const _e=qe.matcher.exec(Le);if(!_e)break;const Re=Le.substring(ei,_e.index),Ye=Oi(Re,_e);ei=_e.index+Ye}Oi(Le.substring(ei))}return en.finalize(),as=en.toHTML(),{language:ye,value:as,relevance:Ri,illegal:!1,_emitter:en,_top:qe}}catch(_e){if(_e.message&&_e.message.includes("Illegal"))return{language:ye,value:un(Le),illegal:!0,relevance:0,_illegalBy:{message:_e.message,index:ei,context:Le.slice(ei-100,ei+100),mode:_e.mode,resultSoFar:as},_emitter:en};if(St)return{language:ye,value:un(Le),illegal:!1,relevance:0,errorRaised:_e,_emitter:en,_top:qe};throw _e}}function Si(ye){const Le={value:un(ye),illegal:!1,relevance:0,_top:Me,_emitter:new Se.__emitter(Se)};return Le._emitter.addText(ye),Le}function ki(ye,Le){Le=Le||Se.languages||Object.keys(me);const Qe=Si(ye),ft=Le.filter(bn).filter(wr).map(_t=>yr(_t,ye,!1));ft.unshift(Qe);const Ht=ft.sort((_t,jn)=>{if(_t.relevance!==jn.relevance)return jn.relevance-_t.relevance;if(_t.language&&jn.language){if(bn(_t.language).supersetOf===jn.language)return 1;if(bn(jn.language).supersetOf===_t.language)return-1}return 0}),[gn,Rn]=Ht,kn=gn;return kn.secondBest=Rn,kn}function lr(ye,Le,Qe){const ft=Le&&Ee[Le]||Qe;ye.classList.add("hljs"),ye.classList.add(`language-${ft}`)}function Ut(ye){let Le=null;const Qe=Bt(ye);if(Ue(Qe))return;if(Jr("before:highlightElement",{el:ye,language:Qe}),ye.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",ye);return}if(ye.children.length>0&&(Se.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(ye)),Se.throwUnescapedHTML))throw new In("One of your code blocks includes unescaped HTML.",ye.innerHTML);Le=ye;const ft=Le.textContent,Ht=Qe?Mt(ft,{language:Qe,ignoreIllegals:!0}):ki(ft);ye.innerHTML=Ht.value,ye.dataset.highlighted="yes",lr(ye,Qe,Ht.language),ye.result={language:Ht.language,re:Ht.relevance,relevance:Ht.relevance},Ht.secondBest&&(ye.secondBest={language:Ht.secondBest.language,relevance:Ht.secondBest.relevance}),Jr("after:highlightElement",{el:ye,result:Ht,text:ft})}function pn(ye){Se=Ni(Se,ye)}const vr=()=>{Ti(),ue("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")};function Ci(){Ti(),ue("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")}let ga=!1;function Ti(){function ye(){Ti()}if(document.readyState==="loading"){ga||window.addEventListener("DOMContentLoaded",ye,!1),ga=!0;return}document.querySelectorAll(Se.cssSelector).forEach(Ut)}function ts(ye,Le){let Qe=null;try{Qe=Le(re)}catch(ft){if(ct("Language definition for '{}' could not be registered.".replace("{}",ye)),St)ct(ft);else throw ft;Qe=Me}Qe.name||(Qe.name=ye),me[ye]=Qe,Qe.rawDefinition=Le.bind(null,re),Qe.aliases&&_r(Qe.aliases,{languageName:ye})}function Wr(ye){delete me[ye];for(const Le of Object.keys(Ee))Ee[Le]===ye&&delete Ee[Le]}function xa(){return Object.keys(me)}function bn(ye){return ye=(ye||"").toLowerCase(),me[ye]||me[Ee[ye]]}function _r(ye,{languageName:Le}){typeof ye=="string"&&(ye=[ye]),ye.forEach(Qe=>{Ee[Qe.toLowerCase()]=Le})}function wr(ye){const Le=bn(ye);return Le&&!Le.disableAutodetect}function Br(ye){ye["before:highlightBlock"]&&!ye["before:highlightElement"]&&(ye["before:highlightElement"]=Le=>{ye["before:highlightBlock"](Object.assign({block:Le.el},Le))}),ye["after:highlightBlock"]&&!ye["after:highlightElement"]&&(ye["after:highlightElement"]=Le=>{ye["after:highlightBlock"](Object.assign({block:Le.el},Le))})}function Ft(ye){Br(ye),Pe.push(ye)}function ns(ye){const Le=Pe.indexOf(ye);Le!==-1&&Pe.splice(Le,1)}function Jr(ye,Le){const Qe=ye;Pe.forEach(function(ft){ft[Qe]&&ft[Qe](Le)})}function Er(ye){return ue("10.7.0","highlightBlock will be removed entirely in v12.0"),ue("10.7.0","Please use highlightElement now."),Ut(ye)}Object.assign(re,{highlight:Mt,highlightAuto:ki,highlightAll:Ti,highlightElement:Ut,highlightBlock:Er,configure:pn,initHighlighting:vr,initHighlightingOnLoad:Ci,registerLanguage:ts,unregisterLanguage:Wr,listLanguages:xa,getLanguage:bn,registerAliases:_r,autoDetection:wr,inherit:Ni,addPlugin:Ft,removePlugin:ns}),re.debugMode=function(){St=!1},re.safeMode=function(){St=!0},re.versionString=ut,re.regex={concat:N,lookahead:b,either:w,optional:_,anyNumberOfTimes:y};for(const ye in oe)typeof oe[ye]=="object"&&e(oe[ye]);return Object.assign(re,oe),re},mn=On({})
Line truncated
|
||||
`},_]}}return Jh=s,Jh}var em,_v;function wj(){if(_v)return em;_v=1;const e=p=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:p.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[p.APOS_STRING_MODE,p.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:p.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),t=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","optgroup","option","p","picture","q","quote","samp","section","select","source","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],r=["defs","g","marker","mask","pattern","svg","switch","symbol","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feGaussianBlur","feImage","feMerge","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence","linearGradient","radialGradient","stop","circle","ellipse","image","line","path","polygon","polyline","rect","text","use","textPath","tspan","foreignObject","clipPath"],a=[...t,...r],s=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"].sort().reverse(),o=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"].sort().reverse(),c=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"].sort().reverse(),d=["accent-color","align-content","align-items","align-self","alignment-baseline","all","anchor-name","animation","animation-composition","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-range","animation-range-end","animation-range-start","animation-timeline","animation-timing-function","appearance","aspect-ratio","backdrop-filter","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-position-x","background-position-y","background-repeat","background-size","baseline-shift","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-end-end-radius","border-end-start-radius","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-start-end-radius","border-start-start-radius","border-style","border-top","border-top
Line truncated
|
||||
]`,N={scope:"string",variants:[g,p,b,y]},S={scope:"number",variants:[{begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?"}],relevance:0},w=["false","null","true"],C=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],E=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],B={keyword:C,literal:($=>{const O=[];return $.forEach(U=>{O.push(U),U.toLowerCase()===U?O.push(U.toUpperCase()):O.push(U.toLowerCase())}),O})(w),built_in:E},R=$=>$.map(O=>O.replace(/\|\d+$/,"")),H={variants:[{match:[/new/,r.concat(_,"+"),r.concat("(?!",R(E).join("\\b|"),"\\b)"),o],scope:{1:"keyword",4:"title.class"}}]},z=r.concat(s,"\\b(?!\\()"),Y={variants:[{match:[r.concat(/::/,r.lookahead(/(?!class\b)/)),z],scope:{2:"variable.constant"}},{match:[/::/,/class/],scope:{2:"variable.language"}},{match:[o,r.concat(/::/,r.lookahead(/(?!class\b)/)),z],scope:{1:"title.class",3:"variable.constant"}},{match:[o,r.concat("::",r.lookahead(/(?!class\b)/))],scope:{1:"title.class"}},{match:[o,/::/,/class/],scope:{1:"title.class",3:"variable.language"}}]},j={scope:"attr",match:r.concat(s,r.lookahead(":"),r.lookahead(/(?!::)/))},I={relevance:0,begin:/\(/,end:/\)/,keywords:B,contains:[j,d,Y,t.C_BLOCK_COMMENT_MODE,N,S,H]},Z={relevance:0,match:[/\b/,r.concat("(?!fn\\b|function\\b|",R(C).join("\\b|"),"|",R(E).join("\\b|"),"\\b)"),s,r.concat(_,"*"),r.lookahead(/(?=\()/)],scope:{3:"title.function.invoke"},contains:[I]};I.contains.push(Z);const P=[j,Y,t.C_BLOCK_COMMENT_MODE,N,S,H],k={begin:r.concat(/#\[\s*\\?/,r.either(o,c)),beginScope:"meta",end:/]/,endScope:"meta",keywords:{literal:w,keyword:["new","array"]},contains:[{begin:/\[/,end:/]/,keywords:{literal:w,keyword:["new","array"]},contains:["self",...P]},...P,{scope:"meta",variants:[{match:o},{match:c}]}]};return{case_insensitive:!1,keywords:B,contains:[k,t.HASH_COMMENT_MODE,t.COMMENT("//","$"),t.COMMENT("/\\*","\\*/",{contains:[{scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/,keywords:"__halt_compiler",starts:{scope:"comment",end:t.MATCH_NOTHING_RE,contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},h,{scope:"variable.language",match:/\$this\b/},d,Z,Y,{match:[/const/,/\s/,s],scope:{1:"keyword",3:"variable.constant"}},H,{scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use"},t.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:B,contains:["self",k,d,Y,t.C_BLOCK_COMMENT_MODE,N,S]}]},{scope:"class",variants:[{beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"cl
Line truncated
|
||||
`)||c))return m.jsx("code",{className:`${e||""} bg-white/8 px-1.5 py-0.5 rounded text-[13px]`,children:t});const h=((w=r==null?void 0:r.data)==null?void 0:w.meta)||((C=r==null?void 0:r.properties)==null?void 0:C.metastring)||"",f=/title=["']?([^"'\s}]+)["']?/.exec(h),p=/startLineNumber=(\d+)/.exec(h),g=(f==null?void 0:f[1])||null,b=p?parseInt(p[1],10):1,y=g?p?`${g}:${b}`:g:null,N=xE(o,c==null?void 0:c[1]).split(`
|
||||
`),S=()=>{Cp(o),s(!0),setTimeout(()=>s(!1),2e3)};return m.jsxs("div",{className:"group/code relative rounded-md border border-[#2a2a2a] my-4 text-[#ddd] overflow-hidden",children:[y?m.jsxs("div",{className:"flex items-stretch",children:[m.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]",children:[y,m.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),m.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),m.jsx("button",{onClick:S,className:"px-3 py-2 text-[#555] hover:text-white transition-colors border-b border-[#2a2a2a]","aria-label":"Copy code",children:a?m.jsx(Vs,{className:"w-3.5 h-3.5 text-emerald-400"}):m.jsx(go,{className:"w-3.5 h-3.5"})})]}):m.jsx("button",{onClick:S,className:"absolute top-2 right-2 z-10 p-1 rounded text-[#444] hover:text-white opacity-0 group-hover/code:opacity-100 transition-opacity","aria-label":"Copy code",children:a?m.jsx(Vs,{className:"w-3.5 h-3.5 text-emerald-400"}):m.jsx(go,{className:"w-3.5 h-3.5"})}),m.jsx("div",{className:"overflow-auto max-h-[400px]",children:m.jsx("table",{className:"w-full border-collapse font-mono text-[12px] leading-[22px] [font-variant-ligatures:none]",children:m.jsx("tbody",{children:N.map((E,A)=>m.jsxs("tr",{children:[m.jsx("td",{className:"select-none w-[1px] whitespace-nowrap px-4 text-right text-[#555] align-top text-[12px] leading-[22px] border-r border-[#2a2a2a]",children:b+A}),m.jsx("td",{className:"pl-4 pr-4 whitespace-pre",dangerouslySetInnerHTML:{__html:E||`
|
||||
`}})]},A))})})})]})}function Zp(){return e=>{const t=r=>{var a;if(r.type==="element"&&r.tagName==="pre"&&r.children){const s=r.children.find(o=>o.type==="element"&&o.tagName==="code");(a=s==null?void 0:s.data)!=null&&a.meta&&(s.properties=s.properties||{},s.properties.metastring=s.data.meta)}r.children&&r.children.forEach(s=>t(s))};t(e)}}const Qp={code:bE,pre:({children:e})=>m.jsx(m.Fragment,{children:e})};function oa({title:e,content:t,action:r}){return m.jsxs("section",{children:[(e||r)&&m.jsxs("div",{className:"flex items-center justify-between gap-3 mb-3",children:[e?m.jsx("h2",{className:"text-xl font-semibold text-white",children:e}):m.jsx("span",{}),r]}),m.jsx("div",{className:"prose-markdown",children:m.jsx(Gp,{remarkPlugins:[Kp],rehypePlugins:[Zp],components:Qp,children:t})})]})}class Kj{diff(t,r,a={}){let s;typeof a=="function"?(s=a,a={}):"callback"in a&&(s=a.callback);const o=this.castInput(t,a),c=this.castInput(r,a),d=this.removeEmpty(this.tokenize(o,a)),h=this.removeEmpty(this.tokenize(c,a));return this.diffWithOptionsObj(d,h,a,s)}diffWithOptionsObj(t,r,a,s){var o;const c=C=>{if(C=this.postProcess(C,a),s){setTimeout(function(){s(C)},0);return}else return C},d=r.length,h=t.length;let f=1,p=d+h;a.maxEditLength!=null&&(p=Math.min(p,a.maxEditLength));const g=(o=a.timeout)!==null&&o!==void 0?o:1/0,b=Date.now()+g,y=[{oldPos:-1,lastComponent:void 0}];let _=this.extractCommon(y[0],r,t,0,a);if(y[0].oldPos+1>=h&&_+1>=d)return c(this.buildValues(y[0].lastComponent,r,t));let N=-1/0,S=1/0;const w=()=>{for(let C=Math.max(N,-f);C<=Math.min(S,f);C+=2){let E;const A=y[C-1],B=y[C+1];A&&(y[C-1]=void 0);let R=!1;if(B){const z=B.oldPos-C;R=B&&0<=z&&z<d}const H=A&&A.oldPos+1<h;if(!R&&!H){y[C]=void 0;continue}if(!H||R&&A.oldPos<B.oldPos?E=this.addToPath(B,!0,!1,0,a):E=this.addToPath(A,!1,!0,1,a),_=this.extractCommon(E,r,t,C,a),E.oldPos+1>=h&&_+1>=d)return c(this.buildValues(E.lastComponent,r,t))||!0;y[C]=E,E.oldPos+1>=h&&(S=Math.min(S,C-1)),_+1>=d&&(N=Math.max(N,C+1))}f++};if(s)(function C(){setTimeout(function(){if(f>p||Date.now()>b)return s(void 0);w()||C()},0)})();else for(;f<=p&&Date.now()<=b;){const C=w();if(C)return C}}addToPath(t,r,a,s,o){const c=t.lastComponent;return c&&!o.oneChangePerToken&&c.added===r&&c.removed===a?{oldPos:t.oldPos+s,lastComponent:{count:c.count+1,added:r,removed:a,previousComponent:c.previousComponent}}:{oldPos:t.oldPos+s,lastComponent:{count:1,added:r,removed:a,previousComponent:c}}}extractCommon(t,r,a,s,o){const c=r.length,d=a.length;let h=t.oldPos,f=h-s,p=0;for(;f+1<c&&h+1<d&&this.equals(a[h+1],r[f+1],o);)f++,h++,p++,o.oneChangePerToken&&(t.lastComponent={count:1,previousComponent:t.lastComponent,added:!1,removed:!1});return p&&!o.oneChangePerToken&&(t.lastComponent={count:p,previousComponent:t.lastComponent,added:!1,removed:!1}),t.oldPos=h,f}equals(t,r,a){return a.comparator?a.comparator(t,r):t===r||!!a.ignoreCase&&t.toLowerCase()===r.toLowerCase()}removeEmpty(t){const r=[];for(let a=0;a<t.length;a++)t[a]&&r.push(t[a]);return r}castInput(t,r){return t}tokenize(t,r){return Array.from(t)}join(t){return t.join("")}postProcess(t,r){return t}get useLongestToken(){return!1}buildValues(t,r,a){const s=[];let o;for(;t;)s.push(t),o=t.previousComponent,delete t.previousComponent,t=o;s.reverse();const c=s.length;let d=0,h=0,f=0;for(;d<c;d++){const p=s[d];if(p.removed)p.value=this.join(a.slice(f,f+p.count)),f+=p.count;else{if(!p.added&&this.useLongestToken){let g=r.slice(h,h+p.count);g=g.map(function(b,y){const _=a[f+y];return _.length>b.length?_:b}),p.value=this.join(g)}else p.value=this.join(r.slice(h,h+p.count));h+=p.count,p.added||(f+=p.count)}}return s}}class Zj extends Kj{constructor(){super(...arguments),this.tokenize=Jj}equals(t,r,a){return a.ignoreWhitespace?((!a.newlineIsToken||!t.includes(`
|
||||
`))}function h(y,_,N,S){const w=N.enter("tableCell"),C=N.enter("phrasing"),E=N.containerPhrasing(y,{...S,before:o,after:o});return C(),w(),E}function f(y,_){return z3(y,{align:_,alignDelimiters:a,padding:r,stringLength:s})}function p(y,_,N){const S=y.children;let w=-1;const C=[],E=_.enter("table");for(;++w<S.length;)C[w]=g(S[w],_,N);return E(),C}function g(y,_,N){const S=y.children;let w=-1;const C=[],E=_.enter("tableRow");for(;++w<S.length;)C[w]=h(S[w],y,_,N);return E(),C}function b(y,_,N){let S=fE.inlineCode(y,_,N);return N.stack.includes("tableCell")&&(S=S.replace(/\|/g,"\\$&")),S}}function E4(){return{exit:{taskListCheckValueChecked:iv,taskListCheckValueUnchecked:iv,paragraph:S4}}}function N4(){return{unsafe:[{atBreak:!0,character:"-",after:"[:|-]"}],handlers:{listItem:k4}}}function iv(e){const t=this.stack[this.stack.length-2];t.type,t.checked=e.type==="taskListCheckValueChecked"}function S4(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 s=t.children;let o=-1,c;for(;++o<s.length;){const d=s[o];if(d.type==="paragraph"){c=d;break}}c===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 k4(e,t,r,a){const s=e.children[0],o=typeof e.checked=="boolean"&&s&&s.type==="paragraph",c="["+(e.checked?"x":" ")+"] ",d=r.createTracker(a);o&&d.move(c);let h=fE.listItem(e,t,r,{...a,...d.current()});return o&&(h=h.replace(/^(?:[*+-]|\d+\.)([\r\n]| {1,3})/,f)),h;function f(p){return p+c}}function C4(){return[a3(),k3(),M3(),g4(),E4()]}function T4(e){return{extensions:[s3(),C3(e),O3(),w4(e),N4()]}}const A4={tokenize:L4,partial:!0},hE={tokenize:z4,partial:!0},mE={tokenize:I4,partial:!0},pE={tokenize:B4,partial:!0},M4={tokenize:U4,partial:!0},gE={name:"wwwAutolink",tokenize:j4,previous:bE},xE={name:"protocolAutolink",tokenize:D4,previous:yE},Ni={name:"emailAutolink",tokenize:R4,previous:vE},Kr={};function O4(){return{text:Kr}}let Ia=48;for(;Ia<123;)Kr[Ia]=Ni,Ia++,Ia===58?Ia=65:Ia===91&&(Ia=97);Kr[43]=Ni;Kr[45]=Ni;Kr[46]=Ni;Kr[95]=Ni;Kr[72]=[Ni,xE];Kr[104]=[Ni,xE];Kr[87]=[Ni,gE];Kr[119]=[Ni,gE];function R4(e,t,r){const a=this;let s,o;return c;function c(g){return!ip(g)||!vE.call(a,a.previous)||Zp(a.events)?r(g):(e.enter("literalAutolink"),e.enter("literalAutolinkEmail"),d(g))}function d(g){return ip(g)?(e.consume(g),d):g===64?(e.consume(g),h):r(g)}function h(g){return g===46?e.check(M4,p,f)(g):g===45||g===95||An(g)?(o=!0,e.consume(g),h):p(g)}function f(g){return e.consume(g),s=!0,h}function p(g){return o&&s&&Ln(a.previous)?(e.exit("literalAutolinkEmail"),e.exit("literalAutolink"),t(g)):r(g)}}function j4(e,t,r){const a=this;return s;function s(c){return c!==87&&c!==119||!bE.call(a,a.previous)||Zp(a.events)?r(c):(e.enter("literalAutolink"),e.enter("literalAutolinkWww"),e.check(A4,e.attempt(hE,e.attempt(mE,o),r),r)(c))}function o(c){return e.exit("literalAutolinkWww"),e.exit("literalAutolink"),t(c)}}function D4(e,t,r){const a=this;let s="",o=!1;return c;function c(g){return(g===72||g===104)&&yE.call(a,a.previous)&&!Zp(a.events)?(e.enter("literalAutolink"),e.enter("literalAutolinkHttp"),s+=String.fromCodePoint(g),e.consume(g),d):r(g)}function d(g){if(Ln(g)&&s.length<5)return s+=String.fromCodePoint(g),e.consume(g),d;if(g===58){const b=s.toLowerCase();if(b==="http"||b==="https")return e.consume(g),h}return r(g)}function h(g){return g===47?(e.consume(g),o?f:(o=!0,h)):r(g)}function f(g){return g===null||ku(g)||Tt(g)||Va(g)||Xu(g)?r(g):e.attempt(hE,e.attempt(mE,p),r)(g)}function p(g){return e.exit("literalAutolinkHttp"),e.exit("literalAutolink"),t(g)}}function L4(e,t,r){let a=0;return s;function s(c){return(c===87||c===119)&&a<3?(a++,e.consume(c),s):c===46&&a===3?(e.consume(c),o):r(c)}function o(c){return c===null?r(c):t(c)}}function z4(e,t,r){let a,s,o;return c;function c(f){return f===46||f===95?e.check(pE,h,d)(f):f===null||Tt(f)||Va(f)||f!==45&&Xu(f)?h(f):(o=!0,e.consume(f),c)}function d(f){return f===95?a=!0:(s=a,a=void 0),e.consume(f),c}function h(f){return s||a||!o?r(f):t(f)}}function I4(e,t){let r=0,a=0;return s;function s(c){return c===40?(r++,e.consume(c),s):c===41&&a<r?o(c):c===33||c===34||c===38||c===39||c===41||c===42||c===44||c===46||c===58||c===59||c===60||c===63||c===93||c===95||c===126?e.check(pE,t,o)(c):c===null||Tt(c)||Va(c)?t(c):(e.consume(c),s)}function o(c){return c===41&&a++,e.consume(c),s}}function B4(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),o):d===93?(e.consume(d),s):d===60||d===null||Tt(d)||Va(d)?t(d):r(d)}function s(d){return d===null||d===40||d===91||Tt(d)||Va(d)?t(d):a(d)}function o(d){return Ln(d)?c(d):r(d)}function c(d){re
Line truncated
|
||||
https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void 0&&(Qe=!0);const gn={code:ft,language:Ht};Jr("before:highlight",gn);const Rn=gn.result?gn.result:vr(gn.language,gn.code,Qe);return Rn.code=gn.code,Jr("after:highlight",Rn),Rn}function vr(ye,Le,Qe,ft){const Ht=Object.create(null);function gn(_e,Re){return _e.keywords[Re]}function Rn(){if(!qe.keywords){en.addText(xt);return}let _e=0;qe.keywordPatternRe.lastIndex=0;let Re=qe.keywordPatternRe.exec(xt),Ye="";for(;Re;){Ye+=xt.substring(_e,Re.index);const rt=dn.case_insensitive?Re[0].toLowerCase():Re[0],$t=gn(qe,rt);if($t){const[or,sl]=$t;if(en.addText(Ye),Ye="",Ht[rt]=(Ht[rt]||0)+1,Ht[rt]<=Xn&&(ji+=sl),or.startsWith("_"))Ye+=Re[0];else{const Fo=dn.classNameAliases[or]||or;jn(Re[0],Fo)}}else Ye+=Re[0];_e=qe.keywordPatternRe.lastIndex,Re=qe.keywordPatternRe.exec(xt)}Ye+=xt.substring(_e),en.addText(Ye)}function kn(){if(xt==="")return;let _e=null;if(typeof qe.subLanguage=="string"){if(!me[qe.subLanguage]){en.addText(xt);return}_e=vr(qe.subLanguage,xt,!0,Po[qe.subLanguage]),Po[qe.subLanguage]=_e._top}else _e=Ci(xt,qe.subLanguage.length?qe.subLanguage:null);qe.relevance>0&&(ji+=_e.relevance),en.__addSublanguage(_e._emitter,_e.language)}function _t(){qe.subLanguage!=null?kn():Rn(),xt=""}function jn(_e,Re){_e!==""&&(en.startScope(Re),en.addText(_e),en.endScope())}function rs(_e,Re){let Ye=1;const rt=Re.length-1;for(;Ye<=rt;){if(!_e._emit[Ye]){Ye++;continue}const $t=dn.classNameAliases[_e[Ye]]||_e[Ye],or=Re[Ye];$t?jn(or,$t):(xt=or,Rn(),xt=""),Ye++}}function Mi(_e,Re){return _e.scope&&typeof _e.scope=="string"&&en.openNode(dn.classNameAliases[_e.scope]||_e.scope),_e.beginScope&&(_e.beginScope._wrap?(jn(xt,dn.classNameAliases[_e.beginScope._wrap]||_e.beginScope._wrap),xt=""):_e.beginScope._multi&&(rs(_e.beginScope,Re),xt="")),qe=Object.create(_e,{parent:{value:qe}}),qe}function Ur(_e,Re,Ye){let rt=E(_e.endRe,Ye);if(rt){if(_e["on:end"]){const $t=new t(_e);_e["on:end"](Re,$t),$t.isMatchIgnored&&(rt=!1)}if(rt){for(;_e.endsParent&&_e.parent;)_e=_e.parent;return _e}}if(_e.endsWithParent)return Ur(_e.parent,Re,Ye)}function Oi(_e){return qe.matcher.regexIndex===0?(xt+=_e[0],1):(Di=!0,0)}function is(_e){const Re=_e[0],Ye=_e.rule,rt=new t(Ye),$t=[Ye.__beforeBegin,Ye["on:begin"]];for(const or of $t)if(or&&(or(_e,rt),rt.isMatchIgnored))return Oi(Re);return Ye.skip?xt+=Re:(Ye.excludeBegin&&(xt+=Re),_t(),!Ye.returnBegin&&!Ye.excludeBegin&&(xt=Re)),Mi(Ye,_e),Ye.returnBegin?0:Re.length}function Cn(_e){const Re=_e[0],Ye=Le.substring(_e.index),rt=Ur(qe,_e,Ye);if(!rt)return nt;const $t=qe;qe.endScope&&qe.endScope._wrap?(_t(),jn(Re,qe.endScope._wrap)):qe.endScope&&qe.endScope._multi?(_t(),rs(qe.endScope,_e)):$t.skip?xt+=Re:($t.returnEnd||$t.excludeEnd||(xt+=Re),_t(),$t.excludeEnd&&(xt=Re));do qe.scope&&en.closeNode(),!qe.skip&&!qe.subLanguage&&(ji+=qe.relevance),qe=qe.parent;while(qe!==rt.parent);return rt.starts&&Mi(rt.starts,_e),$t.returnEnd?0:Re.length}function ba(){const _e=[];for(let Re=qe;Re!==dn;Re=Re.parent)Re.scope&&_e.unshift(Re.scope);_e.forEach(Re=>en.openNode(Re))}let Sr={};function Ri(_e,Re){const Ye=Re&&Re[0];if(xt+=_e,Ye==null)return _t(),0;if(Sr.type==="begin"&&Re.type==="end"&&Sr.index===Re.index&&Ye===""){if(xt+=Le.slice(Re.index,Re.index+1),!St){const rt=new Error(`0 width match regex (${ye})`);throw rt.languageName=ye,rt.badRule=Sr.rule,rt}return 1}if(Sr=Re,Re.type==="begin")return is(Re);if(Re.type==="illegal"&&!Qe){const rt=new Error('Illegal lexeme "'+Ye+'" for mode "'+(qe.scope||"<unnamed>")+'"');throw rt.mode=qe,rt}else if(Re.type==="end"){const rt=Cn(Re);if(rt!==nt)return rt}if(Re.type==="illegal"&&Ye==="")return xt+=`
|
||||
`,1;if(al>1e5&&al>Re.index*3)throw new Error("potential infinite loop, way more iterations than matches");return xt+=Ye,Ye.length}const dn=bn(ye);if(!dn)throw ct(gt.replace("{}",ye)),new Error('Unknown language: "'+ye+'"');const ya=Zt(dn);let as="",qe=ft||ya;const Po={},en=new Se.__emitter(Se);ba();let xt="",ji=0,ei=0,al=0,Di=!1;try{if(dn.__emitTokens)dn.__emitTokens(Le,en);else{for(qe.matcher.considerAll();;){al++,Di?Di=!1:qe.matcher.considerAll(),qe.matcher.lastIndex=ei;const _e=qe.matcher.exec(Le);if(!_e)break;const Re=Le.substring(ei,_e.index),Ye=Ri(Re,_e);ei=_e.index+Ye}Ri(Le.substring(ei))}return en.finalize(),as=en.toHTML(),{language:ye,value:as,relevance:ji,illegal:!1,_emitter:en,_top:qe}}catch(_e){if(_e.message&&_e.message.includes("Illegal"))return{language:ye,value:un(Le),illegal:!0,relevance:0,_illegalBy:{message:_e.message,index:ei,context:Le.slice(ei-100,ei+100),mode:_e.mode,resultSoFar:as},_emitter:en};if(St)return{language:ye,value:un(Le),illegal:!1,relevance:0,errorRaised:_e,_emitter:en,_top:qe};throw _e}}function ki(ye){const Le={value:un(ye),illegal:!1,relevance:0,_top:Me,_emitter:new Se.__emitter(Se)};return Le._emitter.addText(ye),Le}function Ci(ye,Le){Le=Le||Se.languages||Object.keys(me);const Qe=ki(ye),ft=Le.filter(bn).filter(Er).map(_t=>vr(_t,ye,!1));ft.unshift(Qe);const Ht=ft.sort((_t,jn)=>{if(_t.relevance!==jn.relevance)return jn.relevance-_t.relevance;if(_t.language&&jn.language){if(bn(_t.language).supersetOf===jn.language)return 1;if(bn(jn.language).supersetOf===_t.language)return-1}return 0}),[gn,Rn]=Ht,kn=gn;return kn.secondBest=Rn,kn}function lr(ye,Le,Qe){const ft=Le&&Ee[Le]||Qe;ye.classList.add("hljs"),ye.classList.add(`language-${ft}`)}function Ut(ye){let Le=null;const Qe=Bt(ye);if(Ue(Qe))return;if(Jr("before:highlightElement",{el:ye,language:Qe}),ye.dataset.highlighted){console.log("Element previously highlighted. To highlight again, first unset `dataset.highlighted`.",ye);return}if(ye.children.length>0&&(Se.ignoreUnescapedHTML||(console.warn("One of your code blocks includes unescaped HTML. This is a potentially serious security risk."),console.warn("https://github.com/highlightjs/highlight.js/wiki/security"),console.warn("The element with unescaped HTML:"),console.warn(ye)),Se.throwUnescapedHTML))throw new In("One of your code blocks includes unescaped HTML.",ye.innerHTML);Le=ye;const ft=Le.textContent,Ht=Qe?Mt(ft,{language:Qe,ignoreIllegals:!0}):Ci(ft);ye.innerHTML=Ht.value,ye.dataset.highlighted="yes",lr(ye,Qe,Ht.language),ye.result={language:Ht.language,re:Ht.relevance,relevance:Ht.relevance},Ht.secondBest&&(ye.secondBest={language:Ht.secondBest.language,relevance:Ht.secondBest.relevance}),Jr("after:highlightElement",{el:ye,result:Ht,text:ft})}function pn(ye){Se=Si(Se,ye)}const _r=()=>{Ai(),ue("10.6.0","initHighlighting() deprecated. Use highlightAll() now.")};function Ti(){Ai(),ue("10.6.0","initHighlightingOnLoad() deprecated. Use highlightAll() now.")}let ga=!1;function Ai(){function ye(){Ai()}if(document.readyState==="loading"){ga||window.addEventListener("DOMContentLoaded",ye,!1),ga=!0;return}document.querySelectorAll(Se.cssSelector).forEach(Ut)}function ts(ye,Le){let Qe=null;try{Qe=Le(re)}catch(ft){if(ct("Language definition for '{}' could not be registered.".replace("{}",ye)),St)ct(ft);else throw ft;Qe=Me}Qe.name||(Qe.name=ye),me[ye]=Qe,Qe.rawDefinition=Le.bind(null,re),Qe.aliases&&wr(Qe.aliases,{languageName:ye})}function Wr(ye){delete me[ye];for(const Le of Object.keys(Ee))Ee[Le]===ye&&delete Ee[Le]}function xa(){return Object.keys(me)}function bn(ye){return ye=(ye||"").toLowerCase(),me[ye]||me[Ee[ye]]}function wr(ye,{languageName:Le}){typeof ye=="string"&&(ye=[ye]),ye.forEach(Qe=>{Ee[Qe.toLowerCase()]=Le})}function Er(ye){const Le=bn(ye);return Le&&!Le.disableAutodetect}function Br(ye){ye["before:highlightBlock"]&&!ye["before:highlightElement"]&&(ye["before:highlightElement"]=Le=>{ye["before:highlightBlock"](Object.assign({block:Le.el},Le))}),ye["after:highlightBlock"]&&!ye["after:highlightElement"]&&(ye["after:highlightElement"]=Le=>{ye["after:highlightBlock"](Object.assign({block:Le.el},Le))})}function Gt(ye){Br(ye),Pe.push(ye)}function ns(ye){const Le=Pe.indexOf(ye);Le!==-1&&Pe.splice(Le,1)}function Jr(ye,Le){const Qe=ye;Pe.forEach(function(ft){ft[Qe]&&ft[Qe](Le)})}function Nr(ye){return ue("10.7.0","highlightBlock will be removed entirely in v12.0"),ue("10.7.0","Please use highlightElement now."),Ut(ye)}Object.assign(re,{highlight:Mt,highlightAuto:Ci,highlightAll:Ai,highlightElement:Ut,highlightBlock:Nr,configure:pn,initHighlighting:_r,initHighlightingOnLoad:Ti,registerLanguage:ts,unregisterLanguage:Wr,listLanguages:xa,getLanguage:bn,registerAliases:wr,autoDetection:Er,inherit:Si,addPlugin:Gt,removePlugin:ns}),re.debugMode=function(){St=!1},re.safeMode=function(){St=!0},re.versionString=ut,re.regex={concat:N,lookahead:b,either:w,optional:_,anyNumberOfTimes:y};for(const ye in oe)typeof oe[ye]=="object"&&e(oe[ye]);return Object.assign(re,oe),re},mn=On({})
Line truncated
|
||||
`},_]}}return em=s,em}var tm,Ev;function Sj(){if(Ev)return tm;Ev=1;const e=p=>({IMPORTANT:{scope:"meta",begin:"!important"},BLOCK_COMMENT:p.C_BLOCK_COMMENT_MODE,HEXCOLOR:{scope:"number",begin:/#(([0-9a-fA-F]{3,4})|(([0-9a-fA-F]{2}){3,4}))\b/},FUNCTION_DISPATCH:{className:"built_in",begin:/[\w-]+(?=\()/},ATTRIBUTE_SELECTOR_MODE:{scope:"selector-attr",begin:/\[/,end:/\]/,illegal:"$",contains:[p.APOS_STRING_MODE,p.QUOTE_STRING_MODE]},CSS_NUMBER_MODE:{scope:"number",begin:p.NUMBER_RE+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},CSS_VARIABLE:{className:"attr",begin:/--[A-Za-z_][A-Za-z0-9_-]*/}}),t=["a","abbr","address","article","aside","audio","b","blockquote","body","button","canvas","caption","cite","code","dd","del","details","dfn","div","dl","dt","em","fieldset","figcaption","figure","footer","form","h1","h2","h3","h4","h5","h6","header","hgroup","html","i","iframe","img","input","ins","kbd","label","legend","li","main","mark","menu","nav","object","ol","optgroup","option","p","picture","q","quote","samp","section","select","source","span","strong","summary","sup","table","tbody","td","textarea","tfoot","th","thead","time","tr","ul","var","video"],r=["defs","g","marker","mask","pattern","svg","switch","symbol","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feFlood","feGaussianBlur","feImage","feMerge","feMorphology","feOffset","feSpecularLighting","feTile","feTurbulence","linearGradient","radialGradient","stop","circle","ellipse","image","line","path","polygon","polyline","rect","text","use","textPath","tspan","foreignObject","clipPath"],a=[...t,...r],s=["any-hover","any-pointer","aspect-ratio","color","color-gamut","color-index","device-aspect-ratio","device-height","device-width","display-mode","forced-colors","grid","height","hover","inverted-colors","monochrome","orientation","overflow-block","overflow-inline","pointer","prefers-color-scheme","prefers-contrast","prefers-reduced-motion","prefers-reduced-transparency","resolution","scan","scripting","update","width","min-width","max-width","min-height","max-height"].sort().reverse(),o=["active","any-link","blank","checked","current","default","defined","dir","disabled","drop","empty","enabled","first","first-child","first-of-type","fullscreen","future","focus","focus-visible","focus-within","has","host","host-context","hover","indeterminate","in-range","invalid","is","lang","last-child","last-of-type","left","link","local-link","not","nth-child","nth-col","nth-last-child","nth-last-col","nth-last-of-type","nth-of-type","only-child","only-of-type","optional","out-of-range","past","placeholder-shown","read-only","read-write","required","right","root","scope","target","target-within","user-invalid","valid","visited","where"].sort().reverse(),c=["after","backdrop","before","cue","cue-region","first-letter","first-line","grammar-error","marker","part","placeholder","selection","slotted","spelling-error"].sort().reverse(),d=["accent-color","align-content","align-items","align-self","alignment-baseline","all","anchor-name","animation","animation-composition","animation-delay","animation-direction","animation-duration","animation-fill-mode","animation-iteration-count","animation-name","animation-play-state","animation-range","animation-range-end","animation-range-start","animation-timeline","animation-timing-function","appearance","aspect-ratio","backdrop-filter","backface-visibility","background","background-attachment","background-blend-mode","background-clip","background-color","background-image","background-origin","background-position","background-position-x","background-position-y","background-repeat","background-size","baseline-shift","block-size","border","border-block","border-block-color","border-block-end","border-block-end-color","border-block-end-style","border-block-end-width","border-block-start","border-block-start-color","border-block-start-style","border-block-start-width","border-block-style","border-block-width","border-bottom","border-bottom-color","border-bottom-left-radius","border-bottom-right-radius","border-bottom-style","border-bottom-width","border-collapse","border-color","border-end-end-radius","border-end-start-radius","border-image","border-image-outset","border-image-repeat","border-image-slice","border-image-source","border-image-width","border-inline","border-inline-color","border-inline-end","border-inline-end-color","border-inline-end-style","border-inline-end-width","border-inline-start","border-inline-start-color","border-inline-start-style","border-inline-start-width","border-inline-style","border-inline-width","border-left","border-left-color","border-left-style","border-left-width","border-radius","border-right","border-right-color","border-right-style","border-right-width","border-spacing","border-start-end-radius","border-start-start-radius","border-style","border-top","border-top
Line truncated
|
||||
]`,N={scope:"string",variants:[g,p,b,y]},S={scope:"number",variants:[{begin:"\\b0[bB][01]+(?:_[01]+)*\\b"},{begin:"\\b0[oO][0-7]+(?:_[0-7]+)*\\b"},{begin:"\\b0[xX][\\da-fA-F]+(?:_[\\da-fA-F]+)*\\b"},{begin:"(?:\\b\\d+(?:_\\d+)*(\\.(?:\\d+(?:_\\d+)*))?|\\B\\.\\d+)(?:[eE][+-]?\\d+)?"}],relevance:0},w=["false","null","true"],C=["__CLASS__","__DIR__","__FILE__","__FUNCTION__","__COMPILER_HALT_OFFSET__","__LINE__","__METHOD__","__NAMESPACE__","__TRAIT__","die","echo","exit","include","include_once","print","require","require_once","array","abstract","and","as","binary","bool","boolean","break","callable","case","catch","class","clone","const","continue","declare","default","do","double","else","elseif","empty","enddeclare","endfor","endforeach","endif","endswitch","endwhile","enum","eval","extends","final","finally","float","for","foreach","from","global","goto","if","implements","instanceof","insteadof","int","integer","interface","isset","iterable","list","match|0","mixed","new","never","object","or","private","protected","public","readonly","real","return","string","switch","throw","trait","try","unset","use","var","void","while","xor","yield"],E=["Error|0","AppendIterator","ArgumentCountError","ArithmeticError","ArrayIterator","ArrayObject","AssertionError","BadFunctionCallException","BadMethodCallException","CachingIterator","CallbackFilterIterator","CompileError","Countable","DirectoryIterator","DivisionByZeroError","DomainException","EmptyIterator","ErrorException","Exception","FilesystemIterator","FilterIterator","GlobIterator","InfiniteIterator","InvalidArgumentException","IteratorIterator","LengthException","LimitIterator","LogicException","MultipleIterator","NoRewindIterator","OutOfBoundsException","OutOfRangeException","OuterIterator","OverflowException","ParentIterator","ParseError","RangeException","RecursiveArrayIterator","RecursiveCachingIterator","RecursiveCallbackFilterIterator","RecursiveDirectoryIterator","RecursiveFilterIterator","RecursiveIterator","RecursiveIteratorIterator","RecursiveRegexIterator","RecursiveTreeIterator","RegexIterator","RuntimeException","SeekableIterator","SplDoublyLinkedList","SplFileInfo","SplFileObject","SplFixedArray","SplHeap","SplMaxHeap","SplMinHeap","SplObjectStorage","SplObserver","SplPriorityQueue","SplQueue","SplStack","SplSubject","SplTempFileObject","TypeError","UnderflowException","UnexpectedValueException","UnhandledMatchError","ArrayAccess","BackedEnum","Closure","Fiber","Generator","Iterator","IteratorAggregate","Serializable","Stringable","Throwable","Traversable","UnitEnum","WeakReference","WeakMap","Directory","__PHP_Incomplete_Class","parent","php_user_filter","self","static","stdClass"],B={keyword:C,literal:($=>{const O=[];return $.forEach(U=>{O.push(U),U.toLowerCase()===U?O.push(U.toUpperCase()):O.push(U.toLowerCase())}),O})(w),built_in:E},R=$=>$.map(O=>O.replace(/\|\d+$/,"")),H={variants:[{match:[/new/,r.concat(_,"+"),r.concat("(?!",R(E).join("\\b|"),"\\b)"),o],scope:{1:"keyword",4:"title.class"}}]},z=r.concat(s,"\\b(?!\\()"),Y={variants:[{match:[r.concat(/::/,r.lookahead(/(?!class\b)/)),z],scope:{2:"variable.constant"}},{match:[/::/,/class/],scope:{2:"variable.language"}},{match:[o,r.concat(/::/,r.lookahead(/(?!class\b)/)),z],scope:{1:"title.class",3:"variable.constant"}},{match:[o,r.concat("::",r.lookahead(/(?!class\b)/))],scope:{1:"title.class"}},{match:[o,/::/,/class/],scope:{1:"title.class",3:"variable.language"}}]},j={scope:"attr",match:r.concat(s,r.lookahead(":"),r.lookahead(/(?!::)/))},I={relevance:0,begin:/\(/,end:/\)/,keywords:B,contains:[j,d,Y,t.C_BLOCK_COMMENT_MODE,N,S,H]},Z={relevance:0,match:[/\b/,r.concat("(?!fn\\b|function\\b|",R(C).join("\\b|"),"|",R(E).join("\\b|"),"\\b)"),s,r.concat(_,"*"),r.lookahead(/(?=\()/)],scope:{3:"title.function.invoke"},contains:[I]};I.contains.push(Z);const P=[j,Y,t.C_BLOCK_COMMENT_MODE,N,S,H],k={begin:r.concat(/#\[\s*\\?/,r.either(o,c)),beginScope:"meta",end:/]/,endScope:"meta",keywords:{literal:w,keyword:["new","array"]},contains:[{begin:/\[/,end:/]/,keywords:{literal:w,keyword:["new","array"]},contains:["self",...P]},...P,{scope:"meta",variants:[{match:o},{match:c}]}]};return{case_insensitive:!1,keywords:B,contains:[k,t.HASH_COMMENT_MODE,t.COMMENT("//","$"),t.COMMENT("/\\*","\\*/",{contains:[{scope:"doctag",match:"@[A-Za-z]+"}]}),{match:/__halt_compiler\(\);/,keywords:"__halt_compiler",starts:{scope:"comment",end:t.MATCH_NOTHING_RE,contains:[{match:/\?>/,scope:"meta",endsParent:!0}]}},h,{scope:"variable.language",match:/\$this\b/},d,Z,Y,{match:[/const/,/\s/,s],scope:{1:"keyword",3:"variable.constant"}},H,{scope:"function",relevance:0,beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[{beginKeywords:"use"},t.UNDERSCORE_TITLE_MODE,{begin:"=>",endsParent:!0},{scope:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:B,contains:["self",k,d,Y,t.C_BLOCK_COMMENT_MODE,N,S]}]},{scope:"class",variants:[{beginKeywords:"enum",illegal:/[($"]/},{beginKeywords:"cl
Line truncated
|
||||
`)||c))return m.jsx("code",{className:`${e||""} bg-white/8 px-1.5 py-0.5 rounded text-[13px]`,children:t});const h=((w=r==null?void 0:r.data)==null?void 0:w.meta)||((C=r==null?void 0:r.properties)==null?void 0:C.metastring)||"",f=/title=["']?([^"'\s}]+)["']?/.exec(h),p=/startLineNumber=(\d+)/.exec(h),g=(f==null?void 0:f[1])||null,b=p?parseInt(p[1],10):1,y=g?p?`${g}:${b}`:g:null,N=_E(o,c==null?void 0:c[1]).split(`
|
||||
`),S=()=>{Tp(o),s(!0),setTimeout(()=>s(!1),2e3)};return m.jsxs("div",{className:"group/code relative rounded-md border border-[#2a2a2a] my-4 text-[#ddd] overflow-hidden",children:[y?m.jsxs("div",{className:"flex items-stretch",children:[m.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]",children:[y,m.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),m.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),m.jsx("button",{onClick:S,className:"px-3 py-2 text-[#555] hover:text-white transition-colors border-b border-[#2a2a2a]","aria-label":"Copy code",children:a?m.jsx(Vs,{className:"w-3.5 h-3.5 text-emerald-400"}):m.jsx(go,{className:"w-3.5 h-3.5"})})]}):m.jsx("button",{onClick:S,className:"absolute top-2 right-2 z-10 p-1 rounded text-[#444] hover:text-white opacity-0 group-hover/code:opacity-100 transition-opacity","aria-label":"Copy code",children:a?m.jsx(Vs,{className:"w-3.5 h-3.5 text-emerald-400"}):m.jsx(go,{className:"w-3.5 h-3.5"})}),m.jsx("div",{className:"overflow-auto max-h-[400px]",children:m.jsx("table",{className:"w-full border-collapse font-mono text-[12px] leading-[22px] [font-variant-ligatures:none]",children:m.jsx("tbody",{children:N.map((E,A)=>m.jsxs("tr",{children:[m.jsx("td",{className:"select-none w-[1px] whitespace-nowrap px-4 text-right text-[#555] align-top text-[12px] leading-[22px] border-r border-[#2a2a2a]",children:b+A}),m.jsx("td",{className:"pl-4 pr-4 whitespace-pre",dangerouslySetInnerHTML:{__html:E||`
|
||||
`}})]},A))})})})]})}function Wp(){return e=>{const t=r=>{var a;if(r.type==="element"&&r.tagName==="pre"&&r.children){const s=r.children.find(o=>o.type==="element"&&o.tagName==="code");(a=s==null?void 0:s.data)!=null&&a.meta&&(s.properties=s.properties||{},s.properties.metastring=s.data.meta)}r.children&&r.children.forEach(s=>t(s))};t(e)}}const Jp={code:wE,pre:({children:e})=>m.jsx(m.Fragment,{children:e})};function ca({title:e,content:t,action:r}){return m.jsxs("section",{children:[(e||r)&&m.jsxs("div",{className:"flex items-center justify-between gap-3 mb-3",children:[e?m.jsx("h2",{className:"text-xl font-semibold text-white",children:e}):m.jsx("span",{}),r]}),m.jsx("div",{className:"prose-markdown",children:m.jsx(Yp,{remarkPlugins:[Qp],rehypePlugins:[Wp],components:Jp,children:t})})]})}class Wj{diff(t,r,a={}){let s;typeof a=="function"?(s=a,a={}):"callback"in a&&(s=a.callback);const o=this.castInput(t,a),c=this.castInput(r,a),d=this.removeEmpty(this.tokenize(o,a)),h=this.removeEmpty(this.tokenize(c,a));return this.diffWithOptionsObj(d,h,a,s)}diffWithOptionsObj(t,r,a,s){var o;const c=C=>{if(C=this.postProcess(C,a),s){setTimeout(function(){s(C)},0);return}else return C},d=r.length,h=t.length;let f=1,p=d+h;a.maxEditLength!=null&&(p=Math.min(p,a.maxEditLength));const g=(o=a.timeout)!==null&&o!==void 0?o:1/0,b=Date.now()+g,y=[{oldPos:-1,lastComponent:void 0}];let _=this.extractCommon(y[0],r,t,0,a);if(y[0].oldPos+1>=h&&_+1>=d)return c(this.buildValues(y[0].lastComponent,r,t));let N=-1/0,S=1/0;const w=()=>{for(let C=Math.max(N,-f);C<=Math.min(S,f);C+=2){let E;const A=y[C-1],B=y[C+1];A&&(y[C-1]=void 0);let R=!1;if(B){const z=B.oldPos-C;R=B&&0<=z&&z<d}const H=A&&A.oldPos+1<h;if(!R&&!H){y[C]=void 0;continue}if(!H||R&&A.oldPos<B.oldPos?E=this.addToPath(B,!0,!1,0,a):E=this.addToPath(A,!1,!0,1,a),_=this.extractCommon(E,r,t,C,a),E.oldPos+1>=h&&_+1>=d)return c(this.buildValues(E.lastComponent,r,t))||!0;y[C]=E,E.oldPos+1>=h&&(S=Math.min(S,C-1)),_+1>=d&&(N=Math.max(N,C+1))}f++};if(s)(function C(){setTimeout(function(){if(f>p||Date.now()>b)return s(void 0);w()||C()},0)})();else for(;f<=p&&Date.now()<=b;){const C=w();if(C)return C}}addToPath(t,r,a,s,o){const c=t.lastComponent;return c&&!o.oneChangePerToken&&c.added===r&&c.removed===a?{oldPos:t.oldPos+s,lastComponent:{count:c.count+1,added:r,removed:a,previousComponent:c.previousComponent}}:{oldPos:t.oldPos+s,lastComponent:{count:1,added:r,removed:a,previousComponent:c}}}extractCommon(t,r,a,s,o){const c=r.length,d=a.length;let h=t.oldPos,f=h-s,p=0;for(;f+1<c&&h+1<d&&this.equals(a[h+1],r[f+1],o);)f++,h++,p++,o.oneChangePerToken&&(t.lastComponent={count:1,previousComponent:t.lastComponent,added:!1,removed:!1});return p&&!o.oneChangePerToken&&(t.lastComponent={count:p,previousComponent:t.lastComponent,added:!1,removed:!1}),t.oldPos=h,f}equals(t,r,a){return a.comparator?a.comparator(t,r):t===r||!!a.ignoreCase&&t.toLowerCase()===r.toLowerCase()}removeEmpty(t){const r=[];for(let a=0;a<t.length;a++)t[a]&&r.push(t[a]);return r}castInput(t,r){return t}tokenize(t,r){return Array.from(t)}join(t){return t.join("")}postProcess(t,r){return t}get useLongestToken(){return!1}buildValues(t,r,a){const s=[];let o;for(;t;)s.push(t),o=t.previousComponent,delete t.previousComponent,t=o;s.reverse();const c=s.length;let d=0,h=0,f=0;for(;d<c;d++){const p=s[d];if(p.removed)p.value=this.join(a.slice(f,f+p.count)),f+=p.count;else{if(!p.added&&this.useLongestToken){let g=r.slice(h,h+p.count);g=g.map(function(b,y){const _=a[f+y];return _.length>b.length?_:b}),p.value=this.join(g)}else p.value=this.join(r.slice(h,h+p.count));h+=p.count,p.added||(f+=p.count)}}return s}}class Jj extends Wj{constructor(){super(...arguments),this.tokenize=nD}equals(t,r,a){return a.ignoreWhitespace?((!a.newlineIsToken||!t.includes(`
|
||||
`))&&(t=t.trim()),(!a.newlineIsToken||!r.includes(`
|
||||
`))&&(r=r.trim())):a.ignoreNewlineAtEof&&!a.newlineIsToken&&(t.endsWith(`
|
||||
`)&&(t=t.slice(0,-1)),r.endsWith(`
|
||||
`)&&(r=r.slice(0,-1))),super.equals(t,r,a)}}const Qj=new Zj;function Wj(e,t,r){return Qj.diff(e,t,r)}function Jj(e,t){t.stripTrailingCr&&(e=e.replace(/\r\n/g,`
|
||||
`));const r=[],a=e.split(/(\n|\r\n)/);a[a.length-1]||a.pop();for(let s=0;s<a.length;s++){const o=a[s];s%2&&!t.newlineIsToken?r[r.length-1]+=o:r.push(o)}return r}function eD(e,t){try{return zn.highlight(e,{language:t,ignoreIllegals:!0}).value}catch{return zn.highlightAuto(e).value}}function tD({file:e,startLine:t,endLine:r,before:a,after:s,onCopy:o}){const[c,d]=ee.useState(!1),h=r&&r!==t?`${t}-${r}`:`${t}`,f=HT(e)||"text",p=Wj(a,s);let g=t,b=t;const y=p.flatMap(N=>N.value.replace(/\n$/,"").split(`
|
||||
`)&&(r=r.slice(0,-1))),super.equals(t,r,a)}}const eD=new Jj;function tD(e,t,r){return eD.diff(e,t,r)}function nD(e,t){t.stripTrailingCr&&(e=e.replace(/\r\n/g,`
|
||||
`));const r=[],a=e.split(/(\n|\r\n)/);a[a.length-1]||a.pop();for(let s=0;s<a.length;s++){const o=a[s];s%2&&!t.newlineIsToken?r[r.length-1]+=o:r.push(o)}return r}function rD(e,t){try{return zn.highlight(e,{language:t,ignoreIllegals:!0}).value}catch{return zn.highlightAuto(e).value}}function iD({file:e,startLine:t,endLine:r,before:a,after:s,onCopy:o}){const[c,d]=ee.useState(!1),h=r&&r!==t?`${t}-${r}`:`${t}`,f=PT(e)||"text",p=tD(a,s);let g=t,b=t;const y=p.flatMap(N=>N.value.replace(/\n$/,"").split(`
|
||||
`).map(S=>{const w=S===""?`
|
||||
`:f!=="text"?eD(S,f):S.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");let C="",E="";return N.removed?C=String(g++):(N.added||(C=String(g++)),E=String(b++)),{highlighted:w,added:!!N.added,removed:!!N.removed,leftNo:C,rightNo:E}})),_=()=>{Cp(s),d(!0),setTimeout(()=>d(!1),2e3),o==null||o()};return m.jsxs("div",{className:"rounded-md border border-[#2a2a2a] overflow-hidden",children:[m.jsxs("div",{className:"flex items-stretch",children:[m.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a] break-all",children:[e,":",h,m.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),m.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),m.jsx("button",{onClick:_,className:"px-3 py-2 text-[#555] hover:text-white transition-colors flex-shrink-0 border-b border-[#2a2a2a]","aria-label":"Copy fixed code",children:c?m.jsx(Vs,{className:"w-3.5 h-3.5 text-emerald-400"}):m.jsx(go,{className:"w-3.5 h-3.5"})})]}),m.jsx("div",{className:"overflow-auto max-h-[400px]",children:m.jsx("table",{className:"w-full border-collapse font-mono text-[12px] leading-[22px] [font-variant-ligatures:none]",children:m.jsx("tbody",{children:y.map((N,S)=>m.jsxs("tr",{className:N.added?"bg-blue-500/[0.12]":N.removed?"bg-red-500/[0.12]":"",children:[m.jsx("td",{className:"select-none w-[1px] whitespace-nowrap pl-4 pr-1.5 text-right text-[#555] align-top text-[12px] leading-[22px]",children:N.leftNo}),m.jsx("td",{className:"select-none w-[1px] whitespace-nowrap pl-1.5 pr-4 text-right text-[#555] align-top text-[12px] leading-[22px] border-r border-[#2a2a2a]",children:N.rightNo}),m.jsx("td",{className:"pl-4 pr-4 whitespace-pre",dangerouslySetInnerHTML:{__html:N.highlighted}})]},S))})})})]})}const nD=/^```([^\n`]*)\r?\n([\s\S]*?)\r?\n?```$/;function yE(e){if(!e)return{code:""};const t=nD.exec(e.trim());if(!t)return{code:e};const r=t[1].trim();return{language:(r?r.split(/\s+/)[0]:void 0)||void 0,code:t[2]}}function rD({description:e,scriptCode:t,onCopy:r}){const[a,s]=ee.useState(!1);if(!e&&!t)return null;const{language:o,code:c}=yE(t),d=xE(c,o),h=()=>{c&&(Cp(c),s(!0),setTimeout(()=>s(!1),2e3),r==null||r())};return m.jsxs("section",{children:[m.jsx("h2",{className:"text-xl font-semibold text-white mb-3",children:"Proof of Concept"}),m.jsxs("div",{className:"space-y-4",children:[e&&m.jsx("div",{className:"prose-markdown",children:m.jsx(Gp,{remarkPlugins:[Kp],rehypePlugins:[Zp],components:Qp,children:e})}),c&&m.jsxs("div",{className:"group/poc relative rounded-md border border-[#2a2a2a] overflow-hidden",children:[m.jsxs("div",{className:"flex items-stretch",children:[m.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]",children:["PoC Script",m.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),m.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),m.jsx("button",{onClick:h,className:"px-3 py-2 text-[#555] hover:text-white transition-colors flex-shrink-0 border-b border-[#2a2a2a]","aria-label":"Copy PoC code",children:a?m.jsx(Vs,{className:"w-3.5 h-3.5 text-emerald-400"}):m.jsx(go,{className:"w-3.5 h-3.5"})})]}),m.jsx("div",{className:"overflow-auto max-h-[400px] px-4 py-3",children:m.jsx("pre",{className:"font-mono text-[12px] leading-[22px] whitespace-pre [font-variant-ligatures:none]",children:m.jsx("code",{dangerouslySetInnerHTML:{__html:d}})})})]})]})]})}function vE(e){const t=e.match(/(?:https?:\/\/)?(?:www\.)?github\.com\/([^\s/]+\/[^\s/]+)/);if(t){const s=t[1].replace(/\.git$/,"");return{display:s,href:`https://github.com/${s}`,provider:"github"}}const r=e.match(/(?:https?:\/\/)?(?:www\.)?gitlab\.com\/([^\s/]+\/[^\s/]+)/);if(r){const s=r[1].replace(/\.git$/,"");return{display:s,href:`https://gitlab.com/${s}`,provider:"gitlab"}}const a=e.match(/(?:https?:\/\/)?(?:www\.)?bitbucket\.org\/([^\s/]+\/[^\s/]+)/);if(a){const s=a[1].replace(/\.git$/,"");return{display:s,href:`https://bitbucket.org/${s}`,provider:"bitbucket"}}return/^https?:\/\//i.test(e)?{display:e.replace(/^https?:\/\/(www\.)?/,""),href:e,provider:null}:/^[a-zA-Z0-9][\w.-]*\.[a-zA-Z]{2,}/.test(e)?{display:e,href:`https://${e}`,provider:null}:{display:e,href:null,provider:null}}function yo(e,t){return e?vE(e).display.replace(/\/$/,""):t||"Untitled pentest"}function iD({className:e}){return m.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor",className:e,"aria-hidden":"true",children:m.jsx("path",{d:"M2.65 3a.72.72 0 0 0-.72.83l2.86 17.39a.98.98 0 0 0 .96.82h13.72a.72.72 0 0 0 .72-.6l2.86-17.4A.72.72 0 0 0 22.3 3H2.65Zm12.1 12.53H9.3L8.06 8.9h7.8l-1.11 6.63Z"})})}function aD({provider:e,className:t}){const r=t??"w-4 h-4";return e==="gitlab"?m.jsx(UC,{className:`${r} text-orange-400`}):e==="bitbucket"?m.jsx(iD,{className:`${r} text-blue-400`}):m.jsx(IC,{className:`${r} text-white`})}const sD={attack_vector:{N:"Remotely exploitable",A:"Adjacent network",L:"Local access
Line truncated
|
||||
`:f!=="text"?rD(S,f):S.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">");let C="",E="";return N.removed?C=String(g++):(N.added||(C=String(g++)),E=String(b++)),{highlighted:w,added:!!N.added,removed:!!N.removed,leftNo:C,rightNo:E}})),_=()=>{Tp(s),d(!0),setTimeout(()=>d(!1),2e3),o==null||o()};return m.jsxs("div",{className:"rounded-md border border-[#2a2a2a] overflow-hidden",children:[m.jsxs("div",{className:"flex items-stretch",children:[m.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a] break-all",children:[e,":",h,m.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),m.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),m.jsx("button",{onClick:_,className:"px-3 py-2 text-[#555] hover:text-white transition-colors flex-shrink-0 border-b border-[#2a2a2a]","aria-label":"Copy fixed code",children:c?m.jsx(Vs,{className:"w-3.5 h-3.5 text-emerald-400"}):m.jsx(go,{className:"w-3.5 h-3.5"})})]}),m.jsx("div",{className:"overflow-auto max-h-[400px]",children:m.jsx("table",{className:"w-full border-collapse font-mono text-[12px] leading-[22px] [font-variant-ligatures:none]",children:m.jsx("tbody",{children:y.map((N,S)=>m.jsxs("tr",{className:N.added?"bg-blue-500/[0.12]":N.removed?"bg-red-500/[0.12]":"",children:[m.jsx("td",{className:"select-none w-[1px] whitespace-nowrap pl-4 pr-1.5 text-right text-[#555] align-top text-[12px] leading-[22px]",children:N.leftNo}),m.jsx("td",{className:"select-none w-[1px] whitespace-nowrap pl-1.5 pr-4 text-right text-[#555] align-top text-[12px] leading-[22px] border-r border-[#2a2a2a]",children:N.rightNo}),m.jsx("td",{className:"pl-4 pr-4 whitespace-pre",dangerouslySetInnerHTML:{__html:N.highlighted}})]},S))})})})]})}const aD=/^```([^\n`]*)\r?\n([\s\S]*?)\r?\n?```$/;function EE(e){if(!e)return{code:""};const t=aD.exec(e.trim());if(!t)return{code:e};const r=t[1].trim();return{language:(r?r.split(/\s+/)[0]:void 0)||void 0,code:t[2]}}function sD({description:e,scriptCode:t,onCopy:r}){const[a,s]=ee.useState(!1);if(!e&&!t)return null;const{language:o,code:c}=EE(t),d=_E(c,o),h=()=>{c&&(Tp(c),s(!0),setTimeout(()=>s(!1),2e3),r==null||r())};return m.jsxs("section",{children:[m.jsx("h2",{className:"text-xl font-semibold text-white mb-3",children:"Proof of Concept"}),m.jsxs("div",{className:"space-y-4",children:[e&&m.jsx("div",{className:"prose-markdown",children:m.jsx(Yp,{remarkPlugins:[Qp],rehypePlugins:[Wp],components:Jp,children:e})}),c&&m.jsxs("div",{className:"group/poc relative rounded-md border border-[#2a2a2a] overflow-hidden",children:[m.jsxs("div",{className:"flex items-stretch",children:[m.jsxs("span",{className:"relative flex items-center text-[13px] text-[#999] font-mono px-4 py-2 border-r border-[#2a2a2a]",children:["PoC Script",m.jsx("span",{className:"absolute top-0 inset-x-0 h-0.5 bg-white/60 rounded-full"})]}),m.jsx("div",{className:"flex-1 border-b border-[#2a2a2a]"}),m.jsx("button",{onClick:h,className:"px-3 py-2 text-[#555] hover:text-white transition-colors flex-shrink-0 border-b border-[#2a2a2a]","aria-label":"Copy PoC code",children:a?m.jsx(Vs,{className:"w-3.5 h-3.5 text-emerald-400"}):m.jsx(go,{className:"w-3.5 h-3.5"})})]}),m.jsx("div",{className:"overflow-auto max-h-[400px] px-4 py-3",children:m.jsx("pre",{className:"font-mono text-[12px] leading-[22px] whitespace-pre [font-variant-ligatures:none]",children:m.jsx("code",{dangerouslySetInnerHTML:{__html:d}})})})]})]})]})}function NE(e){const t=e.match(/(?:https?:\/\/)?(?:www\.)?github\.com\/([^\s/]+\/[^\s/]+)/);if(t){const s=t[1].replace(/\.git$/,"");return{display:s,href:`https://github.com/${s}`,provider:"github"}}const r=e.match(/(?:https?:\/\/)?(?:www\.)?gitlab\.com\/([^\s/]+\/[^\s/]+)/);if(r){const s=r[1].replace(/\.git$/,"");return{display:s,href:`https://gitlab.com/${s}`,provider:"gitlab"}}const a=e.match(/(?:https?:\/\/)?(?:www\.)?bitbucket\.org\/([^\s/]+\/[^\s/]+)/);if(a){const s=a[1].replace(/\.git$/,"");return{display:s,href:`https://bitbucket.org/${s}`,provider:"bitbucket"}}return/^https?:\/\//i.test(e)?{display:e.replace(/^https?:\/\/(www\.)?/,""),href:e,provider:null}:/^[a-zA-Z0-9][\w.-]*\.[a-zA-Z]{2,}/.test(e)?{display:e,href:`https://${e}`,provider:null}:{display:e,href:null,provider:null}}function yo(e,t){return e?NE(e).display.replace(/\/$/,""):t||"Untitled pentest"}function lD({className:e}){return m.jsx("svg",{viewBox:"0 0 24 24",fill:"currentColor",className:e,"aria-hidden":"true",children:m.jsx("path",{d:"M2.65 3a.72.72 0 0 0-.72.83l2.86 17.39a.98.98 0 0 0 .96.82h13.72a.72.72 0 0 0 .72-.6l2.86-17.4A.72.72 0 0 0 22.3 3H2.65Zm12.1 12.53H9.3L8.06 8.9h7.8l-1.11 6.63Z"})})}function oD({provider:e,className:t}){const r=t??"w-4 h-4";return e==="gitlab"?m.jsx(PC,{className:`${r} text-orange-400`}):e==="bitbucket"?m.jsx(lD,{className:`${r} text-blue-400`}):m.jsx($C,{className:`${r} text-white`})}const cD={attack_vector:{N:"Remotely exploitable",A:"Adjacent network",L:"Local access
Line truncated
|
||||
* @license React
|
||||
* use-sync-external-store-shim.production.js
|
||||
*
|
||||
@@ -466,7 +471,7 @@ https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var w1;function VI(){if(w1)return Om;w1=1;var e=Mo();function t(g,b){return g===b&&(g!==0||1/g===1/b)||g!==g&&b!==b}var r=typeof Object.is=="function"?Object.is:t,a=e.useState,s=e.useEffect,o=e.useLayoutEffect,c=e.useDebugValue;function d(g,b){var y=b(),_=a({inst:{value:y,getSnapshot:b}}),N=_[0].inst,S=_[1];return o(function(){N.value=y,N.getSnapshot=b,h(N)&&S({inst:N})},[g,y,b]),s(function(){return h(N)&&S({inst:N}),g(function(){h(N)&&S({inst:N})})},[g]),c(y),y}function h(g){var b=g.getSnapshot;g=g.value;try{var y=b();return!r(g,y)}catch{return!0}}function f(g,b){return b()}var p=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?f:d;return Om.useSyncExternalStore=e.useSyncExternalStore!==void 0?e.useSyncExternalStore:p,Om}var E1;function YI(){return E1||(E1=1,Mm.exports=VI()),Mm.exports}/**
|
||||
*/var N1;function KI(){if(N1)return Rm;N1=1;var e=Oo();function t(g,b){return g===b&&(g!==0||1/g===1/b)||g!==g&&b!==b}var r=typeof Object.is=="function"?Object.is:t,a=e.useState,s=e.useEffect,o=e.useLayoutEffect,c=e.useDebugValue;function d(g,b){var y=b(),_=a({inst:{value:y,getSnapshot:b}}),N=_[0].inst,S=_[1];return o(function(){N.value=y,N.getSnapshot=b,h(N)&&S({inst:N})},[g,y,b]),s(function(){return h(N)&&S({inst:N}),g(function(){h(N)&&S({inst:N})})},[g]),c(y),y}function h(g){var b=g.getSnapshot;g=g.value;try{var y=b();return!r(g,y)}catch{return!0}}function f(g,b){return b()}var p=typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"?f:d;return Rm.useSyncExternalStore=e.useSyncExternalStore!==void 0?e.useSyncExternalStore:p,Rm}var S1;function ZI(){return S1||(S1=1,Om.exports=KI()),Om.exports}/**
|
||||
* @license React
|
||||
* use-sync-external-store-shim/with-selector.production.js
|
||||
*
|
||||
@@ -474,34 +479,34 @@ https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/var N1;function XI(){if(N1)return Am;N1=1;var e=Mo(),t=YI();function r(f,p){return f===p&&(f!==0||1/f===1/p)||f!==f&&p!==p}var a=typeof Object.is=="function"?Object.is:r,s=t.useSyncExternalStore,o=e.useRef,c=e.useEffect,d=e.useMemo,h=e.useDebugValue;return Am.useSyncExternalStoreWithSelector=function(f,p,g,b,y){var _=o(null);if(_.current===null){var N={hasValue:!1,value:null};_.current=N}else N=_.current;_=d(function(){function w(R){if(!C){if(C=!0,E=R,R=b(R),y!==void 0&&N.hasValue){var H=N.value;if(y(H,R))return A=H}return A=R}if(H=A,a(E,R))return H;var z=b(R);return y!==void 0&&y(H,z)?(E=R,H):(E=R,A=z)}var C=!1,E,A,B=g===void 0?null:g;return[function(){return w(p())},B===null?void 0:function(){return w(B())}]},[p,g,b,y]);var S=s(f,_[0],_[1]);return c(function(){N.hasValue=!0,N.value=S},[S]),h(S),S},Am}var S1;function KI(){return S1||(S1=1,Tm.exports=XI()),Tm.exports}var ZI=KI();const QI=Ao(ZI),WI={},k1=e=>{let t;const r=new Set,a=(p,g)=>{const b=typeof p=="function"?p(t):p;if(!Object.is(b,t)){const y=t;t=g??(typeof b!="object"||b===null)?b:Object.assign({},t,b),r.forEach(_=>_(t,y))}},s=()=>t,h={setState:a,getState:s,getInitialState:()=>f,subscribe:p=>(r.add(p),()=>r.delete(p)),destroy:()=>{(WI?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),r.clear()}},f=t=e(a,s,h);return h},JI=e=>e?k1(e):k1,{useDebugValue:e8}=da,{useSyncExternalStoreWithSelector:t8}=QI,n8=e=>e;function CN(e,t=n8,r){const a=t8(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,r);return e8(a),a}const C1=(e,t)=>{const r=JI(e),a=(s,o=t)=>CN(r,s,o);return Object.assign(a,r),a},r8=(e,t)=>e?C1(e,t):C1;function qt(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(const[a,s]of e)if(!Object.is(s,t.get(a)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(const a of e)if(!t.has(a))return!1;return!0}const r=Object.keys(e);if(r.length!==Object.keys(t).length)return!1;for(const a of r)if(!Object.prototype.hasOwnProperty.call(t,a)||!Object.is(e[a],t[a]))return!1;return!0}D_();const sd=ee.createContext(null),i8=sd.Provider,TN=Lr.error001("react");function dt(e,t){const r=ee.useContext(sd);if(r===null)throw new Error(TN);return CN(r,e,t)}function Lt(){const e=ee.useContext(sd);if(e===null)throw new Error(TN);return ee.useMemo(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe}),[e])}const T1={display:"none"},a8={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},AN="react-flow__node-desc",MN="react-flow__edge-desc",s8="react-flow__aria-live",l8=e=>e.ariaLiveMessage,o8=e=>e.ariaLabelConfig;function c8({rfId:e}){const t=dt(l8);return m.jsx("div",{id:`${s8}-${e}`,"aria-live":"assertive","aria-atomic":"true",style:a8,children:t})}function u8({rfId:e,disableKeyboardA11y:t}){const r=dt(o8);return m.jsxs(m.Fragment,{children:[m.jsx("div",{id:`${AN}-${e}`,style:T1,children:t?r["node.a11yDescription.default"]:r["node.a11yDescription.keyboardDisabled"]}),m.jsx("div",{id:`${MN}-${e}`,style:T1,children:r["edge.a11yDescription.default"]}),!t&&m.jsx(c8,{rfId:e})]})}const ld=ee.forwardRef(({position:e="top-left",children:t,className:r,style:a,...s},o)=>{const c=`${e}`.split("-");return m.jsx("div",{className:on(["react-flow__panel",r,...c]),style:a,ref:o,...s,children:t})});ld.displayName="Panel";const A1="https://reactflow.dev?utm_source=attribution";function d8({proOptions:e,position:t="bottom-right"}){return e!=null&&e.hideAttribution?null:m.jsx(ld,{position:t,className:"react-flow__attribution","data-message":`Please only hide this attribution when you are subscribed to React Flow Pro: ${A1}`,children:m.jsx("a",{href:A1,target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution",children:"React Flow"})})}const f8=e=>{const t=[],r=[];for(const[,a]of e.nodeLookup)a.selected&&t.push(a.internals.userNode);for(const[,a]of e.edgeLookup)a.selected&&r.push(a);return{selectedNodes:t,selectedEdges:r}},fu=e=>e.id;function h8(e,t){return qt(e.selectedNodes.map(fu),t.selectedNodes.map(fu))&&qt(e.selectedEdges.map(fu),t.selectedEdges.map(fu))}function m8({onSelectionChange:e}){const t=Lt(),{selectedNodes:r,selectedEdges:a}=dt(f8,h8);return ee.useEffect(()=>{const s={nodes:r,edges:a};e==null||e(s),t.getState().onSelectionChangeHandlers.forEach(o=>o(s))},[r,a,e]),null}const p8=e=>!!e.onSelectionChangeHandlers;function g8({onSelectionChange:e}){const t=dt(p8);return e||t?m.jsx(m8,{onSelectionChange:e}):null}const ON=[0,0],x8={x:0,y:0,zoom:1},b8=["nodes","edges","defaultNodes","defaultEdges","onConnect","onConnectStart","onConnectEnd","onClickConnectStart","onClickConnectEnd","nodesDraLine truncated
|
||||
*/var k1;function QI(){if(k1)return Mm;k1=1;var e=Oo(),t=ZI();function r(f,p){return f===p&&(f!==0||1/f===1/p)||f!==f&&p!==p}var a=typeof Object.is=="function"?Object.is:r,s=t.useSyncExternalStore,o=e.useRef,c=e.useEffect,d=e.useMemo,h=e.useDebugValue;return Mm.useSyncExternalStoreWithSelector=function(f,p,g,b,y){var _=o(null);if(_.current===null){var N={hasValue:!1,value:null};_.current=N}else N=_.current;_=d(function(){function w(R){if(!C){if(C=!0,E=R,R=b(R),y!==void 0&&N.hasValue){var H=N.value;if(y(H,R))return A=H}return A=R}if(H=A,a(E,R))return H;var z=b(R);return y!==void 0&&y(H,z)?(E=R,H):(E=R,A=z)}var C=!1,E,A,B=g===void 0?null:g;return[function(){return w(p())},B===null?void 0:function(){return w(B())}]},[p,g,b,y]);var S=s(f,_[0],_[1]);return c(function(){N.hasValue=!0,N.value=S},[S]),h(S),S},Mm}var C1;function WI(){return C1||(C1=1,Am.exports=QI()),Am.exports}var JI=WI();const e8=Mo(JI),t8={},T1=e=>{let t;const r=new Set,a=(p,g)=>{const b=typeof p=="function"?p(t):p;if(!Object.is(b,t)){const y=t;t=g??(typeof b!="object"||b===null)?b:Object.assign({},t,b),r.forEach(_=>_(t,y))}},s=()=>t,h={setState:a,getState:s,getInitialState:()=>f,subscribe:p=>(r.add(p),()=>r.delete(p)),destroy:()=>{(t8?"production":void 0)!=="production"&&console.warn("[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected."),r.clear()}},f=t=e(a,s,h);return h},n8=e=>e?T1(e):T1,{useDebugValue:r8}=fa,{useSyncExternalStoreWithSelector:i8}=e8,a8=e=>e;function ON(e,t=a8,r){const a=i8(e.subscribe,e.getState,e.getServerState||e.getInitialState,t,r);return r8(a),a}const A1=(e,t)=>{const r=n8(e),a=(s,o=t)=>ON(r,s,o);return Object.assign(a,r),a},s8=(e,t)=>e?A1(e,t):A1;function Pt(e,t){if(Object.is(e,t))return!0;if(typeof e!="object"||e===null||typeof t!="object"||t===null)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(const[a,s]of e)if(!Object.is(s,t.get(a)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(const a of e)if(!t.has(a))return!1;return!0}const r=Object.keys(e);if(r.length!==Object.keys(t).length)return!1;for(const a of r)if(!Object.prototype.hasOwnProperty.call(t,a)||!Object.is(e[a],t[a]))return!1;return!0}z_();const ld=ee.createContext(null),l8=ld.Provider,RN=Lr.error001("react");function dt(e,t){const r=ee.useContext(ld);if(r===null)throw new Error(RN);return ON(r,e,t)}function Lt(){const e=ee.useContext(ld);if(e===null)throw new Error(RN);return ee.useMemo(()=>({getState:e.getState,setState:e.setState,subscribe:e.subscribe}),[e])}const M1={display:"none"},o8={position:"absolute",width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0px, 0px, 0px, 0px)",clipPath:"inset(100%)"},jN="react-flow__node-desc",DN="react-flow__edge-desc",c8="react-flow__aria-live",u8=e=>e.ariaLiveMessage,d8=e=>e.ariaLabelConfig;function f8({rfId:e}){const t=dt(u8);return m.jsx("div",{id:`${c8}-${e}`,"aria-live":"assertive","aria-atomic":"true",style:o8,children:t})}function h8({rfId:e,disableKeyboardA11y:t}){const r=dt(d8);return m.jsxs(m.Fragment,{children:[m.jsx("div",{id:`${jN}-${e}`,style:M1,children:t?r["node.a11yDescription.default"]:r["node.a11yDescription.keyboardDisabled"]}),m.jsx("div",{id:`${DN}-${e}`,style:M1,children:r["edge.a11yDescription.default"]}),!t&&m.jsx(f8,{rfId:e})]})}const od=ee.forwardRef(({position:e="top-left",children:t,className:r,style:a,...s},o)=>{const c=`${e}`.split("-");return m.jsx("div",{className:on(["react-flow__panel",r,...c]),style:a,ref:o,...s,children:t})});od.displayName="Panel";const O1="https://reactflow.dev?utm_source=attribution";function m8({proOptions:e,position:t="bottom-right"}){return e!=null&&e.hideAttribution?null:m.jsx(od,{position:t,className:"react-flow__attribution","data-message":`Please only hide this attribution when you are subscribed to React Flow Pro: ${O1}`,children:m.jsx("a",{href:O1,target:"_blank",rel:"noopener noreferrer","aria-label":"React Flow attribution",children:"React Flow"})})}const p8=e=>{const t=[],r=[];for(const[,a]of e.nodeLookup)a.selected&&t.push(a.internals.userNode);for(const[,a]of e.edgeLookup)a.selected&&r.push(a);return{selectedNodes:t,selectedEdges:r}},hu=e=>e.id;function g8(e,t){return Pt(e.selectedNodes.map(hu),t.selectedNodes.map(hu))&&Pt(e.selectedEdges.map(hu),t.selectedEdges.map(hu))}function x8({onSelectionChange:e}){const t=Lt(),{selectedNodes:r,selectedEdges:a}=dt(p8,g8);return ee.useEffect(()=>{const s={nodes:r,edges:a};e==null||e(s),t.getState().onSelectionChangeHandlers.forEach(o=>o(s))},[r,a,e]),null}const b8=e=>!!e.onSelectionChangeHandlers;function y8({onSelectionChange:e}){const t=dt(b8);return e||t?m.jsx(x8,{onSelectionChange:e}):null}const LN=[0,0],v8={x:0,y:0,zoom:1},_8=["nodes","edges","defaultNodes","defaultEdges","onConnect","onConnectStart","onConnectEnd","onClickConnectStart","onClickConnectEnd","nodesDraLine truncated
|
||||
`).replace(`
|
||||
|
||||
`,`
|
||||
+`).split(`
|
||||
`)),p=f.reduce((g,b)=>g.concat(...b),[]);return[f,p]}return[[],[]]},[e]);return ee.useEffect(()=>{const h=(t==null?void 0:t.target)??j1,f=(t==null?void 0:t.actInsideInputWithModifier)??!0;if(e!==null){const p=y=>{var S,w;if(s.current=y.ctrlKey||y.metaKey||y.shiftKey||y.altKey,(!s.current||s.current&&!f)&&uN(y))return!1;const N=L1(y.code,d);if(o.current.add(y[N]),D1(c,o.current,!1)){const C=((w=(S=y.composedPath)==null?void 0:S.call(y))==null?void 0:w[0])||y.target,E=(C==null?void 0:C.nodeName)==="BUTTON"||(C==null?void 0:C.nodeName)==="A";t.preventDefault!==!1&&(s.current||!E)&&y.preventDefault(),a(!0)}},g=y=>{const _=L1(y.code,d);D1(c,o.current,!0)?(a(!1),o.current.clear()):o.current.delete(y[_]),y.key==="Meta"&&o.current.clear(),s.current=!1},b=()=>{o.current.clear(),a(!1)};return h==null||h.addEventListener("keydown",p),h==null||h.addEventListener("keyup",g),window.addEventListener("blur",b),window.addEventListener("contextmenu",b),()=>{h==null||h.removeEventListener("keydown",p),h==null||h.removeEventListener("keyup",g),window.removeEventListener("blur",b),window.removeEventListener("contextmenu",b)}}},[e,a]),r}function D1(e,t,r){return e.filter(a=>r||a.length===t.size).some(a=>a.every(s=>t.has(s)))}function L1(e,t){return t.includes(e)?"code":"key"}const w8=()=>{const e=Lt();return ee.useMemo(()=>({zoomIn:async t=>{const{panZoom:r}=e.getState();return r?r.scaleBy(1.2,t):!1},zoomOut:async t=>{const{panZoom:r}=e.getState();return r?r.scaleBy(1/1.2,t):!1},zoomTo:async(t,r)=>{const{panZoom:a}=e.getState();return a?a.scaleTo(t,r):!1},getZoom:()=>e.getState().transform[2],setViewport:async(t,r)=>{const{transform:[a,s,o],panZoom:c}=e.getState();return c?(await c.setViewport({x:t.x??a,y:t.y??s,zoom:t.zoom??o},r),!0):!1},getViewport:()=>{const[t,r,a]=e.getState().transform;return{x:t,y:r,zoom:a}},setCenter:async(t,r,a)=>e.getState().setCenter(t,r,a),fitBounds:async(t,r)=>{const{width:a,height:s,minZoom:o,maxZoom:c,panZoom:d}=e.getState(),h=og(t,a,s,o,c,(r==null?void 0:r.padding)??.1);return d?(await d.setViewport(h,{duration:r==null?void 0:r.duration,ease:r==null?void 0:r.ease,interpolate:r==null?void 0:r.interpolate}),!0):!1},screenToFlowPosition:(t,r={})=>{const{transform:a,snapGrid:s,snapToGrid:o,domNode:c}=e.getState();if(!c)return t;const{x:d,y:h}=c.getBoundingClientRect(),f={x:t.x-d,y:t.y-h},p=r.snapGrid??s,g=r.snapToGrid??o;return Ho(f,a,g,p)},flowToScreenPosition:t=>{const{transform:r,domNode:a}=e.getState();if(!a)return t;const{x:s,y:o}=a.getBoundingClientRect(),c=Js(t,r);return{x:c.x+s,y:c.y+o}}}),[])};function RN(e,t){const r=[],a=new Map,s=[];for(const o of e)if(o.type==="add"){s.push(o);continue}else if(o.type==="remove"||o.type==="replace")a.set(o.id,[o]);else{const c=a.get(o.id);c?c.push(o):a.set(o.id,[o])}for(const o of t){const c=a.get(o.id);if(!c){r.push(o);continue}if(c[0].type==="remove")continue;if(c[0].type==="replace"){r.push({...c[0].item});continue}const d={...o};for(const h of c)E8(h,d);r.push(d)}return s.length&&s.forEach(o=>{o.index!==void 0?r.splice(o.index,0,{...o.item}):r.push({...o.item})}),r}function E8(e,t){switch(e.type){case"select":{t.selected=e.selected;break}case"position":{typeof e.position<"u"&&(t.position=e.position),typeof e.dragging<"u"&&(t.dragging=e.dragging);break}case"dimensions":{typeof e.dimensions<"u"&&(t.measured={...e.dimensions},e.setAttributes&&((e.setAttributes===!0||e.setAttributes==="width")&&(t.width=e.dimensions.width),(e.setAttributes===!0||e.setAttributes==="height")&&(t.height=e.dimensions.height))),typeof e.resizing=="boolean"&&(t.resizing=e.resizing);break}}}function jN(e,t){return RN(e,t)}function DN(e,t){return RN(e,t)}function Ha(e,t){return{id:e,type:"select",selected:t}}function qs(e,t=new Set,r=!1){const a=[];for(const[s,o]of e){const c=t.has(s);!(o.selected===void 0&&!c)&&o.selected!==c&&(r&&(o.selected=c),a.push(Ha(o.id,c)))}return a}function z1({items:e=[],lookup:t}){var s;const r=[],a=new Map(e.map(o=>[o.id,o]));for(const[o,c]of e.entries()){const d=t.get(c.id),h=((s=d==null?void 0:d.internals)==null?void 0:s.userNode)??d;h!==void 0&&h!==c&&r.push({id:c.id,item:c,type:"replace"}),h===void 0&&r.push({item:c,type:"add",index:o})}for(const[o]of t)a.get(o)===void 0&&r.push({id:o,type:"remove"});return r}function I1(e){return{id:e.id,type:"remove"}}const N8=sN();function S8(e,t,r={}){return uI(e,t,{...r,onError:r.onError??N8})}const B1=e=>Kz(e),k8=e=>tN(e);function LN(e){return ee.forwardRef(e)}const zN=typeof window<"u"?ee.useLayoutEffect:ee.useEffect;function U1(e){const[t,r]=ee.useState(BigInt(0)),[a]=ee.useState(()=>C8(()=>r(s=>s+BigInt(1))));return zN(()=>{const s=a.get();s.length&&(e(s),a.reset())},[t]),a}function C8(e){let t=[];return{get:()=>t,reset:()=>{t=[]},push:r=>{t.push(r),e()}}}const IN=ee.createContext(null);function T8({children:e}){const t=Lt(),r=ee.useCallback(d=>{const{nodes:h=[],setNodes:f,hasDefaultNodes:p,onNodesChange:g,nodeLookup:b,fitViewQueued:y,onNodesChangeMiddlewareMap:_}=t.getState();let N=h;for(const w of d)N=
Line truncated
|
||||
M${z.x},${z.y}h${z.width}v${z.height}h${-z.width}z`,fillRule:"evenodd",pointerEvents:"none"})]})})}c2.displayName="MiniMap";const wB=ee.memo(c2),EB=e=>t=>e?`${Math.max(1/t.transform[2],1)}`:void 0,NB={[el.Line]:"right",[el.Handle]:"bottom-right"};function SB({nodeId:e,position:t,variant:r=el.Handle,className:a,style:s=void 0,children:o,color:c,minWidth:d=10,minHeight:h=10,maxWidth:f=Number.MAX_VALUE,maxHeight:p=Number.MAX_VALUE,keepAspectRatio:g=!1,resizeDirection:b,autoScale:y=!0,shouldResize:_,onResizeStart:N,onResize:S,onResizeEnd:w}){const C=HN(),E=typeof e=="string"?e:C,A=Lt(),B=ee.useRef(null),R=r===el.Handle,H=dt(ee.useCallback(EB(R&&y),[R,y]),qt),z=ee.useRef(null),Y=t??NB[r];ee.useEffect(()=>{if(!(!B.current||!E))return z.current||(z.current=GI({domNode:B.current,nodeId:E,getStoreItems:()=>{const{nodeLookup:I,transform:Z,snapGrid:P,snapToGrid:k,nodeOrigin:$,domNode:O}=A.getState();return{nodeLookup:I,transform:Z,snapGrid:P,snapToGrid:k,nodeOrigin:$,paneDomNode:O}},onChange:(I,Z)=>{const{triggerNodeChanges:P,nodeLookup:k,parentLookup:$,nodeOrigin:O}=A.getState(),U=[],K={x:I.x,y:I.y},X=k.get(E);if(X&&X.expandParent&&X.parentId){const M=X.origin??O,L=I.width??X.measured.width??0,F=I.height??X.measured.height??0,D={id:X.id,parentId:X.parentId,rect:{width:L,height:F,...oN({x:I.x??X.position.x,y:I.y??X.position.y},{width:L,height:F},X.parentId,k,M)}},V=mg([D],k,$,O);U.push(...V),K.x=I.x?Math.max(M[0]*L,I.x):void 0,K.y=I.y?Math.max(M[1]*F,I.y):void 0}if(K.x!==void 0&&K.y!==void 0){const M={id:E,type:"position",position:{...K}};U.push(M)}if(I.width!==void 0&&I.height!==void 0){const L={id:E,type:"dimensions",resizing:!0,setAttributes:b?b==="horizontal"?"width":"height":!0,dimensions:{width:I.width,height:I.height}};U.push(L)}for(const M of Z){const L={...M,type:"position"};U.push(L)}P(U)},onEnd:({width:I,height:Z})=>{const P={id:E,type:"dimensions",resizing:!1,dimensions:{width:I,height:Z}};A.getState().triggerNodeChanges([P])}})),z.current.update({controlPosition:Y,boundaries:{minWidth:d,minHeight:h,maxWidth:f,maxHeight:p},keepAspectRatio:g,resizeDirection:b,onResizeStart:N,onResize:S,onResizeEnd:w,shouldResize:_}),()=>{var I;(I=z.current)==null||I.destroy()}},[Y,d,h,f,p,g,N,S,w,_]);const j=Y.split("-");return m.jsx("div",{className:on(["react-flow__resize-control","nodrag",...j,r,a]),ref:B,style:{...s,scale:H,...c&&{[R?"backgroundColor":"borderColor"]:c}},children:o})}ee.memo(SB);var vt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),zr=vt((e,t)=>{var r=Object.defineProperty,a=(P,k,$)=>k in P?r(P,k,{enumerable:!0,configurable:!0,writable:!0,value:$}):P[k]=$,s=(P,k)=>()=>(k||P((k={exports:{}}).exports,k),k.exports),o=(P,k,$)=>a(P,typeof k!="symbol"?k+"":k,$),c=s((P,k)=>{var $="\0",O="\0",U="",K=class{constructor(V){o(this,"_isDirected",!0),o(this,"_isMultigraph",!1),o(this,"_isCompound",!1),o(this,"_label"),o(this,"_defaultNodeLabelFn",()=>{}),o(this,"_defaultEdgeLabelFn",()=>{}),o(this,"_nodes",{}),o(this,"_in",{}),o(this,"_preds",{}),o(this,"_out",{}),o(this,"_sucs",{}),o(this,"_edgeObjs",{}),o(this,"_edgeLabels",{}),o(this,"_nodeCount",0),o(this,"_edgeCount",0),o(this,"_parent"),o(this,"_children"),V&&(this._isDirected=Object.hasOwn(V,"directed")?V.directed:!0,this._isMultigraph=Object.hasOwn(V,"multigraph")?V.multigraph:!1,this._isCompound=Object.hasOwn(V,"compound")?V.compound:!1),this._isCompound&&(this._parent={},this._children={},this._children[O]={})}isDirected(){return this._isDirected}isMultigraph(){return this._isMultigraph}isCompound(){return this._isCompound}setGraph(V){return this._label=V,this}graph(){return this._label}setDefaultNodeLabel(V){return this._defaultNodeLabelFn=V,typeof V!="function"&&(this._defaultNodeLabelFn=()=>V),this}nodeCount(){return this._nodeCount}nodes(){return Object.keys(this._nodes)}sources(){var V=this;return this.nodes().filter(q=>Object.keys(V._in[q]).length===0)}sinks(){var V=this;return this.nodes().filter(q=>Object.keys(V._out[q]).length===0)}setNodes(V,q){var Q=arguments,J=this;return V.forEach(function(W){Q.length>1?J.setNode(W,q):J.setNode(W)}),this}setNode(V,q){return Object.hasOwn(this._nodes,V)?(arguments.length>1&&(this._nodes[V]=q),this):(this._nodes[V]=arguments.length>1?q:this._defaultNodeLabelFn(V),this._isCompound&&(this._parent[V]=O,this._children[V]={},this._children[O][V]=!0),this._in[V]={},this._preds[V]={},this._out[V]={},this._sucs[V]={},++this._nodeCount,this)}node(V){return this._nodes[V]}hasNode(V){return Object.hasOwn(this._nodes,V)}removeNode(V){var q=this;if(Object.hasOwn(this._nodes,V)){var Q=J=>q.removeEdge(q._edgeObjs[J]);delete this._nodes[V],this._isCompound&&(this._removeFromParentsChildList(V),delete this._parent[V],this.children(V).forEach(function(J){q.setParent(J)}),delete this._children[V]),Object.keys(this._in[V]).forEach(Q),delete this._in[V],delete this._preds[V],Object.keys(this._out[V]).forEach(Q),delete this._out[V],delete this._sucs[V],--this._nodeCount}return this}setParent(V,q){if(!this._isCompound
Line truncated
|
||||
`).length>t;return m.jsxs("div",{children:[m.jsx("div",{className:r&&o?"max-h-[1200px] overflow-auto":"",style:!r&&o?{display:"-webkit-box",WebkitLineClamp:t,WebkitBoxOrient:"vertical",overflow:"hidden"}:void 0,children:m.jsx(ua,{text:e})}),o&&m.jsx("button",{onClick:()=>a(!r),className:"text-xs text-[#555] hover:text-[#888] mt-1",children:r?"Show less":"Show more"})]})}function wi({children:e,className:t=""}){const[r,a]=ee.useState(!1),o=typeof e=="string"?e.trimEnd().split(`
|
||||
`):null,c=o!==null&&o.length>n_,d=c&&!r?o.slice(0,n_).join(`
|
||||
`):e;return m.jsxs("div",{children:[m.jsx("pre",{className:`font-mono text-[13px] leading-relaxed whitespace-pre-wrap break-words mt-1 ${r?"overflow-auto max-h-[1200px]":"overflow-hidden"} ${t}`,children:d}),c&&m.jsx("button",{onClick:()=>a(!r),className:"text-xs text-[#555] hover:text-[#888] mt-0.5",children:r?"Show less":"Show more"})]})}function gg({code:e,language:t,className:r="",collapsible:a=!1}){const[s,o]=ee.useState(!1),c=e.trimEnd().split(`
|
||||
`),d=a&&c.length>r_,h=d&&!s?c.slice(0,r_).join(`
|
||||
`):e;let f;try{f=t?zn.highlight(h,{language:t,ignoreIllegals:!0}).value:zn.highlightAuto(h).value}catch{f=zn.highlightAuto(h).value}return m.jsxs("div",{children:[m.jsx("pre",{className:`font-mono text-[12px] leading-relaxed px-0 py-1 mt-1 whitespace-pre-wrap break-all ${a?s?"overflow-auto max-h-[1200px]":"overflow-hidden":"overflow-auto max-h-[400px]"} ${r}`,children:m.jsx("code",{dangerouslySetInnerHTML:{__html:f}})}),d&&m.jsx("button",{onClick:()=>o(!s),className:"text-xs text-[#555] hover:text-[#888] mt-0.5",children:s?"Show less":"Show more"})]})}const a7=50,i_=200,a_=25,s_=24,s7=[/\n?\[Command still running after [\d.]+s - showing output so far\.?\s*(?:Use C-c to interrupt if needed\.)?\]/g,/^\[Below is the output of the previous command\.\]\n?/gm,/^No command is currently running\. Cannot send input\.$/gm,/^A command is already running\. Use is_input=true to send input to it, or interrupt it first \(e\.g\., with C-c\)\.$/gm],l7=/^Chunk ID: [0-9a-f]+\s*$/,o7=[/^Wall time: [\d.]+ seconds\s*$/,/^Process exited with code -?\d+\s*$/,/^Process running with session ID \d+\s*$/,/^Original token count: \d+\s*$/];function c7(e){const t=[];for(let r=0;r<e.length;r++){if(l7.test(e[r])){let a=r+1;for(;a<e.length&&o7.some(s=>s.test(e[a]));)a++;a<e.length&&e[a].trim()==="Output:"&&a++,r=a-1;continue}t.push(e[r])}return t}function u7(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function Lm(e){return e.length>i_?e.slice(0,i_-3)+"...":e}function d7(e,t=""){let r=e.replace(/\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*\x07)/g,"").replace(/\r/g,"");for(const a of s7)r=r.replace(a,"");if(r.trim()){const a=c7(r.split(`
|
||||
`)),s=[];for(const o of a)s.length===0&&!o.trim()||/^\[STRIX_\d+\]\$\s*/.test(o)||t&&o.trim()===t.trim()||t&&new RegExp(`^[\\$#>]\\s*${u7(t.trim())}\\s*$`).test(o)||s.push(o);for(;s.length>0&&/^\[STRIX_\d+\]\$\s*/.test(s[s.length-1]);)s.pop();r=s.join(`
|
||||
`)}return r.trim()}function f7(e){const t=e.split(`
|
||||
`);if(t.length<=a7)return t.map(Lm).join(`
|
||||
`);const r=t.length-a_-s_;return[...t.slice(0,a_).map(Lm),`... ${r} lines truncated ...`,...t.slice(-s_).map(Lm)].join(`
|
||||
`)}function h7({toolName:e,args:t,result:r}){const a=e==="write_stdin",s=a?t.chars??t.input??"":t.command??t.cmd??"",o=r;let c=null,d=null,h=null;if(o&&typeof o=="object"){c=typeof o.content=="string"?o.content:null,d=typeof o.error=="string"?o.error:null,h=typeof o.exit_code=="number"?o.exit_code:null;const p=typeof o.status=="string"?o.status:"";(p==="running"||p==="command still running")&&(c=null)}else typeof o=="string"&&(c=o);const f=c?f7(d7(c,s)):null;return m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:a?"Terminal input":"Terminal"}),s&&m.jsx(gg,{code:s,language:"bash",collapsible:!0}),d&&m.jsx(wi,{className:"text-red-400/70",children:d}),f&&m.jsx(wi,{className:"text-[#666]",children:f}),h!=null&&h!==0&&m.jsxs("div",{className:"font-mono text-[13px] text-red-400/70 mt-0.5",children:["exit code ",h]})]})}const l_={back:"going back in browser history",forward:"going forward in browser history",scroll_down:"scrolling down",scroll_up:"scrolling up",refresh:"refreshing",close_tab:"closing tab",switch_tab:"switching tab",list_tabs:"listing tabs",view_source:"viewing page source",get_console_logs:"getting console logs",screenshot:"taking screenshot",wait:"waiting...",close:"closing"},o_={click:"clicking",double_click:"double clicking",hover:"hovering"};function zm({prefix:e,url:t,suffix:r}){return m.jsxs("span",{className:"text-[#888] text-[13px]",children:[e,t&&m.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer",className:"text-cyan-400/80 hover:underline",children:t}),r]})}function m7(e){const t=e.action??"",r=e.url??void 0;if(t in l_)return l_[t];if(t==="launch")return r?m.jsx(zm,{prefix:"launching ",url:r}):"launching";if(t==="goto"||t==="navigate")return m.jsx(zm,{prefix:"navigating to ",url:r});if(t==="new_tab")return m.jsx(zm,{prefix:"opening tab ",url:r});if(t in o_)return o_[t];if(t==="type")return`typing "${(e.text??"").slice(0,40)}"`;if(t==="press_key"||t==="key_press")return`pressing key ${e.key??""}`;if(t==="save_pdf"||t==="save_as_pdf"){const a=e.file_path??"";return`saving PDF${a?` to ${a}`:""}`}return t==="execute_js"?"executing javascript":t||"browser action"}function p7({args:e}){const r=(e.action??"")==="execute_js"?e.js_code??e.code??"":"",a=m7(e);return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[m.jsx("span",{className:"text-blue-400/80 font-semibold text-sm shrink-0",children:"Browser"}),m.jsx("span",{className:"min-w-0 truncate text-[#888] text-[13px]",children:a})]}),r&&m.jsx(gg,{code:r,language:"javascript",collapsible:!0})]})}function xg(e){return e.length>60?"..."+e.slice(-57):e}const mu=30;function g7({toolName:e,args:t}){const r=t.path??t.file_path??"",a=t.command??"",s=t.old_str??"",o=t.new_str??"",c=t.regex??"";let d;e==="list_files"?d="list":e==="search_files"?d="search":a==="view"?d="view":a==="create"?d="create":a==="str_replace"?d="edit":a==="undo_edit"?d="undo":a==="insert"?d="insert":d="file";const h=r?xg(r):"",f=c?` /${c}/`:"",p=s?s.split(`
|
||||
`)),p=f.reduce((g,b)=>g.concat(...b),[]);return[f,p]}return[[],[]]},[e]);return ee.useEffect(()=>{const h=(t==null?void 0:t.target)??L1,f=(t==null?void 0:t.actInsideInputWithModifier)??!0;if(e!==null){const p=y=>{var S,w;if(s.current=y.ctrlKey||y.metaKey||y.shiftKey||y.altKey,(!s.current||s.current&&!f)&&mN(y))return!1;const N=I1(y.code,d);if(o.current.add(y[N]),z1(c,o.current,!1)){const C=((w=(S=y.composedPath)==null?void 0:S.call(y))==null?void 0:w[0])||y.target,E=(C==null?void 0:C.nodeName)==="BUTTON"||(C==null?void 0:C.nodeName)==="A";t.preventDefault!==!1&&(s.current||!E)&&y.preventDefault(),a(!0)}},g=y=>{const _=I1(y.code,d);z1(c,o.current,!0)?(a(!1),o.current.clear()):o.current.delete(y[_]),y.key==="Meta"&&o.current.clear(),s.current=!1},b=()=>{o.current.clear(),a(!1)};return h==null||h.addEventListener("keydown",p),h==null||h.addEventListener("keyup",g),window.addEventListener("blur",b),window.addEventListener("contextmenu",b),()=>{h==null||h.removeEventListener("keydown",p),h==null||h.removeEventListener("keyup",g),window.removeEventListener("blur",b),window.removeEventListener("contextmenu",b)}}},[e,a]),r}function z1(e,t,r){return e.filter(a=>r||a.length===t.size).some(a=>a.every(s=>t.has(s)))}function I1(e,t){return t.includes(e)?"code":"key"}const S8=()=>{const e=Lt();return ee.useMemo(()=>({zoomIn:async t=>{const{panZoom:r}=e.getState();return r?r.scaleBy(1.2,t):!1},zoomOut:async t=>{const{panZoom:r}=e.getState();return r?r.scaleBy(1/1.2,t):!1},zoomTo:async(t,r)=>{const{panZoom:a}=e.getState();return a?a.scaleTo(t,r):!1},getZoom:()=>e.getState().transform[2],setViewport:async(t,r)=>{const{transform:[a,s,o],panZoom:c}=e.getState();return c?(await c.setViewport({x:t.x??a,y:t.y??s,zoom:t.zoom??o},r),!0):!1},getViewport:()=>{const[t,r,a]=e.getState().transform;return{x:t,y:r,zoom:a}},setCenter:async(t,r,a)=>e.getState().setCenter(t,r,a),fitBounds:async(t,r)=>{const{width:a,height:s,minZoom:o,maxZoom:c,panZoom:d}=e.getState(),h=ug(t,a,s,o,c,(r==null?void 0:r.padding)??.1);return d?(await d.setViewport(h,{duration:r==null?void 0:r.duration,ease:r==null?void 0:r.ease,interpolate:r==null?void 0:r.interpolate}),!0):!1},screenToFlowPosition:(t,r={})=>{const{transform:a,snapGrid:s,snapToGrid:o,domNode:c}=e.getState();if(!c)return t;const{x:d,y:h}=c.getBoundingClientRect(),f={x:t.x-d,y:t.y-h},p=r.snapGrid??s,g=r.snapToGrid??o;return $o(f,a,g,p)},flowToScreenPosition:t=>{const{transform:r,domNode:a}=e.getState();if(!a)return t;const{x:s,y:o}=a.getBoundingClientRect(),c=Js(t,r);return{x:c.x+s,y:c.y+o}}}),[])};function zN(e,t){const r=[],a=new Map,s=[];for(const o of e)if(o.type==="add"){s.push(o);continue}else if(o.type==="remove"||o.type==="replace")a.set(o.id,[o]);else{const c=a.get(o.id);c?c.push(o):a.set(o.id,[o])}for(const o of t){const c=a.get(o.id);if(!c){r.push(o);continue}if(c[0].type==="remove")continue;if(c[0].type==="replace"){r.push({...c[0].item});continue}const d={...o};for(const h of c)k8(h,d);r.push(d)}return s.length&&s.forEach(o=>{o.index!==void 0?r.splice(o.index,0,{...o.item}):r.push({...o.item})}),r}function k8(e,t){switch(e.type){case"select":{t.selected=e.selected;break}case"position":{typeof e.position<"u"&&(t.position=e.position),typeof e.dragging<"u"&&(t.dragging=e.dragging);break}case"dimensions":{typeof e.dimensions<"u"&&(t.measured={...e.dimensions},e.setAttributes&&((e.setAttributes===!0||e.setAttributes==="width")&&(t.width=e.dimensions.width),(e.setAttributes===!0||e.setAttributes==="height")&&(t.height=e.dimensions.height))),typeof e.resizing=="boolean"&&(t.resizing=e.resizing);break}}}function IN(e,t){return zN(e,t)}function BN(e,t){return zN(e,t)}function Ha(e,t){return{id:e,type:"select",selected:t}}function qs(e,t=new Set,r=!1){const a=[];for(const[s,o]of e){const c=t.has(s);!(o.selected===void 0&&!c)&&o.selected!==c&&(r&&(o.selected=c),a.push(Ha(o.id,c)))}return a}function B1({items:e=[],lookup:t}){var s;const r=[],a=new Map(e.map(o=>[o.id,o]));for(const[o,c]of e.entries()){const d=t.get(c.id),h=((s=d==null?void 0:d.internals)==null?void 0:s.userNode)??d;h!==void 0&&h!==c&&r.push({id:c.id,item:c,type:"replace"}),h===void 0&&r.push({item:c,type:"add",index:o})}for(const[o]of t)a.get(o)===void 0&&r.push({id:o,type:"remove"});return r}function U1(e){return{id:e.id,type:"remove"}}const C8=uN();function T8(e,t,r={}){return hI(e,t,{...r,onError:r.onError??C8})}const H1=e=>Wz(e),A8=e=>aN(e);function UN(e){return ee.forwardRef(e)}const HN=typeof window<"u"?ee.useLayoutEffect:ee.useEffect;function $1(e){const[t,r]=ee.useState(BigInt(0)),[a]=ee.useState(()=>M8(()=>r(s=>s+BigInt(1))));return HN(()=>{const s=a.get();s.length&&(e(s),a.reset())},[t]),a}function M8(e){let t=[];return{get:()=>t,reset:()=>{t=[]},push:r=>{t.push(r),e()}}}const $N=ee.createContext(null);function O8({children:e}){const t=Lt(),r=ee.useCallback(d=>{const{nodes:h=[],setNodes:f,hasDefaultNodes:p,onNodesChange:g,nodeLookup:b,fitViewQueued:y,onNodesChangeMiddlewareMap:_}=t.getState();let N=h;for(const w of d)N=
Line truncated
|
||||
M${z.x},${z.y}h${z.width}v${z.height}h${-z.width}z`,fillRule:"evenodd",pointerEvents:"none"})]})})}h2.displayName="MiniMap";const SB=ee.memo(h2),kB=e=>t=>e?`${Math.max(1/t.transform[2],1)}`:void 0,CB={[el.Line]:"right",[el.Handle]:"bottom-right"};function TB({nodeId:e,position:t,variant:r=el.Handle,className:a,style:s=void 0,children:o,color:c,minWidth:d=10,minHeight:h=10,maxWidth:f=Number.MAX_VALUE,maxHeight:p=Number.MAX_VALUE,keepAspectRatio:g=!1,resizeDirection:b,autoScale:y=!0,shouldResize:_,onResizeStart:N,onResize:S,onResizeEnd:w}){const C=FN(),E=typeof e=="string"?e:C,A=Lt(),B=ee.useRef(null),R=r===el.Handle,H=dt(ee.useCallback(kB(R&&y),[R,y]),Pt),z=ee.useRef(null),Y=t??CB[r];ee.useEffect(()=>{if(!(!B.current||!E))return z.current||(z.current=XI({domNode:B.current,nodeId:E,getStoreItems:()=>{const{nodeLookup:I,transform:Z,snapGrid:P,snapToGrid:k,nodeOrigin:$,domNode:O}=A.getState();return{nodeLookup:I,transform:Z,snapGrid:P,snapToGrid:k,nodeOrigin:$,paneDomNode:O}},onChange:(I,Z)=>{const{triggerNodeChanges:P,nodeLookup:k,parentLookup:$,nodeOrigin:O}=A.getState(),U=[],K={x:I.x,y:I.y},X=k.get(E);if(X&&X.expandParent&&X.parentId){const M=X.origin??O,L=I.width??X.measured.width??0,F=I.height??X.measured.height??0,D={id:X.id,parentId:X.parentId,rect:{width:L,height:F,...fN({x:I.x??X.position.x,y:I.y??X.position.y},{width:L,height:F},X.parentId,k,M)}},V=gg([D],k,$,O);U.push(...V),K.x=I.x?Math.max(M[0]*L,I.x):void 0,K.y=I.y?Math.max(M[1]*F,I.y):void 0}if(K.x!==void 0&&K.y!==void 0){const M={id:E,type:"position",position:{...K}};U.push(M)}if(I.width!==void 0&&I.height!==void 0){const L={id:E,type:"dimensions",resizing:!0,setAttributes:b?b==="horizontal"?"width":"height":!0,dimensions:{width:I.width,height:I.height}};U.push(L)}for(const M of Z){const L={...M,type:"position"};U.push(L)}P(U)},onEnd:({width:I,height:Z})=>{const P={id:E,type:"dimensions",resizing:!1,dimensions:{width:I,height:Z}};A.getState().triggerNodeChanges([P])}})),z.current.update({controlPosition:Y,boundaries:{minWidth:d,minHeight:h,maxWidth:f,maxHeight:p},keepAspectRatio:g,resizeDirection:b,onResizeStart:N,onResize:S,onResizeEnd:w,shouldResize:_}),()=>{var I;(I=z.current)==null||I.destroy()}},[Y,d,h,f,p,g,N,S,w,_]);const j=Y.split("-");return m.jsx("div",{className:on(["react-flow__resize-control","nodrag",...j,r,a]),ref:B,style:{...s,scale:H,...c&&{[R?"backgroundColor":"borderColor"]:c}},children:o})}ee.memo(TB);var vt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),zr=vt((e,t)=>{var r=Object.defineProperty,a=(P,k,$)=>k in P?r(P,k,{enumerable:!0,configurable:!0,writable:!0,value:$}):P[k]=$,s=(P,k)=>()=>(k||P((k={exports:{}}).exports,k),k.exports),o=(P,k,$)=>a(P,typeof k!="symbol"?k+"":k,$),c=s((P,k)=>{var $="\0",O="\0",U="",K=class{constructor(V){o(this,"_isDirected",!0),o(this,"_isMultigraph",!1),o(this,"_isCompound",!1),o(this,"_label"),o(this,"_defaultNodeLabelFn",()=>{}),o(this,"_defaultEdgeLabelFn",()=>{}),o(this,"_nodes",{}),o(this,"_in",{}),o(this,"_preds",{}),o(this,"_out",{}),o(this,"_sucs",{}),o(this,"_edgeObjs",{}),o(this,"_edgeLabels",{}),o(this,"_nodeCount",0),o(this,"_edgeCount",0),o(this,"_parent"),o(this,"_children"),V&&(this._isDirected=Object.hasOwn(V,"directed")?V.directed:!0,this._isMultigraph=Object.hasOwn(V,"multigraph")?V.multigraph:!1,this._isCompound=Object.hasOwn(V,"compound")?V.compound:!1),this._isCompound&&(this._parent={},this._children={},this._children[O]={})}isDirected(){return this._isDirected}isMultigraph(){return this._isMultigraph}isCompound(){return this._isCompound}setGraph(V){return this._label=V,this}graph(){return this._label}setDefaultNodeLabel(V){return this._defaultNodeLabelFn=V,typeof V!="function"&&(this._defaultNodeLabelFn=()=>V),this}nodeCount(){return this._nodeCount}nodes(){return Object.keys(this._nodes)}sources(){var V=this;return this.nodes().filter(q=>Object.keys(V._in[q]).length===0)}sinks(){var V=this;return this.nodes().filter(q=>Object.keys(V._out[q]).length===0)}setNodes(V,q){var Q=arguments,J=this;return V.forEach(function(W){Q.length>1?J.setNode(W,q):J.setNode(W)}),this}setNode(V,q){return Object.hasOwn(this._nodes,V)?(arguments.length>1&&(this._nodes[V]=q),this):(this._nodes[V]=arguments.length>1?q:this._defaultNodeLabelFn(V),this._isCompound&&(this._parent[V]=O,this._children[V]={},this._children[O][V]=!0),this._in[V]={},this._preds[V]={},this._out[V]={},this._sucs[V]={},++this._nodeCount,this)}node(V){return this._nodes[V]}hasNode(V){return Object.hasOwn(this._nodes,V)}removeNode(V){var q=this;if(Object.hasOwn(this._nodes,V)){var Q=J=>q.removeEdge(q._edgeObjs[J]);delete this._nodes[V],this._isCompound&&(this._removeFromParentsChildList(V),delete this._parent[V],this.children(V).forEach(function(J){q.setParent(J)}),delete this._children[V]),Object.keys(this._in[V]).forEach(Q),delete this._in[V],delete this._preds[V],Object.keys(this._out[V]).forEach(Q),delete this._out[V],delete this._sucs[V],--this._nodeCount}return this}setParent(V,q){if(!this._isCompound
Line truncated
|
||||
`).length>t;return m.jsxs("div",{children:[m.jsx("div",{className:r&&o?"max-h-[1200px] overflow-auto":"",style:!r&&o?{display:"-webkit-box",WebkitLineClamp:t,WebkitBoxOrient:"vertical",overflow:"hidden"}:void 0,children:m.jsx(da,{text:e})}),o&&m.jsx("button",{onClick:()=>a(!r),className:"text-xs text-[#555] hover:text-[#888] mt-1",children:r?"Show less":"Show more"})]})}function Ei({children:e,className:t=""}){const[r,a]=ee.useState(!1),o=typeof e=="string"?e.trimEnd().split(`
|
||||
`):null,c=o!==null&&o.length>i_,d=c&&!r?o.slice(0,i_).join(`
|
||||
`):e;return m.jsxs("div",{children:[m.jsx("pre",{className:`font-mono text-[13px] leading-relaxed whitespace-pre-wrap break-words mt-1 ${r?"overflow-auto max-h-[1200px]":"overflow-hidden"} ${t}`,children:d}),c&&m.jsx("button",{onClick:()=>a(!r),className:"text-xs text-[#555] hover:text-[#888] mt-0.5",children:r?"Show less":"Show more"})]})}function bg({code:e,language:t,className:r="",collapsible:a=!1}){const[s,o]=ee.useState(!1),c=e.trimEnd().split(`
|
||||
`),d=a&&c.length>a_,h=d&&!s?c.slice(0,a_).join(`
|
||||
`):e;let f;try{f=t?zn.highlight(h,{language:t,ignoreIllegals:!0}).value:zn.highlightAuto(h).value}catch{f=zn.highlightAuto(h).value}return m.jsxs("div",{children:[m.jsx("pre",{className:`font-mono text-[12px] leading-relaxed px-0 py-1 mt-1 whitespace-pre-wrap break-all ${a?s?"overflow-auto max-h-[1200px]":"overflow-hidden":"overflow-auto max-h-[400px]"} ${r}`,children:m.jsx("code",{dangerouslySetInnerHTML:{__html:f}})}),d&&m.jsx("button",{onClick:()=>o(!s),className:"text-xs text-[#555] hover:text-[#888] mt-0.5",children:s?"Show less":"Show more"})]})}const o7=50,s_=200,l_=25,o_=24,c7=[/\n?\[Command still running after [\d.]+s - showing output so far\.?\s*(?:Use C-c to interrupt if needed\.)?\]/g,/^\[Below is the output of the previous command\.\]\n?/gm,/^No command is currently running\. Cannot send input\.$/gm,/^A command is already running\. Use is_input=true to send input to it, or interrupt it first \(e\.g\., with C-c\)\.$/gm],u7=/^Chunk ID: [0-9a-f]+\s*$/,d7=[/^Wall time: [\d.]+ seconds\s*$/,/^Process exited with code -?\d+\s*$/,/^Process running with session ID \d+\s*$/,/^Original token count: \d+\s*$/];function f7(e){const t=[];for(let r=0;r<e.length;r++){if(u7.test(e[r])){let a=r+1;for(;a<e.length&&d7.some(s=>s.test(e[a]));)a++;a<e.length&&e[a].trim()==="Output:"&&a++,r=a-1;continue}t.push(e[r])}return t}function h7(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function zm(e){return e.length>s_?e.slice(0,s_-3)+"...":e}function m7(e,t=""){let r=e.replace(/\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|\][^\x07]*\x07)/g,"").replace(/\r/g,"");for(const a of c7)r=r.replace(a,"");if(r.trim()){const a=f7(r.split(`
|
||||
`)),s=[];for(const o of a)s.length===0&&!o.trim()||/^\[STRIX_\d+\]\$\s*/.test(o)||t&&o.trim()===t.trim()||t&&new RegExp(`^[\\$#>]\\s*${h7(t.trim())}\\s*$`).test(o)||s.push(o);for(;s.length>0&&/^\[STRIX_\d+\]\$\s*/.test(s[s.length-1]);)s.pop();r=s.join(`
|
||||
`)}return r.trim()}function p7(e){const t=e.split(`
|
||||
`);if(t.length<=o7)return t.map(zm).join(`
|
||||
`);const r=t.length-l_-o_;return[...t.slice(0,l_).map(zm),`... ${r} lines truncated ...`,...t.slice(-o_).map(zm)].join(`
|
||||
`)}function g7({toolName:e,args:t,result:r}){const a=e==="write_stdin",s=a?t.chars??t.input??"":t.command??t.cmd??"",o=r;let c=null,d=null,h=null;if(o&&typeof o=="object"){c=typeof o.content=="string"?o.content:null,d=typeof o.error=="string"?o.error:null,h=typeof o.exit_code=="number"?o.exit_code:null;const p=typeof o.status=="string"?o.status:"";(p==="running"||p==="command still running")&&(c=null)}else typeof o=="string"&&(c=o);const f=c?p7(m7(c,s)):null;return m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:a?"Terminal input":"Terminal"}),s&&m.jsx(bg,{code:s,language:"bash",collapsible:!0}),d&&m.jsx(Ei,{className:"text-red-400/70",children:d}),f&&m.jsx(Ei,{className:"text-[#666]",children:f}),h!=null&&h!==0&&m.jsxs("div",{className:"font-mono text-[13px] text-red-400/70 mt-0.5",children:["exit code ",h]})]})}const c_={back:"going back in browser history",forward:"going forward in browser history",scroll_down:"scrolling down",scroll_up:"scrolling up",refresh:"refreshing",close_tab:"closing tab",switch_tab:"switching tab",list_tabs:"listing tabs",view_source:"viewing page source",get_console_logs:"getting console logs",screenshot:"taking screenshot",wait:"waiting...",close:"closing"},u_={click:"clicking",double_click:"double clicking",hover:"hovering"};function Im({prefix:e,url:t,suffix:r}){return m.jsxs("span",{className:"text-[#888] text-[13px]",children:[e,t&&m.jsx("a",{href:t,target:"_blank",rel:"noopener noreferrer",className:"text-cyan-400/80 hover:underline",children:t}),r]})}function x7(e){const t=e.action??"",r=e.url??void 0;if(t in c_)return c_[t];if(t==="launch")return r?m.jsx(Im,{prefix:"launching ",url:r}):"launching";if(t==="goto"||t==="navigate")return m.jsx(Im,{prefix:"navigating to ",url:r});if(t==="new_tab")return m.jsx(Im,{prefix:"opening tab ",url:r});if(t in u_)return u_[t];if(t==="type")return`typing "${(e.text??"").slice(0,40)}"`;if(t==="press_key"||t==="key_press")return`pressing key ${e.key??""}`;if(t==="save_pdf"||t==="save_as_pdf"){const a=e.file_path??"";return`saving PDF${a?` to ${a}`:""}`}return t==="execute_js"?"executing javascript":t||"browser action"}function b7({args:e}){const r=(e.action??"")==="execute_js"?e.js_code??e.code??"":"",a=x7(e);return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2 min-w-0",children:[m.jsx("span",{className:"text-blue-400/80 font-semibold text-sm shrink-0",children:"Browser"}),m.jsx("span",{className:"min-w-0 truncate text-[#888] text-[13px]",children:a})]}),r&&m.jsx(bg,{code:r,language:"javascript",collapsible:!0})]})}function yg(e){return e.length>60?"..."+e.slice(-57):e}const pu=30;function y7({toolName:e,args:t}){const r=t.path??t.file_path??"",a=t.command??"",s=t.old_str??"",o=t.new_str??"",c=t.regex??"";let d;e==="list_files"?d="list":e==="search_files"?d="search":a==="view"?d="view":a==="create"?d="create":a==="str_replace"?d="edit":a==="undo_edit"?d="undo":a==="insert"?d="insert":d="file";const h=r?yg(r):"",f=c?` /${c}/`:"",p=s?s.split(`
|
||||
`):[],g=o?o.split(`
|
||||
`):[],b=p.length+g.length,y=b>mu,_=y?Math.round(mu*(p.length/b)):p.length,N=y?mu-_:g.length;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:d}),h&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:h})]}),f&&m.jsx("div",{className:"text-purple-400/60 font-mono text-[13px] break-all mt-0.5",children:f}),(s||o)&&m.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[p.slice(0,_).map((S,w)=>m.jsxs("div",{className:"text-red-400/60",children:[m.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),S]},`o${w}`)),g.slice(0,N).map((S,w)=>m.jsxs("div",{className:"text-emerald-400/60",children:[m.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),S]},`n${w}`)),y&&m.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",b-mu," more lines"]})]})]})}const pu=30,x7="*** Begin Patch",b7="*** End Patch",c_="*** Add File: ",u_="*** Update File: ",d_="*** Delete File: ",y7={add:"create",update:"edit",delete:"delete"};function v7(e){const t=e.patch;return typeof t=="string"?t:t&&typeof t=="object"&&typeof t.patch=="string"?t.patch:typeof e.input=="string"?e.input:""}function _7(e){const t=[];let r=null;const a=()=>{r&&t.push(r),r=null};for(const s of e.split(`
|
||||
`))if(!(s===x7||s===b7))if(s.startsWith(c_))a(),r={kind:"add",path:s.slice(c_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(u_))a(),r={kind:"update",path:s.slice(u_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(d_))a(),r={kind:"delete",path:s.slice(d_.length).trim(),oldLines:[],newLines:[]};else if((r==null?void 0:r.kind)==="update"){if(s.startsWith("@@"))continue;s.startsWith("-")&&!s.startsWith("---")?r.oldLines.push(s.slice(1)):s.startsWith("+")&&!s.startsWith("+++")&&r.newLines.push(s.slice(1))}else(r==null?void 0:r.kind)==="add"&&(s.startsWith("+")?r.newLines.push(s.slice(1)):s.trim()&&r.newLines.push(s));return a(),t}function w7({op:e}){const t=y7[e.kind]??"file",r=e.oldLines.length+e.newLines.length,a=r>pu,s=a&&r>0?Math.round(pu*(e.oldLines.length/r)):e.oldLines.length,o=a?pu-s:e.newLines.length;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:t}),e.path&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:xg(e.path)})]}),(e.oldLines.length>0||e.newLines.length>0)&&m.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[e.oldLines.slice(0,s).map((c,d)=>m.jsxs("div",{className:"text-red-400/60",children:[m.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),c]},`o${d}`)),e.newLines.slice(0,o).map((c,d)=>m.jsxs("div",{className:"text-emerald-400/60",children:[m.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),c]},`n${d}`)),a&&m.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",r-pu," more lines"]})]})]})}function E7({args:e,result:t,status:r}){const a=_7(v7(e));return a.length===0?m.jsxs("div",{children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm",children:"patch"}),r==="failed"&&typeof t=="string"&&t.trim()&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:t.trim()})]}):m.jsxs("div",{className:"space-y-2",children:[a.map((s,o)=>m.jsx(w7,{op:s},o)),r==="failed"&&typeof t=="string"&&t.trim()&&m.jsx("div",{className:"text-red-400/70 text-[13px]",children:t.trim()})]})}const N7=/data:image\/(png|jpe?g|gif|webp);base64,([A-Za-z0-9+/]+={0,2})/;function S7(e){let t=null;if(typeof e=="string")t=e;else if(e&&typeof e=="object"){const a=e;typeof a.image_url=="string"?t=a.image_url:typeof a.url=="string"&&(t=a.url)}if(!t)return null;const r=N7.exec(t);return!r||r[2].length<100||r[2].length%4!==0?null:`data:image/${r[1]};base64,${r[2]}`}function k7({args:e,result:t}){const r=(e.path??"").trim(),a=S7(t);let s=null;if(!a&&typeof t=="string"){const o=t.trim();o&&!o.toLowerCase().startsWith("data:image/")&&!o.startsWith("{")&&(s=o)}return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:"view image"}),r&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:xg(r)})]}),a&&m.jsx("img",{src:a,alt:r||"Tool image output",className:"mt-1.5 max-w-full max-h-96 rounded-lg border border-white/[0.06] object-contain"}),s&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:s})]})}const C7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400"},T7={high:"text-emerald-400",medium:"text-yellow-400",low:"text-orange-400"};function A7({args:e,result:t}){const r=e.title??"",a=e.description??"",s=e.impact??"",o=e.target??"",c=e.endpoint??"",d=e.method??"",h=e.technical_analysis??"",f=e.poc_description??"",{language:p,code:g}=yE(e.poc_script_code??""),b=e.remediation_steps??"",y=e.cve??"",_=e.cwe??"",N=e.counterevidence??"",S=(e.confidence??"").toLowerCase(),w=e.confidence_rationale??"",C=e.severity_change_conditions??"",E=e.fix_verification??"",A=t,B=(A&&typeof A=="object"?A.severity:null)??e.severity??"medium",R=String(B).toLowerCase(),H=(A&&typeof A=="object"?A.cvss_score:null)??e.cvss??null,z=C7[R]??"text-yellow-400";return m.jsxs("div",{className:"space-y-3",children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[m.jsx("span",{className:`font-semibold text-sm ${z}`,children:R.toUpperCase()}),H!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["CVSS ",H]}),y&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:y}),_&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:_}),S&&m.jsxs("span",{className:`text-[13px] ${T7[S]??"text-[#888]"}`,children:[S," confidence"]})]}),r&&m.jsx("div",{className:"text-[15px] text-white/80 font-semibold",children:r}),(o||c)&&m.jsxs("div",{className:"text-[13px] text-[#888] font-mono",children:[o,c?` ${d} ${c}`:""]}),a&&m.jsx(Yt,{text:a,maxLines:20}),s&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Impact"}),m.jsx("div",{className:"mt-1",children:m.jsx(Yt,{text
Line truncated
|
||||
`),a=r.slice(0,t).map(s=>Xr(s,d2-5)).join(`
|
||||
`):[],b=p.length+g.length,y=b>pu,_=y?Math.round(pu*(p.length/b)):p.length,N=y?pu-_:g.length;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:d}),h&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:h})]}),f&&m.jsx("div",{className:"text-purple-400/60 font-mono text-[13px] break-all mt-0.5",children:f}),(s||o)&&m.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[p.slice(0,_).map((S,w)=>m.jsxs("div",{className:"text-red-400/60",children:[m.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),S]},`o${w}`)),g.slice(0,N).map((S,w)=>m.jsxs("div",{className:"text-emerald-400/60",children:[m.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),S]},`n${w}`)),y&&m.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",b-pu," more lines"]})]})]})}const gu=30,v7="*** Begin Patch",_7="*** End Patch",d_="*** Add File: ",f_="*** Update File: ",h_="*** Delete File: ",w7={add:"create",update:"edit",delete:"delete"};function E7(e){const t=e.patch;return typeof t=="string"?t:t&&typeof t=="object"&&typeof t.patch=="string"?t.patch:typeof e.input=="string"?e.input:""}function N7(e){const t=[];let r=null;const a=()=>{r&&t.push(r),r=null};for(const s of e.split(`
|
||||
`))if(!(s===v7||s===_7))if(s.startsWith(d_))a(),r={kind:"add",path:s.slice(d_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(f_))a(),r={kind:"update",path:s.slice(f_.length).trim(),oldLines:[],newLines:[]};else if(s.startsWith(h_))a(),r={kind:"delete",path:s.slice(h_.length).trim(),oldLines:[],newLines:[]};else if((r==null?void 0:r.kind)==="update"){if(s.startsWith("@@"))continue;s.startsWith("-")&&!s.startsWith("---")?r.oldLines.push(s.slice(1)):s.startsWith("+")&&!s.startsWith("+++")&&r.newLines.push(s.slice(1))}else(r==null?void 0:r.kind)==="add"&&(s.startsWith("+")?r.newLines.push(s.slice(1)):s.trim()&&r.newLines.push(s));return a(),t}function S7({op:e}){const t=w7[e.kind]??"file",r=e.oldLines.length+e.newLines.length,a=r>gu,s=a&&r>0?Math.round(gu*(e.oldLines.length/r)):e.oldLines.length,o=a?gu-s:e.newLines.length;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:t}),e.path&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:yg(e.path)})]}),(e.oldLines.length>0||e.newLines.length>0)&&m.jsxs("div",{className:"font-mono text-[13px] leading-relaxed mt-1.5",children:[e.oldLines.slice(0,s).map((c,d)=>m.jsxs("div",{className:"text-red-400/60",children:[m.jsx("span",{className:"select-none text-red-400/30 mr-1",children:"-"}),c]},`o${d}`)),e.newLines.slice(0,o).map((c,d)=>m.jsxs("div",{className:"text-emerald-400/60",children:[m.jsx("span",{className:"select-none text-emerald-400/30 mr-1",children:"+"}),c]},`n${d}`)),a&&m.jsxs("div",{className:"text-[#444] mt-0.5",children:["... ",r-gu," more lines"]})]})]})}function k7({args:e,result:t,status:r}){const a=N7(E7(e));return a.length===0?m.jsxs("div",{children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm",children:"patch"}),r==="failed"&&typeof t=="string"&&t.trim()&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:t.trim()})]}):m.jsxs("div",{className:"space-y-2",children:[a.map((s,o)=>m.jsx(S7,{op:s},o)),r==="failed"&&typeof t=="string"&&t.trim()&&m.jsx("div",{className:"text-red-400/70 text-[13px]",children:t.trim()})]})}const C7=/data:image\/(png|jpe?g|gif|webp);base64,([A-Za-z0-9+/]+={0,2})/;function T7(e){let t=null;if(typeof e=="string")t=e;else if(e&&typeof e=="object"){const a=e;typeof a.image_url=="string"?t=a.image_url:typeof a.url=="string"&&(t=a.url)}if(!t)return null;const r=C7.exec(t);return!r||r[2].length<100||r[2].length%4!==0?null:`data:image/${r[1]};base64,${r[2]}`}function A7({args:e,result:t}){const r=(e.path??"").trim(),a=T7(t);let s=null;if(!a&&typeof t=="string"){const o=t.trim();o&&!o.toLowerCase().startsWith("data:image/")&&!o.startsWith("{")&&(s=o)}return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-baseline gap-2",children:[m.jsx("span",{className:"text-sky-400/80 font-semibold text-sm shrink-0",children:"view image"}),r&&m.jsx("span",{className:"text-[#888] font-mono text-[13px] break-all",children:yg(r)})]}),a&&m.jsx("img",{src:a,alt:r||"Tool image output",className:"mt-1.5 max-w-full max-h-96 rounded-lg border border-white/[0.06] object-contain"}),s&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1",children:s})]})}const M7={critical:"text-red-400",high:"text-orange-400",medium:"text-yellow-400",low:"text-blue-400",info:"text-cyan-400"},O7={high:"text-emerald-400",medium:"text-yellow-400",low:"text-orange-400"};function R7({args:e,result:t}){const r=e.title??"",a=e.description??"",s=e.impact??"",o=e.target??"",c=e.endpoint??"",d=e.method??"",h=e.technical_analysis??"",f=e.poc_description??"",{language:p,code:g}=EE(e.poc_script_code??""),b=e.remediation_steps??"",y=e.cve??"",_=e.cwe??"",N=e.counterevidence??"",S=(e.confidence??"").toLowerCase(),w=e.confidence_rationale??"",C=e.severity_change_conditions??"",E=e.fix_verification??"",A=t,B=(A&&typeof A=="object"?A.severity:null)??e.severity??"medium",R=String(B).toLowerCase(),H=(A&&typeof A=="object"?A.cvss_score:null)??e.cvss??null,z=M7[R]??"text-yellow-400";return m.jsxs("div",{className:"space-y-3",children:[m.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[m.jsx("span",{className:`font-semibold text-sm ${z}`,children:R.toUpperCase()}),H!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["CVSS ",H]}),y&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:y}),_&&m.jsx("span",{className:"text-[#888] font-mono text-[13px]",children:_}),S&&m.jsxs("span",{className:`text-[13px] ${O7[S]??"text-[#888]"}`,children:[S," confidence"]})]}),r&&m.jsx("div",{className:"text-[15px] text-white/80 font-semibold",children:r}),(o||c)&&m.jsxs("div",{className:"text-[13px] text-[#888] font-mono",children:[o,c?` ${d} ${c}`:""]}),a&&m.jsx(qt,{text:a,maxLines:20}),s&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Impact"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text
Line truncated
|
||||
`),a=r.slice(0,t).map(s=>Xr(s,p2-5)).join(`
|
||||
`);return r.length>t?a+`
|
||||
...`:a}function R7({args:e,result:t}){const r=e.httpql_filter??"",a=t,s=a?a.requests:null,o=Array.isArray(s)?s:[];return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing requests"}),r&&m.jsx("span",{className:"text-[#888] text-[13px]",children:Xr(r,150)})]}),o.length>0&&m.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[o.slice(0,20).map((c,d)=>{const h=(c.method??"GET").toUpperCase(),f=c.host??"",p=c.path??"",g=c.response,b=(g==null?void 0:g.statusCode)??null;return m.jsxs("div",{className:"flex gap-2",children:[m.jsx("span",{className:`w-10 shrink-0 font-bold ${f2[h]??"text-[#888]"}`,children:h}),m.jsx("span",{className:"text-[#777] truncate",children:Xr(f+p,180)}),b!=null&&m.jsx("span",{className:`ml-auto shrink-0 ${bg(b)}`,children:b})]},d)}),o.length>20&&m.jsxs("div",{className:"text-[#555]",children:["... +",o.length-20," more"]})]})]})}function j7({args:e,result:t}){const r=e.request_id,a=e.part??"request",s=e.search_pattern??"",o=t,c=o?o.matches:null,d=Array.isArray(c)?c:[],h=o?o.content??null:null,f=o?!!o.has_more:!1;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[s?"searching":"viewing"," ",a]}),r!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",r]}),s&&m.jsxs("span",{className:"text-[#666] font-mono text-[13px]",children:["/",Xr(s,100),"/"]})]}),d.length>0&&m.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-1",children:[d.slice(0,5).map((p,g)=>{const b=(p.before??"").replace(/\n/g," ").replace(/\r/g,"").slice(-100),y=(p.after??"").replace(/\n/g," ").replace(/\r/g,"").slice(0,100);return m.jsxs("div",{children:[b&&m.jsxs("span",{className:"text-[#555]",children:["...",b]}),m.jsx("span",{className:"text-amber-400/80 font-bold",children:p.match}),y&&m.jsxs("span",{className:"text-[#555]",children:[y,"..."]})]},g)}),d.length>5&&m.jsxs("div",{className:"text-[#555]",children:["... +",d.length-5," more matches"]})]}),h&&!d.length&&(()=>{const p=h.split(`
|
||||
`),g=p.slice(0,15).map(y=>Xr(y,d2)).join(`
|
||||
`),b=f||p.length>15;return m.jsx(wi,{className:"text-[#666]",children:g+(b?`
|
||||
... more content available`:"")})})()]})}function D7({args:e,result:t}){const r=(e.method??"GET").toUpperCase(),a=e.url??"",s=e.headers,o=e.body,c=typeof o=="string"?o:"",d=t,h=d?d.error??null:null,f=d?d.status_code??null:null,p=d?d.response_time_ms??null:null,g=d?d.body:null,b=typeof g=="string"?g:null;return m.jsxs("div",{children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"request"}),m.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[m.jsxs("div",{children:[m.jsx("span",{className:"text-[#555] select-none mr-1",children:">>"}),m.jsx("span",{className:`font-bold ${f2[r]??"text-[#888]"}`,children:r}),m.jsx("span",{className:"text-[#888] ml-1 break-all",children:Xr(a,180)})]}),s&&typeof s=="object"&&Object.entries(s).slice(0,5).map(([y,_])=>m.jsxs("div",{className:"text-[#555] pl-5",children:[y,": ",vp(String(_),150)]},y))]}),c&&m.jsx(wi,{className:"text-[#888]",children:_p(c,4)}),h&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1.5",children:vp(h,150)}),f!=null&&m.jsxs("div",{className:"font-mono text-[13px] mt-1.5",children:[m.jsx("span",{className:"text-[#555] select-none mr-1",children:"<<"}),m.jsx("span",{className:`font-bold ${bg(f)}`,children:f}),p!=null&&m.jsxs("span",{className:"text-[#555] ml-2",children:[p,"ms"]})]}),b&&m.jsx(wi,{className:"text-[#666]",children:_p(b,6)})]})}function L7({args:e,result:t}){const r=e.request_id,a=e.modifications,s=t,o=s?s.status_code??null:null,c=s?s.response_time_ms??null:null,d=s?s.body:null,h=typeof d=="string"?d:null;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"repeating request"}),r!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",r]})]}),a&&typeof a=="object"&&Object.keys(a).length>0&&m.jsx("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:Object.entries(a).slice(0,5).map(([f,p])=>m.jsxs("div",{children:[m.jsxs("span",{className:"text-orange-400/60",children:[f,":"]})," ",m.jsx("span",{className:"text-[#777]",children:vp(typeof p=="string"?p:JSON.stringify(p),150)})]},f))}),o!=null&&m.jsxs("div",{className:"font-mono text-[13px] mt-1.5",children:[m.jsx("span",{className:"text-[#555] select-none mr-1",children:"<<"}),m.jsx("span",{className:`font-bold ${bg(o)}`,children:o}),c!=null&&m.jsxs("span",{className:"text-[#555] ml-2",children:[c,"ms"]})]}),h&&m.jsx(wi,{className:"text-[#666]",children:_p(h,5)})]})}const z7={get:"getting",list:"listing",create:"creating",update:"updating",delete:"deleting"};function I7({args:e}){const t=e.action??"",r=e.scope_name??"",a=z7[t]??(t||"managing");return m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[a," proxy scope"]}),r&&m.jsx("span",{className:"text-[#888] text-[13px]",children:Xr(r,50)})]})}function B7({args:e}){const t=e.parent_id;return m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing sitemap"}),t&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["under #",Xr(String(t),20)]})]})}function U7({args:e}){const t=e.entry_id;return m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"viewing sitemap entry"}),t&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",Xr(String(t),20)]})]})}function H7(e){switch(e.toolName){case"list_requests":return m.jsx(R7,{...e});case"view_request":return m.jsx(j7,{...e});case"send_request":return m.jsx(D7,{...e});case"repeat_request":return m.jsx(L7,{...e});case"scope_rules":return m.jsx(I7,{...e});case"list_sitemap":return m.jsx(B7,{...e});case"view_sitemap_entry":return m.jsx(U7,{...e});default:return m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:e.toolName.replace(/_/g," ")})}}function $7({args:e}){const t=e.thought??e.content??"";return t?m.jsxs("div",{children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"Agent is thinking"}),m.jsx("div",{className:"mt-1.5 italic text-[#888]",children:m.jsx(Yt,{text:t,maxLines:20})})]}):null}function q7({toolName:e,args:t}){if(e==="create_agent"){const r=t.name??t.agent_name??"",a=t.task??"";return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-cyan-400/80 font-semibold text-sm",children:"spawning"}),r&&m.jsx("span",{className:"text-cyan-400 font-semibold text-sm",children:r})]}),a&&m.jsx("div",{className:"mt-1.5",children:m.jsx(Yt,{text:a,maxLines:15})})]})}if(e==="agent_finish"){const r=t.result_summary??"",a=t.success,s=t.findings,o=Array.isArray(s)?s:void 0;return m.jsxs("div",{children:[m.jsx("span",{className:`font-semibold text-sm ${a===!1?"text-red-400/80":"text-emerald-400/80"}`,children:a===!1?"A
Line truncated
|
||||
`);if(t.length<=F7)return t.map(Bm).join(`
|
||||
`);const r=t.length-m_-p_;return[...t.slice(0,m_).map(Bm),`... ${r} lines truncated ...`,...t.slice(-p_).map(Bm)].join(`
|
||||
`)}function Z7({args:e,result:t}){const r=e.action??"",a=e.code??e.script??"",s=t;let o=null;s&&typeof s=="object"?o=typeof s.stdout=="string"?s.stdout:null:typeof s=="string"&&(o=s);const c=r==="new_session"?"new session":r==="close"?"close session":r==="list_sessions"?"list sessions":null,d=o?K7(X7(o)):null;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-yellow-400/80 font-semibold text-sm",children:"Python"}),c&&m.jsx("span",{className:"text-[#888] text-[13px]",children:c})]}),a&&m.jsx(gg,{code:a,language:"python",collapsible:!0}),d&&m.jsx(wi,{className:"text-[#666]",children:d})]})}function Q7({args:e}){const t=e.targets,a=(Array.isArray(t)?t:[]).map(s=>typeof s=="object"&&s?s.original??null:null).filter(Boolean);return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:"Starting penetration test"}),a.length===1&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["on ",a[0]]})]}),a.length>1&&m.jsx("div",{className:"mt-1.5 space-y-0.5",children:a.map((s,o)=>m.jsxs("div",{className:"text-[13px] text-[#888]",children:[m.jsx("span",{className:"text-[#555] mr-1",children:"•"}),s]},o))})]})}function W7({args:e}){const t=e.name??"Unknown Agent",r=e.task??"";return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-[#888] text-[13px]",children:"subagent"}),m.jsx("span",{className:"text-purple-400 font-semibold text-sm",children:t})]}),r&&m.jsx("div",{className:"mt-1.5",children:m.jsx(Yt,{text:r,maxLines:15})})]})}function J7(e){return e.toolName==="subagent_start_info"?m.jsx(W7,{...e}):m.jsx(Q7,{...e})}function eU({args:e}){const t=e.executive_summary??"",r=e.methodology??"",a=e.technical_analysis??"",s=e.recommendations??"";return m.jsxs("div",{className:"space-y-3",children:[m.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:"Penetration test completed"}),t&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Executive Summary"}),m.jsx("div",{className:"mt-1",children:m.jsx(Yt,{text:t,maxLines:25})})]}),r&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Methodology"}),m.jsx("div",{className:"mt-1",children:m.jsx(Yt,{text:r,maxLines:25})})]}),a&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Technical Analysis"}),m.jsx("div",{className:"mt-1",children:m.jsx(Yt,{text:a,maxLines:25})})]}),s&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Recommendations"}),m.jsx("div",{className:"mt-1",children:m.jsx(Yt,{text:s,maxLines:25})})]}),!t&&!r&&!a&&!s&&m.jsx("div",{className:"text-[#555] text-xs",children:"Generating final report..."})]})}function tU({toolName:e,args:t,result:r}){if(e==="create_note"){const a=t.title??"",s=t.content??"",o=t.category??"general";return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note"}),m.jsxs("span",{className:"text-[#555] text-[13px]",children:["(",o,")"]})]}),a&&m.jsx("div",{className:"mt-1.5 text-[#999] text-[13px]",children:a}),s&&m.jsx("div",{className:"mt-1",children:m.jsx(ua,{text:s})})]})}if(e==="delete_note")return m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note removed"});if(e==="update_note"){const a=t.title??"",s=t.content??"";return m.jsxs("div",{children:[m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note updated"}),a&&m.jsx("div",{className:"mt-1.5 text-[#999] text-[13px]",children:a}),s&&m.jsx("div",{className:"mt-1",children:m.jsx(ua,{text:s})})]})}if(e==="get_note"){const a=r,s=a&&typeof a=="object"&&a.success?a.note:void 0;return m.jsxs("div",{children:[m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note read"}),s&&m.jsxs(m.Fragment,{children:[m.jsxs("div",{className:"mt-1.5 text-[#999] text-[13px]",children:[s.title??"(untitled)",m.jsxs("span",{className:"text-[#555] ml-1",children:["(",s.category??"general",")"]}),(s.by_you||s.agent_name)&&m.jsxs("span",{className:"text-[#666] ml-1 text-xs",children:["by ",s.by_you?"you":s.agent_name]})]}),s.content&&m.jsx("div",{className:"mt-1",children:m.jsx(ua,{text:s.content})})]})]})}if(e==="list_notes"){const a=r;let s=[];if(a&&typeof a=="object"&&a.success){const o=a.notes;s=Array.isArray(o)?o:[]}return m.jsxs("div",{children:[m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"notes"}),s.length>0?m.jsx("div",{className:"mt-1.5 space-y-0.5",children:s.map((o,c)=>m.jsxs("div",{className:"text-[13px]",children:[m.jsx("span",{className:"text-[#555] mr-1",children:"-"}),m.jsx("span",{className:"text-[#999]",chil
Line truncated
|
||||
...`:a}function I7({args:e,result:t}){const r=e.httpql_filter??"",a=t,s=a?a.requests:null,o=Array.isArray(s)?s:[];return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing requests"}),r&&m.jsx("span",{className:"text-[#888] text-[13px]",children:Xr(r,150)})]}),o.length>0&&m.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[o.slice(0,20).map((c,d)=>{const h=(c.method??"GET").toUpperCase(),f=c.host??"",p=c.path??"",g=c.response,b=(g==null?void 0:g.statusCode)??null;return m.jsxs("div",{className:"flex gap-2",children:[m.jsx("span",{className:`w-10 shrink-0 font-bold ${g2[h]??"text-[#888]"}`,children:h}),m.jsx("span",{className:"text-[#777] truncate",children:Xr(f+p,180)}),b!=null&&m.jsx("span",{className:`ml-auto shrink-0 ${vg(b)}`,children:b})]},d)}),o.length>20&&m.jsxs("div",{className:"text-[#555]",children:["... +",o.length-20," more"]})]})]})}function B7({args:e,result:t}){const r=e.request_id,a=e.part??"request",s=e.search_pattern??"",o=t,c=o?o.matches:null,d=Array.isArray(c)?c:[],h=o?o.content??null:null,f=o?!!o.has_more:!1;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[s?"searching":"viewing"," ",a]}),r!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",r]}),s&&m.jsxs("span",{className:"text-[#666] font-mono text-[13px]",children:["/",Xr(s,100),"/"]})]}),d.length>0&&m.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-1",children:[d.slice(0,5).map((p,g)=>{const b=(p.before??"").replace(/\n/g," ").replace(/\r/g,"").slice(-100),y=(p.after??"").replace(/\n/g," ").replace(/\r/g,"").slice(0,100);return m.jsxs("div",{children:[b&&m.jsxs("span",{className:"text-[#555]",children:["...",b]}),m.jsx("span",{className:"text-amber-400/80 font-bold",children:p.match}),y&&m.jsxs("span",{className:"text-[#555]",children:[y,"..."]})]},g)}),d.length>5&&m.jsxs("div",{className:"text-[#555]",children:["... +",d.length-5," more matches"]})]}),h&&!d.length&&(()=>{const p=h.split(`
|
||||
`),g=p.slice(0,15).map(y=>Xr(y,p2)).join(`
|
||||
`),b=f||p.length>15;return m.jsx(Ei,{className:"text-[#666]",children:g+(b?`
|
||||
... more content available`:"")})})()]})}function U7({args:e,result:t}){const r=(e.method??"GET").toUpperCase(),a=e.url??"",s=e.headers,o=e.body,c=typeof o=="string"?o:"",d=t,h=d?d.error??null:null,f=d?d.status_code??null:null,p=d?d.response_time_ms??null:null,g=d?d.body:null,b=typeof g=="string"?g:null;return m.jsxs("div",{children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"request"}),m.jsxs("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:[m.jsxs("div",{children:[m.jsx("span",{className:"text-[#555] select-none mr-1",children:">>"}),m.jsx("span",{className:`font-bold ${g2[r]??"text-[#888]"}`,children:r}),m.jsx("span",{className:"text-[#888] ml-1 break-all",children:Xr(a,180)})]}),s&&typeof s=="object"&&Object.entries(s).slice(0,5).map(([y,_])=>m.jsxs("div",{className:"text-[#555] pl-5",children:[y,": ",_p(String(_),150)]},y))]}),c&&m.jsx(Ei,{className:"text-[#888]",children:wp(c,4)}),h&&m.jsx("div",{className:"text-red-400/70 text-[13px] mt-1.5",children:_p(h,150)}),f!=null&&m.jsxs("div",{className:"font-mono text-[13px] mt-1.5",children:[m.jsx("span",{className:"text-[#555] select-none mr-1",children:"<<"}),m.jsx("span",{className:`font-bold ${vg(f)}`,children:f}),p!=null&&m.jsxs("span",{className:"text-[#555] ml-2",children:[p,"ms"]})]}),b&&m.jsx(Ei,{className:"text-[#666]",children:wp(b,6)})]})}function H7({args:e,result:t}){const r=e.request_id,a=e.modifications,s=t,o=s?s.status_code??null:null,c=s?s.response_time_ms??null:null,d=s?s.body:null,h=typeof d=="string"?d:null;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"repeating request"}),r!=null&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",r]})]}),a&&typeof a=="object"&&Object.keys(a).length>0&&m.jsx("div",{className:"mt-1.5 font-mono text-[13px] space-y-0.5",children:Object.entries(a).slice(0,5).map(([f,p])=>m.jsxs("div",{children:[m.jsxs("span",{className:"text-orange-400/60",children:[f,":"]})," ",m.jsx("span",{className:"text-[#777]",children:_p(typeof p=="string"?p:JSON.stringify(p),150)})]},f))}),o!=null&&m.jsxs("div",{className:"font-mono text-[13px] mt-1.5",children:[m.jsx("span",{className:"text-[#555] select-none mr-1",children:"<<"}),m.jsx("span",{className:`font-bold ${vg(o)}`,children:o}),c!=null&&m.jsxs("span",{className:"text-[#555] ml-2",children:[c,"ms"]})]}),h&&m.jsx(Ei,{className:"text-[#666]",children:wp(h,5)})]})}const $7={get:"getting",list:"listing",create:"creating",update:"updating",delete:"deleting"};function q7({args:e}){const t=e.action??"",r=e.scope_name??"",a=$7[t]??(t||"managing");return m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsxs("span",{className:"text-purple-400/80 font-semibold text-sm",children:[a," proxy scope"]}),r&&m.jsx("span",{className:"text-[#888] text-[13px]",children:Xr(r,50)})]})}function P7({args:e}){const t=e.parent_id;return m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"listing sitemap"}),t&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["under #",Xr(String(t),20)]})]})}function F7({args:e}){const t=e.entry_id;return m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"viewing sitemap entry"}),t&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["#",Xr(String(t),20)]})]})}function G7(e){switch(e.toolName){case"list_requests":return m.jsx(I7,{...e});case"view_request":return m.jsx(B7,{...e});case"send_request":return m.jsx(U7,{...e});case"repeat_request":return m.jsx(H7,{...e});case"scope_rules":return m.jsx(q7,{...e});case"list_sitemap":return m.jsx(P7,{...e});case"view_sitemap_entry":return m.jsx(F7,{...e});default:return m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:e.toolName.replace(/_/g," ")})}}function V7({args:e}){const t=e.thought??e.content??"";return t?m.jsxs("div",{children:[m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:"Agent is thinking"}),m.jsx("div",{className:"mt-1.5 italic text-[#888]",children:m.jsx(qt,{text:t,maxLines:20})})]}):null}function Y7({toolName:e,args:t}){if(e==="create_agent"){const r=t.name??t.agent_name??"",a=t.task??"";return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-cyan-400/80 font-semibold text-sm",children:"spawning"}),r&&m.jsx("span",{className:"text-cyan-400 font-semibold text-sm",children:r})]}),a&&m.jsx("div",{className:"mt-1.5",children:m.jsx(qt,{text:a,maxLines:15})})]})}if(e==="agent_finish"){const r=t.result_summary??"",a=t.success,s=t.findings,o=Array.isArray(s)?s:void 0;return m.jsxs("div",{children:[m.jsx("span",{className:`font-semibold text-sm ${a===!1?"text-red-400/80":"text-emerald-400/80"}`,children:a===!1?"A
Line truncated
|
||||
`);if(t.length<=K7)return t.map(Um).join(`
|
||||
`);const r=t.length-g_-x_;return[...t.slice(0,g_).map(Um),`... ${r} lines truncated ...`,...t.slice(-x_).map(Um)].join(`
|
||||
`)}function tU({args:e,result:t}){const r=e.action??"",a=e.code??e.script??"",s=t;let o=null;s&&typeof s=="object"?o=typeof s.stdout=="string"?s.stdout:null:typeof s=="string"&&(o=s);const c=r==="new_session"?"new session":r==="close"?"close session":r==="list_sessions"?"list sessions":null,d=o?eU(J7(o)):null;return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-yellow-400/80 font-semibold text-sm",children:"Python"}),c&&m.jsx("span",{className:"text-[#888] text-[13px]",children:c})]}),a&&m.jsx(bg,{code:a,language:"python",collapsible:!0}),d&&m.jsx(Ei,{className:"text-[#666]",children:d})]})}function nU({args:e}){const t=e.targets,a=(Array.isArray(t)?t:[]).map(s=>typeof s=="object"&&s?s.original??null:null).filter(Boolean);return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:"Starting penetration test"}),a.length===1&&m.jsxs("span",{className:"text-[#888] text-[13px]",children:["on ",a[0]]})]}),a.length>1&&m.jsx("div",{className:"mt-1.5 space-y-0.5",children:a.map((s,o)=>m.jsxs("div",{className:"text-[13px] text-[#888]",children:[m.jsx("span",{className:"text-[#555] mr-1",children:"•"}),s]},o))})]})}function rU({args:e}){const t=e.name??"Unknown Agent",r=e.task??"";return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-[#888] text-[13px]",children:"subagent"}),m.jsx("span",{className:"text-purple-400 font-semibold text-sm",children:t})]}),r&&m.jsx("div",{className:"mt-1.5",children:m.jsx(qt,{text:r,maxLines:15})})]})}function iU(e){return e.toolName==="subagent_start_info"?m.jsx(rU,{...e}):m.jsx(nU,{...e})}function aU({args:e}){const t=e.executive_summary??"",r=e.methodology??"",a=e.technical_analysis??"",s=e.recommendations??"";return m.jsxs("div",{className:"space-y-3",children:[m.jsx("span",{className:"text-emerald-400/80 font-semibold text-sm",children:"Penetration test completed"}),t&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Executive Summary"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:t,maxLines:25})})]}),r&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Methodology"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:r,maxLines:25})})]}),a&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Technical Analysis"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:a,maxLines:25})})]}),s&&m.jsxs("div",{children:[m.jsx("span",{className:"text-emerald-400/60 text-sm font-semibold",children:"Recommendations"}),m.jsx("div",{className:"mt-1",children:m.jsx(qt,{text:s,maxLines:25})})]}),!t&&!r&&!a&&!s&&m.jsx("div",{className:"text-[#555] text-xs",children:"Generating final report..."})]})}function sU({toolName:e,args:t,result:r}){if(e==="create_note"){const a=t.title??"",s=t.content??"",o=t.category??"general";return m.jsxs("div",{children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note"}),m.jsxs("span",{className:"text-[#555] text-[13px]",children:["(",o,")"]})]}),a&&m.jsx("div",{className:"mt-1.5 text-[#999] text-[13px]",children:a}),s&&m.jsx("div",{className:"mt-1",children:m.jsx(da,{text:s})})]})}if(e==="delete_note")return m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note removed"});if(e==="update_note"){const a=t.title??"",s=t.content??"";return m.jsxs("div",{children:[m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note updated"}),a&&m.jsx("div",{className:"mt-1.5 text-[#999] text-[13px]",children:a}),s&&m.jsx("div",{className:"mt-1",children:m.jsx(da,{text:s})})]})}if(e==="get_note"){const a=r,s=a&&typeof a=="object"&&a.success?a.note:void 0;return m.jsxs("div",{children:[m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"note read"}),s&&m.jsxs(m.Fragment,{children:[m.jsxs("div",{className:"mt-1.5 text-[#999] text-[13px]",children:[s.title??"(untitled)",m.jsxs("span",{className:"text-[#555] ml-1",children:["(",s.category??"general",")"]}),(s.by_you||s.agent_name)&&m.jsxs("span",{className:"text-[#666] ml-1 text-xs",children:["by ",s.by_you?"you":s.agent_name]})]}),s.content&&m.jsx("div",{className:"mt-1",children:m.jsx(da,{text:s.content})})]})]})}if(e==="list_notes"){const a=r;let s=[];if(a&&typeof a=="object"&&a.success){const o=a.notes;s=Array.isArray(o)?o:[]}return m.jsxs("div",{children:[m.jsx("span",{className:"text-amber-400/80 font-semibold text-sm",children:"notes"}),s.length>0?m.jsx("div",{className:"mt-1.5 space-y-0.5",children:s.map((o,c)=>m.jsxs("div",{className:"text-[13px]",children:[m.jsx("span",{className:"text-[#555] mr-1",children:"-"}),m.jsx("span",{className:"text-[#999]",chil
Line truncated
|
||||
`)){const s=a.match(/^#{1,6}\s+(.*)$/);if(s){const o=s[1].trim().toLowerCase();if(o===r)continue;r=o}else a.trim()!==""&&(r=null);t.push(a)}return t.join(`
|
||||
`)}function VH({onOpenEmail:e}){return m.jsx("button",{onClick:e,className:"group w-full cursor-pointer rounded-xl border border-emerald-500/25 bg-emerald-500/[0.06] p-4 text-left transition-colors hover:border-emerald-500/40",children:m.jsxs("div",{className:"flex items-center gap-3",children:[m.jsx("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg",style:{border:"1px solid rgba(16,185,129,0.3)",background:"rgba(16,185,129,0.08)"},children:m.jsx(Ep,{className:"h-4 w-4 text-emerald-400","aria-hidden":"true"})}),m.jsxs("div",{className:"min-w-0 flex-1",children:[m.jsx("p",{className:"text-sm font-semibold text-white",children:"Email an encrypted PDF report of this run"}),m.jsx("p",{className:"mt-0.5 text-xs text-[#888]",children:"Encrypted with a key only you can see, email verified with a one-time code before sending."})]}),m.jsx("span",{className:"flex-shrink-0 rounded-lg bg-white px-3 py-1.5 text-xs font-semibold text-black transition-opacity group-hover:opacity-90",children:"Export report to PDF"})]})})}function YH({summary:e,counts:t,total:r,reportMarkdown:a,raw:s,finished:o,onOpenEmail:c}){const d=[["Executive Summary",e.executiveSummary],["Technical Analysis",e.technicalAnalysis],["Methodology",e.methodology],["Recommendations",e.recommendations]].filter(([,h])=>!!h).map(([h,f])=>({title:h,content:FH(f)}));return m.jsxs("div",{className:"space-y-6",children:[m.jsx("div",{className:"animate-card-in",children:m.jsx(OH,{raw:s,durationSeconds:e.durationSeconds})}),r>0&&m.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:m.jsx(pD,{findings:{total:r,...t}})}),o&&m.jsx("div",{className:"animate-card-in",children:m.jsx(VH,{onOpenEmail:c})}),d.length>0?m.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5 space-y-8",children:d.map(h=>m.jsx(oa,{title:h.title,content:h.content},h.title))}):a?m.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:m.jsx(oa,{content:GH(a)})}):r===0&&m.jsx("p",{className:"text-sm text-[#888]",children:"No summary available for this run yet."})]})}function qm({active:e,onClick:t,children:r}){return m.jsxs("button",{onClick:t,className:`cursor-pointer relative pb-2.5 text-sm font-semibold transition-colors ${e?"text-white":"text-[#666] hover:text-white"}`,children:[r,e&&m.jsx("span",{className:"absolute bottom-0 inset-x-0 h-0.5 bg-white rounded-full"})]})}function XH({run:e,canSteer:t}){const{agents:r,events:a}=e.transcript,s=ee.useMemo(()=>DU(r,a),[r,a]),[o,c]=ee.useState(null),d=o?r.find(f=>f.id===o)??null:null,h=t&&!e.finished;return m.jsxs("div",{className:"space-y-5",children:[m.jsxs("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx(Oo,{className:"w-4 h-4 text-[#888]","aria-hidden":"true"}),m.jsx("h2",{className:"text-sm font-semibold text-white",children:"Agent graph"}),m.jsxs("span",{className:"text-xs text-[#666]",children:[r.length," agent",r.length===1?"":"s"]})]}),m.jsx("p",{className:"mt-1 mb-4 text-xs text-[#666]",children:"Click an agent to open its full transcript."}),m.jsx("div",{className:"h-[480px] rounded-lg border border-[#1a1a1a] overflow-hidden",children:m.jsx(i7,{agents:s,selectedAgentId:o,onSelectAgent:f=>c(f),eventsLoaded:!0,eventsEmpty:s.size===0,scanCompleted:e.finished})})]}),h&&m.jsx(E2,{agents:r}),m.jsxs("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[m.jsx("p",{className:"text-sm font-semibold text-white",children:"Run this pentest with more depth"}),m.jsx("p",{className:"mt-0.5 text-xs text-[#666]",children:"Re-run this pentest on managed infra in the cloud."}),m.jsx("div",{className:"mt-3 flex flex-wrap gap-2.5",children:m.jsx(C2,{label:"Re-run in Strix Pro with more depth",desc:"Run this pentest on managed infra with more depth.",slug:"live_scan",surface:"agents",icon:uT})})]}),m.jsx(nH,{open:d!==null,agent:d,events:a,steerable:h,onClose:()=>c(null)})]})}Bk.createRoot(document.getElementById("root")).render(m.jsx(ee.StrictMode,{children:m.jsx(BH,{})}));
|
||||
`)}function QH({onOpenEmail:e}){return m.jsx("button",{onClick:e,className:"group w-full cursor-pointer rounded-xl border border-emerald-500/25 bg-emerald-500/[0.06] p-4 text-left transition-colors hover:border-emerald-500/40",children:m.jsxs("div",{className:"flex items-center gap-3",children:[m.jsx("div",{className:"flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-lg",style:{border:"1px solid rgba(16,185,129,0.3)",background:"rgba(16,185,129,0.08)"},children:m.jsx(Np,{className:"h-4 w-4 text-emerald-400","aria-hidden":"true"})}),m.jsxs("div",{className:"min-w-0 flex-1",children:[m.jsx("p",{className:"text-sm font-semibold text-white",children:"Email an encrypted PDF report of this run"}),m.jsx("p",{className:"mt-0.5 text-xs text-[#888]",children:"Encrypted with a key only you can see, email verified with a one-time code before sending."})]}),m.jsx("span",{className:"flex-shrink-0 rounded-lg bg-white px-3 py-1.5 text-xs font-semibold text-black transition-opacity group-hover:opacity-90",children:"Export report to PDF"})]})})}function WH({summary:e,counts:t,total:r,reportMarkdown:a,raw:s,finished:o,onOpenEmail:c}){const d=[["Executive Summary",e.executiveSummary],["Technical Analysis",e.technicalAnalysis],["Methodology",e.methodology],["Recommendations",e.recommendations]].filter(([,h])=>!!h).map(([h,f])=>({title:h,content:KH(f)}));return m.jsxs("div",{className:"space-y-6",children:[m.jsx("div",{className:"animate-card-in",children:m.jsx(zH,{raw:s,durationSeconds:e.durationSeconds})}),r>0&&m.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:m.jsx(bD,{findings:{total:r,...t}})}),o&&m.jsx("div",{className:"animate-card-in",children:m.jsx(QH,{onOpenEmail:c})}),o&&m.jsxs("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[m.jsx("p",{className:"text-sm font-semibold text-white",children:"Strix Cloud"}),m.jsx("p",{className:"mt-0.5 text-xs text-[#666]",children:"Run your next pentest in Strix Cloud."}),m.jsxs("div",{className:"mt-3 flex flex-wrap gap-2.5",children:[m.jsx(Ao,{label:"Run a pentest in Strix Cloud",desc:"Validated findings, autofix, and PR reviews.",slug:"overview_cloud",surface:"overview",icon:Q_,primary:!0}),m.jsx(Ao,{label:"Try Strix Enterprise",desc:"SSO, compliance-ready reports, VPC or self-hosted deployment.",slug:"book_demo",surface:"overview",icon:$_,href:Ap})]})]}),d.length>0?m.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5 space-y-8",children:d.map(h=>m.jsx(ca,{title:h.title,content:h.content},h.title))}):a?m.jsx("div",{className:"animate-card-in rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:m.jsx(ca,{content:ZH(a)})}):r===0&&m.jsx("p",{className:"text-sm text-[#888]",children:"No summary available for this run yet."})]})}function Pm({active:e,onClick:t,children:r}){return m.jsxs("button",{onClick:t,className:`cursor-pointer relative pb-2.5 text-sm font-semibold transition-colors ${e?"text-white":"text-[#666] hover:text-white"}`,children:[r,e&&m.jsx("span",{className:"absolute bottom-0 inset-x-0 h-0.5 bg-white rounded-full"})]})}function JH({run:e,canSteer:t}){const{agents:r,events:a}=e.transcript,s=ee.useMemo(()=>UU(r,a),[r,a]),[o,c]=ee.useState(null),d=o?r.find(f=>f.id===o)??null:null,h=t&&!e.finished;return m.jsxs("div",{className:"space-y-5",children:[m.jsxs("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[m.jsxs("div",{className:"flex items-center gap-2",children:[m.jsx(Ro,{className:"w-4 h-4 text-[#888]","aria-hidden":"true"}),m.jsx("h2",{className:"text-sm font-semibold text-white",children:"Agent graph"}),m.jsxs("span",{className:"text-xs text-[#666]",children:[r.length," agent",r.length===1?"":"s"]})]}),m.jsx("p",{className:"mt-1 mb-4 text-xs text-[#666]",children:"Click an agent to open its full transcript."}),m.jsx("div",{className:"h-[480px] rounded-lg border border-[#1a1a1a] overflow-hidden",children:m.jsx(l7,{agents:s,selectedAgentId:o,onSelectAgent:f=>c(f),eventsLoaded:!0,eventsEmpty:s.size===0,scanCompleted:e.finished})})]}),h&&m.jsx(C2,{agents:r}),m.jsxs("div",{className:"rounded-xl border border-[#222] bg-[rgba(255,255,255,0.02)] p-5",children:[m.jsx("p",{className:"text-sm font-semibold text-white",children:"Run this pentest with more depth"}),m.jsx("p",{className:"mt-0.5 text-xs text-[#666]",children:"Run this pentest again in Strix Cloud."}),m.jsxs("div",{className:"mt-3 flex flex-wrap gap-2.5",children:[m.jsx(Ao,{label:"Re-run in Strix Pro with more depth",desc:"More depth, validated findings, and autofix.",slug:"live_scan",surface:"agents",icon:Q_,primary:!0}),m.jsx(Ao,{label:"Try Strix Enterprise",desc:"SSO, compliance-ready reports, VPC or self-hosted deployment.",slug:"book_demo",surface:"agents",icon:$_,href:Ap})]})]}),m.jsx(lH,{open:d!==null,agent:d,events:a,steerable:h,onClose:()=>c(null)})]})}$k.createR
Line truncated
|
||||
@@ -6,8 +6,8 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<title>Strix Results</title>
|
||||
<script type="module" crossorigin src="./assets/index-Bpn8GiSb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-qwPOPAGC.css">
|
||||
<script type="module" crossorigin src="./assets/index-DbPDzbcJ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CrLE9rrV.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+234
-48
@@ -36,6 +36,23 @@ _global_report_state: Optional["ReportState"] = None
|
||||
_CONTROL_CHARS = re.compile(r"[\x00-\x1f\x7f]+")
|
||||
|
||||
|
||||
class ReportRollbackError(RuntimeError):
|
||||
"""Raised when a failed deletion could not rewrite the on-disk indexes.
|
||||
|
||||
The report is still on file in memory; the indexes are rewritten from
|
||||
memory on the next save.
|
||||
"""
|
||||
|
||||
def __init__(self, report_id: str, *, cause: BaseException) -> None:
|
||||
self.report_id = report_id
|
||||
self.cause = cause
|
||||
super().__init__(
|
||||
f"Deletion of report '{report_id}' failed ({cause}) and the on-disk indexes "
|
||||
"could not be restored; the report is still on file and the indexes are "
|
||||
"rewritten on the next save"
|
||||
)
|
||||
|
||||
|
||||
def _strix_version() -> str | None:
|
||||
"""Best-effort package version for the SARIF tool.driver.version field."""
|
||||
try:
|
||||
@@ -73,6 +90,7 @@ UPDATABLE_REPORT_FIELDS = frozenset(
|
||||
"cve",
|
||||
"cwe",
|
||||
"code_locations",
|
||||
"http_exchange_ids",
|
||||
"fix_verification",
|
||||
"fix_pr_body",
|
||||
}
|
||||
@@ -220,6 +238,7 @@ class ReportState:
|
||||
self.caido_url: str | None = None
|
||||
self.vulnerability_found_callback: Callable[[dict[str, Any]], None] | None = None
|
||||
self.vulnerability_updated_callback: Callable[[dict[str, Any]], None] | None = None
|
||||
self.vulnerability_deleted_callback: Callable[[dict[str, Any]], None] | None = None
|
||||
|
||||
self._sarif_repo_ctx: dict[str, Any] | None = None
|
||||
self._sarif_repo_ctx_ready: bool = False
|
||||
@@ -333,6 +352,7 @@ class ReportState:
|
||||
cve: str | None = None,
|
||||
cwe: str | None = None,
|
||||
code_locations: list[dict[str, Any]] | None = None,
|
||||
http_exchange_ids: list[str] | None = None,
|
||||
fix_verification: str | None = None,
|
||||
fix_pr_body: str | None = None,
|
||||
finding_class: str | None = None,
|
||||
@@ -340,7 +360,7 @@ class ReportState:
|
||||
agent_id: str | None = None,
|
||||
agent_name: str | None = None,
|
||||
) -> str:
|
||||
report_id = f"vuln-{len(self.vulnerability_reports) + 1:04d}"
|
||||
report_id = self._next_report_id()
|
||||
|
||||
report: dict[str, Any] = {
|
||||
"id": report_id,
|
||||
@@ -391,6 +411,8 @@ class ReportState:
|
||||
report["cwe"] = cwe.strip()
|
||||
if code_locations:
|
||||
report["code_locations"] = code_locations
|
||||
if http_exchange_ids:
|
||||
report["http_exchange_ids"] = http_exchange_ids
|
||||
if fix_verification:
|
||||
report["fix_verification"] = fix_verification.strip()
|
||||
if fix_pr_body:
|
||||
@@ -403,17 +425,35 @@ class ReportState:
|
||||
if agent_name:
|
||||
report["agent_name"] = agent_name
|
||||
|
||||
if self.vulnerability_found_callback:
|
||||
self.vulnerability_found_callback(report)
|
||||
|
||||
self.vulnerability_reports.append(report)
|
||||
logger.info(f"Added vulnerability report: {report_id} - {title}")
|
||||
posthog.finding(severity, cwe=cwe, is_cve=bool(cve))
|
||||
scarf.finding(severity, cwe=cwe, is_cve=bool(cve))
|
||||
|
||||
if self.vulnerability_found_callback:
|
||||
self.vulnerability_found_callback(report)
|
||||
|
||||
self.save_run_data()
|
||||
return report_id
|
||||
|
||||
def _deleted_vulnerability_reports(self) -> list[dict[str, Any]]:
|
||||
raw = self.run_record.get("deleted_vulnerability_reports")
|
||||
return [e for e in raw if isinstance(e, dict)] if isinstance(raw, list) else []
|
||||
|
||||
def _next_report_id(self) -> str:
|
||||
"""Allocate the id after every id this run has ever handed out.
|
||||
|
||||
A deleted report leaves the list, so counting entries would hand its id
|
||||
to the next finding and let that finding overwrite the deleted MD on disk
|
||||
and inherit its history in every consumer that keys on the id.
|
||||
"""
|
||||
used = 0
|
||||
for entry in [*self.vulnerability_reports, *self._deleted_vulnerability_reports()]:
|
||||
match = re.fullmatch(r"vuln-(\d+)", str(entry.get("id", "")))
|
||||
if match:
|
||||
used = max(used, int(match.group(1)))
|
||||
return f"vuln-{used + 1:04d}"
|
||||
|
||||
def update_vulnerability_report(
|
||||
self,
|
||||
report_id: str,
|
||||
@@ -486,11 +526,18 @@ class ReportState:
|
||||
)
|
||||
history.append(entry)
|
||||
|
||||
report.update(changed)
|
||||
revised = {**report, **changed}
|
||||
for dependent in superseded:
|
||||
report.pop(dependent, None)
|
||||
report["update_history"] = history
|
||||
report["updated_at"] = entry["timestamp"]
|
||||
revised.pop(dependent, None)
|
||||
revised["update_history"] = history
|
||||
revised["updated_at"] = entry["timestamp"]
|
||||
|
||||
# Persistence must accept the revision before local state changes. A
|
||||
# failed callback leaves the old evidence intact and the update retryable.
|
||||
if self.vulnerability_updated_callback:
|
||||
self.vulnerability_updated_callback(revised)
|
||||
report.clear()
|
||||
report.update(revised)
|
||||
|
||||
# The markdown on disk still shows the superseded evidence, so let the
|
||||
# writer re-render it.
|
||||
@@ -502,12 +549,91 @@ class ReportState:
|
||||
", ".join(entry["fields"]) or "no field replaced",
|
||||
)
|
||||
|
||||
if self.vulnerability_updated_callback:
|
||||
self.vulnerability_updated_callback(report)
|
||||
|
||||
self.save_run_data()
|
||||
return report
|
||||
|
||||
def delete_vulnerability_report(
|
||||
self,
|
||||
report_id: str,
|
||||
*,
|
||||
delete_reason: str,
|
||||
deleted_by_agent_id: str | None = None,
|
||||
deleted_by_agent_name: str | None = None,
|
||||
) -> dict[str, Any] | None:
|
||||
"""Withdraw a report from the run, keeping a record of the withdrawal.
|
||||
|
||||
Returns the removed report, or ``None`` when the id is unknown. Every
|
||||
agent in a run shares one trust boundary, so any of them may withdraw
|
||||
any report (just as any of them may revise one); the run record keeps
|
||||
who filed it, who withdrew it and why. The report leaves
|
||||
``vulnerability_reports`` and its rendered artifacts, and its id is
|
||||
never reissued.
|
||||
|
||||
The rewritten artifacts and the ``vulnerability_deleted_callback`` must
|
||||
both accept the deletion first: if either fails the report is put back
|
||||
and the error propagates, so the deletion can be retried
|
||||
(:class:`ReportRollbackError` when the indexes could not be put back).
|
||||
"""
|
||||
report = next((r for r in self.vulnerability_reports if r.get("id") == report_id), None)
|
||||
if report is None:
|
||||
logger.warning("cannot delete unknown vulnerability report %s", report_id)
|
||||
return None
|
||||
|
||||
entry: dict[str, Any] = {
|
||||
"id": report_id,
|
||||
"title": report.get("title"),
|
||||
"severity": report.get("severity"),
|
||||
"filed_at": report.get("timestamp"),
|
||||
"deleted_at": datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S UTC"),
|
||||
"reason": delete_reason.strip()[:500],
|
||||
}
|
||||
if report.get("agent_id"):
|
||||
entry["filed_by_agent_id"] = report["agent_id"]
|
||||
if deleted_by_agent_id:
|
||||
entry["agent_id"] = deleted_by_agent_id
|
||||
if deleted_by_agent_name:
|
||||
entry["agent_name"] = deleted_by_agent_name
|
||||
|
||||
position = self.vulnerability_reports.index(report)
|
||||
was_saved = report_id in self._saved_vuln_ids
|
||||
history = self._deleted_vulnerability_reports()
|
||||
|
||||
self.vulnerability_reports.remove(report)
|
||||
self._saved_vuln_ids.discard(report_id)
|
||||
self.run_record["deleted_vulnerability_reports"] = [*history, entry]
|
||||
try:
|
||||
# Local artifacts first: they can be put back if persistence then
|
||||
# refuses, whereas a row deleted elsewhere cannot.
|
||||
self._sync_llm_usage_record()
|
||||
self._write_artifacts()
|
||||
if self.vulnerability_deleted_callback:
|
||||
self.vulnerability_deleted_callback({**report, "deletion": entry})
|
||||
except Exception as exc:
|
||||
self.vulnerability_reports.insert(position, report)
|
||||
if was_saved:
|
||||
self._saved_vuln_ids.add(report_id)
|
||||
if history:
|
||||
self.run_record["deleted_vulnerability_reports"] = history
|
||||
else:
|
||||
self.run_record.pop("deleted_vulnerability_reports", None)
|
||||
try:
|
||||
self._write_artifacts()
|
||||
except Exception as rollback_exc:
|
||||
logger.exception(
|
||||
"could not restore artifacts after failed deletion of %s", report_id
|
||||
)
|
||||
raise ReportRollbackError(report_id, cause=exc) from rollback_exc
|
||||
raise
|
||||
|
||||
md_path = self.get_run_dir() / "vulnerabilities" / f"{report_id}.md"
|
||||
try:
|
||||
md_path.unlink(missing_ok=True)
|
||||
except OSError:
|
||||
logger.exception("could not remove %s", md_path)
|
||||
|
||||
logger.info("Deleted vulnerability report %s - %s", report_id, report.get("title"))
|
||||
return report
|
||||
|
||||
def get_existing_vulnerabilities(self) -> list[dict[str, Any]]:
|
||||
return list(self.vulnerability_reports)
|
||||
|
||||
@@ -531,6 +657,35 @@ class ReportState:
|
||||
def record_observed_llm_cost(self, cost: float) -> None:
|
||||
self._llm_usage.record_observed_cost(cost)
|
||||
|
||||
def record_llm_provider(
|
||||
self,
|
||||
provider: str,
|
||||
*,
|
||||
agent_id: str | None,
|
||||
input_tokens: int,
|
||||
cached_tokens: int,
|
||||
cost: float,
|
||||
) -> None:
|
||||
self._llm_usage.record_provider(
|
||||
provider,
|
||||
agent_id=agent_id,
|
||||
input_tokens=input_tokens,
|
||||
cached_tokens=cached_tokens,
|
||||
cost=cost,
|
||||
cache_block_tokens=load_settings().llm.cache_block_tokens,
|
||||
)
|
||||
|
||||
def get_process_llm_providers(self) -> dict[str, dict[str, float]]:
|
||||
"""Per-provider usage since this process started, like get_process_llm_usage."""
|
||||
baseline = self._telemetry_llm_usage_baseline.get("providers") or {}
|
||||
providers: dict[str, dict[str, float]] = {}
|
||||
for name, tally in (self._llm_usage.to_record().get("providers") or {}).items():
|
||||
before = baseline.get(name) or {}
|
||||
delta = {key: max(0, value - _number(before.get(key))) for key, value in tally.items()}
|
||||
if delta["requests"]:
|
||||
providers[name] = delta
|
||||
return providers
|
||||
|
||||
def get_total_llm_usage(self) -> dict[str, Any]:
|
||||
return dict(self.run_record.get("llm_usage") or self._build_llm_usage_record())
|
||||
|
||||
@@ -691,47 +846,54 @@ class ReportState:
|
||||
return None
|
||||
|
||||
def _save_artifacts(self) -> None:
|
||||
"""Write scan artifacts under ``run_dir``."""
|
||||
run_dir = self.get_run_dir()
|
||||
"""Write scan artifacts under ``run_dir``; a write failure is logged."""
|
||||
try:
|
||||
run_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
coverage = self._coverage_document()
|
||||
if coverage is not None:
|
||||
try:
|
||||
write_coverage(run_dir, coverage)
|
||||
except OSError:
|
||||
logger.exception("coverage.json write failed (non-fatal)")
|
||||
|
||||
if self.final_scan_result:
|
||||
write_executive_report(run_dir, self.final_scan_result)
|
||||
|
||||
if self.vulnerability_reports:
|
||||
write_vulnerabilities(run_dir, self.vulnerability_reports, self._saved_vuln_ids)
|
||||
|
||||
# SARIF 2.1.0 emitter for CI / ASPM integration. Always emit (even
|
||||
# empty) so a clean run overwrites a prior findings.sarif rather than
|
||||
# leaving a stale one — codeql-action's "absent from new submission →
|
||||
# fixed" needs the fresh empty doc to auto-resolve alerts. Isolated
|
||||
# in its own try: a SARIF-build error must NEVER break the CSV/MD/
|
||||
# run-record path (the emitter's own contract).
|
||||
try:
|
||||
write_sarif(
|
||||
run_dir,
|
||||
self.vulnerability_reports,
|
||||
tool_version=_strix_version(),
|
||||
repository_context=self._sarif_repository_context(),
|
||||
coverage=coverage,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("SARIF emit failed (non-fatal; CSV/MD unaffected)")
|
||||
|
||||
write_run_record(run_dir, self.run_record)
|
||||
|
||||
logger.info("Essential scan data saved to: %s", run_dir)
|
||||
self._write_artifacts()
|
||||
except (OSError, RuntimeError):
|
||||
logger.exception("Failed to save scan data")
|
||||
|
||||
def _write_artifacts(self) -> None:
|
||||
"""Write scan artifacts under ``run_dir``, raising when the index or
|
||||
run record cannot be written."""
|
||||
run_dir = self.get_run_dir()
|
||||
run_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
coverage = self._coverage_document()
|
||||
if coverage is not None:
|
||||
try:
|
||||
write_coverage(run_dir, coverage)
|
||||
except OSError:
|
||||
logger.exception("coverage.json write failed (non-fatal)")
|
||||
|
||||
if self.final_scan_result:
|
||||
write_executive_report(run_dir, self.final_scan_result)
|
||||
|
||||
# An index is written for an empty list too once a report was
|
||||
# deleted, or the CSV/JSON on disk would still list it.
|
||||
if self.vulnerability_reports or self._deleted_vulnerability_reports():
|
||||
write_vulnerabilities(run_dir, self.vulnerability_reports, self._saved_vuln_ids)
|
||||
|
||||
# SARIF 2.1.0 emitter for CI / ASPM integration. Always emit (even
|
||||
# empty) so a clean run overwrites a prior findings.sarif rather than
|
||||
# leaving a stale one — codeql-action's "absent from new submission →
|
||||
# fixed" needs the fresh empty doc to auto-resolve alerts. Isolated
|
||||
# in its own try: a SARIF-build error must NEVER break the CSV/MD/
|
||||
# run-record path (the emitter's own contract).
|
||||
try:
|
||||
write_sarif(
|
||||
run_dir,
|
||||
self.vulnerability_reports,
|
||||
tool_version=_strix_version(),
|
||||
repository_context=self._sarif_repository_context(),
|
||||
coverage=coverage,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("SARIF emit failed (non-fatal; CSV/MD unaffected)")
|
||||
|
||||
write_run_record(run_dir, self.run_record)
|
||||
|
||||
logger.info("Essential scan data saved to: %s", run_dir)
|
||||
|
||||
def _sarif_repository_context(self) -> dict[str, Any] | None:
|
||||
"""Repo/commit/branch context for SARIF provenance (repo scans only).
|
||||
|
||||
@@ -857,6 +1019,30 @@ class StreamedOpenRouterCosts:
|
||||
streamed_openrouter_costs = StreamedOpenRouterCosts()
|
||||
|
||||
|
||||
def record_openrouter_provider(provider: Any, usage: Any) -> None:
|
||||
"""Tally which upstream provider served a stream, from its final usage chunk.
|
||||
|
||||
OpenRouter spreads one model across many providers whose prices, quantization
|
||||
and prompt caching differ, so this is what shows where a scan's tokens went.
|
||||
"""
|
||||
# Deferred: request_log pulls in the agents SDK, which strix.report must not import.
|
||||
from strix.llm.request_log import current_call_context
|
||||
|
||||
report_state = get_global_report_state()
|
||||
if report_state is None or not isinstance(usage, dict):
|
||||
return
|
||||
details = usage.get("prompt_tokens_details")
|
||||
report_state.record_llm_provider(
|
||||
provider if isinstance(provider, str) and provider else "unknown",
|
||||
agent_id=current_call_context().agent_id,
|
||||
input_tokens=int(_number(usage.get("prompt_tokens"))),
|
||||
cached_tokens=int(_number(details.get("cached_tokens")))
|
||||
if isinstance(details, dict)
|
||||
else 0,
|
||||
cost=openrouter_stream_cost(usage) or 0.0,
|
||||
)
|
||||
|
||||
|
||||
def litellm_cost_callback(
|
||||
kwargs: Any,
|
||||
completion_response: Any,
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from agents.usage import Usage, deserialize_usage, serialize_usage
|
||||
from pydantic import BaseModel, TypeAdapter, ValidationError
|
||||
|
||||
from strix.report.pricing import resolve_litellm_model
|
||||
|
||||
@@ -13,6 +14,22 @@ from strix.report.pricing import resolve_litellm_model
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ProviderUsage(BaseModel):
|
||||
"""Running totals for one upstream provider OpenRouter routed calls to."""
|
||||
|
||||
requests: int = 0
|
||||
input_tokens: int = 0
|
||||
cached_tokens: int = 0
|
||||
cost: float = 0.0
|
||||
# Calls that didn't find the agent's whole previous prompt cached, and the
|
||||
# previous-prompt tokens they had to pay for again.
|
||||
cache_misses: int = 0
|
||||
missed_tokens: int = 0
|
||||
|
||||
|
||||
_PROVIDER_USAGE = TypeAdapter(dict[str, ProviderUsage])
|
||||
|
||||
|
||||
class LLMUsageLedger:
|
||||
"""Aggregate SDK ``Usage`` objects and attach best-effort cost estimates."""
|
||||
|
||||
@@ -23,6 +40,10 @@ class LLMUsageLedger:
|
||||
self._observed_cost = 0.0
|
||||
self._estimated_cost = 0.0
|
||||
self._has_observed_cost = False
|
||||
# Keyed by upstream provider name, e.g. "Z.AI" or "DeepInfra".
|
||||
self._providers: dict[str, ProviderUsage] = {}
|
||||
# Each agent's last prompt size, which its next call should find cached.
|
||||
self._last_input_tokens: dict[str, int] = {}
|
||||
# When True, tokens are still tracked but cost stays $0 — the run is on a
|
||||
# model subscription, so there is no metered per-token charge to report.
|
||||
self.zero_cost = False
|
||||
@@ -62,6 +83,33 @@ class LLMUsageLedger:
|
||||
self._observed_cost += float(cost)
|
||||
self._has_observed_cost = True
|
||||
|
||||
def record_provider(
|
||||
self,
|
||||
provider: str,
|
||||
*,
|
||||
agent_id: str | None,
|
||||
input_tokens: int,
|
||||
cached_tokens: int,
|
||||
cost: float,
|
||||
cache_block_tokens: int,
|
||||
) -> None:
|
||||
tally = self._providers.setdefault(provider, ProviderUsage())
|
||||
tally.requests += 1
|
||||
tally.input_tokens += input_tokens
|
||||
tally.cached_tokens += cached_tokens
|
||||
if agent_id:
|
||||
previous = self._last_input_tokens.get(agent_id, 0)
|
||||
# The previous prompt is a prefix of this one, so all of it but a
|
||||
# partial last block should read back cached. A shrinking prompt means
|
||||
# compaction rewrote it, so a miss is expected.
|
||||
missed = previous - cached_tokens
|
||||
if input_tokens >= previous and missed >= cache_block_tokens:
|
||||
tally.cache_misses += 1
|
||||
tally.missed_tokens += missed
|
||||
self._last_input_tokens[agent_id] = input_tokens
|
||||
if not self.zero_cost:
|
||||
tally.cost = _round_cost(tally.cost + cost)
|
||||
|
||||
@property
|
||||
def total_cost(self) -> float:
|
||||
if self.zero_cost:
|
||||
@@ -71,6 +119,7 @@ class LLMUsageLedger:
|
||||
def to_record(self) -> dict[str, Any]:
|
||||
record = serialize_usage(self._total_usage)
|
||||
record["cost"] = self.total_cost
|
||||
record["providers"] = {name: tally.model_dump() for name, tally in self._providers.items()}
|
||||
record["agents"] = []
|
||||
|
||||
agent_tokens = {aid: _resolve_total_tokens(u) for aid, u in self._agent_usage.items()}
|
||||
@@ -102,10 +151,16 @@ class LLMUsageLedger:
|
||||
self._observed_cost = 0.0
|
||||
self._estimated_cost = 0.0
|
||||
self._has_observed_cost = False
|
||||
self._providers = {}
|
||||
|
||||
if not isinstance(raw_usage, dict):
|
||||
return
|
||||
|
||||
try:
|
||||
self._providers = _PROVIDER_USAGE.validate_python(raw_usage.get("providers") or {})
|
||||
except ValidationError:
|
||||
logger.exception("Failed to hydrate llm_usage providers from run.json")
|
||||
|
||||
try:
|
||||
self._total_usage = deserialize_usage(raw_usage)
|
||||
except Exception:
|
||||
|
||||
+132
-141
@@ -3,15 +3,16 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import io
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import tarfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from agents.sandbox.entries import BaseEntry, File, LocalDir
|
||||
from agents.sandbox.entries import BaseEntry, LocalDir
|
||||
from agents.sandbox.manifest import Environment, Manifest
|
||||
|
||||
from strix.config import load_settings
|
||||
@@ -21,6 +22,8 @@ from strix.runtime.caido_handle import CaidoBootstrapHandle
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from agents.sandbox.session import BaseSandboxSession
|
||||
|
||||
from strix.runtime.status import StatusSink
|
||||
|
||||
|
||||
@@ -38,6 +41,13 @@ _WORKSPACE_ROOT = "/workspace"
|
||||
|
||||
_PROTECTED_METADATA_NAMES = (".git", ".agents", ".codex")
|
||||
|
||||
# Extra files travel as one tar archive: a single upload plus one extraction
|
||||
# inside the sandbox, instead of several round trips per file.
|
||||
_EXTRA_FILE_ARCHIVE_REL = ".strix-extra-files.tar"
|
||||
_EXTRA_FILE_ARCHIVE = f"{_WORKSPACE_ROOT}/{_EXTRA_FILE_ARCHIVE_REL}"
|
||||
_EXTRA_FILE_EXTRACT_TIMEOUT_S = 120
|
||||
_EXTRA_FILE_MODE = 0o644
|
||||
|
||||
|
||||
def _host_identity_env() -> dict[str, str]:
|
||||
# Read the platform through a local so it is not narrowed to whichever OS is
|
||||
@@ -60,8 +70,9 @@ def build_bind_mounts(local_sources: list[dict[str, Any]]) -> list[dict[str, Any
|
||||
continue
|
||||
resolved = Path(host_path).expanduser().resolve()
|
||||
target = f"{_WORKSPACE_ROOT}/{ws_subdir}"
|
||||
bind_mounts.append({"source": str(resolved), "target": target, "read_only": False})
|
||||
if src.get("protect_metadata"):
|
||||
read_only = bool(src.get("read_only"))
|
||||
bind_mounts.append({"source": str(resolved), "target": target, "read_only": read_only})
|
||||
if src.get("protect_metadata") and not read_only:
|
||||
bind_mounts.extend(_metadata_mounts(resolved, target))
|
||||
return bind_mounts
|
||||
|
||||
@@ -133,98 +144,82 @@ def _extra_file_content(extra_file: dict[str, Any]) -> bytes | None:
|
||||
return None
|
||||
|
||||
|
||||
def build_extra_file_entries(
|
||||
def build_extra_file_archive(
|
||||
extra_files: list[dict[str, Any]],
|
||||
local_sources: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str | Path, BaseEntry]:
|
||||
"""Map extra files to in-memory ``File`` manifest entries.
|
||||
) -> bytes | None:
|
||||
"""Pack extra files into one tar archive rooted at ``/workspace``.
|
||||
|
||||
Each item is ``{"workspace_path": "/workspace/<rel>", "content": bytes|str}``;
|
||||
manifest backends materialize the entry at the requested path alongside the
|
||||
``LocalDir`` source uploads. Invalid items — including paths that collide
|
||||
with a ``local_sources`` tree or with an earlier extra file, which would
|
||||
otherwise replace its manifest entry — are skipped with a warning.
|
||||
Each item is ``{"workspace_path": "/workspace/<rel>", "content": bytes|str}``
|
||||
and becomes a regular-file member at ``<rel>``. Extracting the archive as
|
||||
the sandbox user (see ``stage_extra_files``) leaves every file owned by
|
||||
that user, so the agent can edit it and create siblings. Invalid items —
|
||||
including paths that collide with a ``local_sources`` tree or with an
|
||||
earlier extra file — are skipped with a warning. Returns ``None`` when no
|
||||
valid file remains.
|
||||
"""
|
||||
source_roots = _source_root_rels(local_sources)
|
||||
placed: list[str] = []
|
||||
entries: dict[str | Path, BaseEntry] = {}
|
||||
for extra_file in extra_files:
|
||||
rel = _extra_file_rel_path(str(extra_file.get("workspace_path") or ""))
|
||||
content = _extra_file_content(extra_file)
|
||||
if rel is None or content is None:
|
||||
logger.warning(
|
||||
"Skipping invalid extra file entry (workspace_path=%r)",
|
||||
extra_file.get("workspace_path"),
|
||||
)
|
||||
continue
|
||||
if _collides_with_source_root(rel, source_roots + placed):
|
||||
logger.warning(
|
||||
"Skipping extra file colliding with a local source tree or an "
|
||||
"earlier extra file (workspace_path=%r)",
|
||||
extra_file.get("workspace_path"),
|
||||
)
|
||||
continue
|
||||
placed.append(rel)
|
||||
entries[rel] = File(content=content)
|
||||
return entries
|
||||
placed: list[str] = [_EXTRA_FILE_ARCHIVE_REL]
|
||||
buffer = io.BytesIO()
|
||||
mtime = int(time.time())
|
||||
with tarfile.open(fileobj=buffer, mode="w", format=tarfile.PAX_FORMAT) as archive:
|
||||
for extra_file in extra_files:
|
||||
rel, content = _validated_extra_file(extra_file, source_roots + placed)
|
||||
if rel is None or content is None:
|
||||
continue
|
||||
placed.append(rel)
|
||||
info = tarfile.TarInfo(name=rel)
|
||||
info.size = len(content)
|
||||
info.mode = _EXTRA_FILE_MODE
|
||||
info.mtime = mtime
|
||||
archive.addfile(info, io.BytesIO(content))
|
||||
if len(placed) == 1:
|
||||
return None
|
||||
return buffer.getvalue()
|
||||
|
||||
|
||||
def extra_file_staging_dir(scan_id: str) -> Path:
|
||||
"""A fresh host staging directory for a scan's extra-file bind mounts.
|
||||
def _validated_extra_file(
|
||||
extra_file: dict[str, Any], taken: list[str]
|
||||
) -> tuple[str | None, bytes | None]:
|
||||
rel = _extra_file_rel_path(str(extra_file.get("workspace_path") or ""))
|
||||
content = _extra_file_content(extra_file)
|
||||
if rel is None or content is None:
|
||||
logger.warning(
|
||||
"Skipping invalid extra file entry (workspace_path=%r)",
|
||||
extra_file.get("workspace_path"),
|
||||
)
|
||||
return None, None
|
||||
if _collides_with_source_root(rel, taken):
|
||||
logger.warning(
|
||||
"Skipping extra file colliding with a local source tree or an "
|
||||
"earlier extra file (workspace_path=%r)",
|
||||
extra_file.get("workspace_path"),
|
||||
)
|
||||
return None, None
|
||||
return rel, content
|
||||
|
||||
The docker daemon resolves bind sources in its own filesystem. With a
|
||||
remote daemon (e.g. a dind sidecar) the run directory is not shared, so
|
||||
staging lives under the temp dir like every other bind-mount source.
|
||||
"""
|
||||
safe = "".join(c if c.isalnum() or c in "-_." else "-" for c in scan_id)
|
||||
return Path(tempfile.mkdtemp(prefix=f"strix-extra-files-{safe}-"))
|
||||
|
||||
|
||||
def build_extra_file_bind_mounts(
|
||||
extra_files: list[dict[str, Any]],
|
||||
staging_dir: Path,
|
||||
local_sources: list[dict[str, Any]] | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Stage extra files on the host and map them to read-only bind mounts.
|
||||
async def stage_extra_files(session: BaseSandboxSession, archive: bytes) -> None:
|
||||
"""Upload ``archive`` and unpack it under ``/workspace`` as the sandbox user.
|
||||
|
||||
Bind-mount backends bypass the manifest, so the content is written under
|
||||
``staging_dir`` (one numbered subdirectory per file to avoid basename
|
||||
collisions) and mounted read-only at the same ``/workspace/<rel>`` path the
|
||||
manifest path would use. Invalid items — including paths that collide with
|
||||
a ``local_sources`` tree or with an earlier extra file, which would
|
||||
duplicate or shadow its mount target — are skipped with a warning.
|
||||
``--no-same-owner`` keeps ownership with the extracting user even where
|
||||
the session runs as root, so the agent user can always write the files.
|
||||
"""
|
||||
source_roots = _source_root_rels(local_sources)
|
||||
placed: list[str] = []
|
||||
mounts: list[dict[str, Any]] = []
|
||||
for index, extra_file in enumerate(extra_files):
|
||||
rel = _extra_file_rel_path(str(extra_file.get("workspace_path") or ""))
|
||||
content = _extra_file_content(extra_file)
|
||||
if rel is None or content is None:
|
||||
logger.warning(
|
||||
"Skipping invalid extra file entry (workspace_path=%r)",
|
||||
extra_file.get("workspace_path"),
|
||||
)
|
||||
continue
|
||||
if _collides_with_source_root(rel, source_roots + placed):
|
||||
logger.warning(
|
||||
"Skipping extra file colliding with a local source tree or an "
|
||||
"earlier extra file (workspace_path=%r)",
|
||||
extra_file.get("workspace_path"),
|
||||
)
|
||||
continue
|
||||
placed.append(rel)
|
||||
host_file = staging_dir / str(index) / Path(rel).name
|
||||
host_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
host_file.write_bytes(content)
|
||||
mounts.append(
|
||||
{
|
||||
"source": str(host_file),
|
||||
"target": f"{_WORKSPACE_ROOT}/{rel}",
|
||||
"read_only": True,
|
||||
}
|
||||
await session.write(Path(_EXTRA_FILE_ARCHIVE), io.BytesIO(archive))
|
||||
result = await session.exec(
|
||||
"sh",
|
||||
"-c",
|
||||
'tar --no-same-owner -xf "$1" -C "$2" && rm -f -- "$1"',
|
||||
"sh",
|
||||
_EXTRA_FILE_ARCHIVE,
|
||||
_WORKSPACE_ROOT,
|
||||
timeout=_EXTRA_FILE_EXTRACT_TIMEOUT_S,
|
||||
)
|
||||
if not result.ok():
|
||||
stderr = result.stderr.decode("utf-8", errors="replace").strip()
|
||||
raise RuntimeError(
|
||||
f"unpacking extra files in the sandbox failed (exit {result.exit_code}): {stderr}"
|
||||
)
|
||||
return mounts
|
||||
|
||||
|
||||
def _metadata_mounts(tree: Path, target: str) -> list[dict[str, Any]]:
|
||||
@@ -274,10 +269,9 @@ async def create_or_reuse(
|
||||
``/workspace/<workspace_subdir>`` inside the container.
|
||||
|
||||
Each ``extra_files`` entry (``{"workspace_path": "/workspace/<rel>",
|
||||
"content": bytes | str}``) lands as a single file at its ``workspace_path``
|
||||
regardless of backend: an in-memory ``File`` manifest entry on manifest
|
||||
backends, a read-only bind mount of a host-staged copy on bind-mount
|
||||
backends.
|
||||
"content": bytes | str}``) lands as a regular, agent-writable file at its
|
||||
``workspace_path`` on every backend: the files are uploaded as one archive
|
||||
and unpacked inside the sandbox right after bring-up.
|
||||
"""
|
||||
|
||||
def report(phase: str) -> None:
|
||||
@@ -292,20 +286,15 @@ async def create_or_reuse(
|
||||
backend_name = load_settings().runtime.backend
|
||||
backend = get_backend(backend_name)
|
||||
|
||||
staging_dir: Path | None = None
|
||||
if backend_supports_bind_mounts(backend_name):
|
||||
bind_mounts = build_bind_mounts(local_sources)
|
||||
entries: dict[str | Path, BaseEntry] = {}
|
||||
if extra_files:
|
||||
staging_dir = extra_file_staging_dir(scan_id)
|
||||
bind_mounts.extend(
|
||||
build_extra_file_bind_mounts(extra_files, staging_dir, local_sources)
|
||||
)
|
||||
else:
|
||||
bind_mounts = []
|
||||
entries = build_manifest_entries(local_sources)
|
||||
if extra_files:
|
||||
entries.update(build_extra_file_entries(extra_files, local_sources))
|
||||
extra_file_archive = (
|
||||
build_extra_file_archive(extra_files, local_sources) if extra_files else None
|
||||
)
|
||||
|
||||
# Caido runs as an in-container sidecar; HTTP(S) traffic from any
|
||||
# process started via ``session.exec`` (the SDK's Shell tool, etc.)
|
||||
@@ -335,54 +324,58 @@ async def create_or_reuse(
|
||||
image,
|
||||
)
|
||||
report("Starting sandbox container")
|
||||
try:
|
||||
client, session = await backend(
|
||||
image=image,
|
||||
manifest=manifest,
|
||||
exposed_ports=(_CONTAINER_CAIDO_PORT,),
|
||||
bind_mounts=bind_mounts,
|
||||
client, session = await backend(
|
||||
image=image,
|
||||
manifest=manifest,
|
||||
exposed_ports=(_CONTAINER_CAIDO_PORT,),
|
||||
bind_mounts=bind_mounts,
|
||||
)
|
||||
|
||||
if extra_file_archive is not None:
|
||||
report("Placing workspace files")
|
||||
try:
|
||||
await stage_extra_files(session, extra_file_archive)
|
||||
except BaseException:
|
||||
await _discard_session(client, session)
|
||||
raise
|
||||
|
||||
report("Setting up the proxy")
|
||||
caido_endpoint = await session.resolve_exposed_port(_CONTAINER_CAIDO_PORT)
|
||||
scheme = "https" if caido_endpoint.tls else "http"
|
||||
host_caido_url = f"{scheme}://{caido_endpoint.host}:{caido_endpoint.port}"
|
||||
logger.debug("Caido host endpoint resolved: %s", host_caido_url)
|
||||
|
||||
# The Caido login + project setup polls the guest for a couple of seconds
|
||||
# and nothing needs the client before the first proxy tool call, so it
|
||||
# runs concurrently with the rest of scan start; consumers resolve the
|
||||
# handle at first use (see CaidoBootstrapHandle).
|
||||
caido_client = CaidoBootstrapHandle(
|
||||
asyncio.create_task(
|
||||
bootstrap_caido(
|
||||
session,
|
||||
host_url=host_caido_url,
|
||||
container_url=container_caido_url,
|
||||
),
|
||||
name=f"caido-bootstrap-{scan_id}",
|
||||
)
|
||||
)
|
||||
|
||||
report("Setting up the proxy")
|
||||
caido_endpoint = await session.resolve_exposed_port(_CONTAINER_CAIDO_PORT)
|
||||
scheme = "https" if caido_endpoint.tls else "http"
|
||||
host_caido_url = f"{scheme}://{caido_endpoint.host}:{caido_endpoint.port}"
|
||||
logger.debug("Caido host endpoint resolved: %s", host_caido_url)
|
||||
|
||||
# The Caido login + project setup polls the guest for a couple of seconds
|
||||
# and nothing needs the client before the first proxy tool call, so it
|
||||
# runs concurrently with the rest of scan start; consumers resolve the
|
||||
# handle at first use (see CaidoBootstrapHandle).
|
||||
caido_client = CaidoBootstrapHandle(
|
||||
asyncio.create_task(
|
||||
bootstrap_caido(
|
||||
session,
|
||||
host_url=host_caido_url,
|
||||
container_url=container_caido_url,
|
||||
),
|
||||
name=f"caido-bootstrap-{scan_id}",
|
||||
)
|
||||
)
|
||||
|
||||
bundle = {
|
||||
"client": client,
|
||||
"session": session,
|
||||
"caido_client": caido_client,
|
||||
"extra_file_staging_dir": staging_dir,
|
||||
}
|
||||
_SESSION_CACHE[scan_id] = bundle
|
||||
except BaseException:
|
||||
# Until the bundle is cached, cleanup(scan_id) cannot find the
|
||||
# staging dir, so it is removed here.
|
||||
_remove_staging_dir(staging_dir)
|
||||
raise
|
||||
bundle = {
|
||||
"client": client,
|
||||
"session": session,
|
||||
"caido_client": caido_client,
|
||||
}
|
||||
_SESSION_CACHE[scan_id] = bundle
|
||||
logger.info("Sandbox session for scan %s ready and cached", scan_id)
|
||||
return bundle
|
||||
|
||||
|
||||
def _remove_staging_dir(staging_dir: Path | None) -> None:
|
||||
if staging_dir is not None:
|
||||
shutil.rmtree(staging_dir, ignore_errors=True)
|
||||
async def _discard_session(client: Any, session: Any) -> None:
|
||||
"""Best-effort teardown of a session that never made it into the cache."""
|
||||
try:
|
||||
await client.delete(session)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.warning("Discarding a half-started sandbox session failed", exc_info=True)
|
||||
|
||||
|
||||
async def cleanup(scan_id: str) -> None:
|
||||
@@ -398,8 +391,6 @@ async def cleanup(scan_id: str) -> None:
|
||||
logger.debug("cleanup(%s): no cached session", scan_id)
|
||||
return
|
||||
|
||||
_remove_staging_dir(bundle.get("extra_file_staging_dir"))
|
||||
|
||||
caido_client = bundle.get("caido_client")
|
||||
if caido_client is not None:
|
||||
try:
|
||||
|
||||
@@ -94,6 +94,8 @@ curl https://BUCKET.s3.amazonaws.com/
|
||||
|
||||
### IAM Privilege Escalation
|
||||
|
||||
**Resource control policies:** AWS Organizations RCPs impose an organization-level limit on supported resources, including access by external principals. Map resource-account RCPs as well as principal-account SCPs, identity/resource policies, session policies, and permission boundaries when assessing a cross-account or leaked-key path. RCPs do not grant access and do not apply uniformly to every service; an identity-policy allow alone does not describe the effective permission ([AWS RCP documentation](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_rcps.html)).
|
||||
|
||||
Common escalation paths (verify with `aws iam simulate-principal-policy` when possible):
|
||||
|
||||
| Permission | Escalation |
|
||||
|
||||
@@ -32,6 +32,10 @@ Kubernetes clusters expose a large attack surface through their API server, kube
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Ingress-NGINX Admission
|
||||
|
||||
Inventory validating-webhook reachability from the pod network and the controller's service-account permissions. IngressNightmare illustrates controller execution without a Kubernetes account ([advisory](https://kubernetes.io/blog/2025/03/24/ingress-nginx-cve-2025-1974/)). Identify the controller implementation/image, then verify affected builds, backports, and maintenance status against current project or vendor notices; distinguish community ingress-nginx from other NGINX controllers and the Ingress API ([project notice](https://kubernetes.io/blog/2026/01/29/ingress-nginx-statement/)).
|
||||
|
||||
### RBAC Misconfigurations
|
||||
|
||||
- Wildcard verbs or resources in ClusterRole/Role bindings: `verbs: ["*"]`, `resources: ["*"]`
|
||||
|
||||
@@ -57,6 +57,11 @@ Map endpoints, authentication classes, and permission classes per route.
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Request and Spatial Input Handling
|
||||
|
||||
- Under ASGI, compare underscore/hyphen forms of proxy-injected identity headers through `ASGIRequest` normalization into `request.META`. Resolve the installed branch, vendor backports, and support status from package metadata and current Django release/advisory information ([header-collision advisory](https://www.djangoproject.com/weblog/2026/apr/07/security-releases/)).
|
||||
- For GeoDjango, trace attacker-controlled spatial lookup strings/dictionaries into `GDALRaster`: driver behavior can turn them into server-side requests or file writes. Include custom filter APIs and admin changelist filtering; direct model-field assignment is a separate input path ([spatial lookup advisory](https://www.djangoproject.com/weblog/2026/aug/04/security-releases/)).
|
||||
|
||||
### Authentication & Authorization
|
||||
|
||||
**Permission Class Gaps**
|
||||
@@ -207,7 +212,7 @@ Static analysis is the fastest way to reach the sinks above in white-box scope.
|
||||
- **pip-audit** (PyPA) — dependency CVE scanner for known-vuln Django/DRF/simplejwt versions: `pipx install pip-audit && pip-audit -r requirements.txt`
|
||||
- **ast-grep** (preinstalled) — quick structural grep for risky calls without a full SAST run: `ast-grep run -p 'mark_safe($X)' -l python`
|
||||
|
||||
For the `SECRET_KEY` → signed-cookie/reset-token forgery path noted under Session Issues, Django's own `django.core.signing` is the "tool": with a leaked key you can mint valid `signing.dumps()` values (session cookies, password-reset tokens, and `PickleSerializer`-backed session RCE).
|
||||
For the `SECRET_KEY` → signed-cookie forgery path noted under Session Issues, Django's own `django.core.signing` is the "tool": with a leaked key you can mint valid `signing.dumps()` values using the consumer's serializer and signing salt. Inspect `SESSION_SERIALIZER` and the installed implementation: the session-RCE path requires a reachable pickle-backed serializer; JSON sessions do not provide it. Password-reset tokens instead use `PasswordResetTokenGenerator`, with user state and timestamp in the digest, rather than the generic `signing.dumps()` format ([Django 5.0 removals](https://docs.djangoproject.com/en/5.0/releases/5.0/#features-removed-in-5-0), [token implementation](https://github.com/django/django/blob/stable/5.2.x/django/contrib/auth/tokens.py)).
|
||||
|
||||
## Summary
|
||||
|
||||
|
||||
@@ -58,6 +58,12 @@ For each route, identify:
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Starlette Request Handling
|
||||
|
||||
Resolve Starlette's version independently of FastAPI and identify the ASGI server/front proxy. Check current upstream advisories and any vendor backports for the installed build before treating a parser issue as applicable. Compare middleware authorization against the actual routed path: malformed Host values can alter reconstructed `request.url` without changing routing ([URL parsing advisory](https://github.com/Kludex/starlette/security/advisories/GHSA-86qp-5c8j-p5mr)).
|
||||
|
||||
For `request.form()`, test URL-encoded and multipart limits separately; a limit enforced on one parser may not constrain the other. Check whether crossing an upload's memory-to-disk spool threshold blocks the event loop ([form limits](https://github.com/Kludex/starlette/security/advisories/GHSA-82w8-qh3p-5jfq), [file spooling](https://github.com/Kludex/starlette/security/advisories/GHSA-2c2j-9gv5-cj73)).
|
||||
|
||||
### Authentication & Authorization
|
||||
|
||||
**Dependency Injection Gaps**
|
||||
|
||||
@@ -78,6 +78,13 @@ For each controller and method, identify:
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Adapter and Schema Version Boundaries
|
||||
|
||||
- **Express adapter:** resolve the installed Nest/Express versions and effective query-parser setting from configuration or a harmless nested-query probe. Apply `qs`/nested-operator attacks only when the parser constructs nested objects. Check the matching router documentation and test both root and child paths against authentication middleware ([migration reference](https://nestjs.io/tutorials/what-s-new-in-express-5-eeb51579)).
|
||||
- **Fastify adapter:** inspect the installed version's schema requirements and any custom validator configuration. Verify whether the route schema is actually enforced before relying on a DTO or shorthand-schema declaration ([migration reference](https://fastify.dev/docs/v5.0.x/Guides/Migration-Guide-V5/)).
|
||||
- **Standard Schema, where supported:** `@Body({ schema })`, `@Query({ schema })`, `@Param(..., { schema })`, and `@RawBody({ schema })` attach metadata; enforcement requires `StandardSchemaValidationPipe`. Probe schema-decorated routes for accepted invalid values when the pipe is absent or transport-local. Outgoing schema enforcement uses `StandardSchemaSerializerInterceptor`; check actual output rather than relying only on class-transformer decorators.
|
||||
- **GraphQL transport:** inspect installed packages, enabled IDE, and negotiated WebSocket subprotocol. Verify supported/deprecated transports against the matching Nest documentation, then recheck connection and per-operation authentication on the deployed transport ([migration guide](https://docs.nestjs.com/migration-guide)).
|
||||
|
||||
### Guard Bypass
|
||||
|
||||
**Decorator Stack Gaps**
|
||||
|
||||
@@ -12,7 +12,7 @@ Security testing for Next.js applications. Focus on authorization drift across r
|
||||
**Routers**
|
||||
- App Router (`app/`) and Pages Router (`pages/`) often coexist
|
||||
- Route Handlers (`app/api/**`) and API routes (`pages/api/**`)
|
||||
- Middleware: `middleware.ts` at project root
|
||||
- Middleware/proxy: inspect `middleware.ts`, `proxy.ts`, installed Next.js metadata, and runtime configuration. Check the matching framework docs for supported filenames, Node/Edge behavior, and feature status before selecting runtime-specific probes ([migration guide](https://nextjs.org/docs/app/guides/upgrading/version-16)).
|
||||
|
||||
**Runtimes**
|
||||
- Node.js (full API access)
|
||||
@@ -43,6 +43,8 @@ Security testing for Next.js applications. Focus on authorization drift across r
|
||||
|
||||
## Reconnaissance
|
||||
|
||||
At each assessment, establish the deployed build from package metadata, lockfiles, or runtime evidence. Verify release/support status and applicable fixes through current official docs, advisories, or upstream source; linked advisories are starting points, not a complete or permanently current list. Recheck when the target build or proposed remediation changes, and mark status unverified if evidence is unavailable.
|
||||
|
||||
**Route Discovery**
|
||||
|
||||
```javascript
|
||||
@@ -83,10 +85,15 @@ Inspect Network tab for POST requests with `Next-Action` header. Extract action
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### RSC and Image Processing
|
||||
|
||||
- **React2Shell (CVE-2025-55182):** App Router applications can expose the vulnerable RSC decoder without explicit Server Actions. Identify the bundled `react-server-dom-*` implementation and trace requests into decoding; client-only React and Pages-only apps have different exposure. Check the framework's patched branch, including source-disclosure and DoS fixes ([RCE advisory](https://react.dev/blog/2025/12/03/critical-security-vulnerability-in-react-server-components), [RSC advisories](https://react.dev/blog/2025/12/11/denial-of-service-and-source-code-exposure-in-react-server-components)).
|
||||
- Treat `/_next/image` optimization and Node `next/og` `ImageResponse` as separate input paths. Trace attacker-controlled image bytes into native decoders, and SVG content/attributes/styles into image construction. URL allowlisting does not make image contents trusted; match the decoder, runtime, and Next.js version to the relevant advisory ([optimizer](https://nextjs.org/blog/august-2026-security-release), [ImageResponse](https://github.com/vercel/next.js/security/advisories/GHSA-vcvr-r3jv-pc5j)).
|
||||
|
||||
### Middleware Bypass
|
||||
|
||||
**Known Techniques**
|
||||
- `x-middleware-subrequest` header crafting (CVE-class bypass)
|
||||
- `x-middleware-subrequest` header crafting (CVE-2025-29927): middleware-only authorization can be skipped on affected deployments. Resolve the installed branch and hosting protections from the advisory and provider configuration, then check whether external headers reach the origin and the destination enforces authorization independently ([advisory](https://github.com/vercel/next.js/security/advisories/GHSA-f82v-jwr5-mffw)).
|
||||
- `x-nextjs-data` probing
|
||||
- Look for 307 + `x-middleware-rewrite`/`x-nextjs-redirect` headers
|
||||
|
||||
@@ -118,7 +125,7 @@ Middleware checks first value, handler uses last or array.
|
||||
**Cache Boundary Failures**
|
||||
- User-bound data cached without identity keys (ETag/Set-Cookie unaware)
|
||||
- Personalized content served from shared cache/CDN
|
||||
- Missing `no-store` on sensitive fetches
|
||||
- Missing `no-store` on sensitive fetches matters only when they enter a shared cache. Determine `fetch` and GET Route Handler defaults for the deployed version from docs/configuration and observed responses. Where Cache Components are enabled, inspect `use cache` arguments/closed-over values, `cacheTag`, `cacheLife`, and invalidation for user/tenant separation ([caching documentation](https://nextjs.org/docs/app/getting-started/cache-components)).
|
||||
|
||||
**Flight Data Leakage**
|
||||
|
||||
|
||||
@@ -134,6 +134,10 @@ Parser precedence varies; may bypass validation. Also test default argument valu
|
||||
|
||||
Send unexpected keys in input objects; backends may pass them to resolvers or downstream logic.
|
||||
|
||||
### OneOf Inputs
|
||||
|
||||
Discover `@oneOf` input types through the schema or `__Type.isOneOf`. They require exactly one non-null field, with every other field omitted. Test both inline literals and variables with zero fields, two selectors, and an extra null-valued selector; all must fail coercion. Then test authorization independently for each valid selector (ID, username, organization/email), since schema exclusivity does not ensure identical tenant checks in each resolver branch. Confirm the deployed implementation supports OneOf before treating acceptance as a specification violation ([GraphQL September 2025](https://spec.graphql.org/September2025/#sec-OneOf-Input-Objects)).
|
||||
|
||||
### Cursor Manipulation
|
||||
|
||||
Decode cursors (usually base64) to:
|
||||
|
||||
@@ -77,7 +77,7 @@ com.app://callback (mobile custom scheme)
|
||||
|
||||
### State and Nonce
|
||||
|
||||
- Missing, predictable, or reusable `state` → CSRF on OAuth login (session fixation, account linking)
|
||||
- Missing, predictable, or reusable `state` → test CSRF on OAuth login (session fixation, account linking); RFC 9700 also permits correctly bound PKCE, or OIDC `nonce`, to provide CSRF protection, so establish whether that protection survives a cross-session callback
|
||||
- Missing `nonce` in OIDC → ID token injection/replay
|
||||
- `state` not bound to client session or PKCE verifier
|
||||
|
||||
@@ -102,7 +102,7 @@ com.app://callback (mobile custom scheme)
|
||||
### Scope and Token Issues
|
||||
|
||||
- Scope escalation: request `admin`/`offline_access`/`openid profile email` beyond app need; server grants all requested scopes
|
||||
- Refresh token not rotated or reuse not detected → persistent access
|
||||
- Public-client refresh tokens neither sender-constrained nor rotated with reuse detection → persistent access; a non-rotating token bound to the client's key is permitted by RFC 9700, so test replay without that key
|
||||
- Access token accepted across services (missing audience/resource binding)
|
||||
- Token introspection returns `active:true` without proper auth on introspection endpoint
|
||||
|
||||
@@ -113,6 +113,10 @@ com.app://callback (mobile custom scheme)
|
||||
- Userinfo endpoint returns PII without matching access token scope
|
||||
- `sub` collision across issuers if `iss` not validated
|
||||
|
||||
### OAuth Security BCP (RFC 9700)
|
||||
|
||||
Authorization servers must support PKCE, public clients must use it, and confidential clients are also recommended to use it. Test challenge stripping and verifier injection across both client types. Resource-owner-password grants must not be used; implicit access-token responses are discouraged because tokens can leak or be replayed. For mix-up defenses, bind the selected issuer and endpoints to the authorization transaction; validate an authorization-response `iss` when that defense is used, rather than merely checking the issuer of a later ID token ([RFC 9700](https://www.rfc-editor.org/rfc/rfc9700.html)).
|
||||
|
||||
## Advanced Techniques
|
||||
|
||||
**Referer Leakage**
|
||||
|
||||
@@ -100,6 +100,13 @@ certipy find -u <USER>@<DOMAIN> -p <PASS> -dc-ip <DC_IP> -vulnerable -stdout
|
||||
- **ESC8** — NTLM relay to the CA web-enrollment endpoint (coerce a DC, relay to `/certsrv`) → DC certificate → DCSync.
|
||||
- **ESC others** — ESC2/3 (any-purpose/enrollment-agent), ESC4 (writable template DACL → make it ESC1), ESC6 (`EDITF_ATTRIBUTESUBJECTALTNAME2` on the CA), ESC7 (CA officer rights), ESC9/10 (weak cert mapping), ESC11 (RPC relay), ESC13 (issuance-policy→group), ESC15 (app-policy on v1 templates). `certipy find -vulnerable` flags each.
|
||||
|
||||
**Strong certificate mapping:** establish the DC patch level, mapping policy, and vendor backports; consult Microsoft's current enforcement guidance before assuming Compatibility mode is available. Inspect template SID extensions, explicit mappings, and the principal selected at authentication. A requested privileged UPN alone does not establish a working ESC chain ([KB5014754](https://support.microsoft.com/en-us/servicing/os/windows-server/2022/05/kb5014754-certificate-based-authentication-changes-on-windows-domain-controllers)).
|
||||
|
||||
### Windows Server 2025 dMSA / BadSuccessor
|
||||
|
||||
- **CVE-2025-53779:** on unpatched Server 2025 DCs, control sufficient to create/modify a delegated Managed Service Account can establish a one-way migration link to another principal and obtain its authority/keys through the KDC. Enumerate dMSA creation rights, object ACLs, and migration attributes rather than limiting discovery to conventional service accounts and delegation flags.
|
||||
- **Patched behavior:** the KDC requires mutual dMSA↔target linkage. Writing the dMSA-side attribute still succeeds, so LDAP write success is not proof of exploitation. Determine whether the tester also controls the target object's reciprocal link and whether the KDC actually issues the relevant ticket. Post-patch abuse is a different prerequisite chain from the original low-privilege OU-control escalation ([researchers' patch analysis](https://www.akamai.com/blog/security-research/badsuccessor-is-dead-analyzing-badsuccessor-patch)).
|
||||
|
||||
### NTLM Coercion & Relay
|
||||
|
||||
Force a privileged machine to authenticate to you, then relay that NTLM auth to a service that doesn't enforce signing/EPA (LDAP, AD CS, SMB).
|
||||
|
||||
@@ -86,12 +86,14 @@ Authorization: Bearer <token_with_audience_A>
|
||||
|
||||
### Rules and Actions Abuse
|
||||
|
||||
Inventory which Rules, Hooks, and Actions actually execute in the tenant. Check the current Auth0 lifecycle notice and tenant capabilities for retirement or read-only restrictions. During migration, compare claim assignment, MFA, denial decisions, and account-linking checks across every connection; distinguish secret/configuration access from source-code modification ([lifecycle notice](https://auth0.com/docs/troubleshoot/product-lifecycle/deprecations-and-migrations#rules-and-hooks-deprecations)).
|
||||
|
||||
**Post-Login Rule/Action Injection**
|
||||
- Rules that add claims based on unvalidated user metadata:
|
||||
```javascript
|
||||
user.app_metadata.role = 'admin' // if user can set app_metadata via signup/API
|
||||
```
|
||||
- `context.authorization` manipulation in Actions
|
||||
- Rules use `context`; Actions receive `event` and enforce token/MFA/denial changes through `api` methods. Test migrated checks that only mutate event data: those mutations do not propagate to other Actions or substitute for the corresponding enforcement API ([migration behavior](https://auth0.com/docs/customize/actions/migrate/migrate-from-rules-to-actions)).
|
||||
- Secrets in Rule code exposed to tenant admins or via Management API leak
|
||||
|
||||
**Signup / Registration Actions**
|
||||
|
||||
@@ -79,6 +79,13 @@ Unauthenticated view (and, with `public_mode`, delete) of the lowest-key snapsho
|
||||
### Prometheus / Alertmanager — exposure is the vuln (no auth by default)
|
||||
Prometheus and Alertmanager ship with **no authentication**; the docs explicitly say do not expose them. There is rarely a CVE — reachability itself is the finding, and the payoff is recon + credential leakage + pivoting (below).
|
||||
|
||||
## Plugin and MCP Boundaries
|
||||
|
||||
Resolve installed Grafana, plugin, and MCP server builds separately; check current upstream advisories and vendor backports before applying a listed attack path or recommending a release.
|
||||
|
||||
- Plugin archives are extracted before signature verification. Test chained symlinks and containment before trusting a signature failure to prevent filesystem writes; archive installation is the required trigger ([extraction advisory](https://grafana.com/security/security-advisories/cve-2026-15815/)).
|
||||
- Inventory mcp-grafana separately from Grafana. Trace `grafana_api_request` and `X-Grafana-URL` into destination selection; preventing token forwarding alone does not prevent SSRF ([MCP advisory](https://grafana.com/security/security-advisories/cve-2026-19516/)).
|
||||
|
||||
## Pivoting: Observability → Deeper Compromise
|
||||
|
||||
This is the core value. Chain each exposure into something that matters. Always articulate the pivot in the finding, not just the exposed endpoint.
|
||||
|
||||
@@ -78,11 +78,11 @@ Record both forward and reverse reachability: attacker-controlled input to privi
|
||||
|
||||
## Optional Tool Routing
|
||||
|
||||
Use tools only when they match the deployed surface. Treat generated cases and scanner labels as leads until the application-side boundary is validated.
|
||||
Use tools only when they match the deployed surface. Resolve compatible versions, runtime requirements, maintenance status, and advisories from registry metadata and current upstream documentation, then pin the exact reviewed versions for the run. Treat generated cases and scanner labels as leads until the application-side boundary is validated.
|
||||
|
||||
- **[Promptfoo](https://github.com/promptfoo/promptfoo)** — use for repeatable model/application trials, custom adversarial cases, graders, provider comparisons, and success-rate regression. Install the reviewed version locally with `npm install --save-dev --save-exact promptfoo@0.122.0`, then invoke `./node_modules/.bin/promptfoo redteam run`. Define explicit plugins, assertions, `numTests`, `maxConcurrency`, and `delay`; provider calls may transmit test data and incur cost. Its `owasp:llm` preset still uses the 2025 category mapping in version 0.122.0, so build or select tests from the 2026 matrix above and do not present the preset report as complete 2026 coverage.
|
||||
- **[MCP Inspector](https://github.com/modelcontextprotocol/inspector)** — use for LLM01/LLM03 surface mapping when MCP servers are present. Install the reviewed version with `npm install --save-dev --save-exact @modelcontextprotocol/inspector@2.2.0`, then use `./node_modules/.bin/mcp-inspector --cli --config <reviewed-config> --server <name> --method tools/list` and the equivalent `resources/list` / `prompts/list` operations. Starting a stdio server executes that configured process, initialization/list handlers may have side effects, and `tools/call` can perform the real action; inspect the target and credentials before invoking it.
|
||||
- **[ModelScan](https://github.com/protectai/modelscan)** — use for LLM04 static triage of supported H5, Pickle, and SavedModel artifacts before loading them, for example `uvx modelscan==0.8.8 -p <artifact>`. Run it as an untrusted-file parser in an isolated analysis environment. A clean result covers only the scanner's supported formats and signatures; it does not establish artifact provenance, integrity, or absence of behavioral backdoors.
|
||||
- **[Promptfoo](https://github.com/promptfoo/promptfoo)** — use for repeatable model/application trials, custom adversarial cases, graders, provider comparisons, and success-rate regression. Install the reviewed version locally with `npm install --save-dev --save-exact promptfoo@<reviewed-version>`, then invoke `./node_modules/.bin/promptfoo redteam run`. Define explicit plugins, assertions, `numTests`, `maxConcurrency`, and `delay`; provider calls may transmit test data and incur cost. Inspect the installed `owasp:llm` preset's category mapping and actual tests before claiming coverage; add cases for gaps against the assessment's chosen taxonomy.
|
||||
- **[MCP Inspector](https://github.com/modelcontextprotocol/inspector)** — use for LLM01/LLM03 surface mapping when MCP servers are present. Install the reviewed version with `npm install --save-dev --save-exact @modelcontextprotocol/inspector@<reviewed-version>`, then use `./node_modules/.bin/mcp-inspector --cli --config <reviewed-config> --server <name> --method tools/list` and the equivalent `resources/list` / `prompts/list` operations. Starting a stdio server executes that configured process, initialization/list handlers may have side effects, and `tools/call` can perform the real action; inspect the target and credentials before invoking it.
|
||||
- **[ModelScan](https://github.com/protectai/modelscan)** — use for LLM04 static triage of supported H5, Pickle, and SavedModel artifacts before loading them, for example `uvx modelscan==<reviewed-version> -p <artifact>`. Run it as an untrusted-file parser in an isolated analysis environment. A clean result covers only the scanner's supported formats and signatures; it does not establish artifact provenance, integrity, or absence of behavioral backdoors.
|
||||
|
||||
## LLM01:2026 Prompt Injection
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Security testing for Supabase applications. Focus on mis-scoped Row Level Securi
|
||||
- Functions: `https://<ref>.functions.supabase.co/`
|
||||
|
||||
**Headers**
|
||||
- `apikey: <anon-or-service>` — identifies project
|
||||
- `apikey: <publishable-or-secret-key>` — identifies the application component; legacy `anon` / `service_role` JWT keys can still coexist
|
||||
- `Authorization: Bearer <JWT>` — binds user context
|
||||
|
||||
**Roles**
|
||||
@@ -45,6 +45,13 @@ Security testing for Supabase applications. Focus on mis-scoped Row Level Securi
|
||||
**Key Principle**
|
||||
`auth.uid()` returns current user UUID from JWT. Policies must never trust client-supplied IDs over server context.
|
||||
|
||||
### API Keys and Signing Keys
|
||||
|
||||
- Opaque keys start with `sb_publishable_` or `sb_secret_`; JWT decoding will not classify them. Search bundles, server configuration, and responses for both formats. Publishable keys are intended for public clients; secret keys use `service_role` and bypass RLS. A browser 401 for a leaked secret key is not revocation: the browser restriction uses `User-Agent` and does not stop server-side use.
|
||||
- Creating new keys does not disable legacy keys. After migration, test whether the old `anon` / `service_role` credential remains accepted; dashboard revocation is a separate action. Verify supported key formats and migration/deprecation status from project settings and current Supabase documentation ([API keys](https://supabase.com/docs/guides/getting-started/api-keys)).
|
||||
- Edge Functions use user JWTs in `Authorization` and API keys in `apikey`. Verify the deployed platform's `verify_jwt` behavior with current docs and controlled requests, including API keys on either header; passing a platform check with a publishable key does not establish a signed-in user. Test whether the handler verifies user identity before using a privileged client. Conversely, `verify_jwt=false` with explicit secret-key or webhook verification is not necessarily public. Trace the configured `@supabase/server` auth mode or custom verification ([function authorization](https://supabase.com/docs/guides/functions/auth-headers)).
|
||||
- Asymmetric signing keys are discoverable at `/auth/v1/.well-known/jwks.json`. Test custom API/Edge Function validators for issuer/algorithm binding and stale cached keys after rotation/revocation. Measure cache lifetimes from response headers, SDK configuration, and controlled rotation tests; check service-specific revocation behavior in current documentation rather than assuming platform and custom validators share a cache ([signing keys](https://supabase.com/docs/guides/auth/signing-keys)).
|
||||
|
||||
## High-Value Targets
|
||||
|
||||
- Tables with sensitive data (users, orders, payments, PII)
|
||||
|
||||
@@ -106,10 +106,16 @@ Classify each discovered integration by data read, data write, external communic
|
||||
- For each tool, validate the same authorization and argument checks through every supported transport.
|
||||
- Treat server-launched subprocess configuration, environment variables, and working directories as sensitive executable configuration.
|
||||
- For HTTP/SSE transports, validate OAuth issuer, signature, expiry, audience/resource, tenant, and scope claims at the server boundary. Reject tokens minted for the wrong audience, and do not treat a session ID as identity.
|
||||
- For downstream APIs, do not pass through the same bearer token unless the target explicitly authorizes that audience and principal. Separate upstream MCP authentication from downstream target authorization.
|
||||
- MCP forbids token passthrough: reject access tokens not issued for the MCP server, and use a separate downstream authorization flow instead of forwarding a client's token to another API ([MCP authorization](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)).
|
||||
- For browser or loopback OAuth, review redirect URI, state/PKCE handling, localhost binding, and consent proxying. Treat metadata fetches and tool discovery on remote servers as SSRF-relevant surfaces.
|
||||
- For stdio servers, the launch command and environment are already code execution. Discovery must not execute an unreviewed server binary or mutable package tag.
|
||||
|
||||
#### MCP Protocol Boundaries
|
||||
|
||||
- Identify the protocol revision and client/server SDK builds from configuration and wire traffic, then consult the matching specification and current SDK advisories. Determine whether the transport uses initialization/session IDs or independent requests with client metadata and discovery. Test authorization on every request; client-supplied identity/capabilities in `_meta` are not authenticated identity.
|
||||
- Where multi-round tool requests use `input_required` and `inputResponses`, trace the retry/resume binding. Test whether a response from another user, tool invocation, or approval round can be substituted, and whether a retry repeats a consequential action ([MCP protocol specification](https://blog.modelcontextprotocol.io/posts/2026-07-28/)).
|
||||
- For OAuth callbacks, bind the expected issuer from validated discovery to the PKCE transaction. A present `iss` must match exactly, even if metadata did not advertise support; an absent `iss` must be rejected when `authorization_response_iss_parameter_supported` is true. Test issuer changes between discovery, registration, and callback, including error callbacks. Bind client credentials to their authorization-server issuer. Require the MCP server's `resource` in authorization and token requests and validate the token's audience at the server ([authorization requirements](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)).
|
||||
|
||||
### Executable Component Supply Chain
|
||||
|
||||
Every skill, plugin, MCP server, model adapter, package, and update channel is an executable or behavior-shaping dependency. Record:
|
||||
@@ -158,7 +164,7 @@ npx @modelcontextprotocol/inspector@<reviewed-version> --cli \
|
||||
--method tools/list --format json
|
||||
```
|
||||
|
||||
- Current upstream requirements should be checked before pinning; as of August 12, 2026, MCP Inspector 2.1.0 requires Node.js `>=22.19.0`.
|
||||
- Before choosing MCP Inspector, inspect package-registry metadata, engine requirements, release notes, and advisories. Select a compatible reviewed version and pin that exact version for the run; do not infer safety from a release tag.
|
||||
- Prefer CLI/TUI and loopback binding over exposing the web UI.
|
||||
- Preserve the generated API token; never disable authentication or bind the process-spawning backend to an external interface.
|
||||
- Do not publish ports 6274/6277 or pass through the Docker socket/host devices.
|
||||
@@ -173,7 +179,7 @@ npx @modelcontextprotocol/inspector@<reviewed-version> --cli \
|
||||
npx promptfoo@<reviewed-version> eval
|
||||
```
|
||||
|
||||
- Current upstream engine constraints should be checked before pinning; as of August 12, 2026, Promptfoo documents Node.js `^20.20.0` or `>=22.22.0`.
|
||||
- Resolve Promptfoo's runtime requirements, supported features, and security status from registry metadata and current upstream docs before choosing and pinning a version.
|
||||
- Use synthetic prompts/data and a dedicated test provider/project.
|
||||
- Provider calls transmit data externally and can incur cost even when evaluation orchestration is local. Set request/concurrency and spending ceilings.
|
||||
- Pin model, provider, prompt, tool schema, retrieval corpus revision, and evaluator versions.
|
||||
|
||||
@@ -104,6 +104,12 @@ Prove the strongest reliable capability first. If escalation requires a user ges
|
||||
- Treat scriptless disclosure of a nonce or trusted URL as a primitive; prove a second controllable sink before claiming bypass.
|
||||
- For response splitting, consider whether a same-origin endpoint can be turned into a script resource with a controlled body length or framing.
|
||||
|
||||
### Local Network and Loopback Access
|
||||
|
||||
Identify the browser build, enabled flags, permission state, and enterprise policy; consult its current Local Network Access documentation and verify actual behavior with controlled requests. Test DNS rebinding and browser-to-local-service chains in both permission-denied and permission-granted states. A successful server-side request does not establish browser reachability ([Chrome LNA](https://developer.chrome.com/blog/local-network-access)).
|
||||
|
||||
Discover which permission names and aliases the target supports, including `local-network`, `loopback-network`, and `local-network-access`. Record which destination class was permitted before claiming access to both LAN and localhost services ([permission reference](https://developer.chrome.com/release-notes/145#local_network_access_split_permissions)).
|
||||
|
||||
### JavaScript Gadget Discovery
|
||||
|
||||
- When direct calls are blocked, inspect implicit coercions (`toString`, `valueOf`, iterators, getters, proxies) and callbacks invoked by accessible library functions.
|
||||
|
||||
@@ -97,6 +97,12 @@ transfer-encoding: chunked
|
||||
SMUGGLED
|
||||
```
|
||||
|
||||
### CL.0, 0.CL, and Double Desync
|
||||
|
||||
In CL.0, the front end honors `Content-Length` while the back end ignores the body; in 0.CL the front end ignores it while the back end expects it. The latter commonly deadlocks until an early-response gadget responds without consuming the body and keeps the connection open. Test redirects and early errors for that specific connection behavior. A subsequent controlled request can expose the boundary shift; two successive desyncs can convert 0.CL into CL.0. A lone timeout or 400 does not establish that chain.
|
||||
|
||||
Include `Expect: 100-continue` handling and interim/final response sequencing in differential tests. Calculate offsets from what the back end receives, including proxy-added headers. Use controlled follow-up requests to establish which bytes are consumed and which response belongs to each request ([HTTP/1.1 must die](https://portswigger.net/research/http1-must-die), [0.CL walkthrough](https://portswigger.net/blog/http-1-1-must-die-conquering-the-0-cl-challenge)).
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### Front-End Security Control Bypass
|
||||
|
||||
@@ -79,6 +79,8 @@ JNDI injection is not itself a serialization format. It becomes part of this wor
|
||||
|
||||
### Python Pickle
|
||||
|
||||
**Model checkpoints:** `torch.load(..., weights_only=True)` does not rule out parser/storage memory corruption. Resolve the installed loader build and check current upstream advisories/backports before trusting that flag. Trace untrusted checkpoints through the exact loader and inspect safe-global allowlists ([example advisory](https://github.com/pytorch/pytorch/security/advisories/GHSA-63cw-57p8-fm3p)).
|
||||
|
||||
Pickle executes arbitrary code during unpickling by design:
|
||||
```python
|
||||
import pickle, os, base64
|
||||
@@ -101,13 +103,15 @@ When `yaml.load` used instead of `yaml.safe_load`.
|
||||
- POP chains through framework classes (Laravel, Symfony, WordPress plugins)
|
||||
|
||||
**Phar Deserialization**
|
||||
- Upload or reference `phar://` wrapper triggering metadata deserialization on file operations
|
||||
- Trace `phar://` file operations and explicit `Phar::getMetadata()` / `PharFileInfo::getMetadata()` calls. Verify the installed PHP version's metadata-deserialization behavior and `allowed_classes` handling from source/docs; archive opening alone does not establish a deserialization sink ([migration reference](https://www.php.net/manual/en/migration80.incompatible.php#migration80.incompatible.phar)).
|
||||
|
||||
### .NET Deserialization
|
||||
|
||||
**BinaryFormatter / LosFormatter**
|
||||
- Never safe on untrusted input; full RCE with known gadget chains (ysoserial.net)
|
||||
|
||||
Inspect the target framework and resolved serialization packages to establish whether `BinaryFormatter` executes, throws, or is restored through a compatibility package. Check the matching runtime documentation before choosing gadget chains; `LosFormatter` and other serializers are separate surfaces ([Microsoft guide](https://learn.microsoft.com/en-us/dotnet/standard/serialization/binaryformatter-migration-guide/)).
|
||||
|
||||
**Json.NET**
|
||||
```json
|
||||
{"$type":"System.Windows.Data.ObjectDataProvider, PresentationFramework", ...}
|
||||
@@ -193,7 +197,7 @@ Payload generation is the practitioner's core tool here. The sandbox has `git`/`
|
||||
| **ysoserial** (frohoff) | Java native | Gadget-chain payloads: `CommonsCollections1-7`, `Groovy1`, `Spring1/2`, and `URLDNS` for a safe no-exec DNS oracle. Needs a JRE. |
|
||||
| **phpggc** (ambionics) | PHP `unserialize` / Phar | Framework POP chains (Laravel, Symfony, WordPress, Drupal, Monolog). Needs `php-cli`. |
|
||||
| **ysoserial.net** | .NET `BinaryFormatter` / Json.NET | Windows/.NET gadget payloads. Needs .NET/mono — usually out of scope in a Linux sandbox. |
|
||||
| **marshalsec** | Java Hessian/Burlap, Kryo, JSON, and JNDI reference tooling | Use only from a reviewed, pinned upstream commit when a non-native Java marshaller requires it. It has no stable release and intentionally bundles historical gadget dependencies; do not treat it as a globally installed default tool. |
|
||||
| **marshalsec** | Java Hessian/Burlap, Kryo, JSON, and JNDI reference tooling | Use only from a reviewed, pinned upstream commit when a non-native Java marshaller requires it. Check upstream release status and bundled gadget dependencies before selecting a commit; do not treat it as a globally installed default tool. |
|
||||
|
||||
```
|
||||
# Java: prove the sink with a no-exec DNS oracle BEFORE any RCE chain
|
||||
|
||||
@@ -91,7 +91,7 @@ Binary search the character space to minimize requests. Works on any string fiel
|
||||
|
||||
### `$where` JavaScript Injection
|
||||
|
||||
If `$where` operator is enabled (disabled by default in MongoDB 7.0+; MongoDB 4.4–6.x deprecated it but left `javascriptEnabled` defaulting to `true`), inject arbitrary server-side JavaScript:
|
||||
If `$where` is enabled, inject server-side JavaScript. Inspect `security.javascriptEnabled` / `--noscripting`, managed-service restrictions, and the installed engine version. Verify operator availability, defaults, and supported functions against that build's documentation or controlled probes; deprecation alone does not mean execution is disabled ([MongoDB documentation](https://www.mongodb.com/docs/manual/reference/operator/query/where/)):
|
||||
```json
|
||||
{"$where": "function(){return this.role == 'admin'}"} // direct filter — returns matching documents
|
||||
{"$where": "function(){return this.username == 'admin' && sleep(2000)}"} // timing oracle only — sleep() returns undefined (falsy), so no documents are returned; observe latency
|
||||
|
||||
@@ -148,6 +148,8 @@ Improper file path handling and dynamic inclusion enable sensitive file disclosu
|
||||
- Verify symlink handling and path canonicalization prior to write
|
||||
- Impact: overwrite config/templates or drop webshells into served directories
|
||||
|
||||
**Python extraction filters:** Determine the effective `filter` and `TarFile.extraction_filter` from the installed Python runtime, caller configuration, and matching documentation; verify patch/backport status against current advisories ([documentation](https://docs.python.org/3/library/tarfile.html#extraction-filters)). Filters do not eliminate link-handling bugs: test archive link ordering and post-extraction reads for outside-file disclosure or permission/timestamp changes, separately from content overwrite ([security advisory](https://mail.python.org/archives/list/[email protected]/thread/EFJWGAZJA56AKSBR2WHMHQZO7RRLZPRH/)).
|
||||
|
||||
### File Write to Execution
|
||||
|
||||
Characterize the write primitive before choosing a payload:
|
||||
|
||||
@@ -51,7 +51,7 @@ Prototype pollution corrupts shared object prototypes (`Object.prototype`, `Arra
|
||||
**Common Sinks**
|
||||
- `lodash.merge`, `lodash.defaultsDeep`, `deep-extend`, `merge-options`
|
||||
- Express/query parsers accepting nested objects
|
||||
- YAML `load()` (not `safeLoad`) with prototype keys
|
||||
- YAML merge-key handling with prototype keys: identify the js-yaml version/schema and check applicable upstream advisories. Test whether `<<` merges alter the parsed result's prototype, then trace inherited values into a sensitive consumer; global `Object.prototype` modification is not required. Verify the installed API rather than assuming `load` versus `safeLoad` determines safety ([merge advisory](https://github.com/nodeca/js-yaml/security/advisories/GHSA-mh29-5h37-fv8m)).
|
||||
- JSON.parse → merge into existing object without null prototype
|
||||
|
||||
**RCE Gadget Chains (Node.js)**
|
||||
|
||||
@@ -76,6 +76,8 @@ When output isn't reflected:
|
||||
|
||||
### Jinja2 / Mako (Python)
|
||||
|
||||
**Jinja sandbox:** resolve the installed Jinja build and check current sandbox advisories before selecting indirect `str.format` or `|attr` gadgets. Establish template-source control and inspect custom filters; user data passed only as a variable is a different surface ([release notes](https://jinja.palletsprojects.com/en/stable/changes/)).
|
||||
|
||||
The classic Python class walk — every object exposes its method-resolution-order, which leads to `object`, which exposes every subclass loaded in the interpreter, which includes things like `subprocess.Popen`:
|
||||
|
||||
```jinja
|
||||
@@ -137,7 +139,7 @@ Twig sandbox bypasses are version-specific. The canonical historical gadget (Twi
|
||||
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id")}}
|
||||
```
|
||||
|
||||
This was patched — in Twig 2.x / 3.x `_self` returns the template name as a string and no longer exposes `.env`. Modern bypasses depend on which extensions are loaded and the active sandbox policy; consult Twig's published security advisories for the current state and probe with the version-specific gadgets (filter/function abuse, reflection on `_context` in some configs).
|
||||
In Twig 2.x / 3.x, `_self` returns the template name as a string and does not expose `.env`. Bypasses depend on which extensions are loaded and the active sandbox policy; consult Twig's published security advisories and probe with the version-specific gadgets (filter/function abuse, reflection on `_context` in some configs).
|
||||
|
||||
Smarty `{php}...{/php}` was the historical RCE primitive; deprecated in Smarty 3 and removed in 4. On modern Smarty, the surface is static-method invocation and template-object reflection — `{$smarty.template_object->smarty->...}` walks back to the Smarty engine, and direct static calls on whitelisted classes (e.g. `{Smarty_Internal_Write_File::writeFile(...)}` on misconfigured installs) reach the filesystem. Probe both before assuming Smarty is hardened.
|
||||
|
||||
|
||||
@@ -49,13 +49,15 @@ Weak or default credentials remain one of the most prevalent and high-impact vul
|
||||
|
||||
### Weak Password Policies
|
||||
|
||||
- No minimum length or complexity requirements
|
||||
- Insufficient minimum length for the authentication mode; NIST SP 800-63B-4 requires 15 characters for single-factor passwords and permits a minimum of eight when the password is only used as part of MFA
|
||||
- Allowing common passwords: `password`, `123456`, `qwerty`, `admin`, `letmein`
|
||||
- Not checking against breached password databases (Have I Been Pwned)
|
||||
- Case-insensitive password storage
|
||||
- No password history enforcement
|
||||
- Forced periodic changes without compromise evidence, which encourage predictable password changes
|
||||
- Excessively short maximum length (indicates plaintext or weak hashing)
|
||||
|
||||
NIST SP 800-63B-4 recommends allowing a maximum of at least 64 characters and requires screening against common/compromised passwords and checking the entire password without truncation. Missing character-class rules is not a weakness: the standard prohibits mandatory composition rules and periodic resets without evidence of compromise. Apply these requirements when that standard is the target's policy baseline ([NIST SP 800-63B-4](https://pages.nist.gov/800-63-4/sp800-63b.html#passwordver)).
|
||||
|
||||
### Default and Hardcoded Credentials
|
||||
|
||||
- Vendor defaults: `admin/admin`, `admin/password`, `root/root`, `guest/guest`
|
||||
@@ -191,7 +193,7 @@ No password wordlists ship in the sandbox by default — download what you need
|
||||
5. Test for password spraying (one password, many users) before targeted brute-force
|
||||
6. Check for concurrent session limits; successful logins may kick out legitimate users
|
||||
7. GraphQL batching can test multiple credentials in a single request, bypassing per-request limits
|
||||
8. Document the password policy and recommend minimum standards (length, complexity, breach checking)
|
||||
8. Document the password policy and recommend minimum standards (length appropriate to single-factor/MFA use, breach checking, and resistance to online guessing)
|
||||
9. For web logins prefer `ffuf`; for other services use `nmap` NSE `*-brute` scripts or custom scripts with equivalent logic
|
||||
10. Combine with MFA testing: weak passwords plus missing MFA is a critical finding
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ Cross-site scripting persists because context, parser, and framework edges are c
|
||||
|
||||
## Key Vulnerabilities
|
||||
|
||||
### DOMPurify Live-Node Sanitization
|
||||
|
||||
Distinguish HTML-string input from live DOM objects sanitized with `IN_PLACE`. Resolve the deployed DOMPurify build and check relevant upstream advisories; a controlled observable `nodeName` is one live-node bypass mechanism. Trace same-origin foreign nodes or `adoptNode()` into this mode before selecting the bypass ([example advisory](https://github.com/cure53/DOMPurify/security/advisories/GHSA-x4vx-rjvf-j5p4)).
|
||||
|
||||
### DOM XSS
|
||||
|
||||
**Sources**
|
||||
|
||||
@@ -12,7 +12,7 @@ Privacy is our priority. All collected data is anonymized by default. Each sessi
|
||||
|
||||
We collect only very **basic** usage data including:
|
||||
|
||||
**Session Errors:** Duration and error types (not messages or stack traces)\
|
||||
**Session Errors:** Duration, the failure category, the scan phase, and the exception class name (not messages or stack traces)\
|
||||
**System Context:** OS type, architecture, Strix version\
|
||||
**Scan Context:** Scan mode (quick/standard/deep), scan type (whitebox/blackbox)\
|
||||
**Model Usage:** Which LLM model is being used and whether it runs via an API key or a model subscription (not prompts or responses)\
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
from . import posthog, scarf
|
||||
from ._common import set_scan_phase
|
||||
|
||||
|
||||
def report_error(error_type: str, exc: BaseException | None = None) -> None:
|
||||
"""Beacon a failure category, plus the exception class when one is given.
|
||||
|
||||
Only class names travel: never the message, arguments, or traceback.
|
||||
"""
|
||||
posthog.error(error_type, exc)
|
||||
scarf.error(error_type, exc)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"posthog",
|
||||
"report_error",
|
||||
"scarf",
|
||||
"set_scan_phase",
|
||||
]
|
||||
@@ -5,7 +5,7 @@ import platform
|
||||
import sys
|
||||
from importlib.metadata import PackageNotFoundError, version
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, cast
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
@@ -54,3 +54,43 @@ def base_props() -> dict[str, Any]:
|
||||
"python": f"{sys.version_info.major}.{sys.version_info.minor}",
|
||||
"strix_version": get_version(),
|
||||
}
|
||||
|
||||
|
||||
# Coarse stage of the current run, attached to ``error`` beacons so a failure
|
||||
# can be placed without a message or trace. Process-local, like the rest of the
|
||||
# CLI telemetry: one process runs one scan.
|
||||
_scan_phase = "startup"
|
||||
|
||||
|
||||
def set_scan_phase(phase: str) -> None:
|
||||
global _scan_phase # noqa: PLW0603
|
||||
_scan_phase = phase
|
||||
|
||||
|
||||
def get_scan_phase() -> str:
|
||||
return _scan_phase
|
||||
|
||||
|
||||
def _exception_name(exc: BaseException) -> str:
|
||||
cls = type(exc)
|
||||
package = cls.__module__.split(".")[0]
|
||||
return cls.__name__ if package == "builtins" else f"{package}.{cls.__name__}"
|
||||
|
||||
|
||||
def _unwrap_group(exc: BaseException) -> BaseException:
|
||||
if not isinstance(exc, BaseExceptionGroup):
|
||||
return exc
|
||||
group = cast("BaseExceptionGroup[BaseException]", exc)
|
||||
return group.exceptions[0] if group.exceptions else group
|
||||
|
||||
|
||||
def exception_props(exc: BaseException) -> dict[str, str]:
|
||||
"""Class names only. Messages, arguments, and tracebacks never leave the machine."""
|
||||
exc = _unwrap_group(exc)
|
||||
props = {"exception_type": _exception_name(exc)}
|
||||
cause = exc.__cause__
|
||||
if cause is None and not exc.__suppress_context__:
|
||||
cause = exc.__context__
|
||||
if cause is not None:
|
||||
props["exception_cause"] = _exception_name(cause)
|
||||
return props
|
||||
@@ -9,6 +9,8 @@ from strix.telemetry._common import (
|
||||
SEND_TIMEOUT,
|
||||
SESSION_ID,
|
||||
base_props,
|
||||
exception_props,
|
||||
get_scan_phase,
|
||||
get_version,
|
||||
is_first_run,
|
||||
)
|
||||
@@ -116,6 +118,7 @@ def end(report_state: "ReportState", exit_reason: str = "completed") -> None:
|
||||
}
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
pass
|
||||
providers = report_state.get_process_llm_providers()
|
||||
|
||||
report_state.posthog_scan_ended_sent = _send(
|
||||
"scan_ended",
|
||||
@@ -127,6 +130,7 @@ def end(report_state: "ReportState", exit_reason: str = "completed") -> None:
|
||||
"vulnerabilities_total": len(report_state.vulnerability_reports),
|
||||
**{f"vulnerabilities_{k}": v for k, v in vulnerabilities_counts.items()},
|
||||
**llm_props,
|
||||
**({"llm_providers": providers} if providers else {}),
|
||||
"skills": get_loaded_skill_names(),
|
||||
},
|
||||
)
|
||||
@@ -178,6 +182,12 @@ def viewer_agent_steered() -> None:
|
||||
_send("viewer_agent_steered", {**base_props()})
|
||||
|
||||
|
||||
def error(error_type: str) -> None:
|
||||
props = {**base_props(), "error_type": error_type}
|
||||
def error(error_type: str, exc: BaseException | None = None) -> None:
|
||||
props: dict[str, Any] = {
|
||||
**base_props(),
|
||||
"error_type": error_type,
|
||||
"phase": get_scan_phase(),
|
||||
}
|
||||
if exc is not None:
|
||||
props.update(exception_props(exc))
|
||||
_send("error", props)
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import urllib.parse
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -12,6 +13,8 @@ from strix.telemetry._common import (
|
||||
SEND_TIMEOUT,
|
||||
SESSION_ID,
|
||||
base_props,
|
||||
exception_props,
|
||||
get_scan_phase,
|
||||
get_version,
|
||||
is_first_run,
|
||||
)
|
||||
@@ -118,6 +121,7 @@ def end(report_state: ReportState, exit_reason: str = "completed") -> None:
|
||||
}
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
pass
|
||||
providers = report_state.get_process_llm_providers()
|
||||
|
||||
report_state.scarf_scan_ended_sent = _send(
|
||||
"scan_ended",
|
||||
@@ -130,15 +134,20 @@ def end(report_state: ReportState, exit_reason: str = "completed") -> None:
|
||||
"vulnerabilities_total": len(report_state.vulnerability_reports),
|
||||
**{f"vulnerabilities_{k}": v for k, v in vulnerabilities_counts.items()},
|
||||
**llm_props,
|
||||
# Query params are flat, so the per-provider tally travels as JSON.
|
||||
**({"llm_providers": json.dumps(providers)} if providers else {}),
|
||||
"skills": ",".join(get_loaded_skill_names()),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def error(error_type: str) -> None:
|
||||
def error(error_type: str, exc: BaseException | None = None) -> None:
|
||||
props: dict[str, Any] = {
|
||||
**base_props(),
|
||||
"session": SESSION_ID,
|
||||
"error_type": error_type,
|
||||
"phase": get_scan_phase(),
|
||||
}
|
||||
if exc is not None:
|
||||
props.update(exception_props(exc))
|
||||
_send("error", props)
|
||||
@@ -2,7 +2,13 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from strix.tools.mcp.agent_tools import call_mcp, describe_mcp, list_mcps
|
||||
from strix.tools.mcp.agent_tools import (
|
||||
call_mcp,
|
||||
describe_mcp,
|
||||
get_mcp_tool_schema,
|
||||
list_mcps,
|
||||
search_mcp_tools,
|
||||
)
|
||||
from strix.tools.mcp.client import (
|
||||
ConnectedMcpServer,
|
||||
attach_mcp_requests,
|
||||
@@ -19,8 +25,10 @@ from strix.tools.mcp.naming import namespaced_tool_name
|
||||
from strix.tools.mcp.registry import (
|
||||
CALL_MCP_TOOL,
|
||||
DESCRIBE_MCP_TOOL,
|
||||
GET_MCP_TOOL_SCHEMA_TOOL,
|
||||
MCP_DISPATCH_TOOLS,
|
||||
MCP_REGISTRY_CONTEXT_KEY,
|
||||
SEARCH_MCP_TOOLS_TOOL,
|
||||
McpCallInfo,
|
||||
McpConnectionEntry,
|
||||
McpConnectionRequest,
|
||||
@@ -35,8 +43,10 @@ from strix.tools.mcp.session import McpConnectionUnavailableError, SupervisedMcp
|
||||
__all__ = [
|
||||
"CALL_MCP_TOOL",
|
||||
"DESCRIBE_MCP_TOOL",
|
||||
"GET_MCP_TOOL_SCHEMA_TOOL",
|
||||
"MCP_DISPATCH_TOOLS",
|
||||
"MCP_REGISTRY_CONTEXT_KEY",
|
||||
"SEARCH_MCP_TOOLS_TOOL",
|
||||
"BearerAuth",
|
||||
"ConnectedMcpServer",
|
||||
"FailureInfo",
|
||||
@@ -56,8 +66,10 @@ __all__ = [
|
||||
"classify",
|
||||
"connect_mcp_servers",
|
||||
"describe_mcp",
|
||||
"get_mcp_tool_schema",
|
||||
"list_mcps",
|
||||
"load_user_mcp_configs",
|
||||
"namespaced_tool_name",
|
||||
"resolve_mcp_call",
|
||||
"search_mcp_tools",
|
||||
]
|
||||
+148
-33
@@ -1,18 +1,21 @@
|
||||
"""The three generic MCP dispatch tools every agent carries.
|
||||
"""The generic MCP discovery and dispatch tools every agent carries.
|
||||
|
||||
Under the generic-dispatch model an agent does not get one tool per MCP tool.
|
||||
It gets exactly these three and discovers connections on demand:
|
||||
It gets four primary tools and discovers connections on demand:
|
||||
|
||||
- ``list_mcps()`` returns the connections available this run — each connection's
|
||||
id, name, description, and tool count, with no tool schemas — so the model can
|
||||
discover what it can reach without any inventory in the system prompt.
|
||||
- ``describe_mcp(connection)`` returns, as text, one connection's tools with
|
||||
their names, descriptions, and JSON input schemas — the schemas the model
|
||||
needs, fetched on demand instead of loaded onto every request up front.
|
||||
- ``search_mcp_tools(connection, query)`` returns a small ranked candidate set.
|
||||
- ``get_mcp_tool_schema(connection, tool)`` returns one exact input schema.
|
||||
- ``call_mcp(connection, tool, arguments)`` dispatches one call to a
|
||||
connection's tool and returns its result.
|
||||
|
||||
All three read the per-run :class:`~strix.tools.mcp.registry.McpRegistry` from the
|
||||
``describe_mcp`` remains as a compatibility path for older prompts, but current
|
||||
agents use targeted search and one-schema lookup instead of receiving a whole
|
||||
provider catalog in one model turn.
|
||||
|
||||
All four read the per-run :class:`~strix.tools.mcp.registry.McpRegistry` from the
|
||||
run context under :data:`~strix.tools.mcp.registry.MCP_REGISTRY_CONTEXT_KEY`. They
|
||||
are ordinary ``FunctionTool`` objects placed in the agent factory's base tool set,
|
||||
so the factory's output-bounding and disk-spill wrapping apply to their results
|
||||
@@ -61,12 +64,11 @@ async def list_mcps(ctx: RunContextWrapper) -> dict[str, Any]:
|
||||
"""List the MCP connections available this run, so you can discover them.
|
||||
|
||||
Read-only. Returns one entry per connection with its ``id`` (the exact name
|
||||
you pass to ``describe_mcp`` and ``call_mcp``), ``name``, ``description``, and
|
||||
``tool_count`` — no tool schemas. The three MCP tools work in order: call
|
||||
``list_mcps`` to discover the available connections, then ``describe_mcp`` on
|
||||
one connection to inspect its tools and their input schemas, then ``call_mcp``
|
||||
to run one of its tools. Returns an empty ``connections`` list when the run has
|
||||
no MCP connections.
|
||||
you pass to the other MCP tools), ``name``, ``description``, and
|
||||
``tool_count``. The response does not include tool schemas. Call
|
||||
``search_mcp_tools`` next. Then call ``get_mcp_tool_schema`` for one selected
|
||||
tool before you call ``call_mcp``. Returns an empty ``connections`` list when
|
||||
the run has no MCP connections.
|
||||
"""
|
||||
registry = _registry_from_ctx(ctx)
|
||||
if registry is None or not registry:
|
||||
@@ -80,6 +82,7 @@ async def list_mcps(ctx: RunContextWrapper) -> dict[str, Any]:
|
||||
"description": summary.purpose,
|
||||
"tool_count": summary.tool_count,
|
||||
"dead": dead_by_name.get(summary.name, False),
|
||||
"state": summary.state,
|
||||
}
|
||||
for summary in registry.summaries()
|
||||
]
|
||||
@@ -88,13 +91,12 @@ async def list_mcps(ctx: RunContextWrapper) -> dict[str, Any]:
|
||||
|
||||
@function_tool(timeout=60)
|
||||
async def describe_mcp(ctx: RunContextWrapper, connection: str) -> str:
|
||||
"""List the tools one MCP connection offers, with their input schemas.
|
||||
"""Return one connection's full tool catalog as a compatibility fallback.
|
||||
|
||||
Read-only. Look up a connection by the id ``list_mcps`` reported for it; this
|
||||
returns each of its tools with the tool's name, description, and JSON input
|
||||
schema — the argument shape you pass to ``call_mcp``. Call this before
|
||||
``call_mcp`` on any connection you have not used yet. Nothing is fetched from
|
||||
or run against the connection's data.
|
||||
Read-only. This result can be large because it includes every tool schema.
|
||||
First use ``search_mcp_tools`` and ``get_mcp_tool_schema``. Use this fallback
|
||||
only when targeted search cannot identify an expected tool. Nothing is read
|
||||
from the connected account.
|
||||
|
||||
Args:
|
||||
connection: The connection name exactly as reported by ``list_mcps``.
|
||||
@@ -106,7 +108,7 @@ async def describe_mcp(ctx: RunContextWrapper, connection: str) -> str:
|
||||
if entry is None:
|
||||
return _unknown_connection(connection, registry)
|
||||
try:
|
||||
tools = await entry.session.list_tools()
|
||||
tools = await entry.ensure_catalog()
|
||||
except McpConnectionUnavailableError as exc:
|
||||
return str(exc)
|
||||
if not tools:
|
||||
@@ -116,6 +118,121 @@ async def describe_mcp(ctx: RunContextWrapper, connection: str) -> str:
|
||||
return f"{header}\n{body}"
|
||||
|
||||
|
||||
def _search_score(tool: MCPTool, query_terms: list[str], active: bool) -> tuple[int, str]:
|
||||
name = tool.name.lower()
|
||||
description = (tool.description or "").lower()
|
||||
if not query_terms:
|
||||
return (100 if active else 0, name)
|
||||
score = 100 if active else 0
|
||||
matched_terms = 0
|
||||
for term in query_terms:
|
||||
if name == term:
|
||||
score += 60
|
||||
matched_terms += 1
|
||||
elif name.startswith(term):
|
||||
score += 35
|
||||
matched_terms += 1
|
||||
elif term in name:
|
||||
score += 25
|
||||
matched_terms += 1
|
||||
elif term in description:
|
||||
score += 10
|
||||
matched_terms += 1
|
||||
if matched_terms == 0:
|
||||
return (-1, name)
|
||||
return (score + (matched_terms * 5), name)
|
||||
|
||||
|
||||
@function_tool(timeout=60)
|
||||
async def search_mcp_tools(
|
||||
ctx: RunContextWrapper,
|
||||
connection: str,
|
||||
query: str,
|
||||
limit: int = 8,
|
||||
) -> dict[str, Any] | str:
|
||||
"""Search one connection's tools without returning their full schemas.
|
||||
|
||||
The first search lazily connects the provider and caches its catalog. Results
|
||||
contain only names, short descriptions, and whether each tool is in the
|
||||
scan's active set. Call ``get_mcp_tool_schema`` for the selected tool.
|
||||
|
||||
Args:
|
||||
connection: The connection name exactly as reported by ``list_mcps``.
|
||||
query: Capability words such as ``page content`` or ``workspace identity``.
|
||||
limit: Maximum candidates to return, from 1 through 20.
|
||||
"""
|
||||
registry = _registry_from_ctx(ctx)
|
||||
if registry is None or not registry:
|
||||
return _NO_CONNECTIONS
|
||||
entry = registry.get(connection)
|
||||
if entry is None:
|
||||
return _unknown_connection(connection, registry)
|
||||
try:
|
||||
tools = await entry.ensure_catalog()
|
||||
except McpConnectionUnavailableError as exc:
|
||||
return str(exc)
|
||||
terms = [term for term in query.lower().split() if term]
|
||||
bounded_limit = min(max(limit, 1), 20)
|
||||
ranked = [
|
||||
(score, tool)
|
||||
for tool in tools
|
||||
if (score := _search_score(tool, terms, tool.name in entry.active_tools))[0] >= 0
|
||||
]
|
||||
if not ranked:
|
||||
ranked = [
|
||||
((100, tool.name.lower()), tool) for tool in tools if tool.name in entry.active_tools
|
||||
]
|
||||
ranked.sort(key=lambda item: (-item[0][0], item[0][1]))
|
||||
return {
|
||||
"connection": connection,
|
||||
"query": query,
|
||||
"matches": [
|
||||
{
|
||||
"name": tool.name,
|
||||
"description": (tool.description or "").strip() or None,
|
||||
"active": tool.name in entry.active_tools,
|
||||
}
|
||||
for _, tool in ranked[:bounded_limit]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@function_tool(timeout=60)
|
||||
async def get_mcp_tool_schema(
|
||||
ctx: RunContextWrapper,
|
||||
connection: str,
|
||||
tool: str,
|
||||
) -> dict[str, Any] | str:
|
||||
"""Return one MCP tool's exact input schema.
|
||||
|
||||
Args:
|
||||
connection: The connection name exactly as reported by ``list_mcps``.
|
||||
tool: One exact tool name returned by ``search_mcp_tools``.
|
||||
"""
|
||||
registry = _registry_from_ctx(ctx)
|
||||
if registry is None or not registry:
|
||||
return _NO_CONNECTIONS
|
||||
entry = registry.get(connection)
|
||||
if entry is None:
|
||||
return _unknown_connection(connection, registry)
|
||||
try:
|
||||
tools = await entry.ensure_catalog()
|
||||
except McpConnectionUnavailableError as exc:
|
||||
return str(exc)
|
||||
match = next((candidate for candidate in tools if candidate.name == tool), None)
|
||||
if match is None:
|
||||
return (
|
||||
f"Unknown tool {tool!r} on MCP connection {connection!r}. "
|
||||
"Call search_mcp_tools to find an available tool."
|
||||
)
|
||||
return {
|
||||
"connection": connection,
|
||||
"name": match.name,
|
||||
"description": (match.description or "").strip() or None,
|
||||
"input_schema": match.inputSchema or {"type": "object"},
|
||||
}
|
||||
|
||||
|
||||
@function_tool(timeout=120, strict_mode=False)
|
||||
async def call_mcp(
|
||||
ctx: RunContextWrapper,
|
||||
@@ -125,19 +242,18 @@ async def call_mcp(
|
||||
) -> Any:
|
||||
"""Call one tool on one MCP connection and return its result.
|
||||
|
||||
Address the tool by the connection id from ``list_mcps`` and the tool name
|
||||
from ``describe_mcp`` on that connection. Pass the tool's arguments as an
|
||||
object matching the input schema ``describe_mcp`` showed for it (omit it, or
|
||||
pass an empty object, for a tool that takes no arguments).
|
||||
Use the connection id from ``list_mcps``. Use the tool name from
|
||||
``search_mcp_tools``. Pass an object that matches the schema from
|
||||
``get_mcp_tool_schema``. Omit the arguments for a tool that takes no
|
||||
arguments.
|
||||
|
||||
Args:
|
||||
connection: The connection name exactly as reported by ``list_mcps``.
|
||||
tool: The tool name, exactly as reported by ``describe_mcp``.
|
||||
tool: The tool name exactly as reported by ``search_mcp_tools``.
|
||||
arguments: The tool's arguments as a JSON object of names to values (for
|
||||
example ``{"path": "app.py"}``), or omitted/empty for a tool that
|
||||
takes none. Pass an object, not a stringified one. Its shape is
|
||||
whatever ``describe_mcp`` showed for the tool rather than a shape this
|
||||
tool fixes in advance.
|
||||
takes none. Pass an object, not a stringified object. Match the shape
|
||||
that ``get_mcp_tool_schema`` returned.
|
||||
"""
|
||||
registry = _registry_from_ctx(ctx)
|
||||
if registry is None or not registry:
|
||||
@@ -147,7 +263,7 @@ async def call_mcp(
|
||||
return _unknown_connection(connection, registry)
|
||||
invalid_arguments = (
|
||||
f"Invalid arguments for {connection!r}.{tool}: expected a JSON object of "
|
||||
"argument names to values, or none. Call describe_mcp for the input schema."
|
||||
"argument names to values, or none. Call get_mcp_tool_schema for the schema."
|
||||
)
|
||||
if isinstance(arguments, str):
|
||||
# The ``arguments`` parameter is schema-less (an open object is not
|
||||
@@ -162,18 +278,17 @@ async def call_mcp(
|
||||
if arguments is not None and not isinstance(arguments, dict):
|
||||
return invalid_arguments
|
||||
try:
|
||||
available = await entry.session.list_tools()
|
||||
available = await entry.ensure_catalog()
|
||||
except McpConnectionUnavailableError as exc:
|
||||
return _errored_tool_output(str(exc))
|
||||
valid_names = {mcp_tool.name for mcp_tool in available}
|
||||
if tool not in valid_names:
|
||||
offered = ", ".join(sorted(valid_names)) or "(none)"
|
||||
return (
|
||||
f"Unknown tool {tool!r} on MCP connection {connection!r}. "
|
||||
f"Tools this connection offers: {offered}. "
|
||||
"Call describe_mcp for their input schemas."
|
||||
"Call search_mcp_tools, then get_mcp_tool_schema."
|
||||
)
|
||||
return await entry.session.dispatch(
|
||||
session = await entry.ensure_connected()
|
||||
return await session.dispatch(
|
||||
tool,
|
||||
arguments or {},
|
||||
label=namespaced_tool_name(connection, tool),
|
||||
|
||||
+30
-25
@@ -14,6 +14,7 @@ fails the run.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
@@ -281,8 +282,10 @@ async def _count_session_tools(config: McpConnectionConfig, session: SupervisedM
|
||||
|
||||
async def connect_mcp_servers(
|
||||
configs: list[McpConnectionConfig],
|
||||
*,
|
||||
max_concurrency: int = 6,
|
||||
) -> list[ConnectedMcpServer]:
|
||||
"""Connect each MCP config on its own supervising task and return the sessions.
|
||||
"""Connect MCP configs concurrently under a fixed bound.
|
||||
|
||||
Each connection becomes a :class:`~strix.tools.mcp.session.SupervisedMcpSession`
|
||||
that owns ``connect()``, the held-open session, and ``cleanup()`` on one
|
||||
@@ -301,41 +304,43 @@ async def connect_mcp_servers(
|
||||
:class:`~strix.tools.mcp.registry.McpRegistry` from these sessions, and the
|
||||
agent reaches each tool on demand through ``describe_mcp`` / ``call_mcp``.
|
||||
"""
|
||||
connected: list[ConnectedMcpServer] = []
|
||||
semaphore = asyncio.Semaphore(max(1, max_concurrency))
|
||||
sessions: list[SupervisedMcpSession] = []
|
||||
try:
|
||||
for config in configs:
|
||||
|
||||
async def connect_one(config: McpConnectionConfig) -> ConnectedMcpServer | None:
|
||||
async with semaphore:
|
||||
session = SupervisedMcpSession(config)
|
||||
sessions.append(session)
|
||||
if not await session.start():
|
||||
# Initial connect failed; already logged inside the session. Drop it.
|
||||
await session.aclose()
|
||||
sessions.remove(session)
|
||||
continue
|
||||
try:
|
||||
if not await session.start():
|
||||
await session.aclose()
|
||||
return None
|
||||
tool_count = await _count_session_tools(config, session)
|
||||
except McpConnectionUnavailableError:
|
||||
# The session died between connecting and its first listing; skip it.
|
||||
logger.warning("MCP connection %r died before its first listing", config.name)
|
||||
await session.aclose()
|
||||
sessions.remove(session)
|
||||
continue
|
||||
return None
|
||||
except BaseException:
|
||||
with contextlib.suppress(BaseException):
|
||||
await session.aclose()
|
||||
raise
|
||||
logger.info("Connected MCP server %r (%d tools)", config.name, tool_count)
|
||||
connected.append(
|
||||
ConnectedMcpServer(
|
||||
session=session, name=config.name, tool_count=tool_count, notes=config.notes
|
||||
)
|
||||
return ConnectedMcpServer(
|
||||
session=session,
|
||||
name=config.name,
|
||||
tool_count=tool_count,
|
||||
notes=config.notes,
|
||||
)
|
||||
except BaseException:
|
||||
# Cancelled or errored mid-attach: close every session started so far,
|
||||
# each on its own task, then re-raise. The runner only receives the list
|
||||
# on a clean return, so on an abnormal exit this function owns the cleanup.
|
||||
for session in sessions:
|
||||
with contextlib.suppress(BaseException):
|
||||
await session.aclose()
|
||||
raise
|
||||
|
||||
return connected
|
||||
try:
|
||||
results = await asyncio.gather(*(connect_one(config) for config in configs))
|
||||
except BaseException:
|
||||
await asyncio.gather(
|
||||
*(session.aclose() for session in sessions),
|
||||
return_exceptions=True,
|
||||
)
|
||||
raise
|
||||
return [result for result in results if result is not None]
|
||||
|
||||
|
||||
async def attach_mcp_requests(
|
||||
|
||||
@@ -62,6 +62,9 @@ class McpConnectionConfig(BaseModel):
|
||||
"""Tool allowlist, applied after the server lists its tools. ``None`` (the
|
||||
default) exposes every tool the server lists; a list restricts to it."""
|
||||
|
||||
active_tools: list[str] = Field(default_factory=list)
|
||||
"""Small scan-relevant subset ranked ahead of the broader allowed catalog."""
|
||||
|
||||
notes: str | None = None
|
||||
"""Free-text notes for the agent describing what this connection is and how
|
||||
to use it. When set, the note becomes the connection's purpose line in the
|
||||
|
||||
+246
-28
@@ -22,13 +22,18 @@ single dispatch point.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import dataclasses
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, Literal, NamedTuple
|
||||
|
||||
from strix.tools.mcp.session import SupervisedMcpSession
|
||||
from strix.tools.mcp.session import McpConnectionUnavailableError, SupervisedMcpSession
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from agents.mcp import MCPServer
|
||||
|
||||
from strix.tools.mcp.client import ResultTransform
|
||||
@@ -49,37 +54,60 @@ MCP_REGISTRY_CONTEXT_KEY = "mcp_registry"
|
||||
# tracer all recognise a connection-scoped dispatch call by the same names.
|
||||
CALL_MCP_TOOL = "call_mcp"
|
||||
DESCRIBE_MCP_TOOL = "describe_mcp"
|
||||
MCP_DISPATCH_TOOLS = frozenset({CALL_MCP_TOOL, DESCRIBE_MCP_TOOL})
|
||||
SEARCH_MCP_TOOLS_TOOL = "search_mcp_tools"
|
||||
GET_MCP_TOOL_SCHEMA_TOOL = "get_mcp_tool_schema"
|
||||
MCP_DISPATCH_TOOLS = frozenset(
|
||||
{
|
||||
CALL_MCP_TOOL,
|
||||
DESCRIBE_MCP_TOOL,
|
||||
SEARCH_MCP_TOOLS_TOOL,
|
||||
GET_MCP_TOOL_SCHEMA_TOOL,
|
||||
}
|
||||
)
|
||||
|
||||
McpConnectionState = Literal[
|
||||
"configured",
|
||||
"connecting",
|
||||
"connected",
|
||||
"catalog_loading",
|
||||
"catalog_ready",
|
||||
"unavailable",
|
||||
]
|
||||
_RETRY_DELAY_SECONDS = 5.0
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@dataclasses.dataclass
|
||||
class McpConnectionEntry:
|
||||
"""One live MCP connection a scan may reach, keyed by ``name``.
|
||||
"""One configured MCP connection a scan may reach, keyed by ``name``.
|
||||
|
||||
``session`` is the :class:`~strix.tools.mcp.session.SupervisedMcpSession` that
|
||||
owns the connection on its own task; the dispatch tools list tools and call
|
||||
tools through it (``session.list_tools`` / ``session.dispatch``) so a session
|
||||
failure is contained and can reconnect. ``purpose`` is the human label
|
||||
``list_mcps`` reports as the connection's description (the user's connection
|
||||
notes, or whatever the caller supplies). ``tool_count`` is how many tools the
|
||||
connection offers, also reported by ``list_mcps``. ``result_transform``, when
|
||||
set, runs on each call's structured result at the single dispatch point
|
||||
(strix-pro's sanitizer uses it). ``provider`` is an optional source label
|
||||
(e.g. ``"supabase"``) the caller tags the connection with; the command-line
|
||||
path leaves it ``None``, and event tagging surfaces it when set.
|
||||
|
||||
The connection config the session reconnects with (and its bearer token) lives
|
||||
on ``session`` in memory only. It is reached via :attr:`config` for the
|
||||
reconnect path and is never logged, serialized into the event stream, or
|
||||
written to disk.
|
||||
Registration is inert. The first warm-up, search, schema lookup, or call
|
||||
creates one shared connection task; the first catalog operation creates one
|
||||
shared listing task. Root and child agents therefore reuse the same session
|
||||
and catalog even when they request a cold connection concurrently.
|
||||
"""
|
||||
|
||||
session: SupervisedMcpSession
|
||||
name: str
|
||||
connection_config: McpConnectionConfig | None = dataclasses.field(
|
||||
default=None,
|
||||
repr=False,
|
||||
)
|
||||
session: SupervisedMcpSession | None = dataclasses.field(default=None, repr=False)
|
||||
purpose: str | None = None
|
||||
tool_count: int = 0
|
||||
result_transform: ResultTransform | None = None
|
||||
provider: str | None = None
|
||||
state: McpConnectionState = "configured"
|
||||
_catalog: list[Any] | None = dataclasses.field(default=None, repr=False)
|
||||
_connect_task: asyncio.Task[SupervisedMcpSession] | None = dataclasses.field(
|
||||
default=None,
|
||||
repr=False,
|
||||
)
|
||||
_catalog_task: asyncio.Task[list[Any]] | None = dataclasses.field(
|
||||
default=None,
|
||||
repr=False,
|
||||
)
|
||||
_retry_after: float = dataclasses.field(default=0.0, repr=False)
|
||||
_status_sink: Callable[[], None] | None = dataclasses.field(default=None, repr=False)
|
||||
|
||||
@property
|
||||
def server(self) -> MCPServer | None:
|
||||
@@ -88,12 +116,143 @@ class McpConnectionEntry:
|
||||
Kept so existing callers that read ``entry.server`` keep working; new code
|
||||
should call through ``entry.session`` so reconnect and containment apply.
|
||||
"""
|
||||
return self.session.server
|
||||
return self.session.server if self.session is not None else None
|
||||
|
||||
@property
|
||||
def config(self) -> McpConnectionConfig | None:
|
||||
"""The session's reconnect config. Carries the bearer token; never log it."""
|
||||
return self.session.config
|
||||
"""The reconnect config. Carries the bearer token; never log it."""
|
||||
if self.connection_config is not None:
|
||||
return self.connection_config
|
||||
return self.session.config if self.session is not None else None
|
||||
|
||||
@property
|
||||
def active_tools(self) -> frozenset[str]:
|
||||
config = self.config
|
||||
return frozenset(config.active_tools if config is not None else ())
|
||||
|
||||
def set_status_sink(self, sink: Callable[[], None] | None) -> None:
|
||||
self._status_sink = sink
|
||||
if self.session is not None:
|
||||
self.session.set_on_dead(self._on_dead)
|
||||
|
||||
def _set_state(self, state: McpConnectionState) -> None:
|
||||
if self.state == state:
|
||||
return
|
||||
self.state = state
|
||||
if self._status_sink is not None:
|
||||
self._status_sink()
|
||||
|
||||
def _on_dead(self) -> None:
|
||||
self.session = None
|
||||
self._catalog = None
|
||||
self._catalog_task = None
|
||||
self._retry_after = time.monotonic() + _RETRY_DELAY_SECONDS
|
||||
self._set_state("unavailable")
|
||||
|
||||
async def ensure_connected(self) -> SupervisedMcpSession:
|
||||
"""Return this entry's live session, connecting it once when needed."""
|
||||
if self.session is not None:
|
||||
return self.session
|
||||
if self.connection_config is None:
|
||||
raise McpConnectionUnavailableError(
|
||||
f"MCP connection {self.name!r} is unavailable and cannot reconnect."
|
||||
)
|
||||
if self._connect_task is None:
|
||||
if self.state == "unavailable" and time.monotonic() < self._retry_after:
|
||||
raise McpConnectionUnavailableError(
|
||||
f"MCP connection {self.name!r} is temporarily unavailable."
|
||||
)
|
||||
self._connect_task = asyncio.create_task(
|
||||
self._connect(),
|
||||
name=f"mcp-connect-{self.name}",
|
||||
)
|
||||
task = self._connect_task
|
||||
try:
|
||||
return await asyncio.shield(task)
|
||||
finally:
|
||||
if self._connect_task is task and task.done():
|
||||
self._connect_task = None
|
||||
|
||||
async def _connect(self) -> SupervisedMcpSession:
|
||||
config = self.connection_config
|
||||
if config is None:
|
||||
raise McpConnectionUnavailableError(
|
||||
f"MCP connection {self.name!r} has no connection configuration."
|
||||
)
|
||||
self._set_state("connecting")
|
||||
session = SupervisedMcpSession(config)
|
||||
try:
|
||||
started = await session.start()
|
||||
except BaseException:
|
||||
with contextlib.suppress(BaseException):
|
||||
await session.aclose()
|
||||
self._retry_after = time.monotonic() + _RETRY_DELAY_SECONDS
|
||||
self._set_state("unavailable")
|
||||
raise
|
||||
if not started:
|
||||
await session.aclose()
|
||||
self._retry_after = time.monotonic() + _RETRY_DELAY_SECONDS
|
||||
self._set_state("unavailable")
|
||||
raise McpConnectionUnavailableError(f"MCP connection {self.name!r} could not connect.")
|
||||
self.session = session
|
||||
session.set_on_dead(self._on_dead)
|
||||
self._retry_after = 0.0
|
||||
self._set_state("connected")
|
||||
return session
|
||||
|
||||
async def ensure_catalog(self) -> list[Any]:
|
||||
"""Return the filtered catalog, listing it once on first use."""
|
||||
if (
|
||||
self._catalog is not None
|
||||
and self.session is not None
|
||||
and not self.session.is_dead
|
||||
and not self.session.is_unavailable
|
||||
):
|
||||
return self._catalog
|
||||
if self.session is not None and (self.session.is_dead or self.session.is_unavailable):
|
||||
self._catalog = None
|
||||
if self._catalog_task is None:
|
||||
self._catalog_task = asyncio.create_task(
|
||||
self._load_catalog(),
|
||||
name=f"mcp-catalog-{self.name}",
|
||||
)
|
||||
task = self._catalog_task
|
||||
try:
|
||||
return await asyncio.shield(task)
|
||||
finally:
|
||||
if self._catalog_task is task and task.done():
|
||||
self._catalog_task = None
|
||||
|
||||
async def _load_catalog(self) -> list[Any]:
|
||||
session = await self.ensure_connected()
|
||||
self._set_state("catalog_loading")
|
||||
try:
|
||||
catalog = await session.list_tools()
|
||||
except BaseException:
|
||||
if session.is_dead:
|
||||
self._set_state("unavailable")
|
||||
else:
|
||||
self._set_state("connected")
|
||||
raise
|
||||
self._catalog = list(catalog)
|
||||
self.tool_count = len(self._catalog)
|
||||
self._set_state("catalog_ready")
|
||||
return self._catalog
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Cancel pending initialization and close an opened session."""
|
||||
tasks = [task for task in (self._catalog_task, self._connect_task) if task is not None]
|
||||
for task in tasks:
|
||||
task.cancel()
|
||||
for task in tasks:
|
||||
with contextlib.suppress(BaseException):
|
||||
await task
|
||||
self._catalog_task = None
|
||||
self._connect_task = None
|
||||
if self.session is not None:
|
||||
with contextlib.suppress(BaseException):
|
||||
await self.session.aclose()
|
||||
self.session = None
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -105,6 +264,7 @@ class McpConnectionSummary:
|
||||
purpose: str | None
|
||||
tool_count: int
|
||||
provider: str | None = None
|
||||
state: McpConnectionState = "configured"
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -123,6 +283,7 @@ class McpConnectionStatus:
|
||||
provider: str | None
|
||||
tool_count: int
|
||||
dead: bool
|
||||
state: McpConnectionState
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -165,6 +326,21 @@ class McpRegistry:
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._entries: dict[str, McpConnectionEntry] = {}
|
||||
self._status_sink: Callable[[], None] | None = None
|
||||
self._warmup_task: asyncio.Task[None] | None = None
|
||||
|
||||
def register(self, request: McpConnectionRequest) -> McpConnectionEntry:
|
||||
"""Register an inert request without opening a network connection."""
|
||||
entry = McpConnectionEntry(
|
||||
name=request.config.name,
|
||||
connection_config=request.config,
|
||||
purpose=request.purpose or request.config.notes,
|
||||
result_transform=request.result_transform,
|
||||
provider=request.provider,
|
||||
)
|
||||
entry.set_status_sink(self._status_sink)
|
||||
self._entries[entry.name] = entry
|
||||
return entry
|
||||
|
||||
def add(
|
||||
self,
|
||||
@@ -191,13 +367,16 @@ class McpRegistry:
|
||||
raise ValueError("McpRegistry.add requires either 'session' or 'server'")
|
||||
session = SupervisedMcpSession.adopt(server, name=name, config=config)
|
||||
entry = McpConnectionEntry(
|
||||
session=session,
|
||||
name=name,
|
||||
connection_config=config or session.config,
|
||||
session=session,
|
||||
purpose=purpose,
|
||||
tool_count=tool_count,
|
||||
result_transform=result_transform,
|
||||
provider=provider,
|
||||
state="connected",
|
||||
)
|
||||
entry.set_status_sink(self._status_sink)
|
||||
self._entries[name] = entry
|
||||
return entry
|
||||
|
||||
@@ -217,6 +396,7 @@ class McpRegistry:
|
||||
purpose=entry.purpose,
|
||||
tool_count=entry.tool_count,
|
||||
provider=entry.provider,
|
||||
state=entry.state,
|
||||
)
|
||||
for entry in self._entries.values()
|
||||
]
|
||||
@@ -233,7 +413,9 @@ class McpRegistry:
|
||||
name=entry.name,
|
||||
provider=entry.provider,
|
||||
tool_count=entry.tool_count,
|
||||
dead=entry.session.is_dead,
|
||||
dead=entry.state == "unavailable"
|
||||
or (entry.session is not None and entry.session.is_dead),
|
||||
state=entry.state,
|
||||
)
|
||||
for entry in self._entries.values()
|
||||
]
|
||||
@@ -243,6 +425,42 @@ class McpRegistry:
|
||||
runner)."""
|
||||
self._entries.clear()
|
||||
|
||||
def set_status_sink(self, sink: Callable[[], None] | None) -> None:
|
||||
"""Receive a callback after any connection lifecycle transition."""
|
||||
self._status_sink = sink
|
||||
for entry in self._entries.values():
|
||||
entry.set_status_sink(sink)
|
||||
|
||||
def start_warmup(self, *, max_concurrency: int = 6) -> asyncio.Task[None]:
|
||||
"""Connect every configured entry in the background with a fixed bound."""
|
||||
if self._warmup_task is not None:
|
||||
return self._warmup_task
|
||||
|
||||
async def warm() -> None:
|
||||
semaphore = asyncio.Semaphore(max(1, max_concurrency))
|
||||
|
||||
async def connect(entry: McpConnectionEntry) -> None:
|
||||
async with semaphore:
|
||||
with contextlib.suppress(McpConnectionUnavailableError):
|
||||
await entry.ensure_connected()
|
||||
|
||||
await asyncio.gather(*(connect(entry) for entry in self._entries.values()))
|
||||
|
||||
self._warmup_task = asyncio.create_task(warm(), name="mcp-warmup")
|
||||
return self._warmup_task
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Stop warm-up and close only sessions this registry opened."""
|
||||
if self._warmup_task is not None:
|
||||
self._warmup_task.cancel()
|
||||
with contextlib.suppress(BaseException):
|
||||
await self._warmup_task
|
||||
self._warmup_task = None
|
||||
await asyncio.gather(
|
||||
*(entry.close() for entry in self._entries.values()),
|
||||
return_exceptions=True,
|
||||
)
|
||||
|
||||
def __len__(self) -> int:
|
||||
return len(self._entries)
|
||||
|
||||
@@ -282,6 +500,6 @@ def resolve_mcp_call(
|
||||
if entry is None:
|
||||
return None
|
||||
provider = entry.provider
|
||||
raw_tool = args.get("tool") if tool_name == CALL_MCP_TOOL else ""
|
||||
raw_tool = args.get("tool") if tool_name in {CALL_MCP_TOOL, GET_MCP_TOOL_SCHEMA_TOOL} else ""
|
||||
tool = raw_tool if isinstance(raw_tool, str) else ""
|
||||
return McpCallInfo(connection=connection, tool=tool, provider=provider)
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import dataclasses
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
@@ -67,6 +68,31 @@ async def _call[T](client: Client, fn: Callable[[Client], Awaitable[T]]) -> T:
|
||||
return await fn(client)
|
||||
|
||||
|
||||
async def existing_request_ids(
|
||||
ctx: RunContextWrapper,
|
||||
request_ids: list[str],
|
||||
) -> set[str]:
|
||||
"""Return request IDs that exist in the current Caido project."""
|
||||
if not request_ids:
|
||||
return set()
|
||||
|
||||
client = await _ctx_client(ctx)
|
||||
if client is None:
|
||||
raise RuntimeError("Caido client is not available")
|
||||
|
||||
# Request IDs are not an HTTPQL field. Resolve each ID through the same
|
||||
# project-bound lookup as view_request rather than constructing a filter.
|
||||
existing: set[str] = set()
|
||||
for request_id in request_ids:
|
||||
result = await _call(
|
||||
client,
|
||||
functools.partial(caido_api.get_request_with_client, request_id=request_id),
|
||||
)
|
||||
if result is not None:
|
||||
existing.add(str(result.request.id))
|
||||
return existing
|
||||
|
||||
|
||||
def _to_tool_json(value: Any) -> Any:
|
||||
"""Recursively convert SDK dataclasses/Pydantic objects to tool JSON values."""
|
||||
if value is None or isinstance(value, str | int | float | bool):
|
||||
|
||||
+356
-39
@@ -17,6 +17,7 @@ from typing import TYPE_CHECKING, Any
|
||||
from agents import RunContextWrapper, function_tool
|
||||
|
||||
from strix.tools.nullish import clean_optional
|
||||
from strix.tools.proxy.tools import existing_request_ids
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -168,6 +169,8 @@ _REQUIRED_FIELDS = {
|
||||
|
||||
_VALID_FIX_EFFORT = frozenset({"trivial", "low", "medium", "high"})
|
||||
_VALID_CONFIDENCE = frozenset({"high", "medium", "low"})
|
||||
_MAX_HTTP_EXCHANGE_IDS = 10
|
||||
_MAX_HTTP_EXCHANGE_ID_CHARS = 128
|
||||
|
||||
|
||||
def _validate_required_text(fields: dict[str, str]) -> list[str]:
|
||||
@@ -177,6 +180,97 @@ def _validate_required_text(fields: dict[str, str]) -> list[str]:
|
||||
]
|
||||
|
||||
|
||||
def _normalize_http_exchange_ids(raw: Any) -> tuple[list[str] | None, list[str]]:
|
||||
"""Return distinct proxy exchange ids in their original order."""
|
||||
if raw is None:
|
||||
return None, []
|
||||
if not isinstance(raw, list):
|
||||
return None, ["http_exchange_ids must be a list of proxy request ids"]
|
||||
|
||||
normalized: list[str] = []
|
||||
errors: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for index, value in enumerate(raw):
|
||||
if not isinstance(value, str):
|
||||
errors.append(f"http_exchange_ids[{index}] must be a string")
|
||||
continue
|
||||
request_id = value.strip()
|
||||
if not request_id:
|
||||
errors.append(f"http_exchange_ids[{index}] cannot be empty")
|
||||
continue
|
||||
if len(request_id) > _MAX_HTTP_EXCHANGE_ID_CHARS:
|
||||
errors.append(
|
||||
f"http_exchange_ids[{index}] must be {_MAX_HTTP_EXCHANGE_ID_CHARS} "
|
||||
"characters or fewer"
|
||||
)
|
||||
continue
|
||||
if any(ord(char) < 0x21 or ord(char) > 0x7E for char in request_id):
|
||||
errors.append(f"http_exchange_ids[{index}] must contain only visible ASCII characters")
|
||||
continue
|
||||
if not request_id.isdigit():
|
||||
errors.append(f"http_exchange_ids[{index}] must be a numeric proxy request id")
|
||||
continue
|
||||
if request_id not in seen:
|
||||
seen.add(request_id)
|
||||
normalized.append(request_id)
|
||||
if len(normalized) > _MAX_HTTP_EXCHANGE_IDS:
|
||||
errors.append(
|
||||
f"http_exchange_ids can contain at most "
|
||||
f"{_MAX_HTTP_EXCHANGE_IDS} distinct request ids"
|
||||
)
|
||||
break
|
||||
return normalized, errors
|
||||
|
||||
|
||||
_HTTP_EXCHANGE_DROPPED_WARNING = (
|
||||
"http_exchange_ids were not stored: the proxy project could not be reached to verify "
|
||||
"them. Attach them with update_vulnerability_report when the proxy responds again."
|
||||
)
|
||||
|
||||
|
||||
async def _verify_http_exchange_ids(
|
||||
ctx: RunContextWrapper,
|
||||
raw: Any,
|
||||
) -> tuple[list[str] | None, list[str], str | None]:
|
||||
"""Verify proxy exchange IDs against the current Caido project.
|
||||
|
||||
IDs the project does not know are rejected. When the proxy itself cannot be
|
||||
queried the IDs are dropped and a warning is returned instead, so a proxy
|
||||
outage never blocks a finding and unverified IDs are never recorded as
|
||||
evidence.
|
||||
"""
|
||||
request_ids, errors = _normalize_http_exchange_ids(raw)
|
||||
if request_ids is None or errors or not request_ids:
|
||||
return request_ids, errors, None
|
||||
|
||||
try:
|
||||
existing_ids = await existing_request_ids(ctx, request_ids)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.warning(
|
||||
"Could not verify HTTP exchange IDs against the current Caido project",
|
||||
exc_info=True,
|
||||
)
|
||||
return None, [], _HTTP_EXCHANGE_DROPPED_WARNING
|
||||
|
||||
missing_ids = [request_id for request_id in request_ids if request_id not in existing_ids]
|
||||
if missing_ids:
|
||||
return (
|
||||
None,
|
||||
[
|
||||
"http_exchange_ids do not exist in the current proxy project: "
|
||||
+ ", ".join(missing_ids)
|
||||
],
|
||||
None,
|
||||
)
|
||||
return request_ids, [], None
|
||||
|
||||
|
||||
def _with_warning(result: dict[str, Any], warning: str | None) -> dict[str, Any]:
|
||||
if warning and result.get("success"):
|
||||
result["warning"] = warning
|
||||
return result
|
||||
|
||||
|
||||
def _validate_cvss_breakdown(breakdown: Any) -> list[str]:
|
||||
"""Check the 8 CVSS metrics are all present with legal values."""
|
||||
if not isinstance(breakdown, dict) or not breakdown:
|
||||
@@ -299,7 +393,7 @@ _UPDATE_TEXT_FIELDS = (
|
||||
)
|
||||
|
||||
|
||||
def _collect_update_changes( # noqa: PLR0912
|
||||
def _collect_update_changes( # noqa: PLR0912, PLR0915
|
||||
fields: dict[str, Any],
|
||||
) -> tuple[dict[str, Any], list[str]]:
|
||||
"""Validate the fields a revision replaces and return them with any errors."""
|
||||
@@ -368,6 +462,12 @@ def _collect_update_changes( # noqa: PLR0912
|
||||
if cwe:
|
||||
changes["cwe"] = cwe
|
||||
|
||||
raw_http_exchange_ids = fields.get("http_exchange_ids")
|
||||
http_exchange_ids, http_exchange_errors = _normalize_http_exchange_ids(raw_http_exchange_ids)
|
||||
errors.extend(http_exchange_errors)
|
||||
if raw_http_exchange_ids is not None and not http_exchange_errors:
|
||||
changes["http_exchange_ids"] = http_exchange_ids or []
|
||||
|
||||
return changes, errors
|
||||
|
||||
|
||||
@@ -378,6 +478,7 @@ _DYNAMIC_ONLY_UPDATE_FIELDS = (
|
||||
"method",
|
||||
"poc_description",
|
||||
"poc_script_code",
|
||||
"http_exchange_ids",
|
||||
)
|
||||
|
||||
# A dependency finding is rated in the context of the codebase that pins it, and
|
||||
@@ -551,13 +652,24 @@ def _do_update(
|
||||
if class_error is not None:
|
||||
return class_error
|
||||
|
||||
updated = report_state.update_vulnerability_report(
|
||||
report_id,
|
||||
changes,
|
||||
update_reason=update_reason,
|
||||
updated_by_agent_id=agent_id,
|
||||
updated_by_agent_name=agent_name,
|
||||
)
|
||||
try:
|
||||
updated = report_state.update_vulnerability_report(
|
||||
report_id,
|
||||
changes,
|
||||
update_reason=update_reason,
|
||||
updated_by_agent_id=agent_id,
|
||||
updated_by_agent_name=agent_name,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("update_vulnerability_report persistence failed")
|
||||
return {
|
||||
"success": False,
|
||||
"error": (
|
||||
f"Failed to revise report '{report_id}': {e!s}. "
|
||||
"The report still carries its previous content; retry the update."
|
||||
),
|
||||
"report_id": report_id,
|
||||
}
|
||||
if updated is None:
|
||||
known = [r.get("id") for r in report_state.get_existing_vulnerabilities()]
|
||||
if report_id not in known:
|
||||
@@ -584,6 +696,87 @@ def _do_update(
|
||||
}
|
||||
|
||||
|
||||
def _do_delete(
|
||||
*,
|
||||
report_id: str,
|
||||
delete_reason: str,
|
||||
agent_id: str | None = None,
|
||||
agent_name: str | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Withdraw a report an agent filed and has since disproved.
|
||||
|
||||
Deletion is for a finding that is not a vulnerability at all. A finding
|
||||
that is real but weaker than filed is revised, not deleted.
|
||||
"""
|
||||
report_id = (report_id or "").strip()
|
||||
delete_reason = (delete_reason or "").strip()
|
||||
if not report_id or not delete_reason:
|
||||
missing = "report_id" if not report_id else "delete_reason"
|
||||
return {
|
||||
"success": False,
|
||||
"error": (
|
||||
f"{missing} cannot be empty - name the report you are withdrawing and state "
|
||||
"what disproved it"
|
||||
),
|
||||
}
|
||||
|
||||
from strix.report.state import ReportRollbackError, get_global_report_state
|
||||
|
||||
report_state = get_global_report_state()
|
||||
if report_state is None:
|
||||
return {
|
||||
"success": False,
|
||||
"error": "Report state unavailable - no reports have been filed yet",
|
||||
}
|
||||
|
||||
try:
|
||||
deleted = report_state.delete_vulnerability_report(
|
||||
report_id,
|
||||
delete_reason=delete_reason,
|
||||
deleted_by_agent_id=agent_id,
|
||||
deleted_by_agent_name=agent_name,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.exception("delete_vulnerability_report persistence failed")
|
||||
if isinstance(e, ReportRollbackError):
|
||||
outcome = (
|
||||
f"{e.cause!s}. The report is still on file, but its on-disk indexes could "
|
||||
"not be rewritten and may be stale until the next report is saved"
|
||||
)
|
||||
else:
|
||||
outcome = f"{e!s}. The report is still on file"
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Failed to delete report '{report_id}': {outcome}; retry the deletion.",
|
||||
"report_id": report_id,
|
||||
}
|
||||
if deleted is None:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"Report with id '{report_id}' not found",
|
||||
"report_id": report_id,
|
||||
}
|
||||
|
||||
logger.info(
|
||||
"Vulnerability report %s deleted by %s: %s",
|
||||
report_id,
|
||||
agent_name or agent_id or "an agent",
|
||||
delete_reason[:200],
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"action": "deleted",
|
||||
"message": (
|
||||
f"Report '{report_id}' ({deleted.get('title')}) is withdrawn and no longer "
|
||||
"counts as a finding of this scan. Do not file it again unless new evidence "
|
||||
"proves it."
|
||||
),
|
||||
"report_id": report_id,
|
||||
"title": deleted.get("title"),
|
||||
"severity": deleted.get("severity"),
|
||||
}
|
||||
|
||||
|
||||
async def _do_create(
|
||||
*,
|
||||
title: str,
|
||||
@@ -606,6 +799,7 @@ async def _do_create(
|
||||
cve: str | None,
|
||||
cwe: str | None,
|
||||
code_locations: list[dict[str, Any]] | None,
|
||||
http_exchange_ids: list[str] | None = None,
|
||||
confidence_rationale: str | None = None,
|
||||
fix_verification: str | None = None,
|
||||
fix_pr_body: str | None = None,
|
||||
@@ -651,6 +845,10 @@ async def _do_create(
|
||||
errors.extend(_validate_fix_verification(parsed_locations, fix_verification))
|
||||
cve, cwe, identifier_errors = _validate_identifiers(cve, cwe)
|
||||
errors.extend(identifier_errors)
|
||||
normalized_http_exchange_ids, http_exchange_errors = _normalize_http_exchange_ids(
|
||||
http_exchange_ids
|
||||
)
|
||||
errors.extend(http_exchange_errors)
|
||||
|
||||
if errors:
|
||||
return {"success": False, "error": "Validation failed", "errors": errors}
|
||||
@@ -712,6 +910,7 @@ async def _do_create(
|
||||
"code_locations": parsed_locations,
|
||||
"fix_verification": fix_verification,
|
||||
"fix_pr_body": fix_pr_body,
|
||||
"http_exchange_ids": normalized_http_exchange_ids,
|
||||
}
|
||||
|
||||
dedupe = await check_duplicate(candidate, existing)
|
||||
@@ -738,9 +937,15 @@ async def _do_create(
|
||||
agent_id=agent_id if isinstance(agent_id, str) else None,
|
||||
agent_name=agent_name if isinstance(agent_name, str) else None,
|
||||
)
|
||||
except (ImportError, AttributeError) as e:
|
||||
except Exception as e:
|
||||
logger.exception("create_vulnerability_report persistence failed")
|
||||
return {"success": False, "error": f"Failed to create vulnerability report: {e!s}"}
|
||||
return {
|
||||
"success": False,
|
||||
"error": (
|
||||
f"Failed to create vulnerability report: {e!s}. "
|
||||
"The finding was not stored; file it again."
|
||||
),
|
||||
}
|
||||
else:
|
||||
logger.info(
|
||||
"Vulnerability report created: id=%s severity=%s cvss=%.1f title=%s",
|
||||
@@ -796,6 +1001,7 @@ async def create_vulnerability_report(
|
||||
cve: str | None = None,
|
||||
cwe: str | None = None,
|
||||
code_locations: list[dict[str, Any]] | None = None,
|
||||
http_exchange_ids: list[str] | None = None,
|
||||
confidence_rationale: str | None = None,
|
||||
fix_verification: str | None = None,
|
||||
fix_pr_body: str | None = None,
|
||||
@@ -1040,6 +1246,18 @@ async def create_vulnerability_report(
|
||||
cve: ``CVE-YYYY-NNNNN`` if certain, else omit.
|
||||
cwe: ``CWE-NNN`` (most specific child) if certain, else omit.
|
||||
code_locations: White-box findings — list of location objects.
|
||||
http_exchange_ids: Proxy request IDs that prove this finding.
|
||||
Copy these IDs from ``list_requests`` or ``view_request``.
|
||||
For a finding validated over HTTP, capture and inspect the
|
||||
supporting exchanges and include their IDs here before filing.
|
||||
Include relevant baseline/control requests as well as the exploit.
|
||||
Omit only when the finding has no captured HTTP evidence (for
|
||||
example a static-only code finding). Never invent IDs or drop
|
||||
them to bypass a verification error; retry the capture instead.
|
||||
If the result carries a ``warning`` that the IDs were not
|
||||
stored, the finding is filed without them: attach them with
|
||||
``update_vulnerability_report`` once the proxy responds.
|
||||
Keep IDs out of ``evidence`` and all other report text.
|
||||
|
||||
**How ``fix_before`` / ``fix_after`` work**: they're used as
|
||||
literal GitHub/GitLab PR suggestion blocks. When a reviewer
|
||||
@@ -1186,7 +1404,29 @@ async def create_vulnerability_report(
|
||||
A restrictive CSP that blocks inline script execution would
|
||||
reduce impact and lower the severity.
|
||||
fix_effort: "low"
|
||||
|
||||
Nice to have: for code findings, if the checkout has git history, a quick
|
||||
``git blame`` (quote the paths) on the vulnerable line is worth weaving into
|
||||
``technical_analysis`` — who last touched it, when, and in which commit, as
|
||||
part of the prose, not a separate section. Skip it if the line is
|
||||
uncommitted or the command fails.
|
||||
"""
|
||||
(
|
||||
http_exchange_ids,
|
||||
http_exchange_errors,
|
||||
http_exchange_warning,
|
||||
) = await _verify_http_exchange_ids(ctx, http_exchange_ids)
|
||||
if http_exchange_errors:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": "Validation failed",
|
||||
"errors": http_exchange_errors,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
agent_id, agent_name = _caller_identity(ctx)
|
||||
|
||||
result = await _do_create(
|
||||
@@ -1211,12 +1451,13 @@ async def create_vulnerability_report(
|
||||
cve=cve,
|
||||
cwe=cwe,
|
||||
code_locations=code_locations,
|
||||
http_exchange_ids=http_exchange_ids,
|
||||
fix_verification=fix_verification,
|
||||
fix_pr_body=fix_pr_body,
|
||||
agent_id=agent_id,
|
||||
agent_name=agent_name,
|
||||
)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
return json.dumps(_with_warning(result, http_exchange_warning), ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
@function_tool(timeout=60, strict_mode=False)
|
||||
@@ -1245,6 +1486,7 @@ async def update_vulnerability_report(
|
||||
cve: str | None = None,
|
||||
cwe: str | None = None,
|
||||
code_locations: list[dict[str, Any]] | None = None,
|
||||
http_exchange_ids: list[str] | None = None,
|
||||
fix_verification: str | None = None,
|
||||
fix_pr_body: str | None = None,
|
||||
contextual_cvss_reasoning: str | None = None,
|
||||
@@ -1317,43 +1559,112 @@ async def update_vulnerability_report(
|
||||
cve: Replacement CVE id.
|
||||
cwe: Replacement CWE id.
|
||||
code_locations: Replacement code locations.
|
||||
http_exchange_ids: Replacement proxy request ids. Pass an empty
|
||||
list to remove all linked exchanges.
|
||||
fix_verification: Verification statement for an applyable fix.
|
||||
fix_pr_body: Replacement fix PR body.
|
||||
contextual_cvss_reasoning: Dependency findings only. What you
|
||||
observed in this codebase that justifies the contextual
|
||||
``cvss_breakdown``.
|
||||
"""
|
||||
(
|
||||
http_exchange_ids,
|
||||
http_exchange_errors,
|
||||
http_exchange_warning,
|
||||
) = await _verify_http_exchange_ids(ctx, http_exchange_ids)
|
||||
if http_exchange_errors:
|
||||
return json.dumps(
|
||||
{
|
||||
"success": False,
|
||||
"error": "Validation failed",
|
||||
"errors": http_exchange_errors,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
fields = {
|
||||
"title": title,
|
||||
"description": description,
|
||||
"impact": impact,
|
||||
"target": target,
|
||||
"technical_analysis": technical_analysis,
|
||||
"poc_description": poc_description,
|
||||
"poc_script_code": poc_script_code,
|
||||
"remediation_steps": remediation_steps,
|
||||
"evidence": evidence,
|
||||
"assumptions": assumptions,
|
||||
"counterevidence": counterevidence,
|
||||
"confidence": confidence,
|
||||
"confidence_rationale": confidence_rationale,
|
||||
"severity_change_conditions": severity_change_conditions,
|
||||
"fix_effort": fix_effort,
|
||||
"cvss_breakdown": cvss_breakdown,
|
||||
"endpoint": endpoint,
|
||||
"method": method,
|
||||
"cve": cve,
|
||||
"cwe": cwe,
|
||||
"code_locations": code_locations,
|
||||
"http_exchange_ids": http_exchange_ids,
|
||||
"fix_verification": fix_verification,
|
||||
"fix_pr_body": fix_pr_body,
|
||||
"contextual_cvss_reasoning": contextual_cvss_reasoning,
|
||||
}
|
||||
if http_exchange_warning and all(value is None for value in fields.values()):
|
||||
return json.dumps(
|
||||
{"success": False, "error": http_exchange_warning, "report_id": report_id},
|
||||
ensure_ascii=False,
|
||||
default=str,
|
||||
)
|
||||
|
||||
agent_id, agent_name = _caller_identity(ctx)
|
||||
result = await asyncio.to_thread(
|
||||
_do_update,
|
||||
report_id=report_id,
|
||||
update_reason=update_reason,
|
||||
fields={
|
||||
"title": title,
|
||||
"description": description,
|
||||
"impact": impact,
|
||||
"target": target,
|
||||
"technical_analysis": technical_analysis,
|
||||
"poc_description": poc_description,
|
||||
"poc_script_code": poc_script_code,
|
||||
"remediation_steps": remediation_steps,
|
||||
"evidence": evidence,
|
||||
"assumptions": assumptions,
|
||||
"counterevidence": counterevidence,
|
||||
"confidence": confidence,
|
||||
"confidence_rationale": confidence_rationale,
|
||||
"severity_change_conditions": severity_change_conditions,
|
||||
"fix_effort": fix_effort,
|
||||
"cvss_breakdown": cvss_breakdown,
|
||||
"endpoint": endpoint,
|
||||
"method": method,
|
||||
"cve": cve,
|
||||
"cwe": cwe,
|
||||
"code_locations": code_locations,
|
||||
"fix_verification": fix_verification,
|
||||
"fix_pr_body": fix_pr_body,
|
||||
"contextual_cvss_reasoning": contextual_cvss_reasoning,
|
||||
},
|
||||
fields=fields,
|
||||
agent_id=agent_id,
|
||||
agent_name=agent_name,
|
||||
)
|
||||
return json.dumps(_with_warning(result, http_exchange_warning), ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
@function_tool(timeout=60)
|
||||
async def delete_vulnerability_report(
|
||||
ctx: RunContextWrapper,
|
||||
report_id: str,
|
||||
delete_reason: str,
|
||||
) -> str:
|
||||
"""Withdraw a vulnerability report that later testing disproved.
|
||||
|
||||
Use this when a finding you filed turns out not to be a vulnerability
|
||||
at all: the exploit only worked because of a mistake in your own test
|
||||
setup (a mixed-up session, a self-inflicted state change, a misread
|
||||
response), the behaviour is documented and intended, or the control
|
||||
you believed was missing is in fact enforced. The report is removed
|
||||
from the scan; it does not stay behind as a zero-severity entry.
|
||||
|
||||
Do NOT use this for a finding that is real but weaker than filed —
|
||||
revise it with ``update_vulnerability_report`` so the severity, the
|
||||
impact and the counterevidence come down together. Do NOT rewrite a
|
||||
report into a "retracted" or "false positive" note either: delete it.
|
||||
|
||||
Only withdraw a report you have re-tested yourself, whoever filed it.
|
||||
Call ``get_report`` first to read what it claims, then state in
|
||||
``delete_reason`` exactly what disproved it, so the scan history shows
|
||||
who withdrew the finding and why. A withdrawn report cannot be restored; if new
|
||||
evidence later proves the issue, file it again.
|
||||
|
||||
Args:
|
||||
report_id: Id of the report to withdraw (format ``vuln-NNNN``).
|
||||
delete_reason: What disproved the finding, in one or two
|
||||
sentences.
|
||||
"""
|
||||
agent_id, agent_name = _caller_identity(ctx)
|
||||
result = await asyncio.to_thread(
|
||||
_do_delete,
|
||||
report_id=report_id,
|
||||
delete_reason=delete_reason,
|
||||
agent_id=agent_id,
|
||||
agent_name=agent_name,
|
||||
)
|
||||
@@ -1747,9 +2058,15 @@ async def _do_create_dependency( # noqa: PLR0912
|
||||
agent_id=agent_id if isinstance(agent_id, str) else None,
|
||||
agent_name=agent_name if isinstance(agent_name, str) else None,
|
||||
)
|
||||
except (ImportError, AttributeError) as e:
|
||||
except Exception as e:
|
||||
logger.exception("create_dependency_report persistence failed")
|
||||
return {"success": False, "error": f"Failed to create dependency report: {e!s}"}
|
||||
return {
|
||||
"success": False,
|
||||
"error": (
|
||||
f"Failed to create dependency report: {e!s}. "
|
||||
"The finding was not stored; file it again."
|
||||
),
|
||||
}
|
||||
else:
|
||||
logger.info(
|
||||
"Dependency report created: id=%s cve=%s package=%s severity=%s",
|
||||
|
||||
@@ -121,7 +121,7 @@ def _exa_content(api_key: str, query: str, search_type: str, num_results: int) -
|
||||
api_key,
|
||||
"https://api.exa.ai/search",
|
||||
{
|
||||
"query": f"{_SYSTEM_PROMPT}\n\n{query}",
|
||||
"query": query,
|
||||
"type": search_type,
|
||||
"numResults": num_results,
|
||||
"contents": {"summary": {"query": _EXA_SUMMARY_PROMPT}},
|
||||
|
||||
+133
-1
@@ -2,9 +2,12 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import MagicMock, patch
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from unittest.mock import MagicMock, call, patch
|
||||
|
||||
import httpx
|
||||
import litellm
|
||||
import pytest
|
||||
from litellm.types.utils import LlmProviders
|
||||
@@ -14,6 +17,7 @@ from strix.config.models import (
|
||||
_configure_litellm_compatibility,
|
||||
_install_openrouter_stream_cost_capture,
|
||||
)
|
||||
from strix.llm import request_log
|
||||
from strix.report.state import (
|
||||
ReportState,
|
||||
litellm_cost_callback,
|
||||
@@ -21,6 +25,11 @@ from strix.report.state import (
|
||||
set_global_report_state,
|
||||
streamed_openrouter_costs,
|
||||
)
|
||||
from strix.report.usage import LLMUsageLedger
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.types.llms.openai import AllMessageValues
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
@@ -257,3 +266,126 @@ def test_openrouter_stream_handler_records_cost() -> None:
|
||||
assert streamed_openrouter_costs.take(SimpleNamespace(id="gen-stream")) == pytest.approx(
|
||||
0.0035055
|
||||
)
|
||||
|
||||
|
||||
def test_openrouter_tallies_provider() -> None:
|
||||
_install_openrouter_stream_cost_capture()
|
||||
config = ProviderConfigManager.get_provider_chat_config(
|
||||
model="z-ai/glm-5.3", provider=LlmProviders.OPENROUTER
|
||||
)
|
||||
assert config is not None
|
||||
handler = config.get_model_response_iterator(streaming_response=iter([]), sync_stream=True)
|
||||
report_state = MagicMock()
|
||||
usage = {
|
||||
"prompt_tokens": 1000,
|
||||
"completion_tokens": 10,
|
||||
"cost": 0.002,
|
||||
"prompt_tokens_details": {"cached_tokens": 900},
|
||||
}
|
||||
with patch("strix.report.state.get_global_report_state", return_value=report_state):
|
||||
handler.chunk_parser(
|
||||
{
|
||||
"id": "gen-a",
|
||||
"created": 1,
|
||||
"model": "z-ai/glm-5.3",
|
||||
"provider": "Together",
|
||||
"choices": [{"index": 0, "delta": {"content": None}}],
|
||||
"usage": usage,
|
||||
}
|
||||
)
|
||||
# Non-streamed replies (LLM_DISABLE_STREAMING) carry the same fields.
|
||||
reply = {
|
||||
"choices": [{"message": {"role": "assistant"}}],
|
||||
"provider": "Together",
|
||||
"usage": usage,
|
||||
}
|
||||
config.transform_response(
|
||||
"z-ai/glm-5.3",
|
||||
httpx.Response(200, json=reply),
|
||||
litellm.ModelResponse(),
|
||||
MagicMock(),
|
||||
{},
|
||||
[],
|
||||
{},
|
||||
{},
|
||||
None,
|
||||
)
|
||||
|
||||
tally = call("Together", agent_id=None, input_tokens=1000, cached_tokens=900, cost=0.002)
|
||||
assert report_state.record_llm_provider.call_args_list == [tally, tally]
|
||||
|
||||
|
||||
def test_provider_tally_survives_run_record_round_trip() -> None:
|
||||
ledger = LLMUsageLedger()
|
||||
for input_tokens, cached_tokens, cost in [(1000, 900, 0.002), (500, 0, 0.001)]:
|
||||
ledger.record_provider(
|
||||
"Together",
|
||||
agent_id=None,
|
||||
input_tokens=input_tokens,
|
||||
cached_tokens=cached_tokens,
|
||||
cost=cost,
|
||||
cache_block_tokens=128,
|
||||
)
|
||||
|
||||
restored = LLMUsageLedger()
|
||||
restored.hydrate(ledger.to_record())
|
||||
|
||||
assert restored.to_record()["providers"] == {
|
||||
"Together": {
|
||||
"requests": 2,
|
||||
"input_tokens": 1500,
|
||||
"cached_tokens": 900,
|
||||
"cost": 0.003,
|
||||
"cache_misses": 0,
|
||||
"missed_tokens": 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test_provider_tally_counts_cache_misses_per_agent() -> None:
|
||||
ledger = LLMUsageLedger()
|
||||
calls = [
|
||||
("Z.AI", "a1", 1000, 0), # first call: nothing to miss
|
||||
("Z.AI", "a1", 1200, 960), # 40 short of the previous 1000: within a block
|
||||
("DeepInfra", "a1", 1500, 200), # 1000 of the previous 1200 lost
|
||||
("Z.AI", "a2", 800, 0), # another agent's first call
|
||||
("Z.AI", "a1", 600, 0), # prompt shrank: compaction, not a miss
|
||||
]
|
||||
for provider, agent_id, input_tokens, cached_tokens in calls:
|
||||
ledger.record_provider(
|
||||
provider,
|
||||
agent_id=agent_id,
|
||||
input_tokens=input_tokens,
|
||||
cached_tokens=cached_tokens,
|
||||
cost=0.0,
|
||||
cache_block_tokens=128,
|
||||
)
|
||||
|
||||
providers = ledger.to_record()["providers"]
|
||||
assert providers["DeepInfra"]["cache_misses"] == 1
|
||||
assert providers["DeepInfra"]["missed_tokens"] == 1000
|
||||
assert providers["Z.AI"]["cache_misses"] == 0
|
||||
|
||||
|
||||
def test_openrouter_request_carries_agent_session_id() -> None:
|
||||
_install_openrouter_stream_cost_capture()
|
||||
config = ProviderConfigManager.get_provider_chat_config(
|
||||
model="moonshotai/kimi-k3", provider=LlmProviders.OPENROUTER
|
||||
)
|
||||
assert config is not None
|
||||
messages: list[AllMessageValues] = [{"role": "user", "content": "hi"}]
|
||||
|
||||
def body() -> dict[str, Any]:
|
||||
return config.transform_request("moonshotai/kimi-k3", messages, {}, {}, {})
|
||||
|
||||
assert "session_id" not in body()
|
||||
token = request_log.bind_call_context("a1b2c3d4", "root")
|
||||
try:
|
||||
assert "session_id" not in body()
|
||||
with patch("strix.config.models.load_settings") as settings:
|
||||
settings.return_value.llm.openrouter_sticky_sessions = True
|
||||
session_id = body()["session_id"]
|
||||
assert str(uuid.UUID(session_id)) == session_id
|
||||
assert body()["session_id"] == session_id
|
||||
finally:
|
||||
request_log.reset_call_context(token)
|
||||
@@ -32,6 +32,7 @@ from openai.types.responses import (
|
||||
from strix.config import codex, loader
|
||||
from strix.config.loader import load_settings
|
||||
from strix.config.models import StrixProvider, _NonStreamingModel, _TurnGuardModel
|
||||
from strix.llm.request_log import RequestLoggingModel
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -312,7 +313,8 @@ def test_get_model_keeps_streaming_by_default(
|
||||
|
||||
model = StrixProvider().get_model("openai/gpt-4o-mini")
|
||||
assert isinstance(model, _TurnGuardModel)
|
||||
assert model._inner is inner
|
||||
assert isinstance(model._inner, RequestLoggingModel)
|
||||
assert model._inner._inner is inner
|
||||
|
||||
|
||||
def test_get_model_guards_subscription_model_but_keeps_it_streaming(
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from agents import RunConfig, Runner
|
||||
from openai import APIError, PermissionDeniedError
|
||||
|
||||
from strix.core import execution
|
||||
from strix.core.agents import AgentCoordinator
|
||||
from strix.llm import request_log
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import AsyncIterator
|
||||
|
||||
|
||||
def _request() -> httpx.Request:
|
||||
return httpx.Request("POST", "https://api.openai.com/v1/responses")
|
||||
|
||||
|
||||
def _midstream_api_error() -> APIError:
|
||||
return APIError("An error occurred while processing the request.", _request(), body=None)
|
||||
|
||||
|
||||
def _blocked_error() -> PermissionDeniedError:
|
||||
response = httpx.Response(
|
||||
403, request=_request(), headers={"x-request-id": "req_blocked_hdr"}, text="blocked"
|
||||
)
|
||||
return PermissionDeniedError("Output blocked by policy", response=response, body=None)
|
||||
|
||||
|
||||
class _FakeStream:
|
||||
def __init__(self, exc: BaseException | None = None) -> None:
|
||||
self._exc = exc
|
||||
self.run_loop_exception: BaseException | None = None
|
||||
self.seen_context: request_log.LlmCallContext | None = None
|
||||
|
||||
async def stream_events(self) -> AsyncIterator[Any]:
|
||||
self.seen_context = request_log.current_call_context()
|
||||
if self._exc is not None:
|
||||
raise self._exc
|
||||
items: tuple[Any, ...] = ()
|
||||
for item in items:
|
||||
yield item
|
||||
|
||||
|
||||
async def _run(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
streams: list[_FakeStream],
|
||||
coordinator: AgentCoordinator | None = None,
|
||||
) -> Any:
|
||||
monkeypatch.setattr(execution, "_TRANSIENT_MODEL_RETRY_BASE_DELAY_S", 0.0)
|
||||
monkeypatch.setattr(execution, "_TRANSIENT_MODEL_RETRY_MAX_DELAY_S", 0.0)
|
||||
calls = {"n": 0}
|
||||
|
||||
def _fake_run_streamed(*_args: Any, **_kwargs: Any) -> _FakeStream:
|
||||
stream = streams[calls["n"]]
|
||||
calls["n"] += 1
|
||||
return stream
|
||||
|
||||
monkeypatch.setattr(Runner, "run_streamed", _fake_run_streamed)
|
||||
coordinator = coordinator or AgentCoordinator()
|
||||
await coordinator.register("root", "strix", parent_id=None)
|
||||
return await execution._run_cycle(
|
||||
object(),
|
||||
coordinator,
|
||||
"root",
|
||||
input_data="task",
|
||||
run_config=cast("RunConfig", object()),
|
||||
context={},
|
||||
max_turns=5,
|
||||
session=None,
|
||||
interactive=False,
|
||||
event_sink=None,
|
||||
hooks=None,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_each_transient_replay_is_stamped_with_its_attempt_number(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
streams = [
|
||||
_FakeStream(exc=_midstream_api_error()),
|
||||
_FakeStream(exc=_midstream_api_error()),
|
||||
_FakeStream(),
|
||||
]
|
||||
await _run(monkeypatch, streams)
|
||||
assert [s.seen_context.retry_attempt for s in streams if s.seen_context] == [0, 1, 2]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_blocked_provider_failure_text_carries_request_id(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
coordinator = AgentCoordinator()
|
||||
with pytest.raises(PermissionDeniedError):
|
||||
await _run(monkeypatch, [_FakeStream(exc=_blocked_error())], coordinator)
|
||||
assert coordinator.statuses["root"] == "failed"
|
||||
error = coordinator.errors["root"]
|
||||
assert "Output blocked by policy" in error
|
||||
assert error.endswith("[provider request id: req_blocked_hdr]")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_agent_loop_binds_agent_context_and_resets(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
seen: dict[str, request_log.LlmCallContext] = {}
|
||||
|
||||
async def _fake_loop(**_kwargs: Any) -> None:
|
||||
seen["ctx"] = request_log.current_call_context()
|
||||
|
||||
monkeypatch.setattr(execution, "_run_agent_loop", _fake_loop)
|
||||
|
||||
class _Agent:
|
||||
name = "Recon Agent"
|
||||
|
||||
coordinator = AgentCoordinator()
|
||||
await execution.run_agent_loop(
|
||||
agent=_Agent(),
|
||||
initial_input="task",
|
||||
run_config=cast("RunConfig", object()),
|
||||
context={},
|
||||
max_turns=1,
|
||||
coordinator=coordinator,
|
||||
agent_id="agent-42",
|
||||
interactive=False,
|
||||
)
|
||||
assert seen["ctx"].agent_id == "agent-42"
|
||||
assert seen["ctx"].agent_name == "Recon Agent"
|
||||
assert request_log.current_call_context().agent_id is None
|
||||
@@ -19,6 +19,7 @@ from strix.config.settings import DEFAULT_MAX_TURNS
|
||||
from strix.interface.tui import runtime as go_tui
|
||||
from strix.interface.tui import sidecar
|
||||
from strix.interface.tui.runtime import GoTuiRuntime
|
||||
from strix.report.state import ReportState
|
||||
|
||||
|
||||
def args() -> argparse.Namespace:
|
||||
@@ -923,6 +924,28 @@ async def test_agent_state_sync_uses_latest_graph_snapshot_shape() -> None:
|
||||
assert child["error_message"] == "provider rejected request"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tool_output_carries_the_status_it_parked_the_agent_in() -> None:
|
||||
runtime = GoTuiRuntime(args())
|
||||
await runtime.coordinator.register("root", "Strix", parent_id=None)
|
||||
await runtime._sync_agent_state()
|
||||
notified: list[str] = []
|
||||
runtime.controller._on_change = lambda: notified.append(
|
||||
runtime.live_view.agents["root"]["status"]
|
||||
)
|
||||
|
||||
await runtime.coordinator.park_waiting("root", wait_kind="agents")
|
||||
output = SimpleNamespace(
|
||||
type="tool_call_output_item",
|
||||
raw_item={"call_id": "call-1", "type": "function_call_output"},
|
||||
output=json.dumps({"success": True, "wait_outcome": "waiting"}),
|
||||
)
|
||||
runtime.capture_event("root", SimpleNamespace(type="run_item_stream_event", item=output))
|
||||
await asyncio.gather(*runtime._output_syncs)
|
||||
|
||||
assert notified == ["waiting"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_agent_state_sync_projects_completed_report() -> None:
|
||||
runtime = GoTuiRuntime(args())
|
||||
@@ -1027,3 +1050,29 @@ async def test_prepare_and_start_runs_the_scan_after_preparation(
|
||||
|
||||
assert order == ["preflight", "persist", "prepare", "telemetry", "state", "scan"]
|
||||
assert runtime.controller.scan_state == "running"
|
||||
|
||||
|
||||
def test_sync_fingerprint_tracks_report_revisions(tmp_path: Path) -> None:
|
||||
runtime = GoTuiRuntime(args())
|
||||
runtime.report_state = ReportState(run_name="test-run")
|
||||
runtime.report_state.vulnerability_reports = [{"id": "vuln-0001", "title": "Old title"}]
|
||||
runtime.report_state.get_run_dir = lambda: tmp_path # type: ignore[method-assign]
|
||||
|
||||
report = runtime.report_state.vulnerability_reports[0]
|
||||
timestamp = "2026-09-09 10:00:00 UTC"
|
||||
|
||||
before = runtime._runtime_sync_fingerprint()
|
||||
report.update(
|
||||
{
|
||||
"title": "New title",
|
||||
"updated_at": timestamp,
|
||||
"update_history": [{"timestamp": timestamp, "fields": ["title"]}],
|
||||
}
|
||||
)
|
||||
first_revision = runtime._runtime_sync_fingerprint()
|
||||
assert first_revision != before
|
||||
|
||||
report["title"] = "Newer title"
|
||||
report["update_history"].append({"timestamp": timestamp, "fields": ["title"]})
|
||||
|
||||
assert runtime._runtime_sync_fingerprint() != first_revision
|
||||
@@ -70,7 +70,6 @@ def _cache_points(model_name: str) -> Any:
|
||||
def test_make_model_settings_enables_prompt_cache_for_bedrock_claude() -> None:
|
||||
assert _cache_points("bedrock/global.anthropic.claude-opus-4-8") == [
|
||||
{"location": "message", "role": "system"},
|
||||
{"location": "tool_config"},
|
||||
{"location": "message", "index": -1},
|
||||
]
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -30,7 +30,26 @@ def _local_target(target_path: str) -> dict[str, Any]:
|
||||
def test_collect_local_sources_protects_the_users_own_git() -> None:
|
||||
sources = collect_local_sources([_local_target("/code")])
|
||||
assert sources == [
|
||||
{"source_path": "/code", "workspace_subdir": "repo", "protect_metadata": True}
|
||||
{
|
||||
"source_path": "/code",
|
||||
"workspace_subdir": "repo",
|
||||
"protect_metadata": True,
|
||||
"read_only": False,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_collect_local_sources_forwards_read_only() -> None:
|
||||
target = _local_target("/layout")
|
||||
target["details"]["read_only"] = True
|
||||
sources = collect_local_sources([target])
|
||||
assert sources == [
|
||||
{
|
||||
"source_path": "/layout",
|
||||
"workspace_subdir": "repo",
|
||||
"protect_metadata": True,
|
||||
"read_only": True,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
|
||||
+279
-11
@@ -36,12 +36,15 @@ from strix.tools.mcp import (
|
||||
attach_mcp_requests,
|
||||
call_mcp,
|
||||
describe_mcp,
|
||||
get_mcp_tool_schema,
|
||||
list_mcps,
|
||||
load_user_mcp_configs,
|
||||
namespaced_tool_name,
|
||||
resolve_mcp_call,
|
||||
search_mcp_tools,
|
||||
)
|
||||
from strix.tools.mcp import client as mcp_client
|
||||
from strix.tools.mcp import registry as mcp_registry_mod
|
||||
from strix.tools.mcp import session as mcp_session_mod
|
||||
|
||||
|
||||
@@ -434,8 +437,16 @@ async def test_list_mcps_returns_connections_with_ids_and_descriptions() -> None
|
||||
"description": "local files",
|
||||
"tool_count": 2,
|
||||
"dead": False,
|
||||
"state": "connected",
|
||||
},
|
||||
{
|
||||
"id": "db",
|
||||
"name": "db",
|
||||
"description": None,
|
||||
"tool_count": 1,
|
||||
"dead": False,
|
||||
"state": "connected",
|
||||
},
|
||||
{"id": "db", "name": "db", "description": None, "tool_count": 1, "dead": False},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -485,6 +496,247 @@ async def test_describe_mcp_without_any_connections() -> None:
|
||||
assert out == "No MCP connections are configured for this run."
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_then_get_one_schema_without_returning_other_schemas() -> None:
|
||||
registry = McpRegistry()
|
||||
registry.add(
|
||||
name="docs",
|
||||
server=FakeMCPServer(
|
||||
"docs",
|
||||
[
|
||||
_mcp_tool("find_pages"),
|
||||
MCPTool(
|
||||
name="fetch_page",
|
||||
description="Fetch page content",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {"page_id": {"type": "string"}},
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
config=McpConnectionConfig(
|
||||
name="docs",
|
||||
url="https://example.invalid/mcp",
|
||||
active_tools=["fetch_page"],
|
||||
),
|
||||
)
|
||||
|
||||
searched = await search_mcp_tools.on_invoke_tool(
|
||||
_ctx(registry),
|
||||
json.dumps({"connection": "docs", "query": "page content"}),
|
||||
)
|
||||
assert [match["name"] for match in searched["matches"]] == [
|
||||
"fetch_page",
|
||||
"find_pages",
|
||||
]
|
||||
assert all("input_schema" not in match for match in searched["matches"])
|
||||
|
||||
schema = await get_mcp_tool_schema.on_invoke_tool(
|
||||
_ctx(registry),
|
||||
json.dumps({"connection": "docs", "tool": "fetch_page"}),
|
||||
)
|
||||
assert schema["input_schema"]["properties"] == {"page_id": {"type": "string"}}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_matches_any_query_term_and_prioritizes_active_tools() -> None:
|
||||
registry = McpRegistry()
|
||||
registry.add(
|
||||
name="docs",
|
||||
server=FakeMCPServer(
|
||||
"docs",
|
||||
[
|
||||
MCPTool(
|
||||
name="fetch_page",
|
||||
description="Read page content",
|
||||
inputSchema={"type": "object"},
|
||||
),
|
||||
MCPTool(
|
||||
name="search_documents",
|
||||
description="Search documents",
|
||||
inputSchema={"type": "object"},
|
||||
),
|
||||
MCPTool(
|
||||
name="list_records",
|
||||
description="List records",
|
||||
inputSchema={"type": "object"},
|
||||
),
|
||||
],
|
||||
),
|
||||
config=McpConnectionConfig(
|
||||
name="docs",
|
||||
url="https://example.invalid/mcp",
|
||||
active_tools=["fetch_page"],
|
||||
),
|
||||
)
|
||||
|
||||
searched = await search_mcp_tools.on_invoke_tool(
|
||||
_ctx(registry),
|
||||
json.dumps(
|
||||
{
|
||||
"connection": "docs",
|
||||
"query": "read list search document",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
assert [match["name"] for match in searched["matches"]] == [
|
||||
"fetch_page",
|
||||
"search_documents",
|
||||
"list_records",
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_returns_active_tools_when_text_does_not_match() -> None:
|
||||
registry = McpRegistry()
|
||||
registry.add(
|
||||
name="docs",
|
||||
server=FakeMCPServer(
|
||||
"docs",
|
||||
[
|
||||
_mcp_tool("fetch_page"),
|
||||
_mcp_tool("search_documents"),
|
||||
],
|
||||
),
|
||||
config=McpConnectionConfig(
|
||||
name="docs",
|
||||
url="https://example.invalid/mcp",
|
||||
active_tools=["fetch_page"],
|
||||
),
|
||||
)
|
||||
|
||||
searched = await search_mcp_tools.on_invoke_tool(
|
||||
_ctx(registry),
|
||||
json.dumps(
|
||||
{
|
||||
"connection": "docs",
|
||||
"query": "unrelated capability",
|
||||
}
|
||||
),
|
||||
)
|
||||
|
||||
assert searched["matches"] == [
|
||||
{
|
||||
"name": "fetch_page",
|
||||
"description": "remote tool fetch_page",
|
||||
"active": True,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_registered_entry_connects_and_lists_once_for_concurrent_catalog_requests(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
calls = {"build": 0, "list": 0}
|
||||
|
||||
class _CountingServer(FakeMCPServer):
|
||||
async def list_tools(
|
||||
self,
|
||||
run_context: Any = None,
|
||||
agent: Any = None,
|
||||
) -> list[MCPTool]:
|
||||
calls["list"] += 1
|
||||
await asyncio.sleep(0)
|
||||
return await super().list_tools(run_context, agent)
|
||||
|
||||
server = _CountingServer("docs", [_mcp_tool("fetch_page")])
|
||||
|
||||
def _build(_config: McpConnectionConfig) -> mcp_client.BuiltMcpServer:
|
||||
calls["build"] += 1
|
||||
return _built_server(server)
|
||||
|
||||
monkeypatch.setattr(mcp_client, "_build_server", _build)
|
||||
registry = McpRegistry()
|
||||
entry = registry.register(McpConnectionRequest(config=_config("docs", ["fetch_page"])))
|
||||
assert entry.state == "configured"
|
||||
assert calls == {"build": 0, "list": 0}
|
||||
|
||||
first, second = await asyncio.gather(entry.ensure_catalog(), entry.ensure_catalog())
|
||||
|
||||
assert [tool.name for tool in first] == ["fetch_page"]
|
||||
assert second is first
|
||||
assert calls == {"build": 1, "list": 1}
|
||||
assert registry.statuses()[0].state == "catalog_ready"
|
||||
await registry.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_registered_entry_replaces_a_terminally_dead_session(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
config = _config("docs", ["fetch_page"])
|
||||
original = SupervisedMcpSession.adopt(
|
||||
FakeMCPServer("docs", [_mcp_tool("fetch_page")]),
|
||||
name="docs",
|
||||
config=config,
|
||||
)
|
||||
replacement_server = FakeMCPServer("docs", [_mcp_tool("fetch_page")])
|
||||
monkeypatch.setattr(
|
||||
mcp_client,
|
||||
"_build_server",
|
||||
lambda _config: _built_server(replacement_server),
|
||||
)
|
||||
monkeypatch.setattr(mcp_registry_mod, "_RETRY_DELAY_SECONDS", 0)
|
||||
registry = McpRegistry()
|
||||
entry = registry.add(name="docs", session=original, config=config)
|
||||
|
||||
original._mark_dead()
|
||||
|
||||
assert entry.session is None
|
||||
replacement = await entry.ensure_connected()
|
||||
assert replacement is not original
|
||||
assert replacement.server is replacement_server
|
||||
assert entry.state == "connected"
|
||||
await registry.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_registry_warmup_bounds_parallel_connections(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
active = 0
|
||||
maximum = 0
|
||||
two_started = asyncio.Event()
|
||||
release = asyncio.Event()
|
||||
|
||||
class _BlockedConnectServer(FakeMCPServer):
|
||||
async def connect(self) -> None:
|
||||
nonlocal active, maximum
|
||||
active += 1
|
||||
maximum = max(maximum, active)
|
||||
if active == 2:
|
||||
two_started.set()
|
||||
try:
|
||||
await release.wait()
|
||||
finally:
|
||||
active -= 1
|
||||
|
||||
servers = {
|
||||
f"docs-{index}": _BlockedConnectServer(f"docs-{index}", [_mcp_tool("fetch_page")])
|
||||
for index in range(4)
|
||||
}
|
||||
monkeypatch.setattr(
|
||||
mcp_client,
|
||||
"_build_server",
|
||||
lambda config: _built_server(servers[config.name]),
|
||||
)
|
||||
registry = McpRegistry()
|
||||
for name in servers:
|
||||
registry.register(McpConnectionRequest(config=_config(name, ["fetch_page"])))
|
||||
|
||||
warmup = registry.start_warmup(max_concurrency=2)
|
||||
await asyncio.wait_for(two_started.wait(), timeout=1)
|
||||
assert maximum == 2
|
||||
release.set()
|
||||
await warmup
|
||||
|
||||
assert [summary.state for summary in registry.summaries()] == ["connected"] * 4
|
||||
await registry.close()
|
||||
|
||||
|
||||
# --- call_mcp ----------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -573,7 +825,8 @@ async def test_call_mcp_errors_on_unknown_tool() -> None:
|
||||
)
|
||||
|
||||
assert "Unknown tool 'delete_everything'" in out
|
||||
assert "read_file" in out
|
||||
assert "search_mcp_tools" in out
|
||||
assert "read_file" not in out
|
||||
# A rejected tool name never reaches the server.
|
||||
assert server.calls == []
|
||||
|
||||
@@ -631,21 +884,27 @@ async def test_call_mcp_flags_an_errored_result_failed_for_the_tui() -> None:
|
||||
assert out == {"type": "text", "text": "boom:read_file", "success": False}
|
||||
|
||||
|
||||
# --- the two tools are the only MCP surface every agent gets -----------------
|
||||
# --- generic MCP tools are the only MCP surface every agent gets -------------
|
||||
|
||||
|
||||
def test_agent_carries_exactly_the_dispatch_tools_regardless_of_connections() -> None:
|
||||
def test_agent_carries_exactly_the_generic_mcp_tools_regardless_of_connections() -> None:
|
||||
"""No matter how many MCP connections a run makes, an agent's tool list gains
|
||||
exactly list_mcps, describe_mcp, and call_mcp and never a per-connection
|
||||
provider tool."""
|
||||
exactly the generic MCP tools and never a per-connection provider tool."""
|
||||
root = factory.build_strix_agent(is_root=True)
|
||||
child = factory.build_strix_agent(is_root=False)
|
||||
|
||||
root_names = [t.name for t in root.tools]
|
||||
child_names = [t.name for t in child.tools]
|
||||
|
||||
assert {"list_mcps", "describe_mcp", "call_mcp"} <= set(root_names)
|
||||
assert {"list_mcps", "describe_mcp", "call_mcp"} <= set(child_names)
|
||||
expected = {
|
||||
"list_mcps",
|
||||
"search_mcp_tools",
|
||||
"get_mcp_tool_schema",
|
||||
"describe_mcp",
|
||||
"call_mcp",
|
||||
}
|
||||
assert expected <= set(root_names)
|
||||
assert expected <= set(child_names)
|
||||
|
||||
# Five hypothetical connections would once have added ~all their tools as
|
||||
# namespaced provider tools; none of those names may appear now.
|
||||
@@ -666,16 +925,25 @@ def test_agent_carries_exactly_the_dispatch_tools_regardless_of_connections() ->
|
||||
# --- prompt guidance replaces the old per-connection inventory ---------------
|
||||
|
||||
|
||||
def test_prompt_renders_static_three_tool_guidance_when_mcp_available() -> None:
|
||||
def test_prompt_renders_targeted_tool_guidance_when_mcp_available() -> None:
|
||||
prompt = render_system_prompt(system_prompt_context={"mcp_available": True})
|
||||
|
||||
assert "MCP CONNECTIONS" in prompt
|
||||
# The three discovery/dispatch tools are named as the way in.
|
||||
# Targeted discovery, one-schema lookup, dispatch, and compatibility are named.
|
||||
assert "list_mcps" in prompt
|
||||
assert "search_mcp_tools" in prompt
|
||||
assert "get_mcp_tool_schema" in prompt
|
||||
assert "describe_mcp" in prompt
|
||||
assert "call_mcp" in prompt
|
||||
|
||||
|
||||
def test_model_facing_mcp_guidance_uses_targeted_discovery() -> None:
|
||||
assert "describe_mcp" not in list_mcps.description
|
||||
assert "describe_mcp" not in call_mcp.description
|
||||
assert "full tool catalog" in describe_mcp.description
|
||||
assert "compatibility fallback" in describe_mcp.description
|
||||
|
||||
|
||||
def test_prompt_has_no_mcp_section_without_availability() -> None:
|
||||
assert "MCP CONNECTIONS" not in render_system_prompt(system_prompt_context={})
|
||||
|
||||
@@ -683,7 +951,7 @@ def test_prompt_has_no_mcp_section_without_availability() -> None:
|
||||
def test_prompt_renders_named_connection_inventory() -> None:
|
||||
"""With mcp_available set, the prompt names each connected server (name, tool
|
||||
count, purpose) so every agent sees what is available at the start, alongside
|
||||
the three dispatch tools for re-listing and inspecting them at run time."""
|
||||
the discovery and dispatch tools for re-listing and inspecting them at run time."""
|
||||
prompt = render_system_prompt(
|
||||
system_prompt_context={
|
||||
"mcp_available": True,
|
||||
|
||||
+61
-4
@@ -2,20 +2,28 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import litellm
|
||||
import pytest
|
||||
from agents.extensions.models.litellm_model import LitellmModel
|
||||
from agents.model_settings import ModelSettings
|
||||
from agents.models import _openai_shared
|
||||
from agents.models.openai_chatcompletions import OpenAIChatCompletionsModel
|
||||
from agents.models.openai_responses import OpenAIResponsesModel
|
||||
|
||||
from strix.config.models import (
|
||||
RECOMMENDED_MODEL_NAMES,
|
||||
StrixProvider,
|
||||
_NonStreamingModel,
|
||||
_TurnGuardModel,
|
||||
configure_sdk_model_defaults,
|
||||
is_recommended_or_frontier_model,
|
||||
request_timeout_extra_args,
|
||||
routes_through_litellm,
|
||||
supports_strict_tool_schemas,
|
||||
uses_chat_completions_tool_schema,
|
||||
)
|
||||
from strix.config.settings import Settings
|
||||
from strix.llm.request_log import RequestLoggingModel
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model_name", RECOMMENDED_MODEL_NAMES)
|
||||
@@ -79,6 +87,14 @@ def test_recommended_models_are_matched_case_insensitively() -> None:
|
||||
"zai/glm-5.3-flash",
|
||||
"openrouter/z-ai/glm-5.3",
|
||||
"novita/zai-org/glm-5.2",
|
||||
"openai/glm-5.3",
|
||||
"openai/zai-org/glm-5.3",
|
||||
"hosted_vllm/glm-5.3",
|
||||
"openai/claude-opus-4-8",
|
||||
"openai/deepseek-v4-pro",
|
||||
"custom-ollama/gpt-5-mini-local",
|
||||
"custom-provider/claude-opus-4-local",
|
||||
"custom-provider/glm-5.3-local",
|
||||
],
|
||||
)
|
||||
def test_frontier_model_families_are_accepted(model_name: str) -> None:
|
||||
@@ -93,15 +109,13 @@ def test_frontier_model_families_are_accepted(model_name: str) -> None:
|
||||
"anthropic/claude-3-5-sonnet-latest",
|
||||
"ollama/llama3.1",
|
||||
"deepseek/deepseek-chat",
|
||||
"custom-ollama/gpt-5-mini-local",
|
||||
"custom-provider/claude-opus-4-local",
|
||||
"xai/grok-4.5",
|
||||
"openrouter/x-ai/grok-4",
|
||||
"mistral/mistral-medium-3-5",
|
||||
"mistral/magistral-medium-latest",
|
||||
"zai/glm-4.7",
|
||||
"openai/glm-4.7",
|
||||
"openrouter/z-ai/glm-5",
|
||||
"custom-provider/glm-5.3-local",
|
||||
],
|
||||
)
|
||||
def test_non_frontier_models_are_rejected(model_name: str) -> None:
|
||||
@@ -159,6 +173,49 @@ def test_routes_through_litellm_matches_the_provider(
|
||||
# proves the route is not LiteLLM's.
|
||||
assert not litellm
|
||||
return
|
||||
while isinstance(model, _NonStreamingModel | _TurnGuardModel):
|
||||
while isinstance(model, _NonStreamingModel | _TurnGuardModel | RequestLoggingModel):
|
||||
model = model._inner
|
||||
assert isinstance(model, LitellmModel) is litellm
|
||||
|
||||
|
||||
def test_api_type_override_settings(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setenv("STRIX_LLM", "gpt-4")
|
||||
monkeypatch.setenv("STRIX_API_TYPE", "chat_completions")
|
||||
assert uses_chat_completions_tool_schema("gpt-4", Settings()) is True
|
||||
monkeypatch.setenv("STRIX_LLM", "openai/gpt-4")
|
||||
monkeypatch.setenv("STRIX_API_TYPE", "responses")
|
||||
assert uses_chat_completions_tool_schema("openai/gpt-4", Settings()) is False
|
||||
monkeypatch.setenv("STRIX_LLM", "anthropic/claude-sonnet-4-5")
|
||||
assert uses_chat_completions_tool_schema("anthropic/claude-sonnet-4-5", Settings()) is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("api_type", "expected"),
|
||||
[
|
||||
(None, OpenAIChatCompletionsModel),
|
||||
("chat_completions", OpenAIChatCompletionsModel),
|
||||
("responses", OpenAIResponsesModel),
|
||||
],
|
||||
)
|
||||
def test_api_type_overrides_the_api_base_route(
|
||||
monkeypatch: pytest.MonkeyPatch, api_type: str | None, expected: type
|
||||
) -> None:
|
||||
"""``LLM_API_BASE`` defaults to chat completions. ``STRIX_API_TYPE`` must win."""
|
||||
monkeypatch.setattr(_openai_shared, "_use_responses_by_default", True)
|
||||
monkeypatch.setattr(_openai_shared, "_default_openai_client", None)
|
||||
monkeypatch.setattr(_openai_shared, "_default_openai_key", None)
|
||||
monkeypatch.setattr(litellm, "api_key", None)
|
||||
monkeypatch.setattr(litellm, "api_base", None)
|
||||
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
|
||||
monkeypatch.setenv("OPENAI_BASE_URL", "")
|
||||
monkeypatch.setenv("STRIX_LLM", "gpt-5")
|
||||
monkeypatch.setenv("LLM_API_KEY", "test-key")
|
||||
monkeypatch.setenv("LLM_API_BASE", "https://gateway.example/v1")
|
||||
monkeypatch.delenv("STRIX_API_TYPE", raising=False)
|
||||
if api_type is not None:
|
||||
monkeypatch.setenv("STRIX_API_TYPE", api_type)
|
||||
configure_sdk_model_defaults(Settings())
|
||||
model = StrixProvider().get_model("gpt-5")
|
||||
while isinstance(model, _NonStreamingModel | _TurnGuardModel | RequestLoggingModel):
|
||||
model = model._inner
|
||||
assert isinstance(model, expected)
|
||||
@@ -9,6 +9,7 @@ call at a time against the shared client.
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from types import SimpleNamespace
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import pytest
|
||||
@@ -227,3 +228,27 @@ async def test_ctx_client_degrades_when_bootstrap_failed() -> None:
|
||||
|
||||
handle = CaidoBootstrapHandle(asyncio.ensure_future(_bootstrap()))
|
||||
assert await tools._ctx_client(cast("Any", _Ctx({"caido_client": handle}))) is None
|
||||
|
||||
|
||||
async def test_existing_request_ids_queries_current_project(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
client = _FakeClient("host")
|
||||
looked_up: list[str] = []
|
||||
|
||||
async def get_request_with_client(passed_client: Any, request_id: str) -> Any:
|
||||
assert passed_client is client
|
||||
looked_up.append(request_id)
|
||||
if request_id == "1042":
|
||||
return SimpleNamespace(request=SimpleNamespace(id="1042"))
|
||||
return None
|
||||
|
||||
monkeypatch.setattr(caido_api, "get_request_with_client", get_request_with_client)
|
||||
|
||||
existing = await tools.existing_request_ids(
|
||||
cast("Any", _Ctx({"caido_client": client})),
|
||||
["1042", "1088"],
|
||||
)
|
||||
|
||||
assert existing == {"1042"}
|
||||
assert looked_up == ["1042", "1088"]
|
||||
@@ -2,23 +2,31 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
import json
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import pytest
|
||||
from agents.tool_context import ToolContext
|
||||
|
||||
from strix.report.dedupe import (
|
||||
_check_dependency_duplicate,
|
||||
_prepare_report_for_comparison,
|
||||
check_duplicate,
|
||||
)
|
||||
from strix.report.state import ReportState, set_global_report_state
|
||||
from strix.report.state import ReportRollbackError, ReportState, set_global_report_state
|
||||
from strix.report.writer import write_run_record
|
||||
from strix.tools.finish.tool import finish_scan
|
||||
from strix.tools.reporting import tool as reporting_tool
|
||||
from strix.tools.reporting.tool import (
|
||||
_do_create,
|
||||
_do_create_dependency,
|
||||
_do_delete,
|
||||
_do_update,
|
||||
_normalize_http_exchange_ids,
|
||||
_verify_http_exchange_ids,
|
||||
create_dependency_report,
|
||||
create_vulnerability_report,
|
||||
delete_vulnerability_report,
|
||||
update_vulnerability_report,
|
||||
)
|
||||
|
||||
@@ -115,6 +123,7 @@ async def test_create_report_persists_new_fields(report_state: ReportState) -> N
|
||||
cve=None,
|
||||
cwe="CWE-79",
|
||||
code_locations=None,
|
||||
http_exchange_ids=["1042", "1042", "1088"],
|
||||
fix_pr_body="## Fix\nEncode output.",
|
||||
)
|
||||
assert result["success"] is True
|
||||
@@ -127,6 +136,51 @@ async def test_create_report_persists_new_fields(report_state: ReportState) -> N
|
||||
assert report["counterevidence"] == "No output encoding or CSP observed on this response."
|
||||
assert report["confidence"] == "high"
|
||||
assert report["severity_change_conditions"] == "A strict CSP would lower the severity."
|
||||
assert report["http_exchange_ids"] == ["1042", "1088"]
|
||||
|
||||
|
||||
def test_create_report_does_not_commit_when_callback_fails(
|
||||
report_state: ReportState,
|
||||
) -> None:
|
||||
def fail_persistence(_report: dict[str, Any]) -> None:
|
||||
raise RuntimeError("persistence failed")
|
||||
|
||||
report_state.vulnerability_found_callback = fail_persistence
|
||||
|
||||
with pytest.raises(RuntimeError, match="persistence failed"):
|
||||
report_state.add_vulnerability_report(
|
||||
title="Unstored finding",
|
||||
severity="high",
|
||||
http_exchange_ids=["1042"],
|
||||
)
|
||||
|
||||
assert report_state.vulnerability_reports == []
|
||||
|
||||
|
||||
def test_failed_revision_keeps_old_evidence_and_can_be_retried(
|
||||
report_state: ReportState,
|
||||
) -> None:
|
||||
report_id = report_state.add_vulnerability_report(
|
||||
title="Original finding", severity="high", http_exchange_ids=["1042"]
|
||||
)
|
||||
original = dict(report_state.vulnerability_reports[0])
|
||||
|
||||
def fail_persistence(revised: dict[str, Any]) -> None:
|
||||
assert revised["http_exchange_ids"] == ["1088"]
|
||||
assert report_state.vulnerability_reports[0] == original
|
||||
raise RuntimeError("persistence failed")
|
||||
|
||||
report_state.vulnerability_updated_callback = fail_persistence
|
||||
changes = {"title": "Revised finding", "http_exchange_ids": ["1088"]}
|
||||
with pytest.raises(RuntimeError, match="persistence failed"):
|
||||
report_state.update_vulnerability_report(report_id, changes)
|
||||
assert report_state.vulnerability_reports[0] == original
|
||||
|
||||
report_state.vulnerability_updated_callback = None
|
||||
revised = report_state.update_vulnerability_report(report_id, changes)
|
||||
assert revised is not None
|
||||
assert revised["http_exchange_ids"] == ["1088"]
|
||||
assert len(revised["update_history"]) == 1
|
||||
|
||||
|
||||
async def test_create_report_requires_evidence_and_assumptions(
|
||||
@@ -1038,9 +1092,25 @@ def test_tool_descriptions_include_formatting_guidance() -> None:
|
||||
assert "reachab" in dep_desc.lower()
|
||||
|
||||
|
||||
def test_git_blame_hint_is_a_trailing_note() -> None:
|
||||
desc = create_vulnerability_report.description
|
||||
assert desc.count("blame") == 1
|
||||
tail = desc[desc.index("Nice to have:") :]
|
||||
assert "git blame" in tail
|
||||
assert "technical_analysis" in tail
|
||||
assert "Example" not in tail
|
||||
assert "blame" not in update_vulnerability_report.description
|
||||
|
||||
|
||||
def test_vuln_tool_exposes_new_params() -> None:
|
||||
props = create_vulnerability_report.params_json_schema["properties"]
|
||||
for field in ("evidence", "assumptions", "fix_effort", "fix_pr_body"):
|
||||
for field in (
|
||||
"evidence",
|
||||
"assumptions",
|
||||
"fix_effort",
|
||||
"fix_pr_body",
|
||||
"http_exchange_ids",
|
||||
):
|
||||
assert field in props
|
||||
|
||||
dep_props = create_dependency_report.params_json_schema["properties"]
|
||||
@@ -1355,6 +1425,158 @@ def test_update_vulnerability_report_records_chained_impact(report_state: Report
|
||||
assert report_state.update_vulnerability_report("vuln-0404", {"severity": "high"}) is None
|
||||
|
||||
|
||||
def test_update_replaces_http_exchange_ids(report_state: ReportState) -> None:
|
||||
_seed_weak_report(report_state)
|
||||
|
||||
result = _do_update(
|
||||
report_id="vuln-0009",
|
||||
update_reason="A replay produced a clearer proving exchange.",
|
||||
fields={"http_exchange_ids": ["204", "204", "205"]},
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert report_state.vulnerability_reports[0]["http_exchange_ids"] == ["204", "205"]
|
||||
|
||||
|
||||
async def test_create_rejects_invalid_http_exchange_ids(report_state: ReportState) -> None:
|
||||
result = await _do_create(
|
||||
**_CONFIRMED_KWARGS,
|
||||
http_exchange_ids=["ok", "contains space"],
|
||||
)
|
||||
|
||||
assert result["success"] is False
|
||||
assert any("visible ASCII" in error for error in result["errors"])
|
||||
assert report_state.vulnerability_reports == []
|
||||
|
||||
|
||||
def test_http_exchange_id_limit_applies_after_deduplication() -> None:
|
||||
request_ids, errors = _normalize_http_exchange_ids(["1042"] * 11)
|
||||
|
||||
assert errors == []
|
||||
assert request_ids == ["1042"]
|
||||
|
||||
|
||||
async def test_http_exchange_ids_must_exist_in_current_proxy_project(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
async def existing_request_ids(
|
||||
_ctx: Any,
|
||||
_request_ids: list[str],
|
||||
) -> set[str]:
|
||||
return {"1042"}
|
||||
|
||||
monkeypatch.setattr(reporting_tool, "existing_request_ids", existing_request_ids)
|
||||
|
||||
request_ids, errors, warning = await _verify_http_exchange_ids(
|
||||
cast("Any", object()),
|
||||
["1042", "1088"],
|
||||
)
|
||||
|
||||
assert request_ids is None
|
||||
assert errors == ["http_exchange_ids do not exist in the current proxy project: 1088"]
|
||||
assert warning is None
|
||||
|
||||
|
||||
async def test_http_exchange_ids_are_dropped_when_proxy_cannot_be_queried(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
async def existing_request_ids(
|
||||
_ctx: Any,
|
||||
_request_ids: list[str],
|
||||
) -> set[str]:
|
||||
raise RuntimeError("Caido client is not available")
|
||||
|
||||
monkeypatch.setattr(reporting_tool, "existing_request_ids", existing_request_ids)
|
||||
|
||||
request_ids, errors, warning = await _verify_http_exchange_ids(
|
||||
cast("Any", object()),
|
||||
["1042", "1042", "1088"],
|
||||
)
|
||||
|
||||
assert request_ids is None
|
||||
assert errors == []
|
||||
assert warning is not None
|
||||
assert "not stored" in warning
|
||||
assert "update_vulnerability_report" in warning
|
||||
|
||||
|
||||
async def test_create_reports_persistence_failure_as_tool_error(
|
||||
report_state: ReportState,
|
||||
) -> None:
|
||||
def fail_persistence(_report: dict[str, Any]) -> None:
|
||||
raise RuntimeError("persistence failed")
|
||||
|
||||
report_state.vulnerability_found_callback = fail_persistence
|
||||
|
||||
result = await _do_create(**_CONFIRMED_KWARGS, http_exchange_ids=["1042"])
|
||||
|
||||
assert result["success"] is False
|
||||
assert "persistence failed" in result["error"]
|
||||
assert "file it again" in result["error"]
|
||||
assert report_state.vulnerability_reports == []
|
||||
|
||||
|
||||
async def test_evidence_only_update_reports_proxy_outage_as_retryable(
|
||||
report_state: ReportState,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
_seed_weak_report(report_state)
|
||||
original = dict(report_state.vulnerability_reports[0])
|
||||
|
||||
async def existing_request_ids(
|
||||
_ctx: Any,
|
||||
_request_ids: list[str],
|
||||
) -> set[str]:
|
||||
raise RuntimeError("Caido client is not available")
|
||||
|
||||
monkeypatch.setattr(reporting_tool, "existing_request_ids", existing_request_ids)
|
||||
|
||||
ctx = ToolContext(
|
||||
context={"agent_id": "root"},
|
||||
tool_name="update_vulnerability_report",
|
||||
tool_call_id="call-1",
|
||||
tool_arguments="{}",
|
||||
)
|
||||
raw = await update_vulnerability_report.on_invoke_tool(
|
||||
ctx,
|
||||
json.dumps(
|
||||
{
|
||||
"report_id": "vuln-0009",
|
||||
"update_reason": "A replay produced a clearer proving exchange.",
|
||||
"http_exchange_ids": ["204"],
|
||||
}
|
||||
),
|
||||
)
|
||||
result = json.loads(raw)
|
||||
|
||||
assert result["success"] is False
|
||||
assert "No fields to update" not in result["error"]
|
||||
assert "update_vulnerability_report" in result["error"]
|
||||
assert result["report_id"] == "vuln-0009"
|
||||
assert report_state.vulnerability_reports[0] == original
|
||||
|
||||
|
||||
def test_update_reports_persistence_failure_as_tool_error(report_state: ReportState) -> None:
|
||||
_seed_weak_report(report_state)
|
||||
original = dict(report_state.vulnerability_reports[0])
|
||||
|
||||
def fail_persistence(_report: dict[str, Any]) -> None:
|
||||
raise RuntimeError("persistence failed")
|
||||
|
||||
report_state.vulnerability_updated_callback = fail_persistence
|
||||
|
||||
result = _do_update(
|
||||
report_id="vuln-0009",
|
||||
update_reason="A replay produced a clearer proving exchange.",
|
||||
fields={"http_exchange_ids": ["204"]},
|
||||
)
|
||||
|
||||
assert result["success"] is False
|
||||
assert "persistence failed" in result["error"]
|
||||
assert result["report_id"] == "vuln-0009"
|
||||
assert report_state.vulnerability_reports[0] == original
|
||||
|
||||
|
||||
def test_update_vulnerability_report_ignores_identical_content(report_state: ReportState) -> None:
|
||||
_seed_weak_report(report_state)
|
||||
assert report_state.update_vulnerability_report("vuln-0009", {"severity": "medium"}) is None
|
||||
@@ -1757,3 +1979,270 @@ def test_update_refuses_code_locations_it_cannot_use(report_state: ReportState)
|
||||
|
||||
assert result["success"] is False
|
||||
assert any("start_line" in error for error in result["errors"])
|
||||
|
||||
|
||||
def _seed_saved_report(report_state: ReportState) -> Path:
|
||||
"""The weak report, written to disk the way a filed finding is."""
|
||||
_seed_weak_report(report_state)
|
||||
report_state._saved_vuln_ids.clear()
|
||||
report_state.save_run_data()
|
||||
run_dir = report_state._run_dir
|
||||
assert run_dir is not None
|
||||
md_path = run_dir / "vulnerabilities" / "vuln-0009.md"
|
||||
assert md_path.exists()
|
||||
return run_dir
|
||||
|
||||
|
||||
def test_delete_withdraws_a_disproved_report_and_its_artifacts(
|
||||
report_state: ReportState,
|
||||
) -> None:
|
||||
"""A deleted finding leaves the run's state and every rendered artifact."""
|
||||
run_dir = _seed_saved_report(report_state)
|
||||
persisted: list[dict[str, Any]] = []
|
||||
report_state.vulnerability_deleted_callback = persisted.append
|
||||
|
||||
result = _do_delete(
|
||||
report_id="vuln-0009",
|
||||
delete_reason="The cross-tenant read came from the harness reusing the victim's session.",
|
||||
agent_id="aaaa1111",
|
||||
agent_name="Recon Agent",
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert result["action"] == "deleted"
|
||||
assert result["report_id"] == "vuln-0009"
|
||||
assert result["title"].startswith("Directus 11.5.1")
|
||||
assert report_state.vulnerability_reports == []
|
||||
assert not (run_dir / "vulnerabilities" / "vuln-0009.md").exists()
|
||||
assert json.loads((run_dir / "vulnerabilities.json").read_text(encoding="utf-8")) == []
|
||||
assert "vuln-0009" not in (run_dir / "vulnerabilities.csv").read_text(encoding="utf-8")
|
||||
|
||||
assert len(persisted) == 1
|
||||
assert persisted[0]["id"] == "vuln-0009"
|
||||
assert persisted[0]["agent_id"] == "aaaa1111", "the callback sees the finding as filed"
|
||||
deletion = persisted[0]["deletion"]
|
||||
assert deletion["agent_id"] == "aaaa1111"
|
||||
assert deletion["agent_name"] == "Recon Agent"
|
||||
assert deletion["filed_by_agent_id"] == "aaaa1111"
|
||||
assert deletion["reason"].startswith("The cross-tenant read")
|
||||
|
||||
run = json.loads((run_dir / "run.json").read_text(encoding="utf-8"))
|
||||
history = run["deleted_vulnerability_reports"]
|
||||
assert [entry["id"] for entry in history] == ["vuln-0009"]
|
||||
assert history[0]["severity"] == "medium"
|
||||
assert history[0]["deleted_at"]
|
||||
|
||||
|
||||
def test_delete_never_reissues_the_withdrawn_id(report_state: ReportState) -> None:
|
||||
"""The next finding must not inherit a deleted report's id, file or history."""
|
||||
_seed_saved_report(report_state)
|
||||
assert report_state.delete_vulnerability_report(
|
||||
"vuln-0009", delete_reason="Disproved.", deleted_by_agent_id="aaaa1111"
|
||||
)
|
||||
|
||||
new_id = report_state.add_vulnerability_report(title="A real finding", severity="high")
|
||||
|
||||
assert new_id == "vuln-0010"
|
||||
assert [r["id"] for r in report_state.vulnerability_reports] == ["vuln-0010"]
|
||||
|
||||
|
||||
def _assert_report_still_filed(
|
||||
report_state: ReportState, run_dir: Path, original: dict[str, Any]
|
||||
) -> None:
|
||||
"""The report is in memory and in every artifact exactly as it was filed."""
|
||||
assert report_state.vulnerability_reports == [original]
|
||||
assert "vuln-0009" in report_state._saved_vuln_ids
|
||||
assert (run_dir / "vulnerabilities" / "vuln-0009.md").exists()
|
||||
indexed = json.loads((run_dir / "vulnerabilities.json").read_text(encoding="utf-8"))
|
||||
assert [r["id"] for r in indexed] == ["vuln-0009"]
|
||||
assert "vuln-0009" in (run_dir / "vulnerabilities.csv").read_text(encoding="utf-8")
|
||||
run = json.loads((run_dir / "run.json").read_text(encoding="utf-8"))
|
||||
assert "deleted_vulnerability_reports" not in run
|
||||
assert "deleted_vulnerability_reports" not in report_state.run_record
|
||||
assert report_state._next_report_id() == "vuln-0010"
|
||||
|
||||
|
||||
def test_any_agent_in_the_run_may_delete_a_report(report_state: ReportState) -> None:
|
||||
"""Agents in a run share one trust boundary: a report another agent filed can be
|
||||
withdrawn once disproved, and the history keeps both identities apart."""
|
||||
run_dir = _seed_saved_report(report_state)
|
||||
calls: list[dict[str, Any]] = []
|
||||
report_state.vulnerability_deleted_callback = calls.append
|
||||
|
||||
result = _do_delete(
|
||||
report_id="vuln-0009",
|
||||
delete_reason="Disproved.",
|
||||
agent_id="834f79fb",
|
||||
agent_name="Validation Agent",
|
||||
)
|
||||
|
||||
assert result["success"] is True
|
||||
assert report_state.vulnerability_reports == []
|
||||
assert [c["id"] for c in calls] == ["vuln-0009"]
|
||||
assert not (run_dir / "vulnerabilities" / "vuln-0009.md").exists()
|
||||
(deletion,) = report_state.run_record["deleted_vulnerability_reports"]
|
||||
assert deletion["filed_by_agent_id"] == "aaaa1111"
|
||||
assert deletion["agent_id"] == "834f79fb"
|
||||
assert deletion["agent_name"] == "Validation Agent"
|
||||
|
||||
|
||||
def test_delete_reports_persistence_failure_and_keeps_the_report(
|
||||
report_state: ReportState,
|
||||
) -> None:
|
||||
"""Persistence refused after the local indexes were rewritten: they are rewritten back."""
|
||||
run_dir = _seed_saved_report(report_state)
|
||||
original = dict(report_state.vulnerability_reports[0])
|
||||
|
||||
def fail_persistence(_report: dict[str, Any]) -> None:
|
||||
assert report_state.vulnerability_reports == [], "indexes are rewritten first"
|
||||
raise RuntimeError("persistence failed")
|
||||
|
||||
report_state.vulnerability_deleted_callback = fail_persistence
|
||||
|
||||
result = _do_delete(report_id="vuln-0009", delete_reason="Disproved.", agent_id="aaaa1111")
|
||||
|
||||
assert result["success"] is False
|
||||
assert "persistence failed" in result["error"]
|
||||
assert "still on file" in result["error"]
|
||||
_assert_report_still_filed(report_state, run_dir, original)
|
||||
|
||||
|
||||
def test_delete_reports_an_artifact_write_failure_and_keeps_the_report(
|
||||
report_state: ReportState, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""A disk that cannot take the rewritten run record is a failed deletion, not a
|
||||
success with stale artifacts; persistence is never told about it."""
|
||||
run_dir = _seed_saved_report(report_state)
|
||||
original = dict(report_state.vulnerability_reports[0])
|
||||
calls: list[dict[str, Any]] = []
|
||||
report_state.vulnerability_deleted_callback = calls.append
|
||||
attempts = 0
|
||||
|
||||
def write_run_record_once_failing(run_dir_: Path, record: dict[str, Any]) -> None:
|
||||
nonlocal attempts
|
||||
attempts += 1
|
||||
if attempts == 1:
|
||||
raise OSError(28, "No space left on device")
|
||||
(run_dir_ / "run.json").write_text(json.dumps(record), encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr("strix.report.state.write_run_record", write_run_record_once_failing)
|
||||
|
||||
result = _do_delete(report_id="vuln-0009", delete_reason="Disproved.", agent_id="aaaa1111")
|
||||
|
||||
assert result["success"] is False
|
||||
assert "No space left on device" in result["error"]
|
||||
assert "still on file" in result["error"]
|
||||
assert calls == []
|
||||
assert attempts == 2, "the run record is written back after the failed rewrite"
|
||||
_assert_report_still_filed(report_state, run_dir, original)
|
||||
|
||||
|
||||
def test_delete_reports_when_the_rollback_itself_cannot_be_written(
|
||||
report_state: ReportState, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""A disk that stays full: memory is restored, the caller is told the indexes
|
||||
may be stale, and the next save rewrites them."""
|
||||
run_dir = _seed_saved_report(report_state)
|
||||
original = dict(report_state.vulnerability_reports[0])
|
||||
calls: list[dict[str, Any]] = []
|
||||
report_state.vulnerability_deleted_callback = calls.append
|
||||
disk_full = True
|
||||
|
||||
def write_run_record_while_disk_full(run_dir_: Path, record: dict[str, Any]) -> None:
|
||||
if disk_full:
|
||||
raise OSError(28, "No space left on device")
|
||||
write_run_record(run_dir_, record)
|
||||
|
||||
monkeypatch.setattr("strix.report.state.write_run_record", write_run_record_while_disk_full)
|
||||
|
||||
with pytest.raises(ReportRollbackError) as excinfo:
|
||||
report_state.delete_vulnerability_report(
|
||||
"vuln-0009", delete_reason="Disproved.", deleted_by_agent_id="aaaa1111"
|
||||
)
|
||||
|
||||
assert isinstance(excinfo.value.cause, OSError)
|
||||
assert calls == []
|
||||
assert report_state.vulnerability_reports == [original]
|
||||
assert "vuln-0009" in report_state._saved_vuln_ids
|
||||
assert "deleted_vulnerability_reports" not in report_state.run_record
|
||||
|
||||
result = _do_delete(report_id="vuln-0009", delete_reason="Disproved.", agent_id="aaaa1111")
|
||||
assert result["success"] is False
|
||||
assert "No space left on device" in result["error"]
|
||||
assert "may be stale" in result["error"]
|
||||
|
||||
disk_full = False
|
||||
report_state.save_run_data()
|
||||
_assert_report_still_filed(report_state, run_dir, original)
|
||||
|
||||
|
||||
def test_delete_can_be_retried_after_persistence_recovers(report_state: ReportState) -> None:
|
||||
run_dir = _seed_saved_report(report_state)
|
||||
outcomes = iter([RuntimeError("persistence failed"), None])
|
||||
|
||||
def flaky_persistence(_report: dict[str, Any]) -> None:
|
||||
outcome = next(outcomes)
|
||||
if outcome is not None:
|
||||
raise outcome
|
||||
|
||||
report_state.vulnerability_deleted_callback = flaky_persistence
|
||||
|
||||
first = _do_delete(report_id="vuln-0009", delete_reason="Disproved.", agent_id="aaaa1111")
|
||||
second = _do_delete(report_id="vuln-0009", delete_reason="Disproved.", agent_id="aaaa1111")
|
||||
|
||||
assert first["success"] is False
|
||||
assert second["success"] is True
|
||||
assert report_state.vulnerability_reports == []
|
||||
assert not (run_dir / "vulnerabilities" / "vuln-0009.md").exists()
|
||||
assert json.loads((run_dir / "vulnerabilities.json").read_text(encoding="utf-8")) == []
|
||||
run = json.loads((run_dir / "run.json").read_text(encoding="utf-8"))
|
||||
assert [e["id"] for e in run["deleted_vulnerability_reports"]] == ["vuln-0009"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("report_id", "delete_reason", "expected"),
|
||||
[
|
||||
(" ", "reason", "report_id cannot be empty"),
|
||||
("vuln-0009", " ", "delete_reason cannot be empty"),
|
||||
("vuln-0404", "reason", "not found"),
|
||||
],
|
||||
)
|
||||
def test_delete_rejects_a_call_it_cannot_act_on(
|
||||
report_state: ReportState,
|
||||
report_id: str,
|
||||
delete_reason: str,
|
||||
expected: str,
|
||||
) -> None:
|
||||
_seed_weak_report(report_state)
|
||||
calls: list[dict[str, Any]] = []
|
||||
report_state.vulnerability_deleted_callback = calls.append
|
||||
|
||||
result = _do_delete(report_id=report_id, delete_reason=delete_reason, agent_id="aaaa1111")
|
||||
|
||||
assert result["success"] is False
|
||||
assert expected in result["error"]
|
||||
assert len(report_state.vulnerability_reports) == 1
|
||||
assert calls == [], "nothing reaches persistence for a call that is refused"
|
||||
|
||||
|
||||
async def test_delete_tool_carries_the_caller_identity(report_state: ReportState) -> None:
|
||||
_seed_saved_report(report_state)
|
||||
coordinator = type("Coordinator", (), {"names": {"aaaa1111": "Recon Agent"}})()
|
||||
ctx = ToolContext(
|
||||
context={"agent_id": "aaaa1111", "coordinator": coordinator},
|
||||
tool_name="delete_vulnerability_report",
|
||||
tool_call_id="call-1",
|
||||
tool_arguments="{}",
|
||||
)
|
||||
|
||||
raw = await delete_vulnerability_report.on_invoke_tool(
|
||||
ctx,
|
||||
json.dumps({"report_id": "vuln-0009", "delete_reason": "Disproved on re-test."}),
|
||||
)
|
||||
result = json.loads(raw)
|
||||
|
||||
assert result["success"] is True
|
||||
history = report_state.run_record["deleted_vulnerability_reports"]
|
||||
assert history[0]["agent_id"] == "aaaa1111"
|
||||
assert history[0]["agent_name"] == "Recon Agent"
|
||||
assert "update_vulnerability_report" in delete_vulnerability_report.description
|
||||
+29
-33
@@ -80,13 +80,14 @@ async def test_none_default_attaches_from_the_user_config_file(
|
||||
)
|
||||
monkeypatch.setattr(mcp_pkg, "load_user_mcp_configs", lambda: [file_config])
|
||||
|
||||
captured: list[list[McpConnectionRequest]] = []
|
||||
captured: list[McpConnectionRequest] = []
|
||||
original_register = mcp_pkg.McpRegistry.register
|
||||
|
||||
async def _capture(requests: list[McpConnectionRequest], _registry: Any) -> list[Any]:
|
||||
captured.append(requests)
|
||||
return []
|
||||
def _capture(registry: Any, request: McpConnectionRequest) -> Any:
|
||||
captured.append(request)
|
||||
return original_register(registry, request)
|
||||
|
||||
monkeypatch.setattr(mcp_pkg, "attach_mcp_requests", _capture)
|
||||
monkeypatch.setattr(mcp_pkg.McpRegistry, "register", _capture)
|
||||
|
||||
await runner.run_strix_scan(
|
||||
scan_config={"targets": [], "scan_mode": "deep"},
|
||||
@@ -97,12 +98,11 @@ async def test_none_default_attaches_from_the_user_config_file(
|
||||
|
||||
# Each config from the file is wrapped in a bare request: no provider, no
|
||||
# transform, no explicit purpose (purpose falls back to notes at attach time).
|
||||
(requests,) = captured
|
||||
assert len(requests) == 1
|
||||
assert requests[0].config is file_config
|
||||
assert requests[0].provider is None
|
||||
assert requests[0].result_transform is None
|
||||
assert requests[0].purpose is None
|
||||
(request,) = captured
|
||||
assert request.config is file_config
|
||||
assert request.provider is None
|
||||
assert request.result_transform is None
|
||||
assert request.purpose is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -116,13 +116,14 @@ async def test_supplied_requests_are_attached_and_the_user_file_is_not_read(
|
||||
|
||||
monkeypatch.setattr(mcp_pkg, "load_user_mcp_configs", _fail_if_read)
|
||||
|
||||
captured: list[list[McpConnectionRequest]] = []
|
||||
captured: list[McpConnectionRequest] = []
|
||||
original_register = mcp_pkg.McpRegistry.register
|
||||
|
||||
async def _capture(requests: list[McpConnectionRequest], _registry: Any) -> list[Any]:
|
||||
captured.append(requests)
|
||||
return []
|
||||
def _capture(registry: Any, request: McpConnectionRequest) -> Any:
|
||||
captured.append(request)
|
||||
return original_register(registry, request)
|
||||
|
||||
monkeypatch.setattr(mcp_pkg, "attach_mcp_requests", _capture)
|
||||
monkeypatch.setattr(mcp_pkg.McpRegistry, "register", _capture)
|
||||
|
||||
supplied = [
|
||||
McpConnectionRequest(
|
||||
@@ -139,7 +140,7 @@ async def test_supplied_requests_are_attached_and_the_user_file_is_not_read(
|
||||
mcp_connection_requests=supplied,
|
||||
)
|
||||
|
||||
assert captured == [supplied]
|
||||
assert captured == supplied
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -147,8 +148,8 @@ async def test_roster_is_persisted_even_without_a_status_sink(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Any
|
||||
) -> None:
|
||||
"""The viewer reads the roster off disk, so persistence must not depend on the
|
||||
interface status sink: with ``mcp_status_sink=None`` the connect-time roster is
|
||||
still written, carrying only the non-secret name/provider/tool_count/dead."""
|
||||
interface status sink: with ``mcp_status_sink=None`` the configured roster is
|
||||
still written, carrying only non-secret lifecycle fields."""
|
||||
_wire_runner(monkeypatch, tmp_path)
|
||||
monkeypatch.setattr(
|
||||
mcp_pkg,
|
||||
@@ -156,19 +157,6 @@ async def test_roster_is_persisted_even_without_a_status_sink(
|
||||
lambda: [McpConnectionConfig(name="local_fs", transport="stdio", command="npx")],
|
||||
)
|
||||
|
||||
class _FakeSession:
|
||||
is_dead = False
|
||||
|
||||
def set_on_dead(self, _callback: Any) -> None:
|
||||
return None
|
||||
|
||||
async def _attach(_requests: list[McpConnectionRequest], registry: Any) -> list[Any]:
|
||||
registry.add(name="local_fs", session=_FakeSession(), tool_count=3, provider=None)
|
||||
entry = registry.get("local_fs")
|
||||
return [types.SimpleNamespace(name="local_fs", tool_count=3, session=entry.session)]
|
||||
|
||||
monkeypatch.setattr(mcp_pkg, "attach_mcp_requests", _attach)
|
||||
|
||||
persisted: list[list[dict[str, Any]]] = []
|
||||
|
||||
def _capture_persist(roster: list[dict[str, Any]]) -> None:
|
||||
@@ -185,4 +173,12 @@ async def test_roster_is_persisted_even_without_a_status_sink(
|
||||
)
|
||||
|
||||
assert persisted, "roster must persist even when no status sink is attached"
|
||||
assert persisted[-1] == [{"name": "local_fs", "provider": None, "tool_count": 3, "dead": False}]
|
||||
assert persisted[0] == [
|
||||
{
|
||||
"name": "local_fs",
|
||||
"provider": None,
|
||||
"tool_count": 0,
|
||||
"dead": False,
|
||||
"state": "configured",
|
||||
}
|
||||
]
|
||||
@@ -6,6 +6,7 @@ flow through to the root agent's ``build_strix_agent`` call.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import types
|
||||
from typing import Any
|
||||
|
||||
@@ -17,8 +18,11 @@ from openai import RateLimitError
|
||||
import strix.tools.mcp as mcp_pkg
|
||||
import strix.tools.notes.tools as notes_tools
|
||||
import strix.tools.todo.tools as todo_tools
|
||||
from strix.agents.prompt import render_system_prompt
|
||||
from strix.config.models import _split_cached_prefix
|
||||
from strix.core import runner
|
||||
from strix.core.agents import AgentCoordinator
|
||||
from strix.core.inputs import make_model_settings
|
||||
from strix.runtime import session_manager
|
||||
from strix.tools.mcp import BearerAuth, McpConnectionConfig, McpConnectionRequest
|
||||
|
||||
@@ -132,6 +136,10 @@ async def test_root_prompt_options_flow_into_root_agent(
|
||||
assert "AUTHORIZED TARGETS" in instructions_override
|
||||
assert "https://example.com" in instructions_override
|
||||
assert "CUSTOM SCAN PROMPT" in instructions_override
|
||||
assert instructions_override.count("SYSTEM-VERIFIED SCOPE") == 1
|
||||
assert instructions_override.index("CUSTOM SCAN PROMPT") < instructions_override.index(
|
||||
"SYSTEM-VERIFIED SCOPE"
|
||||
)
|
||||
assert (
|
||||
"cannot expand, replace, or weaken authorized target constraints" in instructions_override
|
||||
)
|
||||
@@ -194,21 +202,12 @@ async def test_mcp_available_flag_set_when_a_connection_attaches(
|
||||
scope_context: dict[str, Any] = {"scope": "built-in"}
|
||||
captured = _patch_engine_scaffold(monkeypatch, tmp_path, scope_context)
|
||||
|
||||
async def _aclose() -> None:
|
||||
return None
|
||||
|
||||
async def _attach(_requests: Any, registry: Any) -> list[Any]:
|
||||
registry.add(name="fs", server=object(), purpose="local files", tool_count=2)
|
||||
session = types.SimpleNamespace(aclose=_aclose)
|
||||
return [types.SimpleNamespace(name="fs", tool_count=2, session=session)]
|
||||
|
||||
monkeypatch.setattr(mcp_pkg, "attach_mcp_requests", _attach)
|
||||
|
||||
request = McpConnectionRequest(
|
||||
config=McpConnectionConfig(
|
||||
name="fs",
|
||||
url="https://mcp.example.com",
|
||||
auth=BearerAuth(token="run-token"),
|
||||
notes="local files",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -224,7 +223,7 @@ async def test_mcp_available_flag_set_when_a_connection_attaches(
|
||||
assert kwargs["system_prompt_context"]["mcp_available"] is True
|
||||
# The named inventory names each connected server for the prompt.
|
||||
assert kwargs["system_prompt_context"]["mcp_connections"] == [
|
||||
{"name": "fs", "purpose": "local files", "tool_count": 2}
|
||||
{"name": "fs", "purpose": "local files", "tool_count": 0}
|
||||
]
|
||||
|
||||
|
||||
@@ -268,3 +267,49 @@ async def test_unknown_tool_calls_are_returned_to_the_model(
|
||||
)
|
||||
|
||||
assert captured["run_config"].tool_not_found_behavior == "return_error_to_model"
|
||||
|
||||
|
||||
def test_scope_is_rendered_once_at_the_end_of_the_prompt() -> None:
|
||||
prompt = render_system_prompt(
|
||||
system_prompt_context={
|
||||
"authorized_targets": [{"type": "web_application", "value": "https://example.com"}],
|
||||
},
|
||||
)
|
||||
|
||||
assert prompt.count("SYSTEM-VERIFIED SCOPE") == 1
|
||||
assert prompt.index("</available_skills>") < prompt.index("SYSTEM-VERIFIED SCOPE")
|
||||
|
||||
|
||||
def test_requested_skills_follow_the_shared_prefix() -> None:
|
||||
xss = render_system_prompt(skills=["xss"], include_scope=False)
|
||||
sqli = render_system_prompt(skills=["sql_injection"], include_scope=False)
|
||||
|
||||
shared = os.path.commonprefix([xss, sqli])
|
||||
assert "</available_skills>" in shared
|
||||
assert shared.count("<cache_point>") == 1
|
||||
assert "<xss>" in xss.split("<cache_point>")[1]
|
||||
|
||||
|
||||
def test_scope_is_sent_as_its_own_system_message_on_cache_point_routes() -> None:
|
||||
settings = make_model_settings(None, model_name="anthropic/claude-sonnet-5-5")
|
||||
prompt = render_system_prompt(
|
||||
system_prompt_context={
|
||||
"authorized_targets": [{"type": "web_application", "value": "https://target.invalid"}],
|
||||
},
|
||||
)
|
||||
|
||||
system, model_input = _split_cached_prefix(prompt, "go", settings)
|
||||
|
||||
assert system is None
|
||||
assert isinstance(model_input, list)
|
||||
assert [item["role"] for item in model_input] == ["system", "system", "user"]
|
||||
assert "https://target.invalid" not in model_input[0]["content"]
|
||||
assert "https://target.invalid" in model_input[1]["content"]
|
||||
assert "<cache_point>" not in model_input[0]["content"] + model_input[1]["content"]
|
||||
|
||||
|
||||
def test_cache_point_marker_is_removed_without_cache_points() -> None:
|
||||
settings = make_model_settings(None, model_name="openai/gpt-5")
|
||||
prompt = "shared\n<cache_point>\ntargets"
|
||||
|
||||
assert _split_cached_prefix(prompt, "go", settings) == ("shared\n\ntargets", "go")
|
||||
Loaded 100 of 106 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user