synapse-cli-agent 0.1.17__tar.gz → 0.1.18__tar.gz

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