virtual-context 0.2.2__tar.gz → 0.2.3__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 (214) hide show
  1. {virtual_context-0.2.2 → virtual_context-0.2.3}/PKG-INFO +1 -1
  2. {virtual_context-0.2.2 → virtual_context-0.2.3}/pyproject.toml +1 -1
  3. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/__init__.py +1 -1
  4. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/hint_builder.py +6 -2
  5. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/handlers.py +100 -12
  6. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/message_filter.py +96 -2
  7. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/server.py +11 -1
  8. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/types.py +1 -0
  9. {virtual_context-0.2.2 → virtual_context-0.2.3}/.gitignore +0 -0
  10. {virtual_context-0.2.2 → virtual_context-0.2.3}/LICENSE +0 -0
  11. {virtual_context-0.2.2 → virtual_context-0.2.3}/README.md +0 -0
  12. {virtual_context-0.2.2 → virtual_context-0.2.3}/assets/hero.png +0 -0
  13. {virtual_context-0.2.2 → virtual_context-0.2.3}/models.yaml +0 -0
  14. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/REGRESSION_MAP.md +0 -0
  15. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/conftest.py +0 -0
  16. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/docker-compose.test.yml +0 -0
  17. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/haiku/__init__.py +0 -0
  18. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/haiku/conftest.py +0 -0
  19. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/haiku/test_compaction.py +0 -0
  20. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/haiku/test_retrieval.py +0 -0
  21. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/haiku/test_tagging.py +0 -0
  22. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/ollama/__init__.py +0 -0
  23. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/ollama/conftest.py +0 -0
  24. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/ollama/test_compactor.py +0 -0
  25. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/ollama/test_pipeline.py +0 -0
  26. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/ollama/test_provider.py +0 -0
  27. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/ollama/test_tag_generator.py +0 -0
  28. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/proxy/__init__.py +0 -0
  29. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/proxy/test_dashboard_cors.py +0 -0
  30. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/proxy/test_metrics.py +0 -0
  31. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_assembler.py +0 -0
  32. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_backend_integration.py +0 -0
  33. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_cli_init.py +0 -0
  34. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_compaction_commit_prune.py +0 -0
  35. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_compactor.py +0 -0
  36. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_compactor_concurrent.py +0 -0
  37. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_composite_store.py +0 -0
  38. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_config.py +0 -0
  39. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_context_bleed.py +0 -0
  40. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_conversation_identity.py +0 -0
  41. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_conversation_lifecycle.py +0 -0
  42. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_conversation_scoping.py +0 -0
  43. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_embedding_tag_generator.py +0 -0
  44. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_empty_turn_skip.py +0 -0
  45. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_engine_integration.py +0 -0
  46. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_engine_lookback.py +0 -0
  47. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_engine_state.py +0 -0
  48. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_fact_enrichment.py +0 -0
  49. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_fact_graph_integration.py +0 -0
  50. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_fact_link_checker.py +0 -0
  51. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_fact_link_query.py +0 -0
  52. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_fact_link_types.py +0 -0
  53. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_fact_links_sqlite.py +0 -0
  54. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_find_quote.py +0 -0
  55. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_headless.py +0 -0
  56. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_history_filter.py +0 -0
  57. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_idf_retrieval.py +0 -0
  58. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_ingest_index_integrity.py +0 -0
  59. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_longmemeval_auth.py +0 -0
  60. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_mcp_server.py +0 -0
  61. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_message_filter.py +0 -0
  62. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_metrics_persistence.py +0 -0
  63. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_model_catalog.py +0 -0
  64. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_model_limits.py +0 -0
  65. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_monitor.py +0 -0
  66. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_multi_instance.py +0 -0
  67. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_noop_fact_link_store.py +0 -0
  68. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_openrouter_provider.py +0 -0
  69. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_paging.py +0 -0
  70. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_passthrough_filter.py +0 -0
  71. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_presets.py +0 -0
  72. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_prev_context_leak.py +0 -0
  73. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_provider_adapters.py +0 -0
  74. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_proxy.py +0 -0
  75. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_proxy_dashboard.py +0 -0
  76. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_proxy_formats.py +0 -0
  77. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_proxy_message_filter.py +0 -0
  78. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_proxy_session.py +0 -0
  79. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_proxy_streaming.py +0 -0
  80. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_raw_content.py +0 -0
  81. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_recall_all.py +0 -0
  82. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_request_captures_persistence.py +0 -0
  83. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_retriever.py +0 -0
  84. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_rrf_scoring.py +0 -0
  85. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_segmenter.py +0 -0
  86. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_semantic_search.py +0 -0
  87. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_sender_identity.py +0 -0
  88. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_session_cache.py +0 -0
  89. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_session_date.py +0 -0
  90. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_storage_protocols.py +0 -0
  91. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_store_sqlite.py +0 -0
  92. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_stub_turn_handling.py +0 -0
  93. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_supersession.py +0 -0
  94. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_supersession_migration.py +0 -0
  95. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tag_canonicalizer.py +0 -0
  96. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tag_consolidator.py +0 -0
  97. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tag_generator.py +0 -0
  98. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tag_splitter.py +0 -0
  99. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_telemetry.py +0 -0
  100. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_telemetry_integration.py +0 -0
  101. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tool_loop.py +0 -0
  102. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tool_output_interceptor.py +0 -0
  103. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tool_result_filter.py +0 -0
  104. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tool_tags.py +0 -0
  105. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_tui.py +0 -0
  106. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_turn_tag_index.py +0 -0
  107. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_unified_budget.py +0 -0
  108. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_upstream_trim.py +0 -0
  109. {virtual_context-0.2.2 → virtual_context-0.2.3}/tests/test_verb_expansion.py +0 -0
  110. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual-context.yaml.example +0 -0
  111. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/cli/__init__.py +0 -0
  112. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/cli/main.py +0 -0
  113. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/config.py +0 -0
  114. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/conversation_identity.py +0 -0
  115. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/__init__.py +0 -0
  116. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/assembler.py +0 -0
  117. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/compaction_pipeline.py +0 -0
  118. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/compactor.py +0 -0
  119. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/composite_store.py +0 -0
  120. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/conversation_store.py +0 -0
  121. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/embedding_provider.py +0 -0
  122. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/embedding_tag_generator.py +0 -0
  123. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/engine_utils.py +0 -0
  124. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/fact_query.py +0 -0
  125. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/fts_preprocessor.py +0 -0
  126. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/llm_utils.py +0 -0
  127. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/math_utils.py +0 -0
  128. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/model_catalog.py +0 -0
  129. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/monitor.py +0 -0
  130. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/paging_manager.py +0 -0
  131. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/protocols.py +0 -0
  132. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/provider_adapters.py +0 -0
  133. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/quote_search.py +0 -0
  134. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/retrieval_assembler.py +0 -0
  135. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/retrieval_scoring.py +0 -0
  136. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/retriever.py +0 -0
  137. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/search_engine.py +0 -0
  138. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/segmenter.py +0 -0
  139. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/semantic_search.py +0 -0
  140. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/store.py +0 -0
  141. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/tag_canonicalizer.py +0 -0
  142. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/tag_consolidator.py +0 -0
  143. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/tag_generator.py +0 -0
  144. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/tag_scoring.py +0 -0
  145. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/tag_splitter.py +0 -0
  146. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/tagging_pipeline.py +0 -0
  147. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/telemetry.py +0 -0
  148. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/temporal_resolver.py +0 -0
  149. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/tool_loop.py +0 -0
  150. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/tool_query.py +0 -0
  151. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/core/turn_tag_index.py +0 -0
  152. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/data/anthropic-tokenizer/tokenizer.json +0 -0
  153. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/engine.py +0 -0
  154. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/ingest/__init__.py +0 -0
  155. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/ingest/curator.py +0 -0
  156. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/ingest/date_resolver.py +0 -0
  157. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/ingest/parsers.py +0 -0
  158. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/ingest/supersession.py +0 -0
  159. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/mcp/__init__.py +0 -0
  160. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/mcp/server.py +0 -0
  161. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/model_limits.py +0 -0
  162. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/openclaw/virtual-context.mjs +0 -0
  163. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/patterns.py +0 -0
  164. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/presets/__init__.py +0 -0
  165. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/presets/agentic.py +0 -0
  166. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/presets/base.py +0 -0
  167. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/presets/coding.py +0 -0
  168. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/providers/__init__.py +0 -0
  169. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/providers/anthropic.py +0 -0
  170. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/providers/base.py +0 -0
  171. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/providers/generic_openai.py +0 -0
  172. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/providers/ollama_native.py +0 -0
  173. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/__init__.py +0 -0
  174. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/_envelope.py +0 -0
  175. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/dashboard.html +0 -0
  176. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/dashboard.py +0 -0
  177. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/formats.py +0 -0
  178. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/helpers.py +0 -0
  179. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/metrics.py +0 -0
  180. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/multi.py +0 -0
  181. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/registry.py +0 -0
  182. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/session_cache.py +0 -0
  183. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/state.py +0 -0
  184. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/static/android-chrome-192x192.png +0 -0
  185. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/static/android-chrome-512x512.png +0 -0
  186. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/static/apple-touch-icon.png +0 -0
  187. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/static/favicon-16x16.png +0 -0
  188. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/static/favicon-32x32.png +0 -0
  189. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/static/favicon.ico +0 -0
  190. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/static/site.webmanifest +0 -0
  191. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/proxy/tool_output_interceptor.py +0 -0
  192. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/storage/__init__.py +0 -0
  193. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/storage/falkordb.py +0 -0
  194. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/storage/filesystem.py +0 -0
  195. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/storage/helpers.py +0 -0
  196. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/storage/neo4j.py +0 -0
  197. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/storage/noop_fact_link_store.py +0 -0
  198. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/storage/postgres.py +0 -0
  199. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/storage/sqlite.py +0 -0
  200. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/token_counter.py +0 -0
  201. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/__init__.py +0 -0
  202. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/app.py +0 -0
  203. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/chat.tcss +0 -0
  204. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/chat_provider.py +0 -0
  205. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/headless.py +0 -0
  206. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/modals/__init__.py +0 -0
  207. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/modals/turn_inspector.py +0 -0
  208. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/state.py +0 -0
  209. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/widgets/__init__.py +0 -0
  210. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/widgets/budget_bar.py +0 -0
  211. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/widgets/chat_view.py +0 -0
  212. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/widgets/input_box.py +0 -0
  213. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/widgets/tag_panel.py +0 -0
  214. {virtual_context-0.2.2 → virtual_context-0.2.3}/virtual_context/tui/widgets/turn_list.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: virtual-context
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: OS-style virtual memory for LLM session context management
5
5
  Project-URL: Homepage, https://virtual-context.com
6
6
  Project-URL: Repository, https://github.com/virtual-context/virtual-context
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "virtual-context"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "OS-style virtual memory for LLM session context management"
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-or-later"
@@ -14,7 +14,7 @@ from .types import (
14
14
  VirtualContextConfig,
15
15
  )
16
16
 
17
- __version__ = "0.2.1"
17
+ __version__ = "0.2.3"
18
18
 
19
19
  __all__ = [
20
20
  "VirtualContextEngine",
@@ -59,7 +59,9 @@ def build_autonomous_hint(
59
59
  "To find detailed information you have the following tools:\n"
60
60
  "- vc_restore_tool(ref): ALL compacted tool turns can be restored to "
61
61
  "full fidelity — thinking, tool calls, and raw output. Use the ref "
62
- "from the compacted stub. This is the fastest way to get exact data.\n"
62
+ "from the compacted stub. This is the fastest way to get exact data "
63
+ "like file paths, directory listings, code blocks, command output, "
64
+ "and search results. Summaries never contain these — restore first.\n"
63
65
  "- vc_find_quote(query): search raw text across ALL topics.\n"
64
66
  "- vc_query_facts(subject?, verb?, status?, object_contains?): "
65
67
  "structured fact lookup.\n"
@@ -73,7 +75,9 @@ def build_autonomous_hint(
73
75
  "For counting/listing questions: scan [all topics] for every topic "
74
76
  "that could relate — items are often spread across unrelated topics.\n"
75
77
  "If a search already returned the answer, stop and respond.\n"
76
- "Never answer without searching first.\n"
78
+ "Virtual context tools allow you to search and restore full "
79
+ "conversational depth and previously compacted tool calls. "
80
+ "Use liberally to answer the user's question.\n"
77
81
  "FACT vs SUMMARY: The <facts> block contains structured events with "
78
82
  "statuses (completed, planned, active). Summaries describe topics "
79
83
  "DISCUSSED — they include plans, itineraries, and ideas that may "
@@ -165,7 +165,30 @@ def _restore_chain_in_place(
165
165
  and _msg_text_contains(msg, "[Compacted turn")
166
166
  and _msg_text_contains(messages[i + 1], ref)
167
167
  ):
168
- new_messages.extend(chain_messages)
168
+ # Prefix first user message so model knows this was recovered
169
+ _tagged = list(chain_messages)
170
+ for _ci, _cm in enumerate(_tagged):
171
+ if isinstance(_cm, dict) and _cm.get("role") in ("user", "human"):
172
+ _cm = dict(_cm) # shallow copy
173
+ _cc = _cm.get("content", "")
174
+ _prefix = (
175
+ "[Previously compacted — restored by vc_restore_tool. "
176
+ "This content was NOT visible before this restore. "
177
+ "It was not there. Use the recovered content directly "
178
+ "to answer the user's question.]\n"
179
+ )
180
+ if isinstance(_cc, str):
181
+ _cm["content"] = _prefix + _cc
182
+ elif isinstance(_cc, list) and _cc:
183
+ _first = _cc[0]
184
+ if isinstance(_first, dict) and _first.get("type") == "text":
185
+ _cc = list(_cc)
186
+ _cc[0] = dict(_first)
187
+ _cc[0]["text"] = _prefix + _cc[0].get("text", "")
188
+ _cm["content"] = _cc
189
+ _tagged[_ci] = _cm
190
+ break
191
+ new_messages.extend(_tagged)
169
192
  i += 2 # skip the stub pair
170
193
  found = True
171
194
  else:
@@ -242,7 +265,11 @@ class _ProxyToolRuntime:
242
265
  )
243
266
  if not restored:
244
267
  return {"error": f"stub for ref {ref} not found in current payload"}
245
- return {"restored": True, "ref": ref}
268
+ return (
269
+ "Restored. The tool output was compacted and has been recovered "
270
+ "into your conversation history above. Use the recovered content "
271
+ "to answer the user's question."
272
+ )
246
273
 
247
274
  def _restore_chain(self, ref: str) -> dict:
248
275
  """Restore a chain stub pair to the full message chain."""
@@ -299,6 +326,54 @@ class _ProxyToolRuntime:
299
326
  # All content was tool_use — drop the message entirely
300
327
  chain = chain[:-1]
301
328
 
329
+ # Strip leading orphaned tool_result blocks from the first message.
330
+ # When the previous chain's trailing tool_use was stripped above,
331
+ # this chain's first user message may reference those gone IDs.
332
+ # Collect tool_use IDs present in this chain to know which are valid.
333
+ if chain:
334
+ _chain_tool_use_ids: set[str] = set()
335
+ for _cm in chain:
336
+ if not isinstance(_cm, dict):
337
+ continue
338
+ _cc = _cm.get("content", [])
339
+ if isinstance(_cc, list):
340
+ for _cb in _cc:
341
+ if (
342
+ isinstance(_cb, dict)
343
+ and _cb.get("type") == "tool_use"
344
+ ):
345
+ _tid = _cb.get("id")
346
+ if _tid:
347
+ _chain_tool_use_ids.add(_tid)
348
+
349
+ first = chain[0]
350
+ if isinstance(first, dict) and first.get("role") in ("user", "human"):
351
+ fc = first.get("content", [])
352
+ if isinstance(fc, list):
353
+ orphaned = [
354
+ b for b in fc
355
+ if (
356
+ isinstance(b, dict)
357
+ and b.get("type") == "tool_result"
358
+ and b.get("tool_use_id") not in _chain_tool_use_ids
359
+ )
360
+ ]
361
+ if orphaned:
362
+ cleaned_first = [
363
+ b for b in fc
364
+ if not (
365
+ isinstance(b, dict)
366
+ and b.get("type") == "tool_result"
367
+ and b.get("tool_use_id") not in _chain_tool_use_ids
368
+ )
369
+ ]
370
+ if cleaned_first:
371
+ first = dict(first)
372
+ first["content"] = cleaned_first
373
+ chain[0] = first
374
+ else:
375
+ chain = chain[1:]
376
+
302
377
  target = self._get_target_body()
303
378
  if not isinstance(target, dict):
304
379
  return {"error": "no mutable payload available for chain restore"}
@@ -307,11 +382,13 @@ class _ProxyToolRuntime:
307
382
  restored = _restore_chain_in_place(target, fmt, ref, chain)
308
383
  if not restored:
309
384
  return {"error": f"stub pair for chain ref {ref} not found in payload"}
310
- return {
311
- "restored": True,
312
- "ref": ref,
313
- "messages_restored": len(chain),
314
- }
385
+ return (
386
+ f"Restored. {len(chain)} messages recovered from compacted storage "
387
+ f"and spliced into your conversation history above. This content was "
388
+ f"previously compacted — it was NOT visible before this restore. "
389
+ f"Do not apologize for not seeing it earlier; it was not there. "
390
+ f"Use the recovered content directly to answer the user's question."
391
+ )
315
392
 
316
393
  def _rehydrate_tool_results_in_message(
317
394
  self, msg: dict, tool_refs: list[str],
@@ -664,7 +741,9 @@ async def _handle_streaming(
664
741
  "text": "".join(current_text_parts),
665
742
  })
666
743
  current_text_parts = []
667
- forwarded_block_count += 1
744
+ # Count ALL forwarded blocks (text, thinking, etc.)
745
+ # so continuation emits use correct indices.
746
+ forwarded_block_count += 1
668
747
 
669
748
  # -- message_delta: extract output_tokens --
670
749
  elif dtype == "message_delta":
@@ -1142,7 +1221,19 @@ async def _handle_streaming(
1142
1221
 
1143
1222
  for tb in text_blocks:
1144
1223
  t = tb.get("text", "")
1145
- if t:
1224
+ if not t:
1225
+ continue
1226
+ # Non-streaming continuations may embed
1227
+ # <thinking>...</thinking> as literal text.
1228
+ # Split into proper thinking + text blocks.
1229
+ import re as _re
1230
+ _think_match = _re.match(
1231
+ r"<thinking>([\s\S]*?)</thinking>\s*",
1232
+ t,
1233
+ )
1234
+ if _think_match:
1235
+ t = t[_think_match.end():]
1236
+ if t.strip():
1146
1237
  text_chunks.append(t)
1147
1238
  for sse_evt in _emit_text_as_sse(
1148
1239
  t, forwarded_block_count,
@@ -1236,9 +1327,6 @@ async def _handle_streaming(
1236
1327
  payload_tokens=state._last_enriched_payload_tokens or None,
1237
1328
  turn_id=turn_id,
1238
1329
  )
1239
- _marker_sid = state.engine.config.conversation_id
1240
- _fmt = get_format(api_format)
1241
- yield _fmt.emit_conversation_marker_sse(_marker_sid)
1242
1330
 
1243
1331
  if session_log_path and state:
1244
1332
  _dump_session_state(state, session_log_path)
@@ -32,6 +32,68 @@ def _is_tool_result_only_user(msg: dict) -> bool:
32
32
  return bool(ctypes and ctypes <= {"tool_result"})
33
33
 
34
34
 
35
+ def sanitize_vc_tool_errors(body: dict, fmt: PayloadFormat) -> dict:
36
+ """Replace stale vc_restore_tool error results with benign acknowledgements.
37
+
38
+ When the client SDK (e.g. Claude Code) didn't recognise vc_restore_tool,
39
+ it returned ``<tool_use_error>`` XML. These errors poison the conversation
40
+ history — the model sees them and refuses to call the tool again. Replace
41
+ them with a neutral message so future calls aren't inhibited.
42
+ """
43
+ # Determine message key by format name
44
+ _fname = fmt.name
45
+ if _fname == "gemini":
46
+ msg_key = "contents"
47
+ elif _fname == "openai_responses":
48
+ msg_key = "input"
49
+ else:
50
+ msg_key = "messages"
51
+ messages = body.get(msg_key) if msg_key else None
52
+ if not messages or not isinstance(messages, list):
53
+ return body
54
+
55
+ _ERROR_NEEDLE = "No such tool available: vc_restore_tool"
56
+ _REPLACEMENT = (
57
+ "The restore was handled internally. The original content is "
58
+ "available in your conversation history above."
59
+ )
60
+ changed = False
61
+
62
+ for msg in messages:
63
+ if not isinstance(msg, dict):
64
+ continue
65
+ content = msg.get("content")
66
+ if isinstance(content, str) and _ERROR_NEEDLE in content:
67
+ msg["content"] = _REPLACEMENT
68
+ if "is_error" in msg:
69
+ del msg["is_error"]
70
+ changed = True
71
+ continue
72
+ if not isinstance(content, list):
73
+ continue
74
+ for block in content:
75
+ if not isinstance(block, dict):
76
+ continue
77
+ # Anthropic tool_result blocks
78
+ if block.get("type") == "tool_result":
79
+ bc = block.get("content", "")
80
+ if isinstance(bc, str) and _ERROR_NEEDLE in bc:
81
+ block["content"] = _REPLACEMENT
82
+ if "is_error" in block:
83
+ del block["is_error"]
84
+ changed = True
85
+ # OpenAI function_call_output
86
+ if block.get("type") == "function_call_output":
87
+ out = block.get("output", "")
88
+ if isinstance(out, str) and _ERROR_NEEDLE in out:
89
+ block["output"] = _REPLACEMENT
90
+ changed = True
91
+
92
+ if changed:
93
+ logger.info("SANITIZE: replaced stale vc_restore_tool error(s) in history")
94
+ return body
95
+
96
+
35
97
  def _consume_responses_tool_round(
36
98
  messages: list[dict],
37
99
  start: int,
@@ -1616,7 +1678,8 @@ def collapse_turn_chains(
1616
1678
  # Build explicit restore instruction
1617
1679
  stub_text = (
1618
1680
  f"[{desc_line}.\n"
1619
- f'To restore full output: {{"type": "tool_use", "name": "vc_restore_tool", '
1681
+ f'To restore and uncompact full tool call results in place: '
1682
+ f'{{"type": "tool_use", "name": "vc_restore_tool", '
1620
1683
  f'"input": {{"ref": "{ref}"}}}}]'
1621
1684
  )
1622
1685
  stub_asst = {
@@ -1635,11 +1698,26 @@ def collapse_turn_chains(
1635
1698
  # 7. Build new message list with collapsed chains
1636
1699
  # ------------------------------------------------------------------
1637
1700
  # Collect all global indices that belong to collapsed chains
1701
+ # and all tool_use IDs within those chains (for orphan cleanup).
1638
1702
  collapsed_indices: set[int] = set()
1703
+ collapsed_tool_use_ids: set[str] = set()
1639
1704
  for chain_idx, (ref, stub_user, stub_asst) in collapse_map.items():
1640
1705
  chain = pairs[chain_idx]
1641
1706
  for gi in chain:
1642
1707
  collapsed_indices.add(gi)
1708
+ msg = messages[gi]
1709
+ if not isinstance(msg, dict):
1710
+ continue
1711
+ content = msg.get("content", [])
1712
+ if isinstance(content, list):
1713
+ for block in content:
1714
+ if (
1715
+ isinstance(block, dict)
1716
+ and block.get("type") == "tool_use"
1717
+ ):
1718
+ _tid = block.get("id")
1719
+ if _tid:
1720
+ collapsed_tool_use_ids.add(_tid)
1643
1721
 
1644
1722
  # Build index → insertion stubs map
1645
1723
  insert_at: dict[int, tuple[dict, dict]] = {}
@@ -1655,7 +1733,23 @@ def collapse_turn_chains(
1655
1733
  new_messages.append(stub_asst)
1656
1734
  # else: skip (interior of a collapsed chain)
1657
1735
  else:
1658
- new_messages.append(messages[mi])
1736
+ msg = messages[mi]
1737
+ # Strip orphaned tool_result blocks whose tool_use was collapsed
1738
+ if collapsed_tool_use_ids and isinstance(msg, dict):
1739
+ content = msg.get("content", [])
1740
+ if isinstance(content, list):
1741
+ cleaned = [
1742
+ block for block in content
1743
+ if not (
1744
+ isinstance(block, dict)
1745
+ and block.get("type") == "tool_result"
1746
+ and block.get("tool_use_id") in collapsed_tool_use_ids
1747
+ )
1748
+ ]
1749
+ if len(cleaned) != len(content):
1750
+ msg = dict(msg)
1751
+ msg["content"] = cleaned if cleaned else [{"type": "text", "text": "[tool results removed — parent tool call was compacted]"}]
1752
+ new_messages.append(msg)
1659
1753
 
1660
1754
  body = dict(body)
1661
1755
  body[_msg_key] = new_messages
@@ -455,6 +455,7 @@ async def prepare_payload(
455
455
  pre_filter_body=None,
456
456
  paging_enabled=False,
457
457
  tool_output_find_quote=False,
458
+ restore_tool_injected=False,
458
459
  inbound_bytes=_inbound_bytes,
459
460
  outbound_bytes=_outbound_bytes,
460
461
  )
@@ -684,6 +685,7 @@ async def prepare_payload(
684
685
  logger.info("TOOL-OUTPUT Injected vc_find_quote tool for truncated output retrieval")
685
686
 
686
687
  # Inject vc_restore_tool when stubs are present but paging didn't already inject it
688
+ _restore_tool_injected = False
687
689
  if _tool_stubs_present and not paging_enabled and fmt.supports_tool_interception:
688
690
  existing_names = {t.get("name") for t in enriched_body.get("tools", []) if isinstance(t, dict)}
689
691
  if "vc_restore_tool" not in existing_names:
@@ -691,8 +693,15 @@ async def prepare_payload(
691
693
  _restore_def = [d for d in vc_tool_definitions() if d["name"] == "vc_restore_tool"]
692
694
  if _restore_def:
693
695
  enriched_body = fmt.inject_tools(enriched_body, _restore_def)
696
+ _restore_tool_injected = True
694
697
  logger.info("TOOL-STUB Injected vc_restore_tool for stub restoration")
695
698
 
699
+ # Sanitize stale vc_restore_tool errors from history so the model isn't
700
+ # poisoned by previous client-side "No such tool" rejections.
701
+ if _restore_tool_injected or paging_enabled:
702
+ from .message_filter import sanitize_vc_tool_errors
703
+ enriched_body = sanitize_vc_tool_errors(enriched_body, fmt)
704
+
696
705
  # Track enriched payload size
697
706
  if state:
698
707
  state._last_enriched_payload_kb = round(len(json.dumps(enriched_body)) / 1024, 1)
@@ -1006,6 +1015,7 @@ async def prepare_payload(
1006
1015
  pre_filter_body=_pre_filter_body,
1007
1016
  paging_enabled=paging_enabled,
1008
1017
  tool_output_find_quote=tool_output_find_quote,
1018
+ restore_tool_injected=_restore_tool_injected,
1009
1019
  inbound_bytes=_inbound_bytes,
1010
1020
  outbound_bytes=_outbound_bytes,
1011
1021
  )
@@ -1387,7 +1397,7 @@ def create_app(
1387
1397
  request_log_dir=_effective_log_dir, log_prefix=_log_prefix,
1388
1398
  )
1389
1399
  else:
1390
- _intercept_vc_tools = result.paging_enabled or result.tool_output_find_quote
1400
+ _intercept_vc_tools = result.paging_enabled or result.tool_output_find_quote or result.restore_tool_injected
1391
1401
 
1392
1402
  if result.is_streaming:
1393
1403
  return await _handle_streaming(
@@ -904,6 +904,7 @@ class PreparedPayload:
904
904
  pre_filter_body: dict | None # body before filtering (for metrics capture)
905
905
  paging_enabled: bool
906
906
  tool_output_find_quote: bool
907
+ restore_tool_injected: bool
907
908
  inbound_bytes: int
908
909
  outbound_bytes: int
909
910
  metadata: dict = field(default_factory=dict) # catch-all for anything else
File without changes