mirror of
https://github.com/usestrix/strix.git
synced 2026-11-10 05:11:25 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a4f39e1b53 | ||
|
|
fdf8747407 |
No files matched your search
@@ -1,4 +1,4 @@
|
||||
.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
|
||||
.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
|
||||
|
||||
TUI_BINARY := build/sidecar/strix-tui$(if $(filter Windows_NT,$(OS)),.exe)
|
||||
|
||||
@@ -10,9 +10,7 @@ help:
|
||||
@echo ""
|
||||
@echo "Code Quality:"
|
||||
@echo " format - Format 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 " lint - Lint code with ruff"
|
||||
@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"
|
||||
@@ -47,12 +45,6 @@ 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/
|
||||
@@ -65,7 +57,7 @@ security:
|
||||
uv run bandit -r strix/ -c pyproject.toml
|
||||
@echo "✅ Security checks complete!"
|
||||
|
||||
check-all: format-check lint-check type-check security
|
||||
check-all: format lint type-check security
|
||||
@echo "✅ All code quality checks passed!"
|
||||
|
||||
pre-commit:
|
||||
|
||||
@@ -308,9 +308,6 @@ 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"]
|
||||
|
||||
+1
-11
@@ -29,13 +29,7 @@ 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,
|
||||
get_mcp_tool_schema,
|
||||
list_mcps,
|
||||
search_mcp_tools,
|
||||
)
|
||||
from strix.tools.mcp import call_mcp, describe_mcp, list_mcps
|
||||
from strix.tools.notes.tools import (
|
||||
create_note,
|
||||
delete_note,
|
||||
@@ -56,7 +50,6 @@ 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,
|
||||
@@ -590,7 +583,6 @@ _BASE_TOOLS: tuple[Tool, ...] = (
|
||||
create_vulnerability_report,
|
||||
create_dependency_report,
|
||||
update_vulnerability_report,
|
||||
delete_vulnerability_report,
|
||||
list_reports,
|
||||
get_report,
|
||||
list_requests,
|
||||
@@ -600,8 +592,6 @@ _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,
|
||||
|
||||
+1
-24
@@ -16,10 +16,6 @@ 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(
|
||||
*,
|
||||
@@ -84,13 +80,8 @@ 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.
|
||||
|
||||
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.
|
||||
"""
|
||||
"""Render the system prompt. Returns empty string on template failure."""
|
||||
try:
|
||||
prompt_dir = get_strix_resource_path("agents", _PROMPT_DIRNAME)
|
||||
loader_dirs = [prompt_dir, *skill_search_dirs()]
|
||||
@@ -118,7 +109,6 @@ def render_system_prompt(
|
||||
interactive=interactive,
|
||||
is_root=is_root,
|
||||
system_prompt_context=system_prompt_context or {},
|
||||
include_scope=include_scope,
|
||||
**skill_content,
|
||||
)
|
||||
except Exception:
|
||||
@@ -134,16 +124,3 @@ 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()
|
||||
@@ -1,35 +0,0 @@
|
||||
<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,6 +58,39 @@ 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
|
||||
@@ -209,7 +242,6 @@ VALIDATION REQUIREMENTS:
|
||||
- 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):
|
||||
@@ -514,7 +546,3 @@ On-demand specialist skills. Spawn a specialist via `create_agent(skills=[...])`
|
||||
{% endfor -%}
|
||||
</available_skills>
|
||||
{% endif %}
|
||||
|
||||
{% if include_scope %}
|
||||
{% include "scope.jinja" %}
|
||||
{% endif %}
|
||||
@@ -356,8 +356,6 @@ 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:
|
||||
@@ -369,7 +367,6 @@ 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,
|
||||
|
||||
+5
-80
@@ -36,12 +36,10 @@ 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:
|
||||
@@ -307,9 +305,6 @@ 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),
|
||||
@@ -343,9 +338,6 @@ 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,
|
||||
@@ -366,27 +358,6 @@ 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):
|
||||
@@ -419,7 +390,7 @@ def _guard_event(
|
||||
event: TResponseStreamEvent, rewriter: TurnCallIdRewriter, limiter: TurnToolCallLimiter
|
||||
) -> TResponseStreamEvent | None:
|
||||
if isinstance(event, ResponseOutputItemAddedEvent | ResponseOutputItemDoneEvent):
|
||||
rewritten = rewriter.rewrite_item(event.item, event.output_index)
|
||||
rewritten = rewriter.rewrite_item(event.item)
|
||||
if not limiter.allow(rewritten):
|
||||
return None
|
||||
if rewritten is not event.item:
|
||||
@@ -554,37 +525,13 @@ 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 = request_log.RequestLoggingModel(
|
||||
_CodexResponsesModel(
|
||||
slug,
|
||||
codex.get_subscription_client(),
|
||||
reasoning_effort=llm.reasoning_effort,
|
||||
),
|
||||
model_name=slug,
|
||||
provider="openai-codex",
|
||||
base_url=None,
|
||||
model: Model = _CodexResponsesModel(
|
||||
slug,
|
||||
codex.get_subscription_client(),
|
||||
reasoning_effort=llm.reasoning_effort,
|
||||
)
|
||||
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
|
||||
@@ -598,23 +545,6 @@ 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(
|
||||
@@ -691,7 +621,6 @@ 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()
|
||||
@@ -844,16 +773,12 @@ 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)
|
||||
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
"""Keep tool-call ids present and unique within a conversation.
|
||||
"""Keep tool-call ids 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.
|
||||
|
||||
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.
|
||||
every retry. Rewriting duplicates to fresh unique ids keeps the history
|
||||
valid for any provider.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -20,7 +15,6 @@ 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
|
||||
|
||||
|
||||
@@ -28,29 +22,23 @@ 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) and call_id:
|
||||
if isinstance(call_id, str):
|
||||
used.add(call_id)
|
||||
elif isinstance(item, ResponseFunctionToolCall) and item.call_id:
|
||||
elif isinstance(item, ResponseFunctionToolCall):
|
||||
used.add(item.call_id)
|
||||
return used
|
||||
|
||||
|
||||
def dedupe_history_call_ids(items: list[Any]) -> tuple[list[Any], bool]:
|
||||
"""Rewrite blank and duplicate call ids in a conversation history.
|
||||
"""Rewrite duplicate call ids in a conversation history.
|
||||
|
||||
Outputs are paired with their call by order, so parallel calls that share
|
||||
an id — or are all missing one — keep answering the right call after the
|
||||
rewrite.
|
||||
an id keep answering the right call after the rewrite.
|
||||
"""
|
||||
used: set[str] = set()
|
||||
pending: dict[str, deque[str]] = defaultdict(deque)
|
||||
@@ -61,24 +49,22 @@ def dedupe_history_call_ids(items: list[Any]) -> tuple[list[Any], bool]:
|
||||
if not isinstance(item, dict):
|
||||
rebuilt.append(item)
|
||||
continue
|
||||
|
||||
kind = item.get("type")
|
||||
if kind not in ("function_call", "function_call_output"):
|
||||
call_id = item.get("call_id")
|
||||
if not isinstance(call_id, str):
|
||||
rebuilt.append(item)
|
||||
continue
|
||||
|
||||
call_id = item.get("call_id")
|
||||
key = _pairing_key(call_id)
|
||||
kind = item.get("type")
|
||||
if kind == "function_call":
|
||||
effective = key
|
||||
if not effective or effective in used:
|
||||
effective = call_id
|
||||
if call_id in used:
|
||||
effective = new_call_id()
|
||||
item = {**item, "call_id": effective} # noqa: PLW2901
|
||||
changed = True
|
||||
used.add(effective)
|
||||
pending[key].append(effective)
|
||||
else:
|
||||
queue = pending.get(key)
|
||||
pending[call_id].append(effective)
|
||||
elif kind == "function_call_output":
|
||||
queue = pending.get(call_id)
|
||||
if queue:
|
||||
effective = queue.popleft()
|
||||
if effective != call_id:
|
||||
@@ -97,7 +83,7 @@ def dedupe_input(model_input: str | list[Any]) -> str | list[Any]:
|
||||
|
||||
|
||||
class TurnCallIdRewriter:
|
||||
"""Rewrite a single turn's tool-call ids that are blank or collide with the history.
|
||||
"""Rewrite a single turn's tool-call ids that 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
|
||||
@@ -107,37 +93,12 @@ 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_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."""
|
||||
def rewrite_item(self, item: Any) -> Any:
|
||||
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)
|
||||
@@ -153,4 +114,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, position) for position, item in enumerate(items)]
|
||||
return [self.rewrite_item(item) for item in items]
|
||||
+3
-46
@@ -33,7 +33,6 @@ 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
|
||||
|
||||
|
||||
@@ -198,44 +197,6 @@ 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,
|
||||
@@ -675,7 +636,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=request_log.failure_text(exc))
|
||||
await coordinator.set_status(agent_id, "failed", error=str(exc) or type(exc).__name__)
|
||||
await notify_parent_on_terminal(coordinator, agent_id, "failed")
|
||||
return None
|
||||
|
||||
@@ -697,7 +658,6 @@ 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] = []
|
||||
@@ -825,7 +785,6 @@ 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
|
||||
@@ -833,9 +792,7 @@ 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=request_log.failure_text(exc)
|
||||
)
|
||||
await coordinator.set_status(agent_id, "failed", error=str(exc))
|
||||
await notify_parent_on_terminal(coordinator, agent_id, "failed")
|
||||
return None
|
||||
if isinstance(exc, MaxTurnsExceeded):
|
||||
@@ -849,7 +806,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=request_log.failure_text(exc))
|
||||
await coordinator.set_status(agent_id, status, error=str(exc) or type(exc).__name__)
|
||||
await notify_parent_on_terminal(coordinator, agent_id, status)
|
||||
if not interactive:
|
||||
raise
|
||||
|
||||
+72
-43
@@ -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_scope_prompt, render_system_prompt
|
||||
from strix.agents.prompt import render_system_prompt
|
||||
from strix.config import load_settings
|
||||
from strix.config.models import (
|
||||
StrixProvider,
|
||||
@@ -59,8 +59,10 @@ if TYPE_CHECKING:
|
||||
|
||||
from strix.runtime.status import StatusSink
|
||||
from strix.tools.mcp import (
|
||||
ConnectedMcpServer,
|
||||
McpConnectionRequest,
|
||||
McpRegistry,
|
||||
SupervisedMcpSession,
|
||||
)
|
||||
|
||||
|
||||
@@ -69,8 +71,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", "state"}), once when the connections
|
||||
# are registered and again on lifecycle transitions. An interface
|
||||
# ({"name", "provider", "tool_count", "dead"}), once when the connections are
|
||||
# established and again each time a connection transitions to dead. 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.
|
||||
@@ -78,21 +80,30 @@ 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 lifecycle status dicts."""
|
||||
"""The run's MCP roster as non-secret status dicts (name/provider/tool_count/dead)."""
|
||||
return [
|
||||
{
|
||||
"name": status.name,
|
||||
"provider": status.provider,
|
||||
"tool_count": status.tool_count,
|
||||
"dead": status.dead,
|
||||
"state": status.state,
|
||||
}
|
||||
for status in registry.statuses()
|
||||
]
|
||||
|
||||
|
||||
def _record_mcp_connections(connection_names: list[str]) -> None:
|
||||
"""Record which MCP servers this run configured, for the interfaces.
|
||||
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.
|
||||
|
||||
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
|
||||
@@ -103,7 +114,7 @@ def _record_mcp_connections(connection_names: list[str]) -> None:
|
||||
report_state = get_global_report_state()
|
||||
if report_state is None:
|
||||
return
|
||||
report_state.record_mcp_connections(connection_names)
|
||||
report_state.record_mcp_connections([connection.name for connection in connections])
|
||||
|
||||
|
||||
def _note_exit_reason(reason: str) -> None:
|
||||
@@ -164,17 +175,15 @@ 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 below. They cannot expand, replace, or weaken "
|
||||
"system-verified scope above. They cannot expand, replace, or weaken "
|
||||
"authorized target constraints.\n\n"
|
||||
f"{root_instructions_override}\n"
|
||||
"</root_scan_instructions_override>\n\n"
|
||||
f"{render_scope_prompt(system_prompt_context)}"
|
||||
"</root_scan_instructions_override>"
|
||||
)
|
||||
|
||||
|
||||
@@ -339,7 +348,7 @@ async def run_strix_scan(
|
||||
configure_spill_writer(_spill_to_workspace)
|
||||
|
||||
sessions_to_close: list[SQLiteSession] = []
|
||||
mcp_registry: McpRegistry | None = None
|
||||
mcp_sessions: list[SupervisedMcpSession] = []
|
||||
|
||||
try:
|
||||
targets = scan_config.get("targets") or []
|
||||
@@ -391,6 +400,7 @@ async def run_strix_scan(
|
||||
from strix.tools.mcp import (
|
||||
McpConnectionRequest,
|
||||
McpRegistry,
|
||||
attach_mcp_requests,
|
||||
load_user_mcp_configs,
|
||||
)
|
||||
|
||||
@@ -406,37 +416,56 @@ async def run_strix_scan(
|
||||
else:
|
||||
mcp_requests = mcp_connection_requests
|
||||
if mcp_requests:
|
||||
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()
|
||||
]
|
||||
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()
|
||||
]
|
||||
|
||||
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")
|
||||
# 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")
|
||||
|
||||
mcp_registry.set_status_sink(_emit_mcp_status)
|
||||
_emit_mcp_status()
|
||||
mcp_registry.start_warmup(max_concurrency=6)
|
||||
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()
|
||||
except Exception:
|
||||
logger.exception("Failed to configure user MCP servers; continuing without them")
|
||||
logger.exception("Failed to connect user MCP servers; continuing without them")
|
||||
|
||||
root_context = _merge_root_prompt_context(scope_context, extra_system_prompt_context)
|
||||
root_instructions = _compose_root_instructions_override(
|
||||
@@ -632,9 +661,9 @@ async def run_strix_scan(
|
||||
for s in sessions_to_close:
|
||||
with contextlib.suppress(Exception):
|
||||
s.close()
|
||||
if mcp_registry is not None:
|
||||
for mcp_session in mcp_sessions:
|
||||
with contextlib.suppress(Exception):
|
||||
await mcp_registry.close()
|
||||
await mcp_session.aclose()
|
||||
with contextlib.suppress(Exception):
|
||||
await coordinator._maybe_snapshot()
|
||||
if cleanup_on_exit:
|
||||
|
||||
@@ -122,32 +122,10 @@ 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()
|
||||
|
||||
@@ -86,8 +86,6 @@ 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,36 +21,6 @@ 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
|
||||
|
||||
@@ -114,9 +114,6 @@ 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:
|
||||
|
||||
@@ -1323,7 +1323,6 @@ 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")),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
-49
@@ -1,49 +0,0 @@
|
||||
"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,7 +12,6 @@ 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";
|
||||
@@ -117,7 +116,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", "delete_vulnerability_report", "list_reports", "get_report"],
|
||||
reporting: ["create_vulnerability_report", "update_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"],
|
||||
@@ -152,7 +151,6 @@ const RENDERER_OVERRIDES: Partial<Record<string, ComponentType<ToolRendererProps
|
||||
view_image: ViewImageRenderer,
|
||||
list_reports: ReportListRenderer,
|
||||
get_report: ReportListRenderer,
|
||||
delete_vulnerability_report: VulnReportDeleteRenderer,
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
+16
-16
@@ -45,8 +45,8 @@
|
||||
`);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,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
|
||||
`+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: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 Ft(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.
|
||||
@@ -424,7 +424,7 @@ Error generating stack: `+u.message+`
|
||||
*/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 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
|
||||
`;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(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()}};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:`
|
||||
@@ -449,11 +449,11 @@ Error generating stack: `+u.message+`
|
||||
`))+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}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 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
|
||||
`))}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
|
||||
`,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 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 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: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 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
|
||||
]`,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(`
|
||||
@@ -463,7 +463,7 @@ https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void
|
||||
`)&&(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"?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
|
||||
`: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
|
||||
*
|
||||
@@ -479,13 +479,13 @@ 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 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
|
||||
*/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 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}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 qt(e.selectedNodes.map(hu),t.selectedNodes.map(hu))&&qt(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)??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
|
||||
`)),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]),qt),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(`
|
||||
@@ -498,15 +498,15 @@ https://github.com/highlightjs/highlight.js/issues/2277`),Ht=ye,ft=Le),Qe===void
|
||||
`)}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>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
|
||||
`))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(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,p2-5)).join(`
|
||||
`);return r.length>t?a+`
|
||||
...`: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(`
|
||||
...`:a}function L7({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 z7({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(`
|
||||
... more content available`:"")})})()]})}function I7({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 B7({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 U7={get:"getting",list:"listing",create:"creating",update:"updating",delete:"deleting"};function H7({args:e}){const t=e.action??"",r=e.scope_name??"",a=U7[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 $7({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 q7({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 P7(e){switch(e.toolName){case"list_requests":return m.jsx(L7,{...e});case"view_request":return m.jsx(z7,{...e});case"send_request":return m.jsx(I7,{...e});case"repeat_request":return m.jsx(B7,{...e});case"scope_rules":return m.jsx(H7,{...e});case"list_sitemap":return m.jsx($7,{...e});case"view_sitemap_entry":return m.jsx(q7,{...e});default:return m.jsx("span",{className:"text-purple-400/80 font-semibold text-sm",children:e.toolName.replace(/_/g," ")})}}function F7({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 G7({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<=Y7)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
|
||||
`)}function J7({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?W7(Q7(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 eU({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 tU({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 nU(e){return e.toolName==="subagent_start_info"?m.jsx(tU,{...e}):m.jsx(eU,{...e})}function rU({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 iU({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 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
|
||||
`)}function KH({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 ZH({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:YH(f)}));return m.jsxs("div",{className:"space-y-6",children:[m.jsx("div",{className:"animate-card-in",children:m.jsx(DH,{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(KH,{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:XH(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 QH({run:e,canSteer:t}){const{agents:r,events:a}=e.transcript,s=ee.useMemo(()=>IU(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(aH,{open:d!==null,agent:d,events:a,steerable:h,onClose:()=>c(null)})]})}$k.createR
Line truncated
|
||||
+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
|
||||
@@ -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-DbPDzbcJ.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-CrLE9rrV.css">
|
||||
<script type="module" crossorigin src="./assets/index-B94ANU8d.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="./assets/index-DN__rVv3.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+38
-163
@@ -36,23 +36,6 @@ _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:
|
||||
@@ -238,7 +221,6 @@ 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
|
||||
@@ -360,7 +342,7 @@ class ReportState:
|
||||
agent_id: str | None = None,
|
||||
agent_name: str | None = None,
|
||||
) -> str:
|
||||
report_id = self._next_report_id()
|
||||
report_id = f"vuln-{len(self.vulnerability_reports) + 1:04d}"
|
||||
|
||||
report: dict[str, Any] = {
|
||||
"id": report_id,
|
||||
@@ -436,24 +418,6 @@ class ReportState:
|
||||
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,
|
||||
@@ -552,88 +516,6 @@ class ReportState:
|
||||
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)
|
||||
|
||||
@@ -817,54 +699,47 @@ class ReportState:
|
||||
return None
|
||||
|
||||
def _save_artifacts(self) -> None:
|
||||
"""Write scan artifacts under ``run_dir``; a write failure is logged."""
|
||||
"""Write scan artifacts under ``run_dir``."""
|
||||
run_dir = self.get_run_dir()
|
||||
try:
|
||||
self._write_artifacts()
|
||||
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)
|
||||
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).
|
||||
|
||||
|
||||
@@ -70,9 +70,8 @@ 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}"
|
||||
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.append({"source": str(resolved), "target": target, "read_only": False})
|
||||
if src.get("protect_metadata"):
|
||||
bind_mounts.extend(_metadata_mounts(resolved, target))
|
||||
return bind_mounts
|
||||
|
||||
|
||||
@@ -2,13 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from strix.tools.mcp.agent_tools import (
|
||||
call_mcp,
|
||||
describe_mcp,
|
||||
get_mcp_tool_schema,
|
||||
list_mcps,
|
||||
search_mcp_tools,
|
||||
)
|
||||
from strix.tools.mcp.agent_tools import call_mcp, describe_mcp, list_mcps
|
||||
from strix.tools.mcp.client import (
|
||||
ConnectedMcpServer,
|
||||
attach_mcp_requests,
|
||||
@@ -25,10 +19,8 @@ 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,
|
||||
@@ -43,10 +35,8 @@ 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",
|
||||
@@ -66,10 +56,8 @@ __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",
|
||||
]
|
||||
+33
-148
@@ -1,21 +1,18 @@
|
||||
"""The generic MCP discovery and dispatch tools every agent carries.
|
||||
"""The three generic MCP dispatch tools every agent carries.
|
||||
|
||||
Under the generic-dispatch model an agent does not get one tool per MCP tool.
|
||||
It gets four primary tools and discovers connections on demand:
|
||||
It gets exactly these three 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.
|
||||
- ``search_mcp_tools(connection, query)`` returns a small ranked candidate set.
|
||||
- ``get_mcp_tool_schema(connection, tool)`` returns one exact input schema.
|
||||
- ``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.
|
||||
- ``call_mcp(connection, tool, arguments)`` dispatches one call to a
|
||||
connection's tool and returns its result.
|
||||
|
||||
``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
|
||||
All three 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
|
||||
@@ -64,11 +61,12 @@ 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 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.
|
||||
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.
|
||||
"""
|
||||
registry = _registry_from_ctx(ctx)
|
||||
if registry is None or not registry:
|
||||
@@ -82,7 +80,6 @@ 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()
|
||||
]
|
||||
@@ -91,12 +88,13 @@ async def list_mcps(ctx: RunContextWrapper) -> dict[str, Any]:
|
||||
|
||||
@function_tool(timeout=60)
|
||||
async def describe_mcp(ctx: RunContextWrapper, connection: str) -> str:
|
||||
"""Return one connection's full tool catalog as a compatibility fallback.
|
||||
"""List the tools one MCP connection offers, with their input schemas.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
Args:
|
||||
connection: The connection name exactly as reported by ``list_mcps``.
|
||||
@@ -108,7 +106,7 @@ async def describe_mcp(ctx: RunContextWrapper, connection: str) -> str:
|
||||
if entry is None:
|
||||
return _unknown_connection(connection, registry)
|
||||
try:
|
||||
tools = await entry.ensure_catalog()
|
||||
tools = await entry.session.list_tools()
|
||||
except McpConnectionUnavailableError as exc:
|
||||
return str(exc)
|
||||
if not tools:
|
||||
@@ -118,121 +116,6 @@ 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,
|
||||
@@ -242,18 +125,19 @@ async def call_mcp(
|
||||
) -> Any:
|
||||
"""Call one tool on one MCP connection and return its result.
|
||||
|
||||
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.
|
||||
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).
|
||||
|
||||
Args:
|
||||
connection: The connection name exactly as reported by ``list_mcps``.
|
||||
tool: The tool name exactly as reported by ``search_mcp_tools``.
|
||||
tool: The tool name, exactly as reported by ``describe_mcp``.
|
||||
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 object. Match the shape
|
||||
that ``get_mcp_tool_schema`` returned.
|
||||
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.
|
||||
"""
|
||||
registry = _registry_from_ctx(ctx)
|
||||
if registry is None or not registry:
|
||||
@@ -263,7 +147,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 get_mcp_tool_schema for the schema."
|
||||
"argument names to values, or none. Call describe_mcp for the input schema."
|
||||
)
|
||||
if isinstance(arguments, str):
|
||||
# The ``arguments`` parameter is schema-less (an open object is not
|
||||
@@ -278,17 +162,18 @@ async def call_mcp(
|
||||
if arguments is not None and not isinstance(arguments, dict):
|
||||
return invalid_arguments
|
||||
try:
|
||||
available = await entry.ensure_catalog()
|
||||
available = await entry.session.list_tools()
|
||||
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}. "
|
||||
"Call search_mcp_tools, then get_mcp_tool_schema."
|
||||
f"Tools this connection offers: {offered}. "
|
||||
"Call describe_mcp for their input schemas."
|
||||
)
|
||||
session = await entry.ensure_connected()
|
||||
return await session.dispatch(
|
||||
return await entry.session.dispatch(
|
||||
tool,
|
||||
arguments or {},
|
||||
label=namespaced_tool_name(connection, tool),
|
||||
|
||||
+24
-29
@@ -14,7 +14,6 @@ fails the run.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import json
|
||||
import logging
|
||||
@@ -282,10 +281,8 @@ async def _count_session_tools(config: McpConnectionConfig, session: SupervisedM
|
||||
|
||||
async def connect_mcp_servers(
|
||||
configs: list[McpConnectionConfig],
|
||||
*,
|
||||
max_concurrency: int = 6,
|
||||
) -> list[ConnectedMcpServer]:
|
||||
"""Connect MCP configs concurrently under a fixed bound.
|
||||
"""Connect each MCP config on its own supervising task and return the sessions.
|
||||
|
||||
Each connection becomes a :class:`~strix.tools.mcp.session.SupervisedMcpSession`
|
||||
that owns ``connect()``, the held-open session, and ``cleanup()`` on one
|
||||
@@ -304,43 +301,41 @@ 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``.
|
||||
"""
|
||||
semaphore = asyncio.Semaphore(max(1, max_concurrency))
|
||||
connected: list[ConnectedMcpServer] = []
|
||||
sessions: list[SupervisedMcpSession] = []
|
||||
|
||||
async def connect_one(config: McpConnectionConfig) -> ConnectedMcpServer | None:
|
||||
async with semaphore:
|
||||
try:
|
||||
for config in configs:
|
||||
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()
|
||||
return None
|
||||
except BaseException:
|
||||
with contextlib.suppress(BaseException):
|
||||
await session.aclose()
|
||||
raise
|
||||
sessions.remove(session)
|
||||
continue
|
||||
logger.info("Connected MCP server %r (%d tools)", config.name, tool_count)
|
||||
return ConnectedMcpServer(
|
||||
session=session,
|
||||
name=config.name,
|
||||
tool_count=tool_count,
|
||||
notes=config.notes,
|
||||
connected.append(
|
||||
ConnectedMcpServer(
|
||||
session=session, name=config.name, tool_count=tool_count, notes=config.notes
|
||||
)
|
||||
)
|
||||
|
||||
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,
|
||||
)
|
||||
# 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 [result for result in results if result is not None]
|
||||
|
||||
return connected
|
||||
|
||||
|
||||
async def attach_mcp_requests(
|
||||
|
||||
@@ -62,9 +62,6 @@ 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
|
||||
|
||||
+28
-246
@@ -22,18 +22,13 @@ single dispatch point.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, Literal, NamedTuple
|
||||
from typing import TYPE_CHECKING, Any, NamedTuple
|
||||
|
||||
from strix.tools.mcp.session import McpConnectionUnavailableError, SupervisedMcpSession
|
||||
from strix.tools.mcp.session import SupervisedMcpSession
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Callable
|
||||
|
||||
from agents.mcp import MCPServer
|
||||
|
||||
from strix.tools.mcp.client import ResultTransform
|
||||
@@ -54,60 +49,37 @@ 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"
|
||||
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
|
||||
MCP_DISPATCH_TOOLS = frozenset({CALL_MCP_TOOL, DESCRIBE_MCP_TOOL})
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class McpConnectionEntry:
|
||||
"""One configured MCP connection a scan may reach, keyed by ``name``.
|
||||
"""One live MCP connection a scan may reach, keyed by ``name``.
|
||||
|
||||
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`` 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.
|
||||
"""
|
||||
|
||||
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:
|
||||
@@ -116,143 +88,12 @@ 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 if self.session is not None else None
|
||||
return self.session.server
|
||||
|
||||
@property
|
||||
def config(self) -> McpConnectionConfig | None:
|
||||
"""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
|
||||
"""The session's reconnect config. Carries the bearer token; never log it."""
|
||||
return self.session.config
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -264,7 +105,6 @@ class McpConnectionSummary:
|
||||
purpose: str | None
|
||||
tool_count: int
|
||||
provider: str | None = None
|
||||
state: McpConnectionState = "configured"
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -283,7 +123,6 @@ class McpConnectionStatus:
|
||||
provider: str | None
|
||||
tool_count: int
|
||||
dead: bool
|
||||
state: McpConnectionState
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
@@ -326,21 +165,6 @@ 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,
|
||||
@@ -367,16 +191,13 @@ class McpRegistry:
|
||||
raise ValueError("McpRegistry.add requires either 'session' or 'server'")
|
||||
session = SupervisedMcpSession.adopt(server, name=name, config=config)
|
||||
entry = McpConnectionEntry(
|
||||
name=name,
|
||||
connection_config=config or session.config,
|
||||
session=session,
|
||||
name=name,
|
||||
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
|
||||
|
||||
@@ -396,7 +217,6 @@ class McpRegistry:
|
||||
purpose=entry.purpose,
|
||||
tool_count=entry.tool_count,
|
||||
provider=entry.provider,
|
||||
state=entry.state,
|
||||
)
|
||||
for entry in self._entries.values()
|
||||
]
|
||||
@@ -413,9 +233,7 @@ class McpRegistry:
|
||||
name=entry.name,
|
||||
provider=entry.provider,
|
||||
tool_count=entry.tool_count,
|
||||
dead=entry.state == "unavailable"
|
||||
or (entry.session is not None and entry.session.is_dead),
|
||||
state=entry.state,
|
||||
dead=entry.session.is_dead,
|
||||
)
|
||||
for entry in self._entries.values()
|
||||
]
|
||||
@@ -425,42 +243,6 @@ 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)
|
||||
|
||||
@@ -500,6 +282,6 @@ def resolve_mcp_call(
|
||||
if entry is None:
|
||||
return None
|
||||
provider = entry.provider
|
||||
raw_tool = args.get("tool") if tool_name in {CALL_MCP_TOOL, GET_MCP_TOOL_SCHEMA_TOOL} else ""
|
||||
raw_tool = args.get("tool") if tool_name == CALL_MCP_TOOL else ""
|
||||
tool = raw_tool if isinstance(raw_tool, str) else ""
|
||||
return McpCallInfo(connection=connection, tool=tool, provider=provider)
|
||||
@@ -696,87 +696,6 @@ 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,
|
||||
@@ -1404,12 +1323,6 @@ 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,
|
||||
@@ -1629,48 +1542,6 @@ async def update_vulnerability_report(
|
||||
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,
|
||||
)
|
||||
return json.dumps(result, ensure_ascii=False, default=str)
|
||||
|
||||
|
||||
_DEP_SEVERITY_FROM_CVSS = {
|
||||
(9.0, 10.0): "critical",
|
||||
(7.0, 9.0): "high",
|
||||
|
||||
@@ -32,7 +32,6 @@ 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:
|
||||
@@ -313,8 +312,7 @@ def test_get_model_keeps_streaming_by_default(
|
||||
|
||||
model = StrixProvider().get_model("openai/gpt-4o-mini")
|
||||
assert isinstance(model, _TurnGuardModel)
|
||||
assert isinstance(model._inner, RequestLoggingModel)
|
||||
assert model._inner._inner is inner
|
||||
assert model._inner is inner
|
||||
|
||||
|
||||
def test_get_model_guards_subscription_model_but_keeps_it_streaming(
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
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
|
||||
File diff suppressed because it is too large.
Load diff
@@ -30,26 +30,7 @@ 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,
|
||||
"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,
|
||||
}
|
||||
{"source_path": "/code", "workspace_subdir": "repo", "protect_metadata": True}
|
||||
]
|
||||
|
||||
|
||||
|
||||
+11
-279
@@ -36,15 +36,12 @@ 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
|
||||
|
||||
|
||||
@@ -437,16 +434,8 @@ 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},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -496,247 +485,6 @@ 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 ----------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -825,8 +573,7 @@ async def test_call_mcp_errors_on_unknown_tool() -> None:
|
||||
)
|
||||
|
||||
assert "Unknown tool 'delete_everything'" in out
|
||||
assert "search_mcp_tools" in out
|
||||
assert "read_file" not in out
|
||||
assert "read_file" in out
|
||||
# A rejected tool name never reaches the server.
|
||||
assert server.calls == []
|
||||
|
||||
@@ -884,27 +631,21 @@ async def test_call_mcp_flags_an_errored_result_failed_for_the_tui() -> None:
|
||||
assert out == {"type": "text", "text": "boom:read_file", "success": False}
|
||||
|
||||
|
||||
# --- generic MCP tools are the only MCP surface every agent gets -------------
|
||||
# --- the two tools are the only MCP surface every agent gets -----------------
|
||||
|
||||
|
||||
def test_agent_carries_exactly_the_generic_mcp_tools_regardless_of_connections() -> None:
|
||||
def test_agent_carries_exactly_the_dispatch_tools_regardless_of_connections() -> None:
|
||||
"""No matter how many MCP connections a run makes, an agent's tool list gains
|
||||
exactly the generic MCP tools and never a per-connection provider tool."""
|
||||
exactly list_mcps, describe_mcp, and call_mcp 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]
|
||||
|
||||
expected = {
|
||||
"list_mcps",
|
||||
"search_mcp_tools",
|
||||
"get_mcp_tool_schema",
|
||||
"describe_mcp",
|
||||
"call_mcp",
|
||||
}
|
||||
assert expected <= set(root_names)
|
||||
assert expected <= set(child_names)
|
||||
assert {"list_mcps", "describe_mcp", "call_mcp"} <= set(root_names)
|
||||
assert {"list_mcps", "describe_mcp", "call_mcp"} <= set(child_names)
|
||||
|
||||
# Five hypothetical connections would once have added ~all their tools as
|
||||
# namespaced provider tools; none of those names may appear now.
|
||||
@@ -925,25 +666,16 @@ def test_agent_carries_exactly_the_generic_mcp_tools_regardless_of_connections()
|
||||
# --- prompt guidance replaces the old per-connection inventory ---------------
|
||||
|
||||
|
||||
def test_prompt_renders_targeted_tool_guidance_when_mcp_available() -> None:
|
||||
def test_prompt_renders_static_three_tool_guidance_when_mcp_available() -> None:
|
||||
prompt = render_system_prompt(system_prompt_context={"mcp_available": True})
|
||||
|
||||
assert "MCP CONNECTIONS" in prompt
|
||||
# Targeted discovery, one-schema lookup, dispatch, and compatibility are named.
|
||||
# The three discovery/dispatch tools are named as the way in.
|
||||
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={})
|
||||
|
||||
@@ -951,7 +683,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 discovery and dispatch tools for re-listing and inspecting them at run time."""
|
||||
the three dispatch tools for re-listing and inspecting them at run time."""
|
||||
prompt = render_system_prompt(
|
||||
system_prompt_context={
|
||||
"mcp_available": True,
|
||||
|
||||
@@ -23,7 +23,6 @@ from strix.config.models import (
|
||||
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)
|
||||
@@ -173,7 +172,7 @@ def test_routes_through_litellm_matches_the_provider(
|
||||
# proves the route is not LiteLLM's.
|
||||
assert not litellm
|
||||
return
|
||||
while isinstance(model, _NonStreamingModel | _TurnGuardModel | RequestLoggingModel):
|
||||
while isinstance(model, _NonStreamingModel | _TurnGuardModel):
|
||||
model = model._inner
|
||||
assert isinstance(model, LitellmModel) is litellm
|
||||
|
||||
@@ -216,6 +215,6 @@ def test_api_type_overrides_the_api_base_route(
|
||||
monkeypatch.setenv("STRIX_API_TYPE", api_type)
|
||||
configure_sdk_model_defaults(Settings())
|
||||
model = StrixProvider().get_model("gpt-5")
|
||||
while isinstance(model, _NonStreamingModel | _TurnGuardModel | RequestLoggingModel):
|
||||
while isinstance(model, _NonStreamingModel | _TurnGuardModel):
|
||||
model = model._inner
|
||||
assert isinstance(model, expected)
|
||||
@@ -13,20 +13,17 @@ from strix.report.dedupe import (
|
||||
_prepare_report_for_comparison,
|
||||
check_duplicate,
|
||||
)
|
||||
from strix.report.state import ReportRollbackError, ReportState, set_global_report_state
|
||||
from strix.report.writer import write_run_record
|
||||
from strix.report.state import ReportState, set_global_report_state
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -1092,16 +1089,6 @@ 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 (
|
||||
@@ -1979,270 +1966,3 @@ 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
|
||||
+33
-29
@@ -80,14 +80,13 @@ async def test_none_default_attaches_from_the_user_config_file(
|
||||
)
|
||||
monkeypatch.setattr(mcp_pkg, "load_user_mcp_configs", lambda: [file_config])
|
||||
|
||||
captured: list[McpConnectionRequest] = []
|
||||
original_register = mcp_pkg.McpRegistry.register
|
||||
captured: list[list[McpConnectionRequest]] = []
|
||||
|
||||
def _capture(registry: Any, request: McpConnectionRequest) -> Any:
|
||||
captured.append(request)
|
||||
return original_register(registry, request)
|
||||
async def _capture(requests: list[McpConnectionRequest], _registry: Any) -> list[Any]:
|
||||
captured.append(requests)
|
||||
return []
|
||||
|
||||
monkeypatch.setattr(mcp_pkg.McpRegistry, "register", _capture)
|
||||
monkeypatch.setattr(mcp_pkg, "attach_mcp_requests", _capture)
|
||||
|
||||
await runner.run_strix_scan(
|
||||
scan_config={"targets": [], "scan_mode": "deep"},
|
||||
@@ -98,11 +97,12 @@ 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).
|
||||
(request,) = captured
|
||||
assert request.config is file_config
|
||||
assert request.provider is None
|
||||
assert request.result_transform is None
|
||||
assert request.purpose is None
|
||||
(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
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -116,14 +116,13 @@ 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[McpConnectionRequest] = []
|
||||
original_register = mcp_pkg.McpRegistry.register
|
||||
captured: list[list[McpConnectionRequest]] = []
|
||||
|
||||
def _capture(registry: Any, request: McpConnectionRequest) -> Any:
|
||||
captured.append(request)
|
||||
return original_register(registry, request)
|
||||
async def _capture(requests: list[McpConnectionRequest], _registry: Any) -> list[Any]:
|
||||
captured.append(requests)
|
||||
return []
|
||||
|
||||
monkeypatch.setattr(mcp_pkg.McpRegistry, "register", _capture)
|
||||
monkeypatch.setattr(mcp_pkg, "attach_mcp_requests", _capture)
|
||||
|
||||
supplied = [
|
||||
McpConnectionRequest(
|
||||
@@ -140,7 +139,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
|
||||
@@ -148,8 +147,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 configured roster is
|
||||
still written, carrying only non-secret lifecycle fields."""
|
||||
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."""
|
||||
_wire_runner(monkeypatch, tmp_path)
|
||||
monkeypatch.setattr(
|
||||
mcp_pkg,
|
||||
@@ -157,6 +156,19 @@ 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:
|
||||
@@ -173,12 +185,4 @@ 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[0] == [
|
||||
{
|
||||
"name": "local_fs",
|
||||
"provider": None,
|
||||
"tool_count": 0,
|
||||
"dead": False,
|
||||
"state": "configured",
|
||||
}
|
||||
]
|
||||
assert persisted[-1] == [{"name": "local_fs", "provider": None, "tool_count": 3, "dead": False}]
|
||||
@@ -17,11 +17,8 @@ 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
|
||||
|
||||
@@ -135,10 +132,6 @@ 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
|
||||
)
|
||||
@@ -201,12 +194,21 @@ 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",
|
||||
)
|
||||
)
|
||||
|
||||
@@ -222,7 +224,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": 0}
|
||||
{"name": "fs", "purpose": "local files", "tool_count": 2}
|
||||
]
|
||||
|
||||
|
||||
@@ -266,39 +268,3 @@ 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_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")
|
||||
@@ -27,15 +27,8 @@ from strix.runtime.session_manager import (
|
||||
)
|
||||
|
||||
|
||||
def _source(
|
||||
subdir: str, path: str, *, protect_metadata: bool = False, read_only: bool = False
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"source_path": path,
|
||||
"workspace_subdir": subdir,
|
||||
"protect_metadata": protect_metadata,
|
||||
"read_only": read_only,
|
||||
}
|
||||
def _source(subdir: str, path: str, *, protect_metadata: bool = False) -> dict[str, Any]:
|
||||
return {"source_path": path, "workspace_subdir": subdir, "protect_metadata": protect_metadata}
|
||||
|
||||
|
||||
def test_source_becomes_writable_bind_mount(tmp_path: Path) -> None:
|
||||
@@ -131,16 +124,6 @@ def test_clone_keeps_its_git_writable(tmp_path: Path) -> None:
|
||||
assert [m["target"] for m in mounts] == ["/workspace/clone"]
|
||||
|
||||
|
||||
def test_read_only_source_is_one_read_only_mount(tmp_path: Path) -> None:
|
||||
(tmp_path / ".git").mkdir()
|
||||
mounts = build_bind_mounts(
|
||||
[_source("image", str(tmp_path), protect_metadata=True, read_only=True)]
|
||||
)
|
||||
assert mounts == [
|
||||
{"source": str(tmp_path.resolve()), "target": "/workspace/image", "read_only": True}
|
||||
]
|
||||
|
||||
|
||||
def test_multiple_sources_each_get_a_mount(tmp_path: Path) -> None:
|
||||
first = tmp_path / "first"
|
||||
second = tmp_path / "second"
|
||||
|
||||
+8
-141
@@ -1,12 +1,11 @@
|
||||
"""Tests for tool-call id presence and uniqueness.
|
||||
"""Tests for tool-call id uniqueness.
|
||||
|
||||
Providers that number tool calls per turn (``exec_command:0``, ``:1``, ...)
|
||||
restart the counter on every turn, so the same id eventually appears twice in
|
||||
one conversation. Others hand back a tool call with no id at all, leaving the
|
||||
paired tool message with a blank ``tool_call_id``. Strict providers reject the
|
||||
whole request either way, and because the history is replayed on every retry
|
||||
the agent can never recover. Gateways that validate ids the way those
|
||||
providers do prove both failures and their fixes.
|
||||
one conversation. Strict providers then reject the whole request, and because
|
||||
the history is replayed on every retry the agent can never recover. A gateway
|
||||
that validates id uniqueness the way those providers do proves both the
|
||||
failure and the fix.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -126,51 +125,10 @@ class _StrictHandler(BaseHTTPRequestHandler):
|
||||
self.wfile.write(encoded)
|
||||
|
||||
|
||||
class _BlankIdHandler(BaseHTTPRequestHandler):
|
||||
"""Gateway that hands out an id-less tool call and rejects blank ids, like GLM does."""
|
||||
|
||||
def log_message(self, *args: Any) -> None:
|
||||
pass
|
||||
|
||||
def do_POST(self) -> None:
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
body = json.loads(self.rfile.read(length) or b"{}")
|
||||
messages = body.get("messages", [])
|
||||
_REQUESTS.append(messages)
|
||||
|
||||
for index, message in enumerate(messages):
|
||||
if message.get("role") == "tool" and not message.get("tool_call_id"):
|
||||
self._respond(
|
||||
400,
|
||||
{
|
||||
"error": {
|
||||
"message": (
|
||||
f"messages[{index}]: tool messages must include "
|
||||
"a non-empty string tool_call_id"
|
||||
),
|
||||
"code": 400,
|
||||
}
|
||||
},
|
||||
)
|
||||
return
|
||||
|
||||
if len(_REQUESTS) == 1:
|
||||
self._respond(200, _tool_call_completion(""))
|
||||
else:
|
||||
self._respond(200, _text_completion("all done"))
|
||||
|
||||
def _respond(self, status: int, payload: dict[str, Any]) -> None:
|
||||
encoded = json.dumps(payload).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(encoded)))
|
||||
self.end_headers()
|
||||
self.wfile.write(encoded)
|
||||
|
||||
|
||||
def _serve(handler: type[BaseHTTPRequestHandler]) -> Iterator[str]:
|
||||
@pytest.fixture
|
||||
def strict_gateway() -> Iterator[str]:
|
||||
_REQUESTS.clear()
|
||||
server = HTTPServer(("127.0.0.1", 0), handler)
|
||||
server = HTTPServer(("127.0.0.1", 0), _StrictHandler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
@@ -180,16 +138,6 @@ def _serve(handler: type[BaseHTTPRequestHandler]) -> Iterator[str]:
|
||||
server.server_close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def strict_gateway() -> Iterator[str]:
|
||||
yield from _serve(_StrictHandler)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def blank_id_gateway() -> Iterator[str]:
|
||||
yield from _serve(_BlankIdHandler)
|
||||
|
||||
|
||||
def _model(base_url: str) -> Model:
|
||||
# The gateway answers plain JSON, so the run loop's streamed turns are
|
||||
# served non-streamed; the ids on the wire are the same either way.
|
||||
@@ -274,61 +222,6 @@ def test_history_dedupe_pairs_parallel_calls_by_order() -> None:
|
||||
assert rebuilt[1]["call_id"] != "dup"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_blank_call_id_is_rejected_by_the_provider_without_the_wrapper(
|
||||
blank_id_gateway: str,
|
||||
) -> None:
|
||||
# Repro: the model answers with a tool call carrying no id, so the paired
|
||||
# tool result goes back as a ``tool`` message with a blank ``tool_call_id``
|
||||
# and the provider rejects the whole replayed history with a 400.
|
||||
with pytest.raises(Exception, match="non-empty string tool_call_id"):
|
||||
await _run_agent(blank_id_gateway, wrap=False)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_blank_call_id_is_filled_in_so_the_history_stays_valid(
|
||||
blank_id_gateway: str,
|
||||
) -> None:
|
||||
result = await _run_agent(blank_id_gateway, wrap=True)
|
||||
|
||||
assert result.final_output == "all done"
|
||||
call_ids = _assistant_call_ids(_REQUESTS[-1])
|
||||
assert len(call_ids) == 1
|
||||
assert call_ids[0].startswith("call_")
|
||||
assert _tool_results(_REQUESTS[-1]) == ["did 1"]
|
||||
|
||||
|
||||
def test_history_dedupe_fills_in_blank_ids_and_keeps_outputs_paired() -> None:
|
||||
items = [
|
||||
{"type": "function_call", "call_id": "", "name": "a", "arguments": "{}"},
|
||||
{"type": "function_call", "call_id": "", "name": "b", "arguments": "{}"},
|
||||
{"type": "function_call_output", "call_id": "", "output": "for-a"},
|
||||
{"type": "function_call_output", "call_id": "", "output": "for-b"},
|
||||
]
|
||||
|
||||
rebuilt, changed = dedupe_history_call_ids(items)
|
||||
|
||||
assert changed
|
||||
ids = [item["call_id"] for item in rebuilt]
|
||||
assert all(call_id.startswith("call_") for call_id in ids)
|
||||
assert ids[0] == ids[2]
|
||||
assert ids[1] == ids[3]
|
||||
assert ids[0] != ids[1]
|
||||
|
||||
|
||||
def test_history_dedupe_fills_in_a_missing_call_id_key() -> None:
|
||||
items = [
|
||||
{"type": "function_call", "name": "a", "arguments": "{}"},
|
||||
{"type": "function_call_output", "output": "x"},
|
||||
]
|
||||
|
||||
rebuilt, changed = dedupe_history_call_ids(items)
|
||||
|
||||
assert changed
|
||||
assert rebuilt[0]["call_id"] == rebuilt[1]["call_id"]
|
||||
assert rebuilt[0]["call_id"].startswith("call_")
|
||||
|
||||
|
||||
def test_history_dedupe_leaves_unique_ids_alone() -> None:
|
||||
items = [
|
||||
{"type": "function_call", "call_id": "call_a", "name": "a", "arguments": "{}"},
|
||||
@@ -354,29 +247,3 @@ def test_turn_rewriter_is_stable_across_repeated_sightings() -> None:
|
||||
|
||||
assert first.call_id != "exec_command:0"
|
||||
assert second.call_id == first.call_id
|
||||
|
||||
|
||||
def test_turn_rewriter_fills_in_a_blank_id_stably() -> None:
|
||||
rewriter = TurnCallIdRewriter([])
|
||||
call = ResponseFunctionToolCall(
|
||||
id="fc_1", call_id="", name="a", arguments="{}", type="function_call"
|
||||
)
|
||||
|
||||
first = rewriter.rewrite_item(call)
|
||||
second = rewriter.rewrite_item(call)
|
||||
|
||||
assert first.call_id.startswith("call_")
|
||||
assert second.call_id == first.call_id
|
||||
assert rewriter.rewrite_item(first).call_id == first.call_id
|
||||
|
||||
|
||||
def test_turn_rewriter_gives_parallel_blank_calls_distinct_ids() -> None:
|
||||
rewriter = TurnCallIdRewriter([])
|
||||
a = ResponseFunctionToolCall(
|
||||
id="fc_1", call_id="", name="a", arguments="{}", type="function_call"
|
||||
)
|
||||
b = ResponseFunctionToolCall(
|
||||
id="fc_2", call_id="", name="b", arguments="{}", type="function_call"
|
||||
)
|
||||
|
||||
assert rewriter.rewrite_item(a).call_id != rewriter.rewrite_item(b).call_id
|
||||
@@ -1,379 +0,0 @@
|
||||
"""Tool-call id repair across provider routes, models, and streaming modes.
|
||||
|
||||
Every model Strix resolves goes through ``StrixProvider``, which picks the
|
||||
OpenAI SDK for ``openai/...`` and LiteLLM for every other prefix, then wraps the
|
||||
result in the turn guard that repairs tool-call ids. Each route parses a
|
||||
provider's tool calls on its own code path, so a blank or missing id — or a
|
||||
per-turn counter that repeats — has to be repaired no matter which route
|
||||
carried it, streamed or not.
|
||||
|
||||
A local OpenAI-compatible gateway stands in for the provider. It hands out
|
||||
tool calls with whatever ids the scenario calls for and rejects a history the
|
||||
way strict providers do: any ``tool`` message with an empty ``tool_call_id``,
|
||||
or two assistant tool calls sharing one id.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import threading
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
from typing import TYPE_CHECKING, Any, ClassVar
|
||||
|
||||
import litellm
|
||||
import pytest
|
||||
from agents import Agent, ModelSettings, Runner, function_tool
|
||||
from agents.run import RunConfig
|
||||
|
||||
from strix.config import loader
|
||||
from strix.config import models as strix_models
|
||||
from strix.config.models import StrixProvider
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Iterator
|
||||
|
||||
|
||||
_MISSING = object()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _Scenario:
|
||||
"""Tool-call ids the gateway hands out, one list per tool-calling turn."""
|
||||
|
||||
turns: tuple[tuple[Any, ...], ...]
|
||||
|
||||
@property
|
||||
def calls(self) -> int:
|
||||
return sum(len(turn) for turn in self.turns)
|
||||
|
||||
@property
|
||||
def has_null_id(self) -> bool:
|
||||
return any(
|
||||
call_id is None or call_id is _MISSING for turn in self.turns for call_id in turn
|
||||
)
|
||||
|
||||
|
||||
SCENARIOS = {
|
||||
"empty-id": _Scenario(turns=(("",),)),
|
||||
"null-id": _Scenario(turns=((None,),)),
|
||||
"missing-id": _Scenario(turns=((_MISSING,),)),
|
||||
"parallel-empty-ids": _Scenario(turns=(("", ""),)),
|
||||
"empty-id-every-turn": _Scenario(turns=(("",), ("",), ("",))),
|
||||
"recycled-counter": _Scenario(turns=(("exec_command:0",), ("exec_command:0",))),
|
||||
"parallel-recycled-counter": _Scenario(
|
||||
turns=(("exec_command:0", "exec_command:1"), ("exec_command:0", "exec_command:1"))
|
||||
),
|
||||
"mixed-blank-and-recycled": _Scenario(
|
||||
turns=(("exec_command:0", ""), ("exec_command:0", None), (_MISSING,))
|
||||
),
|
||||
"valid-ids": _Scenario(turns=(("call_a",), ("call_b", "call_c"))),
|
||||
}
|
||||
|
||||
# One model per provider family Strix recommends or documents, each on the
|
||||
# route ``StrixProvider`` gives it: the OpenAI SDK for ``openai/``, LiteLLM's
|
||||
# OpenAI-compatible adapters for the rest, and a generic OpenAI-compatible
|
||||
# endpoint through ``litellm/openai/``.
|
||||
MODELS = [
|
||||
"openai/gpt-5.4",
|
||||
"openrouter/z-ai/glm-5.3",
|
||||
"openrouter/anthropic/claude-sonnet-4.6",
|
||||
"openrouter/google/gemini-3-pro-preview",
|
||||
"openrouter/qwen/qwen3-coder",
|
||||
"zai/glm-5.3",
|
||||
"zai/glm-5.3-flash",
|
||||
"deepseek/deepseek-chat",
|
||||
"moonshot/kimi-k2.5",
|
||||
"xai/grok-4",
|
||||
"mistral/mistral-large-latest",
|
||||
"together_ai/Qwen/Qwen3-235B-A22B",
|
||||
"fireworks_ai/accounts/fireworks/models/kimi-k2",
|
||||
"dashscope/qwen3-max",
|
||||
"deepinfra/Qwen/Qwen3-32B",
|
||||
"nebius/Qwen/Qwen3-32B",
|
||||
"hosted_vllm/Qwen/Qwen3-32B",
|
||||
"litellm/openai/gw-model",
|
||||
]
|
||||
|
||||
|
||||
class _Gateway(BaseHTTPRequestHandler):
|
||||
scenario: ClassVar[_Scenario]
|
||||
requests: ClassVar[list[dict[str, Any]]]
|
||||
lock: ClassVar[threading.Lock]
|
||||
|
||||
def log_message(self, *args: Any) -> None:
|
||||
pass
|
||||
|
||||
def do_POST(self) -> None: # noqa: N802
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
body = json.loads(self.rfile.read(length) or b"{}")
|
||||
messages = body.get("messages", [])
|
||||
with self.lock:
|
||||
self.requests.append(body)
|
||||
turn = len(self.requests) - 1
|
||||
|
||||
error = _history_error(messages)
|
||||
if error:
|
||||
self._send_json(400, {"error": {"message": error, "code": 400}})
|
||||
return
|
||||
|
||||
if turn < len(self.scenario.turns):
|
||||
ids = self.scenario.turns[turn]
|
||||
message = _tool_call_message(ids, first_n=turn * 10)
|
||||
finish = "tool_calls"
|
||||
else:
|
||||
message = {"role": "assistant", "content": "all done"}
|
||||
finish = "stop"
|
||||
|
||||
if body.get("stream"):
|
||||
self._send_stream(message, finish)
|
||||
else:
|
||||
self._send_json(
|
||||
200,
|
||||
{
|
||||
"id": f"chatcmpl-{turn}",
|
||||
"object": "chat.completion",
|
||||
"created": 0,
|
||||
"model": body.get("model", "gw-model"),
|
||||
"choices": [{"index": 0, "finish_reason": finish, "message": message}],
|
||||
"usage": {"prompt_tokens": 5, "completion_tokens": 2, "total_tokens": 7},
|
||||
},
|
||||
)
|
||||
|
||||
def _send_json(self, status: int, payload: dict[str, Any]) -> None:
|
||||
encoded = json.dumps(payload).encode()
|
||||
self.send_response(status)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(encoded)))
|
||||
self.end_headers()
|
||||
self.wfile.write(encoded)
|
||||
|
||||
def _send_stream(self, message: dict[str, Any], finish: str) -> None:
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "text/event-stream")
|
||||
self.end_headers()
|
||||
|
||||
def chunk(delta: dict[str, Any], finish_reason: str | None = None) -> None:
|
||||
payload = {
|
||||
"id": "chatcmpl-s",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": 0,
|
||||
"model": "gw-model",
|
||||
"choices": [{"index": 0, "delta": delta, "finish_reason": finish_reason}],
|
||||
}
|
||||
self.wfile.write(f"data: {json.dumps(payload)}\n\n".encode())
|
||||
|
||||
chunk({"role": "assistant", "content": ""})
|
||||
if message.get("content"):
|
||||
chunk({"content": message["content"]})
|
||||
for index, call in enumerate(message.get("tool_calls") or []):
|
||||
head: dict[str, Any] = {
|
||||
"index": index,
|
||||
"type": "function",
|
||||
"function": {"name": call["function"]["name"], "arguments": ""},
|
||||
}
|
||||
if "id" in call:
|
||||
head["id"] = call["id"]
|
||||
chunk({"tool_calls": [head]})
|
||||
arguments = call["function"]["arguments"]
|
||||
middle = len(arguments) // 2
|
||||
for part in (arguments[:middle], arguments[middle:]):
|
||||
chunk({"tool_calls": [{"index": index, "function": {"arguments": part}}]})
|
||||
chunk({}, finish)
|
||||
usage = {
|
||||
"id": "chatcmpl-s",
|
||||
"object": "chat.completion.chunk",
|
||||
"created": 0,
|
||||
"model": "gw-model",
|
||||
"choices": [],
|
||||
"usage": {"prompt_tokens": 5, "completion_tokens": 2, "total_tokens": 7},
|
||||
}
|
||||
self.wfile.write(f"data: {json.dumps(usage)}\n\n".encode())
|
||||
self.wfile.write(b"data: [DONE]\n\n")
|
||||
self.wfile.flush()
|
||||
|
||||
|
||||
def _tool_call_message(ids: tuple[Any, ...], *, first_n: int) -> dict[str, Any]:
|
||||
calls = []
|
||||
for offset, call_id in enumerate(ids):
|
||||
call: dict[str, Any] = {
|
||||
"type": "function",
|
||||
"function": {"name": "do_thing", "arguments": json.dumps({"n": first_n + offset})},
|
||||
}
|
||||
if call_id is not _MISSING:
|
||||
call["id"] = call_id
|
||||
calls.append(call)
|
||||
return {"role": "assistant", "content": None, "tool_calls": calls}
|
||||
|
||||
|
||||
def _history_error(messages: list[dict[str, Any]]) -> str | None:
|
||||
seen: set[str] = set()
|
||||
for index, message in enumerate(messages):
|
||||
if message.get("role") == "tool":
|
||||
call_id = message.get("tool_call_id")
|
||||
if not isinstance(call_id, str) or not call_id:
|
||||
return (
|
||||
f"messages[{index}]: tool messages must include a non-empty string tool_call_id"
|
||||
)
|
||||
for call in message.get("tool_calls") or []:
|
||||
call_id = call.get("id")
|
||||
if not isinstance(call_id, str) or not call_id:
|
||||
return f"messages[{index}]: assistant tool_calls must include a non-empty id"
|
||||
if call_id in seen:
|
||||
return f"messages[{index}]: duplicate tool_call id {call_id!r}"
|
||||
seen.add(call_id)
|
||||
return None
|
||||
|
||||
|
||||
def _serve(scenario: _Scenario) -> tuple[HTTPServer, type[_Gateway]]:
|
||||
handler = type(
|
||||
"_ScenarioGateway",
|
||||
(_Gateway,),
|
||||
{"scenario": scenario, "requests": [], "lock": threading.Lock()},
|
||||
)
|
||||
server = HTTPServer(("127.0.0.1", 0), handler)
|
||||
threading.Thread(target=server.serve_forever, daemon=True).start()
|
||||
return server, handler
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _reset_settings(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
for key in ("STRIX_LLM", "LLM_DISABLE_STREAMING", "LLM_MAX_TOOL_CALLS_PER_TURN"):
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
monkeypatch.setattr(loader, "_cached", None)
|
||||
monkeypatch.setattr(loader, "_override", None)
|
||||
# As ``configure_sdk_model_defaults`` sets it, so routes like ``zai/`` that
|
||||
# reject ``parallel_tool_calls`` still get a request out.
|
||||
monkeypatch.setattr(litellm, "drop_params", True)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _gateway(scenario: _Scenario) -> Iterator[tuple[str, type[_Gateway]]]:
|
||||
server, handler = _serve(scenario)
|
||||
try:
|
||||
yield f"http://127.0.0.1:{server.server_address[1]}/v1", handler
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
|
||||
|
||||
async def _run(
|
||||
monkeypatch: pytest.MonkeyPatch, model: str, base_url: str, *, stream: bool, parallel: bool
|
||||
) -> tuple[Any, list[int]]:
|
||||
monkeypatch.setenv("LLM_DISABLE_STREAMING", "false" if stream else "true")
|
||||
monkeypatch.setattr(loader, "_cached", None)
|
||||
# Binding the provider to the gateway sends every route there, the way a
|
||||
# custom endpoint would, while each prefix keeps its own adapter.
|
||||
provider = StrixProvider(api_key="tok", base_url=base_url)
|
||||
ran: list[int] = []
|
||||
|
||||
@function_tool
|
||||
def do_thing(n: int) -> str:
|
||||
ran.append(n)
|
||||
return f"did {n}"
|
||||
|
||||
agent = Agent(
|
||||
name="t",
|
||||
instructions="use the tool",
|
||||
tools=[do_thing],
|
||||
model=model,
|
||||
model_settings=ModelSettings(parallel_tool_calls=parallel),
|
||||
)
|
||||
result = Runner.run_streamed(
|
||||
agent, input="please", max_turns=10, run_config=RunConfig(model_provider=provider)
|
||||
)
|
||||
async for _ in result.stream_events():
|
||||
pass
|
||||
return result, ran
|
||||
|
||||
|
||||
def _final_history(handler: type[_Gateway]) -> list[dict[str, Any]]:
|
||||
messages: list[dict[str, Any]] = handler.requests[-1]["messages"]
|
||||
return messages
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("stream", [True, False], ids=["streamed", "non-streamed"])
|
||||
@pytest.mark.parametrize("scenario_name", list(SCENARIOS))
|
||||
@pytest.mark.parametrize("model", MODELS)
|
||||
async def test_tool_call_ids_are_repaired_on_every_route(
|
||||
request: pytest.FixtureRequest,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
model: str,
|
||||
scenario_name: str,
|
||||
*,
|
||||
stream: bool,
|
||||
) -> None:
|
||||
scenario = SCENARIOS[scenario_name]
|
||||
if model.startswith("openai/") and not stream and scenario.has_null_id:
|
||||
request.applymarker(
|
||||
pytest.mark.xfail(
|
||||
strict=True,
|
||||
reason=(
|
||||
"the Agents SDK's Chat Completions converter rejects a null tool-call id "
|
||||
"in a non-streamed response before the turn guard sees it"
|
||||
),
|
||||
)
|
||||
)
|
||||
with _gateway(scenario) as (base_url, handler):
|
||||
result, ran = await _run(
|
||||
monkeypatch,
|
||||
model,
|
||||
base_url,
|
||||
stream=stream,
|
||||
parallel=any(len(t) > 1 for t in scenario.turns),
|
||||
)
|
||||
|
||||
assert result.final_output == "all done"
|
||||
assert len(handler.requests) == len(scenario.turns) + 1
|
||||
assert all(bool(body.get("stream")) is stream for body in handler.requests)
|
||||
|
||||
history = _final_history(handler)
|
||||
call_ids = [call["id"] for m in history for call in m.get("tool_calls") or []]
|
||||
assert len(call_ids) == scenario.calls
|
||||
assert all(isinstance(call_id, str) and call_id for call_id in call_ids)
|
||||
assert len(set(call_ids)) == len(call_ids)
|
||||
|
||||
outputs = {m["tool_call_id"]: m["content"] for m in history if m.get("role") == "tool"}
|
||||
assert set(outputs) == set(call_ids)
|
||||
|
||||
originals = {
|
||||
turn * 10 + offset: call_id
|
||||
for turn, ids in enumerate(scenario.turns)
|
||||
for offset, call_id in enumerate(ids)
|
||||
}
|
||||
assert sorted(ran) == sorted(originals)
|
||||
|
||||
# Each output still answers the call that produced it.
|
||||
id_by_n = {
|
||||
json.loads(call["function"]["arguments"])["n"]: call["id"]
|
||||
for m in history
|
||||
for call in m.get("tool_calls") or []
|
||||
}
|
||||
assert {id_by_n[n]: f"did {n}" for n in originals} == outputs
|
||||
|
||||
# A usable id is kept the first time it appears; only blanks and repeats change.
|
||||
seen: set[str] = set()
|
||||
for n, original in sorted(originals.items()):
|
||||
if isinstance(original, str) and original and original not in seen:
|
||||
assert id_by_n[n] == original
|
||||
seen.add(original)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("stream", [True, False], ids=["streamed", "non-streamed"])
|
||||
@pytest.mark.parametrize("model", MODELS)
|
||||
async def test_blank_call_id_is_rejected_on_every_route_without_the_guard(
|
||||
monkeypatch: pytest.MonkeyPatch, model: str, *, stream: bool
|
||||
) -> None:
|
||||
# Repro: with the turn guard removed, an empty id reaches the provider on
|
||||
# every route, so the matrix above is exercising the repair and not a
|
||||
# route that happens to fill ids in on its own.
|
||||
monkeypatch.setattr(strix_models, "_TurnGuardModel", lambda model, **_: model)
|
||||
with (
|
||||
_gateway(SCENARIOS["empty-id"]) as (base_url, _handler),
|
||||
pytest.raises(Exception, match="must include a non-empty"),
|
||||
):
|
||||
await _run(monkeypatch, model, base_url, stream=stream, parallel=False)
|
||||
Reference in new issue
Block a user