synapse-cli-agent 0.1.16__tar.gz → 0.1.18__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/.gitignore +2 -1
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/AGENTS.md +52 -22
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/CHANGELOG.md +37 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/PKG-INFO +2 -2
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/README.md +1 -1
- synapse_cli_agent-0.1.18/decode_screenshot.py +26 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/pyproject.toml +1 -1
- synapse_cli_agent-0.1.18/release_body.md +19 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-search-core/Cargo.lock +1 -1
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-search-core/Cargo.toml +1 -1
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-search-core/pyproject.toml +1 -1
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-search-core/src/lib.rs +6 -3
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/app/agent.py +28 -1
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/mcp.py +8 -9
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/content/prompts.py +3 -3
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/mcp_client.py +59 -7
- synapse_cli_agent-0.1.18/src/synapse/observability/debug_inspector.html +19 -0
- synapse_cli_agent-0.1.18/src/synapse/observability/debug_server.py +430 -0
- synapse_cli_agent-0.1.18/src/synapse/observability/llm_debug.py +295 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/backends.py +13 -2
- synapse_cli_agent-0.1.18/src/synapse/runtime/debug_capture_middleware.py +80 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/harness.py +14 -13
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/middleware.py +11 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/tool_ignore.py +13 -6
- synapse_cli_agent-0.1.18/src/synapse/tools/__init__.py +13 -0
- synapse_cli_agent-0.1.18/src/synapse/tools/describe_image.py +92 -0
- synapse_cli_agent-0.1.18/src/synapse/tools/filesystem_search.py +162 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/transcript_blocks.py +0 -28
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/tui.py +52 -7
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/turn_rail_widgets.py +7 -1
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_agent_factory.py +36 -2
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_capabilities.py +7 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_compact_tool_descriptions.py +25 -8
- synapse_cli_agent-0.1.18/tests/test_debug_inspector.py +115 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_describe_image.py +45 -0
- synapse_cli_agent-0.1.18/tests/test_filesystem_search_tools.py +122 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_slash_and_mcp.py +54 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tool_ignore.py +7 -2
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_turn_rail_minimap.py +18 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/uv.lock +6 -6
- synapse_cli_agent-0.1.16/release_body.md +0 -14
- synapse_cli_agent-0.1.16/src/synapse/tools/__init__.py +0 -5
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/.github/workflows/ci.yml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/.github/workflows/docs.yml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/.github/workflows/native-compression-wheels.yml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/.github/workflows/native-search-wheels.yml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/.github/workflows/release.yml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/.python-version +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/.readthedocs.yaml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/agent_upstream.txt +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/cli_upstream.txt +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/agent-development-guide.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/architecture-flow.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/architecture.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/cli.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/codex-session-discovery-phase-1.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/codex-session-import-phase-2.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/config.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/design.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/index.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/install.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/mcp.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/models.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/native-compression-core.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/native-search-core.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/permissions.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/quickstart.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/sessions.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/skills.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/smith_output.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/tool-output-transform.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/tui-cursor-refactor.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/tutorial.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/docs/x-agent-development-guide.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/examples/mcp.example.json +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/examples/models.example.json +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/examples/settings.example.json +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/middleware_upstream.txt +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/mkdocs.yml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-search-core/LICENSE +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-search-core/NOTICE +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-search-core/README.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-search-core/python/synapse_search_core/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/Cargo.lock +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/Cargo.toml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/LICENSE +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/NOTICE +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/README.md +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/pyproject.toml +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/python/synapse_tool_compress_core/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/ccr.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/ccr_in_memory.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/mod.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/relevance/base.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/relevance/bm25.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/signals/keyword_detector.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/signals/line_importance.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/adaptive_sizer.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/anchor_selector.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/code_compressor.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/diff_compressor.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/log_compressor.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/search_compressor.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/analyzer.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/anchors.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/builder.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/classifier.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/classifier.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/compactor.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/formatter.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/ir.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/mod.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/walker.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/config.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/constraints.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/crusher.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/crushers.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/error_keywords.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/field_detect.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/hashing.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/mod.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/observer.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/orchestration.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/outliers.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/planning.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/statistics.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/stats_math.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/traits.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/types.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/rust/synapse-tool-compress-core/src/lib.rs +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/scripts/install.ps1 +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/scripts/release.ps1 +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/slash_cmds_upstream.txt +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/__main__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/app/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/app/agent_md.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/cli.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/compression.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/helpers.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/model.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/result.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/sessions.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/slash_cmds.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/slash_complete.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/commands/theme.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/config.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/content/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/content/input_history.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/content/multimodal.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/content/skills_catalog.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/checkpoint_seed.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/codex_history.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/codex_import.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/codex_sessions.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/describe_image.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/http_clients.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/llm_openai_compat.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/llm_openai_websocket.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/openai_oauth.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/openai_oauth_middleware.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/vision_middleware.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/memory/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/memory/auto_recorder.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/memory/embedder.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/memory/long_term.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/models/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/models/config.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/models/helpers.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/models/profile.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/models/registry.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/models_registry.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/observability/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/observability/startup_trace.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/parallel_subagents.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/planner/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/planner/task_planner.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/rag/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/rag/knowledge_base.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/async_runtime.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/context_compact.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/execute_capture.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/fs_permissions.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/hitl.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/interaction_ledger.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/model_request_compression_middleware.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/pathing.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/safety.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/steer.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/subagents.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/tool_output_eval.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/tool_output_middleware.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/runtime/tool_output_usage_middleware.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/sessions/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/sessions/cancel_repair.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/sessions/session_recap.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/sessions/store.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/sessions/transcript.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/settings/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/settings/config_paths.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/settings/schema.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/subagent_monitor.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/tool_output/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/tool_output/detection.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/tool_output/metrics.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/tool_output/models.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/tool_output/pipeline.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/tool_output/repository.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/tool_output/transformers.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/tools/session_tools.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/answer_divider.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/components/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/components/key_hints.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/components/mcp.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/components/mode.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/components/model.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/components/thread.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/context.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/bottombar/core.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/clipboard.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/base.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/codex_session_list.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/compression_diagnostics.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/git_explore.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/mcp_panel.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/model_picker.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/safety_panel.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/session_list.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/subagent_monitor.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/theme_designer.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/dialogs/theme_picker.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/formatters.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/git_explore/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/git_explore/engine.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/git_explore/provider.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/git_explore/unified.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/rendering.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/selectable_static.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/selectable_text.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/sink.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/steer_widget.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/stream.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/stream_events.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/stream_runtime.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/textual_stream_sink.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/theme.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/timeline.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/tool_blocks.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/components/__init__.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/components/branch.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/components/title.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/components/tool_output.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/components/usage.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/components/workspace.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/context.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/core.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/git_changes_popover.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/git_chrome.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/tool_output_popover.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/topbar/widget.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/turn_rail.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/user_turn.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/user_turn_block.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/ui/welcome.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/synapse.cmd +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/codex_rollouts/duplicate_response_item.jsonl +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/codex_rollouts/incomplete_and_aborted.jsonl +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/codex_rollouts/legacy_compaction.jsonl +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/codex_rollouts/normal_completed.jsonl +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/codex_rollouts/replacement_history.jsonl +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/codex_rollouts/rollback.jsonl +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/codex_rollouts/unsupported_replacement.jsonl +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/sample_repo/calculator.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/sample_repo/test_calculator.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/fixtures/tool_output_eval.json +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_async_checkpointer.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_backends.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_bottombar_registry.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_cancel_repair.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_checkpoint_seed.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_cli.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_cli_codex_import.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_codex_history.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_codex_import.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_codex_sessions.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_codex_sessions_windows.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_config.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_context_compact.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_context_window.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_dag_parallel.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_dialogs.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_git_changes_popover.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_git_chrome.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_git_explore.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_http_clients.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_input_history.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_iteration_abc.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_layered_config.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_mcp_schema.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_mermaid_render.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_middleware_async.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_model_request_compression_middleware.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_model_retry.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_multimodal.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_openai_compat_reasoning.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_openai_oauth.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_openai_oauth_middleware.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_openai_websocket.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_pathing.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_safety.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_session_recap.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_slash_complete.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_startup_defer.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_steer.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_stream_cancel.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_stream_tool_items.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_stream_ui.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_subagent_monitor_dialog.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_subagent_status.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_theme.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_timeline.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tool_intent.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tool_output.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tool_output_eval.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tool_output_slash.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tool_output_usage_middleware.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_topbar_context_ratio.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_topbar_registry.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_transcript.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tui_codex_import.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tui_selection.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_tui_sink.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_usage_restore.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_user_turn_style.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/test_welcome.py +0 -0
- {synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/tests/tool_output_plugin_fixture.py +0 -0
|
@@ -1,27 +1,5 @@
|
|
|
1
1
|
# Synapse 项目协作指南
|
|
2
2
|
|
|
3
|
-
## 用户信息
|
|
4
|
-
- 姓名:肖前程
|
|
5
|
-
- 年龄:35 岁
|
|
6
|
-
- 职业:Java 程序员
|
|
7
|
-
- 当前学习方向:Agent 应用开发
|
|
8
|
-
- 主语言背景:Java → 正在学习 Python/LangChain Agent 生态
|
|
9
|
-
|
|
10
|
-
## 用户信息
|
|
11
|
-
- 姓名:肖前程
|
|
12
|
-
- 年龄:35 岁
|
|
13
|
-
- 职业:Java 程序员
|
|
14
|
-
- 当前学习方向:Agent 应用开发
|
|
15
|
-
- 主语言背景:Java → 正在学习 Python/LangChain Agent 生态
|
|
16
|
-
|
|
17
|
-
## 约定
|
|
18
|
-
- Python 依赖使用 `uv` 管理。
|
|
19
|
-
- 优先小步、可测试的改动。
|
|
20
|
-
- CLI 输出简洁,不使用 emoji。
|
|
21
|
-
- 默认关闭人工审批(自动放行);除非用户要求,不要自行加审批门槛。
|
|
22
|
-
- Backend 仅使用 `LocalShellBackend`,无远程 sandbox。
|
|
23
|
-
- 对用户回复与模型思考过程使用中文;代码标识符/路径/命令可保留原文。
|
|
24
|
-
|
|
25
3
|
## 当前仓库结构
|
|
26
4
|
|
|
27
5
|
| 路径 | 主要职责 |
|
|
@@ -153,3 +131,55 @@ powershell -ExecutionPolicy Bypass -File scripts/release.ps1
|
|
|
153
131
|
```
|
|
154
132
|
|
|
155
133
|
8. 脚本会创建并推送 `v{version}` tag;`.github/workflows/release.yml` 会从 `CHANGELOG.md` 提取同名段落、执行 `uv build` 并创建 GitHub Release。
|
|
134
|
+
|
|
135
|
+
## 10. cdp_take_screenshot 工具使用说明
|
|
136
|
+
|
|
137
|
+
### 10.1 已知限制
|
|
138
|
+
- `filePath` 参数无法写入工作区目录(浏览器沙箱限制),所有指定 `filePath` 的尝试均会报 `Access denied`。
|
|
139
|
+
- 不指定 `filePath` 时,截图以文本格式(含 base64)自动存入 `/large_tool_results/call_xx_xxx`。
|
|
140
|
+
|
|
141
|
+
### 10.2 标准流程:截图 → 解码 → 识图
|
|
142
|
+
|
|
143
|
+
执行完 `cdp_take_screenshot`(不指定 `filePath`)后,按以下步骤操作:
|
|
144
|
+
|
|
145
|
+
#### Step 1 — 编写/复用解码脚本
|
|
146
|
+
如果工作区不存在 `/decode_screenshot.py`,创建它:
|
|
147
|
+
```python
|
|
148
|
+
import base64, re, sys, glob, os
|
|
149
|
+
|
|
150
|
+
files = glob.glob('large_tool_results/call_*')
|
|
151
|
+
if not files:
|
|
152
|
+
print("No screenshot files found")
|
|
153
|
+
sys.exit(1)
|
|
154
|
+
|
|
155
|
+
latest = max(files, key=os.path.getctime)
|
|
156
|
+
print(f"Processing: {latest}")
|
|
157
|
+
|
|
158
|
+
content = open(latest, 'r').read()
|
|
159
|
+
m = re.search(r"data='([^']+)'", content)
|
|
160
|
+
if not m:
|
|
161
|
+
print("No base64 data found")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
|
|
164
|
+
os.makedirs('.tmp', exist_ok=True)
|
|
165
|
+
out = f'.tmp/screenshot_{os.path.getmtime(latest)}.png'
|
|
166
|
+
open(out, 'wb').write(base64.b64decode(m.group(1)))
|
|
167
|
+
print(f"OK -> {out}")
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### Step 2 — 执行解码
|
|
171
|
+
```bash
|
|
172
|
+
python decode_screenshot.py
|
|
173
|
+
# 输出示例: OK -> .tmp/screenshot_1712345678.1234567.png
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Step 3 — 调用识图
|
|
177
|
+
```bash
|
|
178
|
+
# 使用 Step 2 返回的文件名
|
|
179
|
+
describe_image(image_path="/.tmp/screenshot_1712345678.1234567.png", ...)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 10.3 注意事项
|
|
183
|
+
- 每次截图都会在 `/large_tool_results/` 生成一个新文件,解码脚本自动选最新的。
|
|
184
|
+
- 输出到 `/.tmp/` 目录,该目录需加入 `.gitignore`。
|
|
185
|
+
- 不要在对话中手动执行多步解码,应直接调用解码脚本。
|
|
@@ -7,6 +7,43 @@ The release workflow automatically extracts the matching section as release note
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v0.1.18
|
|
11
|
+
|
|
12
|
+
### 新功能
|
|
13
|
+
|
|
14
|
+
- 新增 LLM Debug Inspector(`F11`),实时监控模型通信、工具调用和 token 消耗。
|
|
15
|
+
- Inspector 支持采集开关、跟随最新、按类型筛选(异常/工具/慢调用)、回合折叠和调用详情查看。
|
|
16
|
+
- Inspector 概览栏显示失败率(基于工具级错误检测),工具标签页展示失败工具及原因。
|
|
17
|
+
|
|
18
|
+
### 修复
|
|
19
|
+
|
|
20
|
+
- TUI:`F10` 恢复删除 session 弹框入口;修复鼠标选中与点击复制的冲突,拖选后自动复制。
|
|
21
|
+
|
|
22
|
+
### 工程改进
|
|
23
|
+
|
|
24
|
+
- `DebugCaptureRecord` 增加工具级错误检测(LangChain `ToolMessage.status` + 内容模式)。
|
|
25
|
+
- `_tool_pairs` 返回 `error` 字段,区分 "等待中"(result null)与 "真失败"(有错误内容)。
|
|
26
|
+
- Inspector 前端:失败率仅统计真正失败的工具,"待响应"不计入。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## v0.1.17
|
|
31
|
+
|
|
32
|
+
### 新功能
|
|
33
|
+
|
|
34
|
+
- `find_files` / `search_files` 工具新增 `context_lines`、`case_insensitive`、`head_limit`、`offset` 参数。
|
|
35
|
+
- `search_files` 支持忽略大小写(`case_insensitive`,由 `synapse-search-core` 原生引擎实现)。
|
|
36
|
+
- 支持分页查询(`head_limit` + `offset`),Agent 可按需翻页而非一次性获取全部结果。
|
|
37
|
+
|
|
38
|
+
### 工程改进
|
|
39
|
+
|
|
40
|
+
- 工具的 Pydantic schema 不再定义 `intent` 字段,改由 `build_intent_schema_middleware` 中间件统一管理。
|
|
41
|
+
- 新增 Synapse 自有文件搜索工具 `find_files` / `search_files`,排除 deepagents 内置 `ls`/`glob`/`grep` 工具。
|
|
42
|
+
- 系统提示词中的 `glob`/`grep` 工具名修正为 `find_files` / `search_files`。
|
|
43
|
+
- `synapse-search-core` 升级至 0.1.1(新增 `case_insensitive` 参数)。
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
10
47
|
## v0.1.16
|
|
11
48
|
|
|
12
49
|
### 新功能
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: synapse-cli-agent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.18
|
|
4
4
|
Summary: Local coding agent built on LangChain Deep Agents (LocalShell, no sandbox)
|
|
5
5
|
Project-URL: Homepage, https://github.com/alex8224/synapse-agent
|
|
6
6
|
Project-URL: Repository, https://github.com/alex8224/synapse-agent
|
|
@@ -287,7 +287,7 @@ OpenAI Responses API 同时支持 HTTP/SSE 与普通 LLM WebSocket。profile 中
|
|
|
287
287
|
}
|
|
288
288
|
```
|
|
289
289
|
|
|
290
|
-
`vision_model.model`、`base_url`、`api_key_env` 均可更换;`api_key` 也支持,但建议使用环境变量。`think` 是识图模型独立的思考开关,开启后发送 `thinking: {"type": "enabled"}`;它不影响主模型。默认只处理本地/内存图片;将 `allow_remote_urls` 设为 `true` 才会把消息中的 HTTP(S) 图片 URL
|
|
290
|
+
`vision_model.model`、`base_url`、`api_key_env` 均可更换;`api_key` 也支持,但建议使用环境变量。`think` 是识图模型独立的思考开关,开启后发送 `thinking: {"type": "enabled"}`;它不影响主模型。默认只处理本地/内存图片;将 `allow_remote_urls` 设为 `true` 才会把消息中的 HTTP(S) 图片 URL 转发给识图服务。该服务只用于把图片转换成文字,配置后图片内容会发送到指定服务。主模型 profile 显式配置 `image_input=false` 时,Synapse 还会自动注入 `describe_image` 工具,让 Agent 主动读取并识别工作区内的图片;未配置 `vision_model` 时工具会明确返回不可用。多模态主模型会跳过 middleware,且不会注入该工具。
|
|
291
291
|
|
|
292
292
|
### MCP 服务器配置
|
|
293
293
|
|
|
@@ -251,7 +251,7 @@ OpenAI Responses API 同时支持 HTTP/SSE 与普通 LLM WebSocket。profile 中
|
|
|
251
251
|
}
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
-
`vision_model.model`、`base_url`、`api_key_env` 均可更换;`api_key` 也支持,但建议使用环境变量。`think` 是识图模型独立的思考开关,开启后发送 `thinking: {"type": "enabled"}`;它不影响主模型。默认只处理本地/内存图片;将 `allow_remote_urls` 设为 `true` 才会把消息中的 HTTP(S) 图片 URL
|
|
254
|
+
`vision_model.model`、`base_url`、`api_key_env` 均可更换;`api_key` 也支持,但建议使用环境变量。`think` 是识图模型独立的思考开关,开启后发送 `thinking: {"type": "enabled"}`;它不影响主模型。默认只处理本地/内存图片;将 `allow_remote_urls` 设为 `true` 才会把消息中的 HTTP(S) 图片 URL 转发给识图服务。该服务只用于把图片转换成文字,配置后图片内容会发送到指定服务。主模型 profile 显式配置 `image_input=false` 时,Synapse 还会自动注入 `describe_image` 工具,让 Agent 主动读取并识别工作区内的图片;未配置 `vision_model` 时工具会明确返回不可用。多模态主模型会跳过 middleware,且不会注入该工具。
|
|
255
255
|
|
|
256
256
|
### MCP 服务器配置
|
|
257
257
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import glob
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
# 找到最新的 large_tool_results 文件
|
|
8
|
+
files = glob.glob('large_tool_results/call_*')
|
|
9
|
+
if not files:
|
|
10
|
+
print("No screenshot files found")
|
|
11
|
+
sys.exit(1)
|
|
12
|
+
|
|
13
|
+
latest = max(files, key=os.path.getctime)
|
|
14
|
+
print(f"Processing: {latest}")
|
|
15
|
+
|
|
16
|
+
content = open(latest).read()
|
|
17
|
+
m = re.search(r"data='([^']+)'", content)
|
|
18
|
+
if not m:
|
|
19
|
+
print("No base64 data found")
|
|
20
|
+
sys.exit(1)
|
|
21
|
+
|
|
22
|
+
os.makedirs('.tmp', exist_ok=True)
|
|
23
|
+
ts = str(os.path.getmtime(latest))
|
|
24
|
+
out = f'.tmp/screenshot_{ts}.png'
|
|
25
|
+
open(out, 'wb').write(base64.b64decode(m.group(1)))
|
|
26
|
+
print(f"OK -> {out}")
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## v0.1.18
|
|
2
|
+
|
|
3
|
+
### 新功能
|
|
4
|
+
|
|
5
|
+
- 新增 LLM Debug Inspector(`F11`),实时监控模型通信、工具调用和 token 消耗。
|
|
6
|
+
- Inspector 支持采集开关、跟随最新、按类型筛选(异常/工具/慢调用)、回合折叠和调用详情查看。
|
|
7
|
+
- Inspector 概览栏显示失败率(基于工具级错误检测),工具标签页展示失败工具及原因。
|
|
8
|
+
|
|
9
|
+
### 修复
|
|
10
|
+
|
|
11
|
+
- TUI:`F10` 恢复删除 session 弹框入口;修复鼠标选中与点击复制的冲突,拖选后自动复制。
|
|
12
|
+
|
|
13
|
+
### 工程改进
|
|
14
|
+
|
|
15
|
+
- `DebugCaptureRecord` 增加工具级错误检测(LangChain `ToolMessage.status` + 内容模式)。
|
|
16
|
+
- `_tool_pairs` 返回 `error` 字段,区分 "等待中"(result null)与 "真失败"(有错误内容)。
|
|
17
|
+
- Inspector 前端:失败率仅统计真正失败的工具,"待响应"不计入。
|
|
18
|
+
|
|
19
|
+
---
|
|
@@ -3,7 +3,7 @@ use std::path::{Path, PathBuf};
|
|
|
3
3
|
use std::time::UNIX_EPOCH;
|
|
4
4
|
|
|
5
5
|
use globset::{Glob, GlobSet, GlobSetBuilder};
|
|
6
|
-
use grep_regex::RegexMatcher;
|
|
6
|
+
use grep_regex::{RegexMatcher, RegexMatcherBuilder};
|
|
7
7
|
use grep_searcher::{BinaryDetection, Searcher, SearcherBuilder, Sink, SinkContext, SinkMatch};
|
|
8
8
|
use ignore::WalkBuilder;
|
|
9
9
|
use pyo3::exceptions::PyValueError;
|
|
@@ -121,7 +121,7 @@ fn search_file(
|
|
|
121
121
|
Ok(sink.result)
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
-
#[pyfunction(signature = (base_path, pattern, include_glob=None, max_results=1000, context_lines=0))]
|
|
124
|
+
#[pyfunction(signature = (base_path, pattern, include_glob=None, max_results=1000, context_lines=0, case_insensitive=false))]
|
|
125
125
|
fn grep(
|
|
126
126
|
py: Python<'_>,
|
|
127
127
|
base_path: &str,
|
|
@@ -129,6 +129,7 @@ fn grep(
|
|
|
129
129
|
include_glob: Option<&str>,
|
|
130
130
|
max_results: usize,
|
|
131
131
|
context_lines: usize,
|
|
132
|
+
case_insensitive: bool,
|
|
132
133
|
) -> PyResult<Py<PyDict>> {
|
|
133
134
|
if pattern.is_empty() {
|
|
134
135
|
return Err(PyValueError::new_err("pattern must not be empty"));
|
|
@@ -143,7 +144,9 @@ fn grep(
|
|
|
143
144
|
base_path.display()
|
|
144
145
|
)));
|
|
145
146
|
}
|
|
146
|
-
let matcher =
|
|
147
|
+
let matcher = RegexMatcherBuilder::new()
|
|
148
|
+
.case_insensitive(case_insensitive)
|
|
149
|
+
.build(pattern)
|
|
147
150
|
.map_err(|error| PyValueError::new_err(format!("invalid regex pattern: {error}")))?;
|
|
148
151
|
let include = include_glob
|
|
149
152
|
.map(compile_glob)
|
|
@@ -35,6 +35,7 @@ from synapse.runtime.middleware import (
|
|
|
35
35
|
build_strip_redundant_prompt_blocks,
|
|
36
36
|
build_task_namespace_middleware,
|
|
37
37
|
build_tool_error_recovery_middleware,
|
|
38
|
+
build_tool_exclusion_middleware,
|
|
38
39
|
)
|
|
39
40
|
from synapse.runtime.model_request_compression_middleware import (
|
|
40
41
|
build_model_request_compression_middleware,
|
|
@@ -48,7 +49,11 @@ from synapse.settings import Settings
|
|
|
48
49
|
from synapse.tool_output.pipeline import ToolOutputTransformPipeline
|
|
49
50
|
from synapse.tool_output.repository import ToolOutputRepository
|
|
50
51
|
from synapse.tool_output.transformers import load_transformer_plugins
|
|
51
|
-
from synapse.tools import
|
|
52
|
+
from synapse.tools import (
|
|
53
|
+
build_describe_image_tools,
|
|
54
|
+
build_filesystem_search_tools,
|
|
55
|
+
build_session_tools,
|
|
56
|
+
)
|
|
52
57
|
|
|
53
58
|
|
|
54
59
|
def _build_checkpointer(settings: Settings):
|
|
@@ -228,6 +233,12 @@ def build_coding_agent(
|
|
|
228
233
|
readonly=settings.readonly,
|
|
229
234
|
excluded_tools=settings.excluded_tools,
|
|
230
235
|
)
|
|
236
|
+
# Keep deepagents' built-in ``ls``, ``glob``, and ``grep`` out of model
|
|
237
|
+
# requests. Synapse registers the non-conflicting ``find_files`` and
|
|
238
|
+
# ``search_files`` tools explicitly below.
|
|
239
|
+
model_request_excluded_tools = set(settings.excluded_tools) | {"ls", "glob", "grep"}
|
|
240
|
+
if settings.readonly:
|
|
241
|
+
model_request_excluded_tools.update({"execute", "write_file", "edit_file"})
|
|
231
242
|
|
|
232
243
|
interrupt_on = build_interrupt_on(require_approval=settings.require_approval)
|
|
233
244
|
with span("checkpointer"):
|
|
@@ -279,6 +290,7 @@ def build_coding_agent(
|
|
|
279
290
|
tools: list[Any] = []
|
|
280
291
|
if extra_tools:
|
|
281
292
|
tools.extend(extra_tools)
|
|
293
|
+
tools.extend(build_filesystem_search_tools(backend))
|
|
282
294
|
# 跨会话查阅工具
|
|
283
295
|
try:
|
|
284
296
|
session_tools = build_session_tools(
|
|
@@ -382,6 +394,13 @@ def build_coding_agent(
|
|
|
382
394
|
getattr(selected_profile, "base_url", None) or getattr(settings, "openai_base_url", None),
|
|
383
395
|
)
|
|
384
396
|
vision_config = VisionModelConfig.from_registry(registry, settings)
|
|
397
|
+
tools.extend(
|
|
398
|
+
build_describe_image_tools(
|
|
399
|
+
image_input=getattr(selected_profile, "image_input", None),
|
|
400
|
+
backend=backend,
|
|
401
|
+
config=vision_config,
|
|
402
|
+
)
|
|
403
|
+
)
|
|
385
404
|
|
|
386
405
|
output_repository = ToolOutputRepository(settings.resolved_tool_output_db_path())
|
|
387
406
|
middleware: list[Any] = [
|
|
@@ -392,6 +411,7 @@ def build_coding_agent(
|
|
|
392
411
|
),
|
|
393
412
|
build_model_retry_middleware(),
|
|
394
413
|
build_task_namespace_middleware(),
|
|
414
|
+
build_tool_exclusion_middleware(model_request_excluded_tools),
|
|
395
415
|
]
|
|
396
416
|
transform_enabled = bool(getattr(settings, "enable_tool_output_transform", True))
|
|
397
417
|
try:
|
|
@@ -449,6 +469,13 @@ def build_coding_agent(
|
|
|
449
469
|
|
|
450
470
|
middleware.append(build_openai_oauth_compat_middleware())
|
|
451
471
|
|
|
472
|
+
# Debug capture middleware — innermost, records final provider-ready
|
|
473
|
+
# request/response pairs for the Debug Inspector (F11 / /debug).
|
|
474
|
+
from synapse.observability.llm_debug import get_debug_store
|
|
475
|
+
from synapse.runtime.debug_capture_middleware import build_debug_capture_middleware
|
|
476
|
+
|
|
477
|
+
middleware.append(build_debug_capture_middleware(get_debug_store()))
|
|
478
|
+
|
|
452
479
|
if progress is not None:
|
|
453
480
|
progress("compiling agent graph")
|
|
454
481
|
with span("create_deep_agent"):
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"""MCP slash-command handler and presentation helpers."""
|
|
2
|
+
|
|
2
3
|
from __future__ import annotations
|
|
3
4
|
|
|
4
5
|
import json
|
|
@@ -102,6 +103,7 @@ def _md_tool_list(names: list[str], warnings: list[str] | None = None) -> str:
|
|
|
102
103
|
lines.append(f"\nwarn: {w}")
|
|
103
104
|
return "\n".join(lines)
|
|
104
105
|
|
|
106
|
+
|
|
105
107
|
def handle_mcp(
|
|
106
108
|
args: list[str],
|
|
107
109
|
*,
|
|
@@ -216,8 +218,7 @@ def handle_mcp(
|
|
|
216
218
|
md_lines.append("|---|---|")
|
|
217
219
|
for transport, names in sorted(by_transport.items()):
|
|
218
220
|
md_lines.append(
|
|
219
|
-
f"| {markdown_escape(transport)} | "
|
|
220
|
-
f"{markdown_escape(', '.join(names))} |"
|
|
221
|
+
f"| {markdown_escape(transport)} | {markdown_escape(', '.join(names))} |"
|
|
221
222
|
)
|
|
222
223
|
if result.tools:
|
|
223
224
|
md_lines.append("")
|
|
@@ -291,9 +292,7 @@ def handle_mcp(
|
|
|
291
292
|
loaded = getattr(build_coding_agent, "last_mcp_servers", []) or []
|
|
292
293
|
warnings_list = getattr(build_coding_agent, "last_mcp_warnings", []) or []
|
|
293
294
|
tools = getattr(build_coding_agent, "last_mcp_tool_names", []) or []
|
|
294
|
-
notice = (
|
|
295
|
-
f"mcp '{target}' {'enabled' if changed.enabled else 'disabled'} 路 tools={len(tools)}"
|
|
296
|
-
)
|
|
295
|
+
notice = f"mcp '{target}' {'enabled' if changed.enabled else 'disabled'} tools={len(tools)}"
|
|
297
296
|
lines = [
|
|
298
297
|
f"mcp server '{target}' {'enabled' if changed.enabled else 'disabled'}; agent rebuilt",
|
|
299
298
|
f"loaded servers: {', '.join(loaded) or '(none)'}",
|
|
@@ -325,7 +324,7 @@ def handle_mcp(
|
|
|
325
324
|
return SlashResult(
|
|
326
325
|
handled=True,
|
|
327
326
|
lines=["mcp enabled (run /mcp reload to rebuild agent)"],
|
|
328
|
-
notice="mcp enabled
|
|
327
|
+
notice="mcp enabled reload pending",
|
|
329
328
|
settings_changed=True,
|
|
330
329
|
)
|
|
331
330
|
|
|
@@ -348,7 +347,7 @@ def handle_mcp(
|
|
|
348
347
|
return SlashResult(
|
|
349
348
|
handled=True,
|
|
350
349
|
lines=["mcp disabled; agent rebuilt without MCP tools"],
|
|
351
|
-
notice="mcp disabled
|
|
350
|
+
notice="mcp disabled tools=0",
|
|
352
351
|
agent=new_agent,
|
|
353
352
|
settings_changed=True,
|
|
354
353
|
)
|
|
@@ -372,9 +371,9 @@ def handle_mcp(
|
|
|
372
371
|
warnings = getattr(build_coding_agent, "last_mcp_warnings", []) or []
|
|
373
372
|
tools = getattr(build_coding_agent, "last_mcp_tool_names", []) or []
|
|
374
373
|
enabled_flag = bool(getattr(settings, "enable_mcp", True))
|
|
375
|
-
notice = f"mcp reloaded
|
|
374
|
+
notice = f"mcp reloaded servers={len(loaded)} tools={len(tools)}"
|
|
376
375
|
if not enabled_flag:
|
|
377
|
-
notice = "mcp reloaded
|
|
376
|
+
notice = "mcp reloaded disabled"
|
|
378
377
|
lines = [
|
|
379
378
|
f"agent rebuilt; mcp enabled={getattr(settings, 'enable_mcp', True)}",
|
|
380
379
|
f"loaded servers: {', '.join(loaded) or '(none)'}",
|
|
@@ -46,7 +46,7 @@ Never scan the entire repository without a specific reason.
|
|
|
46
46
|
|
|
47
47
|
## Virtual filesystem
|
|
48
48
|
|
|
49
|
-
File tools such as `
|
|
49
|
+
File tools such as `find_files`, `search_files`, `read_file`, `edit_file`, and `write_file`
|
|
50
50
|
operate on a virtual filesystem rooted at `/`.
|
|
51
51
|
|
|
52
52
|
Valid paths:
|
|
@@ -109,7 +109,7 @@ Do not use generic intent values such as `run tool` or `read_file`.
|
|
|
109
109
|
Search only when required by a clear task.
|
|
110
110
|
Keep searches targeted and avoid unnecessary full-file output.
|
|
111
111
|
|
|
112
|
-
For
|
|
112
|
+
For the `search_files` tool, automatically exclude common build artifacts and caches via the `glob` parameter:
|
|
113
113
|
* `target/`
|
|
114
114
|
* `.venv/`
|
|
115
115
|
* `.node_modules/`
|
|
@@ -133,7 +133,7 @@ Run independent tool calls in parallel within the same turn.
|
|
|
133
133
|
Parallelize when arguments are already known and results do not depend on each other, including:
|
|
134
134
|
|
|
135
135
|
* Multiple file reads
|
|
136
|
-
* `
|
|
136
|
+
* `find_files` and related file reads
|
|
137
137
|
* `execute` and related reads
|
|
138
138
|
* Edits to different files
|
|
139
139
|
* Independent test or lint commands
|
{synapse_cli_agent-0.1.16 → synapse_cli_agent-0.1.18}/src/synapse/integrations/mcp_client.py
RENAMED
|
@@ -13,11 +13,13 @@ import asyncio
|
|
|
13
13
|
import atexit
|
|
14
14
|
import json
|
|
15
15
|
import logging
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
16
18
|
import threading
|
|
17
19
|
from concurrent.futures import Future
|
|
18
20
|
from dataclasses import dataclass, field
|
|
19
21
|
from pathlib import Path
|
|
20
|
-
from typing import Any
|
|
22
|
+
from typing import Any, TextIO
|
|
21
23
|
|
|
22
24
|
logger = logging.getLogger(__name__)
|
|
23
25
|
|
|
@@ -71,6 +73,31 @@ def _expand_mapping(raw: dict[str, Any] | None) -> dict[str, str]:
|
|
|
71
73
|
return out
|
|
72
74
|
|
|
73
75
|
|
|
76
|
+
def _valid_subprocess_stream(stream: TextIO | None) -> TextIO | None:
|
|
77
|
+
"""Return a stream only when its OS descriptor is valid for inheritance."""
|
|
78
|
+
if stream is None or getattr(stream, "closed", False):
|
|
79
|
+
return None
|
|
80
|
+
try:
|
|
81
|
+
fd = stream.fileno()
|
|
82
|
+
os.fstat(fd)
|
|
83
|
+
except (AttributeError, OSError, ValueError):
|
|
84
|
+
return None
|
|
85
|
+
return stream
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _stdio_errlog() -> tuple[TextIO, bool]:
|
|
89
|
+
"""Choose a valid stderr target and report whether the caller owns it.
|
|
90
|
+
|
|
91
|
+
Textual, redirected Windows launchers, and GUI executables can leave
|
|
92
|
+
``sys.stderr`` present while its underlying descriptor is closed. The MCP
|
|
93
|
+
SDK passes this object to subprocess creation, which then raises EBADF.
|
|
94
|
+
"""
|
|
95
|
+
stream = _valid_subprocess_stream(sys.stderr)
|
|
96
|
+
if stream is not None:
|
|
97
|
+
return stream, False
|
|
98
|
+
return open(os.devnull, "w", encoding="utf-8"), True # noqa: SIM115
|
|
99
|
+
|
|
100
|
+
|
|
74
101
|
def _parse_server(raw: dict[str, Any]) -> McpServerConfig:
|
|
75
102
|
name = str(raw.get("name") or "").strip()
|
|
76
103
|
if not name:
|
|
@@ -337,10 +364,11 @@ class _LoopThread:
|
|
|
337
364
|
class _LiveServer:
|
|
338
365
|
config: McpServerConfig
|
|
339
366
|
session: Any
|
|
340
|
-
# Keep transport context managers open for process lifetime.
|
|
367
|
+
# Keep transport context managers and owned streams open for process lifetime.
|
|
341
368
|
transport_cm: Any
|
|
342
369
|
session_cm: Any
|
|
343
370
|
streams: Any = None
|
|
371
|
+
errlog: TextIO | None = None
|
|
344
372
|
|
|
345
373
|
|
|
346
374
|
class McpSessionPool:
|
|
@@ -385,6 +413,8 @@ class McpSessionPool:
|
|
|
385
413
|
await live.transport_cm.__aexit__(None, None, None)
|
|
386
414
|
except Exception: # noqa: BLE001
|
|
387
415
|
pass
|
|
416
|
+
if live.errlog is not None:
|
|
417
|
+
live.errlog.close()
|
|
388
418
|
self._servers.clear()
|
|
389
419
|
|
|
390
420
|
async def _open_stdio(self, server: McpServerConfig) -> _LiveServer:
|
|
@@ -398,16 +428,38 @@ class McpSessionPool:
|
|
|
398
428
|
args=server.args,
|
|
399
429
|
env=server.env or None,
|
|
400
430
|
)
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
431
|
+
errlog, owns_errlog = _stdio_errlog()
|
|
432
|
+
transport_cm = stdio_client(params, errlog=errlog)
|
|
433
|
+
transport_entered = False
|
|
434
|
+
session_cm = None
|
|
435
|
+
session_entered = False
|
|
436
|
+
try:
|
|
437
|
+
read, write = await transport_cm.__aenter__()
|
|
438
|
+
transport_entered = True
|
|
439
|
+
session_cm = ClientSession(read, write)
|
|
440
|
+
session = await session_cm.__aenter__()
|
|
441
|
+
session_entered = True
|
|
442
|
+
await session.initialize()
|
|
443
|
+
except BaseException:
|
|
444
|
+
if session_entered and session_cm is not None:
|
|
445
|
+
try:
|
|
446
|
+
await session_cm.__aexit__(None, None, None)
|
|
447
|
+
except BaseException:
|
|
448
|
+
pass
|
|
449
|
+
if transport_entered:
|
|
450
|
+
try:
|
|
451
|
+
await transport_cm.__aexit__(None, None, None)
|
|
452
|
+
except BaseException:
|
|
453
|
+
pass
|
|
454
|
+
if owns_errlog:
|
|
455
|
+
errlog.close()
|
|
456
|
+
raise
|
|
406
457
|
return _LiveServer(
|
|
407
458
|
config=server,
|
|
408
459
|
session=session,
|
|
409
460
|
transport_cm=transport_cm,
|
|
410
461
|
session_cm=session_cm,
|
|
462
|
+
errlog=errlog if owns_errlog else None,
|
|
411
463
|
)
|
|
412
464
|
|
|
413
465
|
async def _open_http(self, server: McpServerConfig) -> _LiveServer:
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
|
+
<title>Synapse Inspector</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{color-scheme:dark;--bg:#0d131b;--panel:#121b25;--raised:#182431;--hover:#1d2b3a;--line:#29394b;--text:#e7edf4;--muted:#93a4b7;--blue:#68b5f8;--red:#ff8f8f;--amber:#efc36b;--green:#74c69d}*{box-sizing:border-box}body{margin:0;height:100vh;overflow:hidden;background:var(--bg);color:var(--text);font:13px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif}button,input{font:inherit}button{border:0;color:inherit;background:transparent;cursor:pointer}button:focus-visible,input:focus-visible{outline:2px solid var(--blue);outline-offset:2px}.top{height:52px;display:flex;align-items:center;gap:16px;padding:0 16px;border-bottom:1px solid var(--line);background:#101822}.brand{font-size:15px;font-weight:700}.brand span{color:var(--blue)}.capture{display:flex;align-items:center;gap:7px;color:var(--muted)}.switch{width:31px;height:18px;padding:2px;border-radius:10px;background:#465466}.switch i{display:block;width:14px;height:14px;border-radius:50%;background:#edf3f8;transition:.15s}.switch.on{background:var(--green)}.switch.on i{transform:translateX(13px)}.actions{display:flex;align-items:center;gap:4px;margin-left:auto}.btn{min-height:28px;padding:4px 9px;border:1px solid transparent;border-radius:5px;color:var(--muted)}.btn:hover{border-color:var(--line);background:var(--raised);color:var(--text)}.danger:hover{color:var(--red)}#app{display:grid;grid-template-columns:300px minmax(0,1fr);height:calc(100vh - 52px)}#side{display:flex;min-width:0;min-height:0;flex-direction:column;border-right:1px solid var(--line);background:var(--panel)}.side-head{padding:12px;border-bottom:1px solid var(--line)}.side-title{display:flex;justify-content:space-between;color:var(--muted);font-size:11px;font-weight:700}.search{width:100%;height:32px;margin-top:9px;padding:0 9px;border:1px solid var(--line);border-radius:5px;background:#0d151e;color:var(--text)}.filters{display:flex;gap:5px;margin-top:8px}.filter{padding:3px 7px;border:1px solid var(--line);border-radius:4px;color:var(--muted);font-size:11px}.filter.active,.filter:hover{border-color:#3b6d98;background:#1b3044;color:var(--blue)}#records{flex:1;min-height:0;overflow-y:scroll;overflow-x:hidden;padding:6px 0;scrollbar-color:#40546a #111923;scrollbar-width:thin}#records::-webkit-scrollbar{width:9px}#records::-webkit-scrollbar-track{background:#111923}#records::-webkit-scrollbar-thumb{border:2px solid #111923;border-radius:8px;background:#40546a}.turn-group{margin:0}.turn{display:flex;width:100%;align-items:center;gap:7px;padding:8px 12px;color:var(--muted);font-size:10px;font-weight:700;text-align:left;text-transform:uppercase}.turn:hover{background:var(--hover);color:var(--text)}.turn-arrow{width:10px;color:var(--blue);transition:transform .15s}.turn-group.collapsed .turn-arrow{transform:rotate(-90deg)}.turn-count{margin-left:auto;font-weight:500}.turn-group.collapsed .turn-records{display:none}.record{display:block;width:100%;padding:8px 12px;border-left:3px solid transparent;text-align:left}.record:hover{background:var(--hover)}.record.selected{border-left-color:var(--blue);background:#1d3043}.r-top,.r-bottom{display:flex;align-items:center;gap:7px}.r-id{font-weight:700}.r-model{flex:1;overflow:hidden;color:var(--muted);text-overflow:ellipsis;white-space:nowrap}.r-bottom{justify-content:space-between;margin-top:3px;color:var(--muted);font:11px ui-monospace,Consolas,monospace}.bad{color:var(--red)}.warn{color:var(--amber)}.tag{padding:0 5px;border-radius:8px;background:#26384a;color:#b9c8d6}#main{min-width:0;overflow:auto;padding:20px 24px 56px}.empty{display:grid;height:100%;place-content:center;gap:5px;text-align:center;color:var(--muted)}.empty strong{color:var(--text);font-size:16px}.call-head{display:flex;align-items:flex-start;justify-content:space-between;gap:18px}.eyebrow{color:var(--blue);font-size:10px;font-weight:700;text-transform:uppercase}h1{margin:3px 0 2px;font-size:20px}.sub{color:var(--muted);font-size:12px}.metrics{display:grid;grid-template-columns:repeat(6,minmax(80px,1fr));margin:16px 0;border:1px solid var(--line);border-radius:6px;background:var(--panel)}.metric{padding:9px 11px;border-right:1px solid var(--line)}.metric:last-child{border:0}.metric label{display:block;color:var(--muted);font-size:10px}.metric strong{display:block;margin-top:1px;font:600 14px ui-monospace,Consolas,monospace}.tabs{display:flex;gap:2px;border-bottom:1px solid var(--line)}.tab{padding:9px 12px;border-bottom:2px solid transparent;color:var(--muted)}.tab.active{border-bottom-color:var(--blue);color:var(--text)}#view{padding-top:14px}.section{margin-bottom:12px}.section-head{display:flex;align-items:center;margin-bottom:7px}.section-head h2{margin:0;font-size:14px}.section-head .meta{margin-left:8px;color:var(--muted);font-size:11px}.section-head .btn{margin-left:auto}.message,.tool-row,.raw-box{border:1px solid var(--line);border-radius:6px;background:var(--panel)}.message{margin-bottom:8px}.message-head{display:flex;align-items:center;padding:7px 10px;border-bottom:1px solid var(--line)}.role{font-size:11px;font-weight:700;text-transform:uppercase}.message-head .meta{margin-left:8px;color:var(--muted);font-size:11px}.message-head .btn{margin-left:auto}.content,pre{margin:0;overflow:auto;padding:10px;white-space:pre-wrap;overflow-wrap:anywhere;color:#dce6ef;font:12px/1.6 ui-monospace,SFMono-Regular,Consolas,monospace}.content{max-height:340px}.tool-summary{padding:9px 11px;border:1px solid var(--line);border-radius:6px;background:var(--panel);color:var(--muted)}.tool-row{margin-top:7px;overflow:hidden}.tool-toggle{display:grid;width:100%;grid-template-columns:62px 140px minmax(0,1fr);align-items:center;gap:8px;padding:9px 11px;text-align:left}.tool-toggle:hover{background:var(--hover)}.status{font-size:11px}.status.ok{color:var(--green)}.status.missing{color:var(--amber)}.tool-name{overflow:hidden;color:var(--text);font-weight:650;text-overflow:ellipsis;white-space:nowrap}.tool-intent{overflow:hidden;color:var(--muted);text-overflow:ellipsis;white-space:nowrap}.tool-detail{display:none;grid-template-columns:1fr 1fr;gap:1px;border-top:1px solid var(--line);background:var(--line)}.tool-row.open .tool-detail{display:grid}.tool-side{min-width:0;background:#101821}.tool-label{padding:6px 10px;border-bottom:1px solid var(--line);color:var(--muted);font-size:10px;font-weight:700}.tool-side pre{max-height:300px}.raw-box pre{max-height:65vh}.notice{margin-bottom:10px;padding:9px 11px;border-left:3px solid var(--amber);background:#2a251a;color:#dbcda9}.new-records{display:none;color:var(--blue)}@media(max-width:850px){#app{grid-template-columns:250px minmax(0,1fr)}.metrics{grid-template-columns:repeat(3,1fr)}.metric:nth-child(3),.metric:nth-child(6){border-right:0}.tool-detail{grid-template-columns:1fr}}@media(max-width:650px){#app{display:block}#side{display:none}#main{padding:16px}.metrics{grid-template-columns:repeat(2,1fr)}.metric:nth-child(even){border-right:0}}
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<header class="top"><div class="brand"><span>//</span> Synapse Inspector</div><button class="capture" id="capture" title="开启或暂停模型通信采集">采集 <span class="switch"><i></i></span><strong id="state">关闭</strong></button><div class="actions"><button class="btn new-records" id="newRecords"></button><label class="btn"><input id="follow" type="checkbox" checked> 跟随最新</label><button class="btn" id="refresh">刷新</button><button class="btn danger" id="clear">清空</button></div></header>
|
|
13
|
+
<div id="app"><aside id="side"><div class="side-head"><div class="side-title"><span>调用记录</span><span id="count">0 条</span></div><input class="search" id="search" type="search" placeholder="筛选模型、工具、回合或错误"><div class="filters"><button class="filter active" data-filter="all">全部</button><button class="filter" data-filter="error">异常</button><button class="filter" data-filter="tool">工具</button><button class="filter" data-filter="slow">慢调用</button></div></div><div id="records"></div></aside><main id="main"><div class="empty"><strong>等待通信记录</strong><span>开启采集并运行一个回合后,在这里检查模型调用。</span></div></main></div>
|
|
14
|
+
<script>
|
|
15
|
+
const $=id=>document.getElementById(id);let records=[],selected=-1,loaded=null,filter='all',tab='overview',timer,newCount=0;const collapsedTurns=new Set;const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));const dump=v=>JSON.stringify(v,null,2);const ms=v=>v>=1000?(v/1000).toFixed(2)+'s':Math.round(v||0)+'ms';const tok=v=>Number(v||0).toLocaleString();async function api(url,options){try{const response=await fetch(url,options);return response.ok?response.json():null}catch(_){return null}}function copy(text,button){navigator.clipboard?.writeText(text).then(()=>{const old=button.textContent;button.textContent='已复制';setTimeout(()=>button.textContent=old,900)}).catch(()=>{})}function visibleRows(){const query=$('search').value.trim().toLowerCase();return records.filter(r=>(filter==='all'||filter==='error'&&r.error||filter==='tool'&&r.has_tools||filter==='slow'&&r.duration_ms>=10000)&&(!query||`${r.turn_index} ${r.model_call_index} ${r.model_name} ${r.provider} ${r.error||''} ${r.tool_names||''}`.toLowerCase().includes(query)))}function drawList(){const out=$('records'),rows=visibleRows(),groups=new Map,scrollTop=out.scrollTop;out.innerHTML='';$('count').textContent=`${rows.length} / ${records.length} 条`;for(const record of rows){if(!groups.has(record.turn_index))groups.set(record.turn_index,[]);groups.get(record.turn_index).push(record)}for(const [turn,items] of groups){const group=document.createElement('section'),collapsed=collapsedTurns.has(turn);group.className='turn-group'+(collapsed?' collapsed':'');group.innerHTML=`<button class="turn" aria-expanded="${!collapsed}"><span class="turn-arrow">▼</span><span>回合 ${turn}</span><span class="turn-count">${items.length} 次调用</span></button><div class="turn-records"></div>`;const container=group.querySelector('.turn-records');for(const r of items){const button=document.createElement('button'),tools=r.tool_count?`<span class="tag">${r.tool_count} 工具</span>`:'';button.className='record'+(r.index===selected?' selected':'');button.innerHTML=`<div class="r-top"><span class="r-id ${r.error?'bad':''}">#${r.model_call_index}</span><span class="r-model">${esc(r.model_name)}</span>${r.error?'<span class="bad">失败</span>':''}</div><div class="r-bottom"><span class="${r.duration_ms>=10000?'warn':''}">${ms(r.duration_ms)}</span><span>${tok(r.usage?.input_tokens)} → ${tok(r.usage?.output_tokens)}</span>${tools}</div>`;button.onclick=()=>select(r.index);container.appendChild(button)}group.querySelector('.turn').onclick=()=>{if(collapsedTurns.has(turn))collapsedTurns.delete(turn);else collapsedTurns.add(turn);drawList()};out.appendChild(group)}out.scrollTop=scrollTop}function messageCard(message){const text=message.content_full||message.content_preview||'',hasCalls=(message.tool_calls||[]).length;let body=text&&!looksLikeRawCalls(text)?`<div class="content">${esc(text)}</div>`:'';if(!body&&hasCalls)body=`<div class="tool-summary">模型发起 ${hasCalls} 个结构化工具调用。详细参数请在“工具”标签查看。</div>`;if(!body)body='<div class="tool-summary">此消息没有可展示的文本内容。</div>';return `<article class="message"><div class="message-head"><span class="role">${esc(message.role||'unknown')}</span><span class="meta">${message.name?' / '+esc(message.name):''} ${tok(message.estimated_tokens)} tokens${hasCalls?' · '+hasCalls+' 工具':''}</span><button class="btn copy-message" data-text="${encodeURIComponent(text)}">复制文本</button></div>${body}</article>`}function looksLikeRawCalls(text){return text.includes("'type': 'function_call'")||text.includes('"type":"function_call"')}function section(title,meta,body,raw){return `<section class="section"><div class="section-head"><h2>${title}</h2><span class="meta">${meta}</span>${raw?'<button class="btn copy-section">复制 JSON</button>':''}</div>${body}</section>`}function intentOf(pair){try{const value=typeof pair.args==='string'?JSON.parse(pair.args):pair.args;return value?.intent||value?.command||value?.file_path||value?.pattern||''}catch(_){const match=String(pair.args||'').match(/["']intent["']\s*:\s*["']([^"']+)/);return match?.[1]||''}}function toolsView(pairs){if(!pairs.length)return '<div class="empty"><strong>本次调用没有关联工具</strong><span>这里只展示本次请求新增的工具结果和本次响应发起的工具调用。</span></div>';const failed=pairs.filter(p=>p.error===true);let failHtml='';if(failed.length)failHtml=`<div class="notice bad">${failed.length} 个工具失败:${failed.map(p=>esc(p.name)+' — '+esc((p.result||'').slice(0,100))).join('<br>')}</div>`;return `${failHtml}<div class="notice">默认折叠参数和结果;缺失响应的工具会优先展开。</div>${pairs.map((pair,index)=>`<article class="tool-row ${pair.result==null?'open':''}"><button class="tool-toggle"><span class="status ${pair.error===true?'bad':pair.result==null?'missing':'ok'}">${pair.error===true?'失败':pair.result==null?'待响应':'已完成'}</span><span class="tool-name">${esc(pair.name)}</span><span class="tool-intent">${esc(intentOf(pair)||pair.id)}</span></button><div class="tool-detail"><div class="tool-side"><div class="tool-label">调用参数</div><pre>${esc(pair.args||'{}')}</pre></div><div class="tool-side"><div class="tool-label">工具响应</div><pre>${esc(pair.result??'尚未捕获对应工具响应')}</pre></div></div></article>`).join('')}`}
|
|
16
|
+
function renderView(record){const req=record.request_messages||[],delta=req.slice(record.request_delta_start||0),resp=record.response_messages||[],pairs=record.tool_pairs||[];let html;if(tab==='overview'){const directCalls=resp.reduce((n,m)=>n+(m.tool_calls||[]).length,0),results=delta.filter(m=>m.role==='tool').length;html=section('本次请求新增',`${delta.length} 条 · 约 ${tok(delta.reduce((n,m)=>n+(m.estimated_tokens||0),0))} tokens`,delta.length?delta.map(messageCard).join(''):'<div class="tool-summary">本次调用沿用同回合上下文,没有新增请求消息。</div>',dump(delta))+section('模型输出',`${resp.length} 条消息 · ${directCalls} 个工具调用`,resp.length?resp.map(messageCard).join(''):'<div class="tool-summary">模型没有返回消息。</div>',dump(resp))+section('执行摘要',`${results} 条工具结果输入 · ${directCalls} 个新工具调用`,`<div class="tool-summary">本次调用接收了 ${results} 条工具结果,并发起 ${directCalls} 个后续工具调用。切换到“工具”查看结构化参数和结果。</div>`)}else if(tab==='request'){html=section('请求增量',`${delta.length} 新增 / ${req.length} 总消息`,delta.map(messageCard).join('')||'<div class="tool-summary">没有新增消息。</div>',dump(delta))+section('完整请求',`${req.length} 条消息`,req.map(messageCard).join(''),dump(req))}else if(tab==='response'){html=section('响应消息',`${resp.length} 条 · ${tok(record.usage?.output_tokens)} output tokens`,resp.map(messageCard).join('')||'<div class="tool-summary">模型没有返回消息。</div>',dump(resp))}else if(tab==='tools'){html=toolsView(pairs)}else{html='<div class="raw-box"><div class="tool-summary">正在加载捕获快照…</div></div>';loadRaw(record)}$('view').innerHTML=html;bindView(record)}function bindView(record){$('view').querySelectorAll('.tool-toggle').forEach(button=>button.onclick=()=>button.parentElement.classList.toggle('open'));$('view').querySelectorAll('.copy-message').forEach(button=>button.onclick=()=>copy(decodeURIComponent(button.dataset.text),button));$('view').querySelectorAll('.copy-section').forEach(button=>button.onclick=()=>{const title=button.parentElement.querySelector('h2').textContent;let value=title.includes('完整')?record.request_messages:title.includes('响应')?record.response_messages:(record.request_messages||[]).slice(record.request_delta_start||0);copy(dump(value),button)})}async function loadRaw(record){const raw=await api('/api/records/'+selected+'/raw');if(tab!=='raw'||loaded!==record)return;const box=$('view');box.innerHTML=`<section class="section"><div class="section-head"><h2>捕获快照</h2><span class="meta">按需加载</span><button class="btn" id="copyRaw">复制 JSON</button></div><div class="raw-box"><pre>${esc(dump(raw))}</pre></div></section>`;$('copyRaw').onclick=()=>copy(dump(raw),$('copyRaw'))}function drawDetail(record){if(!record){$('main').innerHTML='<div class="empty"><strong>等待通信记录</strong><span>开启采集并运行一个回合后,在这里检查模型调用。</span></div>';return}const pairs=record.tool_pairs||[],delta=(record.request_messages||[]).slice(record.request_delta_start||0),newTokens=delta.reduce((n,m)=>n+(m.estimated_tokens||0),0),tools=pairs.length,failed=tools?pairs.filter(p=>p.error===true).length:0,failRate=tools?Math.round(failed/tools*100)+'%':'—',status=record.error?'失败':'已完成';$('main').innerHTML=`<div class="call-head"><div><div class="eyebrow">模型通信 / 回合 ${record.turn_index}</div><h1>调用 #${record.model_call_index} <span class="${record.error?'bad':''}">${status}</span></h1><div class="sub">${esc(record.provider)} · ${esc(record.model_name)} · ${new Date((record.started_at||0)*1000).toLocaleString()}</div></div><button class="btn" id="copyRawTop">复制原始记录</button></div><div class="metrics"><div class="metric"><label>耗时</label><strong>${ms(record.duration_ms)}</strong></div><div class="metric"><label>总输入</label><strong>${tok(record.usage?.input_tokens)}</strong></div><div class="metric"><label>本次新增</label><strong>+${tok(newTokens)}</strong></div><div class="metric"><label>输出</label><strong>${tok(record.usage?.output_tokens)}</strong></div><div class="metric"><label>关联工具</label><strong>${tools}</strong></div><div class="metric"><label>失败率</label><strong>${failRate}</strong></div></div>${record.error?`<div class="notice bad">${esc(record.error)}</div>`:''}<nav class="tabs">${[['overview','概览'],['request','请求'],['response','响应'],['tools',`工具 ${tools}`],['raw','原始']].map(([id,label])=>`<button class="tab ${tab===id?'active':''}" data-tab="${id}">${label}</button>`).join('')}</nav><div id="view"></div>`;$('main').querySelectorAll('.tab').forEach(button=>button.onclick=()=>{tab=button.dataset.tab;drawDetail(record)});$('copyRawTop').onclick=async()=>{const raw=await api('/api/records/'+selected+'/raw');if(raw)copy(dump(raw),$('copyRawTop'))};renderView(record)}async function select(index){selected=index;drawList();$('main').innerHTML='<div class="empty"><strong>正在加载调用详情</strong></div>';const record=await api('/api/records/'+index);if(selected===index&&record){loaded=record;drawDetail(record)}}async function refresh(forceFollow=false){const [status,next]=await Promise.all([api('/api/status'),api('/api/records')]);if(status){$('capture').querySelector('.switch').classList.toggle('on',!!status.enabled);$('state').textContent=status.enabled?'开启':'关闭'}if(!next)return;const previousCount=records.length,hadSelection=selected>=0;records=next;drawList();if(records.length>previousCount&&hadSelection&&!$('follow').checked){newCount+=records.length-previousCount;$('newRecords').style.display='block';$('newRecords').textContent=`${newCount} 条新记录`}if((!hadSelection||forceFollow||$('follow').checked&&records.length>previousCount)&&records.length)select(records.at(-1).index);else if(selected>=records.length){selected=-1;loaded=null;drawDetail(null)}}$('capture').onclick=async()=>{await api('/api/toggle',{method:'POST'});refresh()};$('refresh').onclick=()=>refresh(false);$('newRecords').onclick=()=>{newCount=0;$('newRecords').style.display='none';refresh(true)};$('clear').onclick=async()=>{if(records.length&&confirm('清空全部已采集的通信记录?')){await api('/api/clear',{method:'POST'});records=[];selected=-1;loaded=null;drawList();drawDetail(null)}};$('search').oninput=drawList;document.querySelectorAll('.filter').forEach(button=>button.onclick=()=>{filter=button.dataset.filter;document.querySelectorAll('.filter').forEach(item=>item.classList.toggle('active',item===button));drawList()});document.addEventListener('visibilitychange',()=>{clearInterval(timer);if(!document.hidden)timer=setInterval(refresh,1500)});refresh();timer=setInterval(refresh,1500);
|
|
17
|
+
</script>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|