synapse-cli-agent 0.1.18__tar.gz → 0.1.19__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 (346) hide show
  1. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/CHANGELOG.md +16 -0
  2. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/PKG-INFO +1 -1
  3. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/config.md +6 -0
  4. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/pyproject.toml +1 -1
  5. synapse_cli_agent-0.1.19/release_body.md +15 -0
  6. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/cli.py +22 -9
  7. synapse_cli_agent-0.1.19/src/synapse/integrations/openai_usage.py +406 -0
  8. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/models/registry.py +23 -7
  9. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/context_compact.py +55 -4
  10. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/components/__init__.py +5 -1
  11. synapse_cli_agent-0.1.19/src/synapse/ui/bottombar/components/codex_usage.py +33 -0
  12. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/context.py +2 -0
  13. synapse_cli_agent-0.1.19/src/synapse/ui/dialogs/codex_reset.py +129 -0
  14. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/tui.py +217 -2
  15. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_bottombar_registry.py +5 -0
  16. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_cli.py +29 -2
  17. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_context_compact.py +16 -1
  18. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_dialogs.py +19 -0
  19. synapse_cli_agent-0.1.19/tests/test_openai_usage.py +151 -0
  20. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/uv.lock +1 -1
  21. synapse_cli_agent-0.1.18/release_body.md +0 -19
  22. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/.github/workflows/ci.yml +0 -0
  23. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/.github/workflows/docs.yml +0 -0
  24. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/.github/workflows/native-compression-wheels.yml +0 -0
  25. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/.github/workflows/native-search-wheels.yml +0 -0
  26. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/.github/workflows/release.yml +0 -0
  27. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/.gitignore +0 -0
  28. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/.python-version +0 -0
  29. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/.readthedocs.yaml +0 -0
  30. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/AGENTS.md +0 -0
  31. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/README.md +0 -0
  32. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/agent_upstream.txt +0 -0
  33. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/cli_upstream.txt +0 -0
  34. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/decode_screenshot.py +0 -0
  35. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/agent-development-guide.md +0 -0
  36. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/architecture-flow.md +0 -0
  37. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/architecture.md +0 -0
  38. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/cli.md +0 -0
  39. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/codex-session-discovery-phase-1.md +0 -0
  40. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/codex-session-import-phase-2.md +0 -0
  41. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/design.md +0 -0
  42. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/index.md +0 -0
  43. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/install.md +0 -0
  44. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/mcp.md +0 -0
  45. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/models.md +0 -0
  46. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/native-compression-core.md +0 -0
  47. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/native-search-core.md +0 -0
  48. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/permissions.md +0 -0
  49. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/quickstart.md +0 -0
  50. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/sessions.md +0 -0
  51. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/skills.md +0 -0
  52. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/smith_output.md +0 -0
  53. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/tool-output-transform.md +0 -0
  54. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/tui-cursor-refactor.md +0 -0
  55. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/tutorial.md +0 -0
  56. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/docs/x-agent-development-guide.md +0 -0
  57. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/examples/mcp.example.json +0 -0
  58. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/examples/models.example.json +0 -0
  59. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/examples/settings.example.json +0 -0
  60. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/middleware_upstream.txt +0 -0
  61. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/mkdocs.yml +0 -0
  62. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-search-core/Cargo.lock +0 -0
  63. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-search-core/Cargo.toml +0 -0
  64. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-search-core/LICENSE +0 -0
  65. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-search-core/NOTICE +0 -0
  66. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-search-core/README.md +0 -0
  67. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-search-core/pyproject.toml +0 -0
  68. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-search-core/python/synapse_search_core/__init__.py +0 -0
  69. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-search-core/src/lib.rs +0 -0
  70. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/Cargo.lock +0 -0
  71. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/Cargo.toml +0 -0
  72. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/LICENSE +0 -0
  73. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/NOTICE +0 -0
  74. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/README.md +0 -0
  75. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/pyproject.toml +0 -0
  76. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/python/synapse_tool_compress_core/__init__.py +0 -0
  77. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/ccr.rs +0 -0
  78. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/ccr_in_memory.rs +0 -0
  79. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/mod.rs +0 -0
  80. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/relevance/base.rs +0 -0
  81. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/relevance/bm25.rs +0 -0
  82. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/signals/keyword_detector.rs +0 -0
  83. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/signals/line_importance.rs +0 -0
  84. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/adaptive_sizer.rs +0 -0
  85. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/anchor_selector.rs +0 -0
  86. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/code_compressor.rs +0 -0
  87. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/diff_compressor.rs +0 -0
  88. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/log_compressor.rs +0 -0
  89. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/search_compressor.rs +0 -0
  90. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/analyzer.rs +0 -0
  91. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/anchors.rs +0 -0
  92. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/builder.rs +0 -0
  93. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/classifier.rs +0 -0
  94. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/classifier.rs +0 -0
  95. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/compactor.rs +0 -0
  96. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/formatter.rs +0 -0
  97. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/ir.rs +0 -0
  98. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/mod.rs +0 -0
  99. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/compaction/walker.rs +0 -0
  100. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/config.rs +0 -0
  101. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/constraints.rs +0 -0
  102. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/crusher.rs +0 -0
  103. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/crushers.rs +0 -0
  104. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/error_keywords.rs +0 -0
  105. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/field_detect.rs +0 -0
  106. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/hashing.rs +0 -0
  107. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/mod.rs +0 -0
  108. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/observer.rs +0 -0
  109. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/orchestration.rs +0 -0
  110. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/outliers.rs +0 -0
  111. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/planning.rs +0 -0
  112. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/statistics.rs +0 -0
  113. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/stats_math.rs +0 -0
  114. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/traits.rs +0 -0
  115. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/headroom_port/transforms/smart_crusher/types.rs +0 -0
  116. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/rust/synapse-tool-compress-core/src/lib.rs +0 -0
  117. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/scripts/install.ps1 +0 -0
  118. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/scripts/release.ps1 +0 -0
  119. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/slash_cmds_upstream.txt +0 -0
  120. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/__init__.py +0 -0
  121. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/__main__.py +0 -0
  122. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/app/__init__.py +0 -0
  123. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/app/agent.py +0 -0
  124. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/app/agent_md.py +0 -0
  125. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/__init__.py +0 -0
  126. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/compression.py +0 -0
  127. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/helpers.py +0 -0
  128. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/mcp.py +0 -0
  129. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/model.py +0 -0
  130. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/result.py +0 -0
  131. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/sessions.py +0 -0
  132. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/slash_cmds.py +0 -0
  133. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/slash_complete.py +0 -0
  134. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/commands/theme.py +0 -0
  135. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/config.py +0 -0
  136. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/content/__init__.py +0 -0
  137. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/content/input_history.py +0 -0
  138. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/content/multimodal.py +0 -0
  139. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/content/prompts.py +0 -0
  140. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/content/skills_catalog.py +0 -0
  141. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/__init__.py +0 -0
  142. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/checkpoint_seed.py +0 -0
  143. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/codex_history.py +0 -0
  144. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/codex_import.py +0 -0
  145. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/codex_sessions.py +0 -0
  146. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/describe_image.py +0 -0
  147. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/http_clients.py +0 -0
  148. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/llm_openai_compat.py +0 -0
  149. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/llm_openai_websocket.py +0 -0
  150. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/mcp_client.py +0 -0
  151. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/openai_oauth.py +0 -0
  152. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/openai_oauth_middleware.py +0 -0
  153. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/integrations/vision_middleware.py +0 -0
  154. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/memory/__init__.py +0 -0
  155. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/memory/auto_recorder.py +0 -0
  156. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/memory/embedder.py +0 -0
  157. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/memory/long_term.py +0 -0
  158. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/models/__init__.py +0 -0
  159. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/models/config.py +0 -0
  160. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/models/helpers.py +0 -0
  161. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/models/profile.py +0 -0
  162. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/models_registry.py +0 -0
  163. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/observability/__init__.py +0 -0
  164. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/observability/debug_inspector.html +0 -0
  165. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/observability/debug_server.py +0 -0
  166. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/observability/llm_debug.py +0 -0
  167. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/observability/startup_trace.py +0 -0
  168. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/parallel_subagents.py +0 -0
  169. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/planner/__init__.py +0 -0
  170. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/planner/task_planner.py +0 -0
  171. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/rag/__init__.py +0 -0
  172. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/rag/knowledge_base.py +0 -0
  173. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/__init__.py +0 -0
  174. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/async_runtime.py +0 -0
  175. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/backends.py +0 -0
  176. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/debug_capture_middleware.py +0 -0
  177. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/execute_capture.py +0 -0
  178. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/fs_permissions.py +0 -0
  179. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/harness.py +0 -0
  180. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/hitl.py +0 -0
  181. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/interaction_ledger.py +0 -0
  182. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/middleware.py +0 -0
  183. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/model_request_compression_middleware.py +0 -0
  184. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/pathing.py +0 -0
  185. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/safety.py +0 -0
  186. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/steer.py +0 -0
  187. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/subagents.py +0 -0
  188. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/tool_ignore.py +0 -0
  189. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/tool_output_eval.py +0 -0
  190. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/tool_output_middleware.py +0 -0
  191. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/runtime/tool_output_usage_middleware.py +0 -0
  192. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/sessions/__init__.py +0 -0
  193. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/sessions/cancel_repair.py +0 -0
  194. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/sessions/session_recap.py +0 -0
  195. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/sessions/store.py +0 -0
  196. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/sessions/transcript.py +0 -0
  197. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/settings/__init__.py +0 -0
  198. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/settings/config_paths.py +0 -0
  199. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/settings/schema.py +0 -0
  200. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/subagent_monitor.py +0 -0
  201. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tool_output/__init__.py +0 -0
  202. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tool_output/detection.py +0 -0
  203. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tool_output/metrics.py +0 -0
  204. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tool_output/models.py +0 -0
  205. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tool_output/pipeline.py +0 -0
  206. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tool_output/repository.py +0 -0
  207. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tool_output/transformers.py +0 -0
  208. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tools/__init__.py +0 -0
  209. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tools/describe_image.py +0 -0
  210. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tools/filesystem_search.py +0 -0
  211. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/tools/session_tools.py +0 -0
  212. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/__init__.py +0 -0
  213. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/answer_divider.py +0 -0
  214. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/__init__.py +0 -0
  215. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/components/key_hints.py +0 -0
  216. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/components/mcp.py +0 -0
  217. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/components/mode.py +0 -0
  218. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/components/model.py +0 -0
  219. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/components/thread.py +0 -0
  220. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/bottombar/core.py +0 -0
  221. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/clipboard.py +0 -0
  222. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/__init__.py +0 -0
  223. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/base.py +0 -0
  224. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/codex_session_list.py +0 -0
  225. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/compression_diagnostics.py +0 -0
  226. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/git_explore.py +0 -0
  227. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/mcp_panel.py +0 -0
  228. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/model_picker.py +0 -0
  229. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/safety_panel.py +0 -0
  230. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/session_list.py +0 -0
  231. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/subagent_monitor.py +0 -0
  232. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/theme_designer.py +0 -0
  233. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/dialogs/theme_picker.py +0 -0
  234. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/formatters.py +0 -0
  235. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/git_explore/__init__.py +0 -0
  236. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/git_explore/engine.py +0 -0
  237. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/git_explore/provider.py +0 -0
  238. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/git_explore/unified.py +0 -0
  239. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/rendering.py +0 -0
  240. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/selectable_static.py +0 -0
  241. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/selectable_text.py +0 -0
  242. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/sink.py +0 -0
  243. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/steer_widget.py +0 -0
  244. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/stream.py +0 -0
  245. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/stream_events.py +0 -0
  246. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/stream_runtime.py +0 -0
  247. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/textual_stream_sink.py +0 -0
  248. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/theme.py +0 -0
  249. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/timeline.py +0 -0
  250. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/tool_blocks.py +0 -0
  251. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/__init__.py +0 -0
  252. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/components/__init__.py +0 -0
  253. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/components/branch.py +0 -0
  254. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/components/title.py +0 -0
  255. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/components/tool_output.py +0 -0
  256. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/components/usage.py +0 -0
  257. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/components/workspace.py +0 -0
  258. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/context.py +0 -0
  259. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/core.py +0 -0
  260. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/git_changes_popover.py +0 -0
  261. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/git_chrome.py +0 -0
  262. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/tool_output_popover.py +0 -0
  263. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/topbar/widget.py +0 -0
  264. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/transcript_blocks.py +0 -0
  265. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/turn_rail.py +0 -0
  266. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/turn_rail_widgets.py +0 -0
  267. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/user_turn.py +0 -0
  268. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/user_turn_block.py +0 -0
  269. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/src/synapse/ui/welcome.py +0 -0
  270. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/synapse.cmd +0 -0
  271. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/codex_rollouts/duplicate_response_item.jsonl +0 -0
  272. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/codex_rollouts/incomplete_and_aborted.jsonl +0 -0
  273. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/codex_rollouts/legacy_compaction.jsonl +0 -0
  274. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/codex_rollouts/normal_completed.jsonl +0 -0
  275. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/codex_rollouts/replacement_history.jsonl +0 -0
  276. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/codex_rollouts/rollback.jsonl +0 -0
  277. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/codex_rollouts/unsupported_replacement.jsonl +0 -0
  278. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/sample_repo/calculator.py +0 -0
  279. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/sample_repo/test_calculator.py +0 -0
  280. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/fixtures/tool_output_eval.json +0 -0
  281. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_agent_factory.py +0 -0
  282. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_async_checkpointer.py +0 -0
  283. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_backends.py +0 -0
  284. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_cancel_repair.py +0 -0
  285. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_capabilities.py +0 -0
  286. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_checkpoint_seed.py +0 -0
  287. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_cli_codex_import.py +0 -0
  288. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_codex_history.py +0 -0
  289. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_codex_import.py +0 -0
  290. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_codex_sessions.py +0 -0
  291. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_codex_sessions_windows.py +0 -0
  292. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_compact_tool_descriptions.py +0 -0
  293. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_config.py +0 -0
  294. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_context_window.py +0 -0
  295. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_dag_parallel.py +0 -0
  296. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_debug_inspector.py +0 -0
  297. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_describe_image.py +0 -0
  298. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_filesystem_search_tools.py +0 -0
  299. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_git_changes_popover.py +0 -0
  300. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_git_chrome.py +0 -0
  301. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_git_explore.py +0 -0
  302. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_http_clients.py +0 -0
  303. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_input_history.py +0 -0
  304. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_iteration_abc.py +0 -0
  305. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_layered_config.py +0 -0
  306. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_mcp_schema.py +0 -0
  307. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_mermaid_render.py +0 -0
  308. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_middleware_async.py +0 -0
  309. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_model_request_compression_middleware.py +0 -0
  310. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_model_retry.py +0 -0
  311. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_multimodal.py +0 -0
  312. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_openai_compat_reasoning.py +0 -0
  313. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_openai_oauth.py +0 -0
  314. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_openai_oauth_middleware.py +0 -0
  315. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_openai_websocket.py +0 -0
  316. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_pathing.py +0 -0
  317. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_safety.py +0 -0
  318. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_session_recap.py +0 -0
  319. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_slash_and_mcp.py +0 -0
  320. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_slash_complete.py +0 -0
  321. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_startup_defer.py +0 -0
  322. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_steer.py +0 -0
  323. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_stream_cancel.py +0 -0
  324. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_stream_tool_items.py +0 -0
  325. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_stream_ui.py +0 -0
  326. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_subagent_monitor_dialog.py +0 -0
  327. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_subagent_status.py +0 -0
  328. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_theme.py +0 -0
  329. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_timeline.py +0 -0
  330. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tool_ignore.py +0 -0
  331. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tool_intent.py +0 -0
  332. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tool_output.py +0 -0
  333. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tool_output_eval.py +0 -0
  334. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tool_output_slash.py +0 -0
  335. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tool_output_usage_middleware.py +0 -0
  336. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_topbar_context_ratio.py +0 -0
  337. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_topbar_registry.py +0 -0
  338. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_transcript.py +0 -0
  339. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tui_codex_import.py +0 -0
  340. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tui_selection.py +0 -0
  341. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_tui_sink.py +0 -0
  342. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_turn_rail_minimap.py +0 -0
  343. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_usage_restore.py +0 -0
  344. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_user_turn_style.py +0 -0
  345. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/test_welcome.py +0 -0
  346. {synapse_cli_agent-0.1.18 → synapse_cli_agent-0.1.19}/tests/tool_output_plugin_fixture.py +0 -0
@@ -7,6 +7,22 @@ The release workflow automatically extracts the matching section as release note
7
7
 
8
8
  ---
9
9
 
10
+ ## v0.1.19
11
+
12
+ ### 新增功能
13
+
14
+ - Codex OAuth 用量底部栏组件:展示 5h/1d 用量窗口、重置剩余时间和账号到期时间;低于 50% 时红色显示。
15
+ - Codex 速率限制重置能力:直接通过 HTTP 请求 wham/rate-limit-reset-credits 读取可用重置次数与到期详情,支持在弹窗中一键消费重置。
16
+ - /codex reset、/codex credits 命令打开重置详情弹窗;底部栏 Codex 区域支持 hover 高亮与点击。
17
+ - 启动时配置错误友好提示:models.json、settings.json 或内联 JSON 环境变量格式错误时输出简洁错误与修复提示,不再抛出完整 traceback。
18
+
19
+ ### 修复
20
+
21
+ - /compact 改为后台 worker 执行,避免模型摘要阻塞 TUI;执行期间禁止取消,防止压缩状态损坏。
22
+ - 兼容 LangChain 1.3 编译图闭包中的 SummarizationMiddleware 定位。
23
+
24
+ ---
25
+
10
26
  ## v0.1.18
11
27
 
12
28
  ### 新功能
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: synapse-cli-agent
3
- Version: 0.1.18
3
+ Version: 0.1.19
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
@@ -113,6 +113,12 @@ Synapse 使用 **Pydantic Settings** 实现分层配置系统。
113
113
 
114
114
  参见 [模型配置](models.md) 页面。
115
115
 
116
+ ### 配置错误提示
117
+
118
+ 启动时如果 `models.json`、`settings.json` 或内联 JSON 环境变量格式错误,Synapse 会输出
119
+ 简短的错误说明和修复提示后退出,不会显示完整 Python traceback。对于 `models.json`,错误信息会
120
+ 包含出错文件路径和 JSON 的行列位置;修复配置后重新启动即可。
121
+
116
122
  ## `.coding-agent/mcp_servers.json` 格式
117
123
 
118
124
  参见 [MCP Server](mcp.md) 页面。
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "synapse-cli-agent"
3
- version = "0.1.18"
3
+ version = "0.1.19"
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,15 @@
1
+ ## v0.1.19
2
+
3
+ ### 新增功能
4
+
5
+ - Codex OAuth 用量底部栏组件:展示 5h/1d 用量窗口、重置剩余时间和账号到期时间;低于 50% 时红色显示。
6
+ - Codex 速率限制重置能力:直接通过 HTTP 请求 wham/rate-limit-reset-credits 读取可用重置次数与到期详情,支持在弹窗中一键消费重置。
7
+ - /codex reset、/codex credits 命令打开重置详情弹窗;底部栏 Codex 区域支持 hover 高亮与点击。
8
+ - 启动时配置错误友好提示:models.json、settings.json 或内联 JSON 环境变量格式错误时输出简洁错误与修复提示,不再抛出完整 traceback。
9
+
10
+ ### 修复
11
+
12
+ - /compact 改为后台 worker 执行,避免模型摘要阻塞 TUI;执行期间禁止取消,防止压缩状态损坏。
13
+ - 兼容 LangChain 1.3 编译图闭包中的 SummarizationMiddleware 定位。
14
+
15
+ ---
@@ -93,6 +93,15 @@ def _print_auth_error(settings, exc: Exception) -> None:
93
93
  )
94
94
 
95
95
 
96
+ def _print_settings_error(exc: Exception) -> None:
97
+ """Print a concise, actionable configuration error without a traceback."""
98
+ print_error(f"Configuration error: {exc}")
99
+ print_info(
100
+ "Check models.json, settings.json, and inline JSON environment variables "
101
+ "(MODELS_JSON / MCP_SERVERS_JSON)."
102
+ )
103
+
104
+
96
105
  # ---------------------------------------------------------------------------
97
106
  # Authentication commands
98
107
  # ---------------------------------------------------------------------------
@@ -252,14 +261,18 @@ def _launch_tui(
252
261
  debug: bool,
253
262
  ) -> None:
254
263
  """Bootstrap settings and open the full-screen Textual TUI."""
255
- env_path = _bootstrap_env()
256
- settings = _resolve_settings(
257
- workspace=workspace,
258
- model=model,
259
- require_approval=require_approval,
260
- debug=debug,
261
- readonly=readonly,
262
- )
264
+ try:
265
+ env_path = _bootstrap_env()
266
+ settings = _resolve_settings(
267
+ workspace=workspace,
268
+ model=model,
269
+ require_approval=require_approval,
270
+ debug=debug,
271
+ readonly=readonly,
272
+ )
273
+ except (OSError, ValueError) as exc:
274
+ _print_settings_error(exc)
275
+ raise typer.Exit(code=1) from exc
263
276
  try:
264
277
  from synapse.ui.tui import run_tui
265
278
  except ImportError as exc: # pragma: no cover - dependency missing
@@ -965,4 +978,4 @@ def main() -> None:
965
978
 
966
979
 
967
980
  if __name__ == "__main__":
968
- main()
981
+ main()
@@ -0,0 +1,406 @@
1
+ """Read-only Codex OAuth usage snapshots for the TUI bottombar."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import threading
6
+ import time
7
+ import uuid
8
+ from dataclasses import dataclass, field
9
+ from typing import Any
10
+
11
+ import httpx
12
+
13
+ from synapse.integrations.openai_oauth import OpenAIOAuthStore, OpenAIOAuthTokenProvider
14
+
15
+ OPENAI_USAGE_ENDPOINT = "https://chatgpt.com/backend-api/wham/usage"
16
+ OPENAI_RESET_CREDITS_ENDPOINT = (
17
+ "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits"
18
+ )
19
+ OPENAI_CONSUME_RESET_ENDPOINT = (
20
+ "https://chatgpt.com/backend-api/wham/rate-limit-reset-credits/consume"
21
+ )
22
+ USAGE_CACHE_TTL_SECONDS = 300.0
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class UsageWindow:
27
+ """One Codex rate-limit window."""
28
+ used_percent: float | None
29
+ window_minutes: int | None
30
+ reset_at: float | None
31
+ @property
32
+ def remaining_percent(self) -> int | None:
33
+ if self.used_percent is None:
34
+ return None
35
+ return round(max(0.0, min(100.0, 100.0 - self.used_percent)))
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class ResetCreditDetail:
40
+ """One available / consumed rate-limit reset credit."""
41
+ id: str
42
+ reset_type: str
43
+ status: str
44
+ granted_at: float | None
45
+ expires_at: float | None
46
+ title: str | None
47
+ description: str | None
48
+
49
+
50
+ @dataclass(frozen=True, slots=True)
51
+ class ResetCredits:
52
+ """Aggregated reset-credit snapshot from the backend."""
53
+ available_count: int
54
+ credits: list[ResetCreditDetail] = field(default_factory=list)
55
+
56
+
57
+ @dataclass(frozen=True, slots=True)
58
+ class ConsumeResetResult:
59
+ """Outcome of a single reset-credit redemption."""
60
+ outcome: str
61
+ idempotency_key: str
62
+
63
+
64
+ @dataclass(frozen=True, slots=True)
65
+ class CodexUsageSnapshot:
66
+ """Parsed Codex primary/secondary usage windows + optional reset credits."""
67
+ primary: UsageWindow | None = None
68
+ secondary: UsageWindow | None = None
69
+ captured_at: float = 0.0
70
+ expires_at: float | None = None
71
+ reset_credits: ResetCredits | None = None
72
+ @property
73
+ def lowest_remaining_percent(self) -> int | None:
74
+ values = [
75
+ value for value in (
76
+ self.primary.remaining_percent if self.primary else None,
77
+ self.secondary.remaining_percent if self.secondary else None,
78
+ ) if value is not None
79
+ ]
80
+ return min(values) if values else None
81
+
82
+
83
+ class CodexUsageClient:
84
+ """Fetch and cache the Codex OAuth usage / reset-credit endpoints."""
85
+ def __init__(self, *, store=None, timeout=10.0, cache_ttl=USAGE_CACHE_TTL_SECONDS):
86
+ self._store = store or OpenAIOAuthStore()
87
+ self._token_provider = OpenAIOAuthTokenProvider(self._store)
88
+ self._timeout = max(1.0, float(timeout))
89
+ self._cache_ttl = max(0.0, float(cache_ttl))
90
+ self._lock = threading.RLock()
91
+ self._cached: CodexUsageSnapshot | None = None
92
+ self._cached_at = 0.0
93
+ self._cached_details: ResetCredits | None = None
94
+ self._cached_details_at = 0.0
95
+
96
+ def _auth_headers(self) -> dict[str, str]:
97
+ tokens = self._store.load()
98
+ if tokens is None:
99
+ raise RuntimeError("OpenAI Codex OAuth is not logged in")
100
+ access_token = self._token_provider.access_token()
101
+ tokens = self._store.load() or tokens
102
+ headers: dict[str, str] = {
103
+ "Authorization": f"Bearer {access_token}",
104
+ "originator": "synapse",
105
+ }
106
+ if tokens.account_id:
107
+ headers["ChatGPT-Account-Id"] = tokens.account_id
108
+ return headers
109
+
110
+ def get_cached(self) -> CodexUsageSnapshot | None:
111
+ with self._lock:
112
+ if self._cached is None:
113
+ return None
114
+ if time.monotonic() - self._cached_at > self._cache_ttl:
115
+ return None
116
+ return self._cached
117
+
118
+ def fetch(self, *, force=False) -> CodexUsageSnapshot:
119
+ with self._lock:
120
+ if not force:
121
+ cached = self.get_cached()
122
+ if cached is not None:
123
+ return cached
124
+ headers = self._auth_headers()
125
+ response = httpx.get(OPENAI_USAGE_ENDPOINT, headers=headers, timeout=self._timeout)
126
+ response.raise_for_status()
127
+ payload = response.json()
128
+ tokens = self._store.load()
129
+ snapshot = parse_usage_payload(payload, expires_at=tokens.expires_at if tokens else None)
130
+ with self._lock:
131
+ self._cached = snapshot
132
+ self._cached_at = time.monotonic()
133
+ return snapshot
134
+
135
+ def get_cached_details(self) -> ResetCredits | None:
136
+ with self._lock:
137
+ if self._cached_details is None:
138
+ return None
139
+ if time.monotonic() - self._cached_details_at > self._cache_ttl:
140
+ return None
141
+ return self._cached_details
142
+
143
+ def fetch_reset_credits(self, *, force=False) -> ResetCredits:
144
+ with self._lock:
145
+ if not force:
146
+ cached = self.get_cached_details()
147
+ if cached is not None:
148
+ return cached
149
+ headers = self._auth_headers()
150
+ response = httpx.get(OPENAI_RESET_CREDITS_ENDPOINT, headers=headers, timeout=self._timeout)
151
+ response.raise_for_status()
152
+ details = parse_reset_credits_details(response.json())
153
+ with self._lock:
154
+ self._cached_details = details
155
+ self._cached_details_at = time.monotonic()
156
+ return details
157
+
158
+ def consume_reset_credit(self, *, credit_id=None, idempotency_key=None) -> ConsumeResetResult:
159
+ key = idempotency_key or uuid.uuid4().hex
160
+ headers = self._auth_headers()
161
+ body: dict[str, str] = {"redeem_request_id": key}
162
+ if credit_id:
163
+ body["credit_id"] = credit_id
164
+ response = httpx.post(
165
+ OPENAI_CONSUME_RESET_ENDPOINT, headers=headers, json=body, timeout=self._timeout,
166
+ )
167
+ response.raise_for_status()
168
+ payload = response.json()
169
+ outcome = str(payload.get("outcome") or payload.get("code") or "unknown")
170
+ return ConsumeResetResult(outcome=outcome, idempotency_key=key)
171
+
172
+
173
+ def parse_usage_payload(payload, *, expires_at=None, captured_at=None) -> CodexUsageSnapshot:
174
+ if not isinstance(payload, dict):
175
+ raise ValueError("Codex usage response must be a JSON object")
176
+ rate_limit = payload.get("rate_limit")
177
+ if not isinstance(rate_limit, dict):
178
+ raise ValueError("Codex usage response has no rate_limit object")
179
+ return CodexUsageSnapshot(
180
+ primary=_parse_window(rate_limit.get("primary_window")),
181
+ secondary=_parse_window(rate_limit.get("secondary_window")),
182
+ captured_at=time.time() if captured_at is None else float(captured_at),
183
+ expires_at=expires_at,
184
+ reset_credits=_parse_reset_credits_summary(payload.get("rate_limit_reset_credits")),
185
+ )
186
+
187
+
188
+ def parse_reset_credits_details(payload) -> ResetCredits:
189
+ if not isinstance(payload, dict):
190
+ raise ValueError("Reset-credits response must be a JSON object")
191
+ count = _as_int(payload.get("available_count")) or 0
192
+ raw = payload.get("credits")
193
+ credits: list[ResetCreditDetail] = []
194
+ if isinstance(raw, list):
195
+ for item in raw:
196
+ if isinstance(item, dict):
197
+ credits.append(_parse_reset_credit_detail(item))
198
+ return ResetCredits(available_count=count, credits=credits)
199
+
200
+
201
+ def _parse_reset_credits_summary(raw) -> ResetCredits | None:
202
+ if not isinstance(raw, dict):
203
+ return None
204
+ count = _as_int(raw.get("available_count"))
205
+ if count is None:
206
+ return None
207
+ raw_list = raw.get("credits")
208
+ credits: list[ResetCreditDetail] = []
209
+ if isinstance(raw_list, list):
210
+ for item in raw_list:
211
+ if isinstance(item, dict):
212
+ credits.append(_parse_reset_credit_detail(item))
213
+ return ResetCredits(available_count=count, credits=credits)
214
+
215
+
216
+ def _parse_reset_credit_detail(raw: dict[str, Any]) -> ResetCreditDetail:
217
+ return ResetCreditDetail(
218
+ id=str(raw.get("id") or ""),
219
+ reset_type=str(raw.get("resetType") or raw.get("reset_type") or "unknown"),
220
+ status=str(raw.get("status") or "unknown"),
221
+ granted_at=_as_timestamp(raw.get("grantedAt") or raw.get("granted_at")),
222
+ expires_at=_as_timestamp(raw.get("expiresAt") or raw.get("expires_at")),
223
+ title=raw.get("title") or None,
224
+ description=raw.get("description") or None,
225
+ )
226
+
227
+
228
+ def _as_timestamp(value: Any) -> float | None:
229
+ """Parse Unix seconds or an ISO 8601 timestamp from backend responses."""
230
+ number = _as_float(value)
231
+ if number is not None:
232
+ return number
233
+ if not isinstance(value, str) or not value.strip():
234
+ return None
235
+ from datetime import datetime
236
+
237
+ normalized = value.strip().replace("Z", "+00:00")
238
+ try:
239
+ return datetime.fromisoformat(normalized).timestamp()
240
+ except ValueError:
241
+ return None
242
+
243
+
244
+ def _parse_window(raw) -> UsageWindow | None:
245
+ if not isinstance(raw, dict):
246
+ return None
247
+ used = _as_float(raw.get("used_percent"))
248
+ if used is None:
249
+ return None
250
+ seconds = _as_int(raw.get("limit_window_seconds"))
251
+ return UsageWindow(
252
+ used_percent=max(0.0, min(100.0, used)),
253
+ window_minutes=(seconds + 59) // 60 if seconds and seconds > 0 else None,
254
+ reset_at=_as_float(raw.get("reset_at")),
255
+ )
256
+
257
+
258
+ def _as_float(value) -> float | None:
259
+ if isinstance(value, bool) or value is None:
260
+ return None
261
+ try:
262
+ return float(value)
263
+ except (TypeError, ValueError):
264
+ return None
265
+
266
+
267
+ def _as_int(value) -> int | None:
268
+ number = _as_float(value)
269
+ return int(number) if number is not None else None
270
+
271
+
272
+ def format_reset_remaining(reset_at, *, now=None) -> str:
273
+ if reset_at is None:
274
+ return "--"
275
+ seconds = max(0, int(reset_at - (time.time() if now is None else now)))
276
+ if seconds < 60:
277
+ return f"{seconds}s"
278
+ minutes = (seconds + 59) // 60
279
+ if minutes < 60:
280
+ return f"{minutes}m"
281
+ hours = (minutes + 59) // 60
282
+ if hours < 24:
283
+ return f"{hours}h"
284
+ return f"{(hours + 23) // 24}d"
285
+
286
+
287
+ def format_usage_label(snapshot) -> str:
288
+ if snapshot is None:
289
+ return "codex n/a"
290
+ parts: list[str] = []
291
+ for label, window in (("5h", snapshot.primary), ("1d", snapshot.secondary)):
292
+ if window is None or window.remaining_percent is None:
293
+ continue
294
+ reset = format_reset_remaining(window.reset_at)
295
+ parts.append(f"{label} {window.remaining_percent}%/{reset}")
296
+ if snapshot.reset_credits is not None and snapshot.reset_credits.available_count > 0:
297
+ parts.append(f"resets {snapshot.reset_credits.available_count}")
298
+ if snapshot.expires_at is not None:
299
+ parts.append(f"exp {format_reset_remaining(snapshot.expires_at)}")
300
+ return " · ".join(parts) if parts else "codex n/a"
301
+
302
+
303
+ def usage_style(snapshot) -> str:
304
+ if snapshot is not None and snapshot.lowest_remaining_percent is not None:
305
+ if snapshot.lowest_remaining_percent < 50:
306
+ return "#f28b82"
307
+ return "#8ab4f8"
308
+
309
+
310
+ # ---------------------------------------------------------------------------
311
+ # TUI-facing service — owns cache / refresh / consume state
312
+ # ---------------------------------------------------------------------------
313
+
314
+
315
+ class CodexUsageService:
316
+ """State holder that the TUI binds to a bottombar component.
317
+
318
+ The TUI calls :meth:`refresh_usage` and :meth:`open_reset_dialog` on a
319
+ Textual worker thread. This object stores the latest snapshot / details
320
+ and exposes a ``label`` property for the render path so the bottombar
321
+ never blocks on I/O.
322
+ """
323
+
324
+ def __init__(self, settings=None) -> None:
325
+ self._client = CodexUsageClient()
326
+ self.snapshot: CodexUsageSnapshot | None = None
327
+ self.loading = False
328
+ self.error = False
329
+ self.reset_credits: ResetCredits | None = None
330
+ self.consuming = False
331
+ self._last_refresh = 0.0
332
+ self._settings = settings
333
+
334
+ # -- helpers called from the TUI UI thread --------------------------------
335
+
336
+ @property
337
+ def label(self) -> object:
338
+ """Return rich Text or a plain str for the bottombar render path."""
339
+ from rich.text import Text
340
+
341
+ if self.loading and self.snapshot is None:
342
+ return Text("codex ...", style="#5f6368")
343
+ if self.snapshot is None:
344
+ return Text("codex n/a", style="#5f6368")
345
+ return Text(
346
+ format_usage_label(self.snapshot),
347
+ style=usage_style(self.snapshot),
348
+ )
349
+
350
+ def has_oauth_profile(self) -> bool:
351
+ """Return True when the active model profile uses Codex OAuth."""
352
+ settings = self._settings
353
+ if settings is None:
354
+ return False
355
+ try:
356
+ from synapse.models.registry import registry_from_settings
357
+
358
+ registry = registry_from_settings(settings)
359
+ selected = getattr(settings, "active_model", None) or registry.default
360
+ return registry.get(selected).auth == "openai_oauth"
361
+ except Exception: # noqa: BLE001
362
+ return False
363
+
364
+ def invalidate(self) -> None:
365
+ """Clear all state (non-OAuth model selected)."""
366
+ self.snapshot = None
367
+ self.loading = False
368
+ self.error = False
369
+ self.reset_credits = None
370
+ self._last_refresh = 0.0
371
+
372
+ # -- refresh (called from TUI worker thread) ------------------------------
373
+
374
+ def should_refresh(self, *, force: bool = False) -> bool:
375
+ if not self.has_oauth_profile():
376
+ return False
377
+ if force:
378
+ return True
379
+ if time.monotonic() - self._last_refresh < 300:
380
+ return False
381
+ if self.loading:
382
+ return False
383
+ return True
384
+
385
+ def refresh_usage(self, *, force: bool = False) -> None:
386
+ """Blocking call — run on a worker thread."""
387
+ self.loading = True
388
+ self.error = False
389
+ self._last_refresh = time.monotonic()
390
+ try:
391
+ self.snapshot = self._client.fetch(force=force)
392
+ self.loading = False
393
+ except Exception: # noqa: BLE001
394
+ self.loading = False
395
+ self.error = True
396
+
397
+ def fetch_reset_credits(self) -> None:
398
+ """Blocking call — fetch detailed reset-credit rows."""
399
+ try:
400
+ self.reset_credits = self._client.fetch_reset_credits()
401
+ except Exception: # noqa: BLE001
402
+ pass
403
+
404
+ def consume_reset(self, credit_id: str) -> ConsumeResetResult:
405
+ """Blocking call — redeem one reset credit."""
406
+ return self._client.consume_reset_credit(credit_id=credit_id)
@@ -518,25 +518,41 @@ def _profiles_from_mapping(data: dict[str, Any]) -> ModelRegistry:
518
518
 
519
519
 
520
520
  def load_models_config(path: Path | str | None) -> ModelRegistry | None:
521
- """Load models JSON if path exists; return None when unset/missing."""
521
+ """Load models JSON if path exists; return None when unset/missing.
522
+
523
+ Raises:
524
+ ValueError: If the file does not contain valid JSON or a valid model registry.
525
+ """
522
526
  if path is None:
523
527
  return None
524
528
  p = Path(path).expanduser()
525
529
  if not p.is_file():
526
530
  return None
527
- data = json.loads(p.read_text(encoding="utf-8"))
528
- if not isinstance(data, dict):
529
- raise ValueError("models config root must be an object")
530
- return _profiles_from_mapping(data)
531
+ try:
532
+ data = json.loads(p.read_text(encoding="utf-8"))
533
+ if not isinstance(data, dict):
534
+ raise ValueError("root must be an object")
535
+ return _profiles_from_mapping(data)
536
+ except json.JSONDecodeError as exc:
537
+ raise ValueError(f"invalid models config JSON in {p}: {exc}") from exc
538
+ except (OSError, ValueError) as exc:
539
+ raise ValueError(f"invalid models config in {p}: {exc}") from exc
531
540
 
532
541
 
533
542
  def load_models_json_blob(blob: str | None) -> ModelRegistry | None:
543
+ """Load an inline MODELS_JSON registry, if configured."""
534
544
  if not blob or not blob.strip():
535
545
  return None
536
- data = json.loads(blob)
546
+ try:
547
+ data = json.loads(blob)
548
+ except json.JSONDecodeError as exc:
549
+ raise ValueError(f"invalid MODELS_JSON: {exc}") from exc
537
550
  if not isinstance(data, dict):
538
551
  raise ValueError("MODELS_JSON root must be an object")
539
- return _profiles_from_mapping(data)
552
+ try:
553
+ return _profiles_from_mapping(data)
554
+ except ValueError as exc:
555
+ raise ValueError(f"invalid MODELS_JSON: {exc}") from exc
540
556
 
541
557
 
542
558
  def merge_model_profiles(base: ModelProfile, override: ModelProfile) -> ModelProfile:
@@ -7,6 +7,9 @@ helpers without registering a model-callable compaction tool.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import functools
11
+ import inspect
12
+ from collections.abc import Iterator, Mapping
10
13
  from typing import Any
11
14
 
12
15
 
@@ -98,16 +101,65 @@ def force_compact_via_agent(
98
101
 
99
102
 
100
103
  def _find_summarization_middleware(agent: Any) -> Any | None:
101
- """Return deepagents' built-in automatic summarization middleware."""
104
+ """Return deepagents' built-in automatic summarization middleware.
105
+
106
+ LangChain 1.3 compiles model-call middleware into the ``model`` node's
107
+ function closure, whereas older versions exposed it through
108
+ ``node.bound.middleware``. Support both representations so ``/compact``
109
+ continues to work across the supported dependency range.
110
+ """
102
111
  nodes = getattr(agent, "nodes", None)
103
112
  node = nodes.get("model") if isinstance(nodes, dict) else None
104
- middleware = getattr(node, "bound", None)
105
- for candidate in getattr(middleware, "middleware", ()):
113
+ bound = getattr(node, "bound", None)
114
+ for candidate in getattr(bound, "middleware", ()):
115
+ if getattr(candidate, "name", None) == "SummarizationMiddleware":
116
+ return candidate
117
+
118
+ for candidate in _iter_closure_values(getattr(bound, "func", None)):
119
+ if getattr(candidate, "name", None) == "SummarizationMiddleware":
120
+ return candidate
121
+ for candidate in _iter_closure_values(getattr(bound, "afunc", None)):
106
122
  if getattr(candidate, "name", None) == "SummarizationMiddleware":
107
123
  return candidate
108
124
  return None
109
125
 
110
126
 
127
+ def _iter_closure_values(root: Any) -> Iterator[Any]:
128
+ """Yield nonlocal objects captured by a compiled model-node callable.
129
+
130
+ This deliberately follows only closure cells and their lightweight
131
+ containers. It avoids arbitrary ``__dict__`` traversal while still finding
132
+ middleware instances nested in LangChain's composed call handlers.
133
+ """
134
+ pending: list[Any] = [root]
135
+ seen: set[int] = set()
136
+ max_items = 256
137
+
138
+ while pending and len(seen) < max_items:
139
+ value = pending.pop()
140
+ if value is None or id(value) in seen:
141
+ continue
142
+ seen.add(id(value))
143
+ yield value
144
+
145
+ if inspect.ismethod(value):
146
+ pending.extend((value.__self__, value.__func__))
147
+ elif inspect.isfunction(value):
148
+ try:
149
+ closure = inspect.getclosurevars(value)
150
+ except (TypeError, ValueError):
151
+ continue
152
+ pending.extend(closure.nonlocals.values())
153
+ elif isinstance(value, functools.partial):
154
+ pending.append(value.func)
155
+ pending.extend(value.args)
156
+ pending.extend((value.keywords or {}).values())
157
+ elif isinstance(value, Mapping):
158
+ pending.extend(value.values())
159
+ elif isinstance(value, (list, tuple, set, frozenset)):
160
+ pending.extend(value)
161
+
162
+
111
163
  def context_status_lines(agent: Any, thread_id: str) -> list[str]:
112
164
  """Report message count + last summarization event for current thread."""
113
165
  lines: list[str] = [f"thread_id={thread_id}"]
@@ -216,4 +268,3 @@ def is_context_compact_text(text: str | None) -> bool:
216
268
  if has_intent and ("ARTIFACTS" in body or "NEXT STEPS" in body):
217
269
  return True
218
270
  return False
219
-