synapse-cli-agent 0.1.24__tar.gz → 0.1.26__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.24 → synapse_cli_agent-0.1.26}/.gitignore +7 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/CHANGELOG.md +36 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/Dockerfile.web +1 -1
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/PKG-INFO +1 -1
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/config.md +1 -1
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/docker-web.md +38 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/sessions.md +8 -4
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/pyproject.toml +1 -1
- synapse_cli_agent-0.1.26/release_body.md +19 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/app/agent.py +17 -6
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/cli.py +177 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/goals/middleware.py +14 -3
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/observability/debug_inspector.html +3 -3
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/observability/debug_server.py +118 -11
- synapse_cli_agent-0.1.26/src/synapse/observability/heap_dump.py +294 -0
- synapse_cli_agent-0.1.26/src/synapse/sessions/transcript_projection.py +438 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/git_explore.py +4 -15
- synapse_cli_agent-0.1.26/src/synapse/ui/textual_lifecycle.py +20 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/textual_stream_sink.py +6 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/tui.py +316 -89
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/turn_rail_widgets.py +30 -7
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/web.py +10 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_debug_inspector.py +71 -1
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_dialogs.py +51 -0
- synapse_cli_agent-0.1.26/tests/test_heap_dump.py +49 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_textual_stream_sink.py +12 -0
- synapse_cli_agent-0.1.26/tests/test_transcript_projection.py +151 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tui_selection.py +100 -3
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_turn_rail_minimap.py +29 -2
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/uv.lock +1 -1
- synapse_cli_agent-0.1.24/release_body.md +0 -14
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/.dockerignore +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/.github/workflows/ci.yml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/.github/workflows/docs.yml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/.github/workflows/native-compression-wheels.yml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/.github/workflows/native-core-tool-wheels.yml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/.github/workflows/release.yml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/.python-version +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/.readthedocs.yaml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/AGENTS.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/LICENSE +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/README.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/agent-studio-prototype/favicon.svg +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/agent-studio-prototype/index.html +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/agent-studio-prototype/script.js +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/agent-studio-prototype/styles.css +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/agent_upstream.txt +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/cli_upstream.txt +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/debug_compact.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/decode_screenshot.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/agent-development-guide.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/architecture-flow.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/architecture.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/cli.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/codex-session-discovery-phase-1.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/codex-session-import-phase-2.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/design.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/index.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/install.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/mcp.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/models.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/native-compression-core.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/native-core-tool.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/permissions.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/quickstart.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/skills.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/smith_output.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/subagent-architecture-survey.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/tool-output-transform.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/tui-cursor-refactor.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/tutorial.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/docs/x-agent-development-guide.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/examples/mcp.example.json +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/examples/models.example.json +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/examples/settings.example.json +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/middleware_upstream.txt +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/mkdocs.yml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/Cargo.lock +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/Cargo.toml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/LICENSE +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/NOTICE +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/README.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/pyproject.toml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/python/synapse_core_tool/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/src/file_access.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/src/file_tools.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/src/lib.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/src/search.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-core-tool/uv.lock +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/Cargo.lock +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/Cargo.toml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/LICENSE +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/NOTICE +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/README.md +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/pyproject.toml +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/python/synapse_tool_compress_core/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/ccr.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/ccr_in_memory.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/mod.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/relevance/base.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/relevance/bm25.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/signals/keyword_detector.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/signals/line_importance.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/adaptive_sizer.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/anchor_selector.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/code_compressor.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/diff_compressor.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/log_compressor.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/search_compressor.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/analyzer.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/anchors.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/builder.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/classifier.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/classifier.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/compactor.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/formatter.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/ir.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/mod.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/walker.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/config.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/constraints.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/crusher.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/crushers.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/error_keywords.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/field_detect.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/hashing.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/mod.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/observer.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/orchestration.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/outliers.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/planning.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/statistics.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/stats_math.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/traits.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/types.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/rust/synapse-tool-compress-core/src/lib.rs +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/scripts/install.ps1 +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/scripts/release.ps1 +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/scripts/serve_web.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/slash_cmds_upstream.txt +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/__main__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/app/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/app/agent_md.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/compression.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/goal.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/helpers.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/mcp.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/model.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/result.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/sessions.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/slash_cmds.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/slash_complete.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/commands/theme.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/config.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/content/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/content/input_history.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/content/multimodal.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/content/prompts.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/content/skills_catalog.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/goals/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/goals/model.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/goals/runtime.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/goals/steering.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/goals/store.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/goals/tools.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/checkpoint_seed.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/codex_history.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/codex_import.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/codex_sessions.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/describe_image.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/http_clients.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/llm_openai_compat.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/llm_openai_websocket.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/mcp_client.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/openai_oauth.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/openai_oauth_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/openai_usage.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/integrations/vision_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/memory/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/memory/auto_recorder.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/memory/embedder.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/memory/long_term.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/models/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/models/config.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/models/helpers.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/models/profile.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/models/registry.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/models_registry.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/observability/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/observability/llm_debug.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/observability/startup_trace.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/parallel_subagents.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/planner/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/planner/task_planner.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/projects/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/projects/catalog.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/rag/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/rag/knowledge_base.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/async_runtime.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/backends.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/context_compact.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/debug_capture_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/execute_capture.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/filesystem_tool_prompt_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/fs_permissions.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/harness.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/hitl.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/interaction_ledger.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/model_request_compression_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/pathing.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/safety.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/session_prewarm.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/steer.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/subagent_routing.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/subagents.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/tool_ignore.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/tool_output_eval.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/tool_output_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/tool_output_usage_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/runtime/tool_response_truncate_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/sessions/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/sessions/cancel_repair.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/sessions/search_index.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/sessions/session_recap.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/sessions/store.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/sessions/summary.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/sessions/transcript.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/settings/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/settings/config_paths.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/settings/schema.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/subagent_monitor.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/subagents.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tool_output/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tool_output/detection.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tool_output/metrics.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tool_output/models.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tool_output/pipeline.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tool_output/repository.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tool_output/transformers.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tools/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tools/describe_image.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tools/filesystem_patch.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tools/filesystem_search.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/tools/session_tools.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/answer_divider.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/codex_usage.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/fast.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/goal.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/key_hints.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/mcp.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/mode.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/model.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/components/thread.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/context.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/bottombar/core.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/clipboard.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/base.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/codex_reset.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/codex_session_list.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/compression_diagnostics.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/mcp_panel.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/model_picker.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/safety_panel.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/session_list.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/subagent_monitor.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/theme_designer.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/dialogs/theme_picker.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/formatters.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/git_explore/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/git_explore/engine.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/git_explore/provider.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/git_explore/unified.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/rendering.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/selectable_static.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/selectable_text.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/sink.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/steer_widget.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/stream.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/stream_events.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/stream_runtime.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/theme.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/timeline.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/tool_blocks.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/components/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/components/branch.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/components/title.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/components/tool_output.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/components/usage.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/components/workspace.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/context.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/core.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/git_changes_popover.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/git_chrome.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/tool_output_popover.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/topbar/widget.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/transcript_blocks.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/turn_rail.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/user_turn.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/user_turn_block.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/ui/welcome.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/synapse.cmd +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/__init__.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/codex_rollouts/duplicate_response_item.jsonl +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/codex_rollouts/incomplete_and_aborted.jsonl +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/codex_rollouts/legacy_compaction.jsonl +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/codex_rollouts/normal_completed.jsonl +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/codex_rollouts/replacement_history.jsonl +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/codex_rollouts/rollback.jsonl +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/codex_rollouts/unsupported_replacement.jsonl +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/sample_repo/calculator.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/sample_repo/test_calculator.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/fixtures/tool_output_eval.json +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_agent_factory.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_async_checkpointer.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_backends.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_bottombar_registry.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_cancel_repair.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_capabilities.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_checkpoint_seed.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_cli.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_cli_codex_import.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_codex_history.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_codex_import.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_codex_sessions.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_codex_sessions_windows.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_compact_tool_descriptions.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_config.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_context_compact.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_context_window.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_dag_parallel.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_debug_capture_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_describe_image.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_filesystem_patch_tool.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_filesystem_search_tools.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_git_changes_popover.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_git_chrome.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_git_explore.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_goals.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_http_clients.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_input_history.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_iteration_abc.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_layered_config.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_mcp_schema.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_mermaid_render.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_middleware_async.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_model_request_compression_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_model_retry.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_multimodal.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_openai_compat_reasoning.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_openai_oauth.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_openai_oauth_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_openai_usage.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_openai_websocket.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_pathing.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_project_catalog.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_prompts.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_safety.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_search_index.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_session_prewarm.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_session_recap.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_session_summary.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_slash_and_mcp.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_slash_complete.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_startup_defer.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_steer.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_stream_cancel.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_stream_tool_items.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_stream_ui.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_subagent_monitor_dialog.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_subagent_status.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_theme.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_timeline.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tool_ignore.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tool_intent.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tool_output.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tool_output_eval.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tool_output_slash.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tool_output_usage_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tool_response_truncate_middleware.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_topbar_context_ratio.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_topbar_registry.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_transcript.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_tui_codex_import.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_usage_restore.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_user_turn_style.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/test_welcome.py +0 -0
- {synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/tests/tool_output_plugin_fixture.py +0 -0
|
@@ -7,6 +7,42 @@ The release workflow automatically extracts the matching section as release note
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v0.1.26
|
|
11
|
+
|
|
12
|
+
### 新增功能
|
|
13
|
+
|
|
14
|
+
- 恢复 `synapse run` 单次执行命令:headless 跑完一个任务后退出,支持 `-w/--workspace`、`-m/--model`、`--readonly`、`--thread-id`、`--debug`、`--stream/--no-stream`;适配 async-only 模型客户端(`ainvoke`)。
|
|
15
|
+
- `build_coding_agent` 新增 `backend=` 与 `system_prompt=` 注入参数,支持评测 harness 将 agent 桥接到远程/容器后端并覆盖默认编码提示。
|
|
16
|
+
- TUI 会话转录改为分页渲染(compact projection),长对话浏览性能提升。
|
|
17
|
+
- 新增堆转储导出(`observability/heap_dump.py`),便于排查长会话内存问题。
|
|
18
|
+
|
|
19
|
+
### 修复
|
|
20
|
+
|
|
21
|
+
- 修复 TUI 切换会话时未释放上一会话资源(stream sink / rail widgets)的问题。
|
|
22
|
+
- 修复 `enable_goals=False` 时 goal 记账 disabled middleware 构造崩溃(`goal_middleware_disabled() takes no arguments`)。
|
|
23
|
+
|
|
24
|
+
### 工程改进
|
|
25
|
+
|
|
26
|
+
- Git Explore 对话框与 turn rail 相关组件同步更新,补充对应单测(transcript projection、heap dump、selection、dialogs 等)。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## v0.1.25
|
|
31
|
+
|
|
32
|
+
### 新增功能
|
|
33
|
+
|
|
34
|
+
- `synapse-web` 新增 `--public-url` 参数:指定 `textual-serve` 生成的页面 WebSocket 与静态资源地址,支持 nginx/TLS 反向代理场景(`https://` 自动推导 `wss://`)。
|
|
35
|
+
|
|
36
|
+
### 修复
|
|
37
|
+
|
|
38
|
+
- 修复容器经 nginx 反向代理后页面内 WebSocket/静态资源地址使用 `http://<host>:<port>` 导致浏览器无法连接的问题。
|
|
39
|
+
|
|
40
|
+
### 工程改进
|
|
41
|
+
|
|
42
|
+
- Docker 部署文档补充容器仅绑定回环端口 + nginx 443 反代的配置示例。
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
10
46
|
## v0.1.24
|
|
11
47
|
|
|
12
48
|
### 新增功能
|
|
@@ -15,7 +15,7 @@ ARG SYNAPSE_MODE=pypi
|
|
|
15
15
|
|
|
16
16
|
# pypi mode: install the released wheel and its dependencies from PyPI.
|
|
17
17
|
FROM python:3.12-slim AS pypi
|
|
18
|
-
ARG SYNAPSE_VERSION=0.1.
|
|
18
|
+
ARG SYNAPSE_VERSION=0.1.25
|
|
19
19
|
RUN pip install --no-cache-dir --disable-pip-version-check \
|
|
20
20
|
"synapse-cli-agent==${SYNAPSE_VERSION}"
|
|
21
21
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: synapse-cli-agent
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.26
|
|
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
|
|
@@ -75,7 +75,7 @@ Synapse 使用 **Pydantic Settings** 实现分层配置系统。
|
|
|
75
75
|
| `PROJECT_CATALOG_PATH` | `~/.synapse/catalog.sqlite` | 全局项目目录数据库路径(默认用户层,可覆盖为任意路径) |
|
|
76
76
|
| `SESSION_SUMMARY_MODE` | `local` | 会话摘要模式:`off` 关闭;`local` 在每轮结束后生成确定性本地摘要(不调用模型)。LLM 摘要为未来扩展位 |
|
|
77
77
|
| `SESSION_SUMMARY_MAX_CHARS` | `600` | 本地会话摘要的最大字符数(含多轮条目,超出时从最旧条目开始裁剪) |
|
|
78
|
-
| `AGENT_HISTORY_TAIL_TURNS` | `20` | TUI
|
|
78
|
+
| `AGENT_HISTORY_TAIL_TURNS` | `20` | TUI 从轻量 transcript 投影初始读取/渲染的最近可见会话轮数;滚动到顶部后按 turn 游标加载更早历史,最多挂载 5 页 |
|
|
79
79
|
| `AGENT_SESSION_PREWARM_ENABLED` | `false` | 恢复大上下文会话后,在后台对该会话历史发起一次最小模型请求,让 provider 预填充并缓存历史前缀,用户第一条消息可命中缓存、大幅缩短首 token 等待。注意:预热本身会按输入 token 计费一次,仅在需要时开启 |
|
|
80
80
|
| `AGENT_ENABLE_TOOL_RESPONSE_TRUNCATE` | `false` | 启用工具响应裁剪:keep 窗口(默认最近 40K tokens)外的 `execute`/`read_file`/`search_files` 输出折叠为摘要,减小每轮请求体积(大上下文会话可省 20-30%)。请求层变换,不修改历史 |
|
|
81
81
|
| `AGENT_TOOL_RESPONSE_TRUNCATE_KEEP_TOKENS` | `40000` | 工具响应裁剪的 keep 窗口(token),窗口内消息不裁剪 |
|
|
@@ -83,3 +83,41 @@ http://<gateway1-ip>:8000
|
|
|
83
83
|
- 每个浏览器标签页对应一个独立的 TUI 子进程。
|
|
84
84
|
- agent 的工具执行都发生在容器内,适合作为沙箱隔离。
|
|
85
85
|
- 容器以非 root 用户 `synapse` 运行,镜像只暴露 `8000` 端口。
|
|
86
|
+
|
|
87
|
+
## 通过 nginx 443 反向代理
|
|
88
|
+
|
|
89
|
+
容器不应直接监听公网。推荐让容器只绑定本机回环地址,由 nginx 承担 TLS 与公网入口:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
docker run --rm -d --name synapse-web \
|
|
93
|
+
-p 127.0.0.1:8000:8000 \
|
|
94
|
+
-v "$PWD:/workspace" \
|
|
95
|
+
synapse-web \
|
|
96
|
+
--host 127.0.0.1 --port 8000 \
|
|
97
|
+
--public-url https://synapse-agent.best/tui
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`--public-url` 是必须的:`textual-serve` 用它生成页面中的 WebSocket 与静态资源
|
|
101
|
+
地址。不设置时默认使用 `http://<host>:<port>`,经 nginx 反代后浏览器无法连接。
|
|
102
|
+
`https://` 前缀会自动推导出 `wss://`。
|
|
103
|
+
|
|
104
|
+
nginx 站点中为 Web TUI 增加一个路径并转发到回环端口(需支持 WebSocket 升级):
|
|
105
|
+
|
|
106
|
+
```nginx
|
|
107
|
+
location /tui/ {
|
|
108
|
+
proxy_pass http://127.0.0.1:8000/;
|
|
109
|
+
proxy_http_version 1.1;
|
|
110
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
111
|
+
proxy_set_header Connection "upgrade";
|
|
112
|
+
proxy_set_header Host $host;
|
|
113
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
114
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
115
|
+
proxy_read_timeout 86400;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
重载配置后访问:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
https://synapse-agent.best/tui
|
|
123
|
+
```
|
|
@@ -36,13 +36,17 @@ TUI 模式下也可以在界面内浏览和切换历史会话。
|
|
|
36
36
|
|
|
37
37
|
### TUI 恢复会话的分页加载
|
|
38
38
|
|
|
39
|
-
TUI
|
|
40
|
-
`history_tail_turns` 轮(默认 20
|
|
41
|
-
|
|
39
|
+
TUI 启动恢复会话时从独立的 `transcript.sqlite` 轻量投影读取最后
|
|
40
|
+
`history_tail_turns` 轮(默认 20 轮,可在配置中覆盖)。它不会长期保留
|
|
41
|
+
完整 LangChain 消息列表;token 汇总也从投影 O(1) 读取。旧会话第一次打开时
|
|
42
|
+
会从 checkpoint 兼容回填投影,后续打开和翻页只读取所需 turn 范围。
|
|
43
|
+
更早的历史通过滚动分页加载:
|
|
42
44
|
|
|
43
45
|
- 启动后自动滚动到 transcript 底部,显示最近 N 轮。
|
|
44
46
|
- 滚动到 transcript 顶部时,异步加载更早的一批历史并插入到当前内容上方,
|
|
45
|
-
|
|
47
|
+
滚动位置保持不变。
|
|
48
|
+
- TUI 最多保留 5 个已挂载历史页;继续向前翻页时会卸载最远的新页面及其
|
|
49
|
+
Widget 业务引用,避免 DOM 和 Python 对象随会话长度无界增长。
|
|
46
50
|
- 切换会话(`/session`)或重新加载后,分页状态随 transcript 一起重置。
|
|
47
51
|
|
|
48
52
|
## 导出会话
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
## v0.1.26
|
|
2
|
+
|
|
3
|
+
### 新增功能
|
|
4
|
+
|
|
5
|
+
- 恢复 `synapse run` 单次执行命令:headless 跑完一个任务后退出,支持 `-w/--workspace`、`-m/--model`、`--readonly`、`--thread-id`、`--debug`、`--stream/--no-stream`;适配 async-only 模型客户端(`ainvoke`)。
|
|
6
|
+
- `build_coding_agent` 新增 `backend=` 与 `system_prompt=` 注入参数,支持评测 harness 将 agent 桥接到远程/容器后端并覆盖默认编码提示。
|
|
7
|
+
- TUI 会话转录改为分页渲染(compact projection),长对话浏览性能提升。
|
|
8
|
+
- 新增堆转储导出(`observability/heap_dump.py`),便于排查长会话内存问题。
|
|
9
|
+
|
|
10
|
+
### 修复
|
|
11
|
+
|
|
12
|
+
- 修复 TUI 切换会话时未释放上一会话资源(stream sink / rail widgets)的问题。
|
|
13
|
+
- 修复 `enable_goals=False` 时 goal 记账 disabled middleware 构造崩溃(`goal_middleware_disabled() takes no arguments`)。
|
|
14
|
+
|
|
15
|
+
### 工程改进
|
|
16
|
+
|
|
17
|
+
- Git Explore 对话框与 turn rail 相关组件同步更新,补充对应单测(transcript projection、heap dump、selection、dialogs 等)。
|
|
18
|
+
|
|
19
|
+
---
|
|
@@ -165,6 +165,8 @@ def build_coding_agent(
|
|
|
165
165
|
mcp_tools: list[Any] | None = None,
|
|
166
166
|
load_mcp: bool | None = None,
|
|
167
167
|
model: Any | None = None,
|
|
168
|
+
backend: Any | None = None,
|
|
169
|
+
system_prompt: str | None = None,
|
|
168
170
|
model_registry: Any | None = None,
|
|
169
171
|
model_cache: dict[str, Any] | None = None,
|
|
170
172
|
steer_queue: SteerQueue | None = None,
|
|
@@ -182,6 +184,11 @@ def build_coding_agent(
|
|
|
182
184
|
- automatic context summarization is built into deepagents
|
|
183
185
|
|
|
184
186
|
Pass ``model=`` / ``checkpointer=`` to rebuild cheaply (e.g. attach MCP).
|
|
187
|
+
Pass ``backend=`` to run the agent against a custom backend (e.g. a
|
|
188
|
+
remote/container-backed shell for benchmark harnesses); defaults to the
|
|
189
|
+
local ``CodingLocalShellBackend``.
|
|
190
|
+
Pass ``system_prompt=`` to override the default coding prompt (used by
|
|
191
|
+
benchmark harnesses that need a neutral terminal-agent persona).
|
|
185
192
|
``prompt_cache_key`` is polled per Codex OAuth request (mirroring the
|
|
186
193
|
codex-rs ``session_id``-keyed prompt cache); pass a callable returning the
|
|
187
194
|
current thread/session id so cache keys stay stable within a session and
|
|
@@ -209,7 +216,7 @@ def build_coding_agent(
|
|
|
209
216
|
if progress is not None:
|
|
210
217
|
progress("preparing backend")
|
|
211
218
|
with span("backend"):
|
|
212
|
-
backend = build_backend(settings)
|
|
219
|
+
backend = backend or build_backend(settings)
|
|
213
220
|
|
|
214
221
|
model_cache_hit = False
|
|
215
222
|
if model_cache is None:
|
|
@@ -224,7 +231,10 @@ def build_coding_agent(
|
|
|
224
231
|
model_name=model_name,
|
|
225
232
|
progress=progress,
|
|
226
233
|
)
|
|
227
|
-
|
|
234
|
+
# Session switches may restore different model profiles. Keep only a
|
|
235
|
+
# small working set so model clients and their HTTP pools do not grow
|
|
236
|
+
# with every profile visited in a long-lived TUI process.
|
|
237
|
+
if len(model_cache) >= 4:
|
|
228
238
|
evicted = model_cache.pop(next(iter(model_cache)))
|
|
229
239
|
try:
|
|
230
240
|
from synapse.integrations.http_clients import close_model_async_http_client
|
|
@@ -536,12 +546,13 @@ def build_coding_agent(
|
|
|
536
546
|
if progress is not None:
|
|
537
547
|
progress("compiling agent graph")
|
|
538
548
|
with span("create_deep_agent"):
|
|
549
|
+
prompt = system_prompt if system_prompt is not None else build_system_prompt(
|
|
550
|
+
root,
|
|
551
|
+
shell_executable=backend.shell_executable,
|
|
552
|
+
)
|
|
539
553
|
agent = create_deep_agent(
|
|
540
554
|
model=model,
|
|
541
|
-
system_prompt=
|
|
542
|
-
root,
|
|
543
|
-
shell_executable=backend.shell_executable,
|
|
544
|
-
),
|
|
555
|
+
system_prompt=prompt,
|
|
545
556
|
backend=backend,
|
|
546
557
|
tools=tools,
|
|
547
558
|
middleware=middleware,
|
|
@@ -14,8 +14,12 @@ from synapse.sessions.store import SessionStore, format_session_table
|
|
|
14
14
|
from synapse.settings import bootstrap_project_env, load_settings
|
|
15
15
|
from synapse.ui.stream import (
|
|
16
16
|
console,
|
|
17
|
+
extract_last_ai_text,
|
|
18
|
+
print_banner,
|
|
17
19
|
print_error,
|
|
20
|
+
print_final,
|
|
18
21
|
print_info,
|
|
22
|
+
stream_agent,
|
|
19
23
|
)
|
|
20
24
|
|
|
21
25
|
app = typer.Typer(
|
|
@@ -427,6 +431,173 @@ def tui_cmd(
|
|
|
427
431
|
)
|
|
428
432
|
|
|
429
433
|
|
|
434
|
+
# ---------------------------------------------------------------------------
|
|
435
|
+
# One-shot run command (headless, for scripts / evals)
|
|
436
|
+
# ---------------------------------------------------------------------------
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def _print_tokens_from_state(state: dict) -> None:
|
|
440
|
+
"""Print token usage summary from agent final state."""
|
|
441
|
+
from synapse.ui.stream_events import _extract_usage
|
|
442
|
+
|
|
443
|
+
messages = state.get("messages") if isinstance(state, dict) else []
|
|
444
|
+
if not messages:
|
|
445
|
+
return
|
|
446
|
+
seen: set[str] = set()
|
|
447
|
+
total_in = 0
|
|
448
|
+
total_out = 0
|
|
449
|
+
for msg in messages:
|
|
450
|
+
msg_id = getattr(msg, "id", None) or id(msg)
|
|
451
|
+
key = f"usage:{msg_id}"
|
|
452
|
+
if key in seen:
|
|
453
|
+
continue
|
|
454
|
+
usage = _extract_usage(msg)
|
|
455
|
+
if usage["input_tokens"] or usage["output_tokens"]:
|
|
456
|
+
seen.add(key)
|
|
457
|
+
total_in += usage["input_tokens"]
|
|
458
|
+
total_out += usage["output_tokens"]
|
|
459
|
+
if total_in or total_out:
|
|
460
|
+
total = total_in + total_out
|
|
461
|
+
print_info(f"tokens: {total} (in={total_in} out={total_out})")
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _run_once(
|
|
465
|
+
agent,
|
|
466
|
+
payload: dict | Any,
|
|
467
|
+
config: dict,
|
|
468
|
+
*,
|
|
469
|
+
use_stream: bool = True,
|
|
470
|
+
token_stream: bool = True,
|
|
471
|
+
max_concurrency: int = 8,
|
|
472
|
+
sink=None,
|
|
473
|
+
) -> tuple[str, bool, Any]:
|
|
474
|
+
"""Execute one turn.
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
(answer_text, already_displayed, stream_result_or_none)
|
|
478
|
+
"""
|
|
479
|
+
if use_stream:
|
|
480
|
+
streamed = stream_agent(
|
|
481
|
+
agent,
|
|
482
|
+
payload,
|
|
483
|
+
config,
|
|
484
|
+
token_stream=token_stream,
|
|
485
|
+
prefer_async=True,
|
|
486
|
+
max_concurrency=max_concurrency,
|
|
487
|
+
sink=sink,
|
|
488
|
+
)
|
|
489
|
+
if streamed.final_text:
|
|
490
|
+
return streamed.final_text, streamed.streamed_answer, streamed
|
|
491
|
+
if streamed.state.get("messages"):
|
|
492
|
+
return extract_last_ai_text(streamed.state), False, streamed
|
|
493
|
+
if streamed.interrupted:
|
|
494
|
+
return "", True, streamed
|
|
495
|
+
print_info("stream empty, falling back to invoke...")
|
|
496
|
+
else:
|
|
497
|
+
print_info("running...")
|
|
498
|
+
|
|
499
|
+
# Model clients are async-only (see b788b62); use ainvoke instead of invoke.
|
|
500
|
+
invoked = asyncio.run(agent.ainvoke(payload, config=config))
|
|
501
|
+
state = invoked if isinstance(invoked, dict) else {"messages": invoked}
|
|
502
|
+
_print_tokens_from_state(state)
|
|
503
|
+
return (
|
|
504
|
+
extract_last_ai_text(state),
|
|
505
|
+
False,
|
|
506
|
+
None,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
@app.command("run")
|
|
511
|
+
def run_cmd(
|
|
512
|
+
task: str = typer.Argument(..., help="Task for the coding agent"),
|
|
513
|
+
workspace: Path | None = typer.Option(
|
|
514
|
+
None, "--workspace", "-w", help="Workspace directory", exists=False, file_okay=False
|
|
515
|
+
),
|
|
516
|
+
model: str | None = typer.Option(
|
|
517
|
+
None, "--model", "-m", help="Model profile alias or provider:model"
|
|
518
|
+
),
|
|
519
|
+
require_approval: bool = typer.Option(
|
|
520
|
+
False,
|
|
521
|
+
"--require-approval/--no-require-approval",
|
|
522
|
+
help="Enable HITL approval (default: disabled, auto-pass)",
|
|
523
|
+
),
|
|
524
|
+
readonly: bool = typer.Option(
|
|
525
|
+
False, "--readonly/--no-readonly", help="Exclude write/execute tools via harness"
|
|
526
|
+
),
|
|
527
|
+
thread_id: str | None = typer.Option(None, "--thread-id", help="Resume a session id"),
|
|
528
|
+
debug: bool = typer.Option(False, "--debug", help="Enable deepagents debug mode"),
|
|
529
|
+
stream: bool = typer.Option(
|
|
530
|
+
True, "--stream/--no-stream", help="Stream intermediate updates"
|
|
531
|
+
),
|
|
532
|
+
) -> None:
|
|
533
|
+
"""Run a single coding task headless and exit."""
|
|
534
|
+
env_path = _bootstrap_env()
|
|
535
|
+
settings = _resolve_settings(
|
|
536
|
+
workspace=workspace,
|
|
537
|
+
model=model,
|
|
538
|
+
require_approval=require_approval,
|
|
539
|
+
debug=debug,
|
|
540
|
+
readonly=readonly,
|
|
541
|
+
)
|
|
542
|
+
print_banner(str(settings.workspace), settings.model, settings.require_approval)
|
|
543
|
+
if env_path is not None:
|
|
544
|
+
print_info(f"loaded env: {env_path}")
|
|
545
|
+
print_info(
|
|
546
|
+
f"auth: key={settings.mask_openai_key()} "
|
|
547
|
+
f"base_url={settings.openai_base_url!r} model={settings.model!r}"
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
try:
|
|
551
|
+
from synapse.app.agent import build_coding_agent, default_thread_id
|
|
552
|
+
|
|
553
|
+
agent = build_coding_agent(
|
|
554
|
+
settings,
|
|
555
|
+
project_root=Path.cwd(),
|
|
556
|
+
load_mcp=bool(settings.enable_mcp),
|
|
557
|
+
)
|
|
558
|
+
except Exception as exc: # noqa: BLE001
|
|
559
|
+
print_error(f"failed to build agent: {exc}")
|
|
560
|
+
raise typer.Exit(code=1) from exc
|
|
561
|
+
|
|
562
|
+
tid = thread_id or default_thread_id()
|
|
563
|
+
store = _session_store(settings)
|
|
564
|
+
store.touch(tid, title_hint=task, model=settings.model)
|
|
565
|
+
config = {
|
|
566
|
+
"configurable": {"thread_id": tid},
|
|
567
|
+
"max_concurrency": settings.max_concurrency,
|
|
568
|
+
}
|
|
569
|
+
payload = {"messages": [{"role": "user", "content": task}]}
|
|
570
|
+
print_info(f"thread_id={tid}")
|
|
571
|
+
print_info(
|
|
572
|
+
f"stream: token={settings.token_stream} "
|
|
573
|
+
f"parallel_tools={settings.parallel_tool_calls} "
|
|
574
|
+
f"max_concurrency={settings.max_concurrency}"
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
try:
|
|
578
|
+
answer, already, streamed = _run_once(
|
|
579
|
+
agent,
|
|
580
|
+
payload,
|
|
581
|
+
config,
|
|
582
|
+
use_stream=stream,
|
|
583
|
+
token_stream=settings.token_stream,
|
|
584
|
+
max_concurrency=settings.max_concurrency,
|
|
585
|
+
)
|
|
586
|
+
if streamed is not None and getattr(streamed, "interrupted", False):
|
|
587
|
+
print_error(
|
|
588
|
+
"task paused for approval; run without --require-approval "
|
|
589
|
+
f"or resume later with thread_id={tid}"
|
|
590
|
+
)
|
|
591
|
+
raise typer.Exit(code=2)
|
|
592
|
+
if not already:
|
|
593
|
+
print_final(answer)
|
|
594
|
+
except typer.Exit:
|
|
595
|
+
raise
|
|
596
|
+
except Exception as exc: # noqa: BLE001
|
|
597
|
+
_print_auth_error(settings, exc)
|
|
598
|
+
raise typer.Exit(code=1) from exc
|
|
599
|
+
|
|
600
|
+
|
|
430
601
|
# ---------------------------------------------------------------------------
|
|
431
602
|
# Sub-commands: sessions
|
|
432
603
|
# ---------------------------------------------------------------------------
|
|
@@ -1158,6 +1329,12 @@ def main() -> None:
|
|
|
1158
1329
|
mark("cli:main")
|
|
1159
1330
|
except Exception: # noqa: BLE001
|
|
1160
1331
|
pass
|
|
1332
|
+
try:
|
|
1333
|
+
from synapse.observability.heap_dump import start_heap_dump_watchdog
|
|
1334
|
+
|
|
1335
|
+
start_heap_dump_watchdog()
|
|
1336
|
+
except Exception: # noqa: BLE001
|
|
1337
|
+
pass
|
|
1161
1338
|
app()
|
|
1162
1339
|
|
|
1163
1340
|
|
|
@@ -64,9 +64,7 @@ def build_goal_middleware(enabled: bool = True):
|
|
|
64
64
|
usage 并结算);goal 未启用时是纯 no-op。
|
|
65
65
|
"""
|
|
66
66
|
if not enabled:
|
|
67
|
-
return
|
|
68
|
-
state_schema=AgentState
|
|
69
|
-
)
|
|
67
|
+
return _DisabledGoalMiddleware()
|
|
70
68
|
|
|
71
69
|
def _before_model(self, state: Any, runtime: Any) -> dict[str, Any] | None: # noqa: ARG001
|
|
72
70
|
service = get_goal_service()
|
|
@@ -120,3 +118,16 @@ def build_goal_middleware(enabled: bool = True):
|
|
|
120
118
|
"awrap_model_call": _awrap_model_call,
|
|
121
119
|
},
|
|
122
120
|
)()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class _DisabledGoalMiddleware(AgentMiddleware):
|
|
124
|
+
"""No-op middleware used when goals are disabled.
|
|
125
|
+
|
|
126
|
+
A plain subclass keeps ``state_schema`` set (``AgentMiddleware`` declares it
|
|
127
|
+
as a class attribute, so instances do not need an explicit ``__init__``).
|
|
128
|
+
"""
|
|
129
|
+
|
|
130
|
+
def __init__(self) -> None:
|
|
131
|
+
super().__init__()
|
|
132
|
+
self.state_schema = AgentState
|
|
133
|
+
|
{synapse_cli_agent-0.1.24 → synapse_cli_agent-0.1.26}/src/synapse/observability/debug_inspector.html
RENAMED
|
@@ -5,15 +5,15 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
6
|
<title>Synapse Inspector</title>
|
|
7
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}}
|
|
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}}button:disabled{opacity:.5;cursor:default}.overlay{position:fixed;inset:0;z-index:50;display:grid;place-items:center;background:#0009;padding:20px}.dialog{width:min(760px,94vw);max-height:86vh;overflow:auto;padding:18px 20px;border:1px solid var(--line);border-radius:8px;background:var(--panel);scrollbar-color:#40546a #111923;scrollbar-width:thin}.dialog h3{margin:0;font-size:16px}.dialog .sub{margin-top:2px;font-size:12px}.dump-table{width:100%;margin-top:10px;border-collapse:collapse;font:11.5px ui-monospace,Consolas,monospace}.dump-table th,.dump-table td{padding:5px 9px;border-bottom:1px solid var(--line);text-align:left}.dump-table th{color:var(--muted);font-size:10px;font-weight:700;text-transform:uppercase}.dump-table td.num{text-align:right}.dialog .actions{display:flex;justify-content:flex-end;gap:8px;margin-top:16px}.dialog a.btn{text-decoration:none}
|
|
9
9
|
</style>
|
|
10
10
|
</head>
|
|
11
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>
|
|
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="heapDump" title="导出当前进程内存占用快照,用于分析实际占用">内存 Dump</button><button class="btn" id="refresh">刷新</button><button class="btn danger" id="clear">清空</button></div></header>
|
|
13
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
14
|
<script>
|
|
15
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 prettyBody=raw=>{if(!raw||!raw.body)return'';try{return dump(JSON.parse(raw.body))}catch(_){return raw.body}};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 if(tab==='http'){const req=record.raw_request||null,resp=record.raw_response||null,bodyView=raw=>raw&&raw.body?`<div class="raw-box"><pre>${esc(prettyBody(raw))}</pre></div>`:'<div class="tool-summary">未捕获。</div>';if(!req&&!resp)html='<div class="notice">该调用未捕获原始 HTTP 通信(当前仅 OpenAI 兼容通道支持;WebSocket 与 Anthropic 通道暂不提供)。</div>';else html=section('原始请求',req?(esc(req.method||'')+' '+esc(req.url||'')+(req.body_truncated?' · 已截断':'')):'未捕获',bodyView(req),req)+section('原始响应',resp&&resp.body_truncated?'已截断':'',bodyView(resp),resp)}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('原始')?title.includes('响应')?record.raw_response:record.raw_request: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}`],['http','原始HTTP'],['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);
|
|
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 if(tab==='http'){const req=record.raw_request||null,resp=record.raw_response||null,bodyView=raw=>raw&&raw.body?`<div class="raw-box"><pre>${esc(prettyBody(raw))}</pre></div>`:'<div class="tool-summary">未捕获。</div>';if(!req&&!resp)html='<div class="notice">该调用未捕获原始 HTTP 通信(当前仅 OpenAI 兼容通道支持;WebSocket 与 Anthropic 通道暂不提供)。</div>';else html=section('原始请求',req?(esc(req.method||'')+' '+esc(req.url||'')+(req.body_truncated?' · 已截断':'')):'未捕获',bodyView(req),req)+section('原始响应',resp&&resp.body_truncated?'已截断':'',bodyView(resp),resp)}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('原始')?title.includes('响应')?record.raw_response:record.raw_request: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}`],['http','原始HTTP'],['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)}};const fmtBytes=n=>{n=Number(n)||0;if(n>=1048576)return(n/1048576).toFixed(2)+' MiB';if(n>=1024)return(n/1024).toFixed(1)+' KiB';return n+' B'};function renderHeapDump(d){const top=(d.top_types||[]).map(t=>`<tr><td>${esc(t[0])}</td><td class="num">${tok(t[1])}</td><td class="num">${fmtBytes(t[2])}</td></tr>`).join(''),cb=d.container_bytes||{},pm=d.process_memory||{},procBytes=pm.private_bytes||pm.working_set||pm.rss||0,procLabel=pm.private_bytes?'私有字节':pm.working_set?'工作集':'RSS',pyBytes=d.python_approx_bytes||0,procLine=procBytes?`<div class="notice" style="margin:10px 0 0">进程占用 <b>${fmtBytes(procBytes)}</b>(${procLabel})· Python 对象约 <b>${fmtBytes(pyBytes)}</b> · 其余 <b>${fmtBytes(Math.max(0,procBytes-pyBytes))}</b> 为 C 扩展 / 解释器 / 线程栈等开销</div>`:'',box=document.createElement('div');box.className='overlay';box.innerHTML=`<div class="dialog"><h3>内存 Dump</h3><div class="sub">PID ${d.pid} · Python ${esc(d.python)} · ${new Date(d.collected_at*1000).toLocaleString()} · 文件 ${fmtBytes(d.size_bytes)}</div>${procLine}<div class="metrics"><div class="metric"><label>GC 对象</label><strong>${tok(d.gc_objects)}</strong></div><div class="metric"><label>垃圾对象</label><strong>${tok(d.gc_garbage)}</strong></div><div class="metric"><label>str 字节</label><strong>${fmtBytes(cb.str?.[1])}</strong></div><div class="metric"><label>bytes 字节</label><strong>${fmtBytes(cb.bytes?.[1])}</strong></div><div class="metric"><label>list 字节</label><strong>${fmtBytes(cb.list?.[1])}</strong></div><div class="metric"><label>dict 字节</label><strong>${fmtBytes(cb.dict?.[1])}</strong></div></div><h3 style="margin-top:14px;font-size:13px">Top 类型</h3><table class="dump-table"><thead><tr><th>类型</th><th>数量</th><th>字节</th></tr></thead><tbody>${top||'<tr><td colspan="3">无数据</td></tr>'}</tbody></table><div class="actions"><a class="btn" href="${d.download_url}" download="${d.file}">下载 JSON</a><button class="btn" id="closeDump">关闭</button></div></div>`;document.body.appendChild(box);box.onclick=e=>{if(e.target===box)box.remove()};box.querySelector('#closeDump').onclick=()=>box.remove()}async function heapDump(){const button=$('heapDump');button.disabled=true;const old=button.textContent;button.textContent='生成中…';try{const d=await api('/api/heap-dump',{method:'POST'});if(!d){alert('内存 dump 生成失败');return}renderHeapDump(d)}finally{button.disabled=false;button.textContent=old}}$('heapDump').onclick=heapDump;$('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
17
|
</script>
|
|
18
18
|
</body>
|
|
19
19
|
</html>
|