SYMBOL INDEX (1417 symbols across 100 files) FILE: bridge/src/index.ts constant PORT (line 26) | const PORT = parseInt(process.env.BRIDGE_PORT || '3001', 10); constant AUTH_DIR (line 27) | const AUTH_DIR = process.env.AUTH_DIR || join(homedir(), '.nanobot', 'wh... constant TOKEN (line 28) | const TOKEN = process.env.BRIDGE_TOKEN || undefined; FILE: bridge/src/server.ts type SendCommand (line 9) | interface SendCommand { type BridgeMessage (line 15) | interface BridgeMessage { class BridgeServer (line 20) | class BridgeServer { method constructor (line 25) | constructor(private port: number, private authDir: string, private tok... method start (line 27) | async start(): Promise { method setupClient (line 70) | private setupClient(ws: WebSocket): void { method handleCommand (line 95) | private async handleCommand(cmd: SendCommand): Promise { method broadcast (line 101) | private broadcast(msg: BridgeMessage): void { method stop (line 110) | async stop(): Promise { FILE: bridge/src/whatsapp.ts constant VERSION (line 23) | const VERSION = '0.1.0'; type InboundMessage (line 25) | interface InboundMessage { type WhatsAppClientOptions (line 35) | interface WhatsAppClientOptions { class WhatsAppClient (line 42) | class WhatsAppClient { method constructor (line 47) | constructor(options: WhatsAppClientOptions) { method connect (line 51) | async connect(): Promise { method downloadMedia (line 162) | private async downloadMedia(msg: any, mimetype?: string, fileName?: st... method getTextContent (line 191) | private getTextContent(message: any): string | null { method sendMessage (line 225) | async sendMessage(to: string, text: string): Promise { method disconnect (line 233) | async disconnect(): Promise { FILE: nanobot/agent/context.py class ContextBuilder (line 16) | class ContextBuilder: method __init__ (line 22) | def __init__(self, workspace: Path): method build_system_prompt (line 27) | def build_system_prompt(self, skill_names: list[str] | None = None) ->... method _get_identity (line 56) | def _get_identity(self) -> str: method _build_runtime_context (line 101) | def _build_runtime_context(channel: str | None, chat_id: str | None) -... method _load_bootstrap_files (line 108) | def _load_bootstrap_files(self) -> str: method build_messages (line 120) | def build_messages( method _build_user_content (line 147) | def _build_user_content(self, text: str, media: list[str] | None) -> s... method add_tool_result (line 173) | def add_tool_result( method add_assistant_message (line 181) | def add_assistant_message( FILE: nanobot/agent/loop.py class AgentLoop (line 37) | class AgentLoop: method __init__ (line 51) | def __init__( method _register_default_tools (line 116) | def _register_default_tools(self) -> None: method _connect_mcp (line 136) | async def _connect_mcp(self) -> None: method _set_tool_context (line 158) | def _set_tool_context(self, channel: str, chat_id: str, message_id: st... method _strip_think (line 166) | def _strip_think(text: str | None) -> str | None: method _tool_hint (line 173) | def _tool_hint(tool_calls: list) -> str: method _run_agent_loop (line 183) | async def _run_agent_loop( method run (line 256) | async def run(self) -> None: method _handle_stop (line 281) | async def _handle_stop(self, msg: InboundMessage) -> None: method _handle_restart (line 297) | async def _handle_restart(self, msg: InboundMessage) -> None: method _dispatch (line 311) | async def _dispatch(self, msg: InboundMessage) -> None: method close_mcp (line 333) | async def close_mcp(self) -> None: method _schedule_background (line 345) | def _schedule_background(self, coro) -> None: method stop (line 351) | def stop(self) -> None: method _process_message (line 356) | async def _process_message( method _save_turn (line 461) | def _save_turn(self, session: Session, messages: list[dict], skip: int... method process_direct (line 498) | async def process_direct( FILE: nanobot/agent/memory.py function _ensure_text (line 48) | def _ensure_text(value: Any) -> str: function _normalize_save_memory_args (line 53) | def _normalize_save_memory_args(args: Any) -> dict[str, Any] | None: function _is_tool_choice_unsupported (line 69) | def _is_tool_choice_unsupported(content: str | None) -> bool: class MemoryStore (line 75) | class MemoryStore: method __init__ (line 80) | def __init__(self, workspace: Path): method read_long_term (line 86) | def read_long_term(self) -> str: method write_long_term (line 91) | def write_long_term(self, content: str) -> None: method append_history (line 94) | def append_history(self, entry: str) -> None: method get_memory_context (line 98) | def get_memory_context(self) -> str: method _format_messages (line 103) | def _format_messages(messages: list[dict]) -> str: method consolidate (line 114) | async def consolidate( method _fail_or_raw_archive (line 201) | def _fail_or_raw_archive(self, messages: list[dict]) -> bool: method _raw_archive (line 210) | def _raw_archive(self, messages: list[dict]) -> None: class MemoryConsolidator (line 222) | class MemoryConsolidator: method __init__ (line 227) | def __init__( method get_lock (line 246) | def get_lock(self, session_key: str) -> asyncio.Lock: method consolidate_messages (line 250) | async def consolidate_messages(self, messages: list[dict[str, object]]... method pick_consolidation_boundary (line 254) | def pick_consolidation_boundary( method estimate_session_prompt_tokens (line 276) | def estimate_session_prompt_tokens(self, session: Session) -> tuple[in... method archive_messages (line 293) | async def archive_messages(self, messages: list[dict[str, object]]) ->... method maybe_consolidate_by_tokens (line 302) | async def maybe_consolidate_by_tokens(self, session: Session) -> None: FILE: nanobot/agent/skills.py class SkillsLoader (line 13) | class SkillsLoader: method __init__ (line 21) | def __init__(self, workspace: Path, builtin_skills_dir: Path | None = ... method list_skills (line 26) | def list_skills(self, filter_unavailable: bool = True) -> list[dict[st... method load_skill (line 59) | def load_skill(self, name: str) -> str | None: method load_skills_for_context (line 82) | def load_skills_for_context(self, skill_names: list[str]) -> str: method build_skills_summary (line 101) | def build_skills_summary(self) -> str: method _get_missing_requirements (line 142) | def _get_missing_requirements(self, skill_meta: dict) -> str: method _get_skill_description (line 154) | def _get_skill_description(self, name: str) -> str: method _strip_frontmatter (line 161) | def _strip_frontmatter(self, content: str) -> str: method _parse_nanobot_metadata (line 169) | def _parse_nanobot_metadata(self, raw: str) -> dict: method _check_requirements (line 177) | def _check_requirements(self, skill_meta: dict) -> bool: method _get_skill_meta (line 188) | def _get_skill_meta(self, name: str) -> dict: method get_always_skills (line 193) | def get_always_skills(self) -> list[str]: method get_skill_metadata (line 203) | def get_skill_metadata(self, name: str) -> dict | None: FILE: nanobot/agent/subagent.py class SubagentManager (line 23) | class SubagentManager: method __init__ (line 26) | def __init__( method spawn (line 50) | async def spawn( method _run_subagent (line 82) | async def _run_subagent( method _announce_result (line 168) | async def _announce_result( method _build_subagent_prompt (line 200) | def _build_subagent_prompt(self) -> str: method cancel_by_session (line 223) | async def cancel_by_session(self, session_key: str) -> int: method get_running_count (line 233) | def get_running_count(self) -> int: FILE: nanobot/agent/tools/base.py class Tool (line 7) | class Tool(ABC): method _resolve_type (line 25) | def _resolve_type(t: Any) -> str | None: method name (line 40) | def name(self) -> str: method description (line 46) | def description(self) -> str: method parameters (line 52) | def parameters(self) -> dict[str, Any]: method execute (line 57) | async def execute(self, **kwargs: Any) -> str: method cast_params (line 69) | def cast_params(self, params: dict[str, Any]) -> dict[str, Any]: method _cast_object (line 77) | def _cast_object(self, obj: Any, schema: dict[str, Any]) -> dict[str, ... method _cast_value (line 93) | def _cast_value(self, val: Any, schema: dict[str, Any]) -> Any: method validate_params (line 138) | def validate_params(self, params: dict[str, Any]) -> list[str]: method _validate (line 147) | def _validate(self, val: Any, schema: dict[str, Any], path: str) -> li... method to_schema (line 190) | def to_schema(self) -> dict[str, Any]: FILE: nanobot/agent/tools/cron.py class CronTool (line 12) | class CronTool(Tool): method __init__ (line 15) | def __init__(self, cron_service: CronService): method set_context (line 21) | def set_context(self, channel: str, chat_id: str) -> None: method set_cron_context (line 26) | def set_cron_context(self, active: bool): method reset_cron_context (line 30) | def reset_cron_context(self, token) -> None: method name (line 35) | def name(self) -> str: method description (line 39) | def description(self) -> str: method parameters (line 43) | def parameters(self) -> dict[str, Any]: method execute (line 74) | async def execute( method _add_job (line 95) | def _add_job( method _format_timing (line 148) | def _format_timing(schedule: CronSchedule) -> str: method _format_state (line 168) | def _format_state(state: CronJobState) -> list[str]: method _list_jobs (line 182) | def _list_jobs(self) -> str: method _remove_job (line 194) | def _remove_job(self, job_id: str | None) -> str: FILE: nanobot/agent/tools/filesystem.py function _resolve_path (line 10) | def _resolve_path( function _is_under (line 28) | def _is_under(path: Path, directory: Path) -> bool: class _FsTool (line 36) | class _FsTool(Tool): method __init__ (line 39) | def __init__( method _resolve (line 49) | def _resolve(self, path: str) -> Path: class ReadFileTool (line 57) | class ReadFileTool(_FsTool): method name (line 64) | def name(self) -> str: method description (line 68) | def description(self) -> str: method parameters (line 75) | def parameters(self) -> dict[str, Any]: method execute (line 94) | async def execute(self, path: str, offset: int = 1, limit: int | None ... class WriteFileTool (line 142) | class WriteFileTool(_FsTool): method name (line 146) | def name(self) -> str: method description (line 150) | def description(self) -> str: method parameters (line 154) | def parameters(self) -> dict[str, Any]: method execute (line 164) | async def execute(self, path: str, content: str, **kwargs: Any) -> str: function _find_match (line 180) | def _find_match(content: str, old_text: str) -> tuple[str | None, int]: class EditFileTool (line 206) | class EditFileTool(_FsTool): method name (line 210) | def name(self) -> str: method description (line 214) | def description(self) -> str: method parameters (line 222) | def parameters(self) -> dict[str, Any]: method execute (line 237) | async def execute( method _not_found_msg (line 272) | def _not_found_msg(old_text: str, content: str, path: str) -> str: class ListDirTool (line 298) | class ListDirTool(_FsTool): method name (line 309) | def name(self) -> str: method description (line 313) | def description(self) -> str: method parameters (line 321) | def parameters(self) -> dict[str, Any]: method execute (line 339) | async def execute( FILE: nanobot/agent/tools/mcp.py class MCPToolWrapper (line 14) | class MCPToolWrapper(Tool): method __init__ (line 17) | def __init__(self, session, server_name: str, tool_def, tool_timeout: ... method name (line 26) | def name(self) -> str: method description (line 30) | def description(self) -> str: method parameters (line 34) | def parameters(self) -> dict[str, Any]: method execute (line 37) | async def execute(self, **kwargs: Any) -> str: function connect_mcp_servers (line 74) | async def connect_mcp_servers( FILE: nanobot/agent/tools/message.py class MessageTool (line 9) | class MessageTool(Tool): method __init__ (line 12) | def __init__( method set_context (line 25) | def set_context(self, channel: str, chat_id: str, message_id: str | No... method set_send_callback (line 31) | def set_send_callback(self, callback: Callable[[OutboundMessage], Awai... method start_turn (line 35) | def start_turn(self) -> None: method name (line 40) | def name(self) -> str: method description (line 44) | def description(self) -> str: method parameters (line 48) | def parameters(self) -> dict[str, Any]: method execute (line 73) | async def execute( FILE: nanobot/agent/tools/registry.py class ToolRegistry (line 8) | class ToolRegistry: method __init__ (line 15) | def __init__(self): method register (line 18) | def register(self, tool: Tool) -> None: method unregister (line 22) | def unregister(self, name: str) -> None: method get (line 26) | def get(self, name: str) -> Tool | None: method has (line 30) | def has(self, name: str) -> bool: method get_definitions (line 34) | def get_definitions(self) -> list[dict[str, Any]]: method execute (line 38) | async def execute(self, name: str, params: dict[str, Any]) -> str: method tool_names (line 62) | def tool_names(self) -> list[str]: method __len__ (line 66) | def __len__(self) -> int: method __contains__ (line 69) | def __contains__(self, name: str) -> bool: FILE: nanobot/agent/tools/shell.py class ExecTool (line 12) | class ExecTool(Tool): method __init__ (line 15) | def __init__( method name (line 42) | def name(self) -> str: method description (line 49) | def description(self) -> str: method parameters (line 53) | def parameters(self) -> dict[str, Any]: method execute (line 78) | async def execute( method _guard_command (line 144) | def _guard_command(self, command: str, cwd: str) -> str | None: method _extract_absolute_paths (line 179) | def _extract_absolute_paths(command: str) -> list[str]: FILE: nanobot/agent/tools/spawn.py class SpawnTool (line 11) | class SpawnTool(Tool): method __init__ (line 14) | def __init__(self, manager: "SubagentManager"): method set_context (line 20) | def set_context(self, channel: str, chat_id: str) -> None: method name (line 27) | def name(self) -> str: method description (line 31) | def description(self) -> str: method parameters (line 41) | def parameters(self) -> dict[str, Any]: method execute (line 57) | async def execute(self, task: str, label: str | None = None, **kwargs:... FILE: nanobot/agent/tools/web.py function _strip_tags (line 27) | def _strip_tags(text: str) -> str: function _normalize (line 35) | def _normalize(text: str) -> str: function _validate_url (line 41) | def _validate_url(url: str) -> tuple[bool, str]: function _validate_url_safe (line 54) | def _validate_url_safe(url: str) -> tuple[bool, str]: function _format_results (line 60) | def _format_results(query: str, items: list[dict[str, Any]], n: int) -> ... class WebSearchTool (line 74) | class WebSearchTool(Tool): method __init__ (line 88) | def __init__(self, config: WebSearchConfig | None = None, proxy: str |... method execute (line 94) | async def execute(self, query: str, count: int | None = None, **kwargs... method _search_brave (line 111) | async def _search_brave(self, query: str, n: int) -> str: method _search_tavily (line 133) | async def _search_tavily(self, query: str, n: int) -> str: method _search_searxng (line 151) | async def _search_searxng(self, query: str, n: int) -> str: method _search_jina (line 173) | async def _search_jina(self, query: str, n: int) -> str: method _search_duckduckgo (line 197) | async def _search_duckduckgo(self, query: str, n: int) -> str: class WebFetchTool (line 215) | class WebFetchTool(Tool): method __init__ (line 230) | def __init__(self, max_chars: int = 50000, proxy: str | None = None): method execute (line 234) | async def execute(self, url: str, extractMode: str = "markdown", maxCh... method _fetch_jina (line 245) | async def _fetch_jina(self, url: str, max_chars: int) -> str | None: method _fetch_readability (line 281) | async def _fetch_readability(self, url: str, extract_mode: str, max_ch... method _to_markdown (line 329) | def _to_markdown(self, html_content: str) -> str: FILE: nanobot/bus/events.py class InboundMessage (line 9) | class InboundMessage: method session_key (line 22) | def session_key(self) -> str: class OutboundMessage (line 28) | class OutboundMessage: FILE: nanobot/bus/queue.py class MessageBus (line 8) | class MessageBus: method __init__ (line 16) | def __init__(self): method publish_inbound (line 20) | async def publish_inbound(self, msg: InboundMessage) -> None: method consume_inbound (line 24) | async def consume_inbound(self) -> InboundMessage: method publish_outbound (line 28) | async def publish_outbound(self, msg: OutboundMessage) -> None: method consume_outbound (line 32) | async def consume_outbound(self) -> OutboundMessage: method inbound_size (line 37) | def inbound_size(self) -> int: method outbound_size (line 42) | def outbound_size(self) -> int: FILE: nanobot/channels/base.py class BaseChannel (line 15) | class BaseChannel(ABC): method __init__ (line 27) | def __init__(self, config: Any, bus: MessageBus): method transcribe_audio (line 39) | async def transcribe_audio(self, file_path: str | Path) -> str: method start (line 53) | async def start(self) -> None: method stop (line 65) | async def stop(self) -> None: method send (line 70) | async def send(self, msg: OutboundMessage) -> None: method is_allowed (line 79) | def is_allowed(self, sender_id: str) -> bool: method _handle_message (line 89) | async def _handle_message( method default_config (line 132) | def default_config(cls) -> dict[str, Any]: method is_running (line 137) | def is_running(self) -> bool: FILE: nanobot/channels/dingtalk.py class NanobotDingTalkHandler (line 41) | class NanobotDingTalkHandler(CallbackHandler): method __init__ (line 47) | def __init__(self, channel: "DingTalkChannel"): method process (line 51) | async def process(self, message: CallbackMessage): class DingTalkConfig (line 149) | class DingTalkConfig(Base): class DingTalkChannel (line 158) | class DingTalkChannel(BaseChannel): method default_config (line 176) | def default_config(cls) -> dict[str, Any]: method __init__ (line 179) | def __init__(self, config: Any, bus: MessageBus): method start (line 194) | async def start(self) -> None: method stop (line 236) | async def stop(self) -> None: method _get_access_token (line 248) | async def _get_access_token(self) -> str | None: method _is_http_url (line 276) | def _is_http_url(value: str) -> bool: method _guess_upload_type (line 279) | def _guess_upload_type(self, media_ref: str) -> str: method _guess_filename (line 286) | def _guess_filename(self, media_ref: str, upload_type: str) -> str: method _read_media_bytes (line 290) | async def _read_media_bytes( method _upload_media (line 332) | async def _upload_media( method _send_batch_message (line 367) | async def _send_batch_message( method _send_markdown_text (line 416) | async def _send_markdown_text(self, token: str, chat_id: str, content:... method _send_media_ref (line 424) | async def _send_media_ref(self, token: str, chat_id: str, media_ref: s... method send (line 483) | async def send(self, msg: OutboundMessage) -> None: method _on_message (line 505) | async def _on_message( method _download_dingtalk_file (line 535) | async def _download_dingtalk_file( FILE: nanobot/channels/discord.py class DiscordConfig (line 25) | class DiscordConfig(Base): class DiscordChannel (line 36) | class DiscordChannel(BaseChannel): method default_config (line 43) | def default_config(cls) -> dict[str, Any]: method __init__ (line 46) | def __init__(self, config: Any, bus: MessageBus): method start (line 58) | async def start(self) -> None: method stop (line 81) | async def stop(self) -> None: method send (line 97) | async def send(self, msg: OutboundMessage) -> None: method _send_payload (line 140) | async def _send_payload( method _send_file (line 162) | async def _send_file( method _gateway_loop (line 210) | async def _gateway_loop(self) -> None: method _identify (line 252) | async def _identify(self) -> None: method _start_heartbeat (line 271) | async def _start_heartbeat(self, interval_s: float) -> None: method _handle_message_create (line 288) | async def _handle_message_create(self, payload: dict[str, Any]) -> None: method _should_respond_in_group (line 351) | def _should_respond_in_group(self, payload: dict[str, Any], content: s... method _start_typing (line 372) | async def _start_typing(self, channel_id: str) -> None: method _stop_typing (line 391) | async def _stop_typing(self, channel_id: str) -> None: FILE: nanobot/channels/email.py class EmailConfig (line 26) | class EmailConfig(Base): class EmailChannel (line 55) | class EmailChannel(BaseChannel): method default_config (line 85) | def default_config(cls) -> dict[str, Any]: method __init__ (line 88) | def __init__(self, config: Any, bus: MessageBus): method start (line 98) | async def start(self) -> None: method stop (line 138) | async def stop(self) -> None: method send (line 142) | async def send(self, msg: OutboundMessage) -> None: method _validate_config (line 190) | def _validate_config(self) -> bool: method _smtp_send (line 210) | def _smtp_send(self, msg: EmailMessage) -> None: method _fetch_new_messages (line 228) | def _fetch_new_messages(self) -> list[dict[str, Any]]: method fetch_messages_between_dates (line 237) | def fetch_messages_between_dates( method _fetch_messages (line 263) | def _fetch_messages( method _format_imap_date (line 362) | def _format_imap_date(cls, value: date) -> str: method _extract_message_bytes (line 368) | def _extract_message_bytes(fetched: list[Any]) -> bytes | None: method _extract_uid (line 375) | def _extract_uid(fetched: list[Any]) -> str: method _decode_header_value (line 385) | def _decode_header_value(value: str) -> str: method _extract_text_body (line 394) | def _extract_text_body(cls, msg: Any) -> str: method _html_to_text (line 434) | def _html_to_text(raw_html: str) -> str: method _reply_subject (line 440) | def _reply_subject(self, base_subject: str) -> str: FILE: nanobot/channels/feishu.py function _extract_share_card_content (line 34) | def _extract_share_card_content(content_json: dict, msg_type: str) -> str: function _extract_interactive_content (line 54) | def _extract_interactive_content(content: dict) -> list[str]: function _extract_element_content (line 95) | def _extract_element_content(element: dict) -> list[str]: function _extract_post_content (line 168) | def _extract_post_content(content_json: dict) -> tuple[str, list[str]]: function _extract_post_text (line 230) | def _extract_post_text(content_json: dict) -> str: class FeishuConfig (line 239) | class FeishuConfig(Base): class FeishuChannel (line 253) | class FeishuChannel(BaseChannel): method default_config (line 269) | def default_config(cls) -> dict[str, Any]: method __init__ (line 272) | def __init__(self, config: Any, bus: MessageBus): method _register_optional_event (line 284) | def _register_optional_event(builder: Any, method_name: str, handler: ... method start (line 289) | async def start(self) -> None: method stop (line 369) | async def stop(self) -> None: method _is_bot_mentioned (line 380) | def _is_bot_mentioned(self, message: Any) -> bool: method _is_group_message_for_bot (line 395) | def _is_group_message_for_bot(self, message: Any) -> bool: method _add_reaction_sync (line 401) | def _add_reaction_sync(self, message_id: str, emoji_type: str) -> None: method _add_reaction (line 422) | async def _add_reaction(self, message_id: str, emoji_type: str = "THUM... method _strip_md_formatting (line 452) | def _strip_md_formatting(cls, text: str) -> str: method _parse_md_table (line 468) | def _parse_md_table(cls, table_text: str) -> dict | None: method _build_card_elements (line 486) | def _build_card_elements(self, content: str) -> list[dict]: method _split_elements_by_table_limit (line 501) | def _split_elements_by_table_limit(elements: list[dict], max_tables: i... method _split_headings (line 528) | def _split_headings(self, content: str) -> list[dict]: method _detect_msg_format (line 597) | def _detect_msg_format(cls, content: str) -> str: method _markdown_to_post (line 635) | def _markdown_to_post(cls, content: str) -> str: method _upload_image_sync (line 686) | def _upload_image_sync(self, file_path: str) -> str | None: method _upload_file_sync (line 710) | def _upload_file_sync(self, file_path: str) -> str | None: method _download_image_sync (line 738) | def _download_image_sync(self, message_id: str, image_key: str) -> tup... method _download_file_sync (line 761) | def _download_file_sync( method _download_and_save_media (line 793) | async def _download_and_save_media( method _get_message_content_sync (line 840) | def _get_message_content_sync(self, message_id: str) -> str | None: method _reply_message_sync (line 884) | def _reply_message_sync(self, parent_message_id: str, msg_type: str, c... method _send_message_sync (line 909) | def _send_message_sync(self, receive_id_type: str, receive_id: str, ms... method send (line 935) | async def send(self, msg: OutboundMessage) -> None: method _on_message_sync (line 1032) | def _on_message_sync(self, data: Any) -> None: method _on_message (line 1040) | async def _on_message(self, data: Any) -> None: method _on_reaction_created (line 1158) | def _on_reaction_created(self, data: Any) -> None: method _on_message_read (line 1162) | def _on_message_read(self, data: Any) -> None: method _on_bot_p2p_chat_entered (line 1166) | def _on_bot_p2p_chat_entered(self, data: Any) -> None: method _format_tool_hint_lines (line 1172) | def _format_tool_hint_lines(tool_hint: str) -> str: method _send_tool_hint_card (line 1217) | async def _send_tool_hint_card(self, receive_id_type: str, receive_id:... FILE: nanobot/channels/manager.py class ChannelManager (line 15) | class ChannelManager: method __init__ (line 25) | def __init__(self, config: Config, bus: MessageBus): method _init_channels (line 33) | def _init_channels(self) -> None: method _validate_allow_from (line 60) | def _validate_allow_from(self) -> None: method _start_channel (line 68) | async def _start_channel(self, name: str, channel: BaseChannel) -> None: method start_all (line 75) | async def start_all(self) -> None: method stop_all (line 93) | async def stop_all(self) -> None: method _dispatch_outbound (line 113) | async def _dispatch_outbound(self) -> None: method get_channel (line 144) | def get_channel(self, name: str) -> BaseChannel | None: method get_status (line 148) | def get_status(self) -> dict[str, Any]: method enabled_channels (line 159) | def enabled_channels(self) -> list[str]: FILE: nanobot/channels/matrix.py function _filter_matrix_html_attribute (line 79) | def _filter_matrix_html_attribute(tag: str, attr: str, value: str) -> st... function _render_markdown_html (line 101) | def _render_markdown_html(text: str) -> str | None: function _build_matrix_text_content (line 117) | def _build_matrix_text_content(text: str) -> dict[str, object]: class _NioLoguruHandler (line 126) | class _NioLoguruHandler(logging.Handler): method emit (line 129) | def emit(self, record: logging.LogRecord) -> None: function _configure_nio_logging_bridge (line 140) | def _configure_nio_logging_bridge() -> None: class MatrixConfig (line 148) | class MatrixConfig(Base): class MatrixChannel (line 165) | class MatrixChannel(BaseChannel): method default_config (line 172) | def default_config(cls) -> dict[str, Any]: method __init__ (line 175) | def __init__( method start (line 196) | async def start(self) -> None: method stop (line 229) | async def stop(self) -> None: method _is_workspace_path_allowed (line 249) | def _is_workspace_path_allowed(self, path: Path) -> bool: method _collect_outbound_media_candidates (line 259) | def _collect_outbound_media_candidates(self, media: list[str]) -> list... method _build_outbound_attachment_content (line 277) | def _build_outbound_attachment_content( method _is_encrypted_room (line 294) | def _is_encrypted_room(self, room_id: str) -> bool: method _send_room_content (line 300) | async def _send_room_content(self, room_id: str, content: dict[str, An... method _resolve_server_upload_limit_bytes (line 309) | async def _resolve_server_upload_limit_bytes(self) -> int | None: method _effective_media_limit_bytes (line 326) | async def _effective_media_limit_bytes(self) -> int: method _upload_and_send_attachment (line 334) | async def _upload_and_send_attachment( method send (line 386) | async def send(self, msg: OutboundMessage) -> None: method _register_event_callbacks (line 417) | def _register_event_callbacks(self) -> None: method _register_response_callbacks (line 422) | def _register_response_callbacks(self) -> None: method _log_response_error (line 427) | def _log_response_error(self, label: str, response: Any) -> None: method _on_sync_error (line 434) | async def _on_sync_error(self, response: SyncError) -> None: method _on_join_error (line 437) | async def _on_join_error(self, response: JoinError) -> None: method _on_send_error (line 440) | async def _on_send_error(self, response: RoomSendError) -> None: method _set_typing (line 443) | async def _set_typing(self, room_id: str, typing: bool) -> None: method _start_typing_keepalive (line 455) | async def _start_typing_keepalive(self, room_id: str) -> None: method _stop_typing_keepalive (line 472) | async def _stop_typing_keepalive(self, room_id: str, *, clear_typing: ... method _sync_loop (line 482) | async def _sync_loop(self) -> None: method _on_room_invite (line 491) | async def _on_room_invite(self, room: MatrixRoom, event: InviteEvent) ... method _is_direct_room (line 495) | def _is_direct_room(self, room: MatrixRoom) -> bool: method _is_bot_mentioned (line 499) | def _is_bot_mentioned(self, event: RoomMessage) -> bool: method _should_process_message (line 512) | def _should_process_message(self, room: MatrixRoom, event: RoomMessage... method _media_dir (line 527) | def _media_dir(self) -> Path: method _event_source_content (line 531) | def _event_source_content(event: RoomMessage) -> dict[str, Any]: method _event_thread_root_id (line 538) | def _event_thread_root_id(self, event: RoomMessage) -> str | None: method _thread_metadata (line 545) | def _thread_metadata(self, event: RoomMessage) -> dict[str, str] | None: method _build_thread_relates_to (line 554) | def _build_thread_relates_to(metadata: dict[str, Any] | None) -> dict[... method _event_attachment_type (line 566) | def _event_attachment_type(self, event: MatrixMediaEvent) -> str: method _is_encrypted_media_event (line 571) | def _is_encrypted_media_event(event: MatrixMediaEvent) -> bool: method _event_declared_size_bytes (line 576) | def _event_declared_size_bytes(self, event: MatrixMediaEvent) -> int |... method _event_mime (line 581) | def _event_mime(self, event: MatrixMediaEvent) -> str | None: method _event_filename (line 588) | def _event_filename(self, event: MatrixMediaEvent, attachment_type: st... method _build_attachment_path (line 595) | def _build_attachment_path(self, event: MatrixMediaEvent, attachment_t... method _download_media_bytes (line 608) | async def _download_media_bytes(self, mxc_url: str) -> bytes | None: method _decrypt_media_bytes (line 629) | def _decrypt_media_bytes(self, event: MatrixMediaEvent, ciphertext: by... method _fetch_media_attachment (line 641) | async def _fetch_media_attachment( method _base_metadata (line 686) | def _base_metadata(self, room: MatrixRoom, event: RoomMessage) -> dict... method _on_message (line 695) | async def _on_message(self, room: MatrixRoom, event: RoomMessageText) ... method _on_media_message (line 708) | async def _on_media_message(self, room: MatrixRoom, event: MatrixMedia... FILE: nanobot/channels/mochat.py class MochatBufferedEntry (line 44) | class MochatBufferedEntry: class DelayState (line 56) | class DelayState: class MochatTarget (line 64) | class MochatTarget: function _safe_dict (line 74) | def _safe_dict(value: Any) -> dict: function _str_field (line 79) | def _str_field(src: dict, *keys: str) -> str: function _make_synthetic_event (line 88) | def _make_synthetic_event( function normalize_mochat_content (line 108) | def normalize_mochat_content(content: Any) -> str: function resolve_mochat_target (line 120) | def resolve_mochat_target(raw: str) -> MochatTarget: function extract_mention_ids (line 139) | def extract_mention_ids(value: Any) -> list[str]: function resolve_was_mentioned (line 157) | def resolve_was_mentioned(payload: dict[str, Any], agent_user_id: str) -... function resolve_require_mention (line 174) | def resolve_require_mention(config: MochatConfig, session_id: str, group... function build_buffered_body (line 183) | def build_buffered_body(entries: list[MochatBufferedEntry], is_group: bo... function parse_timestamp (line 202) | def parse_timestamp(value: Any) -> int | None: class MochatMentionConfig (line 216) | class MochatMentionConfig(Base): class MochatGroupRule (line 222) | class MochatGroupRule(Base): class MochatConfig (line 228) | class MochatConfig(Base): class MochatChannel (line 259) | class MochatChannel(BaseChannel): method default_config (line 266) | def default_config(cls) -> dict[str, Any]: method __init__ (line 269) | def __init__(self, config: Any, bus: MessageBus): method start (line 302) | async def start(self) -> None: method stop (line 322) | async def stop(self) -> None: method send (line 349) | async def send(self, msg: OutboundMessage) -> None: method _seed_targets_from_config (line 380) | def _seed_targets_from_config(self) -> None: method _normalize_id_list (line 390) | def _normalize_id_list(values: list[str]) -> tuple[list[str], bool]: method _start_socket_client (line 396) | async def _start_socket_client(self) -> bool: method _build_notify_handler (line 469) | def _build_notify_handler(self, event_name: str): method _subscribe_all (line 479) | async def _subscribe_all(self) -> bool: method _subscribe_sessions (line 486) | async def _subscribe_sessions(self, session_ids: list[str]) -> bool: method _subscribe_panels (line 515) | async def _subscribe_panels(self, panel_ids: list[str]) -> bool: method _socket_call (line 524) | async def _socket_call(self, event_name: str, payload: dict[str, Any])... method _refresh_loop (line 535) | async def _refresh_loop(self) -> None: method _refresh_targets (line 546) | async def _refresh_targets(self, subscribe_new: bool) -> None: method _refresh_sessions_directory (line 552) | async def _refresh_sessions_directory(self, subscribe_new: bool) -> None: method _refresh_panels (line 586) | async def _refresh_panels(self, subscribe_new: bool) -> None: method _ensure_fallback_workers (line 618) | async def _ensure_fallback_workers(self) -> None: method _stop_fallback_workers (line 631) | async def _stop_fallback_workers(self) -> None: method _session_watch_worker (line 641) | async def _session_watch_worker(self, session_id: str) -> None: method _panel_poll_worker (line 655) | async def _panel_poll_worker(self, panel_id: str) -> None: method _handle_watch_payload (line 684) | async def _handle_watch_payload(self, payload: dict[str, Any], target_... method _process_inbound_event (line 714) | async def _process_inbound_event(self, target_id: str, event: dict[str... method _remember_message_id (line 762) | def _remember_message_id(self, key: str, message_id: str) -> bool: method _enqueue_delayed_entry (line 773) | async def _enqueue_delayed_entry(self, key: str, target_id: str, targe... method _delay_flush_after (line 781) | async def _delay_flush_after(self, key: str, target_id: str, target_ki... method _flush_delayed_entries (line 785) | async def _flush_delayed_entries(self, key: str, target_id: str, targe... method _dispatch_entries (line 799) | async def _dispatch_entries(self, target_id: str, target_kind: str, en... method _cancel_delay_timers (line 816) | async def _cancel_delay_timers(self) -> None: method _handle_notify_chat_message (line 824) | async def _handle_notify_chat_message(self, payload: Any) -> None: method _handle_notify_inbox_append (line 843) | async def _handle_notify_inbox_append(self, payload: Any) -> None: method _mark_session_cursor (line 873) | def _mark_session_cursor(self, session_id: str, cursor: int) -> None: method _save_cursor_debounced (line 880) | async def _save_cursor_debounced(self) -> None: method _load_session_cursors (line 884) | async def _load_session_cursors(self) -> None: method _save_session_cursors (line 898) | async def _save_session_cursors(self) -> None: method _post_json (line 910) | async def _post_json(self, path: str, payload: dict[str, Any]) -> dict... method _api_send (line 931) | async def _api_send(self, path: str, id_key: str, id_val: str, method _read_group_id (line 942) | def _read_group_id(metadata: dict[str, Any]) -> str | None: FILE: nanobot/channels/qq.py function _make_bot_class (line 30) | def _make_bot_class(channel: "QQChannel") -> "type[botpy.Client]": class QQConfig (line 54) | class QQConfig(Base): class QQChannel (line 64) | class QQChannel(BaseChannel): method default_config (line 71) | def default_config(cls) -> dict[str, Any]: method __init__ (line 74) | def __init__(self, config: Any, bus: MessageBus): method start (line 84) | async def start(self) -> None: method _run_bot (line 100) | async def _run_bot(self) -> None: method stop (line 111) | async def stop(self) -> None: method send (line 121) | async def send(self, msg: OutboundMessage) -> None: method _on_message (line 155) | async def _on_message(self, data: "C2CMessage | GroupMessage", is_grou... FILE: nanobot/channels/registry.py function discover_channel_names (line 17) | def discover_channel_names() -> list[str]: function load_channel_class (line 28) | def load_channel_class(module_name: str) -> type[BaseChannel]: function discover_plugins (line 40) | def discover_plugins() -> dict[str, type[BaseChannel]]: function discover_all (line 54) | def discover_all() -> dict[str, type[BaseChannel]]: FILE: nanobot/channels/slack.py class SlackDMConfig (line 22) | class SlackDMConfig(Base): class SlackConfig (line 30) | class SlackConfig(Base): class SlackChannel (line 48) | class SlackChannel(BaseChannel): method default_config (line 55) | def default_config(cls) -> dict[str, Any]: method __init__ (line 58) | def __init__(self, config: Any, bus: MessageBus): method start (line 67) | async def start(self) -> None: method stop (line 100) | async def stop(self) -> None: method send (line 110) | async def send(self, msg: OutboundMessage) -> None: method _on_socket_request (line 149) | async def _on_socket_request( method _update_react_emoji (line 243) | async def _update_react_emoji(self, chat_id: str, ts: str | None) -> N... method _is_allowed (line 265) | def _is_allowed(self, sender_id: str, chat_id: str, channel_type: str)... method _should_respond_in_channel (line 278) | def _should_respond_in_channel(self, event_type: str, text: str, chat_... method _strip_bot_mention (line 289) | def _strip_bot_mention(self, text: str) -> str: method _to_mrkdwn (line 302) | def _to_mrkdwn(cls, text: str) -> str: method _fixup_mrkdwn (line 310) | def _fixup_mrkdwn(cls, text: str) -> str: method _convert_table (line 329) | def _convert_table(match: re.Match) -> str: FILE: nanobot/channels/telegram.py function _strip_md (line 30) | def _strip_md(s: str) -> str: function _render_table_box (line 39) | def _render_table_box(table_lines: list[str]) -> str: function _markdown_to_telegram_html (line 71) | def _markdown_to_telegram_html(text: str) -> str: class TelegramConfig (line 159) | class TelegramConfig(Base): class TelegramChannel (line 172) | class TelegramChannel(BaseChannel): method default_config (line 192) | def default_config(cls) -> dict[str, Any]: method __init__ (line 195) | def __init__(self, config: Any, bus: MessageBus): method is_allowed (line 209) | def is_allowed(self, sender_id: str) -> bool: method start (line 228) | async def start(self) -> None: method stop (line 306) | async def stop(self) -> None: method _get_media_type (line 327) | def _get_media_type(path: str) -> str: method _is_remote_media_url (line 339) | def _is_remote_media_url(path: str) -> bool: method send (line 342) | async def send(self, msg: OutboundMessage) -> None: method _call_with_retry (line 426) | async def _call_with_retry(self, fn, *args, **kwargs): method _send_text (line 441) | async def _send_text( method _send_with_streaming (line 470) | async def _send_with_streaming( method _on_start (line 494) | async def _on_start(self, update: Update, context: ContextTypes.DEFAUL... method _on_help (line 506) | async def _on_help(self, update: Update, context: ContextTypes.DEFAULT... method _sender_id (line 519) | def _sender_id(user) -> str: method _derive_topic_session_key (line 525) | def _derive_topic_session_key(message) -> str | None: method _build_message_metadata (line 533) | def _build_message_metadata(message, user) -> dict: method _extract_reply_context (line 548) | def _extract_reply_context(message) -> str | None: method _download_message_media (line 558) | async def _download_message_media( method _ensure_bot_identity (line 612) | async def _ensure_bot_identity(self) -> tuple[int | None, str | None]: method _has_mention_entity (line 624) | def _has_mention_entity( method _is_group_message_for_bot (line 649) | async def _is_group_message_for_bot(self, message) -> bool: method _remember_thread_context (line 676) | def _remember_thread_context(self, message) -> None: method _forward_command (line 686) | async def _forward_command(self, update: Update, context: ContextTypes... method _on_message (line 701) | async def _on_message(self, update: Update, context: ContextTypes.DEFA... method _flush_media_group (line 788) | async def _flush_media_group(self, key: str) -> None: method _start_typing (line 804) | def _start_typing(self, chat_id: str) -> None: method _stop_typing (line 810) | def _stop_typing(self, chat_id: str) -> None: method _typing_loop (line 816) | async def _typing_loop(self, chat_id: str) -> None: method _on_error (line 827) | async def _on_error(self, update: object, context: ContextTypes.DEFAUL... method _get_extension (line 831) | def _get_extension( FILE: nanobot/channels/wecom.py class WecomConfig (line 20) | class WecomConfig(Base): class WecomChannel (line 39) | class WecomChannel(BaseChannel): method default_config (line 53) | def default_config(cls) -> dict[str, Any]: method __init__ (line 56) | def __init__(self, config: Any, bus: MessageBus): method start (line 68) | async def start(self) -> None: method stop (line 115) | async def stop(self) -> None: method _on_connected (line 122) | async def _on_connected(self, frame: Any) -> None: method _on_authenticated (line 126) | async def _on_authenticated(self, frame: Any) -> None: method _on_disconnected (line 130) | async def _on_disconnected(self, frame: Any) -> None: method _on_error (line 135) | async def _on_error(self, frame: Any) -> None: method _on_text_message (line 139) | async def _on_text_message(self, frame: Any) -> None: method _on_image_message (line 143) | async def _on_image_message(self, frame: Any) -> None: method _on_voice_message (line 147) | async def _on_voice_message(self, frame: Any) -> None: method _on_file_message (line 151) | async def _on_file_message(self, frame: Any) -> None: method _on_mixed_message (line 155) | async def _on_mixed_message(self, frame: Any) -> None: method _on_enter_chat (line 159) | async def _on_enter_chat(self, frame: Any) -> None: method _process_message (line 180) | async def _process_message(self, frame: Any, msg_type: str) -> None: method _download_and_save_media (line 305) | async def _download_and_save_media( method send (line 339) | async def send(self, msg: OutboundMessage) -> None: FILE: nanobot/channels/whatsapp.py class WhatsAppConfig (line 19) | class WhatsAppConfig(Base): class WhatsAppChannel (line 28) | class WhatsAppChannel(BaseChannel): method default_config (line 40) | def default_config(cls) -> dict[str, Any]: method __init__ (line 43) | def __init__(self, config: Any, bus: MessageBus): method start (line 51) | async def start(self) -> None: method stop (line 89) | async def stop(self) -> None: method send (line 98) | async def send(self, msg: OutboundMessage) -> None: method _handle_bridge_message (line 114) | async def _handle_bridge_message(self, raw: str) -> None: FILE: nanobot/cli/commands.py function _flush_pending_tty_input (line 57) | def _flush_pending_tty_input() -> None: function _restore_terminal (line 84) | def _restore_terminal() -> None: function _init_prompt_session (line 95) | def _init_prompt_session() -> None: function _make_console (line 118) | def _make_console() -> Console: function _render_interactive_ansi (line 122) | def _render_interactive_ansi(render_fn) -> str: function _print_agent_response (line 134) | def _print_agent_response(response: str, render_markdown: bool) -> None: function _print_interactive_line (line 145) | async def _print_interactive_line(text: str) -> None: function _print_interactive_response (line 156) | async def _print_interactive_response(response: str, render_markdown: bo... class _ThinkingSpinner (line 173) | class _ThinkingSpinner: method __init__ (line 176) | def __init__(self, enabled: bool): method __enter__ (line 182) | def __enter__(self): method __exit__ (line 188) | def __exit__(self, *exc): method pause (line 195) | def pause(self): function _print_cli_progress_line (line 206) | def _print_cli_progress_line(text: str, thinking: _ThinkingSpinner | Non... function _print_interactive_progress_line (line 212) | async def _print_interactive_progress_line(text: str, thinking: _Thinkin... function _is_exit_command (line 218) | def _is_exit_command(command: str) -> bool: function _read_interactive_input_async (line 223) | async def _read_interactive_input_async() -> str: function version_callback (line 243) | def version_callback(value: bool): function main (line 250) | def main( function onboard (line 265) | def onboard( function _merge_missing_defaults (line 326) | def _merge_missing_defaults(existing: Any, defaults: Any) -> Any: function _onboard_plugins (line 340) | def _onboard_plugins(config_path: Path) -> None: function _make_provider (line 364) | def _make_provider(config: Config): function _load_runtime_config (line 423) | def _load_runtime_config(config: str | None = None, workspace: str | Non... function _print_deprecated_memory_window_notice (line 442) | def _print_deprecated_memory_window_notice(config: Config) -> None: function gateway (line 458) | def gateway( function agent (line 653) | def agent( function channels_status (line 844) | def channels_status(): function _get_bridge_dir (line 871) | def _get_bridge_dir() -> Path: function channels_login (line 933) | def channels_login(): function plugins_list (line 974) | def plugins_list(): function status (line 1013) | def status(): function _register_login (line 1060) | def _register_login(name: str): function provider_login (line 1068) | def provider_login( function _login_openai_codex (line 1091) | def _login_openai_codex() -> None: function _login_github_copilot (line 1115) | def _login_github_copilot() -> None: FILE: nanobot/config/loader.py function set_config_path (line 12) | def set_config_path(path: Path) -> None: function get_config_path (line 18) | def get_config_path() -> Path: function load_config (line 25) | def load_config(config_path: Path | None = None) -> Config: function save_config (line 50) | def save_config(config: Config, config_path: Path | None = None) -> None: function _migrate_config (line 67) | def _migrate_config(data: dict) -> dict: FILE: nanobot/config/paths.py function get_data_dir (line 11) | def get_data_dir() -> Path: function get_runtime_subdir (line 16) | def get_runtime_subdir(name: str) -> Path: function get_media_dir (line 21) | def get_media_dir(channel: str | None = None) -> Path: function get_cron_dir (line 27) | def get_cron_dir() -> Path: function get_logs_dir (line 32) | def get_logs_dir() -> Path: function get_workspace_path (line 37) | def get_workspace_path(workspace: str | None = None) -> Path: function get_cli_history_path (line 43) | def get_cli_history_path() -> Path: function get_bridge_install_dir (line 48) | def get_bridge_install_dir() -> Path: function get_legacy_sessions_dir (line 53) | def get_legacy_sessions_dir() -> Path: FILE: nanobot/config/schema.py class Base (line 11) | class Base(BaseModel): class ChannelsConfig (line 16) | class ChannelsConfig(Base): class AgentDefaults (line 29) | class AgentDefaults(Base): method should_warn_deprecated_memory_window (line 46) | def should_warn_deprecated_memory_window(self) -> bool: class AgentsConfig (line 51) | class AgentsConfig(Base): class ProviderConfig (line 57) | class ProviderConfig(Base): class ProvidersConfig (line 65) | class ProvidersConfig(Base): class HeartbeatConfig (line 92) | class HeartbeatConfig(Base): class GatewayConfig (line 99) | class GatewayConfig(Base): class WebSearchConfig (line 107) | class WebSearchConfig(Base): class WebToolsConfig (line 116) | class WebToolsConfig(Base): class ExecToolConfig (line 125) | class ExecToolConfig(Base): class MCPServerConfig (line 132) | class MCPServerConfig(Base): class ToolsConfig (line 144) | class ToolsConfig(Base): class Config (line 153) | class Config(BaseSettings): method workspace_path (line 163) | def workspace_path(self) -> Path: method _match_provider (line 167) | def _match_provider( method get_provider (line 229) | def get_provider(self, model: str | None = None) -> ProviderConfig | N... method get_provider_name (line 234) | def get_provider_name(self, model: str | None = None) -> str | None: method get_api_key (line 239) | def get_api_key(self, model: str | None = None) -> str | None: method get_api_base (line 244) | def get_api_base(self, model: str | None = None) -> str | None: FILE: nanobot/cron/service.py function _now_ms (line 16) | def _now_ms() -> int: function _compute_next_run (line 20) | def _compute_next_run(schedule: CronSchedule, now_ms: int) -> int | None: function _validate_schedule_for_add (line 49) | def _validate_schedule_for_add(schedule: CronSchedule) -> None: class CronService (line 63) | class CronService: method __init__ (line 66) | def __init__( method _load_store (line 78) | def _load_store(self) -> CronStore: method _save_store (line 130) | def _save_store(self) -> None: method start (line 175) | async def start(self) -> None: method stop (line 184) | def stop(self) -> None: method _recompute_next_runs (line 191) | def _recompute_next_runs(self) -> None: method _get_next_wake_ms (line 200) | def _get_next_wake_ms(self) -> int | None: method _arm_timer (line 208) | def _arm_timer(self) -> None: method _on_timer (line 227) | async def _on_timer(self) -> None: method _execute_job (line 245) | async def _execute_job(self, job: CronJob) -> None: method list_jobs (line 280) | def list_jobs(self, include_disabled: bool = False) -> list[CronJob]: method add_job (line 286) | def add_job( method remove_job (line 326) | def remove_job(self, job_id: str) -> bool: method enable_job (line 340) | def enable_job(self, job_id: str, enabled: bool = True) -> CronJob | N... method run_job (line 356) | async def run_job(self, job_id: str, force: bool = False) -> bool: method status (line 369) | def status(self) -> dict: FILE: nanobot/cron/types.py class CronSchedule (line 8) | class CronSchedule: class CronPayload (line 22) | class CronPayload: class CronJobState (line 33) | class CronJobState: class CronJob (line 42) | class CronJob: class CronStore (line 56) | class CronStore: FILE: nanobot/heartbeat/service.py class HeartbeatService (line 40) | class HeartbeatService: method __init__ (line 53) | def __init__( method heartbeat_file (line 74) | def heartbeat_file(self) -> Path: method _read_heartbeat_file (line 77) | def _read_heartbeat_file(self) -> str | None: method _decide (line 85) | async def _decide(self, content: str) -> tuple[str, str]: method start (line 111) | async def start(self) -> None: method stop (line 124) | def stop(self) -> None: method _run_loop (line 131) | async def _run_loop(self) -> None: method _tick (line 143) | async def _tick(self) -> None: method trigger_now (line 177) | async def trigger_now(self) -> str | None: FILE: nanobot/providers/__init__.py function __getattr__ (line 24) | def __getattr__(name: str): FILE: nanobot/providers/azure_openai_provider.py class AzureOpenAIProvider (line 17) | class AzureOpenAIProvider(LLMProvider): method __init__ (line 29) | def __init__( method _build_chat_url (line 50) | def _build_chat_url(self, deployment_name: str) -> str: method _build_headers (line 64) | def _build_headers(self) -> dict[str, str]: method _supports_temperature (line 73) | def _supports_temperature( method _prepare_request_payload (line 83) | def _prepare_request_payload( method chat (line 114) | async def chat( method _parse_response (line 164) | def _parse_response(self, response: dict[str, Any]) -> LLMResponse: method get_default_model (line 211) | def get_default_model(self) -> str: FILE: nanobot/providers/base.py class ToolCallRequest (line 13) | class ToolCallRequest: method to_openai_tool_call (line 21) | def to_openai_tool_call(self) -> dict[str, Any]: class LLMResponse (line 39) | class LLMResponse: method has_tool_calls (line 49) | def has_tool_calls(self) -> bool: class GenerationSettings (line 55) | class GenerationSettings: class LLMProvider (line 69) | class LLMProvider(ABC): method __init__ (line 95) | def __init__(self, api_key: str | None = None, api_base: str | None = ... method _sanitize_empty_content (line 101) | def _sanitize_empty_content(messages: list[dict[str, Any]]) -> list[di... method _sanitize_request_messages (line 150) | def _sanitize_request_messages( method chat (line 164) | async def chat( method _is_transient_error (line 191) | def _is_transient_error(cls, content: str | None) -> bool: method _strip_image_content (line 196) | def _strip_image_content(messages: list[dict[str, Any]]) -> list[dict[... method _safe_chat (line 217) | async def _safe_chat(self, **kwargs: Any) -> LLMResponse: method chat_with_retry (line 226) | async def chat_with_retry( method get_default_model (line 278) | def get_default_model(self) -> str: FILE: nanobot/providers/custom_provider.py class CustomProvider (line 14) | class CustomProvider(LLMProvider): method __init__ (line 16) | def __init__( method chat (line 37) | async def chat(self, messages: list[dict[str, Any]], tools: list[dict[... method _parse (line 56) | def _parse(self, response: Any) -> LLMResponse: method get_default_model (line 76) | def get_default_model(self) -> str: FILE: nanobot/providers/litellm_provider.py function _short_tool_id (line 22) | def _short_tool_id() -> str: class LiteLLMProvider (line 27) | class LiteLLMProvider(LLMProvider): method __init__ (line 36) | def __init__( method _setup_env (line 67) | def _setup_env(self, api_key: str, api_base: str | None, model: str) -... method _resolve_model (line 91) | def _resolve_model(self, model: str) -> str: method _canonicalize_explicit_prefix (line 111) | def _canonicalize_explicit_prefix(model: str, spec_name: str, canonica... method _supports_cache_control (line 120) | def _supports_cache_control(self, model: str) -> bool: method _apply_cache_control (line 127) | def _apply_cache_control( method _apply_model_overrides (line 153) | def _apply_model_overrides(self, model: str, kwargs: dict[str, Any]) -... method _extra_msg_keys (line 164) | def _extra_msg_keys(original_model: str, resolved_model: str) -> froze... method _normalize_tool_call_id (line 172) | def _normalize_tool_call_id(tool_call_id: Any) -> Any: method _sanitize_messages (line 181) | def _sanitize_messages(messages: list[dict[str, Any]], extra_keys: fro... method chat (line 210) | async def chat( method _parse_response (line 290) | def _parse_response(self, response: Any) -> LLMResponse: method get_default_model (line 353) | def get_default_model(self) -> str: FILE: nanobot/providers/openai_codex_provider.py class OpenAICodexProvider (line 20) | class OpenAICodexProvider(LLMProvider): method __init__ (line 23) | def __init__(self, default_model: str = "openai-codex/gpt-5.1-codex"): method chat (line 27) | async def chat( method get_default_model (line 83) | def get_default_model(self) -> str: function _strip_model_prefix (line 87) | def _strip_model_prefix(model: str) -> str: function _build_headers (line 93) | def _build_headers(account_id: str, token: str) -> dict[str, str]: function _request_codex (line 105) | async def _request_codex( function _convert_tools (line 119) | def _convert_tools(tools: list[dict[str, Any]]) -> list[dict[str, Any]]: function _convert_messages (line 137) | def _convert_messages(messages: list[dict[str, Any]]) -> tuple[str, list... function _convert_user_message (line 197) | def _convert_user_message(content: Any) -> dict[str, Any]: function _split_tool_call_id (line 216) | def _split_tool_call_id(tool_call_id: Any) -> tuple[str, str | None]: function _prompt_cache_key (line 225) | def _prompt_cache_key(messages: list[dict[str, Any]]) -> str: function _iter_sse (line 230) | async def _iter_sse(response: httpx.Response) -> AsyncGenerator[dict[str... function _consume_sse (line 250) | async def _consume_sse(response: httpx.Response) -> tuple[str, list[Tool... function _map_finish_reason (line 310) | def _map_finish_reason(status: str | None) -> str: function _friendly_error (line 314) | def _friendly_error(status_code: int, raw: str) -> str: FILE: nanobot/providers/registry.py class ProviderSpec (line 20) | class ProviderSpec: method label (line 65) | def label(self) -> str: function find_by_model (line 465) | def find_by_model(model: str) -> ProviderSpec | None: function find_gateway (line 487) | def find_gateway( function find_by_name (line 518) | def find_by_name(name: str) -> ProviderSpec | None: FILE: nanobot/providers/transcription.py class GroqTranscriptionProvider (line 10) | class GroqTranscriptionProvider: method __init__ (line 17) | def __init__(self, api_key: str | None = None): method transcribe (line 21) | async def transcribe(self, file_path: str | Path) -> str: FILE: nanobot/security/network.py function _is_private (line 26) | def _is_private(addr: ipaddress.IPv4Address | ipaddress.IPv6Address) -> ... function validate_url_target (line 30) | def validate_url_target(url: str) -> tuple[bool, str]: function validate_resolved_url (line 65) | def validate_resolved_url(url: str) -> tuple[bool, str]: function contains_internal_url (line 97) | def contains_internal_url(command: str) -> bool: FILE: nanobot/session/manager.py class Session (line 17) | class Session: method add_message (line 35) | def add_message(self, role: str, content: str, **kwargs: Any) -> None: method _find_legal_start (line 47) | def _find_legal_start(messages: list[dict[str, Any]]) -> int: method get_history (line 69) | def get_history(self, max_messages: int = 500) -> list[dict[str, Any]]: method clear (line 95) | def clear(self) -> None: class SessionManager (line 102) | class SessionManager: method __init__ (line 109) | def __init__(self, workspace: Path): method _get_session_path (line 115) | def _get_session_path(self, key: str) -> Path: method _get_legacy_session_path (line 120) | def _get_legacy_session_path(self, key: str) -> Path: method get_or_create (line 125) | def get_or_create(self, key: str) -> Session: method _load (line 145) | def _load(self, key: str) -> Session | None: method save (line 192) | def save(self, session: Session) -> None: method invalidate (line 211) | def invalidate(self, key: str) -> None: method list_sessions (line 215) | def list_sessions(self) -> list[dict[str, Any]]: FILE: nanobot/skills/skill-creator/scripts/init_skill.py function normalize_skill_name (line 194) | def normalize_skill_name(skill_name): function title_case_skill_name (line 203) | def title_case_skill_name(skill_name): function parse_resources (line 208) | def parse_resources(raw_resources): function create_resource_dirs (line 227) | def create_resource_dirs(skill_dir, skill_name, skill_title, resources, ... function init_skill (line 255) | def init_skill(skill_name, path, resources, include_examples): function main (line 320) | def main(): FILE: nanobot/skills/skill-creator/scripts/package_skill.py function _is_within (line 20) | def _is_within(path: Path, root: Path) -> bool: function _cleanup_partial_archive (line 28) | def _cleanup_partial_archive(skill_filename: Path) -> None: function package_skill (line 36) | def package_skill(skill_path, output_dir=None): function main (line 129) | def main(): FILE: nanobot/skills/skill-creator/scripts/quick_validate.py function _extract_frontmatter (line 29) | def _extract_frontmatter(content: str) -> Optional[str]: function _parse_simple_frontmatter (line 39) | def _parse_simple_frontmatter(frontmatter_text: str) -> Optional[dict[st... function _load_frontmatter (line 86) | def _load_frontmatter(frontmatter_text: str) -> tuple[Optional[dict], Op... function _validate_skill_name (line 102) | def _validate_skill_name(name: str, folder_name: str) -> Optional[str]: function _validate_description (line 118) | def _validate_description(description: str) -> Optional[str]: function validate_skill (line 132) | def validate_skill(skill_path): FILE: nanobot/utils/evaluator.py function evaluate_response (line 53) | async def evaluate_response( FILE: nanobot/utils/helpers.py function detect_image_mime (line 13) | def detect_image_mime(data: bytes) -> str | None: function ensure_dir (line 26) | def ensure_dir(path: Path) -> Path: function timestamp (line 32) | def timestamp() -> str: function current_time_str (line 37) | def current_time_str() -> str: function safe_filename (line 46) | def safe_filename(name: str) -> str: function split_message (line 51) | def split_message(content: str, max_len: int = 2000) -> list[str]: function build_assistant_message (line 83) | def build_assistant_message( function estimate_prompt_tokens (line 100) | def estimate_prompt_tokens( function estimate_message_tokens (line 125) | def estimate_message_tokens(message: dict[str, Any]) -> int: function estimate_prompt_tokens_chain (line 159) | def estimate_prompt_tokens_chain( function sync_workspace_templates (line 181) | def sync_workspace_templates(workspace: Path, silent: bool = False) -> l... FILE: tests/test_azure_openai_provider.py function test_azure_openai_provider_init (line 11) | def test_azure_openai_provider_init(): function test_azure_openai_provider_init_validation (line 25) | def test_azure_openai_provider_init_validation(): function test_build_chat_url (line 36) | def test_build_chat_url(): function test_build_chat_url_api_base_without_slash (line 56) | def test_build_chat_url_api_base_without_slash(): function test_build_headers (line 69) | def test_build_headers(): function test_prepare_request_payload (line 83) | def test_prepare_request_payload(): function test_prepare_request_payload_sanitizes_messages (line 113) | def test_prepare_request_payload_sanitizes_messages(): function test_chat_success (line 154) | async def test_chat_success(): function test_chat_uses_default_model_when_no_model_provided (line 205) | async def test_chat_uses_default_model_when_no_model_provided(): function test_chat_with_tool_calls (line 240) | async def test_chat_with_tool_calls(): function test_chat_api_error (line 293) | async def test_chat_api_error(): function test_chat_connection_error (line 320) | async def test_chat_connection_error(): function test_parse_response_malformed (line 341) | def test_parse_response_malformed(): function test_get_default_model (line 358) | def test_get_default_model(): FILE: tests/test_base_channel.py class _DummyChannel (line 8) | class _DummyChannel(BaseChannel): method start (line 11) | async def start(self) -> None: method stop (line 14) | async def stop(self) -> None: method send (line 17) | async def send(self, msg: OutboundMessage) -> None: function test_is_allowed_requires_exact_match (line 21) | def test_is_allowed_requires_exact_match() -> None: FILE: tests/test_channel_plugins.py class _FakePlugin (line 21) | class _FakePlugin(BaseChannel): method start (line 25) | async def start(self) -> None: method stop (line 28) | async def stop(self) -> None: method send (line 31) | async def send(self, msg: OutboundMessage) -> None: class _FakeTelegram (line 35) | class _FakeTelegram(BaseChannel): method start (line 40) | async def start(self) -> None: method stop (line 43) | async def stop(self) -> None: method send (line 46) | async def send(self, msg: OutboundMessage) -> None: function _make_entry_point (line 50) | def _make_entry_point(name: str, cls: type): function test_channels_config_accepts_unknown_keys (line 60) | def test_channels_config_accepts_unknown_keys(): function test_channels_config_getattr_returns_extra (line 70) | def test_channels_config_getattr_returns_extra(): function test_channels_config_builtin_fields_removed (line 77) | def test_channels_config_builtin_fields_removed(): function test_discover_plugins_loads_entry_points (line 92) | def test_discover_plugins_loads_entry_points(): function test_discover_plugins_handles_load_error (line 103) | def test_discover_plugins_handles_load_error(): function test_discover_all_includes_builtins (line 120) | def test_discover_all_includes_builtins(): function test_discover_all_includes_external_plugin (line 133) | def test_discover_all_includes_external_plugin(): function test_discover_all_builtin_shadows_plugin (line 144) | def test_discover_all_builtin_shadows_plugin(): function test_manager_loads_plugin_from_dict_config (line 160) | async def test_manager_loads_plugin_from_dict_config(): function test_manager_skips_disabled_plugin (line 187) | async def test_manager_skips_disabled_plugin(): function test_builtin_channel_default_config (line 213) | def test_builtin_channel_default_config(): function test_builtin_channel_init_from_dict (line 222) | def test_builtin_channel_init_from_dict(): FILE: tests/test_cli_input.py function mock_prompt_session (line 11) | def mock_prompt_session(): function test_read_interactive_input_async_returns_input (line 21) | async def test_read_interactive_input_async_returns_input(mock_prompt_se... function test_read_interactive_input_async_handles_eof (line 34) | async def test_read_interactive_input_async_handles_eof(mock_prompt_sess... function test_init_prompt_session_creates_session (line 42) | def test_init_prompt_session_creates_session(): function test_thinking_spinner_pause_stops_and_restarts (line 62) | def test_thinking_spinner_pause_stops_and_restarts(): function test_print_cli_progress_line_pauses_spinner_before_printing (line 80) | def test_print_cli_progress_line_pauses_spinner_before_printing(): function test_print_interactive_progress_line_pauses_spinner_before_printing (line 97) | async def test_print_interactive_progress_line_pauses_spinner_before_pri... FILE: tests/test_commands.py function _strip_ansi (line 17) | def _strip_ansi(text): class _StopGateway (line 25) | class _StopGateway(RuntimeError): function mock_paths (line 30) | def mock_paths(): function test_onboard_fresh_install (line 62) | def test_onboard_fresh_install(mock_paths): function test_onboard_existing_config_refresh (line 79) | def test_onboard_existing_config_refresh(mock_paths): function test_onboard_existing_config_overwrite (line 93) | def test_onboard_existing_config_overwrite(mock_paths): function test_onboard_existing_workspace_safe_create (line 106) | def test_onboard_existing_workspace_safe_create(mock_paths): function test_onboard_help_shows_workspace_and_config_options (line 120) | def test_onboard_help_shows_workspace_and_config_options(): function test_onboard_uses_explicit_config_and_workspace_paths (line 132) | def test_onboard_uses_explicit_config_and_workspace_paths(tmp_path, monk... function test_config_matches_github_copilot_codex_with_hyphen_prefix (line 154) | def test_config_matches_github_copilot_codex_with_hyphen_prefix(): function test_config_matches_openai_codex_with_hyphen_prefix (line 161) | def test_config_matches_openai_codex_with_hyphen_prefix(): function test_config_matches_explicit_ollama_prefix_without_api_key (line 168) | def test_config_matches_explicit_ollama_prefix_without_api_key(): function test_config_explicit_ollama_provider_uses_default_localhost_api_base (line 176) | def test_config_explicit_ollama_provider_uses_default_localhost_api_base(): function test_config_auto_detects_ollama_from_local_api_base (line 185) | def test_config_auto_detects_ollama_from_local_api_base(): function test_config_prefers_ollama_over_vllm_when_both_local_providers_configured (line 197) | def test_config_prefers_ollama_over_vllm_when_both_local_providers_confi... function test_config_falls_back_to_vllm_when_ollama_not_configured (line 212) | def test_config_falls_back_to_vllm_when_ollama_not_configured(): function test_find_by_model_prefers_explicit_prefix_over_generic_codex_keyword (line 226) | def test_find_by_model_prefers_explicit_prefix_over_generic_codex_keywor... function test_litellm_provider_canonicalizes_github_copilot_hyphen_prefix (line 233) | def test_litellm_provider_canonicalizes_github_copilot_hyphen_prefix(): function test_openai_codex_strip_prefix_supports_hyphen_and_underscore (line 241) | def test_openai_codex_strip_prefix_supports_hyphen_and_underscore(): function test_make_provider_passes_extra_headers_to_custom_provider (line 246) | def test_make_provider_passes_extra_headers_to_custom_provider(): function mock_agent_runtime (line 274) | def mock_agent_runtime(tmp_path): function test_agent_help_shows_workspace_and_config_options (line 305) | def test_agent_help_shows_workspace_and_config_options(): function test_agent_uses_default_config_when_no_workspace_or_config_flags (line 316) | def test_agent_uses_default_config_when_no_workspace_or_config_flags(moc... function test_agent_uses_explicit_config_path (line 331) | def test_agent_uses_explicit_config_path(mock_agent_runtime, tmp_path: P... function test_agent_config_sets_active_path (line 341) | def test_agent_config_sets_active_path(monkeypatch, tmp_path: Path) -> N... function test_agent_overrides_workspace_path (line 379) | def test_agent_overrides_workspace_path(mock_agent_runtime): function test_agent_workspace_override_wins_over_config_workspace (line 390) | def test_agent_workspace_override_wins_over_config_workspace(mock_agent_... function test_agent_warns_about_deprecated_memory_window (line 407) | def test_agent_warns_about_deprecated_memory_window(mock_agent_runtime): function test_gateway_uses_workspace_from_config_by_default (line 417) | def test_gateway_uses_workspace_from_config_by_default(monkeypatch, tmp_... function test_gateway_workspace_option_overrides_config (line 447) | def test_gateway_workspace_option_overrides_config(monkeypatch, tmp_path... function test_gateway_warns_about_deprecated_memory_window (line 478) | def test_gateway_warns_about_deprecated_memory_window(monkeypatch, tmp_p... function test_gateway_uses_config_directory_for_cron_store (line 500) | def test_gateway_uses_config_directory_for_cron_store(monkeypatch, tmp_p... function test_gateway_uses_configured_port_when_cli_flag_is_missing (line 530) | def test_gateway_uses_configured_port_when_cli_flag_is_missing(monkeypat... function test_gateway_cli_port_overrides_configured_port (line 552) | def test_gateway_cli_port_overrides_configured_port(monkeypatch, tmp_pat... FILE: tests/test_config_migration.py function test_load_config_keeps_max_tokens_and_warns_on_legacy_memory_window (line 12) | def test_load_config_keeps_max_tokens_and_warns_on_legacy_memory_window(... function test_save_config_writes_context_window_tokens_but_not_memory_window (line 35) | def test_save_config_writes_context_window_tokens_but_not_memory_window(... function test_onboard_refresh_rewrites_legacy_config_template (line 61) | def test_onboard_refresh_rewrites_legacy_config_template(tmp_path, monke... function test_onboard_refresh_backfills_missing_channel_fields (line 92) | def test_onboard_refresh_backfills_missing_channel_fields(tmp_path, monk... FILE: tests/test_config_paths.py function test_runtime_dirs_follow_config_path (line 16) | def test_runtime_dirs_follow_config_path(monkeypatch, tmp_path: Path) ->... function test_media_dir_supports_channel_namespace (line 26) | def test_media_dir_supports_channel_namespace(monkeypatch, tmp_path: Pat... function test_shared_and_legacy_paths_remain_global (line 34) | def test_shared_and_legacy_paths_remain_global() -> None: function test_workspace_path_is_explicitly_resolved (line 40) | def test_workspace_path_is_explicitly_resolved() -> None: FILE: tests/test_consolidate_offset.py function create_session_with_messages (line 15) | def create_session_with_messages(key: str, count: int, role: str = "user... function assert_messages_content (line 32) | def assert_messages_content(messages: list, start_index: int, end_index:... function get_old_messages (line 45) | def get_old_messages(session: Session, last_consolidated: int, keep_coun... class TestSessionLastConsolidated (line 59) | class TestSessionLastConsolidated: method test_initial_last_consolidated_zero (line 62) | def test_initial_last_consolidated_zero(self) -> None: method test_last_consolidated_persistence (line 67) | def test_last_consolidated_persistence(self, tmp_path) -> None: method test_clear_resets_last_consolidated (line 78) | def test_clear_resets_last_consolidated(self) -> None: class TestSessionImmutableHistory (line 88) | class TestSessionImmutableHistory: method test_initial_state (line 91) | def test_initial_state(self) -> None: method test_add_messages_appends_only (line 96) | def test_add_messages_appends_only(self) -> None: method test_get_history_returns_most_recent (line 105) | def test_get_history_returns_most_recent(self) -> None: method test_get_history_with_all_messages (line 117) | def test_get_history_with_all_messages(self) -> None: method test_get_history_stable_for_same_session (line 124) | def test_get_history_stable_for_same_session(self) -> None: method test_messages_list_never_modified (line 131) | def test_messages_list_never_modified(self) -> None: class TestSessionPersistence (line 144) | class TestSessionPersistence: method temp_manager (line 148) | def temp_manager(self, tmp_path): method test_persistence_roundtrip (line 151) | def test_persistence_roundtrip(self, temp_manager): method test_get_history_after_reload (line 161) | def test_get_history_after_reload(self, temp_manager): method test_clear_resets_session (line 172) | def test_clear_resets_session(self, temp_manager): class TestConsolidationTriggerConditions (line 181) | class TestConsolidationTriggerConditions: method test_consolidation_needed_when_messages_exceed_window (line 184) | def test_consolidation_needed_when_messages_exceed_window(self): method test_consolidation_skipped_when_within_keep_count (line 197) | def test_consolidation_skipped_when_within_keep_count(self): method test_consolidation_skipped_when_no_new_messages (line 207) | def test_consolidation_skipped_when_no_new_messages(self): class TestLastConsolidatedEdgeCases (line 226) | class TestLastConsolidatedEdgeCases: method test_last_consolidated_exceeds_message_count (line 229) | def test_last_consolidated_exceeds_message_count(self): method test_last_consolidated_negative_value (line 241) | def test_last_consolidated_negative_value(self): method test_messages_added_after_consolidation (line 254) | def test_messages_added_after_consolidation(self): method test_slice_behavior_when_indices_overlap (line 270) | def test_slice_behavior_when_indices_overlap(self): class TestArchiveAllMode (line 279) | class TestArchiveAllMode: method test_archive_all_consolidates_everything (line 282) | def test_archive_all_consolidates_everything(self): method test_archive_all_resets_last_consolidated (line 293) | def test_archive_all_resets_last_consolidated(self): method test_archive_all_vs_normal_consolidation (line 305) | def test_archive_all_vs_normal_consolidation(self): class TestCacheImmutability (line 321) | class TestCacheImmutability: method test_consolidation_does_not_modify_messages_list (line 324) | def test_consolidation_does_not_modify_messages_list(self): method test_get_history_does_not_modify_messages (line 335) | def test_get_history_does_not_modify_messages(self): method test_consolidation_only_updates_last_consolidated (line 348) | def test_consolidation_only_updates_last_consolidated(self): class TestSliceLogic (line 364) | class TestSliceLogic: method test_slice_extracts_correct_range (line 367) | def test_slice_extracts_correct_range(self): method test_slice_with_partial_consolidation (line 380) | def test_slice_with_partial_consolidation(self): method test_slice_with_various_keep_counts (line 390) | def test_slice_with_various_keep_counts(self): method test_slice_when_keep_count_exceeds_messages (line 400) | def test_slice_when_keep_count_exceeds_messages(self): class TestEmptyAndBoundarySessions (line 408) | class TestEmptyAndBoundarySessions: method test_empty_session_consolidation (line 411) | def test_empty_session_consolidation(self): method test_single_message_session (line 424) | def test_single_message_session(self): method test_exactly_keep_count_messages (line 434) | def test_exactly_keep_count_messages(self): method test_just_over_keep_count (line 443) | def test_just_over_keep_count(self): method test_very_large_session (line 453) | def test_very_large_session(self): method test_session_with_gaps_in_consolidation (line 467) | def test_session_with_gaps_in_consolidation(self): class TestNewCommandArchival (line 483) | class TestNewCommandArchival: method _make_loop (line 487) | def _make_loop(tmp_path: Path): method test_new_clears_session_immediately_even_if_archive_fails (line 508) | async def test_new_clears_session_immediately_even_if_archive_fails(se... method test_new_archives_only_unconsolidated_messages (line 541) | async def test_new_archives_only_unconsolidated_messages(self, tmp_pat... method test_new_clears_session_and_responds (line 571) | async def test_new_clears_session_and_responds(self, tmp_path: Path) -... method test_close_mcp_drains_background_tasks (line 594) | async def test_close_mcp_drains_background_tasks(self, tmp_path: Path)... FILE: tests/test_context_prompt_cache.py class _FakeDatetime (line 13) | class _FakeDatetime(real_datetime): method now (line 17) | def now(cls, tz=None): # type: ignore[override] function _make_workspace (line 21) | def _make_workspace(tmp_path: Path) -> Path: function test_bootstrap_files_are_backed_by_templates (line 27) | def test_bootstrap_files_are_backed_by_templates() -> None: function test_system_prompt_stays_stable_when_clock_changes (line 34) | def test_system_prompt_stays_stable_when_clock_changes(tmp_path, monkeyp... function test_runtime_context_is_separate_untrusted_user_message (line 50) | def test_runtime_context_is_separate_untrusted_user_message(tmp_path) ->... FILE: tests/test_cron_service.py function test_add_job_rejects_unknown_timezone (line 9) | def test_add_job_rejects_unknown_timezone(tmp_path) -> None: function test_add_job_accepts_valid_timezone (line 22) | def test_add_job_accepts_valid_timezone(tmp_path) -> None: function test_running_service_honors_external_disable (line 36) | async def test_running_service_honors_external_disable(tmp_path) -> None: FILE: tests/test_cron_tool_list.py function _make_tool (line 8) | def _make_tool(tmp_path) -> CronTool: function test_format_timing_cron_with_tz (line 16) | def test_format_timing_cron_with_tz() -> None: function test_format_timing_cron_without_tz (line 21) | def test_format_timing_cron_without_tz() -> None: function test_format_timing_every_hours (line 26) | def test_format_timing_every_hours() -> None: function test_format_timing_every_minutes (line 31) | def test_format_timing_every_minutes() -> None: function test_format_timing_every_seconds (line 36) | def test_format_timing_every_seconds() -> None: function test_format_timing_every_non_minute_seconds (line 41) | def test_format_timing_every_non_minute_seconds() -> None: function test_format_timing_every_milliseconds (line 46) | def test_format_timing_every_milliseconds() -> None: function test_format_timing_at (line 51) | def test_format_timing_at() -> None: function test_format_timing_fallback (line 57) | def test_format_timing_fallback() -> None: function test_format_state_empty (line 65) | def test_format_state_empty() -> None: function test_format_state_last_run_ok (line 70) | def test_format_state_last_run_ok() -> None: function test_format_state_last_run_with_error (line 78) | def test_format_state_last_run_with_error() -> None: function test_format_state_next_run_only (line 86) | def test_format_state_next_run_only() -> None: function test_format_state_both (line 93) | def test_format_state_both() -> None: function test_format_state_unknown_status (line 103) | def test_format_state_unknown_status() -> None: function test_list_empty (line 112) | def test_list_empty(tmp_path) -> None: function test_list_cron_job_shows_expression_and_timezone (line 117) | def test_list_cron_job_shows_expression_and_timezone(tmp_path) -> None: function test_list_every_job_shows_human_interval (line 128) | def test_list_every_job_shows_human_interval(tmp_path) -> None: function test_list_every_job_hours (line 139) | def test_list_every_job_hours(tmp_path) -> None: function test_list_every_job_seconds (line 150) | def test_list_every_job_seconds(tmp_path) -> None: function test_list_every_job_non_minute_seconds (line 161) | def test_list_every_job_non_minute_seconds(tmp_path) -> None: function test_list_every_job_milliseconds (line 172) | def test_list_every_job_milliseconds(tmp_path) -> None: function test_list_at_job_shows_iso_timestamp (line 183) | def test_list_at_job_shows_iso_timestamp(tmp_path) -> None: function test_list_shows_last_run_state (line 194) | def test_list_shows_last_run_state(tmp_path) -> None: function test_list_shows_error_message (line 211) | def test_list_shows_error_message(tmp_path) -> None: function test_list_shows_next_run (line 228) | def test_list_shows_next_run(tmp_path) -> None: function test_list_excludes_disabled_jobs (line 239) | def test_list_excludes_disabled_jobs(tmp_path) -> None: FILE: tests/test_custom_provider.py function test_custom_provider_parse_handles_empty_choices (line 6) | def test_custom_provider_parse_handles_empty_choices() -> None: FILE: tests/test_dingtalk_channel.py class _FakeResponse (line 12) | class _FakeResponse: method __init__ (line 13) | def __init__(self, status_code: int = 200, json_body: dict | None = No... method json (line 20) | def json(self) -> dict: class _FakeHttp (line 24) | class _FakeHttp: method __init__ (line 25) | def __init__(self, responses: list[_FakeResponse] | None = None) -> None: method _next_response (line 29) | def _next_response(self) -> _FakeResponse: method post (line 34) | async def post(self, url: str, json=None, headers=None, **kwargs): method get (line 38) | async def get(self, url: str, **kwargs): function test_group_message_keeps_sender_id_and_routes_chat_id (line 44) | async def test_group_message_keeps_sender_id_and_routes_chat_id() -> None: function test_group_send_uses_group_messages_api (line 64) | async def test_group_send_uses_group_messages_api() -> None: function test_handler_uses_voice_recognition_text_when_text_is_empty (line 84) | async def test_handler_uses_voice_recognition_text_when_text_is_empty(mo... function test_handler_processes_file_message (line 127) | async def test_handler_processes_file_message(monkeypatch) -> None: function test_download_dingtalk_file (line 176) | async def test_download_dingtalk_file(tmp_path, monkeypatch) -> None: FILE: tests/test_email_channel.py function _make_config (line 12) | def _make_config() -> EmailConfig: function _make_raw_email (line 28) | def _make_raw_email( function test_fetch_new_messages_parses_unseen_and_marks_seen (line 42) | def test_fetch_new_messages_parses_unseen_and_marks_seen(monkeypatch) ->... function test_extract_text_body_falls_back_to_html (line 85) | def test_extract_text_body_falls_back_to_html() -> None: function test_start_returns_immediately_without_consent (line 98) | async def test_start_returns_immediately_without_consent(monkeypatch) ->... function test_send_uses_smtp_and_reply_subject (line 116) | async def test_send_uses_smtp_and_reply_subject(monkeypatch) -> None: function test_send_skips_reply_when_auto_reply_disabled (line 172) | async def test_send_skips_reply_when_auto_reply_disabled(monkeypatch) ->... function test_send_proactive_email_when_auto_reply_disabled (line 233) | async def test_send_proactive_email_when_auto_reply_disabled(monkeypatch... function test_send_skips_when_consent_not_granted (line 283) | async def test_send_skips_when_consent_not_granted(monkeypatch) -> None: function test_fetch_messages_between_dates_uses_imap_since_before_without_mark_seen (line 325) | def test_fetch_messages_between_dates_uses_imap_since_before_without_mar... FILE: tests/test_evaluator.py class DummyProvider (line 7) | class DummyProvider(LLMProvider): method __init__ (line 8) | def __init__(self, responses: list[LLMResponse]): method chat (line 12) | async def chat(self, *args, **kwargs) -> LLMResponse: method get_default_model (line 17) | def get_default_model(self) -> str: function _eval_tool_call (line 21) | def _eval_tool_call(should_notify: bool, reason: str = "") -> LLMResponse: function test_should_notify_true (line 35) | async def test_should_notify_true() -> None: function test_should_notify_false (line 42) | async def test_should_notify_false() -> None: function test_fallback_on_error (line 49) | async def test_fallback_on_error() -> None: function test_no_tool_call_fallback (line 60) | async def test_no_tool_call_fallback() -> None: FILE: tests/test_exec_security.py function _fake_resolve_private (line 13) | def _fake_resolve_private(hostname, port, family=0, type_=0): function _fake_resolve_localhost (line 17) | def _fake_resolve_localhost(hostname, port, family=0, type_=0): function _fake_resolve_public (line 21) | def _fake_resolve_public(hostname, port, family=0, type_=0): function test_exec_blocks_curl_metadata (line 26) | async def test_exec_blocks_curl_metadata(): function test_exec_blocks_wget_localhost (line 37) | async def test_exec_blocks_wget_localhost(): function test_exec_allows_normal_commands (line 45) | async def test_exec_allows_normal_commands(): function test_exec_allows_curl_to_public_url (line 53) | async def test_exec_allows_curl_to_public_url(): function test_exec_blocks_chained_internal_url (line 62) | async def test_exec_blocks_chained_internal_url(): FILE: tests/test_feishu_markdown_rendering.py function test_parse_md_table_strips_markdown_formatting_in_headers_and_cells (line 4) | def test_parse_md_table_strips_markdown_formatting_in_headers_and_cells(... function test_split_headings_strips_embedded_markdown_before_bolding (line 25) | def test_split_headings_strips_embedded_markdown_before_bolding() -> None: function test_split_headings_keeps_markdown_body_and_code_blocks_intact (line 41) | def test_split_headings_keeps_markdown_body_and_code_blocks_intact() -> ... FILE: tests/test_feishu_post_content.py function test_extract_post_content_supports_post_wrapper_shape (line 4) | def test_extract_post_content_supports_post_wrapper_shape() -> None: function test_extract_post_content_keeps_direct_shape_behavior (line 25) | def test_extract_post_content_keeps_direct_shape_behavior() -> None: function test_register_optional_event_keeps_builder_when_method_missing (line 43) | def test_register_optional_event_keeps_builder_when_method_missing() -> ... function test_register_optional_event_calls_supported_method (line 52) | def test_register_optional_event_calls_supported_method() -> None: FILE: tests/test_feishu_reply.py function _make_feishu_channel (line 19) | def _make_feishu_channel(reply_to_message: bool = False) -> FeishuChannel: function _make_feishu_event (line 34) | def _make_feishu_event( function _make_get_message_response (line 62) | def _make_get_message_response(text: str, msg_type: str = "text", succes... function test_feishu_config_reply_to_message_defaults_false (line 79) | def test_feishu_config_reply_to_message_defaults_false() -> None: function test_feishu_config_reply_to_message_can_be_enabled (line 83) | def test_feishu_config_reply_to_message_can_be_enabled() -> None: function test_get_message_content_sync_returns_reply_prefix (line 92) | def test_get_message_content_sync_returns_reply_prefix() -> None: function test_get_message_content_sync_truncates_long_text (line 101) | def test_get_message_content_sync_truncates_long_text() -> None: function test_get_message_content_sync_returns_none_on_api_failure (line 114) | def test_get_message_content_sync_returns_none_on_api_failure() -> None: function test_get_message_content_sync_returns_none_for_non_text_type (line 127) | def test_get_message_content_sync_returns_none_for_non_text_type() -> None: function test_get_message_content_sync_returns_none_when_empty_text (line 142) | def test_get_message_content_sync_returns_none_when_empty_text() -> None: function test_reply_message_sync_returns_true_on_success (line 155) | def test_reply_message_sync_returns_true_on_success() -> None: function test_reply_message_sync_returns_false_on_api_error (line 167) | def test_reply_message_sync_returns_false_on_api_error() -> None: function test_reply_message_sync_returns_false_on_exception (line 181) | def test_reply_message_sync_returns_false_on_exception() -> None: function test_send_uses_expected_feishu_msg_type_for_uploaded_files (line 199) | async def test_send_uses_expected_feishu_msg_type_for_uploaded_files( function test_send_uses_reply_api_when_configured (line 237) | async def test_send_uses_reply_api_when_configured() -> None: function test_send_uses_create_api_when_reply_disabled (line 256) | async def test_send_uses_create_api_when_reply_disabled() -> None: function test_send_uses_create_api_when_no_message_id (line 275) | async def test_send_uses_create_api_when_no_message_id() -> None: function test_send_skips_reply_for_progress_messages (line 294) | async def test_send_skips_reply_for_progress_messages() -> None: function test_send_fallback_to_create_when_reply_fails (line 313) | async def test_send_fallback_to_create_when_reply_fails() -> None: function test_on_message_captures_parent_and_root_id_in_metadata (line 344) | async def test_on_message_captures_parent_and_root_id_in_metadata() -> N... function test_on_message_parent_and_root_id_none_when_absent (line 372) | async def test_on_message_parent_and_root_id_none_when_absent() -> None: function test_on_message_prepends_reply_context_when_parent_id_present (line 393) | async def test_on_message_prepends_reply_context_when_parent_id_present(... function test_on_message_no_extra_api_call_when_no_parent_id (line 420) | async def test_on_message_no_extra_api_call_when_no_parent_id() -> None: FILE: tests/test_feishu_table_split.py function _md (line 12) | def _md(text: str) -> dict: function _table (line 16) | def _table() -> dict: function test_empty_list_returns_single_empty_group (line 28) | def test_empty_list_returns_single_empty_group() -> None: function test_no_tables_returns_single_group (line 32) | def test_no_tables_returns_single_group() -> None: function test_single_table_stays_in_one_group (line 38) | def test_single_table_stays_in_one_group() -> None: function test_two_tables_split_into_two_groups (line 45) | def test_two_tables_split_into_two_groups() -> None: function test_three_tables_split_into_three_groups (line 70) | def test_three_tables_split_into_three_groups() -> None: function test_leading_markdown_stays_with_first_table (line 82) | def test_leading_markdown_stays_with_first_table() -> None: function test_trailing_markdown_after_second_table (line 90) | def test_trailing_markdown_after_second_table() -> None: function test_non_table_elements_before_first_table_kept_in_first_group (line 98) | def test_non_table_elements_before_first_table_kept_in_first_group() -> ... FILE: tests/test_feishu_tool_hint_code_block.py function mock_feishu_channel (line 14) | def mock_feishu_channel(): function test_tool_hint_sends_code_message (line 28) | async def test_tool_hint_sends_code_message(mock_feishu_channel): function test_tool_hint_empty_content_does_not_send (line 60) | async def test_tool_hint_empty_content_does_not_send(mock_feishu_channel): function test_tool_hint_without_metadata_sends_as_normal (line 77) | async def test_tool_hint_without_metadata_sends_as_normal(mock_feishu_ch... function test_tool_hint_multiple_tools_in_one_message (line 98) | async def test_tool_hint_multiple_tools_in_one_message(mock_feishu_chann... function test_tool_hint_keeps_commas_inside_arguments (line 120) | async def test_tool_hint_keeps_commas_inside_arguments(mock_feishu_chann... FILE: tests/test_filesystem_tools.py class TestReadFileTool (line 17) | class TestReadFileTool: method tool (line 20) | def tool(self, tmp_path): method sample_file (line 24) | def sample_file(self, tmp_path): method test_basic_read_has_line_numbers (line 30) | async def test_basic_read_has_line_numbers(self, tool, sample_file): method test_offset_and_limit (line 36) | async def test_offset_and_limit(self, tool, sample_file): method test_offset_beyond_end (line 44) | async def test_offset_beyond_end(self, tool, sample_file): method test_end_of_file_marker (line 50) | async def test_end_of_file_marker(self, tool, sample_file): method test_empty_file (line 55) | async def test_empty_file(self, tool, tmp_path): method test_file_not_found (line 62) | async def test_file_not_found(self, tool, tmp_path): method test_char_budget_trims (line 68) | async def test_char_budget_trims(self, tool, tmp_path): class TestFindMatch (line 82) | class TestFindMatch: method test_exact_match (line 84) | def test_exact_match(self): method test_exact_no_match (line 89) | def test_exact_no_match(self): method test_crlf_normalisation (line 94) | def test_crlf_normalisation(self): method test_line_trim_fallback (line 103) | def test_line_trim_fallback(self): method test_line_trim_multiple_candidates (line 112) | def test_line_trim_multiple_candidates(self): method test_empty_old_text (line 118) | def test_empty_old_text(self): class TestEditFileTool (line 128) | class TestEditFileTool: method tool (line 131) | def tool(self, tmp_path): method test_exact_match (line 135) | async def test_exact_match(self, tool, tmp_path): method test_crlf_normalisation (line 143) | async def test_crlf_normalisation(self, tool, tmp_path): method test_trim_fallback (line 156) | async def test_trim_fallback(self, tool, tmp_path): method test_ambiguous_match (line 166) | async def test_ambiguous_match(self, tool, tmp_path): method test_replace_all (line 173) | async def test_replace_all(self, tool, tmp_path): method test_not_found (line 183) | async def test_not_found(self, tool, tmp_path): class TestListDirTool (line 195) | class TestListDirTool: method tool (line 198) | def tool(self, tmp_path): method populated_dir (line 202) | def populated_dir(self, tmp_path): method test_basic_list (line 214) | async def test_basic_list(self, tool, populated_dir): method test_recursive (line 223) | async def test_recursive(self, tool, populated_dir): method test_max_entries_truncation (line 235) | async def test_max_entries_truncation(self, tool, tmp_path): method test_empty_dir (line 243) | async def test_empty_dir(self, tool, tmp_path): method test_not_found (line 250) | async def test_not_found(self, tool, tmp_path): class TestWorkspaceRestriction (line 260) | class TestWorkspaceRestriction: method test_read_blocked_outside_workspace (line 263) | async def test_read_blocked_outside_workspace(self, tmp_path): method test_read_allowed_with_extra_dir (line 277) | async def test_read_allowed_with_extra_dir(self, tmp_path): method test_extra_dirs_does_not_widen_write (line 295) | async def test_extra_dirs_does_not_widen_write(self, tmp_path): method test_read_still_blocked_for_unrelated_dir (line 309) | async def test_read_still_blocked_for_unrelated_dir(self, tmp_path): method test_workspace_file_still_readable_with_extra_dirs (line 328) | async def test_workspace_file_still_readable_with_extra_dirs(self, tmp... method test_edit_blocked_in_extra_dir (line 346) | async def test_edit_blocked_in_extra_dir(self, tmp_path): FILE: tests/test_gemini_thought_signature.py function test_litellm_parse_response_preserves_tool_call_provider_fields (line 7) | def test_litellm_parse_response_preserves_tool_call_provider_fields() ->... function test_tool_call_request_serializes_provider_fields (line 40) | def test_tool_call_request_serializes_provider_fields() -> None: FILE: tests/test_heartbeat_service.py class DummyProvider (line 9) | class DummyProvider(LLMProvider): method __init__ (line 10) | def __init__(self, responses: list[LLMResponse]): method chat (line 15) | async def chat(self, *args, **kwargs) -> LLMResponse: method get_default_model (line 21) | def get_default_model(self) -> str: function test_start_is_idempotent (line 26) | async def test_start_is_idempotent(tmp_path) -> None: function test_decide_returns_skip_when_no_tool_call (line 48) | async def test_decide_returns_skip_when_no_tool_call(tmp_path) -> None: function test_trigger_now_executes_when_decision_is_run (line 62) | async def test_trigger_now_executes_when_decision_is_run(tmp_path) -> None: function test_trigger_now_returns_none_when_decision_is_skip (line 97) | async def test_trigger_now_returns_none_when_decision_is_skip(tmp_path) ... function test_tick_notifies_when_evaluator_says_yes (line 127) | async def test_tick_notifies_when_evaluator_says_yes(tmp_path, monkeypat... function test_tick_suppresses_when_evaluator_says_no (line 173) | async def test_tick_suppresses_when_evaluator_says_no(tmp_path, monkeypa... function test_decide_retries_transient_error_then_succeeds (line 219) | async def test_decide_retries_transient_error_then_succeeds(tmp_path, mo... function test_decide_prompt_includes_current_time (line 256) | async def test_decide_prompt_includes_current_time(tmp_path) -> None: FILE: tests/test_litellm_kwargs.py function _fake_response (line 21) | def _fake_response(content: str = "ok") -> SimpleNamespace: function test_openrouter_spec_uses_prefix_not_custom_llm_provider (line 34) | def test_openrouter_spec_uses_prefix_not_custom_llm_provider() -> None: function test_openrouter_prefixes_model_correctly (line 49) | async def test_openrouter_prefixes_model_correctly() -> None: function test_non_gateway_provider_no_extra_kwargs (line 73) | async def test_non_gateway_provider_no_extra_kwargs() -> None: function test_gateway_without_litellm_kwargs_injects_nothing_extra (line 94) | async def test_gateway_without_litellm_kwargs_injects_nothing_extra() ->... function test_openrouter_autodetect_by_key_prefix (line 115) | async def test_openrouter_autodetect_by_key_prefix() -> None: function test_openrouter_native_model_id_gets_double_prefixed (line 136) | async def test_openrouter_native_model_id_gets_double_prefixed() -> None: FILE: tests/test_loop_consolidation_tokens.py function _make_loop (line 11) | def _make_loop(tmp_path, *, estimated_tokens: int, context_window_tokens... function test_prompt_below_threshold_does_not_consolidate (line 29) | async def test_prompt_below_threshold_does_not_consolidate(tmp_path) -> ... function test_prompt_above_threshold_triggers_consolidation (line 39) | async def test_prompt_above_threshold_triggers_consolidation(tmp_path, m... function test_prompt_above_threshold_archives_until_next_user_boundary (line 57) | async def test_prompt_above_threshold_archives_until_next_user_boundary(... function test_consolidation_loops_until_target_met (line 82) | async def test_consolidation_loops_until_target_met(tmp_path, monkeypatc... function test_consolidation_continues_below_trigger_until_half_target (line 118) | async def test_consolidation_continues_below_trigger_until_half_target(t... function test_preflight_consolidation_before_llm_call (line 155) | async def test_preflight_consolidation_before_llm_call(tmp_path, monkeyp... FILE: tests/test_loop_save_turn.py function _mk_loop (line 6) | def _mk_loop() -> AgentLoop: function test_save_turn_skips_multimodal_user_when_only_runtime_context (line 12) | def test_save_turn_skips_multimodal_user_when_only_runtime_context() -> ... function test_save_turn_keeps_image_placeholder_with_path_after_runtime_strip (line 25) | def test_save_turn_keeps_image_placeholder_with_path_after_runtime_strip... function test_save_turn_keeps_image_placeholder_without_meta (line 44) | def test_save_turn_keeps_image_placeholder_without_meta() -> None: function test_save_turn_keeps_tool_results_under_16k (line 63) | def test_save_turn_keeps_tool_results_under_16k() -> None: FILE: tests/test_matrix_channel.py class _DummyTask (line 20) | class _DummyTask: method __init__ (line 21) | def __init__(self) -> None: method cancel (line 24) | def cancel(self) -> None: method __await__ (line 27) | def __await__(self): class _FakeAsyncClient (line 34) | class _FakeAsyncClient: method __init__ (line 35) | def __init__(self, homeserver, user, store_path, config) -> None: method add_event_callback (line 63) | def add_event_callback(self, callback, event_type) -> None: method add_response_callback (line 66) | def add_response_callback(self, callback, response_type) -> None: method load_store (line 69) | def load_store(self) -> None: method stop_sync_forever (line 72) | def stop_sync_forever(self) -> None: method join (line 75) | async def join(self, room_id: str) -> None: method room_send (line 78) | async def room_send( method room_typing (line 96) | async def room_typing( method download (line 106) | async def download(self, **kwargs): method upload (line 116) | async def upload( method content_repository_config (line 154) | async def content_repository_config(self): method close (line 157) | async def close(self) -> None: function _make_config (line 161) | def _make_config(**kwargs) -> MatrixConfig: function test_start_skips_load_store_when_device_id_missing (line 173) | async def test_start_skips_load_store_when_device_id_missing( function test_register_event_callbacks_uses_media_base_filter (line 210) | async def test_register_event_callbacks_uses_media_base_filter() -> None: function test_media_event_filter_does_not_match_text_events (line 222) | def test_media_event_filter_does_not_match_text_events() -> None: function test_start_disables_e2ee_when_configured (line 227) | async def test_start_disables_e2ee_when_configured( function test_stop_stops_sync_forever_before_close (line 261) | async def test_stop_stops_sync_forever_before_close(monkeypatch) -> None: function test_room_invite_ignores_when_allow_list_is_empty (line 278) | async def test_room_invite_ignores_when_allow_list_is_empty() -> None: function test_room_invite_joins_when_sender_allowed (line 292) | async def test_room_invite_joins_when_sender_allowed() -> None: function test_room_invite_respects_allow_list_when_configured (line 305) | async def test_room_invite_respects_allow_list_when_configured() -> None: function test_on_message_sets_typing_for_allowed_sender (line 319) | async def test_on_message_sets_typing_for_allowed_sender() -> None: function test_typing_keepalive_refreshes_periodically (line 343) | async def test_typing_keepalive_refreshes_periodically(monkeypatch) -> N... function test_on_message_skips_typing_for_self_message (line 361) | async def test_on_message_skips_typing_for_self_message() -> None: function test_on_message_skips_typing_for_denied_sender (line 375) | async def test_on_message_skips_typing_for_denied_sender() -> None: function test_on_message_mention_policy_requires_mx_mentions (line 397) | async def test_on_message_mention_policy_requires_mx_mentions() -> None: function test_on_message_mention_policy_accepts_bot_user_mentions (line 419) | async def test_on_message_mention_policy_accepts_bot_user_mentions() -> ... function test_on_message_mention_policy_allows_direct_room_without_mentions (line 445) | async def test_on_message_mention_policy_allows_direct_room_without_ment... function test_on_message_allowlist_policy_requires_room_id (line 467) | async def test_on_message_allowlist_policy_requires_room_id() -> None: function test_on_message_room_mention_requires_opt_in (line 498) | async def test_on_message_room_mention_requires_opt_in() -> None: function test_on_message_sets_thread_metadata_when_threaded_event (line 528) | async def test_on_message_sets_thread_metadata_when_threaded_event() -> ... function test_on_media_message_downloads_attachment_and_sets_metadata (line 565) | async def test_on_media_message_downloads_attachment_and_sets_metadata( function test_on_media_message_sets_thread_metadata_when_threaded_event (line 618) | async def test_on_media_message_sets_thread_metadata_when_threaded_event( function test_on_media_message_respects_declared_size_limit (line 663) | async def test_on_media_message_respects_declared_size_limit( function test_on_media_message_uses_server_limit_when_smaller_than_local_limit (line 698) | async def test_on_media_message_uses_server_limit_when_smaller_than_loca... function test_on_media_message_handles_download_error (line 734) | async def test_on_media_message_handles_download_error(monkeypatch, tmp_... function test_on_media_message_decrypts_encrypted_media (line 768) | async def test_on_media_message_decrypts_encrypted_media(monkeypatch, tm... function test_on_media_message_handles_decrypt_error (line 811) | async def test_on_media_message_handles_decrypt_error(monkeypatch, tmp_p... function test_send_clears_typing_after_send (line 852) | async def test_send_clears_typing_after_send() -> None: function test_send_uploads_media_and_sends_file_event (line 872) | async def test_send_uploads_media_and_sends_file_event(tmp_path) -> None: function test_send_adds_thread_relates_to_for_thread_metadata (line 901) | async def test_send_adds_thread_relates_to_for_thread_metadata() -> None: function test_send_uses_encrypted_media_payload_in_encrypted_room (line 929) | async def test_send_uses_encrypted_media_payload_in_encrypted_room(tmp_p... function test_send_does_not_parse_attachment_marker_without_media (line 959) | async def test_send_does_not_parse_attachment_marker_without_media(tmp_p... function test_send_passes_thread_relates_to_to_attachment_upload (line 979) | async def test_send_passes_thread_relates_to_to_attachment_upload(monkey... function test_send_workspace_restriction_blocks_external_attachment (line 1023) | async def test_send_workspace_restriction_blocks_external_attachment(tmp... function test_send_handles_upload_exception_and_reports_failure (line 1053) | async def test_send_handles_upload_exception_and_reports_failure(tmp_pat... function test_send_uses_server_upload_limit_when_smaller_than_local_limit (line 1080) | async def test_send_uses_server_upload_limit_when_smaller_than_local_lim... function test_send_blocks_all_outbound_media_when_limit_is_zero (line 1104) | async def test_send_blocks_all_outbound_media_when_limit_is_zero(tmp_pat... function test_send_omits_ignore_unverified_devices_when_e2ee_disabled (line 1127) | async def test_send_omits_ignore_unverified_devices_when_e2ee_disabled()... function test_send_stops_typing_keepalive_task (line 1141) | async def test_send_stops_typing_keepalive_task() -> None: function test_send_progress_keeps_typing_keepalive_running (line 1159) | async def test_send_progress_keeps_typing_keepalive_running() -> None: function test_send_clears_typing_when_send_fails (line 1184) | async def test_send_clears_typing_when_send_fails() -> None: function test_send_adds_formatted_body_for_markdown (line 1199) | async def test_send_adds_formatted_body_for_markdown() -> None: function test_send_adds_formatted_body_for_inline_url_superscript_subscript (line 1220) | async def test_send_adds_formatted_body_for_inline_url_superscript_subsc... function test_send_sanitizes_disallowed_link_scheme (line 1243) | async def test_send_sanitizes_disallowed_link_scheme() -> None: function test_matrix_html_cleaner_strips_event_handlers_and_script_tags (line 1259) | def test_matrix_html_cleaner_strips_event_handlers_and_script_tags() -> ... function test_send_keeps_only_mxc_image_sources (line 1269) | async def test_send_keeps_only_mxc_image_sources() -> None: function test_send_falls_back_to_plaintext_when_markdown_render_fails (line 1285) | async def test_send_falls_back_to_plaintext_when_markdown_render_fails(m... function test_send_keeps_plaintext_only_for_plain_text (line 1304) | async def test_send_keeps_plaintext_only_for_plain_text() -> None: FILE: tests/test_mcp_tool.py class _FakeTextContent (line 15) | class _FakeTextContent: method __init__ (line 16) | def __init__(self, text: str) -> None: function fake_mcp_runtime (line 21) | def fake_mcp_runtime() -> dict[str, object | None]: function _fake_mcp_module (line 26) | def _fake_mcp_module( function _make_wrapper (line 78) | def _make_wrapper(session: object, *, timeout: float = 0.1) -> MCPToolWr... function test_execute_returns_text_blocks (line 88) | async def test_execute_returns_text_blocks() -> None: function test_execute_returns_timeout_message (line 101) | async def test_execute_returns_timeout_message() -> None: function test_execute_handles_server_cancelled_error (line 114) | async def test_execute_handles_server_cancelled_error() -> None: function test_execute_re_raises_external_cancellation (line 126) | async def test_execute_re_raises_external_cancellation() -> None: function test_execute_handles_generic_exception (line 145) | async def test_execute_handles_generic_exception() -> None: function _make_tool_def (line 156) | def _make_tool_def(name: str) -> SimpleNamespace: function _make_fake_session (line 164) | def _make_fake_session(tool_names: list[str]) -> SimpleNamespace: function test_connect_mcp_servers_enabled_tools_supports_raw_names (line 175) | async def test_connect_mcp_servers_enabled_tools_supports_raw_names( function test_connect_mcp_servers_enabled_tools_defaults_to_all (line 195) | async def test_connect_mcp_servers_enabled_tools_defaults_to_all( function test_connect_mcp_servers_enabled_tools_supports_wrapped_names (line 215) | async def test_connect_mcp_servers_enabled_tools_supports_wrapped_names( function test_connect_mcp_servers_enabled_tools_empty_list_registers_none (line 235) | async def test_connect_mcp_servers_enabled_tools_empty_list_registers_none( function test_connect_mcp_servers_enabled_tools_warns_on_unknown_entries (line 255) | async def test_connect_mcp_servers_enabled_tools_warns_on_unknown_entries( FILE: tests/test_memory_consolidation_types.py function _make_messages (line 18) | def _make_messages(message_count: int = 30): function _make_tool_response (line 26) | def _make_tool_response(history_entry, memory_update): class ScriptedProvider (line 43) | class ScriptedProvider(LLMProvider): method __init__ (line 44) | def __init__(self, responses: list[LLMResponse]): method chat (line 49) | async def chat(self, *args, **kwargs) -> LLMResponse: method get_default_model (line 55) | def get_default_model(self) -> str: class TestMemoryConsolidationTypeHandling (line 59) | class TestMemoryConsolidationTypeHandling: method test_string_arguments_work (line 63) | async def test_string_arguments_work(self, tmp_path: Path) -> None: method test_dict_arguments_serialized_to_json (line 84) | async def test_dict_arguments_serialized_to_json(self, tmp_path: Path)... method test_string_arguments_as_raw_json (line 110) | async def test_string_arguments_as_raw_json(self, tmp_path: Path) -> N... method test_no_tool_call_returns_false (line 138) | async def test_no_tool_call_returns_false(self, tmp_path: Path) -> None: method test_skips_when_message_chunk_is_empty (line 154) | async def test_skips_when_message_chunk_is_empty(self, tmp_path: Path)... method test_list_arguments_extracts_first_dict (line 167) | async def test_list_arguments_extracts_first_dict(self, tmp_path: Path... method test_list_arguments_empty_list_returns_false (line 196) | async def test_list_arguments_empty_list_returns_false(self, tmp_path:... method test_list_arguments_non_dict_content_returns_false (line 220) | async def test_list_arguments_non_dict_content_returns_false(self, tmp... method test_missing_history_entry_returns_false_without_writing (line 244) | async def test_missing_history_entry_returns_false_without_writing(sel... method test_missing_memory_update_returns_false_without_writing (line 269) | async def test_missing_memory_update_returns_false_without_writing(sel... method test_null_required_field_returns_false_without_writing (line 294) | async def test_null_required_field_returns_false_without_writing(self,... method test_empty_history_entry_returns_false_without_writing (line 313) | async def test_empty_history_entry_returns_false_without_writing(self,... method test_retries_transient_error_then_succeeds (line 332) | async def test_retries_transient_error_then_succeeds(self, tmp_path: P... method test_consolidation_delegates_to_provider_defaults (line 356) | async def test_consolidation_delegates_to_provider_defaults(self, tmp_... method test_tool_choice_fallback_on_unsupported_error (line 379) | async def test_tool_choice_fallback_on_unsupported_error(self, tmp_pat... method test_tool_choice_fallback_auto_no_tool_call (line 412) | async def test_tool_choice_fallback_auto_no_tool_call(self, tmp_path: ... method test_raw_archive_after_consecutive_failures (line 436) | async def test_raw_archive_after_consecutive_failures(self, tmp_path: ... method test_raw_archive_counter_resets_on_success (line 456) | async def test_raw_archive_counter_resets_on_success(self, tmp_path: P... FILE: tests/test_message_tool.py function test_message_tool_returns_error_when_no_target_context (line 7) | async def test_message_tool_returns_error_when_no_target_context() -> None: FILE: tests/test_message_tool_suppress.py function _make_loop (line 15) | def _make_loop(tmp_path: Path) -> AgentLoop: class TestMessageToolSuppressLogic (line 22) | class TestMessageToolSuppressLogic: method test_suppress_when_sent_to_same_target (line 26) | async def test_suppress_when_sent_to_same_target(self, tmp_path: Path)... method test_not_suppress_when_sent_to_different_target (line 51) | async def test_not_suppress_when_sent_to_different_target(self, tmp_pa... method test_not_suppress_when_no_message_tool_used (line 78) | async def test_not_suppress_when_no_message_tool_used(self, tmp_path: ... method test_progress_hides_internal_reasoning (line 89) | async def test_progress_hides_internal_reasoning(self, tmp_path: Path)... class TestMessageToolTurnTracking (line 119) | class TestMessageToolTurnTracking: method test_sent_in_turn_tracks_same_target (line 121) | def test_sent_in_turn_tracks_same_target(self) -> None: method test_start_turn_resets (line 128) | def test_start_turn_resets(self) -> None: FILE: tests/test_provider_retry.py class ScriptedProvider (line 8) | class ScriptedProvider(LLMProvider): method __init__ (line 9) | def __init__(self, responses): method chat (line 15) | async def chat(self, *args, **kwargs) -> LLMResponse: method get_default_model (line 23) | def get_default_model(self) -> str: function test_chat_with_retry_retries_transient_error_then_succeeds (line 28) | async def test_chat_with_retry_retries_transient_error_then_succeeds(mon... function test_chat_with_retry_does_not_retry_non_transient_error (line 49) | async def test_chat_with_retry_does_not_retry_non_transient_error(monkey... function test_chat_with_retry_returns_final_error_after_retries (line 68) | async def test_chat_with_retry_returns_final_error_after_retries(monkeyp... function test_chat_with_retry_preserves_cancelled_error (line 90) | async def test_chat_with_retry_preserves_cancelled_error() -> None: function test_chat_with_retry_uses_provider_generation_defaults (line 98) | async def test_chat_with_retry_uses_provider_generation_defaults() -> None: function test_chat_with_retry_explicit_override_beats_defaults (line 111) | async def test_chat_with_retry_explicit_override_beats_defaults() -> None: function test_non_transient_error_with_images_retries_without_images (line 148) | async def test_non_transient_error_with_images_retries_without_images() ... function test_non_transient_error_without_images_no_retry (line 168) | async def test_non_transient_error_without_images_no_retry() -> None: function test_image_fallback_returns_error_on_second_failure (line 183) | async def test_image_fallback_returns_error_on_second_failure() -> None: function test_image_fallback_without_meta_uses_default_placeholder (line 198) | async def test_image_fallback_without_meta_uses_default_placeholder() ->... FILE: tests/test_providers_init.py function test_importing_providers_package_is_lazy (line 9) | def test_importing_providers_package_is_lazy(monkeypatch) -> None: function test_explicit_provider_import_still_works (line 29) | def test_explicit_provider_import_still_works(monkeypatch) -> None: FILE: tests/test_qq_channel.py class _FakeApi (line 11) | class _FakeApi: method __init__ (line 12) | def __init__(self) -> None: method post_c2c_message (line 16) | async def post_c2c_message(self, **kwargs) -> None: method post_group_message (line 19) | async def post_group_message(self, **kwargs) -> None: class _FakeClient (line 23) | class _FakeClient: method __init__ (line 24) | def __init__(self) -> None: function test_on_group_message_routes_to_group_chat_id (line 29) | async def test_on_group_message_routes_to_group_chat_id() -> None: function test_send_group_message_uses_plain_text_group_api_with_msg_seq (line 47) | async def test_send_group_message_uses_plain_text_group_api_with_msg_seq... function test_send_c2c_message_uses_plain_text_c2c_api_with_msg_seq (line 74) | async def test_send_c2c_message_uses_plain_text_c2c_api_with_msg_seq() -... function test_send_group_message_uses_markdown_when_configured (line 100) | async def test_send_group_message_uses_markdown_when_configured() -> None: FILE: tests/test_restart_command.py function _make_loop (line 13) | def _make_loop(): class TestRestartCommand (line 31) | class TestRestartCommand: method test_restart_sends_message_and_calls_execv (line 34) | async def test_restart_sends_message_and_calls_execv(self): method test_restart_intercepted_in_run_loop (line 47) | async def test_restart_intercepted_in_run_loop(self): method test_help_includes_restart (line 69) | async def test_help_includes_restart(self): FILE: tests/test_security_network.py function _fake_resolve (line 13) | def _fake_resolve(host: str, results: list[str]): function test_rejects_non_http_scheme (line 26) | def test_rejects_non_http_scheme(): function test_rejects_missing_domain (line 32) | def test_rejects_missing_domain(): function test_blocks_private_ipv4 (line 50) | def test_blocks_private_ipv4(ip: str, label: str): function test_blocks_ipv6_loopback (line 57) | def test_blocks_ipv6_loopback(): function test_allows_public_ip (line 69) | def test_allows_public_ip(): function test_allows_normal_https (line 75) | def test_allows_normal_https(): function test_detects_curl_metadata (line 85) | def test_detects_curl_metadata(): function test_detects_wget_localhost (line 90) | def test_detects_wget_localhost(): function test_allows_normal_curl (line 95) | def test_allows_normal_curl(): function test_no_urls_returns_false (line 100) | def test_no_urls_returns_false(): FILE: tests/test_session_manager_history.py function _assert_no_orphans (line 4) | def _assert_no_orphans(history: list[dict]) -> None: function _tool_turn (line 18) | def _tool_turn(prefix: str, idx: int) -> list[dict]: function test_get_history_drops_orphan_tool_results_when_window_cuts_tool_calls (line 36) | def test_get_history_drops_orphan_tool_results_when_window_cuts_tool_cal... function test_legitimate_tool_pairs_preserved_after_trim (line 52) | def test_legitimate_tool_pairs_preserved_after_trim(): function test_orphan_trim_with_last_consolidated (line 69) | def test_orphan_trim_with_last_consolidated(): function test_no_tool_messages_unchanged (line 89) | def test_no_tool_messages_unchanged(): function test_all_orphan_prefix_stripped (line 102) | def test_all_orphan_prefix_stripped(): function test_empty_session_history (line 118) | def test_empty_session_history(): function test_window_cuts_mid_tool_group (line 126) | def test_window_cuts_mid_tool_group(): FILE: tests/test_skill_creator_scripts.py function test_init_skill_creates_expected_files (line 17) | def test_init_skill_creates_expected_files(tmp_path: Path) -> None: function test_validate_skill_accepts_existing_skill_creator (line 32) | def test_validate_skill_accepts_existing_skill_creator() -> None: function test_validate_skill_rejects_placeholder_description (line 40) | def test_validate_skill_rejects_placeholder_description(tmp_path: Path) ... function test_validate_skill_rejects_root_files_outside_allowed_dirs (line 58) | def test_validate_skill_rejects_root_files_outside_allowed_dirs(tmp_path... function test_package_skill_creates_archive (line 77) | def test_package_skill_creates_archive(tmp_path: Path) -> None: function test_package_skill_rejects_symlink (line 102) | def test_package_skill_rejects_symlink(tmp_path: Path) -> None: FILE: tests/test_slack_channel.py class _FakeAsyncWebClient (line 11) | class _FakeAsyncWebClient: method __init__ (line 12) | def __init__(self) -> None: method chat_postMessage (line 18) | async def chat_postMessage( method files_upload_v2 (line 33) | async def files_upload_v2( method reactions_add (line 48) | async def reactions_add( method reactions_remove (line 63) | async def reactions_remove( function test_send_uses_thread_for_channel_messages (line 80) | async def test_send_uses_thread_for_channel_messages() -> None: function test_send_omits_thread_for_dm_messages (line 103) | async def test_send_omits_thread_for_dm_messages() -> None: function test_send_updates_reaction_when_final_response_sent (line 126) | async def test_send_updates_reaction_when_final_response_sent() -> None: FILE: tests/test_task_cancel.py function _make_loop (line 11) | def _make_loop(): class TestHandleStop (line 30) | class TestHandleStop: method test_stop_no_active_task (line 32) | async def test_stop_no_active_task(self): method test_stop_cancels_active_task (line 42) | async def test_stop_cancels_active_task(self): method test_stop_cancels_multiple_tasks (line 67) | async def test_stop_cancels_multiple_tasks(self): class TestDispatch (line 92) | class TestDispatch: method test_dispatch_processes_and_publishes (line 94) | async def test_dispatch_processes_and_publishes(self): method test_processing_lock_serializes (line 107) | async def test_processing_lock_serializes(self): class TestSubagentCancellation (line 129) | class TestSubagentCancellation: method test_cancel_by_session (line 131) | async def test_cancel_by_session(self): method test_cancel_by_session_no_tasks (line 159) | async def test_cancel_by_session_no_tasks(self): method test_subagent_preserves_reasoning_fields_in_tool_turn (line 170) | async def test_subagent_preserves_reasoning_fields_in_tool_turn(self, ... FILE: tests/test_telegram_channel.py class _FakeHTTPXRequest (line 14) | class _FakeHTTPXRequest: method __init__ (line 17) | def __init__(self, **kwargs) -> None: method clear (line 22) | def clear(cls) -> None: class _FakeUpdater (line 26) | class _FakeUpdater: method __init__ (line 27) | def __init__(self, on_start_polling) -> None: method start_polling (line 30) | async def start_polling(self, **kwargs) -> None: class _FakeBot (line 34) | class _FakeBot: method __init__ (line 35) | def __init__(self) -> None: method get_me (line 40) | async def get_me(self): method set_my_commands (line 44) | async def set_my_commands(self, commands) -> None: method send_message (line 47) | async def send_message(self, **kwargs) -> None: method send_photo (line 50) | async def send_photo(self, **kwargs) -> None: method send_voice (line 53) | async def send_voice(self, **kwargs) -> None: method send_audio (line 56) | async def send_audio(self, **kwargs) -> None: method send_document (line 59) | async def send_document(self, **kwargs) -> None: method send_chat_action (line 62) | async def send_chat_action(self, **kwargs) -> None: method get_file (line 65) | async def get_file(self, file_id: str): class _FakeApp (line 72) | class _FakeApp: method __init__ (line 73) | def __init__(self, on_start_polling) -> None: method add_error_handler (line 79) | def add_error_handler(self, handler) -> None: method add_handler (line 82) | def add_handler(self, handler) -> None: method initialize (line 85) | async def initialize(self) -> None: method start (line 88) | async def start(self) -> None: class _FakeBuilder (line 92) | class _FakeBuilder: method __init__ (line 93) | def __init__(self, app: _FakeApp) -> None: method token (line 99) | def token(self, token: str): method request (line 103) | def request(self, request): method get_updates_request (line 107) | def get_updates_request(self, request): method proxy (line 111) | def proxy(self, _proxy): method get_updates_proxy (line 114) | def get_updates_proxy(self, _proxy): method build (line 117) | def build(self): function _make_telegram_update (line 121) | def _make_telegram_update( function test_start_creates_separate_pools_with_proxy (line 151) | async def test_start_creates_separate_pools_with_proxy(monkeypatch) -> N... function test_start_respects_custom_pool_config (line 183) | async def test_start_respects_custom_pool_config(monkeypatch) -> None: function test_send_text_retries_on_timeout (line 213) | async def test_send_text_retries_on_timeout() -> None: function test_send_text_gives_up_after_max_retries (line 248) | async def test_send_text_gives_up_after_max_retries() -> None: function test_derive_topic_session_key_uses_thread_id (line 274) | def test_derive_topic_session_key_uses_thread_id() -> None: function test_get_extension_falls_back_to_original_filename (line 284) | def test_get_extension_falls_back_to_original_filename() -> None: function test_telegram_group_policy_defaults_to_mention (line 291) | def test_telegram_group_policy_defaults_to_mention() -> None: function test_is_allowed_accepts_legacy_telegram_id_username_formats (line 295) | def test_is_allowed_accepts_legacy_telegram_id_username_formats() -> None: function test_is_allowed_rejects_invalid_legacy_telegram_sender_shapes (line 303) | def test_is_allowed_rejects_invalid_legacy_telegram_sender_shapes() -> N... function test_send_progress_keeps_message_in_topic (line 311) | async def test_send_progress_keeps_message_in_topic() -> None: function test_send_reply_infers_topic_from_message_id_cache (line 329) | async def test_send_reply_infers_topic_from_message_id_cache() -> None: function test_send_remote_media_url_after_security_validation (line 349) | async def test_send_remote_media_url_after_security_validation(monkeypat... function test_send_blocks_unsafe_remote_media_url (line 377) | async def test_send_blocks_unsafe_remote_media_url(monkeypatch) -> None: function test_group_policy_mention_ignores_unmentioned_group_message (line 408) | async def test_group_policy_mention_ignores_unmentioned_group_message() ... function test_group_policy_mention_accepts_text_mention_and_caches_bot_identity (line 430) | async def test_group_policy_mention_accepts_text_mention_and_caches_bot_... function test_group_policy_mention_accepts_caption_mention (line 454) | async def test_group_policy_mention_accepts_caption_mention() -> None: function test_group_policy_mention_accepts_reply_to_bot (line 480) | async def test_group_policy_mention_accepts_reply_to_bot() -> None: function test_group_policy_open_accepts_plain_group_message (line 502) | async def test_group_policy_open_accepts_plain_group_message() -> None: function test_extract_reply_context_no_reply (line 523) | def test_extract_reply_context_no_reply() -> None: function test_extract_reply_context_with_text (line 529) | def test_extract_reply_context_with_text() -> None: function test_extract_reply_context_with_caption_only (line 536) | def test_extract_reply_context_with_caption_only() -> None: function test_extract_reply_context_truncation (line 543) | def test_extract_reply_context_truncation() -> None: function test_extract_reply_context_no_text_returns_none (line 555) | def test_extract_reply_context_no_text_returns_none() -> None: function test_on_message_includes_reply_context (line 563) | async def test_on_message_includes_reply_context() -> None: function test_download_message_media_returns_path_when_download_succeeds (line 586) | async def test_download_message_media_returns_path_when_download_succeeds( function test_download_message_media_uses_file_unique_id_when_available (line 623) | async def test_download_message_media_uses_file_unique_id_when_available( function test_on_message_attaches_reply_to_media_when_available (line 673) | async def test_on_message_attaches_reply_to_media_when_available(monkeyp... function test_on_message_reply_to_media_fallback_when_download_fails (line 722) | async def test_on_message_reply_to_media_fallback_when_download_fails() ... function test_on_message_reply_to_caption_and_media (line 756) | async def test_on_message_reply_to_caption_and_media(monkeypatch, tmp_pa... function test_forward_command_does_not_inject_reply_context (line 805) | async def test_forward_command_does_not_inject_reply_context() -> None: function test_on_help_includes_restart_command (line 826) | async def test_on_help_includes_restart_command() -> None: FILE: tests/test_tool_validation.py class SampleTool (line 8) | class SampleTool(Tool): method name (line 10) | def name(self) -> str: method description (line 14) | def description(self) -> str: method parameters (line 18) | def parameters(self) -> dict[str, Any]: method execute (line 40) | async def execute(self, **kwargs: Any) -> str: function test_validate_params_missing_required (line 44) | def test_validate_params_missing_required() -> None: function test_validate_params_type_and_range (line 50) | def test_validate_params_type_and_range() -> None: function test_validate_params_enum_and_min_length (line 59) | def test_validate_params_enum_and_min_length() -> None: function test_validate_params_nested_object_and_array (line 66) | def test_validate_params_nested_object_and_array() -> None: function test_validate_params_ignores_unknown_fields (line 79) | def test_validate_params_ignores_unknown_fields() -> None: function test_registry_returns_validation_error (line 85) | async def test_registry_returns_validation_error() -> None: function test_exec_extract_absolute_paths_keeps_full_windows_path (line 92) | def test_exec_extract_absolute_paths_keeps_full_windows_path() -> None: function test_exec_extract_absolute_paths_ignores_relative_posix_segments (line 98) | def test_exec_extract_absolute_paths_ignores_relative_posix_segments() -... function test_exec_extract_absolute_paths_captures_posix_absolute_paths (line 104) | def test_exec_extract_absolute_paths_captures_posix_absolute_paths() -> ... function test_exec_extract_absolute_paths_captures_home_paths (line 111) | def test_exec_extract_absolute_paths_captures_home_paths() -> None: function test_exec_extract_absolute_paths_captures_quoted_paths (line 118) | def test_exec_extract_absolute_paths_captures_quoted_paths() -> None: function test_exec_guard_blocks_home_path_outside_workspace (line 125) | def test_exec_guard_blocks_home_path_outside_workspace(tmp_path) -> None: function test_exec_guard_blocks_quoted_home_path_outside_workspace (line 131) | def test_exec_guard_blocks_quoted_home_path_outside_workspace(tmp_path) ... class CastTestTool (line 140) | class CastTestTool(Tool): method __init__ (line 143) | def __init__(self, schema: dict[str, Any]) -> None: method name (line 147) | def name(self) -> str: method description (line 151) | def description(self) -> str: method parameters (line 155) | def parameters(self) -> dict[str, Any]: method execute (line 158) | async def execute(self, **kwargs: Any) -> str: function test_cast_params_string_to_int (line 162) | def test_cast_params_string_to_int() -> None: function test_cast_params_string_to_number (line 174) | def test_cast_params_string_to_number() -> None: function test_cast_params_string_to_bool (line 186) | def test_cast_params_string_to_bool() -> None: function test_cast_params_array_items (line 198) | def test_cast_params_array_items() -> None: function test_cast_params_nested_object (line 211) | def test_cast_params_nested_object() -> None: function test_cast_params_bool_not_cast_to_int (line 231) | def test_cast_params_bool_not_cast_to_int() -> None: function test_cast_params_preserves_empty_string (line 245) | def test_cast_params_preserves_empty_string() -> None: function test_cast_params_bool_string_false (line 257) | def test_cast_params_bool_string_false() -> None: function test_cast_params_bool_string_invalid (line 272) | def test_cast_params_bool_string_invalid() -> None: function test_cast_params_invalid_string_to_int (line 287) | def test_cast_params_invalid_string_to_int() -> None: function test_cast_params_invalid_string_to_number (line 301) | def test_cast_params_invalid_string_to_number() -> None: function test_validate_params_bool_not_accepted_as_number (line 313) | def test_validate_params_bool_not_accepted_as_number() -> None: function test_cast_params_none_values (line 325) | def test_cast_params_none_values() -> None: function test_cast_params_single_value_not_auto_wrapped_to_array (line 353) | def test_cast_params_single_value_not_auto_wrapped_to_array() -> None: function test_exec_always_returns_exit_code (line 371) | async def test_exec_always_returns_exit_code() -> None: function test_exec_head_tail_truncation (line 379) | async def test_exec_head_tail_truncation() -> None: function test_exec_timeout_parameter (line 394) | async def test_exec_timeout_parameter() -> None: function test_exec_timeout_capped_at_max (line 403) | async def test_exec_timeout_capped_at_max() -> None: function test_resolve_type_simple_string (line 414) | def test_resolve_type_simple_string() -> None: function test_resolve_type_union_with_null (line 419) | def test_resolve_type_union_with_null() -> None: function test_resolve_type_only_null (line 424) | def test_resolve_type_only_null() -> None: function test_resolve_type_none_input (line 429) | def test_resolve_type_none_input() -> None: function test_validate_nullable_param_accepts_string (line 434) | def test_validate_nullable_param_accepts_string() -> None: function test_validate_nullable_param_accepts_none (line 446) | def test_validate_nullable_param_accepts_none() -> None: function test_cast_nullable_param_no_crash (line 458) | def test_cast_nullable_param_no_crash() -> None: FILE: tests/test_web_fetch_security.py function _fake_resolve_private (line 14) | def _fake_resolve_private(hostname, port, family=0, type_=0): function _fake_resolve_public (line 18) | def _fake_resolve_public(hostname, port, family=0, type_=0): function test_web_fetch_blocks_private_ip (line 23) | async def test_web_fetch_blocks_private_ip(): function test_web_fetch_blocks_localhost (line 33) | async def test_web_fetch_blocks_localhost(): function test_web_fetch_result_contains_untrusted_flag (line 44) | async def test_web_fetch_result_contains_untrusted_flag(): FILE: tests/test_web_search_tool.py function _tool (line 10) | def _tool(provider: str = "brave", api_key: str = "", base_url: str = ""... function _response (line 14) | def _response(status: int = 200, json: dict | None = None) -> httpx.Resp... function test_brave_search (line 22) | async def test_brave_search(monkeypatch): function test_tavily_search (line 38) | async def test_tavily_search(monkeypatch): function test_searxng_search (line 54) | async def test_searxng_search(monkeypatch): function test_duckduckgo_search (line 68) | async def test_duckduckgo_search(monkeypatch): function test_brave_fallback_to_duckduckgo_when_no_key (line 89) | async def test_brave_fallback_to_duckduckgo_when_no_key(monkeypatch): function test_jina_search (line 106) | async def test_jina_search(monkeypatch): function test_unknown_provider (line 122) | async def test_unknown_provider(): function test_default_provider_is_brave (line 130) | async def test_default_provider_is_brave(monkeypatch): function test_searxng_no_base_url_falls_back (line 142) | async def test_searxng_no_base_url_falls_back(monkeypatch): function test_searxng_invalid_url (line 159) | async def test_searxng_invalid_url():