优化加载速度、前缀缓存机制

This commit is contained in:
minorQAQ committed 2026-08-13 17:09:33 +08:00
1 parent 25a811ea62
commit 731768deb9
21 files changed
+569 -100

No files matched your search

+17 -5
View File
@@ -151,7 +151,7 @@ Agent 运行过程中的关键交互界面,支持文件从任意位置**拖拽
┌──────────────────────────────────────────────────────────────┐
│ LangGraph ReAct 图 (core/graph.py) │
│ agent 节点 → tools 节点 → process_tool_artifact 节点 │
│ 内含: 反思注入 · 压缩 · 循环检测 · 路由 │
│ 内含: 动态尾部注入 · 压缩 · 循环检测 · 路由 │
│ 人机交互: 阻塞式普通工具(core/human_request.py) │
└───────────────────────────────┬──────────────────────────────┘
│ 调用
@@ -171,7 +171,7 @@ START → [agent: call_model] ──有 tool_calls──▶ [tools: 并行工具
END
```
- **`call_model`**(`core/nodes.py`):注入反思提示 → LLM 调用 → 每步检查压缩(token 超阈值标记)→ 提取 thinking
- **`call_model`**(`core/nodes.py`):追加动态尾部(TodoList 状态 + 循环提醒;固定反思引导已并入系统提示词前缀)→ LLM 调用 → 每步检查压缩(token 超阈值标记)→ 提取 thinking
- **`should_continue`**(`core/routing.py`):含 tool_calls → 循环检测 → 路由到 `tools` 或 `END`(需人工确认的工具不在此暂停,而是在工具执行钳点阻塞征求用户意见,见「阻塞式人机交互」)
- **`process_tool_artifact`**:gui 截图作为 synthetic HumanMessage 注入,实现视觉闭环
- **`compress`**(`core/nodes.py`):token 超 `窗口×COMPRESS_RATE` 时,将历史工具调用压缩为累积摘要(见「实现细节」)
@@ -433,11 +433,23 @@ cloudflared tunnel --url http://localhost:8765
| 层级 | 位置 | 注入时机 | 作用 |
|------|------|----------|------|
| **L1 系统级** | `memory/system_prompt.py` | 会话开始 | `MAIN_SYSTEM_PROMPT` 定义角色;`PLAN_MODE_PROMPT` 强制先规划 |
| **L1 系统级** | `memory/system_prompt.py` | 会话开始 | `MAIN_SYSTEM_PROMPT` 定义角色;`PLAN_MODE_PROMPT` 强制先规划;固定反思引导 `REFLECTION_PROMPT` 并入系统提示词前缀(思考关闭档位,前缀缓存友好) |
| **L2 工具描述** | 各 `tools/*.py` 的 `description` | 工具绑定时 | 指导 LLM 正确调用(含 one-shot 示例) |
| **L3 运行时注入** | `nodes.py` / `loop_detector.py` | 每轮 ReAct | `REFLECTION_PROMPT` 反思 + 反循环警告,作为 synthetic HumanMessage |
| **L3 运行时动态尾部** | `nodes.py` / `loop_detector.py` | 每轮 ReAct | 变化部分(TodoList 状态 + 循环警告)作为动态尾部追加到消息**末尾**,并持久化为长期记忆(`session_meta.dynamic_tail_history`)供后续轮次查看 |
| **L4 内部子模型** | `gui.py` `_build_batch_prompt` | 工具内部 | 批量坐标定位,one-shot 强制纯坐标输出 |
### 1.1 前缀缓存(Prompt Caching)
上下文按「前缀稳定性」布局,最大化利用 LLM 服务端的前缀缓存:
```
[系统提示词(含固定反思引导,稳定)] + [压缩摘要] + [对话历史] + [用户消息] + [动态尾部(每轮变化)]
```
- **固定部分放前缀**:反思引导等不变提示文本并入系统提示词、位于上下文最前 —— 前缀稳定即可命中缓存;避免把固定文本逐轮塞在消息中间导致整段缓存失效。
- **变化部分放末尾**:TodoList 状态、循环警告等每轮变化的内容追加在消息列表**末尾**,变化只影响尾部、不破坏前缀缓存;无 TodoList 且无循环提醒时**不注入任何尾部提示**(省 token)。
- **动态尾部作为长期记忆**:每轮尾部文本持久化到 `session_meta` 的 `dynamic_tail_history`(连续相同去重),后续轮次把全部历史尾部注入上下文,Agent 能看到之前所有轮的 TodoList 状态与循环提醒。
### 2. 上下文分级压缩(图内)
<!-- 多级压缩机制图占位 -->
@@ -499,7 +511,7 @@ LLM 发出 human_interaction / 需确认工具调用
| 单步执行(多数项目) | 2N + 1 |
| **序列合并(本项目)** | **2**(1 次批量定位 + 1 次决策) |
通过 `MAIN_SYSTEM_PROMPT` + `REFLECTION_PROMPT` 双重强调"同页操作必须合并为一次 `gui` 调用"实现。
通过系统提示词前缀中的固定反思引导(`REFLECTION_PROMPT`)+ `MAIN_SYSTEM_PROMPT` 双重强调"同页操作必须合并为一次 `gui` 调用"实现。
### 6. 双模式源码共用
+36 -5
View File
@@ -16,6 +16,7 @@ from __future__ import annotations
import json
import threading
import time
import uuid
from dataclasses import dataclass, field
from pathlib import Path
@@ -114,7 +115,7 @@ def load_agent_configs() -> list[AgentConfig]:
(忽略 JSON 中的静态值),确保提示词可热更新且包含正确的动态路径。
对于 role='sub' 的 Agent,若未指定 system_prompt 则自动使用默认子 Agent 提示词。
"""
from agent.memory.system_prompt import DEFAULT_SUB_AGENT_PROMPT, get_main_system_prompt
from agent.memory.system_prompt import get_default_sub_agent_prompt, get_main_system_prompt
raw = _read_json(AGENT_CONFIG_PATH)
if not raw or not isinstance(raw, list):
raw = _default_agent_config()
@@ -128,9 +129,9 @@ def load_agent_configs() -> list[AgentConfig]:
system_prompt = get_main_system_prompt()
else:
system_prompt = item.get("system_prompt", "")
# 子 Agent 未指定提示词时使用默认子 Agent 提示词
# 子 Agent 未指定提示词时使用默认子 Agent 提示词(含固定反思引导段)
if not system_prompt:
system_prompt = DEFAULT_SUB_AGENT_PROMPT
system_prompt = get_default_sub_agent_prompt()
configs.append(AgentConfig(
name=item.get("name", ""),
description=item.get("description", ""),
@@ -169,12 +170,25 @@ _TOOL_DEFAULT_AUTO_RULES: dict[str, list[dict]] = {
}
# 工具参数信息缓存(工具注册表运行期静态,首次构建后复用,避免每次打开设置都实例化全部工具类)
_tool_parameters_cache: dict[str, list[dict]] | None = None
_tool_parameters_cache_lock = threading.Lock()
def _get_tool_parameters() -> dict[str, list[dict]]:
"""获取每个工具的参数名和类型信息,供前端生成 auto_execute_rules 提示。
返回:
{tool_name: [{"name": "action", "type": "Literal[open,close]"}, ...], ...}
说明:
结果缓存于模块级(工具注册表运行期静态),避免设置页每次打开都
重新实例化全部工具类。
"""
global _tool_parameters_cache
with _tool_parameters_cache_lock:
if _tool_parameters_cache is not None:
return _tool_parameters_cache
from agent.tools import _ALL_AVAILABLE_TOOL_CLASSES
import typing
result: dict[str, list[dict]] = {}
@@ -199,6 +213,8 @@ def _get_tool_parameters() -> dict[str, list[dict]]:
result[name] = params
except Exception:
result[name] = []
with _tool_parameters_cache_lock:
_tool_parameters_cache = result
return result
@@ -454,10 +470,25 @@ def _serialize_monitors(monitors) -> list[dict]:
return result
# 显示器列表短时缓存(5s TTL):设置页 loadAllConfigs 每次 Promise.all 都会请求
# /api/config/gui,screeninfo 枚举显示器在部分系统上较慢,短时缓存避免重复枚举
_live_monitors_cache: tuple[float, list[dict]] | None = None
_live_monitors_cache_lock = threading.Lock()
_LIVE_MONITORS_TTL = 5.0
def _get_live_monitors() -> list[dict]:
"""实时获取当前连接的显示器列表(通过 screeninfo)。"""
"""实时获取当前连接的显示器列表(通过 screeninfo),带 5s TTL 缓存。"""
global _live_monitors_cache
now = time.time()
with _live_monitors_cache_lock:
if _live_monitors_cache and now - _live_monitors_cache[0] < _LIVE_MONITORS_TTL:
return _live_monitors_cache[1]
from screeninfo import get_monitors
return _serialize_monitors(get_monitors())
monitors = _serialize_monitors(get_monitors())
with _live_monitors_cache_lock:
_live_monitors_cache = (time.time(), monitors)
return monitors
def load_gui_config() -> dict:
+63 -17
View File
@@ -21,7 +21,7 @@ from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from agent.core.loop_detector import build_loop_reflection_prompt
from agent.core.state import AgentState
from agent.memory.system_prompt import REFLECTION_PROMPT, SUB_AGENT_OOM_PROMPT
from agent.memory.system_prompt import SUB_AGENT_OOM_PROMPT
from agent.utils.image_utils import image_bytes_to_openai_image_url_part
from agent.utils.tool_call_utils import normalize_tool_call, extract_reasoning_text
from langchain_core.messages import SystemMessage
@@ -115,29 +115,29 @@ def _filter_short_term_memory(messages: list, trajectory_rounds: int) -> list:
return cleaned
def _build_reflection_prompt(messages: list, session_id: str = "") -> str | None:
"""构建每轮注入模型上下文的提示文本,根据思考档位区分注入内容。
def _build_dynamic_tail_prompt(messages: list, session_id: str = "") -> str:
"""构建每轮追加到消息末尾的动态尾部提示(仅变化部分)。
输入:
messages: 当前消息列表(已过滤短期记忆),供循环检测使用。
session_id: 会话 ID,供循环检测与 TodoList 状态重建使用。
输出:
注入文本;无需注入时返回 None。
动态尾部文本;无 TodoList 且无循环提醒时返回空串
(此时不加任何占位提示,省 token)。
系统定位:
- 思考关闭(low/high):注入反思引导提示词 + TodoList 状态 + 循环警告,
引导模型先一句话反思再调用工具;
- 思考开启(xhigh/max/ultra):仅注入 TodoList 状态(模型原生深度思考,
不注入反思引导与循环警告)。
- 固定反思引导已移入系统提示词前缀(system_prompt._reflection_section),
不再在节点中间构造固定提示;
- 循环警告:思考关闭档位注入(Level 1 仅提醒,不强制终止);
- TodoList 状态:思考开/关均注入(事实状态,供模型跟踪计划进度)。
"""
from agent.utils.env_utils import thinking_enabled
parts: list[str] = []
# 思考关闭:反思引导 + 循环警告(Level 1 仅提醒,不强制终止)
# 思考关闭:循环警告(Level 1 仅提醒,不强制终止)
if not thinking_enabled():
parts.append(REFLECTION_PROMPT)
loop_warning = build_loop_reflection_prompt(messages, session_id)
if loop_warning:
parts.append(loop_warning)
@@ -148,11 +148,47 @@ def _build_reflection_prompt(messages: list, session_id: str = "") -> str | None
todo_status = get_todo_status_text(session_id)
except Exception:
todo_status = ""
parts.append(todo_status if todo_status else "当前无 TodoList。")
if todo_status:
parts.append(todo_status)
return "\n\n".join(parts)
def _dynamic_tail_agent_key() -> str:
"""动态尾部长期记忆的 agent 键:主 Agent 为 "main",子 Agent 用其名称。
主/子 Agent 的动态尾部各自隔离,避免子 Agent 覆盖主 Agent 的长期记忆。
"""
try:
from agent.core.human_request import get_current_agent_name
name = get_current_agent_name()
except Exception:
name = ""
return "main" if name in ("", "__main__") else name
def _persist_dynamic_tail(session_id: str, text: str) -> None:
"""持久化当前轮动态尾部到 session_meta(长期记忆,供后续轮次注入查看)。"""
if not session_id:
return
try:
from agent.utils.agent_utils import update_session_dynamic_tail
update_session_dynamic_tail(session_id, _dynamic_tail_agent_key(), text)
except Exception:
pass
def _load_dynamic_tail_history(session_id: str) -> list[str]:
"""读取当前 Agent 的动态尾部长期记忆历史(时间正序,最新在末尾)。"""
if not session_id:
return []
try:
from agent.utils.agent_utils import get_session_dynamic_tail_history
return get_session_dynamic_tail_history(session_id, _dynamic_tail_agent_key())
except Exception:
return []
# ---------- ReAct 循环内上下文压缩 ----------
def _extract_usage(response: Any) -> dict:
"""从模型 response 提取完整 usage 字典(prompt_tokens/completion_tokens/total_tokens)。"""
@@ -414,12 +450,21 @@ def make_call_model_node(
except Exception:
_sid = ""
# 构建提示文本并注入到消息列表(仅作引导,不记录到前端)
reflection = _build_reflection_prompt(filtered_messages, _sid)
# 构建动态尾部提示并注入到消息列表末尾(仅作引导,不记录到前端)。
# 固定反思引导已并入系统提示词前缀,此处仅注入变化部分:
# TodoList 状态与循环提醒;无 TodoList 且无循环提醒时不注入(省 token)。
messages_for_llm = list(filtered_messages)
if reflection:
reflection_msg = HumanMessage(content=reflection, additional_kwargs={"synthetic": True, "reflection": True})
messages_for_llm.append(reflection_msg)
tail = _build_dynamic_tail_prompt(filtered_messages, _sid)
if tail:
# 长期记忆:持久化本轮动态尾部,供后续轮次注入查看
_persist_dynamic_tail(_sid, tail)
# 注入全部历史动态尾部(含本轮),使 Agent 能看到之前所有轮的
# todo 状态与循环提醒;这些消息仅存在于本次调用,不进图状态。
for hist_text in _load_dynamic_tail_history(_sid):
messages_for_llm.append(HumanMessage(
content=hist_text,
additional_kwargs={"synthetic": True, "reflection": True},
))
response = model_with_tools.invoke(messages_for_llm)
@@ -475,7 +520,8 @@ def make_call_model_node(
from agent.history.tool_call_recorder import record_tool_call_live, get_current_session
sid = get_current_session()
if sid:
record_tool_call_live(sid, tool_name, tool_args)
# 记录工具调用 ID:并行 call_subagent 按 id 匹配注入子轨迹
record_tool_call_live(sid, tool_name, tool_args, tc_normalized.get("id", ""))
except Exception:
pass
+125 -40
View File
@@ -44,31 +44,57 @@ _current_session_fallback = ""
# 同时子 Agent 轨迹仍通过 record_sub_agent_trace 独立存档供持久化注入。
_sub_agent_context: ContextVar[bool] = ContextVar("sub_agent_context", default=False)
# 当前工具调用 ID(tool_call_id):由并行工具节点在调用工具前设置,
# 经上下文传播进入 call_subagent._run,用于并行子 Agent 的轨迹/实时记录隔离。
# 同一轮内多个 call_subagent(甚至同名)并行时,各自按 tool_call_id 独立存取。
_current_tool_call_id: ContextVar[str] = ContextVar("current_tool_call_id", default="")
def is_sub_agent_context() -> bool:
"""当前是否运行在子 Agent 上下文中。"""
return _sub_agent_context.get()
def set_current_tool_call_id(tool_call_id: str) -> None:
"""设置当前线程的工具调用 ID(并行工具节点在执行工具前调用)。"""
_current_tool_call_id.set(tool_call_id or "")
def get_current_tool_call_id() -> str:
"""获取当前工具调用 ID(call_subagent._run 内读取,用于轨迹隔离)。"""
return _current_tool_call_id.get()
def _sub_key(session_id: str, tool_call_id: str | None = None) -> tuple[str, str]:
"""计算子 Agent 记录的隔离 key:(session_id, tool_call_id)。
tool_call_id 为空时回退到会话级 key(旧行为,顺序执行子 Agent 场景),
保证无 tool_call_id 的调用(如人工手动 invoke)也能正常读写。
"""
tc_id = tool_call_id if tool_call_id is not None else get_current_tool_call_id()
return (session_id or "", tc_id or "")
@contextmanager
def sub_agent_context():
def sub_agent_context(tool_call_id: str | None = None):
"""进入子 Agent 执行上下文:将 record_*_live 路由到 call_subagent 嵌套区域。
子 Agent 的工具调用记录写入独立的 _sub_live_records[session_id],
get_live_tool_calls 将其注入到 call_subagent 条目的 sub_tool_calls 中,
子 Agent 的工具调用记录写入独立的 _sub_live_records[(session_id, tool_call_id)],
get_live_tool_calls 将其注入到对应 call_subagent 条目的 sub_tool_calls 中,
前端在 call_subagent 展开时实时渲染嵌套的子工具调用列表。
进入时清空上一子 Agent 遗留的实时记录与待消费思考,
避免同一轮内多个子 Agent 顺序执行时发生记忆串扰。
进入时仅清空当前子 Agent 实例遗留的实时记录与待消费思考,
多个子 Agent(含同名)并行时按 tool_call_id 隔离,互不串扰。
"""
token = _sub_agent_context.set(True)
try:
_sid = get_current_session()
if _sid:
key = _sub_key(_sid, tool_call_id)
with _sub_live_lock:
_sub_live_records.pop(_sid, None)
_sub_live_records.pop(key, None)
with _sub_pending_thinking_lock:
_sub_pending_thinking.pop(_sid, None)
_sub_pending_thinking.pop(key, None)
yield
finally:
_sub_agent_context.reset(token)
@@ -77,13 +103,15 @@ def sub_agent_context():
_live_lock = threading.Lock()
_live_records: dict[str, list[dict[str, Any]]] = {}
# 子 Agent 实时工具调用记录(session_id → list),独立存储,
# 在 get_live_tool_calls 中合并到 call_subagent 条目的 sub_tool_calls 中供前端嵌套展开。
_sub_live_records: dict[str, list[dict[str, Any]]] = {}
# 子 Agent 实时工具调用记录((session_id, tool_call_id) → list),独立存储,
# 在 get_live_tool_calls 中按 tool_call_id 合并到对应 call_subagent 条目的
# sub_tool_calls 中供前端嵌套展开。多个子 Agent 并行时按 (session, tc_id) 隔离。
_sub_live_records: dict[tuple[str, str], list[dict[str, Any]]] = {}
_sub_live_lock = threading.Lock()
# 子 Agent 待附加 thinking(与主 Agent 的 _pending_thinking 隔离)
_sub_pending_thinking: dict[str, str] = {}
# 子 Agent 待附加 thinking(与主 Agent 的 _pending_thinking 隔离),
# key 与 _sub_live_records 一致:(session_id, tool_call_id)。
_sub_pending_thinking: dict[tuple[str, str], str] = {}
_sub_pending_thinking_lock = threading.Lock()
# 轮次开始时间(key = session_id),用于计时
@@ -231,17 +259,30 @@ def init_live_turn_with_id(session_id: str, turn_id: str) -> str:
return turn_id
def record_tool_call_live(session_id: str, name: str, args: dict[str, Any]) -> None:
"""图节点中调用:记录工具调用开始。同时消费 pending thinking。"""
def record_tool_call_live(session_id: str, name: str, args: dict[str, Any], tool_call_id: str | None = None) -> None:
"""图节点中调用:记录工具调用开始。同时消费 pending thinking。
输入:
session_id: 会话 ID。
name: 工具名。
args: 工具参数字典。
tool_call_id: 可选工具调用 ID;子 Agent 上下文中作为隔离 key,
主 Agent 上下文中存入条目供前端按 call_subagent 匹配子轨迹。
"""
# 子 Agent 上下文:路由到 _sub_live_records,由 get_live_tool_calls 合并到
# call_subagent 条目的 sub_tool_calls 中供前端嵌套展开。
# 对应 call_subagent 条目的 sub_tool_calls 中供前端嵌套展开。
# 隔离 key 取上下文中的 tool_call_id(即父级 call_subagent 调用 ID,
# 由 _execute_single 设置并传播到子 Agent 图线程),而非本条目的子工具 ID;
# 多个并行子 Agent(含同名)因此互不串扰。
if is_sub_agent_context():
key = _sub_key(session_id)
with _sub_pending_thinking_lock:
thinking = _sub_pending_thinking.pop(session_id, None)
thinking = _sub_pending_thinking.pop(key, None)
with _sub_live_lock:
if session_id not in _sub_live_records:
_sub_live_records[session_id] = []
if key not in _sub_live_records:
_sub_live_records[key] = []
entry = {
"id": tool_call_id or "",
"name": name,
"args": _json_safe(args),
"args_summary": format_args_summary(args, max_len=40),
@@ -250,7 +291,7 @@ def record_tool_call_live(session_id: str, name: str, args: dict[str, Any]) -> N
}
if thinking:
entry["thinking"] = thinking
_sub_live_records[session_id].append(entry)
_sub_live_records[key].append(entry)
_notify_live(session_id)
return
with _pending_thinking_lock:
@@ -259,6 +300,7 @@ def record_tool_call_live(session_id: str, name: str, args: dict[str, Any]) -> N
if session_id not in _live_records:
_live_records[session_id] = []
entry = {
"id": tool_call_id or "",
"name": name,
"args": _json_safe(args),
"args_summary": format_args_summary(args, max_len=40),
@@ -278,10 +320,11 @@ def record_tool_result_live(
artifact: Any | None = None,
) -> None:
"""记录工具返回结果,供前端实时展示文本、图片预览、音频信息与文件信息。"""
# 子 Agent 上下文:路由到 _sub_live_records
# 子 Agent 上下文:路由到 _sub_live_records(按 (session, tool_call_id) 隔离)
if is_sub_agent_context():
key = _sub_key(session_id)
with _sub_live_lock:
records = _sub_live_records.get(session_id, [])
records = _sub_live_records.get(key, [])
for rec in reversed(records):
if rec["name"] == name and rec["status"] == "running":
rec["status"] = "done"
@@ -472,9 +515,12 @@ def end_live_turn(session_id: str) -> list[dict[str, Any]]:
with _pending_thinking_lock:
_pending_thinking.pop(session_id, None)
with _sub_pending_thinking_lock:
_sub_pending_thinking.pop(session_id, None)
# 清理该会话下所有子 Agent 隔离槽(并行子 Agent 按 (session, tc_id) 分槽)
for k in [k for k in _sub_pending_thinking if k[0] == session_id]:
_sub_pending_thinking.pop(k, None)
with _sub_live_lock:
_sub_live_records.pop(session_id, None)
for k in [k for k in _sub_live_records if k[0] == session_id]:
_sub_live_records.pop(k, None)
with _live_lock:
_active_sessions.discard(session_id)
return _live_records.pop(session_id, [])
@@ -550,6 +596,7 @@ def save_aborted_turn(session_id: str, turn_id: str) -> None:
normalized: list[dict[str, Any]] = []
for r in records:
entry = {
"id": r.get("id", ""),
"name": r.get("name", ""),
"args": r.get("args", {}),
"args_summary": r.get("args_summary", ""),
@@ -607,15 +654,31 @@ def get_live_tool_calls(session_id: str) -> list[dict[str, Any]]:
if not records:
return []
# ---- 注入子 Agent 实时轨迹到 call_subagent 条目 ----
# ---- 注入子 Agent 实时轨迹到 call_subagent 条目(按 tool_call_id 精确匹配) ----
# 并行子 Agent(含同名)按 (session, tool_call_id) 分槽,注入到各自对应条目;
# 无 id 的旧数据回退到"最后一条 running"(旧行为兼容)。
with _sub_live_lock:
sub_calls = list(_sub_live_records.get(session_id, []))
if sub_calls:
# 找到最后一个 running 的 call_subagent 条目(从末尾向前找)
for rec in reversed(records):
if rec.get("name") in ("call_subagent", "agent_call") and rec.get("status") == "running":
sub_map = {k: list(v) for k, v in _sub_live_records.items() if k[0] == session_id}
if sub_map:
matched_ids: set[str] = set()
fallback_rec: dict[str, Any] | None = None
for rec in records:
if rec.get("name") not in ("call_subagent", "agent_call"):
continue
tc_id = rec.get("id") or ""
sub_calls = sub_map.get((session_id, tc_id))
if sub_calls:
rec["sub_tool_calls"] = sub_calls
break
matched_ids.add(tc_id)
elif rec.get("status") == "running" and fallback_rec is None:
fallback_rec = rec
if fallback_rec is not None and not fallback_rec.get("sub_tool_calls"):
leftover: list[dict[str, Any]] = []
for k, v in sub_map.items():
if k[1] not in matched_ids:
leftover.extend(v)
if leftover:
fallback_rec["sub_tool_calls"] = leftover
return records
@@ -654,11 +717,11 @@ def record_reflection_live(session_id: str, content: str) -> None:
"""
if not session_id or not content:
return
# 子 Agent 上下文:thinking 存入 _sub_pending_thinking,
# 子 Agent 上下文:thinking 存入 _sub_pending_thinking(按 (session, tc_id) 隔离),
# 由 record_tool_call_live 消费后附加到子工具调用条目。
if is_sub_agent_context():
with _sub_pending_thinking_lock:
_sub_pending_thinking[session_id] = content
_sub_pending_thinking[_sub_key(session_id)] = content
# 不添加到 _think_store(子 Agent 思考在主 Agent 思考面板中不独立显示,
# 而是嵌套在 call_subagent 展开的各个子工具调用条目的 thinking 字段中)
return
@@ -718,6 +781,7 @@ def extract_tool_calls_from_messages(messages: list[BaseMessage]) -> list[dict[s
for tc in msg.tool_calls:
d = normalize_tool_call(tc)
entry = {
"id": d.get("id", "") or "",
"name": d.get("name", "unknown"),
"args": _json_safe(d.get("args", {})),
"args_summary": format_args_summary(d.get("args", {}), max_len=40),
@@ -924,7 +988,11 @@ def _merge_extracted_with_live(
def _inject_sub_traces_to_record(tool_calls: list[dict[str, Any]], session_id: str, turn_id: str) -> None:
"""将子 Agent 的工具调用轨迹注入到 tool_calls 的 call_subagent 条目中(用于持久化)。"""
"""将子 Agent 的工具调用轨迹注入到 tool_calls 的 call_subagent 条目中(用于持久化)。
优先按条目携带的 tool_call_id 精确匹配(并行/同名子 Agent 各自独立);
无 id 的旧数据回退到按 agent_name 匹配(旧行为兼容)。
"""
for tc in tool_calls:
if tc.get("name") not in ("call_subagent", "agent_call"):
continue
@@ -936,10 +1004,15 @@ def _inject_sub_traces_to_record(tool_calls: list[dict[str, Any]], session_id: s
except Exception:
args = {}
sub_name = args.get("agent_name", "")
if sub_name:
if not sub_name:
continue
tc_id = tc.get("id") or ""
if tc_id:
sub_tc = get_sub_agent_traces_for_turn(session_id, turn_id, sub_name, tc_id)
else:
sub_tc = get_sub_agent_traces_for_turn(session_id, turn_id, sub_name)
if sub_tc:
tc["sub_tool_calls"] = sub_tc
if sub_tc:
tc["sub_tool_calls"] = sub_tc
def list_turns(session_id: str, limit: int = 5) -> list[dict[str, Any]]:
@@ -1058,7 +1131,17 @@ def _tool_calls_from_turn_record(record: dict[str, Any], session_id: str) -> lis
# ---------- 子 Agent 轨迹存取 ----------
def record_sub_agent_trace(session_id: str, turn_id: str, agent_name: str, tool_calls: list[dict[str, Any]]) -> None:
def _sub_trace_key(session_id: str, turn_id: str, agent_name: str, tool_call_id: str = "") -> str:
"""子 Agent 轨迹存储 key:f"{session_id}/{turn_id}/{agent_name}[/{tool_call_id}]"。
携带 tool_call_id 时按调用实例精确隔离(并行/同名子 Agent 各自独立);
为空时回退到旧的 agent_name 级 key(顺序执行/旧数据兼容)。
"""
base = f"{session_id}/{turn_id}/{agent_name}"
return f"{base}/{tool_call_id}" if tool_call_id else base
def record_sub_agent_trace(session_id: str, turn_id: str, agent_name: str, tool_calls: list[dict[str, Any]], tool_call_id: str = "") -> None:
"""记录子 Agent 的完整工具调用轨迹,供前端嵌套展开。
输入:
@@ -1066,27 +1149,29 @@ def record_sub_agent_trace(session_id: str, turn_id: str, agent_name: str, tool_
turn_id: 当前轮次 ID。
agent_name: 被调用的子 Agent 名称。
tool_calls: extract_tool_calls_from_messages 提取的子 Agent 工具调用列表。
tool_call_id: 本次 call_subagent 调用的工具调用 ID(并行隔离用,可为空)。
"""
if not session_id or not turn_id or not agent_name:
return
key = f"{session_id}/{turn_id}/{agent_name}"
key = _sub_trace_key(session_id, turn_id, agent_name, tool_call_id)
with _sub_traces_lock:
_sub_traces_store[key] = tool_calls
def get_sub_agent_traces_for_turn(session_id: str, turn_id: str, agent_name: str) -> list[dict[str, Any]]:
def get_sub_agent_traces_for_turn(session_id: str, turn_id: str, agent_name: str, tool_call_id: str = "") -> list[dict[str, Any]]:
"""获取指定会话/轮次/子 Agent 的轨迹。
输入:
session_id: 会话 ID。
turn_id: 轮次 ID。
agent_name: 子 Agent 名称。
tool_call_id: 可选工具调用 ID;传入时精确匹配该次调用,否则匹配 agent_name 级。
输出:
工具调用轨迹列表。
"""
if not session_id or not turn_id or not agent_name:
return []
key = f"{session_id}/{turn_id}/{agent_name}"
key = _sub_trace_key(session_id, turn_id, agent_name, tool_call_id)
with _sub_traces_lock:
return list(_sub_traces_store.get(key, []))
+29 -6
View File
@@ -40,7 +40,7 @@ def get_main_system_prompt(workspace_dir: str | None = None) -> str:
ws_info = _WS_TAIL.format(workspace_dir=ws_dir)
except Exception:
ws_info = ""
return _BASE_PROMPT + ws_info
return _BASE_PROMPT + _reflection_section() + ws_info
def set_current_workspace(workspace_dir: str | None) -> None:
@@ -63,10 +63,11 @@ def get_cron_system_prompt(workspace_dir: str | None = None) -> str:
try:
from agent.utils.env_utils import get_workspace_dir
ws_dir = workspace_dir or _current_workspace_dir or get_workspace_dir()
return CRON_MODE_PROMPT.format(workspace_dir=ws_dir)
base = CRON_MODE_PROMPT.format(workspace_dir=ws_dir)
except Exception:
# 占位符未填时退回不含工作空间信息的版本,避免 .format 异常中断执行
return CRON_MODE_PROMPT.replace("{workspace_dir}", "")
base = CRON_MODE_PROMPT.replace("{workspace_dir}", "")
return base + _reflection_section()
SUMMARY_SYSTEM_PROMPT ="""
你是一个名为“Summary Agent”的专业摘要助手,专门负责对用户提供的文本内容进行高度凝练、准确的总结。
@@ -157,7 +158,7 @@ def get_plan_system_prompt(workspace_dir: str | None = None) -> str:
ws_info = _WS_TAIL.format(workspace_dir=ws_dir)
except Exception:
ws_info = ""
return PLAN_MODE_PROMPT + ws_info
return PLAN_MODE_PROMPT + _reflection_section() + ws_info
CRON_MODE_PROMPT = """
@@ -187,8 +188,10 @@ CRON_MODE_PROMPT = """
- 写操作限制:doc_tool 的创建/修改/删除、terminal_execute 的文件变更目标必须位于工作空间内。无人值守场景下超出范围的访问会被直接拦截,请务必使用工作空间内的路径。
""".strip()
# ---------- 反思注入提示(作为 synthetic HumanMessage 注入到对话上下文)----------
# 这些不是系统提示词,而是在 call_model 节点中动态拼入消息列表的引导文本。
# ---------- 反思引导提示(固定部分 → 系统提示词前缀,前缀缓存友好)----------
# 固定不变的反思引导文本作为系统提示词的一部分拼在最前面(缓存命中),
# 不再在 call_model 节点中间逐轮构造;变化的部分(TodoList 状态、循环提醒)
# 由 call_model 作为动态尾部追加到消息列表末尾,并持久化为长期记忆。
REFLECTION_PROMPT = """
【重要:如果你判断不需要调用任何工具,请直接回复用户,不要输出任何反思内容!】
@@ -202,6 +205,26 @@ REFLECTION_PROMPT = """
如果你计划在同一页面/界面执行多个连续的 GUI 操作(例如:依次点击多个按钮、填写多个表单字段、连续输入多项内容),必须将所有这些操作合并为一次 gui_tool 调用,在 actions 列表中一次性传入所有动作。严禁将同一页面的连续操作拆分多次 gui_tool 调用——这样会导致严重的延迟。仅当需要等页面跳转/加载后才能确定后续操作时,才分步调用。
""".strip()
def _reflection_section() -> str:
"""固定反思引导段:思考关闭档位时并入系统提示词前缀(缓存友好),
思考开启档位(模型原生深度思考)不注入,保持原语义。
惰性导入 env_utils 避免模块级循环依赖。
"""
try:
from agent.utils.env_utils import thinking_enabled
if thinking_enabled():
return ""
except Exception:
pass
return "\n\n" + REFLECTION_PROMPT
def get_default_sub_agent_prompt() -> str:
"""获取默认子 Agent 系统提示词(含按思考档位门控的固定反思引导)。"""
return DEFAULT_SUB_AGENT_PROMPT + _reflection_section()
# 子 Agent 上下文超限提示(由 agent 节点在子 Agent token 超阈值时注入,强制其整理进度并文字返回)
SUB_AGENT_OOM_PROMPT = """
【系统指令 - 上下文超限】
+32 -14
View File
@@ -153,17 +153,21 @@ class CallSubAgentTool(BaseTool):
# 设置子 Agent 上下文:agent 名称供前端浮窗按 agent 分组
from agent.core.human_request import set_current_agent_name
set_current_agent_name(agent_name)
# 当前 call_subagent 的工具调用 ID:并行多个子 Agent(含同名)时,
# 子 Agent 的实时记录与轨迹存档均按 (session, tool_call_id) 隔离。
from agent.history.tool_call_recorder import get_current_tool_call_id
tool_call_id = get_current_tool_call_id()
# 进入子 Agent 执行上下文:抑制其 record_*_live 调用,
# 避免子 Agent 的思考/工具调用混入主 Agent 的实时列表。
# 子 Agent 轨迹仍由 _record_sub_trace 独立存档,持久化时嵌套注入。
from agent.history.tool_call_recorder import sub_agent_context
try:
with sub_agent_context():
with sub_agent_context(tool_call_id):
result = runtime.graph.invoke({"messages": messages}, config=config)
all_messages = result.get("messages", [])
# 提取子 Agent 工具调用轨迹 → 存档供前端嵌套展开
_record_sub_trace(agent_name, all_messages)
# 提取子 Agent 工具调用轨迹 → 存档供前端嵌套展开(按 tool_call_id 隔离)
_record_sub_trace(agent_name, all_messages, tool_call_id)
# 提取最后一条非工具调用的助手消息作为最终结果
from langchain_core.messages import AIMessage
@@ -200,10 +204,11 @@ class CallSubAgentTool(BaseTool):
# ---------- 子 Agent 轨迹记录 ----------
def _record_sub_trace(agent_name: str, messages: list) -> None:
def _record_sub_trace(agent_name: str, messages: list, tool_call_id: str = "") -> None:
"""记录子 Agent 的工具调用轨迹,供前端嵌套展开。
同时写入 tool_call_recorder 的 session/turn 作用域存储。
同时写入 tool_call_recorder 的 session/turn 作用域存储,
tool_call_id 用于并行子 Agent(含同名)的轨迹隔离。
"""
# 桥接到 tool_call_recorder(按 session/turn 作用域)
@@ -218,7 +223,7 @@ def _record_sub_trace(agent_name: str, messages: list) -> None:
turn_id = _recorder_turn()
if session_id and turn_id:
sub_tc = _recorder_extract(list(messages))
_recorder_record(session_id, turn_id, agent_name, sub_tc)
_recorder_record(session_id, turn_id, agent_name, sub_tc, tool_call_id)
except Exception:
pass
@@ -249,16 +254,24 @@ def make_parallel_tool_node(tools: list[Any], max_workers: int = 5):
"""在线程池中执行单个工具。
委托 invoke_tool_and_build_message 统一处理返回类型。
执行前设置当前 tool_call_id(经上下文传播进入 call_subagent._run,
供并行子 Agent 的轨迹/实时记录按 tool_call_id 隔离)。
返回 (tool_name, content, artifact),artifact 为 None 表示无产物。
"""
tool = tools_by_name.get(tool_name)
if tool is None:
return tool_name, f"工具 '{tool_name}' 未找到", None
from agent.history.tool_call_recorder import set_current_tool_call_id, get_current_tool_call_id
_prev_tc = get_current_tool_call_id()
set_current_tool_call_id(tool_call_id)
try:
content, artifact = invoke_tool_and_build_message(tool, tool_name, tool_args, tool_call_id)
return tool_name, content, artifact
except Exception as e:
return tool_name, f"工具执行出错: {e}", None
tool = tools_by_name.get(tool_name)
if tool is None:
return tool_name, f"工具 '{tool_name}' 未找到", None
try:
content, artifact = invoke_tool_and_build_message(tool, tool_name, tool_args, tool_call_id)
return tool_name, content, artifact
except Exception as e:
return tool_name, f"工具执行出错: {e}", None
finally:
set_current_tool_call_id(_prev_tc)
def parallel_tool_node(state: AgentState) -> dict:
"""并行执行最后一条 AIMessage 中的所有 tool_calls。"""
@@ -285,13 +298,18 @@ def make_parallel_tool_node(tools: list[Any], max_workers: int = 5):
}
# 多个工具调用:线程池并行执行
# 用 contextvars.copy_context() 包装 submit,使 worker 线程继承
# 图线程的执行上下文(session/turn/agent 名称/sub_agent_context 等),
# 保证并行工具(含并行子 Agent)之间的状态隔离与前端归属正确。
import contextvars
futures: dict[concurrent.futures.Future, dict] = {}
with concurrent.futures.ThreadPoolExecutor(max_workers=min(max_workers, len(tool_calls))) as executor:
for tc in tool_calls:
tc_name = tc.get("name") if isinstance(tc, dict) else getattr(tc, "name", "")
tc_args = tc.get("args") if isinstance(tc, dict) else getattr(tc, "args", {})
tc_id = tc.get("id") if isinstance(tc, dict) else getattr(tc, "id", "")
future = executor.submit(_execute_single, tc_name, tc_args, tc_id)
ctx = contextvars.copy_context()
future = executor.submit(ctx.run, _execute_single, tc_name, tc_args, tc_id)
futures[future] = {"id": tc_id, "name": tc_name}
# 收集结果,保持顺序
+43
View File
@@ -182,6 +182,49 @@ def update_session_meta(session_id: str, agent_name: str,
_save_session_meta(session_id, meta)
# ---------- 动态尾部长期记忆(TodoList 状态 + 循环提醒) ----------
def update_session_dynamic_tail(session_id: str, agent_key: str, text: str, max_entries: int = 100) -> None:
"""持久化当前轮的动态尾部提示(TodoList 状态 + 循环提醒)为长期记忆。
输入:
session_id: 会话 ID。
agent_key: session_meta 中的 agent 键(主 Agent 为 "main",子 Agent 用其名称)。
text: 本轮动态尾部文本。
max_entries: 历史条目上限(防止无界增长),超出丢弃最旧。
说明:
与 compressed_content 合并保存(不覆盖);连续相同内容去重,
使 todo 进度等状态变更形成可追溯的历史记录,供后续轮次注入查看
(充分利用前缀缓存:变化部分始终位于消息末尾)。
"""
if not session_id or not agent_key or not text:
return
meta = _load_session_meta(session_id)
entry = dict(meta.get(agent_key) or {})
history = list(entry.get("dynamic_tail_history") or [])
if history and history[-1] == text:
return # 去重:内容未变化则不重复记录
history.append(text)
if len(history) > max_entries:
history = history[-max_entries:]
entry["dynamic_tail_history"] = history
meta[agent_key] = entry
_save_session_meta(session_id, meta)
def get_session_dynamic_tail_history(session_id: str, agent_key: str) -> list[str]:
"""读取指定 Agent 的动态尾部长期记忆历史(时间正序,最新在末尾)。
输出:
[tail_text, ...];无记录返回空列表。
"""
if not session_id or not agent_key:
return []
meta = _load_session_meta(session_id)
entry = meta.get(agent_key) or {}
return list(entry.get("dynamic_tail_history") or [])
def build_tool_history_messages(session_id: str, agent_key: str = "main") -> list:
"""构建压缩游标之后的"历史工具调用与返回值"合成消息列表。
+9 -1
View File
@@ -10,6 +10,7 @@
"""
from __future__ import annotations
import contextvars
import json
import re
import threading
@@ -24,6 +25,12 @@ def _invoke_with_timeout(tool, tool_call_input: dict, timeout: float) -> Any:
超时后工作线程仍在后台运行(Python 无法强杀线程),结果被丢弃。
工具内部抛出的异常会在主线程 re-raise(保留 ABORTED_BY_USER 冒泡)。
上下文传播:
通过 ``contextvars.copy_context()`` 把调用方上下文(session/turn、
agent 名称、sub_agent_context、tool_call_id 等 ContextVar)复制进
超时线程,使工具实现(如 call_subagent 的 _run)在独立线程中也能
读到正确的执行上下文,保证并行工具/并行子 Agent 的隔离性。
输入:
tool: LangChain BaseTool 实例。
tool_call_input: 传给 tool.invoke 的输入字典。
@@ -40,7 +47,8 @@ def _invoke_with_timeout(tool, tool_call_input: dict, timeout: float) -> Any:
except BaseException as e: # noqa: BLE001 - 需透传 ABORTED_BY_USER 等所有异常
container["error"] = e
t = threading.Thread(target=_worker, daemon=True)
ctx = contextvars.copy_context()
t = threading.Thread(target=lambda: ctx.run(_worker), daemon=True)
t.start()
t.join(timeout=timeout)
if t.is_alive():
+90
View File
@@ -0,0 +1,90 @@
/* 本地中文字体(fontsource 子集),离线可用;font-display: swap 保证不阻塞渲染 */
/* generated from @fontsource chinese-simplified + latin subsets */
/* zcool-kuaile - chinese-simplified */
/* zcool-kuaile-chinese-simplified-400-normal */
@font-face {
font-family: 'ZCOOL KuaiLe';
font-style: normal;
font-display: swap;
font-weight: 400;
unicode-range: U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+20000-2A6DF;
src: url(./zcool-kuaile-chinese-simplified-400-normal.woff2) format('woff2');
}
/* zcool-kuaile - latin */
/* zcool-kuaile-latin-400-normal */
@font-face {
font-family: 'ZCOOL KuaiLe';
font-style: normal;
font-display: swap;
font-weight: 400;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url(./zcool-kuaile-latin-400-normal.woff2) format('woff2');
}
/* ma-shan-zheng - chinese-simplified */
/* ma-shan-zheng-chinese-simplified-400-normal */
@font-face {
font-family: 'Ma Shan Zheng';
font-style: normal;
font-display: swap;
font-weight: 400;
unicode-range: U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+20000-2A6DF;
src: url(./ma-shan-zheng-chinese-simplified-400-normal.woff2) format('woff2');
}
/* ma-shan-zheng - latin */
/* ma-shan-zheng-latin-400-normal */
@font-face {
font-family: 'Ma Shan Zheng';
font-style: normal;
font-display: swap;
font-weight: 400;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url(./ma-shan-zheng-latin-400-normal.woff2) format('woff2');
}
/* long-cang - chinese-simplified */
/* long-cang-chinese-simplified-400-normal */
@font-face {
font-family: 'Long Cang';
font-style: normal;
font-display: swap;
font-weight: 400;
unicode-range: U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+20000-2A6DF;
src: url(./long-cang-chinese-simplified-400-normal.woff2) format('woff2');
}
/* long-cang - latin */
/* long-cang-latin-400-normal */
@font-face {
font-family: 'Long Cang';
font-style: normal;
font-display: swap;
font-weight: 400;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url(./long-cang-latin-400-normal.woff2) format('woff2');
}
/* zcool-qingke-huangyou - chinese-simplified */
/* zcool-qingke-huangyou-chinese-simplified-400-normal */
@font-face {
font-family: 'ZCOOL QingKe HuangYou';
font-style: normal;
font-display: swap;
font-weight: 400;
unicode-range: U+3400-4DBF, U+4E00-9FFF, U+F900-FAFF, U+20000-2A6DF;
src: url(./zcool-qingke-huangyou-chinese-simplified-400-normal.woff2) format('woff2');
}
/* zcool-qingke-huangyou - latin */
/* zcool-qingke-huangyou-latin-400-normal */
@font-face {
font-family: 'ZCOOL QingKe HuangYou';
font-style: normal;
font-display: swap;
font-weight: 400;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
src: url(./zcool-qingke-huangyou-latin-400-normal.woff2) format('woff2');
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -8
View File
@@ -3,12 +3,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&family=Ma+Shan+Zheng&family=Long+Cang&family=ZCOOL+QingKe+HuangYou&display=swap"
rel="stylesheet"
/>
<!-- 本地字体(离线可用,不再依赖 Google Fonts CDN) -->
<link rel="stylesheet" href="/fonts/fonts.css" />
<link rel="icon" href="/image/favicon.svg" sizes="any" />
<link rel="stylesheet" href="/app.css?v=6" />
</head>
@@ -543,8 +539,8 @@
</div>
</div>
<div class="toast-container" id="toastContainer" aria-live="polite"></div>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@3.1.6/dist/purify.min.js"></script>
<script src="/packages/vendor/marked.min.js"></script>
<script src="/packages/vendor/purify.min.js"></script>
<!-- Monaco Editor: CDN 优先(Web端),本地 npm 包兜底(Electron 离线) -->
<script>
(function() {
+69
View File
@@ -0,0 +1,69 @@
/**
* marked v15.0.12 - a markdown parser
* Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed)
* https://github.com/markedjs/marked
*/
/**
* DO NOT EDIT THIS FILE
* The code in this file is generated from files in ./src/
*/
(function(g,f){if(typeof exports=="object"&&typeof module<"u"){module.exports=f()}else if("function"==typeof define && define.amd){define("marked",f)}else {g["marked"]=f()}}(typeof globalThis < "u" ? globalThis : typeof self < "u" ? self : this,function(){var exports={};var __exports=exports;var module={exports};
"use strict";var H=Object.defineProperty;var be=Object.getOwnPropertyDescriptor;var Te=Object.getOwnPropertyNames;var we=Object.prototype.hasOwnProperty;var ye=(l,e)=>{for(var t in e)H(l,t,{get:e[t],enumerable:!0})},Re=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of Te(e))!we.call(l,s)&&s!==t&&H(l,s,{get:()=>e[s],enumerable:!(n=be(e,s))||n.enumerable});return l};var Se=l=>Re(H({},"__esModule",{value:!0}),l);var kt={};ye(kt,{Hooks:()=>L,Lexer:()=>x,Marked:()=>E,Parser:()=>b,Renderer:()=>$,TextRenderer:()=>_,Tokenizer:()=>S,defaults:()=>w,getDefaults:()=>z,lexer:()=>ht,marked:()=>k,options:()=>it,parse:()=>pt,parseInline:()=>ct,parser:()=>ut,setOptions:()=>ot,use:()=>lt,walkTokens:()=>at});module.exports=Se(kt);function z(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var w=z();function N(l){w=l}var I={exec:()=>null};function h(l,e=""){let t=typeof l=="string"?l:l.source,n={replace:(s,i)=>{let r=typeof i=="string"?i:i.source;return r=r.replace(m.caret,"$1"),t=t.replace(s,r),n},getRegex:()=>new RegExp(t,e)};return n}var m={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^<a /i,endATag:/^<\/a>/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^</,endAngleBracket:/>$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:l=>new RegExp(`^( {0,3}${l})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}#`),htmlBeginRegex:l=>new RegExp(`^ {0,${Math.min(3,l-1)}}<(?:[a-z].*>|!--)`,"i")},$e=/^(?:[ \t]*(?:\n|$))+/,_e=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Le=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,O=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,ze=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,F=/(?:[*+-]|\d{1,9}[.)])/,ie=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,oe=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Me=h(ie).replace(/bull/g,F).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),Q=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Pe=/^[^\n]+/,U=/(?!\s*\])(?:\\.|[^\[\]\\])+/,Ae=h(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",U).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Ee=h(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,F).getRegex(),v="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",K=/<!--(?:-?>|[\s\S]*?(?:-->|$))/,Ce=h("^ {Line truncated
]`).replace("lheading",oe).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},qe=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,ve=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,ae=/^( {2,}|\\)\n(?!\s*$)/,De=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/,D=/[\p{P}\p{S}]/u,W=/[\s\p{P}\p{S}]/u,ce=/[^\s\p{P}\p{S}]/u,Ze=h(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,W).getRegex(),pe=/(?!~)[\p{P}\p{S}]/u,Ge=/(?!~)[\s\p{P}\p{S}]/u,He=/(?:[^\s\p{P}\p{S}]|~)/u,Ne=/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,ue=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,je=h(ue,"u").replace(/punct/g,D).getRegex(),Fe=h(ue,"u").replace(/punct/g,pe).getRegex(),he="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",Qe=h(he,"gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Ue=h(he,"gu").replace(/notPunctSpace/g,He).replace(/punctSpace/g,Ge).replace(/punct/g,pe).getRegex(),Ke=h("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,ce).replace(/punctSpace/g,W).replace(/punct/g,D).getRegex(),Xe=h(/\\(punct)/,"gu").replace(/punct/g,D).getRegex(),We=h(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),Je=h(K).replace("(?:-->|$)","-->").getRegex(),Ve=h("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment",Je).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),q=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Ye=h(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",q).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),ke=h(/^!?\[(label)\]\[(ref)\]/).replace("label",q).replace("ref",U).getRegex(),ge=h(/^!?\[(ref)\](?:\[\])?/).replace("ref",U).getRegex(),et=h("reflink|nolink(?!\\()","g").replace("reflink",ke).replace("nolink",ge).getRegex(),J={_backpedal:I,anyPunctuation:Xe,autolink:We,blockSkip:Ne,br:ae,code:ve,del:I,emStrongLDelim:je,emStrongRDelimAst:Qe,emStrongRDelimUnd:Ke,escape:qe,link:Ye,nolink:ge,punctuation:Ze,reflink:ke,reflinkSearch:et,tag:Ve,text:De,url:I},tt={...J,link:h(/^!?\[(label)\]\((.*?)\)/).replace("label",q).getRegex(),reflink:h(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",q).getRegex()},j={...J,emStrongRDelimAst:Ue,emStrongLDelim:Fe,url:h(/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,"i").replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\.|[^\\])*?(?:\\.|[^\s~\\]))\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|https?:\/\/|ftp:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/},nt={...j,br:h(ae).replace("{2,}","*").getRegex(),text:h(j.text).replace("\\b_","\\b_| {2,}\\n").replace(/\{2,\}/g,"*").getRegex()},B={normal:X,gfm:Oe,pedantic:Be},P={normal:J,gfm:j,breaks:nt,pedantic:tt};var st={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},fe=l=>st[l];function R(l,e){if(e){if(m.escapeTest.test(l))return l.replace(m.escapeReplace,fe)}else if(m.escapeTestNoEncode.test(l))return l.replace(m.escapeReplaceNoEncode,fe);return l}function V(l){try{l=encodeURI(l).replace(m.percentDecode,"%")}catch{return null}return l}function Y(l,e){let t=l.replace(m.findPipe,(i,r,o)=>{let a=!1,c=r;for(;--c>=0&&o[c]==="\\";)a=!a;return a?"|":" |"}),n=t.split(m.splitPipe),s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),e)if(n.length>e)n.splice(e);else for(;n.length<e;)n.push("");for(;s<n.length;s++)n[s]=n[s].trim().replace(m.slashPipe,"|");return n}function A(l,e,t){let n=l.length;if(n===0)return"";let s=0;for(;s<n;){let i=l.charAt(n-s-1);if(i===e&&!t)s++;else if(i!==e&&t)s++;else break}return l.slice(0,n-s)}function de(l,e){if(l.indexOf(e[1])===-1)return-1;let t=0;for(let n=0;n<l.length;n++)if(l[n]==="\\")n++;else if(l[n]===e[0])t++;else if(l[n]===e[1]&&(t--,t<0))return n;return t>0?-2:-1}fLine truncated
`).map(i=>{let r=i.match(t.other.beginningSpace);if(r===null)return i;let[o]=r;return o.length>=s.length?i.slice(s.length):i}).join(`
`)}var S=class{options;rules;lexer;constructor(e){this.options=e||w}space(e){let t=this.rules.block.newline.exec(e);if(t&&t[0].length>0)return{type:"space",raw:t[0]}}code(e){let t=this.rules.block.code.exec(e);if(t){let n=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?n:A(n,`
`)}}}fences(e){let t=this.rules.block.fences.exec(e);if(t){let n=t[0],s=rt(n,t[3]||"",this.rules);return{type:"code",raw:n,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:s}}}heading(e){let t=this.rules.block.heading.exec(e);if(t){let n=t[2].trim();if(this.rules.other.endingHash.test(n)){let s=A(n,"#");(this.options.pedantic||!s||this.rules.other.endingSpaceChar.test(s))&&(n=s.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(e){let t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:A(t[0],`
`)}}blockquote(e){let t=this.rules.block.blockquote.exec(e);if(t){let n=A(t[0],`
`).split(`
`),s="",i="",r=[];for(;n.length>0;){let o=!1,a=[],c;for(c=0;c<n.length;c++)if(this.rules.other.blockquoteStart.test(n[c]))a.push(n[c]),o=!0;else if(!o)a.push(n[c]);else break;n=n.slice(c);let p=a.join(`
`),u=p.replace(this.rules.other.blockquoteSetextReplace,`
$1`).replace(this.rules.other.blockquoteSetextReplace2,"");s=s?`${s}
${p}`:p,i=i?`${i}
${u}`:u;let d=this.lexer.state.top;if(this.lexer.state.top=!0,this.lexer.blockTokens(u,r,!0),this.lexer.state.top=d,n.length===0)break;let g=r.at(-1);if(g?.type==="code")break;if(g?.type==="blockquote"){let T=g,f=T.raw+`
`+n.join(`
`),y=this.blockquote(f);r[r.length-1]=y,s=s.substring(0,s.length-T.raw.length)+y.raw,i=i.substring(0,i.length-T.text.length)+y.text;break}else if(g?.type==="list"){let T=g,f=T.raw+`
`+n.join(`
`),y=this.list(f);r[r.length-1]=y,s=s.substring(0,s.length-g.raw.length)+y.raw,i=i.substring(0,i.length-T.raw.length)+y.raw,n=f.substring(r.at(-1).raw.length).split(`
`);continue}}return{type:"blockquote",raw:s,tokens:r,text:i}}}list(e){let t=this.rules.block.list.exec(e);if(t){let n=t[1].trim(),s=n.length>1,i={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");let r=this.rules.other.listItemRegex(n),o=!1;for(;e;){let c=!1,p="",u="";if(!(t=r.exec(e))||this.rules.block.hr.test(e))break;p=t[0],e=e.substring(p.length);let d=t[2].split(`
`,1)[0].replace(this.rules.other.listReplaceTabs,Z=>" ".repeat(3*Z.length)),g=e.split(`
`,1)[0],T=!d.trim(),f=0;if(this.options.pedantic?(f=2,u=d.trimStart()):T?f=t[1].length+1:(f=t[2].search(this.rules.other.nonSpaceChar),f=f>4?1:f,u=d.slice(f),f+=t[1].length),T&&this.rules.other.blankLine.test(g)&&(p+=g+`
`,e=e.substring(g.length+1),c=!0),!c){let Z=this.rules.other.nextBulletRegex(f),te=this.rules.other.hrRegex(f),ne=this.rules.other.fencesBeginRegex(f),se=this.rules.other.headingBeginRegex(f),xe=this.rules.other.htmlBeginRegex(f);for(;e;){let G=e.split(`
`,1)[0],C;if(g=G,this.options.pedantic?(g=g.replace(this.rules.other.listReplaceNesting," "),C=g):C=g.replace(this.rules.other.tabCharGlobal," "),ne.test(g)||se.test(g)||xe.test(g)||Z.test(g)||te.test(g))break;if(C.search(this.rules.other.nonSpaceChar)>=f||!g.trim())u+=`
`+C.slice(f);else{if(T||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||ne.test(d)||se.test(d)||te.test(d))break;u+=`
`+g}!T&&!g.trim()&&(T=!0),p+=G+`
`,e=e.substring(G.length+1),d=C.slice(f)}}i.loose||(o?i.loose=!0:this.rules.other.doubleBlankLine.test(p)&&(o=!0));let y=null,ee;this.options.gfm&&(y=this.rules.other.listIsTask.exec(u),y&&(ee=y[0]!=="[ ] ",u=u.replace(this.rules.other.listReplaceTask,""))),i.items.push({type:"list_item",raw:p,task:!!y,checked:ee,loose:!1,text:u,tokens:[]}),i.raw+=p}let a=i.items.at(-1);if(a)a.raw=a.raw.trimEnd(),a.text=a.text.trimEnd();else return;i.raw=i.raw.trimEnd();for(let c=0;c<i.items.length;c++)if(this.lexer.state.top=!1,i.items[c].tokens=this.lexer.blockTokens(i.items[c].text,[]),!i.loose){let p=i.items[c].tokens.filter(d=>d.type==="space"),u=p.length>0&&p.some(d=>this.rules.other.anyLine.test(d.raw));i.loose=u}if(i.loose)for(let c=0;c<i.items.length;c++)i.items[c].loose=!0;return i}}html(e){let t=this.rules.block.html.exec(e);if(t)return{type:"html",block:!0,raw:t[0],pre:t[1]==="pre"||t[1]==="script"||t[1]==="style",text:t[0]}}def(e){let t=this.rules.block.def.exec(e);if(t){let n=t[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),s=t[2]?t[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",i=t[3]?t[3].substring(1,t[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):t[3];return{type:"def",tag:n,raw:t[0],href:s,title:i}}}table(e){let t=this.rules.block.table.exec(e);if(!t||!this.rules.other.tableDelimiter.test(t[2]))return;let n=Y(t[1]),s=t[2].replace(this.rules.other.tableAlignChars,"").split("|"),i=t[3]?.trim()?t[3].replace(this.rules.other.tableRowBlankLine,"").split(`
`):[],r={type:"table",raw:t[0],header:[],align:[],rows:[]};if(n.length===s.length){for(let o of s)this.rules.other.tableAlignRight.test(o)?r.align.push("right"):this.rules.other.tableAlignCenter.test(o)?r.align.push("center"):this.rules.other.tableAlignLeft.test(o)?r.align.push("left"):r.align.push(null);for(let o=0;o<n.length;o++)r.header.push({text:n[o],tokens:this.lexer.inline(n[o]),header:!0,align:r.align[o]});for(let o of i)r.rows.push(Y(o,r.header.length).map((a,c)=>({text:a,tokens:this.lexer.inline(a),header:!1,align:r.align[c]})));return r}}lheading(e){let t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:t[2].charAt(0)==="="?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){let t=this.rules.block.paragraph.exec(e);if(t){let n=t[1].charAt(t[1].length-1)===`
`?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:n,tokens:this.lexer.inline(n)}}}text(e){let t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){let t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){let t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){let t=this.rules.inline.link.exec(e);if(t){let n=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let r=A(n.slice(0,-1),"\\");if((n.length-r.length)%2===0)return}else{let r=de(t[2],"()");if(r===-2)return;if(r>-1){let a=(t[0].indexOf("!")===0?5:4)+t[1].length+r;t[2]=t[2].substring(0,r),t[0]=t[0].substring(0,a).trim(),t[3]=""}}let s=t[2],i="";if(this.options.pedantic){let r=this.rules.other.pedanticHrefTitle.exec(s);r&&(s=r[1],i=r[3])}else i=t[3]?t[3].slice(1,-1):"";return s=s.trim(),this.rules.other.startAngleBracket.test(s)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?s=s.slice(1):s=s.slice(1,-1)),me(t,{href:s&&s.replace(this.rules.inline.anyPunctuation,"$1"),title:i&&i.replace(this.rules.inline.anyPunctuation,"$1")},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){let s=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),i=t[s.toLowerCase()];if(!i){let r=n[0].charAt(0);return{type:"text",raw:r,text:r}}return me(n,i,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s||s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){let r=[...s[0]].length-1,o,a,c=r,p=0,u=s[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(u.lastIndex=0,t=t.slice(-1*e.length+r);(s=u.exec(t))!=null;){if(o=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!o)continue;if(a=[...o].length,s[3]||s[4]){c+=a;continue}else if((s[5]||s[6])&&r%3&&!((r+a)%3)){p+=a;continue}if(c-=a,c>0)continue;a=Math.min(a,a+c+p);let d=[...s[0]][0].length,g=e.slice(0,r+s.index+d+a);if(Math.min(r,a)%2){let f=g.slice(1,-1);return{type:"em",raw:g,text:f,tokens:this.lexer.inlineTokens(f)}}let T=g.slice(2,-2);return{type:"strong",raw:g,text:T,tokens:this.lexer.inlineTokens(T)}}}}codespan(e){let t=this.rules.inline.code.exec(e);if(t){let n=t[2].replace(this.rules.other.newLineCharGlobal," "),s=this.rules.other.nonSpaceChar.test(n),i=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return s&&i&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:t[0],text:n}}}br(e){let t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){let t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){let t=this.rules.inline.autolink.exec(e);if(t){let n,s;return t[2]==="@"?(n=t[1],s="mailto:"+n):(n=t[1],s=n),{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let n,s;if(t[2]==="@")n=t[0],s="mailto:"+n;else{let i;do i=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??"";while(i!==t[0]);n=t[0],t[1]==="www."?s="http://"+t[0]:s=t[0]}return{type:"link",raw:t[0],text:n,href:s,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(e){let t=this.rules.inline.text.exec(e);if(t){let n=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:n}}}};var x=class l{tokens;options;state;tokenizer;inlineQueue;constructor(e){this.tokens=[],this.tokens.links=Object.create(null),this.options=e||w,this.options.tokenizer=this.options.tokenizer||new S,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:m,block:B.normal,inline:P.normal};this.options.pedantic?(t.block=B.pedantic,t.inline=P.pedantic):this.options.gfm&&(t.block=B.gfm,this.options.breaks?t.inline=P.breaks:t.inline=P.gfm),this.tokenizer.rules=t}static get rules(){return{block:B,inline:P}}static lex(e,t){return new l(t).lex(e)}static lexInline(e,t){return new l(t).inlineTokens(e)}lex(e){e=e.replace(m.carriageReturn,`
`),this.blockTokens(e,this.tokens);for(let t=0;t<this.inlineQueue.length;t++){let n=this.inlineQueue[t];this.inlineTokens(n.src,n.tokens)}return this.inlineQueue=[],this.tokens}blockTokens(e,t=[],n=!1){for(this.options.pedantic&&(e=e.replace(m.tabCharGlobal," ").replace(m.spaceLine,""));e;){let s;if(this.options.extensions?.block?.some(r=>(s=r.call({lexer:this},e,t))?(e=e.substring(s.raw.length),t.push(s),!0):!1))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);let r=t.at(-1);s.raw.length===1&&r!==void 0?r.raw+=`
`:t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=`
`+s.raw,r.text+=`
`+s.text,this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="paragraph"||r?.type==="text"?(r.raw+=`
`+s.raw,r.text+=`
`+s.raw,this.inlineQueue.at(-1).src=r.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let i=e;if(this.options.extensions?.startBlock){let r=1/0,o=e.slice(1),a;this.options.extensions.startBlock.forEach(c=>{a=c.call({lexer:this},o),typeof a=="number"&&a>=0&&(r=Math.min(r,a))}),r<1/0&&r>=0&&(i=e.substring(0,r+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i))){let r=t.at(-1);n&&r?.type==="paragraph"?(r.raw+=`
`+s.raw,r.text+=`
`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s),n=i.length!==e.length,e=e.substring(s.raw.length);continue}if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);let r=t.at(-1);r?.type==="text"?(r.raw+=`
`+s.raw,r.text+=`
`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=r.text):t.push(s);continue}if(e){let r="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(r);break}else throw new Error(r)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){let o=Object.keys(this.tokens.links);if(o.length>0)for(;(s=this.tokenizer.rules.inline.reflinkSearch.exec(n))!=null;)o.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(s=this.tokenizer.rules.inline.anyPunctuation.exec(n))!=null;)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;(s=this.tokenizer.rules.inline.blockSkip.exec(n))!=null;)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);let i=!1,r="";for(;e;){i||(r=""),i=!1;let o;if(this.options.extensions?.inline?.some(c=>(o=c.call({lexer:this},e,t))?(e=e.substring(o.raw.length),t.push(o),!0):!1))continue;if(o=this.tokenizer.escape(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.tag(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.link(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(o.raw.length);let c=t.at(-1);o.type==="text"&&c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(o=this.tokenizer.emStrong(e,n,r)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.codespan(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.br(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.del(e)){e=e.substring(o.raw.length),t.push(o);continue}if(o=this.tokenizer.autolink(e)){e=e.substring(o.raw.length),t.push(o);continue}if(!this.state.inLink&&(o=this.tokenizer.url(e))){e=e.substring(o.raw.length),t.push(o);continue}let a=e;if(this.options.extensions?.startInline){let c=1/0,p=e.slice(1),u;this.options.extensions.startInline.forEach(d=>{u=d.call({lexer:this},p),typeof u=="number"&&u>=0&&(c=Math.min(c,u))}),c<1/0&&c>=0&&(a=e.substring(0,c+1))}if(o=this.tokenizer.inlineText(a)){e=e.substring(o.raw.length),o.raw.slice(-1)!=="_"&&(r=o.raw.slice(-1)),i=!0;let c=t.at(-1);c?.type==="text"?(c.raw+=o.raw,c.text+=o.text):t.push(o);continue}if(e){let c="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(c);break}else throw new Error(c)}}return t}};var $=class{options;parser;constructor(e){this.options=e||w}space(e){return""}code({text:e,lang:t,escaped:n}){let s=(t||"").match(m.notSpaceStart)?.[0],i=e.replace(m.endingNewline,"")+`
`;return s?'<pre><code class="language-'+R(s)+'">'+(n?i:R(i,!0))+`</code></pre>
`:"<pre><code>"+(n?i:R(i,!0))+`</code></pre>
`}blockquote({tokens:e}){return`<blockquote>
${this.parser.parse(e)}</blockquote>
`}html({text:e}){return e}heading({tokens:e,depth:t}){return`<h${t}>${this.parser.parseInline(e)}</h${t}>
`}hr(e){return`<hr>
`}list(e){let t=e.ordered,n=e.start,s="";for(let o=0;o<e.items.length;o++){let a=e.items[o];s+=this.listitem(a)}let i=t?"ol":"ul",r=t&&n!==1?' start="'+n+'"':"";return"<"+i+r+`>
`+s+"</"+i+`>
`}listitem(e){let t="";if(e.task){let n=this.checkbox({checked:!!e.checked});e.loose?e.tokens[0]?.type==="paragraph"?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&e.tokens[0].tokens[0].type==="text"&&(e.tokens[0].tokens[0].text=n+" "+R(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`<li>${t}</li>
`}checkbox({checked:e}){return"<input "+(e?'checked="" ':"")+'disabled="" type="checkbox">'}paragraph({tokens:e}){return`<p>${this.parser.parseInline(e)}</p>
`}table(e){let t="",n="";for(let i=0;i<e.header.length;i++)n+=this.tablecell(e.header[i]);t+=this.tablerow({text:n});let s="";for(let i=0;i<e.rows.length;i++){let r=e.rows[i];n="";for(let o=0;o<r.length;o++)n+=this.tablecell(r[o]);s+=this.tablerow({text:n})}return s&&(s=`<tbody>${s}</tbody>`),`<table>
<thead>
`+t+`</thead>
`+s+`</table>
`}tablerow({text:e}){return`<tr>
${e}</tr>
`}tablecell(e){let t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`</${n}>
`}strong({tokens:e}){return`<strong>${this.parser.parseInline(e)}</strong>`}em({tokens:e}){return`<em>${this.parser.parseInline(e)}</em>`}codespan({text:e}){return`<code>${R(e,!0)}</code>`}br(e){return"<br>"}del({tokens:e}){return`<del>${this.parser.parseInline(e)}</del>`}link({href:e,title:t,tokens:n}){let s=this.parser.parseInline(n),i=V(e);if(i===null)return s;e=i;let r='<a href="'+e+'"';return t&&(r+=' title="'+R(t)+'"'),r+=">"+s+"</a>",r}image({href:e,title:t,text:n,tokens:s}){s&&(n=this.parser.parseInline(s,this.parser.textRenderer));let i=V(e);if(i===null)return R(n);e=i;let r=`<img src="${e}" alt="${n}"`;return t&&(r+=` title="${R(t)}"`),r+=">",r}text(e){return"tokens"in e&&e.tokens?this.parser.parseInline(e.tokens):"escaped"in e&&e.escaped?e.text:R(e.text)}};var _=class{strong({text:e}){return e}em({text:e}){return e}codespan({text:e}){return e}del({text:e}){return e}html({text:e}){return e}text({text:e}){return e}link({text:e}){return""+e}image({text:e}){return""+e}br(){return""}};var b=class l{options;renderer;textRenderer;constructor(e){this.options=e||w,this.options.renderer=this.options.renderer||new $,this.renderer=this.options.renderer,this.renderer.options=this.options,this.renderer.parser=this,this.textRenderer=new _}static parse(e,t){return new l(t).parse(e)}static parseInline(e,t){return new l(t).parseInline(e)}parse(e,t=!0){let n="";for(let s=0;s<e.length;s++){let i=e[s];if(this.options.extensions?.renderers?.[i.type]){let o=i,a=this.options.extensions.renderers[o.type].call({parser:this},o);if(a!==!1||!["space","hr","heading","code","table","blockquote","list","html","paragraph","text"].includes(o.type)){n+=a||"";continue}}let r=i;switch(r.type){case"space":{n+=this.renderer.space(r);continue}case"hr":{n+=this.renderer.hr(r);continue}case"heading":{n+=this.renderer.heading(r);continue}case"code":{n+=this.renderer.code(r);continue}case"table":{n+=this.renderer.table(r);continue}case"blockquote":{n+=this.renderer.blockquote(r);continue}case"list":{n+=this.renderer.list(r);continue}case"html":{n+=this.renderer.html(r);continue}case"paragraph":{n+=this.renderer.paragraph(r);continue}case"text":{let o=r,a=this.renderer.text(o);for(;s+1<e.length&&e[s+1].type==="text";)o=e[++s],a+=`
`+this.renderer.text(o);t?n+=this.renderer.paragraph({type:"paragraph",raw:a,text:a,tokens:[{type:"text",raw:a,text:a,escaped:!0}]}):n+=a;continue}default:{let o='Token with "'+r.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}parseInline(e,t=this.renderer){let n="";for(let s=0;s<e.length;s++){let i=e[s];if(this.options.extensions?.renderers?.[i.type]){let o=this.options.extensions.renderers[i.type].call({parser:this},i);if(o!==!1||!["escape","html","link","image","strong","em","codespan","br","del","text"].includes(i.type)){n+=o||"";continue}}let r=i;switch(r.type){case"escape":{n+=t.text(r);break}case"html":{n+=t.html(r);break}case"link":{n+=t.link(r);break}case"image":{n+=t.image(r);break}case"strong":{n+=t.strong(r);break}case"em":{n+=t.em(r);break}case"codespan":{n+=t.codespan(r);break}case"br":{n+=t.br(r);break}case"del":{n+=t.del(r);break}case"text":{n+=t.text(r);break}default:{let o='Token with "'+r.type+'" type was not found.';if(this.options.silent)return console.error(o),"";throw new Error(o)}}}return n}};var L=class{options;block;constructor(e){this.options=e||w}static passThroughHooks=new Set(["preprocess","postprocess","processAllTokens"]);preprocess(e){return e}postprocess(e){return e}processAllTokens(e){return e}provideLexer(){return this.block?x.lex:x.lexInline}provideParser(){return this.block?b.parse:b.parseInline}};var E=class{defaults=z();options=this.setOptions;parse=this.parseMarkdown(!0);parseInline=this.parseMarkdown(!1);Parser=b;Renderer=$;TextRenderer=_;Lexer=x;Tokenizer=S;Hooks=L;constructor(...e){this.use(...e)}walkTokens(e,t){let n=[];for(let s of e)switch(n=n.concat(t.call(this,s)),s.type){case"table":{let i=s;for(let r of i.header)n=n.concat(this.walkTokens(r.tokens,t));for(let r of i.rows)for(let o of r)n=n.concat(this.walkTokens(o.tokens,t));break}case"list":{let i=s;n=n.concat(this.walkTokens(i.items,t));break}default:{let i=s;this.defaults.extensions?.childTokens?.[i.type]?this.defaults.extensions.childTokens[i.type].forEach(r=>{let o=i[r].flat(1/0);n=n.concat(this.walkTokens(o,t))}):i.tokens&&(n=n.concat(this.walkTokens(i.tokens,t)))}}return n}use(...e){let t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach(n=>{let s={...n};if(s.async=this.defaults.async||s.async||!1,n.extensions&&(n.extensions.forEach(i=>{if(!i.name)throw new Error("extension name required");if("renderer"in i){let r=t.renderers[i.name];r?t.renderers[i.name]=function(...o){let a=i.renderer.apply(this,o);return a===!1&&(a=r.apply(this,o)),a}:t.renderers[i.name]=i.renderer}if("tokenizer"in i){if(!i.level||i.level!=="block"&&i.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let r=t[i.level];r?r.unshift(i.tokenizer):t[i.level]=[i.tokenizer],i.start&&(i.level==="block"?t.startBlock?t.startBlock.push(i.start):t.startBlock=[i.start]:i.level==="inline"&&(t.startInline?t.startInline.push(i.start):t.startInline=[i.start]))}"childTokens"in i&&i.childTokens&&(t.childTokens[i.name]=i.childTokens)}),s.extensions=t),n.renderer){let i=this.defaults.renderer||new $(this.defaults);for(let r in n.renderer){if(!(r in i))throw new Error(`renderer '${r}' does not exist`);if(["options","parser"].includes(r))continue;let o=r,a=n.renderer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u||""}}s.renderer=i}if(n.tokenizer){let i=this.defaults.tokenizer||new S(this.defaults);for(let r in n.tokenizer){if(!(r in i))throw new Error(`tokenizer '${r}' does not exist`);if(["options","rules","lexer"].includes(r))continue;let o=r,a=n.tokenizer[o],c=i[o];i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.tokenizer=i}if(n.hooks){let i=this.defaults.hooks||new L;for(let r in n.hooks){if(!(r in i))throw new Error(`hook '${r}' does not exist`);if(["options","block"].includes(r))continue;let o=r,a=n.hooks[o],c=i[o];L.passThroughHooks.has(r)?i[o]=p=>{if(this.defaults.async)return Promise.resolve(a.call(i,p)).then(d=>c.call(i,d));let u=a.call(i,p);return c.call(i,u)}:i[o]=(...p)=>{let u=a.apply(i,p);return u===!1&&(u=c.apply(i,p)),u}}s.hooks=i}if(n.walkTokens){let i=this.defaults.walkTokens,r=n.walkTokens;s.walkTokens=function(o){let a=[];return a.push(r.call(this,o)),i&&(a=a.concat(i.call(this,o))),a}}this.defaults={...this.defaults,...s}}),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return x.lex(e,t??this.defaults)}parser(e,t){return b.parse(e,t??this.defaults)}parseMarkdown(e){return(n,s)=>{let i={...s},r={...this.defaults,...i},o=this.onError(!!r.silent,!!r.async);if(this.defaults.async===!0&&i.async===!1)return o(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof n>"u"||n===null)return o(new Error("marked(): input parameter is undefined or null"));if(typeof n!="string")return o(new Error("marked(): input parameter is of type "+Object.prototype. Line truncated
Please report this to https://github.com/markedjs/marked.`,e){let s="<p>An error occurred:</p><pre>"+R(n.message+"",!0)+"</pre>";return t?Promise.resolve(s):s}if(t)return Promise.reject(n);throw n}}};var M=new E;function k(l,e){return M.parse(l,e)}k.options=k.setOptions=function(l){return M.setOptions(l),k.defaults=M.defaults,N(k.defaults),k};k.getDefaults=z;k.defaults=w;k.use=function(...l){return M.use(...l),k.defaults=M.defaults,N(k.defaults),k};k.walkTokens=function(l,e){return M.walkTokens(l,e)};k.parseInline=M.parseInline;k.Parser=b;k.parser=b.parse;k.Renderer=$;k.TextRenderer=_;k.Lexer=x;k.lexer=x.lex;k.Tokenizer=S;k.Hooks=L;k.parse=k;var it=k.options,ot=k.setOptions,lt=k.use,at=k.walkTokens,ct=k.parseInline,pt=k,ut=b.parse,ht=x.lex;
if(__exports != exports)module.exports = exports;return module.exports}));
+3
View File
@@ -0,0 +1,3 @@
/*! @license DOMPurify 3.1.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.6/LICENSE */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).DOMPurify=t()}(this,(function(){"use strict";const{entries:e,setPrototypeOf:t,isFrozen:n,getPrototypeOf:o,getOwnPropertyDescriptor:r}=Object;let{freeze:i,seal:a,create:l}=Object,{apply:c,construct:s}="undefined"!=typeof Reflect&&Reflect;i||(i=function(e){return e}),a||(a=function(e){return e}),c||(c=function(e,t,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=b(Array.prototype.forEach),m=b(Array.prototype.pop),p=b(Array.prototype.push),f=b(String.prototype.toLowerCase),d=b(String.prototype.toString),h=b(String.prototype.match),g=b(String.prototype.replace),T=b(String.prototype.indexOf),y=b(String.prototype.trim),E=b(Object.prototype.hasOwnProperty),_=b(RegExp.prototype.test),A=(N=TypeError,function(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return s(N,t)});var N;function b(e){return function(t){for(var n=arguments.length,o=new Array(n>1?n-1:0),r=1;r<n;r++)o[r-1]=arguments[r];return c(e,t,o)}}function S(e,o){let r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:f;t&&t(e,null);let i=o.length;for(;i--;){let t=o[i];if("string"==typeof t){const e=r(t);e!==t&&(n(o)||(o[i]=e),t=e)}e[t]=!0}return e}function R(e){for(let t=0;t<e.length;t++){E(e,t)||(e[t]=null)}return e}function w(t){const n=l(null);for(const[o,r]of e(t)){E(t,o)&&(Array.isArray(r)?n[o]=R(r):r&&"object"==typeof r&&r.constructor===Object?n[o]=w(r):n[o]=r)}return n}function C(e,t){for(;null!==e;){const n=r(e,t);if(n){if(n.get)return b(n.get);if("function"==typeof n.value)return b(n.value)}e=o(e)}return function(){return null}}const L=i(["a","abbr","acronym","address","area","article","aside","audio","b","bdi","bdo","big","blink","blockquote","body","br","button","canvas","caption","center","cite","code","col","colgroup","content","data","datalist","dd","decorator","del","details","dfn","dialog","dir","div","dl","dt","element","em","fieldset","figcaption","figure","font","footer","form","h1","h2","h3","h4","h5","h6","head","header","hgroup","hr","html","i","img","input","ins","kbd","label","legend","li","main","map","mark","marquee","menu","menuitem","meter","nav","nobr","ol","optgroup","option","output","p","picture","pre","progress","q","rp","rt","ruby","s","samp","section","select","shadow","small","source","spacer","span","strike","strong","style","sub","summary","sup","table","tbody","td","template","textarea","tfoot","th","thead","time","tr","track","tt","u","ul","var","video","wbr"]),D=i(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),v=i(["feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence"]),O=i(["animate","color-profile","cursor","discard","font-face","font-face-format","font-face-name","font-face-src","font-face-uri","foreignobject","hatch","hatchpath","mesh","meshgradient","meshpatch","meshrow","missing-glyph","script","set","solidcolor","unknown","use"]),x=i(["math","menclose","merror","mfenced","mfrac","mglyph","mi","mlabeledtr","mmultiscripts","mn","mo","mover","mpadded","mphantom","mroot","mrow","ms","mspace","msqrt","mstyle","msub","msup","msubsup","mtable","mtd","mtext","mtr","munder","munderover","mprescripts"]),k=i(["maction","maligngroup","malignmark","mlongdiv","mscarries","mscarry","msgroup","mstack","msline","msrow","semantics","annotation","annotation-xml","mprescripts","none"]),M=i(["#text"]),I=i(["accept","action","align","alt","autocapitalize","autocomplete","autopictureinpicture","autoplay","background","bgcolor","border","capture","cellpadding","cellspacing","checked","cite","class","clear","color","cols","colspan","controls","controlslist","coords","crossorigin","datetime","decoding","default","dir","disabled","disablepictureinpicture","disableremoteplayback","download","draggable","enctype","enterkeyhint","face","for","headers","height","hidden","high","href","hreflang","id","inputmode","integrity","ismap","kind","label","lang","list","loading","loop","low","max","maxlength","media","method","min","minlength","multiple","muted","name","nonce","noshade","novalidate","nowrap","open","optimum","pattern","placeholder","playsinline","popover","popovertarget","popovertargetaction","poster","preload","pubdate","radiogroup","readLine truncated
//# sourceMappingURL=purify.min.js.map
+49 -4
View File
@@ -357,7 +357,7 @@ def _inject_sub_tool_calls(
tool_calls: 工具调用列表(原地修改)。
session_id: 会话 ID。
turn_id: 轮次 ID。
lookup_fn: (session_id, turn_id, agent_name) -> list[dict] 的查询函数。
lookup_fn: (session_id, turn_id, agent_name, tool_call_id) -> list[dict] 的查询函数。
"""
for tc in tool_calls:
if tc.get("name") != "call_subagent" and tc.get("name") != "agent_call":
@@ -374,7 +374,9 @@ def _inject_sub_tool_calls(
args = {}
sub_name = args.get("agent_name", "")
if sub_name:
sub_tc = lookup_fn(session_id, turn_id, sub_name)
# 优先按条目携带的 tool_call_id 精确匹配(并行/同名子 Agent 各自独立),
# lookup_fn 内部对空 tool_call_id 回退到 agent_name 级(旧数据兼容)。
sub_tc = lookup_fn(session_id, turn_id, sub_name, tc.get("id", ""))
if sub_tc:
tc["sub_tool_calls"] = sub_tc
@@ -406,7 +408,22 @@ def _bootstrap() -> tuple[str, list[str], list[dict[str, Any]]]:
@asynccontextmanager
async def lifespan(app):
"""应用生命周期:启动定时任务调度器,退出时关闭。"""
"""应用生命周期:预热重依赖 + 启动定时任务调度器,退出时关闭。
预热目的:
agent.tools(含 chromadb 等重依赖)与 Agent 运行时原先在首次
工具调用/首次打开设置页时惰性导入,首次请求要现场付数秒导入成本。
这里在启动阶段统一预热(成本被 Electron 健康检查吸收),
使设置页与首次聊天即时响应。
"""
try:
# 预热:构建全部 Agent 运行时(顺带导入 agent.tools/chromadb 等重依赖)
from agent.agents import agent_runtime # noqa: F401
# 预热工具参数缓存(实例化全部工具类读取 args_schema)
from agent.core.config_manager import _get_tool_parameters
_get_tool_parameters()
except Exception as e:
print(f"[lifespan] 预热 Agent 运行时失败: {e}")
try:
from agent.cron.scheduler import get_scheduler
get_scheduler().start()
@@ -2575,4 +2592,32 @@ def serve_js_module(filename: str) -> FileResponse:
@app.get("/css/{filename:path}")
def serve_css_module(filename: str) -> FileResponse:
"""返回前端 CSS 模块文件。"""
return FileResponse(_WEB_DIR / "css" / filename)
return FileResponse(_WEB_DIR / "css" / filename)
@app.get("/fonts/{filename}")
def serve_font(filename: str) -> FileResponse:
"""返回本地字体文件(woff2,本地化后不再依赖 Google Fonts CDN)。
带路径包含校验,防止目录穿越。
"""
fonts_root = (_WEB_DIR / "fonts").resolve()
font_path = (fonts_root / filename).resolve()
if not str(font_path).startswith(str(fonts_root) + os.sep) or not font_path.is_file():
raise HTTPException(status_code=404, detail="字体文件不存在")
mime, _ = mimetypes.guess_type(str(font_path))
return FileResponse(font_path, media_type=mime or "font/woff2")
@app.get("/packages/{filename:path}")
def serve_packages(filename: str) -> FileResponse:
"""返回本地 vendor 库与 node_modules(marked/dompurify 本地化 + Monaco 离线兜底)。
带路径包含校验,防止目录穿越。
"""
pkg_root = (_WEB_DIR / "packages").resolve()
target = (pkg_root / filename).resolve()
if not str(target).startswith(str(pkg_root) + os.sep) or not target.is_file():
raise HTTPException(status_code=404, detail="文件不存在")
mime, _ = mimetypes.guess_type(str(target))
return FileResponse(target, media_type=mime or "application/octet-stream")