virtual-context 0.2.7__tar.gz → 0.2.9__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 (225) hide show
  1. {virtual_context-0.2.7 → virtual_context-0.2.9}/PKG-INFO +13 -3
  2. {virtual_context-0.2.7 → virtual_context-0.2.9}/README.md +12 -2
  3. {virtual_context-0.2.7 → virtual_context-0.2.9}/pyproject.toml +1 -1
  4. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_cli_init.py +40 -0
  5. virtual_context-0.2.9/tests/test_fact_redesign.py +95 -0
  6. virtual_context-0.2.9/tests/test_fill_pass.py +274 -0
  7. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_format_agnostic.py +3 -3
  8. virtual_context-0.2.9/tests/test_store_recovery.py +192 -0
  9. virtual_context-0.2.9/virtual-context.yaml +109 -0
  10. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/__init__.py +1 -1
  11. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/cli/main.py +95 -4
  12. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/config.py +1 -0
  13. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/assembler.py +70 -33
  14. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/compaction_pipeline.py +8 -2
  15. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/compactor.py +12 -0
  16. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/composite_store.py +9 -0
  17. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/conversation_store.py +1 -0
  18. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/protocols.py +3 -0
  19. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/retriever.py +12 -5
  20. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/store.py +16 -0
  21. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_generator.py +8 -0
  22. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/engine.py +1 -0
  23. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/dashboard.py +1 -1
  24. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/formats.py +247 -51
  25. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/handlers.py +22 -7
  26. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/message_filter.py +365 -10
  27. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/metrics.py +25 -1
  28. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/server.py +127 -30
  29. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/state.py +2 -2
  30. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/falkordb.py +9 -0
  31. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/filesystem.py +9 -0
  32. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/neo4j.py +11 -0
  33. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/postgres.py +32 -0
  34. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/sqlite.py +83 -0
  35. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/types.py +8 -0
  36. {virtual_context-0.2.7 → virtual_context-0.2.9}/.gitignore +0 -0
  37. {virtual_context-0.2.7 → virtual_context-0.2.9}/LICENSE +0 -0
  38. {virtual_context-0.2.7 → virtual_context-0.2.9}/assets/dashboard.png +0 -0
  39. {virtual_context-0.2.7 → virtual_context-0.2.9}/assets/hero.png +0 -0
  40. {virtual_context-0.2.7 → virtual_context-0.2.9}/models.yaml +0 -0
  41. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/REGRESSION_MAP.md +0 -0
  42. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/conftest.py +0 -0
  43. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/docker-compose.test.yml +0 -0
  44. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/__init__.py +0 -0
  45. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/conftest.py +0 -0
  46. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/test_compaction.py +0 -0
  47. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/test_retrieval.py +0 -0
  48. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/haiku/test_tagging.py +0 -0
  49. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/__init__.py +0 -0
  50. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/conftest.py +0 -0
  51. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/test_compactor.py +0 -0
  52. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/test_pipeline.py +0 -0
  53. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/test_provider.py +0 -0
  54. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/ollama/test_tag_generator.py +0 -0
  55. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/proxy/__init__.py +0 -0
  56. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/proxy/test_dashboard_cors.py +0 -0
  57. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/proxy/test_metrics.py +0 -0
  58. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_assembler.py +0 -0
  59. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_backend_integration.py +0 -0
  60. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_budget_enforcement.py +0 -0
  61. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_compaction_commit_prune.py +0 -0
  62. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_compactor.py +0 -0
  63. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_compactor_concurrent.py +0 -0
  64. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_composite_store.py +0 -0
  65. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_config.py +0 -0
  66. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_context_bleed.py +0 -0
  67. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_conversation_identity.py +0 -0
  68. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_conversation_lifecycle.py +0 -0
  69. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_conversation_scoping.py +0 -0
  70. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_embedding_tag_generator.py +0 -0
  71. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_empty_turn_skip.py +0 -0
  72. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_engine_integration.py +0 -0
  73. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_engine_lookback.py +0 -0
  74. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_engine_state.py +0 -0
  75. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_enrichment.py +0 -0
  76. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_graph_integration.py +0 -0
  77. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_link_checker.py +0 -0
  78. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_link_query.py +0 -0
  79. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_link_types.py +0 -0
  80. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_fact_links_sqlite.py +0 -0
  81. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_find_quote.py +0 -0
  82. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_headless.py +0 -0
  83. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_history_filter.py +0 -0
  84. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_idf_retrieval.py +0 -0
  85. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_ingest_index_integrity.py +0 -0
  86. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_longmemeval_auth.py +0 -0
  87. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_mcp_server.py +0 -0
  88. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_media.py +0 -0
  89. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_message_filter.py +0 -0
  90. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_metrics_persistence.py +0 -0
  91. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_model_catalog.py +0 -0
  92. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_model_limits.py +0 -0
  93. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_monitor.py +0 -0
  94. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_multi_instance.py +0 -0
  95. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_noop_fact_link_store.py +0 -0
  96. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_openrouter_provider.py +0 -0
  97. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_paging.py +0 -0
  98. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_passthrough_filter.py +0 -0
  99. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_presets.py +0 -0
  100. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_prev_context_leak.py +0 -0
  101. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_provider_adapters.py +0 -0
  102. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy.py +0 -0
  103. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_dashboard.py +0 -0
  104. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_formats.py +0 -0
  105. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_message_filter.py +0 -0
  106. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_session.py +0 -0
  107. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_proxy_streaming.py +0 -0
  108. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_raw_content.py +0 -0
  109. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_recall_all.py +0 -0
  110. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_registry_lifecycle.py +0 -0
  111. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_request_captures_persistence.py +0 -0
  112. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_retriever.py +0 -0
  113. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_rrf_scoring.py +0 -0
  114. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_segmenter.py +0 -0
  115. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_semantic_search.py +0 -0
  116. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_sender_identity.py +0 -0
  117. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_session_cache.py +0 -0
  118. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_session_date.py +0 -0
  119. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_storage_protocols.py +0 -0
  120. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_store_sqlite.py +0 -0
  121. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_stub_turn_handling.py +0 -0
  122. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_supersession.py +0 -0
  123. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_supersession_migration.py +0 -0
  124. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tag_canonicalizer.py +0 -0
  125. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tag_consolidator.py +0 -0
  126. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tag_generator.py +0 -0
  127. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tag_splitter.py +0 -0
  128. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_telemetry.py +0 -0
  129. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_telemetry_integration.py +0 -0
  130. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tool_loop.py +0 -0
  131. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tool_output_interceptor.py +0 -0
  132. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tool_result_filter.py +0 -0
  133. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tool_tags.py +0 -0
  134. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_tui.py +0 -0
  135. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_turn_grouping.py +0 -0
  136. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_turn_tag_index.py +0 -0
  137. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_unified_budget.py +0 -0
  138. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_upstream_trim.py +0 -0
  139. {virtual_context-0.2.7 → virtual_context-0.2.9}/tests/test_verb_expansion.py +0 -0
  140. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual-context.yaml.example +0 -0
  141. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/cli/__init__.py +0 -0
  142. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/conversation_identity.py +0 -0
  143. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/__init__.py +0 -0
  144. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/embedding_provider.py +0 -0
  145. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/embedding_tag_generator.py +0 -0
  146. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/engine_utils.py +0 -0
  147. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/fact_query.py +0 -0
  148. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/fts_preprocessor.py +0 -0
  149. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/hint_builder.py +0 -0
  150. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/llm_utils.py +0 -0
  151. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/math_utils.py +0 -0
  152. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/model_catalog.py +0 -0
  153. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/monitor.py +0 -0
  154. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/paging_manager.py +0 -0
  155. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/provider_adapters.py +0 -0
  156. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/quote_search.py +0 -0
  157. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/retrieval_assembler.py +0 -0
  158. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/retrieval_scoring.py +0 -0
  159. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/search_engine.py +0 -0
  160. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/segmenter.py +0 -0
  161. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/semantic_search.py +0 -0
  162. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_canonicalizer.py +0 -0
  163. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_consolidator.py +0 -0
  164. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_scoring.py +0 -0
  165. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tag_splitter.py +0 -0
  166. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tagging_pipeline.py +0 -0
  167. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/telemetry.py +0 -0
  168. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/temporal_resolver.py +0 -0
  169. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tool_loop.py +0 -0
  170. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/tool_query.py +0 -0
  171. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/core/turn_tag_index.py +0 -0
  172. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/data/anthropic-tokenizer/tokenizer.json +0 -0
  173. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/__init__.py +0 -0
  174. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/curator.py +0 -0
  175. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/date_resolver.py +0 -0
  176. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/parsers.py +0 -0
  177. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/ingest/supersession.py +0 -0
  178. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/mcp/__init__.py +0 -0
  179. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/mcp/server.py +0 -0
  180. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/model_limits.py +0 -0
  181. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/openclaw/virtual-context.mjs +0 -0
  182. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/patterns.py +0 -0
  183. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/presets/__init__.py +0 -0
  184. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/presets/agentic.py +0 -0
  185. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/presets/base.py +0 -0
  186. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/presets/coding.py +0 -0
  187. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/__init__.py +0 -0
  188. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/anthropic.py +0 -0
  189. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/base.py +0 -0
  190. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/generic_openai.py +0 -0
  191. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/providers/ollama_native.py +0 -0
  192. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/__init__.py +0 -0
  193. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/_envelope.py +0 -0
  194. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/dashboard.html +0 -0
  195. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/helpers.py +0 -0
  196. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/media.py +0 -0
  197. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/multi.py +0 -0
  198. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/registry.py +0 -0
  199. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/session_cache.py +0 -0
  200. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/android-chrome-192x192.png +0 -0
  201. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/android-chrome-512x512.png +0 -0
  202. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/apple-touch-icon.png +0 -0
  203. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/favicon-16x16.png +0 -0
  204. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/favicon-32x32.png +0 -0
  205. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/favicon.ico +0 -0
  206. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/static/site.webmanifest +0 -0
  207. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/proxy/tool_output_interceptor.py +0 -0
  208. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/__init__.py +0 -0
  209. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/helpers.py +0 -0
  210. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/storage/noop_fact_link_store.py +0 -0
  211. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/token_counter.py +0 -0
  212. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/__init__.py +0 -0
  213. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/app.py +0 -0
  214. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/chat.tcss +0 -0
  215. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/chat_provider.py +0 -0
  216. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/headless.py +0 -0
  217. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/modals/__init__.py +0 -0
  218. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/modals/turn_inspector.py +0 -0
  219. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/state.py +0 -0
  220. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/__init__.py +0 -0
  221. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/budget_bar.py +0 -0
  222. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/chat_view.py +0 -0
  223. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/input_box.py +0 -0
  224. {virtual_context-0.2.7 → virtual_context-0.2.9}/virtual_context/tui/widgets/tag_panel.py +0 -0
  225. {virtual_context-0.2.7 → virtual_context-0.2.9}/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.7
3
+ Version: 0.2.9
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
@@ -180,10 +180,16 @@ proxy:
180
180
  **Daemon mode:** run as a background service:
181
181
 
182
182
  ```bash
183
- virtual-context onboard --install-daemon --upstream https://api.anthropic.com
183
+ # Creates config if needed, installs + starts daemon
184
+ virtual-context daemon install --upstream https://api.anthropic.com
185
+
186
+ # Or: guided interactive setup with daemon
187
+ virtual-context onboard --wizard --install-daemon
184
188
  ```
185
189
 
186
- Daemon setup docs (macOS `launchd`, Linux `systemd --user`, Windows Task Scheduler): [`docs/install.md`](docs/install.md)
190
+ Daemon lifecycle: `daemon status | start | stop | restart | uninstall`
191
+
192
+ Full setup docs (macOS `launchd`, Linux `systemd --user`, Windows Task Scheduler): [`docs/install.md`](docs/install.md)
187
193
 
188
194
  ### Python SDK
189
195
 
@@ -639,10 +645,14 @@ virtual-context transform -m "What about auth?"# tag + retrieve + assemble
639
645
  virtual-context aliases list # show all tag aliases
640
646
  virtual-context aliases suggest # auto-detect potential aliases
641
647
  virtual-context aliases add db database # register alias manually
648
+ virtual-context init coding # create config from preset
649
+ virtual-context onboard # guided setup (create/validate config)
650
+ virtual-context onboard --wizard # interactive multi-instance setup
642
651
  virtual-context proxy -u https://api.anthropic.com # single-instance proxy
643
652
  virtual-context proxy # multi-instance (from config)
644
653
  virtual-context presets list # list available config presets
645
654
  virtual-context presets show coding # dump preset config as YAML
655
+ virtual-context daemon install # install daemon/service
646
656
  virtual-context daemon status # service status (platform-specific)
647
657
  virtual-context daemon start # start/enable daemon
648
658
  virtual-context daemon stop # stop daemon
@@ -133,10 +133,16 @@ proxy:
133
133
  **Daemon mode:** run as a background service:
134
134
 
135
135
  ```bash
136
- virtual-context onboard --install-daemon --upstream https://api.anthropic.com
136
+ # Creates config if needed, installs + starts daemon
137
+ virtual-context daemon install --upstream https://api.anthropic.com
138
+
139
+ # Or: guided interactive setup with daemon
140
+ virtual-context onboard --wizard --install-daemon
137
141
  ```
138
142
 
139
- Daemon setup docs (macOS `launchd`, Linux `systemd --user`, Windows Task Scheduler): [`docs/install.md`](docs/install.md)
143
+ Daemon lifecycle: `daemon status | start | stop | restart | uninstall`
144
+
145
+ Full setup docs (macOS `launchd`, Linux `systemd --user`, Windows Task Scheduler): [`docs/install.md`](docs/install.md)
140
146
 
141
147
  ### Python SDK
142
148
 
@@ -592,10 +598,14 @@ virtual-context transform -m "What about auth?"# tag + retrieve + assemble
592
598
  virtual-context aliases list # show all tag aliases
593
599
  virtual-context aliases suggest # auto-detect potential aliases
594
600
  virtual-context aliases add db database # register alias manually
601
+ virtual-context init coding # create config from preset
602
+ virtual-context onboard # guided setup (create/validate config)
603
+ virtual-context onboard --wizard # interactive multi-instance setup
595
604
  virtual-context proxy -u https://api.anthropic.com # single-instance proxy
596
605
  virtual-context proxy # multi-instance (from config)
597
606
  virtual-context presets list # list available config presets
598
607
  virtual-context presets show coding # dump preset config as YAML
608
+ virtual-context daemon install # install daemon/service
599
609
  virtual-context daemon status # service status (platform-specific)
600
610
  virtual-context daemon start # start/enable daemon
601
611
  virtual-context daemon stop # stop daemon
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "virtual-context"
7
- version = "0.2.7"
7
+ version = "0.2.9"
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"
@@ -97,6 +97,46 @@ def test_daemon_restart_in_help(tmp_cwd):
97
97
  assert "restart" in result.stdout
98
98
 
99
99
 
100
+ def test_daemon_not_installed_guard(tmp_cwd):
101
+ """daemon commands should fail with a helpful message when no daemon is installed."""
102
+ env = {**__import__("os").environ, "HOME": str(tmp_cwd)}
103
+ for action in ("status", "start", "stop", "restart"):
104
+ result = subprocess.run(
105
+ [sys.executable, "-m", "virtual_context.cli.main", "daemon", action],
106
+ capture_output=True,
107
+ text=True,
108
+ env=env,
109
+ )
110
+ assert result.returncode != 0, f"daemon {action} should fail when not installed"
111
+ assert "not installed" in result.stdout.lower(), (
112
+ f"daemon {action} should mention 'not installed'"
113
+ )
114
+ assert "daemon install" in result.stdout, (
115
+ f"daemon {action} should suggest 'daemon install'"
116
+ )
117
+
118
+
119
+ def test_daemon_install_creates_config(tmp_cwd):
120
+ """daemon install should auto-create config from agentic preset if none exists."""
121
+ config_path = tmp_cwd / "virtual-context.yaml"
122
+ assert not config_path.exists()
123
+ # daemon install will fail on launchctl/systemctl, but should create config first
124
+ result = _run_cli("daemon", "install")
125
+ assert config_path.exists(), "daemon install should create config"
126
+ assert "Created config" in result.stdout
127
+ content = config_path.read_text()
128
+ assert "tag_generator" in content
129
+
130
+
131
+ def test_daemon_install_preserves_existing_config(tmp_cwd):
132
+ """daemon install should not overwrite an existing config."""
133
+ config_path = tmp_cwd / "virtual-context.yaml"
134
+ config_path.write_text("custom: true\n")
135
+ result = _run_cli("daemon", "install")
136
+ assert config_path.read_text() == "custom: true\n"
137
+ assert "Created config" not in result.stdout
138
+
139
+
100
140
  def test_presets_list(tmp_cwd):
101
141
  result = _run_cli("presets", "list")
102
142
  assert result.returncode == 0
@@ -0,0 +1,95 @@
1
+ """Tests for fact extraction redesign."""
2
+ from virtual_context.types import CompactorConfig
3
+
4
+
5
+ def test_compactor_config_has_code_mode():
6
+ cc = CompactorConfig()
7
+ assert hasattr(cc, "code_mode")
8
+ assert cc.code_mode is True
9
+
10
+
11
+ def test_config_loader_reads_code_mode():
12
+ from virtual_context.config import load_config
13
+ import tempfile, os, yaml
14
+ cfg = {"compaction": {"code_mode": False}, "context_window": 100000}
15
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".yaml", delete=False) as f:
16
+ yaml.dump(cfg, f)
17
+ tmp_path = f.name
18
+ try:
19
+ config = load_config(tmp_path)
20
+ assert config.compactor.code_mode is False
21
+ finally:
22
+ os.unlink(tmp_path)
23
+
24
+
25
+ def test_replace_facts_for_segment_default():
26
+ from virtual_context.core.store import ContextStore
27
+
28
+ class MinimalStore(ContextStore):
29
+ """Minimal concrete subclass that only implements abstract methods as no-ops."""
30
+ def store_segment(self, segment): return ""
31
+ def get_segment(self, ref, *, conversation_id=None): return None
32
+ def get_summary(self, ref, *, conversation_id=None): return None
33
+ def get_summaries_by_tags(self, tags, min_overlap=1, limit=10, before=None, after=None, conversation_id=None): return []
34
+ def search(self, query, tags=None, limit=5, conversation_id=None): return []
35
+ def get_all_tags(self, conversation_id=None): return []
36
+ def get_conversation_stats(self): return []
37
+ def get_tag_aliases(self, conversation_id=None): return {}
38
+ def set_tag_alias(self, alias, canonical, conversation_id=""): pass
39
+ def delete_segment(self, ref): return False
40
+ def cleanup(self, max_age=None, max_total_tokens=None): return 0
41
+ def save_tag_summary(self, tag_summary, conversation_id=""): pass
42
+ def get_tag_summary(self, tag, conversation_id=""): return None
43
+ def get_all_tag_summaries(self, *, conversation_id=None): return []
44
+ def search_full_text(self, query, limit=5, conversation_id=None): return []
45
+ def get_segments_by_tags(self, tags, min_overlap=1, limit=20, conversation_id=None): return []
46
+
47
+ store = MinimalStore()
48
+ deleted, inserted = store.replace_facts_for_segment("conv1", "seg1", [])
49
+ assert deleted == 0
50
+ assert inserted == 0
51
+
52
+
53
+ def test_compaction_uses_replace_facts_for_segment():
54
+ """Verify the compaction pipeline calls replace_facts_for_segment."""
55
+ import inspect
56
+ from virtual_context.core import compaction_pipeline
57
+ source = inspect.getsource(compaction_pipeline)
58
+ assert "replace_facts_for_segment" in source, \
59
+ "compaction_pipeline must call replace_facts_for_segment"
60
+
61
+
62
+ def test_code_mode_prompt_appended():
63
+ """When code_mode is True, the compactor prompt should include the code mode block."""
64
+ from virtual_context.core.compactor import CODE_MODE_FACT_PROMPT
65
+ assert "Do NOT extract intermediary" in CODE_MODE_FACT_PROMPT
66
+ assert "conclusions, findings, discoveries" in CODE_MODE_FACT_PROMPT
67
+
68
+
69
+ def test_tag_generator_accepts_code_mode():
70
+ from virtual_context.core.tag_generator import LLMTagGenerator
71
+ from virtual_context.types import TagGeneratorConfig
72
+ from unittest.mock import MagicMock
73
+ gen = LLMTagGenerator(llm_provider=MagicMock(), config=TagGeneratorConfig(), code_mode=True)
74
+ assert gen._code_mode is True
75
+
76
+
77
+ def test_build_tag_generator_passes_code_mode():
78
+ from virtual_context.core.tag_generator import build_tag_generator, LLMTagGenerator
79
+ from virtual_context.types import TagGeneratorConfig
80
+ from unittest.mock import MagicMock
81
+ config = TagGeneratorConfig(type="llm", provider="openrouter", model="test")
82
+ gen = build_tag_generator(config, MagicMock(), code_mode=True)
83
+ assert isinstance(gen, LLMTagGenerator)
84
+ assert gen._code_mode is True
85
+ gen2 = build_tag_generator(config, MagicMock())
86
+ assert gen2._code_mode is False
87
+
88
+
89
+ def test_engine_passes_code_mode_to_tag_generator():
90
+ import inspect
91
+ from virtual_context import engine
92
+ source = inspect.getsource(engine)
93
+ assert "code_mode" in source, "engine.py must reference code_mode"
94
+ assert "compactor.code_mode" in source or "config.compactor.code_mode" in source, \
95
+ "engine.py must read code_mode from compactor config"
@@ -0,0 +1,274 @@
1
+ """Tests for the fill pass pipeline stage."""
2
+
3
+ import copy
4
+ import json
5
+ import re
6
+ from unittest.mock import MagicMock
7
+ from datetime import datetime, timezone
8
+
9
+ from virtual_context.proxy.formats import detect_format
10
+ from virtual_context.types import (
11
+ RetrievalResult, StoredSummary, AssembledContext, TagSummary, SegmentMetadata,
12
+ )
13
+
14
+
15
+ def test_retrieval_result_has_overflow_field():
16
+ rr = RetrievalResult()
17
+ assert hasattr(rr, "overflow_summaries")
18
+ assert rr.overflow_summaries == []
19
+
20
+
21
+ def test_retrieval_result_overflow_accepts_summaries():
22
+ s = StoredSummary(ref="seg_001", primary_tag="cooking", summary="test", summary_tokens=50)
23
+ rr = RetrievalResult(overflow_summaries=[s])
24
+ assert len(rr.overflow_summaries) == 1
25
+ assert rr.overflow_summaries[0].ref == "seg_001"
26
+
27
+
28
+ def test_assembled_context_has_retrieval_result():
29
+ ac = AssembledContext()
30
+ assert ac.retrieval_result is None
31
+
32
+
33
+ def test_assembled_context_has_presented_tags():
34
+ ac = AssembledContext()
35
+ assert isinstance(ac.presented_tags, set)
36
+ assert len(ac.presented_tags) == 0
37
+
38
+
39
+ def test_assembled_context_presented_tags_populated():
40
+ ac = AssembledContext(presented_tags={"cooking", "baking", "recipes"})
41
+ assert "cooking" in ac.presented_tags
42
+ assert len(ac.presented_tags) == 3
43
+
44
+
45
+ def test_format_tag_section_standalone():
46
+ """Standalone format_tag_section produces the same XML format as the assembler."""
47
+ from virtual_context.core.assembler import format_tag_section
48
+ from virtual_context.types import StoredSummary, SegmentMetadata
49
+ from datetime import datetime, timezone
50
+
51
+ s1 = StoredSummary(
52
+ ref="s1", primary_tag="cooking", summary="Italian cooking techniques",
53
+ summary_tokens=100, tags=["cooking", "italian"],
54
+ metadata=SegmentMetadata(),
55
+ start_timestamp=datetime.now(timezone.utc),
56
+ )
57
+
58
+ result = format_tag_section("cooking", [s1])
59
+ assert '<virtual-context tags="cooking, italian"' in result
60
+ assert "[1/1]" in result
61
+ assert "Italian cooking techniques" in result
62
+ assert "</virtual-context>" in result
63
+
64
+
65
+ def _make_summary(ref: str, tag: str, tokens: int) -> StoredSummary:
66
+ return StoredSummary(
67
+ ref=ref, primary_tag=tag, tags=[tag],
68
+ summary=f"Summary for {tag}", summary_tokens=tokens,
69
+ metadata=SegmentMetadata(),
70
+ start_timestamp=datetime.now(timezone.utc),
71
+ )
72
+
73
+
74
+ def _make_anthropic_body(user_texts: list[str]) -> dict:
75
+ messages = []
76
+ for text in user_texts:
77
+ messages.append({"role": "user", "content": text})
78
+ messages.append({"role": "assistant", "content": [{"type": "text", "text": f"Reply to: {text}"}]})
79
+ messages.append({"role": "user", "content": "current question"})
80
+ return {"system": "You are helpful.", "messages": messages, "model": "claude-opus-4-6"}
81
+
82
+
83
+ def test_fill_pass_no_op_when_at_target():
84
+ from virtual_context.proxy.message_filter import fill_pass
85
+ body = _make_anthropic_body(["hello", "world"])
86
+ fmt = detect_format(body)
87
+ result, summaries, turns = fill_pass(
88
+ body=body, fmt=fmt, outbound_tokens=90000, target_tokens=90000,
89
+ assembled=None, pre_filter_body=body,
90
+ store=MagicMock(), conversation_id="test",
91
+ )
92
+ assert summaries == 0
93
+ assert turns == 0
94
+
95
+
96
+ def test_fill_pass_no_op_when_over_target():
97
+ from virtual_context.proxy.message_filter import fill_pass
98
+ body = _make_anthropic_body(["hello"])
99
+ fmt = detect_format(body)
100
+ result, summaries, turns = fill_pass(
101
+ body=body, fmt=fmt, outbound_tokens=100000, target_tokens=90000,
102
+ assembled=None, pre_filter_body=body,
103
+ store=MagicMock(), conversation_id="test",
104
+ )
105
+ assert summaries == 0
106
+ assert turns == 0
107
+
108
+
109
+ def test_fill_pass_adds_breadth_summaries():
110
+ from virtual_context.proxy.message_filter import fill_pass
111
+
112
+ body = _make_anthropic_body(["hello"])
113
+ fmt = detect_format(body)
114
+
115
+ ts1 = TagSummary(tag="cooking", summary="Italian cooking", summary_tokens=50,
116
+ source_segment_refs=["seg_a"], updated_at=datetime.now(timezone.utc))
117
+ ts2 = TagSummary(tag="baking", summary="Bread baking", summary_tokens=50,
118
+ source_segment_refs=["seg_b"], updated_at=datetime.now(timezone.utc))
119
+ mock_store = MagicMock()
120
+ mock_store.get_all_tag_summaries.return_value = [ts1, ts2]
121
+
122
+ assembled = AssembledContext(
123
+ presented_segment_refs=set(),
124
+ presented_tags=set(),
125
+ tag_sections={},
126
+ retrieval_result=RetrievalResult(),
127
+ )
128
+
129
+ result, summaries_added, turns_added = fill_pass(
130
+ body=body, fmt=fmt, outbound_tokens=70000, target_tokens=90000,
131
+ assembled=assembled, pre_filter_body=copy.deepcopy(body),
132
+ store=mock_store, conversation_id="test",
133
+ summary_ratio=1.0,
134
+ )
135
+ assert summaries_added >= 1
136
+ mock_store.get_all_tag_summaries.assert_called_once_with(conversation_id="test")
137
+
138
+
139
+ def test_presented_tags_from_segments_and_full_sections():
140
+ """presented_tags must include secondary tags from SEGMENTS/FULL sections."""
141
+ tag_sections = {
142
+ "cooking": (
143
+ '<virtual-context tags="cooking, italian" segments="2">\n'
144
+ "[1/2]\nPasta techniques\n\n---\n\n[2/2]\nRisotto methods\n"
145
+ "</virtual-context>"
146
+ ),
147
+ "baking": (
148
+ '<virtual-context tags="baking, bread, sourdough" segments="1">\n'
149
+ "[1/1]\nSourdough starter maintenance\n"
150
+ "</virtual-context>"
151
+ ),
152
+ }
153
+
154
+ _vc_tags_re = re.compile(r'<virtual-context\s+tags="([^"]*)"')
155
+ presented_tags: set[str] = set()
156
+ for section_text in tag_sections.values():
157
+ for m in _vc_tags_re.finditer(section_text):
158
+ for t in m.group(1).split(", "):
159
+ t = t.strip()
160
+ if t:
161
+ presented_tags.add(t)
162
+ # Also include section keys
163
+ presented_tags.update(tag_sections.keys())
164
+
165
+ assert "cooking" in presented_tags
166
+ assert "baking" in presented_tags
167
+ assert "italian" in presented_tags
168
+ assert "bread" in presented_tags
169
+ assert "sourdough" in presented_tags
170
+ assert "grilling" not in presented_tags
171
+
172
+
173
+ def test_presented_tags_fallback_to_section_key():
174
+ """When tags attr is empty, section key should still be covered."""
175
+ tag_sections = {
176
+ "misc": '<virtual-context tags="" segments="1">\n[1/1]\nRandom\n</virtual-context>',
177
+ }
178
+
179
+ _vc_tags_re = re.compile(r'<virtual-context\s+tags="([^"]*)"')
180
+ presented_tags: set[str] = set()
181
+ for section_text in tag_sections.values():
182
+ for m in _vc_tags_re.finditer(section_text):
183
+ for t in m.group(1).split(", "):
184
+ t = t.strip()
185
+ if t:
186
+ presented_tags.add(t)
187
+ presented_tags.update(tag_sections.keys())
188
+
189
+ # "misc" comes from keys(), not from regex (empty attr)
190
+ assert "misc" in presented_tags
191
+
192
+
193
+ def test_fill_pass_accounting_summary_and_turns():
194
+ """After fill pass, counts must match actual content added."""
195
+ from virtual_context.proxy.message_filter import fill_pass
196
+
197
+ body = _make_anthropic_body(["hello", "world"])
198
+ pre_filter = copy.deepcopy(body)
199
+ extra_msgs = [
200
+ {"role": "user", "content": "older message 1"},
201
+ {"role": "assistant", "content": [{"type": "text", "text": "older reply 1"}]},
202
+ {"role": "user", "content": "older message 2"},
203
+ {"role": "assistant", "content": [{"type": "text", "text": "older reply 2"}]},
204
+ ]
205
+ pre_filter["messages"] = extra_msgs + pre_filter["messages"]
206
+
207
+ fmt = detect_format(body)
208
+
209
+ ts1 = TagSummary(tag="history", summary="Historical events discussed",
210
+ summary_tokens=30, source_segment_refs=["seg_h1"],
211
+ updated_at=datetime.now(timezone.utc))
212
+ mock_store = MagicMock()
213
+ mock_store.get_all_tag_summaries.return_value = [ts1]
214
+
215
+ assembled = AssembledContext(
216
+ presented_segment_refs=set(),
217
+ presented_tags=set(),
218
+ tag_sections={},
219
+ retrieval_result=RetrievalResult(),
220
+ )
221
+
222
+ result_body, summaries_added, turns_added = fill_pass(
223
+ body=body, fmt=fmt,
224
+ outbound_tokens=70000, target_tokens=90000,
225
+ assembled=assembled, pre_filter_body=pre_filter,
226
+ store=mock_store, conversation_id="test-conv",
227
+ summary_ratio=0.5,
228
+ )
229
+
230
+ assert summaries_added >= 1
231
+ result_json = json.dumps(result_body)
232
+ assert "Historical events discussed" in result_json
233
+
234
+
235
+ def test_fill_pass_sanitizes_restored_turns():
236
+ """Restored turns must have thinking blocks stripped and media replaced."""
237
+ from virtual_context.proxy.message_filter import _sanitize_restored_turn
238
+
239
+ messages = [
240
+ {"role": "user", "content": [
241
+ {"type": "text", "text": "show me the image"},
242
+ {"type": "image", "source": {"type": "base64", "data": "abc123"}},
243
+ ]},
244
+ {"role": "assistant", "content": [
245
+ {"type": "thinking", "thinking": "let me think..."},
246
+ {"type": "text", "text": "here is my answer"},
247
+ ]},
248
+ ]
249
+
250
+ sanitized = _sanitize_restored_turn(messages)
251
+
252
+ user_content = sanitized[0]["content"]
253
+ assert any(b.get("text") == "[image removed from restored turn]" for b in user_content)
254
+ assert not any(b.get("type") == "image" for b in user_content)
255
+
256
+ asst_content = sanitized[1]["content"]
257
+ assert not any(b.get("type") == "thinking" for b in asst_content)
258
+ assert any(b.get("text") == "here is my answer" for b in asst_content)
259
+
260
+
261
+ def test_fill_pass_no_restore_tool_references():
262
+ """Media placeholders must NOT reference vc_restore_tool."""
263
+ from virtual_context.proxy.message_filter import _sanitize_restored_turn
264
+
265
+ messages = [
266
+ {"role": "user", "content": [
267
+ {"type": "input_image", "image_url": "data:image/png;base64,abc123"},
268
+ ]},
269
+ ]
270
+
271
+ sanitized = _sanitize_restored_turn(messages)
272
+ result_json = json.dumps(sanitized)
273
+ assert "vc_restore_tool" not in result_json
274
+ assert "image removed from restored turn" in result_json
@@ -450,7 +450,7 @@ class TestCollapseChainsCrossFormat:
450
450
  fmt = detect_format(body)
451
451
  tti = TurnTagIndex()
452
452
  original_count = len(body["messages"])
453
- result, collapsed, refs = collapse_turn_chains(
453
+ result, collapsed, refs, _ = collapse_turn_chains(
454
454
  body, fmt, protected_recent_turns=3, turn_tag_index=tti,
455
455
  store=None, conversation_id="test",
456
456
  )
@@ -480,7 +480,7 @@ class TestCollapseChainsCrossFormat:
480
480
  fmt = detect_format(body)
481
481
  tti = TurnTagIndex()
482
482
  original_count = len(body["input"])
483
- result, collapsed, refs = collapse_turn_chains(
483
+ result, collapsed, refs, _ = collapse_turn_chains(
484
484
  body, fmt, protected_recent_turns=3, turn_tag_index=tti,
485
485
  store=None, conversation_id="test",
486
486
  )
@@ -507,7 +507,7 @@ class TestCollapseChainsCrossFormat:
507
507
  fmt = detect_format(body)
508
508
  tti = TurnTagIndex()
509
509
  original_count = len(body["messages"])
510
- result, collapsed, refs = collapse_turn_chains(
510
+ result, collapsed, refs, _ = collapse_turn_chains(
511
511
  body, fmt, protected_recent_turns=3, turn_tag_index=tti,
512
512
  store=None, conversation_id="test",
513
513
  )