virtual-context 0.2.6__tar.gz → 0.2.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.2.6 → virtual_context-0.2.8}/PKG-INFO +5 -2
- {virtual_context-0.2.6 → virtual_context-0.2.8}/README.md +4 -1
- virtual_context-0.2.8/assets/dashboard.png +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/pyproject.toml +1 -1
- virtual_context-0.2.8/tests/test_fill_pass.py +274 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/__init__.py +1 -1
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/conversation_identity.py +6 -2
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/assembler.py +70 -33
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/retriever.py +12 -5
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/formats.py +77 -29
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/message_filter.py +192 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/metrics.py +25 -1
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/server.py +69 -4
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/types.py +6 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/.gitignore +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/LICENSE +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/assets/hero.png +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/models.yaml +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/REGRESSION_MAP.md +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/conftest.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/docker-compose.test.yml +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/haiku/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/haiku/conftest.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/haiku/test_compaction.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/haiku/test_retrieval.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/haiku/test_tagging.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/ollama/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/ollama/conftest.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/ollama/test_compactor.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/ollama/test_pipeline.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/ollama/test_provider.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/ollama/test_tag_generator.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/proxy/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/proxy/test_dashboard_cors.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/proxy/test_metrics.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_assembler.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_backend_integration.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_budget_enforcement.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_cli_init.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_compaction_commit_prune.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_compactor.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_compactor_concurrent.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_composite_store.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_config.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_context_bleed.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_conversation_identity.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_conversation_lifecycle.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_conversation_scoping.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_embedding_tag_generator.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_empty_turn_skip.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_engine_integration.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_engine_lookback.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_engine_state.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_fact_enrichment.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_fact_graph_integration.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_fact_link_checker.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_fact_link_query.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_fact_link_types.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_fact_links_sqlite.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_find_quote.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_format_agnostic.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_headless.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_history_filter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_idf_retrieval.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_ingest_index_integrity.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_longmemeval_auth.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_mcp_server.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_media.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_message_filter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_metrics_persistence.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_model_catalog.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_model_limits.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_monitor.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_multi_instance.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_noop_fact_link_store.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_openrouter_provider.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_paging.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_passthrough_filter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_presets.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_prev_context_leak.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_provider_adapters.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_proxy.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_proxy_dashboard.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_proxy_formats.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_proxy_message_filter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_proxy_session.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_proxy_streaming.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_raw_content.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_recall_all.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_registry_lifecycle.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_request_captures_persistence.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_retriever.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_rrf_scoring.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_segmenter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_semantic_search.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_sender_identity.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_session_cache.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_session_date.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_storage_protocols.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_store_sqlite.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_stub_turn_handling.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_supersession.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_supersession_migration.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tag_canonicalizer.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tag_consolidator.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tag_generator.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tag_splitter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_telemetry.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_telemetry_integration.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tool_loop.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tool_output_interceptor.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tool_result_filter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tool_tags.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_tui.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_turn_grouping.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_turn_tag_index.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_unified_budget.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_upstream_trim.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/tests/test_verb_expansion.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual-context.yaml.example +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/cli/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/cli/main.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/config.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/compaction_pipeline.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/compactor.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/composite_store.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/conversation_store.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/embedding_provider.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/embedding_tag_generator.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/engine_utils.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/fact_query.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/fts_preprocessor.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/hint_builder.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/llm_utils.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/math_utils.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/model_catalog.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/monitor.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/paging_manager.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/protocols.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/provider_adapters.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/quote_search.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/retrieval_assembler.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/retrieval_scoring.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/search_engine.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/segmenter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/semantic_search.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/store.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/tag_canonicalizer.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/tag_consolidator.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/tag_generator.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/tag_scoring.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/tag_splitter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/tagging_pipeline.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/telemetry.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/temporal_resolver.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/tool_loop.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/tool_query.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/core/turn_tag_index.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/data/anthropic-tokenizer/tokenizer.json +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/engine.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/ingest/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/ingest/curator.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/ingest/date_resolver.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/ingest/parsers.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/ingest/supersession.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/mcp/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/mcp/server.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/model_limits.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/openclaw/virtual-context.mjs +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/patterns.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/presets/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/presets/agentic.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/presets/base.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/presets/coding.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/providers/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/providers/anthropic.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/providers/base.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/providers/generic_openai.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/providers/ollama_native.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/_envelope.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/dashboard.html +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/dashboard.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/handlers.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/helpers.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/media.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/multi.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/registry.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/session_cache.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/state.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/static/android-chrome-192x192.png +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/static/android-chrome-512x512.png +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/static/apple-touch-icon.png +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/static/favicon-16x16.png +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/static/favicon-32x32.png +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/static/favicon.ico +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/static/site.webmanifest +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/proxy/tool_output_interceptor.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/storage/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/storage/falkordb.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/storage/filesystem.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/storage/helpers.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/storage/neo4j.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/storage/noop_fact_link_store.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/storage/postgres.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/storage/sqlite.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/token_counter.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/app.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/chat.tcss +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/chat_provider.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/headless.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/modals/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/modals/turn_inspector.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/state.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/widgets/__init__.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/widgets/budget_bar.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/widgets/chat_view.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/widgets/input_box.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/virtual_context/tui/widgets/tag_panel.py +0 -0
- {virtual_context-0.2.6 → virtual_context-0.2.8}/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.
|
|
3
|
+
Version: 0.2.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
|
|
@@ -46,8 +46,11 @@ Requires-Dist: redis>=5.0; extra == 'redis'
|
|
|
46
46
|
Description-Content-Type: text/markdown
|
|
47
47
|
|
|
48
48
|
<p align="center">
|
|
49
|
-
<
|
|
49
|
+
<a href="assets/dashboard.png">
|
|
50
|
+
<img src="assets/dashboard.png" alt="virtual-context dashboard" width="800">
|
|
51
|
+
</a>
|
|
50
52
|
</p>
|
|
53
|
+
<p align="center"><sub>virtual-context cloud - running 3 million virtual token window at 80k actual tokens</sub></p>
|
|
51
54
|
|
|
52
55
|
# virtual-context
|
|
53
56
|
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<
|
|
2
|
+
<a href="assets/dashboard.png">
|
|
3
|
+
<img src="assets/dashboard.png" alt="virtual-context dashboard" width="800">
|
|
4
|
+
</a>
|
|
3
5
|
</p>
|
|
6
|
+
<p align="center"><sub>virtual-context cloud - running 3 million virtual token window at 80k actual tokens</sub></p>
|
|
4
7
|
|
|
5
8
|
# virtual-context
|
|
6
9
|
|
|
Binary file
|
|
@@ -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
|
|
@@ -314,12 +314,16 @@ def resolve_conversation_id(
|
|
|
314
314
|
"""
|
|
315
315
|
if explicit_id and explicit_id.strip():
|
|
316
316
|
explicit_id = explicit_id.strip()
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
# If explicit_id is already a valid UUID (e.g. from a vc:conversation
|
|
318
|
+
# marker in a previous response), use it as-is. The marker IS the
|
|
319
|
+
# canonical conversation ID — re-hashing it with format_name would
|
|
320
|
+
# break the round-trip and create orphan conversations.
|
|
319
321
|
try:
|
|
320
322
|
_uuid.UUID(explicit_id)
|
|
321
323
|
return explicit_id
|
|
322
324
|
except ValueError:
|
|
325
|
+
if format_name:
|
|
326
|
+
return _candidate_to_uuid("explicit_id", f"{format_name}:{explicit_id}")
|
|
323
327
|
return _candidate_to_uuid("explicit_id", explicit_id)
|
|
324
328
|
|
|
325
329
|
if not body:
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import fnmatch
|
|
6
6
|
import logging
|
|
7
|
+
import re
|
|
7
8
|
from datetime import datetime, timezone
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Callable
|
|
@@ -24,6 +25,60 @@ from ..types import (
|
|
|
24
25
|
)
|
|
25
26
|
|
|
26
27
|
|
|
28
|
+
def format_tag_section(
|
|
29
|
+
tag: str,
|
|
30
|
+
summaries: list["StoredSummary"],
|
|
31
|
+
store: "ContextStore | None" = None,
|
|
32
|
+
conversation_id: str = "",
|
|
33
|
+
) -> str:
|
|
34
|
+
"""Render a tag section in the canonical <virtual-context> format.
|
|
35
|
+
|
|
36
|
+
Shared by the assembler and the fill pass. Tool-hint enrichment is
|
|
37
|
+
optional (gracefully degrades if store is None).
|
|
38
|
+
"""
|
|
39
|
+
if not summaries:
|
|
40
|
+
return ""
|
|
41
|
+
|
|
42
|
+
summaries = sorted(summaries, key=lambda s: s.start_timestamp)
|
|
43
|
+
|
|
44
|
+
all_tags = sorted({t for s in summaries for t in s.tags})
|
|
45
|
+
tags_attr = ", ".join(all_tags) if all_tags else tag
|
|
46
|
+
|
|
47
|
+
total = len(summaries)
|
|
48
|
+
summary_texts: list[str] = []
|
|
49
|
+
for idx, s in enumerate(summaries, 1):
|
|
50
|
+
prefix = f"[{idx}/{total}]"
|
|
51
|
+
session = s.metadata.session_date
|
|
52
|
+
if session:
|
|
53
|
+
prefix += f" [{session}]"
|
|
54
|
+
text = f"{prefix}\n{s.summary}"
|
|
55
|
+
tool_tags = [t for t in s.tags if t.startswith("tool_")]
|
|
56
|
+
if tool_tags:
|
|
57
|
+
text += f'\n[tool output truncated — vc_expand_topic("{tool_tags[0]}") for full result]'
|
|
58
|
+
# Optional tool hint enrichment
|
|
59
|
+
if store and conversation_id and s.ref:
|
|
60
|
+
get_refs = getattr(store, "get_tool_outputs_for_segment", None)
|
|
61
|
+
get_names = getattr(store, "get_tool_names_for_segment", None)
|
|
62
|
+
if callable(get_refs) and callable(get_names):
|
|
63
|
+
try:
|
|
64
|
+
refs = get_refs(conversation_id, s.ref)
|
|
65
|
+
if refs:
|
|
66
|
+
names = get_names(conversation_id, s.ref)
|
|
67
|
+
names_str = ", ".join(names) if names else "tools"
|
|
68
|
+
text += f"\n[Tools: {names_str} -- {len(refs)} outputs restorable via vc_restore_tool]"
|
|
69
|
+
except Exception:
|
|
70
|
+
pass
|
|
71
|
+
summary_texts.append(text)
|
|
72
|
+
|
|
73
|
+
body = "\n\n---\n\n".join(summary_texts)
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
f'<virtual-context tags="{tags_attr}" segments="{len(summaries)}">\n'
|
|
77
|
+
f"{body}\n"
|
|
78
|
+
f"</virtual-context>"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
27
82
|
class ContextAssembler:
|
|
28
83
|
"""Assemble enriched context within token budget.
|
|
29
84
|
|
|
@@ -275,6 +330,18 @@ class ContextAssembler:
|
|
|
275
330
|
|
|
276
331
|
total_tokens = core_tokens + tag_tokens + facts_tokens_actual + conv_tokens
|
|
277
332
|
|
|
333
|
+
# Compute presented_tags from rendered <virtual-context tags="..."> headers
|
|
334
|
+
_vc_tags_re = re.compile(r'<virtual-context\s+tags="([^"]*)"')
|
|
335
|
+
_presented_tags: set[str] = set()
|
|
336
|
+
for _section_text in tag_sections.values():
|
|
337
|
+
for _m in _vc_tags_re.finditer(_section_text):
|
|
338
|
+
for _t in _m.group(1).split(", "):
|
|
339
|
+
_t = _t.strip()
|
|
340
|
+
if _t:
|
|
341
|
+
_presented_tags.add(_t)
|
|
342
|
+
# Also include section keys as baseline for edge cases
|
|
343
|
+
_presented_tags.update(tag_sections.keys())
|
|
344
|
+
|
|
278
345
|
return AssembledContext(
|
|
279
346
|
core_context=core,
|
|
280
347
|
tag_sections=tag_sections,
|
|
@@ -291,6 +358,8 @@ class ContextAssembler:
|
|
|
291
358
|
prepend_text=prepend_text,
|
|
292
359
|
presented_segment_refs=presented_refs,
|
|
293
360
|
selected_facts=selected_facts,
|
|
361
|
+
retrieval_result=retrieval_result,
|
|
362
|
+
presented_tags=_presented_tags,
|
|
294
363
|
)
|
|
295
364
|
|
|
296
365
|
def _format_facts(self, facts: list[Fact], max_tokens: int) -> str:
|
|
@@ -313,39 +382,7 @@ class ContextAssembler:
|
|
|
313
382
|
return "<facts>\n" + "\n".join(lines) + "\n</facts>"
|
|
314
383
|
|
|
315
384
|
def _format_tag_section(self, tag: str, summaries: list[StoredSummary]) -> str:
|
|
316
|
-
|
|
317
|
-
return ""
|
|
318
|
-
|
|
319
|
-
# Sort chronologically so reader sees old → new progression
|
|
320
|
-
summaries = sorted(summaries, key=lambda s: s.start_timestamp)
|
|
321
|
-
|
|
322
|
-
all_tags = sorted({t for s in summaries for t in s.tags})
|
|
323
|
-
tags_attr = ", ".join(all_tags) if all_tags else tag
|
|
324
|
-
|
|
325
|
-
# Prefix each summary with sequence number and optional session date
|
|
326
|
-
total = len(summaries)
|
|
327
|
-
summary_texts: list[str] = []
|
|
328
|
-
for idx, s in enumerate(summaries, 1):
|
|
329
|
-
prefix = f"[{idx}/{total}]"
|
|
330
|
-
session = s.metadata.session_date
|
|
331
|
-
if session:
|
|
332
|
-
prefix += f" [{session}]"
|
|
333
|
-
text = f"{prefix}\n{s.summary}"
|
|
334
|
-
# Expansion hint for tool segments
|
|
335
|
-
tool_tags = [t for t in s.tags if t.startswith("tool_")]
|
|
336
|
-
if tool_tags:
|
|
337
|
-
text += f'\n[tool output truncated — vc_expand_topic("{tool_tags[0]}") for full result]'
|
|
338
|
-
# Tool-enriched segment summary: append tool hint at assembly time
|
|
339
|
-
text = self._maybe_append_tool_hint(text, s.ref)
|
|
340
|
-
summary_texts.append(text)
|
|
341
|
-
|
|
342
|
-
body = "\n\n---\n\n".join(summary_texts)
|
|
343
|
-
|
|
344
|
-
return (
|
|
345
|
-
f'<virtual-context tags="{tags_attr}" segments="{len(summaries)}">\n'
|
|
346
|
-
f"{body}\n"
|
|
347
|
-
f"</virtual-context>"
|
|
348
|
-
)
|
|
385
|
+
return format_tag_section(tag, summaries, store=self._store, conversation_id=self._conversation_id)
|
|
349
386
|
|
|
350
387
|
def _format_segments_section(self, tag: str, segments: list[StoredSegment]) -> str:
|
|
351
388
|
if not segments:
|
|
@@ -120,6 +120,7 @@ class ContextRetriever:
|
|
|
120
120
|
"""
|
|
121
121
|
start_time = time.monotonic()
|
|
122
122
|
active_tags = set(current_active_tags or [])
|
|
123
|
+
overflow: list[StoredSummary] = []
|
|
123
124
|
|
|
124
125
|
# Tag the inbound message — scope vocabulary to current conversation
|
|
125
126
|
store_tags = [ts.tag for ts in self.store.get_all_tags(
|
|
@@ -207,6 +208,7 @@ class ContextRetriever:
|
|
|
207
208
|
summaries=floor_summaries,
|
|
208
209
|
total_tokens=floor_tokens,
|
|
209
210
|
facts=self._fetch_all_facts(),
|
|
211
|
+
overflow_summaries=overflow,
|
|
210
212
|
retrieval_metadata={
|
|
211
213
|
"elapsed_ms": round(elapsed * 1000, 1),
|
|
212
214
|
"tags_from_message": tag_result.tags,
|
|
@@ -226,6 +228,7 @@ class ContextRetriever:
|
|
|
226
228
|
summaries=[],
|
|
227
229
|
total_tokens=0,
|
|
228
230
|
facts=self._fetch_all_facts(),
|
|
231
|
+
overflow_summaries=overflow,
|
|
229
232
|
retrieval_metadata={
|
|
230
233
|
"elapsed_ms": round(elapsed * 1000, 1),
|
|
231
234
|
"tags_from_message": tag_result.tags,
|
|
@@ -293,15 +296,18 @@ class ContextRetriever:
|
|
|
293
296
|
selected: list[StoredSummary] = []
|
|
294
297
|
selected_refs: set[str] = set()
|
|
295
298
|
total_tokens = 0
|
|
299
|
+
_overflow_cap = 50
|
|
296
300
|
for summary in all_summaries:
|
|
297
301
|
if summary.ref in selected_refs:
|
|
298
302
|
continue
|
|
299
303
|
if total_tokens + summary.summary_tokens > token_budget:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
304
|
+
if len(overflow) < _overflow_cap:
|
|
305
|
+
overflow.append(summary)
|
|
306
|
+
logger.debug(
|
|
307
|
+
"Retriever: '%s' OVERFLOW (%dt, %d candidates)",
|
|
308
|
+
summary.primary_tag, summary.summary_tokens, len(overflow),
|
|
309
|
+
)
|
|
310
|
+
continue
|
|
305
311
|
selected.append(summary)
|
|
306
312
|
selected_refs.add(summary.ref)
|
|
307
313
|
total_tokens += summary.summary_tokens
|
|
@@ -401,6 +407,7 @@ class ContextRetriever:
|
|
|
401
407
|
full_detail=full_detail,
|
|
402
408
|
total_tokens=total_tokens,
|
|
403
409
|
facts=facts,
|
|
410
|
+
overflow_summaries=overflow,
|
|
404
411
|
retrieval_metadata=retrieval_metadata,
|
|
405
412
|
retrieval_scores=retrieval_scores,
|
|
406
413
|
query_embedding=query_embedding,
|