virtual-context 0.3.7__tar.gz → 0.3.8__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.
- {virtual_context-0.3.7 → virtual_context-0.3.8}/.github/workflows/python-publish.yml +6 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/PKG-INFO +1 -1
- {virtual_context-0.3.7 → virtual_context-0.3.8}/pyproject.toml +1 -1
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_compaction_commit_prune.py +15 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_config.py +2 -1
- virtual_context-0.3.8/tests/test_postgres_store.py +102 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_presets.py +3 -3
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_proxy_session.py +31 -14
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_request_captures_persistence.py +31 -1
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_session_state.py +15 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_temporal_resolver.py +139 -2
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tool_loop.py +76 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tool_result_filter.py +39 -1
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_vcattach.py +145 -7
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual-context.yaml +0 -4
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual-context.yaml.example +1 -8
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/__init__.py +1 -1
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/config.py +0 -1
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/compaction_pipeline.py +0 -10
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/composite_store.py +3 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/protocols.py +1 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/temporal_resolver.py +348 -82
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/tool_loop.py +63 -13
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/presets/agentic.py +4 -8
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/presets/coding.py +3 -8
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/formats.py +156 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/handlers.py +106 -47
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/metrics.py +15 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/registry.py +5 -3
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/server.py +13 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/session_state.py +21 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/vcattach.py +24 -13
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/storage/filesystem.py +5 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/storage/postgres.py +31 -8
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/storage/sqlite.py +8 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/types.py +1 -2
- {virtual_context-0.3.7 → virtual_context-0.3.8}/.gitignore +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/CLAUDE.md +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/LICENSE +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/README.md +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/assets/dashboard.png +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/assets/hero.png +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/models.yaml +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/scripts/install.ps1 +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/scripts/install.sh +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/specs/cache-aware-compaction.md +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/REGRESSION_MAP.md +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/conftest.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/docker-compose.test.yml +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/haiku/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/haiku/conftest.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/haiku/test_compaction.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/haiku/test_retrieval.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/haiku/test_tagging.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/ollama/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/ollama/conftest.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/ollama/test_compactor.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/ollama/test_pipeline.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/ollama/test_provider.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/ollama/test_tag_generator.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/proxy/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/proxy/test_dashboard_cors.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/proxy/test_metrics.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_assembler.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_backend_integration.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_beam_judge.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_budget_enforcement.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_cli_init.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_compactor.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_compactor_concurrent.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_composite_store.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_context_bleed.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_conversation_coverage_report.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_conversation_identity.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_conversation_lifecycle.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_conversation_scoping.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_deferred_payload_compaction.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_embedding_tag_generator.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_empty_turn_skip.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_engine_integration.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_engine_lookback.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_engine_state.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_engine_sync_turns.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_fact_enrichment.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_fact_graph_integration.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_fact_link_checker.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_fact_link_query.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_fact_link_types.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_fact_links_sqlite.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_fact_redesign.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_fill_pass.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_find_quote.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_format_agnostic.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_full_text_backfill.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_headless.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_history_filter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_idf_retrieval.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_ingest_index_integrity.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_longmemeval_auth.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_mcp_server.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_media.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_message_filter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_metrics_persistence.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_model_catalog.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_model_limits.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_monitor.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_multi_instance.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_noop_fact_link_store.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_openrouter_provider.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_paging.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_passthrough_filter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_prev_context_leak.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_provider_adapters.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_proxy.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_proxy_dashboard.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_proxy_formats.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_proxy_message_filter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_proxy_streaming.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_raw_content.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_recall_all.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_registry_lifecycle.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_retriever.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_rrf_scoring.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_segmenter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_semantic_search.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_sender_identity.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_session_cache.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_session_date.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_storage_protocols.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_store_recovery.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_store_sqlite.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_stub_turn_handling.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_supersession.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_supersession_migration.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tag_canonicalizer.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tag_consolidator.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tag_generator.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tag_splitter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_telemetry.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_telemetry_integration.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tool_output_interceptor.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tool_tags.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_tui.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_turn_grouping.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_turn_tag_index.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_unified_budget.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_upstream_trim.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/tests/test_verb_expansion.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/cli/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/cli/main.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/conversation_identity.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/assembler.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/compactor.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/conversation_store.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/coverage_report.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/embedding_provider.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/embedding_tag_generator.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/engine_utils.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/fact_query.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/fts_preprocessor.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/full_text_backfill.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/hint_builder.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/llm_utils.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/math_utils.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/model_catalog.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/monitor.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/paging_manager.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/provider_adapters.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/quote_search.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/retrieval_assembler.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/retrieval_scoring.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/retriever.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/search_engine.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/segmenter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/semantic_search.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/store.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/tag_canonicalizer.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/tag_consolidator.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/tag_generator.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/tag_scoring.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/tag_splitter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/tagging_pipeline.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/telemetry.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/tool_query.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/core/turn_tag_index.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/data/anthropic-tokenizer/tokenizer.json +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/data/models.yaml +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/engine.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/ingest/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/ingest/curator.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/ingest/date_resolver.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/ingest/parsers.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/ingest/supersession.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/mcp/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/mcp/server.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/model_limits.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/openclaw/virtual-context.mjs +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/patterns.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/presets/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/presets/base.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/providers/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/providers/anthropic.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/providers/base.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/providers/generic_openai.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/providers/ollama_native.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/_envelope.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/dashboard.html +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/dashboard.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/helpers.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/media.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/message_filter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/multi.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/session_cache.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/state.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/static/android-chrome-192x192.png +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/static/android-chrome-512x512.png +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/static/apple-touch-icon.png +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/static/favicon-16x16.png +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/static/favicon-32x32.png +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/static/favicon.ico +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/static/site.webmanifest +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/proxy/tool_output_interceptor.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/storage/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/storage/falkordb.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/storage/helpers.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/storage/neo4j.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/storage/noop_fact_link_store.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/token_counter.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/app.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/chat.tcss +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/chat_provider.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/headless.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/modals/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/modals/turn_inspector.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/state.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/widgets/__init__.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/widgets/budget_bar.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/widgets/chat_view.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/widgets/input_box.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/widgets/tag_panel.py +0 -0
- {virtual_context-0.3.7 → virtual_context-0.3.8}/virtual_context/tui/widgets/turn_list.py +0 -0
|
@@ -22,6 +22,12 @@ jobs:
|
|
|
22
22
|
with:
|
|
23
23
|
python-version: "3.12"
|
|
24
24
|
|
|
25
|
+
- name: Set version from tag
|
|
26
|
+
run: |
|
|
27
|
+
VERSION=${GITHUB_REF_NAME#v}
|
|
28
|
+
sed -i "s/^version = .*/version = \"$VERSION\"/" pyproject.toml
|
|
29
|
+
sed -i "s/^__version__ = .*/__version__ = \"$VERSION\"/" virtual_context/__init__.py
|
|
30
|
+
|
|
25
31
|
- name: Build
|
|
26
32
|
run: |
|
|
27
33
|
python -m pip install build
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: virtual-context
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.8
|
|
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
|
|
@@ -70,3 +70,18 @@ def test_compaction_skips_prune_when_committed_checkpoint_lags_expected():
|
|
|
70
70
|
pipeline._commit_compaction_state([])
|
|
71
71
|
|
|
72
72
|
store.prune_turn_messages.assert_not_called()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_run_compaction_never_triggers_store_cleanup_from_tag_rules():
|
|
76
|
+
pipeline, store = _make_pipeline(save_ok=True, committed_turn=4)
|
|
77
|
+
pipeline._segmenter.segment = MagicMock(return_value=[])
|
|
78
|
+
pipeline._compact_and_store = MagicMock(return_value=[])
|
|
79
|
+
pipeline._refresh_shared_retrieval_snapshots = MagicMock()
|
|
80
|
+
pipeline._config = SimpleNamespace(
|
|
81
|
+
conversation_id="conv-1",
|
|
82
|
+
tag_rules=[SimpleNamespace(match="debug*", priority=7)],
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
pipeline._run_compaction([], [], generated_by_turn_id="turn-123")
|
|
86
|
+
|
|
87
|
+
store.cleanup.assert_not_called()
|
|
@@ -52,7 +52,8 @@ class TestLoadConfig:
|
|
|
52
52
|
assert len(config.tag_rules) == 2
|
|
53
53
|
assert config.tag_rules[0].match == "architecture*"
|
|
54
54
|
assert config.tag_rules[0].priority == 10
|
|
55
|
-
assert config.tag_rules[1].
|
|
55
|
+
assert config.tag_rules[1].match == "debug*"
|
|
56
|
+
assert config.tag_rules[1].priority == 7
|
|
56
57
|
|
|
57
58
|
def test_load_strategy_config(self):
|
|
58
59
|
config = load_config(config_dict={
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Focused unit tests for PostgresStore connection management."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _FakeConn:
|
|
9
|
+
def __init__(self, name: str) -> None:
|
|
10
|
+
self.name = name
|
|
11
|
+
self.closed = False
|
|
12
|
+
self.executed: list[tuple[str, tuple | None]] = []
|
|
13
|
+
|
|
14
|
+
def execute(self, sql: str, params=None):
|
|
15
|
+
self.executed.append((sql, params))
|
|
16
|
+
return self
|
|
17
|
+
|
|
18
|
+
def close(self) -> None:
|
|
19
|
+
self.closed = True
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _FakeRowsResult:
|
|
23
|
+
def __init__(self, rows):
|
|
24
|
+
self._rows = rows
|
|
25
|
+
|
|
26
|
+
def fetchall(self):
|
|
27
|
+
return self._rows
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_postgres_store_uses_thread_local_connections(monkeypatch):
|
|
31
|
+
from virtual_context.storage import postgres as pg
|
|
32
|
+
|
|
33
|
+
created: list[_FakeConn] = []
|
|
34
|
+
|
|
35
|
+
def _fake_connect(*args, **kwargs):
|
|
36
|
+
conn = _FakeConn(f"conn-{len(created)}")
|
|
37
|
+
created.append(conn)
|
|
38
|
+
return conn
|
|
39
|
+
|
|
40
|
+
monkeypatch.setattr(pg.psycopg, "connect", _fake_connect)
|
|
41
|
+
|
|
42
|
+
store = pg.PostgresStore("postgresql://example")
|
|
43
|
+
main_conn = store._get_conn()
|
|
44
|
+
|
|
45
|
+
from_worker: list[_FakeConn] = []
|
|
46
|
+
|
|
47
|
+
def _worker() -> None:
|
|
48
|
+
from_worker.append(store._get_conn())
|
|
49
|
+
|
|
50
|
+
worker = threading.Thread(target=_worker)
|
|
51
|
+
worker.start()
|
|
52
|
+
worker.join()
|
|
53
|
+
|
|
54
|
+
assert store._get_conn() is main_conn
|
|
55
|
+
assert from_worker == [created[1]]
|
|
56
|
+
assert from_worker[0] is not main_conn
|
|
57
|
+
assert len(created) == 2
|
|
58
|
+
|
|
59
|
+
store.close()
|
|
60
|
+
|
|
61
|
+
assert all(conn.closed for conn in created)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_postgres_store_get_all_segments_uses_batch_tag_lookup(monkeypatch):
|
|
65
|
+
from virtual_context.storage import postgres as pg
|
|
66
|
+
|
|
67
|
+
class _RowsConn(_FakeConn):
|
|
68
|
+
def execute(self, sql: str, params=None):
|
|
69
|
+
self.executed.append((sql, params))
|
|
70
|
+
if "FROM segments" in sql:
|
|
71
|
+
return _FakeRowsResult([
|
|
72
|
+
{
|
|
73
|
+
"ref": "seg-1",
|
|
74
|
+
"conversation_id": "conv-1",
|
|
75
|
+
"primary_tag": "tag-a",
|
|
76
|
+
"summary": "summary",
|
|
77
|
+
"summary_tokens": 3,
|
|
78
|
+
"full_text": "full text",
|
|
79
|
+
"full_tokens": 6,
|
|
80
|
+
"messages_json": "[]",
|
|
81
|
+
"metadata_json": "{\"turn_count\": 2}",
|
|
82
|
+
"created_at": "2026-04-14T00:00:00+00:00",
|
|
83
|
+
"start_timestamp": "2026-04-14T00:00:00+00:00",
|
|
84
|
+
"end_timestamp": "2026-04-14T00:01:00+00:00",
|
|
85
|
+
"compaction_model": "test-model",
|
|
86
|
+
"compression_ratio": 0.5,
|
|
87
|
+
}
|
|
88
|
+
])
|
|
89
|
+
return _FakeRowsResult([])
|
|
90
|
+
|
|
91
|
+
conn = _RowsConn("conn-0")
|
|
92
|
+
|
|
93
|
+
monkeypatch.setattr(pg.psycopg, "connect", lambda *args, **kwargs: conn)
|
|
94
|
+
|
|
95
|
+
store = pg.PostgresStore("postgresql://example")
|
|
96
|
+
monkeypatch.setattr(store, "_batch_get_tags", lambda refs: {"seg-1": ["tag-a", "tag-b"]})
|
|
97
|
+
|
|
98
|
+
segments = store.get_all_segments(conversation_id="conv-1")
|
|
99
|
+
|
|
100
|
+
assert len(segments) == 1
|
|
101
|
+
assert segments[0].ref == "seg-1"
|
|
102
|
+
assert segments[0].tags == ["tag-a", "tag-b"]
|
|
@@ -74,9 +74,9 @@ def test_architecture_highest_priority():
|
|
|
74
74
|
assert arch_rule["priority"] == 10
|
|
75
75
|
|
|
76
76
|
|
|
77
|
-
def
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
def test_coding_rules_do_not_define_ttl():
|
|
78
|
+
for rule in CODING_TAG_RULES:
|
|
79
|
+
assert "ttl_days" not in rule
|
|
80
80
|
|
|
81
81
|
|
|
82
82
|
def test_catch_all_rule_exists():
|
|
@@ -640,11 +640,15 @@ class TestContentFingerprintRouting:
|
|
|
640
640
|
assert is_new is False
|
|
641
641
|
assert result is state
|
|
642
642
|
|
|
643
|
-
def
|
|
644
|
-
"""
|
|
643
|
+
def test_alias_wins_even_when_live_conversation_exists(self):
|
|
644
|
+
"""VCATTACH aliases are authoritative — they redirect even when the
|
|
645
|
+
source conversation still has a live session. VCATTACH no longer
|
|
646
|
+
deletes the old conversation, so the alias table is the single
|
|
647
|
+
source of truth for routing.
|
|
648
|
+
"""
|
|
645
649
|
metrics = ProxyMetrics()
|
|
646
650
|
store = MagicMock()
|
|
647
|
-
store.resolve_conversation_alias.return_value = "
|
|
651
|
+
store.resolve_conversation_alias.return_value = "redirected-target"
|
|
648
652
|
registry = SessionRegistry(
|
|
649
653
|
config_path=None,
|
|
650
654
|
upstream="http://fake:9999",
|
|
@@ -652,17 +656,23 @@ class TestContentFingerprintRouting:
|
|
|
652
656
|
store=store,
|
|
653
657
|
)
|
|
654
658
|
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
registry._conversations["
|
|
659
|
+
live_engine = MagicMock()
|
|
660
|
+
live_engine.config.conversation_id = "original-session"
|
|
661
|
+
live_state = ProxyState(live_engine, metrics=metrics)
|
|
662
|
+
registry._conversations["original-session"] = live_state
|
|
663
|
+
|
|
664
|
+
target_engine = MagicMock()
|
|
665
|
+
target_engine.config.conversation_id = "redirected-target"
|
|
666
|
+
target_state = ProxyState(target_engine, metrics=metrics)
|
|
667
|
+
registry._conversations["redirected-target"] = target_state
|
|
659
668
|
|
|
660
669
|
body = self._make_body(["hello"])
|
|
661
|
-
result, is_new = registry.get_or_create("
|
|
670
|
+
result, is_new = registry.get_or_create("original-session", body=body)
|
|
662
671
|
|
|
672
|
+
# Alias redirects to the target even though original-session is live.
|
|
663
673
|
assert is_new is False
|
|
664
|
-
assert result is
|
|
665
|
-
store.resolve_conversation_alias.
|
|
674
|
+
assert result is target_state
|
|
675
|
+
store.resolve_conversation_alias.assert_called_once_with("original-session")
|
|
666
676
|
|
|
667
677
|
def test_cold_start_marker_restores_persisted_session(self, tmp_path):
|
|
668
678
|
"""A marker referencing a persisted-but-not-loaded session should restore it, not create a new random one."""
|
|
@@ -699,8 +709,13 @@ class TestContentFingerprintRouting:
|
|
|
699
709
|
assert result.engine.config.conversation_id == saved_id
|
|
700
710
|
assert len(result.engine._turn_tag_index.entries) > 0
|
|
701
711
|
|
|
702
|
-
def
|
|
703
|
-
"""
|
|
712
|
+
def test_cold_start_alias_redirects_even_when_persisted_source_exists(self, tmp_path):
|
|
713
|
+
"""VCATTACH aliases persistently redirect: when a client sends the old
|
|
714
|
+
id after a VCATTACH, the alias routes to the target even though the
|
|
715
|
+
original conversation still has persisted data. VCATTACH no longer
|
|
716
|
+
deletes the old conversation, so the alias is the only signal that
|
|
717
|
+
a redirect is intended.
|
|
718
|
+
"""
|
|
704
719
|
from virtual_context.engine import VirtualContextEngine
|
|
705
720
|
|
|
706
721
|
config_path = tmp_path / "vc.yaml"
|
|
@@ -714,7 +729,7 @@ class TestContentFingerprintRouting:
|
|
|
714
729
|
engine1.close()
|
|
715
730
|
|
|
716
731
|
from virtual_context.storage.sqlite import SQLiteStore
|
|
717
|
-
SQLiteStore(str(tmp_path / "store.db")).save_conversation_alias(saved_id, "
|
|
732
|
+
SQLiteStore(str(tmp_path / "store.db")).save_conversation_alias(saved_id, "target-session")
|
|
718
733
|
|
|
719
734
|
metrics = ProxyMetrics()
|
|
720
735
|
registry = SessionRegistry(
|
|
@@ -727,7 +742,9 @@ class TestContentFingerprintRouting:
|
|
|
727
742
|
body = self._make_body(["new message"])
|
|
728
743
|
result, is_new = registry.get_or_create(saved_id, body=body)
|
|
729
744
|
|
|
730
|
-
|
|
745
|
+
# Alias redirects saved_id -> "target-session", producing a new
|
|
746
|
+
# session for target-session (there's no persisted target yet).
|
|
747
|
+
assert result.engine.config.conversation_id == "target-session"
|
|
731
748
|
assert is_new is True
|
|
732
749
|
|
|
733
750
|
|
|
@@ -335,6 +335,34 @@ class TestProxyMetricsStoreIntegration:
|
|
|
335
335
|
assert loaded[0]["client_payload_earliest_timestamp"] == "2026-03-15T16:10:00+00:00"
|
|
336
336
|
assert loaded[0]["client_payload_latest_timestamp"] == "2026-03-16T18:12:00+00:00"
|
|
337
337
|
|
|
338
|
+
def test_capture_persists_payload_accounting(self, tmp_path):
|
|
339
|
+
from virtual_context.proxy.metrics import ProxyMetrics
|
|
340
|
+
|
|
341
|
+
store = self._make_store(tmp_path)
|
|
342
|
+
m = ProxyMetrics(store=store)
|
|
343
|
+
m.capture_request(
|
|
344
|
+
turn=1,
|
|
345
|
+
body={"model": "test", "messages": [{"role": "user", "content": "hi"}]},
|
|
346
|
+
api_format="anthropic",
|
|
347
|
+
conversation_id="c1",
|
|
348
|
+
payload_accounting={
|
|
349
|
+
"raw_payload_entry_count": 4624,
|
|
350
|
+
"normalized_payload_entry_count": 986,
|
|
351
|
+
"normalized_chat_entry_count": 986,
|
|
352
|
+
"normalized_non_chat_entry_count": 3638,
|
|
353
|
+
"extracted_history_message_count": 986,
|
|
354
|
+
"extracted_history_pair_count": 493,
|
|
355
|
+
"tool_result_user_entries_folded": 1770,
|
|
356
|
+
},
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
loaded = store.load_request_captures(conversation_id="c1")
|
|
360
|
+
assert len(loaded) == 1
|
|
361
|
+
assert loaded[0]["raw_payload_entry_count"] == 4624
|
|
362
|
+
assert loaded[0]["normalized_payload_entry_count"] == 986
|
|
363
|
+
assert loaded[0]["extracted_history_pair_count"] == 493
|
|
364
|
+
assert loaded[0]["payload_accounting"]["tool_result_user_entries_folded"] == 1770
|
|
365
|
+
|
|
338
366
|
def test_capture_enriched_persists_to_store(self, tmp_path):
|
|
339
367
|
from virtual_context.proxy.metrics import ProxyMetrics
|
|
340
368
|
from unittest.mock import patch
|
|
@@ -347,7 +375,9 @@ class TestProxyMetricsStoreIntegration:
|
|
|
347
375
|
# Verify _persist_capture is called by capture_enriched
|
|
348
376
|
with patch.object(m, '_persist_capture', wraps=m._persist_capture) as mock_persist:
|
|
349
377
|
m.capture_enriched(turn=1, body={"model": "test", "messages": [], "system": "ctx"})
|
|
350
|
-
mock_persist.
|
|
378
|
+
mock_persist.assert_called_once()
|
|
379
|
+
assert mock_persist.call_args.args == (1,)
|
|
380
|
+
assert mock_persist.call_args.kwargs == {"turn_id": None}
|
|
351
381
|
# The store should still have the capture (enriched body excluded from summary)
|
|
352
382
|
loaded = store.load_request_captures()
|
|
353
383
|
assert len(loaded) == 1
|
|
@@ -136,6 +136,21 @@ def test_save_rejected_after_tombstone(provider, mock_redis):
|
|
|
136
136
|
assert blob["deleted"] is True
|
|
137
137
|
|
|
138
138
|
|
|
139
|
+
def test_undelete_allows_reuse_of_same_conversation_id(provider, mock_redis):
|
|
140
|
+
provider.delete("conv-123")
|
|
141
|
+
|
|
142
|
+
provider.undelete("conv-123")
|
|
143
|
+
|
|
144
|
+
state = SessionState()
|
|
145
|
+
state.last_indexed_turn = 7
|
|
146
|
+
provider.save("conv-123", state)
|
|
147
|
+
|
|
148
|
+
loaded = provider.load("conv-123")
|
|
149
|
+
assert loaded is not None
|
|
150
|
+
assert loaded.deleted is False
|
|
151
|
+
assert loaded.last_indexed_turn == 7
|
|
152
|
+
|
|
153
|
+
|
|
139
154
|
def test_turn_tag_entries_roundtrip(provider, mock_redis):
|
|
140
155
|
state = SessionState()
|
|
141
156
|
state.turn_tag_entries = [
|
|
@@ -369,7 +369,7 @@ def test_change_over_time_supplements_fact_backed_empty_dates() -> None:
|
|
|
369
369
|
assert supplemented[0]["match_type"] == "summary_date_expand"
|
|
370
370
|
|
|
371
371
|
|
|
372
|
-
def
|
|
372
|
+
def test_remember_when_uses_query_matched_facts_after_date_window_scan():
|
|
373
373
|
store = FakeStore()
|
|
374
374
|
search = FakeSearch()
|
|
375
375
|
resolver = TemporalResolver(store=store, search_engine=search, config=_make_config())
|
|
@@ -394,11 +394,88 @@ def test_remember_when_uses_query_matched_facts_before_date_fallback():
|
|
|
394
394
|
)
|
|
395
395
|
|
|
396
396
|
fact_dates = [item["when"] for item in result["facts_in_window"]]
|
|
397
|
-
assert store.fallback_called is
|
|
397
|
+
assert store.fallback_called is True
|
|
398
398
|
assert "2024-07-29" in fact_dates
|
|
399
399
|
assert any("microservices" in item["what"].lower() for item in result["facts_in_window"])
|
|
400
400
|
|
|
401
401
|
|
|
402
|
+
def test_remember_when_lookup_scans_window_even_for_weak_query() -> None:
|
|
403
|
+
store = FakeStore()
|
|
404
|
+
search = FakeSearch()
|
|
405
|
+
resolver = TemporalResolver(store=store, search_engine=search, config=_make_config())
|
|
406
|
+
|
|
407
|
+
store.segments["seg-day-1"] = _make_segment(
|
|
408
|
+
"seg-day-1",
|
|
409
|
+
"nginx-proxy",
|
|
410
|
+
"April-13-2026",
|
|
411
|
+
"Configured nginx reverse proxy for the internal dashboard on port 5757.",
|
|
412
|
+
)
|
|
413
|
+
store.segments["seg-day-2"] = _make_segment(
|
|
414
|
+
"seg-day-2",
|
|
415
|
+
"dashboard-auth",
|
|
416
|
+
"April-13-2026",
|
|
417
|
+
"Worked on dashboard authentication flow and access restrictions.",
|
|
418
|
+
)
|
|
419
|
+
store.segments["seg-other"] = _make_segment(
|
|
420
|
+
"seg-other",
|
|
421
|
+
"other-day",
|
|
422
|
+
"April-12-2026",
|
|
423
|
+
"Worked on an unrelated caching task.",
|
|
424
|
+
)
|
|
425
|
+
|
|
426
|
+
result = resolver.remember_when(
|
|
427
|
+
query="what did we work on on april 13th",
|
|
428
|
+
time_range={"kind": "between_dates", "start": "2026-04-13", "end": "2026-04-13"},
|
|
429
|
+
max_results=4,
|
|
430
|
+
mode="lookup",
|
|
431
|
+
)
|
|
432
|
+
|
|
433
|
+
assert result["found"] is True
|
|
434
|
+
assert {item["segment_ref"] for item in result["results"]} == {"seg-day-1", "seg-day-2"}
|
|
435
|
+
assert all(item["session_date_normalized"] == "2026-04-13" for item in result["results"])
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def test_remember_when_window_overview_allows_blank_query() -> None:
|
|
439
|
+
store = FakeStore()
|
|
440
|
+
search = FakeSearch()
|
|
441
|
+
resolver = TemporalResolver(store=store, search_engine=search, config=_make_config())
|
|
442
|
+
|
|
443
|
+
store.segments["seg-day-1"] = _make_segment(
|
|
444
|
+
"seg-day-1",
|
|
445
|
+
"nginx-proxy",
|
|
446
|
+
"April-13-2026",
|
|
447
|
+
"Configured nginx reverse proxy for the internal dashboard on port 5757.",
|
|
448
|
+
)
|
|
449
|
+
store.segments["seg-day-2"] = _make_segment(
|
|
450
|
+
"seg-day-2",
|
|
451
|
+
"dashboard-auth",
|
|
452
|
+
"April-13-2026",
|
|
453
|
+
"Worked on dashboard authentication flow and access restrictions.",
|
|
454
|
+
)
|
|
455
|
+
store.fallback_facts = [
|
|
456
|
+
_make_fact(
|
|
457
|
+
"fact-day-1",
|
|
458
|
+
"2026-04-13",
|
|
459
|
+
"Set up nginx proxy routing for the dashboard service",
|
|
460
|
+
tags=["nginx-proxy", "dashboard"],
|
|
461
|
+
segment_ref="seg-day-1",
|
|
462
|
+
),
|
|
463
|
+
]
|
|
464
|
+
|
|
465
|
+
result = resolver.remember_when(
|
|
466
|
+
query="",
|
|
467
|
+
time_range={"kind": "between_dates", "start": "2026-04-13", "end": "2026-04-13"},
|
|
468
|
+
max_results=4,
|
|
469
|
+
mode="window_overview",
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
assert result["mode"] == "window_overview"
|
|
473
|
+
assert result["found"] is True
|
|
474
|
+
assert {item["segment_ref"] for item in result["results"]} == {"seg-day-1", "seg-day-2"}
|
|
475
|
+
assert any(item["what"].startswith("Set up nginx proxy routing") for item in result["facts_in_window"])
|
|
476
|
+
assert result["date_buckets"][0]["date"] == "2026-04-13"
|
|
477
|
+
|
|
478
|
+
|
|
402
479
|
def test_remember_when_change_over_time_mode_merges_broad_window_facts():
|
|
403
480
|
store = FakeStore()
|
|
404
481
|
search = FakeSearch()
|
|
@@ -683,6 +760,66 @@ def test_remember_when_summarize_over_time_emits_ordered_milestones():
|
|
|
683
760
|
assert "deploy stages" in milestones[3]["point"].lower()
|
|
684
761
|
|
|
685
762
|
|
|
763
|
+
def test_remember_when_change_over_time_also_emits_ordered_milestones():
|
|
764
|
+
store = FakeStore()
|
|
765
|
+
search = FakeSearch()
|
|
766
|
+
resolver = TemporalResolver(store=store, search_engine=search, config=_make_config())
|
|
767
|
+
|
|
768
|
+
store.segment_hits["architecture"] = [
|
|
769
|
+
_make_quote("seg-1", "architecture", "July-03-2024", "architecture anchor"),
|
|
770
|
+
]
|
|
771
|
+
store.segments["seg-1"] = _make_segment("seg-1", "architecture", "July-03-2024", "July 3 summary")
|
|
772
|
+
|
|
773
|
+
store.fact_hits["architecture"] = [
|
|
774
|
+
_make_fact(
|
|
775
|
+
"fact-anchor",
|
|
776
|
+
"2024-07-03",
|
|
777
|
+
"Architecture updated to support higher throughput and availability",
|
|
778
|
+
tags=["architecture", "performance-optimization"],
|
|
779
|
+
segment_ref="seg-1",
|
|
780
|
+
),
|
|
781
|
+
]
|
|
782
|
+
store.fallback_facts = [
|
|
783
|
+
_make_fact(
|
|
784
|
+
"fact-health",
|
|
785
|
+
"2024-07-09",
|
|
786
|
+
"Weighted round robin load balancing with health checks for unhealthy servers",
|
|
787
|
+
tags=["load-balancing", "high-availability"],
|
|
788
|
+
),
|
|
789
|
+
_make_fact(
|
|
790
|
+
"fact-redis",
|
|
791
|
+
"2024-07-11",
|
|
792
|
+
"Redis caching tier added for repeated query traffic",
|
|
793
|
+
tags=["distributed-caching", "performance-optimization"],
|
|
794
|
+
),
|
|
795
|
+
_make_fact(
|
|
796
|
+
"fact-cicd",
|
|
797
|
+
"2024-07-24",
|
|
798
|
+
"GitLab CI/CD pipeline added build, test, and deploy stages for the rollout",
|
|
799
|
+
tags=["deployment", "ci-cd"],
|
|
800
|
+
),
|
|
801
|
+
]
|
|
802
|
+
|
|
803
|
+
result = resolver.remember_when(
|
|
804
|
+
query="system architecture performance optimization plans",
|
|
805
|
+
time_range={"kind": "between_dates", "start": "2024-07-01", "end": "2024-07-29"},
|
|
806
|
+
max_results=4,
|
|
807
|
+
mode="change_over_time",
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
milestones = result["ordered_milestones"]
|
|
811
|
+
|
|
812
|
+
assert [item["date"] for item in milestones] == [
|
|
813
|
+
"2024-07-03",
|
|
814
|
+
"2024-07-09",
|
|
815
|
+
"2024-07-11",
|
|
816
|
+
"2024-07-24",
|
|
817
|
+
]
|
|
818
|
+
assert "health checks" in milestones[1]["point"].lower()
|
|
819
|
+
assert "redis caching" in milestones[2]["point"].lower()
|
|
820
|
+
assert "deploy stages" in milestones[3]["point"].lower()
|
|
821
|
+
|
|
822
|
+
|
|
686
823
|
def test_ordered_milestones_prefer_segment_summary_for_shared_date():
|
|
687
824
|
resolver = TemporalResolver(store=FakeStore(), search_engine=FakeSearch(), config=_make_config())
|
|
688
825
|
|
|
@@ -485,6 +485,22 @@ class TestToolResultVerificationHint:
|
|
|
485
485
|
"facts": [],
|
|
486
486
|
}
|
|
487
487
|
],
|
|
488
|
+
"ordered_milestones": [
|
|
489
|
+
{
|
|
490
|
+
"date": "2024-11-05",
|
|
491
|
+
"theme": "context window mismatch",
|
|
492
|
+
"point": "Window size mismatch error handling was discussed.",
|
|
493
|
+
"source": "segment",
|
|
494
|
+
}
|
|
495
|
+
],
|
|
496
|
+
"phase_milestones": [
|
|
497
|
+
{
|
|
498
|
+
"start_date": "2024-11-05",
|
|
499
|
+
"end_date": "2024-11-05",
|
|
500
|
+
"focus": "context window mismatch",
|
|
501
|
+
"points": ["Window size mismatch error handling was discussed."],
|
|
502
|
+
}
|
|
503
|
+
],
|
|
488
504
|
}
|
|
489
505
|
|
|
490
506
|
result = execute_vc_tool(
|
|
@@ -499,11 +515,71 @@ class TestToolResultVerificationHint:
|
|
|
499
515
|
|
|
500
516
|
parsed = json.loads(result)
|
|
501
517
|
assert parsed["mode"] == "change_over_time"
|
|
518
|
+
assert "phase_milestones" in parsed
|
|
519
|
+
assert "ordered_milestones" in parsed
|
|
502
520
|
assert parsed["date_buckets"][0]["date"] == "2024-11-05"
|
|
503
521
|
assert "results" not in parsed
|
|
504
522
|
assert "facts_in_window" not in parsed
|
|
505
523
|
assert "reader_hint" in parsed
|
|
506
524
|
|
|
525
|
+
def test_remember_when_window_overview_prefers_date_buckets_payload(self):
|
|
526
|
+
engine = _mock_engine()
|
|
527
|
+
engine.remember_when.return_value = {
|
|
528
|
+
"query": "",
|
|
529
|
+
"mode": "window_overview",
|
|
530
|
+
"found": True,
|
|
531
|
+
"range": {
|
|
532
|
+
"kind": "between_dates",
|
|
533
|
+
"start": "2026-04-13",
|
|
534
|
+
"end": "2026-04-13",
|
|
535
|
+
},
|
|
536
|
+
"results": [
|
|
537
|
+
{"excerpt": "Configured nginx reverse proxy", "topic": "nginx-proxy"}
|
|
538
|
+
],
|
|
539
|
+
"facts_in_window": [
|
|
540
|
+
{"what": "Set up nginx proxy routing", "when": "2026-04-13"}
|
|
541
|
+
],
|
|
542
|
+
"date_buckets": [
|
|
543
|
+
{
|
|
544
|
+
"date": "2026-04-13",
|
|
545
|
+
"results": [
|
|
546
|
+
{
|
|
547
|
+
"topic": "nginx-proxy",
|
|
548
|
+
"excerpt": "Configured nginx reverse proxy",
|
|
549
|
+
"matched_terms": [],
|
|
550
|
+
"segment_ref": "seg-1",
|
|
551
|
+
"match_type": "summary_window",
|
|
552
|
+
}
|
|
553
|
+
],
|
|
554
|
+
"facts": [
|
|
555
|
+
{
|
|
556
|
+
"what": "Set up nginx proxy routing",
|
|
557
|
+
"tags": ["nginx-proxy"],
|
|
558
|
+
"matched_terms": [],
|
|
559
|
+
"segment_ref": "seg-1",
|
|
560
|
+
}
|
|
561
|
+
],
|
|
562
|
+
}
|
|
563
|
+
],
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
result = execute_vc_tool(
|
|
567
|
+
engine,
|
|
568
|
+
"vc_remember_when",
|
|
569
|
+
{
|
|
570
|
+
"query": "",
|
|
571
|
+
"time_range": {"kind": "between_dates", "start": "2026-04-13", "end": "2026-04-13"},
|
|
572
|
+
"mode": "window_overview",
|
|
573
|
+
},
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
parsed = json.loads(result)
|
|
577
|
+
assert parsed["mode"] == "window_overview"
|
|
578
|
+
assert parsed["date_buckets"][0]["date"] == "2026-04-13"
|
|
579
|
+
assert "results" not in parsed
|
|
580
|
+
assert "facts_in_window" not in parsed
|
|
581
|
+
assert "reader_hint" in parsed
|
|
582
|
+
|
|
507
583
|
def test_unknown_tool_returns_error(self):
|
|
508
584
|
engine = MagicMock()
|
|
509
585
|
result = execute_vc_tool(engine, "vc_unknown", {})
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
"""Tests for tool_result filtering in extract_history_pairs — assistant responses
|
|
2
2
|
after tool_result must not be skipped."""
|
|
3
3
|
|
|
4
|
-
from virtual_context.proxy.formats import
|
|
4
|
+
from virtual_context.proxy.formats import (
|
|
5
|
+
AnthropicFormat,
|
|
6
|
+
summarize_payload_accounting,
|
|
7
|
+
summarize_raw_payload_entries,
|
|
8
|
+
)
|
|
5
9
|
|
|
6
10
|
|
|
7
11
|
class TestToolResultFilterPreservesAssistantResponse:
|
|
@@ -106,3 +110,37 @@ class TestToolResultFilterPreservesAssistantResponse:
|
|
|
106
110
|
all_content = " ".join(p.content for p in pairs)
|
|
107
111
|
|
|
108
112
|
assert "48MP" in all_content, "Final assistant synthesis was dropped"
|
|
113
|
+
|
|
114
|
+
def test_payload_accounting_explains_tool_fold_and_pair_counts(self):
|
|
115
|
+
fmt = AnthropicFormat()
|
|
116
|
+
body = {
|
|
117
|
+
"messages": [
|
|
118
|
+
{"role": "user", "content": "Tell me about rare beauty ingredients"},
|
|
119
|
+
{"role": "assistant", "content": [
|
|
120
|
+
{"type": "tool_use", "id": "t1", "name": "web_search", "input": {"q": "rare beauty"}},
|
|
121
|
+
{"type": "tool_use", "id": "t2", "name": "web_search", "input": {"q": "revolution"}},
|
|
122
|
+
]},
|
|
123
|
+
{"role": "user", "content": [
|
|
124
|
+
{"type": "tool_result", "tool_use_id": "t1", "content": "Rare Beauty uses..."},
|
|
125
|
+
{"type": "tool_result", "tool_use_id": "t2", "content": "Revolution uses..."},
|
|
126
|
+
]},
|
|
127
|
+
{"role": "assistant", "content": [
|
|
128
|
+
{"type": "thinking", "thinking": "Comparing the two..."},
|
|
129
|
+
{"type": "text", "text": "Same Caprylic/Capric Triglyceride for lightweight moisture"},
|
|
130
|
+
]},
|
|
131
|
+
{"role": "user", "content": "Why do you think I have medium skin tone?"},
|
|
132
|
+
{"role": "assistant", "content": "Based on the shade recommendations..."},
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
raw = summarize_raw_payload_entries(body["messages"])
|
|
137
|
+
accounting = summarize_payload_accounting(body, fmt, raw_summary=raw)
|
|
138
|
+
|
|
139
|
+
assert accounting["raw_payload_entry_count"] == 6
|
|
140
|
+
assert accounting["raw_tool_use_blocks"] == 2
|
|
141
|
+
assert accounting["raw_tool_result_only_user_entries"] == 1
|
|
142
|
+
assert accounting["assistant_tool_only_entries_folded"] == 1
|
|
143
|
+
assert accounting["tool_result_user_entries_folded"] == 1
|
|
144
|
+
assert accounting["tool_followup_pairs"] == 1
|
|
145
|
+
assert accounting["extracted_history_message_count"] == 4
|
|
146
|
+
assert accounting["extracted_history_pair_count"] == 2
|